From d16025c84b513a74d6f81b817660ef63e9375be1 Mon Sep 17 00:00:00 2001 From: Shaowen Yin Date: Mon, 29 Jan 2024 17:46:30 +0800 Subject: [PATCH] gui: introduce ClickableMixin (#787) 1. click the accordion header can fold/unfold contents 2. click the cover label to show/hide nowplaying overlay (demo) --- feeluown/gui/helpers.py | 49 +++++++++++++++++++---- feeluown/gui/ui.py | 3 ++ feeluown/gui/uimain/nowplaying_overlay.py | 34 ++++++++++++++++ feeluown/gui/uimain/player_bar.py | 14 ++++++- feeluown/gui/widgets/accordion.py | 21 +++++----- 5 files changed, 100 insertions(+), 21 deletions(-) create mode 100644 feeluown/gui/uimain/nowplaying_overlay.py diff --git a/feeluown/gui/helpers.py b/feeluown/gui/helpers.py index e1b227a47c..e92c5f2ed0 100644 --- a/feeluown/gui/helpers.py +++ b/feeluown/gui/helpers.py @@ -25,14 +25,10 @@ from contextlib import contextmanager from typing import TypeVar, List, Optional, Generic, Union, cast, TYPE_CHECKING -try: - # helper module should work in no-window mode - from PyQt5.QtCore import QModelIndex, QSize, Qt, pyqtSignal, QSortFilterProxyModel, \ - QAbstractListModel - from PyQt5.QtGui import QPalette, QFontMetrics, QColor, QPainter - from PyQt5.QtWidgets import QApplication, QScrollArea, QWidget -except ImportError: - pass +from PyQt5.QtCore import QModelIndex, QSize, Qt, pyqtSignal, QSortFilterProxyModel, \ + QAbstractListModel, QPoint +from PyQt5.QtGui import QPalette, QFontMetrics, QColor, QPainter, QMouseEvent +from PyQt5.QtWidgets import QApplication, QScrollArea, QWidget from feeluown.utils.aio import run_afn, run_fn from feeluown.utils.reader import AsyncReader, Reader @@ -574,6 +570,43 @@ def secondary_text_color(palette: QPalette): return non_text_color +class ClickableMixin: + clicked = pyqtSignal() + + def __init__(self, **kwargs): + super().__init__(**kwargs) # Cooperative multi-inheritance. + + self._down = False + + def mousePressEvent(self, e: QMouseEvent): + if e.button() != Qt.LeftButton: + # Call super.mousePressEvent because the concrete class may do sth inside it. + super().mousePressEvent(e) + return + if self._hit_button(e.pos()): + self._down = True + e.accept() + else: + super().mousePressEvent(e) + + def mouseReleaseEvent(self, e: QMouseEvent): + if e.button() != Qt.LeftButton or not self._down: + super().mouseReleaseEvent(e) + return + self._down = False + if self._hit_button(e.pos()): + self.clicked.emit() + e.accept() + else: + super().mouseReleaseEvent(e) + + def _hit_button(self, pos: QPoint): + return self.rect().contains(pos) + + def set_down(self, down: bool): + self._down = down + + # https://ethanschoonover.com/solarized/ SOLARIZED_COLORS = { 'yellow': '#b58900', diff --git a/feeluown/gui/ui.py b/feeluown/gui/ui.py index d3b1514f18..2bcc0b11a4 100644 --- a/feeluown/gui/ui.py +++ b/feeluown/gui/ui.py @@ -11,6 +11,7 @@ from feeluown.gui.uimain.page_view import RightPanel from feeluown.gui.uimain.player_bar import TopPanel from feeluown.gui.uimain.playlist_overlay import PlaylistOverlay +from feeluown.gui.uimain.nowplaying_overlay import NowplayingOverlay logger = logging.getLogger(__name__) @@ -39,6 +40,7 @@ def __init__(self, app): self.toolbar = self.bottom_panel = self.right_panel.bottom_panel self.mpv_widget = MpvOpenGLWidget(self._app) self.playlist_overlay = PlaylistOverlay(app, parent=app) + self.nowplaying_overlay = NowplayingOverlay(app, parent=app) # alias self.magicbox = self.bottom_panel.magicbox @@ -64,6 +66,7 @@ def _setup_ui(self): self._splitter.addWidget(self.right_panel) self._message_line.hide() self.playlist_overlay.hide() + self.nowplaying_overlay.hide() self.right_panel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) diff --git a/feeluown/gui/uimain/nowplaying_overlay.py b/feeluown/gui/uimain/nowplaying_overlay.py new file mode 100644 index 0000000000..065bf303c2 --- /dev/null +++ b/feeluown/gui/uimain/nowplaying_overlay.py @@ -0,0 +1,34 @@ +from typing import TYPE_CHECKING, cast + +from PyQt5.QtCore import QEvent +from PyQt5.QtGui import QResizeEvent, QKeySequence +from PyQt5.QtWidgets import QWidget, QShortcut + +if TYPE_CHECKING: + from feeluown.app.gui_app import GuiApp + + +class NowplayingOverlay(QWidget): + """ + TODO + """ + + def __init__(self, app: 'GuiApp', parent=None): + super().__init__(parent=parent) + self._app = app + self._app.installEventFilter(self) + + QShortcut(QKeySequence.Cancel, self).activated.connect(self.hide) + + self.setStyleSheet('background: pink') + + def show(self): + self.resize(self._app.size()) + super().show() + + def eventFilter(self, obj, event): + if self.isVisible() and obj is self._app and event.type() == QEvent.Resize: + event = cast(QResizeEvent, event) + print('resize', event.size(), self.size()) + self.resize(event.size()) + return False diff --git a/feeluown/gui/uimain/player_bar.py b/feeluown/gui/uimain/player_bar.py index 0200f45580..c36a912572 100644 --- a/feeluown/gui/uimain/player_bar.py +++ b/feeluown/gui/uimain/player_bar.py @@ -14,7 +14,7 @@ LineSongLabel, MediaButtons, LyricButton, WatchButton, LikeButton, MVButton, PlaylistButton, SongSourceTag, ) -from feeluown.gui.helpers import IS_MACOS +from feeluown.gui.helpers import IS_MACOS, ClickableMixin if TYPE_CHECKING: from feeluown.app.gui_app import GuiApp @@ -22,6 +22,11 @@ logger = logging.getLogger(__name__) +class ClickableCover(ClickableMixin, CoverLabelV2): + def __init__(self, app, **kwargs): + super().__init__(app=app, **kwargs) + + class PlayerControlPanel(QFrame): def __init__(self, app: 'GuiApp', parent=None): @@ -69,7 +74,7 @@ def __init__(self, *args, **kwargs): self.song_title_label.setAlignment(Qt.AlignCenter) self.song_source_label = SongSourceTag(self._app, parent=self) - self.cover_label = CoverLabelV2(app) + self.cover_label = ClickableCover(app) self.duration_label = DurationLabel(app, parent=self) self.position_label = ProgressLabel(app, parent=self) @@ -82,6 +87,7 @@ def __init__(self, *args, **kwargs): player = self._app.player player.metadata_changed.connect(self.on_metadata_changed, aioqueue=True) player.volume_changed.connect(self.volume_btn.on_volume_changed) + self.cover_label.clicked.connect(self.show_nowplaying_overlay) self._setup_ui() @@ -185,6 +191,10 @@ def on_metadata_changed(self, metadata): else: self.cover_label.show_img(None) + def show_nowplaying_overlay(self): + self._app.ui.nowplaying_overlay.show() + self._app.ui.nowplaying_overlay.raise_() + class TopPanel(QFrame): def __init__(self, app, parent=None): diff --git a/feeluown/gui/widgets/accordion.py b/feeluown/gui/widgets/accordion.py index 1264f4dcb1..c0e1f98dcd 100644 --- a/feeluown/gui/widgets/accordion.py +++ b/feeluown/gui/widgets/accordion.py @@ -1,22 +1,20 @@ -from PyQt5.QtCore import pyqtSignal from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QHBoxLayout from feeluown.gui.widgets.textbtn import TextButton +from feeluown.gui.helpers import ClickableMixin -class ClickableHeader(QWidget): +class ClickableHeader(ClickableMixin, QWidget): btn_text_fold = '△' btn_text_unfold = '▼' - clicked = pyqtSignal() - def __init__(self, header, checked=False, *args, **kwargs): super().__init__(*args, **kwargs) + self._is_checked = False + self.inner_header = header - self.btn = TextButton(self._get_btn_text(checked)) - self.btn.setCheckable(True) - self.btn.setChecked(checked) + self.btn = TextButton(self._get_btn_text(self._is_checked)) self._layout = QHBoxLayout(self) self._layout.setContentsMargins(0, 0, 0, 0) @@ -25,11 +23,12 @@ def __init__(self, header, checked=False, *args, **kwargs): self._layout.addStretch(0) self._layout.addWidget(self.btn) - self.btn.clicked.connect(self.toggle) + self.btn.clicked.connect(self.clicked.emit) + self.clicked.connect(self.toggle) - def toggle(self, checked): - self.clicked.emit() - self.btn.setText(self._get_btn_text(checked)) + def toggle(self): + self._is_checked = not self._is_checked + self.btn.setText(self._get_btn_text(self._is_checked)) def _get_btn_text(self, checked): return self.btn_text_unfold if checked else self.btn_text_fold