diff --git a/main.py b/main.py index 89ce127..6c1ecb7 100755 --- a/main.py +++ b/main.py @@ -3946,29 +3946,70 @@ class AudioWaveform(QWidget): def __init__(self, parent=None): super().__init__(parent) self._peaks: list[float] = [] - self.setFixedHeight(48) + self._view_start = 0.0 + self._view_dur = 0.0 + self._sel: tuple[float, float] | None = None + self._playhead: float | None = None + self.setFixedHeight(96) self.setToolTip("Waveform of the current audio area (↻ to refresh)") def set_peaks(self, peaks: list[float]) -> None: self._peaks = list(peaks or []) self.update() + def set_view(self, start: float, dur: float) -> None: + self._view_start = start + self._view_dur = dur + self.update() + + def set_selection(self, start: float, end: float) -> None: + if self._view_dur > 0: + lo = self._view_start + hi = self._view_start + self._view_dur + start = min(max(start, lo), hi) + end = min(max(end, lo), hi) + if start > end: + start, end = end, start + self._sel = (start, end) + self.update() + + def selection(self) -> tuple[float, float] | None: + return self._sel + + def set_playhead(self, t: float | None) -> None: + self._playhead = t + self.update() + def clear(self) -> None: self._peaks = [] + self._sel = None + self._playhead = None self.update() def paintEvent(self, _ev): + from core.waveform import t_to_x p = QPainter(self) p.fillRect(self.rect(), QColor(30, 30, 30)) - if not self._peaks: - return w = self.width(); h = self.height(); mid = h / 2 - n = len(self._peaks) - p.setPen(QPen(QColor(0, 220, 190))) - for i, v in enumerate(self._peaks): - x = int(i * w / n) - bar_h = v * (h - 4) / 2 - p.drawLine(x, int(mid - bar_h), x, int(mid + bar_h)) + if self._peaks: + n = len(self._peaks) + p.setPen(QPen(QColor(0, 220, 190))) + for i, v in enumerate(self._peaks): + x = int(i * w / n) + bar_h = v * (h - 4) / 2 + p.drawLine(x, int(mid - bar_h), x, int(mid + bar_h)) + if self._sel and self._view_dur > 0: + x1 = t_to_x(self._sel[0], w, self._view_start, self._view_dur) + x2 = t_to_x(self._sel[1], w, self._view_start, self._view_dur) + p.fillRect(QRect(x1, 0, max(1, x2 - x1), h), QColor(0, 200, 180, 50)) + p.setPen(QPen(QColor(0, 220, 190), 2)) + p.drawLine(x1, 0, x1, h) + p.drawLine(x2, 0, x2, h) + if (self._playhead is not None and self._view_dur > 0 + and self._view_start <= self._playhead <= self._view_start + self._view_dur): + px = t_to_x(self._playhead, w, self._view_start, self._view_dur) + p.setPen(QPen(QColor(255, 255, 255, 160), 1)) + p.drawLine(px, 0, px, h) class MainWindow(QMainWindow): diff --git a/tests/test_ui_structure.py b/tests/test_ui_structure.py index e0eb433..a53ee87 100644 --- a/tests/test_ui_structure.py +++ b/tests/test_ui_structure.py @@ -398,6 +398,22 @@ def test_waveform_strip_present_and_safe(win): win._on_wave_refresh() +def test_waveform_view_and_selection(win): + w = win._wave + w.set_view(10.0, 4.0) + w.set_selection(11.0, 12.5) + assert w._view_start == 10.0 and w._view_dur == 4.0 + assert w.selection() == (11.0, 12.5) + # selection clamps into the view window and keeps start < end + w.set_selection(9.0, 20.0) + s, e = w.selection() + assert s >= 10.0 and e <= 14.0 and s < e + w.set_peaks([0.1, 0.9, 0.3]) # still paints without crashing + w.set_playhead(11.5) + w.clear() # resets display state + assert w.selection() is None + + def test_audition_button_present_and_safe(win): from PyQt6.QtWidgets import QPushButton assert isinstance(win._btn_audio_play, QPushButton)