From bf4f5f77f734e6a2ee94b83efe7fbbf46aec17cc Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Fri, 25 Sep 2026 01:13:21 +0100 Subject: [PATCH] fix: tame animated resize scaling --- compositor/src/handlers/mod.rs | 6 +++--- compositor/src/input.rs | 9 +++++---- compositor/src/state.rs | 2 +- compositor/src/winit.rs | 2 +- crates/ferese-animation/src/lib.rs | 16 +++++++++------- docs/ferese-window-manager-spec.md | 14 ++++++++------ 6 files changed, 27 insertions(+), 22 deletions(-) diff --git a/compositor/src/handlers/mod.rs b/compositor/src/handlers/mod.rs index 909ea62..7d2fb2a 100644 --- a/compositor/src/handlers/mod.rs +++ b/compositor/src/handlers/mod.rs @@ -194,14 +194,14 @@ impl PointerConstraintsHandler for Ferese { }; if focused_surface == *surface { let current = pointer.current_location(); - let scale = self + let (scale_x, scale_y) = self .window_under_visual(current) .and_then(|window| self.visual_scale_for_window(&window)) - .unwrap_or(1.0); + .unwrap_or((1.0, 1.0)); let current_surface_location = current - origin; let offset = location - current_surface_location; - pointer.set_location(current + Point::from((offset.x * scale, offset.y * scale))); + pointer.set_location(current + Point::from((offset.x * scale_x, offset.y * scale_y))); } } } diff --git a/compositor/src/input.rs b/compositor/src/input.rs index 1dc2636..3834e69 100644 --- a/compositor/src/input.rs +++ b/compositor/src/input.rs @@ -174,10 +174,10 @@ impl Ferese { let pointer = self.seat.get_pointer().expect("seat has a pointer"); let position = pointer.current_location(); let focus = self.surface_under(position); - let scale = self + let (scale_x, scale_y) = self .window_under_visual(position) .and_then(|window| self.visual_scale_for_window(&window)) - .unwrap_or(1.0); + .unwrap_or((1.0, 1.0)); let delta = event.delta(); let delta_unaccel = event.delta_unaccel(); @@ -185,8 +185,9 @@ impl Ferese { self, focus, &RelativeMotionEvent { - delta: (delta.x / scale, delta.y / scale).into(), - delta_unaccel: (delta_unaccel.x / scale, delta_unaccel.y / scale).into(), + delta: (delta.x / scale_x, delta.y / scale_y).into(), + delta_unaccel: (delta_unaccel.x / scale_x, delta_unaccel.y / scale_y) + .into(), utime: (event.time_msec() as u64).saturating_mul(1_000), }, ); diff --git a/compositor/src/state.rs b/compositor/src/state.rs index c324193..f43d34d 100644 --- a/compositor/src/state.rs +++ b/compositor/src/state.rs @@ -332,7 +332,7 @@ impl Ferese { }) } - pub fn visual_scale_for_window(&self, window: &Window) -> Option { + pub fn visual_scale_for_window(&self, window: &Window) -> Option<(f64, f64)> { let id = self.window_ids.get(window)?; self.window_geometry.get(id)?.visual_scale() } diff --git a/compositor/src/winit.rs b/compositor/src/winit.rs index 5ef900c..4efbc2f 100644 --- a/compositor/src/winit.rs +++ b/compositor/src/winit.rs @@ -232,7 +232,7 @@ pub(crate) fn animated_window_elements( constrain, ConstrainBehavior { reference: ConstrainReference::Geometry, - behavior: ConstrainScaleBehavior::Zoom, + behavior: ConstrainScaleBehavior::Stretch, align: ConstrainAlign::TOP | ConstrainAlign::LEFT, }, )) diff --git a/crates/ferese-animation/src/lib.rs b/crates/ferese-animation/src/lib.rs index 21385c9..94801de 100644 --- a/crates/ferese-animation/src/lib.rs +++ b/crates/ferese-animation/src/lib.rs @@ -237,19 +237,22 @@ impl WindowGeometry { pub fn inverse_visual_point(&self, x: f64, y: f64) -> Option<(f64, f64)> { let visual = self.visual.current; - let scale = self.visual_scale()?; + let (scale_x, scale_y) = self.visual_scale()?; - Some(((x - visual.x) / scale, (y - visual.y) / scale)) + Some(((x - visual.x) / scale_x, (y - visual.y) / scale_y)) } - pub fn visual_scale(&self) -> Option { + pub fn visual_scale(&self) -> Option<(f64, f64)> { let source = self.client.committed_size?; let visual = self.visual.current; if source.width <= 0 || source.height <= 0 || visual.width <= 0.0 || visual.height <= 0.0 { return None; } - Some((visual.width / f64::from(source.width)).max(visual.height / f64::from(source.height))) + Some(( + visual.width / f64::from(source.width), + visual.height / f64::from(source.height), + )) } } @@ -381,15 +384,14 @@ mod tests { } #[test] - fn inverse_visual_point_matches_uniform_cover_transform() { + fn inverse_visual_point_matches_stretched_transform() { let initial = Rect::new(100.0, 50.0, 400.0, 300.0); let mut geometry = WindowGeometry::new(initial, Some(ClientSize::from_rect(initial))); geometry.visual.current = Rect::new(200.0, 100.0, 600.0, 600.0); - // Cover scales the 4:3 source by 2x for a square allocation, then clips it. assert_eq!( geometry.inverse_visual_point(600.0, 500.0), - Some((200.0, 200.0)) + Some((400.0 * 2.0 / 3.0, 200.0)) ); } diff --git a/docs/ferese-window-manager-spec.md b/docs/ferese-window-manager-spec.md index b6d4560..09e5e20 100644 --- a/docs/ferese-window-manager-spec.md +++ b/docs/ferese-window-manager-spec.md @@ -549,12 +549,14 @@ to avoid a visible source-size discontinuity. Whether using a live committed buffer or snapshot, client content MUST remain aligned with the visual geometry used for hit testing. -During a non-interactive resize, committed content is scaled uniformly to cover -the visual content box and clipped to that box, anchored at its top-left content -origin. Ferese MUST NOT expose uninitialized regions. When a matching commit -arrives it replaces the scaled source without restarting the animation. A commit -is matching when its acknowledged configure and effective buffer geometry match -the requested client size after scale and transform. +During a non-interactive resize, committed content is scaled independently on +each axis to fill the visual content box and clipped to that box, anchored at +its top-left content origin. This avoids the oversized crop produced by uniform +cover scaling when only one dimension changes. Ferese MUST NOT expose +uninitialized regions. When a matching commit arrives it replaces the scaled +source without restarting the animation. A commit is matching when its +acknowledged configure and effective buffer geometry match the requested client +size after scale and transform. If a client has not acknowledged and committed the final configure within 500 ms, Ferese MUST stop waiting for visual completion, retain the target layout,