Compare commits

...

2 Commits

Author SHA1 Message Date
a4679be043
Added an experimental light theme and automatic system theme detection 2025-05-20 22:26:11 -04:00
7caa7775ff
Force correct coloring so that text is always legible on all system themes
Since NUSGet only supports a dark theme right now, but your system may use a light theme, the OS may recolor text on screen to be black on black, which is obviously bad for readability. All text will now be white, so you can always read it.
And hey, maybe there will be a proper light theme soon!
2025-05-20 21:11:29 -04:00
9 changed files with 654 additions and 30 deletions

View File

@ -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)

52
modules/theme.py Normal file
View File

@ -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()

View File

@ -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

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="16"
height="16"
viewBox="0 0 16 16"
version="1.1"
id="svg1"
inkscape:version="1.4 (e7c3feb1, 2024-10-09)"
sodipodi:docname="down_arrow_black.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:showpageshadow="2"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
inkscape:zoom="57.72"
inkscape:cx="8.0128205"
inkscape:cy="8.3939709"
inkscape:window-width="1512"
inkscape:window-height="834"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
sodipodi:type="star"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.154168;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path1"
inkscape:flatsided="false"
sodipodi:sides="3"
sodipodi:cx="5.0945272"
sodipodi:cy="5.9900498"
sodipodi:r1="1.9104478"
sodipodi:r2="0.95522392"
sodipodi:arg1="1.5707963"
sodipodi:arg2="2.6179939"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 5.0945273,7.9004977 4.2672791,6.4676618 3.4400309,5.034826 l 1.6544964,-10e-8 1.6544963,0 -0.8272482,1.4328359 z"
inkscape:transform-center-y="0.68257261"
transform="matrix(2.3912596,0,0,1.4291353,-4.1823371,-1.2431638)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="16"
height="16"
viewBox="0 0 16 16"
version="1.1"
id="svg1"
inkscape:version="1.4 (e7c3feb1, 2024-10-09)"
sodipodi:docname="right_arrow_black.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:showpageshadow="2"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
inkscape:zoom="57.72"
inkscape:cx="6.8866944"
inkscape:cy="7.52772"
inkscape:window-width="1512"
inkscape:window-height="834"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
sodipodi:type="star"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.154168;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path1"
inkscape:flatsided="false"
sodipodi:sides="3"
sodipodi:cx="5.0945272"
sodipodi:cy="5.9900498"
sodipodi:r1="1.9104478"
sodipodi:r2="0.95522392"
sodipodi:arg1="1.5707963"
sodipodi:arg2="2.6179939"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 5.0945273,7.9004977 4.2672791,6.4676618 3.4400309,5.034826 l 1.6544964,-10e-8 1.6544963,0 -0.8272482,1.4328359 z"
transform="matrix(0,-2.3912596,1.4291353,0,-0.56059112,19.499764)"
inkscape:transform-center-x="-0.68257261" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="16"
height="16"
viewBox="0 0 16 16"
version="1.1"
id="svg1"
inkscape:version="1.4 (e7c3feb1, 2024-10-09)"
sodipodi:docname="right_arrow.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:showpageshadow="2"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
inkscape:zoom="57.72"
inkscape:cx="6.8693694"
inkscape:cy="7.52772"
inkscape:window-width="1512"
inkscape:window-height="836"
inkscape:window-x="0"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
sodipodi:type="star"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.154168;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path1"
inkscape:flatsided="false"
sodipodi:sides="3"
sodipodi:cx="5.0945272"
sodipodi:cy="5.9900498"
sodipodi:r1="1.9104478"
sodipodi:r2="0.95522392"
sodipodi:arg1="1.5707963"
sodipodi:arg2="2.6179939"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 5.0945273,7.9004977 4.2672791,6.4676618 3.4400309,5.034826 l 1.6544964,-10e-8 1.6544963,0 -0.8272482,1.4328359 z"
transform="matrix(0,-2.3912596,1.4291353,0,-0.56059112,19.499764)"
inkscape:transform-center-x="-0.68257261" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -145,6 +145,7 @@ QTreeView {
}
QTreeView QHeaderView::section {
color: white;
background-color: #2b2b2b;
border: 0;
font-weight: 500;
@ -170,7 +171,18 @@ 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_white.svg");
}
QTreeView::branch:open:has-children:!has-siblings,
QTreeView::branch:open:has-children:has-siblings {
image: url("{IMAGE_PREFIX}/down_arrow_white.svg");
}
QTextBrowser {
color: white;
background-color: #1a1a1a;
selection-background-color: #1a73e8;
}
@ -242,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 {
@ -326,6 +338,10 @@ QScrollBar::sub-line:horizontal {
subcontrol-origin: margin;
}
QMessageBox QLabel {
color: white;
}
WrapCheckboxWidget {
show-decoration-selected: 1;
outline: 0;

398
resources/style_light.qss Normal file
View File

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