SDL: Added the ability to rename your controller

From 57820071a497d57e362f6a7c71ab10c4a6fb5228 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 16 Jul 2023 15:11:24 -0700
Subject: [PATCH] Added the ability to rename your controller

---
 test/gamepadutils.c   |   2 +-
 test/testcontroller.c | 185 +++++++++++++++++++++++++++++++++++++-----
 2 files changed, 167 insertions(+), 20 deletions(-)

diff --git a/test/gamepadutils.c b/test/gamepadutils.c
index c2239d640c12..d3c2f12404c1 100644
--- a/test/gamepadutils.c
+++ b/test/gamepadutils.c
@@ -2423,7 +2423,7 @@ char *SetMappingName(char *mapping, const char *name)
     /* Remove any commas, which are field separators in the mapping */
     length = SDL_strlen(new_name);
     while ((spot = SDL_strchr(new_name, ',')) != NULL) {
-        SDL_memmove(spot, spot + 1, length - (spot - new_name) - 1);
+        SDL_memmove(spot, spot + 1, length - (spot - new_name) + 1);
         --length;
     }
 
diff --git a/test/testcontroller.c b/test/testcontroller.c
index 666f24a5baef..d8e67159ee69 100644
--- a/test/testcontroller.c
+++ b/test/testcontroller.c
@@ -86,6 +86,10 @@ 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_FRect title_area;
+static SDL_bool title_highlighted;
+static SDL_bool title_pressed;
+static char *controller_name;
 static SDL_Joystick *virtual_joystick = NULL;
 static SDL_GamepadAxis virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID;
 static float virtual_axis_start_x;
