A binary tree representing the spatial layout of editor windows.
Each leaf is a window id. Each branch is a horizontal or vertical split containing two subtrees. The tree is used to compute screen regions for each window and to navigate between them directionally.
Structure
{:leaf, window_id}
{:split, :vertical, left_tree, right_tree}
{:split, :horizontal, top_tree, bottom_tree}Vertical splits produce side-by-side panes. Horizontal splits produce stacked panes.
Summary
Types
Split direction.
Navigation direction for focus movement.
A screen rectangle: {row, col, width, height}.
The tree structure.
Functions
Resolves a split size: 0 means "half", otherwise clamp to [1, total-1].
Removes the leaf containing window_id from the tree.
Returns the number of leaves (windows) in the tree.
Finds the neighbor window id when moving from from_id in the given direction.
Computes the screen rectangle for each leaf window.
Returns all window ids in the tree, left-to-right / top-to-bottom order.
Returns true if the given window id exists in the tree.
Creates a tree with a single window.
Resizes the split that owns the separator at separator_pos to place
that separator at new_pos. Only resizes vertical splits (by column)
for now.
Tests whether a screen coordinate is on a separator.
Splits the leaf containing window_id in the given direction.
Finds which window contains the given screen coordinate.
Types
@type direction() :: :vertical | :horizontal
Split direction.
@type rect() :: {non_neg_integer(), non_neg_integer(), pos_integer(), pos_integer()}
A screen rectangle: {row, col, width, height}.
@type t() :: {:leaf, MingaEditor.Window.id()} | {:split, direction(), t(), t(), non_neg_integer()}
The tree structure.
Split nodes carry a size — the number of columns (vertical) or rows
(horizontal) allocated to the first child. The second child gets the
remainder minus any separator. A size of 0 means "split evenly" and is
resolved during layout.
Functions
@spec clamp_size(non_neg_integer(), pos_integer()) :: pos_integer()
Resolves a split size: 0 means "half", otherwise clamp to [1, total-1].
Used by layout and renderer to consistently compute child dimensions.
@spec close(t(), MingaEditor.Window.id()) :: {:ok, t()} | :error
Removes the leaf containing window_id from the tree.
The sibling subtree takes the removed node's place. Returns :error if
the tree is a single leaf (cannot close the last window) or if the id
is not found.
@spec count(t()) :: pos_integer()
Returns the number of leaves (windows) in the tree.
@spec focus_neighbor(t(), MingaEditor.Window.id(), nav_direction(), rect()) :: {:ok, MingaEditor.Window.id()} | :error
Finds the neighbor window id when moving from from_id in the given direction.
Uses the layout to determine spatial adjacency. Returns {:ok, neighbor_id}
or :error if there is no neighbor in that direction.
@spec layout(t(), rect()) :: [{MingaEditor.Window.id(), rect()}]
Computes the screen rectangle for each leaf window.
Given the total available rect {row, col, width, height}, recursively
splits the space according to the tree structure. Vertical splits divide
width (with 1 column reserved for the separator). Horizontal splits
divide height.
Returns a list of {window_id, {row, col, width, height}}.
@spec leaves(t()) :: [MingaEditor.Window.id()]
Returns all window ids in the tree, left-to-right / top-to-bottom order.
@spec member?(t(), MingaEditor.Window.id()) :: boolean()
Returns true if the given window id exists in the tree.
@spec new(MingaEditor.Window.id()) :: t()
Creates a tree with a single window.
@spec resize_at(t(), rect(), direction(), non_neg_integer(), non_neg_integer()) :: {:ok, t()} | :error
Resizes the split that owns the separator at separator_pos to place
that separator at new_pos. Only resizes vertical splits (by column)
for now.
Returns {:ok, new_tree} or :error if no matching split is found.
@spec separator_at(t(), rect(), non_neg_integer(), non_neg_integer()) :: {:ok, {direction(), non_neg_integer()}} | :error
Tests whether a screen coordinate is on a separator.
Returns {:ok, :vertical | :horizontal, separator_position} or :error.
For vertical splits, separator_position is the column; for horizontal,
it's the row. The position is used to identify which split node to resize.
@spec split(t(), MingaEditor.Window.id(), direction(), MingaEditor.Window.id()) :: {:ok, t()} | :error
Splits the leaf containing window_id in the given direction.
The existing window stays in the first position (left/top) and the new window takes the second position (right/bottom).
Returns {:ok, new_tree} or :error if window_id is not found.
@spec window_at(t(), rect(), non_neg_integer(), non_neg_integer()) :: {:ok, MingaEditor.Window.id(), rect()} | :error
Finds which window contains the given screen coordinate.
Returns {:ok, window_id, {row, col, width, height}} or :error if the
coordinate is outside any window rect (e.g. on a separator).