diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 1782f11f5..36f41e818 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -478,6 +478,13 @@ static constexpr const std::array s_ps_button_mapping{ std::make_pair(ICON_PF_RIGHT_TRIGGER_RT, ICON_PF_RIGHT_TRIGGER_R2), }; +static constexpr std::array s_theme_names = {FSUI_NSTR("Automatic"), FSUI_NSTR("Dark"), FSUI_NSTR("Light"), + FSUI_NSTR("AMOLED"), FSUI_NSTR("Cobalt Sky"), FSUI_NSTR("Grey Matter"), + FSUI_NSTR("Pinky Pals"), FSUI_NSTR("Purple Rain")}; + +static constexpr std::array s_theme_values = {"", "Dark", "Light", "AMOLED", + "CobaltSky", "GreyMatter", "PinkyPals", "PurpleRain"}; + ////////////////////////////////////////////////////////////////////////// // State ////////////////////////////////////////////////////////////////////////// @@ -649,6 +656,30 @@ void ImGuiFullscreen::GetInputDialogHelpText(SmallStringBase& dest) } } +std::vector FullscreenUI::GetThemeNames() +{ + std::vector ret; + ret.reserve(std::size(s_theme_names)); + for (const char* name : s_theme_names) + ret.push_back(TRANSLATE_SV("FullscreenUI", name)); + return ret; +} + +std::span FullscreenUI::GetThemeConfigNames() +{ + return s_theme_values; +} + +void FullscreenUI::SetTheme() +{ + TinyString theme = + Host::GetBaseTinyStringSettingValue("UI", "FullscreenUITheme", Host::GetDefaultFullscreenUITheme()); + if (theme.empty()) + theme = Host::GetDefaultFullscreenUITheme(); + + ImGuiFullscreen::SetTheme(theme); +} + ////////////////////////////////////////////////////////////////////////// // Main ////////////////////////////////////////////////////////////////////////// @@ -3701,13 +3732,6 @@ void FullscreenUI::DrawInterfaceSettingsPage() { SettingsInterface* bsi = GetEditingSettingsInterface(); - static constexpr const char* s_theme_name[] = { - FSUI_NSTR("Dark"), FSUI_NSTR("Light"), FSUI_NSTR("AMOLED"), FSUI_NSTR("Cobalt Sky"), - FSUI_NSTR("Grey Matter"), FSUI_NSTR("Pinky Pals"), FSUI_NSTR("Purple Rain")}; - - static constexpr const char* s_theme_value[] = {"Dark", "Light", "AMOLED", "CobaltSky", - "GreyMatter", "PinkyPals", "PurpleRain"}; - BeginMenuButtons(); MenuHeading(FSUI_CSTR("Behavior")); @@ -3782,10 +3806,11 @@ void FullscreenUI::DrawInterfaceSettingsPage() } } - DrawStringListSetting(bsi, FSUI_ICONSTR(ICON_FA_PAINT_BRUSH, "Theme"), - FSUI_CSTR("Selects the color style to be used for Big Picture UI."), "UI", "FullscreenUITheme", - "Dark", s_theme_name, s_theme_value, true, LAYOUT_MENU_BUTTON_HEIGHT, UIStyle.LargeFont, - UIStyle.MediumFont, &ImGuiFullscreen::SetTheme); + DrawStringListSetting( + bsi, FSUI_ICONSTR(ICON_FA_PAINT_BRUSH, "Theme"), + FSUI_CSTR("Selects the color style to be used for Big Picture UI."), "UI", "FullscreenUITheme", "Dark", + s_theme_names, s_theme_values, true, LAYOUT_MENU_BUTTON_HEIGHT, UIStyle.LargeFont, UIStyle.MediumFont, + [](std::string_view) { Host::RunOnCPUThread([]() { GPUThread::RunOnThread(&FullscreenUI::SetTheme); }); }); if (const TinyString current_value = bsi->GetTinyStringValue("Main", "FullscreenUIBackground", DEFAULT_BACKGROUND_NAME); @@ -8750,6 +8775,7 @@ TRANSLATE_NOOP("FullscreenUI", "Audio Backend"); TRANSLATE_NOOP("FullscreenUI", "Audio Control"); TRANSLATE_NOOP("FullscreenUI", "Audio Settings"); TRANSLATE_NOOP("FullscreenUI", "Auto-Detect"); +TRANSLATE_NOOP("FullscreenUI", "Automatic"); TRANSLATE_NOOP("FullscreenUI", "Automatic Mapping"); TRANSLATE_NOOP("FullscreenUI", "Automatic based on window size"); TRANSLATE_NOOP("FullscreenUI", "Automatic mapping completed for {}."); diff --git a/src/core/fullscreen_ui.h b/src/core/fullscreen_ui.h index 0c36f2900..7eda96e1c 100644 --- a/src/core/fullscreen_ui.h +++ b/src/core/fullscreen_ui.h @@ -9,8 +9,10 @@ #include #include +#include #include #include +#include class SmallStringBase; @@ -39,8 +41,13 @@ void UpdateLoadingScreen(std::string_view image, std::string_view message, s32 p s32 progress_value = -1); void CloseLoadingScreen(); +void SetTheme(); + #ifndef __ANDROID__ +std::vector GetThemeNames(); +std::span GetThemeConfigNames(); + void OpenPauseMenu(); void OpenCheatsMenu(); void OpenDiscChangeMenu(); diff --git a/src/duckstation-mini/mini_host.cpp b/src/duckstation-mini/mini_host.cpp index f376772dd..ca610afdc 100644 --- a/src/duckstation-mini/mini_host.cpp +++ b/src/duckstation-mini/mini_host.cpp @@ -1424,6 +1424,11 @@ void Host::OpenHostFileSelectorAsync(std::string_view title, bool select_directo callback(std::string()); } +const char* Host::GetDefaultFullscreenUITheme() +{ + return ""; +} + bool Host::ShouldPreferHostFileSelector() { return false; diff --git a/src/duckstation-qt/graphicssettingswidget.cpp b/src/duckstation-qt/graphicssettingswidget.cpp index 5fbba3311..39f378a45 100644 --- a/src/duckstation-qt/graphicssettingswidget.cpp +++ b/src/duckstation-qt/graphicssettingswidget.cpp @@ -7,6 +7,7 @@ #include "settingwidgetbinder.h" #include "ui_texturereplacementsettingsdialog.h" +#include "core/fullscreen_ui.h" #include "core/game_database.h" #include "core/gpu.h" #include "core/settings.h" @@ -192,6 +193,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.osdScale, "Display", "OSDScale", 100); SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.osdMargin, "Display", "OSDMargin", ImGuiManager::DEFAULT_SCREEN_MARGIN); + SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.fullscreenUITheme, "UI", "FullscreenUITheme"); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showOSDMessages, "Display", "ShowOSDMessages", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showFPS, "Display", "ShowFPS", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showSpeed, "Display", "ShowSpeed", false); @@ -206,6 +208,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showFrameTimes, "Display", "ShowFrameTimes", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showSettings, "Display", "ShowEnhancements", false); + connect(m_ui.fullscreenUITheme, QOverload::of(&QComboBox::currentIndexChanged), g_emu_thread, + &EmuThread::updateFullscreenUITheme); + // Capture Tab SettingWidgetBinder::BindWidgetToEnumSetting( @@ -520,6 +525,8 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* dialog->registerWidgetHelp( m_ui.osdScale, tr("OSD Scale"), tr("100%"), tr("Changes the size at which on-screen elements, including status and messages are displayed.")); + dialog->registerWidgetHelp(m_ui.fullscreenUITheme, tr("Theme"), tr("Automatic"), + tr("Determines the theme to use for on-screen display elements and the Big Picture UI.")); dialog->registerWidgetHelp(m_ui.showOSDMessages, tr("Show OSD Messages"), tr("Checked"), tr("Shows on-screen-display messages when events occur such as save states being " "created/loaded, screenshots being taken, etc.")); @@ -724,6 +731,16 @@ void GraphicsSettingsWidget::setupAdditionalUi() QString::fromUtf8(Settings::GetForceVideoTimingDisplayName(static_cast(i)))); } + // OSD Tab + + const std::vector fsui_theme_names = FullscreenUI::GetThemeNames(); + const std::span fsui_theme_values = FullscreenUI::GetThemeConfigNames(); + for (size_t i = 0; i < fsui_theme_names.size(); i++) + { + m_ui.fullscreenUITheme->addItem(QtUtils::StringViewToQString(fsui_theme_names[i]), + QString::fromUtf8(fsui_theme_values[i])); + } + // Advanced Tab for (u32 i = 0; i < static_cast(DisplayExclusiveFullscreenControl::Count); i++) diff --git a/src/duckstation-qt/graphicssettingswidget.ui b/src/duckstation-qt/graphicssettingswidget.ui index 4c6fe56ef..3acc7548a 100644 --- a/src/duckstation-qt/graphicssettingswidget.ui +++ b/src/duckstation-qt/graphicssettingswidget.ui @@ -680,7 +680,7 @@ - + @@ -785,6 +785,16 @@ + + + + Theme: + + + + + + diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 120254bc7..3d66a8244 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -2238,6 +2238,7 @@ void MainWindow::connectSignals() void MainWindow::updateTheme() { QtHost::UpdateApplicationTheme(); + g_emu_thread->updateFullscreenUITheme(); reloadThemeSpecificImages(); } diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 8d5514e9a..b4a13bc86 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -1857,6 +1857,19 @@ void EmuThread::setGPUThreadRunIdle(bool active) g_emu_thread->startBackgroundControllerPollTimer(); } +void EmuThread::updateFullscreenUITheme() +{ + if (!isCurrentThread()) + { + QMetaObject::invokeMethod(this, &EmuThread::updateFullscreenUITheme, Qt::QueuedConnection); + return; + } + + // don't bother if nothing is running + if (GPUThread::IsFullscreenUIRequested() || GPUThread::IsGPUBackendRequested()) + GPUThread::RunOnThread(&FullscreenUI::SetTheme); +} + void EmuThread::start() { AssertMsg(!g_emu_thread, "Emu thread does not exist"); diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index f416d8a48..1f661150f 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -214,6 +214,7 @@ public Q_SLOTS: void captureGPUFrameDump(); void startControllerTest(); void setGPUThreadRunIdle(bool active); + void updateFullscreenUITheme(); private Q_SLOTS: void stopInThread(); diff --git a/src/duckstation-qt/qtthemes.cpp b/src/duckstation-qt/qtthemes.cpp index 0db844da6..9fe7a57ab 100644 --- a/src/duckstation-qt/qtthemes.cpp +++ b/src/duckstation-qt/qtthemes.cpp @@ -1,9 +1,11 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin and contributors. // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "interfacesettingswidget.h" #include "qthost.h" +#include "util/imgui_fullscreen.h" + #include "common/path.h" #include @@ -40,7 +42,8 @@ void QtHost::UpdateApplicationTheme() void QtHost::SetStyleFromSettings() { - const std::string theme = Host::GetBaseStringSettingValue("UI", "Theme", InterfaceSettingsWidget::DEFAULT_THEME_NAME); + const TinyString theme = + Host::GetBaseTinyStringSettingValue("UI", "Theme", InterfaceSettingsWidget::DEFAULT_THEME_NAME); if (theme == "qdarkstyle") { @@ -199,75 +202,75 @@ void QtHost::SetStyleFromSettings() qApp->setPalette(darkPalette); qApp->setStyleSheet(QString()); } - else if (theme == "pinkypals") - { - qApp->setStyle(QStyleFactory::create("Fusion")); + else if (theme == "pinkypals") + { + qApp->setStyle(QStyleFactory::create("Fusion")); - const QColor black(25, 25, 25); - const QColor pink(255, 174, 201); - const QColor darkerPink(214, 145, 168); - const QColor brightPink(224, 88, 133); - const QColor congoPink(255, 127, 121); + const QColor black(25, 25, 25); + const QColor pink(255, 174, 201); + const QColor darkerPink(214, 145, 168); + const QColor brightPink(224, 88, 133); + const QColor congoPink(255, 127, 121); - QPalette PinkyPalsPalette; - PinkyPalsPalette.setColor(QPalette::Window, pink); - PinkyPalsPalette.setColor(QPalette::WindowText, black); - PinkyPalsPalette.setColor(QPalette::Base, darkerPink); - PinkyPalsPalette.setColor(QPalette::AlternateBase, brightPink); - PinkyPalsPalette.setColor(QPalette::ToolTipBase, pink); - PinkyPalsPalette.setColor(QPalette::ToolTipText, darkerPink); - PinkyPalsPalette.setColor(QPalette::Text, black); - PinkyPalsPalette.setColor(QPalette::Button, pink); - PinkyPalsPalette.setColor(QPalette::ButtonText, black); - PinkyPalsPalette.setColor(QPalette::Link, black); - PinkyPalsPalette.setColor(QPalette::Highlight, congoPink); - PinkyPalsPalette.setColor(QPalette::HighlightedText, black); + QPalette PinkyPalsPalette; + PinkyPalsPalette.setColor(QPalette::Window, pink); + PinkyPalsPalette.setColor(QPalette::WindowText, black); + PinkyPalsPalette.setColor(QPalette::Base, darkerPink); + PinkyPalsPalette.setColor(QPalette::AlternateBase, brightPink); + PinkyPalsPalette.setColor(QPalette::ToolTipBase, pink); + PinkyPalsPalette.setColor(QPalette::ToolTipText, darkerPink); + PinkyPalsPalette.setColor(QPalette::Text, black); + PinkyPalsPalette.setColor(QPalette::Button, pink); + PinkyPalsPalette.setColor(QPalette::ButtonText, black); + PinkyPalsPalette.setColor(QPalette::Link, black); + PinkyPalsPalette.setColor(QPalette::Highlight, congoPink); + PinkyPalsPalette.setColor(QPalette::HighlightedText, black); - PinkyPalsPalette.setColor(QPalette::Active, QPalette::Button, pink); - PinkyPalsPalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(Qt::white).darker()); - PinkyPalsPalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(Qt::white).darker()); - PinkyPalsPalette.setColor(QPalette::Disabled, QPalette::Text, QColor(Qt::white).darker()); - PinkyPalsPalette.setColor(QPalette::Disabled, QPalette::Light, QColor(Qt::white).darker()); + PinkyPalsPalette.setColor(QPalette::Active, QPalette::Button, pink); + PinkyPalsPalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(Qt::white).darker()); + PinkyPalsPalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(Qt::white).darker()); + PinkyPalsPalette.setColor(QPalette::Disabled, QPalette::Text, QColor(Qt::white).darker()); + PinkyPalsPalette.setColor(QPalette::Disabled, QPalette::Light, QColor(Qt::white).darker()); - qApp->setPalette(PinkyPalsPalette); - qApp->setStyleSheet(QString()); - } - else if (theme == "AMOLED") - { - // Custom palette by KamFretoZ, A pure concentrated darkness - // of a theme designed for maximum eye comfort and benefits - // OLED screens. - qApp->setStyle(QStyleFactory::create("Fusion")); + qApp->setPalette(PinkyPalsPalette); + qApp->setStyleSheet(QString()); + } + else if (theme == "AMOLED") + { + // Custom palette by KamFretoZ, A pure concentrated darkness + // of a theme designed for maximum eye comfort and benefits + // OLED screens. + qApp->setStyle(QStyleFactory::create("Fusion")); - const QColor black(0, 0, 0); - const QColor gray(25, 25, 25); - const QColor lighterGray(75, 75, 75); - const QColor blue(198, 238, 255); + const QColor black(0, 0, 0); + const QColor gray(25, 25, 25); + const QColor lighterGray(75, 75, 75); + const QColor blue(198, 238, 255); - QPalette AMOLEDPalette; - AMOLEDPalette.setColor(QPalette::Window, black); - AMOLEDPalette.setColor(QPalette::WindowText, Qt::white); - AMOLEDPalette.setColor(QPalette::Base, gray); - AMOLEDPalette.setColor(QPalette::AlternateBase, black); - AMOLEDPalette.setColor(QPalette::ToolTipBase, gray); - AMOLEDPalette.setColor(QPalette::ToolTipText, Qt::white); - AMOLEDPalette.setColor(QPalette::Text, Qt::white); - AMOLEDPalette.setColor(QPalette::Button, gray); - AMOLEDPalette.setColor(QPalette::ButtonText, Qt::white); - AMOLEDPalette.setColor(QPalette::Link, blue); - AMOLEDPalette.setColor(QPalette::Highlight, lighterGray); - AMOLEDPalette.setColor(QPalette::HighlightedText, Qt::white); - AMOLEDPalette.setColor(QPalette::PlaceholderText, QColor(Qt::white).darker()); + QPalette AMOLEDPalette; + AMOLEDPalette.setColor(QPalette::Window, black); + AMOLEDPalette.setColor(QPalette::WindowText, Qt::white); + AMOLEDPalette.setColor(QPalette::Base, gray); + AMOLEDPalette.setColor(QPalette::AlternateBase, black); + AMOLEDPalette.setColor(QPalette::ToolTipBase, gray); + AMOLEDPalette.setColor(QPalette::ToolTipText, Qt::white); + AMOLEDPalette.setColor(QPalette::Text, Qt::white); + AMOLEDPalette.setColor(QPalette::Button, gray); + AMOLEDPalette.setColor(QPalette::ButtonText, Qt::white); + AMOLEDPalette.setColor(QPalette::Link, blue); + AMOLEDPalette.setColor(QPalette::Highlight, lighterGray); + AMOLEDPalette.setColor(QPalette::HighlightedText, Qt::white); + AMOLEDPalette.setColor(QPalette::PlaceholderText, QColor(Qt::white).darker()); - AMOLEDPalette.setColor(QPalette::Active, QPalette::Button, gray); - AMOLEDPalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(Qt::white).darker()); - AMOLEDPalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(Qt::white).darker()); - AMOLEDPalette.setColor(QPalette::Disabled, QPalette::Text, QColor(Qt::white).darker()); - AMOLEDPalette.setColor(QPalette::Disabled, QPalette::Light, QColor(Qt::white).darker()); + AMOLEDPalette.setColor(QPalette::Active, QPalette::Button, gray); + AMOLEDPalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(Qt::white).darker()); + AMOLEDPalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(Qt::white).darker()); + AMOLEDPalette.setColor(QPalette::Disabled, QPalette::Text, QColor(Qt::white).darker()); + AMOLEDPalette.setColor(QPalette::Disabled, QPalette::Light, QColor(Qt::white).darker()); - qApp->setPalette(AMOLEDPalette); - qApp->setStyleSheet(QString()); - } + qApp->setPalette(AMOLEDPalette); + qApp->setStyleSheet(QString()); + } else if (theme == "darkruby") { qApp->setStyle(QStyleFactory::create("Fusion")); @@ -358,3 +361,24 @@ void QtHost::SetIconThemeFromStyle() const bool dark = IsDarkApplicationTheme(); QIcon::setThemeName(dark ? QStringLiteral("white") : QStringLiteral("black")); } + +const char* Host::GetDefaultFullscreenUITheme() +{ + const TinyString theme = + Host::GetBaseTinyStringSettingValue("UI", "Theme", InterfaceSettingsWidget::DEFAULT_THEME_NAME); + + if (theme == "cobaltsky") + return "CobaltSky"; + else if (theme == "greymatter") + return "GreyMatter"; + else if (theme == "pinkypals") + return "PinkyPals"; + else if (theme == "purplerain") + return "PurpleRain"; + else if (theme == "AMOLED") + return "AMOLED"; + else if (theme == "windowsvista") + return "Light"; + else // if (theme == "fusion" || theme == "darkfusion" || theme == "darkfusionblue" || theme == "darkruby") + return "Dark"; +} diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index 97b0ac0e8..17f44dd19 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -535,6 +535,11 @@ void Host::OnCoverDownloaderOpenRequested() // noop } +const char* Host::GetDefaultFullscreenUITheme() +{ + return ""; +} + bool Host::ShouldPreferHostFileSelector() { return false; diff --git a/src/util/imgui_fullscreen.h b/src/util/imgui_fullscreen.h index 3b2898aba..cc98cdd08 100644 --- a/src/util/imgui_fullscreen.h +++ b/src/util/imgui_fullscreen.h @@ -406,6 +406,10 @@ void GetInputDialogHelpText(SmallStringBase& dest); // Host UI triggers from Big Picture mode. namespace Host { + +/// Returns the name of the default Big Picture theme to use based on the host theme. +const char* GetDefaultFullscreenUITheme(); + /// Returns true if native file dialogs should be preferred over Big Picture. bool ShouldPreferHostFileSelector(); @@ -415,4 +419,5 @@ using FileSelectorFilters = std::vector; void OpenHostFileSelectorAsync(std::string_view title, bool select_directory, FileSelectorCallback callback, FileSelectorFilters filters = FileSelectorFilters(), std::string_view initial_directory = std::string_view()); + } // namespace Host diff --git a/src/util/imgui_manager.cpp b/src/util/imgui_manager.cpp index ae105f80d..ff681c13b 100644 --- a/src/util/imgui_manager.cpp +++ b/src/util/imgui_manager.cpp @@ -10,6 +10,7 @@ #include "input_manager.h" // TODO: Remove me when GPUDevice config is also cleaned up. +#include "core/fullscreen_ui.h" #include "core/gpu_thread.h" #include "core/host.h" #include "core/settings.h" @@ -257,7 +258,7 @@ bool ImGuiManager::Initialize(float global_scale, float screen_margin, Error* er SetKeyMap(); SetStyle(s_state.imgui_context->Style, s_state.global_scale); - ImGuiFullscreen::SetTheme(Host::GetBaseStringSettingValue("UI", "FullscreenUITheme", "Dark")); + FullscreenUI::SetTheme(); if (!AddImGuiFonts(false, false) || !g_gpu_device->UpdateImGuiFontTexture()) {