From 73eb401c93585453f6f2eb41d51e4fceebbf42da Mon Sep 17 00:00:00 2001 From: Irastris Date: Thu, 7 May 2026 12:00:35 -0400 Subject: [PATCH] Add remaining controller config options, remove ImGui config --- src/dusk/imgui/ImGuiConsole.cpp | 5 +- src/dusk/imgui/ImGuiMenuGame.cpp | 552 ------------------------------ src/dusk/imgui/ImGuiMenuGame.hpp | 3 - src/dusk/ui/controller_config.cpp | 180 +++++++++- src/dusk/ui/controller_config.hpp | 4 + 5 files changed, 181 insertions(+), 563 deletions(-) diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 09d1ca6e81..1c5d525f44 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -261,7 +261,9 @@ namespace dusk { } if (ImGui::IsKeyPressed(ImGuiKey_F11)) { - ImGuiMenuGame::ToggleFullscreen(); + getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen); + VISetWindowFullscreen(getSettings().video.enableFullscreen); + config::Save(); } if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) { @@ -360,7 +362,6 @@ namespace dusk { ImGui::End(); } - m_menuGame.windowControllerConfig(); m_menuGame.windowInputViewer(); m_menuGame.drawSpeedrunTimerOverlay(); diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 616e0ddbac..99685ce5d4 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -1,574 +1,22 @@ #include "fmt/format.h" #include "imgui.h" -#include "ImGuiEngine.hpp" #include "ImGuiConsole.hpp" #include "ImGuiConfig.hpp" #include "dusk/main.h" -#include "dusk/hotkeys.h" #include "m_Do/m_Do_main.h" -#include - namespace dusk { - void ImGuiMenuGame::ToggleFullscreen() { - getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen); - VISetWindowFullscreen(getSettings().video.enableFullscreen); - config::Save(); - } - ImGuiMenuGame::ImGuiMenuGame() {} void ImGuiMenuGame::draw() { if (ImGui::BeginMenu("Settings")) { - // TODO: Remove this once Controller Config exists in RmlUi - if (ImGui::Button("Configure Controller")){ - m_showControllerConfig = !m_showControllerConfig; - } - ImGui::Checkbox("Show Input Viewer", &m_showInputViewer); - ImGui::EndMenu(); } } - static void drawVirtualStick(const char* id, const ImVec2& stick) { - float scale = ImGuiScale(); - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 45 * scale, ImGui::GetCursorPos().y + 10)); - - ImGui::BeginChild(id, ImVec2(80 * scale, 80 * scale), 0, ImGuiWindowFlags_NoBackground); - ImDrawList* dl = ImGui::GetWindowDrawList(); - ImVec2 p = ImGui::GetCursorScreenPos(); - - float radius = 30.0f * scale; - 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 * scale, red); - ImGui::EndChild(); - } - - struct SpecificButtonName { - SDL_GamepadType Type; - const char* Name; - }; - - struct ButtonNames { - SDL_GamepadButton Button; - std::vector Names; - }; - -// clang-format off - static const std::vector GamepadButtonNames = { - { SDL_GAMEPAD_BUTTON_LEFT_STICK, { - {SDL_GAMEPAD_TYPE_PS3, "L3"}, - {SDL_GAMEPAD_TYPE_PS4, "L3"}, - {SDL_GAMEPAD_TYPE_PS5, "L3"}, - {SDL_GAMEPAD_TYPE_XBOX360, "Left Stick"}, - {SDL_GAMEPAD_TYPE_XBOXONE, "Left Stick"}, - {SDL_GAMEPAD_TYPE_GAMECUBE, "Control Stick"}, - }}, - { SDL_GAMEPAD_BUTTON_RIGHT_STICK, { - {SDL_GAMEPAD_TYPE_PS3, "R3"}, - {SDL_GAMEPAD_TYPE_PS4, "R3"}, - {SDL_GAMEPAD_TYPE_PS5, "R3"}, - {SDL_GAMEPAD_TYPE_XBOX360, "Right Stick"}, - {SDL_GAMEPAD_TYPE_XBOXONE, "Right Stick"}, - {SDL_GAMEPAD_TYPE_GAMECUBE, "C Stick"}, - }}, - { SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, { - {SDL_GAMEPAD_TYPE_PS3, "L1"}, - {SDL_GAMEPAD_TYPE_PS4, "L1"}, - {SDL_GAMEPAD_TYPE_PS5, "L1"}, - {SDL_GAMEPAD_TYPE_XBOX360, "LB"}, - {SDL_GAMEPAD_TYPE_XBOXONE, "LB"}, - }}, - { SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, { - {SDL_GAMEPAD_TYPE_PS3, "R1"}, - {SDL_GAMEPAD_TYPE_PS4, "R1"}, - {SDL_GAMEPAD_TYPE_PS5, "R1"}, - {SDL_GAMEPAD_TYPE_XBOX360, "RB"}, - {SDL_GAMEPAD_TYPE_XBOXONE, "RB"}, - {SDL_GAMEPAD_TYPE_GAMECUBE, "Z"}, - }}, - { SDL_GAMEPAD_BUTTON_BACK, { - {SDL_GAMEPAD_TYPE_PS3, "Select"}, - {SDL_GAMEPAD_TYPE_PS4, "Share"}, - {SDL_GAMEPAD_TYPE_PS5, "Create"}, - {SDL_GAMEPAD_TYPE_XBOX360, "Back"}, - {SDL_GAMEPAD_TYPE_XBOXONE, "View"}, - }}, - { SDL_GAMEPAD_BUTTON_START, { - {SDL_GAMEPAD_TYPE_PS3, "Start"}, - {SDL_GAMEPAD_TYPE_PS4, "Options"}, - {SDL_GAMEPAD_TYPE_PS5, "Options"}, - {SDL_GAMEPAD_TYPE_XBOX360, "Start"}, - {SDL_GAMEPAD_TYPE_XBOXONE, "Menu"}, - {SDL_GAMEPAD_TYPE_GAMECUBE, "Start/Pause"}, - }}, - }; -// clang-format on - - static const char* GetNameForGamepadButton(SDL_Gamepad* gamepad, u32 buttonUntyped) { - if (buttonUntyped == PAD_NATIVE_BUTTON_INVALID) { - return "Not bound"; - } - - auto button = static_cast(buttonUntyped); - auto label = SDL_GetGamepadButtonLabel(gamepad, button); - - switch (label) { - case SDL_GAMEPAD_BUTTON_LABEL_A: - return "A"; - case SDL_GAMEPAD_BUTTON_LABEL_B: - return "B"; - case SDL_GAMEPAD_BUTTON_LABEL_X: - return "X"; - case SDL_GAMEPAD_BUTTON_LABEL_Y: - return "Y"; - case SDL_GAMEPAD_BUTTON_LABEL_CROSS: - return "Cross"; - case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: - return "Circle"; - case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE: - return "Triangle"; - case SDL_GAMEPAD_BUTTON_LABEL_SQUARE: - return "Square"; - default:; // Fall through - } - - auto padType = SDL_GetGamepadType(gamepad); - for (const auto& buttonNames : GamepadButtonNames) { - if (buttonNames.Button != button) { - continue; - } - - for (const auto& name : buttonNames.Names) { - if (name.Type == padType) { - return name.Name; - } - } - } - - switch (button) { - case SDL_GAMEPAD_BUTTON_DPAD_LEFT: - return "D-pad left"; - case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: - return "D-pad right"; - case SDL_GAMEPAD_BUTTON_DPAD_UP: - return "D-pad up"; - case SDL_GAMEPAD_BUTTON_DPAD_DOWN: - return "D-pad down"; - default: - return PADGetNativeButtonName(buttonUntyped); - } - } - - void ImGuiMenuGame::windowControllerConfig() { - if (!m_showControllerConfig) { - return; - } - - // if pending for a button mapping, check to set new input - if (m_controllerConfig.m_pendingButtonMapping != nullptr) { - s32 nativeButton = PADGetNativeButtonPressed(m_controllerConfig.m_pendingPort); - if (nativeButton != -1) { - m_controllerConfig.m_pendingButtonMapping->nativeButton = nativeButton; - m_controllerConfig.m_pendingButtonMapping = nullptr; - m_controllerConfig.m_pendingPort = -1; - PADBlockInput(false); - PADSerializeMappings(); - } - } - - // if pending for an axis mapping, check to set new input - if (m_controllerConfig.m_pendingAxisMapping != nullptr) { - auto nativeAxis = PADGetNativeAxisPulled(m_controllerConfig.m_pendingPort); - if (nativeAxis.nativeAxis != -1) { - m_controllerConfig.m_pendingAxisMapping->nativeAxis = nativeAxis; - m_controllerConfig.m_pendingAxisMapping->nativeButton = -1; - m_controllerConfig.m_pendingAxisMapping = nullptr; - m_controllerConfig.m_pendingPort = -1; - PADBlockInput(false); - PADSerializeMappings(); - } else { - auto nativeButton = PADGetNativeButtonPressed(m_controllerConfig.m_pendingPort); - if (nativeButton != -1) { - m_controllerConfig.m_pendingAxisMapping->nativeAxis = {-1, AXIS_SIGN_POSITIVE}; - m_controllerConfig.m_pendingAxisMapping->nativeButton = nativeButton; - m_controllerConfig.m_pendingAxisMapping = nullptr; - m_controllerConfig.m_pendingPort = -1; - PADBlockInput(false); - PADSerializeMappings(); - } - } - } - - float scale = ImGuiScale(); - ImGuiWindowFlags windowFlags = - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_AlwaysAutoResize; - - // ImGui::SetNextWindowBgAlpha(0.65f); - - if (!ImGui::Begin("Controller Config", &m_showControllerConfig, 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_pendingButtonMapping != nullptr || - m_controllerConfig.m_pendingAxisMapping != nullptr) && - m_controllerConfig.m_pendingPort != m_controllerConfig.m_selectedPort) - { - m_controllerConfig.m_pendingButtonMapping = nullptr; - m_controllerConfig.m_pendingAxisMapping = 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; - ImGui::SetNextItemWidth(400.0f * scale); - 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); - } - PADSerializeMappings(); - } - - // restore defaults button - ImGui::SameLine(); - if (ImGui::Button("Default")) { - PADRestoreDefaultMapping(m_controllerConfig.m_selectedPort); - PADSerializeMappings(); - } - - // buttons panel - const float uiButtonSize = 40 * scale; - ImVec2 btnSize(110.0f * scale, 30.0f * scale); - - ImGuiBeginGroupPanel("Buttons", ImVec2(150 * scale, 20 * scale)); - - SDL_Gamepad* gamepad = PADGetSDLGamepadForIndex(PADGetIndexForPort(m_controllerConfig.m_selectedPort)); - u32 buttonCount; - PADButtonMapping* btnMappingList = PADGetButtonMappings(m_controllerConfig.m_selectedPort, &buttonCount); - if (btnMappingList != nullptr) { - for (int i = 0; i < buttonCount; i++) { - const char* btnName = PADGetButtonName(btnMappingList[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 - uiButtonSize)); - ImGui::Text("%s", btnName); - ImGui::SameLine(); - - ImGui::SetCursorPosY(pos.y); - - std::string dispName; - if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingButtonMapping == &btnMappingList[i]) { - dispName = fmt::format("Press a Key...##{}", btnName); - } else { - const char* nativeName = GetNameForGamepadButton(gamepad, btnMappingList[i].nativeButton); - if (nativeName == nullptr) { - nativeName = "[unbound]"; - } - dispName = fmt::format("{0}##-{1}", nativeName, i); - } - bool pressed = ImGui::Button(dispName.c_str(), - btnSize); - - if (pressed) { - m_controllerConfig.m_isReading = true; - m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort; - m_controllerConfig.m_pendingButtonMapping = &btnMappingList[i]; - PADBlockInput(true); - } - } - } - - ImGuiEndGroupPanel(); - ImGui::SameLine(); - - uint32_t axisCount; - PADAxisMapping* axisMappingList = PADGetAxisMappings(m_controllerConfig.m_selectedPort, &axisCount); - - ImGuiBeginGroupPanel("Analog Triggers", ImVec2(150 * scale, 20 * scale)); - - PADAxis triggers[] = {PAD_AXIS_TRIGGER_L, PAD_AXIS_TRIGGER_R}; - if (axisMappingList != nullptr) { - for (PADAxis trigger : triggers) { - const char* axisName = PADGetAxisName(axisMappingList[trigger].padAxis); - ImVec2 len = ImGui::CalcTextSize(axisName); - ImVec2 pos = ImGui::GetCursorPos(); - - ImGui::SetCursorPosY(pos.y + len.y / 4); - ImGui::SetCursorPosX(pos.x + abs(len.x - uiButtonSize)); - ImGui::Text("%s", axisName); - ImGui::SameLine(); - - ImGui::SetCursorPosY(pos.y); - - std::string dispName; - if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[trigger]) { - dispName = fmt::format("Press a Key...##{}", axisName); - } else { - dispName = fmt::format("{0}##-{1}", PADGetNativeAxisName(axisMappingList[trigger].nativeAxis), trigger); - } - bool pressed = ImGui::Button(dispName.c_str(), - btnSize); - - if (pressed) { - m_controllerConfig.m_isReading = true; - m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort; - m_controllerConfig.m_pendingAxisMapping = &axisMappingList[trigger]; - PADBlockInput(true); - } - } - } - - int port = m_controllerConfig.m_selectedPort; - PADDeadZones* deadZones = PADGetDeadZones(port); - - if (deadZones != nullptr) { - ImGui::Text("L Threshold"); - ImGui::SameLine(); - { - 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); - PADSerializeMappings(); - } - } - } - - if (deadZones != nullptr) { - ImGui::Text("R Threshold"); - ImGui::SameLine(); - { - 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); - PADSerializeMappings(); - } - } - } - - ImGuiEndGroupPanel(); - ImGui::SameLine(); - - // main stick panel - ImGuiBeginGroupPanel("Control Stick", ImVec2(150 * scale, 20 * scale)); - - drawVirtualStick("##mainStick", ImVec2{ mDoCPd_c::getStickX(port), mDoCPd_c::getStickY(port) }); - - if (axisMappingList != nullptr) { - const PADAxis lStickAxes[] = {PAD_AXIS_LEFT_Y_POS, PAD_AXIS_LEFT_Y_NEG, PAD_AXIS_LEFT_X_NEG, PAD_AXIS_LEFT_X_POS}; - for (auto axis : lStickAxes) { - const char* label = PADGetAxisDirectionLabel(axis); - ImVec2 len = ImGui::CalcTextSize(label); - ImVec2 pos = ImGui::GetCursorPos(); - - ImGui::SetCursorPosY(pos.y + len.y / 4); - ImGui::SetCursorPosX(pos.x + abs(len.x - uiButtonSize)); - ImGui::Text("%s", label); - ImGui::SameLine(); - - ImGui::SetCursorPosY(pos.y); - - std::string dispName; - if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[axis]) { - dispName = fmt::format("Press a Key...##{}", label); - } else { - if (axisMappingList[axis].nativeAxis.nativeAxis != -1) { - const char* signStr; - if (axis == PAD_AXIS_TRIGGER_L || axis == PAD_AXIS_TRIGGER_R) { - signStr = ""; - } else if (axisMappingList[axis].nativeAxis.sign == AXIS_SIGN_POSITIVE) { - signStr = "+"; - } else { - signStr = "-"; - } - dispName = fmt::format("{0}{1}##-{2}", PADGetNativeAxisName(axisMappingList[axis].nativeAxis), signStr, axis); - } else { - assert(axisMappingList[axis].nativeButton != -1); - dispName = fmt::format("{0}##-{1}", PADGetNativeButtonName(axisMappingList[axis].nativeButton), axis); - } - } - bool pressed = ImGui::Button(dispName.c_str(), btnSize); - - if (pressed) { - m_controllerConfig.m_isReading = true; - m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort; - m_controllerConfig.m_pendingAxisMapping = &axisMappingList[axis]; - PADBlockInput(true); - } - } - } - - 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); - PADSerializeMappings(); - } - } - } - - ImGuiEndGroupPanel(); - ImGui::SameLine(); - - // sub stick panel - ImGuiBeginGroupPanel("C Stick", ImVec2(150 * scale, 20 * scale)); - - drawVirtualStick("##subStick", ImVec2{ mDoCPd_c::getSubStickX(port), mDoCPd_c::getSubStickY(port) }); - - if (axisMappingList != nullptr) { - const PADAxis rStickAxes[] = {PAD_AXIS_RIGHT_Y_POS, PAD_AXIS_RIGHT_Y_NEG, PAD_AXIS_RIGHT_X_NEG, PAD_AXIS_RIGHT_X_POS}; - for (auto axis : rStickAxes) { - const char* label = PADGetAxisDirectionLabel(axisMappingList[axis].padAxis); - ImVec2 len = ImGui::CalcTextSize(label); - ImVec2 pos = ImGui::GetCursorPos(); - - ImGui::SetCursorPosY(pos.y + len.y / 4); - ImGui::SetCursorPosX(pos.x + abs(len.x - uiButtonSize)); - ImGui::Text("%s", label); - ImGui::SameLine(); - - ImGui::SetCursorPosY(pos.y); - - std::string dispName; - if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[axis]) { - dispName = fmt::format("Press a Key...##sub{}", label); - } else { - if (axisMappingList[axis].nativeAxis.nativeAxis != -1) { - const char* signStr; - if (axis == PAD_AXIS_TRIGGER_L || axis == PAD_AXIS_TRIGGER_R) { - signStr = ""; - } else if (axisMappingList[axis].nativeAxis.sign == AXIS_SIGN_POSITIVE) { - signStr = "+"; - } else { - signStr = "-"; - } - dispName = fmt::format("{0}{1}##-{2}", PADGetNativeAxisName(axisMappingList[axis].nativeAxis), signStr, axis); - } else { - assert(axisMappingList[axis].nativeButton != -1); - dispName = fmt::format("{0}##-{1}", PADGetNativeButtonName(axisMappingList[axis].nativeButton), axis); - } - } - bool pressed = ImGui::Button(fmt::format("{0}##sub{1}", dispName, label).c_str(), btnSize); - - if (pressed) { - m_controllerConfig.m_isReading = true; - m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort; - m_controllerConfig.m_pendingAxisMapping = &axisMappingList[axis]; - PADBlockInput(true); - } - } - } - - 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); - PADSerializeMappings(); - } - } - } - - ImGuiEndGroupPanel(); - ImGui::SameLine(); - - // Options panel - ImGuiBeginGroupPanel("Options", ImVec2(150 * scale, -1)); - - if (deadZones != nullptr) { - if (ImGui::Checkbox("Enable Dead Zones", &deadZones->useDeadzones)) { - PADSerializeMappings(); - } - if (ImGui::Checkbox("Emulate Triggers", &deadZones->emulateTriggers)) { - PADSerializeMappings(); - } - } - - if (PADSupportsRumbleIntensity(m_controllerConfig.m_selectedPort)) { - ImGuiBeginGroupPanel("Rumble Intensity", ImVec2(150 * scale, -1)); - u16 low; - u16 high; - (void)PADGetRumbleIntensity(m_controllerConfig.m_selectedPort, &low, &high); - float fLow = (static_cast(low) / 32767.f) * 100.f; - bool changed = ImGui::SliderFloat("Low", &fLow, 0.f, 100.f, "%.0f%%"); - float fHigh = (static_cast(high) / 32767.f) * 100.f; - changed |= ImGui::SliderFloat("High", &fHigh, 0.f, 100.f, "%.0f%%"); - if (changed) { - PADSetRumbleIntensity(m_controllerConfig.m_selectedPort, - static_cast((fLow / 100) * 32767), - static_cast((fHigh / 100) * 32767)); - PADSerializeMappings(); - } - if (ImGui::Button(fmt::format("{0}...##rumbleTest", m_controllerConfig.m_isRumbling ? "Stop": "Test").c_str(), {-1, 0})) { - PADControlMotor(m_controllerConfig.m_selectedPort, !m_controllerConfig.m_isRumbling ? PAD_MOTOR_RUMBLE : PAD_MOTOR_STOP_HARD); - m_controllerConfig.m_isRumbling ^= 1; - } - ImGuiEndGroupPanel(); - } - ImGuiEndGroupPanel(); - - ImGui::End(); - } - static std::string GetFormattedTime(OSTime ticks) { OSCalendarTime time; OSTicksToCalendarTime(ticks, &time); diff --git a/src/dusk/imgui/ImGuiMenuGame.hpp b/src/dusk/imgui/ImGuiMenuGame.hpp index b5ecfa37c0..9faf045ec3 100644 --- a/src/dusk/imgui/ImGuiMenuGame.hpp +++ b/src/dusk/imgui/ImGuiMenuGame.hpp @@ -47,11 +47,8 @@ namespace dusk { void draw(); void windowInputViewer(); - void windowControllerConfig(); void drawSpeedrunTimerOverlay(); - static void ToggleFullscreen(); - static void resetForSpeedrunMode(); private: diff --git a/src/dusk/ui/controller_config.cpp b/src/dusk/ui/controller_config.cpp index a5fe9ea7d2..52c6f6c702 100644 --- a/src/dusk/ui/controller_config.cpp +++ b/src/dusk/ui/controller_config.cpp @@ -3,7 +3,7 @@ #include "bool_button.hpp" #include "button.hpp" #include "pane.hpp" -#include "select_button.hpp" +#include "number_button.hpp" #include #include @@ -279,6 +279,18 @@ s32 keyboard_key_pressed() { return PAD_KEY_INVALID; } +u16 percent_to_raw(int percent) { + return static_cast((static_cast(percent) / 100.f) * 32767.f); +} + +int deadzone_raw_to_percent(u16 raw) { + return static_cast((static_cast(raw) * 100.f) / 32767.f + 0.5f); +} + +int rumble_raw_to_percent(u16 raw) { + return static_cast((static_cast(raw) / 32767.f) * 100.f + 0.5f); +} + } // namespace ControllerConfigWindow::ControllerConfigWindow() { @@ -308,6 +320,7 @@ ControllerConfigWindow::ControllerConfigWindow() { } void ControllerConfigWindow::hide(bool close) { + stop_rumble_test(); cancel_pending_binding(); Window::hide(close); } @@ -318,16 +331,18 @@ void ControllerConfigWindow::update() { } void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) { + stop_rumble_test(); auto& leftPane = add_child(content, Pane::Type::Controlled); auto& rightPane = add_child(content, Pane::Type::Uncontrolled); mRightPane = &rightPane; mActivePort = port; auto addPageButton = [this, &leftPane, &rightPane, port]( - Page page, Rml::String key, auto getValue) { + Page page, Rml::String key, auto getValue, auto isDisabled) { leftPane.register_control(leftPane.add_select_button({ .key = std::move(key), .getValue = std::move(getValue), + .isDisabled = std::move(isDisabled), }), rightPane, [this, port, page](Pane& pane) { mPage = page; @@ -335,10 +350,11 @@ void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) { }); }; - addPageButton(Page::Controller, "Controller", [port] { return current_controller_name(port); }); - addPageButton(Page::Buttons, "Buttons", [] { return Rml::String(">"); }); - addPageButton(Page::Triggers, "Triggers", [] { return Rml::String(">"); }); - addPageButton(Page::Sticks, "Sticks", [] { return Rml::String(">"); }); + addPageButton(Page::Controller, "Controller", [port] { return current_controller_name(port); }, [] { return false; }); + addPageButton(Page::Buttons, "Buttons", [] { return Rml::String(">"); }, [] { return false; }); + addPageButton(Page::Triggers, "Triggers", [] { return Rml::String(">"); }, [] { return false; }); + addPageButton(Page::Sticks, "Sticks", [] { return Rml::String(">"); }, [] { return false; }); + addPageButton(Page::Rumble, "Rumble", [] { return Rml::String(">"); }, [port] { return !PADSupportsRumbleIntensity(static_cast(port)); }); leftPane.add_section("Options"); leftPane.register_control(leftPane.add_child(BoolButton::Props{ @@ -684,6 +700,38 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) { }); } } + + if (PADDeadZones* deadZones = PADGetDeadZones(port)) { + pane.add_section("Emulated Trigger Thresholds"); + pane.add_child(NumberButton::Props{ + .key = "L Threshold", + .getValue = [deadZones] { return deadzone_raw_to_percent(deadZones->leftTriggerActivationZone); }, + .setValue = + [deadZones](int value) { + deadZones->leftTriggerActivationZone = percent_to_raw(value); + PADSerializeMappings(); + }, + .isDisabled = [deadZones] { return !deadZones->emulateTriggers; }, + .min = 0, + .max = 100, + .step = 1, + .suffix = "%", + }); + pane.add_child(NumberButton::Props{ + .key = "R Threshold", + .getValue = [deadZones] { return deadzone_raw_to_percent(deadZones->rightTriggerActivationZone); }, + .setValue = + [deadZones](int value) { + deadZones->rightTriggerActivationZone = percent_to_raw(value); + PADSerializeMappings(); + }, + .isDisabled = [deadZones] { return !deadZones->emulateTriggers; }, + .min = 0, + .max = 100, + .step = 1, + .suffix = "%", + }); + } break; } case Page::Sticks: { @@ -769,12 +817,121 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) { addAxis(PAD_AXIS_LEFT_Y_NEG); addAxis(PAD_AXIS_LEFT_X_NEG); addAxis(PAD_AXIS_LEFT_X_POS); + if (PADDeadZones* deadZones = PADGetDeadZones(port)) { + pane.add_child(NumberButton::Props{ + .key = "Deadzone", + .getValue = [deadZones] { return deadzone_raw_to_percent(deadZones->stickDeadZone); }, + .setValue = + [deadZones](int value) { + deadZones->stickDeadZone = percent_to_raw(value); + PADSerializeMappings(); + }, + .isDisabled = [deadZones] { return !deadZones->useDeadzones; }, + .min = 0, + .max = 100, + .step = 1, + .suffix = "%", + }); + } pane.add_section("C Stick"); addAxis(PAD_AXIS_RIGHT_Y_POS); addAxis(PAD_AXIS_RIGHT_Y_NEG); addAxis(PAD_AXIS_RIGHT_X_NEG); addAxis(PAD_AXIS_RIGHT_X_POS); + if (PADDeadZones* deadZones = PADGetDeadZones(port)) { + pane.add_child(NumberButton::Props{ + .key = "Deadzone", + .getValue = [deadZones] { return deadzone_raw_to_percent(deadZones->substickDeadZone); }, + .setValue = + [deadZones](int value) { + deadZones->substickDeadZone = percent_to_raw(value); + PADSerializeMappings(); + }, + .isDisabled = [deadZones] { return !deadZones->useDeadzones; }, + .min = 0, + .max = 100, + .step = 1, + .suffix = "%", + }); + } + + break; + } + case Page::Rumble: { + auto& rumbleTest = pane.add_select_button({ + .key = "Test Rumble", + .getValue = + [this, port] { + return (mRumbleTestActive && mRumbleTestPort == port) ? Rml::String("Stop") + : Rml::String("Start"); + }, + }); + rumbleTest.on_pressed([this, port] { + if (!PADSupportsRumbleIntensity(static_cast(port))) { + return; + } + mDoAud_seStartMenu(kSoundItemChange); + if (mRumbleTestActive && mRumbleTestPort == port) { + PADControlMotor(port, PAD_MOTOR_STOP_HARD); + mRumbleTestActive = false; + mRumbleTestPort = -1; + } else { + if (mRumbleTestActive) { + PADControlMotor(mRumbleTestPort, PAD_MOTOR_STOP_HARD); + } + PADControlMotor(port, PAD_MOTOR_RUMBLE); + mRumbleTestActive = true; + mRumbleTestPort = port; + } + }); + pane.add_child(NumberButton::Props{ + .key = "Low Rumble Frequency", + .getValue = + [port] { + u16 low = 0; + u16 high = 0; + PADGetRumbleIntensity(static_cast(port), &low, &high); + return rumble_raw_to_percent(low); + }, + .setValue = + [port](int value) { + u16 low = 0; + u16 high = 0; + PADGetRumbleIntensity(static_cast(port), &low, &high); + PADSetRumbleIntensity(static_cast(port), percent_to_raw(value), high); + PADSerializeMappings(); + }, + .isDisabled = [this] { return mRumbleTestActive; }, + .min = 0, + .max = 100, + .step = 1, + .suffix = "%", + }); + pane.add_child(NumberButton::Props{ + .key = "High Rumble Frequency", + .getValue = + [port] { + u16 low = 0; + u16 high = 0; + PADGetRumbleIntensity(static_cast(port), &low, &high); + return rumble_raw_to_percent(high); + }, + .setValue = + [port](int value) { + u16 low = 0; + u16 high = 0; + PADGetRumbleIntensity(static_cast(port), &low, &high); + PADSetRumbleIntensity(static_cast(port), low, percent_to_raw(value)); + PADSerializeMappings(); + }, + .isDisabled = [this] { return mRumbleTestActive; }, + .min = 0, + .max = 100, + .step = 1, + .suffix = "%", + }); + pane.add_text("Configure your desired rumble intensities, then run a test to check how they feel."); break; } } @@ -938,4 +1095,15 @@ Rml::String ControllerConfigWindow::pending_key_label() const { return mPendingBindingArmed ? "Press a key or mouse button..." : "Waiting..."; } +void ControllerConfigWindow::stop_rumble_test() { + if (!mRumbleTestActive) { + return; + } + if (mRumbleTestPort >= PAD_CHAN0 && mRumbleTestPort < PAD_CHANMAX) { + PADControlMotor(mRumbleTestPort, PAD_MOTOR_STOP_HARD); + } + mRumbleTestActive = false; + mRumbleTestPort = -1; +} + } // namespace dusk::ui diff --git a/src/dusk/ui/controller_config.hpp b/src/dusk/ui/controller_config.hpp index c2a037309e..0fa096c7d1 100644 --- a/src/dusk/ui/controller_config.hpp +++ b/src/dusk/ui/controller_config.hpp @@ -19,6 +19,7 @@ private: Buttons, Triggers, Sticks, + Rumble, }; void build_port_tab(Rml::Element* content, int port); @@ -34,6 +35,7 @@ private: void cancel_pending_binding(); void finish_pending_key_binding(); Rml::String pending_key_label() const; + void stop_rumble_test(); Page mPage = Page::Controller; Pane* mRightPane = nullptr; @@ -46,6 +48,8 @@ private: PADAxisMapping* mPendingAxisMapping = nullptr; int mPendingKeyButton = -1; int mPendingKeyAxis = -1; + bool mRumbleTestActive = false; + int mRumbleTestPort = -1; }; } // namespace dusk::ui