import type { Value } from './types'; import { valueToUI } from './valueToUI'; import { render, hitTest, hitTestTextInput, handleKeyboard } from './ui'; import { evaluate } from './interpreter'; export type App = { init: Value; update: Value; // State / Event / State view: Value; // State / UI } export function runApp(app: App, canvas: HTMLCanvasElement) { let state = app.init; function rerender() { if (app.view.kind !== 'closure') throw new Error('view must be a function'); const callEnv = new Map(app.view.env); callEnv.set(app.view.params[0], state); const uiValue = evaluate(app.view.body, callEnv); const ui = valueToUI(uiValue); render(ui, canvas); } function handleEvent(event: Value) { if (app.update.kind !== 'closure') throw new Error('update must be a function'); if (app.update.params.length !== 2) throw new Error('update must have 2 parameters'); const callEnv = new Map(app.update.env); callEnv.set(app.update.params[0], state); callEnv.set(app.update.params[1], event); const newState = evaluate(app.update.body, callEnv); state = newState; rerender(); } canvas.addEventListener('click', (e) => { const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; // Text Inputs const hitTextInput = hitTestTextInput(x, y); if (hitTextInput) { rerender(); return; } const eventName = hitTest(x, y); if (eventName) { const event: Value = { kind: 'constructor', name: eventName, args: [] } handleEvent(event); } }); window.addEventListener('keydown', (e) => { const result = handleKeyboard(e.key); if (result) { const event: Value = { kind: 'constructor', name: result.event, args: [{ kind: 'string', value: result.value }] } handleEvent(event); e.preventDefault(); } }) rerender(); }