Allow using A and B to navigate the controller binding flow

This commit is contained in:
Sam Lantinga 2023-07-16 12:14:52 -07:00
parent f3fe579cf0
commit 8f21be87fc
3 changed files with 212 additions and 61 deletions

View file

@ -83,6 +83,8 @@ static Controller *controllers;
static Controller *controller;
static SDL_JoystickID mapping_controller = 0;
static int binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
static int last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
static SDL_bool binding_flow = SDL_FALSE;
static Uint64 binding_advance_time = 0;
static SDL_Joystick *virtual_joystick = NULL;
static SDL_GamepadAxis virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID;
@ -90,6 +92,44 @@ static float virtual_axis_start_x;
static float virtual_axis_start_y;
static SDL_GamepadButton virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID;
static int s_arrBindingOrder[] = {
/* Standard sequence */
SDL_GAMEPAD_BUTTON_A,
SDL_GAMEPAD_BUTTON_B,
SDL_GAMEPAD_BUTTON_Y,
SDL_GAMEPAD_BUTTON_X,
SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE,
SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE,
SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE,
SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE,
SDL_GAMEPAD_BUTTON_LEFT_STICK,
SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE,
SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE,
SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE,
SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE,
SDL_GAMEPAD_BUTTON_RIGHT_STICK,
SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER,
SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER,
SDL_GAMEPAD_BUTTON_DPAD_UP,
SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
SDL_GAMEPAD_BUTTON_DPAD_DOWN,
SDL_GAMEPAD_BUTTON_DPAD_LEFT,
SDL_GAMEPAD_BUTTON_BACK,
SDL_GAMEPAD_BUTTON_START,
SDL_GAMEPAD_BUTTON_GUIDE,
SDL_GAMEPAD_BUTTON_MISC1,
SDL_GAMEPAD_ELEMENT_INVALID,
/* Paddle sequence */
SDL_GAMEPAD_BUTTON_PADDLE1,
SDL_GAMEPAD_BUTTON_PADDLE2,
SDL_GAMEPAD_BUTTON_PADDLE3,
SDL_GAMEPAD_BUTTON_PADDLE4,
SDL_GAMEPAD_ELEMENT_INVALID,
};
static const char *GetSensorName(SDL_SensorType sensor)
{
@ -161,7 +201,7 @@ static void CyclePS5TriggerEffect(Controller *device)
SDL_SendGamepadEffect(device->gamepad, &state, sizeof(state));
}
static void ClearButtonHighlights()
static void ClearButtonHighlights(void)
{
ClearGamepadImage(image);
SetGamepadDisplayHighlight(gamepad_elements, SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
@ -215,11 +255,17 @@ static void SetAndFreeGamepadMapping(char *mapping)
SDL_free(mapping);
}
static void SetCurrentBindingElement(int element)
static void SetCurrentBindingElement(int element, SDL_bool flow)
{
int i;
if (element == SDL_GAMEPAD_ELEMENT_INVALID) {
last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
} else {
last_binding_element = binding_element;
}
binding_element = element;
binding_flow = flow || (element == SDL_GAMEPAD_BUTTON_A);
binding_advance_time = 0;
for (i = 0; i < controller->num_axes; ++i) {
@ -229,45 +275,8 @@ static void SetCurrentBindingElement(int element)
SetGamepadDisplaySelected(gamepad_elements, element);
}
static void SetNextBindingElement()
static void SetNextBindingElement(void)
{
static int s_arrBindingOrder[] = {
/* Standard sequence */
SDL_GAMEPAD_BUTTON_A,
SDL_GAMEPAD_BUTTON_B,
SDL_GAMEPAD_BUTTON_Y,
SDL_GAMEPAD_BUTTON_X,
SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE,
SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE,
SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE,
SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE,
SDL_GAMEPAD_BUTTON_LEFT_STICK,
SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE,
SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE,
SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE,
SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE,
SDL_GAMEPAD_BUTTON_RIGHT_STICK,
SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER,
SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER,
SDL_GAMEPAD_BUTTON_DPAD_UP,
SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
SDL_GAMEPAD_BUTTON_DPAD_DOWN,
SDL_GAMEPAD_BUTTON_DPAD_LEFT,
SDL_GAMEPAD_BUTTON_BACK,
SDL_GAMEPAD_BUTTON_START,
SDL_GAMEPAD_BUTTON_GUIDE,
SDL_GAMEPAD_BUTTON_MISC1,
SDL_GAMEPAD_ELEMENT_INVALID,
/* Paddle sequence */
SDL_GAMEPAD_BUTTON_PADDLE1,
SDL_GAMEPAD_BUTTON_PADDLE2,
SDL_GAMEPAD_BUTTON_PADDLE3,
SDL_GAMEPAD_BUTTON_PADDLE4,
SDL_GAMEPAD_ELEMENT_INVALID,
};
int i;
if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
@ -276,21 +285,40 @@ static void SetNextBindingElement()
for (i = 0; i < SDL_arraysize(s_arrBindingOrder); ++i) {
if (binding_element == s_arrBindingOrder[i]) {
SetCurrentBindingElement(s_arrBindingOrder[i + 1]);
SetCurrentBindingElement(s_arrBindingOrder[i + 1], SDL_TRUE);
return;
}
}
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID);
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
}
static void CancelBinding(void)
static void SetPrevBindingElement(void)
{
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID);
int i;
if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
return;
}
for (i = 1; i < SDL_arraysize(s_arrBindingOrder); ++i) {
if (binding_element == s_arrBindingOrder[i]) {
SetCurrentBindingElement(s_arrBindingOrder[i - 1], SDL_TRUE);
return;
}
}
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
}
static void StopBinding(void)
{
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
}
static void CommitBindingElement(const char *binding, SDL_bool force)
{
char *mapping;
int direction = 1;
SDL_bool ignore_binding = SDL_FALSE;
if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
return;
@ -304,7 +332,6 @@ static void CommitBindingElement(const char *binding, SDL_bool force)
/* If the controller generates multiple events for a single element, pick the best one */
if (!force && binding_advance_time) {
SDL_bool ignore_binding = SDL_FALSE;
char *current = GetElementBinding(mapping, binding_element);
SDL_bool native_button = (binding_element < SDL_GAMEPAD_BUTTON_MAX);
SDL_bool native_axis = (binding_element >= SDL_GAMEPAD_BUTTON_MAX &&
@ -348,18 +375,60 @@ static void CommitBindingElement(const char *binding, SDL_bool force)
}
}
SDL_free(current);
}
if (ignore_binding) {
return;
if (!ignore_binding && binding_flow && !force) {
int existing = GetElementForBinding(mapping, binding);
if (existing != SDL_GAMEPAD_ELEMENT_INVALID) {
if (existing == SDL_GAMEPAD_BUTTON_A) {
if (binding_element == SDL_GAMEPAD_BUTTON_A) {
/* Just move on to the next one */
ignore_binding = SDL_TRUE;
SetNextBindingElement();
} else {
/* Clear the current binding and move to the next one */
binding = NULL;
direction = 1;
force = SDL_TRUE;
}
} else if (existing == SDL_GAMEPAD_BUTTON_B) {
if (binding_element != SDL_GAMEPAD_BUTTON_A &&
last_binding_element != SDL_GAMEPAD_BUTTON_A) {
/* Clear the current binding and move to the previous one */
binding = NULL;
direction = -1;
force = SDL_TRUE;
}
} else if (existing == binding_element) {
/* We're rebinding the same thing, just move to the next one */
ignore_binding = SDL_TRUE;
SetNextBindingElement();
} else if (binding_element != SDL_GAMEPAD_BUTTON_A &&
binding_element != SDL_GAMEPAD_BUTTON_B) {
ignore_binding = SDL_TRUE;
}
}
}
if (ignore_binding) {
SDL_free(mapping);
return;
}
mapping = ClearMappingBinding(mapping, binding);
mapping = SetElementBinding(mapping, binding_element, binding);
SetAndFreeGamepadMapping(mapping);
if (force) {
SetNextBindingElement();
if (binding_flow) {
if (direction > 0) {
SetNextBindingElement();
} else if (direction < 0) {
SetPrevBindingElement();
}
} else {
StopBinding();
}
} else {
/* Wait to see if any more bindings come in */
binding_advance_time = SDL_GetTicks() + 30;
@ -383,9 +452,9 @@ static void SetDisplayMode(ControllerDisplayMode mode)
}
mapping_controller = controller->id;
if (MappingHasBindings(backup_mapping)) {
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID);
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
} else {
SetCurrentBindingElement(SDL_GAMEPAD_BUTTON_A);
SetCurrentBindingElement(SDL_GAMEPAD_BUTTON_A, SDL_TRUE);
}
} else {
if (backup_mapping) {
@ -393,7 +462,7 @@ static void SetDisplayMode(ControllerDisplayMode mode)
backup_mapping = NULL;
}
mapping_controller = 0;
CancelBinding();
StopBinding();
}
display_mode = mode;
@ -416,7 +485,7 @@ static void CancelMapping(void)
static void ClearMapping(void)
{
SetAndFreeGamepadMapping(NULL);
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID);
SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
}
static void CopyMapping(void)
@ -431,7 +500,7 @@ static void PasteMapping(void)
if (controller) {
char *mapping = SDL_GetClipboardText();
if (MappingHasBindings(mapping)) {
CancelBinding();
StopBinding();
SetAndFreeGamepadMapping(mapping);
} else {
/* Not a valid mapping, ignore it */
@ -988,6 +1057,7 @@ static void DrawBindingTips(SDL_Renderer *renderer)
} else {
Uint8 r, g, b, a;
SDL_FRect rect;
SDL_bool bound_A, bound_B;
y -= (FONT_CHARACTER_SIZE + BUTTON_MARGIN) / 2;
@ -1003,7 +1073,14 @@ static void DrawBindingTips(SDL_Renderer *renderer)
SDLTest_DrawString(renderer, (float)x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, (float)y, text);
y += (FONT_CHARACTER_SIZE + BUTTON_MARGIN);
text = "(press SPACE to clear binding and ESC to cancel)";
bound_A = MappingHasElement(controller->mapping, SDL_GAMEPAD_BUTTON_A);
bound_B = MappingHasElement(controller->mapping, SDL_GAMEPAD_BUTTON_B);
if (binding_flow && bound_A && bound_B) {
text = "(press A to skip, B to go back, and ESC to cancel)";
} else {
text = "(press SPACE to clear binding and ESC to cancel)";
}
SDLTest_DrawString(renderer, (float)x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, (float)y, text);
}
}
@ -1281,7 +1358,9 @@ static void loop(void *arg)
gamepad_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, event.button.x, event.button.y);
if (gamepad_element != SDL_GAMEPAD_ELEMENT_INVALID) {
SetCurrentBindingElement(gamepad_element);
/* Set this to SDL_FALSE if you don't want to start the binding flow at this point */
const SDL_bool should_start_flow = SDL_TRUE;
SetCurrentBindingElement(gamepad_element, should_start_flow);
}
joystick_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, event.button.x, event.button.y);
@ -1329,7 +1408,7 @@ static void loop(void *arg)
ClearBinding();
} else if (event.key.keysym.sym == SDLK_ESCAPE) {
if (binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
CancelBinding();
StopBinding();
} else {
CancelMapping();
}
@ -1348,7 +1427,11 @@ static void loop(void *arg)
in case a gamepad sends multiple events for a single control (e.g. axis and button for trigger)
*/
if (binding_advance_time && SDL_GetTicks() > (binding_advance_time + 30)) {
SetNextBindingElement();
if (binding_flow) {
SetNextBindingElement();
} else {
StopBinding();
}
}
/* blank screen, set up for drawing this frame. */