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;
+}