Add options for binding custom buttons to specific actions (#1141)

* custom action framework and first person custom action

* add bind for midna call

* custom binding for opening dusklight menu

* turbo speed button action

* text descriptions

* fix not stopping default GC controller menu combo

* more explanation text

* block bind actions when in the dusklight menu
This commit is contained in:
gymnast86
2026-05-12 21:36:07 -04:00
committed by GitHub
parent 76efa02beb
commit ef43b94370
17 changed files with 456 additions and 78 deletions
+2
View File
@@ -1411,6 +1411,7 @@ set(DOLPHIN_FILES
)
set(DUSK_FILES
include/dusk/action_bindings.h
include/dusk/endian_gx.hpp
include/dusk/config.hpp
include/dusk/dvd_asset.hpp
@@ -1522,6 +1523,7 @@ set(DUSK_FILES
src/dusk/discord.hpp
src/dusk/discord_presence.cpp
src/dusk/version.cpp
src/dusk/action_bindings.cpp
)
set(DUSK_HTTP_BACKEND_FILES
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <unordered_map>
#include "dusk/config_var.hpp"
namespace dusk {
enum class ActionBinds {
FIRST_PERSON_CAMERA,
CALL_MIDNA,
OPEN_DUSKLIGHT_MENU,
TURBO_SPEED_BUTTON,
COUNT,
};
struct ActionBindData {
std::array<config::ActionBindConfigVar, 4>* configVars{};
std::string actionName{};
};
struct ActionBindPressData {
bool pressedCurFrame{false};
bool pressedPrevFrame{false};
};
using ActionBindsMap = std::unordered_map<ActionBinds, ActionBindData>;
ActionBindsMap& getActionBinds();
bool isActionBound(ActionBinds action, u32 port);
void updateActionBindings();
bool getActionBindTrig(ActionBinds action, u32 port);
bool getActionBindHold(ActionBinds action, u32 port);
bool getActionBindHoldAnyPort(ActionBinds action);
int getActionBindButton(ActionBinds action, u32 port);
}
+7
View File
@@ -112,6 +112,13 @@ void Save();
*/
ConfigVarBase* GetConfigVar(std::string_view name);
/**
* \brief Resets all custom action bindings for a specific port to nothing
*
* @param port The port to be cleared of action bindings
*/
void ClearAllActionBindings(int port);
/**
* \brief Call a function on every registered CVar.
*/
+2
View File
@@ -287,6 +287,8 @@ public:
}
};
using ActionBindConfigVar = ConfigVar<int>;
}
#endif // DUSK_CONFIG_VAR_HPP
+10
View File
@@ -1,6 +1,8 @@
#ifndef DUSK_CONFIG_H
#define DUSK_CONFIG_H
#include <array>
#include "dusk/config_var.hpp"
namespace dusk {
@@ -197,6 +199,14 @@ struct UserSettings {
ConfigVar<int> cardFileType;
ConfigVar<bool> enableAdvancedSettings;
} backend;
// Arrays of size 4 for 4 ports
struct {
std::array<ActionBindConfigVar, 4> firstPersonCamera;
std::array<ActionBindConfigVar, 4> callMidna;
std::array<ActionBindConfigVar, 4> openDusklightMenu;
std::array<ActionBindConfigVar, 4> turboSpeedButton;
} actionBindings;
};
UserSettings& getSettings();
+7
View File
@@ -4,6 +4,10 @@
#include <cmath>
#include "os_report.h"
#if TARGET_PC
#include "dusk/action_bindings.h"
#endif
u32 JUTGamePad::CRumble::sChannelMask[4] = {
PAD_CHAN0_BIT,
PAD_CHAN1_BIT,
@@ -85,6 +89,9 @@ u32 JUTGamePad::sRumbleSupported;
u32 JUTGamePad::read() {
sRumbleSupported = PADRead(mPadStatus);
#if TARGET_PC
dusk::updateActionBindings();
#endif
switch (sClampMode) {
case EClampStick:
+9
View File
@@ -51,10 +51,13 @@
#include "d/actor/d_a_ni.h"
#include "d/d_s_play.h"
#if TARGET_PC
#include "dusk/action_bindings.h"
#include "dusk/frame_interpolation.h"
#include "dusk/settings.h"
#include "res/Object/Alink.h"
#include <cstring>
#endif
static int daAlink_Create(fopAc_ac_c* i_this);
static int daAlink_Delete(daAlink_c* i_this);
@@ -9363,6 +9366,12 @@ BOOL daAlink_c::spActionTrigger() {
}
BOOL daAlink_c::midnaTalkTrigger() const {
#if TARGET_PC
// If we have a custom bind for Midna, check that instead
if (dusk::isActionBound(dusk::ActionBinds::CALL_MIDNA, 0)) {
return dusk::getActionBindTrig(dusk::ActionBinds::CALL_MIDNA, 0);
}
#endif
return mItemTrigger & BTN_Z;
}
+4 -1
View File
@@ -12,6 +12,7 @@
#if TARGET_PC
#include "dusk/gyro.h"
#include "dusk/action_bindings.h"
#endif
bool daAlink_c::checkNoSubjectModeCamera() {
@@ -192,7 +193,9 @@ BOOL daAlink_c::subjectCancelTrigger() {
BOOL daAlink_c::checkSubjectEnd(BOOL i_isPlaySe) {
setDoStatus(BUTTON_STATUS_BACK);
if (checkEventRun() || checkEquipAnime() || doTrigger() || checkSetItemTrigger(dItemNo_HAWK_EYE_e) || subjectCancelTrigger() || checkEndResetFlg0(ERFLG0_FORCE_SUBJECT_CANCEL) || dComIfGp_checkCameraAttentionStatus(field_0x317c, 0x2000)) {
// Allow pressing the first person binding to also leave first person
if (IF_DUSK(dusk::getActionBindTrig(dusk::ActionBinds::FIRST_PERSON_CAMERA, 0)) ||
checkEventRun() || checkEquipAnime() || doTrigger() || checkSetItemTrigger(dItemNo_HAWK_EYE_e) || subjectCancelTrigger() || checkEndResetFlg0(ERFLG0_FORCE_SUBJECT_CANCEL) || dComIfGp_checkCameraAttentionStatus(field_0x317c, 0x2000)) {
if (i_isPlaySe) {
seStartSystem(Z2SE_SUBJ_VIEW_OUT);
}
+15 -5
View File
@@ -31,6 +31,7 @@
#if TARGET_PC
#include "dusk/frame_interpolation.h"
#include "dusk/logging.h"
#include "dusk/action_bindings.h"
#include "imgui.h"
#endif
@@ -838,6 +839,12 @@ void dCamera_c::updatePad() {
mTrigB = mDoCPd_c::getTrigB(mPadID) ? true : false;
#if TARGET_PC
// If our custom action binding is triggered, and we're not already in first person, go into first person
if (dusk::getActionBindTrig(dusk::ActionBinds::FIRST_PERSON_CAMERA, mPadID) && mGear != -1) {
setComStat(0x1000);
mGear = 0;
}
if (mCamParam.mManualMode) {
return;
}
@@ -877,7 +884,8 @@ void dCamera_c::updatePad() {
if (mPadInfo.mCStick.mLastPosY < -mCamSetup.mCStick.SwTHH()) {
if (mCStickYState != -1) {
if (mGear == -1 && mCurMode == 4) {
// Don't use regular first person trigger if custom mapping is set
if (mGear == -1 && mCurMode == 4 IF_DUSK(&& !dusk::isActionBound(dusk::ActionBinds::FIRST_PERSON_CAMERA, mPadID))) {
mGear = 0;
setComStat(0x2000);
} else if (mGear == 0 && sp6C) {
@@ -888,7 +896,8 @@ void dCamera_c::updatePad() {
mCStickYState = -1;
} else if (mPadInfo.mCStick.mLastPosY > mCamSetup.mCStick.SwTHH()) {
if (mCStickYState != 1) {
if (mGear == 0 && sp6B) {
// Don't use regular first person trigger if custom mapping is set
if (mGear == 0 && sp6B IF_DUSK(&& !dusk::isActionBound(dusk::ActionBinds::FIRST_PERSON_CAMERA, mPadID))) {
setComStat(0x1000);
} else if (mGear == 1) {
mGear = 0;
@@ -7649,9 +7658,10 @@ bool dCamera_c::freeCamera() {
f32 magnitude = sqrt(mPadInfo.mCStick.mLastPosX * mPadInfo.mCStick.mLastPosX + mPadInfo.mCStick.mLastPosY * mPadInfo.mCStick.mLastPosY);
// If we aren't in manual cam mode, don't trigger it if the player tries to hit C-up
// for first person
if (mPadInfo.mCStick.mLastPosX != 0 || mPadInfo.mCStick.mLastPosY < 0 ||
(mCamParam.mManualMode == 1 && mPadInfo.mCStick.mLastPosY != 0)) {
// for first person unless they have first person bound to a custom binding
if ((dusk::isActionBound(dusk::ActionBinds::FIRST_PERSON_CAMERA, mPadID) && mPadInfo.mCStick.mLastPosY != 0) ||
mPadInfo.mCStick.mLastPosX != 0 || mPadInfo.mCStick.mLastPosY < 0 || (mCamParam.mManualMode == 1 && mPadInfo.mCStick.mLastPosY != 0))
{
mCamParam.mManualMode = 1;
camMovement = camMovement.normalize();
camMovement.y *= dusk::getSettings().game.invertCameraYAxis ? 1.0f : -1.0f;
+96
View File
@@ -0,0 +1,96 @@
#include "dusk/action_bindings.h"
#include "aurora/lib/input.hpp"
#include "dusk/settings.h"
#include "dusk/ui/ui.hpp"
namespace dusk {
static std::array<std::array<ActionBindPressData, static_cast<int>(ActionBinds::COUNT)>, PAD_CHANMAX> actionPressData{};
ActionBindsMap& getActionBinds() {
static ActionBindsMap actionBinds = {
{ActionBinds::FIRST_PERSON_CAMERA, {&getSettings().actionBindings.firstPersonCamera, "First Person Camera"}},
{ActionBinds::CALL_MIDNA, {&getSettings().actionBindings.callMidna, "Call Midna"}},
{ActionBinds::OPEN_DUSKLIGHT_MENU, {&getSettings().actionBindings.openDusklightMenu, "Open Dusklight Menu"}},
{ActionBinds::TURBO_SPEED_BUTTON, {&getSettings().actionBindings.turboSpeedButton, "Turbo Speed Button"}},
};
return actionBinds;
}
bool isActionBound(ActionBinds action, u32 port) {
auto& actionBinds = getActionBinds();
// Check to make sure action is properly bound
if (!actionBinds.contains(action)) {
return false;
}
return getActionBindButton(action, port) != PAD_NATIVE_BUTTON_INVALID;
}
void updateActionBindings() {
for (u32 port = 0; port < PAD_CHANMAX; ++port) {
// Move the current press to the previous frame
for (auto& pressData : actionPressData[port]) {
pressData.pressedPrevFrame = pressData.pressedCurFrame;
pressData.pressedCurFrame = false;
}
// Update current frame with whether action button is pressed
for (auto& [action, boundAction] : getActionBinds()) {
// If the action isn't bound, or if documents are visible and the action isn't
// opening the dusklight menu, don't update. Otherwise, we may accidentally
// perform actions while the dusklight menu is open.
if (!isActionBound(action, port) ||
(ui::any_document_visible() && action != ActionBinds::OPEN_DUSKLIGHT_MENU)) {
continue;
}
int button = boundAction.configVars->at(port);
// If keyboard is active for this port
u32 count = 0;
if (PADGetKeyButtonBindings(port, &count) != nullptr) {
int numKeys = 0;
const bool* kbState = SDL_GetKeyboardState(&numKeys);
if (kbState[button]) {
actionPressData[port][static_cast<int>(action)].pressedCurFrame = true;
}
} else {
// If controller is active
auto controller = aurora::input::get_controller_for_player(port);
if (controller) {
if (SDL_GetGamepadButton(controller->m_controller, static_cast<SDL_GamepadButton>(button))) {
actionPressData[port][static_cast<int>(action)].pressedCurFrame = true;
}
}
}
}
}
}
bool getActionBindTrig(ActionBinds action, u32 port) {
return isActionBound(action, port) &&
actionPressData[port][static_cast<int>(action)].pressedCurFrame &&
!actionPressData[port][static_cast<int>(action)].pressedPrevFrame;
}
bool getActionBindHold(ActionBinds action, u32 port) {
return isActionBound(action, port) &&
actionPressData[port][static_cast<int>(action)].pressedCurFrame &&
actionPressData[port][static_cast<int>(action)].pressedPrevFrame;
}
bool getActionBindHoldAnyPort(ActionBinds action) {
for (u32 port = 0; port < PAD_CHANMAX; ++port) {
if (getActionBindHold(action, port)) {
return true;
}
}
return false;
}
int getActionBindButton(ActionBinds action, u32 port) {
return (*getActionBinds()[action].configVars)[port];
}
}
+8
View File
@@ -11,6 +11,7 @@
#include <string>
#include "dusk/main.h"
#include "dusk/action_bindings.h"
using namespace dusk::config;
@@ -256,6 +257,13 @@ void dusk::config::Save() {
io::FileStream::WriteAllText(reinterpret_cast<const char*>(configJsonPath.c_str()), j.dump(4));
}
void dusk::config::ClearAllActionBindings(int port) {
for (auto& actionBinding : getActionBinds() | std::views::values) {
actionBinding.configVars->at(port).setValue(PAD_NATIVE_BUTTON_INVALID);
}
Save();
}
ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) {
const auto configVar = RegisteredConfigVars.find(name);
if (configVar != RegisteredConfigVars.end()) {
+3 -1
View File
@@ -13,6 +13,7 @@
#include "ImGuiEngine.hpp"
#include "JSystem/JUtility/JUTGamePad.h"
#include "SDL3/SDL_mouse.h"
#include "dusk/action_bindings.h"
#include "dusk/audio/DuskAudioSystem.h"
#include "dusk/config.hpp"
#include "dusk/data.hpp"
@@ -239,7 +240,8 @@ namespace dusk {
}
void ImGuiConsole::UpdateSettings() {
getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab);
getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind &&
(ImGui::IsKeyDown(ImGuiKey_Tab) || getActionBindHoldAnyPort(ActionBinds::TURBO_SPEED_BUTTON));
if (dusk::frame_interp::get_ui_tick_pending() && mDoMain::developmentMode == 1 && (mDoCPd_c::getHold(PAD_1) & (PAD_TRIGGER_R | PAD_TRIGGER_L)) == (PAD_TRIGGER_R | PAD_TRIGGER_L) && mDoCPd_c::getTrigY(PAD_1)) {
getTransientSettings().moveLinkActive = !getTransientSettings().moveLinkActive;
+45
View File
@@ -133,6 +133,34 @@ UserSettings g_userSettings = {
.checkForUpdates {"backend.checkForUpdates", true},
.cardFileType {"backend.cardFileType", static_cast<int>(CARD_GCIFOLDER)},
.enableAdvancedSettings {"backend.enableAdvancedSettings", false},
},
// Not sure if there's a better way to declare this
.actionBindings = {
.firstPersonCamera {
ActionBindConfigVar{"actionBindings.firstPersonCamera_port0", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.firstPersonCamera_port1", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.firstPersonCamera_port2", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.firstPersonCamera_port3", PAD_NATIVE_BUTTON_INVALID},
},
.callMidna {
ActionBindConfigVar{"actionBindings.callMidna_port0", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.callMidna_port1", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.callMidna_port2", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.callMidna_port3", PAD_NATIVE_BUTTON_INVALID},
},
.openDusklightMenu {
ActionBindConfigVar{"actionBindings.openDusklightMenu_port0", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.openDusklightMenu_port1", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.openDusklightMenu_port2", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.openDusklightMenu_port3", PAD_NATIVE_BUTTON_INVALID},
},
.turboSpeedButton {
ActionBindConfigVar{"actionBindings.turboButton_port0", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.turboButton_port1", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.turboButton_port2", PAD_NATIVE_BUTTON_INVALID},
ActionBindConfigVar{"actionBindings.turboButton_port3", PAD_NATIVE_BUTTON_INVALID},
},
}
};
@@ -248,6 +276,23 @@ void registerSettings() {
Register(g_userSettings.backend.checkForUpdates);
Register(g_userSettings.backend.cardFileType);
Register(g_userSettings.backend.enableAdvancedSettings);
Register(g_userSettings.actionBindings.firstPersonCamera[0]);
Register(g_userSettings.actionBindings.firstPersonCamera[1]);
Register(g_userSettings.actionBindings.firstPersonCamera[2]);
Register(g_userSettings.actionBindings.firstPersonCamera[3]);
Register(g_userSettings.actionBindings.callMidna[0]);
Register(g_userSettings.actionBindings.callMidna[1]);
Register(g_userSettings.actionBindings.callMidna[2]);
Register(g_userSettings.actionBindings.callMidna[3]);
Register(g_userSettings.actionBindings.openDusklightMenu[0]);
Register(g_userSettings.actionBindings.openDusklightMenu[1]);
Register(g_userSettings.actionBindings.openDusklightMenu[2]);
Register(g_userSettings.actionBindings.openDusklightMenu[3]);
Register(g_userSettings.actionBindings.turboSpeedButton[0]);
Register(g_userSettings.actionBindings.turboSpeedButton[1]);
Register(g_userSettings.actionBindings.turboSpeedButton[2]);
Register(g_userSettings.actionBindings.turboSpeedButton[3]);
}
// Transient settings
+166 -65
View File
@@ -15,6 +15,9 @@
#include <utility>
#include <vector>
#include "dusk/action_bindings.h"
#include "dusk/config.hpp"
namespace dusk::ui {
namespace {
@@ -108,68 +111,6 @@ const std::vector<ButtonNames> kGamepadButtonNames = {
};
// clang-format on
Rml::String native_button_name(SDL_Gamepad* gamepad, u32 buttonUntyped) {
if (buttonUntyped == PAD_NATIVE_BUTTON_INVALID) {
return "Not bound";
}
auto button = static_cast<SDL_GamepadButton>(buttonUntyped);
if (gamepad != nullptr) {
switch (SDL_GetGamepadButtonLabel(gamepad, button)) {
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:
break;
}
}
const SDL_GamepadType type =
gamepad != nullptr ? SDL_GetGamepadType(gamepad) : SDL_GAMEPAD_TYPE_UNKNOWN;
for (const auto& buttonNames : kGamepadButtonNames) {
if (buttonNames.button != button) {
continue;
}
for (const auto& name : buttonNames.names) {
if (name.type == type) {
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:
break;
}
if (const char* name = PADGetNativeButtonName(buttonUntyped)) {
return name;
}
return "Unknown";
}
Rml::String native_axis_name(const PADAxisMapping& mapping, SDL_Gamepad* gamepad) {
if (mapping.nativeAxis.nativeAxis != -1) {
Rml::String value = PADGetNativeAxisName(mapping.nativeAxis);
@@ -367,6 +308,7 @@ void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) {
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<u32>(port)); });
addPageButton(Page::Actions, "Custom Action Bindings", [] {return Rml::String(">"); }, [] { return false; });
leftPane.add_section("Options");
leftPane.register_control(leftPane.add_child<BoolButton>(BoolButton::Props{
@@ -428,6 +370,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
PADClearPort(port);
PADSetKeyboardActive(static_cast<u32>(port), FALSE);
PADSerializeMappings();
ClearAllActionBindings(port);
});
pane.add_button({
@@ -440,6 +383,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
PADClearPort(port);
PADSetKeyboardActive(static_cast<u32>(port), TRUE);
PADSerializeMappings();
ClearAllActionBindings(port);
});
const u32 controllerCount = PADCount();
@@ -461,6 +405,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
PADSetKeyboardActive(static_cast<u32>(port), FALSE);
PADSetPortForIndex(i, port);
PADSerializeMappings();
ClearAllActionBindings(port);
});
}
break;
@@ -946,6 +891,77 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
pane.add_text("Configure your desired rumble intensities, then run a test to check how they feel.");
break;
}
case Page::Actions: {
if (keyboard_active(port)) {
auto addActionBinding = [&](auto actionBind, const std::string& key) {
pane.add_select_button(
{
.key = key,
.getValue =
[this, actionBind] {
if (mPendingActionBinding == actionBind) {
return pending_key_label();
}
return keyboard_key_name(actionBind->getValue());
},
})
.on_pressed([this, port, actionBind] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingActionBinding = actionBind;
});
};
pane.add_section("Custom Action Bindings");
pane.add_text("A key bound to any action here will REPLACE the default control for"
" that action. Only bind buttons here that aren't used anywhere else.");
for (auto& [configVars, actionName] : getActionBinds() | std::views::values) {
addActionBinding(&configVars->at(port), actionName);
}
break;
}
u32 buttonCount = 0;
PADButtonMapping* mappings = PADGetButtonMappings(port, &buttonCount);
if (mappings == nullptr) {
pane.add_text("No controller selected");
break;
}
SDL_Gamepad* gamepad = gamepad_for_port(port);
pane.add_section("Custom Action Bindings");
pane.add_text("A button bound to any action here will REPLACE the default control for"
" that action. Only bind buttons here that aren't used anywhere else. The glyphs"
" shown for in game actions will not change. This is not recommended for "
" regular Gamecube controllers.");
auto addActionBinding = [&](auto actionBind, const std::string& key) {
pane.add_select_button({
.key = key,
.getValue =
[this, gamepad, actionBind] {
if (mPendingActionBinding == actionBind) {
return pending_button_label();
}
return native_button_name(
gamepad, actionBind->getValue());
},
})
.on_pressed([this, port, actionBind] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingActionBinding = actionBind;
});
};
for (auto& [configVars, actionName] : getActionBinds() | std::views::values) {
addActionBinding(&configVars->at(port), actionName);
}
break;
}
}
}
@@ -1020,12 +1036,31 @@ void ControllerConfigWindow::poll_pending_binding() {
mPendingAxisMapping->nativeButton = nativeButton;
finish_pending_binding(completedPort);
}
return;
}
if (mPendingActionBinding != nullptr) {
int button{};
if (keyboard_active(mPendingPort)) {
button = keyboard_key_pressed();
} else {
button = PADGetNativeButtonPressed(mPendingPort);
}
if (button != -1) {
const int completedPort = mPendingPort;
mPendingActionBinding->setValue(button);
config::Save();
finish_pending_binding(completedPort);
}
return;
}
}
void ControllerConfigWindow::finish_pending_binding(int completedPort) {
mPendingButtonMapping = nullptr;
mPendingAxisMapping = nullptr;
mPendingActionBinding = nullptr;
mPendingPort = -1;
mPendingBindingArmed = false;
mSuppressNavigationUntilNeutral = true;
@@ -1035,7 +1070,7 @@ void ControllerConfigWindow::finish_pending_binding(int completedPort) {
void ControllerConfigWindow::unmap_pending_binding() {
if (mPendingButtonMapping == nullptr && mPendingAxisMapping == nullptr &&
mPendingKeyButton < 0 && mPendingKeyAxis < 0)
mPendingActionBinding == nullptr && mPendingKeyButton < 0 && mPendingKeyAxis < 0)
{
return;
}
@@ -1048,6 +1083,9 @@ void ControllerConfigWindow::unmap_pending_binding() {
mPendingAxisMapping->nativeAxis = {-1, AXIS_SIGN_POSITIVE};
mPendingAxisMapping->nativeButton = -1;
finish_pending_binding(completedPort);
} else if (mPendingActionBinding != nullptr) {
mPendingActionBinding->setValue(PAD_NATIVE_BUTTON_INVALID);
finish_pending_binding(completedPort);
} else if (mPendingKeyButton >= 0) {
PADSetKeyButtonBinding(static_cast<u32>(completedPort),
{PAD_KEY_INVALID, static_cast<PADButton>(mPendingKeyButton)});
@@ -1061,7 +1099,7 @@ void ControllerConfigWindow::unmap_pending_binding() {
bool ControllerConfigWindow::capture_active() const {
return mPendingButtonMapping != nullptr || mPendingAxisMapping != nullptr ||
mPendingKeyButton >= 0 || mPendingKeyAxis >= 0;
mPendingActionBinding != nullptr || mPendingKeyButton >= 0 || mPendingKeyAxis >= 0;
}
bool ControllerConfigWindow::pending_input_neutral() const {
@@ -1080,13 +1118,14 @@ Rml::String ControllerConfigWindow::pending_axis_label() const {
}
void ControllerConfigWindow::cancel_pending_binding() {
if (mPendingButtonMapping == nullptr && mPendingAxisMapping == nullptr &&
if (mPendingButtonMapping == nullptr && mPendingAxisMapping == nullptr && mPendingActionBinding == nullptr &&
!mSuppressNavigationUntilNeutral && mPendingKeyButton < 0 && mPendingKeyAxis < 0)
{
return;
}
mPendingButtonMapping = nullptr;
mPendingAxisMapping = nullptr;
mPendingActionBinding = nullptr;
mPendingKeyButton = -1;
mPendingKeyAxis = -1;
mPendingPort = -1;
@@ -1118,4 +1157,66 @@ void ControllerConfigWindow::stop_rumble_test() {
mRumbleTestPort = -1;
}
Rml::String native_button_name(SDL_Gamepad* gamepad, u32 buttonUntyped) {
if (buttonUntyped == PAD_NATIVE_BUTTON_INVALID) {
return "Not bound";
}
auto button = static_cast<SDL_GamepadButton>(buttonUntyped);
if (gamepad != nullptr) {
switch (SDL_GetGamepadButtonLabel(gamepad, button)) {
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:
break;
}
}
const SDL_GamepadType type =
gamepad != nullptr ? SDL_GetGamepadType(gamepad) : SDL_GAMEPAD_TYPE_UNKNOWN;
for (const auto& buttonNames : kGamepadButtonNames) {
if (buttonNames.button != button) {
continue;
}
for (const auto& name : buttonNames.names) {
if (name.type == type) {
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:
break;
}
if (const char* name = PADGetNativeButtonName(buttonUntyped)) {
return name;
}
return "Unknown";
}
} // namespace dusk::ui
+5
View File
@@ -1,6 +1,7 @@
#pragma once
#include "window.hpp"
#include "dusk/config_var.hpp"
#include <pad.h>
@@ -20,6 +21,7 @@ private:
Triggers,
Sticks,
Rumble,
Actions,
};
void build_port_tab(Rml::Element* content, int port);
@@ -50,6 +52,9 @@ private:
int mPendingKeyAxis = -1;
bool mRumbleTestActive = false;
int mRumbleTestPort = -1;
ActionBindConfigVar* mPendingActionBinding = nullptr;
};
Rml::String native_button_name(SDL_Gamepad* gamepad, u32 buttonUntyped);
} // namespace dusk::ui
+21 -5
View File
@@ -12,6 +12,8 @@
#include <algorithm>
#include <array>
#include "dusk/action_bindings.h"
namespace dusk::ui::input {
namespace {
@@ -203,6 +205,9 @@ Rml::Input::KeyIdentifier map_raw_gamepad_button(SDL_GamepadButton button) noexc
case SDL_GAMEPAD_BUTTON_SOUTH:
return Rml::Input::KI_RETURN;
case SDL_GAMEPAD_BUTTON_BACK:
if (isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, PAD_CHAN0)) {
return Rml::Input::KI_UNKNOWN;
}
return Rml::Input::KI_F1;
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
return Rml::Input::KI_NEXT;
@@ -216,6 +221,9 @@ Rml::Input::KeyIdentifier map_raw_gamepad_button(SDL_GamepadButton button) noexc
Rml::Input::KeyIdentifier map_raw_button_alias(SDL_GamepadButton button) noexcept {
switch (button) {
case SDL_GAMEPAD_BUTTON_BACK:
if (isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, PAD_CHAN0)) {
return Rml::Input::KI_UNKNOWN;
}
return Rml::Input::KI_F1;
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
return Rml::Input::KI_NEXT;
@@ -318,12 +326,20 @@ bool find_event_pad_button(
Rml::Input::KeyIdentifier map_gamepad_button(const SDL_GamepadButtonEvent& event) noexcept {
const auto nativeButton = static_cast<SDL_GamepadButton>(event.button);
if (nativeButton == SDL_GAMEPAD_BUTTON_BACK) {
u32 port = 0;
bool foundEventPort = find_event_port(event.which, port);
if (foundEventPort) {
int openMenuButton = getActionBindButton(ActionBinds::OPEN_DUSKLIGHT_MENU, port);
if (openMenuButton != PAD_NATIVE_BUTTON_INVALID && openMenuButton == nativeButton) {
return Rml::Input::KI_F1;
}
}
if (nativeButton == SDL_GAMEPAD_BUTTON_BACK && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port)) {
return Rml::Input::KI_F1;
}
u32 port = 0;
if (!find_event_port(event.which, port)) {
if (!foundEventPort) {
return map_raw_gamepad_button(nativeButton);
}
@@ -631,7 +647,7 @@ void process_axis_direction(
if (chorded) {
consume_menu_chord(port, context);
}
const auto key = chorded ? Rml::Input::KI_F1 : map_gamepad_axis(event, sign);
const auto key = chorded && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port) ? Rml::Input::KI_F1 : map_gamepad_axis(event, sign);
if (key == Rml::Input::KI_UNKNOWN) {
return;
}
@@ -719,7 +735,7 @@ void handle_event(const SDL_Event& event) noexcept {
if (chorded) {
consume_menu_chord(port, *context);
}
const auto key = chorded ? Rml::Input::KI_F1 : map_gamepad_button(event.gbutton);
const auto key = chorded && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port) ? Rml::Input::KI_F1 : map_gamepad_button(event.gbutton);
if (key != Rml::Input::KI_UNKNOWN) {
bool deferred = false;
if (repeat != nullptr) {
+13 -1
View File
@@ -2,6 +2,8 @@
#include "aurora/lib/logging.hpp"
#include "dusk/achievements.h"
#include "dusk/action_bindings.h"
#include "controller_config.hpp"
#include "dusk/livesplit.h"
#include "dusk/speedrun.h"
#include "fmt/format.h"
@@ -152,12 +154,22 @@ Rml::Element* create_menu_notification(Rml::Element* parent) {
auto* elem = append(parent, "toast");
elem->SetClass("menu-notification", true);
// Get name of button for action binding if the action is bound
Rml::String padButton{};
SDL_Gamepad* gamepad = gamepad_for_port(PAD_CHAN0);
if (isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, PAD_CHAN0) && gamepad != nullptr) {
padButton = native_button_name(gamepad,
getActionBindButton(ActionBinds::OPEN_DUSKLIGHT_MENU, PAD_CHAN0));
} else {
padButton = back_button_name();
}
auto* message = append(elem, "message");
auto* row = append(message, "row");
append(row, "span")->SetInnerRML(kMenuNotificationPrefix);
auto* icon = append(row, "icon");
icon->SetClass("controller", true);
append(row, "span")->SetInnerRML(escape(back_button_name()));
append(row, "span")->SetInnerRML(escape(padButton));
append(row, "span")->SetInnerRML("to open menu");
return elem;