class ThrobbingButton(QToolButton): @pyqtProperty(int) def icon_size(self): return self._icon_size @icon_size.setter def icon_size(self, value): self._icon_size = value def __init__(self, *args): QToolButton.__init__(self, *args) # vertically size policy must be expanding for it to align inside a # toolbar self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding) self._icon_size = -1 QToolButton.setIcon(self, QIcon(I('donate.png'))) self.setText('\xa0') self.animation = QPropertyAnimation(self, b'icon_size', self) self.animation.setDuration(int(60/72.*1000)) self.animation.setLoopCount(4) self.animation.valueChanged.connect(self.value_changed) self.setCursor(Qt.CursorShape.PointingHandCursor) self.animation.finished.connect(self.animation_finished) def animation_finished(self): self.icon_size = self.iconSize().width() def enterEvent(self, ev): self.start_animation() def leaveEvent(self, ev): self.stop_animation() def value_changed(self, val): self.update() def start_animation(self): if config['disable_animations']: return if self.animation.state() != QAbstractAnimation.State.Stopped or not self.isVisible(): return size = self.iconSize().width() smaller = int(0.7 * size) self.animation.setStartValue(smaller) self.animation.setEndValue(size) QMetaObject.invokeMethod(self.animation, 'start', Qt.ConnectionType.QueuedConnection) def stop_animation(self): self.animation.stop() self.animation_finished() def paintEvent(self, ev): size = self._icon_size if self._icon_size > 10 else self.iconSize().width() p = QPainter(self) opt = QStyleOptionToolButton() self.initStyleOption(opt) s = self.style() opt.iconSize = QSize(size, size) s.drawComplexControl(QStyle.ComplexControl.CC_ToolButton, opt, p, self)
class Pointer(QWidget): def __init__(self, gui): QWidget.__init__(self, gui) self.setObjectName('jobs_pointer') self.setVisible(False) self.resize(100, 80) self.animation = QPropertyAnimation(self, b"geometry", self) self.animation.setDuration(750) self.animation.setLoopCount(2) self.animation.setEasingCurve(QEasingCurve.Type.Linear) self.animation.finished.connect(self.hide) taily, heady = 0, 55 self.arrow_path = QPainterPath(QPointF(40, taily)) self.arrow_path.lineTo(40, heady) self.arrow_path.lineTo(20, heady) self.arrow_path.lineTo(50, self.height()) self.arrow_path.lineTo(80, heady) self.arrow_path.lineTo(60, heady) self.arrow_path.lineTo(60, taily) self.arrow_path.closeSubpath() c = self.palette().color(QPalette.ColorGroup.Active, QPalette.ColorRole.WindowText) self.color = QColor(c) self.color.setAlpha(100) self.brush = QBrush(self.color, Qt.BrushStyle.SolidPattern) # from qt.core import QTimer # QTimer.singleShot(1000, self.start) @property def gui(self): return self.parent() def point_at(self, frac): return (self.path.pointAtPercent(frac).toPoint() - QPoint(self.rect().center().x(), self.height())) def rect_at(self, frac): return QRect(self.point_at(frac), self.size()) def abspos(self, widget): pos = widget.pos() parent = widget.parent() while parent is not self.gui: pos += parent.pos() parent = parent.parent() return pos def start(self): if config['disable_animations']: return self.setVisible(True) self.raise_() end = self.abspos(self.gui.jobs_button) end = QPointF(end.x() + self.gui.jobs_button.width() / 3.0, end.y() + 20) start = QPointF(end.x(), end.y() - 0.5 * self.height()) self.path = QPainterPath(QPointF(start)) self.path.lineTo(end) self.path.closeSubpath() self.animation.setStartValue(self.rect_at(0.0)) self.animation.setEndValue(self.rect_at(1.0)) self.animation.setDirection(QAbstractAnimation.Direction.Backward) num_keys = 100 for i in range(1, num_keys): i /= num_keys self.animation.setKeyValueAt(i, self.rect_at(i)) self.animation.start() def paintEvent(self, ev): p = QPainter(self) p.setRenderHints(QPainter.RenderHint.Antialiasing) p.setBrush(self.brush) p.setPen(Qt.PenStyle.NoPen) p.drawPath(self.arrow_path) p.end()