fix: configure zoom clients at final size
This commit is contained in:
1 parent
03033bf5c3
commit
b3124119d2
2 files changed
+59
-42
No files matched your search
@@ -356,20 +356,10 @@ impl WindowGeometry {
|
|||||||
self.presentation_size_request(now)
|
self.presentation_size_request(now)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pace intermediate resizes to client commits, but always deliver the final
|
/// Configure the destination once. Intermediate sizes make clients reflow
|
||||||
/// size. Fast clients redraw near the presented size instead of one giant
|
/// and replace buffers on every frame, disrupting an otherwise smooth zoom.
|
||||||
/// buffer being stretched throughout the zoom.
|
|
||||||
pub fn presentation_size_request(&mut self, now: Duration) -> Option<ClientSize> {
|
pub fn presentation_size_request(&mut self, now: Duration) -> Option<ClientSize> {
|
||||||
if self.zoom.is_some() && self.client.waiting_since.is_some() && !self.client.timed_out(now)
|
let size = ClientSize::from_rect(self.logical);
|
||||||
{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let rect = if self.zoom.is_some() {
|
|
||||||
self.visual.current
|
|
||||||
} else {
|
|
||||||
self.logical
|
|
||||||
};
|
|
||||||
let size = ClientSize::from_rect(rect);
|
|
||||||
self.client.request_size(size, now).then_some(size)
|
self.client.request_size(size, now).then_some(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,37 +621,63 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn slow_client_does_not_queue_intermediate_sizes_but_gets_final_size() {
|
fn zoom_requests_destination_once_even_with_a_responsive_client() {
|
||||||
let start = Rect::new(0.0, 0.0, 600.0, 800.0);
|
let start = Rect::new(0.0, 0.0, 600.0, 800.0);
|
||||||
let end = Rect::new(0.0, 0.0, 1920.0, 1080.0);
|
let end = Rect::new(0.0, 0.0, 1920.0, 1080.0);
|
||||||
let mut geometry = WindowGeometry::new(start, Some(ClientSize::from_rect(start)));
|
let mut geometry = WindowGeometry::new(start, Some(ClientSize::from_rect(start)));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
geometry.set_presentation_target(end, true, Duration::ZERO),
|
geometry.set_presentation_target(end, true, Duration::ZERO),
|
||||||
None
|
|
||||||
);
|
|
||||||
geometry.advance(Duration::from_millis(16), SpringConfig::default(), true);
|
|
||||||
let first = geometry
|
|
||||||
.presentation_size_request(Duration::from_millis(16))
|
|
||||||
.unwrap();
|
|
||||||
assert_ne!(first, ClientSize::from_rect(end));
|
|
||||||
geometry.advance(Duration::from_millis(16), SpringConfig::default(), true);
|
|
||||||
assert_eq!(
|
|
||||||
geometry.presentation_size_request(Duration::from_millis(32)),
|
|
||||||
None
|
|
||||||
);
|
|
||||||
geometry.client.commit(first);
|
|
||||||
assert!(
|
|
||||||
geometry
|
|
||||||
.presentation_size_request(Duration::from_millis(32))
|
|
||||||
.is_some()
|
|
||||||
);
|
|
||||||
for _ in 0..240 {
|
|
||||||
geometry.advance(Duration::from_millis(8), SpringConfig::default(), true);
|
|
||||||
}
|
|
||||||
assert_eq!(
|
|
||||||
geometry.presentation_size_request(Duration::from_secs(2)),
|
|
||||||
Some(ClientSize::from_rect(end))
|
Some(ClientSize::from_rect(end))
|
||||||
);
|
);
|
||||||
|
geometry.client.commit(ClientSize::from_rect(end));
|
||||||
|
for frame in 0..240 {
|
||||||
|
geometry.advance(Duration::from_millis(8), SpringConfig::default(), true);
|
||||||
|
assert_eq!(
|
||||||
|
geometry.presentation_size_request(Duration::from_millis(frame * 8)),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
geometry.set_presentation_target(start, false, Duration::from_secs(2)),
|
||||||
|
Some(ClientSize::from_rect(start))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn left_and_right_zoom_edges_do_not_reverse_direction() {
|
||||||
|
let fullscreen = Rect::new(0.0, 0.0, 1920.0, 1080.0);
|
||||||
|
for tiled in [
|
||||||
|
Rect::new(12.0, 12.0, 942.0, 1056.0),
|
||||||
|
Rect::new(966.0, 12.0, 942.0, 1056.0),
|
||||||
|
] {
|
||||||
|
for rate in [60, 144] {
|
||||||
|
let mut geometry = WindowGeometry::new(tiled, Some(ClientSize::from_rect(tiled)));
|
||||||
|
for (target, enabled) in [(fullscreen, true), (tiled, false)] {
|
||||||
|
geometry.set_presentation_target(target, enabled, Duration::ZERO);
|
||||||
|
let mut previous = geometry.visual.current;
|
||||||
|
for _ in 0..rate * 2 {
|
||||||
|
geometry.advance(
|
||||||
|
Duration::from_secs_f64(1.0 / f64::from(rate)),
|
||||||
|
SpringConfig::default(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
let current = geometry.visual.current;
|
||||||
|
let edges = |rect: Rect| {
|
||||||
|
[rect.x, rect.y, rect.x + rect.width, rect.y + rect.height]
|
||||||
|
};
|
||||||
|
for ((before, after), end) in edges(previous)
|
||||||
|
.into_iter()
|
||||||
|
.zip(edges(current))
|
||||||
|
.zip(edges(target))
|
||||||
|
{
|
||||||
|
assert!((after - end).abs() <= (before - end).abs() + 1e-9);
|
||||||
|
}
|
||||||
|
previous = current;
|
||||||
|
}
|
||||||
|
assert_eq!(geometry.visual.current, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Animation regression checks
|
# Animation regression checks
|
||||||
|
|
||||||
Run `cargo test -p ferese-animation -p ferese --offline` for transition,
|
Run `cargo test -p ferese-animation -p ferese --offline` for transition,
|
||||||
resize pacing, reduced motion, stacking, and refresh-rate regression tests.
|
resize requests, reduced motion, stacking, and refresh-rate regression tests.
|
||||||
|
|
||||||
For visual validation, use a disposable Ferese session with three tiled windows
|
For visual validation, use a disposable Ferese session with three tiled windows
|
||||||
containing text and at least one floating window. Test both the nested and DRM
|
containing text and at least one floating window. Test both the nested and DRM
|
||||||
@@ -15,11 +15,12 @@ single screenshots cannot establish smoothness.
|
|||||||
| Cycle width, then immediately Super+F | The old column-width spring cannot override fullscreen width. |
|
| Cycle width, then immediately Super+F | The old column-width spring cannot override fullscreen width. |
|
||||||
| Exit fullscreen and immediately resize or scroll | The current frame is retained when the target changes; scrolling resumes without a position jump. |
|
| Exit fullscreen and immediately resize or scroll | The current frame is retained when the target changes; scrolling resumes without a position jump. |
|
||||||
| Drag a floating window; resize from its top-left corner | The window follows the pointer directly. The opposite edge stays anchored. Delayed client commits do not move or raise it. |
|
| Drag a floating window; resize from its top-left corner | The window follows the pointer directly. The opposite edge stays anchored. Delayed client commits do not move or raise it. |
|
||||||
| Zoom a slow-redrawing client | Intermediate resize requests wait for commits or timeout. The final size is always requested and the animation keeps moving. |
|
| Zoom responsive and slow-redrawing clients | Each zoom requests the destination size once. Client redraws do not generate intermediate resize requests or restart the motion. |
|
||||||
|
| Zoom the leftmost and rightmost columns in and out | Each outer edge moves monotonically toward its destination; neither side trembles or reverses near the screen edge. |
|
||||||
| Disable borders and shadows, then zoom | Rounded corner pixels repaint through the final frame; no corner remnants remain. |
|
| Disable borders and shadows, then zoom | Rounded corner pixels repaint through the final frame; no corner remnants remain. |
|
||||||
| Repeat on 60 Hz and 144 Hz outputs; move focus between them | Similar elapsed-time motion with no double advancement. Check frame timing on actual DRM hardware. |
|
| Repeat on 60 Hz and 144 Hz outputs; move focus between them | Similar elapsed-time motion with no double advancement. Check frame timing on actual DRM hardware. |
|
||||||
| Enable reduced motion | Geometry and decorations reach their final values together. |
|
| Enable reduced motion | Geometry and decorations reach their final values together. |
|
||||||
|
|
||||||
The zoom resize policy asks responsive clients to redraw near the current visual
|
Zoom requests the destination client size once and scales the available buffer
|
||||||
size. A late client buffer is still stretched as a fallback; this is not a buffer
|
along the transition. This avoids repeated content reflow during motion. It is
|
||||||
snapshot or a guarantee of perfectly sharp text on every intermediate frame.
|
not a buffer snapshot or a guarantee of perfectly sharp intermediate text.
|
||||||
Reference in new issue
Block a user