1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
|
use std::fmt::Write as _;
use std::time::Duration;
use insta::assert_snapshot;
use niri_config::animations::{Curve, EasingParams, Kind};
use niri_config::Config;
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 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: Kind = Kind::Easing(EasingParams {
duration_ms: 1000,
curve: Curve::Linear,
});
let mut config = Config::default();
config.layout.gaps = 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);
f.niri_complete_animations();
(f, id, surface1, surface2)
}
#[test]
fn egl_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 egl_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
");
}
|