mirror of
				https://github.com/libsdl-org/SDL.git
				synced 2025-10-26 12:27:44 +00:00 
			
		
		
		
	 bb0d6221c1
			
		
	
	bb0d6221c1
	
	
	
		
			
			These are much smaller than the previous BMP files Fixes https://github.com/libsdl-org/SDL/issues/14159
		
			
				
	
	
		
			3661 lines
		
	
	
		
			120 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			3661 lines
		
	
	
		
			120 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|   Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
 | |
| 
 | |
|   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 <SDL3/SDL.h>
 | |
| #include <SDL3/SDL_test_font.h>
 | |
| 
 | |
| #include "gamepadutils.h"
 | |
| #include "gamepad_front.h"
 | |
| #include "gamepad_back.h"
 | |
| #include "gamepad_face_abxy.h"
 | |
| #include "gamepad_face_axby.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"
 | |
| 
 | |
| #include <limits.h>
 | |
| 
 | |
| #define RAD_TO_DEG (180.0f / SDL_PI_F)
 | |
| 
 | |
| /* Used to draw a 3D cube to represent the gyroscope orientation */
 | |
| typedef struct
 | |
| {
 | |
|     float x, y, z;
 | |
| } Vector3;
 | |
| 
 | |
| struct Quaternion
 | |
| {
 | |
|     float x, y, z, w;
 | |
| };
 | |
| 
 | |
| static const Vector3 debug_cube_vertices[] = {
 | |
|     { -1.0f, -1.0f, -1.0f },
 | |
|     { 1.0f, -1.0f, -1.0f },
 | |
|     { 1.0f, 1.0f, -1.0f },
 | |
|     { -1.0f, 1.0f, -1.0f },
 | |
|     { -1.0f, -1.0f, 1.0f },
 | |
|     { 1.0f, -1.0f, 1.0f },
 | |
|     { 1.0f, 1.0f, 1.0f },
 | |
|     { -1.0f, 1.0f, 1.0f },
 | |
| };
 | |
| 
 | |
| static const int debug_cube_edges[][2] = {
 | |
|     { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 0 }, /* bottom square */
 | |
|     { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 4 }, /* top square */
 | |
|     { 0, 4 }, { 1, 5 }, { 2, 6 }, { 3, 7 }, /* verticals */
 | |
| };
 | |
| 
 | |
| static Vector3 RotateVectorByQuaternion(const Vector3 *v, const Quaternion *q) {
 | |
|     /* v' = q * v * q^-1 */
 | |
|     float x = v->x, y = v->y, z = v->z;
 | |
|     float qx = q->x, qy = q->y, qz = q->z, qw = q->w;
 | |
| 
 | |
|     /* Calculate quaternion *vector */
 | |
|     float ix = qw * x + qy * z - qz * y;
 | |
|     float iy = qw * y + qz * x - qx * z;
 | |
|     float iz = qw * z + qx * y - qy * x;
 | |
|     float iw = -qx * x - qy * y - qz * z;
 | |
| 
 | |
|     /* Result = result * conjugate(q) */
 | |
|     Vector3 out;
 | |
|     out.x = ix * qw + iw * -qx + iy * -qz - iz * -qy;
 | |
|     out.y = iy * qw + iw * -qy + iz * -qx - ix * -qz;
 | |
|     out.z = iz * qw + iw * -qz + ix * -qy - iy * -qx;
 | |
|     return out;
 | |
| }
 | |
| 
 | |
| #ifdef GYRO_ISOMETRIC_PROJECTION
 | |
| static SDL_FPoint ProjectVec3ToRect(const Vector3 *v, const SDL_FRect *rect)
 | |
| {
 | |
|     SDL_FPoint out;
 | |
|     /* Simple orthographic projection using X and Y; scale to fit into rect */
 | |
|     out.x = rect->x + (rect->w / 2.0f) + (v->x * (rect->w / 2.0f));
 | |
|     out.y = rect->y + (rect->h / 2.0f) - (v->y * (rect->h / 2.0f)); /* Y inverted */
 | |
|     return out;
 | |
| }
 | |
| #else
 | |
| static SDL_FPoint ProjectVec3ToRect(const Vector3 *v, const SDL_FRect *rect)
 | |
