v0.51.2: album art sits in the empty art panel's own frame, below the list, instead of inside the list's frame
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
"""LinTunes — iTunes-style music library manager and player for Linux."""
|
||||
|
||||
__version__ = "0.51.1"
|
||||
__version__ = "0.51.2"
|
||||
|
||||
+28
-52
@@ -2,9 +2,9 @@ from PyQt6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QPushButton, QTreeWidget, QTreeWidgetItem, QMenu,
|
||||
QInputDialog, QMessageBox, QAbstractItemView, QLineEdit, QApplication,
|
||||
QHBoxLayout, QListWidget, QListWidgetItem, QStackedWidget, QSizePolicy,
|
||||
QFrame, QStyle, QStyleOptionFrame,
|
||||
QFrame,
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QTimer, QPoint, QRect, QRectF, pyqtSignal
|
||||
from PyQt6.QtCore import Qt, QTimer, QPoint, QRectF, pyqtSignal
|
||||
from PyQt6.QtGui import QBrush, QPainter, QPalette, QPixmap
|
||||
|
||||
from lintunes.models import PlaylistType
|
||||
@@ -76,9 +76,7 @@ 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.
|
||||
# The playlists in their frame; the album art panel sits below it.
|
||||
group = QFrame()
|
||||
self._lists = QStackedWidget()
|
||||
self.tree = PlaylistTree(manager)
|
||||
@@ -95,9 +93,9 @@ class SidebarPanel(QWidget):
|
||||
inner.addWidget(self._lists, stretch=1)
|
||||
self.art = SidebarArt(player)
|
||||
layout.addWidget(group, stretch=1)
|
||||
self._inner, self._outer = inner, layout
|
||||
self.art.has_art_changed.connect(self._place_art)
|
||||
self._place_art(False)
|
||||
# Art or none, the same panel in the same place: a cover changes
|
||||
# what is in the panel, never the frame around it.
|
||||
layout.addWidget(self.art)
|
||||
# What the header row lines the Library button up with.
|
||||
self.list_area = group
|
||||
|
||||
@@ -109,14 +107,6 @@ class SidebarPanel(QWidget):
|
||||
self.friend_list.playlist_selected.connect(self._on_friend_playlist)
|
||||
self.friend_list.follow_toggled.connect(self.follow_toggled)
|
||||
|
||||
def _place_art(self, has_art: bool):
|
||||
"""Art goes inside the list's frame; the empty panel below it, on
|
||||
the window, where it reads as set into the window like the
|
||||
visualizer rather than as a box inside a white one."""
|
||||
for lay in (self._inner, self._outer):
|
||||
lay.removeWidget(self.art)
|
||||
(self._inner if has_art else self._outer).addWidget(self.art)
|
||||
|
||||
def _on_library_clicked(self):
|
||||
self.library_button.setChecked(True)
|
||||
if self.friend_mode:
|
||||
@@ -263,22 +253,18 @@ class FriendPlaylistList(QListWidget):
|
||||
self.playlist_selected.emit(current.data(Qt.ItemDataRole.UserRole))
|
||||
|
||||
|
||||
# The no-art panel: on the window under the list's frame, this far below
|
||||
# it, with the visualizer's corners.
|
||||
# The art panel: on the window under the list's frame, this far below it,
|
||||
# with the visualizer's corners.
|
||||
NO_ART_GAP = 6
|
||||
NO_ART_RADIUS = 8
|
||||
# Art sits inside the list's frame, so the frame never closes between them;
|
||||
# the art draws that closing line itself, this tall, so a cover with a white
|
||||
# border doesn't run straight on from the list.
|
||||
ART_RULE = 1
|
||||
|
||||
|
||||
class SidebarArt(QWidget):
|
||||
"""Square album art pinned to the sidebar bottom, inside the playlist
|
||||
list's frame. With no embedded art it is an empty panel instead, set
|
||||
into the window below the frame like the visualizer (SidebarPanel moves
|
||||
it on ``has_art_changed``). A click opens the big art window, or, on the
|
||||
empty panel, asks for the track's art to be downloaded."""
|
||||
"""Square album art pinned to the sidebar bottom: a panel set into the
|
||||
window below the playlist list's frame, like the visualizer, with the
|
||||
cover inside its border. With no embedded art the panel is empty. A
|
||||
click opens the big art window, or, on the empty panel, asks for the
|
||||
track's art to be downloaded."""
|
||||
|
||||
download_art_requested = pyqtSignal(int) # track id
|
||||
has_art_changed = pyqtSignal(bool)
|
||||
@@ -329,8 +315,8 @@ class SidebarArt(QWidget):
|
||||
|
||||
def _fit_height(self):
|
||||
# Keep it square: height follows the sidebar's width (plus the gap
|
||||
# above the empty panel)
|
||||
height = self.width() + (ART_RULE if self.has_art() else NO_ART_GAP)
|
||||
# above the panel)
|
||||
height = self.width() + NO_ART_GAP
|
||||
if self.height() != height:
|
||||
self.setFixedHeight(height)
|
||||
|
||||
@@ -351,40 +337,30 @@ class SidebarArt(QWidget):
|
||||
super().mouseReleaseEvent(event)
|
||||
|
||||
def paintEvent(self, event):
|
||||
painter = QPainter(self)
|
||||
if self._pixmap is None or self._pixmap.isNull():
|
||||
# No art: straight onto the window, a panel debossed in the
|
||||
# visualizer's color and shaded at its edges as the visualizer is.
|
||||
# Straight onto the window, a panel debossed in the visualizer's
|
||||
# color and shaded at its edges as the visualizer is.
|
||||
from lintunes.gui.transport import CONTROL_HEIGHT
|
||||
from PyQt6.QtGui import QPainterPath
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||
rect = QRectF(self.rect()).adjusted(0.5, NO_ART_GAP + 0.5, -0.5, -0.5)
|
||||
theme.paint_deboss(painter, rect,
|
||||
self.palette().alternateBase().color(),
|
||||
NO_ART_RADIUS, like=CONTROL_HEIGHT)
|
||||
if self._pixmap is None or self._pixmap.isNull():
|
||||
return
|
||||
# The list frame's own bottom edge, drawn by the style: a frame
|
||||
# whose bottom row is this widget's top row, its sides pushed out of
|
||||
# view, so the line is exactly the one under the list with no art.
|
||||
option = QStyleOptionFrame()
|
||||
option.initFrom(self)
|
||||
option.frameShape = QFrame.Shape.StyledPanel
|
||||
option.state |= QStyle.StateFlag.State_Sunken
|
||||
option.lineWidth = option.midLineWidth = 0
|
||||
frame = self.style().pixelMetric(QStyle.PixelMetric.PM_DefaultFrameWidth)
|
||||
option.rect = QRect(-frame - 4, ART_RULE - 4 * frame - 8,
|
||||
self.width() + 2 * frame + 8, 4 * frame + 8)
|
||||
painter.save()
|
||||
painter.setClipRect(0, 0, self.width(), ART_RULE)
|
||||
self.style().drawPrimitive(QStyle.PrimitiveElement.PE_Frame,
|
||||
option, painter, self)
|
||||
painter.restore()
|
||||
area = self.rect().adjusted(0, ART_RULE, 0, 0)
|
||||
# The cover fills the panel inside its border, so the border and its
|
||||
# darker top edge stay where they are with or without art.
|
||||
inside = rect.adjusted(1, 1, -1, -1)
|
||||
clip = QPainterPath()
|
||||
clip.addRoundedRect(inside, NO_ART_RADIUS - 1, NO_ART_RADIUS - 1)
|
||||
painter.setClipPath(clip)
|
||||
area = inside.toAlignedRect()
|
||||
scaled = self._pixmap.scaled(
|
||||
area.size(), Qt.AspectRatioMode.KeepAspectRatioByExpanding,
|
||||
Qt.TransformationMode.SmoothTransformation)
|
||||
x = (area.width() - scaled.width()) // 2
|
||||
x = area.left() + (area.width() - scaled.width()) // 2
|
||||
y = area.top() + (area.height() - scaled.height()) // 2
|
||||
painter.setClipRect(area)
|
||||
painter.drawPixmap(x, y, scaled)
|
||||
|
||||
|
||||
|
||||
@@ -136,24 +136,6 @@ def test_no_art_panel_sits_below_the_frame(qapp, tmp_path):
|
||||
window.close()
|
||||
|
||||
|
||||
def test_real_art_goes_inside_the_frame(qapp, tmp_path):
|
||||
from PyQt6.QtGui import QPixmap
|
||||
from lintunes.gui.sidebar import ART_RULE
|
||||
window, sidebar = _sidebar(qapp, tmp_path)
|
||||
art = sidebar.art
|
||||
pixmap = QPixmap(10, 10)
|
||||
pixmap.fill(QColor("red"))
|
||||
art._pixmap = pixmap
|
||||
art.has_art_changed.emit(True)
|
||||
art._fit_height()
|
||||
assert art.parentWidget() is sidebar.list_area
|
||||
assert art.height() == art.width() + ART_RULE
|
||||
art._pixmap = None
|
||||
art.set_track(None)
|
||||
assert art.parentWidget() is sidebar
|
||||
window.close()
|
||||
|
||||
|
||||
def test_tall_deboss_copies_a_short_panels_edges(qapp):
|
||||
"""Shaded ``like`` a 62 px panel, a 400 px one matches it row for row
|
||||
along the top, and is flat in the middle."""
|
||||
|
||||
+33
-15
@@ -1,27 +1,45 @@
|
||||
"""Round 82: the list frame closes above album art too."""
|
||||
"""Round 82: album art sits in the same panel, in the same place, as the
|
||||
empty art panel: the cover changes what is inside the frame, never the
|
||||
frame."""
|
||||
from PyQt6.QtGui import QColor, QPixmap
|
||||
|
||||
from tests.test_round80 import _sidebar
|
||||
|
||||
|
||||
def test_art_draws_the_frames_closing_line(qapp, tmp_path):
|
||||
"""A white cover inside the list's frame starts under a line in the
|
||||
frame's own color, not straight on from the list."""
|
||||
from lintunes.gui.sidebar import ART_RULE
|
||||
window, sidebar = _sidebar(qapp, tmp_path)
|
||||
art = sidebar.art
|
||||
def _show_art(qapp, art, color):
|
||||
pixmap = QPixmap(10, 10)
|
||||
pixmap.fill(QColor("white"))
|
||||
pixmap.fill(QColor(color))
|
||||
art._pixmap = pixmap
|
||||
art.has_art_changed.emit(True)
|
||||
art._fit_height()
|
||||
qapp.processEvents()
|
||||
group = sidebar.list_area.grab().toImage()
|
||||
# the frame's own left edge, halfway down the list
|
||||
frame = group.pixelColor(0, group.height() // 4)
|
||||
|
||||
|
||||
def test_art_keeps_the_empty_panels_place_and_border(qapp, tmp_path):
|
||||
from lintunes.gui.sidebar import NO_ART_GAP
|
||||
window, sidebar = _sidebar(qapp, tmp_path)
|
||||
art = sidebar.art
|
||||
empty_geometry = art.geometry()
|
||||
empty = art.grab().toImage()
|
||||
_show_art(qapp, art, "white")
|
||||
assert art.parentWidget() is sidebar
|
||||
assert art.geometry() == empty_geometry
|
||||
assert art.height() == art.width() + NO_ART_GAP
|
||||
image = art.grab().toImage()
|
||||
middle = image.width() // 2
|
||||
for y in range(ART_RULE):
|
||||
assert image.pixelColor(middle, y) == frame
|
||||
assert image.pixelColor(middle, ART_RULE) == QColor("white")
|
||||
# the gap and the panel's darker top edge, unchanged
|
||||
for y in range(NO_ART_GAP + 1):
|
||||
assert image.pixelColor(middle, y) == empty.pixelColor(middle, y)
|
||||
# ...and the cover just inside it
|
||||
assert image.pixelColor(middle, NO_ART_GAP + 2) == QColor("white")
|
||||
assert image.pixelColor(middle, image.height() // 2) == QColor("white")
|
||||
window.close()
|
||||
|
||||
|
||||
def test_art_changes_no_layout(qapp, tmp_path):
|
||||
window, sidebar = _sidebar(qapp, tmp_path)
|
||||
list_geometry = sidebar.list_area.geometry()
|
||||
_show_art(qapp, sidebar.art, "red")
|
||||
sidebar.art.has_art_changed.emit(True)
|
||||
qapp.processEvents()
|
||||
assert sidebar.list_area.geometry() == list_geometry
|
||||
window.close()
|
||||
|
||||
Reference in New Issue
Block a user