diff --git a/screen.js b/screen.js index cd0f7541..0a5d0bc2 100644 --- a/screen.js +++ b/screen.js @@ -10375,12 +10375,19 @@ function _tempoMapDraw(w, h) { ctx.font = '11px sans-serif'; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; - ctx.fillText( - `Tempo Map — ${measures.length} measures · drag a pole to retime · ` - + `right-click: insert/delete · [ ]: time signature`, - LABEL_W + 6, hudY); + ctx.fillText(_tempoMapHudTextPure(measures.length, w), LABEL_W + 6, hudY); } +/* @pure:tempo-map-guidance:start */ +function _tempoMapHudTextPure(measureCount, width) { + const n = Number.isFinite(Number(measureCount)) ? Number(measureCount) : 0; + if (Number(width) < 760) { + return `Tempo Map - ${n} measures - right-click sync point: BPM / signature`; + } + return `Tempo Map - ${n} measures - drag poles to retime - right-click sync point: BPM / signature/delete - right-click grid: insert`; +} +/* @pure:tempo-map-guidance:end */ + function _ensureTempoSignatureControl() { let wrap = document.getElementById('editor-tempo-sig-wrap'); if (wrap) return wrap; @@ -10414,7 +10421,7 @@ function _ensureTempoMapButton() { btn.type = 'button'; btn.textContent = '🎵 Tempo Map'; btn.className = 'px-3 py-1 bg-dark-600 hover:bg-dark-500 rounded text-xs font-medium hidden'; - btn.title = 'Open the EOF-style tempo-map editor'; + btn.title = 'Open Tempo Map to edit sync points, BPM, and time signatures'; btn.onclick = () => { // Finalize any in-progress canvas drag before switching // modes — commit a moved sync-point / drum drag (don't @@ -10609,8 +10616,8 @@ function _tempoMapOnMouseDown(e, x, y) { draw(); } -// Right-click in tempo-map mode: insert a sync point on open grid, or -// delete / change the time signature of the sync point under the cursor. +// Right-click in tempo-map mode: insert on open grid, or edit/delete +// the sync point under the cursor. function _tempoMapOnContextMenu(e) { const { x, y } = getMousePos(e); const menu = document.getElementById('editor-context-menu'); diff --git a/tests/tempo_map_guidance.test.js b/tests/tempo_map_guidance.test.js new file mode 100644 index 00000000..42f0580b --- /dev/null +++ b/tests/tempo_map_guidance.test.js @@ -0,0 +1,50 @@ +'use strict'; +/* + * Tempo-map guidance helper tests for screen.js. + * + * Run: node tests/tempo_map_guidance.test.js + */ +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); + +const src = fs.readFileSync(path.join(__dirname, '..', 'screen.js'), 'utf8'); +const m = src.match(/\/\* @pure:tempo-map-guidance:start \*\/[\s\S]*?\/\* @pure:tempo-map-guidance:end \*\//); +if (!m) { + console.error('FAIL: @pure:tempo-map-guidance block not found in screen.js'); + process.exit(1); +} + +const api = new Function( + '"use strict";' + m[0] + '\nreturn { _tempoMapHudTextPure };' +)(); + +let pass = 0; +let fail = 0; +function t(name, fn) { + try { fn(); pass++; console.log(' ok ' + name); } + catch (e) { fail++; console.error(' FAIL ' + name + ': ' + e.message); } +} + +t('uses compact guidance on narrow canvases', () => { + const text = api._tempoMapHudTextPure(12, 640); + assert.ok(text.includes('12 measures')); + assert.ok(text.includes('right-click sync point')); + assert.ok(text.includes('BPM / signature')); + assert.ok(!text.includes('right-click grid')); +}); + +t('uses full guidance when there is room', () => { + const text = api._tempoMapHudTextPure(24, 960); + assert.ok(text.includes('24 measures')); + assert.ok(text.includes('drag poles to retime')); + assert.ok(text.includes('BPM / signature/delete')); + assert.ok(text.includes('right-click grid: insert')); +}); + +t('normalizes invalid measure counts to zero', () => { + assert.ok(api._tempoMapHudTextPure('bad', 960).includes('0 measures')); +}); + +console.log(`\n${pass} passed, ${fail} failed`); +process.exit(fail ? 1 : 0);