Automatic dark theme switching for Windows and Linux

- Windows dark theme uses "fusion" style, which is better suited, but has minor differences
- Improve OS theme detection
  - Linux:
    - Listen for OS color schemes changes on D-Bus
    - Read OS scheme for D-Bus. Fallback with gsettings, reading org.gnome.desktop.interface.
      First "color-scheme" key, then "gtk-theme". Finally, fallback to checking window palette
  - Windows (dark mode detection was not implemented before):
    - Force dark palette when OS uses dark mode by setting QT_QPA_PLATFORM to "windows:darkmode=2"
    - This enables to detect dark mode by checking the window palette
- Improve theming capabilites:
  - Linux uses custom palette when dark mode is detected.
    By using palette(xxx) in .qss files, there is no need to create a dark stylesheet
  - Allow themes to have stylesheet variants, dark.qss and light.qss
  - If current mode is dark, use dark icons for controller and keyboard applets
  - Add "dark" property to RendererStatusBarButton and GPUStatusBarButton, set to true when dark mode is used.
    Allows to have distinct colors for GPU API and accuracy buttons depending on dark mode or not
  - Enable all themes to have dark icon alternatives, not just "default" and "colorful"
    - If dark mode, icons are loaded from the directory "THEME-NAME_dark/icons"
  - If current mode is dark, use dark icons for controller and keyboard applets
  - Only qdarkstyle, qdarkstyle_midnight_blue, colorful_dark and
    colorful_midnight_blue used elements specific to dark themes
This commit is contained in:
flodavid 2024-02-04 04:04:47 +01:00
parent 6e904b602c
commit faa76d372c
8 changed files with 411 additions and 158 deletions

View File

