Add buffers with text drawing
This commit is contained in:
parent
1d10420cad
commit
55238b5065
8 changed files with 191 additions and 35 deletions
21
ansi.ts
21
ansi.ts
|
@ -1,10 +1,22 @@
|
|||
import { Color, Display } from "./display.ts";
|
||||
import { BufferLocation, BufferSize, Char, Color } from "./base.ts";
|
||||
import { Display } from "./display.ts";
|
||||
|
||||
/**
|
||||
* ANSI terminal display
|
||||
*/
|
||||
export class AnsiTerminalDisplay implements Display {
|
||||
constructor(private writer: Deno.Writer = Deno.stdout) {
|
||||
constructor(
|
||||
private writer: Deno.Writer = Deno.stdout,
|
||||
private reader: Deno.Reader = Deno.stdin,
|
||||
) {
|
||||
}
|
||||
|
||||
async getSize(): Promise<BufferSize> {
|
||||
const size = Deno.consoleSize(Deno.stdout.rid);
|
||||
return {
|
||||
w: size.columns,
|
||||
h: size.rows,
|
||||
};
|
||||
}
|
||||
|
||||
async setupPalette(colors: readonly Color[]): Promise<readonly Color[]> {
|
||||
|
@ -14,6 +26,11 @@ export class AnsiTerminalDisplay implements Display {
|
|||
async clear(): Promise<void> {
|
||||
await this.writer.write(CLEAR);
|
||||
}
|
||||
|
||||
async setChar(at: BufferLocation, char: Char): Promise<void> {
|
||||
// TODO colors
|
||||
await this.writer.write(escape(`[${at.y};${at.x}H${char.ch}`));
|
||||
}
|
||||
}
|
||||
|
||||
function escape(sequence: string): Uint8Array {
|
||||
|
|
24
base.test.ts
Normal file
24
base.test.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { BufferDrawing, CharBuffer } from "./base.ts";
|
||||
import { Buffer, describe, expect, it } from "./deps.test.ts";
|
||||
|
||||
describe(CharBuffer, () => {
|
||||
it("initializes empty, sets and gets characters", () => {
|
||||
const buffer = new CharBuffer({ w: 3, h: 2 });
|
||||
expect(buffer.toString()).toEqual(" ");
|
||||
buffer.set({ x: 2, y: 0 }, { ch: "x", fg: 1, bg: 4 });
|
||||
buffer.set({ x: 1, y: 1 }, { ch: "y", fg: 2, bg: 5 });
|
||||
expect(buffer.toString()).toEqual(" x y ");
|
||||
expect(buffer.get({ x: 0, y: 0 })).toEqual({ ch: " ", fg: 0, bg: 0 });
|
||||
expect(buffer.get({ x: 1, y: 1 })).toEqual({ ch: "y", fg: 2, bg: 5 });
|
||||
});
|
||||
});
|
||||
|
||||
describe(BufferDrawing, () => {
|
||||
it("draws text", () => {
|
||||
const buffer = new CharBuffer({ w: 4, h: 2 });
|
||||
const drawing = new BufferDrawing(buffer);
|
||||
drawing.text("testing", { x: -1, y: 0 });
|
||||
drawing.text("so", { x: 1, y: 1 });
|
||||
expect(buffer.toString()).toEqual("esti so ");
|
||||
});
|
||||
});
|
94
base.ts
Normal file
94
base.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* Color represented by RGB (0.0-1.0) components
|
||||
*/
|
||||
export type Color = {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Displayable character, with background and foreground color taken from the palette
|
||||
*/
|
||||
export type Char = Readonly<{
|
||||
ch: string;
|
||||
bg: number;
|
||||
fg: number;
|
||||
}>;
|
||||
|
||||
export type BufferSize = Readonly<{ w: number; h: number }>;
|
||||
export type BufferLocation = Readonly<{ x: number; y: number }>;
|
||||
|
||||
export const SPACE: Char = { ch: " ", bg: 0, fg: 0 } as const;
|
||||
|
||||
/**
|
||||
* Rectangular buffer of displayable characters
|
||||
*/
|
||||
export class CharBuffer {
|
||||
private chars: Array<Char>;
|
||||
|
||||
constructor(private size: BufferSize) {
|
||||
this.chars = new Array(size.w * size.h).fill(SPACE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the character buffered at a given at
|
||||
*
|
||||
* This does not properly check for out-of-bounds coordinates,
|
||||
* use BufferDrawing for this
|
||||
*/
|
||||
get(at: BufferLocation): Char {
|
||||
const i = at.y * this.size.w + at.x;
|
||||
if (i > 0 && i < this.chars.length) {
|
||||
return this.chars[i];
|
||||
} else {
|
||||
return SPACE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the character buffered at a given location
|
||||
*
|
||||
* This does not properly check for out-of-bounds coordinates,
|
||||
* use BufferDrawing for this
|
||||
*/
|
||||
set(at: BufferLocation, char: Char): void {
|
||||
const i = at.y * this.size.w + at.x;
|
||||
if (i >= 0 && i < this.chars.length) {
|
||||
this.chars[i] = char;
|
||||
}
|
||||
}
|
||||
|
||||
getSize(): BufferSize {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.chars.map((c) => c.ch).join("");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tools for drawing inside a display buffer
|
||||
*/
|
||||
export class BufferDrawing {
|
||||
constructor(private readonly buffer: CharBuffer) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a piece of text of the same color
|
||||
*/
|
||||
text(content: string, from: BufferLocation): void {
|
||||
let { w, h } = this.buffer.getSize();
|
||||
let { x, y } = from;
|
||||
let buf = this.buffer;
|
||||
if (y >= 0 && y < h) {
|
||||
for (let ch of content) {
|
||||
if (x >= 0 && x < w) {
|
||||
buf.set({ x, y }, { ch, bg: 0, fg: 0 });
|
||||
}
|
||||
x++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13
common.ts
13
common.ts
|
@ -1,13 +0,0 @@
|
|||
import { Display } from "./display.ts";
|
||||
|
||||
/**
|
||||
* Common abstraction for a textual UI
|
||||
*/
|
||||
export class TextUI {
|
||||
constructor(private display: Display) {
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
await this.display.clear();
|
||||
}
|
||||
}
|
6
demo.ts
6
demo.ts
|
@ -1,9 +1,11 @@
|
|||
#!/usr/bin/env -S deno run
|
||||
#!/usr/bin/env -S deno run --allow-env --unstable
|
||||
|
||||
import { AnsiTerminalDisplay } from "./ansi.ts";
|
||||
import { TextUI } from "./common.ts";
|
||||
import { TextUI } from "./ui.ts";
|
||||
|
||||
const display = new AnsiTerminalDisplay();
|
||||
const ui = new TextUI(display);
|
||||
await ui.init();
|
||||
ui.drawing.text("hello", { x: 10, y: 3 });
|
||||
await ui.flush();
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
|
|
2
deps.ts
2
deps.ts
|
@ -1 +1 @@
|
|||
export { Sys } from "https://code.thunderk.net/typescript/devtools/raw/1.2.2/system.ts";
|
||||
export * from "https://code.thunderk.net/typescript/functional/raw/1.0.0/all.ts";
|
||||
|
|
28
display.ts
28
display.ts
|
@ -1,25 +1,14 @@
|
|||
/**
|
||||
* Color represented by RGB (0.0-1.0) components
|
||||
*/
|
||||
export type Color = {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Displayable character, with background and foreground color taken from the palette
|
||||
*/
|
||||
export type Char = {
|
||||
ch: string;
|
||||
bg: number;
|
||||
fg: number;
|
||||
};
|
||||
import { BufferLocation, BufferSize, Char, Color } from "./base.ts";
|
||||
|
||||
/**
|
||||
* Display protocol, to allow the UI to draw things on "screen"
|
||||
*/
|
||||
export interface Display {
|
||||
/**
|
||||
* Get the displayable grid size
|
||||
*/
|
||||
getSize(): Promise<BufferSize>;
|
||||
|
||||
/**
|
||||
* Setup the palette for color display
|
||||
*
|
||||
|
@ -34,4 +23,9 @@ export interface Display {
|
|||
* Clear the whole screen
|
||||
*/
|
||||
clear(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Draw a single character on screen
|
||||
*/
|
||||
setChar(at: BufferLocation, char: Char): Promise<void>;
|
||||
}
|
||||
|
|
38
ui.ts
Normal file
38
ui.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { BufferDrawing, CharBuffer } from "./base.ts";
|
||||
import { Display } from "./display.ts";
|
||||
|
||||
/**
|
||||
* Common abstraction for a textual UI
|
||||
*/
|
||||
export class TextUI {
|
||||
private screen = new CharBuffer({ w: 1, h: 1 });
|
||||
|
||||
constructor(private display: Display) {
|
||||
}
|
||||
|
||||
get drawing(): BufferDrawing {
|
||||
return new BufferDrawing(this.screen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the display
|
||||
*/
|
||||
async init(): Promise<void> {
|
||||
var size = await this.display.getSize();
|
||||
this.screen = new CharBuffer(size);
|
||||
await this.display.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the internal buffer to the display
|
||||
*/
|
||||
async flush(): Promise<void> {
|
||||
// TODO only dirty chars
|
||||
const { w, h } = this.screen.getSize();
|
||||
for (let x = 0; x < w; x++) {
|
||||
for (let y = 0; y < h; y++) {
|
||||
await this.display.setChar({ x, y }, this.screen.get({ x, y }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue