Builtin Library
README.md
bindings
Binding primitives and evaluators. A binding names which physical input
satisfies a control on one device class — the value a .inputBinding
record's kbm / gamepad / touch field holds. A binding is a plain
descriptor table (e.g. { kind = "key", code = "KeyW" }); the evaluators
(evalHeld, evalPressed, evalReleased, evalAxis, evalVector)
dispatch on kind and read Zin.state / Zin.virtual to produce the live
signal. Zin.scheme runs them once per tick for every live binding, and
they are public so chords, axes and library authors can evaluate a binding
without reimplementing the dispatch.
-- boost.inputBinding/init.luau
local B = require("@builtin::modules.zinput.bindings")
return {
label = "Boost",
kind = "button",
kbm = { B.key("ShiftLeft") },
gamepad = { B.padButton("left_shoulder") },
touch = { B.touchButton({ zone = "right-lower" }) },
}
Two frames, and which binding speaks which
- Stick bindings (
vector,touchStick,padStick) report the direction the player means — forward is+y. Amovecontrol carrying a keyboard class and a stick class drives both devices the same way. - Delta bindings (
mouseDelta,touchDrag,scroll) report movement that happened — down is+y, because that is what the pointer did.
They also differ in magnitude: a stick is a position bounded at 1.0, a
delta is however far the device moved this frame. A control carrying both —
look, zoom — declares as = "delta" on its held class with the
unitsPerSecond a full deflection is worth, and scale on a delta class
that counts in its own units (a pinch in pixels beside a wheel in notches).
Zin.scheme applies both, so one sensitivity covers every device.
Exports
Keyboard and mouse constructors:
M.key(code: string) -> BindingM.mouse(button: string) -> Binding—"left"/"right"/"middle".M.modKey(spec: string) -> Binding—"Ctrl+S","Shift+Alt+P","Escape".M.axis(plus: Binding, minus: Binding, opts: StickOpts?) -> BindingM.vector(right, left, up, down: Binding) -> BindingM.wasd() -> BindingM.arrowKeys() -> BindingM.mouseDelta(axis: string?) -> Binding— this frame's mouse delta.M.scroll(axis: string?) -> Binding— scrollwheel delta.M.pointerLocked() -> Binding— boolean, true while locked.
Touch constructors (fed by Zin.virtual, which the on-screen controls write):
M.touchStick(opts: { zone, axis? }) -> Binding— vector.M.touchButton(opts: { zone?, label?, icon?, priority?, size?, group? }) -> Binding— boolean.M.touchDrag(opts: { zone, axis? }) -> Binding— per-frame delta vector.M.touchPinch(opts: StickOpts?) -> Binding— scalar pinch delta.
Gamepad constructors (addressed by canonical position, or by the device's own
0-based element index for a wheel, pedals, a flight stick or any element past
the canonical layout; slot names one pad by number or a device by a piece of
its name, omitting it means any connected pad; rest / full calibrate an
axis so rest reads 0 and full reads 1):
M.padButton(button: string | number, slot: (number | string)?) -> BindingM.padAxis(axis: string | number, opts: PadAxisOpts?) -> BindingM.padStick(stick: string, opts: StickOpts?) -> BindingM.padTrigger(trigger: string, slot: number?) -> Binding
Predicates:
M.isBoolean(b) -> booleanM.isAxis(b) -> booleanM.isVector(b) -> boolean
Evaluators:
M.evalHeld(b) -> booleanM.evalPressed(b) -> booleanM.evalReleased(b) -> booleanM.evalAxis(b) -> numberM.evalVector(b) -> Vec2
Pretty-printers:
M.format(b: any) -> string— single binding ("Space", "Ctrl+S", "WASD").M.formatAll(bindings: any?, sep: string?) -> string— joined summary; non-table arg returns"<no bindings>".
Types:
Binding = { [string]: any }— descriptor table withkindplus type-specific fields.Vec2 = { x: number, y: number }StickOpts = { slot: number?, as: string?, unitsPerSecond: number?, scale: number? }
Notes
- Bindings are pure data tables — they serialize cleanly, compare with
Json.encode, and have no metatables. modKeynormalises modifier order informat()so a key written asShift+Ctrl+KeySdisplays the same asCtrl+Shift+KeyS. The internalmods = { ctrl, shift, alt }flags are the canonical representation.- Mouse-delta, scroll, drag and pinch values are raw physical deltas, not
clamped to
[-1, 1].as = "delta"+unitsPerSecondon the stick class beside them is what puts every class in one unit. as = "delta"needsunitsPerSecondand has no default: the number that makes a pad feel right is the point of declaring the conversion, and a default would be right for one control and quietly wrong for the next.- Reading a member this module does not have raises immediately, naming the
nearest members and the full list — a wrong guess at a constructor
surfaces at the call rather than as
attempt to call a nil valuelater. format()always returns a string, never throws — returns"<invalid>"for malformed input.