diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2025-08-12 22:34:13 +0300 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2025-08-14 15:58:59 +0300 |
| commit | 7a6be2a923d8d19d544bb6c52cf172b89ca8298c (patch) | |
| tree | 4f06c5b1bb4b26083ebb0d5e848b4c1ea5b2ca6d | |
| parent | bba9ca1fa273ec57748f2bfabcbf6b19e9200512 (diff) | |
| download | niri-7a6be2a923d8d19d544bb6c52cf172b89ca8298c.tar.gz niri-7a6be2a923d8d19d544bb6c52cf172b89ca8298c.tar.bz2 niri-7a6be2a923d8d19d544bb6c52cf172b89ca8298c.zip | |
Add basic client-server resize animation tests
| -rw-r--r-- | src/tests/animations.rs | 195 | ||||
| -rw-r--r-- | src/tests/mod.rs | 1 |
2 files changed, 196 insertions, 0 deletions
diff --git a/src/tests/animations.rs b/src/tests/animations.rs new file mode 100644 index 00000000..d930d254 --- /dev/null +++ b/src/tests/animations.rs @@ -0,0 +1,195 @@ +use std::fmt::Write as _; +use std::time::Duration; + +use insta::assert_snapshot; +use niri_config::{AnimationCurve, AnimationKind, Config, EasingParams, FloatOrInt}; +use niri_ipc::SizeChange; +use smithay::utils::{Point, Size}; +use wayland_client::protocol::wl_surface::WlSurface; + +use super::client::ClientId; +use super::*; +use crate::niri::Niri; + +fn format_tiles(niri: &Niri) -> String { + let mut buf = String::new(); + let ws = niri.layout.active_workspace().unwrap(); + let mut tiles: Vec<_> = ws.tiles_with_render_positions().collect(); + + // We sort by id since that gives us a consistent order (from first opened to last), but we + // don't print the id since it's nondeterministic (the id is a global counter across all + // running tests in the same binary). + tiles.sort_by_key(|(tile, _, _)| tile.window().id().get()); + for (tile, pos, _visible) in tiles { + let Size { w, h, .. } = tile.animated_tile_size(); + let Point { x, y, .. } = pos; + writeln!(&mut buf, "{w:>3.0} × {h:>3.0} at x:{x:>3.0} y:{y:>3.0}").unwrap(); + } + buf +} + +fn create_window(f: &mut Fixture, id: ClientId, w: u16, h: u16) -> WlSurface { + let window = f.client(id).create_window(); + let surface = window.surface.clone(); + window.commit(); + f.roundtrip(id); + + let window = f.client(id).window(&surface); + window.attach_new_buffer(); + window.set_size(w, h); + window.ack_last_and_commit(); + f.roundtrip(id); + + surface +} + +fn complete_animations(niri: &mut Niri) { + niri.clock.set_complete_instantly(true); + niri.advance_animations(); + niri.clock.set_complete_instantly(false); +} + +fn set_time(niri: &mut Niri, time: Duration) { + // This is a bit involved because we're dealing with an AdjustableClock that maintains its own + // internal current_time. + + // First, reset current_time to zero by matching unadjusted time to it (at rate 0.0), then + // setting unadjusted time to zero at rate 1.0 (causing current_time to also go to zero). + let now = niri.clock.now(); + niri.clock.set_unadjusted(now); + let _ = niri.clock.now(); + niri.clock.set_unadjusted(Duration::ZERO); + niri.clock.set_rate(1.0); + let _ = niri.clock.now(); + + // Now, set the desired time at rate 1.0. + niri.clock.set_unadjusted(time); + let _ = niri.clock.now(); + + // Freeze the clock so that clear() inside the niri loop callback followed by some get() + // doesn't replace it with the monotonic time. + niri.clock.set_rate(0.0); +} + +// Sets up a fixture with linear animations, a renderer, and an output. +fn set_up() -> Fixture { + const LINEAR: AnimationKind = AnimationKind::Easing(EasingParams { + duration_ms: 1000, + curve: AnimationCurve::Linear, + }); + + let mut config = Config::default(); + config.layout.gaps = FloatOrInt(0.0); + config.animations.window_resize.anim.kind = LINEAR; + config.animations.window_movement.0.kind = LINEAR; + + let mut f = Fixture::with_config(config); + f.niri_state().backend.headless().add_renderer().unwrap(); + f.add_output(1, (1920, 1080)); + + f +} + +fn set_up_two_in_column() -> (Fixture, ClientId, WlSurface, WlSurface) { + let mut f = set_up(); + + let id = f.add_client(); + + let surface1 = create_window(&mut f, id, 100, 100); + let surface2 = create_window(&mut f, id, 200, 200); + f.double_roundtrip(id); + + let _ = f.client(id).window(&surface1).recent_configures(); + let _ = f.client(id).window(&surface2).recent_configures(); + + // Consume into one column. + f.niri().layout.focus_left(); + f.niri().layout.consume_into_column(); + f.double_roundtrip(id); + + // Commit for the column consume. + let window = f.client(id).window(&surface1); + window.ack_last_and_commit(); + + let window = f.client(id).window(&surface2); + window.ack_last_and_commit(); + + f.double_roundtrip(id); + + set_time(f.niri(), Duration::ZERO); + complete_animations(f.niri()); + + (f, id, surface1, surface2) +} + +#[test] +fn height_resize_animates_next_y() { + let (mut f, id, surface1, surface2) = set_up_two_in_column(); + + // Issue a resize. + f.niri() + .layout + .set_window_height(None, SizeChange::AdjustFixed(-50)); + f.double_roundtrip(id); + + // The top window shrinks in response, the bottom remains as is. + let window = f.client(id).window(&surface1); + window.set_size(100, 50); + window.ack_last_and_commit(); + let window = f.client(id).window(&surface2); + window.ack_last_and_commit(); + + // This starts the resize animation for the top window and the Y move for the bottom. + f.roundtrip(id); + + // No time had passed yet, so we're at the initial state. + assert_snapshot!(format_tiles(f.niri()), @r" + 100 × 100 at x: 0 y: 0 + 200 × 200 at x: 0 y:100 + "); + + // Advance the time halfway. + set_time(f.niri(), Duration::from_millis(500)); + f.niri().advance_animations(); + + // Top window is half-resized at 75 px tall, bottom window is at y=75 matching it. + assert_snapshot!(format_tiles(f.niri()), @r" + 100 × 75 at x: 0 y: 0 + 200 × 200 at x: 0 y: 75 + "); + + // Advance the time to completion. + set_time(f.niri(), Duration::from_millis(1000)); + f.niri().advance_animations(); + + // Final state at 50 px. + assert_snapshot!(format_tiles(f.niri()), @r" + 100 × 50 at x: 0 y: 0 + 200 × 200 at x: 0 y: 50 + "); +} + +#[test] +fn clientside_height_change_doesnt_animate() { + let (mut f, id, surface1, _surface2) = set_up_two_in_column(); + + // The initial state. + assert_snapshot!(format_tiles(f.niri()), @r" + 100 × 100 at x: 0 y: 0 + 200 × 200 at x: 0 y:100 + "); + + // The top window shrinks by itself, without a niri-issued resize. + let window = f.client(id).window(&surface1); + window.set_size(100, 50); + window.commit(); + + // This does not start any animations. + f.roundtrip(id); + + // No time had passed yet, but we are at the final state right away. + assert_snapshot!(format_tiles(f.niri()), @r" + 100 × 50 at x: 0 y: 0 + 200 × 200 at x: 0 y: 50 + "); +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index d57ce22b..cdd04d9c 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -4,6 +4,7 @@ mod client; mod fixture; mod server; +mod animations; mod floating; mod fullscreen; mod layer_shell; |
