/**
* Keyboard Input Demo - 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
*
* This page shows 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.
* - Text input (BT.inputString) for characters in one frame. 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.
*
* The panels and readouts are drawn with the shared demo UI kit (src/shared/ui.js),
* so this page looks like every other demo in the series. The inputs themselves stay
* deliberately keyboard-only - there is no touch stand-in for a physical keyboard,
* so on a touch device the page shows a "needs a keyboard" notice instead.
*
* Try this:
* - Hold W, A, S, D and Space / N on player 1; arrow keys and ; ' on player 2.
* - Tap the same letter repeatedly and watch the press counter climb (edge-only
* BT.isKeyPressed, no repeat rate). Press a different key and the counter resets.
* - Hold Q to see isKeyDown; tap F and watch the release readout.
* - 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;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT } from 'blit386';
import { import applyThemeapplyTheme, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js';
/** @typedef {import('blit386').IBTDemo} IBTDemo */
/** @typedef {import('blit386').HardwareSettings} HardwareSettings */
/** @typedef {import('blit386').Palette} Palette */
// How many characters we keep in the typed-text demo line (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).
const const PRESS_COUNTER_KEYS: {}PRESS_COUNTER_KEYS = [
...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map((letter: anyletter) => `Key${letter: anyletter}`),
'Space',
'ArrowUp',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
];
// Palette slots of three shared UI theme colors, for the overlay timing chart below.
// applyTheme() in init() writes the twelve theme colors into slots 240-251 (its default
// start slot), but configure() runs BEFORE init(), so the chart style cannot read
// this.theme yet - we spell out where the colors will land: 244 = text, 245 = dim gray,
// 247 = accent green.
const const THEME_TEXT_SLOT: 244THEME_TEXT_SLOT = 244;
const const THEME_DIM_SLOT: 245THEME_DIM_SLOT = 245;
const const THEME_ACCENT_SLOT: 247THEME_ACCENT_SLOT = 247;
// Fixed vertical layout, in display pixels. 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.
const const NOTICE_Y: 24NOTICE_Y = 24; // The hint / touch-notice line right under the title strip.
const const PANEL_TOP_Y: 42PANEL_TOP_Y = 42; // Top edge of the face-button panels and the press counter.
const const RAW_PANEL_Y: 134RAW_PANEL_Y = 134; // Top edge of the raw-key panel (below the press counter).
const const PLAYER0_PANEL_X: 6PLAYER0_PANEL_X = 6; // Left edge of the player 0 panel.
const const PLAYER1_PANEL_X: 68PLAYER1_PANEL_X = 68; // Left edge of the player 1 panel (next to player 0).
const const READOUT_COLUMN_X: 130READOUT_COLUMN_X = 130; // Left edge of the press-counter / raw-key column.
const const TYPED_PANEL_WIDTH: 308TYPED_PANEL_WIDTH = 308; // The typed-text panel spans almost the full screen.
/**
* Turns `KeyH` into `H`, `Digit5` into `5`, and leaves other codes readable.
*
* @param {string} code - KeyboardEvent.code value.
* @returns {string}
*/
function function formatKeyCode(code: string): stringTurns `KeyH` into `H`, `Digit5` into `5`, and leaves other codes readable.formatKeyCode(code: string- KeyboardEvent.code value.code) {
if (code: string- KeyboardEvent.code value.code.startsWith('Key')) {
return code: string- KeyboardEvent.code value.code.slice(3);
}
if (code: string- KeyboardEvent.code value.code.startsWith('Digit')) {
return code: string- KeyboardEvent.code value.code.slice(5);
}
return code: string- KeyboardEvent.code value.code;
}
/**
* Shows keyboard face-button maps, low-level key queries, and `inputString`.
*
* @implements {IBTDemo}
*/
class class DemoShows keyboard face-button maps, low-level key queries, and `inputString`.Demo {
/** @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;
/**
* Face buttons that have at least one key in each player's default map, built once
* in init(). Index 0 is player 0's list, index 1 is player 1's.
*
* @type {Array<Array<{ label: string, code: number }>>}
*/
Demo.faceButtons: Array<Array<{
label: string;
code: number;
}>>
Face buttons that have at least one key in each player's default map, built once
in init(). Index 0 is player 0's list, index 1 is player 1's.faceButtons = [];
/** @type {number | null} Engine tick when `BT.isKeyReleased('KeyF')` last fired. */
Demo.lastFReleaseTick: number | nulllastFReleaseTick = null;
/** @type {string | null} KeyboardEvent.code for the key we are counting presses on. */
Demo.activePressKey: string | nullactivePressKey = null;
// How many edge-only `BT.isKeyPressed` events fired for `activePressKey` this run.
Demo.keyPressCount: numberkeyPressCount = 0;
// Text built from `BT.inputString` over time (capped).
Demo.typedBuffer: stringtypedBuffer = '';
/**
* Timing chart on so key-release milestones show on the overlay HUD.
*
* @returns {Partial<HardwareSettings>}
*/
Demo.configure(): Partial<HardwareSettings>Timing chart on so key-release milestones show on the overlay HUD.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,
isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true,
overlayTimingChartStyle: {
updateBarPaletteIndex: number;
renderBarPaletteIndex: number;
tagPaletteIndex: number;
}
overlayTimingChartStyle: {
updateBarPaletteIndex: numberupdateBarPaletteIndex: const THEME_DIM_SLOT: 245THEME_DIM_SLOT,
renderBarPaletteIndex: numberrenderBarPaletteIndex: const THEME_TEXT_SLOT: 244THEME_TEXT_SLOT,
tagPaletteIndex: numbertagPaletteIndex: const THEME_ACCENT_SLOT: 247THEME_ACCENT_SLOT,
},
};
}
/**
* Install the shared UI theme and build the face-button lists once at startup.
*
* @returns {Promise<boolean>}
*/
async Demo.init(): Promise<boolean>Install the shared UI theme and build the face-button lists once at startup.init() {
// inputMapReset() restores the engine's default player-1 / player-2 key bindings.
// Other demos (for example input-map remapping) may change those maps; we reset here
// so W/A/S/D and 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;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.inputMapReset: () => voidRestores built-in default keyboard maps for players `0` and `1`.
Same tables as `BT.DEFAULT_KEYBOARD_PLAYER1` and `BT.DEFAULT_KEYBOARD_PLAYER2`.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;
... 106 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;
... 106 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;
... 106 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;
... 106 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;
... 106 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;
... 106 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;
... 106 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;
... 106 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;
... 106 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;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_SELECT: numberSelect button bit flag.BTN_SELECT },
];
// Keep only the buttons that actually have keys in each player's default map
// (player 2's Select slot is empty, for example, so its row would never light).
// .map() walks the two default maps and .filter() drops the empty slots.
this.Demo.faceButtons: Array<Array<{
label: string;
code: number;
}>>
Face buttons that have at least one key in each player's default map, built once
in init(). Index 0 is player 0's list, index 1 is player 1's.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;
... 106 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;
... 106 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.filter((button: anybutton) => (map: anymap[button: anybutton.code] ?? []).length > 0),
);
return true;
}
/**
* Read keyboard state after the engine has updated input for this tick.
*/
Demo.update(): voidRead keyboard state after the engine has updated input for this tick.update() {
// 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) = true EVERY tick while the key is held (like holding a door shut).
// BT.isKeyPressed(code) = true only on the FIRST tick the key goes down (one-shot "tap").
// BT.isKeyPressed(code, repeatTicks) = first tick down, then again every repeatTicks fixed
// engine ticks while still held (repeat on the game clock, not the OS key-repeat rate).
// BT.isKeyReleased(code) = true only on the FIRST tick the key comes up (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;
... 106 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;
... 106 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;
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;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.assignTag: (label?: string) => voidPlaces a labeled marker on the overlay timing chart at the current tick.
Requires `isOverlayTimingChartEnabled: true` in `configure()`. Tags scroll with the chart
history and are pruned when they leave the visible window. Empty labels become
`"Untitled"`. Chart width resets add an automatic `"Start"` tag.assignTag('Key F released');
}
// 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;
... 106 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
// Characters arrive for this frame only; concat now or they are gone next frame.
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;
... 106 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);
}
}
}
/**
* Clear the frame and declare every kit panel.
*/
Demo.render(): voidClear the frame and declare every kit panel.render() {
// 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;
... 106 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();
// One borderless line right under the title. On a touch device it becomes a
// warning - this page has no on-screen substitute for a physical keyboard - and
// otherwise it lists the default key maps as a quick reference. pad: 0 removes
// the group's inner padding so the single line sits snug against its y position.
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { y: numbery: const NOTICE_Y: 24NOTICE_Y, pad: numberpad: 0 });
if (import uiui.hasTouch()) {
import uiui.label('This demo needs a keyboard', { color: stringcolor: 'warm' });
} else {
import uiui.label('P0: W A S D Space N 5 Esc - P1: arrows ; quote /', { color: stringcolor: 'dim' });
}
import uiui.end();
// The two face-button panels sit side by side, like two little gamepads.
this.Demo.renderFacePanel(player: number, x: number): voidOne player's mapped face buttons as a panel of lit/unlit pip rows.renderFacePanel(0, const PLAYER0_PANEL_X: 6PLAYER0_PANEL_X);
this.Demo.renderFacePanel(player: number, x: number): voidOne player's mapped face buttons as a panel of lit/unlit pip rows.renderFacePanel(1, const PLAYER1_PANEL_X: 68PLAYER1_PANEL_X);
// The right-hand column: press counter on top, raw-key readouts below.
this.Demo.renderPressCounter(): voidReadout for edge-only press counting on one key at a time.renderPressCounter();
this.Demo.renderRawKeyPanel(): voidPanel for Q held state and the last F release.renderRawKeyPanel();
// The typed-text line hugs the bottom edge of the screen.
this.Demo.renderTypedLine(): voidShows text accumulated from `BT.inputString`.renderTypedLine();
}
/**
* One player's mapped face buttons as a panel of lit/unlit pip rows.
*
* @param {number} player - 0 or 1 (`BT.isDown` player index).
* @param {number} x - Left edge of the panel in display pixels.
*/
Demo.renderFacePanel(player: number, x: number): voidOne player's mapped face buttons as a panel of lit/unlit pip rows.renderFacePanel(player: number- 0 or 1 (`BT.isDown` player index).player, x: number- Left edge of the panel in display pixels.x) {
// Pin the panel to its column; both player panels share the same top edge.
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx, y: numbery: const PANEL_TOP_Y: 42PANEL_TOP_Y });
import uiui.panel(player: number- 0 or 1 (`BT.isDown` player index).player === 0 ? 'Player 0' : 'Player 1');
const const buttons: Array<Array<{
label: string;
code: number;
}>>
buttons = this.Demo.faceButtons: Array<Array<{
label: string;
code: number;
}>>
Face buttons that have at least one key in each player's default map, built once
in init(). Index 0 is player 0's list, index 1 is player 1's.faceButtons[player: number- 0 or 1 (`BT.isDown` player index).player];
// 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().
for (let let i: numberi = 0; let i: numberi < const buttons: Array<Array<{
label: string;
code: number;
}>>
buttons.length; let i: numberi++) {
const { const label: Array<Array<{
label: string;
code: number;
}>>
label, const code: Array<Array<{
label: string;
code: number;
}>>
code } = const buttons: Array<Array<{
label: string;
code: number;
}>>
buttons[let i: numberi];
import uiui.pip(const label: Array<Array<{
label: string;
code: number;
}>>
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;
... 106 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.isDown(const code: Array<Array<{
label: string;
code: number;
}>>
code, player: number- 0 or 1 (`BT.isDown` player index).player));
}
import uiui.end();
}
/**
* Readout for edge-only press counting on one key at a time.
*/
Demo.renderPressCounter(): voidReadout for edge-only press counting on one key at a time.renderPressCounter() {
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const READOUT_COLUMN_X: 130READOUT_COLUMN_X, y: numbery: const PANEL_TOP_Y: 42PANEL_TOP_Y });
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 ? function formatKeyCode(code: string): stringTurns `KeyH` into `H`, `Digit5` into `5`, and leaves other codes readable.formatKeyCode(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();
}
/**
* Panel for Q held state and the last F release.
*/
Demo.renderRawKeyPanel(): voidPanel for Q held state and the last F release.renderRawKeyPanel() {
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const READOUT_COLUMN_X: 130READOUT_COLUMN_X, y: numbery: const RAW_PANEL_Y: 134RAW_PANEL_Y });
import uiui.panel('Raw keys');
// Held state is safe to read in render() (see renderFacePanel above).
import uiui.pip('Q held (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;
... 106 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}`);
import uiui.end();
}
/**
* Shows text accumulated from `BT.inputString`.
*/
Demo.renderTypedLine(): voidShows text accumulated from `BT.inputString`.renderTypedLine() {
// A fixed width keeps the panel spanning the screen even while the buffer is
// short; bottomLeft anchors it just above the bottom edge.
import uiui.begin(import UI_ANCHORSUI_ANCHORS.BOTTOM_LEFT, { width: numberwidth: const TYPED_PANEL_WIDTH: 308TYPED_PANEL_WIDTH });
import uiui.panel('BT.inputString (typed this session)');
const const hasText: booleanhasText = this.Demo.typedBuffer: stringtypedBuffer.length > 0;
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 DemoShows keyboard face-button maps, low-level key queries, and `inputString`.Demo);