Add per-area color preference sliders
Five grayscale (black->white) sliders in Preferences that update live:
outer-chrome background (QPalette.Window), the rounded transport button
boxes, the now-playing panel, the now-playing text, and the row stripes
(QPalette.AlternateBase). Defaults are None ("inherit current") so nothing
changes until the user drags. Decouples the button boxes from the stripe
color (they used to share palette alternate-base). Live preview via
Preferences.set_live (no disk write); persisted ~0.4s after the drag.
This commit is contained in:
@@ -3,14 +3,24 @@ from PyQt6.QtWidgets import (
|
||||
QRadioButton, QButtonGroup, QLineEdit, QPushButton, QCheckBox,
|
||||
QFormLayout,
|
||||
)
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtCore import Qt, QTimer
|
||||
from PyQt6.QtGui import QPixmap, QColor, QIcon
|
||||
|
||||
from lintunes import theme
|
||||
from lintunes.theme import HIGHLIGHT_COLORS
|
||||
|
||||
|
||||
_SCALES = ["small", "medium", "large"]
|
||||
|
||||
# (preference key, row label) for the per-area grayscale sliders.
|
||||
_COLOR_ROWS = [
|
||||
("color_background", "Background:"),
|
||||
("color_button", "Buttons:"),
|
||||
("color_now_playing_bg", "Now-playing panel:"),
|
||||
("color_now_playing_text", "Now-playing text:"),
|
||||
("color_stripe", "Stripes:"),
|
||||
]
|
||||
|
||||
|
||||
def _swatch(color_hex: str) -> QIcon:
|
||||
pixmap = QPixmap(18, 18)
|
||||
@@ -26,8 +36,16 @@ class PreferencesDialog(QDialog):
|
||||
self.setWindowTitle("Preferences")
|
||||
self.setMinimumWidth(420)
|
||||
|
||||
# Color sliders preview live (set_live emits without writing); persist
|
||||
# once the user pauses so a drag doesn't hammer the disk.
|
||||
self._color_save_timer = QTimer(self)
|
||||
self._color_save_timer.setSingleShot(True)
|
||||
self._color_save_timer.setInterval(400)
|
||||
self._color_save_timer.timeout.connect(self._prefs.save)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.addWidget(self._build_general_group())
|
||||
layout.addWidget(self._build_colors_group())
|
||||
layout.addWidget(self._build_lastfm_group())
|
||||
layout.addStretch()
|
||||
|
||||
@@ -81,6 +99,30 @@ class PreferencesDialog(QDialog):
|
||||
self._size_label.setText(scale.capitalize())
|
||||
self._prefs.set("ui_scale", scale)
|
||||
|
||||
# ---- Colors ----
|
||||
|
||||
def _build_colors_group(self) -> QGroupBox:
|
||||
group = QGroupBox("Colors")
|
||||
form = QFormLayout(group)
|
||||
note = QLabel("Each slider runs black → white and updates instantly.")
|
||||
note.setWordWrap(True)
|
||||
form.addRow(note)
|
||||
|
||||
self._color_sliders = {}
|
||||
for key, label in _COLOR_ROWS:
|
||||
slider = QSlider(Qt.Orientation.Horizontal)
|
||||
slider.setRange(0, 255)
|
||||
slider.setValue(theme.gray_value(self._prefs, key)) # before connect
|
||||
slider.valueChanged.connect(
|
||||
lambda value, k=key: self._on_color_changed(k, value))
|
||||
self._color_sliders[key] = slider
|
||||
form.addRow(label, slider)
|
||||
return group
|
||||
|
||||
def _on_color_changed(self, key: str, value: int):
|
||||
self._prefs.set_live(key, value) # live preview, no disk write yet
|
||||
self._color_save_timer.start() # persist once the drag settles
|
||||
|
||||
# ---- Last.fm ----
|
||||
|
||||
def _build_lastfm_group(self) -> QGroupBox:
|
||||
|
||||
+49
-21
@@ -29,19 +29,22 @@ BAR_HEIGHT = 84
|
||||
# at ~46px. Tune to taste.
|
||||
CONTROL_HEIGHT = 62
|
||||
|
||||
_BOX_STYLE = """
|
||||
QFrame#transportBox {
|
||||
def _box_style(bg: QColor) -> str:
|
||||
return f"""
|
||||
QFrame#transportBox {{
|
||||
border: 1px solid palette(mid);
|
||||
border-radius: 8px;
|
||||
background: palette(alternate-base);
|
||||
}
|
||||
background: {bg.name()};
|
||||
}}
|
||||
"""
|
||||
|
||||
_NOWPLAYING_STYLE = """
|
||||
QFrame#nowPlayingBox {
|
||||
background: white;
|
||||
|
||||
def _nowplaying_style(bg: QColor) -> str:
|
||||
return f"""
|
||||
QFrame#nowPlayingBox {{
|
||||
background: {bg.name()};
|
||||
border-radius: 8px;
|
||||
}
|
||||
}}
|
||||
"""
|
||||
|
||||
# Title sits this many points above the artist/album line, both following the
|
||||
@@ -86,15 +89,17 @@ class FlashButton(QToolButton):
|
||||
self.setIcon(transport_icon(self._kind, color))
|
||||
|
||||
|
||||
def _box(*widgets, margins=(10, 3, 10, 3), hug=False, height=None) -> QFrame:
|
||||
def _box(*widgets, bg: QColor, margins=(10, 3, 10, 3), hug=False,
|
||||
height=None) -> QFrame:
|
||||
"""A small rounded frame with its content centered inside.
|
||||
|
||||
With hug=True the frame stays at its size hint instead of stretching
|
||||
to fill the row it sits in. height pins the frame's height (width is
|
||||
left untouched), so several boxes can be lined up to the same height."""
|
||||
left untouched), so several boxes can be lined up to the same height.
|
||||
bg is the rounded-rectangle fill (a user-tunable grayscale)."""
|
||||
frame = QFrame()
|
||||
frame.setObjectName("transportBox")
|
||||
frame.setStyleSheet(_BOX_STYLE)
|
||||
frame.setStyleSheet(_box_style(bg))
|
||||
inner = QHBoxLayout(frame)
|
||||
inner.setContentsMargins(*margins)
|
||||
inner.addStretch()
|
||||
@@ -229,16 +234,25 @@ class TransportBar(QWidget):
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(8, 3, 8, 3)
|
||||
|
||||
# Rounded button boxes carry a user-tunable grayscale fill, decoupled
|
||||
# from the stripe color they used to share via palette(alternate-base).
|
||||
self._boxes = []
|
||||
box_bg = theme.gray(theme.gray_value(prefs, "color_button"))
|
||||
|
||||
self._prev_btn = FlashButton("previous")
|
||||
self._play_btn = FlashButton("play")
|
||||
self._next_btn = FlashButton("next")
|
||||
layout.addWidget(_box(self._prev_btn, self._play_btn, self._next_btn,
|
||||
height=CONTROL_HEIGHT))
|
||||
transport_box = _box(self._prev_btn, self._play_btn, self._next_btn,
|
||||
bg=box_bg, height=CONTROL_HEIGHT)
|
||||
self._boxes.append(transport_box)
|
||||
layout.addWidget(transport_box)
|
||||
|
||||
self._shuffle_btn = FlashButton("shuffle")
|
||||
self._shuffle_btn.setCheckable(True)
|
||||
self._shuffle_btn.setToolTip("Shuffle (doesn't change playlist order)")
|
||||
layout.addWidget(_box(self._shuffle_btn, height=CONTROL_HEIGHT))
|
||||
shuffle_box = _box(self._shuffle_btn, bg=box_bg, height=CONTROL_HEIGHT)
|
||||
self._boxes.append(shuffle_box)
|
||||
layout.addWidget(shuffle_box)
|
||||
|
||||
layout.addSpacing(10)
|
||||
self._visualizer = VisualizerWidget(player)
|
||||
@@ -274,7 +288,7 @@ class TransportBar(QWidget):
|
||||
# artist/album line, centered, filling the zone above the seek row.
|
||||
self._now_playing = QFrame()
|
||||
self._now_playing.setObjectName("nowPlayingBox")
|
||||
self._now_playing.setStyleSheet(_NOWPLAYING_STYLE)
|
||||
self._apply_now_playing_bg()
|
||||
np = QVBoxLayout(self._now_playing)
|
||||
np.setContentsMargins(12, 6, 12, 6)
|
||||
np.setSpacing(2)
|
||||
@@ -315,8 +329,10 @@ class TransportBar(QWidget):
|
||||
# centered 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)
|
||||
layout.addWidget(_box(self._bpm_button, margins=(8, 8, 8, 8), hug=True,
|
||||
height=CONTROL_HEIGHT))
|
||||
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)
|
||||
|
||||
# Wiring
|
||||
self._prev_btn.clicked.connect(player.previous)
|
||||
@@ -332,12 +348,22 @@ class TransportBar(QWidget):
|
||||
player.duration_changed.connect(self._on_duration_changed)
|
||||
|
||||
def refresh_theme(self):
|
||||
"""Rebuild button glyphs after a highlight-color or font change."""
|
||||
"""Re-apply everything driven by Preferences: button glyphs (highlight
|
||||
color), the tunable grayscale button boxes, and the now-playing panel
|
||||
background + text color."""
|
||||
for button in (self._prev_btn, self._play_btn, self._next_btn,
|
||||
self._shuffle_btn):
|
||||
button.refresh_icons()
|
||||
box_bg = theme.gray(theme.gray_value(self._prefs, "color_button"))
|
||||
for box in self._boxes:
|
||||
box.setStyleSheet(_box_style(box_bg))
|
||||
self._apply_now_playing_bg()
|
||||
self._apply_now_playing_font()
|
||||
|
||||
def _apply_now_playing_bg(self):
|
||||
bg = theme.gray(theme.gray_value(self._prefs, "color_now_playing_bg"))
|
||||
self._now_playing.setStyleSheet(_nowplaying_style(bg))
|
||||
|
||||
def _on_volume_changed(self, value: int):
|
||||
"""Slider moved: update audio live, defer the (theme-reapplying) save."""
|
||||
self._player.set_volume(value / 100)
|
||||
@@ -348,16 +374,18 @@ class TransportBar(QWidget):
|
||||
|
||||
def _apply_now_playing_font(self):
|
||||
"""Set the now-playing labels to Century Gothic (or the user's chosen
|
||||
substitute), black text; title slightly larger than artist/album.
|
||||
Sizes track the app font so they follow the UI-scale preference."""
|
||||
substitute); title slightly larger than artist/album. Sizes track the
|
||||
app font so they follow the UI-scale preference; the text color is the
|
||||
user-tunable grayscale (black by default)."""
|
||||
family = theme.now_playing_font_family(self._prefs)
|
||||
base_pt = self.font().pointSize()
|
||||
text_color = theme.gray(theme.gray_value(self._prefs, "color_now_playing_text"))
|
||||
for label, delta in ((self._title_label, TITLE_PT_BUMP),
|
||||
(self._artist_label, 0)):
|
||||
font = QFont(family) if family else QFont(self.font())
|
||||
font.setPointSize(max(1, base_pt + delta))
|
||||
label.setFont(font)
|
||||
label.setStyleSheet("color: black;")
|
||||
label.setStyleSheet(f"color: {text_color.name()};")
|
||||
|
||||
# ---- player events ----
|
||||
|
||||
|
||||
Reference in New Issue
Block a user