From 3e0e4fb8aaa941b99877e04b7dcd743356402e38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Lemaire?= Date: Tue, 7 Sep 2021 00:35:15 +0200 Subject: [PATCH] Scaffolding --- .gitignore | 2 + cli.ts | 7 +++ demo.ts | 34 ----------- deps.testing.ts | 2 +- mod.ts | 45 +------------- ansi.test.ts => src/ansi.test.ts | 2 +- ansi.ts => src/ansi.ts | 2 +- base.test.ts => src/base.test.ts | 2 +- base.ts => src/base.ts | 0 src/colors.ts | 77 +++++++++++++++++++++++ config.ts => src/config.ts | 0 src/controls.ts | 2 + web-demo/demo.js => src/demo.ts | 18 ++++-- display.test.ts => src/display.test.ts | 2 +- display.ts => src/display.ts | 0 src/main.ts | 40 ++++++++++++ ui.test.ts => src/ui.test.ts | 2 +- ui.ts => src/ui.ts | 85 +------------------------- web.ts => src/web.ts | 0 web-demo/.gitignore | 1 - {web-demo => web}/canvas.html | 4 +- {web-demo => web}/demo.css | 0 {web-demo => web}/div.html | 4 +- 23 files changed, 156 insertions(+), 175 deletions(-) create mode 100755 cli.ts delete mode 100755 demo.ts rename ansi.test.ts => src/ansi.test.ts (98%) rename ansi.ts => src/ansi.ts (99%) rename base.test.ts => src/base.test.ts (96%) rename base.ts => src/base.ts (100%) create mode 100644 src/colors.ts rename config.ts => src/config.ts (100%) create mode 100644 src/controls.ts rename web-demo/demo.js => src/demo.ts (62%) mode change 100644 => 100755 rename display.test.ts => src/display.test.ts (94%) rename display.ts => src/display.ts (100%) create mode 100644 src/main.ts rename ui.test.ts => src/ui.test.ts (96%) rename ui.ts => src/ui.ts (55%) rename web.ts => src/web.ts (100%) delete mode 100644 web-demo/.gitignore rename {web-demo => web}/canvas.html (63%) rename {web-demo => web}/demo.css (100%) rename {web-demo => web}/div.html (64%) diff --git a/.gitignore b/.gitignore index d69a4c0..8b7abdc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ deno.d.ts .vscode .local +.output +web/*.js diff --git a/cli.ts b/cli.ts new file mode 100755 index 0000000..6ceb132 --- /dev/null +++ b/cli.ts @@ -0,0 +1,7 @@ +#!./run + +import { runUIDemo } from "./src/demo.ts"; + +if (import.meta.main) { + await runUIDemo(); +} diff --git a/demo.ts b/demo.ts deleted file mode 100755 index f1e1369..0000000 --- a/demo.ts +++ /dev/null @@ -1,34 +0,0 @@ -#!./run - -import { AnsiTerminalDisplay } from "./ansi.ts"; -import { UIConfig } from "./config.ts"; -import { TextUI } from "./ui.ts"; - -const display = new AnsiTerminalDisplay(); -let x = 0; -const config: Partial = { - palette: [ - { r: 0, g: 0, b: 0 }, - { r: 1, g: 1, b: 1 }, - { r: 0, g: 1, b: 1 }, - ], - onResize: draw, - onKeyStroke: (key) => { - ui.drawing.color(1, 0).text(key, { x, y: 7 }); - x += key.length + 1; - }, - onMouseClick: (loc) => { - const text = `${loc.x}:${loc.y}`; - ui.drawing.color(1, 0).text(text, { x, y: 7 }); - x += text.length + 1; - }, -}; -const ui = new TextUI(display, config); -await ui.init(); -draw(); -await ui.loop(); - -function draw() { - ui.drawing.color(2, 0).text("hello", { x: 10, y: 3 }); - ui.drawing.color(0, 1).text("world", { x: 10, y: 5 }); -} diff --git a/deps.testing.ts b/deps.testing.ts index 309939a..e87497c 100644 --- a/deps.testing.ts +++ b/deps.testing.ts @@ -2,5 +2,5 @@ export { describe, expect, it, -} from "https://js.thunderk.net/devtools@1.3.0/testing.ts"; +} from "https://js.thunderk.net/testing@1.0.0/mod.ts"; export { Buffer } from "https://deno.land/std@0.106.0/io/buffer.ts"; diff --git a/mod.ts b/mod.ts index caf3d1d..1718b45 100644 --- a/mod.ts +++ b/mod.ts @@ -1,42 +1,3 @@ -import { AnsiTerminalDisplay } from "./ansi.ts"; -import { UIConfig } from "./config.ts"; -import { Display } from "./display.ts"; -import { TextUI } from "./ui.ts"; -import { CanvasTerminalDisplay, DivTerminalDisplay } from "./web.ts"; - -export { TextUI }; - -export const UI_DISPLAY_TYPES = { - autodetect: undefined, - ansi: AnsiTerminalDisplay, - web_div: DivTerminalDisplay, - web_canvas: CanvasTerminalDisplay, - dummy: Display, -} as const; - -export async function createTextUI( - config: Partial, - display_type: keyof typeof UI_DISPLAY_TYPES = "autodetect", -): Promise { - if (display_type == "autodetect") { - if (typeof (window as any).document != "undefined") { - display_type = "web_canvas"; - // TODO if canvas is not available, fall back to div - } else if (typeof (Deno as any) != "undefined") { - display_type = "ansi"; - } else { - const message = "Cannot initialize display"; - if (typeof alert == "function") { - alert(message); - } - throw new Error(message); - } - } - - var display = new UI_DISPLAY_TYPES[display_type](); - - var ui = new TextUI(display, config); - await ui.init(); - - return ui; -} +export { TextUI } from "./src/ui.ts"; +export { createTextUI, UI_DISPLAY_TYPES } from "./src/main.ts"; +export { runUIDemo } from "./src/demo.ts"; diff --git a/ansi.test.ts b/src/ansi.test.ts similarity index 98% rename from ansi.test.ts rename to src/ansi.test.ts index 41c321f..d61f172 100644 --- a/ansi.test.ts +++ b/src/ansi.test.ts @@ -1,5 +1,5 @@ import { AnsiColorMode, AnsiTerminalDisplay } from "./ansi.ts"; -import { Buffer, describe, expect, it } from "./deps.testing.ts"; +import { Buffer, describe, expect, it } from "../deps.testing.ts"; function createTestDisplay(): { stdout: Buffer; diff --git a/ansi.ts b/src/ansi.ts similarity index 99% rename from ansi.ts rename to src/ansi.ts index ec3d3a2..4ea0997 100644 --- a/ansi.ts +++ b/src/ansi.ts @@ -1,5 +1,5 @@ import { BufferLocation, BufferSize, Char, Color } from "./base.ts"; -import { readKeypress } from "./deps.ts"; +import { readKeypress } from "../deps.ts"; import { Display } from "./display.ts"; export enum AnsiColorMode { diff --git a/base.test.ts b/src/base.test.ts similarity index 96% rename from base.test.ts rename to src/base.test.ts index 7b8683e..9b99221 100644 --- a/base.test.ts +++ b/src/base.test.ts @@ -1,5 +1,5 @@ import { BufferDrawing, BufferLocation, CharBuffer } from "./base.ts"; -import { describe, expect, it } from "./deps.testing.ts"; +import { describe, expect, it } from "../deps.testing.ts"; describe(CharBuffer, () => { it("initializes empty, sets and gets characters", () => { diff --git a/base.ts b/src/base.ts similarity index 100% rename from base.ts rename to src/base.ts diff --git a/src/colors.ts b/src/colors.ts new file mode 100644 index 0000000..249bb5e --- /dev/null +++ b/src/colors.ts @@ -0,0 +1,77 @@ +import { Color, PaletteMap } from "./base.ts"; +import { UIPalette } from "./config.ts"; +import { cmp } from "../deps.ts"; +import { Display } from "./display.ts"; + +export async function getPaletteMapping( + palette: UIPalette, + display: Display, +): Promise { + // get the colors supported by display + const app_colors = palette.map((c): Color[] => Array.isArray(c) ? c : [c]); + const all_colors = app_colors.reduce((acc, val) => acc.concat(val), []); + const display_colors = await display.setupPalette(all_colors); + + // rank all supported colors by proximity to each app color + let ranked: { + color: Color; + idx: number; + penalty: number; + matches: { color: Color; idx: number; distance: number }[]; + }[] = []; + app_colors.forEach((colors, idx) => { + colors.forEach((color, alt) => { + ranked.push({ + color, + idx, + penalty: alt + 1, + matches: display_colors.map((display_color, didx) => { + return { + color: display_color, + idx: didx, + distance: colorDistance(color, display_color), + }; + }).sort(cmp({ key: (info) => info.distance })), + }); + }); + }); + + // TODO negatively score colors too much near previously chosen ones + + // find the best color mapping for each source color + const result = palette.map(() => -1); + while (ranked.length > 0) { + ranked.sort( + cmp({ key: (info) => info.matches[0].distance * info.penalty }), + ); + + const best = ranked[0]; + const app_idx = best.idx; + const display_idx = best.matches[0].idx; + result[app_idx] = display_idx; + + for (const color of ranked) { + color.matches = color.matches.filter((match) => + match.idx !== display_idx + ); + } + ranked = ranked.filter((color) => + color.idx !== app_idx && color.matches.length > 0 + ); + } + return result; +} + +function colorDistance(e1: Color, e2: Color): number { + /*return (e2.r - e1.r) * (e2.r - e1.r) + + (e2.g - e1.g) * (e2.g - e1.g) + + (e2.b - e1.b) * (e2.b - e1.b);*/ + const c = (x: number) => Math.round(x * 255); + const rmean = (c(e1.r) + c(e2.r)) / 2; + const r = c(e1.r) - c(e2.r); + const g = c(e1.g) - c(e2.g); + const b = c(e1.b) - c(e2.b); + return Math.sqrt( + (((512 + rmean) * r * r) >> 8) + 4 * g * g + (((767 - rmean) * b * b) >> 8), + ); +} diff --git a/config.ts b/src/config.ts similarity index 100% rename from config.ts rename to src/config.ts diff --git a/src/controls.ts b/src/controls.ts new file mode 100644 index 0000000..f336276 --- /dev/null +++ b/src/controls.ts @@ -0,0 +1,2 @@ +export interface Control { +} diff --git a/web-demo/demo.js b/src/demo.ts old mode 100644 new mode 100755 similarity index 62% rename from web-demo/demo.js rename to src/demo.ts index 7edd325..cb97ea8 --- a/web-demo/demo.js +++ b/src/demo.ts @@ -1,9 +1,11 @@ -import { createTextUI } from "./textui.js"; +import { UIConfig } from "./config.ts"; +import { createTextUI, UI_DISPLAY_TYPES } from "./main.ts"; -export async function demo(display_type) { - await new Promise((resolve) => setTimeout(resolve, 500)); +export async function runUIDemo( + display_type: keyof typeof UI_DISPLAY_TYPES = "autodetect", +): Promise { let x = 0; - const ui = await createTextUI({ + const config: Partial = { palette: [ { r: 0, g: 0, b: 0 }, { r: 1, g: 1, b: 1 }, @@ -19,10 +21,14 @@ export async function demo(display_type) { ui.drawing.color(1, 0).text(text, { x, y: 7 }); x += text.length + 1; }, - }, display_type); + }; + const ui = await createTextUI(config, display_type); + await ui.init(); + draw(); + await ui.loop(); + function draw() { ui.drawing.color(2, 0).text("hello", { x: 10, y: 3 }); ui.drawing.color(0, 1).text("world", { x: 10, y: 5 }); } - await ui.loop(); } diff --git a/display.test.ts b/src/display.test.ts similarity index 94% rename from display.test.ts rename to src/display.test.ts index 3565da8..95c18db 100644 --- a/display.test.ts +++ b/src/display.test.ts @@ -1,5 +1,5 @@ import { Display } from "./display.ts"; -import { describe, expect, it } from "./deps.testing.ts"; +import { describe, expect, it } from "../deps.testing.ts"; describe(Display, () => { it("buffers unique events", async () => { diff --git a/display.ts b/src/display.ts similarity index 100% rename from display.ts rename to src/display.ts diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..09d6b9c --- /dev/null +++ b/src/main.ts @@ -0,0 +1,40 @@ +import { AnsiTerminalDisplay } from "./ansi.ts"; +import { UIConfig } from "./config.ts"; +import { Display } from "./display.ts"; +import { TextUI } from "./ui.ts"; +import { CanvasTerminalDisplay, DivTerminalDisplay } from "./web.ts"; + +export const UI_DISPLAY_TYPES = { + autodetect: undefined, + ansi: AnsiTerminalDisplay, + web_div: DivTerminalDisplay, + web_canvas: CanvasTerminalDisplay, + dummy: Display, +} as const; + +export async function createTextUI( + config: Partial, + display_type: keyof typeof UI_DISPLAY_TYPES = "autodetect", +): Promise { + if (display_type == "autodetect") { + if (typeof (window as any).document != "undefined") { + display_type = "web_canvas"; + // TODO if canvas is not available, fall back to div + } else if (typeof (Deno as any) != "undefined") { + display_type = "ansi"; + } else { + const message = "Cannot initialize display"; + if (typeof alert == "function") { + alert(message); + } + throw new Error(message); + } + } + + var display = new UI_DISPLAY_TYPES[display_type](); + + var ui = new TextUI(display, config); + await ui.init(); + + return ui; +} diff --git a/ui.test.ts b/src/ui.test.ts similarity index 96% rename from ui.test.ts rename to src/ui.test.ts index 8933723..b289ac3 100644 --- a/ui.test.ts +++ b/src/ui.test.ts @@ -1,5 +1,5 @@ import { BufferLocation, Char, Color, SPACE } from "./base.ts"; -import { describe, expect, it } from "./deps.testing.ts"; +import { describe, expect, it } from "../deps.testing.ts"; import { Display } from "./display.ts"; import { TextUI } from "./ui.ts"; diff --git a/ui.ts b/src/ui.ts similarity index 55% rename from ui.ts rename to src/ui.ts index 9542450..df141e7 100644 --- a/ui.ts +++ b/src/ui.ts @@ -1,12 +1,6 @@ -import { - BufferDrawing, - BufferSize, - CharBuffer, - Color, - PaletteMap, -} from "./base.ts"; -import { UI_CONFIG_DEFAULTS, UIConfig, UIPalette } from "./config.ts"; -import { cmp } from "./deps.ts"; +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"; /** @@ -136,76 +130,3 @@ export class TextUI { } } } - -async function getPaletteMapping( - palette: UIPalette, - display: Display, -): Promise { - // get the colors supported by display - const app_colors = palette.map((c): Color[] => Array.isArray(c) ? c : [c]); - const all_colors = app_colors.reduce((acc, val) => acc.concat(val), []); - const display_colors = await display.setupPalette(all_colors); - - // rank all supported colors by proximity to each app color - let ranked: { - color: Color; - idx: number; - penalty: number; - matches: { color: Color; idx: number; distance: number }[]; - }[] = []; - app_colors.forEach((colors, idx) => { - colors.forEach((color, alt) => { - ranked.push({ - color, - idx, - penalty: alt + 1, - matches: display_colors.map((display_color, didx) => { - return { - color: display_color, - idx: didx, - distance: colorDistance(color, display_color), - }; - }).sort(cmp({ key: (info) => info.distance })), - }); - }); - }); - - // TODO negatively score colors too much near previously chosen ones - - // find the best color mapping for each source color - const result = palette.map(() => -1); - while (ranked.length > 0) { - ranked.sort( - cmp({ key: (info) => info.matches[0].distance * info.penalty }), - ); - - const best = ranked[0]; - const app_idx = best.idx; - const display_idx = best.matches[0].idx; - result[app_idx] = display_idx; - - for (const color of ranked) { - color.matches = color.matches.filter((match) => - match.idx !== display_idx - ); - } - ranked = ranked.filter((color) => - color.idx !== app_idx && color.matches.length > 0 - ); - } - return result; -} - -function colorDistance(e1: Color, e2: Color): number { - /*return (e2.r - e1.r) * (e2.r - e1.r) + - (e2.g - e1.g) * (e2.g - e1.g) + - (e2.b - e1.b) * (e2.b - e1.b);*/ - const c = (x: number) => Math.round(x * 255); - const rmean = (c(e1.r) + c(e2.r)) / 2; - const r = c(e1.r) - c(e2.r); - const g = c(e1.g) - c(e2.g); - const b = c(e1.b) - c(e2.b); - return Math.sqrt( - (((512 + rmean) * r * r) >> 8) + 4 * g * g + (((767 - rmean) * b * b) >> 8), - ); -} diff --git a/web.ts b/src/web.ts similarity index 100% rename from web.ts rename to src/web.ts diff --git a/web-demo/.gitignore b/web-demo/.gitignore deleted file mode 100644 index a00eb62..0000000 --- a/web-demo/.gitignore +++ /dev/null @@ -1 +0,0 @@ -textui.js \ No newline at end of file diff --git a/web-demo/canvas.html b/web/canvas.html similarity index 63% rename from web-demo/canvas.html rename to web/canvas.html index 29e7344..bfce443 100644 --- a/web-demo/canvas.html +++ b/web/canvas.html @@ -4,8 +4,8 @@ diff --git a/web-demo/demo.css b/web/demo.css similarity index 100% rename from web-demo/demo.css rename to web/demo.css diff --git a/web-demo/div.html b/web/div.html similarity index 64% rename from web-demo/div.html rename to web/div.html index e87b179..93e3277 100644 --- a/web-demo/div.html +++ b/web/div.html @@ -4,8 +4,8 @@