espruino-web-ide
Version:
A Terminal and Graphical code Editor for Espruino JavaScript Microcontrollers
263 lines (246 loc) • 7.44 kB
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">📷</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>