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

@ -2248,6 +2248,22 @@ static char *RecreateMapping(MappingParts *parts, char *mapping)
return mapping;
}
static SDL_bool MappingHasKey(const char *mapping, const char *key)
{
int i;
MappingParts parts;
SDL_bool result = SDL_FALSE;
SplitMapping(mapping, &parts);
i = FindMappingKey(&parts, key);
if (i >= 0) {
result = SDL_TRUE;
}
FreeMappingParts(&parts);
return result;
}
static char *GetMappingValue(const char *mapping, const char *key)
{
int i;
@ -2484,6 +2500,17 @@ static const char *GetElementKey(int element)
}
}
SDL_bool MappingHasElement(const char *mapping, int element)
{
const char *key;
key = GetElementKey(element);
if (!key) {
return SDL_FALSE;
}
return MappingHasKey(mapping, key);
}
char *GetElementBinding(const char *mapping, int element)
{
const char *key;
@ -2504,6 +2531,34 @@ char *SetElementBinding(char *mapping, int element, const char *binding)
}
}
int GetElementForBinding(char *mapping, const char *binding)
{
MappingParts parts;
int i, element;
int result = SDL_GAMEPAD_ELEMENT_INVALID;
if (!binding) {
return SDL_GAMEPAD_ELEMENT_INVALID;
}
SplitMapping(mapping, &parts);
for (i = 0; i < parts.num_elements; ++i) {
if (SDL_strcmp(binding, parts.values[i]) == 0) {
for (element = 0; element < SDL_GAMEPAD_ELEMENT_MAX; ++element) {
const char *key = GetElementKey(element);
if (key && SDL_strcmp(key, parts.keys[i]) == 0) {
result = element;
break;
}
}
break;
}
}
FreeMappingParts(&parts);
return result;
}
SDL_bool MappingHasBinding(const char *mapping, const char *binding)
{
MappingParts parts;
@ -2515,7 +2570,7 @@ SDL_bool MappingHasBinding(const char *mapping, const char *binding)
}
SplitMapping(mapping, &parts);
for (i = parts.num_elements - 1; i >= 0; --i) {
for (i = 0; i < parts.num_elements; ++i) {
if (SDL_strcmp(binding, parts.values[i]) == 0) {
result = SDL_TRUE;
break;
@ -2530,6 +2585,7 @@ char *ClearMappingBinding(char *mapping, const char *binding)
{
MappingParts parts;
int i;
SDL_bool modified = SDL_FALSE;
if (!binding) {
return mapping;
@ -2539,7 +2595,13 @@ char *ClearMappingBinding(char *mapping, const char *binding)
for (i = parts.num_elements - 1; i >= 0; --i) {
if (SDL_strcmp(binding, parts.values[i]) == 0) {
RemoveMappingValueAt(&parts, i);
modified = SDL_TRUE;
}
}
return RecreateMapping(&parts, mapping);
if (modified) {
return RecreateMapping(&parts, mapping);
} else {
FreeMappingParts(&parts);
return mapping;
}
}

View File

@ -134,15 +134,21 @@ extern char *SetMappingName(char *mapping, const char *name);
/* Return the type from a mapping, which should be freed using SDL_free(), or NULL if there is no type specified */
extern char *GetMappingType(const char *mapping);
/* Set the name in a mapping, freeing the mapping passed in and returning a new mapping */
/* Set the type in a mapping, freeing the mapping passed in and returning a new mapping */
extern char *SetMappingType(char *mapping, const char *type);
/* Return true if a mapping has this element bound */
extern SDL_bool MappingHasElement(const char *mapping, int element);
/* Get the binding for an element, which should be freed using SDL_free(), or NULL if the element isn't bound */
extern char *GetElementBinding(const char *mapping, int element);
/* Set the binding for an element, or NULL to clear it */
/* Set the binding for an element, or NULL to clear it, freeing the mapping passed in and returning a new mapping */
extern char *SetElementBinding(char *mapping, int element, const char *binding);
/* Get the element for a binding, or SDL_GAMEPAD_ELEMENT_INVALID if that binding isn't used */
extern int GetElementForBinding(char *mapping, const char *binding);
/* Return true if a mapping contains this binding */
extern SDL_bool MappingHasBinding(const char *mapping, const char *binding);

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. */