atagen

monolith

public
Star0
Watch0

Commit 9d1128e3c227

tarball

atagen <boss@atagen.co>2026-10-28 04:06 UTC

3 files changed+116-60
layout: keep hidden windows' share as windows open and close beside them

Tree::insert_split halves its anchor and Tree::remove never gave the weight back, so each window split off and closed beside a hidden sibling doubled that sibling's share for when it is shown again. Hidden siblings now scale with the room the visible ones gain or lose, matching how resize already treats them.
Modified file: crates/layout/src/lib.rs+9 additions−5 deletions
@@ -427,12 +427,14 @@ impl Layout {
427427 let split = anchor
428428 .filter(|_| self.autotile)
429429 .and_then(|a| self.split_axis(o, a));
430- let tree = &mut self.outputs[o].tree;
430+ let mut tree = std::mem::take(&mut self.outputs[o].tree);
431+ let vis = self.vis_on(o);
431432 match (anchor, split) {
432- (Some(a), Some(axis)) => tree.insert_split(w, a, axis),
433- (Some(a), None) => tree.insert_after(w, a),
434- (None, _) => tree.insert_row(w),
433+ (Some(a), Some(axis)) => tree.insert_split(w, a, axis, vis),
434+ (Some(a), None) => tree.insert_after(w, a, vis),
435+ (None, _) => tree.insert_row(w, vis),
435436 }
437+ self.outputs[o].tree = tree;
436438 }
437439
438440 /// The axis a window opening beside `anchor` should split it along, the
@@ -456,8 +458,10 @@ impl Layout {
456458 let Some(o) = self.windows[w].output else {
457459 return;
458460 };
461+ let mut tree = std::mem::take(&mut self.outputs[o].tree);
462+ tree.remove(w, self.vis_on(o));
459463 let out = &mut self.outputs[o];
460- out.tree.remove(w);
464+ out.tree = tree;
461465 out.floating.retain(|&f| f != w);
462466 }
463467
Modified file: crates/layout/src/tests.rs+71 additions−47 deletions
@@ -240,11 +240,11 @@ proptest! {
240240 fn hidden_windows_keep_their_slots() {
241241 let (a, b, c, d, e) = (1, 2, 3, 4, 5);
242242 let mut t: Tree<u32> = Tree::default();
243- t.insert_row(a);
244- t.insert_after(d, a);
245- t.insert_after(b, d);
246- t.insert_row(c);
247- t.insert_after(e, c);
243+ t.insert_row(a, |_| true);
244+ t.insert_after(d, a, |_| true);
245+ t.insert_after(b, d, |_| true);
246+ t.insert_row(c, |_| true);
247+ t.insert_after(e, c, |_| true);
248248 t.check();
249249 let hidden = |w: u32| w != d && w != e;
250250 let all = |_| true;
@@ -270,11 +270,11 @@ fn hidden_windows_keep_their_slots() {
270270 fn moving_toward_a_hidden_lane_joins_it() {
271271 let (a, b, c, d, e) = (1, 2, 3, 4, 5);
272272 let mut t: Tree<u32> = Tree::default();
273- t.insert_row(a);
274- t.insert_after(b, a);
275- t.insert_after(c, b);
276- t.insert_row(d);
277- t.insert_after(e, c);
273+ t.insert_row(a, |_| true);
274+ t.insert_after(b, a, |_| true);
275+ t.insert_after(c, b, |_| true);
276+ t.insert_row(d, |_| true);
277+ t.insert_after(e, c, |_| true);
278278 let tag_two = |w: u32| w != a && w != d;
279279 assert!(t.move_window(e, Dir::Down, tag_two));
280280 t.check();
@@ -290,9 +290,9 @@ fn merging_steps_out_of_a_stack_before_joining_another() {
290290 let (a, b, c) = (1, 2, 3);
291291 let stacked = || {
292292 let mut t: Tree<u32> = Tree::default();
293- t.insert_row(a);
294- t.insert_after(b, a);
295- t.insert_after(c, b);
293+ t.insert_row(a, |_| true);
294+ t.insert_after(b, a, |_| true);
295+ t.insert_after(c, b, |_| true);
296296 assert!(t.merge_window(b, Dir::Left, |_| true));
297297 t
298298 };
@@ -325,9 +325,9 @@ fn extended_windows_move_their_group() {
325325 let (a, b, c) = (1, 2, 3);
326326 let all = |_| true;
327327 let mut t: Tree<u32> = Tree::default();
328- t.insert_row(a);
329- t.insert_after(b, a);
330- t.insert_after(c, b);
328+ t.insert_row(a, |_| true);
329+ t.insert_after(b, a, |_| true);
330+ t.insert_after(c, b, |_| true);
331331 assert!(t.merge_window(b, Dir::Left, all));
332332 assert!(t.toggle_expand(b, 0, 1, all));
333333 let rects = |t: &Tree<u32>| {
@@ -355,10 +355,10 @@ fn retagging_an_extended_window_leaves_hidden_members() {
355355 let (a, b, c, d) = (1, 2, 3, 4);
356356 let shown = |w| w != d;
357357 let mut t: Tree<u32> = Tree::default();
358- t.insert_row(a);
359- t.insert_after(b, a);
360- t.insert_after(c, b);
361- t.insert_after(d, c);
358+ t.insert_row(a, |_| true);
359+ t.insert_after(b, a, |_| true);
360+ t.insert_after(c, b, |_| true);
361+ t.insert_after(d, c, |_| true);
362362 assert!(t.toggle_expand(b, 0, 1, shown));
363363 let mut group = t.group_windows(b, shown);
364364 group.sort();
@@ -372,9 +372,9 @@ fn retagging_an_extended_window_leaves_hidden_members() {
372372 fn expand_skips_containers_of_hidden_windows() {
373373 let (a, b, c) = (1, 2, 3);
374374 let mut t: Tree<u32> = Tree::default();
375- t.insert_row(a);
376- t.insert_after(b, a);
377- t.insert_after(c, b);
375+ t.insert_row(a, |_| true);
376+ t.insert_after(b, a, |_| true);
377+ t.insert_after(c, b, |_| true);
378378 assert!(t.merge_window(a, Dir::Right, |_| true));
379379 let shown = |w: u32| w != a;
380380 assert!(t.toggle_expand(b, 0, 1, shown));
@@ -390,9 +390,9 @@ fn stacking_in_and_out_does_not_drift() {
390390 for out in [Dir::Up, Dir::Down, Dir::Right] {
391391 let (a, b, c) = (1, 2, 3);
392392 let mut t: Tree<u32> = Tree::default();
393- t.insert_row(a);
394- t.insert_after(b, a);
395- t.insert_after(c, b);
393+ t.insert_row(a, |_| true);
394+ t.insert_after(b, a, |_| true);
395+ t.insert_after(c, b, |_| true);
396396 assert!(t.merge_window(c, Dir::Left, |_| true));
397397 assert!(t.merge_window(c, out, |_| true));
398398 let once = t.arrange(AREA, 0.0, |_| true);
@@ -411,10 +411,10 @@ fn stacking_in_and_out_does_not_drift() {
411411 fn moving_past_hidden_windows_does_not_drift() {
412412 let (a, b, h, h2) = (1, 2, 3, 4);
413413 let mut t: Tree<u32> = Tree::default();
414- t.insert_row(h);
415- t.insert_row(a);
416- t.insert_after(h2, a);
417- t.insert_after(b, h2);
414+ t.insert_row(h, |_| true);
415+ t.insert_row(a, |_| true);
416+ t.insert_after(h2, a, |_| true);
417+ t.insert_after(b, h2, |_| true);
418418 let vis = |w: u32| w != h && w != h2;
419419 let mut once = None;
420420 for _ in 0..4 {
@@ -459,9 +459,9 @@ fn windows_return_to_where_they_were_first_evicted_from() {
459459 fn moving_toward_a_stack_showing_one_window_swaps() {
460460 let (a, b, h) = (1, 2, 3);
461461 let mut t: Tree<u32> = Tree::default();
462- t.insert_row(a);
463- t.insert_after(b, a);
464- t.insert_after(h, b);
462+ t.insert_row(a, |_| true);
463+ t.insert_after(b, a, |_| true);
464+ t.insert_after(h, b, |_| true);
465465 assert!(t.merge_window(h, Dir::Left, |_| true));
466466 let vis = |w: u32| w != h;
467467 assert!(t.move_window(a, Dir::Right, vis));
@@ -483,12 +483,12 @@ fn moves_are_seen_or_undone() {
483483 for _ in 0..2000 {
484484 let seq = proptest::strategy::ValueTree::current(&ops.new_tree(&mut runner).unwrap());
485485 let mut t: Tree<u32> = Tree::default();
486- t.insert_row(0);
486+ t.insert_row(0, |_| true);
487487 let mut n = 1;
488488 for &(w, d, kind, hide) in &seq {
489489 let vis = |x: u32| hide & (1 << x) == 0;
490490 if kind == 0 && n < 5 {
491- t.insert_after(n, u32::from(w) % n);
491+ t.insert_after(n, u32::from(w) % n, |_| true);
492492 n += 1;
493493 continue;
494494 }
@@ -569,14 +569,14 @@ fn rect_of(t: &Tree<u32>, w: u32, vis: impl Fn(u32) -> bool) -> Rect {
569569 fn splitting_a_lone_window_shares_its_space() {
570570 let (a, b, c) = (1, 2, 3);
571571 let mut t: Tree<u32> = Tree::default();
572- t.insert_row(a);
573- t.insert_split(b, a, Axis::Horizontal);
572+ t.insert_row(a, |_| true);
573+ t.insert_split(b, a, Axis::Horizontal, |_| true);
574574 t.check();
575575 let (ra, rb) = (rect_of(&t, a, |_| true), rect_of(&t, b, |_| true));
576576 assert_eq!((ra.w, rb.w, ra.h), (AREA.w / 2.0, AREA.w / 2.0, AREA.h));
577577 assert!(ra.x < rb.x);
578578
579- t.insert_split(c, b, Axis::Vertical);
579+ t.insert_split(c, b, Axis::Vertical, |_| true);
580580 t.check();
581581 let (rb, rc) = (rect_of(&t, b, |_| true), rect_of(&t, c, |_| true));
582582 assert_eq!(
@@ -591,11 +591,11 @@ fn splitting_a_lone_window_shares_its_space() {
591591 fn splitting_along_the_parents_axis_appends_a_sibling() {
592592 let (a, b, c, d) = (1, 2, 3, 4);
593593 let mut t: Tree<u32> = Tree::default();
594- t.insert_row(a);
595- t.insert_split(b, a, Axis::Horizontal);
596- t.insert_split(c, b, Axis::Vertical);
594+ t.insert_row(a, |_| true);
595+ t.insert_split(b, a, Axis::Horizontal, |_| true);
596+ t.insert_split(c, b, Axis::Vertical, |_| true);
597597 let before = rect_of(&t, a, |_| true);
598- t.insert_split(d, a, Axis::Horizontal);
598+ t.insert_split(d, a, Axis::Horizontal, |_| true);
599599 t.check();
600600 let all = |_| true;
601601 let (ra, rd, rb) = (
@@ -612,19 +612,19 @@ fn splitting_along_the_parents_axis_appends_a_sibling() {
612612 fn splitting_beside_hidden_windows_leaves_their_slots_alone() {
613613 let (a, b, c, d) = (1, 2, 3, 4);
614614 let mut t: Tree<u32> = Tree::default();
615- t.insert_row(a);
616- t.insert_after(b, a);
617- t.insert_after(c, a);
615+ t.insert_row(a, |_| true);
616+ t.insert_after(b, a, |_| true);
617+ t.insert_after(c, a, |_| true);
618618 let tag_one = |w: u32| w == a || w == d;
619619 assert_eq!(rect_of(&t, a, tag_one).h, AREA.h);
620620
621- t.insert_split(d, a, Axis::Vertical);
621+ t.insert_split(d, a, Axis::Vertical, |_| true);
622622 t.check();
623623 let (ra, rd) = (rect_of(&t, a, tag_one), rect_of(&t, d, tag_one));
624624 assert_eq!((ra.h, rd.h, ra.w), (AREA.h / 2.0, AREA.h / 2.0, AREA.w));
625625 assert!(ra.y < rd.y);
626626
627- t.insert_split(d + 1, a, Axis::Horizontal);
627+ t.insert_split(d + 1, a, Axis::Horizontal, |_| true);
628628 t.check();
629629 let shown = |w: u32| w == a || w == d + 1;
630630 let (ra, re) = (rect_of(&t, a, shown), rect_of(&t, d + 1, shown));
@@ -633,6 +633,30 @@ fn splitting_beside_hidden_windows_leaves_their_slots_alone() {
633633 assert_eq!(t.windows().count(), 5, "hidden windows stay in the tree");
634634 }
635635
636+/// Windows opened and closed beside a hidden one used to leave it a larger
637+/// share each time, doubling it per split.
638+#[test]
639+fn hidden_shares_survive_windows_coming_and_going() {
640+ let (a, h, n) = (1, 2, 3);
641+ let mut t: Tree<u32> = Tree::default();
642+ t.insert_row(a, |_| true);
643+ t.insert_after(h, a, |_| true);
644+ let vis = |w: u32| w != h;
645+ let before = rect_of(&t, h, |_| true);
646+ for _ in 0..3 {
647+ t.insert_split(n, a, Axis::Horizontal, vis);
648+ t.remove(n, vis);
649+ t.insert_after(n, a, vis);
650+ t.remove(n, vis);
651+ }
652+ t.check();
653+ let after = rect_of(&t, h, |_| true);
654+ assert!(
655+ (after.w - before.w).abs() < 1e-6,
656+ "{before:?} became {after:?}"
657+ );
658+}
659+
636660 #[test]
637661 fn autotile_dwindles_by_the_anchors_shape() {
638662 let mut l = Layout {
Modified file: crates/layout/src/tree.rs+36 additions−8 deletions
@@ -118,9 +118,9 @@ impl<W: Copy + Eq + Hash> Tree<W> {
118118
119119 /// Inserts `w` as the next sibling of `anchor`, or as a new row if
120120 /// `anchor` is not in the tree.
121- pub fn insert_after(&mut self, w: W, anchor: W) {
121+ pub fn insert_after(&mut self, w: W, anchor: W, vis: impl Fn(W) -> bool) {
122122 let Some(&a) = self.leaves.get(&anchor) else {
123- return self.insert_row(w);
123+ return self.insert_row(w, vis);
124124 };
125125 match self.position(a) {
126126 Some((p, i)) if Some(p) == self.root && self.axis(p) == Some(self.stack) => {
@@ -135,6 +135,7 @@ impl<W: Copy + Eq + Hash> Tree<W> {
135135 Some((p, i)) => {
136136 let n = self.new_leaf(w, self.size(p));
137137 self.attach(p, i + 1, n);
138+ self.rescale_hidden(n, true, &vis);
138139 self.reveal(n, true);
139140 }
140141 None => {
@@ -149,9 +150,9 @@ impl<W: Copy + Eq + Hash> Tree<W> {
149150
150151 /// Inserts `w` after `anchor` by splitting `anchor` along `axis`. They
151152 /// share the space `anchor` had, and its siblings keep theirs.
152- pub fn insert_split(&mut self, w: W, anchor: W, axis: Axis) {
153+ pub fn insert_split(&mut self, w: W, anchor: W, axis: Axis, vis: impl Fn(W) -> bool) {
153154 let Some(&a) = self.leaves.get(&anchor) else {
154- return self.insert_row(w);
155+ return self.insert_row(w, vis);
155156 };
156157 let size = self.size(a);
157158 let n = self.new_leaf(w, size);
@@ -184,8 +185,32 @@ impl<W: Copy + Eq + Hash> Tree<W> {
184185 }
185186 }
186187
188+ /// Scales the hidden siblings of a visible `n` arriving in or leaving its
189+ /// container by as much as the visible ones grow or shrink together, so
190+ /// the share they come back to does not drift as windows come and go.
191+ fn rescale_hidden(&mut self, n: NodeId, arriving: bool, vis: &impl Fn(W) -> bool) {
192+ let (Some((p, _)), true) = (self.position(n), self.visible(n, vis)) else {
193+ return;
194+ };
195+ let axis = self.axis(p).expect("parents are containers");
196+ let (shown, hidden): (Vec<NodeId>, Vec<NodeId>) = self
197+ .children(p)
198+ .iter()
199+ .filter(|&&c| c != n)
200+ .partition(|&&c| self.visible(c, vis));
201+ let rest: f64 = shown.iter().map(|&c| self.extent(c, axis, vis)).sum();
202+ if rest <= 0.0 {
203+ return;
204+ }
205+ let with = rest + self.extent(n, axis, vis);
206+ let by = if arriving { with / rest } else { rest / with };
207+ for c in hidden {
208+ self.scale(c, axis, by);
209+ }
210+ }
211+
187212 /// Appends `w` as a row of its own along the stacking axis.
188- pub fn insert_row(&mut self, w: W) {
213+ pub fn insert_row(&mut self, w: W, vis: impl Fn(W) -> bool) {
189214 let size = self.root.map_or([1.0; 2], |r| self.size(r));
190215 let n = self.new_leaf(w, size);
191216 match self.root {
@@ -193,6 +218,7 @@ impl<W: Copy + Eq + Hash> Tree<W> {
193218 Some(r) if self.axis(r) == Some(self.stack) => {
194219 let end = self.children(r).len();
195220 self.attach(r, end, n);
221+ self.rescale_hidden(n, true, &vis);
196222 }
197223 Some(r) => {
198224 let c = self.new_container(self.stack);
@@ -204,10 +230,12 @@ impl<W: Copy + Eq + Hash> Tree<W> {
204230 self.reveal(n, true);
205231 }
206232
207- pub fn remove(&mut self, w: W) -> bool {
208- let Some(n) = self.leaves.remove(&w) else {
233+ pub fn remove(&mut self, w: W, vis: impl Fn(W) -> bool) -> bool {
234+ let Some(&n) = self.leaves.get(&w) else {
209235 return false;
210236 };
237+ self.rescale_hidden(n, false, &vis);
238+ self.leaves.remove(&w);
211239 self.detach(n);
212240 self.nodes.remove(n);
213241 true
@@ -216,7 +244,7 @@ impl<W: Copy + Eq + Hash> Tree<W> {
216244 pub fn retain(&mut self, mut keep: impl FnMut(W) -> bool) {
217245 let gone: Vec<W> = self.windows().filter(|&w| !keep(w)).collect();
218246 for w in gone {
219- self.remove(w);
247+ self.remove(w, |_| false);
220248 }
221249 }