Ver código fonte

Added binding mode to testcontroller

Sam Lantinga 1 ano atrás
pai
commit
404e030b39
3 arquivos alterados com 439 adições e 274 exclusões
  1. 114 131
      test/gamepadutils.c
  2. 11 6
      test/gamepadutils.h
  3. 314 137
      test/testcontroller.c

+ 114 - 131
test/gamepadutils.c

@@ -114,9 +114,9 @@ struct GamepadImage
     int x;
     int y;
     SDL_bool showing_front;
-    SDL_bool showing_battery;
     SDL_bool showing_touchpad;
     GamepadImageFaceStyle face_style;
+    ControllerDisplayMode display_mode;
 
     SDL_bool buttons[SDL_GAMEPAD_BUTTON_MAX];
     int axes[SDL_GAMEPAD_AXIS_MAX];
@@ -231,57 +231,13 @@ void SetGamepadImageFaceStyle(GamepadImage *ctx, GamepadImageFaceStyle face_styl
     ctx->face_style = face_style;
 }
 
-void SetGamepadImageShowingBattery(GamepadImage *ctx, SDL_bool showing_battery)
+void SetGamepadImageDisplayMode(GamepadImage *ctx, ControllerDisplayMode display_mode)
 {
     if (!ctx) {
         return;
     }
 
-    ctx->showing_battery = showing_battery;
-}
-
-void SetGamepadImageShowingTouchpad(GamepadImage *ctx, SDL_bool showing_touchpad)
-{
-    if (!ctx) {
-        return;
-    }
-
-    ctx->showing_touchpad = showing_touchpad;
-}
-
-void GetGamepadImageArea(GamepadImage *ctx, int *x, int *y, int *width, int *height)
-{
-    if (!ctx) {
-        if (x) {
-            *x = 0;
-        }
-        if (y) {
-            *y = 0;
-        }
-        if (width) {
-            *width = 0;
-        }
-        if (height) {
-            *height = 0;
-        }
-        return;
-    }
-
-    if (x) {
-        *x = ctx->x;
-    }
-    if (y) {
-        *y = ctx->y;
-    }
-    if (width) {
-        *width = ctx->gamepad_width;
-    }
-    if (height) {
-        *height = ctx->gamepad_height;
-        if (ctx->showing_touchpad) {
-            *height += ctx->touchpad_height;
-        }
-    }
+    ctx->display_mode = display_mode;
 }
 
 int GetGamepadImageButtonWidth(GamepadImage *ctx)
@@ -482,12 +438,14 @@ void UpdateGamepadImageFromGamepad(GamepadImage *ctx, SDL_Gamepad *gamepad)
 
             SDL_GetGamepadTouchpadFinger(gamepad, 0, i, &finger->state, &finger->x, &finger->y, &finger->pressure);
         }
+        ctx->showing_touchpad = SDL_TRUE;
     } else {
         if (ctx->fingers) {
             SDL_free(ctx->fingers);
             ctx->fingers = NULL;
             ctx->num_fingers = 0;
         }
+        ctx->showing_touchpad = SDL_FALSE;
     }
 }
 
@@ -568,7 +526,7 @@ void RenderGamepadImage(GamepadImage *ctx)
         }
     }
 
-    if (ctx->showing_battery) {
+    if (ctx->display_mode == CONTROLLER_MODE_TESTING && ctx->battery_level != SDL_JOYSTICK_POWER_UNKNOWN) {
         dst.x = (float)ctx->x + ctx->gamepad_width - ctx->battery_width;
         dst.y = (float)ctx->y;
         dst.w = (float)ctx->battery_width;
@@ -576,7 +534,7 @@ void RenderGamepadImage(GamepadImage *ctx)
         SDL_RenderTexture(ctx->renderer, ctx->battery_texture[1 + ctx->battery_level], NULL, &dst);
     }
 
-    if (ctx->showing_touchpad) {
+    if (ctx->display_mode == CONTROLLER_MODE_TESTING && ctx->showing_touchpad) {
         dst.x = (float)ctx->x + (ctx->gamepad_width - ctx->touchpad_width) / 2;
         dst.y = (float)ctx->y + ctx->gamepad_height;
         dst.w = (float)ctx->touchpad_width;
@@ -673,6 +631,8 @@ struct GamepadDisplay
     float gyro_data[3];
     Uint64 last_sensor_update;
 
+    ControllerDisplayMode display_mode;
+
     SDL_Rect area;
 };
 
@@ -691,16 +651,22 @@ GamepadDisplay *CreateGamepadDisplay(SDL_Renderer *renderer)
     return ctx;
 }
 
-void SetGamepadDisplayArea(GamepadDisplay *ctx, int x, int y, int w, int h)
+void SetGamepadDisplayDisplayMode(GamepadDisplay *ctx, ControllerDisplayMode display_mode)
+{
+    if (!ctx) {
+        return;
+    }
+
+    ctx->display_mode = display_mode;
+}
+
+void SetGamepadDisplayArea(GamepadDisplay *ctx, const SDL_Rect *area)
 {
     if (!ctx) {
         return;
     }
 
-    ctx->area.x = x;
-    ctx->area.y = y;
-    ctx->area.w = w;
-    ctx->area.h = h;
+    SDL_copyp(&ctx->area, area);
 }
 
 static SDL_bool GetBindingString(const char *label, char *mapping, char *text, size_t size)
@@ -794,7 +760,7 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
     const float arrow_extent = 48.0f;
     SDL_FRect dst, rect;
     Uint8 r, g, b, a;
-    char *mapping;
+    char *mapping = NULL;
     SDL_bool has_accel;
     SDL_bool has_gyro;
 
@@ -812,6 +778,11 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
     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;
+        }
+
         SDL_snprintf(text, sizeof(text), "%s:", gamepad_button_names[i]);
         SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
 