| {
 | |
|     const float verticalFOV_deg = 40.0f;
 | |
|     const float cameraZ = 4.0f; /* Camera is at(0, 0, +4), looking toward origin */
 | |
|     float aspect = rect->w / rect->h;
 | |
| 
 | |
|     float fovScaleY = SDL_tanf(((verticalFOV_deg / 180.0f) * SDL_PI_F) * 0.5f);
 | |
|     float fovScaleX = fovScaleY * aspect;
 | |
| 
 | |
|     float relZ = cameraZ - v->z;
 | |
|     if (relZ < 0.01f) {
 | |
|         relZ = 0.01f; /* Prevent division by 0 or negative depth */
 | |
|     }
 | |
| 
 | |
|     float ndc_x = (v->x / relZ) / fovScaleX;
 | |
|     float ndc_y = (v->y / relZ) / fovScaleY;
 | |
| 
 | |
|     /* Convert to screen space */
 | |
|     SDL_FPoint out;
 | |
|     out.x = rect->x + (rect->w / 2.0f) + (ndc_x * rect->w / 2.0f);
 | |
|     out.y = rect->y + (rect->h / 2.0f) - (ndc_y * rect->h / 2.0f); /* flip Y */
 | |
|     return out;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| void DrawGyroDebugCube(SDL_Renderer *renderer, const Quaternion *orientation, const SDL_FRect *rect)
 | |
| {
 | |
|     SDL_FPoint projected[8];
 | |
|     int i;
 | |
|     for (i = 0; i < 8; ++i) {
 | |
|         Vector3 rotated = RotateVectorByQuaternion(&debug_cube_vertices[i], orientation);
 | |
|         projected[i] = ProjectVec3ToRect(&rotated, rect);
 | |
|     }
 | |
| 
 | |
|     for (i = 0; i < 12; ++i) {
 | |
|         const SDL_FPoint p0 = projected[debug_cube_edges[i][0]];
 | |
|         const SDL_FPoint p1 = projected[debug_cube_edges[i][1]];
 | |
|         SDL_RenderLine(renderer, p0.x, p0.y, p1.x, p1.y);
 | |
|     }
 | |
| }
 | |
| 
 | |
| #define CIRCLE_SEGMENTS 64
 | |
| 
 | |
| static Vector3 kCirclePoints3D_XY_Plane[CIRCLE_SEGMENTS];
 | |
| static Vector3 kCirclePoints3D_XZ_Plane[CIRCLE_SEGMENTS];
 | |
| static Vector3 kCirclePoints3D_YZ_Plane[CIRCLE_SEGMENTS];
 | |
| 
 | |
| void InitCirclePoints3D(void)
 | |
| {
 | |
|     int i;
 | |
|     for (i = 0; i < CIRCLE_SEGMENTS; ++i) {
 | |
|         float theta = ((float)i / CIRCLE_SEGMENTS) * SDL_PI_F * 2.0f;
 | |
|         kCirclePoints3D_XY_Plane[i].x = SDL_cosf(theta);
 | |
|         kCirclePoints3D_XY_Plane[i].y = SDL_sinf(theta);
 | |
|         kCirclePoints3D_XY_Plane[i].z = 0.0f;
 | |
|     }
 | |
| 
 | |
|     for (i = 0; i < CIRCLE_SEGMENTS; ++i) {
 | |
|         float theta = ((float)i / CIRCLE_SEGMENTS) * SDL_PI_F * 2.0f;
 | |
|         kCirclePoints3D_XZ_Plane[i].x = SDL_cosf(theta);
 | |
|         kCirclePoints3D_XZ_Plane[i].y = 0.0f;
 | |
|         kCirclePoints3D_XZ_Plane[i].z = SDL_sinf(theta);
 | |
|     }
 | |
| 
 | |
|     for (i = 0; i < CIRCLE_SEGMENTS; ++i) {
 | |
|         float theta = ((float)i / CIRCLE_SEGMENTS) * SDL_PI_F * 2.0f;
 | |
|         kCirclePoints3D_YZ_Plane[i].x = 0.0f;
 | |
|         kCirclePoints3D_YZ_Plane[i].y = SDL_cosf(theta);
 | |
|         kCirclePoints3D_YZ_Plane[i].z = SDL_sinf(theta);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void DrawGyroCircle(
 | |
|     SDL_Renderer *renderer,
 | |
|     const Vector3 *circlePoints,
 | |
|     int numSegments,
 | |
|     const Quaternion *orientation,
 | |
|     const SDL_FRect *bounds,
 | |
|     Uint8 r, Uint8 g, Uint8 b, Uint8 a)
 | |
| {
 | |
|     SDL_SetRenderDrawColor(renderer, r, g, b, a);
 | |
| 
 | |
|     SDL_FPoint lastScreenPt = { 0 };
 | |
|     bool hasLast = false;
 | |
|     int i;
 | |
|     for (i = 0; i <= numSegments; ++i) {
 | |
|         int index = i % numSegments;
 | |
| 
 | |
|         Vector3 rotated = RotateVectorByQuaternion(&circlePoints[index], orientation);
 | |
|         SDL_FPoint screenPtVec2 = ProjectVec3ToRect(&rotated, bounds);
 | |
|         SDL_FPoint screenPt;
 | |
|         screenPt.x = screenPtVec2.x;
 | |
|         screenPt.y = screenPtVec2.y;
 | |
| 
 | |
| 
 | |
|         if (hasLast) {
 | |
|             SDL_RenderLine(renderer, lastScreenPt.x, lastScreenPt.y, screenPt.x, screenPt.y);
 | |
|         }
 | |
| 
 | |
|         lastScreenPt = screenPt;
 | |
|         hasLast = true;
 | |
|     }
 | |
| }
 | |
| 
 | |
| void DrawGyroDebugCircle(SDL_Renderer *renderer, const Quaternion *orientation, const SDL_FRect *bounds)
 | |
| {
 | |
|     /* Store current color */
 | |
|     Uint8 r, g, b, a;
 | |
|     SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
 | |
|     DrawGyroCircle(renderer, kCirclePoints3D_YZ_Plane, CIRCLE_SEGMENTS, orientation, bounds, GYRO_COLOR_RED);   /* X axis - pitch */
 | |
|     DrawGyroCircle(renderer, kCirclePoints3D_XZ_Plane, CIRCLE_SEGMENTS, orientation, bounds, GYRO_COLOR_GREEN); /* Y axis - yaw */
 | |
|     DrawGyroCircle(renderer, kCirclePoints3D_XY_Plane, CIRCLE_SEGMENTS, orientation, bounds, GYRO_COLOR_BLUE);  /* Z axis - Roll */
 | |
| 
 | |
|     /* Restore current color */
 | |
|     SDL_SetRenderDrawColor(renderer, r, g, b, a);
 | |
| }
 | |
| 
 | |
| 
 | |
| void DrawGyroDebugAxes(SDL_Renderer *renderer, const Quaternion *orientation, const SDL_FRect *bounds)
 | |
| {
 | |
|     /* Store current color */
 | |
|     Uint8 r, g, b, a;
 | |
|     SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
 | |
| 
 | |
|     Vector3 origin = { 0.0f, 0.0f, 0.0f };
 | |
| 
 | |
|     Vector3 right = { 1.0f, 0.0f, 0.0f };
 | |
|     Vector3 up = { 0.0f, 1.0f, 0.0f };
 | |
|     Vector3 back = { 0.0f, 0.0f, 1.0f };
 | |
| 
 | |
|     Vector3 world_right = RotateVectorByQuaternion(&right, orientation);
 | |
|     Vector3 world_up = RotateVectorByQuaternion(&up, orientation);
 | |
|     Vector3 world_back = RotateVectorByQuaternion(&back, orientation);
 | |
| 
 | |
|     SDL_FPoint origin_screen = ProjectVec3ToRect(&origin, bounds);
 | |
|     SDL_FPoint right_screen = ProjectVec3ToRect(&world_right, bounds);
 | |
|     SDL_FPoint up_screen = ProjectVec3ToRect(&world_up, bounds);
 | |
|     SDL_FPoint back_screen = ProjectVec3ToRect(&world_back, bounds);
 | |
| 
 | |
|     SDL_SetRenderDrawColor(renderer, GYRO_COLOR_RED);
 | |
|     SDL_RenderLine(renderer, origin_screen.x, origin_screen.y, right_screen.x, right_screen.y);
 | |
|     SDL_SetRenderDrawColor(renderer, GYRO_COLOR_GREEN);
 | |
|     SDL_RenderLine(renderer, origin_screen.x, origin_screen.y, up_screen.x, up_screen.y);
 | |
|     SDL_SetRenderDrawColor(renderer, GYRO_COLOR_BLUE);
 | |
|     SDL_RenderLine(renderer, origin_screen.x, origin_screen.y, back_screen.x, back_screen.y);
 | |
| 
 | |
|     /* Restore current color */
 | |
|     SDL_SetRenderDrawColor(renderer, r, g, b, a);
 | |
| }
 | |
| 
 | |
| void DrawAccelerometerDebugArrow(SDL_Renderer *renderer, const Quaternion *gyro_quaternion, const float *accel_data, const SDL_FRect *bounds)
 | |
| {
 | |
|     /* Store current color */
 | |
|     Uint8 r, g, b, a;
 | |
|     SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
 | |
| 
 | |
|     const float flGravity = 9.81f;
 | |
|     Vector3 vAccel;
 | |
|     vAccel.x = accel_data[0] / flGravity;
 | |
|     vAccel.y = accel_data[1] / flGravity;
 | |
|     vAccel.z = accel_data[2] / flGravity;
 | |
| 
 | |
|     Vector3 origin = { 0.0f, 0.0f, 0.0f };
 | |
|     Vector3 rotated_accel = RotateVectorByQuaternion(&vAccel, gyro_quaternion);
 | |
| 
 | |
|     /* Project the origin and rotated vector to screen space */
 | |
|     SDL_FPoint origin_screen = ProjectVec3ToRect(&origin, bounds);
 | |
|     SDL_FPoint accel_screen = ProjectVec3ToRect(&rotated_accel, bounds);
 | |
| 
 | |
|     /* Draw the line from origin to the rotated accelerometer vector */
 | |
|     SDL_SetRenderDrawColor(renderer, GYRO_COLOR_ORANGE);
 | |
|     SDL_RenderLine(renderer, origin_screen.x, origin_screen.y, accel_screen.x, accel_screen.y);
 | |
| 
 | |
|     const float head_width = 4.0f;
 | |
|     SDL_FRect arrow_head_rect;
 | |
|     arrow_head_rect.x = accel_screen.x - head_width * 0.5f;
 | |
|     arrow_head_rect.y = accel_screen.y - head_width * 0.5f;
 | |
|     arrow_head_rect.w = head_width;
 | |
|     arrow_head_rect.h = head_width;
 | |
|     SDL_RenderRect(renderer, &arrow_head_rect);
 | |
| 
 | |
|     /* Restore current color */
 | |
|     SDL_SetRenderDrawColor(renderer, r, g, b, a);
 | |
| }
 | |
| 
 | |
| /* 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_axby_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;
 | |
|     SDL_GamepadButtonLabel east_label;
 | |
|     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_LoadPNG_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_png, gamepad_front_png_len);
 | |
|         ctx->back_texture = CreateTexture(renderer, gamepad_back_png, gamepad_back_png_len);
 | |
|         SDL_GetTextureSize(ctx->front_texture, &ctx->gamepad_width, &ctx->gamepad_height);
 | |
| 
 | |
|         ctx->face_abxy_texture = CreateTexture(renderer, gamepad_face_abxy_png, gamepad_face_abxy_png_len);
 | |
|         ctx->face_axby_texture = CreateTexture(renderer, gamepad_face_axby_png, gamepad_face_axby_png_len);
 | |
|         ctx->face_bayx_texture = CreateTexture(renderer, gamepad_face_bayx_png, gamepad_face_bayx_png_len);
 | |
|         ctx->face_sony_texture = CreateTexture(renderer, gamepad_face_sony_png, gamepad_face_sony_png_len);
 | |
|         SDL_GetTextureSize(ctx->face_abxy_texture, &ctx->face_width, &ctx->face_height);
 | |
| 
 | |
|         ctx->connection_texture[0] = CreateTexture(renderer, gamepad_wired_png, gamepad_wired_png_len);
 | |
|         ctx->connection_texture[1] = CreateTexture(renderer, gamepad_wireless_png, gamepad_wireless_png_len);
 | |
|         SDL_GetTextureSize(ctx->connection_texture[0], &ctx->connection_width, &ctx->connection_height);
 | |
| 
 | |
|         ctx->battery_texture[0] = CreateTexture(renderer, gamepad_battery_png, gamepad_battery_png_len);
 | |
|         ctx->battery_texture[1] = CreateTexture(renderer, gamepad_battery_wired_png, gamepad_battery_wired_png_len);
 | |
|         SDL_GetTextureSize(ctx->battery_texture[0], &ctx->battery_width, &ctx->battery_height);
 | |
| 
 | |
|         ctx->touchpad_texture = CreateTexture(renderer, gamepad_touchpad_png, gamepad_touchpad_png_len);
 | |
|         SDL_GetTextureSize(ctx->touchpad_texture, &ctx->touchpad_width, &ctx->touchpad_height);
 | |
| 
 | |
|         ctx->button_texture = CreateTexture(renderer, gamepad_button_png, gamepad_button_png_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_png, gamepad_axis_png_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);
 | |
|     ctx->east_label = SDL_GetGamepadButtonLabel(gamepad, SDL_GAMEPAD_BUTTON_EAST);
 | |
|     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 (ctx->east_label) {
 | |
|         case SDL_GAMEPAD_BUTTON_LABEL_B:
 | |
|             SDL_RenderTexture(ctx->renderer, ctx->face_abxy_texture, NULL, &dst);
 | |
|             break;
 | |
|         case SDL_GAMEPAD_BUTTON_LABEL_X:
 | |
|             SDL_RenderTexture(ctx->renderer, ctx->face_axby_texture, NULL, &dst);
 | |
|             break;
 | |
|         case SDL_GAMEPAD_BUTTON_LABEL_A:
 | |
|             SDL_RenderTexture(ctx->renderer, ctx->face_bayx_texture, NULL, &dst);
 | |
|             break;
 | |
|         case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
 | |
|             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_axby_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->fingers);
 | |
|         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];
 | |
|     float gyro_drift_correction_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_png, gamepad_button_small_png_len);
 | |
|         SDL_GetTextureSize(ctx->button_texture, &ctx->button_width, &ctx->button_height);
 | |
| 
 | |
|         ctx->arrow_texture = CreateTexture(renderer, gamepad_axis_arrow_png, gamepad_axis_arrow_png_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;
 | |
| 
 | |
|         SDL_zeroa(ctx->accel_data);
 | |
|         SDL_zeroa(ctx->gyro_data);
 | |
|         SDL_zeroa(ctx->gyro_drift_correction_data);
 | |
|     }
 | |
|     return ctx;
 | |
| }
 | |
| 
 | |
| struct GyroDisplay
 | |
| {
 | |
|     SDL_Renderer *renderer;
 | |
| 
 | |
|     /* Main drawing area */
 | |
|     SDL_FRect area;
 | |
| 
 | |
|     /* This part displays extra info from the IMUstate in order to figure out actual polling rates. */
 | |
|     float gyro_drift_solution[3];
 | |
|     int reported_sensor_rate_hz;           /*hz - comes from HIDsdl implementation. Could be fixed, platform time, or true sensor time*/
 | |
|     Uint64 next_reported_sensor_time;      /* SDL ticks used to throttle the display */
 | |
| 
 | |
|     int estimated_sensor_rate_hz;          /*hz - our estimation of the actual polling rate by observing packets received*/
 | |
|     float euler_displacement_angles[3];    /* pitch, yaw, roll */
 | |
|     Quaternion gyro_quaternion;            /* Rotation since startup/reset, comprised of each gyro speed packet times sensor delta time. */
 | |
|     EGyroCalibrationPhase current_calibration_phase;
 | |
|     float calibration_phase_progress_fraction; /* [0..1] */
 | |
|     float accelerometer_noise_sq;          /* Distance between last noise and new noise. Used to indicate motion.*/
 | |
|     float accelerometer_noise_tolerance_sq; /* Maximum amount of noise detected during the Noise Profiling Phase */
 | |
| 
 | |
|     GamepadButton *reset_gyro_button;
 | |
|     GamepadButton *calibrate_gyro_button;
 | |
| };
 | |
| 
 | |
| GyroDisplay *CreateGyroDisplay(SDL_Renderer *renderer)
 | |
| {
 | |
|     GyroDisplay *ctx = SDL_calloc(1, sizeof(*ctx));
 | |
|     {
 | |
|         ctx->renderer = renderer;
 | |
|         ctx->estimated_sensor_rate_hz = 0;
 | |
|         SDL_zeroa(ctx->gyro_drift_solution);
 | |
|         Quaternion quat_identity = { 0.0f, 0.0f, 0.0f, 1.0f };
 | |
|         ctx->gyro_quaternion = quat_identity;
 | |
|         ctx->reported_sensor_rate_hz = 0;
 | |
|         ctx->next_reported_sensor_time = 0;
 | |
|         ctx->current_calibration_phase = GYRO_CALIBRATION_PHASE_OFF;
 | |
|         ctx->calibration_phase_progress_fraction = 0.0f; /* [0..1] */
 | |
|         ctx->accelerometer_noise_sq = 0.0f;
 | |
|         ctx->accelerometer_noise_tolerance_sq = ACCELEROMETER_NOISE_THRESHOLD; /* Will be overwritten but this avoids divide by zero. */
 | |
|         ctx->reset_gyro_button = CreateGamepadButton(renderer, "Reset View");
 | |
|         ctx->calibrate_gyro_button = CreateGamepadButton(renderer, "Recalibrate Drift");
 | |
|     }
 | |
| 
 | |
|     return ctx;
 | |
| }
 | |
| 
 | |
| void SetGyroDisplayArea(GyroDisplay *ctx, const SDL_FRect *area)
 | |
| {
 | |
|     if (!ctx) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     SDL_copyp(&ctx->area, area);
 | |
|     /* Place the reset button to the bottom right of the gyro display area.*/
 | |
|     SDL_FRect reset_button_area;
 | |
|     reset_button_area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(ctx->reset_gyro_button) + 2 * BUTTON_PADDING);
 | |
|     reset_button_area.h = GetGamepadButtonLabelHeight(ctx->reset_gyro_button) + BUTTON_PADDING;
 | |
|     reset_button_area.x = area->x + area->w - reset_button_area.w - BUTTON_PADDING;
 | |
|     reset_button_area.y = area->y + area->h - reset_button_area.h - BUTTON_PADDING;
 | |
|     SetGamepadButtonArea(ctx->reset_gyro_button, &reset_button_area);
 | |
| }
 | |
| 
 | |
| 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);
 | |
