Controller type refactor

This commit is contained in:
Antonio Martinez 2024-09-22 21:56:11 -07:00 committed by AJ Martinez
parent 80912b173c
commit cad7273ff4
8 changed files with 354 additions and 155 deletions

View file

@ -103,12 +103,16 @@ CV_PossibleValue_t gpdifficulty_cons_t[] = {
{0, NULL}
};
CV_PossibleValue_t descriptiveinput_cons_t[] = {
{0, "Emulator"},
{0, "\"Emulator\""},
{1, "Modern"},
{2, "6Bt. (Match)"},
{3, "6Bt. (LB LT)"},
{4, "6Bt. (LT RT)"},
{5, "6bt. (LB RB)"},
{2, "Modern Flip"},
{3, "6Bt. (Auto)"},
{4, "6Bt. (A)"},
{5, "6Bt. (B)"},
{6, "6Bt. (C)"},
{7, "6Bt. (D)"},
{8, "6Bt. (E)"},
{0, NULL}
};
// Filter consvars by EXECVERSION

View file

@ -56,6 +56,33 @@ typedef enum
NUMINPUTS = MOUSEINPUTEND,
} key_input_e;
// Helper to keep descriptive input setup slightly more readable
typedef enum
{
nc_a = KEY_JOY1,
nc_b,
nc_x,
nc_y,
nc_back,
nc_guide,
nc_start,
nc_ls,
nc_rs,
nc_lb,
nc_rb,
nc_hatup,
nc_hatdown,
nc_hatleft,
nc_hatright,
nc_touch = KEY_JOY1+20,
nc_lsleft = KEY_AXIS1+0,
nc_lsright,
nc_lsup,
nc_lsdown,
nc_lt = KEY_AXIS1+8,
nc_rt,
} named_controls_e;
typedef enum
{
gc_null = 0, // a key/button mapped to gc_null has no effect

View file

@ -5099,7 +5099,7 @@ void M_DrawProfileControls(void)
consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar;
w = V_MenuStringWidth(cv->string, 0);
V_DrawMenuString(x + 12, y + 13, ((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? warningflags : highlightflags), cv->string);
V_DrawMenuString(x + 12, y + 13, (!CV_IsSetToDefault(cv) ? warningflags : highlightflags), cv->string);
if (i == itemOn)
{
V_DrawMenuString(x - (skullAnimCounter/5), y+12, highlightflags, "\x1C"); // left arrow
@ -5288,7 +5288,45 @@ void M_DrawProfileControls(void)
if (currentMenu->menuitems[itemOn].tooltip != NULL)
{
INT32 ypos = BASEVIDHEIGHT + hintofs - 9 - 12;
V_DrawThinString(12, ypos, V_YELLOWMAP, currentMenu->menuitems[itemOn].tooltip);
if (!strcmp(currentMenu->menuitems[itemOn].tooltip, "DESCRIPTIVEINPUT-SENTINEL"))
{
char* help = va("Modern: Standard console controller prompts.");
switch (cv_dummyprofiledescriptiveinput.value)
{
case 0:
help = va("\"Emulator\": Display the default (Saturn) controls.");
break;
case 2:
help = va("Modern Flip: Swap A+X/B+Y. Use if Modern is wrong.");
break;
case 3:
help = va("6Bt. (Auto): Tries to guess your 6-button pad's layout.");
break;
case 4:
help = va("6Bt. (A): Saturn buttons, Retro-Bit Wired DInput layout.");
break;
case 5:
help = va("6Bt. (B): Saturn buttons, Retro-Bit Wireless DInput layout.");
break;
case 6:
help = va("6Bt. (C): Saturn buttons, Retro-Bit XInput layout.");
break;
case 7:
help = va("6Bt. (D): Saturn buttons, arcade/8BitDo layout. (C/Z = RT/RB)");
break;
case 8:
help = va("6Bt. (E): Saturn buttons, Hori/M30X layout. (LB/LT = LS/RS)");
break;
}
V_DrawThinString(12, ypos, V_YELLOWMAP, help);
}
else
{
V_DrawThinString(12, ypos, V_YELLOWMAP, currentMenu->menuitems[itemOn].tooltip);
}
boolean standardbuttons = gamedata->gonerlevel > GDGONER_PROFILE;
INT32 xpos = BASEVIDWIDTH - 12;

View file

@ -586,16 +586,11 @@ static void PR_ApplyProfile_Memory(UINT8 profilenum, UINT8 playernum)
// set memory cvar
CV_StealthSetValue(&cv_lastprofile[playernum], profilenum);
CONS_Printf("Applying profile memory %d to player %d", profilenum, playernum);
// If we're doing this on P1, also change current profile.
if (playernum == 0)
{
CV_StealthSetValue(&cv_currprofile, profilenum);
CONS_Printf(" and swapping currprofile");
}
CONS_Printf("\n");
}
void PR_ApplyProfile(UINT8 profilenum, UINT8 playernum)

View file

@ -18,6 +18,12 @@
menuitem_t OPTIONS_ProfileControls[] = {
{IT_HEADER, "DEVICE SETTINGS", "",
NULL, {NULL}, 0, 0},
{IT_STRING2 | IT_CVAR, "Button Display", "DESCRIPTIVEINPUT-SENTINEL",
NULL, {.cvar = &cv_dummyprofiledescriptiveinput}, 0, 0},
{IT_HEADER, "MAIN CONTROLS", "That's the stuff on the controller!!",
NULL, {NULL}, 0, 0},
@ -99,9 +105,6 @@ menuitem_t OPTIONS_ProfileControls[] = {
{IT_STRING | IT_CALL, "TRY MAPPINGS", "Test your controls.",
NULL, {.routine = M_ProfileTryController}, 0, 0},
{IT_STRING2 | IT_CVAR, "Show Button Names", "Change how help text displays your controls.",
NULL, {.cvar = &cv_dummyprofiledescriptiveinput}, 0, 0},
{IT_STRING | IT_CALL, "RESET TO DEFAULT", "Reset all controls back to default.",
NULL, {.routine = M_ProfileDefaultControls}, 0, 0},

View file

@ -40,26 +40,26 @@ Draw::TextElement& Draw::TextElement::parse(std::string_view raw)
{str "_animated", 0xA0 | lower_bits},\
{str "_pressed", 0x90 | lower_bits}
BUTTON("up", 0x00),
BUTTON("down", 0x01),
BUTTON("right", 0x02),
BUTTON("left", 0x03),
BUTTON("up", sb_up),
BUTTON("down", sb_down),
BUTTON("right", sb_right),
BUTTON("left", sb_left),
BUTTON("lua1", 0x04),
BUTTON("lua2", 0x05),
BUTTON("lua3", 0x06),
BUTTON("lua1", sb_lua1),
BUTTON("lua2", sb_lua2),
BUTTON("lua3", sb_lua3),
BUTTON("r", 0x07),
BUTTON("l", 0x08),
BUTTON("start", 0x09),
BUTTON("r", sb_r),
BUTTON("l", sb_l),
BUTTON("start", sb_start),
BUTTON("a", 0x0A),
BUTTON("b", 0x0B),
BUTTON("c", 0x0C),
BUTTON("a", sb_a),
BUTTON("b", sb_b),
BUTTON("c", sb_c),
BUTTON("x", 0x0D),
BUTTON("y", 0x0E),
BUTTON("z", 0x0F),
BUTTON("x", sb_x),
BUTTON("y", sb_y),
BUTTON("z", sb_z),
#undef BUTTON
@ -87,7 +87,7 @@ Draw::TextElement& Draw::TextElement::parse(std::string_view raw)
{"tan", 0x8F},
};
// What glyphs should be rewritten as gamecontrols?
// When we encounter a Saturn button, what gamecontrol does it represent?
static const std::unordered_map<char, gamecontrols_e> inputdefinition = {
{0x00, gc_up},
{0x01, gc_down},
@ -111,75 +111,22 @@ Draw::TextElement& Draw::TextElement::parse(std::string_view raw)
{0x0F, gc_z},
};
// What physical binds should be rewritten as base Saturn icons?
// What physical binds should appear as Saturn icons anyway?
// (We don't have generic binds for stick/dpad directions, so
// using the existing arrow graphics is the best thing here.)
static const std::unordered_map<INT32, char> prettyinputs = {
{KEY_UPARROW, 0x00},
{KEY_DOWNARROW, 0x01},
{KEY_RIGHTARROW, 0x02},
{KEY_LEFTARROW, 0x03},
{KEY_JOY1+11, 0x00},
{KEY_JOY1+12, 0x01},
{KEY_JOY1+14, 0x02},
{KEY_JOY1+13, 0x03},
{KEY_AXIS1+0, 0x03}, // Left
{KEY_AXIS1+1, 0x02}, // Right
{KEY_AXIS1+2, 0x00}, // Up
{KEY_AXIS1+3, 0x01}, // Down
};
// What physical binds should be rewritten as generic icons?
static const std::unordered_map<INT32, char> genericinputs = {
{KEY_JOY1+0, 0x00}, // ABXY
{KEY_JOY1+1, 0x01},
{KEY_JOY1+2, 0x02},
{KEY_JOY1+3, 0x03},
{KEY_JOY1+9, 0x04}, // LBRB
{KEY_JOY1+10, 0x05},
{KEY_AXIS1+8, 0x06}, // LTRT
{KEY_AXIS1+9, 0x07},
{KEY_JOY1+6, 0x08}, // NAV
{KEY_JOY1+4, 0x09},
{KEY_JOY1+7, 0x0A}, // CLICK
{KEY_JOY1+8, 0x0B},
};
// Saturn Type 1 - Retrobit Wired Dinput, RB RT LB LT
static const std::unordered_map<INT32, char> saturntype1 = {
{KEY_JOY1+0, 0x0A}, // ABXY
{KEY_JOY1+1, 0x0B},
{KEY_JOY1+2, 0x0D},
{KEY_JOY1+3, 0x0E},
{KEY_JOY1+9, 0x08}, // LBRB
{KEY_JOY1+10, 0x0C},
{KEY_AXIS1+8, 0x07}, // LTRT
{KEY_AXIS1+9, 0x0F},
{KEY_JOY1+6, 0x09}, // NAV
};
// Saturn Type 2 - Retrobit Wireless Dinput, LB RB LT RT
static const std::unordered_map<INT32, char> saturntype2 = {
{KEY_JOY1+0, 0x0A}, // ABXY
{KEY_JOY1+1, 0x0B},
{KEY_JOY1+2, 0x0D},
{KEY_JOY1+3, 0x0E},
{KEY_JOY1+9, 0x0C}, // LBRB
{KEY_JOY1+10, 0x0F},
{KEY_AXIS1+8, 0x08}, // LTRT
{KEY_AXIS1+9, 0x07},
{KEY_JOY1+6, 0x09}, // NAV
};
// Saturn Type 3 - Retrobit Wireless Xinput, RT LT LB RB
static const std::unordered_map<INT32, char> saturntype3 = {
{KEY_JOY1+0, 0x0A}, // ABXY
{KEY_JOY1+1, 0x0B},
{KEY_JOY1+2, 0x0D},
{KEY_JOY1+3, 0x0E},
{KEY_JOY1+9, 0x08}, // LBRB
{KEY_JOY1+10, 0x07},
{KEY_AXIS1+8, 0x0F}, // LTRT
{KEY_AXIS1+9, 0x0C},
{KEY_JOY1+6, 0x09}, // NAV
{KEY_UPARROW, sb_up},
{KEY_DOWNARROW, sb_down},
{KEY_LEFTARROW, sb_left},
{KEY_RIGHTARROW, sb_right},
{nc_hatup, sb_up},
{nc_hatdown, sb_down},
{nc_hatleft, sb_left},
{nc_hatright, sb_right},
{nc_lsup, sb_up},
{nc_lsdown, sb_down},
{nc_lsleft, sb_left},
{nc_lsright, sb_right},
};
string_.clear();
@ -251,44 +198,65 @@ Draw::TextElement& Draw::TextElement::parse(std::string_view raw)
// EXTRA: descriptiveinput values above 1 translate binds back to Saturn buttons,
// with various modes for various fucked up 6bt pads
std::unordered_map<INT32, char> saturnconfig = {};
std::unordered_map<INT32, char> padconfig = {};
switch (cv_descriptiveinput[localplayer].value)
{
case 1:
padconfig = standardpad;
break;
case 2:
padconfig = flippedpad;
break;
case 3:
{
// Most players will map gc_L to their physical L button,
// and gc_R to their physical R button. Assuming this is
// true, try to guess their physical layout based on what
// they've chosen.
INT32 leftbumper = G_FindPlayerBindForGameControl(localplayer, gc_l);
INT32 rightbumper = G_FindPlayerBindForGameControl(localplayer, gc_r);
if (leftbumper == KEY_JOY1+9 && rightbumper == KEY_AXIS1+8)
if (leftbumper == nc_lb && rightbumper == nc_lt)
{
saturnconfig = saturntype1; // LB LT
// CONS_Printf("Saturn type 1\n");
padconfig = saturntypeA;
}
else if (leftbumper == KEY_AXIS1+8 && rightbumper == KEY_AXIS1+9)
else if (leftbumper == nc_lt && rightbumper == nc_rt)
{
saturnconfig = saturntype2; // LT RT
// CONS_Printf("Saturn type 2\n");
padconfig = saturntypeB;
}
else if (leftbumper == KEY_JOY1+9 && rightbumper == KEY_JOY1+10)
else if (leftbumper == nc_lb && rightbumper == nc_rb)
{
saturnconfig = saturntype3; // LB RB
// CONS_Printf("Saturn type 3\n");
padconfig = saturntypeC;
}
else if (leftbumper == nc_ls && rightbumper == nc_lb)
{
padconfig = saturntypeE;
}
else if (leftbumper == nc_rs && rightbumper == nc_lt)
{
padconfig = saturntypeE; // Not a typo! Users might bind a Hori layout pad to either bumpers or triggers
}
else
{
saturnconfig = saturntype1; // :( ???
// CONS_Printf("Unknown, falling back to type 1\n");
padconfig = saturntypeA; // :( ???
}
break;
}
case 3:
saturnconfig = saturntype1;
break;
case 4:
saturnconfig = saturntype2;
padconfig = saturntypeA;
break;
case 5:
saturnconfig = saturntype3;
padconfig = saturntypeB;
break;
case 6:
padconfig = saturntypeC;
break;
case 7:
padconfig = saturntypeD;
break;
case 8:
padconfig = saturntypeE;
break;
}
@ -296,15 +264,20 @@ Draw::TextElement& Draw::TextElement::parse(std::string_view raw)
{
string_.push_back((it->second & 0xF0) | pretty->second); // original invocation has the animation bits, but the glyph bits come from the table
}
else if (auto st = saturnconfig.find(bind); st != saturnconfig.end())
else if (auto pad = padconfig.find(bind); pad != padconfig.end())
{
string_.push_back((it->second & 0xF0) | st->second); // original invocation has the animation bits, but the glyph bits come from the table
}
else if (auto generic = genericinputs.find(bind); generic != genericinputs.end()) // Non-directional gamepad input, display it to the player as they are
{
string_.push_back(0xEF); // Control code: "switch to descriptive input mode" - Saturn buttons will draw as generic gamepad buttons
string_.push_back(0xEB); // Control code: "large button"
string_.push_back((it->second & 0xF0) | generic->second); // original invocation has the animation bits, but the glyph bits come from the table
// If high bits are set, this is meant to be a generic button.
if (pad->second & 0xF0)
{
string_.push_back(0xEF); // Control code: "switch to descriptive input mode" - buttons will draw as generics
string_.push_back(0xEB); // Control code: "large button"
}
// Clear high bits so we can add animation bits back cleanly.
pad->second = pad->second & (0x0F);
// original invocation has the animation bits, but the glyph bits come from the table
string_.push_back((it->second & 0xF0) | pad->second);
}
else
{

View file

@ -23,6 +23,165 @@
#include "screen.h" // BASEVIDWIDTH
#include "typedef.h"
#include "v_video.h"
#include "g_input.h"
// Helpers for setting up pad napping nonsense
typedef enum
{
gb_mask = 0xF0,
gb_a = 0xF0,
gb_b,
gb_x,
gb_y,
gb_lb,
gb_rb,
gb_lt,
gb_rt,
gb_start,
gb_back,
gb_ls,
gb_rs,
gb_dpad
} generic_buttons_e;
typedef enum
{
sb_up = 0x00,
sb_down,
sb_right,
sb_left,
sb_lua1,
sb_lua2,
sb_lua3,
sb_r,
sb_l,
sb_start,
sb_a,
sb_b,
sb_c,
sb_x,
sb_y,
sb_z
} saturn_buttons_e;
// Garden-variety standard gamepad
static const std::unordered_map<INT32, char> standardpad = {
{nc_a, gb_a},
{nc_b, gb_b},
{nc_x, gb_x},
{nc_y, gb_y},
{nc_lb, gb_lb},
{nc_rb, gb_rb},
{nc_lt, gb_lt},
{nc_rt, gb_rt},
{nc_start, gb_start},
{nc_back, gb_back},
{nc_ls, gb_ls},
{nc_rs, gb_rs},
};
// Standard gamepad, but evil Nintendo layout flip was applied by your
// controller firmware or Steam Input—swap B/A and X/Y
static const std::unordered_map<INT32, char> flippedpad = {
{nc_a, gb_b},
{nc_b, gb_a},
{nc_x, gb_y},
{nc_y, gb_x},
{nc_lb, gb_lb},
{nc_rb, gb_rb},
{nc_lt, gb_lt},
{nc_rt, gb_rt},
{nc_start, gb_start},
{nc_back, gb_back},
{nc_ls, gb_ls},
{nc_rs, gb_rs},
};
// Saturn Type A - Retrobit Wired Dinput, RB RT LB LT (CZLR)
static const std::unordered_map<INT32, char> saturntypeA = {
{nc_a, sb_a},
{nc_b, sb_b},
{nc_x, sb_x},
{nc_y, sb_y},
{nc_rb, sb_c},
{nc_rt, sb_z},
{nc_lb, sb_l},
{nc_lt, sb_r},
{nc_start, gb_start},
{nc_back, gb_back},
{nc_ls, gb_ls},
{nc_rs, gb_rs},
};
// Saturn Type B - Retrobit Wireless Dinput, LB RB LT RT (CZLR)
static const std::unordered_map<INT32, char> saturntypeB = {
{nc_a, sb_a},
{nc_b, sb_b},
{nc_x, sb_x},
{nc_y, sb_y},
{nc_lb, sb_c},
{nc_rb, sb_z},
{nc_lt, sb_l},
{nc_rt, sb_r},
{nc_start, gb_start},
{nc_back, gb_back},
{nc_ls, gb_ls},
{nc_rs, gb_rs},
};
// Saturn Type C - Retrobit Xinput, RT LT LB RB (CZLR)
static const std::unordered_map<INT32, char> saturntypeC = {
{nc_a, sb_a},
{nc_b, sb_b},
{nc_x, sb_x},
{nc_y, sb_y},
{nc_rt, sb_c},
{nc_lb, sb_z},
{nc_lb, sb_l},
{nc_rb, sb_r},
{nc_start, gb_start},
{nc_back, gb_back},
{nc_ls, gb_ls},
{nc_rs, gb_rs},
};
// Saturn Type D - 8BitDo M30 / arcade, RT RB LB LT (CZLR)
// This cannot be disambiguated (shares L/R with type 1)
// but is more spatially correct w/r/t SDL expectations
// and standard arcade mapping (Z on top, C on bottom)
static const std::unordered_map<INT32, char> saturntypeD = {
{nc_a, sb_a},
{nc_b, sb_b},
{nc_x, sb_x},
{nc_y, sb_y},
{nc_rt, sb_c},
{nc_rb, sb_z},
{nc_lb, sb_l},
{nc_lt, sb_r},
{nc_start, gb_start},
{nc_back, gb_back},
{nc_ls, gb_ls},
{nc_rs, gb_rs},
};
// Saturn Type E - Hori, RT RB (CZ) with some fucked up bumpers/triggers
// The Hori layout is, to my knowledge, the only 6bt one that has fully
// unique buttons in every slot while having both bumpers and triggers,
// so there's no way to accurately portray it without using generics.
static const std::unordered_map<INT32, char> saturntypeE = {
{nc_a, sb_a},
{nc_b, sb_b},
{nc_x, sb_x},
{nc_y, sb_y},
{nc_rt, sb_c},
{nc_rb, sb_z},
{nc_lb, gb_rb},
{nc_lt, gb_rt},
{nc_ls, gb_lb},
{nc_rs, gb_lt},
{nc_start, gb_start},
{nc_back, gb_back},
};
namespace srb2
{

View file

@ -2835,27 +2835,27 @@ void V_DrawStringScaled(
{
switch (c & 0x0F)
{
case 0x00: return {{0, 3, Draw::Button::up}};
case 0x01: return {{0, 3, Draw::Button::down}};
case 0x02: return {{0, 3, Draw::Button::right}};
case 0x03: return {{0, 3, Draw::Button::left}};
case sb_up: return {{0, 3, Draw::Button::up}};
case sb_down: return {{0, 3, Draw::Button::down}};
case sb_right: return {{0, 3, Draw::Button::right}};
case sb_left: return {{0, 3, Draw::Button::left}};
case 0x04: return {{0, 4, Draw::Button::lua1}};
case 0x05: return {{0, 4, Draw::Button::lua2}};
case 0x06: return {{0, 4, Draw::Button::lua3}};
case sb_lua1: return {{0, 4, Draw::Button::lua1}};
case sb_lua2: return {{0, 4, Draw::Button::lua2}};
case sb_lua3: return {{0, 4, Draw::Button::lua3}};
case 0x07: return {{0, 2, Draw::Button::r}};
case 0x08: return {{0, 2, Draw::Button::l}};
case sb_r: return {{0, 2, Draw::Button::r}};
case sb_l: return {{0, 2, Draw::Button::l}};
case 0x09: return {{0, 1, Draw::Button::start}};
case sb_start: return {{0, 1, Draw::Button::start}};
case 0x0A: return {{2, 1, Draw::Button::a}};
case 0x0B: return {{2, 1, Draw::Button::b}};
case 0x0C: return {{2, 1, Draw::Button::c}};
case sb_a: return {{2, 1, Draw::Button::a}};
case sb_b: return {{2, 1, Draw::Button::b}};
case sb_c: return {{2, 1, Draw::Button::c}};
case 0x0D: return {{2, 1, Draw::Button::x}};
case 0x0E: return {{2, 1, Draw::Button::y}};
case 0x0F: return {{2, 1, Draw::Button::z}};
case sb_x: return {{2, 1, Draw::Button::x}};
case sb_y: return {{2, 1, Draw::Button::y}};
case sb_z: return {{2, 1, Draw::Button::z}};
default: return {};
}
@ -2906,21 +2906,21 @@ void V_DrawStringScaled(
auto bt_inst = [c]() -> std::optional<BtConf>
{
switch (c & 0x0F)
switch ((c & 0x0F) | gb_mask)
{
case 0x00: return {{0, 2, Draw::GenericButton::a}};
case 0x01: return {{0, 2, Draw::GenericButton::b}};
case 0x02: return {{0, 2, Draw::GenericButton::x}};
case 0x03: return {{0, 2, Draw::GenericButton::y}};
case 0x04: return {{1, 3, Draw::GenericButton::lb}};
case 0x05: return {{1, 3, Draw::GenericButton::rb}};
case 0x06: return {{1, 4, Draw::GenericButton::lt}};
case 0x07: return {{1, 4, Draw::GenericButton::rt}};
case 0x08: return {{1, 6, Draw::GenericButton::start}};
case 0x09: return {{1, 6, Draw::GenericButton::back}};
case 0x0A: return {{0, 5, Draw::GenericButton::ls}};
case 0x0B: return {{0, 5, Draw::GenericButton::rs}};
case 0x0C: return {{0, 4, Draw::GenericButton::dpad}};
case gb_a: return {{0, 2, Draw::GenericButton::a}};
case gb_b: return {{0, 2, Draw::GenericButton::b}};
case gb_x: return {{0, 2, Draw::GenericButton::x}};
case gb_y: return {{0, 2, Draw::GenericButton::y}};
case gb_lb: return {{1, 3, Draw::GenericButton::lb}};
case gb_rb: return {{1, 3, Draw::GenericButton::rb}};
case gb_lt: return {{1, 4, Draw::GenericButton::lt}};
case gb_rt: return {{1, 4, Draw::GenericButton::rt}};
case gb_start: return {{1, 6, Draw::GenericButton::start}};
case gb_back: return {{1, 6, Draw::GenericButton::back}};
case gb_ls: return {{0, 5, Draw::GenericButton::ls}};
case gb_rs: return {{0, 5, Draw::GenericButton::rs}};
case gb_dpad: return {{0, 4, Draw::GenericButton::dpad}};
default: return {};
}
}();