UI overhaul: icon buttons, video controls, popout anchor, layout flip, compact top bar

- Preview/popout toolbar: icon buttons (☆/★, ↓/✕, ⊘, ⊗, ⧉) with QSS
  object names (#_tb_bookmark, #_tb_save, etc.) for theme targeting
- Video controls: QPainter-drawn icons for play/pause, volume/mute;
  text labels for loop/once/next and autoplay
- Popout anchor setting: resize pivot (center/tl/tr/bl/br) controls
  which corner stays fixed on aspect change, works on all platforms
- Hyprland monitor reserved areas: reads waybar exclusive zones from
  hyprctl monitors -j for correct edge positioning
- Layout flip setting: swap grid and preview sides
- Compact top bar: AdjustToContents combos, tighter spacing, named
  containers (#_top_bar, #_nav_bar) for QSS targeting
- Reduced main window minimum size from 900x600 to 740x400
- Trimmed bundled QSS: removed 12 unused widget selectors, added
  popout overlay font-weight/size, regenerated all 12 theme files
- Updated themes/README.md with icon button reference
This commit is contained in:
pax 2026-04-10 19:58:11 -05:00
parent d7b3c304d7
commit 93459dfff6
22 changed files with 895 additions and 2025 deletions

View File

@ -119,6 +119,8 @@ QWidget#_slideshow_controls QPushButton {
color: white; color: white;
border: 1px solid rgba(255, 255, 255, 80); border: 1px solid rgba(255, 255, 255, 80);
padding: 2px 6px; padding: 2px 6px;
font-size: 15px;
font-weight: bold;
} }
QWidget#_slideshow_toolbar QPushButton:hover, QWidget#_slideshow_toolbar QPushButton:hover,
QWidget#_slideshow_controls QPushButton:hover { QWidget#_slideshow_controls QPushButton:hover {

View File

@ -63,7 +63,7 @@ class BooruApp(QMainWindow):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self.setWindowTitle("booru-viewer") self.setWindowTitle("booru-viewer")
self.setMinimumSize(900, 600) self.setMinimumSize(740, 400)
self.resize(1200, 800) self.resize(1200, 800)
self._db = Database() self._db = Database()
@ -224,17 +224,22 @@ class BooruApp(QMainWindow):
layout.setSpacing(6) layout.setSpacing(6)
# Top bar: site selector + rating + search # Top bar: site selector + rating + search
top = QHBoxLayout() _top_bar = QWidget()
_top_bar.setObjectName("_top_bar")
top = QHBoxLayout(_top_bar)
top.setContentsMargins(0, 0, 0, 0)
top.setSpacing(3)
self._site_combo = QComboBox() self._site_combo = QComboBox()
self._site_combo.setMinimumWidth(150) self._site_combo.setMinimumWidth(80)
self._site_combo.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContents)
self._site_combo.currentIndexChanged.connect(self._on_site_changed) self._site_combo.currentIndexChanged.connect(self._on_site_changed)
top.addWidget(self._site_combo) top.addWidget(self._site_combo)
# Rating filter # Rating filter
self._rating_combo = QComboBox() self._rating_combo = QComboBox()
self._rating_combo.addItems(["All", "General", "Sensitive", "Questionable", "Explicit"]) self._rating_combo.addItems(["All", "General", "Sensitive", "Questionable", "Explicit"])
self._rating_combo.setMinimumWidth(100) self._rating_combo.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContents)
self._rating_combo.currentTextChanged.connect(self._on_rating_changed) self._rating_combo.currentTextChanged.connect(self._on_rating_changed)
top.addWidget(self._rating_combo) top.addWidget(self._rating_combo)
@ -242,23 +247,17 @@ class BooruApp(QMainWindow):
self._media_filter = QComboBox() self._media_filter = QComboBox()
self._media_filter.addItems(["All", "Animated", "Video", "GIF", "Audio"]) self._media_filter.addItems(["All", "Animated", "Video", "GIF", "Audio"])
self._media_filter.setToolTip("Filter by media type") self._media_filter.setToolTip("Filter by media type")
self._media_filter.setFixedWidth(90) self._media_filter.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContents)
top.addWidget(self._media_filter) top.addWidget(self._media_filter)
# Score filter — type the value directly. Spinbox arrows hidden # Score filter
# since the field is small enough to type into and the +/- buttons score_label = QLabel("Score\u2265")
# were just visual noise. setFixedHeight(23) overrides Qt's
# QSpinBox sizeHint which still reserves vertical space for the
# arrow buttons internally even when `setButtonSymbols(NoButtons)`
# is set, leaving the spinbox 3px taller than the surrounding
# combos/inputs/buttons in the top toolbar (26 vs 23).
score_label = QLabel("Score≥")
top.addWidget(score_label) top.addWidget(score_label)
self._score_spin = QSpinBox() self._score_spin = QSpinBox()
self._score_spin.setRange(0, 99999) self._score_spin.setRange(0, 99999)
self._score_spin.setValue(0) self._score_spin.setValue(0)
self._score_spin.setFixedWidth(40) self._score_spin.setFixedWidth(36)
self._score_spin.setFixedHeight(23) self._score_spin.setFixedHeight(21)
self._score_spin.setButtonSymbols(QSpinBox.ButtonSymbols.NoButtons) self._score_spin.setButtonSymbols(QSpinBox.ButtonSymbols.NoButtons)
top.addWidget(self._score_spin) top.addWidget(self._score_spin)
@ -267,8 +266,8 @@ class BooruApp(QMainWindow):
self._page_spin = QSpinBox() self._page_spin = QSpinBox()
self._page_spin.setRange(1, 99999) self._page_spin.setRange(1, 99999)
self._page_spin.setValue(1) self._page_spin.setValue(1)
self._page_spin.setFixedWidth(40) self._page_spin.setFixedWidth(36)
self._page_spin.setFixedHeight(23) # match the surrounding 23px row self._page_spin.setFixedHeight(21)
self._page_spin.setButtonSymbols(QSpinBox.ButtonSymbols.NoButtons) self._page_spin.setButtonSymbols(QSpinBox.ButtonSymbols.NoButtons)
top.addWidget(self._page_spin) top.addWidget(self._page_spin)
@ -277,10 +276,15 @@ class BooruApp(QMainWindow):
self._search_bar.autocomplete_requested.connect(self._search_ctrl.request_autocomplete) self._search_bar.autocomplete_requested.connect(self._search_ctrl.request_autocomplete)
top.addWidget(self._search_bar, stretch=1) top.addWidget(self._search_bar, stretch=1)
layout.addLayout(top) layout.addWidget(_top_bar)
# Nav bar # Nav bar
nav = QHBoxLayout() _nav_bar = QWidget()
_nav_bar.setObjectName("_nav_bar")
nav = QHBoxLayout(_nav_bar)
nav.setContentsMargins(0, 0, 0, 0)
nav.setSpacing(3)
self._browse_btn = QPushButton("Browse") self._browse_btn = QPushButton("Browse")
self._browse_btn.setCheckable(True) self._browse_btn.setCheckable(True)
self._browse_btn.setChecked(True) self._browse_btn.setChecked(True)
@ -294,11 +298,10 @@ class BooruApp(QMainWindow):
self._library_btn = QPushButton("Library") self._library_btn = QPushButton("Library")
self._library_btn.setCheckable(True) self._library_btn.setCheckable(True)
self._library_btn.setFixedWidth(80)
self._library_btn.clicked.connect(lambda: self._switch_view(2)) self._library_btn.clicked.connect(lambda: self._switch_view(2))
nav.addWidget(self._library_btn) nav.addWidget(self._library_btn)
layout.addLayout(nav) layout.addWidget(_nav_bar)
# Main content # Main content
self._splitter = QSplitter(Qt.Orientation.Horizontal) self._splitter = QSplitter(Qt.Orientation.Horizontal)
@ -359,7 +362,7 @@ class BooruApp(QMainWindow):
# BL Post, [stretch], Popout) has room to lay out all five buttons # BL Post, [stretch], Popout) has room to lay out all five buttons
# at their fixed widths plus spacing without clipping the rightmost # at their fixed widths plus spacing without clipping the rightmost
# one or compressing the row visually. # one or compressing the row visually.
self._preview.setMinimumWidth(380) self._preview.setMinimumWidth(200)
right.addWidget(self._preview) right.addWidget(self._preview)
self._dl_progress = QProgressBar() self._dl_progress = QProgressBar()
@ -405,6 +408,10 @@ class BooruApp(QMainWindow):
self._splitter.addWidget(right) self._splitter.addWidget(right)
# Flip layout: preview on the left, grid on the right
if self._db.get_setting_bool("flip_layout"):
self._splitter.insertWidget(0, right)
# Restore the persisted main-splitter sizes if present, otherwise # Restore the persisted main-splitter sizes if present, otherwise
# fall back to the historic default. The sizes are saved as a # fall back to the historic default. The sizes are saved as a
# comma-separated string in the settings table — same format as # comma-separated string in the settings table — same format as

View File

