|
|
|
|
@ -4,12 +4,13 @@ export type UIValue =
|
|
|
|
|
| { kind: 'text', content: string, color?: string }
|
|
|
|
|
| { kind: 'row', children: UIValue[], gap: number }
|
|
|
|
|
| { kind: 'column', children: UIValue[], gap: number }
|
|
|
|
|
| { kind: 'clickable', child: UIValue, event: any }
|
|
|
|
|
| { kind: 'padding', child: UIValue, amount: number }
|
|
|
|
|
| { kind: 'positioned', x: number, y: number, child: UIValue }
|
|
|
|
|
| { kind: 'opacity', child: UIValue, opacity: number }
|
|
|
|
|
| { kind: 'clip', child: UIValue, w: number, h: number }
|
|
|
|
|
| { kind: 'stack', children: UIValue[] }
|
|
|
|
|
| { kind: 'clickable', child: UIValue, onClick: any }
|
|
|
|
|
| { kind: 'scrollable', child: UIValue, w: number, h: number, scrollX: number, scrollY: number, onScroll: any }
|
|
|
|
|
| { kind: 'stateful', key: string, focusable: boolean, init: any, update: any, view: any }
|
|
|
|
|
|
|
|
|
|
type ClickRegion = {
|
|
|
|
|
@ -17,11 +18,21 @@ type ClickRegion = {
|
|
|
|
|
y: number;
|
|
|
|
|
width: number;
|
|
|
|
|
height: number;
|
|
|
|
|
event: any;
|
|
|
|
|
onClick: any;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let clickRegions: ClickRegion[] = [];
|
|
|
|
|
|
|
|
|
|
type ScrollRegion = {
|
|
|
|
|
x: number;
|
|
|
|
|
y: number;
|
|
|
|
|
width: number;
|
|
|
|
|
height: number;
|
|
|
|
|
onScroll: any;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let scrollRegions: ScrollRegion[] = [];
|
|
|
|
|
|
|
|
|
|
export function render(ui: UIValue, canvas: HTMLCanvasElement) {
|
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
|
if (ctx) {
|
|
|
|
|
@ -32,6 +43,7 @@ export function render(ui: UIValue, canvas: HTMLCanvasElement) {
|
|
|
|
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
|
|
|
|
|
|
|
|
clickRegions = [];
|
|
|
|
|
scrollRegions = [];
|
|
|
|
|
renderUI(ui, ctx, 0, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -88,7 +100,7 @@ function renderUI(ui: UIValue, ctx: CanvasRenderingContext2D, x: number, y: numb
|
|
|
|
|
case 'row': {
|
|
|
|
|
let offsetX = 0;
|
|
|
|
|
for (const child of ui.children) {
|
|
|
|
|
const size = measure(child);
|
|
|
|
|
// const size = measure(child);
|
|
|
|
|
renderUI(child, ctx, x + offsetX, y);
|
|
|
|
|
offsetX += measure(child).width + (ui.gap || 0);
|
|
|
|
|
}
|
|
|
|
|
@ -106,11 +118,22 @@ function renderUI(ui: UIValue, ctx: CanvasRenderingContext2D, x: number, y: numb
|
|
|
|
|
|
|
|
|
|
case 'clickable': {
|
|
|
|
|
const size = measure(ui.child);
|
|
|
|
|
clickRegions.push({ x, y, width: size.width, height: size.height, event: ui.event })
|
|
|
|
|
clickRegions.push({ x, y, width: size.width, height: size.height, onClick: ui.onClick })
|
|
|
|
|
renderUI(ui.child, ctx, x, y);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case 'scrollable': {
|
|
|
|
|
ctx.save();
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
ctx.rect(x, y, ui.w, ui.h);
|
|
|
|
|
ctx.clip();
|
|
|
|
|
renderUI(ui.child, ctx, x - ui.scrollX, y - ui.scrollY);
|
|
|
|
|
ctx.restore();
|
|
|
|
|
scrollRegions.push({ x, y, width: ui.w, height: ui.h, onScroll: ui.onScroll })
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case 'padding':
|
|
|
|
|
renderUI(ui.child, ctx, x + ui.amount, y + ui.amount);
|
|
|
|
|
break;
|
|
|
|
|
@ -187,6 +210,9 @@ export function _measure(ui: UIValue): { width: number, height: number } {
|
|
|
|
|
case 'clickable':
|
|
|
|
|
return measure(ui.child);
|
|
|
|
|
|
|
|
|
|
case 'scrollable':
|
|
|
|
|
return { width: ui.w, height: ui.h };
|
|
|
|
|
|
|
|
|
|
case 'opacity':
|
|
|
|
|
return measure(ui.child);
|
|
|
|
|
|
|
|
|
|
@ -223,14 +249,14 @@ export function _measure(ui: UIValue): { width: number, height: number } {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function hitTest(x: number, y: number): { event: any, relativeX: number, relativeY: number } | null {
|
|
|
|
|
export function hitTest(x: number, y: number): { onClick: any, relativeX: number, relativeY: number } | null {
|
|
|
|
|
for (let i = clickRegions.length - 1; i >= 0; i--) {
|
|
|
|
|
const region = clickRegions[i];
|
|
|
|
|
|
|
|
|
|
if (x >= region.x && x < region.x + region.width &&
|
|
|
|
|
y >= region.y && y < region.y + region.height) {
|
|
|
|
|
return {
|
|
|
|
|
event: region.event,
|
|
|
|
|
onClick: region.onClick,
|
|
|
|
|
relativeX: x - region.x,
|
|
|
|
|
relativeY: y - region.y,
|
|
|
|
|
};
|
|
|
|
|
@ -238,3 +264,14 @@ export function hitTest(x: number, y: number): { event: any, relativeX: number,
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function scrollHitTest(x: number, y: number): { onScroll: any } | null {
|
|
|
|
|
for (let i = scrollRegions.length - 1; i >= 0; i--) {
|
|
|
|
|
const region = scrollRegions[i];
|
|
|
|
|
if (x >= region.x && x < region.x + region.width &&
|
|
|
|
|
y >= region.y && y < region.y + region.height) {
|
|
|
|
|
return { onScroll: region.onScroll };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|