Started working on CG UI primitives. rendering to a canvas
parent
232d9351c1
commit
52647a9ce1
@ -1,47 +1,30 @@
|
|||||||
import { evaluate } from './interpreter'
|
import type { UIValue } from './types';
|
||||||
import type { Env } from './env'
|
import { render } from './ui';
|
||||||
import { tokenize } from './lexer'
|
|
||||||
import { Parser } from './parser'
|
|
||||||
import { prettyPrint } from './ast';
|
|
||||||
|
|
||||||
function e(str: string) {
|
const canvas = document.createElement('canvas');
|
||||||
console.log(str);
|
canvas.width = 800;
|
||||||
const tokens = tokenize(str);
|
canvas.height = 600;
|
||||||
console.log(tokens);
|
document.body.appendChild(canvas);
|
||||||
const p = new Parser(tokens);
|
|
||||||
const ast = p.parse();
|
|
||||||
console.log(ast);
|
|
||||||
console.log(prettyPrint(ast));
|
|
||||||
const env: Env = new Map();
|
|
||||||
console.log(evaluate(ast, env));
|
|
||||||
}
|
|
||||||
|
|
||||||
e('add1 = (x \\ x + 1); add1 3');
|
const ui: UIValue = {
|
||||||
e('sum = x y \\ x + y; sum 5 3');
|
kind: 'column',
|
||||||
e('[1, 2, 3]');
|
gap: 10,
|
||||||
e('c = 5; { a = 3, b = c }');
|
children: [
|
||||||
e('rec = { a = 3, b = 5 }; rec.a');
|
{ kind: 'text', content: "Hello CG World", x: 0, y: 20 },
|
||||||
e('rec = { a = 3, b = 5 }; rec{ a = 10 }');
|
{ kind: 'rect', w: 200, h: 50, color: 'blue' },
|
||||||
e('add1 = (x \\ x + 1); 3 > add1');
|
{ kind: 'text', content: "YESS", x: 0, y: 20 },
|
||||||
e('[1, 2] & [3, 4]');
|
{ kind: 'row', gap: 13, children:
|
||||||
e('"abc" & "def"');
|
[
|
||||||
// e('n | 0 \\ 1 | _ \\ 99');
|
{ kind: 'text', content: "In a row", x: 0, y: 10 },
|
||||||
// e('m | Some x \\ 1 | None \\ 0');
|
{ kind: 'clickable', event: "test", child: {
|
||||||
// e('head = list \\ list | [x, _] \\ Some x | [] \\ None; head');
|
kind: 'padding', amount: 10, child: {
|
||||||
e('n = 5; n | 5 \\ "five" | _ \\ "other"');
|
kind: 'rect', w: 80, h: 30, color: '#4a90e2'
|
||||||
e('list = [1, 2, 3]; list | [x, y, z] \\ x + y + z');
|
}
|
||||||
e('point = {x = 5, y = 10}; point | {x = px, y = py} \\ px + py');
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
e(`factorial = n \\ n
|
render(ui, canvas);
|
||||||
| 0 \\ 1
|
|
||||||
| n \\ n * factorial (n - 1);
|
|
||||||
|
|
||||||
factorial 5`)
|
|
||||||
|
|
||||||
e(`factorial = n \\ n
|
|
||||||
| 0 \\ 1
|
|
||||||
| n \\ n * factorial (n - 1);
|
|
||||||
|
|
||||||
factorial 5`)
|
|
||||||
|
|
||||||
e('some5 = Some 5; some5');
|
|
||||||
|
|||||||
@ -0,0 +1,52 @@
|
|||||||
|
import { evaluate } from './interpreter'
|
||||||
|
import type { Env } from './env'
|
||||||
|
import { tokenize } from './lexer'
|
||||||
|
import { Parser } from './parser'
|
||||||
|
import { prettyPrint } from './ast';
|
||||||
|
|
||||||
|
function e(str: string) {
|
||||||
|
console.log(str);
|
||||||
|
const tokens = tokenize(str);
|
||||||
|
console.log(tokens);
|
||||||
|
const p = new Parser(tokens);
|
||||||
|
const ast = p.parse();
|
||||||
|
console.log(ast);
|
||||||
|
console.log(prettyPrint(ast));
|
||||||
|
const env: Env = new Map();
|
||||||
|
console.log(evaluate(ast, env));
|
||||||
|
}
|
||||||
|
|
||||||
|
e('add1 = (x \\ x + 1); add1 3');
|
||||||
|
e('sum = x y \\ x + y; sum 5 3');
|
||||||
|
e('[1, 2, 3]');
|
||||||
|
e('c = 5; { a = 3, b = c }');
|
||||||
|
e('rec = { a = 3, b = 5 }; rec.a');
|
||||||
|
e('rec = { a = 3, b = 5 }; rec{ a = 10 }');
|
||||||
|
e('add1 = (x \\ x + 1); 3 > add1');
|
||||||
|
e('[1, 2] & [3, 4]');
|
||||||
|
e('"abc" & "def"');
|
||||||
|
e('head = list \\ list | [x, _] \\ Some x | [] \\ None; head');
|
||||||
|
e('n = 5; n | 5 \\ "five" | _ \\ "other"');
|
||||||
|
e('list = [1, 2, 3]; list | [x, y, z] \\ x + y + z');
|
||||||
|
e('point = {x = 5, y = 10}; point | {x = px, y = py} \\ px + py');
|
||||||
|
e('some5 = Some 5; some5');
|
||||||
|
|
||||||
|
e(`factorial = n \\ n
|
||||||
|
| 0 \\ 1
|
||||||
|
| n \\ n * factorial (n - 1);
|
||||||
|
|
||||||
|
factorial 5`)
|
||||||
|
|
||||||
|
e(`
|
||||||
|
factorial = n \\ n
|
||||||
|
| 0 \\ 1
|
||||||
|
| n \\ n * factorial (n - 1);
|
||||||
|
|
||||||
|
fib = n \\ n
|
||||||
|
| 0 \\ 0
|
||||||
|
| 1 \\ 1
|
||||||
|
| n \\ fib (n - 1) + fib (n - 2);
|
||||||
|
|
||||||
|
[ fib 10, factorial 5 ];
|
||||||
|
`);
|
||||||
|
|
||||||
@ -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 };
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue