Input
Pointer, keyboard, gamepad, and text-accumulation input in BLIT386.
BLIT386 provides DOM-backed input: pointer (mouse, touch, pen), keyboard (KeyboardEvent.code tracking and virtual face
buttons), gamepad (up to four players via navigator.getGamepads()), and text accumulation for UI entry
(BT.inputString).
All pointer coordinates are returned in logical display space (the displaySize from configure() or
defaultConfig()), independent of the canvas's CSS or backing-buffer size.
Pointer slot model
The engine tracks up to four simultaneous pointers:
| Slot | Source |
|---|---|
| 0 | Mouse (always reserved) |
| 1 | First touch / pen contact (in arrival order) |
| 2 | Second touch / pen contact |
| 3 | Third touch / pen contact |
The engine tracks four pointer slots (indices 0-3). Overflow contacts beyond slot 3 are dropped silently.
Mouse slot (slot 0)
- Becomes valid on the first
pointermoveinside the canvas. - Stays valid while the mouse remains over the canvas; cleared on
pointerleave. - Tracks all four buttons (A, B, C, D).
- Position is preserved after the mouse leaves; a new
pointermovere-syncs it. - A pointer event with an empty
pointerTypecounts as the mouse, so synthetic events from test scripts work without it. Synthetic touch or pen events must setpointerTypeto'touch'or'pen'.
Touch / pen slots (slots 1-3)
- Allocated on
pointerdownin the first free slot; freed onpointerup,pointercancel, orpointerleave. - Only
BTN_POINTER_Areports true while the contact is down; B, C, D always return false. - Position and delta are preserved on release so release-frame code can read the final position and flick velocity.
- The engine calls
setPointerCaptureso off-canvas drags continue delivering events.
Position and delta
BT.pointerPos Since 1.0.3
BT.pointerDelta Since 1.0.3
BT.isPointerActive Since 1.1.1
// Current position in display coordinates
const = .(); // slot 0 (mouse)
const = .(1); // slot 1 (first touch)
// Movement since the last frame
const = .(); // slot 0 (mouse)
const = .(1); // slot 1
// Validity check - false means no live pointer in this slot
if (.()) {
// Mouse is over the canvas
}
if (.(1)) {
// Touch slot 1 is active
}BT.pointerPos() returns Vector2i.zero() when the slot index is out of [0, 3] or the engine is not initialized.
BT.pointerDelta() similarly returns Vector2i.zero() for invalid slots.
Zero-allocation reads
BT.pointerPosTo Since 1.7.0
BT.pointerDeltaTo Since 1.7.0
BT.pointerPos() and BT.pointerDelta() allocate a new Vector2i on every call. A demo reading pointer position or
delta more than once per frame should reuse a scratch vector with the *To(out) counterparts instead:
.(); // slot 0 (mouse)
.(); // slot 0 (mouse)
// Same slot argument as pointerPos()/pointerDelta(), now trailing the out param.
.(, 1); // slot 1 (first touch)Both write (0, 0) into out under the same conditions pointerPos()/pointerDelta() return Vector2i.zero() for
(invalid slot, engine not initialized), and return out itself so the call can be chained.
Buttons
BT.isDown Since 1.1.1
BT.isPressed Since 1.1.1
BT.isReleased Since 1.1.1
Use BT.isDown(), BT.isPressed(), and BT.isReleased() with the BTN_POINTER_* constants. The second parameter is
the pointer slot (defaults to 0):
// Hold detection
.(.); // left mouse button held
.(.); // right mouse button held
.(.); // middle mouse button held
.(.); // back / forward button held
.(., 1); // touch slot 1 in contact
// Edge detection (true only on the transition frame)
.(.); // left button just pressed
.(.); // left button just released
.(., 2); // touch slot 2 just touched down
// Composite pointer mask (ANY-match semantics)
.(.); // any pointer button on slot 0
.(. | .); // left or right mouse button heldMouse button mapping
BT.BTN_POINTER_A Since 0.1.0
BT.BTN_POINTER_B Since 0.1.0
BT.BTN_POINTER_C Since 0.1.0
BT.BTN_POINTER_D Since 1.0.3
BT.BTN_POINTER_ANY Since 1.0.3
The mapping follows the RetroBlit canonical order, not the DOM
PointerEvent.button index:
| Button constant | Mouse button | DOM button index |
|---|---|---|
BTN_POINTER_A | Left | 0 |
BTN_POINTER_B | Right | 2 |
BTN_POINTER_C | Middle / wheel click | 1 |
BTN_POINTER_D | Back or forward | 3 or 4 |
Keyboard
Listeners attach to the canvas during BT.init(). For reliable delivery, ensure the canvas can receive focus (for
example canvas.tabIndex = 0 and canvas.focus()). The library follows KeyboardEvent.code (physical-key semantics),
not event.key layout text.
Raw key queries
BT.isKeyDown Since 1.1.1
BT.isKeyPressed Since 1.1.1
BT.isKeyReleased Since 1.1.1
Use these for direct key checks:
// Held state
if (.('KeyW')) {
// W key held
}
// Edge and optional tick-based repeat (repeat interval in fixed-update ticks)
if (.('ArrowUp', 10)) {
// first press or repeat every 10 ticks while held
}
// Release edge
if (.('Escape')) {
// Escape released this frame
}Read edges in update(), not render()
BT.isKeyPressed and BT.isKeyReleased clear once per fixed-update tick, and that tick always finishes before
render() runs for the same frame. Reading either from render() instead of update() intermittently drops presses
under rapid input - the tick already consumed and cleared the edge before render got a chance to see it. BT.isKeyDown
(held state) has no such restriction and is safe to call from render().
Any key and any button
BT.isAnyKeyDown Since 1.8.0
BT.isAnyKeyPressed Since 1.8.0
BT.isAnyKeyReleased Since 1.8.0
BT.isAnyButtonDown Since 1.8.0
BT.isAnyButtonPressed Since 1.8.0
BT.isAnyButtonReleased Since 1.8.0
For "press any key to start" screens and idle checks, ask about the whole keyboard (or one player's gamepad) instead of a named key:
if (.()) {
// any key went down this tick, even if another key was already held
}
if (.(10)) {
// same, then again every 10 ticks while keys stay held
}
if (.()) {
// the last held key came up: nothing is held now, something was held before
}
if (.(0)) {
// any button on player 0's gamepad went down
}isAnyKeyPressedfires on every new key, so holdingAand then tappingBfires it forB. With a repeat rate, the cadence is counted from the earliest key still held.isAnyKeyReleasedandisAnyButtonReleasedmean "everything is up now". Letting go of one key while another stays down does not fire them; useBT.isKeyReleased(code)for per-key release edges. A gamepad disconnecting while buttons were held counts as the release.isAnyButton*covers theBTN_UPtoBTN_SELECTbuttons of that player's gamepad, and nothing else: no sticks, no triggers (neither the analog pull norBTN_L2/BTN_R2), no keyboard, no pointers. UseisAnyKey*for the keyboard.playerdefaults to0.- Call the pressed and released checks from
update(), notrender(). The held checks (isAnyKeyDown,isAnyButtonDown) are safe anywhere. - The keyboard edges clear once per fixed-update tick, and are buffered so a fast tap is never dropped. The gamepad
edges follow
BT.isPressedandBT.isReleasedinstead: they roll over once per rendered frame, so on a display faster thantargetFPSa gamepad edge can be missed by a frame that runs no update, and a frame that runs several updates reports the same edge to each of them. See Frame-timing semantics.
Face buttons (BTN_UP through BTN_SELECT)
BT.BTN_UP Since 0.1.0
BT.BTN_DOWN Since 0.1.0
BT.BTN_LEFT Since 0.1.0
BT.BTN_RIGHT Since 0.1.0
BT.BTN_A Since 0.1.0
BT.BTN_B Since 0.1.0
BT.BTN_X Since 0.1.0
BT.BTN_Y Since 0.1.0
BT.BTN_L Since 0.1.0
BT.BTN_R Since 0.1.0
BT.BTN_START Since 0.1.0
BT.BTN_SELECT Since 0.1.0
BT.BTN_ABXY Since 1.0.3
BT.BTN_SHOULDER Since 1.0.3
BT.DEFAULT_KEYBOARD_PLAYER1 Since 1.0.3
BT.DEFAULT_KEYBOARD_PLAYER2 Since 1.0.3
BT.DEFAULT_KEYBOARD_SINGLE_PLAYER1 Since 1.8.0
BT.DEFAULT_KEYBOARD_SINGLE_PLAYER2 Since 1.8.0
Face button constants are bit flags. You can pass a single button or a combined mask; matching uses ANY semantics:
BT.isDown(BT.BTN_A | BT.BTN_B) is true when either A or B is down.
For player 0 and player 1, face-button reads merge keyboard maps and gamepad state (logical OR). For player 2 and player 3, face-button reads use gamepad only.
BT.isPressed supports optional tick-based repeat via repeatRate (0 or omitted = edge only), matching
BT.isKeyPressed semantics. For players 0 and 1, the keyboard-mapped half of isPressed / isReleased shares the same
update()-only edge timing as BT.isKeyPressed above.
// Player 0 (default: WASD-style + Space / KeyB for A, etc.)
.(., 0);
.(., 0, 6); // edge + repeat every 6 ticks while held
// Player 1 (default: arrow keys + alternate bindings)
.(., 1);
// Composite face-button masks (ANY-match semantics)
.(., 0); // A, B, X, or Y for player 0
.(., 1); // L or R shoulder for player 1Arrow keys belong to player 1 by default
Out of the box the engine assumes two keyboard players: player 0 steers with W, A, S, D and player 1 with the arrow
keys. A one-player game that reads BT.isDown(BT.BTN_LEFT) does not react to the left arrow. Set
keyboardLayout: 'single' in configure() to give player 0 both WASD and the arrows, or remap single buttons with
BT.inputMap(0, ...).
Triggers: analog or digital
BT.BTN_L2 Since 1.8.0
BT.BTN_R2 Since 1.8.0
BT.BTN_TRIGGER Since 1.8.0
Each trigger is available two ways. BT.getAxis(BT.AXIS_TRIGGER_L) / AXIS_TRIGGER_R return the pull from 0 to 1.
BT.BTN_L2 and BT.BTN_R2 (mask BT.BTN_TRIGGER) work with BT.isDown, BT.isPressed and BT.isReleased and turn
on at 50% pull or more, judged from the analog value alone (the browser's own pressed flag is ignored). The threshold is
fixed. BTN_L and BTN_R are still the shoulder buttons, not the triggers.
Trigger buttons are gamepad only. Keyboard players never trigger them and BT.inputMap ignores them, so give keyboard
players an explicit fallback if your game needs one:
// Either trigger on player 0 (gamepad only, ANY-match like other masks)
.(., 0);
// Same action with a keyboard fallback
const = .(., 0) || .(., 0);Keyboard layouts
KeyboardLayout Since 1.8.0
HardwareSettings.keyboardLayout picks which built-in map the two keyboard players start with:
| Layout | Player 0 D-pad | Player 1 D-pad |
|---|---|---|
'versus' (default) | W A S D | Arrow keys |
'single' | W A S D and arrow keys | I J K L |
Face and system buttons are the same in both layouts (player 0: Space / B for A, N for B, 5 for Start, Escape for
Select; player 1: ; / Numpad 1, ' / Numpad 2, Backspace / Numpad /). 'single' also turns on
isCapturingKeyboardScroll so the arrows stop scrolling the host page, unless configure() sets that
flag itself.
import { type HardwareSettings } from 'blit386';
function (): <HardwareSettings> {
return {
: 'single', // one player: WASD and the arrows both steer player 0
};
}The layout is applied before your init() runs, so BT.inputMap calls in init() still override single buttons, and
BT.inputMapReset() restores the active layout's defaults. Changing it during a hot reload triggers a full reload.
Built-in defaults are exposed as read-only tables (same values the engine starts with):
BT.DEFAULT_KEYBOARD_PLAYER1/BT.DEFAULT_KEYBOARD_PLAYER2-'versus'BT.DEFAULT_KEYBOARD_SINGLE_PLAYER1/BT.DEFAULT_KEYBOARD_SINGLE_PLAYER2-'single'
Remapping (BT.inputMap / BT.inputMapReset)
BT.inputMap Since 1.0.3
BT.inputMapReset Since 1.0.3
Override bindings at runtime. The first argument is the zero-based player index (0 or 1 only; other values are
ignored). The second is the face button constant. Remaining arguments are KeyboardEvent.code strings.
.(0, ., 'Space');
.(0, ., 'ArrowUp', 'KeyW'); // either key triggers UP for player 0
.(1, ., 'Enter', 'NumpadEnter');
// Restore the active keyboardLayout's defaults for both keyboard players (does not affect pointer or gamepad state)
.();Pass no key codes to clear keyboard bindings for that player and button until you map again:
.(0, .); // player 0 X has no keyboard keys until remappedGamepad API
BT.isGamepadConnected Since 1.1.1
BT.gamepadCount Since 1.0.4
BT.getAxis Since 1.0.3
BT.AXIS_LEFT_X Since 1.0.3
BT.AXIS_LEFT_Y Since 1.0.3
BT.AXIS_RIGHT_X Since 1.0.3
BT.AXIS_RIGHT_Y Since 1.0.3
BT.AXIS_TRIGGER_L Since 1.0.3
BT.AXIS_TRIGGER_R Since 1.0.3
BT.PLAYER_ONE Since 1.0.3
BT.PLAYER_TWO Since 1.0.3
BT.PLAYER_THREE Since 1.0.3
BT.PLAYER_FOUR Since 1.0.3
.(0); // true when player 0 has a connected gamepad
.; // number of connected gamepads (0..4)
.(., 0); // -1.0 .. 1.0 (dead-zone filtered)
.(., 0); // 0.0 .. 1.0Axis constants:
AXIS_LEFT_X,AXIS_LEFT_YAXIS_RIGHT_X,AXIS_RIGHT_YAXIS_TRIGGER_L,AXIS_TRIGGER_R
Player constants:
PLAYER_ONE(0)PLAYER_TWO(1)PLAYER_THREE(2)PLAYER_FOUR(3)
The default dead zone is large
Default stick dead zone is 0.75 (GamepadInput.DEFAULT_GAMEPAD_DEAD_ZONE internally; not a public export). Filtering
is per axis, not radial: each stick axis is zeroed when Math.abs(axis) <= deadZone, then the remaining range is
re-normalized with (abs - deadZone) / (1 - deadZone). At the default 0.75, a stick axis must exceed ~75% deflection
before movement registers - much larger than typical 0.1-0.2 dead zones. There is no public BT API to change this
value today.
Text input buffer
BT.inputString Since 1.0.4
BT.inputString returns characters accumulated from filtered beforeinput (and Tab / Escape where needed). The buffer
clears at the end of each fixed update step (read it during update()). See public JSDoc on BT.inputString for
details.
Scroll delta
BT.pointerScrollDelta Since 1.0.4
// Accumulated vertical scroll for the current frame, in pixels
const = .;
if ( > 0) {
// Scrolled down
}
if ( < 0) {
// Scrolled up
}The value aggregates all WheelEvent.deltaY values received since the last frame, normalizing line and page delta modes
to pixels. It resets to zero each frame.
Wheel capture is opt-in. Set isCapturingPointerScroll: true in configure() so the engine calls preventDefault() on
canvas wheel events and fills BT.pointerScrollDelta. When the flag is omitted (the default), the host page scrolls
normally while the pointer is over the canvas and BT.pointerScrollDelta stays 0.
The same flag also gates canvas.style.touch-action. When enabled, touch-action is none so the canvas does not
compete with the wheel capture above. When the flag is omitted (the default) and no other capture source is active,
touch-action is pan-y, so touch devices can still tap-hold-scroll the host page past the canvas.
The overlay palette grid still captures wheel while the pointer is over its band, even without the configure flag, so
scrolling palette rows does not move the page. The same palette-band capture also forces touch-action to none for as
long as a pointer is over the band, independent of the configure flag.
Upgrading from 1.3.0 or earlier
Pointer wheel capture is opt-in as of 1.4.0. If your game relied on BT.pointerScrollDelta without setting
isCapturingPointerScroll: true in configure(), add the flag - otherwise the delta stays zero and the host page
scrolls normally.
import { type HardwareSettings } from 'blit386';
function (): <HardwareSettings> {
return {
: true,
};
}Keyboard scroll capture is also opt-in. Set isCapturingKeyboardScroll: true in configure() so the engine calls
preventDefault() on canvas keydown for keys that scroll the host page by default (arrow keys, Space,
PageUp/PageDown, Home, End). When the flag is omitted (the default), those keys still scroll the page while the canvas
is focused - except under keyboardLayout: 'single', which turns capture on unless you set the flag to false.
import { type HardwareSettings } from 'blit386';
function (): <HardwareSettings> {
return {
: true,
};
}Cursor control
BT.hideCursor Since 1.0.3
BT.showCursor Since 1.0.3
// In init(): hide the OS cursor and draw your own crosshair / sprite
.();
// Restore at any time
.();hideCursor() sets canvas.style.cursor = 'none'. showCursor() restores whatever the cursor was at attach() time.
Both are no-ops before the engine is initialized. The cursor is restored automatically when the engine stops.
Frame-timing semantics
Pointer and gamepad previous-state rollover happens at the end of each animation frame, after demo.update() and
demo.render() have run. Keyboard press/release edges and BT.inputString align with the fixed update rate
(targetFPS), not the display refresh rate:
- On a 120 Hz monitor with
targetFPS: 60,render()may run twice perupdate(). Keyboard edges are buffered on DOM events and consumed on the next fixed update so fast taps are not dropped between render-only frames. pointerDelta()reflects movement between the lastupdate()and the current one.- Gamepad current state is polled from the Gamepad API once per render frame, immediately after that frame's previous-state rollover. Button/axis queries read that cached snapshot and never trigger a poll themselves.
- Read
BT.inputStringduringupdate(); the buffer clears at the end of each fixed update step.
Pointer transitions:
- Any pointer event that fires between two animation frames is visible as a transition on the next
update()call. - A pointer button press that lands in a frame that runs no update (a display faster than
targetFPS) is carried to the next frame that does, soBT.isPressednever misses a click. Onlyupdate()sees the carried press; the engine overlay and other render-side readers still see each press once.
Page-interaction guards
attach() installs guards on the canvas to prevent browser defaults from interfering:
wheelwith{ passive: false }- whenisCapturingPointerScrollistrue(or the overlay forces capture over the palette band),preventDefault()stops page scroll and the delta feedsBT.pointerScrollDelta. When capture is off, the page scrolls normally.keydown- whenisCapturingKeyboardScrollistrue,preventDefault()stops page scroll for arrow keys, Space, PageUp/PageDown, Home, and End while the canvas is focused. When capture is off, those keys keep their browser defaults.canvas.style.touchAction- whenisCapturingPointerScrollistrue(or the overlay forces capture over the palette band), touch-action is'none', preventing iOS Safari pinch-zoom, double-tap-zoom, and touch scroll from competing with capture. When capture is off, touch-action is'pan-y', so the host page still scrolls vertically past the canvas on touch devices.contextmenuwithpreventDefault()- prevents the OS context menu on right-click soBTN_POINTER_Bworks.
detach() reverses the pointer and keyboard guards and removes all event listeners. This happens automatically when the
engine stops or when demo.init() throws.
Coordinate conversion
Client coordinates from DOM events are converted to display space using the canvas bounding rect:
display_x = floor((clientX - rect.left) / rect.width * displaySize.x)
display_y = floor((clientY - rect.top) / rect.height * displaySize.y)Coordinates are clamped to [0, displaySize - 1] on each axis. The conversion is skipped when the canvas has zero size
(no layout yet) to avoid NaN coordinates.
Converting your own DOM events
BT.nativeScreenToDisplayPos Since 1.8.0
BT.nativeScreenToDisplayPos(clientX, clientY) runs the same conversion for coordinates the engine does not track
itself, such as a file dropped onto the canvas. It returns a new Vector2i, or null when there is no valid position
to report:
- the engine is not initialized;
- the canvas has zero width or height (no layout yet);
- a coordinate is not finite.
A point outside the canvas is not an error: it clamps to the nearest edge pixel, exactly as pointer input does. The canvas rect is read on every call, so the result stays correct after a resize or page scroll. It does not depend on pointer state, so it works before the first pointer event and for events the pointer subsystem never sees.
?.('dragover', () => .());
?.('drop', () => {
.();
const = .(., .);
if ( !== null) {
.(`dropped at display (${.}, ${.})`);
}
});Implementation notes
API history
| Symbol | Since | Last changed | Status |
|---|---|---|---|
BT.AXIS_LEFT_X | 1.0.3 | – | stable |
BT.AXIS_LEFT_Y | 1.0.3 | – | stable |
BT.AXIS_RIGHT_X | 1.0.3 | – | stable |
BT.AXIS_RIGHT_Y | 1.0.3 | – | stable |
BT.AXIS_TRIGGER_L | 1.0.3 | – | stable |
BT.AXIS_TRIGGER_R | 1.0.3 | – | stable |
BT.BTN_A | 0.1.0 | – | stable |
BT.BTN_ABXY | 1.0.3 | – | stable |
BT.BTN_B | 0.1.0 | – | stable |
BT.BTN_DOWN | 0.1.0 | – | stable |
BT.BTN_L | 0.1.0 | – | stable |
BT.BTN_L2 | 1.8.0 | – | stable |
BT.BTN_LEFT | 0.1.0 | – | stable |
BT.BTN_POINTER_A | 0.1.0 | – | stable |
BT.BTN_POINTER_ANY | 1.0.3 | – | stable |
BT.BTN_POINTER_B | 0.1.0 | – | stable |
BT.BTN_POINTER_C | 0.1.0 | – | stable |
BT.BTN_POINTER_D | 1.0.3 | – | stable |
BT.BTN_R | 0.1.0 | – | stable |
BT.BTN_R2 | 1.8.0 | – | stable |
BT.BTN_RIGHT | 0.1.0 | – | stable |
BT.BTN_SELECT | 0.1.0 | – | stable |
BT.BTN_SHOULDER | 1.0.3 | – | stable |
BT.BTN_START | 0.1.0 | – | stable |
BT.BTN_TRIGGER | 1.8.0 | – | stable |
BT.BTN_UP | 0.1.0 | – | stable |
BT.BTN_X | 0.1.0 | – | stable |
BT.BTN_Y | 0.1.0 | – | stable |
BT.DEFAULT_KEYBOARD_PLAYER1 | 1.0.3 | 1.8.0 | stable |
BT.DEFAULT_KEYBOARD_PLAYER2 | 1.0.3 | 1.8.0 | stable |
BT.DEFAULT_KEYBOARD_SINGLE_PLAYER1 | 1.8.0 | – | stable |
BT.DEFAULT_KEYBOARD_SINGLE_PLAYER2 | 1.8.0 | – | stable |
BT.gamepadCount | 1.0.4 | – | stable |
BT.getAxis | 1.0.3 | – | stable |
BT.hideCursor | 1.0.3 | – | stable |
BT.inputMap | 1.0.3 | – | stable |
BT.inputMapReset | 1.0.3 | 1.8.0 | stable |
BT.inputString | 1.0.4 | – | stable |
BT.isAnyButtonDown | 1.8.0 | – | stable |
BT.isAnyButtonPressed | 1.8.0 | – | stable |
BT.isAnyButtonReleased | 1.8.0 | – | stable |
BT.isAnyKeyDown | 1.8.0 | – | stable |
BT.isAnyKeyPressed | 1.8.0 | – | stable |
BT.isAnyKeyReleased | 1.8.0 | – | stable |
BT.isDown | 1.1.1 | 1.8.0 | stable |
BT.isGamepadConnected | 1.1.1 | – | stable |
BT.isKeyDown | 1.1.1 | – | stable |
BT.isKeyPressed | 1.1.1 | – | stable |
BT.isKeyReleased | 1.1.1 | – | stable |
BT.isPointerActive | 1.1.1 | – | stable |
BT.isPressed | 1.1.1 | 1.8.0 | stable |
BT.isReleased | 1.1.1 | 1.8.0 | stable |
BT.nativeScreenToDisplayPos | 1.8.0 | – | stable |
BT.PLAYER_FOUR | 1.0.3 | – | stable |
BT.PLAYER_ONE | 1.0.3 | – | stable |
BT.PLAYER_THREE | 1.0.3 | – | stable |
BT.PLAYER_TWO | 1.0.3 | – | stable |
BT.pointerDelta | 1.0.3 | – | stable |
BT.pointerDeltaTo | 1.7.0 | – | stable |
BT.pointerPos | 1.0.3 | – | stable |
BT.pointerPosTo | 1.7.0 | – | stable |
BT.pointerScrollDelta | 1.0.4 | 1.3.1 | stable |
BT.showCursor | 1.0.3 | – | stable |
KeyboardLayout | 1.8.0 | – | stable |
1.8.0
- Added
BT.BTN_L2 - Added
BT.BTN_R2 - Added
BT.BTN_TRIGGER - Changed
BT.DEFAULT_KEYBOARD_PLAYER1Now the `'versus'` `keyboardLayout` table (the default); see `BT.DEFAULT_KEYBOARD_SINGLE_PLAYER1` for `'single'`. - Changed
BT.DEFAULT_KEYBOARD_PLAYER2Now the `'versus'` `keyboardLayout` table (the default); see `BT.DEFAULT_KEYBOARD_SINGLE_PLAYER2` for `'single'`. - Added
BT.DEFAULT_KEYBOARD_SINGLE_PLAYER1 - Added
BT.DEFAULT_KEYBOARD_SINGLE_PLAYER2 - Changed
BT.inputMapResetRestores the defaults of the active `HardwareSettings.keyboardLayout` instead of always the `'versus'` tables. - Added
BT.isAnyButtonDown - Added
BT.isAnyButtonPressed - Added
BT.isAnyButtonReleased - Added
BT.isAnyKeyDown - Added
BT.isAnyKeyPressed - Added
BT.isAnyKeyReleased - Changed
BT.isDownAlso accepts BTN_L2 / BTN_R2 / BTN_TRIGGER (gamepad only). - Changed
BT.isPressedAlso accepts BTN_L2 / BTN_R2 / BTN_TRIGGER (gamepad only). - Changed
BT.isReleasedAlso accepts BTN_L2 / BTN_R2 / BTN_TRIGGER (gamepad only). - Added
BT.nativeScreenToDisplayPos - Added
KeyboardLayout
1.7.0 - Fri Sep 11 2026
- Added
BT.pointerDeltaTo - Added
BT.pointerPosTo
1.3.1 - Sun Jul 19 2026
- Changed
BT.pointerScrollDeltaRequires `HardwareSettings.isCapturingPointerScroll` (or overlay force); opt-in now, not default.
1.1.1 - Sun Jun 07 2026
- Added
BT.isDown - Added
BT.isGamepadConnected - Added
BT.isKeyDown - Added
BT.isKeyPressed - Added
BT.isKeyReleased - Added
BT.isPointerActive - Added
BT.isPressed - Added
BT.isReleased
1.0.4 - Sun May 17 2026
- Added
BT.gamepadCount - Added
BT.inputString - Added
BT.pointerScrollDelta
1.0.3 - Sat May 16 2026
- Added
BT.AXIS_LEFT_X - Added
BT.AXIS_LEFT_Y - Added
BT.AXIS_RIGHT_X - Added
BT.AXIS_RIGHT_Y - Added
BT.AXIS_TRIGGER_L - Added
BT.AXIS_TRIGGER_R - Added
BT.BTN_ABXY - Added
BT.BTN_POINTER_ANY - Added
BT.BTN_POINTER_D - Added
BT.BTN_SHOULDER - Added
BT.DEFAULT_KEYBOARD_PLAYER1 - Added
BT.DEFAULT_KEYBOARD_PLAYER2 - Added
BT.PLAYER_FOUR - Added
BT.PLAYER_ONE - Added
BT.PLAYER_THREE - Added
BT.PLAYER_TWO - Added
BT.getAxis - Added
BT.hideCursor - Added
BT.inputMap - Added
BT.inputMapReset - Added
BT.pointerDelta - Added
BT.pointerPos - Added
BT.showCursor
0.1.0 - Sat Dec 13 2025
- Added
BT.BTN_A - Added
BT.BTN_B - Added
BT.BTN_DOWN - Added
BT.BTN_L - Added
BT.BTN_LEFT - Added
BT.BTN_POINTER_A - Added
BT.BTN_POINTER_B - Added
BT.BTN_POINTER_C - Added
BT.BTN_R - Added
BT.BTN_RIGHT - Added
BT.BTN_SELECT - Added
BT.BTN_START - Added
BT.BTN_UP - Added
BT.BTN_X - Added
BT.BTN_Y
See also
API: Core
Overlay Guide
Deprecation Timeline
Developer Experience
Last updated on October 18, 2026