SDL: Show the gamepad bindings in testcontroller

From e674d818447caa2baa36640827e9e4c760934911 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 11 Jul 2023 16:49:38 -0700
Subject: [PATCH] Show the gamepad bindings in testcontroller

---
 test/gamepadutils.c | 240 +++++++++++++++++++++++++++++++-------------
 1 file changed, 173 insertions(+), 67 deletions(-)

diff --git a/test/gamepadutils.c b/test/gamepadutils.c
index 441cb96b3c28..f399cbbc5ae7 100644
--- a/test/gamepadutils.c
+++ b/test/gamepadutils.c
@@ -606,113 +606,217 @@ void SetGamepadDisplayArea(GamepadDisplay *ctx, int x, int y, int w, int h)
     ctx->area.h = h;
 }
 
+static SDL_bool GetBindingString(const char *label, char *mapping, char *text, size_t size)
+{
+    char *key;
+    char *value, *end;
+    size_t length;
+
+    *text = '\0';
+
+    if (!mapping) {
+        return SDL_FALSE;
+    }
+
+    key = SDL_strstr(mapping, label);
+    if (key) {
+        value = key + SDL_strlen(label);
+        end = SDL_strchr(value, ',');
+        if (end) {
+            length = (end - value);
+        } else {
+            length = SDL_strlen(value);
+        }
+        if (length >= size) {
+            length = size - 1;
+        }
+        SDL_memcpy(text, value, length);
+        text[length] = '\0';
+    }
+    return *text ? SDL_TRUE : SDL_FALSE;
+}
+
+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));
+    return GetBindingString(label, mapping, text, size);
+}
+
+static SDL_bool GetAxisBindingString(SDL_GamepadAxis axis, int direction, char *mapping, char *text, size_t size)
+{
+    char label[32];
+
+    /* Check for explicit half-axis */
+    if (direction < 0) {
+        SDL_snprintf(label, sizeof(label), "-%s:", SDL_GetGamepadStringForAxis(axis));
+    } else {
+        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));
+    if (!GetBindingString(label, mapping, text, size)) {
+        return SDL_FALSE;
+    }
+    if (axis != SDL_GAMEPAD_AXIS_LEFT_TRIGGER && axis != SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
+        if (*text == 'a') {
+            /* Split the axis */
+            size_t length = SDL_strlen(text) + 1;
+            if ((length + 1) <= size) {
+                SDL_memmove(text + 1, text, length);
+                if (direction > 0) {
+                    *text = '+';
+                } else {
+                    *text = '-';
+                }
+            }
+        } else if (*text == '~') {
+            /* Invert directions and split the axis */
+            if (direction > 0) {
+                *text = '-';
+            } else {
+                *text = '+';
+            }
+        }
+    }
+    return SDL_TRUE;
+}
+
 void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
 {
     float x, y;
     int i;
-    char text[128];
+    char text[128], binding[32];
     const float margin = 8.0f;
     const float center = ctx->area.w / 2.0f;
     const float arrow_extent = 48.0f;
     SDL_FRect dst, rect;
     Uint8 r, g, b, a;
+    char *mapping;
     SDL_bool has_accel;
     SDL_bool has_gyro;
 
-    if (!ctx || !gamepad) {
+    if (!ctx) {
         return;
     }
 
     SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a);
 
+    mapping = SDL_GetGamepadMapping(gamepad);
+
     x = ctx->area.x + margin;
     y = ctx->area.y + margin;
 
     for (i = 0; i < SDL_GAMEPAD_BUTTON_MAX; ++i) {
-        if (SDL_GamepadHasButton(gamepad, (SDL_GamepadButton)i)) {
-            SDL_snprintf(text, sizeof(text), "%s:", gamepad_button_names[i]);
-            SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
+        SDL_GamepadButton button = (SDL_GamepadButton)i;
 
-            if (SDL_GetGamepadButton(gamepad, (SDL_GamepadButton)i)) {
-                SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21);
-            } else {
-                SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255);
-            }
+        SDL_snprintf(text, sizeof(text), "%s:", gamepad_button_names[i]);
+        SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
 
-            dst.x = x + center + 2.0f;
-            dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
-            dst.w = (float)ctx->button_width;
-            dst.h = (float)ctx->button_height;
-            SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst);
+        if (SDL_GetGamepadButton(gamepad, button)) {
+            SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21);
+        } else {
+            SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255);
+        }
 
