/*
System Font Glyphs: browse every glyph the built-in system font covers.
@description Browse every glyph the built-in system font defines, and click one to copy it to your clipboard.
Part of the BLIT386 demo series.
Prerequisites:
- Fonts: https://demos.blit386.dev/fonts
- Bitmap Font: https://demos.blit386.dev/bitmap-font
Live version: https://demos.blit386.dev/system-font-glyphs
Guides:
- Bitmap Fonts: https://blit386.dev/docs/guides/bitmap-fonts#built-in-system-font
- Input: https://blit386.dev/docs/guides/input#pointer-slot-model
- Overlay: https://blit386.dev/docs/guides/overlay#visibility-model
Reference:
- Assets: https://blit386.dev/docs/api/assets#system-font
- Rendering: https://blit386.dev/docs/api/rendering#system-font
- Core: https://blit386.dev/docs/api/core#hardware-settings
---
WHAT YOU WILL SEE
-----------------
A 600x315 screen with a title bar across the top that reads "174 glyphs - Click one to
copy to the clipboard" (the number is read from the font, so it follows whatever the
engine ships). Below it sits a grid of 16 columns of small button-shaped cells, each
holding one white glyph of the built-in 6x14 system font, in the order the font lists
them: printable ASCII first, then the extra characters (dashes, accented letters, arrows,
Greek capitals, and so on). The last row is only partly filled.
Move the pointer over a cell and it lights up, with a tooltip above it showing the code
point, like "U+00E9 (233) é". Click it and the tooltip switches to "Copied é" with a
green rim on the cell for three quarters of a second, or to "Copy failed" with an orange
rim when the browser refused. The engine overlay is switched off for this demo.
WHAT YOU WILL LEARN
-------------------
- BT.systemFont is the real BitmapFont object BT.systemPrint() draws with, so everything
the Bitmap Font demo did with a loaded font works on it too.
- font.codePoints lists every Unicode code point the font has a glyph for, in ascending
order. The grid is built from that list, so nothing here hardcodes the glyph count.
- BT.printFont(BT.systemFont, pos, char, 0) draws one glyph anywhere. The system font's
pixels live at palette index 1, so offset 0 draws slot 1 - which is why init() paints
that slot white.
- BT.systemPrintMeasure('M') gives the glyph size instead of hardcoding 6x14.
- Pointer input across all four slots: BT.isPointerActive(slot), BT.pointerPos(slot), and
BT.isPressed(BT.BTN_POINTER_A, slot) for the press edge, read in update() rather than
render().
- navigator.clipboard.writeText() - the browser's own clipboard API, called from an async
method whose result arrives later and is shown through a short status message.
- The shared UI kit's ui.drawButtonFace(), ui.tooltip(), and ui.overWidget().
- configure() returning displaySize and isOverlayEnabled: false.
WHY 600 BY 315?
---------------
That is exactly half of the 1200x630 social card the demo site generates, so the card
shows the screen at a clean 2x scale with no blurry resampling. It also leaves room for 16
columns of 23-pixel cells with 2-pixel gaps.
TRY THIS
--------
- Hover any cell to read its code point in both hex and decimal.
- Click a cell (or tap it on a touch screen - touches use pointer slots 1-3), then paste
somewhere: the character itself lands on the clipboard, not its number.
- Click a cell in the top row: the tooltip has no room above, so it drops below the cell
instead of covering the title bar.
- Copying can fail when the page has not been focused or clipboard permission was denied -
the cell rim turns orange and the tooltip says "Copy failed".
*/
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, 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 BUTTON_LOOKBUTTON_LOOK, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js';
/** @typedef {import('blit386').Palette} Palette */
/** @typedef {import('blit386').BitmapFont} BitmapFont */
// The screen is 600x315 display pixels: exactly half of the 1200x630 social card, so the
// card shows it at a clean 2x scale with no blurry resampling.
const const DISPLAY_W: 600DISPLAY_W = 600;
const const DISPLAY_H: 315DISPLAY_H = 315;
// The built-in system font stores every glyph pixel as palette index 1 (see the Fonts
// demo). A fresh palette starts that slot black, so the glyphs would vanish on the dark
// button faces unless it is painted white.
const const C_GLYPH: 1C_GLYPH = 1;
// The grid layout. GRID_COLS = 16 deliberately matches the font's own texture atlas (16
// glyphs per row), so the grid echoes how the font stores its pixels. The row count is
// not fixed - see gridRows below, computed from however many glyphs the font reports.
const const GRID_COLS: 16GRID_COLS = 16;
const const CELL_SIZE: 23CELL_SIZE = 23;
const const CELL_GAP: 2CELL_GAP = 2; // gap between cells, so borders do not touch
const const CELL_PITCH: numberCELL_PITCH = const CELL_SIZE: 23CELL_SIZE + const CELL_GAP: 2CELL_GAP; // distance from one cell's corner to the next
const const GRID_ORIGIN_Y: 28GRID_ORIGIN_Y = 28; // top of the grid, just below the title bar
// How long the "Copied" / "Copy failed" message stays up before the tooltip goes back to
// following the pointer.
const const COPY_STATUS_SECONDS: 0.75COPY_STATUS_SECONDS = 0.75;
// The three states of the copy feedback. Naming them once means a typo in a comparison
// below fails loudly (an undefined property) instead of silently never matching.
const const COPY_STATUS: anyCOPY_STATUS = Object.freeze({
type IDLE: stringIDLE: 'idle', // nothing copied recently: the tooltip follows the pointer
type COPIED: stringCOPIED: 'copied', // the last click copied its glyph
type FAILED: stringFAILED: 'failed', // the last click could not reach the clipboard
});
// Tests whether a display-pixel position lands inside a real grid cell. `localX` and
// `localY` are already offset so 0 is the grid's top-left corner. Returns the glyph index
// at that position, or -1 when the position is outside every cell - including the thin
// gaps between cells and the empty cells past the last real glyph in the final row.
function function hitTestGlyphCell(localX: any, localY: any, glyphCount: any): anyhitTestGlyphCell(localX: anylocalX, localY: anylocalY, glyphCount: anyglyphCount) {
// A negative position is above or to the left of the grid entirely.
if (localX: anylocalX < 0 || localY: anylocalY < 0) {
return -1;
}
// Dividing by the pitch and rounding down tells which column and row we landed in,
// like working out which square of a checkerboard a finger is over.
const const col: anycol = Math.floor(localX: anylocalX / const CELL_PITCH: numberCELL_PITCH);
const const row: anyrow = Math.floor(localY: anylocalY / const CELL_PITCH: numberCELL_PITCH);
// A column past the last one means we are off the right edge of the grid.
if (const col: anycol >= const GRID_COLS: 16GRID_COLS) {
return -1;
}
// How far into this column's pitch did we land? Past CELL_SIZE means the pointer is
// in the gap between two cells, not on a cell. Then the same check for the row.
if (localX: anylocalX - const col: anycol * const CELL_PITCH: numberCELL_PITCH >= const CELL_SIZE: 23CELL_SIZE) {
return -1;
}
if (localY: anylocalY - const row: anyrow * const CELL_PITCH: numberCELL_PITCH >= const CELL_SIZE: 23CELL_SIZE) {
return -1;
}
// Cells are numbered left to right, top to bottom - the order codePoints lists them.
const const index: anyindex = const row: anyrow * const GRID_COLS: 16GRID_COLS + const col: anycol;
// The last row may have empty cells past the final real glyph - reject those.
return const index: anyindex < glyphCount: anyglyphCount ? const index: anyindex : -1;
}
// Draws every glyph BT.systemFont lists in a grid and mirrors the engine overlay's
// palette-swatch click-to-copy using only public API: BT.pointerPos, BT.isPressed with
// BT.BTN_POINTER_A, BT.printFont, and the browser's navigator.clipboard.
class class DemoDemo {
/** @type {Palette | null} */
Demo.palette: Palette | nullpalette = null;
// Palette slot numbers of the shared UI kit colors, filled in by applyTheme() in
// init(). theme.bg clears the screen.
Demo.theme: nulltheme = null;
// Filled in by init() from BT.systemFont - see there for why.
/** @type {BitmapFont | null} */
Demo.font: BitmapFont | nullfont = null;
/** @type {readonly number[]} */
Demo.codePoints: {}codePoints = [];
// How many rows the grid needs to fit every glyph. Computed in init() from
// codePoints.length, never hardcoded - see the comment on GRID_COLS above.
Demo.gridRows: numbergridRows = 0;
// Pixel size of one glyph, measured in init() with BT.systemPrintMeasure('M') instead
// of hardcoded, so the demo cannot drift out of sync with the font's real dimensions.
Demo.glyphWidth: numberglyphWidth = 6;
Demo.glyphHeight: numberglyphHeight = 14;
// A running clock in seconds, used to time how long the "Copied" / "Copy failed"
// message stays up. BT.deltaSeconds is the time one fixed update covers - adding it
// up every tick gives a stopwatch.
Demo.elapsed: numberelapsed = 0;
// Whether a glyph was just copied, and whether it worked. IDLE means nothing was
// copied recently, so the tooltip shows whatever is hovered.
Demo.copyStatus: anycopyStatus = const COPY_STATUS: anyCOPY_STATUS.IDLE;
Demo.copyStatusIndex: numbercopyStatusIndex = -1; // which glyph index the status message is about
Demo.copyStatusExpiry: numbercopyStatusExpiry = 0; // this.elapsed value at which the status message clears
// One rectangle, reused every frame, telling ui.tooltip() which cell to sit above.
// Building a new one each frame would be wasteful - the widget only reads the
// numbers.
Demo.tooltipAnchor: Rect2itooltipAnchor = new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2iCreates an integer rectangle, truncating all inputs toward zero.Rect2i(0, 0, const CELL_SIZE: 23CELL_SIZE, const CELL_SIZE: 23CELL_SIZE);
// Picks a display big enough for the whole glyph grid. Runs before init().
Demo.configure(): {
displaySize: Vector2i;
isOverlayEnabled: boolean;
}
configure() {
return {
displaySize: Vector2idisplaySize: new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const DISPLAY_W: 600DISPLAY_W, const DISPLAY_H: 315DISPLAY_H),
// Turn the engine's debug overlay off, along with its small corner toggle
// mark. This demo has no use for it.
isOverlayEnabled: booleanisOverlayEnabled: false,
};
}
// Sets up the palette and reads BT.systemFont's glyph list.
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);
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_GLYPH: 1C_GLYPH, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 255, 255));
// Install the shared UI kit colors (slots 240-251) before BT.paletteSet().
// ui.tooltip() and the button faces draw with those.
this.Demo.theme: nulltheme = import applyThemeapplyTheme(this.Demo.palette: Palettepalette);
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 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);
// BT.systemFont hands back the exact BitmapFont object BT.systemPrint() draws
// with - a real object we can ask questions of. codePoints is the point of this
// demo: it lists every Unicode character the font can draw, live, so the grid
// stays accurate even if the font's glyph set changes later.
this.Demo.font: BitmapFont | nullfont = 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.systemFont: BitmapFontBuilt-in 6x14 system font used by
{@link
BT.systemPrint
}
(live reference, not a copy).
Read `codePoints` on the returned font to see every Unicode code point it covers, or
`hasGlyph(char)` to test one character. Pass it to
{@link
BT.printFont
}
to draw with the
exact glyphs and metrics
{@link
BT.systemPrint
}
uses internally.systemFont;
this.Demo.codePoints: {}codePoints = this.Demo.font: BitmapFontfont.BitmapFont.codePoints: {}Returns every Unicode code point this font defines a glyph for, ascending.
Derived live from the same
{@link
glyphs
}
map
{@link
getGlyph
}
/
{@link
hasGlyph
}
read -
it can never drift from what actually renders. Includes ordinary ASCII (the ASCII
fast-path array is populated from this same map at construction time, not a second,
disjoint source) and the fallback glyph when the font defines one (see
`FALLBACK_GLYPH_CHAR`). Skips a key that spans more than one Unicode scalar value (for
example a multi-character string from a malformed `.btfont` file) rather than reporting
just its first scalar - nothing validates glyph keys down to a single scalar on load, so
this getter defends its own "one code point per real glyph" guarantee instead of risking
a duplicate or misleading entry. `codePoints.length` can therefore be lower than
{@link
glyphCount
}
for such a font.codePoints;
// Math.ceil rounds up: 174 glyphs / 16 columns = 10.875, which becomes 11 rows
// because the last, partly empty row still needs to exist.
this.Demo.gridRows: numbergridRows = Math.ceil(this.Demo.codePoints: {}codePoints.length / const GRID_COLS: 16GRID_COLS);
// Measure one glyph so it can be centered inside its cell, instead of hardcoding
// the font's pixel dimensions here.
const const glyphSize: Vector2iglyphSize = 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.systemPrintMeasure: (text: string) => Vector2iMeasures the pixel dimensions of a string rendered with the built-in
system font.systemPrintMeasure('M');
this.Demo.glyphWidth: numberglyphWidth = const glyphSize: Vector2iglyphSize.Vector2i.x: numberHorizontal component (defaults to 0).x;
this.Demo.glyphHeight: numberglyphHeight = const glyphSize: Vector2iglyphSize.Vector2i.y: numberVertical component (defaults to 0).y;
return true;
}
// Runs at a fixed rate (60 times per second). Advances the copy-status clock and
// handles clicks and taps. Reading the press edge here, not in render(), matches how
// every other pointer demo in the series handles clicks.
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;
// Once the "Copied" / "Copy failed" message has been up long enough, clear it so
// the tooltip goes back to showing whatever the pointer is hovering.
if (this.Demo.copyStatus: anycopyStatus !== const COPY_STATUS: anyCOPY_STATUS.IDLE && this.Demo.elapsed: numberelapsed >= this.Demo.copyStatusExpiry: numbercopyStatusExpiry) {
this.Demo.copyStatus: anycopyStatus = const COPY_STATUS: anyCOPY_STATUS.IDLE;
this.Demo.copyStatusIndex: numbercopyStatusIndex = -1;
}
// Walk every pointer slot. Slot 0 is the mouse; slots 1-3 are up to three
// simultaneous touches. BTN_POINTER_A "just pressed" means a mouse click or a
// touch just landed on any of them.
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.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_A: numberPrimary pointer button code.
Maps to mouse left for slot 0; touch contact for slots 1-3.BTN_POINTER_A, let slot: numberslot)) {
this.Demo.tryCopyAt(slot: any): voidtryCopyAt(let slot: numberslot);
}
}
}
// If this pointer slot landed on a real glyph cell (and not on the title panel above
// the grid), starts copying that glyph to the clipboard.
Demo.tryCopyAt(slot: any): voidtryCopyAt(slot: anyslot) {
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(slot: anyslot)) {
return;
}
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(slot: anyslot);
// Do not let a click on the title panel fall through to the grid underneath it.
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)) {
return;
}
const const index: anyindex = function hitTestGlyphCell(localX: any, localY: any, glyphCount: any): anyhitTestGlyphCell(const pos: Vector2ipos.Vector2i.x: numberHorizontal component (defaults to 0).x - this.Demo.gridOriginX: anygridOriginX, const pos: Vector2ipos.Vector2i.y: numberVertical component (defaults to 0).y - const GRID_ORIGIN_Y: 28GRID_ORIGIN_Y, this.Demo.codePoints: {}codePoints.length);
if (const index: anyindex !== -1) {
// `void` tells readers (and linters) we are deliberately not waiting for this
// promise before update() moves on - copyGlyph() updates copyStatus itself
// once the clipboard write settles.
void this.Demo.copyGlyph(index: any): anycopyGlyph(const index: anyindex);
}
}
// Writes one glyph's actual character to the system clipboard. `index` points into
// this.codePoints.
async Demo.copyGlyph(index: any): anycopyGlyph(index: anyindex) {
// String.fromCodePoint turns a numeric code point (like 233) back into the real
// character (like 'é'), so the letter itself lands on the clipboard, not a
// number.
const const char: anychar = String.fromCodePoint(this.Demo.codePoints: {}codePoints[index: anyindex]);
try {
// Older or unusual browsers may not implement the Clipboard API at all.
if (!navigator.clipboard?.writeText) {
throw new Error('Clipboard API unavailable');
}
await navigator.clipboard.writeText(const char: anychar);
this.Demo.setCopyStatus(status: any, index: any): voidsetCopyStatus(const COPY_STATUS: anyCOPY_STATUS.COPIED, index: anyindex);
} catch {
// Browsers can also refuse the call outright - for example when the page was
// never focused, or the user denied clipboard permission.
this.Demo.setCopyStatus(status: any, index: any): voidsetCopyStatus(const COPY_STATUS: anyCOPY_STATUS.FAILED, index: anyindex);
}
}
// Records the outcome of a copy attempt (COPY_STATUS.COPIED or COPY_STATUS.FAILED)
// and schedules when its tooltip message disappears.
Demo.setCopyStatus(status: any, index: any): voidsetCopyStatus(status: anystatus, index: anyindex) {
this.Demo.copyStatus: anycopyStatus = status: anystatus;
this.Demo.copyStatusIndex: numbercopyStatusIndex = index: anyindex;
this.Demo.copyStatusExpiry: numbercopyStatusExpiry = this.Demo.elapsed: numberelapsed + const COPY_STATUS_SECONDS: 0.75COPY_STATUS_SECONDS;
}
// Runs once per screen refresh: clears the screen, draws the title panel, works out
// which cell (if any) the pointer is over, then draws the grid and its tooltip.
Demo.render(): voidrender() {
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);
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_BAR);
import uiui.panel(`${this.Demo.codePoints: {}codePoints.length} glyphs - Click one to copy to the clipboard`);
import uiui.end();
// Reading the pointer position (as opposed to a press edge) is safe from
// render().
let let hoveredIndex: numberhoveredIndex = -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);
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)) {
continue;
}
const const index: anyindex = function hitTestGlyphCell(localX: any, localY: any, glyphCount: any): anyhitTestGlyphCell(const pos: Vector2ipos.Vector2i.x: numberHorizontal component (defaults to 0).x - this.Demo.gridOriginX: anygridOriginX, const pos: Vector2ipos.Vector2i.y: numberVertical component (defaults to 0).y - const GRID_ORIGIN_Y: 28GRID_ORIGIN_Y, this.Demo.codePoints: {}codePoints.length);
if (const index: anyindex !== -1) {
let hoveredIndex: numberhoveredIndex = const index: anyindex;
break; // first active pointer that lands on a cell wins
}
}
this.Demo.drawGrid(hoveredIndex: any): voiddrawGrid(let hoveredIndex: numberhoveredIndex);
// While a copy status message is active, the tooltip talks about that glyph (the
// one just clicked) instead of whatever the pointer is over now. It is drawn
// after the grid, so it covers the cells and glyphs underneath it.
const const tooltipIndex: numbertooltipIndex = this.Demo.copyStatus: anycopyStatus === const COPY_STATUS: anyCOPY_STATUS.IDLE ? let hoveredIndex: numberhoveredIndex : this.Demo.copyStatusIndex: numbercopyStatusIndex;
if (const tooltipIndex: numbertooltipIndex !== -1) {
this.Demo.placeTooltipAnchor(index: any): voidplaceTooltipAnchor(const tooltipIndex: numbertooltipIndex);
// minY keeps the box out of the title panel. On the top row, where there is
// no room above the cell, ui.tooltip() drops the box down over the grid
// instead.
import uiui.tooltip(this.Demo.tooltipAnchor: Rect2itooltipAnchor, this.Demo.tooltipLabel(index: any): stringtooltipLabel(const tooltipIndex: numbertooltipIndex), { minY: numberminY: const GRID_ORIGIN_Y: 28GRID_ORIGIN_Y });
}
}
// X position of the grid's top-left corner, centered on the display. A getter (worked
// out each time it is read, like a tiny function) rather than a number saved in
// init(), so it always matches the current CELL_SIZE and display width - even while
// you tweak those numbers with hot reload, which does not re-run init() for a
// method-only change.
get Demo.gridOriginX: anygridOriginX() {
// Subtracting one CELL_GAP accounts for the last column having no gap after it.
const const gridWidth: numbergridWidth = const GRID_COLS: 16GRID_COLS * const CELL_PITCH: numberCELL_PITCH - const CELL_GAP: 2CELL_GAP;
return Math.floor((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.displaySize: Vector2iActive logical render resolution in pixels.
This is the game/simulation coordinate space configured by the demo, not
the canvas element's CSS size. Each read returns a clone.displaySize.Vector2i.x: numberHorizontal component (defaults to 0).x - const gridWidth: numbergridWidth) / 2);
}
// Draws every glyph cell: a kit button face (lighter under the pointer, with a
// colored rim right after a copy) and the glyph itself via BT.printFont() with the
// font BT.systemFont handed us. `hoveredIndex` is the cell under the pointer, or -1.
Demo.drawGrid(hoveredIndex: any): voiddrawGrid(hoveredIndex: anyhoveredIndex) {
for (let let index: numberindex = 0; let index: numberindex < this.Demo.codePoints: {}codePoints.length; let index: numberindex++) {
const const col: numbercol = let index: numberindex % const GRID_COLS: 16GRID_COLS;
const const row: anyrow = Math.floor(let index: numberindex / const GRID_COLS: 16GRID_COLS);
const const cellX: anycellX = this.Demo.gridOriginX: anygridOriginX + const col: numbercol * const CELL_PITCH: numberCELL_PITCH;
const const cellY: numbercellY = const GRID_ORIGIN_Y: 28GRID_ORIGIN_Y + const row: anyrow * const CELL_PITCH: numberCELL_PITCH;
const const cellRect: Rect2icellRect = new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2iCreates an integer rectangle, truncating all inputs toward zero.Rect2i(const cellX: anycellX, const cellY: numbercellY, const CELL_SIZE: 23CELL_SIZE, const CELL_SIZE: 23CELL_SIZE);
// Each cell wears the same rounded face as a kit button (ui.drawButtonFace),
// picked by state. Right after a copy attempt the rim lights up - accent
// green when it worked, warm orange when it failed - so clicking gives
// visible feedback even without reading the tooltip. Otherwise the cell under
// the pointer gets the lighter hover face, and every other cell the resting
// one.
let let look: anylook = import BUTTON_LOOKBUTTON_LOOK.REST;
if (let index: numberindex === this.Demo.copyStatusIndex: numbercopyStatusIndex) {
let look: anylook = this.Demo.copyStatus: anycopyStatus === const COPY_STATUS: anyCOPY_STATUS.COPIED ? import BUTTON_LOOKBUTTON_LOOK.HELD : import BUTTON_LOOKBUTTON_LOOK.WARN;
} else if (let index: numberindex === hoveredIndex: anyhoveredIndex) {
let look: anylook = import BUTTON_LOOKBUTTON_LOOK.HOVER;
}
import uiui.drawButtonFace(const cellRect: Rect2icellRect, let look: anylook);
// BT.printFont(font, position, text, paletteOffset) draws with any BitmapFont
// - here BT.systemFont, the same font BT.systemPrint() uses internally.
// Centering: the cell size minus the glyph size, halved and rounded down to
// whole pixels.
const const char: anychar = String.fromCodePoint(this.Demo.codePoints: {}codePoints[let index: numberindex]);
const const glyphX: anyglyphX = const cellX: anycellX + Math.floor((const CELL_SIZE: 23CELL_SIZE - this.Demo.glyphWidth: numberglyphWidth) / 2);
const const glyphY: anyglyphY = const cellY: numbercellY + Math.floor((const CELL_SIZE: 23CELL_SIZE - this.Demo.glyphHeight: numberglyphHeight) / 2);
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.printFont: (font: BitmapFont, pos: Vector2i, text: string, paletteOffset?: number) => voidDraws text with a bitmap font through the indexed sprite pipeline.
Supports proportional glyph widths and glyph-level offsets defined by the
supplied
{@link
BitmapFont
}
. The font's underlying sprite sheet must have
been indexized before calling this.
Palette offset semantics and out-of-range behavior are identical to
{@link
BT.drawSprite
}
.printFont(this.Demo.font: BitmapFont | nullfont, new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const glyphX: anyglyphX, const glyphY: anyglyphY), const char: anychar, 0);
}
}
// Writes this.tooltipAnchor so it covers the cell for `index`. ui.tooltip() docks its
// box above whatever rectangle it is given.
Demo.placeTooltipAnchor(index: any): voidplaceTooltipAnchor(index: anyindex) {
const const col: numbercol = index: anyindex % const GRID_COLS: 16GRID_COLS;
const const row: anyrow = Math.floor(index: anyindex / const GRID_COLS: 16GRID_COLS);
this.Demo.tooltipAnchor: Rect2itooltipAnchor.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(
this.Demo.gridOriginX: anygridOriginX + const col: numbercol * const CELL_PITCH: numberCELL_PITCH,
const GRID_ORIGIN_Y: 28GRID_ORIGIN_Y + const row: anyrow * const CELL_PITCH: numberCELL_PITCH,
const CELL_SIZE: 23CELL_SIZE,
const CELL_SIZE: 23CELL_SIZE,
);
}
// The words inside the tooltip for one glyph: its code point, or a short note about
// the copy that just happened.
Demo.tooltipLabel(index: any): stringtooltipLabel(index: anyindex) {
const const codePoint: anycodePoint = this.Demo.codePoints: {}codePoints[index: anyindex];
const const char: anychar = String.fromCodePoint(const codePoint: anycodePoint);
if (this.Demo.copyStatus: anycopyStatus === const COPY_STATUS: anyCOPY_STATUS.COPIED && this.Demo.copyStatusIndex: numbercopyStatusIndex === index: anyindex) {
return `Copied ${const char: anychar}`;
}
if (this.Demo.copyStatus: anycopyStatus === const COPY_STATUS: anyCOPY_STATUS.FAILED && this.Demo.copyStatusIndex: numbercopyStatusIndex === index: anyindex) {
return 'Copy failed';
}
// toString(16) turns the number into hexadecimal (base 16) digits, the way
// Unicode code points are usually written. padStart(4, '0') adds leading zeros so
// "U+00E9" is the same width as "U+2013".
const const hex: anyhex = const codePoint: anycodePoint.toString(16).toUpperCase().padStart(4, '0');
return `U+${const hex: anyhex} (${const codePoint: anycodePoint}) ${const char: anychar}`;
}
}
// 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);