@@ -827,9 +798,11 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
         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);
+        if (ctx->display_mode == CONTROLLER_MODE_BINDING) {
+            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;
@@ -838,7 +811,14 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
     for (i = 0; i < SDL_GAMEPAD_AXIS_MAX; ++i) {
         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);
+        Sint16 value;
+
+        if (ctx->display_mode == CONTROLLER_MODE_TESTING &&
+            !SDL_GamepadHasAxis(gamepad, axis)) {
+            continue;
+        }
+
+        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);
@@ -875,12 +855,14 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
             SDL_RenderFillRect(ctx->renderer, &rect);
         }
 
-        if (has_negative && GetAxisBindingString(axis, -1, mapping, binding, sizeof(binding))) {
-            float text_x;
+        if (ctx->display_mode == CONTROLLER_MODE_BINDING && has_negative) {
+            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);
+                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);
+            }
         }
 
         dst.x += arrow_extent;
@@ -894,12 +876,14 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
             SDL_RenderFillRect(ctx->renderer, &rect);
         }
 
-        if (GetAxisBindingString(axis, 1, mapping, binding, sizeof(binding))) {
-            float text_x;
+        if (ctx->display_mode == CONTROLLER_MODE_BINDING) {
+            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);
+                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);
+            }
         }
 
         dst.x += arrow_extent;
@@ -916,72 +900,74 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
         y += ctx->button_height + 2;
     }
 
-    if (SDL_GetNumGamepadTouchpads(gamepad) > 0) {
-        int num_fingers = SDL_GetNumGamepadTouchpadFingers(gamepad, 0);
-        for (i = 0; i < num_fingers; ++i) {
-            Uint8 state;
-            float finger_x, finger_y, finger_pressure;
+    if (ctx->display_mode == CONTROLLER_MODE_TESTING) {
+        if (SDL_GetNumGamepadTouchpads(gamepad) > 0) {
+            int num_fingers = SDL_GetNumGamepadTouchpadFingers(gamepad, 0);
+            for (i = 0; i < num_fingers; ++i) {
+                Uint8 state;
+                float finger_x, finger_y, finger_pressure;
 
-            if (SDL_GetGamepadTouchpadFinger(gamepad, 0, i, &state, &finger_x, &finger_y, &finger_pressure) < 0) {
-                continue;
-            }
+                if (SDL_GetGamepadTouchpadFinger(gamepad, 0, i, &state, &finger_x, &finger_y, &finger_pressure) < 0) {
+                    continue;
+                }
 
-            SDL_snprintf(text, sizeof(text), "Touch finger %d:", i);
-            SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
+                SDL_snprintf(text, sizeof(text), "Touch finger %d:", i);
+                SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
 
-            if (state) {
-                SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21);
-            } else {
-                SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255);
-            }
+                if (state) {
+                    SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21);
+                } else {
+                    SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255);
+                }
 
-            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);
+                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 (state) {
-                SDL_snprintf(text, sizeof(text), "(%.2f,%.2f)", finger_x, finger_y);
-                SDLTest_DrawString(ctx->renderer, x + center + ctx->button_width + 4.0f, y, text);
-            }
+                if (state) {
+                    SDL_snprintf(text, sizeof(text), "(%.2f,%.2f)", finger_x, finger_y);
+                    SDLTest_DrawString(ctx->renderer, x + center + ctx->button_width + 4.0f, y, text);
+                }
 
-            y += ctx->button_height + 2.0f;
+                y += ctx->button_height + 2.0f;
+            }
         }
-    }
 
-    has_accel = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL);
-    has_gyro = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO);
-    if (has_accel || has_gyro) {
-        const int SENSOR_UPDATE_INTERVAL_MS = 100;
-        Uint64 now = SDL_GetTicks();
+        has_accel = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL);
+        has_gyro = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO);
+        if (has_accel || has_gyro) {
+            const int SENSOR_UPDATE_INTERVAL_MS = 100;
+            Uint64 now = SDL_GetTicks();
 
-        if (now >= ctx->last_sensor_update + SENSOR_UPDATE_INTERVAL_MS) {
-            if (has_accel) {
-                SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_ACCEL, ctx->accel_data, SDL_arraysize(ctx->accel_data));
-            }
-            if (has_gyro) {
-                SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_GYRO, ctx->gyro_data, SDL_arraysize(ctx->gyro_data));
+            if (now >= ctx->last_sensor_update + SENSOR_UPDATE_INTERVAL_MS) {
+                if (has_accel) {
+                    SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_ACCEL, ctx->accel_data, SDL_arraysize(ctx->accel_data));
+                }
+                if (has_gyro) {
+                    SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_GYRO, ctx->gyro_data, SDL_arraysize(ctx->gyro_data));
+                }
+                ctx->last_sensor_update = now;
             }
