aboutsummaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-06-17 09:16:28 +0300
committerIvan Molodetskikh <yalterz@gmail.com>2024-06-18 14:01:28 +0300
commit1dae45c58d7eabeda21ef490d712915890bf6cff (patch)
tree62c473ab1662a1161ed522517ea57b7bd8db340c /src/layout
parent997119c44338ad96a40b4a1d6e958f77062a37ef (diff)
downloadniri-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')
-rw-r--r--src/layout/closing_window.rs25
-rw-r--r--src/layout/focus_ring.rs73
-rw-r--r--src/layout/mod.rs204
-rw-r--r--src/layout/monitor.rs56
-rw-r--r--src/layout/opening_window.rs18
-rw-r--r--src/layout/tile.rs221
-rw-r--r--src/layout/workspace.rs560
7 files changed, 708 insertions, 449 deletions
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.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<Value = u16> {
+ #[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<Value = f64> {
// 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<Value = Struts> {
@@ -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<W: LayoutElement> Monitor<W> {
/// 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<Rectangle<i32, Logical>> {
+ pub fn active_tile_visual_rectangle(&self) -> Option<Rectangle<f64, Logical>> {
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<W: LayoutElement> Monitor<W> {
pub fn window_under(
&self,
pos_within_output: Point<f64, Logical>,
- ) -> Option<(&W, Option<Point<i32, Logical>>)> {
+ ) -> Option<(&W, Option<Point<f64, Logical>>)> {
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<W: LayoutElement> Monitor<W> {
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<W: LayoutElement> Monitor<W> {
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<W: LayoutElement> Monitor<W> {
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<W: LayoutElement> Monitor<W> {
) -> Vec<MonitorRenderElement<R>> {
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<W: LayoutElement> Monitor<W> {
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<W: LayoutElement> Monitor<W> {
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<W: LayoutElement> Monitor<W> {
(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<W: LayoutElement> Monitor<W> {
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)),