summaryrefslogtreecommitdiff
path: root/src/web/app.mjs
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-16 15:49:12 -0300
committerGabriel Schneider <[email protected]>2026-08-18 23:44:42 -0300
commit1551e409c31992437cb2fa864f576d45c8433801 (patch)
treee2fae8451f87b735a1360c7c2e383fdc40165789 /src/web/app.mjs
parentbe2a9957708cbf0c478ca861c4a1f0f227bbfe10 (diff)
downloadpardes-1551e409c31992437cb2fa864f576d45c8433801.tar.gz
pardes-1551e409c31992437cb2fa864f576d45c8433801.zip
big slow change: prebuilt shaders (SPIR-V/Metal), core gui reflow, docs, web + snapshot refresh
Diffstat (limited to 'src/web/app.mjs')
-rw-r--r--src/web/app.mjs39
1 files changed, 35 insertions, 4 deletions
diff --git a/src/web/app.mjs b/src/web/app.mjs
index d7a4b6f6..631c6067 100644
--- a/src/web/app.mjs
+++ b/src/web/app.mjs
@@ -1,4 +1,7 @@
const CELL_SIZE = 20;
+const ATTR_TAGLINE = 1 << 7;
+const ANIMATION_FRAME_MS = 1000 / 60;
+const MAX_ANIMATION_TICKS = 4;
const DEFAULT_FG = "#cccccc";
const DEFAULT_BG = "#121212";
const decoder = new TextDecoder();
@@ -31,6 +34,9 @@ export class DomRenderer {
this.cols = 0;
this.rowCount = 0;
this.snapshot = null;
+ const taglinePercent = this.wasm.pardes_gui_tagline_font_percent();
+ if (taglinePercent < 1 || taglinePercent > 100) throw new Error(`bad tagline font percentage ${taglinePercent}`);
+ this.root.style.setProperty("--pardes-tagline-font-size", `${taglinePercent}%`);
}
render() {
@@ -117,6 +123,7 @@ export class PardesRuntime {
this.running = false;
this.pointer = null;
this.frameHandle = 0;
+ this.animationClock = { last: null, lag: 0 };
this.bindEvents();
}
@@ -130,7 +137,8 @@ export class PardesRuntime {
this.renderer.render();
this.drainEffects();
this.running = true;
- this.frameHandle = requestAnimationFrame(() => this.frame());
+ this.animationClock = { last: null, lag: 0 };
+ this.frameHandle = requestAnimationFrame((timestamp) => this.frame(timestamp));
this.root.focus({ preventScroll: true });
}
@@ -140,12 +148,14 @@ export class PardesRuntime {
this.wasm.pardes_deinit();
}
- frame() {
+ frame(timestamp) {
if (!this.running) return;
- this.wasm.pardes_tick();
+ for (let ticks = animationTicks(this.animationClock, timestamp); ticks > 0; ticks -= 1) {
+ this.wasm.pardes_tick();
+ }
this.renderer.render();
this.drainEffects();
- this.frameHandle = requestAnimationFrame(() => this.frame());
+ this.frameHandle = requestAnimationFrame((nextTimestamp) => this.frame(nextTimestamp));
}
measure() {
@@ -299,6 +309,7 @@ export class PardesRuntime {
this.root.addEventListener("pointermove", (event) => this.pointerMove(event));
this.root.addEventListener("pointerup", (event) => this.pointerUp(event));
this.root.addEventListener("pointercancel", (event) => this.pointerUp(event));
+ this.root.addEventListener("pointerleave", () => this.wasm.pardes_pointer_leave());
this.root.addEventListener("wheel", (event) => this.wheel(event), { passive: false });
globalThis.addEventListener?.("resize", () => this.resize());
}
@@ -342,6 +353,25 @@ export class PardesRuntime {
}
}
+/// Spend monotonic requestAnimationFrame time as fixed 60 Hz core ticks. A
+/// high-refresh display therefore renders repeated states rather than making
+/// frame-count transitions run twice as fast. Long suspension is capped so a
+/// returning tab cannot execute an unbounded loop before drawing.
+export function animationTicks(clock, timestamp) {
+ if (!Number.isFinite(timestamp)) return 0;
+ if (clock.last === null || timestamp < clock.last) {
+ clock.last = timestamp;
+ clock.lag = 0;
+ return 0;
+ }
+ const elapsed = Math.min(timestamp - clock.last, ANIMATION_FRAME_MS * MAX_ANIMATION_TICKS);
+ clock.last = timestamp;
+ clock.lag += elapsed;
+ const ticks = Math.min(MAX_ANIMATION_TICKS, Math.floor((clock.lag + 1e-7) / ANIMATION_FRAME_MS));
+ clock.lag -= ticks * ANIMATION_FRAME_MS;
+ return ticks;
+}
+
export async function instantiate(url = "./pardes.wasm") {
const response = await fetch(url);
if (!response.ok) throw new Error(`fetch ${url}: HTTP ${response.status}`);
@@ -386,6 +416,7 @@ function applyStyle(cell, fgCode, bgCode, attrs, cursor, cursorBar) {
cell.classList.toggle("is-cursor", cursor);
cell.classList.toggle("is-bar", cursor && cursorBar);
cell.classList.toggle("is-blinking", Boolean(attrs & (1 << 3)));
+ cell.classList.toggle("is-tagline", Boolean(attrs & ATTR_TAGLINE));
}
function color(encoded, fallback) {