[ion/simulator/web] Use a shared layout.json file

This commit is contained in:
Romain Goyet
2021-02-23 21:11:55 -05:00
committed by Léa Saviot
parent 32d591317b
commit 1aac2a1d4d
7 changed files with 328 additions and 273 deletions

View File

@@ -0,0 +1,61 @@
{
"background": [0, 0, 1160, 2220],
"screen": [192, 191, 776, 582],
"keys": {
"0": [133, 983, 140, 87],
"1": [231, 886, 87, 140],
"2": [231, 1026, 87, 140],
"3": [275, 983, 140, 87],
"4": [745, 963, 129, 129],
"5": [898, 963, 129, 129],
"6": [502, 908, 156, 101],
"7": [502, 1044, 156, 101],
"12": [133, 1210, 129, 87],
"13": [286, 1210, 129, 87],
"14": [439, 1210, 129, 87],
"15": [592, 1210, 129, 87],
"16": [745, 1210, 129, 87],
"17": [898, 1210, 129, 87],
"18": [133, 1332, 129, 87],
"19": [286, 1332, 129, 87],
"20": [439, 1332, 129, 87],
"21": [592, 1332, 129, 87],
"22": [745, 1332, 129, 87],
"23": [898, 1332, 129, 87],
"24": [133, 1455, 129, 87],
"25": [286, 1455, 129, 87],
"26": [439, 1455, 129, 87],
"27": [592, 1455, 129, 87],
"28": [745, 1455, 129, 87],
"29": [898, 1455, 129, 87],
"30": [133, 1578, 156, 101],
"31": [317, 1578, 156, 101],
"32": [502, 1578, 156, 101],
"33": [686, 1578, 156, 101],
"34": [871, 1578, 156, 101],
"36": [133, 1715, 156, 101],
"37": [317, 1715, 156, 101],
"38": [502, 1715, 156, 101],
"39": [686, 1715, 156, 101],
"40": [871, 1715, 156, 101],
"42": [133, 1851, 156, 101],
"43": [317, 1851, 156, 101],
"44": [502, 1851, 156, 101],
"45": [686, 1851, 156, 101],
"46": [871, 1851, 156, 101],
"48": [133, 1988, 156, 101],
"49": [317, 1988, 156, 101],
"50": [502, 1988, 156, 101],
"51": [686, 1988, 156, 101],
"52": [871, 1988, 156, 101]
}
}

View File

@@ -36,6 +36,12 @@ endif
DEFAULT = epsilon.zip
$(addprefix $(BUILD_DIR)/ion/src/simulator/web/,calculator.html calculator.css): ion/src/simulator/shared/layout.json
$(call rule_label,LAYOUT)
$(Q) $(PYTHON) ion/src/simulator/web/layout.py --html $(BUILD_DIR)/ion/src/simulator/web/calculator.html --css $(BUILD_DIR)/ion/src/simulator/web/calculator.css $<
$(BUILD_DIR)/ion/src/simulator/web/simulator.html: $(addprefix $(BUILD_DIR)/ion/src/simulator/web/,calculator.html calculator.css)
$(BUILD_DIR)/epsilon%zip: $(BUILD_DIR)/epsilon%js $(BUILD_DIR)/ion/src/simulator/web/simulator.html
@rm -rf $(basename $@)
@mkdir -p $(basename $@)

View File

