# `Atui.Panes`
[🔗](https://github.com/iboard/atui/blob/v0.3.0/lib/atui/panes.ex#L1)

A tiling window manager a view can hold in its own state.

`Atui.Runtime` stacks views: each push draws *over* what is below it, which is
what a popup wants. Panes are the other arrangement — several views visible at
once, side by side, sharing the space. A view that holds a `%Atui.Panes{}`
becomes a container: it opens and closes windows, moves focus between them,
rearranges them, and gives each one its own ticker, without any of them
knowing they are not alone on the screen.

Panes hold ordinary `Atui.View` modules. A pane is either such a module or
`:self` — the placeholder for content the host view draws itself, so the
container is a window in its own layout rather than a frame around one.

## Holding one

The host keeps the struct in its state and delegates the callbacks to it.
`into/3` rewrites a reply about the panes into a reply about the state that
holds them, which is what makes the delegation one line long:

    defmodule Dashboard do
      use Atui.View

      alias Atui.Panes

      def mount(_opts), do: {:ok, %{panes: Panes.new(host: __MODULE__)}}

      def handle_key({:char, "q"}, state), do: {:halt, state}

      def handle_key(key, state) do
        case state.panes |> Panes.handle_key(key) |> Panes.into(state) do
          {:pass, state} -> shortcut(state, key)
          reply -> reply
        end
      end

      def handle_event(event, state) do
        state.panes |> Panes.handle_event(event) |> Panes.into(state)
      end

      def render(state, rect) do
        Panes.render(state.panes, rect, fn tile -> banner(state, tile) end)
      end

      def unmount(state), do: Panes.unmount_all(state.panes)
    end

`host:` is the host's own module. Panes address their timers back to it
through `Atui.Runtime.schedule_event_to/3`, so `handle_event/2` has to be
delegated for a window's ticker to run at all.

## Keys, and who gets them

`handle_key/2` offers the key to the focused pane first and answers with
whatever that pane replied — including `{:pass, panes}` for a key the pane did
not claim, which is the host's cue to treat it as a shortcut of its own. A
window is therefore free to bind a key the container also uses, and only what
no window wants reaches the container.

Movement is not automatic: bind `focus_next/1`, `move_focus/2` and `swap/2` to
whichever keys suit the application, and bind them *before* delegating so a
window cannot swallow them.

## Ticks

Runtime timers only cover views in its stack, so a pane's ticker is a
scheduled event addressed back to the host. Each pane is asked for its
`c:Atui.View.tick_interval/1` when it opens and again after every tick, and
counts its own ticks from one — the same contract a view in the stack gets.

# `pane`

```elixir
@type pane() :: :self | module()
```

# `t`

```elixir
@type t() :: %Atui.Panes{
  focus: non_neg_integer(),
  focus_style: Atui.Style.t() | nil,
  host: module(),
  panes: [pane()],
  states: %{required(module()) =&gt; Atui.View.state()},
  ticks: %{required(module()) =&gt; non_neg_integer()}
}
```

# `close`

Unmounts `module`'s pane and takes it out of the layout.

Focus stays where it is unless the closed pane was the last one, in which case
it moves back to what is now the end. Closing a module with no pane is a no-op.

# `count`

How many panes there are.

# `focus`

Focuses the pane at `index`. `nil` or an index out of range changes nothing.

# `focus_index`

The index of the focused pane.

# `focus_next`

Moves focus to the next pane in layout order, wrapping at the end.

# `focus_prev`

Moves focus to the previous pane in layout order, wrapping at the start.

# `focused`

The focused pane — `:self`, a module, or `nil` when there are none.

# `handle_event`

Handles an event on behalf of the panes.

A pane's ticker comes through here, which is why the host has to delegate this
callback. Anything else is broadcast to every pane, the way a container passes
on news that is nobody's in particular — a job finishing, a subscription
firing. Broadcast replies only update pane state; a pane cannot halt the
application or close itself from an event it shares with its siblings.

# `handle_key`

Offers `key` to the focused pane.

Returns the pane's own reply, with the panes as its state:

  * `{:ok, panes}` / `{:pass, panes}` — the pane's new state is kept, and a
    pass is still a pass, so the host can treat the key as a shortcut
  * `{:push, module, opts, panes}` — passed along; a pushed view lands on the
    runtime's stack, over the whole layout
  * `{:pop, _}` from a pane closes it, and comes back as `{:ok, panes}`
  * `{:halt, panes}` — passed along, quitting the application

With focus on `:self` there is no pane to ask, so the reply is `{:pass, panes}`.

# `into`

Rewrites a reply about the panes into a reply about the state holding them.

`key` is where the struct lives in that state, `:panes` by default.

    state.panes |> Panes.handle_event(event) |> Panes.into(state)

# `list`

The panes in layout order, as `:self` or a module.

# `move_focus`

Moves focus to the neighbouring pane in `direction`.

`direction` is `:left`, `:right`, `:up` or `:down`. Focus stays put at the
edge of the grid — see `Atui.Layout.neighbour/3` for how the grid is shaped.

# `new`

Builds a set of panes.

Options:

  * `:host` — the module holding these panes (required). Pane timers are
    delivered to it as events, so it has to delegate `handle_event/2`.
  * `:panes` — what to start with, in order: `:self`, a view module, or
    `{module, mount_opts}`. Defaults to `[:self]`.
  * `:focus_style` — the `Atui.Style` the focused pane's border is recoloured
    with. `nil` draws no focus ring, for a container that marks focus itself.

# `open`

Mounts `module` as a new pane at the end of the layout, and focuses it.

`mount_opts` is passed to the view's `c:Atui.View.mount/1`. Opening a module
that already has a pane simply focuses it, so a shortcut cannot mount the same
window twice.

# `open?`

Whether `module` currently has a pane.

# `put_state`

Replaces the state of `module`'s pane — how a host drives one directly.

# `render`

Draws every pane into a screen the size of `rect`.

`self_fun` is called with the local rect of the `:self` pane and returns the
screen to put there — the host's own content, drawn as a window among the
others. It is not called when there is no such pane.

Each pane renders in local coordinates, unaware of where in the layout it
landed, and the focused pane's border is recoloured afterwards, so a container
can mark one of its children without knowing how that child framed itself.

# `state`

The state of `module`'s pane, or `nil` if it is not open.

# `swap`

Swaps the focused pane with its neighbour in `direction`.

This is what rearranges the layout: two panes trade places and focus travels
with the one that moved, so a pane can be walked across the grid one swap at a
time. Nothing happens at the edge.

# `ticks`

How many ticks `module`'s pane has had.

# `tiles`

How `rect` is divided between the panes, in layout order.

The same rects `render/3` draws into — for a host that needs to know where a
pane sits, to place a cursor or to read a mouse position.

# `toggle`

Closes `module`'s pane if it is open, and opens it if it is not.

# `unmount_all`

Unmounts every pane. Call it from the host's `c:Atui.View.unmount/1`.

---

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