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.
Summary
Functions
The actions a key event presses or releases, whichever way keys are held.
Whether spec is bound to any action.
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, and how hard each is held.
An input reading keymap.
Whether releases are being reported.
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.
What the player is doing now.
Whether pressing a key for any of actions drops the aim.
Types
@type action() :: atom()
@type gesture() ::
:wheel_up | :wheel_down | :move_left | :move_right | :move_up | :move_down
@type t() :: %Cauldron2D.Input{ aim: {number(), number()} | nil, bindings: [{key_spec(), action()}], expiries: %{required(action()) => integer()}, external: MapSet.t(action()), gesture_ms: pos_integer(), gestured: %{required(action()) => integer()}, held: MapSet.t(action()), holds: %{required(action()) => pos_integer()}, releases?: boolean(), repeat_ms: pos_integer(), steering: MapSet.t(action()), strength: %{required(action()) => float()}, 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.
Whether spec is bound to any action.
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, 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.
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[]:gesture_ms— how long a wheel notch or a pointer move holds what it is bound to. Default100
Whether releases are being reported.
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.
What the player is doing now.
Whether pressing a key for any of actions drops the aim.