Abfahrtsanzeiger Display Basic Library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
libmonoformat/ts/index.html

207 lines
6.8 KiB

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MonoDisplay Test</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: #111;
color: #ccc;
font-family: monospace;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
gap: 20px;
padding: 20px;
}
h1 {
font-size: 14px;
letter-spacing: 0.15em;
color: #555;
text-transform: uppercase;
}
/*
* Display container: holds the canvas at a fixed 2:1 aspect ratio (160:80).
* Canvas internal resolution stays at 160×80 logical pixels.
* CSS stretches it to fill this box — image-rendering: pixelated keeps crisp edges.
* To resize the on-screen display: change max-width here. Driver is unaffected.
*/
#display-box {
width: 100%;
max-width: 800px;
aspect-ratio: 2 / 1;
background: #000;
border: 2px solid #333;
border-radius: 4px;
overflow: hidden;
position: relative;
}
#canvas_root {
width: 100%;
height: 100%;
display: block;
image-rendering: pixelated;
image-rendering: crisp-edges; /* Firefox */
}
#resolution {
position: absolute;
bottom: 6px;
right: 8px;
font-size: 10px;
color: #333;
pointer-events: none;
}
#controls {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
}
#controls button {
background: #1a1a1a;
color: #888;
border: 1px solid #333;
border-radius: 3px;
padding: 6px 14px;
font-family: monospace;
font-size: 12px;
cursor: pointer;
transition: background 0.1s, color 0.1s;
}
#controls button:hover { background: #222; color: #bbb; }
#controls button.active { background: #1e3a1e; color: #33ff66; border-color: #33ff66; }
#status { font-size: 11px; color: #444; min-height: 1em; }
</style>
</head>
<body>
<h1>MonoDisplay — 160 × 80</h1>
<div id="display-box">
<!-- Internal res = displayWidth × displayHeight (160×80). CSS does the upscaling. -->
<canvas id="canvas_root" width="160" height="80"></canvas>
<div id="resolution">160 × 80 @ 25fps</div>
</div>
<div id="controls"></div>
<div id="status">select a demo</div>
<!--
Build the library first: bun run build
Produces public/mono-display.js — exposes all types on window.MonoDisplay.
Then serve index.html from any static file server (or file:// directly).
-->
<script src="./public/mono-display.js"></script>
<script>
// -------------------------------------------------------------------------
// Demo helpers — build synthetic .bin ArrayBuffers for all 4 content types
// -------------------------------------------------------------------------
function concat(...parts) {
const arrays = parts.map(p => p instanceof ArrayBuffer ? new Uint8Array(p) : p);
const total = arrays.reduce((s, a) => s + a.byteLength, 0);
const out = new Uint8Array(total);
let off = 0;
for (const a of arrays) { out.set(a, off); off += a.byteLength; }
return out.buffer;
}
function u8(n) { return new Uint8Array([n]); }
function u16le(n) { const b = new Uint8Array(2); new DataView(b.buffer).setUint16(0, n, true); return b; }
function u24le(n) { return new Uint8Array([n & 0xFF, (n >> 8) & 0xFF, (n >> 16) & 0xFF]); }
function u32le(n) { const b = new Uint8Array(4); new DataView(b.buffer).setUint32(0, n, true); return b; }
/** Wrap section payload in a valid 1-section file envelope */
function makeFile(sectionType, sectionData) {
const fileHdr = concat(MAGIC, u32le(1), u16le(1), u16le(0)); // magic+ver+numSec+reserved
const secHdr = concat(u8(sectionType), u24le(sectionData.byteLength));
return concat(fileHdr, secHdr, sectionData);
}
// 160×80 checkerboard — static section (type 0x01)
function makeCheckerboard() {
const W = 160, H = 80;
const pixels = new Uint8Array(W * H);
for (let y = 0; y < H; y++)
for (let x = 0; x < W; x++)
pixels[y * W + x] = (x + y) % 2;
return makeFile(0x01, new Uint8Array(concat(u16le(W), u16le(H), pixels)));
}
// 160×80 two-frame blink — animation section (type 0x02)
function makeAnimation() {
const W = 160, H = 80;
const f = (fill, ms) => new Uint8Array(concat(u32le(ms), new Uint8Array(W * H).fill(fill)));
return makeFile(0x02, new Uint8Array(concat(u16le(W), u16le(H), u16le(2), f(1, 200), f(0, 200))));
}
// Text section (type 0x03)
function makeTextBin(text, align /* 0=left 1=center 2=right */) {
const enc = new TextEncoder().encode(text);
return makeFile(0x03, new Uint8Array(concat(u16le(enc.byteLength), enc, u8(0), u8(align ?? 1))));
}
// Scrolltext section (type 0x04)
function makeScrollTextBin(text, speedPps, direction /* 0=left 1=right */) {
const enc = new TextEncoder().encode(text);
return makeFile(0x04, new Uint8Array(concat(u16le(enc.byteLength), enc, u16le(speedPps ?? 60), u8(direction ?? 0))));
}
// -------------------------------------------------------------------------
// Driver — 160×80, 25fps, green-on-dark
// -------------------------------------------------------------------------
const driver = new MonoDisplayDriver("canvas_root", {
onColor: "#33ff66",
offColor: "#0a0a0a",
fps: 25,
// displayWidth/Height default to 160×80
// scale stays 1 — CSS handles visual upscaling
});
// -------------------------------------------------------------------------
// Demo catalogue
// -------------------------------------------------------------------------
const demos = [
{ label: "Checkerboard (static)", make: makeCheckerboard },
{ label: "Blink (animation)", make: makeAnimation },
{ label: "Text (centered)", make: () => makeTextBin("Hello, World!", 1) },
{ label: "Scrolltext (left)", make: () => makeScrollTextBin("MONO DISPLAY — scrolling ticker — 日本語テスト 🚀 ", 80, 0) },
];
const controls = document.getElementById("controls");
let activeBtn = null;
for (const demo of demos) {
const btn = document.createElement("button");
btn.textContent = demo.label;
btn.onclick = () => {
if (activeBtn) activeBtn.classList.remove("active");
btn.classList.add("active");
activeBtn = btn;
driver.load(() => Promise.resolve(demo.make()));
};
controls.appendChild(btn);
}
// Auto-load first demo
controls.firstElementChild.click();
</script>
</body>
</html>