-            y += ctx->button_height + 2.0f;
+        dst.x = x + center + 2.0f;
+        dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
+        dst.w = (float)ctx->button_width;
+        dst.h = (float)ctx->button_height;
+        SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst);
+
+        if (GetButtonBindingString(button, mapping, binding, sizeof(binding))) {
+            dst.x += dst.w + 2 * margin;
+            SDLTest_DrawString(ctx->renderer, dst.x, y, binding);
         }
+
+        y += ctx->button_height + 2.0f;
     }
 
     for (i = 0; i < SDL_GAMEPAD_AXIS_MAX; ++i) {
-        if (SDL_GamepadHasAxis(gamepad, (SDL_GamepadAxis)i)) {
-            SDL_bool has_negative = (i != SDL_GAMEPAD_AXIS_LEFT_TRIGGER && i != SDL_GAMEPAD_AXIS_RIGHT_TRIGGER);
-            Sint16 value = SDL_GetGamepadAxis(gamepad, (SDL_GamepadAxis)i);
-
-            SDL_snprintf(text, sizeof(text), "%s:", gamepad_axis_names[i]);
-            SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
-            dst.x = x + center + 2.0f;
-            dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->arrow_height / 2;
-            dst.w = (float)ctx->arrow_width;
-            dst.h = (float)ctx->arrow_height;
-
-            if (has_negative) {
-                if (value == SDL_MIN_SINT16) {
-                    SDL_SetTextureColorMod(ctx->arrow_texture, 10, 255, 21);
-                } else {
-                    SDL_SetTextureColorMod(ctx->arrow_texture, 255, 255, 255);
-                }
-                SDL_RenderTextureRotated(ctx->renderer, ctx->arrow_texture, NULL, &dst, 0.0f, NULL, SDL_FLIP_HORIZONTAL);
+        SDL_GamepadAxis axis = (SDL_GamepadAxis)i;
+        SDL_bool has_negative = (axis != SDL_GAMEPAD_AXIS_LEFT_TRIGGER && axis != SDL_GAMEPAD_AXIS_RIGHT_TRIGGER);
+        Sint16 value = SDL_GetGamepadAxis(gamepad, axis);
+
+        SDL_snprintf(text, sizeof(text), "%s:", gamepad_axis_names[i]);
+        SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
+        dst.x = x + center + 2.0f;
+        dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->arrow_height / 2;
+        dst.w = (float)ctx->arrow_width;
+        dst.h = (float)ctx->arrow_height;
+
+        if (has_negative) {
+            if (value == SDL_MIN_SINT16) {
+                SDL_SetTextureColorMod(ctx->arrow_texture, 10, 255, 21);
+            } else {
+                SDL_SetTextureColorMod(ctx->arrow_texture, 255, 255, 255);
             }
+            SDL_RenderTextureRotated(ctx->renderer, ctx->arrow_texture, NULL, &dst, 0.0f, NULL, SDL_FLIP_HORIZONTAL);
+        }
 
-            dst.x += (float)ctx->arrow_width;
-
-            SDL_SetRenderDrawColor(ctx->renderer, 200, 200, 200, SDL_ALPHA_OPAQUE);
-            rect.x = dst.x + arrow_extent - 2.0f;
-            rect.y = dst.y;
-            rect.w = 4.0f;
-            rect.h = (float)ctx->arrow_height;
+        dst.x += (float)ctx->arrow_width;
+
+        SDL_SetRenderDrawColor(ctx->renderer, 200, 200, 200, SDL_ALPHA_OPAQUE);
+        rect.x = dst.x + arrow_extent - 2.0f;
+        rect.y = dst.y;
+        rect.w = 4.0f;
+        rect.h = (float)ctx->arrow_height;
+        SDL_RenderFillRect(ctx->renderer, &rect);
+        SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
+
+        if (value < 0) {
+            SDL_SetRenderDrawColor(ctx->renderer, 8, 200, 16, SDL_ALPHA_OPAQUE);
+            rect.w = ((float)value / SDL_MIN_SINT16) * arrow_extent;
+            rect.x = dst.x + arrow_extent - rect.w;
+            rect.y = dst.y + ctx->arrow_height * 0.25f;
+            rect.h = ctx->arrow_height / 2.0f;
             SDL_RenderFillRect(ctx->renderer, &rect);
-            SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
+        }
 
-            if (value < 0) {
-                SDL_SetRenderDrawColor(ctx->renderer, 8, 200, 16, SDL_ALPHA_OPAQUE);
-                rect.w = ((float)value / SDL_MIN_SINT16) * arrow_extent;
-                rect.x = dst.x + arrow_extent - rect.w;
-                rect.y = dst.y + ctx->arrow_height * 0.25f;
-                rect.h = ctx->arrow_height / 2.0f;
-                SDL_RenderFillRect(ctx->renderer, &rect);
-            }
+        if (has_negative && GetAxisBindingString(axis, -1, mapping, binding, sizeof(binding))) {
+            float text_x;
 
-            dst.x += arrow_extent;
+            SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
+            text_x = dst.x + arrow_extent / 2 - ((float)FONT_CHARACTER_SIZE * SDL_strlen(binding)) / 2;
+            SDLTest_DrawString(ctx->renderer, text_x, y, binding);
+        }
 
-            if (value > 0) {
-                SDL_SetRenderDrawColor(ctx->renderer, 8, 200, 16, SDL_ALPHA_OPAQUE);
-                rect.w = ((float)value / SDL_MAX_SINT16) * arrow_extent;
-                rect.x = dst.x;
-                rect.y = dst.y + ctx->arrow_height * 0.25f;
-                rect.h = ctx->arrow_height / 2.0f;
-                SDL_RenderFillRect(ctx->renderer, &rect);
-            }
+        dst.x += arrow_extent;
 
-            dst.x += arrow_extent;
+        if (value > 0) {
+            SDL_SetRenderDrawColor(ctx->renderer, 8, 200, 16, SDL_ALPHA_OPAQUE);
+            rect.w = ((float)value / SDL_MAX_SINT16) * arrow_extent;
+            rect.x = dst.x;
+            rect.y = dst.y + ctx->arrow_height * 0.25f;
+            rect.h = ctx->arrow_height / 2.0f;
+            SDL_RenderFillRect(ctx->renderer, &rect);
+        }
 
-            if (value == SDL_MAX_SINT16) {
-                SDL_SetTextureColorMod(ctx->arrow_texture, 10, 255, 21);
-            } else {
-                SDL_SetTextureColorMod(ctx->arrow_texture, 255, 255, 255);
-            }
-            SDL_RenderTexture(ctx->renderer, ctx->arrow_texture, NULL, &dst);
+        if (GetAxisBindingString(axis, 1, mapping, binding, sizeof(binding))) {
+            float text_x;
 
             SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
+            text_x = dst.x + arrow_extent / 2 - ((float)FONT_CHARACTER_SIZE * SDL_strlen(binding)) / 2;
+            SDLTest_DrawString(ctx->renderer, text_x, y, binding);
+        }
 
-            y += ctx->button_height + 2;
+        dst.x += arrow_extent;
+
+        if (value == SDL_MAX_SINT16) {
+            SDL_SetTextureColorMod(ctx->arrow_texture, 10, 255, 21);
+        } else {
+            SDL_SetTextureColorMod(ctx->arrow_texture, 255, 255, 255);
         }
+        SDL_RenderTexture(ctx->renderer, ctx->arrow_texture, NULL, &dst);
+
+        SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
+
+        y += ctx->button_height + 2;
     }
 
     if (SDL_GetNumGamepadTouchpads(gamepad) > 0) {
@@ -783,6 +887,8 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
             y += ctx->button_height + 2.0f;
         }
     }
+
+    SDL_free(mapping);
 }
 
 void DestroyGamepadDisplay(GamepadDisplay *ctx)