Added an experimental light theme and automatic system theme detection

This commit is contained in:
Campbell 2025-05-20 22:26:11 -04:00
parent 7caa7775ff
commit a4679be043
Signed by: NinjaCheetah
GPG Key ID: 39C2500E1778B156
9 changed files with 581 additions and 32 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

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

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

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