| }
 | |
| void SetGamepadDisplayGyroDriftCorrection(GamepadDisplay *ctx, float *gyro_drift_correction)
 | |
| {
 | |
|     if (!ctx) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     ctx->gyro_drift_correction_data[0] = gyro_drift_correction[0];
 | |
|     ctx->gyro_drift_correction_data[1] = gyro_drift_correction[1];
 | |
|     ctx->gyro_drift_correction_data[2] = gyro_drift_correction[2];
 | |
| }
 | |
| 
 | |
| 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 SetGamepadDisplayIMUValues(GyroDisplay *ctx, float *gyro_drift_solution, float *euler_displacement_angles, Quaternion *gyro_quaternion, int reported_senor_rate_hz, int estimated_sensor_rate_hz, EGyroCalibrationPhase calibration_phase, float drift_calibration_progress_frac, float accelerometer_noise_sq, float accelerometer_noise_tolerance_sq)
 | |
| {
 | |
|     if (!ctx) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     const int SENSOR_UPDATE_INTERVAL_MS = 100;
 | |
|     Uint64 now = SDL_GetTicks();
 | |
|     if (now > ctx->next_reported_sensor_time) {
 | |
|         ctx->estimated_sensor_rate_hz = estimated_sensor_rate_hz;
 | |
|         if (reported_senor_rate_hz != 0) {
 | |
|             ctx->reported_sensor_rate_hz = reported_senor_rate_hz;
 | |
|         }
 | |
|         ctx->next_reported_sensor_time = now + SENSOR_UPDATE_INTERVAL_MS;
 | |
|     }
 | |
| 
 | |
|     SDL_memcpy(ctx->gyro_drift_solution, gyro_drift_solution, sizeof(ctx->gyro_drift_solution));
 | |
|     SDL_memcpy(ctx->euler_displacement_angles, euler_displacement_angles, sizeof(ctx->euler_displacement_angles));
 | |
|     ctx->gyro_quaternion = *gyro_quaternion;
 | |
|     ctx->current_calibration_phase = calibration_phase;
 | |
|     ctx->calibration_phase_progress_fraction = drift_calibration_progress_frac;
 | |
|     ctx->accelerometer_noise_sq = accelerometer_noise_sq;
 | |
|     ctx->accelerometer_noise_tolerance_sq = accelerometer_noise_tolerance_sq;
 | |
| }
 | |
