SDL: Additional work on binding mode for testcontroller

From d90b938f0f6a0ba75890662f96239fdc467a2d1b Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 15 Jul 2023 00:00:53 -0700
Subject: [PATCH] Additional work on binding mode for testcontroller

---
 test/gamepadutils.c   | 1457 +++++++++++++++++++++++++++++++++++------
 test/gamepadutils.h   |   80 ++-
 test/testcontroller.c |  772 +++++++++++++++++-----
 3 files changed, 1943 insertions(+), 366 deletions(-)

diff --git a/test/gamepadutils.c b/test/gamepadutils.c
index aecd2a6c5240..c678d3618ffe 100644
--- a/test/gamepadutils.c
+++ b/test/gamepadutils.c
@@ -31,7 +31,8 @@
 #include "gamepad_axis_arrow.h"
 #include "gamepad_button_background.h"
 
-/* This is indexed by SDL_GamepadButton. */
+
+/* This is indexed by gamepad element */
 static const struct
 {
     int x;
@@ -44,9 +45,9 @@ static const struct
     { 199, 157 }, /* SDL_GAMEPAD_BUTTON_BACK */
     { 257, 153 }, /* SDL_GAMEPAD_BUTTON_GUIDE */
     { 314, 157 }, /* SDL_GAMEPAD_BUTTON_START */
-    { 100, 179 },  /* SDL_GAMEPAD_BUTTON_LEFT_STICK */
-    { 330, 255 }, /* SDL_GAMEPAD_BUTTON_RIGHT_STICK */
-    { 102, 65 },   /* SDL_GAMEPAD_BUTTON_LEFT_SHOULDER */
+    {  98, 177 }, /* SDL_GAMEPAD_BUTTON_LEFT_STICK */
+    { 331, 254 }, /* SDL_GAMEPAD_BUTTON_RIGHT_STICK */
+    { 102, 65 },  /* SDL_GAMEPAD_BUTTON_LEFT_SHOULDER */
     { 421, 61 },  /* SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER */
     { 179, 213 }, /* SDL_GAMEPAD_BUTTON_DPAD_UP */
     { 179, 274 }, /* SDL_GAMEPAD_BUTTON_DPAD_DOWN */
@@ -59,19 +60,23 @@ static const struct
     { 355, 200 }, /* SDL_GAMEPAD_BUTTON_PADDLE4 */
 };
 
