- commit
- 4636e19b640ac577f813f69a7ae959f978bbb50a
- parent
- b3a754ee49b8e85c14e957f8f74442f2056e5fce
- author
- Dami <dami.etoile@gmail.com>
| M | src/ | 42 | +++++++++++++++++++++++++----- |
| M | tests/ | 9 | ++++++ |
@@ -3,6 +3,22 @@ import { drawIndex } from "./index.js";
33 import { drawKeycap, drawTouchControls } from "./stage-hud.js";44 import { pauseMotionFrame, pauseMotionLayout } from "./menu-motion.js";55 6+export function wrapCanvasText(ctx, value, maxWidth) {7+ const words = String(value || "").trim().split(/\s+/).filter(Boolean);8+ if (!words.length) return [];9+ const lines = [];10+ let line = "";11+ for (const word of words) {12+ const candidate = line ? `${line} ${word}` : word;13+ if (line && ctx.measureText(candidate).width > maxWidth) {14+ lines.push(line);15+ line = word;16+ } else line = candidate;17+ }18+ if (line) lines.push(line);19+ return lines;20+}21+622 export function renderPauseScene(ctx, view = {}) {723 const { width: w, height: h } = view;824 const {@@ -54,30 +70,42 @@ export function renderPauseScene(ctx, view = {}) {
5470 ctx.globalAlpha = 1;5571 const itemTop = h * 0.24;5672 const selectedY = itemTop + motion.selectedPosition * pauseMotionLayout.pitch;73+ pauseItems.forEach((item, i) => {74+ const itemMotion = motion.itemProgress[i] ?? 1;75+ const y = itemTop + i * pauseMotionLayout.pitch - (1 - itemMotion) * 22;76+ ctx.globalAlpha = itemMotion;77+ ctx.fillStyle = "#000000"; ctx.fillRect(w / 2 - 154, y - 21, 320, 54);78+ ctx.fillStyle = "#232120";79+ ctx.fillRect(w / 2 - 160, y - 27, 320, 54);80+ ctx.globalAlpha = 1;81+ });5782 if (pauseItems.length && motion.openProgress > 0) {5883 ctx.fillStyle = "#FF4B3E";59- ctx.fillRect(w / 2 - 160, selectedY - 27, 320, 54);84+ ctx.fillRect(w / 2 - 160, selectedY - 27, 320, pauseMotionLayout.height);6085 }6186 pauseItems.forEach((item, i) => {6287 const itemMotion = motion.itemProgress[i] ?? 1;6388 const y = itemTop + i * pauseMotionLayout.pitch - (1 - itemMotion) * 22;89+ const highlightTop = selectedY - 27;90+ const labelInHighlight = y >= highlightTop && y <= highlightTop + pauseMotionLayout.height;6491 ctx.globalAlpha = itemMotion;65- ctx.fillStyle = "#000000"; ctx.fillRect(w / 2 - 154, y - 21, 320, 54);66- ctx.fillStyle = i === selected ? "rgba(255,75,62,0)" : "#232120";67- ctx.fillRect(w / 2 - 160, y - 27, 320, 54);6892 ctx.fillStyle = "#0F0E0D";6993 ctx.font = "800 20px 'Apple Color Emoji', 'Segoe UI Emoji', sans-serif";7094 ctx.textAlign = "right";7195 ctx.fillText(i === Math.round(motion.selectedPosition) ? "🐾" : "", w / 2 - 176,7296 y + 1 - (i === Math.round(motion.selectedPosition) ? motion.pawHop : 0));73- ctx.fillStyle = i === selected ? "#0F0E0D" : "#F2EEE6";97+ ctx.fillStyle = labelInHighlight ? "#0F0E0D" : "#F2EEE6";7498 ctx.font = "800 13px Recursive, monospace"; ctx.textAlign = "center";7599 ctx.fillText(item.label, w / 2, y + 1 + (i === selected ? motion.pressOffset : 0));76100 ctx.globalAlpha = 1;77101 });78102 ctx.textAlign = "left"; ctx.fillStyle = "#8E877C"; ctx.font = "500 11px Recursive, monospace";79- if (pausedFrom !== "index" && pausedFrom !== "title")80- ctx.fillText(String(boardState?.hint || "Choose an action").trim(), 32, h - 86);103+ if (pausedFrom !== "index" && pausedFrom !== "title") {104+ const hintLines = wrapCanvasText(ctx, boardState?.hint || "Choose an action", Math.max(120, w - 64));105+ const hintLineHeight = 14;106+ const hintStart = h - 86 - (hintLines.length - 1) * hintLineHeight;107+ hintLines.forEach((line, index) => ctx.fillText(line, 32, hintStart + index * hintLineHeight));108+ }81109 const coarsePointer = view.coarsePointer ?? false;82110 if (w > 760 && !coarsePointer) {83111 drawKeycap(ctx, "↑↓", "choose", 32, h - 46);@@ -1,6 +1,7 @@
11 import test from "node:test";22 import assert from "node:assert/strict";33 import { pauseMotionFrame, pauseMotionTimings } from "../src/render/menu-motion.js";4+import { wrapCanvasText } from "../src/render/stage-pause.js";45 56 test("pause menu drops items on a 35ms stagger and settles at the end", () => {67 const start = 1000;@@ -35,3 +36,11 @@ test("pause exit reverses item order and locks input choreography", () => {
3536 assert.equal(reduced.done, true);3637 assert.deepEqual(reduced.itemProgress, [1, 1, 1]);3738 });39+40+test("pause hints wrap to the available canvas width", () => {41+ const ctx = { measureText: value => ({ width: value.length * 8 }) };42+ assert.deepEqual(wrapCanvasText(ctx, "Push STOP out of ROCK IS STOP, then walk through the rock.", 326), [43+ "Push STOP out of ROCK IS STOP, then walk",44+ "through the rock.",45+ ]);46+});