You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
84 lines
2.2 KiB
TypeScript
84 lines
2.2 KiB
TypeScript
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();
|
|
}
|