Atlas - gamepadutils.c
Home / ext / SDL / test Lines: 1 | Size: 122999 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 3 4 This software is provided 'as-is', without any express or implied 5 warranty. In no event will the authors be held liable for any damages 6 arising from the use of this software. 7 8 Permission is granted to anyone to use this software for any purpose, 9 including commercial applications, and to alter it and redistribute it 10 freely. 11*/ 12#include <SDL3/SDL.h> 13#include <SDL3/SDL_test_font.h> 14 15#include "gamepadutils.h" 16#include "gamepad_front.h" 17#include "gamepad_back.h" 18#include "gamepad_face_abxy.h" 19#include "gamepad_face_axby.h" 20#include "gamepad_face_bayx.h" 21#include "gamepad_face_sony.h" 22#include "gamepad_battery.h" 23#include "gamepad_battery_wired.h" 24#include "gamepad_touchpad.h" 25#include "gamepad_button.h" 26#include "gamepad_button_small.h" 27#include "gamepad_axis.h" 28#include "gamepad_axis_arrow.h" 29#include "gamepad_button_background.h" 30#include "gamepad_wired.h" 31#include "gamepad_wireless.h" 32 33#include <limits.h> 34 35#define RAD_TO_DEG (180.0f / SDL_PI_F) 36 37/* Used to draw a 3D cube to represent the gyroscope orientation */ 38typedef struct 39{ 40 float x, y, z; 41} Vector3; 42 43struct Quaternion 44{ 45 float x, y, z, w; 46}; 47 48static const Vector3 debug_cube_vertices[] = { 49 { -1.0f, -1.0f, -1.0f }, 50 { 1.0f, -1.0f, -1.0f }, 51 { 1.0f, 1.0f, -1.0f }, 52 { -1.0f, 1.0f, -1.0f }, 53 { -1.0f, -1.0f, 1.0f }, 54 { 1.0f, -1.0f, 1.0f }, 55 { 1.0f, 1.0f, 1.0f }, 56 { -1.0f, 1.0f, 1.0f }, 57}; 58 59static const int debug_cube_edges[][2] = { 60 { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 0 }, /* bottom square */ 61 { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 4 }, /* top square */ 62 { 0, 4 }, { 1, 5 }, { 2, 6 }, { 3, 7 }, /* verticals */ 63}; 64 65static Vector3 RotateVectorByQuaternion(const Vector3 *v, const Quaternion *q) { 66 /* v' = q * v * q^-1 */ 67 float x = v->x, y = v->y, z = v->z; 68 float qx = q->x, qy = q->y, qz = q->z, qw = q->w; 69 70 /* Calculate quaternion *vector */ 71 float ix = qw * x + qy * z - qz * y; 72 float iy = qw * y + qz * x - qx * z; 73 float iz = qw * z + qx * y - qy * x; 74 float iw = -qx * x - qy * y - qz * z; 75 76 /* Result = result * conjugate(q) */ 77 Vector3 out; 78 out.x = ix * qw + iw * -qx + iy * -qz - iz * -qy; 79 out.y = iy * qw + iw * -qy + iz * -qx - ix * -qz; 80 out.z = iz * qw + iw * -qz + ix * -qy - iy * -qx; 81 return out; 82} 83 84#ifdef GYRO_ISOMETRIC_PROJECTION 85static SDL_FPoint ProjectVec3ToRect(const Vector3 *v, const SDL_FRect *rect) 86{ 87 SDL_FPoint out; 88 /* Simple orthographic projection using X and Y; scale to fit into rect */ 89 out.x = rect->x + (rect->w / 2.0f) + (v->x * (rect->w / 2.0f)); 90 out.y = rect->y + (rect->h / 2.0f) - (v->y * (rect->h / 2.0f)); /* Y inverted */ 91 return out; 92} 93#else 94static SDL_FPoint ProjectVec3ToRect(const Vector3 *v, const SDL_FRect *rect) 95{ 96 const float verticalFOV_deg = 40.0f; 97 const float cameraZ = 4.0f; /* Camera is at(0, 0, +4), looking toward origin */ 98 float aspect = rect->w / rect->h; 99 100 float fovScaleY = SDL_tanf(((verticalFOV_deg / 180.0f) * SDL_PI_F) * 0.5f); 101 float fovScaleX = fovScaleY * aspect; 102 103 float relZ = cameraZ - v->z; 104 if (relZ < 0.01f) { 105 relZ = 0.01f; /* Prevent division by 0 or negative depth */ 106 } 107 108 float ndc_x = (v->x / relZ) / fovScaleX; 109 float ndc_y = (v->y / relZ) / fovScaleY; 110 111 /* Convert to screen space */ 112 SDL_FPoint out; 113 out.x = rect->x + (rect->w / 2.0f) + (ndc_x * rect->w / 2.0f); 114 out.y = rect->y + (rect->h / 2.0f) - (ndc_y * rect->h / 2.0f); /* flip Y */ 115 return out; 116} 117#endif 118 119void DrawGyroDebugCube(SDL_Renderer *renderer, const Quaternion *orientation, const SDL_FRect *rect) 120{ 121 SDL_FPoint projected[8]; 122 int i; 123 for (i = 0; i < 8; ++i) { 124 Vector3 rotated = RotateVectorByQuaternion(&debug_cube_vertices[i], orientation); 125 projected[i] = ProjectVec3ToRect(&rotated, rect); 126 } 127 128 for (i = 0; i < 12; ++i) { 129 const SDL_FPoint p0 = projected[debug_cube_edges[i][0]]; 130 const SDL_FPoint p1 = projected[debug_cube_edges[i][1]]; 131 SDL_RenderLine(renderer, p0.x, p0.y, p1.x, p1.y); 132 } 133} 134 135#define CIRCLE_SEGMENTS 64 136 137static Vector3 kCirclePoints3D_XY_Plane[CIRCLE_SEGMENTS]; 138static Vector3 kCirclePoints3D_XZ_Plane[CIRCLE_SEGMENTS]; 139static Vector3 kCirclePoints3D_YZ_Plane[CIRCLE_SEGMENTS]; 140 141void InitCirclePoints3D(void) 142{ 143 int i; 144 for (i = 0; i < CIRCLE_SEGMENTS; ++i) { 145 float theta = ((float)i / CIRCLE_SEGMENTS) * SDL_PI_F * 2.0f; 146 kCirclePoints3D_XY_Plane[i].x = SDL_cosf(theta); 147 kCirclePoints3D_XY_Plane[i].y = SDL_sinf(theta); 148 kCirclePoints3D_XY_Plane[i].z = 0.0f; 149 } 150 151 for (i = 0; i < CIRCLE_SEGMENTS; ++i) { 152 float theta = ((float)i / CIRCLE_SEGMENTS) * SDL_PI_F * 2.0f; 153 kCirclePoints3D_XZ_Plane[i].x = SDL_cosf(theta); 154 kCirclePoints3D_XZ_Plane[i].y = 0.0f; 155 kCirclePoints3D_XZ_Plane[i].z = SDL_sinf(theta); 156 } 157 158 for (i = 0; i < CIRCLE_SEGMENTS; ++i) { 159 float theta = ((float)i / CIRCLE_SEGMENTS) * SDL_PI_F * 2.0f; 160 kCirclePoints3D_YZ_Plane[i].x = 0.0f; 161 kCirclePoints3D_YZ_Plane[i].y = SDL_cosf(theta); 162 kCirclePoints3D_YZ_Plane[i].z = SDL_sinf(theta); 163 } 164} 165 166void DrawGyroCircle( 167 SDL_Renderer *renderer, 168 const Vector3 *circlePoints, 169 int numSegments, 170 const Quaternion *orientation, 171 const SDL_FRect *bounds, 172 Uint8 r, Uint8 g, Uint8 b, Uint8 a) 173{ 174 SDL_SetRenderDrawColor(renderer, r, g, b, a); 175 176 SDL_FPoint lastScreenPt = { 0 }; 177 bool hasLast = false; 178 int i; 179 for (i = 0; i <= numSegments; ++i) { 180 int index = i % numSegments; 181 182 Vector3 rotated = RotateVectorByQuaternion(&circlePoints[index], orientation); 183 SDL_FPoint screenPtVec2 = ProjectVec3ToRect(&rotated, bounds); 184 SDL_FPoint screenPt; 185 screenPt.x = screenPtVec2.x; 186 screenPt.y = screenPtVec2.y; 187 188 189 if (hasLast) { 190 SDL_RenderLine(renderer, lastScreenPt.x, lastScreenPt.y, screenPt.x, screenPt.y); 191 } 192 193 lastScreenPt = screenPt; 194 hasLast = true; 195 } 196} 197 198void DrawGyroDebugCircle(SDL_Renderer *renderer, const Quaternion *orientation, const SDL_FRect *bounds) 199{ 200 /* Store current color */ 201 Uint8 r, g, b, a; 202 SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); 203 DrawGyroCircle(renderer, kCirclePoints3D_YZ_Plane, CIRCLE_SEGMENTS, orientation, bounds, GYRO_COLOR_RED); /* X axis - pitch */ 204 DrawGyroCircle(renderer, kCirclePoints3D_XZ_Plane, CIRCLE_SEGMENTS, orientation, bounds, GYRO_COLOR_GREEN); /* Y axis - yaw */ 205 DrawGyroCircle(renderer, kCirclePoints3D_XY_Plane, CIRCLE_SEGMENTS, orientation, bounds, GYRO_COLOR_BLUE); /* Z axis - Roll */ 206 207 /* Restore current color */ 208 SDL_SetRenderDrawColor(renderer, r, g, b, a); 209} 210 211 212void DrawGyroDebugAxes(SDL_Renderer *renderer, const Quaternion *orientation, const SDL_FRect *bounds) 213{ 214 /* Store current color */ 215 Uint8 r, g, b, a; 216 SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); 217 218 Vector3 origin = { 0.0f, 0.0f, 0.0f }; 219 220 Vector3 right = { 1.0f, 0.0f, 0.0f }; 221 Vector3 up = { 0.0f, 1.0f, 0.0f }; 222 Vector3 back = { 0.0f, 0.0f, 1.0f }; 223 224 Vector3 world_right = RotateVectorByQuaternion(&right, orientation); 225 Vector3 world_up = RotateVectorByQuaternion(&up, orientation); 226 Vector3 world_back = RotateVectorByQuaternion(&back, orientation); 227 228 SDL_FPoint origin_screen = ProjectVec3ToRect(&origin, bounds); 229 SDL_FPoint right_screen = ProjectVec3ToRect(&world_right, bounds); 230 SDL_FPoint up_screen = ProjectVec3ToRect(&world_up, bounds); 231 SDL_FPoint back_screen = ProjectVec3ToRect(&world_back, bounds); 232 233 SDL_SetRenderDrawColor(renderer, GYRO_COLOR_RED); 234 SDL_RenderLine(renderer, origin_screen.x, origin_screen.y, right_screen.x, right_screen.y); 235 SDL_SetRenderDrawColor(renderer, GYRO_COLOR_GREEN); 236 SDL_RenderLine(renderer, origin_screen.x, origin_screen.y, up_screen.x, up_screen.y); 237 SDL_SetRenderDrawColor(renderer, GYRO_COLOR_BLUE); 238 SDL_RenderLine(renderer, origin_screen.x, origin_screen.y, back_screen.x, back_screen.y); 239 240 /* Restore current color */ 241 SDL_SetRenderDrawColor(renderer, r, g, b, a); 242} 243 244void DrawAccelerometerDebugArrow(SDL_Renderer *renderer, const Quaternion *gyro_quaternion, const float *accel_data, const SDL_FRect *bounds) 245{ 246 /* Store current color */ 247 Uint8 r, g, b, a; 248 SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); 249 250 const float flGravity = 9.81f; 251 Vector3 vAccel; 252 vAccel.x = accel_data[0] / flGravity; 253 vAccel.y = accel_data[1] / flGravity; 254 vAccel.z = accel_data[2] / flGravity; 255 256 Vector3 origin = { 0.0f, 0.0f, 0.0f }; 257 Vector3 rotated_accel = RotateVectorByQuaternion(&vAccel, gyro_quaternion); 258 259 /* Project the origin and rotated vector to screen space */ 260 SDL_FPoint origin_screen = ProjectVec3ToRect(&origin, bounds); 261 SDL_FPoint accel_screen = ProjectVec3ToRect(&rotated_accel, bounds); 262 263 /* Draw the line from origin to the rotated accelerometer vector */ 264 SDL_SetRenderDrawColor(renderer, GYRO_COLOR_ORANGE); 265 SDL_RenderLine(renderer, origin_screen.x, origin_screen.y, accel_screen.x, accel_screen.y); 266 267 const float head_width = 4.0f; 268 SDL_FRect arrow_head_rect; 269 arrow_head_rect.x = accel_screen.x - head_width * 0.5f; 270 arrow_head_rect.y = accel_screen.y - head_width * 0.5f; 271 arrow_head_rect.w = head_width; 272 arrow_head_rect.h = head_width; 273 SDL_RenderRect(renderer, &arrow_head_rect); 274 275 /* Restore current color */ 276 SDL_SetRenderDrawColor(renderer, r, g, b, a); 277} 278 279/* This is indexed by gamepad element */ 280static const struct 281{ 282 int x; 283 int y; 284} button_positions[] = { 285 { 413, 190 }, /* SDL_GAMEPAD_BUTTON_SOUTH */ 286 { 456, 156 }, /* SDL_GAMEPAD_BUTTON_EAST */ 287 { 372, 159 }, /* SDL_GAMEPAD_BUTTON_WEST */ 288 { 415, 127 }, /* SDL_GAMEPAD_BUTTON_NORTH */ 289 { 199, 157 }, /* SDL_GAMEPAD_BUTTON_BACK */ 290 { 257, 153 }, /* SDL_GAMEPAD_BUTTON_GUIDE */ 291 { 314, 157 }, /* SDL_GAMEPAD_BUTTON_START */ 292 { 98, 177 }, /* SDL_GAMEPAD_BUTTON_LEFT_STICK */ 293 { 331, 254 }, /* SDL_GAMEPAD_BUTTON_RIGHT_STICK */ 294 { 102, 65 }, /* SDL_GAMEPAD_BUTTON_LEFT_SHOULDER */ 295 { 421, 61 }, /* SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER */ 296 { 179, 213 }, /* SDL_GAMEPAD_BUTTON_DPAD_UP */ 297 { 179, 274 }, /* SDL_GAMEPAD_BUTTON_DPAD_DOWN */ 298 { 141, 242 }, /* SDL_GAMEPAD_BUTTON_DPAD_LEFT */ 299 { 211, 242 }, /* SDL_GAMEPAD_BUTTON_DPAD_RIGHT */ 300 { 257, 199 }, /* SDL_GAMEPAD_BUTTON_MISC1 */ 301 { 157, 160 }, /* SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 */ 302 { 355, 160 }, /* SDL_GAMEPAD_BUTTON_LEFT_PADDLE1 */ 303 { 157, 200 }, /* SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2 */ 304 { 355, 200 }, /* SDL_GAMEPAD_BUTTON_LEFT_PADDLE2 */ 305}; 306 307/* This is indexed by gamepad element */ 308static const struct 309{ 310 int x; 311 int y; 312 double angle; 313} axis_positions[] = { 314 { 99, 178, 270.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE */ 315 { 99, 178, 90.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE */ 316 { 99, 178, 0.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE */ 317 { 99, 178, 180.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE */ 318 { 331, 256, 270.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE */ 319 { 331, 256, 90.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE */ 320 { 331, 256, 0.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE */ 321 { 331, 256, 180.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE */ 322 { 116, 5, 180.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER */ 323 { 400, 5, 180.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER */ 324}; 325 326static SDL_FRect touchpad_area = { 327 148.0f, 20.0f, 216.0f, 118.0f 328}; 329 330typedef struct 331{ 332 bool down; 333 float x; 334 float y; 335 float pressure; 336} GamepadTouchpadFinger; 337 338struct GamepadImage 339{ 340 SDL_Renderer *renderer; 341 SDL_Texture *front_texture; 342 SDL_Texture *back_texture; 343 SDL_Texture *face_abxy_texture; 344 SDL_Texture *face_axby_texture; 345 SDL_Texture *face_bayx_texture; 346 SDL_Texture *face_sony_texture; 347 SDL_Texture *connection_texture[2]; 348 SDL_Texture *battery_texture[2]; 349 SDL_Texture *touchpad_texture; 350 SDL_Texture *button_texture; 351 SDL_Texture *axis_texture; 352 float gamepad_width; 353 float gamepad_height; 354 float face_width; 355 float face_height; 356 float connection_width; 357 float connection_height; 358 float battery_width; 359 float battery_height; 360 float touchpad_width; 361 float touchpad_height; 362 float button_width; 363 float button_height; 364 float axis_width; 365 float axis_height; 366 367 float x; 368 float y; 369 bool showing_front; 370 bool showing_touchpad; 371 SDL_GamepadType type; 372 SDL_GamepadButtonLabel east_label; 373 ControllerDisplayMode display_mode; 374 375 bool elements[SDL_GAMEPAD_ELEMENT_MAX]; 376 377 SDL_JoystickConnectionState connection_state; 378 SDL_PowerState battery_state; 379 int battery_percent; 380 381 int num_fingers; 382 GamepadTouchpadFinger *fingers; 383}; 384 385static SDL_Texture *CreateTexture(SDL_Renderer *renderer, unsigned char *data, unsigned int len) 386{ 387 SDL_Texture *texture = NULL; 388 SDL_Surface *surface; 389 SDL_IOStream *src = SDL_IOFromConstMem(data, len); 390 if (src) { 391 surface = SDL_LoadPNG_IO(src, true); 392 if (surface) { 393 texture = SDL_CreateTextureFromSurface(renderer, surface); 394 SDL_DestroySurface(surface); 395 } 396 } 397 return texture; 398} 399 400GamepadImage *CreateGamepadImage(SDL_Renderer *renderer) 401{ 402 GamepadImage *ctx = SDL_calloc(1, sizeof(*ctx)); 403 if (ctx) { 404 ctx->renderer = renderer; 405 ctx->front_texture = CreateTexture(renderer, gamepad_front_png, gamepad_front_png_len); 406 ctx->back_texture = CreateTexture(renderer, gamepad_back_png, gamepad_back_png_len); 407 SDL_GetTextureSize(ctx->front_texture, &ctx->gamepad_width, &ctx->gamepad_height); 408 409 ctx->face_abxy_texture = CreateTexture(renderer, gamepad_face_abxy_png, gamepad_face_abxy_png_len); 410 ctx->face_axby_texture = CreateTexture(renderer, gamepad_face_axby_png, gamepad_face_axby_png_len); 411 ctx->face_bayx_texture = CreateTexture(renderer, gamepad_face_bayx_png, gamepad_face_bayx_png_len); 412 ctx->face_sony_texture = CreateTexture(renderer, gamepad_face_sony_png, gamepad_face_sony_png_len); 413 SDL_GetTextureSize(ctx->face_abxy_texture, &ctx->face_width, &ctx->face_height); 414 415 ctx->connection_texture[0] = CreateTexture(renderer, gamepad_wired_png, gamepad_wired_png_len); 416 ctx->connection_texture[1] = CreateTexture(renderer, gamepad_wireless_png, gamepad_wireless_png_len); 417 SDL_GetTextureSize(ctx->connection_texture[0], &ctx->connection_width, &ctx->connection_height); 418 419 ctx->battery_texture[0] = CreateTexture(renderer, gamepad_battery_png, gamepad_battery_png_len); 420 ctx->battery_texture[1] = CreateTexture(renderer, gamepad_battery_wired_png, gamepad_battery_wired_png_len); 421 SDL_GetTextureSize(ctx->battery_texture[0], &ctx->battery_width, &ctx->battery_height); 422 423 ctx->touchpad_texture = CreateTexture(renderer, gamepad_touchpad_png, gamepad_touchpad_png_len); 424 SDL_GetTextureSize(ctx->touchpad_texture, &ctx->touchpad_width, &ctx->touchpad_height); 425 426 ctx->button_texture = CreateTexture(renderer, gamepad_button_png, gamepad_button_png_len); 427 SDL_GetTextureSize(ctx->button_texture, &ctx->button_width, &ctx->button_height); 428 SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); 429 430 ctx->axis_texture = CreateTexture(renderer, gamepad_axis_png, gamepad_axis_png_len); 431 SDL_GetTextureSize(ctx->axis_texture, &ctx->axis_width, &ctx->axis_height); 432 SDL_SetTextureColorMod(ctx->axis_texture, 10, 255, 21); 433 434 ctx->showing_front = true; 435 } 436 return ctx; 437} 438 439void SetGamepadImagePosition(GamepadImage *ctx, float x, float y) 440{ 441 if (!ctx) { 442 return; 443 } 444 445 ctx->x = x; 446 ctx->y = y; 447} 448 449void GetGamepadImageArea(GamepadImage *ctx, SDL_FRect *area) 450{ 451 if (!ctx) { 452 SDL_zerop(area); 453 return; 454 } 455 456 area->x = ctx->x; 457 area->y = ctx->y; 458 area->w = ctx->gamepad_width; 459 area->h = ctx->gamepad_height; 460 if (ctx->showing_touchpad) { 461 area->h += ctx->touchpad_height; 462 } 463} 464 465void GetGamepadTouchpadArea(GamepadImage *ctx, SDL_FRect *area) 466{ 467 if (!ctx) { 468 SDL_zerop(area); 469 return; 470 } 471 472 area->x = ctx->x + (ctx->gamepad_width - ctx->touchpad_width) / 2 + touchpad_area.x; 473 area->y = ctx->y + ctx->gamepad_height + touchpad_area.y; 474 area->w = touchpad_area.w; 475 area->h = touchpad_area.h; 476} 477 478void SetGamepadImageShowingFront(GamepadImage *ctx, bool showing_front) 479{ 480 if (!ctx) { 481 return; 482 } 483 484 ctx->showing_front = showing_front; 485} 486 487SDL_GamepadType GetGamepadImageType(GamepadImage *ctx) 488{ 489 if (!ctx) { 490 return SDL_GAMEPAD_TYPE_UNKNOWN; 491 } 492 493 return ctx->type; 494} 495 496void SetGamepadImageDisplayMode(GamepadImage *ctx, ControllerDisplayMode display_mode) 497{ 498 if (!ctx) { 499 return; 500 } 501 502 ctx->display_mode = display_mode; 503} 504 505float GetGamepadImageButtonWidth(GamepadImage *ctx) 506{ 507 if (!ctx) { 508 return 0; 509 } 510 511 return ctx->button_width; 512} 513 514float GetGamepadImageButtonHeight(GamepadImage *ctx) 515{ 516 if (!ctx) { 517 return 0; 518 } 519 520 return ctx->button_height; 521} 522 523float GetGamepadImageAxisWidth(GamepadImage *ctx) 524{ 525 if (!ctx) { 526 return 0; 527 } 528 529 return ctx->axis_width; 530} 531 532float GetGamepadImageAxisHeight(GamepadImage *ctx) 533{ 534 if (!ctx) { 535 return 0; 536 } 537 538 return ctx->axis_height; 539} 540 541int GetGamepadImageElementAt(GamepadImage *ctx, float x, float y) 542{ 543 SDL_FPoint point; 544 int i; 545 546 if (!ctx) { 547 return SDL_GAMEPAD_ELEMENT_INVALID; 548 } 549 550 point.x = x; 551 point.y = y; 552 553 if (ctx->showing_front) { 554 for (i = 0; i < SDL_arraysize(axis_positions); ++i) { 555 const int element = SDL_GAMEPAD_BUTTON_COUNT + i; 556 SDL_FRect rect; 557 558 if (element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER || 559 element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER) { 560 rect.w = ctx->axis_width; 561 rect.h = ctx->axis_height; 562 rect.x = ctx->x + axis_positions[i].x - rect.w / 2; 563 rect.y = ctx->y + axis_positions[i].y - rect.h / 2; 564 if (SDL_PointInRectFloat(&point, &rect)) { 565 if (element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER) { 566 return SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER; 567 } else { 568 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER; 569 } 570 } 571 } else if (element == SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE) { 572 rect.w = ctx->button_width * 2.0f; 573 rect.h = ctx->button_height * 2.0f; 574 rect.x = ctx->x + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].x - rect.w / 2; 575 rect.y = ctx->y + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].y - rect.h / 2; 576 if (SDL_PointInRectFloat(&point, &rect)) { 577 float delta_x, delta_y; 578 float delta_squared; 579 float thumbstick_radius = ctx->button_width * 0.1f; 580 581 delta_x = (x - (ctx->x + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].x)); 582 delta_y = (y - (ctx->y + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].y)); 583 delta_squared = (delta_x * delta_x) + (delta_y * delta_y); 584 if (delta_squared > (thumbstick_radius * thumbstick_radius)) { 585 float angle = SDL_atan2f(delta_y, delta_x) + SDL_PI_F; 586 if (angle < SDL_PI_F * 0.25f) { 587 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE; 588 } else if (angle < SDL_PI_F * 0.75f) { 589 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE; 590 } else if (angle < SDL_PI_F * 1.25f) { 591 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE; 592 } else if (angle < SDL_PI_F * 1.75f) { 593 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE; 594 } else { 595 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE; 596 } 597 } 598 } 599 } else if (element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE) { 600 rect.w = ctx->button_width * 2.0f; 601 rect.h = ctx->button_height * 2.0f; 602 rect.x = ctx->x + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].x - rect.w / 2; 603 rect.y = ctx->y + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].y - rect.h / 2; 604 if (SDL_PointInRectFloat(&point, &rect)) { 605 float delta_x, delta_y; 606 float delta_squared; 607 float thumbstick_radius = ctx->button_width * 0.1f; 608 609 delta_x = (x - (ctx->x + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].x)); 610 delta_y = (y - (ctx->y + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].y)); 611 delta_squared = (delta_x * delta_x) + (delta_y * delta_y); 612 if (delta_squared > (thumbstick_radius * thumbstick_radius)) { 613 float angle = SDL_atan2f(delta_y, delta_x) + SDL_PI_F; 614 if (angle < SDL_PI_F * 0.25f) { 615 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE; 616 } else if (angle < SDL_PI_F * 0.75f) { 617 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE; 618 } else if (angle < SDL_PI_F * 1.25f) { 619 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE; 620 } else if (angle < SDL_PI_F * 1.75f) { 621 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE; 622 } else { 623 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE; 624 } 625 } 626 } 627 } 628 } 629 } 630 631 for (i = 0; i < SDL_arraysize(button_positions); ++i) { 632 bool on_front = true; 633 634 if (i >= SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 && i <= SDL_GAMEPAD_BUTTON_LEFT_PADDLE2) { 635 on_front = false; 636 } 637 if (on_front == ctx->showing_front) { 638 SDL_FRect rect; 639 rect.x = ctx->x + button_positions[i].x - ctx->button_width / 2; 640 rect.y = ctx->y + button_positions[i].y - ctx->button_height / 2; 641 rect.w = ctx->button_width; 642 rect.h = ctx->button_height; 643 if (SDL_PointInRectFloat(&point, &rect)) { 644 return (SDL_GamepadButton)i; 645 } 646 } 647 } 648 return SDL_GAMEPAD_ELEMENT_INVALID; 649} 650 651void ClearGamepadImage(GamepadImage *ctx) 652{ 653 if (!ctx) { 654 return; 655 } 656 657 SDL_zeroa(ctx->elements); 658} 659 660void SetGamepadImageElement(GamepadImage *ctx, int element, bool active) 661{ 662 if (!ctx) { 663 return; 664 } 665 666 ctx->elements[element] = active; 667} 668 669void UpdateGamepadImageFromGamepad(GamepadImage *ctx, SDL_Gamepad *gamepad) 670{ 671 int i; 672 673 if (!ctx) { 674 return; 675 } 676 677 ctx->type = SDL_GetGamepadType(gamepad); 678 ctx->east_label = SDL_GetGamepadButtonLabel(gamepad, SDL_GAMEPAD_BUTTON_EAST); 679 char *mapping = SDL_GetGamepadMapping(gamepad); 680 if (mapping) { 681 if (SDL_strstr(mapping, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS")) { 682 /* Just for display purposes */ 683 ctx->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO; 684 } 685 SDL_free(mapping); 686 } 687 688 for (i = 0; i < SDL_GAMEPAD_BUTTON_TOUCHPAD; ++i) { 689 const SDL_GamepadButton button = (SDL_GamepadButton)i; 690 691 SetGamepadImageElement(ctx, button, SDL_GetGamepadButton(gamepad, button)); 692 } 693 694 for (i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) { 695 const SDL_GamepadAxis axis = (SDL_GamepadAxis)i; 696 const Sint16 deadzone = 8000; /* !!! FIXME: real deadzone */ 697 const Sint16 value = SDL_GetGamepadAxis(gamepad, axis); 698 switch (i) { 699 case SDL_GAMEPAD_AXIS_LEFTX: 700 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE, (value < -deadzone)); 701 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE, (value > deadzone)); 702 break; 703 case SDL_GAMEPAD_AXIS_RIGHTX: 704 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE, (value < -deadzone)); 705 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE, (value > deadzone)); 706 break; 707 case SDL_GAMEPAD_AXIS_LEFTY: 708 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE, (value < -deadzone)); 709 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE, (value > deadzone)); 710 break; 711 case SDL_GAMEPAD_AXIS_RIGHTY: 712 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE, (value < -deadzone)); 713 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE, (value > deadzone)); 714 break; 715 case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: 716 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER, (value > deadzone)); 717 break; 718 case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: 719 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER, (value > deadzone)); 720 break; 721 default: 722 break; 723 } 724 } 725 726 ctx->connection_state = SDL_GetGamepadConnectionState(gamepad); 727 ctx->battery_state = SDL_GetGamepadPowerInfo(gamepad, &ctx->battery_percent); 728 729 if (SDL_GetNumGamepadTouchpads(gamepad) > 0) { 730 int num_fingers = SDL_GetNumGamepadTouchpadFingers(gamepad, 0); 731 if (num_fingers != ctx->num_fingers) { 732 GamepadTouchpadFinger *fingers = (GamepadTouchpadFinger *)SDL_realloc(ctx->fingers, num_fingers * sizeof(*fingers)); 733 if (fingers) { 734 ctx->fingers = fingers; 735 ctx->num_fingers = num_fingers; 736 } else { 737 num_fingers = SDL_min(ctx->num_fingers, num_fingers); 738 } 739 } 740 for (i = 0; i < num_fingers; ++i) { 741 GamepadTouchpadFinger *finger = &ctx->fingers[i]; 742 743 SDL_GetGamepadTouchpadFinger(gamepad, 0, i, &finger->down, &finger->x, &finger->y, &finger->pressure); 744 } 745 ctx->showing_touchpad = true; 746 } else { 747 if (ctx->fingers) { 748 SDL_free(ctx->fingers); 749 ctx->fingers = NULL; 750 ctx->num_fingers = 0; 751 } 752 ctx->showing_touchpad = false; 753 } 754} 755 756void RenderGamepadImage(GamepadImage *ctx) 757{ 758 SDL_FRect dst; 759 int i; 760 761 if (!ctx) { 762 return; 763 } 764 765 dst.x = ctx->x; 766 dst.y = ctx->y; 767 dst.w = ctx->gamepad_width; 768 dst.h = ctx->gamepad_height; 769 770 if (ctx->showing_front) { 771 SDL_RenderTexture(ctx->renderer, ctx->front_texture, NULL, &dst); 772 } else { 773 SDL_RenderTexture(ctx->renderer, ctx->back_texture, NULL, &dst); 774 } 775 776 for (i = 0; i < SDL_arraysize(button_positions); ++i) { 777 if (ctx->elements[i]) { 778 SDL_GamepadButton button_position = (SDL_GamepadButton)i; 779 bool on_front = true; 780 781 if (i >= SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 && i <= SDL_GAMEPAD_BUTTON_LEFT_PADDLE2) { 782 on_front = false; 783 } 784 if (on_front == ctx->showing_front) { 785 dst.w = ctx->button_width; 786 dst.h = ctx->button_height; 787 dst.x = ctx->x + button_positions[button_position].x - dst.w / 2; 788 dst.y = ctx->y + button_positions[button_position].y - dst.h / 2; 789 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); 790 } 791 } 792 } 793 794 if (ctx->showing_front) { 795 dst.x = ctx->x + 363; 796 dst.y = ctx->y + 118; 797 dst.w = ctx->face_width; 798 dst.h = ctx->face_height; 799 800 switch (ctx->east_label) { 801 case SDL_GAMEPAD_BUTTON_LABEL_B: 802 SDL_RenderTexture(ctx->renderer, ctx->face_abxy_texture, NULL, &dst); 803 break; 804 case SDL_GAMEPAD_BUTTON_LABEL_X: 805 SDL_RenderTexture(ctx->renderer, ctx->face_axby_texture, NULL, &dst); 806 break; 807 case SDL_GAMEPAD_BUTTON_LABEL_A: 808 SDL_RenderTexture(ctx->renderer, ctx->face_bayx_texture, NULL, &dst); 809 break; 810 case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: 811 SDL_RenderTexture(ctx->renderer, ctx->face_sony_texture, NULL, &dst); 812 break; 813 default: 814 break; 815 } 816 } 817 818 if (ctx->showing_front) { 819 for (i = 0; i < SDL_arraysize(axis_positions); ++i) { 820 const int element = SDL_GAMEPAD_BUTTON_COUNT + i; 821 if (ctx->elements[element]) { 822 const double angle = axis_positions[i].angle; 823 dst.w = ctx->axis_width; 824 dst.h = ctx->axis_height; 825 dst.x = ctx->x + axis_positions[i].x - dst.w / 2; 826 dst.y = ctx->y + axis_positions[i].y - dst.h / 2; 827 SDL_RenderTextureRotated(ctx->renderer, ctx->axis_texture, NULL, &dst, angle, NULL, SDL_FLIP_NONE); 828 } 829 } 830 } 831 832 if (ctx->display_mode == CONTROLLER_MODE_TESTING) { 833 dst.x = ctx->x + ctx->gamepad_width - ctx->battery_width - 4 - ctx->connection_width; 834 dst.y = ctx->y; 835 dst.w = ctx->connection_width; 836 dst.h = ctx->connection_height; 837 838 switch (ctx->connection_state) { 839 case SDL_JOYSTICK_CONNECTION_WIRED: 840 SDL_RenderTexture(ctx->renderer, ctx->connection_texture[0], NULL, &dst); 841 break; 842 case SDL_JOYSTICK_CONNECTION_WIRELESS: 843 SDL_RenderTexture(ctx->renderer, ctx->connection_texture[1], NULL, &dst); 844 break; 845 default: 846 break; 847 } 848 } 849 850 if (ctx->display_mode == CONTROLLER_MODE_TESTING && 851 ctx->battery_state != SDL_POWERSTATE_NO_BATTERY && 852 ctx->battery_state != SDL_POWERSTATE_UNKNOWN) { 853 Uint8 r, g, b, a; 854 SDL_FRect fill; 855 856 dst.x = ctx->x + ctx->gamepad_width - ctx->battery_width; 857 dst.y = ctx->y; 858 dst.w = ctx->battery_width; 859 dst.h = ctx->battery_height; 860 861 SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); 862 if (ctx->battery_percent > 40) { 863 SDL_SetRenderDrawColor(ctx->renderer, 0x00, 0xD4, 0x50, 0xFF); 864 } else if (ctx->battery_percent > 10) { 865 SDL_SetRenderDrawColor(ctx->renderer, 0xFF, 0xC7, 0x00, 0xFF); 866 } else { 867 SDL_SetRenderDrawColor(ctx->renderer, 0xC8, 0x1D, 0x13, 0xFF); 868 } 869 870 fill = dst; 871 fill.x += 2; 872 fill.y += 2; 873 fill.h -= 4; 874 fill.w = 25.0f * (ctx->battery_percent / 100.0f); 875 SDL_RenderFillRect(ctx->renderer, &fill); 876 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); 877 878 if (ctx->battery_state == SDL_POWERSTATE_ON_BATTERY) { 879 SDL_RenderTexture(ctx->renderer, ctx->battery_texture[0], NULL, &dst); 880 } else { 881 SDL_RenderTexture(ctx->renderer, ctx->battery_texture[1], NULL, &dst); 882 } 883 } 884 885 if (ctx->display_mode == CONTROLLER_MODE_TESTING && ctx->showing_touchpad) { 886 dst.x = ctx->x + (ctx->gamepad_width - ctx->touchpad_width) / 2; 887 dst.y = ctx->y + ctx->gamepad_height; 888 dst.w = ctx->touchpad_width; 889 dst.h = ctx->touchpad_height; 890 SDL_RenderTexture(ctx->renderer, ctx->touchpad_texture, NULL, &dst); 891 892 for (i = 0; i < ctx->num_fingers; ++i) { 893 GamepadTouchpadFinger *finger = &ctx->fingers[i]; 894 895 if (finger->down) { 896 dst.x = ctx->x + (ctx->gamepad_width - ctx->touchpad_width) / 2; 897 dst.x += touchpad_area.x + finger->x * touchpad_area.w; 898 dst.x -= ctx->button_width / 2; 899 dst.y = ctx->y + ctx->gamepad_height; 900 dst.y += touchpad_area.y + finger->y * touchpad_area.h; 901 dst.y -= ctx->button_height / 2; 902 dst.w = ctx->button_width; 903 dst.h = ctx->button_height; 904 SDL_SetTextureAlphaMod(ctx->button_texture, (Uint8)(finger->pressure * SDL_ALPHA_OPAQUE)); 905 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); 906 SDL_SetTextureAlphaMod(ctx->button_texture, SDL_ALPHA_OPAQUE); 907 } 908 } 909 } 910} 911 912void DestroyGamepadImage(GamepadImage *ctx) 913{ 914 if (ctx) { 915 int i; 916 917 SDL_DestroyTexture(ctx->front_texture); 918 SDL_DestroyTexture(ctx->back_texture); 919 SDL_DestroyTexture(ctx->face_abxy_texture); 920 SDL_DestroyTexture(ctx->face_axby_texture); 921 SDL_DestroyTexture(ctx->face_bayx_texture); 922 SDL_DestroyTexture(ctx->face_sony_texture); 923 for (i = 0; i < SDL_arraysize(ctx->battery_texture); ++i) { 924 SDL_DestroyTexture(ctx->battery_texture[i]); 925 } 926 SDL_DestroyTexture(ctx->touchpad_texture); 927 SDL_DestroyTexture(ctx->button_texture); 928 SDL_DestroyTexture(ctx->axis_texture); 929 SDL_free(ctx->fingers); 930 SDL_free(ctx); 931 } 932} 933 934static const char *gamepad_button_names[] = { 935 "South", 936 "East", 937 "West", 938 "North", 939 "Back", 940 "Guide", 941 "Start", 942 "Left Stick", 943 "Right Stick", 944 "Left Shoulder", 945 "Right Shoulder", 946 "DPAD Up", 947 "DPAD Down", 948 "DPAD Left", 949 "DPAD Right", 950 "Misc1", 951 "Right Paddle 1", 952 "Left Paddle 1", 953 "Right Paddle 2", 954 "Left Paddle 2", 955 "Touchpad", 956 "Misc2", 957 "Misc3", 958 "Misc4", 959 "Misc5", 960 "Misc6", 961}; 962SDL_COMPILE_TIME_ASSERT(gamepad_button_names, SDL_arraysize(gamepad_button_names) == SDL_GAMEPAD_BUTTON_COUNT); 963 964static const char *gamepad_axis_names[] = { 965 "LeftX", 966 "LeftY", 967 "RightX", 968 "RightY", 969 "Left Trigger", 970 "Right Trigger", 971}; 972SDL_COMPILE_TIME_ASSERT(gamepad_axis_names, SDL_arraysize(gamepad_axis_names) == SDL_GAMEPAD_AXIS_COUNT); 973 974struct GamepadDisplay 975{ 976 SDL_Renderer *renderer; 977 SDL_Texture *button_texture; 978 SDL_Texture *arrow_texture; 979 float button_width; 980 float button_height; 981 float arrow_width; 982 float arrow_height; 983 984 float accel_data[3]; 985 float gyro_data[3]; 986 float gyro_drift_correction_data[3]; 987 988 Uint64 last_sensor_update; 989 990 ControllerDisplayMode display_mode; 991 int element_highlighted; 992 bool element_pressed; 993 int element_selected; 994 995 SDL_FRect area; 996}; 997 998GamepadDisplay *CreateGamepadDisplay(SDL_Renderer *renderer) 999{ 1000 GamepadDisplay *ctx = SDL_calloc(1, sizeof(*ctx)); 1001 if (ctx) { 1002 ctx->renderer = renderer; 1003 1004 ctx->button_texture = CreateTexture(renderer, gamepad_button_small_png, gamepad_button_small_png_len); 1005 SDL_GetTextureSize(ctx->button_texture, &ctx->button_width, &ctx->button_height); 1006 1007 ctx->arrow_texture = CreateTexture(renderer, gamepad_axis_arrow_png, gamepad_axis_arrow_png_len); 1008 SDL_GetTextureSize(ctx->arrow_texture, &ctx->arrow_width, &ctx->arrow_height); 1009 1010 ctx->element_highlighted = SDL_GAMEPAD_ELEMENT_INVALID; 1011 ctx->element_selected = SDL_GAMEPAD_ELEMENT_INVALID; 1012 1013 SDL_zeroa(ctx->accel_data); 1014 SDL_zeroa(ctx->gyro_data); 1015 SDL_zeroa(ctx->gyro_drift_correction_data); 1016 } 1017 return ctx; 1018} 1019 1020struct GyroDisplay 1021{ 1022 SDL_Renderer *renderer; 1023 1024 /* Main drawing area */ 1025 SDL_FRect area; 1026 1027 /* This part displays extra info from the IMUstate in order to figure out actual polling rates. */ 1028 float gyro_drift_solution[3]; 1029 int reported_sensor_rate_hz; /*hz - comes from HIDsdl implementation. Could be fixed, platform time, or true sensor time*/ 1030 Uint64 next_reported_sensor_time; /* SDL ticks used to throttle the display */ 1031 1032 int estimated_sensor_rate_hz; /*hz - our estimation of the actual polling rate by observing packets received*/ 1033 float euler_displacement_angles[3]; /* pitch, yaw, roll */ 1034 Quaternion gyro_quaternion; /* Rotation since startup/reset, comprised of each gyro speed packet times sensor delta time. */ 1035 EGyroCalibrationPhase current_calibration_phase; 1036 float calibration_phase_progress_fraction; /* [0..1] */ 1037 float accelerometer_noise_sq; /* Distance between last noise and new noise. Used to indicate motion.*/ 1038 float accelerometer_noise_tolerance_sq; /* Maximum amount of noise detected during the Noise Profiling Phase */ 1039 1040 GamepadButton *reset_gyro_button; 1041 GamepadButton *calibrate_gyro_button; 1042}; 1043 1044GyroDisplay *CreateGyroDisplay(SDL_Renderer *renderer) 1045{ 1046 GyroDisplay *ctx = SDL_calloc(1, sizeof(*ctx)); 1047 { 1048 ctx->renderer = renderer; 1049 ctx->estimated_sensor_rate_hz = 0; 1050 SDL_zeroa(ctx->gyro_drift_solution); 1051 Quaternion quat_identity = { 0.0f, 0.0f, 0.0f, 1.0f }; 1052 ctx->gyro_quaternion = quat_identity; 1053 ctx->reported_sensor_rate_hz = 0; 1054 ctx->next_reported_sensor_time = 0; 1055 ctx->current_calibration_phase = GYRO_CALIBRATION_PHASE_OFF; 1056 ctx->calibration_phase_progress_fraction = 0.0f; /* [0..1] */ 1057 ctx->accelerometer_noise_sq = 0.0f; 1058 ctx->accelerometer_noise_tolerance_sq = ACCELEROMETER_NOISE_THRESHOLD; /* Will be overwritten but this avoids divide by zero. */ 1059 ctx->reset_gyro_button = CreateGamepadButton(renderer, "Reset View"); 1060 ctx->calibrate_gyro_button = CreateGamepadButton(renderer, "Recalibrate Drift"); 1061 } 1062 1063 return ctx; 1064} 1065 1066void SetGyroDisplayArea(GyroDisplay *ctx, const SDL_FRect *area) 1067{ 1068 if (!ctx) { 1069 return; 1070 } 1071 1072 SDL_copyp(&ctx->area, area); 1073 /* Place the reset button to the bottom right of the gyro display area.*/ 1074 SDL_FRect reset_button_area; 1075 reset_button_area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(ctx->reset_gyro_button) + 2 * BUTTON_PADDING); 1076 reset_button_area.h = GetGamepadButtonLabelHeight(ctx->reset_gyro_button) + BUTTON_PADDING; 1077 reset_button_area.x = area->x + area->w - reset_button_area.w - BUTTON_PADDING; 1078 reset_button_area.y = area->y + area->h - reset_button_area.h - BUTTON_PADDING; 1079 SetGamepadButtonArea(ctx->reset_gyro_button, &reset_button_area); 1080} 1081 1082void SetGamepadDisplayDisplayMode(GamepadDisplay *ctx, ControllerDisplayMode display_mode) 1083{ 1084 if (!ctx) { 1085 return; 1086 } 1087 1088 ctx->display_mode = display_mode; 1089} 1090 1091void SetGamepadDisplayArea(GamepadDisplay *ctx, const SDL_FRect *area) 1092{ 1093 if (!ctx) { 1094 return; 1095 } 1096 1097 SDL_copyp(&ctx->area, area); 1098} 1099void SetGamepadDisplayGyroDriftCorrection(GamepadDisplay *ctx, float *gyro_drift_correction) 1100{ 1101 if (!ctx) { 1102 return; 1103 } 1104 1105 ctx->gyro_drift_correction_data[0] = gyro_drift_correction[0]; 1106 ctx->gyro_drift_correction_data[1] = gyro_drift_correction[1]; 1107 ctx->gyro_drift_correction_data[2] = gyro_drift_correction[2]; 1108} 1109 1110static bool GetBindingString(const char *label, const char *mapping, char *text, size_t size) 1111{ 1112 char *key; 1113 char *value, *end; 1114 size_t length; 1115 bool found = false; 1116 1117 *text = '\0'; 1118 1119 if (!mapping) { 1120 return false; 1121 } 1122 1123 key = SDL_strstr(mapping, label); 1124 while (key && size > 1) { 1125 if (found) { 1126 *text++ = ','; 1127 *text = '\0'; 1128 --size; 1129 } else { 1130 found = true; 1131 } 1132 value = key + SDL_strlen(label); 1133 end = SDL_strchr(value, ','); 1134 if (end) { 1135 length = (end - value); 1136 } else { 1137 length = SDL_strlen(value); 1138 } 1139 if (length >= size) { 1140 length = size - 1; 1141 } 1142 SDL_memcpy(text, value, length); 1143 text[length] = '\0'; 1144 text += length; 1145 size -= length; 1146 key = SDL_strstr(value, label); 1147 } 1148 return found; 1149} 1150 1151static bool GetButtonBindingString(SDL_GamepadButton button, const char *mapping, char *text, size_t size) 1152{ 1153 char label[32]; 1154 bool baxy_mapping = false; 1155 1156 if (!mapping) { 1157 return false; 1158 } 1159 1160 SDL_snprintf(label, sizeof(label), ",%s:", SDL_GetGamepadStringForButton(button)); 1161 if (GetBindingString(label, mapping, text, size)) { 1162 return true; 1163 } 1164 1165 /* Try the legacy button names */ 1166 if (SDL_strstr(mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) { 1167 baxy_mapping = true; 1168 } 1169 switch (button) { 1170 case SDL_GAMEPAD_BUTTON_SOUTH: 1171 if (baxy_mapping) { 1172 return GetBindingString(",b:", mapping, text, size); 1173 } else { 1174 return GetBindingString(",a:", mapping, text, size); 1175 } 1176 case SDL_GAMEPAD_BUTTON_EAST: 1177 if (baxy_mapping) { 1178 return GetBindingString(",a:", mapping, text, size); 1179 } else { 1180 return GetBindingString(",b:", mapping, text, size); 1181 } 1182 case SDL_GAMEPAD_BUTTON_WEST: 1183 if (baxy_mapping) { 1184 return GetBindingString(",y:", mapping, text, size); 1185 } else { 1186 return GetBindingString(",x:", mapping, text, size); 1187 } 1188 case SDL_GAMEPAD_BUTTON_NORTH: 1189 if (baxy_mapping) { 1190 return GetBindingString(",x:", mapping, text, size); 1191 } else { 1192 return GetBindingString(",y:", mapping, text, size); 1193 } 1194 default: 1195 return false; 1196 } 1197} 1198 1199static bool GetAxisBindingString(SDL_GamepadAxis axis, int direction, const char *mapping, char *text, size_t size) 1200{ 1201 char label[32]; 1202 1203 /* Check for explicit half-axis */ 1204 if (direction < 0) { 1205 SDL_snprintf(label, sizeof(label), ",-%s:", SDL_GetGamepadStringForAxis(axis)); 1206 } else { 1207 SDL_snprintf(label, sizeof(label), ",+%s:", SDL_GetGamepadStringForAxis(axis)); 1208 } 1209 if (GetBindingString(label, mapping, text, size)) { 1210 return true; 1211 } 1212 1213 /* Get the binding for the whole axis and split it if necessary */ 1214 SDL_snprintf(label, sizeof(label), ",%s:", SDL_GetGamepadStringForAxis(axis)); 1215 if (!GetBindingString(label, mapping, text, size)) { 1216 return false; 1217 } 1218 if (axis != SDL_GAMEPAD_AXIS_LEFT_TRIGGER && axis != SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { 1219 if (*text == 'a') { 1220 /* Split the axis */ 1221 size_t length = SDL_strlen(text) + 1; 1222 if ((length + 1) <= size) { 1223 SDL_memmove(text + 1, text, length); 1224 if (text[SDL_strlen(text) - 1] == '~') { 1225 direction *= -1; 1226 text[SDL_strlen(text) - 1] = '\0'; 1227 } 1228 if (direction > 0) { 1229 *text = '+'; 1230 } else { 1231 *text = '-'; 1232 } 1233 } 1234 } 1235 } 1236 return true; 1237} 1238 1239void SetGamepadDisplayHighlight(GamepadDisplay *ctx, int element, bool pressed) 1240{ 1241 if (!ctx) { 1242 return; 1243 } 1244 1245 ctx->element_highlighted = element; 1246 ctx->element_pressed = pressed; 1247} 1248 1249void SetGamepadDisplaySelected(GamepadDisplay *ctx, int element) 1250{ 1251 if (!ctx) { 1252 return; 1253 } 1254 1255 ctx->element_selected = element; 1256} 1257 1258int GetGamepadDisplayElementAt(GamepadDisplay *ctx, SDL_Gamepad *gamepad, float x, float y) 1259{ 1260 int i; 1261 const float margin = 8.0f; 1262 const float center = ctx->area.w / 2.0f; 1263 const float arrow_extent = 48.0f; 1264 SDL_FPoint point; 1265 SDL_FRect rect; 1266 1267 if (!ctx) { 1268 return SDL_GAMEPAD_ELEMENT_INVALID; 1269 } 1270 1271 point.x = x; 1272 point.y = y; 1273 1274 rect.x = ctx->area.x + margin; 1275 rect.y = ctx->area.y + margin + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; 1276 rect.w = ctx->area.w - (margin * 2); 1277 rect.h = ctx->button_height; 1278 1279 for (i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) { 1280 SDL_GamepadButton button = (SDL_GamepadButton)i; 1281 1282 if (ctx->display_mode == CONTROLLER_MODE_TESTING && 1283 !SDL_GamepadHasButton(gamepad, button)) { 1284 continue; 1285 } 1286 1287 1288 if (SDL_PointInRectFloat(&point, &rect)) { 1289 return i; 1290 } 1291 1292 rect.y += ctx->button_height + 2.0f; 1293 } 1294 1295 for (i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) { 1296 SDL_GamepadAxis axis = (SDL_GamepadAxis)i; 1297 SDL_FRect area; 1298 1299 if (ctx->display_mode == CONTROLLER_MODE_TESTING && 1300 !SDL_GamepadHasAxis(gamepad, axis)) { 1301 continue; 1302 } 1303 1304 area.x = rect.x + center + 2.0f; 1305 area.y = rect.y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; 1306 area.w = ctx->arrow_width + arrow_extent; 1307 area.h = ctx->button_height; 1308 1309 if (SDL_PointInRectFloat(&point, &area)) { 1310 switch (axis) { 1311 case SDL_GAMEPAD_AXIS_LEFTX: 1312 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE; 1313 case SDL_GAMEPAD_AXIS_LEFTY: 1314 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE; 1315 case SDL_GAMEPAD_AXIS_RIGHTX: 1316 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE; 1317 case SDL_GAMEPAD_AXIS_RIGHTY: 1318 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE; 1319 default: 1320 break; 1321 } 1322 } 1323 1324 area.x += area.w; 1325 1326 if (SDL_PointInRectFloat(&point, &area)) { 1327 switch (axis) { 1328 case SDL_GAMEPAD_AXIS_LEFTX: 1329 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE; 1330 case SDL_GAMEPAD_AXIS_LEFTY: 1331 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE; 1332 case SDL_GAMEPAD_AXIS_RIGHTX: 1333 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE; 1334 case SDL_GAMEPAD_AXIS_RIGHTY: 1335 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE; 1336 case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: 1337 return SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER; 1338 case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: 1339 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER; 1340 default: 1341 break; 1342 } 1343 } 1344 1345 rect.y += ctx->button_height + 2.0f; 1346 } 1347 return SDL_GAMEPAD_ELEMENT_INVALID; 1348} 1349 1350static void RenderGamepadElementHighlight(GamepadDisplay *ctx, int element, const SDL_FRect *area) 1351{ 1352 if (element == ctx->element_highlighted || element == ctx->element_selected) { 1353 Uint8 r, g, b, a; 1354 1355 SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); 1356 1357 if (element == ctx->element_highlighted) { 1358 if (ctx->element_pressed) { 1359 SDL_SetRenderDrawColor(ctx->renderer, PRESSED_COLOR); 1360 } else { 1361 SDL_SetRenderDrawColor(ctx->renderer, HIGHLIGHT_COLOR); 1362 } 1363 } else { 1364 SDL_SetRenderDrawColor(ctx->renderer, SELECTED_COLOR); 1365 } 1366 SDL_RenderFillRect(ctx->renderer, area); 1367 1368 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); 1369 } 1370} 1371 1372void 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) 1373{ 1374 if (!ctx) { 1375 return; 1376 } 1377 1378 const int SENSOR_UPDATE_INTERVAL_MS = 100; 1379 Uint64 now = SDL_GetTicks(); 1380 if (now > ctx->next_reported_sensor_time) { 1381 ctx->estimated_sensor_rate_hz = estimated_sensor_rate_hz; 1382 if (reported_senor_rate_hz != 0) { 1383 ctx->reported_sensor_rate_hz = reported_senor_rate_hz; 1384 } 1385 ctx->next_reported_sensor_time = now + SENSOR_UPDATE_INTERVAL_MS; 1386 } 1387 1388 SDL_memcpy(ctx->gyro_drift_solution, gyro_drift_solution, sizeof(ctx->gyro_drift_solution)); 1389 SDL_memcpy(ctx->euler_displacement_angles, euler_displacement_angles, sizeof(ctx->euler_displacement_angles)); 1390 ctx->gyro_quaternion = *gyro_quaternion; 1391 ctx->current_calibration_phase = calibration_phase; 1392 ctx->calibration_phase_progress_fraction = drift_calibration_progress_frac; 1393 ctx->accelerometer_noise_sq = accelerometer_noise_sq; 1394 ctx->accelerometer_noise_tolerance_sq = accelerometer_noise_tolerance_sq; 1395} 1396 1397extern GamepadButton *GetGyroResetButton(GyroDisplay *ctx) 1398{ 1399 if (!ctx) { 1400 return NULL; 1401 } 1402 return ctx->reset_gyro_button; 1403} 1404 1405extern GamepadButton *GetGyroCalibrateButton(GyroDisplay *ctx) 1406{ 1407 if (!ctx) { 1408 return NULL; 1409 } 1410 return ctx->calibrate_gyro_button; 1411} 1412 1413void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad) 1414{ 1415 float x, y; 1416 int i; 1417 char text[128], binding[32]; 1418 const float margin = 8.0f; 1419 const float center = ctx->area.w / 2.0f; 1420 const float arrow_extent = 48.0f; 1421 SDL_FRect dst, rect, highlight; 1422 Uint8 r, g, b, a; 1423 char *mapping; 1424 bool has_accel; 1425 bool has_gyro; 1426 1427 if (!ctx) { 1428 return; 1429 } 1430 1431 SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); 1432 1433 mapping = SDL_GetGamepadMapping(gamepad); 1434 1435 x = ctx->area.x + margin; 1436 y = ctx->area.y + margin; 1437 1438 for (i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) { 1439 SDL_GamepadButton button = (SDL_GamepadButton)i; 1440 1441 if (ctx->display_mode == CONTROLLER_MODE_TESTING && 1442 !SDL_GamepadHasButton(gamepad, button)) { 1443 continue; 1444 } 1445 1446 highlight.x = x; 1447 highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; 1448 highlight.w = ctx->area.w - (margin * 2); 1449 highlight.h = ctx->button_height; 1450 RenderGamepadElementHighlight(ctx, i, &highlight); 1451 1452 SDL_snprintf(text, sizeof(text), "%s:", gamepad_button_names[i]); 1453 SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); 1454 1455 if (SDL_GetGamepadButton(gamepad, button)) { 1456 SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); 1457 } else { 1458 SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); 1459 } 1460 1461 dst.x = x + center + 2.0f; 1462 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; 1463 dst.w = ctx->button_width; 1464 dst.h = ctx->button_height; 1465 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); 1466 1467 if (ctx->display_mode == CONTROLLER_MODE_BINDING) { 1468 if (GetButtonBindingString(button, mapping, binding, sizeof(binding))) { 1469 dst.x += dst.w + 2 * margin; 1470 SDLTest_DrawString(ctx->renderer, dst.x, y, binding); 1471 } 1472 } 1473 1474 y += ctx->button_height + 2.0f; 1475 } 1476 1477 for (i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) { 1478 SDL_GamepadAxis axis = (SDL_GamepadAxis)i; 1479 bool has_negative = (axis != SDL_GAMEPAD_AXIS_LEFT_TRIGGER && axis != SDL_GAMEPAD_AXIS_RIGHT_TRIGGER); 1480 Sint16 value; 1481 1482 if (ctx->display_mode == CONTROLLER_MODE_TESTING && 1483 !SDL_GamepadHasAxis(gamepad, axis)) { 1484 continue; 1485 } 1486 1487 value = SDL_GetGamepadAxis(gamepad, axis); 1488 1489 SDL_snprintf(text, sizeof(text), "%s:", gamepad_axis_names[i]); 1490 SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); 1491 1492 highlight.x = x + center + 2.0f; 1493 highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; 1494 highlight.w = ctx->arrow_width + arrow_extent; 1495 highlight.h = ctx->button_height; 1496 1497 switch (axis) { 1498 case SDL_GAMEPAD_AXIS_LEFTX: 1499 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE, &highlight); 1500 break; 1501 case SDL_GAMEPAD_AXIS_LEFTY: 1502 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE, &highlight); 1503 break; 1504 case SDL_GAMEPAD_AXIS_RIGHTX: 1505 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE, &highlight); 1506 break; 1507 case SDL_GAMEPAD_AXIS_RIGHTY: 1508 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE, &highlight); 1509 break; 1510 default: 1511 break; 1512 } 1513 1514 highlight.x += highlight.w; 1515 1516 switch (axis) { 1517 case SDL_GAMEPAD_AXIS_LEFTX: 1518 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE, &highlight); 1519 break; 1520 case SDL_GAMEPAD_AXIS_LEFTY: 1521 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE, &highlight); 1522 break; 1523 case SDL_GAMEPAD_AXIS_RIGHTX: 1524 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE, &highlight); 1525 break; 1526 case SDL_GAMEPAD_AXIS_RIGHTY: 1527 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE, &highlight); 1528 break; 1529 case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: 1530 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER, &highlight); 1531 break; 1532 case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: 1533 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER, &highlight); 1534 break; 1535 default: 1536 break; 1537 } 1538 1539 dst.x = x + center + 2.0f; 1540 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->arrow_height / 2; 1541 dst.w = ctx->arrow_width; 1542 dst.h = ctx->arrow_height; 1543 1544 if (has_negative) { 1545 if (value == SDL_MIN_SINT16) { 1546 SDL_SetTextureColorMod(ctx->arrow_texture, 10, 255, 21); 1547 } else { 1548 SDL_SetTextureColorMod(ctx->arrow_texture, 255, 255, 255); 1549 } 1550 SDL_RenderTextureRotated(ctx->renderer, ctx->arrow_texture, NULL, &dst, 0.0f, NULL, SDL_FLIP_HORIZONTAL); 1551 } 1552 1553 dst.x += ctx->arrow_width; 1554 1555 SDL_SetRenderDrawColor(ctx->renderer, 200, 200, 200, SDL_ALPHA_OPAQUE); 1556 rect.x = dst.x + arrow_extent - 2.0f; 1557 rect.y = dst.y; 1558 rect.w = 4.0f; 1559 rect.h = ctx->arrow_height; 1560 SDL_RenderFillRect(ctx->renderer, &rect); 1561 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); 1562 1563 if (value < 0) { 1564 SDL_SetRenderDrawColor(ctx->renderer, 8, 200, 16, SDL_ALPHA_OPAQUE); 1565 rect.w = ((float)value / SDL_MIN_SINT16) * arrow_extent; 1566 rect.x = dst.x + arrow_extent - rect.w; 1567 rect.y = dst.y + ctx->arrow_height * 0.25f; 1568 rect.h = ctx->arrow_height / 2.0f; 1569 SDL_RenderFillRect(ctx->renderer, &rect); 1570 } 1571 1572 if (ctx->display_mode == CONTROLLER_MODE_BINDING && has_negative) { 1573 if (GetAxisBindingString(axis, -1, mapping, binding, sizeof(binding))) { 1574 float text_x; 1575 1576 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); 1577 text_x = dst.x + arrow_extent / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(binding)) / 2; 1578 SDLTest_DrawString(ctx->renderer, text_x, y, binding); 1579 } 1580 } 1581 1582 dst.x += arrow_extent; 1583 1584 if (value > 0) { 1585 SDL_SetRenderDrawColor(ctx->renderer, 8, 200, 16, SDL_ALPHA_OPAQUE); 1586 rect.w = ((float)value / SDL_MAX_SINT16) * arrow_extent; 1587 rect.x = dst.x; 1588 rect.y = dst.y + ctx->arrow_height * 0.25f; 1589 rect.h = ctx->arrow_height / 2.0f; 1590 SDL_RenderFillRect(ctx->renderer, &rect); 1591 } 1592 1593 if (ctx->display_mode == CONTROLLER_MODE_BINDING) { 1594 if (GetAxisBindingString(axis, 1, mapping, binding, sizeof(binding))) { 1595 float text_x; 1596 1597 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); 1598 text_x = dst.x + arrow_extent / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(binding)) / 2; 1599 SDLTest_DrawString(ctx->renderer, text_x, y, binding); 1600 } 1601 } 1602 1603 dst.x += arrow_extent; 1604 1605 if (value == SDL_MAX_SINT16) { 1606 SDL_SetTextureColorMod(ctx->arrow_texture, 10, 255, 21); 1607 } else { 1608 SDL_SetTextureColorMod(ctx->arrow_texture, 255, 255, 255); 1609 } 1610 SDL_RenderTexture(ctx->renderer, ctx->arrow_texture, NULL, &dst); 1611 1612 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); 1613 1614 y += ctx->button_height + 2.0f; 1615 } 1616 1617 if (ctx->display_mode == CONTROLLER_MODE_TESTING) { 1618 if (SDL_GetNumGamepadTouchpads(gamepad) > 0) { 1619 int num_fingers = SDL_GetNumGamepadTouchpadFingers(gamepad, 0); 1620 for (i = 0; i < num_fingers; ++i) { 1621 bool down; 1622 float finger_x, finger_y, finger_pressure; 1623 1624 if (!SDL_GetGamepadTouchpadFinger(gamepad, 0, i, &down, &finger_x, &finger_y, &finger_pressure)) { 1625 continue; 1626 } 1627 1628 SDL_snprintf(text, sizeof(text), "Touch finger %d:", i); 1629 SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); 1630 1631 if (down) { 1632 SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); 1633 } else { 1634 SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); 1635 } 1636 1637 dst.x = x + center + 2.0f; 1638 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; 1639 dst.w = ctx->button_width; 1640 dst.h = ctx->button_height; 1641 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); 1642 1643 if (down) { 1644 SDL_snprintf(text, sizeof(text), "(%.2f,%.2f)", finger_x, finger_y); 1645 SDLTest_DrawString(ctx->renderer, x + center + ctx->button_width + 4.0f, y, text); 1646 } 1647 1648 y += ctx->button_height + 2.0f; 1649 } 1650 } 1651 1652 has_accel = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL); 1653 has_gyro = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO); 1654 1655 if (has_accel || has_gyro) { 1656 const int SENSOR_UPDATE_INTERVAL_MS = 100; 1657 Uint64 now = SDL_GetTicks(); 1658 1659 if (now >= ctx->last_sensor_update + SENSOR_UPDATE_INTERVAL_MS) { 1660 if (has_accel) { 1661 SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_ACCEL, ctx->accel_data, SDL_arraysize(ctx->accel_data)); 1662 } 1663 if (has_gyro) { 1664 SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_GYRO, ctx->gyro_data, SDL_arraysize(ctx->gyro_data)); 1665 } 1666 ctx->last_sensor_update = now; 1667 } 1668 1669 if (has_accel) { 1670 SDL_strlcpy(text, "Accelerometer:", sizeof(text)); 1671 SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); 1672 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 ); 1673 SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text); 1674 y += ctx->button_height + 2.0f; 1675 } 1676 1677 if (has_gyro) { 1678 SDL_strlcpy(text, "Gyro:", sizeof(text)); 1679 SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); 1680 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); 1681 SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text); 1682 1683 /* Display the testcontroller tool's evaluation of drift. This is also useful to get an average rate of turn in calibrated turntable tests. */ 1684 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 ) 1685 { 1686 y += ctx->button_height + 2.0f; 1687 SDL_strlcpy(text, "Gyro Drift:", sizeof(text)); 1688 SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); 1689 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); 1690 SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text); 1691 } 1692 } 1693 } 1694 } 1695 SDL_free(mapping); 1696} 1697 1698void DestroyGamepadDisplay(GamepadDisplay *ctx) 1699{ 1700 if (!ctx) { 1701 return; 1702 } 1703 1704 SDL_DestroyTexture(ctx->button_texture); 1705 SDL_DestroyTexture(ctx->arrow_texture); 1706 SDL_free(ctx); 1707} 1708 1709void RenderSensorTimingInfo(GyroDisplay *ctx, GamepadDisplay *gamepad_display) 1710{ 1711 /* Sensor timing section */ 1712 char text[128]; 1713 const float new_line_height = gamepad_display->button_height + 2.0f; 1714 const float text_offset_x = ctx->area.x + ctx->area.w / 4.0f + 35.0f; 1715 /* Anchor to bottom left of principle rect. */ 1716 float text_y_pos = ctx->area.y + ctx->area.h - new_line_height * 2; 1717 /* 1718 * Display rate of gyro as reported by the HID implementation. 1719 * This could be based on a hardware time stamp (PS5), or it could be generated by the HID implementation. 1720 * One should expect this to match the estimated rate below, assuming a wired connection. 1721 */ 1722 1723 SDL_strlcpy(text, "HID Sensor Time:", sizeof(text)); 1724 SDLTest_DrawString(ctx->renderer, text_offset_x - SDL_strlen(text) * FONT_CHARACTER_SIZE, text_y_pos, text); 1725 if (ctx->reported_sensor_rate_hz > 0) { 1726 /* Convert to micro seconds */ 1727 const int delta_time_us = (int)1e6 / ctx->reported_sensor_rate_hz; 1728 SDL_snprintf(text, sizeof(text), "%d%ss %dhz", delta_time_us, MICRO_UTF8, ctx->reported_sensor_rate_hz); 1729 } else { 1730 SDL_snprintf(text, sizeof(text), "????%ss ???hz", MICRO_UTF8); 1731 } 1732 SDLTest_DrawString(ctx->renderer, text_offset_x + 2.0f, text_y_pos, text); 1733 1734 /* 1735 * Display the instrumentation's count of all sensor packets received over time. 1736 * This may represent a more accurate polling rate for the IMU 1737 * But only when using a wired connection. 1738 * It does not necessarily reflect the rate at which the IMU is sampled. 1739 */ 1740 1741 text_y_pos += new_line_height; 1742 SDL_strlcpy(text, "Est.Sensor Time:", sizeof(text)); 1743 SDLTest_DrawString(ctx->renderer, text_offset_x - SDL_strlen(text) * FONT_CHARACTER_SIZE, text_y_pos, text); 1744 if (ctx->estimated_sensor_rate_hz > 0) { 1745 /* Convert to micro seconds */ 1746 const int delta_time_us = (int)1e6 / ctx->estimated_sensor_rate_hz; 1747 SDL_snprintf(text, sizeof(text), "%d%ss %dhz", delta_time_us, MICRO_UTF8, ctx->estimated_sensor_rate_hz); 1748 } else { 1749 SDL_snprintf(text, sizeof(text), "????%ss ???hz", MICRO_UTF8); 1750 } 1751 SDLTest_DrawString(ctx->renderer, text_offset_x + 2.0f, text_y_pos, text); 1752} 1753 1754void RenderGyroDriftCalibrationButton(GyroDisplay *ctx, GamepadDisplay *gamepad_display ) 1755{ 1756 char label_text[128]; 1757 float log_y = ctx->area.y + BUTTON_PADDING; 1758 const float new_line_height = gamepad_display->button_height + 2.0f; 1759 GamepadButton *start_calibration_button = GetGyroCalibrateButton(ctx); 1760 1761 /* Show the recalibration progress bar. */ 1762 float recalibrate_button_width = GetGamepadButtonLabelWidth(start_calibration_button) + 2 * BUTTON_PADDING; 1763 SDL_FRect recalibrate_button_area; 1764 recalibrate_button_area.x = ctx->area.x + ctx->area.w - recalibrate_button_width - BUTTON_PADDING; 1765 recalibrate_button_area.y = log_y + FONT_CHARACTER_SIZE * 0.5f - gamepad_display->button_height * 0.5f; 1766 recalibrate_button_area.w = GetGamepadButtonLabelWidth(start_calibration_button) + 2.0f * BUTTON_PADDING; 1767 recalibrate_button_area.h = gamepad_display->button_height + BUTTON_PADDING * 2.0f; 1768 1769 /* Above button */ 1770 SDL_strlcpy(label_text, "Gyro Orientation:", sizeof(label_text)); 1771 SDLTest_DrawString(ctx->renderer, recalibrate_button_area.x, recalibrate_button_area.y - new_line_height, label_text); 1772 1773 /* Button label vs state */ 1774 if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_OFF) { 1775 SDL_strlcpy(label_text, "Start Gyro Calibration", sizeof(label_text)); 1776 } else if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_NOISE_PROFILING) { 1777 SDL_snprintf(label_text, sizeof(label_text), "Noise Progress: %3.0f%% ", ctx->calibration_phase_progress_fraction * 100.0f); 1778 } else if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_DRIFT_PROFILING) { 1779 SDL_snprintf(label_text, sizeof(label_text), "Drift Progress: %3.0f%% ", ctx->calibration_phase_progress_fraction * 100.0f); 1780 } else if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_COMPLETE) { 1781 SDL_strlcpy(label_text, "Recalibrate Gyro", sizeof(label_text)); 1782 } 1783 1784 SetGamepadButtonLabel(start_calibration_button, label_text); 1785 SetGamepadButtonArea(start_calibration_button, &recalibrate_button_area); 1786 RenderGamepadButton(start_calibration_button); 1787 1788 bool bExtremeNoise = ctx->accelerometer_noise_sq > ACCELEROMETER_MAX_NOISE_G_SQ; 1789 /* Explicit warning message if we detect too much movement */ 1790 if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_OFF) { 1791 if (bExtremeNoise) { 1792 SDL_strlcpy(label_text, "GamePad Must Be Still", sizeof(label_text)); 1793 SDLTest_DrawString(ctx->renderer, recalibrate_button_area.x, recalibrate_button_area.y + recalibrate_button_area.h + new_line_height, label_text); 1794 SDL_strlcpy(label_text, "Place GamePad On Table", sizeof(label_text)); 1795 SDLTest_DrawString(ctx->renderer, recalibrate_button_area.x, recalibrate_button_area.y + recalibrate_button_area.h + new_line_height * 2, label_text); 1796 } 1797 } 1798 1799 if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_NOISE_PROFILING || 1800 ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_DRIFT_PROFILING) 1801 { 1802 float flAbsoluteNoiseFraction = SDL_clamp(ctx->accelerometer_noise_sq / ACCELEROMETER_MAX_NOISE_G_SQ, 0.0f, 1.0f); 1803 float flAbsoluteToleranceFraction = SDL_clamp(ctx->accelerometer_noise_tolerance_sq / ACCELEROMETER_MAX_NOISE_G_SQ, 0.0f, 1.0f); 1804 1805 float flMaxNoiseForThisPhase = ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_NOISE_PROFILING ? ACCELEROMETER_MAX_NOISE_G_SQ : ctx->accelerometer_noise_tolerance_sq; 1806 float flRelativeNoiseFraction = SDL_clamp(ctx->accelerometer_noise_sq / flMaxNoiseForThisPhase, 0.0f, 1.0f); 1807 1808 float noise_bar_height = gamepad_display->button_height; 1809 SDL_FRect noise_bar_rect; 1810 noise_bar_rect.x = recalibrate_button_area.x; 1811 noise_bar_rect.y = recalibrate_button_area.y + recalibrate_button_area.h + BUTTON_PADDING; 1812 noise_bar_rect.w = recalibrate_button_area.w; 1813 noise_bar_rect.h = noise_bar_height; 1814 1815 SDL_snprintf(label_text, sizeof(label_text), "Accelerometer Noise Tolerance: %3.3fG ", SDL_sqrtf(ctx->accelerometer_noise_tolerance_sq) ); 1816 SDLTest_DrawString(ctx->renderer, recalibrate_button_area.x, recalibrate_button_area.y + recalibrate_button_area.h + new_line_height * 2, label_text); 1817 1818 /* Adjust the noise bar rectangle based on the accelerometer noise value */ 1819 float noise_bar_fill_width = flAbsoluteNoiseFraction * noise_bar_rect.w; /* Scale the width based on the noise value */ 1820 SDL_FRect noise_bar_fill_rect; 1821 noise_bar_fill_rect.x = noise_bar_rect.x + (noise_bar_rect.w - noise_bar_fill_width) * 0.5f; 1822 noise_bar_fill_rect.y = noise_bar_rect.y; 1823 noise_bar_fill_rect.w = noise_bar_fill_width; 1824 noise_bar_fill_rect.h = noise_bar_height; 1825 1826 /* Set the color based on the noise value vs the tolerance */ 1827 Uint8 red = (Uint8)(flRelativeNoiseFraction * 255.0f); 1828 Uint8 green = (Uint8)((1.0f - flRelativeNoiseFraction) * 255.0f); 1829 SDL_SetRenderDrawColor(ctx->renderer, red, green, 0, 255); /* red when high noise, green when low noise */ 1830 SDL_RenderFillRect(ctx->renderer, &noise_bar_fill_rect); /* draw the filled rectangle */ 1831 1832 float tolerance_bar_fill_width = flAbsoluteToleranceFraction * noise_bar_rect.w; /* Scale the width based on the noise value */ 1833 SDL_FRect tolerance_bar_rect; 1834 tolerance_bar_rect.x = noise_bar_rect.x + (noise_bar_rect.w - tolerance_bar_fill_width) * 0.5f; 1835 tolerance_bar_rect.y = noise_bar_rect.y; 1836 tolerance_bar_rect.w = tolerance_bar_fill_width; 1837 tolerance_bar_rect.h = noise_bar_height; 1838 1839 SDL_SetRenderDrawColor(ctx->renderer, 128, 128, 0, 255); 1840 SDL_RenderRect(ctx->renderer, &tolerance_bar_rect); /* draw the tolerance rectangle */ 1841 1842 SDL_SetRenderDrawColor(ctx->renderer, 100, 100, 100, 255); /* gray box */ 1843 SDL_RenderRect(ctx->renderer, &noise_bar_rect); /* draw the outline rectangle */ 1844 1845 /* Explicit warning message if we detect too much movement */ 1846 bool bTooMuchNoise = (flAbsoluteNoiseFraction == 1.0f); 1847 if (bTooMuchNoise) { 1848 SDL_strlcpy(label_text, "Place GamePad Down!", sizeof(label_text)); 1849 SDLTest_DrawString(ctx->renderer, recalibrate_button_area.x, noise_bar_rect.y + noise_bar_rect.h + new_line_height, label_text); 1850 } 1851 1852 /* Drift progress bar */ 1853 /* Demonstrate how far we are through the drift progress, and how it resets when there's "high noise", i.e if flNoiseFraction == 1.0f */ 1854 SDL_FRect progress_bar_rect; 1855 progress_bar_rect.x = recalibrate_button_area.x + BUTTON_PADDING; 1856 progress_bar_rect.y = recalibrate_button_area.y + recalibrate_button_area.h * 0.5f + BUTTON_PADDING * 0.5f; 1857 progress_bar_rect.w = recalibrate_button_area.w - BUTTON_PADDING * 2.0f; 1858 progress_bar_rect.h = BUTTON_PADDING * 0.5f; 1859 1860 /* Adjust the drift bar rectangle based on the drift calibration progress fraction */ 1861 float drift_bar_fill_width = bTooMuchNoise ? 1.0f : ctx->calibration_phase_progress_fraction * progress_bar_rect.w; 1862 SDL_FRect progress_bar_fill; 1863 progress_bar_fill.x = progress_bar_rect.x; 1864 progress_bar_fill.y = progress_bar_rect.y; 1865 progress_bar_fill.w = drift_bar_fill_width; 1866 progress_bar_fill.h = progress_bar_rect.h; 1867 1868 /* Set the color based on the drift calibration progress fraction */ 1869 SDL_SetRenderDrawColor(ctx->renderer, GYRO_COLOR_GREEN); /* red when too much noise, green when low noise*/ 1870 /* Now draw the bars with the filled, then empty rectangles */ 1871 SDL_RenderFillRect(ctx->renderer, &progress_bar_fill); /* draw the filled rectangle*/ 1872 SDL_SetRenderDrawColor(ctx->renderer, 100, 100, 100, 255); /* gray box*/ 1873 SDL_RenderRect(ctx->renderer, &progress_bar_rect); /* draw the outline rectangle*/ 1874 1875 /* If there is too much movement, we are going to draw two diagonal red lines between the progress rect corners.*/ 1876 if (bTooMuchNoise) { 1877 SDL_SetRenderDrawColor(ctx->renderer, GYRO_COLOR_RED); /* red */ 1878 SDL_RenderFillRect(ctx->renderer, &progress_bar_fill); /* draw the filled rectangle */ 1879 } 1880 } 1881} 1882 1883float RenderEulerReadout(GyroDisplay *ctx, GamepadDisplay *gamepad_display ) 1884{ 1885 /* Get the mater button's width and base our width off that */ 1886 GamepadButton *master_button = GetGyroCalibrateButton(ctx); 1887 SDL_FRect gyro_calibrate_button_rect; 1888 GetGamepadButtonArea(master_button, &gyro_calibrate_button_rect); 1889 1890 char text[128]; 1891 float log_y = gyro_calibrate_button_rect.y + gyro_calibrate_button_rect.h + BUTTON_PADDING; 1892 const float new_line_height = gamepad_display->button_height + 2.0f; 1893 float log_gyro_euler_text_x = gyro_calibrate_button_rect.x; 1894 1895 Uint8 r, g, b, a; 1896 SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); 1897 /* Pitch Readout */ 1898 SDL_SetRenderDrawColor(ctx->renderer, GYRO_COLOR_RED); 1899 SDL_snprintf(text, sizeof(text), "Pitch: %6.2f%s", ctx->euler_displacement_angles[0], DEGREE_UTF8); 1900 SDLTest_DrawString(ctx->renderer, log_gyro_euler_text_x + 2.0f, log_y, text); 1901 1902 /* Yaw Readout */ 1903 SDL_SetRenderDrawColor(ctx->renderer, GYRO_COLOR_GREEN); 1904 log_y += new_line_height; 1905 SDL_snprintf(text, sizeof(text), " Yaw: %6.2f%s", ctx->euler_displacement_angles[1], DEGREE_UTF8); 1906 SDLTest_DrawString(ctx->renderer, log_gyro_euler_text_x + 2.0f, log_y, text); 1907 1908 /* Roll Readout */ 1909 SDL_SetRenderDrawColor(ctx->renderer, GYRO_COLOR_BLUE); 1910 log_y += new_line_height; 1911 SDL_snprintf(text, sizeof(text), " Roll: %6.2f%s", ctx->euler_displacement_angles[2], DEGREE_UTF8); 1912 SDLTest_DrawString(ctx->renderer, log_gyro_euler_text_x + 2.0f, log_y, text); 1913 1914 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); 1915 return log_y + new_line_height; /* Return the next y position for further rendering */ 1916} 1917 1918/* Draws the 3D cube, circles and accel arrow, positioning itself relative to the calibrate button. */ 1919void RenderGyroGizmo(GyroDisplay *ctx, SDL_Gamepad *gamepad, float top) 1920{ 1921 /* Get the calibrate button's on-screen area: */ 1922 GamepadButton *btn = GetGyroCalibrateButton(ctx); 1923 SDL_FRect btnArea; 1924 GetGamepadButtonArea(btn, &btnArea); 1925 1926 float gizmoSize = btnArea.w; 1927 /* Position it centered horizontally above the button with a small gap */ 1928 SDL_FRect gizmoRect; 1929 gizmoRect.x = btnArea.x + (btnArea.w - gizmoSize) * 0.5f; 1930 gizmoRect.y = top; 1931 gizmoRect.w = gizmoSize; 1932 gizmoRect.h = gizmoSize; 1933 1934 /* Draw the rotated cube */ 1935 DrawGyroDebugCube(ctx->renderer, &ctx->gyro_quaternion, &gizmoRect); 1936 1937 /* Draw positive axes */ 1938 DrawGyroDebugAxes(ctx->renderer, &ctx->gyro_quaternion, &gizmoRect); 1939 1940 /* Overlay the XYZ circles */ 1941 DrawGyroDebugCircle(ctx->renderer, &ctx->gyro_quaternion, &gizmoRect); 1942 1943 /* If we have accel, draw that arrow too */ 1944 if (SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL)) { 1945 float accel[3]; 1946 SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_ACCEL, accel, SDL_arraysize(accel)); 1947 DrawAccelerometerDebugArrow(ctx->renderer, &ctx->gyro_quaternion, accel, &gizmoRect); 1948 } 1949 1950 /* Follow the size of the main button, but position it below the gizmo */ 1951 GamepadButton *reset_button = GetGyroResetButton(ctx); 1952 if (reset_button) { 1953 SDL_FRect reset_area; 1954 GetGamepadButtonArea(reset_button, &reset_area); 1955 /* Position the reset button below the gizmo */ 1956 reset_area.x = btnArea.x; 1957 reset_area.y = gizmoRect.y + gizmoRect.h + BUTTON_PADDING * 0.5f; 1958 reset_area.w = btnArea.w; 1959 reset_area.h = btnArea.h; 1960 SetGamepadButtonArea(reset_button, &reset_area); 1961 RenderGamepadButton(reset_button); 1962 } 1963} 1964 1965void RenderGyroDisplay(GyroDisplay *ctx, GamepadDisplay *gamepadElements, SDL_Gamepad *gamepad) 1966{ 1967 if (!ctx) 1968 return; 1969 1970 bool bHasAccelerometer = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL); 1971 bool bHasGyroscope = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO); 1972 bool bHasIMU = bHasAccelerometer || bHasGyroscope; 1973 if (!bHasIMU) 1974 return; 1975 1976 Uint8 r, g, b, a; 1977 SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); 1978 1979 RenderSensorTimingInfo(ctx, gamepadElements); 1980 RenderGyroDriftCalibrationButton(ctx, gamepadElements); 1981 1982 /* Render Gyro calibration phases */ 1983 if (ctx->current_calibration_phase == GYRO_CALIBRATION_PHASE_COMPLETE) { 1984 float bottom = RenderEulerReadout(ctx, gamepadElements); 1985 RenderGyroGizmo(ctx, gamepad, bottom); 1986 } 1987 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); 1988} 1989 1990void DestroyGyroDisplay(GyroDisplay *ctx) 1991{ 1992 if (!ctx) { 1993 return; 1994 } 1995 DestroyGamepadButton(ctx->reset_gyro_button); 1996 DestroyGamepadButton(ctx->calibrate_gyro_button); 1997 SDL_free(ctx); 1998} 1999 2000 2001struct GamepadTypeDisplay 2002{ 2003 SDL_Renderer *renderer; 2004 2005 int type_highlighted; 2006 bool type_pressed; 2007 int type_selected; 2008 SDL_GamepadType real_type; 2009 2010 SDL_FRect area; 2011}; 2012 2013GamepadTypeDisplay *CreateGamepadTypeDisplay(SDL_Renderer *renderer) 2014{ 2015 GamepadTypeDisplay *ctx = SDL_calloc(1, sizeof(*ctx)); 2016 if (ctx) { 2017 ctx->renderer = renderer; 2018 2019 ctx->type_highlighted = SDL_GAMEPAD_TYPE_UNSELECTED; 2020 ctx->type_selected = SDL_GAMEPAD_TYPE_UNSELECTED; 2021 ctx->real_type = SDL_GAMEPAD_TYPE_UNKNOWN; 2022 } 2023 return ctx; 2024} 2025 2026void SetGamepadTypeDisplayArea(GamepadTypeDisplay *ctx, const SDL_FRect *area) 2027{ 2028 if (!ctx) { 2029 return; 2030 } 2031 2032 SDL_copyp(&ctx->area, area); 2033} 2034 2035void SetGamepadTypeDisplayHighlight(GamepadTypeDisplay *ctx, int type, bool pressed) 2036{ 2037 if (!ctx) { 2038 return; 2039 } 2040 2041 ctx->type_highlighted = type; 2042 ctx->type_pressed = pressed; 2043} 2044 2045void SetGamepadTypeDisplaySelected(GamepadTypeDisplay *ctx, int type) 2046{ 2047 if (!ctx) { 2048 return; 2049 } 2050 2051 ctx->type_selected = type; 2052} 2053 2054void SetGamepadTypeDisplayRealType(GamepadTypeDisplay *ctx, SDL_GamepadType type) 2055{ 2056 if (!ctx) { 2057 return; 2058 } 2059 2060 ctx->real_type = type; 2061} 2062 2063int GetGamepadTypeDisplayAt(GamepadTypeDisplay *ctx, float x, float y) 2064{ 2065 int i; 2066 const float margin = 8.0f; 2067 const float line_height = 16.0f; 2068 SDL_FRect highlight; 2069 SDL_FPoint point; 2070 2071 if (!ctx) { 2072 return SDL_GAMEPAD_TYPE_UNSELECTED; 2073 } 2074 2075 point.x = x; 2076 point.y = y; 2077 2078 x = ctx->area.x + margin; 2079 y = ctx->area.y + margin; 2080 2081 for (i = SDL_GAMEPAD_TYPE_UNKNOWN; i < SDL_GAMEPAD_TYPE_COUNT; ++i) { 2082 highlight.x = x; 2083 highlight.y = y; 2084 highlight.w = ctx->area.w - (margin * 2); 2085 highlight.h = line_height; 2086 2087 if (SDL_PointInRectFloat(&point, &highlight)) { 2088 return i; 2089 } 2090 2091 y += line_height; 2092 } 2093 return SDL_GAMEPAD_TYPE_UNSELECTED; 2094} 2095 2096static void RenderGamepadTypeHighlight(GamepadTypeDisplay *ctx, int type, const SDL_FRect *area) 2097{ 2098 if (type == ctx->type_highlighted || type == ctx->type_selected) { 2099 Uint8 r, g, b, a; 2100 2101 SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); 2102 2103 if (type == ctx->type_highlighted) { 2104 if (ctx->type_pressed) { 2105 SDL_SetRenderDrawColor(ctx->renderer, PRESSED_COLOR); 2106 } else { 2107 SDL_SetRenderDrawColor(ctx->renderer, HIGHLIGHT_COLOR); 2108 } 2109 } else { 2110 SDL_SetRenderDrawColor(ctx->renderer, SELECTED_COLOR); 2111 } 2112 SDL_RenderFillRect(ctx->renderer, area); 2113 2114 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); 2115 } 2116} 2117 2118void RenderGamepadTypeDisplay(GamepadTypeDisplay *ctx) 2119{ 2120 float x, y; 2121 int i; 2122 char text[128]; 2123 const float margin = 8.0f; 2124 const float line_height = 16.0f; 2125 SDL_FPoint dst; 2126 SDL_FRect highlight; 2127 2128 if (!ctx) { 2129 return; 2130 } 2131 2132 x = ctx->area.x + margin; 2133 y = ctx->area.y + margin; 2134 2135 for (i = SDL_GAMEPAD_TYPE_UNKNOWN; i < SDL_GAMEPAD_TYPE_COUNT; ++i) { 2136 highlight.x = x; 2137 highlight.y = y; 2138 highlight.w = ctx->area.w - (margin * 2); 2139 highlight.h = line_height; 2140 RenderGamepadTypeHighlight(ctx, i, &highlight); 2141 2142 if (i == SDL_GAMEPAD_TYPE_UNKNOWN) { 2143 if (ctx->real_type == SDL_GAMEPAD_TYPE_UNKNOWN || 2144 ctx->real_type == SDL_GAMEPAD_TYPE_STANDARD) { 2145 SDL_strlcpy(text, "Auto (Standard)", sizeof(text)); 2146 } else { 2147 SDL_snprintf(text, sizeof(text), "Auto (%s)", GetGamepadTypeString(ctx->real_type)); 2148 } 2149 } else if (i == SDL_GAMEPAD_TYPE_STANDARD) { 2150 SDL_strlcpy(text, "Standard", sizeof(text)); 2151 } else { 2152 SDL_strlcpy(text, GetGamepadTypeString((SDL_GamepadType)i), sizeof(text)); 2153 } 2154 2155 dst.x = x + margin; 2156 dst.y = y + line_height / 2 - FONT_CHARACTER_SIZE / 2; 2157 SDLTest_DrawString(ctx->renderer, dst.x, dst.y, text); 2158 2159 y += line_height; 2160 } 2161} 2162 2163void DestroyGamepadTypeDisplay(GamepadTypeDisplay *ctx) 2164{ 2165 if (!ctx) { 2166 return; 2167 } 2168 2169 SDL_free(ctx); 2170} 2171 2172 2173struct JoystickDisplay 2174{ 2175 SDL_Renderer *renderer; 2176 SDL_Texture *button_texture; 2177 SDL_Texture *arrow_texture; 2178 float button_width; 2179 float button_height; 2180 float arrow_width; 2181 float arrow_height; 2182 2183 SDL_FRect area; 2184 2185 char *element_highlighted; 2186 bool element_pressed; 2187}; 2188 2189JoystickDisplay *CreateJoystickDisplay(SDL_Renderer *renderer) 2190{ 2191 JoystickDisplay *ctx = SDL_calloc(1, sizeof(*ctx)); 2192 if (ctx) { 2193 ctx->renderer = renderer; 2194 2195 ctx->button_texture = CreateTexture(renderer, gamepad_button_small_png, gamepad_button_small_png_len); 2196 SDL_GetTextureSize(ctx->button_texture, &ctx->button_width, &ctx->button_height); 2197 2198 ctx->arrow_texture = CreateTexture(renderer, gamepad_axis_arrow_png, gamepad_axis_arrow_png_len); 2199 SDL_GetTextureSize(ctx->arrow_texture, &ctx->arrow_width, &ctx->arrow_height); 2200 } 2201 return ctx; 2202} 2203 2204void SetJoystickDisplayArea(JoystickDisplay *ctx, const SDL_FRect *area) 2205{ 2206 if (!ctx) { 2207 return; 2208 } 2209 2210 SDL_copyp(&ctx->area, area); 2211} 2212 2213char *GetJoystickDisplayElementAt(JoystickDisplay *ctx, SDL_Joystick *joystick, float x, float y) 2214{ 2215 SDL_FPoint point; 2216 int i; 2217 int nbuttons = SDL_GetNumJoystickButtons(joystick); 2218 int naxes = SDL_GetNumJoystickAxes(joystick); 2219 int nhats = SDL_GetNumJoystickHats(joystick); 2220 char text[32]; 2221 const float margin = 8.0f; 2222 const float center = 80.0f; 2223 const float arrow_extent = 48.0f; 2224 SDL_FRect dst, highlight; 2225 char *element = NULL; 2226 2227 if (!ctx) { 2228 return NULL; 2229 } 2230 2231 point.x = x; 2232 point.y = y; 2233 2234 x = ctx->area.x + margin; 2235 y = ctx->area.y + margin; 2236 2237 if (nbuttons > 0) { 2238 y += FONT_LINE_HEIGHT + 2; 2239 2240 for (i = 0; i < nbuttons; ++i) { 2241 highlight.x = x; 2242 highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; 2243 highlight.w = center - (margin * 2); 2244 highlight.h = ctx->button_height; 2245 if (SDL_PointInRectFloat(&point, &highlight)) { 2246 SDL_asprintf(&element, "b%d", i); 2247 return element; 2248 } 2249 2250 y += ctx->button_height + 2; 2251 } 2252 } 2253 2254 x = ctx->area.x + margin + center + margin; 2255 y = ctx->area.y + margin; 2256 2257 if (naxes > 0) { 2258 y += FONT_LINE_HEIGHT + 2; 2259 2260 for (i = 0; i < naxes; ++i) { 2261 SDL_snprintf(text, sizeof(text), "%d:", i); 2262 2263 highlight.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2.0f; 2264 highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; 2265 highlight.w = ctx->arrow_width + arrow_extent; 2266 highlight.h = ctx->button_height; 2267 if (SDL_PointInRectFloat(&point, &highlight)) { 2268 SDL_asprintf(&element, "-a%d", i); 2269 return element; 2270 } 2271 2272 highlight.x += highlight.w; 2273 if (SDL_PointInRectFloat(&point, &highlight)) { 2274 SDL_asprintf(&element, "+a%d", i); 2275 return element; 2276 } 2277 2278 y += ctx->button_height + 2; 2279 } 2280 } 2281 2282 y += FONT_LINE_HEIGHT + 2; 2283 2284 if (nhats > 0) { 2285 y += FONT_LINE_HEIGHT + 2 + 1.5f * ctx->button_height - FONT_CHARACTER_SIZE / 2; 2286 2287 for (i = 0; i < nhats; ++i) { 2288 SDL_snprintf(text, sizeof(text), "%d:", i); 2289 2290 dst.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2; 2291 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; 2292 dst.w = ctx->button_width; 2293 dst.h = ctx->button_height; 2294 if (SDL_PointInRectFloat(&point, &dst)) { 2295 SDL_asprintf(&element, "h%d.%d", i, SDL_HAT_LEFT); 2296 return element; 2297 } 2298 2299 dst.x += ctx->button_width; 2300 dst.y -= ctx->button_height; 2301 if (SDL_PointInRectFloat(&point, &dst)) { 2302 SDL_asprintf(&element, "h%d.%d", i, SDL_HAT_UP); 2303 return element; 2304 } 2305 2306 dst.y += ctx->button_height * 2; 2307 if (SDL_PointInRectFloat(&point, &dst)) { 2308 SDL_asprintf(&element, "h%d.%d", i, SDL_HAT_DOWN); 2309 return element; 2310 } 2311 2312 dst.x += ctx->button_width; 2313 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; 2314 if (SDL_PointInRectFloat(&point, &dst)) { 2315 SDL_asprintf(&element, "h%d.%d", i, SDL_HAT_RIGHT); 2316 return element; 2317 } 2318 2319 y += 3 * ctx->button_height + 2; 2320 } 2321 } 2322 return NULL; 2323} 2324 2325void SetJoystickDisplayHighlight(JoystickDisplay *ctx, const char *element, bool pressed) 2326{ 2327 if (ctx->element_highlighted) { 2328 SDL_free(ctx->element_highlighted); 2329 ctx->element_highlighted = NULL; 2330 ctx->element_pressed = false; 2331 } 2332 2333 if (element) { 2334 ctx->element_highlighted = SDL_strdup(element); 2335 ctx->element_pressed = pressed; 2336 } 2337} 2338 2339static void RenderJoystickButtonHighlight(JoystickDisplay *ctx, int button, const SDL_FRect *area) 2340{ 2341 if (!ctx->element_highlighted || *ctx->element_highlighted != 'b') { 2342 return; 2343 } 2344 2345 if (SDL_atoi(ctx->element_highlighted + 1) == button) { 2346 Uint8 r, g, b, a; 2347 2348 SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); 2349 2350 if (ctx->element_pressed) { 2351 SDL_SetRenderDrawColor(ctx->renderer, PRESSED_COLOR); 2352 } else { 2353 SDL_SetRenderDrawColor(ctx->renderer, HIGHLIGHT_COLOR); 2354 } 2355 SDL_RenderFillRect(ctx->renderer, area); 2356 2357 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); 2358 } 2359} 2360 2361static void RenderJoystickAxisHighlight(JoystickDisplay *ctx, int axis, int direction, const SDL_FRect *area) 2362{ 2363 char prefix = (direction < 0 ? '-' : '+'); 2364 2365 if (!ctx->element_highlighted || 2366 ctx->element_highlighted[0] != prefix || 2367 ctx->element_highlighted[1] != 'a') { 2368 return; 2369 } 2370 2371 if (SDL_atoi(ctx->element_highlighted + 2) == axis) { 2372 Uint8 r, g, b, a; 2373 2374 SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); 2375 2376 if (ctx->element_pressed) { 2377 SDL_SetRenderDrawColor(ctx->renderer, PRESSED_COLOR); 2378 } else { 2379 SDL_SetRenderDrawColor(ctx->renderer, HIGHLIGHT_COLOR); 2380 } 2381 SDL_RenderFillRect(ctx->renderer, area); 2382 2383 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); 2384 } 2385} 2386 2387static bool SetupJoystickHatHighlight(JoystickDisplay *ctx, int hat, int direction) 2388{ 2389 if (!ctx->element_highlighted || *ctx->element_highlighted != 'h') { 2390 return false; 2391 } 2392 2393 if (SDL_atoi(ctx->element_highlighted + 1) == hat && 2394 ctx->element_highlighted[2] == '.' && 2395 SDL_atoi(ctx->element_highlighted + 3) == direction) { 2396 if (ctx->element_pressed) { 2397 SDL_SetTextureColorMod(ctx->button_texture, PRESSED_TEXTURE_MOD); 2398 } else { 2399 SDL_SetTextureColorMod(ctx->button_texture, HIGHLIGHT_TEXTURE_MOD); 2400 } 2401 return true; 2402 } 2403 return false; 2404} 2405 2406void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick) 2407{ 2408 float x, y; 2409 int i; 2410 int nbuttons = SDL_GetNumJoystickButtons(joystick); 2411 int naxes = SDL_GetNumJoystickAxes(joystick); 2412 int nhats = SDL_GetNumJoystickHats(joystick); 2413 char text[32]; 2414 const float margin = 8.0f; 2415 const float center = 80.0f; 2416 const float arrow_extent = 48.0f; 2417 SDL_FRect dst, rect, highlight; 2418 Uint8 r, g, b, a; 2419 2420 if (!ctx) { 2421 return; 2422 } 2423 2424 SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); 2425 2426 x = ctx->area.x + margin; 2427 y = ctx->area.y + margin; 2428 2429 if (nbuttons > 0) { 2430 SDLTest_DrawString(ctx->renderer, x, y, "BUTTONS"); 2431 y += FONT_LINE_HEIGHT + 2; 2432 2433 for (i = 0; i < nbuttons; ++i) { 2434 highlight.x = x; 2435 highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; 2436 highlight.w = center - (margin * 2); 2437 highlight.h = ctx->button_height; 2438 RenderJoystickButtonHighlight(ctx, i, &highlight); 2439 2440 SDL_snprintf(text, sizeof(text), "%2d:", i); 2441 SDLTest_DrawString(ctx->renderer, x, y, text); 2442 2443 if (SDL_GetJoystickButton(joystick, (Uint8)i)) { 2444 SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); 2445 } else { 2446 SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); 2447 } 2448 2449 dst.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2; 2450 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; 2451 dst.w = ctx->button_width; 2452 dst.h = ctx->button_height; 2453 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); 2454 2455 y += ctx->button_height + 2; 2456 } 2457 } 2458 2459 x = ctx->area.x + margin + center + margin; 2460 y = ctx->area.y + margin; 2461 2462 if (naxes > 0) { 2463 SDLTest_DrawString(ctx->renderer, x, y, "AXES"); 2464 y += FONT_LINE_HEIGHT + 2; 2465 2466 for (i = 0; i < naxes; ++i) { 2467 Sint16 value = SDL_GetJoystickAxis(joystick, i); 2468 2469 SDL_snprintf(text, sizeof(text), "%d:", i); 2470 SDLTest_DrawString(ctx->renderer, x, y, text); 2471 2472 highlight.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2.0f; 2473 highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; 2474 highlight.w = ctx->arrow_width + arrow_extent; 2475 highlight.h = ctx->button_height; 2476 RenderJoystickAxisHighlight(ctx, i, -1, &highlight); 2477 2478 highlight.x += highlight.w; 2479 RenderJoystickAxisHighlight(ctx, i, 1, &highlight); 2480 2481 dst.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2.0f; 2482 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->arrow_height / 2; 2483 dst.w = ctx->arrow_width; 2484 dst.h = ctx->arrow_height; 2485 2486 if (value == SDL_MIN_SINT16) { 2487 SDL_SetTextureColorMod(ctx->arrow_texture, 10, 255, 21); 2488 } else { 2489 SDL_SetTextureColorMod(ctx->arrow_texture, 255, 255, 255); 2490 } 2491 SDL_RenderTextureRotated(ctx->renderer, ctx->arrow_texture, NULL, &dst, 0.0f, NULL, SDL_FLIP_HORIZONTAL); 2492 2493 dst.x += ctx->arrow_width; 2494 2495 SDL_SetRenderDrawColor(ctx->renderer, 200, 200, 200, SDL_ALPHA_OPAQUE); 2496 rect.x = dst.x + arrow_extent - 2.0f; 2497 rect.y = dst.y; 2498 rect.w = 4.0f; 2499 rect.h = ctx->arrow_height; 2500 SDL_RenderFillRect(ctx->renderer, &rect); 2501 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); 2502 2503 if (value < 0) { 2504 SDL_SetRenderDrawColor(ctx->renderer, 8, 200, 16, SDL_ALPHA_OPAQUE); 2505 rect.w = ((float)value / SDL_MIN_SINT16) * arrow_extent; 2506 rect.x = dst.x + arrow_extent - rect.w; 2507 rect.y = dst.y + ctx->arrow_height * 0.25f; 2508 rect.h = ctx->arrow_height / 2.0f; 2509 SDL_RenderFillRect(ctx->renderer, &rect); 2510 } 2511 2512 dst.x += arrow_extent; 2513 2514 if (value > 0) { 2515 SDL_SetRenderDrawColor(ctx->renderer, 8, 200, 16, SDL_ALPHA_OPAQUE); 2516 rect.w = ((float)value / SDL_MAX_SINT16) * arrow_extent; 2517 rect.x = dst.x; 2518 rect.y = dst.y + ctx->arrow_height * 0.25f; 2519 rect.h = ctx->arrow_height / 2.0f; 2520 SDL_RenderFillRect(ctx->renderer, &rect); 2521 } 2522 2523 dst.x += arrow_extent; 2524 2525 if (value == SDL_MAX_SINT16) { 2526 SDL_SetTextureColorMod(ctx->arrow_texture, 10, 255, 21); 2527 } else { 2528 SDL_SetTextureColorMod(ctx->arrow_texture, 255, 255, 255); 2529 } 2530 SDL_RenderTexture(ctx->renderer, ctx->arrow_texture, NULL, &dst); 2531 2532 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); 2533 2534 y += ctx->button_height + 2; 2535 } 2536 } 2537 2538 y += FONT_LINE_HEIGHT + 2; 2539 2540 if (nhats > 0) { 2541 SDLTest_DrawString(ctx->renderer, x, y, "HATS"); 2542 y += FONT_LINE_HEIGHT + 2 + 1.5f * ctx->button_height - FONT_CHARACTER_SIZE / 2; 2543 2544 for (i = 0; i < nhats; ++i) { 2545 Uint8 value = SDL_GetJoystickHat(joystick, i); 2546 2547 SDL_snprintf(text, sizeof(text), "%d:", i); 2548 SDLTest_DrawString(ctx->renderer, x, y, text); 2549 2550 if (value & SDL_HAT_LEFT) { 2551 SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); 2552 } else if (!SetupJoystickHatHighlight(ctx, i, SDL_HAT_LEFT)) { 2553 SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); 2554 } 2555 2556 dst.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2; 2557 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; 2558 dst.w = ctx->button_width; 2559 dst.h = ctx->button_height; 2560 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); 2561 2562 if (value & SDL_HAT_UP) { 2563 SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); 2564 } else if (!SetupJoystickHatHighlight(ctx, i, SDL_HAT_UP)) { 2565 SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); 2566 } 2567 2568 dst.x += ctx->button_width; 2569 dst.y -= ctx->button_height; 2570 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); 2571 2572 if (value & SDL_HAT_DOWN) { 2573 SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); 2574 } else if (!SetupJoystickHatHighlight(ctx, i, SDL_HAT_DOWN)) { 2575 SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); 2576 } 2577 2578 dst.y += ctx->button_height * 2; 2579 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); 2580 2581 if (value & SDL_HAT_RIGHT) { 2582 SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); 2583 } else if (!SetupJoystickHatHighlight(ctx, i, SDL_HAT_RIGHT)) { 2584 SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); 2585 } 2586 2587 dst.x += ctx->button_width; 2588 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; 2589 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); 2590 2591 y += 3 * ctx->button_height + 2; 2592 } 2593 } 2594} 2595 2596void DestroyJoystickDisplay(JoystickDisplay *ctx) 2597{ 2598 if (!ctx) { 2599 return; 2600 } 2601 2602 SDL_DestroyTexture(ctx->button_texture); 2603 SDL_DestroyTexture(ctx->arrow_texture); 2604 SDL_free(ctx); 2605} 2606 2607 2608struct GamepadButton 2609{ 2610 SDL_Renderer *renderer; 2611 SDL_Texture *background; 2612 float background_width; 2613 float background_height; 2614 2615 SDL_FRect area; 2616 2617 char *label; 2618 float label_width; 2619 float label_height; 2620 2621 bool highlight; 2622 bool pressed; 2623}; 2624 2625GamepadButton *CreateGamepadButton(SDL_Renderer *renderer, const char *label) 2626{ 2627 GamepadButton *ctx = SDL_calloc(1, sizeof(*ctx)); 2628 if (ctx) { 2629 ctx->renderer = renderer; 2630 2631 ctx->background = CreateTexture(renderer, gamepad_button_background_png, gamepad_button_background_png_len); 2632 SDL_GetTextureSize(ctx->background, &ctx->background_width, &ctx->background_height); 2633 2634 SetGamepadButtonLabel(ctx, label); 2635 } 2636 return ctx; 2637} 2638 2639void SetGamepadButtonLabel(GamepadButton *ctx, const char *label) 2640{ 2641 if (!ctx) { 2642 return; 2643 } 2644 2645 SDL_free(ctx->label); 2646 2647 ctx->label = SDL_strdup(label); 2648 ctx->label_width = (float)(FONT_CHARACTER_SIZE * SDL_strlen(label)); 2649 ctx->label_height = (float)FONT_CHARACTER_SIZE; 2650} 2651void SetGamepadButtonArea(GamepadButton *ctx, const SDL_FRect *area) 2652{ 2653 if (!ctx) { 2654 return; 2655 } 2656 2657 SDL_copyp(&ctx->area, area); 2658} 2659 2660void GetGamepadButtonArea(GamepadButton *ctx, SDL_FRect *area) 2661{ 2662 if (!ctx) { 2663 SDL_zerop(area); 2664 return; 2665 } 2666 2667 SDL_copyp(area, &ctx->area); 2668} 2669 2670void SetGamepadButtonHighlight(GamepadButton *ctx, bool highlight, bool pressed) 2671{ 2672 if (!ctx) { 2673 return; 2674 } 2675 2676 ctx->highlight = highlight; 2677 if (highlight) { 2678 ctx->pressed = pressed; 2679 } else { 2680 ctx->pressed = false; 2681 } 2682} 2683 2684float GetGamepadButtonLabelWidth(GamepadButton *ctx) 2685{ 2686 if (!ctx) { 2687 return 0; 2688 } 2689 2690 return ctx->label_width; 2691} 2692 2693float GetGamepadButtonLabelHeight(GamepadButton *ctx) 2694{ 2695 if (!ctx) { 2696 return 0; 2697 } 2698 2699 return ctx->label_height; 2700} 2701 2702bool GamepadButtonContains(GamepadButton *ctx, float x, float y) 2703{ 2704 SDL_FPoint point; 2705 2706 if (!ctx) { 2707 return false; 2708 } 2709 2710 point.x = x; 2711 point.y = y; 2712 return SDL_PointInRectFloat(&point, &ctx->area); 2713} 2714 2715void RenderGamepadButton(GamepadButton *ctx) 2716{ 2717 SDL_FRect src, dst; 2718 float one_third_src_width; 2719 float one_third_src_height; 2720 2721 if (!ctx) { 2722 return; 2723 } 2724 2725 one_third_src_width = ctx->background_width / 3; 2726 one_third_src_height = ctx->background_height / 3; 2727 2728 if (ctx->pressed) { 2729 SDL_SetTextureColorMod(ctx->background, PRESSED_TEXTURE_MOD); 2730 } else if (ctx->highlight) { 2731 SDL_SetTextureColorMod(ctx->background, HIGHLIGHT_TEXTURE_MOD); 2732 } else { 2733 SDL_SetTextureColorMod(ctx->background, 255, 255, 255); 2734 } 2735 2736 /* Top left */ 2737 src.x = 0.0f; 2738 src.y = 0.0f; 2739 src.w = one_third_src_width; 2740 src.h = one_third_src_height; 2741 dst.x = ctx->area.x; 2742 dst.y = ctx->area.y; 2743 dst.w = src.w; 2744 dst.h = src.h; 2745 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst); 2746 2747 /* Bottom left */ 2748 src.y = ctx->background_height - src.h; 2749 dst.y = ctx->area.y + ctx->area.h - dst.h; 2750 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst); 2751 2752 /* Bottom right */ 2753 src.x = ctx->background_width - src.w; 2754 dst.x = ctx->area.x + ctx->area.w - dst.w; 2755 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst); 2756 2757 /* Top right */ 2758 src.y = 0.0f; 2759 dst.y = ctx->area.y; 2760 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst); 2761 2762 /* Left */ 2763 src.x = 0.0f; 2764 src.y = one_third_src_height; 2765 dst.x = ctx->area.x; 2766 dst.y = ctx->area.y + one_third_src_height; 2767 dst.w = one_third_src_width; 2768 dst.h = ctx->area.h - 2 * one_third_src_height; 2769 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst); 2770 2771 /* Right */ 2772 src.x = ctx->background_width - one_third_src_width; 2773 dst.x = ctx->area.x + ctx->area.w - one_third_src_width; 2774 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst); 2775 2776 /* Top */ 2777 src.x = one_third_src_width; 2778 src.y = 0.0f; 2779 dst.x = ctx->area.x + one_third_src_width; 2780 dst.y = ctx->area.y; 2781 dst.w = ctx->area.w - 2 * one_third_src_width; 2782 dst.h = one_third_src_height; 2783 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst); 2784 2785 /* Bottom */ 2786 src.y = ctx->background_height - src.h; 2787 dst.y = ctx->area.y + ctx->area.h - one_third_src_height; 2788 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst); 2789 2790 /* Center */ 2791 src.x = one_third_src_width; 2792 src.y = one_third_src_height; 2793 dst.x = ctx->area.x + one_third_src_width; 2794 dst.y = ctx->area.y + one_third_src_height; 2795 dst.w = ctx->area.w - 2 * one_third_src_width; 2796 dst.h = ctx->area.h - 2 * one_third_src_height; 2797 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst); 2798 2799 /* Label */ 2800 dst.x = ctx->area.x + ctx->area.w / 2 - ctx->label_width / 2; 2801 dst.y = ctx->area.y + ctx->area.h / 2 - ctx->label_height / 2; 2802 SDLTest_DrawString(ctx->renderer, dst.x, dst.y, ctx->label); 2803} 2804 2805void DestroyGamepadButton(GamepadButton *ctx) 2806{ 2807 if (!ctx) { 2808 return; 2809 } 2810 2811 SDL_DestroyTexture(ctx->background); 2812 SDL_free(ctx->label); 2813 SDL_free(ctx); 2814} 2815 2816 2817typedef struct 2818{ 2819 char *guid; 2820 char *name; 2821 int num_elements; 2822 char **keys; 2823 char **values; 2824} MappingParts; 2825 2826static bool AddMappingKeyValue(MappingParts *parts, char *key, char *value); 2827 2828static bool AddMappingHalfAxisValue(MappingParts *parts, const char *key, const char *value, char sign) 2829{ 2830 char *new_key, *new_value; 2831 2832 if (SDL_asprintf(&new_key, "%c%s", sign, key) < 0) { 2833 return false; 2834 } 2835 2836 if (*value && value[SDL_strlen(value) - 1] == '~') { 2837 /* Invert the sign of the bound axis */ 2838 if (sign == '+') { 2839 sign = '-'; 2840 } else { 2841 sign = '+'; 2842 } 2843 } 2844 2845 if (SDL_asprintf(&new_value, "%c%s", sign, value) < 0) { 2846 SDL_free(new_key); 2847 return false; 2848 } 2849 if (new_value[SDL_strlen(new_value) - 1] == '~') { 2850 new_value[SDL_strlen(new_value) - 1] = '\0'; 2851 } 2852 2853 return AddMappingKeyValue(parts, new_key, new_value); 2854} 2855 2856static bool AddMappingKeyValue(MappingParts *parts, char *key, char *value) 2857{ 2858 int axis; 2859 char **new_keys, **new_values; 2860 2861 if (!key || !value) { 2862 SDL_free(key); 2863 SDL_free(value); 2864 return false; 2865 } 2866 2867 /* Split axis values for easy binding purposes */ 2868 for (axis = 0; axis < SDL_GAMEPAD_AXIS_LEFT_TRIGGER; ++axis) { 2869 if (SDL_strcmp(key, SDL_GetGamepadStringForAxis((SDL_GamepadAxis)axis)) == 0) { 2870 bool result; 2871 2872 result = AddMappingHalfAxisValue(parts, key, value, '-') && 2873 AddMappingHalfAxisValue(parts, key, value, '+'); 2874 SDL_free(key); 2875 SDL_free(value); 2876 return result; 2877 } 2878 } 2879 2880 new_keys = (char **)SDL_realloc(parts->keys, (parts->num_elements + 1) * sizeof(*new_keys)); 2881 if (!new_keys) { 2882 return false; 2883 } 2884 parts->keys = new_keys; 2885 2886 new_values = (char **)SDL_realloc(parts->values, (parts->num_elements + 1) * sizeof(*new_values)); 2887 if (!new_values) { 2888 return false; 2889 } 2890 parts->values = new_values; 2891 2892 new_keys[parts->num_elements] = key; 2893 new_values[parts->num_elements] = value; 2894 ++parts->num_elements; 2895 return true; 2896} 2897 2898static void SplitMapping(const char *mapping, MappingParts *parts) 2899{ 2900 const char *current, *comma, *colon, *key, *value; 2901 char *new_key, *new_value; 2902 2903 SDL_zerop(parts); 2904 2905 if (!mapping || !*mapping) { 2906 return; 2907 } 2908 2909 /* Get the guid */ 2910 current = mapping; 2911 comma = SDL_strchr(current, ','); 2912 if (!comma) { 2913 parts->guid = SDL_strdup(current); 2914 return; 2915 } 2916 parts->guid = SDL_strndup(current, (comma - current)); 2917 current = comma + 1; 2918 2919 /* Get the name */ 2920 comma = SDL_strchr(current, ','); 2921 if (!comma) { 2922 parts->name = SDL_strdup(current); 2923 return; 2924 } 2925 if (*current != '*') { 2926 parts->name = SDL_strndup(current, (comma - current)); 2927 } 2928 current = comma + 1; 2929 2930 for (;;) { 2931 colon = SDL_strchr(current, ':'); 2932 if (!colon) { 2933 break; 2934 } 2935 2936 key = current; 2937 value = colon + 1; 2938 comma = SDL_strchr(value, ','); 2939 2940 new_key = SDL_strndup(key, (colon - key)); 2941 if (comma) { 2942 new_value = SDL_strndup(value, (comma - value)); 2943 } else { 2944 new_value = SDL_strdup(value); 2945 } 2946 if (!AddMappingKeyValue(parts, new_key, new_value)) { 2947 break; 2948 } 2949 2950 if (comma) { 2951 current = comma + 1; 2952 } else { 2953 break; 2954 } 2955 } 2956} 2957 2958static int FindMappingKey(const MappingParts *parts, const char *key) 2959{ 2960 int i; 2961 2962 if (key) { 2963 for (i = 0; i < parts->num_elements; ++i) { 2964 if (SDL_strcmp(key, parts->keys[i]) == 0) { 2965 return i; 2966 } 2967 } 2968 } 2969 return -1; 2970} 2971 2972static void RemoveMappingValueAt(MappingParts *parts, int index) 2973{ 2974 SDL_free(parts->keys[index]); 2975 SDL_free(parts->values[index]); 2976 --parts->num_elements; 2977 if (index < parts->num_elements) { 2978 SDL_memmove(&parts->keys[index], &parts->keys[index] + 1, (parts->num_elements - index) * sizeof(parts->keys[index])); 2979 SDL_memmove(&parts->values[index], &parts->values[index] + 1, (parts->num_elements - index) * sizeof(parts->values[index])); 2980 } 2981} 2982 2983static void ConvertBAXYMapping(MappingParts *parts) 2984{ 2985 int i; 2986 bool baxy_mapping = false; 2987 2988 for (i = 0; i < parts->num_elements; ++i) { 2989 const char *key = parts->keys[i]; 2990 const char *value = parts->values[i]; 2991 2992 if (SDL_strcmp(key, "hint") == 0 && 2993 SDL_strcmp(value, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") == 0) { 2994 baxy_mapping = true; 2995 } 2996 } 2997 2998 if (!baxy_mapping) { 2999 return; 3000 } 3001 3002 /* Swap buttons, invert hint */ 3003 for (i = 0; i < parts->num_elements; ++i) { 3004 char *key = parts->keys[i]; 3005 char *value = parts->values[i]; 3006 3007 if (SDL_strcmp(key, "a") == 0) { 3008 parts->keys[i] = SDL_strdup("b"); 3009 SDL_free(key); 3010 } else if (SDL_strcmp(key, "b") == 0) { 3011 parts->keys[i] = SDL_strdup("a"); 3012 SDL_free(key); 3013 } else if (SDL_strcmp(key, "x") == 0) { 3014 parts->keys[i] = SDL_strdup("y"); 3015 SDL_free(key); 3016 } else if (SDL_strcmp(key, "y") == 0) { 3017 parts->keys[i] = SDL_strdup("x"); 3018 SDL_free(key); 3019 } else if (SDL_strcmp(key, "hint") == 0 && 3020 SDL_strcmp(value, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") == 0) { 3021 parts->values[i] = SDL_strdup("!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1"); 3022 SDL_free(value); 3023 } 3024 } 3025} 3026 3027static void UpdateLegacyElements(MappingParts *parts) 3028{ 3029 ConvertBAXYMapping(parts); 3030} 3031 3032static bool CombineMappingAxes(MappingParts *parts) 3033{ 3034 int i, matching, axis; 3035 3036 for (i = 0; i < parts->num_elements; ++i) { 3037 char *key = parts->keys[i]; 3038 char *current_value; 3039 char *matching_key; 3040 char *matching_value; 3041 3042 if (*key != '-' && *key != '+') { 3043 continue; 3044 } 3045 3046 for (axis = 0; axis < SDL_GAMEPAD_AXIS_LEFT_TRIGGER; ++axis) { 3047 if (SDL_strcmp(key + 1, SDL_GetGamepadStringForAxis((SDL_GamepadAxis)axis)) == 0) { 3048 /* Look for a matching axis with the opposite sign */ 3049 if (SDL_asprintf(&matching_key, "%c%s", (*key == '-' ? '+' : '-'), key + 1) < 0) { 3050 return false; 3051 } 3052 matching = FindMappingKey(parts, matching_key); 3053 if (matching >= 0) { 3054 /* Check to see if they're bound to the same axis */ 3055 current_value = parts->values[i]; 3056 matching_value = parts->values[matching]; 3057 if (((*current_value == '-' && *matching_value == '+') || 3058 (*current_value == '+' && *matching_value == '-')) && 3059 SDL_strcmp(current_value + 1, matching_value + 1) == 0) { 3060 /* Combine these axes */ 3061 if (*key == *current_value) { 3062 SDL_memmove(current_value, current_value + 1, SDL_strlen(current_value)); 3063 } else { 3064 /* Invert the bound axis */ 3065 SDL_memmove(current_value, current_value + 1, SDL_strlen(current_value)-1); 3066 current_value[SDL_strlen(current_value) - 1] = '~'; 3067 } 3068 SDL_memmove(key, key + 1, SDL_strlen(key)); 3069 RemoveMappingValueAt(parts, matching); 3070 } 3071 } 3072 SDL_free(matching_key); 3073 break; 3074 } 3075 } 3076 } 3077 return true; 3078} 3079 3080typedef struct 3081{ 3082 MappingParts *parts; 3083 int index; 3084} MappingSortEntry; 3085 3086static int SDLCALL SortMapping(const void *a, const void *b) 3087{ 3088 MappingSortEntry *A = (MappingSortEntry *)a; 3089 MappingSortEntry *B = (MappingSortEntry *)b; 3090 const char *keyA = A->parts->keys[A->index]; 3091 const char *keyB = B->parts->keys[B->index]; 3092 3093 return SDL_strcmp(keyA, keyB); 3094} 3095 3096static void MoveSortedEntry(const char *key, MappingSortEntry *sort_order, int num_elements, bool front) 3097{ 3098 int i; 3099 3100 for (i = 0; i < num_elements; ++i) { 3101 MappingSortEntry *entry = &sort_order[i]; 3102 if (SDL_strcmp(key, entry->parts->keys[entry->index]) == 0) { 3103 if (front && i != 0) { 3104 MappingSortEntry tmp = sort_order[i]; 3105 SDL_memmove(&sort_order[1], &sort_order[0], sizeof(*sort_order)*i); 3106 sort_order[0] = tmp; 3107 } else if (!front && i != (num_elements - 1)) { 3108 MappingSortEntry tmp = sort_order[i]; 3109 SDL_memmove(&sort_order[i], &sort_order[i + 1], sizeof(*sort_order)*(num_elements - i - 1)); 3110 sort_order[num_elements - 1] = tmp; 3111 } 3112 break; 3113 } 3114 } 3115} 3116 3117static char *JoinMapping(MappingParts *parts) 3118{ 3119 int i; 3120 size_t length; 3121 char *mapping; 3122 const char *guid; 3123 const char *name; 3124 MappingSortEntry *sort_order; 3125 3126 UpdateLegacyElements(parts); 3127 CombineMappingAxes(parts); 3128 3129 guid = parts->guid; 3130 if (!guid || !*guid) { 3131 guid = "*"; 3132 } 3133 3134 name = parts->name; 3135 if (!name || !*name) { 3136 name = "*"; 3137 } 3138 3139 length = SDL_strlen(guid) + 1 + SDL_strlen(name) + 1; 3140 for (i = 0; i < parts->num_elements; ++i) { 3141 length += SDL_strlen(parts->keys[i]) + 1; 3142 length += SDL_strlen(parts->values[i]) + 1; 3143 } 3144 length += 1; 3145 3146 /* The sort order is: crc, platform, type, *, sdk, hint */ 3147 sort_order = SDL_stack_alloc(MappingSortEntry, parts->num_elements); 3148 for (i = 0; i < parts->num_elements; ++i) { 3149 sort_order[i].parts = parts; 3150 sort_order[i].index = i; 3151 } 3152 SDL_qsort(sort_order, parts->num_elements, sizeof(*sort_order), SortMapping); 3153 MoveSortedEntry("face", sort_order, parts->num_elements, true); 3154 MoveSortedEntry("type", sort_order, parts->num_elements, true); 3155 MoveSortedEntry("platform", sort_order, parts->num_elements, true); 3156 MoveSortedEntry("crc", sort_order, parts->num_elements, true); 3157 MoveSortedEntry("sdk>=", sort_order, parts->num_elements, false); 3158 MoveSortedEntry("sdk<=", sort_order, parts->num_elements, false); 3159 MoveSortedEntry("hint", sort_order, parts->num_elements, false); 3160 3161 /* Move platform to the front */ 3162 3163 mapping = (char *)SDL_malloc(length); 3164 if (mapping) { 3165 *mapping = '\0'; 3166 SDL_strlcat(mapping, guid, length); 3167 SDL_strlcat(mapping, ",", length); 3168 SDL_strlcat(mapping, name, length); 3169 SDL_strlcat(mapping, ",", length); 3170 for (i = 0; i < parts->num_elements; ++i) { 3171 int next = sort_order[i].index; 3172 SDL_strlcat(mapping, parts->keys[next], length); 3173 SDL_strlcat(mapping, ":", length); 3174 SDL_strlcat(mapping, parts->values[next], length); 3175 SDL_strlcat(mapping, ",", length); 3176 } 3177 } 3178 3179 SDL_stack_free(sort_order); 3180 3181 return mapping; 3182} 3183 3184static void FreeMappingParts(MappingParts *parts) 3185{ 3186 int i; 3187 3188 SDL_free(parts->guid); 3189 SDL_free(parts->name); 3190 for (i = 0; i < parts->num_elements; ++i) { 3191 SDL_free(parts->keys[i]); 3192 SDL_free(parts->values[i]); 3193 } 3194 SDL_free(parts->keys); 3195 SDL_free(parts->values); 3196 SDL_zerop(parts); 3197} 3198 3199/* Create a new mapping from the parts and free the old mapping and parts */ 3200static char *RecreateMapping(MappingParts *parts, char *mapping) 3201{ 3202 char *new_mapping = JoinMapping(parts); 3203 if (new_mapping) { 3204 SDL_free(mapping); 3205 mapping = new_mapping; 3206 } 3207 FreeMappingParts(parts); 3208 return mapping; 3209} 3210 3211static const char *GetLegacyKey(const char *key, bool baxy) 3212{ 3213 if (SDL_strcmp(key, SDL_GetGamepadStringForButton(SDL_GAMEPAD_BUTTON_SOUTH)) == 0) { 3214 if (baxy) { 3215 return "b"; 3216 } else { 3217 return "a"; 3218 } 3219 } 3220 3221 if (SDL_strcmp(key, SDL_GetGamepadStringForButton(SDL_GAMEPAD_BUTTON_EAST)) == 0) { 3222 if (baxy) { 3223 return "a"; 3224 } else { 3225 return "b"; 3226 } 3227 } 3228 3229 if (SDL_strcmp(key, SDL_GetGamepadStringForButton(SDL_GAMEPAD_BUTTON_WEST)) == 0) { 3230 if (baxy) { 3231 return "y"; 3232 } else { 3233 return "x"; 3234 } 3235 } 3236 3237 if (SDL_strcmp(key, SDL_GetGamepadStringForButton(SDL_GAMEPAD_BUTTON_NORTH)) == 0) { 3238 if (baxy) { 3239 return "y"; 3240 } else { 3241 return "x"; 3242 } 3243 } 3244 3245 return key; 3246} 3247 3248static bool MappingHasKey(const char *mapping, const char *key) 3249{ 3250 int i; 3251 MappingParts parts; 3252 bool result = false; 3253 3254 SplitMapping(mapping, &parts); 3255 i = FindMappingKey(&parts, key); 3256 if (i < 0) { 3257 bool baxy_mapping = false; 3258 3259 if (mapping && SDL_strstr(mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) { 3260 baxy_mapping = true; 3261 } 3262 i = FindMappingKey(&parts, GetLegacyKey(key, baxy_mapping)); 3263 } 3264 if (i >= 0) { 3265 result = true; 3266 } 3267 FreeMappingParts(&parts); 3268 3269 return result; 3270} 3271 3272static char *GetMappingValue(const char *mapping, const char *key) 3273{ 3274 int i; 3275 MappingParts parts; 3276 char *value = NULL; 3277 3278 SplitMapping(mapping, &parts); 3279 i = FindMappingKey(&parts, key); 3280 if (i < 0) { 3281 bool baxy_mapping = false; 3282 3283 if (mapping && SDL_strstr(mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) { 3284 baxy_mapping = true; 3285 } 3286 i = FindMappingKey(&parts, GetLegacyKey(key, baxy_mapping)); 3287 } 3288 if (i >= 0) { 3289 value = parts.values[i]; 3290 parts.values[i] = NULL; /* So we don't free it */ 3291 } 3292 FreeMappingParts(&parts); 3293 3294 return value; 3295} 3296 3297static char *SetMappingValue(char *mapping, const char *key, const char *value) 3298{ 3299 MappingParts parts; 3300 int i; 3301 char *new_key = NULL; 3302 char *new_value = NULL; 3303 char **new_keys = NULL; 3304 char **new_values = NULL; 3305 bool result = false; 3306 3307 if (!key) { 3308 return mapping; 3309 } 3310 3311 SplitMapping(mapping, &parts); 3312 i = FindMappingKey(&parts, key); 3313 if (i >= 0) { 3314 new_value = SDL_strdup(value); 3315 if (new_value) { 3316 SDL_free(parts.values[i]); 3317 parts.values[i] = new_value; 3318 result = true; 3319 } 3320 } else { 3321 int count = parts.num_elements; 3322 3323 new_key = SDL_strdup(key); 3324 if (new_key) { 3325 new_value = SDL_strdup(value); 3326 if (new_value) { 3327 new_keys = (char **)SDL_realloc(parts.keys, (count + 1) * sizeof(*new_keys)); 3328 if (new_keys) { 3329 new_values = (char **)SDL_realloc(parts.values, (count + 1) * sizeof(*new_values)); 3330 if (new_values) { 3331 new_keys[count] = new_key; 3332 new_values[count] = new_value; 3333 parts.num_elements = (count + 1); 3334 parts.keys = new_keys; 3335 parts.values = new_values; 3336 result = true; 3337 } 3338 } 3339 } 3340 } 3341 } 3342 3343 if (result) { 3344 mapping = RecreateMapping(&parts, mapping); 3345 } else { 3346 SDL_free(new_key); 3347 SDL_free(new_value); 3348 SDL_free(new_keys); 3349 SDL_free(new_values); 3350 } 3351 return mapping; 3352} 3353 3354static char *RemoveMappingValue(char *mapping, const char *key) 3355{ 3356 MappingParts parts; 3357 int i; 3358 3359 SplitMapping(mapping, &parts); 3360 i = FindMappingKey(&parts, key); 3361 if (i >= 0) { 3362 RemoveMappingValueAt(&parts, i); 3363 } 3364 return RecreateMapping(&parts, mapping); 3365} 3366 3367bool MappingHasBindings(const char *mapping) 3368{ 3369 MappingParts parts; 3370 int i; 3371 bool result = false; 3372 3373 if (!mapping || !*mapping) { 3374 return false; 3375 } 3376 3377 SplitMapping(mapping, &parts); 3378 for (i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) { 3379 if (FindMappingKey(&parts, SDL_GetGamepadStringForButton((SDL_GamepadButton)i)) >= 0) { 3380 result = true; 3381 break; 3382 } 3383 } 3384 if (!result) { 3385 for (i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) { 3386 if (FindMappingKey(&parts, SDL_GetGamepadStringForAxis((SDL_GamepadAxis)i)) >= 0) { 3387 result = true; 3388 break; 3389 } 3390 } 3391 } 3392 FreeMappingParts(&parts); 3393 3394 return result; 3395} 3396 3397bool MappingHasName(const char *mapping) 3398{ 3399 MappingParts parts; 3400 bool result; 3401 3402 SplitMapping(mapping, &parts); 3403 result = parts.name ? true : false; 3404 FreeMappingParts(&parts); 3405 return result; 3406} 3407 3408char *GetMappingName(const char *mapping) 3409{ 3410 MappingParts parts; 3411 char *name; 3412 3413 SplitMapping(mapping, &parts); 3414 name = parts.name; 3415 parts.name = NULL; /* Don't free the name we're about to return */ 3416 FreeMappingParts(&parts); 3417 return name; 3418} 3419 3420char *SetMappingName(char *mapping, const char *name) 3421{ 3422 MappingParts parts; 3423 char *new_name, *spot; 3424 size_t length; 3425 3426 if (!name) { 3427 return mapping; 3428 } 3429 3430 /* Remove any leading whitespace */ 3431 while (*name && SDL_isspace(*name)) { 3432 ++name; 3433 } 3434 3435 new_name = SDL_strdup(name); 3436 if (!new_name) { 3437 return mapping; 3438 } 3439 3440 /* Remove any commas, which are field separators in the mapping */ 3441 length = SDL_strlen(new_name); 3442 while ((spot = SDL_strchr(new_name, ',')) != NULL) { 3443 SDL_memmove(spot, spot + 1, length - (spot - new_name) + 1); 3444 --length; 3445 } 3446 3447 /* Remove any trailing whitespace */ 3448 while (length > 0 && SDL_isspace(new_name[length - 1])) { 3449 --length; 3450 } 3451 3452 /* See if we have anything left */ 3453 if (length == 0) { 3454 SDL_free(new_name); 3455 return mapping; 3456 } 3457 3458 /* null terminate to cut off anything we've trimmed */ 3459 new_name[length] = '\0'; 3460 3461 SplitMapping(mapping, &parts); 3462 SDL_free(parts.name); 3463 parts.name = new_name; 3464 return RecreateMapping(&parts, mapping); 3465} 3466 3467 3468const char *GetGamepadTypeString(SDL_GamepadType type) 3469{ 3470 switch (type) { 3471 case SDL_GAMEPAD_TYPE_XBOX360: 3472 return "Xbox 360"; 3473 case SDL_GAMEPAD_TYPE_XBOXONE: 3474 return "Xbox One"; 3475 case SDL_GAMEPAD_TYPE_PS3: 3476 return "PS3"; 3477 case SDL_GAMEPAD_TYPE_PS4: 3478 return "PS4"; 3479 case SDL_GAMEPAD_TYPE_PS5: 3480 return "PS5"; 3481 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: 3482 return "Nintendo Switch"; 3483 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: 3484 return "Joy-Con (L)"; 3485 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: 3486 return "Joy-Con (R)"; 3487 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: 3488 return "Joy-Con Pair"; 3489 case SDL_GAMEPAD_TYPE_GAMECUBE: 3490 return "GameCube"; 3491 default: 3492 return ""; 3493 } 3494} 3495 3496SDL_GamepadType GetMappingType(const char *mapping) 3497{ 3498 return SDL_GetGamepadTypeFromString(GetMappingValue(mapping, "type")); 3499} 3500 3501char *SetMappingType(char *mapping, SDL_GamepadType type) 3502{ 3503 const char *type_string = SDL_GetGamepadStringForType(type); 3504 if (!type_string || type == SDL_GAMEPAD_TYPE_UNKNOWN) { 3505 return RemoveMappingValue(mapping, "type"); 3506 } else { 3507 return SetMappingValue(mapping, "type", type_string); 3508 } 3509} 3510 3511static const char *GetElementKey(int element) 3512{ 3513 if (element < SDL_GAMEPAD_BUTTON_COUNT) { 3514 return SDL_GetGamepadStringForButton((SDL_GamepadButton)element); 3515 } else { 3516 static char key[16]; 3517 3518 switch (element) { 3519 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE: 3520 SDL_snprintf(key, sizeof(key), "-%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFTX)); 3521 break; 3522 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE: 3523 SDL_snprintf(key, sizeof(key), "+%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFTX)); 3524 break; 3525 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE: 3526 SDL_snprintf(key, sizeof(key), "-%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFTY)); 3527 break; 3528 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE: 3529 SDL_snprintf(key, sizeof(key), "+%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFTY)); 3530 break; 3531 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE: 3532 SDL_snprintf(key, sizeof(key), "-%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHTX)); 3533 break; 3534 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE: 3535 SDL_snprintf(key, sizeof(key), "+%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHTX)); 3536 break; 3537 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE: 3538 SDL_snprintf(key, sizeof(key), "-%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHTY)); 3539 break; 3540 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE: 3541 SDL_snprintf(key, sizeof(key), "+%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHTY)); 3542 break; 3543 case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER: 3544 return SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFT_TRIGGER); 3545 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER: 3546 return SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHT_TRIGGER); 3547 default: 3548 return NULL; 3549 } 3550 return key; 3551 } 3552} 3553 3554bool MappingHasElement(const char *mapping, int element) 3555{ 3556 const char *key; 3557 3558 key = GetElementKey(element); 3559 if (!key) { 3560 return false; 3561 } 3562 return MappingHasKey(mapping, key); 3563} 3564 3565char *GetElementBinding(const char *mapping, int element) 3566{ 3567 const char *key; 3568 3569 key = GetElementKey(element); 3570 if (!key) { 3571 return NULL; 3572 } 3573 return GetMappingValue(mapping, key); 3574} 3575 3576char *SetElementBinding(char *mapping, int element, const char *binding) 3577{ 3578 if (binding) { 3579 return SetMappingValue(mapping, GetElementKey(element), binding); 3580 } else { 3581 return RemoveMappingValue(mapping, GetElementKey(element)); 3582 } 3583} 3584 3585int GetElementForBinding(char *mapping, const char *binding) 3586{ 3587 MappingParts parts; 3588 int i, element; 3589 int result = SDL_GAMEPAD_ELEMENT_INVALID; 3590 3591 if (!binding) { 3592 return SDL_GAMEPAD_ELEMENT_INVALID; 3593 } 3594 3595 SplitMapping(mapping, &parts); 3596 for (i = 0; i < parts.num_elements; ++i) { 3597 if (SDL_strcmp(binding, parts.values[i]) == 0) { 3598 for (element = 0; element < SDL_GAMEPAD_ELEMENT_MAX; ++element) { 3599 const char *key = GetElementKey(element); 3600 if (key && SDL_strcmp(key, parts.keys[i]) == 0) { 3601 result = element; 3602 break; 3603 } 3604 } 3605 break; 3606 } 3607 } 3608 FreeMappingParts(&parts); 3609 3610 return result; 3611} 3612 3613bool MappingHasBinding(const char *mapping, const char *binding) 3614{ 3615 MappingParts parts; 3616 int i; 3617 bool result = false; 3618 3619 if (!binding) { 3620 return false; 3621 } 3622 3623 SplitMapping(mapping, &parts); 3624 for (i = 0; i < parts.num_elements; ++i) { 3625 if (SDL_strcmp(binding, parts.values[i]) == 0) { 3626 result = true; 3627 break; 3628 } 3629 } 3630 FreeMappingParts(&parts); 3631 3632 return result; 3633} 3634 3635char *ClearMappingBinding(char *mapping, const char *binding) 3636{ 3637 MappingParts parts; 3638 int i; 3639 bool modified = false; 3640 3641 if (!binding) { 3642 return mapping; 3643 } 3644 3645 SplitMapping(mapping, &parts); 3646 for (i = parts.num_elements - 1; i >= 0; --i) { 3647 if (SDL_strcmp(binding, parts.values[i]) == 0) { 3648 RemoveMappingValueAt(&parts, i); 3649 modified = true; 3650 } 3651 } 3652 if (modified) { 3653 return RecreateMapping(&parts, mapping); 3654 } else { 3655 FreeMappingParts(&parts); 3656 return mapping; 3657 } 3658} 3659[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.