# `Drafter.Layout`
[🔗](https://github.com/jaman/drafter/blob/main/lib/drafter/layout.ex#L1)

Pure layout calculation for the component tree.

All functions are stateless. They take component descriptors and rects,
return geometry (rects or size lists), and have no side effects.

# `component`

```elixir
@type component() :: tuple()
```

A component descriptor tuple, as an app's `render/1` returns.

# `dimension`

```elixir
@type dimension() ::
  non_neg_integer() | {:percent, number()} | {:fr, number()} | :auto | nil
```

A size as written in a component's options.

A non-negative integer is a cell count, `{:percent, n}` a share of the container,
`{:fr, n}` a share of what is left after fixed siblings, and `:auto` defers to the
component's own preferred size.

# `hierarchy`

```elixir
@type hierarchy() :: map()
```

A widget hierarchy map, consulted for widgets that report their own size.

# `rect`

```elixir
@type rect() :: %{
  x: integer(),
  y: integer(),
  width: non_neg_integer(),
  height: non_neg_integer()
}
```

A rectangle in terminal cells.

`x`/`y` are the top-left corner and may be negative for a component scrolled out of
view. `width`/`height` may be `0` — `apply_margin/2` produces zero-sized rects when
the margin exceeds the space.

# `sides`

```elixir
@type sides() :: {integer(), integer(), integer(), integer()}
```

Edge insets as `{top, right, bottom, left}`.

# `apply_margin`

```elixir
@spec apply_margin(rect(), sides()) :: rect()
```

Shrink a rect by a component's margin, leaving the space outside its box.

Width and height floor at `0`, so a margin larger than the rect collapses it.

## Examples

    iex> Drafter.Layout.apply_margin(%{x: 0, y: 0, width: 10, height: 6}, {1, 2, 1, 2})
    %{x: 2, y: 1, width: 6, height: 4}

    iex> Drafter.Layout.apply_margin(%{x: 0, y: 0, width: 2, height: 2}, {5, 5, 5, 5})
    %{x: 5, y: 5, width: 0, height: 0}

# `apply_padding`

```elixir
@spec apply_padding(rect(), sides()) :: rect()
```

Shrink a rect by a component's padding, leaving the space inside its border.

Unlike `apply_margin/2`, width and height floor at `1`, so a padded component always
keeps a cell to draw in.

## Examples

    iex> Drafter.Layout.apply_padding(%{x: 0, y: 0, width: 10, height: 6}, {1, 2, 1, 2})
    %{x: 2, y: 1, width: 6, height: 4}

    iex> Drafter.Layout.apply_padding(%{x: 0, y: 0, width: 2, height: 2}, {5, 5, 5, 5})
    %{x: 5, y: 5, width: 1, height: 1}

# `calculate_grid_layout`

```elixir
@spec calculate_grid_layout([component()], rect(), keyword()) :: [rect()]
```

Place children on a two-dimensional grid.

`:columns` fixes the column count; without it, columns are inferred from
`:rows`, or fall back to a single row of all children. `:gap` separates cells
in both directions, or `{row_gap, col_gap}` separates them independently.
Children may span with `:col_span` and `:row_span`.

The grid always fills its rect: columns left over from uneven division are
handed out one cell at a time to the leftmost columns.

# `calculate_horizontal_layout`

```elixir
@spec calculate_horizontal_layout([component()], rect(), keyword()) :: [
  %{x: integer(), width: pos_integer()}
]
```

Lay children out across `rect`, returning a `%{x: x, width: width}` per child.

## Options

  * `:children_opts` - list of per-child option keywords, positionally matched to
    `children`. Default: `[]`. When no entry carries `:width` or `:flex`, the rect
    is divided evenly and `:gap` applies; otherwise those per-child options drive
    the widths and `:gap` is not used.
  * `:gap` - blank columns between children. Default: `0`. Honoured only on the
    even-division path described above.

# `calculate_vertical_layout`

```elixir
@spec calculate_vertical_layout(
  [component()],
  rect(),
  keyword(),
  hierarchy() | nil
) :: [%{y: integer(), height: pos_integer()}]
```

Stack children down `rect`, returning a `%{y: y, height: height}` per child.

Fixed-height children keep their preferred height; the rest share what is left in
proportion to their flex weight, with a floor of one cell each. Every result is
clipped to the bottom of `rect`, so a child that does not fit gets height `0`.

## Options

  * `:gap` - blank rows between children. Default: `0`. Gaps are subtracted from
    the space the flexing children share.

Each child's own `:height`, `:min_height`, `:max_height`, `:flex` and `:fr` options
are read from the child descriptor, not from `opts`.

# `clamp_size`

```elixir
@spec clamp_size(non_neg_integer(), keyword(), :height | :width) :: non_neg_integer()
```

Clamp a size to the `:min_height`/`:max_height` or `:min_width`/`:max_width` in `opts`.

A bound that is absent or not an integer is ignored. When both are present and
contradict each other, the maximum wins.

## Examples

    iex> Drafter.Layout.clamp_size(3, [min_height: 5], :height)
    5

    iex> Drafter.Layout.clamp_size(30, [max_width: 20], :width)
    20

    iex> Drafter.Layout.clamp_size(3, [], :height)
    3

# `component_hidden?`

```elixir
@spec component_hidden?(component()) :: boolean()
```

Whether a component paints.

Distinct from `component_visible?/1`: a hidden component keeps the space it was
allotted and simply does not draw, whereas an invisible one is removed from the
layout entirely and its siblings close the gap.

# `component_opts`

```elixir
@spec component_opts(component()) :: keyword()
```

The options keyword list of a component, whatever its arity.

# `component_visible?`

```elixir
@spec component_visible?(component()) :: boolean()
```

# `count_component_slots`

```elixir
@spec count_component_slots(component()) :: pos_integer()
```

How many widget slots a component descriptor occupies, counting nested children.

A layout contributes nothing of its own; a box, card, scrollable or collapsible
contributes one plus its children. Anything else is `1`.

## Examples

    iex> Drafter.Layout.count_component_slots({:label, [text: "hi"]})
    1

    iex> Drafter.Layout.count_component_slots({:layout, :vertical, [{:label, []}, {:label, []}], []})
    2

    iex> Drafter.Layout.count_component_slots({:box, [{:label, []}], []})
    2

# `dock_edge`

```elixir
@spec dock_edge(component()) :: :top | :bottom | :left | :right | nil
```

Which edge a component is pinned to, if any.

A docked component is taken out of the normal flow and given the full span of
its edge; the remaining space is what the undocked siblings share. Reads the
`:dock` option of any component, and treats `:footer` as docked to `:bottom`.
Returns `nil` when the component is not docked.

# `get_child_vertical_spec`

```elixir
@spec get_child_vertical_spec(component(), hierarchy() | nil) ::
  {pos_integer() | :auto, non_neg_integer(), boolean(), pos_integer() | nil}
```

A child's vertical sizing inputs, as `{preferred, weight, flexes?, max_height}`.

`weight` is the child's `:fr` count when it has one, otherwise its `:flex` value,
never less than `1`. `flexes?` says whether the child should absorb leftover space.
`max_height` is its `:max_height` option, or `nil`.

# `get_margin`

```elixir
@spec get_margin(keyword()) :: sides()
```

Margin around a component, as `{top, right, bottom, left}`.

Shares the shorthand forms of `get_padding/1`: a single value applies to all
sides, a pair is vertical then horizontal.

Returns `{0, 0, 0, 0}` when `:margin` is absent or unrecognised.

## Examples

    iex> Drafter.Layout.get_margin(margin: 2)
    {2, 2, 2, 2}

    iex> Drafter.Layout.get_margin(margin: {1, 4})
    {1, 4, 1, 4}

    iex> Drafter.Layout.get_margin([])
    {0, 0, 0, 0}

# `get_padding`

```elixir
@spec get_padding(keyword()) :: sides()
```

Padding inside a component, as `{top, right, bottom, left}`.

A single integer applies to all four sides, a `{vertical, horizontal}` pair to two
each, and a full four-tuple is taken as written. Returns `{0, 0, 0, 0}` when
`:padding` is absent or unrecognised.

## Examples

    iex> Drafter.Layout.get_padding(padding: 1)
    {1, 1, 1, 1}

    iex> Drafter.Layout.get_padding(padding: {0, 2, 3, 4})
    {0, 2, 3, 4}

    iex> Drafter.Layout.get_padding([])
    {0, 0, 0, 0}

# `get_preferred_height`

```elixir
@spec get_preferred_height(component(), hierarchy() | nil) :: pos_integer() | :auto
```

How tall a component would like to be, in cells.

Container components sum their children plus their own chrome; a registered widget
is asked through the widget registry. `hierarchy` supplies live widget state
where a widget's height depends on it, and defaults to `nil`. Anything unrecognised
is `1`.

# `partition_docked`

```elixir
@spec partition_docked([component()]) :: {[{component(), atom()}], [component()]}
```

Split components into those docked to an edge and those in normal flow.

# `rect`

```elixir
@spec rect(integer(), integer(), non_neg_integer(), non_neg_integer()) :: rect()
```

Build a rect map from its four components.

No clamping or validation is applied.

## Examples

    iex> Drafter.Layout.rect(1, 2, 30, 4)
    %{x: 1, y: 2, width: 30, height: 4}

# `resolve_dimension`

```elixir
@spec resolve_dimension(term(), non_neg_integer(), non_neg_integer() | :auto) ::
  non_neg_integer() | :auto
```

Resolve a dimension against the space available to it.

Accepts a plain cell count, `{:percent, n}` for a share of the container,
`{:fr, n}` for a share of what remains after fixed siblings, or `:auto` to
defer to the component's own preferred size.

Returns a cell count for integers and percentages. `nil`, `:auto`, `{:fr, n}`
and any unrecognised value return `fallback`.

## Examples

    iex> Drafter.Layout.resolve_dimension(10, 100, :auto)
    10

    iex> Drafter.Layout.resolve_dimension({:percent, 25}, 80, :auto)
    20

    iex> Drafter.Layout.resolve_dimension(nil, 80, 7)
    7

    iex> Drafter.Layout.resolve_dimension({:fr, 2}, 80, 7)
    7

---

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