Example #1
0
class SongInfoCard(QWidget):
    """ 歌曲信息卡 """

    showPlayBarSignal = pyqtSignal()
    hidePlayBarSignal = pyqtSignal()
    switchToAlbumInterfaceSig = pyqtSignal(str, str)

    def __init__(self, parent=None, songInfo: dict = None):
        super().__init__(parent)
        self.setSongInfo(songInfo)
        self.timer = QTimer(self)
        self.albumCoverLabel = QLabel(self)
        self.songNameLabel = ClickableLabel(parent=self)
        self.songerAlbumLabel = ClickableLabel(parent=self)
        # 初始化标志位
        self.isPlayBarVisible = False
        # 初始化
        self.__initWidget()

    def __initWidget(self):
        """ 初始化小部件 """
        self.resize(1307, 136)
        self.setFixedHeight(136)
        self.setMinimumWidth(400)
        self.albumCoverLabel.move(30, 0)
        self.albumCoverLabel.setFixedSize(136, 136)
        self.setAttribute(Qt.WA_TranslucentBackground)
        self.updateCard(self.songInfo)
        # 初始化定时器
        self.timer.setInterval(3000)
        self.timer.timeout.connect(self.timerSlot)
        # 分配ID
        self.songNameLabel.setObjectName("songNameLabel")
        self.songerAlbumLabel.setObjectName("songerAlbumLabel")
        # 设置属性
        self.songNameLabel.setProperty("state", "normal")
        self.songerAlbumLabel.setProperty("state", "normal")
        # self.__initLayout()
        self.__setQss()
        # 安装事件过滤器
        self.songNameLabel.installEventFilter(self)
        self.songerAlbumLabel.installEventFilter(self)
        # 信号连接到槽
        def slot():
            return self.switchToAlbumInterfaceSig.emit(self.album, self.songerName)

        self.songNameLabel.clicked.connect(slot)
        self.songerAlbumLabel.clicked.connect(slot)

    def __initLayout(self):
        """ 初始化布局 """
        self.songNameLabel.move(186, 30)
        self.songerAlbumLabel.move(186, 82)

    def setSongInfo(self, songInfo: dict):
        """ 设置歌曲信息 """
        self.songInfo = songInfo
        if not self.songInfo:
            self.songInfo = {}
        self.album = self.songInfo.get("album", "未知专辑")
        self.songName = self.songInfo.get("songName", "未知歌名")
        self.songerName = self.songInfo.get("songer", "未知歌手")
        self.albumCoverPath = getCoverPath(self.songInfo.get("modifiedAlbum"))

    def updateCard(self, songInfo: dict):
        """ 更新歌曲信息卡 """
        self.setSongInfo(songInfo)
        self.songNameLabel.setText(self.songName)
        self.songerAlbumLabel.setText(self.songerName + " • " + self.album)
        self.albumCoverLabel.setPixmap(
            QPixmap(self.albumCoverPath).scaled(
                136, 136, Qt.KeepAspectRatio, Qt.SmoothTransformation
            )
        )
        # 调整文本
        self.adjustText()

    def __setQss(self):
        """ 设置层叠样式 """
        with open(
            r"app\resource\css\playInterfaceSongInfoCard.qss", encoding="utf-8"
        ) as f:
            self.setStyleSheet(f.read())

    def eventFilter(self, obj, e: QEvent):
        """ 重写事件过滤器 """
        if obj in [self.songNameLabel, self.songerAlbumLabel]:
            if e.type() == QEvent.MouseButtonPress:
                self.songNameLabel.setProperty("state", "pressed")
                self.songerAlbumLabel.setProperty("state", "pressed")
                self.setStyle(QApplication.style())
            elif e.type() == QEvent.Enter:
                self.songNameLabel.setProperty("state", "hover")
                self.songerAlbumLabel.setProperty("state", "hover")
                self.setStyle(QApplication.style())
            elif e.type() in [QEvent.MouseButtonRelease, QEvent.Leave]:
                self.songNameLabel.setProperty("state", "normal")
                self.songerAlbumLabel.setProperty("state", "normal")
                self.setStyle(QApplication.style())
        return super().eventFilter(obj, e)

    def enterEvent(self, e):
        """ 鼠标进入时打开计时器并显示播放栏 """
        if not self.isPlayBarVisible:
            if not self.timer.isActive():
                # 显示播放栏
                self.timer.start()
                self.showPlayBarSignal.emit()
            else:
                # 重置定时器
                self.timer.stop()
                self.timer.start()

    def timerSlot(self):
        """ 定时器溢出时隐藏播放栏 """
        self.timer.stop()
        self.hidePlayBarSignal.emit()

    def adjustText(self):
        """ 根据文本长度决定是否插入换行符 """
        maxWidth = self.width() - 232
        # 设置专辑名歌手名标签的长度
        fontMetrics = QFontMetrics(QFont("Microsoft YaHei", 13))
        songerAlbumWidth = sum(
            [fontMetrics.width(i) for i in self.songerAlbumLabel.text()]
        )
        self.songerAlbumLabel.setFixedWidth(min(maxWidth, songerAlbumWidth))
        # 调整专辑名标签
        fontMetrics = QFontMetrics(QFont("Microsoft YaHei", 21, 75))
        newSongName_list = list(self.songName)  # type:list
        totalWidth = 0
        isWrap = False
        for i in range(len(self.songName)):
            totalWidth += fontMetrics.width(self.songName[i])
            if totalWidth > maxWidth:
                newSongName_list.insert(i, "\n")
                isWrap = True
                break
        if isWrap:
            self.songNameLabel.setText("".join(newSongName_list))
            self.songNameLabel.move(186, 6)
            self.songerAlbumLabel.move(186, 101)
            self.songNameLabel.setFixedSize(maxWidth, 83)
        else:
            self.songNameLabel.move(186, 26)
            self.songerAlbumLabel.move(186, 82)
            self.songNameLabel.setFixedSize(totalWidth, 46)
            self.songNameLabel.setText(self.songName)
