From 4feb2f4b1aed64594eaa681e76b8cae4a375bdf1 Mon Sep 17 00:00:00 2001 From: Sam Lantinga 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 61a4f24fe..004005f8e 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 9e56d6349..fbfc77cbf 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 c04ae757b..b74546866 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);