- Rework Settings tab into grouped sections - Add preferred-region matching and full-hash-by-default setting - Replace combo-box square arrow with a proper chevron - Fix UMD Refresh button (clicked bool was clearing the view) - Remove pkg2zip.exe fallback (pure-Python decryptor is the primary path) - Move LAN IP/share paths to .env; use example defaults in config
317 lines
7.0 KiB
Python
317 lines
7.0 KiB
Python
"""Global dark theme for the GUI (Fusion style + palette + stylesheet).
|
|
|
|
Applied once at startup from ``gui.main``. The per-widget ``setStyleSheet``
|
|
colors sprinkled through ``gui.py`` (``#999`` secondary text, ``#e06c6c`` delete
|
|
buttons, ``#14151a`` preview panels) were written for a dark background, so a
|
|
single cohesive dark theme here makes the whole UI consistent.
|
|
"""
|
|
from PySide6.QtGui import QColor, QPalette
|
|
|
|
from . import config
|
|
|
|
# Design tokens
|
|
BG = "#1e1f24" # window background
|
|
SURFACE = "#26272e" # cards, inputs, table headers
|
|
RAISED = "#2d2e36" # buttons, combos, menus
|
|
BORDER = "#3a3b44"
|
|
BORDER_SOFT = "#33343d"
|
|
TEXT = "#e6e6ea"
|
|
TEXT_DIM = "#9a9ba5"
|
|
TEXT_FAINT = "#6f7079"
|
|
ACCENT = "#4a9eff"
|
|
ACCENT_HOVER = "#6bb0ff"
|
|
|
|
|
|
_STYLESHEET = f"""
|
|
QMainWindow {{
|
|
background: {BG};
|
|
}}
|
|
QWidget {{
|
|
color: {TEXT};
|
|
}}
|
|
|
|
/* --- Tabs --- */
|
|
QTabWidget::pane {{
|
|
border: 1px solid {BORDER};
|
|
border-radius: 6px;
|
|
top: -1px;
|
|
}}
|
|
QTabBar::tab {{
|
|
background: transparent;
|
|
color: {TEXT_DIM};
|
|
padding: 8px 18px;
|
|
border: none;
|
|
border-bottom: 2px solid transparent;
|
|
margin-right: 2px;
|
|
}}
|
|
QTabBar::tab:hover:!selected {{
|
|
color: {TEXT};
|
|
}}
|
|
QTabBar::tab:selected {{
|
|
color: {TEXT};
|
|
border-bottom: 2px solid {ACCENT};
|
|
}}
|
|
|
|
/* --- Buttons --- */
|
|
QPushButton {{
|
|
background: {RAISED};
|
|
border: 1px solid {BORDER};
|
|
border-radius: 6px;
|
|
padding: 6px 12px;
|
|
}}
|
|
QPushButton:hover {{
|
|
background: #343640;
|
|
border-color: {ACCENT_HOVER};
|
|
}}
|
|
QPushButton:pressed {{
|
|
background: {SURFACE};
|
|
}}
|
|
QPushButton:disabled {{
|
|
color: {TEXT_FAINT};
|
|
background: {SURFACE};
|
|
border-color: {BORDER_SOFT};
|
|
}}
|
|
QPushButton:checked {{
|
|
background: {ACCENT};
|
|
border-color: {ACCENT};
|
|
color: #101116;
|
|
}}
|
|
|
|
/* --- Inputs --- */
|
|
QLineEdit, QSpinBox, QPlainTextEdit, QTextEdit {{
|
|
background: {SURFACE};
|
|
border: 1px solid {BORDER};
|
|
border-radius: 6px;
|
|
padding: 5px 8px;
|
|
selection-background-color: {ACCENT};
|
|
selection-color: #101116;
|
|
}}
|
|
QLineEdit:focus, QSpinBox:focus, QPlainTextEdit:focus, QTextEdit:focus {{
|
|
border-color: {ACCENT};
|
|
}}
|
|
QLineEdit:disabled, QSpinBox:disabled {{
|
|
color: {TEXT_FAINT};
|
|
}}
|
|
|
|
/* --- Combo boxes --- */
|
|
QComboBox {{
|
|
background: {RAISED};
|
|
border: 1px solid {BORDER};
|
|
border-radius: 6px;
|
|
padding: 4px 10px;
|
|
}}
|
|
QComboBox:hover {{
|
|
border-color: {ACCENT_HOVER};
|
|
}}
|
|
QComboBox::drop-down {{
|
|
border: none;
|
|
width: 22px;
|
|
}}
|
|
QComboBox::down-arrow {{
|
|
image: url(__CHEVRON__);
|
|
width: 12px;
|
|
height: 8px;
|
|
margin-right: 6px;
|
|
}}
|
|
QComboBox QAbstractItemView {{
|
|
background: {RAISED};
|
|
border: 1px solid {BORDER};
|
|
border-radius: 6px;
|
|
selection-background-color: rgba(74, 158, 255, 0.25);
|
|
selection-color: {TEXT};
|
|
outline: 0;
|
|
}}
|
|
|
|
/* --- Tables --- */
|
|
QTableWidget {{
|
|
background: transparent;
|
|
border: none;
|
|
gridline-color: {BORDER_SOFT};
|
|
}}
|
|
QHeaderView::section {{
|
|
background: {SURFACE};
|
|
color: {TEXT_DIM};
|
|
border: none;
|
|
border-bottom: 1px solid {BORDER};
|
|
padding: 6px 8px;
|
|
}}
|
|
QTableWidget::item {{
|
|
padding: 4px 6px;
|
|
}}
|
|
QTableWidget::item:selected {{
|
|
background: rgba(74, 158, 255, 0.22);
|
|
color: {TEXT};
|
|
}}
|
|
QTableCornerButton::section {{
|
|
background: {SURFACE};
|
|
border: none;
|
|
border-bottom: 1px solid {BORDER};
|
|
}}
|
|
|
|
/* --- Cover grid / lists --- */
|
|
QListWidget {{
|
|
background: transparent;
|
|
border: none;
|
|
}}
|
|
QListWidget::item {{
|
|
border-radius: 6px;
|
|
}}
|
|
QListWidget::item:hover:!selected {{
|
|
background: rgba(255, 255, 255, 0.04);
|
|
}}
|
|
QListWidget::item:selected {{
|
|
background: rgba(74, 158, 255, 0.22);
|
|
}}
|
|
|
|
/* --- Cards (details panels use QScrollArea) --- */
|
|
QScrollArea {{
|
|
background: {SURFACE};
|
|
border: none;
|
|
}}
|
|
|
|
/* --- Menus --- */
|
|
QMenu {{
|
|
background: {RAISED};
|
|
border: 1px solid {BORDER};
|
|
border-radius: 6px;
|
|
padding: 4px;
|
|
}}
|
|
QMenu::item {{
|
|
padding: 6px 26px 6px 12px;
|
|
border-radius: 4px;
|
|
}}
|
|
QMenu::item:selected {{
|
|
background: rgba(74, 158, 255, 0.25);
|
|
}}
|
|
QMenu::separator {{
|
|
height: 1px;
|
|
background: {BORDER};
|
|
margin: 4px 8px;
|
|
}}
|
|
|
|
/* --- Scrollbars --- */
|
|
QScrollBar:vertical {{
|
|
background: transparent;
|
|
width: 10px;
|
|
margin: 0;
|
|
}}
|
|
QScrollBar::handle:vertical {{
|
|
background: {BORDER};
|
|
border-radius: 5px;
|
|
min-height: 30px;
|
|
}}
|
|
QScrollBar::handle:vertical:hover {{
|
|
background: {ACCENT};
|
|
}}
|
|
QScrollBar:horizontal {{
|
|
background: transparent;
|
|
height: 10px;
|
|
margin: 0;
|
|
}}
|
|
QScrollBar::handle:horizontal {{
|
|
background: {BORDER};
|
|
border-radius: 5px;
|
|
min-width: 30px;
|
|
}}
|
|
QScrollBar::handle:horizontal:hover {{
|
|
background: {ACCENT};
|
|
}}
|
|
QScrollBar::add-line, QScrollBar::sub-line {{
|
|
width: 0;
|
|
height: 0;
|
|
}}
|
|
QScrollBar::add-page, QScrollBar::sub-page {{
|
|
background: transparent;
|
|
}}
|
|
|
|
/* --- Splitter --- */
|
|
QSplitter::handle {{
|
|
background: transparent;
|
|
}}
|
|
QSplitter::handle:horizontal {{
|
|
width: 1px;
|
|
}}
|
|
QSplitter::handle:vertical {{
|
|
height: 1px;
|
|
}}
|
|
|
|
/* --- Misc --- */
|
|
QToolTip {{
|
|
background: {RAISED};
|
|
color: {TEXT};
|
|
border: 1px solid {BORDER};
|
|
padding: 4px 6px;
|
|
}}
|
|
QStatusBar {{
|
|
background: {SURFACE};
|
|
color: {TEXT_DIM};
|
|
border-top: 1px solid {BORDER};
|
|
}}
|
|
QProgressBar {{
|
|
background: {SURFACE};
|
|
border: 1px solid {BORDER};
|
|
border-radius: 6px;
|
|
text-align: center;
|
|
}}
|
|
QProgressBar::chunk {{
|
|
background: {ACCENT};
|
|
border-radius: 5px;
|
|
}}
|
|
QCheckBox {{
|
|
spacing: 8px;
|
|
}}
|
|
QCheckBox::indicator {{
|
|
width: 16px;
|
|
height: 16px;
|
|
border: 1px solid {BORDER};
|
|
border-radius: 4px;
|
|
background: {SURFACE};
|
|
}}
|
|
QCheckBox::indicator:checked {{
|
|
background: {ACCENT};
|
|
border-color: {ACCENT};
|
|
}}
|
|
QGroupBox {{
|
|
border: 1px solid {BORDER};
|
|
border-radius: 6px;
|
|
margin-top: 10px;
|
|
}}
|
|
QGroupBox::title {{
|
|
subcontrol-origin: margin;
|
|
left: 10px;
|
|
padding: 0 4px;
|
|
color: {TEXT_DIM};
|
|
}}
|
|
"""
|
|
|
|
|
|
def _dark_palette():
|
|
p = QPalette()
|
|
p.setColor(QPalette.Window, QColor(BG))
|
|
p.setColor(QPalette.WindowText, QColor(TEXT))
|
|
p.setColor(QPalette.Base, QColor(SURFACE))
|
|
p.setColor(QPalette.AlternateBase, QColor(RAISED))
|
|
p.setColor(QPalette.ToolTipBase, QColor(RAISED))
|
|
p.setColor(QPalette.ToolTipText, QColor(TEXT))
|
|
p.setColor(QPalette.Text, QColor(TEXT))
|
|
p.setColor(QPalette.Button, QColor(RAISED))
|
|
p.setColor(QPalette.ButtonText, QColor(TEXT))
|
|
p.setColor(QPalette.BrightText, QColor("#ff5c5c"))
|
|
p.setColor(QPalette.Link, QColor(ACCENT))
|
|
p.setColor(QPalette.Highlight, QColor(ACCENT))
|
|
p.setColor(QPalette.HighlightedText, QColor("#101116"))
|
|
p.setColor(QPalette.PlaceholderText, QColor(TEXT_FAINT))
|
|
|
|
faint = QColor(TEXT_FAINT)
|
|
for role in (QPalette.WindowText, QPalette.Text, QPalette.ButtonText):
|
|
p.setColor(QPalette.Disabled, role, faint)
|
|
return p
|
|
|
|
|
|
def apply_theme(app):
|
|
"""Apply the dark Fusion theme to ``app`` (call once before showing UI)."""
|
|
app.setStyle("Fusion")
|
|
app.setPalette(_dark_palette())
|
|
chevron = str(config.resource_path("img", "chevron.png"))
|
|
app.setStyleSheet(_STYLESHEET.replace("__CHEVRON__", chevron))
|