def moveEvent(self, tetritile): translation = QPropertyAnimation(self, b"pos") start, end = self.pos(), QPointF(tetritile.row, tetritile.column) curve, speed, delay = QEasingCurve.OutBack, 1 / 50, -1 self.animate(translation, start, end, curve, speed, delay) rotation = QPropertyAnimation(self, b"rotation") start, end = self.rotation(), tetritile.rotation curve, speed, delay = QEasingCurve.OutBack, 1, -1 self.animate(rotation, start, end, curve, speed, delay) rotation.setDuration(translation.duration()) self.moveAnimation.clear() self.moveAnimation.addAnimation(translation) self.moveAnimation.addAnimation(rotation) self.moveAnimation.start()
class SplitterResizer(QObject): """An object able to control the size of a widget in a QSpliter instance. """ def __init__(self, parent=None): QObject.__init__(self, parent) self.__splitter = None self.__widget = None self.__animationEnabled = True self.__size = -1 self.__expanded = False self.__animation = QPropertyAnimation(self, b"size_", self) self.__action = QAction("toogle-expanded", self, checkable=True) self.__action.triggered[bool].connect(self.setExpanded) def setSize(self, size): """Set the size of the controlled widget (either width or height depending on the orientation). """ if self.__size != size: self.__size = size self.__update() def size(self): """Return the size of the widget in the splitter (either height of width) depending on the splitter orientation. """ if self.__splitter and self.__widget: index = self.__splitter.indexOf(self.__widget) sizes = self.__splitter.sizes() return sizes[index] else: return -1 size_ = Property(int, fget=size, fset=setSize) def setAnimationEnabled(self, enable): """Enable/disable animation. """ self.__animation.setDuration(0 if enable else 200) def animationEnabled(self): return self.__animation.duration() == 0 def setSplitterAndWidget(self, splitter, widget): """Set the QSplitter and QWidget instance the resizer should control. .. note:: the widget must be in the splitter. """ if splitter and widget and not splitter.indexOf(widget) > 0: raise ValueError("Widget must be in a spliter.") if self.__widget: self.__widget.removeEventFilter() self.__splitter = splitter self.__widget = widget if widget: widget.installEventFilter(self) self.__update() def toogleExpandedAction(self): """Return a QAction that can be used to toggle expanded state. """ return self.__action def open(self): """Open the controlled widget (expand it to it sizeHint). """ self.__expanded = True self.__action.setChecked(True) if not (self.__splitter and self.__widget): return size = self.size() if size > 0: # Already has non zero size. return hint = self.__widget.sizeHint() if self.__splitter.orientation() == Qt.Vertical: end = hint.height() else: end = hint.width() self.__animation.setStartValue(0) self.__animation.setEndValue(end) self.__animation.start() def close(self): """Close the controlled widget (shrink to size 0). """ self.__expanded = False self.__action.setChecked(False) if not (self.__splitter and self.__widget): return self.__animation.setStartValue(self.size()) self.__animation.setEndValue(0) self.__animation.start() def setExpanded(self, expanded): """Set the expanded state. """ if self.__expanded != expanded: if expanded: self.open() else: self.close() def expanded(self): """Return the expanded state. """ return self.__expanded def __update(self): """Update the splitter sizes. """ if self.__splitter and self.__widget: splitter = self.__splitter index = splitter.indexOf(self.__widget) sizes = splitter.sizes() current = sizes[index] diff = current - self.__size sizes[index] = self.__size sizes[index - 1] = sizes[index - 1] + diff self.__splitter.setSizes(sizes) def eventFilter(self, obj, event): if event.type() == QEvent.Resize: if self.__splitter.orientation() == Qt.Vertical: size = event.size().height() else: size = event.size().width() if self.__expanded and size == 0: self.__action.setChecked(False) self.__expanded = False elif not self.__expanded and size > 0: self.__action.setChecked(True) self.__expanded = True return QObject.eventFilter(self, obj, event)
class StackedWidgetSlideOverAnimator(AbstractAnimator): m_direction: AnimationDirection m_coveredWidget: QWidget m_decorator: StackedWidgetSlideOverDecorator m_animation: QPropertyAnimation def __init__(self, _container, _widgetForSlide): super(StackedWidgetSlideOverAnimator, self).__init__(_container) self.m_direction = AnimationDirection.FromLeftToRight self.m_coveredWidget = _container.currentWidget() self.m_decorator = StackedWidgetSlideOverDecorator( _container, _widgetForGrab=_widgetForSlide) self.m_animation = QPropertyAnimation(self.m_decorator, b"pos") _container.installEventFilter(self) self.m_animation.setDuration(400) self.m_decorator.hide() def finished(): self.setAnimatedStopped() if self.isAnimatedForward(): _container.setCurrentWidget(_widgetForSlide) self.m_decorator.hide() self.m_animation.finished.connect(finished) def updateCoveredWidget(self): self.m_coveredWidget = self.stackedWidget().currentWidget() def setAnimationDirection(self, _direction): if self.m_direction != _direction: self.m_direction = _direction def animationDuration(self): return self.m_animation.duration() def animateForward(self): self.slideOverIn() def slideOverIn(self): if self.isAnimated() and self.isAnimatedForward(): return self.setAnimatedForward() self.m_decorator.grabWidget() startPos = QPoint() finalPos = QPoint() if self.m_direction == AnimationDirection.FromLeftToRight: startPos.setX(-1 * self.stackedWidget().width()) if self.m_direction == AnimationDirection.FromRightToLeft: startPos.setX(self.stackedWidget().width()) if self.m_direction == AnimationDirection.FromTopToBottom: startPos.setY(-1 * self.stackedWidget().height()) if self.m_direction == AnimationDirection.FromBottomToTop: startPos.setY(self.stackedWidget().height()) self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.InOutExpo) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startPos) self.m_animation.setEndValue(finalPos) self.m_animation.start() def animateBackward(self): self.slideOverOut() def slideOverOut(self): if self.isAnimated() and self.isAnimatedBackward(): return self.setAnimatedBackward() self.m_decorator.grabWidget() startPos = QPoint() finalPos = QPoint() if self.m_direction == AnimationDirection.FromLeftToRight: finalPos.setX(-1 * self.stackedWidget().width()) if self.m_direction == AnimationDirection.FromRightToLeft: finalPos.setX(self.stackedWidget().width()) if self.m_direction == AnimationDirection.FromTopToBottom: finalPos.setY(-1 * self.stackedWidget().height()) if self.m_direction == AnimationDirection.FromBottomToTop: finalPos.setY(self.stackedWidget().height()) if isinstance(self.stackedWidget(), type(self)): self.stackedWidget().setCurrentWidget(self.m_coveredWidget) self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.InOutExpo) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startPos) self.m_animation.setEndValue(finalPos) self.m_animation.start() def eventFilter(self, _object, _event): if _object == self.stackedWidget() and _event.type( ) == QEvent.Resize and self.m_decorator.isVisible(): self.m_decorator.grabWidget() return QWidget.eventFilter(self, _object, _event) def stackedWidget(self): return self.parent()
class StackedWidgetFadeInAnimator(AbstractAnimator): m_decorator: StackedWidgetFadeInDecorator m_animation: QPropertyAnimation def __init__(self, _container, _fadeWidget): super(StackedWidgetFadeInAnimator, self).__init__(_container) self.m_decorator = StackedWidgetFadeInDecorator(_container, _fadeWidget=_fadeWidget) self.m_animation = QPropertyAnimation(self.m_decorator, b"opacity") _container.installEventFilter(self) self.m_animation.setDuration(200) self.m_decorator.hide() def finished(): self.setAnimatedStopped() if self.m_animation.direction() == QPropertyAnimation.Forward: _container.setCurrentWidget(_fadeWidget) self.m_decorator.hide() self.m_animation.finished.connect(finished) def animationDuration(self): return self.m_animation.duration() def animateForward(self): self.fadeIn() def fadeIn(self): if self.isAnimated() and self.isAnimatedForward(): return self.setAnimatedForward() self.m_decorator.grabContainer() self.m_decorator.grabFadeWidget() startOpacity = 0 finalOpacity = 1 self.m_decorator.setOpacity(startOpacity) self.m_decorator.move(0, 0) self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.InQuad) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startOpacity) self.m_animation.setEndValue(finalOpacity) self.m_animation.start() def eventFilter(self, _object, _event): if _object == self.fadeWidget() and _event.type() == QEvent.Resize and self.m_decorator.isVisible(): self.m_decorator.grabContainer() self.m_decorator.grabFadeWidget() return QWidget.eventFilter(self, _object, _event) def fadeWidget(self): return self.parent()
class CircleFillAnimator(AbstractAnimator): m_decorator: CircleFillDecorator m_animation: QPropertyAnimation m_hideAfterFinish = True def __init__(self, _widgetForFill): super(CircleFillAnimator, self).__init__(_widgetForFill) self.m_decorator = CircleFillDecorator(_widgetForFill) self.m_animation = QPropertyAnimation(self.m_decorator, b"_radius") _widgetForFill.installEventFilter(self) self.m_animation.setDuration(500) self.m_decorator.setAttribute(Qt.WA_TransparentForMouseEvents) self.m_decorator.hide() def finished(): self.setAnimatedStopped() if self.m_hideAfterFinish: self.hideDecorator() self.m_animation.finished.connect(finished) def setStartPoint(self, _point): self.m_decorator.setStartPoint(_point) def setFillColor(self, _color): self.m_decorator.setFillColor(_color) def setHideAfterFinish(self, _hide): self.m_hideAfterFinish = _hide def animationDuration(self): return self.m_animation.duration() def animateForward(self): self.fillIn() def fillIn(self): if self.isAnimated() and self.isAnimatedForward(): return self.setAnimatedForward() startRadius = 0 finalRadius = math.sqrt( self.widgetForFill().height() * self.widgetForFill().height() + self.widgetForFill().width() * self.widgetForFill().width()) self.m_decorator.resize(self.widgetForFill().size()) self.m_decorator.move(0, 0) self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.OutQuart) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startRadius) self.m_animation.setEndValue(finalRadius) self.m_animation.start() def animateBackward(self): self.fillOut() def fillOut(self): if self.isAnimated() and self.isAnimatedBackward(): return self.setAnimatedBackward() startRadius = math.sqrt( self.widgetForFill().height() * self.widgetForFill().height() + self.widgetForFill().width() * self.widgetForFill().width()) finalRadius = 0 self.m_decorator.resize(self.widgetForFill().size()) self.m_decorator.move(0, 0) self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.OutQuart) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startRadius) self.m_animation.setEndValue(finalRadius) self.m_animation.start() def hideDecorator(self): hideEffect = self.m_decorator.graphicsEffect() if hideEffect == None: hideEffect = QGraphicsOpacityEffect(self.m_decorator) self.m_decorator.setGraphicsEffect(hideEffect) hideEffect.setOpacity(1) hideAnimation = QPropertyAnimation(hideEffect, b"opacity", self.m_decorator) hideAnimation.setDuration(400) hideAnimation.setStartValue(1) hideAnimation.setEndValue(0) def finished(): self.m_decorator.hide() hideEffect.setOpacity(1) hideAnimation.finished.connect(finished) hideAnimation.start(QAbstractAnimation.DeleteWhenStopped) def widgetForFill(self): return self.parent()
class ExpandAnimator(AbstractAnimator): m_decorator: ExpandDecorator m_animation: QPropertyAnimation m_expandRect: QRect def __init__(self, _widgetForFill): super(ExpandAnimator, self).__init__(_widgetForFill) self.m_decorator = ExpandDecorator(_widgetForFill) self.m_animation = QPropertyAnimation(self.m_decorator, b"_expandRect") _widgetForFill.installEventFilter(self) self.m_animation.setDuration(400) self.m_decorator.hide() def finished(): self.setAnimatedStopped() if self.isAnimatedBackward(): self.m_decorator.hide() self.m_animation.finished.connect(finished) def setExpandRect(self, _rect): self.m_expandRect = _rect self.m_decorator.setExpandRect(_rect) def setFillColor(self, _color): self.m_decorator.setFillColor(_color) def animationDuration(self): return self.m_animation.duration() def animateForward(self): self.expandIn() def expandIn(self): if self.isAnimated() and self.isAnimatedForward(): return self.setAnimatedForward() startExpandRect = self.m_expandRect finalExpandRect = self.widgetForFill().rect() self.m_decorator.resize(self.widgetForFill().size()) self.m_decorator.move(0, 0) self.m_decorator.grabExpandRect() self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.InOutQuart) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startExpandRect) self.m_animation.setEndValue(finalExpandRect) self.m_animation.start() def animateBackward(self): self.expandOut() def expandOut(self): if self.isAnimated() and self.isAnimatedBackward(): return self.setAnimatedBackward() startExpandRect = self.widgetForFill().rect() finalExpandRect = self.m_expandRect self.m_decorator.resize(self.widgetForFill().size()) self.m_decorator.move(0, 0) self.m_decorator.hide() self.m_decorator.grabExpandRect() self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.InOutQuart) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startExpandRect) self.m_animation.setEndValue(finalExpandRect) self.m_animation.start() def eventFilter(self, _object, _event): if _object == self.widgetForFill() and _event.type( ) == QEvent.Resize and self.m_decorator.isVisible(): self.m_decorator.resize(self.widgetForFill().size()) self.m_animation.setEndValue(self.widgetForFill().rect()) return QObject.eventFilter(self, _object, _event) def widgetForFill(self): return self.parent()
class SlideAnimator(AbstractAnimator): m_direction:AnimationDirection m_isFixBackground:bool m_isFixStartSize:bool m_startMinSize = QSize() m_startMaxSize = QSize() m_startSize = QSize() m_decorator: SlideForegroundDecorator m_animation: QPropertyAnimation def __init__(self, _widgetForSlide): super(SlideAnimator, self).__init__(_widgetForSlide) self.m_direction = AnimationDirection.FromLeftToRight self.m_isFixBackground = True self.m_isFixStartSize = False self.m_decorator = SlideForegroundDecorator(_widgetForSlide) self.m_animation = QPropertyAnimation(self.m_decorator, b"maximumWidth") _widgetForSlide.installEventFilter(self) self.m_animation.setDuration(300) self.m_decorator.hide() def valueChanged(_value): if self.isWidth(): self.widgetForSlide().setMaximumWidth(_value) else: self.widgetForSlide().setMaximumHeight(_value) self.m_animation.valueChanged.connect(valueChanged) def finished(): self.setAnimatedStopped() self.m_decorator.hide() self.m_animation.finished.connect(finished) def setAnimationDirection(self, _direction): if self.m_direction != _direction: self.m_direction = _direction self.m_animation.setPropertyName(b"maximumWidth" if self.isWidth() else b"maximumHeight") def setFixBackground(self, _fix): if self.m_isFixBackground != _fix: self.m_isFixBackground = _fix def setFixStartSize(self, _fix): if self.m_isFixStartSize != _fix: self.m_isFixStartSize = _fix def animationDuration(self): return self.m_animation.duration() def animateForward(self): self.slideIn() def slideIn(self): if not self.m_startMinSize.isValid(): self.m_startMinSize = self.widgetForSlide().minimumSize() if not self.m_startMaxSize.isValid(): self.m_startMaxSize = self.widgetForSlide().maximumSize() if not self.m_startSize.isValid(): self.m_startSize = self.widgetForSlide().sizeHint() if self.isAnimated() and self.isAnimatedForward(): return self.setAnimatedForward() if self.isWidth(): self.widgetForSlide().setMaximumWidth(0) else: self.widgetForSlide().setMaximumHeight(0) self.widgetForSlide().show() currentSize = self.widgetForSlide().size() finalSize = QSize(currentSize.width(), currentSize.height()) self.fixSize(self.m_startSize, finalSize) self.widgetForSlide().hide() self.fixSizeOfWidgetForSlide(finalSize) self.m_decorator.grabParent(finalSize) self.fixSizeOfWidgetForSlide(currentSize) self.widgetForSlide().show() if self.m_isFixBackground: self.m_decorator.move(0, 0) self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.OutQuart) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(self.widgetForSlide().width() if self.isWidth() else self.widgetForSlide().height()) self.m_animation.setEndValue(finalSize.width() if self.isWidth() else finalSize.height()) self.m_animation.start() def animateBackward(self): self.slideOut() def slideOut(self): if not self.m_startMinSize.isValid(): self.m_startMinSize = self.widgetForSlide().minimumSize() if not self.m_startMaxSize.isValid(): self.m_startMaxSize = self.widgetForSlide().maximumSize() if not self.m_startSize.isValid() or not self.m_isFixStartSize: self.m_startSize = self.widgetForSlide().size() if self.isAnimated() and self.isAnimatedBackward(): return self.setAnimatedBackward() finalSize = self.widgetForSlide().size() if self.isWidth(): finalSize.setWidth(0) else: finalSize.setHeight(0) self.m_decorator.grabParent(self.widgetForSlide().size()) if self.m_isFixBackground: self.m_decorator.move(0, 0) self.m_decorator.show() self.m_decorator.raise_() if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.InQuart) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(self.widgetForSlide().width() if self.isWidth() else self.widgetForSlide().height()) self.m_animation.setEndValue(finalSize.width() if self.isWidth() else finalSize.height()) self.m_animation.start() def eventFilter(self, _object, _event): if _object == self.widgetForSlide() and _event.type() == QEvent.Resize and self.m_decorator.isVisible(): if self.m_direction == AnimationDirection.FromLeftToRight: self.m_decorator.move(self.widgetForSlide().width() - self.m_decorator.width(), 0) if self.m_direction == AnimationDirection.FromTopToBottom: self.m_decorator.move(0, self.widgetForSlide().height() - self.m_decorator.height()) return QObject.eventFilter(self, _object, _event) def isWidth(self): return self.m_direction == AnimationDirection.FromLeftToRight or self.m_direction == AnimationDirection.FromRightToLeft def fixSize(self, _sourceSize, _targetSize): if self.isWidth(): _targetSize.setWidth(_sourceSize.width()) else: _targetSize.setHeight(_sourceSize.height()) def fixSizeOfWidgetForSlide(self, _sourceSize): if self.isWidth(): self.widgetForSlide().setFixedWidth(_sourceSize.width()) else: self.widgetForSlide().setFixedHeight(_sourceSize.height()) def widgetForSlide(self): return self.parent()
class SideSlideAnimator(AbstractAnimator): m_side: ApplicationSide m_decorateBackground: bool m_decorator: SideSlideDecorator m_animation: QPropertyAnimation def __init__(self, _widgetForSlide): super(SideSlideAnimator, self).__init__(_widgetForSlide) self.m_side = ApplicationSide() self.m_decorateBackground = True self.m_decorator = SideSlideDecorator(_widgetForSlide.parentWidget()) self.m_animation = QPropertyAnimation(self.m_decorator, b"_slidePos") _widgetForSlide.parentWidget().installEventFilter(self) self.m_animation.setEasingCurve(QEasingCurve.InQuad) self.m_animation.setDuration(260) self.m_decorator.hide() def finished(): self.setAnimatedStopped() if self.isAnimatedForward(): self.widgetForSlide().move(self.m_decorator.slidePos()) else: self.m_decorator.hide() self.m_animation.finished.connect(finished) self.m_decorator.clicked.connect(self.slideOut) def setApplicationSide(self, _side): if self.m_side != _side: self.m_side = _side def setDecorateBackground(self, _decorate): if self.m_decorateBackground != _decorate: self.m_decorateBackground = _decorate def animationDuration(self): return self.m_animation.duration() def animateForward(self): self.slideIn() def slideIn(self): if self.isAnimated() and self.isAnimatedForward(): return self.setAnimatedForward() self.widgetForSlide().lower() self.widgetForSlide().move(-self.widgetForSlide().width(), -self.widgetForSlide().height()) self.widgetForSlide().show() self.widgetForSlide().resize(self.widgetForSlide().sizeHint()) _topWidget = self.widgetForSlide() topWidget = self.widgetForSlide() while _topWidget: topWidget = _topWidget _topWidget = topWidget.parentWidget() finalSize = self.widgetForSlide().size() startPosition = QPoint() finalPosition = QPoint() if self.m_side == ApplicationSide.LeftSide: finalSize.setHeight(topWidget.height()) startPosition = QPoint(-finalSize.width(), 0) finalPosition = QPoint(0, 0) if self.m_side == ApplicationSide.TopSide: finalSize.setWidth(topWidget.width()) startPosition = QPoint(0, -finalSize.height()) finalPosition = QPoint(0, 0) if self.m_side == ApplicationSide.RightSide: finalSize.setHeight(topWidget.height()) startPosition = QPoint(topWidget.width(), 0) finalPosition = QPoint(topWidget.width() - finalSize.width(), 0) if self.m_side == ApplicationSide.BottomSide: finalSize.setWidth(topWidget.width()) startPosition = QPoint(0, topWidget.height()) finalPosition = QPoint(0, topWidget.height() - finalSize.height()) if self.m_decorateBackground: self.m_decorator.setParent(topWidget) self.m_decorator.move(0, 0) self.m_decorator.grabParent() self.m_decorator.show() self.m_decorator.raise_() self.widgetForSlide().move(startPosition) self.widgetForSlide().resize(finalSize) self.widgetForSlide().raise_() self.m_decorator.grabSlideWidget(self.widgetForSlide()) if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.OutQuart) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startPosition) self.m_animation.setEndValue(finalPosition) self.m_animation.start() if self.m_decorateBackground: DARKER = True self.m_decorator.decorate(DARKER) def animateBackward(self): self.slideOut() def slideOut(self): if self.isAnimated() and self.isAnimatedBackward(): return self.setAnimatedBackward() if self.widgetForSlide().isVisible(): _topWidget = self.widgetForSlide() topWidget = self.widgetForSlide() while _topWidget: topWidget = _topWidget _topWidget = topWidget.parentWidget() self.widgetForSlide().hide() finalSize = self.widgetForSlide().size() startPosition = self.widgetForSlide().pos() finalPosition = QPoint() if self.m_side == ApplicationSide.LeftSide: startPosition = QPoint(0, 0) finalPosition = QPoint(-finalSize.width(), 0) if self.m_side == ApplicationSide.TopSide: startPosition = QPoint(0, 0) finalPosition = QPoint(0, -finalSize.height()) if self.m_side == ApplicationSide.RightSide: startPosition = QPoint(topWidget.width() - finalSize.width(), 0) finalPosition = QPoint(topWidget.width(), 0) if self.m_side == ApplicationSide.BottomSide: startPosition = QPoint(0, topWidget.height() - finalSize.height()) finalPosition = QPoint(0, topWidget.height()) if self.m_animation.state() == QPropertyAnimation.Running: self.m_animation.pause() self.m_animation.setDirection(QPropertyAnimation.Backward) self.m_animation.resume() else: self.m_animation.setEasingCurve(QEasingCurve.InQuart) self.m_animation.setDirection(QPropertyAnimation.Forward) self.m_animation.setStartValue(startPosition) self.m_animation.setEndValue(finalPosition) self.m_animation.start() if self.m_decorateBackground: LIGHTER = False self.m_decorator.decorate(LIGHTER) def eventFilter(self, _object, _event): if _object == self.widgetForSlide().parentWidget() and _event.type( ) == QEvent.Resize: widgetForSlideParent = self.widgetForSlide().parentWidget() if self.m_side == ApplicationSide.RightSide: self.widgetForSlide().move( widgetForSlideParent.width() - self.widgetForSlide().width(), 0) if self.m_side == ApplicationSide.LeftSide: self.widgetForSlide().resize(self.widgetForSlide().width(), widgetForSlideParent.height()) if self.m_side == ApplicationSide.BottomSide: self.widgetForSlide().move( 0, widgetForSlideParent.height() - self.widgetForSlide().height()) if self.m_side == ApplicationSide.TopSide: self.widgetForSlide().resize(widgetForSlideParent.width(), self.widgetForSlide().height()) self.m_decorator.grabSlideWidget(self.widgetForSlide()) return QObject.eventFilter(self, _object, _event) def widgetForSlide(self): return self.parent()
class Button(QGraphicsObject): font: QFont = QFont("monospace", 16) clicked = pyqtSignal('PyQt_PyObject') hovered_ = pyqtSignal('PyQt_PyObject') def __init__(self, btn_id: str, label: str, tooltip: str = "", parent: QGraphicsItem = None): QGraphicsObject.__init__(self, parent) self.setFlag(QGraphicsItem.ItemIgnoresTransformations, True) self.scaling = 1.0 self.label = QGraphicsSimpleTextItem(label, self) self.label.setFont(Button.font) self.text_color_enabled = QColor(255, 255, 255, 255) self.text_color_disabled = QColor(200, 200, 200, 255) self.fill_color_disabled = QColor(125, 125, 125, 200) self.label.setBrush(QBrush(self.text_color_enabled)) self.btn_id = btn_id self.rect = QRectF(0, 0, 0, 0) self.tooltip = QGraphicsSimpleTextItem(tooltip, self) self.tooltip.setBrush(QColor(255, 255, 255, 200)) self.tooltip.setFont(Button.font) self.tooltip.setVisible(False) self.tooltip.setZValue(25) self.tooltip_shown = False self.base_color = ButtonColor.GRAY self.hor_margin = 10 self.ver_margin = 5 self.current_timer = 0 self.logic: Union[CheckbuttonLogic, PushbuttonLogic] = PushbuttonLogic("gray") self.fill_color_current = self.logic.idle_color() self.color_animation = QPropertyAnimation(self, b"current_fill_color") self.color_animation.setEasingCurve(QEasingCurve.Linear) self.hovered = False self.setAcceptHoverEvents(True) self.setZValue(4) self.set_height(12) self.pixmap: QPixmap = None self.max_pixmap_height = 128 self.disabled = False self.mode = ButtonMode.Button def set_height(self, h: int): self.prepareGeometryChange() self.ver_margin = int(0.25 * h) font: QFont = self.label.font() font.setPointSize(h) self.label.setFont(font) self.rect.setWidth(self.label.boundingRect().width() + 2 * self.hor_margin) self.rect.setHeight(self.label.boundingRect().height() + 2 * self.ver_margin) self._reposition_text() def set_width(self, w: int): if self.pixmap is not None: return self.prepareGeometryChange() self.rect.setWidth(w) self.hor_margin = self.ver_margin if self.label.boundingRect().width() > self.rect.width(): w = self.rect.width() - 2 * self.hor_margin factor = w / self.label.boundingRect().width() h = factor * self.label.boundingRect().height() font = self.label.font() font.setPixelSize(max(h, 12)) self.label.setFont(font) self._reposition_text() def set_button_height(self, h: int): self.rect.setHeight(h) self._reposition_text() def scale_button(self, factor: float): factor = 1.0 self.rect.setHeight(int(factor * self.rect.height())) self.rect.setWidth(int(factor * self.rect.width())) self.label.setScale(self.scaling) self.fit_to_contents() def _reposition_text(self): x = self.rect.width() / 2 - self.label.boundingRect().width() / 2 y = self.rect.height() / 2 - self.label.boundingRect().height() / 2 self.label.setPos(QPointF(x, y)) self.update() def boundingRect(self): return self.rect def paint(self, painter: QPainter, options, widget=None): painter.setBrush(QBrush(self.fill_color_current)) painter.setPen(QPen(QColor(0, 0, 0, 0))) painter.drawRoundedRect(self.rect, 5, 5) if self.pixmap is not None: painter.drawPixmap(self.hor_margin * self.scaling, self.ver_margin * self.scaling, self.pixmap) if self.tooltip_shown: painter.setBrush(QBrush(QColor(50, 50, 50, 200))) painter.drawRoundedRect(self.tooltip.boundingRect().translated(self.tooltip.pos()) .marginsAdded(QMarginsF(5, 0, 5, 0)), 5, 5) def mousePressEvent(self, event: QGraphicsSceneMouseEvent): if self.disabled or self.mode == ButtonMode.Label: return self.fill_color_current = self.logic.press_color() self.scene().update() def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent): if self.disabled or self.mode == ButtonMode.Label: return self.click_button() def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent): if self.disabled or self.mode == ButtonMode.Label: return self.hovered_.emit({'btn_id': self.btn_id, 'button': self, 'hovered': True}) self.hovered = True if len(self.tooltip.text()) > 0: self.tooltip_shown = True self.tooltip.setVisible(True) view = self.scene().views()[0] rect_ = view.mapFromScene(self.tooltip.sceneBoundingRect()).boundingRect() pos = self.boundingRect().topRight() mouse_pos = view.mapFromScene(event.scenePos()) if mouse_pos.x() + rect_.width() >= view.viewport().width(): pos = QPointF(-self.tooltip.boundingRect().width(), 0) self.tooltip.setPos(pos) self.color_animation.setDuration(200) self.color_animation.setStartValue(self.logic.idle_color()) self.color_animation.setEndValue(self.logic.hover_enter_color()) self.scene().update() if self.current_timer >= 0: self.killTimer(self.current_timer) self.color_animation.start() self.current_timer = self.startTimer(self.color_animation.duration() // 80) def hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent): if self.disabled or self.mode == ButtonMode.Label: return self.hovered_.emit({'btn_id': self.btn_id, 'button': self, 'hovered': False}) self.hovered = False self.tooltip_shown = False self.tooltip.setVisible(False) self.color_animation.setDuration(200) self.color_animation.setStartValue(self.logic.hover_enter_color()) self.color_animation.setEndValue(self.logic.hover_left_color()) self.scene().update() if self.current_timer > 0: self.killTimer(self.current_timer) self.color_animation.start() self.current_timer = self.startTimer(self.color_animation.duration() // 80) def hoverMoveEvent(self, event: QGraphicsSceneHoverEvent): pass @pyqtProperty(QColor) def current_fill_color(self): return self.fill_color_current @current_fill_color.setter def current_fill_color(self, color: QColor): self.fill_color_current = color def timerEvent(self, ev: QTimerEvent): self.scene().update() if self.color_animation.state() == QPropertyAnimation.Stopped: self.killTimer(ev.timerId()) self.current_timer = 0 def set_base_color(self, colors: List[ButtonColor]): self.logic.set_colors(colors) self.fill_color_current = self.logic.idle_color() def is_on(self): return self.logic.is_down() def set_on(self, value: bool): if isinstance(self.logic, CheckbuttonLogic): self.logic.down = value self.fill_color_current = self.logic.press_color() if value else self.logic.idle_color() self.update() def set_is_check_button(self, value: bool): if value: self.logic = CheckbuttonLogic() else: self.logic = PushbuttonLogic("gray") def set_pixmap(self, pixmap: QPixmap): if pixmap is not None: self.pixmap = pixmap.scaledToHeight(self.max_pixmap_height * self.scaling) if pixmap is not None else None self.fit_to_contents() def fit_to_contents(self): self.prepareGeometryChange() width = 2 * self.hor_margin * self.label.scale() height = 2 * self.ver_margin * self.label.scale() + self.label.boundingRect().height() * self.label.scale() if self.pixmap is not None: width += max(self.pixmap.width(), self.label.boundingRect().width() * self.label.scale()) height += self.ver_margin * self.scaling + self.pixmap.height() else: width += self.label.boundingRect().width() * self.label.scale() self.rect.setWidth(width) self.rect.setHeight(height) self.label.setPos(0.5 * width - 0.5 * self.label.boundingRect().width() * self.label.scale() + 0.0 * self.hor_margin, height - self.label.boundingRect().height() * self.label.scale() - self.ver_margin * self.scaling) self.update() def adjust_text_to_button(self): height_diff = 0.75 * self.rect.height() - self.label.boundingRect().height() fac = height_diff / (0.75 * self.rect.height()) self.label.setTransformOriginPoint(self.label.boundingRect().center()) self.label.setScale(1.0 + fac) self._reposition_text() def set_label(self, text: str, direction: str = 'horizontal'): if direction == 'vertical': text = '\n'.join(list(text)) self.label.setText(text) self._reposition_text() def click_button(self, artificial_emit: bool = False): if self.disabled: return self.logic.do_click() self.fill_color_current = self.logic.release_color() if not artificial_emit else self.logic.idle_color() self.clicked.emit({"btn_id": self.btn_id, "btn_label": self.label, "button": self, 'checked': self.is_on()}) if artificial_emit: self.hovered_.emit({'btn_id': self.btn_id, 'button': self, 'hovered': False}) self.update() if self.scene() is not None: self.scene().update() def set_opacity(self, opacity: float): self.setOpacity(opacity) self.update() def set_default_state(self): self.logic.reset_state() self.fill_color_current = self.logic.idle_color() self.tooltip.setVisible(False) self.tooltip_shown = False self.update() def set_disabled(self, disabled: bool): self.disabled = disabled if disabled: self.label.setBrush(QBrush(self.text_color_disabled)) self.fill_color_current = self.fill_color_disabled else: self.label.setBrush(QBrush(self.text_color_enabled)) self.fill_color_current = self.logic.idle_color() self.update() def set_tooltip(self, tooltip: str): self.tooltip.setVisible(False) self.tooltip_shown = False self.tooltip.setText(tooltip) def set_custom_color(self, colors: List[QColor]): if isinstance(self.logic, CheckbuttonLogic): if len(colors) < 2: return self.logic.set_colors(colors=[ colors[0], colors[0].lighter(120), colors[0].darker(120), colors[1], colors[1].lighter(120), colors[1].darker(120) ]) else: self.logic.set_colors(colors=[ colors[0], colors[0].lighter(120), colors[0].darker(120) ]) self.fill_color_current = self.logic.idle_color() self.update() def set_mode(self, mode: ButtonMode): self.mode = mode