UNPKG

espruino-web-ide

Version:

A Terminal and Graphical code Editor for Espruino JavaScript Microcontrollers

263 lines (246 loc) 7.44 kB
<!DOCTYPE html> <html><!-- index for ws.js or totally online Web IDE --> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Bangle.js emulator</title> <style> body { overflow:hidden; } #emu { width:500px; height:500px; border:1px solid black; } #gfxdiv { position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:264px;height:244px; } #BTN1 { width:20px;height:73px;position:absolute;right:0px;top:0px; } #BTN2 { width:20px;height:73px;position:absolute;right:0px;top:73px; } #BTN3 { width:20px;height:73px;position:absolute;right:0px;top:146px; } #BTN4 { width:125px;height:240px;position:absolute;left:0px;top:0px; } #BTN5 { width:125px;height:240px;position:absolute;left:120px;top:0px; } #screenshot { width:20px;height:21px;position:absolute;right:0px;top:219px;color:white;text-decoration:none;background-color:black; } #gfxcanvas { image-rendering: pixelated; } </style> </head> <body> <div id="gfxdiv"> <canvas id="gfxcanvas" width="240" height="240"></canvas> <button id="BTN1">1</button> <button id="BTN2">2</button> <button id="BTN3">3</button> <a id="screenshot">&#x1F4F7;</a> <div id="BTN4"></div> <div id="BTN5"></div> </div> <script src="js/plugins/emulator_espruino.js"></script> <script> var DEVICE = 21; // USB var BTN1 = 24; var BTN2 = 22; var BTN3 = 23; var BTN4 = 11; var BTN5 = 16; var jsRXCallback; var jsIdleTimeout; // used to interface to Espruino emscripten... var hwPinValue = new Uint8Array(32); var flashMemory = new Uint8Array(4096*1024); var maxFlashMemory = 0; flashMemory.fill(255); if (window.localStorage) { let s = localStorage.getItem("BANGLE_STORAGE"); if (s!=null) { s.split(",").forEach((n,i)=>{ flashMemory[i] = parseInt(n); maxFlashMemory = i; }); } } // Save storage contents on exit window.addEventListener("unload", function() { var a = new Uint8Array(flashMemory.buffer, 0, maxFlashMemory+1); localStorage.setItem("BANGLE_STORAGE", a.toString()); // 1,2,3,4, etc... }); function hwSetPinValue(pin,v) { hwPinValue[pin] = v; } function hwGetPinValue(pin) { return hwPinValue[pin]; } function hwFlashWrite(addr,v) { flashMemory[addr] = v; if (addr>maxFlashMemory) maxFlashMemory=addr; } function hwFlashRead(addr) { return flashMemory[addr]; } function jsHandleIO() { var device; var l = ""; do { device = Module.ccall('jshGetDeviceToTransmit', 'number', [], []); if (device) { var ch = Module.ccall('jshGetCharToTransmit', 'number', ['number'], [device]); if (jsRXCallback) jsRXCallback(ch); l += String.fromCharCode(ch); var ll = l.split("\n"); if (ll.length>1) { console.log("EMSCRIPTEN:",ll[0]); l = ll[1]; } } } while (device); } function jsTransmitChar(c) { //console.log("TX -> ",c); Module.ccall('jshPushIOCharEvent', 'number', ['number','number'], [DEVICE,c]); jsIdle(); } function jsTransmitPinEvent(pin) { //console.log("TX -> ",c); Module.ccall('jsSendPinWatchEvent', 'number', ['number'], [pin]); jsIdle(); } function jsIdle() { if (jsIdleTimeout) { clearTimeout(jsIdleTimeout); jsIdleTimeout = undefined; } var tries = 5; var msToNext = -1; while (tries-- && msToNext<0) { msToNext = Module.ccall('jsIdle', 'number', [], []); jsHandleIO(); } if (msToNext<10) msToNext=10; jsIdleTimeout = setTimeout(jsIdle,msToNext); jsUpdateGfx(); } function jsUpdateGfx() { var changed = Module.ccall('jsGfxChanged', 'number', [], []); if (changed) { var canvas = document.getElementById('gfxcanvas'); var ctx = canvas.getContext('2d'); var imgData = ctx.createImageData(240, 240); var rgba = imgData.data; var i = 0; for (var y=0;y<240;y++) { var p = Module.ccall('jsGfxGetPtr', 'number', ['number'], [y])>>1; for (var x=0;x<240;x++) { var c = p ? Module.HEAP16[p+x] : 0; rgba[i++]=(c>>8)&0xF8; rgba[i++]=(c>>3)&0xFC; rgba[i++]=(c<<3)&0xF8; rgba[i++]=255; } } ctx.putImageData(imgData, 0, 0); } jsHandleIO(); } function jsInit() { var div = document.getElementById("gfxdiv"); function handleButton(n, pin) { hwPinValue[pin]=1; // inverted var btn = document.getElementById("BTN"+n); btn.addEventListener('mousedown', e => { e.preventDefault(); hwPinValue[pin]=0; // inverted jsTransmitPinEvent(pin); }); btn.addEventListener('mouseup', e => { hwPinValue[pin]=1; // inverted jsTransmitPinEvent(pin); }); btn.addEventListener('mouseenter', e => { if (e.buttons) { hwPinValue[pin]=0; // inverted jsTransmitPinEvent(pin); } }); btn.addEventListener('mouseleave', e => { // mouseleave comes *before* mouseenter usually - so delay it // in order to get overlap when swiping to make Bangle.js screen swipes work setTimeout(function() { if (!hwPinValue[pin]) { hwPinValue[pin]=1; // inverted jsTransmitPinEvent(pin); } },10); }); } handleButton(1,BTN1); handleButton(2,BTN2); handleButton(3,BTN3); handleButton(4,BTN4); handleButton(5,BTN5); document.getElementById("screenshot").addEventListener('click', e => { document.getElementById("screenshot").href = document.getElementById("gfxcanvas").toDataURL(); document.getElementById("screenshot").download = "screenshot.png"; }); Module.ccall('jsInit', 'number', [], []); jsHandleIO(); } function jsKill() { var d = document.getElementById("gfxdiv"); if (d) d.remove(); Module.ccall('jsKill', 'number', [], []); } function onResize(e) { var scale = Math.floor(Math.min(window.innerWidth/264, window.innerHeight/244)*2)/2; document.getElementById("gfxdiv").style.transform = "translate(-50%,-50%) scale("+scale+","+scale+")"; } window.addEventListener('resize', onResize); onResize(); /* =========================================================================== Frame to frame comms =========================================================================== HOST sends: {type:"rx",for:"emu",data:string} {type:"init",for:"emu"} - set up the backchannel WE send: {type:"tx",from:"emu",data:string} */ var hostWindow = window.parent; window.addEventListener('message', function(e) { var event = e.data; console.log("EMU MESSAGE ---------------------------------------"); console.log(JSON.stringify(event,null,2)); console.log("-----------------------------------------------"); if (typeof event!="object" || event.for!="emu") return; switch (event.type) { case "init": { console.log("HOST WINDOW CONFIGURED"); hostWindow = e.source; post({type:"init"}); jsInit(); jsIdle(); } break; case "rx": { var d = event.data; if (typeof d!="string") console.error("receive event expecting data string"); for (var i=0;i<d.length;i++) { jsTransmitChar(d.charCodeAt(i)); } } break; default: console.error("Unknown event type ",event.type); break; } }); function post(msg) { msg.from="emu"; hostWindow.postMessage(msg,"*"); } jsRXCallback = function(c) { post({type:"tx",data:String.fromCharCode(c)}); } console.log("Waiting for posted {type:init} message"); </script> </body> </html>