-/* This is indexed by SDL_GamepadAxis. */
+/* This is indexed by gamepad element */
 static const struct
 {
     int x;
     int y;
     double angle;
 } axis_positions[] = {
-    { 99, 178, 270.0 },  /* LEFTX */
-    { 99, 178, 0.0 },    /* LEFTY */
-    { 331, 256, 270.0 }, /* RIGHTX */
-    { 331, 256, 0.0 },   /* RIGHTY */
-    { 116, 5, 0.0 },    /* TRIGGERLEFT */
-    { 400, 5, 0.0 },   /* TRIGGERRIGHT */
+    { 99, 178, 270.0 },  /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE */
+    { 99, 178, 90.0 },   /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE */
+    { 99, 178, 0.0 },    /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE */
+    { 99, 178, 180.0 },  /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE */
+    { 331, 256, 270.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE */
+    { 331, 256, 90.0 },  /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE */
+    { 331, 256, 0.0 },   /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE */
+    { 331, 256, 180.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE */
+    { 116, 5, 180.0 },   /* SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER */
+    { 400, 5, 180.0 },   /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER */
 };
 
 static SDL_Rect touchpad_area = {
@@ -118,8 +123,7 @@ struct GamepadImage
     GamepadImageFaceStyle face_style;
     ControllerDisplayMode display_mode;
 
-    SDL_bool buttons[SDL_GAMEPAD_BUTTON_MAX];
-    int axes[SDL_GAMEPAD_AXIS_MAX];
+    SDL_bool elements[SDL_GAMEPAD_ELEMENT_MAX];
 
     SDL_JoystickPowerLevel battery_level;
 
@@ -213,6 +217,21 @@ void SetGamepadImagePosition(GamepadImage *ctx, int x, int y)
     ctx->y = y;
 }
 
+void GetGamepadImageArea(GamepadImage *ctx, SDL_Rect *area)
+{
+    if (!ctx) {
+        SDL_zerop(area);
+    }
+
+    area->x = ctx->x;
+    area->y = ctx->y;
+    area->w = ctx->gamepad_width;
+    area->h = ctx->gamepad_height;
+    if (ctx->showing_touchpad) {
+        area->h += ctx->touchpad_height;
+    }
+}
+
 void SetGamepadImageShowingFront(GamepadImage *ctx, SDL_bool showing_front)
 {
     if (!ctx) {
@@ -231,6 +250,15 @@ void SetGamepadImageFaceStyle(GamepadImage *ctx, GamepadImageFaceStyle face_styl
     ctx->face_style = face_style;
 }
 
+GamepadImageFaceStyle GetGamepadImageFaceStyle(GamepadImage *ctx)
+{
+    if (!ctx) {
+        return GAMEPAD_IMAGE_FACE_BLANK;
+    }
+
+    return ctx->face_style;
+}
+
 void SetGamepadImageDisplayMode(GamepadImage *ctx, ControllerDisplayMode display_mode)
 {
     if (!ctx) {
@@ -276,17 +304,96 @@ int GetGamepadImageAxisHeight(GamepadImage *ctx)
     return ctx->axis_height;
 }
 
-SDL_GamepadButton GetGamepadImageButtonAt(GamepadImage *ctx, float x, float y)
+int GetGamepadImageElementAt(GamepadImage *ctx, float x, float y)
 {
     SDL_FPoint point;
     int i;
 
     if (!ctx) {
-        return SDL_GAMEPAD_BUTTON_INVALID;
+        return SDL_GAMEPAD_ELEMENT_INVALID;
     }
 
     point.x = x;
     point.y = y;
+
+    if (ctx->showing_front) {
+        for (i = 0; i < SDL_arraysize(axis_positions); ++i) {
+            const int element = SDL_GAMEPAD_BUTTON_MAX + i;
+            SDL_FRect rect;
+
+            if (element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER ||
+                element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER) {
+                rect.w = (float)ctx->axis_width;
+                rect.h = (float)ctx->axis_height;
+                rect.x = (float)ctx->x + axis_positions[i].x - rect.w / 2;
+                rect.y = (float)ctx->y + axis_positions[i].y - rect.h / 2;
+                if (SDL_PointInRectFloat(&point, &rect)) {
+                    if (element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER) {
+                        return SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER;
+                    } else {
+                        return SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER;
+                    }
+                }
+            } else if (element == SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE) {
+                rect.w = (float)ctx->button_width * 2.0f;
+                rect.h = (float)ctx->button_height * 2.0f;
+                rect.x = (float)ctx->x + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].x - rect.w / 2;
+                rect.y = (float)ctx->y + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].y - rect.h / 2;
+                if (SDL_PointInRectFloat(&point, &rect)) {
+                    float delta_x, delta_y;
+                    float delta_squared;
+                    float thumbstick_radius = (float)ctx->button_width * 0.1f;
+
+                    delta_x = (x - (ctx->x + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].x));
+                    delta_y = (y - (ctx->y + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].y));
+                    delta_squared = (delta_x * delta_x) + (delta_y * delta_y);
+                    if (delta_squared > (thumbstick_radius * thumbstick_radius)) {
+                        float angle = SDL_atan2f(delta_y, delta_x) + SDL_PI_F;
+                        if (angle < SDL_PI_F * 0.25f) {
+                            return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE;
+                        } else if (angle < SDL_PI_F * 0.75f) {
+                            return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE;
+                        } else if (angle < SDL_PI_F * 1.25f) {
+                            return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE;
+                        } else if (angle < SDL_PI_F * 1.75f) {
+                            return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE;
+                        } else {
+                            return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE;
+                        }
+                    }
+                }
+            } else if (element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE) {
+                rect.w = (float)ctx->button_width * 2.0f;
+                rect.h = (float)ctx->button_height * 2.0f;
+                rect.x = (float)ctx->x + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].x - rect.w / 2;
+                rect.y = (float)ctx->y + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].y - rect.h / 2;
+                if (SDL_PointInRectFloat(&point, &rect)) {
+                    float delta_x, delta_y;
+                    float delta_squared;
+                    float thumbstick_radius = (float)ctx->button_width * 0.1f;
+
+                    delta_x = (x - (ctx->x + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].x));
+                    delta_y = (y - (ctx->y + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].y));
+                    delta_squared = (delta_x * delta_x) + (delta_y * delta_y);
+                    if (delta_squared > (thumbstick_radius * thumbstick_radius)) {
+                        float angle = SDL_atan2f(delta_y, delta_x) + SDL_PI_F;
+                        if (angle < SDL_PI_F * 0.25f) {
+                            return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE;
+                        } else if (angle < SDL_PI_F * 0.75f) {
+                            return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE;
+                        } else if (angle < SDL_PI_F * 1.25f) {
+                            return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE;
+                        } else if (angle < SDL_PI_F * 1.75f) {
+                            return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE;
+                        } else {
+                            return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     for (i = 0; i < SDL_arraysize(button_positions); ++i) {
         SDL_bool on_front = SDL_TRUE;
 
@@ -304,33 +411,7 @@ SDL_GamepadButton GetGamepadImageButtonAt(GamepadImage *ctx, float x, float y)
             }
         }
     }
-    return SDL_GAMEPAD_BUTTON_INVALID;
-}
-
-SDL_GamepadAxis GetGamepadImageAxisAt(GamepadImage *ctx, float x, float y)
-{
-    SDL_FPoint point;
-    int i;
-
-    if (!ctx) {
-        return SDL_GAMEPAD_AXIS_INVALID;
-    }
-
-    point.x = x;
-    point.y = y;
-    if (ctx->showing_front) {
-        for (i = 0; i < SDL_arraysize(axis_positions); ++i) {
-            SDL_FRect rect;
-            rect.x = (float)ctx->x + axis_positions[i].x - ctx->axis_width / 2;
-            rect.y = (float)ctx->y + axis_positions[i].y - ctx->axis_height / 2;
-            rect.w = (float)ctx->axis_width;
-            rect.h = (float)ctx->axis_height;
-            if (SDL_PointInRectFloat(&point, &rect)) {
-                return (SDL_GamepadAxis)i;
-            }
-        }
-    }
-    return SDL_GAMEPAD_AXIS_INVALID;
+    return SDL_GAMEPAD_ELEMENT_INVALID;
 }
 
 void ClearGamepadImage(GamepadImage *ctx)
@@ -339,26 +420,16 @@ void ClearGamepadImage(GamepadImage *ctx)
         return;
     }
 
