Files
muuhd/frontend/ascii_window.js
Kim Ravn Hansen 5a5fd475d7 perms
2026-02-22 08:30:44 +01:00

134 lines
3.9 KiB
JavaScript

export class PseudoPixel {
/**
* @param {HTMLElement} htmlElement
* @param {string} char
* @param {number|string} color text/foreground color
*/
constructor(htmlElement, char = " ", color = "#fff") {
//
/** @type {HTMLElement} el the html element that makes up this cell*/
this.htmlElement = htmlElement;
/** @type {string} char */
this.char = char;
/** @type {number|string} fg color color */
this.color = color;
/** @type {boolean} Has this pixel's text content been updated since it was flushed to DOM ? */
this.dirtyChar = true;
/** @type {boolean} Has this pixel's color been updated since it was flushed to DOM ? */
this.dirtyColor = true;
}
clone() {
return new PseudoPixel(this.htmlElement, this.car, this.color);
}
}
export class AsciiWindow {
/**
* @param {HTMLElement} htmlElement the html element that contains all the pseudo-pixel elements
* @param {number} width Canvas width (in pseudo-pixels)
* @param {number} height Canvas height (in pseudo-pixels)
*/
constructor(htmlElement, width, height) {
//
/** @type {HTMLElement} the html element that contains all the pseudo-pixels */
this.htmlElement = htmlElement;
/** @type {number} width Canvas width (in pseudo-pixels) */
this.width = width;
/** @type {number} height Canvas height (in pseudo-pixels) */
this.height = height;
/** @type {PseudoPixel[]} */
this.pseudoPixels = [];
this.initializeCanvaas();
}
fill(char = " ", color = "#000") {
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
this.put(x, y, char, color);
}
}
}
/**
* Create the html elements that make up the canvas,
* as well as a buffer that holds a copy of the data
* in the cells so we can diff properly.
*/
initializeCanvaas() {
const w = this.width;
const h = this.height;
this.pseudoPixels = new Array(w * h).fill();
let i = 0;
for (let y = 0; y < h; y++) {
const rowEl = document.createElement("div");
this.htmlElement.appendChild(rowEl);
for (let x = 0; x < w; x++) {
const pixelEl = document.createElement("code");
rowEl.appendChild(pixelEl);
pixelEl.textContent = " ";
this.pseudoPixels[i] = new PseudoPixel(pixelEl, " ", "#fff");
i++;
}
}
}
withinBounds(x, y) {
return x >= 0 && x < this.width && y >= 0 && y < this.height;
}
mustBeWithinBounds(x, y) {
if (!this.withinBounds(x, y)) {
throw new Error(`Coordinate [${x}, ${y}] is out of bounds`);
}
}
put(x, y, char, color) {
//
this.mustBeWithinBounds(x, y);
const idx = this.width * y + x;
const pixel = this.pseudoPixels[idx];
// Check for changes in text contents
if (char !== undefined && char !== null && char !== pixel.char) {
pixel.char = char;
pixel.dirtyChar = true;
}
if (color !== undefined && color !== null && color !== pixel.color) {
pixel.color = color;
pixel.dirtyColor = true;
}
}
/**
* Apply all patches to the DOM
*
* @return {number} number of DOM updates made
*/
commitToDOM() {
this.pseudoPixels.forEach((pixel) => {
if (pixel.dirtyChar) {
pixel.htmlElement.textContent = pixel.char;
pixel.dirtyChar = false;
}
if (pixel.dirtyColor) {
pixel.htmlElement.style.color = pixel.color;
pixel.dirtyColor = false;
}
});
}
}