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.
Summary
Functions
The actions a key event presses or releases, whichever way keys are held.
Drop every tapped hold whose window has passed by millisecond time now.
Apply one terminal event at millisecond time now.
Replace the actions held by an outside source.
An input reading keymap.
Whether releases are being reported.
What the player is doing now.
Whether pressing a key for any of actions drops the aim.
Types
@type action() :: atom()
@type t() :: %Cauldron2D.Input{ aim: {number(), number()} | nil, bindings: [{key_spec(), action()}], expiries: %{required(action()) => integer()}, external: MapSet.t(action()), held: MapSet.t(action()), holds: %{required(action()) => pos_integer()}, releases?: boolean(), repeat_ms: pos_integer(), steering: MapSet.t(action()), tap_ms: pos_integer(), toggled: MapSet.t(action()), toggles: MapSet.t(action()) }
Functions
The actions a key event presses or releases, whichever way keys are held.
Drop every tapped hold whose window has passed by millisecond time now.
Apply one terminal event at millisecond time now.
Replace the actions held by an outside source.
An input reading keymap.
Options
:releases?— whether the terminal reports key releases. Defaultfalse: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. Default120: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. Default150:steering— actions whose key press drops the pointer's aim. Default[]
Whether releases are being reported.
What the player is doing now.
Whether pressing a key for any of actions drops the aim.