# `Cauldron2D.Input`
[🔗](https://github.com/jaman/cauldron/blob/v0.1.3/cauldron_2d/lib/cauldron_2d/input.ex#L1)

Turns terminal events into the set of actions a player is holding.

A game declares its actions and a keymap from action to keys:

    keymap = %{left: [:left, :a], right: [:right, :d], jump: [:up, {:w, []}, :left_shift], act: [:" ", {:f, [:ctrl]}]}
    input = Cauldron2D.Input.new(keymap, releases?: true, toggles: [:jump])

Feed it every event with `handle/3` and call `expire/2` once a tick; `state/1` is what
the player is doing now: `%{held: MapSet.t(), aim: {column, row} | nil}`. A state
from an analog control may add `strength: %{action => 0.0..1.0}`, how hard each such
action is held; an action without an entry is held in full. Keys give none.

## Two ways to hold a key

With `releases?: true` the terminal reports key releases, and an action is held from
`{:key_down, ...}` to `{:key_up, ...}`. Legacy `{:key, ...}` events are ignored.

With `releases?: false` a `{:key, ...}` press holds the action for `:tap_ms`
milliseconds; the terminal's key repeat keeps it held while the key is down, and a tap
holds it for one window. An action listed in `:holds` is held for its own, longer,
window on a first press — long enough to cover the terminal's delay before it starts
repeating — and each repeat then extends it by `:repeat_ms`, so it stays held while
the key is down and lets go soon after. Actions listed in `:toggles` flip on each
press instead and stay until pressed again. A `{:key_release_support, true}` event
switches to the first way and drops every hold.

Keys are matched by their unshifted name: `:A` and `{:a, [:shift]}` both press what
`:a` is bound to. A key given with modifiers in the keymap, `{:f, [:ctrl]}`, matches
only with them; a bare key matches with any.

## Keys against the pointer

Actions listed in `:steering` are the ones the pointer also drives. Pressing a key
bound to one drops the aim, so the keys take over, until the pointer moves again.

## Mouse buttons

A key spec `{:mouse, :left | :middle | :right}` binds a mouse button. A button is
held from its `:mouse_down` to its `:mouse_up`, through any drag, whichever way keys
are being held; the pointer position of every mouse event becomes the aim.

`merge/2` adds actions held by another source, such as a controller.

# `action`

```elixir
@type action() :: atom()
```

# `key_spec`

```elixir
@type key_spec() :: atom() | {atom(), [atom()]} | {:mouse, :left | :middle | :right}
```

# `keymap`

```elixir
@type keymap() :: %{required(action()) =&gt; [key_spec()]}
```

# `state`

```elixir
@type state() :: %{
  :held =&gt; MapSet.t(action()),
  :aim =&gt; {number(), number()} | nil,
  optional(:strength) =&gt; %{required(action()) =&gt; float()}
}
```

# `t`

```elixir
@type t() :: %Cauldron2D.Input{
  aim: {number(), number()} | nil,
  bindings: [{key_spec(), action()}],
  expiries: %{required(action()) =&gt; integer()},
  external: MapSet.t(action()),
  held: MapSet.t(action()),
  holds: %{required(action()) =&gt; pos_integer()},
  releases?: boolean(),
  repeat_ms: pos_integer(),
  steering: MapSet.t(action()),
  tap_ms: pos_integer(),
  toggled: MapSet.t(action()),
  toggles: MapSet.t(action())
}
```

# `actions`

```elixir
@spec actions(t(), term()) :: [action()]
```

The actions a key event presses or releases, whichever way keys are held.

# `expire`

```elixir
@spec expire(t(), integer()) :: t()
```

Drop every tapped hold whose window has passed by millisecond time `now`.

# `handle`

```elixir
@spec handle(t(), term(), integer()) :: t()
```

Apply one terminal event at millisecond time `now`.

# `merge`

```elixir
@spec merge(t(), MapSet.t(action())) :: t()
```

Replace the actions held by an outside source.

# `new`

```elixir
@spec new(keymap(), keyword()) :: t()
```

An input reading `keymap`.

## Options

  * `:releases?` — whether the terminal reports key releases. Default `false`
  * `:toggles` — actions that flip on each press when releases are not reported.
    Default `[]`
  * `:tap_ms` — how long a press holds an action when releases are not reported.
    Default `120`
  * `:holds` — `[action: milliseconds]`, actions a first press holds for that long
    instead, when releases are not reported. Default `[]`
  * `:repeat_ms` — how long a repeat of a held action extends it, when releases are
    not reported. Default `150`
  * `:steering` — actions whose key press drops the pointer's aim. Default `[]`

# `releases?`

```elixir
@spec releases?(t()) :: boolean()
```

Whether releases are being reported.

# `state`

```elixir
@spec state(t()) :: state()
```

What the player is doing now.

# `steers?`

```elixir
@spec steers?(t(), [action()]) :: boolean()
```

Whether pressing a key for any of `actions` drops the aim.

---

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