| 
 | |
| extern GamepadButton *GetGyroResetButton(GyroDisplay *ctx)
 | |
| {
 | |
|     if (!ctx) {
 | |
|         return NULL;
 | |
|     }
 | |
|     return ctx->reset_gyro_button;
 | |
| }
 | |
| 
 | |
| extern GamepadButton *GetGyroCalibrateButton(GyroDisplay *ctx)
 | |
| {
 | |
|     if (!ctx) {
 | |
|         return NULL;
 | |
|     }
 | |
|     return ctx->calibrate_gyro_button;
 | |
| }
 | |
| 
 | |
| 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]m/s%s", ctx->accel_data[0], ctx->accel_data[1], ctx->accel_data[2], SQUARED_UTF8 );
 | |
|                 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]%s/s", ctx->gyro_data[0] * RAD_TO_DEG, ctx->gyro_data[1] * RAD_TO_DEG, ctx->gyro_data[2] * RAD_TO_DEG, DEGREE_UTF8);
 | |
|                 SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text);
 | |
| 
 | |
|                 /* Display the testcontroller tool's evaluation of drift. This is also useful to get an average rate of turn in calibrated turntable tests. */
 | |
|                 if (ctx->gyro_drift_correction_data[0] != 0.0f && ctx->gyro_drift_correction_data[2] != 0.0f && ctx->gyro_drift_correction_data[2] != 0.0f )
 | |
