diff --git a/Common/UI/UIScreen.h b/Common/UI/UIScreen.h index 149b05068c..4c1fae115e 100644 --- a/Common/UI/UIScreen.h +++ b/Common/UI/UIScreen.h @@ -48,6 +48,9 @@ public: virtual bool AllowKeyboardNavigation() const { return true; } + // Process keyboard shortcuts even outside the game. + virtual InputMode PassInputToMapper() const { return InputMode::Keyboard; } + protected: virtual void CreateViews() = 0; virtual bool AllowFocusMovement() const { return true; } diff --git a/Core/Util/PortManager.cpp b/Core/Util/PortManager.cpp index 083aa0488d..d1d95a687c 100644 --- a/Core/Util/PortManager.cpp +++ b/Core/Util/PortManager.cpp @@ -179,8 +179,12 @@ bool PortManager::Initialize(const unsigned int timeout) { } // Using Game ID & Player Name as default description for mapping - std::string gameID = g_paramSFO.GetDiscID(); - m_defaultDesc = "PPSSPP:" + gameID + ":" + g_Config.sNickName; // Some routers may automatically prefixed it with "UPnP:" + if (PSP_IsInited()) { + std::string gameID = g_paramSFO.GetDiscID(); + m_defaultDesc = "PPSSPP:" + gameID + ":" + g_Config.sNickName; // Some routers may automatically prefixed it with "UPnP:" + } else { + m_defaultDesc = "PPSSPP:at_menu:" + g_Config.sNickName; + } freeUPNPDevlist(devlist); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 93cc0c9350..5580274d96 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -75,7 +75,6 @@ using namespace std::placeholders; #include "Core/RetroAchievements.h" #include "Core/SaveState.h" #include "Core/Screenshot.h" -#include "UI/ImDebugger/ImDebugger.h" #include "Core/HLE/__sceAudio.h" #include "Core/HW/Display.h" @@ -98,12 +97,6 @@ using namespace std::placeholders; #include "UI/ChatScreen.h" #include "UI/DebugOverlay.h" -#include "ext/imgui/imgui.h" -#include "ext/imgui/imgui_internal.h" -#include "ext/imgui/imgui_impl_thin3d.h" -#include "ext/imgui/imgui_impl_platform.h" - - #if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) #include "Windows/MainWindow.h" #endif @@ -114,15 +107,6 @@ static AVIDump avi; extern bool g_TakeScreenshot; -static void AssertCancelCallback(const char *message, void *userdata) { - NOTICE_LOG(Log::CPU, "Broke after assert: %s", message); - Core_Break(BreakReason::AssertChoice); - g_Config.bShowImDebugger = true; - - EmuScreen *emuScreen = (EmuScreen *)userdata; - emuScreen->SendImDebuggerCommand(ImCommand{ ImCmd::SHOW_IN_CPU_DISASM, currentMIPS->pc }); -} - // Handles control rotation due to internal screen rotation. void EmuScreen::UpdatePSPButtons(uint32_t bitsToSet, uint32_t bitsToClear) { if (!IsOnTop()) { @@ -283,8 +267,6 @@ void EmuScreen::ProcessGameBoot(const Path &filename) { break; } - SetAssertCancelCallback(&AssertCancelCallback, this); - if (!g_Config.bShaderCache) { // Only developers should ever see this. g_OSD.Show(OSDType::MESSAGE_WARNING, "Shader cache is disabled (developer)"); @@ -454,11 +436,6 @@ void EmuScreen::bootComplete() { EmuScreen::~EmuScreen() { g_controlMapper.RemoveListener(this); - if (imguiInited_) { - ImGui_ImplThin3d_Shutdown(); - ImGui::DestroyContext(ctx_); - } - std::string gameID = g_paramSFO.GetValueString("DISC_ID"); g_Config.TimeTracker().Stop(gameID); @@ -533,9 +510,6 @@ void EmuScreen::dialogFinished(const Screen *dialog, DialogResult result) { } SetExtraAssertInfo(extraAssertInfoStr_.c_str()); - - // Make sure we re-enable keyboard mode if it was disabled by the dialog, and if needed. - lastImguiEnabled_ = false; } void EmuScreen::focusChanged(ScreenFocusChange focusChange) { @@ -771,11 +745,6 @@ void EmuScreen::ProcessVKey(VirtKey virtKey, bool down) { } break; - case VIRTKEY_TOGGLE_DEBUGGER: - if (down) { - g_Config.bShowImDebugger = !g_Config.bShowImDebugger; - } - break; case VIRTKEY_TOGGLE_TILT: if (down) { g_Config.bTiltInputEnabled = !g_Config.bTiltInputEnabled; @@ -1160,27 +1129,12 @@ InputMode EmuScreen::PassInputToMapper() const { return InputMode::None; } - InputMode modes = InputMode::Keyboard | InputMode::Mouse | InputMode::Other | InputMode::ImDebuggerToggle; - if (g_Config.bShowImDebugger && imguiInited_) { - if (ImGui::GetIO().WantCaptureKeyboard) { - modes &= ~InputMode::Keyboard; - } - if (ImGui::GetIO().WantCaptureMouse) { - modes &= ~InputMode::Mouse; - } - return modes; - } - - return modes; + return InputMode::Keyboard | InputMode::Mouse | InputMode::Other | InputMode::ImDebuggerToggle; } bool EmuScreen::key(const KeyInput &key) { bool retval = UIScreen::key(key); - if (!retval && g_Config.bShowImDebugger && imguiInited_) { - ImGui_ImplPlatform_KeyEvent(key); - } - if (!retval && (key.flags & KeyInputFlags::DOWN) != 0 && UI::IsEscapeKey(key)) { if (chatMenu_) chatMenu_->Close(); @@ -1206,18 +1160,13 @@ bool EmuScreen::touch(const TouchInput &touch) { } if (touch.flags & TouchInputFlags::DOWN) { - if (!(g_Config.bShowImDebugger && imguiInited_) && !ignoreGamepad) { + if (!g_Config.bShowImDebugger && !ignoreGamepad) { // This just prevents the gamepad from timing out. GamepadTouch(); } } - if (g_Config.bShowImDebugger && imguiInited_) { - ImGui_ImplPlatform_TouchEvent(touch); - if (!ImGui::GetIO().WantCaptureMouse) { - return UIScreen::touch(touch); - } - } else if (g_Config.bMouseControl && !(touch.flags & TouchInputFlags::UP) && (touch.flags & TouchInputFlags::MOUSE)) { + if (g_Config.bMouseControl && !(touch.flags & TouchInputFlags::UP) && (touch.flags & TouchInputFlags::MOUSE)) { // don't do anything as the mouse pointer is hidden in this case. // But we let touch-up events through to avoid getting stuck if the user toggles mouse control. } else { @@ -1385,20 +1334,10 @@ void EmuScreen::deviceLost() { } UIScreen::deviceLost(); - - if (imguiInited_) { - if (imDebugger_) { - imDebugger_->DeviceLost(); - } - ImGui_ImplThin3d_DestroyDeviceObjects(); - } } void EmuScreen::deviceRestored(Draw::DrawContext *draw) { UIScreen::deviceRestored(draw); - if (imguiInited_) { - ImGui_ImplThin3d_CreateDeviceObjects(draw); - } } void EmuScreen::OnDevTools(UI::EventParams ¶ms) { @@ -1707,7 +1646,6 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) { Draw::BackendState state = draw->GetCurrentBackendState(); if (!(mode & ScreenRenderMode::TOP)) { - renderImDebugger(); // We're in run-behind mode, but we don't want to draw chat, debug UI and stuff. We do draw the imdebugger though. // So, darken and bail here. // Reset viewport/scissor to be sure. @@ -1735,7 +1673,7 @@ ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) { SetVRAppMode(screenManager()->topScreen() == this ? VRAppMode::VR_GAME_MODE : VRAppMode::VR_DIALOG_MODE); } - renderImDebugger(); + // renderImDebugger(); return screenRenderFlags; } @@ -1859,108 +1797,9 @@ ScreenRenderFlags EmuScreen::RunEmulation(bool skipBufferEffects) { } } - runImDebugger(); - return flags; } -void EmuScreen::runImDebugger() { - if (!lastImguiEnabled_ && g_Config.bShowImDebugger) { -#if !defined(MOBILE_DEVICE) - // On mobile devices (specifically iOS) we don't want to pop the keyboard - // on activating imgui. Instead, we should do it when a text edit field in imgui gets focus, - // although we'll still have ugly overlap problems. - System_NotifyUIEvent(UIEventNotification::TEXT_GOTFOCUS); -#endif - VERBOSE_LOG(Log::System, "activating keyboard"); - } else if (lastImguiEnabled_ && !g_Config.bShowImDebugger) { - System_NotifyUIEvent(UIEventNotification::TEXT_LOSTFOCUS); - VERBOSE_LOG(Log::System, "deactivating keyboard"); - } - lastImguiEnabled_ = g_Config.bShowImDebugger; - if (g_Config.bShowImDebugger) { - Draw::DrawContext *draw = screenManager()->getDrawContext(); - if (!imguiInited_) { - // TODO: Do this only on demand. - IMGUI_CHECKVERSION(); - ctx_ = ImGui::CreateContext(); - - ImGui_ImplPlatform_Init(GetSysDirectory(DIRECTORY_SYSTEM) / "imgui.ini"); - imDebugger_ = std::make_unique(); - - // Read the TTF font - size_t propSize = 0; - const uint8_t *propFontData = g_VFS.ReadFile("Roboto_Condensed-Regular.ttf", &propSize); - size_t fixedSize = 0; - const uint8_t *fixedFontData = g_VFS.ReadFile("Inconsolata-Regular.ttf", &fixedSize); - // This call works even if fontData is nullptr, in which case the font just won't get loaded. - // This takes ownership of the font array. - ImGui_ImplThin3d_Init(draw, propFontData, propSize, fixedFontData, fixedSize); - imguiInited_ = true; - } - - if (PSP_IsInited()) { - _dbg_assert_(imDebugger_); - - ImGui_ImplPlatform_NewFrame(); - ImGui_ImplThin3d_NewFrame(draw, ui_draw2d.GetDrawMatrix()); - - ImGui::NewFrame(); - - if (imCmd_.cmd != ImCmd::NONE) { - imDebugger_->PostCmd(imCmd_); - imCmd_.cmd = ImCmd::NONE; - } - - // Update keyboard modifiers. - auto &io = ImGui::GetIO(); - - KeyModifier modifiers = NativeGetKeyModifiers(); - - const bool keyCtrl = (modifiers & KeyModifier::LCTRL) || (modifiers & KeyModifier::RCTRL); - const bool keyShift = (modifiers & KeyModifier::LSHIFT) || (modifiers & KeyModifier::RSHIFT); - const bool keyAlt = (modifiers & KeyModifier::LALT) || (modifiers & KeyModifier::RALT); - io.AddKeyEvent(ImGuiMod_Ctrl, keyCtrl); - io.AddKeyEvent(ImGuiMod_Shift, keyShift); - io.AddKeyEvent(ImGuiMod_Alt, keyAlt); - // io.AddKeyEvent(ImGuiMod_Super, e.key.super); - - ImGuiID dockID = ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_NoDockingOverCentralNode); - ImGuiDockNode* node = ImGui::DockBuilderGetCentralNode(dockID); - - // Not elegant! But don't know how else to pass through the bounds, without making a mess. - Bounds centralNode(node->Pos.x, node->Pos.y, node->Size.x, node->Size.y); - SetOverrideScreenFrame(¢ralNode); - - if (!io.WantCaptureKeyboard) { - // Draw a focus rectangle to indicate inputs will be passed through. - ImGui::GetBackgroundDrawList()->AddRect - ( - node->Pos, - { node->Pos.x + node->Size.x, node->Pos.y + node->Size.y }, - IM_COL32(255, 255, 255, 90), - 0.f, - ImDrawFlags_None, - 1.f - ); - } - imDebugger_->Frame(currentDebugMIPS, gpu, draw); - - // Convert to drawlists. - ImGui::Render(); - } - } -} - -void EmuScreen::renderImDebugger() { - if (g_Config.bShowImDebugger) { - Draw::DrawContext *draw = screenManager()->getDrawContext(); - if (PSP_IsInited() && imDebugger_) { - ImGui_ImplThin3d_RenderDrawData(ImGui::GetDrawData(), draw); - } - } -} - bool EmuScreen::hasVisibleUI() { // Regular but uncommon UI. if (saveStatePreview_->GetVisibility() != UI::V_GONE || loadingSpinner_->GetVisibility() == UI::V_VISIBLE) diff --git a/UI/EmuScreen.h b/UI/EmuScreen.h index 8ca0a385ac..08a7dda430 100644 --- a/UI/EmuScreen.h +++ b/UI/EmuScreen.h @@ -61,10 +61,6 @@ public: void deviceLost() override; void deviceRestored(Draw::DrawContext *draw) override; - void SendImDebuggerCommand(const ImCommand &command) { - imCmd_ = command; - } - protected: void darken(); void focusChanged(ScreenFocusChange focusChange) override; @@ -91,9 +87,6 @@ private: void bootComplete(); bool hasVisibleUI(); void renderUI(); - void runImDebugger(); - void renderImDebugger(); - void AutoLoadSaveState(); bool checkPowerDown(); @@ -141,18 +134,9 @@ private: std::string extraAssertInfoStr_; - std::unique_ptr imDebugger_; - ImCommand imCmd_{}; // needed to buffer commands in case imgui wasn't created yet. - - bool imguiInited_ = false; - - bool lastImguiEnabled_ = false; - std::mutex queuedVirtKeysLock_; std::vector> queuedVirtKeys_; - ImGuiContext *ctx_ = nullptr; - bool frameStep_ = false; #ifndef MOBILE_DEVICE bool startDumping_ = false; diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 4672d6439f..c9d1a91e79 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -2363,19 +2363,21 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: // Watch the step counters to figure out when to update things. - if (lastCpuStepCount_ != Core_GetSteppingCounter()) { - lastCpuStepCount_ = Core_GetSteppingCounter(); - snapshot_ = newSnapshot_; // Compare against the previous snapshot. - Snapshot(currentMIPS); - disasm_.NotifyStep(); - } + if (PSP_IsInited()) { + if (lastCpuStepCount_ != Core_GetSteppingCounter()) { + lastCpuStepCount_ = Core_GetSteppingCounter(); + snapshot_ = newSnapshot_; // Compare against the previous snapshot. + Snapshot(currentMIPS); + disasm_.NotifyStep(); + } - if (lastGpuStepCount_ != GPUStepping::GetSteppingCounter()) { - // A GPU step has happened since last time. This means that we should re-center the cursor. - // Snapshot(); - lastGpuStepCount_ = GPUStepping::GetSteppingCounter(); - SnapshotGPU(gpuDebug); - geDebugger_.NotifyStep(); + if (gpuDebug && lastGpuStepCount_ != GPUStepping::GetSteppingCounter()) { + // A GPU step has happened since last time. This means that we should re-center the cursor. + // Snapshot(); + lastGpuStepCount_ = GPUStepping::GetSteppingCounter(); + SnapshotGPU(gpuDebug); + geDebugger_.NotifyStep(); + } } ImControl control{}; @@ -2645,12 +2647,14 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: atracToolWindow_.Draw(cfg_); } - if (cfg_.framebuffersOpen) { - DrawFramebuffersWindow(cfg_, gpuDebug->GetFramebufferManagerCommon()); - } + if (gpuDebug) { + if (cfg_.framebuffersOpen) { + DrawFramebuffersWindow(cfg_, gpuDebug->GetFramebufferManagerCommon()); + } - if (cfg_.texturesOpen) { - DrawTexturesWindow(cfg_, gpuDebug->GetTextureCacheCommon()); + if (cfg_.texturesOpen) { + DrawTexturesWindow(cfg_, gpuDebug->GetTextureCacheCommon()); + } } if (cfg_.logConfigOpen) { @@ -2661,78 +2665,82 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: logWindow_.Draw(cfg_); } - if (cfg_.jitViewerOpen) { - jitViewer_.Draw(cfg_, control); - } + if (PSP_IsInited()) { + if (cfg_.jitViewerOpen) { + jitViewer_.Draw(cfg_, control); + } - if (cfg_.displayOpen) { - DrawDisplayWindow(cfg_, gpuDebug->GetFramebufferManagerCommon()); - } + if (cfg_.displayOpen) { + DrawDisplayWindow(cfg_, gpuDebug->GetFramebufferManagerCommon()); + } - if (cfg_.debugStatsOpen) { - DrawDebugStatsWindow(cfg_); + if (cfg_.debugStatsOpen) { + DrawDebugStatsWindow(cfg_); + } } if (cfg_.structViewerOpen) { structViewer_.Draw(cfg_, control, mipsDebug); } - if (cfg_.paramSFOOpen) { - DrawParamSFO(cfg_, control); - } - - if (cfg_.geDebuggerOpen) { - geDebugger_.Draw(cfg_, control, gpuDebug, draw); - } - - if (cfg_.geStateOpen) { - geStateWindow_.Draw(cfg_, control, gpuDebug); - } - - if (cfg_.geVertsOpen) { - DrawImGeVertsWindow(cfg_, control, gpuDebug); - } - - if (cfg_.schedulerOpen) { - DrawSchedulerView(cfg_); - } - - if (cfg_.timeOpen) { - DrawTimeView(cfg_); - } - - if (cfg_.pixelViewerOpen) { - pixelViewer_.Draw(cfg_, control, gpuDebug, draw); - } - - if (cfg_.memDumpOpen) { - memDumpWindow_.Draw(cfg_, mipsDebug); - } - - if (cfg_.watchOpen) { - watchWindow_.Draw(cfg_, control, mipsDebug); - } - - for (int i = 0; i < 4; i++) { - if (cfg_.memViewOpen[i]) { - mem_[i].Draw(mipsDebug, cfg_, control, i); + if (PSP_IsInited()) { + if (cfg_.paramSFOOpen) { + DrawParamSFO(cfg_, control); } - } - if (cfg_.socketsOpen) { - DrawSockets(cfg_); - } + if (cfg_.geDebuggerOpen) { + geDebugger_.Draw(cfg_, control, gpuDebug, draw); + } - if (cfg_.npOpen) { - DrawNp(cfg_); - } + if (cfg_.geStateOpen) { + geStateWindow_.Draw(cfg_, control, gpuDebug); + } - if (cfg_.adhocOpen) { - DrawAdhoc(cfg_); - } + if (cfg_.geVertsOpen) { + DrawImGeVertsWindow(cfg_, control, gpuDebug); + } - if (cfg_.apctlOpen) { - DrawApctl(cfg_); + if (cfg_.schedulerOpen) { + DrawSchedulerView(cfg_); + } + + if (cfg_.timeOpen) { + DrawTimeView(cfg_); + } + + if (cfg_.pixelViewerOpen) { + pixelViewer_.Draw(cfg_, control, gpuDebug, draw); + } + + if (cfg_.memDumpOpen) { + memDumpWindow_.Draw(cfg_, mipsDebug); + } + + if (cfg_.watchOpen) { + watchWindow_.Draw(cfg_, control, mipsDebug); + } + + for (int i = 0; i < 4; i++) { + if (cfg_.memViewOpen[i]) { + mem_[i].Draw(mipsDebug, cfg_, control, i); + } + } + + if (cfg_.socketsOpen) { + DrawSockets(cfg_); + } + + if (cfg_.npOpen) { + DrawNp(cfg_); + } + + if (cfg_.adhocOpen) { + DrawAdhoc(cfg_); + } + + if (cfg_.apctlOpen) { + DrawApctl(cfg_); + } } if (cfg_.internalsOpen) { diff --git a/UI/MiscViews.cpp b/UI/MiscViews.cpp index cef4a1b605..6fe54781d3 100644 --- a/UI/MiscViews.cpp +++ b/UI/MiscViews.cpp @@ -436,14 +436,24 @@ void ViewSearch::ApplySearchFilter(UI::ViewGroup *viewGroup, bool setKeyboardFoc } } +static bool IsSearchableChar(int unichar) { + // 127 gets produced from Ctrl+Backspace on Windows for some reason. + return unichar >= 0x20 && unichar != 127; +} + +static bool IsFirstSearchableChar(int unichar) { + // Don't allow spaces as the first character, it looks confusing (empty search field) + return IsSearchableChar(unichar) && unichar != ' ' && unichar != '`' && unichar != '.' && unichar != ','; +} + bool ViewSearch::Key(UI::ViewGroup *viewGroup, const KeyInput &input) { bool retval = false; // Only one is visible at a time, so we can just grab all Char input. if (input.flags & KeyInputFlags::CHAR) { const int unichar = input.keyCode; - if (unichar >= 0x20 && unichar != 127) { // 127 gets produced from Ctrl+Backspace on Windows for some reason. + if (IsSearchableChar(unichar)) { // Don't allow spaces as the first character, it looks confusing (empty search field) - if (searchFilter.empty() && unichar == ' ') { + if (searchFilter.empty() && !IsFirstSearchableChar(unichar)) { return false; } diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 9758b96816..933e73ce60 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -38,6 +38,13 @@ #include #include + +#include "ext/imgui/imgui.h" +#include "ext/imgui/imgui_internal.h" +#include "ext/imgui/imgui_impl_thin3d.h" +#include "ext/imgui/imgui_impl_platform.h" + + #if defined(_WIN32) #include "Windows/WindowsAudio.h" #include "Windows/MainWindow.h" @@ -121,6 +128,7 @@ #include "GPU/GPUCommon.h" #include "GPU/Common/PresentationCommon.h" +#include "UI/ImDebugger/ImDebugger.h" #include "UI/AudioCommon.h" #include "UI/Background.h" #include "UI/BackgroundAudio.h" @@ -139,7 +147,6 @@ #include "UI/Theme.h" #include "UI/PauseScreen.h" #include "UI/UIAtlas.h" - #if PPSSPP_PLATFORM(UWP) #include #include "UWP/UWPHelpers/InputHelpers.h" @@ -202,6 +209,32 @@ static bool g_nativeMainThreadReady = false; static std::mutex g_inputEventQueueLock; static std::vector g_inputEventQueue; +static std::unique_ptr imDebugger_; +static ImCommand imCmd_{}; // needed to buffer commands in case imgui wasn't created yet. +static bool imguiInited_ = false; +static bool lastImguiEnabled_ = false; +static ImGuiContext *ctx_ = nullptr; + +class GlobalListener : public ControlListener { + virtual void OnVKey(VirtKey vkey, bool down) { + switch (vkey) { + case VIRTKEY_TOGGLE_DEBUGGER: + if (down) { + g_Config.bShowImDebugger = !g_Config.bShowImDebugger; + } + break; + } + } +}; +GlobalListener g_globalListener; + +static void AssertCancelCallback(const char *message, void *userdata) { + NOTICE_LOG(Log::CPU, "Broke after assert: %s", message); + Core_Break(BreakReason::AssertChoice); + g_Config.bShowImDebugger = true; + imCmd_ = ImCommand{ImCmd::SHOW_IN_CPU_DISASM, currentMIPS->pc}; +} + static void ApplyAchievementsRuntimeSettings() { auto *client = Achievements::GetClient(); if (!client) { @@ -261,6 +294,101 @@ static void RunAchievementsOverrideUpdate(std::function func) { } } +void runImDebugger(Draw::DrawContext *draw) { + bool lastImguiEnabled_ = false; // temp + if (lastImguiEnabled_ && g_Config.bShowImDebugger) { +#if !defined(MOBILE_DEVICE) + // On mobile devices (specifically iOS) we don't want to pop the keyboard + // on activating imgui. Instead, we should do it when a text edit field in imgui gets focus, + // although we'll still have ugly overlap problems. + System_NotifyUIEvent(UIEventNotification::TEXT_GOTFOCUS); +#endif + VERBOSE_LOG(Log::System, "activating keyboard"); + } else if (lastImguiEnabled_ && !g_Config.bShowImDebugger) { + System_NotifyUIEvent(UIEventNotification::TEXT_LOSTFOCUS); + VERBOSE_LOG(Log::System, "deactivating keyboard"); + } + lastImguiEnabled_ = g_Config.bShowImDebugger; + if (g_Config.bShowImDebugger) { + if (!imguiInited_) { + // TODO: Do this only on demand. + IMGUI_CHECKVERSION(); + ctx_ = ImGui::CreateContext(); + + ImGui_ImplPlatform_Init(GetSysDirectory(DIRECTORY_SYSTEM) / "imgui.ini"); + imDebugger_ = std::make_unique(); + + // Read the TTF font + size_t propSize = 0; + const uint8_t *propFontData = g_VFS.ReadFile("Roboto_Condensed-Regular.ttf", &propSize); + size_t fixedSize = 0; + const uint8_t *fixedFontData = g_VFS.ReadFile("Inconsolata-Regular.ttf", &fixedSize); + // This call works even if fontData is nullptr, in which case the font just won't get loaded. + // This takes ownership of the font array. + ImGui_ImplThin3d_Init(draw, propFontData, propSize, fixedFontData, fixedSize); + imguiInited_ = true; + } + + _dbg_assert_(imDebugger_); + + ImGui_ImplPlatform_NewFrame(); + ImGui_ImplThin3d_NewFrame(draw, ui_draw2d.GetDrawMatrix()); + + ImGui::NewFrame(); + + if (imCmd_.cmd != ImCmd::NONE) { + imDebugger_->PostCmd(imCmd_); + imCmd_.cmd = ImCmd::NONE; + } + + // Update keyboard modifiers. + auto &io = ImGui::GetIO(); + + KeyModifier modifiers = NativeGetKeyModifiers(); + + const bool keyCtrl = (modifiers & KeyModifier::LCTRL) || (modifiers & KeyModifier::RCTRL); + const bool keyShift = (modifiers & KeyModifier::LSHIFT) || (modifiers & KeyModifier::RSHIFT); + const bool keyAlt = (modifiers & KeyModifier::LALT) || (modifiers & KeyModifier::RALT); + io.AddKeyEvent(ImGuiMod_Ctrl, keyCtrl); + io.AddKeyEvent(ImGuiMod_Shift, keyShift); + io.AddKeyEvent(ImGuiMod_Alt, keyAlt); + // io.AddKeyEvent(ImGuiMod_Super, e.key.super); + + ImGuiID dockID = ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_NoDockingOverCentralNode); + ImGuiDockNode* node = ImGui::DockBuilderGetCentralNode(dockID); + + // Not elegant! But don't know how else to pass through the bounds, without making a mess. + Bounds centralNode(node->Pos.x, node->Pos.y, node->Size.x, node->Size.y); + SetOverrideScreenFrame(¢ralNode); + + if (!io.WantCaptureKeyboard) { + // Draw a focus rectangle to indicate inputs will be passed through. + ImGui::GetBackgroundDrawList()->AddRect + ( + node->Pos, + {node->Pos.x + node->Size.x, node->Pos.y + node->Size.y}, + IM_COL32(255, 255, 255, 90), + 0.f, + ImDrawFlags_None, + 1.f + ); + } + imDebugger_->Frame(currentDebugMIPS, gpu, draw); + + // Convert to drawlists. + ImGui::Render(); + } +} + +void renderImDebugger(Draw::DrawContext *draw) { + if (g_Config.bShowImDebugger) { + if (imDebugger_) { + ImGui_ImplThin3d_RenderDrawData(ImGui::GetDrawData(), draw); + } + } +} + + std::vector> g_pendingClosures; AudioBackend *g_audioBackend = nullptr; @@ -395,6 +523,10 @@ void NativeInit(int argc, const char *argv[], const CommandLineOptions &cmdLineO g_Config.Init(); + g_controlMapper.AddListener(&g_globalListener); + + SetAssertCancelCallback(&AssertCancelCallback, nullptr); + IncrementDebugCounter(DebugCounter::APP_BOOT); // Probably an excessive timeout. it only causes delays on shutdown, though. @@ -851,6 +983,11 @@ bool NativeInitGraphics(GraphicsContext *graphicsContext) { gpu->DeviceRestore(g_draw); } + if (imguiInited_) { + ImGui_ImplThin3d_CreateDeviceObjects(g_draw); + } + + INFO_LOG(Log::System, "NativeInitGraphics completed"); return true; @@ -922,6 +1059,15 @@ void NativeShutdownGraphics(GraphicsContext *graphicsContext) { gpu->DeviceLost(); } + if (imguiInited_) { + if (imDebugger_) { + imDebugger_->DeviceLost(); + } + ImGui_ImplThin3d_DestroyDeviceObjects(); + ImGui_ImplThin3d_Shutdown(); + ImGui::DestroyContext(ctx_); + } + #if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) if (winCamera) { winCamera->waitShutDown(); @@ -1038,8 +1184,47 @@ void NativeFrame(GraphicsContext *graphicsContext) { } for (auto &event : inputEvents) { - g_screenManager->ProcessInputEvent(event); + bool filterTouch = false; + bool filterKey = false; + if (g_Config.bShowImDebugger && imguiInited_) { + // Let ImGui handle input if it's active. + if (ImGui::GetIO().WantCaptureMouse) { + filterTouch = true; + } + if (ImGui::GetIO().WantCaptureKeyboard) { + filterKey = true; + } + } + + switch (event.type) { + case QueuedEventType::KEY: + // Let through up events to avoid stuck keys. + if (!filterKey || (event.key.flags & KeyInputFlags::UP)) { + g_screenManager->ProcessInputEvent(event); + } + break; + case QueuedEventType::TOUCH: + if (!filterTouch) { + g_screenManager->ProcessInputEvent(event); + } + break; + default: + g_screenManager->ProcessInputEvent(event); + break; + } + + if (g_Config.bShowImDebugger && imguiInited_) { + switch (event.type) { + case QueuedEventType::KEY: + ImGui_ImplPlatform_KeyEvent(event.key); + break; + case QueuedEventType::TOUCH: + ImGui_ImplPlatform_TouchEvent(event.touch); + break; + } + } } + g_screenManager->Update(); // This must happen *after* input event processing! // Do this after g_screenManager.update() so we can receive setting changes before rendering. @@ -1111,6 +1296,8 @@ void NativeFrame(GraphicsContext *graphicsContext) { ui_draw2d.PopDrawMatrix(); + runImDebugger(g_draw); + renderImDebugger(g_draw); g_draw->EndFrame(); // This, between EndFrame and Present, is where we should actually wait to do present time management. @@ -1379,6 +1566,16 @@ bool NativeKey(const KeyInput &key) { // Filtering, detailed rules needed for good imgui behavior without having to ask the screen about what to do. InputMode inputMode = g_screenManager->PassInputToMapper(); + + if (g_Config.bShowImDebugger && imguiInited_) { + if (ImGui::GetIO().WantCaptureKeyboard) { + inputMode &= ~InputMode::Keyboard; + } + if (ImGui::GetIO().WantCaptureMouse) { + inputMode &= ~InputMode::Mouse; + } + } + bool passKeyThrough = false; if (inputMode != InputMode::None) { if ((inputMode & InputMode::ImDebuggerToggle) && (key.flags & (KeyInputFlags::UP | KeyInputFlags::DOWN))) { @@ -1394,6 +1591,7 @@ bool NativeKey(const KeyInput &key) { } } } + if (key.deviceId == DEVICE_ID_MOUSE) { if (inputMode & InputMode::Mouse) { passKeyThrough = true; @@ -1637,6 +1835,8 @@ void NativeShutdown() { ClearAchievementsHostOverride(); g_nativeMainThreadReady = false; + g_controlMapper.RemoveListener(&g_globalListener); + Achievements::Shutdown(); if (g_Config.bAchievementsEnable) {