#[non_exhaustive]pub enum LayoutTree {
Leaf(WindowId),
Split {
dir: SplitDir,
ratio: f32,
fixed: Option<Fixed>,
a: Box<Self>,
b: Box<Self>,
last_rect: Option<LayoutRect>,
},
}Expand description
A binary spatial tree that partitions the editor area into windows.
§Examples
use hjkl_layout::{LayoutTree, SplitDir};
let tree = LayoutTree::split(
SplitDir::Horizontal,
0.5,
LayoutTree::Leaf(0),
LayoutTree::Leaf(1),
);
assert_eq!(tree.leaves(), vec![0, 1]);
assert_eq!(tree.neighbor_below(0), Some(1));Variants (Non-exhaustive)§
This enum is marked as non-exhaustive
Leaf(WindowId)
A single window occupying the full allocated area.
Split
Two sub-trees dividing the available space.
Fields
ratio: f32Fraction of the available space allocated to a. 0.0 < ratio < 1.0.
Ignored when fixed is Some — but still retained (and still
mutated by resize commands) so that clearing fixed restores the
previous proportional layout.
fixed: Option<Fixed>Exact cell allocation for one child, overriding ratio when set.
This is how a dock-style window keeps a constant width/height while its sibling absorbs every resize.
last_rect: Option<LayoutRect>Rect this split last occupied. Filled by the renderer each frame; read by resize commands to convert line/col deltas to ratio updates. None before the first render.
Implementations§
Source§impl LayoutTree
impl LayoutTree
Sourcepub fn split(dir: SplitDir, ratio: f32, a: Self, b: Self) -> Self
pub fn split(dir: SplitDir, ratio: f32, a: Self, b: Self) -> Self
Convenience constructor for an ordinary proportional split
(fixed: None, last_rect: None).
§Examples
use hjkl_layout::{LayoutTree, SplitDir};
let tree = LayoutTree::split(
SplitDir::Vertical,
0.5,
LayoutTree::Leaf(0),
LayoutTree::Leaf(1),
);
assert_eq!(tree.leaves(), vec![0, 1]);Sourcepub fn split_fixed(
dir: SplitDir,
ratio: f32,
fixed: Fixed,
a: Self,
b: Self,
) -> Self
pub fn split_fixed( dir: SplitDir, ratio: f32, fixed: Fixed, a: Self, b: Self, ) -> Self
Convenience constructor for a split that renders one child at a fixed number of cells along the split axis.
ratio is still stored (and used if fixed is later cleared); see
Fixed for the exact geometry.
§Examples
use hjkl_layout::{Fixed, LayoutRect, LayoutTree, SplitDir};
// A 30-column dock on the left, the rest for window 1.
let tree = LayoutTree::split_fixed(
SplitDir::Vertical,
0.5,
Fixed::First(30),
LayoutTree::Leaf(0),
LayoutTree::Leaf(1),
);
let rects = tree.window_rects(LayoutRect::new(0, 0, 80, 24));
// 30 columns of dock, 1 separator column, 49 columns for window 1.
assert_eq!(rects[0].1.w, 30);
assert_eq!(rects[1].1.w, 49);Sourcepub fn leaves(&self) -> Vec<WindowId> ⓘ
pub fn leaves(&self) -> Vec<WindowId> ⓘ
Pre-order traversal — returns all leaf ids in the order they appear top-to-bottom / left-to-right in the layout.
§Examples
use hjkl_layout::{LayoutTree, SplitDir};
let tree = LayoutTree::split(
SplitDir::Horizontal,
0.5,
LayoutTree::Leaf(0),
LayoutTree::Leaf(1),
);
assert_eq!(tree.leaves(), vec![0, 1]);Sourcepub fn next_leaf(&self, id: WindowId) -> Option<WindowId>
pub fn next_leaf(&self, id: WindowId) -> Option<WindowId>
Return the next leaf in pre-order traversal, wrapping around.
Returns None only if id is not in the tree (shouldn’t happen in
practice).
Sourcepub fn prev_leaf(&self, id: WindowId) -> Option<WindowId>
pub fn prev_leaf(&self, id: WindowId) -> Option<WindowId>
Return the previous leaf in pre-order traversal, wrapping around.
Returns None only if id is not in the tree (shouldn’t happen in
practice).
Sourcepub fn replace_leaf<F: FnOnce(WindowId) -> Self + 'static>(
&mut self,
id: WindowId,
f: F,
) -> bool
pub fn replace_leaf<F: FnOnce(WindowId) -> Self + 'static>( &mut self, id: WindowId, f: F, ) -> bool
Find the leaf for id and replace it in-place with f(id).
Returns true if the leaf was found and replaced.
Sourcepub fn neighbor_below(&self, id: WindowId) -> Option<WindowId>
pub fn neighbor_below(&self, id: WindowId) -> Option<WindowId>
Return the id of the next leaf below id in a Horizontal split,
using pre-order traversal semantics.
“Below” means: walking up from id, find the innermost enclosing
Horizontal split where id lives in a; the answer is then the
first (leftmost) leaf of b. If id is the bottom-most window
(or there are no horizontal splits above it), returns None.
Sourcepub fn neighbor_above(&self, id: WindowId) -> Option<WindowId>
pub fn neighbor_above(&self, id: WindowId) -> Option<WindowId>
Return the id of the next leaf above id in a Horizontal split.
Sourcepub fn neighbor_left(&self, id: WindowId) -> Option<WindowId>
pub fn neighbor_left(&self, id: WindowId) -> Option<WindowId>
Return the id of the next leaf to the left of id in a Vertical
split. Horizontal splits are passed through.
Sourcepub fn neighbor_right(&self, id: WindowId) -> Option<WindowId>
pub fn neighbor_right(&self, id: WindowId) -> Option<WindowId>
Return the id of the next leaf to the right of id in a Vertical
split. Horizontal splits are passed through.
Sourcepub fn enclosing_split_mut(
&mut self,
id: WindowId,
dir: SplitDir,
) -> Option<(&mut f32, Option<LayoutRect>, bool)>
pub fn enclosing_split_mut( &mut self, id: WindowId, dir: SplitDir, ) -> Option<(&mut f32, Option<LayoutRect>, bool)>
Walk the tree looking for the innermost enclosing Split with matching
dir that contains id. Returns a mutable reference to the ratio,
a copy of the last_rect, and whether the focused leaf is in a.
Returns None if no such enclosing Split exists.
Splits carrying a Fixed allocation are never candidates. Their
size belongs to whoever set it (a dock’s configured width, say), not to
a resize command, and ratio is ignored while fixed is set — writing
it would do nothing now and make the layout jump later, when fixed is
cleared. The search simply continues outward, so a <C-w>+-style resize
inside a fixed pane moves the nearest resizable ancestor instead. That
is vim’s winfixwidth / winfixheight behaviour.
Sourcepub fn equalize_all(&mut self, pinned: &[WindowId])
pub fn equalize_all(&mut self, pinned: &[WindowId])
Reset all splits in the tree to 0.5 ratio, leaving pinned leaves at their current size.
pinned lists the leaves that must survive equalization unchanged
(docks and other fixed-size windows). A split is left alone when it
carries a Fixed allocation or when either of its immediate children
is a pinned leaf; recursion still descends into both children so
ordinary splits underneath a pinned one are still equalized.
Pass &[] for the plain “equalize everything” behaviour.
§Examples
use hjkl_layout::{Fixed, LayoutRect, LayoutTree, SplitDir};
let mut tree = LayoutTree::split_fixed(
SplitDir::Vertical,
0.5,
Fixed::First(30),
LayoutTree::Leaf(0),
LayoutTree::Leaf(1),
);
let area = LayoutRect::new(0, 0, 80, 24);
let before = tree.window_rects(area);
tree.equalize_all(&[0]);
assert_eq!(tree.window_rects(area), before);Sourcepub fn only(&mut self, keep: WindowId, pinned: &[WindowId]) -> Vec<WindowId> ⓘ
pub fn only(&mut self, keep: WindowId, pinned: &[WindowId]) -> Vec<WindowId> ⓘ
Collapse the tree down to keep plus every pinned leaf (vim’s :only).
Leaves that are neither keep nor listed in pinned are dropped and
their parent splits collapse onto the surviving sibling, so the relative
arrangement of the retained leaves (and the geometry of the splits that
join them) is preserved.
Returns the ids of the removed leaves so the caller can dispose of the
corresponding window state. Returns an empty vector — and leaves the
tree untouched — when keep is not in the tree.
§Examples
use hjkl_layout::{LayoutTree, SplitDir};
// dock | (1 | 2)
let mut tree = LayoutTree::split(
SplitDir::Vertical,
0.5,
LayoutTree::Leaf(9),
LayoutTree::split(SplitDir::Vertical, 0.5, LayoutTree::Leaf(1), LayoutTree::Leaf(2)),
);
let removed = tree.only(1, &[9]);
assert_eq!(removed, vec![2]);
assert_eq!(tree.leaves(), vec![9, 1]);Sourcepub fn for_each_ancestor<F>(&mut self, id: WindowId, f: &mut F)
pub fn for_each_ancestor<F>(&mut self, id: WindowId, f: &mut F)
For each enclosing Split on the path from root to leaf id, invoke
f with the split’s mutable state. Order: outermost first.
Splits carrying a Fixed allocation are skipped (recursion still
descends through them) for the same reason
enclosing_split_mut refuses them: their
size is not a ratio anyone may rewrite.
Sourcepub fn window_rects(&self, area: LayoutRect) -> Vec<(WindowId, LayoutRect)>
pub fn window_rects(&self, area: LayoutRect) -> Vec<(WindowId, LayoutRect)>
Walk the tree and compute the LayoutRect each leaf window occupies
within area. Every step goes through split_geometry, which is also
what the TUI renderer descends with, so headless geometry is the same
geometry the user sees rather than a copy of it.
§Split math (see split_geometry)
For a Horizontal split (stacks top-to-bottom, Axis::Row):
a_h = round(area.h * ratio).clamp(1, area.h - 1)
b_h = area.h - a_h
If a_h >= 2 and b_h > 0: the bottom row of a becomes a separator;
a shrinks by 1 (height becomes a_h - 1), b starts after the separator.
For a Vertical split (side-by-side, Axis::Col):
a_w = round(area.w * ratio).clamp(1, area.w - 1)
b_w = area.w - a_w
If a_w >= 2 and b_w > 0: the rightmost column of a becomes a separator;
a shrinks by 1 (width becomes a_w - 1), b starts right after a.
Leaf → single entry (id, area).
§Fixed sizes
When the split carries fixed: Some(..), the round(len * ratio) step
above is replaced by whatever allocation makes the named child render
the requested number of cells, and the sibling takes the remainder. The
two variants are symmetric: on an 80-column vertical split both
Fixed::First(30) and Fixed::Second(30) produce a 30-column rect for
their child, with the separator absorbed on the a side either way
(First(30) → a = 30, b = 49; Fixed::Second(30) → a = 49,
b = 30).
The allocation is clamped to 1 ..= axis_len - 1 — the same clamp the
ratio path applies — so an oversized fixed size degrades to “as large as
possible while the sibling still gets one cell” instead of underflowing
or starving the sibling.
§Examples
use hjkl_layout::{LayoutTree, SplitDir, LayoutRect};
let tree = LayoutTree::Leaf(0);
let area = LayoutRect::new(0, 0, 80, 23);
let rects = tree.window_rects(area);
assert_eq!(rects, vec![(0, area)]);Sourcepub fn swap_with_sibling(&mut self, id: WindowId, pinned: &[WindowId]) -> bool
pub fn swap_with_sibling(&mut self, id: WindowId, pinned: &[WindowId]) -> bool
Swap the two children of the deepest Split that directly contains
Leaf(id) as one of its a or b children.
Returns true if the swap was applied (i.e. there is an enclosing
Split — false when id is the only window).
Refuses (returns false, tree untouched) when either side of the swap
is pinned: id itself being in pinned, or the sibling subtree
containing any pinned leaf. A dock must not be dragged across the
layout, nor another window dragged through it.
Pass &[] for the plain “swap anything” behaviour.
Sourcepub fn remove_leaf(&mut self, id: WindowId) -> Result<WindowId, &'static str>
pub fn remove_leaf(&mut self, id: WindowId) -> Result<WindowId, &'static str>
Remove the leaf id from the tree. When its parent Split is left
with only the sibling, that split is replaced by the sibling subtree
(collapse).
Returns the WindowId of the leaf that should receive focus after
removal (the sibling that survived the collapse), or Err if id is
the only remaining leaf.
§Errors
Returns Err("E444: Cannot close last window") when attempting to
remove the only leaf in the tree.
Trait Implementations§
Source§impl Clone for LayoutTree
impl Clone for LayoutTree
Source§fn clone(&self) -> LayoutTree
fn clone(&self) -> LayoutTree
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more