From e5ca5af3f23701cae41d7662b0d2f1f9e85a43a0 Mon Sep 17 00:00:00 2001 From: TakaRikka Date: Sun, 15 Mar 2026 15:54:03 -0700 Subject: [PATCH] work on controller config --- src/dusk/imgui/ImGuiMenuGame.cpp | 344 +++++++++++++++++++++++++++++- src/dusk/imgui/ImGuiMenuGame.hpp | 8 + src/dusk/imgui/ImGuiMenuTools.cpp | 17 +- src/dusk/imgui/ImGuiMenuTools.hpp | 2 + 4 files changed, 368 insertions(+), 3 deletions(-) diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index a92db0cdd1..c0b750c982 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -4,8 +4,10 @@ #include "ImGuiConsole.hpp" #include "ImGuiMenuGame.hpp" +#include #include "JSystem/JUtility/JUTGamePad.h" +#include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_audio.h" namespace dusk { @@ -65,23 +67,363 @@ namespace dusk { } } + // from https://github.com/ocornut/imgui/issues/1496#issuecomment-569892444 + void ImGuiBeginGroupPanel(const char* name, const ImVec2& size) { + ImGui::BeginGroup(); + + auto cursorPos = ImGui::GetCursorScreenPos(); + auto itemSpacing = ImGui::GetStyle().ItemSpacing; + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + + auto frameHeight = ImGui::GetFrameHeight(); + ImGui::BeginGroup(); + + ImVec2 effectiveSize = size; + if (size.x < 0.0f) + effectiveSize.x = ImGui::GetContentRegionAvail().x; + else + effectiveSize.x = size.x; + ImGui::Dummy(ImVec2(effectiveSize.x, 0.0f)); + + ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f)); + ImGui::SameLine(0.0f, 0.0f); + ImGui::BeginGroup(); + ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f)); + ImGui::SameLine(0.0f, 0.0f); + ImGui::TextUnformatted(name); + ImGui::SameLine(0.0f, 0.0f); + ImGui::Dummy(ImVec2(0.0, frameHeight + itemSpacing.y)); + ImGui::BeginGroup(); + + ImGui::PopStyleVar(2); + + ImGui::GetCurrentWindow()->ContentRegionRect.Max.x -= frameHeight * 0.5f; + ImGui::GetCurrentWindow()->WorkRect.Max.x -= frameHeight * 0.5f; + ImGui::GetCurrentWindow()->Size.x -= frameHeight; + + ImGui::PushItemWidth(effectiveSize.x - frameHeight); + } + + // from https://github.com/ocornut/imgui/issues/1496#issuecomment-569892444 + void ImGuiEndGroupPanel() { + ImGui::PopItemWidth(); + + auto itemSpacing = ImGui::GetStyle().ItemSpacing; + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + + auto frameHeight = ImGui::GetFrameHeight(); + + // workaround for incorrect capture of columns/table width by placing + // zero-sized dummy element in the same group, this ensure + // max X cursor position is updated correctly + ImGui::SameLine(0.0f, 0.0f); + ImGui::Dummy(ImVec2(0.0f, 0.0f)); + + ImGui::EndGroup(); + ImGui::EndGroup(); + + ImGui::SameLine(0.0f, 0.0f); + ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f)); + ImGui::Dummy(ImVec2(0.0, frameHeight - frameHeight * 0.5f - itemSpacing.y)); + + ImGui::EndGroup(); + + auto itemMin = ImGui::GetItemRectMin(); + auto itemMax = ImGui::GetItemRectMax(); + + ImVec2 halfFrame = ImVec2((frameHeight * 0.25f) * 0.5f, frameHeight * 0.5f); + ImGui::GetWindowDrawList()->AddRect( + ImVec2(itemMin.x + halfFrame.x, itemMin.y + halfFrame.y), + ImVec2(itemMax.x - halfFrame.x, itemMax.y), + ImColor(ImGui::GetStyleColorVec4(ImGuiCol_Border)), + halfFrame.x); + + ImGui::PopStyleVar(2); + + ImGui::GetCurrentWindow()->ContentRegionRect.Max.x += frameHeight * 0.5f; + ImGui::GetCurrentWindow()->WorkRect.Max.x += frameHeight * 0.5f; + ImGui::GetCurrentWindow()->Size.x += frameHeight; + + ImGui::Dummy(ImVec2(0.0f, 0.0f)); + + ImGui::EndGroup(); + } + + static void drawVirtualStick(const char* id, const ImVec2& stick) { + ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 5, ImGui::GetCursorPos().y)); + + ImGui::BeginChild(id, ImVec2(80, 80)); + ImDrawList* dl = ImGui::GetWindowDrawList(); + ImVec2 p = ImGui::GetCursorScreenPos(); + + float radius = ImGui::GetCurrentContext()->CurrentDpiScale * 30.0f; + ImVec2 pos = ImVec2(p.x + radius, p.y + radius); + + constexpr ImU32 stickGray = IM_COL32(150, 150, 150, 255); + constexpr ImU32 white = IM_COL32(255, 255, 255, 255); + constexpr ImU32 red = IM_COL32(230, 0, 0, 255); + + dl->AddCircleFilled(pos, radius, stickGray, 8); + dl->AddCircleFilled(ImVec2(pos.x + stick.x * (radius), pos.y + -stick.y * (radius)), 3, red); + ImGui::EndChild(); + } + void ImGuiMenuGame::windowControllerConfig() { if (!m_showControllerConfig) { return; } + // if pending for an input mapping, check to set new input + if (m_controllerConfig.m_pendingMapping != nullptr) { + s32 nativeButton = PADGetNativeButtonPressed(m_controllerConfig.m_pendingPort); + if (nativeButton != -1) { + m_controllerConfig.m_pendingMapping->nativeButton = nativeButton; + m_controllerConfig.m_pendingMapping = nullptr; + m_controllerConfig.m_pendingPort = -1; + PADBlockInput(false); + } + } + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize; ImGui::SetNextWindowBgAlpha(0.65f); - ImGui::SetNextWindowSizeConstraints(ImVec2(500, 200), ImVec2(1000, 200)); + ImGui::SetNextWindowSizeConstraints(ImVec2(850, 400), ImVec2(850, 400)); if (!ImGui::Begin("Controller Config", nullptr, windowFlags)) { ImGui::End(); return; } + // port tabs + ImGui::BeginTabBar("##ControllerTabs"); + for (int i = PAD_1; i <= PAD_4; i++) { + if (ImGui::BeginTabItem(fmt::format("Port {}", i + 1).c_str())) { + m_controllerConfig.m_selectedPort = i; + ImGui::EndTabItem(); + } + } + ImGui::EndTabBar(); + + // if tab is changed while waiting for input, cancel pending + if (m_controllerConfig.m_pendingMapping != nullptr && + m_controllerConfig.m_pendingPort != m_controllerConfig.m_selectedPort) + { + m_controllerConfig.m_pendingMapping = nullptr; + m_controllerConfig.m_pendingPort = -1; + PADBlockInput(false); + } + + // get a list of all available controller's names + std::vector controllerList; + controllerList.push_back("None"); + for (int i = 0; i < PADCount(); i++) { + // attach index to name for unique name + controllerList.push_back(fmt::format("{}-{}", PADGetNameForControllerIndex(i), i)); + } + + // get current controller name + const char* tmpName = PADGetName(m_controllerConfig.m_selectedPort); + std::string currentName = "None"; + if (tmpName != nullptr) { + currentName = fmt::format("{}-{}", tmpName, PADGetIndexForPort(m_controllerConfig.m_selectedPort)); + } + + // controller selection combo box + bool changedController = false; + int changedControllerIndex = 0; + if (ImGui::BeginCombo("##ControllerDeviceList", currentName.c_str())) { + for (int i = 0; const auto& name : controllerList) { + if (ImGui::Selectable(name.c_str(), currentName == name)) { + changedControllerIndex = i; + changedController = true; + } + i++; + } + ImGui::EndCombo(); + } + + // handle controller change + if (changedController) { + if (changedControllerIndex > 0) { + PADSetPortForIndex(changedControllerIndex - 1, m_controllerConfig.m_selectedPort); + } + else if (changedControllerIndex == 0) { + // if "None" selected + PADClearPort(m_controllerConfig.m_selectedPort); + } + } + + // save mappings button + ImGui::SameLine(); + if (ImGui::Button("Save")) { + PADSerializeMappings(); + } + + // restore defaults button + ImGui::SameLine(); + if (ImGui::Button("Default")) { + PADRestoreDefaultMapping(m_controllerConfig.m_selectedPort); + } + + // buttons panel + constexpr float buttonSize = 40; + + ImGuiBeginGroupPanel("Buttons", ImVec2(150, 20)); + + u32 buttonCount; + PADButtonMapping* mappingList = PADGetButtonMappings(m_controllerConfig.m_selectedPort, &buttonCount); + if (mappingList != nullptr) { + for (int i = 0; i < buttonCount; i++) { + const char* btnName = PADGetButtonName(mappingList[i].padButton); + ImVec2 len = ImGui::CalcTextSize(btnName); + ImVec2 pos = ImGui::GetCursorPos(); + + ImGui::SetCursorPosY(pos.y + len.y / 4); + ImGui::SetCursorPosX(pos.x + abs(len.x - buttonSize)); + ImGui::Text("%s", btnName); + ImGui::SameLine(); + + ImGui::SetCursorPosY(pos.y); + + bool pressed = ImGui::Button(m_controllerConfig.m_isReading && m_controllerConfig.m_pendingMapping == &mappingList[i] + ? fmt::format("Press a Key...##{}", btnName).c_str() + : fmt::format("{0}##-{1}", PADGetNativeButtonName(mappingList[i].nativeButton), i).c_str(), + ImVec2(100.0f, 20.0f)); + + if (pressed) { + m_controllerConfig.m_isReading = true; + m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort; + m_controllerConfig.m_pendingMapping = &mappingList[i]; + PADBlockInput(true); + } + } + } + + ImGuiEndGroupPanel(); + ImGui::SameLine(); + + int port = m_controllerConfig.m_selectedPort; + + const char* stickDirections[] = { + "Up", + "Down", + "Left", + "Right", + }; + + // main stick panel + ImGuiBeginGroupPanel("Control Stick", ImVec2(150, 20)); + + drawVirtualStick("##mainStick", ImVec2{ mDoCPd_c::getStickX(port), mDoCPd_c::getStickY(port) }); + + { + for (int i = 0; i < 4; i++) { + const char* label = stickDirections[i]; + ImVec2 len = ImGui::CalcTextSize(label); + ImVec2 pos = ImGui::GetCursorPos(); + + ImGui::SetCursorPosY(pos.y + len.y / 4); + ImGui::SetCursorPosX(pos.x + abs(len.x - buttonSize)); + ImGui::Text("%s", label); + ImGui::SameLine(); + + ImGui::SetCursorPosY(pos.y); + + bool pressed = ImGui::Button(fmt::format("Temp##{}", label).c_str(), ImVec2(100.0f, 20.0f)); + } + } + + PADDeadZones* deadZones = PADGetDeadZones(port); + + if (deadZones != nullptr) { + ImGui::Text("Dead Zone"); + { + float tmp = static_cast(deadZones->stickDeadZone * 100.f) / 32767.f; + if (ImGui::DragFloat("##mainDeadZone", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { + deadZones->stickDeadZone = static_cast((tmp / 100.f) * 32767); + } + } + } + + ImGuiEndGroupPanel(); + ImGui::SameLine(); + + // sub stick panel + ImGuiBeginGroupPanel("C Stick", ImVec2(150, 20)); + + drawVirtualStick("##subStick", ImVec2{ mDoCPd_c::getSubStickX(port), mDoCPd_c::getSubStickY(port) }); + + { + for (int i = 0; i < 4; i++) { + const char* label = stickDirections[i]; + ImVec2 len = ImGui::CalcTextSize(label); + ImVec2 pos = ImGui::GetCursorPos(); + + ImGui::SetCursorPosY(pos.y + len.y / 4); + ImGui::SetCursorPosX(pos.x + abs(len.x - buttonSize)); + ImGui::Text("%s", label); + ImGui::SameLine(); + + ImGui::SetCursorPosY(pos.y); + + bool pressed = ImGui::Button(fmt::format("Temp##sub{}", label).c_str(), ImVec2(100.0f, 20.0f)); + } + } + + if (deadZones != nullptr) { + ImGui::Text("Dead Zone"); + { + float tmp = static_cast(deadZones->substickDeadZone * 100.f) / 32767.f; + if (ImGui::DragFloat("##subDeadZone", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { + deadZones->substickDeadZone = static_cast((tmp / 100.f) * 32767); + } + } + } + + ImGuiEndGroupPanel(); + ImGui::SameLine(); + + // Triggers Panel + ImGuiBeginGroupPanel("Triggers", ImVec2(150, 20)); + + if (deadZones != nullptr) { + ImGui::Text("L Threshold"); + { + float tmp = static_cast(deadZones->leftTriggerActivationZone * 100.f) / 32767.f; + if (ImGui::DragFloat("##LThreshold", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { + deadZones->leftTriggerActivationZone = static_cast((tmp / 100.f) * 32767); + } + } + } + + if (deadZones != nullptr) { + ImGui::Text("R Threshold"); + { + float tmp = static_cast(deadZones->rightTriggerActivationZone * 100.f) / 32767.f; + if (ImGui::DragFloat("##RThreshold", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { + deadZones->rightTriggerActivationZone = static_cast((tmp / 100.f) * 32767); + } + } + } + + ImGuiEndGroupPanel(); + ImGui::SameLine(); + + // Options panel + ImGuiBeginGroupPanel("Options", ImVec2(150, 20)); + + if (deadZones != nullptr) { + ImGui::Checkbox("Enable Dead Zones", &deadZones->useDeadzones); + ImGui::Checkbox("Emulate Triggers", &deadZones->emulateTriggers); + } + + ImGuiEndGroupPanel(); + ImGui::End(); } } \ No newline at end of file diff --git a/src/dusk/imgui/ImGuiMenuGame.hpp b/src/dusk/imgui/ImGuiMenuGame.hpp index 3b09a62da3..7945250608 100644 --- a/src/dusk/imgui/ImGuiMenuGame.hpp +++ b/src/dusk/imgui/ImGuiMenuGame.hpp @@ -2,6 +2,7 @@ #define DUSK_IMGUI_MENUGAME_HPP #include +#include #include #include "imgui.h" @@ -24,6 +25,13 @@ namespace dusk { float m_fanfareVolume = 1.0f; } m_audioSettings; + struct { + int m_selectedPort = 0; + bool m_isReading = false; + PADButtonMapping* m_pendingMapping = nullptr; + int m_pendingPort = -1; + } m_controllerConfig; + bool m_showControllerConfig = false; bool m_showInputViewer = false; diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 64afcc2ddf..9df65e1c2c 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -5,20 +5,33 @@ #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" +#include "m_Do/m_Do_main.h" + namespace dusk { ImGuiMenuTools::ImGuiMenuTools() {} void ImGuiMenuTools::draw() { + bool isToggleDevelopmentMode = false; + if (ImGui::BeginMenu("Tools")) { + if (ImGui::Checkbox("Development Mode", &m_isDevelopmentMode)) { + isToggleDevelopmentMode = true; + } + ImGui::Separator(); + ImGui::MenuItem("Process Management", "F2", &m_showProcessManagement); ImGui::MenuItem("Debug Overlay", "F3", &m_showDebugOverlay); - ImGui::MenuItem("Heaps", "F4", &m_showHeapOverlay); + ImGui::MenuItem("Heap Viewer", "F4", &m_showHeapOverlay); ImGui::MenuItem("Stub Log", "F5", &m_showStubLog); - ImGui::MenuItem("Camera", "F6", &m_showCameraOverlay); + ImGui::MenuItem("Debug Camera", "F6", &m_showCameraOverlay); ImGui::MenuItem("Map Loader", nullptr, &m_showMapLoader); ImGui::EndMenu(); } + if (isToggleDevelopmentMode) { + mDoMain::developmentMode = m_isDevelopmentMode ? 1 : -1; + } + ShowDebugOverlay(); ShowCameraOverlay(); ShowProcessManager(); diff --git a/src/dusk/imgui/ImGuiMenuTools.hpp b/src/dusk/imgui/ImGuiMenuTools.hpp index 1a2e5c705f..885c948f82 100644 --- a/src/dusk/imgui/ImGuiMenuTools.hpp +++ b/src/dusk/imgui/ImGuiMenuTools.hpp @@ -44,6 +44,8 @@ namespace dusk { int layer = -1; bool showInternalNames = false; } m_mapLoaderInfo; + + bool m_isDevelopmentMode = false; }; }