-            ctx->last_sensor_update = now;
-        }
 
-        if (has_accel) {
-            SDL_strlcpy(text, "Accelerometer:", sizeof(text));
-            SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
-            SDL_snprintf(text, sizeof(text), "(%.2f,%.2f,%.2f)", ctx->accel_data[0], ctx->accel_data[1], ctx->accel_data[2]);
-            SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text);
+            if (has_accel) {
+                SDL_strlcpy(text, "Accelerometer:", sizeof(text));
+                SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
+                SDL_snprintf(text, sizeof(text), "(%.2f,%.2f,%.2f)", ctx->accel_data[0], ctx->accel_data[1], ctx->accel_data[2]);
+                SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text);
 
-            y += ctx->button_height + 2.0f;
-        }
+                y += ctx->button_height + 2.0f;
+            }
 
-        if (has_gyro) {
-            SDL_strlcpy(text, "Gyro:", sizeof(text));
-            SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
-            SDL_snprintf(text, sizeof(text), "(%.2f,%.2f,%.2f)", ctx->gyro_data[0], ctx->gyro_data[1], ctx->gyro_data[2]);
-            SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text);
+            if (has_gyro) {
+                SDL_strlcpy(text, "Gyro:", sizeof(text));
+                SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
+                SDL_snprintf(text, sizeof(text), "(%.2f,%.2f,%.2f)", ctx->gyro_data[0], ctx->gyro_data[1], ctx->gyro_data[2]);
+                SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text);
 
-            y += ctx->button_height + 2.0f;
+                y += ctx->button_height + 2.0f;
+            }
         }
     }
 
@@ -1028,16 +1014,13 @@ JoystickDisplay *CreateJoystickDisplay(SDL_Renderer *renderer)
     return ctx;
 }
 
-void SetJoystickDisplayArea(JoystickDisplay *ctx, int x, int y, int w, int h)
+void SetJoystickDisplayArea(JoystickDisplay *ctx, const SDL_Rect *area)
 {
     if (!ctx) {
         return;
     }
 
-    ctx->area.x = x;
-    ctx->area.y = y;
-    ctx->area.w = w;
-    ctx->area.h = h;
+    SDL_copyp(&ctx->area, area);
 }
 
 void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick)
@@ -1259,16 +1242,16 @@ GamepadButton *CreateGamepadButton(SDL_Renderer *renderer, const char *label)
     return ctx;
 }
 
-void SetGamepadButtonArea(GamepadButton *ctx, int x, int y, int w, int h)
+void SetGamepadButtonArea(GamepadButton *ctx, const SDL_Rect *area)
 {
     if (!ctx) {
         return;
     }
 
-    ctx->area.x = (float)x;
-    ctx->area.y = (float)y;
-    ctx->area.w = (float)w;
-    ctx->area.h = (float)h;
+    ctx->area.x = (float)area->x;
+    ctx->area.y = (float)area->y;
+    ctx->area.w = (float)area->w;
+    ctx->area.h = (float)area->h;
 }
 
 void SetGamepadButtonHighlight(GamepadButton *ctx, SDL_bool highlight)

+ 11 - 6
test/gamepadutils.h

@@ -14,6 +14,12 @@
 
 typedef struct GamepadImage GamepadImage;
 
+typedef enum
+{
+    CONTROLLER_MODE_TESTING,
+    CONTROLLER_MODE_BINDING,
+} ControllerDisplayMode;
+
 typedef enum
 {
     GAMEPAD_IMAGE_FACE_BLANK,
@@ -26,9 +32,7 @@ extern GamepadImage *CreateGamepadImage(SDL_Renderer *renderer);
 extern void SetGamepadImagePosition(GamepadImage *ctx, int x, int y);
 extern void SetGamepadImageShowingFront(GamepadImage *ctx, SDL_bool showing_front);
 extern void SetGamepadImageFaceStyle(GamepadImage *ctx, GamepadImageFaceStyle face_style);
-extern void SetGamepadImageShowingBattery(GamepadImage *ctx, SDL_bool showing_battery);
-extern void SetGamepadImageShowingTouchpad(GamepadImage *ctx, SDL_bool showing_touchpad);
-extern void GetGamepadImageArea(GamepadImage *ctx, int *x, int *y, int *width, int *height);
+extern void SetGamepadImageDisplayMode(GamepadImage *ctx, ControllerDisplayMode display_mode);
 extern int GetGamepadImageButtonWidth(GamepadImage *ctx);
 extern int GetGamepadImageButtonHeight(GamepadImage *ctx);
 extern int GetGamepadImageAxisWidth(GamepadImage *ctx);
@@ -50,7 +54,8 @@ extern void DestroyGamepadImage(GamepadImage *ctx);
 typedef struct GamepadDisplay GamepadDisplay;
 
 extern GamepadDisplay *CreateGamepadDisplay(SDL_Renderer *renderer);
-extern void SetGamepadDisplayArea(GamepadDisplay *ctx, int x, int y, int w, int h);
+extern void SetGamepadDisplayDisplayMode(GamepadDisplay *ctx, ControllerDisplayMode display_mode);
+extern void SetGamepadDisplayArea(GamepadDisplay *ctx, const SDL_Rect *area);
 extern void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad);
 extern void DestroyGamepadDisplay(GamepadDisplay *ctx);
 
