atagen

monolith

public
Star0
Watch0

Commit 60f00dd4f2be

tarball

atagen <boss@atagen.co>2026-10-16 11:50 UTC

4 files changed+68-3
layout: let moved windows join lanes of hidden windows

Stepping a window out of a row toward a lane whose windows were all hidden used to open a new lane between them, so showing the tag again stacked an extra row the user never asked for. The window now joins that lane, and shares it once the hidden windows return.
Modified file: crates/layout/proptest-regressions/tests.txt+1 additions
@@ -5,3 +5,4 @@
55 # It is recommended to check this file in to source control so that
66 # everyone who runs the test benefits from these saved cases.
77 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)]
Modified file: crates/layout/src/tests.rs+21 additions
@@ -186,3 +186,24 @@ fn hidden_windows_keep_their_slots() {
186186 t.check();
187187 assert_eq!(rect(&t, e).w, AREA.w, "E is demoted to a row of its own");
188188 }
189+
190+/// Moving out of a row toward a lane of hidden windows joins that lane, so
191+/// they share it when shown again instead of a new lane opening between.
192+#[test]
193+fn moving_toward_a_hidden_lane_joins_it() {
194+ let (a, b, c, d, e) = (1, 2, 3, 4, 5);
195+ let mut t: Tree<u32> = Tree::default();
196+ t.insert_row(a);
197+ t.insert_after(b, a);
198+ t.insert_after(c, b);
199+ t.insert_row(d);
200+ t.insert_after(e, c);
201+ let tag_two = |w: u32| w != a && w != d;
202+ assert!(t.move_window(e, Dir::Down, tag_two));
203+ t.check();
204+ let all = t.arrange(AREA, 0.0, |_| true);
205+ let rect = |w| all.iter().find(|p| p.0 == w).unwrap().1;
206+ assert_eq!(rect(e).y, rect(d).y);
207+ assert!(rect(d).x < rect(e).x);
208+ assert!(rect(a).y < rect(d).y);
209+}
Modified file: crates/layout/src/tree.rs+45 additions−2 deletions
@@ -339,9 +339,16 @@ impl<W: Copy + Eq + Hash> Tree<W> {
339339 let mut cur = n;
340340 while let Some((p, i)) = self.position(cur) {
341341 if self.axis(p) == Some(dir.axis()) {
342+ let hidden_lane = self
343+ .adjacent(p, i, dir)
344+ .filter(|&j| enter && !self.visible(self.children(p)[j].node, vis));
342345 if cur != n {
343- let at = if dir.forward() { i + 1 } else { i };
344- self.relocate(n, p, at);
346+ if let Some(j) = hidden_lane {
347+ self.join_lane(n, p, j);
348+ } else {
349+ let at = if dir.forward() { i + 1 } else { i };
350+ self.relocate(n, p, at);
351+ }
345352 return true;
346353 }
347354 if let Some(j) = self.next_shown(p, i, dir, vis) {
@@ -356,6 +363,10 @@ impl<W: Copy + Eq + Hash> Tree<W> {
356363 }
357364 return true;
358365 }
366+ if let Some(j) = hidden_lane {
367+ self.join_lane(n, p, j);
368+ return true;
369+ }
359370 }
360371 cur = p;
361372 }
@@ -397,6 +408,38 @@ impl<W: Copy + Eq + Hash> Tree<W> {
397408 }
398409 }
399410
411+ fn adjacent(&self, p: NodeId, i: usize, dir: Dir) -> Option<usize> {
412+ let j = if dir.forward() {
413+ i + 1
414+ } else {
415+ i.checked_sub(1)?
416+ };
417+ (j < self.children(p).len()).then_some(j)
418+ }
419+
420+ /// Moves `n` to the end of the lane at `p`'s child `j`, which holds only
421+ /// hidden windows, so it shares that lane when they come back instead of
422+ /// opening a new one beside it. A lone window becomes a lane of its own.
423+ fn join_lane(&mut self, n: NodeId, p: NodeId, j: usize) {
424+ let lane = self.children(p)[j].node;
425+ let lane = if self.axis(lane).is_some() {
426+ lane
427+ } else {
428+ let c = self.new_container(self.axis(p).expect("p is a container").other());
429+ self.children_mut(p)[j].node = c;
430+ self.nodes[c].parent = Some(p);
431+ if let Kind::Container { expanded, .. } = &mut self.nodes[p].kind
432+ && *expanded == Some(lane)
433+ {
434+ *expanded = Some(c);
435+ }
436+ self.attach(c, 0, lane, 1.0);
437+ c
438+ };
439+ let end = self.children(lane).len();
440+ self.relocate(n, lane, end);
441+ }
442+
400443 fn relocate(&mut self, n: NodeId, p: NodeId, at: usize) {
401444 let (old, i) = self.position(n).expect("relocated nodes have a parent");
402445 let weight = self.mean_weight(p);
Modified file: design/decisions.md+1 additions−1 deletions
@@ -92,7 +92,7 @@ Running log of settled decisions and open questions. Research backing lives in `
9292 - Floating to tiled and fullscreen use a direct glide above the tiled layer. Expand is a tiled operation planned normally, siblings squeezed to zero-size lines.
9393 - One layout tree per output, tags filter it. Hidden windows keep their slots, visible moves swap visible slots only, containers collapse by the normal one-child rule counting hidden children, and a hidden only child is demoted into the grandparent. This replaces per-tag layouts and the row-stacking merge rule, row stacking is just the default arrangement.
9494 - Lanes span only the band of the involved windows, never the whole output.
95-- Hidden-slot rules confirmed.
95+- Hidden-slot rules confirmed. A window moving toward a lane that holds only hidden windows joins that lane rather than opening a new one beside it.
9696 - Input hit-tests against final layout geometry, never animated geometry.
9797 - smithay is pinned by git rev. The repo uses jj.