Started working on CG UI primitives. rendering to a canvas

This commit is contained in:
Dustin Swan 2026-02-01 21:35:04 -07:00
parent 232d9351c1
commit 52647a9ce1
No known key found for this signature in database
GPG key ID: 30D46587E2100467
4 changed files with 178 additions and 45 deletions

90
src/ui.ts Normal file
View file

@ -0,0 +1,90 @@
import type { UIValue } from './types';
export function render(ui: UIValue, canvas: HTMLCanvasElement) {
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
renderUI(ui, ctx, 0, 0);
}
}
function renderUI(ui: UIValue, ctx: CanvasRenderingContext2D, x: number, y: number) {
switch (ui.kind) {
case 'rect':
ctx.fillStyle = ui.color;
ctx.fillRect(x, y, ui.w, ui.h);
break;
case 'text':
ctx.fillStyle = 'black';
ctx.font = '16px monospace';
ctx.fillText(ui.content, x + ui.x, y + ui.y);
break;
case 'row': {
let offsetX = 0;
for (const child of ui.children) {
renderUI(child, ctx, x + offsetX, y);
offsetX += measure(child).width + ui.gap;
}
break;
}
case 'column': {
let offsetY = 0;
for (const child of ui.children) {
renderUI(child, ctx, x, y + offsetY);
offsetY += measure(child).height + ui.gap;
}
break;
}
case 'clickable': {
renderUI(ui.child, ctx, x, y);
break;
}
case 'padding':
renderUI(ui.child, ctx, x + ui.amount, y + ui.amount);
break;
}
}
function measure(ui: UIValue): { width: number, height: number } {
switch (ui.kind) {
case 'rect': return { width: ui.w, height: ui.h };
case 'text': return { width: ui.content.length * 10, height: 20 }; // TODO
case 'row': {
let totalWidth = 0;
let maxHeight = 0;
for (const child of ui.children) {
const size = measure(child);
totalWidth += size.width;
maxHeight = Math.max(maxHeight, size.height);
}
totalWidth += ui.gap * (ui.children.length - 1);
return { width: totalWidth, height: maxHeight };
}
case 'column': {
let totalHeight = 0;
let maxWidth = 0;
for (const child of ui.children) {
const size = measure(child);
totalHeight += size.height;
maxWidth = Math.max(maxWidth, size.width);
}
totalHeight += ui.gap * (ui.children.length - 1);
return { width: maxWidth, height: totalHeight };
}
case 'clickable':
return measure(ui.child);
case 'padding': {
const childSize = measure(ui.child);
return {
width: childSize.width + ui.amount * 2,
height: childSize.height + ui.amount * 2,
}
}
}
return { width: 0, height: 0 };
}