From 2a55d0dc2cd81cf7b9210e5cc1b78e49a4143697 Mon Sep 17 00:00:00 2001 From: yuncishu <3053860096@qq.com> Date: Sat, 31 Oct 2026 22:38:59 +0800 Subject: [PATCH] feat: redesign play queue and per-track queue actions (1.2.3) --- README.md | 2 +- src/main.py | 156 ++++++++++++++++++++-------------------- src/theme.py | 48 +++++++++---- src/version.py | 2 +- src/widgets.py | 191 ++++++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 295 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index 00210c8..37bae21 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ - 标准 / 较高 / 极高 / 无损音质切换 - 逐行歌词、精确居中、点击歌词跳转 - 全屏播放页、黑胶旋转、音量与播放进度控制 -- 播放栏显示当前播放歌单 +- 播放队列面板:下一首播放、删除单曲、清空保留当前 - 应用内检查更新、自动下载、SHA-256 校验和重启安装 ## 开发运行 diff --git a/src/main.py b/src/main.py index c072dc6..1bac08e 100644 --- a/src/main.py +++ b/src/main.py @@ -7,11 +7,11 @@ import sys import requests from PySide6.QtCore import Qt, QUrl, QSize, QTimer, QRunnable, QThreadPool, Signal, QObject -from PySide6.QtGui import QPixmap, QIcon, QColor +from PySide6.QtGui import QPixmap, QIcon, QCursor from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput from PySide6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, - QLineEdit, QStackedWidget, QListWidget, QListWidgetItem, QSlider, QComboBox, + QLineEdit, QStackedWidget, QSlider, QComboBox, QToolTip, QGridLayout, QSizePolicy, QProgressBar, QDialog, ) @@ -22,7 +22,7 @@ from version import APP_NAME, APP_VERSION from theme import QSS, ACCENT from widgets import ( make_icon, rounded_pixmap, DiscWidget, ClickSlider, LyricView, TrackTable, icon_button, - CoverBackdrop, + CoverBackdrop, QueuePopup, ) @@ -108,8 +108,6 @@ class MainWindow(QMainWindow): self.queue = [] self.q_index = -1 self.current = None - self.stack_context_name = "发现音乐" - self.current_playlist_name = "" self.mode = "list" self.quality = "exhigh" self.volume = 78 @@ -230,6 +228,7 @@ class MainWindow(QMainWindow): self.stack.addWidget(self.charts_page) self.table = TrackTable() self.table.play_requested.connect(self._play_from_list) + self.table.next_requested.connect(self._queue_next) self.table.verticalScrollBar().valueChanged.connect(self._load_visible_covers) self.stack.addWidget(self.table) self.empty_page = self._build_empty_page() @@ -242,6 +241,11 @@ class MainWindow(QMainWindow): self.fs.setParent(root) self.fs.hide() + self.queue_popup = QueuePopup(self) + self.queue_popup.play_requested.connect(self._play_index) + self.queue_popup.remove_requested.connect(self._remove_queue_item) + self.queue_popup.clear_requested.connect(self._clear_queue) + def _build_sidebar(self): side = QWidget(objectName="side") side.setFixedWidth(224) @@ -281,26 +285,7 @@ class MainWindow(QMainWindow): layout.addWidget(button) self.nav_btns[key] = button - queue_header = QHBoxLayout() - queue_header.setContentsMargins(12, 28, 6, 8) - queue_header.addWidget(QLabel("当前队列", objectName="sideSection")) - queue_header.addStretch() - clear = QPushButton("清空", objectName="clearBtn") - clear.setCursor(Qt.PointingHandCursor) - clear.clicked.connect(self._clear_queue) - queue_header.addWidget(clear) - layout.addLayout(queue_header) - - self.queue_list = QListWidget(objectName="queue") - self.queue_list.setCursor(Qt.PointingHandCursor) - self.queue_list.viewport().setCursor(Qt.PointingHandCursor) - self.queue_list.itemClicked.connect(self._on_queue_click) - layout.addWidget(self.queue_list, 1) - self.queue_empty = QLabel("播放的歌曲会出现在这里", objectName="queueEmpty") - self.queue_empty.setContentsMargins(12, 4, 8, 8) - layout.addWidget(self.queue_empty) - - layout.addSpacing(8) + layout.addStretch() version_row = QHBoxLayout() version_row.setContentsMargins(10, 8, 2, 0) self.version_label = QLabel("v%s" % APP_VERSION, objectName="versionLabel") @@ -468,11 +453,8 @@ class MainWindow(QMainWindow): layout.addLayout(center, 1) right_widget = QWidget() - right_widget.setFixedWidth(290) - right_column = QVBoxLayout(right_widget) - right_column.setContentsMargins(0, 1, 0, 0) - right_column.setSpacing(4) - right = QHBoxLayout() + right_widget.setFixedWidth(324) + right = QHBoxLayout(right_widget) right.setContentsMargins(0, 0, 0, 0) right.setSpacing(7) right.addStretch() @@ -502,12 +484,9 @@ class MainWindow(QMainWindow): self.vol_slider.setToolTip("音量") self.vol_slider.valueChanged.connect(self._on_volume) right.addWidget(self.vol_slider) - right_column.addLayout(right) - self.bar_playlist = QLabel("歌单 · 未播放", objectName="barPlaylist") - self.bar_playlist.setAlignment(Qt.AlignRight | Qt.AlignVCenter) - self.bar_playlist.setFixedHeight(14) - self.bar_playlist.setToolTip("当前播放队列来源") - right_column.addWidget(self.bar_playlist) + self.btn_queue = icon_button("bars", 34, 18, tooltip="播放队列") + self.btn_queue.clicked.connect(self._toggle_queue_popup) + right.addWidget(self.btn_queue) layout.addWidget(right_widget) return bar @@ -629,20 +608,17 @@ class MainWindow(QMainWindow): self.page_eyebrow.setText("LIBRARY / DISCOVER") self.page_title.setText("发现音乐") self.page_meta.setText("网易云音乐官方榜单") - self.stack_context_name = "发现音乐" self.stack.setCurrentWidget(self.charts_page) elif key == "search": self.page_eyebrow.setText("LIBRARY / SEARCH") self.page_title.setText("搜索") self.page_meta.setText("在网易云音乐曲库中寻找声音") - self.stack_context_name = "搜索" self._show_empty("搜索一首歌", "输入歌曲、歌手或专辑名称,按回车开始") self.search_input.setFocus() else: self.page_eyebrow.setText("LIBRARY / HISTORY") self.page_title.setText("最近播放") self.page_meta.setText("保存在这台电脑上的播放记录") - self.stack_context_name = "最近播放" if not self.recent: self._show_empty("还没有播放记录", "播放过的歌曲会自动保存在这里") else: @@ -788,7 +764,6 @@ class MainWindow(QMainWindow): token = self.view_token self.page_eyebrow.setText("CHARTS / OFFICIAL") self.page_title.setText(name) - self.stack_context_name = name self.btn_back.setVisible(True) self.btn_refresh.setVisible(False) self.btn_play_all.setVisible(False) @@ -877,7 +852,6 @@ class MainWindow(QMainWindow): ) return self.page_title.setText(playlist.get("name") or "导入歌单") - self.stack_context_name = playlist.get("name") or "导入歌单" total = playlist.get("trackCount") or len(songs) extra = " · 当前导入前 %d 首" % len(songs) if total > len(songs) else "" self.page_meta.setText("共 %d 首%s · 来自网易云音乐" % (total, extra)) @@ -923,7 +897,6 @@ class MainWindow(QMainWindow): self.view_token += 1 token = self.view_token self.page_meta.setText("正在搜索「%s」…" % keyword) - self.stack_context_name = "搜索 · %s" % keyword self.btn_play_all.setVisible(False) self._show_empty("正在搜索", "在整个曲库中寻找「%s」" % keyword, busy=True) @@ -950,41 +923,79 @@ class MainWindow(QMainWindow): # ---------- 队列 ---------- def _render_queue(self): - self.queue_list.clear() - for i, song in enumerate(self.queue): - item = QListWidgetItem("%s\n%s" % (song.get("name", ""), core.artists_of(song))) - item.setData(Qt.UserRole, i) - item.setToolTip(song.get("name", "")) - if i == self.q_index: - item.setForeground(QColor("#f27770")) - font = item.font() - font.setBold(True) - item.setFont(font) - self.queue_list.addItem(item) - self.queue_empty.setVisible(not self.queue) - if 0 <= self.q_index < self.queue_list.count(): - self.queue_list.scrollToItem(self.queue_list.item(self.q_index)) + if hasattr(self, "queue_popup") and self.queue_popup.isVisible(): + current_id = self.current.get("id") if self.current else None + self.queue_popup.set_queue(self.queue, self.q_index, current_id) - def _on_queue_click(self, item): - index = item.data(Qt.UserRole) - if index is not None and 0 <= int(index) < len(self.queue): - self._play_index(int(index)) + def _toggle_queue_popup(self): + if self.queue_popup.isVisible(): + self.queue_popup.hide() + return + current_id = self.current.get("id") if self.current else None + self.queue_popup.set_queue(self.queue, self.q_index, current_id) + self.queue_popup.show_above(self.btn_queue) + + def _remove_queue_item(self, index): + if not (0 <= index < len(self.queue)): + return + was_current = index == self.q_index + self.queue.pop(index) + if was_current: + self.q_index = index - 1 + elif index < self.q_index: + self.q_index -= 1 + if not self.queue: + self.q_index = -1 + elif self.q_index >= len(self.queue): + self.q_index = len(self.queue) - 1 + self._render_queue() def _clear_queue(self): - self.queue = [] - self.q_index = -1 + if self.current is not None: + self.queue = [self.current] + self.q_index = 0 + else: + self.queue = [] + self.q_index = -1 self._render_queue() + def _queue_next(self, song): + if not song: + return + if self.current and song.get("id") == self.current.get("id"): + QToolTip.showText(QCursor.pos(), "这首歌正在播放") + return + existing = next( + (i for i, item in enumerate(self.queue) if item.get("id") == song.get("id")), + None, + ) + if existing is not None: + self.queue.pop(existing) + if existing < self.q_index: + self.q_index -= 1 + insert_at = self.q_index + 1 if self.q_index >= 0 else 0 + insert_at = max(0, min(insert_at, len(self.queue))) + self.queue.insert(insert_at, dict(song)) + self._render_queue() + QToolTip.showText(QCursor.pos(), "已设为下一首播放") + # ---------- 播放 ---------- def _play_from_list(self, songs, index): - self.queue = list(songs) - self._set_current_playlist(self.stack_context_name) - self._play_index(index) + if not (0 <= index < len(songs)): + return + song = dict(songs[index]) + existing = next( + (i for i, item in enumerate(self.queue) if item.get("id") == song.get("id")), + None, + ) + if existing is None: + self.queue.append(song) + existing = len(self.queue) - 1 + self._play_index(existing) def _play_all(self): if self.stack_songs: self.queue = list(self.stack_songs) - self._set_current_playlist(self.stack_context_name) self._play_index(0) def _play_index(self, index): @@ -1147,19 +1158,6 @@ class MainWindow(QMainWindow): lambda error: self.bar_artist.setText("音质切换失败")) # ---------- 当前歌曲与封面 ---------- - def _update_bar_playlist(self): - label = self.current_playlist_name or "未播放" - raw = "当前歌单:" + label - width = max(80, self.bar_playlist.width() - 2) - shown = self.bar_playlist.fontMetrics().elidedText( - "歌单 · " + label, Qt.ElideRight, width) - self.bar_playlist.setText(shown) - self.bar_playlist.setToolTip(raw) - - def _set_current_playlist(self, name): - self.current_playlist_name = (name or "").strip() or "未命名歌单" - self._update_bar_playlist() - def _update_bar(self): song = self.current if not song: diff --git a/src/theme.py b/src/theme.py index cee0e83..978040d 100644 --- a/src/theme.py +++ b/src/theme.py @@ -29,15 +29,6 @@ QPushButton#nav { } QPushButton#nav:hover { color: #f3f3f4; background: #28292e; } QPushButton#nav[active="true"] { color: #ffffff; background: #323338; font-weight: 600; } -QPushButton#clearBtn { color: #777981; border: none; background: transparent; font-size: 11px; } -QPushButton#clearBtn:hover { color: #f0f0f1; } -QListWidget#queue { background: transparent; border: none; outline: none; } -QListWidget#queue::item { - color: #9698a0; border: none; border-radius: 7px; padding: 0 8px; height: 44px; -} -QListWidget#queue::item:hover { background: #28292e; color: #e7e7e9; } -QListWidget#queue::item:selected { background: #303137; color: #f3f3f4; } -#queueEmpty { color: #62646c; font-size: 11px; } #versionLabel { color: #666871; font-size: 9.5px; letter-spacing: .4px; } QPushButton#versionBtn { color: #8b8d95; background: transparent; border: none; border-radius: 5px; @@ -110,10 +101,6 @@ QProgressBar#emptyProgress::chunk { background: #e6493f; border-radius: 2px; } #barName { font-size: 13px; font-weight: 650; color: #2c2a27; } #barArtist { font-size: 11px; color: #918d85; } #barHint { color: #b0aba3; font-size: 10px; } -#barPlaylist { - color: #aaa49b; font-size: 9.5px; letter-spacing: .02em; - background: transparent; -} #timeLabel { color: #9b978f; font-size: 10px; font-family: "Segoe UI", sans-serif; } QSlider::groove:horizontal { height: 4px; background: #e8e4de; border-radius: 2px; } QSlider::sub-page:horizontal { height: 4px; background: #e6493f; border-radius: 2px; } @@ -136,6 +123,41 @@ QComboBox QAbstractItemView { selection-background-color: #eeeae4; selection-color: #242322; } +/* 播放队列弹层 */ +#queueCard { background:#fffefa; border:1px solid #e4dfd8; border-radius:14px; } +#queueTitle { color:#242322; font-size:15px; font-weight:700; } +#queueCount { color:#aaa49b; font-size:10px; } +QPushButton#queueClear { + color:#8d8880; background:transparent; border:1px solid #e6e1da; + border-radius:6px; padding:0 9px; min-height:25px; font-size:10.5px; +} +QPushButton#queueClear:hover { color:#c83b32; border-color:#e7c5c0; background:#fdf5f3; } +QPushButton#queueClear:disabled { color:#c9c4bc; border-color:#efebe5; } +QScrollArea#queueScroll { background:transparent; border:none; } +QScrollArea#queueScroll > QWidget > QWidget { background:transparent; } +QWidget#queueBody { background:transparent; } +QWidget#queueRow { background:transparent; border-radius:8px; } +QWidget#queueRow:hover { background:#f4f0ea; } +QWidget#queueRow[current="true"] { background:#fdf1ef; } +QPushButton#queueRemove { + background:transparent; border:none; border-radius:6px; padding:0; +} +QPushButton#queueRemove:hover { background:#efe7e1; } +#queueEmpty { color:#aaa49b; font-size:11px; padding:54px 0; } +QPushButton#trackMore { + background:transparent; border:none; border-radius:6px; padding:0; +} +QPushButton#trackMore:hover { background:#efebe5; } +QMenu { + background:#fffefa; border:1px solid #e1dcd4; border-radius:8px; padding:5px; +} +QMenu::item { + color:#4f4b46; background:transparent; padding:8px 20px 8px 14px; + border-radius:5px; font-size:12px; +} +QMenu::item:selected { background:#f4f0ea; color:#242322; } +QMenu::separator { height:1px; background:#eeeae3; margin:4px 8px; } + /* 导入歌单 */ #importCard { background:#fffefa; border:1px solid #e4dfd8; border-radius:16px; } #importTitle { color:#22211f; font-size:19px; font-weight:700; } diff --git a/src/version.py b/src/version.py index 6faeea0..4e5591e 100644 --- a/src/version.py +++ b/src/version.py @@ -1,7 +1,7 @@ """应用版本与更新地址。""" APP_NAME = "网易云播放器" -APP_VERSION = "1.2.2" +APP_VERSION = "1.2.3" BUILD_DATE = "2026-10-31" UPDATE_ENDPOINT = "https://music.yuncishu.xyz/api/app/update" DOWNLOAD_PAGE = "https://music.yuncishu.xyz/download/" diff --git a/src/widgets.py b/src/widgets.py index 07ef8f2..2310333 100644 --- a/src/widgets.py +++ b/src/widgets.py @@ -2,7 +2,7 @@ import bisect from PySide6.QtCore import ( - Qt, QSize, QRectF, QByteArray, QTimer, QElapsedTimer, + Qt, QSize, QPoint, QRectF, QByteArray, QTimer, QElapsedTimer, QPropertyAnimation, QEasingCurve, Signal, ) from PySide6.QtGui import QPixmap, QIcon, QColor, QFont, QPainter, QPainterPath, QImage, QLinearGradient @@ -10,7 +10,7 @@ from PySide6.QtSvg import QSvgRenderer from PySide6.QtWidgets import ( QWidget, QLabel, QPushButton, QSlider, QStyle, QScrollArea, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QHeaderView, QAbstractItemView, QSizePolicy, - QGraphicsScene, QGraphicsBlurEffect, + QGraphicsScene, QGraphicsBlurEffect, QFrame, QGraphicsDropShadowEffect, QMenu, ) import core @@ -35,6 +35,8 @@ ICONS = { "clock": _S % '', "back": _S % '', "plus": _S % '', + "bars": _S % '', + "more": _S % '', "playlist": _S % '', "expand": _S % '', "music": _S % '', @@ -404,12 +406,160 @@ class LyricView(QScrollArea): super().wheelEvent(event) -class TrackTable(QTableWidget): - play_requested = Signal(list, int) - COLS = ["", "歌曲", "专辑", "时长"] +class QueueRow(QWidget): + play_requested = Signal(int) + remove_requested = Signal(int) + + def __init__(self, index, song, current=False, parent=None): + super().__init__(parent) + self.index = index + self.setObjectName("queueRow") + self.setProperty("current", "true" if current else "false") + self.setAttribute(Qt.WA_StyledBackground, True) + self.setFixedHeight(60) + self.setCursor(Qt.PointingHandCursor) + row = QHBoxLayout(self) + row.setContentsMargins(10, 4, 8, 4) + row.setSpacing(11) + number = QLabel("▶" if current else "%02d" % (index + 1), objectName="queueIndex") + number.setFixedWidth(24) + number.setAlignment(Qt.AlignCenter) + number.setStyleSheet("color:%s; font-size:%spx; font-weight:600; background:transparent;" % ( + "#e6493f" if current else "#aaa59c", 9 if current else 10)) + row.addWidget(number) + text = QVBoxLayout() + text.setSpacing(2) + title = QLabel(song.get("name", ""), objectName="queueSong") + title.setStyleSheet("color:%s; font-size:12.5px; font-weight:%s; background:transparent;" % ( + "#e6493f" if current else "#2c2a27", "600" if current else "500")) + artist = QLabel(core.artists_of(song), objectName="queueArtist") + artist.setStyleSheet("color:%s; font-size:10px; background:transparent;" % ( + "#c16b64" if current else "#9b968e")) + text.addWidget(title) + text.addWidget(artist) + row.addLayout(text, 1) + remove = QPushButton() + remove.setObjectName("queueRemove") + remove.setFixedSize(26, 26) + remove.setCursor(Qt.PointingHandCursor) + remove.setIcon(make_icon("close", "#aaa49b", 12)) + remove.setIconSize(QSize(12, 12)) + remove.setToolTip("从队列移除") + remove.clicked.connect(lambda: self.remove_requested.emit(self.index)) + row.addWidget(remove) + for label in (number, title, artist): + label.setAttribute(Qt.WA_TransparentForMouseEvents) + + def mouseReleaseEvent(self, event): + if event.button() == Qt.LeftButton: + self.play_requested.emit(self.index) + super().mouseReleaseEvent(event) + + +class QueuePopup(QFrame): + play_requested = Signal(int) + remove_requested = Signal(int) + clear_requested = Signal() def __init__(self, parent=None): - super().__init__(0, 4, parent) + super().__init__(parent, Qt.Popup | Qt.FramelessWindowHint | Qt.NoDropShadowWindowHint) + self.setAttribute(Qt.WA_TranslucentBackground) + self.setFixedWidth(380) + self.queue = [] + self.current_index = -1 + self._build() + + def _build(self): + outer = QVBoxLayout(self) + outer.setContentsMargins(10, 10, 10, 10) + card = QFrame(objectName="queueCard") + shadow = QGraphicsDropShadowEffect(card) + shadow.setBlurRadius(32) + shadow.setOffset(0, 10) + shadow.setColor(QColor(28, 27, 31, 55)) + card.setGraphicsEffect(shadow) + outer.addWidget(card) + + layout = QVBoxLayout(card) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + header = QHBoxLayout() + header.setContentsMargins(18, 15, 14, 9) + header.setSpacing(8) + title = QLabel("播放队列", objectName="queueTitle") + header.addWidget(title) + self.count = QLabel("0 首", objectName="queueCount") + header.addWidget(self.count) + header.addStretch() + self.clear_btn = QPushButton("清空", objectName="queueClear") + self.clear_btn.setCursor(Qt.PointingHandCursor) + self.clear_btn.setToolTip("清空队列,保留当前播放的歌曲") + self.clear_btn.clicked.connect(self.clear_requested) + header.addWidget(self.clear_btn) + layout.addLayout(header) + + self.scroll = QScrollArea(objectName="queueScroll") + self.scroll.setWidgetResizable(True) + self.scroll.setFrameShape(QFrame.NoFrame) + self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + body = QWidget(objectName="queueBody") + self.rows_layout = QVBoxLayout(body) + self.rows_layout.setContentsMargins(8, 2, 8, 10) + self.rows_layout.setSpacing(1) + self.scroll.setWidget(body) + layout.addWidget(self.scroll, 1) + + def set_queue(self, queue, current_index=-1, current_id=None): + self.queue = list(queue or []) + self.current_index = current_index + while self.rows_layout.count(): + item = self.rows_layout.takeAt(0) + if item.widget(): + item.widget().deleteLater() + self.count.setText("%d 首" % len(self.queue)) + self.clear_btn.setEnabled(bool(self.queue)) + scroll_index = -1 + if not self.queue: + empty = QLabel("播放队列是空的", objectName="queueEmpty") + empty.setAlignment(Qt.AlignCenter) + self.rows_layout.addWidget(empty) + self.rows_layout.addStretch() + else: + if current_id is not None: + scroll_index = next( + (i for i, song in enumerate(self.queue) if song.get("id") == current_id), -1) + for i, song in enumerate(self.queue): + row = QueueRow(i, song, song.get("id") == current_id) + row.play_requested.connect(self.play_requested) + row.remove_requested.connect(self.remove_requested) + self.rows_layout.addWidget(row) + self.rows_layout.addStretch() + visible_rows = max(1, min(len(self.queue), 7)) + self.setFixedHeight(104 + visible_rows * 60) + if scroll_index >= 0: + QTimer.singleShot(0, lambda: self.scroll.verticalScrollBar().setValue( + max(0, (scroll_index - 2) * 60))) + + def show_above(self, anchor): + screen = anchor.screen() + geo = screen.availableGeometry() + pos = anchor.mapToGlobal(QPoint(anchor.width(), 0)) + x = min(max(geo.left() + 8, pos.x() - self.width()), geo.right() - self.width() - 8) + y = pos.y() - self.height() - 10 + if y < geo.top() + 8: + y = geo.top() + 8 + self.move(x, y) + self.show() + self.raise_() + + +class TrackTable(QTableWidget): + play_requested = Signal(list, int) + next_requested = Signal(dict) + COLS = ["", "歌曲", "专辑", "时长", ""] + + def __init__(self, parent=None): + super().__init__(0, 5, parent) self.songs = [] self.playing_id = None self.covers = {} @@ -445,6 +595,8 @@ class TrackTable(QTableWidget): self.setColumnWidth(2, 190) header.setSectionResizeMode(3, QHeaderView.Fixed) self.setColumnWidth(3, 72) + header.setSectionResizeMode(4, QHeaderView.Fixed) + self.setColumnWidth(4, 42) for i, alignment in enumerate((Qt.AlignCenter, Qt.AlignLeft | Qt.AlignVCenter, Qt.AlignLeft | Qt.AlignVCenter, Qt.AlignRight | Qt.AlignVCenter)): item = self.horizontalHeaderItem(i) @@ -513,8 +665,21 @@ class TrackTable(QTableWidget): duration.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter) duration.setForeground(QColor("#aaa59c")) self.setItem(i, 3, duration) + more = QTableWidgetItem() + more.setIcon(make_icon("more", "#9d988f", 16)) + more.setTextAlignment(Qt.AlignCenter) + more.setToolTip("更多操作") + self.setItem(i, 4, more) self._paint_row_state(i) self.verticalScrollBar().setValue(0) + self.setIconSize(QSize(16, 16)) + + def _show_more_menu(self, song, position): + menu = QMenu(self) + action = menu.addAction("下一首播放") + action.triggered.connect(lambda: self.next_requested.emit(dict(song))) + menu.exec(position) + menu.deleteLater() def _paint_row_state(self, row, hover=None): if not (0 <= row < self.rowCount()): @@ -523,13 +688,14 @@ class TrackTable(QTableWidget): and self.songs[row].get("id") == self.playing_id) hot = self._hover_row if hover is None else hover color = QColor("#fdf3f1" if current else ("#f5f2ed" if row == hot else "#fffefa")) - for col in (0, 2, 3): + for col in (0, 1, 2, 3, 4): item = self.item(row, col) if item: item.setBackground(color) cell = self.cellWidget(row, 1) if cell: - cell.setStyleSheet("background:%s;" % color.name()) + cell.setAttribute(Qt.WA_StyledBackground, False) + cell.setStyleSheet("background:transparent;") def mouseMoveEvent(self, event): row = self.indexAt(event.position().toPoint()).row() @@ -586,8 +752,13 @@ class TrackTable(QTableWidget): def _on_click(self, index): row = index.row() - if 0 <= row < len(self.songs): - self.play_requested.emit(self.songs, row) + if not (0 <= row < len(self.songs)): + return + if index.column() == 4: + pos = self.viewport().mapToGlobal(self.visualRect(index).bottomLeft()) + self._show_more_menu(self.songs[row], pos) + return + self.play_requested.emit(self.songs, row) def icon_button(name, size=36, icon_size=18, color="#5f5b55", tooltip=""):