mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
Add basic client-server resize animation tests
This commit is contained in:
@@ -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
|
||||
");
|
||||
}
|
||||
@@ -4,6 +4,7 @@ mod client;
|
||||
mod fixture;
|
||||
mod server;
|
||||
|
||||
mod animations;
|
||||
mod floating;
|
||||
mod fullscreen;
|
||||
mod layer_shell;
|
||||
|
||||
Reference in New Issue
Block a user