From 05bed05afeac6e5d5412886feac5f2e2984135e5 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sun, 15 May 2022 18:20:21 +1000 Subject: [PATCH] Frontend: Add fullscreen UI implementation --- .../fullscreenui/applications-system.png | Bin 0 -> 119419 bytes bin/resources/fullscreenui/media-cdrom.png | Bin 0 -> 206920 bytes bin/resources/fullscreenui/placeholder.png | Bin 0 -> 2762 bytes pcsx2-qt/AboutDialog.cpp | 12 +- pcsx2-qt/DisplayWidget.cpp | 31 +- pcsx2-qt/DisplayWidget.h | 4 + pcsx2-qt/EmuThread.cpp | 110 +- pcsx2-qt/EmuThread.h | 14 +- pcsx2-qt/MainWindow.cpp | 29 +- pcsx2-qt/MainWindow.h | 3 +- pcsx2-qt/MainWindow.ui | 20 + pcsx2-qt/QtHost.cpp | 16 +- pcsx2/CMakeLists.txt | 4 + pcsx2/Frontend/FullscreenUI.cpp | 4384 +++++++++++++++++ pcsx2/Frontend/FullscreenUI.h | 75 + pcsx2/Frontend/ImGuiFullscreen.cpp | 2265 +++++++++ pcsx2/Frontend/ImGuiFullscreen.h | 239 + pcsx2/Frontend/ImGuiManager.cpp | 103 +- pcsx2/Frontend/ImGuiManager.h | 14 + pcsx2/GS/GS.cpp | 7 + pcsx2/PAD/Host/PAD.cpp | 3 +- pcsx2/SysForwardDefs.h | 6 + pcsx2/VMManager.cpp | 84 +- pcsx2/VMManager.h | 5 +- pcsx2/pcsx2core.vcxproj | 4 + pcsx2/pcsx2core.vcxproj.filters | 12 + tools/generate_update_fa_glyph_ranges.py | 95 + 27 files changed, 7463 insertions(+), 76 deletions(-) create mode 100644 bin/resources/fullscreenui/applications-system.png create mode 100644 bin/resources/fullscreenui/media-cdrom.png create mode 100644 bin/resources/fullscreenui/placeholder.png create mode 100644 pcsx2/Frontend/FullscreenUI.cpp create mode 100644 pcsx2/Frontend/FullscreenUI.h create mode 100644 pcsx2/Frontend/ImGuiFullscreen.cpp create mode 100644 pcsx2/Frontend/ImGuiFullscreen.h create mode 100644 tools/generate_update_fa_glyph_ranges.py diff --git a/bin/resources/fullscreenui/applications-system.png b/bin/resources/fullscreenui/applications-system.png new file mode 100644 index 0000000000000000000000000000000000000000..2007dbb7d6111289e2f7b24d534d8c466fd449b7 GIT binary patch literal 119419 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%0DVgC)uJ$u&G?HK;l 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μ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

7<1gqQrDhgTsnYm<^SU*Qhu#j|&uqh;lZ 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%0A}3WUgPHa);0?p`(Yyq3a^3$sk$TS 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 NgQu&X%Q~loCIDGS1#18R literal 0 HcmV?d00001 diff --git a/bin/resources/fullscreenui/placeholder.png b/bin/resources/fullscreenui/placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..064082d2c8704ef09d71ee17a25367f0ce3743ba GIT binary patch literal 2762 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 literal 0 HcmV?d00001 diff --git a/pcsx2-qt/AboutDialog.cpp b/pcsx2-qt/AboutDialog.cpp index ffb224c32a..8e7cf67eb8 100644 --- a/pcsx2-qt/AboutDialog.cpp +++ b/pcsx2-qt/AboutDialog.cpp @@ -15,6 +15,8 @@ #include "PrecompiledHeader.h" +#include "pcsx2/SysForwardDefs.h" + #include "AboutDialog.h" #include "QtHost.h" #include "QtUtils.h" @@ -52,25 +54,25 @@ AboutDialog::~AboutDialog() = default; QString AboutDialog::getWebsiteUrl() { - return QStringLiteral("https://pcsx2.net/"); + return QString::fromUtf8(PCSX2_WEBSITE_URL); } QString AboutDialog::getSupportForumsUrl() { - return QStringLiteral("https://forums.pcsx2.net/"); + return QString::fromUtf8(PCSX2_FORUMS_URL); } QString AboutDialog::getGitHubRepositoryUrl() { - return QStringLiteral("https://github.com/PCSX2/pcsx2"); + return QString::fromUtf8(PCSX2_GITHUB_URL); } QString AboutDialog::getLicenseUrl() { - return QStringLiteral("https://github.com/PCSX2/pcsx2/blob/master/pcsx2/Docs/License.txt"); + return QString::fromUtf8(PCSX2_LICENSE_URL); } QString AboutDialog::getDiscordServerUrl() { - return QStringLiteral("https://discord.com/invite/TCz3t9k"); + return QString::fromUtf8(PCSX2_DISCORD_URL); } diff --git a/pcsx2-qt/DisplayWidget.cpp b/pcsx2-qt/DisplayWidget.cpp index cfd820eaff..80a3f06eb2 100644 --- a/pcsx2-qt/DisplayWidget.cpp +++ b/pcsx2-qt/DisplayWidget.cpp @@ -183,6 +183,24 @@ void DisplayWidget::updateCursor(bool master_enable) unsetCursor(); } +void DisplayWidget::handleCloseEvent(QCloseEvent* event) +{ + // Closing the separate widget will either cancel the close, or trigger shutdown. + // In the latter case, it's going to destroy us, so don't let Qt do it first. + if (QtHost::IsVMValid()) + { + QMetaObject::invokeMethod(g_main_window, "requestShutdown", Q_ARG(bool, true), + Q_ARG(bool, true), Q_ARG(bool, false)); + } + else + { + QMetaObject::invokeMethod(g_main_window, &MainWindow::requestExit); + } + + // Cancel the event from closing the window. + event->ignore(); +} + void DisplayWidget::updateCenterPos() { #ifdef _WIN32 @@ -330,6 +348,7 @@ bool DisplayWidget::event(QEvent* event) // don't toggle fullscreen when we're bound.. that wouldn't end well. if (event->type() == QEvent::MouseButtonDblClick && static_cast(event)->button() == Qt::LeftButton && + QtHost::IsVMValid() && !QtHost::IsVMPaused() && !InputManager::HasAnyBindingsForKey(InputManager::MakePointerButtonKey(0, 0)) && Host::GetBoolSettingValue("UI", "DoubleClickTogglesFullscreen", true)) { @@ -384,10 +403,7 @@ bool DisplayWidget::event(QEvent* event) case QEvent::Close: { - // Closing the separate widget will either cancel the close, or trigger shutdown. - // In the latter case, it's going to destroy us, so don't let Qt do it first. - QMetaObject::invokeMethod(g_main_window, "requestShutdown", Q_ARG(bool, true), Q_ARG(bool, true), Q_ARG(bool, false)); - event->ignore(); + handleCloseEvent(static_cast(event)); return true; } @@ -454,12 +470,9 @@ DisplayWidget* DisplayContainer::removeDisplayWidget() bool DisplayContainer::event(QEvent* event) { - if (event->type() == QEvent::Close) + if (event->type() == QEvent::Close && m_display_widget) { - // Closing the separate widget will either cancel the close, or trigger shutdown. - // In the latter case, it's going to destroy us, so don't let Qt do it first. - QMetaObject::invokeMethod(g_main_window, "requestShutdown", Q_ARG(bool, true), Q_ARG(bool, true), Q_ARG(bool, false)); - event->ignore(); + m_display_widget->handleCloseEvent(static_cast(event)); return true; } diff --git a/pcsx2-qt/DisplayWidget.h b/pcsx2-qt/DisplayWidget.h index d97969b310..34905ddda3 100644 --- a/pcsx2-qt/DisplayWidget.h +++ b/pcsx2-qt/DisplayWidget.h @@ -20,6 +20,8 @@ #include #include +class QCloseEvent; + class DisplayWidget final : public QWidget { Q_OBJECT @@ -41,6 +43,8 @@ public: void updateRelativeMode(bool master_enable); void updateCursor(bool master_enable); + void handleCloseEvent(QCloseEvent* event); + Q_SIGNALS: void windowResizedEvent(int width, int height, float scale); void windowRestoredEvent(); diff --git a/pcsx2-qt/EmuThread.cpp b/pcsx2-qt/EmuThread.cpp index 1395265f13..ac9a7b4d39 100644 --- a/pcsx2-qt/EmuThread.cpp +++ b/pcsx2-qt/EmuThread.cpp @@ -30,6 +30,7 @@ #include "pcsx2/Counters.h" #include "pcsx2/Frontend/InputManager.h" #include "pcsx2/Frontend/ImGuiManager.h" +#include "pcsx2/Frontend/FullscreenUI.h" #include "pcsx2/GS.h" #include "pcsx2/GS/GS.h" #include "pcsx2/GSDumpReplayer.h" @@ -87,6 +88,9 @@ void EmuThread::stopInThread() if (VMManager::HasValidVM()) destroyVM(); + if (m_run_fullscreen_ui) + stopFullscreenUI(); + m_event_loop->quit(); m_shutdown_flag.store(true); } @@ -126,6 +130,53 @@ bool EmuThread::confirmMessage(const QString& title, const QString& message) return result; } +void EmuThread::startFullscreenUI(bool fullscreen) +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, "startFullscreenUI", Qt::QueuedConnection, Q_ARG(bool, fullscreen)); + return; + } + + if (VMManager::HasValidVM()) + return; + + m_run_fullscreen_ui = true; + if (fullscreen) + m_is_fullscreen = true; + + if (!GetMTGS().WaitForOpen()) + { + m_run_fullscreen_ui = false; + return; + } + + // poll more frequently so we don't lose events + stopBackgroundControllerPollTimer(); + startBackgroundControllerPollTimer(); +} + +void EmuThread::stopFullscreenUI() +{ + if (!isOnEmuThread()) + { + QMetaObject::invokeMethod(this, &EmuThread::stopFullscreenUI, Qt::QueuedConnection); + + // wait until the host display is gone + while (s_host_display) + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1); + + return; + } + + if (!s_host_display) + return; + + pxAssertRel(!VMManager::HasValidVM(), "VM is not valid at FSUI shutdown time"); + m_run_fullscreen_ui = false; + GetMTGS().WaitForClose(); +} + void EmuThread::startVM(std::shared_ptr boot_params) { if (!isOnEmuThread()) @@ -136,15 +187,16 @@ void EmuThread::startVM(std::shared_ptr boot_params) } pxAssertRel(!VMManager::HasValidVM(), "VM is shut down"); - loadOurSettings(); + + // Only initialize fullscreen/render-to-main when we're not running big picture. + if (!m_run_fullscreen_ui) + loadOurInitialSettings(); + + if (boot_params->fullscreen.has_value()) + m_is_fullscreen = boot_params->fullscreen.value(); emit onVMStarting(); - // create the display, this may take a while... - m_is_fullscreen = boot_params->fullscreen.value_or(Host::GetBaseBoolSettingValue("UI", "StartFullscreen", false)); - m_is_rendering_to_main = shouldRenderToMain(); - m_is_surfaceless = false; - m_save_state_on_shutdown = false; if (!VMManager::Initialize(*boot_params)) return; @@ -265,6 +317,7 @@ void EmuThread::saveStateToSlot(qint32 slot) void EmuThread::run() { + Threading::SetNameOfCurrentThread("EmuThread"); PerformanceMetrics::SetCPUThread(Threading::ThreadHandle::GetForCallingThread()); m_event_loop = new QEventLoop(); m_started_semaphore.release(); @@ -273,8 +326,13 @@ void EmuThread::run() if (!VMManager::Internal::InitializeGlobals() || !VMManager::Internal::InitializeMemory()) pxFailRel("Failed to allocate memory map"); - // we need input sources ready for binding - reloadInputSources(); + // We want settings loaded so we choose the correct renderer for big picture mode. + // This also sorts out input sources. + loadOurSettings(); + loadOurInitialSettings(); + VMManager::LoadSettings(); + + // Start background polling because the VM won't do it for us. createBackgroundControllerPollTimer(); startBackgroundControllerPollTimer(); connectSignals(); @@ -362,7 +420,9 @@ void EmuThread::startBackgroundControllerPollTimer() if (m_background_controller_polling_timer->isActive()) return; - m_background_controller_polling_timer->start(BACKGROUND_CONTROLLER_POLLING_INTERVAL); + m_background_controller_polling_timer->start(FullscreenUI::IsInitialized() ? + FULLSCREEN_UI_CONTROLLER_POLLING_INTERVAL : + BACKGROUND_CONTROLLER_POLLING_INTERVAL); } void EmuThread::stopBackgroundControllerPollTimer() @@ -397,7 +457,7 @@ void EmuThread::setFullscreen(bool fullscreen) return; } - if (!VMManager::HasValidVM() || m_is_fullscreen == fullscreen) + if (!GetMTGS().IsOpen() || m_is_fullscreen == fullscreen) return; // This will call back to us on the MTGS thread. @@ -417,9 +477,13 @@ void EmuThread::setSurfaceless(bool surfaceless) return; } - if (!VMManager::HasValidVM() || m_is_surfaceless == surfaceless) + if (!GetMTGS().IsOpen() || m_is_surfaceless == surfaceless) return; + // If we went surfaceless and were running the fullscreen UI, stop MTGS running idle. + // Otherwise, we'll keep trying to present to nothing. + GetMTGS().SetRunIdle(!surfaceless && m_run_fullscreen_ui); + // This will call back to us on the MTGS thread. m_is_surfaceless = surfaceless; GetMTGS().UpdateDisplayWindow(); @@ -476,11 +540,19 @@ void EmuThread::connectSignals() connect(qApp, &QGuiApplication::applicationStateChanged, this, &EmuThread::onApplicationStateChanged); } +void EmuThread::loadOurInitialSettings() +{ + m_is_fullscreen = Host::GetBaseBoolSettingValue("UI", "StartFullscreen", false); + m_is_rendering_to_main = shouldRenderToMain(); + m_is_surfaceless = false; + m_save_state_on_shutdown = false; +} + void EmuThread::checkForSettingChanges() { QMetaObject::invokeMethod(g_main_window, &MainWindow::checkForSettingChanges, Qt::QueuedConnection); - if (VMManager::HasValidVM()) + if (s_host_display) { const bool render_to_main = shouldRenderToMain(); if (!m_is_fullscreen && m_is_rendering_to_main != render_to_main) @@ -772,6 +844,14 @@ HostDisplay* EmuThread::acquireHostDisplay(HostDisplay::RenderAPI api) Console.WriteLn(Color_StrongGreen, "%s Graphics Driver Info:", HostDisplay::RenderAPIToString(s_host_display->GetRenderAPI())); Console.Indent().WriteLn(s_host_display->GetDriverInfo()); + if (m_run_fullscreen_ui && !FullscreenUI::Initialize()) + { + Console.Error("Failed to initialize fullscreen UI"); + releaseHostDisplay(); + m_run_fullscreen_ui = false; + return nullptr; + } + return s_host_display.get(); } @@ -816,6 +896,7 @@ void Host::EndPresentFrame() if (GSDumpReplayer::IsReplayingDump()) GSDumpReplayer::RenderUI(); + FullscreenUI::Render(); ImGuiManager::RenderOSD(); s_host_display->EndPresent(); ImGuiManager::NewFrame(); @@ -1036,14 +1117,15 @@ void Host::RequestExit(bool save_state_if_running) QMetaObject::invokeMethod(g_main_window, "requestExit", Qt::QueuedConnection); } -void Host::RequestVMShutdown(bool allow_confirm, bool allow_save_state) +void Host::RequestVMShutdown(bool allow_confirm, bool allow_save_state, bool default_save_state) { if (!VMManager::HasValidVM()) return; // Run it on the host thread, that way we get the confirm prompt (if enabled). QMetaObject::invokeMethod(g_main_window, "requestShutdown", Qt::QueuedConnection, - Q_ARG(bool, allow_confirm), Q_ARG(bool, allow_save_state), Q_ARG(bool, false)); + Q_ARG(bool, allow_confirm), Q_ARG(bool, allow_save_state), + Q_ARG(bool, default_save_state), Q_ARG(bool, false)); } bool Host::IsFullscreen() diff --git a/pcsx2-qt/EmuThread.h b/pcsx2-qt/EmuThread.h index bb43889f7b..776971fcb3 100644 --- a/pcsx2-qt/EmuThread.h +++ b/pcsx2-qt/EmuThread.h @@ -47,6 +47,7 @@ public: __fi bool isFullscreen() const { return m_is_fullscreen; } __fi bool isRenderingToMain() const { return m_is_rendering_to_main; } __fi bool isSurfaceless() const { return m_is_surfaceless; } + __fi bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; } bool isOnEmuThread() const; @@ -62,6 +63,8 @@ public: public Q_SLOTS: bool confirmMessage(const QString& title, const QString& message); + void startFullscreenUI(bool fullscreen); + void stopFullscreenUI(); void startVM(std::shared_ptr boot_params); void resetVM(); void setVMPaused(bool paused); @@ -133,8 +136,11 @@ protected: void run(); private: - static constexpr u32 BACKGROUND_CONTROLLER_POLLING_INTERVAL = - 100; /// Interval at which the controllers are polled when the system is not active. + /// Interval at which the controllers are polled when the system is not active. + static constexpr u32 BACKGROUND_CONTROLLER_POLLING_INTERVAL = 100; + + /// Poll at half the vsync rate for FSUI to reduce the chance of getting a press+release in the same frame. + static constexpr u32 FULLSCREEN_UI_CONTROLLER_POLLING_INTERVAL = 8; void destroyVM(); void executeVM(); @@ -143,8 +149,9 @@ private: void createBackgroundControllerPollTimer(); void destroyBackgroundControllerPollTimer(); - void loadOurSettings(); void connectSignals(); + void loadOurSettings(); + void loadOurInitialSettings(); private Q_SLOTS: void stopInThread(); @@ -162,6 +169,7 @@ private: std::atomic_bool m_shutdown_flag{false}; bool m_verbose_status = false; + bool m_run_fullscreen_ui = false; bool m_is_rendering_to_main = false; bool m_is_fullscreen = false; bool m_is_surfaceless = false; diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index 5582777abe..879b8f64d1 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -251,8 +251,8 @@ void MainWindow::connectSignals() connect(m_ui.actionRemoveDisc, &QAction::triggered, this, &MainWindow::onRemoveDiscActionTriggered); connect(m_ui.menuChangeDisc, &QMenu::aboutToShow, this, &MainWindow::onChangeDiscMenuAboutToShow); connect(m_ui.menuChangeDisc, &QMenu::aboutToHide, this, &MainWindow::onChangeDiscMenuAboutToHide); - connect(m_ui.actionPowerOff, &QAction::triggered, this, [this]() { requestShutdown(true, true); }); - connect(m_ui.actionPowerOffWithoutSaving, &QAction::triggered, this, [this]() { requestShutdown(false, false); }); + connect(m_ui.actionPowerOff, &QAction::triggered, this, [this]() { requestShutdown(true, true, EmuConfig.SaveStateOnShutdown); }); + connect(m_ui.actionPowerOffWithoutSaving, &QAction::triggered, this, [this]() { requestShutdown(false, false, false); }); connect(m_ui.actionLoadState, &QAction::triggered, this, [this]() { m_ui.menuLoadState->exec(QCursor::pos()); }); connect(m_ui.actionSaveState, &QAction::triggered, this, [this]() { m_ui.menuSaveState->exec(QCursor::pos()); }); connect(m_ui.actionExit, &QAction::triggered, this, &MainWindow::close); @@ -354,6 +354,8 @@ void MainWindow::connectSignals() void MainWindow::connectVMThreadSignals(EmuThread* thread) { + connect(m_ui.actionStartFullscreenUI, &QAction::triggered, thread, &EmuThread::startFullscreenUI); + connect(m_ui.actionStartFullscreenUI2, &QAction::triggered, thread, &EmuThread::startFullscreenUI); connect(thread, &EmuThread::messageConfirmed, this, &MainWindow::confirmMessage, Qt::BlockingQueuedConnection); connect(thread, &EmuThread::onCreateDisplayRequested, this, &MainWindow::createDisplay, Qt::BlockingQueuedConnection); connect(thread, &EmuThread::onUpdateDisplayRequested, this, &MainWindow::updateDisplay, Qt::BlockingQueuedConnection); @@ -387,7 +389,7 @@ void MainWindow::connectVMThreadSignals(EmuThread* thread) void MainWindow::recreate() { if (s_vm_valid) - requestShutdown(false, true, true); + requestShutdown(false, true, EmuConfig.SaveStateOnShutdown); close(); g_main_window = nullptr; @@ -950,7 +952,7 @@ void MainWindow::switchToGameListView() return; } - if (s_vm_valid) + if (m_display_created) { m_was_paused_on_surface_loss = s_vm_paused; if (!s_vm_paused) @@ -965,7 +967,7 @@ void MainWindow::switchToGameListView() void MainWindow::switchToEmulationView() { - if (!s_vm_valid || !isShowingGameList()) + if (!m_display_created || !isShowingGameList()) return; // we're no longer surfaceless! this will call back to UpdateDisplay(), which will swap the widget out. @@ -1014,14 +1016,14 @@ void MainWindow::runOnUIThread(const std::function& func) func(); } -bool MainWindow::requestShutdown(bool allow_confirm /* = true */, bool allow_save_to_state /* = true */, bool block_until_done /* = false */) +bool MainWindow::requestShutdown(bool allow_confirm /* = true */, bool allow_save_to_state /* = true */, bool default_save_to_state /* = true */, bool block_until_done /* = false */) { if (!s_vm_valid) return true; // If we don't have a crc, we can't save state. allow_save_to_state &= (m_current_game_crc != 0); - bool save_state = allow_save_to_state && EmuConfig.SaveStateOnShutdown; + bool save_state = allow_save_to_state && default_save_to_state; // Only confirm on UI thread because we need to display a msgbox. if (!m_is_closing && allow_confirm && !GSDumpReplayer::IsReplayingDump() && Host::GetBaseBoolSettingValue("UI", "ConfirmShutdown", true)) @@ -1081,7 +1083,7 @@ bool MainWindow::requestShutdown(bool allow_confirm /* = true */, bool allow_sav void MainWindow::requestExit() { // this is block, because otherwise closeEvent() will also prompt - if (!requestShutdown(true, true, true)) + if (!requestShutdown(true, true, EmuConfig.SaveStateOnShutdown, true)) return; // We could use close here, but if we're not visible (e.g. quitting from fullscreen), closing the window @@ -1334,7 +1336,7 @@ void MainWindow::onViewGameGridActionTriggered() void MainWindow::onViewSystemDisplayTriggered() { - if (s_vm_valid) + if (m_display_created) switchToEmulationView(); } @@ -1643,7 +1645,7 @@ void MainWindow::showEvent(QShowEvent* event) void MainWindow::closeEvent(QCloseEvent* event) { - if (!requestShutdown(true, true, true)) + if (!requestShutdown(true, true, EmuConfig.SaveStateOnShutdown, true)) { event->ignore(); return; @@ -1742,6 +1744,8 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main) return nullptr; } + m_display_created = true; + if (is_exclusive_fullscreen) setDisplayFullscreen(fullscreen_mode); @@ -1750,6 +1754,8 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main) m_ui.actionViewSystemDisplay->setEnabled(true); m_ui.actionFullscreen->setEnabled(true); + m_ui.actionStartFullscreenUI->setEnabled(false); + m_ui.actionStartFullscreenUI2->setEnabled(false); m_display_widget->setShouldHideCursor(shouldHideMouseCursor()); m_display_widget->updateRelativeMode(s_vm_valid && !s_vm_paused); @@ -1940,9 +1946,12 @@ void MainWindow::destroyDisplay() { // Now we can safely destroy the display window. destroyDisplayWidget(true); + m_display_created = false; m_ui.actionViewSystemDisplay->setEnabled(false); m_ui.actionFullscreen->setEnabled(false); + m_ui.actionStartFullscreenUI->setEnabled(true); + m_ui.actionStartFullscreenUI2->setEnabled(true); } void MainWindow::destroyDisplayWidget(bool show_game_list) diff --git a/pcsx2-qt/MainWindow.h b/pcsx2-qt/MainWindow.h index 5060a44573..38a8e45ff3 100644 --- a/pcsx2-qt/MainWindow.h +++ b/pcsx2-qt/MainWindow.h @@ -104,7 +104,7 @@ public Q_SLOTS: void reportError(const QString& title, const QString& message); bool confirmMessage(const QString& title, const QString& message); void runOnUIThread(const std::function& func); - bool requestShutdown(bool allow_confirm = true, bool allow_save_to_state = true, bool block_until_done = false); + bool requestShutdown(bool allow_confirm = true, bool allow_save_to_state = true, bool default_save_to_state = true, bool block_until_done = false); void requestExit(); void checkForSettingChanges(); @@ -258,6 +258,7 @@ private: QString m_current_game_name; quint32 m_current_game_crc; + bool m_display_created = false; bool m_save_states_invalidated = false; bool m_was_paused_on_surface_loss = false; bool m_was_disc_change_request = false; diff --git a/pcsx2-qt/MainWindow.ui b/pcsx2-qt/MainWindow.ui index c4b0956119..227f90a089 100644 --- a/pcsx2-qt/MainWindow.ui +++ b/pcsx2-qt/MainWindow.ui @@ -66,6 +66,7 @@ + @@ -225,6 +226,7 @@ + @@ -829,6 +831,24 @@ Enable Log Timestamps + + + + .. + + + Start Big Picture Mode + + + + + + .. + + + Big Picture + + diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index b17c6051ea..c38391fc60 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -82,6 +82,8 @@ static std::unique_ptr s_settings_save_timer; static std::unique_ptr s_base_settings_interface; static bool s_batch_mode = false; static bool s_nogui_mode = false; +static bool s_start_fullscreen_ui = false; +static bool s_start_fullscreen_ui_fullscreen = false; ////////////////////////////////////////////////////////////////////////// // Initialization/Shutdown @@ -617,6 +619,7 @@ bool QtHost::ParseCommandLineOptions(int argc, char* argv[], std::shared_ptrfullscreen = true; + s_start_fullscreen_ui_fullscreen = true; continue; } else if (CHECK_ARG("-nofullscreen")) @@ -629,6 +632,11 @@ bool QtHost::ParseCommandLineOptions(int argc, char* argv[], std::shared_ptrshow(); + // Initialize big picture mode if requested. + if (s_start_fullscreen_ui) + g_emu_thread->startFullscreenUI(s_start_fullscreen_ui_fullscreen); + // Skip the update check if we're booting a game directly. if (autoboot) g_emu_thread->startVM(std::move(autoboot)); - else + else if (!s_nogui_mode) main_window->startupUpdateCheck(); // This doesn't return until we exit. diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index cc71b4f8a3..a9e4d3f4b3 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -1063,7 +1063,9 @@ endif() if(PCSX2_CORE) list(APPEND pcsx2FrontendSources + Frontend/FullscreenUI.cpp Frontend/GameList.cpp + Frontend/ImGuiFullscreen.cpp Frontend/INISettingsInterface.cpp Frontend/InputManager.cpp Frontend/InputSource.cpp @@ -1074,7 +1076,9 @@ if(PCSX2_CORE) VMManager.cpp ) list(APPEND pcsx2FrontendHeaders + Frontend/FullscreenUI.h Frontend/GameList.h + Frontend/ImGuiFullscreen.h Frontend/INISettingsInterface.h Frontend/InputManager.h Frontend/InputSource.h diff --git a/pcsx2/Frontend/FullscreenUI.cpp b/pcsx2/Frontend/FullscreenUI.cpp new file mode 100644 index 0000000000..9634f2d857 --- /dev/null +++ b/pcsx2/Frontend/FullscreenUI.cpp @@ -0,0 +1,4384 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#define IMGUI_DEFINE_MATH_OPERATORS + +#include "Frontend/FullscreenUI.h" +#include "Frontend/ImGuiManager.h" +#include "Frontend/ImGuiFullscreen.h" +#include "Frontend/INISettingsInterface.h" +#include "Frontend/InputManager.h" +#include "Frontend/GameList.h" +#include "IconsFontAwesome5.h" + +#include "common/FileSystem.h" +#include "common/Console.h" +#include "common/Image.h" +#include "common/Path.h" +#include "common/SettingsInterface.h" +#include "common/SettingsWrapper.h" +#include "common/StringUtil.h" +#include "common/Timer.h" + +#include "CDVD/CDVDcommon.h" +#include "CDVD/CDVDdiscReader.h" +#include "GS.h" +#include "Host.h" +#include "HostDisplay.h" +#include "HostSettings.h" +#include "MemoryCardFile.h" +#include "PAD/Host/PAD.h" +#include "ps2/BiosTools.h" +#include "Sio.h" +#include "VMManager.h" + +#include "svnrev.h" + +#include "imgui.h" +#include "imgui_internal.h" + +#include "fmt/core.h" + +#include +#include +#include + +static constexpr s32 MAX_SAVE_STATE_SLOTS = 10; + +using ImGuiFullscreen::g_large_font; +using ImGuiFullscreen::g_layout_padding_left; +using ImGuiFullscreen::g_layout_padding_top; +using ImGuiFullscreen::g_medium_font; +using ImGuiFullscreen::LAYOUT_LARGE_FONT_SIZE; +using ImGuiFullscreen::LAYOUT_MEDIUM_FONT_SIZE; +using ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT; +using ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY; +using ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING; +using ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING; +using ImGuiFullscreen::LAYOUT_SCREEN_HEIGHT; +using ImGuiFullscreen::LAYOUT_SCREEN_WIDTH; +using ImGuiFullscreen::UIBackgroundColor; +using ImGuiFullscreen::UIBackgroundHighlightColor; +using ImGuiFullscreen::UIBackgroundLineColor; +using ImGuiFullscreen::UIBackgroundTextColor; +using ImGuiFullscreen::UIDisabledColor; +using ImGuiFullscreen::UIPrimaryColor; +using ImGuiFullscreen::UIPrimaryDarkColor; +using ImGuiFullscreen::UIPrimaryLightColor; +using ImGuiFullscreen::UIPrimaryLineColor; +using ImGuiFullscreen::UIPrimaryTextColor; +using ImGuiFullscreen::UISecondaryColor; +using ImGuiFullscreen::UISecondaryDarkColor; +using ImGuiFullscreen::UISecondaryLightColor; +using ImGuiFullscreen::UISecondaryTextColor; +using ImGuiFullscreen::UITextHighlightColor; + +using ImGuiFullscreen::ActiveButton; +using ImGuiFullscreen::AddNotification; +using ImGuiFullscreen::BeginFullscreenColumns; +using ImGuiFullscreen::BeginFullscreenColumnWindow; +using ImGuiFullscreen::BeginFullscreenWindow; +using ImGuiFullscreen::BeginMenuButtons; +using ImGuiFullscreen::BeginNavBar; +using ImGuiFullscreen::CenterImage; +using ImGuiFullscreen::CloseChoiceDialog; +using ImGuiFullscreen::CloseFileSelector; +using ImGuiFullscreen::DPIScale; +using ImGuiFullscreen::EndFullscreenColumns; +using ImGuiFullscreen::EndFullscreenColumnWindow; +using ImGuiFullscreen::EndFullscreenWindow; +using ImGuiFullscreen::EndMenuButtons; +using ImGuiFullscreen::EndNavBar; +using ImGuiFullscreen::EnumChoiceButton; +using ImGuiFullscreen::FloatingButton; +using ImGuiFullscreen::GetCachedTexture; +using ImGuiFullscreen::GetCachedTextureAsync; +using ImGuiFullscreen::GetPlaceholderTexture; +using ImGuiFullscreen::LayoutScale; +using ImGuiFullscreen::LoadTexture; +using ImGuiFullscreen::MenuButton; +using ImGuiFullscreen::MenuButtonFrame; +using ImGuiFullscreen::MenuButtonWithValue; +using ImGuiFullscreen::MenuHeading; +using ImGuiFullscreen::MenuHeadingButton; +using ImGuiFullscreen::MenuImageButton; +using ImGuiFullscreen::NavButton; +using ImGuiFullscreen::NavTitle; +using ImGuiFullscreen::OpenChoiceDialog; +using ImGuiFullscreen::OpenFileSelector; +using ImGuiFullscreen::OpenInputStringDialog; +using ImGuiFullscreen::PopPrimaryColor; +using ImGuiFullscreen::PushPrimaryColor; +using ImGuiFullscreen::QueueResetFocus; +using ImGuiFullscreen::RangeButton; +using ImGuiFullscreen::ResetFocusHere; +using ImGuiFullscreen::RightAlignNavButtons; +using ImGuiFullscreen::ShowToast; +using ImGuiFullscreen::ThreeWayToggleButton; +using ImGuiFullscreen::ToggleButton; +using ImGuiFullscreen::WantsToCloseMenu; + +namespace FullscreenUI +{ + enum class MainWindowType + { + None, + Landing, + GameList, + Settings, + PauseMenu + }; + + enum class PauseSubMenu + { + None, + Exit, + }; + + enum class SettingsPage + { + Summary, + Interface, + GameList, + BIOS, + Emulation, + System, + Graphics, + Audio, + MemoryCard, + Controller, + Hotkey, + Achievements, + Advanced, + GameFixes, + Count + }; + + ////////////////////////////////////////////////////////////////////////// + // Utility + ////////////////////////////////////////////////////////////////////////// + static std::string TimeToPrintableString(time_t t); + + ////////////////////////////////////////////////////////////////////////// + // Main + ////////////////////////////////////////////////////////////////////////// + static void UpdateForcedVsync(bool should_force); + static void UpdateGameDetails(std::string path, std::string serial, std::string title, u32 crc); + static void PauseForMenuOpen(); + static void ClosePauseMenu(); + static void OpenPauseSubMenu(PauseSubMenu submenu); + static void ReturnToMainWindow(); + static void DrawLandingWindow(); + static void DrawPauseMenu(MainWindowType type); + static void ExitFullscreenAndOpenURL(const std::string_view& url); + static void CopyTextToClipboard(std::string title, const std::string_view& text); + static void DrawAboutWindow(); + static void OpenAboutWindow(); + + static MainWindowType s_current_main_window = MainWindowType::None; + static PauseSubMenu s_current_pause_submenu = PauseSubMenu::None; + static bool s_initialized = false; + static bool s_tried_to_initialize = false; + static bool s_pause_menu_was_open = false; + static bool s_was_paused_on_quick_menu_open = false; + static bool s_about_window_open = false; + + // local copies of the currently-running game + static std::string s_current_game_title; + static std::string s_current_game_subtitle; + static std::string s_current_game_serial; + static std::string s_current_game_path; + static u32 s_current_game_crc; + + ////////////////////////////////////////////////////////////////////////// + // Resources + ////////////////////////////////////////////////////////////////////////// + static bool LoadResources(); + static void DestroyResources(); + + static std::shared_ptr s_app_icon_texture; + static std::array, static_cast(GameDatabaseSchema::Compatibility::Perfect)> + s_game_compatibility_textures; + static std::shared_ptr s_fallback_disc_texture; + static std::shared_ptr s_fallback_exe_texture; + static std::vector> s_cleanup_textures; + + ////////////////////////////////////////////////////////////////////////// + // Landing + ////////////////////////////////////////////////////////////////////////// + static void SwitchToLanding(); + static ImGuiFullscreen::FileSelectorFilters GetDiscImageFilters(); + static void DoStartPath( + const std::string& path, std::optional state_index = std::nullopt, std::optional fast_boot = std::nullopt); + static void DoStartFile(); + static void DoStartBIOS(); + static void DoStartDisc(const std::string& drive); + static void DoStartDisc(); + static void DoToggleFrameLimit(); + static void DoToggleSoftwareRenderer(); + static void DoShutdown(bool save_state); + static void DoReset(); + static void DoChangeDiscFromFile(); + static void DoChangeDisc(); + static void DoRequestExit(); + static void DoToggleFullscreen(); + + ////////////////////////////////////////////////////////////////////////// + // Settings + ////////////////////////////////////////////////////////////////////////// + + static constexpr double INPUT_BINDING_TIMEOUT_SECONDS = 5.0; + static constexpr u32 NUM_MEMORY_CARD_PORTS = 2; + + static void SwitchToSettings(); + static void SwitchToGameSettings(); + static void SwitchToGameSettings(const std::string& path); + static void SwitchToGameSettings(const GameList::Entry* entry); + static void SwitchToGameSettings(const std::string_view& serial, u32 crc); + static void DrawSettingsWindow(); + static void DrawSummarySettingsPage(); + static void DrawInterfaceSettingsPage(); + static void DrawGameListSettingsPage(); + static void DrawBIOSSettingsPage(); + static void DrawEmulationSettingsPage(); + static void DrawSystemSettingsPage(); + static void DrawGraphicsSettingsPage(); + static void DrawAudioSettingsPage(); + static void DrawMemoryCardSettingsPage(); + static void DrawCreateMemoryCardWindow(); + static void DrawControllerSettingsPage(); + static void DrawHotkeySettingsPage(); + static void DrawAchievementsSettingsPage(); + static void DrawAdvancedSettingsPage(); + static void DrawGameFixesSettingsPage(); + + static bool IsEditingGameSettings(SettingsInterface* bsi); + static SettingsInterface* GetEditingSettingsInterface(); + static SettingsInterface* GetEditingSettingsInterface(bool game_settings); + static void SetSettingsChanged(SettingsInterface* bsi); + static bool GetEffectiveBoolSetting(SettingsInterface* bsi, const char* section, const char* key, bool default_value); + static s32 GetEffectiveIntSetting(SettingsInterface* bsi, const char* section, const char* key, s32 default_value); + static void DoCopyGameSettings(); + static void DoClearGameSettings(); + static void CopyGlobalControllerSettingsToGame(); + static void ResetControllerSettings(); + static void DoLoadInputProfile(); + static void DoSaveInputProfile(); + static void DoSaveInputProfile(const std::string& name); + + static bool DrawToggleSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key, + bool default_value, bool enabled = true, bool allow_tristate = true, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, + ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); + static void DrawIntListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key, + int default_value, const char* const* options, size_t option_count, int option_offset = 0, bool enabled = true, + float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); + static void DrawIntRangeSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key, + int default_value, int min_value, int max_value, const char* format = "%d", bool enabled = true, + float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); + static void DrawFloatRangeSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key, + float default_value, float min_value, float max_value, const char* format = "%f", float multiplier = 1.0f, bool enabled = true, + float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); + static void DrawIntRectSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, + const char* left_key, int default_left, const char* top_key, int default_top, const char* right_key, int default_right, + const char* bottom_key, int default_bottom, int min_value, int max_value, const char* format = "%d", bool enabled = true, + float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); + static void DrawStringListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key, + const char* default_value, const char* const* options, const char* const* option_values, size_t option_count, bool enabled = true, + float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); + static void DrawFloatListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key, + float default_value, const char* const* options, const float* option_values, size_t option_count, bool enabled = true, + float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); + static void DrawFolderSetting(SettingsInterface* bsi, const char* title, const char* section, const char* key, + const std::string& runtime_var, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, + ImFont* summary_font = g_medium_font); + static void DrawClampingModeSetting(SettingsInterface* bsi, const char* title, const char* summary, bool vu); + static void PopulateGraphicsAdapterList(); + static void PopulateGameListDirectoryCache(SettingsInterface* si); + static ImGuiFullscreen::ChoiceDialogOptions GetGameListDirectoryOptions(bool recursive_as_checked); + static void BeginInputBinding(SettingsInterface* bsi, PAD::ControllerBindingType type, const std::string_view& section, + const std::string_view& key, const std::string_view& display_name); + static void DrawInputBindingWindow(); + static void DrawInputBindingButton(SettingsInterface* bsi, PAD::ControllerBindingType type, const char* section, const char* name, + const char* display_name, bool show_type = true); + static void ClearInputBindingVariables(); + static void StartAutomaticBinding(u32 port); + + static SettingsPage s_settings_page = SettingsPage::Interface; + static std::unique_ptr s_game_settings_interface; + static std::unique_ptr s_game_settings_entry; + static std::vector> s_game_list_directories_cache; + static std::vector s_graphics_adapter_list_cache; + static std::vector s_fullscreen_mode_list_cache; + static std::vector s_hotkey_list_cache; + static std::atomic_bool s_settings_changed{false}; + static std::atomic_bool s_game_settings_changed{false}; + static PAD::ControllerBindingType s_input_binding_type = PAD::ControllerBindingType::Unknown; + static std::string s_input_binding_section; + static std::string s_input_binding_key; + static std::string s_input_binding_display_name; + static std::vector s_input_binding_new_bindings; + static Common::Timer s_input_binding_timer; + + ////////////////////////////////////////////////////////////////////////// + // Save State List + ////////////////////////////////////////////////////////////////////////// + struct SaveStateListEntry + { + std::string title; + std::string summary; + std::string path; + std::unique_ptr preview_texture; + s32 slot; + }; + + static void InitializePlaceholderSaveStateListEntry( + SaveStateListEntry* li, const std::string& title, const std::string& serial, u32 crc, s32 slot); + static bool InitializeSaveStateListEntry( + SaveStateListEntry* li, const std::string& title, const std::string& serial, u32 crc, s32 slot); + static void ClearSaveStateEntryList(); + static u32 PopulateSaveStateListEntries(const std::string& title, const std::string& serial, u32 crc); + static bool OpenLoadStateSelectorForGame(const std::string& game_path); + static bool OpenSaveStateSelector(bool is_loading); + static void CloseSaveStateSelector(); + static void DrawSaveStateSelector(bool is_loading, bool fullscreen); + static void DoLoadState(std::string path); + + static std::vector s_save_state_selector_slots; + static std::string s_save_state_selector_game_path; + static bool s_save_state_selector_open = false; + static bool s_save_state_selector_loading = true; + + ////////////////////////////////////////////////////////////////////////// + // Game List + ////////////////////////////////////////////////////////////////////////// + static void DrawGameListWindow(); + static void SwitchToGameList(); + static void PopulateGameListEntryList(); + static HostDisplayTexture* GetTextureForGameListEntryType(GameList::EntryType type); + static HostDisplayTexture* GetGameListCover(const GameList::Entry* entry); + static HostDisplayTexture* GetCoverForCurrentGame(); + static std::string GetNotificationImageForGame(const GameList::Entry* entry); + static std::string GetNotificationImageForGame(const std::string& game_path); + + // Lazily populated cover images. + static std::unordered_map s_cover_image_map; + static std::vector s_game_list_sorted_entries; +} // namespace FullscreenUI + +////////////////////////////////////////////////////////////////////////// +// Utility +////////////////////////////////////////////////////////////////////////// + +std::string FullscreenUI::TimeToPrintableString(time_t t) +{ + struct tm lt = {}; +#ifdef _MSC_VER + localtime_s(<, &t); +#else + localtime_r(&t, <); +#endif + + char buf[256]; + std::strftime(buf, sizeof(buf), "%c", <); + return std::string(buf); +} + +////////////////////////////////////////////////////////////////////////// +// Main +////////////////////////////////////////////////////////////////////////// + +bool FullscreenUI::Initialize() +{ + if (s_initialized) + return true; + + if (s_tried_to_initialize) + return false; + + ImGuiFullscreen::SetTheme(); + ImGuiFullscreen::UpdateLayoutScale(); + + if (!ImGuiManager::AddFullscreenFontsIfMissing() || !ImGuiFullscreen::Initialize("fullscreenui/placeholder.png") || !LoadResources()) + { + DestroyResources(); + ImGuiFullscreen::Shutdown(); + s_tried_to_initialize = true; + return false; + } + + s_initialized = true; + s_current_main_window = MainWindowType::None; + s_current_pause_submenu = PauseSubMenu::None; + s_pause_menu_was_open = false; + s_was_paused_on_quick_menu_open = false; + s_about_window_open = false; + s_hotkey_list_cache = InputManager::GetHotkeyList(); + GetMTGS().SetRunIdle(true); + + if (VMManager::HasValidVM()) + { + UpdateGameDetails(VMManager::GetDiscPath(), VMManager::GetGameSerial(), VMManager::GetGameName(), VMManager::GetGameCRC()); + } + else + { + SwitchToLanding(); + } + + // force vsync on so we don't run at thousands of fps + // Initialize is called on the GS thread, so we can access the display directly. + UpdateForcedVsync(VMManager::GetState() != VMState::Running); + + return true; +} + +bool FullscreenUI::IsInitialized() +{ + return s_initialized; +} + +bool FullscreenUI::HasActiveWindow() +{ + return s_current_main_window != MainWindowType::None || s_save_state_selector_open || ImGuiFullscreen::IsChoiceDialogOpen() || + ImGuiFullscreen::IsFileSelectorOpen(); +} + +void FullscreenUI::UpdateForcedVsync(bool should_force) +{ + // force vsync on so we don't run at thousands of fps + const VsyncMode mode = EmuConfig.GetEffectiveVsyncMode(); + + // toss it through regardless of the mode, because options can change it + Host::GetHostDisplay()->SetVSync((should_force && mode == VsyncMode::Off) ? VsyncMode::On : mode); +} + +void FullscreenUI::OnVMStarted() +{ + if (!IsInitialized()) + return; + + GetMTGS().RunOnGSThread([]() { + if (!IsInitialized()) + return; + + s_current_main_window = MainWindowType::None; + QueueResetFocus(); + }); +} + +void FullscreenUI::OnVMPaused() +{ + if (!IsInitialized()) + return; + + GetMTGS().RunOnGSThread([]() { + if (!IsInitialized()) + return; + + UpdateForcedVsync(true); + }); +} + +void FullscreenUI::OnVMResumed() +{ + if (!IsInitialized()) + return; + + GetMTGS().RunOnGSThread([]() { + if (!IsInitialized()) + return; + + UpdateForcedVsync(false); + }); +} + +void FullscreenUI::OnVMDestroyed() +{ + if (!IsInitialized()) + return; + + GetMTGS().RunOnGSThread([]() { + if (!IsInitialized()) + return; + + s_pause_menu_was_open = false; + SwitchToLanding(); + UpdateForcedVsync(true); + }); +} + +void FullscreenUI::OnRunningGameChanged(std::string path, std::string serial, std::string title, u32 crc) +{ + if (!IsInitialized()) + return; + + GetMTGS().RunOnGSThread([path = std::move(path), serial = std::move(serial), title = std::move(title), crc]() { + if (!IsInitialized()) + return; + + UpdateGameDetails(std::move(path), std::move(serial), std::move(title), crc); + }); +} + +void FullscreenUI::UpdateGameDetails(std::string path, std::string serial, std::string title, u32 crc) +{ + if (!serial.empty()) + s_current_game_subtitle = fmt::format("{0} - {1}", serial, Path::GetFileName(path)); + else + s_current_game_subtitle = {}; + + s_current_game_title = std::move(title); + s_current_game_serial = std::move(serial); + s_current_game_path = std::move(path); + s_current_game_crc = crc; +} + +void FullscreenUI::PauseForMenuOpen() +{ + s_was_paused_on_quick_menu_open = (VMManager::GetState() == VMState::Paused); + if (Host::GetBoolSettingValue("UI", "PauseOnMenu", true) && !s_was_paused_on_quick_menu_open) + Host::RunOnCPUThread([]() { VMManager::SetPaused(true); }); + + s_pause_menu_was_open = true; +} + +void FullscreenUI::OpenPauseMenu() +{ + if (!VMManager::HasValidVM()) + return; + + GetMTGS().RunOnGSThread([]() { + if (!Initialize() || s_current_main_window != MainWindowType::None) + return; + + PauseForMenuOpen(); + s_current_main_window = MainWindowType::PauseMenu; + s_current_pause_submenu = PauseSubMenu::None; + QueueResetFocus(); + }); +} + +void FullscreenUI::ClosePauseMenu() +{ + if (!IsInitialized() || !VMManager::HasValidVM()) + return; + + if (VMManager::GetState() == VMState::Paused && !s_was_paused_on_quick_menu_open) + Host::RunOnCPUThread([]() { VMManager::SetPaused(false); }); + + s_current_main_window = MainWindowType::None; + s_current_pause_submenu = PauseSubMenu::None; + s_pause_menu_was_open = false; + QueueResetFocus(); +} + +void FullscreenUI::OpenPauseSubMenu(PauseSubMenu submenu) +{ + s_current_main_window = MainWindowType::PauseMenu; + s_current_pause_submenu = submenu; + QueueResetFocus(); +} + +void FullscreenUI::Shutdown() +{ + CloseSaveStateSelector(); + s_cover_image_map.clear(); + s_game_list_sorted_entries = {}; + s_game_list_directories_cache = {}; + s_fullscreen_mode_list_cache = {}; + s_graphics_adapter_list_cache = {}; + s_hotkey_list_cache = {}; + s_current_game_title = {}; + s_current_game_subtitle = {}; + s_current_game_serial = {}; + s_current_game_path = {}; + s_current_game_crc = 0; + DestroyResources(); + ImGuiFullscreen::Shutdown(); + s_initialized = false; + s_tried_to_initialize = false; +} + +void FullscreenUI::Render() +{ + if (!s_initialized) + return; + + for (std::unique_ptr& tex : s_cleanup_textures) + tex.reset(); + s_cleanup_textures.clear(); + ImGuiFullscreen::UploadAsyncTextures(); + + ImGuiFullscreen::BeginLayout(); + + switch (s_current_main_window) + { + case MainWindowType::Landing: + DrawLandingWindow(); + break; + case MainWindowType::GameList: + DrawGameListWindow(); + break; + case MainWindowType::Settings: + DrawSettingsWindow(); + break; + case MainWindowType::PauseMenu: + DrawPauseMenu(s_current_main_window); + break; + default: + break; + } + + if (s_save_state_selector_open) + DrawSaveStateSelector(s_save_state_selector_loading, false); + + if (s_about_window_open) + DrawAboutWindow(); + + if (s_input_binding_type != PAD::ControllerBindingType::Unknown) + DrawInputBindingWindow(); + + ImGuiFullscreen::EndLayout(); + + if (s_settings_changed.load(std::memory_order_relaxed)) + { + Host::CommitBaseSettingChanges(); + Host::RunOnCPUThread([]() { VMManager::ApplySettings(); }); + s_settings_changed.store(false, std::memory_order_release); + } + if (s_game_settings_changed.load(std::memory_order_relaxed)) + { + if (s_game_settings_interface) + { + s_game_settings_interface->Save(); + if (VMManager::HasValidVM()) + Host::RunOnCPUThread([]() { VMManager::ReloadGameSettings(); }); + } + s_game_settings_changed.store(false, std::memory_order_release); + } + + ImGuiFullscreen::ResetCloseMenuIfNeeded(); +} + +void FullscreenUI::ReturnToMainWindow() +{ + if (s_pause_menu_was_open) + ClosePauseMenu(); + + s_current_main_window = VMManager::HasValidVM() ? MainWindowType::None : MainWindowType::Landing; +} + +bool FullscreenUI::LoadResources() +{ + s_app_icon_texture = LoadTexture("icons/AppIconLarge.png"); + + s_fallback_disc_texture = LoadTexture("fullscreenui/media-cdrom.png"); + s_fallback_exe_texture = LoadTexture("fullscreenui/applications-system.png"); + + for (u32 i = static_cast(GameDatabaseSchema::Compatibility::Nothing); + i <= static_cast(GameDatabaseSchema::Compatibility::Perfect); i++) + { + s_game_compatibility_textures[i - 1] = LoadTexture(fmt::format("icons/star-{}.png", i - 1).c_str()); + } + + return true; +} + +void FullscreenUI::DestroyResources() +{ + s_app_icon_texture.reset(); + s_fallback_exe_texture.reset(); + s_fallback_disc_texture.reset(); + for (auto& tex : s_game_compatibility_textures) + tex.reset(); + for (auto& tex : s_cleanup_textures) + tex.reset(); +} + +////////////////////////////////////////////////////////////////////////// +// Utility +////////////////////////////////////////////////////////////////////////// + +ImGuiFullscreen::FileSelectorFilters FullscreenUI::GetDiscImageFilters() +{ + return {"*.bin", "*.iso", "*.cue", "*.chd", "*.cso", "*.gz", "*.elf", "*.irx", "*.m3u", "*.gs", "*.gs.xz", "*.gs.zst"}; +} + +void FullscreenUI::DoStartPath(const std::string& path, std::optional state_index, std::optional fast_boot) +{ + VMBootParameters params; + params.filename = path; + params.state_index = state_index; + params.fast_boot = fast_boot; + + Host::RunOnCPUThread([params = std::move(params)]() { + if (VMManager::HasValidVM()) + return; + + if (VMManager::Initialize(params)) + VMManager::SetState(VMState::Running); + else + SwitchToLanding(); + }); + + // switch to nothing, we'll get brought back if init fails + s_current_main_window = MainWindowType::None; +} + +void FullscreenUI::DoStartFile() +{ + auto callback = [](const std::string& path) { + if (!path.empty()) + DoStartPath(path); + + QueueResetFocus(); + CloseFileSelector(); + }; + + OpenFileSelector(ICON_FA_FOLDER_OPEN " Select Disc Image", false, std::move(callback), GetDiscImageFilters()); +} + +void FullscreenUI::DoStartBIOS() +{ + Host::RunOnCPUThread([]() { + if (VMManager::HasValidVM()) + return; + + VMBootParameters params; + if (VMManager::Initialize(params)) + VMManager::SetState(VMState::Running); + else + SwitchToLanding(); + }); + + // switch to nothing, we'll get brought back if init fails + s_current_main_window = MainWindowType::None; +} + +void FullscreenUI::DoStartDisc(const std::string& drive) +{ + Host::RunOnCPUThread([drive]() { + if (VMManager::HasValidVM()) + return; + + VMBootParameters params; + params.filename = std::move(drive); + params.source_type = CDVD_SourceType::Disc; + if (VMManager::Initialize(params)) + VMManager::SetState(VMState::Running); + else + SwitchToLanding(); + }); +} + +void FullscreenUI::DoStartDisc() +{ + std::vector devices(GetOpticalDriveList()); + if (devices.empty()) + { + ShowToast(std::string(), + "Could not find any CD/DVD-ROM devices. Please ensure you have a drive connected and sufficient permissions to access it."); + return; + } + + // if there's only one, select it automatically + if (devices.size() == 1) + { + DoStartDisc(devices.front()); + return; + } + + ImGuiFullscreen::ChoiceDialogOptions options; + for (std::string& drive : devices) + options.emplace_back(std::move(drive), false); + OpenChoiceDialog(ICON_FA_COMPACT_DISC " Select Disc Drive", false, std::move(options), [](s32, const std::string& path, bool) { + DoStartDisc(path); + CloseChoiceDialog(); + QueueResetFocus(); + }); +} + +void FullscreenUI::DoToggleFrameLimit() +{ + Host::RunOnCPUThread([]() { + if (!VMManager::HasValidVM()) + return; + + VMManager::SetLimiterMode( + (EmuConfig.LimiterMode != LimiterModeType::Unlimited) ? LimiterModeType::Unlimited : LimiterModeType::Nominal); + }); +} + +void FullscreenUI::DoToggleSoftwareRenderer() +{ + Host::RunOnCPUThread([]() { + if (!VMManager::HasValidVM()) + return; + + GetMTGS().ToggleSoftwareRendering(); + }); +} + +void FullscreenUI::DoShutdown(bool save_state) +{ + Host::RunOnCPUThread([save_state]() { Host::RequestVMShutdown(false, save_state, save_state); }); +} + +void FullscreenUI::DoReset() +{ + Host::RunOnCPUThread([]() { + if (!VMManager::HasValidVM()) + return; + + VMManager::Reset(); + }); +} + +void FullscreenUI::DoChangeDiscFromFile() +{ + auto callback = [](const std::string& path) { + if (!path.empty()) + { + if (!GameList::IsScannableFilename(path)) + { + ShowToast({}, fmt::format("{} is not a valid disc image.", FileSystem::GetDisplayNameFromPath(path))); + } + else + { + Host::RunOnCPUThread([path]() { VMManager::ChangeDisc(CDVD_SourceType::Iso, std::move(path)); }); + } + } + + QueueResetFocus(); + CloseFileSelector(); + ReturnToMainWindow(); + }; + + OpenFileSelector(ICON_FA_COMPACT_DISC " Select Disc Image", false, std::move(callback), GetDiscImageFilters(), + std::string(Path::GetDirectory(s_current_game_path))); +} + +void FullscreenUI::DoChangeDisc() +{ + DoChangeDiscFromFile(); +} + +void FullscreenUI::DoRequestExit() +{ + Host::RunOnCPUThread([]() { Host::RequestExit(EmuConfig.SaveStateOnShutdown); }); +} + +void FullscreenUI::DoToggleFullscreen() +{ + Host::RunOnCPUThread([]() { Host::SetFullscreen(!Host::IsFullscreen()); }); +} + +////////////////////////////////////////////////////////////////////////// +// Landing Window +////////////////////////////////////////////////////////////////////////// + +void FullscreenUI::SwitchToLanding() +{ + s_current_main_window = MainWindowType::Landing; + QueueResetFocus(); +} + +void FullscreenUI::DrawLandingWindow() +{ + BeginFullscreenColumns(); + + if (BeginFullscreenColumnWindow(0.0f, 570.0f, "logo", UIPrimaryDarkColor)) + { + const float image_size = LayoutScale(380.f); + ImGui::SetCursorPos( + ImVec2((ImGui::GetWindowWidth() * 0.5f) - (image_size * 0.5f), (ImGui::GetWindowHeight() * 0.5f) - (image_size * 0.5f))); + ImGui::Image(s_app_icon_texture->GetHandle(), ImVec2(image_size, image_size)); + + ImGui::SetCursorPos(ImVec2(LayoutScale(20.0f), ImGui::GetWindowHeight() - g_medium_font->FontSize - LayoutScale(20.0f))); + ImGui::PushFont(g_medium_font); + ImGui::Text(GIT_REV); + ImGui::PopFont(); + } + EndFullscreenColumnWindow(); + + if (BeginFullscreenColumnWindow(570.0f, LAYOUT_SCREEN_WIDTH, "menu", UIBackgroundColor)) + { + ResetFocusHere(); + + BeginMenuButtons(6, 0.5f); + + if (MenuButton(" " ICON_FA_LIST " Game List", "Launch a game from images scanned from your game directories.")) + { + SwitchToGameList(); + } + + if (MenuButton(" " ICON_FA_FOLDER_OPEN " Start File", "Launch a game by selecting a file/disc image.")) + { + DoStartFile(); + } + + if (MenuButton(" " ICON_FA_TOOLBOX " Start BIOS", "Start the console without any disc inserted.")) + { + DoStartBIOS(); + } + + if (MenuButton(" " ICON_FA_COMPACT_DISC " Start Disc", "Start a game from a disc in your PC's DVD drive.")) + { + DoStartDisc(); + } + + if (MenuButton(" " ICON_FA_SLIDERS_H " Settings", "Change settings for the emulator.")) + SwitchToSettings(); + + if (MenuButton(" " ICON_FA_SIGN_OUT_ALT " Exit", "Exits the program.")) + { + DoRequestExit(); + } + + { + ImVec2 fullscreen_pos; + if (FloatingButton(ICON_FA_WINDOW_CLOSE, 0.0f, 0.0f, -1.0f, -1.0f, 1.0f, 0.0f, true, g_large_font, &fullscreen_pos)) + { + DoRequestExit(); + } + + if (FloatingButton(ICON_FA_EXPAND, fullscreen_pos.x, 0.0f, -1.0f, -1.0f, -1.0f, 0.0f, true, g_large_font, &fullscreen_pos)) + { + DoToggleFullscreen(); + } + + if (FloatingButton(ICON_FA_QUESTION_CIRCLE, fullscreen_pos.x, 0.0f, -1.0f, -1.0f, -1.0f, 0.0f)) + OpenAboutWindow(); + } + + EndMenuButtons(); + } + + EndFullscreenColumnWindow(); + + EndFullscreenColumns(); +} + +bool FullscreenUI::IsEditingGameSettings(SettingsInterface* bsi) +{ + return (bsi == s_game_settings_interface.get()); +} + +SettingsInterface* FullscreenUI::GetEditingSettingsInterface() +{ + return s_game_settings_interface ? s_game_settings_interface.get() : Host::Internal::GetBaseSettingsLayer(); +} + +SettingsInterface* FullscreenUI::GetEditingSettingsInterface(bool game_settings) +{ + return (game_settings && s_game_settings_interface) ? s_game_settings_interface.get() : Host::Internal::GetBaseSettingsLayer(); +} + +void FullscreenUI::SetSettingsChanged(SettingsInterface* bsi) +{ + if (bsi && bsi == s_game_settings_interface.get()) + s_game_settings_changed.store(true, std::memory_order_release); + else + s_settings_changed.store(true, std::memory_order_release); +} + +bool FullscreenUI::GetEffectiveBoolSetting(SettingsInterface* bsi, const char* section, const char* key, bool default_value) +{ + if (IsEditingGameSettings(bsi)) + { + std::optional value = bsi->GetOptionalBoolValue(section, key, std::nullopt); + if (value.has_value()) + return value.value(); + } + + return Host::Internal::GetBaseSettingsLayer()->GetBoolValue(section, key, default_value); +} + +s32 FullscreenUI::GetEffectiveIntSetting(SettingsInterface* bsi, const char* section, const char* key, s32 default_value) +{ + if (IsEditingGameSettings(bsi)) + { + std::optional value = bsi->GetOptionalIntValue(section, key, std::nullopt); + if (value.has_value()) + return value.value(); + } + + return Host::Internal::GetBaseSettingsLayer()->GetIntValue(section, key, default_value); +} + +void FullscreenUI::DrawInputBindingButton(SettingsInterface* bsi, PAD::ControllerBindingType type, const char* section, const char* name, + const char* display_name, bool show_type) +{ + std::string title(fmt::format("{}/{}", section, name)); + + ImRect bb; + bool visible, hovered, clicked; + clicked = MenuButtonFrame(title.c_str(), true, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, &visible, &hovered, &bb.Min, &bb.Max); + if (!visible) + return; + + const float midpoint = bb.Min.y + g_large_font->FontSize + LayoutScale(4.0f); + const ImRect title_bb(bb.Min, ImVec2(bb.Max.x, midpoint)); + const ImRect summary_bb(ImVec2(bb.Min.x, midpoint), bb.Max); + + if (show_type) + { + switch (type) + { + case PAD::ControllerBindingType::Button: + title = fmt::format(ICON_FA_DOT_CIRCLE " {}", display_name); + break; + case PAD::ControllerBindingType::Axis: + case PAD::ControllerBindingType::HalfAxis: + title = fmt::format(ICON_FA_BULLSEYE " {}", display_name); + break; + case PAD::ControllerBindingType::Motor: + title = fmt::format(ICON_FA_BELL " {}", display_name); + break; + case PAD::ControllerBindingType::Macro: + title = fmt::format(ICON_FA_PIZZA_SLICE " {}", display_name); + break; + default: + title = display_name; + break; + } + } + + ImGui::PushFont(g_large_font); + ImGui::RenderTextClipped( + title_bb.Min, title_bb.Max, show_type ? title.c_str() : display_name, nullptr, nullptr, ImVec2(0.0f, 0.0f), &title_bb); + ImGui::PopFont(); + + const std::string value(bsi->GetStringValue(section, name)); + ImGui::PushFont(g_medium_font); + ImGui::RenderTextClipped( + summary_bb.Min, summary_bb.Max, value.empty() ? "No Binding" : value.c_str(), nullptr, nullptr, ImVec2(0.0f, 0.0f), &summary_bb); + ImGui::PopFont(); + + if (clicked) + { + BeginInputBinding(bsi, type, section, name, display_name); + } + else if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) + { + bsi->DeleteValue(section, name); + SetSettingsChanged(bsi); + } +} + +void FullscreenUI::ClearInputBindingVariables() +{ + s_input_binding_type = PAD::ControllerBindingType::Unknown; + s_input_binding_section = {}; + s_input_binding_key = {}; + s_input_binding_display_name = {}; + s_input_binding_new_bindings = {}; +} + +void FullscreenUI::BeginInputBinding(SettingsInterface* bsi, PAD::ControllerBindingType type, const std::string_view& section, + const std::string_view& key, const std::string_view& display_name) +{ + if (s_input_binding_type != PAD::ControllerBindingType::Unknown) + { + InputManager::RemoveHook(); + ClearInputBindingVariables(); + } + + s_input_binding_type = type; + s_input_binding_section = section; + s_input_binding_key = key; + s_input_binding_display_name = display_name; + s_input_binding_new_bindings = {}; + s_input_binding_timer.Reset(); + + const bool game_settings = IsEditingGameSettings(bsi); + + InputManager::SetHook([game_settings](InputBindingKey key, float value) -> InputInterceptHook::CallbackResult { + // holding the settings lock here will protect the input binding list + auto lock = Host::GetSettingsLock(); + + const float abs_value = std::abs(value); + + for (InputBindingKey other_key : s_input_binding_new_bindings) + { + if (other_key.MaskDirection() == key.MaskDirection()) + { + if (abs_value < 0.5f) + { + // if this key is in our new binding list, it's a "release", and we're done + SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); + const std::string new_binding(InputManager::ConvertInputBindingKeysToString( + s_input_binding_new_bindings.data(), s_input_binding_new_bindings.size())); + bsi->SetStringValue(s_input_binding_section.c_str(), s_input_binding_key.c_str(), new_binding.c_str()); + SetSettingsChanged(bsi); + ClearInputBindingVariables(); + return InputInterceptHook::CallbackResult::RemoveHookAndStopProcessingEvent; + } + + // otherwise, keep waiting + return InputInterceptHook::CallbackResult::StopProcessingEvent; + } + } + + // new binding, add it to the list, but wait for a decent distance first, and then wait for release + if (abs_value >= 0.5f) + { + InputBindingKey key_to_add = key; + key_to_add.negative = (value < 0.0f); + s_input_binding_new_bindings.push_back(key_to_add); + } + + return InputInterceptHook::CallbackResult::StopProcessingEvent; + }); +} + +void FullscreenUI::DrawInputBindingWindow() +{ + pxAssert(s_input_binding_type != PAD::ControllerBindingType::Unknown); + + const double time_remaining = INPUT_BINDING_TIMEOUT_SECONDS - s_input_binding_timer.GetTimeSeconds(); + if (time_remaining <= 0.0) + { + InputManager::RemoveHook(); + ClearInputBindingVariables(); + return; + } + + const char* title = ICON_FA_GAMEPAD " Set Input Binding"; + ImGui::SetNextWindowSize(LayoutScale(500.0f, 0.0f)); + ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + ImGui::OpenPopup(title); + + ImGui::PushFont(g_large_font); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, + LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); + + if (ImGui::BeginPopupModal(title, nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoInputs)) + { + ImGui::TextWrapped("Setting %s binding %s.", s_input_binding_section.c_str(), s_input_binding_display_name.c_str()); + ImGui::TextUnformatted("Push a controller button or axis now."); + ImGui::NewLine(); + ImGui::Text("Timing out in %.0f seconds...", time_remaining); + ImGui::EndPopup(); + } + + ImGui::PopStyleVar(3); + ImGui::PopFont(); +} + +bool FullscreenUI::DrawToggleSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key, + bool default_value, bool enabled, bool allow_tristate, float height, ImFont* font, ImFont* summary_font) +{ + if (!allow_tristate || !IsEditingGameSettings(bsi)) + { + bool value = bsi->GetBoolValue(section, key, default_value); + if (!ToggleButton(title, summary, &value, enabled, height, font, summary_font)) + return false; + + bsi->SetBoolValue(section, key, value); + } + else + { + std::optional value(false); + if (!bsi->GetBoolValue(section, key, &value.value())) + value.reset(); + if (!ThreeWayToggleButton(title, summary, &value, enabled, height, font, summary_font)) + return false; + + if (value.has_value()) + bsi->SetBoolValue(section, key, value.value()); + else + bsi->DeleteValue(section, key); + } + + SetSettingsChanged(bsi); + return true; +} + +void FullscreenUI::DrawIntListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key, + int default_value, const char* const* options, size_t option_count, int option_offset, bool enabled, float height, ImFont* font, + ImFont* summary_font) +{ + const bool game_settings = IsEditingGameSettings(bsi); + const std::optional value = + bsi->GetOptionalIntValue(section, key, game_settings ? std::nullopt : std::optional(default_value)); + const int index = value.has_value() ? (value.value() - option_offset) : std::numeric_limits::min(); + const char* value_text = (value.has_value()) ? + ((index < 0 || static_cast(index) >= option_count) ? "Unknown" : options[index]) : + "Use Global Setting"; + + if (MenuButtonWithValue(title, summary, value_text, enabled, height, font, summary_font)) + { + ImGuiFullscreen::ChoiceDialogOptions cd_options; + cd_options.reserve(option_count + 1); + if (game_settings) + cd_options.emplace_back("Use Global Setting", !value.has_value()); + for (size_t i = 0; i < option_count; i++) + cd_options.emplace_back(options[i], (i == static_cast(index))); + OpenChoiceDialog(title, false, std::move(cd_options), + [game_settings, section, key, option_offset](s32 index, const std::string& title, bool checked) { + if (index >= 0) + { + auto lock = Host::GetSettingsLock(); + SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); + if (game_settings) + { + if (index == 0) + bsi->DeleteValue(section, key); + else + bsi->SetIntValue(section, key, index - 1 + option_offset); + } + else + { + bsi->SetIntValue(section, key, index + option_offset); + } + + SetSettingsChanged(bsi); + } + + CloseChoiceDialog(); + }); + } +} + +void FullscreenUI::DrawIntRangeSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key, + int default_value, int min_value, int max_value, const char* format, bool enabled, float height, ImFont* font, ImFont* summary_font) +{ + const bool game_settings = IsEditingGameSettings(bsi); + const std::optional value = + bsi->GetOptionalIntValue(section, key, game_settings ? std::nullopt : std::optional(default_value)); + const std::string value_text( + value.has_value() ? StringUtil::StdStringFromFormat(format, value.value()) : std::string("Use Global Setting")); + + if (MenuButtonWithValue(title, summary, value_text.c_str(), enabled, height, font, summary_font)) + ImGui::OpenPopup(title); + + ImGui::SetNextWindowSize(LayoutScale(500.0f, 180.0f)); + + ImGui::PushFont(g_large_font); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, + LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); + + if (ImGui::BeginPopupModal(title, nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) + { + ImGui::SetNextItemWidth(LayoutScale(450.0f)); + s32 dlg_value = static_cast(value.value_or(default_value)); + if (ImGui::SliderInt("##value", &dlg_value, min_value, max_value, format, ImGuiSliderFlags_NoInput)) + { + if (IsEditingGameSettings(bsi) && dlg_value == default_value) + bsi->DeleteValue(section, key); + else + bsi->SetIntValue(section, key, dlg_value); + + SetSettingsChanged(bsi); + } + + BeginMenuButtons(); + if (MenuButton("OK", nullptr, true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) + ImGui::CloseCurrentPopup(); + EndMenuButtons(); + + ImGui::EndPopup(); + } + + ImGui::PopStyleVar(3); + ImGui::PopFont(); +} + +void FullscreenUI::DrawFloatRangeSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, + const char* key, float default_value, float min_value, float max_value, const char* format, float multiplier, bool enabled, + float height, ImFont* font, ImFont* summary_font) +{ + const bool game_settings = IsEditingGameSettings(bsi); + const std::optional value = + bsi->GetOptionalFloatValue(section, key, game_settings ? std::nullopt : std::optional(default_value)); + const std::string value_text( + value.has_value() ? StringUtil::StdStringFromFormat(format, value.value() * multiplier) : std::string("Use Global Setting")); + + if (MenuButtonWithValue(title, summary, value_text.c_str(), enabled, height, font, summary_font)) + ImGui::OpenPopup(title); + + ImGui::SetNextWindowSize(LayoutScale(500.0f, 180.0f)); + + ImGui::PushFont(g_large_font); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, + LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); + + if (ImGui::BeginPopupModal(title, nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) + { + ImGui::SetNextItemWidth(LayoutScale(450.0f)); + float dlg_value = value.value_or(default_value) * multiplier; + if (ImGui::SliderFloat("##value", &dlg_value, min_value, max_value, format, ImGuiSliderFlags_NoInput)) + { + dlg_value /= multiplier; + + if (IsEditingGameSettings(bsi) && dlg_value == default_value) + bsi->DeleteValue(section, key); + else + bsi->SetFloatValue(section, key, dlg_value); + + SetSettingsChanged(bsi); + } + + BeginMenuButtons(); + if (MenuButton("OK", nullptr, true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) + ImGui::CloseCurrentPopup(); + EndMenuButtons(); + + ImGui::EndPopup(); + } + + ImGui::PopStyleVar(3); + ImGui::PopFont(); +} + +void FullscreenUI::DrawIntRectSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, + const char* left_key, int default_left, const char* top_key, int default_top, const char* right_key, int default_right, + const char* bottom_key, int default_bottom, int min_value, int max_value, const char* format, bool enabled, float height, ImFont* font, + ImFont* summary_font) +{ + const bool game_settings = IsEditingGameSettings(bsi); + const std::optional left_value = + bsi->GetOptionalIntValue(section, left_key, game_settings ? std::nullopt : std::optional(default_left)); + const std::optional top_value = + bsi->GetOptionalIntValue(section, top_key, game_settings ? std::nullopt : std::optional(default_top)); + const std::optional right_value = + bsi->GetOptionalIntValue(section, right_key, game_settings ? std::nullopt : std::optional(default_right)); + const std::optional bottom_value = + bsi->GetOptionalIntValue(section, bottom_key, game_settings ? std::nullopt : std::optional(default_bottom)); + const std::string value_text(fmt::format("{}/{}/{}/{}", + left_value.has_value() ? StringUtil::StdStringFromFormat(format, left_value.value()) : std::string("Default"), + top_value.has_value() ? StringUtil::StdStringFromFormat(format, top_value.value()) : std::string("Default"), + right_value.has_value() ? StringUtil::StdStringFromFormat(format, right_value.value()) : std::string("Default"), + bottom_value.has_value() ? StringUtil::StdStringFromFormat(format, bottom_value.value()) : std::string("Default"))); + + if (MenuButtonWithValue(title, summary, value_text.c_str(), enabled, height, font, summary_font)) + ImGui::OpenPopup(title); + + ImGui::SetNextWindowSize(LayoutScale(500.0f, 320.0f)); + + ImGui::PushFont(g_large_font); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, + LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); + + if (ImGui::BeginPopupModal(title, nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) + { + s32 dlg_left_value = static_cast(left_value.value_or(default_left)); + s32 dlg_top_value = static_cast(top_value.value_or(default_top)); + s32 dlg_right_value = static_cast(right_value.value_or(default_right)); + s32 dlg_bottom_value = static_cast(bottom_value.value_or(default_bottom)); + + ImGui::TextUnformatted("Left: "); + ImGui::SameLine(); + const bool left_modified = ImGui::SliderInt("##left", &dlg_left_value, min_value, max_value, format, ImGuiSliderFlags_NoInput); + ImGui::TextUnformatted("Top: "); + ImGui::SameLine(); + const bool top_modified = ImGui::SliderInt("##top", &dlg_top_value, min_value, max_value, format, ImGuiSliderFlags_NoInput); + ImGui::TextUnformatted("Right: "); + ImGui::SameLine(); + const bool right_modified = ImGui::SliderInt("##right", &dlg_right_value, min_value, max_value, format, ImGuiSliderFlags_NoInput); + ImGui::TextUnformatted("Bottom: "); + ImGui::SameLine(); + const bool bottom_modified = + ImGui::SliderInt("##bottom", &dlg_bottom_value, min_value, max_value, format, ImGuiSliderFlags_NoInput); + if (left_modified) + { + if (IsEditingGameSettings(bsi) && dlg_left_value == default_left) + bsi->DeleteValue(section, left_key); + else + bsi->SetIntValue(section, left_key, dlg_left_value); + } + if (top_modified) + { + if (IsEditingGameSettings(bsi) && dlg_top_value == default_top) + bsi->DeleteValue(section, top_key); + else + bsi->SetIntValue(section, top_key, dlg_top_value); + } + if (right_modified) + { + if (IsEditingGameSettings(bsi) && dlg_right_value == default_right) + bsi->DeleteValue(section, right_key); + else + bsi->SetIntValue(section, right_key, dlg_right_value); + } + if (bottom_modified) + { + if (IsEditingGameSettings(bsi) && dlg_bottom_value == default_bottom) + bsi->DeleteValue(section, bottom_key); + else + bsi->SetIntValue(section, bottom_key, dlg_bottom_value); + } + + if (left_modified || top_modified || right_modified || bottom_modified) + SetSettingsChanged(bsi); + + BeginMenuButtons(); + if (MenuButton("OK", nullptr, true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) + ImGui::CloseCurrentPopup(); + EndMenuButtons(); + + ImGui::EndPopup(); + } + + ImGui::PopStyleVar(3); + ImGui::PopFont(); +} + +void FullscreenUI::DrawStringListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, + const char* key, const char* default_value, const char* const* options, const char* const* option_values, size_t option_count, + bool enabled, float height, ImFont* font, ImFont* summary_font) +{ + const bool game_settings = IsEditingGameSettings(bsi); + const std::optional value( + bsi->GetOptionalStringValue(section, key, game_settings ? std::nullopt : std::optional(default_value))); + + if (option_count == 0) + { + // select from null entry + while (options && options[option_count] != nullptr) + option_count++; + } + + size_t index = option_count; + if (value.has_value()) + { + for (size_t i = 0; i < option_count; i++) + { + if (value == option_values[i]) + { + index = i; + break; + } + } + } + + if (MenuButtonWithValue(title, summary, + value.has_value() ? ((index < option_count) ? options[index] : "Unknown") : "Use Global Setting", enabled, height, font, + summary_font)) + { + ImGuiFullscreen::ChoiceDialogOptions cd_options; + cd_options.reserve(option_count + 1); + if (game_settings) + cd_options.emplace_back("Use Global Setting", !value.has_value()); + for (size_t i = 0; i < option_count; i++) + cd_options.emplace_back(options[i], (value.has_value() && i == static_cast(index))); + OpenChoiceDialog(title, false, std::move(cd_options), + [game_settings, section, key, option_values](s32 index, const std::string& title, bool checked) { + if (index >= 0) + { + auto lock = Host::GetSettingsLock(); + SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); + if (game_settings) + { + if (index == 0) + bsi->DeleteValue(section, key); + else + bsi->SetStringValue(section, key, option_values[index - 1]); + } + else + { + bsi->SetStringValue(section, key, option_values[index]); + } + + SetSettingsChanged(bsi); + } + + CloseChoiceDialog(); + }); + } +} + +void FullscreenUI::DrawFloatListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, + const char* key, float default_value, const char* const* options, const float* option_values, size_t option_count, bool enabled, + float height, ImFont* font, ImFont* summary_font) +{ + const bool game_settings = IsEditingGameSettings(bsi); + const std::optional value( + bsi->GetOptionalFloatValue(section, key, game_settings ? std::nullopt : std::optional(default_value))); + + if (option_count == 0) + { + // select from null entry + while (options && options[option_count] != nullptr) + option_count++; + } + + size_t index = option_count; + if (value.has_value()) + { + for (size_t i = 0; i < option_count; i++) + { + if (value == option_values[i]) + { + index = i; + break; + } + } + } + + if (MenuButtonWithValue(title, summary, + value.has_value() ? ((index < option_count) ? options[index] : "Unknown") : "Use Global Setting", enabled, height, font, + summary_font)) + { + ImGuiFullscreen::ChoiceDialogOptions cd_options; + cd_options.reserve(option_count + 1); + if (game_settings) + cd_options.emplace_back("Use Global Setting", !value.has_value()); + for (size_t i = 0; i < option_count; i++) + cd_options.emplace_back(options[i], (value.has_value() && i == static_cast(index))); + OpenChoiceDialog(title, false, std::move(cd_options), + [game_settings, section, key, option_values](s32 index, const std::string& title, bool checked) { + if (index >= 0) + { + auto lock = Host::GetSettingsLock(); + SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); + if (game_settings) + { + if (index == 0) + bsi->DeleteValue(section, key); + else + bsi->SetFloatValue(section, key, option_values[index - 1]); + } + else + { + bsi->SetFloatValue(section, key, option_values[index]); + } + + SetSettingsChanged(bsi); + } + + CloseChoiceDialog(); + }); + } +} + +void FullscreenUI::DrawFolderSetting(SettingsInterface* bsi, const char* title, const char* section, const char* key, + const std::string& runtime_var, float height /* = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT */, ImFont* font /* = g_large_font */, + ImFont* summary_font /* = g_medium_font */) +{ + if (MenuButton(title, runtime_var.c_str())) + { + OpenFileSelector(title, true, + [game_settings = IsEditingGameSettings(bsi), section = std::string(section), key = std::string(key)](const std::string& dir) { + if (dir.empty()) + return; + + auto lock = Host::GetSettingsLock(); + SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); + std::string relative_path(Path::MakeRelative(dir, EmuFolders::DataRoot)); + bsi->SetStringValue(section.c_str(), key.c_str(), relative_path.c_str()); + SetSettingsChanged(bsi); + + Host::RunOnCPUThread(&Host::Internal::UpdateEmuFolders); + + CloseFileSelector(); + }); + } +} + +void FullscreenUI::StartAutomaticBinding(u32 port) +{ + // messy because the enumeration has to happen on the input thread + Host::RunOnCPUThread([port]() { + std::vector> devices(InputManager::EnumerateDevices()); + GetMTGS().RunOnGSThread([port, devices = std::move(devices)]() { + if (devices.empty()) + { + ShowToast({}, "Automatic binding failed, no devices are available."); + return; + } + + std::vector names; + ImGuiFullscreen::ChoiceDialogOptions options; + options.reserve(devices.size()); + names.reserve(devices.size()); + for (auto& [name, display_name] : devices) + { + names.push_back(std::move(name)); + options.emplace_back(std::move(display_name), false); + } + OpenChoiceDialog("Select Device", false, std::move(options), + [port, names = std::move(names)](s32 index, const std::string& title, bool checked) { + if (index < 0) + return; + + // since this is working with the device, it has to happen on the input thread too + Host::RunOnCPUThread([port, name = std::move(names[index])]() { + auto lock = Host::GetSettingsLock(); + const bool result = + PAD::MapController(*GetEditingSettingsInterface(), port, InputManager::GetGenericBindingMapping(name)); + + // and the toast needs to happen on the UI thread. + GetMTGS().RunOnGSThread([result, name = std::move(name)]() { + ShowToast({}, result ? fmt::format("Automatic mapping completed for {}.", name) : + fmt::format("Automatic mapping failed for {}.", name)); + }); + }); + CloseChoiceDialog(); + }); + }); + }); +} + +void FullscreenUI::SwitchToSettings() +{ + s_game_settings_entry.reset(); + s_game_settings_interface.reset(); + + // populate the cache with all settings from ini + auto lock = Host::GetSettingsLock(); + SettingsInterface* bsi = Host::Internal::GetBaseSettingsLayer(); + + PopulateGameListDirectoryCache(bsi); + PopulateGraphicsAdapterList(); + + s_current_main_window = MainWindowType::Settings; + s_settings_page = SettingsPage::Interface; +} + +void FullscreenUI::SwitchToGameSettings(const std::string_view& serial, u32 crc) +{ + s_game_settings_entry.reset(); + s_game_settings_interface = std::make_unique(VMManager::GetGameSettingsPath(serial, crc)); + s_game_settings_interface->Load(); + s_current_main_window = MainWindowType::Settings; + s_settings_page = SettingsPage::Summary; + QueueResetFocus(); +} + +void FullscreenUI::SwitchToGameSettings() +{ + if (s_current_game_serial.empty() || s_current_game_crc == 0) + return; + + auto lock = GameList::GetLock(); + const GameList::Entry* entry = GameList::GetEntryForPath(s_current_game_path.c_str()); + if (!entry) + { + entry = GameList::GetEntryBySerialAndCRC(s_current_game_serial.c_str(), s_current_game_crc); + if (!entry) + { + SwitchToGameSettings(s_current_game_serial.c_str(), s_current_game_crc); + return; + } + } + + SwitchToGameSettings(entry); +} + +void FullscreenUI::SwitchToGameSettings(const std::string& path) +{ + auto lock = GameList::GetLock(); + const GameList::Entry* entry = GameList::GetEntryForPath(path.c_str()); + if (entry) + SwitchToGameSettings(entry); +} + +void FullscreenUI::SwitchToGameSettings(const GameList::Entry* entry) +{ + SwitchToGameSettings(entry->serial.c_str(), entry->crc); + s_game_settings_entry = std::make_unique(*entry); +} + +void FullscreenUI::PopulateGraphicsAdapterList() +{ + HostDisplay* display = Host::GetHostDisplay(); + HostDisplay::AdapterAndModeList ml(display->GetAdapterAndModeList()); + s_graphics_adapter_list_cache = std::move(ml.adapter_names); + s_fullscreen_mode_list_cache = std::move(ml.fullscreen_modes); + s_fullscreen_mode_list_cache.insert(s_fullscreen_mode_list_cache.begin(), "Borderless Fullscreen"); +} + +void FullscreenUI::PopulateGameListDirectoryCache(SettingsInterface* si) +{ + s_game_list_directories_cache.clear(); + for (std::string& dir : si->GetStringList("GameList", "Paths")) + s_game_list_directories_cache.emplace_back(std::move(dir), false); + for (std::string& dir : si->GetStringList("GameList", "RecursivePaths")) + s_game_list_directories_cache.emplace_back(std::move(dir), true); +} + +ImGuiFullscreen::ChoiceDialogOptions FullscreenUI::GetGameListDirectoryOptions(bool recursive_as_checked) +{ + ImGuiFullscreen::ChoiceDialogOptions options; + for (const auto& it : s_game_list_directories_cache) + options.emplace_back(it.first, it.second && recursive_as_checked); + return options; +} + +void FullscreenUI::DoCopyGameSettings() +{ + if (!s_game_settings_interface) + return; + + Pcsx2Config temp; + { + SettingsLoadWrapper wrapper(*GetEditingSettingsInterface(false)); + temp.LoadSave(wrapper); + } + { + SettingsSaveWrapper wrapper(*s_game_settings_interface.get()); + temp.LoadSave(wrapper); + } + + SetSettingsChanged(s_game_settings_interface.get()); + + ShowToast(std::string(), fmt::format("Game settings initialized with global settings for '{}'.", + Path::GetFileTitle(s_game_settings_interface->GetFileName()))); +} + +void FullscreenUI::DoClearGameSettings() +{ + if (!s_game_settings_interface) + return; + + s_game_settings_interface->Clear(); + if (!s_game_settings_interface->GetFileName().empty()) + FileSystem::DeleteFilePath(s_game_settings_interface->GetFileName().c_str()); + + SetSettingsChanged(s_game_settings_interface.get()); + + ShowToast(std::string(), + fmt::format("Game settings have been cleared for '{}'.", Path::GetFileTitle(s_game_settings_interface->GetFileName()))); +} + +void FullscreenUI::DrawSettingsWindow() +{ + ImGuiIO& io = ImGui::GetIO(); + ImVec2 heading_size = + ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY + LAYOUT_MENU_BUTTON_Y_PADDING * 2.0f + 2.0f)); + + const float bg_alpha = VMManager::HasValidVM() ? 0.90f : 1.0f; + + if (BeginFullscreenWindow(ImVec2(0.0f, 0.0f), heading_size, "settings_category", UIPrimaryColor)) + { + static constexpr float ITEM_WIDTH = 22.0f; + + static constexpr const char* global_icons[] = {ICON_FA_WINDOW_MAXIMIZE, ICON_FA_LIST, ICON_FA_MICROCHIP, ICON_FA_SLIDERS_H, + ICON_FA_HDD, ICON_FA_MAGIC, ICON_FA_HEADPHONES, ICON_FA_SD_CARD, ICON_FA_GAMEPAD, ICON_FA_KEYBOARD, ICON_FA_TROPHY, + ICON_FA_COGS}; + static constexpr const char* per_game_icons[] = {ICON_FA_PARAGRAPH, ICON_FA_SLIDERS_H, ICON_FA_HDD, ICON_FA_MAGIC, + ICON_FA_HEADPHONES, ICON_FA_SD_CARD, ICON_FA_GAMEPAD, ICON_FA_BAN}; + static constexpr SettingsPage global_pages[] = {SettingsPage::Interface, SettingsPage::GameList, SettingsPage::BIOS, + SettingsPage::Emulation, SettingsPage::System, SettingsPage::Graphics, SettingsPage::Audio, SettingsPage::MemoryCard, + SettingsPage::Controller, SettingsPage::Hotkey, SettingsPage::Achievements, SettingsPage::Advanced}; + static constexpr SettingsPage per_game_pages[] = {SettingsPage::Summary, SettingsPage::Emulation, SettingsPage::System, + SettingsPage::Graphics, SettingsPage::Audio, SettingsPage::MemoryCard, SettingsPage::Controller, SettingsPage::GameFixes}; + static constexpr const char* titles[] = {"Summary", "Interface Settings", "Game List Settings", "BIOS Settings", + "Emulation Settings", "System Settings", "Graphics Settings", "Audio Settings", "Memory Card Settings", "Controller Settings", + "Hotkey Settings", "Achievements Settings", "Advanced Settings", "Game Fixes"}; + + const bool game_settings = IsEditingGameSettings(GetEditingSettingsInterface()); + const u32 count = game_settings ? std::size(per_game_pages) : std::size(global_pages); + const char* const* icons = game_settings ? per_game_icons : global_icons; + const SettingsPage* pages = game_settings ? per_game_pages : global_pages; + u32 index = 0; + for (u32 i = 0; i < count; i++) + { + if (pages[i] == s_settings_page) + { + index = i; + break; + } + } + + BeginNavBar(); + + if (ImGui::IsNavInputTest(ImGuiNavInput_FocusPrev, ImGuiNavReadMode_Pressed)) + { + index = (index == 0) ? (count - 1) : (index - 1); + s_settings_page = pages[index]; + } + else if (ImGui::IsNavInputTest(ImGuiNavInput_FocusNext, ImGuiNavReadMode_Pressed)) + { + index = (index + 1) % count; + s_settings_page = pages[index]; + } + + if (NavButton(ICON_FA_BACKWARD, true, true)) + ReturnToMainWindow(); + + if (s_game_settings_entry) + NavTitle(fmt::format("{} ({})", titles[static_cast(pages[index])], s_game_settings_entry->title).c_str()); + else + NavTitle(titles[static_cast(pages[index])]); + + RightAlignNavButtons(count, ITEM_WIDTH, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); + + for (u32 i = 0; i < count; i++) + { + if (NavButton(icons[i], i == index, true, ITEM_WIDTH, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) + { + s_settings_page = pages[i]; + } + } + + EndNavBar(); + } + + EndFullscreenWindow(); + + if (BeginFullscreenWindow(ImVec2(0.0f, heading_size.y), ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y), "settings_parent", + ImVec4(UIBackgroundColor.x, UIBackgroundColor.y, UIBackgroundColor.z, bg_alpha))) + { + ResetFocusHere(); + + if (WantsToCloseMenu()) + { + if (ImGui::IsWindowFocused()) + ReturnToMainWindow(); + } + + auto lock = Host::GetSettingsLock(); + + switch (s_settings_page) + { + case SettingsPage::Summary: + DrawSummarySettingsPage(); + break; + + case SettingsPage::Interface: + DrawInterfaceSettingsPage(); + break; + + case SettingsPage::GameList: + DrawGameListSettingsPage(); + break; + + case SettingsPage::BIOS: + DrawBIOSSettingsPage(); + break; + + case SettingsPage::Emulation: + DrawEmulationSettingsPage(); + break; + + case SettingsPage::System: + DrawSystemSettingsPage(); + break; + + case SettingsPage::Graphics: + DrawGraphicsSettingsPage(); + break; + + case SettingsPage::Audio: + DrawAudioSettingsPage(); + break; + + case SettingsPage::MemoryCard: + DrawMemoryCardSettingsPage(); + break; + + case SettingsPage::Controller: + DrawControllerSettingsPage(); + break; + + case SettingsPage::Hotkey: + DrawHotkeySettingsPage(); + break; + + case SettingsPage::Achievements: + DrawAchievementsSettingsPage(); + break; + + case SettingsPage::Advanced: + DrawAdvancedSettingsPage(); + break; + + case SettingsPage::GameFixes: + DrawGameFixesSettingsPage(); + break; + + default: + break; + } + } + + EndFullscreenWindow(); +} + +void FullscreenUI::DrawSummarySettingsPage() +{ + BeginMenuButtons(); + + MenuHeading("Details"); + + if (s_game_settings_entry) + { + if (MenuButton(ICON_FA_WINDOW_MAXIMIZE " Title", s_game_settings_entry->title.c_str(), true)) + CopyTextToClipboard("Game title copied to clipboard.", s_game_settings_entry->title); + if (MenuButton(ICON_FA_PAGER " Serial", s_game_settings_entry->serial.c_str(), true)) + CopyTextToClipboard("Game serial copied to clipboard.", s_game_settings_entry->serial); + if (MenuButton(ICON_FA_CODE " CRC", fmt::format("{:08X}", s_game_settings_entry->crc).c_str(), true)) + CopyTextToClipboard("Game CRC copied to clipboard.", fmt::format("{:08X}", s_game_settings_entry->crc)); + if (MenuButton(ICON_FA_COMPACT_DISC " Type", GameList::EntryTypeToString(s_game_settings_entry->type), true)) + CopyTextToClipboard("Game type copied to clipboard.", GameList::EntryTypeToString(s_game_settings_entry->type)); + if (MenuButton(ICON_FA_BOX " Region", GameList::RegionToString(s_game_settings_entry->region), true)) + CopyTextToClipboard("Game region copied to clipboard.", GameList::RegionToString(s_game_settings_entry->region)); + if (MenuButton(ICON_FA_STAR " Compatibility Rating", + GameList::EntryCompatibilityRatingToString(s_game_settings_entry->compatibility_rating), true)) + { + CopyTextToClipboard("Game compatibility copied to clipboard.", + GameList::EntryCompatibilityRatingToString(s_game_settings_entry->compatibility_rating)); + } + if (MenuButton(ICON_FA_FOLDER_OPEN " Path", s_game_settings_entry->path.c_str(), true)) + CopyTextToClipboard("Game path copied to clipboard.", s_game_settings_entry->path); + } + else + { + MenuButton(ICON_FA_BAN " Details unavailable for game not scanned in game list.", ""); + } + + MenuHeading("Options"); + + if (MenuButton(ICON_FA_COPY " Copy Settings", "Copies the current global settings to this game.")) + DoCopyGameSettings(); + if (MenuButton(ICON_FA_TRASH " Clear Settings", "Clears all settings set for this game.")) + DoClearGameSettings(); + + EndMenuButtons(); +} + +void FullscreenUI::DrawInterfaceSettingsPage() +{ + SettingsInterface* bsi = GetEditingSettingsInterface(); + + BeginMenuButtons(); + + MenuHeading("Behaviour"); + + DrawToggleSetting(bsi, ICON_FA_MAGIC " Inhibit Screensaver", + "Prevents the screen saver from activating and the host from sleeping while emulation is running.", "UI", "InhibitScreensaver", + true); +#ifdef WITH_DISCORD_PRESENCE + DrawToggleSetting(bsi, "Enable Discord Presence", "Shows the game you are currently playing as part of your profile on Discord.", "UI", + "DiscordPresence", false); +#endif + DrawToggleSetting(bsi, ICON_FA_PAUSE " Pause On Start", "Pauses the emulator when a game is started.", "UI", "StartPaused", false); + DrawToggleSetting(bsi, ICON_FA_VIDEO " Pause On Focus Loss", + "Pauses the emulator when you minimize the window or switch to another application, and unpauses when you switch back.", "UI", + "PauseOnFocusLoss", false); + DrawToggleSetting(bsi, ICON_FA_WINDOW_MAXIMIZE " Pause On Menu", + "Pauses the emulator when you open the quick menu, and unpauses when you close it.", "UI", "PauseOnMenu", true); + DrawToggleSetting(bsi, ICON_FA_POWER_OFF " Confirm Shutdown", + "Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed.", "UI", + "ConfirmShutdown", true); + DrawToggleSetting(bsi, ICON_FA_SAVE " Save State On Shutdown", + "Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next " + "time.", + "EmuCore", "SaveStateOnShutdown", false); + + MenuHeading("Game Display"); + DrawToggleSetting(bsi, ICON_FA_TV " Start Fullscreen", "Automatically switches to fullscreen mode when the program is started.", "UI", + "StartFullscreen", false); + DrawToggleSetting(bsi, ICON_FA_MOUSE " Double-Click Toggles Fullscreen", + "Switches between full screen and windowed when the window is double-clicked.", "UI", "DoubleClickTogglesFullscreen", true); + DrawToggleSetting(bsi, ICON_FA_MOUSE_POINTER " Hide Cursor In Fullscreen", + "Hides the mouse pointer/cursor when the emulator is in fullscreen mode.", "UI", "HideMouseCursor", false); + + MenuHeading("On-Screen Display"); + DrawIntRangeSetting(bsi, ICON_FA_SEARCH " OSD Scale", "Determines how large the on-screen messages and monitor are.", "EmuCore/GS", + "OsdScale", 100, 25, 500, "%d%%"); + DrawToggleSetting(bsi, ICON_FA_LIST " Show Messages", + "Shows on-screen-display messages when events occur such as save states being created/loaded, screenshots being taken, etc.", + "EmuCore/GS", "OsdShowMessages", true); + DrawToggleSetting(bsi, ICON_FA_CLOCK " Show Speed", + "Shows the current emulation speed of the system in the top-right corner of the display as a percentage.", "EmuCore/GS", + "OsdShowSpeed", false); + DrawToggleSetting(bsi, ICON_FA_RULER " Show FPS", + "Shows the number of video frames (or v-syncs) displayed per second by the system in the top-right corner of the display.", + "EmuCore/GS", "OsdShowFPS", false); + DrawToggleSetting(bsi, ICON_FA_BATTERY_HALF " Show CPU Usage", + "Shows the CPU usage based on threads in the top-right corner of the display.", "EmuCore/GS", "OsdShowCPU", false); + DrawToggleSetting(bsi, ICON_FA_SPINNER " Show GPU Usage", "Shows the host's GPU usage in the top-right corner of the display.", + "EmuCore/GS", "OsdShowGPU", false); + DrawToggleSetting(bsi, ICON_FA_RULER_VERTICAL " Show Resolution", + "Shows the resolution the game is rendering at in the top-right corner of the display.", "EmuCore/GS", "OsdShowResolution", false); + DrawToggleSetting(bsi, ICON_FA_BARS " Show GS Statistics", + "Shows statistics about GS (primitives, draw calls) in the top-right corner of the display.", "EmuCore/GS", "OsdShowGSStats", + false); + DrawToggleSetting(bsi, ICON_FA_PLAY " Show Status Indicators", + "Shows indicators when fast forwarding, pausing, and other abnormal states are active.", "EmuCore/GS", "OsdShowIndicators", true); + + EndMenuButtons(); +} + +void FullscreenUI::DrawGameListSettingsPage() +{ + BeginMenuButtons(); + + MenuHeading("Game List"); + + if (MenuButton(ICON_FA_FOLDER_PLUS " Add Search Directory", "Adds a new directory to the game search list.")) + { + OpenFileSelector(ICON_FA_FOLDER_PLUS " Add Search Directory", true, [](const std::string& dir) { + if (!dir.empty()) + { + auto lock = Host::GetSettingsLock(); + SettingsInterface* bsi = Host::Internal::GetBaseSettingsLayer(); + + bsi->AddToStringList("GameList", "RecursivePaths", dir.c_str()); + bsi->RemoveFromStringList("GameList", "Paths", dir.c_str()); + bsi->Save(); + PopulateGameListDirectoryCache(bsi); + Host::RefreshGameListAsync(false); + } + + CloseFileSelector(); + }); + } + + if (MenuButton( + ICON_FA_FOLDER_OPEN " Change Recursive Directories", "Sets whether subdirectories are searched for each game directory")) + { + OpenChoiceDialog(ICON_FA_FOLDER_OPEN " Change Recursive Directories", true, GetGameListDirectoryOptions(true), + [](s32 index, const std::string& title, bool checked) { + if (index < 0) + return; + + auto lock = Host::GetSettingsLock(); + SettingsInterface* bsi = Host::Internal::GetBaseSettingsLayer(); + if (checked) + { + bsi->RemoveFromStringList("GameList", "Paths", title.c_str()); + bsi->AddToStringList("GameList", "RecursivePaths", title.c_str()); + } + else + { + bsi->RemoveFromStringList("GameList", "RecursivePaths", title.c_str()); + bsi->AddToStringList("GameList", "Paths", title.c_str()); + } + + bsi->Save(); + PopulateGameListDirectoryCache(bsi); + Host::RefreshGameListAsync(false); + }); + } + + if (MenuButton(ICON_FA_FOLDER_MINUS " Remove Search Directory", "Removes a directory from the game search list.")) + { + OpenChoiceDialog(ICON_FA_FOLDER_MINUS " Remove Search Directory", false, GetGameListDirectoryOptions(false), + [](s32 index, const std::string& title, bool checked) { + if (index < 0) + return; + + auto lock = Host::GetSettingsLock(); + SettingsInterface* bsi = Host::Internal::GetBaseSettingsLayer(); + bsi->RemoveFromStringList("GameList", "Paths", title.c_str()); + bsi->RemoveFromStringList("GameList", "RecursivePaths", title.c_str()); + bsi->Save(); + PopulateGameListDirectoryCache(bsi); + Host::RefreshGameListAsync(false); + CloseChoiceDialog(); + }); + } + + if (MenuButton(ICON_FA_SEARCH " Scan For New Games", "Identifies any new files added to the game directories.")) + Host::RefreshGameListAsync(false); + if (MenuButton(ICON_FA_SEARCH_PLUS " Rescan All Games", "Forces a full rescan of all games previously identified.")) + Host::RefreshGameListAsync(true); + + MenuHeading("Search Directories"); + for (const auto& it : s_game_list_directories_cache) + MenuButton(it.first.c_str(), it.second ? "Scanning Subdirectories" : "Not Scanning Subdirectories", false); + + EndMenuButtons(); +} + +void FullscreenUI::DrawBIOSSettingsPage() +{ + SettingsInterface* bsi = GetEditingSettingsInterface(); + + BeginMenuButtons(); + + MenuHeading("BIOS Configuration"); + + DrawFolderSetting(bsi, ICON_FA_FOLDER_OPEN " Change Search Directory", "Folders", "Bios", EmuFolders::Bios); + + const std::string bios_selection(GetEditingSettingsInterface()->GetStringValue("Filenames", "BIOS", "")); + if (MenuButtonWithValue(ICON_FA_MICROCHIP " BIOS Selection", "Changes the BIOS image used to start future sessions.", + bios_selection.empty() ? "Automatic" : bios_selection.c_str())) + { + ImGuiFullscreen::ChoiceDialogOptions choices; + choices.emplace_back("Automatic", bios_selection.empty()); + + std::vector values; + values.push_back(""); + + FileSystem::FindResultsArray results; + FileSystem::FindFiles(EmuFolders::Bios.c_str(), "*", FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES, &results); + for (const FILESYSTEM_FIND_DATA& fd : results) + { + u32 version, region; + std::string description, zone; + if (!IsBIOS(fd.FileName.c_str(), version, description, region, zone)) + continue; + + const std::string_view filename(Path::GetFileName(fd.FileName)); + choices.emplace_back(fmt::format("{} ({})", description, filename), bios_selection == filename); + values.emplace_back(filename); + } + + OpenChoiceDialog("BIOS Selection", false, std::move(choices), + [game_settings = IsEditingGameSettings(bsi), values = std::move(values)](s32 index, const std::string& title, bool checked) { + if (index < 0) + return; + + auto lock = Host::GetSettingsLock(); + SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); + bsi->SetStringValue("Filenames", "BIOS", values[index].c_str()); + SetSettingsChanged(bsi); + CloseChoiceDialog(); + }); + } + + MenuHeading("Options and Patches"); + DrawToggleSetting( + bsi, ICON_FA_LIGHTBULB " Fast Boot", "Skips the intro screen, and bypasses region checks.", "EmuCore", "EnableFastBoot", true); + + EndMenuButtons(); +} + +void FullscreenUI::DrawEmulationSettingsPage() +{ + static constexpr int DEFAULT_FRAME_LATENCY = 2; + + static constexpr const char* speed_entries[] = { + "2% [1 FPS (NTSC) / 1 FPS (PAL)]", + "10% [6 FPS (NTSC) / 5 FPS (PAL)]", + "25% [15 FPS (NTSC) / 12 FPS (PAL)]", + "50% [30 FPS (NTSC) / 25 FPS (PAL)]", + "75% [45 FPS (NTSC) / 37 FPS (PAL)]", + "90% [54 FPS (NTSC) / 45 FPS (PAL)]", + "100% [60 FPS (NTSC) / 50 FPS (PAL)]", + "110% [66 FPS (NTSC) / 55 FPS (PAL)]", + "120% [72 FPS (NTSC) / 60 FPS (PAL)]", + "150% [90 FPS (NTSC) / 75 FPS (PAL)]", + "175% [105 FPS (NTSC) / 87 FPS (PAL)]", + "200% [120 FPS (NTSC) / 100 FPS (PAL)]", + "300% [180 FPS (NTSC) / 150 FPS (PAL)]", + "400% [240 FPS (NTSC) / 200 FPS (PAL)]", + "500% [300 FPS (NTSC) / 250 FPS (PAL)]", + "1000% [600 FPS (NTSC) / 500 FPS (PAL)]", + }; + static constexpr const float speed_values[] = { + 0.02f, + 0.10f, + 0.25f, + 0.50f, + 0.75f, + 0.90f, + 1.00f, + 1.10f, + 1.20f, + 1.50f, + 1.75f, + 2.00f, + 3.00f, + 4.00f, + 5.00f, + 10.00f, + }; + static constexpr const char* queue_entries[] = {"0 Frames (Hard Sync)", "1 Frame", "2 Frames", "3 Frames"}; + + SettingsInterface* bsi = GetEditingSettingsInterface(); + + BeginMenuButtons(); + + MenuHeading("Speed Control"); + + DrawFloatListSetting(bsi, "Normal Speed", "Sets the speed when running without fast forwarding.", "Framerate", "NominalScalar", 1.00f, + speed_entries, speed_values, std::size(speed_entries)); + DrawFloatListSetting(bsi, "Fast Forward Speed", "Sets the speed when using the fast forward hotkey.", "Framerate", "TurboScalar", 2.00f, + speed_entries, speed_values, std::size(speed_entries)); + DrawFloatListSetting(bsi, "Slow Motion Speed", "Sets the speed when using the slow motion hotkey.", "Framerate", "SlomoScalar", 0.50f, + speed_entries, speed_values, std::size(speed_entries)); + DrawToggleSetting( + bsi, "Enable Speed Limiter", "When disabled, the game will run as fast as possible.", "EmuCore/GS", "FrameLimitEnable", true); + + MenuHeading("Frame Pacing/Latency Control"); + + bool optimal_frame_pacing = (bsi->GetIntValue("EmuCore/GS", "VsyncQueueSize", DEFAULT_FRAME_LATENCY) == 0); + + DrawIntListSetting(bsi, "Maximum Frame Latency", "Sets the number of frames which can be queued.", "EmuCore/GS", "VsyncQueueSize", + DEFAULT_FRAME_LATENCY, queue_entries, std::size(queue_entries), 0, !optimal_frame_pacing); + + if (ToggleButton("Optimal Frame Pacing", + "Synchronize EE and GS threads after each frame. Lowest input latency, but increases system requirements.", + &optimal_frame_pacing)) + { + bsi->SetIntValue("EmuCore/GS", "VsyncQueueSize", optimal_frame_pacing ? 0 : DEFAULT_FRAME_LATENCY); + SetSettingsChanged(bsi); + } + + DrawToggleSetting(bsi, "Adjust To Host Refresh Rate", "Speeds up emulation so that the guest refresh rate matches the host.", + "EmuCore/GS", "SyncToHostRefreshRate", false); + + MenuHeading("Game Settings"); + + DrawToggleSetting(bsi, "Enable Cheats", "Enables loading cheats from pnach files.", "EmuCore", "EnableCheats", false); + DrawToggleSetting(bsi, "Enable Widescreen Patches", "Enables loading widescreen patches from pnach files.", "EmuCore", + "EnableWideScreenPatches", false); + DrawToggleSetting(bsi, "Enable No-Interlacing Patches", "Enables loading no-interlacing patches from pnach files.", "EmuCore", + "EnableNoInterlacingPatches", false); + DrawToggleSetting(bsi, "Enable Per-Game Settings", "Enables loading ini overlays from gamesettings, or custom settings per-game.", + "EmuCore", "EnablePerGameSettings", true); + DrawToggleSetting(bsi, "Enable Host Filesystem", "Enables access to files from the host: namespace in the virtual machine.", "EmuCore", + "HostFs", false); + + EndMenuButtons(); +} + +void FullscreenUI::DrawClampingModeSetting(SettingsInterface* bsi, const char* title, const char* summary, bool vu) +{ + // This is so messy... maybe we should just make the mode an int in the settings too... + const bool base = IsEditingGameSettings(bsi) ? 1 : 0; + std::optional default_false = IsEditingGameSettings(bsi) ? std::nullopt : std::optional(false); + std::optional default_true = IsEditingGameSettings(bsi) ? std::nullopt : std::optional(true); + + std::optional third = bsi->GetOptionalBoolValue("EmuCore/CPU/Recompiler", vu ? "vuSignOverflow" : "fpuFullMode", default_false); + std::optional second = + bsi->GetOptionalBoolValue("EmuCore/CPU/Recompiler", vu ? "vuExtraOverflow" : "fpuExtraOverflow", default_false); + std::optional first = bsi->GetOptionalBoolValue("EmuCore/CPU/Recompiler", vu ? "vuOverflow" : "fpuOverflow", default_true); + + int index; + if (third.has_value() && third.value()) + index = base + 3; + else if (second.has_value() && second.value()) + index = base + 2; + else if (first.has_value() && first.value()) + index = base + 1; + else if (first.has_value()) + index = base + 0; // none + else + index = 0; // no per game override + + static constexpr const char* ee_clamping_mode_settings[] = { + "Use Global Setting", "None", "Normal (Default)", "Extra + Preserve Sign", "Full"}; + static constexpr const char* vu_clamping_mode_settings[] = { + "Use Global Setting", "None", "Normal (Default)", "Extra", "Extra + Preserve Sign"}; + const char* const* options = vu ? vu_clamping_mode_settings : ee_clamping_mode_settings; + const int setting_offset = IsEditingGameSettings(bsi) ? 0 : 1; + + if (MenuButtonWithValue(title, summary, options[index + setting_offset])) + { + ImGuiFullscreen::ChoiceDialogOptions cd_options; + cd_options.reserve(std::size(ee_clamping_mode_settings)); + for (int i = setting_offset; i < static_cast(std::size(ee_clamping_mode_settings)); i++) + cd_options.emplace_back(options[i], (i == (index + setting_offset))); + OpenChoiceDialog(title, false, std::move(cd_options), + [game_settings = IsEditingGameSettings(bsi), vu](s32 index, const std::string& title, bool checked) { + if (index >= 0) + { + auto lock = Host::GetSettingsLock(); + + std::optional first, second, third; + + if (!game_settings || index > 0) + { + const bool base = game_settings ? 1 : 0; + third = (index >= (base + 3)); + second = (index >= (base + 2)); + first = (index >= (base + 1)); + } + + SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); + bsi->SetOptionalBoolValue("EmuCore/CPU/Recompiler", vu ? "vuSignOverflow" : "fpuFullMode", third); + bsi->SetOptionalBoolValue("EmuCore/CPU/Recompiler", vu ? "vuExtraOverflow" : "fpuExtraOverflow", second); + bsi->SetOptionalBoolValue("EmuCore/CPU/Recompiler", vu ? "vuOverflow" : "fpuOverflow", first); + SetSettingsChanged(bsi); + } + + CloseChoiceDialog(); + }); + } +} + +void FullscreenUI::DrawSystemSettingsPage() +{ + static constexpr const char* ee_cycle_rate_settings[] = { + "50% Speed", "60% Speed", "75% Speed", "100% Speed (Default)", "130% Speed", "180% Speed", "300% Speed"}; + static constexpr const char* ee_cycle_skip_settings[] = { + "Normal (Default)", "Mild Underclock", "Moderate Overclock", "Maximum Overclock"}; + static constexpr const char* ee_rounding_mode_settings[] = {"Nearest", "Negative", "Positive", "Chop/Zero (Default)"}; + static constexpr const char* affinity_control_settings[] = { + "Disabled", "EE > VU > GS", "EE > GS > VU", "VU > EE > GS", "VU > GS > EE", "GS > EE > VU", "GS > VU > EE"}; + + SettingsInterface* bsi = GetEditingSettingsInterface(); + + BeginMenuButtons(); + + MenuHeading("Emotion Engine (MIPS-III/MIPS-IV)"); + DrawIntListSetting(bsi, "Cycle Rate", "Underclocks or overclocks the emulated Emotion Engine CPU.", "EmuCore/Speedhacks", "EECycleRate", + 0, ee_cycle_rate_settings, std::size(ee_cycle_rate_settings), -3); + DrawIntListSetting(bsi, "Cycle Skip", "Adds a penalty to the Emulated Emotion Engine for executing VU programs.", "EmuCore/Speedhacks", + "EECycleSkip", 0, ee_cycle_skip_settings, std::size(ee_cycle_skip_settings)); + DrawIntListSetting(bsi, "Rounding Mode##ee_rounding_mode", + "Determines how the results of floating-point operations are rounded. Some games need specific settings.", "EmuCore/CPU", + "FPU.Roundmode", 3, ee_rounding_mode_settings, std::size(ee_rounding_mode_settings)); + DrawClampingModeSetting(bsi, "Clamping Mode##ee_clamping_mode", + "Determines how out-of-range floating point numbers are handled. Some games need specific settings.", false); + DrawIntListSetting(bsi, "Affinity Control Mode", + "Pins emulation threads to CPU cores to potentially improve performance/frame time variance.", "EmuCore/CPU", "AffinityControlMode", + 0, affinity_control_settings, std::size(affinity_control_settings), 0); + + MenuHeading("Vector Units"); + DrawIntListSetting(bsi, "Rounding Mode##vu_rounding_mode", + "Determines how the results of floating-point operations are rounded. Some games need specific settings.", "EmuCore/CPU", + "VU.Roundmode", 3, ee_rounding_mode_settings, std::size(ee_rounding_mode_settings)); + DrawClampingModeSetting(bsi, "Clamping Mode##vu_clamping_mode", + "Determines how out-of-range floating point numbers are handled. Some games need specific settings.", true); + DrawToggleSetting(bsi, "Enable MTVU (Multi-Threaded VU1)", "Uses a second thread for VU1 micro programs. Sizable speed boost.", + "EmuCore/Speedhacks", "vuThread", false); + DrawToggleSetting(bsi, "Enable Instant VU1", + "Reduces timeslicing between VU1 and EE recompilers, effectively running VU1 at an infinite clock speed.", "EmuCore/Speedhacks", + "vu1Instant", true); + + MenuHeading("I/O Processor (MIPS-I)"); + DrawToggleSetting( + bsi, "Enable Fast CDVD", "Fast disc access, less loading times. Not recommended.", "EmuCore/Speedhacks", "fastCDVD", false); + + EndMenuButtons(); +} + +void FullscreenUI::DrawGraphicsSettingsPage() +{ + static constexpr const char* s_renderer_names[] = {"Automatic", +#ifdef _WIN32 + "Direct3D 11", "Direct3D 12", +#endif +#ifdef ENABLE_OPENGL + "OpenGL", +#endif +#ifdef ENABLE_VULKAN + "Vulkan", +#endif +#ifdef __APPLE__ + "Metal", +#endif + "Software", "Null"}; + static constexpr const char* s_renderer_values[] = { + "-1", //GSRendererType::Auto, +#ifdef _WIN32 + "3", //GSRendererType::DX11, + "15", //GSRendererType::DX12, +#endif +#ifdef ENABLE_OPENGL + "12", //GSRendererType::OGL, +#endif +#ifdef ENABLE_VULKAN + "14", //GSRendererType::VK, +#endif +#ifdef __APPLE__ + "17", //GSRendererType::Metal, +#endif + "13", //GSRendererType::SW, + "11", //GSRendererType::Null + }; + static constexpr const char* s_vsync_values[] = {"Off", "On", "Adaptive"}; + static constexpr const char* s_deinterlacing_options[] = {"None", "Weave (Top Field First, Sawtooth)", + "Weave (Bottom Field First, Sawtooth)", "Bob (Top Field First)", "Bob (Bottom Field First)", "Blend (Top Field First, Half FPS)", + "Blend (Bottom Field First, Half FPS)", "Automatic (Default)"}; + static constexpr const char* s_resolution_options[] = { + "Native (PS2)", + "2x Native (~720p)", + "3x Native (~1080p)", + "4x Native (~1440p/2K)", + "5x Native (~1620p)", + "6x Native (~2160p/4K)", + "7x Native (~2520p)", + "8x Native (~2880p)", + }; + static constexpr const char* s_mipmapping_options[] = {"Automatic (Default)", "Off", "Basic (Generated Mipmaps)", "Full (PS2 Mipmaps)"}; + static constexpr const char* s_bilinear_options[] = { + "Nearest", "Bilinear (Forced)", "Bilinear (PS2)", "Bilinear (Forced excluding sprite)"}; + static constexpr const char* s_trilinear_options[] = {"Automatic (Default)", "Off (None)", "Trilinear (PS2)", "Trilinear (Forced)"}; + static constexpr const char* s_dithering_options[] = {"Off", "Scaled", "Unscaled (Default)"}; + static constexpr const char* s_crc_fix_options[] = { + "Automatic (Default)", "None (Debug)", "Minimum (Debug)", "Partial (OpenGL)", "Full (Direct3D)", "Aggressive"}; + static constexpr const char* s_blending_options[] = { + "Minimum", "Basic (Recommended)", "Medium", "High", "Full (Slow)", "Maximum (Very Slow)"}; + static constexpr const char* s_anisotropic_filtering_entries[] = {"Off (Default)", "2x", "4x", "8x", "16x"}; + static constexpr const char* s_anisotropic_filtering_values[] = {"0", "2", "4", "8", "16"}; + static constexpr const char* s_preloading_options[] = {"None", "Partial", "Full (Hash Cache)"}; + static constexpr const char* s_generic_options[] = {"Automatic (Default)", "Force Disabled", "Force Enabled"}; + + SettingsInterface* bsi = GetEditingSettingsInterface(); + + const GSRendererType renderer = + static_cast(GetEffectiveIntSetting(bsi, "EmuCore/GS", "Renderer", static_cast(GSRendererType::Auto))); + const bool is_hardware = (renderer == GSRendererType::Auto || renderer == GSRendererType::DX11 || renderer == GSRendererType::DX12 || + renderer == GSRendererType::OGL || renderer == GSRendererType::VK || renderer == GSRendererType::Metal); + //const bool is_software = (renderer == GSRendererType::SW); + + BeginMenuButtons(); + + MenuHeading("Renderer"); + DrawStringListSetting(bsi, "Renderer", "Selects the API used to render the emulated GS.", "EmuCore/GS", "Renderer", "-1", + s_renderer_names, s_renderer_values, std::size(s_renderer_names)); + DrawIntListSetting(bsi, "Sync To Host Refresh (VSync)", "Synchronizes frame presentation with host refresh.", "EmuCore/GS", + "VsyncEnable", static_cast(VsyncMode::Off), s_vsync_values, std::size(s_vsync_values)); + + MenuHeading("Display"); + DrawStringListSetting(bsi, "Aspect Ratio", "Selects the aspect ratio to display the game content at.", "EmuCore/GS", "AspectRatio", + "Auto 4:3/3:2", Pcsx2Config::GSOptions::AspectRatioNames, Pcsx2Config::GSOptions::AspectRatioNames, 0); + DrawStringListSetting(bsi, "FMV Aspect Ratio", "Selects the aspect ratio for display when a FMV is detected as playing.", "EmuCore/GS", + "FMVAspectRatioSwitch", "Auto 4:3/3:2", Pcsx2Config::GSOptions::FMVAspectRatioSwitchNames, + Pcsx2Config::GSOptions::FMVAspectRatioSwitchNames, 0); + DrawIntListSetting(bsi, "Deinterlacing", + "Selects the algorithm used to convert the PS2's interlaced output to progressive for display.", "EmuCore/GS", "deinterlace", + static_cast(GSInterlaceMode::Automatic), s_deinterlacing_options, std::size(s_deinterlacing_options)); + DrawIntRangeSetting(bsi, "Zoom", "Increases or decreases the virtual picture size both horizontally and vertically.", "EmuCore/GS", + "Zoom", 100, 10, 300, "%d%%"); + DrawIntRangeSetting(bsi, "Vertical Stretch", "Increases or decreases the virtual picture size vertically.", "EmuCore/GS", "StretchY", + 100, 10, 300, "%d%%"); + DrawIntRectSetting(bsi, "Crop", "Crops the image, while respecting aspect ratio.", "EmuCore/GS", "CropLeft", 0, "CropTop", 0, + "CropRight", 0, "CropBottom", 0, 0, 720, "%dpx"); + DrawToggleSetting( + bsi, "Bilinear Upscaling", "Smooths out the image when upscaling the console to the screen.", "EmuCore/GS", "linear_present", true); + DrawToggleSetting(bsi, "Integer Upscaling", + "Adds padding to the display area to ensure that the ratio between pixels on the host to pixels in the console is an integer " + "number. May result in a sharper image in some 2D games.", + "EmuCore/GS", "IntegerScaling", false); + DrawToggleSetting(bsi, "Internal Resolution Screenshots", + "Save screenshots at the full render resolution, rather than display resolution.", "EmuCore/GS", "InternalResolutionScreenshots", + false); + DrawToggleSetting(bsi, "Screen Offsets", "Enables PCRTC Offsets which position the screen as the game requests.", "EmuCore/GS", + "pcrtc_offsets", false); + DrawToggleSetting(bsi, "Show Overscan", + "Enables the option to show the overscan area on games which draw more than the safe area of the screen.", "EmuCore/GS", + "pcrtc_overscan", false); + DrawToggleSetting(bsi, "Anti-Blur", + "Enables internal Anti-Blur hacks. Less accurate to PS2 rendering but will make a lot of games look less blurry.", "EmuCore/GS", + "pcrtc_antiblur", true); + + MenuHeading("Rendering"); + if (is_hardware) + { + DrawIntListSetting(bsi, "Internal Resolution", "Multiplies the render resolution by the specified factor (upscaling).", + "EmuCore/GS", "upscale_multiplier", 1, s_resolution_options, std::size(s_resolution_options), 1); + DrawIntListSetting(bsi, "Mipmapping", "Determines how mipmaps are used when rendering textures.", "EmuCore/GS", "mipmap_hw", + static_cast(HWMipmapLevel::Automatic), s_mipmapping_options, std::size(s_mipmapping_options), -1); + DrawIntListSetting(bsi, "Bilinear Filtering", "Selects where bilinear filtering is utilized when rendering textures.", "EmuCore/GS", + "filter", static_cast(BiFiltering::PS2), s_bilinear_options, std::size(s_bilinear_options)); + DrawIntListSetting(bsi, "Trilinear Filtering", "Selects where trilinear filtering is utilized when rendering textures.", + "EmuCore/GS", "UserHacks_TriFilter", static_cast(TriFiltering::Automatic), s_trilinear_options, + std::size(s_trilinear_options), -1); + DrawStringListSetting(bsi, "Anisotropic Filtering", "Selects where anistropic filtering is utilized when rendering textures.", + "EmuCore/GS", "MaxAnisotropy", "0", s_anisotropic_filtering_entries, s_anisotropic_filtering_values, + std::size(s_anisotropic_filtering_entries)); + DrawIntListSetting(bsi, "Dithering", "Selects the type of dithering applies when the game requests it.", "EmuCore/GS", + "dithering_ps2", 2, s_dithering_options, std::size(s_dithering_options)); + DrawIntListSetting(bsi, "CRC Fix Level", "Applies manual fixes to difficult-to-emulate effects in the hardware renderers.", + "EmuCore/GS", "crc_hack_level", static_cast(CRCHackLevel::Automatic), s_crc_fix_options, std::size(s_crc_fix_options), -1); + DrawIntListSetting(bsi, "Blending Accuracy", + "Determines the level of accuracy when emulating blend modes not supported by the host graphics API.", "EmuCore/GS", + "accurate_blending_unit", static_cast(AccBlendLevel::Basic), s_blending_options, std::size(s_blending_options)); + DrawIntListSetting(bsi, "Texture Preloading", + "Uploads full textures to the GPU on use, rather than only the utilized regions. Can improve performance in some games.", + "EmuCore/GS", "texture_preloading", static_cast(TexturePreloadingLevel::Off), s_preloading_options, + std::size(s_preloading_options)); + DrawToggleSetting(bsi, "Accurate Destination Alpha Test", + "Implement a more accurate algorithm to compute GS destination alpha testing.", "EmuCore/GS", "accurate_date", true); + DrawToggleSetting(bsi, "GPU Palette Conversion", + "Applies palettes to textures on the GPU instead of the CPU. Can result in speed improvements in some games.", "EmuCore/GS", + "paltex", false); + } + else + { + } + + if (is_hardware) + { + MenuHeading("Hardware Fixes"); + DrawToggleSetting(bsi, "Manual Hardware Fixes", "Disables automatic hardware fixes, allowing you to set fixes manually.", + "EmuCore/GS", "UserHacks", false); + + const bool manual_hw_fixes = GetEffectiveBoolSetting(bsi, "EmuCore/GS", "UserHacks", false); + if (manual_hw_fixes) + { + static constexpr const char* s_cpu_sprite_render_bw_options[] = {"0 (Disabled)", "1 (64 Max Width)", "2 (128 Max Width)", + "3 (192 Max Width)", "4 (256 Max Width)", "5 (320 Max Width)", "6 (384 Max Width)", "7 (448 Max Width)", + "8 (512 Max Width)", "9 (576 Max Width)", "10 (640 Max Width)"}; + static constexpr const char* s_half_pixel_offset_options[] = { + "Off (Default)", "Normal (Vertex)", "Special (Texture)", "Special (Texture - Aggressive)"}; + static constexpr const char* s_round_sprite_options[] = {"Off (Default)", "Half", "Full"}; + + DrawIntListSetting(bsi, "Half-Bottom Override", "Control the half-screen fix detection on texture shuffling.", "EmuCore/GS", + "UserHacks_Half_Bottom_Override", -1, s_generic_options, std::size(s_generic_options), -1); + DrawIntListSetting(bsi, "CPU Sprite Render Size", "Uses sofware renderer to draw texture decompression-like sprites.", + "EmuCore/GS", "UserHacks_CPUSpriteRenderBW", 0, s_cpu_sprite_render_bw_options, std::size(s_cpu_sprite_render_bw_options)); + DrawIntRangeSetting( + bsi, "Skip Draw Start", "Object range to skip drawing.", "EmuCore/GS", "UserHacks_SkipDraw_Start", 0, 0, 5000); + DrawIntRangeSetting(bsi, "Skip Draw End", "Object range to skip drawing.", "EmuCore/GS", "UserHacks_SkipDraw_End", 0, 0, 5000); + DrawToggleSetting(bsi, "Auto Flush (Hardware)", "Force a primitive flush when a framebuffer is also an input texture.", + "EmuCore/GS", "UserHacks_AutoFlush", false, manual_hw_fixes); + DrawToggleSetting(bsi, "CPU Framebuffer Conversion", "Convert 4-bit and 8-bit frame buffer on the CPU instead of the GPU.", + "EmuCore/GS", "UserHacks_CPU_FB_Conversion", false, manual_hw_fixes); + DrawToggleSetting(bsi, "Disable Depth Support", "Disable the support of depth buffer in the texture cache.", "EmuCore/GS", + "UserHacks_DisableDepthSupport", false, manual_hw_fixes); + DrawToggleSetting( + bsi, "Wrap GS Memory", "Emulates GS memory wrapping accurately.", "EmuCore/GS", "wrap_gs_mem", false, manual_hw_fixes); + DrawToggleSetting(bsi, "Disable Safe Features", "This option disables multiple safe features.", "EmuCore/GS", + "UserHacks_Disable_Safe_Features", false, manual_hw_fixes); + DrawToggleSetting(bsi, "Preload Frame", "Uploads GS data when rendering a new frame to reproduce some effects accurately.", + "EmuCore/GS", "preload_frame_with_gs_data", false, manual_hw_fixes); + DrawToggleSetting(bsi, "Disable Partial Invalidation", + "Removes texture cache entries when there is any intersection, rather than only the intersected areas.", "EmuCore/GS", + "UserHacks_DisablePartialInvalidation", false, manual_hw_fixes); + DrawToggleSetting(bsi, "Texture Inside Render Target", + "Allows the texture cache to reuse as an input texture the inner portion of a previous framebuffer.", "EmuCore/GS", + "UserHacks_TextureInsideRt", false, manual_hw_fixes); + + MenuHeading("Upscaling Fixes"); + DrawIntListSetting(bsi, "Half-Pixel Offset", "Adjusts vertices relative to upscaling.", "EmuCore/GS", + "UserHacks_HalfPixelOffset", 0, s_half_pixel_offset_options, std::size(s_half_pixel_offset_options)); + DrawIntListSetting(bsi, "Round Sprite", "Adjusts sprite coordinates.", "EmuCore/GS", "UserHacks_round_sprite_offset", 0, + s_round_sprite_options, std::size(s_round_sprite_options)); + DrawIntRangeSetting(bsi, "TC Offset X", "Adjusts target texture offsets.", "EmuCore/GS", "UserHacks_TCOffsetX", 0, -4096, 4096); + DrawIntRangeSetting(bsi, "TC Offset Y", "Adjusts target texture offsets.", "EmuCore/GS", "UserHacks_TCOffsetY", 0, -4096, 4096); + DrawToggleSetting(bsi, "Align Sprite", "Fixes issues with upscaling (vertical lines) in some games.", "EmuCore/GS", + "UserHacks_align_sprite_X", false, manual_hw_fixes); + DrawToggleSetting(bsi, "Merge Sprite", "Replaces multiple post-processing sprites with a larger single sprite.", "EmuCore/GS", + "UserHacks_merge_pp_sprite", false, manual_hw_fixes); + DrawToggleSetting(bsi, "Wild Arms Hack", + "Lowers the GS precision to avoid gaps between pixels when upscaling. Fixes the text on Wild Arms games.", "EmuCore/GS", + "UserHacks_WildHack", false, manual_hw_fixes); + } + } + else + { + // extrathreads + DrawIntRangeSetting(bsi, "Software Rendering Threads", + "Number of threads to use in addition to the main GS thread for rasterization.", "EmuCore/GS", "extrathreads", 2, 0, 10); + DrawToggleSetting(bsi, "Auto Flush (Software)", "Force a primitive flush when a framebuffer is also an input texture.", + "EmuCore/GS", "autoflush_sw", true); + DrawToggleSetting(bsi, "Edge AA (AA1)", "Enables emulation of the GS's edge anti-aliasing (AA1).", "EmuCore/GS", "aa1", true); + DrawToggleSetting(bsi, "Mipmapping", "Enables emulation of the GS's texture mipmapping.", "EmuCore/GS", "mipmap", true); + } + + if (is_hardware) + { + const bool dumping_active = GetEffectiveBoolSetting(bsi, "EmuCore/GS", "DumpReplaceableTextures", false); + const bool replacement_active = GetEffectiveBoolSetting(bsi, "EmuCore/GS", "LoadTextureReplacements", false); + + MenuHeading("Texture Replacement"); + DrawToggleSetting(bsi, "Load Textures", "Loads replacement textures where available and user-provided.", "EmuCore/GS", + "LoadTextureReplacements", false); + DrawToggleSetting(bsi, "Asynchronous Texture Loading", + "Loads replacement textures on a worker thread, reducing microstutter when replacements are enabled.", "EmuCore/GS", + "LoadTextureReplacementsAsync", true, replacement_active); + DrawToggleSetting(bsi, "Precache Replacements", + "Preloads all replacement textures to memory. Not necessary with asynchronous loading.", "EmuCore/GS", + "PrecacheTextureReplacements", false, replacement_active); + + MenuHeading("Texture Dumping"); + DrawToggleSetting(bsi, "Dump Textures", "Dumps replacable textures to disk. Will reduce performance.", "EmuCore/GS", + "DumpReplaceableTextures", false); + DrawToggleSetting( + bsi, "Dump Mipmaps", "Includes mipmaps when dumping textures.", "EmuCore/GS", "DumpReplaceableMipmaps", false, dumping_active); + DrawToggleSetting(bsi, "Dump FMV Textures", "Allows texture dumping when FMVs are active. You should not enable this.", + "EmuCore/GS", "DumpTexturesWithFMVActive", false, dumping_active); + } + + MenuHeading("Post-Processing"); + { + const bool shadeboost_active = GetEffectiveBoolSetting(bsi, "EmuCore/GS", "ShadeBoost", false); + DrawToggleSetting(bsi, "FXAA", "Enables FXAA post-processing shader.", "EmuCore/GS", "fxaa", false); + DrawToggleSetting(bsi, "Shade Boost", "Enables brightness/contrast/saturation adjustment.", "EmuCore/GS", "ShadeBoost", false); + DrawIntRangeSetting(bsi, "Shade Boost Brightness", "Adjusts brightness. 50 is normal.", "EmuCore/GS", "ShadeBoost_Brightness", 50, + 1, 100, "%d", shadeboost_active); + DrawIntRangeSetting(bsi, "Shade Boost Contrast", "Adjusts contrast. 50 is normal.", "EmuCore/GS", "ShadeBoost_Contrast", 50, 1, 100, + "%d", shadeboost_active); + DrawIntRangeSetting(bsi, "Shade Boost Saturation", "Adjusts saturation. 50 is normal.", "EmuCore/GS", "ShadeBoost_Saturation", 50, + 1, 100, "%d", shadeboost_active); + + static constexpr const char* s_tv_shaders[] = { + "None", "Scanline Filter", "Diagonal Filter", "Triangular Filter", "Wave Filter", "Lottes CRT"}; + DrawIntListSetting( + bsi, "TV Shaders", "Selects post-processing TV shader.", "EmuCore/GS", "TVShader", 0, s_tv_shaders, std::size(s_tv_shaders)); + } + + static constexpr const char* s_gsdump_compression[] = {"Uncompressed", "LZMA (xz)", "Zstandard (zst)"}; + + MenuHeading("Advanced"); + DrawToggleSetting(bsi, "Skip Presenting Duplicate Frames", + "Skips displaying frames that don't change in 25/30fps games. Can improve speed but increase input lag/make frame pacing worse.", + "EmuCore/GS", "SkipDuplicateFrames", false); + DrawToggleSetting(bsi, "Disable Hardware Readbacks", + "Skips thread synchronization for GS downloads. Can improve speed, but break graphical effects.", "EmuCore/GS", + "HWDisableReadbacks", false); + DrawIntListSetting(bsi, "Override Texture Barriers", "Forces texture barrier functionality to the specified value.", "EmuCore/GS", + "OverrideTextureBarriers", -1, s_generic_options, std::size(s_generic_options), -1); + DrawIntListSetting(bsi, "Override Geometry Shaders", "Forces geometry shader functionality to the specified value.", "EmuCore/GS", + "OverrideGeometryShaders", -1, s_generic_options, std::size(s_generic_options), -1); + DrawIntListSetting(bsi, "GS Dump Compression", "Sets the compression algorithm for GS dumps.", "EmuCore/GS", "GSDumpCompression", + static_cast(GSDumpCompressionMethod::LZMA), s_gsdump_compression, std::size(s_gsdump_compression)); + DrawToggleSetting(bsi, "Disable Framebuffer Fetch", "Prevents the usage of framebuffer fetch when supported by host GPU.", "EmuCore/GS", + "DisableFramebufferFetch", false); + DrawToggleSetting(bsi, "Disable Dual-Source Blending", "Prevents the usage of dual-source blending when supported by host GPU.", + "EmuCore/GS", "DisableDualSourceBlend", false); + + EndMenuButtons(); +} + +void FullscreenUI::DrawAudioSettingsPage() +{ + static constexpr const char* interpolation_modes[] = { + "Nearest (Fastest / worst quality)", + "Linear (Simple / okay sound)", + "Cubic (Fake highs / okay sound)", + "Hermite (Better highs / okay sound)", + "Catmull-Rom (PS2-like / good sound)", + "Gaussian (PS2-like / great sound)", + }; + static constexpr const char* synchronization_modes[] = { + "TimeStretch (Recommended)", + "Async Mix (Breaks some games!)", + "None (Audio can skip.)", + }; + static constexpr const char* expansion_modes[] = { + "Stereo (None, Default)", + "Quadrafonic", + "Surround 5.1", + "Surround 7.1", + }; + static constexpr const char* output_entries[] = { + "No Sound (Emulate SPU2 only)", +#ifdef SPU2X_CUBEB + "Cubeb (Cross-platform)", +#endif +#ifdef _WIN32 + "XAudio2", +#endif + }; + static constexpr const char* output_values[] = { + "nullout", +#ifdef SPU2X_CUBEB + "cubeb", +#endif +#ifdef _WIN32 + "xaudio2", +#endif + }; +#if defined(SPU2X_CUBEB) + static constexpr const char* default_output_module = "cubeb"; +#elif defined(_WIN32) + static constexpr const char* default_output_module = "xaudio2"; +#else + static constexpr const char* default_output_module = "nullout"; +#endif + + SettingsInterface* bsi = GetEditingSettingsInterface(); + + BeginMenuButtons(); + + MenuHeading("Runtime Settings"); + DrawIntRangeSetting(bsi, ICON_FA_VOLUME_UP " Output Volume", "Applies a global volume modifier to all sound produced by the game.", + "SPU2/Mixing", "FinalVolume", 100, 0, 100, "%d%%"); + + MenuHeading("Mixing Settings"); + DrawIntListSetting(bsi, ICON_FA_MUSIC " Interpolation Mode", "Determines how ADPCM samples are interpolated to the target pitch.", + "SPU2/Mixing", "Interpolation", static_cast(Pcsx2Config::SPU2Options::InterpolationMode::Gaussian), interpolation_modes, + std::size(interpolation_modes)); + DrawIntListSetting(bsi, ICON_FA_RULER " Synchronization Mode", "Changes when SPU samples are generated relative to system emulation.", + "SPU2/Output", "SynchMode", static_cast(Pcsx2Config::SPU2Options::SynchronizationMode::TimeStretch), synchronization_modes, + std::size(synchronization_modes)); + DrawIntListSetting(bsi, ICON_FA_PLUS " Expansion Mode", "Determines how the stereo output is transformed to greater speaker counts.", + "SPU2/Output", "SpeakerConfiguration", 0, expansion_modes, std::size(expansion_modes)); + + MenuHeading("Output Settings"); + DrawStringListSetting(bsi, ICON_FA_PLAY_CIRCLE " Output Module", + "Determines which API is used to play back audio samples on the host.", "SPU2/Output", "OutputModule", default_output_module, + output_entries, output_values, std::size(output_entries)); + DrawIntRangeSetting(bsi, ICON_FA_CLOCK " Latency", "Sets the average output latency when using the cubeb backend.", "SPU2/Output", + "Latency", 100, 15, 200, "%d ms (avg)"); + + MenuHeading("Timestretch Settings"); + DrawIntRangeSetting(bsi, ICON_FA_RULER_HORIZONTAL " Sequence Length", + "Affects how the timestretcher operates when not running at 100% speed.", "Soundtouch", "SequenceLengthMS", 30, 20, 100, "%d ms"); + DrawIntRangeSetting(bsi, ICON_FA_WINDOW_MAXIMIZE " Seekwindow Size", + "Affects how the timestretcher operates when not running at 100% speed.", "Soundtouch", "SeekWindowMS", 20, 10, 30, "%d ms"); + DrawIntRangeSetting(bsi, ICON_FA_RECEIPT " Overlap", "Affects how the timestretcher operates when not running at 100% speed.", + "Soundtouch", "OverlapMS", 20, 5, 15, "%d ms"); + + EndMenuButtons(); +} + +void FullscreenUI::DrawMemoryCardSettingsPage() +{ + BeginMenuButtons(); + + SettingsInterface* bsi = GetEditingSettingsInterface(); + + MenuHeading("Settings and Operations"); + if (MenuButton(ICON_FA_PLUS " Create Memory Card", "Creates a new memory card file or folder.")) + ImGui::OpenPopup("Create Memory Card"); + DrawCreateMemoryCardWindow(); + + DrawFolderSetting(bsi, ICON_FA_FOLDER_OPEN " Memory Card Directory", "Folders", "MemoryCards", EmuFolders::MemoryCards); + DrawToggleSetting(bsi, ICON_FA_SEARCH " Folder Memory Card Filter", + "Simulates a larger memory card by filtering saves only to the current game.", "EmuCore", "McdFolderAutoManage", true); + DrawToggleSetting(bsi, ICON_FA_MAGIC " Auto Eject When Loading", + "Automatically ejects memory cards when they differ after loading a state.", "EmuCore", "McdEnableEjection", true); + + for (u32 port = 0; port < NUM_MEMORY_CARD_PORTS; port++) + { + const std::string title(fmt::format("Console Port {}", port + 1)); + MenuHeading(title.c_str()); + + std::string enable_key(fmt::format("Slot{}_Enable", port + 1)); + std::string file_key(fmt::format("Slot{}_Filename", port + 1)); + + DrawToggleSetting(bsi, fmt::format(ICON_FA_SD_CARD " Card Enabled##card_enabled_{}", port).c_str(), + "If not set, this card will be considered unplugged.", "MemoryCards", enable_key.c_str(), true); + + const bool enabled = GetEffectiveBoolSetting(bsi, "MemoryCards", enable_key.c_str(), true); + + std::optional value(bsi->GetOptionalStringValue("MemoryCards", file_key.c_str(), + IsEditingGameSettings(bsi) ? std::nullopt : std::optional(FileMcd_GetDefaultName(port).c_str()))); + + if (MenuButtonWithValue(fmt::format(ICON_FA_FILE " Card Name##card_name_{}", port).c_str(), + "The selected memory card image will be used for this slot.", value.has_value() ? value->c_str() : "Use Global Setting", + enabled)) + { + ImGuiFullscreen::ChoiceDialogOptions options; + std::vector names; + if (IsEditingGameSettings(bsi)) + options.emplace_back("Use Global Setting", !value.has_value()); + if (value.has_value() && !value->empty()) + { + options.emplace_back(fmt::format("{} (Current)", value.value()), true); + names.push_back(std::move(value.value())); + } + for (AvailableMcdInfo& mci : FileMcd_GetAvailableCards(IsEditingGameSettings(bsi))) + { + if (mci.type == MemoryCardType::Folder) + { + options.emplace_back(fmt::format("{} (Folder)", mci.name), false); + } + else + { + static constexpr const char* file_type_names[] = { + "Unknown", "PS2 (8MB)", "PS2 (16MB)", "PS2 (32MB)", "PS2 (64MB)", "PS1"}; + options.emplace_back(fmt::format("{} ({})", mci.name, file_type_names[static_cast(mci.file_type)]), false); + } + names.push_back(std::move(mci.name)); + } + OpenChoiceDialog(title.c_str(), false, std::move(options), + [game_settings = IsEditingGameSettings(bsi), names = std::move(names), file_key = std::move(file_key)]( + s32 index, const std::string& title, bool checked) { + if (index < 0) + return; + + auto lock = Host::GetSettingsLock(); + SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); + if (game_settings && index == 0) + { + bsi->DeleteValue("MemoryCards", file_key.c_str()); + } + else + { + if (game_settings) + index--; + bsi->SetStringValue("MemoryCards", file_key.c_str(), names[index].c_str()); + } + SetSettingsChanged(bsi); + CloseChoiceDialog(); + }); + } + + if (MenuButton( + fmt::format(ICON_FA_EJECT " Eject Card##eject_card_{}", port).c_str(), "Resets the card name for this slot.", enabled)) + { + bsi->SetStringValue("MemoryCards", file_key.c_str(), ""); + SetSettingsChanged(bsi); + } + } + + + EndMenuButtons(); +} + +void FullscreenUI::DrawCreateMemoryCardWindow() +{ + ImGui::SetNextWindowSize(LayoutScale(700.0f, 0.0f)); + ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); + ImGui::PushFont(g_large_font); + + bool is_open = true; + if (ImGui::BeginPopupModal("Create Memory Card", &is_open, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize)) + { + ImGui::TextWrapped("Enter the name of the memory card you wish to create, and choose a size. We recommend either using 8MB memory " + "cards, or folder memory cards for best compatibility."); + ImGui::NewLine(); + + static char memcard_name[256] = {}; + ImGui::Text("Card Name: "); + ImGui::InputText("##name", memcard_name, sizeof(memcard_name)); + + ImGui::NewLine(); + + static constexpr std::tuple memcard_types[] = { + {"8 MB [Most Compatible]", MemoryCardType::File, MemoryCardFileType::PS2_8MB}, + {"16 MB", MemoryCardType::File, MemoryCardFileType::PS2_8MB}, + {"32 MB", MemoryCardType::File, MemoryCardFileType::PS2_8MB}, + {"64 MB", MemoryCardType::File, MemoryCardFileType::PS2_8MB}, + {"Folder [Recommended]", MemoryCardType::Folder, MemoryCardFileType::PS2_8MB}, + {"128 KB [PS1]", MemoryCardType::File, MemoryCardFileType::PS1}, + }; + + static int memcard_type = 0; + for (int i = 0; i < static_cast(std::size(memcard_types)); i++) + ImGui::RadioButton(std::get<0>(memcard_types[i]), &memcard_type, i); + + ImGui::NewLine(); + + BeginMenuButtons(); + + const bool create_enabled = (std::strlen(memcard_name) > 0); + + if (ActiveButton(ICON_FA_FOLDER_OPEN " Create", false, create_enabled) && std::strlen(memcard_name) > 0) + { + const std::string real_card_name(fmt::format("{}.ps2", memcard_name)); + if (!FileMcd_GetCardInfo(real_card_name).has_value()) + { + const auto& [type_title, type, file_type] = memcard_types[memcard_type]; + if (FileMcd_CreateNewCard(real_card_name, type, file_type)) + { + ShowToast(std::string(), fmt::format("Memory card '{}' created.", real_card_name)); + + std::memset(memcard_name, 0, sizeof(memcard_name)); + memcard_type = 0; + ImGui::CloseCurrentPopup(); + } + else + { + ShowToast(std::string(), fmt::format("Failed to create memory card '{}'.", real_card_name)); + } + } + else + { + ShowToast(std::string(), fmt::format("A memory card with the name '{}' already exists.", real_card_name)); + } + } + + if (ActiveButton(ICON_FA_TIMES " Cancel", false)) + { + std::memset(memcard_name, 0, sizeof(memcard_name)); + memcard_type = 0; + + ImGui::CloseCurrentPopup(); + } + + EndMenuButtons(); + + ImGui::EndPopup(); + } + + ImGui::PopFont(); + ImGui::PopStyleVar(2); +} + +void FullscreenUI::CopyGlobalControllerSettingsToGame() +{ + SettingsInterface* dsi = GetEditingSettingsInterface(true); + SettingsInterface* ssi = GetEditingSettingsInterface(false); + + PAD::CopyConfiguration(dsi, *ssi, true, true, false); + SetSettingsChanged(dsi); + + ShowToast(std::string(), "Per-game controller configuration initialized with global settings."); +} + +void FullscreenUI::ResetControllerSettings() +{ + SettingsInterface* dsi = GetEditingSettingsInterface(); + + PAD::SetDefaultConfig(*dsi); + ShowToast(std::string(), "Controller settings reset to default."); +} + +void FullscreenUI::DoLoadInputProfile() +{ + std::vector profiles(PAD::GetInputProfileNames()); + if (profiles.empty()) + { + ShowToast(std::string(), "No input profiles available."); + return; + } + + ImGuiFullscreen::ChoiceDialogOptions coptions; + coptions.reserve(profiles.size()); + for (std::string& name : profiles) + coptions.emplace_back(std::move(name), false); + OpenChoiceDialog( + ICON_FA_FOLDER_OPEN " Load Profile", false, std::move(coptions), [](s32 index, const std::string& title, bool checked) { + if (index < 0) + return; + + INISettingsInterface ssi(VMManager::GetInputProfilePath(title)); + if (!ssi.Load()) + { + ShowToast(std::string(), fmt::format("Failed to load '{}'.", title)); + CloseChoiceDialog(); + return; + } + + auto lock = Host::GetSettingsLock(); + SettingsInterface* dsi = GetEditingSettingsInterface(); + PAD::CopyConfiguration(dsi, ssi, true, true, IsEditingGameSettings(dsi)); + SetSettingsChanged(dsi); + ShowToast(std::string(), fmt::format("Input profile '{}' loaded.", title)); + CloseChoiceDialog(); + }); +} + +void FullscreenUI::DoSaveInputProfile(const std::string& name) +{ + INISettingsInterface dsi(VMManager::GetInputProfilePath(name)); + + auto lock = Host::GetSettingsLock(); + SettingsInterface* ssi = GetEditingSettingsInterface(); + PAD::CopyConfiguration(&dsi, *ssi, true, true, IsEditingGameSettings(ssi)); + if (dsi.Save()) + ShowToast(std::string(), fmt::format("Input profile '{}' saved.", name)); + else + ShowToast(std::string(), fmt::format("Failed to save input profile '{}'.", name)); +} + +void FullscreenUI::DoSaveInputProfile() +{ + std::vector profiles(PAD::GetInputProfileNames()); + if (profiles.empty()) + { + ShowToast(std::string(), "No input profiles available."); + return; + } + + ImGuiFullscreen::ChoiceDialogOptions coptions; + coptions.reserve(profiles.size() + 1); + coptions.emplace_back("Create New...", false); + for (std::string& name : profiles) + coptions.emplace_back(std::move(name), false); + OpenChoiceDialog(ICON_FA_SAVE " Save Profile", false, std::move(coptions), [](s32 index, const std::string& title, bool checked) { + if (index < 0) + return; + + if (index > 0) + { + DoSaveInputProfile(title); + CloseChoiceDialog(); + return; + } + + CloseChoiceDialog(); + + OpenInputStringDialog(ICON_FA_SAVE " Save Profile", "Enter the name of the input profile you wish to create.", std::string(), + ICON_FA_FOLDER_PLUS " Create", [](std::string title) { + if (!title.empty()) + DoSaveInputProfile(title); + }); + }); +} + +void FullscreenUI::DrawControllerSettingsPage() +{ + BeginMenuButtons(); + + SettingsInterface* bsi = GetEditingSettingsInterface(); + + MenuHeading("Configuration"); + + if (IsEditingGameSettings(bsi)) + { + if (DrawToggleSetting(bsi, ICON_FA_COG " Per-Game Configuration", "Uses game-specific settings for controllers for this game.", + "Pad", "UseGameSettingsForController", false, IsEditingGameSettings(bsi), false)) + { + // did we just enable per-game for the first time? + if (bsi->GetBoolValue("Pad", "UseGameSettingsForController", false) && + !bsi->GetBoolValue("Pad", "GameSettingsInitialized", false)) + { + bsi->SetBoolValue("Pad", "GameSettingsInitialized", true); + CopyGlobalControllerSettingsToGame(); + } + } + } + + if (IsEditingGameSettings(bsi) && !bsi->GetBoolValue("Pad", "UseGameSettingsForController", false)) + { + // nothing to edit.. + EndMenuButtons(); + return; + } + + if (IsEditingGameSettings(bsi)) + { + if (MenuButton(ICON_FA_COPY " Copy Global Settings", "Copies the global controller configuration to this game.")) + CopyGlobalControllerSettingsToGame(); + } + else + { + if (MenuButton(ICON_FA_FOLDER_MINUS " Reset Settings", "Resets all configuration to defaults (including bindings).")) + ResetControllerSettings(); + } + + if (MenuButton(ICON_FA_FOLDER_OPEN " Load Profile", "Replaces these settings with a previously saved input profile.")) + DoLoadInputProfile(); + if (MenuButton(ICON_FA_SAVE " Save Profile", "Stores the current settings to an input profile.")) + DoSaveInputProfile(); + + MenuHeading("Input Sources"); + +#ifdef SDL_BUILD + DrawToggleSetting(bsi, ICON_FA_COG " Enable SDL Input Source", "The SDL input source supports most controllers.", "InputSources", + "SDL", true, true, false); + DrawToggleSetting(bsi, ICON_FA_WIFI " SDL DualShock 4 / DualSense Enhanced Mode", + "Provides vibration and LED control support over Bluetooth.", "InputSources", "SDLControllerEnhancedMode", false, + bsi->GetBoolValue("InputSources", "SDL", true), false); +#endif +#ifdef _WIN32 + DrawToggleSetting(bsi, ICON_FA_COG " Enable XInput Input Source", + "The XInput source provides support for XBox 360/XBox One/XBox Series controllers.", "InputSources", "XInput", false, true, false); +#endif + + MenuHeading("Multitap"); + DrawToggleSetting(bsi, ICON_FA_PLUS_SQUARE " Enable Console Port 1 Multitap", + "Enables an additional three controller slots. Not supported in all games.", "Pad", "MultitapPort1", false, true, false); + DrawToggleSetting(bsi, ICON_FA_PLUS_SQUARE " Enable Console Port 2 Multitap", + "Enables an additional three controller slots. Not supported in all games.", "Pad", "MultitapPort2", false, true, false); + + const std::array mtap_enabled = { + {bsi->GetBoolValue("Pad", "MultitapPort1", false), bsi->GetBoolValue("Pad", "MultitapPort2", false)}}; + + // we reorder things a little to make it look less silly for mtap + static constexpr const std::array mtap_slot_names = {{'A', 'B', 'C', 'D'}}; + static constexpr const std::array mtap_port_order = {{0, 2, 3, 4, 1, 5, 6, 7}}; + + // create the ports + for (u32 global_slot : mtap_port_order) + { + const bool is_mtap_port = sioPadIsMultitapSlot(global_slot); + const auto [mtap_port, mtap_slot] = sioConvertPadToPortAndSlot(global_slot); + if (is_mtap_port && !mtap_enabled[mtap_port]) + continue; + + MenuHeading( + (mtap_enabled[mtap_port] ? fmt::format(ICON_FA_PLUG " Controller Port {}{}", mtap_port + 1, mtap_slot_names[mtap_slot]) : + fmt::format(ICON_FA_PLUG " Controller Port {}", mtap_port + 1)) + .c_str()); + + const std::string section(fmt::format("Pad{}", global_slot + 1)); + const std::string type(bsi->GetStringValue(section.c_str(), "Type", PAD::GetDefaultPadType(global_slot))); + const PAD::ControllerInfo* ci = PAD::GetControllerInfo(type); + if (MenuButton(fmt::format(ICON_FA_GAMEPAD " Controller Type##type{}", global_slot).c_str(), ci ? ci->display_name : "Unknown")) + { + std::vector> raw_options(PAD::GetControllerTypeNames()); + ImGuiFullscreen::ChoiceDialogOptions options; + options.reserve(raw_options.size()); + for (auto& it : raw_options) + { + options.emplace_back(std::move(it.second), type == it.first); + } + OpenChoiceDialog(fmt::format("Port {} Controller Type", global_slot + 1).c_str(), false, std::move(options), + [game_settings = IsEditingGameSettings(bsi), section, raw_options = std::move(raw_options)]( + s32 index, const std::string& title, bool checked) { + if (index < 0) + return; + + auto lock = Host::GetSettingsLock(); + SettingsInterface* bsi = GetEditingSettingsInterface(game_settings); + bsi->SetStringValue(section.c_str(), "Type", raw_options[index].first.c_str()); + SetSettingsChanged(bsi); + CloseChoiceDialog(); + }); + } + + if (!ci || ci->num_bindings == 0) + continue; + + if (MenuButton(ICON_FA_MAGIC " Automatic Mapping", "Attempts to map the selected port to a chosen controller.")) + StartAutomaticBinding(global_slot); + + for (u32 i = 0; i < ci->num_bindings; i++) + { + const PAD::ControllerBindingInfo& bi = ci->bindings[i]; + DrawInputBindingButton(bsi, bi.type, section.c_str(), bi.name, bi.display_name, true); + } + + MenuHeading((mtap_enabled[mtap_port] ? + fmt::format(ICON_FA_MICROCHIP " Controller Port {}{} Macros", mtap_port + 1, mtap_slot_names[mtap_slot]) : + fmt::format(ICON_FA_MICROCHIP " Controller Port {} Macros", mtap_port + 1)) + .c_str()); + + for (u32 macro_index = 0; macro_index < PAD::NUM_MACRO_BUTTONS_PER_CONTROLLER; macro_index++) + { + DrawInputBindingButton(bsi, PAD::ControllerBindingType::Macro, section.c_str(), fmt::format("Macro{}", macro_index + 1).c_str(), + fmt::format("Macro {} Trigger", macro_index + 1).c_str()); + + std::string binds_string(bsi->GetStringValue(section.c_str(), fmt::format("Macro{}Binds", macro_index + 1).c_str())); + if (MenuButton(fmt::format(ICON_FA_KEYBOARD " Macro {} Buttons", macro_index + 1).c_str(), + binds_string.empty() ? "No Buttons Selected" : binds_string.c_str())) + { + std::vector buttons_split(StringUtil::SplitString(binds_string, '&', true)); + ImGuiFullscreen::ChoiceDialogOptions options; + for (u32 i = 0; i < ci->num_bindings; i++) + { + const PAD::ControllerBindingInfo& bi = ci->bindings[i]; + if (bi.type != PAD::ControllerBindingType::Button && bi.type != PAD::ControllerBindingType::Axis && + bi.type != PAD::ControllerBindingType::HalfAxis) + { + continue; + } + options.emplace_back(bi.display_name, std::any_of(buttons_split.begin(), buttons_split.end(), + [bi](const std::string_view& it) { return (it == bi.name); })); + } + + OpenChoiceDialog(fmt::format("Select Macro {} Binds", macro_index + 1).c_str(), true, std::move(options), + [section, macro_index, ci](s32 index, const std::string& title, bool checked) { + // convert display name back to bind name + std::string_view to_modify; + for (u32 j = 0; j < ci->num_bindings; j++) + { + const PAD::ControllerBindingInfo& bi = ci->bindings[j]; + if (bi.display_name == title) + { + to_modify = bi.name; + break; + } + } + if (to_modify.empty()) + { + // wtf? + return; + } + + auto lock = Host::GetSettingsLock(); + SettingsInterface* bsi = GetEditingSettingsInterface(); + const std::string key(fmt::format("Macro{}Binds", macro_index + 1)); + + std::string binds_string(bsi->GetStringValue(section.c_str(), key.c_str())); + std::vector buttons_split(StringUtil::SplitString(binds_string, '&', true)); + auto it = std::find(buttons_split.begin(), buttons_split.end(), to_modify); + if (checked) + { + if (it == buttons_split.end()) + buttons_split.push_back(to_modify); + } + else + { + if (it != buttons_split.end()) + buttons_split.erase(it); + } + + binds_string = StringUtil::JoinString(buttons_split.begin(), buttons_split.end(), " & "); + if (binds_string.empty()) + bsi->DeleteValue(section.c_str(), key.c_str()); + else + bsi->SetStringValue(section.c_str(), key.c_str(), binds_string.c_str()); + }); + } + + const std::string freq_key(fmt::format("Macro{}Frequency", macro_index + 1)); + const std::string freq_title(fmt::format(ICON_FA_LIGHTBULB " Macro {} Frequency", macro_index + 1)); + s32 frequency = bsi->GetIntValue(section.c_str(), freq_key.c_str(), 0); + const std::string freq_summary((frequency == 0) ? std::string("Macro will not auto-toggle.") : + fmt::format("Macro will toggle every {} frames.", frequency)); + if (MenuButton(freq_title.c_str(), freq_summary.c_str())) + ImGui::OpenPopup(freq_title.c_str()); + + ImGui::SetNextWindowSize(LayoutScale(500.0f, 180.0f)); + + ImGui::PushFont(g_large_font); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, + LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); + + if (ImGui::BeginPopupModal( + freq_title.c_str(), nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) + { + ImGui::SetNextItemWidth(LayoutScale(450.0f)); + if (ImGui::SliderInt("##value", &frequency, 0, 60, "Toggle every %d frames", ImGuiSliderFlags_NoInput)) + { + if (frequency == 0) + bsi->DeleteValue(section.c_str(), freq_key.c_str()); + else + bsi->SetIntValue(section.c_str(), freq_key.c_str(), frequency); + } + + BeginMenuButtons(); + if (MenuButton("OK", nullptr, true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) + ImGui::CloseCurrentPopup(); + EndMenuButtons(); + + ImGui::EndPopup(); + } + + ImGui::PopStyleVar(3); + ImGui::PopFont(); + } + + if (ci->num_settings > 0) + { + MenuHeading((mtap_enabled[mtap_port] ? + fmt::format(ICON_FA_SLIDERS_H " Controller Port {}{} Settings", mtap_port + 1, mtap_slot_names[mtap_slot]) : + fmt::format(ICON_FA_SLIDERS_H " Controller Port {} Settings", mtap_port + 1)) + .c_str()); + + for (u32 i = 0; i < ci->num_settings; i++) + { + const PAD::ControllerSettingInfo& si = ci->settings[i]; + std::string title(fmt::format(ICON_FA_COG " {}", si.display_name)); + switch (si.type) + { + case PAD::ControllerSettingInfo::Type::Boolean: + DrawToggleSetting( + bsi, title.c_str(), si.description, section.c_str(), si.name, si.BooleanDefaultValue(), true, false); + break; + case PAD::ControllerSettingInfo::Type::Integer: + DrawIntRangeSetting(bsi, title.c_str(), si.description, section.c_str(), si.name, si.IntegerDefaultValue(), + si.IntegerMinValue(), si.IntegerMaxValue(), si.format, true); + break; + case PAD::ControllerSettingInfo::Type::Float: + DrawFloatRangeSetting(bsi, title.c_str(), si.description, section.c_str(), si.name, si.FloatDefaultValue(), + si.FloatMinValue(), si.FloatMaxValue(), si.format, si.multiplier, true); + break; + default: + break; + } + } + } + } + + EndMenuButtons(); +} + +void FullscreenUI::DrawHotkeySettingsPage() +{ + SettingsInterface* bsi = GetEditingSettingsInterface(); + + BeginMenuButtons(); + + InputManager::GetHotkeyList(); + + const HotkeyInfo* last_category = nullptr; + for (const HotkeyInfo* hotkey : s_hotkey_list_cache) + { + if (!last_category || hotkey->category != last_category->category) + { + MenuHeading(hotkey->category); + last_category = hotkey; + } + + DrawInputBindingButton(bsi, PAD::ControllerBindingType::Button, "Hotkeys", hotkey->name, hotkey->display_name, false); + } + + EndMenuButtons(); +} + +void FullscreenUI::DrawAchievementsSettingsPage() +{ + // TODO: Implement once achievements are merged. + + BeginMenuButtons(); + ActiveButton(ICON_FA_BAN " This build was not compiled with Achivements support.", false, false, + ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); + EndMenuButtons(); +} + +void FullscreenUI::DrawAdvancedSettingsPage() +{ + SettingsInterface* bsi = GetEditingSettingsInterface(); + + BeginMenuButtons(); + + MenuHeading("Logging"); + + DrawToggleSetting(bsi, "System Console", "Writes log messages to the system console (console window/standard output).", "Logging", + "EnableSystemConsole", false); + DrawToggleSetting(bsi, "File Logging", "Writes log messages to emulog.txt.", "Logging", "EnableFileLogging", false); + DrawToggleSetting(bsi, "Verbose Logging", "Writes dev log messages to log sinks.", "Logging", "EnableVerbose", false, !IsDevBuild); + DrawToggleSetting(bsi, "Log Timestamps", "Writes timestamps alongside log messages.", "Logging", "EnableTimestamps", true); + DrawToggleSetting( + bsi, "EE Console", "Writes debug messages from the game's EE code to the console.", "Logging", "EnableEEConsole", true); + DrawToggleSetting( + bsi, "IOP Console", "Writes debug messages from the game's IOP code to the console.", "Logging", "EnableIOPConsole", true); + DrawToggleSetting(bsi, "CDVD Verbose Reads", "Logs disc reads from games.", "EmuCore", "CdvdVerboseReads", false); + + MenuHeading("Advanced System"); + + DrawToggleSetting(bsi, "Enable EE Recompiler", + "Performs just-in-time binary translation of 64-bit MIPS-IV machine code to native code.", "EmuCore/CPU/Recompiler", "EnableEE", + true); + DrawToggleSetting( + bsi, "Enable EE Cache", "Enables simulation of the EE's cache. Slow.", "EmuCore/CPU/Recompiler", "EnableEECache", false); + DrawToggleSetting(bsi, "Enable INTC Spin Detection", "Huge speedup for some games, with almost no compatibility side effects.", + "EmuCore/Speedhacks", "IntcStat", true); + DrawToggleSetting(bsi, "Enable Wait Loop Detection", "Moderate speedup for some games, with no known side effects.", + "EmuCore/Speedhacks", "WaitLoop", true); + DrawToggleSetting(bsi, "Enable VU0 Recompiler (Micro Mode)", + "New Vector Unit recompiler with much improved compatibility. Recommended.", "EmuCore/CPU/Recompiler", "EnableVU0", true); + DrawToggleSetting(bsi, "Enable VU1 Recompiler", "New Vector Unit recompiler with much improved compatibility. Recommended.", + "EmuCore/CPU/Recompiler", "EnableVU1", true); + DrawToggleSetting(bsi, "Enable VU Flag Optimization", "Good speedup and high compatibility, may cause graphical errors.", + "EmuCore/Speedhacks", "vuFlagHack", true); + DrawToggleSetting(bsi, "Enable IOP Recompiler", + "Performs just-in-time binary translation of 32-bit MIPS-I machine code to native code.", "EmuCore/CPU/Recompiler", "EnableIOP", + true); + + MenuHeading("Graphics"); + + DrawToggleSetting(bsi, "Use Debug Device", "Enables API-level validation of graphics commands", "EmuCore/GS", "UseDebugDevice", false); + + + + EndMenuButtons(); +} + +void FullscreenUI::DrawGameFixesSettingsPage() +{ + SettingsInterface* bsi = GetEditingSettingsInterface(); + + BeginMenuButtons(); + + MenuHeading("Game Fixes"); + ActiveButton("Game fixes should not be modified unless you are aware of what each option does and the implications of doing so.", false, + false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); + + DrawToggleSetting(bsi, "FPU Multiply Hack", "For Tales of Destiny.", "EmuCore/Gamefixes", "FpuMulHack", false); + DrawToggleSetting(bsi, "FPU Negative Div Hack", "For Gundam games.", "EmuCore/Gamefixes", "FpuNegDivHack", false); + DrawToggleSetting(bsi, "Preload TLB Hack", "To avoid tlb miss on Goemon.", "EmuCore/Gamefixes", "GoemonTlbHack", false); + DrawToggleSetting(bsi, "Switch to Software renderer for FMVs.", "Needed for some games with complex FMV rendering.", + "EmuCore/Gamefixes", "SoftwareRendererFMVHack", false); + DrawToggleSetting( + bsi, "Skip MPEG Hack", "Skips videos/FMVs in games to avoid game hanging/freezes.", "EmuCore/Gamefixes", "SkipMPEGHack", false); + DrawToggleSetting(bsi, "OPH Flag Hack", "Known to affect following games: Bleach Blade Battler, Growlanser II and III, Wizardry.", + "EmuCore/Gamefixes", "OPHFlagHack", false); + DrawToggleSetting(bsi, "EE Timing Hack", + "Known to affect following games: Digital Devil Saga (Fixes FMV and crashes), SSX (Fixes bad graphics and crashes).", + "EmuCore/Gamefixes", "EETimingHack", false); + DrawToggleSetting(bsi, "Handle DMAC writes when it is busy.", + "Known to affect following games: Mana Khemia 1 (Going \"off campus\"), Metal Saga (Intro FMV), Pilot Down Behind Enemy Lines.", + "EmuCore/Gamefixes", "DMABusyHack", false); + DrawToggleSetting(bsi, "Force GIF PATH3 transfers through FIFO", "(Fifa Street 2).", "EmuCore/Gamefixes", "GIFFIFOHack", false); + DrawToggleSetting(bsi, "Simulate VIF1 FIFO read ahead. Fixes slow loading games.", + "Known to affect following games: Test Drive Unlimited, Transformers.", "EmuCore/Gamefixes", "VIFFIFOHack", false); + DrawToggleSetting( + bsi, "Delay VIF1 Stalls (VIF1 FIFO)", "For SOCOM 2 HUD and Spy Hunter loading hang.", "EmuCore/Gamefixes", "VIF1StallHack", false); + DrawToggleSetting(bsi, "VU Add Hack", "Games that need this hack to boot: Star Ocean 3, Radiata Stories, Valkyrie Profile 2.", + "EmuCore/Gamefixes", "VuAddSubHack", false); + DrawToggleSetting(bsi, "VU I bit Hack avoid constant recompilation in some games", + "Scarface The World Is Yours, Crash Tag Team Racing.", "EmuCore/Gamefixes", "IbitHack", false); + DrawToggleSetting(bsi, "VU Sync (Run behind)", "To avoid sync problems when reading or writing VU registers.", "EmuCore/Gamefixes", + "VUSyncHack", false); + DrawToggleSetting( + bsi, "VU Overflow Hack", "To check for possible float overflows (Superman Returns).", "EmuCore/Gamefixes", "VUOverflowHack", false); + DrawToggleSetting(bsi, "VU XGkick Sync", "Use accurate timing for VU XGKicks (slower).", "EmuCore/Gamefixes", "XgKickHack", false); + DrawToggleSetting(bsi, "Use Blit for internal FPS", + "Use alternative method to calclate internal FPS to avoid false readings in some games.", "EmuCore/Gamefixes", + "BlitInternalFPSHack", false); + + EndMenuButtons(); +} + +void FullscreenUI::DrawPauseMenu(MainWindowType type) +{ + ImDrawList* dl = ImGui::GetBackgroundDrawList(); + const ImVec2 display_size(ImGui::GetIO().DisplaySize); + dl->AddRectFilled(ImVec2(0.0f, 0.0f), display_size, IM_COL32(0x21, 0x21, 0x21, 200)); + + // title info + { + const ImVec2 title_size( + g_large_font->CalcTextSizeA(g_large_font->FontSize, std::numeric_limits::max(), -1.0f, s_current_game_title.c_str())); + const ImVec2 subtitle_size(g_medium_font->CalcTextSizeA( + g_medium_font->FontSize, std::numeric_limits::max(), -1.0f, s_current_game_subtitle.c_str())); + + ImVec2 title_pos(display_size.x - LayoutScale(20.0f + 50.0f + 20.0f) - title_size.x, display_size.y - LayoutScale(20.0f + 50.0f)); + ImVec2 subtitle_pos(display_size.x - LayoutScale(20.0f + 50.0f + 20.0f) - subtitle_size.x, + title_pos.y + g_large_font->FontSize + LayoutScale(4.0f)); + float rp_height = 0.0f; + + dl->AddText(g_large_font, g_large_font->FontSize, title_pos, IM_COL32(255, 255, 255, 255), s_current_game_title.c_str()); + dl->AddText(g_medium_font, g_medium_font->FontSize, subtitle_pos, IM_COL32(255, 255, 255, 255), s_current_game_subtitle.c_str()); + + const ImVec2 image_min( + display_size.x - LayoutScale(20.0f + 50.0f) - rp_height, display_size.y - LayoutScale(20.0f + 50.0f) - rp_height); + const ImVec2 image_max(image_min.x + LayoutScale(50.0f) + rp_height, image_min.y + LayoutScale(50.0f) + rp_height); + dl->AddImage(GetCoverForCurrentGame()->GetHandle(), image_min, image_max); + } + + const ImVec2 window_size(LayoutScale(500.0f, LAYOUT_SCREEN_HEIGHT)); + const ImVec2 window_pos(0.0f, display_size.y - window_size.y); + + if (BeginFullscreenWindow( + window_pos, window_size, "pause_menu", ImVec4(0.0f, 0.0f, 0.0f, 0.0f), 0.0f, 10.0f, ImGuiWindowFlags_NoBackground)) + { + static constexpr u32 submenu_item_count[] = { + 10, // None + 4, // Exit + }; + + const bool just_focused = ResetFocusHere(); + BeginMenuButtons(submenu_item_count[static_cast(s_current_pause_submenu)], 1.0f, ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, + ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); + + switch (s_current_pause_submenu) + { + case PauseSubMenu::None: + { + // NOTE: Menu close must come first, because otherwise VM destruction options will race. + const bool can_load_or_save_state = s_current_game_crc != 0; + + if (ActiveButton(ICON_FA_PLAY " Resume Game", false) || WantsToCloseMenu()) + ClosePauseMenu(); + + if (ActiveButton(ICON_FA_FAST_FORWARD " Toggle Frame Limit", false)) + { + ClosePauseMenu(); + DoToggleFrameLimit(); + } + + if (ActiveButton(ICON_FA_UNDO " Load State", false, can_load_or_save_state)) + { + if (OpenSaveStateSelector(true)) + s_current_main_window = MainWindowType::None; + } + + if (ActiveButton(ICON_FA_DOWNLOAD " Save State", false, can_load_or_save_state)) + { + if (OpenSaveStateSelector(false)) + s_current_main_window = MainWindowType::None; + } + + if (ActiveButton(ICON_FA_WRENCH " Game Properties", false, can_load_or_save_state)) + { + SwitchToGameSettings(); + } + + if (ActiveButton(ICON_FA_CAMERA " Save Screenshot", false)) + { + GSQueueSnapshot(std::string()); + ClosePauseMenu(); + } + + if (ActiveButton(GSConfig.UseHardwareRenderer() ? (ICON_FA_PAINT_BRUSH " Switch To Software Renderer") : + (ICON_FA_PAINT_BRUSH " Switch To Hardware Renderer"), + false)) + { + ClosePauseMenu(); + DoToggleSoftwareRenderer(); + } + + if (ActiveButton(ICON_FA_COMPACT_DISC " Change Disc", false)) + { + s_current_main_window = MainWindowType::None; + DoChangeDisc(); + } + + if (ActiveButton(ICON_FA_SLIDERS_H " Settings", false)) + SwitchToSettings(); + + if (ActiveButton(ICON_FA_POWER_OFF " Close Game", false)) + { + // skip submenu when we can't save anyway + if (!can_load_or_save_state) + DoShutdown(false); + else + OpenPauseSubMenu(PauseSubMenu::Exit); + } + } + break; + + case PauseSubMenu::Exit: + { + if (just_focused) + ImGui::SetFocusID(ImGui::GetID(ICON_FA_POWER_OFF " Exit Without Saving"), ImGui::GetCurrentWindow()); + + if (ActiveButton(ICON_FA_BACKWARD " Back To Pause Menu", false)) + { + OpenPauseSubMenu(PauseSubMenu::None); + } + + if (ActiveButton(ICON_FA_SYNC " Reset System", false)) + { + ClosePauseMenu(); + DoReset(); + } + + if (ActiveButton(ICON_FA_SAVE " Exit And Save State", false)) + DoShutdown(true); + + if (ActiveButton(ICON_FA_POWER_OFF " Exit Without Saving", false)) + DoShutdown(false); + } + break; + } + + EndMenuButtons(); + + EndFullscreenWindow(); + } +} + +void FullscreenUI::InitializePlaceholderSaveStateListEntry( + SaveStateListEntry* li, const std::string& title, const std::string& serial, u32 crc, s32 slot) +{ + li->title = fmt::format("{0} Slot {1}##game_slot_{1}", s_current_game_title, slot); + li->summary = "No Save State"; + li->path = {}; + li->slot = slot; + li->preview_texture = {}; +} + +bool FullscreenUI::InitializeSaveStateListEntry( + SaveStateListEntry* li, const std::string& title, const std::string& serial, u32 crc, s32 slot) +{ + std::string filename(VMManager::GetSaveStateFileName(serial.c_str(), crc, slot)); + FILESYSTEM_STAT_DATA sd; + if (filename.empty() || !FileSystem::StatFile(filename.c_str(), &sd)) + { + InitializePlaceholderSaveStateListEntry(li, title, serial, crc, slot); + return false; + } + + li->title = fmt::format("{0} Slot {1}##game_slot_{1}", title, slot); + li->summary = fmt::format("{0} - Saved {1}", serial, TimeToPrintableString(sd.ModificationTime)); + li->slot = slot; + li->path = std::move(filename); + + li->preview_texture.reset(); + + u32 screenshot_width, screenshot_height; + std::vector screenshot_pixels; + if (SaveState_ReadScreenshot(li->path, &screenshot_width, &screenshot_height, &screenshot_pixels)) + { + li->preview_texture = Host::GetHostDisplay()->CreateTexture( + screenshot_width, screenshot_height, screenshot_pixels.data(), sizeof(u32) * screenshot_width, false); + if (!li->preview_texture) + Console.Error("Failed to upload save state image to GPU"); + } + + return true; +} + +void FullscreenUI::ClearSaveStateEntryList() +{ + for (SaveStateListEntry& entry : s_save_state_selector_slots) + { + if (entry.preview_texture) + s_cleanup_textures.push_back(std::move(entry.preview_texture)); + } + s_save_state_selector_slots.clear(); +} + +u32 FullscreenUI::PopulateSaveStateListEntries(const std::string& title, const std::string& serial, u32 crc) +{ + ClearSaveStateEntryList(); + + for (s32 i = 0; i <= MAX_SAVE_STATE_SLOTS; i++) + { + SaveStateListEntry li; + if (InitializeSaveStateListEntry(&li, title, serial, crc, i) || !s_save_state_selector_loading) + s_save_state_selector_slots.push_back(std::move(li)); + } + + return static_cast(s_save_state_selector_slots.size()); +} + +bool FullscreenUI::OpenLoadStateSelectorForGame(const std::string& game_path) +{ + auto lock = GameList::GetLock(); + const GameList::Entry* entry = GameList::GetEntryForPath(game_path.c_str()); + if (entry) + { + s_save_state_selector_loading = true; + if (PopulateSaveStateListEntries(entry->title.c_str(), entry->serial.c_str(), entry->crc) > 0) + { + s_save_state_selector_open = true; + s_save_state_selector_game_path = game_path; + return true; + } + } + + ShowToast({}, "No save states found.", 5.0f); + return false; +} + +bool FullscreenUI::OpenSaveStateSelector(bool is_loading) +{ + s_save_state_selector_game_path = {}; + s_save_state_selector_loading = is_loading; + if (PopulateSaveStateListEntries(s_current_game_title.c_str(), s_current_game_serial.c_str(), s_current_game_crc) > 0) + { + s_save_state_selector_open = true; + return true; + } + + ShowToast({}, "No save states found.", 5.0f); + return false; +} + +void FullscreenUI::CloseSaveStateSelector() +{ + ClearSaveStateEntryList(); + s_save_state_selector_open = false; + s_save_state_selector_loading = false; + s_save_state_selector_game_path = {}; + ReturnToMainWindow(); +} + +void FullscreenUI::DrawSaveStateSelector(bool is_loading, bool fullscreen) +{ + if (fullscreen) + { + if (!BeginFullscreenColumns()) + { + EndFullscreenColumns(); + return; + } + + if (!BeginFullscreenColumnWindow(0.0f, LAYOUT_SCREEN_WIDTH, "save_state_selector_slots")) + { + EndFullscreenColumnWindow(); + EndFullscreenColumns(); + return; + } + } + else + { + const char* window_title = is_loading ? "Load State" : "Save State"; + + ImGui::PushFont(g_large_font); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, + LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING)); + + ImGui::SetNextWindowSize(LayoutScale(1000.0f, 680.0f)); + ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + ImGui::OpenPopup(window_title); + bool is_open = !WantsToCloseMenu(); + if (!ImGui::BeginPopupModal( + window_title, &is_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove) || + !is_open) + { + ImGui::PopStyleVar(2); + ImGui::PopFont(); + CloseSaveStateSelector(); + return; + } + } + + BeginMenuButtons(); + + constexpr float padding = 10.0f; + constexpr float button_height = 96.0f; + constexpr float max_image_width = 96.0f; + constexpr float max_image_height = 96.0f; + + for (const SaveStateListEntry& entry : s_save_state_selector_slots) + { + ImRect bb; + bool visible, hovered; + bool pressed = MenuButtonFrame(entry.title.c_str(), true, button_height, &visible, &hovered, &bb.Min, &bb.Max); + if (!visible) + continue; + + ImVec2 pos(bb.Min); + + // use aspect ratio of screenshot to determine height + const HostDisplayTexture* image = entry.preview_texture ? entry.preview_texture.get() : GetPlaceholderTexture().get(); + const float image_height = max_image_width / (static_cast(image->GetWidth()) / static_cast(image->GetHeight())); + const float image_margin = (max_image_height - image_height) / 2.0f; + const ImRect image_bb( + ImVec2(pos.x, pos.y + LayoutScale(image_margin)), pos + LayoutScale(max_image_width, image_margin + image_height)); + pos.x += LayoutScale(max_image_width + padding); + + ImRect text_bb(pos, ImVec2(bb.Max.x, pos.y + g_large_font->FontSize)); + ImGui::PushFont(g_large_font); + ImGui::RenderTextClipped(text_bb.Min, text_bb.Max, entry.title.c_str(), nullptr, nullptr, ImVec2(0.0f, 0.0f), &text_bb); + ImGui::PopFont(); + + ImGui::PushFont(g_medium_font); + + if (!entry.summary.empty()) + { + text_bb.Min.y = text_bb.Max.y + LayoutScale(4.0f); + text_bb.Max.y = text_bb.Min.y + g_medium_font->FontSize; + ImGui::RenderTextClipped(text_bb.Min, text_bb.Max, entry.summary.c_str(), nullptr, nullptr, ImVec2(0.0f, 0.0f), &text_bb); + } + + if (!entry.path.empty()) + { + text_bb.Min.y = text_bb.Max.y + LayoutScale(4.0f); + text_bb.Max.y = text_bb.Min.y + g_medium_font->FontSize; + ImGui::RenderTextClipped(text_bb.Min, text_bb.Max, entry.path.c_str(), nullptr, nullptr, ImVec2(0.0f, 0.0f), &text_bb); + } + + ImGui::PopFont(); + + ImGui::GetWindowDrawList()->AddImage( + static_cast(entry.preview_texture ? entry.preview_texture->GetHandle() : GetPlaceholderTexture()->GetHandle()), + image_bb.Min, image_bb.Max); + + if (pressed) + { + if (is_loading) + { + DoLoadState(entry.path); + CloseSaveStateSelector(); + } + else + { + Host::RunOnCPUThread([slot = entry.slot]() { VMManager::SaveStateToSlot(slot); }); + CloseSaveStateSelector(); + } + } + } + + EndMenuButtons(); + + if (fullscreen) + { + EndFullscreenColumnWindow(); + EndFullscreenColumns(); + } + else + { + ImGui::EndPopup(); + ImGui::PopStyleVar(2); + ImGui::PopFont(); + } +} + +void FullscreenUI::DoLoadState(std::string path) +{ + Host::RunOnCPUThread([boot_path = s_save_state_selector_game_path, path = std::move(path)]() { + if (VMManager::HasValidVM()) + { + VMManager::LoadState(path.c_str()); + if (!boot_path.empty() && VMManager::GetDiscPath() != boot_path) + VMManager::ChangeDisc(CDVD_SourceType::Iso, std::move(boot_path)); + } + else + { + VMBootParameters params; + params.filename = std::move(boot_path); + params.save_state = std::move(path); + if (VMManager::Initialize(params)) + VMManager::SetState(VMState::Running); + } + }); +} + +void FullscreenUI::PopulateGameListEntryList() +{ + const u32 count = GameList::GetEntryCount(); + s_game_list_sorted_entries.resize(count); + for (u32 i = 0; i < count; i++) + s_game_list_sorted_entries[i] = GameList::GetEntryByIndex(i); + + // TODO: Custom sort types + std::sort(s_game_list_sorted_entries.begin(), s_game_list_sorted_entries.end(), + [](const GameList::Entry* lhs, const GameList::Entry* rhs) { return lhs->title < rhs->title; }); +} + +void FullscreenUI::DrawGameListWindow() +{ + if (!BeginFullscreenColumns()) + { + EndFullscreenColumns(); + return; + } + + auto game_list_lock = GameList::GetLock(); + const GameList::Entry* selected_entry = nullptr; + PopulateGameListEntryList(); + + if (BeginFullscreenColumnWindow(0.0f, 750.0f, "game_list_entries")) + { + const ImVec2 image_size(LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT * 0.68f, LAYOUT_MENU_BUTTON_HEIGHT)); + + ResetFocusHere(); + + BeginMenuButtons(); + + // TODO: replace with something not heap allocating + std::string summary; + + for (const GameList::Entry* entry : s_game_list_sorted_entries) + { + ImRect bb; + bool visible, hovered; + bool pressed = MenuButtonFrame(entry->path.c_str(), true, LAYOUT_MENU_BUTTON_HEIGHT, &visible, &hovered, &bb.Min, &bb.Max); + if (!visible) + continue; + + HostDisplayTexture* cover_texture = GetGameListCover(entry); + + summary.clear(); + if (entry->serial.empty()) + fmt::format_to(std::back_inserter(summary), "{} - ", GameList::RegionToString(entry->region)); + else + fmt::format_to(std::back_inserter(summary), "{} - {} - ", entry->serial, GameList::RegionToString(entry->region)); + + const std::string_view filename(Path::GetFileName(entry->path)); + summary.append(filename); + + const ImRect image_rect(CenterImage(ImRect(bb.Min, bb.Min + image_size), + ImVec2(static_cast(cover_texture->GetWidth()), static_cast(cover_texture->GetHeight())))); + + ImGui::GetWindowDrawList()->AddImage(cover_texture->GetHandle(), image_rect.Min, image_rect.Max, ImVec2(0.0f, 0.0f), + ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); + + const float midpoint = bb.Min.y + g_large_font->FontSize + LayoutScale(4.0f); + const float text_start_x = bb.Min.x + image_size.x + LayoutScale(15.0f); + const ImRect title_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint)); + const ImRect summary_bb(ImVec2(text_start_x, midpoint), bb.Max); + + ImGui::PushFont(g_large_font); + ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, entry->title.c_str(), entry->title.c_str() + entry->title.size(), nullptr, + ImVec2(0.0f, 0.0f), &title_bb); + ImGui::PopFont(); + + if (!summary.empty()) + { + ImGui::PushFont(g_medium_font); + ImGui::RenderTextClipped( + summary_bb.Min, summary_bb.Max, summary.c_str(), nullptr, nullptr, ImVec2(0.0f, 0.0f), &summary_bb); + ImGui::PopFont(); + } + + if (pressed) + { + // launch game + DoStartPath(entry->path); + } + + if (hovered) + selected_entry = entry; + + if (selected_entry && + (ImGui::IsItemClicked(ImGuiMouseButton_Right) || ImGui::IsNavInputTest(ImGuiNavInput_Input, ImGuiNavReadMode_Pressed))) + { + ImGuiFullscreen::ChoiceDialogOptions options = { + {"Open Game Properties", false}, + {"Resume Game", false}, + {"Load State", false}, + {"Default Boot", false}, + {"Fast Boot", false}, + {"Slow Boot", false}, + {"Close Menu", false}, + }; + + OpenChoiceDialog(selected_entry->title.c_str(), false, std::move(options), + [entry_path = selected_entry->path](s32 index, const std::string& title, bool checked) { + switch (index) + { + case 0: // Open Game Properties + SwitchToGameSettings(entry_path); + break; + case 1: // Resume Game + DoStartPath(entry_path, -1); + break; + case 2: // Load State + OpenLoadStateSelectorForGame(entry_path); + break; + case 3: // Default Boot + DoStartPath(entry_path); + break; + case 4: // Fast Boot + DoStartPath(entry_path, std::nullopt, true); + break; + case 5: // Slow Boot + DoStartPath(entry_path, std::nullopt, false); + break; + default: + break; + } + + CloseChoiceDialog(); + }); + } + } + + EndMenuButtons(); + } + EndFullscreenColumnWindow(); + + if (BeginFullscreenColumnWindow(750.0f, LAYOUT_SCREEN_WIDTH, "game_list_info", UIPrimaryDarkColor)) + { + const HostDisplayTexture* cover_texture = + selected_entry ? GetGameListCover(selected_entry) : GetTextureForGameListEntryType(GameList::EntryType::Count); + if (cover_texture) + { + const ImRect image_rect(CenterImage(LayoutScale(ImVec2(275.0f, 400.0f)), + ImVec2(static_cast(cover_texture->GetWidth()), static_cast(cover_texture->GetHeight())))); + + ImGui::SetCursorPos(LayoutScale(ImVec2(128.0f, 20.0f)) + image_rect.Min); + ImGui::Image(selected_entry ? GetGameListCover(selected_entry)->GetHandle() : + GetTextureForGameListEntryType(GameList::EntryType::Count)->GetHandle(), + image_rect.GetSize()); + } + + const float work_width = ImGui::GetCurrentWindow()->WorkRect.GetWidth(); + constexpr float field_margin_y = 10.0f; + constexpr float start_x = 50.0f; + float text_y = 440.0f; + float text_width; + + ImGui::SetCursorPos(LayoutScale(start_x, text_y)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, field_margin_y)); + ImGui::PushTextWrapPos(LayoutScale(480.0f)); + ImGui::BeginGroup(); + + if (selected_entry) + { + // title + ImGui::PushFont(g_large_font); + const std::string_view title( + std::string_view(selected_entry->title).substr(0, (selected_entry->title.length() > 37) ? 37 : std::string_view::npos)); + text_width = ImGui::CalcTextSize(title.data(), title.data() + title.length(), false, work_width).x; + ImGui::SetCursorPosX((work_width - text_width) / 2.0f); + ImGui::TextWrapped( + "%.*s%s", static_cast(title.size()), title.data(), (title.length() == selected_entry->title.length()) ? "" : "..."); + ImGui::PopFont(); + + ImGui::PushFont(g_medium_font); + + // code + text_width = ImGui::CalcTextSize(selected_entry->serial.c_str(), nullptr, false, work_width).x; + ImGui::SetCursorPosX((work_width - text_width) / 2.0f); + ImGui::TextWrapped("%s", selected_entry->serial.c_str()); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 15.0f); + + // file tile + const std::string_view filename(Path::GetFileName(selected_entry->path)); + ImGui::TextWrapped("File: %.*s", static_cast(filename.size()), filename.data()); + + // crc + ImGui::Text("CRC: %08X", selected_entry->crc); + + // region + { + std::string flag_texture(fmt::format("icons/flags/{}.png", GameList::RegionToString(selected_entry->region))); + ImGui::TextUnformatted("Region: "); + ImGui::SameLine(); + ImGui::Image(GetCachedTextureAsync(flag_texture.c_str())->GetHandle(), LayoutScale(23.0f, 16.0f)); + ImGui::SameLine(); + ImGui::Text(" (%s)", GameList::RegionToString(selected_entry->region)); + } + + // compatibility + ImGui::TextUnformatted("Compatibility: "); + ImGui::SameLine(); + if (selected_entry->compatibility_rating != GameDatabaseSchema::Compatibility::Unknown) + { + ImGui::Image(s_game_compatibility_textures[static_cast(selected_entry->compatibility_rating) - 1]->GetHandle(), + LayoutScale(64.0f, 16.0f)); + ImGui::SameLine(); + } + ImGui::Text(" (%s)", GameList::EntryCompatibilityRatingToString(selected_entry->compatibility_rating)); + + // size + ImGui::Text("Size: %.2f MB", static_cast(selected_entry->total_size) / 1048576.0f); + + ImGui::PopFont(); + } + else + { + // title + const char* title = "No Game Selected"; + ImGui::PushFont(g_large_font); + text_width = ImGui::CalcTextSize(title, nullptr, false, work_width).x; + ImGui::SetCursorPosX((work_width - text_width) / 2.0f); + ImGui::TextWrapped("%s", title); + ImGui::PopFont(); + } + + ImGui::EndGroup(); + ImGui::PopTextWrapPos(); + ImGui::PopStyleVar(); + + ImGui::SetCursorPosY(ImGui::GetWindowHeight() - LayoutScale(50.0f)); + BeginMenuButtons(); + if (ActiveButton(ICON_FA_BACKWARD " Back", false)) + ReturnToMainWindow(); + EndMenuButtons(); + } + EndFullscreenColumnWindow(); + + EndFullscreenColumns(); +} + +void FullscreenUI::SwitchToGameList() +{ + s_current_main_window = MainWindowType::GameList; + QueueResetFocus(); +} + +HostDisplayTexture* FullscreenUI::GetGameListCover(const GameList::Entry* entry) +{ + // lookup and grab cover image + auto cover_it = s_cover_image_map.find(entry->path); + if (cover_it == s_cover_image_map.end()) + { + std::string cover_path(GameList::GetCoverImagePathForEntry(entry)); + cover_it = s_cover_image_map.emplace(entry->path, std::move(cover_path)).first; + } + + HostDisplayTexture* tex = (!cover_it->second.empty()) ? GetCachedTextureAsync(cover_it->second.c_str()) : nullptr; + return tex ? tex : GetTextureForGameListEntryType(entry->type); +} + +HostDisplayTexture* FullscreenUI::GetTextureForGameListEntryType(GameList::EntryType type) +{ + switch (type) + { + case GameList::EntryType::ELF: + return s_fallback_exe_texture.get(); + + case GameList::EntryType::PS1Disc: + case GameList::EntryType::PS2Disc: + default: + return s_fallback_disc_texture.get(); + } +} + +HostDisplayTexture* FullscreenUI::GetCoverForCurrentGame() +{ + auto lock = GameList::GetLock(); + + const GameList::Entry* entry = GameList::GetEntryForPath(s_current_game_path.c_str()); + if (!entry) + return s_fallback_disc_texture.get(); + + return GetGameListCover(entry); +} + +std::string FullscreenUI::GetNotificationImageForGame(const GameList::Entry* entry) +{ + std::string ret; + + if (entry) + ret = GameList::GetCoverImagePathForEntry(entry); + + return ret; +} + +std::string FullscreenUI::GetNotificationImageForGame(const std::string& game_path) +{ + auto lock = GameList::GetLock(); + const GameList::Entry* entry = GameList::GetEntryForPath(game_path.c_str()); + return entry ? GetNotificationImageForGame(entry) : std::string(); +} + +////////////////////////////////////////////////////////////////////////// +// Overlays +////////////////////////////////////////////////////////////////////////// + +void FullscreenUI::ExitFullscreenAndOpenURL(const std::string_view& url) +{ + Host::RunOnCPUThread([url = std::string(url)]() { + if (Host::IsFullscreen()) + Host::SetFullscreen(false); + + Host::OpenURL(url); + }); +} + +void FullscreenUI::CopyTextToClipboard(std::string title, const std::string_view& text) +{ + if (Host::CopyTextToClipboard(text)) + ShowToast(std::string(), std::move(title)); + else + ShowToast(std::string(), "Failed to copy text to clipboard."); +} + +void FullscreenUI::OpenAboutWindow() +{ + s_about_window_open = true; +} + +void FullscreenUI::DrawAboutWindow() +{ + ImGui::SetNextWindowSize(LayoutScale(1000.0f, 500.0f)); + ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + ImGui::OpenPopup("About PCSX2"); + + ImGui::PushFont(g_large_font); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); + + if (ImGui::BeginPopupModal("About PCSX2", &s_about_window_open, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize)) + { + ImGui::TextWrapped( + "PCSX2 is a free and open-source PlayStation 2 (PS2) emulator. Its purpose is to emulate the PS2's hardware, using a " + "combination of MIPS CPU Interpreters, Recompilers and a Virtual Machine which manages hardware states and PS2 system memory. " + "This allows you to play PS2 games on your PC, with many additional features and benefits."); + + ImGui::NewLine(); + + ImGui::TextWrapped("PlayStation 2 and PS2 are registered trademarks of Sony Interactive Entertainment. This application is not " + "affiliated in any way with Sony Interactive Entertainment."); + + ImGui::NewLine(); + + BeginMenuButtons(); + + if (ActiveButton(ICON_FA_GLOBE " Website", false)) + ExitFullscreenAndOpenURL(PCSX2_WEBSITE_URL); + + if (ActiveButton(ICON_FA_PERSON_BOOTH " Support Forums", false)) + ExitFullscreenAndOpenURL(PCSX2_FORUMS_URL); + + if (ActiveButton(ICON_FA_BUG " GitHub Repository", false)) + ExitFullscreenAndOpenURL(PCSX2_GITHUB_URL); + + if (ActiveButton(ICON_FA_NEWSPAPER " License", false)) + ExitFullscreenAndOpenURL(PCSX2_LICENSE_URL); + + if (ActiveButton(ICON_FA_WINDOW_CLOSE " Close", false)) + { + ImGui::CloseCurrentPopup(); + s_about_window_open = false; + } + EndMenuButtons(); + + ImGui::EndPopup(); + } + + ImGui::PopStyleVar(2); + ImGui::PopFont(); +} + +bool FullscreenUI::DrawErrorWindow(const char* message) +{ + bool is_open = true; + + ImGuiFullscreen::BeginLayout(); + + ImGui::SetNextWindowSize(LayoutScale(500.0f, 0.0f)); + ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + ImGui::OpenPopup("ReportError"); + + ImGui::PushFont(g_large_font); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(10.0f, 10.0f)); + + if (ImGui::BeginPopupModal("ReportError", &is_open, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize)) + { + ImGui::SetCursorPos(LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING, LAYOUT_MENU_BUTTON_Y_PADDING)); + ImGui::TextWrapped("%s", message); + ImGui::GetCurrentWindow()->DC.CursorPos.y += LayoutScale(5.0f); + + BeginMenuButtons(); + + if (ActiveButton(ICON_FA_WINDOW_CLOSE " Close", false)) + { + ImGui::CloseCurrentPopup(); + is_open = false; + } + EndMenuButtons(); + + ImGui::EndPopup(); + } + + ImGui::PopStyleVar(2); + ImGui::PopFont(); + + ImGuiFullscreen::EndLayout(); + return !is_open; +} + +bool FullscreenUI::DrawConfirmWindow(const char* message, bool* result) +{ + bool is_open = true; + + ImGuiFullscreen::BeginLayout(); + + ImGui::SetNextWindowSize(LayoutScale(500.0f, 0.0f)); + ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + ImGui::OpenPopup("ConfirmMessage"); + + ImGui::PushFont(g_large_font); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(10.0f, 10.0f)); + + if (ImGui::BeginPopupModal("ConfirmMessage", &is_open, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize)) + { + ImGui::SetCursorPos(LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING, LAYOUT_MENU_BUTTON_Y_PADDING)); + ImGui::TextWrapped("%s", message); + ImGui::GetCurrentWindow()->DC.CursorPos.y += LayoutScale(5.0f); + + BeginMenuButtons(); + + bool done = false; + + if (ActiveButton(ICON_FA_CHECK " Yes", false)) + { + *result = true; + done = true; + } + + if (ActiveButton(ICON_FA_TIMES " No", false)) + { + *result = false; + done = true; + } + if (done) + { + ImGui::CloseCurrentPopup(); + is_open = false; + } + + EndMenuButtons(); + + ImGui::EndPopup(); + } + + ImGui::PopStyleVar(2); + ImGui::PopFont(); + + ImGuiFullscreen::EndLayout(); + return !is_open; +} + +FullscreenUI::ProgressCallback::ProgressCallback(std::string name) + : BaseProgressCallback() + , m_name(std::move(name)) +{ + ImGuiFullscreen::OpenBackgroundProgressDialog(m_name.c_str(), "", 0, 100, 0); +} + +FullscreenUI::ProgressCallback::~ProgressCallback() +{ + ImGuiFullscreen::CloseBackgroundProgressDialog(m_name.c_str()); +} + +void FullscreenUI::ProgressCallback::PushState() +{ + BaseProgressCallback::PushState(); +} + +void FullscreenUI::ProgressCallback::PopState() +{ + BaseProgressCallback::PopState(); + Redraw(true); +} + +void FullscreenUI::ProgressCallback::SetCancellable(bool cancellable) +{ + BaseProgressCallback::SetCancellable(cancellable); + Redraw(true); +} + +void FullscreenUI::ProgressCallback::SetTitle(const char* title) +{ + // todo? +} + +void FullscreenUI::ProgressCallback::SetStatusText(const char* text) +{ + BaseProgressCallback::SetStatusText(text); + Redraw(true); +} + +void FullscreenUI::ProgressCallback::SetProgressRange(u32 range) +{ + u32 last_range = m_progress_range; + + BaseProgressCallback::SetProgressRange(range); + + if (m_progress_range != last_range) + Redraw(false); +} + +void FullscreenUI::ProgressCallback::SetProgressValue(u32 value) +{ + u32 lastValue = m_progress_value; + + BaseProgressCallback::SetProgressValue(value); + + if (m_progress_value != lastValue) + Redraw(false); +} + +void FullscreenUI::ProgressCallback::Redraw(bool force) +{ + const int percent = static_cast((static_cast(m_progress_value) / static_cast(m_progress_range)) * 100.0f); + if (percent == m_last_progress_percent && !force) + return; + + m_last_progress_percent = percent; + ImGuiFullscreen::UpdateBackgroundProgressDialog(m_name.c_str(), m_status_text.c_str(), 0, 100, percent); +} + +void FullscreenUI::ProgressCallback::DisplayError(const char* message) +{ + Console.Error(message); + Host::ReportErrorAsync("Error", message); +} + +void FullscreenUI::ProgressCallback::DisplayWarning(const char* message) +{ + Console.Warning(message); +} + +void FullscreenUI::ProgressCallback::DisplayInformation(const char* message) +{ + Console.WriteLn(message); +} + +void FullscreenUI::ProgressCallback::DisplayDebugMessage(const char* message) +{ + DevCon.WriteLn(message); +} + +void FullscreenUI::ProgressCallback::ModalError(const char* message) +{ + Console.Error(message); + Host::ReportErrorAsync("Error", message); +} + +bool FullscreenUI::ProgressCallback::ModalConfirmation(const char* message) +{ + return false; +} + +void FullscreenUI::ProgressCallback::ModalInformation(const char* message) +{ + Console.WriteLn(message); +} + +void FullscreenUI::ProgressCallback::SetCancelled() +{ + if (m_cancellable) + m_cancelled = true; +} diff --git a/pcsx2/Frontend/FullscreenUI.h b/pcsx2/Frontend/FullscreenUI.h new file mode 100644 index 0000000000..e57e523875 --- /dev/null +++ b/pcsx2/Frontend/FullscreenUI.h @@ -0,0 +1,75 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include "common/Pcsx2Defs.h" +#include "common/ProgressCallback.h" +#include +#include + +class HostDisplayTexture; + +namespace FullscreenUI +{ + bool Initialize(); + bool IsInitialized(); + bool HasActiveWindow(); + void OnVMStarted(); + void OnVMPaused(); + void OnVMResumed(); + void OnVMDestroyed(); + void OnRunningGameChanged(std::string path, std::string serial, std::string title, u32 crc); + void OpenPauseMenu(); + + void Shutdown(); + void Render(); + + // Returns true if the message has been dismissed. + bool DrawErrorWindow(const char* message); + bool DrawConfirmWindow(const char* message, bool* result); + + class ProgressCallback final : public BaseProgressCallback + { + public: + ProgressCallback(std::string name); + ~ProgressCallback() override; + + void PushState() override; + void PopState() override; + + void SetCancellable(bool cancellable) override; + void SetTitle(const char* title) override; + void SetStatusText(const char* text) override; + void SetProgressRange(u32 range) override; + void SetProgressValue(u32 value) override; + + void DisplayError(const char* message) override; + void DisplayWarning(const char* message) override; + void DisplayInformation(const char* message) override; + void DisplayDebugMessage(const char* message) override; + + void ModalError(const char* message) override; + bool ModalConfirmation(const char* message) override; + void ModalInformation(const char* message) override; + + void SetCancelled(); + + private: + void Redraw(bool force); + + std::string m_name; + int m_last_progress_percent = -1; + }; +} // namespace FullscreenUI diff --git a/pcsx2/Frontend/ImGuiFullscreen.cpp b/pcsx2/Frontend/ImGuiFullscreen.cpp new file mode 100644 index 0000000000..1fcb544813 --- /dev/null +++ b/pcsx2/Frontend/ImGuiFullscreen.cpp @@ -0,0 +1,2265 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#define IMGUI_DEFINE_MATH_OPERATORS + +#include "ImGuiFullscreen.h" +#include "IconsFontAwesome5.h" +#include "common/Assertions.h" +#include "common/Easing.h" +#include "common/Image.h" +#include "common/LRUCache.h" +#include "common/FileSystem.h" +#include "common/Path.h" +#include "common/StringUtil.h" +#include "common/Threading.h" +#include "common/Timer.h" +#include "fmt/core.h" +#include "HostDisplay.h" +#include "imgui_internal.h" +#include "misc/cpp/imgui_stdlib.h" +#include +#include +#include + +namespace ImGuiFullscreen +{ + static std::optional LoadTextureImage(const char* path); + static std::shared_ptr UploadTexture(const char* path, const Common::RGBA8Image& image); + static void TextureLoaderThread(); + + static void DrawFileSelector(); + static void DrawChoiceDialog(); + static void DrawInputDialog(); + static void DrawBackgroundProgressDialogs(ImVec2& position, float spacing); + static void DrawNotifications(ImVec2& position, float spacing); + static void DrawToast(); + static void GetMenuButtonFrameBounds(float height, ImVec2* pos, ImVec2* size); + static bool MenuButtonFrame(const char* str_id, bool enabled, float height, bool* visible, bool* hovered, ImRect* bb, + ImGuiButtonFlags flags = 0, float hover_alpha = 1.0f); + static void PopulateFileSelectorItems(); + static void SetFileSelectorDirectory(std::string dir); + static ImGuiID GetBackgroundProgressID(const char* str_id); + + ImFont* g_standard_font = nullptr; + ImFont* g_medium_font = nullptr; + ImFont* g_large_font = nullptr; + ImFont* g_icon_font = nullptr; + + float g_layout_scale = 1.0f; + float g_layout_padding_left = 0.0f; + float g_layout_padding_top = 0.0f; + + ImVec4 UIBackgroundColor; + ImVec4 UIBackgroundTextColor; + ImVec4 UIBackgroundLineColor; + ImVec4 UIBackgroundHighlightColor; + ImVec4 UIDisabledColor; + ImVec4 UIPrimaryColor; + ImVec4 UIPrimaryLightColor; + ImVec4 UIPrimaryDarkColor; + ImVec4 UIPrimaryTextColor; + ImVec4 UITextHighlightColor; + ImVec4 UIPrimaryLineColor; + ImVec4 UISecondaryColor; + ImVec4 UISecondaryLightColor; + ImVec4 UISecondaryDarkColor; + ImVec4 UISecondaryTextColor; + + static u32 s_menu_button_index = 0; + static u32 s_close_button_state = 0; + static bool s_focus_reset_queued = false; + + static LRUCache> s_texture_cache(128, true); + static std::shared_ptr s_placeholder_texture; + static std::atomic_bool s_texture_load_thread_quit{false}; + static std::mutex s_texture_load_mutex; + static std::condition_variable s_texture_load_cv; + static std::deque s_texture_load_queue; + static std::deque> s_texture_upload_queue; + static Threading::Thread s_texture_load_thread; + + static bool s_choice_dialog_open = false; + static bool s_choice_dialog_checkable = false; + static std::string s_choice_dialog_title; + static ChoiceDialogOptions s_choice_dialog_options; + static ChoiceDialogCallback s_choice_dialog_callback; + static ImGuiID s_enum_choice_button_id = 0; + static s32 s_enum_choice_button_value = 0; + static bool s_enum_choice_button_set = false; + + static bool s_input_dialog_open = false; + static std::string s_input_dialog_title; + static std::string s_input_dialog_message; + static std::string s_input_dialog_caption; + static std::string s_input_dialog_text; + static std::string s_input_dialog_ok_text; + static InputStringDialogCallback s_input_dialog_callback; + + struct FileSelectorItem + { + FileSelectorItem() = default; + FileSelectorItem(std::string display_name_, std::string full_path_, bool is_file_) + : display_name(std::move(display_name_)) + , full_path(std::move(full_path_)) + , is_file(is_file_) + { + } + FileSelectorItem(const FileSelectorItem&) = default; + FileSelectorItem(FileSelectorItem&&) = default; + ~FileSelectorItem() = default; + + FileSelectorItem& operator=(const FileSelectorItem&) = default; + FileSelectorItem& operator=(FileSelectorItem&&) = default; + + std::string display_name; + std::string full_path; + bool is_file; + }; + + static bool s_file_selector_open = false; + static bool s_file_selector_directory = false; + static std::string s_file_selector_title; + static ImGuiFullscreen::FileSelectorCallback s_file_selector_callback; + static std::string s_file_selector_current_directory; + static std::vector s_file_selector_filters; + static std::vector s_file_selector_items; + + struct Notification + { + std::string title; + std::string text; + std::string badge_path; + Common::Timer::Value start_time; + float duration; + }; + + static std::vector s_notifications; + + static std::string s_toast_title; + static std::string s_toast_message; + static Common::Timer::Value s_toast_start_time; + static float s_toast_duration; + + struct BackgroundProgressDialogData + { + std::string message; + ImGuiID id; + s32 min; + s32 max; + s32 value; + }; + + static std::vector s_background_progress_dialogs; + static std::mutex s_background_progress_lock; +} // namespace ImGuiFullscreen + +void ImGuiFullscreen::SetFonts(ImFont* standard_font, ImFont* medium_font, ImFont* large_font) +{ + g_standard_font = standard_font; + g_medium_font = medium_font; + g_large_font = large_font; +} + +bool ImGuiFullscreen::Initialize(const char* placeholder_image_path) +{ + s_focus_reset_queued = true; + s_close_button_state = 0; + + s_placeholder_texture = LoadTexture(placeholder_image_path); + if (!s_placeholder_texture) + { + Console.Error("Missing placeholder texture '%s', cannot continue", placeholder_image_path); + return false; + } + + s_texture_load_thread_quit.store(false, std::memory_order_release); + s_texture_load_thread.Start(TextureLoaderThread); + return true; +} + +void ImGuiFullscreen::Shutdown() +{ + if (s_texture_load_thread.Joinable()) + { + { + std::unique_lock lock(s_texture_load_mutex); + s_texture_load_thread_quit.store(true, std::memory_order_release); + s_texture_load_cv.notify_one(); + } + s_texture_load_thread.Join(); + } + + s_texture_upload_queue.clear(); + s_placeholder_texture.reset(); + g_standard_font = nullptr; + g_medium_font = nullptr; + g_large_font = nullptr; + + s_texture_cache.Clear(); + + s_notifications.clear(); + s_background_progress_dialogs.clear(); + CloseInputDialog(); + s_choice_dialog_open = false; + s_choice_dialog_checkable = false; + s_choice_dialog_title = {}; + s_choice_dialog_options.clear(); + s_choice_dialog_callback = {}; + s_enum_choice_button_id = 0; + s_enum_choice_button_value = 0; + s_enum_choice_button_set = false; + s_file_selector_open = false; + s_file_selector_directory = false; + s_file_selector_title = {}; + s_file_selector_callback = {}; + s_file_selector_current_directory = {}; + s_file_selector_filters.clear(); + s_file_selector_items.clear(); +} + +const std::shared_ptr& ImGuiFullscreen::GetPlaceholderTexture() +{ + return s_placeholder_texture; +} + +std::optional ImGuiFullscreen::LoadTextureImage(const char* path) +{ + std::optional image; + + std::optional> data; + if (Path::IsAbsolute(path)) + data = FileSystem::ReadBinaryFile(path); + else + data = Host::ReadResourceFile(path); + if (data.has_value()) + { + image = Common::RGBA8Image(); + if (!image->LoadFromBuffer(path, data->data(), data->size())) + { + Console.Error("Failed to read texture resource '%s'", path); + image.reset(); + } + } + else + { + Console.Error("Failed to open texture resource '%s'", path); + } + + return image; +} + +std::shared_ptr ImGuiFullscreen::UploadTexture(const char* path, const Common::RGBA8Image& image) +{ + std::unique_ptr texture = + Host::GetHostDisplay()->CreateTexture(image.GetWidth(), image.GetHeight(), image.GetPixels(), image.GetByteStride()); + if (!texture) + { + Console.Error("failed to create %ux%u texture for resource", image.GetWidth(), image.GetHeight()); + return {}; + } + + DevCon.WriteLn("Uploaded texture resource '%s' (%ux%u)", path, image.GetWidth(), image.GetHeight()); + return std::shared_ptr(std::move(texture)); +} + +std::shared_ptr ImGuiFullscreen::LoadTexture(const char* path) +{ + std::optional image(LoadTextureImage(path)); + if (image.has_value()) + { + std::shared_ptr ret(UploadTexture(path, image.value())); + if (ret) + return ret; + } + + return s_placeholder_texture; +} + +HostDisplayTexture* ImGuiFullscreen::GetCachedTexture(const char* name) +{ + std::shared_ptr* tex_ptr = s_texture_cache.Lookup(name); + if (!tex_ptr) + { + std::shared_ptr tex(LoadTexture(name)); + tex_ptr = s_texture_cache.Insert(name, std::move(tex)); + } + + return tex_ptr->get(); +} + +HostDisplayTexture* ImGuiFullscreen::GetCachedTextureAsync(const char* name) +{ + std::shared_ptr* tex_ptr = s_texture_cache.Lookup(name); + if (!tex_ptr) + { + // insert the placeholder + tex_ptr = s_texture_cache.Insert(name, s_placeholder_texture); + + // queue the actual load + std::unique_lock lock(s_texture_load_mutex); + s_texture_load_queue.emplace_back(name); + s_texture_load_cv.notify_one(); + } + + return tex_ptr->get(); +} + +bool ImGuiFullscreen::InvalidateCachedTexture(const std::string& path) +{ + return s_texture_cache.Remove(path); +} + +void ImGuiFullscreen::UploadAsyncTextures() +{ + std::unique_lock lock(s_texture_load_mutex); + while (!s_texture_upload_queue.empty()) + { + std::pair it(std::move(s_texture_upload_queue.front())); + s_texture_upload_queue.pop_front(); + lock.unlock(); + + std::shared_ptr tex = UploadTexture(it.first.c_str(), it.second); + if (tex) + s_texture_cache.Insert(std::move(it.first), std::move(tex)); + + lock.lock(); + } +} + +void ImGuiFullscreen::TextureLoaderThread() +{ + Threading::SetNameOfCurrentThread("ImGuiFullscreen Texture Loader"); + + std::unique_lock lock(s_texture_load_mutex); + + for (;;) + { + s_texture_load_cv.wait( + lock, []() { return (s_texture_load_thread_quit.load(std::memory_order_acquire) || !s_texture_load_queue.empty()); }); + + if (s_texture_load_thread_quit.load(std::memory_order_acquire)) + break; + + while (!s_texture_load_queue.empty()) + { + std::string path(std::move(s_texture_load_queue.front())); + s_texture_load_queue.pop_front(); + + lock.unlock(); + std::optional image(LoadTextureImage(path.c_str())); + lock.lock(); + + // don't bother queuing back if it doesn't exist + if (image) + s_texture_upload_queue.emplace_back(std::move(path), std::move(image.value())); + } + } + + s_texture_load_queue.clear(); +} + +bool ImGuiFullscreen::UpdateLayoutScale() +{ + static constexpr float LAYOUT_RATIO = LAYOUT_SCREEN_WIDTH / LAYOUT_SCREEN_HEIGHT; + const ImGuiIO& io = ImGui::GetIO(); + + const float screen_width = io.DisplaySize.x; + const float screen_height = io.DisplaySize.y; + const float screen_ratio = screen_width / screen_height; + const float old_scale = g_layout_scale; + + if (screen_ratio > LAYOUT_RATIO) + { + // screen is wider, use height, pad width + g_layout_scale = screen_height / LAYOUT_SCREEN_HEIGHT; + g_layout_padding_top = 0.0f; + g_layout_padding_left = (screen_width - (LAYOUT_SCREEN_WIDTH * g_layout_scale)) / 2.0f; + } + else + { + // screen is taller, use width, pad height + g_layout_scale = screen_width / LAYOUT_SCREEN_WIDTH; + g_layout_padding_top = (screen_height - (LAYOUT_SCREEN_HEIGHT * g_layout_scale)) / 2.0f; + g_layout_padding_left = 0.0f; + } + + return g_layout_scale != old_scale; +} + +ImRect ImGuiFullscreen::CenterImage(const ImVec2& fit_size, const ImVec2& image_size) +{ + const float fit_ar = fit_size.x / fit_size.y; + const float image_ar = image_size.x / image_size.y; + + ImRect ret; + if (fit_ar > image_ar) + { + // center horizontally + const float width = fit_size.y * image_ar; + const float offset = (fit_size.x - width) / 2.0f; + const float height = fit_size.y; + ret = ImRect(ImVec2(offset, 0.0f), ImVec2(offset + width, height)); + } + else + { + // center vertically + const float height = fit_size.x / image_ar; + const float offset = (fit_size.y - height) / 2.0f; + const float width = fit_size.x; + ret = ImRect(ImVec2(0.0f, offset), ImVec2(width, offset + height)); + } + + return ret; +} + +ImRect ImGuiFullscreen::CenterImage(const ImRect& fit_rect, const ImVec2& image_size) +{ + ImRect ret(CenterImage(fit_rect.Max - fit_rect.Min, image_size)); + ret.Translate(fit_rect.Min); + return ret; +} + +void ImGuiFullscreen::BeginLayout() +{ + // we evict from the texture cache at the start of the frame, in case we go over mid-frame, + // we need to keep all those textures alive until the end of the frame + s_texture_cache.ManualEvict(); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleColor(ImGuiCol_Text, UISecondaryTextColor); + ImGui::PushStyleColor(ImGuiCol_TextDisabled, UIDisabledColor); + ImGui::PushStyleColor(ImGuiCol_Button, UISecondaryColor); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, UIBackgroundColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UIBackgroundHighlightColor); + ImGui::PushStyleColor(ImGuiCol_Border, UIBackgroundLineColor); + ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, UIBackgroundColor); + ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, UIPrimaryColor); + ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, UIPrimaryLightColor); + ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, UIPrimaryDarkColor); +} + +void ImGuiFullscreen::EndLayout() +{ + DrawFileSelector(); + DrawChoiceDialog(); + DrawInputDialog(); + + const float notification_margin = LayoutScale(10.0f); + const float spacing = LayoutScale(10.0f); + const float notification_vertical_pos = GetNotificationVerticalPosition(); + ImVec2 position(notification_margin, notification_vertical_pos * ImGui::GetIO().DisplaySize.y + + ((notification_vertical_pos >= 0.5f) ? -notification_margin : notification_margin)); + DrawBackgroundProgressDialogs(position, spacing); + DrawNotifications(position, spacing); + DrawToast(); + + ImGui::PopStyleColor(10); + ImGui::PopStyleVar(2); +} + +void ImGuiFullscreen::QueueResetFocus() +{ + s_focus_reset_queued = true; + s_close_button_state = 0; +} + +bool ImGuiFullscreen::ResetFocusHere() +{ + if (!s_focus_reset_queued) + return false; + + s_focus_reset_queued = false; + ImGui::SetWindowFocus(); + + // only do the active selection magic when we're using keyboard/gamepad + return (GImGui->NavInputSource == ImGuiInputSource_Keyboard || GImGui->NavInputSource == ImGuiInputSource_Gamepad); +} + +bool ImGuiFullscreen::WantsToCloseMenu() +{ + // Wait for the Close button to be released, THEN pressed + if (s_close_button_state == 0) + { + if (ImGui::IsNavInputTest(ImGuiNavInput_Cancel, ImGuiNavReadMode_Pressed)) + s_close_button_state = 1; + } + else if (s_close_button_state == 1) + { + if (ImGui::IsNavInputTest(ImGuiNavInput_Cancel, ImGuiNavReadMode_Released)) + { + s_close_button_state = 2; + } + } + return s_close_button_state > 1; +} + +void ImGuiFullscreen::ResetCloseMenuIfNeeded() +{ + // If s_close_button_state reached the "Released" state, reset it after the tick + if (s_close_button_state > 1) + { + s_close_button_state = 0; + } +} + +void ImGuiFullscreen::PushPrimaryColor() +{ + ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor); + ImGui::PushStyleColor(ImGuiCol_Button, UIPrimaryDarkColor); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, UIPrimaryColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UIPrimaryLightColor); + ImGui::PushStyleColor(ImGuiCol_Border, UIPrimaryLightColor); +} + +void ImGuiFullscreen::PopPrimaryColor() +{ + ImGui::PopStyleColor(5); +} + +void ImGuiFullscreen::PushSecondaryColor() +{ + ImGui::PushStyleColor(ImGuiCol_Text, UISecondaryTextColor); + ImGui::PushStyleColor(ImGuiCol_Button, UISecondaryDarkColor); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, UISecondaryColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UISecondaryLightColor); + ImGui::PushStyleColor(ImGuiCol_Border, UISecondaryLightColor); +} + +void ImGuiFullscreen::PopSecondaryColor() +{ + ImGui::PopStyleColor(5); +} + +bool ImGuiFullscreen::BeginFullscreenColumns(const char* title) +{ + ImGui::SetNextWindowPos(ImVec2(g_layout_padding_left, 0.0f)); + ImGui::SetNextWindowSize(ImVec2(LayoutScale(LAYOUT_SCREEN_WIDTH), ImGui::GetIO().DisplaySize.y)); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + + bool clipped; + if (title) + { + ImGui::PushFont(g_large_font); + clipped = ImGui::Begin(title, nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize); + ImGui::PopFont(); + } + else + { + clipped = ImGui::Begin( + "fullscreen_ui_columns_parent", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize); + } + + return clipped; +} + +void ImGuiFullscreen::EndFullscreenColumns() +{ + ImGui::End(); + ImGui::PopStyleVar(3); +} + +bool ImGuiFullscreen::BeginFullscreenColumnWindow(float start, float end, const char* name, const ImVec4& background) +{ + const ImVec2 pos(LayoutScale(start), 0.0f); + const ImVec2 size(LayoutScale(end - start), ImGui::GetIO().DisplaySize.y); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, background); + + ImGui::SetCursorPos(pos); + + return ImGui::BeginChild(name, size, false, ImGuiWindowFlags_NavFlattened); +} + +void ImGuiFullscreen::EndFullscreenColumnWindow() +{ + ImGui::EndChild(); + ImGui::PopStyleColor(); +} + +bool ImGuiFullscreen::BeginFullscreenWindow(float left, float top, float width, float height, const char* name, + const ImVec4& background /* = HEX_TO_IMVEC4(0x212121, 0xFF) */, float rounding /*= 0.0f*/, float padding /*= 0.0f*/, + ImGuiWindowFlags flags /*= 0*/) +{ + if (left < 0.0f) + left = (LAYOUT_SCREEN_WIDTH - width) * -left; + if (top < 0.0f) + top = (LAYOUT_SCREEN_HEIGHT - height) * -top; + + const ImVec2 pos(ImVec2(LayoutScale(left) + g_layout_padding_left, LayoutScale(top) + g_layout_padding_top)); + const ImVec2 size(LayoutScale(ImVec2(width, height))); + return BeginFullscreenWindow(pos, size, name, background, rounding, padding, flags); +} + +bool ImGuiFullscreen::BeginFullscreenWindow(const ImVec2& position, const ImVec2& size, const char* name, + const ImVec4& background /* = HEX_TO_IMVEC4(0x212121, 0xFF) */, float rounding /*= 0.0f*/, float padding /*= 0.0f*/, + ImGuiWindowFlags flags /*= 0*/) +{ + ImGui::SetNextWindowPos(position); + ImGui::SetNextWindowSize(size); + + ImGui::PushStyleColor(ImGuiCol_WindowBg, background); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(padding, padding)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(rounding)); + + return ImGui::Begin(name, nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBringToFrontOnFocus | + flags); +} + +void ImGuiFullscreen::EndFullscreenWindow() +{ + ImGui::End(); + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(); +} + +void ImGuiFullscreen::BeginMenuButtons(u32 num_items, float y_align, float x_padding, float y_padding, float item_height) +{ + s_menu_button_index = 0; + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(x_padding, y_padding)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + + if (y_align != 0.0f) + { + const float total_size = + static_cast(num_items) * LayoutScale(item_height + (y_padding * 2.0f)) + LayoutScale(y_padding * 2.0f); + const float window_height = ImGui::GetWindowHeight(); + if (window_height > total_size) + ImGui::SetCursorPosY((window_height - total_size) * y_align); + } +} + +void ImGuiFullscreen::EndMenuButtons() +{ + ImGui::PopStyleVar(4); +} + +void ImGuiFullscreen::DrawWindowTitle(const char* title) +{ + ImGuiWindow* window = ImGui::GetCurrentWindow(); + const ImVec2 pos(window->DC.CursorPos + LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING, LAYOUT_MENU_BUTTON_Y_PADDING)); + const ImVec2 size(window->WorkRect.GetWidth() - (LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING) * 2.0f), + g_large_font->FontSize + LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f); + const ImRect rect(pos, pos + size); + + ImGui::ItemSize(size); + if (!ImGui::ItemAdd(rect, window->GetID("window_title"))) + return; + + ImGui::PushFont(g_large_font); + ImGui::RenderTextClipped(rect.Min, rect.Max, title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &rect); + ImGui::PopFont(); + + const ImVec2 line_start(pos.x, pos.y + g_large_font->FontSize + LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING)); + const ImVec2 line_end(pos.x + size.x, line_start.y); + const float line_thickness = LayoutScale(1.0f); + ImDrawList* dl = ImGui::GetWindowDrawList(); + dl->AddLine(line_start, line_end, IM_COL32(255, 255, 255, 255), line_thickness); +} + +void ImGuiFullscreen::GetMenuButtonFrameBounds(float height, ImVec2* pos, ImVec2* size) +{ + ImGuiWindow* window = ImGui::GetCurrentWindow(); + *pos = window->DC.CursorPos; + *size = ImVec2(window->WorkRect.GetWidth(), LayoutScale(height) + ImGui::GetStyle().FramePadding.y * 2.0f); +} + +bool ImGuiFullscreen::MenuButtonFrame( + const char* str_id, bool enabled, float height, bool* visible, bool* hovered, ImRect* bb, ImGuiButtonFlags flags, float hover_alpha) +{ + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + { + *visible = false; + *hovered = false; + return false; + } + + ImVec2 pos, size; + GetMenuButtonFrameBounds(height, &pos, &size); + *bb = ImRect(pos, pos + size); + + const ImGuiID id = window->GetID(str_id); + ImGui::ItemSize(size); + if (enabled) + { + if (!ImGui::ItemAdd(*bb, id)) + { + *visible = false; + *hovered = false; + return false; + } + } + else + { + if (ImGui::IsClippedEx(*bb, id)) + { + *visible = false; + *hovered = false; + return false; + } + } + + *visible = true; + + bool held; + bool pressed; + if (enabled) + { + pressed = ImGui::ButtonBehavior(*bb, id, hovered, &held, flags); + if (*hovered) + { + const ImU32 col = ImGui::GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered, hover_alpha); + + const float t = std::min(std::abs(std::sin(ImGui::GetTime() * 0.75) * 1.1), 1.0f); + ImGui::PushStyleColor(ImGuiCol_Border, ImGui::GetColorU32(ImGuiCol_Border, t)); + + ImGui::RenderFrame(bb->Min, bb->Max, col, true, 0.0f); + + ImGui::PopStyleColor(); + } + } + else + { + pressed = false; + held = false; + } + + const ImGuiStyle& style = ImGui::GetStyle(); + bb->Min += style.FramePadding; + bb->Max -= style.FramePadding; + + return pressed; +} + +bool ImGuiFullscreen::MenuButtonFrame(const char* str_id, bool enabled, float height, bool* visible, bool* hovered, ImVec2* min, + ImVec2* max, ImGuiButtonFlags flags /*= 0*/, float hover_alpha /*= 0*/) +{ + ImRect bb; + const bool result = MenuButtonFrame(str_id, enabled, height, visible, hovered, &bb, flags, hover_alpha); + *min = bb.Min; + *max = bb.Max; + return result; +} + +void ImGuiFullscreen::MenuHeading(const char* title, bool draw_line /*= true*/) +{ + const float line_thickness = draw_line ? LayoutScale(1.0f) : 0.0f; + const float line_padding = draw_line ? LayoutScale(5.0f) : 0.0f; + + bool visible, hovered; + ImRect bb; + MenuButtonFrame(title, false, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, &visible, &hovered, &bb); + if (!visible) + return; + + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled)); + ImGui::PushFont(g_large_font); + ImGui::RenderTextClipped(bb.Min, bb.Max, title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &bb); + ImGui::PopFont(); + ImGui::PopStyleColor(); + + if (draw_line) + { + const ImVec2 line_start(bb.Min.x, bb.Min.y + g_large_font->FontSize + line_padding); + const ImVec2 line_end(bb.Max.x, line_start.y); + ImGui::GetWindowDrawList()->AddLine(line_start, line_end, ImGui::GetColorU32(ImGuiCol_TextDisabled), line_thickness); + } +} + +bool ImGuiFullscreen::MenuHeadingButton( + const char* title, const char* value /*= nullptr*/, bool enabled /*= true*/, bool draw_line /*= true*/) +{ + const float line_thickness = draw_line ? LayoutScale(1.0f) : 0.0f; + const float line_padding = draw_line ? LayoutScale(5.0f) : 0.0f; + + ImRect bb; + bool visible, hovered; + bool pressed = MenuButtonFrame(title, enabled, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, &visible, &hovered, &bb); + if (!visible) + return false; + + if (!enabled) + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled)); + ImGui::PushFont(g_large_font); + ImGui::RenderTextClipped(bb.Min, bb.Max, title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &bb); + + if (value) + { + const ImVec2 value_size(g_large_font->CalcTextSizeA(g_large_font->FontSize, std::numeric_limits::max(), 0.0f, value)); + const ImRect value_bb(ImVec2(bb.Max.x - value_size.x, bb.Min.y), ImVec2(bb.Max.x, bb.Max.y)); + ImGui::RenderTextClipped(value_bb.Min, value_bb.Max, value, nullptr, nullptr, ImVec2(0.0f, 0.0f), &value_bb); + } + + ImGui::PopFont(); + if (!enabled) + ImGui::PopStyleColor(); + + if (draw_line) + { + const ImVec2 line_start(bb.Min.x, bb.Min.y + g_large_font->FontSize + line_padding); + const ImVec2 line_end(bb.Max.x, line_start.y); + ImGui::GetWindowDrawList()->AddLine(line_start, line_end, ImGui::GetColorU32(ImGuiCol_TextDisabled), line_thickness); + } + + return pressed; +} + +bool ImGuiFullscreen::ActiveButton(const char* title, bool is_active, bool enabled, float height, ImFont* font) +{ + if (is_active) + { + ImVec2 pos, size; + GetMenuButtonFrameBounds(height, &pos, &size); + ImGui::RenderFrame(pos, pos + size, ImGui::GetColorU32(UIPrimaryColor), false); + } + + ImRect bb; + bool visible, hovered; + bool pressed = MenuButtonFrame(title, enabled, height, &visible, &hovered, &bb); + if (!visible) + return false; + + const ImRect title_bb(bb.GetTL(), bb.GetBR()); + + if (!enabled) + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled)); + + ImGui::PushFont(font); + ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &title_bb); + ImGui::PopFont(); + + if (!enabled) + ImGui::PopStyleColor(); + + s_menu_button_index++; + return pressed; +} + +bool ImGuiFullscreen::MenuButton(const char* title, const char* summary, bool enabled, float height, ImFont* font, ImFont* summary_font) +{ + ImRect bb; + bool visible, hovered; + bool pressed = MenuButtonFrame(title, enabled, height, &visible, &hovered, &bb); + if (!visible) + return false; + + const float midpoint = bb.Min.y + font->FontSize + LayoutScale(4.0f); + const ImRect title_bb(bb.Min, ImVec2(bb.Max.x, midpoint)); + const ImRect summary_bb(ImVec2(bb.Min.x, midpoint), bb.Max); + + if (!enabled) + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled)); + + ImGui::PushFont(font); + ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &title_bb); + ImGui::PopFont(); + + if (summary) + { + ImGui::PushFont(summary_font); + ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, summary, nullptr, nullptr, ImVec2(0.0f, 0.0f), &summary_bb); + ImGui::PopFont(); + } + + if (!enabled) + ImGui::PopStyleColor(); + + s_menu_button_index++; + return pressed; +} + +bool ImGuiFullscreen::MenuImageButton(const char* title, const char* summary, ImTextureID user_texture_id, const ImVec2& image_size, + bool enabled, float height, const ImVec2& uv0, const ImVec2& uv1, ImFont* title_font, ImFont* summary_font) +{ + ImRect bb; + bool visible, hovered; + bool pressed = MenuButtonFrame(title, enabled, height, &visible, &hovered, &bb); + if (!visible) + return false; + + ImGui::GetWindowDrawList()->AddImage(user_texture_id, bb.Min, bb.Min + image_size, uv0, uv1, + enabled ? IM_COL32(255, 255, 255, 255) : ImGui::GetColorU32(ImGuiCol_TextDisabled)); + + const float midpoint = bb.Min.y + title_font->FontSize + LayoutScale(4.0f); + const float text_start_x = bb.Min.x + image_size.x + LayoutScale(15.0f); + const ImRect title_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint)); + const ImRect summary_bb(ImVec2(text_start_x, midpoint), bb.Max); + + if (!enabled) + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled)); + + ImGui::PushFont(title_font); + ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &title_bb); + ImGui::PopFont(); + + if (summary) + { + ImGui::PushFont(summary_font); + ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, summary, nullptr, nullptr, ImVec2(0.0f, 0.0f), &summary_bb); + ImGui::PopFont(); + } + + if (!enabled) + ImGui::PopStyleColor(); + + s_menu_button_index++; + return pressed; +} + +bool ImGuiFullscreen::FloatingButton(const char* text, float x, float y, float width, float height, float anchor_x, float anchor_y, + bool enabled, ImFont* font, ImVec2* out_position) +{ + const ImVec2 text_size(font->CalcTextSizeA(font->FontSize, std::numeric_limits::max(), 0.0f, text)); + const ImVec2& padding(ImGui::GetStyle().FramePadding); + if (width < 0.0f) + width = (padding.x * 2.0f) + text_size.x; + if (height < 0.0f) + height = (padding.y * 2.0f) + text_size.y; + + const ImVec2 window_size(ImGui::GetWindowSize()); + if (anchor_x == -1.0f) + x -= width; + else if (anchor_x == -0.5f) + x -= (width * 0.5f); + else if (anchor_x == 0.5f) + x = (window_size.x * 0.5f) - (width * 0.5f) - x; + else if (anchor_x == 1.0f) + x = window_size.x - width - x; + if (anchor_y == -1.0f) + y -= height; + else if (anchor_y == -0.5f) + y -= (height * 0.5f); + else if (anchor_y == 0.5f) + y = (window_size.y * 0.5f) - (height * 0.5f) - y; + else if (anchor_y == 1.0f) + y = window_size.y - height - y; + + if (out_position) + *out_position = ImVec2(x, y); + + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImVec2 base(ImGui::GetWindowPos() + ImVec2(x, y)); + ImRect bb(base, base + ImVec2(width, height)); + + const ImGuiID id = window->GetID(text); + if (enabled) + { + if (!ImGui::ItemAdd(bb, id)) + return false; + } + else + { + if (ImGui::IsClippedEx(bb, id)) + return false; + } + + bool hovered; + bool held; + bool pressed; + if (enabled) + { + pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, 0); + if (hovered) + { + const ImU32 col = ImGui::GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered, 1.0f); + ImGui::RenderFrame(bb.Min, bb.Max, col, true, 0.0f); + } + } + else + { + hovered = false; + pressed = false; + held = false; + } + + bb.Min += padding; + bb.Max -= padding; + + if (!enabled) + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled)); + + ImGui::PushFont(font); + ImGui::RenderTextClipped(bb.Min, bb.Max, text, nullptr, nullptr, ImVec2(0.0f, 0.0f), &bb); + ImGui::PopFont(); + + if (!enabled) + ImGui::PopStyleColor(); + + return pressed; +} + +bool ImGuiFullscreen::ToggleButton( + const char* title, const char* summary, bool* v, bool enabled, float height, ImFont* font, ImFont* summary_font) +{ + ImRect bb; + bool visible, hovered; + bool pressed = MenuButtonFrame(title, enabled, height, &visible, &hovered, &bb, ImGuiButtonFlags_PressedOnClick); + if (!visible) + return false; + + const float midpoint = bb.Min.y + font->FontSize + LayoutScale(4.0f); + const ImRect title_bb(bb.Min, ImVec2(bb.Max.x, midpoint)); + const ImRect summary_bb(ImVec2(bb.Min.x, midpoint), bb.Max); + + if (!enabled) + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled)); + + ImGui::PushFont(font); + ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &title_bb); + ImGui::PopFont(); + + if (summary) + { + ImGui::PushFont(summary_font); + ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, summary, nullptr, nullptr, ImVec2(0.0f, 0.0f), &summary_bb); + ImGui::PopFont(); + } + + if (!enabled) + ImGui::PopStyleColor(); + + const float toggle_width = LayoutScale(50.0f); + const float toggle_height = LayoutScale(25.0f); + const float toggle_x = LayoutScale(8.0f); + const float toggle_y = (LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT) - toggle_height) * 0.5f; + const float toggle_radius = toggle_height * 0.5f; + const ImVec2 toggle_pos(bb.Max.x - toggle_width - toggle_x, bb.Min.y + toggle_y); + + if (pressed) + *v = !*v; + + float t = *v ? 1.0f : 0.0f; + ImDrawList* dl = ImGui::GetWindowDrawList(); + ImGuiContext& g = *GImGui; + if (g.LastActiveId == g.CurrentWindow->GetID(title)) + { + static constexpr const float ANIM_SPEED = 0.08f; + float t_anim = ImSaturate(g.LastActiveIdTimer / ANIM_SPEED); + t = *v ? (t_anim) : (1.0f - t_anim); + } + + ImU32 col_bg; + ImU32 col_knob; + if (!enabled) + { + col_bg = ImGui::GetColorU32(UIDisabledColor); + col_knob = IM_COL32(200, 200, 200, 200); + } + else + { + col_bg = ImGui::GetColorU32(ImLerp(HEX_TO_IMVEC4(0x8C8C8C, 0xff), UISecondaryLightColor, t)); + col_knob = IM_COL32(255, 255, 255, 255); + } + + dl->AddRectFilled(toggle_pos, ImVec2(toggle_pos.x + toggle_width, toggle_pos.y + toggle_height), col_bg, toggle_height * 0.5f); + dl->AddCircleFilled(ImVec2(toggle_pos.x + toggle_radius + t * (toggle_width - toggle_radius * 2.0f), toggle_pos.y + toggle_radius), + toggle_radius - 1.5f, col_knob, 32); + + s_menu_button_index++; + return pressed; +} + +bool ImGuiFullscreen::ThreeWayToggleButton( + const char* title, const char* summary, std::optional* v, bool enabled, float height, ImFont* font, ImFont* summary_font) +{ + ImRect bb; + bool visible, hovered; + bool pressed = MenuButtonFrame(title, enabled, height, &visible, &hovered, &bb, ImGuiButtonFlags_PressedOnClick); + if (!visible) + return false; + + const float midpoint = bb.Min.y + font->FontSize + LayoutScale(4.0f); + const ImRect title_bb(bb.Min, ImVec2(bb.Max.x, midpoint)); + const ImRect summary_bb(ImVec2(bb.Min.x, midpoint), bb.Max); + + if (!enabled) + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled)); + + ImGui::PushFont(font); + ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &title_bb); + ImGui::PopFont(); + + if (summary) + { + ImGui::PushFont(summary_font); + ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, summary, nullptr, nullptr, ImVec2(0.0f, 0.0f), &summary_bb); + ImGui::PopFont(); + } + + if (!enabled) + ImGui::PopStyleColor(); + + const float toggle_width = LayoutScale(50.0f); + const float toggle_height = LayoutScale(25.0f); + const float toggle_x = LayoutScale(8.0f); + const float toggle_y = (LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT) - toggle_height) * 0.5f; + const float toggle_radius = toggle_height * 0.5f; + const ImVec2 toggle_pos(bb.Max.x - toggle_width - toggle_x, bb.Min.y + toggle_y); + + if (pressed) + { + if (v->has_value() && v->value()) + *v = false; + else if (v->has_value() && !v->value()) + v->reset(); + else + *v = true; + } + + float t = v->has_value() ? (v->value() ? 1.0f : 0.0f) : 0.5f; + ImDrawList* dl = ImGui::GetWindowDrawList(); + ImGuiContext& g = *GImGui; + if (g.LastActiveId == g.CurrentWindow->GetID(title)) + { + static constexpr const float ANIM_SPEED = 0.08f; + float t_anim = ImSaturate(g.LastActiveIdTimer / ANIM_SPEED); + t = (v->has_value() ? (v->value() ? std::min(t_anim + 0.5f, 1.0f) : (1.0f - t_anim)) : (t_anim * 0.5f)); + } + + const float color_t = v->has_value() ? t : 0.0f; + + ImU32 col_bg; + if (!enabled) + col_bg = IM_COL32(0x75, 0x75, 0x75, 0xff); + else if (hovered) + col_bg = ImGui::GetColorU32( + ImLerp(v->has_value() ? HEX_TO_IMVEC4(0xf05100, 0xff) : HEX_TO_IMVEC4(0x9e9e9e, 0xff), UISecondaryLightColor, color_t)); + else + col_bg = ImGui::GetColorU32( + ImLerp(v->has_value() ? HEX_TO_IMVEC4(0xc45100, 0xff) : HEX_TO_IMVEC4(0x757575, 0xff), UISecondaryLightColor, color_t)); + + dl->AddRectFilled(toggle_pos, ImVec2(toggle_pos.x + toggle_width, toggle_pos.y + toggle_height), col_bg, toggle_height * 0.5f); + dl->AddCircleFilled(ImVec2(toggle_pos.x + toggle_radius + t * (toggle_width - toggle_radius * 2.0f), toggle_pos.y + toggle_radius), + toggle_radius - 1.5f, IM_COL32(255, 255, 255, 255), 32); + + s_menu_button_index++; + return pressed; +} + +bool ImGuiFullscreen::RangeButton(const char* title, const char* summary, s32* value, s32 min, s32 max, s32 increment, const char* format, + bool enabled /*= true*/, float height /*= LAYOUT_MENU_BUTTON_HEIGHT*/, ImFont* font /*= g_large_font*/, + ImFont* summary_font /*= g_medium_font*/) +{ + ImRect bb; + bool visible, hovered; + bool pressed = MenuButtonFrame(title, enabled, height, &visible, &hovered, &bb); + if (!visible) + return false; + + const std::string value_text(StringUtil::StdStringFromFormat(format, *value)); + const ImVec2 value_size(ImGui::CalcTextSize(value_text.c_str())); + + const float midpoint = bb.Min.y + font->FontSize + LayoutScale(4.0f); + const float text_end = bb.Max.x - value_size.x; + const ImRect title_bb(bb.Min, ImVec2(text_end, midpoint)); + const ImRect summary_bb(ImVec2(bb.Min.x, midpoint), ImVec2(text_end, bb.Max.y)); + + if (!enabled) + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled)); + + ImGui::PushFont(font); + ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &title_bb); + ImGui::RenderTextClipped(bb.Min, bb.Max, value_text.c_str(), nullptr, nullptr, ImVec2(1.0f, 0.5f), &bb); + ImGui::PopFont(); + + if (summary) + { + ImGui::PushFont(summary_font); + ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, summary, nullptr, nullptr, ImVec2(0.0f, 0.0f), &summary_bb); + ImGui::PopFont(); + } + + if (!enabled) + ImGui::PopStyleColor(); + + if (pressed) + ImGui::OpenPopup(title); + + bool changed = false; + + ImGui::SetNextWindowSize(LayoutScale(500.0f, 180.0f)); + + ImGui::PushFont(g_large_font); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, + LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); + + if (ImGui::BeginPopupModal(title, nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) + { + ImGui::SetNextItemWidth(LayoutScale(450.0f)); + changed = ImGui::SliderInt("##value", value, min, max, format, ImGuiSliderFlags_NoInput); + + BeginMenuButtons(); + if (MenuButton("OK", nullptr, true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) + ImGui::CloseCurrentPopup(); + EndMenuButtons(); + + ImGui::EndPopup(); + } + + ImGui::PopStyleVar(3); + ImGui::PopFont(); + + return changed; +} + +bool ImGuiFullscreen::RangeButton(const char* title, const char* summary, float* value, float min, float max, float increment, + const char* format, bool enabled /*= true*/, float height /*= LAYOUT_MENU_BUTTON_HEIGHT*/, ImFont* font /*= g_large_font*/, + ImFont* summary_font /*= g_medium_font*/) +{ + ImRect bb; + bool visible, hovered; + bool pressed = MenuButtonFrame(title, enabled, height, &visible, &hovered, &bb); + if (!visible) + return false; + + const std::string value_text(StringUtil::StdStringFromFormat(format, *value)); + const ImVec2 value_size(ImGui::CalcTextSize(value_text.c_str())); + + const float midpoint = bb.Min.y + font->FontSize + LayoutScale(4.0f); + const float text_end = bb.Max.x - value_size.x; + const ImRect title_bb(bb.Min, ImVec2(text_end, midpoint)); + const ImRect summary_bb(ImVec2(bb.Min.x, midpoint), ImVec2(text_end, bb.Max.y)); + + if (!enabled) + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled)); + + ImGui::PushFont(font); + ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &title_bb); + ImGui::RenderTextClipped(bb.Min, bb.Max, value_text.c_str(), nullptr, nullptr, ImVec2(1.0f, 0.5f), &bb); + ImGui::PopFont(); + + if (summary) + { + ImGui::PushFont(summary_font); + ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, summary, nullptr, nullptr, ImVec2(0.0f, 0.0f), &summary_bb); + ImGui::PopFont(); + } + + if (!enabled) + ImGui::PopStyleColor(); + + if (pressed) + ImGui::OpenPopup(title); + + bool changed = false; + + ImGui::SetNextWindowSize(LayoutScale(500.0f, 180.0f)); + + ImGui::PushFont(g_large_font); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, + LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING, ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); + + if (ImGui::BeginPopupModal(title, nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) + { + ImGui::SetNextItemWidth(LayoutScale(450.0f)); + changed = ImGui::SliderFloat("##value", value, min, max, format, ImGuiSliderFlags_NoInput); + + BeginMenuButtons(); + if (MenuButton("OK", nullptr, true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) + ImGui::CloseCurrentPopup(); + EndMenuButtons(); + + ImGui::EndPopup(); + } + + ImGui::PopStyleVar(3); + ImGui::PopFont(); + + return changed; +} + +bool ImGuiFullscreen::MenuButtonWithValue( + const char* title, const char* summary, const char* value, bool enabled, float height, ImFont* font, ImFont* summary_font) +{ + ImRect bb; + bool visible, hovered; + bool pressed = MenuButtonFrame(title, enabled, height, &visible, &hovered, &bb); + if (!visible) + return false; + + const ImVec2 value_size(ImGui::CalcTextSize(value)); + + const float midpoint = bb.Min.y + font->FontSize + LayoutScale(4.0f); + const float text_end = bb.Max.x - value_size.x; + const ImRect title_bb(bb.Min, ImVec2(text_end, midpoint)); + const ImRect summary_bb(ImVec2(bb.Min.x, midpoint), ImVec2(text_end, bb.Max.y)); + + if (!enabled) + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_TextDisabled)); + + ImGui::PushFont(font); + ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &title_bb); + ImGui::RenderTextClipped(bb.Min, bb.Max, value, nullptr, nullptr, ImVec2(1.0f, 0.5f), &bb); + ImGui::PopFont(); + + if (summary) + { + ImGui::PushFont(summary_font); + ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, summary, nullptr, nullptr, ImVec2(0.0f, 0.0f), &summary_bb); + ImGui::PopFont(); + } + + if (!enabled) + ImGui::PopStyleColor(); + + return pressed; +} + +bool ImGuiFullscreen::EnumChoiceButtonImpl(const char* title, const char* summary, s32* value_pointer, + const char* (*to_display_name_function)(s32 value, void* opaque), void* opaque, u32 count, bool enabled, float height, ImFont* font, + ImFont* summary_font) +{ + const bool pressed = + MenuButtonWithValue(title, summary, to_display_name_function(*value_pointer, opaque), enabled, height, font, summary_font); + + if (pressed) + { + s_enum_choice_button_id = ImGui::GetID(title); + s_enum_choice_button_value = *value_pointer; + s_enum_choice_button_set = false; + + ChoiceDialogOptions options; + options.reserve(count); + for (u32 i = 0; i < count; i++) + options.emplace_back(to_display_name_function(static_cast(i), opaque), static_cast(*value_pointer) == i); + OpenChoiceDialog(title, false, std::move(options), [](s32 index, const std::string& title, bool checked) { + if (index >= 0) + s_enum_choice_button_value = index; + + s_enum_choice_button_set = true; + CloseChoiceDialog(); + }); + } + + bool changed = false; + if (s_enum_choice_button_set && s_enum_choice_button_id == ImGui::GetID(title)) + { + changed = s_enum_choice_button_value != *value_pointer; + if (changed) + *value_pointer = s_enum_choice_button_value; + + s_enum_choice_button_id = 0; + s_enum_choice_button_value = 0; + s_enum_choice_button_set = false; + } + + return changed; +} + +void ImGuiFullscreen::BeginNavBar(float x_padding /*= LAYOUT_MENU_BUTTON_X_PADDING*/, float y_padding /*= LAYOUT_MENU_BUTTON_Y_PADDING*/) +{ + s_menu_button_index = 0; + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(x_padding, y_padding)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, LayoutScale(1.0f, 1.0f)); + PushPrimaryColor(); +} + +void ImGuiFullscreen::EndNavBar() +{ + PopPrimaryColor(); + ImGui::PopStyleVar(4); +} + +void ImGuiFullscreen::NavTitle(const char* title, float height /*= LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY*/, ImFont* font /*= g_large_font*/) +{ + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return; + + s_menu_button_index++; + + const ImVec2 text_size(font->CalcTextSizeA(font->FontSize, std::numeric_limits::max(), 0.0f, title)); + const ImVec2 pos(window->DC.CursorPos); + const ImGuiStyle& style = ImGui::GetStyle(); + const ImVec2 size = ImVec2(text_size.x, LayoutScale(height) + style.FramePadding.y * 2.0f); + + ImGui::ItemSize(ImVec2(size.x + style.FrameBorderSize + style.ItemSpacing.x, size.y + style.FrameBorderSize + style.ItemSpacing.y)); + ImGui::SameLine(); + + ImRect bb(pos, pos + size); + if (ImGui::IsClippedEx(bb, 0)) + return; + + bb.Min.y += style.FramePadding.y; + bb.Max.y -= style.FramePadding.y; + + ImGui::PushFont(font); + ImGui::RenderTextClipped(bb.Min, bb.Max, title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &bb); + ImGui::PopFont(); +} + +void ImGuiFullscreen::RightAlignNavButtons(u32 num_items /*= 0*/, float item_width /*= LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY*/, + float item_height /*= LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY*/) +{ + ImGuiWindow* window = ImGui::GetCurrentWindow(); + const ImGuiStyle& style = ImGui::GetStyle(); + + const float total_item_width = style.FramePadding.x * 2.0f + style.FrameBorderSize + style.ItemSpacing.x + LayoutScale(item_width); + const float margin = total_item_width * static_cast(num_items); + ImGui::SetCursorPosX(window->InnerClipRect.Max.x - margin - style.FramePadding.x); +} + +bool ImGuiFullscreen::NavButton(const char* title, bool is_active, bool enabled /* = true */, float width /* = -1.0f */, + float height /* = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY */, ImFont* font /* = g_large_font */) +{ + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + s_menu_button_index++; + + const ImVec2 text_size(font->CalcTextSizeA(font->FontSize, std::numeric_limits::max(), 0.0f, title)); + const ImVec2 pos(window->DC.CursorPos); + const ImGuiStyle& style = ImGui::GetStyle(); + const ImVec2 size = ImVec2(((width < 0.0f) ? text_size.x : LayoutScale(width)) + style.FramePadding.x * 2.0f, + LayoutScale(height) + style.FramePadding.y * 2.0f); + + ImGui::ItemSize(ImVec2(size.x + style.FrameBorderSize + style.ItemSpacing.x, size.y + style.FrameBorderSize + style.ItemSpacing.y)); + ImGui::SameLine(); + + ImRect bb(pos, pos + size); + const ImGuiID id = window->GetID(title); + if (enabled) + { + if (!ImGui::ItemAdd(bb, id)) + return false; + } + else + { + if (ImGui::IsClippedEx(bb, id)) + return false; + } + + bool held; + bool pressed; + bool hovered; + if (enabled) + { + pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, 0); + if (hovered) + { + const ImU32 col = ImGui::GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered, 1.0f); + ImGui::RenderFrame(bb.Min, bb.Max, col, true, 0.0f); + } + } + else + { + pressed = false; + held = false; + hovered = false; + } + + bb.Min += style.FramePadding; + bb.Max -= style.FramePadding; + + ImGui::PushStyleColor( + ImGuiCol_Text, ImGui::GetColorU32(enabled ? (is_active ? ImGuiCol_Text : ImGuiCol_TextDisabled) : ImGuiCol_ButtonHovered)); + + ImGui::PushFont(font); + ImGui::RenderTextClipped(bb.Min, bb.Max, title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &bb); + ImGui::PopFont(); + + ImGui::PopStyleColor(); + + return pressed; +} + +void ImGuiFullscreen::PopulateFileSelectorItems() +{ + s_file_selector_items.clear(); + + if (s_file_selector_current_directory.empty()) + { + for (std::string& root_path : FileSystem::GetRootDirectoryList()) + { + s_file_selector_items.emplace_back( + StringUtil::StdStringFromFormat(ICON_FA_FOLDER " %s", root_path.c_str()), std::move(root_path), false); + } + } + else + { + FileSystem::FindResultsArray results; + FileSystem::FindFiles(s_file_selector_current_directory.c_str(), "*", + FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_FOLDERS | FILESYSTEM_FIND_HIDDEN_FILES | FILESYSTEM_FIND_RELATIVE_PATHS, &results); + + std::string parent_path; + std::string::size_type sep_pos = s_file_selector_current_directory.rfind(FS_OSPATH_SEPARATOR_CHARACTER); + if (sep_pos != std::string::npos) + { + parent_path = s_file_selector_current_directory.substr(0, sep_pos); + //FIXME FileSystem::CanonicalizePath(parent_path, true); + } + + s_file_selector_items.emplace_back(ICON_FA_FOLDER_OPEN " ", std::move(parent_path), false); + std::sort(results.begin(), results.end(), [](const FILESYSTEM_FIND_DATA& lhs, const FILESYSTEM_FIND_DATA& rhs) { + if ((lhs.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) != (rhs.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY)) + return (lhs.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) != 0; + + // return std::lexicographical_compare(lhs.FileName.begin(), lhs.FileName.end(), rhs.FileName.begin(), + // rhs.FileName.end()); + return (StringUtil::Strcasecmp(lhs.FileName.c_str(), rhs.FileName.c_str()) < 0); + }); + + for (const FILESYSTEM_FIND_DATA& fd : results) + { + std::string full_path(StringUtil::StdStringFromFormat( + "%s" FS_OSPATH_SEPARATOR_STR "%s", s_file_selector_current_directory.c_str(), fd.FileName.c_str())); + + if (fd.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) + { + std::string title(StringUtil::StdStringFromFormat(ICON_FA_FOLDER " %s", fd.FileName.c_str())); + s_file_selector_items.emplace_back(std::move(title), std::move(full_path), false); + } + else + { + if (s_file_selector_filters.empty() || + std::none_of(s_file_selector_filters.begin(), s_file_selector_filters.end(), + [&fd](const std::string& filter) { return StringUtil::WildcardMatch(fd.FileName.c_str(), filter.c_str()); })) + { + continue; + } + + std::string title(StringUtil::StdStringFromFormat(ICON_FA_FILE " %s", fd.FileName.c_str())); + s_file_selector_items.emplace_back(std::move(title), std::move(full_path), true); + } + } + } +} + +void ImGuiFullscreen::SetFileSelectorDirectory(std::string dir) +{ + while (!dir.empty() && dir.back() == FS_OSPATH_SEPARATOR_CHARACTER) + dir.erase(dir.size() - 1); + + s_file_selector_current_directory = std::move(dir); + PopulateFileSelectorItems(); +} + +bool ImGuiFullscreen::IsFileSelectorOpen() +{ + return s_file_selector_open; +} + +void ImGuiFullscreen::OpenFileSelector( + const char* title, bool select_directory, FileSelectorCallback callback, FileSelectorFilters filters, std::string initial_directory) +{ + if (s_file_selector_open) + CloseFileSelector(); + + s_file_selector_open = true; + s_file_selector_directory = select_directory; + s_file_selector_title = StringUtil::StdStringFromFormat("%s##file_selector", title); + s_file_selector_callback = std::move(callback); + s_file_selector_filters = std::move(filters); + + if (initial_directory.empty() || !FileSystem::DirectoryExists(initial_directory.c_str())) + initial_directory = FileSystem::GetWorkingDirectory(); + SetFileSelectorDirectory(std::move(initial_directory)); +} + +void ImGuiFullscreen::CloseFileSelector() +{ + if (!s_file_selector_open) + return; + + s_file_selector_open = false; + s_file_selector_directory = false; + std::string().swap(s_file_selector_title); + FileSelectorCallback().swap(s_file_selector_callback); + FileSelectorFilters().swap(s_file_selector_filters); + std::string().swap(s_file_selector_current_directory); + s_file_selector_items.clear(); + ImGui::CloseCurrentPopup(); + QueueResetFocus(); +} + +void ImGuiFullscreen::DrawFileSelector() +{ + if (!s_file_selector_open) + return; + + ImGui::SetNextWindowSize(LayoutScale(1000.0f, 680.0f)); + ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + ImGui::OpenPopup(s_file_selector_title.c_str()); + + FileSelectorItem* selected = nullptr; + + ImGui::PushFont(g_large_font); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING, LAYOUT_MENU_BUTTON_Y_PADDING)); + ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor); + ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor); + ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor); + ImGui::PushStyleColor(ImGuiCol_PopupBg, UIBackgroundColor); + + bool is_open = !WantsToCloseMenu(); + bool directory_selected = false; + if (ImGui::BeginPopupModal( + s_file_selector_title.c_str(), &is_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) + { + ImGui::PushStyleColor(ImGuiCol_Text, UIBackgroundTextColor); + + BeginMenuButtons(); + + if (!s_file_selector_current_directory.empty()) + { + MenuButton(fmt::format(ICON_FA_FOLDER_OPEN " {}", s_file_selector_current_directory).c_str(), nullptr, false, + LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); + } + + if (s_file_selector_directory && !s_file_selector_current_directory.empty()) + { + if (MenuButton(ICON_FA_FOLDER_PLUS " ", nullptr, true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) + directory_selected = true; + } + + for (FileSelectorItem& item : s_file_selector_items) + { + if (MenuButton(item.display_name.c_str(), nullptr, true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) + selected = &item; + } + + EndMenuButtons(); + + ImGui::PopStyleColor(1); + + ImGui::EndPopup(); + } + else + { + is_open = false; + } + + ImGui::PopStyleColor(4); + ImGui::PopStyleVar(2); + ImGui::PopFont(); + + if (selected) + { + if (selected->is_file) + { + s_file_selector_callback(selected->full_path); + } + else + { + SetFileSelectorDirectory(std::move(selected->full_path)); + } + } + else if (directory_selected) + { + s_file_selector_callback(s_file_selector_current_directory); + } + else if (!is_open) + { + std::string no_path; + s_file_selector_callback(no_path); + CloseFileSelector(); + } +} + +bool ImGuiFullscreen::IsChoiceDialogOpen() +{ + return s_choice_dialog_open; +} + +void ImGuiFullscreen::OpenChoiceDialog(const char* title, bool checkable, ChoiceDialogOptions options, ChoiceDialogCallback callback) +{ + if (s_choice_dialog_open) + CloseChoiceDialog(); + + s_choice_dialog_open = true; + s_choice_dialog_checkable = checkable; + s_choice_dialog_title = StringUtil::StdStringFromFormat("%s##choice_dialog", title); + s_choice_dialog_options = std::move(options); + s_choice_dialog_callback = std::move(callback); +} + +void ImGuiFullscreen::CloseChoiceDialog() +{ + if (!s_choice_dialog_open) + return; + + s_choice_dialog_open = false; + s_choice_dialog_checkable = false; + std::string().swap(s_choice_dialog_title); + ChoiceDialogOptions().swap(s_choice_dialog_options); + ChoiceDialogCallback().swap(s_choice_dialog_callback); + QueueResetFocus(); +} + +void ImGuiFullscreen::DrawChoiceDialog() +{ + if (!s_choice_dialog_open) + return; + + const float width = 600.0f; + const float title_height = g_large_font->FontSize + ImGui::GetStyle().FramePadding.y * 2.0f + ImGui::GetStyle().WindowPadding.y * 2.0f; + const float height = std::min(400.0f, title_height + (LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY + (LAYOUT_MENU_BUTTON_Y_PADDING * 2.0f)) * + static_cast(s_choice_dialog_options.size())); + ImGui::SetNextWindowSize(LayoutScale(width, height)); + ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + ImGui::OpenPopup(s_choice_dialog_title.c_str()); + + ImGui::PushFont(g_large_font); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING, LAYOUT_MENU_BUTTON_Y_PADDING)); + ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor); + ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor); + ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor); + ImGui::PushStyleColor(ImGuiCol_PopupBg, UIBackgroundColor); + + bool is_open = !WantsToCloseMenu(); + s32 choice = -1; + + if (ImGui::BeginPopupModal( + s_choice_dialog_title.c_str(), &is_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) + { + ImGui::PushStyleColor(ImGuiCol_Text, UIBackgroundTextColor); + + BeginMenuButtons(); + + if (s_choice_dialog_checkable) + { + for (s32 i = 0; i < static_cast(s_choice_dialog_options.size()); i++) + { + auto& option = s_choice_dialog_options[i]; + + const std::string title(fmt::format("{0} {1}", option.second ? ICON_FA_CHECK_SQUARE : ICON_FA_SQUARE, option.first)); + if (MenuButton(title.c_str(), nullptr, true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) + { + choice = i; + option.second = !option.second; + } + } + } + else + { + for (s32 i = 0; i < static_cast(s_choice_dialog_options.size()); i++) + { + auto& option = s_choice_dialog_options[i]; + std::string title; + if (option.second) + title += ICON_FA_CHECK " "; + title += option.first; + + if (ActiveButton(title.c_str(), option.second, true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY)) + { + choice = i; + for (s32 j = 0; j < static_cast(s_choice_dialog_options.size()); j++) + s_choice_dialog_options[j].second = (j == i); + } + } + } + + EndMenuButtons(); + + ImGui::PopStyleColor(1); + + ImGui::EndPopup(); + } + else + { + is_open = false; + } + + ImGui::PopStyleColor(4); + ImGui::PopStyleVar(2); + ImGui::PopFont(); + + if (choice >= 0) + { + const auto& option = s_choice_dialog_options[choice]; + s_choice_dialog_callback(choice, option.first, option.second); + } + else if (!is_open) + { + std::string no_string; + s_choice_dialog_callback(-1, no_string, false); + CloseChoiceDialog(); + } +} + + +bool ImGuiFullscreen::IsInputDialogOpen() +{ + return s_input_dialog_open; +} + +void ImGuiFullscreen::OpenInputStringDialog( + std::string title, std::string message, std::string caption, std::string ok_button_text, InputStringDialogCallback callback) +{ + s_input_dialog_open = true; + s_input_dialog_title = std::move(title); + s_input_dialog_message = std::move(message); + s_input_dialog_caption = std::move(caption); + s_input_dialog_ok_text = std::move(ok_button_text); + s_input_dialog_callback = std::move(callback); +} + +void ImGuiFullscreen::DrawInputDialog() +{ + if (!s_input_dialog_open) + return; + + ImGui::SetNextWindowSize(LayoutScale(700.0f, 0.0f)); + ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + ImGui::OpenPopup(s_input_dialog_title.c_str()); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(20.0f, 20.0f)); + ImGui::PushFont(g_large_font); + + bool is_open = true; + if (ImGui::BeginPopupModal(s_input_dialog_title.c_str(), &is_open, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) + { + ImGui::TextWrapped("%s", s_input_dialog_message.c_str()); + ImGui::NewLine(); + + if (!s_input_dialog_caption.empty()) + ImGui::TextUnformatted(s_input_dialog_caption.c_str()); + ImGui::InputText("##input", &s_input_dialog_text); + + ImGui::NewLine(); + + BeginMenuButtons(); + + const bool ok_enabled = !s_input_dialog_text.empty(); + + if (ActiveButton(s_input_dialog_ok_text.c_str(), false, ok_enabled) && ok_enabled) + { + // have to move out in case they open another dialog in the callback + InputStringDialogCallback cb(std::move(s_input_dialog_callback)); + std::string text(std::move(s_input_dialog_text)); + CloseInputDialog(); + ImGui::CloseCurrentPopup(); + cb(std::move(text)); + } + + if (ActiveButton(ICON_FA_TIMES " Cancel", false)) + { + CloseInputDialog(); + + ImGui::CloseCurrentPopup(); + } + + EndMenuButtons(); + + ImGui::EndPopup(); + } + if (!is_open) + CloseInputDialog(); + + ImGui::PopFont(); + ImGui::PopStyleVar(2); +} + +void ImGuiFullscreen::CloseInputDialog() +{ + if (!s_input_dialog_open) + return; + + s_input_dialog_open = false; + s_input_dialog_title = {}; + s_input_dialog_message = {}; + s_input_dialog_caption = {}; + s_input_dialog_ok_text = {}; + s_input_dialog_text = {}; + s_input_dialog_callback = {}; +} + +static float s_notification_vertical_position = 0.3f; +static float s_notification_vertical_direction = -1.0f; + +float ImGuiFullscreen::GetNotificationVerticalPosition() +{ + return s_notification_vertical_position; +} + +float ImGuiFullscreen::GetNotificationVerticalDirection() +{ + return s_notification_vertical_direction; +} + +void ImGuiFullscreen::SetNotificationVerticalPosition(float position, float direction) +{ + s_notification_vertical_position = position; + s_notification_vertical_direction = direction; +} + +ImGuiID ImGuiFullscreen::GetBackgroundProgressID(const char* str_id) +{ + return ImHashStr(str_id); +} + +void ImGuiFullscreen::OpenBackgroundProgressDialog(const char* str_id, std::string message, s32 min, s32 max, s32 value) +{ + const ImGuiID id = GetBackgroundProgressID(str_id); + + std::unique_lock lock(s_background_progress_lock); + + for (const BackgroundProgressDialogData& data : s_background_progress_dialogs) + { + pxAssert(data.id != id); + } + + BackgroundProgressDialogData data; + data.id = id; + data.message = std::move(message); + data.min = min; + data.max = max; + data.value = value; + s_background_progress_dialogs.push_back(std::move(data)); +} + +void ImGuiFullscreen::UpdateBackgroundProgressDialog(const char* str_id, std::string message, s32 min, s32 max, s32 value) +{ + const ImGuiID id = GetBackgroundProgressID(str_id); + + std::unique_lock lock(s_background_progress_lock); + + for (BackgroundProgressDialogData& data : s_background_progress_dialogs) + { + if (data.id == id) + { + data.message = std::move(message); + data.min = min; + data.max = max; + data.value = value; + return; + } + } + + pxFailRel("Updating unknown progress entry."); +} + +void ImGuiFullscreen::CloseBackgroundProgressDialog(const char* str_id) +{ + const ImGuiID id = GetBackgroundProgressID(str_id); + + std::unique_lock lock(s_background_progress_lock); + + for (auto it = s_background_progress_dialogs.begin(); it != s_background_progress_dialogs.end(); ++it) + { + if (it->id == id) + { + s_background_progress_dialogs.erase(it); + return; + } + } + + pxFailRel("Closing unknown progress entry."); +} + +void ImGuiFullscreen::DrawBackgroundProgressDialogs(ImVec2& position, float spacing) +{ + std::unique_lock lock(s_background_progress_lock); + if (s_background_progress_dialogs.empty()) + return; + + const float window_width = LayoutScale(500.0f); + const float window_height = LayoutScale(75.0f); + + ImGui::PushStyleColor(ImGuiCol_WindowBg, UIPrimaryDarkColor); + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, UISecondaryLightColor); + ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, LayoutScale(4.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, LayoutScale(1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(10.0f, 10.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, LayoutScale(10.0f, 10.0f)); + ImGui::PushFont(g_medium_font); + + ImDrawList* dl = ImGui::GetForegroundDrawList(); + + for (const BackgroundProgressDialogData& data : s_background_progress_dialogs) + { + const float window_pos_x = position.x; + const float window_pos_y = position.y - ((s_notification_vertical_direction < 0.0f) ? window_height : 0.0f); + + dl->AddRectFilled(ImVec2(window_pos_x, window_pos_y), ImVec2(window_pos_x + window_width, window_pos_y + window_height), + IM_COL32(0x11, 0x11, 0x11, 200), LayoutScale(10.0f)); + + ImVec2 pos(window_pos_x + LayoutScale(10.0f), window_pos_y + LayoutScale(10.0f)); + dl->AddText(g_medium_font, g_medium_font->FontSize, pos, IM_COL32(255, 255, 255, 255), data.message.c_str(), nullptr, 0.0f); + pos.y += g_medium_font->FontSize + LayoutScale(10.0f); + + ImVec2 bar_end(pos.x + window_width - LayoutScale(10.0f * 2.0f), pos.y + LayoutScale(25.0f)); + float fraction = static_cast(data.value - data.min) / static_cast(data.max - data.min); + dl->AddRectFilled(pos, bar_end, ImGui::GetColorU32(UIPrimaryDarkColor)); + dl->AddRectFilled(pos, ImVec2(pos.x + fraction * (bar_end.x - pos.x), bar_end.y), ImGui::GetColorU32(UISecondaryColor)); + + const std::string text(fmt::format("{}%", static_cast(std::round(fraction * 100.0f)))); + const ImVec2 text_size(ImGui::CalcTextSize(text.c_str())); + const ImVec2 text_pos( + pos.x + ((bar_end.x - pos.x) / 2.0f) - (text_size.x / 2.0f), pos.y + ((bar_end.y - pos.y) / 2.0f) - (text_size.y / 2.0f)); + dl->AddText(g_medium_font, g_medium_font->FontSize, text_pos, ImGui::GetColorU32(UIPrimaryTextColor), text.c_str()); + + position.y += s_notification_vertical_direction * (window_height + spacing); + } + + ImGui::PopFont(); + ImGui::PopStyleVar(4); + ImGui::PopStyleColor(2); +} + +////////////////////////////////////////////////////////////////////////// +// Notifications +////////////////////////////////////////////////////////////////////////// + +void ImGuiFullscreen::AddNotification(float duration, std::string title, std::string text, std::string image_path) +{ + Notification notif; + notif.duration = duration; + notif.title = std::move(title); + notif.text = std::move(text); + notif.badge_path = std::move(image_path); + notif.start_time = Common::Timer::GetCurrentValue(); + s_notifications.push_back(std::move(notif)); +} + +void ImGuiFullscreen::ClearNotifications() +{ + s_notifications.clear(); +} + +void ImGuiFullscreen::DrawNotifications(ImVec2& position, float spacing) +{ + if (s_notifications.empty()) + return; + + static constexpr float EASE_IN_TIME = 0.6f; + static constexpr float EASE_OUT_TIME = 0.6f; + const Common::Timer::Value current_time = Common::Timer::GetCurrentValue(); + + const float horizontal_padding = ImGuiFullscreen::LayoutScale(20.0f); + const float vertical_padding = ImGuiFullscreen::LayoutScale(10.0f); + const float horizontal_spacing = ImGuiFullscreen::LayoutScale(10.0f); + const float vertical_spacing = ImGuiFullscreen::LayoutScale(4.0f); + const float badge_size = ImGuiFullscreen::LayoutScale(48.0f); + const float min_width = ImGuiFullscreen::LayoutScale(200.0f); + const float max_width = ImGuiFullscreen::LayoutScale(800.0f); + const float max_text_width = max_width - badge_size - (horizontal_padding * 2.0f) - horizontal_spacing; + const float min_height = (vertical_padding * 2.0f) + badge_size; + const float shadow_size = ImGuiFullscreen::LayoutScale(4.0f); + const float rounding = ImGuiFullscreen::LayoutScale(4.0f); + + ImFont* const title_font = ImGuiFullscreen::g_large_font; + ImFont* const text_font = ImGuiFullscreen::g_medium_font; + +#if 0 + static constexpr u32 toast_background_color = IM_COL32(241, 241, 241, 255); + static constexpr u32 toast_border_color = IM_COL32(0x88, 0x88, 0x88, 255); + static constexpr u32 toast_title_color = IM_COL32(1, 1, 1, 255); + static constexpr u32 toast_text_color = IM_COL32(0, 0, 0, 255); +#else + static constexpr u32 toast_background_color = IM_COL32(0x21, 0x21, 0x21, 255); + static constexpr u32 toast_border_color = IM_COL32(0x48, 0x48, 0x48, 255); + static constexpr u32 toast_title_color = IM_COL32(0xff, 0xff, 0xff, 255); + static constexpr u32 toast_text_color = IM_COL32(0xff, 0xff, 0xff, 255); +#endif + + for (u32 index = 0; index < static_cast(s_notifications.size());) + { + const Notification& notif = s_notifications[index]; + const float time_passed = static_cast(Common::Timer::ConvertValueToSeconds(current_time - notif.start_time)); + if (time_passed >= notif.duration) + { + s_notifications.erase(s_notifications.begin() + index); + continue; + } + + const ImVec2 title_size(text_font->CalcTextSizeA( + title_font->FontSize, max_text_width, max_text_width, notif.title.c_str(), notif.title.c_str() + notif.title.size())); + + const ImVec2 text_size(text_font->CalcTextSizeA( + text_font->FontSize, max_text_width, max_text_width, notif.text.c_str(), notif.text.c_str() + notif.text.size())); + + const float box_width = + std::max((horizontal_padding * 2.0f) + badge_size + horizontal_spacing + std::max(title_size.x, text_size.x), min_width); + const float box_height = std::max((vertical_padding * 2.0f) + title_size.y + vertical_spacing + text_size.y, min_height); + + float x_offset = 0.0f; + if (time_passed < EASE_IN_TIME) + { + const float disp = (box_width + position.x); + x_offset = -(disp - (disp * Easing::InBack(time_passed / EASE_IN_TIME))); + } + else if (time_passed > (notif.duration - EASE_OUT_TIME)) + { + const float disp = (box_width + position.x); + x_offset = -(disp - (disp * Easing::OutBack((notif.duration - time_passed) / EASE_OUT_TIME))); + } + + const ImVec2 box_min(position.x + x_offset, position.y - ((s_notification_vertical_direction < 0.0f) ? box_height : 0.0f)); + const ImVec2 box_max(box_min.x + box_width, box_min.y + box_height); + + ImDrawList* dl = ImGui::GetForegroundDrawList(); + dl->AddRectFilled(ImVec2(box_min.x + shadow_size, box_min.y + shadow_size), + ImVec2(box_max.x + shadow_size, box_max.y + shadow_size), IM_COL32(20, 20, 20, 180), rounding, ImDrawCornerFlags_All); + dl->AddRectFilled(box_min, box_max, toast_background_color, rounding, ImDrawCornerFlags_All); + dl->AddRect(box_min, box_max, toast_border_color, rounding, ImDrawCornerFlags_All, ImGuiFullscreen::LayoutScale(1.0f)); + + const ImVec2 badge_min(box_min.x + horizontal_padding, box_min.y + vertical_padding); + const ImVec2 badge_max(badge_min.x + badge_size, badge_min.y + badge_size); + if (!notif.badge_path.empty()) + { + HostDisplayTexture* tex = GetCachedTexture(notif.badge_path.c_str()); + if (tex) + dl->AddImage(static_cast(tex->GetHandle()), badge_min, badge_max); + } + + const ImVec2 title_min(badge_max.x + horizontal_spacing, box_min.y + vertical_padding); + const ImVec2 title_max(title_min.x + title_size.x, title_min.y + title_size.y); + dl->AddText(title_font, title_font->FontSize, title_min, toast_title_color, notif.title.c_str(), + notif.title.c_str() + notif.title.size(), max_text_width); + + const ImVec2 text_min(badge_max.x + horizontal_spacing, title_max.y + vertical_spacing); + const ImVec2 text_max(text_min.x + text_size.x, text_min.y + text_size.y); + dl->AddText(text_font, text_font->FontSize, text_min, toast_text_color, notif.text.c_str(), notif.text.c_str() + notif.text.size(), + max_text_width); + + position.y += s_notification_vertical_direction * (box_height + shadow_size + spacing); + index++; + } +} + +void ImGuiFullscreen::ShowToast(std::string title, std::string message, float duration) +{ + s_toast_title = std::move(title); + s_toast_message = std::move(message); + s_toast_start_time = Common::Timer::GetCurrentValue(); + s_toast_duration = duration; +} + +void ImGuiFullscreen::ClearToast() +{ + s_toast_message = {}; + s_toast_title = {}; + s_toast_start_time = 0; + s_toast_duration = 0.0f; +} + +void ImGuiFullscreen::DrawToast() +{ + if (s_toast_title.empty() && s_toast_message.empty()) + return; + + const float elapsed = static_cast(Common::Timer::ConvertValueToSeconds(Common::Timer::GetCurrentValue() - s_toast_start_time)); + if (elapsed >= s_toast_duration) + { + ClearToast(); + return; + } + + // fade out the last second + const float alpha = std::min(std::min(elapsed * 4.0f, s_toast_duration - elapsed), 1.0f); + + const float max_width = LayoutScale(600.0f); + + ImFont* title_font = g_large_font; + ImFont* message_font = g_medium_font; + const float padding = LayoutScale(20.0f); + const float total_padding = padding * 2.0f; + const float margin = LayoutScale(20.0f); + const float spacing = s_toast_title.empty() ? 0.0f : LayoutScale(10.0f); + const ImVec2 display_size(ImGui::GetIO().DisplaySize); + const ImVec2 title_size(s_toast_title.empty() ? ImVec2(0.0f, 0.0f) : + title_font->CalcTextSizeA(title_font->FontSize, FLT_MAX, max_width, + s_toast_title.c_str(), s_toast_title.c_str() + s_toast_title.length())); + const ImVec2 message_size(s_toast_message.empty() ? ImVec2(0.0f, 0.0f) : + message_font->CalcTextSizeA(message_font->FontSize, FLT_MAX, max_width, + s_toast_message.c_str(), s_toast_message.c_str() + s_toast_message.length())); + const ImVec2 comb_size(std::max(title_size.x, message_size.x), title_size.y + spacing + message_size.y); + + const ImVec2 box_size(comb_size.x + total_padding, comb_size.y + total_padding); + const ImVec2 box_pos((display_size.x - box_size.x) * 0.5f, (display_size.y - margin - box_size.y)); + + ImDrawList* dl = ImGui::GetForegroundDrawList(); + dl->AddRectFilled(box_pos, box_pos + box_size, ImGui::GetColorU32(ModAlpha(UIPrimaryColor, alpha)), padding); + if (!s_toast_title.empty()) + { + const float offset = (comb_size.x - title_size.x) * 0.5f; + dl->AddText(title_font, title_font->FontSize, box_pos + ImVec2(offset + padding, padding), + ImGui::GetColorU32(ModAlpha(UIPrimaryTextColor, alpha)), s_toast_title.c_str(), s_toast_title.c_str() + s_toast_title.length(), + max_width); + } + if (!s_toast_message.empty()) + { + const float offset = (comb_size.x - message_size.x) * 0.5f; + dl->AddText(message_font, message_font->FontSize, box_pos + ImVec2(offset + padding, padding + spacing + title_size.y), + ImGui::GetColorU32(ModAlpha(UIPrimaryTextColor, alpha)), s_toast_message.c_str(), + s_toast_message.c_str() + s_toast_message.length(), max_width); + } +} + +void ImGuiFullscreen::SetTheme() +{ +#if 1 + // dark + UIBackgroundColor = HEX_TO_IMVEC4(0x212121, 0xff); + UIBackgroundTextColor = HEX_TO_IMVEC4(0xffffff, 0xff); + UIBackgroundLineColor = HEX_TO_IMVEC4(0xf0f0f0, 0xff); + UIBackgroundHighlightColor = HEX_TO_IMVEC4(0x4b4b4b, 0xff); + UIPrimaryColor = HEX_TO_IMVEC4(0x2e2e2e, 0xff); + UIPrimaryLightColor = HEX_TO_IMVEC4(0x484848, 0xff); + UIPrimaryDarkColor = HEX_TO_IMVEC4(0x000000, 0xff); + UIPrimaryTextColor = HEX_TO_IMVEC4(0xffffff, 0xff); + UIDisabledColor = HEX_TO_IMVEC4(0xaaaaaa, 0xff); + UITextHighlightColor = HEX_TO_IMVEC4(0x90caf9, 0xff); + UIPrimaryLineColor = HEX_TO_IMVEC4(0xffffff, 0xff); + UISecondaryColor = HEX_TO_IMVEC4(0x0d47a1, 0xff); + UISecondaryLightColor = HEX_TO_IMVEC4(0x63a4ff, 0xff); + UISecondaryDarkColor = HEX_TO_IMVEC4(0x002171, 0xff); + UISecondaryTextColor = HEX_TO_IMVEC4(0xffffff, 0xff); +#elif 1 + // light + UIBackgroundColor = HEX_TO_IMVEC4(0xf5f5f6, 0xff); + UIBackgroundTextColor = HEX_TO_IMVEC4(0x000000, 0xff); + UIBackgroundLineColor = HEX_TO_IMVEC4(0xe1e2e1, 0xff); + UIBackgroundHighlightColor = HEX_TO_IMVEC4(0xe1e2e1, 0xff); + UIPrimaryColor = HEX_TO_IMVEC4(0x0d47a1, 0xff); + UIPrimaryLightColor = HEX_TO_IMVEC4(0x5472d3, 0xff); + UIPrimaryDarkColor = HEX_TO_IMVEC4(0x002171, 0xff); + UIPrimaryTextColor = HEX_TO_IMVEC4(0xffffff, 0xff); + UIDisabledColor = HEX_TO_IMVEC4(0xaaaaaa, 0xff); + UITextHighlightColor = HEX_TO_IMVEC4(0x8e8e8e, 0xff); + UIPrimaryLineColor = HEX_TO_IMVEC4(0x000000, 0xff); + UISecondaryColor = HEX_TO_IMVEC4(0x3d5afe, 0xff); + UISecondaryLightColor = HEX_TO_IMVEC4(0xc0cfff, 0xff); + UISecondaryDarkColor = HEX_TO_IMVEC4(0x0031ca, 0xff); + UISecondaryTextColor = HEX_TO_IMVEC4(0x000000, 0xff); +#endif +} diff --git a/pcsx2/Frontend/ImGuiFullscreen.h b/pcsx2/Frontend/ImGuiFullscreen.h new file mode 100644 index 0000000000..e8db08153e --- /dev/null +++ b/pcsx2/Frontend/ImGuiFullscreen.h @@ -0,0 +1,239 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include "common/Pcsx2Defs.h" +#include "imgui.h" +#include "imgui_internal.h" +#include +#include +#include +#include +#include + +class HostDisplayTexture; + +namespace ImGuiFullscreen +{ +#define HEX_TO_IMVEC4(hex, alpha) \ + ImVec4(static_cast((hex >> 16) & 0xFFu) / 255.0f, static_cast((hex >> 8) & 0xFFu) / 255.0f, \ + static_cast(hex & 0xFFu) / 255.0f, static_cast(alpha) / 255.0f) + + static constexpr float LAYOUT_SCREEN_WIDTH = 1280.0f; + static constexpr float LAYOUT_SCREEN_HEIGHT = 720.0f; + static constexpr float LAYOUT_LARGE_FONT_SIZE = 26.0f; + static constexpr float LAYOUT_MEDIUM_FONT_SIZE = 16.0f; + static constexpr float LAYOUT_SMALL_FONT_SIZE = 10.0f; + static constexpr float LAYOUT_MENU_BUTTON_HEIGHT = 50.0f; + static constexpr float LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY = 26.0f; + static constexpr float LAYOUT_MENU_BUTTON_X_PADDING = 15.0f; + static constexpr float LAYOUT_MENU_BUTTON_Y_PADDING = 10.0f; + + extern ImFont* g_standard_font; + extern ImFont* g_medium_font; + extern ImFont* g_large_font; + + extern float g_layout_scale; + extern float g_layout_padding_left; + extern float g_layout_padding_top; + + extern ImVec4 UIBackgroundColor; + extern ImVec4 UIBackgroundTextColor; + extern ImVec4 UIBackgroundLineColor; + extern ImVec4 UIBackgroundHighlightColor; + extern ImVec4 UIDisabledColor; + extern ImVec4 UIPrimaryColor; + extern ImVec4 UIPrimaryLightColor; + extern ImVec4 UIPrimaryDarkColor; + extern ImVec4 UIPrimaryTextColor; + extern ImVec4 UITextHighlightColor; + extern ImVec4 UIPrimaryLineColor; + extern ImVec4 UISecondaryColor; + extern ImVec4 UISecondaryLightColor; + extern ImVec4 UISecondaryDarkColor; + extern ImVec4 UISecondaryTextColor; + + static __fi float DPIScale(float v) { return ImGui::GetIO().DisplayFramebufferScale.x * v; } + + static __fi float DPIScale(int v) { return ImGui::GetIO().DisplayFramebufferScale.x * static_cast(v); } + + static __fi ImVec2 DPIScale(const ImVec2& v) + { + const ImVec2& fbs = ImGui::GetIO().DisplayFramebufferScale; + return ImVec2(v.x * fbs.x, v.y * fbs.y); + } + + static __fi float WindowWidthScale(float v) { return ImGui::GetWindowWidth() * v; } + + static __fi float WindowHeightScale(float v) { return ImGui::GetWindowHeight() * v; } + + static __fi float LayoutScale(float v) { return g_layout_scale * v; } + + static __fi ImVec2 LayoutScale(const ImVec2& v) { return ImVec2(v.x * g_layout_scale, v.y * g_layout_scale); } + + static __fi ImVec2 LayoutScale(float x, float y) { return ImVec2(x * g_layout_scale, y * g_layout_scale); } + + static __fi ImVec2 LayoutScaleAndOffset(float x, float y) + { + return ImVec2(g_layout_padding_left + x * g_layout_scale, g_layout_padding_top + y * g_layout_scale); + } + + static __fi ImVec4 ModAlpha(const ImVec4& v, float a) + { + return ImVec4(v.x, v.y, v.z, a); + } + + /// Centers an image within the specified bounds, scaling up or down as needed. + ImRect CenterImage(const ImVec2& fit_size, const ImVec2& image_size); + ImRect CenterImage(const ImRect& fit_rect, const ImVec2& image_size); + + /// Initializes, setting up any state. + bool Initialize(const char* placeholder_image_path); + + void SetTheme(); + void SetFonts(ImFont* standard_font, ImFont* medium_font, ImFont* large_font); + bool UpdateLayoutScale(); + + /// Shuts down, clearing all state. + void Shutdown(); + + /// Texture cache. + const std::shared_ptr& GetPlaceholderTexture(); + std::shared_ptr LoadTexture(const char* path); + HostDisplayTexture* GetCachedTexture(const char* name); + HostDisplayTexture* GetCachedTextureAsync(const char* name); + bool InvalidateCachedTexture(const std::string& path); + void UploadAsyncTextures(); + + void BeginLayout(); + void EndLayout(); + + void QueueResetFocus(); + bool ResetFocusHere(); + bool WantsToCloseMenu(); + void ResetCloseMenuIfNeeded(); + + void PushPrimaryColor(); + void PopPrimaryColor(); + void PushSecondaryColor(); + void PopSecondaryColor(); + + void DrawWindowTitle(const char* title); + + bool BeginFullscreenColumns(const char* title = nullptr); + void EndFullscreenColumns(); + + bool BeginFullscreenColumnWindow(float start, float end, const char* name, const ImVec4& background = UIBackgroundColor); + void EndFullscreenColumnWindow(); + + bool BeginFullscreenWindow(float left, float top, float width, float height, const char* name, + const ImVec4& background = HEX_TO_IMVEC4(0x212121, 0xFF), float rounding = 0.0f, float padding = 0.0f, ImGuiWindowFlags flags = 0); + bool BeginFullscreenWindow(const ImVec2& position, const ImVec2& size, const char* name, + const ImVec4& background = HEX_TO_IMVEC4(0x212121, 0xFF), float rounding = 0.0f, float padding = 0.0f, ImGuiWindowFlags flags = 0); + void EndFullscreenWindow(); + + void BeginMenuButtons(u32 num_items = 0, float y_align = 0.0f, float x_padding = LAYOUT_MENU_BUTTON_X_PADDING, + float y_padding = LAYOUT_MENU_BUTTON_Y_PADDING, float item_height = LAYOUT_MENU_BUTTON_HEIGHT); + void EndMenuButtons(); + bool MenuButtonFrame(const char* str_id, bool enabled, float height, bool* visible, bool* hovered, ImVec2* min, ImVec2* max, + ImGuiButtonFlags flags = 0, float hover_alpha = 1.0f); + void MenuHeading(const char* title, bool draw_line = true); + bool MenuHeadingButton(const char* title, const char* value = nullptr, bool enabled = true, bool draw_line = true); + bool ActiveButton(const char* title, bool is_active, bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, + ImFont* font = g_large_font); + bool MenuButton(const char* title, const char* summary, bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT, + ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); + bool MenuButtonWithValue(const char* title, const char* summary, const char* value, bool enabled = true, + float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); + bool MenuImageButton(const char* title, const char* summary, ImTextureID user_texture_id, const ImVec2& image_size, bool enabled = true, + float height = LAYOUT_MENU_BUTTON_HEIGHT, const ImVec2& uv0 = ImVec2(0.0f, 0.0f), const ImVec2& uv1 = ImVec2(1.0f, 1.0f), + ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); + bool FloatingButton(const char* text, float x, float y, float width = -1.0f, float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, + float anchor_x = 0.0f, float anchor_y = 0.0f, bool enabled = true, ImFont* font = g_large_font, ImVec2* out_position = nullptr); + bool ToggleButton(const char* title, const char* summary, bool* v, bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT, + ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); + bool ThreeWayToggleButton(const char* title, const char* summary, std::optional* v, bool enabled = true, + float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); + bool RangeButton(const char* title, const char* summary, s32* value, s32 min, s32 max, s32 increment, const char* format = "%d", + bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); + bool RangeButton(const char* title, const char* summary, float* value, float min, float max, float increment, const char* format = "%f", + bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, ImFont* summary_font = g_medium_font); + bool EnumChoiceButtonImpl(const char* title, const char* summary, s32* value_pointer, + const char* (*to_display_name_function)(s32 value, void* opaque), void* opaque, u32 count, bool enabled, float height, ImFont* font, + ImFont* summary_font); + + template + static __fi bool EnumChoiceButton(const char* title, const char* summary, DataType* value_pointer, + const char* (*to_display_name_function)(DataType value), CountType count, bool enabled = true, + float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font, ImFont* summary_font = g_medium_font) + { + s32 value = static_cast(*value_pointer); + auto to_display_name_wrapper = [](s32 value, void* opaque) -> const char* { + return (*static_cast(opaque))(static_cast(value)); + }; + + if (EnumChoiceButtonImpl(title, summary, &value, to_display_name_wrapper, &to_display_name_function, static_cast(count), + enabled, height, font, summary_font)) + { + *value_pointer = static_cast(value); + return true; + } + else + { + return false; + } + } + + void BeginNavBar(float x_padding = LAYOUT_MENU_BUTTON_X_PADDING, float y_padding = LAYOUT_MENU_BUTTON_Y_PADDING); + void EndNavBar(); + void NavTitle(const char* title, float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = g_large_font); + void RightAlignNavButtons(u32 num_items = 0, float item_width = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, + float item_height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); + bool NavButton(const char* title, bool is_active, bool enabled = true, float width = -1.0f, + float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = g_large_font); + + using FileSelectorCallback = std::function; + using FileSelectorFilters = std::vector; + bool IsFileSelectorOpen(); + void OpenFileSelector(const char* title, bool select_directory, FileSelectorCallback callback, + FileSelectorFilters filters = FileSelectorFilters(), std::string initial_directory = std::string()); + void CloseFileSelector(); + + using ChoiceDialogCallback = std::function; + using ChoiceDialogOptions = std::vector>; + bool IsChoiceDialogOpen(); + void OpenChoiceDialog(const char* title, bool checkable, ChoiceDialogOptions options, ChoiceDialogCallback callback); + void CloseChoiceDialog(); + + using InputStringDialogCallback = std::function; + bool IsInputDialogOpen(); + void OpenInputStringDialog( + std::string title, std::string message, std::string caption, std::string ok_button_text, InputStringDialogCallback callback); + void CloseInputDialog(); + + float GetNotificationVerticalPosition(); + float GetNotificationVerticalDirection(); + void SetNotificationVerticalPosition(float position, float direction); + + void OpenBackgroundProgressDialog(const char* str_id, std::string message, s32 min, s32 max, s32 value); + void UpdateBackgroundProgressDialog(const char* str_id, std::string message, s32 min, s32 max, s32 value); + void CloseBackgroundProgressDialog(const char* str_id); + + void AddNotification(float duration, std::string title, std::string text, std::string image_path); + void ClearNotifications(); + + void ShowToast(std::string title, std::string message, float duration = 10.0f); + void ClearToast(); +} // namespace ImGuiFullscreen diff --git a/pcsx2/Frontend/ImGuiManager.cpp b/pcsx2/Frontend/ImGuiManager.cpp index 45b7455881..095d71319b 100644 --- a/pcsx2/Frontend/ImGuiManager.cpp +++ b/pcsx2/Frontend/ImGuiManager.cpp @@ -34,7 +34,6 @@ #include "Config.h" #include "Counters.h" #include "Frontend/ImGuiManager.h" -#include "Frontend/InputManager.h" #include "GS.h" #include "GS/GS.h" #include "Host.h" @@ -43,6 +42,10 @@ #include "PerformanceMetrics.h" #ifdef PCSX2_CORE +#include "Frontend/FullscreenUI.h" +#include "Frontend/ImGuiManager.h" +#include "Frontend/ImGuiFullscreen.h" +#include "Frontend/InputManager.h" #include "VMManager.h" #endif @@ -52,7 +55,7 @@ namespace ImGuiManager static void SetKeyMap(); static bool LoadFontData(); static void UnloadFontData(); - static bool AddImGuiFonts(); + static bool AddImGuiFonts(bool fullscreen_fonts); static ImFont* AddTextFont(float size); static ImFont* AddFixedFont(float size); static bool AddIconFonts(float size); @@ -66,6 +69,8 @@ static float s_global_scale = 1.0f; static ImFont* s_standard_font; static ImFont* s_fixed_font; +static ImFont* s_medium_font; +static ImFont* s_large_font; static std::vector s_standard_font_data; static std::vector s_fixed_font_data; @@ -111,6 +116,10 @@ bool ImGuiManager::Initialize() SetKeyMap(); SetStyle(); +#ifdef PCSX2_CORE + pxAssertRel(!FullscreenUI::IsInitialized(), "Fullscreen UI is not initialized on ImGui init"); +#endif + if (!display->CreateImGuiContext()) { pxFailRel("Failed to create ImGui device context"); @@ -120,7 +129,7 @@ bool ImGuiManager::Initialize() return false; } - if (!AddImGuiFonts() || !display->UpdateImGuiFontTexture()) + if (!AddImGuiFonts(false) || !display->UpdateImGuiFontTexture()) { pxFailRel("Failed to create ImGui font text"); display->DestroyImGuiContext(); @@ -138,6 +147,10 @@ bool ImGuiManager::Initialize() void ImGuiManager::Shutdown() { +#ifdef PCSX2_CORE + FullscreenUI::Shutdown(); +#endif + HostDisplay* display = Host::GetHostDisplay(); if (display) display->DestroyImGuiContext(); @@ -146,6 +159,11 @@ void ImGuiManager::Shutdown() s_standard_font = nullptr; s_fixed_font = nullptr; + s_medium_font = nullptr; + s_large_font = nullptr; +#ifdef PCSX2_CORE + ImGuiFullscreen::SetFonts(nullptr, nullptr, nullptr); +#endif UnloadFontData(); } @@ -172,8 +190,13 @@ void ImGuiManager::UpdateScale() const float window_scale = display ? display->GetWindowScale() : 1.0f; const float scale = std::max(window_scale * static_cast(EmuConfig.GS.OsdScale / 100.0), 1.0f); +#ifdef PCSX2_CORE + if (scale == s_global_scale && (!HasFullscreenFonts() || !ImGuiFullscreen::UpdateLayoutScale())) + return; +#else if (scale == s_global_scale) return; +#endif // This is assumed to be called mid-frame. ImGui::EndFrame(); @@ -185,7 +208,7 @@ void ImGuiManager::UpdateScale() SetStyle(); ImGui::GetStyle().ScaleAllSizes(scale); - if (!AddImGuiFonts()) + if (!AddImGuiFonts(HasFullscreenFonts())) pxFailRel("Failed to create ImGui font text"); if (!display->UpdateImGuiFontTexture()) @@ -393,7 +416,7 @@ ImFont* ImGuiManager::AddFixedFont(float size) bool ImGuiManager::AddIconFonts(float size) { - static const ImWchar range_fa[] = {ICON_MIN_FA, ICON_MAX_FA, 0}; + static constexpr ImWchar range_fa[] = { 0xf001,0xf002,0xf005,0xf005,0xf00c,0xf00e,0xf011,0xf011,0xf013,0xf013,0xf017,0xf017,0xf019,0xf019,0xf021,0xf021,0xf025,0xf025,0xf028,0xf028,0xf030,0xf030,0xf03a,0xf03a,0xf03d,0xf03d,0xf04a,0xf04c,0xf04e,0xf04e,0xf050,0xf050,0xf052,0xf052,0xf059,0xf059,0xf05e,0xf05e,0xf065,0xf065,0xf067,0xf067,0xf071,0xf071,0xf07b,0xf07c,0xf085,0xf085,0xf091,0xf091,0xf0a0,0xf0a0,0xf0ac,0xf0ad,0xf0c5,0xf0c5,0xf0c7,0xf0c9,0xf0d0,0xf0d0,0xf0e2,0xf0e2,0xf0eb,0xf0eb,0xf0f3,0xf0f3,0xf0fe,0xf0fe,0xf110,0xf110,0xf11b,0xf11c,0xf121,0xf121,0xf140,0xf140,0xf144,0xf144,0xf14a,0xf14a,0xf15b,0xf15b,0xf188,0xf188,0xf192,0xf192,0xf1c9,0xf1c9,0xf1dd,0xf1de,0xf1e6,0xf1e6,0xf1ea,0xf1eb,0xf1f8,0xf1f8,0xf1fc,0xf1fc,0xf242,0xf242,0xf245,0xf245,0xf26c,0xf26c,0xf2d0,0xf2d0,0xf2db,0xf2db,0xf2f5,0xf2f5,0xf410,0xf410,0xf466,0xf466,0xf51f,0xf51f,0xf543,0xf543,0xf545,0xf545,0xf547,0xf548,0xf552,0xf552,0xf65d,0xf65e,0xf756,0xf756,0xf7c2,0xf7c2,0xf815,0xf815,0xf818,0xf818,0xf8cc,0xf8cc,0x0,0x0 }; ImFontConfig cfg; cfg.MergeMode = true; @@ -406,7 +429,7 @@ bool ImGuiManager::AddIconFonts(float size) s_icon_font_data.data(), static_cast(s_icon_font_data.size()), size * 0.75f, &cfg, range_fa) != nullptr); } -bool ImGuiManager::AddImGuiFonts() +bool ImGuiManager::AddImGuiFonts(bool fullscreen_fonts) { const float standard_font_size = std::ceil(15.0f * s_global_scale); @@ -421,9 +444,56 @@ bool ImGuiManager::AddImGuiFonts() if (!s_fixed_font) return false; +#ifdef PCSX2_CORE + if (fullscreen_fonts) + { + const float medium_font_size = std::ceil(ImGuiFullscreen::LayoutScale(ImGuiFullscreen::LAYOUT_MEDIUM_FONT_SIZE)); + s_medium_font = AddTextFont(medium_font_size); + if (!s_medium_font || !AddIconFonts(medium_font_size)) + return false; + + const float large_font_size = std::ceil(ImGuiFullscreen::LayoutScale(ImGuiFullscreen::LAYOUT_LARGE_FONT_SIZE)); + s_large_font = AddTextFont(large_font_size); + if (!s_large_font || !AddIconFonts(large_font_size)) + return false; + } + else + { + s_medium_font = nullptr; + s_large_font = nullptr; + } + + ImGuiFullscreen::SetFonts(s_standard_font, s_medium_font, s_large_font); +#endif + return io.Fonts->Build(); } +bool ImGuiManager::AddFullscreenFontsIfMissing() +{ + if (HasFullscreenFonts()) + return true; + + // can't do this in the middle of a frame + ImGui::EndFrame(); + + if (!AddImGuiFonts(true)) + { + Console.Error("Failed to lazily allocate fullscreen fonts."); + AddImGuiFonts(false); + } + + Host::GetHostDisplay()->UpdateImGuiFontTexture(); + NewFrame(); + + return HasFullscreenFonts(); +} + +bool ImGuiManager::HasFullscreenFonts() +{ + return (s_medium_font && s_large_font); +} + struct OSDMessage { std::string key; @@ -767,7 +837,13 @@ void ImGuiManager::RenderOSD() // acquire for IO.MousePos. std::atomic_thread_fence(std::memory_order_acquire); +#ifdef PCSX2_CORE + // Don't draw OSD when we're just running big picture. + if (VMManager::HasValidVM()) + DrawPerformanceOverlay(); +#else DrawPerformanceOverlay(); +#endif AcquirePendingOSDMessages(); DrawOSDMessages(); @@ -788,6 +864,18 @@ ImFont* ImGuiManager::GetFixedFont() return s_fixed_font; } +ImFont* ImGuiManager::GetMediumFont() +{ + AddFullscreenFontsIfMissing(); + return s_medium_font; +} + +ImFont* ImGuiManager::GetLargeFont() +{ + AddFullscreenFontsIfMissing(); + return s_large_font; +} + #ifdef PCSX2_CORE bool ImGuiManager::WantsTextInput() @@ -900,4 +988,5 @@ bool ImGuiManager::ProcessGenericInputEvent(GenericInputBinding key, float value return true; } -#endif // PCSX2_CORE \ No newline at end of file +#endif // PCSX2_CORE + diff --git a/pcsx2/Frontend/ImGuiManager.h b/pcsx2/Frontend/ImGuiManager.h index 54473083e8..c50e7255bf 100644 --- a/pcsx2/Frontend/ImGuiManager.h +++ b/pcsx2/Frontend/ImGuiManager.h @@ -43,12 +43,26 @@ namespace ImGuiManager /// Returns the scale of all on-screen elements. float GetGlobalScale(); + /// Returns true if fullscreen fonts are present. + bool HasFullscreenFonts(); + + /// Allocates/adds fullscreen fonts if they're not loaded. + bool AddFullscreenFontsIfMissing(); + /// Returns the standard font for external drawing. ImFont* GetStandardFont(); /// Returns the fixed-width font for external drawing. ImFont* GetFixedFont(); + /// Returns the medium font for external drawing, scaled by ImGuiFullscreen. + /// This font is allocated on demand. + ImFont* GetMediumFont(); + + /// Returns the large font for external drawing, scaled by ImGuiFullscreen. + /// This font is allocated on demand. + ImFont* GetLargeFont(); + #ifdef PCSX2_CORE /// Returns true if imgui wants to intercept text input. bool WantsTextInput(); diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index 0445cd40b1..e3d4110383 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -40,6 +40,7 @@ #include "pcsx2/GS.h" #ifdef PCSX2_CORE #include "pcsx2/HostSettings.h" +#include "pcsx2/Frontend/FullscreenUI.h" #include "pcsx2/Frontend/InputManager.h" #endif @@ -281,7 +282,13 @@ static bool DoGSOpen(GSRendererType renderer, u8* basemem) return false; } +#ifdef PCSX2_CORE + // Don't override the fullscreen UI's vsync choice. + if (!FullscreenUI::IsInitialized()) + display->SetVSync(EmuConfig.GetEffectiveVsyncMode()); +#else display->SetVSync(EmuConfig.GetEffectiveVsyncMode()); +#endif GSConfig.OsdShowGPU = EmuConfig.GS.OsdShowGPU && display->SetGPUTimingEnabled(true); g_gs_renderer->SetRegsMem(basemem); diff --git a/pcsx2/PAD/Host/PAD.cpp b/pcsx2/PAD/Host/PAD.cpp index e8df1dcb08..ee9ee1aa22 100644 --- a/pcsx2/PAD/Host/PAD.cpp +++ b/pcsx2/PAD/Host/PAD.cpp @@ -361,7 +361,8 @@ void PAD::SetDefaultConfig(SettingsInterface& si) // si.SetStringValue("Hotkeys", "FrameAdvance", "Keyboard"); TBD // si.SetStringValue("Hotkeys", "IncreaseSpeed", "Keyboard"); TBD // si.SetStringValue("Hotkeys", "ResetVM", "Keyboard"); TBD - si.SetStringValue("Hotkeys", "ShutdownVM", "Keyboard/Escape"); + // si.SetStringValue("Hotkeys", "ShutdownVM", "Keyboard"); TBD + si.SetStringValue("Hotkeys", "OpenPauseMenu", "Keyboard/Escape"); si.SetStringValue("Hotkeys", "ToggleFrameLimit", "Keyboard/F4"); si.SetStringValue("Hotkeys", "TogglePause", "Keyboard/Space"); si.SetStringValue("Hotkeys", "ToggleSlowMotion", "Keyboard/Shift & Keyboard/Backtab"); diff --git a/pcsx2/SysForwardDefs.h b/pcsx2/SysForwardDefs.h index 6d595e3af3..d13caed1ae 100644 --- a/pcsx2/SysForwardDefs.h +++ b/pcsx2/SysForwardDefs.h @@ -36,6 +36,12 @@ #define VER_INTERNAL_NAME_STR VER_ORIGINAL_FILENAME_STR #define VER_COPYRIGHT_STR "Copyright (C) 2022" +#define PCSX2_WEBSITE_URL "https://pcsx2.net/" +#define PCSX2_FORUMS_URL "https://forums.pcsx2.net/" +#define PCSX2_GITHUB_URL "https://github.com/PCSX2/pcsx2" +#define PCSX2_LICENSE_URL "https://github.com/PCSX2/pcsx2/blob/master/pcsx2/Docs/License.txt" +#define PCSX2_DISCORD_URL "https://discord.com/invite/TCz3t9k" + static const bool PCSX2_isReleaseVersion = 0; class SysCoreThread; diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index 91691d90df..f3934b31fd 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -56,6 +56,7 @@ #include "DebugTools/MIPSAnalyst.h" #include "DebugTools/SymbolMap.h" +#include "Frontend/FullscreenUI.h" #include "Frontend/INISettingsInterface.h" #include "Frontend/InputManager.h" #include "Frontend/GameList.h" @@ -72,7 +73,6 @@ namespace VMManager { - static void LoadSettings(); static void ApplyGameFixes(); static bool UpdateGameSettingsLayer(); static void CheckForConfigChanges(const Pcsx2Config& old_config); @@ -137,6 +137,7 @@ static s32 s_current_save_slot = 1; static u32 s_frame_advance_count = 0; static u32 s_mxcsr_saved; static std::optional s_limiter_mode_prior_to_hold_interaction; +static bool s_gs_open_on_initialize = false; bool VMManager::PerformEarlyHardwareChecks(const char** error) { @@ -200,9 +201,15 @@ void VMManager::SetState(VMState state) SPU2SetOutputPaused(state == VMState::Paused); if (state == VMState::Paused) + { Host::OnVMPaused(); + FullscreenUI::OnVMPaused(); + } else + { Host::OnVMResumed(); + FullscreenUI::OnVMResumed(); + } } } @@ -256,6 +263,12 @@ bool VMManager::Internal::InitializeGlobals() x86caps.CalculateMHz(); SysLogMachineCaps(); + if (GSinit() != 0) + { + Host::ReportErrorAsync("Error", "Failed to initialize GS (GSinit())."); + return false; + } + return true; } @@ -683,6 +696,12 @@ void VMManager::UpdateRunningGame(bool resetting, bool game_starting) GetMTGS().SendGameCRC(new_crc); Host::OnGameChanged(s_disc_path, s_game_serial, s_game_name, s_game_crc); + if (FullscreenUI::IsInitialized()) + { + GetMTGS().RunOnGSThread([disc_path = s_disc_path, game_serial = s_game_serial, game_name = s_game_name, game_crc = s_game_crc]() { + FullscreenUI::OnRunningGameChanged(std::move(disc_path), std::move(game_serial), std::move(game_name), game_crc); + }); + } #if 0 // TODO: Enable this when the debugger is added to Qt, and it's active. Otherwise, this is just a waste of time. @@ -846,8 +865,6 @@ bool VMManager::Initialize(VMBootParameters boot_params) Host::OnVMDestroyed(); }; - LoadSettings(); - std::string state_to_load; if (!ApplyBootParameters(std::move(boot_params), &state_to_load)) return false; @@ -870,14 +887,18 @@ bool VMManager::Initialize(VMBootParameters boot_params) ScopedGuard close_cdvd = [] { DoCDVDclose(); }; Console.WriteLn("Opening GS..."); - if (!GetMTGS().WaitForOpen()) + s_gs_open_on_initialize = GetMTGS().IsOpen(); + if (!s_gs_open_on_initialize && !GetMTGS().WaitForOpen()) { // we assume GS is going to report its own error Console.WriteLn("Failed to open GS."); return false; } - ScopedGuard close_gs = []() { GetMTGS().WaitForClose(); }; + ScopedGuard close_gs = []() { + if (!s_gs_open_on_initialize) + GetMTGS().WaitForClose(); + }; Console.WriteLn("Opening SPU2..."); if (SPU2init() != 0 || SPU2open() != 0) @@ -964,6 +985,7 @@ bool VMManager::Initialize(VMBootParameters boot_params) Console.WriteLn("VM subsystems initialized in %.2f ms", init_timer.GetTimeMilliseconds()); s_state.store(VMState::Paused, std::memory_order_release); Host::OnVMStarted(); + FullscreenUI::OnVMStarted(); UpdateRunningGame(true, false); @@ -1047,7 +1069,14 @@ void VMManager::Shutdown(bool save_resume_state) DoCDVDclose(); FWclose(); FileMcd_EmuClose(); - GetMTGS().WaitForClose(); + + // If the fullscreen UI is running, do a hardware reset on the GS + // so that the texture cache and targets are all cleared. + if (s_gs_open_on_initialize) + GetMTGS().ResetGS(true); + else + GetMTGS().WaitForClose(); + USBshutdown(); SPU2shutdown(); PADshutdown(); @@ -1058,6 +1087,7 @@ void VMManager::Shutdown(bool save_resume_state) s_state.store(VMState::Shutdown, std::memory_order_release); Host::OnVMDestroyed(); + FullscreenUI::OnVMDestroyed(); } void VMManager::Reset() @@ -1623,20 +1653,27 @@ void VMManager::CheckForMemoryCardConfigChanges(const Pcsx2Config& old_config) void VMManager::CheckForConfigChanges(const Pcsx2Config& old_config) { - CheckForCPUConfigChanges(old_config); - CheckForGSConfigChanges(old_config); - CheckForFramerateConfigChanges(old_config); - CheckForPatchConfigChanges(old_config); - CheckForSPU2ConfigChanges(old_config); - CheckForDEV9ConfigChanges(old_config); - CheckForMemoryCardConfigChanges(old_config); - - if (EmuConfig.EnableCheats != old_config.EnableCheats || - EmuConfig.EnableWideScreenPatches != old_config.EnableWideScreenPatches || - EmuConfig.EnableNoInterlacingPatches != old_config.EnableNoInterlacingPatches) + if (HasValidVM()) { - VMManager::ReloadPatches(true, true); + CheckForCPUConfigChanges(old_config); + CheckForFramerateConfigChanges(old_config); + CheckForPatchConfigChanges(old_config); + CheckForSPU2ConfigChanges(old_config); + CheckForDEV9ConfigChanges(old_config); + CheckForMemoryCardConfigChanges(old_config); + + if (EmuConfig.EnableCheats != old_config.EnableCheats || + EmuConfig.EnableWideScreenPatches != old_config.EnableWideScreenPatches || + EmuConfig.EnableNoInterlacingPatches != old_config.EnableNoInterlacingPatches) + { + VMManager::ReloadPatches(true, true); + } } + + // For the big picture UI, we still need to update GS settings, since it's running, + // and we don't update its config when we start the VM. + if (HasValidVM() || GetMTGS().IsOpen()) + CheckForGSConfigChanges(old_config); } void VMManager::ApplySettings() @@ -1654,9 +1691,7 @@ void VMManager::ApplySettings() const Pcsx2Config old_config(EmuConfig); LoadSettings(); - - if (HasValidVM()) - CheckForConfigChanges(old_config); + CheckForConfigChanges(old_config); } bool VMManager::ReloadGameSettings() @@ -1741,6 +1776,10 @@ static void HotkeySaveStateSlot(s32 slot) } BEGIN_HOTKEY_LIST(g_vm_manager_hotkeys) +DEFINE_HOTKEY("OpenPauseMenu", "System", "Open Pause Menu", [](s32 pressed) { + if (!pressed) + FullscreenUI::OpenPauseMenu(); +}) DEFINE_HOTKEY("TogglePause", "System", "Toggle Pause", [](s32 pressed) { if (!pressed && VMManager::HasValidVM()) VMManager::SetPaused(VMManager::GetState() != VMState::Paused); @@ -1801,7 +1840,7 @@ DEFINE_HOTKEY("FrameAdvance", "System", "Frame Advance", [](s32 pressed) { }) DEFINE_HOTKEY("ShutdownVM", "System", "Shut Down Virtual Machine", [](s32 pressed) { if (!pressed && VMManager::HasValidVM()) - Host::RequestVMShutdown(true, true); + Host::RequestVMShutdown(true, true, EmuConfig.SaveStateOnShutdown); }) DEFINE_HOTKEY("ResetVM", "System", "Reset Virtual Machine", [](s32 pressed) { if (!pressed && VMManager::HasValidVM()) @@ -1858,7 +1897,6 @@ DEFINE_HOTKEY_SAVESTATE_X(10) DEFINE_HOTKEY_LOADSTATE_X(10) #undef DEFINE_HOTKEY_SAVESTATE_X #undef DEFINE_HOTKEY_LOADSTATE_X - END_HOTKEY_LIST() #ifdef _WIN32 diff --git a/pcsx2/VMManager.h b/pcsx2/VMManager.h index 0399f3666f..f002800967 100644 --- a/pcsx2/VMManager.h +++ b/pcsx2/VMManager.h @@ -76,6 +76,9 @@ namespace VMManager /// Returns the name of the disc/executable currently running. std::string GetGameName(); + /// Loads global settings (i.e. EmuConfig). + void LoadSettings(); + /// Initializes all system components. bool Initialize(VMBootParameters boot_params); @@ -249,7 +252,7 @@ namespace Host void RequestExit(bool save_state_if_running); /// Requests shut down of the current virtual machine. - void RequestVMShutdown(bool allow_confirm, bool allow_save_state); + void RequestVMShutdown(bool allow_confirm, bool allow_save_state, bool default_save_state); /// Returns true if the hosting application is currently fullscreen. bool IsFullscreen(); diff --git a/pcsx2/pcsx2core.vcxproj b/pcsx2/pcsx2core.vcxproj index 2adbb524e4..be8a425101 100644 --- a/pcsx2/pcsx2core.vcxproj +++ b/pcsx2/pcsx2core.vcxproj @@ -188,7 +188,9 @@ + + @@ -508,7 +510,9 @@ + + diff --git a/pcsx2/pcsx2core.vcxproj.filters b/pcsx2/pcsx2core.vcxproj.filters index 4e90e6ba82..bdbbb39d64 100644 --- a/pcsx2/pcsx2core.vcxproj.filters +++ b/pcsx2/pcsx2core.vcxproj.filters @@ -1275,6 +1275,12 @@ Host + + Host + + + Host + @@ -2119,6 +2125,12 @@ Host + + Host + + + Host + diff --git a/tools/generate_update_fa_glyph_ranges.py b/tools/generate_update_fa_glyph_ranges.py new file mode 100644 index 0000000000..98f887b0d2 --- /dev/null +++ b/tools/generate_update_fa_glyph_ranges.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 + +import sys +import os +import glob +import re +import functools + +# PCSX2 - PS2 Emulator for PCs +# Copyright (C) 2002-2022 PCSX2 Dev Team +# +# PCSX2 is free software: you can redistribute it and/or modify it under the terms +# of the GNU Lesser General Public License as published by the Free Software Found- +# ation, either version 3 of the License, or (at your option) any later version. +# +# PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with PCSX2. +# If not, see . + +# pylint: disable=bare-except, disable=missing-function-docstring + +src_dirs = [os.path.join(os.path.dirname(__file__), "..", "pcsx2"), os.path.join(os.path.dirname(__file__), "..", "pcsx2-qt")] +fa_file = os.path.join(os.path.dirname(__file__), "..", "3rdparty", "include", "IconsFontAwesome5.h") +dst_file = os.path.join(os.path.dirname(__file__), "..", "pcsx2", "Frontend", "ImguiManager.cpp") + +all_source_files = list(functools.reduce(lambda prev, src_dir: prev + glob.glob(os.path.join(src_dir, "**", "*.cpp"), recursive=True) + \ + glob.glob(os.path.join(src_dir, "**", "*.h"), recursive=True) + \ + glob.glob(os.path.join(src_dir, "**", "*.inl"), recursive=True), src_dirs, [])) + +tokens = set() +for filename in all_source_files: + data = None + with open(filename, "r") as f: + try: + data = f.read() + except: + continue + + tokens = tokens.union(set(re.findall("(ICON_FA_[a-zA-Z0-9_]+)", data))) + +print("{} tokens found.".format(len(tokens))) +if len(tokens) == 0: + sys.exit(0) + +u8_encodings = {} +with open(fa_file, "r") as f: + for line in f.readlines(): + match = re.match("#define (ICON_FA_[^ ]+) \"([^\"]+)\"", line) + if match is None: + continue + u8_encodings[match[1]] = bytes.fromhex(match[2].replace("\\x", "")) + +out_pattern = "(static constexpr ImWchar range_fa\[\] = \{)[0-9A-Z_a-z, \n]+(\};)" + +codepoints = list() +for token in tokens: + u8_bytes = u8_encodings[token] + u8 = str(u8_bytes, "utf-8") + u16 = u8.encode("utf-16le") + if len(u16) > 2: + raise ValueError("{} too long".format(u8_bytes)) + + codepoint = int.from_bytes(u16, byteorder="little", signed=False) + codepoints.append(codepoint) +codepoints.sort() +codepoints.append(0) # null terminator + +startc = codepoints[0] +endc = None +pairs = [startc] +for codepoint in codepoints: + if endc is not None and (endc + 1) != codepoint: + pairs.append(endc) + pairs.append(codepoint) + startc = codepoint + endc = codepoint + else: + endc = codepoint +pairs.append(endc) + +pairs_str = ",".join(map("0x{:x}".format, pairs)) + +with open(dst_file, "r") as f: + original = f.read() + updated = re.sub(out_pattern, "\\1 " + pairs_str + " \\2", original) + if original != updated: + with open(dst_file, "w") as f: + f.write(updated) + print("Updated {}".format(dst_file)) + else: + print("Skipping updating {}".format(dst_file)) +