-    SDL_zeroa(ctx->buttons);
-    SDL_zeroa(ctx->axes);
-}
-
-void SetGamepadImageButton(GamepadImage *ctx, SDL_GamepadButton button, SDL_bool active)
-{
-    if (!ctx) {
-        return;
-    }
-
-    ctx->buttons[button] = active;
+    SDL_zeroa(ctx->elements);
 }
 
-void SetGamepadImageAxis(GamepadImage *ctx, SDL_GamepadAxis axis, int direction)
+void SetGamepadImageElement(GamepadImage *ctx, int element, SDL_bool active)
 {
     if (!ctx) {
         return;
     }
 
-    ctx->axes[axis] = direction;
+    ctx->elements[element] = active;
 }
 
 void UpdateGamepadImageFromGamepad(GamepadImage *ctx, SDL_Gamepad *gamepad)
@@ -369,41 +440,37 @@ void UpdateGamepadImageFromGamepad(GamepadImage *ctx, SDL_Gamepad *gamepad)
         return;
     }
 
-    if (gamepad) {
-        char *mapping = SDL_GetGamepadMapping(gamepad);
-        if (mapping) {
-            SDL_GamepadType gamepad_type = SDL_GetGamepadType(gamepad);
-            switch (gamepad_type) {
-            case SDL_GAMEPAD_TYPE_PS3:
-            case SDL_GAMEPAD_TYPE_PS4:
-            case SDL_GAMEPAD_TYPE_PS5:
-                ctx->face_style = GAMEPAD_IMAGE_FACE_SONY;
-                break;
-            case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:
-            case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT:
-            case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT:
-            case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR:
-                ctx->face_style = GAMEPAD_IMAGE_FACE_BAYX;
-                break;
-            default:
-                if (SDL_strstr(mapping, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS")) {
-                    ctx->face_style = GAMEPAD_IMAGE_FACE_BAYX;
-                } else {
-                    ctx->face_style = GAMEPAD_IMAGE_FACE_ABXY;
-                }
-                break;
-            }
-            SDL_free(mapping);
+    char *mapping = SDL_GetGamepadMapping(gamepad);
+    SDL_GamepadType gamepad_type = SDL_GetGamepadType(gamepad);
+    switch (gamepad_type) {
+    case SDL_GAMEPAD_TYPE_PS3:
+    case SDL_GAMEPAD_TYPE_PS4:
+    case SDL_GAMEPAD_TYPE_PS5:
+        ctx->face_style = GAMEPAD_IMAGE_FACE_SONY;
+        break;
+    case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:
+    case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT:
+    case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT:
+    case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR:
+        ctx->face_style = GAMEPAD_IMAGE_FACE_BAYX;
+        break;
+    default:
+        if (mapping && SDL_strstr(mapping, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS")) {
+            ctx->face_style = GAMEPAD_IMAGE_FACE_BAYX;
+        } else {
+            ctx->face_style = GAMEPAD_IMAGE_FACE_ABXY;
         }
+        break;
     }
+    SDL_free(mapping);
 
     for (i = 0; i < SDL_GAMEPAD_BUTTON_TOUCHPAD; ++i) {
         const SDL_GamepadButton button = (SDL_GamepadButton)i;
 
         if (SDL_GetGamepadButton(gamepad, button) == SDL_PRESSED) {
-            SetGamepadImageButton(ctx, button, SDL_TRUE);
+            SetGamepadImageElement(ctx, button, SDL_TRUE);
         } else {
-            SetGamepadImageButton(ctx, button, SDL_FALSE);
+            SetGamepadImageElement(ctx, button, SDL_FALSE);
         }
     }
 
@@ -411,12 +478,31 @@ void UpdateGamepadImageFromGamepad(GamepadImage *ctx, SDL_Gamepad *gamepad)
         const SDL_GamepadAxis axis = (SDL_GamepadAxis)i;
         const Sint16 deadzone = 8000; /* !!! FIXME: real deadzone */
         const Sint16 value = SDL_GetGamepadAxis(gamepad, axis);
-        if (value < -deadzone) {
-            SetGamepadImageAxis(ctx, axis, -1);
-        } else if (value > deadzone) {
-            SetGamepadImageAxis(ctx, axis, 1);
-        } else {
-            SetGamepadImageAxis(ctx, axis, 0);
+        switch (i) {
+        case SDL_GAMEPAD_AXIS_LEFTX:
+            SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE, (value < -deadzone));
+            SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE, (value > deadzone));
+            break;
+        case SDL_GAMEPAD_AXIS_RIGHTX:
+            SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE, (value < -deadzone));
+            SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE, (value > deadzone));
+            break;
+        case SDL_GAMEPAD_AXIS_LEFTY:
+            SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE, (value < -deadzone));
+            SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE, (value > deadzone));
+            break;
+        case SDL_GAMEPAD_AXIS_RIGHTY:
+            SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE, (value < -deadzone));
+            SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE, (value > deadzone));
+            break;
+        case SDL_GAMEPAD_AXIS_LEFT_TRIGGER:
+            SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER, (value > deadzone));
+            break;
+        case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
+            SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER, (value > deadzone));
+            break;
+        default:
+            break;
         }
     }
 
