Commit 8f1f743a2f6b
tarball2 files changed+102-16
@@ -417,3 +417,44 @@ fn moving_toward_a_stack_showing_one_window_swaps() {
417417
assert!(rect(b).x < rect(a).x);418418
assert_eq!(rect(a).h, AREA.h);419419
}420
+421
+/// A move either changes what is on screen or changes nothing at all. Steps422
+/// around hidden windows used to succeed invisibly and eat the keypress.423
+#[test]424
+fn moves_are_seen_or_undone() {425
+ let dirs = [Dir::Left, Dir::Right, Dir::Up, Dir::Down];426
+ let mut runner = proptest::test_runner::TestRunner::deterministic();427
+ let ops = prop::collection::vec((0u8..4, 0u8..4, 0u8..3, any::<bool>()), 1..14);428
+ for _ in 0..2000 {429
+ let seq = proptest::strategy::ValueTree::current(&ops.new_tree(&mut runner).unwrap());430
+ let mut t: Tree<u32> = Tree::default();431
+ t.insert_row(0);432
+ let mut n = 1;433
+ for &(w, d, kind, hide) in &seq {434
+ let vis = |x: u32| !(hide && x == 0);435
+ if kind == 0 && n < 5 {436
+ t.insert_after(n, u32::from(w) % n);437
+ n += 1;438
+ continue;439
+ }440
+ let w = u32::from(w) % n;441
+ let shown = |t: &Tree<u32>| {442
+ let mut v: Vec<_> = t443
+ .arrange(AREA, 0.0, vis)444
+ .into_iter()445
+ .filter(|p| vis(p.0))446
+ .collect();447
+ v.sort_by_key(|p| p.0);448
+ v449
+ };450
+ let before = shown(&t);451
+ let moved = if kind == 1 {452
+ t.merge_window(w, dirs[usize::from(d)], vis)453
+ } else {454
+ t.move_window(w, dirs[usize::from(d)], vis)455
+ };456
+ t.check();457
+ assert_eq!(moved, shown(&t) != before, "{seq:?}");458
+ }459
+ }460
+}@@ -190,8 +190,10 @@ impl<W: Copy + Eq + Hash> Tree<W> {
190190
let Some(&n) = self.leaves.get(&w) else {191191
return false;192192
};193
- let moved = self.move_node(n, dir, &vis, true);194
- self.reveal(n, moved)193
+ self.until_seen(&vis, |t| {194
+ let moved = t.move_node(n, dir, &vis, true);195
+ t.reveal(n, moved)196
+ })195197
}196198
197199
/// Moves the container holding `w` as a rigid block. A window sitting@@ -200,12 +202,14 @@ impl<W: Copy + Eq + Hash> Tree<W> {
200202
let Some(&n) = self.leaves.get(&w) else {201203
return false;202204
};203
- let block = self.nodes[n]204
- .parent205
- .filter(|&p| Some(p) != self.root)206
- .unwrap_or(n);207
- let moved = self.move_node(block, dir, &vis, false);208
- self.reveal(n, moved)205
+ self.until_seen(&vis, |t| {206
+ let block = t.nodes[n]207
+ .parent208
+ .filter(|&p| Some(p) != t.root)209
+ .unwrap_or(n);210
+ let moved = t.move_node(block, dir, &vis, false);211
+ t.reveal(n, moved)212
+ })209213
}210214
211215
/// Moves a window to the end of the next visible lane in `dir` among its@@ -216,15 +220,56 @@ impl<W: Copy + Eq + Hash> Tree<W> {
216220
let Some(&n) = self.leaves.get(&w) else {217221
return false;218222
};219
- if let Some((p, i)) = self.position(n)220
- && self.axis(p) == Some(dir.axis())221
- && let Some(j) = self.next_shown(p, i, dir, &vis)222
- {223
- self.join_lane(n, p, j);224
- return self.reveal(n, true);223
+ self.until_seen(&vis, |t| {224
+ if let Some((p, i)) = t.position(n)225
+ && t.axis(p) == Some(dir.axis())226
+ && let Some(j) = t.next_shown(p, i, dir, &vis)227
+ {228
+ t.join_lane(n, p, j);229
+ return t.reveal(n, true);230
+ }231
+ let moved = t.move_node(n, dir, &vis, true);232
+ t.reveal(n, moved)233
+ })234
+ }235
+236
+ /// Repeats `step` until the visible layout changes, or undoes it all when237
+ /// it never does. Steps around hidden windows otherwise ate keypresses238
+ /// and rearranged windows the user could not see.239
+ fn until_seen(240
+ &mut self,241
+ vis: &impl Fn(W) -> bool,242
+ mut step: impl FnMut(&mut Self) -> bool,243
+ ) -> bool {244
+ let seen = |t: &Self| -> Vec<(W, Rect)> {245
+ t.arrange(Rect::new(0.0, 0.0, 1000.0, 1000.0), 0.0, vis)246
+ .into_iter()247
+ .filter(|p| vis(p.0))248
+ .collect()249
+ };250
+ let same = |a: &[(W, Rect)], b: &[(W, Rect)]| {251
+ a.len() == b.len()252
+ && a.iter().all(|(v, r)| {253
+ b.iter().any(|(w, s)| {254
+ v == w255
+ && [r.x - s.x, r.y - s.y, r.w - s.w, r.h - s.h]256
+ .iter()257
+ .all(|d| d.abs() < 1e-6)258
+ })259
+ })260
+ };261
+ let before = self.clone();262
+ let start = seen(self);263
+ for _ in 0..=before.nodes.len() {264
+ if !step(self) {265
+ break;266
+ }267
+ if !same(&start, &seen(self)) {268
+ return true;269
+ }225270
}226
- let moved = self.move_node(n, dir, &vis, true);227
- self.reveal(n, moved)271
+ *self = before;272
+ false228273
}229274
230275
/// Undoes any expansion along `leaf`'s ancestry that favours another