// --------------------------------------------------------------------------- // MonoDisplayRenderer - tick-based canvas renderer // --------------------------------------------------------------------------- import { MonoDisplayParser } from "./parser"; import { MonoDisplayRenderer } from "./renderer"; /** Optional constructor arguments for MonoDisplayDriver */ export interface MonoDisplayDriverOptions { /** CSS colour for on-pixels. Default "#ffffff" */ onColor?: string; /** CSS colour for off-pixels / background. Default "#000000" */ offColor?: string; /** * Scale factor - each logical pixel → NxN canvas pixels. * Keep at 1 and use CSS to stretch for crisp upscaling. */ scale?: number; /** Default display width for elements without inherent size. Default 120. */ displayWidth?: number; /** Default display height. Default 60. */ displayHeight?: number; /** * Render rate in ticks per second. All animations and scrolls are driven by this. * Animation updateInterval, scroll speed, etc. are relative to this tick rate. * Default 25. */ fps?: number; /** Loop animations. Default true. */ loop?: boolean; /** Error handler. Default: throw. */ onError?: (err: Error) => void; /** * Clock source for CurrentTime elements. Called once per render tick. * Default: `() => new Date()` (system time). * Override to freeze or mock the clock, e.g. `() => new Date('2024-01-01T12:00:00Z')`. */ now?: () => Date; } // --------------------------------------------------------------------------- // MonoDisplayDriver - public API surface // --------------------------------------------------------------------------- /** * Top-level driver. Attach to a canvas by ID, call load() with an async * factory that returns a .bin ArrayBuffer. * * @example * ```ts * const driver = new MonoDisplayDriver("canvas_root", { fps: 25 }); * driver.load(() => loadBinFile("display.bin")); * ``` */ export class MonoDisplayDriver { private canvas: HTMLCanvasElement; private opts: Required; private parser: MonoDisplayParser; private renderer: MonoDisplayRenderer | null = null; constructor(canvasId: string, options: MonoDisplayDriverOptions = {}) { const el = document.getElementById(canvasId); if (!el || el.tagName !== "CANVAS") throw new Error(`#${canvasId} is not a `); this.canvas = el as HTMLCanvasElement; this.opts = { onColor: options.onColor ?? "#EC0", offColor: options.offColor ?? "#000000", scale: options.scale ?? 1, displayWidth: options.displayWidth ?? 120, displayHeight: options.displayHeight ?? 60, fps: options.fps ?? 25, loop: options.loop ?? true, onError: options.onError ?? ((e: Error) => { throw e; }), now: options.now ?? (() => new Date()), }; this.parser = new MonoDisplayParser(); } async load(loader: () => Promise): Promise { try { const buffer = await loader(); const file = this.parser.parse(buffer); if (!this.renderer) this.renderer = new MonoDisplayRenderer(this.canvas, this.opts); this.renderer.render(file); } catch (err) { this.opts.onError(err instanceof Error ? err : new Error(String(err))); } } stop(): void { this.renderer?.stop(); } }