@@ -203,6 +207,9 @@ static void CyclePS5TriggerEffect(Controller *device)
 
 static void ClearButtonHighlights(void)
 {
+    title_highlighted = SDL_FALSE;
+    title_pressed = SDL_FALSE;
+
     ClearGamepadImage(image);
     SetGamepadDisplayHighlight(gamepad_elements, SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
     SetGamepadButtonHighlight(setup_mapping_button, SDL_FALSE, SDL_FALSE);
@@ -220,9 +227,20 @@ static void UpdateButtonHighlights(float x, float y, SDL_bool button_down)
     if (display_mode == CONTROLLER_MODE_TESTING) {
         SetGamepadButtonHighlight(setup_mapping_button, GamepadButtonContains(setup_mapping_button, x, y), button_down);
     } else if (display_mode == CONTROLLER_MODE_BINDING) {
+        SDL_FPoint point;
         int gamepad_highlight_element = SDL_GAMEPAD_ELEMENT_INVALID;
         char *joystick_highlight_element;
 
+        point.x = x;
+        point.y = y;
+        if (SDL_PointInRectFloat(&point, &title_area)) {
+            title_highlighted = SDL_TRUE;
+            title_pressed = button_down;
+        } else {
+            title_highlighted = SDL_FALSE;
+            title_pressed = SDL_FALSE;
+        }
+
         if (controller->joystick != virtual_joystick) {
             gamepad_highlight_element = GetGamepadImageElementAt(image, x, y);
         }
@@ -254,6 +272,20 @@ static int StandardizeAxisValue(int nValue)
     }
 }
 
+static void RefreshControllerName(void)
+{
+    SDL_free(controller_name);
+    controller_name = NULL;
+
+    if (controller) {
+        if (controller->gamepad) {
+            controller_name = SDL_strdup(SDL_GetGamepadName(controller->gamepad));
+        } else {
+            controller_name = SDL_strdup(SDL_GetJoystickName(controller->joystick));
+        }
+    }
+}
+
 static void SetAndFreeGamepadMapping(char *mapping)
 {
     SDL_SetGamepadMapping(controller->id, mapping);
@@ -264,6 +296,10 @@ static void SetCurrentBindingElement(int element, SDL_bool flow)
 {
     int i;
 
+    if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
+        RefreshControllerName();
+    }
+
     if (element == SDL_GAMEPAD_ELEMENT_INVALID) {
         last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
     } else {
@@ -507,6 +543,7 @@ static void PasteMapping(void)
         if (MappingHasBindings(mapping)) {
             StopBinding();
             SetAndFreeGamepadMapping(mapping);
+            RefreshControllerName();
         } else {
             /* Not a valid mapping, ignore it */
             SDL_free(mapping);
@@ -514,6 +551,61 @@ static void PasteMapping(void)
     }
 }
 
+static void CommitControllerName(void)
+{
+    char *mapping = NULL;
+
+    if (controller->mapping) {
+        mapping = SDL_strdup(controller->mapping);
+    } else {
+        mapping = NULL;
+    }
+    mapping = SetMappingName(mapping, controller_name);
+    SetAndFreeGamepadMapping(mapping);
+}
+
+static void AddControllerNameText(const char *text)
+{
+    size_t current_length = (controller_name ? SDL_strlen(controller_name) : 0);
+    size_t text_length = SDL_strlen(text);
+    size_t size = current_length + text_length + 1;
+    char *name = (char *)SDL_realloc(controller_name, size);
+    if (name) {
+        SDL_memcpy(&name[current_length], text, text_length + 1);
+        controller_name = name;
+    }
+    CommitControllerName();
+}
+
+static void BackspaceControllerName(void)
+{
+    size_t length = (controller_name ? SDL_strlen(controller_name) : 0);
+    if (length > 0) {
+        controller_name[length - 1] = '\0';
+    }
+    CommitControllerName();
+}
+
+static void ClearControllerName(void)
+{
+    if (controller_name) {
+        *controller_name = '\0';
+    }
+    CommitControllerName();
+}
+
+static void CopyControllerName(void)
+{
+    SDL_SetClipboardText(controller_name);
+}
+
+static void PasteControllerName(void)
+{
+    SDL_free(controller_name);
+    controller_name = SDL_GetClipboardText();
+    CommitControllerName();
+}
+
 static const char *GetBindingInstruction(void)
 {
     switch (binding_element) {
@@ -631,6 +723,8 @@ static void SetController(SDL_JoystickID id)
     } else {
         controller = NULL;
     }
+
+    RefreshControllerName();
 }
 
 static void HandleGamepadRemapped(SDL_JoystickID id)
@@ -644,6 +738,7 @@ static void HandleGamepadRemapped(SDL_JoystickID id)
 
     if (!controllers[i].gamepad) {
         controllers[i].gamepad = SDL_OpenGamepad(id);
+        RefreshControllerName();
     }
 
     /* Get the current mapping */
@@ -814,7 +909,7 @@ static SDL_bool ShowingFront(void)
             break;
         }
     }
-    if (SDL_GetModState() & SDL_KMOD_SHIFT) {
+    if ((SDL_GetModState() & SDL_KMOD_SHIFT) && binding_element != SDL_GAMEPAD_ELEMENT_NAME) {
         showing_front = SDL_FALSE;
     }
     return showing_front;
@@ -1002,20 +1097,29 @@ static void DrawGamepadWaiting(SDL_Renderer *renderer)
 
 static void DrawGamepadInfo(SDL_Renderer *renderer)
 {
-    const char *name;
     const char *serial;
     char text[128];
     float x, y;
 
-    if (controller->gamepad) {
-        name = SDL_GetGamepadName(controller->gamepad);
-    } else {
-        name = SDL_GetJoystickName(controller->joystick);
+    if (title_highlighted) {
+        Uint8 r, g, b, a;
+
+        SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
+
+        if (title_pressed) {
+            SDL_SetRenderDrawColor(renderer, PRESSED_COLOR);
+        } else {
+            SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR);
+        }
+        SDL_RenderFillRect(renderer, &title_area);
+
+        SDL_SetRenderDrawColor(renderer, r, g, b, a);
     }
-    if (name && *name) {
-        x = (float)SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(name)) / 2;
+
+    if (controller_name && *controller_name) {
+        x = (float)SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(controller_name)) / 2;
         y = (float)TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2;
-        SDLTest_DrawString(renderer, x, y, name);
+        SDLTest_DrawString(renderer, x, y, controller_name);
     }
 
     if (SDL_IsJoystickVirtual(controller->id)) {
@@ -1079,12 +1183,16 @@ static void DrawBindingTips(SDL_Renderer *renderer)
 
         y += (FONT_CHARACTER_SIZE + BUTTON_MARGIN);
 
-        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)";
+        if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
+            text = "(press RETURN to complete)";
         } else {
-            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);
     }
