from __future__ import annotations import sys from pathlib import Path from PySide6.QtCore import Qt from PySide6.QtGui import QColor, QPalette from PySide6.QtWidgets import QApplication ASSET_DIR = Path(__file__).resolve().parent / "assets" def asset_url(name: str) -> str: return ASSET_DIR.joinpath(name).as_posix() def apply_app_theme(theme: str) -> None: app = QApplication.instance() if app is None: return effective = effective_theme(theme) app.setStyle("Fusion") if effective == "dark": app.setPalette(dark_palette()) app.setStyleSheet(dark_stylesheet()) else: app.setPalette(light_palette()) app.setStyleSheet(light_stylesheet()) def effective_theme(theme: str) -> str: if theme == "dark": return "dark" if theme == "light": return "light" return system_theme() def system_theme() -> str: app = QApplication.instance() if app is not None: try: color_scheme = app.styleHints().colorScheme() if color_scheme == Qt.ColorScheme.Dark: return "dark" if color_scheme == Qt.ColorScheme.Light: return "light" except (AttributeError, TypeError): pass if sys.platform.startswith("win"): try: import winreg with winreg.OpenKey( winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", ) as key: apps_use_light_theme, _ = winreg.QueryValueEx(key, "AppsUseLightTheme") return "light" if int(apps_use_light_theme) else "dark" except OSError: pass if app is not None: return "dark" if app.style().standardPalette().color(QPalette.Window).lightness() < 128 else "light" return "light" def light_palette() -> QPalette: palette = QPalette() palette.setColor(QPalette.Window, QColor("#f8fafc")) palette.setColor(QPalette.WindowText, QColor("#111827")) palette.setColor(QPalette.Base, QColor("#ffffff")) palette.setColor(QPalette.AlternateBase, QColor("#f1f5f9")) palette.setColor(QPalette.ToolTipBase, QColor("#ffffff")) palette.setColor(QPalette.ToolTipText, QColor("#111827")) palette.setColor(QPalette.Text, QColor("#111827")) palette.setColor(QPalette.Button, QColor("#e5e7eb")) palette.setColor(QPalette.ButtonText, QColor("#111827")) palette.setColor(QPalette.Highlight, QColor("#c084fc")) palette.setColor(QPalette.HighlightedText, QColor("#111827")) return palette def dark_palette() -> QPalette: palette = QPalette() palette.setColor(QPalette.Window, QColor("#1f1f1f")) palette.setColor(QPalette.WindowText, QColor("#f8fafc")) palette.setColor(QPalette.Base, QColor("#2b2b2b")) palette.setColor(QPalette.AlternateBase, QColor("#353535")) palette.setColor(QPalette.ToolTipBase, QColor("#111827")) palette.setColor(QPalette.ToolTipText, QColor("#f8fafc")) palette.setColor(QPalette.Text, QColor("#f8fafc")) palette.setColor(QPalette.Button, QColor("#2f2f2f")) palette.setColor(QPalette.ButtonText, QColor("#f8fafc")) palette.setColor(QPalette.Highlight, QColor("#c084fc")) palette.setColor(QPalette.HighlightedText, QColor("#111827")) return palette def light_stylesheet() -> str: return """ QWidget { background-color: #f8fafc; color: #111827; selection-background-color: #93c5fd; selection-color: #111827; } QGraphicsView { background-color: #ffffff; border: none; } QMenuBar { background-color: #f1f5f9; color: #111827; } QMenuBar::item { background: transparent; padding: 5px 10px; } QMenuBar::item:selected { background-color: #e2e8f0; } QMenu { background-color: #ffffff; color: #111827; border: 1px solid #cbd5e1; } QMenu::item { padding: 5px 24px 5px 24px; } QMenu::item:selected { background-color: #dbeafe; } QMenu::item:disabled { color: #94a3b8; background-color: transparent; } QMenu::item:selected:disabled { color: #94a3b8; background-color: transparent; } QPushButton, QToolButton { background-color: #e5e7eb; color: #111827; border: 1px solid #cbd5e1; border-radius: 4px; padding: 4px 8px; } QPushButton:hover, QToolButton:hover { background-color: #dbeafe; } QPushButton:disabled, QToolButton:disabled { background-color: #f1f5f9; color: #94a3b8; border-color: #e2e8f0; } QTreeWidget, QTableWidget, QListWidget, QLineEdit, QComboBox, QSpinBox { background-color: #ffffff; color: #111827; alternate-background-color: #f1f5f9; border: 1px solid #cbd5e1; border-radius: 4px; min-height: 24px; selection-background-color: #bfdbfe; selection-color: #111827; } QLineEdit, QComboBox, QSpinBox { padding: 2px 8px; } QComboBox { padding-right: 28px; } QComboBox:hover, QSpinBox:hover, QLineEdit:hover { border-color: #94a3b8; } QComboBox:focus, QSpinBox:focus, QLineEdit:focus { border-color: #60a5fa; } QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 24px; border-left: 1px solid #cbd5e1; border-top-right-radius: 4px; border-bottom-right-radius: 4px; background-color: #f1f5f9; } QComboBox::drop-down:hover { background-color: #e2e8f0; } QComboBox::down-arrow { image: url("__INPUT_DOWN_ARROW__"); width: 10px; height: 10px; } QSpinBox { padding-right: 28px; } QSpinBox::up-button, QSpinBox::down-button { subcontrol-origin: border; width: 24px; border-left: 1px solid #cbd5e1; background-color: #f1f5f9; } QSpinBox::up-button { subcontrol-position: top right; border-top-right-radius: 4px; } QSpinBox::down-button { subcontrol-position: bottom right; border-bottom-right-radius: 4px; } QSpinBox::up-button:hover, QSpinBox::down-button:hover { background-color: #e2e8f0; } QSpinBox::up-arrow { image: url("__INPUT_UP_ARROW__"); width: 10px; height: 10px; } QSpinBox::down-arrow { image: url("__INPUT_DOWN_ARROW__"); width: 10px; height: 10px; } QComboBox QAbstractItemView { background-color: #ffffff; color: #111827; border: 1px solid #cbd5e1; selection-background-color: #bfdbfe; selection-color: #111827; } QHeaderView::section { background-color: #e5e7eb; color: #111827; border: 1px solid #cbd5e1; padding: 4px; } QSplitter::handle { background-color: #e2e8f0; } QStatusBar { background-color: #f1f5f9; color: #111827; } QToolTip { background-color: #ffffff; color: #111827; border: 1px solid #cbd5e1; } QScrollBar:vertical { background: #f1f5f9; width: 12px; margin: 12px 0 12px 0; border: none; } QScrollBar::handle:vertical { background: #94a3b8; min-height: 28px; margin: 0 4px; border-radius: 2px; } QScrollBar::handle:vertical:hover { background: #64748b; margin: 0 2px; border-radius: 4px; } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: transparent; border: none; height: 0; } QScrollBar::sub-line:vertical, QScrollBar::add-line:vertical { background: #f1f5f9; border: none; height: 12px; subcontrol-origin: margin; } QScrollBar::sub-line:vertical { subcontrol-position: top; } QScrollBar::add-line:vertical { subcontrol-position: bottom; } QScrollBar::up-arrow:vertical { image: url("__UP_ARROW__"); width: 8px; height: 8px; } QScrollBar::down-arrow:vertical { image: url("__DOWN_ARROW__"); width: 8px; height: 8px; } QScrollBar:horizontal { background: #f1f5f9; height: 12px; margin: 0 12px 0 12px; border: none; } QScrollBar::handle:horizontal { background: #94a3b8; min-width: 28px; margin: 4px 0; border-radius: 2px; } QScrollBar::handle:horizontal:hover { background: #64748b; margin: 2px 0; border-radius: 4px; } QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: transparent; border: none; width: 0; } QScrollBar::sub-line:horizontal, QScrollBar::add-line:horizontal { background: #f1f5f9; border: none; width: 12px; subcontrol-origin: margin; } QScrollBar::sub-line:horizontal { subcontrol-position: left; } QScrollBar::add-line:horizontal { subcontrol-position: right; } QScrollBar::left-arrow:horizontal { image: url("__LEFT_ARROW__"); width: 8px; height: 8px; } QScrollBar::right-arrow:horizontal { image: url("__RIGHT_ARROW__"); width: 8px; height: 8px; } """.replace("__INPUT_UP_ARROW__", asset_url("input-arrow-up-light.svg")) \ .replace("__INPUT_DOWN_ARROW__", asset_url("input-arrow-down-light.svg")) \ .replace("__UP_ARROW__", asset_url("scrollbar-arrow-up-light.svg")) \ .replace("__DOWN_ARROW__", asset_url("scrollbar-arrow-down-light.svg")) \ .replace("__LEFT_ARROW__", asset_url("scrollbar-arrow-left-light.svg")) \ .replace("__RIGHT_ARROW__", asset_url("scrollbar-arrow-right-light.svg")) def dark_stylesheet() -> str: return """ QWidget { background-color: #1f1f1f; color: #f8fafc; selection-background-color: #3b82f6; selection-color: #f8fafc; } QGraphicsView { background-color: #2b2b2b; border: none; } QMenuBar { background-color: #242424; color: #f8fafc; } QMenuBar::item { background: transparent; padding: 5px 10px; } QMenuBar::item:selected { background-color: #343434; } QMenu { background-color: #242424; color: #f8fafc; border: 1px solid #444444; } QMenu::item { padding: 5px 24px 5px 24px; } QMenu::item:selected { background-color: #3a3a3a; } QMenu::item:disabled { color: #737373; background-color: transparent; } QMenu::item:selected:disabled { color: #737373; background-color: transparent; } QPushButton, QToolButton { background-color: #2f2f2f; color: #f8fafc; border: 1px solid #4b5563; border-radius: 4px; padding: 4px 8px; } QPushButton:hover, QToolButton:hover { background-color: #3f3f46; } QPushButton:disabled, QToolButton:disabled { background-color: #262626; color: #737373; border-color: #3a3a3a; } QTreeWidget, QTableWidget, QListWidget, QLineEdit, QComboBox, QSpinBox { background-color: #2b2b2b; color: #f8fafc; alternate-background-color: #353535; border: 1px solid #444444; border-radius: 4px; min-height: 24px; selection-background-color: #3b82f6; selection-color: #f8fafc; } QLineEdit, QComboBox, QSpinBox { padding: 2px 8px; } QComboBox { padding-right: 28px; } QComboBox:hover, QSpinBox:hover, QLineEdit:hover { border-color: #737373; } QComboBox:focus, QSpinBox:focus, QLineEdit:focus { border-color: #60a5fa; } QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 24px; border-left: 1px solid #444444; border-top-right-radius: 4px; border-bottom-right-radius: 4px; background-color: #262626; } QComboBox::drop-down:hover { background-color: #3f3f46; } QComboBox::down-arrow { image: url("__INPUT_DOWN_ARROW__"); width: 10px; height: 10px; } QSpinBox { padding-right: 28px; } QSpinBox::up-button, QSpinBox::down-button { subcontrol-origin: border; width: 24px; border-left: 1px solid #444444; background-color: #262626; } QSpinBox::up-button { subcontrol-position: top right; border-top-right-radius: 4px; } QSpinBox::down-button { subcontrol-position: bottom right; border-bottom-right-radius: 4px; } QSpinBox::up-button:hover, QSpinBox::down-button:hover { background-color: #3f3f46; } QSpinBox::up-arrow { image: url("__INPUT_UP_ARROW__"); width: 10px; height: 10px; } QSpinBox::down-arrow { image: url("__INPUT_DOWN_ARROW__"); width: 10px; height: 10px; } QComboBox QAbstractItemView { background-color: #2b2b2b; color: #f8fafc; border: 1px solid #444444; selection-background-color: #3b82f6; selection-color: #f8fafc; } QHeaderView::section { background-color: #3a3a3a; color: #f8fafc; border: 1px solid #444444; padding: 4px; } QSplitter::handle { background-color: #111827; } QStatusBar { background-color: #1f1f1f; color: #f8fafc; } QToolTip { background-color: #111827; color: #f8fafc; border: 1px solid #4b5563; } QScrollBar:vertical { background: #1f1f1f; width: 12px; margin: 12px 0 12px 0; border: none; } QScrollBar::handle:vertical { background: #737373; min-height: 28px; margin: 0 4px; border-radius: 2px; } QScrollBar::handle:vertical:hover { background: #a3a3a3; margin: 0 2px; border-radius: 4px; } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: transparent; border: none; height: 0; } QScrollBar::sub-line:vertical, QScrollBar::add-line:vertical { background: #1f1f1f; border: none; height: 12px; subcontrol-origin: margin; } QScrollBar::sub-line:vertical { subcontrol-position: top; } QScrollBar::add-line:vertical { subcontrol-position: bottom; } QScrollBar::up-arrow:vertical { image: url("__UP_ARROW__"); width: 8px; height: 8px; } QScrollBar::down-arrow:vertical { image: url("__DOWN_ARROW__"); width: 8px; height: 8px; } QScrollBar:horizontal { background: #1f1f1f; height: 12px; margin: 0 12px 0 12px; border: none; } QScrollBar::handle:horizontal { background: #737373; min-width: 28px; margin: 4px 0; border-radius: 2px; } QScrollBar::handle:horizontal:hover { background: #a3a3a3; margin: 2px 0; border-radius: 4px; } QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background: transparent; border: none; width: 0; } QScrollBar::sub-line:horizontal, QScrollBar::add-line:horizontal { background: #1f1f1f; border: none; width: 12px; subcontrol-origin: margin; } QScrollBar::sub-line:horizontal { subcontrol-position: left; } QScrollBar::add-line:horizontal { subcontrol-position: right; } QScrollBar::left-arrow:horizontal { image: url("__LEFT_ARROW__"); width: 8px; height: 8px; } QScrollBar::right-arrow:horizontal { image: url("__RIGHT_ARROW__"); width: 8px; height: 8px; } """.replace("__INPUT_UP_ARROW__", asset_url("input-arrow-up-dark.svg")) \ .replace("__INPUT_DOWN_ARROW__", asset_url("input-arrow-down-dark.svg")) \ .replace("__UP_ARROW__", asset_url("scrollbar-arrow-up-dark.svg")) \ .replace("__DOWN_ARROW__", asset_url("scrollbar-arrow-down-dark.svg")) \ .replace("__LEFT_ARROW__", asset_url("scrollbar-arrow-left-dark.svg")) \ .replace("__RIGHT_ARROW__", asset_url("scrollbar-arrow-right-dark.svg"))