mirror of
https://github.com/c3d/DB48X-on-DM42.git
synced 2024-09-28 03:20:53 +02:00
4c2306d2a5
Fix handling of mouse click position. The coordinates were computed relative to the wrong dimensions, so the buttons were all the more unreliable the more they were at the bottom and to the right. Fixes: #1096 Signed-off-by: Christophe de Dinechin <christophe@dinechin.org>
468 lines
14 KiB
HTML
468 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>DB48X</title>
|
|
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=0.9, maximum-scale=1.0" />
|
|
<link rel="manifest" href="manifest.json">
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
<link rel="apple-touch-icon" href="./logo.png">
|
|
<link rel="icon" type="image/png" href="/user/themes/quark/images/favicon.png" />
|
|
|
|
<link href="/user/plugins/markdown-notices/assets/notices.css" type="text/css" rel="stylesheet">
|
|
<link href="/user/plugins/form/assets/form-styles.css" type="text/css" rel="stylesheet">
|
|
<link href="/user/themes/quark/css-compiled/spectre.min.css" type="text/css" rel="stylesheet">
|
|
<link href="/user/themes/quark/css-compiled/theme.min.css" type="text/css" rel="stylesheet">
|
|
<link href="/user/themes/quark/css/custom.css" type="text/css" rel="stylesheet">
|
|
<link href="/user/themes/quark/css/line-awesome.min.css" type="text/css" rel="stylesheet">
|
|
|
|
<script src="/system/assets/jquery/jquery-3.x.min.js"></script>
|
|
|
|
<script src="coi-serviceworker.js"></script>
|
|
<script src="db48x.js"></script>
|
|
<style>
|
|
canvas {
|
|
display: block;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
#keyCanvas {
|
|
background-image: url('keyboard-db48x.png');
|
|
background-repeat: no-repeat;
|
|
background-size: 100% 100%;
|
|
margin: 0 auto;
|
|
align-self: center;
|
|
width: 100%;
|
|
}
|
|
|
|
#lcdCanvas {
|
|
align-self: center;
|
|
width: 100%;
|
|
/* IE, only works on <img> tags */
|
|
-ms-interpolation-mode: nearest-neighbor;
|
|
/* Firefox */
|
|
image-rendering: crisp-edges;
|
|
/* Chromium + Safari */
|
|
image-rendering: pixelated;
|
|
}
|
|
#calcContainer {
|
|
margin: auto;
|
|
grid-column: 1 / 1;
|
|
grid-row: 2;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-items: center;
|
|
width: 420px;
|
|
}
|
|
|
|
</style>
|
|
|
|
</head>
|
|
<body id="top" class="title-center title-h1h2 header-fixed header-animated sticky-footer">>
|
|
<div id="page-wrapper">
|
|
<section id="header" class="section">
|
|
<section class="container grid-lg">
|
|
<nav class="navbar">
|
|
<section class="navbar-section logo">
|
|
<a href="/" class="navbar-brand mr-10">
|
|
<img src="/user/themes/quark/images/logo/DB48X-MiniLogo.png" alt="DB48X" />
|
|
</a> </section>
|
|
<section class="navbar-section desktop-menu">
|
|
|
|
<nav class="dropmenu animated">
|
|
|
|
<ul >
|
|
<li>
|
|
<a href="/" class="active">
|
|
Home
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="/project" class="">
|
|
Project
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="/download" class="">
|
|
Download
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="/documentation" class="">
|
|
Documentation
|
|
</a>
|
|
</li>
|
|
|
|
</ul>
|
|
</nav>
|
|
|
|
|
|
</section>
|
|
</nav>
|
|
</section>
|
|
</section>
|
|
<div class="mobile-menu">
|
|
<div class="button_container" id="toggle">
|
|
<span class="top"></span>
|
|
<span class="middle"></span>
|
|
<span class="bottom"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<section id="start">
|
|
<section id="body-wrapper" class="section">
|
|
<section class="container grid-lg">
|
|
|
|
<h1>DB48X</h1>
|
|
<h2>A modern tribute to Hewlett-Packard RPL Calculators</h2>
|
|
<p>
|
|
DB48X is a modern scientific calculator in the spirit of the
|
|
Hewlett-Packard HP48X, featuring a complete, from-scratch
|
|
reimplementation of the Reverse Polish Lisp (RPL) programming
|
|
language.
|
|
</p>
|
|
<p>You can try the calculator directly in your browser below (you may need to click on image):</p>
|
|
|
|
<div id="calcContainer">
|
|
<canvas id="lcdCanvas"></canvas>
|
|
<canvas id="keyCanvas"></canvas>
|
|
</div>
|
|
</section>
|
|
</section>
|
|
</section>
|
|
|
|
</div>
|
|
|
|
<section id="footer" class="section bg-gray">
|
|
<section class="container grid-lg">
|
|
<p><a href="http://48calc.org">DB48X</a> was <i class="fa fa-code"></i> with <i class="fa fa-heart-o pulse "></i> by <a href="https://c3d.github.com">Christophe de Dinechin</a>.</p>
|
|
</section>
|
|
</section>
|
|
|
|
<div class="mobile-container">
|
|
<div class="overlay" id="overlay">
|
|
<div class="mobile-logo">
|
|
<a href="/" class="navbar-brand mr-10">
|
|
<img src="/user/themes/quark/images/logo/DB48X-MiniLogo.png" alt="DB48X" />
|
|
</a>
|
|
</div>
|
|
<nav class="overlay-menu">
|
|
|
|
<ul class="tree">
|
|
<li>
|
|
<a href="/" class="active">
|
|
Home
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="/project" class="">
|
|
Project
|
|
</a>
|
|
</li>
|
|
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/user/themes/quark/js/jquery.treemenu.js"></script>
|
|
<script src="/user/themes/quark/js/site.js"></script>
|
|
|
|
<script>
|
|
document.getElementsByTagName('body')[0].onscroll = function(event) {
|
|
event.preventDefault();
|
|
};
|
|
|
|
const DEBUG = false;
|
|
const SIM_LCD_H = 240; // Height of the LCD
|
|
const SIM_LCD_SCANLINE = 416; // Width of the LCD scanline
|
|
const SIM_LCD_OFFSET = 16;
|
|
const SIM_LCD_W = SIM_LCD_SCANLINE - SIM_LCD_OFFSET;
|
|
const BPP = 1; // Bits per pixel
|
|
const WORD_SIZE = 32; // Size of the word in bits
|
|
const MASK = (1 << BPP) - 1; // Mask to extract BPP bits
|
|
const KB_RATIO = 1.3; // Keyboard aspect ratio
|
|
let keyBuffer = []; // keyBuffer
|
|
let threadSince = 0;
|
|
let threadInterval = 50; // 50ms
|
|
|
|
// Keyboard mapping with key areas
|
|
const key_map = [
|
|
[ 38, 0.03, 0.15, 0.03, 0.10 ], // Qt::Key_F1,
|
|
[ 39, 0.20, 0.32, 0.03, 0.10 ], // Qt::Key_F2,
|
|
[ 40, 0.345, 0.47, 0.03, 0.10 ], // Qt::Key_F3,
|
|
[ 41, 0.52, 0.63, 0.03, 0.10 ], // Qt::Key_F4,
|
|
[ 42, 0.68, 0.80, 0.03, 0.10 ], // Qt::Key_F5,
|
|
[ 43, 0.83, 0.95, 0.03, 0.10 ], // Qt::Key_F6,
|
|
|
|
[ 1, 0.03, 0.15, 0.15, 0.22 ], // Qt::Key_A,
|
|
[ 2, 0.20, 0.32, 0.15, 0.22 ], // Qt::Key_B,
|
|
[ 3, 0.345, 0.47, 0.15, 0.22 ], // Qt::Key_C,
|
|
[ 4, 0.52, 0.63, 0.15, 0.22 ], // Qt::Key_D,
|
|
[ 5, 0.68, 0.80, 0.15, 0.22 ], // Qt::Key_E,
|
|
[ 6, 0.83, 0.95, 0.15, 0.22 ], // Qt::Key_F,
|
|
|
|
[ 7, 0.03, 0.15, 0.275, 0.345 ], // Qt::Key_G,
|
|
[ 8, 0.20, 0.32, 0.275, 0.345 ], // Qt::Key_H,
|
|
[ 9, 0.345, 0.47, 0.275, 0.345 ], // Qt::Key_I,
|
|
[ 10, 0.52, 0.63, 0.275, 0.345 ], // Qt::Key_J,
|
|
[ 11, 0.68, 0.80, 0.275, 0.345 ], // Qt::Key_K,
|
|
[ 12, 0.83, 0.95, 0.275, 0.345 ], // Qt::Key_L,
|
|
|
|
[ 13, 0.03, 0.32, 0.40, 0.47 ], // Qt::Key_Return,
|
|
[ 14, 0.345, 0.47, 0.40, 0.47 ], // Qt::Key_M,
|
|
[ 15, 0.51, 0.64, 0.40, 0.47 ], // Qt::Key_N,
|
|
[ 16, 0.68, 0.80, 0.40, 0.47 ], // Qt::Key_O,
|
|
[ 17, 0.83, 0.95, 0.40, 0.47 ], // Qt::Key_Backspace
|
|
|
|
[ 18, 0.03, 0.15, 0.52, 0.59 ], // Qt::Key_Up,
|
|
[ 19, 0.23, 0.36, 0.52, 0.59 ], // Qt::Key_7,
|
|
[ 20, 0.42, 0.56, 0.52, 0.59 ], // Qt::Key_8,
|
|
[ 21, 0.62, 0.75, 0.52, 0.59 ], // Qt::Key_9,
|
|
[ 22, 0.81, 0.95, 0.52, 0.59 ], // Qt::Key_Slash,
|
|
|
|
[ 23, 0.03, 0.15, 0.645, 0.715 ], // Qt::Key_Down,
|
|
[ 24, 0.23, 0.36, 0.645, 0.715 ], // Qt::Key_4,
|
|
[ 25, 0.42, 0.56, 0.645, 0.715 ], // Qt::Key_5,
|
|
[ 26, 0.62, 0.75, 0.645, 0.715 ], // Qt::Key_6,
|
|
[ 27, 0.81, 0.95, 0.645, 0.715 ], // Qt::Key_Asterisk,
|
|
|
|
[ 28, 0.028, 0.145, 0.77, 0.84 ], // Qt::Key_Control,
|
|
[ 29, 0.23, 0.36, 0.77, 0.84 ], // Qt::Key_1,
|
|
[ 30, 0.42, 0.56, 0.77, 0.84 ], // Qt::Key_2,
|
|
[ 31, 0.62, 0.75, 0.77, 0.84 ], // Qt::Key_3,
|
|
[ 32, 0.81, 0.95, 0.77, 0.84 ], // Qt::Key_Minus,
|
|
|
|
[ 33, 0.03, 0.15, 0.89, 0.97 ], // Qt::Key_Escape,
|
|
[ 34, 0.23, 0.36, 0.89, 0.97 ], // Qt::Key_0,
|
|
[ 35, 0.42, 0.55, 0.89, 0.97 ], // Qt::Key_Period,
|
|
[ 36, 0.62, 0.74, 0.89, 0.97 ], // Qt::Key_Question,
|
|
[ 37, 0.81, 0.95, 0.89, 0.97 ], // Qt::Key_Plus,
|
|
];
|
|
|
|
const keystroke_map = {
|
|
"0": 34,
|
|
"1": 29,
|
|
"2": 30,
|
|
"3": 31,
|
|
"4": 24,
|
|
"5": 25,
|
|
"6": 26,
|
|
"7": 19,
|
|
"8": 20,
|
|
"9": 21,
|
|
".": 35,
|
|
"+": 37,
|
|
"-": 32,
|
|
"*": 27,
|
|
"/": 22,
|
|
"'": 6,
|
|
"=": 36,
|
|
" ": 36,
|
|
"`": 1,
|
|
"Alt": 1,
|
|
"Meta": 1,
|
|
"Enter": 13,
|
|
"Backspace": 17,
|
|
"Escape": 33,
|
|
"F1": 38,
|
|
"F2": 39,
|
|
"F3": 40,
|
|
"F4": 41,
|
|
"F5": 42,
|
|
"F6": 43,
|
|
"Up": 18,
|
|
"Down": 23,
|
|
"Shift": 28,
|
|
"Control": 28,
|
|
"Tab": 28,
|
|
"a": 1,
|
|
"b": 2,
|
|
"c": 3,
|
|
"d": 4,
|
|
"e": 5,
|
|
"f": 6,
|
|
"g": 7,
|
|
"h": 8,
|
|
"i": 9,
|
|
"j": 10,
|
|
"k": 11,
|
|
"l": 12,
|
|
"m": 14,
|
|
"n": 15,
|
|
"o": 16,
|
|
"p": 19,
|
|
"q": 20,
|
|
"r": 21,
|
|
"s": 22,
|
|
"t": 24,
|
|
"u": 25,
|
|
"v": 26,
|
|
"w": 27,
|
|
"x": 29,
|
|
"y": 30,
|
|
"z": 31,
|
|
"ArrowLeft": 18,
|
|
"ArrowRight": 23,
|
|
"ArrowUp": 18,
|
|
"ArrowDown": 23,
|
|
};
|
|
|
|
// canvas for the keyboard
|
|
const canvas_key = document.getElementById('keyCanvas');
|
|
const KEY_H = SIM_LCD_SCANLINE * KB_RATIO;
|
|
const KEY_W = SIM_LCD_W;
|
|
canvas_key.width = KEY_W;
|
|
canvas_key.height = KEY_H;
|
|
const ctx_key = canvas_key.getContext('2d');
|
|
ctx_key.clearRect( 0, 0, KEY_W, KEY_H);
|
|
// debug presses
|
|
if (DEBUG) {
|
|
ctx_key.strokeStyle = "red";
|
|
for (let i=0; i<key_map.length; i++) {
|
|
ctx_key.beginPath();
|
|
ctx_key.moveTo(key_map[i][1]*KEY_W, key_map[i][3]*KEY_H);
|
|
ctx_key.lineTo(key_map[i][1]*KEY_W, key_map[i][4]*KEY_H);
|
|
ctx_key.lineTo(key_map[i][2]*KEY_W, key_map[i][4]*KEY_H);
|
|
ctx_key.lineTo(key_map[i][2]*KEY_W, key_map[i][3]*KEY_H);
|
|
ctx_key.lineTo(key_map[i][1]*KEY_W, key_map[i][3]*KEY_H);
|
|
ctx_key.stroke();
|
|
}
|
|
}
|
|
|
|
// get the mouse/touch position
|
|
const getMousePosition = function(canvas, event) {
|
|
let rect = canvas.getBoundingClientRect();
|
|
let x = (event.clientX - rect.left) / rect.width;
|
|
let y = (event.clientY - rect.top) / rect.height;
|
|
let xx = x * canvas.width;
|
|
let yy = y * canvas.height;
|
|
if (DEBUG) {
|
|
console.log("clicked x: " + x, " y: " + y + " xx:" + xx + " yy: " + yy);
|
|
}
|
|
|
|
// should use html elements for the keys in future
|
|
for (let i=0; i<key_map.length; i++) {
|
|
if (x > key_map[i][1] && x < key_map[i][2] &&
|
|
y > key_map[i][3] && y < key_map[i][4]) {
|
|
if (DEBUG) {
|
|
console.log("clicked:", key_map[i][0]);
|
|
}
|
|
keyBuffer.push(key_map[i][0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
const processKey = function() {
|
|
if (keyBuffer.length > 0) {
|
|
let key = keyBuffer.shift();
|
|
if (DEBUG) {
|
|
console.log("processed key:", key);
|
|
}
|
|
Module.ui_push_key(key);
|
|
Module.ui_push_key(0);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// add an event listener to the canvas so we can parse the key presses
|
|
canvas_key.addEventListener("mousedown", function (e) {
|
|
getMousePosition(canvas_key, e);
|
|
});
|
|
|
|
// add an event listener to the canvas so we can parse the key presses
|
|
document.addEventListener("keyup", (event)=>{
|
|
// check that event.key matches a key in the keystroke_map
|
|
if (Object.keys(keystroke_map).includes(event.key)) {
|
|
keyBuffer.push(keystroke_map[event.key]);
|
|
} else {
|
|
if (DEBUG) {
|
|
console.log("Key not found in keystroke_map: ", event.key);
|
|
}
|
|
}
|
|
});
|
|
|
|
// draw the LCD panel
|
|
const canvas = document.getElementById('lcdCanvas');
|
|
canvas.width = SIM_LCD_W;
|
|
canvas.height = SIM_LCD_H;
|
|
const ctx = canvas.getContext('2d');
|
|
const imgData = ctx.createImageData(canvas.width, canvas.height);
|
|
|
|
// parse the LCD data and draw to the canvas
|
|
const drawBitmap = function(canvasId, dataArray, width, height) {
|
|
const canvas = document.getElementById(canvasId);
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
canvas.width = SIM_LCD_W;
|
|
canvas.height = SIM_LCD_H;
|
|
|
|
const imageData = ctx.createImageData(width, height);
|
|
const data = imageData.data;
|
|
|
|
for (let i = 0; i < dataArray.length; i++) {
|
|
const bitmask = dataArray[i];
|
|
for (let bit = 0; bit < 32; bit++) {
|
|
const value = (bitmask >> bit) & 1;
|
|
const pixelIndex = i * 32 + bit;
|
|
const x = pixelIndex % width;
|
|
const y = Math.floor(pixelIndex / width);
|
|
const mirroredX = width - 1 - x; // Mirroring the x position
|
|
const index = (y * width + mirroredX) * 4;
|
|
// const index = (y * width + x) * 4;
|
|
|
|
const color = value === 1 ? 213 : 0;
|
|
data[index] = color;
|
|
data[index + 1] = color;
|
|
data[index + 2] = color;
|
|
data[index + 3] = 255; // Alpha channel
|
|
}
|
|
}
|
|
|
|
// offset -16 pixels to the left
|
|
ctx.putImageData(imageData, -SIM_LCD_OFFSET, 0);
|
|
}
|
|
|
|
|
|
Module.onRuntimeInitialized = function() {
|
|
|
|
// initialise the calculator
|
|
console.log("loaded wasm...");
|
|
var init = Module.ui_init();
|
|
console.log("ui_init()", init);
|
|
var rpl_lcd = 0;
|
|
|
|
const redraw = function() {
|
|
if (rpl_lcd) {
|
|
if (DEBUG)
|
|
console.log("Display changed");
|
|
var lcd_buffer = [];
|
|
for (let v=0; v < (13*240); v++) {
|
|
var index = rpl_lcd/Int32Array.BYTES_PER_ELEMENT+v;
|
|
var word = Module.HEAP32[index]
|
|
lcd_buffer.push(word)
|
|
}
|
|
rpl_lcd = 0
|
|
drawBitmap('lcdCanvas',
|
|
lcd_buffer, SIM_LCD_SCANLINE, SIM_LCD_H);
|
|
}
|
|
}
|
|
|
|
// fetch and display the RPL display
|
|
const checkRPL = function() {
|
|
if (!rpl_lcd)
|
|
rpl_lcd = Module.ui_lcd_buffer();
|
|
if (processKey() || rpl_lcd)
|
|
window.requestAnimationFrame(redraw)
|
|
window.setTimeout(checkRPL, 50);
|
|
}
|
|
|
|
window.requestAnimationFrame(checkRPL);
|
|
}
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|