/*
Named Colors: the Color32 named color registry and custom registration.
@description The Color32 named color registry: resolve a name to a color, register your own, and update it.
Part of the BLIT386 demo series.
Prerequisites:
- Basics: https://demos.blit386.dev/basics
- Colors: https://demos.blit386.dev/colors
Live version: https://demos.blit386.dev/named-colors
Guides:
- Overlay: https://blit386.dev/docs/guides/overlay#optional-bands
Reference:
- Core Types: https://blit386.dev/docs/api/core-types#color32
- Overlay: https://blit386.dev/docs/api/overlay#event-tags
---
WHAT YOU WILL SEE
-----------------
A deep navy screen with a full-width title strip across the top that reads "Built-in
lookups + register / update / unregister". Below it, two UI kit panels share one width.
The "Named colors" panel holds four color swatches in a row, each with its name and a dim
"built-in" or "custom" tag centered under it:
- tomato: the built-in named color "tomato", a warm red.
- cornflower: the built-in named color "cornflowerblue", a soft blue.
- dynamic: a custom name, "demo-dynamic", whose color slides back and forth between the
two built-ins.
- optional: a custom name, "demo-optional", a pink that disappears every 3 seconds. While
the name is unregistered the swatch turns gray.
Under the swatches, three lines name the two custom colors and report whether
"demo-optional" is currently registered (green) or missing (orange). A "Tips" panel below
lists three rules of the registry.
WHAT YOU WILL LEARN
-------------------
- Color32.resolveNamedColor(name) looks a name up in the global registry and returns the
Color32, or undefined when the name is unknown.
- Color32.registerColor(name, color) adds a new name. It throws if the name is taken.
- Color32.updateColor(name, color) changes an existing name's color. It throws if the name
is missing.
- Color32.unregisterColor(name) removes a name. It throws if the name is missing.
- Names are trimmed and lowercased before matching, so 'Tomato' and ' tomato ' both find
"tomato".
- Named colors are plain Color32 values: to draw with one, copy it into a palette slot in
update() and draw with the slot number in render().
- Color32.lerp() blends two colors, and BT.assignTag() drops a label on the overlay's
timing chart when something changes.
THINK OF THE REGISTRY AS A DICTIONARY
-------------------------------------
- The "word" is a name like "cornflowerblue".
- The "definition" is a Color32 value (r, g, b, a).
- resolveNamedColor(name) asks the dictionary: "Do you know this word?"
- registerColor() adds a new word, updateColor() rewrites a definition, and
unregisterColor() tears the page out.
TRY THIS
--------
- Watch the "optional" swatch and the status line below it: every 3 seconds the name is
unregistered (gray swatch, orange "missing" text) and 3 seconds later registered again
(pink swatch, green "registered" text).
- Press the backquote key (`) to open the engine overlay. The timing chart is enabled
here, and each toggle drops an "Optional registered" or "Optional removed" tag on it.
*/
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';
// The shared demo UI kit. applyTheme() installs the kit's twelve UI colors high in the
// palette (slots 240-251, far above this demo's slots 1-9), and ui.* draws the title
// strip, the captions, and the Tips panel.
import { import applyThemeapplyTheme, import FONT_WFONT_W, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js';
/** @typedef {import('blit386').Palette} Palette */
// Palette slots for this demo's own colors. Slot 0 stays transparent. Panels, captions,
// and status text draw with the shared UI theme instead (installed by applyTheme() in
// init()), so only two kinds of slots live down here: the overlay timing chart's bar
// colors, and the lesson's swatch slots.
//
// The chart slots exist because configure() runs BEFORE init() installs the theme, so the
// chart style cannot point at theme slots. init() copies the matching theme colors here.
const const C_CHART_UPDATE: 1C_CHART_UPDATE = 1; // timing chart: update() bar (the theme's blue-gray border)
const const C_CHART_RENDER: 2C_CHART_RENDER = 2; // timing chart: render() bar (the theme's off-white text)
const const C_CHART_TAG: 3C_CHART_TAG = 3; // timing chart: tag labels (the theme's green accent)
const const C_TOMATO: 5C_TOMATO = 5; // swatch slot for the built-in name "tomato"
const const C_CORNFLOWER: 6C_CORNFLOWER = 6; // swatch slot for the built-in name "cornflowerblue"
const const C_CUSTOM_DYNAMIC: 7C_CUSTOM_DYNAMIC = 7; // swatch slot for the animated custom name "demo-dynamic"
const const C_OPTIONAL: 8C_OPTIONAL = 8; // swatch slot for the toggled custom name "demo-optional"
const const C_OPTIONAL_FALLBACK: 9C_OPTIONAL_FALLBACK = 9; // gray shown while demo-optional is unregistered
// The two custom names this demo registers.
const const CUSTOM_DYNAMIC_NAME: "demo-dynamic"CUSTOM_DYNAMIC_NAME = 'demo-dynamic';
const const CUSTOM_OPTIONAL_NAME: "demo-optional"CUSTOM_OPTIONAL_NAME = 'demo-optional';
// The four swatches: which palette slot each one shows, its label, and whether the name
// behind it is built into the engine or one this demo registers.
const const SWATCHES: {}SWATCHES = [
{ slot: numberslot: const C_TOMATO: 5C_TOMATO, label: stringlabel: 'tomato', kind: stringkind: 'built-in' },
{ slot: numberslot: const C_CORNFLOWER: 6C_CORNFLOWER, label: stringlabel: 'cornflower', kind: stringkind: 'built-in' },
{ slot: numberslot: const C_CUSTOM_DYNAMIC: 7C_CUSTOM_DYNAMIC, label: stringlabel: 'dynamic', kind: stringkind: 'custom' },
{ slot: numberslot: const C_OPTIONAL: 8C_OPTIONAL, label: stringlabel: 'optional', kind: stringkind: 'custom' },
];
// Swatch size in pixels.
const const SWATCH_W: 56SWATCH_W = 56;
const const SWATCH_H: 24SWATCH_H = 24;
// Layout: two panels 4 pixels from the screen sides (312 pixels wide) and 4 pixels apart,
// centered in the space between the 22-pixel title strip and the bottom 13 pixels the
// engine overlay keeps for its toggle hint.
//
// - Named colors panel: 22 title + the swatch band (2 + 24 swatch + 3 + two 10-pixel
// caption lines + 6) + three 10-pixel status lines + 11 bottom.
// - Tips panel: 22 title + three 10-pixel lines + 11 bottom.
const const EDGE: 4EDGE = 4;
const const GAP: 4GAP = 4;
const const PANEL_W: numberPANEL_W = 320 - 2 * const EDGE: 4EDGE;
const const PANEL_PAD: 9PANEL_PAD = 9;
const const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP = 22;
const const SWATCH_BAND_H: numberSWATCH_BAND_H = 2 + const SWATCH_H: 24SWATCH_H + 3 + 2 * 10 + 6;
const const COLORS_PANEL_H: numberCOLORS_PANEL_H = const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP + const SWATCH_BAND_H: numberSWATCH_BAND_H + 3 * 10 + 11;
const const TIPS_PANEL_H: numberTIPS_PANEL_H = const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP + 3 * 10 + 11;
const const FREE_TOP: 22FREE_TOP = 22;
const const FREE_BOTTOM: numberFREE_BOTTOM = 240 - 13;
const const COLORS_PANEL_Y: anyCOLORS_PANEL_Y = const FREE_TOP: 22FREE_TOP + Math.floor((const FREE_BOTTOM: numberFREE_BOTTOM - const FREE_TOP: 22FREE_TOP - const COLORS_PANEL_H: numberCOLORS_PANEL_H - const GAP: 4GAP - const TIPS_PANEL_H: numberTIPS_PANEL_H) / 2);
const const TIPS_PANEL_Y: anyTIPS_PANEL_Y = const COLORS_PANEL_Y: anyCOLORS_PANEL_Y + const COLORS_PANEL_H: numberCOLORS_PANEL_H + const GAP: 4GAP;
// The swatches sit in four equal cells across the panel's content width.
const const CELL_W: numberCELL_W = (const PANEL_W: numberPANEL_W - 2 * const PANEL_PAD: 9PANEL_PAD) / const SWATCHES: {}SWATCHES.length;
// Shows built-in and custom named colors: four swatches, a live status line, and tips.
class class DemoDemo {
// The palette holds every color this demo draws with.
/** @type {Palette | null} */
Demo.palette: Palette | nullpalette = null;
// The palette slot numbers of the shared UI kit colors, filled in by applyTheme() in
// init(). We use them for the background, the swatch panel frame, and the swatch
// outlines and labels.
Demo.theme: nulltheme = null;
// Whether "demo-optional" is currently in the registry. update() flips it every 3
// seconds.
Demo.optionalRegistered: booleanoptionalRegistered = true;
// A clock that counts up in seconds, driving the blend and the 3-second toggle.
Demo.elapsed: numberelapsed = 0;
// Reused for every swatch rectangle drawn in render(), so drawing allocates no new
// Rect2i.
Demo.swatchRect: Rect2iswatchRect = new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2iCreates an integer rectangle, truncating all inputs toward zero.Rect2i(0, 0, 0, 0);
// Opt in to the overlay timing chart, with bar colors that match the UI theme.
Demo.configure(): {
isOverlayTimingChartEnabled: boolean;
overlayTimingChartStyle: {
updateBarPaletteIndex: number;
renderBarPaletteIndex: number;
tagPaletteIndex: number;
};
}
configure() {
return {
isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true,
overlayTimingChartStyle: {
updateBarPaletteIndex: number;
renderBarPaletteIndex: number;
tagPaletteIndex: number;
}
overlayTimingChartStyle: {
updateBarPaletteIndex: numberupdateBarPaletteIndex: const C_CHART_UPDATE: 1C_CHART_UPDATE,
renderBarPaletteIndex: numberrenderBarPaletteIndex: const C_CHART_RENDER: 2C_CHART_RENDER,
tagPaletteIndex: numbertagPaletteIndex: const C_CHART_TAG: 3C_CHART_TAG,
},
};
}
// Builds the palette and registers the custom named colors. Returns true when ready.
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);
// Overlay timing chart colors. These copy the shared theme's hex values into the
// low slots configure() pointed the chart style at (see the note on the C_CHART_*
// constants above for why the chart cannot use theme slots directly).
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_CHART_UPDATE: 1C_CHART_UPDATE, class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.fromHex(hex: string): Color32Parses a CSS-style hex color string.
Supports `#RGB`, `#RGBA`, `#RRGGBB`, and `#RRGGBBAA`.fromHex('#5a6480')); // theme border
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_CHART_RENDER: 2C_CHART_RENDER, class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.fromHex(hex: string): Color32Parses a CSS-style hex color string.
Supports `#RGB`, `#RGBA`, `#RRGGBB`, and `#RRGGBBAA`.fromHex('#e6ecf5')); // theme text
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_CHART_TAG: 3C_CHART_TAG, class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.fromHex(hex: string): Color32Parses a CSS-style hex color string.
Supports `#RGB`, `#RGBA`, `#RRGGBB`, and `#RRGGBBAA`.fromHex('#7dffa5')); // theme accent green
// Fallback gray for the moments when demo-optional is unregistered.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_OPTIONAL_FALLBACK: 9C_OPTIONAL_FALLBACK, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(80, 80, 80));
// Install the shared UI kit colors. They land in slots 240-251, far above the
// swatch slots this demo animates (5-9), so the two can never collide. The
// returned map remembers which slot each UI color went to (theme.bg, theme.text,
// ...).
this.Demo.theme: nulltheme = import applyThemeapplyTheme(this.Demo.palette: Palettepalette);
// Remove old custom names before registering fresh ones. Hot reload can leave
// names in memory from a previous run, and registerColor() throws when a name
// already exists.
this.Demo.removeIfExists(name: any): voidremoveIfExists(const CUSTOM_DYNAMIC_NAME: "demo-dynamic"CUSTOM_DYNAMIC_NAME);
this.Demo.removeIfExists(name: any): voidremoveIfExists(const CUSTOM_OPTIONAL_NAME: "demo-optional"CUSTOM_OPTIONAL_NAME);
// registerColor() adds a NEW entry to the global name table. It throws if the
// name is already taken, which is why we cleared it above. 'demo-dynamic' gets
// rewritten every tick with updateColor() in update().
class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.registerColor(name: string, color: Color32): voidRegisters a named color in the global color registry.
Name matching is case-insensitive and trims surrounding whitespace.registerColor(const CUSTOM_DYNAMIC_NAME: "demo-dynamic"CUSTOM_DYNAMIC_NAME, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(90, 170, 255));
// 'demo-optional' starts registered. update() unregisters and re-registers it on
// a timer so you can watch resolveNamedColor() fall back to gray.
class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.registerColor(name: string, color: Color32): voidRegisters a named color in the global color registry.
Name matching is case-insensitive and trims surrounding whitespace.registerColor(const CUSTOM_OPTIONAL_NAME: "demo-optional"CUSTOM_OPTIONAL_NAME, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 90, 150));
this.Demo.optionalRegistered: booleanoptionalRegistered = true;
this.Demo.elapsed: numberelapsed = 0;
// Tell the engine to use this palette for all drawing.
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);
// Run one update so the swatch slots hold real colors before the first render.
// Without it, a frame drawn before the first update() shows four black swatches.
// This priming call also advances the clock by one tick, the same as every
// regular call from the engine's game loop.
this.Demo.update(): voidupdate();
return true;
}
// Animates the custom named colors every tick.
Demo.update(): voidupdate() {
this.Demo.elapsed: numberelapsed += 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.deltaSeconds: numberFixed-step seconds per update tick.
Equivalent to `1 / BT.targetFPS` when `BT.targetFPS` is finite and positive.
Falls back to `1 / 60` when target FPS is non-finite or non-positive.deltaSeconds;
// Read two built-in names from the registry. resolveOr() carries the same values
// as a fallback, so the swatches would still work if a name were ever missing.
const const tomato: anytomato = this.Demo.resolveOr(name: any, fallback: any): anyresolveOr('tomato', new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 99, 71));
const const cornflower: anycornflower = this.Demo.resolveOr(name: any, fallback: any): anyresolveOr('cornflowerblue', new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(100, 149, 237));
// Blend between the two built-ins to make a moving custom color. Math.sin()
// returns -1..1, so (sin + 1) / 2 moves it to 0..1, and lerp() picks the color
// that far between tomato and cornflower.
const const t: numbert = (Math.sin(this.Demo.elapsed: numberelapsed * 2) + 1) / 2;
const const dynamicColor: anydynamicColor = const tomato: anytomato.lerp(const cornflower: anycornflower, const t: numbert);
class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.updateColor(name: string, color: Color32): voidUpdates an existing named color in the global color registry.
Name matching is case-insensitive and trims surrounding whitespace.
All alias keys that share the same object reference are updated in sync.updateColor(const CUSTOM_DYNAMIC_NAME: "demo-dynamic"CUSTOM_DYNAMIC_NAME, const dynamicColor: anydynamicColor);
// Every 3 seconds toggle demo-optional: registered, unregistered, registered, ...
// Math.floor(elapsed / 3) counts whole 3-second blocks, and % 2 (the remainder
// after dividing by 2) turns that count into 0, 1, 0, 1.
const const cycle: numbercycle = Math.floor(this.Demo.elapsed: numberelapsed / 3) % 2;
const const shouldBeRegistered: booleanshouldBeRegistered = const cycle: numbercycle === 0;
if (const shouldBeRegistered: booleanshouldBeRegistered && !this.Demo.optionalRegistered: booleanoptionalRegistered) {
// Put the name back and mark the moment on the overlay's timing chart.
class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.registerColor(name: string, color: Color32): voidRegisters a named color in the global color registry.
Name matching is case-insensitive and trims surrounding whitespace.registerColor(const CUSTOM_OPTIONAL_NAME: "demo-optional"CUSTOM_OPTIONAL_NAME, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 90, 150));
this.Demo.optionalRegistered: booleanoptionalRegistered = true;
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.assignTag: (label?: string) => voidPlaces a labeled marker on the overlay timing chart at the current tick.
Requires `isOverlayTimingChartEnabled: true` in `configure()`. Tags scroll with the chart
history and are pruned when they leave the visible window. Empty labels become
`"Untitled"`. Chart width resets add an automatic `"Start"` tag.assignTag('Optional registered');
} else if (!const shouldBeRegistered: booleanshouldBeRegistered && this.Demo.optionalRegistered: booleanoptionalRegistered) {
// Take the name out and mark that too.
class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.unregisterColor(name: string): voidRemoves a named color from the global color registry.
Name matching is case-insensitive and trims surrounding whitespace.
All alias keys that share the same object reference are removed in sync.unregisterColor(const CUSTOM_OPTIONAL_NAME: "demo-optional"CUSTOM_OPTIONAL_NAME);
this.Demo.optionalRegistered: booleanoptionalRegistered = false;
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.assignTag: (label?: string) => voidPlaces a labeled marker on the overlay timing chart at the current tick.
Requires `isOverlayTimingChartEnabled: true` in `configure()`. Tags scroll with the chart
history and are pruned when they leave the visible window. Empty labels become
`"Untitled"`. Chart width resets add an automatic `"Start"` tag.assignTag('Optional removed');
}
// Copy the current named colors into the palette slots the draw calls use.
this.Demo.palette: Palette | nullpalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_TOMATO: 5C_TOMATO, const tomato: anytomato);
this.Demo.palette: Palette | nullpalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_CORNFLOWER: 6C_CORNFLOWER, const cornflower: anycornflower);
this.Demo.palette: Palette | nullpalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_CUSTOM_DYNAMIC: 7C_CUSTOM_DYNAMIC, this.Demo.resolveOr(name: any, fallback: any): anyresolveOr(const CUSTOM_DYNAMIC_NAME: "demo-dynamic"CUSTOM_DYNAMIC_NAME, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 255, 255)));
// While demo-optional is unregistered, show the reserved C_OPTIONAL_FALLBACK gray
// (set in init()) instead of looking up the built-in name "gray".
this.Demo.palette: Palette | nullpalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_OPTIONAL: 8C_OPTIONAL, this.Demo.resolveOr(name: any, fallback: any): anyresolveOr(const CUSTOM_OPTIONAL_NAME: "demo-optional"CUSTOM_OPTIONAL_NAME, this.Demo.palette: Palette | nullpalette.Palette.get(index: number): Color32Returns a copy of the color stored at a palette index.
Returns a clone so callers cannot mutate internal state without going
through
{@link
set
}
, which keeps the dirty flag accurate.get(const C_OPTIONAL_FALLBACK: 9C_OPTIONAL_FALLBACK)));
}
// Draws the two kit panels: the swatches with the live registry status, and the
// tips.
Demo.render(): voidrender() {
// Fill the screen with the shared UI theme's deep navy background.
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.clear: (paletteIndex: number) => voidSets the frame clear color using a palette index.
The renderer uses this color when clearing the full display at the start
of the next frame.clear(this.Demo.theme: nulltheme.bg);
// Full-width title strip across the top, drawn by the shared UI kit. 'topBar' is
// the classic 22-pixel strip; panel() gives it a background and a title.
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_BAR);
import uiui.panel('Built-in lookups + register / update / unregister');
import uiui.end();
// The Named colors panel. ui.spacer() reserves the band the swatches are drawn
// into after ui.end(); the three status lines follow under it.
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const EDGE: 4EDGE, y: anyy: const COLORS_PANEL_Y: anyCOLORS_PANEL_Y, width: numberwidth: const PANEL_W: numberPANEL_W });
import uiui.panel('Named colors');
import uiui.spacer(const SWATCH_BAND_H: numberSWATCH_BAND_H);
import uiui.label("Custom dynamic name: 'demo-dynamic'");
import uiui.label("Custom optional name: 'demo-optional'");
// The status line flips between the theme's green 'accent' and orange 'warm'
// roles, so you can watch registerColor / unregisterColor take effect every 3
// seconds (update() drives the toggle).
if (this.Demo.optionalRegistered: booleanoptionalRegistered) {
import uiui.label('demo-optional is registered (registerColor)', { color: stringcolor: 'accent' });
} else {
import uiui.label('demo-optional is missing (unregisterColor)', { color: stringcolor: 'warm' });
}
import uiui.end();
// The four swatches, one per cell, on top of the panel.
for (let let i: numberi = 0; let i: numberi < const SWATCHES: {}SWATCHES.length; let i: numberi++) {
this.Demo.drawSwatch(cellX: any, swatch: any): voiddrawSwatch(const EDGE: 4EDGE + const PANEL_PAD: 9PANEL_PAD + let i: numberi * const CELL_W: numberCELL_W, const SWATCHES: {}SWATCHES[let i: numberi]);
}
// The Tips panel below it, the same width.
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const EDGE: 4EDGE, y: anyy: const TIPS_PANEL_Y: anyTIPS_PANEL_Y, width: numberwidth: const PANEL_W: numberPANEL_W });
import uiui.panel('Tips');
import uiui.label('Names are trim + lowercase normalized.', { color: stringcolor: 'dim' });
import uiui.label('registerColor throws if the name exists.', { color: stringcolor: 'dim' });
import uiui.label('updateColor / unregisterColor throw if missing.', { color: stringcolor: 'dim' });
import uiui.end();
}
// Looks up a named color and returns `fallback` when the name is missing.
Demo.resolveOr(name: any, fallback: any): anyresolveOr(name: anyname, fallback: anyfallback) {
const const resolved: Color32 | undefinedresolved = class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.resolveNamedColor(name: string): Color32 | undefinedLooks up a named color from the global registry.
Name matching is case-insensitive and trims surrounding whitespace.resolveNamedColor(name: anyname);
if (const resolved: Color32 | undefinedresolved === var undefinedundefined) {
return fallback: anyfallback;
}
return const resolved: Color32resolved;
}
// Removes a custom name if it exists, so init() can register it cleanly. This makes
// hot reload safer during development.
Demo.removeIfExists(name: any): voidremoveIfExists(name: anyname) {
if (class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.resolveNamedColor(name: string): Color32 | undefinedLooks up a named color from the global registry.
Name matching is case-insensitive and trims surrounding whitespace.resolveNamedColor(name: anyname) !== var undefinedundefined) {
class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.unregisterColor(name: string): voidRemoves a named color from the global color registry.
Name matching is case-insensitive and trims surrounding whitespace.
All alias keys that share the same object reference are removed in sync.unregisterColor(name: anyname);
}
}
// Draws one swatch centered in the cell that starts at `cellX`, with its label and
// its built-in / custom tag centered under it. `swatch` is one SWATCHES entry.
Demo.drawSwatch(cellX: any, swatch: any): voiddrawSwatch(cellX: anycellX, swatch: anyswatch) {
const const centerX: anycenterX = cellX: anycellX + const CELL_W: numberCELL_W / 2;
const const x: anyx = Math.round(const centerX: anycenterX - const SWATCH_W: 56SWATCH_W / 2);
const const y: anyy = const COLORS_PANEL_Y: anyCOLORS_PANEL_Y + const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP + 2;
// Fill the swatch with the palette slot we are demonstrating: the lesson itself.
// The outline is chrome, so it uses the shared UI theme's border color. Both use
// the kit's sprite drawing (ui.drawRectFill / ui.drawRect): the engine draws
// every BT.drawRectFill() before any sprite, and the kit panel is a sprite, so a
// plain rectangle would hide under it.
this.Demo.swatchRect: Rect2iswatchRect.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 x: anyx, const y: anyy, const SWATCH_W: 56SWATCH_W, const SWATCH_H: 24SWATCH_H);
import uiui.drawRectFill(this.Demo.swatchRect: Rect2iswatchRect, swatch: anyswatch.slot);
import uiui.drawRect(this.Demo.swatchRect: Rect2iswatchRect, this.Demo.theme: nulltheme.border);
// Two caption lines centered under the swatch. Every character of the system
// font is FONT_W (6) pixels wide, so a line starts half its width left of the
// middle; a caption's text starts 4 pixels above the y it is given.
const const textY: anytextY = const y: anyy + const SWATCH_H: 24SWATCH_H + 3;
import uiui.caption(Math.round(const centerX: anycenterX - (swatch: anyswatch.label.length * import FONT_WFONT_W) / 2), const textY: anytextY + 4, swatch: anyswatch.label, {
color: stringcolor: 'text',
});
import uiui.caption(Math.round(const centerX: anycenterX - (swatch: anyswatch.kind.length * import FONT_WFONT_W) / 2), const textY: anytextY + 14, swatch: anyswatch.kind, { color: stringcolor: 'dim' });
}
}
// Hand the Demo class to BLIT386 to start the demo loop.
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);