module src/renderers/js import src/renderer import bytearray // js ffi extern type Node extern io def getElementById(id: String): Node = jsWeb "document.getElementById(${id})" extern io def addListener(event: String, node: Node) {handler: () => Unit}: Unit = jsWeb "${node}.addEventListener(${event}, () => $effekt.runToplevel((ks) => ${box handler}(ks)))" // Custom logging function that logs to the console and the page extern io def log(msg: String): Unit = jsWeb "log(${msg});" extern io def loadRom(): ByteArray = jsWeb "rom ? rom : new Uint8Array(0)" extern io def requestAnimationFrame {callback: () => Unit}: Unit = jsWeb "requestAnimationFrame(() => $effekt.runToplevel((ks) => ${box callback}(ks)))" extern jsWeb """ let rom = undefined; // Global Log Function function log(msg) { const message = new Date().toLocaleTimeString() + ' - ' + msg + ' \n' console.log(message); // Logging with timestamp // document.getElementById('logs').innerText += message; } function romCheck() { document.getElementById('rom').addEventListener('change', async (event) => { rom = await event.target.files[0].bytes(); document.getElementById('start').removeAttribute('disabled'); }); } """ val pageContent = """ Effekt8

Effekt8: Chip8 Simulator written in Effekt Language



Logs:


      
""" // JS Renderer has 640x320 pixels, so we need to scale the screen by 10x. val width = 640 val height = 320 val scale = 10 extern io def renderPage(content: String): Unit = jsWeb "document.write(${content}); romCheck();" // Initialize the screen def init(run: (ByteArray) => Unit at {io, global}) = { renderPage(pageContent) val startButton = getElementById("start") addListener("click", startButton) {eventHandler {run}} } def eventHandler {onClick: (ByteArray) => Unit}: Unit = { log("Start button clicked! Loading ROM...") val rom: ByteArray = loadRom() if (rom.size() != 0) { log("ROM with size: " ++ show(rom.size()) ++ " loaded!") onClick(rom) } else { log("No ROM loaded! Please select a ROM file.") } } // Clear the screen (set black canvas) def clear(): Unit = fill("black") // Draw at (x, y) on the screen extern io def draw(x: Int, y: Int): Unit = jsWeb """ (() => { const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); ctx.fillStyle = 'white'; ctx.fillRect(10 * ${x}, 10 * ${y}, 10, 10); })(); """ extern io def fill(color: String): Unit = jsWeb """ (() => { const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); ctx.fillStyle = ${color}; ctx.fillRect(0, 0, canvas.width, canvas.height); })(); """ extern io def get(x: Int, y: Int): Bool = jsWeb """ (() => { const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const pixel = ctx.getImageData(10 * ${x}, 10 * ${y}, 1, 1).data; return pixel[0] === 255; })(); """ extern io def update(f: () => Unit at {io, global}): Unit = jsWeb """ setInterval(() => { $effekt.runToplevel((ks) => ${f}(ks)); }, 1000 / 120); """ namespace JSRenderer { // make JS Renderer def makeRenderer: Renderer = new Renderer { def init(run) = init(run) def clear() = clear() def draw(x: Int, y: Int) = draw(x, y) def update(f: () => Unit at {io, global}) = update(f) def log(msg: String) = log(msg) def fill(color: String) = fill(color) def get(x: Int, y: Int) = get(x, y) } }