@@ -489,7 +575,7 @@ void RenderGamepadImage(GamepadImage *ctx)
     }
 
     for (i = 0; i < SDL_arraysize(button_positions); ++i) {
-        if (ctx->buttons[i]) {
+        if (ctx->elements[i]) {
             SDL_GamepadButton button_position = GetRemappedButton(ctx->face_style, (SDL_GamepadButton)i);
             SDL_bool on_front = SDL_TRUE;
 
@@ -497,10 +583,10 @@ void RenderGamepadImage(GamepadImage *ctx)
                 on_front = SDL_FALSE;
             }
             if (on_front == ctx->showing_front) {
-                dst.x = (float)ctx->x + button_positions[button_position].x - ctx->button_width / 2;
-                dst.y = (float)ctx->y + button_positions[button_position].y - ctx->button_height / 2;
                 dst.w = (float)ctx->button_width;
                 dst.h = (float)ctx->button_height;
+                dst.x = (float)ctx->x + button_positions[button_position].x - dst.w / 2;
+                dst.y = (float)ctx->y + button_positions[button_position].y - dst.h / 2;
                 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst);
             }
         }
@@ -508,19 +594,13 @@ void RenderGamepadImage(GamepadImage *ctx)
 
     if (ctx->showing_front) {
         for (i = 0; i < SDL_arraysize(axis_positions); ++i) {
-            if (ctx->axes[i] < 0) {
+            const int element = SDL_GAMEPAD_BUTTON_MAX + i;
+            if (ctx->elements[element]) {
                 const double angle = axis_positions[i].angle;
-                dst.x = (float)ctx->x + axis_positions[i].x - ctx->axis_width / 2;
-                dst.y = (float)ctx->y + axis_positions[i].y - ctx->axis_height / 2;
-                dst.w = (float)ctx->axis_width;
-                dst.h = (float)ctx->axis_height;
-                SDL_RenderTextureRotated(ctx->renderer, ctx->axis_texture, NULL, &dst, angle, NULL, SDL_FLIP_NONE);
-            } else if (ctx->axes[i] > 0) {
-                const double angle = axis_positions[i].angle + 180.0;
-                dst.x = (float)ctx->x + axis_positions[i].x - ctx->axis_width / 2;
-                dst.y = (float)ctx->y + axis_positions[i].y - ctx->axis_height / 2;
                 dst.w = (float)ctx->axis_width;
                 dst.h = (float)ctx->axis_height;
+                dst.x = (float)ctx->x + axis_positions[i].x - dst.w / 2;
+                dst.y = (float)ctx->y + axis_positions[i].y - dst.h / 2;
                 SDL_RenderTextureRotated(ctx->renderer, ctx->axis_texture, NULL, &dst, angle, NULL, SDL_FLIP_NONE);
             }
         }
@@ -609,7 +689,7 @@ SDL_COMPILE_TIME_ASSERT(gamepad_button_names, SDL_arraysize(gamepad_button_names
 
 static const char *gamepad_axis_names[] = {
     "LeftX",
-    "RightX",
+    "LeftY",
     "RightX",
     "RightY",
     "Left Trigger",
@@ -632,6 +712,9 @@ struct GamepadDisplay
     Uint64 last_sensor_update;
 
     ControllerDisplayMode display_mode;
+    int element_highlighted;
+    SDL_bool element_pressed;
+    int element_selected;
 
     SDL_Rect area;
 };
@@ -647,6 +730,9 @@ GamepadDisplay *CreateGamepadDisplay(SDL_Renderer *renderer)
 
         ctx->arrow_texture = CreateTexture(renderer, gamepad_axis_arrow_bmp, gamepad_axis_arrow_bmp_len);
         SDL_QueryTexture(ctx->arrow_texture, NULL, NULL, &ctx->arrow_width, &ctx->arrow_height);
+
+        ctx->element_highlighted = SDL_GAMEPAD_ELEMENT_INVALID;
+        ctx->element_selected = SDL_GAMEPAD_ELEMENT_INVALID;
     }
     return ctx;
 }
@@ -674,6 +760,7 @@ static SDL_bool GetBindingString(const char *label, char *mapping, char *text, s
     char *key;
     char *value, *end;
     size_t length;
+    SDL_bool found = SDL_FALSE;
 
     *text = '\0';
 
@@ -682,7 +769,14 @@ static SDL_bool GetBindingString(const char *label, char *mapping, char *text, s
     }
 
     key = SDL_strstr(mapping, label);
-    if (key) {
+    while (key && size > 1) {
+        if (found) {
+            *text++ = ',';
+            *text = '\0';
+            --size;
+        } else {
+            found = SDL_TRUE;
+        }
         value = key + SDL_strlen(label);
         end = SDL_strchr(value, ',');
         if (end) {
@@ -695,15 +789,18 @@ static SDL_bool GetBindingString(const char *label, char *mapping, char *text, s
         }
         SDL_memcpy(text, value, length);
         text[length] = '\0';
+        text += length;
+        size -= length;
+        key = SDL_strstr(value, label);
     }
-    return *text ? SDL_TRUE : SDL_FALSE;
+    return found;
 }
 
 static SDL_bool GetButtonBindingString(SDL_GamepadButton button, char *mapping, char *text, size_t size)
 {
     char label[32];
 
-    SDL_snprintf(label, sizeof(label), "%s:", SDL_GetGamepadStringForButton(button));
+    SDL_snprintf(label, sizeof(label), ",%s:", SDL_GetGamepadStringForButton(button));
     return GetBindingString(label, mapping, text, size);
 }
 
@@ -713,16 +810,16 @@ static SDL_bool GetAxisBindingString(SDL_GamepadAxis axis, int direction, char *
 
     /* Check for explicit half-axis */
     if (direction < 0) {
-        SDL_snprintf(label, sizeof(label), "-%s:", SDL_GetGamepadStringForAxis(axis));
+        SDL_snprintf(label, sizeof(label), ",-%s:", SDL_GetGamepadStringForAxis(axis));
     } else {
-        SDL_snprintf(label, sizeof(label), "+%s:", SDL_GetGamepadStringForAxis(axis));
+        SDL_snprintf(label, sizeof(label), ",+%s:", SDL_GetGamepadStringForAxis(axis));
     }
     if (GetBindingString(label, mapping, text, size)) {
         return SDL_TRUE;
     }
 
     /* Get the binding for the whole axis and split it if necessary */
-    SDL_snprintf(label, sizeof(label), "%s:", SDL_GetGamepadStringForAxis(axis));
+    SDL_snprintf(label, sizeof(label), ",%s:", SDL_GetGamepadStringForAxis(axis));
     if (!GetBindingString(label, mapping, text, size)) {
         return SDL_FALSE;
     }
@@ -732,22 +829,152 @@ static SDL_bool GetAxisBindingString(SDL_GamepadAxis axis, int direction, char *
             size_t length = SDL_strlen(text) + 1;
             if ((length + 1) <= size) {
                 SDL_memmove(text + 1, text, length);
+                if (text[SDL_strlen(text) - 1] == '~') {
+                    direction *= -1;
+                    text[SDL_strlen(text) - 1] = '\0';
+                }
                 if (direction > 0) {
                     *text = '+';
                 } else {
                     *text = '-';
                 }
             }
-        } else if (*text == '~') {
-            /* Invert directions and split the axis */
-            if (direction > 0) {
-                *text = '-';
+        }
+    }
+    return SDL_TRUE;
+}
+
+void SetGamepadDisplayHighlight(GamepadDisplay *ctx, int element, SDL_bool pressed)
+{
+    if (!ctx) {
+        return;
+    }
+
+    ctx->element_highlighted = element;
+    ctx->element_pressed = pressed;
+}
+
+void SetGamepadDisplaySelected(GamepadDisplay *ctx, int element)
+{
+    if (!ctx) {
+        return;
+    }
+
+    ctx->element_selected = element;
+}
+
+int GetGamepadDisplayElementAt(GamepadDisplay *ctx, SDL_Gamepad *gamepad, float x, float y)
+{
+    int i;
+    const float margin = 8.0f;
+    const float center = ctx->area.w / 2.0f;
+    const float arrow_extent = 48.0f;
+    SDL_FPoint point;
+    SDL_FRect rect;
+
+    if (!ctx) {
+        return SDL_GAMEPAD_ELEMENT_INVALID;
+    }
+
+    point.x = x;
+    point.y = y;
+
+    rect.x = ctx->area.x + margin;
+    rect.y = ctx->area.y + margin + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
+    rect.w = (float)ctx->area.w - (margin * 2);
+    rect.h = (float)ctx->button_height;
+
+    for (i = 0; i < SDL_GAMEPAD_BUTTON_MAX; ++i) {
+        SDL_GamepadButton button = (SDL_GamepadButton)i;
+
+        if (ctx->display_mode == CONTROLLER_MODE_TESTING &&
+            !SDL_GamepadHasButton(gamepad, button)) {
+            continue;
+        }
+
+
+        if (SDL_PointInRectFloat(&point, &rect)) {
+            return i;
+        }
+
+        rect.y += (float)ctx->button_height + 2.0f;
+    }
+
+    for (i = 0; i < SDL_GAMEPAD_AXIS_MAX; ++i) {
+        SDL_GamepadAxis axis = (SDL_GamepadAxis)i;
+        SDL_FRect area;
+
+        if (ctx->display_mode == CONTROLLER_MODE_TESTING &&
+            !SDL_GamepadHasAxis(gamepad, axis)) {
+            continue;
+        }
+
+        area.x = rect.x + center + 2.0f;
+        area.y = rect.y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
+        area.w = (float)ctx->arrow_width + arrow_extent;
+        area.h = (float)ctx->button_height;
+
+        if (SDL_PointInRectFloat(&point, &area)) {
+            switch (axis) {
+            case SDL_GAMEPAD_AXIS_LEFTX:
+                return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE;
+            case SDL_GAMEPAD_AXIS_LEFTY:
+                return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE;
+            case SDL_GAMEPAD_AXIS_RIGHTX:
+                return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE;
+            case SDL_GAMEPAD_AXIS_RIGHTY:
+                return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE;
+            default:
+                break;
+            }
+        }
+
+        area.x += area.w;
+
+        if (SDL_PointInRectFloat(&point, &area)) {
+            switch (axis) {
+            case SDL_GAMEPAD_AXIS_LEFTX:
+                return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE;
+            case SDL_GAMEPAD_AXIS_LEFTY:
+                return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE;
+            case SDL_GAMEPAD_AXIS_RIGHTX:
+                return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE;
+            case SDL_GAMEPAD_AXIS_RIGHTY:
+                return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE;
+            case SDL_GAMEPAD_AXIS_LEFT_TRIGGER:
+                return SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER;
+            case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
+                return SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER;
+            default:
+                break;
+            }
+        }
+
+        rect.y += (float)ctx->button_height + 2.0f;
+    }
+    return SDL_GAMEPAD_ELEMENT_INVALID;
+}
+
+static void RenderGamepadElementHighlight(GamepadDisplay *ctx, int element, const SDL_FRect *area)
+{
+    if (element == ctx->element_highlighted || element == ctx->element_selected) {
+        Uint8 r, g, b, a;
+
+        SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a);
+
+        if (element == ctx->element_highlighted) {
+            if (ctx->element_pressed) {
+                SDL_SetRenderDrawColor(ctx->renderer, PRESSED_COLOR);
             } else {
-                *text = '+';
+                SDL_SetRenderDrawColor(ctx->renderer, HIGHLIGHT_COLOR);
             }
+        } else {
+            SDL_SetRenderDrawColor(ctx->renderer, SELECTED_COLOR);
         }
+        SDL_RenderFillRect(ctx->renderer, area);
+
+        SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
     }
-    return SDL_TRUE;
 }
 
 void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
@@ -758,7 +985,7 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
     const float margin = 8.0f;
     const float center = ctx->area.w / 2.0f;
     const float arrow_extent = 48.0f;
-    SDL_FRect dst, rect;
+    SDL_FRect dst, rect, highlight;
     Uint8 r, g, b, a;
     char *mapping = NULL;
     SDL_bool has_accel;
@@ -783,6 +1010,12 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
             continue;
         }
 
+        highlight.x = x;
+        highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
+        highlight.w = (float)ctx->area.w - (margin * 2);
+        highlight.h = (float)ctx->button_height;
+        RenderGamepadElementHighlight(ctx, i, &highlight);
+
         SDL_snprintf(text, sizeof(text), "%s:", gamepad_button_names[i]);
         SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
 
@@ -822,6 +1055,54 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
 
         SDL_snprintf(text, sizeof(text), "%s:", gamepad_axis_names[i]);
         SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
+
+        highlight.x = x + center + 2.0f;
+        highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
+        highlight.w = (float)ctx->arrow_width + arrow_extent;
+        highlight.h = (float)ctx->button_height;
+
+        switch (axis) {
+        case SDL_GAMEPAD_AXIS_LEFTX:
+            RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE, &highlight);
+            break;
+        case SDL_GAMEPAD_AXIS_LEFTY:
+            RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE, &highlight);
+            break;
+        case SDL_GAMEPAD_AXIS_RIGHTX:
+            RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE, &highlight);
+            break;
+        case SDL_GAMEPAD_AXIS_RIGHTY:
+            RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE, &highlight);
+            break;
+        default:
+            break;
+        }
+
+        highlight.x += highlight.w;
+
+        switch (axis) {
+        case SDL_GAMEPAD_AXIS_LEFTX:
+            RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE, &highlight);
+            break;
+        case SDL_GAMEPAD_AXIS_LEFTY:
+            RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE, &highlight);
+            break;
+        case SDL_GAMEPAD_AXIS_RIGHTX:
+            RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE, &highlight);
+            break;
+        case SDL_GAMEPAD_AXIS_RIGHTY:
+            RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE, &highlight);
+            break;
+        case SDL_GAMEPAD_AXIS_LEFT_TRIGGER:
+            RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER, &highlight);
+            break;
+        case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
+            RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER, &highlight);
+            break;
+        default:
+            break;
+        }
+
         dst.x = x + center + 2.0f;
         dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->arrow_height / 2;
         dst.w = (float)ctx->arrow_width;