@@ -59,7 +64,7 @@ extern void DestroyGamepadDisplay(GamepadDisplay *ctx);
 typedef struct JoystickDisplay JoystickDisplay;
 
 extern JoystickDisplay *CreateJoystickDisplay(SDL_Renderer *renderer);
-extern void SetJoystickDisplayArea(JoystickDisplay *ctx, int x, int y, int w, int h);
+extern void SetJoystickDisplayArea(JoystickDisplay *ctx, const SDL_Rect *area);
 extern void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick);
 extern void DestroyJoystickDisplay(JoystickDisplay *ctx);
 
@@ -68,7 +73,7 @@ extern void DestroyJoystickDisplay(JoystickDisplay *ctx);
 typedef struct GamepadButton GamepadButton;
 
 extern GamepadButton *CreateGamepadButton(SDL_Renderer *renderer, const char *label);
-extern void SetGamepadButtonArea(GamepadButton *ctx, int x, int y, int w, int h);
+extern void SetGamepadButtonArea(GamepadButton *ctx, const SDL_Rect *area);
 extern void SetGamepadButtonHighlight(GamepadButton *ctx, SDL_bool highlight);
 extern int GetGamepadButtonLabelWidth(GamepadButton *ctx);
 extern int GetGamepadButtonLabelHeight(GamepadButton *ctx);

+ 314 - 137
test/testcontroller.c

@@ -27,7 +27,8 @@
 #define TITLE_HEIGHT 48
 #define PANEL_SPACING 25
 #define PANEL_WIDTH 250
-#define BUTTON_MARGIN 8
+#define MINIMUM_BUTTON_WIDTH 96
+#define BUTTON_MARGIN 16
 #define BUTTON_PADDING 12
 #define GAMEPAD_WIDTH 512
 #define GAMEPAD_HEIGHT 480
@@ -56,10 +57,17 @@ typedef struct
 
 static SDL_Window *window = NULL;
 static SDL_Renderer *screen = NULL;
+static ControllerDisplayMode display_mode = CONTROLLER_MODE_TESTING;
 static GamepadImage *image = NULL;
 static GamepadDisplay *gamepad_elements = NULL;
 static JoystickDisplay *joystick_elements = NULL;
+static GamepadButton *setup_mapping_button = NULL;
+static GamepadButton *test_mapping_button = NULL;
+static GamepadButton *cancel_button = NULL;
+static GamepadButton *clear_button = NULL;
 static GamepadButton *copy_button = NULL;
+static GamepadButton *paste_button = NULL;
+static char *backup_mapping = NULL;
 static SDL_bool retval = SDL_FALSE;
 static SDL_bool done = SDL_FALSE;
 static SDL_bool set_LED = SDL_FALSE;
@@ -209,6 +217,104 @@ static void CyclePS5TriggerEffect(Controller *device)
     SDL_SendGamepadEffect(device->gamepad, &state, sizeof(state));
 }
 
