DB48X-on-DM42/wasm/index.html
Christophe de Dinechin 4c2306d2a5 48calc.org: Fix mouse click position
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>
2024-08-02 01:32:52 +02:00

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>