from typing import List from PyQt6.QtCore import Qt, QPointF, pyqtSignal from PyQt6.QtGui import QPainter, QColor, QPolygonF from PyQt6.QtWidgets import QWidget MARKER_COLOR = QColor(230, 126, 34) # orange CLICK_TOLERANCE_PX = 8.0 class MarkerStrip(QWidget): """Thin strip drawn directly above the seek slider (same width, via a shared parent layout) showing detected clap/onset markers as small triangles. Clicking one seeks the player to that exact timestamp. """ markerClicked = pyqtSignal(float) def __init__(self, parent=None): super().__init__(parent) self.setFixedHeight(14) self.setCursor(Qt.CursorShape.PointingHandCursor) self._duration = 0.0 self._markers: List[float] = [] def set_duration(self, duration: float): self._duration = duration self.update() def set_markers(self, markers: List[float]): self._markers = sorted(markers) self.update() def markers(self) -> List[float]: return list(self._markers) def clear(self): self._markers = [] self.update() def _x_for_time(self, t: float) -> float: if self._duration <= 0: return 0.0 return (t / self._duration) * self.width() def paintEvent(self, event): if not self._markers or self._duration <= 0: return painter = QPainter(self) painter.setRenderHint(QPainter.RenderHint.Antialiasing) painter.setPen(Qt.PenStyle.NoPen) painter.setBrush(MARKER_COLOR) for t in self._markers: x = self._x_for_time(t) triangle = QPolygonF([ QPointF(x - 5, 0), QPointF(x + 5, 0), QPointF(x, 10), ]) painter.drawPolygon(triangle) def mousePressEvent(self, event): if not self._markers or self._duration <= 0: return click_x = event.position().x() closest = min(self._markers, key=lambda t: abs(self._x_for_time(t) - click_x)) if abs(self._x_for_time(closest) - click_x) <= CLICK_TOLERANCE_PX: self.markerClicked.emit(closest)