From 6aaedb813fa11ba0679c3051bc2eb28646b9506c Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 30 Aug 2025 16:53:58 -0700 Subject: Update to SDL3 --- src/contrib/SDL-3.2.20/test/gamepadutils.c | 2972 ++++++++++++++++++++++++++++ 1 file changed, 2972 insertions(+) create mode 100644 src/contrib/SDL-3.2.20/test/gamepadutils.c (limited to 'src/contrib/SDL-3.2.20/test/gamepadutils.c') diff --git a/src/contrib/SDL-3.2.20/test/gamepadutils.c b/src/contrib/SDL-3.2.20/test/gamepadutils.c new file mode 100644 index 0000000..910887f --- /dev/null +++ b/src/contrib/SDL-3.2.20/test/gamepadutils.c @@ -0,0 +1,2972 @@ +/* + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely. +*/ +#include +#include + +#include "gamepadutils.h" +#include "gamepad_front.h" +#include "gamepad_back.h" +#include "gamepad_face_abxy.h" +#include "gamepad_face_bayx.h" +#include "gamepad_face_sony.h" +#include "gamepad_battery.h" +#include "gamepad_battery_wired.h" +#include "gamepad_touchpad.h" +#include "gamepad_button.h" +#include "gamepad_button_small.h" +#include "gamepad_axis.h" +#include "gamepad_axis_arrow.h" +#include "gamepad_button_background.h" +#include "gamepad_wired.h" +#include "gamepad_wireless.h" + + +/* This is indexed by gamepad element */ +static const struct +{ + int x; + int y; +} button_positions[] = { + { 413, 190 }, /* SDL_GAMEPAD_BUTTON_SOUTH */ + { 456, 156 }, /* SDL_GAMEPAD_BUTTON_EAST */ + { 372, 159 }, /* SDL_GAMEPAD_BUTTON_WEST */ + { 415, 127 }, /* SDL_GAMEPAD_BUTTON_NORTH */ + { 199, 157 }, /* SDL_GAMEPAD_BUTTON_BACK */ + { 257, 153 }, /* SDL_GAMEPAD_BUTTON_GUIDE */ + { 314, 157 }, /* SDL_GAMEPAD_BUTTON_START */ + { 98, 177 }, /* SDL_GAMEPAD_BUTTON_LEFT_STICK */ + { 331, 254 }, /* SDL_GAMEPAD_BUTTON_RIGHT_STICK */ + { 102, 65 }, /* SDL_GAMEPAD_BUTTON_LEFT_SHOULDER */ + { 421, 61 }, /* SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER */ + { 179, 213 }, /* SDL_GAMEPAD_BUTTON_DPAD_UP */ + { 179, 274 }, /* SDL_GAMEPAD_BUTTON_DPAD_DOWN */ + { 141, 242 }, /* SDL_GAMEPAD_BUTTON_DPAD_LEFT */ + { 211, 242 }, /* SDL_GAMEPAD_BUTTON_DPAD_RIGHT */ + { 257, 199 }, /* SDL_GAMEPAD_BUTTON_MISC1 */ + { 157, 160 }, /* SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 */ + { 355, 160 }, /* SDL_GAMEPAD_BUTTON_LEFT_PADDLE1 */ + { 157, 200 }, /* SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2 */ + { 355, 200 }, /* SDL_GAMEPAD_BUTTON_LEFT_PADDLE2 */ +}; + +/* This is indexed by gamepad element */ +static const struct +{ + int x; + int y; + double angle; +} axis_positions[] = { + { 99, 178, 270.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE */ + { 99, 178, 90.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE */ + { 99, 178, 0.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE */ + { 99, 178, 180.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE */ + { 331, 256, 270.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE */ + { 331, 256, 90.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE */ + { 331, 256, 0.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE */ + { 331, 256, 180.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE */ + { 116, 5, 180.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER */ + { 400, 5, 180.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER */ +}; + +static SDL_FRect touchpad_area = { + 148.0f, 20.0f, 216.0f, 118.0f +}; + +typedef struct +{ + bool down; + float x; + float y; + float pressure; +} GamepadTouchpadFinger; + +struct GamepadImage +{ + SDL_Renderer *renderer; + SDL_Texture *front_texture; + SDL_Texture *back_texture; + SDL_Texture *face_abxy_texture; + SDL_Texture *face_bayx_texture; + SDL_Texture *face_sony_texture; + SDL_Texture *connection_texture[2]; + SDL_Texture *battery_texture[2]; + SDL_Texture *touchpad_texture; + SDL_Texture *button_texture; + SDL_Texture *axis_texture; + float gamepad_width; + float gamepad_height; + float face_width; + float face_height; + float connection_width; + float connection_height; + float battery_width; + float battery_height; + float touchpad_width; + float touchpad_height; + float button_width; + float button_height; + float axis_width; + float axis_height; + + float x; + float y; + bool showing_front; + bool showing_touchpad; + SDL_GamepadType type; + ControllerDisplayMode display_mode; + + bool elements[SDL_GAMEPAD_ELEMENT_MAX]; + + SDL_JoystickConnectionState connection_state; + SDL_PowerState battery_state; + int battery_percent; + + int num_fingers; + GamepadTouchpadFinger *fingers; +}; + +static SDL_Texture *CreateTexture(SDL_Renderer *renderer, unsigned char *data, unsigned int len) +{ + SDL_Texture *texture = NULL; + SDL_Surface *surface; + SDL_IOStream *src = SDL_IOFromConstMem(data, len); + if (src) { + surface = SDL_LoadBMP_IO(src, true); + if (surface) { + texture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_DestroySurface(surface); + } + } + return texture; +} + +GamepadImage *CreateGamepadImage(SDL_Renderer *renderer) +{ + GamepadImage *ctx = SDL_calloc(1, sizeof(*ctx)); + if (ctx) { + ctx->renderer = renderer; + ctx->front_texture = CreateTexture(renderer, gamepad_front_bmp, gamepad_front_bmp_len); + ctx->back_texture = CreateTexture(renderer, gamepad_back_bmp, gamepad_back_bmp_len); + SDL_GetTextureSize(ctx->front_texture, &ctx->gamepad_width, &ctx->gamepad_height); + + ctx->face_abxy_texture = CreateTexture(renderer, gamepad_face_abxy_bmp, gamepad_face_abxy_bmp_len); + ctx->face_bayx_texture = CreateTexture(renderer, gamepad_face_bayx_bmp, gamepad_face_bayx_bmp_len); + ctx->face_sony_texture = CreateTexture(renderer, gamepad_face_sony_bmp, gamepad_face_sony_bmp_len); + SDL_GetTextureSize(ctx->face_abxy_texture, &ctx->face_width, &ctx->face_height); + + ctx->connection_texture[0] = CreateTexture(renderer, gamepad_wired_bmp, gamepad_wired_bmp_len); + ctx->connection_texture[1] = CreateTexture(renderer, gamepad_wireless_bmp, gamepad_wireless_bmp_len); + SDL_GetTextureSize(ctx->connection_texture[0], &ctx->connection_width, &ctx->connection_height); + + ctx->battery_texture[0] = CreateTexture(renderer, gamepad_battery_bmp, gamepad_battery_bmp_len); + ctx->battery_texture[1] = CreateTexture(renderer, gamepad_battery_wired_bmp, gamepad_battery_wired_bmp_len); + SDL_GetTextureSize(ctx->battery_texture[0], &ctx->battery_width, &ctx->battery_height); + + ctx->touchpad_texture = CreateTexture(renderer, gamepad_touchpad_bmp, gamepad_touchpad_bmp_len); + SDL_GetTextureSize(ctx->touchpad_texture, &ctx->touchpad_width, &ctx->touchpad_height); + + ctx->button_texture = CreateTexture(renderer, gamepad_button_bmp, gamepad_button_bmp_len); + SDL_GetTextureSize(ctx->button_texture, &ctx->button_width, &ctx->button_height); + SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); + + ctx->axis_texture = CreateTexture(renderer, gamepad_axis_bmp, gamepad_axis_bmp_len); + SDL_GetTextureSize(ctx->axis_texture, &ctx->axis_width, &ctx->axis_height); + SDL_SetTextureColorMod(ctx->axis_texture, 10, 255, 21); + + ctx->showing_front = true; + } + return ctx; +} + +void SetGamepadImagePosition(GamepadImage *ctx, float x, float y) +{ + if (!ctx) { + return; + } + + ctx->x = x; + ctx->y = y; +} + +void GetGamepadImageArea(GamepadImage *ctx, SDL_FRect *area) +{ + if (!ctx) { + SDL_zerop(area); + return; + } + + area->x = ctx->x; + area->y = ctx->y; + area->w = ctx->gamepad_width; + area->h = ctx->gamepad_height; + if (ctx->showing_touchpad) { + area->h += ctx->touchpad_height; + } +} + +void GetGamepadTouchpadArea(GamepadImage *ctx, SDL_FRect *area) +{ + if (!ctx) { + SDL_zerop(area); + return; + } + + area->x = ctx->x + (ctx->gamepad_width - ctx->touchpad_width) / 2 + touchpad_area.x; + area->y = ctx->y + ctx->gamepad_height + touchpad_area.y; + area->w = touchpad_area.w; + area->h = touchpad_area.h; +} + +void SetGamepadImageShowingFront(GamepadImage *ctx, bool showing_front) +{ + if (!ctx) { + return; + } + + ctx->showing_front = showing_front; +} + +SDL_GamepadType GetGamepadImageType(GamepadImage *ctx) +{ + if (!ctx) { + return SDL_GAMEPAD_TYPE_UNKNOWN; + } + + return ctx->type; +} + +void SetGamepadImageDisplayMode(GamepadImage *ctx, ControllerDisplayMode display_mode) +{ + if (!ctx) { + return; + } + + ctx->display_mode = display_mode; +} + +float GetGamepadImageButtonWidth(GamepadImage *ctx) +{ + if (!ctx) { + return 0; + } + + return ctx->button_width; +} + +float GetGamepadImageButtonHeight(GamepadImage *ctx) +{ + if (!ctx) { + return 0; + } + + return ctx->button_height; +} + +float GetGamepadImageAxisWidth(GamepadImage *ctx) +{ + if (!ctx) { + return 0; + } + + return ctx->axis_width; +} + +float GetGamepadImageAxisHeight(GamepadImage *ctx) +{ + if (!ctx) { + return 0; + } + + return ctx->axis_height; +} + +int GetGamepadImageElementAt(GamepadImage *ctx, float x, float y) +{ + SDL_FPoint point; + int i; + + if (!ctx) { + return SDL_GAMEPAD_ELEMENT_INVALID; + } + + point.x = x; + point.y = y; + + if (ctx->showing_front) { + for (i = 0; i < SDL_arraysize(axis_positions); ++i) { + const int element = SDL_GAMEPAD_BUTTON_COUNT + i; + SDL_FRect rect; + + if (element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER || + element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER) { + rect.w = ctx->axis_width; + rect.h = ctx->axis_height; + rect.x = ctx->x + axis_positions[i].x - rect.w / 2; + rect.y = ctx->y + axis_positions[i].y - rect.h / 2; + if (SDL_PointInRectFloat(&point, &rect)) { + if (element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER) { + return SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER; + } else { + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER; + } + } + } else if (element == SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE) { + rect.w = ctx->button_width * 2.0f; + rect.h = ctx->button_height * 2.0f; + rect.x = ctx->x + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].x - rect.w / 2; + rect.y = ctx->y + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].y - rect.h / 2; + if (SDL_PointInRectFloat(&point, &rect)) { + float delta_x, delta_y; + float delta_squared; + float thumbstick_radius = ctx->button_width * 0.1f; + + delta_x = (x - (ctx->x + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].x)); + delta_y = (y - (ctx->y + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].y)); + delta_squared = (delta_x * delta_x) + (delta_y * delta_y); + if (delta_squared > (thumbstick_radius * thumbstick_radius)) { + float angle = SDL_atan2f(delta_y, delta_x) + SDL_PI_F; + if (angle < SDL_PI_F * 0.25f) { + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE; + } else if (angle < SDL_PI_F * 0.75f) { + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE; + } else if (angle < SDL_PI_F * 1.25f) { + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE; + } else if (angle < SDL_PI_F * 1.75f) { + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE; + } else { + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE; + } + } + } + } else if (element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE) { + rect.w = ctx->button_width * 2.0f; + rect.h = ctx->button_height * 2.0f; + rect.x = ctx->x + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].x - rect.w / 2; + rect.y = ctx->y + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].y - rect.h / 2; + if (SDL_PointInRectFloat(&point, &rect)) { + float delta_x, delta_y; + float delta_squared; + float thumbstick_radius = ctx->button_width * 0.1f; + + delta_x = (x - (ctx->x + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].x)); + delta_y = (y - (ctx->y + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].y)); + delta_squared = (delta_x * delta_x) + (delta_y * delta_y); + if (delta_squared > (thumbstick_radius * thumbstick_radius)) { + float angle = SDL_atan2f(delta_y, delta_x) + SDL_PI_F; + if (angle < SDL_PI_F * 0.25f) { + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE; + } else if (angle < SDL_PI_F * 0.75f) { + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE; + } else if (angle < SDL_PI_F * 1.25f) { + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE; + } else if (angle < SDL_PI_F * 1.75f) { + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE; + } else { + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE; + } + } + } + } + } + } + + for (i = 0; i < SDL_arraysize(button_positions); ++i) { + bool on_front = true; + + if (i >= SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 && i <= SDL_GAMEPAD_BUTTON_LEFT_PADDLE2) { + on_front = false; + } + if (on_front == ctx->showing_front) { + SDL_FRect rect; + rect.x = ctx->x + button_positions[i].x - ctx->button_width / 2; + rect.y = ctx->y + button_positions[i].y - ctx->button_height / 2; + rect.w = ctx->button_width; + rect.h = ctx->button_height; + if (SDL_PointInRectFloat(&point, &rect)) { + return (SDL_GamepadButton)i; + } + } + } + return SDL_GAMEPAD_ELEMENT_INVALID; +} + +void ClearGamepadImage(GamepadImage *ctx) +{ + if (!ctx) { + return; + } + + SDL_zeroa(ctx->elements); +} + +void SetGamepadImageElement(GamepadImage *ctx, int element, bool active) +{ + if (!ctx) { + return; + } + + ctx->elements[element] = active; +} + +void UpdateGamepadImageFromGamepad(GamepadImage *ctx, SDL_Gamepad *gamepad) +{ + int i; + + if (!ctx) { + return; + } + + ctx->type = SDL_GetGamepadType(gamepad); + char *mapping = SDL_GetGamepadMapping(gamepad); + if (mapping) { + if (SDL_strstr(mapping, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS")) { + /* Just for display purposes */ + ctx->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO; + } + SDL_free(mapping); + } + + for (i = 0; i < SDL_GAMEPAD_BUTTON_TOUCHPAD; ++i) { + const SDL_GamepadButton button = (SDL_GamepadButton)i; + + SetGamepadImageElement(ctx, button, SDL_GetGamepadButton(gamepad, button)); + } + + for (i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) { + const SDL_GamepadAxis axis = (SDL_GamepadAxis)i; + const Sint16 deadzone = 8000; /* !!! FIXME: real deadzone */ + const Sint16 value = SDL_GetGamepadAxis(gamepad, axis); + switch (i) { + case SDL_GAMEPAD_AXIS_LEFTX: + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE, (value < -deadzone)); + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE, (value > deadzone)); + break; + case SDL_GAMEPAD_AXIS_RIGHTX: + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE, (value < -deadzone)); + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE, (value > deadzone)); + break; + case SDL_GAMEPAD_AXIS_LEFTY: + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE, (value < -deadzone)); + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE, (value > deadzone)); + break; + case SDL_GAMEPAD_AXIS_RIGHTY: + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE, (value < -deadzone)); + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE, (value > deadzone)); + break; + case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER, (value > deadzone)); + break; + case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER, (value > deadzone)); + break; + default: + break; + } + } + + ctx->connection_state = SDL_GetGamepadConnectionState(gamepad); + ctx->battery_state = SDL_GetGamepadPowerInfo(gamepad, &ctx->battery_percent); + + if (SDL_GetNumGamepadTouchpads(gamepad) > 0) { + int num_fingers = SDL_GetNumGamepadTouchpadFingers(gamepad, 0); + if (num_fingers != ctx->num_fingers) { + GamepadTouchpadFinger *fingers = (GamepadTouchpadFinger *)SDL_realloc(ctx->fingers, num_fingers * sizeof(*fingers)); + if (fingers) { + ctx->fingers = fingers; + ctx->num_fingers = num_fingers; + } else { + num_fingers = SDL_min(ctx->num_fingers, num_fingers); + } + } + for (i = 0; i < num_fingers; ++i) { + GamepadTouchpadFinger *finger = &ctx->fingers[i]; + + SDL_GetGamepadTouchpadFinger(gamepad, 0, i, &finger->down, &finger->x, &finger->y, &finger->pressure); + } + ctx->showing_touchpad = true; + } else { + if (ctx->fingers) { + SDL_free(ctx->fingers); + ctx->fingers = NULL; + ctx->num_fingers = 0; + } + ctx->showing_touchpad = false; + } +} + +void RenderGamepadImage(GamepadImage *ctx) +{ + SDL_FRect dst; + int i; + + if (!ctx) { + return; + } + + dst.x = ctx->x; + dst.y = ctx->y; + dst.w = ctx->gamepad_width; + dst.h = ctx->gamepad_height; + + if (ctx->showing_front) { + SDL_RenderTexture(ctx->renderer, ctx->front_texture, NULL, &dst); + } else { + SDL_RenderTexture(ctx->renderer, ctx->back_texture, NULL, &dst); + } + + for (i = 0; i < SDL_arraysize(button_positions); ++i) { + if (ctx->elements[i]) { + SDL_GamepadButton button_position = (SDL_GamepadButton)i; + bool on_front = true; + + if (i >= SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 && i <= SDL_GAMEPAD_BUTTON_LEFT_PADDLE2) { + on_front = false; + } + if (on_front == ctx->showing_front) { + dst.w = ctx->button_width; + dst.h = ctx->button_height; + dst.x = ctx->x + button_positions[button_position].x - dst.w / 2; + dst.y = ctx->y + button_positions[button_position].y - dst.h / 2; + SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); + } + } + } + + if (ctx->showing_front) { + dst.x = ctx->x + 363; + dst.y = ctx->y + 118; + dst.w = ctx->face_width; + dst.h = ctx->face_height; + + switch (SDL_GetGamepadButtonLabelForType(ctx->type, SDL_GAMEPAD_BUTTON_SOUTH)) { + case SDL_GAMEPAD_BUTTON_LABEL_A: + SDL_RenderTexture(ctx->renderer, ctx->face_abxy_texture, NULL, &dst); + break; + case SDL_GAMEPAD_BUTTON_LABEL_B: + SDL_RenderTexture(ctx->renderer, ctx->face_bayx_texture, NULL, &dst); + break; + case SDL_GAMEPAD_BUTTON_LABEL_CROSS: + SDL_RenderTexture(ctx->renderer, ctx->face_sony_texture, NULL, &dst); + break; + default: + break; + } + } + + if (ctx->showing_front) { + for (i = 0; i < SDL_arraysize(axis_positions); ++i) { + const int element = SDL_GAMEPAD_BUTTON_COUNT + i; + if (ctx->elements[element]) { + const double angle = axis_positions[i].angle; + dst.w = ctx->axis_width; + dst.h = ctx->axis_height; + dst.x = ctx->x + axis_positions[i].x - dst.w / 2; + dst.y = ctx->y + axis_positions[i].y - dst.h / 2; + SDL_RenderTextureRotated(ctx->renderer, ctx->axis_texture, NULL, &dst, angle, NULL, SDL_FLIP_NONE); + } + } + } + + if (ctx->display_mode == CONTROLLER_MODE_TESTING) { + dst.x = ctx->x + ctx->gamepad_width - ctx->battery_width - 4 - ctx->connection_width; + dst.y = ctx->y; + dst.w = ctx->connection_width; + dst.h = ctx->connection_height; + + switch (ctx->connection_state) { + case SDL_JOYSTICK_CONNECTION_WIRED: + SDL_RenderTexture(ctx->renderer, ctx->connection_texture[0], NULL, &dst); + break; + case SDL_JOYSTICK_CONNECTION_WIRELESS: + SDL_RenderTexture(ctx->renderer, ctx->connection_texture[1], NULL, &dst); + break; + default: + break; + } + } + + if (ctx->display_mode == CONTROLLER_MODE_TESTING && + ctx->battery_state != SDL_POWERSTATE_NO_BATTERY && + ctx->battery_state != SDL_POWERSTATE_UNKNOWN) { + Uint8 r, g, b, a; + SDL_FRect fill; + + dst.x = ctx->x + ctx->gamepad_width - ctx->battery_width; + dst.y = ctx->y; + dst.w = ctx->battery_width; + dst.h = ctx->battery_height; + + SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); + if (ctx->battery_percent > 40) { + SDL_SetRenderDrawColor(ctx->renderer, 0x00, 0xD4, 0x50, 0xFF); + } else if (ctx->battery_percent > 10) { + SDL_SetRenderDrawColor(ctx->renderer, 0xFF, 0xC7, 0x00, 0xFF); + } else { + SDL_SetRenderDrawColor(ctx->renderer, 0xC8, 0x1D, 0x13, 0xFF); + } + + fill = dst; + fill.x += 2; + fill.y += 2; + fill.h -= 4; + fill.w = 25.0f * (ctx->battery_percent / 100.0f); + SDL_RenderFillRect(ctx->renderer, &fill); + SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); + + if (ctx->battery_state == SDL_POWERSTATE_ON_BATTERY) { + SDL_RenderTexture(ctx->renderer, ctx->battery_texture[0], NULL, &dst); + } else { + SDL_RenderTexture(ctx->renderer, ctx->battery_texture[1], NULL, &dst); + } + } + + if (ctx->display_mode == CONTROLLER_MODE_TESTING && ctx->showing_touchpad) { + dst.x = ctx->x + (ctx->gamepad_width - ctx->touchpad_width) / 2; + dst.y = ctx->y + ctx->gamepad_height; + dst.w = ctx->touchpad_width; + dst.h = ctx->touchpad_height; + SDL_RenderTexture(ctx->renderer, ctx->touchpad_texture, NULL, &dst); + + for (i = 0; i < ctx->num_fingers; ++i) { + GamepadTouchpadFinger *finger = &ctx->fingers[i]; + + if (finger->down) { + dst.x = ctx->x + (ctx->gamepad_width - ctx->touchpad_width) / 2; + dst.x += touchpad_area.x + finger->x * touchpad_area.w; + dst.x -= ctx->button_width / 2; + dst.y = ctx->y + ctx->gamepad_height; + dst.y += touchpad_area.y + finger->y * touchpad_area.h; + dst.y -= ctx->button_height / 2; + dst.w = ctx->button_width; + dst.h = ctx->button_height; + SDL_SetTextureAlphaMod(ctx->button_texture, (Uint8)(finger->pressure * SDL_ALPHA_OPAQUE)); + SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); + SDL_SetTextureAlphaMod(ctx->button_texture, SDL_ALPHA_OPAQUE); + } + } + } +} + +void DestroyGamepadImage(GamepadImage *ctx) +{ + if (ctx) { + int i; + + SDL_DestroyTexture(ctx->front_texture); + SDL_DestroyTexture(ctx->back_texture); + SDL_DestroyTexture(ctx->face_abxy_texture); + SDL_DestroyTexture(ctx->face_bayx_texture); + SDL_DestroyTexture(ctx->face_sony_texture); + for (i = 0; i < SDL_arraysize(ctx->battery_texture); ++i) { + SDL_DestroyTexture(ctx->battery_texture[i]); + } + SDL_DestroyTexture(ctx->touchpad_texture); + SDL_DestroyTexture(ctx->button_texture); + SDL_DestroyTexture(ctx->axis_texture); + SDL_free(ctx); + } +} + + +static const char *gamepad_button_names[] = { + "South", + "East", + "West", + "North", + "Back", + "Guide", + "Start", + "Left Stick", + "Right Stick", + "Left Shoulder", + "Right Shoulder", + "DPAD Up", + "DPAD Down", + "DPAD Left", + "DPAD Right", + "Misc1", + "Right Paddle 1", + "Left Paddle 1", + "Right Paddle 2", + "Left Paddle 2", + "Touchpad", + "Misc2", + "Misc3", + "Misc4", + "Misc5", + "Misc6", +}; +SDL_COMPILE_TIME_ASSERT(gamepad_button_names, SDL_arraysize(gamepad_button_names) == SDL_GAMEPAD_BUTTON_COUNT); + +static const char *gamepad_axis_names[] = { + "LeftX", + "LeftY", + "RightX", + "RightY", + "Left Trigger", + "Right Trigger", +}; +SDL_COMPILE_TIME_ASSERT(gamepad_axis_names, SDL_arraysize(gamepad_axis_names) == SDL_GAMEPAD_AXIS_COUNT); + +struct GamepadDisplay +{ + SDL_Renderer *renderer; + SDL_Texture *button_texture; + SDL_Texture *arrow_texture; + float button_width; + float button_height; + float arrow_width; + float arrow_height; + + float accel_data[3]; + float gyro_data[3]; + Uint64 last_sensor_update; + + ControllerDisplayMode display_mode; + int element_highlighted; + bool element_pressed; + int element_selected; + + SDL_FRect area; +}; + +GamepadDisplay *CreateGamepadDisplay(SDL_Renderer *renderer) +{ + GamepadDisplay *ctx = SDL_calloc(1, sizeof(*ctx)); + if (ctx) { + ctx->renderer = renderer; + + ctx->button_texture = CreateTexture(renderer, gamepad_button_small_bmp, gamepad_button_small_bmp_len); + SDL_GetTextureSize(ctx->button_texture, &ctx->button_width, &ctx->button_height); + + ctx->arrow_texture = CreateTexture(renderer, gamepad_axis_arrow_bmp, gamepad_axis_arrow_bmp_len); + SDL_GetTextureSize(ctx->arrow_texture, &ctx->arrow_width, &ctx->arrow_height); + + ctx->element_highlighted = SDL_GAMEPAD_ELEMENT_INVALID; + ctx->element_selected = SDL_GAMEPAD_ELEMENT_INVALID; + } + return ctx; +} + +void SetGamepadDisplayDisplayMode(GamepadDisplay *ctx, ControllerDisplayMode display_mode) +{ + if (!ctx) { + return; + } + + ctx->display_mode = display_mode; +} + +void SetGamepadDisplayArea(GamepadDisplay *ctx, const SDL_FRect *area) +{ + if (!ctx) { + return; + } + + SDL_copyp(&ctx->area, area); +} + +static bool GetBindingString(const char *label, const char *mapping, char *text, size_t size) +{ + char *key; + char *value, *end; + size_t length; + bool found = false; + + *text = '\0'; + + if (!mapping) { + return false; + } + + key = SDL_strstr(mapping, label); + while (key && size > 1) { + if (found) { + *text++ = ','; + *text = '\0'; + --size; + } else { + found = true; + } + 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'; + text += length; + size -= length; + key = SDL_strstr(value, label); + } + return found; +} + +static bool GetButtonBindingString(SDL_GamepadButton button, const char *mapping, char *text, size_t size) +{ + char label[32]; + bool baxy_mapping = false; + + if (!mapping) { + return false; + } + + SDL_snprintf(label, sizeof(label), ",%s:", SDL_GetGamepadStringForButton(button)); + if (GetBindingString(label, mapping, text, size)) { + return true; + } + + /* Try the legacy button names */ + if (SDL_strstr(mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) { + baxy_mapping = true; + } + switch (button) { + case SDL_GAMEPAD_BUTTON_SOUTH: + if (baxy_mapping) { + return GetBindingString(",b:", mapping, text, size); + } else { + return GetBindingString(",a:", mapping, text, size); + } + case SDL_GAMEPAD_BUTTON_EAST: + if (baxy_mapping) { + return GetBindingString(",a:", mapping, text, size); + } else { + return GetBindingString(",b:", mapping, text, size); + } + case SDL_GAMEPAD_BUTTON_WEST: + if (baxy_mapping) { + return GetBindingString(",y:", mapping, text, size); + } else { + return GetBindingString(",x:", mapping, text, size); + } + case SDL_GAMEPAD_BUTTON_NORTH: + if (baxy_mapping) { + return GetBindingString(",x:", mapping, text, size); + } else { + return GetBindingString(",y:", mapping, text, size); + } + default: + return false; + } +} + +static bool GetAxisBindingString(SDL_GamepadAxis axis, int direction, const 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 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 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 (text[SDL_strlen(text) - 1] == '~') { + direction *= -1; + text[SDL_strlen(text) - 1] = '\0'; + } + if (direction > 0) { + *text = '+'; + } else { + *text = '-'; + } + } + } + } + return true; +} + +void SetGamepadDisplayHighlight(GamepadDisplay *ctx, int element, bool pressed) +{ + if (!ctx) { + return; + } + + ctx->element_highlighted = element; + ctx->element_pressed = pressed; +} + +void SetGamepadDisplaySelected(GamepadDisplay *ctx, int element) +{ + if (!ctx) { + return; + } + + ctx->element_selected = element; +} + +int GetGamepadDisplayElementAt(GamepadDisplay *ctx, SDL_Gamepad *gamepad, float x, float y) +{ + int i; + const float margin = 8.0f; + const float center = ctx->area.w / 2.0f; + const float arrow_extent = 48.0f; + SDL_FPoint point; + SDL_FRect rect; + + if (!ctx) { + return SDL_GAMEPAD_ELEMENT_INVALID; + } + + point.x = x; + point.y = y; + + rect.x = ctx->area.x + margin; + rect.y = ctx->area.y + margin + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + rect.w = ctx->area.w - (margin * 2); + rect.h = ctx->button_height; + + for (i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) { + SDL_GamepadButton button = (SDL_GamepadButton)i; + + if (ctx->display_mode == CONTROLLER_MODE_TESTING && + !SDL_GamepadHasButton(gamepad, button)) { + continue; + } + + + if (SDL_PointInRectFloat(&point, &rect)) { + return i; + } + + rect.y += ctx->button_height + 2.0f; + } + + for (i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) { + SDL_GamepadAxis axis = (SDL_GamepadAxis)i; + SDL_FRect area; + + if (ctx->display_mode == CONTROLLER_MODE_TESTING && + !SDL_GamepadHasAxis(gamepad, axis)) { + continue; + } + + area.x = rect.x + center + 2.0f; + area.y = rect.y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + area.w = ctx->arrow_width + arrow_extent; + area.h = ctx->button_height; + + if (SDL_PointInRectFloat(&point, &area)) { + switch (axis) { + case SDL_GAMEPAD_AXIS_LEFTX: + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE; + case SDL_GAMEPAD_AXIS_LEFTY: + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE; + case SDL_GAMEPAD_AXIS_RIGHTX: + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE; + case SDL_GAMEPAD_AXIS_RIGHTY: + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE; + default: + break; + } + } + + area.x += area.w; + + if (SDL_PointInRectFloat(&point, &area)) { + switch (axis) { + case SDL_GAMEPAD_AXIS_LEFTX: + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE; + case SDL_GAMEPAD_AXIS_LEFTY: + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE; + case SDL_GAMEPAD_AXIS_RIGHTX: + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE; + case SDL_GAMEPAD_AXIS_RIGHTY: + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE; + case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: + return SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER; + case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER; + default: + break; + } + } + + rect.y += ctx->button_height + 2.0f; + } + return SDL_GAMEPAD_ELEMENT_INVALID; +} + +static void RenderGamepadElementHighlight(GamepadDisplay *ctx, int element, const SDL_FRect *area) +{ + if (element == ctx->element_highlighted || element == ctx->element_selected) { + Uint8 r, g, b, a; + + SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); + + if (element == ctx->element_highlighted) { + if (ctx->element_pressed) { + SDL_SetRenderDrawColor(ctx->renderer, PRESSED_COLOR); + } else { + SDL_SetRenderDrawColor(ctx->renderer, HIGHLIGHT_COLOR); + } + } else { + SDL_SetRenderDrawColor(ctx->renderer, SELECTED_COLOR); + } + SDL_RenderFillRect(ctx->renderer, area); + + SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); + } +} + +void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad) +{ + float x, y; + int i; + 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, highlight; + Uint8 r, g, b, a; + char *mapping; + bool has_accel; + bool has_gyro; + + 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_COUNT; ++i) { + SDL_GamepadButton button = (SDL_GamepadButton)i; + + if (ctx->display_mode == CONTROLLER_MODE_TESTING && + !SDL_GamepadHasButton(gamepad, button)) { + continue; + } + + highlight.x = x; + highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + highlight.w = ctx->area.w - (margin * 2); + highlight.h = ctx->button_height; + RenderGamepadElementHighlight(ctx, i, &highlight); + + SDL_snprintf(text, sizeof(text), "%s:", gamepad_button_names[i]); + SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); + + if (SDL_GetGamepadButton(gamepad, button)) { + 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 = ctx->button_width; + dst.h = ctx->button_height; + SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); + + 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; + } + + for (i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) { + SDL_GamepadAxis axis = (SDL_GamepadAxis)i; + bool has_negative = (axis != SDL_GAMEPAD_AXIS_LEFT_TRIGGER && axis != SDL_GAMEPAD_AXIS_RIGHT_TRIGGER); + 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); + + highlight.x = x + center + 2.0f; + highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + highlight.w = ctx->arrow_width + arrow_extent; + highlight.h = ctx->button_height; + + switch (axis) { + case SDL_GAMEPAD_AXIS_LEFTX: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE, &highlight); + break; + case SDL_GAMEPAD_AXIS_LEFTY: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE, &highlight); + break; + case SDL_GAMEPAD_AXIS_RIGHTX: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE, &highlight); + break; + case SDL_GAMEPAD_AXIS_RIGHTY: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE, &highlight); + break; + default: + break; + } + + highlight.x += highlight.w; + + switch (axis) { + case SDL_GAMEPAD_AXIS_LEFTX: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE, &highlight); + break; + case SDL_GAMEPAD_AXIS_LEFTY: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE, &highlight); + break; + case SDL_GAMEPAD_AXIS_RIGHTX: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE, &highlight); + break; + case SDL_GAMEPAD_AXIS_RIGHTY: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE, &highlight); + break; + case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER, &highlight); + break; + case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER, &highlight); + break; + default: + break; + } + + dst.x = x + center + 2.0f; + dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->arrow_height / 2; + dst.w = ctx->arrow_width; + dst.h = 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 += 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 = 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); + } + + 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 - (FONT_CHARACTER_SIZE * SDL_strlen(binding)) / 2; + SDLTest_DrawString(ctx->renderer, text_x, y, binding); + } + } + + 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 (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 - (FONT_CHARACTER_SIZE * SDL_strlen(binding)) / 2; + SDLTest_DrawString(ctx->renderer, text_x, y, binding); + } + } + + 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.0f; + } + + 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) { + bool down; + float finger_x, finger_y, finger_pressure; + + if (!SDL_GetGamepadTouchpadFinger(gamepad, 0, i, &down, &finger_x, &finger_y, &finger_pressure)) { + continue; + } + + SDL_snprintf(text, sizeof(text), "Touch finger %d:", i); + SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); + + if (down) { + 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 = ctx->button_width; + dst.h = ctx->button_height; + SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); + + if (down) { + 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; + } + } + + 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)); + } + 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); + + 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); + + y += ctx->button_height + 2.0f; + } + } + } + SDL_free(mapping); +} + +void DestroyGamepadDisplay(GamepadDisplay *ctx) +{ + if (!ctx) { + return; + } + + SDL_DestroyTexture(ctx->button_texture); + SDL_DestroyTexture(ctx->arrow_texture); + SDL_free(ctx); +} + +struct GamepadTypeDisplay +{ + SDL_Renderer *renderer; + + int type_highlighted; + bool type_pressed; + int type_selected; + SDL_GamepadType real_type; + + SDL_FRect area; +}; + +GamepadTypeDisplay *CreateGamepadTypeDisplay(SDL_Renderer *renderer) +{ + GamepadTypeDisplay *ctx = SDL_calloc(1, sizeof(*ctx)); + if (ctx) { + ctx->renderer = renderer; + + ctx->type_highlighted = SDL_GAMEPAD_TYPE_UNSELECTED; + ctx->type_selected = SDL_GAMEPAD_TYPE_UNSELECTED; + ctx->real_type = SDL_GAMEPAD_TYPE_UNKNOWN; + } + return ctx; +} + +void SetGamepadTypeDisplayArea(GamepadTypeDisplay *ctx, const SDL_FRect *area) +{ + if (!ctx) { + return; + } + + SDL_copyp(&ctx->area, area); +} + +void SetGamepadTypeDisplayHighlight(GamepadTypeDisplay *ctx, int type, bool pressed) +{ + if (!ctx) { + return; + } + + ctx->type_highlighted = type; + ctx->type_pressed = pressed; +} + +void SetGamepadTypeDisplaySelected(GamepadTypeDisplay *ctx, int type) +{ + if (!ctx) { + return; + } + + ctx->type_selected = type; +} + +void SetGamepadTypeDisplayRealType(GamepadTypeDisplay *ctx, SDL_GamepadType type) +{ + if (!ctx) { + return; + } + + ctx->real_type = type; +} + +int GetGamepadTypeDisplayAt(GamepadTypeDisplay *ctx, float x, float y) +{ + int i; + const float margin = 8.0f; + const float line_height = 16.0f; + SDL_FRect highlight; + SDL_FPoint point; + + if (!ctx) { + return SDL_GAMEPAD_TYPE_UNSELECTED; + } + + point.x = x; + point.y = y; + + x = ctx->area.x + margin; + y = ctx->area.y + margin; + + for (i = SDL_GAMEPAD_TYPE_UNKNOWN; i < SDL_GAMEPAD_TYPE_COUNT; ++i) { + highlight.x = x; + highlight.y = y; + highlight.w = ctx->area.w - (margin * 2); + highlight.h = line_height; + + if (SDL_PointInRectFloat(&point, &highlight)) { + return i; + } + + y += line_height; + } + return SDL_GAMEPAD_TYPE_UNSELECTED; +} + +static void RenderGamepadTypeHighlight(GamepadTypeDisplay *ctx, int type, const SDL_FRect *area) +{ + if (type == ctx->type_highlighted || type == ctx->type_selected) { + Uint8 r, g, b, a; + + SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); + + if (type == ctx->type_highlighted) { + if (ctx->type_pressed) { + SDL_SetRenderDrawColor(ctx->renderer, PRESSED_COLOR); + } else { + SDL_SetRenderDrawColor(ctx->renderer, HIGHLIGHT_COLOR); + } + } else { + SDL_SetRenderDrawColor(ctx->renderer, SELECTED_COLOR); + } + SDL_RenderFillRect(ctx->renderer, area); + + SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); + } +} + +void RenderGamepadTypeDisplay(GamepadTypeDisplay *ctx) +{ + float x, y; + int i; + char text[128]; + const float margin = 8.0f; + const float line_height = 16.0f; + SDL_FPoint dst; + SDL_FRect highlight; + + if (!ctx) { + return; + } + + x = ctx->area.x + margin; + y = ctx->area.y + margin; + + for (i = SDL_GAMEPAD_TYPE_UNKNOWN; i < SDL_GAMEPAD_TYPE_COUNT; ++i) { + highlight.x = x; + highlight.y = y; + highlight.w = ctx->area.w - (margin * 2); + highlight.h = line_height; + RenderGamepadTypeHighlight(ctx, i, &highlight); + + if (i == SDL_GAMEPAD_TYPE_UNKNOWN) { + if (ctx->real_type == SDL_GAMEPAD_TYPE_UNKNOWN || + ctx->real_type == SDL_GAMEPAD_TYPE_STANDARD) { + SDL_strlcpy(text, "Auto (Standard)", sizeof(text)); + } else { + SDL_snprintf(text, sizeof(text), "Auto (%s)", GetGamepadTypeString(ctx->real_type)); + } + } else if (i == SDL_GAMEPAD_TYPE_STANDARD) { + SDL_strlcpy(text, "Standard", sizeof(text)); + } else { + SDL_strlcpy(text, GetGamepadTypeString((SDL_GamepadType)i), sizeof(text)); + } + + dst.x = x + margin; + dst.y = y + line_height / 2 - FONT_CHARACTER_SIZE / 2; + SDLTest_DrawString(ctx->renderer, dst.x, dst.y, text); + + y += line_height; + } +} + +void DestroyGamepadTypeDisplay(GamepadTypeDisplay *ctx) +{ + if (!ctx) { + return; + } + + SDL_free(ctx); +} + + +struct JoystickDisplay +{ + SDL_Renderer *renderer; + SDL_Texture *button_texture; + SDL_Texture *arrow_texture; + float button_width; + float button_height; + float arrow_width; + float arrow_height; + + SDL_FRect area; + + char *element_highlighted; + bool element_pressed; +}; + +JoystickDisplay *CreateJoystickDisplay(SDL_Renderer *renderer) +{ + JoystickDisplay *ctx = SDL_calloc(1, sizeof(*ctx)); + if (ctx) { + ctx->renderer = renderer; + + ctx->button_texture = CreateTexture(renderer, gamepad_button_small_bmp, gamepad_button_small_bmp_len); + SDL_GetTextureSize(ctx->button_texture, &ctx->button_width, &ctx->button_height); + + ctx->arrow_texture = CreateTexture(renderer, gamepad_axis_arrow_bmp, gamepad_axis_arrow_bmp_len); + SDL_GetTextureSize(ctx->arrow_texture, &ctx->arrow_width, &ctx->arrow_height); + } + return ctx; +} + +void SetJoystickDisplayArea(JoystickDisplay *ctx, const SDL_FRect *area) +{ + if (!ctx) { + return; + } + + SDL_copyp(&ctx->area, area); +} + +char *GetJoystickDisplayElementAt(JoystickDisplay *ctx, SDL_Joystick *joystick, float x, float y) +{ + SDL_FPoint point; + int i; + int nbuttons = SDL_GetNumJoystickButtons(joystick); + int naxes = SDL_GetNumJoystickAxes(joystick); + int nhats = SDL_GetNumJoystickHats(joystick); + char text[32]; + const float margin = 8.0f; + const float center = 80.0f; + const float arrow_extent = 48.0f; + SDL_FRect dst, highlight; + char *element = NULL; + + if (!ctx) { + return NULL; + } + + point.x = x; + point.y = y; + + x = ctx->area.x + margin; + y = ctx->area.y + margin; + + if (nbuttons > 0) { + y += FONT_LINE_HEIGHT + 2; + + for (i = 0; i < nbuttons; ++i) { + highlight.x = x; + highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + highlight.w = center - (margin * 2); + highlight.h = ctx->button_height; + if (SDL_PointInRectFloat(&point, &highlight)) { + SDL_asprintf(&element, "b%d", i); + return element; + } + + y += ctx->button_height + 2; + } + } + + x = ctx->area.x + margin + center + margin; + y = ctx->area.y + margin; + + if (naxes > 0) { + y += FONT_LINE_HEIGHT + 2; + + for (i = 0; i < naxes; ++i) { + SDL_snprintf(text, sizeof(text), "%d:", i); + + highlight.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2.0f; + highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + highlight.w = ctx->arrow_width + arrow_extent; + highlight.h = ctx->button_height; + if (SDL_PointInRectFloat(&point, &highlight)) { + SDL_asprintf(&element, "-a%d", i); + return element; + } + + highlight.x += highlight.w; + if (SDL_PointInRectFloat(&point, &highlight)) { + SDL_asprintf(&element, "+a%d", i); + return element; + } + + y += ctx->button_height + 2; + } + } + + y += FONT_LINE_HEIGHT + 2; + + if (nhats > 0) { + y += FONT_LINE_HEIGHT + 2 + 1.5f * ctx->button_height - FONT_CHARACTER_SIZE / 2; + + for (i = 0; i < nhats; ++i) { + SDL_snprintf(text, sizeof(text), "%d:", i); + + dst.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2; + dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + dst.w = ctx->button_width; + dst.h = ctx->button_height; + if (SDL_PointInRectFloat(&point, &dst)) { + SDL_asprintf(&element, "h%d.%d", i, SDL_HAT_LEFT); + return element; + } + + dst.x += ctx->button_width; + dst.y -= ctx->button_height; + if (SDL_PointInRectFloat(&point, &dst)) { + SDL_asprintf(&element, "h%d.%d", i, SDL_HAT_UP); + return element; + } + + dst.y += ctx->button_height * 2; + if (SDL_PointInRectFloat(&point, &dst)) { + SDL_asprintf(&element, "h%d.%d", i, SDL_HAT_DOWN); + return element; + } + + dst.x += ctx->button_width; + dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + if (SDL_PointInRectFloat(&point, &dst)) { + SDL_asprintf(&element, "h%d.%d", i, SDL_HAT_RIGHT); + return element; + } + + y += 3 * ctx->button_height + 2; + } + } + return NULL; +} + +void SetJoystickDisplayHighlight(JoystickDisplay *ctx, const char *element, bool pressed) +{ + if (ctx->element_highlighted) { + SDL_free(ctx->element_highlighted); + ctx->element_highlighted = NULL; + ctx->element_pressed = false; + } + + if (element) { + ctx->element_highlighted = SDL_strdup(element); + ctx->element_pressed = pressed; + } +} + +static void RenderJoystickButtonHighlight(JoystickDisplay *ctx, int button, const SDL_FRect *area) +{ + if (!ctx->element_highlighted || *ctx->element_highlighted != 'b') { + return; + } + + if (SDL_atoi(ctx->element_highlighted + 1) == button) { + Uint8 r, g, b, a; + + SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); + + if (ctx->element_pressed) { + SDL_SetRenderDrawColor(ctx->renderer, PRESSED_COLOR); + } else { + SDL_SetRenderDrawColor(ctx->renderer, HIGHLIGHT_COLOR); + } + SDL_RenderFillRect(ctx->renderer, area); + + SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); + } +} + +static void RenderJoystickAxisHighlight(JoystickDisplay *ctx, int axis, int direction, const SDL_FRect *area) +{ + char prefix = (direction < 0 ? '-' : '+'); + + if (!ctx->element_highlighted || + ctx->element_highlighted[0] != prefix || + ctx->element_highlighted[1] != 'a') { + return; + } + + if (SDL_atoi(ctx->element_highlighted + 2) == axis) { + Uint8 r, g, b, a; + + SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); + + if (ctx->element_pressed) { + SDL_SetRenderDrawColor(ctx->renderer, PRESSED_COLOR); + } else { + SDL_SetRenderDrawColor(ctx->renderer, HIGHLIGHT_COLOR); + } + SDL_RenderFillRect(ctx->renderer, area); + + SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); + } +} + +static bool SetupJoystickHatHighlight(JoystickDisplay *ctx, int hat, int direction) +{ + if (!ctx->element_highlighted || *ctx->element_highlighted != 'h') { + return false; + } + + if (SDL_atoi(ctx->element_highlighted + 1) == hat && + ctx->element_highlighted[2] == '.' && + SDL_atoi(ctx->element_highlighted + 3) == direction) { + if (ctx->element_pressed) { + SDL_SetTextureColorMod(ctx->button_texture, PRESSED_TEXTURE_MOD); + } else { + SDL_SetTextureColorMod(ctx->button_texture, HIGHLIGHT_TEXTURE_MOD); + } + return true; + } + return false; +} + +void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick) +{ + float x, y; + int i; + int nbuttons = SDL_GetNumJoystickButtons(joystick); + int naxes = SDL_GetNumJoystickAxes(joystick); + int nhats = SDL_GetNumJoystickHats(joystick); + char text[32]; + const float margin = 8.0f; + const float center = 80.0f; + const float arrow_extent = 48.0f; + SDL_FRect dst, rect, highlight; + Uint8 r, g, b, a; + + if (!ctx) { + return; + } + + SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); + + x = ctx->area.x + margin; + y = ctx->area.y + margin; + + if (nbuttons > 0) { + SDLTest_DrawString(ctx->renderer, x, y, "BUTTONS"); + y += FONT_LINE_HEIGHT + 2; + + for (i = 0; i < nbuttons; ++i) { + highlight.x = x; + highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + highlight.w = center - (margin * 2); + highlight.h = ctx->button_height; + RenderJoystickButtonHighlight(ctx, i, &highlight); + + SDL_snprintf(text, sizeof(text), "%2d:", i); + SDLTest_DrawString(ctx->renderer, x, y, text); + + if (SDL_GetJoystickButton(joystick, (Uint8)i)) { + SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); + } else { + SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); + } + + dst.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2; + dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + dst.w = ctx->button_width; + dst.h = ctx->button_height; + SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); + + y += ctx->button_height + 2; + } + } + + x = ctx->area.x + margin + center + margin; + y = ctx->area.y + margin; + + if (naxes > 0) { + SDLTest_DrawString(ctx->renderer, x, y, "AXES"); + y += FONT_LINE_HEIGHT + 2; + + for (i = 0; i < naxes; ++i) { + Sint16 value = SDL_GetJoystickAxis(joystick, i); + + SDL_snprintf(text, sizeof(text), "%d:", i); + SDLTest_DrawString(ctx->renderer, x, y, text); + + highlight.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2.0f; + highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + highlight.w = ctx->arrow_width + arrow_extent; + highlight.h = ctx->button_height; + RenderJoystickAxisHighlight(ctx, i, -1, &highlight); + + highlight.x += highlight.w; + RenderJoystickAxisHighlight(ctx, i, 1, &highlight); + + dst.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2.0f; + dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->arrow_height / 2; + dst.w = ctx->arrow_width; + dst.h = ctx->arrow_height; + + 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 += 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 = 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); + } + + 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); + } + + 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; + } + } + + y += FONT_LINE_HEIGHT + 2; + + if (nhats > 0) { + SDLTest_DrawString(ctx->renderer, x, y, "HATS"); + y += FONT_LINE_HEIGHT + 2 + 1.5f * ctx->button_height - FONT_CHARACTER_SIZE / 2; + + for (i = 0; i < nhats; ++i) { + Uint8 value = SDL_GetJoystickHat(joystick, i); + + SDL_snprintf(text, sizeof(text), "%d:", i); + SDLTest_DrawString(ctx->renderer, x, y, text); + + if (value & SDL_HAT_LEFT) { + SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); + } else if (!SetupJoystickHatHighlight(ctx, i, SDL_HAT_LEFT)) { + SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); + } + + dst.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2; + dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + dst.w = ctx->button_width; + dst.h = ctx->button_height; + SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); + + if (value & SDL_HAT_UP) { + SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); + } else if (!SetupJoystickHatHighlight(ctx, i, SDL_HAT_UP)) { + SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); + } + + dst.x += ctx->button_width; + dst.y -= ctx->button_height; + SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); + + if (value & SDL_HAT_DOWN) { + SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); + } else if (!SetupJoystickHatHighlight(ctx, i, SDL_HAT_DOWN)) { + SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); + } + + dst.y += ctx->button_height * 2; + SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); + + if (value & SDL_HAT_RIGHT) { + SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); + } else if (!SetupJoystickHatHighlight(ctx, i, SDL_HAT_RIGHT)) { + SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); + } + + dst.x += ctx->button_width; + dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); + + y += 3 * ctx->button_height + 2; + } + } +} + +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; + float background_width; + float background_height; + + SDL_FRect area; + + char *label; + float label_width; + float label_height; + + bool highlight; + bool pressed; +}; + +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_GetTextureSize(ctx->background, &ctx->background_width, &ctx->background_height); + + ctx->label = SDL_strdup(label); + ctx->label_width = (float)(FONT_CHARACTER_SIZE * SDL_strlen(label)); + ctx->label_height = (float)FONT_CHARACTER_SIZE; + } + return ctx; +} + +void SetGamepadButtonArea(GamepadButton *ctx, const SDL_FRect *area) +{ + if (!ctx) { + return; + } + + SDL_copyp(&ctx->area, area); +} + +void GetGamepadButtonArea(GamepadButton *ctx, SDL_FRect *area) +{ + if (!ctx) { + SDL_zerop(area); + return; + } + + SDL_copyp(area, &ctx->area); +} + +void SetGamepadButtonHighlight(GamepadButton *ctx, bool highlight, bool pressed) +{ + if (!ctx) { + return; + } + + ctx->highlight = highlight; + if (highlight) { + ctx->pressed = pressed; + } else { + ctx->pressed = false; + } +} + +float GetGamepadButtonLabelWidth(GamepadButton *ctx) +{ + if (!ctx) { + return 0; + } + + return ctx->label_width; +} + +float GetGamepadButtonLabelHeight(GamepadButton *ctx) +{ + if (!ctx) { + return 0; + } + + return ctx->label_height; +} + +bool GamepadButtonContains(GamepadButton *ctx, float x, float y) +{ + SDL_FPoint point; + + if (!ctx) { + return 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 = ctx->background_width / 3; + one_third_src_height = ctx->background_height / 3; + + if (ctx->pressed) { + SDL_SetTextureColorMod(ctx->background, PRESSED_TEXTURE_MOD); + } else if (ctx->highlight) { + SDL_SetTextureColorMod(ctx->background, HIGHLIGHT_TEXTURE_MOD); + } 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 = 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 = 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 = ctx->area.h - 2 * one_third_src_height; + SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst); + + /* Right */ + src.x = 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 = 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 = 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); +} + + +typedef struct +{ + char *guid; + char *name; + int num_elements; + char **keys; + char **values; +} MappingParts; + +static bool AddMappingKeyValue(MappingParts *parts, char *key, char *value); + +static bool AddMappingHalfAxisValue(MappingParts *parts, const char *key, const char *value, char sign) +{ + char *new_key, *new_value; + + if (SDL_asprintf(&new_key, "%c%s", sign, key) < 0) { + return false; + } + + if (*value && value[SDL_strlen(value) - 1] == '~') { + /* Invert the sign of the bound axis */ + if (sign == '+') { + sign = '-'; + } else { + sign = '+'; + } + } + + if (SDL_asprintf(&new_value, "%c%s", sign, value) < 0) { + SDL_free(new_key); + return false; + } + if (new_value[SDL_strlen(new_value) - 1] == '~') { + new_value[SDL_strlen(new_value) - 1] = '\0'; + } + + return AddMappingKeyValue(parts, new_key, new_value); +} + +static bool AddMappingKeyValue(MappingParts *parts, char *key, char *value) +{ + int axis; + char **new_keys, **new_values; + + if (!key || !value) { + SDL_free(key); + SDL_free(value); + return false; + } + + /* Split axis values for easy binding purposes */ + for (axis = 0; axis < SDL_GAMEPAD_AXIS_LEFT_TRIGGER; ++axis) { + if (SDL_strcmp(key, SDL_GetGamepadStringForAxis((SDL_GamepadAxis)axis)) == 0) { + bool result; + + result = AddMappingHalfAxisValue(parts, key, value, '-') && + AddMappingHalfAxisValue(parts, key, value, '+'); + SDL_free(key); + SDL_free(value); + return result; + } + } + + new_keys = (char **)SDL_realloc(parts->keys, (parts->num_elements + 1) * sizeof(*new_keys)); + if (!new_keys) { + return false; + } + parts->keys = new_keys; + + new_values = (char **)SDL_realloc(parts->values, (parts->num_elements + 1) * sizeof(*new_values)); + if (!new_values) { + return false; + } + parts->values = new_values; + + new_keys[parts->num_elements] = key; + new_values[parts->num_elements] = value; + ++parts->num_elements; + return true; +} + +static void SplitMapping(const char *mapping, MappingParts *parts) +{ + const char *current, *comma, *colon, *key, *value; + char *new_key, *new_value; + + SDL_zerop(parts); + + if (!mapping || !*mapping) { + return; + } + + /* Get the guid */ + current = mapping; + comma = SDL_strchr(current, ','); + if (!comma) { + parts->guid = SDL_strdup(current); + return; + } + parts->guid = SDL_strndup(current, (comma - current)); + current = comma + 1; + + /* Get the name */ + comma = SDL_strchr(current, ','); + if (!comma) { + parts->name = SDL_strdup(current); + return; + } + if (*current != '*') { + parts->name = SDL_strndup(current, (comma - current)); + } + current = comma + 1; + + for (;;) { + colon = SDL_strchr(current, ':'); + if (!colon) { + break; + } + + key = current; + value = colon + 1; + comma = SDL_strchr(value, ','); + + new_key = SDL_strndup(key, (colon - key)); + if (comma) { + new_value = SDL_strndup(value, (comma - value)); + } else { + new_value = SDL_strdup(value); + } + if (!AddMappingKeyValue(parts, new_key, new_value)) { + break; + } + + if (comma) { + current = comma + 1; + } else { + break; + } + } +} + +static int FindMappingKey(const MappingParts *parts, const char *key) +{ + int i; + + if (key) { + for (i = 0; i < parts->num_elements; ++i) { + if (SDL_strcmp(key, parts->keys[i]) == 0) { + return i; + } + } + } + return -1; +} + +static void RemoveMappingValueAt(MappingParts *parts, int index) +{ + SDL_free(parts->keys[index]); + SDL_free(parts->values[index]); + --parts->num_elements; + if (index < parts->num_elements) { + SDL_memmove(&parts->keys[index], &parts->keys[index] + 1, (parts->num_elements - index) * sizeof(parts->keys[index])); + SDL_memmove(&parts->values[index], &parts->values[index] + 1, (parts->num_elements - index) * sizeof(parts->values[index])); + } +} + +static void ConvertBAXYMapping(MappingParts *parts) +{ + int i; + bool baxy_mapping = false; + + for (i = 0; i < parts->num_elements; ++i) { + const char *key = parts->keys[i]; + const char *value = parts->values[i]; + + if (SDL_strcmp(key, "hint") == 0 && + SDL_strcmp(value, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") == 0) { + baxy_mapping = true; + } + } + + if (!baxy_mapping) { + return; + } + + /* Swap buttons, invert hint */ + for (i = 0; i < parts->num_elements; ++i) { + char *key = parts->keys[i]; + char *value = parts->values[i]; + + if (SDL_strcmp(key, "a") == 0) { + parts->keys[i] = SDL_strdup("b"); + SDL_free(key); + } else if (SDL_strcmp(key, "b") == 0) { + parts->keys[i] = SDL_strdup("a"); + SDL_free(key); + } else if (SDL_strcmp(key, "x") == 0) { + parts->keys[i] = SDL_strdup("y"); + SDL_free(key); + } else if (SDL_strcmp(key, "y") == 0) { + parts->keys[i] = SDL_strdup("x"); + SDL_free(key); + } else if (SDL_strcmp(key, "hint") == 0 && + SDL_strcmp(value, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") == 0) { + parts->values[i] = SDL_strdup("!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1"); + SDL_free(value); + } + } +} + +static void UpdateLegacyElements(MappingParts *parts) +{ + ConvertBAXYMapping(parts); +} + +static bool CombineMappingAxes(MappingParts *parts) +{ + int i, matching, axis; + + for (i = 0; i < parts->num_elements; ++i) { + char *key = parts->keys[i]; + char *current_value; + char *matching_key; + char *matching_value; + + if (*key != '-' && *key != '+') { + continue; + } + + for (axis = 0; axis < SDL_GAMEPAD_AXIS_LEFT_TRIGGER; ++axis) { + if (SDL_strcmp(key + 1, SDL_GetGamepadStringForAxis((SDL_GamepadAxis)axis)) == 0) { + /* Look for a matching axis with the opposite sign */ + if (SDL_asprintf(&matching_key, "%c%s", (*key == '-' ? '+' : '-'), key + 1) < 0) { + return false; + } + matching = FindMappingKey(parts, matching_key); + if (matching >= 0) { + /* Check to see if they're bound to the same axis */ + current_value = parts->values[i]; + matching_value = parts->values[matching]; + if (((*current_value == '-' && *matching_value == '+') || + (*current_value == '+' && *matching_value == '-')) && + SDL_strcmp(current_value + 1, matching_value + 1) == 0) { + /* Combine these axes */ + if (*key == *current_value) { + SDL_memmove(current_value, current_value + 1, SDL_strlen(current_value)); + } else { + /* Invert the bound axis */ + SDL_memmove(current_value, current_value + 1, SDL_strlen(current_value)-1); + current_value[SDL_strlen(current_value) - 1] = '~'; + } + SDL_memmove(key, key + 1, SDL_strlen(key)); + RemoveMappingValueAt(parts, matching); + } + } + SDL_free(matching_key); + break; + } + } + } + return true; +} + +typedef struct +{ + MappingParts *parts; + int index; +} MappingSortEntry; + +static int SDLCALL SortMapping(const void *a, const void *b) +{ + MappingSortEntry *A = (MappingSortEntry *)a; + MappingSortEntry *B = (MappingSortEntry *)b; + const char *keyA = A->parts->keys[A->index]; + const char *keyB = B->parts->keys[B->index]; + + return SDL_strcmp(keyA, keyB); +} + +static void MoveSortedEntry(const char *key, MappingSortEntry *sort_order, int num_elements, bool front) +{ + int i; + + for (i = 0; i < num_elements; ++i) { + MappingSortEntry *entry = &sort_order[i]; + if (SDL_strcmp(key, entry->parts->keys[entry->index]) == 0) { + if (front && i != 0) { + MappingSortEntry tmp = sort_order[i]; + SDL_memmove(&sort_order[1], &sort_order[0], sizeof(*sort_order)*i); + sort_order[0] = tmp; + } else if (!front && i != (num_elements - 1)) { + MappingSortEntry tmp = sort_order[i]; + SDL_memmove(&sort_order[i], &sort_order[i + 1], sizeof(*sort_order)*(num_elements - i - 1)); + sort_order[num_elements - 1] = tmp; + } + break; + } + } +} + +static char *JoinMapping(MappingParts *parts) +{ + int i; + size_t length; + char *mapping; + const char *guid; + const char *name; + MappingSortEntry *sort_order; + + UpdateLegacyElements(parts); + CombineMappingAxes(parts); + + guid = parts->guid; + if (!guid || !*guid) { + guid = "*"; + } + + name = parts->name; + if (!name || !*name) { + name = "*"; + } + + length = SDL_strlen(guid) + 1 + SDL_strlen(name) + 1; + for (i = 0; i < parts->num_elements; ++i) { + length += SDL_strlen(parts->keys[i]) + 1; + length += SDL_strlen(parts->values[i]) + 1; + } + length += 1; + + /* The sort order is: crc, platform, type, *, sdk, hint */ + sort_order = SDL_stack_alloc(MappingSortEntry, parts->num_elements); + for (i = 0; i < parts->num_elements; ++i) { + sort_order[i].parts = parts; + sort_order[i].index = i; + } + SDL_qsort(sort_order, parts->num_elements, sizeof(*sort_order), SortMapping); + MoveSortedEntry("type", sort_order, parts->num_elements, true); + MoveSortedEntry("platform", sort_order, parts->num_elements, true); + MoveSortedEntry("crc", sort_order, parts->num_elements, true); + MoveSortedEntry("sdk>=", sort_order, parts->num_elements, false); + MoveSortedEntry("sdk<=", sort_order, parts->num_elements, false); + MoveSortedEntry("hint", sort_order, parts->num_elements, false); + + /* Move platform to the front */ + + mapping = (char *)SDL_malloc(length); + if (mapping) { + *mapping = '\0'; + SDL_strlcat(mapping, guid, length); + SDL_strlcat(mapping, ",", length); + SDL_strlcat(mapping, name, length); + SDL_strlcat(mapping, ",", length); + for (i = 0; i < parts->num_elements; ++i) { + int next = sort_order[i].index; + SDL_strlcat(mapping, parts->keys[next], length); + SDL_strlcat(mapping, ":", length); + SDL_strlcat(mapping, parts->values[next], length); + SDL_strlcat(mapping, ",", length); + } + } + + SDL_stack_free(sort_order); + + return mapping; +} + +static void FreeMappingParts(MappingParts *parts) +{ + int i; + + SDL_free(parts->guid); + SDL_free(parts->name); + for (i = 0; i < parts->num_elements; ++i) { + SDL_free(parts->keys[i]); + SDL_free(parts->values[i]); + } + SDL_free(parts->keys); + SDL_free(parts->values); + SDL_zerop(parts); +} + +/* Create a new mapping from the parts and free the old mapping and parts */ +static char *RecreateMapping(MappingParts *parts, char *mapping) +{ + char *new_mapping = JoinMapping(parts); + if (new_mapping) { + SDL_free(mapping); + mapping = new_mapping; + } + FreeMappingParts(parts); + return mapping; +} + +static const char *GetLegacyKey(const char *key, bool baxy) +{ + if (SDL_strcmp(key, SDL_GetGamepadStringForButton(SDL_GAMEPAD_BUTTON_SOUTH)) == 0) { + if (baxy) { + return "b"; + } else { + return "a"; + } + } + + if (SDL_strcmp(key, SDL_GetGamepadStringForButton(SDL_GAMEPAD_BUTTON_EAST)) == 0) { + if (baxy) { + return "a"; + } else { + return "b"; + } + } + + if (SDL_strcmp(key, SDL_GetGamepadStringForButton(SDL_GAMEPAD_BUTTON_WEST)) == 0) { + if (baxy) { + return "y"; + } else { + return "x"; + } + } + + if (SDL_strcmp(key, SDL_GetGamepadStringForButton(SDL_GAMEPAD_BUTTON_NORTH)) == 0) { + if (baxy) { + return "y"; + } else { + return "x"; + } + } + + return key; +} + +static bool MappingHasKey(const char *mapping, const char *key) +{ + int i; + MappingParts parts; + bool result = false; + + SplitMapping(mapping, &parts); + i = FindMappingKey(&parts, key); + if (i < 0) { + bool baxy_mapping = false; + + if (mapping && SDL_strstr(mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) { + baxy_mapping = true; + } + i = FindMappingKey(&parts, GetLegacyKey(key, baxy_mapping)); + } + if (i >= 0) { + result = true; + } + FreeMappingParts(&parts); + + return result; +} + +static char *GetMappingValue(const char *mapping, const char *key) +{ + int i; + MappingParts parts; + char *value = NULL; + + SplitMapping(mapping, &parts); + i = FindMappingKey(&parts, key); + if (i < 0) { + bool baxy_mapping = false; + + if (mapping && SDL_strstr(mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) { + baxy_mapping = true; + } + i = FindMappingKey(&parts, GetLegacyKey(key, baxy_mapping)); + } + if (i >= 0) { + value = parts.values[i]; + parts.values[i] = NULL; /* So we don't free it */ + } + FreeMappingParts(&parts); + + return value; +} + +static char *SetMappingValue(char *mapping, const char *key, const char *value) +{ + MappingParts parts; + int i; + char *new_key = NULL; + char *new_value = NULL; + char **new_keys = NULL; + char **new_values = NULL; + bool result = false; + + if (!key) { + return mapping; + } + + SplitMapping(mapping, &parts); + i = FindMappingKey(&parts, key); + if (i >= 0) { + new_value = SDL_strdup(value); + if (new_value) { + SDL_free(parts.values[i]); + parts.values[i] = new_value; + result = true; + } + } else { + int count = parts.num_elements; + + new_key = SDL_strdup(key); + if (new_key) { + new_value = SDL_strdup(value); + if (new_value) { + new_keys = (char **)SDL_realloc(parts.keys, (count + 1) * sizeof(*new_keys)); + if (new_keys) { + new_values = (char **)SDL_realloc(parts.values, (count + 1) * sizeof(*new_values)); + if (new_values) { + new_keys[count] = new_key; + new_values[count] = new_value; + parts.num_elements = (count + 1); + parts.keys = new_keys; + parts.values = new_values; + result = true; + } + } + } + } + } + + if (result) { + mapping = RecreateMapping(&parts, mapping); + } else { + SDL_free(new_key); + SDL_free(new_value); + SDL_free(new_keys); + SDL_free(new_values); + } + return mapping; +} + +static char *RemoveMappingValue(char *mapping, const char *key) +{ + MappingParts parts; + int i; + + SplitMapping(mapping, &parts); + i = FindMappingKey(&parts, key); + if (i >= 0) { + RemoveMappingValueAt(&parts, i); + } + return RecreateMapping(&parts, mapping); +} + +bool MappingHasBindings(const char *mapping) +{ + MappingParts parts; + int i; + bool result = false; + + if (!mapping || !*mapping) { + return false; + } + + SplitMapping(mapping, &parts); + for (i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) { + if (FindMappingKey(&parts, SDL_GetGamepadStringForButton((SDL_GamepadButton)i)) >= 0) { + result = true; + break; + } + } + if (!result) { + for (i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) { + if (FindMappingKey(&parts, SDL_GetGamepadStringForAxis((SDL_GamepadAxis)i)) >= 0) { + result = true; + break; + } + } + } + FreeMappingParts(&parts); + + return result; +} + +bool MappingHasName(const char *mapping) +{ + MappingParts parts; + bool result; + + SplitMapping(mapping, &parts); + result = parts.name ? true : false; + FreeMappingParts(&parts); + return result; +} + +char *GetMappingName(const char *mapping) +{ + MappingParts parts; + char *name; + + SplitMapping(mapping, &parts); + name = parts.name; + parts.name = NULL; /* Don't free the name we're about to return */ + FreeMappingParts(&parts); + return name; +} + +char *SetMappingName(char *mapping, const char *name) +{ + MappingParts parts; + char *new_name, *spot; + size_t length; + + if (!name) { + return mapping; + } + + /* Remove any leading whitespace */ + while (*name && SDL_isspace(*name)) { + ++name; + } + + new_name = SDL_strdup(name); + if (!new_name) { + return mapping; + } + + /* 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); + --length; + } + + /* Remove any trailing whitespace */ + while (length > 0 && SDL_isspace(new_name[length - 1])) { + --length; + } + + /* See if we have anything left */ + if (length == 0) { + SDL_free(new_name); + return mapping; + } + + /* null terminate to cut off anything we've trimmed */ + new_name[length] = '\0'; + + SplitMapping(mapping, &parts); + SDL_free(parts.name); + parts.name = new_name; + return RecreateMapping(&parts, mapping); +} + + +const char *GetGamepadTypeString(SDL_GamepadType type) +{ + switch (type) { + case SDL_GAMEPAD_TYPE_XBOX360: + return "Xbox 360"; + case SDL_GAMEPAD_TYPE_XBOXONE: + return "Xbox One"; + case SDL_GAMEPAD_TYPE_PS3: + return "PS3"; + case SDL_GAMEPAD_TYPE_PS4: + return "PS4"; + case SDL_GAMEPAD_TYPE_PS5: + return "PS5"; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: + return "Nintendo Switch"; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: + return "Joy-Con (L)"; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: + return "Joy-Con (R)"; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + return "Joy-Con Pair"; + default: + return ""; + } +} + +SDL_GamepadType GetMappingType(const char *mapping) +{ + return SDL_GetGamepadTypeFromString(GetMappingValue(mapping, "type")); +} + +char *SetMappingType(char *mapping, SDL_GamepadType type) +{ + const char *type_string = SDL_GetGamepadStringForType(type); + if (!type_string || type == SDL_GAMEPAD_TYPE_UNKNOWN) { + return RemoveMappingValue(mapping, "type"); + } else { + return SetMappingValue(mapping, "type", type_string); + } +} + +static const char *GetElementKey(int element) +{ + if (element < SDL_GAMEPAD_BUTTON_COUNT) { + return SDL_GetGamepadStringForButton((SDL_GamepadButton)element); + } else { + static char key[16]; + + switch (element) { + case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE: + SDL_snprintf(key, sizeof(key), "-%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFTX)); + break; + case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE: + SDL_snprintf(key, sizeof(key), "+%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFTX)); + break; + case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE: + SDL_snprintf(key, sizeof(key), "-%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFTY)); + break; + case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE: + SDL_snprintf(key, sizeof(key), "+%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFTY)); + break; + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE: + SDL_snprintf(key, sizeof(key), "-%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHTX)); + break; + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE: + SDL_snprintf(key, sizeof(key), "+%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHTX)); + break; + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE: + SDL_snprintf(key, sizeof(key), "-%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHTY)); + break; + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE: + SDL_snprintf(key, sizeof(key), "+%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHTY)); + break; + case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER: + return SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFT_TRIGGER); + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER: + return SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHT_TRIGGER); + default: + return NULL; + } + return key; + } +} + +bool MappingHasElement(const char *mapping, int element) +{ + const char *key; + + key = GetElementKey(element); + if (!key) { + return false; + } + return MappingHasKey(mapping, key); +} + +char *GetElementBinding(const char *mapping, int element) +{ + const char *key; + + key = GetElementKey(element); + if (!key) { + return NULL; + } + return GetMappingValue(mapping, key); +} + +char *SetElementBinding(char *mapping, int element, const char *binding) +{ + if (binding) { + return SetMappingValue(mapping, GetElementKey(element), binding); + } else { + return RemoveMappingValue(mapping, GetElementKey(element)); + } +} + +int GetElementForBinding(char *mapping, const char *binding) +{ + MappingParts parts; + int i, element; + int result = SDL_GAMEPAD_ELEMENT_INVALID; + + if (!binding) { + return SDL_GAMEPAD_ELEMENT_INVALID; + } + + SplitMapping(mapping, &parts); + for (i = 0; i < parts.num_elements; ++i) { + if (SDL_strcmp(binding, parts.values[i]) == 0) { + for (element = 0; element < SDL_GAMEPAD_ELEMENT_MAX; ++element) { + const char *key = GetElementKey(element); + if (key && SDL_strcmp(key, parts.keys[i]) == 0) { + result = element; + break; + } + } + break; + } + } + FreeMappingParts(&parts); + + return result; +} + +bool MappingHasBinding(const char *mapping, const char *binding) +{ + MappingParts parts; + int i; + bool result = false; + + if (!binding) { + return false; + } + + SplitMapping(mapping, &parts); + for (i = 0; i < parts.num_elements; ++i) { + if (SDL_strcmp(binding, parts.values[i]) == 0) { + result = true; + break; + } + } + FreeMappingParts(&parts); + + return result; +} + +char *ClearMappingBinding(char *mapping, const char *binding) +{ + MappingParts parts; + int i; + bool modified = false; + + if (!binding) { + return mapping; + } + + SplitMapping(mapping, &parts); + for (i = parts.num_elements - 1; i >= 0; --i) { + if (SDL_strcmp(binding, parts.values[i]) == 0) { + RemoveMappingValueAt(&parts, i); + modified = true; + } + } + if (modified) { + return RecreateMapping(&parts, mapping); + } else { + FreeMappingParts(&parts); + return mapping; + } +} -- cgit v1.2.3