MingaEditor.WindowTree (Minga v0.1.0)

Copy Markdown View Source

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

t()

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

direction()

@type direction() :: :vertical | :horizontal

Split direction.

nav_direction()

@type nav_direction() :: :left | :right | :up | :down

Navigation direction for focus movement.

rect()

A screen rectangle: {row, col, width, height}.

t()

@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

clamp_size(size, total)

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

close(tree, target)

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

count(arg)

@spec count(t()) :: pos_integer()

Returns the number of leaves (windows) in the tree.

focus_neighbor(tree, from_id, direction, screen_rect)

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

layout(tree, rect)

@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}}.

leaves(arg)

@spec leaves(t()) :: [MingaEditor.Window.id()]

Returns all window ids in the tree, left-to-right / top-to-bottom order.

member?(arg, id)

@spec member?(t(), MingaEditor.Window.id()) :: boolean()

Returns true if the given window id exists in the tree.

new(id)

@spec new(MingaEditor.Window.id()) :: t()

Creates a tree with a single window.

resize_at(tree, screen_rect, direction, separator_pos, new_pos)

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

separator_at(tree, screen_rect, row, col)

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

split(tree, target_id, direction, new_id)

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

window_at(tree, screen_rect, row, col)

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