pub enum Binding {
Show 16 variants
Key(KeyCode),
MouseButton(PointerButton),
MouseMotion,
MouseScroll,
PointerPosition {
axis: PointerAxis,
},
KeyAxis {
negative: KeyCode,
positive: KeyCode,
},
Chord {
modifier: Modifier,
key: KeyCode,
},
ButtonChord {
modifier: Modifier,
button: PointerButton,
},
ScrollChord {
held: KeyCode,
},
Virtual(String),
Wasd {
up: KeyCode,
down: KeyCode,
left: KeyCode,
right: KeyCode,
},
PadButton(PadButton),
PadDpad,
PadStick {
stick: Stick,
deadzone: f32,
},
PadChord {
modifier: PadButton,
button: PadButton,
},
PadTrigger {
trigger: Trigger,
threshold: f32,
},
}Expand description
A binding source: what raw input triggers this action.
Keyboard, mouse, gamepad and on-screen controls. A game
played with one finger needs nothing touch-specific — the platforms
deliver the primary contact as an ordinary pointer, so
Binding::MouseButton and Binding::PointerPosition are what a phone
plays it through. Binding::Virtual is what a game needs when one finger
is not enough.
PartialEq and not Eq: a pad binding’s dead zone is an f32.
Its Display and FromStr are a stable text form, KeyR or Pad:South,
for a player’s saved rebinds; binding_text has the spelling of each.
Variants§
Key(KeyCode)
A single key.
MouseButton(PointerButton)
A mouse button.
MouseMotion
Mouse motion (delta in pixels).
MouseScroll
Mouse scroll wheel.
PointerPosition
Where the pointer is, normalised to the surface it is over: −1.0 at one edge, +1.0 at the other, with +X right and +Y up.
Drives an ActionKind::Axis1, whose −1…1 range is already exactly a
normalised coordinate. Feeding an Axis2 instead would put a place in
the same value as Binding::Wasd’s direction, and a consumer handed
(0.5, 0.0) could not tell “half way to the right edge” from “moving
right at half speed”; a fourth ActionKind for one binding would be a
whole value shape for a case Axis1 already covers. Any other kind
ignores this binding, as Binding::MouseMotion does on a button.
Normalised, not pixels, because pixels are the surface’s business: a device-pixel coordinate makes every consumer redo the DPI arithmetic the windowing layer already did, and get it wrong on exactly the displays nobody develops on. What the consumer still owns is the step from the surface to its own world — a camera’s half width is not the map’s to know.
A pointer that leaves keeps its last position. Leaving is not a command: the platform reports it with no coordinate at all, so there is nothing to resolve, and an axis that recentred itself would walk a paddle to the middle of the field every time a finger lifted.
On an axis that also has a relative source — a Binding::KeyAxis,
a Binding::MouseScroll — this one wins whenever the pointer has a
position, because summing them would add a place to a rate and produce
neither. That is a resolution, not an arbitration: a consumer whose
keyboard means something genuinely different from its pointer (a paddle
driven left at a speed, versus a paddle put at a coordinate) declares two
actions and picks between them with
Axis1Action::pointer_moved.
Fields
axis: PointerAxisWhich coordinate this action reads.
KeyAxis
Two keys driving one 1-D axis: negative contributes −1.0, positive
+1.0, and both at once cancel.
A single Binding::Key on an Axis1 can only ever push the axis
positive, so a keyboard-driven throttle or zoom needs this.
Chord
A key that counts only while a Modifier is held — Shift+Tab.
The more specific binding takes the key. While the modifier is
held, every plain Binding::Key, Binding::KeyAxis and
Binding::Wasd on the same key, in the context that owns the key,
reads it as up — so ui_next on Tab and ui_prev on Shift+Tab never
both fire. Holding Tab and then pressing Shift therefore releases the
one and presses the other.
The modifier is read, not consumed: a chord owns its key and
nothing else, so a Shift+Tab in a pushed context leaves a sprint bound
to Shift in the context beneath it alone.
ButtonChord
A mouse button that counts only while a Modifier is held —
Alt+right click beside a plain right click. The mouse’s
Binding::Chord, on the same two rules, and read as
Binding::MouseButton is: down on an ActionKind::Button, inert
on the other kinds.
The more specific binding takes the button. While the modifier is
held, every plain Binding::MouseButton on the same button, in the
context that owns the button, reads it as up — so an aim on the right
button and a zoom on Alt+right never both fire. Holding the button and
then pressing the modifier releases the one and presses the other;
letting the modifier go first hands the button back.
The modifier is read, not consumed: a button chord owns its
button and nothing else, so a lean bound to Alt still sees every
press of Alt, chorded or not.
ScrollChord
The mouse wheel, read only while held is down — Z+wheel, Ctrl+wheel.
Read as Binding::MouseScroll is: the vertical delta on an
ActionKind::Axis1, inert on the other kinds. It reads the wheel as
one, too, so it takes the wheel for its context as a MouseScroll
would.
The more specific binding takes the wheel, as it takes the key for a
Binding::Chord: while the held key of any scroll chord in the
context that owns the wheel is down, every plain MouseScroll in that
context reads zero. With several of those keys down, the most recently
pressed one takes the wheel and the chords on the others read zero,
so letting go of it hands the wheel back to the one pressed before. An
OS auto-repeat of a key already down is not a press and takes nothing.
The held key is read, not consumed, as a chord’s modifier is: a
scroll chord owns the wheel and nothing else, so a Binding::Key on
Z still sees Z — a game that also taps Z cancels that tap with
ActionMap::cancel_patterns when the wheel turns. A held key
withheld from its owner (see context.rs) is up for this read too.
Only an enabled action’s scroll chord competes. A key whose action
is disabled (ActionMap::set_enabled) takes the wheel from nothing
and silences no plain MouseScroll, however recently it was pressed:
Ctrl held for one chord keeps the wheel when Alt goes down for a chord
that cannot act right now. Enabling it again restores its place by
when its key went down, not by when it was enabled.
Virtual(String)
An on-screen control, by the id the widget drawing it was given.
The touch row of a binding table, and the one binding whose device is a
piece of the game’s own UI: a crcbl-ui control hit-tests the raw
contacts, decides what its geometry makes of them, and reports the
result here through ActionMap::virtual_button or
ActionMap::virtual_stick. From this side it is a device like any
other — the same action can carry a key, a mouse button and one of
these, and nothing downstream can tell which of them spoke.
That split is deliberate and it is why there is no binding on a raw
contact. A finger reports a place on the glass; what it means is a
question only the thing drawn under it can answer, and an ActionMap
that hit-tested widgets would have to be told where every control was
drawn this frame.
A control is a button or a stick, and an action reads whichever
matches its own kind: an ActionKind::Button is down while the
named control is held, an ActionKind::Axis2 takes the named
control’s deflection, and an ActionKind::Axis1 takes nothing —
reducing a stick to one axis is a choice the control would have to make
and none of them makes it. A binding whose kind does not match is inert,
the way Binding::MouseMotion is on a button.
The vector arrives already inside the unit disc, and is clamped to it again here: a control with a longer throw than its own radius is a broken control, not a player asking to run faster.
Wasd
WASD composite: four keys mapped to a 2-D axis.
The composite is normalized, so a diagonal is a unit vector rather than
(1, 1) — otherwise diagonal movement is 41% faster than cardinal.
Fields
PadButton(PadButton)
A pad button, by position — see PadButton.
Read like a Binding::Key: down on an ActionKind::Button, +1.0 on
an ActionKind::Axis1, nothing on an ActionKind::Axis2 — the
d-pad’s Axis2 is Binding::PadDpad. Down while any connected pad
holds it.
PadDpad
The d-pad as one 2-D axis: Binding::Wasd over
PadButton::DpadUp, PadButton::DpadDown, PadButton::DpadLeft
and PadButton::DpadRight, +Y up.
Resolved exactly as Wasd is: into the same unit disc as the keys and
the sticks on the action, so a diagonal is a unit vector and opposite
directions cancel; down on an ActionKind::Button while any of the
four is held; inert on an ActionKind::Axis1. It owns the four
buttons for routing, as Wasd owns its four keys.
PadStick
A pad stick’s deflection, +Y up, through a scaled radial dead zone:
zero within deadzone of centre, rescaled so the zone’s edge reads 0
and full throw reads 1.
Drives an ActionKind::Axis2, into the same unit disc as a
Binding::Wasd or a Binding::Virtual stick on the action, so a
stick and a key pushed together ask for one direction. Inert on the
other kinds, as a Binding::Virtual stick is on an Axis1.
Fields
deadzone: f32The dead zone’s radius, in 0.0..1.0; anything else is refused
with ActionMapError::InvalidDeadzone.
PadChord
A pad button that counts only while another pad button is held — a
held shoulder giving the face buttons a second layer. The pad’s
Binding::Chord, on the same two rules:
The more specific binding takes the button. While modifier is
held, every plain Binding::PadButton on button (and a
Binding::PadDpad reading it), in the context that owns the button,
reads it as up — so a jump on South and a vault on LB+South never both
fire. Holding South and then pressing LB releases the one and presses
the other. A chord in a context beneath the owner reads nothing,
unless its action is marked with ActionMap::set_pad_chords_outrank:
then it takes the button from the contexts above while modifier is
held (context.rs).
The modifier is read, not consumed: a pad chord owns its button
and nothing else, so modifier can still carry a plain binding of its
own, which sees every press of it, chorded or not.
PadTrigger
A pad trigger past a threshold.
On an ActionKind::Button, down while the pull is past threshold;
on an ActionKind::Axis1, the pull rescaled so the threshold reads 0
and full pull reads 1. Inert on an ActionKind::Axis2.
Fields
threshold: f32The threshold, in 0.0..1.0; anything else is refused with
ActionMapError::InvalidDeadzone.
Implementations§
Source§impl Binding
impl Binding
Sourcepub const fn device(&self) -> Device
pub const fn device(&self) -> Device
The kind of device this binding listens to — which list of a binding
asset it is written in (binding_asset.rs), and what
ActionMap::hint matches against ActionMap::last_device.
A binding that reads two devices belongs to the one its value comes
from: a Binding::ScrollChord is the wheel with a key held, so it is
Device::Pointer’s. Binding::PointerPosition is the pointer’s
too, though a phone’s primary contact drives it — the platform hands
that contact over as a pointer.
Source§impl Binding
impl Binding
Sourcepub fn visit_keys(&self, visit: impl FnMut(KeyCode))
pub fn visit_keys(&self, visit: impl FnMut(KeyCode))
Calls visit with every key this binding owns — the keys a context
consumes by binding it. A Binding::Chord’s modifier is not one, and
neither is a Binding::ScrollChord’s held key.
Sourcepub const fn reads_gamepad(&self) -> bool
pub const fn reads_gamepad(&self) -> bool
Whether this binding reads a gamepad.