# `Ascii.Piece`
[🔗](https://github.com/houllette/ascii_art/blob/main/lib/ascii/piece.ex#L1)

The piece contract, upstream's in Elixir.

Upstream, a piece's default function takes the options, precomputes, and
returns a closure `frame(t, env)`. Here `init/1` is the outer function: it
receives every option (the defaults merged with what was asked for) and
returns a state. `frame/3` is the closure: given the state, the play time
`t` in seconds and the `Ascii.Env`, it returns the picture as iodata,
the colours, and the next state.

The picture is `rows` lines of exactly `cols` characters joined by `\n`.
Colours are `nil` unless the piece has a palette and `env.color?` is set;
then they are a binary of `cols * rows` palette indices, row by row.

State is threaded because upstream closures keep state between frames: a
simulation that steps by how far `t` moved, a cache of the last minute
drawn. A piece behaves exactly as its upstream closure does when called with
the same sequence of `t`, including `t` going backwards.

## Example

    defmodule MySpinner do
      @behaviour Ascii.Piece

      @meta %Ascii.Meta{
        name: "spinner",
        category: :ui,
        note: "a line turning while something loads",
        cols: 10,
        rows: 1,
        fps: 6
      }

      @impl true
      def meta, do: @meta

      @impl true
      def init(_options), do: nil

      @impl true
      def frame(state, t, _env) do
        {[Enum.at(~w(| / - \\), rem(floor(t * 3), 4)), " loading "], nil, state}
      end
    end

# `describe`
*optional* 

```elixir
@callback describe(options :: map()) :: String.t() | nil
```

What the piece shows, in words, for a screen reader: read off the
options, the data a component draws ("load: 72%", "boot log: 9 ok, 1
warning"). `nil` when the options hold nothing to tell (the piece plays
its own script), for `Ascii.describe/1` to say what the piece is.

# `frame`

```elixir
@callback frame(state :: term(), t :: float(), env :: Ascii.Env.t()) ::
  {iodata(), colors :: binary() | nil, state :: term()}
```

The picture at `t` seconds, its colours, and the next state.

# `handle_event`
*optional* 

```elixir
@callback handle_event(event :: Ascii.event(), options :: map()) ::
  {:ok, options :: map()} | :ignore
```

The options after an event, for a piece that can be used: a key or a
click (see `t:Ascii.event/0`) moving a cursor, opening a folder,
ticking a box. What it changes is in the options it returns, so an app
reads what was chosen from them. `:ignore` for an event it has no use
for.

# `init`

```elixir
@callback init(options :: map()) :: state :: term()
```

Precomputes from every option, merged with the defaults.

# `meta`

```elixir
@callback meta() :: Ascii.Meta.t()
```

The piece's `Ascii.Meta`.

# `parts`
*optional* 

```elixir
@callback parts(frame :: Ascii.Frame.t(), options :: map()) :: Ascii.Parts.t()
```

The parts of a frame in one ink, for a piece whose meta lists `parts`:
each row as runs of `{name, text}` making up the row (see
`Ascii.Parts`). Given the frame as drawn, and the options it was drawn
with; called only when parts are to be coloured.

# `size`
*optional* 

```elixir
@callback size(options :: map()) :: {cols :: pos_integer(), rows :: pos_integer()}
```

The frame's size for these options, `{cols, rows}`, for a piece whose size
follows its options (a width to fit a terminal, a row a series). Without
it, every frame is the meta's size.

# `update`
*optional* 

```elixir
@callback update(state :: term(), options :: map()) :: state :: term()
```

The state for new options while playing (see `Ascii.put_options/2`),
keeping what it can of `state`: a needle swinging to a new reading, a
scroll carrying on. Without it, `init/1` starts afresh.

---

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