// import type { UIValue } from './types'; import { valueToUI } from './valueToUI-compiled'; import { render, hitTest } from './ui'; type UIValue = any; type App = { init: any; update: (state: any) => (event: any) => any; view: (state: any) => (viewport: any) => any; } type ComponentInstance = { state: any; update: (state: any) => (event: any) => any; view: (state: any) => any; }; export function runAppCompiled(app: App, canvas: HTMLCanvasElement, rt: any) { let state = app.init; const componentInstances = new Map(); let focusedComponentKey: string | null = null; function setupCanvas() { const dpr = window.devicePixelRatio || 1; canvas.width = window.innerWidth * dpr; canvas.height = window.innerHeight * dpr; canvas.style.width = window.innerWidth + 'px'; canvas.style.height = window.innerHeight + 'px'; } setupCanvas(); function setFocus(componentKey: string | null) { if (focusedComponentKey === componentKey) return; const oldFocus = focusedComponentKey; focusedComponentKey = componentKey; // Blur event to the previous if (oldFocus && componentInstances.has(oldFocus)) { handleComponentEvent(oldFocus, { _tag: 'Blurred' }); } // Focus event to the new if (componentKey && componentInstances.has(componentKey)) { handleComponentEvent(componentKey, { name: 'Focused' }); } rerender(); } function handleComponentEvent(componentKey: string, event: any) { const instance = componentInstances.get(componentKey); if (!instance) return; try { const result = instance.update(instance.state)(event); instance.state = result.state; if (result.emit && Array.isArray(result.emit)) { for (const e of result.emit) { handleEvent(e); } } rerender(); } catch(error) { console.error('Component event error:', error); } } function expandStateful(ui: UIValue, path: number[]): UIValue { switch (ui.kind) { case 'stateful': { const fullKey = [...path, ui.key].join('.'); let instance = componentInstances.get(fullKey); if (!instance) { instance = { state: ui.init, update: ui.update, view: ui.view }; componentInstances.set(fullKey, instance); } else { // refresh closures, pick up new values instance.update = ui.update; instance.view = ui.view; } const viewResult = instance.view(instance.state); let viewUI = valueToUI(viewResult); if (ui.focusable) { viewUI = { kind: 'clickable', child: viewUI, event: { _tag: 'FocusAndClick', _0: fullKey } }; } return expandStateful(viewUI, path); } case 'stack': case 'row': case 'column': { return { ...ui, children: ui.children.map((child: UIValue, i: number) => expandStateful(child, [...path, i]) ) } } case 'clickable': case 'padding': case 'positioned': case 'opacity': case 'clip': { return { ...ui, child: expandStateful((ui as any).child, [...path, 0]) }; } default: // leaf nodes return ui; } } function rerender() { const viewport = { width: window.innerWidth, height: window.innerHeight }; try { const uiValue = app.view(state)(viewport); const ui = valueToUI(uiValue); const expandedUI = expandStateful(ui, []); render(expandedUI, canvas); } catch (error) { console.error('Render error:', error); } } function handleEvent(event: any) { if (!event || !event._tag) return; if (event._tag === 'Batch' && event._0) { for (const e of event._0) { handleEvent(e); } return; } if (event._tag === 'FocusAndClick') { const componentKey = event._0; const coords = event._1; setFocus(componentKey); handleComponentEvent(componentKey, { _tag: 'Clicked', _0: coords }); return; } if (event._tag === 'Rebind') { rt.rebind(event._0, event._1, event._2); rerender(); return; } if (event._tag === 'Focus') { setFocus(event._0); return; } if (event._tag === 'NoOp') return; state = app.update(state)(event); rerender(); } canvas.addEventListener('click', (e) => { const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const hitResult = hitTest(x, y); if (hitResult) { const { event, relativeX, relativeY } = hitResult; if (event._tag === 'FocusAndClick') { handleEvent({ _tag: 'FocusAndClick', _0: event._0, _1: { x: Math.floor(relativeX), y: Math.floor(relativeY) } }); } else { handleEvent(event); } } }); window.addEventListener('keydown', (e) => { let event: any; if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) { event = { _tag: 'Char', _0: e.key }; } else { event = { _tag: e.key }; } if (focusedComponentKey) { handleComponentEvent(focusedComponentKey, event); } else { handleEvent(event); } e.preventDefault(); }); window.addEventListener('resize', () => { setupCanvas(); rerender(); }) rerender(); }