@@ -0,0 +1,98 @@
#!/usr/bin/env python3
import argparse
import json
def css_rule(selector, *declarations):
css = ''
css += selector + ' {\n'
for decl in declarations:
css += decl
css += '}\n\n'
return css
def css_declaration(prop, value):
return " %s: %s;\n" % (prop, value)
def css_percentage_declaration(prop, ratio):
return css_declaration(prop, "%.2f%%" % (100*ratio))
def css_rect_declarations(rect, ref):
css = ''
css += css_percentage_declaration("left", rect[0]/ref[2])
css += css_percentage_declaration("top", rect[1]/ref[3])
css += css_percentage_declaration("width", rect[2]/ref[2])
css += css_percentage_declaration("height", rect[3]/ref[3])
return css
def css(layout):
background = layout["background"]
css = ''
css += css_rule(
'.calculator',
css_declaration('position', 'relative'),
)
css += css_rule(
'.calculator-aspect-ratio',
css_percentage_declaration('padding-bottom', background[3]/background[2])
)
css += css_rule(
'.calculator canvas',
css_declaration('position', 'absolute'),
css_rect_declarations(layout['screen'], background)
)
css += css_rule(
'.calculator span',
css_declaration('position', 'absolute'),
css_declaration('display', 'block'),
css_declaration('border-radius', '999px'),
css_declaration('cursor', 'pointer'),
)
css += css_rule(
'.calculator span:hover',
css_declaration('background-color', 'rgba(0, 0, 0, 0.1)')
)
css += css_rule(
'.calculator span:active',
css_declaration('background-color', 'rgba(0, 0, 0, 0.2)')
)
for key,rect in layout["keys"].items():
css += css_rule(
'.calculator span[data-key="%s"]' % key,
css_rect_declarations(rect, background)
)
css += css_rule(
'.calculator-mirror canvas',
css_declaration('image-rendering', '-moz-crisp-edges'),
css_declaration('image-rendering', 'pixelated'),
css_declaration('-ms-interpolation-mode', 'nearest-neighbor')
)
return css
def html(layout):
screen = layout["screen"]
background = layout["background"]
html = ''
html += '<div class="calculator">\n'
html += ' <canvas tabindex="1"></canvas>\n'
for key in layout["keys"]:
html += ' <span data-key="%s"></span>\n' % key
html += '</div>'
return html
# Argument parsing
parser = argparse.ArgumentParser(description='Generate HTML and CSS files from JSON layout')
parser.add_argument('file', type=str, help='a JSON layout file')
parser.add_argument('--css', type=str, help='an output HTML file')
parser.add_argument('--html', type=str, help='an output CSS file')
args = parser.parse_args()
with open(args.file) as f:
layout = json.load(f)
if args.css:
with open(args.css, 'w') as o:
o.write(css(layout))
if args.html:
with open(args.html, 'w') as o:
o.write(html(layout))

View File

@@ -1,54 +0,0 @@
<div id="keyboard">
<div class="nav">
<span class="left" data-key="0"></span>
<span class="top" data-key="1"></span>
<span class="bottom" data-key="2"></span>
<span class="right" data-key="3"></span>
<span class="ok" data-key="4"></span>
<span class="back" data-key="5"></span>
<span class="home" data-key="6"></span>
<span class="power" data-key="7"></span>
</div>
<div class="functions">
<span data-key="12"></span>
<span data-key="13"></span>
<span data-key="14"></span>
<span data-key="15"></span>
<span data-key="16"></span>
<span data-key="17"></span>
<span data-key="18"></span>
<span data-key="19"></span>
<span data-key="20"></span>
<span data-key="21"></span>
<span data-key="22"></span>
<span data-key="23"></span>
<span data-key="24"></span>
<span data-key="25"></span>
<span data-key="26"></span>
<span data-key="27"></span>
<span data-key="28"></span>
<span data-key="29"></span>
</div>
<div class="digits">
<span data-key="30"></span>
<span data-key="31"></span>
<span data-key="32"></span>
<span data-key="33"></span>
<span data-key="34"></span>
<span data-key="36"></span>
<span data-key="37"></span>
<span data-key="38"></span>
<span data-key="39"></span>
<span data-key="40"></span>
<span data-key="42"></span>
<span data-key="43"></span>
<span data-key="44"></span>
<span data-key="45"></span>
<span data-key="46"></span>
<span data-key="48"></span>
<span data-key="49"></span>
<span data-key="50"></span>
<span data-key="51"></span>
<span data-key="52"></span>
</div>
</div>

View File

