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/layout/workspace.rs | |
| 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/layout/workspace.rs')
| -rw-r--r-- | src/layout/workspace.rs | 560 |
1 files changed, 332 insertions, 228 deletions
diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index cf0a83e9..e9f6d6a6 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -5,6 +5,7 @@ use std::time::Duration; use niri_config::{CenterFocusedColumn, PresetWidth, Struts, Workspace as WorkspaceConfig}; use niri_ipc::SizeChange; +use ordered_float::NotNan; use smithay::backend::renderer::gles::GlesRenderer; use smithay::desktop::{layer_map_for_output, Window}; use smithay::output::Output; @@ -54,13 +55,13 @@ pub struct Workspace<W: LayoutElement> { /// /// This should be computed from the current workspace output size, or, if all outputs have /// been disconnected, preserved until a new output is connected. - view_size: Size<i32, Logical>, + view_size: Size<f64, Logical>, /// Latest known working area for this workspace. /// /// This is similar to view size, but takes into account things like layer shell exclusive /// zones. - working_area: Rectangle<i32, Logical>, + working_area: Rectangle<f64, Logical>, /// Columns of windows on this workspace. pub columns: Vec<Column<W>>, @@ -79,7 +80,7 @@ pub struct Workspace<W: LayoutElement> { /// Any gaps, including left padding from work area left exclusive zone, is handled /// with this view offset (rather than added as a constant elsewhere in the code). This allows /// for natural handling of fullscreen windows, which must ignore work area padding. - view_offset: i32, + view_offset: f64, /// Adjustment of the view offset, if one is currently ongoing. view_offset_adj: Option<ViewOffsetAdjustment>, @@ -94,15 +95,18 @@ pub struct Workspace<W: LayoutElement> { /// index of the previous column to activate. /// /// The value is the view offset that the previous column had before, to restore it. - activate_prev_column_on_removal: Option<i32>, + activate_prev_column_on_removal: Option<f64>, /// View offset to restore after unfullscreening. - view_offset_before_fullscreen: Option<i32>, + view_offset_before_fullscreen: Option<f64>, /// Windows in the closing animation. closing_windows: Vec<ClosingWindow>, - /// Configurable properties of the layout. + /// Configurable properties of the layout as received from the parent monitor. + pub base_options: Rc<Options>, + + /// Configurable properties of the layout with logical sizes adjusted for the current `scale`. pub options: Rc<Options>, /// Optional name of this workspace. @@ -137,7 +141,7 @@ niri_render_elements! { #[derive(Debug, Clone, Copy, PartialEq)] struct ColumnData { /// Cached actual column width. - width: i32, + width: f64, } #[derive(Debug)] @@ -152,7 +156,7 @@ struct ViewGesture { tracker: SwipeTracker, delta_from_tracker: f64, // The view offset we'll use if needed for activate_prev_column_on_removal. - static_view_offset: i32, + static_view_offset: f64, /// Whether the gesture is controlled by the touchpad. is_touchpad: bool, } @@ -160,7 +164,7 @@ struct ViewGesture { #[derive(Debug)] struct InteractiveResize<W: LayoutElement> { window: W::Id, - original_window_size: Size<i32, Logical>, + original_window_size: Size<f64, Logical>, data: InteractiveResizeData, } @@ -175,7 +179,7 @@ pub enum ColumnWidth { /// proportions. Preset(usize), /// Fixed width in logical pixels. - Fixed(i32), + Fixed(f64), } /// Height of a window in a column. @@ -190,12 +194,12 @@ pub enum ColumnWidth { /// This does not preclude the usual set of binds to set or resize a window proportionally. Just, /// they are converted to, and stored as fixed height right away, so that once you resize a window /// to fit the desired content, it can never become smaller than that when moving between monitors. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum WindowHeight { /// Automatically computed height, evenly distributed across the column. Auto, /// Fixed height in logical pixels. - Fixed(i32), + Fixed(f64), } #[derive(Debug)] @@ -229,10 +233,13 @@ pub struct Column<W: LayoutElement> { move_animation: Option<Animation>, /// Latest known view size for this column's workspace. - view_size: Size<i32, Logical>, + view_size: Size<f64, Logical>, /// Latest known working area for this column's workspace. - working_area: Rectangle<i32, Logical>, + working_area: Rectangle<f64, Logical>, + + /// Scale of the output the column is on (and rounds its sizes to). + scale: f64, /// Configurable properties of the layout. options: Rc<Options>, @@ -247,7 +254,7 @@ struct TileData { height: WindowHeight, /// Cached actual size of the tile. - size: Size<i32, Logical>, + size: Size<f64, Logical>, /// Cached whether the tile is being interactively resized by its left edge. interactively_resizing_by_left_edge: bool, @@ -274,7 +281,7 @@ impl ViewOffsetAdjustment { impl ColumnData { pub fn new<W: LayoutElement>(column: &Column<W>) -> Self { - let mut rv = Self { width: 0 }; + let mut rv = Self { width: 0. }; rv.update(column); rv } @@ -285,10 +292,10 @@ impl ColumnData { } impl ColumnWidth { - fn resolve(self, options: &Options, view_width: i32) -> i32 { + fn resolve(self, options: &Options, view_width: f64) -> f64 { match self { ColumnWidth::Proportion(proportion) => { - ((view_width - options.gaps) as f64 * proportion).floor() as i32 - options.gaps + (view_width - options.gaps) * proportion - options.gaps } ColumnWidth::Preset(idx) => options.preset_widths[idx].resolve(options, view_width), ColumnWidth::Fixed(width) => width, @@ -300,7 +307,7 @@ impl From<PresetWidth> for ColumnWidth { fn from(value: PresetWidth) -> Self { match value { PresetWidth::Proportion(p) => Self::Proportion(p.clamp(0., 10000.)), - PresetWidth::Fixed(f) => Self::Fixed(f.clamp(1, 100000)), + PresetWidth::Fixed(f) => Self::Fixed(f64::from(f.clamp(1, 100000))), } } } @@ -333,7 +340,7 @@ impl<W: LayoutElement> Workspace<W> { pub fn new_with_config( output: Output, config: Option<WorkspaceConfig>, - options: Rc<Options>, + base_options: Rc<Options>, ) -> Self { let original_output = config .as_ref() @@ -341,10 +348,15 @@ impl<W: LayoutElement> Workspace<W> { .map(OutputId) .unwrap_or(OutputId::new(&output)); + let scale = output.current_scale(); + let options = + Rc::new(Options::clone(&base_options).adjusted_for_scale(scale.fractional_scale())); + let working_area = compute_working_area(&output, options.struts); + Self { original_output, - scale: output.current_scale(), + scale, transform: output.current_transform(), view_size: output_size(&output), working_area, @@ -353,11 +365,12 @@ impl<W: LayoutElement> Workspace<W> { data: vec![], active_column_idx: 0, interactive_resize: None, - view_offset: 0, + view_offset: 0., view_offset_adj: None, activate_prev_column_on_removal: None, view_offset_before_fullscreen: None, closing_windows: vec![], + base_options, options, name: config.map(|c| c.name.0), id: WorkspaceId::next(), @@ -366,7 +379,7 @@ impl<W: LayoutElement> Workspace<W> { pub fn new_with_config_no_outputs( config: Option<WorkspaceConfig>, - options: Rc<Options>, + base_options: Rc<Options>, ) -> Self { let original_output = OutputId( config @@ -374,22 +387,28 @@ impl<W: LayoutElement> Workspace<W> { .and_then(|c| c.open_on_output) .unwrap_or_default(), ); + + let scale = smithay::output::Scale::Integer(1); + let options = + Rc::new(Options::clone(&base_options).adjusted_for_scale(scale.fractional_scale())); + Self { output: None, - scale: smithay::output::Scale::Integer(1), + scale, transform: Transform::Normal, original_output, - view_size: Size::from((1280, 720)), - working_area: Rectangle::from_loc_and_size((0, 0), (1280, 720)), + view_size: Size::from((1280., 720.)), + working_area: Rectangle::from_loc_and_size((0., 0.), (1280., 720.)), columns: vec![], data: vec![], active_column_idx: 0, interactive_resize: None, - view_offset: 0, + view_offset: 0., view_offset_adj: None, activate_prev_column_on_removal: None, view_offset_before_fullscreen: None, closing_windows: vec![], + base_options, options, name: config.map(|c| c.name.0), id: WorkspaceId::next(), @@ -408,15 +427,19 @@ impl<W: LayoutElement> Workspace<W> { self.name = None; } + pub fn scale(&self) -> smithay::output::Scale { + self.scale + } + pub fn advance_animations(&mut self, current_time: Duration) { if let Some(ViewOffsetAdjustment::Animation(anim)) = &mut self.view_offset_adj { anim.set_current_time(current_time); - self.view_offset = anim.value().round() as i32; + self.view_offset = anim.value(); if anim.is_done() { self.view_offset_adj = None; } } else if let Some(ViewOffsetAdjustment::Gesture(gesture)) = &self.view_offset_adj { - self.view_offset = gesture.current_view_offset.round() as i32; + self.view_offset = gesture.current_view_offset; } for col in &mut self.columns { @@ -444,24 +467,28 @@ impl<W: LayoutElement> Workspace<W> { } pub fn update_render_elements(&mut self, is_active: bool) { - let view_pos = Point::from((self.view_pos(), 0)); + let view_pos = Point::from((self.view_pos(), 0.)); let view_size = self.view_size(); let active_idx = self.active_column_idx; for (col_idx, (col, col_x)) in self.columns_mut().enumerate() { let is_active = is_active && col_idx == active_idx; - let col_off = Point::from((col_x, 0)); + let col_off = Point::from((col_x, 0.)); let col_pos = view_pos - col_off - col.render_offset(); let view_rect = Rectangle::from_loc_and_size(col_pos, view_size); col.update_render_elements(is_active, view_rect); } } - pub fn update_config(&mut self, options: Rc<Options>) { + pub fn update_config(&mut self, base_options: Rc<Options>) { + let scale = self.scale.fractional_scale(); + let options = Rc::new(Options::clone(&base_options).adjusted_for_scale(scale)); + for (column, data) in zip(&mut self.columns, &mut self.data) { - column.update_config(options.clone()); + column.update_config(scale, options.clone()); data.update(column); } + self.base_options = base_options; self.options = options; } @@ -527,8 +554,8 @@ impl<W: LayoutElement> Workspace<W> { &mut self, scale: smithay::output::Scale, transform: Transform, - size: Size<i32, Logical>, - working_area: Rectangle<i32, Logical>, + size: Size<f64, Logical>, + working_area: Rectangle<f64, Logical>, ) { let scale_transform_changed = self.transform != transform || self.scale.integer_scale() != scale.integer_scale() @@ -537,11 +564,18 @@ impl<W: LayoutElement> Workspace<W> { return; } + let fractional_scale_changed = self.scale.fractional_scale() != scale.fractional_scale(); + self.scale = scale; self.transform = transform; self.view_size = size; self.working_area = working_area; + if fractional_scale_changed { + // Options need to be recomputed for the new scale. + self.update_config(self.base_options.clone()); + } + for col in &mut self.columns { col.set_view_size(self.view_size, self.working_area); } @@ -553,7 +587,7 @@ impl<W: LayoutElement> Workspace<W> { } } - pub fn view_size(&self) -> Size<i32, Logical> { + pub fn view_size(&self) -> Size<f64, Logical> { self.view_size } @@ -586,20 +620,20 @@ impl<W: LayoutElement> Workspace<W> { let mut width = width.resolve(&self.options, self.working_area.size.w); if !is_fixed && !border.off { - width -= border.width as i32 * 2; + width -= border.width.0 * 2.; } - max(1, width) + max(1, width.floor() as i32) } else { 0 }; - let mut height = self.working_area.size.h - self.options.gaps * 2; + let mut height = self.working_area.size.h - self.options.gaps * 2.; if !border.off { - height -= border.width as i32 * 2; + height -= border.width.0 * 2.; } - Size::from((width, max(height, 1))) + Size::from((width, max(height.floor() as i32, 1))) } pub fn configure_new_window( @@ -617,7 +651,7 @@ impl<W: LayoutElement> Workspace<W> { .expect("no x11 support") .with_pending_state(|state| { if state.states.contains(xdg_toplevel::State::Fullscreen) { - state.size = Some(self.view_size); + state.size = Some(self.view_size.to_i32_round()); } else { state.size = Some(self.new_window_size(width, rules)); } @@ -626,15 +660,15 @@ impl<W: LayoutElement> Workspace<W> { }); } - fn compute_new_view_offset_for_column(&self, current_x: i32, idx: usize) -> i32 { + fn compute_new_view_offset_for_column(&self, current_x: f64, idx: usize) -> f64 { if self.columns[idx].is_fullscreen { - return 0; + return 0.; } let new_col_x = self.column_x(idx); let final_x = if let Some(ViewOffsetAdjustment::Animation(anim)) = &self.view_offset_adj { - current_x - self.view_offset + anim.to().round() as i32 + current_x - self.view_offset + anim.to() } else { current_x }; @@ -651,7 +685,7 @@ impl<W: LayoutElement> Workspace<W> { new_offset - self.working_area.loc.x } - fn animate_view_offset(&mut self, current_x: i32, idx: usize, new_view_offset: i32) { + fn animate_view_offset(&mut self, current_x: f64, idx: usize, new_view_offset: f64) { self.animate_view_offset_with_config( current_x, idx, @@ -662,9 +696,9 @@ impl<W: LayoutElement> Workspace<W> { fn animate_view_offset_with_config( &mut self, - current_x: i32, + current_x: f64, idx: usize, - new_view_offset: i32, + new_view_offset: f64, config: niri_config::Animation, ) { let new_col_x = self.column_x(idx); @@ -673,9 +707,8 @@ impl<W: LayoutElement> Workspace<W> { // If we're already animating towards that, don't restart it. if let Some(ViewOffsetAdjustment::Animation(anim)) = &self.view_offset_adj { - if anim.value().round() as i32 == self.view_offset - && anim.to().round() as i32 == new_view_offset - { + let pixel = 1. / self.scale.fractional_scale(); + if (anim.value() - self.view_offset).abs() < pixel && anim.to() == new_view_offset { return; } } @@ -688,8 +721,8 @@ impl<W: LayoutElement> Workspace<W> { // FIXME: also compute and use current velocity. self.view_offset_adj = Some(ViewOffsetAdjustment::Animation(Animation::new( - self.view_offset as f64, - new_view_offset as f64, + self.view_offset, + new_view_offset, 0., config, ))); @@ -697,7 +730,7 @@ impl<W: LayoutElement> Workspace<W> { fn animate_view_offset_to_column_fit( &mut self, - current_x: i32, + current_x: f64, idx: usize, config: niri_config::Animation, ) { @@ -707,7 +740,7 @@ impl<W: LayoutElement> Workspace<W> { fn animate_view_offset_to_column_centered( &mut self, - current_x: i32, + current_x: f64, idx: usize, config: niri_config::Animation, ) { @@ -731,14 +764,14 @@ impl<W: LayoutElement> Workspace<W> { return; } - let new_view_offset = -(self.working_area.size.w - width) / 2 - self.working_area.loc.x; + let new_view_offset = -(self.working_area.size.w - width) / 2. - self.working_area.loc.x; self.animate_view_offset_with_config(current_x, idx, new_view_offset, config); } fn animate_view_offset_to_column( &mut self, - current_x: i32, + current_x: f64, idx: usize, prev_idx: Option<usize>, ) { @@ -752,7 +785,7 @@ impl<W: LayoutElement> Workspace<W> { fn animate_view_offset_to_column_with_config( &mut self, - current_x: i32, + current_x: f64, idx: usize, prev_idx: Option<usize>, config: niri_config::Animation, @@ -786,7 +819,7 @@ impl<W: LayoutElement> Workspace<W> { } else { // Source is right from target. source_x - target_x + source_width - } + self.options.gaps * 2; + } + self.options.gaps * 2.; // If it fits together, do a normal animation, otherwise center the new column. if total_width <= self.working_area.size.w { @@ -853,7 +886,7 @@ impl<W: LayoutElement> Workspace<W> { width: ColumnWidth, is_full_width: bool, ) { - let tile = Tile::new(window, self.options.clone()); + let tile = Tile::new(window, self.scale.fractional_scale(), self.options.clone()); self.add_tile_at(col_idx, tile, activate, width, is_full_width, None); } @@ -874,6 +907,7 @@ impl<W: LayoutElement> Workspace<W> { tile, self.view_size, self.working_area, + self.scale.fractional_scale(), self.options.clone(), width, is_full_width, @@ -889,7 +923,7 @@ impl<W: LayoutElement> Workspace<W> { if was_empty { if self.options.center_focused_column == CenterFocusedColumn::Always { self.view_offset = - -(self.working_area.size.w - width) / 2 - self.working_area.loc.x; + -(self.working_area.size.w - width) / 2. - self.working_area.loc.x; } else { // Try to make the code produce a left-aligned offset, even in presence of left // exclusive zones. @@ -974,6 +1008,7 @@ impl<W: LayoutElement> Workspace<W> { window, self.view_size, self.working_area, + self.scale.fractional_scale(), self.options.clone(), width, is_full_width, @@ -1017,6 +1052,7 @@ impl<W: LayoutElement> Workspace<W> { self.active_column_idx + 1 }; + column.update_config(self.scale.fractional_scale(), self.options.clone()); column.set_view_size(self.view_size, self.working_area); let width = column.width(); self.data.insert(idx, ColumnData::new(&column)); @@ -1028,7 +1064,7 @@ impl<W: LayoutElement> Workspace<W> { if was_empty { if self.options.center_focused_column == CenterFocusedColumn::Always { self.view_offset = - -(self.working_area.size.w - width) / 2 - self.working_area.loc.x; + -(self.working_area.size.w - width) / 2. - self.working_area.loc.x; } else { // Try to make the code produce a left-aligned offset, even in presence of left // exclusive zones. @@ -1294,7 +1330,7 @@ impl<W: LayoutElement> Workspace<W> { // Move other columns in tandem with resizing. let started_resize_anim = - column.tiles[tile_idx].resize_animation().is_some() && offset != 0; + column.tiles[tile_idx].resize_animation().is_some() && offset != 0.; if started_resize_anim { if self.active_column_idx <= col_idx { for col in &mut self.columns[col_idx + 1..] { @@ -1317,7 +1353,7 @@ impl<W: LayoutElement> Workspace<W> { // If offset == 0, then don't mess with the view or the gesture. Some clients (Firefox, // Chromium, Electron) currently don't commit after the ack of a configure that drops // the Resizing state, which can trigger this code path for a while. - let resize = if offset != 0 { resize } else { None }; + let resize = if offset != 0. { resize } else { None }; if let Some(resize) = resize { // If this is an interactive resize commit of an active window, then we need to // either preserve the view offset or adjust it accordingly. @@ -1327,17 +1363,17 @@ impl<W: LayoutElement> Workspace<W> { let offset = if centered { // FIXME: when view_offset becomes fractional, this can be made additive too. let new_offset = - -(self.working_area.size.w - width) / 2 - self.working_area.loc.x; + -(self.working_area.size.w - width) / 2. - self.working_area.loc.x; new_offset - self.view_offset } else if resize.edges.contains(ResizeEdge::LEFT) { -offset } else { - 0 + 0. }; self.view_offset += offset; if let Some(ViewOffsetAdjustment::Animation(anim)) = &mut self.view_offset_adj { - anim.offset(offset as f64); + anim.offset(offset); } else { // Don't bother with the gesture. self.view_offset_adj = None; @@ -1388,7 +1424,7 @@ impl<W: LayoutElement> Workspace<W> { pub fn store_unmap_snapshot_if_empty(&mut self, renderer: &mut GlesRenderer, window: &W::Id) { let output_scale = Scale::from(self.scale.fractional_scale()); let view_size = self.view_size(); - for (tile, tile_pos) in self.tiles_with_render_positions_mut() { + for (tile, tile_pos) in self.tiles_with_render_positions_mut(false) { if tile.window().id() == window { let view_pos = Point::from((-tile_pos.x, -tile_pos.y)); let view_rect = Rectangle::from_loc_and_size(view_pos, view_size); @@ -1418,7 +1454,7 @@ impl<W: LayoutElement> Workspace<W> { let output_scale = Scale::from(self.scale.fractional_scale()); let (tile, mut tile_pos) = self - .tiles_with_render_positions_mut() + .tiles_with_render_positions_mut(false) .find(|(tile, _)| tile.window().id() == window) .unwrap(); @@ -1454,8 +1490,11 @@ impl<W: LayoutElement> Workspace<W> { .data .iter() .enumerate() - .filter_map(|(idx, data)| (idx != tile_idx).then_some(data.size.w)) + .filter_map(|(idx, data)| { + (idx != tile_idx).then_some(NotNan::new(data.size.w).unwrap()) + }) .max() + .map(NotNan::into_inner) .unwrap() }; tile_pos.x -= offset; @@ -1476,10 +1515,13 @@ impl<W: LayoutElement> Workspace<W> { #[cfg(test)] pub fn verify_invariants(&self) { - assert!(self.view_size.w > 0); - assert!(self.view_size.h > 0); - assert!(self.scale.fractional_scale() > 0.); - assert!(self.scale.fractional_scale().is_finite()); + use approx::assert_abs_diff_eq; + + let scale = self.scale.fractional_scale(); + assert!(self.view_size.w > 0.); + assert!(self.view_size.h > 0.); + assert!(scale > 0.); + assert!(scale.is_finite()); assert_eq!(self.columns.len(), self.data.len()); if !self.columns.is_empty() { @@ -1487,6 +1529,7 @@ impl<W: LayoutElement> Workspace<W> { for (column, data) in zip(&self.columns, &self.data) { assert!(Rc::ptr_eq(&self.options, &column.options)); + assert_eq!(self.scale.fractional_scale(), column.scale); column.verify_invariants(); let mut data2 = *data; @@ -1506,6 +1549,14 @@ impl<W: LayoutElement> Workspace<W> { }) ); } + + for (_, tile_pos) in self.tiles_with_render_positions() { + let rounded_pos = tile_pos.to_physical_precise_round(scale).to_logical(scale); + + // Tile positions must be rounded to physical pixels. + assert_abs_diff_eq!(tile_pos.x, rounded_pos.x, epsilon = 1e-5); + assert_abs_diff_eq!(tile_pos.y, rounded_pos.y, epsilon = 1e-5); + } } if let Some(resize) = &self.interactive_resize { @@ -1599,7 +1650,7 @@ impl<W: LayoutElement> Workspace<W> { let view_offset_delta = -self.column_x(self.active_column_idx) + current_col_x; self.view_offset += view_offset_delta; if let Some(ViewOffsetAdjustment::Animation(anim)) = &mut self.view_offset_adj { - anim.offset(view_offset_delta as f64); + anim.offset(view_offset_delta); } // The column we just moved is offset by the difference between its new and old position. @@ -1679,7 +1730,7 @@ impl<W: LayoutElement> Workspace<W> { } let offset = self.column_x(source_col_idx) - self.column_x(source_col_idx - 1); - let mut offset = Point::from((offset, 0)); + let mut offset = Point::from((offset, 0.)); // Move into adjacent column. let target_column_idx = source_col_idx - 1; @@ -1720,7 +1771,7 @@ impl<W: LayoutElement> Workspace<W> { let width = source_column.width; let is_full_width = source_column.is_full_width; - let mut offset = Point::from((source_column.render_offset().x, 0)); + let mut offset = Point::from((source_column.render_offset().x, 0.)); let tile = self.remove_tile_by_idx(source_col_idx, source_column.active_tile_idx, None); @@ -1749,7 +1800,7 @@ impl<W: LayoutElement> Workspace<W> { let source_col_idx = self.active_column_idx; let offset = self.column_x(source_col_idx) - self.column_x(source_col_idx + 1); - let mut offset = Point::from((offset, 0)); + let mut offset = Point::from((offset, 0.)); let source_column = &self.columns[source_col_idx]; offset.x += source_column.render_offset().x; @@ -1827,7 +1878,7 @@ impl<W: LayoutElement> Workspace<W> { let offset = self.column_x(source_column_idx) + self.columns[source_column_idx].render_offset().x - self.column_x(self.active_column_idx); - let mut offset = Point::from((offset, 0)); + let mut offset = Point::from((offset, 0.)); let prev_off = self.columns[source_column_idx].tile_offset(0); let tile = self.remove_tile_by_idx(source_column_idx, 0, None); @@ -1867,7 +1918,7 @@ impl<W: LayoutElement> Workspace<W> { let offset = self.column_x(self.active_column_idx) - self.column_x(self.active_column_idx + 1); - let mut offset = Point::from((offset, 0)); + let mut offset = Point::from((offset, 0.)); let source_column = &self.columns[self.active_column_idx]; if source_column.tiles.len() == 1 { @@ -1909,17 +1960,17 @@ impl<W: LayoutElement> Workspace<W> { } } - fn view_pos(&self) -> i32 { + fn view_pos(&self) -> f64 { self.column_x(self.active_column_idx) + self.view_offset } /// Returns a view offset value suitable for saving and later restoration. /// /// This means that it shouldn't return an in-progress animation or gesture value. - fn static_view_offset(&self) -> i32 { + fn static_view_offset(&self) -> f64 { match &self.view_offset_adj { // For animations we can return the final value. - Some(ViewOffsetAdjustment::Animation(anim)) => anim.to().round() as i32, + Some(ViewOffsetAdjustment::Animation(anim)) => anim.to(), Some(ViewOffsetAdjustment::Gesture(gesture)) => gesture.static_view_offset, _ => self.view_offset, } @@ -1927,12 +1978,12 @@ impl<W: LayoutElement> Workspace<W> { // HACK: pass a self.data iterator in manually as a workaround for the lack of method partial // borrowing. Note that this method's return value does not borrow the entire &Self! - fn column_xs(&self, data: impl Iterator<Item = ColumnData>) -> impl Iterator<Item = i32> { + fn column_xs(&self, data: impl Iterator<Item = ColumnData>) -> impl Iterator<Item = f64> { let gaps = self.options.gaps; - let mut x = 0; + let mut x = 0.; // Chain with a dummy value to be able to get one past all columns' X. - let dummy = ColumnData { width: 0 }; + let dummy = ColumnData { width: 0. }; let data = data.chain(iter::once(dummy)); data.map(move |data| { @@ -1942,7 +1993,7 @@ impl<W: LayoutElement> Workspace<W> { }) } - fn column_x(&self, column_idx: usize) -> i32 { + fn column_x(&self, column_idx: usize) -> f64 { self.column_xs(self.data.iter().copied()) .nth(column_idx) .unwrap() @@ -1951,7 +2002,7 @@ impl<W: LayoutElement> Workspace<W> { fn column_xs_in_render_order( &self, data: impl Iterator<Item = ColumnData>, - ) -> impl Iterator<Item = i32> { + ) -> impl Iterator<Item = f64> { let active_idx = self.active_column_idx; let active_pos = self.column_x(active_idx); let offsets = self @@ -1961,12 +2012,12 @@ impl<W: LayoutElement> Workspace<W> { iter::once(active_pos).chain(offsets) } - fn columns_mut(&mut self) -> impl Iterator<Item = (&mut Column<W>, i32)> + '_ { + fn columns_mut(&mut self) -> impl Iterator<Item = (&mut Column<W>, f64)> + '_ { let offsets = self.column_xs(self.data.iter().copied()); zip(&mut self.columns, offsets) } - fn columns_in_render_order(&self) -> impl Iterator<Item = (&Column<W>, i32)> + '_ { + fn columns_in_render_order(&self) -> impl Iterator<Item = (&Column<W>, f64)> + '_ { let offsets = self.column_xs_in_render_order(self.data.iter().copied()); let (first, rest) = self.columns.split_at(self.active_column_idx); @@ -1976,7 +2027,7 @@ impl<W: LayoutElement> Workspace<W> { zip(tiles, offsets) } - fn columns_in_render_order_mut(&mut self) -> impl Iterator<Item = (&mut Column<W>, i32)> + '_ { + fn columns_in_render_order_mut(&mut self) -> impl Iterator<Item = (&mut Column<W>, f64)> + '_ { let offsets = self.column_xs_in_render_order(self.data.iter().copied()); let (first, rest) = self.columns.split_at_mut(self.active_column_idx); @@ -1986,14 +2037,17 @@ impl<W: LayoutElement> Workspace<W> { zip(tiles, offsets) } - fn tiles_with_render_positions(&self) -> impl Iterator<Item = (&Tile<W>, Point<i32, Logical>)> { - let view_off = Point::from((-self.view_pos(), 0)); + fn tiles_with_render_positions(&self) -> impl Iterator<Item = (&Tile<W>, Point<f64, Logical>)> { + let scale = self.scale.fractional_scale(); + let view_off = Point::from((-self.view_pos(), 0.)); self.columns_in_render_order() .flat_map(move |(col, col_x)| { - let col_off = Point::from((col_x, 0)); + let col_off = Point::from((col_x, 0.)); let col_render_off = col.render_offset(); col.tiles_in_render_order().map(move |(tile, tile_off)| { let pos = view_off + col_off + col_render_off + tile_off + tile.render_offset(); + // Round to physical pixels. + let pos = pos.to_physical_precise_round(scale).to_logical(scale); (tile, pos) }) }) @@ -2001,16 +2055,22 @@ impl<W: LayoutElement> Workspace<W> { fn tiles_with_render_positions_mut( &mut self, - ) -> impl Iterator<Item = (&mut Tile<W>, Point<i32, Logical>)> { - let view_off = Point::from((-self.view_pos(), 0)); + round: bool, + ) -> impl Iterator<Item = (&mut Tile<W>, Point<f64, Logical>)> { + let scale = self.scale.fractional_scale(); + let view_off = Point::from((-self.view_pos(), 0.)); self.columns_in_render_order_mut() .flat_map(move |(col, col_x)| { - let col_off = Point::from((col_x, 0)); + let col_off = Point::from((col_x, 0.)); let col_render_off = col.render_offset(); col.tiles_in_render_order_mut() .map(move |(tile, tile_off)| { - let pos = + let mut pos = view_off + col_off + col_render_off + tile_off + tile.render_offset(); + // Round to physical pixels. + if round { + pos = pos.to_physical_precise_round(scale).to_logical(scale); + } (tile, pos) }) }) @@ -2019,14 +2079,14 @@ impl<W: LayoutElement> Workspace<W> { /// Returns the geometry of the active tile relative to and clamped to the view. /// /// During animations, assumes the final view position. - pub fn active_tile_visual_rectangle(&self) -> Option<Rectangle<i32, Logical>> { + pub fn active_tile_visual_rectangle(&self) -> Option<Rectangle<f64, Logical>> { let col = self.columns.get(self.active_column_idx)?; let final_view_offset = self .view_offset_adj .as_ref() - .map_or(self.view_offset, |adj| adj.target_view_offset() as i32); - let view_off = Point::from((-final_view_offset, 0)); + .map_or(self.view_offset, |adj| adj.target_view_offset()); + let view_off = Point::from((-final_view_offset, 0.)); let (tile, tile_off) = col.tiles().nth(col.active_tile_idx).unwrap(); @@ -2034,21 +2094,21 @@ impl<W: LayoutElement> Workspace<W> { let tile_size = tile.tile_size(); let tile_rect = Rectangle::from_loc_and_size(tile_pos, tile_size); - let view = Rectangle::from_loc_and_size((0, 0), self.view_size); + let view = Rectangle::from_loc_and_size((0., 0.), self.view_size); view.intersection(tile_rect) } pub fn window_under( &self, pos: Point<f64, Logical>, - ) -> Option<(&W, Option<Point<i32, Logical>>)> { + ) -> Option<(&W, Option<Point<f64, Logical>>)> { if self.columns.is_empty() { return None; } self.tiles_with_render_positions() .find_map(|(tile, tile_pos)| { - let pos_within_tile = pos - tile_pos.to_f64(); + let pos_within_tile = pos - tile_pos; if tile.is_in_input_region(pos_within_tile) { let pos_within_surface = tile_pos + tile.buf_loc(); @@ -2068,7 +2128,7 @@ impl<W: LayoutElement> Workspace<W> { self.tiles_with_render_positions() .find_map(|(tile, tile_pos)| { - let pos_within_tile = pos - tile_pos.to_f64(); |