|                 {
 | |
|                     y += ctx->button_height + 2.0f;
 | |
|                     SDL_strlcpy(text, "Gyro Drift:", sizeof(text));
 | |
|                     SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
 | |
|                     SDL_snprintf(text, sizeof(text), "[%.2f,%.2f,%.2f]%s/s", ctx->gyro_drift_correction_data[0] * RAD_TO_DEG, ctx->gyro_drift_correction_data[1] * RAD_TO_DEG, ctx->gyro_drift_correction_data[2] * RAD_TO_DEG, DEGREE_UTF8);
 | |
|                     SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     SDL_free(mapping);
 | |
| }
 | |
| 
 | |
| void DestroyGamepadDisplay(GamepadDisplay *ctx)
 | |
| {
 | |
|     if (!ctx) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     SDL_DestroyTexture(ctx->button_texture);
 | |
|     SDL_DestroyTexture(ctx->arrow_texture);
 | |
|     SDL_free(ctx);
 | |
| }
 | |
| 
 | |
| void RenderSensorTimingInfo(GyroDisplay *ctx, GamepadDisplay *gamepad_display)
 | |
| {
 | |
|     /* Sensor timing section */
 | |
|     char text[128];
 | |
|     const float new_line_height = gamepad_display->button_height + 2.0f;
 | |
|     const float text_offset_x = ctx->area.x + ctx->area.w / 4.0f + 35.0f;
 | |
|     /* Anchor to bottom left of principle rect. */
 | |
|     float text_y_pos = ctx->area.y + ctx->area.h - new_line_height * 2;
 | |
|     /*
 | |
|      * Display rate of gyro as reported by the HID implementation.
 | |
|      * This could be based on a hardware time stamp (PS5), or it could be generated by the HID implementation.
 | |
|      * One should expect this to match the estimated rate below, assuming a wired connection.
 | |
|      */
 | |
| 
 | |
|     SDL_strlcpy(text, "HID Sensor Time:", sizeof(text));
 | |
|     SDLTest_DrawString(ctx->renderer, text_offset_x - SDL_strlen(text) * FONT_CHARACTER_SIZE, text_y_pos, text);
 | |
|     if (ctx->reported_sensor_rate_hz > 0) {
 | |
|         /* Convert to micro seconds */
 | |
|         const int delta_time_us = (int)1e6 / ctx->reported_sensor_rate_hz;
 | |
|         SDL_snprintf(text, sizeof(text), "%d%ss %dhz", delta_time_us, MICRO_UTF8, ctx->reported_sensor_rate_hz);
 | |
|     } else {
 | |
|         SDL_snprintf(text, sizeof(text), "????%ss ???hz", MICRO_UTF8);
 | |
|     }
 | |
|     SDLTest_DrawString(ctx->renderer, text_offset_x + 2.0f, text_y_pos, text);
 | |
| 
 | |
|     /*
 | |
|      * Display the instrumentation's count of all sensor packets received over time.
 | |
|      * This may represent a more accurate polling rate for the IMU
 | |
|      * But only when using a wired connection.
 | |
|      * It does not necessarily reflect the rate at which the IMU is sampled.
 | |
|      */
 | |
| 
 | |
|     text_y_pos += new_line_height;
 | |
|     SDL_strlcpy(text, "Est.Sensor Time:", sizeof(text));
 | |
|     SDLTest_DrawString(ctx->renderer, text_offset_x - SDL_strlen(text) * FONT_CHARACTER_SIZE, text_y_pos, text);
 | |
|     if (ctx->estimated_sensor_rate_hz > 0) {
 | |
|         /* Convert to micro seconds */
 | |
|         const int delta_time_us = (int)1e6 / ctx->estimated_sensor_rate_hz;
 | |
|         SDL_snprintf(text, sizeof(text), "%d%ss %dhz", delta_time_us, MICRO_UTF8, ctx->estimated_sensor_rate_hz);
 | |
|     } else {
 | |
|         SDL_snprintf(text, sizeof(text), "????%ss ???hz", MICRO_UTF8);
 | |
|     }
 | |
|     SDLTest_DrawString(ctx->renderer, text_offset_x + 2.0f, text_y_pos, text);
 | |
| }
 | |
| 
 | |
