/*
Keyboard Input: face buttons, raw keys, and typed text.
@description Face buttons for two players, raw key state with optional tick repeat, and text from BT.inputString.
Part of the BLIT386 demo series.
Prerequisites:
- Basics: https://demos.blit386.dev/basics
- Pointer Basics: https://demos.blit386.dev/pointer-basics
Live version: https://demos.blit386.dev/keyboard-input
Guides:
- Input: https://blit386.dev/docs/guides/input#keyboard
- Input: https://blit386.dev/docs/guides/input#frame-timing-semantics
---
WHAT YOU WILL SEE
-----------------
A title strip across the top. On the left, two panels sit side by side like two little
gamepads: "Player 0" and "Player 1", each with the same eight face buttons in the same
order - a pip that fills while the button is held, its short name, and on the right the
key it is mapped to (arrow characters for player 1's arrow keys). Player 1 has no
Select key by default, so its last row shows a dash and never lights.
To the right of them is a column of two panels, ending level with the gamepads.
"isKeyPressed counter" shows which key you tapped last and how many times in a row. "Raw
keys" has a pip for Q, the engine tick of the last F release, a pip that lights while any
key at all is held, and the tick when the last key came up. Along the bottom, a wide
panel shows the last 48 characters you typed (on a touch screen it says "This demo needs
a keyboard" instead).
Every panel and pip is drawn with the shared UI kit (src/shared/ui.js). The inputs stay
deliberately keyboard-only: there is no touch stand-in for a physical keyboard.
WHAT YOU WILL LEARN
-------------------
- Face buttons BT.BTN_UP through BT.BTN_SELECT, read with BT.isDown(button, player) for
players 0 and 1. Each button maps to one or more physical keys through the keyboard
map; the defaults live in BT.DEFAULT_KEYBOARD_PLAYER1 and BT.DEFAULT_KEYBOARD_PLAYER2,
and BT.inputMapReset() restores them.
- Raw keys by KeyboardEvent.code string: BT.isKeyDown(code) for held state,
BT.isKeyPressed(code) for the press edge, BT.isKeyPressed(code, repeatTicks) for a
fixed-tick repeat, and BT.isKeyReleased(code) for the release edge.
- Whole-keyboard checks: BT.isAnyKeyDown() and BT.isAnyKeyReleased().
- BT.inputString, the characters typed since the last update tick.
- Edges (pressed, released, inputString) are read in update(); held state is safe to
read in render().
- isCapturingKeyboardScroll keeps arrow keys and Space from scrolling the page.
THREE LAYERS OF KEYBOARD SUPPORT
--------------------------------
- Face buttons (BT.BTN_UP through BT.BTN_SELECT) for players 0 and 1. Each button can map
to one or more physical keys. The engine uses KeyboardEvent.code strings (like KeyW,
ArrowUp) so labels stay the same even when the OS keyboard layout changes (unlike
event.key, which might print different letters).
- Raw keys (BT.isKeyDown, BT.isKeyPressed, BT.isKeyReleased) when you need a specific key,
optional fixed-tick repeats with BT.isKeyPressed(code, rate), and release edges. When
you do NOT care which key, BT.isAnyKeyDown and BT.isAnyKeyReleased ask about the whole
keyboard instead.
- Text input (BT.inputString) for characters typed since the last tick. The buffer clears
once per fixed-update tick, and that tick always finishes before render() runs, so read
it in update(), not render(), or you can miss characters.
TRY THIS
--------
- Hold W, A, S, D, Space (or B), N, 5, and Esc for player 0; the arrow keys, ; (or
Numpad 1), ' (or Numpad 2), and Backspace (or Numpad /) for player 1. Each panel shows
the first key of every button's mapping.
- Tap the same letter repeatedly and watch the press counter climb (edge-only
BT.isKeyPressed, no repeat rate). Tap a different key and the counter resets to 1. The
counter listens to letters, Space, and the arrow keys.
- Hold Q to see isKeyDown; tap F and watch the "F rel" readout.
- Hold two keys, then let go of one: "Any key held" stays lit. Let go of the last one and
the "All up" tick updates - isAnyKeyReleased means EVERYTHING is up.
- Type letters into the buffer line at the bottom.
- If keys stop responding, click the canvas - focus may have moved to another part of the
page after you tabbed away.
*/
import { function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>One-liner bootstrap function for BLIT386 demos.
Handles canvas retrieval and engine initialization. Backend selection
(WebGPU or software fallback) is managed internally by BTAPI.
This function provides a streamlined way to start a demo with sensible defaults
while allowing customization through options.bootstrap, const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT } from 'blit386';
import { import keyLabelkeyLabel } from './shared/key-names.js';
import { import applyThemeapplyTheme, import FONT_WFONT_W, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js';
/** @typedef {import('blit386').Palette} Palette */
// How many characters the typed-text line keeps (a rolling window). The line lives in a
// 308-pixel-wide panel and the system font is 6 pixels per character, so 48 characters
// fill the panel without spilling over its border.
const const TYPED_BUFFER_MAX: 48TYPED_BUFFER_MAX = 48;
// Keys the press counter listens to (KeyboardEvent.code strings): every letter, Space,
// and the four arrow keys.
const const PRESS_COUNTER_KEYS: {}PRESS_COUNTER_KEYS = [
...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map((letter: anyletter) => `Key${letter: anyletter}`),
'Space',
'ArrowUp',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
];
// Fixed layout, in display pixels, on a 4-pixel grid: 4 pixels from the screen edges and
// 4 between panels. The kit normally stacks panels on its own, but this page places
// several groups side by side, so each one gets a pinned x/y.
//
// - Two 76-pixel gamepad panels on the left, a 152-pixel column on the right
// (4 + 76 + 4 + 76 + 4 + 152 + 4 = 320).
// - The right column holds two 73-pixel panels (22 title + 4 rows of 10 + 11 bottom),
// 150 pixels with the gap between them, and the gamepad panels are made exactly
// that tall too, so every bottom edge lines up.
// - The typed-text panel spans the full width under them.
const const EDGE: 4EDGE = 4;
const const GAP: 4GAP = 4;
const const PANEL_TOP_Y: 32PANEL_TOP_Y = 32;
const const PAD_PANEL_W: 76PAD_PANEL_W = 76;
const const PLAYER0_PANEL_X: 4PLAYER0_PANEL_X = const EDGE: 4EDGE;
const const PLAYER1_PANEL_X: numberPLAYER1_PANEL_X = const PLAYER0_PANEL_X: 4PLAYER0_PANEL_X + const PAD_PANEL_W: 76PAD_PANEL_W + const GAP: 4GAP;
const const READOUT_COLUMN_X: numberREADOUT_COLUMN_X = const PLAYER1_PANEL_X: numberPLAYER1_PANEL_X + const PAD_PANEL_W: 76PAD_PANEL_W + const GAP: 4GAP;
const const READOUT_COLUMN_W: numberREADOUT_COLUMN_W = 320 - const EDGE: 4EDGE - const READOUT_COLUMN_X: numberREADOUT_COLUMN_X;
const const READOUT_PANEL_H: numberREADOUT_PANEL_H = 22 + 4 * 10 + 11;
const const RAW_PANEL_Y: numberRAW_PANEL_Y = const PANEL_TOP_Y: 32PANEL_TOP_Y + const READOUT_PANEL_H: numberREADOUT_PANEL_H + const GAP: 4GAP;
const const COLUMN_H: numberCOLUMN_H = 2 * const READOUT_PANEL_H: numberREADOUT_PANEL_H + const GAP: 4GAP;
const const TYPED_PANEL_Y: numberTYPED_PANEL_Y = const PANEL_TOP_Y: 32PANEL_TOP_Y + const COLUMN_H: numberCOLUMN_H + const GAP: 4GAP;
const const TYPED_PANEL_W: numberTYPED_PANEL_W = 320 - 2 * const EDGE: 4EDGE;
// Inside a gamepad panel: eight 10-pixel pip rows with a 5-pixel gap after each of the
// first seven, plus 2 pixels at the end, fill the 117 pixels between the 22-pixel title
// strip and the 11-pixel bottom rim (8 x 10 + 7 x 5 + 2 = 117 = 150 - 22 - 11).
const const PAD_ROW_GAP: 5PAD_ROW_GAP = 5;
const const PAD_ROW_PITCH: numberPAD_ROW_PITCH = 10 + const PAD_ROW_GAP: 5PAD_ROW_GAP;
const const PAD_END_SPACER: numberPAD_END_SPACER = const COLUMN_H: numberCOLUMN_H - 22 - 11 - (8 * 10 + 7 * const PAD_ROW_GAP: 5PAD_ROW_GAP);
// Kit panel content starts 9 pixels in from the panel edge and 22 pixels below its top.
const const PANEL_PAD: 9PANEL_PAD = 9;
const const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP = 22;
// Shows keyboard face-button maps, raw key queries, and BT.inputString side by side.
class class DemoDemo {
/** @type {Palette | null} */
Demo.palette: Palette | nullpalette = null;
// Palette slots of the shared UI theme colors, filled by applyTheme() in init().
/** @type {ReturnType<typeof applyTheme> | null} */
Demo.theme: anytheme = null;
// The eight face buttons for each player, built once in init(): the on-screen label,
// the engine's button constant, and the short name of the first key mapped to it
// ('-' when the player has no key for that button). Index 0 is player 0's list,
// index 1 is player 1's.
/** @type {Array<Array<{ label: string, code: number, key: string }>>} */
Demo.faceButtons: Array<Array<{
label: string;
code: number;
key: string;
}>>
faceButtons = [];
// Engine tick when BT.isKeyReleased('KeyF') last fired.
/** @type {number | null} */
Demo.lastFReleaseTick: number | nulllastFReleaseTick = null;
// Engine tick when BT.isAnyKeyReleased() last fired (every key up).
/** @type {number | null} */
Demo.lastAllUpTick: number | nulllastAllUpTick = null;
// KeyboardEvent.code of the key we are counting presses on.
/** @type {string | null} */
Demo.activePressKey: string | nullactivePressKey = null;
// How many edge-only BT.isKeyPressed events fired for activePressKey in a row.
Demo.keyPressCount: numberkeyPressCount = 0;
// Text built from BT.inputString over time (capped at TYPED_BUFFER_MAX).
Demo.typedBuffer: stringtypedBuffer = '';
// Keeps the arrow keys and Space from scrolling the page, and turns the engine
// overlay off - the panels already show everything this page is about.
Demo.configure(): {
isCapturingKeyboardScroll: boolean;
isOverlayEnabled: boolean;
}
configure() {
return {
// Arrow keys and Space scroll the host page by default. This demo maps those
// keys (player 1 uses arrows), so opt in so pressing them does not move the
// page.
isCapturingKeyboardScroll: booleanisCapturingKeyboardScroll: true,
isOverlayEnabled: booleanisOverlayEnabled: false,
};
}
// Installs the shared UI theme and builds the face-button lists once at startup.
async Demo.init(): unknowninit() {
// inputMapReset() restores the engine's default player 0 / player 1 key bindings.
// Other demos (for example Input Map Remapping) may change those maps; we reset
// here so W/A/S/D and the arrow keys always match what this page describes.
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.inputMapReset: () => voidRestores built-in default keyboard maps for players `0` and `1`.
Uses the tables for the active `keyboardLayout` from `configure()`:
`BT.DEFAULT_KEYBOARD_PLAYER1` / `BT.DEFAULT_KEYBOARD_PLAYER2` for `'versus'` (the
default), `BT.DEFAULT_KEYBOARD_SINGLE_PLAYER1` / `BT.DEFAULT_KEYBOARD_SINGLE_PLAYER2`
for `'single'`.inputMapReset();
this.Demo.palette: Palette | nullpalette = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.paletteCreate: (size?: number) => PaletteCreates a standalone palette instance.paletteCreate(256);
// applyTheme() installs the twelve shared UI colors (into high palette slots, far
// above where scene art normally lives) and reports their slots, so render() can
// clear the screen with the theme's background color. Every panel, pip, and text
// row on this page draws with these colors - no hand-picked HUD slots needed.
this.Demo.theme: anytheme = import applyThemeapplyTheme(this.Demo.palette: Palettepalette);
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.paletteSet: (palette: Palette) => voidStores the active engine palette.
Use this to swap the **entire palette** (e.g. switch between a day and night
theme). After this call the renderer uploads the new palette uniform on the
next frame.
**Palette-value swap (change what a slot looks like):** mutate the live
{@link
BT.palette
}
in place with `palette.set(slot, newColor)`. The renderer
uploads dirty slots on the next frame; no `paletteSet()` or
{@link
BT.spritesRefresh
}
needed.
**Palette-layout swap (same colors, different slot positions):** build a new
palette with the same colors at new indices, call `paletteSet()`, then call
{@link
BT.spritesRefresh
}
so every sprite sheet re-maps its original RGBA
pixels against the new slot layout.paletteSet(this.Demo.palette: Palettepalette);
// The full face-button roster, in the order the panels list them. Each entry
// pairs a short on-screen label with the engine's button constant.
const const allButtons: {}allButtons = [
{ label: stringlabel: 'Up', code: numbercode: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_UP: numberUp button bit flag.BTN_UP },
{ label: stringlabel: 'Dn', code: numbercode: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_DOWN: numberDown button bit flag.BTN_DOWN },
{ label: stringlabel: 'Lf', code: numbercode: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_LEFT: numberLeft button bit flag.BTN_LEFT },
{ label: stringlabel: 'Rt', code: numbercode: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_RIGHT: numberRight button bit flag.BTN_RIGHT },
{ label: stringlabel: 'A', code: numbercode: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_A: numberA button bit flag.BTN_A },
{ label: stringlabel: 'B', code: numbercode: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_B: numberB button bit flag.BTN_B },
{ label: stringlabel: 'St', code: numbercode: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_START: numberStart button bit flag.BTN_START },
{ label: stringlabel: 'Sl', code: numbercode: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_SELECT: numberSelect button bit flag.BTN_SELECT },
];
// Give every button the short name of the first key in each player's default map
// (keyLabel() from src/shared/key-names.js turns KeyW into W and the arrow keys
// into arrow characters).
// Both players keep all eight rows, so their panels line up row for row; a button
// with no key (player 1's Select, for example) shows a dash and never lights.
// .map() walks the two default maps, and the inner .map() the eight buttons.
this.Demo.faceButtons: Array<Array<{
label: string;
code: number;
key: string;
}>>
faceButtons = [const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type DEFAULT_KEYBOARD_PLAYER1: Readonly<Record<number, {}>>Default `KeyboardEvent.code` values for player index 0 (first keyboard player).DEFAULT_KEYBOARD_PLAYER1, const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type DEFAULT_KEYBOARD_PLAYER2: Readonly<Record<number, {}>>Default `KeyboardEvent.code` values for player index 1 (second keyboard player).DEFAULT_KEYBOARD_PLAYER2].map((map: anymap) =>
const allButtons: {}allButtons.map((button: anybutton) => {
const const keys: anykeys = map: anymap[button: anybutton.code] ?? [];
return { ...button: anybutton, key: anykey: const keys: anykeys.length > 0 ? import keyLabelkeyLabel(const keys: anykeys[0]) : '-' };
}),
);
return true;
}
// Reads keyboard state after the engine has updated input for this tick.
Demo.update(): voidupdate() {
// The UI kit's once-per-tick housekeeping. This demo has no kit buttons, but
// ui.tick() is also where the kit notices touch contacts - ui.hasTouch() in
// render() relies on it to know when to show the "needs a keyboard" notice.
import uiui.tick();
// Raw keys (KeyboardEvent.code strings, layout-independent):
//
// BT.isKeyDown(code) is true EVERY tick while the key is held (like holding a
// door shut). BT.isKeyPressed(code) is true only on the FIRST tick the key goes
// down (a one-shot "tap"). BT.isKeyPressed(code, repeatTicks) is true on that
// first tick and then again every repeatTicks fixed engine ticks while still held
// (repeat on the game clock, not the OS key-repeat rate). BT.isKeyReleased(code)
// is true only on the FIRST tick the key comes up (a one-shot "let go").
//
// Face buttons (BT.BTN_UP, BT.isDown, ...) are separate: they go through the
// input map. Use raw keys when you need a specific key regardless of player slot
// or remapping.
// Release edge for F: fires once when you let go of F. We remember the engine
// tick it happened on so the raw-key panel can show it.
if (const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isKeyReleased: (key: string) => booleanChecks whether a keyboard key was released on the current frame.
Call from `update()`, not `render()`: the release edge clears once per fixed-update
tick, which always runs before that frame's `render()`, so a release read from
`render()` can be intermittently missed under rapid input.isKeyReleased('KeyF')) {
this.Demo.lastFReleaseTick: number | nulllastFReleaseTick = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.ticks: numberCurrent fixed-update tick counter.
Increments once per engine update. Reset via
{@link
BT.ticksReset
}
.ticks;
}
// Every key up: unlike isKeyReleased('KeyF') above, this does NOT fire when you
// let go of one key while another is still down - only when nothing is held any
// more. Like the other edges it must be read here in update(), so we remember the
// tick for render().
if (const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isAnyKeyReleased: () => booleanChecks whether every keyboard key has just been released.
Matches RetroBlit: `true` only on the tick where no key is held any more and at least one
was held before. Releasing one key while another stays down does not count; use
{@link
isKeyReleased
}
for per-key edges. Call from `update()`, not `render()`.isAnyKeyReleased()) {
this.Demo.lastAllUpTick: number | nulllastAllUpTick = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.ticks: numberCurrent fixed-update tick counter.
Increments once per engine update. Reset via
{@link
BT.ticksReset
}
.ticks;
}
// Q held: we only read isKeyDown in render() for a live lit/unlit pip (no state
// here).
// Edge-only press counter: tap the same key to climb; another key resets to 1.
for (let let i: numberi = 0; let i: numberi < const PRESS_COUNTER_KEYS: {}PRESS_COUNTER_KEYS.length; let i: numberi++) {
const const code: anycode = const PRESS_COUNTER_KEYS: {}PRESS_COUNTER_KEYS[let i: numberi];
if (!const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isKeyPressed: (key: string, repeatRate?: number) => booleanChecks whether a keyboard key was pressed on the current fixed-update tick.
Optional `repeatRate` is in fixed ticks between repeats (`0` or omitted =
edge only). When `repeatRate > 0`, repeats fire while held per
`(ticks - firstPressTick) > 0 && (ticks - firstPressTick) % repeatRate === 0`.
Call from `update()`, not `render()`: the press edge clears once per fixed-update
tick, which always runs before that frame's `render()`, so a press read from
`render()` can be intermittently missed under rapid input.isKeyPressed(const code: anycode)) {
continue;
}
if (this.Demo.activePressKey: string | nullactivePressKey === const code: anycode) {
this.Demo.keyPressCount: numberkeyPressCount += 1;
} else {
this.Demo.activePressKey: string | nullactivePressKey = const code: anycode;
this.Demo.keyPressCount: numberkeyPressCount = 1;
}
break;
}
// Text buffer: the characters typed since the last tick arrive here once, so add
// them to our own string now or they are gone next tick.
const const chunk: stringchunk = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.inputString: stringText accumulated since the last fixed-update flush from filtered `beforeinput`
(and Tab / Escape where `beforeinput` is unreliable). Read during `update()`;
the buffer clears at the end of each fixed update step.inputString;
if (const chunk: stringchunk.length > 0) {
this.Demo.typedBuffer: stringtypedBuffer += const chunk: stringchunk;
if (this.Demo.typedBuffer: stringtypedBuffer.length > const TYPED_BUFFER_MAX: 48TYPED_BUFFER_MAX) {
// Keep the most recent characters so long paste tests still show the
// tail.
this.Demo.typedBuffer: stringtypedBuffer = this.Demo.typedBuffer: stringtypedBuffer.slice(-const TYPED_BUFFER_MAX: 48TYPED_BUFFER_MAX);
}
}
}
// Clears the frame and declares every kit panel.
Demo.render(): voidrender() {
// Paint the whole screen with the theme's deep navy background.
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.clear: (paletteIndex: number) => voidSets the frame clear color using a palette index.
The renderer uses this color when clearing the full display at the start
of the next frame.clear(this.Demo.theme: anytheme.bg);
// Full-width title strip across the top, in the classic 22-pixel top-bar style.
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_BAR);
import uiui.panel('Keyboard Input (KeyboardEvent.code)');
import uiui.end();
// The two face-button panels sit side by side, like two little gamepads.
this.Demo.renderFacePanel(player: any, x: any): voidrenderFacePanel(0, const PLAYER0_PANEL_X: 4PLAYER0_PANEL_X);
this.Demo.renderFacePanel(player: any, x: any): voidrenderFacePanel(1, const PLAYER1_PANEL_X: numberPLAYER1_PANEL_X);
// The right-hand column: press counter on top, raw-key readouts below.
this.Demo.renderPressCounter(): voidrenderPressCounter();
this.Demo.renderRawKeyPanel(): voidrenderRawKeyPanel();
// The typed-text line spans the full width under the other panels.
this.Demo.renderTypedLine(): voidrenderTypedLine();
}
// Draws one player's face buttons as a panel of lit/unlit pip rows, each with the key
// it is mapped to on the right. `player` is 0 or 1 (the BT.isDown player index), `x`
// the panel's left edge in display pixels.
Demo.renderFacePanel(player: any, x: any): voidrenderFacePanel(player: anyplayer, x: anyx) {
// Pin the panel to its column; both player panels share the same top edge.
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: anyx, y: numbery: const PANEL_TOP_Y: 32PANEL_TOP_Y, width: numberwidth: const PAD_PANEL_W: 76PAD_PANEL_W });
import uiui.panel(player: anyplayer === 0 ? 'Player 0' : 'Player 1');
const const buttons: Array<Array<{
label: string;
code: number;
key: string;
}>>
buttons = this.Demo.faceButtons: Array<Array<{
label: string;
code: number;
key: string;
}>>
faceButtons[player: anyplayer];
// One read-only pip per button: filled while the button is held, hollow when it
// is up. BT.isDown() reports held state (not a press edge), so reading it here in
// render() is safe - only press/release EDGES must stay in update(). A small
// spacer after each row (except the last) spreads the rows out so the panel ends
// level with the column beside it.
for (let let i: numberi = 0; let i: numberi < const buttons: Array<Array<{
label: string;
code: number;
key: string;
}>>
buttons.length; let i: numberi++) {
const { const label: Array<Array<{
label: string;
code: number;
key: string;
}>>
label, const code: Array<Array<{
label: string;
code: number;
key: string;
}>>
code } = const buttons: Array<Array<{
label: string;
code: number;
key: string;
}>>
buttons[let i: numberi];
import uiui.pip(const label: Array<Array<{
label: string;
code: number;
key: string;
}>>
label, const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isDown: (button: number, player?: number) => booleanChecks whether a button is currently held.
For pointer buttons (`BTN_POINTER_A..D`), the second parameter is the
pointer slot index (0 = mouse, 1-3 = touch / pen). For mouse slot 0:
`A` is left, `B` is right, `C` is middle, `D` is back / forward
(matches RetroBlit canonical, not DOM `PointerEvent.button` index).
Touch / pen slots only support `A`; B/C/D return `false`.
`button` accepts one or more bit flags from the `BTN_*` set (for example
`BT.BTN_A | BT.BTN_B`). Matching uses ANY semantics: returns `true` when
any selected button is held.
For face buttons (`BTN_UP`…`BTN_SELECT`), players `0` and `1` merge keyboard
and gamepad input (logical OR). Players `2` and `3` use gamepad only.
Pointer flags (`BTN_POINTER_*`) use the `player` argument as pointer slot.
`BTN_L2`, `BTN_R2` and `BTN_TRIGGER` are gamepad only (no keyboard mapping).isDown(const code: Array<Array<{
label: string;
code: number;
key: string;
}>>
code, player: anyplayer));
if (let i: numberi < const buttons: Array<Array<{
label: string;
code: number;
key: string;
}>>
buttons.length - 1) {
import uiui.spacer(const PAD_ROW_GAP: 5PAD_ROW_GAP);
}
}
import uiui.spacer(const PAD_END_SPACER: numberPAD_END_SPACER);
import uiui.end();
// The mapped keys, right-aligned against the panel's right edge, on the same rows
// as the pip labels. Every character of the system font is FONT_W (6) pixels
// wide. The kit prints a pip's label 1 pixel below the top of its row, and a
// caption 4 pixels above the y it is given, so y = rowTop + 5 puts both texts on
// the same line.
const const rightEdge: numberrightEdge = x: anyx + const PAD_PANEL_W: 76PAD_PANEL_W - const PANEL_PAD: 9PANEL_PAD;
for (let let i: numberi = 0; let i: numberi < const buttons: Array<Array<{
label: string;
code: number;
key: string;
}>>
buttons.length; let i: numberi++) {
const { const key: Array<Array<{
label: string;
code: number;
key: string;
}>>
key } = const buttons: Array<Array<{
label: string;
code: number;
key: string;
}>>
buttons[let i: numberi];
const const rowTop: numberrowTop = const PANEL_TOP_Y: 32PANEL_TOP_Y + const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP + let i: numberi * const PAD_ROW_PITCH: numberPAD_ROW_PITCH;
import uiui.caption(const rightEdge: numberrightEdge - const key: Array<Array<{
label: string;
code: number;
key: string;
}>>
key.length * import FONT_WFONT_W, const rowTop: numberrowTop + 5, const key: Array<Array<{
label: string;
code: number;
key: string;
}>>
key, { color: stringcolor: 'dim' });
}
}
// Draws the readout for edge-only press counting on one key at a time.
Demo.renderPressCounter(): voidrenderPressCounter() {
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const READOUT_COLUMN_X: numberREADOUT_COLUMN_X, y: numbery: const PANEL_TOP_Y: 32PANEL_TOP_Y, width: numberwidth: const READOUT_COLUMN_W: numberREADOUT_COLUMN_W });
import uiui.panel('isKeyPressed counter');
// Before any counted key is tapped there is nothing to show, so both rows fall
// back to placeholder text.
const const hasKey: booleanhasKey = this.Demo.activePressKey: string | nullactivePressKey !== null;
import uiui.kv('Key', const hasKey: booleanhasKey ? import keyLabelkeyLabel(this.Demo.activePressKey: string | nullactivePressKey) : 'none yet');
import uiui.kv('Count', const hasKey: booleanhasKey ? this.Demo.keyPressCount: numberkeyPressCount : '-');
import uiui.label('Same key: +1', { color: stringcolor: 'dim' });
import uiui.label('New key: reset', { color: stringcolor: 'dim' });
import uiui.end();
}
// Draws the panel for Q held state, the last F release, and the whole-keyboard
// checks.
Demo.renderRawKeyPanel(): voidrenderRawKeyPanel() {
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const READOUT_COLUMN_X: numberREADOUT_COLUMN_X, y: numbery: const RAW_PANEL_Y: numberRAW_PANEL_Y, width: numberwidth: const READOUT_COLUMN_W: numberREADOUT_COLUMN_W });
import uiui.panel('Raw keys');
// Held state is safe to read in render() (see renderFacePanel above).
import uiui.pip('Q (isKeyDown)', const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isKeyDown: (key: string) => booleanChecks whether a keyboard key is currently held.
Uses `KeyboardEvent.code` (for example `"KeyW"`, `"Space"`, `"ArrowUp"`).isKeyDown('KeyQ'));
// The release EDGE was caught in update(); here we only display the remembered
// tick.
import uiui.kv('F rel', this.Demo.lastFReleaseTick: number | nulllastFReleaseTick === null ? 'tap F' : `tick ${this.Demo.lastFReleaseTick: numberlastFReleaseTick}`);
// Held state again, but for the whole keyboard: lit while ANY key is down.
import uiui.pip('Any (isAnyKeyDown)', const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isAnyKeyDown: () => booleanChecks whether at least one keyboard key is currently held.isAnyKeyDown());
// The "all keys up" edge was caught in update(); here we only show the remembered
// tick.
import uiui.kv('All up', this.Demo.lastAllUpTick: number | nulllastAllUpTick === null ? 'press, release' : `tick ${this.Demo.lastAllUpTick: numberlastAllUpTick}`);
import uiui.end();
}
// Draws the text accumulated from BT.inputString. On a touch device it shows a
// warning instead - this page has no on-screen substitute for a physical keyboard.
Demo.renderTypedLine(): voidrenderTypedLine() {
// A fixed width keeps the panel spanning the screen even while the buffer is
// short.
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const EDGE: 4EDGE, y: numbery: const TYPED_PANEL_Y: numberTYPED_PANEL_Y, width: numberwidth: const TYPED_PANEL_W: numberTYPED_PANEL_W });
import uiui.panel('BT.inputString (typed this session)');
const const hasText: booleanhasText = this.Demo.typedBuffer: stringtypedBuffer.length > 0;
if (import uiui.hasTouch()) {
import uiui.label('This demo needs a keyboard', { color: stringcolor: 'warm' });
} else {
import uiui.label(const hasText: booleanhasText ? this.Demo.typedBuffer: stringtypedBuffer : '...', { color: stringcolor: const hasText: booleanhasText ? 'text' : 'dim' });
}
import uiui.end();
}
}
function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>One-liner bootstrap function for BLIT386 demos.
Handles canvas retrieval and engine initialization. Backend selection
(WebGPU or software fallback) is managed internally by BTAPI.
This function provides a streamlined way to start a demo with sensible defaults
while allowing customization through options.bootstrap(class DemoDemo);