2021-09-06 22:35:15 +00:00
|
|
|
import { BufferDrawing, BufferSize, CharBuffer, PaletteMap } from "./base.ts";
|
|
|
|
import { getPaletteMapping } from "./colors.ts";
|
|
|
|
import { UI_CONFIG_DEFAULTS, UIConfig } from "./config.ts";
|
2021-05-13 22:04:47 +00:00
|
|
|
import { Display } from "./display.ts";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Common abstraction for a textual UI
|
|
|
|
*/
|
|
|
|
export class TextUI {
|
2021-06-28 18:21:32 +00:00
|
|
|
private config: UIConfig;
|
2021-07-19 22:48:00 +00:00
|
|
|
private buffer = new CharBuffer({ w: 1, h: 1 });
|
2021-06-24 22:41:34 +00:00
|
|
|
private palettemap: PaletteMap = [];
|
2021-06-28 18:21:32 +00:00
|
|
|
private quitting = false;
|
2021-05-13 22:04:47 +00:00
|
|
|
|
2021-06-28 18:21:32 +00:00
|
|
|
constructor(private display: Display, config: Partial<UIConfig>) {
|
|
|
|
this.config = { ...UI_CONFIG_DEFAULTS, ...config };
|
2021-05-13 22:04:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
get drawing(): BufferDrawing {
|
2021-07-19 22:48:00 +00:00
|
|
|
return new BufferDrawing(this.buffer, this.palettemap);
|
2021-05-13 22:04:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-06-28 18:21:32 +00:00
|
|
|
* Initializes the UI and display
|
|
|
|
*
|
|
|
|
* If config.loopInterval is defined, the UI loop is
|
|
|
|
* started but not awaited.
|
2021-05-13 22:04:47 +00:00
|
|
|
*/
|
2021-06-28 18:21:32 +00:00
|
|
|
async init(): Promise<void> {
|
2021-05-13 22:04:47 +00:00
|
|
|
var size = await this.display.getSize();
|
2021-07-19 22:48:00 +00:00
|
|
|
this.buffer = new CharBuffer(size);
|
|
|
|
|
|
|
|
await this.display.init();
|
2021-06-28 18:21:32 +00:00
|
|
|
this.palettemap = await getPaletteMapping(
|
|
|
|
this.config.palette,
|
|
|
|
this.display,
|
|
|
|
);
|
2021-07-19 22:48:00 +00:00
|
|
|
if (this.config.hideCursor) {
|
|
|
|
await this.display.setCursorVisibility(false);
|
2021-06-28 18:21:32 +00:00
|
|
|
}
|
2021-07-19 22:48:00 +00:00
|
|
|
await this.clear();
|
2021-06-28 18:21:32 +00:00
|
|
|
|
|
|
|
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;
|
2021-07-19 22:48:00 +00:00
|
|
|
await this.clear();
|
|
|
|
await this.display.setCursorVisibility(true);
|
|
|
|
await this.display.uninit();
|
2021-06-28 18:21:32 +00:00
|
|
|
if (typeof Deno != "undefined") {
|
|
|
|
Deno.exit();
|
|
|
|
}
|
2021-05-13 22:04:47 +00:00
|
|
|
}
|
|
|
|
|
2021-05-19 13:00:52 +00:00
|
|
|
/**
|
|
|
|
* Get the current display size
|
|
|
|
*/
|
|
|
|
getSize(): BufferSize {
|
2021-07-19 22:48:00 +00:00
|
|
|
return this.buffer.getSize();
|
2021-05-19 13:00:52 +00:00
|
|
|
}
|
|
|
|
|
2021-05-13 22:04:47 +00:00
|
|
|
/**
|
|
|
|
* Flush the internal buffer to the display
|
|
|
|
*/
|
|
|
|
async flush(): Promise<void> {
|
2021-07-19 22:48:00 +00:00
|
|
|
await this.buffer.forEachDirty((at, char) =>
|
2021-06-28 20:42:42 +00:00
|
|
|
this.display.setChar(at, char)
|
|
|
|
);
|
2021-07-19 22:48:00 +00:00
|
|
|
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);
|
2021-05-13 22:04:47 +00:00
|
|
|
}
|
2021-05-19 13:00:52 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Start the event loop, waiting for input
|
|
|
|
*/
|
2021-06-28 20:42:42 +00:00
|
|
|
async loop(refresh = 100): Promise<void> {
|
2021-06-28 18:21:32 +00:00
|
|
|
while (!this.quitting) {
|
2021-08-26 18:12:36 +00:00
|
|
|
// 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);
|
|
|
|
}
|
2021-07-19 22:48:00 +00:00
|
|
|
|
2021-08-26 18:12:36 +00:00
|
|
|
if (click) {
|
|
|
|
this.config.onMouseClick(click);
|
2021-06-28 18:21:32 +00:00
|
|
|
}
|
|
|
|
}
|
2021-07-19 22:48:00 +00:00
|
|
|
|
|
|
|
// flush
|
2021-05-19 13:00:52 +00:00
|
|
|
await this.flush();
|
2021-07-19 22:48:00 +00:00
|
|
|
|
|
|
|
// wait
|
2021-05-19 13:00:52 +00:00
|
|
|
await new Promise((resolve) => setTimeout(resolve, refresh));
|
|
|
|
}
|
|
|
|
}
|
2021-06-28 18:21:32 +00:00
|
|
|
}
|