From 6509cb714a41257de697158a3546238092a5b954 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Sat, 24 Oct 2026 22:03:23 +0100 Subject: [PATCH] fix(panel): snap Morph dimensions before centering --- shell/ferese-shell/src/panel_hub.rs | 74 ++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/shell/ferese-shell/src/panel_hub.rs b/shell/ferese-shell/src/panel_hub.rs index 8103da5..ed27a5c 100644 --- a/shell/ferese-shell/src/panel_hub.rs +++ b/shell/ferese-shell/src/panel_hub.rs @@ -594,6 +594,8 @@ impl FereseShell { let panel = self.panel_for_bar(id); let effects = entry.effects.as_ref(); let bounds = sample.geometry.bounds; + let grid = ferese_panel::shape::PixelGrid::try_new(entry.scale) + .expect("panel output scale is positive and finite"); let (target_size, previous_size) = { let runtime = entry.runtime.hub.as_ref().expect("Morph runtime").borrow(); (runtime.hub.target_size(), runtime.previous.size) @@ -626,8 +628,10 @@ impl FereseShell { } let body = container(layers) .id("ferese-panel-hub") - .width(bounds.width as f32) - .height(bounds.height as f32) + // Snap dimensions before centering. Snapping the two edges of an + // unsnapped hub can alternate its width by a pixel on odd surfaces. + .width(grid.snap(bounds.width) as f32) + .height(grid.snap(bounds.height) as f32) .clip(true); let root = container(body) .center_x(Length::Fill) @@ -4406,6 +4410,72 @@ mod tests { } } + #[test] + fn hub_dimensions_snap_before_centering_on_odd_and_even_backing_surfaces() { + use cosmic::iced::advanced::renderer::Headless; + #[derive(Default)] + struct Bounds(Option); + impl widget::Operation for Bounds { + fn traverse(&mut self, children: &mut dyn FnMut(&mut dyn widget::Operation)) { + children(self); + } + fn container(&mut self, id: Option<&widget::Id>, bounds: Rectangle) { + if id == Some(&widget::Id::new("ferese-panel-hub")) { + self.0 = Some(bounds); + } + } + } + let renderer = cosmic::iced::futures::executor::block_on(cosmic::Renderer::new( + cosmic::iced::Font::default(), + cosmic::iced::Pixels(14.), + Some("tiny-skia"), + )) + .unwrap(); + let mut shell = shell(); + let id = shell.outputs[0].bar; + let sample = shell.outputs[0] + .runtime + .hub + .as_ref() + .unwrap() + .borrow() + .hub + .sample(); + for scale in [1., 1.25, 1.5, 1.75] { + shell.outputs[0].scale = scale; + for backing_width in [329., 330., 401.] { + for delta in [-0.02, 0., 0.02] { + let mut sample = sample; + sample.geometry.bounds.width = 320. + delta; + sample.geometry.bounds.height = 48. + delta; + let mut view = shell.view_hub_at(id, sample, None, None); + let mut tree = widget::Tree::new(&view); + let node = view.as_widget_mut().layout( + &mut tree, + &renderer, + &layout::Limits::new(Size::ZERO, Size::new(backing_width, 230.)), + ); + let mut bounds = Bounds::default(); + view.as_widget_mut().operate( + &mut tree, + Layout::new(&node), + &renderer, + &mut bounds, + ); + let bounds = bounds.0.unwrap(); + assert_eq!( + bounds.width, 320., + "scale={scale}, backing={backing_width}, delta={delta}" + ); + assert_eq!( + bounds.height, 48., + "scale={scale}, backing={backing_width}, delta={delta}" + ); + } + } + } + } + #[test] fn native_input_follows_the_animated_hub_and_bottom_anchor_mirrors_it() { use cosmic::iced::advanced::renderer::Headless;