/*
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.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 Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32, class Rect2iInteger 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.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 | nullpalette = 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: anylayer = 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): Rect2iCreates an integer rectangle, truncating all inputs toward zero.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): Rect2iCreates an integer rectangle, truncating all inputs toward zero.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 | nullpalette = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.paletteCreate: (size?: number) => PaletteCreates a standalone palette instance.paletteCreate(256);
// 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: Palettepalette);
// 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: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const SLOT_PAINT: {}SLOT_PAINT[0], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(240, 240, 240));
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const SLOT_PAINT: {}SLOT_PAINT[1], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 100, 100));
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const SLOT_PAINT: {}SLOT_PAINT[2], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(100, 220, 120));
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const SLOT_PAINT: {}SLOT_PAINT[3], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.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) => 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);
// 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: () => voidHides 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.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: anylayer = 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) => booleanChecks 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).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: numberSecondary 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.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) => booleanChecks 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).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: numberTertiary 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.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) => booleanReports 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.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) => booleanChecks whether a button is currently held.
For pointer buttons (`BTN_POINTER_A..D`), the second parameter is the
pointer slot index (0 = mouse, 1-3 = touch / pen). For mouse slot 0:
`A` is left, `B` is right, `C` is middle, `D` is back / forward
(matches RetroBlit canonical, not DOM `PointerEvent.button` index).
Touch / pen slots only support `A`; B/C/D return `false`.
`button` accepts one or more bit flags from the `BTN_*` set (for example
`BT.BTN_A | BT.BTN_B`). Matching uses ANY semantics: returns `true` when
any selected button is held.
For face buttons (`BTN_UP`…`BTN_SELECT`), players `0` and `1` merge keyboard
and gamepad input (logical OR). Players `2` and `3` use gamepad only.
Pointer flags (`BTN_POINTER_*`) use the `player` argument as pointer slot.
`BTN_L2`, `BTN_R2` and `BTN_TRIGGER` are gamepad only (no keyboard mapping).isDown(const 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: numberPrimary pointer button code.
Maps to mouse left for slot 0; touch contact for slots 1-3.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) => Vector2iReturns 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.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: numberHorizontal component (defaults to 0).x, const pos: Vector2ipos.Vector2i.y: numberVertical 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: numberHorizontal component (defaults to 0).x;
this.Demo.lastPosY: {}lastPosY[let slot: numberslot] = const pos: Vector2ipos.Vector2i.y: numberVertical 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: numberHorizontal component (defaults to 0).x, const pos: Vector2ipos.Vector2i.y: numberVertical 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: numberHorizontal component (defaults to 0).x;
this.Demo.lastPosY: {}lastPosY[let slot: numberslot] = const pos: Vector2ipos.Vector2i.y: numberVertical 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) => 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: 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: anylayer.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: anylayer[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: anylayer[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: anylayer[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) => voidDraws a filled rectangle.drawRectFill(this.Demo.runRect: Rect2irunRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2iSets all components of this rectangle.
Modifies this rectangle directly for maximum performance.
WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.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) => booleanReports 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.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) => Vector2iReturns 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.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): Rect2iSets all components of this rectangle.
Modifies this rectangle directly for maximum performance.
WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.set(const pos: Vector2ipos.Vector2i.x: numberHorizontal component (defaults to 0).x - const CURSOR_ARM: 5CURSOR_ARM, const pos: Vector2ipos.Vector2i.y: numberVertical 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): Rect2iSets all components of this rectangle.
Modifies this rectangle directly for maximum performance.
WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.set(const pos: Vector2ipos.Vector2i.x: numberHorizontal component (defaults to 0).x, const pos: Vector2ipos.Vector2i.y: numberVertical 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) => booleanReports 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.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): Rect2iSets all components of this rectangle.
Modifies this rectangle directly for maximum performance.
WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.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): Rect2iSets all components of this rectangle.
Modifies this rectangle directly for maximum performance.
WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.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.bootstrap(class DemoDemo);