Files
HVideoTool/hvideotool/ui/marker_slider.py
T

72 lines
2.5 KiB
Python

"""A horizontal QSlider that paints marks at frames where censorship was found.
Used as the navigation scrubber under the image: the file list / `_results`
cache is projected onto the groove as small vertical ticks, so you can see at a
glance where the detected regions are along the whole sequence.
"""
from __future__ import annotations
from collections.abc import Iterable
from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QPainter
from PySide6.QtWidgets import QSlider, QStyle, QStyleOptionSlider
class MarkerSlider(QSlider):
def __init__(self, orientation=Qt.Horizontal, parent=None) -> None:
super().__init__(orientation, parent)
self._marks: set[int] = set()
# Bright cyan stands out against both the dark track and the orange fill.
self._mark_color = QColor(0, 220, 255)
def set_marks(self, marks: Iterable[int]) -> None:
marks = set(marks)
if marks != self._marks:
self._marks = marks
self.update()
def clear_marks(self) -> None:
if self._marks:
self._marks = set()
self.update()
def paintEvent(self, event) -> None:
super().paintEvent(event)
if not self._marks or self.maximum() <= self.minimum():
return
opt = QStyleOptionSlider()
self.initStyleOption(opt)
groove = self.style().subControlRect(
QStyle.CC_Slider, opt, QStyle.SC_SliderGroove, self
)
handle = self.style().subControlRect(
QStyle.CC_Slider, opt, QStyle.SC_SliderHandle, self
)
span = groove.width() - handle.width()
if span <= 0:
return
lo, hi = self.minimum(), self.maximum()
half = handle.width() // 2
# Span (almost) the full widget height so marks read over the fill/handle.
top = self.rect().top() + 1
bottom = self.rect().bottom() - 1
painter = QPainter(self)
pen = painter.pen()
pen.setColor(self._mark_color)
pen.setWidth(2)
painter.setPen(pen)
# Many frames can collapse onto the same pixel column — dedupe to keep
# repaint cheap on big folders (tens of thousands of frames).
seen_x: set[int] = set()
for m in self._marks:
pos = QStyle.sliderPositionFromValue(lo, hi, m, span, opt.upsideDown)
x = groove.x() + half + pos
if x not in seen_x:
seen_x.add(x)
painter.drawLine(x, top, x, bottom)
painter.end()