| void RenderGyroDriftCalibrationButton(GyroDisplay *ctx, GamepadDisplay *gamepad_display )
 | |
| {
 | |
|     char label_text[128];
 | |
|     float log_y = ctx->area.y + BUTTON_PADDING;
 | |
|     const float new_line_height = gamepad_display->button_height + 2.0f;
 | |
|     GamepadButton *start_calibration_button = GetGyroCalibrateButton(ctx);
 | |
| 
 | |
|     /* Show the recalibration progress bar. */
 | |
|     float recalibrate_button_width = GetGamepadButtonLabelWidth(start_calibration_button) + 2 * BUTTON_PADDING;
 | |
|     SDL_FRect recalibrate_button_area;
 | |
|     recalibrate_button_area.x = ctx->area.x + ctx->area.w - recalibrate_button_width - BUTTON_PADDING;
 | |
|     recalibrate_button_area.y = log_y + FONT_CHARACTER_SIZE * 0.5f - gamepad_display->button_height * 0.5f;
 | |
|     recalibrate_button_area.w = GetGamepadButtonLabelWidth(start_calibration_button) + 2.0f * BUTTON_PADDING;
 | |
|     recalibrate_button_area.h = gamepad_display->button_height + BUTTON_PADDING * 2.0f;
 | |
| 
 | |
|     /* Above button */
 | |
|     SDL_strlcpy(label_text, "Gyro Orientation:", sizeof(label_text));
 | |
|     SDLTest_DrawString(ctx->renderer, recalibrate_button_area.x, recalibrate_button_area.y - new_line_height, label_text);
 | |
| 
 | |
|     /* Button label vs state */
 | |
|     if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_OFF) {
 | |
|         SDL_strlcpy(label_text, "Start Gyro Calibration", sizeof(label_text));
 | |
|     } else if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_NOISE_PROFILING) {
 | |
|         SDL_snprintf(label_text, sizeof(label_text), "Noise Progress: %3.0f%% ", ctx->calibration_phase_progress_fraction * 100.0f);
 | |
|     } else if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_DRIFT_PROFILING) {
 | |
|         SDL_snprintf(label_text, sizeof(label_text), "Drift Progress: %3.0f%% ", ctx->calibration_phase_progress_fraction * 100.0f);
 | |
|     } else if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_COMPLETE) {
 | |
|         SDL_strlcpy(label_text, "Recalibrate Gyro", sizeof(label_text));
 | |
|     }
 | |
| 
 | |
|     SetGamepadButtonLabel(start_calibration_button, label_text);
 | |
|     SetGamepadButtonArea(start_calibration_button, &recalibrate_button_area);
 | |
|     RenderGamepadButton(start_calibration_button);
 | |
| 
 | |
|     bool bExtremeNoise = ctx->accelerometer_noise_sq > ACCELEROMETER_MAX_NOISE_G_SQ;
 | |
|     /* Explicit warning message if we detect too much movement */
 | |
