# `Cauldron2D.Input`
[🔗](https://github.com/jaman/cauldron/blob/v0.2.2/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.

The wheel and the pointer's movement are bound like keys that are tapped:
`{:mouse, :wheel_up | :wheel_down}` is pressed by each notch of the wheel, and
`{:mouse, :move_left | :move_right | :move_up | :move_down}` by each move or drag that
takes the pointer that way from where it last was. Each press holds its actions for
`:gesture_ms`, and another press before that has passed holds them on, so a wheel
turned or a pointer moved steadily holds them until it stops. These holds are kept
apart from the keys', so letting go of a key never drops what the mouse holds.

`merge/3` adds actions held by another source, such as a `Cauldron2D.Input.Pad`,
with how hard each is held.

# `action`

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

# `gesture`

```elixir
@type gesture() ::
  :wheel_up | :wheel_down | :move_left | :move_right | :move_up | :move_down
```

# `key_spec`

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

# `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()),
  gesture_ms: pos_integer(),
  gestured: %{required(action()) =&gt; integer()},
  held: MapSet.t(action()),
  holds: %{required(action()) =&gt; pos_integer()},
  releases?: boolean(),
  repeat_ms: pos_integer(),
  steering: MapSet.t(action()),
  strength: %{required(action()) =&gt; float()},
  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.

# `binds?`

```elixir
@spec binds?(t(), key_spec()) :: boolean()
```

Whether `spec` is bound to any action.

# `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()), %{required(action()) =&gt; float()}) :: t()
```

Replace the actions held by an outside source, and how hard each is held.

`strength` maps an action in `external` to `0.0..1.0`; an action without an entry is
held in full.

# `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 `[]`
  * `:gesture_ms` — how long a wheel notch or a pointer move holds what it is bound
    to. Default `100`

# `releases?`

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

Whether releases are being reported.

# `spec_of`

```elixir
@spec spec_of(term()) :: key_spec() | nil
```

The key spec a terminal event presses, for binding it: a key (`:a`, or `{:f, [:ctrl]}`
with modifiers other than shift), a mouse button `{:mouse, :left | :middle | :right}`
or a wheel notch `{:mouse, :wheel_up | :wheel_down}`; `nil` for an event that presses
nothing, such as a release or a pointer move.

# `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*
