Exemple #1
0
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)
Exemple #2
0
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()
class QtBubbleLabel(QWidget):
    BackgroundColor = QColor(195, 195, 195)
    BorderColor = QColor(150, 150, 150)

    def __init__(self, *args, **kwargs):
        super(QtBubbleLabel, 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)
        layout.setContentsMargins(8, 8, 8, 16)
        self.label = QLabel(self)
        layout.addWidget(self.label)
        self._desktop = QApplication.instance().desktop()
        self.animationGroup = QParallelAnimationGroup(self)

    def setText(self, text):
        self.label.setText(text)

    def text(self):
        return self.label.text()

    def stop(self):
        self.hide()
        self.animationGroup.stop()
        self.animationGroup.clear()
        self.close()

    def show(self):
        super(QtBubbleLabel, self).show()
        x = self.parent().geometry().x()
        y = self.parent().geometry().y()
        x2 = self.parent().size().width()
        y2 = self.parent().size().height()
        startPos = QPoint(x + int(x2 / 2) - int(self.width() / 2),
                          y + int(y2 / 2))
        endPos = QPoint(x + int(x2 / 2) - int(self.width() / 2),
                        y + int(y2 / 2) - self.height() * 3 - 5)
        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(3000)  # 在4秒的时间内完成
        # 往上移动动画
        moveAnimation = QPropertyAnimation(self, b"pos")
        moveAnimation.setStartValue(startPos)
        moveAnimation.setEndValue(endPos)
        moveAnimation.setEasingCurve(QEasingCurve.InQuad)
        moveAnimation.setDuration(4000)  # 在5秒的时间内完成
        # 并行动画组(目的是让上面的两个动画同时进行)
        self.animationGroup.addAnimation(opacityAnimation)
        self.animationGroup.addAnimation(moveAnimation)
        self.animationGroup.finished.connect(self.close)  # 动画结束时关闭窗口
        self.animationGroup.start()

    def paintEvent(self, event):
        super(QtBubbleLabel, self).paintEvent(event)
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)  # 抗锯齿

        rectPath = QPainterPath()  # 圆角矩形

        height = self.height() - 8  # 往上偏移8
        rectPath.addRoundedRect(QRectF(0, 0, self.width(), height), 5, 5)
        x = self.width() / 5 * 4
        # 边框画笔
        painter.setPen(
            QPen(self.BorderColor, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
        # 背景画刷
        painter.setBrush(self.BackgroundColor)
        # 绘制形状
        painter.drawPath(rectPath)

    def windowOpacity(self):
        return super(QtBubbleLabel, self).windowOpacity()

    def setWindowOpacity(self, opacity):
        super(QtBubbleLabel, self).setWindowOpacity(opacity)

    opacity = pyqtProperty(float, windowOpacity, setWindowOpacity)

    def ShowMsg(self, text):
        self.stop()
        self.setText(text)
        self.setStyleSheet("color:black")
        self.show()

    @staticmethod
    def ShowMsgEx(owner, text):
        data = QtBubbleLabel(owner)
        data.setText(text)
        data.setStyleSheet("color:black")
        data.show()

    def ShowError(self, text):
        self.stop()
        self.setText(text)
        self.setStyleSheet("color:red")
        self.show()

    @staticmethod
    def ShowErrorEx(owner, text):
        data = QtBubbleLabel(owner)
        data.setText(text)
        data.setStyleSheet("color:red")
        data.show()
Exemple #4
0
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
Exemple #5
0
Fichier : ui.py Projet : xmye/games
    class QTile(QGraphicsObject):
        colorMap = {
            Tetris.I: QColor("#53bbf4"),
            Tetris.J: QColor("#e25fb8"),
            Tetris.L: QColor("#ffac00"),
            Tetris.O: QColor("#ecff2e"),
            Tetris.S: QColor("#97eb00"),
            Tetris.T: QColor("#ff85cb"),
            Tetris.Z: QColor("#ff5a48")
        }

        def __init__(self, qTetris: 'QTetris', tetrimino: Tetris.Tetrimino,
                     tile: Tetris.Tile):
            super(QTetris.QTile, self).__init__()
            tile.delegate = self
            self.color = self.colorMap[type(tetrimino)]
            self.qTetris = qTetris
            self.moveAnimation = QParallelAnimationGroup()
            self.dropAnimation = QPropertyAnimation(self, b'pos')
            self.collapseAnimation = QPropertyAnimation(self, b'pos')
            self.shiftAnimation = QPropertyAnimation(self, b'pos')
            self.collapseAnimation.finished.connect(
                lambda tl=tile: tile.delegate.disappeared(tl))
            self.qTetris.scene.addItem(self)
            self.setPos(QPointF(0, 4))
            self.moved(tile)

        def moved(self, tile: Tetris.Tile):
            translation = QPropertyAnimation(self, b'pos')
            start, end = self.pos(), QPointF(tile.row, tile.column)
            curve, speed, delay = QEasingCurve.OutBack, 1 / 50, -1
            self.animate(translation, start, end, curve, speed, delay)

            rotation = QPropertyAnimation(self, b'rotation')
            start, end = self.rotation(), tile.rotation
            curve, speed, delay = QEasingCurve.OutBack, 1, -1
            self.animate(rotation, start, end, curve, speed, delay)
            rotation.setDuration(translation.duration())

            self.moveAnimation.clear()
            self.moveAnimation.addAnimation(translation)
            self.moveAnimation.addAnimation(rotation)
            self.moveAnimation.start()

        def dropped(self, tile: Tetris.Tile):
            start, end = self.pos(), QPointF(tile.row, tile.column)
            curve, speed, delay = QEasingCurve.OutBounce, 1 / 50, 0
            self.animate(self.dropAnimation, start, end, curve, speed, delay)

        def collapsed(self, tile: Tetris.Tile):
            start, end = self.pos(), QPointF(
                tile.row, tile.column + 2 * tile.tetris.num_columns)
            curve, speed, delay = QEasingCurve.InOutExpo, 1 / 50, 800
            if self.dropAnimation.state() == QAbstractAnimation.Running:
                start = self.dropAnimation.endValue()
            self.animate(self.collapseAnimation, start, end, curve, speed,
                         delay)

        def shifted(self, tile: Tetris.Tile):
            start, end = self.pos(), QPointF(tile.row, tile.column)
            curve, speed, delay = QEasingCurve.OutBounce, 1 / 100, 1200
            if self.dropAnimation.state() == QAbstractAnimation.Running:
                start = self.dropAnimation.endValue()
            self.animate(self.shiftAnimation, start, end, curve, speed, delay)

        def disappeared(self, tile: Tetris.Tile):
            self.qTetris.scene.removeItem(self)

        def paint(self,
                  painter: QPainter,
                  styleOption: QStyleOptionGraphicsItem,
                  widget: QWidget = None):
            pen = QPen()
            pen.setWidthF(0.05)
            pen.setColor(Qt.darkGray)
            painter.setPen(pen)
            brush = QBrush()
            brush.setColor(self.color)
            brush.setStyle(Qt.SolidPattern)
            painter.setBrush(brush)
            topLeft = QPointF(0, 0)
            bottomRight = QPointF(1, 1)
            rectangle = QRectF(topLeft, bottomRight)
            rectangle.translate(-0.5, -0.5)
            painter.drawRect(rectangle)

        @staticmethod
        def animate(animation: QPropertyAnimation,
                    start: Union[QPointF, int, float],
                    end: Union[QPointF, int, float],
                    curve: QEasingCurve = QEasingCurve.Linear,
                    speed: float = 1 / 50,
                    delay: int = -1):
            animation.setStartValue(start)
            animation.setEndValue(end)
            animation.setEasingCurve(curve)
            if type(start) == type(end) == QPointF:
                distance = (end - start).manhattanLength()
            else:
                distance = abs(end - start)
            animation.setDuration(round(distance / speed))
            if delay == 0:
                animation.start()
            if delay > 0:
                QTimer.singleShot(delay, animation.start)

        def boundingRect(self):
            topLeft = QPointF(0, 0)
            bottomRight = QPointF(1, 1)
            rectangle = QRectF(topLeft, bottomRight)
            rectangle.translate(-0.5, -0.5)
            return rectangle