atagen

monolith

public
Star0
Watch0

Commit ae47743b5745

tarball

atagen <boss@atagen.co>2026-10-19 03:52 UTC

2 files changed+45-11
layout: refuse to move hidden windows

The move docs promised hidden windows never move, but nothing checked it, and a step that moved a hidden window could still change the visible layout. Moves now fail outright for a window its own view hides.
Modified file: crates/layout/src/tests.rs+20 additions−8 deletions
@@ -418,20 +418,21 @@ fn moving_toward_a_stack_showing_one_window_swaps() {
418418 assert_eq!(rect(a).h, AREA.h);
419419 }
420420
421-/// A move either changes what is on screen or changes nothing at all. Steps
422-/// around hidden windows used to succeed invisibly and eat the keypress.
421+/// A move either changes what is on screen or changes nothing at all, and a
422+/// window move never rearranges hidden windows among themselves. Steps around
423+/// hidden windows used to succeed invisibly and eat the keypress.
423424 #[test]
424425 fn moves_are_seen_or_undone() {
425426 let dirs = [Dir::Left, Dir::Right, Dir::Up, Dir::Down];
426427 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+ let ops = prop::collection::vec((0u8..4, 0u8..4, 0u8..4, 0u8..32), 1..14);
428429 for _ in 0..2000 {
429430 let seq = proptest::strategy::ValueTree::current(&ops.new_tree(&mut runner).unwrap());
430431 let mut t: Tree<u32> = Tree::default();
431432 t.insert_row(0);
432433 let mut n = 1;
433434 for &(w, d, kind, hide) in &seq {
434- let vis = |x: u32| !(hide && x == 0);
435+ let vis = |x: u32| hide & (1 << x) == 0;
435436 if kind == 0 && n < 5 {
436437 t.insert_after(n, u32::from(w) % n);
437438 n += 1;
@@ -448,13 +449,24 @@ fn moves_are_seen_or_undone() {
448449 v
449450 };
450451 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)
452+ let hidden = |t: &Tree<u32>| {
453+ let mut t = t.clone();
454+ t.retain(|x| !vis(x));
455+ t.shape()
456+ };
457+ let rest = hidden(&t);
458+ let d = dirs[usize::from(d)];
459+ let moved = match kind {
460+ 1 => t.merge_window(w, d, vis),
461+ 2 => t.move_window(w, d, vis),
462+ 3 => t.move_container(w, d, vis),
463+ _ => continue,
455464 };
456465 t.check();
457466 assert_eq!(moved, shown(&t) != before, "{seq:?}");
467+ if kind != 3 {
468+ assert_eq!(hidden(&t), rest, "{seq:?}");
469+ }
458470 }
459471 }
460472 }
Modified file: crates/layout/src/tree.rs+25 additions−3 deletions
@@ -187,7 +187,7 @@ impl<W: Copy + Eq + Hash> Tree<W> {
187187 /// visible sibling, enter a sibling container, or step out beside their
188188 /// container. Hidden windows never move.
189189 pub fn move_window(&mut self, w: W, dir: Dir, vis: impl Fn(W) -> bool) -> bool {
190- let Some(&n) = self.leaves.get(&w) else {
190+ let Some(&n) = self.leaves.get(&w).filter(|_| vis(w)) else {
191191 return false;
192192 };
193193 self.until_seen(&vis, |t| {
@@ -199,7 +199,7 @@ impl<W: Copy + Eq + Hash> Tree<W> {
199199 /// Moves the container holding `w` as a rigid block. A window sitting
200200 /// directly in the root is its own block.
201201 pub fn move_container(&mut self, w: W, dir: Dir, vis: impl Fn(W) -> bool) -> bool {
202- let Some(&n) = self.leaves.get(&w) else {
202+ let Some(&n) = self.leaves.get(&w).filter(|_| vis(w)) else {
203203 return false;
204204 };
205205 self.until_seen(&vis, |t| {
@@ -217,7 +217,7 @@ impl<W: Copy + Eq + Hash> Tree<W> {
217217 /// sibling lane that way leaves its stack as `move_window` would, so it
218218 /// takes one press to step out and another to merge.
219219 pub fn merge_window(&mut self, w: W, dir: Dir, vis: impl Fn(W) -> bool) -> bool {
220- let Some(&n) = self.leaves.get(&w) else {
220+ let Some(&n) = self.leaves.get(&w).filter(|_| vis(w)) else {
221221 return false;
222222 };
223223 self.until_seen(&vis, |t| {
@@ -779,6 +779,28 @@ impl<W: Copy + Eq + Hash> Tree<W> {
779779 }
780780
781781 #[cfg(test)]
782+ pub(crate) fn shape(&self) -> String
783+ where
784+ W: std::fmt::Debug,
785+ {
786+ fn go<W: Copy + Eq + Hash + std::fmt::Debug>(t: &Tree<W>, n: NodeId) -> String {
787+ match &t.nodes[n].kind {
788+ Kind::Leaf(w, [x, y]) => format!("{w:?}[{x:.6},{y:.6}]"),
789+ Kind::Container {
790+ axis,
791+ children,
792+ expanded,
793+ } => {
794+ let inner: Vec<String> = children.iter().map(|&c| go(t, c)).collect();
795+ let e = expanded.and_then(|e| children.iter().position(|&c| c == e));
796+ format!("{axis:?}{e:?}({})", inner.join(" "))
797+ }
798+ }
799+ }
800+ self.root.map(|r| go(self, r)).unwrap_or_default()
801+ }
802+
803+ #[cfg(test)]
782804 pub(crate) fn check(&self) {
783805 let reachable = match self.root {
784806 Some(r) => {