@@ -1,43 +1,75 @@
/* To use this file you should adhere to the following conventions:
* - Main canvas has id #canvas
* - Secondary canvas (fullscreen on the side) has id #secondary-canvas
* - Calculator keyboard has id #keyboard */
function EpsilonSetup(emModule) {
/* emModule is an optional parameter.
* In addition to the ones supported by Emscripten, here are the values that
* this object can have:
* - calculator: a DOM element containing a copy of 'calculator.html' as
* generated by 'layout.py'
* - mirrorCanvas: a DOM element where the main canvas will be mirrored
*/
var Module;
(function () {
var mainCanvas = document.getElementById('canvas');
var secondaryCanvasContext = document.getElementById('secondary-canvas').getContext('2d');
var epsilonLanguage = document.documentElement.lang || window.navigator.language.split('-')[0];
Module = {
// Configure emModule
var emModule = (typeof emModule === 'undefined') ? {} : emModule;
var calculator = emModule.calculator || document.querySelector('.calculator');
var mainCanvas = calculator.querySelector("canvas");
if (typeof emModule.mirrorCanvas === 'undefined') {
/* If emModule.mirrorCanvas is defined as null, don't do anything */
emModule.mirrorCanvas = document.querySelector('.calculator-mirror canvas');
}
var mirrorCanvasContext = emModule.mirrorCanvas ? emModule.mirrorCanvas.getContext('2d') : null;
var defaultModule = {
canvas: mainCanvas,
arguments: ['--language', epsilonLanguage],
onDisplayRefresh: function() {
secondaryCanvasContext.drawImage(mainCanvas, 0, 0);
arguments: [
'--language',
document.documentElement.lang || window.navigator.language.split('-')[0]
],
downloadScreenshot: function() {
// toDataURL needs the canvas to be refreshed
this._IonDisplayForceRefresh();
var link = document.createElement('a');
link.download = 'screenshot.png';
link.href = mainCanvas.toDataURL('image/png').replace('image/png', 'image/octet-stream');
link.click();
}
};
if (mirrorCanvasContext) {
defaultModule.onDisplayRefresh = function() {
mirrorCanvasContext.drawImage(mainCanvas, 0, 0);
}
}
for (var attrname in defaultModule) {
if (!emModule.hasOwnProperty(attrname)) {
emModule[attrname] = defaultModule[attrname];
}
}
Epsilon(Module);
// Load and run Epsilon
Epsilon(emModule);
document.querySelectorAll('#keyboard span').forEach(function(span){
function eventHandler(keyHandler) {
return function(ev) {
var key = this.getAttribute('data-key');
keyHandler(key);
/* Always prevent default action of event.
* First, this will prevent the browser from delaying that event. Indeed
* the browser would otherwise try to see if that event could have any
* other meaning (e.g. a click) and might delay it as a result.
* Second, this prevents touch events to be handled twice. Indeed, for
* backward compatibility reasons, mobile browsers usually create a fake
* mouse event after each real touch event. This allows desktop websites
* to work unmodified on mobile devices. But here we are explicitly
* handling both touch and mouse events. We therefore need to disable
* the default action of touch events, otherwise the handler would get
* called twice. */
ev.preventDefault();
};
}
/* Install event handlers
* This needs to be done after loading Epsilon, otherwise the _IonSimulator*
* functions haven't been defined just yet. */
function eventHandler(keyHandler) {
return function(ev) {
var key = this.getAttribute('data-key');
keyHandler(key);
/* Always prevent default action of event.
* First, this will prevent the browser from delaying that event. Indeed
* the browser would otherwise try to see if that event could have any
* other meaning (e.g. a click) and might delay it as a result.
* Second, this prevents touch events to be handled twice. Indeed, for
* backward compatibility reasons, mobile browsers usually create a fake
* mouse event after each real touch event. This allows desktop websites
* to work unmodified on mobile devices. But here we are explicitly
* handling both touch and mouse events. We therefore need to disable
* the default action of touch events, otherwise the handler would get
* called twice. */
ev.preventDefault();
};
}
var downHandler = eventHandler(emModule._IonSimulatorKeyboardKeyDown);
var upHandler = eventHandler(emModule._IonSimulatorKeyboardKeyUp);
calculator.querySelectorAll('span').forEach(function(span){
/* We decide to hook both to touch and mouse events
* On most mobile browsers, mouse events are generated if addition to touch
* events, so this could seem pointless. But those mouse events are not
@@ -45,21 +77,14 @@ var Module;
* in a very rapid sequence. This prevents Epsilon from generating an event
* since this quick sequence will trigger the debouncer. */
['touchstart', 'mousedown'].forEach(function(type){
span.addEventListener(type, eventHandler(Module._IonSimulatorKeyboardKeyDown));
span.addEventListener(type, downHandler);
});
['touchend', 'mouseup'].forEach(function(type){
span.addEventListener(type, eventHandler(Module._IonSimulatorKeyboardKeyUp));
span.addEventListener(type, upHandler);
});
});
}());
function screenshot() {
// toDataURL needs the canvas to be refreshed
Module._IonDisplayForceRefresh();
var canvas = document.getElementById('canvas');
var link = document.createElement('a');
link.download = 'screenshot.png';
link.href = canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream');
link.click();
}
if (typeof exports === 'object' && typeof module === 'object') {
module.exports = EpsilonSetup;
}

View File

@@ -0,0 +1,80 @@
body {
background: #C2C2C2;
margin: 0;
padding: 0;
text-align: center;
}
.col-fullscreen {
display: none;
width: 60%;
float: left;
}
.col-fullscreen canvas {
margin-top: 7%;
width: 100%;
}
body.fullscreen .col-fullscreen {
display: block;
}
body.fullscreen .col-calculator {
width: 35%;
float: left;
}
a.action {
display: block;
width: 4%;
padding: 1.5% 3%;
border: 1px solid black;
border-radius: 100px;
cursor: pointer;
opacity: 0.65;
}
a.action:hover {
opacity: 1.0;
background-color: rgba(0,0,0,0.125);
}
a.action svg {
display: block;
}
.calculator-container {
margin: 0 auto;
position: relative;
display: inline-block;
text-align: left;
}
.calculator-container > img {
/* Rely on the img content to set the dimensions */
max-height: 92.0vh;
max-width: 100%;
display: block;
}
.calculator {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.calculator-container .actions {
position: absolute;
top: 94.0vh;
text-align: center;
left: 0;
right: 0;
margin-left: 20px; /* Center the buttons - compensate the 10px margin below */
}
.calculator-container .actions a {
display: inline-block;
margin-right: 10px;
}

View File

@@ -1,5 +1,3 @@
#define KEYBOARD #keyboard
<!DOCTYPE html>
<html>
<head>
@@ -7,178 +5,17 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>NumWorks graphing calculator</title>
<style>
body {
background: #C2C2C2;
margin: 0;
padding: 0;
text-align: center;
}
.col-fullscreen {
display: none;
width: 60%;
float: left;
}
.col-fullscreen canvas {
margin-top: 7%;
width: 100%;
image-rendering: -moz-crisp-edges;
image-rendering: pixelated;
-ms-interpolation-mode: nearest-neighbor;
}
body.fullscreen .col-fullscreen {
display: block;
}
body.fullscreen .col-calculator {
width: 35%;
float: left;
}
a.action {
display: block;
width: 4%;
padding: 1.5% 3%;
border: 1px solid black;
border-radius: 100px;
cursor: pointer;
opacity: 0.65;
}
a.action:hover {
opacity: 1.0;
background-color: rgba(0,0,0,0.125);
}
a.action svg {
display: block;
}
.calculator {
margin: 0 auto;
position: relative;
display: inline-block;
text-align: left; }
.calculator > img {
/* Rely on the img content to set the dimensions */
max-height: 92.0vh;
max-width: 100%;
display: block; }
.calculator #canvas {
position: absolute;
top: 8.5%;
left: 16.25%;
width: 67.5%;
height: 26.3%;
border: 0 none; }
.calculator .actions {
position: absolute;
top: 94.0vh;
text-align: center;
left: 0;
right: 0;
margin-left: 20px; /* Center the buttons - compensate the 10px margin below */
}
.calculator .actions a {
display: inline-block;
margin-right: 10px;
}
KEYBOARD {
position: absolute;
top: 38.5%;
left: 10%;
width: 81%;
bottom: 5%; }
KEYBOARD span {
cursor: pointer;
border-radius: 40%;
display: block;
float: left; }
KEYBOARD span:hover {
background-color: rgba(0, 0, 0, 0.1); }
KEYBOARD span:active {
background-color: rgba(0, 0, 0, 0.2); }
KEYBOARD .nav {
position: absolute;
top: 0;
height: 27%;
width: 100%; }
KEYBOARD .nav span {
position: absolute; }
KEYBOARD .nav .left {
top: 37%;
left: 2%;
width: 15%;
height: 28%; }
KEYBOARD .nav .right {
top: 37%;
left: 17%;
width: 15%;
height: 28%; }
KEYBOARD .nav .top {
top: 9%;
left: 12%;
width: 10%;
height: 40%; }
KEYBOARD .nav .bottom {
top: 53%;
left: 12%;
width: 10%;
height: 40%; }
KEYBOARD .nav .home {
top: 15%;
left: 41%;
width: 16.5%;
height: 31%; }
KEYBOARD .nav .power {
top: 55.4%;
left: 41%;
width: 16.5%;
height: 31%; }
KEYBOARD .nav .ok {
top: 31%;
left: 67%;
width: 13.5%;
height: 38%;
border-radius: 48%; }
KEYBOARD .nav .back {
top: 31%;
left: 83.3%;
width: 13.5%;
height: 38%;
border-radius: 48%; }
KEYBOARD .functions {
position: absolute;
top: 26.75%;
left: 0.5%;
width: 98%; }
KEYBOARD .functions span {
margin: 1.7% 1%;
width: 14.65%;
height: 0;
padding-top: 10%; }
KEYBOARD .digits {
position: absolute;
top: 56.5%;
left: 0.5%;
width: 98%; }
KEYBOARD .digits span {
margin: 1.8% 2%;
width: 16%;
height: 0;
padding-top: 11.3%; }
</style>
<style>
#include "calculator.css"
#include "simulator.css"
</style>
</head>
<body>
<div class="row">
<div class="col-calculator">
<div class="calculator">
<div class="calculator-container">
<img src="background.jpg" alt="NumWorks Calculator">
<canvas id="canvas" oncontextmenu="event.preventDefault()"></canvas>
#include "simulator-keyboard.html"
#include "calculator.html"
<div class="actions">
<a id="action-fullscreen" class="action">
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g fill="#434343"><path d="M5.075,6.95 L6.918,5.088 L3.938,2.062 L5.955,0.018 L0.052,0.018 L0.052,6.004 L2.098,3.928 L5.075,6.95 Z" class="si-glyph-fill"></path><path d="M16.0034788,9.916 L13.832,12.013 L10.799,8.96 L8.918,10.841 L11.957,13.897 L9.961,15.9813842 L16.0034788,15.9813842 L16.0034788,9.916 Z"></path></g></g></svg>
@@ -189,13 +26,15 @@ a.action svg {
</div>
</div>
</div>
<div class="col-fullscreen">
<canvas id="secondary-canvas" width="320" height="240"></canvas>
<div class="col-fullscreen calculator-mirror">
<canvas width="320" height="240"></canvas>
</div>
</div>
<script src="epsilon.js"></script>
<script>
#include "simulator-setup.js"
var Module = {};
EpsilonSetup(Module);
</script>
<script>
document.getElementById("action-fullscreen").addEventListener("click", function(e){
@@ -207,7 +46,7 @@ document.getElementById("action-fullscreen").addEventListener("click", function(
});
document.getElementById("action-screenshot").addEventListener("click", function(e) {
screenshot();
Module.downloadScreenshot();
});
</script>
<script async src="https://www.numworks.com/simulator/update.js"></script>