const EVENT_UP = 0; const EVENT_DOWN = 1; const EVENT_SELECT = 2; const EVENT_BACK = 3; const canvas = document.getElementById('display'); const context = canvas.getContext('2d'); const status = document.getElementById('status'); const buttons = Array.from(document.querySelectorAll('[data-event]')); let wasm; let lastPointerTime = -Infinity; async function loadWasm() { const imports = {}; const response = fetch('./pkg/kinda.wasm'); if ('instantiateStreaming' in WebAssembly) { try { return await WebAssembly.instantiateStreaming(response, imports); } catch (error) { console.warn('Falling back to ArrayBuffer WebAssembly load', error); } } const bytes = await (await fetch('./pkg/kinda.wasm')).arrayBuffer(); return WebAssembly.instantiate(bytes, imports); } function render() { const width = wasm.kinda_sim_width(); const height = wasm.kinda_sim_height(); const pixelPointer = wasm.kinda_sim_pixels(); const pixelLength = wasm.kinda_sim_pixel_len(); const pixels = new Uint8Array(wasm.memory.buffer, pixelPointer, pixelLength); canvas.width = width; canvas.height = height; const image = context.createImageData(width, height); for (let source = 0, target = 0; source < pixels.length; source += 1, target += 4) { const value = Math.round(18 + (pixels[source] * 230) / 255); image.data[target] = value; image.data[target + 1] = value; image.data[target + 2] = value; image.data[target + 3] = 255; } context.putImageData(image, 0, 0); } function sendInput(eventCode) { if (!wasm) { return; } wasm.kinda_sim_input(eventCode); render(); } function sendTouch(clientX, clientY) { if (!wasm) { return; } const bounds = canvas.getBoundingClientRect(); const x = Math.max( 0, Math.min(canvas.width - 1, Math.floor(((clientX - bounds.left) * canvas.width) / bounds.width)) ); const y = Math.max( 0, Math.min(canvas.height - 1, Math.floor(((clientY - bounds.top) * canvas.height) / bounds.height)) ); wasm.kinda_sim_touch(x, y); render(); } function capturePointer(event) { if (!canvas.setPointerCapture || event.pointerId === undefined) { return; } try { canvas.setPointerCapture(event.pointerId); } catch (_) { // Synthetic and some browser-generated events do not have an active pointer. } } function handlePointerDown(event) { event.preventDefault(); lastPointerTime = event.timeStamp; capturePointer(event); sendTouch(event.clientX, event.clientY); } function handleMouseDown(event) { if (event.timeStamp - lastPointerTime < 50) { return; } event.preventDefault(); sendTouch(event.clientX, event.clientY); } function handleTouchStart(event) { if (event.timeStamp - lastPointerTime < 50) { return; } const touch = event.changedTouches[0]; if (!touch) { return; } event.preventDefault(); sendTouch(touch.clientX, touch.clientY); } function keyboardEventCode(event) { if (event.key === 'ArrowUp') { return EVENT_UP; } if (event.key === 'ArrowDown') { return EVENT_DOWN; } if (event.key === 'Enter') { return EVENT_SELECT; } if (event.key === 'Escape' || event.key === 'Backspace') { return EVENT_BACK; } return null; } buttons.forEach((button) => { button.disabled = true; button.addEventListener('click', () => sendInput(Number(button.dataset.event))); }); canvas.addEventListener('pointerdown', handlePointerDown); canvas.addEventListener('mousedown', handleMouseDown); canvas.addEventListener('touchstart', handleTouchStart, { passive: false }); window.addEventListener('keydown', (event) => { const eventCode = keyboardEventCode(event); if (eventCode === null) { return; } event.preventDefault(); sendInput(eventCode); }); try { const instance = await loadWasm(); wasm = instance.instance.exports; wasm.kinda_sim_init(); render(); buttons.forEach((button) => { button.disabled = false; }); status.textContent = 'Ready. Press buttons or click/touch the display.'; } catch (error) { console.error(error); status.textContent = error instanceof Error ? error.message : 'Could not load simulator.'; }