/**
* Pointer Paint Demo - multi-touch finger painting with mouse + up to 3 touches.
* @description Multi-touch finger painting across all four pointer slots: a mouse plus up to three fingers at once.
*
* Prerequisites: Pointer Basics - https://demos.blit386.dev/pointer-basics
*
* Live version: https://demos.blit386.dev/pointer-paint
*
* This demo shows how all four pointer slots work side by side. Each slot
* paints in its own color:
* slot 0 = mouse (white)
* slot 1 = first touch (red)
* slot 2 = second touch (green)
* slot 3 = third touch (blue)
*
* Mouse: hold the left button (BTN_POINTER_A) to paint. Right-click
* (BTN_POINTER_B) clears the canvas. Middle-click (BTN_POINTER_C) cycles the
* brush size between three preset thicknesses.
*
* Touch: each finger paints automatically while in contact. Up to three touches
* are tracked at once; a fourth simultaneous touch is dropped silently. Because
* touch devices have no right or middle button, the shared UI kit
* (src/shared/ui.js) draws a small panel with a Clear button and a Brush button
* that do exactly the same thing as the mouse shortcuts - so the whole demo
* works with fingers alone.
*
* What this demonstrates:
* - BT.isPressed() for one-shot mouse actions (clear canvas, cycle brush size)
* - BT.isDown(BT.BTN_POINTER_A) while BT.isPointerActive(0) for mouse painting
* - BT.isPointerActive(slot) / BT.pointerPos(slot) for per-slot touch painting
* - lastPosX / lastPosY per-slot stamping: draws from the previous frame's
* position to the current one so fast strokes look continuous instead of dotted
* - ui.overWidget() to keep brush strokes from landing underneath the UI panel
*
* The painting happens on an offscreen palette layer (a 2D array of palette
* indices) so brush strokes persist across frames even though render() clears
* to a background color first.
*/
import { function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>One-liner bootstrap function for BLIT386 demos.
Handles canvas retrieval and engine initialization. Backend selection
(WebGPU or software fallback) is managed internally by BTAPI.
This function provides a streamlined way to start a demo with sensible defaults
while allowing customization through options.bootstrap, const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT, class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32, class Vector2iInteger 2D vector for pixel-perfect positioning.
Used for points, sizes, directions, and camera offsets throughout the engine.
The API includes both allocation-free `*To()` / `*InPlace()` variants and
convenience methods that return new vectors.Vector2i } from 'blit386';
import { import applyThemeapplyTheme, import THEME_DEFAULT_START_SLOTTHEME_DEFAULT_START_SLOT, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js';
/** @typedef {import('blit386').IBTDemo} IBTDemo */
/** @typedef {import('blit386').HardwareSettings} HardwareSettings */
/** @typedef {import('blit386').Palette} Palette */
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'];
/**
* Multi-touch / 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 with
* BT.drawPixel() so strokes persist between frames. Stroke input comes from
* checking BT.isPointerActive() / BT.isDown() / BT.pointerPos() on each
* of the four slots in update().
*
* @implements {IBTDemo}
*/
class class DemoMulti-touch / 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 with
BT.drawPixel() so strokes persist between frames. Stroke input comes from
checking BT.isPointerActive() / BT.isDown() / BT.pointerPos() on each
of the four slots in update().Demo {
/** @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;
// Last known position per slot, recorded the frame the pointer became
// active or last had its button pressed. Used to draw a stroke from the
// previous frame's position to the current one, which fills gaps when the
// pointer moves faster than one pixel per frame.
Demo.lastPosX: {}lastPosX = [0, 0, 0, 0];
Demo.lastPosY: {}lastPosY = [0, 0, 0, 0];
// True while we should be painting from this slot. For mouse this is
// BTN_POINTER_A held. For touch this is "slot is valid" (contact down).
Demo.painting: {}painting = [false, false, false, false];
/**
* Finger painting can spike render() when strokes are long; the chart makes that visible.
*
* @returns {Partial<HardwareSettings>}
*/
Demo.configure(): Partial<HardwareSettings>Finger painting can spike render() when strokes are long; the chart makes that visible.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 + scene paint colors) and allocates
* the offscreen paint layer.
*
* @returns {Promise<boolean>}
*/
async Demo.init(): Promise<boolean>Sets up the palette (shared UI theme + scene paint colors) and allocates
the offscreen paint layer.init() {
this.Demo.palette: Palette | nullpalette = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.paletteCreate: (size?: number) => PaletteCreates a standalone palette instance.paletteCreate(256);
// 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;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.paletteSet: (palette: Palette) => voidStores the active engine palette.
Use this to swap the **entire palette** (e.g. switch between a day and night
theme). After this call the renderer uploads the new palette uniform on the
next frame.
**Palette-value swap (change what a slot looks like):** mutate the live
{@link
BT.palette
}
in place with `palette.set(slot, newColor)`. The renderer
uploads dirty slots on the next frame; no `paletteSet()` or
{@link
BT.spritesRefresh
}
needed.
**Palette-layout swap (same colors, different slot positions):** build a new
palette with the same colors at new indices, call `paletteSet()`, then call
{@link
BT.spritesRefresh
}
so every sprite sheet re-maps its original RGBA
pixels against the new slot layout.paletteSet(this.Demo.palette: Palettepalette);
// 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;
... 106 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. `fill(0)` makes every pixel start blank
// (transparent) so 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 layer.
*/
Demo.update(): voidPer-tick: read input from each slot and write strokes into layer.update() {
// 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 (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;
... 106 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()`.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;
... 106 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(): voidWipes every painted pixel back to blank. Shared by right-click and the
Clear button so both inputs behave identically.clearCanvas();
}
if (const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.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()`.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;
... 106 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(): voidSteps to the next brush size. Shared by middle-click and the Brush
button so both inputs behave identically.cycleBrush();
}
// 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 valid).
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;
... 106 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;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isDown: (button: number, player?: number) => booleanChecks whether a button is currently held.
For pointer buttons (`BTN_POINTER_A..D`), the second parameter is the
pointer slot index (0 = mouse, 1-3 = touch / pen). For mouse slot 0:
`A` is left, `B` is right, `C` is middle, `D` is back / forward
(matches RetroBlit canonical, not DOM `PointerEvent.button` index).
Touch / pen slots only support `A`; B/C/D return `false`.
`button` accepts one or more bit flags from the `BTN_*` set (for example
`BT.BTN_A | BT.BTN_B`). Matching uses ANY semantics: returns `true` when
any selected button is held.
For face buttons (`BTN_UP`…`BTN_SELECT`), players `0` and `1` merge keyboard
and gamepad input (logical OR). Players `2` and `3` use gamepad only.
Pointer flags (`BTN_POINTER_*`) use the `player` argument as pointer slot.isDown(const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_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;
... 106 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 last position so the first
// stamp doesn't 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 previous position to 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): voidStamps 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.stamp(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 live overlays (cursors and the
* kit control panel).
*/
Demo.render(): voidPer-frame render: paint layer first, then live overlays (cursors and the
kit control panel).render() {
// 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;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.clear: (paletteIndex: number) => voidSets the frame clear color using a palette index.
The renderer uses this color when clearing the full display at the start
of the next frame.clear(this.Demo.theme: nulltheme.bg);
this.Demo.renderLayer(): voidCopies the persistent paint layer onto the screen. Pixels with palette
index 0 are skipped so the background color shows through.renderLayer();
this.Demo.renderCursors(): voidDraws a small ring around each active pointer so the user can see where
each finger / mouse is, even when not currently painting.renderCursors();
this.Demo.renderPanel(): voidThe kit control panel: per-slot activity pips, the current brush, and
two touch-friendly buttons. Anchored bottom-right so it stays clear of
the engine overlay's toggle corner (the bottom-left 17x13 pixels are
reserved for that).renderPanel();
}
/**
* Wipes every painted pixel back to blank. Shared by right-click and the
* Clear button so both inputs behave identically.
*/
Demo.clearCanvas(): voidWipes every painted pixel back to blank. Shared by right-click and the
Clear button so both inputs behave identically.clearCanvas() {
this.Demo.layer: anylayer.fill(0);
}
/**
* Steps to the next brush size. Shared by middle-click and the Brush
* button so both inputs behave identically.
*/
Demo.cycleBrush(): voidSteps to the next brush size. Shared by middle-click and the Brush
button so both inputs behave identically.cycleBrush() {
// 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): voidStamps 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.stamp(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): voidPaints a filled disc of the current brush radius centered on (cx, cy).stampAt(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): voidPaints a filled disc of the current brush radius centered on (cx, cy).stampAt(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): voidWrites a palette index into layer, ignoring out-of-bounds writes.setPixel(cx: anycx, cy: anycy, color: anycolor);
return;
}
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): voidWrites a palette index into layer, ignoring out-of-bounds writes.setPixel(cx: anycx + let dx: numberdx, cy: anycy + let dy: numberdy, color: anycolor);
}
}
}
}
/**
* Writes a palette index into layer, ignoring out-of-bounds writes.
*/
Demo.setPixel(x: any, y: any, color: any): voidWrites a palette index into layer, ignoring out-of-bounds writes.setPixel(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;
}
/**
* Copies the persistent paint layer onto the screen. Pixels with palette
* index 0 are skipped so the background color shows through.
*/
Demo.renderLayer(): voidCopies the persistent paint layer onto the screen. Pixels with palette
index 0 are skipped so the background color shows through.renderLayer() {
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;
for (let let x: numberx = 0; let x: numberx < const DISPLAY_W: 320DISPLAY_W; let x: numberx++) {
const const c: anyc = this.Demo.layer: anylayer[const row: numberrow + let x: numberx];
if (const c: anyc !== 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;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.drawPixel: (posOrX: Vector2i | number, yOrColor: number, maybeColor?: number) => voidDraws a single pixel.
Accepts either:
- `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index.
- `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.drawPixel(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(let x: numberx, let y: numbery), const c: anyc);
}
}
}
}
/**
* Draws a small ring around each active pointer so the user can see where
* each finger / mouse is, even when not currently painting.
*/
Demo.renderCursors(): voidDraws a small ring around each active pointer so the user can see where
each finger / mouse is, even when not currently painting.renderCursors() {
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;
... 106 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;
... 106 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];
// Crosshair that doesn't depend on a circle primitive.
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.drawLine: (p0: Vector2i, p1: Vector2i, paletteIndex: number) => voidDraws a pixel-perfect line between two points.
Uses rasterized line drawing without antialiasing.drawLine(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const pos: Vector2ipos.Vector2i.x: numberHorizontal component (defaults to 0).x - 5, const pos: Vector2ipos.Vector2i.y: numberVertical component (defaults to 0).y), new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const pos: Vector2ipos.Vector2i.x: numberHorizontal component (defaults to 0).x + 5, const pos: Vector2ipos.Vector2i.y: numberVertical component (defaults to 0).y), const color: anycolor);
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.drawLine: (p0: Vector2i, p1: Vector2i, paletteIndex: number) => voidDraws a pixel-perfect line between two points.
Uses rasterized line drawing without antialiasing.drawLine(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const pos: Vector2ipos.Vector2i.x: numberHorizontal component (defaults to 0).x, const pos: Vector2ipos.Vector2i.y: numberVertical component (defaults to 0).y - 5), new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const pos: Vector2ipos.Vector2i.x: numberHorizontal component (defaults to 0).x, const pos: Vector2ipos.Vector2i.y: numberVertical component (defaults to 0).y + 5), const color: anycolor);
}
}
/**
* The kit control panel: per-slot activity pips, the current brush, and
* two touch-friendly buttons. Anchored bottom-right so it stays clear of
* the engine overlay's toggle corner (the bottom-left 17x13 pixels are
* reserved for that).
*/
Demo.renderPanel(): voidThe kit control panel: per-slot activity pips, the current brush, and
two touch-friendly buttons. Anchored bottom-right so it stays clear of
the engine overlay's toggle corner (the bottom-left 17x13 pixels are
reserved for that).renderPanel() {
import uiui.begin(import UI_ANCHORSUI_ANCHORS.BOTTOM_RIGHT);
import uiui.panel('Paint');
// One pip per pointer slot: lit while that mouse / finger is active.
// This is the same per-slot status the old hand-drawn panel showed.
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;
... 106 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));
}
import uiui.separator();
// Which brush is selected right now, as a readable name.
const const name: anyname = const BRUSH_NAMES: {}BRUSH_NAMES[this.Demo.brushIndex: numberbrushIndex];
import uiui.kv('Brush', const name: anyname);
// 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' })) {
this.Demo.cycleBrush(): voidSteps to the next brush size. Shared by middle-click and the Brush
button so both inputs behave identically.cycleBrush();
}
// 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')) {
this.Demo.clearCanvas(): voidWipes every painted pixel back to blank. Shared by right-click and the
Clear button so both inputs behave identically.clearCanvas();
}
import uiui.end();
}
}
function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>One-liner bootstrap function for BLIT386 demos.
Handles canvas retrieval and engine initialization. Backend selection
(WebGPU or software fallback) is managed internally by BTAPI.
This function provides a streamlined way to start a demo with sensible defaults
while allowing customization through options.bootstrap(class DemoMulti-touch / 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 with
BT.drawPixel() so strokes persist between frames. Stroke input comes from
checking BT.isPointerActive() / BT.isDown() / BT.pointerPos() on each
of the four slots in update().Demo);