diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-15 14:54:32 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-15 14:54:32 +0100 |
| commit | 2031e24c976d1a11eb48badb97e824b1db07741a (patch) | |
| tree | 479120fe608bc5645e73c25e50e07937c986c343 /tools/timeline_editor | |
| parent | a7bd63b1fc2b573141677da2d613c2a84455260e (diff) | |
feat(timeline-editor): add timing tooltip and cursor in waveform view
Shows precise time (seconds) and beat position under mouse cursor with
a vertical guide line for accurate sample timing measurements.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'tools/timeline_editor')
| -rw-r--r-- | tools/timeline_editor/index.html | 8 | ||||
| -rw-r--r-- | tools/timeline_editor/timeline-viewport.js | 36 |
2 files changed, 43 insertions, 1 deletions
diff --git a/tools/timeline_editor/index.html b/tools/timeline_editor/index.html index 4efbcd8..3c5f48c 100644 --- a/tools/timeline_editor/index.html +++ b/tools/timeline_editor/index.html @@ -48,6 +48,8 @@ .waveform-container { position: relative; height: 80px; overflow: hidden; background: rgba(0, 0, 0, 0.3); border-radius: var(--radius); cursor: crosshair; } #cpuLoadCanvas { position: absolute; left: 0; bottom: 0; height: 10px; display: block; z-index: 1; } #waveformCanvas { position: absolute; left: 0; top: 0; height: 80px; display: block; z-index: 2; } + .waveform-cursor { position: absolute; top: 0; bottom: 0; width: 1px; background: rgba(78, 201, 176, 0.6); pointer-events: none; z-index: 3; display: none; } + .waveform-tooltip { position: absolute; background: rgba(30, 30, 30, 0.95); color: var(--text-primary); padding: 6px 10px; border-radius: 4px; font-size: 12px; pointer-events: none; z-index: 4; display: none; white-space: nowrap; border: 1px solid var(--border-color); box-shadow: 0 2px 8px rgba(0,0,0,0.3); } .playback-indicator { position: absolute; top: 0; bottom: 0; left: 20px; width: 2px; background: var(--accent-red); box-shadow: 0 0 4px rgba(244, 135, 113, 0.8); pointer-events: none; z-index: 110; display: none; } @@ -155,6 +157,8 @@ <div class="waveform-container" id="waveformContainer"> <canvas id="cpuLoadCanvas"></canvas> <canvas id="waveformCanvas"></canvas> + <div class="waveform-cursor" id="waveformCursor"></div> + <div class="waveform-tooltip" id="waveformTooltip"></div> </div> <div class="time-markers" id="timeMarkers"></div> </div> @@ -236,7 +240,9 @@ bpmSlider: document.getElementById('bpmSlider'), currentBPM: document.getElementById('currentBPM'), showBeatsCheckbox: document.getElementById('showBeatsCheckbox'), - quantizeSelect: document.getElementById('quantizeSelect') + quantizeSelect: document.getElementById('quantizeSelect'), + waveformCursor: document.getElementById('waveformCursor'), + waveformTooltip: document.getElementById('waveformTooltip') }; // Parser diff --git a/tools/timeline_editor/timeline-viewport.js b/tools/timeline_editor/timeline-viewport.js index becf8e9..396b648 100644 --- a/tools/timeline_editor/timeline-viewport.js +++ b/tools/timeline_editor/timeline-viewport.js @@ -32,6 +32,11 @@ export class ViewportController { this.dom.propertiesPanel.addEventListener('wheel', e => e.stopPropagation()); document.querySelector('.zoom-controls').addEventListener('wheel', e => e.stopPropagation()); document.querySelector('.stats').addEventListener('wheel', e => e.stopPropagation()); + + // Waveform hover tracking + this.dom.waveformContainer.addEventListener('mouseenter', () => this.showWaveformCursor()); + this.dom.waveformContainer.addEventListener('mouseleave', () => this.hideWaveformCursor()); + this.dom.waveformContainer.addEventListener('mousemove', e => this.updateWaveformCursor(e)); } handleZoomSlider(e) { @@ -126,6 +131,37 @@ export class ViewportController { } } + showWaveformCursor() { + this.dom.waveformCursor.style.display = 'block'; + this.dom.waveformTooltip.style.display = 'block'; + } + + hideWaveformCursor() { + this.dom.waveformCursor.style.display = 'none'; + this.dom.waveformTooltip.style.display = 'none'; + } + + updateWaveformCursor(e) { + const rect = this.dom.waveformContainer.getBoundingClientRect(); + const mouseX = e.clientX - rect.left; + const scrollLeft = this.dom.timelineContent.scrollLeft; + const timeBeats = (scrollLeft + mouseX - this.TIMELINE_LEFT_PADDING) / this.state.pixelsPerSecond; + const timeSeconds = timeBeats * 60.0 / this.state.bpm; + + // Position cursor + this.dom.waveformCursor.style.left = `${mouseX}px`; + + // Position and update tooltip + const tooltipText = `${timeSeconds.toFixed(3)}s (${timeBeats.toFixed(2)}b)`; + this.dom.waveformTooltip.textContent = tooltipText; + + // Position tooltip above cursor, offset to the right + const tooltipX = mouseX + 10; + const tooltipY = 5; + this.dom.waveformTooltip.style.left = `${tooltipX}px`; + this.dom.waveformTooltip.style.top = `${tooltipY}px`; + } + // Helper timeToBeats(seconds) { return seconds * this.state.bpm / 60.0; |
