diff --git a/src/lib/painter/canvas.old.ts b/src/lib/painter/canvas.old.ts new file mode 100644 index 0000000..bed41d4 --- /dev/null +++ b/src/lib/painter/canvas.old.ts @@ -0,0 +1,125 @@ +import { ANSI_BUFFERS, ANSI_DYNAMIC, writeAnsi } from '../util/ansi'; +import { tryCatch } from '@maxmorozoff/try-catch-tuple'; + +type CanvasAPI = { + clear: () => Promise; + moveToOrigin: () => Promise; + availableHeight: number; + availableWidth: number; +}; + +type CleanupHandler = (api: { + clear: () => Promise; + endPosition: { x: number; y: number }; +}) => Promise; + +export function createCanvas( + paint: (api: CanvasAPI) => Promise, + cleanup?: CleanupHandler +) { + let isActive = true; + let origin = { x: 0, y: 0 }; + let currentEnd = { x: 0, y: 0 }; + + const canvasAPI: CanvasAPI = { + clear: async () => { + await writeAnsi([ + ANSI_BUFFERS.SAVE_CURSOR, + ANSI_DYNAMIC.CURSOR_TO(origin.x, origin.y), + ]); + + // Clear each line in the canvas area + for (let line = 0; line <= currentEnd.y - origin.y; line++) { + await writeAnsi([ + ANSI_BUFFERS.CLEAR_LINE, + ...(line < currentEnd.y - origin.y + ? [ANSI_DYNAMIC.CURSOR_DOWN(1)] + : []), + ]); + } + + await writeAnsi([ANSI_BUFFERS.RESTORE_CURSOR]); + }, + + moveToOrigin: async () => { + await writeAnsi([ANSI_DYNAMIC.CURSOR_TO(origin.x, origin.y)]); + }, + + get availableHeight() { + return currentEnd.y - origin.y; + }, + + get availableWidth() { + return currentEnd.x - origin.x; + }, + }; + + async function trackPaintArea(fn: () => Promise) { + // Save initial cursor position + const [_, saveError] = await writeAnsi([ANSI_BUFFERS.SAVE_CURSOR]); + if (saveError) throw saveError; + + const [initialPos, initialPosError] = await tryCatch(getCursorPosition); + if (initialPosError) { + await writeAnsi([ANSI_BUFFERS.RESTORE_CURSOR]); + throw initialPosError; + } + if (initialPos) origin = initialPos; + + // Execute painting + await fn(); + + // Record new end position + const [endPos, endPosError] = await tryCatch(getCursorPosition); + if (endPosError) { + await writeAnsi([ANSI_BUFFERS.RESTORE_CURSOR]); + throw endPosError; + } + if (endPos) currentEnd = endPos; + + // Restore original cursor + await writeAnsi([ANSI_BUFFERS.RESTORE_CURSOR]); + } + + return { + async paint() { + if (!isActive) return; + await trackPaintArea(async () => { + await canvasAPI.moveToOrigin(); + await paint(canvasAPI); + }); + }, + + async destroy() { + if (!isActive) return; + isActive = false; + + if (cleanup) { + await cleanup({ + clear: canvasAPI.clear, + endPosition: currentEnd, + }); + } + }, + }; +} + +async function getCursorPosition(): Promise<{ x: number; y: number }> { + const [pos, posError] = await tryCatch(async () => { + await Bun.write(Bun.stdout, ANSI_BUFFERS.GET_CURSOR_POSITION); + + let response = ''; + const decoder = new TextDecoder(); + for await (const chunk of Bun.stdin.stream()) { + response += decoder.decode(chunk); + if (response.includes('R')) break; + } + + const [_, y, x] = response.match(/\[(\d+);(\d+)R/) || []; + if (!x) throw TypeError("The 'x' position was unset."); + if (!y) throw TypeError("The 'y' position was unset."); + return { x: parseInt(x), y: parseInt(y) }; + }); + + return pos || { x: 0, y: 0 }; +} diff --git a/src/lib/painter/canvas.ts b/src/lib/painter/canvas.ts new file mode 100644 index 0000000..5eff322 --- /dev/null +++ b/src/lib/painter/canvas.ts @@ -0,0 +1,6 @@ +export const createCanvas = ( + paint: () => Promise, + cleanup?: () => Promise +) => { + return [1]; +};