+static void ClearButtonHighlights()
+{
+    SetGamepadButtonHighlight(setup_mapping_button, SDL_FALSE);
+    SetGamepadButtonHighlight(test_mapping_button, SDL_FALSE);
+    SetGamepadButtonHighlight(cancel_button, SDL_FALSE);
+    SetGamepadButtonHighlight(clear_button, SDL_FALSE);
+    SetGamepadButtonHighlight(copy_button, SDL_FALSE);
+    SetGamepadButtonHighlight(paste_button, SDL_FALSE);
+}
+
+static void UpdateButtonHighlights(float x, float y)
+{
+    ClearButtonHighlights();
+
+    if (display_mode == CONTROLLER_MODE_TESTING) {
+        SetGamepadButtonHighlight(setup_mapping_button, GamepadButtonContains(setup_mapping_button, x, y));
+    } else if (display_mode == CONTROLLER_MODE_BINDING) {
+        SetGamepadButtonHighlight(test_mapping_button, GamepadButtonContains(test_mapping_button, x, y));
+        SetGamepadButtonHighlight(cancel_button, GamepadButtonContains(cancel_button, x, y));
+        SetGamepadButtonHighlight(clear_button, GamepadButtonContains(clear_button, x, y));
+        SetGamepadButtonHighlight(copy_button, GamepadButtonContains(copy_button, x, y));
+        SetGamepadButtonHighlight(paste_button, GamepadButtonContains(paste_button, x, y));
+    }
+}
+
+static void SetDisplayMode(ControllerDisplayMode mode)
+{
+    float x, y;
+
+    if (mode == CONTROLLER_MODE_BINDING) {
+        /* Make a backup of the current mapping */
+        backup_mapping = SDL_GetGamepadMapping(controller->gamepad);
+    }
+
+    display_mode = mode;
+    SetGamepadImageDisplayMode(image, mode);
+    SetGamepadDisplayDisplayMode(gamepad_elements, mode);
+
+    SDL_GetMouseState(&x, &y);
+    SDL_RenderCoordinatesFromWindow(screen, x, y, &x, &y);
+    UpdateButtonHighlights(x, y);
+}
+
+static void CancelMapping(void)
+{
+    if (backup_mapping) {
+        SDL_SetGamepadMapping(controller->id, backup_mapping);
+        SDL_free(backup_mapping);
+        backup_mapping = NULL;
+    }
+    SetDisplayMode(CONTROLLER_MODE_TESTING);
+}
+
+static void ClearMapping(void)
+{
+    SDL_SetGamepadMapping(controller->id, NULL);
+}
+
+static void CopyMapping(void)
+{
+    if (controller && controller->gamepad) {
+        char *mapping = SDL_GetGamepadMapping(controller->gamepad);
+        if (mapping) {
+            const char *name = SDL_GetGamepadName(controller->gamepad);
+            char *wildcard = SDL_strchr(mapping, '*');
+            if (wildcard && name && *name) {
+                char *text;
+                size_t size;
+
+                /* Personalize the mapping for this controller */
+                *wildcard++ = '\0';
+                size = SDL_strlen(mapping) + SDL_strlen(name) + SDL_strlen(wildcard) + 1;
+                text = SDL_malloc(size);
+                if (!text) {
+                    return;
+                }
+                SDL_snprintf(text, size, "%s%s%s", mapping, name, wildcard);
+                SDL_SetClipboardText(text);
+                SDL_free(text);
+            } else {
+                SDL_SetClipboardText(mapping);
+            }
+            SDL_free(mapping);
+        }
+    }
+}
+
+static void PasteMapping(void)
+{
+    if (controller) {
+        char *mapping = SDL_GetClipboardText();
+        if (*mapping) {
+            SDL_SetGamepadMapping(controller->id, mapping);
+        }
+        SDL_free(mapping);
+    }
+}
+
 static int FindController(SDL_JoystickID id)
 {
     int i;
@@ -225,29 +331,21 @@ static void SetController(SDL_JoystickID id)
 {
     int i = FindController(id);
 
+    if (i >= 0 && display_mode == CONTROLLER_MODE_BINDING) {
+        /* Don't change controllers while binding */
+        return;
+    }
+
     if (i < 0 && num_controllers > 0) {
         i = 0;
     }
 
     if (i >= 0) {
         controller = &controllers[i];
-
-        if (controller->gamepad) {
-            SetGamepadImageShowingBattery(image, SDL_TRUE);
-        } else {
-            SetGamepadImageShowingBattery(image, SDL_FALSE);
-        }
-        if (SDL_GetNumGamepadTouchpads(controller->gamepad) > 0) {
-            SetGamepadImageShowingTouchpad(image, SDL_TRUE);
-        } else {
-            SetGamepadImageShowingTouchpad(image, SDL_FALSE);
-        }
     } else {
         controller = NULL;
-
-        SetGamepadImageShowingBattery(image, SDL_FALSE);
-        SetGamepadImageShowingTouchpad(image, SDL_FALSE);
     }
+    SetDisplayMode(CONTROLLER_MODE_TESTING);
 }
 
 static void AddController(SDL_JoystickID id, SDL_bool verbose)
@@ -354,6 +452,19 @@ static void DelController(SDL_JoystickID id)
     SetController(0);
 }
 
+static void HandleGamepadRemapped(SDL_JoystickID id)
+{
+    int i = FindController(id);
+
+    if (i < 0) {
+        return;
+    }
+
+    if (!controllers[i].gamepad) {
+        controllers[i].gamepad = SDL_OpenGamepad(id);
+    }
+}
+
 static Uint16 ConvertAxisToRumble(Sint16 axisval)
 {
     /* Only start rumbling if the axis is past the halfway point */
@@ -413,6 +524,10 @@ static void OpenVirtualGamepad(void)
     SDL_VirtualJoystickDesc desc;
     SDL_JoystickID virtual_id;
 
+    if (virtual_joystick) {
+        return;
+    }
+
     SDL_zero(desc);
     desc.version = SDL_VIRTUAL_JOYSTICK_DESC_VERSION;
     desc.type = SDL_JOYSTICK_TYPE_GAMEPAD;
@@ -578,47 +693,76 @@ static void DrawGamepadInfo(SDL_Renderer *renderer)
         SDLTest_DrawString(renderer, x, y, text);
     }
 
-    SDL_snprintf(text, SDL_arraysize(text), "VID: 0x%.4x PID: 0x%.4x",
-        SDL_GetJoystickVendor(controller->joystick),
-        SDL_GetJoystickProduct(controller->joystick));
-    y = (float)SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT;
-    x = (float)SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text));
-    SDLTest_DrawString(renderer, x, y, text);
-
-    serial = SDL_GetJoystickSerial(controller->joystick);
-    if (serial && *serial) {
-        SDL_snprintf(text, SDL_arraysize(text), "Serial: %s", serial);
-        x = (float)SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2;
+    if (display_mode == CONTROLLER_MODE_TESTING) {
+        SDL_snprintf(text, SDL_arraysize(text), "VID: 0x%.4x PID: 0x%.4x",
+                     SDL_GetJoystickVendor(controller->joystick),
+                     SDL_GetJoystickProduct(controller->joystick));
         y = (float)SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT;
+        x = (float)SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text));
         SDLTest_DrawString(renderer, x, y, text);
+
+        serial = SDL_GetJoystickSerial(controller->joystick);
+        if (serial && *serial) {
+            SDL_snprintf(text, SDL_arraysize(text), "Serial: %s", serial);
+            x = (float)SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2;
+            y = (float)SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT;
+            SDLTest_DrawString(renderer, x, y, text);
+        }
     }
 }
 