@@ -1357,6 +1465,8 @@ static void loop(void *arg)
                     CopyMapping();
                 } else if (GamepadButtonContains(paste_button, event.button.x, event.button.y)) {
                     PasteMapping();
+                } else if (title_pressed) {
+                    SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_NAME, SDL_FALSE);
                 } else {
                     int gamepad_element = SDL_GAMEPAD_ELEMENT_INVALID;
                     char *joystick_element;
@@ -1408,14 +1518,38 @@ static void loop(void *arg)
                 }
             } else if (display_mode == CONTROLLER_MODE_BINDING) {
                 if (event.key.keysym.sym == SDLK_c && (event.key.keysym.mod & SDL_KMOD_CTRL)) {
-                    CopyMapping();
+                    if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
+                        CopyControllerName();
+                    } else {
+                        CopyMapping();
+                    }
                 } else if (event.key.keysym.sym == SDLK_v && (event.key.keysym.mod & SDL_KMOD_CTRL)) {
-                    PasteMapping();
+                    if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
+                        ClearControllerName();
+                        PasteControllerName();
+                    } else {
+                        PasteMapping();
+                    }
                 } else if (event.key.keysym.sym == SDLK_x && (event.key.keysym.mod & SDL_KMOD_CTRL)) {
-                    CopyMapping();
-                    ClearMapping();
+                    if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
+                        CopyControllerName();
+                        ClearControllerName();
+                    } else {
+                        CopyMapping();
+                        ClearMapping();
+                    }
                 } else if (event.key.keysym.sym == SDLK_SPACE) {
-                    ClearBinding();
+                    if (binding_element != SDL_GAMEPAD_ELEMENT_NAME) {
+                        ClearBinding();
+                    }
+                } else if (event.key.keysym.sym == SDLK_BACKSPACE) {
+                    if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
+                        BackspaceControllerName();
+                    }
+                } else if (event.key.keysym.sym == SDLK_RETURN) {
+                    if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
+                        StopBinding();
+                    }
                 } else if (event.key.keysym.sym == SDLK_ESCAPE) {
                     if (binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
                         StopBinding();
@@ -1425,6 +1559,13 @@ static void loop(void *arg)
                 }
             }
             break;
+        case SDL_EVENT_TEXT_INPUT:
+            if (display_mode == CONTROLLER_MODE_BINDING) {
+                if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
+                    AddControllerNameText(event.text.text);
+                }
+            }
+            break;
         case SDL_EVENT_QUIT:
             done = SDL_TRUE;
             break;
@@ -1594,6 +1735,12 @@ int main(int argc, char *argv[])
                                      SDL_LOGICAL_PRESENTATION_LETTERBOX,
                                      SDL_SCALEMODE_LINEAR);
 
+
+    title_area.w = (float)GAMEPAD_WIDTH;
+    title_area.h = (float)FONT_CHARACTER_SIZE + 2 * BUTTON_MARGIN;
+    title_area.x = (float)PANEL_WIDTH + PANEL_SPACING;
+    title_area.y = (float)TITLE_HEIGHT / 2 - title_area.h / 2;
+
     image = CreateGamepadImage(screen);
     if (image == NULL) {
         SDL_DestroyRenderer(screen);