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

BEAM-side display list: a styled text run intermediate representation.

Sits between editor state and protocol encoding. The renderer produces
a `Frame` struct containing all visual content, then `to_commands/1`
converts it to protocol command binaries for the TUI frontend. Other
frontends (GUI, headless) can consume the frame directly.

## Coordinate system

All coordinates within a `WindowFrame` are **window-relative**: row 0,
col 0 is the top-left of the window's content rect. The `rect` field
carries the absolute screen position; `to_commands/1` adds the rect
offset when generating protocol commands.

Other frame sections (file_tree, agent_panel, minibuffer, overlays) use
**absolute screen coordinates** since they're not scoped to a window.

## Types

* `draw()` — a pending draw: `{row, col, text, style}`. This is the
  return type of all renderer modules after the display list refactor.
* `text_run()` — column + text + style (no row; row is the map key).
* `display_line()` — a list of text runs for one screen row.
* `render_layer()` — rows mapped to their display lines.

# `color`

```elixir
@type color() :: non_neg_integer()
```

RGB color as a 24-bit integer (e.g. `0xFF6C6B`).

# `display_line`

```elixir
@type display_line() :: [text_run()]
```

All text runs on one screen row.

# `draw`

```elixir
@type draw() ::
  {non_neg_integer(), non_neg_integer(), String.t(), Minga.Core.Face.t()}
```

A pending draw command: `{row, col, text, Face.t()}`.

This is the intermediate representation that renderer modules produce.

# `render_layer`

```elixir
@type render_layer() :: %{required(non_neg_integer()) =&gt; display_line()}
```

Screen rows mapped to their display lines.

# `style`

```elixir
@type style() :: Minga.Core.Face.t()
```

Style: a resolved Face struct.

# `text_run`

```elixir
@type text_run() ::
  {col :: non_neg_integer(), text :: String.t(), style :: Minga.Core.Face.t()}
```

A single styled text span at a specific column.

# `changed_rows`

```elixir
@spec changed_rows(render_layer(), render_layer()) :: MapSet.t(non_neg_integer())
```

Compares two render layers line-by-line and returns the set of changed rows.

This enables incremental rendering: only rows that differ between
frames need to be re-sent to the frontend.

# `draw`

```elixir
@spec draw(non_neg_integer(), non_neg_integer(), String.t(), Minga.Core.Face.t()) ::
  draw()
```

Creates a draw tuple with a Face style.

## Examples

    iex> DisplayList.draw(0, 5, "hello")
    {0, 5, "hello", %Face{name: "_"}}

    iex> DisplayList.draw(0, 5, "hello", Face.new(fg: 0xFF0000, bold: true))
    {0, 5, "hello", %Face{name: "_", fg: 0xFF0000, bold: true}}

# `draws_to_commands`

```elixir
@spec draws_to_commands([draw()]) :: [binary()]
```

Converts a list of draw tuples to protocol command binaries.

Uses `encode_draw_smart/4` which automatically selects the compact
`draw_text` opcode for simple styles (fg/bg/bold/italic/underline/reverse)
or the extended `draw_styled_text` opcode when the style includes
strikethrough, underline_style, underline_color, or blend.

# `draws_to_layer`

```elixir
@spec draws_to_layer([draw()]) :: render_layer()
```

Groups a list of draws by row into a render layer.

Each draw's row becomes the map key; the draw is converted to a text_run
(col, text, style) within that row's display_line.

# `grayscale_draws`

```elixir
@spec grayscale_draws([draw()]) :: [draw()]
```

Converts draw tuples to grayscale (luminance-weighted).

# `layer_to_draws`

```elixir
@spec layer_to_draws(render_layer()) :: [draw()]
```

Flattens a render layer back into a list of draw tuples.

# `offset_draws`

```elixir
@spec offset_draws([draw()], non_neg_integer(), non_neg_integer()) :: [draw()]
```

Offsets draw tuples by the given row and column amounts.

# `to_commands`

```elixir
@spec to_commands(
  MingaEditor.DisplayList.Frame.t(),
  keyword()
) :: [binary()]
```

Converts a frame into a list of protocol command binaries.

Produces the same output that the old renderer sent to the port:
clear, regions, content draws, cursor, batch_end.

Options:
- `batch_end: false` — omit the trailing `batch_end` command. Used by
  the GUI emit path which appends Metal-critical chrome commands before
  sending `batch_end` to ensure atomic frame delivery.

---

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