-static void CopyMappingToClipboard()
+static void UpdateGamepadEffects(void)
 {
-    if (controller && controller->gamepad) {
-        char *mapping = SDL_GetGamepadMapping(controller->gamepad);
-        if (mapping) {
-            const char *name = SDL_GetGamepadName(controller->gamepad);
-            char *wildcard = SDL_strchr(mapping, '*');
-            if (wildcard && name && *name) {
-                char *text;
-                size_t size;
+    if (display_mode != CONTROLLER_MODE_TESTING || !controller->gamepad) {
+        return;
+    }
 
-                /* Personalize the mapping for this controller */
-                *wildcard++ = '\0';
-                size = SDL_strlen(mapping) + SDL_strlen(name) + SDL_strlen(wildcard) + 1;
-                text = SDL_malloc(size);
-                if (!text) {
-                    return;
-                }
-                SDL_snprintf(text, size, "%s%s%s", mapping, name, wildcard);
-                SDL_SetClipboardText(text);
-                SDL_free(text);
+    /* Update LED based on left thumbstick position */
+    {
+        Sint16 x = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTX);
+        Sint16 y = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY);
+
+        if (!set_LED) {
+            set_LED = (x < -8000 || x > 8000 || y > 8000);
+        }
+        if (set_LED) {
+            Uint8 r, g, b;
+
+            if (x < 0) {
+                r = (Uint8)(((~x) * 255) / 32767);
+                b = 0;
             } else {
-                SDL_SetClipboardText(mapping);
+                r = 0;
+                b = (Uint8)(((int)(x)*255) / 32767);
             }
-            SDL_free(mapping);
+            if (y > 0) {
+                g = (Uint8)(((int)(y)*255) / 32767);
+            } else {
+                g = 0;
+            }
+
+            SDL_SetGamepadLED(controller->gamepad, r, g, b);
+        }
+    }
+
+    if (controller->trigger_effect == 0) {
+        /* Update rumble based on trigger state */
+        {
+            Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER);
+            Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER);
+            Uint16 low_frequency_rumble = ConvertAxisToRumble(left);
+            Uint16 high_frequency_rumble = ConvertAxisToRumble(right);
+            SDL_RumbleGamepad(controller->gamepad, low_frequency_rumble, high_frequency_rumble, 250);
+        }
+
+        /* Update trigger rumble based on thumbstick state */
+        {
+            Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY);
+            Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHTY);
+            Uint16 left_rumble = ConvertAxisToRumble(~left);
+            Uint16 right_rumble = ConvertAxisToRumble(~right);
+
+            SDL_RumbleGamepadTriggers(controller->gamepad, left_rumble, right_rumble, 250);
         }
     }
 }
@@ -653,6 +797,10 @@ static void loop(void *arg)
             SetController(event.jbutton.which);
             break;
 
+        case SDL_EVENT_GAMEPAD_REMAPPED:
+            HandleGamepadRemapped(event.gdevice.which);
+            break;
+
         case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
         case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
         case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
@@ -702,11 +850,13 @@ static void loop(void *arg)
                     SDL_GetGamepadStringForButton((SDL_GamepadButton) event.gbutton.button),
                     event.gbutton.state ? "pressed" : "released");
 
-            /* Cycle PS5 trigger effects when the microphone button is pressed */
-            if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN &&
-                controller && SDL_GetGamepadType(controller->gamepad) == SDL_GAMEPAD_TYPE_PS5 &&
-                event.gbutton.button == SDL_GAMEPAD_BUTTON_MISC1) {
-                CyclePS5TriggerEffect(controller);
+            if (display_mode == CONTROLLER_MODE_TESTING) {
+                /* Cycle PS5 trigger effects when the microphone button is pressed */
+                if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN &&
+                    controller && SDL_GetGamepadType(controller->gamepad) == SDL_GAMEPAD_TYPE_PS5 &&
+                    event.gbutton.button == SDL_GAMEPAD_BUTTON_MISC1) {
+                    CyclePS5TriggerEffect(controller);
+                }
             }
             break;
 
@@ -715,48 +865,72 @@ static void loop(void *arg)
             break;
 
         case SDL_EVENT_MOUSE_BUTTON_DOWN:
