diff --git a/lintunes/__init__.py b/lintunes/__init__.py index a0de2ea..8156cc1 100644 --- a/lintunes/__init__.py +++ b/lintunes/__init__.py @@ -1,3 +1,3 @@ """LinTunes — iTunes-style music library manager and player for Linux.""" -__version__ = "0.31.5" +__version__ = "0.31.6" diff --git a/lintunes/gui/sidebar.py b/lintunes/gui/sidebar.py index 05bbddf..0decca9 100644 --- a/lintunes/gui/sidebar.py +++ b/lintunes/gui/sidebar.py @@ -2,6 +2,7 @@ from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QPushButton, QTreeWidget, QTreeWidgetItem, QMenu, QInputDialog, QMessageBox, QAbstractItemView, QLineEdit, QApplication, QHBoxLayout, QListWidget, QListWidgetItem, QStackedWidget, QSizePolicy, + QFrame, ) from PyQt6.QtCore import Qt, QTimer, QPoint, pyqtSignal from PyQt6.QtGui import QBrush, QPainter, QPalette, QPixmap @@ -75,17 +76,28 @@ class SidebarPanel(QWidget): self.header = top top.header_owner = self + # The playlists and the album art under them are one group inside + # one frame — the list's own frame, drawn around both, so the art + # sits within it rather than hanging below. + group = QFrame() self._lists = QStackedWidget() self.tree = PlaylistTree(manager) self._lists.addWidget(self.tree) self.friend_list = FriendPlaylistList() self._lists.addWidget(self.friend_list) - layout.addWidget(self._lists, stretch=1) - # What the header row lines the Library button up with. - self.list_area = self._lists - + group.setFrameShape(self.tree.frameShape()) + group.setFrameShadow(self.tree.frameShadow()) + for view in (self.tree, self.friend_list): + view.setFrameShape(QFrame.Shape.NoFrame) + inner = QVBoxLayout(group) + inner.setContentsMargins(0, 0, 0, 0) + inner.setSpacing(0) + inner.addWidget(self._lists, stretch=1) self.art = SidebarArt(player) - layout.addWidget(self.art) + inner.addWidget(self.art) + layout.addWidget(group, stretch=1) + # What the header row lines the Library button up with. + self.list_area = group self.library_button.clicked.connect(self._on_library_clicked) self.tree.playlist_selected.connect(self._on_playlist_selected) @@ -305,7 +317,9 @@ class SidebarArt(QWidget): def paintEvent(self, event): painter = QPainter(self) - painter.fillRect(self.rect(), self.palette().mid().color()) + # A shade lighter than mid: the sidebar group's frame line is drawn + # in mid, and would vanish into a placeholder of the same gray. + painter.fillRect(self.rect(), self.palette().mid().color().lighter(112)) if self._pixmap is None or self._pixmap.isNull(): return scaled = self._pixmap.scaled( diff --git a/lintunes/gui/transport.py b/lintunes/gui/transport.py index f51bbce..410f03e 100644 --- a/lintunes/gui/transport.py +++ b/lintunes/gui/transport.py @@ -276,6 +276,10 @@ class HeaderRow(QWidget): # lists: every item in the row stands on one floor, BOTTOM px up. TOP_PAD = 2 BOTTOM = 5 + # Text and the playhead have no hard bottom edge, so on the floor itself + # they read as sitting lower than the Library button beside them; this + # lifts them onto the button's bottom line by eye. + LIFT = 2 def __init__(self, seek: QWidget, parent=None): super().__init__(parent) @@ -336,7 +340,7 @@ class HeaderRow(QWidget): self.seek.layout().invalidate() self.seek.setGeometry(seek_left, self.TOP_PAD, max(0, panel.right() + 1 - seek_left), - box_h + drop) + box_h + drop - self.LIFT) header = self.view_header if header is not None: width = max(0, seek_left - self.GAP - content_left) @@ -344,7 +348,7 @@ class HeaderRow(QWidget): header.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBottom) header.setGeometry(content_left, self.TOP_PAD, width, - box_h + self._drop(header)) + box_h + self._drop(header) - self.LIFT) else: header.setGeometry(content_left, self.TOP_PAD, width, box_h) diff --git a/tests/test_round69.py b/tests/test_round69.py index 3a9dde3..f84ad52 100644 --- a/tests/test_round69.py +++ b/tests/test_round69.py @@ -52,6 +52,8 @@ def test_library_button_spans_the_playlist_list(qapp, tmp_path): window = _window(qapp, tmp_path) header = window.sidebar.header lists = window.sidebar.list_area + # The frame around the playlists takes in the album art too. + assert window.sidebar.art.parentWidget() is lists assert header.parentWidget() is not window.sidebar assert _gx(header) == _gx(lists) > _gx(window) assert header.width() == lists.width() @@ -71,12 +73,13 @@ def test_header_row_items_share_one_floor(qapp, tmp_path): assert bottom(window.sidebar.library_button) == floor assert bottom(window._library_view._search) == floor - assert bottom(window._transport._slider) == floor + # Text and the playhead sit LIFT px up, on the button's bottom line by eye. + assert bottom(window._transport._slider) == floor - HeaderRow.LIFT window._show_playlist("abcd1234") qapp.processEvents() label = window._playlist_view.header baseline = bottom(label) - label.fontMetrics().descent() - assert abs(baseline - floor) <= 1 + assert abs(baseline - (floor - HeaderRow.LIFT)) <= 1 def test_search_then_title_sit_where_the_view_starts(qapp, tmp_path):