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

A terminal UI toolkit for Elixir, in the shape of Phoenix LiveView.

You write views — modules with state, a `render/2` that returns cells, and
callbacks for keys, ticks and events — and `Atui.Runtime` runs the IO loop
that feeds them and paints the result. Views never touch the terminal, so a
whole UI can be driven and asserted on without a tty in sight.

## Installation

    def deps do
      [{:atui, "~> 0.3.0"}]
    end

## A first view

    defmodule Hello do
      use Atui.View

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

      def tick_interval(_state), do: 1_000

      def handle_tick(tick, state), do: {:ok, %{state | count: tick}}

      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: " hello ")
        |> Atui.Screen.put_lines_centered(rect, ["up #{state.count}s", "q to quit"])
      end
    end

Start it from your application's supervision tree:

    def start(_type, _args) do
      Supervisor.start_link([{Atui, view: Hello}], strategy: :one_for_one)
    end

## Running it

A TUI needs the VM started so that Ctrl-C reaches the application rather than
opening the emulator BREAK menu. That is the `+Bc` flag:

    elixir --erl "+Bc" -S mix run --no-halt      # from source
    # or, in a release's rel/vm.args.eex:
    +Bc

`mix run --no-halt` works without it, but the BEAM keeps Ctrl-C for itself.
Under IEx the shell owns the terminal and input stays line buffered — see
`Atui.Terminal` for what raw mode actually requires.

## The pieces

  * `Atui.Runtime` — the main loop: terminal, view stack, ticks, repaint
  * `Atui.View` — the behaviour a screen implements
  * `Atui.Panes` — a tiling window manager a view can hold in its state
  * `Atui.Screen` / `Atui.Rect` / `Atui.Layout` — the drawing surface, its
    geometry, and how to divide it
  * `Atui.Style` — colour and attributes for a cell
  * `Atui.TextInput` — a single-line text field a view holds in its state
  * `Atui.Key` / `Atui.Input` — raw bytes decoded into key events
  * `Atui.Terminal` — raw mode, alternate screen, size

Views are **stacked** by the runtime: each push draws over what is below it,
which is what a popup wants. `Atui.Panes` is the other arrangement — several
views visible at once, tiled, each with its own ticker.

## Driving a view from outside

Views react to keys and ticks on their own. Anything else — a job finishing, a
file changing, a message from another process — arrives as an event:

    Atui.send_event(:refresh)                  # to the view with focus
    Atui.send_event_to(Atui.Runtime, MyView, {:rows, rows})

Sending the runtime process a plain message does the same thing, so a `Task`
reply or a subscription needs no adapter.

## Testing a UI

`:headless` renders into memory instead of a terminal, and `:size` fixes the
viewport, so a test can press keys and read the frame back as text:

    pid = start_supervised!({Atui.Runtime, view: Hello, headless: true, halt: :stop, size: {40, 10}})

    Atui.Runtime.send_key(pid, {:char, "x"})
    assert Atui.Runtime.screen(pid) |> Atui.Screen.to_text() =~ "hello"

# `child_spec`

The child spec to put the UI under a supervisor.

    Supervisor.start_link([{Atui, view: MyView}], strategy: :one_for_one)

`:temporary` — a UI that has quit has nothing to restart into, and restarting
it would take the terminal back from whatever the user returned to.

# `send_event`

Sends an event to the view with focus. See `Atui.Runtime.send_event/2`.

# `send_event_to`

Sends an event to a named view. See `Atui.Runtime.send_event_to/3`.

# `start`

Starts the UI loop with `view` as the root view.

Accepts every option `Atui.Runtime.start_link/1` takes; `:view` is required.

# `version`

The version of Atui in use — for an application to put in a title bar.

---

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