def addWidget(self, widget, deltaX: int = 0, deltaY: int = 22, isNeedOpacityAni=True): """ 添加堆叠窗口\n Parameters ----------- widget : 窗口\n deltaX : 窗口动画开始到结束的x轴偏移量\n deltaY : 窗口动画开始到结束的y轴偏移量\n isNeedOpacityAni : 是否需要淡入淡出动画\n """ super().addWidget(widget) # 创建动画 popUpAni = QPropertyAnimation(widget, b'geometry') aniGroup = QParallelAnimationGroup(self) aniGroup.addAnimation(popUpAni) self.__widgetAni_list.append({ 'widget': widget, 'deltaX': deltaX, 'deltaY': deltaY, 'aniGroup': aniGroup, 'popUpAni': popUpAni, 'isNeedOpacityAni': isNeedOpacityAni })
def start(self): height = self.height() width = self.width() for i in range(self._ball_numbers): self._seq_animations.append(QSequentialAnimationGroup(self)) # 增加间隔 self._seq_animations[i].addPause(self.duration_pause * i) # 第一段 par_animation1 = QParallelAnimationGroup(self) ra = RoundAnimation(self._balls[i], b'pos', self) ra.setEasingCurve(self.ec) ra.setDuration(self.duration) ra.setStartValue(QPoint(2 * self.ball_radius, 2 * self.ball_radius)) ra.setEndValue(QPoint(width - 2 * self.ball_radius, height - 2 * self.ball_radius)) par_animation1.addAnimation(ra) pa = QPropertyAnimation(self._balls[i], b'_style', self) pa.setEasingCurve(self.ec) pa.setDuration(self.duration) pa.setStartValue(0) pa.setKeyValueAt(0.5, 255) pa.setEndValue(0) par_animation1.addAnimation(pa) self._seq_animations[i].addAnimation(par_animation1) # 增加间隔 self._seq_animations[i].addPause(self.duration_pause * (self._ball_numbers - i - 1)) for seq in self._seq_animations: seq.setLoopCount(-1) seq.start(QAbstractAnimation.DeleteWhenStopped)
def __init__(self): super(Robot, self).__init__() self.setFlag(self.ItemHasNoContents) self.torsoItem = RobotTorso(self) self.headItem = RobotHead(self.torsoItem) self.upperLeftArmItem = RobotLimb(self.torsoItem) self.lowerLeftArmItem = RobotLimb(self.upperLeftArmItem) self.upperRightArmItem = RobotLimb(self.torsoItem) self.lowerRightArmItem = RobotLimb(self.upperRightArmItem) self.upperRightLegItem = RobotLimb(self.torsoItem) self.lowerRightLegItem = RobotLimb(self.upperRightLegItem) self.upperLeftLegItem = RobotLimb(self.torsoItem) self.lowerLeftLegItem = RobotLimb(self.upperLeftLegItem) settings = ( # Item Position Rotation Scale # x y start end (self.headItem, 0, -18, 20, -20, 1.1), (self.upperLeftArmItem, -15, -10, 190, 180, 0), (self.lowerLeftArmItem, 30, 0, 50, 10, 0), (self.upperRightArmItem, 15, -10, 300, 310, 0), (self.lowerRightArmItem, 30, 0, 0, -70, 0), (self.upperRightLegItem, 10, 32, 40, 120, 0), (self.lowerRightLegItem, 30, 0, 10, 50, 0), (self.upperLeftLegItem, -10, 32, 150, 80, 0), (self.lowerLeftLegItem, 30, 0, 70, 10, 0), (self.torsoItem, 0, 0, 5, -20, 0), ) animation = QParallelAnimationGroup(self) for item, pos_x, pos_y, start_rot, end_rot, scale in settings: item.setPos(pos_x, pos_y) rot_animation = QPropertyAnimation(item, b"rotation") rot_animation.setStartValue(start_rot) rot_animation.setEndValue(end_rot) rot_animation.setEasingCurve(QEasingCurve.SineCurve) rot_animation.setDuration(2000) animation.addAnimation(rot_animation) if scale > 0: scale_animation = QPropertyAnimation(item, b"scale") scale_animation.setEndValue(scale) scale_animation.setEasingCurve(QEasingCurve.SineCurve) scale_animation.setDuration(2000) animation.addAnimation(scale_animation) animation.setLoopCount(-1) animation.start()
def __init__(self): super(Robot, self).__init__() self.setFlag(self.ItemHasNoContents) self.torsoItem = RobotTorso(self) self.headItem = RobotHead(self.torsoItem) self.upperLeftArmItem = RobotLimb(self.torsoItem) self.lowerLeftArmItem = RobotLimb(self.upperLeftArmItem) self.upperRightArmItem = RobotLimb(self.torsoItem) self.lowerRightArmItem = RobotLimb(self.upperRightArmItem) self.upperRightLegItem = RobotLimb(self.torsoItem) self.lowerRightLegItem = RobotLimb(self.upperRightLegItem) self.upperLeftLegItem = RobotLimb(self.torsoItem) self.lowerLeftLegItem = RobotLimb(self.upperLeftLegItem) settings = ( # Item Position Rotation Scale # x y start end (self.headItem, 0, -18, 20, -20, 1.1), (self.upperLeftArmItem, -15, -10, 190, 180, 0), (self.lowerLeftArmItem, 30, 0, 50, 10, 0), (self.upperRightArmItem, 15, -10, 300, 310, 0), (self.lowerRightArmItem, 30, 0, 0, -70, 0), (self.upperRightLegItem, 10, 32, 40, 120, 0), (self.lowerRightLegItem, 30, 0, 10, 50, 0), (self.upperLeftLegItem, -10, 32, 150, 80, 0), (self.lowerLeftLegItem, 30, 0, 70, 10, 0), (self.torsoItem, 0, 0, 5, -20, 0), ) animation = QParallelAnimationGroup(self) for item, pos_x, pos_y, start_rot, end_rot, scale in settings: item.setPos(pos_x, pos_y) rot_animation = QPropertyAnimation(item, b"rotation") rot_animation.setStartValue(start_rot) rot_animation.setEndValue(end_rot) rot_animation.setEasingCurve(QEasingCurve.SineCurve) rot_animation.setDuration(2000) animation.addAnimation(rot_animation) if scale > 0: scale_animation = QPropertyAnimation(item, b"scale") scale_animation.setEndValue(scale) scale_animation.setEasingCurve(QEasingCurve.SineCurve) scale_animation.setDuration(2000) animation.addAnimation(scale_animation) animation.setLoopCount(-1) animation.start()
def reprise(pix): ## reposition pixitems to starting x,y, etc. node = Node(pix) node.pix.setOriginPt() sync = 1000 reprise = QPropertyAnimation(node, b'pos') reprise.setDuration(sync) reprise.setStartValue(node.pix.pos()) reprise.setEndValue(QPointF(pix.x, pix.y)) spin = QPropertyAnimation(node, b'rotate') spin.setDuration(sync) spin.setStartValue(node.pix.rotation) spin.setKeyValueAt(0.50, pix.rotation + random.randint(15, 45)) spin.setEndValue(pix.rotation) scale = QPropertyAnimation(node, b'scale') scale.setDuration(sync) scale.setStartValue(node.pix.scale) scale.setEndValue(pix.scale) opacity = QPropertyAnimation(node, b'opacity') opacity.setDuration(sync) opacity.setStartValue(node.pix.opacity()) opacity.setEndValue(1) group = QParallelAnimationGroup() group.addAnimation(reprise) group.addAnimation(spin) group.addAnimation(scale) group.addAnimation(opacity) return group
def step_ani(self, is_back=False): title_move_ani, title_fade_ani = self.fade_to_left_ani(self.title) content1_move_ani, content1_fade_ani = self.fade_to_left_ani( self.content1) content2_move_ani, content2_fade_ani = self.fade_to_left_ani( self.content2) form_move_ani, form_fade_ani = self.fade_to_left_ani(self.form) # 动画队列 ani_group = QParallelAnimationGroup(self) ani_group.addAnimation(title_move_ani) ani_group.addAnimation(title_fade_ani) ani_group.addAnimation(content1_move_ani) ani_group.addAnimation(content1_fade_ani) ani_group.addAnimation(content2_move_ani) ani_group.addAnimation(content2_fade_ani) ani_group.addAnimation(form_move_ani) ani_group.addAnimation(form_fade_ani) if is_back: ani_group.setDirection(ani_group.Backward) ani_group.start() return ani_group
def __init__(self, parent=None): super(myobj, self).__init__(parent) self.color = QColor(Qt.lightGray) animation = QParallelAnimationGroup(self) rot_animation = QPropertyAnimation(self, b'rotation') rot_animation.setStartValue(-200) rot_animation.setEndValue(600) rot_animation.setEasingCurve(QEasingCurve.SineCurve) rot_animation.setDuration(3000) animation.addAnimation(rot_animation) animation.setLoopCount(-1) animation.start()
def createAnimation(self, target, index): '''创建动画''' # 暂停动画一 PauseAnimation1 = QPauseAnimation(target) PauseAnimation1.setDuration(150 * index) # 并行动画组一 # #透明度动画一 OpacityAnimation1 = QPropertyAnimation(target, b"opacity") OpacityAnimation1.setDuration(400) OpacityAnimation1.setStartValue(0) OpacityAnimation1.setEndValue(1) # #移动动画一 MoveAnimation1 = _MoveAnimation(target, self.parent, self.easing) MoveAnimation1.setMoveType(_MoveAnimation.MOVE1) MoveAnimation1.setDuration(400) MoveAnimation1.setStartValue(QPoint(0, 0)) MoveAnimation1.setEndValue(QPoint(self.parent.width() / 4.0, 0)) # 添加到并行动画里面 ParallelAnimation1 = QParallelAnimationGroup() ParallelAnimation1.addAnimation(OpacityAnimation1) ParallelAnimation1.addAnimation(MoveAnimation1) # 移动动画二 MoveAnimation2 = _MoveAnimation(target, self.parent, self.easing) MoveAnimation2.setMoveType(_MoveAnimation.MOVE2) MoveAnimation2.setDuration(2000) MoveAnimation2.setEndValue(QPoint((self.parent.width() / 4.0) * 3.0, 0)) # 并行动画组二 # #透明度动画二 OpacityAnimation2 = QPropertyAnimation(target, b"opacity") OpacityAnimation2.setDuration(400) OpacityAnimation2.setStartValue(1) OpacityAnimation2.setEndValue(0) # #移动动画三 MoveAnimation3 = _MoveAnimation(target, self.parent, self.easing) MoveAnimation3.setMoveType(_MoveAnimation.MOVE3) MoveAnimation3.setDuration(400) MoveAnimation3.setEndValue(QPoint(self.parent.width(), 0)) # 添加到并行动画里面 ParallelAnimation2 = QParallelAnimationGroup() ParallelAnimation2.addAnimation(OpacityAnimation2) ParallelAnimation2.addAnimation(MoveAnimation3) # 暂停动画二 PauseAnimation2 = QPauseAnimation(target) PauseAnimation2.setDuration(150 * (5 - index - 1)) # 串行动画组 self.setLoopCount(-1) # 无限循环 self.addAnimation(PauseAnimation1) self.addAnimation(ParallelAnimation1) self.addAnimation(MoveAnimation2) self.addAnimation(ParallelAnimation2) self.addAnimation(PauseAnimation2)
def createAnimation(self, target, index): '''创建动画''' # 暂停动画一 PauseAnimation1 = QPauseAnimation(target) PauseAnimation1.setDuration(150 * index) # 并行动画组一 # #透明度动画一 OpacityAnimation1 = QPropertyAnimation(target, b"opacity") OpacityAnimation1.setDuration(400) OpacityAnimation1.setStartValue(0) OpacityAnimation1.setEndValue(1) # #移动动画一 MoveAnimation1 = _MoveAnimation(target, self.parent, self.easing) MoveAnimation1.setMoveType(_MoveAnimation.MOVE1) MoveAnimation1.setDuration(400) MoveAnimation1.setStartValue(QPoint(0, 0)) MoveAnimation1.setEndValue(QPoint(self.parent.width() / 4.0, 0)) # 添加到并行动画里面 ParallelAnimation1 = QParallelAnimationGroup() ParallelAnimation1.addAnimation(OpacityAnimation1) ParallelAnimation1.addAnimation(MoveAnimation1) # 移动动画二 MoveAnimation2 = _MoveAnimation(target, self.parent, self.easing) MoveAnimation2.setMoveType(_MoveAnimation.MOVE2) MoveAnimation2.setDuration(2000) MoveAnimation2.setEndValue(QPoint((self.parent.width() / 4.0) * 3.0, 0)) # 并行动画组二 # #透明度动画二 OpacityAnimation2 = QPropertyAnimation(target, b"opacity") OpacityAnimation2.setDuration(400) OpacityAnimation2.setStartValue(1) OpacityAnimation2.setEndValue(0) # #移动动画三 MoveAnimation3 = _MoveAnimation(target, self.parent, self.easing) MoveAnimation3.setMoveType(_MoveAnimation.MOVE3) MoveAnimation3.setDuration(400) MoveAnimation3.setEndValue(QPoint(self.parent.width(), 0)) # 添加到并行动画里面 ParallelAnimation2 = QParallelAnimationGroup() ParallelAnimation2.addAnimation(OpacityAnimation2) ParallelAnimation2.addAnimation(MoveAnimation3) # 暂停动画二 PauseAnimation2 = QPauseAnimation(target) PauseAnimation2.setDuration(150 * (5 - index - 1)) # 串行动画组 self.setLoopCount(-1) # 无限循环 self.addAnimation(PauseAnimation1) self.addAnimation(ParallelAnimation1) self.addAnimation(MoveAnimation2) self.addAnimation(ParallelAnimation2) self.addAnimation(PauseAnimation2)
def Show(self): print("Initiating animation") animation_group = QParallelAnimationGroup(self) for w in (self.LoginText, self.UploadButton, self.PassText): start_pos = w.pos() end_pos = w.pos() + QPoint(0, -250) animation = QPropertyAnimation( self, propertyName=b"pos", targetObject=w, startValue=start_pos, endValue=end_pos, duration=900, ) animation_group.addAnimation(animation) animation_group.start(QAnimationGroup.DeleteWhenStopped) print("Aniamtion initiated") self.ui.show()
class CollapsibleBox(QWidget): def __init__(self, title="", parent=None): super(CollapsibleBox, self).__init__(parent) self.toggle_button = QToolButton(text=title, checkable=True, checked=False) self.toggle_button.setStyleSheet("QToolButton { border: none; }") self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggle_button.setArrowType(Qt.RightArrow) self.toggle_button.pressed.connect(self.on_pressed) self.toggle_animation = QParallelAnimationGroup(self) self.content_area = QScrollArea(maximumHeight=0, minimumHeight=0) self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.content_area.setFrameShape(QFrame.NoFrame) lay = QVBoxLayout(self) lay.setSpacing(0) lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(self.toggle_button) lay.addWidget(self.content_area) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self, b"maximumHeight")) self.toggle_animation.addAnimation( QPropertyAnimation(self.content_area, b"maximumHeight")) @pyqtSlot() def on_pressed(self): checked = self.toggle_button.isChecked() self.toggle_button.setArrowType( Qt.DownArrow if not checked else Qt.RightArrow) self.toggle_animation.setDirection( QAbstractAnimation.Forward if not checked else QAbstractAnimation. Backward) self.toggle_animation.start() def setContentLayout(self, layout): lay = self.content_area.layout() del lay self.content_area.setLayout(layout) collapsed_height = self.sizeHint().height( ) - self.content_area.maximumHeight() content_height = layout.sizeHint().height() for i in range(self.toggle_animation.animationCount()): animation = self.toggle_animation.animationAt(i) animation.setDuration(500) animation.setStartValue(collapsed_height) animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt( self.toggle_animation.animationCount() - 1) content_animation.setDuration(500) content_animation.setStartValue(0) content_animation.setEndValue(content_height)
def fin(pix): ## delete pixitem node = Node(pix) node.pix.setOriginPt() sync = random.randint(6, 10) * 75 rot = 270 if not random.randint(0, 1): rot = -270 rotate = QPropertyAnimation(node, b'rotate') rotate.setDuration(sync) rotate.setStartValue(node.pix.rotation) rotate.setEndValue(rot + node.pix.rotation) opacity = QPropertyAnimation(node, b'opacity') opacity.setDuration(sync) opacity.setStartValue(node.pix.opacity()) opacity.setEndValue(0) scale = QPropertyAnimation(node, b'scale') scale.setDuration(sync) scale.setStartValue(node.pix.scale) scale.setEndValue(node.pix.scale * .25) group = QParallelAnimationGroup() group.addAnimation(rotate) group.addAnimation(opacity) group.addAnimation(scale) group.setLoopCount(1) return group
def start(self): bh = self.height() / 2 - self._ball_radius width = self.width() for i in range(self._ball_numbers): self._seq_animations.append(QSequentialAnimationGroup(self)) # 增加间隔 self._seq_animations[i].addPause(self.duration_pause * i) # 第一段 par_animation1 = QParallelAnimationGroup(self) pa = QPropertyAnimation(self._balls[i], b'pos', self) pa.setEasingCurve(self._ec1) pa.setDuration(self.duration * self._first_flag) pa.setStartValue(QPoint(0, bh)) pa.setEndValue(QPoint(width * 0.4, bh)) par_animation1.addAnimation(pa) pa = QPropertyAnimation(self._balls[i], b'_style', self) pa.setEasingCurve(self._ec1) pa.setDuration(self.duration * self._first_flag) pa.setStartValue(0) pa.setEndValue(255) par_animation1.addAnimation(pa) self._seq_animations[i].addAnimation(par_animation1) # 第二段 pa = QPropertyAnimation(self._balls[i], b'pos', self) pa.setDuration(self.duration * self._second_flag) pa.setStartValue(QPoint(width * 0.4, bh)) pa.setEndValue(QPoint(width * 0.6, bh)) self._seq_animations[i].addAnimation(pa) # 第三段 par_animation2 = QParallelAnimationGroup(self) pa = QPropertyAnimation(self._balls[i], b'pos', self) pa.setEasingCurve(self._ec2) pa.setDuration(self.duration * self._three_flag) pa.setStartValue(QPoint(width * 0.6, bh)) pa.setEndValue(QPoint(width, bh)) par_animation2.addAnimation(pa) pa = QPropertyAnimation(self._balls[i], b'_style', self) pa.setEasingCurve(self._ec2) pa.setDuration(self.duration * self._three_flag) pa.setStartValue(255) pa.setEndValue(0) par_animation2.addAnimation(pa) self._seq_animations[i].addAnimation(par_animation2) # 增加间隔 self._seq_animations[i].addPause(self.duration_pause * (self._ball_numbers - i - 1)) for seq in self._seq_animations: seq.setLoopCount(-1) seq.start(QAbstractAnimation.DeleteWhenStopped)
def rain(pix, node): node.pix.setOriginPt() pos = node.pix.pos() sync = random.randint(17, 31) * 50 y = int(pos.y()) ViewH = common["ViewH"] bottom = y + ViewH + node.pix.height * 2 top = y + node.pix.height * 2 rain1 = QPropertyAnimation(node, b'pos') rain1.setDuration(sync) rain1.setStartValue(pos) rain1.setEndValue(pos + QPointF(0, bottom)) opacity1 = QPropertyAnimation(node, b'opacity') opacity1.setDuration(sync) opacity1.setStartValue(node.pix.opacity()) opacity1.setKeyValueAt(.10, .50) opacity1.setEndValue(0) par1 = QParallelAnimationGroup() par1.addAnimation(rain1) par1.addAnimation(opacity1) rain2 = QPropertyAnimation(node, b'pos') rain2.setDuration(sync) rain2.setStartValue(pos + QPointF(0, -top)) rain2.setEndValue(pos) opacity2 = QPropertyAnimation(node, b'opacity') opacity2.setDuration(sync) opacity2.setStartValue(node.pix.opacity()) opacity2.setEndValue(.85) par2 = QParallelAnimationGroup() par2.addAnimation(rain2) par2.addAnimation(opacity2) rain = QSequentialAnimationGroup() rain.addAnimation(par1) rain.addAnimation(par2) rain.setLoopCount(-1) return rain
def _initAnimations(self): for index in range(5): # 5个小圆 item = CircleItem(self) item.valueChanged.connect(self.update) # 串行动画组 seqAnimation = QSequentialAnimationGroup(self) seqAnimation.setLoopCount(-1) self._items.append((item, seqAnimation)) # 暂停延迟动画 seqAnimation.addAnimation(QPauseAnimation(150 * index, self)) # 加速,并行动画组1 parAnimation1 = QParallelAnimationGroup(self) # 透明度 parAnimation1.addAnimation(QPropertyAnimation( item, b'opacity', self, duration=400, startValue=0, endValue=1.0)) # x坐标 parAnimation1.addAnimation(QPropertyAnimation( item, b'x', self, duration=400, startValue=0, endValue=25.0)) seqAnimation.addAnimation(parAnimation1) ## # 匀速 seqAnimation.addAnimation(QPropertyAnimation( item, b'x', self, duration=2000, startValue=25.0, endValue=75.0)) # 加速,并行动画组2 parAnimation2 = QParallelAnimationGroup(self) # 透明度 parAnimation2.addAnimation(QPropertyAnimation( item, b'opacity', self, duration=400, startValue=1.0, endValue=0)) # x坐标 parAnimation2.addAnimation(QPropertyAnimation( item, b'x', self, duration=400, startValue=75.0, endValue=100.0)) seqAnimation.addAnimation(parAnimation2) ## # 暂停延迟动画 seqAnimation.addAnimation( QPauseAnimation((5 - index - 1) * 150, self)) for _, animation in self._items: animation.start()
def step_ani(self, is_back=False): img_ani = QPropertyAnimation(self.img, b'geometry', self) img_ani.setStartValue(QRect(0, 0, self.img_width, self.img_height)) img_ani.setEndValue( QRect(0, -self.img_height * 2, self.img_width * 2, self.img_height * 2)) img_ani.setDuration(200) img_ani.setEasingCurve(QEasingCurve.OutCurve) title_ani = QPropertyAnimation(self.title, b'pos', self) title_ani.setStartValue(QPoint(self.title_x, self.title_y)) title_ani.setEndValue(QPoint(244, 78)) title_ani.setDuration(200) title_ani.setEasingCurve(QEasingCurve.OutCurve) content1_ani = QPropertyAnimation(self.content1, b'pos', self) content1_ani.setStartValue(QPoint(self.content1_x, self.content1_y)) content1_ani.setEndValue(QPoint(244, 112)) content1_ani.setDuration(200) content1_ani.setEasingCurve(QEasingCurve.OutCurve) content2_ani = QPropertyAnimation(self.content2, b'pos', self) content2_ani.setStartValue(QPoint(self.content2_x, self.content2_y)) content2_ani.setEndValue(QPoint(244, 134)) content2_ani.setDuration(200) content2_ani.setEasingCurve(QEasingCurve.OutCurve) # 动画队列 ani_group = QParallelAnimationGroup(self) ani_group.addAnimation(img_ani) ani_group.addAnimation(title_ani) ani_group.addAnimation(content1_ani) ani_group.addAnimation(content2_ani) if is_back: ani_group.setDirection(ani_group.Backward) ani_group.start() return ani_group
class BubbleLabel(QWidget): BackgroundColor = QColor(195, 195, 195) BorderColor = QColor(150, 150, 150) def __init__(self, *args, **kwargs): text = kwargs.pop("text", "") super(BubbleLabel, self).__init__(*args, **kwargs) # 设置无边框置顶 self.setWindowFlags(Qt.Window | Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.X11BypassWindowManagerHint) # 设置最小宽度和高度 self.setMinimumWidth(200) self.setMinimumHeight(48) self.setAttribute(Qt.WA_TranslucentBackground, True) layout = QVBoxLayout(self) # 左上右下的边距(下方16是因为包括了三角形) layout.setContentsMargins(8, 8, 8, 16) self.label = QLabel(self) layout.addWidget(self.label) self.setText(text) # 获取屏幕高宽 self._desktop = QApplication.instance().desktop() def setText(self, text): self.label.setText(text) def text(self): return self.label.text() def stop(self): self.hide() self.animationGroup.stop() self.close() def show(self): super(BubbleLabel, self).show() # 窗口开始位置 startPos = QPoint( self._desktop.screenGeometry().width() - self.width() - 100, self._desktop.availableGeometry().height() - self.height()) endPos = QPoint( self._desktop.screenGeometry().width() - self.width() - 100, self._desktop.availableGeometry().height() - self.height() * 3 - 5) print(startPos, endPos) self.move(startPos) # 初始化动画 self.initAnimation(startPos, endPos) def initAnimation(self, startPos, endPos): # 透明度动画 opacityAnimation = QPropertyAnimation(self, b"opacity") opacityAnimation.setStartValue(1.0) opacityAnimation.setEndValue(0.0) # 设置动画曲线 opacityAnimation.setEasingCurve(QEasingCurve.InQuad) opacityAnimation.setDuration(4000) # 在4秒的时间内完成 # 往上移动动画 moveAnimation = QPropertyAnimation(self, b"pos") moveAnimation.setStartValue(startPos) moveAnimation.setEndValue(endPos) moveAnimation.setEasingCurve(QEasingCurve.InQuad) moveAnimation.setDuration(5000) # 在5秒的时间内完成 # 并行动画组(目的是让上面的两个动画同时进行) self.animationGroup = QParallelAnimationGroup(self) self.animationGroup.addAnimation(opacityAnimation) self.animationGroup.addAnimation(moveAnimation) self.animationGroup.finished.connect(self.close) # 动画结束时关闭窗口 self.animationGroup.start() def paintEvent(self, event): super(BubbleLabel, self).paintEvent(event) painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) # 抗锯齿 rectPath = QPainterPath() # 圆角矩形 triPath = QPainterPath() # 底部三角形 height = self.height() - 8 # 往上偏移8 rectPath.addRoundedRect(QRectF(0, 0, self.width(), height), 5, 5) x = self.width() / 5 * 4 triPath.moveTo(x, height) # 移动到底部横线4/5处 # 画三角形 triPath.lineTo(x + 6, height + 8) triPath.lineTo(x + 12, height) rectPath.addPath(triPath) # 添加三角形到之前的矩形上 # 边框画笔 painter.setPen( QPen(self.BorderColor, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) # 背景画刷 painter.setBrush(self.BackgroundColor) # 绘制形状 painter.drawPath(rectPath) # 三角形底边绘制一条线保证颜色与背景一样 painter.setPen( QPen(self.BackgroundColor, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) painter.drawLine(x, height, x + 12, height) def windowOpacity(self): return super(BubbleLabel, self).windowOpacity() def setWindowOpacity(self, opacity): super(BubbleLabel, self).setWindowOpacity(opacity) # 由于opacity属性不在QWidget中需要重新定义一个 opacity = pyqtProperty(float, windowOpacity, setWindowOpacity)
class Expander(QWidget): expanded = pyqtSignal(object) clicked = pyqtSignal() def __init__(self, header, normal_icon="", hovered_icon="", selected_icon=""): QWidget.__init__(self) self.is_expanded = False self.text = header self.label_normal_color = self.palette().link().color().name() self.label_hovered_color = self.palette().highlight().color().name() self.label_selected_color = self.palette().highlightedText().color().name() self.normal_color = self.palette().base().color().name() self.selected_color = self.palette().highlight().color() self.hovered_color = self.palette().alternateBase().color() self.setCursor(Qt.PointingHandCursor) if normal_icon: self.normal_icon = QImage(normal_icon) if hovered_icon: self.hovered_icon = QImage(hovered_icon) if selected_icon: self.selected_icon = QImage(selected_icon) self.setAttribute(Qt.WA_Hover, True) self.color = self.normal_color self.setAutoFillBackground(True) vbox = QVBoxLayout() hbox = QHBoxLayout() self.icon = QLabel() self.icon.setPixmap(QPixmap.fromImage(self.normal_icon)) self.hyper = QLabel() self.hyper.setText("<a style=\"color: " + self.label_normal_color + " text-decoration: none\" href=\"#\">" + self.text + "</a>") hbox.addWidget(self.icon) hbox.addSpacing(5) hbox.addWidget(self.hyper) hbox.addStretch() hbox.setContentsMargins(8, 8, 8, 8) vbox.addLayout(hbox) self.content = QWidget() self.content.setStyleSheet("background-color: " + self.palette().base().color().name()) self.content.setMaximumHeight(0) vbox.addWidget(self.content) vbox.setContentsMargins(0, 0, 0, 0) self.setLayout(vbox) self.hyper.linkActivated.connect(self.buttonClicked) self.anim = QParallelAnimationGroup() self.height_anim = QPropertyAnimation(self.content, "maximumHeight".encode("utf-8")) self.color_anim = QPropertyAnimation(self, "color".encode("utf-8")) self.height_anim.setDuration(200) self.color_anim.setDuration(200) self.anim.addAnimation(self.height_anim) self.anim.addAnimation(self.color_anim) def setExpanded(self, value): if value == self.is_expanded: return if value: self.is_expanded = True pal = self.palette() pal.setColor(QPalette.Background, self.selected_color) self.setPalette(pal) self.icon.setPixmap(QPixmap.fromImage(self.selected_icon)) self.hyper.setText("<a style=\"color: " + self.label_selected_color + "; text-decoration: none;\" href=\"#\">" + self.text + "</a>") else: self.is_expanded = False pal = self.palette() pal.setColor(QPalette.Background, QColor(self.normal_color)) self.setPalette(pal) self.icon.setPixmap(QPixmap.fromImage(self.normal_icon)) self.hyper.setText("<a style=\"color: " + self.label_normal_color + "; text-decoration: none;\" href=\"#\">" + self.text + "</a>") if self.is_expanded: self.expandContent() else: self.collapseContent() self.expanded.emit(self.is_expanded) def addLayout(self, layout): self.content.setLayout(layout) @pyqtProperty('QColor') def color(self): return Qt.black @color.setter def color(self, color): pal = self.palette() pal.setColor(QPalette.Background, QColor(color)) self.setPalette(pal) def mouseReleaseEvent(self, me): self.setExpanded(not self.is_expanded) if self.is_expanded: self.clicked.emit() def expandContent(self): if self.content.layout(): self.height_anim.setEndValue(self.content.layout().sizeHint().height()) else: self.height_anim.setEndValue(0) self.height_anim.setStartValue(0) self.color_anim.setStartValue(self.normal_color) self.color_anim.setEndValue(self.selected_color) self.anim.start() def collapseContent(self): if self.content.layout(): self.height_anim.setStartValue(self.content.layout().sizeHint().height()) else: self.height_anim.setStartValue(0) self.height_anim.setEndValue(0) self.color_anim.setStartValue(self.selected_color) self.color_anim.setEndValue(self.normal_color) self.anim.start() def buttonClicked(self): self.setExpanded(not self.is_expanded) if self.is_expanded: self.clicked.emit() def enterEvent(self, event): if not self.is_expanded: pal = self.palette() pal.setColor(QPalette.Background, QColor(self.hovered_color)) self.setPalette(pal) self.icon.setPixmap(QPixmap.fromImage(self.hovered_icon)) self.hyper.setText("<a style=\"color: " + self.label_hovered_color + "; text-decoration: none;\" href=\"#\">" + self.text + "</a>") QWidget.enterEvent(self, event) def leaveEvent(self, event): if not self.is_expanded: pal = self.palette() pal.setColor(QPalette.Background, QColor(self.normal_color)) self.setPalette(pal) self.icon.setPixmap(QPixmap.fromImage(self.normal_icon)) self.hyper.setText("<a style=\"color: " + self.label_normal_color + "; text-decoration: none;\" href=\"#\">" + self.text + "</a>") QWidget.leaveEvent(self, event)
class Preferences(QDialog): # Signal to warn that the window is closed settingsClosed = pyqtSignal() def __init__(self, parent=None): super(Preferences, self).__init__(parent) # Main container # This contains a grid main_box = QVBoxLayout(self) main_box.setContentsMargins(200, 50, 200, 100) # The grid contains two containers # left container and right container grid = QGridLayout() # Left Container left_container = QVBoxLayout() left_container.setContentsMargins(0, 0, 0, 0) # General group_gral = QGroupBox(self.tr("General")) box_gral = QVBoxLayout(group_gral) # Updates btn_updates = QPushButton(self.tr("Check for updates")) box_gral.addWidget(btn_updates) # Language group_language = QGroupBox(self.tr("Language")) box = QVBoxLayout(group_language) # Find .qm files in language path available_langs = file_manager.get_files_from_folder( settings.LANGUAGE_PATH) languages = ["English"] + available_langs self._combo_lang = QComboBox() box.addWidget(self._combo_lang) self._combo_lang.addItems(languages) self._combo_lang.currentIndexChanged[int].connect( self._change_lang) if PSetting.LANGUAGE: self._combo_lang.setCurrentText(PSetting.LANGUAGE) box.addWidget(QLabel(self.tr("(Requires restart)"))) # Add widgets left_container.addWidget(group_gral) left_container.addWidget(group_language) left_container.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) # Right Container right_container = QVBoxLayout() right_container.setContentsMargins(0, 0, 0, 0) # Editor editor_group = QGroupBox(self.tr("Editor Configurations")) box_editor = QHBoxLayout(editor_group) # Current line self._highlight_current_line = QCheckBox( self.tr("Highlight Current Line")) self._highlight_current_line.setChecked( PSetting.HIGHLIGHT_CURRENT_LINE) self._highlight_current_line.stateChanged[int].connect( self.__current_line_value_changed) box_editor.addWidget(self._highlight_current_line) # Matching paren self._matching_paren = QCheckBox(self.tr("Matching Parenthesis")) self._matching_paren.setChecked( PSetting.MATCHING_PARENTHESIS) self._matching_paren.stateChanged[int].connect( self.__set_enabled_matching_parenthesis) box_editor.addWidget(self._matching_paren) # Font group font_group = QGroupBox(self.tr("Font")) font_grid = QGridLayout(font_group) font_grid.addWidget(QLabel(self.tr("Family")), 0, 0) self._combo_font = QFontComboBox() self._combo_font.setCurrentFont(PSetting.FONT) font_grid.addWidget(self._combo_font, 0, 1) font_grid.addWidget(QLabel(self.tr("Point Size")), 1, 0) self._combo_font_size = QComboBox() fdb = QFontDatabase() combo_sizes = fdb.pointSizes(PSetting.FONT.family()) current_size_index = combo_sizes.index( PSetting.FONT.pointSize()) self._combo_font_size.addItems([str(f) for f in combo_sizes]) self._combo_font_size.setCurrentIndex(current_size_index) font_grid.addWidget(self._combo_font_size, 1, 1) right_container.addWidget(editor_group) right_container.addWidget(font_group) right_container.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) # Add widgets grid.addLayout(left_container, 0, 0) grid.addLayout(right_container, 0, 1) main_box.addLayout(grid) # Button close and reset hbox = QHBoxLayout() hbox.setSpacing(20) hbox.addItem(QSpacerItem(1, 0, QSizePolicy.Expanding)) btn_cancel = QPushButton(self.tr("Back")) hbox.addWidget(btn_cancel) btn_reset = QPushButton(self.tr("Reset Configurations")) hbox.addWidget(btn_reset) main_box.addLayout(hbox) # Overlay self.overlay = overlay_widget.OverlayWidget(self) self.overlay.hide() # Effect and animations self.effect = QGraphicsOpacityEffect() self.setGraphicsEffect(self.effect) duration, x = 180, 150 # Animation duration # Animation start # Opacity animation self.opacity_animation_s = QPropertyAnimation(self.effect, b"opacity") self.opacity_animation_s.setDuration(duration) self.opacity_animation_s.setStartValue(0.0) self.opacity_animation_s.setEndValue(1.0) # X animation self.x_animation_s = QPropertyAnimation(self, b"geometry") self.x_animation_s.setDuration(duration) self.x_animation_s.setStartValue(QRect(x, 0, parent.width(), parent.height())) self.x_animation_s.setEndValue(QRect(0, 0, parent.width(), parent.height())) # Animation end # Opacity animation self.opacity_animation_e = QPropertyAnimation(self.effect, b"opacity") self.opacity_animation_e.setDuration(duration) self.opacity_animation_e.setStartValue(1.0) self.opacity_animation_e.setEndValue(0.0) # X animation self.x_animation_e = QPropertyAnimation(self, b"geometry") self.x_animation_e.setDuration(duration) self.x_animation_e.setStartValue(QRect(0, 0, parent.width(), parent.height())) self.x_animation_e.setEndValue(QRect(-x, 0, parent.width(), parent.height())) # Group animation start self.group_animation_s = QParallelAnimationGroup() self.group_animation_s.addAnimation(self.opacity_animation_s) self.group_animation_s.addAnimation(self.x_animation_s) # Group animation end self.group_animation_e = QParallelAnimationGroup() self.group_animation_e.addAnimation(self.opacity_animation_e) self.group_animation_e.addAnimation(self.x_animation_e) # Connections self.group_animation_e.finished.connect( self._on_group_animation_finished) btn_cancel.clicked.connect(self.close) btn_reset.clicked.connect(self._reset_settings) btn_updates.clicked.connect(self._check_for_updates) # self.thread.finished.connect(self._on_thread_finished) self._combo_font.currentFontChanged.connect( self._change_font) self._combo_font_size.currentTextChanged.connect( self._change_font_size) def __current_line_value_changed(self, value): qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) qs.setValue('highlight_current_line', value) PSetting.HIGHLIGHT_CURRENT_LINE = value def __set_enabled_matching_parenthesis(self, value): qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) qs.setValue("matching_parenthesis", value) PSetting.MATCHING_PARENTHESIS = value def _change_font(self, font): # FIXME: un quilombo esto central = Pireal.get_service("central") mcontainer = central.get_active_db() if mcontainer is not None: query_widget = mcontainer.query_container.currentWidget() if query_widget is not None: weditor = query_widget.get_editor() if weditor is not None: qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) weditor.set_font(font) qs.setValue("font", font) def _change_font_size(self, size): # FIXME: un quilombo esto font = self._combo_font.currentFont() font.setPointSize(int(size)) central = Pireal.get_service("central") mcontainer = central.get_active_db() if mcontainer is not None: query_widget = mcontainer.query_container.currentWidget() if query_widget is not None: weditor = query_widget.get_editor() if weditor is not None: qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) weditor.set_font(font) qs.setValue("font", font) def showEvent(self, event): super(Preferences, self).showEvent(event) self.group_animation_s.start() def resizeEvent(self, event): self.overlay.resize(self.size()) event.accept() def done(self, result): self.res = result self.group_animation_e.start() def _on_group_animation_finished(self): super(Preferences, self).done(self.res) self.settingsClosed.emit() def _check_for_updates(self): # Thread self._thread = QThread() self._updater = updater.Updater() self._updater.moveToThread(self._thread) self._thread.started.connect(self._updater.check_updates) self._updater.finished.connect(self.__on_thread_update_finished) # Show overlay widget self.overlay.show() # Start thread self._thread.start() def __on_thread_update_finished(self): # Hide overlay widget self.overlay.hide() self._thread.quit() msg = QMessageBox(self) if not self._updater.error: if self._updater.version: version = self._updater.version msg.setWindowTitle(self.tr("New version available!")) msg.setText(self.tr("Check the web site to " "download <b>Pireal {}</b>".format( version))) download_btn = msg.addButton(self.tr("Download!"), QMessageBox.YesRole) msg.addButton(self.tr("Cancel"), QMessageBox.RejectRole) msg.exec_() r = msg.clickedButton() if r == download_btn: webbrowser.open_new( "http://centaurialpha.github.io/pireal") else: msg.setWindowTitle(self.tr("Information")) msg.setText(self.tr("Last version installed")) msg.addButton(self.tr("Ok"), QMessageBox.AcceptRole) msg.exec_() else: msg.critical(self, self.tr("Error"), self.tr("Connection error")) self._thread.deleteLater() self._updater.deleteLater() def _reset_settings(self): """ Remove all settings """ msg = QMessageBox(self) msg.setWindowTitle(self.tr("Reset Settings")) msg.setText(self.tr("Are you sure you want to clear all settings?")) msg.setIcon(QMessageBox.Question) msg.addButton(self.tr("No"), QMessageBox.NoRole) yes_btn = msg.addButton(self.tr("Yes"), QMessageBox.YesRole) msg.exec_() r = msg.clickedButton() if r == yes_btn: QSettings(settings.SETTINGS_PATH, QSettings.IniFormat).clear() self.close() def _change_lang(self, index): lang = self._combo_lang.itemText(index) qs = QSettings(settings.SETTINGS_PATH, QSettings.IniFormat) qs.setValue('language', lang)
class StartTuto(QDialog): def __init__(self): super(StartTuto, self).__init__() QDialog.__init__(self) self.Next = 0 self.setWindowFlags(Qt.FramelessWindowHint) self.setAttribute(Qt.WA_TranslucentBackground, True) self.setWindowTitle("لنتعلم معا") self.font = QFont('abdo salem') self.font.setPointSize(20) self.font.setBold(True) self.ex = QPushButton(self) self.ex.setFont(self.font) self.ex.setGeometry(400 + 80, 450, 150, 50) self.ex.setText('خروج') self.ex.setStyleSheet( "QPushButton:hover{background-color:rgb(241, 90, 36);border:5px solid rgb(0, 97, 157);}\n" "QPushButton{color:white;background-color: rgb(50, 50, 50);border:5px solid rgb(255, 255, 255);}QPushButton:pressed{color:white;background-color: rgb(50, 50, 50);border:5px solid rgb(255, 255, 255);}\n" "") self.ex.clicked.connect(self.Exit) self.hm = QPushButton(self) self.hm.setFont(self.font) self.hm.setGeometry(200 + 80, 450, 150, 50) self.hm.setText('الرئيسية') self.hm.setStyleSheet( "QPushButton:hover{background-color:rgb(241, 90, 36);border:5px solid rgb(0, 97, 157);}\n" "QPushButton{color:white;background-color: rgb(50, 50, 50);border:5px solid rgb(255, 255, 255);}QPushButton:pressed{color:white;background-color: rgb(50, 50, 50);border:5px solid rgb(255, 255, 255);}\n" "") self.hm.clicked.connect(self.home) self.resize(925, 500) self.Ind = [ 'data', 'if', 'for', 'while', 'def', 'class', 'oop', 'exp', 'prj2', 'prj3', 'st' ] self.names = { 'home': [ 'المتغير و\n أنواع البيانات', 'الجملة الشرطية\nIF', 'حلقة التقسيم\nFOR', 'حلقة مادام\nWHILE', 'الدالة\nDEF', 'الفئة\nClass', 'البرمجة كائنية\nالتوجه OOP', 'العبارات', 'المشروع الاول\nRANGE', 'المشروع الثاني\nStr', 'ابدء' ], 'data': [ 'المتغير', 'الأحرف ', 'الأرقام ', 'القائمة', 'القاموس', 'الخطأ\nالصحيح', 'دوال \nانواع البيانات', 'الأمثلة' ], 'if': [ 'الجملة الشرطية\nIF', 'ادوات المقارنة', 'استعمالات\n ELIF و ELSE' ], 'for': ['فكرة \nFOR', 'استعمالات \nFOR', 'امثلة'], 'while': ['فكرة \nWHILE', 'استعمالات \nWHILE', 'امثلة'], 'def': [ 'دالة بسيطة', 'arg\nدالة مع', '*arg\nدالة مع', 'دالة مع\n**kwargs' ], 'class': ['فئة بسيطة\n مع متغير', 'فئة بسيطة\n مع دالة'], 'oop': ['البناء\n__init__', 'خاصية الاراثة', 'دالة\nsuper()'], 'exp': ['عبارة\nreturn', 'عبارة\nassert', 'عبارة\nyield'], 'prj2': ['المشروع'], 'prj3': ['محاكات\nCount', 'محاكات\nFind', 'تطوير\nFind'], 'st': 'exit' } self.Home() def Home(self): self.items = [] for i in range(len(self.names['home'])): item = QPushButton(self) item.setText(self.names['home'][i]) item.setGeometry(395, 350, 120, 80) item.setStyleSheet( "QPushButton:hover{background-color:rgb(241, 90, 36);border:5px solid rgb(0, 97, 157);}\n" "QPushButton{color:white;background-color: rgb(50, 50, 50);border:5px solid rgb(255, 255, 255);}QPushButton:pressed{color:white;background-color: rgb(50, 50, 50);border:5px solid rgb(255, 255, 255);}\n" "") self.font.setPointSize(15) item.setFont(self.font) self.items.append(item) exec("""item.clicked.connect(partial(self.IND,i=%i))""" % (i)) self.rootState = QState() self.tiledState = QState(self.rootState) self.centeredState = QState(self.rootState) for i, item in enumerate(self.items): self.tiledState.assignProperty( item, 'pos', QPointF(((i % 6) * 5.3) * 30, ((i // 6) * 5.3) * 30)) self.centeredState.assignProperty(item, 'pos', QPointF()) self.states = QStateMachine() self.states.addState(self.rootState) self.states.setInitialState(self.rootState) self.rootState.setInitialState(self.centeredState) self.group = QParallelAnimationGroup() for i, item in enumerate(self.items): anim = QPropertyAnimation(item, b'pos') anim.setStartValue(QPoint(400, 300)) anim.setDuration(750 + i * 25) anim.setEasingCurve(QEasingCurve.InOutBack) self.group.addAnimation(anim) for u in self.items: trans = self.rootState.addTransition(u.clicked, self.tiledState) trans.addAnimation(self.group) self.states.start() def Exit(self): open('Files/choice', 'w').write('exit') self.close() def home(self): open('Files/choice', 'w').write('home') self.close() def IND(self, i): open('Files/choice', 'a').write(str(i)) if self.Next != 2: self.Next += 1 if self.names[self.Ind[i]] != 'exit': a = self.names[self.Ind[i]] k = 0 for i in self.items[0:len(a)]: i.setText(a[k]) k += 1 for i in self.items[len(a):]: i.hide() else: self.items[-1].hide() else: self.close()
view.setBackgroundBrush(QBrush(bgPix)) view.setCacheMode(QGraphicsView.CacheBackground) view.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) view.show() states = QStateMachine() states.addState(rootState) states.setInitialState(rootState) rootState.setInitialState(centeredState) group = QParallelAnimationGroup() for i, item in enumerate(items): anim = QPropertyAnimation(item, b'pos') anim.setDuration(750 + i * 25) anim.setEasingCurve(QEasingCurve.InOutBack) group.addAnimation(anim) trans = rootState.addTransition(ellipseButton.pressed, ellipseState) trans.addAnimation(group) trans = rootState.addTransition(figure8Button.pressed, figure8State) trans.addAnimation(group) trans = rootState.addTransition(randomButton.pressed, randomState) trans.addAnimation(group) trans = rootState.addTransition(tiledButton.pressed, tiledState) trans.addAnimation(group) trans = rootState.addTransition(centeredButton.pressed, centeredState) trans.addAnimation(group)
def parallel_animation(self, *animations): group = QParallelAnimationGroup() for anim in animations: group.addAnimation(anim) return group
class SlidingStackedWidget(QStackedWidget): LEFT2RIGHT, RIGHT2LEFT, TOP2BOTTOM, BOTTOM2TOP, AUTOMATIC = range(5) def __init__(self, *args, **kwargs): super(SlidingStackedWidget, self).__init__(*args, **kwargs) self._pnow = QPoint(0, 0) # 动画速度 self._speed = 500 # 当前索引 self._now = 0 # 自动模式的当前索引 self._current = 0 # 下一个索引 self._next = 0 # 是否激活 self._active = 0 # 动画方向(默认是横向) self._orientation = Qt.Horizontal # 动画曲线类型 self._easing = QEasingCurve.Linear # 初始化动画 self._initAnimation() def setSpeed(self, speed=500): """设置动画速度 :param speed: 速度值,默认值为500 :type speed: int """ self._speed = speed @pyqtProperty(int, fset=setSpeed) def speed(self): return self._speed def setOrientation(self, orientation=Qt.Horizontal): """设置动画的方向(横向和纵向) :param orientation: 方向(Qt.Horizontal或Qt.Vertical) :type orientation: http://doc.qt.io/qt-5/qt.html#Orientation-enum """ self._orientation = orientation @pyqtProperty(int, fset=setOrientation) def orientation(self): return self._orientation def setEasing(self, easing=QEasingCurve.OutBack): """设置动画的曲线类型 :param easing: 默认为QEasingCurve.OutBack :type easing: http://doc.qt.io/qt-5/qeasingcurve.html#Type-enum """ self._easing = easing @pyqtProperty(int, fset=setEasing) def easing(self): return self._easing def slideInNext(self): """滑动到下一页""" now = self.currentIndex() if now < self.count() - 1: self.slideInIdx(now + 1) self._current = now + 1 def slideInPrev(self): """滑动到上一页""" now = self.currentIndex() if now > 0: self.slideInIdx(now - 1) self._current = now - 1 def slideInIdx(self, idx, direction=4): """滑动到指定序号 :param idx: 序号 :type idx: int :param direction: 方向,默认是自动AUTOMATIC=4 :type direction: int """ if idx > self.count() - 1: direction = self.TOP2BOTTOM if self._orientation == Qt.Vertical else self.RIGHT2LEFT idx = idx % self.count() elif idx < 0: direction = self.BOTTOM2TOP if self._orientation == Qt.Vertical else self.LEFT2RIGHT idx = (idx + self.count()) % self.count() self.slideInWgt(self.widget(idx), direction) def slideInWgt(self, widget, direction): """滑动到指定的widget :param widget: QWidget, QLabel, etc... :type widget: QWidget Base Class :param direction: 方向 :type direction: int """ if self._active: return self._active = 1 _now = self.currentIndex() _next = self.indexOf(widget) if _now == _next: self._active = 0 return w_now = self.widget(_now) w_next = self.widget(_next) # 自动判断方向 if _now < _next: directionhint = self.TOP2BOTTOM if self._orientation == Qt.Vertical else self.RIGHT2LEFT else: directionhint = self.BOTTOM2TOP if self._orientation == Qt.Vertical else self.LEFT2RIGHT if direction == self.AUTOMATIC: direction = directionhint # 计算偏移量 offsetX = self.frameRect().width() offsetY = self.frameRect().height() w_next.setGeometry(0, 0, offsetX, offsetY) if direction == self.BOTTOM2TOP: offsetX = 0 offsetY = -offsetY elif direction == self.TOP2BOTTOM: offsetX = 0 elif direction == self.RIGHT2LEFT: offsetX = -offsetX offsetY = 0 elif direction == self.LEFT2RIGHT: offsetY = 0 # 重新定位显示区域外部/旁边的下一个窗口小部件 pnext = w_next.pos() pnow = w_now.pos() self._pnow = pnow # 移动到指定位置并显示 w_next.move(pnext.x() - offsetX, pnext.y() - offsetY) w_next.show() w_next.raise_() self._animnow.setTargetObject(w_now) self._animnow.setDuration(self._speed) self._animnow.setEasingCurve(self._easing) self._animnow.setStartValue(QPoint(pnow.x(), pnow.y())) self._animnow.setEndValue( QPoint(offsetX + pnow.x(), offsetY + pnow.y())) self._animnext.setTargetObject(w_next) self._animnext.setDuration(self._speed) self._animnext.setEasingCurve(self._easing) self._animnext.setStartValue( QPoint(-offsetX + pnext.x(), offsetY + pnext.y())) self._animnext.setEndValue(QPoint(pnext.x(), pnext.y())) self._next = _next self._now = _now self._active = 1 self._animgroup.start() def _initAnimation(self): """初始化当前页和下一页的动画变量""" # 当前页的动画 self._animnow = QPropertyAnimation(self, propertyName=b'pos', duration=self._speed, easingCurve=self._easing) # 下一页的动画 self._animnext = QPropertyAnimation(self, propertyName=b'pos', duration=self._speed, easingCurve=self._easing) # 并行动画组 self._animgroup = QParallelAnimationGroup( self, finished=self.animationDoneSlot) self._animgroup.addAnimation(self._animnow) self._animgroup.addAnimation(self._animnext) def setCurrentIndex(self, index): # 覆盖该方法实现的动画切换 # super(SlidingStackedWidget, self).setCurrentIndex(index) # 坚决不能调用上面的函数,否则动画失效 self.slideInIdx(index) def setCurrentWidget(self, widget): # 覆盖该方法实现的动画切换 super(SlidingStackedWidget, self).setCurrentWidget(widget) # 坚决不能调用上面的函数,否则动画失效 self.setCurrentIndex(self.indexOf(widget)) def animationDoneSlot(self): """动画结束处理函数""" # 由于重写了setCurrentIndex方法所以这里要用父类本身的方法 # self.setCurrentIndex(self._next) QStackedWidget.setCurrentIndex(self, self._next) w = self.widget(self._now) w.hide() w.move(self._pnow) self._active = 0 def autoStop(self): """停止自动播放""" if hasattr(self, '_autoTimer'): self._autoTimer.stop() def autoStart(self, msec=3000): """自动轮播 :param time: 时间, 默认3000, 3秒 """ if not hasattr(self, '_autoTimer'): self._autoTimer = QTimer(self, timeout=self._autoStart) self._autoTimer.stop() self._autoTimer.start(msec) def _autoStart(self): if self._current == self.count(): self._current = 0 self._current += 1 self.setCurrentIndex(self._current)
class LifeCycle(object): def __init__(self, stickMan, keyReceiver): self.m_stickMan = stickMan self.m_keyReceiver = keyReceiver # Create animation group to be used for all transitions. self.m_animationGroup = QParallelAnimationGroup() stickManNodeCount = self.m_stickMan.nodeCount() self._pas = [] for i in range(stickManNodeCount): pa = QPropertyAnimation(self.m_stickMan.node(i), 'pos') self._pas.append(pa) self.m_animationGroup.addAnimation(pa) # Set up intial state graph. self.m_machine = QStateMachine() self.m_machine.addDefaultAnimation(self.m_animationGroup) self.m_alive = QState(self.m_machine) self.m_alive.setObjectName('alive') # Make it blink when lightning strikes before entering dead animation. lightningBlink = QState(self.m_machine) lightningBlink.assignProperty(self.m_stickMan.scene(), 'backgroundBrush', Qt.white) lightningBlink.assignProperty(self.m_stickMan, 'penColor', Qt.black) lightningBlink.assignProperty(self.m_stickMan, 'fillColor', Qt.white) lightningBlink.assignProperty(self.m_stickMan, 'isDead', True) timer = QTimer(lightningBlink) timer.setSingleShot(True) timer.setInterval(100) lightningBlink.entered.connect(timer.start) lightningBlink.exited.connect(timer.stop) self.m_dead = QState(self.m_machine) self.m_dead.assignProperty(self.m_stickMan.scene(), 'backgroundBrush', Qt.black) self.m_dead.assignProperty(self.m_stickMan, 'penColor', Qt.white) self.m_dead.assignProperty(self.m_stickMan, 'fillColor', Qt.black) self.m_dead.setObjectName('dead') # Idle state (sets no properties). self.m_idle = QState(self.m_alive) self.m_idle.setObjectName('idle') self.m_alive.setInitialState(self.m_idle) # Lightning strikes at random. self.m_alive.addTransition(LightningStrikesTransition(lightningBlink)) lightningBlink.addTransition(timer.timeout, self.m_dead) self.m_machine.setInitialState(self.m_alive) def setDeathAnimation(self, fileName): deathAnimation = self.makeState(self.m_dead, fileName) self.m_dead.setInitialState(deathAnimation) def start(self): self.m_machine.start() def addActivity(self, fileName, key): state = self.makeState(self.m_alive, fileName) self.m_alive.addTransition( KeyPressTransition(self.m_keyReceiver, key, state)) def makeState(self, parentState, animationFileName): topLevel = QState(parentState) animation = Animation() file = QFile(animationFileName) if file.open(QIODevice.ReadOnly): animation.load(file) frameCount = animation.totalFrames() previousState = None for i in range(frameCount): animation.setCurrentFrame(i) frameState = QState(topLevel) nodeCount = animation.nodeCount() for j in range(nodeCount): frameState.assignProperty(self.m_stickMan.node(j), 'pos', animation.nodePos(j)) frameState.setObjectName('frame %d' % i) if previousState is None: topLevel.setInitialState(frameState) else: previousState.addTransition(previousState.propertiesAssigned, frameState) previousState = frameState previousState.addTransition(previousState.propertiesAssigned, topLevel.initialState()) return topLevel
class PlaylistCardInterface(QWidget): """ 播放列表卡界面 """ playSig = pyqtSignal(list) nextToPlaySig = pyqtSignal(list) deletePlaylistSig = pyqtSignal(dict) renamePlaylistSig = pyqtSignal(dict, dict) selectionModeStateChanged = pyqtSignal(bool) def __init__(self, playlists: list, parent=None): super().__init__(parent) self.columnNum = 1 self.sortMode = 'modifiedTime' self.playlists = deepcopy(playlists) self.playlistCard_list = [] self.playlistCardDict_list = [] # type:list[dict] self.checkedPlaylistCard_list = [] self.isInSelectionMode = False self.isAllPlaylistCardChecked = False # 创建小部件 self.__createWidgets() # 初始化 self.__initWidget() def __createWidgets(self): """ 创建小部件 """ # 创建磨砂背景 self.scrollArea = ScrollArea(self) self.scrollWidget = QWidget(self) self.gridLayout = GridLayout() self.blurBackground = BlurBackground(self.scrollWidget) # 创建播放列表卡 self.__createPlaylistCards() # 创建白色遮罩 self.whiteMask = QWidget(self) self.sortModeLabel = QLabel('排序依据:', self) self.playlistLabel = QLabel('播放列表', self) self.sortModeButton = QPushButton('修改日期', self) self.createPlaylistButton = ThreeStateButton( { 'normal': r'resource\images\playlist_card_interface\newPlaylist_normal.png', 'hover': r'resource\images\playlist_card_interface\newPlaylist_hover.png', 'pressed': r'resource\images\playlist_card_interface\newPlaylist_pressed.png' }, self, (129, 19)) # 创建导航标签 self.guideLabel = QLabel('这里没有可显示的内容。请尝试其他筛选器。', self) self.guideLabel.setStyleSheet( "color: black; font: 25px 'Microsoft YaHei'") self.guideLabel.resize(500, 26) self.guideLabel.move(44, 196) # 创建排序菜单 self.sortModeMenu = AeroMenu(parent=self) self.sortByModifiedTimeAct = QAction( '修改时间', self, triggered=lambda: self.__sortPlaylist('modifiedTime')) self.sortByAToZAct = QAction( 'A到Z', self, triggered=lambda: self.__sortPlaylist('AToZ')) self.sortAct_list = [self.sortByModifiedTimeAct, self.sortByAToZAct] # 创建选择状态栏 self.selectionModeBar = SelectionModeBar(self) # 记录当前的排序方式 self.currentSortAct = self.sortByModifiedTimeAct def __createPlaylistCards(self): """ 创建播放列表卡 """ # 创建并行动画组 self.hideCheckBoxAniGroup = QParallelAnimationGroup(self) self.hideCheckBoxAni_list = [] for playlist in self.playlists: self.__createOnePlaylistCard(playlist) def __createOnePlaylistCard(self, playlist: dict): """ 创建一个播放列表卡 """ playlistCard = PlaylistCard(playlist, self) self.playlistCard_list.append(playlistCard) self.playlistCardDict_list.append({ 'playlistCard': playlistCard, 'playlist': playlist }) # 创建动画 hideCheckBoxAni = QPropertyAnimation( playlistCard.checkBoxOpacityEffect, b'opacity') self.hideCheckBoxAniGroup.addAnimation(hideCheckBoxAni) self.hideCheckBoxAni_list.append(hideCheckBoxAni) # 信号连接到槽 playlistCard.showBlurBackgroundSig.connect(self.__showBlurBackground) playlistCard.hideBlurBackgroundSig.connect(self.blurBackground.hide) playlistCard.renamePlaylistSig.connect(self.__showRenamePlaylistPanel) playlistCard.deleteCardSig.connect(self.__showDeleteCardPanel) playlistCard.playSig.connect(self.playSig) playlistCard.checkedStateChanged.connect( self.__playlistCardCheckedStateChangedSlot) playlistCard.nextToPlaySig.connect(self.nextToPlaySig) def __initWidget(self): """ 初始化小部件 """ # 隐藏小部件 self.blurBackground.hide() self.selectionModeBar.hide() self.guideLabel.setHidden(bool(self.playlistCard_list)) # 初始化滚动条 self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 将动作添加到菜单中 self.sortModeMenu.addActions(self.sortAct_list) # 分配ID和属性 self.scrollWidget.setObjectName('scrollWidget') self.playlistLabel.setObjectName('playlistLabel') self.sortModeLabel.setObjectName('sortModeLabel') self.sortModeButton.setObjectName('sortModeButton') self.sortModeMenu.setObjectName('sortModeMenu') self.sortModeMenu.setProperty('modeNumber', '2') self.__setQss() self.__initLayout() self.resize(1270, 760) self.__connectSignalToSlot() def __initLayout(self): """ 初始化布局 """ self.scrollArea.move(0, 0) self.playlistLabel.move(30, 54) self.sortModeLabel.move(190, 135) self.sortModeButton.move(264, 130) self.createPlaylistButton.move(30, 135) self.selectionModeBar.move( 0, self.height() - self.selectionModeBar.height()) # 设置布局的间距和外边距 self.gridLayout.setVerticalSpacing(20) self.gridLayout.setHorizontalSpacing(10) self.gridLayout.setContentsMargins(15, 175, 15, 120) self.scrollArea.setWidget(self.scrollWidget) self.scrollWidget.setLayout(self.gridLayout) # 如果没有播放列表就直接返回 if not self.playlistCard_list: return # 按照修改日期排序播放列表 self.__sortPlaylist('modifiedTime') def __setQss(self): """ 设置层叠样式 """ with open(r'resource\css\playlistCardInterface.qss', encoding='utf-8') as f: self.setStyleSheet(f.read()) def resizeEvent(self, e): """ 调整小部件尺寸和位置 """ super().resizeEvent(e) self.scrollArea.resize(self.size()) self.whiteMask.resize(self.width() - 15, 175) self.scrollArea.verticalScrollBar().resize(4, self.height() - 116) self.scrollArea.verticalScrollBar().move(-1, 40) self.selectionModeBar.resize(self.width(), self.selectionModeBar.height()) if self.width() < 641 and self.columnNum != 1: self.__setColumnNum(1) elif 641 <= self.width() < 954 and self.columnNum != 2: self.__setColumnNum(2) elif 954 <= self.width() < 1267 and self.columnNum != 3: self.__setColumnNum(3) elif 1267 <= self.width() < 1580 and self.columnNum != 4: self.__setColumnNum(4) elif 1580 <= self.width() < 1893 and self.columnNum != 5: self.__setColumnNum(5) elif self.width() >= 1893 and self.columnNum != 6: self.__setColumnNum(6) def __setColumnNum(self, columnNum: int): """ 设置网格列数 """ self.columnNum = columnNum self.gridLayout.updateColumnNum(columnNum, 298, 288) self.scrollWidget.resize(self.width(), 175 + self.gridLayout.rowCount() * 298 + 120) def __sortPlaylist(self, key): """ 排序播放列表 """ self.sortMode = key if key == 'modifiedTime': self.sortModeButton.setText('修改时间') self.currentSortAct = self.sortByModifiedTimeAct self.playlistCardDict_list.sort( key=self.__sortPlaylistByModifiedTime, reverse=True) else: self.sortModeButton.setText('A到Z') self.currentSortAct = self.sortByAToZAct self.playlistCardDict_list.sort(key=self.__sortPlaylistByAToZ, reverse=False) # 先将小部件布局中移除 self.gridLayout.removeAllWidgets() # 将小部件添加到布局中 for index, playlistCard_dict in enumerate(self.playlistCardDict_list): row = index // self.columnNum column = index - self.columnNum * row playlistCard = playlistCard_dict['playlistCard'] self.gridLayout.addWidget(playlistCard, row, column, Qt.AlignLeft) def __showBlurBackground(self, pos: QPoint, playlistCoverPath: str): """ 显示磨砂背景 """ # 将全局坐标转换为窗口坐标 pos = self.scrollWidget.mapFromGlobal(pos) self.blurBackground.setBlurPic(playlistCoverPath, 40) self.blurBackground.move(pos.x() - 30, pos.y() - 20) self.blurBackground.show() def __showSortModeMenu(self): """ 显示排序方式菜单 """ # 设置默认选中动作 self.sortModeMenu.setDefaultAction(self.currentSortAct) actIndex = self.sortAct_list.index(self.currentSortAct) self.sortModeMenu.exec( self.mapToGlobal( QPoint(self.sender().x(), self.sender().y() - 37 * actIndex - 1))) def __playlistCardCheckedStateChangedSlot(self, playlistCard: PlaylistCard, isChecked: bool): """ 播放列表卡选中状态改变槽函数 """ # 如果专辑信息不在选中的专辑信息列表中且对应的专辑卡变为选中状态就将专辑信息添加到列表中 if playlistCard not in self.checkedPlaylistCard_list and isChecked: self.checkedPlaylistCard_list.append(playlistCard) self.__checkPlaylistCardNumChangedSlot( len(self.checkedPlaylistCard_list)) # 如果专辑信息已经在列表中且该专辑卡变为非选中状态就弹出该专辑信息 elif playlistCard in self.checkedPlaylistCard_list and not isChecked: self.checkedPlaylistCard_list.pop( self.checkedPlaylistCard_list.index(playlistCard)) self.__checkPlaylistCardNumChangedSlot( len(self.checkedPlaylistCard_list)) # 如果先前不处于选择模式那么这次发生选中状态改变就进入选择模式 if not self.isInSelectionMode: # 所有专辑卡进入选择模式 self.__setAllPlaylistCardSelectionModeOpen(True) # 发送信号要求主窗口隐藏播放栏 self.selectionModeStateChanged.emit(True) self.selectionModeBar.show() # 更新标志位 self.isInSelectionMode = True else: if not self.checkedPlaylistCard_list: # 所有专辑卡退出选择模式 self.__setAllPlaylistCardSelectionModeOpen(False) # 发送信号要求主窗口显示播放栏 self.selectionModeBar.hide() self.selectionModeStateChanged.emit(False) # 更新标志位 self.isInSelectionMode = False def __setAllPlaylistCardSelectionModeOpen(self, isOpenSelectionMode: bool): """ 设置所有播放列表卡是否进入选择模式 """ for playlistCard in self.playlistCard_list: playlistCard.setSelectionModeOpen(isOpenSelectionMode) # 退出选择模式时开启隐藏所有复选框的动画 if not isOpenSelectionMode: self.__startHideCheckBoxAni() def __startHideCheckBoxAni(self): """ 开始隐藏复选框动画 """ for ani in self.hideCheckBoxAni_list: ani.setStartValue(1) ani.setEndValue(0) ani.setDuration(140) self.hideCheckBoxAniGroup.start() def __hideAllCheckBox(self): """ 隐藏所有复选框 """ for playlistCard in self.playlistCard_list: playlistCard.checkBox.hide() def __unCheckPlaylistCards(self): """ 取消所有已处于选中状态的播放列表卡的选中状态 """ checkedPlaylistCard_list_copy = self.checkedPlaylistCard_list.copy() for playlistCard in checkedPlaylistCard_list_copy: playlistCard.setChecked(False) # 更新按钮的图标为全选 self.selectionModeBar.checkAllButton.setCheckedState(True) def setAllPlaylistCardCheckedState(self, isAllChecked: bool): """ 设置所有的专辑卡checked状态 """ if self.isAllPlaylistCardChecked == isAllChecked: return self.isAllPlaylistCardChecked = isAllChecked for playlistCard in self.playlistCard_list: playlistCard.setChecked(isAllChecked) def __checkPlaylistCardNumChangedSlot(self, num: int): """ 选中的歌曲卡数量改变对应的槽函数 """ self.selectionModeBar.setPartButtonHidden(num > 1) self.selectionModeBar.move( 0, self.height() - self.selectionModeBar.height()) def __checkAllButtonSlot(self): """ 全选/取消全选按钮槽函数 """ self.setAllPlaylistCardCheckedState(not self.isAllPlaylistCardChecked) def __sortPlaylistByModifiedTime(self, playlistCard_dict: dict) -> str: return playlistCard_dict['playlist']['modifiedTime'] def __sortPlaylistByAToZ(self, playlistCard_dict: dict) -> str: return pinyin.get_initial( playlistCard_dict['playlist']['playlistName'])[0].lower() def addOnePlaylistCard(self, playlist: dict): """ 添加一个播放列表卡 """ self.__createOnePlaylistCard(playlist) self.playlists.append(playlist) self.guideLabel.hide() # 向布局添加小部件 self.gridLayout.appendWidget(self.playlistCard_list[-1]) self.scrollWidget.resize(self.width(), 175 + self.gridLayout.rowCount() * 298 + 120) # 按照当前排序方式重新排序播放列表卡 self.__sortPlaylist(self.sortMode) def __showRenamePlaylistPanel(self, oldPlaylist: dict, playlistCard: PlaylistCard = None): """ 显示重命名播放列表面板 """ playlistCard = self.sender( ) if not playlistCard else playlistCard # type:PlaylistCard renamePlaylistPanel = RenamePlaylistPanel(oldPlaylist, self.window()) renamePlaylistPanel.renamePlaylistSig.connect( lambda oldPlaylist, newPlaylist: self.__renamePlaylistSlot( oldPlaylist, newPlaylist, playlistCard)) renamePlaylistPanel.exec() def __renamePlaylistSlot(self, oldPlaylist: dict, newPlaylist: dict, playlistCard: PlaylistCard): """ 重命名播放列表槽函数 """ playlistCard.updateWindow(newPlaylist) # 重新排序播放列表卡 index = self.playlists.index(oldPlaylist) self.playlists[index] = newPlaylist index = self.getIndexByPlaylist(oldPlaylist) self.playlistCardDict_list[index]['playlist'] = newPlaylist self.__sortPlaylist(self.sortMode) # 发送信号 self.renamePlaylistSig.emit(oldPlaylist, newPlaylist) def __showDeleteCardPanel(self, playlist: dict, playlistCard: PlaylistCard = None): """ 显示删除播放列表卡对话框 """ playlistCard = self.sender() if not playlistCard else playlistCard title = '是否确定要删除此项?' content = f"""如果删除"{playlist['playlistName']}",它将不再位于此设备上。""" deleteCardPanel = DeleteCardPanel(title, content, self.window()) deleteCardPanel.deleteCardSig.connect( lambda: self.__deleteOnePlaylistCard(playlistCard, playlist)) deleteCardPanel.exec() def __deleteOnePlaylistCard(self, playlistCard: PlaylistCard, playlist: dict): """ 删除一个播放列表卡 """ # 从布局中移除播放列表卡 self.gridLayout.removeWidget(playlistCard) # 从列表中弹出小部件 self.playlists.remove(playlist) self.playlistCard_list.remove(playlistCard) self.playlistCardDict_list.pop( self.getIndexByPlaylistCard(playlistCard)) # 删除播放列表卡 playlistCard.deleteLater() # 调整高度 self.scrollWidget.resize(self.width(), 175 + self.gridLayout.rowCount() * 298 + 120) # 删除json文件并发送删除播放列表的信号 remove(f'Playlists\\{playlist["playlistName"]}.json') self.deletePlaylistSig.emit(playlist) # 如果没有专辑卡就显示导航标签 self.guideLabel.setHidden(bool(self.playlistCard_list)) def __deleteMultiPlaylistCards(self, playlistCard_list: list, playlists: list): """ 删除多个播放列表卡 """ for playlistCard, playlist in zip(playlistCard_list, playlists): self.__deleteOnePlaylistCard(playlistCard, playlist) def __emitCheckedPlaylists(self): """ 发送选中的播放列表中的歌曲 """ # 发送播放列表 playlist = [] for playlistCard in self.checkedPlaylistCard_list: playlist.extend(playlistCard.songInfo_list) # 取消所有播放列表卡的选中 self.__unCheckPlaylistCards() if self.sender() is self.selectionModeBar.playButton: self.playSig.emit(playlist) elif self.sender() is self.selectionModeBar.nextToPlayButton: self.nextToPlaySig.emit(playlist) def __selectionBarRenameButtonSlot(self): """ 选择栏重命名按钮的槽函数 """ playlistCard = self.checkedPlaylistCard_list[0] self.__unCheckPlaylistCards() self.__showRenamePlaylistPanel(playlistCard.playlist, playlistCard) def __selectionModeBarDeleteButtonSlot(self): """ 选择栏删除按钮槽函数 """ if len(self.checkedPlaylistCard_list) == 1: playlistCard = self.checkedPlaylistCard_list[0] # 取消所有歌曲卡的选中 self.__unCheckPlaylistCards() self.__showDeleteCardPanel(playlistCard.playlist, playlistCard) else: title = '确定要删除这些项?' content = f"若删除这些播放列表,它们将不再位于此设备上。" playlistCard_list = self.checkedPlaylistCard_list[:] playlists = [ playlistCard.playlist for playlistCard in playlistCard_list ] # 取消所有歌曲卡的选中 self.__unCheckPlaylistCards() # 显示删除对话框 deleteCardPanel = DeleteCardPanel(title, content, self.window()) deleteCardPanel.deleteCardSig.connect( lambda: self.__deleteMultiPlaylistCards( playlistCard_list, playlists)) deleteCardPanel.exec() def addSongsToPlaylist(self, playlistName: str, songInfo_list: list) -> dict: """ 将歌曲添加到播放列表中,返回修改后的播放列表 """ # 直接修改播放列表卡字典中的播放列表 index = self.getIndexByPlaylistName(playlistName) playlistCard_dict = self.playlistCardDict_list[index] playlist = playlistCard_dict['playlist'] # 更新播放列表 playlist['modifiedTime'] = QDateTime.currentDateTime().toString( Qt.ISODate) playlist['songInfo_list'] = songInfo_list + \ playlist['songInfo_list'] playlistCard_dict['playlistCard'].updateWindow(playlist) # 更新json文件 with open(f'Playlists\\{playlistName}.json', 'w', encoding='utf-8') as f: dump(playlist, f) return playlist def getIndexByPlaylistName(self, playlistName: str) -> int: """ 通过播放列表名字获取播放列表在播放列表卡字典列表中的下标 """ for index, playlistCard_dict in enumerate(self.playlistCardDict_list): if playlistCard_dict['playlist']['playlistName'] == playlistName: return index raise Exception(f'指定的播放列表"{playlistName}"不存在') def getIndexByPlaylistCard(self, playlistCard: PlaylistCard) -> int: """ 通过播放列表卡获取播放列表在播放列表卡字典列表中的下标 """ for index, playlistCard_dict in enumerate(self.playlistCardDict_list): if playlistCard_dict['playlistCard'] is playlistCard: return index raise Exception(f'指定的播放列表卡"{playlistCard.playlistName}"不存在') def getIndexByPlaylist(self, playlist: dict) -> int: """ 通过播放列表获取播放列表在播放列表卡字典列表中的下标 """ for index, playlistCard_dict in enumerate(self.playlistCardDict_list): if playlistCard_dict['playlist'] == playlist: return index raise Exception(f"""指定的播放列表"{playlist['playlistName']}"不存在""") def exitSelectionMode(self): """ 退出选择模式 """ self.__unCheckPlaylistCards() def __connectSignalToSlot(self): """ 将信号连接到槽 """ self.sortModeButton.clicked.connect(self.__showSortModeMenu) self.selectionModeBar.cancelButton.clicked.connect( self.__unCheckPlaylistCards) self.selectionModeBar.checkAllButton.clicked.connect( self.__checkAllButtonSlot) self.selectionModeBar.playButton.clicked.connect( self.__emitCheckedPlaylists) self.selectionModeBar.nextToPlayButton.clicked.connect( self.__emitCheckedPlaylists) self.selectionModeBar.renameButton.clicked.connect( self.__selectionBarRenameButtonSlot) self.selectionModeBar.deleteButton.clicked.connect( self.__selectionModeBarDeleteButtonSlot)
class NS_Animate(object): def __init__(self, scene, x_max, y_max, back_color): scene = QGraphicsScene(0, 0, x_max, y_max) scene.setBackgroundBrush(back_color) color = [Qt.green, Qt.lightGray, Qt.darkYellow, QtGui.QColor.fromRgb(255, 85, 0)] self.anim_butt = [ QGraphicsRectWidget(color[j]) for j in range(4) ] for j in range(4): scene.addItem(self.anim_butt[j]) self.window = QGraphicsView(scene) self.window.setFrameStyle(0) self.window.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.window.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.window.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.machine = QStateMachine() self.group = QState() self.timer = QTimer() self.timer.setInterval(1250) self.timer.setSingleShot(True) self.group.entered.connect(self.timer.start) # set states positions anim_state_rects = [ [QRect(x_max*xp/6, y_max*yp/4, 8, 8) for xp in range(4)] for yp in range(4) ] self.states = [ self.createGeometryState( self.anim_butt[0], anim_state_rects[0][j], self.anim_butt[1], anim_state_rects[1][j], self.anim_butt[2], anim_state_rects[2][j], self.anim_butt[3], anim_state_rects[3][j], self.group ) for j in range(4) ] self.group.setInitialState(self.states[0]) self.animationGroup = QParallelAnimationGroup() self.anim = QPropertyAnimation(self.anim_butt[3], 'geometry') self.anim.setDuration(1250) self.anim.setEasingCurve(QEasingCurve.InBack) self.animationGroup.addAnimation(self.anim) self.subGroup = QSequentialAnimationGroup(self.animationGroup) self.subGroup.addPause(100) self.anim = QPropertyAnimation(self.anim_butt[2], 'geometry') self.anim.setDuration(1000) self.anim.setEasingCurve(QEasingCurve.OutElastic) self.subGroup.addAnimation(self.anim) self.subGroup = QSequentialAnimationGroup(self.animationGroup) self.subGroup.addPause(500) self.anim = QPropertyAnimation(self.anim_butt[1], 'geometry') self.anim.setDuration(500) self.anim.setEasingCurve(QEasingCurve.OutElastic) self.subGroup.addAnimation(self.anim) self.subGroup = QSequentialAnimationGroup(self.animationGroup) self.subGroup.addPause(750) self.anim = QPropertyAnimation(self.anim_butt[0], 'geometry') self.anim.setDuration(250) self.anim.setEasingCurve(QEasingCurve.OutElastic) self.subGroup.addAnimation(self.anim) self.stateSwitcher = StateSwitcher(self.machine) self.group.addTransition(self.timer.timeout, self.stateSwitcher) for j in range(4): self.stateSwitcher.addState(self.states[j], self.animationGroup) self.machine.addState(self.group) self.machine.setInitialState(self.group) self.machine.start() # def createGeometryState(self, w1, rect1, w2, rect2, w3, rect3, w4, rect4, parent): result = QState(parent) result.assignProperty(w1, 'geometry', rect1) result.assignProperty(w1, 'geometry', rect1) result.assignProperty(w2, 'geometry', rect2) result.assignProperty(w3, 'geometry', rect3) result.assignProperty(w4, 'geometry', rect4) return result
class LifeCycle(object): def __init__(self, stickMan, keyReceiver): self.m_stickMan = stickMan self.m_keyReceiver = keyReceiver # Create animation group to be used for all transitions. self.m_animationGroup = QParallelAnimationGroup() stickManNodeCount = self.m_stickMan.nodeCount() self._pas = [] for i in range(stickManNodeCount): pa = QPropertyAnimation(self.m_stickMan.node(i), b"pos") self._pas.append(pa) self.m_animationGroup.addAnimation(pa) # Set up intial state graph. self.m_machine = QStateMachine() self.m_machine.addDefaultAnimation(self.m_animationGroup) self.m_alive = QState(self.m_machine) self.m_alive.setObjectName("alive") # Make it blink when lightning strikes before entering dead animation. lightningBlink = QState(self.m_machine) lightningBlink.assignProperty(self.m_stickMan.scene(), "backgroundBrush", Qt.white) lightningBlink.assignProperty(self.m_stickMan, "penColor", Qt.black) lightningBlink.assignProperty(self.m_stickMan, "fillColor", Qt.white) lightningBlink.assignProperty(self.m_stickMan, "isDead", True) timer = QTimer(lightningBlink) timer.setSingleShot(True) timer.setInterval(100) lightningBlink.entered.connect(timer.start) lightningBlink.exited.connect(timer.stop) self.m_dead = QState(self.m_machine) self.m_dead.assignProperty(self.m_stickMan.scene(), "backgroundBrush", Qt.black) self.m_dead.assignProperty(self.m_stickMan, "penColor", Qt.white) self.m_dead.assignProperty(self.m_stickMan, "fillColor", Qt.black) self.m_dead.setObjectName("dead") # Idle state (sets no properties). self.m_idle = QState(self.m_alive) self.m_idle.setObjectName("idle") self.m_alive.setInitialState(self.m_idle) # Lightning strikes at random. self.m_alive.addTransition(LightningStrikesTransition(lightningBlink)) lightningBlink.addTransition(timer.timeout, self.m_dead) self.m_machine.setInitialState(self.m_alive) def setDeathAnimation(self, fileName): deathAnimation = self.makeState(self.m_dead, fileName) self.m_dead.setInitialState(deathAnimation) def start(self): self.m_machine.start() def addActivity(self, fileName, key): state = self.makeState(self.m_alive, fileName) self.m_alive.addTransition(KeyPressTransition(self.m_keyReceiver, key, state)) def makeState(self, parentState, animationFileName): topLevel = QState(parentState) animation = Animation() file = QFile(animationFileName) if file.open(QIODevice.ReadOnly): animation.load(file) frameCount = animation.totalFrames() previousState = None for i in range(frameCount): animation.setCurrentFrame(i) frameState = QState(topLevel) nodeCount = animation.nodeCount() for j in range(nodeCount): frameState.assignProperty(self.m_stickMan.node(j), "pos", animation.nodePos(j)) frameState.setObjectName("frame %d" % i) if previousState is None: topLevel.setInitialState(frameState) else: previousState.addTransition(previousState.propertiesAssigned, frameState) previousState = frameState previousState.addTransition(previousState.propertiesAssigned, topLevel.initialState()) return topLevel
class BasicSongCard(QWidget): """ 歌曲卡基类 """ clicked = pyqtSignal(int) doubleClicked = pyqtSignal(int) playButtonClicked = pyqtSignal(int) addSongToPlayingSig = pyqtSignal(dict) checkedStateChanged = pyqtSignal(int, bool) addSongsToCustomPlaylistSig = pyqtSignal(str, list) def __init__(self, songInfo: dict, songCardType, parent=None): """ 实例化歌曲卡 Parameters ---------- songInfo: dict 歌曲信息字典 songCardType: `~SongCardType` 歌曲卡类型 parent: 父级 """ super().__init__(parent) self._getInfo(songInfo) self.__resizeTime = 0 self.__songCardType = songCardType # 歌曲卡类型 self.__SongNameCard = [SongTabSongNameCard, TrackNumSongNameCard][songCardType.value] # 初始化各标志位 self.isSongExist = True self.isPlaying = False self.isSelected = False self.isChecked = False self.isInSelectionMode = False self.isDoubleClicked = False # 记录songCard对应的item的下标 self.itemIndex = None # 创建小部件 if self.__songCardType == SongCardType.SONG_TAB_SONG_CARD: self.songNameCard = self.__SongNameCard(self.songName, self) elif self.__songCardType == SongCardType.ALBUM_INTERFACE_SONG_CARD: self.songNameCard = self.__SongNameCard(self.songName, self.tracknumber, self) self.__referenceWidgets() # 初始化小部件列表 self.__scaleableLabelTextWidth_list = [] # 可拉伸的标签的文本的宽度列表 self.__scaleableWidgetMaxWidth_list = [] # 可拉伸部件的最大宽度列表 self.__dynamicStyleLabel_list = [] self.__scaleableWidget_list = [] self.__clickableLabel_list = [] self.__labelSpacing_list = [] self.__label_list = [] self.__widget_list = [] # 存放所有的小部件 # 创建动画组和动画列表 self.aniGroup = QParallelAnimationGroup(self) self.__aniWidget_list = [] self.__deltaX_list = [] self.ani_list = [] # 安装事件过滤器 self.installEventFilter(self) # 信号连接到槽 self.playButton.clicked.connect(self.playButtonSlot) self.addToButton.clicked.connect(self.__showAddToMenu) self.checkBox.stateChanged.connect(self.checkedStateChangedSlot) def _getInfo(self, songInfo: dict): """ 从歌曲信息字典中获取信息 """ self.songInfo = songInfo self.songPath = songInfo.get("songPath", "") # type:str self.songName = songInfo.get("songName", "未知歌曲") # type:str self.songer = songInfo.get("songer", "未知歌手") # type:str self.album = songInfo.get("album", "未知专辑") # type:str self.year = songInfo.get("year", "未知年份") # type:str self.tcon = songInfo.get("tcon", "未知流派") # type:str self.duration = songInfo.get("duration", "0:00") # type:str self.tracknumber = songInfo.get("tracknumber", "0") # type:str def setScalableWidgets(self, scaleableWidget_list: list, scalebaleWidgetWidth_list: list, fixedWidth=0): """ 设置可随着歌曲卡的伸缩而伸缩的标签 Parameters ---------- scaleableWidget_list : 随着歌曲卡的伸缩而伸缩的小部件列表,要求第一个元素为歌名卡,后面的元素都是label scaleableWidgetWidth_list : 与可伸缩小部件相对应的小部件初始长度列表 fixedWidth : 为其他不可拉伸的小部件保留的宽度 """ if self.__scaleableWidgetMaxWidth_list: return self.__checkIsLengthEqual(scaleableWidget_list, scalebaleWidgetWidth_list) # 必须先将所有标签添加到列表中后才能调用这个函数 if not self.__label_list: raise Exception("必须先调用addLabels函数将标签添加到窗口中") # 歌名卡默认可拉伸 self.__scaleableWidget_list = scaleableWidget_list self.__scaleableWidgetMaxWidth_list = scalebaleWidgetWidth_list # 计算初始宽度 initWidth = (sum(self.__scaleableWidgetMaxWidth_list) + sum(self.__labelSpacing_list) + fixedWidth) self.resize(initWidth, 60) self.setFixedHeight(60) def addLabels(self, label_list: list, labelSpacing_list: list): """ 往歌曲卡中添加除了歌曲名卡之外的标签,只能初始化一次标签列表 Paramerter ---------- label_list : 歌名卡后的标签列表 labelSpacing_list : 每个标签的前置空白 """ if self.__label_list: return self.__checkIsLengthEqual(label_list, labelSpacing_list) self.__label_list = label_list self.__labelSpacing_list = labelSpacing_list self.__widget_list = [self.songNameCard] + self.__label_list # 移动小部件 for i in range(len(label_list)): label_list[i] def setDynamicStyleLabels(self, label_list: list): """ 设置需要动态更新样式的标签列表 """ self.__dynamicStyleLabel_list = label_list def setClickableLabels(self, clickableLabel_list: list): """ 设置可点击的标签列表 """ self.__clickableLabel_list = clickableLabel_list # 分配ID for label in self.__clickableLabel_list: label.setObjectName("clickableLabel") def setSelected(self, isSelected: bool): """ 设置选中状态 """ self.isSelected = isSelected if isSelected: self.setWidgetState("selected-leave") self.setCheckBoxBtLabelState("selected") else: self.songNameCard.setWidgetHidden(True) self.setWidgetState("notSelected-leave") state = "notSelected-play" if self.isPlaying else "notSelected-notPlay" self.setCheckBoxBtLabelState(state) self.setStyle(QApplication.style()) def setPlay(self, isPlay: bool): """ 设置播放状态并更新样式 """ self.isPlaying = isPlay self.isSelected = isPlay # 判断歌曲文件是否存在 self.isSongExist = os.path.exists(self.songPath) if isPlay: self.isSelected = True self.setCheckBoxBtLabelState("selected") self.setWidgetState("selected-leave") else: self.setCheckBoxBtLabelState("notSelected-notPlay") self.setWidgetState("notSelected-leave") self.songNameCard.setPlay(isPlay, self.isSongExist) self.setStyle(QApplication.style()) def setCheckBoxBtLabelState(self, state: str): """ 设置复选框、按钮和标签动态属性 Parameters ---------- state: str 复选框、按钮和标签的状态,可以是: * `notSelected-notPlay` * `notSelected-play` * `selected` """ self.songNameCard.setCheckBoxBtLabelState(state, self.isSongExist) for label in self.__dynamicStyleLabel_list: label.setProperty("state", state) def setWidgetState(self, state: str): """ 设置按钮组窗口和自己的状态 Parameters ---------- state: str 窗口状态,可以是: * `notSelected-leave` * `notSelected-enter` * `notSelected-pressed` * `selected-leave` * `selected-enter` * `selected-pressed` """ self.songNameCard.setButtonGroupState(state) self.setProperty("state", state) def setAnimation(self, aniWidget_list: list, deltaX_list: list): """ 设置小部件的动画 Parameters ---------- aniWidget_list : 需要设置动画的小部件列表 deltaX_list : 和aniWidget_list相对应的动画位置偏移量列表 """ self.__checkIsLengthEqual(aniWidget_list, deltaX_list) self.__aniWidget_list = aniWidget_list self.__deltaX_list = deltaX_list # 清空动画组的内容 self.ani_list.clear() self.aniGroup.clear() self.__ani_list = [ QPropertyAnimation(widget, b"geometry") for widget in self.__aniWidget_list ] # 初始化动画 for ani in self.__ani_list: ani.setDuration(400) ani.setEasingCurve(QEasingCurve.OutQuad) self.aniGroup.addAnimation(ani) self.getAniTargetX_list() def getAniTargetX_list(self): """ 计算动画的初始值 """ self.__aniTargetX_list = [] for deltaX, widget in zip(self.__deltaX_list, self.__aniWidget_list): self.__aniTargetX_list.append(deltaX + widget.x()) def eventFilter(self, obj, e: QEvent): """ 安装监听 """ if obj == self: if e.type() == QEvent.Enter: self.songNameCard.checkBox.show() self.songNameCard.buttonGroup.setHidden(self.isInSelectionMode) state = "selected-enter" if self.isSelected else "notSelected-enter" self.setWidgetState(state) self.setStyle(QApplication.style()) elif e.type() == QEvent.Leave: # 不处于选择模式下时,如果歌曲卡没被选中而鼠标离开窗口就隐藏复选框和按钮组窗口 if not self.isSelected: self.songNameCard.buttonGroup.hide() self.songNameCard.checkBox.setHidden( not self.isInSelectionMode) state = "selected-leave" if self.isSelected else "notSelected-leave" self.setWidgetState(state) self.setStyle(QApplication.style()) elif e.type() == QEvent.MouseButtonPress: state = "selected-pressed" if self.isSelected else "notSelected-pressed" if e.button() == Qt.LeftButton: self.isSelected = True self.setWidgetState(state) self.setStyle(QApplication.style()) elif e.type() == QEvent.MouseButtonRelease and e.button( ) == Qt.LeftButton: self.setWidgetState("selected-leave") self.setCheckBoxBtLabelState("selected") # 鼠标松开时将设置标签为白色 self.setStyle(QApplication.style()) elif e.type() == QEvent.MouseButtonDblClick: self.isDoubleClicked = True return super().eventFilter(obj, e) def mousePressEvent(self, e): """ 鼠标按下时移动小部件 """ super().mousePressEvent(e) # 移动小部件 if self.aniGroup.state() == QAbstractAnimation.Stopped: for deltaX, widget in zip(self.__deltaX_list, self.__aniWidget_list): widget.move(widget.x() + deltaX, widget.y()) else: self.aniGroup.stop() # 强制停止还未结束的动画 for targetX, widget in zip(self.__aniTargetX_list, self.__aniWidget_list): widget.move(targetX, widget.y()) def mouseReleaseEvent(self, e: QMouseEvent): """ 鼠标松开时开始动画 """ for ani, widget, deltaX in zip(self.__ani_list, self.__aniWidget_list, self.__deltaX_list): ani.setStartValue( QRect(widget.x(), widget.y(), widget.width(), widget.height())) ani.setEndValue( QRect(widget.x() - deltaX, widget.y(), widget.width(), widget.height())) self.aniGroup.start() if e.button() == Qt.LeftButton: self.clicked.emit(self.itemIndex) # 左键点击时才发送信号 if self.isDoubleClicked and e.button() == Qt.LeftButton: self.isDoubleClicked = False if not self.isPlaying: # 发送点击信号 self.aniGroup.finished.connect(self.__aniFinishedSlot) def __aniFinishedSlot(self): """ 动画完成时发出双击信号 """ self.doubleClicked.emit(self.itemIndex) self.aniGroup.disconnect() def setClickableLabelCursor(self, cursor): """ 设置可点击标签的光标样式 """ for label in self.__clickableLabel_list: label.setCursor(cursor) def __referenceWidgets(self): """ 引用小部件 """ self.buttonGroup = self.songNameCard.buttonGroup self.playButton = self.songNameCard.playButton self.addToButton = self.songNameCard.addToButton self.checkBox = self.songNameCard.checkBox def playButtonSlot(self): """ 播放按钮按下时更新样式 """ self.playButtonClicked.emit(self.itemIndex) def checkedStateChangedSlot(self): """ 复选框选中状态改变对应的槽函数 """ self.isChecked = self.checkBox.isChecked() self.setSelected(self.isChecked) # 只要点击了复选框就进入选择模式,由父级控制退出选择模式 self.checkBox.show() self.setSelectionModeOpen(True) # 发出选中状态改变信号 self.checkedStateChanged.emit(self.itemIndex, self.isChecked) def setSelectionModeOpen(self, isOpenSelectionMode: bool): """ 设置是否进入选择模式, 处于选择模式下复选框一直可见,按钮不管是否处于选择模式都不可见 """ if self.isInSelectionMode == isOpenSelectionMode: return # 更新标志位 self.isInSelectionMode = isOpenSelectionMode # 设置按钮和复选框的可见性 self.checkBox.setHidden(not isOpenSelectionMode) self.buttonGroup.setHidden(True) def setChecked(self, isChecked: bool): """ 设置歌曲卡选中状态 """ self.checkBox.setChecked(isChecked) def updateSongCard(self): """ 更新歌曲卡 """ # 必须被子类重写 raise NotImplementedError def __checkIsLengthEqual(self, list_1: list, list_2: list): """ 检查输入的两个列表的长度是否相等,不相等则引发错误 """ if len(list_1) != len(list_2): raise Exception("两个列表的长度必须一样") def resizeEvent(self, e): """ 改变窗口大小时移动标签 """ self.__resizeTime += 1 if self.__resizeTime > 1: # 分配多出来的宽度 deltaWidth = self.width() - self.__originalWidth self.__originalWidth = self.width() equalWidth = int(deltaWidth / len(self.__scaleableWidget_list)) self.__scaleableWidgetMaxWidth_list = [ i + equalWidth for i in self.__scaleableWidgetMaxWidth_list ] else: self.__originalWidth = self.width() # 调整小部件宽度 self.adjustWidgetWidth() # 移动标签 x = self.songNameCard.width() for label, spacing in zip(self.__label_list, self.__labelSpacing_list): # 如果标签是可变长度的,就将width设置为其最大可变宽度 if label in self.__scaleableWidget_list: index = self.__scaleableWidget_list.index(label) width = self.__scaleableWidgetMaxWidth_list[index] else: width = label.width() label.move(x + spacing, 20) x = width + label.x() # 更新动画目标移动位置 self.getAniTargetX_list() def adjustWidgetWidth(self): """ 调整小部件宽度 """ # 计算标签的宽度 self.__getScaleableLabelTextWidth() self.songNameCard.resize(self.__scaleableWidgetMaxWidth_list[0], 60) for i in range(1, len(self.__scaleableWidget_list)): label = self.__scaleableWidget_list[i] textWidth = self.__scaleableLabelTextWidth_list[i - 1] maxLabelWidth = self.__scaleableWidgetMaxWidth_list[i] width = maxLabelWidth if textWidth > maxLabelWidth else textWidth label.setFixedWidth(width) def __getScaleableLabelTextWidth(self): """ 计算可拉伸的标签的文本宽度 """ fontMetrics = QFontMetrics(QFont("Microsoft YaHei", 9)) self.__scaleableLabelTextWidth_list = [ fontMetrics.width(label.text()) for label in self.__scaleableWidget_list[1:] ] def __showAddToMenu(self): """ 显示添加到菜单 """ addToMenu = AddToMenu(parent=self) addToGlobalPos = self.mapToGlobal(QPoint(0, 0)) + QPoint( self.addToButton.x() + self.buttonGroup.x(), 0) x = addToGlobalPos.x() + self.addToButton.width() + 5 y = addToGlobalPos.y() + int(self.addToButton.height() / 2 - (13 + 38 * addToMenu.actionCount()) / 2) addToMenu.playingAct.triggered.connect( lambda: self.addSongToPlayingSig.emit(self.songInfo)) addToMenu.addSongsToPlaylistSig.connect( lambda name: self.addSongsToCustomPlaylistSig.emit( name, [self.songInfo])) addToMenu.exec(QPoint(x, y)) @property def widget_list(self) -> list: """ 返回窗口内的所有小部件组成的列表 """ return self.__widget_list @property def label_list(self) -> list: """ 返回窗口内所有标签组成的列表 """ return self.__label_list
class RubberBandButton(QPushButton): def __init__(self, *args, **kwargs): super(RubberBandButton, self).__init__(*args, **kwargs) self.setFlat(True) self.setCursor(Qt.PointingHandCursor) self._width = 0 self._height = 0 self._bgcolor = QColor(Qt.green) def paintEvent(self, event): self._initAnimate() painter = QStylePainter(self) painter.setRenderHint(QPainter.Antialiasing, True) painter.setRenderHint(QPainter.HighQualityAntialiasing, True) painter.setRenderHint(QPainter.SmoothPixmapTransform, True) painter.setBrush(QColor(self._bgcolor)) painter.setPen(QColor(self._bgcolor)) painter.drawEllipse(QRectF( (self.minimumWidth() - self._width) / 2, (self.minimumHeight() - self._height) / 2, self._width, self._height )) # 绘制本身的文字和图标 options = QStyleOptionButton() options.initFrom(self) size = options.rect.size() size.transpose() options.rect.setSize(size) options.features = QStyleOptionButton.Flat options.text = self.text() options.icon = self.icon() options.iconSize = self.iconSize() painter.drawControl(QStyle.CE_PushButton, options) event.accept() def _initAnimate(self): if hasattr(self, '_animate'): return self._width = self.minimumWidth() * 7 / 8 self._height = self.minimumHeight() * 7 / 8 # self._width=175 # self._height=175 wanimate = QPropertyAnimation(self, b'rWidth') wanimate.setEasingCurve(QEasingCurve.OutElastic) wanimate.setDuration(700) wanimate.valueChanged.connect(self.update) wanimate.setKeyValueAt(0, self._width) # wanimate.setKeyValueAt(0.1, 180) # wanimate.setKeyValueAt(0.2, 185) # wanimate.setKeyValueAt(0.3, 190) # wanimate.setKeyValueAt(0.4, 195) wanimate.setKeyValueAt(0.5, self._width + 6) # wanimate.setKeyValueAt(0.6, 195) # wanimate.setKeyValueAt(0.7, 190) # wanimate.setKeyValueAt(0.8, 185) # wanimate.setKeyValueAt(0.9, 180) wanimate.setKeyValueAt(1, self._width) hanimate = QPropertyAnimation(self, b'rHeight') hanimate.setEasingCurve(QEasingCurve.OutElastic) hanimate.setDuration(700) hanimate.setKeyValueAt(0, self._height) # hanimate.setKeyValueAt(0.1, 170) # hanimate.setKeyValueAt(0.3, 165) hanimate.setKeyValueAt(0.5, self._height - 6) # hanimate.setKeyValueAt(0.7, 165) # hanimate.setKeyValueAt(0.9, 170) hanimate.setKeyValueAt(1, self._height) self._animate = QParallelAnimationGroup(self) self._animate.addAnimation(wanimate) self._animate.addAnimation(hanimate) def enterEvent(self, event): super(RubberBandButton, self).enterEvent(event) self._animate.stop() self._animate.start() @pyqtProperty(int) def rWidth(self): return self._width @rWidth.setter def rWidth(self, value): self._width = value @pyqtProperty(int) def rHeight(self): return self._height @rHeight.setter def rHeight(self, value): self._height = value @pyqtProperty(QColor) def bgColor(self): return self._bgcolor @bgColor.setter def bgColor(self, color): self._bgcolor = QColor(color)
class Spoiler(QWidget): def __init__(self, title="", animationDuration=300, parent=None): QWidget.__init__(self, parent) self.mainLayout = QGridLayout() self.toggleButton = QToolButton() self.toggleAnimation = QParallelAnimationGroup() self.contentArea = QScrollArea() self.headerLine = QFrame() self.animationDuration = animationDuration self.toggleButton.setStyleSheet("QToolButton { border: none; }") self.toggleButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggleButton.setArrowType(Qt.ArrowType.RightArrow) self.toggleButton.setText(title) self.toggleButton.setCheckable(True) self.toggleButton.setChecked(False) self.headerLine.setFrameShape(QFrame.HLine) self.headerLine.setFrameShadow(QFrame.Sunken) self.headerLine.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) self.contentArea.setStyleSheet( "QScrollArea { background-color: white; border: none; }") self.contentArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.contentArea.setMaximumHeight(0) self.contentArea.setMinimumHeight(0) # let the entire widget grow and shrink with its content self.toggleAnimation.addAnimation(QPropertyAnimation()) self.toggleAnimation.addAnimation(QPropertyAnimation(self)) self.toggleAnimation.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.toggleAnimation.addAnimation( QPropertyAnimation(self, b"maximumHeight")) self.toggleAnimation.addAnimation( QPropertyAnimation(self.contentArea, b"maximumHeight")) # don't waste space self.mainLayout.setVerticalSpacing(0) self.mainLayout.setContentsMargins(0, 0, 0, 0) row = 0 self.mainLayout.addWidget(self.toggleButton, 0, 0, 1, 1, Qt.AlignLeft) self.mainLayout.addWidget(self.headerLine, 0, 2, 1, 1) self.mainLayout.addWidget(self.contentArea, 1, 0, 1, 3) self.setLayout(self.mainLayout) self.toggleButton.clicked[bool].connect(self.expandShrinkContent) def expandShrinkContent(self, checked): if checked: self.toggleButton.setArrowType(Qt.ArrowType.DownArrow) self.toggleAnimation.setDirection(QAbstractAnimation.Forward) else: self.toggleButton.setArrowType(Qt.ArrowType.RightArrow) self.toggleAnimation.setDirection(QAbstractAnimation.Backward) self.toggleAnimation.start() def setContentLayout(self, contentLayout): self.contentArea.setLayout(contentLayout) collapsedHeight = self.sizeHint().height( ) - self.contentArea.maximumHeight() contentHeight = contentLayout.sizeHint().height() i = 0 while i < self.toggleAnimation.animationCount(): i += 1 spoilerAnimation = self.toggleAnimation.animationAt(i) if spoilerAnimation is not None: spoilerAnimation.setDuration(self.animationDuration) spoilerAnimation.setStartValue(collapsedHeight) spoilerAnimation.setEndValue(collapsedHeight + contentHeight) contentAnimation = self.toggleAnimation.animationAt( self.toggleAnimation.animationCount() - 1) contentAnimation.setDuration(self.animationDuration) contentAnimation.setStartValue(0) contentAnimation.setEndValue(contentHeight)
class CoffeeFundWindow(QWidget): signal_back = pyqtSignal() signal_coffee = pyqtSignal() signal_account = pyqtSignal() def __init__(self, parent=None): super().__init__() self.cards = {} # type: Dict[GuiCards, QWidget] """ Python's GC will clean up QPropertyAnimations as soon as it leaves the button handler, therefore they will appear not to work. Use members to store the animations. see http://stackoverflow.com/a/6953965 """ self.slide_in_animation = None self.slide_out_animation = None self.animation_group = None self.setObjectName("coffeeFundWindow") """ Store the position and size of visible/hidden cards for the animation sequences """ self.hidden_geometry = None self.visible_geometry = None layout = QStackedLayout() layout.setStackingMode(QStackedLayout.StackAll) card_remove = RemoveCard() self.cards[GuiCards.RemoveCard] = card_remove layout.addWidget(card_remove) card_choose_action = ChooseActionCard() self.cards[GuiCards.ChooseAction] = card_choose_action layout.addWidget(card_choose_action) card_account = AccountCard() self.cards[GuiCards.AccountInfo] = card_account layout.addWidget(card_account) # keep this as last initialized card, the last card will be shown on startup! card_start = StartCard() self.cards[GuiCards.Start] = card_start layout.addWidget(card_start) self.setLayout(layout) self.setWindowTitle("Kaffeekasse") layout.setCurrentWidget(card_start) self.active_card = None card_choose_action.button_account.clicked.connect(self.signal_account) card_choose_action.button_coffee.clicked.connect(self.signal_coffee) card_account.button_back.clicked.connect(self.signal_back) def set_card_hidden(self, card: QWidget): card.setGeometry(self.hidden_geometry) def show_start(self): self.show_card(GuiCards.Start) def show_account(self, name, value): self.cards[GuiCards.AccountInfo].set_user_name(name) self.cards[GuiCards.AccountInfo].set_balance(value) self.show_card(GuiCards.AccountInfo) def show_choose_action(self, name: str): self.cards[GuiCards.ChooseAction].set_user_name(name) self.show_card(GuiCards.ChooseAction) def show_remove(self): self.show_card(GuiCards.RemoveCard) def show_card(self, card_id: GuiCards): if self.active_card is None: self.active_card = self.cards[GuiCards.Start] if self.active_card == self.cards[card_id]: return if self.visible_geometry is None or self.hidden_geometry is None: self.visible_geometry = self.active_card.geometry() # type: QRect self.hidden_geometry = QRect(self.visible_geometry.x(), self.visible_geometry.height() * 1.5, self.visible_geometry.width(), self.visible_geometry.height()) for key in self.cards.keys(): if key != self.active_card: self.set_card_hidden(self.cards[key]) card_to_show = self.cards[card_id] self.start_card_switch(card_to_show) self.active_card = self.cards[card_id] self.layout().setCurrentWidget(self.active_card) def start_card_switch(self, card_to_show): self.slide_out_animation = QPropertyAnimation(self.active_card, "geometry") self.slide_out_animation.setDuration(ANIMATION_DURATION) self.slide_out_animation.setEasingCurve(QEasingCurve.OutCubic) self.slide_out_animation.setStartValue(self.visible_geometry) self.slide_out_animation.setEndValue(self.hidden_geometry) self.set_card_hidden(card_to_show) self.slide_in_animation = QPropertyAnimation(card_to_show, "geometry") self.slide_in_animation.setDuration(ANIMATION_DURATION) self.slide_in_animation.setEasingCurve(QEasingCurve.InCubic) self.slide_in_animation.setStartValue(self.hidden_geometry) self.slide_in_animation.setEndValue(self.visible_geometry) self.animation_group = QParallelAnimationGroup() self.animation_group.addAnimation(self.slide_out_animation) self.animation_group.addAnimation(self.slide_in_animation) self.animation_group.start()
class AlbumCardViewer(QWidget): """ 定义一个专辑卡视图 """ playSignal = pyqtSignal(list) nextPlaySignal = pyqtSignal(list) albumNumChanged = pyqtSignal(int) saveAlbumInfoSig = pyqtSignal(dict, dict) addAlbumToPlayingSignal = pyqtSignal(list) # 将专辑添加到正在播放 switchToAlbumInterfaceSig = pyqtSignal(dict) selectionModeStateChanged = pyqtSignal(bool) checkedAlbumCardNumChanged = pyqtSignal(int) addAlbumToNewCustomPlaylistSig = pyqtSignal(list) # 将专辑添加到新建的播放列表 addAlbumToCustomPlaylistSig = pyqtSignal(str, list) # 将专辑添加到已存在的自定义播放列表 def __init__(self, albumInfo_list: list, parent=None): super().__init__(parent) self.albumInfo_list = albumInfo_list # 初始化网格的列数 self.columnNum = 1 self.albumCardDict_list = [] self.albumCard_list = [] self.checkedAlbumCard_list = [] self.currentGroupDict_list = [] # 初始化标志位 self.isInSelectionMode = False self.isAllAlbumCardsChecked = False # 设置当前排序方式 self.sortMode = '添加时间' # 实例化滚动部件的竖直布局 self.scrollWidgetVBoxLayout = QVBoxLayout() # 实例化滚动区域和滚动区域的窗口 self.__createGuideLabel() self.scrollArea = ScrollArea(self) self.scrollWidget = QWidget() self.albumBlurBackground = AlbumBlurBackground(self.scrollWidget) # 创建专辑卡并将其添加到布局中 self.__createAlbumCards() # 初始化小部件 self.__initWidget() def __initWidget(self): """ 初始化小部件 """ self.resize(1270, 760) # 隐藏磨砂背景 self.albumBlurBackground.hide() # 设置导航标签的可见性 self.guideLabel.raise_() self.guideLabel.setHidden(bool(self.albumCard_list)) # 初始化滚动条 self.scrollArea.setHorizontalScrollBarPolicy( Qt.ScrollBarAlwaysOff) self.scrollWidget.setObjectName('scrollWidget') self.__connectSignalToSlot() self.__initLayout() self.__setQss() def __createGuideLabel(self): """ 创建导航标签 """ self.guideLabel = QLabel('这里没有可显示的内容。请尝试其他筛选器。', self) self.guideLabel.setStyleSheet( "color: black; font: 25px 'Microsoft YaHei'") self.guideLabel.resize(500, 26) self.guideLabel.move(35, 286) def __createAlbumCards(self): """ 将专辑卡添加到窗口中 """ # 创建并行动画组 self.hideCheckBoxAniGroup = QParallelAnimationGroup(self) self.hideCheckBoxAni_list = [] for albumInfo in self.albumInfo_list: self.__createOneAlbumCard(albumInfo) def __createOneAlbumCard(self, albumInfo: dict): """ 创建一个专辑卡 """ # 实例化专辑卡和动画 albumCard = AlbumCard(albumInfo, self) # 创建动画 hideCheckBoxAni = QPropertyAnimation( albumCard.checkBoxOpacityEffect, b'opacity') self.hideCheckBoxAniGroup.addAnimation(hideCheckBoxAni) self.hideCheckBoxAni_list.append(hideCheckBoxAni) # 将含有专辑卡及其信息的字典插入列表 album = albumInfo['album'] self.albumCard_list.append(albumCard) self.albumCardDict_list.append({'albumCard': albumCard, 'albumName': album, 'year': albumInfo['year'][:4], 'songer': albumInfo['songer'], 'firstLetter': pinyin.get_initial(album[0])[0].upper()}) # 专辑卡信号连接到槽函数 albumCard.playSignal.connect(self.playSignal) albumCard.nextPlaySignal.connect(self.nextPlaySignal) albumCard.saveAlbumInfoSig.connect(self.saveAlbumInfoSig) albumCard.deleteCardSig.connect(self.showDeleteOneCardPanel) albumCard.addToPlayingSignal.connect( self.addAlbumToPlayingSignal) albumCard.switchToAlbumInterfaceSig.connect( self.switchToAlbumInterfaceSig) albumCard.checkedStateChanged.connect( self.__albumCardCheckedStateChangedSlot) albumCard.showBlurAlbumBackgroundSig.connect( self.__showBlurAlbumBackground) albumCard.hideBlurAlbumBackgroundSig.connect( self.albumBlurBackground.hide) albumCard.addAlbumToCustomPlaylistSig.connect( self.addAlbumToCustomPlaylistSig) albumCard.addAlbumToNewCustomPlaylistSig.connect( self.addAlbumToNewCustomPlaylistSig) def __connectSignalToSlot(self): """ 将信号连接到槽函数 """ # 动画完成隐藏复选框 self.hideCheckBoxAniGroup.finished.connect(self.__hideAllCheckBox) def __initLayout(self): """ 初始化布局 """ # 按照添加时间分组 self.sortByAddTime() self.scrollWidgetVBoxLayout.setSpacing(30) self.scrollWidgetVBoxLayout.setContentsMargins(10, 245, 0, 120) self.scrollWidget.setLayout(self.scrollWidgetVBoxLayout) self.scrollArea.setWidget(self.scrollWidget) def resizeEvent(self, event): """ 根据宽度调整网格的列数 """ super().resizeEvent(event) self.scrollArea.resize(self.size()) # 如果第一次超过1337就调整网格的列数 if self.width() >= 1790 and self.columnNum != 8: self.__updateColumnNum(8) if 1570 <= self.width() < 1790 and self.columnNum != 7: self.__updateColumnNum(7) elif 1350 <= self.width() < 1570 and self.columnNum != 6: self.__updateColumnNum(6) elif 1130 < self.width() < 1350 and self.columnNum != 5: self.__updateColumnNum(5) elif 910 < self.width() <= 1130 and self.columnNum != 4: self.__updateColumnNum(4) elif 690 < self.width() <= 910: self.__updateColumnNum(3) elif self.width() <= 690: self.__updateColumnNum(2) # 调整滚动条 self.scrollArea.verticalScrollBar().move(-1, 40) self.scrollArea.verticalScrollBar().resize( self.scrollArea.verticalScrollBar().width(), self.height() - 156) def __updateColumnNum(self, columnNum: int): """ 更新网格列数 """ self.columnNum = columnNum for currentGroup_dict in self.currentGroupDict_list: gridLayout = currentGroup_dict['gridLayout'] # type:GridLayout gridLayout.updateColumnNum(columnNum, 210, 290) self.__adjustScrollWidgetSize() def __adjustScrollWidgetSize(self): """ 调整滚动部件的高度 """ rowCount = sum([currentGroup_dict['gridLayout'].rowCount() for currentGroup_dict in self.currentGroupDict_list]) containerCount = len(self.currentGroupDict_list) self.scrollWidget.resize( self.width(), 310 * rowCount + 60 * containerCount * (self.sortMode != '添加日期') + 120 + 245) def __removeContainerFromVBoxLayout(self): """ 从竖直布局中移除专辑卡容器 """ for currentGroup_dict in self.currentGroupDict_list: # 将专辑卡从每个网格布局中移除 currentGroup_dict['gridLayout'].removeAllWidgets() self.scrollWidgetVBoxLayout.removeWidget( currentGroup_dict['container']) currentGroup_dict['container'].deleteLater() currentGroup_dict['gridLayout'].deleteLater() self.currentGroupDict_list = [] def __addContainterToVBoxLayout(self): """ 将当前的分组添加到箱式布局中 """ for currentGroup_dict in self.currentGroupDict_list: self.scrollWidgetVBoxLayout.addWidget( currentGroup_dict['container'], 0, Qt.AlignTop) def __addAlbumCardToGridLayout(self): """ 将专辑卡添加到每一个网格布局中 """ for currentGroup_dict in self.currentGroupDict_list: for index, albumCard in enumerate(currentGroup_dict['albumCard_list']): row = index // self.columnNum column = index - row * self.columnNum currentGroup_dict['gridLayout'].addWidget( albumCard, row, column) currentGroup_dict['gridLayout'].setAlignment(Qt.AlignLeft) def sortByAddTime(self): """ 按照添加时间分组 """ self.sortMode = '添加时间' # 创建一个包含所有歌曲卡的网格布局 container = QWidget() gridLayout = GridLayout() gridLayout.setVerticalSpacing(20) gridLayout.setHorizontalSpacing(10) container.setLayout(gridLayout) # 从竖直布局中移除小部件 self.__removeContainerFromVBoxLayout() # 构造一个包含布局和小部件列表字典的列表 self.addTimeGroup_list = [ {'container': container, 'gridLayout': gridLayout, 'albumCard_list': self.albumCard_list}] # 创建一个对当前分组列表引用的列表 self.currentGroupDict_list = self.addTimeGroup_list # 将专辑卡添加到布局中 self.__addAlbumCardToGridLayout() self.__addContainterToVBoxLayout() self.__adjustScrollWidgetSize() def sortByFirstLetter(self): """ 按照专辑名的首字母进行分组排序 """ self.sortMode = 'A到Z' # 将专辑卡从旧布局中移除 self.__removeContainerFromVBoxLayout() # 创建分组 firstLetter_list = [] self.firsetLetterGroupDict_list = [] # 将专辑卡添加到分组中 for albumCard_dict in self.albumCardDict_list: # 获取专辑卡的专辑名首字母(有可能不是字母) firstLetter = albumCard_dict['firstLetter'] firstLetter = firstLetter if 65 <= ord( firstLetter) <= 90 else '...' # 如果首字母属于不在列表中就将创建分组(仅限于A-Z和...) if firstLetter not in firstLetter_list: # firstLetter_list的首字母顺序和firsetLetterGroupDict_list保持一致 firstLetter_list.append(firstLetter) group = GroupBox(firstLetter) gridLayout = GridLayout() group.setLayout(gridLayout) gridLayout.setVerticalSpacing(20) gridLayout.setHorizontalSpacing(10) self.firsetLetterGroupDict_list.append( {'container': group, 'firstLetter': firstLetter, 'gridLayout': gridLayout, 'albumCard_list': []}) # 将专辑卡添加到分组中 index = firstLetter_list.index(firstLetter) self.firsetLetterGroupDict_list[index]['albumCard_list'].append( albumCard_dict['albumCard']) # 排序列表 self.firsetLetterGroupDict_list.sort( key=lambda item: item['firstLetter']) # 将...分组移到最后 if '...' in firstLetter_list: unique_group = self.firsetLetterGroupDict_list.pop(0) self.firsetLetterGroupDict_list.append(unique_group) # 将专辑加到分组的网格布局中 self.currentGroupDict_list = self.firsetLetterGroupDict_list # 将专辑卡添加到网格布局中并将容器添加到竖直布局中 self.__addAlbumCardToGridLayout() self.__addContainterToVBoxLayout() self.__adjustScrollWidgetSize() def sortByYear(self): """ 按照专辑的年份进行分组排序 """ self.sortMode = '发行年份' # 将专辑卡从旧布局中移除 self.__removeContainerFromVBoxLayout() # 创建分组 year_list = [] self.yearGroupDict_list = [] # 将专辑加到分组中 for albumCard_dict in self.albumCardDict_list: year = albumCard_dict['year'] year = '未知' if year == '未知年份' else year # 如果年份不在年份列表中就创建分组 if year not in year_list: year_list.append(year) # 实例化分组和网格布局 group = GroupBox(year) gridLayout = GridLayout() group.setLayout(gridLayout) gridLayout.setVerticalSpacing(20) gridLayout.setHorizontalSpacing(10) self.yearGroupDict_list.append( {'container': group, 'year': year, 'gridLayout': gridLayout, 'albumCard_list': []}) # 将专辑卡添加到分组中 index = year_list.index(year) self.yearGroupDict_list[index]['albumCard_list'].append( albumCard_dict['albumCard']) # 按照年份从进到远排序 self.yearGroupDict_list.sort( key=lambda item: item['year'], reverse=True) # 检测是否含有未知分组,有的话将其移到最后一个 if '未知' in year_list: unique_group = self.yearGroupDict_list.pop(0) self.yearGroupDict_list.append(unique_group) # 将专辑加到分组的网格布局中 self.currentGroupDict_list = self.yearGroupDict_list self.__addAlbumCardToGridLayout() self.__addContainterToVBoxLayout() self.__adjustScrollWidgetSize() def sortBySonger(self): """ 按照专辑的专辑进行分组排序 """ self.sortMode = '歌手' # 将专辑卡从旧布局中移除 self.__removeContainerFromVBoxLayout() # 创建列表 songer_list = [] self.songerGroupDict_list = [] # 将专辑加到分组中 for albumCard_dict in self.albumCardDict_list: songer = albumCard_dict['songer'] if songer not in songer_list: songer_list.append(songer) group = GroupBox(songer) gridLayout = GridLayout() group.setLayout(gridLayout) gridLayout.setVerticalSpacing(20) gridLayout.setHorizontalSpacing(10) self.songerGroupDict_list.append( {'container': group, 'songer': songer, 'gridLayout': gridLayout, 'albumCard_list': []}) # 将专辑卡添加到分组中 index = songer_list.index(songer) self.songerGroupDict_list[index]['albumCard_list'].append( albumCard_dict['albumCard']) # 排序列表 self.songerGroupDict_list.sort(key=lambda item: item['songer'].lower()) # 将专辑加到分组的网格布局中 self.currentGroupDict_list = self.songerGroupDict_list self.__addAlbumCardToGridLayout() self.__addContainterToVBoxLayout() self.__adjustScrollWidgetSize() def __setQss(self): """ 设置层叠样式 """ with open('resource\\css\\albumCardViewer.qss', encoding='utf-8') as f: self.setStyleSheet(f.read()) def findAlbumCardByAlbumInfo(self, albumInfo: dict) -> AlbumCard: """ 通过albumInfo获取对AlbumCard实例的引用 """ for albumCard in self.albumCard_list: if albumCard.albumInfo == albumInfo: return albumCard return None def findAlbumCardByName(self, albumName: str, songerName: str) -> AlbumCard: """ 通过名字查找专辑卡 """ for albumCard in self.albumCard_list: if albumCard.albumInfo['album'] == albumName: # 如果歌手名也相同就直接返回,否则在歌曲列表中寻找 if albumCard.albumInfo['songer'] == songerName: return albumCard else: for songInfo in albumCard.albumInfo['songInfo_list']: if songInfo['songer'] == songerName: return albumCard return None def __albumCardCheckedStateChangedSlot(self, albumCard: AlbumCard, isChecked: bool): """ 专辑卡选中状态改变对应的槽函数 """ # 如果专辑信息不在选中的专辑信息列表中且对应的专辑卡变为选中状态就将专辑信息添加到列表中 if albumCard not in self.checkedAlbumCard_list and isChecked: self.checkedAlbumCard_list.append(albumCard) self.checkedAlbumCardNumChanged.emit( len(self.checkedAlbumCard_list)) # 如果专辑信息已经在列表中且该专辑卡变为非选中状态就弹出该专辑信息 elif albumCard in self.checkedAlbumCard_list and not isChecked: self.checkedAlbumCard_list.pop( self.checkedAlbumCard_list.index(albumCard)) self.checkedAlbumCardNumChanged.emit( len(self.checkedAlbumCard_list)) # 如果先前不处于选择模式那么这次发生选中状态改变就进入选择模式 if not self.isInSelectionMode: # 所有专辑卡进入选择模式 self.__setAllAlbumCardSelectionModeOpen(True) # 发送信号要求主窗口隐藏播放栏 self.selectionModeStateChanged.emit(True) # 更新标志位 self.isInSelectionMode = True else: if not self.checkedAlbumCard_list: # 所有专辑卡退出选择模式 self.__setAllAlbumCardSelectionModeOpen(False) # 发送信号要求主窗口显示播放栏 self.selectionModeStateChanged.emit(False) # 更新标志位 self.isInSelectionMode = False def __setAllAlbumCardSelectionModeOpen(self, isOpenSelectionMode: bool): """ 设置所有专辑卡是否进入选择模式 """ for albumCard in self.albumCard_list: albumCard.setSelectionModeOpen(isOpenSelectionMode) # 退出选择模式时开启隐藏所有复选框的动画 if not isOpenSelectionMode: self.__startHideCheckBoxAni() def __startHideCheckBoxAni(self): """ 开始隐藏复选框动画 """ for ani in self.hideCheckBoxAni_list: ani.setStartValue(1) ani.setEndValue(0) ani.setDuration(140) self.hideCheckBoxAniGroup.start() def __hideAllCheckBox(self): """ 隐藏所有复选框 """ for albumCard in self.albumCard_list: albumCard.checkBox.hide() def unCheckAlbumCards(self): """ 取消所有已处于选中状态的专辑卡的选中状态 """ checkedAlbumCard_list_copy = self.checkedAlbumCard_list.copy() for albumCard in checkedAlbumCard_list_copy: albumCard.setChecked(False) def setAllAlbumCardCheckedState(self, isAllChecked: bool): """ 设置所有的专辑卡checked状态 """ if self.isAllAlbumCardsChecked == isAllChecked: return self.isAllAlbumCardsChecked = isAllChecked for albumCard in self.albumCard_list: albumCard.setChecked(isAllChecked) def __showBlurAlbumBackground(self, pos: QPoint, picPath: str): """ 显示磨砂背景 """ # 将全局坐标转为窗口坐标 pos = self.scrollWidget.mapFromGlobal(pos) self.albumBlurBackground.setBlurAlbum(picPath) self.albumBlurBackground.move(pos.x() - 31, pos.y() - 16) self.albumBlurBackground.show() def updateOneAlbumCardSongInfo(self, newSongInfo: dict): """ 更新一个专辑卡的一首歌的信息 """ for albumCard in self.albumCard_list: albumInfo = albumCard.albumInfo if albumInfo['album'] == newSongInfo['album'] and albumInfo['songer'] == newSongInfo['songer']: for i, songInfo in enumerate(albumInfo['songInfo_list']): if songInfo['songPath'] == newSongInfo['songPath']: albumInfo['songInfo_list'][i] = newSongInfo.copy() return albumInfo return {} def updateAllAlbumCards(self, albumInfo_list: list): """ 更新所有专辑卡 """ oldAlbumInfo_list = [ albumCard.albumInfo for albumCard in self.albumCard_list] if albumInfo_list == oldAlbumInfo_list: return # 将专辑卡从布局中移除 self.__removeContainerFromVBoxLayout() # 根据具体情况增减专辑卡 newCardNum = len(albumInfo_list) oldCardNum = len(self.albumCard_list) deltaNum = newCardNum - oldCardNum if deltaNum < 0: for i in range(oldCardNum - 1, newCardNum - 1, -1): albumCard = self.albumCard_list.pop() self.hideCheckBoxAni_list.pop() self.albumCardDict_list.pop() self.hideCheckBoxAniGroup.takeAnimation(i) albumCard.deleteLater() elif deltaNum > 0: for albumInfo in albumInfo_list[oldCardNum:]: self.__createOneAlbumCard(albumInfo) QApplication.processEvents() # 更新部分专辑卡 self.albumInfo_list = albumInfo_list iterRange = range(oldCardNum) if deltaNum > 0 else range(newCardNum) for i in iterRange: albumInfo = albumInfo_list[i] album = albumInfo['album'] self.albumCard_list[i].updateWindow(albumInfo) QApplication.processEvents() self.albumCardDict_list[i] = {'albumCard': self.albumCard_list[i], 'albumName': album, 'year': albumInfo['year'][:4], 'songer': albumInfo['songer'], 'firstLetter': pinyin.get_initial(album)[0].upper()} # 重新排序专辑卡 self.setSortMode(self.sortMode) # 根据当前专辑卡数决定是否显示导航标签 self.guideLabel.setHidden(bool(albumInfo_list)) if deltaNum != 0: self.albumNumChanged.emit(newCardNum) def setSortMode(self, sortMode: str): """ 排序专辑卡 """ self.sortMode = sortMode if sortMode == '添加时间': self.sortByAddTime() elif sortMode == 'A到Z': self.sortByFirstLetter() elif sortMode == '发行年份': self.sortByYear() elif sortMode == '歌手': self.sortBySonger() else: raise Exception(f'排序依据"{sortMode}"不存在') def showDeleteOneCardPanel(self, albumInfo: dict): """ 显示删除一个专辑卡的对话框 """ title = '是否确定要删除此项?' content = f"""如果删除"{albumInfo['album']}",它将不再位于此设备上。""" deleteCardPanel = DeleteCardPanel(title, content, self.window()) deleteCardPanel.deleteCardSig.connect( lambda: self.__deleteAlbumCards([albumInfo])) deleteCardPanel.exec_() def showDeleteMultiAlbumCardPanel(self, albumInfo_list: list): """ 显示删除多个专辑卡的对话框 """ title = '确定要删除这些项?' content = "如果你删除这些专辑,它们将不再位于此设备上。" deleteCardPanel = DeleteCardPanel(title, content, self.window()) deleteCardPanel.deleteCardSig.connect( lambda: self.__deleteAlbumCards(albumInfo_list)) deleteCardPanel.exec() def __deleteAlbumCards(self, albumInfo_list: list): """ 删除一个专辑卡 """ for albumInfo in albumInfo_list: self.albumInfo_list.remove(albumInfo) self.updateAllAlbumCards(self.albumInfo_list)
def __init__(self, size, parent=None): super(PadNavigator, self).__init__(parent) self.form = Ui_Form() splash = SplashItem() splash.setZValue(1) pad = FlippablePad(size) flipRotation = QGraphicsRotation(pad) xRotation = QGraphicsRotation(pad) yRotation = QGraphicsRotation(pad) flipRotation.setAxis(Qt.YAxis) xRotation.setAxis(Qt.YAxis) yRotation.setAxis(Qt.XAxis) pad.setTransformations([flipRotation, xRotation, yRotation]) backItem = QGraphicsProxyWidget(pad) widget = QWidget() self.form.setupUi(widget) self.form.hostName.setFocus() backItem.setWidget(widget) backItem.setVisible(False) backItem.setFocus() backItem.setCacheMode(QGraphicsItem.ItemCoordinateCache) r = backItem.rect() backItem.setTransform(QTransform().rotate(180, Qt.YAxis).translate( -r.width() / 2, -r.height() / 2)) selectionItem = RoundRectItem(QRectF(-60, -60, 120, 120), QColor(Qt.gray), pad) selectionItem.setZValue(0.5) smoothSplashMove = QPropertyAnimation(splash) smoothSplashOpacity = QPropertyAnimation(splash) smoothSplashMove.setEasingCurve(QEasingCurve.InQuad) smoothSplashMove.setDuration(250) smoothSplashOpacity.setDuration(250) smoothXSelection = QPropertyAnimation(selectionItem) smoothYSelection = QPropertyAnimation(selectionItem) smoothXRotation = QPropertyAnimation(xRotation) smoothYRotation = QPropertyAnimation(yRotation) smoothXSelection.setDuration(125) smoothYSelection.setDuration(125) smoothXRotation.setDuration(125) smoothYRotation.setDuration(125) smoothXSelection.setEasingCurve(QEasingCurve.InOutQuad) smoothYSelection.setEasingCurve(QEasingCurve.InOutQuad) smoothXRotation.setEasingCurve(QEasingCurve.InOutQuad) smoothYRotation.setEasingCurve(QEasingCurve.InOutQuad) smoothFlipRotation = QPropertyAnimation(flipRotation) smoothFlipScale = QPropertyAnimation(pad) smoothFlipXRotation = QPropertyAnimation(xRotation) smoothFlipYRotation = QPropertyAnimation(yRotation) flipAnimation = QParallelAnimationGroup(self) smoothFlipScale.setDuration(500) smoothFlipRotation.setDuration(500) smoothFlipXRotation.setDuration(500) smoothFlipYRotation.setDuration(500) smoothFlipScale.setEasingCurve(QEasingCurve.InOutQuad) smoothFlipRotation.setEasingCurve(QEasingCurve.InOutQuad) smoothFlipXRotation.setEasingCurve(QEasingCurve.InOutQuad) smoothFlipYRotation.setEasingCurve(QEasingCurve.InOutQuad) smoothFlipScale.setKeyValueAt(0, 1.0) smoothFlipScale.setKeyValueAt(0.5, 0.7) smoothFlipScale.setKeyValueAt(1, 1.0) flipAnimation.addAnimation(smoothFlipRotation) flipAnimation.addAnimation(smoothFlipScale) flipAnimation.addAnimation(smoothFlipXRotation) flipAnimation.addAnimation(smoothFlipYRotation) setVariablesSequence = QSequentialAnimationGroup() setFillAnimation = QPropertyAnimation(pad) setBackItemVisibleAnimation = QPropertyAnimation(backItem) setSelectionItemVisibleAnimation = QPropertyAnimation(selectionItem) setFillAnimation.setDuration(0) setBackItemVisibleAnimation.setDuration(0) setSelectionItemVisibleAnimation.setDuration(0) setVariablesSequence.addPause(250) setVariablesSequence.addAnimation(setBackItemVisibleAnimation) setVariablesSequence.addAnimation(setSelectionItemVisibleAnimation) setVariablesSequence.addAnimation(setFillAnimation) flipAnimation.addAnimation(setVariablesSequence) stateMachine = QStateMachine(self) splashState = QState(stateMachine) frontState = QState(stateMachine) historyState = QHistoryState(frontState) backState = QState(stateMachine) frontState.assignProperty(pad, "fill", False) frontState.assignProperty(splash, "opacity", 0.0) frontState.assignProperty(backItem, "visible", False) frontState.assignProperty(flipRotation, "angle", 0.0) frontState.assignProperty(selectionItem, "visible", True) backState.assignProperty(pad, "fill", True) backState.assignProperty(backItem, "visible", True) backState.assignProperty(xRotation, "angle", 0.0) backState.assignProperty(yRotation, "angle", 0.0) backState.assignProperty(flipRotation, "angle", 180.0) backState.assignProperty(selectionItem, "visible", False) stateMachine.addDefaultAnimation(smoothXRotation) stateMachine.addDefaultAnimation(smoothYRotation) stateMachine.addDefaultAnimation(smoothXSelection) stateMachine.addDefaultAnimation(smoothYSelection) stateMachine.setInitialState(splashState) anyKeyTransition = QEventTransition(self, QEvent.KeyPress, splashState) anyKeyTransition.setTargetState(frontState) anyKeyTransition.addAnimation(smoothSplashMove) anyKeyTransition.addAnimation(smoothSplashOpacity) enterTransition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Enter, backState) returnTransition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Return, backState) backEnterTransition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Enter, frontState) backReturnTransition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Return, frontState) enterTransition.setTargetState(historyState) returnTransition.setTargetState(historyState) backEnterTransition.setTargetState(backState) backReturnTransition.setTargetState(backState) enterTransition.addAnimation(flipAnimation) returnTransition.addAnimation(flipAnimation) backEnterTransition.addAnimation(flipAnimation) backReturnTransition.addAnimation(flipAnimation) columns = size.width() rows = size.height() stateGrid = [] for y in range(rows): stateGrid.append([QState(frontState) for _ in range(columns)]) frontState.setInitialState(stateGrid[0][0]) selectionItem.setPos(pad.iconAt(0, 0).pos()) for y in range(rows): for x in range(columns): state = stateGrid[y][x] rightTransition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Right, state) leftTransition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Left, state) downTransition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Down, state) upTransition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Up, state) rightTransition.setTargetState(stateGrid[y][(x + 1) % columns]) leftTransition.setTargetState( stateGrid[y][((x - 1) + columns) % columns]) downTransition.setTargetState(stateGrid[(y + 1) % rows][x]) upTransition.setTargetState(stateGrid[((y - 1) + rows) % rows][x]) icon = pad.iconAt(x, y) state.assignProperty(xRotation, "angle", -icon.x() / 6.0) state.assignProperty(yRotation, "angle", icon.y() / 6.0) state.assignProperty(selectionItem, "x", icon.x()) state.assignProperty(selectionItem, "y", icon.y()) frontState.assignProperty(icon, "visible", True) backState.assignProperty(icon, "visible", False) setIconVisibleAnimation = QPropertyAnimation(icon) setIconVisibleAnimation.setDuration(0) setVariablesSequence.addAnimation(setIconVisibleAnimation) scene = QGraphicsScene(self) scene.setBackgroundBrush( QBrush(QPixmap(":/images/blue_angle_swirl.jpg"))) scene.setItemIndexMethod(QGraphicsScene.NoIndex) scene.addItem(pad) scene.setSceneRect(scene.itemsBoundingRect()) self.setScene(scene) sbr = splash.boundingRect() splash.setPos(-sbr.width() / 2, scene.sceneRect().top() - 2) frontState.assignProperty(splash, "y", splash.y() - 100.0) scene.addItem(splash) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setMinimumSize(50, 50) self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) self.setCacheMode(QGraphicsView.CacheBackground) self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.TextAntialiasing) if QGLFormat.hasOpenGL(): self.setViewport(QGLWidget(QGLFormat(QGL.SampleBuffers))) stateMachine.start()
def __init__(self, size, parent=None): super(PadNavigator, self).__init__(parent) self.form = Ui_Form() splash = SplashItem() splash.setZValue(1) pad = FlippablePad(size) flipRotation = QGraphicsRotation(pad) xRotation = QGraphicsRotation(pad) yRotation = QGraphicsRotation(pad) flipRotation.setAxis(Qt.YAxis) xRotation.setAxis(Qt.YAxis) yRotation.setAxis(Qt.XAxis) pad.setTransformations([flipRotation, xRotation, yRotation]) backItem = QGraphicsProxyWidget(pad) widget = QWidget() self.form.setupUi(widget) self.form.hostName.setFocus() backItem.setWidget(widget) backItem.setVisible(False) backItem.setFocus() backItem.setCacheMode(QGraphicsItem.ItemCoordinateCache) r = backItem.rect() backItem.setTransform(QTransform().rotate(180, Qt.YAxis).translate(-r.width()/2, -r.height()/2)) selectionItem = RoundRectItem(QRectF(-60, -60, 120, 120), QColor(Qt.gray), pad) selectionItem.setZValue(0.5) smoothSplashMove = QPropertyAnimation(splash, b'y') smoothSplashOpacity = QPropertyAnimation(splash, b'opacity') smoothSplashMove.setEasingCurve(QEasingCurve.InQuad) smoothSplashMove.setDuration(250) smoothSplashOpacity.setDuration(250) smoothXSelection = QPropertyAnimation(selectionItem, b'x') smoothYSelection = QPropertyAnimation(selectionItem, b'y') smoothXRotation = QPropertyAnimation(xRotation, b'angle') smoothYRotation = QPropertyAnimation(yRotation, b'angle') smoothXSelection.setDuration(125) smoothYSelection.setDuration(125) smoothXRotation.setDuration(125) smoothYRotation.setDuration(125) smoothXSelection.setEasingCurve(QEasingCurve.InOutQuad) smoothYSelection.setEasingCurve(QEasingCurve.InOutQuad) smoothXRotation.setEasingCurve(QEasingCurve.InOutQuad) smoothYRotation.setEasingCurve(QEasingCurve.InOutQuad) smoothFlipRotation = QPropertyAnimation(flipRotation, b'angle') smoothFlipScale = QPropertyAnimation(pad, b'scale') smoothFlipXRotation = QPropertyAnimation(xRotation, b'angle') smoothFlipYRotation = QPropertyAnimation(yRotation, b'angle') flipAnimation = QParallelAnimationGroup(self) smoothFlipScale.setDuration(500) smoothFlipRotation.setDuration(500) smoothFlipXRotation.setDuration(500) smoothFlipYRotation.setDuration(500) smoothFlipScale.setEasingCurve(QEasingCurve.InOutQuad) smoothFlipRotation.setEasingCurve(QEasingCurve.InOutQuad) smoothFlipXRotation.setEasingCurve(QEasingCurve.InOutQuad) smoothFlipYRotation.setEasingCurve(QEasingCurve.InOutQuad) smoothFlipScale.setKeyValueAt(0, 1.0) smoothFlipScale.setKeyValueAt(0.5, 0.7) smoothFlipScale.setKeyValueAt(1, 1.0) flipAnimation.addAnimation(smoothFlipRotation) flipAnimation.addAnimation(smoothFlipScale) flipAnimation.addAnimation(smoothFlipXRotation) flipAnimation.addAnimation(smoothFlipYRotation) setVariablesSequence = QSequentialAnimationGroup() setFillAnimation = QPropertyAnimation(pad, b'fill') setBackItemVisibleAnimation = QPropertyAnimation(backItem, b'visible') setSelectionItemVisibleAnimation = QPropertyAnimation(selectionItem, b'visible') setFillAnimation.setDuration(0) setBackItemVisibleAnimation.setDuration(0) setSelectionItemVisibleAnimation.setDuration(0) setVariablesSequence.addPause(250) setVariablesSequence.addAnimation(setBackItemVisibleAnimation) setVariablesSequence.addAnimation(setSelectionItemVisibleAnimation) setVariablesSequence.addAnimation(setFillAnimation) flipAnimation.addAnimation(setVariablesSequence) stateMachine = QStateMachine(self) splashState = QState(stateMachine) frontState = QState(stateMachine) historyState = QHistoryState(frontState) backState = QState(stateMachine) frontState.assignProperty(pad, "fill", False) frontState.assignProperty(splash, "opacity", 0.0) frontState.assignProperty(backItem, "visible", False) frontState.assignProperty(flipRotation, "angle", 0.0) frontState.assignProperty(selectionItem, "visible", True) backState.assignProperty(pad, "fill", True) backState.assignProperty(backItem, "visible", True) backState.assignProperty(xRotation, "angle", 0.0) backState.assignProperty(yRotation, "angle", 0.0) backState.assignProperty(flipRotation, "angle", 180.0) backState.assignProperty(selectionItem, "visible", False) stateMachine.addDefaultAnimation(smoothXRotation) stateMachine.addDefaultAnimation(smoothYRotation) stateMachine.addDefaultAnimation(smoothXSelection) stateMachine.addDefaultAnimation(smoothYSelection) stateMachine.setInitialState(splashState) anyKeyTransition = QEventTransition(self, QEvent.KeyPress, splashState) anyKeyTransition.setTargetState(frontState) anyKeyTransition.addAnimation(smoothSplashMove) anyKeyTransition.addAnimation(smoothSplashOpacity) enterTransition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Enter, backState) returnTransition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Return, backState) backEnterTransition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Enter, frontState) backReturnTransition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Return, frontState) enterTransition.setTargetState(historyState) returnTransition.setTargetState(historyState) backEnterTransition.setTargetState(backState) backReturnTransition.setTargetState(backState) enterTransition.addAnimation(flipAnimation) returnTransition.addAnimation(flipAnimation) backEnterTransition.addAnimation(flipAnimation) backReturnTransition.addAnimation(flipAnimation) columns = size.width() rows = size.height() stateGrid = [] for y in range(rows): stateGrid.append([QState(frontState) for _ in range(columns)]) frontState.setInitialState(stateGrid[0][0]) selectionItem.setPos(pad.iconAt(0, 0).pos()) for y in range(rows): for x in range(columns): state = stateGrid[y][x] rightTransition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Right, state) leftTransition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Left, state) downTransition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Down, state) upTransition = QKeyEventTransition(self, QEvent.KeyPress, Qt.Key_Up, state) rightTransition.setTargetState(stateGrid[y][(x + 1) % columns]) leftTransition.setTargetState(stateGrid[y][((x - 1) + columns) % columns]) downTransition.setTargetState(stateGrid[(y + 1) % rows][x]) upTransition.setTargetState(stateGrid[((y - 1) + rows) % rows][x]) icon = pad.iconAt(x, y) state.assignProperty(xRotation, "angle", -icon.x() / 6.0) state.assignProperty(yRotation, "angle", icon.y() / 6.0) state.assignProperty(selectionItem, "x", icon.x()) state.assignProperty(selectionItem, "y", icon.y()) frontState.assignProperty(icon, "visible", True) backState.assignProperty(icon, "visible", False) setIconVisibleAnimation = QPropertyAnimation(icon, b'visible') setIconVisibleAnimation.setDuration(0) setVariablesSequence.addAnimation(setIconVisibleAnimation) scene = QGraphicsScene(self) scene.setBackgroundBrush(QBrush(QPixmap(":/images/blue_angle_swirl.jpg"))) scene.setItemIndexMethod(QGraphicsScene.NoIndex) scene.addItem(pad) scene.setSceneRect(scene.itemsBoundingRect()) self.setScene(scene) sbr = splash.boundingRect() splash.setPos(-sbr.width() / 2, scene.sceneRect().top() - 2) frontState.assignProperty(splash, "y", splash.y() - 100.0) scene.addItem(splash) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setMinimumSize(50, 50) self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) self.setCacheMode(QGraphicsView.CacheBackground) self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.TextAntialiasing) if QGLFormat.hasOpenGL(): self.setViewport(QGLWidget(QGLFormat(QGL.SampleBuffers))) stateMachine.start()
class Spoiler(QWidget): class Orientation(Enum): HORIZONTAL = 1 VERTICAL = 2 def __init__(self, orientation=Orientation.HORIZONTAL, animationDuration=120, parent=None): QWidget.__init__(self, parent) self.opened = False self.orientation = orientation self.animationDuration = animationDuration self.mainLayout = None self.animator = QParallelAnimationGroup() if orientation is self.Orientation.HORIZONTAL: self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setMaximumWidth(0) self.setMinimumWidth(0) # let the entire widget grow and shrink with its content self.animator.addAnimation(QPropertyAnimation(self)) self.animator.addAnimation( QPropertyAnimation(self, b"minimumWidth")) self.animator.addAnimation( QPropertyAnimation(self, b"maximumWidth")) elif orientation is self.Orientation.VERTICAL: self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setMaximumHeight(0) self.setMinimumHeight(0) # let the entire widget grow and shrink with its content self.animator.addAnimation(QPropertyAnimation(self)) self.animator.addAnimation( QPropertyAnimation(self, b"minimumHeight")) self.animator.addAnimation( QPropertyAnimation(self, b"maximumHeight")) def open(self): self.animator.setDirection(QAbstractAnimation.Forward) self.animator.start() self.opened = True def close(self): self.animator.setDirection(QAbstractAnimation.Backward) self.animator.start() self.opened = False def isOpened(self): return self.opened def setContentLayout(self, contentLayout): self.setLayout(contentLayout) if self.orientation is self.Orientation.HORIZONTAL: collapsedSize = self.maximumWidth() contentSize = contentLayout.sizeHint().width() elif self.orientation is self.Orientation.VERTICAL: collapsedSize = self.maximumHeight() contentSize = contentLayout.sizeHint().height() i = 0 while i < self.animator.animationCount(): animation = self.animator.animationAt(i) animation.setDuration(self.animationDuration) animation.setStartValue(collapsedSize) animation.setEndValue(collapsedSize + contentSize) i += 1
class AngleSet(QWidget): acted = pyqtSignal(int, BtnSource) def __init__(self, source, parent=None): super().__init__(parent) self.source = source self.active = 0 self.is_limited = False self.limit = 4 if self.source is BtnSource.HUNDREDS else 10 self.setMinimumSize(100, 105 + self.limit * 50) self.anim_gp = QParallelAnimationGroup() self.anim_gp.finished.connect(self.post_animation) y_offset = 55 self.digits = [] self.digits_pos = [idx for idx in range(self.limit)] for digit in range(self.limit): tmp_btn = AngleButton(digit, self) tmp_btn.move(0, y_offset) y_offset = y_offset + 50 tmp_btn.pressed.connect(self.switch_active) self.digits.append(tmp_btn) @pyqtSlot(int) def switch_active(self, target=None): if target is None: target = -1 for index in range(len(self.digits)): self.active = target self.digits[index].activate(False) return for index in range(len(self.digits)): cur = self.digits[index] if cur.index == self.active: cur.activate(False) if cur.index == target: cur.activate(True) self.active = target self.acted.emit(self.active, self.source) self.anim_set(target) def anim_set(self, target): self.anims = [] self.anim_gp.clear() diff = self.digits_pos.index(target) if not diff: return positions = self.digits_pos[diff:] + self.digits_pos[:diff] duration = diff * 50 * 1.5 self.loopers = [] y_offset = 55 + self.limit * 50 for position in positions: digit = self.digits[position] new_pos = positions.index(digit.index) cur_pos = self.digits_pos.index(digit.index) if new_pos > cur_pos: dummy = DummyButton(digit.index, self.limit, self) dummy.move(0, y_offset) y_offset = y_offset + 50 dummy.show() self.loopers.append(dummy) anim = QPropertyAnimation(dummy, b"pos", dummy) anim.setStartValue(dummy.pos()) anim.setEndValue( QPoint(dummy.pos().x(), dummy.pos().y() - diff * 50)) self.anims.append(anim) anim = QPropertyAnimation(digit, b"pos", digit) anim.setStartValue(digit.pos()) anim.setEndValue( QPoint(digit.pos().x(), digit.pos().y() - diff * 50)) self.anims.append(anim) digit.disable(True) for anim in self.anims: anim.setDuration(duration) anim.setEasingCurve(QEasingCurve.OutQuad) self.anim_gp.addAnimation(anim) self.anim_gp.start() self.digits_pos = positions @pyqtSlot() def post_animation(self): for looper in self.loopers: self.digits[looper.index].move(looper.pos()) looper.setParent(None) for digit in self.digits: digit.disable(False) self.parent().rectify() def reset(self): for digit in self.digits: digit.activate(False) self.active = -1 self.anim_set(0)
class Expander(QWidget): expanded = pyqtSignal(object) clicked = pyqtSignal() def __init__(self, header, svg): QWidget.__init__(self) self.svg = svg self.is_expanded = False self.text = header self.icon = QLabel() self.hyper = QLabel() self.setColors() self.setCursor(Qt.PointingHandCursor) self.setAttribute(Qt.WA_Hover, True) self.setAutoFillBackground(True) vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(self.icon) hbox.addSpacing(5) hbox.addWidget(self.hyper) hbox.addStretch() hbox.setContentsMargins(4, 4, 4, 4) vbox.addLayout(hbox) self.content = QWidget() self.content.setStyleSheet("background-color: " + self.palette().base().color().name()) self.content.setMaximumHeight(0) vbox.addWidget(self.content) vbox.setContentsMargins(0, 0, 0, 0) self.setLayout(vbox) self.hyper.linkActivated.connect(self.buttonClicked) self.anim = QParallelAnimationGroup() self.height_anim = QPropertyAnimation(self.content, "maximumHeight".encode("utf-8")) self.color_anim = QPropertyAnimation(self, "color".encode("utf-8")) self.height_anim.setDuration(200) self.color_anim.setDuration(200) self.anim.addAnimation(self.height_anim) self.anim.addAnimation(self.color_anim) def setColors(self): self.label_normal_color = self.palette().link().color().name() self.label_hovered_color = self.palette().highlight().color().name() self.label_selected_color = self.palette().highlightedText().color( ).name() self.normal_color = self.palette().base().color().name() self.selected_color = self.palette().highlight().color() self.hovered_color = self.palette().alternateBase().color() self.normal_icon = QPixmap(self.createIcon(self.svg, self.normal_color)) self.hovered_icon = QPixmap( self.createIcon(self.svg, self.label_hovered_color)) self.selected_icon = QPixmap( self.createIcon(self.svg, self.label_hovered_color)) self.icon.setPixmap(self.normal_icon) self.color = self.normal_color self.hyper.setText("<a style=\"color: " + self.label_normal_color + " text-decoration: none\" href=\"#\">" + self.text + "</a>") def createIcon(self, source, hilite_color): temp = QDir.tempPath() file = QFile(source) file.open(QIODevice.ReadOnly | QIODevice.Text) data = str(file.readAll(), encoding="utf-8") file.close() out = os.path.join(temp, hilite_color + ".svg") with open(out, "w") as fp: fp.write(data.replace("#ff00ff", hilite_color)) return out def setExpanded(self, value): if value == self.is_expanded: return if value: self.is_expanded = True pal = self.palette() pal.setColor(QPalette.Background, self.selected_color) self.setPalette(pal) self.icon.setPixmap(self.selected_icon) self.hyper.setText("<a style=\"color: " + self.label_selected_color + "; text-decoration: none;\" href=\"#\">" + self.text + "</a>") else: self.is_expanded = False pal = self.palette() pal.setColor(QPalette.Background, QColor(self.normal_color)) self.setPalette(pal) self.icon.setPixmap(self.normal_icon) self.hyper.setText("<a style=\"color: " + self.label_normal_color + "; text-decoration: none;\" href=\"#\">" + self.text + "</a>") if self.is_expanded: self.expandContent() else: self.collapseContent() self.expanded.emit(self.is_expanded) def addLayout(self, layout): self.content.setLayout(layout) @pyqtProperty('QColor') def color(self): return Qt.black @color.setter def color(self, color): pal = self.palette() pal.setColor(QPalette.Background, QColor(color)) self.setPalette(pal) def mouseReleaseEvent(self, me): if me.button() != Qt.LeftButton or me.y() > 32: return self.setExpanded(not self.is_expanded) if self.is_expanded: self.clicked.emit() def expandContent(self): if self.content.layout(): self.height_anim.setEndValue( self.content.layout().sizeHint().height()) else: self.height_anim.setEndValue(0) self.height_anim.setStartValue(0) self.color_anim.setStartValue(self.normal_color) self.color_anim.setEndValue(self.selected_color) self.anim.start() def collapseContent(self): if self.content.layout(): self.height_anim.setStartValue( self.content.layout().sizeHint().height()) else: self.height_anim.setStartValue(0) self.height_anim.setEndValue(0) self.color_anim.setStartValue(self.selected_color) self.color_anim.setEndValue(self.normal_color) self.anim.start() def buttonClicked(self): self.setExpanded(not self.is_expanded) if self.is_expanded: self.clicked.emit() def enterEvent(self, event): if not self.is_expanded: pal = self.palette() pal.setColor(QPalette.Background, QColor(self.hovered_color)) self.setPalette(pal) self.icon.setPixmap(self.hovered_icon) self.hyper.setText("<a style=\"color: " + self.label_hovered_color + "; text-decoration: none;\" href=\"#\">" + self.text + "</a>") QWidget.enterEvent(self, event) def leaveEvent(self, event): if not self.is_expanded: pal = self.palette() pal.setColor(QPalette.Background, QColor(self.normal_color)) self.setPalette(pal) self.icon.setPixmap(self.normal_icon) self.hyper.setText("<a style=\"color: " + self.label_normal_color + "; text-decoration: none;\" href=\"#\">" + self.text + "</a>") QWidget.leaveEvent(self, event)
state6 = createGeometryState(button1, QRect(50, 50, 50, 50), button2, QRect(200, 50, 50, 50), button3, QRect(50, 200, 50, 50), button4, QRect(200, 200, 50, 50), group) state7 = createGeometryState(button1, QRect(0, 0, 50, 50), button2, QRect(250, 0, 50, 50), button3, QRect(0, 250, 50, 50), button4, QRect(250, 250, 50, 50), group) group.setInitialState(state1) animationGroup = QParallelAnimationGroup() anim = QPropertyAnimation(button4, 'geometry') anim.setDuration(1000) anim.setEasingCurve(QEasingCurve.OutElastic) animationGroup.addAnimation(anim) subGroup = QSequentialAnimationGroup(animationGroup) subGroup.addPause(100) anim = QPropertyAnimation(button3, 'geometry') anim.setDuration(1000) anim.setEasingCurve(QEasingCurve.OutElastic) subGroup.addAnimation(anim) subGroup = QSequentialAnimationGroup(animationGroup) subGroup.addPause(150) anim = QPropertyAnimation(button2, 'geometry') anim.setDuration(1000) anim.setEasingCurve(QEasingCurve.OutElastic) subGroup.addAnimation(anim)
class SlidingStackedWidget(QStackedWidget): animationEasingCurve = QtDynamicProperty('animationEasingCurve', int) animationDuration = QtDynamicProperty('animationDuration', int) verticalMode = QtDynamicProperty('verticalMode', bool) wrap = QtDynamicProperty('wrap', bool) animationFinished = pyqtSignal() LeftToRight, RightToLeft, TopToBottom, BottomToTop, Automatic = list(range(5)) def __init__(self, parent=None): super(SlidingStackedWidget, self).__init__(parent) self.animationEasingCurve = QEasingCurve.Linear self.animationDuration = 250 self.verticalMode = False self.wrap = False self._active = False self._animation_group = QParallelAnimationGroup() self._animation_group.finished.connect(self._SH_AnimationGroupFinished) def slideInNext(self): next_index = self.currentIndex() + 1 if self.wrap or next_index < self.count(): self.slideInIndex(next_index % self.count(), direction=self.BottomToTop if self.verticalMode else self.RightToLeft) def slideInPrev(self): previous_index = self.currentIndex() - 1 if self.wrap or previous_index >= 0: self.slideInIndex(previous_index % self.count(), direction=self.TopToBottom if self.verticalMode else self.LeftToRight) def slideInIndex(self, index, direction=Automatic): self.slideInWidget(self.widget(index), direction) def slideInWidget(self, widget, direction=Automatic): if self.indexOf(widget) == -1 or widget is self.currentWidget(): return if self._active: return self._active = True prev_widget = self.currentWidget() next_widget = widget if direction == self.Automatic: if self.indexOf(prev_widget) < self.indexOf(next_widget): direction = self.BottomToTop if self.verticalMode else self.RightToLeft else: direction = self.TopToBottom if self.verticalMode else self.LeftToRight width = self.frameRect().width() height = self.frameRect().height() # the following is important, to ensure that the new widget has correct geometry information when sliding in the first time next_widget.setGeometry(0, 0, width, height) if direction in (self.TopToBottom, self.BottomToTop): offset = QPoint(0, height if direction == self.TopToBottom else -height) elif direction in (self.LeftToRight, self.RightToLeft): offset = QPoint(width if direction == self.LeftToRight else -width, 0) # re-position the next widget outside of the display area prev_widget_position = prev_widget.pos() next_widget_position = next_widget.pos() next_widget.move(next_widget_position - offset) next_widget.show() next_widget.raise_() prev_widget_animation = QPropertyAnimation(prev_widget, b"pos") prev_widget_animation.setDuration(self.animationDuration) prev_widget_animation.setEasingCurve(QEasingCurve(self.animationEasingCurve)) prev_widget_animation.setStartValue(prev_widget_position) prev_widget_animation.setEndValue(prev_widget_position + offset) next_widget_animation = QPropertyAnimation(next_widget, b"pos") next_widget_animation.setDuration(self.animationDuration) next_widget_animation.setEasingCurve(QEasingCurve(self.animationEasingCurve)) next_widget_animation.setStartValue(next_widget_position - offset) next_widget_animation.setEndValue(next_widget_position) self._animation_group.clear() self._animation_group.addAnimation(prev_widget_animation) self._animation_group.addAnimation(next_widget_animation) self._animation_group.start() def _SH_AnimationGroupFinished(self): prev_widget_animation = self._animation_group.animationAt(0) next_widget_animation = self._animation_group.animationAt(1) prev_widget = prev_widget_animation.targetObject() next_widget = next_widget_animation.targetObject() self.setCurrentWidget(next_widget) prev_widget.hide() # this may have been done already by QStackedWidget when changing the current widget above -Dan prev_widget.move(prev_widget_animation.startValue()) # move the out-shifted widget back to its original position self._animation_group.clear() self._active = False self.animationFinished.emit()