|     if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_OFF) {
 | |
|         if (bExtremeNoise) {
 | |
|             SDL_strlcpy(label_text, "GamePad Must Be Still", sizeof(label_text));
 | |
|             SDLTest_DrawString(ctx->renderer, recalibrate_button_area.x, recalibrate_button_area.y + recalibrate_button_area.h + new_line_height, label_text);
 | |
|             SDL_strlcpy(label_text, "Place GamePad On Table", sizeof(label_text));
 | |
|             SDLTest_DrawString(ctx->renderer, recalibrate_button_area.x, recalibrate_button_area.y + recalibrate_button_area.h + new_line_height * 2, label_text);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_NOISE_PROFILING ||
 | |
|         ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_DRIFT_PROFILING)
 | |
|     {
 | |
|         float flAbsoluteNoiseFraction = SDL_clamp(ctx->accelerometer_noise_sq / ACCELEROMETER_MAX_NOISE_G_SQ, 0.0f, 1.0f);
 | |
|         float flAbsoluteToleranceFraction = SDL_clamp(ctx->accelerometer_noise_tolerance_sq / ACCELEROMETER_MAX_NOISE_G_SQ, 0.0f, 1.0f);
 | |
| 
 | |
|         float flMaxNoiseForThisPhase = ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_NOISE_PROFILING ? ACCELEROMETER_MAX_NOISE_G_SQ : ctx->accelerometer_noise_tolerance_sq;
 | |
|         float flRelativeNoiseFraction = SDL_clamp(ctx->accelerometer_noise_sq / flMaxNoiseForThisPhase, 0.0f, 1.0f);
 | |
| 
 | |
|         float noise_bar_height = gamepad_display->button_height;
 | |
|         SDL_FRect noise_bar_rect;
 | |
|         noise_bar_rect.x = recalibrate_button_area.x;
 | |
|         noise_bar_rect.y = recalibrate_button_area.y + recalibrate_button_area.h + BUTTON_PADDING;
 | |
|         noise_bar_rect.w = recalibrate_button_area.w;
 | |
|         noise_bar_rect.h = noise_bar_height;
 | |
| 
 | |
|         SDL_snprintf(label_text, sizeof(label_text), "Accelerometer Noise Tolerance: %3.3fG ", SDL_sqrtf(ctx->accelerometer_noise_tolerance_sq) );
 | |
|         SDLTest_DrawString(ctx->renderer, recalibrate_button_area.x, recalibrate_button_area.y + recalibrate_button_area.h + new_line_height * 2, label_text);
 | |
| 
 | |
|         /* Adjust the noise bar rectangle based on the accelerometer noise value */
 | |
|         float noise_bar_fill_width = flAbsoluteNoiseFraction * noise_bar_rect.w; /* Scale the width based on the noise value */
 | |
|         SDL_FRect noise_bar_fill_rect;
 | |
|         noise_bar_fill_rect.x = noise_bar_rect.x + (noise_bar_rect.w - noise_bar_fill_width) * 0.5f;
 | |
|         noise_bar_fill_rect.y = noise_bar_rect.y;
 | |
|         noise_bar_fill_rect.w = noise_bar_fill_width;
 | |
|         noise_bar_fill_rect.h = noise_bar_height;
 | |
| 
 | |
|         /* Set the color based on the noise value vs the tolerance */
 | |
|         Uint8 red = (Uint8)(flRelativeNoiseFraction * 255.0f);
 | |
|         Uint8 green = (Uint8)((1.0f - flRelativeNoiseFraction) * 255.0f);
 | |
|         SDL_SetRenderDrawColor(ctx->renderer, red, green, 0, 255); /* red when high noise, green when low noise */
 | |
|         SDL_RenderFillRect(ctx->renderer, &noise_bar_fill_rect);   /* draw the filled rectangle */
 | |
| 
 | |
|         float tolerance_bar_fill_width = flAbsoluteToleranceFraction * noise_bar_rect.w; /* Scale the width based on the noise value */
 | |
|         SDL_FRect tolerance_bar_rect;
 | |
|         tolerance_bar_rect.x = noise_bar_rect.x + (noise_bar_rect.w - tolerance_bar_fill_width) * 0.5f;
 | |
|         tolerance_bar_rect.y = noise_bar_rect.y;
 | |
|         tolerance_bar_rect.w = tolerance_bar_fill_width;
 | |
|         tolerance_bar_rect.h = noise_bar_height;
 | |
| 
 | |
|         SDL_SetRenderDrawColor(ctx->renderer, 128, 128, 0, 255);
 | |
|         SDL_RenderRect(ctx->renderer, &tolerance_bar_rect);        /* draw the tolerance rectangle */
 | |
| 
 | |
|         SDL_SetRenderDrawColor(ctx->renderer, 100, 100, 100, 255); /* gray box */
 | |
|         SDL_RenderRect(ctx->renderer, &noise_bar_rect);            /* draw the outline rectangle */
 | |
| 
 | |
|         /* Explicit warning message if we detect too much movement */
 | |
|         bool bTooMuchNoise = (flAbsoluteNoiseFraction == 1.0f);
 | |
|         if (bTooMuchNoise) {
 | |
|             SDL_strlcpy(label_text, "Place GamePad Down!", sizeof(label_text));
 | |
|             SDLTest_DrawString(ctx->renderer, recalibrate_button_area.x, noise_bar_rect.y + noise_bar_rect.h + new_line_height, label_text);
 | |
|         }
 | |
| 
 | |
|         /* Drift progress bar */
 | |
|         /* Demonstrate how far we are through the drift progress, and how it resets when there's "high noise", i.e if flNoiseFraction == 1.0f */
 | |
|         SDL_FRect progress_bar_rect;
 | |
|         progress_bar_rect.x = recalibrate_button_area.x + BUTTON_PADDING;
 | |
|         progress_bar_rect.y = recalibrate_button_area.y + recalibrate_button_area.h * 0.5f + BUTTON_PADDING * 0.5f;
 | |
|         progress_bar_rect.w = recalibrate_button_area.w - BUTTON_PADDING * 2.0f;
 | |
|         progress_bar_rect.h = BUTTON_PADDING * 0.5f;
 | |
| 
 | |
|         /* Adjust the drift bar rectangle based on the drift calibration progress fraction */
 | |
|         float drift_bar_fill_width = bTooMuchNoise ? 1.0f : ctx->calibration_phase_progress_fraction * progress_bar_rect.w;
 | |
|         SDL_FRect progress_bar_fill;
 | |
|         progress_bar_fill.x = progress_bar_rect.x;
 | |
|         progress_bar_fill.y = progress_bar_rect.y;
 | |
|         progress_bar_fill.w = drift_bar_fill_width;
 | |
|         progress_bar_fill.h = progress_bar_rect.h;
 | |
| 
 | |
|         /* Set the color based on the drift calibration progress fraction */
 | |
|         SDL_SetRenderDrawColor(ctx->renderer, GYRO_COLOR_GREEN);        /* red when too much noise, green when low noise*/
 | |
|         /* Now draw the bars with the filled, then empty rectangles */
 | |
|         SDL_RenderFillRect(ctx->renderer, &progress_bar_fill);          /* draw the filled rectangle*/
 | |
|         SDL_SetRenderDrawColor(ctx->renderer, 100, 100, 100, 255);      /* gray box*/
 | |
|         SDL_RenderRect(ctx->renderer, &progress_bar_rect);              /* draw the outline rectangle*/
 | |
| 
 | |
|         /* If there is too much movement, we are going to draw two diagonal red lines between the progress rect corners.*/
 | |
|         if (bTooMuchNoise) {
 | |
|             SDL_SetRenderDrawColor(ctx->renderer, GYRO_COLOR_RED);      /* red */
 | |
|             SDL_RenderFillRect(ctx->renderer, &progress_bar_fill);      /* draw the filled rectangle */
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| float RenderEulerReadout(GyroDisplay *ctx, GamepadDisplay *gamepad_display )
 | |
| {
 | |
|     /* Get the mater button's width and base our width off that */
 | |
|     GamepadButton *master_button = GetGyroCalibrateButton(ctx);
 | |
|     SDL_FRect gyro_calibrate_button_rect;
 | |
|     GetGamepadButtonArea(master_button, &gyro_calibrate_button_rect);
 | |
| 
 | |
|     char text[128];
 | |
|     float log_y = gyro_calibrate_button_rect.y + gyro_calibrate_button_rect.h + BUTTON_PADDING;
 | |
|     const float new_line_height = gamepad_display->button_height + 2.0f;
 | |
|     float log_gyro_euler_text_x = gyro_calibrate_button_rect.x;
 | |
| 
 | |
|     Uint8 r, g, b, a;
 | |
|     SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a);
 | |
|     /* Pitch Readout */
 | |
|     SDL_SetRenderDrawColor(ctx->renderer, GYRO_COLOR_RED);
 | |
|     SDL_snprintf(text, sizeof(text), "Pitch: %6.2f%s", ctx->euler_displacement_angles[0], DEGREE_UTF8);
 | |
|     SDLTest_DrawString(ctx->renderer, log_gyro_euler_text_x + 2.0f, log_y, text);
 | |
| 
 | |
|     /* Yaw Readout */
 | |
|     SDL_SetRenderDrawColor(ctx->renderer, GYRO_COLOR_GREEN);
 | |
|     log_y += new_line_height;
 | |
|     SDL_snprintf(text, sizeof(text), "  Yaw: %6.2f%s", ctx->euler_displacement_angles[1], DEGREE_UTF8);
 | |
|     SDLTest_DrawString(ctx->renderer, log_gyro_euler_text_x + 2.0f, log_y, text);
 | |
| 
 | |
|     /* Roll Readout */
 | |
|     SDL_SetRenderDrawColor(ctx->renderer, GYRO_COLOR_BLUE);
 | |
|     log_y += new_line_height;
 | |
|     SDL_snprintf(text, sizeof(text), " Roll: %6.2f%s", ctx->euler_displacement_angles[2], DEGREE_UTF8);
 | |
|     SDLTest_DrawString(ctx->renderer, log_gyro_euler_text_x + 2.0f, log_y, text);
 | |
| 
 | |
|     SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
 | |
|     return log_y + new_line_height; /* Return the next y position for further rendering */
 | |
| }
 | |
| 
 | |
| /* Draws the 3D cube, circles and accel arrow, positioning itself relative to the calibrate button. */
 | |
| void RenderGyroGizmo(GyroDisplay *ctx, SDL_Gamepad *gamepad, float top)
 | |
| {
 | |
|     /* Get the calibrate button's on-screen area: */
 | |
|     GamepadButton *btn = GetGyroCalibrateButton(ctx);
 | |
|     SDL_FRect btnArea;
 | |
|     GetGamepadButtonArea(btn, &btnArea);
 | |
| 
 | |
|     float gizmoSize = btnArea.w;
 | |
|     /* Position it centered horizontally above the button with a small gap */
 | |
|     SDL_FRect gizmoRect;
 | |
|     gizmoRect.x = btnArea.x + (btnArea.w - gizmoSize) * 0.5f;
 | |
|     gizmoRect.y = top;
 | |
|     gizmoRect.w = gizmoSize;
 | |
|     gizmoRect.h = gizmoSize;
 | |
| 
 | |
|     /* Draw the rotated cube */
 | |
|     DrawGyroDebugCube(ctx->renderer, &ctx->gyro_quaternion, &gizmoRect);
 | |
| 
 | |
|     /* Draw positive axes */
 | |
|     DrawGyroDebugAxes(ctx->renderer, &ctx->gyro_quaternion, &gizmoRect);
 | |
| 
 | |
|     /* Overlay the XYZ circles */
 | |
|     DrawGyroDebugCircle(ctx->renderer, &ctx->gyro_quaternion, &gizmoRect);
 | |
| 
 | |
|     /* If we have accel, draw that arrow too */
 | |
|     if (SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL)) {
 | |
|         float accel[3];
 | |
|         SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_ACCEL, accel, SDL_arraysize(accel));
 | |
|         DrawAccelerometerDebugArrow(ctx->renderer, &ctx->gyro_quaternion, accel, &gizmoRect);
 | |
|     }
 | |
| 
 | |
|     /* Follow the size of the main button, but position it below the gizmo */
 | |
|     GamepadButton *reset_button = GetGyroResetButton(ctx);
 | |
|     if (reset_button) {
 | |
|         SDL_FRect reset_area;
 | |
|         GetGamepadButtonArea(reset_button, &reset_area);
 | |
|         /* Position the reset button below the gizmo */
 | |
|         reset_area.x = btnArea.x;
 | |
|         reset_area.y = gizmoRect.y + gizmoRect.h + BUTTON_PADDING * 0.5f;
 | |
|         reset_area.w = btnArea.w;
 | |
|         reset_area.h = btnArea.h;
 | |
|         SetGamepadButtonArea(reset_button, &reset_area);
 | |
|         RenderGamepadButton(reset_button);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void RenderGyroDisplay(GyroDisplay *ctx, GamepadDisplay *gamepadElements, SDL_Gamepad *gamepad)
 | |
| {
 | |
|     if (!ctx)
 | |
|         return;
 | |
| 
 | |
|     bool bHasAccelerometer = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL);
 | |
|     bool bHasGyroscope = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO);
 | |
|     bool bHasIMU = bHasAccelerometer || bHasGyroscope;
 | |
|     if (!bHasIMU)
 | |
|         return;
 | |
| 
 | |
|     Uint8 r, g, b, a;
 | |
|     SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a);
 | |