-            if (virtual_joystick) {
+            if (virtual_joystick && controller->joystick == virtual_joystick) {
                 VirtualGamepadMouseDown(event.button.x, event.button.y);
             }
-            SetGamepadButtonHighlight(copy_button, GamepadButtonContains(copy_button, event.button.x, event.button.y));
+            UpdateButtonHighlights(event.button.x, event.button.y);
             break;
 
         case SDL_EVENT_MOUSE_BUTTON_UP:
-            if (virtual_joystick) {
+            if (virtual_joystick && controller->joystick == virtual_joystick) {
                 VirtualGamepadMouseUp(event.button.x, event.button.y);
             }
-            if (GamepadButtonContains(copy_button, event.button.x, event.button.y)) {
-                CopyMappingToClipboard();
+
+            ClearButtonHighlights();
+
+            if (display_mode == CONTROLLER_MODE_TESTING) {
+                if (GamepadButtonContains(setup_mapping_button, event.button.x, event.button.y)) {
+                    SetDisplayMode(CONTROLLER_MODE_BINDING);
+                }
+            } else if (display_mode == CONTROLLER_MODE_BINDING) {
+                if (GamepadButtonContains(test_mapping_button, event.button.x, event.button.y)) {
+                    SetDisplayMode(CONTROLLER_MODE_TESTING);
+                } else if (GamepadButtonContains(cancel_button, event.button.x, event.button.y)) {
+                    CancelMapping();
+                } else if (GamepadButtonContains(clear_button, event.button.x, event.button.y)) {
+                    ClearMapping();
+                } else if (GamepadButtonContains(copy_button, event.button.x, event.button.y)) {
+                    CopyMapping();
+                } else if (GamepadButtonContains(paste_button, event.button.x, event.button.y)) {
+                    PasteMapping();
+                }
             }
-            SetGamepadButtonHighlight(copy_button, SDL_FALSE);
             break;
 
         case SDL_EVENT_MOUSE_MOTION:
-            if (virtual_joystick) {
+            if (virtual_joystick && controller->joystick == virtual_joystick) {
                 VirtualGamepadMouseMotion(event.motion.x, event.motion.y);
             }
-            SetGamepadButtonHighlight(copy_button, event.motion.state && GamepadButtonContains(copy_button, event.motion.x, event.motion.y));
+            UpdateButtonHighlights(event.motion.x, event.motion.y);
             break;
 
         case SDL_EVENT_KEY_DOWN:
-            if (event.key.keysym.sym >= SDLK_0 && event.key.keysym.sym <= SDLK_9) {
-                if (controller && controller->gamepad) {
-                    int player_index = (event.key.keysym.sym - SDLK_0);
+            if (display_mode == CONTROLLER_MODE_TESTING) {
+                if (event.key.keysym.sym >= SDLK_0 && event.key.keysym.sym <= SDLK_9) {
+                    if (controller && controller->gamepad) {
+                        int player_index = (event.key.keysym.sym - SDLK_0);
 
-                    SDL_SetGamepadPlayerIndex(controller->gamepad, player_index);
+                        SDL_SetGamepadPlayerIndex(controller->gamepad, player_index);
+                    }
+                    break;
+                } else if (event.key.keysym.sym == SDLK_a) {
+                    OpenVirtualGamepad();
+                } else if (event.key.keysym.sym == SDLK_d) {
+                    CloseVirtualGamepad();
+                } else if (event.key.keysym.sym == SDLK_ESCAPE) {
+                    done = SDL_TRUE;
+                }
+            } else if (display_mode == CONTROLLER_MODE_BINDING) {
+                if (event.key.keysym.sym == SDLK_c && (event.key.keysym.mod & SDL_KMOD_CTRL)) {
+                    CopyMapping();
+                } else if (event.key.keysym.sym == SDLK_v && (event.key.keysym.mod & SDL_KMOD_CTRL)) {
+                    PasteMapping();
+                } else if (event.key.keysym.sym == SDLK_x && (event.key.keysym.mod & SDL_KMOD_CTRL)) {
+                    CopyMapping();
+                    ClearMapping();
+                } else if (event.key.keysym.sym == SDLK_ESCAPE) {
+                    CancelMapping();
                 }
-                break;
-            }
-            if (event.key.keysym.sym == SDLK_a) {
-                OpenVirtualGamepad();
-                break;
-            }
-            if (event.key.keysym.sym == SDLK_d) {
-                CloseVirtualGamepad();
-                break;
-            }
-            if (event.key.keysym.sym == SDLK_ESCAPE) {
-                done = SDL_TRUE;
             }
             break;
         case SDL_EVENT_QUIT:
@@ -780,62 +954,21 @@ static void loop(void *arg)
         RenderGamepadDisplay(gamepad_elements, controller->gamepad);
         RenderJoystickDisplay(joystick_elements, controller->joystick);
 
-        if (controller->gamepad) {
-            RenderGamepadButton(copy_button);
+        if (display_mode == CONTROLLER_MODE_TESTING) {
+            RenderGamepadButton(setup_mapping_button);
+        } else if (display_mode == CONTROLLER_MODE_BINDING) {
+            RenderGamepadButton(test_mapping_button);
+            RenderGamepadButton(cancel_button);
+            RenderGamepadButton(clear_button);
+            if (controller->gamepad) {
+                RenderGamepadButton(copy_button);
+            }
+            RenderGamepadButton(paste_button);
         }
 
         DrawGamepadInfo(screen);
 
-        if (controller->gamepad) {
-            /* Update LED based on left thumbstick position */
-            {
-                Sint16 x = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTX);
-                Sint16 y = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY);
-
-                if (!set_LED) {
-                    set_LED = (x < -8000 || x > 8000 || y > 8000);
-                }
-                if (set_LED) {
-                    Uint8 r, g, b;
-
-                    if (x < 0) {
-                        r = (Uint8)(((~x) * 255) / 32767);
-                        b = 0;
-                    } else {
-                        r = 0;
-                        b = (Uint8)(((int)(x)*255) / 32767);
-                    }
-                    if (y > 0) {
-                        g = (Uint8)(((int)(y)*255) / 32767);
-                    } else {
-                        g = 0;
-                    }
-
-                    SDL_SetGamepadLED(controller->gamepad, r, g, b);
-                }
-            }
-
-            if (controller->trigger_effect == 0) {
-                /* Update rumble based on trigger state */
-                {
-                    Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER);
-                    Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER);
-                    Uint16 low_frequency_rumble = ConvertAxisToRumble(left);
-                    Uint16 high_frequency_rumble = ConvertAxisToRumble(right);
-                    SDL_RumbleGamepad(controller->gamepad, low_frequency_rumble, high_frequency_rumble, 250);
-                }
-
-                /* Update trigger rumble based on thumbstick state */
-                {
-                    Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY);
-                    Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHTY);
-                    Uint16 left_rumble = ConvertAxisToRumble(~left);
-                    Uint16 right_rumble = ConvertAxisToRumble(~right);
-
-                    SDL_RumbleGamepadTriggers(controller->gamepad, left_rumble, right_rumble, 250);
-                }
-            }
-        }
+        UpdateGamepadEffects();
     } else {
         DrawGamepadWaiting(screen);
     }
