Cauldron2D.Input (Cauldron2D v0.2.2)

Copy Markdown View Source

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

action()

@type action() :: atom()

gesture()

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

key_spec()

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

keymap()

@type keymap() :: %{required(action()) => [key_spec()]}

state()

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

t()

@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

actions(input, arg2)

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

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

binds?(input, spec)

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

Whether spec is bound to any action.

expire(input, now)

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

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

handle(input, arg2, now)

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

Apply one terminal event at millisecond time now.

merge(input, external, strength \\ %{})

@spec merge(t(), MapSet.t(action()), %{required(action()) => 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(keymap, opts \\ [])

@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?(input)

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

Whether releases are being reported.

spec_of(arg1)

@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(input)

@spec state(t()) :: state()

What the player is doing now.

steers?(input, actions)

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

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