import { BufferDrawing, BufferSize, CharBuffer, PaletteMap } from "./base.ts"; import { getPaletteMapping } from "./colors.ts"; import { UI_CONFIG_DEFAULTS, UIConfig } from "./config.ts"; import { Display } from "./display.ts"; /** * Common abstraction for a textual UI */ export class TextUI { private config: UIConfig; private buffer = new CharBuffer({ w: 1, h: 1 }); private palettemap: PaletteMap = []; private quitting = false; constructor(private display: Display, config: Partial) { this.config = { ...UI_CONFIG_DEFAULTS, ...config }; } get drawing(): BufferDrawing { return new BufferDrawing(this.buffer, this.palettemap); } /** * Initializes the UI and display * * If config.loopInterval is defined, the UI loop is * started but not awaited. */ async init(): Promise { var size = await this.display.getSize(); this.buffer = new CharBuffer(size); await this.display.init(); this.palettemap = await getPaletteMapping( this.config.palette, this.display, ); if (this.config.hideCursor) { await this.display.setCursorVisibility(false); } await this.clear(); if (this.config.loopInterval) { this.loop(this.config.loopInterval); // purposefully not awaited } } /** * Quit the UI (this will exit the executable) */ async quit(): Promise { this.quitting = true; await this.clear(); await this.display.setCursorVisibility(true); await this.display.uninit(); if (typeof Deno != "undefined") { Deno.exit(); } } /** * Get the current display size */ getSize(): BufferSize { return this.buffer.getSize(); } /** * Flush the internal buffer to the display */ async flush(): Promise { await this.buffer.forEachDirty((at, char) => this.display.setChar(at, char) ); await this.display.flush(); } /** * Clear the whole screen */ async clear(bg = 0): Promise { const { w, h } = this.getSize(); const drawing = this.drawing.color(bg, bg); for (let y = 0; y < h; y++) { drawing.text(Array(w).fill(" ").join(""), { x: 0, y }); } await this.flush(); } /** * Resize the buffer to match the display */ async resize(size: BufferSize): Promise { this.buffer = new CharBuffer(size); await this.clear(); this.config.onResize(size); } /** * Start the event loop, waiting for input */ async loop(refresh = 100): Promise { while (!this.quitting) { // handle events for (const event of await this.display.getEvents()) { const { key, click, size } = event; if (key) { if (!this.config.ignoreCtrlC && key == "ctrl+c") { await this.quit(); } else { this.config.onKeyStroke(key); } } if (size) { await this.resize(size); } if (click) { this.config.onMouseClick(click); } } // flush await this.flush(); // wait await new Promise((resolve) => setTimeout(resolve, refresh)); } } }