diff --git a/android/app/src/cpp/android_host_interface.cpp b/android/app/src/cpp/android_host_interface.cpp index 28d69fa20..aeac2fd24 100644 --- a/android/app/src/cpp/android_host_interface.cpp +++ b/android/app/src/cpp/android_host_interface.cpp @@ -354,7 +354,7 @@ void AndroidHostInterface::EmulationThreadEntryPoint(JNIEnv* env, jobject emulat m_emulation_activity_object = emulation_activity; m_emulation_thread_id = std::this_thread::get_id(); } - CreateImGuiContext(); + ApplySettings(true); // Boot system. @@ -400,8 +400,6 @@ void AndroidHostInterface::EmulationThreadEntryPoint(JNIEnv* env, jobject emulat } env->CallVoidMethod(emulation_activity, s_EmulationActivity_method_onEmulationStopped); - - DestroyImGuiContext(); } void AndroidHostInterface::EmulationThreadLoop(JNIEnv* env) @@ -486,6 +484,9 @@ bool AndroidHostInterface::AcquireHostDisplay() wi.surface_width = ANativeWindow_getWidth(m_surface); wi.surface_height = ANativeWindow_getHeight(m_surface); + // TODO: Really need a better way of determining this. + wi.surface_scale = 2.0f; + switch (g_settings.gpu_renderer) { case GPURenderer::HardwareVulkan: @@ -500,20 +501,20 @@ bool AndroidHostInterface::AcquireHostDisplay() if (!m_display->CreateRenderDevice(wi, {}, g_settings.gpu_use_debug_device, g_settings.gpu_threaded_presentation) || !m_display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device, - g_settings.gpu_threaded_presentation) || - !m_display->CreateImGuiContext()) + g_settings.gpu_threaded_presentation)) { m_display->DestroyRenderDevice(); m_display.reset(); return false; } - // The alignement was set prior to booting. + // The alignment was set prior to booting. m_display->SetDisplayAlignment(m_display_alignment); - if (!m_display->UpdateImGuiFontTexture() || !CreateHostDisplayResources()) + if (!CreateHostDisplayResources()) { ReportError("Failed to create host display resources"); + ReleaseHostDisplayResources(); ReleaseHostDisplay(); return false; } @@ -524,9 +525,11 @@ bool AndroidHostInterface::AcquireHostDisplay() void AndroidHostInterface::ReleaseHostDisplay() { ReleaseHostDisplayResources(); - m_display->DestroyImGuiContext(); - m_display->DestroyRenderDevice(); - m_display.reset(); + if (m_display) + { + m_display->DestroyRenderDevice(); + m_display.reset(); + } } std::unique_ptr AndroidHostInterface::CreateAudioStream(AudioBackend backend) @@ -611,6 +614,7 @@ void AndroidHostInterface::SurfaceChanged(ANativeWindow* surface, int format, in wi.window_handle = surface; wi.surface_width = width; wi.surface_height = height; + wi.surface_scale = m_display->GetWindowScale(); m_display->ChangeRenderWindow(wi); @@ -628,27 +632,6 @@ void AndroidHostInterface::SetDisplayAlignment(HostDisplay::Alignment alignment) m_display->SetDisplayAlignment(alignment); } -void AndroidHostInterface::CreateImGuiContext() -{ - ImGui::CreateContext(); - - const float framebuffer_scale = 2.0f; - - auto& io = ImGui::GetIO(); - io.IniFilename = nullptr; - io.DisplayFramebufferScale.x = framebuffer_scale; - io.DisplayFramebufferScale.y = framebuffer_scale; - ImGui::GetStyle().ScaleAllSizes(framebuffer_scale); - - ImGui::StyleColorsDarker(); - ImGui::AddRobotoRegularFont(15.0f * framebuffer_scale); -} - -void AndroidHostInterface::DestroyImGuiContext() -{ - ImGui::DestroyContext(); -} - void AndroidHostInterface::SetControllerType(u32 index, std::string_view type_name) { ControllerType type = diff --git a/android/app/src/cpp/android_host_interface.h b/android/app/src/cpp/android_host_interface.h index 501b17561..813b0327f 100644 --- a/android/app/src/cpp/android_host_interface.h +++ b/android/app/src/cpp/android_host_interface.h @@ -82,9 +82,6 @@ protected: private: void EmulationThreadLoop(JNIEnv* env); - void CreateImGuiContext(); - void DestroyImGuiContext(); - void LoadSettings(SettingsInterface& si) override; void SetVibration(bool enabled); void UpdateVibration(); diff --git a/src/core/host_display.h b/src/core/host_display.h index aae82258f..9f46246d4 100644 --- a/src/core/host_display.h +++ b/src/core/host_display.h @@ -63,6 +63,7 @@ public: ALWAYS_INLINE s32 GetWindowWidth() const { return static_cast(m_window_info.surface_width); } ALWAYS_INLINE s32 GetWindowHeight() const { return static_cast(m_window_info.surface_height); } + ALWAYS_INLINE float GetWindowScale() const { return m_window_info.surface_scale; } // Position is relative to the top-left corner of the window. ALWAYS_INLINE s32 GetMousePositionX() const { return m_mouse_position_x; } diff --git a/src/duckstation-nogui/nogui_host_interface.cpp b/src/duckstation-nogui/nogui_host_interface.cpp index 377dea4dd..045b8f1b9 100644 --- a/src/duckstation-nogui/nogui_host_interface.cpp +++ b/src/duckstation-nogui/nogui_host_interface.cpp @@ -11,7 +11,6 @@ #include "frontend-common/controller_interface.h" #include "frontend-common/fullscreen_ui.h" #include "frontend-common/icon.h" -#include "frontend-common/imgui_fullscreen.h" #include "frontend-common/imgui_styles.h" #include "frontend-common/ini_settings_interface.h" #include "frontend-common/opengl_host_display.h" @@ -42,18 +41,15 @@ bool NoGUIHostInterface::Initialize() m_settings_interface = std::make_unique(GetSettingsFileName()); // TODO: Make command line. - m_fullscreen_ui_enabled = true; + m_flags.force_fullscreen_ui = true; if (!CommonHostInterface::Initialize()) return false; - CreateImGuiContext(); - - const bool start_fullscreen = m_command_line_flags.start_fullscreen || g_settings.start_fullscreen; + const bool start_fullscreen = m_flags.start_fullscreen || g_settings.start_fullscreen; if (!CreatePlatformWindow(start_fullscreen)) { Log_ErrorPrintf("Failed to create platform window"); - ImGui::DestroyContext(); return false; } @@ -61,10 +57,12 @@ bool NoGUIHostInterface::Initialize() { Log_ErrorPrintf("Failed to create host display"); DestroyPlatformWindow(); - ImGui::DestroyContext(); return false; } + if (m_fullscreen_ui_enabled) + FullscreenUI::QueueGameListRefresh(); + // process events to pick up controllers before updating input map PollAndUpdate(); UpdateInputMap(); @@ -73,45 +71,10 @@ bool NoGUIHostInterface::Initialize() void NoGUIHostInterface::Shutdown() { - CommonHostInterface::Shutdown(); - - if (m_display) - { - DestroyDisplay(); - ImGui::DestroyContext(); - } - + DestroyDisplay(); DestroyPlatformWindow(); -} -void NoGUIHostInterface::CreateImGuiContext() -{ - ImGui::CreateContext(); - ImGui::GetIO().IniFilename = nullptr; - ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad; -} - -void NoGUIHostInterface::OnPlatformWindowResized(u32 new_width, u32 new_height, float new_scale) -{ - if (new_scale != ImGui::GetIO().DisplayFramebufferScale.x) - { - ImGui::GetIO().DisplayFramebufferScale = ImVec2(new_scale, new_scale); - ImGui::GetStyle() = ImGuiStyle(); - ImGui::StyleColorsDarker(); - ImGui::GetStyle().ScaleAllSizes(new_scale); - } - - if (ImGuiFullscreen::UpdateLayoutScale()) - { - if (ImGuiFullscreen::UpdateFonts()) - { - if (!m_display->UpdateImGuiFontTexture()) - Panic("Failed to update font texture"); - } - } - - if (!System::IsShutdown()) - g_gpu->UpdateResolutionScale(); + CommonHostInterface::Shutdown(); } bool NoGUIHostInterface::CreateDisplay() @@ -163,33 +126,19 @@ bool NoGUIHostInterface::CreateDisplay() return false; } - if (!m_display->CreateImGuiContext() || - (m_fullscreen_ui_enabled && !FullscreenUI::Initialize(this)) || - !m_display->UpdateImGuiFontTexture()) - { - if (m_fullscreen_ui_enabled) - FullscreenUI::Shutdown(); - - m_display->DestroyImGuiContext(); - m_display->DestroyRenderDevice(); - m_display.reset(); - ReportError("Failed to initialize imgui/fonts/fullscreen UI"); - return false; - } + if (!CreateHostDisplayResources()) + Log_WarningPrint("Failed to create host display resources"); return true; } void NoGUIHostInterface::DestroyDisplay() { - if (m_fullscreen_ui_enabled) - FullscreenUI::Shutdown(); + ReleaseHostDisplayResources(); if (m_display) - { - m_display->DestroyImGuiContext(); m_display->DestroyRenderDevice(); - } + m_display.reset(); } @@ -235,42 +184,15 @@ bool NoGUIHostInterface::AcquireHostDisplay() Panic("Failed to recreate display on GPU renderer switch"); } - if (!CreateHostDisplayResources()) - return false; - return true; } void NoGUIHostInterface::ReleaseHostDisplay() { - ReleaseHostDisplayResources(); - // restore vsync, since we don't want to burn cycles at the menu m_display->SetVSync(true); } -void NoGUIHostInterface::OnSystemCreated() -{ - CommonHostInterface::OnSystemCreated(); - if (m_fullscreen_ui_enabled) - FullscreenUI::SystemCreated(); -} - -void NoGUIHostInterface::OnSystemPaused(bool paused) -{ - CommonHostInterface::OnSystemPaused(paused); - if (m_fullscreen_ui_enabled) - FullscreenUI::SystemPaused(paused); -} - -void NoGUIHostInterface::OnSystemDestroyed() -{ - CommonHostInterface::OnSystemDestroyed(); - ReportFormattedMessage("System shut down."); - if (m_fullscreen_ui_enabled) - FullscreenUI::SystemDestroyed(); -} - void NoGUIHostInterface::OnRunningGameChanged(const std::string& path, CDImage* image, const std::string& game_code, const std::string& game_title) { diff --git a/src/duckstation-nogui/nogui_host_interface.h b/src/duckstation-nogui/nogui_host_interface.h index 3f6a2af68..b8fa0f62f 100644 --- a/src/duckstation-nogui/nogui_host_interface.h +++ b/src/duckstation-nogui/nogui_host_interface.h @@ -41,9 +41,6 @@ protected: bool AcquireHostDisplay() override; void ReleaseHostDisplay() override; - void OnSystemCreated() override; - void OnSystemPaused(bool paused) override; - void OnSystemDestroyed() override; void OnRunningGameChanged(const std::string& path, CDImage* image, const std::string& game_code, const std::string& game_title) override; @@ -53,11 +50,9 @@ protected: virtual bool CreatePlatformWindow(bool fullscreen) = 0; virtual void DestroyPlatformWindow() = 0; virtual std::optional GetPlatformWindowInfo() = 0; - void OnPlatformWindowResized(u32 new_width, u32 new_height, float new_scale); bool CreateDisplay(); void DestroyDisplay(); - void CreateImGuiContext(); void RunCallbacks(); std::deque> m_queued_callbacks; diff --git a/src/duckstation-nogui/sdl_host_interface.cpp b/src/duckstation-nogui/sdl_host_interface.cpp index 0b19b662b..1da384d43 100644 --- a/src/duckstation-nogui/sdl_host_interface.cpp +++ b/src/duckstation-nogui/sdl_host_interface.cpp @@ -98,7 +98,7 @@ bool SDLHostInterface::SetFullscreen(bool enabled) int window_width, window_height; SDL_GetWindowSize(m_window, &window_width, &window_height); m_display->ResizeRenderWindow(window_width, window_height); - OnPlatformWindowResized(window_width, window_height, GetDPIScaleFactor(m_window)); + OnHostDisplayResized(window_width, window_height, GetDPIScaleFactor(m_window)); m_fullscreen = enabled; return true; @@ -289,7 +289,7 @@ void SDLHostInterface::HandleSDLEvent(const SDL_Event* event) if (event->window.event == SDL_WINDOWEVENT_RESIZED) { m_display->ResizeRenderWindow(event->window.data1, event->window.data2); - OnPlatformWindowResized(event->window.data1, event->window.data2, GetDPIScaleFactor(m_window)); + OnHostDisplayResized(event->window.data1, event->window.data2, GetDPIScaleFactor(m_window)); } else if (event->window.event == SDL_WINDOWEVENT_MOVED) { diff --git a/src/duckstation-qt/generalsettingswidget.cpp b/src/duckstation-qt/generalsettingswidget.cpp index 3621501c3..75e74a104 100644 --- a/src/duckstation-qt/generalsettingswidget.cpp +++ b/src/duckstation-qt/generalsettingswidget.cpp @@ -33,6 +33,8 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.applyGameSettings, "Main", "ApplyGameSettings", true); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.autoLoadCheats, "Main", "AutoLoadCheats", false); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableFullscreenUI, "Main", "EnableFullscreenUI", + false); SettingWidgetBinder::BindWidgetToEnumSetting( m_host_interface, m_ui.controllerBackend, "Main", "ControllerBackend", &ControllerInterface::ParseBackendName, @@ -74,9 +76,12 @@ GeneralSettingsWidget::GeneralSettingsWidget(QtHostInterface* host_interface, QW ControllerInterface::GetDefaultBackend())), tr("Determines the backend which is used for controller input. Windows users may prefer " "to use XInput over SDL2 for compatibility.")); + dialog->registerWidgetHelp( + m_ui.enableFullscreenUI, tr("Enable Fullscreen UI"), tr("Unchecked"), + tr("Enables the fullscreen UI mode, suitable for controller operation which is used in the NoGUI frontend.")); // Since this one is compile-time selected, we don't put it in the .ui file. - int current_col = 0; + int current_col = 1; int current_row = m_ui.formLayout_4->rowCount() - current_col; #ifdef WITH_DISCORD_PRESENCE { diff --git a/src/duckstation-qt/generalsettingswidget.ui b/src/duckstation-qt/generalsettingswidget.ui index d252f9542..7e9eadb24 100644 --- a/src/duckstation-qt/generalsettingswidget.ui +++ b/src/duckstation-qt/generalsettingswidget.ui @@ -102,6 +102,13 @@ + + + + Enable Fullscreen UI + + + diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 0f3487c20..45f6d6b3b 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -10,6 +10,7 @@ #include "core/gpu.h" #include "core/system.h" #include "frontend-common/game_list.h" +#include "frontend-common/imgui_fullscreen.h" #include "frontend-common/imgui_styles.h" #include "frontend-common/ini_settings_interface.h" #include "frontend-common/opengl_host_display.h" @@ -97,6 +98,10 @@ bool QtHostInterface::initializeOnThread() if (!CommonHostInterface::Initialize()) return false; + // imgui setup + setImGuiFont(); + setImGuiKeyMap(); + // make sure the controllers have been detected if (m_controller_interface) m_controller_interface->PollEvents(); @@ -294,7 +299,7 @@ void QtHostInterface::ApplySettings(bool display_osd_messages) m_is_rendering_to_main = render_to_main; updateDisplayState(); } - else + else if (!m_fullscreen_ui_enabled) { renderDisplay(); } @@ -367,6 +372,11 @@ void QtHostInterface::resumeSystemFromMostRecentState() void QtHostInterface::onDisplayWindowKeyEvent(int key, bool pressed) { DebugAssert(isOnWorkerThread()); + + const u32 masked_key = static_cast(key) & IMGUI_KEY_MASK; + if (masked_key < countof(ImGuiIO::KeysDown)) + ImGui::GetIO().KeysDown[masked_key] = pressed; + HandleHostKeyEvent(key, pressed); } @@ -377,14 +387,11 @@ void QtHostInterface::onDisplayWindowMouseMoveEvent(int x, int y) if (!m_display) return; - m_display->SetMousePosition(x, y); + ImGuiIO& io = ImGui::GetIO(); + io.MousePos[0] = static_cast(x); + io.MousePos[1] = static_cast(y); - if (ImGui::GetCurrentContext()) - { - ImGuiIO& io = ImGui::GetIO(); - io.MousePos[0] = static_cast(x); - io.MousePos[1] = static_cast(y); - } + m_display->SetMousePosition(x, y); } void QtHostInterface::onDisplayWindowMouseButtonEvent(int button, bool pressed) @@ -413,6 +420,7 @@ void QtHostInterface::onHostDisplayWindowResized(int width, int height) return; m_display->ResizeRenderWindow(width, height); + OnHostDisplayResized(width, height, m_display->GetWindowScale()); // re-render the display, since otherwise it will be out of date and stretched if paused if (!System::IsShutdown()) @@ -426,8 +434,9 @@ void QtHostInterface::onHostDisplayWindowResized(int width, int height) updateDisplayState(); } - g_gpu->UpdateResolutionScale(); - renderDisplay(); + // force redraw if we're paused + if (!m_fullscreen_ui_enabled) + renderDisplay(); } } @@ -470,16 +479,12 @@ bool QtHostInterface::AcquireHostDisplay() return false; } - createImGuiContext(display_widget->devicePixelRatioFromScreen()); - if (!m_display->MakeRenderContextCurrent() || !m_display->InitializeRenderDevice(GetShaderCacheBasePath(), g_settings.gpu_use_debug_device, g_settings.gpu_threaded_presentation) || - !m_display->CreateImGuiContext() || !m_display->UpdateImGuiFontTexture() || !CreateHostDisplayResources()) + !CreateHostDisplayResources()) { ReleaseHostDisplayResources(); - m_display->DestroyImGuiContext(); - destroyImGuiContext(); m_display->DestroyRenderDevice(); emit destroyDisplayRequested(); m_display.reset(); @@ -545,12 +550,16 @@ void QtHostInterface::updateDisplayState() connectDisplaySignals(display_widget); m_is_exclusive_fullscreen = m_display->IsFullscreen(); + OnHostDisplayResized(m_display->GetWindowWidth(), m_display->GetWindowHeight(), m_display->GetWindowScale()); + if (!System::IsShutdown()) { - g_gpu->UpdateResolutionScale(); UpdateSoftwareCursor(); - redrawDisplayWindow(); + + if (!m_fullscreen_ui_enabled) + redrawDisplayWindow(); } + UpdateSpeedLimiterState(); } @@ -559,9 +568,7 @@ void QtHostInterface::ReleaseHostDisplay() Assert(m_display); ReleaseHostDisplayResources(); - m_display->DestroyImGuiContext(); m_display->DestroyRenderDevice(); - destroyImGuiContext(); emit destroyDisplayRequested(); m_display.reset(); m_is_fullscreen = false; @@ -1370,32 +1377,40 @@ void QtHostInterface::threadEntryPoint() // TODO: Event which flags the thread as ready while (!m_shutdown_flag.load()) { - if (!System::IsRunning()) + if (System::IsRunning()) { - // wait until we have a system before running - m_worker_thread_event_loop->exec(); - continue; - } + if (m_display_all_frames) + System::RunFrame(); + else + System::RunFrames(); - if (m_display_all_frames) - System::RunFrame(); + UpdateControllerRumble(); + if (m_frame_step_request) + { + m_frame_step_request = false; + PauseSystem(true); + } + + renderDisplay(); + + System::UpdatePerformanceCounters(); + + if (m_throttler_enabled) + System::Throttle(); + } else - System::RunFrames(); - - UpdateControllerRumble(); - if (m_frame_step_request) { - m_frame_step_request = false; - PauseSystem(true); + // we want to keep rendering the UI when paused and fullscreen UI is enabled + if (!m_fullscreen_ui_enabled || !System::IsValid()) + { + // wait until we have a system before running + m_worker_thread_event_loop->exec(); + continue; + } + + renderDisplay(); } - renderDisplay(); - - System::UpdatePerformanceCounters(); - - if (m_throttler_enabled) - System::Throttle(); - m_worker_thread_event_loop->processEvents(QEventLoop::AllEvents); PollAndUpdate(); } @@ -1447,8 +1462,10 @@ static std::string GetFontPath(const char* name) #endif } -static bool AddImGuiFont(const std::string& language, float size, float framebuffer_scale) +void QtHostInterface::setImGuiFont() { + std::string language(GetStringSettingValue("Main", "Language", "")); + std::string path; const ImWchar* range = nullptr; #ifdef WIN32 @@ -1465,34 +1482,35 @@ static bool AddImGuiFont(const std::string& language, float size, float framebuf #endif if (!path.empty()) - { - return (ImGui::GetIO().Fonts->AddFontFromFileTTF(path.c_str(), size * framebuffer_scale, nullptr, range) != - nullptr); - } - - return false; + ImGuiFullscreen::SetFontFilename(std::move(path)); + if (range) + ImGuiFullscreen::SetFontGlyphRanges(range); } -void QtHostInterface::createImGuiContext(float framebuffer_scale) +void QtHostInterface::setImGuiKeyMap() { - ImGui::CreateContext(); - - auto& io = ImGui::GetIO(); - io.IniFilename = nullptr; - io.DisplayFramebufferScale.x = framebuffer_scale; - io.DisplayFramebufferScale.y = framebuffer_scale; - ImGui::GetStyle().ScaleAllSizes(framebuffer_scale); - - ImGui::StyleColorsDarker(); - - std::string language = GetStringSettingValue("Main", "Language", ""); - if (!AddImGuiFont(language, 15.0f, framebuffer_scale)) - ImGui::AddRobotoRegularFont(15.0f * framebuffer_scale); -} - -void QtHostInterface::destroyImGuiContext() -{ - ImGui::DestroyContext(); + ImGuiIO& io = ImGui::GetIO(); + io.KeyMap[ImGuiKey_Tab] = Qt::Key_Tab & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_LeftArrow] = Qt::Key_Left & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_RightArrow] = Qt::Key_Right & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_UpArrow] = Qt::Key_Up & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_DownArrow] = Qt::Key_Down & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_PageUp] = Qt::Key_PageUp & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_PageDown] = Qt::Key_PageDown & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_Home] = Qt::Key_Home & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_End] = Qt::Key_End & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_Insert] = Qt::Key_Insert & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_Delete] = Qt::Key_Delete & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_Backspace] = Qt::Key_Backspace & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_Space] = Qt::Key_Space & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_Enter] = Qt::Key_Enter & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_Escape] = Qt::Key_Escape & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_A] = Qt::Key_A & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_C] = Qt::Key_C & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_V] = Qt::Key_V & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_X] = Qt::Key_X & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_Y] = Qt::Key_Y & IMGUI_KEY_MASK; + io.KeyMap[ImGuiKey_Z] = Qt::Key_Z & IMGUI_KEY_MASK; } TinyString QtHostInterface::TranslateString(const char* context, const char* str) const diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index 0ef5022c1..9ff94b720 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -213,7 +213,10 @@ private: BACKGROUND_CONTROLLER_POLLING_INTERVAL = 100, /// Interval at which the controllers are polled when the system is not active. - SETTINGS_SAVE_DELAY = 1000 + SETTINGS_SAVE_DELAY = 1000, + + /// Crappy solution to the Qt indices being massive. + IMGUI_KEY_MASK = 511, }; using InputButtonHandler = std::function; @@ -242,8 +245,8 @@ private: void startBackgroundControllerPollTimer(); void stopBackgroundControllerPollTimer(); - void createImGuiContext(float framebuffer_scale); - void destroyImGuiContext(); + void setImGuiFont(); + void setImGuiKeyMap(); void createThread(); void stopThread(); diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index 45628a182..871056b35 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -25,6 +25,8 @@ #include "game_list.h" #include "icon.h" #include "imgui.h" +#include "imgui_fullscreen.h" +#include "imgui_styles.h" #include "ini_settings_interface.h" #include "save_state_selector_ui.h" #include "scmversion/scmversion.h" @@ -84,6 +86,9 @@ bool CommonHostInterface::Initialize() RegisterHotkeys(); UpdateControllerInterface(); + + CreateImGuiContext(); + return true; } @@ -91,6 +96,8 @@ void CommonHostInterface::Shutdown() { HostInterface::Shutdown(); + ImGui::DestroyContext(); + #ifdef WITH_DISCORD_PRESENCE ShutdownDiscordPresence(); #endif @@ -139,7 +146,7 @@ void CommonHostInterface::InitializeUserDirectory() bool CommonHostInterface::BootSystem(const SystemBootParameters& parameters) { // If the fullscreen UI is enabled, make sure it's finished loading the game list so we don't race it. - if (m_fullscreen_ui_enabled) + if (m_display && m_fullscreen_ui_enabled) FullscreenUI::EnsureGameListLoaded(); if (!HostInterface::BootSystem(parameters)) @@ -268,7 +275,7 @@ bool CommonHostInterface::ParseCommandLineParameters(int argc, char* argv[], else if (CHECK_ARG("-batch")) { Log_InfoPrintf("Enabling batch mode."); - m_command_line_flags.batch_mode = true; + m_flags.batch_mode = true; continue; } else if (CHECK_ARG("-fastboot")) @@ -286,7 +293,7 @@ bool CommonHostInterface::ParseCommandLineParameters(int argc, char* argv[], else if (CHECK_ARG("-nocontroller")) { Log_InfoPrintf("Disabling controller support."); - m_command_line_flags.disable_controller_interface = true; + m_flags.disable_controller_interface = true; continue; } else if (CHECK_ARG("-resume")) @@ -307,7 +314,7 @@ bool CommonHostInterface::ParseCommandLineParameters(int argc, char* argv[], else if (CHECK_ARG("-fullscreen")) { Log_InfoPrintf("Going fullscreen after booting."); - m_command_line_flags.start_fullscreen = true; + m_flags.start_fullscreen = true; force_fullscreen = true; continue; } @@ -444,6 +451,16 @@ bool CommonHostInterface::SetFullscreen(bool enabled) return false; } +void CommonHostInterface::CreateImGuiContext() +{ + ImGui::CreateContext(); + ImGui::GetIO().IniFilename = nullptr; +#ifndef __ANDROID__ + // Android has no keyboard, nor are we using ImGui for any actual user-interactable windows. + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad; +#endif +} + bool CommonHostInterface::CreateHostDisplayResources() { m_logo_texture = m_display->CreateTexture(APP_ICON_WIDTH, APP_ICON_HEIGHT, 1, 1, 1, HostDisplayPixelFormat::RGBA8, @@ -451,14 +468,83 @@ bool CommonHostInterface::CreateHostDisplayResources() if (!m_logo_texture) Log_WarningPrintf("Failed to create logo texture"); + const float framebuffer_scale = m_display->GetWindowScale(); + ImGui::GetIO().DisplayFramebufferScale = ImVec2(framebuffer_scale, framebuffer_scale); + ImGui::GetStyle() = ImGuiStyle(); + ImGui::StyleColorsDarker(); + ImGui::GetStyle().ScaleAllSizes(framebuffer_scale); + + if (!m_display->CreateImGuiContext()) + { + Log_ErrorPrintf("Failed to create ImGui device context"); + return false; + } + + if (m_fullscreen_ui_enabled) + { + if (!FullscreenUI::Initialize(this)) + { + Log_ErrorPrintf("Failed to initialize fullscreen UI, disabling."); + m_fullscreen_ui_enabled = false; + } + } + + if (!m_fullscreen_ui_enabled) + ImGuiFullscreen::ResetFonts(); + + if (!m_display->UpdateImGuiFontTexture()) + { + Log_ErrorPrintf("Failed to create ImGui font text"); + if (m_fullscreen_ui_enabled) + FullscreenUI::Shutdown(); + + m_display->DestroyImGuiContext(); + return false; + } + return true; } void CommonHostInterface::ReleaseHostDisplayResources() { + if (m_fullscreen_ui_enabled) + FullscreenUI::Shutdown(); + + if (m_display) + m_display->DestroyImGuiContext(); + m_logo_texture.reset(); } +void CommonHostInterface::OnHostDisplayResized(u32 new_width, u32 new_height, float new_scale) +{ + if (new_scale != ImGui::GetIO().DisplayFramebufferScale.x) + { + ImGui::GetIO().DisplayFramebufferScale = ImVec2(new_scale, new_scale); + ImGui::GetStyle() = ImGuiStyle(); + ImGui::StyleColorsDarker(); + ImGui::GetStyle().ScaleAllSizes(new_scale); + ImGuiFullscreen::ResetFonts(); + if (!m_display->UpdateImGuiFontTexture()) + Panic("Failed to recreate font texture after resize"); + } + + if (m_fullscreen_ui_enabled) + { + if (ImGuiFullscreen::UpdateLayoutScale()) + { + if (ImGuiFullscreen::UpdateFonts()) + { + if (!m_display->UpdateImGuiFontTexture()) + Panic("Failed to update font texture"); + } + } + } + + if (!System::IsShutdown()) + g_gpu->UpdateResolutionScale(); +} + std::unique_ptr CommonHostInterface::CreateAudioStream(AudioBackend backend) { switch (backend) @@ -492,7 +578,7 @@ void CommonHostInterface::UpdateControllerInterface() ControllerInterface::ParseBackendName(backend_str.c_str()); const ControllerInterface::Backend current_backend = (m_controller_interface ? m_controller_interface->GetBackend() : ControllerInterface::Backend::None); - if (new_backend == current_backend || m_command_line_flags.disable_controller_interface) + if (new_backend == current_backend || m_flags.disable_controller_interface) return; if (m_controller_interface) @@ -781,6 +867,9 @@ void CommonHostInterface::OnSystemCreated() { HostInterface::OnSystemCreated(); + if (m_fullscreen_ui_enabled) + FullscreenUI::SystemCreated(); + if (g_settings.display_post_processing && !m_display->SetPostProcessingChain(g_settings.display_post_process_chain)) AddOSDMessage(TranslateStdString("OSDMessage", "Failed to load post processing shader chain."), 20.0f); } @@ -789,6 +878,9 @@ void CommonHostInterface::OnSystemPaused(bool paused) { ReportFormattedMessage("System %s.", paused ? "paused" : "resumed"); + if (m_fullscreen_ui_enabled) + FullscreenUI::SystemPaused(paused); + if (paused) { if (IsFullscreen() && !m_fullscreen_ui_enabled) @@ -808,6 +900,9 @@ void CommonHostInterface::OnSystemDestroyed() HostInterface::OnSystemDestroyed(); + if (m_fullscreen_ui_enabled) + FullscreenUI::SystemDestroyed(); + StopControllerRumble(); } @@ -837,6 +932,9 @@ void CommonHostInterface::OnControllerTypeChanged(u32 slot) void CommonHostInterface::DrawImGuiWindows() { + if (m_save_state_selector_ui->IsOpen()) + m_save_state_selector_ui->Draw(); + if (m_fullscreen_ui_enabled) { FullscreenUI::Render(); @@ -850,9 +948,6 @@ void CommonHostInterface::DrawImGuiWindows() } DrawOSDMessages(); - - if (m_save_state_selector_ui->IsOpen()) - m_save_state_selector_ui->Draw(); } void CommonHostInterface::DrawFPSWindow() @@ -2383,6 +2478,36 @@ void CommonHostInterface::LoadSettings(SettingsInterface& si) #ifdef WITH_DISCORD_PRESENCE SetDiscordPresenceEnabled(si.GetBoolValue("Main", "EnableDiscordPresence", false)); #endif + + const bool fullscreen_ui_enabled = + si.GetBoolValue("Main", "EnableFullscreenUI", false) || m_flags.force_fullscreen_ui; + if (fullscreen_ui_enabled != m_fullscreen_ui_enabled) + { + m_fullscreen_ui_enabled = fullscreen_ui_enabled; + if (m_display) + { + if (!fullscreen_ui_enabled) + { + FullscreenUI::Shutdown(); + ImGuiFullscreen::ResetFonts(); + if (!m_display->UpdateImGuiFontTexture()) + Panic("Failed to recreate font texture after fullscreen UI disable"); + } + else + { + if (FullscreenUI::Initialize(this)) + { + if (!m_display->UpdateImGuiFontTexture()) + Panic("Failed to recreate font textre after fullscreen UI enable"); + } + else + { + Log_ErrorPrintf("Failed to initialize fullscreen UI. Disabling."); + m_fullscreen_ui_enabled = false; + } + } + } + } } void CommonHostInterface::SaveSettings(SettingsInterface& si) diff --git a/src/frontend-common/common_host_interface.h b/src/frontend-common/common_host_interface.h index 2d69aad05..0e886aeb8 100644 --- a/src/frontend-common/common_host_interface.h +++ b/src/frontend-common/common_host_interface.h @@ -122,6 +122,7 @@ public: virtual bool SetFullscreen(bool enabled); virtual bool Initialize() override; + virtual void Shutdown() override; virtual bool BootSystem(const SystemBootParameters& parameters) override; @@ -141,7 +142,7 @@ public: ALWAYS_INLINE ControllerInterface* GetControllerInterface() const { return m_controller_interface.get(); } /// Returns true if running in batch mode, i.e. exit after emulation. - ALWAYS_INLINE bool InBatchMode() const { return m_command_line_flags.batch_mode; } + ALWAYS_INLINE bool InBatchMode() const { return m_flags.batch_mode; } /// Returns true if the fullscreen UI is enabled. ALWAYS_INLINE bool IsFullscreenUIEnabled() const { return m_fullscreen_ui_enabled; } @@ -390,6 +391,7 @@ protected: bool CreateHostDisplayResources(); void ReleaseHostDisplayResources(); + void OnHostDisplayResized(u32 new_width, u32 new_height, float new_scale); virtual void DrawImGuiWindows(); @@ -430,7 +432,10 @@ protected: // starting fullscreen (outside of boot options) BitField start_fullscreen; - } m_command_line_flags = {}; + + // force fullscreen UI enabled (nogui) + BitField force_fullscreen_ui; + } m_flags = {}; private: void LoadSettings(); @@ -444,6 +449,7 @@ private: bool UpdateControllerInputMapFromGameSettings(); void UpdateHotkeyInputMap(SettingsInterface& si); void ClearAllControllerBindings(); + void CreateImGuiContext(); #ifdef WITH_DISCORD_PRESENCE void SetDiscordPresenceEnabled(bool enabled); diff --git a/src/frontend-common/fullscreen_ui.cpp b/src/frontend-common/fullscreen_ui.cpp index 786a61717..67245cb3b 100644 --- a/src/frontend-common/fullscreen_ui.cpp +++ b/src/frontend-common/fullscreen_ui.cpp @@ -35,6 +35,8 @@ Log_SetChannel(FullscreenUI); static constexpr float LAYOUT_MAIN_MENU_BAR_SIZE = 20.0f; // Should be DPI scaled, not layout scaled! 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; @@ -171,7 +173,6 @@ static bool s_save_state_selector_loading = true; ////////////////////////////////////////////////////////////////////////// static void DrawGameListWindow(); static void SwitchToGameList(); -static void QueueGameListRefresh(); static void SortGameList(); static HostDisplayTexture* GetTextureForGameListEntryType(GameListEntryType type); static HostDisplayTexture* GetGameListCover(const GameListEntry* entry); @@ -194,12 +195,16 @@ bool Initialize(CommonHostInterface* host_interface) s_settings_copy.Load(*s_host_interface->GetSettingsInterface()); SetDebugMenuEnabled(s_host_interface->GetSettingsInterface()->GetBoolValue("Main", "ShowDebugMenu", false)); - QueueGameListRefresh(); ImGuiFullscreen::UpdateLayoutScale(); ImGuiFullscreen::UpdateFonts(); ImGuiFullscreen::SetResolveTextureFunction(ResolveTextureHandle); + if (System::IsValid()) + SystemCreated(); + if (System::IsPaused()) + SystemPaused(true); + return true; } diff --git a/src/frontend-common/fullscreen_ui.h b/src/frontend-common/fullscreen_ui.h index f55827b99..4e28e01ef 100644 --- a/src/frontend-common/fullscreen_ui.h +++ b/src/frontend-common/fullscreen_ui.h @@ -54,6 +54,7 @@ bool InvalidateCachedTexture(const std::string& path); bool DrawErrorWindow(const char* message); bool DrawConfirmWindow(const char* message, bool* result); +void QueueGameListRefresh(); void EnsureGameListLoaded(); Settings& GetSettingsCopy(); diff --git a/src/frontend-common/imgui_fullscreen.cpp b/src/frontend-common/imgui_fullscreen.cpp index dc2922739..8175dabdc 100644 --- a/src/frontend-common/imgui_fullscreen.cpp +++ b/src/frontend-common/imgui_fullscreen.cpp @@ -47,10 +47,18 @@ void SetFontFilename(const char* filename) std::string().swap(s_font_filename); } -void SetIconFontFilename(const char* icon_font_filename) +void SetFontFilename(std::string filename) { - if (icon_font_filename) - s_icon_font_filename = icon_font_filename; + if (!filename.empty()) + s_font_filename = std::move(filename); + else + std::string().swap(s_font_filename); +} + +void SetIconFontFilename(std::string icon_font_filename) +{ + if (!icon_font_filename.empty()) + s_icon_font_filename = std::move(icon_font_filename); else std::string().swap(s_icon_font_filename); } @@ -111,8 +119,8 @@ bool UpdateFonts() const float medium_font_size = std::ceil(LayoutScale(LAYOUT_MEDIUM_FONT_SIZE)); const float large_font_size = std::ceil(LayoutScale(LAYOUT_LARGE_FONT_SIZE)); - if (g_standard_font && g_standard_font->FontSize == standard_font_size && medium_font_size && - g_medium_font->FontSize == medium_font_size && large_font_size && g_large_font->FontSize == large_font_size) + if (g_standard_font && g_standard_font->FontSize == standard_font_size && g_medium_font && + g_medium_font->FontSize == medium_font_size && g_large_font && g_large_font->FontSize == large_font_size) { return false; } @@ -147,6 +155,30 @@ bool UpdateFonts() return true; } +void ResetFonts() +{ + const float standard_font_size = std::ceil(DPIScale(s_font_size)); + + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->Clear(); + + if (s_font_filename.empty()) + { + g_standard_font = ImGui::AddRobotoRegularFont(standard_font_size); + } + else + { + g_standard_font = + io.Fonts->AddFontFromFileTTF(s_font_filename.c_str(), standard_font_size, nullptr, s_font_glyph_range); + } + + g_medium_font = nullptr; + g_large_font = nullptr; + + if (!io.Fonts->Build()) + Panic("Failed to rebuild font atlas"); +} + bool UpdateLayoutScale() { static constexpr float LAYOUT_RATIO = LAYOUT_SCREEN_WIDTH / LAYOUT_SCREEN_HEIGHT; diff --git a/src/frontend-common/imgui_fullscreen.h b/src/frontend-common/imgui_fullscreen.h index 5d2e5a083..3fdff40bb 100644 --- a/src/frontend-common/imgui_fullscreen.h +++ b/src/frontend-common/imgui_fullscreen.h @@ -141,6 +141,9 @@ void SetResolveTextureFunction(ResolveTextureHandleCallback callback); /// Rebuilds fonts to a new scale if needed. Returns true if fonts have changed and the texture needs updating. bool UpdateFonts(); +/// Removes the fullscreen fonts, leaving only the standard font. +void ResetFonts(); + bool UpdateLayoutScale(); void BeginLayout();