v0.31.4: wider now-playing panel, tighter knolled top row
The now-playing panel (and the seek row under it) caps at 700px, up from 560. The top row gets one equal gap before, between and after its controls, capped at 28px; past that the extra splits between the two ends, so the row sits centered instead of spreading out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
"""LinTunes — iTunes-style music library manager and player for Linux."""
|
"""LinTunes — iTunes-style music library manager and player for Linux."""
|
||||||
|
|
||||||
__version__ = "0.31.3"
|
__version__ = "0.31.4"
|
||||||
|
|||||||
+32
-19
@@ -2,7 +2,7 @@ import time
|
|||||||
|
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QWidget, QFrame, QHBoxLayout, QVBoxLayout, QLabel, QToolButton, QSlider,
|
QWidget, QFrame, QHBoxLayout, QVBoxLayout, QLabel, QToolButton, QSlider,
|
||||||
QPushButton, QSizePolicy, QStyle, QLayout,
|
QPushButton, QSizePolicy, QStyle, QLayout, QSpacerItem,
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, QTimer, QEvent, QPoint, QRect, pyqtSignal
|
from PyQt6.QtCore import Qt, QTimer, QEvent, QPoint, QRect, pyqtSignal
|
||||||
from PyQt6.QtGui import QColor, QFont
|
from PyQt6.QtGui import QColor, QFont
|
||||||
@@ -20,16 +20,15 @@ ICON_GRAY = QColor("#4A4A4A")
|
|||||||
TOP_MARGIN = 9
|
TOP_MARGIN = 9
|
||||||
# Between the row of boxes and the header row under it.
|
# Between the row of boxes and the header row under it.
|
||||||
ROW_GAP = 4
|
ROW_GAP = 4
|
||||||
# Side margin of the row of boxes — the Library button's own left margin, so
|
# The now-playing panel grows with the window up to this width.
|
||||||
# the transport box and the Library button share a left edge.
|
NOW_PLAYING_MAX = 700
|
||||||
SIDE_MARGIN = 8
|
|
||||||
|
|
||||||
# The now-playing panel grows with the window up to this width; past it the
|
|
||||||
# extra goes to the gaps between the controls, evenly.
|
|
||||||
NOW_PLAYING_MAX = 560
|
|
||||||
NOW_PLAYING_MIN = 220
|
NOW_PLAYING_MIN = 220
|
||||||
# Floor for each of those gaps.
|
# The row of boxes is knolled: the space before the first control, between
|
||||||
|
# each pair and after the last is one equal gap, between these bounds. Once
|
||||||
|
# the gaps are as wide as they get, any extra splits between the two ends,
|
||||||
|
# so the row sits centered rather than spreading out.
|
||||||
MIN_GAP = 12
|
MIN_GAP = 12
|
||||||
|
MAX_GAP = 28
|
||||||
|
|
||||||
# Shared height for the boxed side controls (transport, shuffle, bpm) and the
|
# Shared height for the boxed side controls (transport, shuffle, bpm) and the
|
||||||
# visualizer, so they line up. ~midway between their old heights: the stretchy
|
# visualizer, so they line up. ~midway between their old heights: the stretchy
|
||||||
@@ -355,16 +354,20 @@ class TransportBar(QWidget):
|
|||||||
# window could never shrink past the width it last had.
|
# window could never shrink past the width it last had.
|
||||||
outer.setSizeConstraint(QLayout.SizeConstraint.SetNoConstraint)
|
outer.setSizeConstraint(QLayout.SizeConstraint.SetNoConstraint)
|
||||||
layout = QHBoxLayout()
|
layout = QHBoxLayout()
|
||||||
layout.setContentsMargins(SIDE_MARGIN, 0, SIDE_MARGIN, 0)
|
layout.setContentsMargins(MIN_GAP, 0, MIN_GAP, 0)
|
||||||
layout.setSpacing(0)
|
layout.setSpacing(0)
|
||||||
outer.addLayout(layout)
|
outer.addLayout(layout)
|
||||||
|
|
||||||
# Knolled: every control keeps its size and the gaps between them
|
# Knolled: every control keeps its size and the gaps between them
|
||||||
# are equal, growing together once the now-playing panel is as wide
|
# are equal; _size_now_playing sizes them (and the two ends).
|
||||||
# as it gets.
|
self._row = layout
|
||||||
|
self._gaps: list[QSpacerItem] = []
|
||||||
|
|
||||||
def gap():
|
def gap():
|
||||||
layout.addSpacing(MIN_GAP)
|
spacer = QSpacerItem(MIN_GAP, 0, QSizePolicy.Policy.Fixed,
|
||||||
layout.addStretch(1)
|
QSizePolicy.Policy.Minimum)
|
||||||
|
self._gaps.append(spacer)
|
||||||
|
layout.addSpacerItem(spacer)
|
||||||
|
|
||||||
# Rounded button boxes carry a user-tunable grayscale fill, decoupled
|
# Rounded button boxes carry a user-tunable grayscale fill, decoupled
|
||||||
# from the stripe color they used to share via palette(alternate-base).
|
# from the stripe color they used to share via palette(alternate-base).
|
||||||
@@ -466,8 +469,7 @@ class TransportBar(QWidget):
|
|||||||
# the seek row hangs underneath the panel, in the header row.
|
# the seek row hangs underneath the panel, in the header row.
|
||||||
self._now_playing.setFixedHeight(CONTROL_HEIGHT)
|
self._now_playing.setFixedHeight(CONTROL_HEIGHT)
|
||||||
# Its width is set by _size_now_playing: all the room the controls
|
# Its width is set by _size_now_playing: all the room the controls
|
||||||
# and minimal gaps leave, up to NOW_PLAYING_MAX; the gaps share the
|
# and minimal gaps leave, up to NOW_PLAYING_MAX.
|
||||||
# rest.
|
|
||||||
self._now_playing.setFixedWidth(NOW_PLAYING_MIN)
|
self._now_playing.setFixedWidth(NOW_PLAYING_MIN)
|
||||||
layout.addWidget(self._now_playing, alignment=TOP)
|
layout.addWidget(self._now_playing, alignment=TOP)
|
||||||
self._now_playing.installEventFilter(self)
|
self._now_playing.installEventFilter(self)
|
||||||
@@ -547,16 +549,27 @@ class TransportBar(QWidget):
|
|||||||
def _controls_width(self) -> int:
|
def _controls_width(self) -> int:
|
||||||
"""Everything in the row of boxes but the now-playing panel, with
|
"""Everything in the row of boxes but the now-playing panel, with
|
||||||
each gap at its floor."""
|
each gap at its floor."""
|
||||||
return (2 * SIDE_MARGIN
|
return (2 * MIN_GAP
|
||||||
# A fixed width shows in the minimum, not the size hint.
|
# A fixed width shows in the minimum, not the size hint.
|
||||||
+ sum(max(w.sizeHint().width(), w.minimumWidth())
|
+ sum(max(w.sizeHint().width(), w.minimumWidth())
|
||||||
for w in self._fixed_controls)
|
for w in self._fixed_controls)
|
||||||
+ len(self._fixed_controls) * MIN_GAP)
|
+ len(self._fixed_controls) * MIN_GAP)
|
||||||
|
|
||||||
def _size_now_playing(self):
|
def _size_now_playing(self):
|
||||||
|
"""Panel first, up to its cap; then equal gaps up to MAX_GAP; then
|
||||||
|
whatever is still left, split between the two ends."""
|
||||||
room = self.width() - self._controls_width()
|
room = self.width() - self._controls_width()
|
||||||
self._now_playing.setFixedWidth(
|
panel = max(NOW_PLAYING_MIN, min(NOW_PLAYING_MAX, room))
|
||||||
max(NOW_PLAYING_MIN, min(NOW_PLAYING_MAX, room)))
|
self._now_playing.setFixedWidth(panel)
|
||||||
|
controls = self._controls_width() - (2 + len(self._gaps)) * MIN_GAP
|
||||||
|
free = max(0, self.width() - controls - panel)
|
||||||
|
gap = max(MIN_GAP, min(MAX_GAP, free // (len(self._gaps) + 2)))
|
||||||
|
end = max(MIN_GAP, (free - gap * len(self._gaps)) // 2)
|
||||||
|
for spacer in self._gaps:
|
||||||
|
spacer.changeSize(gap, 0, QSizePolicy.Policy.Fixed,
|
||||||
|
QSizePolicy.Policy.Minimum)
|
||||||
|
self._row.setContentsMargins(end, 0, end, 0)
|
||||||
|
self._row.invalidate()
|
||||||
|
|
||||||
def minimumSizeHint(self):
|
def minimumSizeHint(self):
|
||||||
hint = super().minimumSizeHint()
|
hint = super().minimumSizeHint()
|
||||||
|
|||||||
@@ -113,3 +113,22 @@ def test_long_name_shrinks_then_elides(qapp):
|
|||||||
assert label.font().pointSize() >= base - 2
|
assert label.font().pointSize() >= base - 2
|
||||||
assert label.text().endswith("…")
|
assert label.text().endswith("…")
|
||||||
assert label.toolTip() == label.full_text()
|
assert label.toolTip() == label.full_text()
|
||||||
|
|
||||||
|
|
||||||
|
def test_row_is_knolled_with_equal_gaps_and_ends(qapp, tmp_path):
|
||||||
|
from lintunes.gui.transport import MAX_GAP, MIN_GAP
|
||||||
|
window = _window(qapp, tmp_path)
|
||||||
|
bar = window._transport
|
||||||
|
for width in (900, 1280, 1800):
|
||||||
|
window.resize(width, 500)
|
||||||
|
qapp.processEvents()
|
||||||
|
row = [bar._prev_btn.parentWidget(), bar._shuffle_btn.parentWidget(),
|
||||||
|
bar._visualizer, bar._volume_slider, bar._now_playing,
|
||||||
|
bar._bpm_button.parentWidget()]
|
||||||
|
edges = [(w.geometry().left(), w.geometry().right()) for w in row]
|
||||||
|
gaps = [b[0] - a[1] - 1 for a, b in zip(edges, edges[1:])]
|
||||||
|
assert max(gaps) - min(gaps) <= 1
|
||||||
|
assert MIN_GAP <= gaps[0] <= MAX_GAP
|
||||||
|
left_end, right_end = edges[0][0], bar.width() - 1 - edges[-1][1]
|
||||||
|
assert abs(left_end - right_end) <= 1
|
||||||
|
assert left_end >= gaps[0] - 1
|
||||||
|
|||||||
Reference in New Issue
Block a user