Files
wiicompiled/runtime/include/controller_button_names.h
Cristian Boehm 149cfef608 keyboard and mouse support, rebinding overhaul, analog triggers to digital inputs (#162)
* add keyboard support, analog triggers to digital input, and rebinding overhaul

* Update README.md

* Update README.md

readme typo

* implemented code rabbits suggestions

- Preserved NSO GameCube analog triggers.
  - Made modal closure and Escape cancel every rebind kind.
  - Deduced the native button array size; <array> already existed.
  - Centralized axis/sign decoding.
  - Kept threshold updates live, saving only when editing ends.

* add dimming when in settings and add clear mapping button

* Update settings_overlay.cpp

---------

Co-authored-by: patchzyy <64382339+patchzyy@users.noreply.github.com>
2026-09-10 17:37:38 +02:00

171 lines
7.4 KiB
C++

#pragma once
// The single vocabulary shared by everything that has to turn a Config.toml
// controller name into a real button: the F10 settings bar, the macro engine,
// and the startup mapping pass. Keeping one table here means a name that the
// settings bar offers is always a name the config parser accepts, and vice
// versa; the two used to drift because each side carried its own copy.
#include <algorithm>
#include <array>
#include <cstdint>
#include <string>
#include <string_view>
#include <SDL3/SDL_gamepad.h>
#include <dolphin/pad.h>
namespace ControllerNames {
// A GameCube button as the game sees it, with the Config.toml key that selects
// it. Order matches RuntimeConfigFile::kControllerButtonKeys.
struct GameCubeButtonItem {
const char* configKey;
const char* label;
PADButton padButton;
};
inline constexpr std::array<GameCubeButtonItem, PAD_BUTTON_COUNT> kGameCubeButtons = {{
{"a", "A", PAD_BUTTON_A},
{"b", "B", PAD_BUTTON_B},
{"x", "X", PAD_BUTTON_X},
{"y", "Y", PAD_BUTTON_Y},
{"start", "Start", PAD_BUTTON_START},
{"z", "Z", PAD_TRIGGER_Z},
{"l", "L", PAD_TRIGGER_L},
{"r", "R", PAD_TRIGGER_R},
{"up", "D-pad Up", PAD_BUTTON_UP},
{"down", "D-pad Down", PAD_BUTTON_DOWN},
{"left", "D-pad Left", PAD_BUTTON_LEFT},
{"right", "D-pad Right", PAD_BUTTON_RIGHT},
}};
// A physical button on the host pad. Names are positional (south/east/...)
// rather than Xbox-labelled so one config reads the same on any hardware.
struct NativeButtonItem {
const char* configName;
const char* label;
uint32_t nativeButton;
};
inline constexpr auto kNativeButtons = std::to_array<NativeButtonItem>({
{"disabled", "Unmapped", PAD_NATIVE_BUTTON_DISABLED},
{"left_trigger", "Left trigger (LT / L2)", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_LEFT_TRIGGER, false)},
{"right_trigger", "Right trigger (RT / R2)", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, false)},
{"left_stick_left", "Left stick left", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_LEFTX, true)},
{"left_stick_right", "Left stick right", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_LEFTX, false)},
{"left_stick_up", "Left stick up", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_LEFTY, true)},
{"left_stick_down", "Left stick down", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_LEFTY, false)},
{"right_stick_left", "Right stick left", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_RIGHTX, true)},
{"right_stick_right", "Right stick right", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_RIGHTX, false)},
{"right_stick_up", "Right stick up", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_RIGHTY, true)},
{"right_stick_down", "Right stick down", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_RIGHTY, false)},
{"unmapped", "Default", PAD_NATIVE_BUTTON_INVALID},
{"south", "South (A / Cross)", SDL_GAMEPAD_BUTTON_SOUTH},
{"east", "East (B / Circle)", SDL_GAMEPAD_BUTTON_EAST},
{"west", "West (X / Square)", SDL_GAMEPAD_BUTTON_WEST},
{"north", "North (Y / Triangle)", SDL_GAMEPAD_BUTTON_NORTH},
{"back", "Back / Select / Create", SDL_GAMEPAD_BUTTON_BACK},
{"guide", "Guide / Home / PS", SDL_GAMEPAD_BUTTON_GUIDE},
{"start", "Start / Options", SDL_GAMEPAD_BUTTON_START},
{"left_stick", "Left stick click (L3)", SDL_GAMEPAD_BUTTON_LEFT_STICK},
{"right_stick", "Right stick click (R3)", SDL_GAMEPAD_BUTTON_RIGHT_STICK},
{"left_shoulder", "Left bumper (LB / L1)", SDL_GAMEPAD_BUTTON_LEFT_SHOULDER},
{"right_shoulder", "Right bumper (RB / R1)", SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER},
{"dpad_up", "D-pad Up", SDL_GAMEPAD_BUTTON_DPAD_UP},
{"dpad_down", "D-pad Down", SDL_GAMEPAD_BUTTON_DPAD_DOWN},
{"dpad_left", "D-pad Left", SDL_GAMEPAD_BUTTON_DPAD_LEFT},
{"dpad_right", "D-pad Right", SDL_GAMEPAD_BUTTON_DPAD_RIGHT},
{"misc1", "Misc 1 / Share / Mic", SDL_GAMEPAD_BUTTON_MISC1},
{"right_paddle1", "Right paddle 1", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1},
{"left_paddle1", "Left paddle 1", SDL_GAMEPAD_BUTTON_LEFT_PADDLE1},
{"right_paddle2", "Right paddle 2", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2},
{"left_paddle2", "Left paddle 2", SDL_GAMEPAD_BUTTON_LEFT_PADDLE2},
{"touchpad", "Touchpad click", SDL_GAMEPAD_BUTTON_TOUCHPAD},
{"misc2", "Misc 2", SDL_GAMEPAD_BUTTON_MISC2},
{"misc3", "Misc 3 / GC L click", SDL_GAMEPAD_BUTTON_MISC3},
{"misc4", "Misc 4 / GC R click", SDL_GAMEPAD_BUTTON_MISC4},
{"misc5", "Misc 5", SDL_GAMEPAD_BUTTON_MISC5},
{"misc6", "Misc 6", SDL_GAMEPAD_BUTTON_MISC6},
});
inline std::string TrimToken(std::string_view token) {
const size_t begin = token.find_first_not_of(" \t");
if (begin == std::string_view::npos) {
return {};
}
const size_t end = token.find_last_not_of(" \t");
return std::string(token.substr(begin, end - begin + 1));
}
inline const NativeButtonItem* FindNativeButton(std::string_view configName) {
const std::string name = TrimToken(configName.substr(0, configName.find('@')));
const auto it = std::find_if(kNativeButtons.begin(), kNativeButtons.end(),
[&](const NativeButtonItem& item) { return name == item.configName; });
return it == kNativeButtons.end() ? nullptr : &*it;
}
// Falls back to the "unmapped" entry so callers always have a label to draw.
inline const NativeButtonItem& NativeButtonForValue(uint32_t nativeButton) {
const auto it = std::find_if(kNativeButtons.begin(), kNativeButtons.end(),
[&](const NativeButtonItem& item) { return PADAxisButtonIdentity(nativeButton) == PADAxisButtonIdentity(item.nativeButton); });
return it == kNativeButtons.end() ? *FindNativeButton("unmapped") : *it;
}
inline const GameCubeButtonItem* FindGameCubeButton(std::string_view configKey) {
const std::string key = TrimToken(configKey);
const auto it = std::find_if(kGameCubeButtons.begin(), kGameCubeButtons.end(),
[&](const GameCubeButtonItem& item) { return key == item.configKey; });
return it == kGameCubeButtons.end() ? nullptr : &*it;
}
// "up" or "up,a" -> the OR of those GC button bits. Unknown names are skipped so
// a typo costs one button instead of the whole macro.
inline uint16_t GameCubeMaskFromKeys(std::string_view keys) {
uint16_t mask = 0;
size_t begin = 0;
while (begin <= keys.size()) {
const size_t comma = keys.find(',', begin);
const std::string_view token =
keys.substr(begin, comma == std::string_view::npos ? std::string_view::npos : comma - begin);
if (const GameCubeButtonItem* item = FindGameCubeButton(token)) {
mask |= static_cast<uint16_t>(item->padButton);
}
if (comma == std::string_view::npos) {
break;
}
begin = comma + 1;
}
return mask;
}
inline std::string GameCubeKeysFromMask(uint16_t mask) {
std::string keys;
for (const auto& item : kGameCubeButtons) {
if ((mask & static_cast<uint16_t>(item.padButton)) == 0) {
continue;
}
if (!keys.empty()) {
keys += ',';
}
keys += item.configKey;
}
return keys;
}
inline std::string GameCubeLabelsFromMask(uint16_t mask) {
std::string labels;
for (const auto& item : kGameCubeButtons) {
if ((mask & static_cast<uint16_t>(item.padButton)) == 0) {
continue;
}
if (!labels.empty()) {
labels += " + ";
}
labels += item.label;
}
return labels.empty() ? std::string("None") : labels;
}
} // namespace ControllerNames