pub struct PaneTree { /* private fields */ }Expand description
The pane tree owned by App (DESIGN.md §5.9, lives in
lattice-ui-tui). v1 supports
arbitrary recursive splits; the sole constraint is that the
active pane must always exist (closing the last pane is a
no-op so the App is never “paneless”).
Leaves are addressed two ways: by index into Self::leaves
(what PaneNode::Leaf and most methods use — not stable across
Self::close_active) and by PaneId (stable; resolve with
Self::index_of).
§Examples
use lattice_core::ui::pane::{PaneRect, PaneState, PaneTree, SplitOrientation};
let mut tree = PaneTree::single(PaneState::default());
let right = tree.split_active(SplitOrientation::Vertical); // `<C-w>v`
assert_eq!(tree.len(), 2);
assert_eq!(tree.active_index(), 0); // focus stays on the original
// An even vertical split of an 80x24 area.
let area = PaneRect { x: 0, y: 0, width: 80, height: 24 };
let rects = tree.compute_rects(area);
assert_eq!(rects[0], (0, PaneRect { x: 0, y: 0, width: 40, height: 24 }));
assert_eq!(rects[1], (right, PaneRect { x: 40, y: 0, width: 40, height: 24 }));
// Zoom (`<C-w>z`) hands the active pane the whole area, non-destructively.
assert!(tree.toggle_zoom());
assert_eq!(tree.compute_rects(area), [(0, area)]);
assert!(tree.toggle_zoom());
assert_eq!(tree.compute_rects(area).len(), 2);
// Closing the last pane is refused.
assert!(tree.close_active());
assert!(!tree.close_active());Implementations§
Source§impl PaneTree
impl PaneTree
Sourcepub fn zoomed(&self) -> Option<PaneId>
pub fn zoomed(&self) -> Option<PaneId>
The zoomed pane’s id, or None when the full split
layout is showing.
Slice: ZP.1.
Sourcepub fn is_zoomed(&self) -> bool
pub fn is_zoomed(&self) -> bool
Whether a pane is currently zoomed. Read by the
modeline’s core.zoom element and the tabline marker.
Slice: ZP.1.
Sourcepub fn zoomed_index(&self) -> Option<usize>
pub fn zoomed_index(&self) -> Option<usize>
The zoomed pane’s leaf index, resolved through
Self::index_of. None when nothing is zoomed, and also
when the recorded id no longer names a live leaf — a state
the enforcement below is meant to prevent, but resolving
rather than trusting means a stale id degrades to “not
zoomed” instead of to a panic on the render path.
Slice: ZP.1.
Sourcepub fn toggle_zoom(&mut self) -> bool
pub fn toggle_zoom(&mut self) -> bool
Toggle zoom on the active pane (<C-w>z). Returns
true if the zoom state changed.
A single-leaf tree is a no-op: there is nothing to hide, and marking it zoomed would light the indicator for a state the user cannot see.
Slice: ZP.1.
Sourcepub fn clear_zoom(&mut self) -> bool
pub fn clear_zoom(&mut self) -> bool
Drop zoom unconditionally. Returns true if it was
set. Called by every mutation that would otherwise break the
zoomed-is-active invariant.
Slice: ZP.1.
Sourcepub fn root(&self) -> &PaneNode
pub fn root(&self) -> &PaneNode
The layout root, ignoring zoom. Renderers that recurse over the
tree should use Self::render_root instead.
Sourcepub fn leaves(&self) -> &[PaneState]
pub fn leaves(&self) -> &[PaneState]
Every pane, in leaf-index order (the indices PaneNode::Leaf
holds). Never empty.
Sourcepub fn leaves_mut(&mut self) -> &mut [PaneState]
pub fn leaves_mut(&mut self) -> &mut [PaneState]
Mutable access to every pane’s state (cursor, scroll, viewport size…). The slice cannot grow or shrink, so the tree shape stays consistent.
Sourcepub fn is_empty(&self) -> bool
pub fn is_empty(&self) -> bool
Always false — a tree is never paneless. Present for the
len/is_empty convention.
Sourcepub fn active_index(&self) -> usize
pub fn active_index(&self) -> usize
Leaf index of the focused pane.
Sourcepub fn active_mut(&mut self) -> &mut PaneState
pub fn active_mut(&mut self) -> &mut PaneState
The focused pane’s state, mutably.
Sourcepub fn set_active(&mut self, idx: usize) -> bool
pub fn set_active(&mut self, idx: usize) -> bool
Set the active pane by index. Out-of-bounds indices are
ignored. Returns true if the index changed.
Sourcepub fn index_of(&self, id: PaneId) -> Option<usize>
pub fn index_of(&self, id: PaneId) -> Option<usize>
Locate a pane by its PaneId. Returns the index into
Self::leaves or None if the id is unknown.
Sourcepub fn split_active(&mut self, orientation: SplitOrientation) -> usize
pub fn split_active(&mut self, orientation: SplitOrientation) -> usize
Split the active pane along orientation, inserting a new
leaf next to it. The new leaf inherits the active pane’s
buffer + cursor + scroll (vim’s <C-w>s / <C-w>v default).
Returns the new pane’s index. The active pane stays the
original leaf – the new sibling becomes inactive.
Sourcepub fn close_active(&mut self) -> bool
pub fn close_active(&mut self) -> bool
Close the active pane. The parent split collapses to the
surviving sibling. If the tree has only one pane, the close
is a no-op (the App is never paneless). Returns true if a
pane was actually removed.
Sourcepub fn collapse_to_active(&mut self) -> bool
pub fn collapse_to_active(&mut self) -> bool
<C-w>o / :only / emacs C-x 1 – close every pane except
the active one, collapsing the whole tree to a single leaf that
keeps the active pane’s state. No-op (returns false) when only
one pane is open. Unlike repeated Self::close_active, this
keeps the active pane and drops its siblings in one step.
Sourcepub fn equalize_ratios(&mut self) -> bool
pub fn equalize_ratios(&mut self) -> bool
Walk the tree and reset every
split’s ratio to DEFAULT_SPLIT_RATIO (0.5). Vim’s
<C-w>=. Returns true if any ratio actually changed,
so the renderer can skip the publish when there’s
nothing to do.
Slice: Issue #28 (2026-05-22).
Sourcepub fn resize_active_split(
&mut self,
orientation: SplitOrientation,
delta: f32,
) -> bool
pub fn resize_active_split( &mut self, orientation: SplitOrientation, delta: f32, ) -> bool
Adjust the ratio of the nearest split-of-the-
requested-orientation containing the active pane. Vim’s
<C-w>+ / <C-w>- (HorizontalSplit) / <C-w>> /
<C-w>< (VerticalSplit). delta is added to the
current ratio (positive = grow active side); clamped to
[MIN_SPLIT_RATIO, MAX_SPLIT_RATIO]. Returns true if a
ratio was found and changed.
“Active side” semantics: if the active leaf is in the
top (or left) child, growing means increasing the
ratio (top/left gets bigger). If active is in bottom
(or right), growing means DECREASING the ratio.
Slice: Issue #28.
Navigate cardinally from the active pane. Returns the new
active leaf index, or None if there’s no neighbour in that
direction. Geometry comes from Self::compute_rects so
the navigation matches what the renderer drew.
A candidate must also OVERLAP the source on the perpendicular axis,
and among those that do, the source’s cursor decides. Both halves are
load-bearing, and their absence was one bug:
In a 2×2 grid every pane below the top row starts at the same y, so
ranking by travel distance alone left every candidate tied — and the
winner fell out of leaf iteration order, which is tree order, not
screen order. <C-w>j from the top-RIGHT pane landed in the bottom-
LEFT one, and so did <C-w>j from the top-left, which is how the bug
reads to a user: the direction keys ignore where you are.
Overlap alone is not enough either. One wide pane above two narrow ones overlaps both, so vim breaks that tie with the cursor’s screen position — you go down into the pane under your cursor — and that is the behaviour muscle memory expects.
Sourcepub fn compute_rects(&self, area: PaneRect) -> Vec<(usize, PaneRect)>
pub fn compute_rects(&self, area: PaneRect) -> Vec<(usize, PaneRect)>
Compute the rectangle each leaf occupies inside area. The
renderer + navigation use this to lay out / find spatial
neighbours. Each split divides its area by its ratio
(rounded to whole cells). Under zoom, only the zoomed pane is
returned, with the whole area. Entries are (leaf index, rect)
in depth-first, top-to-bottom / left-to-right order.
Sourcepub fn render_root(&self) -> Cow<'_, PaneNode>
pub fn render_root(&self) -> Cow<'_, PaneNode>
The node a renderer should paint — the zoomed leaf when zoomed, otherwise the real root.
For renderers that recurse over PaneNode themselves rather
than calling Self::compute_rects (the GPUI peer’s
collect_pane_geometries and paint_pane_tree). Expressing
zoom as “the tree is one leaf” means those walks stay exactly
as they were — no zoom branch inside the recursion, where it
would have to be re-checked at every level.
Allocation-free in both arms: borrowed for the real root, and
the owned arm is a bare Leaf(usize) with no boxed children.
Slice: ZP.3.
Sourcepub fn compute_rects_layout(&self, area: PaneRect) -> Vec<(usize, PaneRect)>
pub fn compute_rects_layout(&self, area: PaneRect) -> Vec<(usize, PaneRect)>
The always-unzoomed peer of Self::compute_rects —
the full split layout, whatever the zoom state.
One caller: Self::navigate. Cardinal navigation has to
ask where a pane sits in the REAL layout, because the answer
decides where focus lands after the zoom drops. Reading the
zoom-aware view instead would hand it a one-entry list, no
neighbour would be found in any direction, and <C-w>j while
zoomed would silently do nothing.
Slice: ZP.1.