@@ -854,7 +987,7 @@ int main(int argc, char *argv[])
     int i;
     float content_scale;
     int screen_width, screen_height;
-    int button_width, button_height;
+    SDL_Rect area;
     int gamepad_index = -1;
     SDLTest_CommonState *state;
 
@@ -960,18 +1093,62 @@ int main(int argc, char *argv[])
         return 2;
     }
     SetGamepadImagePosition(image, PANEL_WIDTH + PANEL_SPACING, TITLE_HEIGHT);
-    SetGamepadImageShowingBattery(image, SDL_TRUE);
 
     gamepad_elements = CreateGamepadDisplay(screen);
-    SetGamepadDisplayArea(gamepad_elements, 0, TITLE_HEIGHT, PANEL_WIDTH, GAMEPAD_HEIGHT);
+    area.x = 0;
+    area.y = TITLE_HEIGHT;
+    area.w = PANEL_WIDTH;
+    area.h = GAMEPAD_HEIGHT;
+    SetGamepadDisplayArea(gamepad_elements, &area);
 
     joystick_elements = CreateJoystickDisplay(screen);
-    SetJoystickDisplayArea(joystick_elements, PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING, TITLE_HEIGHT, PANEL_WIDTH, GAMEPAD_HEIGHT);
-
-    copy_button = CreateGamepadButton(screen, "Copy to Clipboard");
-    button_width = GetGamepadButtonLabelWidth(copy_button) + 2 * BUTTON_PADDING;
-    button_height = GetGamepadButtonLabelHeight(copy_button) + 2 * BUTTON_PADDING;
-    SetGamepadButtonArea(copy_button, BUTTON_MARGIN, SCREEN_HEIGHT - BUTTON_MARGIN - button_height, button_width, button_height);
+    area.x = PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING;
+    area.y = TITLE_HEIGHT;
+    area.w = PANEL_WIDTH;
+    area.h = GAMEPAD_HEIGHT;
+    SetJoystickDisplayArea(joystick_elements, &area);
+
+    setup_mapping_button = CreateGamepadButton(screen, "Setup Mapping");
+    area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(setup_mapping_button) + 2 * BUTTON_PADDING);
+    area.h = GetGamepadButtonLabelHeight(setup_mapping_button) + 2 * BUTTON_PADDING;
+    area.x = BUTTON_MARGIN;
+    area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
+    SetGamepadButtonArea(setup_mapping_button, &area);
+
+    cancel_button = CreateGamepadButton(screen, "Cancel");
+    area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(cancel_button) + 2 * BUTTON_PADDING);
+    area.h = GetGamepadButtonLabelHeight(cancel_button) + 2 * BUTTON_PADDING;
+    area.x = BUTTON_MARGIN;
+    area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
+    SetGamepadButtonArea(cancel_button, &area);
+
+    clear_button = CreateGamepadButton(screen, "Clear");
+    area.x += area.w + BUTTON_PADDING;
+    area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(clear_button) + 2 * BUTTON_PADDING);
+    area.h = GetGamepadButtonLabelHeight(clear_button) + 2 * BUTTON_PADDING;
+    area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
+    SetGamepadButtonArea(clear_button, &area);
+
+    copy_button = CreateGamepadButton(screen, "Copy");
+    area.x += area.w + BUTTON_PADDING;
+    area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(copy_button) + 2 * BUTTON_PADDING);
+    area.h = GetGamepadButtonLabelHeight(copy_button) + 2 * BUTTON_PADDING;
+    area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
+    SetGamepadButtonArea(copy_button, &area);
+
+    paste_button = CreateGamepadButton(screen, "Paste");
+    area.x += area.w + BUTTON_PADDING;
+    area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(paste_button) + 2 * BUTTON_PADDING);
+    area.h = GetGamepadButtonLabelHeight(paste_button) + 2 * BUTTON_PADDING;
+    area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
+    SetGamepadButtonArea(paste_button, &area);
+
+    test_mapping_button = CreateGamepadButton(screen, "Done");
+    area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(test_mapping_button) + 2 * BUTTON_PADDING);
+    area.h = GetGamepadButtonLabelHeight(test_mapping_button) + 2 * BUTTON_PADDING;
+    area.x = SCREEN_WIDTH / 2 - area.w / 2;
+    area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
+    SetGamepadButtonArea(test_mapping_button, &area);
 
     /* Process the initial gamepad list */
     loop(NULL);