v0.24.1: bpm box in line with the now-playing panel
The boxed controls were centered on the whole bar while the now-playing panel hung from its top, so the bpm box sat ~11px below the panel beside it. The panel is now CONTROL_HEIGHT tall and every control shares its top edge; the seek row hangs under the panel. 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."""
|
||||
|
||||
__version__ = "0.24.0"
|
||||
__version__ = "0.24.1"
|
||||
|
||||
+25
-18
@@ -16,12 +16,10 @@ from lintunes import theme
|
||||
|
||||
ICON_GRAY = QColor("#4A4A4A")
|
||||
|
||||
# Height floor for the top control panel. It must exceed the natural stacked
|
||||
# height of title + artist + timeline (~69px at medium scale) so the leftover
|
||||
# becomes vertical slack that the equal stretches split into matching borders
|
||||
# above the title and below the artist. Bigger = more breathing room (and a
|
||||
# taller bar); the text block stays centered either way.
|
||||
BAR_HEIGHT = 84
|
||||
# Height floor for the top control panel: the row of CONTROL_HEIGHT boxes and
|
||||
# now-playing panel, the seek row hanging under the panel (~17px at medium
|
||||
# scale), and the 3px margins. Any leftover sits below the seek row.
|
||||
BAR_HEIGHT = 86
|
||||
|
||||
# Shared height for the boxed side controls (transport, shuffle, bpm) and the
|
||||
# visualizer, so they line up. ~midway between their old heights: the stretchy
|
||||
@@ -29,6 +27,10 @@ BAR_HEIGHT = 84
|
||||
# at ~46px. Tune to taste.
|
||||
CONTROL_HEIGHT = 62
|
||||
|
||||
# Every control in the bar hangs from the same top edge as the now-playing
|
||||
# panel (the seek row sits below the panel, not beside the boxes).
|
||||
TOP = Qt.AlignmentFlag.AlignTop
|
||||
|
||||
def _box_style(bg: QColor) -> str:
|
||||
return f"""
|
||||
QFrame#transportBox {{
|
||||
@@ -263,7 +265,7 @@ class TransportBar(QWidget):
|
||||
bg=box_bg, margins=(3, 3, 3, 3),
|
||||
height=CONTROL_HEIGHT, split=True)
|
||||
self._boxes.append(transport_box)
|
||||
layout.addWidget(transport_box)
|
||||
layout.addWidget(transport_box, alignment=TOP)
|
||||
|
||||
self._shuffle_btn = FlashButton("shuffle")
|
||||
self._shuffle_btn.setMinimumWidth(50)
|
||||
@@ -272,12 +274,12 @@ class TransportBar(QWidget):
|
||||
shuffle_box = _box(self._shuffle_btn, bg=box_bg, margins=(3, 3, 3, 3),
|
||||
height=CONTROL_HEIGHT, split=True)
|
||||
self._boxes.append(shuffle_box)
|
||||
layout.addWidget(shuffle_box)
|
||||
layout.addWidget(shuffle_box, alignment=TOP)
|
||||
|
||||
layout.addSpacing(10)
|
||||
self._visualizer = VisualizerWidget(player, prefs)
|
||||
self._visualizer.setFixedHeight(CONTROL_HEIGHT)
|
||||
layout.addWidget(self._visualizer)
|
||||
layout.addWidget(self._visualizer, alignment=TOP)
|
||||
layout.addSpacing(10)
|
||||
|
||||
# Master output volume: a slim horizontal slider in its own slot
|
||||
@@ -286,6 +288,9 @@ class TransportBar(QWidget):
|
||||
self._volume_slider = ClickJumpSlider()
|
||||
self._volume_slider.setRange(0, 100)
|
||||
self._volume_slider.setFixedWidth(96)
|
||||
# As tall as the boxes, so its groove (drawn centered) sits on the
|
||||
# same midline as everything else in the row.
|
||||
self._volume_slider.setFixedHeight(CONTROL_HEIGHT)
|
||||
self._volume_slider.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
||||
self._volume_slider.setToolTip("Volume")
|
||||
initial_volume = _clamp_volume(self._prefs.get("volume", 0.8))
|
||||
@@ -298,7 +303,7 @@ class TransportBar(QWidget):
|
||||
self._volume_save_timer.setInterval(400)
|
||||
self._volume_save_timer.timeout.connect(self._save_volume)
|
||||
self._volume_slider.valueChanged.connect(self._on_volume_changed)
|
||||
layout.addWidget(self._volume_slider)
|
||||
layout.addWidget(self._volume_slider, alignment=TOP)
|
||||
layout.addSpacing(10)
|
||||
|
||||
center = QVBoxLayout()
|
||||
@@ -338,21 +343,23 @@ class TransportBar(QWidget):
|
||||
seek_row.addWidget(self._slider)
|
||||
seek_row.addWidget(self._remaining_label)
|
||||
|
||||
# The now-playing panel fills the zone above the seek row; the seek row
|
||||
# stays pinned to the bottom so the timeline sits low. Its height is
|
||||
# just the slider + time labels, so the panel takes the rest.
|
||||
center.addWidget(self._now_playing, stretch=1)
|
||||
# The now-playing panel is exactly as tall as the boxes and shares
|
||||
# their top edge, so boxes, visualizer, panel and bpm read as one row;
|
||||
# the seek row hangs underneath the panel and any slack goes below it.
|
||||
self._now_playing.setFixedHeight(CONTROL_HEIGHT)
|
||||
center.addWidget(self._now_playing)
|
||||
center.addLayout(seek_row)
|
||||
center.addStretch(1)
|
||||
layout.addLayout(center, stretch=1)
|
||||
|
||||
# bpm tap button lives on the far right as its own box, vertically
|
||||
# centered like the transport boxes — kept out of the seek row so it
|
||||
# doesn't drive the timeline row's height.
|
||||
# bpm tap button lives on the far right as its own box, top-aligned
|
||||
# with the now-playing panel like the transport boxes — kept out of
|
||||
# the seek row so it doesn't drive the timeline row's height.
|
||||
self._bpm_button = BpmButton(player, manager)
|
||||
bpm_box = _box(self._bpm_button, bg=box_bg, margins=(8, 8, 8, 8),
|
||||
hug=True, height=CONTROL_HEIGHT)
|
||||
self._boxes.append(bpm_box)
|
||||
layout.addWidget(bpm_box)
|
||||
layout.addWidget(bpm_box, alignment=TOP)
|
||||
|
||||
# Wiring
|
||||
self._prev_btn.clicked.connect(player.previous)
|
||||
|
||||
@@ -875,6 +875,7 @@ class TestTransportLayout:
|
||||
bar.resize(1200, BAR_HEIGHT)
|
||||
bar.show()
|
||||
idle_y = bar._volume_slider.pos().y()
|
||||
idle_height = bar.sizeHint().height()
|
||||
|
||||
# The visualizer panel doubles as the cast indicator, so connecting
|
||||
# adds no widget and nothing moves.
|
||||
@@ -882,7 +883,9 @@ class TestTransportLayout:
|
||||
bar.layout().activate()
|
||||
|
||||
assert bar._volume_slider.pos().y() == idle_y
|
||||
assert bar.sizeHint().height() <= BAR_HEIGHT
|
||||
# Compared with idle rather than BAR_HEIGHT: the seek row's time
|
||||
# labels follow the app font, which other tests may have scaled up.
|
||||
assert bar.sizeHint().height() == idle_height
|
||||
bar.hide()
|
||||
|
||||
def test_clicking_the_visualizer_while_casting_disconnects(
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
"""Round 62: the bpm box lines up with the rest of the transport bar.
|
||||
|
||||
The boxed controls (transport, shuffle, visualizer, bpm) were centered on the
|
||||
whole bar while the now-playing panel hung from its top, so the bpm box sat
|
||||
~11px lower than the panel right beside it. Now the panel is CONTROL_HEIGHT
|
||||
tall and every control shares its top edge; the seek row sits under the panel.
|
||||
"""
|
||||
|
||||
from tests.test_round21 import _build_bar
|
||||
from lintunes.gui.transport import CONTROL_HEIGHT
|
||||
|
||||
|
||||
def test_every_control_shares_the_now_playing_row(qapp, tmp_path):
|
||||
bar = _build_bar(qapp, tmp_path)
|
||||
panel = bar._now_playing.geometry()
|
||||
assert panel.height() == CONTROL_HEIGHT
|
||||
for widget in (bar._prev_btn.parentWidget(), bar._shuffle_btn.parentWidget(),
|
||||
bar._visualizer, bar._volume_slider,
|
||||
bar._bpm_button.parentWidget()):
|
||||
g = widget.geometry()
|
||||
assert (g.top(), g.bottom()) == (panel.top(), panel.bottom())
|
||||
|
||||
|
||||
def test_seek_row_hangs_under_the_panel(qapp, tmp_path):
|
||||
bar = _build_bar(qapp, tmp_path)
|
||||
assert bar._slider.geometry().top() > bar._now_playing.geometry().bottom()
|
||||
Reference in New Issue
Block a user