Commit 3b1cf5ee2ea2
tarball3 files changed+103-13
@@ -1,8 +0,0 @@
1
-# Seeds for failure cases proptest has generated in the past. It is2
-# automatically read and these particular cases re-run before any3
-# novel cases are generated.4
-#5
-# It is recommended to check this file in to source control so that6
-# everyone who runs the test benefits from these saved cases.7
-cc feaaec708a35beac0ab9749dd260c423ed4177da099bd85a2041f38172334f65 # shrinks to ops = [Add(0, false), Add(0, false), Add(0, false), View(1), Scratch(0), Add(0, false), Add(0, false), Add(0, false), Summon, Summon, Flip, Add(1, false), Move(Left), Add(0, false), Add(0, false), Focus(114), Flip, Move(Left), Focus(0), Add(0, false), View(0), View(0), View(0), Add(0, false), Expand(0)]8
-cc 796d3b6a182db230a58f8fcf320dde6b4ffd5ff3128b07a4f4c75737b25d9e88 # shrinks to ops = [Add(0, false), Focus(0), Add(0, false), Focus(30), View(3), Focus(0), Add(3, false), Move(Up), Add(0, false), Move(Left), Add(0, false), View(0), Add(0, false), Block(Right), Add(0, false), Move(Right), Add(0, false), View(3), Flip, Add(3, false), Expand(0), View(0), Move(Right)]@@ -128,6 +128,18 @@ fn apply(l: &mut Layout, tags: &[TagId], op: &Op) {
128128
}129129
}130130
131
+/// The area a tiled window gets, zero for floating or unplaced ones.132
+fn tiled_area(l: &Layout, w: WindowId) -> f64 {133
+ let Some(o) = l.window(w).and_then(|x| x.output) else {134
+ return 0.0;135
+ };136
+ l.arrange(o)137
+ .tiled138
+ .iter()139
+ .find(|p| p.window == w && p.visible)140
+ .map_or(0.0, |p| p.rect.w * p.rect.h)141
+}142
+131143
fn check_geometry(l: &Layout) {132144
for (o, out) in l.outputs() {133145
let arr = l.arrange(o);@@ -182,9 +194,14 @@ proptest! {
182194
l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0));183195
let tags = [l.homeless(), l.tag("1"), l.tag("2"), l.tag("3")];184196
for op in &ops {197
+ let moving = matches!(op, Op::Move(_) | Op::Merge(_) | Op::Block(_));198
+ let shown_before = l.focused().filter(|&w| tiled_area(&l, w) > 1.0);185199
apply(&mut l, &tags, op);186200
l.check();187201
check_geometry(&l);202
+ if moving && let Some(w) = shown_before {203
+ prop_assert!(tiled_area(&l, w) > 1.0, "{w:?} vanished after {op:?}");204
+ }188205
}189206
}190207
@@ -312,3 +329,25 @@ fn expand_skips_containers_of_hidden_windows() {
312329
let rect = |w| all.iter().find(|p| p.0 == w).unwrap().1;313330
assert_eq!(rect(b).w, AREA.w);314331
}332
+333
+/// Joining a stack hands the window's weight to it and leaving hands it back,334
+/// so going in and out repeatedly leaves every window the size it was.335
+#[test]336
+fn stacking_in_and_out_does_not_drift() {337
+ for out in [Dir::Up, Dir::Down, Dir::Right] {338
+ let (a, b, c) = (1, 2, 3);339
+ let mut t: Tree<u32> = Tree::default();340
+ t.insert_row(a);341
+ t.insert_after(b, a);342
+ t.insert_after(c, b);343
+ assert!(t.merge_window(c, Dir::Left, |_| true));344
+ assert!(t.merge_window(c, out, |_| true));345
+ let once = t.arrange(AREA, 0.0, |_| true);346
+ for _ in 0..8 {347
+ t.merge_window(c, Dir::Left, |_| true);348
+ t.merge_window(c, out, |_| true);349
+ }350
+ t.check();351
+ assert_eq!(t.arrange(AREA, 0.0, |_| true), once, "stepping out {out:?}");352
+ }353
+}@@ -138,6 +138,7 @@ impl<W: Copy + Eq + Hash> Tree<W> {
138138
self.root = Some(c);139139
}140140
}141
+ self.reveal(n, true);141142
}142143
143144
/// Appends `w` as a row of its own along the stacking axis.@@ -157,6 +158,7 @@ impl<W: Copy + Eq + Hash> Tree<W> {
157158
self.root = Some(c);158159
}159160
}161
+ self.reveal(n, true);160162
}161163
162164
pub fn remove(&mut self, w: W) -> bool {@@ -182,7 +184,8 @@ impl<W: Copy + Eq + Hash> Tree<W> {
182184
let Some(&n) = self.leaves.get(&w) else {183185
return false;184186
};185
- self.move_node(n, dir, &vis, true)187
+ let moved = self.move_node(n, dir, &vis, true);188
+ self.reveal(n, moved)186189
}187190
188191
/// Moves the container holding `w` as a rigid block. A window sitting@@ -195,7 +198,8 @@ impl<W: Copy + Eq + Hash> Tree<W> {
195198
.parent196199
.filter(|&p| Some(p) != self.root)197200
.unwrap_or(n);198
- self.move_node(block, dir, &vis, false)201
+ let moved = self.move_node(block, dir, &vis, false);202
+ self.reveal(n, moved)199203
}200204
201205
/// Moves a window to the end of the next visible lane in `dir` among its@@ -211,9 +215,27 @@ impl<W: Copy + Eq + Hash> Tree<W> {
211215
&& let Some(j) = self.next_shown(p, i, dir, &vis)212216
{213217
self.join_lane(n, p, j);214
- return true;218
+ return self.reveal(n, true);215219
}216
- self.move_node(n, dir, &vis, true)220
+ let moved = self.move_node(n, dir, &vis, true);221
+ self.reveal(n, moved)222
+ }223
+224
+ /// Undoes any expansion along `leaf`'s ancestry that favours another225
+ /// child, once it has `moved` or arrived. An expanded container gives its226
+ /// other children no room, so a window moved into one vanished while227
+ /// staying focused.228
+ fn reveal(&mut self, leaf: NodeId, moved: bool) -> bool {229
+ let mut cur = leaf;230
+ while moved && let Some(p) = self.nodes[cur].parent {231
+ if let Kind::Container { expanded, .. } = &mut self.nodes[p].kind232
+ && expanded.is_some_and(|e| e != cur)233
+ {234
+ *expanded = None;235
+ }236
+ cur = p;237
+ }238
+ moved217239
}218240
219241
/// Whether every container above `w` is expanded towards it.@@ -405,7 +427,7 @@ impl<W: Copy + Eq + Hash> Tree<W> {
405427
self.join_lane(n, p, j);406428
} else {407429
let at = if dir.forward() { i + 1 } else { i };408
- self.relocate(n, p, at);430
+ self.step_out(n, p, i, at);409431
}410432
return true;411433
}@@ -432,6 +454,7 @@ impl<W: Copy + Eq + Hash> Tree<W> {
432454
return false;433455
}434456
let (old, i) = self.position(n).expect("n is below the root");457
+ self.release_share(n);435458
self.unlink(old, i);436459
self.normalize(old);437460
let r = self.root.expect("the rest of the tree survives");@@ -494,7 +517,43 @@ impl<W: Copy + Eq + Hash> Tree<W> {
494517
c495518
};496519
let end = self.children(lane).len();520
+ let from_p = self.position(n).filter(|&(old, _)| old == p);521
+ let weight = from_p.map(|(_, i)| self.children(p)[i].weight);497522
self.relocate(n, lane, end);523
+ if let Some(weight) = weight524
+ && self.nodes.contains_key(lane)525
+ && let Some((q, k)) = self.position(lane)526
+ && q == p527
+ {528
+ self.children_mut(p)[k].weight += weight;529
+ }530
+ }531
+532
+ /// Moves `n` out of `p`'s child `i` to sit beside it at `at`, taking533
+ /// along the share of that child's weight it had.534
+ fn step_out(&mut self, n: NodeId, p: NodeId, i: usize, at: usize) {535
+ let cur = self.children(p)[i].node;536
+ let Some((parent, k)) = self.position(n).filter(|&(q, _)| q == cur) else {537
+ self.relocate(n, p, at);538
+ return;539
+ };540
+ let share = self.release_share(n).unwrap_or_else(|| self.mean_weight(p));541
+ self.unlink(parent, k);542
+ self.attach(p, at, n, share);543
+ self.normalize(parent);544
+ }545
+546
+ /// Takes `n`'s share of its container's weight away from the container,547
+ /// for `n` to carry out. Joining a lane hands a window's weight to the548
+ /// lane, so leaving gives it back and windows going in and out of549
+ /// stacks do not shrink their neighbours a little more each time.550
+ fn release_share(&mut self, n: NodeId) -> Option<f64> {551
+ let (old, i) = self.position(n)?;552
+ let (q, k) = self.position(old)?;553
+ let total: f64 = self.children(old).iter().map(|c| c.weight).sum();554
+ let share = self.children(q)[k].weight * self.children(old)[i].weight / total;555
+ self.children_mut(q)[k].weight -= share;556
+ Some(share)498557
}499558
500559
fn relocate(&mut self, n: NodeId, p: NodeId, at: usize) {