Files
lintunes/lintunes/preferences.py
T
trav dcbd288b31 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.
2026-06-26 21:41:49 -04:00

90 lines
3.0 KiB
Python

import copy
import json
from pathlib import Path
from PyQt6.QtCore import QObject, pyqtSignal
DEFAULTS = {
"ui_scale": "medium", # small | medium | large
"highlight": "blue", # see theme.HIGHLIGHT_COLORS
"now_playing_font": None, # None=prefer Century Gothic, ""=use app
# default, "Family"=use app
"volume": 0.8, # logical 0..1 master output volume
# Per-area grayscale overrides (0=black .. 255=white). None = inherit the
# current/default look for that surface, so nothing changes until the user
# drags a slider in Preferences.
"color_background": None, # outer chrome (QPalette.Window)
"color_button": None, # rounded transport button boxes
"color_now_playing_bg": None, # panel behind the now-playing text
"color_now_playing_text": None, # the now-playing title/artist text
"color_stripe": None, # alternating row stripes (QPalette.AlternateBase)
"lastfm": {
"api_key": "",
"api_secret": "",
"username": "",
"session_key": "",
"scrobble_enabled": False,
},
}
class Preferences(QObject):
"""App preferences persisted to <data_dir>/preferences.json (so they ride
the same syncthing share as the library)."""
changed = pyqtSignal()
def __init__(self, data_dir: Path, parent=None):
super().__init__(parent)
self._path = Path(data_dir) / "preferences.json"
self._data = copy.deepcopy(DEFAULTS)
if self._path.exists():
try:
with open(self._path, "r", encoding="utf-8") as f:
stored = json.load(f)
_deep_merge(self._data, stored)
except (ValueError, OSError):
pass
def get(self, key: str, default=None):
return self._data.get(key, default)
def set(self, key: str, value):
if self._data.get(key) == value:
return
self._data[key] = value
self.save()
self.changed.emit()
def set_live(self, key: str, value):
"""Update a preference and notify listeners *without* writing to disk —
for live drag-preview (e.g. the color sliders). Call save() to persist
once the user stops dragging."""
self._data[key] = value
self.changed.emit()
def update_lastfm(self, **fields):
self._data["lastfm"].update(fields)
self.save()
self.changed.emit()
@property
def lastfm(self) -> dict:
return self._data["lastfm"]
def save(self):
self._path.parent.mkdir(parents=True, exist_ok=True)
tmp = self._path.with_suffix(".json.tmp")
with open(tmp, "w", encoding="utf-8") as f:
json.dump(self._data, f, indent=2)
tmp.replace(self._path)
def _deep_merge(base: dict, override: dict):
for key, value in override.items():
if isinstance(value, dict) and isinstance(base.get(key), dict):
_deep_merge(base[key], value)
else:
base[key] = value