@ -1,3 +1,17 @@
/*
* SPDX-FileCopyrightText: 2018 yuzu Emulator Project
* SPDX-License-Identifier: GPL-2.0-or-later
*/
QWidget:item:hover {
background-color: #18465d;
color: #eff0f1;
}
QWidget:item:selected {
background-color: #18465d;
}
QAbstractSpinBox {
min-height: 19px;
}
@ -94,21 +108,21 @@ QGroupBox#groupPlayer5Connected:checked,
QGroupBox#groupPlayer6Connected:checked,
QGroupBox#groupPlayer7Connected:checked,
QGroupBox#groupPlayer8Connected:checked {
background-color: #f5f5f5;
background-color: palette(window);
}
QWidget#topControllerApplet {
border-bottom: 1px solid #828790
border-bottom: 1px solid palette(dark)
}
QWidget#bottomPerGameInput,
QWidget#bottomControllerApplet {
border-top: 1px solid #828790
border-top: 1px solid palette(dark)
}
QWidget#topPerGameInput,
QWidget#middleControllerApplet {
background-color: #fff;
background-color: palette(base)
}
QWidget#topPerGameInput QComboBox,
@ -345,7 +359,7 @@ QWidget#lineDialog {
QStackedWidget#bottomOSK,
QWidget#contentDialog,
QWidget#contentRichDialog {
background: rgba(240, 240, 240, 1);
background: palette(base);
}
QWidget#contentDialog,
@ -402,6 +416,7 @@ QWidget#inputOSK QLineEdit {
background: transparent;
border: none;
color: #ccc;
padding: 0px;
}
QWidget#inputBoxOSK {
@ -431,6 +446,27 @@ QWidget#boxOSK QLabel#label_characters_box {
color: #ccc;
}
QWidget#buttonsDialog,
QWidget#buttonsRichDialog,
QWidget#mainOSK,
QWidget#headerOSK,
QWidget#normalOSK,
QWidget#shiftOSK,
QWidget#numOSK,
QWidget#subOSK,
QWidget#inputOSK,
QWidget#inputBoxOSK,
QWidget#charactersOSK,
QWidget#charactersBoxOSK,
QWidget#legendOSK,
QWidget#legendOSK QWidget,
QWidget#legendOSKshift,
QWidget#legendOSKshift QWidget,
QWidget#legendOSKnum,
QWidget#legendOSKnum QWidget {
background: transparent;
}
QWidget#contentDialog QLabel#label_title,
QWidget#contentRichDialog QLabel#label_title_rich {
color: #888;
@ -471,8 +507,8 @@ QDialog#OverlayDialog QPushButton:pressed {
}
QDialog#QtSoftwareKeyboardDialog QPushButton {
background: rgba(232, 232, 232, 1);
border: 2px solid rgba(240, 240, 240, 1);
background: palette(window);
border: 2px solid palette(base);
}
QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift,
@ -481,27 +517,35 @@ QDialog#QtSoftwareKeyboardDialog QPushButton#button_space,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift {
background: rgba(218, 218, 218, 1);
border: 2px solid rgba(240, 240, 240, 1);
background: palette(alternate-base);
border: 2px solid palette(base);
}
QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num {
color: rgba(240, 240, 240, 1);
background: rgba(44, 44, 44, 1);
border: 2px solid rgba(240, 240, 240, 1);
color: palette(base);
background: palette(mid);
border: 2px solid palette(base);
}
QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num {
color: rgba(240, 240, 240, 1);
background: rgba(49, 79, 239, 1);
border: 2px solid rgba(240, 240, 240, 1);
color: palette(base);
background: palette(highlight);
border: 2px solid palette(base);
}
QDialog#QtSoftwareKeyboardDialog QPushButton:focus,
QDialog#QtSoftwareKeyboardDialog QPushButton:hover
{
background: palette(base);
border: 5px solid rgba(148, 250, 202, 1);
border-radius: 6px;
outline: none;
}
QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:focus,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:focus,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:focus,
@ -514,8 +558,6 @@ QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:focus,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:focus,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:focus,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:focus,
QDialog#QtSoftwareKeyboardDialog QPushButton:hover,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:hover,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift:hover,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_space:hover,
@ -524,12 +566,11 @@ QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace:hover,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_shift:hover,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_shift_shift:hover,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_space_shift:hover,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:hover,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:hover,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:hover,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:hover {
color: rgba(0, 0, 0, 1);
background: rgba(255, 255, 255, 1);
QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:hover,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:hover
{
border: 5px solid rgba(148, 250, 202, 1);
border-radius: 6px;
outline: none;
@ -548,7 +589,7 @@ QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:pressed,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:pressed,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:pressed,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:pressed {
color: rgba(240, 240, 240, 1);
color: palette(base);
background: rgba(150, 150, 150, 1);
border: 5px solid rgba(148, 250, 202, 1);
border-radius: 6px;
@ -653,8 +694,8 @@ QDialog#QtSoftwareKeyboardDialog QPushButton#button_return_shift:disabled,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_shift:disabled,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok_num:disabled,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_backspace_num:disabled {
color: rgba(164, 164, 164, 1);
background-color: rgba(218, 218, 218, 1);
color: palette(midlight);
background-color: palette(alternate-base);
}
QDialog#QtSoftwareKeyboardDialog QPushButton#button_at:disabled,
@ -671,7 +712,7 @@ QDialog#QtSoftwareKeyboardDialog QPushButton#button_8:disabled,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_9:disabled,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_0:disabled,
QDialog#QtSoftwareKeyboardDialog QPushButton#button_return:disabled {
color: rgba(164, 164, 164, 1);
color: palette(midlight);
}
QDialog#QtSoftwareKeyboardDialog QPushButton#button_ok:disabled,

View File

@ -384,10 +384,12 @@ bool QtControllerSelectorDialog::CheckIfParametersMet() {
void QtControllerSelectorDialog::SetSupportedControllers() {
const QString theme = [] {
if (QIcon::themeName().contains(QStringLiteral("dark"))) {
return QStringLiteral("_dark");
} else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
return QStringLiteral("_midnight");
} else if (GMainWindow::CheckDarkMode() ||
QIcon::themeName().contains(QStringLiteral("dark"))) {
// Use dark icons if current OS mode is dark, or the theme contains "dark" in its name
return QStringLiteral("_dark");
} else {
return QString{};
}
@ -572,10 +574,12 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index)
}
const QString theme = [] {
if (QIcon::themeName().contains(QStringLiteral("dark"))) {
return QStringLiteral("_dark");
} else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
return QStringLiteral("_midnight");
} else if (GMainWindow::CheckDarkMode() ||
QIcon::themeName().contains(QStringLiteral("dark"))) {
// Use dark icons if current OS mode is dark, or the theme contains "dark" in its name
return QStringLiteral("_dark");
} else {
return QString{};
}

View File

@ -823,7 +823,9 @@ void QtSoftwareKeyboardDialog::SetControllerImage() {
handheld->IsConnected() ? handheld->GetNpadStyleIndex() : player_1->GetNpadStyleIndex();
const QString theme = [] {
if (QIcon::themeName().contains(QStringLiteral("dark")) ||
// Use dark icons if current OS mode is dark, or the theme contains "dark", or "midnight" in
// its name
if (GMainWindow::CheckDarkMode() || QIcon::themeName().contains(QStringLiteral("dark")) ||
QIcon::themeName().contains(QStringLiteral("midnight"))) {
return QStringLiteral("_dark");
} else {

View File

@ -9,7 +9,9 @@
#include <fstream>
#include <iostream>
#include <memory>
#include <QStyleFactory>
#include <thread>
#include "core/hle/service/am/applet_manager.h"
#include "core/loader/nca.h"
#include "core/loader/nro.h"
@ -20,6 +22,9 @@
#endif
#ifdef __unix__
#include <csignal>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QDBusMessage>
#include <QtDBus/QtDBus>
#include <sys/socket.h>
#include "common/linux/gamemode.h"
#endif
@ -271,18 +276,6 @@ static void OverrideWindowsFont() {
}
#endif
bool GMainWindow::CheckDarkMode() {
#ifdef __unix__
const QPalette test_palette(qApp->palette());
const QColor text_color = test_palette.color(QPalette::Active, QPalette::Text);
const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window);
return (text_color.value() > window_color.value());
#else
// TODO: Windows
return false;
#endif // __unix__
}
GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulkan)
: ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()},
input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::move(config_)},
@ -303,8 +296,6 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
ui->setupUi(this);
statusBar()->hide();
// Check dark mode before a theme is loaded
os_dark_mode = CheckDarkMode();
startup_icon_theme = QIcon::themeName();
// fallback can only be set once, colorful theme icons are okay on both light/dark
QIcon::setFallbackThemeName(QStringLiteral("colorful"));
@ -329,6 +320,7 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
SetDefaultUIGeometry();
RestoreUIState();
UpdateUITheme();
ConnectMenuEvents();
ConnectWidgetEvents();
@ -449,7 +441,10 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
SDL_EnableScreenSaver();
#endif
#ifdef __unix__
SetupPrepareForSleep();
ListenColorSchemeChange();
#endif
QStringList args = QApplication::arguments();
@ -1647,8 +1642,8 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
}
}
void GMainWindow::SetupPrepareForSleep() {
#ifdef __unix__
void GMainWindow::SetupPrepareForSleep() {
auto bus = QDBusConnection::systemBus();
if (bus.isConnected()) {
const bool success = bus.connect(
@ -1662,8 +1657,8 @@ void GMainWindow::SetupPrepareForSleep() {
} else {
LOG_WARNING(Frontend, "QDBusConnection system bus is not connected");
}
#endif // __unix__
}
#endif // __unix__
void GMainWindow::OnPrepareForSleep(bool prepare_sleep) {
if (emu_thread == nullptr) {
@ -4799,9 +4794,100 @@ void GMainWindow::filterBarSetChecked(bool state) {
emit(OnToggleFilterBar());
}
void GMainWindow::UpdateUITheme() {
LOG_DEBUG(Frontend, "Updating UI");
QString default_theme = QString::fromStdString(UISettings::default_theme.data());
QString current_theme = UISettings::values.theme;
if (current_theme.isEmpty()) {
current_theme = default_theme;
}
const bool current_dark_mode = CheckDarkMode();
UpdateIcons(current_theme);
/* Find the stylesheet to load */
if (TryLoadStylesheet(current_theme)) {
return;
}
// Reading new theme failed, loading default stylesheet
LOG_ERROR(Frontend, "Unable to open style \"{}\", fallback to the default theme",
current_theme.toStdString());
if (TryLoadStylesheet(QStringLiteral(":/%1/").arg(default_theme))) {
return;
}
// Reading default failed, loading empty stylesheet
LOG_ERROR(Frontend, "Unable to set default style, stylesheet file not found");
qApp->setStyleSheet({});
setStyleSheet({});
}
void GMainWindow::UpdateIcons(const QString& theme_used) {
// Append _dark to the theme name to use dark variant icons
if (CheckDarkMode()) {
QIcon::setThemeName(theme_used + QStringLiteral("_dark"));
} else {
QIcon::setThemeName(theme_used);
}
const QString theme_directory{
QString::fromStdString(Common::FS::GetSuyuPathString(Common::FS::SuyuPath::ThemesDir))};
// Set path for default icons
// Use icon resources from application binary and current theme local subdirectory, if it exists
QStringList theme_paths;
theme_paths << QString::fromStdString(":/icons") << QStringLiteral("%1").arg(theme_directory);
QIcon::setThemeSearchPaths(theme_paths);
// Change current directory, to allow user themes to use their own icons
QDir::setCurrent(QStringLiteral("%1/%2").arg(theme_directory, UISettings::values.theme));
emit UpdateThemedIcons();
}
bool GMainWindow::TryLoadStylesheet(const QString& theme_uri) {
QString style_path;
// Use themed stylesheet if it exists
if (CheckDarkMode()) {
style_path = theme_uri + QStringLiteral("dark.qss");
} else {
style_path = theme_uri + QStringLiteral("light.qss");
}
if (!QFile::exists(style_path)) {
LOG_INFO(Frontend, "Themed (light/dark) stylesheet could not be found, using default one");
// Use common stylesheet if themed one does not exist
style_path = theme_uri + QStringLiteral("style.qss");
}
// Loading stylesheet
QFile style_file(style_path);
if (style_file.open(QFile::ReadOnly | QFile::Text)) {
// Update the color palette before applying the stylesheet
UpdateThemePalette();
LOG_INFO(Frontend, "Loading stylesheet in: {}", theme_uri.toStdString());
QTextStream ts_theme(&style_file);
qApp->setStyleSheet(ts_theme.readAll());
setStyleSheet(ts_theme.readAll());
SetCustomStylesheet();
return true;
}
// Opening the file failed
return false;
}
bool GMainWindow::TryLoadStylesheet(const std::filesystem::path& theme_path) {
return TryLoadStylesheet(QString::fromStdString(theme_path.string() + "/"));
}
static void AdjustLinkColor() {
QPalette new_pal(qApp->palette());
if (UISettings::IsDarkTheme()) {
if (GMainWindow::CheckDarkMode()) {
new_pal.setColor(QPalette::Link, QColor(0, 190, 255, 255));
} else {
new_pal.setColor(QPalette::Link, QColor(0, 140, 200, 255));
@ -4811,77 +4897,201 @@ static void AdjustLinkColor() {
}
}
void GMainWindow::UpdateUITheme() {
QString default_theme = QString::fromStdString(UISettings::default_theme.data());
QString current_theme = UISettings::values.theme;
if (current_theme.isEmpty()) {
current_theme = default_theme;
}
void GMainWindow::UpdateThemePalette() {
QPalette themePalette(qApp->palette());
#ifdef _WIN32
QIcon::setThemeName(current_theme);
AdjustLinkColor();
#else
if (current_theme == QStringLiteral("default") || current_theme == QStringLiteral("colorful")) {
LOG_INFO(Frontend, "Theme is default or colorful: {}", current_theme.toStdString());
QIcon::setThemeName(current_theme == QStringLiteral("colorful") ? current_theme
: startup_icon_theme);
QIcon::setThemeSearchPaths(QStringList(default_theme_paths));
if (CheckDarkMode()) {
current_theme = QStringLiteral("default_dark");
QColor dark(25, 25, 25);
QColor darkGray(100, 100, 100);
QColor gray(150, 150, 150);
QColor light(230, 230, 230);
// By default, revert fusion style set for Windows dark theme
QString style;
if (CheckDarkMode()) {
// AlternateBase is kept at rgb(233, 231, 227) or rgb(245, 245, 245) on Windows dark
// palette, fix this. Sometimes, it even is rgb(0, 0, 0), but uses a very light gray for
// alternate rows, do not know why
if (themePalette.alternateBase().color() == QColor(233, 231, 227) ||
themePalette.alternateBase().color() == QColor(245, 245, 245) ||
themePalette.alternateBase().color() == QColor(0, 0, 0)) {
themePalette.setColor(QPalette::AlternateBase, dark);
alternate_base_modified = true;
}
// Use fusion theme, since its close to windowsvista, but works well with a dark palette
style = QStringLiteral("fusion");
} else {
LOG_INFO(Frontend, "Theme is NOT default or colorful: {}", current_theme.toStdString());
QIcon::setThemeName(current_theme);
// Use icon resources from application binary and current theme subdirectory if it exists
QStringList theme_paths;
theme_paths << QString::fromStdString(":/icons")
<< QStringLiteral("%1/%2/icons")
.arg(QString::fromStdString(
Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ThemesDir)),
current_theme);
QIcon::setThemeSearchPaths(theme_paths);
AdjustLinkColor();
// Reset AlternateBase if it has been modified
if (alternate_base_modified) {
themePalette.setColor(QPalette::AlternateBase, QColor(245, 245, 245));
alternate_base_modified = false;
}
// Reset Windows theme to the default
style = QStringLiteral("windowsvista");
}
LOG_DEBUG(Frontend, "Using style: {}", style.toStdString());
qApp->setStyle(style);
#else
if (CheckDarkMode()) {
// Set Dark palette on non Windows platforms (that may not have a dark palette)
LOG_INFO(Frontend, "Using custom dark palette");
themePalette.setColor(QPalette::Window, QColor(53, 53, 53));
themePalette.setColor(QPalette::WindowText, Qt::white);
themePalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127));
themePalette.setColor(QPalette::Base, QColor(42, 42, 42));
themePalette.setColor(QPalette::AlternateBase, QColor(66, 66, 66));
themePalette.setColor(QPalette::ToolTipBase, Qt::white);
themePalette.setColor(QPalette::ToolTipText, QColor(53, 53, 53));
themePalette.setColor(QPalette::Text, Qt::white);
themePalette.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127));
themePalette.setColor(QPalette::Dark, QColor(35, 35, 35));
themePalette.setColor(QPalette::Shadow, QColor(20, 20, 20));
themePalette.setColor(QPalette::Button, QColor(53, 53, 53));
themePalette.setColor(QPalette::ButtonText, Qt::white);
themePalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127));
themePalette.setColor(QPalette::BrightText, Qt::red);
themePalette.setColor(QPalette::Link, QColor(42, 130, 218));
themePalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
themePalette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80));
themePalette.setColor(QPalette::HighlightedText, Qt::white);
themePalette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(127, 127, 127));
} else {
LOG_INFO(Frontend, "Using standard palette");
// Reset light palette on non Windows platforms
themePalette = this->style()->standardPalette();
}
#endif
if (current_theme != default_theme) {
QString theme_uri{current_theme + QStringLiteral("style.qss")};
if (tryLoadStylesheet(theme_uri)) {
return;
}
// Reading new theme failed, loading default stylesheet
LOG_ERROR(Frontend, "Unable to open style \"{}\", fallback to the default theme",
current_theme.toStdString());
current_theme = default_theme;
theme_uri = QStringLiteral(":%1/style.qss").arg(default_theme);
if (tryLoadStylesheet(theme_uri)) {
return;
}
// Reading default failed, loading empty stylesheet
LOG_ERROR(Frontend, "Unable to set style \"{}\", stylesheet file not found",
current_theme.toStdString());
qApp->setStyleSheet({});
setStyleSheet({});
}
qApp->setPalette(themePalette);
AdjustLinkColor();
}
bool GMainWindow::tryLoadStylesheet(const QString& theme_path) {
QFile theme_file(theme_path);
if (theme_file.open(QFile::ReadOnly | QFile::Text)) {
LOG_INFO(Frontend, "Loading style in: {}", theme_path.toStdString());
QTextStream ts(&theme_file);
qApp->setStyleSheet(ts.readAll());
setStyleSheet(ts.readAll());
return true;
void GMainWindow::SetCustomStylesheet() {
setStyleSheet(QStringLiteral("QStatusBar::item { border: none; }"));
// Set "dark" qss property value, that may be used in stylesheets
bool is_dark_mode = CheckDarkMode();
if (renderer_status_button) {
renderer_status_button->setProperty("dark", is_dark_mode);
}
// Opening the file failed
if (gpu_accuracy_button) {
gpu_accuracy_button->setProperty("dark", is_dark_mode);
}
#ifdef _WIN32
// Windows dark mode uses "fusion" style. Make it look like more "windowsvista" light style
if (is_dark_mode) {
/* the groove expands to the size of the slider by default. by giving it a height, it has a
fixed size */
/* handle is placed by default on the contents rect of the groove. Negative margin expands
it outside the groove */
setStyleSheet(QStringLiteral("QSlider:horizontal{ height:30px; }\
QSlider::sub-page:horizontal { background-color: palette(highlight); }\
QSlider::add-page:horizontal { background-color: palette(midlight);}\
QSlider::groove:horizontal { border-width: 1px; margin: 1px 0; height: 2px;}\
QSlider::handle:horizontal { border-width: 1px; border-style: solid; border-color: palette(dark);\
width: 10px; margin: -10px 0px; }\
QSlider::handle { background-color: palette(button); }\
QSlider::handle:hover { background-color: palette(highlight); }"));
}
#endif
}
#ifdef __unix__
bool GMainWindow::ListenColorSchemeChange() {
auto bus = QDBusConnection::sessionBus();
if (bus.isConnected()) {
const QString dbus_service = QStringLiteral("org.freedesktop.portal.Desktop");
const QString dbus_path = QStringLiteral("/org/freedesktop/portal/desktop");
const QString dbus_interface = QStringLiteral("org.freedesktop.portal.Settings");
const QString dbus_method = QStringLiteral("SettingChanged");
QStringList dbus_arguments;
dbus_arguments << QStringLiteral("org.freedesktop.appearance")
<< QStringLiteral("color-scheme");
const QString dbus_signature = QStringLiteral("ssv");
LOG_INFO(Frontend, "Connected to DBus, listening for OS theme changes");
return bus.connect(dbus_service, dbus_path, dbus_interface, dbus_method, dbus_arguments,
dbus_signature, this, SLOT(UpdateUITheme()));
}
LOG_WARNING(Frontend, "Unable to connect to DBus to listen for OS theme changes");
return false;
}
#endif
bool GMainWindow::CheckDarkMode() {
const QPalette current_palette(qApp->palette());
#ifdef __unix__
QProcess process;
QStringList gdbus_arguments;
// Using the freedesktop specifications for checking dark mode
LOG_INFO(Frontend, "Retrieving theme from freedesktop color-scheme...");
gdbus_arguments << QStringLiteral("--dest=org.freedesktop.portal.Desktop")
<< QStringLiteral("--object-path /org/freedesktop/portal/desktop")
<< QStringLiteral("--method org.freedesktop.portal.Settings.Read "
"org.freedesktop.appearance color-scheme");
process.start(QStringLiteral("gdbus call --session"), gdbus_arguments);
process.waitForFinished(1000);
QByteArray dbus_output = process.readAllStandardOutput();
if (!dbus_output.isEmpty()) {
const int systemColorSchema = QString::fromUtf8(dbus_output).trimmed().right(1).toInt();
return systemColorSchema == 1;
}
// Try alternative for Gnome if the previous one failed
QStringList gsettings_arguments;
gsettings_arguments << QStringLiteral("get")
<< QStringLiteral("org.gnome.desktop.interface")
<< QStringLiteral("color-scheme");
LOG_DEBUG(Frontend, "failed, retrieving theme from gsettings color-scheme...");
process.start(QStringLiteral("gsettings"), gsettings_arguments);
process.waitForFinished(1000);
QByteArray gsettings_output = process.readAllStandardOutput();
// Try older gtk-theme method if the previous one failed
if (gsettings_output.isEmpty()) {
LOG_INFO(Frontend, "failed, retrieving theme from gtk-theme...");
gsettings_arguments.takeLast();
gsettings_arguments << QStringLiteral("gtk-theme");
process.start(QStringLiteral("gsettings"), gsettings_arguments);
process.waitForFinished(1000);
gsettings_output = process.readAllStandardOutput();
}
// Interpret gsettings value if it succeeded
if (!gsettings_output.isEmpty()) {
QString systeme_theme = QString::fromUtf8(gsettings_output);
LOG_DEBUG(Frontend, "Gsettings output: {}", systeme_theme.toStdString());
return systeme_theme.contains(QStringLiteral("dark"), Qt::CaseInsensitive);
}
LOG_DEBUG(Frontend, "failed, retrieving theme from palette");
#endif
// Use default method based on palette swap by OS.
// It is the only method on Windows with Qt 5.
// Windows needs QT_QPA_PLATFORM env variable set to windows:darkmode=2 to force palette change
return (current_palette.color(QPalette::WindowText).lightness() >
current_palette.color(QPalette::Window).lightness());
}
void GMainWindow::changeEvent(QEvent* event) {
// PaletteChange event appears to only reach so far into the GUI, explicitly asking to
// UpdateUITheme is a decent work around
if (event->type() == QEvent::PaletteChange ||
event->type() == QEvent::ApplicationPaletteChange) {
LOG_INFO(Frontend,
"Window color palette changed by event: {} (QEvent::PaletteChange is: {})",
event->type(), QEvent::PaletteChange);
const QPalette test_palette(qApp->palette());
// Keeping eye on QPalette::Window to avoid looping. QPalette::Text might be useful too
const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window);
if (last_window_color != window_color) {
last_window_color = window_color;
UpdateUITheme();
}
} else QWidget::changeEvent(event);
}
void GMainWindow::LoadTranslation() {
bool loaded;
@ -4935,26 +5145,6 @@ void GMainWindow::SetGamemodeEnabled(bool state) {
}
#endif
void GMainWindow::changeEvent(QEvent* event) {
#ifdef __unix__
// PaletteChange event appears to only reach so far into the GUI, explicitly asking to
// UpdateUITheme is a decent work around
if (event->type() == QEvent::PaletteChange) {
const QPalette test_palette(qApp->palette());
const QString& current_theme = UISettings::values.theme;
// Keeping eye on QPalette::Window to avoid looping. QPalette::Text might be useful too
static QColor last_window_color;
const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window);
if (last_window_color != window_color && (current_theme == QStringLiteral("default") ||
current_theme == QStringLiteral("colorful"))) {
UpdateUITheme();
}
last_window_color = window_color;
}
#endif // __unix__
QWidget::changeEvent(event);
}
Service::AM::FrontendAppletParameters GMainWindow::ApplicationAppletParameters() {
return Service::AM::FrontendAppletParameters{
.applet_id = Service::AM::AppletId::Application,

View File

@ -25,9 +25,8 @@
#include "suyu/util/controller_navigation.h"
#ifdef __unix__
#include <QSocketNotifier>
#include <QVariant>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QtDBus>
#endif
class QtConfig;
@ -165,14 +164,9 @@ class GMainWindow : public QMainWindow {
CREATE_SHORTCUT_MSGBOX_APPVOLATILE_WARNING,
};
/**
* Try to load a stylesheet from its path. If the path starts with ":/", its embedded in the app
* @returns true if the text file could be opened as read-only
*/
bool tryLoadStylesheet(const QString& theme_path);
public:
void filterBarSetChecked(bool state);
static bool CheckDarkMode();
void UpdateUITheme();
explicit GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulkan);
~GMainWindow() override;
@ -265,12 +259,44 @@ private:
void SetDefaultUIGeometry();
void RestoreUIState();
/**
* Load the icons used by the current theme. Use dark icons if the current mode is dark
*/
void UpdateIcons(const QString& theme_used);
/**
* Set the palette used by the stylsheet for the dark/light mode selected, according to the OS
*/
void UpdateThemePalette();
/**
* Try to load a stylesheet from its URI.
* If the path starts with ":/", its embedded in the app, otherwise its in a local directory
* @returns true if the text file could be opened as read-only
*/
bool TryLoadStylesheet(const QString& theme_uri);
/**
* Try to load a stylesheet from filesystem path
* @returns true if the text file could be opened as read-only
*/
bool TryLoadStylesheet(const std::filesystem::path& theme_path);
/**
* Default customizations to the stylesheets
*/
void SetCustomStylesheet();
#ifdef __unix__
/**
* Create a signal to update the UI theme when the OS color scheme is changed
* @returns true if we could connect to dbus
*/
bool ListenColorSchemeChange();
#endif
void ConnectWidgetEvents();
void ConnectMenuEvents();
void UpdateMenuState();
#ifdef __unix__
void SetupPrepareForSleep();
#endif
void PreventOSSleep();
void AllowOSSleep();
@ -401,6 +427,7 @@ private slots:
void ResetWindowSize720();
void ResetWindowSize900();
void ResetWindowSize1080();
void UpdateUITheme();
void OnAlbum();
void OnCabinet(Service::NFP::CabinetMode mode);
void OnMiiEdit();
@ -447,7 +474,7 @@ private:
void OpenURL(const QUrl& url);
void LoadTranslation();
void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
bool CheckDarkMode();
bool CheckSystemArchiveDecryption();
bool CheckFirmwarePresence();
void SetFirmwareVersion();
void ConfigureFilesystemProvider(const std::string& filepath);
@ -531,7 +558,8 @@ private:
QTimer update_input_timer;
QString startup_icon_theme;
bool os_dark_mode = false;
bool alternate_base_modified = false;
QColor last_window_color;
// FS
std::shared_ptr<FileSys::VfsFilesystem> vfs;

View File

@ -5,6 +5,8 @@
#ifdef _WIN32
#include <cstring>
#include <QByteArray>
#include <QtGlobal>
#include <processthreadsapi.h>
#include <windows.h>
#elif defined(SUYU_UNIX)
@ -36,6 +38,9 @@ void CheckVulkan() {
bool CheckEnvVars(bool* is_child) {
#ifdef _WIN32
// Force adapting theme to follow Windows dark mode
qputenv("QT_QPA_PLATFORM", QByteArray("windows:darkmode=2"));
// Check environment variable to see if we are the child
char variable_contents[8];
const DWORD startup_check_var =

View File

@ -31,11 +31,6 @@ const Themes included_themes{{
{"Midnight Blue Colorful", ":/colorful_midnight_blue/"},
}};
bool IsDarkTheme() {
return UISettings::values.theme.contains(QStringLiteral("dark")) ||
UISettings::values.theme.contains(QStringLiteral("midnight"));
}
Values values = {};
u32 CalculateWidth(u32 height, Settings::AspectRatio ratio) {

View File

@ -35,12 +35,6 @@ extern template class Setting<unsigned long long>;
namespace UISettings {
/**
* Check if the theme is dark
* @returns true if the current theme contains the string "dark" in its name
*/
bool IsDarkTheme();
struct ContextualShortcut {
std::string keyseq;
std::string controller_keyseq;
@ -54,13 +48,7 @@ struct Shortcut {
ContextualShortcut shortcut;
};
static constexpr std::string_view default_theme{
#ifdef _WIN32
"colorful_dark"
#else
"colorful"
#endif
};
static constexpr std::string_view default_theme{"colorful"};
using Themes = std::array<std::pair<const char*, const char*>, 6>;
extern const Themes included_themes;