diff --git a/Source/Core/DolphinNoGUI/PlatformWin32.cpp b/Source/Core/DolphinNoGUI/PlatformWin32.cpp index d08bb7e004..78a9cd783d 100644 --- a/Source/Core/DolphinNoGUI/PlatformWin32.cpp +++ b/Source/Core/DolphinNoGUI/PlatformWin32.cpp @@ -180,6 +180,17 @@ LRESULT PlatformWin32::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(platform)); return DefWindowProc(hwnd, msg, wParam, lParam); } + + case WM_CREATE: + { + if (hwnd) + { + // Remove rounded corners from the render window on Windows 11 + const DWM_WINDOW_CORNER_PREFERENCE corner_preference = DWMWCP_DONOTROUND; + DwmSetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &corner_preference, + sizeof(corner_preference)); + } + } break; case WM_SIZE: diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp index 2ee01b1515..51f2814d9b 100644 --- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp @@ -166,6 +166,11 @@ CodeViewWidget::CodeViewWidget() FontBasedSizing(); +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, + [this](Qt::ColorScheme colorScheme) { OnSelectionChanged(); }); +#endif + connect(this, &CodeViewWidget::customContextMenuRequested, this, &CodeViewWidget::OnContextMenu); connect(this, &CodeViewWidget::itemSelectionChanged, this, &CodeViewWidget::OnSelectionChanged); connect(&Settings::Instance(), &Settings::DebugFontChanged, this, diff --git a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp index 479a3f7f9f..15683c973d 100644 --- a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp @@ -160,6 +160,13 @@ void CodeWidget::CreateWidgets() void CodeWidget::ConnectWidgets() { +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, + [this](Qt::ColorScheme colorScheme) { + m_box_splitter->setStyleSheet(BOX_SPLITTER_STYLESHEET); + }); +#endif + connect(m_search_address, &QLineEdit::textChanged, this, &CodeWidget::OnSearchAddress); connect(m_search_address, &QLineEdit::returnPressed, this, &CodeWidget::OnSearchAddress); connect(m_search_symbols, &QLineEdit::textChanged, this, &CodeWidget::OnSearchSymbols); diff --git a/Source/Core/DolphinQt/Main.cpp b/Source/Core/DolphinQt/Main.cpp index 14f51e3ad4..9262360069 100644 --- a/Source/Core/DolphinQt/Main.cpp +++ b/Source/Core/DolphinQt/Main.cpp @@ -256,6 +256,7 @@ int main(int argc, char* argv[]) Settings::Instance().InitDefaultPalette(); Settings::Instance().UpdateSystemDark(); + Settings::Instance().ApplyStyle(); MainWindow win{std::move(boot), static_cast(options.get("movie"))}; win.Show(); diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 43fc3f17fa..10af87f152 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -241,6 +241,11 @@ MainWindow::MainWindow(std::unique_ptr boot_parameters, ConnectMenuBar(); ConnectHotkeys(); +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, + [](Qt::ColorScheme colorScheme) { Settings::Instance().ApplyStyle(); }); +#endif + connect(m_cheats_manager, &CheatsManager::OpenGeneralSettings, this, &MainWindow::ShowGeneralWindow); @@ -1860,6 +1865,29 @@ QSize MainWindow::sizeHint() const #ifdef _WIN32 bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr* result) { + auto* msg = reinterpret_cast(message); + if (msg && msg->message == WM_SETTINGCHANGE && msg->lParam != NULL && + std::wstring_view(L"ImmersiveColorSet") + .compare(reinterpret_cast(msg->lParam)) == 0) + { + // Windows light/dark theme has changed. Update our flag and refresh the theme. + auto& settings = Settings::Instance(); + const bool was_dark_before = settings.IsSystemDark(); + settings.UpdateSystemDark(); + if (settings.IsSystemDark() != was_dark_before) + { + settings.ApplyStyle(); + + // force the colors in the Skylander window to update + if (m_skylander_window) + m_skylander_window->RefreshList(); + } + + // TODO: When switching from light to dark, the window decorations remain light. Qt seems very + // convinced that it needs to change these in response to this message, so even if we set them + // to dark here, Qt sets them back to light afterwards. + } + return false; } #endif diff --git a/Source/Core/DolphinQt/RenderWidget.cpp b/Source/Core/DolphinQt/RenderWidget.cpp index 6ac5878e54..597ac78e6c 100644 --- a/Source/Core/DolphinQt/RenderWidget.cpp +++ b/Source/Core/DolphinQt/RenderWidget.cpp @@ -136,6 +136,15 @@ void RenderWidget::dropEvent(QDropEvent* event) void RenderWidget::OnHandleChanged(void* handle) { + if (handle) + { +#ifdef _WIN32 + // Remove rounded corners from the render window on Windows 11 + const DWM_WINDOW_CORNER_PREFERENCE corner_preference = DWMWCP_DONOTROUND; + DwmSetWindowAttribute(reinterpret_cast(handle), DWMWA_WINDOW_CORNER_PREFERENCE, + &corner_preference, sizeof(corner_preference)); +#endif + } Host::GetInstance()->SetRenderHandle(handle); } diff --git a/Source/Core/DolphinQt/Settings.cpp b/Source/Core/DolphinQt/Settings.cpp index 61f74d5638..fca24370b7 100644 --- a/Source/Core/DolphinQt/Settings.cpp +++ b/Source/Core/DolphinQt/Settings.cpp @@ -124,6 +124,20 @@ void Settings::SetThemeName(const QString& theme_name) emit ThemeChanged(); } +QString Settings::GetUserStyleName() const +{ + if (GetQSettings().contains(QStringLiteral("userstyle/name"))) + return GetQSettings().value(QStringLiteral("userstyle/name")).toString(); + + // Migration code for the old way of storing this setting + return QFileInfo(GetQSettings().value(QStringLiteral("userstyle/path")).toString()).fileName(); +} + +void Settings::SetUserStyleName(const QString& stylesheet_name) +{ + GetQSettings().setValue(QStringLiteral("userstyle/name"), stylesheet_name); +} + void Settings::InitDefaultPalette() { s_default_palette = std::make_unique(qApp->palette()); @@ -158,7 +172,111 @@ bool Settings::IsSystemDark() bool Settings::IsThemeDark() { return qApp->palette().color(QPalette::Base).valueF() < 0.5; +} +// Calling this before the main window has been created breaks the style of some widgets. +void Settings::ApplyStyle() +{ + const StyleType style_type = GetStyleType(); + const QString stylesheet_name = GetUserStyleName(); + QString stylesheet_contents; + + // If we haven't found one, we continue with an empty (default) style + if (!stylesheet_name.isEmpty() && style_type == StyleType::User) + { + // Load custom user stylesheet + QDir directory = QDir(QString::fromStdString(File::GetUserPath(D_STYLES_IDX))); + QFile stylesheet(directory.filePath(stylesheet_name)); + + if (stylesheet.open(QFile::ReadOnly)) + stylesheet_contents = QString::fromUtf8(stylesheet.readAll().data()); + } + +#ifdef _WIN32 + if (stylesheet_contents.isEmpty()) + { + // No theme selected or found. Usually we would just fallthrough and set an empty stylesheet + // which would select Qt's default theme, but unlike other OSes we don't automatically get a + // default dark theme on Windows when the user has selected dark mode in the Windows settings. + // So manually check if the user wants dark mode and, if yes, load our embedded dark theme. + if (style_type == StyleType::Dark || (style_type != StyleType::Light && IsSystemDark())) + { + QFile file(QStringLiteral(":/dolphin_dark_win/dark.qss")); + if (file.open(QFile::ReadOnly)) + stylesheet_contents = QString::fromUtf8(file.readAll().data()); + + QPalette palette = qApp->style()->standardPalette(); + palette.setColor(QPalette::Window, QColor(32, 32, 32)); + palette.setColor(QPalette::WindowText, QColor(220, 220, 220)); + palette.setColor(QPalette::Base, QColor(32, 32, 32)); + palette.setColor(QPalette::AlternateBase, QColor(48, 48, 48)); + palette.setColor(QPalette::PlaceholderText, QColor(126, 126, 126)); + palette.setColor(QPalette::Text, QColor(220, 220, 220)); + palette.setColor(QPalette::Button, QColor(48, 48, 48)); + palette.setColor(QPalette::ButtonText, QColor(220, 220, 220)); + palette.setColor(QPalette::BrightText, QColor(255, 255, 255)); + palette.setColor(QPalette::Highlight, QColor(0, 120, 215)); + palette.setColor(QPalette::HighlightedText, QColor(255, 255, 255)); + palette.setColor(QPalette::Link, QColor(100, 160, 220)); + palette.setColor(QPalette::LinkVisited, QColor(100, 160, 220)); + qApp->setPalette(palette); + } + else + { + // reset any palette changes that may exist from a previously set dark mode + if (s_default_palette) + qApp->setPalette(*s_default_palette); + } + } +#endif + + // Define tooltips style if not already defined + if (!stylesheet_contents.contains(QStringLiteral("QToolTip"), Qt::CaseSensitive)) + { + const QPalette& palette = qApp->palette(); + QColor window_color; + QColor text_color; + QColor unused_text_emphasis_color; + QColor border_color; + GetToolTipStyle(window_color, text_color, unused_text_emphasis_color, border_color, palette, + palette); + + const auto tooltip_stylesheet = + QStringLiteral("QToolTip { background-color: #%1; color: #%2; padding: 8px; " + "border: 1px; border-style: solid; border-color: #%3; }") + .arg(window_color.rgba(), 0, 16) + .arg(text_color.rgba(), 0, 16) + .arg(border_color.rgba(), 0, 16); + stylesheet_contents.append(QStringLiteral("%1").arg(tooltip_stylesheet)); + } + + qApp->setStyleSheet(stylesheet_contents); +} + +Settings::StyleType Settings::GetStyleType() const +{ + if (GetQSettings().contains(QStringLiteral("userstyle/styletype"))) + { + bool ok = false; + const int type_int = GetQSettings().value(QStringLiteral("userstyle/styletype")).toInt(&ok); + if (ok && type_int >= static_cast(StyleType::MinValue) && + type_int <= static_cast(StyleType::MaxValue)) + { + return static_cast(type_int); + } + } + + // if the style type is unset or invalid, try the old enabled flag instead + const bool enabled = GetQSettings().value(QStringLiteral("userstyle/enabled"), false).toBool(); + return enabled ? StyleType::User : StyleType::System; +} + +void Settings::SetStyleType(StyleType type) +{ + GetQSettings().setValue(QStringLiteral("userstyle/styletype"), static_cast(type)); + + // also set the old setting so that the config is correctly intepreted by older Dolphin builds + GetQSettings().setValue(QStringLiteral("userstyle/enabled"), type == StyleType::User); } void Settings::GetToolTipStyle(QColor& window_color, QColor& text_color, diff --git a/Source/Core/DolphinQt/Settings/GeneralPane.cpp b/Source/Core/DolphinQt/Settings/GeneralPane.cpp index 40df2aec8f..e7ab58cc1d 100644 --- a/Source/Core/DolphinQt/Settings/GeneralPane.cpp +++ b/Source/Core/DolphinQt/Settings/GeneralPane.cpp @@ -68,8 +68,6 @@ void GeneralPane::CreateLayout() // Create layout here CreateBasic(); - - CreateFallbackRegion(); #if defined(USE_ANALYTICS) && USE_ANALYTICS @@ -303,6 +301,11 @@ void GeneralPane::OnSaveConfig() Config::ConfigChangeCallbackGuard config_guard; auto& settings = SConfig::GetInstance(); + if (AutoUpdateChecker::SystemSupportsAutoUpdates()) + { + Settings::Instance().SetAutoUpdateTrack( + UpdateTrackFromIndex(m_combobox_update_track->currentIndex())); + } #ifdef USE_DISCORD_PRESENCE Discord::SetDiscordPresenceEnabled(m_checkbox_discord_presence->isChecked()); diff --git a/Source/Core/DolphinQt/Settings/InterfacePane.cpp b/Source/Core/DolphinQt/Settings/InterfacePane.cpp index 24351b654e..08137f68b2 100644 --- a/Source/Core/DolphinQt/Settings/InterfacePane.cpp +++ b/Source/Core/DolphinQt/Settings/InterfacePane.cpp @@ -131,6 +131,21 @@ void InterfacePane::CreateUI() m_combobox_theme->addItem(qt_name); } + // User Style Combobox + m_combobox_userstyle = new QComboBox; + m_label_userstyle = new QLabel(tr("Style:")); + combobox_layout->addRow(m_label_userstyle, m_combobox_userstyle); + + auto userstyle_search_results = Common::DoFileSearch({File::GetUserPath(D_STYLES_IDX)}); + + m_combobox_userstyle->addItem(tr("System"), static_cast(Settings::StyleType::System)); + + for (const std::string& path : userstyle_search_results) + { + const QFileInfo file_info(QString::fromStdString(path)); + m_combobox_userstyle->addItem(file_info.completeBaseName(), file_info.fileName()); + } + // Checkboxes m_checkbox_use_builtin_title_database = new QCheckBox(tr("Use Built-In Database of Game Names")); m_checkbox_use_covers = @@ -208,6 +223,8 @@ void InterfacePane::ConnectLayout() connect(m_combobox_theme, &QComboBox::currentIndexChanged, this, [this](int index) { Settings::Instance().SetThemeName(m_combobox_theme->itemText(index)); }); + connect(m_combobox_userstyle, &QComboBox::currentIndexChanged, this, + &InterfacePane::OnSaveConfig); connect(m_combobox_language, &QComboBox::currentIndexChanged, this, &InterfacePane::OnSaveConfig); connect(m_checkbox_top_window, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); connect(m_checkbox_confirm_on_stop, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig); @@ -253,6 +270,15 @@ void InterfacePane::LoadConfig() ->setCurrentIndex( m_combobox_theme->findText(QString::fromStdString(Config::Get(Config::MAIN_THEME_NAME)))); + const Settings::StyleType style_type = Settings::Instance().GetStyleType(); + const QString userstyle = Settings::Instance().GetUserStyleName(); + const int index = style_type == Settings::StyleType::User ? + m_combobox_userstyle->findData(userstyle) : + m_combobox_userstyle->findData(static_cast(style_type)); + + if (index > 0) + SignalBlocking(m_combobox_userstyle)->setCurrentIndex(index); + // Render Window Options SignalBlocking(m_checkbox_top_window) ->setChecked(Settings::Instance().IsKeepWindowOnTopEnabled()); @@ -283,6 +309,15 @@ void InterfacePane::OnSaveConfig() Config::SetBase(Config::MAIN_USE_BUILT_IN_TITLE_DATABASE, m_checkbox_use_builtin_title_database->isChecked()); Settings::Instance().SetDebugModeEnabled(m_checkbox_show_debugging_ui->isChecked()); + const auto selected_style = m_combobox_userstyle->currentData(); + bool is_builtin_type = false; + const int style_type_int = selected_style.toInt(&is_builtin_type); + Settings::Instance().SetStyleType(is_builtin_type ? + static_cast(style_type_int) : + Settings::StyleType::User); + if (!is_builtin_type) + Settings::Instance().SetUserStyleName(selected_style.toString()); + Settings::Instance().ApplyStyle(); // Render Window Options Settings::Instance().SetKeepWindowOnTop(m_checkbox_top_window->isChecked());