# coding:utf-8 from typing import List from PySide6.QtCore import (QAbstractAnimation, QEasingCurve, QPoint, QPropertyAnimation, Signal) from PySide6.QtWidgets import QGraphicsOpacityEffect, QStackedWidget, QWidget class OpacityAniStackedWidget(QStackedWidget): """ Stacked widget with fade in and fade out animation """ def __init__(self, parent=None): super().__init__(parent=parent) self.__nextIndex = 0 self.__effects = [] # type:List[QPropertyAnimation] self.__anis = [] # type:List[QPropertyAnimation] def addWidget(self, w: QWidget): super().addWidget(w) effect = QGraphicsOpacityEffect(self) effect.setOpacity(1) ani = QPropertyAnimation(effect, b'opacity', self) ani.setDuration(220) ani.finished.connect(self.__onAniFinished) self.__anis.append(ani) self.__effects.append(effect) w.setGraphicsEffect(effect) def setCurrentIndex(self, index: int): index_ = self.currentIndex() if index == index_: return if index > index_: ani = self.__anis[index] ani.setStartValue(0) ani.setEndValue(1) super().setCurrentIndex(index) else: ani = self.__anis[index_] ani.setStartValue(1) ani.setEndValue(0) self.widget(index_).show() self.__nextIndex = index ani.start() def setCurrentWidget(self, w: QWidget): self.setCurrentIndex(self.indexOf(w)) def __onAniFinished(self): super().setCurrentIndex(self.__nextIndex) class PopUpAniInfo: """ Pop up ani info """ def __init__(self, widget: QWidget, deltaX: int, deltaY, ani: QPropertyAnimation): self.widget = widget self.deltaX = deltaX self.deltaY = deltaY self.ani = ani class PopUpAniStackedWidget(QStackedWidget): """ Stacked widget with pop up animation """ aniFinished = Signal() aniStart = Signal() def __init__(self, parent=None): super().__init__(parent) self.aniInfos = [] # type: List[PopUpAniInfo] self._nextIndex = None self._ani = None def addWidget(self, widget, deltaX=0, deltaY=76): """ add widget to window Parameters ----------- widget: widget to be added deltaX: int the x-axis offset from the beginning to the end of animation deltaY: int the y-axis offset from the beginning to the end of animation """ super().addWidget(widget) self.aniInfos.append(PopUpAniInfo( widget=widget, deltaX=deltaX, deltaY=deltaY, ani=QPropertyAnimation(widget, b'pos'), )) def setCurrentIndex(self, index: int, needPopOut: bool = False, showNextWidgetDirectly: bool = True, duration: int = 250, easingCurve=QEasingCurve.OutQuad): """ set current window to display Parameters ---------- index: int the index of widget to display isNeedPopOut: bool need pop up animation or not showNextWidgetDirectly: bool whether to show next widget directly when animation started duration: int animation duration easingCurve: QEasingCurve the interpolation mode of animation """ if index < 0 or index >= self.count(): raise Exception(f'The index `{index}` is illegal') if index == self.currentIndex(): return if self._ani and self._ani.state() == QAbstractAnimation.Running: self._ani.stop() self.__onAniFinished() # get the index of widget to be displayed self._nextIndex = index # get animation nextAniInfo = self.aniInfos[index] currentAniInfo = self.aniInfos[self.currentIndex()] currentWidget = self.currentWidget() nextWidget = nextAniInfo.widget ani = currentAniInfo.ani if needPopOut else nextAniInfo.ani self._ani = ani if needPopOut: deltaX, deltaY = currentAniInfo.deltaX, currentAniInfo.deltaY pos = currentWidget.pos() + QPoint(deltaX, deltaY) self.__setAnimation(ani, currentWidget.pos(), pos, duration, easingCurve) nextWidget.setVisible(showNextWidgetDirectly) else: deltaX, deltaY = nextAniInfo.deltaX, nextAniInfo.deltaY pos = nextWidget.pos() + QPoint(deltaX, deltaY) self.__setAnimation(ani, pos, QPoint(nextWidget.x(), self.y()), duration, easingCurve) super().setCurrentIndex(index) # start animation ani.finished.connect(self.__onAniFinished) ani.start() self.aniStart.emit() def setCurrentWidget(self, widget, needPopOut: bool = False, showNextWidgetDirectly: bool = True, duration: int = 250, easingCurve=QEasingCurve.OutQuad): """ set currect widget Parameters ---------- widget: the widget to be displayed isNeedPopOut: bool need pop up animation or not showNextWidgetDirectly: bool whether to show next widget directly when animation started duration: int animation duration easingCurve: QEasingCurve the interpolation mode of animation """ self.setCurrentIndex( self.indexOf(widget), needPopOut, showNextWidgetDirectly, duration, easingCurve) def __setAnimation(self, ani, startValue, endValue, duration, easingCurve=QEasingCurve.Linear): """ set the config of animation """ ani.setEasingCurve(easingCurve) ani.setStartValue(startValue) ani.setEndValue(endValue) ani.setDuration(duration) def __onAniFinished(self): """ animation finished slot """ self._ani.finished.disconnect() super().setCurrentIndex(self._nextIndex) self.aniFinished.emit()