@@ -897,7 +1178,7 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
 
         SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
 
-        y += ctx->button_height + 2;
+        y += ctx->button_height + 2.0f;
     }
 
     if (ctx->display_mode == CONTROLLER_MODE_TESTING) {
@@ -997,6 +1278,9 @@ struct JoystickDisplay
     int arrow_height;
 
     SDL_Rect area;
+
+    char *element_highlighted;
+    SDL_bool element_pressed;
 };
 
 JoystickDisplay *CreateJoystickDisplay(SDL_Renderer *renderer)
@@ -1023,9 +1307,9 @@ void SetJoystickDisplayArea(JoystickDisplay *ctx, const SDL_Rect *area)
     SDL_copyp(&ctx->area, area);
 }
 
-void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick)
+char *GetJoystickDisplayElementAt(JoystickDisplay *ctx, SDL_Joystick *joystick, float x, float y)
 {
-    float x, y;
+    SDL_FPoint point;
     int i;
     int nbuttons = SDL_GetNumJoystickButtons(joystick);
     int naxes = SDL_GetNumJoystickAxes(joystick);
@@ -1034,38 +1318,32 @@ void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick)
     const float margin = 8.0f;
     const float center = 80.0f;
     const float arrow_extent = 48.0f;
-    SDL_FRect dst, rect;
-    Uint8 r, g, b, a;
+    SDL_FRect dst, highlight;
+    char *element = NULL;
 
     if (!ctx) {
-        return;
+        return NULL;
     }
 
-    SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a);
+    point.x = x;
+    point.y = y;
 
     x = (float)ctx->area.x + margin;
     y = (float)ctx->area.y + margin;
 
     if (nbuttons > 0) {
-        SDLTest_DrawString(ctx->renderer, x, y, "BUTTONS");
         y += FONT_LINE_HEIGHT + 2;
 
         for (i = 0; i < nbuttons; ++i) {
-            SDL_snprintf(text, sizeof(text), "%2.d:", i);
-            SDLTest_DrawString(ctx->renderer, x, y, text);
-
-            if (SDL_GetJoystickButton(joystick, (Uint8)i)) {
-               

(Patch may be truncated, please check the link at the top of this post.)