Skip to main content

LayoutTree

Enum LayoutTree 

Source
#[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
Non-exhaustive enums could have additional variants added in future. Therefore, when matching against variants of non-exhaustive enums, an extra wildcard arm must be added to account for any future variants.
§

Leaf(WindowId)

A single window occupying the full allocated area.

§

Split

Two sub-trees dividing the available space.

Fields

§dir: SplitDir

Axis along which the space is divided.

§ratio: f32

Fraction 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.

§a: Box<Self>

The first (top / left) sub-tree.

§b: Box<Self>

The second (bottom / right) sub-tree.

§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

Source

pub fn new(id: WindowId) -> Self

Create a new single-leaf layout tree containing id.

Source

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]);
Source

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);
Source

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]);
Source

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).

Source

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).

Source

pub fn contains(&self, id: WindowId) -> bool

Return true if id appears anywhere in the tree.

Source

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.

Source

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.

Source

pub fn neighbor_above(&self, id: WindowId) -> Option<WindowId>

Return the id of the next leaf above id in a Horizontal split.

Source

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.

Source

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.

Source

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.

Source

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);
Source

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]);
Source

pub fn for_each_ancestor<F>(&mut self, id: WindowId, f: &mut F)
where F: FnMut(SplitDir, &mut f32, bool, Option<LayoutRect>),

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.

Source

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)]);
Source

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.

Source

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

Source§

fn clone(&self) -> LayoutTree

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for LayoutTree

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for LayoutTree

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.