diff --git a/NUSGet.py b/NUSGet.py index 888c8e0..06e7170 100644 --- a/NUSGet.py +++ b/NUSGet.py @@ -30,6 +30,7 @@ from qt.py.ui_AboutDialog import AboutNUSGet from qt.py.ui_MainMenu import Ui_MainWindow from modules.core import * +from modules.theme import is_dark_theme from modules.tree import NUSGetTreeModel, TIDFilterProxyModel from modules.download_batch import run_nus_download_batch from modules.download_wii import run_nus_download_wii @@ -154,10 +155,14 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.trees[tree].collapsed.connect(lambda: self.resize_tree(self.ui.platform_tabs.currentIndex())) # This stylesheet patch allows me to add the correct padding above the scrollbar so that it doesn't overlap # the QTreeView's header. + if is_dark_theme(): + bg_color = "#2b2b2b" + else: + bg_color = "#e3e3e3" self.trees[tree].setStyleSheet(self.trees[tree].styleSheet() + f""" QTreeView QScrollBar::sub-line:vertical {{ border: 0; - background: #2b2b2b; + background: {bg_color}; height: {self.trees[tree].header().sizeHint().height()}px; }}""") # Prevent resizing. @@ -650,7 +655,10 @@ if __name__ == "__main__": # Load Fusion because that's objectively the best base theme, and then load the fancy stylesheet on top to make # NUSGet look nice and pretty. app.setStyle("fusion") - stylesheet = open(os.path.join(os.path.dirname(__file__), "resources", "style.qss")).read() + if is_dark_theme(): + stylesheet = open(os.path.join(os.path.dirname(__file__), "resources", "style_dark.qss")).read() + else: + stylesheet = open(os.path.join(os.path.dirname(__file__), "resources", "style_light.qss")).read() image_path_prefix = pathlib.Path(os.path.join(os.path.dirname(__file__), "resources")).resolve().as_posix() stylesheet = stylesheet.replace("{IMAGE_PREFIX}", image_path_prefix) app.setStyleSheet(stylesheet) diff --git a/modules/theme.py b/modules/theme.py new file mode 100644 index 0000000..1906b9a --- /dev/null +++ b/modules/theme.py @@ -0,0 +1,52 @@ +# "modules/theme.py", licensed under the MIT license +# Copyright 2024-2025 NinjaCheetah & Contributors + +import platform +import subprocess + +def is_dark_theme_windows(): + # This has to be here so that Python doesn't try to import it on non-Windows. + import winreg + try: + registry = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) + key = winreg.OpenKey(registry, r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize") + # This value is "AppsUseLightTheme" so a "1" is light and a "0" is dark. Side note: I hate the Windows registry. + value, _ = winreg.QueryValueEx(key, "AppsUseLightTheme") + return value == 0 + except Exception: + return False + +def is_dark_theme_macos(): + # macOS is weird. If the dark theme is on, then `defaults read -g AppleInterfaceStyle` returns "Dark". If the light + # theme is on, then trying to read this key fails and returns an error instead. + try: + result = subprocess.run( + ["defaults", "read", "-g", "AppleInterfaceStyle"], + capture_output=True, text=True + ) + return "Dark" in result.stdout + except Exception: + return False + +def is_dark_theme_linux(): + try: + import subprocess + result = subprocess.run( + ["gsettings", "get", "org.gnome.desktop.interface", "gtk-theme"], + capture_output=True, text=True + ) + # Looking for *not* "Light", because I want any theme that isn't light to be dark. An example of this is my own + # KDE Plasma setup on my desktop, where I use the "Breeze" GTK theme and want dark NUSGet to be used in that + # case. + return not "light" in result.stdout.lower() + except Exception: + return False + +def is_dark_theme(): + system = platform.system() + if system == "Windows": + return is_dark_theme_windows() + elif system == "Darwin": + return is_dark_theme_macos() + else: + return is_dark_theme_linux() diff --git a/qt/py/ui_AboutDialog.py b/qt/py/ui_AboutDialog.py index 2da906b..8b1bb01 100644 --- a/qt/py/ui_AboutDialog.py +++ b/qt/py/ui_AboutDialog.py @@ -19,17 +19,9 @@ class AboutNUSGet(QDialog): # Set background color to match main app self.setStyleSheet(""" - Credits { - background-color: #222222; - color: #ffffff; - } - QLabel { - color: #ffffff; - } QLabel[class="title"] { font-size: 20px; font-weight: bold; - color: #ffffff; } QLabel[class="version"] { font-size: 13px; @@ -45,25 +37,6 @@ class AboutNUSGet(QDialog): border-bottom: 1px solid #444444; padding-bottom: 4px; margin-top: 8px; - } - QPushButton { - background-color: transparent; - border: 1px solid rgba(70, 70, 70, 1); - border-radius: 8px; - padding: 8px 12px; - margin: 4px 0px; - font-size: 13px; - font-weight: 500; - color: #ffffff; - text-align: center; - } - QPushButton:hover { - background-color: rgba(60, 60, 60, 1); - border-color: #4a86e8; - } - QPushButton:pressed { - background-color: rgba(26, 115, 232, 0.15); - border: 1px solid #1a73e8; }""") # Create main layout diff --git a/resources/down_arrow_black.svg b/resources/down_arrow_black.svg new file mode 100644 index 0000000..2f1a769 --- /dev/null +++ b/resources/down_arrow_black.svg @@ -0,0 +1,59 @@ + + + + + + + + + + diff --git a/resources/down_arrow.svg b/resources/down_arrow_white.svg similarity index 100% rename from resources/down_arrow.svg rename to resources/down_arrow_white.svg diff --git a/resources/right_arrow_black.svg b/resources/right_arrow_black.svg new file mode 100644 index 0000000..34720f0 --- /dev/null +++ b/resources/right_arrow_black.svg @@ -0,0 +1,59 @@ + + + + + + + + + + diff --git a/resources/right_arrow.svg b/resources/right_arrow_white.svg similarity index 100% rename from resources/right_arrow.svg rename to resources/right_arrow_white.svg diff --git a/resources/style.qss b/resources/style_dark.qss similarity index 97% rename from resources/style.qss rename to resources/style_dark.qss index 5b4aaaa..897ed36 100644 --- a/resources/style.qss +++ b/resources/style_dark.qss @@ -173,12 +173,12 @@ QTreeView QScrollBar:vertical { QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { - image: url("{IMAGE_PREFIX}/right_arrow.svg"); + image: url("{IMAGE_PREFIX}/right_arrow_white.svg"); } QTreeView::branch:open:has-children:!has-siblings, QTreeView::branch:open:has-children:has-siblings { - image: url("{IMAGE_PREFIX}/down_arrow.svg"); + image: url("{IMAGE_PREFIX}/down_arrow_white.svg"); } QTextBrowser { @@ -254,7 +254,7 @@ QComboBox::drop-down { } QComboBox::down-arrow { - image: url("{IMAGE_PREFIX}/down_arrow.svg"); + image: url("{IMAGE_PREFIX}/down_arrow_white.svg"); } QComboBox QAbstractItemView { diff --git a/resources/style_light.qss b/resources/style_light.qss new file mode 100644 index 0000000..d333062 --- /dev/null +++ b/resources/style_light.qss @@ -0,0 +1,398 @@ +/* "resources/style.qss" from NUSGet by NinjaCheetah & Contributors */ +/* Much of this QSS was written by Alex (https://github.com/Humanoidear) */ +/* from WiiLink for the fancy new WiiLink Patcher GUI. Used with permission. */ + +QMainWindow, QDialog { + background-color: #ffffff; +} + +QMainWindow QLabel { + color: #000000; +} + +QMenuBar { + background-color: #ffffff; +} + +QMenuBar::item:selected { + background-color: rgba(60, 60, 60, 1); + color: white; +} + +QMenuBar::item:pressed { + background-color: #1a73e8; + color: #000000; +} + +QMenu { + background-color: #ffffff; + border: 1px solid rgb(163, 163, 163); + border-radius: 8px; + padding: 6px 2px; + margin: 4px 0; + color: #000000; +} + +QMenu::item { + padding: 6px 2px; + margin: 2px; + border-radius: 4px; + background-color: transparent; +} + +QMenu::item:selected { + background-color: #1a73e8; + color: #000000; +} + +QMenu::icon { + padding: 4px; +} + +QRadioButton { + background-color: transparent; + border: 1px solid rgb(163, 163, 163); + border-radius: 8px; + padding: 8px 10px; + font-size: 13px; + font-weight: 500; + color: #ffffff; +} + +QRadioButton:hover { + background-color: rgba(60, 60, 60, 1); + border-color: #4a86e8; +} + +QRadioButton:checked { + background-color: rgba(26, 115, 232, 0.08); + border: 1px solid #1a73e8; + color: #1a73e8; +} + +QRadioButton::indicator { + width: 18px; + height: 18px; + border-radius: 5px; + border: 1px solid #5f6368; + margin-right: 8px; + subcontrol-position: left center; +} + +QRadioButton::indicator:checked { + background-color: #1a73e8; + border: 1px solid #1a73e8; + image: url("{IMAGE_PREFIX}/rounded_square.svg"); +} + +QRadioButton::indicator:hover { + border-color: #1a73e8; +} + +QLineEdit { + background-color: transparent; + border: 1px solid rgb(163, 163, 163); + border-radius: 8px; + padding: 6px 10px; + margin: 4px 0px; + font-size: 13px; + color: #000000; + selection-background-color: #1a73e8; +} + +QLineEdit:focus { + border-color: #1a73e8; +} + +QLineEdit:disabled { + background-color: rgba(182, 182, 182, 0.5); + border: 1px solid rgba(100, 100, 100, 0.3); + color: rgba(255, 255, 255, 0.3); +} + +QTabWidget::pane { + border: 1px solid rgb(163, 163, 163); + border-top-right-radius: 8px; + border-bottom-right-radius: 8px; + border-bottom-left-radius: 8px; + background-color: #e3e3e3; + top: -1px; +} + +QTabBar::tab { + background-color: transparent; + border-top: 1px solid rgb(163, 163, 163); + border-left: 1px solid rgb(163, 163, 163); + border-right: 1px solid rgb(163, 163, 163); + border-top-left-radius: 6px; + border-top-right-radius: 6px; + padding: 6px 10px; + font-size: 13px; + font-weight: 500; + color: #000000; +} + +QTabBar::tab:selected, QTabBar::tab:hover { + background-color: #e3e3e3; +} + +QTreeView { + show-decoration-selected: 1; + outline: 0; + background-color: #ffffff; + border: 0; + border-radius: 8px; +} + +QTreeView QHeaderView::section { + color: #000000; + background-color: #e3e3e3; + border: 0; + font-weight: 500; +} + +QTreeView::item { + color: #000000; +} + +QTreeView::item:hover { + background-color: rgb(195, 195, 195); +} + +QTreeView::item:focus { + background-color: rgba(26, 115, 232, 0.08); +} + +QTreeView::item:selected { + background-color: rgb(127, 182, 255); +} + +QTreeView::branch:selected { + background-color: rgb(127, 182, 255); +} + +QTreeView QScrollBar:vertical { + margin-top: 16px; +} + +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings { + image: url("{IMAGE_PREFIX}/right_arrow_black.svg"); +} + +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings { + image: url("{IMAGE_PREFIX}/down_arrow_black.svg"); +} + +QTextBrowser { + color: #000000; + background-color: #ececec; + selection-background-color: #1a73e8; + selection-color: #ffffff; +} + +QPushButton { + outline: 0; + show-decoration-selected: 1; + background-color: transparent; + border: 1px solid rgb(163, 163, 163); + border-radius: 8px; + padding: 6px 10px; + margin: 4px 0px; + font-size: 13px; + font-weight: 500; + color: #000000; +} + +QPushButton:hover { + background-color: rgb(195, 195, 195); + border-color: #4a86e8; +} + +QPushButton:focus { + background-color: rgb(195, 195, 195); + border-color: #4a86e8; +} + +QPushButton:pressed { + background-color: rgba(26, 115, 232, 0.15); + border: 1px solid #1a73e8; +} + +QPushButton:disabled { + background-color: rgba(182, 182, 182, 0.5); + border: 1px solid rgba(100, 100, 100, 0.3); + color: rgba(255, 255, 255, 0.3); +} + +QComboBox { + background-color: transparent; + combobox-popup: 0; + border: 1px solid rgb(163, 163, 163); + border-radius: 8px; + padding: 6px 10px; + margin: 4px 0px; + font-size: 13px; + font-weight: 500; + color: #000000; +} + +QComboBox:on { + background-color: rgba(26, 115, 232, 0.15); + border: 1px solid #1a73e8; +} + +QComboBox:hover { + background-color: rgb(195, 195, 195); + border-color: #4a86e8; +} + +QComboBox:focus { + background-color: rgb(195, 195, 195); + border-color: #4a86e8; +} + +QComboBox::drop-down { + border: 0; + width: 24px; +} + +QComboBox::down-arrow { + image: url("{IMAGE_PREFIX}/down_arrow_black.svg"); +} + +QComboBox QAbstractItemView { + background-color: #ffffff; + border: 1px solid rgb(163, 163, 163); + border-radius: 8px; + padding: 4px; + outline: none; +} + +QComboBox QAbstractItemView::item { + height: 25px; + border-radius: 4px; + padding: 4px 8px; + margin: 2px 0px; + color: #000000; +} + +QComboBox QAbstractItemView::item:hover { + background-color: #1a73e8; + color: #ffffff; +} + +QScrollBar:vertical { + border: 0; + border-radius: 8px; + padding: 2px 0 2px 0; + background-color: #f1f1f1; +} + +QScrollBar::handle:vertical { + background-color: #e3e3e3; + margin: 0 2px 0 2px; + width: 10px; + border: 1px solid rgb(163, 163, 163); + border-radius: 4px; +} + +QScrollBar::handle:vertical:hover { + background-color: rgba(26, 115, 232, 0.4); +} + +QScrollBar::add-line:vertical { + height: 0; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:vertical { + height: 0; + subcontrol-position: top; + subcontrol-origin: margin; +} + +QScrollBar:horizontal { + border: 0; + border-radius: 8px; + padding: 2px 0 2px 0; + background-color: #f1f1f1; +} + +QScrollBar::handle:horizontal { + background-color: #e3e3e3; + border: 1px solid rgb(163, 163, 163); + margin: 0px 2px 0px 2px; + border: 1px solid rgb(163, 163, 163); + border-radius: 4px; +} + +QScrollBar::handle:horizontal:hover { + background-color: rgba(26, 115, 232, 0.4); +} + +QScrollBar::add-line:horizontal { + height: 0; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:horizontal { + height: 0; + subcontrol-position: top; + subcontrol-origin: margin; +} + +QMessageBox QLabel { + color: #000000; +} + +WrapCheckboxWidget { + show-decoration-selected: 1; + outline: 0; + background-color: transparent; + border: 1px solid rgb(163, 163, 163); + border-radius: 8px; + padding: 12px 10px; + font-size: 13px; + font-weight: 500; + color: #000000; +} + +WrapCheckboxWidget:hover { + background-color: rgb(195, 195, 195); + border-color: #4a86e8; +} + +WrapCheckboxWidget:disabled { + background-color: rgba(182, 182, 182, 0.5); + border: 1px solid rgba(100, 100, 100, 0.3); + color: rgba(255, 255, 255, 0.3); +} + +WrapCheckboxWidget QLabel:disabled { + color: #919191; +} + +WrapCheckboxWidget QCheckBox::indicator { + width: 16px; + height: 16px; + border-radius: 4px; + border: 1px solid #5f6368; +} + +WrapCheckboxWidget QCheckBox::indicator:checked { + background-color: #1a73e8; + border: 1px solid #1a73e8; + image: url("{IMAGE_PREFIX}/check.svg"); +} + +WrapCheckboxWidget QCheckBox::indicator:hover { + border-color: #1a73e8; +} + +WrapCheckboxWidget QCheckBox:checked { + color: #1a73e8; +}