v0.31.6: album art inside the playlists' frame; text and playhead lifted 2px

The playlist list and the album art under it share one frame (the list's
own style, drawn around both), and the no-art placeholder is a shade
lighter than that frame line so the line still reads around it. The
playlist name, times and playhead sit 2px up (HeaderRow.LIFT), on the
Library button's bottom line by eye.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 23:36:48 -07:00
co-authored by Claude Opus 5.5
parent 6f50248202
commit 8ffec1be82
4 changed files with 32 additions and 11 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
"""LinTunes — iTunes-style music library manager and player for Linux."""
__version__ = "0.31.5"
__version__ = "0.31.6"
+20 -6
View File
@@ -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(
+6 -2
View File
@@ -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)
+5 -2
View File
@@ -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):