# `MingaEditor.Layout`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga_editor/layout.ex#L1)

Single source of truth for all screen rectangles.

`Layout.compute/1` takes editor state and produces a struct of named
rectangles for every UI element. The renderer never computes its own
coordinates; it receives pre-computed rectangles and draws into them.

## Rectangle format

All rectangles are `{row, col, width, height}` tuples matching the
existing `WindowTree.rect()` type. The origin is the top-left corner
of the allocated area.

## Regions vs Overlays

**Regions** are non-overlapping areas that tile the screen: file tree,
editor area, agent panel, modeline, minibuffer. They participate in
the non-overlap invariant.

**Overlays** float over regions: picker, which-key popup, completion
menu. They have positioning rects but don't participate in the tiling
constraint.

# `horizontal_separator`

```elixir
@type horizontal_separator() ::
  {non_neg_integer(), non_neg_integer(), pos_integer(), String.t()}
```

A horizontal separator between split panes: {row, col, width, filename}.

# `rect`

```elixir
@type rect() :: {non_neg_integer(), non_neg_integer(), pos_integer(), pos_integer()}
```

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

# `t`

```elixir
@type t() :: %MingaEditor.Layout{
  agent_panel: rect() | nil,
  editor_area: rect(),
  file_tree: rect() | nil,
  horizontal_separators: [horizontal_separator()],
  minibuffer: rect(),
  status_bar: rect() | nil,
  tab_bar: rect() | nil,
  terminal: rect(),
  window_layouts: %{required(MingaEditor.Window.id()) =&gt; window_layout()}
}
```

Complete layout for one frame.

# `window_layout`

```elixir
@type window_layout() :: %{
  total: rect(),
  content: rect(),
  modeline: {non_neg_integer(), non_neg_integer(), pos_integer(), 0},
  sidebar: rect() | nil
}
```

Layout for a single editor window, with sub-rects for each chrome element.

- `total` — the full window rect (from WindowTree.layout)
- `content` — the text area within the window (full window; no per-window modeline)
- `modeline` — always zero-height; kept for backward compatibility. The global status bar
  at `Layout.t().status_bar` replaces per-window modelines.
- `sidebar` — optional info panel (agent chat dashboard)

# `active_content_height`

```elixir
@spec active_content_height(t(), MingaEditor.State.t()) :: pos_integer()
```

Returns the content height (visible rows) for the active window.

# `active_content_width`

```elixir
@spec active_content_width(t(), MingaEditor.State.t()) :: pos_integer()
```

Returns the content width for the active window.

This is useful for computing gutter width and wrap maps before rendering.

# `active_window_layout`

```elixir
@spec active_window_layout(t(), MingaEditor.State.t()) :: window_layout() | nil
```

Returns the window layout for the active window, or nil.

# `add_sidebar`

```elixir
@spec add_sidebar(window_layout()) :: window_layout()
```

Splits a window layout's content rect to carve out a sidebar.

Returns the layout with `content` narrowed and `sidebar` set to
the right-hand info panel rect. Only applied when the content width
exceeds `@sidebar_threshold`.

# `compute`

```elixir
@spec compute(MingaEditor.State.t() | map()) :: t()
```

Computes the complete layout for the current frame.

Delegates to the shell's `compute_layout` callback, which dispatches
to the appropriate TUI or GUI layout module.

# `compute_window_layouts_with_separators`

```elixir
@spec compute_window_layouts_with_separators(
  MingaEditor.WindowTree.t(),
  rect(),
  map()
) ::
  {%{required(MingaEditor.Window.id()) =&gt; window_layout()},
   [horizontal_separator()]}
```

Computes window layouts and horizontal separator positions simultaneously.

Horizontal splits steal 1 row from the top window for a separator bar.
The separator shows the lower window's buffer filename.

Returns `{window_layouts_map, horizontal_separators_list}`.

# `get`

```elixir
@spec get(MingaEditor.State.t() | map()) :: t()
```

Returns the cached layout from state, or computes it fresh.

Prefer this over `compute/1` when you have a state that might already
have a cached layout. The cache is invalidated on resize, file tree
toggle, and agent panel toggle.

# `invalidate`

```elixir
@spec invalidate(MingaEditor.State.t()) :: MingaEditor.State.t()
```

Invalidates the cached layout. Call when layout-affecting state changes
(viewport resize, file tree toggle, agent panel toggle, window split/close).

# `put`

```elixir
@spec put(MingaEditor.State.t() | map()) :: map()
```

Computes the layout and stores it in state for reuse within the same frame.

Call this once at the start of a render cycle or event handler, then
read `state.layout` downstream.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
