textui/src/ui.ts
2021-09-07 00:35:15 +02:00

133 lines
3.1 KiB
TypeScript

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<UIConfig>) {
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<void> {
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<void> {
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<void> {
await this.buffer.forEachDirty((at, char) =>
this.display.setChar(at, char)
);
await this.display.flush();
}
/**
* Clear the whole screen
*/
async clear(bg = 0): Promise<void> {
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<void> {
this.buffer = new CharBuffer(size);
await this.clear();
this.config.onResize(size);
}
/**
* Start the event loop, waiting for input
*/
async loop(refresh = 100): Promise<void> {
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));
}
}
}