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

The behaviour every screen implements — the LiveView half of Atui.

A view is a pure function of its own state: `mount/1` builds that state,
`render/2` turns it into cells, and the callbacks return a new state plus an
optional instruction to the runtime. Views never touch the terminal, which is
what makes them testable — `render/2` returns an `Atui.Screen` you can assert
on without a tty in sight.

## Callback replies

  * `{:ok, state}` — carry on with new state
  * `{:pass, state}` — this key was not mine; let the focused view have it
  * `{:push, module, opts, state}` — stack another view on top (a popup)
  * `{:pop, state}` — close this view and return to the one below
  * `{:halt, state}` — quit the application

## Stacked, or tiled

The runtime *stacks* views: a `{:push, module, opts, state}` reply draws the
new view over what is below it, which is what a popup wants. For several views
visible at once — tiled, each with its own ticker — a view holds an
`Atui.Panes` in its state instead and becomes their container.

## Keys, and who gets them

The root view — the one the application started with — sees every key first,
so global keys like quit live in one place. It answers `{:pass, state}` for
the keys it does not claim, and those go on to the view with focus, which is
the topmost one. That is the default reply, so a view that says nothing about
a key never swallows it.

## Ticks

A view that wants a clock says how often in `c:tick_interval/1`; the runtime
gives it a timer of its own and calls `c:handle_tick/2` with that view's own
count. Views without an interval never tick, and a slow view does not drag on
a fast one.

## Example

    defmodule MyView do
      use Atui.View

      def mount(_opts), do: {:ok, %{count: 0}}

      def tick_interval(_state), do: 1_000

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

      def render(state, rect) do
        Atui.Screen.new(rect.width, rect.height)
        |> Atui.Screen.box(rect, title: " demo ")
        |> Atui.Screen.put_lines_centered(rect, ["count: #{state.count}"])
      end
    end

# `reply`

```elixir
@type reply() ::
  {:ok, state()}
  | {:pass, state()}
  | {:push, module(), keyword(), state()}
  | {:pop, state()}
  | {:halt, state()}
```

# `state`

```elixir
@type state() :: term()
```

# `handle_event`

```elixir
@callback handle_event(event :: term(), state()) :: reply()
```

Handles anything that is neither a key nor a tick.

Events come from `Atui.Runtime.send_event/2` and from plain messages sent to
the runtime process — a `Task` reply, a PubSub broadcast, a poller telling the
UI that something changed. This is the `handle_info/2` of a LiveView.

# `handle_key`

```elixir
@callback handle_key(key :: Atui.Key.t(), state()) :: reply()
```

Handles a key press. Only the topmost view receives keys.

# `handle_tick`

```elixir
@callback handle_tick(tick :: non_neg_integer(), state()) :: reply()
```

Handles a timer tick. `tick` counts this view's own ticks, from zero.

# `mount`

```elixir
@callback mount(opts :: keyword()) :: {:ok, state()}
```

Builds the view's initial state.

# `place`

```elixir
@callback place(state(), viewport :: Atui.Rect.t()) :: Atui.Rect.t()
```

Chooses where the view sits inside the viewport.

Defaults to the whole viewport; popups override it to centre themselves.

# `render`

```elixir
@callback render(state(), rect :: Atui.Rect.t()) :: Atui.Screen.t()
```

Draws the view.

`rect` is the view's own area with its origin at `{0, 0}` — a view always
draws in local coordinates, and the runtime places the result.

# `tick_interval`

```elixir
@callback tick_interval(state()) :: pos_integer() | nil
```

How often this view wants a tick, in milliseconds.

`nil` — the default — means no ticker at all. Read fresh before each timer is
set, so a view can change its own rate as its state changes.

# `unmount`
*optional* 

```elixir
@callback unmount(state()) :: :ok
```

Called before the view is discarded (pop, halt or shutdown).

# `assign`

Puts several values at once: `assign(state, now: t, count: n)`.

# `assign`

Puts a value in a map-shaped state.

Sugar for the LiveView habit of naming what changed rather than rebuilding the
map. `import Atui.View` to use it unqualified.

    def handle_tick(_tick, state), do: {:ok, assign(state, :now, DateTime.utc_now())}

---

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