Example #2
0
class SongCard(QWidget):
    """ 歌曲卡 """

    clicked = pyqtSignal(int)
    switchToAlbumInterfaceSig = pyqtSignal(str, str)  # 发送专辑名和歌手名

    def __init__(self, songInfo: dict, parent=None):
        super().__init__(parent)
        self.__getInfo(songInfo)
        self.__resizeTime = 0
        # 记录播放状态
        self.isPlaying = False
        self.__currentState = "leave-notPlay"
        # 记录下每个小部件所占的最大宽度
        self.__maxSongNameCardWidth = 420
        self.__maxSongerLabelWidth = 284
        self.__maxAlbumLabelWidth = 284
        # 记录songCard对应的item的下标
        self.itemIndex = None
        # 创建小部件
        self.songNameCard = SongNameCard(songInfo["songName"], self)
        self.songerLabel = ClickableLabel(songInfo["songer"], self, False)
        self.albumLabel = ClickableLabel(songInfo["album"], self, False)
        self.yearLabel = QLabel(songInfo["year"], self)
        self.durationLabel = QLabel(songInfo["duration"], self)
        self.buttonGroup = self.songNameCard.buttonGroup
        self.playButton = self.songNameCard.playButton
        self.addToButton = self.songNameCard.addToButton
        self.__label_list = [
            self.songerLabel,
            self.albumLabel,
            self.yearLabel,
            self.durationLabel,
        ]
        # 创建动画
        self.__createAnimations()
        # 初始化
        self.__initWidget()

    def __initWidget(self):
        """ 初始化小部件 """
        self.__getLabelWidth()
        self.resize(1234, 60)
        self.resize(1234, 60)
        self.setFixedHeight(60)
        self.albumLabel.setCursor(Qt.PointingHandCursor)
        self.songerLabel.setCursor(Qt.PointingHandCursor)
        self.setAttribute(Qt.WA_StyledBackground)
        # 分配ID和属性
        self.setObjectName("songCard")
        self.albumLabel.setObjectName("clickableLabel")
        self.songerLabel.setObjectName("clickableLabel")
        self.setDynamicProperty(self.__currentState)
        # self.__setQss()
        # 安装事件过滤器
        self.installEventFilter(self)
        # 信号连接到槽
        self.playButton.clicked.connect(lambda: self.clicked.emit(self.itemIndex))
        self.albumLabel.clicked.connect(
            lambda: self.switchToAlbumInterfaceSig.emit(
                self.albumLabel.text(), self.songerLabel.text()
            )
        )

    def __setQss(self):
        """ 设置层叠样式 """
        with open(r"app\resource\css\playInterfaceSongCard.qss", encoding="utf-8") as f:
            self.setStyleSheet(f.read())

    def __getLabelWidth(self):
        """ 计算标签的长度 """
        fontMetrics = QFontMetrics(QFont("Microsoft YaHei", 9))
        self.songerWidth = fontMetrics.width(self.songInfo["songer"])
        self.albumWidth = fontMetrics.width(self.songInfo["album"])

    def setDynamicProperty(self, state: str):
        """ 设置动态属性,总共4状态,分别为enter-notPlay、enter-play、leave-notPlay、leave-play """
        self.yearLabel.setProperty("state", state)
        self.albumLabel.setProperty("state", state)
        self.songerLabel.setProperty("state", state)
        self.durationLabel.setProperty("state", state)
        if state.endswith("play"):
            self.isPlaying = True
            self.songNameCard.setPlay(True)
        if state.startswith("enter"):
            self.setProperty("state", "hover")
            self.songNameCard.buttonGroup.setProperty("state", "hover")
        else:
            self.setProperty("state", "leave")
            self.songNameCard.buttonGroup.setProperty("state", "leave")
        self.setStyle(QApplication.style())

    def resizeEvent(self, e):
        """ 改变窗口大小时移动标签 """
        # super().resizeEvent(e)
        self.__resizeTime += 1
        if self.__resizeTime == 1:
            self.originalWidth = self.width()
            width = self.width() - 246
            # 计算各个标签所占的最大宽度
            self.__maxSongNameCardWidth = int(42 / 99 * width)
            self.__maxSongerLabelWidth = int((width - self.__maxSongNameCardWidth) / 2)
            self.__maxAlbumLabelWidth = self.__maxSongerLabelWidth
            # 如果实际尺寸大于可分配尺寸,就调整大小
            self.__adjustWidgetWidth()
        elif self.__resizeTime > 1:
            deltaWidth = self.width() - self.originalWidth
            self.originalWidth = self.width()
            # 分配多出来的宽度
            threeEqualWidth = int(deltaWidth / 3)
            self.__maxSongNameCardWidth += threeEqualWidth
            self.__maxSongerLabelWidth += threeEqualWidth
            self.__maxAlbumLabelWidth += deltaWidth - 2 * threeEqualWidth
            self.__adjustWidgetWidth()
        # 移动标签
        self.durationLabel.move(self.width() - 45, 20)
        self.yearLabel.move(self.width() - 190, 20)
        self.songerLabel.move(self.__maxSongNameCardWidth + 26, 20)
        self.albumLabel.move(self.songerLabel.x() + self.__maxSongerLabelWidth + 15, 20)
        # 更新动画目标移动位置
        self.__getAniTargetX_list()

    def eventFilter(self, obj, e: QEvent):
        """ 更新样式 """
        if obj == self:
            if e.type() in [QEvent.Enter, QEvent.MouseButtonRelease]:
                self.songNameCard.setWidgetHidden(False)
                state = "enter-play" if self.isPlaying else "enter-notPlay"
                self.setDynamicProperty(state)
            elif e.type() == QEvent.Leave:
                self.songNameCard.setWidgetHidden(True)
                state = "leave-play" if self.isPlaying else "leave-notPlay"
                self.setDynamicProperty(state)
            elif e.type() == QEvent.MouseButtonPress:
                self.setProperty("state", "pressed")
                self.songNameCard.buttonGroup.setProperty("state", "pressed")
                self.setStyle(QApplication.style())
        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:
            if not self.isPlaying:
                self.aniGroup.finished.connect(self.aniFinishedSlot)

    def aniFinishedSlot(self):
        """ 动画完成时更新样式 """
        # 发送点击信号
        self.setDynamicProperty("enter-play")
        self.songNameCard.checkBox.hide()
        self.clicked.emit(self.itemIndex)
        # 动画完成后需要断开连接,为下一次样式更新做准备
        self.aniGroup.disconnect()

    def __adjustWidgetWidth(self):
        """ 调整小部件宽度 """
        # if self.songNameCard.songNameWidth + 41 > self.__maxSongNameCardWidth:
        self.songNameCard.resize(self.__maxSongNameCardWidth, 60)
        if self.songerWidth > self.__maxSongerLabelWidth:
            self.songerLabel.setFixedWidth(self.__maxSongerLabelWidth)
        else:
            self.songerLabel.setFixedWidth(self.songerWidth)
        if self.albumWidth > self.__maxAlbumLabelWidth:
            self.albumLabel.setFixedWidth(self.__maxAlbumLabelWidth)
        else:
            self.albumLabel.setFixedWidth(self.albumWidth)

    def __createAnimations(self):
        """ 创建动画 """
        self.aniGroup = QParallelAnimationGroup(self)
        self.__deltaX_list = [13, 5, -3, -11, -13]
        self.__aniWidget_list = [
            self.songNameCard,
            self.songerLabel,
            self.albumLabel,
            self.yearLabel,
            self.durationLabel,
        ]
        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 setPlay(self, isPlay: bool):
        """ 设置歌曲卡的播放状态 """
        self.isPlaying = isPlay
        self.songNameCard.setPlay(isPlay)
        if isPlay:
            self.setDynamicProperty("enter-play")
        else:
            self.setDynamicProperty("leave-notPlay")

    def updateSongCard(self, songInfo: dict):
        """ 更新歌曲卡信息 """
        self.resize(self.size())
        self.__getInfo(songInfo)
        self.songNameCard.setSongName(songInfo["songName"])
        self.songerLabel.setText(songInfo["songer"])
        self.albumLabel.setText(songInfo["album"])
        self.yearLabel.setText(songInfo["year"])
        self.durationLabel.setText(songInfo["duration"])
        # 调整宽度
        self.__getLabelWidth()
        songerWidth = (
            self.songerWidth
            if self.songerWidth <= self.__maxSongerLabelWidth
            else self.__maxSongerLabelWidth
        )
        albumWidth = (
            self.albumWidth
            if self.albumWidth <= self.__maxAlbumLabelWidth
            else self.__maxAlbumLabelWidth
        )
        self.songerLabel.setFixedWidth(songerWidth)
        self.albumLabel.setFixedWidth(albumWidth)

    def __getInfo(self, songInfo: dict):
        """ 从歌曲信息中分离信息 """
        self.songInfo = songInfo
        self.year = songInfo["year"]  # type:str
        self.songer = songInfo["songer"]  # type:str
        self.album = songInfo["album"]  # type:str
        self.duration = songInfo["duration"]  # type:str
        self.songName = songInfo["songName"]  # type:str