"""Small activity spinner: a rotating accent-coloured arc. Runs only while visible.""" from PySide6.QtCore import QEvent, QRectF, QTimer, Qt from PySide6.QtGui import QColor, QPainter, QPalette, QPen from PySide6.QtWidgets import QWidget from . import icons STEP_MS = 16 STEP_DEG = 9 def paint_arc(p, rect, angle, color=icons.ACCENT): """Draw the spinner (faint ring + 100 degree arc) into rect; used by Spinner and the waveform overlay.""" w = max(1.5, rect.width() / 8) r = rect.adjusted(w / 2, w / 2, -w / 2, -w / 2) p.save() p.setRenderHint(QPainter.RenderHint.Antialiasing) ring = QColor(color) ring.setAlpha(45) p.setPen(QPen(ring, w)) p.drawEllipse(r) pen = QPen(QColor(color), w) pen.setCapStyle(Qt.PenCapStyle.RoundCap) p.setPen(pen) p.drawArc(r, int(-angle * 16), 100 * 16) p.restore() class Spinner(QWidget): def __init__(self, size=16, parent=None): super().__init__(parent) self.setFixedSize(size, size) self.angle = 0 self.timer = QTimer(self) self.timer.setInterval(STEP_MS) self.timer.timeout.connect(self._step) self.hide() def _step(self): self.angle = (self.angle + STEP_DEG) % 360 self.update() def showEvent(self, e): self.timer.start() super().showEvent(e) def hideEvent(self, e): self.timer.stop() super().hideEvent(e) def paintEvent(self, _): p = QPainter(self) paint_arc(p, QRectF(self.rect()), self.angle) class BusyOverlay(QWidget): """A list being (re)loaded: a light veil over the widget with the spinner and a message in its centre, the same arc as the waveform's loading state. Covers the target and follows its size; blocks clicks while on.""" def __init__(self, target): super().__init__(target) self.target = target self.message = "" self.angle = 0 self.timer = QTimer(self) self.timer.setInterval(STEP_MS) self.timer.timeout.connect(self._step) target.installEventFilter(self) self.hide() def start(self, message): self.message = message self.setGeometry(self.target.rect()) self.raise_() self.show() self.timer.start() self.update() def stop(self): self.timer.stop() self.hide() @property def busy(self): return not self.isHidden() # on, whether or not the window is on screen yet def _step(self): self.angle = (self.angle + STEP_DEG) % 360 if self.isVisible(): self.update() def eventFilter(self, obj, e): if obj is self.target and e.type() == QEvent.Type.Resize: self.setGeometry(self.target.rect()) return False def paintEvent(self, _): p = QPainter(self) p.setRenderHint(QPainter.RenderHint.Antialiasing) pal = self.palette() veil = QColor(pal.color(QPalette.ColorRole.Base)) veil.setAlpha(210) p.fillRect(self.rect(), veil) fm = p.fontMetrics() d, gap, pad = 20, 12, 16 tw = fm.horizontalAdvance(self.message) w, h = pad + d + gap + tw + pad, max(d, fm.height()) + 2 * pad - 8 box = QRectF((self.width() - w) / 2, (self.height() - h) / 2, w, h) border = QColor(pal.color(QPalette.ColorRole.Text)) border.setAlpha(40) p.setPen(QPen(border, 1)) p.setBrush(pal.color(QPalette.ColorRole.Window)) p.drawRoundedRect(box, 8, 8) y = box.center().y() paint_arc(p, QRectF(box.left() + pad, y - d / 2, d, d), self.angle) p.setPen(pal.color(QPalette.ColorRole.Text)) p.drawText(QRectF(box.left() + pad + d + gap, y - fm.height() / 2, tw + 2, fm.height()), Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft, self.message)