From 559a7429eae9f31d582dac38e82702d5dcbdb82d Mon Sep 17 00:00:00 2001 From: KiwiYu <92352309+kiwiyu-thegoat@users.noreply.github.com> Date: Mon, 26 Oct 2026 13:23:39 +0100 Subject: [PATCH] ImDebugger: Add a compact menu layout and a bar left margin The menus are split into lambdas, so they can be shown either side by side or as submenus of a single "Menu" dropdown. The compact layout is used on narrow screens, or when chosen from the Misc menu (the default on phones). The bar also gets an adjustable left margin (to keep the first button clear of rounded screen corners, on by default on phones), slightly taller menu rows in the compact layout, and a Running/Stepping indicator. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/ImDebugger/ImDebugger.cpp | 341 ++++++++++++++++++++++------------- UI/ImDebugger/ImDebugger.h | 4 + 2 files changed, 218 insertions(+), 127 deletions(-) diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 861395d6f7..80bfde030e 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -1,4 +1,5 @@ #include +#include "ppsspp_config.h" #include "ext/imgui/imgui_internal.h" @@ -77,6 +78,13 @@ static ImVec4 g_normalTextColor; +// Phones default to the compact menu, and to a bar left margin to keep the first button clear of rounded screen corners. +#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) +static constexpr bool kIsMobilePlatform = true; +#else +static constexpr bool kIsMobilePlatform = false; +#endif + void ShowInMemoryViewerMenuItem(uint32_t addr, ImControl &control) { if (ImGui::BeginMenu("Show in memory viewer")) { for (int i = 0; i < 4; i++) { @@ -2706,8 +2714,31 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: ImControl control{}; - if (ImGui::BeginMainMenuBar()) { - if (ImGui::BeginMenu("File")) { + // ---- Menu bar ---- + // Compact layout (chosen, or on narrow screens): [Break/Run] [Menu] [Close] [status], where "Menu" is one dropdown + // holding every menu as a cascading submenu. Otherwise the same menus are side by side. + const float em = ImGui::GetFontSize(); + const bool compactMenu = cfg_.compactMenu || ImGui::GetIO().DisplaySize.x < em * 46.0f; + const float rowExtra = compactMenu ? em * 0.2f : 0.0f; // Slightly taller rows for fingers. + + auto beginMenu = [&](const char *label) -> bool { + if (!ImGui::BeginMenu(label)) { + return false; + } + const ImGuiStyle &st = ImGui::GetStyle(); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(st.ItemSpacing.x, st.ItemSpacing.y + rowExtra)); + return true; + }; + auto endMenu = [&]() { + ImGui::PopStyleVar(); // Must be popped before EndMenu(), the popup window checks the style stack. + ImGui::EndMenu(); + }; + auto closeDebugger = [&]() { + g_Config.bShowImDebugger = false; + }; + + auto menuFile = [&]() { + if (beginMenu("File")) { if (ImGui::MenuItem("Load")) { System_BrowseForFile(reqToken_, "Load", BrowseFileType::SAVE_STATE, [](std::string_view responseString, int) { Path path(responseString); @@ -2762,10 +2793,12 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: if (ImGui::MenuItem("Exit")) { System_ExitApp(); } - ImGui::EndMenu(); + endMenu(); } + }; - if (ImGui::BeginMenu("Emulation")) { + auto menuEmulation = [&]() { + if (beginMenu("Emulation")) { if (ImGui::MenuItem("Pause")) { System_PostUIMessage(UIMessage::REQUEST_GAME_PAUSE); } @@ -2776,10 +2809,12 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: System_PostUIMessage(UIMessage::REQUEST_GAME_RESET); } ImGui::Separator(); - ImGui::EndMenu(); + endMenu(); } + }; - if (ImGui::BeginMenu("View")) { + auto menuView = [&]() { + if (beginMenu("View")) { if (PSP_IsInited()) { ImGui::BeginDisabled(); // Can't change this setting while running. } @@ -2787,10 +2822,12 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: if (PSP_IsInited()) { ImGui::EndDisabled(); } - ImGui::EndMenu(); + endMenu(); } + }; - if (ImGui::BeginMenu("Debug")) { + auto menuDebug = [&]() { + if (beginMenu("Debug")) { if (PSP_IsInited()) { switch (coreState) { case CoreState::CORE_STEPPING_CPU: @@ -2833,126 +2870,122 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: } ImGui::Separator(); if (ImGui::MenuItem("Close")) { - g_Config.bShowImDebugger = false; + closeDebugger(); } - ImGui::EndMenu(); + endMenu(); } + }; - /* - if (ImGui::BeginMenu("Options")) { - ImGui::EndMenu(); + auto menuDebuggerWindows = [&]() { + if (beginMenu("Core")) { + ImGui::MenuItem("Scheduler", nullptr, &cfg_.schedulerOpen); + ImGui::MenuItem("Time", nullptr, &cfg_.timeOpen); + endMenu(); } - */ - - if (showDebugger) { - if (ImGui::BeginMenu("Core")) { - ImGui::MenuItem("Scheduler", nullptr, &cfg_.schedulerOpen); - ImGui::MenuItem("Time", nullptr, &cfg_.timeOpen); - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("CPU")) { - ImGui::MenuItem("CPU debugger", nullptr, &cfg_.disasmOpen); - ImGui::MenuItem("GPR regs", nullptr, &cfg_.gprOpen); - ImGui::MenuItem("FPR regs", nullptr, &cfg_.fprOpen); - ImGui::MenuItem("VFPU regs", nullptr, &cfg_.vfpuOpen); - ImGui::Separator(); - ImGui::MenuItem("Callstacks", nullptr, &cfg_.callstackOpen); - ImGui::MenuItem("Breakpoints", nullptr, &cfg_.breakpointsOpen); - ImGui::MenuItem("Watch", nullptr, &cfg_.watchOpen); - ImGui::MenuItem("JIT viewer", nullptr, &cfg_.jitViewerOpen); - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Symbols")) { - ImGui::MenuItem("Symbol browser", nullptr, &cfg_.symbolsOpen); - ImGui::Separator(); - - if (ImGui::MenuItem("Load .ppmap...")) { - System_BrowseForFile(reqToken_, "Load PPSSPP symbol map", BrowseFileType::SYMBOL_MAP, [](std::string_view responseString, int) { - Path path(responseString); - if (!g_symbolMap->LoadSymbolMap(path)) { - ERROR_LOG(Log::Common, "Failed to load symbol map"); - } - }); - } - if (ImGui::MenuItem("Save .ppmap...")) { - System_BrowseForFileSave(reqToken_, "Save PPSSPP symbol map", "symbols.ppmap", BrowseFileType::SYMBOL_MAP, [](std::string_view responseString, int) { - Path path(responseString); - if (!g_symbolMap->SaveSymbolMap(path)) { - ERROR_LOG(Log::Common, "Failed to save symbol map"); - } - }); - } - if (ImGui::MenuItem("Load No$ .sym...")) { - System_BrowseForFile(reqToken_, "Load No$ symbol map", BrowseFileType::SYMBOL_MAP, [](std::string_view responseString, int) { - Path path(responseString); - if (!g_symbolMap->LoadNocashSym(path)) { - ERROR_LOG(Log::Common, "Failed to load No$ symbol map"); - } - }); - } - if (ImGui::MenuItem("Save No$ .sym...")) { - System_BrowseForFileSave(reqToken_, "Save No$ symbol map", "symbols.sym", BrowseFileType::SYMBOL_MAP, [](std::string_view responseString, int) { - Path path(responseString); - if (!g_symbolMap->SaveNocashSym(path)) { - ERROR_LOG(Log::Common, "Failed to save No$ symbol map"); - } - }); - } - ImGui::Separator(); - ImGui::MenuItem("Compress .ppmap files", nullptr, &g_Config.bCompressSymbols); - if (ImGui::MenuItem("Reset symbol map")) { - g_symbolMap->Clear(); - // NotifyDebuggerMapLoaded(); - } - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Memory")) { - for (int i = 0; i < 4; i++) { - char title[64]; - snprintf(title, sizeof(title), "Memory %d", i + 1); - ImGui::MenuItem(title, nullptr, &cfg_.memViewOpen[i]); - } - ImGui::MenuItem("Memory Dumper", nullptr, &cfg_.memDumpOpen); - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("OS HLE")) { - ImGui::MenuItem("HLE module browser", nullptr, &cfg_.hleModulesOpen); - ImGui::MenuItem("File System Browser", nullptr, &cfg_.filesystemBrowserOpen); - ImGui::MenuItem("Kernel Objects", nullptr, &cfg_.kernelObjectsOpen); - ImGui::MenuItem("Threads", nullptr, &cfg_.threadsOpen); - ImGui::MenuItem("Modules", nullptr, &cfg_.modulesOpen); - ImGui::MenuItem("Utility Modules", nullptr, &cfg_.utilityModulesOpen); - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Graphics")) { - ImGui::MenuItem("GE Debugger", nullptr, &cfg_.geDebuggerOpen); - ImGui::MenuItem("GE State", nullptr, &cfg_.geStateOpen); - ImGui::MenuItem("GE Vertices", nullptr, &cfg_.geVertsOpen); - ImGui::MenuItem("Display Output", nullptr, &cfg_.displayOpen); - ImGui::MenuItem("Textures", nullptr, &cfg_.texturesOpen); - ImGui::MenuItem("Framebuffers", nullptr, &cfg_.framebuffersOpen); - ImGui::MenuItem("Pixel Viewer", nullptr, &cfg_.pixelViewerOpen); - // More to come here... - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Audio/Video")) { - ImGui::MenuItem("SasAudio mixer", nullptr, &cfg_.sasAudioOpen); - ImGui::MenuItem("Raw audio channels", nullptr, &cfg_.audioChannelsOpen); - ImGui::MenuItem("AV Decoder contexts", nullptr, &cfg_.mediaDecodersOpen); - ImGui::Separator(); - ImGui::MenuItem("Audio output / buffer", nullptr, &cfg_.audioOutOpen); - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Network")) { - ImGui::MenuItem("ApCtl", nullptr, &cfg_.apctlOpen); - ImGui::MenuItem("Sockets", nullptr, &cfg_.socketsOpen); - ImGui::MenuItem("NP", nullptr, &cfg_.npOpen); - ImGui::MenuItem("AdHoc", nullptr, &cfg_.adhocOpen); - ImGui::EndMenu(); - } + if (beginMenu("CPU")) { + ImGui::MenuItem("CPU debugger", nullptr, &cfg_.disasmOpen); + ImGui::MenuItem("GPR regs", nullptr, &cfg_.gprOpen); + ImGui::MenuItem("FPR regs", nullptr, &cfg_.fprOpen); + ImGui::MenuItem("VFPU regs", nullptr, &cfg_.vfpuOpen); + ImGui::Separator(); + ImGui::MenuItem("Callstacks", nullptr, &cfg_.callstackOpen); + ImGui::MenuItem("Breakpoints", nullptr, &cfg_.breakpointsOpen); + ImGui::MenuItem("Watch", nullptr, &cfg_.watchOpen); + ImGui::MenuItem("JIT viewer", nullptr, &cfg_.jitViewerOpen); + endMenu(); } + if (beginMenu("Symbols")) { + ImGui::MenuItem("Symbol browser", nullptr, &cfg_.symbolsOpen); + ImGui::Separator(); - if (ImGui::BeginMenu("Tools")) { + if (ImGui::MenuItem("Load .ppmap...")) { + System_BrowseForFile(reqToken_, "Load PPSSPP symbol map", BrowseFileType::SYMBOL_MAP, [](std::string_view responseString, int) { + Path path(responseString); + if (!g_symbolMap->LoadSymbolMap(path)) { + ERROR_LOG(Log::Common, "Failed to load symbol map"); + } + }); + } + if (ImGui::MenuItem("Save .ppmap...")) { + System_BrowseForFileSave(reqToken_, "Save PPSSPP symbol map", "symbols.ppmap", BrowseFileType::SYMBOL_MAP, [](std::string_view responseString, int) { + Path path(responseString); + if (!g_symbolMap->SaveSymbolMap(path)) { + ERROR_LOG(Log::Common, "Failed to save symbol map"); + } + }); + } + if (ImGui::MenuItem("Load No$ .sym...")) { + System_BrowseForFile(reqToken_, "Load No$ symbol map", BrowseFileType::SYMBOL_MAP, [](std::string_view responseString, int) { + Path path(responseString); + if (!g_symbolMap->LoadNocashSym(path)) { + ERROR_LOG(Log::Common, "Failed to load No$ symbol map"); + } + }); + } + if (ImGui::MenuItem("Save No$ .sym...")) { + System_BrowseForFileSave(reqToken_, "Save No$ symbol map", "symbols.sym", BrowseFileType::SYMBOL_MAP, [](std::string_view responseString, int) { + Path path(responseString); + if (!g_symbolMap->SaveNocashSym(path)) { + ERROR_LOG(Log::Common, "Failed to save No$ symbol map"); + } + }); + } + ImGui::Separator(); + ImGui::MenuItem("Compress .ppmap files", nullptr, &g_Config.bCompressSymbols); + if (ImGui::MenuItem("Reset symbol map")) { + g_symbolMap->Clear(); + // NotifyDebuggerMapLoaded(); + } + endMenu(); + } + if (beginMenu("Memory")) { + for (int i = 0; i < 4; i++) { + char title[64]; + snprintf(title, sizeof(title), "Memory %d", i + 1); + ImGui::MenuItem(title, nullptr, &cfg_.memViewOpen[i]); + } + ImGui::MenuItem("Memory Dumper", nullptr, &cfg_.memDumpOpen); + endMenu(); + } + if (beginMenu("OS HLE")) { + ImGui::MenuItem("HLE module browser", nullptr, &cfg_.hleModulesOpen); + ImGui::MenuItem("File System Browser", nullptr, &cfg_.filesystemBrowserOpen); + ImGui::MenuItem("Kernel Objects", nullptr, &cfg_.kernelObjectsOpen); + ImGui::MenuItem("Threads", nullptr, &cfg_.threadsOpen); + ImGui::MenuItem("Modules", nullptr, &cfg_.modulesOpen); + ImGui::MenuItem("Utility Modules", nullptr, &cfg_.utilityModulesOpen); + endMenu(); + } + if (beginMenu("Graphics")) { + ImGui::MenuItem("GE Debugger", nullptr, &cfg_.geDebuggerOpen); + ImGui::MenuItem("GE State", nullptr, &cfg_.geStateOpen); + ImGui::MenuItem("GE Vertices", nullptr, &cfg_.geVertsOpen); + ImGui::MenuItem("Display Output", nullptr, &cfg_.displayOpen); + ImGui::MenuItem("Textures", nullptr, &cfg_.texturesOpen); + ImGui::MenuItem("Framebuffers", nullptr, &cfg_.framebuffersOpen); + ImGui::MenuItem("Pixel Viewer", nullptr, &cfg_.pixelViewerOpen); + // More to come here... + endMenu(); + } + if (beginMenu("Audio/Video")) { + ImGui::MenuItem("SasAudio mixer", nullptr, &cfg_.sasAudioOpen); + ImGui::MenuItem("Raw audio channels", nullptr, &cfg_.audioChannelsOpen); + ImGui::MenuItem("AV Decoder contexts", nullptr, &cfg_.mediaDecodersOpen); + ImGui::Separator(); + ImGui::MenuItem("Audio output / buffer", nullptr, &cfg_.audioOutOpen); + endMenu(); + } + if (beginMenu("Network")) { + ImGui::MenuItem("ApCtl", nullptr, &cfg_.apctlOpen); + ImGui::MenuItem("Sockets", nullptr, &cfg_.socketsOpen); + ImGui::MenuItem("NP", nullptr, &cfg_.npOpen); + ImGui::MenuItem("AdHoc", nullptr, &cfg_.adhocOpen); + endMenu(); + } + }; + + auto menuTools = [&]() { + if (beginMenu("Tools")) { ImGui::MenuItem("Lua Console", nullptr, &cfg_.luaConsoleOpen); ImGui::MenuItem("Log", nullptr, &cfg_.logOpen); ImGui::MenuItem("Log channels", nullptr, &cfg_.logConfigOpen); @@ -2963,18 +2996,27 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: } ImGui::MenuItem("Struct viewer", nullptr, &cfg_.structViewerOpen); ImGui::MenuItem("Atrac Tool", nullptr, &cfg_.atracToolOpen); - ImGui::EndMenu(); + endMenu(); } + }; - if (ImGui::BeginMenu("Misc")) { + auto menuMisc = [&]() { + if (beginMenu("Misc")) { ImGui::MenuItem("PPSSPP Internals", nullptr, &cfg_.internalsOpen); ImGui::MenuItem("Dear ImGui Demo", nullptr, &cfg_.demoOpen); ImGui::MenuItem("Dear ImGui Style editor", nullptr, &cfg_.styleEditorOpen); - ImGui::EndMenu(); + ImGui::Separator(); + ImGui::MenuItem("Compact menu", nullptr, &cfg_.compactMenu); + ImGui::SetNextItemWidth(em * 10.0f); + ImGui::SliderInt("Bar left margin", &cfg_.barLeftMargin, 0, 600, "%d%%"); // Percent of the text height. + endMenu(); } + }; - if (ImGui::MenuItem("Close")) { - g_Config.bShowImDebugger = false; + if (ImGui::BeginMainMenuBar()) { + // Keep the first button clear of rounded screen corners / camera cutouts. + if (cfg_.barLeftMargin > 0) { + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + em * (float)cfg_.barLeftMargin / 100.0f); } switch (coreState) { case CoreState::CORE_STEPPING_CPU: @@ -2990,6 +3032,49 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: default: break; } + + if (compactMenu) { + if (beginMenu("Menu")) { + menuFile(); + menuEmulation(); + menuView(); + menuDebug(); + if (showDebugger) { + menuDebuggerWindows(); + } + menuTools(); + menuMisc(); + endMenu(); + } + } else { + menuFile(); + menuEmulation(); + menuView(); + menuDebug(); + if (showDebugger) { + menuDebuggerWindows(); + } + menuTools(); + menuMisc(); + } + + if (ImGui::MenuItem("Close")) { + closeDebugger(); + } + + // Core state indicator. + ImGui::AlignTextToFramePadding(); + switch (coreState) { + case CoreState::CORE_RUNNING_CPU: + ImGui::TextColored(ImVec4(0.30f, 0.90f, 0.30f, 1.0f), "Running"); + break; + case CoreState::CORE_STEPPING_CPU: + ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.20f, 1.0f), "Stepping"); + break; + default: + break; + } + ImGui::EndMainMenuBar(); } @@ -3340,4 +3425,6 @@ void ImConfig::SyncConfig(IniFile *ini, bool save) { sync.SetSection(ini->GetOrCreateSection("Settings")); sync.Sync("displayLatched", &displayLatched, false); sync.Sync("realtimePixelPreview", &realtimePixelPreview, false); + sync.Sync("compactMenu", &compactMenu, kIsMobilePlatform); + sync.Sync("barLeftMargin", &barLeftMargin, kIsMobilePlatform ? 200 : 0); } diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h index caf982a6b9..8f62d4a0ef 100644 --- a/UI/ImDebugger/ImDebugger.h +++ b/UI/ImDebugger/ImDebugger.h @@ -131,6 +131,10 @@ struct ImConfig { float fbViewerZoom = 1.0f; + // Touch / mobile helpers. + int barLeftMargin = 0; // Empty space left of the first bar button, in percent of the text height (keeps it clear of rounded screen corners). + bool compactMenu = false; // Always use the single "Menu" dropdown (it's also chosen automatically on narrow screens). + // We use a separate ini file from the main PPSSPP config. void LoadConfig(const Path &iniFile);