| 
 | |
|     RenderSensorTimingInfo(ctx, gamepadElements);
 | |
|     RenderGyroDriftCalibrationButton(ctx, gamepadElements);
 | |
| 
 | |
|     /* Render Gyro calibration phases */
 | |
|     if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_COMPLETE) {
 | |
|         float bottom = RenderEulerReadout(ctx, gamepadElements);
 | |
|         RenderGyroGizmo(ctx, gamepad, bottom);
 | |
|     }
 | |
|     SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
 | |
| }
 | |
| 
 | |
| void DestroyGyroDisplay(GyroDisplay *ctx)
 | |
| {
 | |
|     if (!ctx) {
 | |
|         return;
 | |
|     }
 | |
|     DestroyGamepadButton(ctx->reset_gyro_button);
 | |
|     DestroyGamepadButton(ctx->calibrate_gyro_button);
 | |
|     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_png, gamepad_button_small_png_len);
 | |
|         SDL_GetTextureSize(ctx->button_texture, &ctx->button_width, &ctx->button_height);
 | |
| 
 | |
|         ctx->arrow_texture = CreateTexture(renderer, gamepad_axis_arrow_png, gamepad_axis_arrow_png_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_png, gamepad_button_background_png_len);
 | |
|         SDL_GetTextureSize(ctx->background, &ctx->background_width, &ctx->background_height);
 | |
| 
 | |
|         SetGamepadButtonLabel(ctx, label);
 | |
|     }
 | |
|     return ctx;
 | |
| }
 | |
| 
 | |
| void SetGamepadButtonLabel(GamepadButton *ctx, const char *label)
 | |
| {
 | |
|     if (!ctx) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (ctx->label) {
 | |
|         SDL_free(ctx->label);
 | |
|     }
 | |
| 
 | |
|     ctx->label = SDL_strdup(label);
 | |
|     ctx->label_width = (float)(FONT_CHARACTER_SIZE * SDL_strlen(label));
 | |
|     ctx->label_height = (float)FONT_CHARACTER_SIZE;
 | |
| }
 | |
| 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("face", sort_order, parts->num_elements, true);
 | |
|     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";
 | |
|     case SDL_GAMEPAD_TYPE_GAMECUBE:
 | |
|         return "GameCube";
 | |
|     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;
 | |
|     }
 | |
| }
 |