/*

Pointer Paint: multi-touch finger painting with a mouse plus up to three fingers.
@description Multi-touch finger painting across all four pointer slots: a mouse plus up to three fingers at once.

Part of the BLIT386 demo series.

Prerequisites:
  - Primitives: https://demos.blit386.dev/primitives
  - Pointer Basics: https://demos.blit386.dev/pointer-basics

Live version: https://demos.blit386.dev/pointer-paint

Guides:
  - Input: https://blit386.dev/docs/guides/input#pointer-slot-model
  - Overlay: https://blit386.dev/docs/guides/overlay#colors-and-hud-palette-slots

Reference:
  - Core: https://blit386.dev/docs/api/core#hardware-settings

---

WHAT YOU WILL SEE
-----------------

An empty canvas in the shared theme's background color, with a small "Paint" panel in the
bottom-right corner. Until something is painted, two dim lines in the middle of the
canvas say how to start. Every active pointer shows a crosshair in its own color, drawn
on top of the panel too, and painting leaves strokes behind in that color:

  - slot 0 = mouse        (white)
  - slot 1 = first touch  (red)
  - slot 2 = second touch (green)
  - slot 3 = third touch  (blue)

The panel has one pip per slot (lit while that mouse or finger is over the canvas) with a
swatch of that slot's paint color at the end of the row, and two full-width buttons:
"Brush: Thin/Medium/Thick" names and cycles the size, and "Clear" wipes the canvas.

WHAT YOU WILL LEARN
-------------------

- How all four pointer slots work side by side: BT.isPointerActive(slot) and
  BT.pointerPos(slot) for each of them.
- BT.isDown(BT.BTN_POINTER_A, 0) gates mouse painting on the left button. Touch slots
  have no separate button, so being in contact is enough.
- BT.isPressed(BT.BTN_POINTER_B, 0) and BT.isPressed(BT.BTN_POINTER_C, 0) as one-shot
  edges: right-click clears, middle-click cycles the brush, each exactly once per click.
- Stamping from the previous tick's position to the current one, so fast strokes look
  continuous instead of dotted.
- ui.overWidget(x, y) keeps brush strokes from landing underneath the UI panel.
- A persistent paint layer: one palette index per display pixel in a Uint8Array, redrawn
  every frame as runs of same-colored pixels, so strokes survive the per-frame BT.clear().
- Reusing the shared UI theme slots for the engine overlay bars via overlayStyle.

TRY THIS
--------

- Hold the left mouse button and move to paint in white.
- Right-click to clear the canvas; middle-click to cycle the brush size.
- On a touchscreen, paint with one, two, or three fingers at once - each gets its own
  color. A fourth finger is ignored. Use the "Brush" and "Clear" buttons instead of the
  mouse shortcuts.
- Press the backquote key (`) to open the engine overlay: long strokes make the render
  bars in the timing chart spike, and the GPU diagnostics row sits below the metrics.

*/

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.
@since0.2.0@changed1.4.0 Calling `bootstrap()` again while already initialized now routes to a hot swap (via {@link registerHotReload}) when a Vite HMR context is registered, or logs a double-bootstrap guard and returns `false` otherwise - previously it silently started a second, unstoppable `GameLoop`.@changed1.7.0 Exposes `BT` on `window.BT` after bootstrap finishes, gated by {@link BootstrapOptions.exposeGlobal} (default: {@link BT.isDevMode}).@paramDemoClass - Demo class constructor implementing `IBTDemo` (optional `configure()` for hardware settings).@paramoptions - Optional configuration for IDs and callbacks.@returns`true` when the demo boots successfully; otherwise `false`.@example// Simplest usage - uses default IDs. bootstrap(MyDemo);@example// With custom options. bootstrap(MyDemo, { canvasID: 'custom-canvas', containerID: 'custom-container', onSuccess: () => console.log('Demo started!'), onError: (err) => analytics.trackError(err), });@example// Await the result. const success = await bootstrap(MyDemo); if (success) { console.log('Demo is running'); }
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
, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
, class Rect2i
Integer rectangle for pixel-perfect bounds and regions. Used throughout the engine for sprite regions, display-space bounds, and zero-allocation geometry helpers. Both convenience getters and allocation-free `*To()` helpers are provided so callers can choose between readability and hot-path efficiency.
@since0.1.0
Rect2i
} from 'blit386';
import { import applyThemeapplyTheme, import FONT_WFONT_W, import PIP_SIZEPIP_SIZE, import THEME_DEFAULT_START_SLOTTHEME_DEFAULT_START_SLOT, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js'; /** @typedef {import('blit386').Palette} Palette */ const const DISPLAY_W: 320DISPLAY_W = 320; const const DISPLAY_H: 240DISPLAY_H = 240; // One paint color per pointer slot. The slot index is the same as the array index here so // update() can write SLOT_PAINT[slot] directly. These are scene colors (the artwork // itself); the UI panel colors come from the shared theme, which lives far away in slots // 240-251. const const SLOT_PAINT: {}SLOT_PAINT = [ 10, // slot 0 (mouse) 11, // slot 1 (first touch) 12, // slot 2 (second touch) 13, // slot 3 (third touch) ]; // Friendly names for the panel rows: which slot is which input device. const const SLOT_LABELS: {}SLOT_LABELS = ['Mouse', 'Touch 1', 'Touch 2', 'Touch 3']; // Brush sizes that middle-click (or the Brush button) cycles through. Values are radii in // pixels; a radius of 0 paints a single pixel. const const BRUSH_SIZES: {}BRUSH_SIZES = [0, 2, 4]; // Human-readable names for the same brushes, shown on the Brush button and in the panel's // Brush row. Same order as BRUSH_SIZES. const const BRUSH_NAMES: {}BRUSH_NAMES = ['Thin', 'Medium', 'Thick']; // The Paint panel sits in the bottom-right corner, 4 pixels from the edges. Its height // comes from the rows it stacks: the 22-pixel title strip, four 10-pixel pip rows, a // 5-pixel gap, two 27-pixel button rows, and the 11-pixel bottom rim. Placing it // from these numbers (instead of letting the kit anchor it) lets the slot color swatches // line up exactly with the pip rows. const const PANEL_W: 112PANEL_W = 112; const const PANEL_H: numberPANEL_H = 22 + 4 * 10 + 5 + 2 * 27 + 11; const const PANEL_X: numberPANEL_X = const DISPLAY_W: 320DISPLAY_W - 4 - const PANEL_W: 112PANEL_W; const const PANEL_Y: numberPANEL_Y = const DISPLAY_H: 240DISPLAY_H - 4 - const PANEL_H: numberPANEL_H; // Kit panel content starts 9 pixels in from the panel edge, and the first row starts 22 // pixels below its top. const const PANEL_PAD: 9PANEL_PAD = 9; const const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP = 22; // Half the length of each cursor crosshair arm, in pixels. const const CURSOR_ARM: 5CURSOR_ARM = 5; // Multi-touch and mouse paint demo. The "canvas" we paint onto is a flat array of palette // indices, one entry per display pixel. Each frame, render() copies that array onto the // screen as thin filled rectangles so strokes persist between frames. Stroke input comes // from checking BT.isPointerActive() / BT.isDown() / BT.pointerPos() on each of the four // slots in update(). class class DemoDemo { /** @type {Palette | null} */ Demo.palette: Palette | null
@type{Palette | null}
palette
= null;
// Palette slot map for the shared UI theme, filled in by applyTheme() in init(). // Gives us named slots like this.theme.bg for our own drawing. Demo.theme: nulltheme = null; // Painting layer: one palette index per display pixel. 0 means "blank" (the // background color shows through). Length = DISPLAY_W * DISPLAY_H. /** @type {Uint8Array | null} */ Demo.layer: any
@type{Uint8Array | null}
layer
= null;
// Index into BRUSH_SIZES; cycled by middle-click or the Brush button. Demo.brushIndex: numberbrushIndex = 1; // One reusable rectangle for renderLayer(), so drawing a run allocates nothing per // frame. Demo.runRect: Rect2irunRect = new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2i
Creates an integer rectangle, truncating all inputs toward zero.
@paramx - Left-edge X coordinate (defaults to 0).@paramy - Top-edge Y coordinate (defaults to 0).@paramwidth - Width in pixels (defaults to 0).@paramheight - Height in pixels (defaults to 0).
Rect2i
(0, 0, 1, 1);
// Another reusable rectangle for the cursors and the panel's color swatches. Demo.uiRect: Rect2iuiRect = new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2i
Creates an integer rectangle, truncating all inputs toward zero.
@paramx - Left-edge X coordinate (defaults to 0).@paramy - Top-edge Y coordinate (defaults to 0).@paramwidth - Width in pixels (defaults to 0).@paramheight - Height in pixels (defaults to 0).
Rect2i
(0, 0, 1, 1);
// True once anything has been painted, so the "how to paint" hint can step aside. // clearCanvas() sets it back to false. Demo.hasPaint: booleanhasPaint = false; // Last known position per slot, recorded the tick the pointer started painting and // after every stamp since. Used to draw a stroke from the previous tick's position to // the current one, which fills gaps when the pointer moves faster than one pixel per // tick. Demo.lastPosX: {}lastPosX = [0, 0, 0, 0]; Demo.lastPosY: {}lastPosY = [0, 0, 0, 0]; // True while we should be painting from this slot. For the mouse this is // BTN_POINTER_A held. For touch this is "slot is active" (contact down). Demo.painting: {}painting = [false, false, false, false]; // Finger painting can spike render() when strokes are long; the chart makes that // visible.
Demo.configure(): {
    isWakeLockEnabled: boolean;
    isOverlayTimingChartEnabled: boolean;
    overlayTimingChartDiagnostics: string;
    isOverlayRendererDiagnosticsBarEnabled: boolean;
    overlayStyle: {
        barPaletteIndex: any;
        textPaletteIndex: any;
        gapPaletteIndex: any;
    };
    overlayTimingChartStyle: {
        updateBarPaletteIndex: any;
        renderBarPaletteIndex: any;
        warningPaletteIndex: any;
        errorPaletteIndex: any;
        tagPaletteIndex: any;
    };
}
configure
() {
return { // Phones and tablets dim, then lock, the screen after 30-60 seconds without a // touch - easy to hit during a slow, careful painting session. This asks the // browser to keep the screen on while you paint; unsupported browsers just // ignore the request. isWakeLockEnabled: booleanisWakeLockEnabled: true, isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true, overlayTimingChartDiagnostics: stringoverlayTimingChartDiagnostics: 'rich', isOverlayRendererDiagnosticsBarEnabled: booleanisOverlayRendererDiagnosticsBarEnabled: true, // The engine overlay bars reuse the shared UI theme colors so everything on // screen matches. applyTheme() writes panel at THEME_DEFAULT_START_SLOT + 2 // and text at + 4; configure() runs before init(), so we derive those slot // numbers here.
overlayStyle: {
    barPaletteIndex: any;
    textPaletteIndex: any;
    gapPaletteIndex: any;
}
overlayStyle
: {
barPaletteIndex: anybarPaletteIndex: import THEME_DEFAULT_START_SLOTTHEME_DEFAULT_START_SLOT + 2, textPaletteIndex: anytextPaletteIndex: import THEME_DEFAULT_START_SLOTTHEME_DEFAULT_START_SLOT + 4, gapPaletteIndex: anygapPaletteIndex: import THEME_DEFAULT_START_SLOTTHEME_DEFAULT_START_SLOT + 2, },
overlayTimingChartStyle: {
    updateBarPaletteIndex: any;
    renderBarPaletteIndex: any;
    warningPaletteIndex: any;
    errorPaletteIndex: any;
    tagPaletteIndex: any;
}
overlayTimingChartStyle
: {
updateBarPaletteIndex: anyupdateBarPaletteIndex: const SLOT_PAINT: {}SLOT_PAINT[0], renderBarPaletteIndex: anyrenderBarPaletteIndex: const SLOT_PAINT: {}SLOT_PAINT[1], warningPaletteIndex: anywarningPaletteIndex: const SLOT_PAINT: {}SLOT_PAINT[2], errorPaletteIndex: anyerrorPaletteIndex: const SLOT_PAINT: {}SLOT_PAINT[3], tagPaletteIndex: anytagPaletteIndex: import THEME_DEFAULT_START_SLOTTHEME_DEFAULT_START_SLOT + 4, }, }; } // Sets up the palette (shared UI theme plus scene paint colors) and allocates the // offscreen paint layer. async Demo.init(): unknowninit() { this.Demo.palette: Palette | null
@type{Palette | null}
palette
=
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) => Palette
Creates a standalone palette instance.
@since1.0.3@paramsize - Palette size. Defaults to 256 colors.@returnsNew mutable palette.
paletteCreate
(256);
// Install the shared UI colors (slots 240-251) that the kit's panel, pips, and // buttons draw with. Must happen before BT.paletteSet() so the colors actually // reach the GPU. this.Demo.theme: nulltheme = import applyThemeapplyTheme(this.Demo.palette: Palette
@type{Palette | null}
palette
);
// One paint color per slot. Slot 0 (mouse) gets a soft white; the three touch // slots get bold primary colors so multiple fingers are easy to tell apart. this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const SLOT_PAINT: {}SLOT_PAINT[0], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(240, 240, 240));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const SLOT_PAINT: {}SLOT_PAINT[1], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(255, 100, 100));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const SLOT_PAINT: {}SLOT_PAINT[2], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(100, 220, 120));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const SLOT_PAINT: {}SLOT_PAINT[3], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(100, 160, 255));
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) => void
Stores 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.
@since1.0.3@parampalette - Palette to make active.
paletteSet
(this.Demo.palette: Palette
@type{Palette | null}
palette
);
// Hide the native OS cursor so the drawn crosshair is the only cursor visible // while the pointer is over the canvas.
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
.hideCursor: () => void
Hides the native OS cursor while the pointer is over the canvas. Call once from `init()` when the demo draws its own crosshair or cursor sprite in place of the system arrow. The cursor is restored automatically when the engine shuts down. No-op before the engine is initialized.
@since1.0.3
hideCursor
();
// Allocate the paint layer. A new Uint8Array starts out all zeros, so every pixel // begins blank and the background color shows through. this.Demo.layer: any
@type{Uint8Array | null}
layer
= new Uint8Array(const DISPLAY_W: 320DISPLAY_W * const DISPLAY_H: 240DISPLAY_H);
return true; } // Per-tick: read input from each slot and write strokes into the layer. Demo.update(): voidupdate() { // Let the UI kit do its per-tick housekeeping (touch tracking) before we read any // input ourselves. Always the first line of update(). import uiui.tick(); // Mouse shortcuts: right-click (button B) clears the canvas and middle-click // (button C) cycles the brush size. The Clear and Brush buttons in the kit panel // (see renderPanel()) call the exact same helper methods, so mouse and touch // users get identical features. We use isPressed (an edge) so a single click // triggers exactly once. 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
.isPressed: (button: number, player?: number, repeatRate?: number) => boolean
Checks whether a button was pressed on the current frame. Same parameter semantics as {@link isDown } ; returns `true` only on the frame the button transitions from up to down. Call from `update()`, not `render()`, for reliable detection: for keyboard-mapped face buttons (players 0 and 1), the press edge clears once per fixed-update tick, which always runs before that frame's `render()`. `BTN_L2`, `BTN_R2` and `BTN_TRIGGER` are gamepad only (no keyboard mapping).
@since1.1.1@changed1.8.0 Also accepts BTN_L2 / BTN_R2 / BTN_TRIGGER (gamepad only).@parambutton - Button constant from the `BTN_*` set.@paramplayer - Zero-based player index for gamepads, or pointer slot (0-3) for `BTN_POINTER_*`.@paramrepeatRate - Optional repeat interval in fixed ticks (`0`/omitted = edge only).@returns`true` on the transition frame.
isPressed
(
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_POINTER_B: number
Secondary pointer button code. Maps to mouse right for slot 0 (matches RetroBlit canonical, not the DOM `PointerEvent.button` index where 1 is middle and 2 is right). Always `false` for touch slots 1-3.
@since0.1.0
BTN_POINTER_B
, 0)) {
this.Demo.clearCanvas(): voidclearCanvas(); } 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
.isPressed: (button: number, player?: number, repeatRate?: number) => boolean
Checks whether a button was pressed on the current frame. Same parameter semantics as {@link isDown } ; returns `true` only on the frame the button transitions from up to down. Call from `update()`, not `render()`, for reliable detection: for keyboard-mapped face buttons (players 0 and 1), the press edge clears once per fixed-update tick, which always runs before that frame's `render()`. `BTN_L2`, `BTN_R2` and `BTN_TRIGGER` are gamepad only (no keyboard mapping).
@since1.1.1@changed1.8.0 Also accepts BTN_L2 / BTN_R2 / BTN_TRIGGER (gamepad only).@parambutton - Button constant from the `BTN_*` set.@paramplayer - Zero-based player index for gamepads, or pointer slot (0-3) for `BTN_POINTER_*`.@paramrepeatRate - Optional repeat interval in fixed ticks (`0`/omitted = edge only).@returns`true` on the transition frame.
isPressed
(
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_POINTER_C: number
Tertiary pointer button code. Maps to mouse middle for slot 0 (matches RetroBlit canonical, not the DOM `PointerEvent.button` index where 1 is middle and 2 is right). Always `false` for touch slots 1-3.
@since0.1.0
BTN_POINTER_C
, 0)) {
this.Demo.cycleBrush(): voidcycleBrush(); } // Walk the four slots. Slot 0 paints while BTN_POINTER_A is held; slots 1-3 paint // while their touch is in contact (slot is active). for (let let slot: numberslot = 0; let slot: numberslot < 4; let slot: numberslot++) { const const valid: booleanvalid =
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
.isPointerActive: (pointerIndex?: number) => boolean
Reports whether the given pointer slot has a live pointer. For slot 0 (mouse) this is true while the mouse is hovering inside the canvas; cleared on `pointerleave`. For slots 1-3 (touch / pen) this is true while the contact is down.
@since1.1.1@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returns`true` while the slot has live position data.
isPointerActive
(let slot: numberslot);
// For slot 0 (mouse) painting is gated on the left button. For touch slots // there is only one button (A); the mere presence of a contact is enough. const const wantPaint: booleanwantPaint = let slot: numberslot === 0 ?
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) => boolean
Checks 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).
@since1.1.1@changed1.8.0 Also accepts BTN_L2 / BTN_R2 / BTN_TRIGGER (gamepad only).@parambutton - Button constant from the `BTN_*` set.@paramplayer - Zero-based player index for gamepads / keyboard, or pointer slot (0-3) for `BTN_POINTER_*`.@returns`true` while the button remains pressed.
isDown
(
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_POINTER_A: number
Primary pointer button code. Maps to mouse left for slot 0; touch contact for slots 1-3.
@since0.1.0
BTN_POINTER_A
, 0) && const valid: booleanvalid : const valid: booleanvalid;
if (!const wantPaint: booleanwantPaint) { this.Demo.painting: {}painting[let slot: numberslot] = false; continue; } const const pos: Vector2ipos =
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
.pointerPos: (pointerIndex?: number) => Vector2i
Returns the position of the pointer in the given slot, in display coordinates. Slot 0 is the mouse; slots 1 through 3 are touch / pen contacts assigned in arrival order. Returns `Vector2i.zero()` when the engine has not been initialized, the slot index is out of `[0, 3]`, or the slot has no live pointer.
@since1.0.3@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returnsPointer position in display coordinates.
pointerPos
(let slot: numberslot);
// Never paint underneath the UI. Without this check, tapping the Clear button // would also drop a dot of paint under the button, because a tap is a pointer // contact like any other. Marking the slot as "not painting" also re-seeds // the stroke start when the pointer leaves the widget again, so no straight // line gets drawn through the area the panel covers. if (import uiui.overWidget(const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
, const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
)) {
this.Demo.painting: {}painting[let slot: numberslot] = false; continue; } if (!this.Demo.painting: {}painting[let slot: numberslot]) { // Just started painting - seed the last position so the first stamp does // not draw a line all the way from (0, 0). this.Demo.lastPosX: {}lastPosX[let slot: numberslot] = const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
;
this.Demo.lastPosY: {}lastPosY[let slot: numberslot] = const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
;
this.Demo.painting: {}painting[let slot: numberslot] = true; } // Stamp from the previous position to the current one. This is what makes // fast strokes look continuous instead of dotted. this.Demo.stamp(x0: any, y0: any, x1: any, y1: any, color: any): voidstamp(this.Demo.lastPosX: {}lastPosX[let slot: numberslot], this.Demo.lastPosY: {}lastPosY[let slot: numberslot], const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
, const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
, const SLOT_PAINT: {}SLOT_PAINT[let slot: numberslot]);
this.Demo.lastPosX: {}lastPosX[let slot: numberslot] = const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
;
this.Demo.lastPosY: {}lastPosY[let slot: numberslot] = const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
;
} } // Per-frame render: paint layer first, then the kit control panel, the hint, and // the cursors last, so a cursor is never hidden behind the panel. Demo.render(): voidrender() { // Clear to the shared theme's background color so the canvas matches the UI panel // and the engine overlay.
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) => void
Sets 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.
@since0.1.0@parampaletteIndex - Palette index for the full-screen clear pass.
clear
(this.Demo.theme: nulltheme.bg);
this.Demo.renderLayer(): voidrenderLayer(); this.Demo.renderPanel(): voidrenderPanel(); this.Demo.renderHint(): voidrenderHint(); this.Demo.renderCursors(): voidrenderCursors(); } // Wipes every painted pixel back to blank. Shared by right-click and the Clear button // so both inputs behave identically. Demo.clearCanvas(): voidclearCanvas() { this.Demo.layer: any
@type{Uint8Array | null}
layer
.fill(0);
this.Demo.hasPaint: booleanhasPaint = false; } // Steps to the next brush size. Shared by middle-click and the Brush button so both // inputs behave identically. Demo.cycleBrush(): voidcycleBrush() { // The % (remainder) operator wraps the index around: after the last brush it // lands back on 0, like a clock rolling over from 12 to 1. this.Demo.brushIndex: numberbrushIndex = (this.Demo.brushIndex: numberbrushIndex + 1) % const BRUSH_SIZES: {}BRUSH_SIZES.length; } // Stamps the current brush along the line segment from (x0, y0) to (x1, y1), writing // palette index `color` into the paint layer at every covered pixel. Uses a simple // step-by-distance walker (good enough for small distances): for each step, paint a // filled disc whose radius is the current brush. Demo.stamp(x0: any, y0: any, x1: any, y1: any, color: any): voidstamp(x0: anyx0, y0: anyy0, x1: anyx1, y1: anyy1, color: anycolor) { const const dx: numberdx = x1: anyx1 - x0: anyx0; const const dy: numberdy = y1: anyy1 - y0: anyy0; const const distance: anydistance = Math.max(1, Math.ceil(Math.hypot(const dx: numberdx, const dy: numberdy))); for (let let i: numberi = 0; let i: numberi <= const distance: anydistance; let i: numberi++) { const const t: numbert = let i: numberi / const distance: anydistance; const const x: anyx = Math.round(x0: anyx0 + const dx: numberdx * const t: numbert); const const y: anyy = Math.round(y0: anyy0 + const dy: numberdy * const t: numbert); this.Demo.stampAt(cx: any, cy: any, color: any): voidstampAt(const x: anyx, const y: anyy, color: anycolor); } } // Paints a filled disc of the current brush radius centered on (cx, cy). Demo.stampAt(cx: any, cy: any, color: any): voidstampAt(cx: anycx, cy: anycy, color: anycolor) { const const radius: anyradius = const BRUSH_SIZES: {}BRUSH_SIZES[this.Demo.brushIndex: numberbrushIndex]; if (const radius: anyradius === 0) { this.Demo.setPixel(x: any, y: any, color: any): voidsetPixel(cx: anycx, cy: anycy, color: anycolor); return; } // A pixel is inside the disc when its squared distance from the center is at most // the squared radius - comparing squares avoids a square root per pixel. const const r2: numberr2 = const radius: anyradius * const radius: anyradius; for (let let dy: numberdy = -const radius: anyradius; let dy: numberdy <= const radius: anyradius; let dy: numberdy++) { for (let let dx: numberdx = -const radius: anyradius; let dx: numberdx <= const radius: anyradius; let dx: numberdx++) { if (let dx: numberdx * let dx: numberdx + let dy: numberdy * let dy: numberdy <= const r2: numberr2) { this.Demo.setPixel(x: any, y: any, color: any): voidsetPixel(cx: anycx + let dx: numberdx, cy: anycy + let dy: numberdy, color: anycolor); } } } } // Writes a palette index into the layer, ignoring out-of-bounds writes. Demo.setPixel(x: any, y: any, color: any): voidsetPixel(x: anyx, y: anyy, color: anycolor) { if (x: anyx < 0 || x: anyx >= const DISPLAY_W: 320DISPLAY_W || y: anyy < 0 || y: anyy >= const DISPLAY_H: 240DISPLAY_H) { return; } this.Demo.layer: any
@type{Uint8Array | null}
layer
[y: anyy * const DISPLAY_W: 320DISPLAY_W + x: anyx] = color: anycolor;
this.Demo.hasPaint: booleanhasPaint = true; } // Copies the persistent paint layer onto the screen. Pixels with palette index 0 are // skipped so the background color shows through. Drawing one pixel at a time would // use up the engine's per-frame drawing budget once a lot of the canvas is painted. // Instead we walk each row and draw every run of same-colored pixels as ONE thin // rectangle, which costs the same no matter how long the run is. Demo.renderLayer(): voidrenderLayer() { for (let let y: numbery = 0; let y: numbery < const DISPLAY_H: 240DISPLAY_H; let y: numbery++) { const const row: numberrow = let y: numbery * const DISPLAY_W: 320DISPLAY_W; let let x: numberx = 0; while (let x: numberx < const DISPLAY_W: 320DISPLAY_W) { const const c: anyc = this.Demo.layer: any
@type{Uint8Array | null}
layer
[const row: numberrow + let x: numberx];
// Blank pixel: nothing to draw, move on. if (const c: anyc === 0) { let x: numberx++; continue; } // Find where this run of identical colors ends. const const start: numberstart = let x: numberx; while (let x: numberx < const DISPLAY_W: 320DISPLAY_W && this.Demo.layer: any
@type{Uint8Array | null}
layer
[const row: numberrow + let x: numberx] === const c: anyc) {
let x: numberx++; }
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
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(this.Demo.runRect: Rect2irunRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(const start: numberstart, let y: numbery, let x: numberx - const start: numberstart, 1), const c: anyc);
} } } // Draws a small crosshair at each active pointer so the user can see where each // finger or the mouse is, even when not currently painting. Demo.renderCursors(): voidrenderCursors() { const const span: numberspan = const CURSOR_ARM: 5CURSOR_ARM * 2 + 1; for (let let slot: numberslot = 0; let slot: numberslot < 4; let slot: numberslot++) { 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
.isPointerActive: (pointerIndex?: number) => boolean
Reports whether the given pointer slot has a live pointer. For slot 0 (mouse) this is true while the mouse is hovering inside the canvas; cleared on `pointerleave`. For slots 1-3 (touch / pen) this is true while the contact is down.
@since1.1.1@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returns`true` while the slot has live position data.
isPointerActive
(let slot: numberslot)) {
continue; } const const pos: Vector2ipos =
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
.pointerPos: (pointerIndex?: number) => Vector2i
Returns the position of the pointer in the given slot, in display coordinates. Slot 0 is the mouse; slots 1 through 3 are touch / pen contacts assigned in arrival order. Returns `Vector2i.zero()` when the engine has not been initialized, the slot index is out of `[0, 3]`, or the slot has no live pointer.
@since1.0.3@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returnsPointer position in display coordinates.
pointerPos
(let slot: numberslot);
const const color: anycolor = const SLOT_PAINT: {}SLOT_PAINT[let slot: numberslot]; // Two 1-pixel-thick arms through the pointer position, in the slot's paint // color. They are drawn with ui.drawRectFill(), the sprite version of // BT.drawRectFill(): the engine draws every BT.drawLine() and // BT.drawRectFill() before any sprite, and the kit panel is a sprite, so a // plain line would vanish whenever the pointer moved over the panel. import uiui.drawRectFill(this.Demo.uiRect: Rect2iuiRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
- const CURSOR_ARM: 5CURSOR_ARM, const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
, const span: numberspan, 1), const color: anycolor);
import uiui.drawRectFill(this.Demo.uiRect: Rect2iuiRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
, const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
- const CURSOR_ARM: 5CURSOR_ARM, 1, const span: numberspan), const color: anycolor);
} } // Two dim lines in the middle of the open canvas saying how to paint. Shown only // while nothing is painted, so a new visitor is not left facing an empty screen. Demo.renderHint(): voidrenderHint() { if (this.Demo.hasPaint: booleanhasPaint) { return; } // The open canvas is everything left of the panel. Every character of the system // font is FONT_W (6) pixels wide, so starting a line half its width left of the // middle centers it. const const centerX: anycenterX = Math.floor(const PANEL_X: numberPANEL_X / 2); const const centerY: anycenterY = Math.floor(const DISPLAY_H: 240DISPLAY_H / 2); const const lines: {}lines = ['Hold the left mouse button', 'or touch the screen to paint']; for (let let i: numberi = 0; let i: numberi < const lines: {}lines.length; let i: numberi++) { import uiui.caption(const centerX: anycenterX - (const lines: {}lines[let i: numberi].length * import FONT_WFONT_W) / 2, const centerY: anycenterY - 8 + let i: numberi * 12, const lines: {}lines[let i: numberi], { color: stringcolor: 'dim' }); } } // The kit control panel: per-slot activity pips with each slot's paint color, and // two touch-friendly buttons. It sits in the bottom-right corner so it stays clear of // the engine overlay's toggle corner (the bottom-left 17x13 pixels are reserved for // that). Demo.renderPanel(): voidrenderPanel() { const const buttonW: numberbuttonW = const PANEL_W: 112PANEL_W - 2 * const PANEL_PAD: 9PANEL_PAD; import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const PANEL_X: numberPANEL_X, y: numbery: const PANEL_Y: numberPANEL_Y, width: numberwidth: const PANEL_W: 112PANEL_W }); import uiui.panel('Paint'); // One pip per pointer slot: lit while that mouse or finger is active. for (let let slot: numberslot = 0; let slot: numberslot < 4; let slot: numberslot++) { import uiui.pip(const SLOT_LABELS: {}SLOT_LABELS[let slot: numberslot],
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
.isPointerActive: (pointerIndex?: number) => boolean
Reports whether the given pointer slot has a live pointer. For slot 0 (mouse) this is true while the mouse is hovering inside the canvas; cleared on `pointerleave`. For slots 1-3 (touch / pen) this is true while the contact is down.
@since1.1.1@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returns`true` while the slot has live position data.
isPointerActive
(let slot: numberslot));
} // A small gap between the pips and the buttons. import uiui.spacer(5); // The current brush, as a readable name on the Brush button itself. const const name: anyname = const BRUSH_NAMES: {}BRUSH_NAMES[this.Demo.brushIndex: numberbrushIndex]; // The Brush button cycles the size - the touch equivalent of the middle-click // shortcut in update(). Its label changes with the brush, and the kit normally // recognizes a widget by its label, so we give it a stable id to keep it the // "same" button across frames. if (import uiui.button(`Brush: ${const name: anyname}`, { id: stringid: 'brush', width: numberwidth: const buttonW: numberbuttonW })) { this.Demo.cycleBrush(): voidcycleBrush(); } // The Clear button wipes the canvas - the touch equivalent of the right-click // shortcut in update(). Both paths call clearCanvas(). if (import uiui.button('Clear', { width: numberwidth: const buttonW: numberbuttonW })) { this.Demo.clearCanvas(): voidclearCanvas(); } import uiui.end(); // A swatch of each slot's paint color at the right end of its pip row, so you // can see which color each finger paints in. Each swatch mirrors the pip box on // the left: the kit draws that box PIP_SIZE (10) pixels square with a border // outline, 3 pixels below the top of its row, PANEL_PAD pixels in from the panel // edge - so the swatch is the same outline the same distance in from the right // edge, on the same rows, with the color filling its inside. Drawn after ui.end() // with the kit's sprite drawing so it lands on top of the panel. const const swatchX: numberswatchX = const PANEL_X: numberPANEL_X + const PANEL_W: 112PANEL_W - const PANEL_PAD: 9PANEL_PAD - import PIP_SIZEPIP_SIZE; for (let let slot: numberslot = 0; let slot: numberslot < 4; let slot: numberslot++) { const const boxY: numberboxY = const PANEL_Y: numberPANEL_Y + const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP + let slot: numberslot * 10 + 3; import uiui.drawRect(this.Demo.uiRect: Rect2iuiRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(const swatchX: numberswatchX, const boxY: numberboxY, import PIP_SIZEPIP_SIZE, import PIP_SIZEPIP_SIZE), this.Demo.theme: nulltheme.border);
import uiui.drawRectFill(this.Demo.uiRect: Rect2iuiRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(const swatchX: numberswatchX + 1, const boxY: numberboxY + 1, import PIP_SIZEPIP_SIZE - 2, import PIP_SIZEPIP_SIZE - 2), const SLOT_PAINT: {}SLOT_PAINT[let slot: numberslot]);
} } } 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.
@since0.2.0@changed1.4.0 Calling `bootstrap()` again while already initialized now routes to a hot swap (via {@link registerHotReload}) when a Vite HMR context is registered, or logs a double-bootstrap guard and returns `false` otherwise - previously it silently started a second, unstoppable `GameLoop`.@changed1.7.0 Exposes `BT` on `window.BT` after bootstrap finishes, gated by {@link BootstrapOptions.exposeGlobal} (default: {@link BT.isDevMode}).@paramDemoClass - Demo class constructor implementing `IBTDemo` (optional `configure()` for hardware settings).@paramoptions - Optional configuration for IDs and callbacks.@returns`true` when the demo boots successfully; otherwise `false`.@example// Simplest usage - uses default IDs. bootstrap(MyDemo);@example// With custom options. bootstrap(MyDemo, { canvasID: 'custom-canvas', containerID: 'custom-container', onSuccess: () => console.log('Demo started!'), onError: (err) => analytics.trackError(err), });@example// Await the result. const success = await bootstrap(MyDemo); if (success) { console.log('Demo is running'); }
bootstrap
(class DemoDemo);