You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
95 lines
3.3 KiB
95 lines
3.3 KiB
// ---------------------------------------------------------------------------
|
|
// 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<MonoDisplayDriverOptions>;
|
|
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 <canvas>`);
|
|
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<ArrayBuffer>): Promise<void> {
|
|
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(); }
|
|
}
|
|
|
|
|
|
|
|
|