From 1dae45c58d7eabeda21ef490d712915890bf6cff Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Mon, 17 Jun 2024 09:16:28 +0300 Subject: 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. --- src/layout/closing_window.rs | 25 +- src/layout/focus_ring.rs | 73 +++--- src/layout/mod.rs | 204 +++++++++++++--- src/layout/monitor.rs | 56 +++-- src/layout/opening_window.rs | 18 +- src/layout/tile.rs | 221 +++++++++-------- src/layout/workspace.rs | 560 +++++++++++++++++++++++++------------------ 7 files changed, 708 insertions(+), 449 deletions(-) (limited to 'src/layout') 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, /// Size of the window geometry. - geo_size: Size, + geo_size: Size, /// Position in the workspace. - pos: Point, + pos: Point, /// How much the texture should be offset. buffer_offset: Point, @@ -64,8 +64,8 @@ impl ClosingWindow { renderer: &mut GlesRenderer, snapshot: RenderSnapshot, scale: Scale, - geo_size: Size, - pos: Point, + geo_size: Size, + pos: Point, anim: Animation, ) -> anyhow::Result { let _span = tracy_client::span!("ClosingWindow::new"); @@ -123,7 +123,7 @@ impl ClosingWindow { pub fn render( &self, renderer: &mut GlesRenderer, - view_rect: Rectangle, + view_rect: Rectangle, scale: Scale, 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; 8], - sizes: [Size; 8], + locations: [Point; 8], + sizes: [Size; 8], borders: [BorderRenderElement; 8], - full_size: Size, + full_size: Size, 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, + win_size: Size, is_active: bool, is_border: bool, - view_rect: Rectangle, + view_rect: Rectangle, 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, - scale: Scale, + location: Point, ) -> impl Iterator { 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| { + let mut push = |buffer, border: &BorderRenderElement, location: Point| { 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 => { @@ -110,7 +110,7 @@ pub trait LayoutElement { fn render( &self, renderer: &mut R, - location: Point, + location: Point, scale: Scale, alpha: f32, target: RenderTarget, @@ -120,7 +120,7 @@ pub trait LayoutElement { fn render_normal( &self, renderer: &mut R, - location: Point, + location: Point, scale: Scale, alpha: f32, target: RenderTarget, @@ -132,7 +132,7 @@ pub trait LayoutElement { fn render_popups( &self, renderer: &mut R, - location: Point, + location: Point, scale: Scale, alpha: f32, target: RenderTarget, @@ -206,10 +206,10 @@ enum MonitorSet { }, } -#[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 Layout { @@ -486,12 +496,12 @@ impl Layout { width: Option, 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 Layout { width: Option, 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 Layout { width: Option, 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 Layout { width: Option, 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 Layout { None } - pub fn window_loc(&self, window: &W::Id) -> Option> { + pub fn window_loc(&self, window: &W::Id) -> Option> { match &self.monitor_set { MonitorSet::Normal { monitors, .. } => { for mon in monitors { @@ -1440,7 +1450,7 @@ impl Layout { &self, output: &Output, pos_within_output: Point, - ) -> Option<(&W, Option>)> { + ) -> Option<(&W, Option>)> { let MonitorSet::Normal { monitors, .. } = &self.monitor_set else { return None; }; @@ -1485,8 +1495,15 @@ impl Layout { ); 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 Layout { 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 Default for MonitorSet { 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 Default for Layout { fn default() -> Self { @@ -2459,7 +2484,7 @@ mod tests { fn render( &self, _renderer: &mut R, - _location: Point, + _location: Point, _scale: Scale, _alpha: f32, _target: RenderTarget, @@ -2595,9 +2620,19 @@ mod tests { ] } + fn arbitrary_scale() -> impl Strategy { + 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.width = FloatOrInt(1.); check_ops_with_options(options, &ops); } @@ -3916,7 +3977,7 @@ mod tests { fn config_change_updates_cached_sizes() { let mut config = Config::default(); config.layout.border.off = false; - config.layout.border.width = 2; + config.layout.border.width = FloatOrInt(2.); let mut layout = Layout::new(&config); @@ -3927,18 +3988,83 @@ mod tests { } .apply(&mut layout); - config.layout.border.width = 4; + config.layout.border.width = FloatOrInt(4.); layout.update_config(&config); layout.verify_invariants(); } - fn arbitrary_spacing() -> impl Strategy { + #[test] + fn working_area_starts_at_physical_pixel() { + let struts = Struts { + left: FloatOrInt(0.5), + right: FloatOrInt(1.), + top: FloatOrInt(0.75), + bottom: FloatOrInt(1.), + }; + + let output = Output::new( + String::from("output"), + 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, + None, + None, + ); + + let area = compute_working_area(&output, struts); + + assert_eq!(round_logical_in_physical(1., area.loc.x), area.loc.x); + assert_eq!(round_logical_in_physical(1., area.loc.y), area.loc.y); + } + + #[test] + fn large_fractional_strut() { + let struts = Struts { + left: FloatOrInt(0.), + right: FloatOrInt(0.), + top: FloatOrInt(50000.5), + bottom: FloatOrInt(0.), + }; + + let output = Output::new( + String::from("output"), + 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, + None, + None, + ); + + compute_working_area(&output, struts); + } + + fn arbitrary_spacing() -> impl Strategy { // Give equal weight to: // - 0: the element is disabled // - 4: some reasonable value // - random value, likely unreasonably big - prop_oneof![Just(0), Just(4), (1..=u16::MAX)] + prop_oneof![Just(0.), Just(4.), ((1.)..=65535.)] } fn arbitrary_struts() -> impl Strategy { @@ -3949,10 +4075,10 @@ mod tests { arbitrary_spacing(), ) .prop_map(|(left, right, top, bottom)| Struts { - left, - right, - top, - bottom, + left: FloatOrInt(left), + right: FloatOrInt(right), + top: FloatOrInt(top), + bottom: FloatOrInt(bottom), }) } @@ -3971,7 +4097,7 @@ mod tests { ) -> niri_config::FocusRing { niri_config::FocusRing { off, - width, + width: FloatOrInt(width), ..Default::default() } } @@ -3984,7 +4110,7 @@ mod tests { ) -> niri_config::Border { niri_config::Border { off, - width, + width: FloatOrInt(width), ..Default::default() } } @@ -3999,7 +4125,7 @@ mod tests { center_focused_column in arbitrary_center_focused_column(), ) -> Options { Options { - gaps: gaps.into(), + gaps, struts, center_focused_column, focus_ring, diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs index b166879b..eb72242f 100644 --- a/src/layout/monitor.rs +++ b/src/layout/monitor.rs @@ -7,7 +7,7 @@ use smithay::backend::renderer::element::utils::{ CropRenderElement, Relocate, RelocateRenderElement, }; use smithay::output::Output; -use smithay::utils::{Logical, Point, Rectangle, Scale}; +use smithay::utils::{Logical, Point, Rectangle}; use super::workspace::{ compute_working_area, Column, ColumnWidth, OutputId, Workspace, WorkspaceId, @@ -19,7 +19,7 @@ use crate::input::swipe_tracker::SwipeTracker; use crate::render_helpers::renderer::NiriRenderer; use crate::render_helpers::RenderTarget; use crate::rubber_band::RubberBand; -use crate::utils::{output_size, ResizeEdge}; +use crate::utils::{output_size, to_physical_precise_round, ResizeEdge}; /// Amount of touchpad movement to scroll the height of one workspace. const WORKSPACE_GESTURE_MOVEMENT: f64 = 300.; @@ -761,16 +761,16 @@ impl Monitor { /// Returns the geometry of the active tile relative to and clamped to the output. /// /// During animations, assumes the final view position. - pub fn active_tile_visual_rectangle(&self) -> Option> { + pub fn active_tile_visual_rectangle(&self) -> Option> { let mut rect = self.active_workspace_ref().active_tile_visual_rectangle()?; if let Some(switch) = &self.workspace_switch { - let size = output_size(&self.output); + let size = output_size(&self.output).to_f64(); let offset = switch.target_idx() - self.active_workspace_idx as f64; - let offset = (offset * size.h as f64).round() as i32; + let offset = offset * size.h; - let clip_rect = Rectangle::from_loc_and_size((0, -offset), size); + let clip_rect = Rectangle::from_loc_and_size((0., -offset), size); rect = rect.intersection(clip_rect)?; } @@ -780,16 +780,16 @@ impl Monitor { pub fn window_under( &self, pos_within_output: Point, - ) -> Option<(&W, Option>)> { + ) -> Option<(&W, Option>)> { match &self.workspace_switch { Some(switch) => { - let size = output_size(&self.output); + let size = output_size(&self.output).to_f64(); let render_idx = switch.current_idx(); let before_idx = render_idx.floor(); let after_idx = render_idx.ceil(); - let offset = ((render_idx - before_idx) * size.h as f64).round() as i32; + let offset = (render_idx - before_idx) * size.h; if after_idx < 0. || before_idx as usize >= self.workspaces.len() { return None; @@ -797,22 +797,22 @@ impl Monitor { let after_idx = after_idx as usize; - let (idx, ws_offset) = if pos_within_output.y < (size.h - offset) as f64 { + let (idx, ws_offset) = if pos_within_output.y < size.h - offset { if before_idx < 0. { return None; } - (before_idx as usize, Point::from((0, offset))) + (before_idx as usize, Point::from((0., offset))) } else { if after_idx >= self.workspaces.len() { return None; } - (after_idx, Point::from((0, -size.h + offset))) + (after_idx, Point::from((0., -size.h + offset))) }; let ws = &self.workspaces[idx]; - let (win, win_pos) = ws.window_under(pos_within_output + ws_offset.to_f64())?; + let (win, win_pos) = ws.window_under(pos_within_output + ws_offset)?; Some((win, win_pos.map(|p| p - ws_offset))) } None => { @@ -831,7 +831,7 @@ impl Monitor { let before_idx = render_idx.floor(); let after_idx = render_idx.ceil(); - let offset = ((render_idx - before_idx) * size.h as f64).round() as i32; + let offset = (render_idx - before_idx) * size.h; if after_idx < 0. || before_idx as usize >= self.workspaces.len() { return None; @@ -839,22 +839,22 @@ impl Monitor { let after_idx = after_idx as usize; - let (idx, ws_offset) = if pos_within_output.y < (size.h - offset) as f64 { + let (idx, ws_offset) = if pos_within_output.y < size.h - offset { if before_idx < 0. { return None; } - (before_idx as usize, Point::from((0, offset))) + (before_idx as usize, Point::from((0., offset))) } else { if after_idx >= self.workspaces.len() { return None; } - (after_idx, Point::from((0, -size.h + offset))) + (after_idx, Point::from((0., -size.h + offset))) }; let ws = &self.workspaces[idx]; - ws.resize_edges_under(pos_within_output + ws_offset.to_f64()) + ws.resize_edges_under(pos_within_output + ws_offset) } None => { let ws = &self.workspaces[self.active_workspace_idx]; @@ -880,10 +880,8 @@ impl Monitor { ) -> Vec> { let _span = tracy_client::span!("Monitor::render_elements"); - let output_scale = Scale::from(self.output.current_scale().fractional_scale()); - let output_transform = self.output.current_transform(); - let output_mode = self.output.current_mode().unwrap(); - let size = output_transform.transform_size(output_mode.size); + let scale = self.output.current_scale().fractional_scale(); + let size = output_size(&self.output); match &self.workspace_switch { Some(switch) => { @@ -891,7 +889,7 @@ impl Monitor { let before_idx = render_idx.floor(); let after_idx = render_idx.ceil(); - let offset = ((render_idx - before_idx) * size.h as f64).round() as i32; + let offset = (render_idx - before_idx) * size.h; if after_idx < 0. || before_idx as usize >= self.workspaces.len() { return vec![]; @@ -904,7 +902,7 @@ impl Monitor { Some(RelocateRenderElement::from_element( CropRenderElement::from_element( elem, - output_scale, + scale, // HACK: crop to infinite bounds for all sides except the side // where the workspaces join, // otherwise it will cut pixel shaders and mess up @@ -914,7 +912,7 @@ impl Monitor { (i32::MAX / 2, i32::MAX / 2), ), )?, - (0, -offset + size.h), + Point::from((0., -offset + size.h)).to_physical_precise_round(scale), Relocate::Relative, )) }); @@ -934,13 +932,13 @@ impl Monitor { Some(RelocateRenderElement::from_element( CropRenderElement::from_element( elem, - output_scale, + scale, Rectangle::from_extemities( (-i32::MAX / 2, -i32::MAX / 2), - (i32::MAX / 2, size.h), + (i32::MAX / 2, to_physical_precise_round(scale, size.h)), ), )?, - (0, -offset), + Point::from((0., -offset)).to_physical_precise_round(scale), Relocate::Relative, )) }); @@ -955,7 +953,7 @@ impl Monitor { Some(RelocateRenderElement::from_element( CropRenderElement::from_element( elem, - output_scale, + scale, // HACK: set infinite crop bounds due to a damage tracking bug // which causes glitched rendering for maximized GTK windows. // FIXME: use proper bounds after fixing the Crop element. diff --git a/src/layout/opening_window.rs b/src/layout/opening_window.rs index 38eb89ab..d265c463 100644 --- a/src/layout/opening_window.rs +++ b/src/layout/opening_window.rs @@ -55,8 +55,8 @@ impl OpenAnimation { &self, renderer: &mut GlesRenderer, elements: &[impl RenderElement], - geo_size: Size, - location: Point, + geo_size: Size, + location: Point, scale: Scale, ) -> anyhow::Result { let progress = self.anim.value(); @@ -75,17 +75,17 @@ impl OpenAnimation { let texture_size = geo.size.to_f64().to_logical(scale); if Shaders::get(renderer).program(ProgramType::Open).is_some() { - let mut area = Rectangle::from_loc_and_size(location.to_f64() + offset, texture_size); + let mut area = Rectangle::from_loc_and_size(location + offset, texture_size); // Expand the area a bit to allow for more varied effects. let mut target_size = area.size.upscale(1.5); target_size.w = f64::max(area.size.w + 1000., target_size.w); target_size.h = f64::max(area.size.h + 1000., target_size.h); - let diff = target_size.to_point() - area.size.to_point(); - area.loc -= diff.downscale(2.); - area.size += diff.to_size(); + let diff = (target_size.to_point() - area.size.to_point()).downscale(2.); + let diff = diff.to_physical_precise_round(scale).to_logical(scale); + area.loc -= diff; + area.size += diff.upscale(2.).to_size(); - let area = area.to_i32_up(); let area_loc = Vec2::new(area.loc.x as f32, area.loc.y as f32); let area_size = Vec2::new(area.size.w as f32, area.size.h as f32); @@ -135,7 +135,7 @@ impl OpenAnimation { let elem = PrimaryGpuTextureRenderElement(elem); - let center = geo_size.to_point().to_f64().downscale(2.); + let center = geo_size.to_point().downscale(2.); let elem = RescaleRenderElement::from_element( elem, (center - offset).to_physical_precise_round(scale), @@ -144,7 +144,7 @@ impl OpenAnimation { let elem = RelocateRenderElement::from_element( elem, - (location.to_f64() + offset).to_physical_precise_round(scale), + (location + offset).to_physical_precise_round(scale), Relocate::Relative, ); diff --git a/src/layout/tile.rs b/src/layout/tile.rs index 719f12bc..943a9574 100644 --- a/src/layout/tile.rs +++ b/src/layout/tile.rs @@ -1,10 +1,8 @@ -use std::cmp::max; use std::rc::Rc; use std::time::Duration; use niri_config::CornerRadius; use smithay::backend::allocator::Fourcc; -use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; use smithay::backend::renderer::element::{Element, Kind}; use smithay::backend::renderer::gles::GlesRenderer; use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform}; @@ -23,6 +21,7 @@ use crate::render_helpers::damage::ExtraDamage; use crate::render_helpers::renderer::NiriRenderer; use crate::render_helpers::resize::ResizeRenderElement; use crate::render_helpers::snapshot::RenderSnapshot; +use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement}; use crate::render_helpers::{render_to_encompassing_texture, RenderTarget}; /// Toplevel window with decorations. @@ -50,7 +49,7 @@ pub struct Tile { fullscreen_backdrop: SolidColorBuffer, /// The size we were requested to fullscreen into. - fullscreen_size: Size, + fullscreen_size: Size, /// The animation upon opening a window. open_animation: Option, @@ -70,6 +69,9 @@ pub struct Tile { /// Extra damage for clipped surface corner radius changes. rounded_corner_damage: RoundedCornerDamage, + /// Scale of the output the tile is on (and rounds its sizes to). + scale: f64, + /// Configurable properties of the layout. pub options: Rc, } @@ -93,18 +95,18 @@ type TileRenderSnapshot = #[derive(Debug)] struct ResizeAnimation { anim: Animation, - size_from: Size, + size_from: Size, snapshot: LayoutElementRenderSnapshot, } #[derive(Debug)] struct MoveAnimation { anim: Animation, - from: i32, + from: f64, } impl Tile { - pub fn new(window: W, options: Rc) -> Self { + pub fn new(window: W, scale: f64, options: Rc) -> Self { let rules = window.rules(); let border_config = rules.border.resolve_against(options.border); let focus_ring_config = rules.focus_ring.resolve_against(options.focus_ring.into()); @@ -114,7 +116,7 @@ impl Tile { border: FocusRing::new(border_config.into()), focus_ring: FocusRing::new(focus_ring_config.into()), is_fullscreen: false, // FIXME: up-to-date fullscreen right away, but we need size. - fullscreen_backdrop: SolidColorBuffer::new((0, 0), [0., 0., 0., 1.]), + fullscreen_backdrop: SolidColorBuffer::new((0., 0.), [0., 0., 0., 1.]), fullscreen_size: Default::default(), open_animation: None, resize_animation: None, @@ -122,11 +124,13 @@ impl Tile { move_y_animation: None, unmap_snapshot: None, rounded_corner_damage: Default::default(), + scale, options, } } - pub fn update_config(&mut self, options: Rc) { + pub fn update_config(&mut self, scale: f64, options: Rc) { + self.scale = scale; self.options = options; let rules = self.window.rules(); @@ -147,7 +151,7 @@ impl Tile { pub fn update_window(&mut self) { // FIXME: remove when we can get a fullscreen size right away. - if self.fullscreen_size != Size::from((0, 0)) { + if self.fullscreen_size != Size::from((0., 0.)) { self.is_fullscreen = self.window.is_fullscreen(); } @@ -160,16 +164,16 @@ impl Tile { let val = resize.anim.value(); let size_from = resize.size_from; - size.w = (size_from.w as f64 + (size.w - size_from.w) as f64 * val).round() as i32; - size.h = (size_from.h as f64 + (size.h - size_from.h) as f64 * val).round() as i32; + size.w = size_from.w + (size.w - size_from.w) * val; + size.h = size_from.h + (size.h - size_from.h) * val; size } else { animate_from.size }; - let change = self.window.size().to_point() - size_from.to_point(); - let change = max(change.x.abs(), change.y.abs()); + let change = self.window.size().to_f64().to_point() - size_from.to_point(); + let change = f64::max(change.x.abs(), change.y.abs()); if change > RESIZE_ANIMATION_THRESHOLD { let anim = Animation::new(0., 1., 0., self.options.animations.window_resize.anim); self.resize_animation = Some(ResizeAnimation { @@ -235,13 +239,13 @@ impl Tile { || self.move_y_animation.is_some() } - pub fn update(&mut self, is_active: bool, view_rect: Rectangle) { + pub fn update(&mut self, is_active: bool, view_rect: Rectangle) { let rules = self.window.rules(); let draw_border_with_background = rules .draw_border_with_background .unwrap_or_else(|| !self.window.has_ssd()); - let border_width = self.effective_border_width().unwrap_or(0); + let border_width = self.effective_border_width().unwrap_or(0.); let radius = if self.is_fullscreen { CornerRadius::default() } else { @@ -260,6 +264,7 @@ impl Tile { view_rect.size, ), radius, + self.scale, ); let draw_focus_ring_with_background = if self.effective_border_width().is_some() { @@ -281,20 +286,25 @@ impl Tile { !draw_focus_ring_with_background, view_rect, radius, + self.scale, ); } - pub fn render_offset(&self) -> Point { + pub fn scale(&self) -> f64 { + self.scale + } + + pub fn render_offset(&self) -> Point { let mut offset = Point::from((0., 0.)); if let Some(move_) = &self.move_x_animation { - offset.x += f64::from(move_.from) * move_.anim.value(); + offset.x += move_.from * move_.anim.value(); } if let Some(move_) = &self.move_y_animation { - offset.y += f64::from(move_.from) * move_.anim.value(); + offset.y += move_.from * move_.anim.value(); } - offset.to_i32_round() + offset } pub fn start_open_animation(&mut self) { @@ -310,16 +320,16 @@ impl Tile { self.resize_animation.as_ref().map(|resize| &resize.anim) } - pub fn animate_move_from(&mut self, from: Point) { + pub fn animate_move_from(&mut self, from: Point) { self.animate_move_x_from(from.x); self.animate_move_y_from(from.y); } - pub fn animate_move_x_from(&mut self, from: i32) { + pub fn animate_move_x_from(&mut self, from: f64) { self.animate_move_x_from_with_config(from, self.options.animations.window_movement.0); } - pub fn animate_move_x_from_with_config(&mut self, from: i32, config: niri_config::Animation) { + pub fn animate_move_x_from_with_config(&mut self, from: f64, config: niri_config::Animation) { let current_offset = self.render_offset().x; // Preserve the previous config if ongoing. @@ -334,11 +344,11 @@ impl Tile { }); } - pub fn animate_move_y_from(&mut self, from: i32) { + pub fn animate_move_y_from(&mut self, from: f64) { self.animate_move_y_from_with_config(from, self.options.animations.window_movement.0); } - pub fn animate_move_y_from_with_config(&mut self, from: i32, config: niri_config::Animation) { + pub fn animate_move_y_from_with_config(&mut self, from: f64, config: niri_config::Animation) { let current_offset = self.render_offset().y; // Preserve the previous config if ongoing. @@ -370,7 +380,7 @@ impl Tile { } /// Returns `None` if the border is hidden and `Some(width)` if it should be shown. - fn effective_border_width(&self) -> Option { + fn effective_border_width(&self) -> Option { if self.is_fullscreen { return None; } @@ -383,22 +393,27 @@ impl Tile { } /// Returns the location of the window's visual geometry within this Tile. - pub fn window_loc(&self) -> Point { - let mut loc = Point::from((0, 0)); + pub fn window_loc(&self) -> Point { + let mut loc = Point::from((0., 0.)); // In fullscreen, center the window in the given size. if self.is_fullscreen { - let window_size = self.window.size(); + let window_size = self.window_size(); let target_size = self.fullscreen_size; // Windows aren't supposed to be larger than the fullscreen size, but in case we get // one, leave it at the top-left as usual. if window_size.w < target_size.w { - loc.x += (target_size.w - window_size.w) / 2; + loc.x += (target_size.w - window_size.w) / 2.; } if window_size.h < target_size.h { - loc.y += (target_size.h - window_size.h) / 2; + loc.y += (target_size.h - window_size.h) / 2.; } + + // Round to physical pixels. + loc = loc + .to_physical_precise_round(self.scale) + .to_logical(self.scale); } if let Some(width) = self.effective_border_width() { @@ -408,68 +423,73 @@ impl Tile { loc } - pub fn tile_size(&self) -> Size { - let mut size = self.window.size(); + pub fn tile_size(&self) -> Size { + let mut size = self.window_size(); if self.is_fullscreen { // Normally we'd just return the fullscreen size here, but this makes things a bit // nicer if a fullscreen window is bigger than the fullscreen size for some reason. - size.w = max(size.w, self.fullscreen_size.w); - size.h = max(size.h, self.fullscreen_size.h); + size.w = f64::max(size.w, self.fullscreen_size.w); + size.h = f64::max(size.h, self.fullscreen_size.h); return size; } if let Some(width) = self.effective_border_width() { - size.w = size.w.saturating_add(width * 2); - size.h = size.h.saturating_add(width * 2); + size.w += width * 2.; + size.h += width * 2.; } size } - pub fn window_size(&self) -> Size { - self.window.size() + pub fn window_size(&self) -> Size { + let mut size = self.window.size().to_f64(); + size = size + .to_physical_precise_round(self.scale) + .to_logical(self.scale); + size } - fn animated_window_size(&self) -> Size { - let mut size = self.window.size(); + fn animated_window_size(&self) -> Size { + let mut size = self.window_size(); if let Some(resize) = &self.resize_animation { let val = resize.anim.value(); - let size_from = resize.size_from; + let size_from = resize.size_from.to_f64(); - size.w = (size_from.w as f64 + (size.w - size_from.w) as f64 * val).round() as i32; - size.w = max(1, size.w); - size.h = (size_from.h as f64 + (size.h - size_from.h) as f64 * val).round() as i32; - size.h = max(1, size.h); + size.w = f64::max(1., size_from.w + (size.w - size_from.w) * val); + size.h = f64::max(1., size_from.h + (size.h - size_from.h) * val); + size = size + .to_physical_precise_round(self.scale) + .to_logical(self.scale); } size } - fn animated_tile_size(&self) -> Size { + fn animated_tile_size(&self) -> Size { let mut size = self.animated_window_size(); if self.is_fullscreen { // Normally we'd just return the fullscreen size here, but this makes things a bit // nicer if a fullscreen window is bigger than the fullscreen size for some reason. - size.w = max(size.w, self.fullscreen_size.w); - size.h = max(size.h, self.fullscreen_size.h); + size.w = f64::max(size.w, self.fullscreen_size.w); + size.h = f64::max(size.h, self.fullscreen_size.h); return size; } if let Some(width) = self.effective_border_width() { - size.w = size.w.saturating_add(width * 2); - size.h = size.h.saturating_add(width * 2); + size.w += width * 2.; + size.h += width * 2.; } size } - pub fn buf_loc(&self) -> Point { - let mut loc = Point::from((0, 0)); + pub fn buf_loc(&self) -> Point { + let mut loc = Point::from((0., 0.)); loc += self.window_loc(); - loc += self.window.buf_loc(); + loc += self.window.buf_loc().to_f64(); loc } @@ -479,74 +499,85 @@ impl Tile { } pub fn is_in_activation_region(&self, point: Point) -> bool { - let activation_region = Rectangle::from_loc_and_size((0, 0), self.tile_size()); - activation_region.to_f64().contains(point) + let activation_region = Rectangle::from_loc_and_size((0., 0.), self.tile_size()); + activation_region.contains(point) } - pub fn request_tile_size(&mut self, mut size: Size, animate: bool) { + pub fn request_tile_size(&mut self, mut size: Size, animate: bool) { // Can't go through effective_border_width() because we might be fullscreen. if !self.border.is_off() { let width = self.border.width(); - size.w = max(1, size.w - width * 2); - size.h = max(1, size.h - width * 2); + size.w = f64::max(1., size.w - width * 2.); + size.h = f64::max(1., size.h - width * 2.); } - self.window.request_size(size, animate); + // The size request has to be i32 unfortunately, due to Wayland. We floor here instead of + // round to avoid situations where proportionally-sized columns don't fit on the screen + // exactly. + self.window.request_size(size.to_i32_floor(), animate); + } + + pub fn tile_width_for_window_width(&self, size: f64) -> f64 { + if self.border.is_off() { + size + } else { + size + self.border.width() * 2. + } } - pub fn tile_width_for_window_width(&self, size: i32) -> i32 { + pub fn tile_height_for_window_height(&self, size: f64) -> f64 { if self.border.is_off() { size } else { - size.saturating_add(self.border.width() * 2) + size + self.border.width() * 2. } } - pub fn tile_height_for_window_height(&self, size: i32) -> i32 { + pub fn window_width_for_tile_width(&self, size: f64) -> f64 { if self.border.is_off() { size } else { - size.saturating_add(self.border.width() * 2) + size - self.border.width() * 2. } } - pub fn window_height_for_tile_height(&self, size: i32) -> i32 { + pub fn window_height_for_tile_height(&self, size: f64) -> f64 { if self.border.is_off() { size } else { - size.saturating_sub(self.border.width() * 2) + size - self.border.width() * 2. } } - pub fn request_fullscreen(&mut self, size: Size) { + pub fn request_fullscreen(&mut self, size: Size) { self.fullscreen_backdrop.resize(size); self.fullscreen_size = size; - self.window.request_fullscreen(size); + self.window.request_fullscreen(size.to_i32_round()); } - pub fn min_size(&self) -> Size { - let mut size = self.window.min_size(); + pub fn min_size(&self) -> Size { + let mut size = self.window.min_size().to_f64(); if let Some(width) = self.effective_border_width() { - size.w = max(1, size.w); - size.h = max(1, size.h); + size.w = f64::max(1., size.w); + size.h = f64::max(1., size.h); - size.w = size.w.saturating_add(width * 2); - size.h = size.h.saturating_add(width * 2); + size.w += width * 2.; + size.h += width * 2.; } size } - pub fn max_size(&self) -> Size { - let mut size = self.window.max_size(); + pub fn max_size(&self) -> Size { + let mut size = self.window.max_size().to_f64(); if let Some(width) = self.effective_border_width() { - if size.w > 0 { - size.w = size.w.saturating_add(width * 2); + if size.w > 0. { + size.w += width * 2.; } - if size.h > 0 { - size.h = size.h.saturating_add(width * 2); + if size.h > 0. { + size.h += width * 2.; } } @@ -567,7 +598,7 @@ impl Tile { fn render_inner( &self, renderer: &mut R, - location: Point, + location: Point, scale: Scale, focus_ring: bool, target: RenderTarget, @@ -581,7 +612,7 @@ impl Tile { }; let window_loc = self.window_loc(); - let window_size = self.window_size(); + let window_size = self.window_size().to_f64(); let animated_window_size = self.animated_window_size(); let window_render_loc = location + window_loc; let area = Rectangle::from_loc_and_size(window_render_loc, animated_window_size); @@ -609,7 +640,7 @@ impl Tile { if let Some(texture_from) = resize.snapshot.texture(gles_renderer, scale, target) { let window_elements = self.window.render_normal( gles_renderer, - Point::from((0, 0)), + Point::from((0., 0.)), scale, 1., target, @@ -664,8 +695,7 @@ impl Tile { resize_fallback = Some( SolidColorRenderElement::from_buffer( &fallback_buffer, - area.loc.to_physical_precise_round(scale), - scale, + area.loc, alpha, Kind::Unspecified, ) @@ -726,11 +756,11 @@ impl Tile { if radius != CornerRadius::default() && has_border_shader { return BorderRenderElement::new( geo.size, - Rectangle::from_loc_and_size((0, 0), geo.size), + Rectangle::from_loc_and_size((0., 0.), geo.size), elem.color(), elem.color(), 0., - Rectangle::from_loc_and_size((0, 0), geo.size), + Rectangle::from_loc_and_size((0., 0.), geo.size), 0., radius, ) @@ -758,8 +788,7 @@ impl Tile { let elem = self.is_fullscreen.then(|| { SolidColorRenderElement::from_buffer( &self.fullscreen_backdrop, - location.to_physical_precise_round(scale), - scale, + location, 1., Kind::Unspecified, ) @@ -769,23 +798,19 @@ impl Tile { let elem = self.effective_border_width().map(|width| { self.border - .render(renderer, location + Point::from((width, width)), scale) + .render(renderer, location + Point::from((width, width))) .map(Into::into) }); let rv = rv.chain(elem.into_iter().flatten()); - let elem = focus_ring.then(|| { - self.focus_ring - .render(renderer, location, scale) - .map(Into::into) - }); + let elem = focus_ring.then(|| self.focus_ring.render(renderer, location).map(Into::into)); rv.chain(elem.into_iter().flatten()) } pub fn render( &self, renderer: &mut R, - location: Point, + location: Point, scale: Scale, focus_ring: bool, target: RenderTarget, @@ -798,7 +823,7 @@ impl Tile { if let Some(open) = &self.open_animation { let renderer = renderer.as_gles_renderer(); let elements = - self.render_inner(renderer, Point::from((0, 0)), scale, focus_ring, target); + self.render_inner(renderer, Point::from((0., 0.)), scale, focus_ring, target); let elements = elements.collect::>>(); match open.render(renderer, &elements, self.tile_size(), location, scale) { Ok(elem) => { @@ -843,7 +868,7 @@ impl Tile { let contents = self.render( renderer, - Point::from((0, 0)), + Point::from((0., 0.)), scale, false, RenderTarget::Output, @@ -852,7 +877,7 @@ impl Tile { // A bit of a hack to render blocked out as for screencast, but I think it's fine here. let blocked_out_contents = self.render( renderer, - Point::from((0, 0)), + Point::from((0., 0.)), scale, false, RenderTarget::Screencast, 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 { /// /// 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, + view_size: Size, /// 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, + working_area: Rectangle, /// Columns of windows on this workspace. pub columns: Vec>, @@ -79,7 +80,7 @@ pub struct Workspace { /// 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, @@ -94,15 +95,18 @@ pub struct Workspace { /// 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, + activate_prev_column_on_removal: Option, /// View offset to restore after unfullscreening. - view_offset_before_fullscreen: Option, + view_offset_before_fullscreen: Option, /// Windows in the closing animation. closing_windows: Vec, - /// Configurable properties of the layout. + /// Configurable properties of the layout as received from the parent monitor. + pub base_options: Rc, + + /// Configurable properties of the layout with logical sizes adjusted for the current `scale`. pub options: Rc, /// 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 { window: W::Id, - original_window_size: Size, + original_window_size: Size,