diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2024-06-17 09:16:28 +0300 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2024-06-18 14:01:28 +0300 |
| commit | 1dae45c58d7eabeda21ef490d712915890bf6cff (patch) | |
| tree | 62c473ab1662a1161ed522517ea57b7bd8db340c /src | |
| parent | 997119c44338ad96a40b4a1d6e958f77062a37ef (diff) | |
| download | niri-1dae45c58d7eabeda21ef490d712915890bf6cff.tar.gz niri-1dae45c58d7eabeda21ef490d712915890bf6cff.tar.bz2 niri-1dae45c58d7eabeda21ef490d712915890bf6cff.zip | |
Refactor layout to fractional-logical
Lets borders, gaps, and everything else stay pixel-perfect even with
fractional scale. Allows setting fractional border widths, gaps,
struts.
See the new wiki .md for more details.
Diffstat (limited to 'src')
| -rw-r--r-- | src/handlers/xdg_shell.rs | 41 | ||||
| -rw-r--r-- | src/input/mod.rs | 8 | ||||
| -rw-r--r-- | src/input/resize_grab.rs | 4 | ||||
| -rw-r--r-- | src/input/view_offset_grab.rs | 4 | ||||
| -rw-r--r-- | src/layout/closing_window.rs | 25 | ||||
| -rw-r--r-- | src/layout/focus_ring.rs | 73 | ||||
| -rw-r--r-- | src/layout/mod.rs | 204 | ||||
| -rw-r--r-- | src/layout/monitor.rs | 56 | ||||
| -rw-r--r-- | src/layout/opening_window.rs | 18 | ||||
| -rw-r--r-- | src/layout/tile.rs | 221 | ||||
| -rw-r--r-- | src/layout/workspace.rs | 560 | ||||
| -rw-r--r-- | src/niri.rs | 39 | ||||
| -rw-r--r-- | src/render_helpers/border.rs | 20 | ||||
| -rw-r--r-- | src/render_helpers/clipped_surface.rs | 16 | ||||
| -rw-r--r-- | src/render_helpers/damage.rs | 8 | ||||
| -rw-r--r-- | src/render_helpers/mod.rs | 24 | ||||
| -rw-r--r-- | src/render_helpers/resize.rs | 16 | ||||
| -rw-r--r-- | src/render_helpers/shader_element.rs | 18 | ||||
| -rw-r--r-- | src/render_helpers/snapshot.rs | 6 | ||||
| -rw-r--r-- | src/render_helpers/surface.rs | 6 | ||||
| -rw-r--r-- | src/ui/config_error_notification.rs | 2 | ||||
| -rw-r--r-- | src/utils/mod.rs | 20 | ||||
| -rw-r--r-- | src/window/mapped.rs | 50 |
23 files changed, 847 insertions, 592 deletions
diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs index c1e29ea4..3e6234fa 100644 --- a/src/handlers/xdg_shell.rs +++ b/src/handlers/xdg_shell.rs @@ -788,9 +788,9 @@ impl State { // window can be scrolled to both edges of the screen), but within the whole monitor's // height. let mut target = - Rectangle::from_loc_and_size((0, 0), (window_geo.size.w, output_geo.size.h)); + Rectangle::from_loc_and_size((0, 0), (window_geo.size.w, output_geo.size.h)).to_f64(); target.loc -= self.niri.layout.window_loc(window).unwrap(); - target.loc -= get_popup_toplevel_coords(popup); + target.loc -= get_popup_toplevel_coords(popup).to_f64(); self.position_popup_within_rect(popup, target); } @@ -813,10 +813,10 @@ impl State { target.loc -= layer_geo.loc; target.loc -= get_popup_toplevel_coords(popup); - self.position_popup_within_rect(popup, target); + self.position_popup_within_rect(popup, target.to_f64()); } - fn position_popup_within_rect(&self, popup: &PopupKind, target: Rectangle<i32, Logical>) { + fn position_popup_within_rect(&self, popup: &PopupKind, target: Rectangle<f64, Logical>) { match popup { PopupKind::Xdg(popup) => { popup.with_pending_state(|state| { @@ -826,28 +826,29 @@ impl State { PopupKind::InputMethod(popup) => { let text_input_rectangle = popup.text_input_rectangle(); let mut bbox = - utils::bbox_from_surface_tree(popup.wl_surface(), text_input_rectangle.loc); + utils::bbox_from_surface_tree(popup.wl_surface(), text_input_rectangle.loc) + .to_f64(); // Position bbox horizontally first. let overflow_x = (bbox.loc.x + bbox.size.w) - (target.loc.x + target.size.w); - if overflow_x > 0 { + if overflow_x > 0. { bbox.loc.x -= overflow_x; } // Ensure that the popup starts within the window. - bbox.loc.x = bbox.loc.x.max(target.loc.x); + bbox.loc.x = f64::max(bbox.loc.x, target.loc.x); // Try to position IME popup below the text input rectangle. let mut below = bbox; - below.loc.y += text_input_rectangle.size.h; + below.loc.y += f64::from(text_input_rectangle.size.h); let mut above = bbox; above.loc.y -= bbox.size.h; if target.loc.y + target.size.h >= below.loc.y + below.size.h { - popup.set_location(below.loc); + popup.set_location(below.loc.to_i32_round()); } else { - popup.set_location(above.loc); + popup.set_location(above.loc.to_i32_round()); } } } @@ -907,25 +908,25 @@ impl State { fn unconstrain_with_padding( positioner: PositionerState, - target: Rectangle<i32, Logical>, + target: Rectangle<f64, Logical>, ) -> Rectangle<i32, Logical> { // Try unconstraining with a small padding first which looks nicer, then if it doesn't fit try // unconstraining without padding. - const PADDING: i32 = 8; + const PADDING: f64 = 8.; let mut padded = target; - if PADDING * 2 < padded.size.w { + if PADDING * 2. < padded.size.w { padded.loc.x += PADDING; - padded.size.w -= PADDING * 2; + padded.size.w -= PADDING * 2.; } - if PADDING * 2 < padded.size.h { + if PADDING * 2. < padded.size.h { padded.loc.y += PADDING; - padded.size.h -= PADDING * 2; + padded.size.h -= PADDING * 2.; } // No padding, so just unconstrain with the original target. if padded == target { - return positioner.get_unconstrained_geometry(target); + return positioner.get_unconstrained_geometry(target.to_i32_round()); } // Do not try to resize to fit the padded target rectangle. @@ -937,13 +938,13 @@ fn unconstrain_with_padding( .constraint_adjustment .remove(ConstraintAdjustment::ResizeY); - let geo = no_resize.get_unconstrained_geometry(padded); - if padded.contains_rect(geo) { + let geo = no_resize.get_unconstrained_geometry(padded.to_i32_round()); + if padded.contains_rect(geo.to_f64()) { return geo; } // Could not unconstrain into the padded target, so resort to the regular one. - positioner.get_unconstrained_geometry(target) + positioner.get_unconstrained_geometry(target.to_i32_round()) } pub fn add_mapped_toplevel_pre_commit_hook(toplevel: &ToplevelSurface) -> HookId { diff --git a/src/input/mod.rs b/src/input/mod.rs index acb08e1c..d0e1c55b 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -935,7 +935,7 @@ impl State { // Check if we have an active pointer constraint. let mut pointer_confined = None; if let Some(focus) = &self.niri.pointer_focus.surface { - let pos_within_surface = pos.to_i32_round() - focus.1; + let pos_within_surface = pos - focus.1; let mut pointer_locked = false; with_pointer_constraint(&focus.0, &pointer, |constraint| { @@ -946,7 +946,7 @@ impl State { // Constraint does not apply if not within region. if let Some(region) = constraint.region() { - if !region.contains(pos_within_surface) { + if !region.contains(pos_within_surface.to_i32_round()) { return; } } @@ -1036,8 +1036,8 @@ impl State { // Prevent the pointer from leaving the confine region, if any. if let Some(region) = region { - let new_pos_within_surface = new_pos.to_i32_round() - focus_surface.1; - if !region.contains(new_pos_within_surface) { + let new_pos_within_surface = new_pos - focus_surface.1; + if !region.contains(new_pos_within_surface.to_i32_round()) { prevent = true; } } diff --git a/src/input/resize_grab.rs b/src/input/resize_grab.rs index 38483ca6..535bae32 100644 --- a/src/input/resize_grab.rs +++ b/src/input/resize_grab.rs @@ -35,7 +35,7 @@ impl PointerGrab<State> for ResizeGrab { &mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>, - _focus: Option<(<State as SeatHandler>::PointerFocus, Point<i32, Logical>)>, + _focus: Option<(<State as SeatHandler>::PointerFocus, Point<f64, Logical>)>, event: &MotionEvent, ) { // While the grab is active, no client has pointer focus. @@ -60,7 +60,7 @@ impl PointerGrab<State> for ResizeGrab { &mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>, - _focus: Option<(<State as SeatHandler>::PointerFocus, Point<i32, Logical>)>, + _focus: Option<(<State as SeatHandler>::PointerFocus, Point<f64, Logical>)>, event: &RelativeMotionEvent, ) { // While the grab is active, no client has pointer focus. diff --git a/src/input/view_offset_grab.rs b/src/input/view_offset_grab.rs index 4e2d2785..b4f9f96c 100644 --- a/src/input/view_offset_grab.rs +++ b/src/input/view_offset_grab.rs @@ -46,7 +46,7 @@ impl PointerGrab<State> for ViewOffsetGrab { &mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>, - _focus: Option<(<State as SeatHandler>::PointerFocus, Point<i32, Logical>)>, + _focus: Option<(<State as SeatHandler>::PointerFocus, Point<f64, Logical>)>, event: &MotionEvent, ) { // While the grab is active, no client has pointer focus. @@ -74,7 +74,7 @@ impl PointerGrab<State> for ViewOffsetGrab { &mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>, - _focus: Option<(<State as SeatHandler>::PointerFocus, Point<i32, Logical>)>, + _focus: Option<(<State as SeatHandler>::PointerFocus, Point<f64, Logical>)>, event: &RelativeMotionEvent, ) { // While the grab is active, no client has pointer focus. diff --git a/src/layout/closing_window.rs b/src/layout/closing_window.rs index 8945d2aa..06b5927c 100644 --- a/src/layout/closing_window.rs +++ b/src/layout/closing_window.rs @@ -34,10 +34,10 @@ pub struct ClosingWindow { block_out_from: Option<BlockOutFrom>, /// Size of the window geometry. - geo_size: Size<i32, Logical>, + geo_size: Size<f64, Logical>, /// Position in the workspace. - pos: Point<i32, Logical>, + pos: Point<f64, Logical>, /// How much the texture should be offset. buffer_offset: Point<f64, Logical>, @@ -64,8 +64,8 @@ impl ClosingWindow { renderer: &mut GlesRenderer, snapshot: RenderSnapshot<E, E>, scale: Scale<f64>, - geo_size: Size<i32, Logical>, - pos: Point<i32, Logical>, + geo_size: Size<f64, Logical>, + pos: Point<f64, Logical>, anim: Animation, ) -> anyhow::Result<Self> { let _span = tracy_client::span!("ClosingWindow::new"); @@ -123,7 +123,7 @@ impl ClosingWindow { pub fn render( &self, renderer: &mut GlesRenderer, - view_rect: Rectangle<i32, Logical>, + view_rect: Rectangle<f64, Logical>, scale: Scale<f64>, target: RenderTarget, ) -> ClosingWindowRenderElement { @@ -140,7 +140,12 @@ impl ClosingWindow { let area_loc = Vec2::new(view_rect.loc.x as f32, view_rect.loc.y as f32); let area_size = Vec2::new(view_rect.size.w as f32, view_rect.size.h as f32); - let geo_loc = Vec2::new(self.pos.x as f32, self.pos.y as f32); + // Round to physical pixels relative to the view position. This is similar to what + // happens when rendering normal windows. + let relative = self.pos - view_rect.loc; + let pos = view_rect.loc + relative.to_physical_precise_round(scale).to_logical(scale); + + let geo_loc = Vec2::new(pos.x as f32, pos.y as f32); let geo_size = Vec2::new(self.geo_size.w as f32, self.geo_size.h as f32); let input_to_geo = Mat3::from_scale(area_size / geo_size) @@ -171,7 +176,7 @@ impl ClosingWindow { HashMap::from([(String::from("niri_tex"), buffer.texture().clone())]), Kind::Unspecified, ) - .with_location(Point::from((0, 0))) + .with_location(Point::from((0., 0.))) .into(); } @@ -186,15 +191,15 @@ impl ClosingWindow { let elem = PrimaryGpuTextureRenderElement(elem); - let center = self.geo_size.to_point().to_f64().downscale(2.); + let center = self.geo_size.to_point().downscale(2.); let elem = RescaleRenderElement::from_element( elem, (center - offset).to_physical_precise_round(scale), ((1. - clamped_progress) / 5. + 0.8).max(0.), ); - let mut location = self.pos.to_f64() + offset; - location.x -= view_rect.loc.x as f64; + let mut location = self.pos + offset; + location.x -= view_rect.loc.x; let elem = RelocateRenderElement::from_element( elem, location.to_physical_precise_round(scale), diff --git a/src/layout/focus_ring.rs b/src/layout/focus_ring.rs index b198e838..e7c0388b 100644 --- a/src/layout/focus_ring.rs +++ b/src/layout/focus_ring.rs @@ -1,23 +1,22 @@ -use std::cmp::{max, min}; use std::iter::zip; use arrayvec::ArrayVec; use niri_config::{CornerRadius, Gradient, GradientRelativeTo}; -use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; use smithay::backend::renderer::element::Kind; -use smithay::utils::{Logical, Point, Rectangle, Scale, Size}; +use smithay::utils::{Logical, Point, Rectangle, Size}; use crate::niri_render_elements; use crate::render_helpers::border::BorderRenderElement; use crate::render_helpers::renderer::NiriRenderer; +use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement}; #[derive(Debug)] pub struct FocusRing { buffers: [SolidColorBuffer; 8], - locations: [Point<i32, Logical>; 8], - sizes: [Size<i32, Logical>; 8], + locations: [Point<f64, Logical>; 8], + sizes: [Size<f64, Logical>; 8], borders: [BorderRenderElement; 8], - full_size: Size<i32, Logical>, + full_size: Size<f64, Logical>, is_border: bool, use_border_shader: bool, config: niri_config::FocusRing, @@ -56,14 +55,15 @@ impl FocusRing { pub fn update_render_elements( &mut self, - win_size: Size<i32, Logical>, + win_size: Size<f64, Logical>, is_active: bool, is_border: bool, - view_rect: Rectangle<i32, Logical>, + view_rect: Rectangle<f64, Logical>, radius: CornerRadius, + scale: f64, ) { - let width = i32::from(self.config.width); - self.full_size = win_size + Size::from((width * 2, width * 2)); + let width = self.config.width.0; + self.full_size = win_size + Size::from((width, width)).upscale(2.); let color = if is_active { self.config.active_color @@ -107,39 +107,48 @@ impl FocusRing { 0. }; + let ceil = |logical: f64| (logical * scale).ceil() / scale; + + // All of this stuff should end up aligned to physical pixels because: + // * Window size and border width are rounded to physical pixels before being passed to this + // function. + // * We will ceil the corner radii below. + // * We do not divide anything, only add, subtract and multiply by integers. + // * At rendering time, tile positions are rounded to physical pixels. + if is_border { - let top_left = max(width, radius.top_left.ceil() as i32); - let top_right = min( + let top_left = f64::max(width, ceil(f64::from(radius.top_left))); + let top_right = f64::min( self.full_size.w - top_left, - max(width, radius.top_right.ceil() as i32), + f64::max(width, ceil(f64::from(radius.top_right))), ); - let bottom_left = min( + let bottom_left = f64::min( self.full_size.h - top_left, - max(width, radius.bottom_left.ceil() as i32), + f64::max(width, ceil(f64::from(radius.bottom_left))), ); - let bottom_right = min( + let bottom_right = f64::min( self.full_size.h - top_right, - min( + f64::min( self.full_size.w - bottom_left, - max(width, radius.bottom_right.ceil() as i32), + f64::max(width, ceil(f64::from(radius.bottom_right))), ), ); // Top edge. - self.sizes[0] = Size::from((win_size.w + width * 2 - top_left - top_right, width)); + self.sizes[0] = Size::from((win_size.w + width * 2. - top_left - top_right, width)); self.locations[0] = Point::from((-width + top_left, -width)); // Bottom edge. self.sizes[1] = - Size::from((win_size.w + width * 2 - bottom_left - bottom_right, width)); + Size::from((win_size.w + width * 2. - bottom_left - bottom_right, width)); self.locations[1] = Point::from((-width + bottom_left, win_size.h)); // Left edge. - self.sizes[2] = Size::from((width, win_size.h + width * 2 - top_left - bottom_left)); + self.sizes[2] = Size::from((width, win_size.h + width * 2. - top_left - bottom_left)); self.locations[2] = Point::from((-width, -width + top_left)); // Right edge. - self.sizes[3] = Size::from((width, win_size.h + width * 2 - top_right - bottom_right)); + self.sizes[3] = Size::from((width, win_size.h + width * 2. - top_right - bottom_right)); self.locations[3] = Point::from((win_size.w, -width + top_right)); // Top-left corner. @@ -203,8 +212,7 @@ impl FocusRing { pub fn render( &self, renderer: &mut impl NiriRenderer, - location: Point<i32, Logical>, - scale: Scale<f64>, + location: Point<f64, Logical>, ) -> impl Iterator<Item = FocusRingRenderElement> { let mut rv = ArrayVec::<_, 8>::new(); @@ -215,24 +223,17 @@ impl FocusRing { let border_width = -self.locations[0].y; // If drawing as a border with width = 0, then there's nothing to draw. - if self.is_border && border_width == 0 { + if self.is_border && border_width == 0. { return rv.into_iter(); } let has_border_shader = BorderRenderElement::has_shader(renderer); - let mut push = |buffer, border: &BorderRenderElement, location: Point<i32, Logical>| { + let mut push = |buffer, border: &BorderRenderElement, location: Point<f64, Logical>| { let elem = if self.use_border_shader && has_border_shader { border.clone().with_location(location).into() } else { - SolidColorRenderElement::from_buffer( - buffer, - location.to_physical_precise_round(scale), - scale, - 1., - Kind::Unspecified, - ) - .into() + SolidColorRenderElement::from_buffer(buffer, location, 1., Kind::Unspecified).into() }; rv.push(elem); }; @@ -252,8 +253,8 @@ impl FocusRing { rv.into_iter() } - pub fn width(&self) -> i32 { - self.config.width.into() + pub fn width(&self) -> f64 { + self.config.width.0 } pub fn is_off(&self) -> bool { diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 135f694f..ffc5e0e8 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -34,9 +34,8 @@ use std::mem; use std::rc::Rc; use std::time::Duration; -use niri_config::{CenterFocusedColumn, Config, Struts, Workspace as WorkspaceConfig}; +use niri_config::{CenterFocusedColumn, Config, FloatOrInt, Struts, Workspace as WorkspaceConfig}; use niri_ipc::SizeChange; -use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement; use smithay::backend::renderer::element::Id; use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture}; @@ -50,9 +49,10 @@ use self::workspace::{compute_working_area, Column, ColumnWidth, OutputId, Works use crate::niri_render_elements; use crate::render_helpers::renderer::NiriRenderer; use crate::render_helpers::snapshot::RenderSnapshot; +use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement}; use crate::render_helpers::texture::TextureBuffer; use crate::render_helpers::{BakedBuffer, RenderTarget, SplitElements}; -use crate::utils::{output_size, ResizeEdge}; +use crate::utils::{output_size, round_logical_in_physical_max1, ResizeEdge}; use crate::window::ResolvedWindowRules; pub mod closing_window; @@ -63,7 +63,7 @@ pub mod tile; pub mod workspace; /// Size changes up to this many pixels don't animate. -pub const RESIZE_ANIMATION_THRESHOLD: i32 = 10; +pub const RESIZE_ANIMATION_THRESHOLD: f64 = 10.; niri_render_elements! { LayoutElementRenderElement<R> => { @@ -110,7 +110,7 @@ pub trait LayoutElement { fn render<R: NiriRenderer>( &self, renderer: &mut R, - location: Point<i32, Logical>, + location: Point<f64, Logical>, scale: Scale<f64>, alpha: f32, target: RenderTarget, @@ -120,7 +120,7 @@ pub trait LayoutElement { fn render_normal<R: NiriRenderer>( &self, renderer: &mut R, - location: Point<i32, Logical>, + location: Point<f64, Logical>, scale: Scale<f64>, alpha: f32, target: RenderTarget, @@ -132,7 +132,7 @@ pub trait LayoutElement { fn render_popups<R: NiriRenderer>( &self, renderer: &mut R, - location: Point<i32, Logical>, + location: Point<f64, Logical>, scale: Scale<f64>, alpha: f32, target: RenderTarget, @@ -206,10 +206,10 @@ enum MonitorSet<W: LayoutElement> { }, } -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct Options { /// Padding around windows in logical pixels. - pub gaps: i32, + pub gaps: f64, /// Extra padding around the working area in logical pixels. pub struts: Struts, pub focus_ring: niri_config::FocusRing, @@ -225,7 +225,7 @@ pub struct Options { impl Default for Options { fn default() -> Self { Self { - gaps: 16, + gaps: 16., struts: Default::default(), focus_ring: Default::default(), border: Default::default(), @@ -265,7 +265,7 @@ impl Options { .unwrap_or(Some(ColumnWidth::Proportion(0.5))); Self { - gaps: layout.gaps.into(), + gaps: layout.gaps.0, struts: layout.struts, focus_ring: layout.focus_ring, border: layout.border, @@ -275,6 +275,16 @@ impl Options { animations: config.animations.clone(), } } + + fn adjusted_for_scale(mut self, scale: f64) -> Self { + let round = |logical: f64| round_logical_in_physical_max1(scale, logical); + + self.gaps = round(self.gaps); + self.focus_ring.width = FloatOrInt(round(self.focus_ring.width.0)); + self.border.width = FloatOrInt(round(self.border.width.0)); + + self + } } impl<W: LayoutElement> Layout<W> { @@ -486,12 +496,12 @@ impl<W: LayoutElement> Layout<W> { width: Option<ColumnWidth>, is_full_width: bool, ) -> Option<&Output> { - let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(window.size().w)); + let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(f64::from(window.size().w))); if let ColumnWidth::Fixed(w) = &mut width { let rules = window.rules(); let border_config = rules.border.resolve_against(self.options.border); if !border_config.off { - *w += border_config.width as i32 * 2; + *w += border_config.width.0 * 2.; } } @@ -575,12 +585,12 @@ impl<W: LayoutElement> Layout<W> { width: Option<ColumnWidth>, is_full_width: bool, ) -> Option<&Output> { - let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(window.size().w)); + let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(f64::from(window.size().w))); if let ColumnWidth::Fixed(w) = &mut width { let rules = window.rules(); let border_config = rules.border.resolve_against(self.options.border); if !border_config.off { - *w += border_config.width as i32 * 2; + *w += border_config.width.0 * 2.; } } @@ -633,12 +643,12 @@ impl<W: LayoutElement> Layout<W> { width: Option<ColumnWidth>, is_full_width: bool, ) -> Option<&Output> { - let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(window.size().w)); + let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(f64::from(window.size().w))); if let ColumnWidth::Fixed(w) = &mut width { let rules = window.rules(); let border_config = rules.border.resolve_against(self.options.border); if !border_config.off { - *w += border_config.width as i32 * 2; + *w += border_config.width.0 * 2.; } } @@ -671,12 +681,12 @@ impl<W: LayoutElement> Layout<W> { width: Option<ColumnWidth>, is_full_width: bool, ) { - let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(window.size().w)); + let mut width = width.unwrap_or_else(|| ColumnWidth::Fixed(f64::from(window.size().w))); if let ColumnWidth::Fixed(w) = &mut width { let rules = window.rules(); let border_config = rules.border.resolve_against(self.options.border); if !border_config.off { - *w += border_config.width as i32 * 2; + *w += border_config.width.0 * 2.; } } @@ -887,7 +897,7 @@ impl<W: LayoutElement> Layout<W> { None } - pub fn window_loc(&self, window: &W::Id) -> Option<Point<i32, Logical>> { + pub fn window_loc(&self, window: &W::Id) -> Option<Point<f64, Logical>> { match &self.monitor_set { MonitorSet::Normal { monitors, .. } => { for mon in monitors { @@ -1440,7 +1450,7 @@ impl<W: LayoutElement> Layout<W> { &self, output: &Output, pos_within_output: Point<f64, Logical>, - ) -> Option<(&W, Option<Point<i32, Logical>>)> { + ) -> Option<(&W, Option<Point<f64, Logical>>)> { let MonitorSet::Normal { monitors, .. } = &self.monitor_set else { return None; }; @@ -1485,8 +1495,15 @@ impl<W: LayoutElement> Layout<W> { ); assert_eq!( - workspace.options, self.options, - "workspace options must be synchronized with layout" + workspace.base_options, self.options, + "workspace base options must be synchronized with layout" + ); + + let options = Options::clone(&workspace.base_options) + .adjusted_for_scale(workspace.scale().fractional_scale()); + assert_eq!( + &*workspace.options, &options, + "workspace options must be base options adjusted for workspace scale" ); assert!( @@ -1589,10 +1606,17 @@ impl<W: LayoutElement> Layout<W> { for workspace in &monitor.workspaces { assert_eq!( - workspace.options, self.options, + workspace.base_options, self.options, "workspace options must be synchronized with layout" ); + let options = Options::clone(&workspace.base_options) + .adjusted_for_scale(workspace.scale().fractional_scale()); + assert_eq!( + &*workspace.options, &options, + "workspace options must be base options adjusted for workspace scale" + ); + assert!( seen_workspace_id.insert(workspace.id()), "workspace id must be unique" @@ -2368,13 +2392,14 @@ impl<W: LayoutElement> Default for MonitorSet<W> { mod tests { use std::cell::Cell; - use niri_config::WorkspaceName; + use niri_config::{FloatOrInt, WorkspaceName}; use proptest::prelude::*; use proptest_derive::Arbitrary; use smithay::output::{Mode, PhysicalProperties, Subpixel}; use smithay::utils::Rectangle; use super::*; + use crate::utils::round_logical_in_physical; impl<W: LayoutElement> Default for Layout<W> { fn default() -> Self { @@ -2459,7 +2484,7 @@ mod tests { fn render<R: NiriRenderer>( &self, _renderer: &mut R, - _location: Point<i32, Logical>, + _location: Point<f64, Logical>, _scale: Scale<f64>, _alpha: f32, _target: RenderTarget, @@ -2595,9 +2620,19 @@ mod tests { ] } + fn arbitrary_scale() -> impl Strategy<Value = f64> { + prop_oneof![Just(1.), Just(1.5), Just(2.),] + } + #[derive(Debug, Clone, Copy, Arbitrary)] enum Op { AddOutput(#[proptest(strategy = "1..=5usize")] usize), + AddScaledOutput { + #[proptest(strategy = "1..=5usize")] + id: usize, + #[proptest(strategy = "arbitrary_scale()")] + scale: f64, + }, RemoveOutput(#[proptest(strategy = "1..=5usize")] usize), FocusOutput(#[proptest(strategy = "1..=5usize")] usize), AddNamedWorkspace { @@ -2769,6 +2804,32 @@ mod tests { ); layout.add_output(output.clone()); } + Op::AddScaledOutput { id, scale } => { + let name = format!("output{id}"); + if layout.outputs().any(|o| o.name() == name) { + return; + } + + let output = Output::new( + name, + PhysicalProperties { + size: Size::from((1280, 720)), + subpixel: Subpixel::Unknown, + make: String::new(), + model: String::new(), + }, + ); + output.change_current_state( + Some(Mode { + size: Size::from((1280, 720)), + refresh: 60000, + }), + None, + Some(smithay::output::Scale::Fractional(scale)), + None, + ); + layout.add_output(output.clone()); + } Op::RemoveOutput(id) => { let name = format!("output{id}"); let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { @@ -3560,7 +3621,7 @@ mod tests { let mut options = Options::default(); options.border.off = false; - options.border.width = 1; + options.border.width = FloatOrInt(1.); check_ops_with_options(options, &ops); } @@ -3578,7 +3639,7 @@ mod tests { let mut options = Options::default(); options.border.off = false; - options.border.width = 1; + options.border |
