Input
- The windowed backend pumps OS events into one snapshot per frame. Scripts
read it through the
inputmodule. - Headless runs read neutral state.
- A replay records the snapshot per tick.
Actions
Declared in project.toml:
[input.actions]
jump = ["Space", "gamepad:south"]
move_x = ["keys:KeyA,KeyD", "axis:left_x"]
fire = ["mouse:left"]
if input::action_just_pressed("jump") { /* ... */ }
let x = input::action_value("move_x"); // -1 to 1
- An action takes any number of keys, buttons, sticks or key pairs.
action_valueruns -1 to 1: a key answers 0 or 1, a stick orkeys:KeyA,KeyDthe whole range.action_downis held, andaction_just_pressedandaction_just_releasedare the edges.- A binding names a key by its W3C code (
Space,KeyA,ArrowLeft),mouse:lefttomouse:forward,gamepad:south, oraxis:left_x, with+or-for one direction. input::bind(name, bindings)rebinds an action and saves it to the user data directory.reset_bindingsrestores the project's bindings.- Actions derive from the raw snapshot every frame. A recording holds the keys; a replay derives the actions.
Keyboard and mouse
if input::key_just_pressed(input::KEY_SPACE) { /* ... */ }
if input::mouse_down(input::MOUSE_BUTTON_LEFT) { /* ... */ }
let (mx, my) = input::mouse_position();
key_down, key_just_pressed and key_just_released cover keys by named
constant, and a key's value is its W3C KeyboardEvent.code. KEY_SHIFT,
KEY_CONTROL, KEY_ALT and KEY_META answer for either side. The mouse adds
position, per-frame delta, five MOUSE_BUTTON_* buttons and scroll delta.
Gamepads
for pad in input::gamepads() {
if input::gamepad_just_pressed(pad, input::GAMEPAD_BUTTON_SOUTH) { /* ... */ }
let x = input::gamepad_axis(pad, input::GAMEPAD_AXIS_LEFT_X);
}
input::gamepads()lists the connected pads. Every query takes a pad id and a named constant.- Buttons are
GAMEPAD_BUTTON_*, named by position as SDL3 names them. - Axes are
GAMEPAD_AXIS_*: a stick reads -1 to 1 with up and right positive, and a trigger reads 0 to 1. input::gamepad_name(pad)names the hardware.
Rumble, motion and the touchpad
let turn = input::gamepad_gyro(pad); // radians per second
let tilt = input::gamepad_acceleration(pad); // g, with gravity in it
for touch in input::gamepad_touches(pad) {
// touch.x and touch.y run 0 to 1 across the surface
}
| Call | What it does |
|---|---|
gamepad_rumble(pad, #{ strong, weak, duration }) | Runs the two motors at 0 to 1 for that many seconds; answers whether it started |
gamepad_stop_rumble(pad) | Stops it |
gamepad_can_rumble(pad) | Whether the pad has motors |
gamepad_gyro, gamepad_acceleration, gamepad_touches | A PlayStation pad's motion and touchpad, decoded from its HID reports over USB or Bluetooth |
A pad with no gyroscope or touchpad reads zero and an empty list, as an absent pad does. All of it is in the recorded snapshot and replays.
Touch
input::touches()returns the fingers on the screen as#{ id, x, y }, oldest first, withtouches_just_started()andtouches_just_ended()for the edges.- A finger is also a left click, so every widget kind works on a phone.
emulate_touch_from_mousemakes a held mouse button a finger.
[input]
emulate_mouse_from_touch = true # the default
emulate_touch_from_mouse = false # the default
Touch controls
A touch_button presses an action while a finger is on it. A touch_stick
pushes one action per axis. The game reads the action.
[nodes.touch_stick]
action_x = "move_x"
action_y = "move_y" # 1 pushed away from the player, as a gamepad reads
anchor = "bottom_left"
offset = [130.0, -130.0] # design pixels to the centre, y down
radius = 90.0
[nodes.touch_button]
action = "jump"
anchor = "bottom_right"
shape = "circle"
- An action bound to Space gains the button as a second source; the furthest from rest wins. An action no manifest declared still answers.
anchorandoffsetplace a control in design pixels, inside the safe area. In the editor they sit in the viewport while the game plays.visibility = "touchscreen"is the default: shown on a phone, a touch browser, or a project withemulate_touch_from_mouse."always"shows them everywhere.- A thumb that slides off a button still holds it.
recenter = trueputs the stick under the thumb that found it, anddeadzoneholds a resting thumb at zero. - Both are components hit-tested inside the tick: they run headless, land in the digest and replay.
ui::wants_pointer()says whether a widget took this frame's finger.
Gestures
if let Some(scale) = input::pinch().get("scale") { zoom *= scale; }
let pan = input::pan(); // #{ x, y }, two fingers or more
if let Some(dx) = input::swipe().get("x") { /* a unit direction */ }
if input::long_press().get("x").is_some() { /* once per hold */ }
| Call | Answers |
|---|---|
pinch() | #{ scale, x, y } while two fingers move apart or together; scale is against last frame |
pan() | #{ x, y }, the average movement of every finger down |
swipe() | #{ x, y, speed } on the frame a finger that travelled far enough lifts |
long_press() | #{ x, y } once, when a still finger has held long enough |
Each derives from the recorded fingers and the fixed step, and replays. Each
answers empty when nothing is happening. swipe_pixels,
long_press_seconds and long_press_slop_pixels in [input] set the thresholds.
The on-screen keyboard
- A focused
fieldraises the system keyboard on iOS and Android. input::keyboard_height()is the covered height in pixels from the bottom, read from UIKit, the Android activity or the page, and recorded.avoid_keyboard = trueon a root widget measures the bottom of the screen from the top of the keyboard.
Dropped files
input::dropped_files() returns the paths of files dropped onto the window
this frame.
Feeding input from a script
feed_key, feed_mouse, feed_mouse_button, feed_touch(id, x, y, phase)
and feed_action(name, value) put input into the frame as if a device
reported it. A test, a showcase or an automation client drives a game with
them. A fed frame records and replays like a real one.
Seeing input
- The editor's input overlay draws this frame's input: a chip per key and mouse button held, the actions they drive, a cursor at the pointer, a ring from every press.
- It reads the game's snapshot. During a replay the cursor follows the recording.
- Enabled from the command palette (Show input overlay), or with
--state inputat start. - The actions come from the game's
project.toml. The editor, itself a Balaur project, declares the game's actions when it opens one.
The full surface
Every function and all named constants, keys, mouse buttons, pad buttons and axes, are in the script modules reference.