@ -6,12 +6,91 @@ import logging
import os import os
from pathlib import Path from pathlib import Path
from PySide6.QtCore import Qt, QTimer, Signal, Property from PySide6.QtCore import Qt, QTimer, Signal, Property, QPoint
from PySide6.QtGui import QColor from PySide6.QtGui import QColor, QIcon, QPixmap, QPainter, QPen, QBrush, QPolygon, QPainterPath
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QSlider, QStyle, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QSlider, QStyle,
QApplication,
) )
def _paint_icon(shape: str, color: QColor, size: int = 16) -> QIcon:
"""Paint a media control icon using the given color."""
pix = QPixmap(size, size)
pix.fill(Qt.GlobalColor.transparent)
p = QPainter(pix)
p.setRenderHint(QPainter.RenderHint.Antialiasing)
p.setPen(Qt.PenStyle.NoPen)
p.setBrush(color)
s = size
if shape == "play":
p.drawPolygon(QPolygon([QPoint(3, 2), QPoint(3, s - 2), QPoint(s - 2, s // 2)]))
elif shape == "pause":
w = max(2, s // 4)
p.drawRect(2, 2, w, s - 4)
p.drawRect(s - 2 - w, 2, w, s - 4)
elif shape == "volume":
# Speaker cone
p.drawPolygon(QPolygon([
QPoint(1, s // 2 - 2), QPoint(4, s // 2 - 2),
QPoint(8, 2), QPoint(8, s - 2),
QPoint(4, s // 2 + 2), QPoint(1, s // 2 + 2),
]))
# Sound waves
p.setPen(QPen(color, 1.5))
p.setBrush(Qt.BrushStyle.NoBrush)
path = QPainterPath()
path.arcMoveTo(8, 3, 6, s - 6, 45)
path.arcTo(8, 3, 6, s - 6, 45, -90)
p.drawPath(path)
elif shape == "muted":
p.drawPolygon(QPolygon([
QPoint(1, s // 2 - 2), QPoint(4, s // 2 - 2),
QPoint(8, 2), QPoint(8, s - 2),
QPoint(4, s // 2 + 2), QPoint(1, s // 2 + 2),
]))
p.setPen(QPen(color, 2))
p.drawLine(10, 4, s - 2, s - 4)
p.drawLine(10, s - 4, s - 2, 4)
elif shape == "loop":
p.setPen(QPen(color, 1.5))
p.setBrush(Qt.BrushStyle.NoBrush)
path = QPainterPath()
path.arcMoveTo(2, 2, s - 4, s - 4, 30)
path.arcTo(2, 2, s - 4, s - 4, 30, 300)
p.drawPath(path)
# Arrowhead
p.setPen(Qt.PenStyle.NoPen)
p.setBrush(color)
end = path.currentPosition().toPoint()
p.drawPolygon(QPolygon([
end, QPoint(end.x() - 4, end.y() - 3), QPoint(end.x() + 1, end.y() - 4),
]))
elif shape == "once":
p.setPen(QPen(color, 2))
p.setBrush(Qt.BrushStyle.NoBrush)
mid = s // 2
p.drawLine(mid, 3, mid, s - 3)
p.drawLine(mid - 2, 5, mid, 3)
elif shape == "next":
p.drawPolygon(QPolygon([QPoint(2, 2), QPoint(2, s - 2), QPoint(s - 5, s // 2)]))
p.drawRect(s - 4, 2, 2, s - 4)
elif shape == "auto":
mid = s // 2
p.drawPolygon(QPolygon([QPoint(1, 3), QPoint(1, s - 3), QPoint(mid - 1, s // 2)]))
p.drawPolygon(QPolygon([QPoint(mid, 3), QPoint(mid, s - 3), QPoint(s - 2, s // 2)]))
p.end()
return QIcon(pix)
import mpv as mpvlib import mpv as mpvlib
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -119,15 +198,22 @@ class VideoPlayer(QWidget):
controls = QHBoxLayout(self._controls_bar) controls = QHBoxLayout(self._controls_bar)
controls.setContentsMargins(4, 2, 4, 2) controls.setContentsMargins(4, 2, 4, 2)
# Compact-padding override matches the top preview toolbar so the _btn_sz = 24
# bottom controls bar reads as part of the same panel rather than _fg = self.palette().buttonText().color()
# as a stamped-in overlay. Bundled themes' default `padding: 5px 12px`
# is too wide for short labels in narrow button slots.
_ctrl_btn_style = "padding: 2px 6px;"
self._play_btn = QPushButton("Play") def _icon_btn(shape: str, name: str, tip: str) -> QPushButton:
self._play_btn.setMaximumWidth(65) btn = QPushButton()
self._play_btn.setStyleSheet(_ctrl_btn_style) btn.setObjectName(name)
btn.setIcon(_paint_icon(shape, _fg))
btn.setFixedSize(_btn_sz, _btn_sz)
btn.setToolTip(tip)
return btn
self._icon_fg = _fg
self._play_icon = _paint_icon("play", _fg)
self._pause_icon = _paint_icon("pause", _fg)
self._play_btn = _icon_btn("play", "_ctrl_play", "Play / Pause (Space)")
self._play_btn.clicked.connect(self._toggle_play) self._play_btn.clicked.connect(self._toggle_play)
controls.addWidget(self._play_btn) controls.addWidget(self._play_btn)
@ -152,28 +238,29 @@ class VideoPlayer(QWidget):
self._vol_slider.valueChanged.connect(self._set_volume) self._vol_slider.valueChanged.connect(self._set_volume)
controls.addWidget(self._vol_slider) controls.addWidget(self._vol_slider)
self._mute_btn = QPushButton("Mute") self._vol_icon = _paint_icon("volume", _fg)
self._mute_btn.setMaximumWidth(80) self._muted_icon = _paint_icon("muted", _fg)
self._mute_btn.setStyleSheet(_ctrl_btn_style)
self._mute_btn = _icon_btn("volume", "_ctrl_mute", "Mute / Unmute")
self._mute_btn.clicked.connect(self._toggle_mute) self._mute_btn.clicked.connect(self._toggle_mute)
controls.addWidget(self._mute_btn) controls.addWidget(self._mute_btn)
self._autoplay = True self._autoplay = True
self._autoplay_btn = QPushButton("Auto") self._auto_icon = _paint_icon("auto", _fg)
self._autoplay_btn.setMaximumWidth(70) self._autoplay_btn = _icon_btn("auto", "_ctrl_autoplay", "Auto-play videos when selected")
self._autoplay_btn.setStyleSheet(_ctrl_btn_style)
self._autoplay_btn.setCheckable(True) self._autoplay_btn.setCheckable(True)
self._autoplay_btn.setChecked(True) self._autoplay_btn.setChecked(True)
self._autoplay_btn.setToolTip("Auto-play videos when selected")
self._autoplay_btn.clicked.connect(self._toggle_autoplay) self._autoplay_btn.clicked.connect(self._toggle_autoplay)
self._autoplay_btn.hide() self._autoplay_btn.hide()
controls.addWidget(self._autoplay_btn) controls.addWidget(self._autoplay_btn)
self._loop_icons = {
0: _paint_icon("loop", _fg),
1: _paint_icon("once", _fg),
2: _paint_icon("next", _fg),
}
self._loop_state = 0 # 0=Loop, 1=Once, 2=Next self._loop_state = 0 # 0=Loop, 1=Once, 2=Next
self._loop_btn = QPushButton("Loop") self._loop_btn = _icon_btn("loop", "_ctrl_loop", "Loop / Once / Next")
self._loop_btn.setMaximumWidth(60)
self._loop_btn.setStyleSheet(_ctrl_btn_style)
self._loop_btn.setToolTip("Loop: repeat / Once: stop at end / Next: advance")
self._loop_btn.clicked.connect(self._cycle_loop) self._loop_btn.clicked.connect(self._cycle_loop)
controls.addWidget(self._loop_btn) controls.addWidget(self._loop_btn)
@ -295,7 +382,7 @@ class VideoPlayer(QWidget):
self._pending_mute = val self._pending_mute = val
if self._mpv: if self._mpv:
self._mpv.mute = val self._mpv.mute = val
self._mute_btn.setText("Unmute" if val else "Mute") self._mute_btn.setIcon(self._muted_icon if val else self._vol_icon)
@property @property
def autoplay(self) -> bool: def autoplay(self) -> bool:
@ -314,8 +401,9 @@ class VideoPlayer(QWidget):
@loop_state.setter @loop_state.setter
def loop_state(self, val: int) -> None: def loop_state(self, val: int) -> None:
self._loop_state = val self._loop_state = val
labels = ["Loop", "Once", "Next"] tips = ["Loop: repeat", "Once: stop at end", "Next: advance"]
self._loop_btn.setText(labels[val]) self._loop_btn.setIcon(self._loop_icons[val])
self._loop_btn.setToolTip(tips[val])
self._autoplay_btn.setVisible(val == 2) self._autoplay_btn.setVisible(val == 2)
self._apply_loop_to_mpv() self._apply_loop_to_mpv()
@ -386,7 +474,7 @@ class VideoPlayer(QWidget):
m.pause = False m.pause = False
else: else:
m.pause = True m.pause = True
self._play_btn.setText("Pause" if not m.pause else "Play") self._play_btn.setIcon(self._pause_icon if not m.pause else self._play_icon)
self._poll_timer.start() self._poll_timer.start()
def stop(self) -> None: def stop(self) -> None:
@ -397,17 +485,17 @@ class VideoPlayer(QWidget):
self._time_label.setText("0:00") self._time_label.setText("0:00")
self._duration_label.setText("0:00") self._duration_label.setText("0:00")
self._seek_slider.setRange(0, 0) self._seek_slider.setRange(0, 0)
self._play_btn.setText("Play") self._play_btn.setIcon(self._play_icon)
def pause(self) -> None: def pause(self) -> None:
if self._mpv: if self._mpv:
self._mpv.pause = True self._mpv.pause = True
self._play_btn.setText("Play") self._play_btn.setIcon(self._play_icon)
def resume(self) -> None: def resume(self) -> None:
if self._mpv: if self._mpv:
self._mpv.pause = False self._mpv.pause = False
self._play_btn.setText("Pause") self._play_btn.setIcon(self._pause_icon)
# -- Internal controls -- # -- Internal controls --
@ -415,11 +503,12 @@ class VideoPlayer(QWidget):
if not self._mpv: if not self._mpv:
return return
self._mpv.pause = not self._mpv.pause self._mpv.pause = not self._mpv.pause
self._play_btn.setText("Play" if self._mpv.pause else "Pause") self._play_btn.setIcon(self._play_icon if self._mpv.pause else self._pause_icon)
def _toggle_autoplay(self, checked: bool = True) -> None: def _toggle_autoplay(self, checked: bool = True) -> None:
self._autoplay = self._autoplay_btn.isChecked() self._autoplay = self._autoplay_btn.isChecked()
self._autoplay_btn.setText("Autoplay" if self._autoplay else "Manual") self._autoplay_btn.setIcon(self._auto_icon if self._autoplay else self._play_icon)
self._autoplay_btn.setToolTip("Autoplay on" if self._autoplay else "Autoplay off")
def _cycle_loop(self) -> None: def _cycle_loop(self) -> None:
self.loop_state = (self._loop_state + 1) % 3 self.loop_state = (self._loop_state + 1) % 3
@ -463,7 +552,7 @@ class VideoPlayer(QWidget):
if self._mpv: if self._mpv:
self._mpv.mute = not self._mpv.mute self._mpv.mute = not self._mpv.mute
self._pending_mute = bool(self._mpv.mute) self._pending_mute = bool(self._mpv.mute)
self._mute_btn.setText("Unmute" if self._mpv.mute else "Mute") self._mute_btn.setIcon(self._muted_icon if self._mpv.mute else self._vol_icon)
# -- mpv callbacks (called from mpv thread) -- # -- mpv callbacks (called from mpv thread) --
@ -528,9 +617,9 @@ class VideoPlayer(QWidget):
# Pause state # Pause state
paused = self._mpv.pause paused = self._mpv.pause
expected_text = "Play" if paused else "Pause" expected_icon = self._play_icon if paused else self._pause_icon
if self._play_btn.text() != expected_text: if self._play_btn.icon().cacheKey() != expected_icon.cacheKey():
self._play_btn.setText(expected_text) self._play_btn.setIcon(expected_icon)
# Video size (set by observer on mpv thread, emitted here on main thread) # Video size (set by observer on mpv thread, emitted here on main thread)
if self._pending_video_size is not None: if self._pending_video_size is not None:

View File

@ -171,8 +171,48 @@ def _dispatch_batch(cmds: list[str]) -> None:
pass pass
def get_monitor_available_rect(monitor_id: int | None = None) -> tuple[int, int, int, int] | None:
"""Return (x, y, w, h) of a monitor's usable area, accounting for
exclusive zones (Waybar, etc.) via the ``reserved`` field.
Falls back to the first monitor if *monitor_id* is None or not found.
Returns None if not on Hyprland or the query fails.
"""
if not _on_hyprland():
return None
try:
result = subprocess.run(
["hyprctl", "monitors", "-j"],
capture_output=True, text=True, timeout=1,
)
monitors = json.loads(result.stdout)
if not monitors:
return None
mon = None
if monitor_id is not None:
mon = next((m for m in monitors if m.get("id") == monitor_id), None)
if mon is None:
mon = monitors[0]
mx = mon.get("x", 0)
my = mon.get("y", 0)
mw = mon.get("width", 0)
mh = mon.get("height", 0)
# reserved: [left, top, right, bottom]
res = mon.get("reserved", [0, 0, 0, 0])
left, top, right, bottom = res[0], res[1], res[2], res[3]
return (
mx + left,
my + top,
mw - left - right,
mh - top - bottom,
)
except Exception:
return None
__all__ = [ __all__ = [
"get_window", "get_window",
"get_monitor_available_rect",
"resize", "resize",
"resize_and_move", "resize_and_move",
] ]

View File

@ -8,19 +8,45 @@ from typing import NamedTuple
class Viewport(NamedTuple): class Viewport(NamedTuple):
"""Where and how large the user wants popout content to appear. """Where and how large the user wants popout content to appear.
Three numbers, no aspect. Aspect is a property of the currently- Three numbers + an anchor mode, no aspect. Aspect is a property of
displayed post and is recomputed from actual content on every the currently-displayed post and is recomputed from actual content
navigation. The viewport stays put across navigations; the window on every navigation. The viewport stays put across navigations; the
rect is a derived projection (Viewport, content_aspect) (x,y,w,h). window rect is a derived projection (Viewport, content_aspect)
(x,y,w,h).
`long_side` is the binding edge length: for landscape it becomes `long_side` is the binding edge length: for landscape it becomes
width, for portrait it becomes height. Symmetric across the two width, for portrait it becomes height. Symmetric across the two
orientations, which is the property that breaks the orientations, which is the property that breaks the
width-anchor ratchet that the previous `_fit_to_content` had. width-anchor ratchet that the previous `_fit_to_content` had.
`anchor` controls which point of the window stays fixed across
navigations as the window size changes with aspect ratio:
``"center"`` (default) pins the window center; ``"tl"``/``"tr"``/
``"bl"``/``"br"`` pin the corresponding corner. The window
grows/shrinks away from the anchored corner. The user can drag the
window anywhere the anchor only affects resize direction, not
screen position.
`center_x`/`center_y` hold the anchor point coordinates (center
of the window in center mode, the pinned corner in corner modes).
""" """
center_x: float center_x: float
center_y: float center_y: float
long_side: float long_side: float
anchor: str = "center"
def anchor_point(x: float, y: float, w: float, h: float, anchor: str) -> tuple[float, float]:
"""Extract the anchor point from a window rect based on anchor mode."""
if anchor == "tl":
return (x, y)
if anchor == "tr":
return (x + w, y)
if anchor == "bl":
return (x, y + h)
if anchor == "br":
return (x + w, y + h)
return (x + w / 2, y + h / 2)
# Maximum drift between our last-dispatched window rect and the current # Maximum drift between our last-dispatched window rect and the current

View File

@ -54,7 +54,7 @@ from .state import (
WindowMoved, WindowMoved,
WindowResized, WindowResized,
) )
from .viewport import Viewport, _DRIFT_TOLERANCE from .viewport import Viewport, _DRIFT_TOLERANCE, anchor_point
# Adapter logger — separate from the popout's main `booru` logger so # Adapter logger — separate from the popout's main `booru` logger so
@ -121,10 +121,11 @@ class FullscreenPreview(QMainWindow):
privacy_requested = Signal() privacy_requested = Signal()
closed = Signal() closed = Signal()
def __init__(self, grid_cols: int = 3, show_actions: bool = True, monitor: str = "", parent=None) -> None: def __init__(self, grid_cols: int = 3, show_actions: bool = True, monitor: str = "", anchor: str = "center", parent=None) -> None:
super().__init__(parent, Qt.WindowType.Window) super().__init__(parent, Qt.WindowType.Window)
self.setWindowTitle("booru-viewer — Popout") self.setWindowTitle("booru-viewer — Popout")
self._grid_cols = grid_cols self._grid_cols = grid_cols
self._anchor = anchor
# Central widget — media fills the entire window # Central widget — media fills the entire window
central = QWidget() central = QWidget()
@ -181,10 +182,6 @@ class FullscreenPreview(QMainWindow):
toolbar.setContentsMargins(8, 4, 8, 4) toolbar.setContentsMargins(8, 4, 8, 4)
# Same compact-padding override as the embedded preview toolbar — # Same compact-padding override as the embedded preview toolbar —
# bundled themes' default `padding: 5px 12px` is too wide for these
# short labels in narrow fixed slots.
_tb_btn_style = "padding: 2px 6px;"
# Bookmark folders for the popout's Bookmark-as submenu — wired # Bookmark folders for the popout's Bookmark-as submenu — wired
# by app.py via set_bookmark_folders_callback after construction. # by app.py via set_bookmark_folders_callback after construction.
self._bookmark_folders_callback = None self._bookmark_folders_callback = None
@ -195,30 +192,29 @@ class FullscreenPreview(QMainWindow):
# are independent name spaces and need separate callbacks. # are independent name spaces and need separate callbacks.
self._folders_callback = None self._folders_callback = None
self._bookmark_btn = QPushButton("Bookmark") _tb_sz = 24
self._bookmark_btn.setMaximumWidth(90)
self._bookmark_btn.setStyleSheet(_tb_btn_style) def _icon_btn(text: str, name: str, tip: str) -> QPushButton:
btn = QPushButton(text)
btn.setObjectName(name)
btn.setFixedSize(_tb_sz, _tb_sz)
btn.setToolTip(tip)
return btn
self._bookmark_btn = _icon_btn("\u2606", "_tb_bookmark", "Bookmark (B)")
self._bookmark_btn.clicked.connect(self._on_bookmark_clicked) self._bookmark_btn.clicked.connect(self._on_bookmark_clicked)
toolbar.addWidget(self._bookmark_btn) toolbar.addWidget(self._bookmark_btn)
self._save_btn = QPushButton("Save") self._save_btn = _icon_btn("\u2193", "_tb_save", "Save to library (S)")
self._save_btn.setMaximumWidth(70)
self._save_btn.setStyleSheet(_tb_btn_style)
self._save_btn.clicked.connect(self._on_save_clicked) self._save_btn.clicked.connect(self._on_save_clicked)
toolbar.addWidget(self._save_btn) toolbar.addWidget(self._save_btn)
self._is_saved = False self._is_saved = False
self._bl_tag_btn = QPushButton("BL Tag") self._bl_tag_btn = _icon_btn("\u2298", "_tb_bl_tag", "Blacklist a tag")
self._bl_tag_btn.setMaximumWidth(65)
self._bl_tag_btn.setStyleSheet(_tb_btn_style)
self._bl_tag_btn.setToolTip("Blacklist a tag")
self._bl_tag_btn.clicked.connect(self._show_bl_tag_menu) self._bl_tag_btn.clicked.connect(self._show_bl_tag_menu)
toolbar.addWidget(self._bl_tag_btn) toolbar.addWidget(self._bl_tag_btn)
self._bl_post_btn = QPushButton("BL Post") self._bl_post_btn = _icon_btn("\u2297", "_tb_bl_post", "Blacklist this post")
self._bl_post_btn.setMaximumWidth(70)
self._bl_post_btn.setStyleSheet(_tb_btn_style)
self._bl_post_btn.setToolTip("Blacklist this post")
self._bl_post_btn.clicked.connect(self.blacklist_post_requested) self._bl_post_btn.clicked.connect(self.blacklist_post_requested)
toolbar.addWidget(self._bl_post_btn) toolbar.addWidget(self._bl_post_btn)
@ -642,10 +638,11 @@ class FullscreenPreview(QMainWindow):
def update_state(self, bookmarked: bool, saved: bool) -> None: def update_state(self, bookmarked: bool, saved: bool) -> None:
self._is_bookmarked = bookmarked self._is_bookmarked = bookmarked
self._bookmark_btn.setText("Unbookmark" if bookmarked else "Bookmark") self._bookmark_btn.setText("\u2605" if bookmarked else "\u2606") # ★ / ☆
self._bookmark_btn.setMaximumWidth(90 if bookmarked else 80) self._bookmark_btn.setToolTip("Unbookmark (B)" if bookmarked else "Bookmark (B)")
self._is_saved = saved self._is_saved = saved
self._save_btn.setText("Unsave" if saved else "Save") self._save_btn.setText("\u2715" if saved else "\u2193") # ✕ / ⤓
self._save_btn.setToolTip("Unsave from library" if saved else "Save to library (S)")
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# Public method interface (commit 15) # Public method interface (commit 15)
@ -1054,7 +1051,8 @@ class FullscreenPreview(QMainWindow):
@staticmethod @staticmethod
def _compute_window_rect( def _compute_window_rect(
viewport: Viewport, content_aspect: float, screen viewport: Viewport, content_aspect: float, screen,
avail_override: tuple[int, int, int, int] | None = None,
) -> tuple[int, int, int, int]: ) -> tuple[int, int, int, int]:
"""Project a viewport onto a window rect for the given content aspect. """Project a viewport onto a window rect for the given content aspect.
@ -1064,6 +1062,16 @@ class FullscreenPreview(QMainWindow):
if either would exceed its 0.90-of-screen ceiling, preserving if either would exceed its 0.90-of-screen ceiling, preserving
aspect exactly. Pure function no side effects, no widget aspect exactly. Pure function no side effects, no widget
access, all inputs explicit so it's trivial to reason about. access, all inputs explicit so it's trivial to reason about.
``viewport.center_x``/``center_y`` hold the anchor point the
window center in ``"center"`` mode, or the pinned corner in
corner modes. The anchor stays fixed; the window grows/shrinks
away from it.
*avail_override* is an (x, y, w, h) tuple that replaces
``screen.availableGeometry()`` used on Hyprland where Qt
doesn't see Waybar's exclusive zone but ``hyprctl monitors -j``
reports it via the ``reserved`` array.
""" """
if content_aspect >= 1.0: # landscape or square if content_aspect >= 1.0: # landscape or square
w = viewport.long_side w = viewport.long_side
@ -1072,19 +1080,37 @@ class FullscreenPreview(QMainWindow):
h = viewport.long_side h = viewport.long_side
w = viewport.long_side * content_aspect w = viewport.long_side * content_aspect
avail = screen.availableGeometry() if avail_override:
cap_w = avail.width() * 0.90 ax, ay, aw, ah = avail_override
cap_h = avail.height() * 0.90 else:
_a = screen.availableGeometry()
ax, ay, aw, ah = _a.x(), _a.y(), _a.width(), _a.height()
cap_w = aw * 0.90
cap_h = ah * 0.90
scale = min(1.0, cap_w / w, cap_h / h) scale = min(1.0, cap_w / w, cap_h / h)
w *= scale w *= scale
h *= scale h *= scale
anchor = viewport.anchor
if anchor == "tl":
x = viewport.center_x
y = viewport.center_y
elif anchor == "tr":
x = viewport.center_x - w
y = viewport.center_y
elif anchor == "bl":
x = viewport.center_x
y = viewport.center_y - h
elif anchor == "br":
x = viewport.center_x - w
y = viewport.center_y - h
else:
x = viewport.center_x - w / 2 x = viewport.center_x - w / 2
y = viewport.center_y - h / 2 y = viewport.center_y - h / 2
# Nudge onto screen if the projected rect would land off-edge. # Nudge onto screen if the window would land off-edge.
x = max(avail.x(), min(x, avail.right() - w)) x = max(ax, min(x, ax + aw - w))
y = max(avail.y(), min(y, avail.bottom() - h)) y = max(ay, min(y, ay + ah - h))
return (round(x), round(y), round(w), round(h)) return (round(x), round(y), round(w), round(h))
@ -1110,18 +1136,20 @@ class FullscreenPreview(QMainWindow):
if win and win.get("at") and win.get("size"): if win and win.get("at") and win.get("size"):
wx, wy = win["at"] wx, wy = win["at"]
ww, wh = win["size"] ww, wh = win["size"]
ax, ay = anchor_point(wx, wy, ww, wh, self._anchor)
return Viewport( return Viewport(
center_x=wx + ww / 2, center_x=ax, center_y=ay,
center_y=wy + wh / 2,
long_side=float(max(ww, wh)), long_side=float(max(ww, wh)),
anchor=self._anchor,
) )
if floating is None: if floating is None:
rect = self.geometry() rect = self.geometry()
if rect.width() > 0 and rect.height() > 0: if rect.width() > 0 and rect.height() > 0:
ax, ay = anchor_point(rect.x(), rect.y(), rect.width(), rect.height(), self._anchor)
return Viewport( return Viewport(
center_x=rect.x() + rect.width() / 2, center_x=ax, center_y=ay,
center_y=rect.y() + rect.height() / 2,
long_side=float(max(rect.width(), rect.height())), long_side=float(max(rect.width(), rect.height())),
anchor=self._anchor,
) )
return None return None
@ -1162,10 +1190,11 @@ class FullscreenPreview(QMainWindow):
if self._first_fit_pending and self._pending_size and self._pending_position_restore: if self._first_fit_pending and self._pending_size and self._pending_position_restore:
pw, ph = self._pending_size pw, ph = self._pending_size
px, py = self._pending_position_restore px, py = self._pending_position_restore
ax, ay = anchor_point(px, py, pw, ph, self._anchor)
self._viewport = Viewport( self._viewport = Viewport(
center_x=px + pw / 2, center_x=ax, center_y=ay,
center_y=py + ph / 2,
long_side=float(max(pw, ph)), long_side=float(max(pw, ph)),
anchor=self._anchor,
) )
return self._viewport return self._viewport
@ -1192,10 +1221,11 @@ class FullscreenPreview(QMainWindow):
) )
if drift > _DRIFT_TOLERANCE: if drift > _DRIFT_TOLERANCE:
# External move/resize detected. Adopt current as intent. # External move/resize detected. Adopt current as intent.
ax, ay = anchor_point(cur_x, cur_y, cur_w, cur_h, self._anchor)
self._viewport = Viewport( self._viewport = Viewport(
center_x=cur_x + cur_w / 2, center_x=ax, center_y=ay,
center_y=cur_y + cur_h / 2,
long_side=float(max(cur_w, cur_h)), long_side=float(max(cur_w, cur_h)),
anchor=self._anchor,
) )
return self._viewport return self._viewport
@ -1260,7 +1290,10 @@ class FullscreenPreview(QMainWindow):
# the one-shots would lose the saved position; leaving them # the one-shots would lose the saved position; leaving them
# set lets a subsequent fit retry. # set lets a subsequent fit retry.
return return
x, y, w, h = self._compute_window_rect(viewport, aspect, screen) avail_rect = None
if on_hypr and win:
avail_rect = hyprland.get_monitor_available_rect(win.get("monitor"))
x, y, w, h = self._compute_window_rect(viewport, aspect, screen, avail_override=avail_rect)
# Identical-rect skip. If the computed rect is exactly what # Identical-rect skip. If the computed rect is exactly what
# we last dispatched, the window is already in that state and # we last dispatched, the window is already in that state and
# there's nothing for hyprctl (or setGeometry) to do. Skipping # there's nothing for hyprctl (or setGeometry) to do. Skipping
@ -1472,19 +1505,21 @@ class FullscreenPreview(QMainWindow):
x, y = win["at"] x, y = win["at"]
w, h = win["size"] w, h = win["size"]
self._windowed_geometry = QRect(x, y, w, h) self._windowed_geometry = QRect(x, y, w, h)
ax, ay = anchor_point(x, y, w, h, self._anchor)
self._viewport = Viewport( self._viewport = Viewport(
center_x=x + w / 2, center_x=ax, center_y=ay,
center_y=y + h / 2,
long_side=float(max(w, h)), long_side=float(max(w, h)),
anchor=self._anchor,
) )
else: else:
self._windowed_geometry = self.frameGeometry() self._windowed_geometry = self.frameGeometry()
rect = self._windowed_geometry rect = self._windowed_geometry
if rect.width() > 0 and rect.height() > 0: if rect.width() > 0 and rect.height() > 0:
ax, ay = anchor_point(rect.x(), rect.y(), rect.width(), rect.height(), self._anchor)
self._viewport = Viewport( self._viewport = Viewport(
center_x=rect.x() + rect.width() / 2, center_x=ax, center_y=ay,
center_y=rect.y() + rect.height() / 2,
long_side=float(max(rect.width(), rect.height())), long_side=float(max(rect.width(), rect.height())),
anchor=self._anchor,
) )
self.showFullScreen() self.showFullScreen()
@ -1581,10 +1616,11 @@ class FullscreenPreview(QMainWindow):
return return
rect = self.geometry() rect = self.geometry()
if rect.width() > 0 and rect.height() > 0: if rect.width() > 0 and rect.height() > 0:
ax, ay = anchor_point(rect.x(), rect.y(), rect.width(), rect.height(), self._anchor)
self._viewport = Viewport( self._viewport = Viewport(
center_x=rect.x() + rect.width() / 2, center_x=ax, center_y=ay,
center_y=rect.y() + rect.height() / 2,
long_side=float(max(rect.width(), rect.height())), long_side=float(max(rect.width(), rect.height())),
anchor=self._anchor,
) )
# Parallel state machine dispatch for the same event. # Parallel state machine dispatch for the same event.
self._dispatch_and_apply(WindowResized(rect=( self._dispatch_and_apply(WindowResized(rect=(
@ -1611,11 +1647,12 @@ class FullscreenPreview(QMainWindow):
rect = self.geometry() rect = self.geometry()
if rect.width() > 0 and rect.height() > 0: if rect.width() > 0 and rect.height() > 0:
# Move-only update: keep the existing long_side, just # Move-only update: keep the existing long_side, just
# update the center to where the window now sits. # update the anchor point to where the window now sits.
ax, ay = anchor_point(rect.x(), rect.y(), rect.width(), rect.height(), self._anchor)
self._viewport = Viewport( self._viewport = Viewport(
center_x=rect.x() + rect.width() / 2, center_x=ax, center_y=ay,
center_y=rect.y() + rect.height() / 2,
long_side=self._viewport.long_side, long_side=self._viewport.long_side,
anchor=self._anchor,
) )
# Parallel state machine dispatch for the same event. # Parallel state machine dispatch for the same event.
self._dispatch_and_apply(WindowMoved(rect=( self._dispatch_and_apply(WindowMoved(rect=(

View File

@ -90,7 +90,8 @@ class PopoutController:
cols = self._app._grid._flow.columns cols = self._app._grid._flow.columns
show_actions = self._app._stack.currentIndex() != 2 show_actions = self._app._stack.currentIndex() != 2
monitor = self._app._db.get_setting("slideshow_monitor") monitor = self._app._db.get_setting("slideshow_monitor")
self._fullscreen_window = FullscreenPreview(grid_cols=cols, show_actions=show_actions, monitor=monitor, parent=self._app) anchor = self._app._db.get_setting("popout_anchor") or "center"
self._fullscreen_window = FullscreenPreview(grid_cols=cols, show_actions=show_actions, monitor=monitor, anchor=anchor, parent=self._app)
self._fullscreen_window.navigate.connect(self.navigate) self._fullscreen_window.navigate.connect(self.navigate)
self._fullscreen_window.play_next_requested.connect(self._app._on_video_end_next) self._fullscreen_window.play_next_requested.connect(self._app._on_video_end_next)
from ..core.config import library_folders from ..core.config import library_folders

View File

@ -64,50 +64,34 @@ class ImagePreview(QWidget):
tb.setContentsMargins(4, 1, 4, 1) tb.setContentsMargins(4, 1, 4, 1)
tb.setSpacing(4) tb.setSpacing(4)
# Compact toolbar buttons. The bundled themes set _tb_sz = 24
# `QPushButton { padding: 5px 12px }` which eats 24px of horizontal
# space — too much for these short labels in fixed-width slots.
# Override with tighter padding inline so the labels (Unbookmark,
# Unsave, BL Tag, BL Post, Popout) fit cleanly under any theme.
# Same pattern as the search-bar score buttons in app.py and the
# settings dialog spinbox +/- buttons.
_tb_btn_style = "padding: 2px 6px;"
self._bookmark_btn = QPushButton("Bookmark") def _icon_btn(text: str, name: str, tip: str) -> QPushButton:
self._bookmark_btn.setFixedWidth(100) btn = QPushButton(text)
self._bookmark_btn.setStyleSheet(_tb_btn_style) btn.setObjectName(name)
btn.setFixedSize(_tb_sz, _tb_sz)
btn.setToolTip(tip)
return btn
self._bookmark_btn = _icon_btn("\u2606", "_tb_bookmark", "Bookmark (B)")
self._bookmark_btn.clicked.connect(self._on_bookmark_clicked) self._bookmark_btn.clicked.connect(self._on_bookmark_clicked)
tb.addWidget(self._bookmark_btn) tb.addWidget(self._bookmark_btn)
self._save_btn = QPushButton("Save") self._save_btn = _icon_btn("\u2193", "_tb_save", "Save to library (S)")
# 75 fits "Unsave" (6 chars) cleanly across every bundled theme.
# The previous 60 was tight enough that some themes clipped the
# last character on library files where the label flips to Unsave.
self._save_btn.setFixedWidth(75)
self._save_btn.setStyleSheet(_tb_btn_style)
self._save_btn.clicked.connect(self._on_save_clicked) self._save_btn.clicked.connect(self._on_save_clicked)
tb.addWidget(self._save_btn) tb.addWidget(self._save_btn)
self._bl_tag_btn = QPushButton("BL Tag") self._bl_tag_btn = _icon_btn("\u2298", "_tb_bl_tag", "Blacklist a tag")
self._bl_tag_btn.setFixedWidth(60)
self._bl_tag_btn.setStyleSheet(_tb_btn_style)
self._bl_tag_btn.setToolTip("Blacklist a tag")
self._bl_tag_btn.clicked.connect(self._show_bl_tag_menu) self._bl_tag_btn.clicked.connect(self._show_bl_tag_menu)
tb.addWidget(self._bl_tag_btn) tb.addWidget(self._bl_tag_btn)
self._bl_post_btn = QPushButton("BL Post") self._bl_post_btn = _icon_btn("\u2297", "_tb_bl_post", "Blacklist this post")
self._bl_post_btn.setFixedWidth(65)
self._bl_post_btn.setStyleSheet(_tb_btn_style)
self._bl_post_btn.setToolTip("Blacklist this post")
self._bl_post_btn.clicked.connect(self.blacklist_post_requested) self._bl_post_btn.clicked.connect(self.blacklist_post_requested)
tb.addWidget(self._bl_post_btn) tb.addWidget(self._bl_post_btn)
tb.addStretch() tb.addStretch()
self._popout_btn = QPushButton("Popout") self._popout_btn = _icon_btn("\u29c9", "_tb_popout", "Popout")
self._popout_btn.setFixedWidth(65)
self._popout_btn.setStyleSheet(_tb_btn_style)
self._popout_btn.setToolTip("Open in popout")
self._popout_btn.clicked.connect(self.fullscreen_requested) self._popout_btn.clicked.connect(self.fullscreen_requested)
tb.addWidget(self._popout_btn) tb.addWidget(self._popout_btn)
@ -239,12 +223,13 @@ class ImagePreview(QWidget):
def update_bookmark_state(self, bookmarked: bool) -> None: def update_bookmark_state(self, bookmarked: bool) -> None:
self._is_bookmarked = bookmarked self._is_bookmarked = bookmarked
self._bookmark_btn.setText("Unbookmark" if bookmarked else "Bookmark") self._bookmark_btn.setText("\u2605" if bookmarked else "\u2606") # ★ / ☆
self._bookmark_btn.setFixedWidth(90 if bookmarked else 80) self._bookmark_btn.setToolTip("Unbookmark (B)" if bookmarked else "Bookmark (B)")
def update_save_state(self, saved: bool) -> None: def update_save_state(self, saved: bool) -> None:
self._is_saved = saved self._is_saved = saved
self._save_btn.setText("Unsave" if saved else "Save") self._save_btn.setText("\u2715" if saved else "\u2193") # ✕ / ⤓
self._save_btn.setToolTip("Unsave from library" if saved else "Save to library (S)")

View File

@ -197,6 +197,11 @@ class SettingsDialog(QDialog):
self._search_history.setChecked(self._db.get_setting_bool("search_history_enabled")) self._search_history.setChecked(self._db.get_setting_bool("search_history_enabled"))
form.addRow("", self._search_history) form.addRow("", self._search_history)
# Flip layout
self._flip_layout = QCheckBox("Preview on left (restart required)")
self._flip_layout.setChecked(self._db.get_setting_bool("flip_layout"))
form.addRow("", self._flip_layout)
# Slideshow monitor # Slideshow monitor
from PySide6.QtWidgets import QApplication from PySide6.QtWidgets import QApplication
self._monitor_combo = QComboBox() self._monitor_combo = QComboBox()
@ -210,6 +215,16 @@ class SettingsDialog(QDialog):
self._monitor_combo.setCurrentIndex(idx) self._monitor_combo.setCurrentIndex(idx)
form.addRow("Popout monitor:", self._monitor_combo) form.addRow("Popout monitor:", self._monitor_combo)
# Popout anchor — resize pivot point
self._popout_anchor = QComboBox()
self._popout_anchor.addItems(["Center", "Top-left", "Top-right", "Bottom-left", "Bottom-right"])
_anchor_map = {"center": "Center", "tl": "Top-left", "tr": "Top-right", "bl": "Bottom-left", "br": "Bottom-right"}
current_anchor = self._db.get_setting("popout_anchor") or "center"
idx = self._popout_anchor.findText(_anchor_map.get(current_anchor, "Center"))
if idx >= 0:
self._popout_anchor.setCurrentIndex(idx)
form.addRow("Popout anchor:", self._popout_anchor)
# File dialog platform (Linux only) # File dialog platform (Linux only)
self._file_dialog_combo = None self._file_dialog_combo = None
if not IS_WINDOWS: if not IS_WINDOWS:
@ -791,7 +806,10 @@ class SettingsDialog(QDialog):
self._db.set_setting("infinite_scroll", "1" if self._infinite_scroll.isChecked() else "0") self._db.set_setting("infinite_scroll", "1" if self._infinite_scroll.isChecked() else "0")
self._db.set_setting("unbookmark_on_save", "1" if self._unbookmark_on_save.isChecked() else "0") self._db.set_setting("unbookmark_on_save", "1" if self._unbookmark_on_save.isChecked() else "0")
self._db.set_setting("search_history_enabled", "1" if self._search_history.isChecked() else "0") self._db.set_setting("search_history_enabled", "1" if self._search_history.isChecked() else "0")
self._db.set_setting("flip_layout", "1" if self._flip_layout.isChecked() else "0")
self._db.set_setting("slideshow_monitor", self._monitor_combo.currentText()) self._db.set_setting("slideshow_monitor", self._monitor_combo.currentText())
_anchor_rmap = {"Center": "center", "Top-left": "tl", "Top-right": "tr", "Bottom-left": "bl", "Bottom-right": "br"}
self._db.set_setting("popout_anchor", _anchor_rmap.get(self._popout_anchor.currentText(), "center"))
self._db.set_setting("library_dir", self._library_dir.text().strip()) self._db.set_setting("library_dir", self._library_dir.text().strip())
self._db.set_setting("library_filename_template", self._library_filename_template.text().strip()) self._db.set_setting("library_filename_template", self._library_filename_template.text().strip())
self._db.set_setting("max_cache_mb", str(self._max_cache.value())) self._db.set_setting("max_cache_mb", str(self._max_cache.value()))

View File

@ -118,15 +118,7 @@ QPushButton:pressed { background-color: ${bg_active}; }
QPushButton:checked { background-color: ${accent}; } /* Active tab (Browse/Bookmarks/Library), Autoplay, Loop toggles */ QPushButton:checked { background-color: ${accent}; } /* Active tab (Browse/Bookmarks/Library), Autoplay, Loop toggles */
``` ```
**Note:** Qt's QSS does not support the CSS `content` property, so you cannot replace button text (e.g. "Play" → "") via stylesheet alone. However, you can use a Nerd Font to change how unicode characters render: **Note:** Qt's QSS does not support the CSS `content` property, so you cannot replace button text (e.g. swap icon symbols) via stylesheet alone. The toolbar icon buttons use hardcoded Unicode symbols — to change which symbols appear, modify the Python source directly (see `preview_pane.py` and `popout/window.py`).
```css
QPushButton {
font-family: "JetBrainsMono Nerd Font", monospace;
}
```
To use icon buttons, you would need to modify the Python source code directly — the button labels are set in `preview.py` via `QPushButton("Play")` etc.
### Text Inputs ### Text Inputs
@ -312,11 +304,34 @@ QWidget#_slideshow_controls QPushButton {
} }
``` ```
### Preview Toolbar ### Preview & Popout Toolbar Icon Buttons
The preview panel has an action toolbar (Bookmark, Save, BL Tag, BL Post, Popout) that appears above the media when a post is active. This toolbar uses the app's default button styling. The preview and popout toolbars use 24x24 icon buttons with Unicode symbols. Each button has an object name for QSS targeting:
The toolbar does not have a named object ID — it inherits the app's `QPushButton` styles directly. | Object Name | Symbol | Action |
|-------------|--------|--------|
| `#_tb_bookmark` | ☆ / ★ | Bookmark / Unbookmark |
| `#_tb_save` | ⤓ / ✕ | Save / Unsave |
| `#_tb_bl_tag` | ⊘ | Blacklist a tag |
| `#_tb_bl_post` | ⊗ | Blacklist this post |
| `#_tb_popout` | ⧉ | Open popout (preview only) |
```css
/* Style all toolbar icon buttons */
QPushButton#_tb_bookmark,
QPushButton#_tb_save,
QPushButton#_tb_bl_tag,
QPushButton#_tb_bl_post,
QPushButton#_tb_popout {
background: transparent;
border: 1px solid ${border};
color: ${text};
padding: 0px;
}
```
The same object names are used in both the preview pane and the popout overlay, so one rule targets both. The symbols themselves are hardcoded in Python — QSS can style the buttons but cannot change which symbol is displayed.
### Progress Bar (Download) ### Progress Bar (Download)

View File

@ -28,7 +28,6 @@
warning: #f9e2af warning: #f9e2af
overlay_bg: rgba(30, 30, 46, 200) overlay_bg: rgba(30, 30, 46, 200)
*/ */
/* ---------- Base ---------- */ /* ---------- Base ---------- */
QWidget { QWidget {
@ -43,8 +42,6 @@ QWidget:disabled {
color: ${text_disabled}; color: ${text_disabled};
} }
/* Labels should never paint an opaque background they sit on top of
* other widgets in many places (toolbars, info panels, overlays). */
QLabel { QLabel {
background: transparent; background: transparent;
} }
@ -92,49 +89,26 @@ QPushButton:flat:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QToolButton {
background-color: transparent;
color: ${text};
border: 1px solid transparent;
border-radius: 4px;
padding: 4px;
}
QToolButton:hover {
background-color: ${bg_hover};
border-color: ${border_strong};
}
QToolButton:pressed, QToolButton:checked {
background-color: ${bg_active};
}
/* ---------- Inputs ---------- */ /* ---------- Inputs ---------- */
QLineEdit, QSpinBox, QDoubleSpinBox, QTextEdit, QPlainTextEdit { QLineEdit, QSpinBox, QTextEdit {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 6px; padding: 2px 6px;
/* min-height ensures the painted text fits inside the widget bounds
* even when a parent layout (e.g. QFormLayout inside a QGroupBox)
* compresses the natural sizeHint. Without this, spinboxes in dense
* forms render with the top of the value text clipped. */
min-height: 16px; min-height: 16px;
selection-background-color: ${accent}; selection-background-color: ${accent};
selection-color: ${accent_text}; selection-color: ${accent_text};
} }
QLineEdit:focus, QLineEdit:focus,
QSpinBox:focus, QSpinBox:focus,
QDoubleSpinBox:focus, QTextEdit:focus {
QTextEdit:focus,
QPlainTextEdit:focus {
border-color: ${accent}; border-color: ${accent};
} }
QLineEdit:disabled, QLineEdit:disabled,
QSpinBox:disabled, QSpinBox:disabled,
QDoubleSpinBox:disabled, QTextEdit:disabled {
QTextEdit:disabled,
QPlainTextEdit:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
color: ${text_disabled}; color: ${text_disabled};
border-color: ${border}; border-color: ${border};
@ -315,19 +289,6 @@ QSlider::handle:horizontal:hover {
background: ${accent_dim}; background: ${accent_dim};
} }
QSlider::groove:vertical {
background: ${bg_subtle};
width: 4px;
border-radius: 2px;
}
QSlider::handle:vertical {
background: ${accent};
width: 12px;
height: 12px;
margin: 0 -5px;
border-radius: 6px;
}
/* ---------- Progress ---------- */ /* ---------- Progress ---------- */
QProgressBar { QProgressBar {
@ -343,33 +304,28 @@ QProgressBar::chunk {
border-radius: 3px; border-radius: 3px;
} }
/* ---------- Checkboxes & radio buttons ---------- */ /* ---------- Checkboxes ---------- */
QCheckBox, QRadioButton { QCheckBox {
background: transparent; background: transparent;
color: ${text}; color: ${text};
spacing: 6px; spacing: 6px;
} }
QCheckBox::indicator, QRadioButton::indicator { QCheckBox::indicator {
width: 14px; width: 14px;
height: 14px; height: 14px;
background-color: ${bg_subtle}; background-color: ${bg_subtle};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
}
QCheckBox::indicator {
border-radius: 3px; border-radius: 3px;
} }
QRadioButton::indicator { QCheckBox::indicator:hover {
border-radius: 7px;
}
QCheckBox::indicator:hover, QRadioButton::indicator:hover {
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:checked, QRadioButton::indicator:checked { QCheckBox::indicator:checked {
background-color: ${accent}; background-color: ${accent};
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:disabled, QRadioButton::indicator:disabled { QCheckBox::indicator:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
border-color: ${border}; border-color: ${border};
} }
@ -384,9 +340,9 @@ QToolTip {
border-radius: 3px; border-radius: 3px;
} }
/* ---------- Item views (lists, trees, tables) ---------- */ /* ---------- Lists ---------- */
QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget { QListView, QListWidget {
background-color: ${bg}; background-color: ${bg};
alternate-background-color: ${bg_alt}; alternate-background-color: ${bg_alt};
color: ${text}; color: ${text};
@ -395,35 +351,18 @@ QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget {
selection-color: ${accent_text}; selection-color: ${accent_text};
outline: none; outline: none;
} }
QListView::item, QListWidget::item, QListView::item, QListWidget::item {
QTreeView::item, QTreeWidget::item,
QTableView::item, QTableWidget::item {
padding: 4px; padding: 4px;
} }
QListView::item:hover, QListWidget::item:hover, QListView::item:hover, QListWidget::item:hover {
QTreeView::item:hover, QTreeWidget::item:hover,
QTableView::item:hover, QTableWidget::item:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QListView::item:selected, QListWidget::item:selected, QListView::item:selected, QListWidget::item:selected {
QTreeView::item:selected, QTreeWidget::item:selected,
QTableView::item:selected, QTableWidget::item:selected {
background-color: ${accent}; background-color: ${accent};
color: ${accent_text}; color: ${accent_text};
} }
QHeaderView::section { /* ---------- Tabs (settings dialog) ---------- */
background-color: ${bg_subtle};
color: ${text};
border: none;
border-right: 1px solid ${border};
padding: 4px 8px;
}
QHeaderView::section:hover {
background-color: ${bg_hover};
}
/* ---------- Tabs ---------- */
QTabWidget::pane { QTabWidget::pane {
border: 1px solid ${border}; border: 1px solid ${border};
@ -448,7 +387,7 @@ QTabBar::tab:hover:!selected {
color: ${text}; color: ${text};
} }
/* ---------- Group boxes ---------- */ /* ---------- Group boxes (settings dialog) ---------- */
QGroupBox { QGroupBox {
background: transparent; background: transparent;
@ -465,63 +404,14 @@ QGroupBox::title {
color: ${text_dim}; color: ${text_dim};
} }
/* ---------- Frames ---------- */ /* ---------- Rubber band (multi-select drag) ---------- */
QFrame[frameShape="4"], /* HLine */
QFrame[frameShape="5"] /* VLine */ {
background: ${border};
color: ${border};
}
/* ---------- Toolbars ---------- */
QToolBar {
background: ${bg};
border: none;
spacing: 4px;
padding: 2px;
}
QToolBar::separator {
background: ${border};
width: 1px;
margin: 4px 4px;
}
/* ---------- Dock widgets ---------- */
QDockWidget {
color: ${text};
titlebar-close-icon: none;
}
QDockWidget::title {
background: ${bg_subtle};
padding: 4px;
border: 1px solid ${border};
}
/* ---------- Rubber band (multi-select drag rectangle) ---------- */
QRubberBand { QRubberBand {
background: ${accent}; background: ${accent};
border: 1px solid ${accent}; border: 1px solid ${accent};
/* Qt blends rubber band at ~30% so this reads as a translucent
* accent-tinted rectangle without needing rgba(). */
} }
/* ---------- Library count label states ---------- */ /* ---------- Library count label states ---------- */
/*
* The library tab's count label switches between three visual states
* depending on what refresh() found. The state is exposed as a Qt
* dynamic property `libraryCountState` so users can override these
* rules in their custom.qss without touching the Python.
*
* normal N files default text color, no rule needed
* empty no items dim text (no items found, search miss)
* error bad/unreachable danger color + bold (real error)
*/
QLabel[libraryCountState="empty"] { QLabel[libraryCountState="empty"] {
color: ${text_dim}; color: ${text_dim};
@ -531,18 +421,18 @@ QLabel[libraryCountState="error"] {
font-weight: bold; font-weight: bold;
} }
/* ---------- Thumbnail dot indicators (Qt properties on ThumbnailWidget) ---------- */ /* ---------- Thumbnail indicators ---------- */
ThumbnailWidget { ThumbnailWidget {
qproperty-savedColor: #22cc22; /* green dot: saved to library — universal "confirmed" feel */ qproperty-savedColor: #22cc22;
qproperty-bookmarkedColor: #ffcc00; /* yellow star: bookmarked */ qproperty-bookmarkedColor: #ffcc00;
qproperty-selectionColor: ${accent}; qproperty-selectionColor: ${accent};
qproperty-multiSelectColor: ${accent_dim}; qproperty-multiSelectColor: ${accent_dim};
qproperty-hoverColor: ${accent}; qproperty-hoverColor: ${accent};
qproperty-idleColor: ${border_strong}; qproperty-idleColor: ${border_strong};
} }
/* ---------- Info panel tag category colors ---------- */ /* ---------- Info panel tag colors ---------- */
InfoPanel { InfoPanel {
qproperty-tagArtistColor: ${warning}; qproperty-tagArtistColor: ${warning};
@ -553,19 +443,13 @@ InfoPanel {
qproperty-tagLoreColor: ${text_dim}; qproperty-tagLoreColor: ${text_dim};
} }
/* ---------- Video player letterbox / pillarbox color (mpv background) ---------- */ /* ---------- Video player letterbox ---------- */
VideoPlayer { VideoPlayer {
qproperty-letterboxColor: ${bg}; qproperty-letterboxColor: ${bg};
} }
/* ---------- Popout overlay bars (slideshow toolbar + slideshow controls + embedded preview controls) ---------- */ /* ---------- Popout overlay bars ---------- */
/*
* The popout window's translucent toolbar (top) and transport controls
* (bottom) float over the video content. The bg color comes from the
* @palette overlay_bg slot. Children get the classic overlay treatment:
* transparent backgrounds, near-white text, hairline borders.
*/
QWidget#_slideshow_toolbar, QWidget#_slideshow_toolbar,
QWidget#_slideshow_controls, QWidget#_slideshow_controls,
@ -588,6 +472,8 @@ QWidget#_preview_controls QPushButton {
color: white; color: white;
border: 1px solid rgba(255, 255, 255, 80); border: 1px solid rgba(255, 255, 255, 80);
padding: 2px 6px; padding: 2px 6px;
font-size: 15px;
font-weight: bold;
} }
QWidget#_slideshow_toolbar QPushButton:hover, QWidget#_slideshow_toolbar QPushButton:hover,
QWidget#_slideshow_controls QPushButton:hover, QWidget#_slideshow_controls QPushButton:hover,

View File

@ -28,7 +28,6 @@
warning: #f9e2af warning: #f9e2af
overlay_bg: rgba(30, 30, 46, 200) overlay_bg: rgba(30, 30, 46, 200)
*/ */
/* ---------- Base ---------- */ /* ---------- Base ---------- */
QWidget { QWidget {
@ -43,8 +42,6 @@ QWidget:disabled {
color: ${text_disabled}; color: ${text_disabled};
} }
/* Labels should never paint an opaque background they sit on top of
* other widgets in many places (toolbars, info panels, overlays). */
QLabel { QLabel {
background: transparent; background: transparent;
} }
@ -91,47 +88,25 @@ QPushButton:flat:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QToolButton {
background-color: transparent;
color: ${text};
border: 1px solid transparent;
padding: 4px;
}
QToolButton:hover {
background-color: ${bg_hover};
border-color: ${border_strong};
}
QToolButton:pressed, QToolButton:checked {
background-color: ${bg_active};
}
/* ---------- Inputs ---------- */ /* ---------- Inputs ---------- */
QLineEdit, QSpinBox, QDoubleSpinBox, QTextEdit, QPlainTextEdit { QLineEdit, QSpinBox, QTextEdit {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 6px; padding: 2px 6px;
/* min-height ensures the painted text fits inside the widget bounds
* even when a parent layout (e.g. QFormLayout inside a QGroupBox)
* compresses the natural sizeHint. Without this, spinboxes in dense
* forms render with the top of the value text clipped. */
min-height: 16px; min-height: 16px;
selection-background-color: ${accent}; selection-background-color: ${accent};
selection-color: ${accent_text}; selection-color: ${accent_text};
} }
QLineEdit:focus, QLineEdit:focus,
QSpinBox:focus, QSpinBox:focus,
QDoubleSpinBox:focus, QTextEdit:focus {
QTextEdit:focus,
QPlainTextEdit:focus {
border-color: ${accent}; border-color: ${accent};
} }
QLineEdit:disabled, QLineEdit:disabled,
QSpinBox:disabled, QSpinBox:disabled,
QDoubleSpinBox:disabled, QTextEdit:disabled {
QTextEdit:disabled,
QPlainTextEdit:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
color: ${text_disabled}; color: ${text_disabled};
border-color: ${border}; border-color: ${border};
@ -309,17 +284,6 @@ QSlider::handle:horizontal:hover {
background: ${accent_dim}; background: ${accent_dim};
} }
QSlider::groove:vertical {
background: ${bg_subtle};
width: 4px;
}
QSlider::handle:vertical {
background: ${accent};
width: 12px;
height: 12px;
margin: 0 -5px;
}
/* ---------- Progress ---------- */ /* ---------- Progress ---------- */
QProgressBar { QProgressBar {
@ -333,32 +297,27 @@ QProgressBar::chunk {
background-color: ${accent}; background-color: ${accent};
} }
/* ---------- Checkboxes & radio buttons ---------- */ /* ---------- Checkboxes ---------- */
QCheckBox, QRadioButton { QCheckBox {
background: transparent; background: transparent;
color: ${text}; color: ${text};
spacing: 6px; spacing: 6px;
} }
QCheckBox::indicator, QRadioButton::indicator { QCheckBox::indicator {
width: 14px; width: 14px;
height: 14px; height: 14px;
background-color: ${bg_subtle}; background-color: ${bg_subtle};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
} }
QCheckBox::indicator { QCheckBox::indicator:hover {
}
QRadioButton::indicator {
border-radius: 7px;
}
QCheckBox::indicator:hover, QRadioButton::indicator:hover {
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:checked, QRadioButton::indicator:checked { QCheckBox::indicator:checked {
background-color: ${accent}; background-color: ${accent};
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:disabled, QRadioButton::indicator:disabled { QCheckBox::indicator:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
border-color: ${border}; border-color: ${border};
} }
@ -372,9 +331,9 @@ QToolTip {
padding: 4px 6px; padding: 4px 6px;
} }
/* ---------- Item views (lists, trees, tables) ---------- */ /* ---------- Lists ---------- */
QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget { QListView, QListWidget {
background-color: ${bg}; background-color: ${bg};
alternate-background-color: ${bg_alt}; alternate-background-color: ${bg_alt};
color: ${text}; color: ${text};
@ -383,35 +342,18 @@ QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget {
selection-color: ${accent_text}; selection-color: ${accent_text};
outline: none; outline: none;
} }
QListView::item, QListWidget::item, QListView::item, QListWidget::item {
QTreeView::item, QTreeWidget::item,
QTableView::item, QTableWidget::item {
padding: 4px; padding: 4px;
} }
QListView::item:hover, QListWidget::item:hover, QListView::item:hover, QListWidget::item:hover {
QTreeView::item:hover, QTreeWidget::item:hover,
QTableView::item:hover, QTableWidget::item:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QListView::item:selected, QListWidget::item:selected, QListView::item:selected, QListWidget::item:selected {
QTreeView::item:selected, QTreeWidget::item:selected,
QTableView::item:selected, QTableWidget::item:selected {
background-color: ${accent}; background-color: ${accent};
color: ${accent_text}; color: ${accent_text};
} }
QHeaderView::section { /* ---------- Tabs (settings dialog) ---------- */
background-color: ${bg_subtle};
color: ${text};
border: none;
border-right: 1px solid ${border};
padding: 4px 8px;
}
QHeaderView::section:hover {
background-color: ${bg_hover};
}
/* ---------- Tabs ---------- */
QTabWidget::pane { QTabWidget::pane {
border: 1px solid ${border}; border: 1px solid ${border};
@ -436,7 +378,7 @@ QTabBar::tab:hover:!selected {
color: ${text}; color: ${text};
} }
/* ---------- Group boxes ---------- */ /* ---------- Group boxes (settings dialog) ---------- */
QGroupBox { QGroupBox {
background: transparent; background: transparent;
@ -452,63 +394,14 @@ QGroupBox::title {
color: ${text_dim}; color: ${text_dim};
} }
/* ---------- Frames ---------- */ /* ---------- Rubber band (multi-select drag) ---------- */
QFrame[frameShape="4"], /* HLine */
QFrame[frameShape="5"] /* VLine */ {
background: ${border};
color: ${border};
}
/* ---------- Toolbars ---------- */
QToolBar {
background: ${bg};
border: none;
spacing: 4px;
padding: 2px;
}
QToolBar::separator {
background: ${border};
width: 1px;
margin: 4px 4px;
}
/* ---------- Dock widgets ---------- */
QDockWidget {
color: ${text};
titlebar-close-icon: none;
}
QDockWidget::title {
background: ${bg_subtle};
padding: 4px;
border: 1px solid ${border};
}
/* ---------- Rubber band (multi-select drag rectangle) ---------- */
QRubberBand { QRubberBand {
background: ${accent}; background: ${accent};
border: 1px solid ${accent}; border: 1px solid ${accent};
/* Qt blends rubber band at ~30% so this reads as a translucent
* accent-tinted rectangle without needing rgba(). */
} }
/* ---------- Library count label states ---------- */ /* ---------- Library count label states ---------- */
/*
* The library tab's count label switches between three visual states
* depending on what refresh() found. The state is exposed as a Qt
* dynamic property `libraryCountState` so users can override these
* rules in their custom.qss without touching the Python.
*
* normal N files default text color, no rule needed
* empty no items dim text (no items found, search miss)
* error bad/unreachable danger color + bold (real error)
*/
QLabel[libraryCountState="empty"] { QLabel[libraryCountState="empty"] {
color: ${text_dim}; color: ${text_dim};
@ -518,18 +411,18 @@ QLabel[libraryCountState="error"] {
font-weight: bold; font-weight: bold;
} }
/* ---------- Thumbnail dot indicators (Qt properties on ThumbnailWidget) ---------- */ /* ---------- Thumbnail indicators ---------- */
ThumbnailWidget { ThumbnailWidget {
qproperty-savedColor: #22cc22; /* green dot: saved to library — universal "confirmed" feel */ qproperty-savedColor: #22cc22;
qproperty-bookmarkedColor: #ffcc00; /* yellow star: bookmarked */ qproperty-bookmarkedColor: #ffcc00;
qproperty-selectionColor: ${accent}; qproperty-selectionColor: ${accent};
qproperty-multiSelectColor: ${accent_dim}; qproperty-multiSelectColor: ${accent_dim};
qproperty-hoverColor: ${accent}; qproperty-hoverColor: ${accent};
qproperty-idleColor: ${border_strong}; qproperty-idleColor: ${border_strong};
} }
/* ---------- Info panel tag category colors ---------- */ /* ---------- Info panel tag colors ---------- */
InfoPanel { InfoPanel {
qproperty-tagArtistColor: ${warning}; qproperty-tagArtistColor: ${warning};
@ -540,19 +433,13 @@ InfoPanel {
qproperty-tagLoreColor: ${text_dim}; qproperty-tagLoreColor: ${text_dim};
} }
/* ---------- Video player letterbox / pillarbox color (mpv background) ---------- */ /* ---------- Video player letterbox ---------- */
VideoPlayer { VideoPlayer {
qproperty-letterboxColor: ${bg}; qproperty-letterboxColor: ${bg};
} }
/* ---------- Popout overlay bars (slideshow toolbar + slideshow controls + embedded preview controls) ---------- */ /* ---------- Popout overlay bars ---------- */
/*
* The popout window's translucent toolbar (top) and transport controls
* (bottom) float over the video content. The bg color comes from the
* @palette overlay_bg slot. Children get the classic overlay treatment:
* transparent backgrounds, near-white text, hairline borders.
*/
QWidget#_slideshow_toolbar, QWidget#_slideshow_toolbar,
QWidget#_slideshow_controls, QWidget#_slideshow_controls,
@ -575,6 +462,8 @@ QWidget#_preview_controls QPushButton {
color: white; color: white;
border: 1px solid rgba(255, 255, 255, 80); border: 1px solid rgba(255, 255, 255, 80);
padding: 2px 6px; padding: 2px 6px;
font-size: 15px;
font-weight: bold;
} }
QWidget#_slideshow_toolbar QPushButton:hover, QWidget#_slideshow_toolbar QPushButton:hover,
QWidget#_slideshow_controls QPushButton:hover, QWidget#_slideshow_controls QPushButton:hover,

View File

@ -1,4 +1,4 @@
/* booru-viewer Everforest Dark /* booru-viewer Everforest
* *
* Edit the @palette block below to recolor this rounded variant. The body uses * Edit the @palette block below to recolor this rounded variant. The body uses
* ${...} placeholders that the app's _load_user_qss preprocessor * ${...} placeholders that the app's _load_user_qss preprocessor
@ -28,7 +28,6 @@
warning: #dbbc7f warning: #dbbc7f
overlay_bg: rgba(45, 53, 59, 200) overlay_bg: rgba(45, 53, 59, 200)
*/ */
/* ---------- Base ---------- */ /* ---------- Base ---------- */
QWidget { QWidget {
@ -43,8 +42,6 @@ QWidget:disabled {
color: ${text_disabled}; color: ${text_disabled};
} }
/* Labels should never paint an opaque background they sit on top of
* other widgets in many places (toolbars, info panels, overlays). */
QLabel { QLabel {
background: transparent; background: transparent;
} }
@ -92,49 +89,26 @@ QPushButton:flat:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QToolButton {
background-color: transparent;
color: ${text};
border: 1px solid transparent;
border-radius: 4px;
padding: 4px;
}
QToolButton:hover {
background-color: ${bg_hover};
border-color: ${border_strong};
}
QToolButton:pressed, QToolButton:checked {
background-color: ${bg_active};
}
/* ---------- Inputs ---------- */ /* ---------- Inputs ---------- */
QLineEdit, QSpinBox, QDoubleSpinBox, QTextEdit, QPlainTextEdit { QLineEdit, QSpinBox, QTextEdit {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 6px; padding: 2px 6px;
/* min-height ensures the painted text fits inside the widget bounds
* even when a parent layout (e.g. QFormLayout inside a QGroupBox)
* compresses the natural sizeHint. Without this, spinboxes in dense
* forms render with the top of the value text clipped. */
min-height: 16px; min-height: 16px;
selection-background-color: ${accent}; selection-background-color: ${accent};
selection-color: ${accent_text}; selection-color: ${accent_text};
} }
QLineEdit:focus, QLineEdit:focus,
QSpinBox:focus, QSpinBox:focus,
QDoubleSpinBox:focus, QTextEdit:focus {
QTextEdit:focus,
QPlainTextEdit:focus {
border-color: ${accent}; border-color: ${accent};
} }
QLineEdit:disabled, QLineEdit:disabled,
QSpinBox:disabled, QSpinBox:disabled,
QDoubleSpinBox:disabled, QTextEdit:disabled {
QTextEdit:disabled,
QPlainTextEdit:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
color: ${text_disabled}; color: ${text_disabled};
border-color: ${border}; border-color: ${border};
@ -315,19 +289,6 @@ QSlider::handle:horizontal:hover {
background: ${accent_dim}; background: ${accent_dim};
} }
QSlider::groove:vertical {
background: ${bg_subtle};
width: 4px;
border-radius: 2px;
}
QSlider::handle:vertical {
background: ${accent};
width: 12px;
height: 12px;
margin: 0 -5px;
border-radius: 6px;
}
/* ---------- Progress ---------- */ /* ---------- Progress ---------- */
QProgressBar { QProgressBar {
@ -343,33 +304,28 @@ QProgressBar::chunk {
border-radius: 3px; border-radius: 3px;
} }
/* ---------- Checkboxes & radio buttons ---------- */ /* ---------- Checkboxes ---------- */
QCheckBox, QRadioButton { QCheckBox {
background: transparent; background: transparent;
color: ${text}; color: ${text};
spacing: 6px; spacing: 6px;
} }
QCheckBox::indicator, QRadioButton::indicator { QCheckBox::indicator {
width: 14px; width: 14px;
height: 14px; height: 14px;
background-color: ${bg_subtle}; background-color: ${bg_subtle};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
}
QCheckBox::indicator {
border-radius: 3px; border-radius: 3px;
} }
QRadioButton::indicator { QCheckBox::indicator:hover {
border-radius: 7px;
}
QCheckBox::indicator:hover, QRadioButton::indicator:hover {
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:checked, QRadioButton::indicator:checked { QCheckBox::indicator:checked {
background-color: ${accent}; background-color: ${accent};
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:disabled, QRadioButton::indicator:disabled { QCheckBox::indicator:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
border-color: ${border}; border-color: ${border};
} }
@ -384,9 +340,9 @@ QToolTip {
border-radius: 3px; border-radius: 3px;
} }
/* ---------- Item views (lists, trees, tables) ---------- */ /* ---------- Lists ---------- */
QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget { QListView, QListWidget {
background-color: ${bg}; background-color: ${bg};
alternate-background-color: ${bg_alt}; alternate-background-color: ${bg_alt};
color: ${text}; color: ${text};
@ -395,35 +351,18 @@ QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget {
selection-color: ${accent_text}; selection-color: ${accent_text};
outline: none; outline: none;
} }
QListView::item, QListWidget::item, QListView::item, QListWidget::item {
QTreeView::item, QTreeWidget::item,
QTableView::item, QTableWidget::item {
padding: 4px; padding: 4px;
} }
QListView::item:hover, QListWidget::item:hover, QListView::item:hover, QListWidget::item:hover {
QTreeView::item:hover, QTreeWidget::item:hover,
QTableView::item:hover, QTableWidget::item:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QListView::item:selected, QListWidget::item:selected, QListView::item:selected, QListWidget::item:selected {
QTreeView::item:selected, QTreeWidget::item:selected,
QTableView::item:selected, QTableWidget::item:selected {
background-color: ${accent}; background-color: ${accent};
color: ${accent_text}; color: ${accent_text};
} }
QHeaderView::section { /* ---------- Tabs (settings dialog) ---------- */
background-color: ${bg_subtle};
color: ${text};
border: none;
border-right: 1px solid ${border};
padding: 4px 8px;
}
QHeaderView::section:hover {
background-color: ${bg_hover};
}
/* ---------- Tabs ---------- */
QTabWidget::pane { QTabWidget::pane {
border: 1px solid ${border}; border: 1px solid ${border};
@ -448,7 +387,7 @@ QTabBar::tab:hover:!selected {
color: ${text}; color: ${text};
} }
/* ---------- Group boxes ---------- */ /* ---------- Group boxes (settings dialog) ---------- */
QGroupBox { QGroupBox {
background: transparent; background: transparent;
@ -465,63 +404,14 @@ QGroupBox::title {
color: ${text_dim}; color: ${text_dim};
} }
/* ---------- Frames ---------- */ /* ---------- Rubber band (multi-select drag) ---------- */
QFrame[frameShape="4"], /* HLine */
QFrame[frameShape="5"] /* VLine */ {
background: ${border};
color: ${border};
}
/* ---------- Toolbars ---------- */
QToolBar {
background: ${bg};
border: none;
spacing: 4px;
padding: 2px;
}
QToolBar::separator {
background: ${border};
width: 1px;
margin: 4px 4px;
}
/* ---------- Dock widgets ---------- */
QDockWidget {
color: ${text};
titlebar-close-icon: none;
}
QDockWidget::title {
background: ${bg_subtle};
padding: 4px;
border: 1px solid ${border};
}
/* ---------- Rubber band (multi-select drag rectangle) ---------- */
QRubberBand { QRubberBand {
background: ${accent}; background: ${accent};
border: 1px solid ${accent}; border: 1px solid ${accent};
/* Qt blends rubber band at ~30% so this reads as a translucent
* accent-tinted rectangle without needing rgba(). */
} }
/* ---------- Library count label states ---------- */ /* ---------- Library count label states ---------- */
/*
* The library tab's count label switches between three visual states
* depending on what refresh() found. The state is exposed as a Qt
* dynamic property `libraryCountState` so users can override these
* rules in their custom.qss without touching the Python.
*
* normal N files default text color, no rule needed
* empty no items dim text (no items found, search miss)
* error bad/unreachable danger color + bold (real error)
*/
QLabel[libraryCountState="empty"] { QLabel[libraryCountState="empty"] {
color: ${text_dim}; color: ${text_dim};
@ -531,18 +421,18 @@ QLabel[libraryCountState="error"] {
font-weight: bold; font-weight: bold;
} }
/* ---------- Thumbnail dot indicators (Qt properties on ThumbnailWidget) ---------- */ /* ---------- Thumbnail indicators ---------- */
ThumbnailWidget { ThumbnailWidget {
qproperty-savedColor: #22cc22; /* green dot: saved to library — universal "confirmed" feel */ qproperty-savedColor: #22cc22;
qproperty-bookmarkedColor: #ffcc00; /* yellow star: bookmarked */ qproperty-bookmarkedColor: #ffcc00;
qproperty-selectionColor: ${accent}; qproperty-selectionColor: ${accent};
qproperty-multiSelectColor: ${accent_dim}; qproperty-multiSelectColor: ${accent_dim};
qproperty-hoverColor: ${accent}; qproperty-hoverColor: ${accent};
qproperty-idleColor: ${border_strong}; qproperty-idleColor: ${border_strong};
} }
/* ---------- Info panel tag category colors ---------- */ /* ---------- Info panel tag colors ---------- */
InfoPanel { InfoPanel {
qproperty-tagArtistColor: ${warning}; qproperty-tagArtistColor: ${warning};
@ -553,19 +443,13 @@ InfoPanel {
qproperty-tagLoreColor: ${text_dim}; qproperty-tagLoreColor: ${text_dim};
} }
/* ---------- Video player letterbox / pillarbox color (mpv background) ---------- */ /* ---------- Video player letterbox ---------- */
VideoPlayer { VideoPlayer {
qproperty-letterboxColor: ${bg}; qproperty-letterboxColor: ${bg};
} }
/* ---------- Popout overlay bars (slideshow toolbar + slideshow controls + embedded preview controls) ---------- */ /* ---------- Popout overlay bars ---------- */
/*
* The popout window's translucent toolbar (top) and transport controls
* (bottom) float over the video content. The bg color comes from the
* @palette overlay_bg slot. Children get the classic overlay treatment:
* transparent backgrounds, near-white text, hairline borders.
*/
QWidget#_slideshow_toolbar, QWidget#_slideshow_toolbar,
QWidget#_slideshow_controls, QWidget#_slideshow_controls,
@ -588,6 +472,8 @@ QWidget#_preview_controls QPushButton {
color: white; color: white;
border: 1px solid rgba(255, 255, 255, 80); border: 1px solid rgba(255, 255, 255, 80);
padding: 2px 6px; padding: 2px 6px;
font-size: 15px;
font-weight: bold;
} }
QWidget#_slideshow_toolbar QPushButton:hover, QWidget#_slideshow_toolbar QPushButton:hover,
QWidget#_slideshow_controls QPushButton:hover, QWidget#_slideshow_controls QPushButton:hover,

View File

@ -1,4 +1,4 @@
/* booru-viewer Everforest Dark /* booru-viewer Everforest
* *
* Edit the @palette block below to recolor this square variant. The body uses * Edit the @palette block below to recolor this square variant. The body uses
* ${...} placeholders that the app's _load_user_qss preprocessor * ${...} placeholders that the app's _load_user_qss preprocessor
@ -28,7 +28,6 @@
warning: #dbbc7f warning: #dbbc7f
overlay_bg: rgba(45, 53, 59, 200) overlay_bg: rgba(45, 53, 59, 200)
*/ */
/* ---------- Base ---------- */ /* ---------- Base ---------- */
QWidget { QWidget {
@ -43,8 +42,6 @@ QWidget:disabled {
color: ${text_disabled}; color: ${text_disabled};
} }
/* Labels should never paint an opaque background they sit on top of
* other widgets in many places (toolbars, info panels, overlays). */
QLabel { QLabel {
background: transparent; background: transparent;
} }
@ -91,47 +88,25 @@ QPushButton:flat:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QToolButton {
background-color: transparent;
color: ${text};
border: 1px solid transparent;
padding: 4px;
}
QToolButton:hover {
background-color: ${bg_hover};
border-color: ${border_strong};
}
QToolButton:pressed, QToolButton:checked {
background-color: ${bg_active};
}
/* ---------- Inputs ---------- */ /* ---------- Inputs ---------- */
QLineEdit, QSpinBox, QDoubleSpinBox, QTextEdit, QPlainTextEdit { QLineEdit, QSpinBox, QTextEdit {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 6px; padding: 2px 6px;
/* min-height ensures the painted text fits inside the widget bounds
* even when a parent layout (e.g. QFormLayout inside a QGroupBox)
* compresses the natural sizeHint. Without this, spinboxes in dense
* forms render with the top of the value text clipped. */
min-height: 16px; min-height: 16px;
selection-background-color: ${accent}; selection-background-color: ${accent};
selection-color: ${accent_text}; selection-color: ${accent_text};
} }
QLineEdit:focus, QLineEdit:focus,
QSpinBox:focus, QSpinBox:focus,
QDoubleSpinBox:focus, QTextEdit:focus {
QTextEdit:focus,
QPlainTextEdit:focus {
border-color: ${accent}; border-color: ${accent};
} }
QLineEdit:disabled, QLineEdit:disabled,
QSpinBox:disabled, QSpinBox:disabled,
QDoubleSpinBox:disabled, QTextEdit:disabled {
QTextEdit:disabled,
QPlainTextEdit:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
color: ${text_disabled}; color: ${text_disabled};
border-color: ${border}; border-color: ${border};
@ -309,17 +284,6 @@ QSlider::handle:horizontal:hover {
background: ${accent_dim}; background: ${accent_dim};
} }
QSlider::groove:vertical {
background: ${bg_subtle};
width: 4px;
}
QSlider::handle:vertical {
background: ${accent};
width: 12px;
height: 12px;
margin: 0 -5px;
}
/* ---------- Progress ---------- */ /* ---------- Progress ---------- */
QProgressBar { QProgressBar {
@ -333,32 +297,27 @@ QProgressBar::chunk {
background-color: ${accent}; background-color: ${accent};
} }
/* ---------- Checkboxes & radio buttons ---------- */ /* ---------- Checkboxes ---------- */
QCheckBox, QRadioButton { QCheckBox {
background: transparent; background: transparent;
color: ${text}; color: ${text};
spacing: 6px; spacing: 6px;
} }
QCheckBox::indicator, QRadioButton::indicator { QCheckBox::indicator {
width: 14px; width: 14px;
height: 14px; height: 14px;
background-color: ${bg_subtle}; background-color: ${bg_subtle};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
} }
QCheckBox::indicator { QCheckBox::indicator:hover {
}
QRadioButton::indicator {
border-radius: 7px;
}
QCheckBox::indicator:hover, QRadioButton::indicator:hover {
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:checked, QRadioButton::indicator:checked { QCheckBox::indicator:checked {
background-color: ${accent}; background-color: ${accent};
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:disabled, QRadioButton::indicator:disabled { QCheckBox::indicator:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
border-color: ${border}; border-color: ${border};
} }
@ -372,9 +331,9 @@ QToolTip {
padding: 4px 6px; padding: 4px 6px;
} }
/* ---------- Item views (lists, trees, tables) ---------- */ /* ---------- Lists ---------- */
QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget { QListView, QListWidget {
background-color: ${bg}; background-color: ${bg};
alternate-background-color: ${bg_alt}; alternate-background-color: ${bg_alt};
color: ${text}; color: ${text};
@ -383,35 +342,18 @@ QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget {
selection-color: ${accent_text}; selection-color: ${accent_text};
outline: none; outline: none;
} }
QListView::item, QListWidget::item, QListView::item, QListWidget::item {
QTreeView::item, QTreeWidget::item,
QTableView::item, QTableWidget::item {
padding: 4px; padding: 4px;
} }
QListView::item:hover, QListWidget::item:hover, QListView::item:hover, QListWidget::item:hover {
QTreeView::item:hover, QTreeWidget::item:hover,
QTableView::item:hover, QTableWidget::item:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QListView::item:selected, QListWidget::item:selected, QListView::item:selected, QListWidget::item:selected {
QTreeView::item:selected, QTreeWidget::item:selected,
QTableView::item:selected, QTableWidget::item:selected {
background-color: ${accent}; background-color: ${accent};
color: ${accent_text}; color: ${accent_text};
} }
QHeaderView::section { /* ---------- Tabs (settings dialog) ---------- */
background-color: ${bg_subtle};
color: ${text};
border: none;
border-right: 1px solid ${border};
padding: 4px 8px;
}
QHeaderView::section:hover {
background-color: ${bg_hover};
}
/* ---------- Tabs ---------- */
QTabWidget::pane { QTabWidget::pane {
border: 1px solid ${border}; border: 1px solid ${border};
@ -436,7 +378,7 @@ QTabBar::tab:hover:!selected {
color: ${text}; color: ${text};
} }
/* ---------- Group boxes ---------- */ /* ---------- Group boxes (settings dialog) ---------- */
QGroupBox { QGroupBox {
background: transparent; background: transparent;
@ -452,63 +394,14 @@ QGroupBox::title {
color: ${text_dim}; color: ${text_dim};
} }
/* ---------- Frames ---------- */ /* ---------- Rubber band (multi-select drag) ---------- */
QFrame[frameShape="4"], /* HLine */
QFrame[frameShape="5"] /* VLine */ {
background: ${border};
color: ${border};
}
/* ---------- Toolbars ---------- */
QToolBar {
background: ${bg};
border: none;
spacing: 4px;
padding: 2px;
}
QToolBar::separator {
background: ${border};
width: 1px;
margin: 4px 4px;
}
/* ---------- Dock widgets ---------- */
QDockWidget {
color: ${text};
titlebar-close-icon: none;
}
QDockWidget::title {
background: ${bg_subtle};
padding: 4px;
border: 1px solid ${border};
}
/* ---------- Rubber band (multi-select drag rectangle) ---------- */
QRubberBand { QRubberBand {
background: ${accent}; background: ${accent};
border: 1px solid ${accent}; border: 1px solid ${accent};
/* Qt blends rubber band at ~30% so this reads as a translucent
* accent-tinted rectangle without needing rgba(). */
} }
/* ---------- Library count label states ---------- */ /* ---------- Library count label states ---------- */
/*
* The library tab's count label switches between three visual states
* depending on what refresh() found. The state is exposed as a Qt
* dynamic property `libraryCountState` so users can override these
* rules in their custom.qss without touching the Python.
*
* normal N files default text color, no rule needed
* empty no items dim text (no items found, search miss)
* error bad/unreachable danger color + bold (real error)
*/
QLabel[libraryCountState="empty"] { QLabel[libraryCountState="empty"] {
color: ${text_dim}; color: ${text_dim};
@ -518,18 +411,18 @@ QLabel[libraryCountState="error"] {
font-weight: bold; font-weight: bold;
} }
/* ---------- Thumbnail dot indicators (Qt properties on ThumbnailWidget) ---------- */ /* ---------- Thumbnail indicators ---------- */
ThumbnailWidget { ThumbnailWidget {
qproperty-savedColor: #22cc22; /* green dot: saved to library — universal "confirmed" feel */ qproperty-savedColor: #22cc22;
qproperty-bookmarkedColor: #ffcc00; /* yellow star: bookmarked */ qproperty-bookmarkedColor: #ffcc00;
qproperty-selectionColor: ${accent}; qproperty-selectionColor: ${accent};
qproperty-multiSelectColor: ${accent_dim}; qproperty-multiSelectColor: ${accent_dim};
qproperty-hoverColor: ${accent}; qproperty-hoverColor: ${accent};
qproperty-idleColor: ${border_strong}; qproperty-idleColor: ${border_strong};
} }
/* ---------- Info panel tag category colors ---------- */ /* ---------- Info panel tag colors ---------- */
InfoPanel { InfoPanel {
qproperty-tagArtistColor: ${warning}; qproperty-tagArtistColor: ${warning};
@ -540,19 +433,13 @@ InfoPanel {
qproperty-tagLoreColor: ${text_dim}; qproperty-tagLoreColor: ${text_dim};
} }
/* ---------- Video player letterbox / pillarbox color (mpv background) ---------- */ /* ---------- Video player letterbox ---------- */
VideoPlayer { VideoPlayer {
qproperty-letterboxColor: ${bg}; qproperty-letterboxColor: ${bg};
} }
/* ---------- Popout overlay bars (slideshow toolbar + slideshow controls + embedded preview controls) ---------- */ /* ---------- Popout overlay bars ---------- */
/*
* The popout window's translucent toolbar (top) and transport controls
* (bottom) float over the video content. The bg color comes from the
* @palette overlay_bg slot. Children get the classic overlay treatment:
* transparent backgrounds, near-white text, hairline borders.
*/
QWidget#_slideshow_toolbar, QWidget#_slideshow_toolbar,
QWidget#_slideshow_controls, QWidget#_slideshow_controls,
@ -575,6 +462,8 @@ QWidget#_preview_controls QPushButton {
color: white; color: white;
border: 1px solid rgba(255, 255, 255, 80); border: 1px solid rgba(255, 255, 255, 80);
padding: 2px 6px; padding: 2px 6px;
font-size: 15px;
font-weight: bold;
} }
QWidget#_slideshow_toolbar QPushButton:hover, QWidget#_slideshow_toolbar QPushButton:hover,
QWidget#_slideshow_controls QPushButton:hover, QWidget#_slideshow_controls QPushButton:hover,

View File

@ -1,4 +1,4 @@
/* booru-viewer Gruvbox Dark /* booru-viewer Gruvbox
* *
* Edit the @palette block below to recolor this rounded variant. The body uses * Edit the @palette block below to recolor this rounded variant. The body uses
* ${...} placeholders that the app's _load_user_qss preprocessor * ${...} placeholders that the app's _load_user_qss preprocessor
@ -28,7 +28,6 @@
warning: #fabd2f warning: #fabd2f
overlay_bg: rgba(40, 40, 40, 200) overlay_bg: rgba(40, 40, 40, 200)
*/ */
/* ---------- Base ---------- */ /* ---------- Base ---------- */
QWidget { QWidget {
@ -43,8 +42,6 @@ QWidget:disabled {
color: ${text_disabled}; color: ${text_disabled};
} }
/* Labels should never paint an opaque background they sit on top of
* other widgets in many places (toolbars, info panels, overlays). */
QLabel { QLabel {
background: transparent; background: transparent;
} }
@ -92,49 +89,26 @@ QPushButton:flat:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QToolButton {
background-color: transparent;
color: ${text};
border: 1px solid transparent;
border-radius: 4px;
padding: 4px;
}
QToolButton:hover {
background-color: ${bg_hover};
border-color: ${border_strong};
}
QToolButton:pressed, QToolButton:checked {
background-color: ${bg_active};
}
/* ---------- Inputs ---------- */ /* ---------- Inputs ---------- */
QLineEdit, QSpinBox, QDoubleSpinBox, QTextEdit, QPlainTextEdit { QLineEdit, QSpinBox, QTextEdit {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 6px; padding: 2px 6px;
/* min-height ensures the painted text fits inside the widget bounds
* even when a parent layout (e.g. QFormLayout inside a QGroupBox)
* compresses the natural sizeHint. Without this, spinboxes in dense
* forms render with the top of the value text clipped. */
min-height: 16px; min-height: 16px;
selection-background-color: ${accent}; selection-background-color: ${accent};
selection-color: ${accent_text}; selection-color: ${accent_text};
} }
QLineEdit:focus, QLineEdit:focus,
QSpinBox:focus, QSpinBox:focus,
QDoubleSpinBox:focus, QTextEdit:focus {
QTextEdit:focus,
QPlainTextEdit:focus {
border-color: ${accent}; border-color: ${accent};
} }
QLineEdit:disabled, QLineEdit:disabled,
QSpinBox:disabled, QSpinBox:disabled,
QDoubleSpinBox:disabled, QTextEdit:disabled {
QTextEdit:disabled,
QPlainTextEdit:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
color: ${text_disabled}; color: ${text_disabled};
border-color: ${border}; border-color: ${border};
@ -315,19 +289,6 @@ QSlider::handle:horizontal:hover {
background: ${accent_dim}; background: ${accent_dim};
} }
QSlider::groove:vertical {
background: ${bg_subtle};
width: 4px;
border-radius: 2px;
}
QSlider::handle:vertical {
background: ${accent};
width: 12px;
height: 12px;
margin: 0 -5px;
border-radius: 6px;
}
/* ---------- Progress ---------- */ /* ---------- Progress ---------- */
QProgressBar { QProgressBar {
@ -343,33 +304,28 @@ QProgressBar::chunk {
border-radius: 3px; border-radius: 3px;
} }
/* ---------- Checkboxes & radio buttons ---------- */ /* ---------- Checkboxes ---------- */
QCheckBox, QRadioButton { QCheckBox {
background: transparent; background: transparent;
color: ${text}; color: ${text};
spacing: 6px; spacing: 6px;
} }
QCheckBox::indicator, QRadioButton::indicator { QCheckBox::indicator {
width: 14px; width: 14px;
height: 14px; height: 14px;
background-color: ${bg_subtle}; background-color: ${bg_subtle};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
}
QCheckBox::indicator {
border-radius: 3px; border-radius: 3px;
} }
QRadioButton::indicator { QCheckBox::indicator:hover {
border-radius: 7px;
}
QCheckBox::indicator:hover, QRadioButton::indicator:hover {
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:checked, QRadioButton::indicator:checked { QCheckBox::indicator:checked {
background-color: ${accent}; background-color: ${accent};
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:disabled, QRadioButton::indicator:disabled { QCheckBox::indicator:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
border-color: ${border}; border-color: ${border};
} }
@ -384,9 +340,9 @@ QToolTip {
border-radius: 3px; border-radius: 3px;
} }
/* ---------- Item views (lists, trees, tables) ---------- */ /* ---------- Lists ---------- */
QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget { QListView, QListWidget {
background-color: ${bg}; background-color: ${bg};
alternate-background-color: ${bg_alt}; alternate-background-color: ${bg_alt};
color: ${text}; color: ${text};
@ -395,35 +351,18 @@ QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget {
selection-color: ${accent_text}; selection-color: ${accent_text};
outline: none; outline: none;
} }
QListView::item, QListWidget::item, QListView::item, QListWidget::item {
QTreeView::item, QTreeWidget::item,
QTableView::item, QTableWidget::item {
padding: 4px; padding: 4px;
} }
QListView::item:hover, QListWidget::item:hover, QListView::item:hover, QListWidget::item:hover {
QTreeView::item:hover, QTreeWidget::item:hover,
QTableView::item:hover, QTableWidget::item:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QListView::item:selected, QListWidget::item:selected, QListView::item:selected, QListWidget::item:selected {
QTreeView::item:selected, QTreeWidget::item:selected,
QTableView::item:selected, QTableWidget::item:selected {
background-color: ${accent}; background-color: ${accent};
color: ${accent_text}; color: ${accent_text};
} }
QHeaderView::section { /* ---------- Tabs (settings dialog) ---------- */
background-color: ${bg_subtle};
color: ${text};
border: none;
border-right: 1px solid ${border};
padding: 4px 8px;
}
QHeaderView::section:hover {
background-color: ${bg_hover};
}
/* ---------- Tabs ---------- */
QTabWidget::pane { QTabWidget::pane {
border: 1px solid ${border}; border: 1px solid ${border};
@ -448,7 +387,7 @@ QTabBar::tab:hover:!selected {
color: ${text}; color: ${text};
} }
/* ---------- Group boxes ---------- */ /* ---------- Group boxes (settings dialog) ---------- */
QGroupBox { QGroupBox {
background: transparent; background: transparent;
@ -465,63 +404,14 @@ QGroupBox::title {
color: ${text_dim}; color: ${text_dim};
} }
/* ---------- Frames ---------- */ /* ---------- Rubber band (multi-select drag) ---------- */
QFrame[frameShape="4"], /* HLine */
QFrame[frameShape="5"] /* VLine */ {
background: ${border};
color: ${border};
}
/* ---------- Toolbars ---------- */
QToolBar {
background: ${bg};
border: none;
spacing: 4px;
padding: 2px;
}
QToolBar::separator {
background: ${border};
width: 1px;
margin: 4px 4px;
}
/* ---------- Dock widgets ---------- */
QDockWidget {
color: ${text};
titlebar-close-icon: none;
}
QDockWidget::title {
background: ${bg_subtle};
padding: 4px;
border: 1px solid ${border};
}
/* ---------- Rubber band (multi-select drag rectangle) ---------- */
QRubberBand { QRubberBand {
background: ${accent}; background: ${accent};
border: 1px solid ${accent}; border: 1px solid ${accent};
/* Qt blends rubber band at ~30% so this reads as a translucent
* accent-tinted rectangle without needing rgba(). */
} }
/* ---------- Library count label states ---------- */ /* ---------- Library count label states ---------- */
/*
* The library tab's count label switches between three visual states
* depending on what refresh() found. The state is exposed as a Qt
* dynamic property `libraryCountState` so users can override these
* rules in their custom.qss without touching the Python.
*
* normal N files default text color, no rule needed
* empty no items dim text (no items found, search miss)
* error bad/unreachable danger color + bold (real error)
*/
QLabel[libraryCountState="empty"] { QLabel[libraryCountState="empty"] {
color: ${text_dim}; color: ${text_dim};
@ -531,18 +421,18 @@ QLabel[libraryCountState="error"] {
font-weight: bold; font-weight: bold;
} }
/* ---------- Thumbnail dot indicators (Qt properties on ThumbnailWidget) ---------- */ /* ---------- Thumbnail indicators ---------- */
ThumbnailWidget { ThumbnailWidget {
qproperty-savedColor: #22cc22; /* green dot: saved to library — universal "confirmed" feel */ qproperty-savedColor: #22cc22;
qproperty-bookmarkedColor: #ffcc00; /* yellow star: bookmarked */ qproperty-bookmarkedColor: #ffcc00;
qproperty-selectionColor: ${accent}; qproperty-selectionColor: ${accent};
qproperty-multiSelectColor: ${accent_dim}; qproperty-multiSelectColor: ${accent_dim};
qproperty-hoverColor: ${accent}; qproperty-hoverColor: ${accent};
qproperty-idleColor: ${border_strong}; qproperty-idleColor: ${border_strong};
} }
/* ---------- Info panel tag category colors ---------- */ /* ---------- Info panel tag colors ---------- */
InfoPanel { InfoPanel {
qproperty-tagArtistColor: ${warning}; qproperty-tagArtistColor: ${warning};
@ -553,19 +443,13 @@ InfoPanel {
qproperty-tagLoreColor: ${text_dim}; qproperty-tagLoreColor: ${text_dim};
} }
/* ---------- Video player letterbox / pillarbox color (mpv background) ---------- */ /* ---------- Video player letterbox ---------- */
VideoPlayer { VideoPlayer {
qproperty-letterboxColor: ${bg}; qproperty-letterboxColor: ${bg};
} }
/* ---------- Popout overlay bars (slideshow toolbar + slideshow controls + embedded preview controls) ---------- */ /* ---------- Popout overlay bars ---------- */
/*
* The popout window's translucent toolbar (top) and transport controls
* (bottom) float over the video content. The bg color comes from the
* @palette overlay_bg slot. Children get the classic overlay treatment:
* transparent backgrounds, near-white text, hairline borders.
*/
QWidget#_slideshow_toolbar, QWidget#_slideshow_toolbar,
QWidget#_slideshow_controls, QWidget#_slideshow_controls,
@ -588,6 +472,8 @@ QWidget#_preview_controls QPushButton {
color: white; color: white;
border: 1px solid rgba(255, 255, 255, 80); border: 1px solid rgba(255, 255, 255, 80);
padding: 2px 6px; padding: 2px 6px;
font-size: 15px;
font-weight: bold;
} }
QWidget#_slideshow_toolbar QPushButton:hover, QWidget#_slideshow_toolbar QPushButton:hover,
QWidget#_slideshow_controls QPushButton:hover, QWidget#_slideshow_controls QPushButton:hover,

View File

@ -1,4 +1,4 @@
/* booru-viewer Gruvbox Dark /* booru-viewer Gruvbox
* *
* Edit the @palette block below to recolor this square variant. The body uses * Edit the @palette block below to recolor this square variant. The body uses
* ${...} placeholders that the app's _load_user_qss preprocessor * ${...} placeholders that the app's _load_user_qss preprocessor
@ -28,7 +28,6 @@
warning: #fabd2f warning: #fabd2f
overlay_bg: rgba(40, 40, 40, 200) overlay_bg: rgba(40, 40, 40, 200)
*/ */
/* ---------- Base ---------- */ /* ---------- Base ---------- */
QWidget { QWidget {
@ -43,8 +42,6 @@ QWidget:disabled {
color: ${text_disabled}; color: ${text_disabled};
} }
/* Labels should never paint an opaque background they sit on top of
* other widgets in many places (toolbars, info panels, overlays). */
QLabel { QLabel {
background: transparent; background: transparent;
} }
@ -91,47 +88,25 @@ QPushButton:flat:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QToolButton {
background-color: transparent;
color: ${text};
border: 1px solid transparent;
padding: 4px;
}
QToolButton:hover {
background-color: ${bg_hover};
border-color: ${border_strong};
}
QToolButton:pressed, QToolButton:checked {
background-color: ${bg_active};
}
/* ---------- Inputs ---------- */ /* ---------- Inputs ---------- */
QLineEdit, QSpinBox, QDoubleSpinBox, QTextEdit, QPlainTextEdit { QLineEdit, QSpinBox, QTextEdit {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 6px; padding: 2px 6px;
/* min-height ensures the painted text fits inside the widget bounds
* even when a parent layout (e.g. QFormLayout inside a QGroupBox)
* compresses the natural sizeHint. Without this, spinboxes in dense
* forms render with the top of the value text clipped. */
min-height: 16px; min-height: 16px;
selection-background-color: ${accent}; selection-background-color: ${accent};
selection-color: ${accent_text}; selection-color: ${accent_text};
} }
QLineEdit:focus, QLineEdit:focus,
QSpinBox:focus, QSpinBox:focus,
QDoubleSpinBox:focus, QTextEdit:focus {
QTextEdit:focus,
QPlainTextEdit:focus {
border-color: ${accent}; border-color: ${accent};
} }
QLineEdit:disabled, QLineEdit:disabled,
QSpinBox:disabled, QSpinBox:disabled,
QDoubleSpinBox:disabled, QTextEdit:disabled {
QTextEdit:disabled,
QPlainTextEdit:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
color: ${text_disabled}; color: ${text_disabled};
border-color: ${border}; border-color: ${border};
@ -309,17 +284,6 @@ QSlider::handle:horizontal:hover {
background: ${accent_dim}; background: ${accent_dim};
} }
QSlider::groove:vertical {
background: ${bg_subtle};
width: 4px;
}
QSlider::handle:vertical {
background: ${accent};
width: 12px;
height: 12px;
margin: 0 -5px;
}
/* ---------- Progress ---------- */ /* ---------- Progress ---------- */
QProgressBar { QProgressBar {
@ -333,32 +297,27 @@ QProgressBar::chunk {
background-color: ${accent}; background-color: ${accent};
} }
/* ---------- Checkboxes & radio buttons ---------- */ /* ---------- Checkboxes ---------- */
QCheckBox, QRadioButton { QCheckBox {
background: transparent; background: transparent;
color: ${text}; color: ${text};
spacing: 6px; spacing: 6px;
} }
QCheckBox::indicator, QRadioButton::indicator { QCheckBox::indicator {
width: 14px; width: 14px;
height: 14px; height: 14px;
background-color: ${bg_subtle}; background-color: ${bg_subtle};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
} }
QCheckBox::indicator { QCheckBox::indicator:hover {
}
QRadioButton::indicator {
border-radius: 7px;
}
QCheckBox::indicator:hover, QRadioButton::indicator:hover {
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:checked, QRadioButton::indicator:checked { QCheckBox::indicator:checked {
background-color: ${accent}; background-color: ${accent};
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:disabled, QRadioButton::indicator:disabled { QCheckBox::indicator:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
border-color: ${border}; border-color: ${border};
} }
@ -372,9 +331,9 @@ QToolTip {
padding: 4px 6px; padding: 4px 6px;
} }
/* ---------- Item views (lists, trees, tables) ---------- */ /* ---------- Lists ---------- */
QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget { QListView, QListWidget {
background-color: ${bg}; background-color: ${bg};
alternate-background-color: ${bg_alt}; alternate-background-color: ${bg_alt};
color: ${text}; color: ${text};
@ -383,35 +342,18 @@ QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget {
selection-color: ${accent_text}; selection-color: ${accent_text};
outline: none; outline: none;
} }
QListView::item, QListWidget::item, QListView::item, QListWidget::item {
QTreeView::item, QTreeWidget::item,
QTableView::item, QTableWidget::item {
padding: 4px; padding: 4px;
} }
QListView::item:hover, QListWidget::item:hover, QListView::item:hover, QListWidget::item:hover {
QTreeView::item:hover, QTreeWidget::item:hover,
QTableView::item:hover, QTableWidget::item:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QListView::item:selected, QListWidget::item:selected, QListView::item:selected, QListWidget::item:selected {
QTreeView::item:selected, QTreeWidget::item:selected,
QTableView::item:selected, QTableWidget::item:selected {
background-color: ${accent}; background-color: ${accent};
color: ${accent_text}; color: ${accent_text};
} }
QHeaderView::section { /* ---------- Tabs (settings dialog) ---------- */
background-color: ${bg_subtle};
color: ${text};
border: none;
border-right: 1px solid ${border};
padding: 4px 8px;
}
QHeaderView::section:hover {
background-color: ${bg_hover};
}
/* ---------- Tabs ---------- */
QTabWidget::pane { QTabWidget::pane {
border: 1px solid ${border}; border: 1px solid ${border};
@ -436,7 +378,7 @@ QTabBar::tab:hover:!selected {
color: ${text}; color: ${text};
} }
/* ---------- Group boxes ---------- */ /* ---------- Group boxes (settings dialog) ---------- */
QGroupBox { QGroupBox {
background: transparent; background: transparent;
@ -452,63 +394,14 @@ QGroupBox::title {
color: ${text_dim}; color: ${text_dim};
} }
/* ---------- Frames ---------- */ /* ---------- Rubber band (multi-select drag) ---------- */
QFrame[frameShape="4"], /* HLine */
QFrame[frameShape="5"] /* VLine */ {
background: ${border};
color: ${border};
}
/* ---------- Toolbars ---------- */
QToolBar {
background: ${bg};
border: none;
spacing: 4px;
padding: 2px;
}
QToolBar::separator {
background: ${border};
width: 1px;
margin: 4px 4px;
}
/* ---------- Dock widgets ---------- */
QDockWidget {
color: ${text};
titlebar-close-icon: none;
}
QDockWidget::title {
background: ${bg_subtle};
padding: 4px;
border: 1px solid ${border};
}
/* ---------- Rubber band (multi-select drag rectangle) ---------- */
QRubberBand { QRubberBand {
background: ${accent}; background: ${accent};
border: 1px solid ${accent}; border: 1px solid ${accent};
/* Qt blends rubber band at ~30% so this reads as a translucent
* accent-tinted rectangle without needing rgba(). */
} }
/* ---------- Library count label states ---------- */ /* ---------- Library count label states ---------- */
/*
* The library tab's count label switches between three visual states
* depending on what refresh() found. The state is exposed as a Qt
* dynamic property `libraryCountState` so users can override these
* rules in their custom.qss without touching the Python.
*
* normal N files default text color, no rule needed
* empty no items dim text (no items found, search miss)
* error bad/unreachable danger color + bold (real error)
*/
QLabel[libraryCountState="empty"] { QLabel[libraryCountState="empty"] {
color: ${text_dim}; color: ${text_dim};
@ -518,18 +411,18 @@ QLabel[libraryCountState="error"] {
font-weight: bold; font-weight: bold;
} }
/* ---------- Thumbnail dot indicators (Qt properties on ThumbnailWidget) ---------- */ /* ---------- Thumbnail indicators ---------- */
ThumbnailWidget { ThumbnailWidget {
qproperty-savedColor: #22cc22; /* green dot: saved to library — universal "confirmed" feel */ qproperty-savedColor: #22cc22;
qproperty-bookmarkedColor: #ffcc00; /* yellow star: bookmarked */ qproperty-bookmarkedColor: #ffcc00;
qproperty-selectionColor: ${accent}; qproperty-selectionColor: ${accent};
qproperty-multiSelectColor: ${accent_dim}; qproperty-multiSelectColor: ${accent_dim};
qproperty-hoverColor: ${accent}; qproperty-hoverColor: ${accent};
qproperty-idleColor: ${border_strong}; qproperty-idleColor: ${border_strong};
} }
/* ---------- Info panel tag category colors ---------- */ /* ---------- Info panel tag colors ---------- */
InfoPanel { InfoPanel {
qproperty-tagArtistColor: ${warning}; qproperty-tagArtistColor: ${warning};
@ -540,19 +433,13 @@ InfoPanel {
qproperty-tagLoreColor: ${text_dim}; qproperty-tagLoreColor: ${text_dim};
} }
/* ---------- Video player letterbox / pillarbox color (mpv background) ---------- */ /* ---------- Video player letterbox ---------- */
VideoPlayer { VideoPlayer {
qproperty-letterboxColor: ${bg}; qproperty-letterboxColor: ${bg};
} }
/* ---------- Popout overlay bars (slideshow toolbar + slideshow controls + embedded preview controls) ---------- */ /* ---------- Popout overlay bars ---------- */
/*
* The popout window's translucent toolbar (top) and transport controls
* (bottom) float over the video content. The bg color comes from the
* @palette overlay_bg slot. Children get the classic overlay treatment:
* transparent backgrounds, near-white text, hairline borders.
*/
QWidget#_slideshow_toolbar, QWidget#_slideshow_toolbar,
QWidget#_slideshow_controls, QWidget#_slideshow_controls,
@ -575,6 +462,8 @@ QWidget#_preview_controls QPushButton {
color: white; color: white;
border: 1px solid rgba(255, 255, 255, 80); border: 1px solid rgba(255, 255, 255, 80);
padding: 2px 6px; padding: 2px 6px;
font-size: 15px;
font-weight: bold;
} }
QWidget#_slideshow_toolbar QPushButton:hover, QWidget#_slideshow_toolbar QPushButton:hover,
QWidget#_slideshow_controls QPushButton:hover, QWidget#_slideshow_controls QPushButton:hover,

View File

@ -28,7 +28,6 @@
warning: #ebcb8b warning: #ebcb8b
overlay_bg: rgba(46, 52, 64, 200) overlay_bg: rgba(46, 52, 64, 200)
*/ */
/* ---------- Base ---------- */ /* ---------- Base ---------- */
QWidget { QWidget {
@ -43,8 +42,6 @@ QWidget:disabled {
color: ${text_disabled}; color: ${text_disabled};
} }
/* Labels should never paint an opaque background they sit on top of
* other widgets in many places (toolbars, info panels, overlays). */
QLabel { QLabel {
background: transparent; background: transparent;
} }
@ -92,49 +89,26 @@ QPushButton:flat:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QToolButton {
background-color: transparent;
color: ${text};
border: 1px solid transparent;
border-radius: 4px;
padding: 4px;
}
QToolButton:hover {
background-color: ${bg_hover};
border-color: ${border_strong};
}
QToolButton:pressed, QToolButton:checked {
background-color: ${bg_active};
}
/* ---------- Inputs ---------- */ /* ---------- Inputs ---------- */
QLineEdit, QSpinBox, QDoubleSpinBox, QTextEdit, QPlainTextEdit { QLineEdit, QSpinBox, QTextEdit {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 6px; padding: 2px 6px;
/* min-height ensures the painted text fits inside the widget bounds
* even when a parent layout (e.g. QFormLayout inside a QGroupBox)
* compresses the natural sizeHint. Without this, spinboxes in dense
* forms render with the top of the value text clipped. */
min-height: 16px; min-height: 16px;
selection-background-color: ${accent}; selection-background-color: ${accent};
selection-color: ${accent_text}; selection-color: ${accent_text};
} }
QLineEdit:focus, QLineEdit:focus,
QSpinBox:focus, QSpinBox:focus,
QDoubleSpinBox:focus, QTextEdit:focus {
QTextEdit:focus,
QPlainTextEdit:focus {
border-color: ${accent}; border-color: ${accent};
} }
QLineEdit:disabled, QLineEdit:disabled,
QSpinBox:disabled, QSpinBox:disabled,
QDoubleSpinBox:disabled, QTextEdit:disabled {
QTextEdit:disabled,
QPlainTextEdit:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
color: ${text_disabled}; color: ${text_disabled};
border-color: ${border}; border-color: ${border};
@ -315,19 +289,6 @@ QSlider::handle:horizontal:hover {
background: ${accent_dim}; background: ${accent_dim};
} }
QSlider::groove:vertical {
background: ${bg_subtle};
width: 4px;
border-radius: 2px;
}
QSlider::handle:vertical {
background: ${accent};
width: 12px;
height: 12px;
margin: 0 -5px;
border-radius: 6px;
}
/* ---------- Progress ---------- */ /* ---------- Progress ---------- */
QProgressBar { QProgressBar {
@ -343,33 +304,28 @@ QProgressBar::chunk {
border-radius: 3px; border-radius: 3px;
} }
/* ---------- Checkboxes & radio buttons ---------- */ /* ---------- Checkboxes ---------- */
QCheckBox, QRadioButton { QCheckBox {
background: transparent; background: transparent;
color: ${text}; color: ${text};
spacing: 6px; spacing: 6px;
} }
QCheckBox::indicator, QRadioButton::indicator { QCheckBox::indicator {
width: 14px; width: 14px;
height: 14px; height: 14px;
background-color: ${bg_subtle}; background-color: ${bg_subtle};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
}
QCheckBox::indicator {
border-radius: 3px; border-radius: 3px;
} }
QRadioButton::indicator { QCheckBox::indicator:hover {
border-radius: 7px;
}
QCheckBox::indicator:hover, QRadioButton::indicator:hover {
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:checked, QRadioButton::indicator:checked { QCheckBox::indicator:checked {
background-color: ${accent}; background-color: ${accent};
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:disabled, QRadioButton::indicator:disabled { QCheckBox::indicator:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
border-color: ${border}; border-color: ${border};
} }
@ -384,9 +340,9 @@ QToolTip {
border-radius: 3px; border-radius: 3px;
} }
/* ---------- Item views (lists, trees, tables) ---------- */ /* ---------- Lists ---------- */
QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget { QListView, QListWidget {
background-color: ${bg}; background-color: ${bg};
alternate-background-color: ${bg_alt}; alternate-background-color: ${bg_alt};
color: ${text}; color: ${text};
@ -395,35 +351,18 @@ QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget {
selection-color: ${accent_text}; selection-color: ${accent_text};
outline: none; outline: none;
} }
QListView::item, QListWidget::item, QListView::item, QListWidget::item {
QTreeView::item, QTreeWidget::item,
QTableView::item, QTableWidget::item {
padding: 4px; padding: 4px;
} }
QListView::item:hover, QListWidget::item:hover, QListView::item:hover, QListWidget::item:hover {
QTreeView::item:hover, QTreeWidget::item:hover,
QTableView::item:hover, QTableWidget::item:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QListView::item:selected, QListWidget::item:selected, QListView::item:selected, QListWidget::item:selected {
QTreeView::item:selected, QTreeWidget::item:selected,
QTableView::item:selected, QTableWidget::item:selected {
background-color: ${accent}; background-color: ${accent};
color: ${accent_text}; color: ${accent_text};
} }
QHeaderView::section { /* ---------- Tabs (settings dialog) ---------- */
background-color: ${bg_subtle};
color: ${text};
border: none;
border-right: 1px solid ${border};
padding: 4px 8px;
}
QHeaderView::section:hover {
background-color: ${bg_hover};
}
/* ---------- Tabs ---------- */
QTabWidget::pane { QTabWidget::pane {
border: 1px solid ${border}; border: 1px solid ${border};
@ -448,7 +387,7 @@ QTabBar::tab:hover:!selected {
color: ${text}; color: ${text};
} }
/* ---------- Group boxes ---------- */ /* ---------- Group boxes (settings dialog) ---------- */
QGroupBox { QGroupBox {
background: transparent; background: transparent;
@ -465,63 +404,14 @@ QGroupBox::title {
color: ${text_dim}; color: ${text_dim};
} }
/* ---------- Frames ---------- */ /* ---------- Rubber band (multi-select drag) ---------- */
QFrame[frameShape="4"], /* HLine */
QFrame[frameShape="5"] /* VLine */ {
background: ${border};
color: ${border};
}
/* ---------- Toolbars ---------- */
QToolBar {
background: ${bg};
border: none;
spacing: 4px;
padding: 2px;
}
QToolBar::separator {
background: ${border};
width: 1px;
margin: 4px 4px;
}
/* ---------- Dock widgets ---------- */
QDockWidget {
color: ${text};
titlebar-close-icon: none;
}
QDockWidget::title {
background: ${bg_subtle};
padding: 4px;
border: 1px solid ${border};
}
/* ---------- Rubber band (multi-select drag rectangle) ---------- */
QRubberBand { QRubberBand {
background: ${accent}; background: ${accent};
border: 1px solid ${accent}; border: 1px solid ${accent};
/* Qt blends rubber band at ~30% so this reads as a translucent
* accent-tinted rectangle without needing rgba(). */
} }
/* ---------- Library count label states ---------- */ /* ---------- Library count label states ---------- */
/*
* The library tab's count label switches between three visual states
* depending on what refresh() found. The state is exposed as a Qt
* dynamic property `libraryCountState` so users can override these
* rules in their custom.qss without touching the Python.
*
* normal N files default text color, no rule needed
* empty no items dim text (no items found, search miss)
* error bad/unreachable danger color + bold (real error)
*/
QLabel[libraryCountState="empty"] { QLabel[libraryCountState="empty"] {
color: ${text_dim}; color: ${text_dim};
@ -531,18 +421,18 @@ QLabel[libraryCountState="error"] {
font-weight: bold; font-weight: bold;
} }
/* ---------- Thumbnail dot indicators (Qt properties on ThumbnailWidget) ---------- */ /* ---------- Thumbnail indicators ---------- */
ThumbnailWidget { ThumbnailWidget {
qproperty-savedColor: #22cc22; /* green dot: saved to library — universal "confirmed" feel */ qproperty-savedColor: #22cc22;
qproperty-bookmarkedColor: #ffcc00; /* yellow star: bookmarked */ qproperty-bookmarkedColor: #ffcc00;
qproperty-selectionColor: ${accent}; qproperty-selectionColor: ${accent};
qproperty-multiSelectColor: ${accent_dim}; qproperty-multiSelectColor: ${accent_dim};
qproperty-hoverColor: ${accent}; qproperty-hoverColor: ${accent};
qproperty-idleColor: ${border_strong}; qproperty-idleColor: ${border_strong};
} }
/* ---------- Info panel tag category colors ---------- */ /* ---------- Info panel tag colors ---------- */
InfoPanel { InfoPanel {
qproperty-tagArtistColor: ${warning}; qproperty-tagArtistColor: ${warning};
@ -553,19 +443,13 @@ InfoPanel {
qproperty-tagLoreColor: ${text_dim}; qproperty-tagLoreColor: ${text_dim};
} }
/* ---------- Video player letterbox / pillarbox color (mpv background) ---------- */ /* ---------- Video player letterbox ---------- */
VideoPlayer { VideoPlayer {
qproperty-letterboxColor: ${bg}; qproperty-letterboxColor: ${bg};
} }
/* ---------- Popout overlay bars (slideshow toolbar + slideshow controls + embedded preview controls) ---------- */ /* ---------- Popout overlay bars ---------- */
/*
* The popout window's translucent toolbar (top) and transport controls
* (bottom) float over the video content. The bg color comes from the
* @palette overlay_bg slot. Children get the classic overlay treatment:
* transparent backgrounds, near-white text, hairline borders.
*/
QWidget#_slideshow_toolbar, QWidget#_slideshow_toolbar,
QWidget#_slideshow_controls, QWidget#_slideshow_controls,
@ -588,6 +472,8 @@ QWidget#_preview_controls QPushButton {
color: white; color: white;
border: 1px solid rgba(255, 255, 255, 80); border: 1px solid rgba(255, 255, 255, 80);
padding: 2px 6px; padding: 2px 6px;
font-size: 15px;
font-weight: bold;
} }
QWidget#_slideshow_toolbar QPushButton:hover, QWidget#_slideshow_toolbar QPushButton:hover,
QWidget#_slideshow_controls QPushButton:hover, QWidget#_slideshow_controls QPushButton:hover,

View File

@ -28,7 +28,6 @@
warning: #ebcb8b warning: #ebcb8b
overlay_bg: rgba(46, 52, 64, 200) overlay_bg: rgba(46, 52, 64, 200)
*/ */
/* ---------- Base ---------- */ /* ---------- Base ---------- */
QWidget { QWidget {
@ -43,8 +42,6 @@ QWidget:disabled {
color: ${text_disabled}; color: ${text_disabled};
} }
/* Labels should never paint an opaque background they sit on top of
* other widgets in many places (toolbars, info panels, overlays). */
QLabel { QLabel {
background: transparent; background: transparent;
} }
@ -91,47 +88,25 @@ QPushButton:flat:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QToolButton {
background-color: transparent;
color: ${text};
border: 1px solid transparent;
padding: 4px;
}
QToolButton:hover {
background-color: ${bg_hover};
border-color: ${border_strong};
}
QToolButton:pressed, QToolButton:checked {
background-color: ${bg_active};
}
/* ---------- Inputs ---------- */ /* ---------- Inputs ---------- */
QLineEdit, QSpinBox, QDoubleSpinBox, QTextEdit, QPlainTextEdit { QLineEdit, QSpinBox, QTextEdit {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 6px; padding: 2px 6px;
/* min-height ensures the painted text fits inside the widget bounds
* even when a parent layout (e.g. QFormLayout inside a QGroupBox)
* compresses the natural sizeHint. Without this, spinboxes in dense
* forms render with the top of the value text clipped. */
min-height: 16px; min-height: 16px;
selection-background-color: ${accent}; selection-background-color: ${accent};
selection-color: ${accent_text}; selection-color: ${accent_text};
} }
QLineEdit:focus, QLineEdit:focus,
QSpinBox:focus, QSpinBox:focus,
QDoubleSpinBox:focus, QTextEdit:focus {
QTextEdit:focus,
QPlainTextEdit:focus {
border-color: ${accent}; border-color: ${accent};
} }
QLineEdit:disabled, QLineEdit:disabled,
QSpinBox:disabled, QSpinBox:disabled,
QDoubleSpinBox:disabled, QTextEdit:disabled {
QTextEdit:disabled,
QPlainTextEdit:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
color: ${text_disabled}; color: ${text_disabled};
border-color: ${border}; border-color: ${border};
@ -309,17 +284,6 @@ QSlider::handle:horizontal:hover {
background: ${accent_dim}; background: ${accent_dim};
} }
QSlider::groove:vertical {
background: ${bg_subtle};
width: 4px;
}
QSlider::handle:vertical {
background: ${accent};
width: 12px;
height: 12px;
margin: 0 -5px;
}
/* ---------- Progress ---------- */ /* ---------- Progress ---------- */
QProgressBar { QProgressBar {
@ -333,32 +297,27 @@ QProgressBar::chunk {
background-color: ${accent}; background-color: ${accent};
} }
/* ---------- Checkboxes & radio buttons ---------- */ /* ---------- Checkboxes ---------- */
QCheckBox, QRadioButton { QCheckBox {
background: transparent; background: transparent;
color: ${text}; color: ${text};
spacing: 6px; spacing: 6px;
} }
QCheckBox::indicator, QRadioButton::indicator { QCheckBox::indicator {
width: 14px; width: 14px;
height: 14px; height: 14px;
background-color: ${bg_subtle}; background-color: ${bg_subtle};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
} }
QCheckBox::indicator { QCheckBox::indicator:hover {
}
QRadioButton::indicator {
border-radius: 7px;
}
QCheckBox::indicator:hover, QRadioButton::indicator:hover {
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:checked, QRadioButton::indicator:checked { QCheckBox::indicator:checked {
background-color: ${accent}; background-color: ${accent};
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:disabled, QRadioButton::indicator:disabled { QCheckBox::indicator:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
border-color: ${border}; border-color: ${border};
} }
@ -372,9 +331,9 @@ QToolTip {
padding: 4px 6px; padding: 4px 6px;
} }
/* ---------- Item views (lists, trees, tables) ---------- */ /* ---------- Lists ---------- */
QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget { QListView, QListWidget {
background-color: ${bg}; background-color: ${bg};
alternate-background-color: ${bg_alt}; alternate-background-color: ${bg_alt};
color: ${text}; color: ${text};
@ -383,35 +342,18 @@ QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget {
selection-color: ${accent_text}; selection-color: ${accent_text};
outline: none; outline: none;
} }
QListView::item, QListWidget::item, QListView::item, QListWidget::item {
QTreeView::item, QTreeWidget::item,
QTableView::item, QTableWidget::item {
padding: 4px; padding: 4px;
} }
QListView::item:hover, QListWidget::item:hover, QListView::item:hover, QListWidget::item:hover {
QTreeView::item:hover, QTreeWidget::item:hover,
QTableView::item:hover, QTableWidget::item:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QListView::item:selected, QListWidget::item:selected, QListView::item:selected, QListWidget::item:selected {
QTreeView::item:selected, QTreeWidget::item:selected,
QTableView::item:selected, QTableWidget::item:selected {
background-color: ${accent}; background-color: ${accent};
color: ${accent_text}; color: ${accent_text};
} }
QHeaderView::section { /* ---------- Tabs (settings dialog) ---------- */
background-color: ${bg_subtle};
color: ${text};
border: none;
border-right: 1px solid ${border};
padding: 4px 8px;
}
QHeaderView::section:hover {
background-color: ${bg_hover};
}
/* ---------- Tabs ---------- */
QTabWidget::pane { QTabWidget::pane {
border: 1px solid ${border}; border: 1px solid ${border};
@ -436,7 +378,7 @@ QTabBar::tab:hover:!selected {
color: ${text}; color: ${text};
} }
/* ---------- Group boxes ---------- */ /* ---------- Group boxes (settings dialog) ---------- */
QGroupBox { QGroupBox {
background: transparent; background: transparent;
@ -452,63 +394,14 @@ QGroupBox::title {
color: ${text_dim}; color: ${text_dim};
} }
/* ---------- Frames ---------- */ /* ---------- Rubber band (multi-select drag) ---------- */
QFrame[frameShape="4"], /* HLine */
QFrame[frameShape="5"] /* VLine */ {
background: ${border};
color: ${border};
}
/* ---------- Toolbars ---------- */
QToolBar {
background: ${bg};
border: none;
spacing: 4px;
padding: 2px;
}
QToolBar::separator {
background: ${border};
width: 1px;
margin: 4px 4px;
}
/* ---------- Dock widgets ---------- */
QDockWidget {
color: ${text};
titlebar-close-icon: none;
}
QDockWidget::title {
background: ${bg_subtle};
padding: 4px;
border: 1px solid ${border};
}
/* ---------- Rubber band (multi-select drag rectangle) ---------- */
QRubberBand { QRubberBand {
background: ${accent}; background: ${accent};
border: 1px solid ${accent}; border: 1px solid ${accent};
/* Qt blends rubber band at ~30% so this reads as a translucent
* accent-tinted rectangle without needing rgba(). */
} }
/* ---------- Library count label states ---------- */ /* ---------- Library count label states ---------- */
/*
* The library tab's count label switches between three visual states
* depending on what refresh() found. The state is exposed as a Qt
* dynamic property `libraryCountState` so users can override these
* rules in their custom.qss without touching the Python.
*
* normal N files default text color, no rule needed
* empty no items dim text (no items found, search miss)
* error bad/unreachable danger color + bold (real error)
*/
QLabel[libraryCountState="empty"] { QLabel[libraryCountState="empty"] {
color: ${text_dim}; color: ${text_dim};
@ -518,18 +411,18 @@ QLabel[libraryCountState="error"] {
font-weight: bold; font-weight: bold;
} }
/* ---------- Thumbnail dot indicators (Qt properties on ThumbnailWidget) ---------- */ /* ---------- Thumbnail indicators ---------- */
ThumbnailWidget { ThumbnailWidget {
qproperty-savedColor: #22cc22; /* green dot: saved to library — universal "confirmed" feel */ qproperty-savedColor: #22cc22;
qproperty-bookmarkedColor: #ffcc00; /* yellow star: bookmarked */ qproperty-bookmarkedColor: #ffcc00;
qproperty-selectionColor: ${accent}; qproperty-selectionColor: ${accent};
qproperty-multiSelectColor: ${accent_dim}; qproperty-multiSelectColor: ${accent_dim};
qproperty-hoverColor: ${accent}; qproperty-hoverColor: ${accent};
qproperty-idleColor: ${border_strong}; qproperty-idleColor: ${border_strong};
} }
/* ---------- Info panel tag category colors ---------- */ /* ---------- Info panel tag colors ---------- */
InfoPanel { InfoPanel {
qproperty-tagArtistColor: ${warning}; qproperty-tagArtistColor: ${warning};
@ -540,19 +433,13 @@ InfoPanel {
qproperty-tagLoreColor: ${text_dim}; qproperty-tagLoreColor: ${text_dim};
} }
/* ---------- Video player letterbox / pillarbox color (mpv background) ---------- */ /* ---------- Video player letterbox ---------- */
VideoPlayer { VideoPlayer {
qproperty-letterboxColor: ${bg}; qproperty-letterboxColor: ${bg};
} }
/* ---------- Popout overlay bars (slideshow toolbar + slideshow controls + embedded preview controls) ---------- */ /* ---------- Popout overlay bars ---------- */
/*
* The popout window's translucent toolbar (top) and transport controls
* (bottom) float over the video content. The bg color comes from the
* @palette overlay_bg slot. Children get the classic overlay treatment:
* transparent backgrounds, near-white text, hairline borders.
*/
QWidget#_slideshow_toolbar, QWidget#_slideshow_toolbar,
QWidget#_slideshow_controls, QWidget#_slideshow_controls,
@ -575,6 +462,8 @@ QWidget#_preview_controls QPushButton {
color: white; color: white;
border: 1px solid rgba(255, 255, 255, 80); border: 1px solid rgba(255, 255, 255, 80);
padding: 2px 6px; padding: 2px 6px;
font-size: 15px;
font-weight: bold;
} }
QWidget#_slideshow_toolbar QPushButton:hover, QWidget#_slideshow_toolbar QPushButton:hover,
QWidget#_slideshow_controls QPushButton:hover, QWidget#_slideshow_controls QPushButton:hover,

View File

@ -28,7 +28,6 @@
warning: #b58900 warning: #b58900
overlay_bg: rgba(0, 43, 54, 200) overlay_bg: rgba(0, 43, 54, 200)
*/ */
/* ---------- Base ---------- */ /* ---------- Base ---------- */
QWidget { QWidget {
@ -43,8 +42,6 @@ QWidget:disabled {
color: ${text_disabled}; color: ${text_disabled};
} }
/* Labels should never paint an opaque background they sit on top of
* other widgets in many places (toolbars, info panels, overlays). */
QLabel { QLabel {
background: transparent; background: transparent;
} }
@ -92,49 +89,26 @@ QPushButton:flat:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QToolButton {
background-color: transparent;
color: ${text};
border: 1px solid transparent;
border-radius: 4px;
padding: 4px;
}
QToolButton:hover {
background-color: ${bg_hover};
border-color: ${border_strong};
}
QToolButton:pressed, QToolButton:checked {
background-color: ${bg_active};
}
/* ---------- Inputs ---------- */ /* ---------- Inputs ---------- */
QLineEdit, QSpinBox, QDoubleSpinBox, QTextEdit, QPlainTextEdit { QLineEdit, QSpinBox, QTextEdit {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 6px; padding: 2px 6px;
/* min-height ensures the painted text fits inside the widget bounds
* even when a parent layout (e.g. QFormLayout inside a QGroupBox)
* compresses the natural sizeHint. Without this, spinboxes in dense
* forms render with the top of the value text clipped. */
min-height: 16px; min-height: 16px;
selection-background-color: ${accent}; selection-background-color: ${accent};
selection-color: ${accent_text}; selection-color: ${accent_text};
} }
QLineEdit:focus, QLineEdit:focus,
QSpinBox:focus, QSpinBox:focus,
QDoubleSpinBox:focus, QTextEdit:focus {
QTextEdit:focus,
QPlainTextEdit:focus {
border-color: ${accent}; border-color: ${accent};
} }
QLineEdit:disabled, QLineEdit:disabled,
QSpinBox:disabled, QSpinBox:disabled,
QDoubleSpinBox:disabled, QTextEdit:disabled {
QTextEdit:disabled,
QPlainTextEdit:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
color: ${text_disabled}; color: ${text_disabled};
border-color: ${border}; border-color: ${border};
@ -315,19 +289,6 @@ QSlider::handle:horizontal:hover {
background: ${accent_dim}; background: ${accent_dim};
} }
QSlider::groove:vertical {
background: ${bg_subtle};
width: 4px;
border-radius: 2px;
}
QSlider::handle:vertical {
background: ${accent};
width: 12px;
height: 12px;
margin: 0 -5px;
border-radius: 6px;
}
/* ---------- Progress ---------- */ /* ---------- Progress ---------- */
QProgressBar { QProgressBar {
@ -343,33 +304,28 @@ QProgressBar::chunk {
border-radius: 3px; border-radius: 3px;
} }
/* ---------- Checkboxes & radio buttons ---------- */ /* ---------- Checkboxes ---------- */
QCheckBox, QRadioButton { QCheckBox {
background: transparent; background: transparent;
color: ${text}; color: ${text};
spacing: 6px; spacing: 6px;
} }
QCheckBox::indicator, QRadioButton::indicator { QCheckBox::indicator {
width: 14px; width: 14px;
height: 14px; height: 14px;
background-color: ${bg_subtle}; background-color: ${bg_subtle};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
}
QCheckBox::indicator {
border-radius: 3px; border-radius: 3px;
} }
QRadioButton::indicator { QCheckBox::indicator:hover {
border-radius: 7px;
}
QCheckBox::indicator:hover, QRadioButton::indicator:hover {
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:checked, QRadioButton::indicator:checked { QCheckBox::indicator:checked {
background-color: ${accent}; background-color: ${accent};
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:disabled, QRadioButton::indicator:disabled { QCheckBox::indicator:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
border-color: ${border}; border-color: ${border};
} }
@ -384,9 +340,9 @@ QToolTip {
border-radius: 3px; border-radius: 3px;
} }
/* ---------- Item views (lists, trees, tables) ---------- */ /* ---------- Lists ---------- */
QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget { QListView, QListWidget {
background-color: ${bg}; background-color: ${bg};
alternate-background-color: ${bg_alt}; alternate-background-color: ${bg_alt};
color: ${text}; color: ${text};
@ -395,35 +351,18 @@ QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget {
selection-color: ${accent_text}; selection-color: ${accent_text};
outline: none; outline: none;
} }
QListView::item, QListWidget::item, QListView::item, QListWidget::item {
QTreeView::item, QTreeWidget::item,
QTableView::item, QTableWidget::item {
padding: 4px; padding: 4px;
} }
QListView::item:hover, QListWidget::item:hover, QListView::item:hover, QListWidget::item:hover {
QTreeView::item:hover, QTreeWidget::item:hover,
QTableView::item:hover, QTableWidget::item:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QListView::item:selected, QListWidget::item:selected, QListView::item:selected, QListWidget::item:selected {
QTreeView::item:selected, QTreeWidget::item:selected,
QTableView::item:selected, QTableWidget::item:selected {
background-color: ${accent}; background-color: ${accent};
color: ${accent_text}; color: ${accent_text};
} }
QHeaderView::section { /* ---------- Tabs (settings dialog) ---------- */
background-color: ${bg_subtle};
color: ${text};
border: none;
border-right: 1px solid ${border};
padding: 4px 8px;
}
QHeaderView::section:hover {
background-color: ${bg_hover};
}
/* ---------- Tabs ---------- */
QTabWidget::pane { QTabWidget::pane {
border: 1px solid ${border}; border: 1px solid ${border};
@ -448,7 +387,7 @@ QTabBar::tab:hover:!selected {
color: ${text}; color: ${text};
} }
/* ---------- Group boxes ---------- */ /* ---------- Group boxes (settings dialog) ---------- */
QGroupBox { QGroupBox {
background: transparent; background: transparent;
@ -465,63 +404,14 @@ QGroupBox::title {
color: ${text_dim}; color: ${text_dim};
} }
/* ---------- Frames ---------- */ /* ---------- Rubber band (multi-select drag) ---------- */
QFrame[frameShape="4"], /* HLine */
QFrame[frameShape="5"] /* VLine */ {
background: ${border};
color: ${border};
}
/* ---------- Toolbars ---------- */
QToolBar {
background: ${bg};
border: none;
spacing: 4px;
padding: 2px;
}
QToolBar::separator {
background: ${border};
width: 1px;
margin: 4px 4px;
}
/* ---------- Dock widgets ---------- */
QDockWidget {
color: ${text};
titlebar-close-icon: none;
}
QDockWidget::title {
background: ${bg_subtle};
padding: 4px;
border: 1px solid ${border};
}
/* ---------- Rubber band (multi-select drag rectangle) ---------- */
QRubberBand { QRubberBand {
background: ${accent}; background: ${accent};
border: 1px solid ${accent}; border: 1px solid ${accent};
/* Qt blends rubber band at ~30% so this reads as a translucent
* accent-tinted rectangle without needing rgba(). */
} }
/* ---------- Library count label states ---------- */ /* ---------- Library count label states ---------- */
/*
* The library tab's count label switches between three visual states
* depending on what refresh() found. The state is exposed as a Qt
* dynamic property `libraryCountState` so users can override these
* rules in their custom.qss without touching the Python.
*
* normal N files default text color, no rule needed
* empty no items dim text (no items found, search miss)
* error bad/unreachable danger color + bold (real error)
*/
QLabel[libraryCountState="empty"] { QLabel[libraryCountState="empty"] {
color: ${text_dim}; color: ${text_dim};
@ -531,18 +421,18 @@ QLabel[libraryCountState="error"] {
font-weight: bold; font-weight: bold;
} }
/* ---------- Thumbnail dot indicators (Qt properties on ThumbnailWidget) ---------- */ /* ---------- Thumbnail indicators ---------- */
ThumbnailWidget { ThumbnailWidget {
qproperty-savedColor: #22cc22; /* green dot: saved to library — universal "confirmed" feel */ qproperty-savedColor: #22cc22;
qproperty-bookmarkedColor: #ffcc00; /* yellow star: bookmarked */ qproperty-bookmarkedColor: #ffcc00;
qproperty-selectionColor: ${accent}; qproperty-selectionColor: ${accent};
qproperty-multiSelectColor: ${accent_dim}; qproperty-multiSelectColor: ${accent_dim};
qproperty-hoverColor: ${accent}; qproperty-hoverColor: ${accent};
qproperty-idleColor: ${border_strong}; qproperty-idleColor: ${border_strong};
} }
/* ---------- Info panel tag category colors ---------- */ /* ---------- Info panel tag colors ---------- */
InfoPanel { InfoPanel {
qproperty-tagArtistColor: ${warning}; qproperty-tagArtistColor: ${warning};
@ -553,19 +443,13 @@ InfoPanel {
qproperty-tagLoreColor: ${text_dim}; qproperty-tagLoreColor: ${text_dim};
} }
/* ---------- Video player letterbox / pillarbox color (mpv background) ---------- */ /* ---------- Video player letterbox ---------- */
VideoPlayer { VideoPlayer {
qproperty-letterboxColor: ${bg}; qproperty-letterboxColor: ${bg};
} }
/* ---------- Popout overlay bars (slideshow toolbar + slideshow controls + embedded preview controls) ---------- */ /* ---------- Popout overlay bars ---------- */
/*
* The popout window's translucent toolbar (top) and transport controls
* (bottom) float over the video content. The bg color comes from the
* @palette overlay_bg slot. Children get the classic overlay treatment:
* transparent backgrounds, near-white text, hairline borders.
*/
QWidget#_slideshow_toolbar, QWidget#_slideshow_toolbar,
QWidget#_slideshow_controls, QWidget#_slideshow_controls,
@ -588,6 +472,8 @@ QWidget#_preview_controls QPushButton {
color: white; color: white;
border: 1px solid rgba(255, 255, 255, 80); border: 1px solid rgba(255, 255, 255, 80);
padding: 2px 6px; padding: 2px 6px;
font-size: 15px;
font-weight: bold;
} }
QWidget#_slideshow_toolbar QPushButton:hover, QWidget#_slideshow_toolbar QPushButton:hover,
QWidget#_slideshow_controls QPushButton:hover, QWidget#_slideshow_controls QPushButton:hover,

View File

@ -28,7 +28,6 @@
warning: #b58900 warning: #b58900
overlay_bg: rgba(0, 43, 54, 200) overlay_bg: rgba(0, 43, 54, 200)
*/ */
/* ---------- Base ---------- */ /* ---------- Base ---------- */
QWidget { QWidget {
@ -43,8 +42,6 @@ QWidget:disabled {
color: ${text_disabled}; color: ${text_disabled};
} }
/* Labels should never paint an opaque background they sit on top of
* other widgets in many places (toolbars, info panels, overlays). */
QLabel { QLabel {
background: transparent; background: transparent;
} }
@ -91,47 +88,25 @@ QPushButton:flat:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QToolButton {
background-color: transparent;
color: ${text};
border: 1px solid transparent;
padding: 4px;
}
QToolButton:hover {
background-color: ${bg_hover};
border-color: ${border_strong};
}
QToolButton:pressed, QToolButton:checked {
background-color: ${bg_active};
}
/* ---------- Inputs ---------- */ /* ---------- Inputs ---------- */
QLineEdit, QSpinBox, QDoubleSpinBox, QTextEdit, QPlainTextEdit { QLineEdit, QSpinBox, QTextEdit {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 6px; padding: 2px 6px;
/* min-height ensures the painted text fits inside the widget bounds
* even when a parent layout (e.g. QFormLayout inside a QGroupBox)
* compresses the natural sizeHint. Without this, spinboxes in dense
* forms render with the top of the value text clipped. */
min-height: 16px; min-height: 16px;
selection-background-color: ${accent}; selection-background-color: ${accent};
selection-color: ${accent_text}; selection-color: ${accent_text};
} }
QLineEdit:focus, QLineEdit:focus,
QSpinBox:focus, QSpinBox:focus,
QDoubleSpinBox:focus, QTextEdit:focus {
QTextEdit:focus,
QPlainTextEdit:focus {
border-color: ${accent}; border-color: ${accent};
} }
QLineEdit:disabled, QLineEdit:disabled,
QSpinBox:disabled, QSpinBox:disabled,
QDoubleSpinBox:disabled, QTextEdit:disabled {
QTextEdit:disabled,
QPlainTextEdit:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
color: ${text_disabled}; color: ${text_disabled};
border-color: ${border}; border-color: ${border};
@ -309,17 +284,6 @@ QSlider::handle:horizontal:hover {
background: ${accent_dim}; background: ${accent_dim};
} }
QSlider::groove:vertical {
background: ${bg_subtle};
width: 4px;
}
QSlider::handle:vertical {
background: ${accent};
width: 12px;
height: 12px;
margin: 0 -5px;
}
/* ---------- Progress ---------- */ /* ---------- Progress ---------- */
QProgressBar { QProgressBar {
@ -333,32 +297,27 @@ QProgressBar::chunk {
background-color: ${accent}; background-color: ${accent};
} }
/* ---------- Checkboxes & radio buttons ---------- */ /* ---------- Checkboxes ---------- */
QCheckBox, QRadioButton { QCheckBox {
background: transparent; background: transparent;
color: ${text}; color: ${text};
spacing: 6px; spacing: 6px;
} }
QCheckBox::indicator, QRadioButton::indicator { QCheckBox::indicator {
width: 14px; width: 14px;
height: 14px; height: 14px;
background-color: ${bg_subtle}; background-color: ${bg_subtle};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
} }
QCheckBox::indicator { QCheckBox::indicator:hover {
}
QRadioButton::indicator {
border-radius: 7px;
}
QCheckBox::indicator:hover, QRadioButton::indicator:hover {
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:checked, QRadioButton::indicator:checked { QCheckBox::indicator:checked {
background-color: ${accent}; background-color: ${accent};
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:disabled, QRadioButton::indicator:disabled { QCheckBox::indicator:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
border-color: ${border}; border-color: ${border};
} }
@ -372,9 +331,9 @@ QToolTip {
padding: 4px 6px; padding: 4px 6px;
} }
/* ---------- Item views (lists, trees, tables) ---------- */ /* ---------- Lists ---------- */
QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget { QListView, QListWidget {
background-color: ${bg}; background-color: ${bg};
alternate-background-color: ${bg_alt}; alternate-background-color: ${bg_alt};
color: ${text}; color: ${text};
@ -383,35 +342,18 @@ QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget {
selection-color: ${accent_text}; selection-color: ${accent_text};
outline: none; outline: none;
} }
QListView::item, QListWidget::item, QListView::item, QListWidget::item {
QTreeView::item, QTreeWidget::item,
QTableView::item, QTableWidget::item {
padding: 4px; padding: 4px;
} }
QListView::item:hover, QListWidget::item:hover, QListView::item:hover, QListWidget::item:hover {
QTreeView::item:hover, QTreeWidget::item:hover,
QTableView::item:hover, QTableWidget::item:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QListView::item:selected, QListWidget::item:selected, QListView::item:selected, QListWidget::item:selected {
QTreeView::item:selected, QTreeWidget::item:selected,
QTableView::item:selected, QTableWidget::item:selected {
background-color: ${accent}; background-color: ${accent};
color: ${accent_text}; color: ${accent_text};
} }
QHeaderView::section { /* ---------- Tabs (settings dialog) ---------- */
background-color: ${bg_subtle};
color: ${text};
border: none;
border-right: 1px solid ${border};
padding: 4px 8px;
}
QHeaderView::section:hover {
background-color: ${bg_hover};
}
/* ---------- Tabs ---------- */
QTabWidget::pane { QTabWidget::pane {
border: 1px solid ${border}; border: 1px solid ${border};
@ -436,7 +378,7 @@ QTabBar::tab:hover:!selected {
color: ${text}; color: ${text};
} }
/* ---------- Group boxes ---------- */ /* ---------- Group boxes (settings dialog) ---------- */
QGroupBox { QGroupBox {
background: transparent; background: transparent;
@ -452,63 +394,14 @@ QGroupBox::title {
color: ${text_dim}; color: ${text_dim};
} }
/* ---------- Frames ---------- */ /* ---------- Rubber band (multi-select drag) ---------- */
QFrame[frameShape="4"], /* HLine */
QFrame[frameShape="5"] /* VLine */ {
background: ${border};
color: ${border};
}
/* ---------- Toolbars ---------- */
QToolBar {
background: ${bg};
border: none;
spacing: 4px;
padding: 2px;
}
QToolBar::separator {
background: ${border};
width: 1px;
margin: 4px 4px;
}
/* ---------- Dock widgets ---------- */
QDockWidget {
color: ${text};
titlebar-close-icon: none;
}
QDockWidget::title {
background: ${bg_subtle};
padding: 4px;
border: 1px solid ${border};
}
/* ---------- Rubber band (multi-select drag rectangle) ---------- */
QRubberBand { QRubberBand {
background: ${accent}; background: ${accent};
border: 1px solid ${accent}; border: 1px solid ${accent};
/* Qt blends rubber band at ~30% so this reads as a translucent
* accent-tinted rectangle without needing rgba(). */
} }
/* ---------- Library count label states ---------- */ /* ---------- Library count label states ---------- */
/*
* The library tab's count label switches between three visual states
* depending on what refresh() found. The state is exposed as a Qt
* dynamic property `libraryCountState` so users can override these
* rules in their custom.qss without touching the Python.
*
* normal N files default text color, no rule needed
* empty no items dim text (no items found, search miss)
* error bad/unreachable danger color + bold (real error)
*/
QLabel[libraryCountState="empty"] { QLabel[libraryCountState="empty"] {
color: ${text_dim}; color: ${text_dim};
@ -518,18 +411,18 @@ QLabel[libraryCountState="error"] {
font-weight: bold; font-weight: bold;
} }
/* ---------- Thumbnail dot indicators (Qt properties on ThumbnailWidget) ---------- */ /* ---------- Thumbnail indicators ---------- */
ThumbnailWidget { ThumbnailWidget {
qproperty-savedColor: #22cc22; /* green dot: saved to library — universal "confirmed" feel */ qproperty-savedColor: #22cc22;
qproperty-bookmarkedColor: #ffcc00; /* yellow star: bookmarked */ qproperty-bookmarkedColor: #ffcc00;
qproperty-selectionColor: ${accent}; qproperty-selectionColor: ${accent};
qproperty-multiSelectColor: ${accent_dim}; qproperty-multiSelectColor: ${accent_dim};
qproperty-hoverColor: ${accent}; qproperty-hoverColor: ${accent};
qproperty-idleColor: ${border_strong}; qproperty-idleColor: ${border_strong};
} }
/* ---------- Info panel tag category colors ---------- */ /* ---------- Info panel tag colors ---------- */
InfoPanel { InfoPanel {
qproperty-tagArtistColor: ${warning}; qproperty-tagArtistColor: ${warning};
@ -540,19 +433,13 @@ InfoPanel {
qproperty-tagLoreColor: ${text_dim}; qproperty-tagLoreColor: ${text_dim};
} }
/* ---------- Video player letterbox / pillarbox color (mpv background) ---------- */ /* ---------- Video player letterbox ---------- */
VideoPlayer { VideoPlayer {
qproperty-letterboxColor: ${bg}; qproperty-letterboxColor: ${bg};
} }
/* ---------- Popout overlay bars (slideshow toolbar + slideshow controls + embedded preview controls) ---------- */ /* ---------- Popout overlay bars ---------- */
/*
* The popout window's translucent toolbar (top) and transport controls
* (bottom) float over the video content. The bg color comes from the
* @palette overlay_bg slot. Children get the classic overlay treatment:
* transparent backgrounds, near-white text, hairline borders.
*/
QWidget#_slideshow_toolbar, QWidget#_slideshow_toolbar,
QWidget#_slideshow_controls, QWidget#_slideshow_controls,
@ -575,6 +462,8 @@ QWidget#_preview_controls QPushButton {
color: white; color: white;
border: 1px solid rgba(255, 255, 255, 80); border: 1px solid rgba(255, 255, 255, 80);
padding: 2px 6px; padding: 2px 6px;
font-size: 15px;
font-weight: bold;
} }
QWidget#_slideshow_toolbar QPushButton:hover, QWidget#_slideshow_toolbar QPushButton:hover,
QWidget#_slideshow_controls QPushButton:hover, QWidget#_slideshow_controls QPushButton:hover,

View File

@ -28,7 +28,6 @@
warning: #e0af68 warning: #e0af68
overlay_bg: rgba(26, 27, 38, 200) overlay_bg: rgba(26, 27, 38, 200)
*/ */
/* ---------- Base ---------- */ /* ---------- Base ---------- */
QWidget { QWidget {
@ -43,8 +42,6 @@ QWidget:disabled {
color: ${text_disabled}; color: ${text_disabled};
} }
/* Labels should never paint an opaque background they sit on top of
* other widgets in many places (toolbars, info panels, overlays). */
QLabel { QLabel {
background: transparent; background: transparent;
} }
@ -92,49 +89,26 @@ QPushButton:flat:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QToolButton {
background-color: transparent;
color: ${text};
border: 1px solid transparent;
border-radius: 4px;
padding: 4px;
}
QToolButton:hover {
background-color: ${bg_hover};
border-color: ${border_strong};
}
QToolButton:pressed, QToolButton:checked {
background-color: ${bg_active};
}
/* ---------- Inputs ---------- */ /* ---------- Inputs ---------- */
QLineEdit, QSpinBox, QDoubleSpinBox, QTextEdit, QPlainTextEdit { QLineEdit, QSpinBox, QTextEdit {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
border-radius: 4px; border-radius: 4px;
padding: 2px 6px; padding: 2px 6px;
/* min-height ensures the painted text fits inside the widget bounds
* even when a parent layout (e.g. QFormLayout inside a QGroupBox)
* compresses the natural sizeHint. Without this, spinboxes in dense
* forms render with the top of the value text clipped. */
min-height: 16px; min-height: 16px;
selection-background-color: ${accent}; selection-background-color: ${accent};
selection-color: ${accent_text}; selection-color: ${accent_text};
} }
QLineEdit:focus, QLineEdit:focus,
QSpinBox:focus, QSpinBox:focus,
QDoubleSpinBox:focus, QTextEdit:focus {
QTextEdit:focus,
QPlainTextEdit:focus {
border-color: ${accent}; border-color: ${accent};
} }
QLineEdit:disabled, QLineEdit:disabled,
QSpinBox:disabled, QSpinBox:disabled,
QDoubleSpinBox:disabled, QTextEdit:disabled {
QTextEdit:disabled,
QPlainTextEdit:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
color: ${text_disabled}; color: ${text_disabled};
border-color: ${border}; border-color: ${border};
@ -315,19 +289,6 @@ QSlider::handle:horizontal:hover {
background: ${accent_dim}; background: ${accent_dim};
} }
QSlider::groove:vertical {
background: ${bg_subtle};
width: 4px;
border-radius: 2px;
}
QSlider::handle:vertical {
background: ${accent};
width: 12px;
height: 12px;
margin: 0 -5px;
border-radius: 6px;
}
/* ---------- Progress ---------- */ /* ---------- Progress ---------- */
QProgressBar { QProgressBar {
@ -343,33 +304,28 @@ QProgressBar::chunk {
border-radius: 3px; border-radius: 3px;
} }
/* ---------- Checkboxes & radio buttons ---------- */ /* ---------- Checkboxes ---------- */
QCheckBox, QRadioButton { QCheckBox {
background: transparent; background: transparent;
color: ${text}; color: ${text};
spacing: 6px; spacing: 6px;
} }
QCheckBox::indicator, QRadioButton::indicator { QCheckBox::indicator {
width: 14px; width: 14px;
height: 14px; height: 14px;
background-color: ${bg_subtle}; background-color: ${bg_subtle};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
}
QCheckBox::indicator {
border-radius: 3px; border-radius: 3px;
} }
QRadioButton::indicator { QCheckBox::indicator:hover {
border-radius: 7px;
}
QCheckBox::indicator:hover, QRadioButton::indicator:hover {
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:checked, QRadioButton::indicator:checked { QCheckBox::indicator:checked {
background-color: ${accent}; background-color: ${accent};
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:disabled, QRadioButton::indicator:disabled { QCheckBox::indicator:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
border-color: ${border}; border-color: ${border};
} }
@ -384,9 +340,9 @@ QToolTip {
border-radius: 3px; border-radius: 3px;
} }
/* ---------- Item views (lists, trees, tables) ---------- */ /* ---------- Lists ---------- */
QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget { QListView, QListWidget {
background-color: ${bg}; background-color: ${bg};
alternate-background-color: ${bg_alt}; alternate-background-color: ${bg_alt};
color: ${text}; color: ${text};
@ -395,35 +351,18 @@ QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget {
selection-color: ${accent_text}; selection-color: ${accent_text};
outline: none; outline: none;
} }
QListView::item, QListWidget::item, QListView::item, QListWidget::item {
QTreeView::item, QTreeWidget::item,
QTableView::item, QTableWidget::item {
padding: 4px; padding: 4px;
} }
QListView::item:hover, QListWidget::item:hover, QListView::item:hover, QListWidget::item:hover {
QTreeView::item:hover, QTreeWidget::item:hover,
QTableView::item:hover, QTableWidget::item:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QListView::item:selected, QListWidget::item:selected, QListView::item:selected, QListWidget::item:selected {
QTreeView::item:selected, QTreeWidget::item:selected,
QTableView::item:selected, QTableWidget::item:selected {
background-color: ${accent}; background-color: ${accent};
color: ${accent_text}; color: ${accent_text};
} }
QHeaderView::section { /* ---------- Tabs (settings dialog) ---------- */
background-color: ${bg_subtle};
color: ${text};
border: none;
border-right: 1px solid ${border};
padding: 4px 8px;
}
QHeaderView::section:hover {
background-color: ${bg_hover};
}
/* ---------- Tabs ---------- */
QTabWidget::pane { QTabWidget::pane {
border: 1px solid ${border}; border: 1px solid ${border};
@ -448,7 +387,7 @@ QTabBar::tab:hover:!selected {
color: ${text}; color: ${text};
} }
/* ---------- Group boxes ---------- */ /* ---------- Group boxes (settings dialog) ---------- */
QGroupBox { QGroupBox {
background: transparent; background: transparent;
@ -465,63 +404,14 @@ QGroupBox::title {
color: ${text_dim}; color: ${text_dim};
} }
/* ---------- Frames ---------- */ /* ---------- Rubber band (multi-select drag) ---------- */
QFrame[frameShape="4"], /* HLine */
QFrame[frameShape="5"] /* VLine */ {
background: ${border};
color: ${border};
}
/* ---------- Toolbars ---------- */
QToolBar {
background: ${bg};
border: none;
spacing: 4px;
padding: 2px;
}
QToolBar::separator {
background: ${border};
width: 1px;
margin: 4px 4px;
}
/* ---------- Dock widgets ---------- */
QDockWidget {
color: ${text};
titlebar-close-icon: none;
}
QDockWidget::title {
background: ${bg_subtle};
padding: 4px;
border: 1px solid ${border};
}
/* ---------- Rubber band (multi-select drag rectangle) ---------- */
QRubberBand { QRubberBand {
background: ${accent}; background: ${accent};
border: 1px solid ${accent}; border: 1px solid ${accent};
/* Qt blends rubber band at ~30% so this reads as a translucent
* accent-tinted rectangle without needing rgba(). */
} }
/* ---------- Library count label states ---------- */ /* ---------- Library count label states ---------- */
/*
* The library tab's count label switches between three visual states
* depending on what refresh() found. The state is exposed as a Qt
* dynamic property `libraryCountState` so users can override these
* rules in their custom.qss without touching the Python.
*
* normal N files default text color, no rule needed
* empty no items dim text (no items found, search miss)
* error bad/unreachable danger color + bold (real error)
*/
QLabel[libraryCountState="empty"] { QLabel[libraryCountState="empty"] {
color: ${text_dim}; color: ${text_dim};
@ -531,18 +421,18 @@ QLabel[libraryCountState="error"] {
font-weight: bold; font-weight: bold;
} }
/* ---------- Thumbnail dot indicators (Qt properties on ThumbnailWidget) ---------- */ /* ---------- Thumbnail indicators ---------- */
ThumbnailWidget { ThumbnailWidget {
qproperty-savedColor: #22cc22; /* green dot: saved to library — universal "confirmed" feel */ qproperty-savedColor: #22cc22;
qproperty-bookmarkedColor: #ffcc00; /* yellow star: bookmarked */ qproperty-bookmarkedColor: #ffcc00;
qproperty-selectionColor: ${accent}; qproperty-selectionColor: ${accent};
qproperty-multiSelectColor: ${accent_dim}; qproperty-multiSelectColor: ${accent_dim};
qproperty-hoverColor: ${accent}; qproperty-hoverColor: ${accent};
qproperty-idleColor: ${border_strong}; qproperty-idleColor: ${border_strong};
} }
/* ---------- Info panel tag category colors ---------- */ /* ---------- Info panel tag colors ---------- */
InfoPanel { InfoPanel {
qproperty-tagArtistColor: ${warning}; qproperty-tagArtistColor: ${warning};
@ -553,19 +443,13 @@ InfoPanel {
qproperty-tagLoreColor: ${text_dim}; qproperty-tagLoreColor: ${text_dim};
} }
/* ---------- Video player letterbox / pillarbox color (mpv background) ---------- */ /* ---------- Video player letterbox ---------- */
VideoPlayer { VideoPlayer {
qproperty-letterboxColor: ${bg}; qproperty-letterboxColor: ${bg};
} }
/* ---------- Popout overlay bars (slideshow toolbar + slideshow controls + embedded preview controls) ---------- */ /* ---------- Popout overlay bars ---------- */
/*
* The popout window's translucent toolbar (top) and transport controls
* (bottom) float over the video content. The bg color comes from the
* @palette overlay_bg slot. Children get the classic overlay treatment:
* transparent backgrounds, near-white text, hairline borders.
*/
QWidget#_slideshow_toolbar, QWidget#_slideshow_toolbar,
QWidget#_slideshow_controls, QWidget#_slideshow_controls,
@ -588,6 +472,8 @@ QWidget#_preview_controls QPushButton {
color: white; color: white;
border: 1px solid rgba(255, 255, 255, 80); border: 1px solid rgba(255, 255, 255, 80);
padding: 2px 6px; padding: 2px 6px;
font-size: 15px;
font-weight: bold;
} }
QWidget#_slideshow_toolbar QPushButton:hover, QWidget#_slideshow_toolbar QPushButton:hover,
QWidget#_slideshow_controls QPushButton:hover, QWidget#_slideshow_controls QPushButton:hover,

View File

@ -28,7 +28,6 @@
warning: #e0af68 warning: #e0af68
overlay_bg: rgba(26, 27, 38, 200) overlay_bg: rgba(26, 27, 38, 200)
*/ */
/* ---------- Base ---------- */ /* ---------- Base ---------- */
QWidget { QWidget {
@ -43,8 +42,6 @@ QWidget:disabled {
color: ${text_disabled}; color: ${text_disabled};
} }
/* Labels should never paint an opaque background they sit on top of
* other widgets in many places (toolbars, info panels, overlays). */
QLabel { QLabel {
background: transparent; background: transparent;
} }
@ -91,47 +88,25 @@ QPushButton:flat:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QToolButton {
background-color: transparent;
color: ${text};
border: 1px solid transparent;
padding: 4px;
}
QToolButton:hover {
background-color: ${bg_hover};
border-color: ${border_strong};
}
QToolButton:pressed, QToolButton:checked {
background-color: ${bg_active};
}
/* ---------- Inputs ---------- */ /* ---------- Inputs ---------- */
QLineEdit, QSpinBox, QDoubleSpinBox, QTextEdit, QPlainTextEdit { QLineEdit, QSpinBox, QTextEdit {
background-color: ${bg_subtle}; background-color: ${bg_subtle};
color: ${text}; color: ${text};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
padding: 2px 6px; padding: 2px 6px;
/* min-height ensures the painted text fits inside the widget bounds
* even when a parent layout (e.g. QFormLayout inside a QGroupBox)
* compresses the natural sizeHint. Without this, spinboxes in dense
* forms render with the top of the value text clipped. */
min-height: 16px; min-height: 16px;
selection-background-color: ${accent}; selection-background-color: ${accent};
selection-color: ${accent_text}; selection-color: ${accent_text};
} }
QLineEdit:focus, QLineEdit:focus,
QSpinBox:focus, QSpinBox:focus,
QDoubleSpinBox:focus, QTextEdit:focus {
QTextEdit:focus,
QPlainTextEdit:focus {
border-color: ${accent}; border-color: ${accent};
} }
QLineEdit:disabled, QLineEdit:disabled,
QSpinBox:disabled, QSpinBox:disabled,
QDoubleSpinBox:disabled, QTextEdit:disabled {
QTextEdit:disabled,
QPlainTextEdit:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
color: ${text_disabled}; color: ${text_disabled};
border-color: ${border}; border-color: ${border};
@ -309,17 +284,6 @@ QSlider::handle:horizontal:hover {
background: ${accent_dim}; background: ${accent_dim};
} }
QSlider::groove:vertical {
background: ${bg_subtle};
width: 4px;
}
QSlider::handle:vertical {
background: ${accent};
width: 12px;
height: 12px;
margin: 0 -5px;
}
/* ---------- Progress ---------- */ /* ---------- Progress ---------- */
QProgressBar { QProgressBar {
@ -333,32 +297,27 @@ QProgressBar::chunk {
background-color: ${accent}; background-color: ${accent};
} }
/* ---------- Checkboxes & radio buttons ---------- */ /* ---------- Checkboxes ---------- */
QCheckBox, QRadioButton { QCheckBox {
background: transparent; background: transparent;
color: ${text}; color: ${text};
spacing: 6px; spacing: 6px;
} }
QCheckBox::indicator, QRadioButton::indicator { QCheckBox::indicator {
width: 14px; width: 14px;
height: 14px; height: 14px;
background-color: ${bg_subtle}; background-color: ${bg_subtle};
border: 1px solid ${border_strong}; border: 1px solid ${border_strong};
} }
QCheckBox::indicator { QCheckBox::indicator:hover {
}
QRadioButton::indicator {
border-radius: 7px;
}
QCheckBox::indicator:hover, QRadioButton::indicator:hover {
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:checked, QRadioButton::indicator:checked { QCheckBox::indicator:checked {
background-color: ${accent}; background-color: ${accent};
border-color: ${accent}; border-color: ${accent};
} }
QCheckBox::indicator:disabled, QRadioButton::indicator:disabled { QCheckBox::indicator:disabled {
background-color: ${bg_alt}; background-color: ${bg_alt};
border-color: ${border}; border-color: ${border};
} }
@ -372,9 +331,9 @@ QToolTip {
padding: 4px 6px; padding: 4px 6px;
} }
/* ---------- Item views (lists, trees, tables) ---------- */ /* ---------- Lists ---------- */
QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget { QListView, QListWidget {
background-color: ${bg}; background-color: ${bg};
alternate-background-color: ${bg_alt}; alternate-background-color: ${bg_alt};
color: ${text}; color: ${text};
@ -383,35 +342,18 @@ QListView, QListWidget, QTreeView, QTreeWidget, QTableView, QTableWidget {
selection-color: ${accent_text}; selection-color: ${accent_text};
outline: none; outline: none;
} }
QListView::item, QListWidget::item, QListView::item, QListWidget::item {
QTreeView::item, QTreeWidget::item,
QTableView::item, QTableWidget::item {
padding: 4px; padding: 4px;
} }
QListView::item:hover, QListWidget::item:hover, QListView::item:hover, QListWidget::item:hover {
QTreeView::item:hover, QTreeWidget::item:hover,
QTableView::item:hover, QTableWidget::item:hover {
background-color: ${bg_hover}; background-color: ${bg_hover};
} }
QListView::item:selected, QListWidget::item:selected, QListView::item:selected, QListWidget::item:selected {
QTreeView::item:selected, QTreeWidget::item:selected,
QTableView::item:selected, QTableWidget::item:selected {
background-color: ${accent}; background-color: ${accent};
color: ${accent_text}; color: ${accent_text};
} }
QHeaderView::section { /* ---------- Tabs (settings dialog) ---------- */
background-color: ${bg_subtle};
color: ${text};
border: none;
border-right: 1px solid ${border};
padding: 4px 8px;
}
QHeaderView::section:hover {
background-color: ${bg_hover};
}
/* ---------- Tabs ---------- */
QTabWidget::pane { QTabWidget::pane {
border: 1px solid ${border}; border: 1px solid ${border};
@ -436,7 +378,7 @@ QTabBar::tab:hover:!selected {
color: ${text}; color: ${text};
} }
/* ---------- Group boxes ---------- */ /* ---------- Group boxes (settings dialog) ---------- */
QGroupBox { QGroupBox {
background: transparent; background: transparent;
@ -452,63 +394,14 @@ QGroupBox::title {
color: ${text_dim}; color: ${text_dim};
} }
/* ---------- Frames ---------- */ /* ---------- Rubber band (multi-select drag) ---------- */
QFrame[frameShape="4"], /* HLine */
QFrame[frameShape="5"] /* VLine */ {
background: ${border};
color: ${border};
}
/* ---------- Toolbars ---------- */
QToolBar {
background: ${bg};
border: none;
spacing: 4px;
padding: 2px;
}
QToolBar::separator {
background: ${border};
width: 1px;
margin: 4px 4px;
}
/* ---------- Dock widgets ---------- */
QDockWidget {
color: ${text};
titlebar-close-icon: none;
}
QDockWidget::title {
background: ${bg_subtle};
padding: 4px;
border: 1px solid ${border};
}
/* ---------- Rubber band (multi-select drag rectangle) ---------- */
QRubberBand { QRubberBand {
background: ${accent}; background: ${accent};
border: 1px solid ${accent}; border: 1px solid ${accent};
/* Qt blends rubber band at ~30% so this reads as a translucent
* accent-tinted rectangle without needing rgba(). */
} }
/* ---------- Library count label states ---------- */ /* ---------- Library count label states ---------- */
/*
* The library tab's count label switches between three visual states
* depending on what refresh() found. The state is exposed as a Qt
* dynamic property `libraryCountState` so users can override these
* rules in their custom.qss without touching the Python.
*
* normal N files default text color, no rule needed
* empty no items dim text (no items found, search miss)
* error bad/unreachable danger color + bold (real error)
*/
QLabel[libraryCountState="empty"] { QLabel[libraryCountState="empty"] {
color: ${text_dim}; color: ${text_dim};
@ -518,18 +411,18 @@ QLabel[libraryCountState="error"] {
font-weight: bold; font-weight: bold;
} }
/* ---------- Thumbnail dot indicators (Qt properties on ThumbnailWidget) ---------- */ /* ---------- Thumbnail indicators ---------- */
ThumbnailWidget { ThumbnailWidget {
qproperty-savedColor: #22cc22; /* green dot: saved to library — universal "confirmed" feel */ qproperty-savedColor: #22cc22;
qproperty-bookmarkedColor: #ffcc00; /* yellow star: bookmarked */ qproperty-bookmarkedColor: #ffcc00;
qproperty-selectionColor: ${accent}; qproperty-selectionColor: ${accent};
qproperty-multiSelectColor: ${accent_dim}; qproperty-multiSelectColor: ${accent_dim};
qproperty-hoverColor: ${accent}; qproperty-hoverColor: ${accent};
qproperty-idleColor: ${border_strong}; qproperty-idleColor: ${border_strong};
} }
/* ---------- Info panel tag category colors ---------- */ /* ---------- Info panel tag colors ---------- */
InfoPanel { InfoPanel {
qproperty-tagArtistColor: ${warning}; qproperty-tagArtistColor: ${warning};
@ -540,19 +433,13 @@ InfoPanel {
qproperty-tagLoreColor: ${text_dim}; qproperty-tagLoreColor: ${text_dim};
} }
/* ---------- Video player letterbox / pillarbox color (mpv background) ---------- */ /* ---------- Video player letterbox ---------- */
VideoPlayer { VideoPlayer {
qproperty-letterboxColor: ${bg}; qproperty-letterboxColor: ${bg};
} }
/* ---------- Popout overlay bars (slideshow toolbar + slideshow controls + embedded preview controls) ---------- */ /* ---------- Popout overlay bars ---------- */
/*
* The popout window's translucent toolbar (top) and transport controls
* (bottom) float over the video content. The bg color comes from the
* @palette overlay_bg slot. Children get the classic overlay treatment:
* transparent backgrounds, near-white text, hairline borders.
*/
QWidget#_slideshow_toolbar, QWidget#_slideshow_toolbar,
QWidget#_slideshow_controls, QWidget#_slideshow_controls,
@ -575,6 +462,8 @@ QWidget#_preview_controls QPushButton {
color: white; color: white;
border: 1px solid rgba(255, 255, 255, 80); border: 1px solid rgba(255, 255, 255, 80);
padding: 2px 6px; padding: 2px 6px;
font-size: 15px;
font-weight: bold;
} }
QWidget#_slideshow_toolbar QPushButton:hover, QWidget#_slideshow_toolbar QPushButton:hover,
QWidget#_slideshow_controls QPushButton:hover, QWidget#_slideshow_controls QPushButton:hover,