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