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 PlayingInterface(QWidget): """ 正在播放界面 """ nextSongSig = pyqtSignal() # 点击下一首或者上一首按钮时由主界面的播放列表决定下一首的Index lastSongSig = pyqtSignal() switchPlayStateSig = pyqtSignal() randomPlayAllSignal = pyqtSignal() removeMediaSignal = pyqtSignal(int) # 点击歌曲卡或者滑动歌曲信息卡滑槽时直接设置新的index,index由自己决定 currentIndexChanged = pyqtSignal(int) switchToAlbumInterfaceSig = pyqtSignal(str, str) # 发出进入最小模式的信号 smallestModeStateChanged = pyqtSignal(bool) # 退出全屏信号 exitFullScreenSig = pyqtSignal() def __init__(self, playlist: list = None, parent=None): super().__init__(parent) self.playlist = playlist.copy() self.currentIndex = 0 self.isPlaylistVisible = False # 创建小部件 self.blurPixmap = None self.blurBackgroundPic = QLabel(self) self.blurCoverThread = BlurCoverThread(self) self.songInfoCardChute = SongInfoCardChute(self, self.playlist) self.parallelAniGroup = QParallelAnimationGroup(self) self.songInfoCardChuteAni = QPropertyAnimation(self.songInfoCardChute, b"geometry") self.playBar = PlayBar(self) self.songListWidget = SongListWidget(self.playlist, self) self.smallestModeInterface = SmallestPlayModeInterface(playlist, self) self.playBarAni = QPropertyAnimation(self.playBar, b"geometry") self.songListWidgetAni = QPropertyAnimation(self.songListWidget, b"geometry") self.guideLabel = QLabel("在这里,你将看到正在播放的歌曲以及即将播放的歌曲。", self) self.randomPlayAllButton = ThreeStatePushButton( { "normal": r"app\resource\images\playing_interface\全部随机播放_normal.png", "hover": r"app\resource\images\playing_interface\全部随机播放_hover.png", "pressed": r"app\resource\images\playing_interface\全部随机播放_pressed.png", }, " 随机播放你收藏中的所有内容", (30, 22), self) # 创建定时器 self.showPlaylistTimer = QTimer(self) self.hidePlaylistTimer = QTimer(self) # 初始化 self.__initWidget() def __initWidget(self): """ 初始化小部件 """ self.resize(1100, 870) self.currentSmallestModeSize = QSize(340, 340) self.setAttribute(Qt.WA_StyledBackground) self.guideLabel.move(45, 62) self.randomPlayAllButton.move(45, 117) self.playBar.move(0, self.height() - self.playBar.height()) # 隐藏部件 self.smallestModeInterface.hide() self.randomPlayAllButton.hide() self.guideLabel.hide() self.playBar.hide() # 设置层叠样式 self.setObjectName("playingInterface") self.guideLabel.setObjectName("guideLabel") self.randomPlayAllButton.setObjectName("randomPlayAllButton") self.__setQss() # 开启磨砂线程 if self.playlist: self.startBlurThread( self.songInfoCardChute.curSongInfoCard.albumCoverPath) # 将信号连接到槽 self.__connectSignalToSlot() # 初始化动画 self.playBarAni.setDuration(350) self.songListWidgetAni.setDuration(350) self.songListWidgetAni.setEasingCurve(QEasingCurve.InOutQuad) self.playBarAni.setEasingCurve(QEasingCurve.InOutQuad) self.parallelAniGroup.addAnimation(self.playBarAni) self.parallelAniGroup.addAnimation(self.songInfoCardChuteAni) # 初始化定时器 self.showPlaylistTimer.setInterval(120) self.hidePlaylistTimer.setInterval(120) self.showPlaylistTimer.timeout.connect(self.showPlayListTimerSlot) self.hidePlaylistTimer.timeout.connect(self.hidePlayListTimerSlot) def __setQss(self): """ 设置层叠样式 """ with open(r"app\resource\css\playInterface.qss", encoding="utf-8") as f: self.setStyleSheet(f.read()) def setBlurPixmap(self, blurPixmap): """ 设置磨砂pixmap """ self.blurPixmap = blurPixmap # 更新背景 self.__resizeBlurPixmap() def __resizeBlurPixmap(self): """ 调整背景图尺寸 """ maxWidth = max(self.width(), self.height()) if self.blurPixmap: self.blurBackgroundPic.setPixmap( self.blurPixmap.scaled( maxWidth, maxWidth, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation, )) def startBlurThread(self, albumCoverPath): """ 开启磨砂线程 """ blurRadius = [6, 40][self.smallestModeInterface.isVisible()] self.blurCoverThread.setTargetCover(albumCoverPath, blurRadius=blurRadius) self.blurCoverThread.start() def resizeEvent(self, e): """ 改变尺寸时也改变小部件的大小 """ super().resizeEvent(e) self.__resizeBlurPixmap() self.songInfoCardChute.resize(self.size()) self.blurBackgroundPic.setFixedSize(self.size()) self.playBar.resize(self.width(), self.playBar.height()) self.songListWidget.resize(self.width(), self.height() - 382) self.smallestModeInterface.resize(self.size()) if self.isPlaylistVisible: self.playBar.move(0, 190) self.songListWidget.move(0, 382) self.songInfoCardChute.move(0, 258 - self.height()) else: self.playBar.move(0, self.height() - self.playBar.height()) self.songListWidget.move(0, self.height()) def showPlayBar(self): """ 显示播放栏 """ # 只在播放栏不可见的时候显示播放栏和开启动画 if not self.playBar.isVisible(): self.playBar.show() self.songInfoCardChuteAni.setDuration(450) self.songInfoCardChuteAni.setEasingCurve(QEasingCurve.OutCubic) self.songInfoCardChuteAni.setStartValue( self.songInfoCardChute.rect()) self.songInfoCardChuteAni.setEndValue( QRect(0, -self.playBar.height() + 68, self.width(), self.height())) self.songInfoCardChuteAni.start() def hidePlayBar(self): """ 隐藏播放栏 """ if self.playBar.isVisible() and not self.isPlaylistVisible: self.playBar.hide() self.songInfoCardChuteAni.setEasingCurve(QEasingCurve.OutCirc) self.songInfoCardChuteAni.setStartValue( QRect(0, -self.playBar.height() + 68, self.width(), self.height())) self.songInfoCardChuteAni.setEndValue( QRect(0, 0, self.width(), self.height())) self.songInfoCardChuteAni.start() def showPlaylist(self): """ 显示播放列表 """ if self.songListWidgetAni.state() != QAbstractAnimation.Running: self.songInfoCardChuteAni.setDuration(350) self.songInfoCardChuteAni.setEasingCurve(QEasingCurve.InOutQuad) self.songInfoCardChuteAni.setStartValue( QRect(0, self.songInfoCardChute.y(), self.width(), self.height())) self.songInfoCardChuteAni.setEndValue( QRect(0, 258 - self.height(), self.width(), self.height())) self.playBarAni.setStartValue( QRect(0, self.playBar.y(), self.width(), self.playBar.height())) self.playBarAni.setEndValue( QRect(0, 190, self.width(), self.playBar.height())) self.songListWidgetAni.setStartValue( QRect( self.songListWidget.x(), self.songListWidget.y(), self.songListWidget.width(), self.songListWidget.height(), )) self.songListWidgetAni.setEndValue( QRect( self.songListWidget.x(), 382, self.songListWidget.width(), self.songListWidget.height(), )) if self.sender() == self.playBar.showPlaylistButton: self.playBar.pullUpArrowButton.timer.start() self.playBar.show() self.parallelAniGroup.start() self.blurBackgroundPic.hide() self.showPlaylistTimer.start() def showPlayListTimerSlot(self): """ 显示播放列表定时器溢出槽函数 """ self.showPlaylistTimer.stop() self.songListWidgetAni.start() self.isPlaylistVisible = True def hidePlayListTimerSlot(self): """ 显示播放列表定时器溢出槽函数 """ self.hidePlaylistTimer.stop() self.parallelAniGroup.start() def hidePlaylist(self): """ 隐藏播放列表 """ if self.parallelAniGroup.state() != QAbstractAnimation.Running: self.songInfoCardChuteAni.setDuration(350) self.songInfoCardChuteAni.setEasingCurve(QEasingCurve.InOutQuad) self.songInfoCardChuteAni.setStartValue( QRect(0, self.songInfoCardChute.y(), self.width(), self.height())) self.songInfoCardChuteAni.setEndValue( QRect(0, -self.playBar.height() + 68, self.width(), self.height())) self.playBarAni.setStartValue( QRect(0, 190, self.width(), self.playBar.height())) self.playBarAni.setEndValue( QRect( 0, self.height() - self.playBar.height(), self.width(), self.playBar.height(), )) self.songListWidgetAni.setStartValue( QRect( self.songListWidget.x(), self.songListWidget.y(), self.songListWidget.width(), self.songListWidget.height(), )) self.songListWidgetAni.setEndValue( QRect( self.songListWidget.x(), self.height(), self.songListWidget.width(), self.songListWidget.height(), )) if self.sender() == self.playBar.showPlaylistButton: self.playBar.pullUpArrowButton.timer.start() # self.parallelAniGroup.start() self.songListWidgetAni.start() self.hidePlaylistTimer.start() self.blurBackgroundPic.show() self.isPlaylistVisible = False def showPlaylistButtonSlot(self): """ 显示或隐藏播放列表 """ if not self.isPlaylistVisible: self.showPlaylist() else: self.hidePlaylist() def setCurrentIndex(self, index): """ 更新播放列表下标 """ # 下标大于等于0时才更新 if self.currentIndex != index and index > -1: # 在播放列表的最后一首歌被移除时不更新样式 if index >= len(self.playlist): return if self.smallestModeInterface.isVisible(): self.smallestModeInterface.setCurrentIndex(index) self.currentIndex = index self.songListWidget.setCurrentIndex(index) self.songInfoCardChute.setCurrentIndex(index) def setPlaylist(self, playlist: list, isResetIndex: bool = True): """ 更新播放列表 Parameters ---------- playlist: list 播放列表,每一个元素都是songInfo字典 isResetIndex: bool 是否将下标重置为0 """ self.playlist = deepcopy(playlist) self.currentIndex = 0 if isResetIndex else self.currentIndex if playlist: self.songInfoCardChute.setPlaylist(self.playlist, isResetIndex) self.smallestModeInterface.setPlaylist(self.playlist, isResetIndex) self.songListWidget.updateSongCards(self.playlist) # 如果小部件不可见就显示 if playlist and not self.songListWidget.isVisible(): self.__setGuideLabelHidden(True) def __settleDownPlayBar(self): """ 定住播放栏 """ self.songInfoCardChute.stopSongInfoCardTimer() def __startSongInfoCardTimer(self): """ 重新打开歌曲信息卡的定时器 """ if not self.playBar.volumeSlider.isVisible(): # 只有音量滑动条不可见才打开计时器 self.songInfoCardChute.startSongInfoCardTimer() def __songListWidgetCurrentChangedSlot(self, index): """ 歌曲列表当前下标改变插槽 """ self.currentIndex = index self.songInfoCardChute.setCurrentIndex(index) self.currentIndexChanged.emit(index) def __songInfoCardChuteCurrentChangedSlot(self, index): """ 歌曲列表当前下标改变插槽 """ self.currentIndex = index self.songListWidget.setCurrentIndex(index) self.currentIndexChanged.emit(index) def __removeSongFromPlaylist(self, index): """ 从播放列表中移除选中的歌曲 """ lastSongRemoved = False if self.currentIndex > index: self.currentIndex -= 1 self.songInfoCardChute.currentIndex -= 1 elif self.currentIndex == index: # 如果被移除的是最后一首需要将当前下标-1 if index == self.songListWidget.currentIndex + 1: self.currentIndex -= 1 self.songInfoCardChute.currentIndex -= 1 lastSongRemoved = True else: self.songInfoCardChute.setCurrentIndex(self.currentIndex) self.removeMediaSignal.emit(index) # 如果播放列表为空,隐藏小部件 if len(self.playlist) == 0: self.__setGuideLabelHidden(False) # 如果被移除的是最后一首就将当前播放歌曲置为被移除后的播放列表最后一首 """ if lastSongRemoved: self.currentIndexChanged.emit(self.currentIndex) """ def clearPlaylist(self): """ 清空歌曲卡 """ self.playlist.clear() self.songListWidget.clearSongCards() # 显示随机播放所有按钮 self.__setGuideLabelHidden(False) def __setGuideLabelHidden(self, isHidden): """ 设置导航标签和随机播放所有按钮的可见性 """ self.randomPlayAllButton.setHidden(isHidden) self.guideLabel.setHidden(isHidden) self.songListWidget.setHidden(not isHidden) if isHidden: # 隐藏导航标签时根据播放列表是否可见设置磨砂背景和播放栏的可见性 self.blurBackgroundPic.setHidden(self.isPlaylistVisible) self.playBar.setHidden(not self.isPlaylistVisible) else: # 显示导航标签时隐藏磨砂背景 self.blurBackgroundPic.hide() self.playBar.hide() # 最后再显示歌曲信息卡 self.songInfoCardChute.setHidden(not isHidden) def updateOneSongCard(self, oldSongInfo: dict, newSongInfo): """ 更新一个歌曲卡 """ self.songListWidget.updateOneSongCard(oldSongInfo, newSongInfo) self.playlist = self.songListWidget.playlist self.songInfoCardChute.playlist = self.playlist def updateMultiSongCards(self, oldSongInfo_list: list, newSongInfo_list: list): """ 更新多个歌曲卡 """ self.songListWidget.updateMultiSongCards(oldSongInfo_list, newSongInfo_list) self.playlist = self.songListWidget.playlist self.songInfoCardChute.playlist = self.playlist def showSmallestModeInterface(self): """ 显示最小播放模式界面 """ self.exitFullScreenSig.emit() # 记录下正常尺寸 self.currentGeometry = self.window().geometry() # type:QRect # 更新磨砂半径 self.blurCoverThread.setTargetCover( self.blurCoverThread.albumCoverPath, 40, (350, 350)) self.blurCoverThread.start() self.playBar.hide() self.songListWidget.hide() self.songInfoCardChute.hide() self.blurBackgroundPic.show() # 先更新歌曲信息卡再显示界面 self.smallestModeInterface.setCurrentIndex(self.currentIndex) self.smallestModeInterface.show() # 发出隐藏标题栏按钮的信号 self.smallestModeStateChanged.emit(True) self.window().setMinimumSize(206, 197) self.window().setGeometry( self.currentGeometry.x() + self.currentGeometry.width() - self.currentSmallestModeSize.width(), self.currentGeometry.y(), self.currentSmallestModeSize.width(), self.currentSmallestModeSize.height(), ) def __hideSmallestModeInterface(self): """ 隐藏最小播放模式界面 """ # 记录下最小播放模式的尺寸 self.currentSmallestModeSize = self.window().size() # type:QSize # 更新磨砂半径 self.blurCoverThread.setTargetCover( self.blurCoverThread.albumCoverPath, 6, (450, 450)) self.blurCoverThread.start() self.smallestModeInterface.hide() self.window().setMinimumSize(1030, 850) self.window().setGeometry(self.currentGeometry) # 发出显示标题栏按钮的信号 self.smallestModeStateChanged.emit(False) self.blurBackgroundPic.setHidden(self.isPlaylistVisible) self.playBar.show() self.songListWidget.show() self.songInfoCardChute.show() def __connectSignalToSlot(self): """ 将信号连接到槽 """ self.blurCoverThread.blurDone.connect(self.setBlurPixmap) # 更新背景封面和下标 self.songInfoCardChute.currentIndexChanged[int].connect( self.__songInfoCardChuteCurrentChangedSlot) self.songInfoCardChute.currentIndexChanged[str].connect( self.startBlurThread) # 显示和隐藏播放栏 self.songInfoCardChute.showPlayBarSignal.connect(self.showPlayBar) self.songInfoCardChute.hidePlayBarSignal.connect(self.hidePlayBar) # 将播放栏的信号连接到槽 self.playBar.lastSongButton.clicked.connect(self.lastSongSig) self.playBar.nextSongButton.clicked.connect(self.nextSongSig) self.playBar.playButton.clicked.connect(self.switchPlayStateSig) self.playBar.pullUpArrowButton.clicked.connect( self.showPlaylistButtonSlot) self.playBar.showPlaylistButton.clicked.connect( self.showPlaylistButtonSlot) self.playBar.smallPlayModeButton.clicked.connect( self.showSmallestModeInterface) self.playBar.enterSignal.connect(self.__settleDownPlayBar) self.playBar.leaveSignal.connect(self.__startSongInfoCardTimer) # 将歌曲列表的信号连接到槽函数 self.songListWidget.currentIndexChanged.connect( self.__songListWidgetCurrentChangedSlot) self.songListWidget.removeItemSignal.connect( self.__removeSongFromPlaylist) self.randomPlayAllButton.clicked.connect(self.randomPlayAllSignal) # 将最小化播放界面的信号连接到槽函数 self.smallestModeInterface.lastSongButton.clicked.connect( self.lastSongSig) self.smallestModeInterface.nextSongButton.clicked.connect( self.nextSongSig) self.smallestModeInterface.playButton.clicked.connect( self.switchPlayStateSig) self.smallestModeInterface.exitSmallestModeButton.clicked.connect( self.__hideSmallestModeInterface) # 切换到专辑界面 self.songInfoCardChute.switchToAlbumInterfaceSig.connect( self.switchToAlbumInterfaceSig) self.songListWidget.switchToAlbumInterfaceSig.connect( self.switchToAlbumInterfaceSig)
class PopupPost(QMainWindow, Ui_PopupPost): def __init__(self, read_the_weibo): super().__init__( None, Qt.ToolTip | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) self.setupUi(self) self._close_timer = QTimer(self) self._close_timer.setSingleShot(True) self._close_timer.timeout.connect(self.close) self._read_the_weibo = read_the_weibo def setupUi(self, popup_post): super().setupUi(popup_post) self.setAttribute(Qt.WA_TranslucentBackground) desktop = QApplication.desktop() self.move(desktop.width() - 50 - self.width(), desktop.height() - 300 - self.height()) # 因为带浏览器的透明窗口不能渲染,浏览器放在独立窗口 self.content_view = WeiboWebView(self) self.content_view.setWindowOpacity(0) # 淡出淡入动画 self._window_fade_anim = QPropertyAnimation(self, b'windowOpacity', self) self._window_fade_anim.setDuration(300) self._window_fade_anim.setStartValue(0) self._window_fade_anim.setEndValue(0.5) self._content_fade_anim = QPropertyAnimation(self.content_view, b'windowOpacity', self) self._content_fade_anim.setDuration(300) self._content_fade_anim.setStartValue(0) self._content_fade_anim.setEndValue(0.7) self._fade_anim_group = QParallelAnimationGroup(self) self._fade_anim_group.addAnimation(self._window_fade_anim) self._fade_anim_group.addAnimation(self._content_fade_anim) self._fade_anim_group.finished.connect(self._on_anim_finish) def moveEvent(self, event): logger.debug('moveEvent') # 如果在这里更新,content_widget位置和尺寸不对 QTimer.singleShot(0, self._update_content_view_geometry) def resizeEvent(self, event): logger.debug('resizeEvent') # 如果在这里更新,content_widget位置和尺寸不对 QTimer.singleShot(0, self._update_content_view_geometry) def _update_content_view_geometry(self): pos = self.mapToGlobal(self.content_widget.pos()) size = self.content_widget.size() logger.debug('content_widget: (%d, %d) %d x %d', pos.x(), pos.y(), size.width(), size.height()) self.content_view.setGeometry(pos.x(), pos.y(), size.width(), size.height()) def show_post(self, post): """ 显示一条微博 :param post: 微博 """ self.show() self.content_view.show_post(post) # 淡入 self._fade_anim_group.setDirection(QPropertyAnimation.Forward) self._fade_anim_group.start() # 如果只弹窗不发声则过一段时间自动关闭 if not self._read_the_weibo.speak_post: content_len = len(post.content) if post.is_repost: content_len += len(post.original_post.content) self._close_timer.start(int((3 + content_len * 0.1) * 1000)) def closeEvent(self, event): """ 取消关闭事件,改成淡出、隐藏窗口 """ logger.debug('closeEvent') event.ignore() self._close_timer.stop() if (self._fade_anim_group.state() != QPropertyAnimation.Running or self._fade_anim_group.direction() != QPropertyAnimation.Backward): self._read_the_weibo.on_popup_post_close() # 淡出 self._fade_anim_group.setDirection(QPropertyAnimation.Backward) self._fade_anim_group.start() def _on_anim_finish(self): if self._fade_anim_group.direction() == QPropertyAnimation.Backward: logger.debug('_on_anim_finish, direction = Backward') self.content_view.hide() self.hide() self._read_the_weibo.on_popup_post_hide() else: logger.debug('_on_anim_finish, direction = Forward')
class SongInfoCardChute(QWidget): """ 歌曲卡滑槽,有三个歌曲卡在循环滑动,当前歌曲卡切换时就切换歌曲 当前下标为0时,不能换到上一首, 当前下标为len-1时,不能换到下一首 """ # 当前歌曲切换信号 currentIndexChanged = pyqtSignal([int], [str]) # 显示和隐藏播放栏信号 showPlayBarSignal = pyqtSignal() hidePlayBarSignal = pyqtSignal() # 切换到专辑界面 switchToAlbumInterfaceSig = pyqtSignal(str, str) def __init__(self, parent=None, playlist=None): super().__init__(parent) self.shiftLeftTime = 0 self.shiftRightTime = 0 # 引用播放列表 self.playlist = playlist self.currentIndex = 0 self.songInfoCard_list = [] # 设置发送信号标志位 self.needToEmitSignal = True # 创建并行动画组 self.parallelAniGroup = QParallelAnimationGroup(self) # 记录有待完成的动画 self.__unCompleteShift_list = [] # 初始化 self.__initWidget() self.now = QDateTime.currentDateTime().toMSecsSinceEpoch() def __initWidget(self): """ 初始化小部件 """ self.resize(1300, 970) self.setAttribute(Qt.WA_TranslucentBackground) self.__createWidgets() for songInfoCard in self.songInfoCard_list: songInfoCard.resize(self.width(), 136) # 将信号连接到槽函数 self.parallelAniGroup.finished.connect(self.__switchSongInfoCard) for songInfoCard in self.songInfoCard_list: songInfoCard.showPlayBarSignal.connect(self.__showPlayBar) songInfoCard.hidePlayBarSignal.connect(self.__hidePlayBar) songInfoCard.switchToAlbumInterfaceSig.connect( self.switchToAlbumInterfaceSig) def __createWidgets(self): """ 创建小部件 """ # 创建三个歌曲信息卡,分别为last, current, next self.songInfoCard_list = [SongInfoCard(self) for i in range(3)] # 引用当前歌曲卡 self.lastSongInfoCard = self.songInfoCard_list[0] self.curSongInfoCard = self.songInfoCard_list[1] self.nextSongInfoCard = self.songInfoCard_list[2] # 创建动画 self.songInfoCardAni_list = [ QPropertyAnimation(self.songInfoCard_list[i], b'geometry') for i in range(3) ] self.lastSongInfoCardAni = self.songInfoCardAni_list[0] self.curSongInfoCardAni = self.songInfoCardAni_list[1] self.nextSongInfoCardAni = self.songInfoCardAni_list[2] # 将动画添加到动画组中 for ani in self.songInfoCardAni_list: self.parallelAniGroup.addAnimation(ani) # 先隐藏所有歌曲信息卡并设置位置 for i in range(3): self.songInfoCard_list[i].move((i - 1) * self.width(), self.height() - 204) self.songInfoCard_list[i].hide() # 初始化歌曲卡 if self.playlist: self.curSongInfoCard.updateCard(self.playlist[0]) self.curSongInfoCard.show() if len(self.playlist) >= 2: self.nextSongInfoCard.show() self.nextSongInfoCard.updateCard(self.playlist[1]) def mousePressEvent(self, e: QMouseEvent): """ 按钮按下时记录鼠标位置 """ super().mousePressEvent(e) self.mousePressPosX = e.pos().x() self.lastMousePosX = e.pos().x() # 记下按下的时间 self.mousePressTime = QDateTime.currentDateTime().toMSecsSinceEpoch() def mouseMoveEvent(self, e: QMouseEvent): """ 鼠标按下可拖动歌曲信息卡 """ for songInfoCard in self.songInfoCard_list: songInfoCard.move( songInfoCard.x() - (self.lastMousePosX - e.pos().x()), songInfoCard.y()) # 更新鼠标位置 self.lastMousePosX = e.pos().x() def mouseReleaseEvent(self, e: QMouseEvent): """ 鼠标松开时重新设置歌曲卡位置 """ # 记下松开的时间 self.mouseReleaseTime = QDateTime.currentDateTime().toMSecsSinceEpoch() mouseDeltaTime = self.mouseReleaseTime - self.mousePressTime # self.mouseDeltaX > 0代表向右移动 self.mouseDeltaX = self.lastMousePosX - self.mousePressPosX # 设置默认循环移位方式 self.loopMode = SongInfoCardLoopMode.NO_LOOP if self.playlist: if len(self.playlist) == 1: # 只有1个歌曲卡时不管移动距离是多少都把该歌曲卡放回原位 self.__restoreCardPosition() # 播放列表长度大于等于2 elif len(self.playlist) >= 2: # 下标为0时右移或者下标为len-1左移都恢复原状 if (self.currentIndex == 0 and self.mouseDeltaX > 0) or ( self.currentIndex == len(self.playlist) - 1 and self.mouseDeltaX < 0): self.__restoreCardPosition() else: if mouseDeltaTime < 360 and abs(self.mouseDeltaX) >= 120: self.__cycleShift() else: if abs(self.mouseDeltaX) < int(self.width() / 2): self.__restoreCardPosition() else: self.__cycleShift() def __cycleLeftShift(self): """ 循环左移 """ self.loopMode = SongInfoCardLoopMode.CYCLE_LEFT_SHIFT self.__setAnimation(self.curSongInfoCardAni, self.curSongInfoCard, -self.width()) self.__setAnimation(self.nextSongInfoCardAni, self.nextSongInfoCard, 0) self.parallelAniGroup.removeAnimation(self.lastSongInfoCardAni) self.parallelAniGroup.start() # 发送更新背景信号 self.currentIndexChanged[str].emit( self.nextSongInfoCard.albumCoverPath) def __cycleRightShift(self): """ 循环右移 """ self.loopMode = SongInfoCardLoopMode.CYCLE_RIGHT_SHIFT self.__setAnimation(self.curSongInfoCardAni, self.curSongInfoCard, self.width()) self.__setAnimation(self.lastSongInfoCardAni, self.lastSongInfoCard, 0) self.parallelAniGroup.removeAnimation(self.nextSongInfoCardAni) self.parallelAniGroup.start() # 发送更新背景信号 self.currentIndexChanged[str].emit( self.lastSongInfoCard.albumCoverPath) def __switchSongInfoCard(self): """ 交换对底层歌曲卡对象的引用 """ # 循环左移 if self.loopMode == SongInfoCardLoopMode.CYCLE_LEFT_SHIFT: self.__resetRef(moveDirection=0) # 更新动画组 self.parallelAniGroup.addAnimation(self.lastSongInfoCardAni) # 移动底层对象 self.__moveObject( self.songInfoCard_list[[2, 0, 1][self.shiftLeftTime]], self.width()) # 更新下标 self.currentIndex += 1 if self.currentIndex != len(self.playlist) - 1: # 更新歌曲信息卡 self.updateCards() self.nextSongInfoCard.show() else: self.nextSongInfoCard.hide() # 根据标志位决定是否发送下标更新信号 if self.needToEmitSignal: self.currentIndexChanged[int].emit(self.currentIndex) else: self.needToEmitSignal = True # 循环右移 elif self.loopMode == SongInfoCardLoopMode.CYCLE_RIGHT_SHIFT: self.__resetRef(moveDirection=1) # 更新动画组 self.parallelAniGroup.addAnimation(self.nextSongInfoCardAni) # 移动底层对象 self.__moveObject( self.songInfoCard_list[[0, 2, 1][self.shiftRightTime]], -self.width()) # 更新下标 self.currentIndex -= 1 if self.currentIndex != 0: self.updateCards() self.lastSongInfoCard.show() else: self.lastSongInfoCard.hide() # 根据标志位决定是否发送下标更新信号 if self.needToEmitSignal: self.currentIndexChanged[int].emit(self.currentIndex) else: self.needToEmitSignal = True # 完成未完成的移位动作 if self.__unCompleteShift_list: index = self.__unCompleteShift_list.pop(0) self.needToEmitSignal = False self.__completeShift(index) def __cycleShift(self): """ 三卡片移动 """ # 播放播放列表的上一首(右移歌曲卡) if self.mouseDeltaX > 0: self.__cycleRightShift() # 播放播放列表的下一首(左移歌曲卡) elif self.mouseDeltaX < 0: self.__cycleLeftShift() def __setAnimation(self, animation: QPropertyAnimation, songInfoCard, endX): """ 设置动画 """ animation.setEasingCurve(QEasingCurve.OutQuart) animation.setTargetObject(songInfoCard) animation.setDuration(500) # 设置起始值 animation.setStartValue( QRect(songInfoCard.x(), songInfoCard.y(), songInfoCard.width(), songInfoCard.height())) # 设置结束值 animation.setEndValue( QRect(endX, songInfoCard.y(), songInfoCard.width(), songInfoCard.height())) def __restoreCardPosition(self): """ 恢复卡片位置 """ self.__setAnimation(self.curSongInfoCardAni, self.curSongInfoCard, 0) self.__setAnimation(self.nextSongInfoCardAni, self.nextSongInfoCard, self.width()) self.__setAnimation(self.lastSongInfoCardAni, self.lastSongInfoCard, -self.width()) self.parallelAniGroup.start() def updateCards(self): """ 更新三个歌曲信息卡 """ if self.curSongInfoCard: self.curSongInfoCard.updateCard(self.playlist[self.currentIndex]) if self.lastSongInfoCard and self.currentIndex >= 1: self.lastSongInfoCard.updateCard(self.playlist[self.currentIndex - 1]) if self.nextSongInfoCard and self.currentIndex <= len( self.playlist) - 2: self.nextSongInfoCard.updateCard(self.playlist[self.currentIndex + 1]) def setCurrentIndex(self, index): """ 更新当前下标并移动和更新歌曲信息卡 """ # 移位完成后不发送信号 self.needToEmitSignal = False if self.playlist: # 新的下标大于当前下标时,歌曲卡左移 if index != self.currentIndex: if self.parallelAniGroup.state() != QAbstractAnimation.Running: self.__completeShift(index) else: self.__unCompleteShift_list.append(index) elif index == self.currentIndex: self.updateCards() self.needToEmitSignal = True def setPlaylist(self, playlist, isResetIndex: bool = True): """ 更新播放列表 """ self.playlist = playlist self.currentIndex = 0 if isResetIndex else self.currentIndex self.lastSongInfoCard.hide() if playlist: self.curSongInfoCard.updateCard(self.playlist[self.currentIndex]) self.currentIndexChanged[str].emit( self.curSongInfoCard.albumCoverPath) self.curSongInfoCard.show() if len(self.playlist) == 1: self.nextSongInfoCard.hide() else: self.nextSongInfoCard.show() self.nextSongInfoCard.updateCard( self.playlist[self.currentIndex + 1]) else: self.curSongInfoCard.hide() self.nextSongInfoCard.hide() def resizeEvent(self, e): """ 改变窗口大小时也改变歌曲卡的大小 """ super().resizeEvent(e) for i in range(3): self.songInfoCard_list[i].resize(self.width(), 136) self.songInfoCard_list[i].adjustText() self.curSongInfoCard.move(0, self.height() - 204) self.lastSongInfoCard.move(-self.width(), self.height() - 204) self.nextSongInfoCard.move(self.width(), self.height() - 204) def __moveObject(self, songInfoCardObj, x): """ 移动底层对象 """ songInfoCardObj.hide() songInfoCardObj.move(x, self.height() - 204) songInfoCardObj.show() def __resetRef(self, moveDirection=0): """ 设置变量对底层对象的引用,moveDirection = 0 代表左移,moveDirection = 1 代表右移 """ # 循环左移 if moveDirection == 0: self.shiftLeftTime = (self.shiftLeftTime + 1) % 3 self.shiftRightTime = (self.shiftRightTime - 1) % 3 if self.shiftLeftTime == 0: self.__resetRefIndex(0, 1, 2) elif self.shiftLeftTime == 1: self.__resetRefIndex(1, 2, 0) elif self.shiftLeftTime == 2: self.__resetRefIndex(2, 0, 1) # 循环右移 elif moveDirection == 1: self.shiftLeftTime = (self.shiftLeftTime - 1) % 3 self.shiftRightTime = (self.shiftRightTime + 1) % 3 if self.shiftRightTime == 0: self.__resetRefIndex(0, 1, 2) elif self.shiftRightTime == 1: self.__resetRefIndex(2, 0, 1) elif self.shiftRightTime == 2: self.__resetRefIndex(1, 2, 0) def __resetRefIndex(self, lastIndex, curIndex, nextIndex): """ refsetFunc的子函数 """ self.curSongInfoCard = self.songInfoCard_list[curIndex] self.lastSongInfoCard = self.songInfoCard_list[lastIndex] self.nextSongInfoCard = self.songInfoCard_list[nextIndex] def __showPlayBar(self): """ 显示播放栏 """ self.showPlayBarSignal.emit() for songInfoCard in self.songInfoCard_list: songInfoCard.isPlayBarVisible = True def __hidePlayBar(self): """ 隐藏播放栏 """ self.hidePlayBarSignal.emit() for songInfoCard in self.songInfoCard_list: songInfoCard.isPlayBarVisible = False def stopSongInfoCardTimer(self): """ 停止歌曲信息卡的计时器 """ for songInfoCard in self.songInfoCard_list: songInfoCard.timer.stop() def startSongInfoCardTimer(self): """ 打开歌曲信息卡的计时器 """ self.curSongInfoCard.timer.start() def __completeShift(self, index): """ 完成移位,只在调用setCurrentIndex时调用 """ if index > self.currentIndex: self.currentIndex = index - 1 self.nextSongInfoCard.updateCard(self.playlist[index]) self.__cycleLeftShift() elif index < self.currentIndex: self.currentIndex = index + 1 self.lastSongInfoCard.updateCard(self.playlist[index]) self.__cycleRightShift()
class SmallestPlayModeInterface(QWidget): """ 最小播放模式界面 """ CYCLE_LEFT_SHIFT = 0 CYCLE_RIGHT_SHIFT = 1 def __init__(self, playlist: list, parent=None): super().__init__(parent) self.playlist = playlist self.currentIndex = 0 self.shiftLeftTime = 0 self.shiftRightTime = 0 self.songInfoCard_list = [] self.__unCompleteShift_list = [] # 创建按钮 self.playButton = PlayButton( [ r"app\resource\images\smallest_play_mode\播放_45_45.png", r"app\resource\images\smallest_play_mode\暂停_45_45.png", ], self, ) self.lastSongButton = SmallestPlayModeButton( r"app\resource\images\smallest_play_mode\上一首_45_45.png", self) self.nextSongButton = SmallestPlayModeButton( r"app\resource\images\smallest_play_mode\下一首_45_45.png", self) self.exitSmallestModeButton = BasicCircleButton( r"app\resource\images\playing_interface\最小模式播放_47_47.png", self) self.progressBar = QSlider(Qt.Horizontal, self) self.aniGroup = QParallelAnimationGroup(self) # 创建歌曲信息卡 self.__createSongInfoCards() # 初始化 self.__initWidget() def __initWidget(self): """ 初始化界面 """ self.resize(350, 350) self.setMinimumSize(206, 197) self.setAttribute(Qt.WA_TranslucentBackground) self.setObjectName("smallestModeInterface") self.progressBar.setObjectName("smallestModeSlider") self.progressBar.installEventFilter(self) self.aniGroup.finished.connect(self.__switchSongInfoCard) self.__setQss() def __createSongInfoCards(self): """ 创建歌曲信息卡 """ # 创建三个歌曲信息卡,分别为last, current, next self.songInfoCard_list = [SongInfoCard(parent=self) for i in range(3)] # 引用当前歌曲卡 self.lastSongInfoCard = self.songInfoCard_list[0] # type:SongInfoCard self.curSongInfoCard = self.songInfoCard_list[1] # type:SongInfoCard self.nextSongInfoCard = self.songInfoCard_list[2] # type:SongInfoCard # 创建动画 self.songInfoCardAni_list = [ QPropertyAnimation(self.songInfoCard_list[i], b"geometry") for i in range(3) ] self.lastSongInfoCardAni = self.songInfoCardAni_list[0] self.curSongInfoCardAni = self.songInfoCardAni_list[1] self.nextSongInfoCardAni = self.songInfoCardAni_list[2] # 将动画添加到动画组中 for ani in self.songInfoCardAni_list: self.aniGroup.addAnimation(ani) # 初始化歌曲卡 for i in range(3): self.songInfoCard_list[i].move((i - 1) * self.width(), self.height() - 106) if self.playlist: self.curSongInfoCard.updateCard(self.playlist[0]) if len(self.playlist) >= 2: self.nextSongInfoCard.updateCard(self.playlist[1]) def resizeEvent(self, e): """ 改变窗口大小时调整按钮位置和标签宽度 """ self.progressBar.resize(self.width(), 5) self.progressBar.move(0, self.height() - 5) self.exitSmallestModeButton.move( self.width() - 7 - self.exitSmallestModeButton.width(), self.height() - 7 - self.exitSmallestModeButton.height(), ) self.playButton.move( int(self.width() / 2 - self.playButton.width() / 2), int(self.height() / 2 - self.playButton.height() / 2), ) self.lastSongButton.move(self.playButton.x() - 75, self.playButton.y()) self.nextSongButton.move(self.playButton.x() + 75, self.playButton.y()) # 所有歌曲信息卡设置位置 for i in range(3): self.songInfoCard_list[i].resize( self.width(), self.songInfoCard_list[i].height()) self.curSongInfoCard.move(0, self.height() - 106) self.lastSongInfoCard.move(-self.width(), self.height() - 106) self.nextSongInfoCard.move(self.width(), self.height() - 106) # 高度太小时隐藏歌曲信息卡 if self.height() <= 320: if self.curSongInfoCard.isVisible(): self.curSongInfoCard.aniHide() self.lastSongInfoCard.hide() self.nextSongInfoCard.hide() elif self.height() > 320: if not self.curSongInfoCard.isVisible(): self.curSongInfoCard.aniShow() else: self.curSongInfoCard.show() self.lastSongInfoCard.show() self.nextSongInfoCard.show() def __setQss(self): """ 设置层叠样式 """ with open(r"app\resource\css\playInterface.qss", encoding="utf-8") as f: self.setStyleSheet(f.read()) def eventFilter(self, obj, e: QEvent): """ 过滤事件 """ if obj == self.progressBar: if e.type() in [ QEvent.MouseButtonPress, QEvent.MouseButtonDblClick ]: return True return super().eventFilter(obj, e) def __cycleLeftShift(self): """ 循环左移 """ self.loopMode = self.CYCLE_LEFT_SHIFT self.__setAnimation(self.curSongInfoCardAni, self.curSongInfoCard, -self.width()) self.__setAnimation(self.nextSongInfoCardAni, self.nextSongInfoCard, 0) self.aniGroup.removeAnimation(self.lastSongInfoCardAni) self.aniGroup.start() def __cycleRightShift(self): """ 循环右移 """ self.loopMode = self.CYCLE_RIGHT_SHIFT self.__setAnimation(self.curSongInfoCardAni, self.curSongInfoCard, self.width()) self.__setAnimation(self.lastSongInfoCardAni, self.lastSongInfoCard, 0) self.aniGroup.removeAnimation(self.nextSongInfoCardAni) self.aniGroup.start() def __setAnimation(self, animation: QPropertyAnimation, songInfoCard, endX): """ 设置动画 """ animation.setEasingCurve(QEasingCurve.OutQuart) animation.setTargetObject(songInfoCard) animation.setDuration(500) # 设置起始值 animation.setStartValue( QRect( songInfoCard.x(), songInfoCard.y(), songInfoCard.width(), songInfoCard.height(), )) # 设置结束值 animation.setEndValue( QRect(endX, songInfoCard.y(), songInfoCard.width(), songInfoCard.height())) def __switchSongInfoCard(self): """ 交换对底层歌曲卡对象的引用 """ # 循环左移 if self.loopMode == self.CYCLE_LEFT_SHIFT: self.__resetRef(moveDirection=0) # 更新动画组 self.aniGroup.addAnimation(self.lastSongInfoCardAni) # 移动底层对象 self.songInfoCard_list[[2, 0, 1][self.shiftLeftTime]].move( self.width(), self.height() - 106) # 更新下标 self.currentIndex += 1 if self.currentIndex != len(self.playlist) - 1: # 更新歌曲信息卡 self.updateCards() # 循环右移 elif self.loopMode == self.CYCLE_RIGHT_SHIFT: self.__resetRef(moveDirection=1) # 更新动画组 self.aniGroup.addAnimation(self.nextSongInfoCardAni) # 移动底层对象 self.songInfoCard_list[[0, 2, 1][self.shiftRightTime]].move( -self.width(), self.height() - 106) # 更新下标 self.currentIndex -= 1 if self.currentIndex != 0: self.updateCards() # 完成未完成的移位动作 if self.__unCompleteShift_list: index = self.__unCompleteShift_list.pop(0) self.__completeShift(index) def updateCards(self): """ 更新三个歌曲信息卡 """ self.curSongInfoCard.updateCard(self.playlist[self.currentIndex]) if self.currentIndex >= 1: self.lastSongInfoCard.updateCard(self.playlist[self.currentIndex - 1]) if self.currentIndex <= len(self.playlist) - 2: self.nextSongInfoCard.updateCard(self.playlist[self.currentIndex + 1]) def __resetRef(self, moveDirection=0): """ 设置变量对底层对象的引用,moveDirection = 0 代表左移,moveDirection = 1 代表右移 """ # 循环左移 if moveDirection == 0: self.shiftLeftTime = (self.shiftLeftTime + 1) % 3 self.shiftRightTime = (self.shiftRightTime - 1) % 3 if self.shiftLeftTime == 0: self.__resetRefIndex(0, 1, 2) elif self.shiftLeftTime == 1: self.__resetRefIndex(1, 2, 0) elif self.shiftLeftTime == 2: self.__resetRefIndex(2, 0, 1) # 循环右移 elif moveDirection == 1: self.shiftLeftTime = (self.shiftLeftTime - 1) % 3 self.shiftRightTime = (self.shiftRightTime + 1) % 3 if self.shiftRightTime == 0: self.__resetRefIndex(0, 1, 2) elif self.shiftRightTime == 1: self.__resetRefIndex(2, 0, 1) elif self.shiftRightTime == 2: self.__resetRefIndex(1, 2, 0) def __resetRefIndex(self, lastIndex, curIndex, nextIndex): """ refsetFunc的子函数 """ self.curSongInfoCard = self.songInfoCard_list[curIndex] self.lastSongInfoCard = self.songInfoCard_list[lastIndex] self.nextSongInfoCard = self.songInfoCard_list[nextIndex] def setCurrentIndex(self, index): """ 更新当前下标并移动和更新歌曲信息卡 """ if self.playlist: # 新的下标大于当前下标时,歌曲卡左移 if index != self.currentIndex: if self.aniGroup.state() != QAbstractAnimation.Running: self.__completeShift(index) else: self.__unCompleteShift_list.append(index) elif index == self.currentIndex: self.updateCards() self.needToEmitSignal = True def setPlaylist(self, playlist, isResetIndex: bool = True): """ 更新播放列表 """ self.playlist = playlist self.currentIndex = 0 if isResetIndex else self.currentIndex if playlist: self.curSongInfoCard.updateCard(self.playlist[0]) self.curSongInfoCard.show() if len(self.playlist) > 1: self.nextSongInfoCard.updateCard(self.playlist[1]) else: self.curSongInfoCard.hide() def __completeShift(self, index): """ 完成移位,只在调用setCurrentIndex时调用 """ if index > self.currentIndex: self.currentIndex = index - 1 self.nextSongInfoCard.updateCard(self.playlist[index]) self.__cycleLeftShift() elif index < self.currentIndex: self.currentIndex = index + 1 self.lastSongInfoCard.updateCard(self.playlist[index]) self.__cycleRightShift()