Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Stage 10 — Split-Tree Layout

What it is. Replacing the flat, list-order-driven spiral layout with an explicit split tree: every tiled window is a leaf in a tree of vertical/horizontal splits, each split with its own adjustable ratio.

Why it matters. The current layout is a pure function of the window list’s order — simple, reproducible, unit-testable (see Design & ideas), but it can’t express “make this window a bit wider” and it’s the structural cause of the corner-touch ambiguity in directional navigation documented in Stage 5. A real tree fixes both: per-window resize, and fully reversible neighbor relationships.

Deliverable (from KICKOFF.md): resize a window from the keyboard and the layout keeps it.

How it went

The tree (layout::Node) is a binary Leaf/Split { vertical, ratio, first, second }, one per workspace (Workspace.tree), shaped exactly like the old spiral at the default 0.5 ratio — a parity test checks the two produce bit-identical rects. The key design choice: leaves carry no payload. Which window occupies tiled-position i is decided entirely by tiled_windows(ws)’s order, the same as it always was; the tree only adds the one thing that was missing, a persisted ratio per split. That statelessness is what makes MoveWindow’s .swap() need zero tree code — swapping two Vec entries already reproduces “swap tiling position” without touching a single Node.

Keeping the tree’s leaf count exactly matched to tiled_windows(ws).len() turned out to be the real work of the stage: every place a window starts or stops tiling — map, unmap/destroy, set_floating, set_fullscreen, move_to_workspace — now calls tree_track/tree_untrack around the state flip. tree_insert_at/tree_remove are pure and ratio-preserving: removing a window collapses its parent split into whichever sibling remains (ratios elsewhere untouched), and inserting elsewhere than the end (a window rejoining the tiled set after a float/fullscreen toggle) walks to the exact position its Workspace.windows order implies. The old spiral survives only as spiral_rects, #[cfg(test)]-gated — the reference oracle the tree gets checked against, no longer called at runtime. The initial-configure size prediction (Stage 8) now simulates an append on a clone of the real tree (predict_tile_rect) instead of recomputing from a count, so a client’s first frame still matches what it actually gets on map.

tree_resize is the deliverable itself: from the focused window’s leaf, walk up to the nearest ancestor split whose axis matches the pressed direction (vertical for Left/Right, horizontal for Up/Down) and move that split’s one shared edge — deliberately local, no cascading to a farther split even when one exists further up. The first attempt made the “wrong” direction a no-op (reasoning: that edge is the window’s own outer boundary, nothing to push into) — wrong, because it’s still the same shared edge, just moved the other way. Fixed so every direction on the matching axis does something: whichever key grows the focused window, the opposite always undoes it, on either side of the split. Bound to Mod+Ctrl+hjkl.

Not pursued: the “fully reversible neighbor relationships” half of why it matters above. spatial_neighbor still uses Stage 5’s geometric heuristic, corner-touch case and all — swapping it for exact tree adjacency is now possible (the tree has the real structure spatial_neighbor was missing) but wasn’t necessary for the resize deliverable, so it’s left as a possible follow-up rather than done here.

Verified with cargo test (parity, insert/remove ratio-preservation, resize’s grow/shrink/clamp/locality) and manually on a real TTY session: opened several windows, resized one with Mod+Ctrl+L, opened another window and confirmed the resized one kept its size, and confirmed the opposite key (Mod+Ctrl+H) shrinks it back.

Status

Done. The gate — resize a window from the keyboard and the layout keeps it — is verified both by tests and on real hardware.