From 4feb2f4b1aed64594eaa681e76b8cae4a375bdf1 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 11 Jul 2023 10:04:25 -0700
Subject: [PATCH] Added a button to copy the gamepad mapping to the clipboard
---
test/gamepadutils.c | 204 ++++++++++++++++++++++++++++++++++++++++++++
test/gamepadutils.h | 12 +++
test/testgamepad.c | 46 ++++++++++
3 files changed, 262 insertions(+)
diff --git a/test/gamepadutils.c b/test/gamepadutils.c
index 61a4f24fe202..004005f8e4c8 100644
--- a/test/gamepadutils.c
+++ b/test/gamepadutils.c
@@ -26,6 +26,7 @@
#include "gamepad_button_small.h"
#include "gamepad_axis.h"
#include "gamepad_axis_arrow.h"
+#include "gamepad_button_background.h"
/* This is indexed by SDL_GamepadButton. */
static const struct
@@ -612,6 +613,10 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
SDL_bool has_accel;
SDL_bool has_gyro;
+ if (!ctx) {
+ return;
+ }
+
SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a);
x = ctx->area.x + margin;
@@ -776,6 +781,12 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
void DestroyGamepadDisplay(GamepadDisplay *ctx)
{
+ if (!ctx) {
+ return;
+ }
+
+ SDL_DestroyTexture(ctx->button_texture);
+ SDL_DestroyTexture(ctx->arrow_texture);
SDL_free(ctx);
}
@@ -834,6 +845,10 @@ void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick)
SDL_FRect dst, rect;
Uint8 r, g, b, a;
+ if (!ctx) {
+ return;
+ }
+
SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a);
x = (float)ctx->area.x + margin;
@@ -993,6 +1008,195 @@ void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick)
void DestroyJoystickDisplay(JoystickDisplay *ctx)
{
+ if (!ctx) {
+ return;
+ }
+
+ SDL_DestroyTexture(ctx->button_texture);
+ SDL_DestroyTexture(ctx->arrow_texture);
SDL_free(ctx);
}
+
+struct GamepadButton
+{
+ SDL_Renderer *renderer;
+ SDL_Texture *background;
+ int background_width;
+ int background_height;
+
+ SDL_FRect area;
+
+ char *label;
+ int label_width;
+ int label_height;
+
+ SDL_bool highlight;
+};
+
+GamepadButton *CreateGamepadButton(SDL_Renderer *renderer, const char *label)
+{
+ GamepadButton *ctx = SDL_calloc(1, sizeof(*ctx));
+ if (ctx) {
+ ctx->renderer = renderer;
+
+ ctx->background = CreateTexture(renderer, gamepad_button_background_bmp, gamepad_button_background_bmp_len);
+ SDL_QueryTexture(ctx->background, NULL, NULL, &ctx->background_width, &ctx->background_height);
+
+ ctx->label = SDL_strdup(label);
+ ctx->label_width = (int)(FONT_CHARACTER_SIZE * SDL_strlen(label));
+ ctx->label_height = FONT_CHARACTER_SIZE;
+ }
+ return ctx;
+}
+
+void SetGamepadButtonArea(GamepadButton *ctx, int x, int y, int w, int h)
+{
+ if (!ctx) {
+ return;
+ }
+
+ ctx->area.x = (float)x;
+ ctx->area.y = (float)y;
+ ctx->area.w = (float)w;
+ ctx->area.h = (float)h;
+}
+
+void SetGamepadButtonHighlight(GamepadButton *ctx, SDL_bool highlight)
+{
+ if (!ctx) {
+ return;
+ }
+
+ ctx->highlight = highlight;
+}
+
+int GetGamepadButtonLabelWidth(GamepadButton *ctx)
+{
+ if (!ctx) {
+ return 0;
+ }
+
+ return ctx->label_width;
+}
+
+int GetGamepadButtonLabelHeight(GamepadButton *ctx)
+{
+ if (!ctx) {
+ return 0;
+ }
+
+ return ctx->label_height;
+}
+
+SDL_bool GamepadButtonContains(GamepadButton *ctx, float x, float y)
+{
+ SDL_FPoint point;
+
+ if (!ctx) {
+ return SDL_FALSE;
+ }
+
+ point.x = x;
+ point.y = y;
+ return SDL_PointInRectFloat(&point, &ctx->area);
+}
+
+void RenderGamepadButton(GamepadButton *ctx)
+{
+ SDL_FRect src, dst;
+ float one_third_src_width;
+ float one_third_src_height;
+
+ if (!ctx) {
+ return;
+ }
+
+ one_third_src_width = (float)ctx->background_width / 3;
+ one_third_src_height = (float)ctx->background_height / 3;
+
+ if (ctx->highlight) {
+ SDL_SetTextureColorMod(ctx->background, 10, 255, 21);
+ } else {
+ SDL_SetTextureColorMod(ctx->background, 255, 255, 255);
+ }
+
+ /* Top left */
+ src.x = 0.0f;
+ src.y = 0.0f;
+ src.w = one_third_src_width;
+ src.h = one_third_src_height;
+ dst.x = ctx->area.x;
+ dst.y = ctx->area.y;
+ dst.w = src.w;
+ dst.h = src.h;
+ SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
+
+ /* Bottom left */
+ src.y = (float)ctx->background_height - src.h;
+ dst.y = ctx->area.y + ctx->area.h - dst.h;
+ SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
+
+ /* Bottom right */
+ src.x = (float)ctx->background_width - src.w;
+ dst.x = ctx->area.x + ctx->area.w - dst.w;
+ SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
+
+ /* Top right */
+ src.y = 0.0f;
+ dst.y = ctx->area.y;
+ SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
+
+ /* Left */
+ src.x = 0.0f;
+ src.y = one_third_src_height;
+ dst.x = ctx->area.x;
+ dst.y = ctx->area.y + one_third_src_height;
+ dst.w = one_third_src_width;
+ dst.h = (float)ctx->area.h - 2 * one_third_src_height;
+ SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
+
+ /* Right */
+ src.x = (float)ctx->background_width - one_third_src_width;
+ dst.x = ctx->area.x + ctx->area.w - one_third_src_width;
+ SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
+
+ /* Top */
+ src.x = one_third_src_width;
+ src.y = 0.0f;
+ dst.x = ctx->area.x + one_third_src_width;
+ dst.y = ctx->area.y;
+ dst.w = ctx->area.w - 2 * one_third_src_width;
+ dst.h = one_third_src_height;
+ SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
+
+ /* Bottom */
+ src.y = (float)ctx->background_height - src.h;
+ dst.y = ctx->area.y + ctx->area.h - one_third_src_height;
+ SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
+
+ /* Center */
+ src.x = one_third_src_width;
+ src.y = one_third_src_height;
+ dst.x = ctx->area.x + one_third_src_width;
+ dst.y = ctx->area.y + one_third_src_height;
+ dst.w = ctx->area.w - 2 * one_third_src_width;
+ dst.h = (float)ctx->area.h - 2 * one_third_src_height;
+ SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
+
+ /* Label */
+ dst.x = ctx->area.x + ctx->area.w / 2 - ctx->label_width / 2;
+ dst.y = ctx->area.y + ctx->area.h / 2 - ctx->label_height / 2;
+ SDLTest_DrawString(ctx->renderer, dst.x, dst.y, ctx->label);
+}
+
+void DestroyGamepadButton(GamepadButton *ctx)
+{
+ if (!ctx) {
+ return;
+ }
+
+ SDL_DestroyTexture(ctx->background);
+ SDL_free(ctx->label);
+ SDL_free(ctx);
+}
diff --git a/test/gamepadutils.h b/test/gamepadutils.h
index 9e56d63497e3..fbfc77cbf81f 100644
--- a/test/gamepadutils.h
+++ b/test/gamepadutils.h
@@ -54,3 +54,15 @@ extern void SetJoystickDisplayArea(JoystickDisplay *ctx, int x, int y, int w, in
extern void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick);
extern void DestroyJoystickDisplay(JoystickDisplay *ctx);
+/* Simple buttons */
+
+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 SetGamepadButtonHighlight(GamepadButton *ctx, SDL_bool highlight);
+extern int GetGamepadButtonLabelWidth(GamepadButton *ctx);
+extern int GetGamepadButtonLabelHeight(GamepadButton *ctx);
+extern SDL_bool GamepadButtonContains(GamepadButton *ctx, float x, float y);
+extern void RenderGamepadButton(GamepadButton *ctx);
+extern void DestroyGamepadButton(GamepadButton *ctx);
diff --git a/test/testgamepad.c b/test/testgamepad.c
index c04ae757be35..b74546866718 100644
--- a/test/testgamepad.c
+++ b/test/testgamepad.c
@@ -27,6 +27,8 @@
#define TITLE_HEIGHT 48
#define PANEL_SPACING 25
#define PANEL_WIDTH 250
+#define BUTTON_MARGIN 8
+#define BUTTON_PADDING 12
#define GAMEPAD_WIDTH 512
#define GAMEPAD_HEIGHT 480
@@ -49,6 +51,8 @@ static SDL_Renderer *screen = NULL;
static GamepadImage *image = NULL;
static GamepadDisplay *gamepad_elements = NULL;
static JoystickDisplay *joystick_elements = NULL;
+static GamepadButton *copy_button = NULL;
+static SDL_bool in_copy_button = SDL_FALSE;
static SDL_bool retval = SDL_FALSE;
static SDL_bool done = SDL_FALSE;
static SDL_bool set_LED = SDL_FALSE;
@@ -594,6 +598,33 @@ static void DrawGamepadInfo(SDL_Renderer *renderer)
}
}
+static void CopyMappingToClipboard()
+{
+ char *mapping = SDL_GetGamepadMapping(gamepad);
+ if (mapping) {
+ const char *name = SDL_GetGamepadName(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 loop(void *arg)
{
SDL_Event event;
@@ -687,18 +718,24 @@ static void loop(void *arg)
if (virtual_joystick) {
VirtualGamepadMouseDown(event.button.x, event.button.y);
}
+ SetGamepadButtonHighlight(copy_button, GamepadButtonContains(copy_button, event.button.x, event.button.y));
break;
case SDL_EVENT_MOUSE_BUTTON_UP:
if (virtual_joystick) {
VirtualGamepadMouseUp(event.button.x, event.button.y);
}
+ if (GamepadButtonContains(copy_button, event.button.x, event.button.y)) {
+ CopyMappingToClipboard();
+ }
+ SetGamepadButtonHighlight(copy_button, SDL_FALSE);
break;
case SDL_EVENT_MOUSE_MOTION:
if (virtual_joystick) {
VirtualGamepadMouseMotion(event.motion.x, event.motion.y);
}
+ SetGamepadButtonHighlight(copy_button, event.motion.state && GamepadButtonContains(copy_button, event.motion.x, event.motion.y));
break;
case SDL_EVENT_KEY_DOWN:
@@ -743,6 +780,8 @@ static void loop(void *arg)
RenderGamepadDisplay(gamepad_elements, gamepad);
RenderJoystickDisplay(joystick_elements, SDL_GetGamepadJoystick(gamepad));
+ RenderGamepadButton(copy_button);
+
DrawGamepadInfo(screen);
/* Update LED based on left thumbstick position */
@@ -811,6 +850,7 @@ int main(int argc, char *argv[])
int i;
float content_scale;
int screen_width, screen_height;
+ int button_width, button_height;
int gamepad_index = -1;
SDLTest_CommonState *state;
@@ -924,6 +964,11 @@ int main(int argc, char *argv[])
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);
+
/* Process the initial gamepad list */
loop(NULL);
@@ -953,6 +998,7 @@ int main(int argc, char *argv[])
DestroyGamepadImage(image);
DestroyGamepadDisplay(gamepad_elements);
DestroyJoystickDisplay(joystick_elements);
+ DestroyGamepadButton(copy_button);
SDL_DestroyRenderer(screen);
SDL_DestroyWindow(window);
SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD);