summaryrefslogtreecommitdiff
path: root/src/contrib/SDL-3.2.20/test/gamepadutils.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-08-30 16:53:58 -0700
committer3gg <3gg@shellblade.net>2025-08-30 16:53:58 -0700
commit6aaedb813fa11ba0679c3051bc2eb28646b9506c (patch)
tree34acbfc9840e02cb4753e6306ea7ce978bf8b58e /src/contrib/SDL-3.2.20/test/gamepadutils.c
parent8f228ade99dd3d4c8da9b78ade1815c9adf85c8f (diff)
Update to SDL3
Diffstat (limited to 'src/contrib/SDL-3.2.20/test/gamepadutils.c')
-rw-r--r--src/contrib/SDL-3.2.20/test/gamepadutils.c2972
1 files changed, 2972 insertions, 0 deletions
diff --git a/src/contrib/SDL-3.2.20/test/gamepadutils.c b/src/contrib/SDL-3.2.20/test/gamepadutils.c
new file mode 100644
index 0000000..910887f
--- /dev/null
+++ b/src/contrib/SDL-3.2.20/test/gamepadutils.c
@@ -0,0 +1,2972 @@
1/*
2 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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_bayx.h"
20#include "gamepad_face_sony.h"
21#include "gamepad_battery.h"
22#include "gamepad_battery_wired.h"
23#include "gamepad_touchpad.h"
24#include "gamepad_button.h"
25#include "gamepad_button_small.h"
26#include "gamepad_axis.h"
27#include "gamepad_axis_arrow.h"
28#include "gamepad_button_background.h"
29#include "gamepad_wired.h"
30#include "gamepad_wireless.h"
31
32
33/* This is indexed by gamepad element */
34static const struct
35{
36 int x;
37 int y;
38} button_positions[] = {
39 { 413, 190 }, /* SDL_GAMEPAD_BUTTON_SOUTH */
40 { 456, 156 }, /* SDL_GAMEPAD_BUTTON_EAST */
41 { 372, 159 }, /* SDL_GAMEPAD_BUTTON_WEST */
42 { 415, 127 }, /* SDL_GAMEPAD_BUTTON_NORTH */
43 { 199, 157 }, /* SDL_GAMEPAD_BUTTON_BACK */
44 { 257, 153 }, /* SDL_GAMEPAD_BUTTON_GUIDE */
45 { 314, 157 }, /* SDL_GAMEPAD_BUTTON_START */
46 { 98, 177 }, /* SDL_GAMEPAD_BUTTON_LEFT_STICK */
47 { 331, 254 }, /* SDL_GAMEPAD_BUTTON_RIGHT_STICK */
48 { 102, 65 }, /* SDL_GAMEPAD_BUTTON_LEFT_SHOULDER */
49 { 421, 61 }, /* SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER */
50 { 179, 213 }, /* SDL_GAMEPAD_BUTTON_DPAD_UP */
51 { 179, 274 }, /* SDL_GAMEPAD_BUTTON_DPAD_DOWN */
52 { 141, 242 }, /* SDL_GAMEPAD_BUTTON_DPAD_LEFT */
53 { 211, 242 }, /* SDL_GAMEPAD_BUTTON_DPAD_RIGHT */
54 { 257, 199 }, /* SDL_GAMEPAD_BUTTON_MISC1 */
55 { 157, 160 }, /* SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 */
56 { 355, 160 }, /* SDL_GAMEPAD_BUTTON_LEFT_PADDLE1 */
57 { 157, 200 }, /* SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2 */
58 { 355, 200 }, /* SDL_GAMEPAD_BUTTON_LEFT_PADDLE2 */
59};
60
61/* This is indexed by gamepad element */
62static const struct
63{
64 int x;
65 int y;
66 double angle;
67} axis_positions[] = {
68 { 99, 178, 270.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE */
69 { 99, 178, 90.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE */
70 { 99, 178, 0.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE */
71 { 99, 178, 180.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE */
72 { 331, 256, 270.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE */
73 { 331, 256, 90.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE */
74 { 331, 256, 0.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE */
75 { 331, 256, 180.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE */
76 { 116, 5, 180.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER */
77 { 400, 5, 180.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER */
78};
79
80static SDL_FRect touchpad_area = {
81 148.0f, 20.0f, 216.0f, 118.0f
82};
83
84typedef struct
85{
86 bool down;
87 float x;
88 float y;
89 float pressure;
90} GamepadTouchpadFinger;
91
92struct GamepadImage
93{
94 SDL_Renderer *renderer;
95 SDL_Texture *front_texture;
96 SDL_Texture *back_texture;
97 SDL_Texture *face_abxy_texture;
98 SDL_Texture *face_bayx_texture;
99 SDL_Texture *face_sony_texture;
100 SDL_Texture *connection_texture[2];
101 SDL_Texture *battery_texture[2];
102 SDL_Texture *touchpad_texture;
103 SDL_Texture *button_texture;
104 SDL_Texture *axis_texture;
105 float gamepad_width;
106 float gamepad_height;
107 float face_width;
108 float face_height;
109 float connection_width;
110 float connection_height;
111 float battery_width;
112 float battery_height;
113 float touchpad_width;
114 float touchpad_height;
115 float button_width;
116 float button_height;
117 float axis_width;
118 float axis_height;
119
120 float x;
121 float y;
122 bool showing_front;
123 bool showing_touchpad;
124 SDL_GamepadType type;
125 ControllerDisplayMode display_mode;
126
127 bool elements[SDL_GAMEPAD_ELEMENT_MAX];
128
129 SDL_JoystickConnectionState connection_state;
130 SDL_PowerState battery_state;
131 int battery_percent;
132
133 int num_fingers;
134 GamepadTouchpadFinger *fingers;
135};
136
137static SDL_Texture *CreateTexture(SDL_Renderer *renderer, unsigned char *data, unsigned int len)
138{
139 SDL_Texture *texture = NULL;
140 SDL_Surface *surface;
141 SDL_IOStream *src = SDL_IOFromConstMem(data, len);
142 if (src) {
143 surface = SDL_LoadBMP_IO(src, true);
144 if (surface) {
145 texture = SDL_CreateTextureFromSurface(renderer, surface);
146 SDL_DestroySurface(surface);
147 }
148 }
149 return texture;
150}
151
152GamepadImage *CreateGamepadImage(SDL_Renderer *renderer)
153{
154 GamepadImage *ctx = SDL_calloc(1, sizeof(*ctx));
155 if (ctx) {
156 ctx->renderer = renderer;
157 ctx->front_texture = CreateTexture(renderer, gamepad_front_bmp, gamepad_front_bmp_len);
158 ctx->back_texture = CreateTexture(renderer, gamepad_back_bmp, gamepad_back_bmp_len);
159 SDL_GetTextureSize(ctx->front_texture, &ctx->gamepad_width, &ctx->gamepad_height);
160
161 ctx->face_abxy_texture = CreateTexture(renderer, gamepad_face_abxy_bmp, gamepad_face_abxy_bmp_len);
162 ctx->face_bayx_texture = CreateTexture(renderer, gamepad_face_bayx_bmp, gamepad_face_bayx_bmp_len);
163 ctx->face_sony_texture = CreateTexture(renderer, gamepad_face_sony_bmp, gamepad_face_sony_bmp_len);
164 SDL_GetTextureSize(ctx->face_abxy_texture, &ctx->face_width, &ctx->face_height);
165
166 ctx->connection_texture[0] = CreateTexture(renderer, gamepad_wired_bmp, gamepad_wired_bmp_len);
167 ctx->connection_texture[1] = CreateTexture(renderer, gamepad_wireless_bmp, gamepad_wireless_bmp_len);
168 SDL_GetTextureSize(ctx->connection_texture[0], &ctx->connection_width, &ctx->connection_height);
169
170 ctx->battery_texture[0] = CreateTexture(renderer, gamepad_battery_bmp, gamepad_battery_bmp_len);
171 ctx->battery_texture[1] = CreateTexture(renderer, gamepad_battery_wired_bmp, gamepad_battery_wired_bmp_len);
172 SDL_GetTextureSize(ctx->battery_texture[0], &ctx->battery_width, &ctx->battery_height);
173
174 ctx->touchpad_texture = CreateTexture(renderer, gamepad_touchpad_bmp, gamepad_touchpad_bmp_len);
175 SDL_GetTextureSize(ctx->touchpad_texture, &ctx->touchpad_width, &ctx->touchpad_height);
176
177 ctx->button_texture = CreateTexture(renderer, gamepad_button_bmp, gamepad_button_bmp_len);
178 SDL_GetTextureSize(ctx->button_texture, &ctx->button_width, &ctx->button_height);
179 SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21);
180
181 ctx->axis_texture = CreateTexture(renderer, gamepad_axis_bmp, gamepad_axis_bmp_len);
182 SDL_GetTextureSize(ctx->axis_texture, &ctx->axis_width, &ctx->axis_height);
183 SDL_SetTextureColorMod(ctx->axis_texture, 10, 255, 21);
184
185 ctx->showing_front = true;
186 }
187 return ctx;
188}
189
190void SetGamepadImagePosition(GamepadImage *ctx, float x, float y)
191{
192 if (!ctx) {
193 return;
194 }
195
196 ctx->x = x;
197 ctx->y = y;
198}
199
200void GetGamepadImageArea(GamepadImage *ctx, SDL_FRect *area)
201{
202 if (!ctx) {
203 SDL_zerop(area);
204 return;
205 }
206
207 area->x = ctx->x;
208 area->y = ctx->y;
209 area->w = ctx->gamepad_width;
210 area->h = ctx->gamepad_height;
211 if (ctx->showing_touchpad) {
212 area->h += ctx->touchpad_height;
213 }
214}
215
216void GetGamepadTouchpadArea(GamepadImage *ctx, SDL_FRect *area)
217{
218 if (!ctx) {
219 SDL_zerop(area);
220 return;
221 }
222
223 area->x = ctx->x + (ctx->gamepad_width - ctx->touchpad_width) / 2 + touchpad_area.x;
224 area->y = ctx->y + ctx->gamepad_height + touchpad_area.y;
225 area->w = touchpad_area.w;
226 area->h = touchpad_area.h;
227}
228
229void SetGamepadImageShowingFront(GamepadImage *ctx, bool showing_front)
230{
231 if (!ctx) {
232 return;
233 }
234
235 ctx->showing_front = showing_front;
236}
237
238SDL_GamepadType GetGamepadImageType(GamepadImage *ctx)
239{
240 if (!ctx) {
241 return SDL_GAMEPAD_TYPE_UNKNOWN;
242 }
243
244 return ctx->type;
245}
246
247void SetGamepadImageDisplayMode(GamepadImage *ctx, ControllerDisplayMode display_mode)
248{
249 if (!ctx) {
250 return;
251 }
252
253 ctx->display_mode = display_mode;
254}
255
256float GetGamepadImageButtonWidth(GamepadImage *ctx)
257{
258 if (!ctx) {
259 return 0;
260 }
261
262 return ctx->button_width;
263}
264
265float GetGamepadImageButtonHeight(GamepadImage *ctx)
266{
267 if (!ctx) {
268 return 0;
269 }
270
271 return ctx->button_height;
272}
273
274float GetGamepadImageAxisWidth(GamepadImage *ctx)
275{
276 if (!ctx) {
277 return 0;
278 }
279
280 return ctx->axis_width;
281}
282
283float GetGamepadImageAxisHeight(GamepadImage *ctx)
284{
285 if (!ctx) {
286 return 0;
287 }
288
289 return ctx->axis_height;
290}
291
292int GetGamepadImageElementAt(GamepadImage *ctx, float x, float y)
293{
294 SDL_FPoint point;
295 int i;
296
297 if (!ctx) {
298 return SDL_GAMEPAD_ELEMENT_INVALID;
299 }
300
301 point.x = x;
302 point.y = y;
303
304 if (ctx->showing_front) {
305 for (i = 0; i < SDL_arraysize(axis_positions); ++i) {
306 const int element = SDL_GAMEPAD_BUTTON_COUNT + i;
307 SDL_FRect rect;
308
309 if (element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER ||
310 element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER) {
311 rect.w = ctx->axis_width;
312 rect.h = ctx->axis_height;
313 rect.x = ctx->x + axis_positions[i].x - rect.w / 2;
314 rect.y = ctx->y + axis_positions[i].y - rect.h / 2;
315 if (SDL_PointInRectFloat(&point, &rect)) {
316 if (element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER) {
317 return SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER;
318 } else {
319 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER;
320 }
321 }
322 } else if (element == SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE) {
323 rect.w = ctx->button_width * 2.0f;
324 rect.h = ctx->button_height * 2.0f;
325 rect.x = ctx->x + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].x - rect.w / 2;
326 rect.y = ctx->y + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].y - rect.h / 2;
327 if (SDL_PointInRectFloat(&point, &rect)) {
328 float delta_x, delta_y;
329 float delta_squared;
330 float thumbstick_radius = ctx->button_width * 0.1f;
331
332 delta_x = (x - (ctx->x + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].x));
333 delta_y = (y - (ctx->y + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].y));
334 delta_squared = (delta_x * delta_x) + (delta_y * delta_y);
335 if (delta_squared > (thumbstick_radius * thumbstick_radius)) {
336 float angle = SDL_atan2f(delta_y, delta_x) + SDL_PI_F;
337 if (angle < SDL_PI_F * 0.25f) {
338 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE;
339 } else if (angle < SDL_PI_F * 0.75f) {
340 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE;
341 } else if (angle < SDL_PI_F * 1.25f) {
342 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE;
343 } else if (angle < SDL_PI_F * 1.75f) {
344 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE;
345 } else {
346 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE;
347 }
348 }
349 }
350 } else if (element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE) {
351 rect.w = ctx->button_width * 2.0f;
352 rect.h = ctx->button_height * 2.0f;
353 rect.x = ctx->x + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].x - rect.w / 2;
354 rect.y = ctx->y + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].y - rect.h / 2;
355 if (SDL_PointInRectFloat(&point, &rect)) {
356 float delta_x, delta_y;
357 float delta_squared;
358 float thumbstick_radius = ctx->button_width * 0.1f;
359
360 delta_x = (x - (ctx->x + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].x));
361 delta_y = (y - (ctx->y + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].y));
362 delta_squared = (delta_x * delta_x) + (delta_y * delta_y);
363 if (delta_squared > (thumbstick_radius * thumbstick_radius)) {
364 float angle = SDL_atan2f(delta_y, delta_x) + SDL_PI_F;
365 if (angle < SDL_PI_F * 0.25f) {
366 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE;
367 } else if (angle < SDL_PI_F * 0.75f) {
368 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE;
369 } else if (angle < SDL_PI_F * 1.25f) {
370 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE;
371 } else if (angle < SDL_PI_F * 1.75f) {
372 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE;
373 } else {
374 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE;
375 }
376 }
377 }
378 }
379 }
380 }
381
382 for (i = 0; i < SDL_arraysize(button_positions); ++i) {
383 bool on_front = true;
384
385 if (i >= SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 && i <= SDL_GAMEPAD_BUTTON_LEFT_PADDLE2) {
386 on_front = false;
387 }
388 if (on_front == ctx->showing_front) {
389 SDL_FRect rect;
390 rect.x = ctx->x + button_positions[i].x - ctx->button_width / 2;
391 rect.y = ctx->y + button_positions[i].y - ctx->button_height / 2;
392 rect.w = ctx->button_width;
393 rect.h = ctx->button_height;
394 if (SDL_PointInRectFloat(&point, &rect)) {
395 return (SDL_GamepadButton)i;
396 }
397 }
398 }
399 return SDL_GAMEPAD_ELEMENT_INVALID;
400}
401
402void ClearGamepadImage(GamepadImage *ctx)
403{
404 if (!ctx) {
405 return;
406 }
407
408 SDL_zeroa(ctx->elements);
409}
410
411void SetGamepadImageElement(GamepadImage *ctx, int element, bool active)
412{
413 if (!ctx) {
414 return;
415 }
416
417 ctx->elements[element] = active;
418}
419
420void UpdateGamepadImageFromGamepad(GamepadImage *ctx, SDL_Gamepad *gamepad)
421{
422 int i;
423
424 if (!ctx) {
425 return;
426 }
427
428 ctx->type = SDL_GetGamepadType(gamepad);
429 char *mapping = SDL_GetGamepadMapping(gamepad);
430 if (mapping) {
431 if (SDL_strstr(mapping, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS")) {
432 /* Just for display purposes */
433 ctx->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO;
434 }
435 SDL_free(mapping);
436 }
437
438 for (i = 0; i < SDL_GAMEPAD_BUTTON_TOUCHPAD; ++i) {
439 const SDL_GamepadButton button = (SDL_GamepadButton)i;
440
441 SetGamepadImageElement(ctx, button, SDL_GetGamepadButton(gamepad, button));
442 }
443
444 for (i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) {
445 const SDL_GamepadAxis axis = (SDL_GamepadAxis)i;
446 const Sint16 deadzone = 8000; /* !!! FIXME: real deadzone */
447 const Sint16 value = SDL_GetGamepadAxis(gamepad, axis);
448 switch (i) {
449 case SDL_GAMEPAD_AXIS_LEFTX:
450 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE, (value < -deadzone));
451 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE, (value > deadzone));
452 break;
453 case SDL_GAMEPAD_AXIS_RIGHTX:
454 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE, (value < -deadzone));
455 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE, (value > deadzone));
456 break;
457 case SDL_GAMEPAD_AXIS_LEFTY:
458 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE, (value < -deadzone));
459 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE, (value > deadzone));
460 break;
461 case SDL_GAMEPAD_AXIS_RIGHTY:
462 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE, (value < -deadzone));
463 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE, (value > deadzone));
464 break;
465 case SDL_GAMEPAD_AXIS_LEFT_TRIGGER:
466 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER, (value > deadzone));
467 break;
468 case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
469 SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER, (value > deadzone));
470 break;
471 default:
472 break;
473 }
474 }
475
476 ctx->connection_state = SDL_GetGamepadConnectionState(gamepad);
477 ctx->battery_state = SDL_GetGamepadPowerInfo(gamepad, &ctx->battery_percent);
478
479 if (SDL_GetNumGamepadTouchpads(gamepad) > 0) {
480 int num_fingers = SDL_GetNumGamepadTouchpadFingers(gamepad, 0);
481 if (num_fingers != ctx->num_fingers) {
482 GamepadTouchpadFinger *fingers = (GamepadTouchpadFinger *)SDL_realloc(ctx->fingers, num_fingers * sizeof(*fingers));
483 if (fingers) {
484 ctx->fingers = fingers;
485 ctx->num_fingers = num_fingers;
486 } else {
487 num_fingers = SDL_min(ctx->num_fingers, num_fingers);
488 }
489 }
490 for (i = 0; i < num_fingers; ++i) {
491 GamepadTouchpadFinger *finger = &ctx->fingers[i];
492
493 SDL_GetGamepadTouchpadFinger(gamepad, 0, i, &finger->down, &finger->x, &finger->y, &finger->pressure);
494 }
495 ctx->showing_touchpad = true;
496 } else {
497 if (ctx->fingers) {
498 SDL_free(ctx->fingers);
499 ctx->fingers = NULL;
500 ctx->num_fingers = 0;
501 }
502 ctx->showing_touchpad = false;
503 }
504}
505
506void RenderGamepadImage(GamepadImage *ctx)
507{
508 SDL_FRect dst;
509 int i;
510
511 if (!ctx) {
512 return;
513 }
514
515 dst.x = ctx->x;
516 dst.y = ctx->y;
517 dst.w = ctx->gamepad_width;
518 dst.h = ctx->gamepad_height;
519
520 if (ctx->showing_front) {
521 SDL_RenderTexture(ctx->renderer, ctx->front_texture, NULL, &dst);
522 } else {
523 SDL_RenderTexture(ctx->renderer, ctx->back_texture, NULL, &dst);
524 }
525
526 for (i = 0; i < SDL_arraysize(button_positions); ++i) {
527 if (ctx->elements[i]) {
528 SDL_GamepadButton button_position = (SDL_GamepadButton)i;
529 bool on_front = true;
530
531 if (i >= SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1 && i <= SDL_GAMEPAD_BUTTON_LEFT_PADDLE2) {
532 on_front = false;
533 }
534 if (on_front == ctx->showing_front) {
535 dst.w = ctx->button_width;
536 dst.h = ctx->button_height;
537 dst.x = ctx->x + button_positions[button_position].x - dst.w / 2;
538 dst.y = ctx->y + button_positions[button_position].y - dst.h / 2;
539 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst);
540 }
541 }
542 }
543
544 if (ctx->showing_front) {
545 dst.x = ctx->x + 363;
546 dst.y = ctx->y + 118;
547 dst.w = ctx->face_width;
548 dst.h = ctx->face_height;
549
550 switch (SDL_GetGamepadButtonLabelForType(ctx->type, SDL_GAMEPAD_BUTTON_SOUTH)) {
551 case SDL_GAMEPAD_BUTTON_LABEL_A:
552 SDL_RenderTexture(ctx->renderer, ctx->face_abxy_texture, NULL, &dst);
553 break;
554 case SDL_GAMEPAD_BUTTON_LABEL_B:
555 SDL_RenderTexture(ctx->renderer, ctx->face_bayx_texture, NULL, &dst);
556 break;
557 case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
558 SDL_RenderTexture(ctx->renderer, ctx->face_sony_texture, NULL, &dst);
559 break;
560 default:
561 break;
562 }
563 }
564
565 if (ctx->showing_front) {
566 for (i = 0; i < SDL_arraysize(axis_positions); ++i) {
567 const int element = SDL_GAMEPAD_BUTTON_COUNT + i;
568 if (ctx->elements[element]) {
569 const double angle = axis_positions[i].angle;
570 dst.w = ctx->axis_width;
571 dst.h = ctx->axis_height;
572 dst.x = ctx->x + axis_positions[i].x - dst.w / 2;
573 dst.y = ctx->y + axis_positions[i].y - dst.h / 2;
574 SDL_RenderTextureRotated(ctx->renderer, ctx->axis_texture, NULL, &dst, angle, NULL, SDL_FLIP_NONE);
575 }
576 }
577 }
578
579 if (ctx->display_mode == CONTROLLER_MODE_TESTING) {
580 dst.x = ctx->x + ctx->gamepad_width - ctx->battery_width - 4 - ctx->connection_width;
581 dst.y = ctx->y;
582 dst.w = ctx->connection_width;
583 dst.h = ctx->connection_height;
584
585 switch (ctx->connection_state) {
586 case SDL_JOYSTICK_CONNECTION_WIRED:
587 SDL_RenderTexture(ctx->renderer, ctx->connection_texture[0], NULL, &dst);
588 break;
589 case SDL_JOYSTICK_CONNECTION_WIRELESS:
590 SDL_RenderTexture(ctx->renderer, ctx->connection_texture[1], NULL, &dst);
591 break;
592 default:
593 break;
594 }
595 }
596
597 if (ctx->display_mode == CONTROLLER_MODE_TESTING &&
598 ctx->battery_state != SDL_POWERSTATE_NO_BATTERY &&
599 ctx->battery_state != SDL_POWERSTATE_UNKNOWN) {
600 Uint8 r, g, b, a;
601 SDL_FRect fill;
602
603 dst.x = ctx->x + ctx->gamepad_width - ctx->battery_width;
604 dst.y = ctx->y;
605 dst.w = ctx->battery_width;
606 dst.h = ctx->battery_height;
607
608 SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a);
609 if (ctx->battery_percent > 40) {
610 SDL_SetRenderDrawColor(ctx->renderer, 0x00, 0xD4, 0x50, 0xFF);
611 } else if (ctx->battery_percent > 10) {
612 SDL_SetRenderDrawColor(ctx->renderer, 0xFF, 0xC7, 0x00, 0xFF);
613 } else {
614 SDL_SetRenderDrawColor(ctx->renderer, 0xC8, 0x1D, 0x13, 0xFF);
615 }
616
617 fill = dst;
618 fill.x += 2;
619 fill.y += 2;
620 fill.h -= 4;
621 fill.w = 25.0f * (ctx->battery_percent / 100.0f);
622 SDL_RenderFillRect(ctx->renderer, &fill);
623 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
624
625 if (ctx->battery_state == SDL_POWERSTATE_ON_BATTERY) {
626 SDL_RenderTexture(ctx->renderer, ctx->battery_texture[0], NULL, &dst);
627 } else {
628 SDL_RenderTexture(ctx->renderer, ctx->battery_texture[1], NULL, &dst);
629 }
630 }
631
632 if (ctx->display_mode == CONTROLLER_MODE_TESTING && ctx->showing_touchpad) {
633 dst.x = ctx->x + (ctx->gamepad_width - ctx->touchpad_width) / 2;
634 dst.y = ctx->y + ctx->gamepad_height;
635 dst.w = ctx->touchpad_width;
636 dst.h = ctx->touchpad_height;
637 SDL_RenderTexture(ctx->renderer, ctx->touchpad_texture, NULL, &dst);
638
639 for (i = 0; i < ctx->num_fingers; ++i) {
640 GamepadTouchpadFinger *finger = &ctx->fingers[i];
641
642 if (finger->down) {
643 dst.x = ctx->x + (ctx->gamepad_width - ctx->touchpad_width) / 2;
644 dst.x += touchpad_area.x + finger->x * touchpad_area.w;
645 dst.x -= ctx->button_width / 2;
646 dst.y = ctx->y + ctx->gamepad_height;
647 dst.y += touchpad_area.y + finger->y * touchpad_area.h;
648 dst.y -= ctx->button_height / 2;
649 dst.w = ctx->button_width;
650 dst.h = ctx->button_height;
651 SDL_SetTextureAlphaMod(ctx->button_texture, (Uint8)(finger->pressure * SDL_ALPHA_OPAQUE));
652 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst);
653 SDL_SetTextureAlphaMod(ctx->button_texture, SDL_ALPHA_OPAQUE);
654 }
655 }
656 }
657}
658
659void DestroyGamepadImage(GamepadImage *ctx)
660{
661 if (ctx) {
662 int i;
663
664 SDL_DestroyTexture(ctx->front_texture);
665 SDL_DestroyTexture(ctx->back_texture);
666 SDL_DestroyTexture(ctx->face_abxy_texture);
667 SDL_DestroyTexture(ctx->face_bayx_texture);
668 SDL_DestroyTexture(ctx->face_sony_texture);
669 for (i = 0; i < SDL_arraysize(ctx->battery_texture); ++i) {
670 SDL_DestroyTexture(ctx->battery_texture[i]);
671 }
672 SDL_DestroyTexture(ctx->touchpad_texture);
673 SDL_DestroyTexture(ctx->button_texture);
674 SDL_DestroyTexture(ctx->axis_texture);
675 SDL_free(ctx);
676 }
677}
678
679
680static const char *gamepad_button_names[] = {
681 "South",
682 "East",
683 "West",
684 "North",
685 "Back",
686 "Guide",
687 "Start",
688 "Left Stick",
689 "Right Stick",
690 "Left Shoulder",
691 "Right Shoulder",
692 "DPAD Up",
693 "DPAD Down",
694 "DPAD Left",
695 "DPAD Right",
696 "Misc1",
697 "Right Paddle 1",
698 "Left Paddle 1",
699 "Right Paddle 2",
700 "Left Paddle 2",
701 "Touchpad",
702 "Misc2",
703 "Misc3",
704 "Misc4",
705 "Misc5",
706 "Misc6",
707};
708SDL_COMPILE_TIME_ASSERT(gamepad_button_names, SDL_arraysize(gamepad_button_names) == SDL_GAMEPAD_BUTTON_COUNT);
709
710static const char *gamepad_axis_names[] = {
711 "LeftX",
712 "LeftY",
713 "RightX",
714 "RightY",
715 "Left Trigger",
716 "Right Trigger",
717};
718SDL_COMPILE_TIME_ASSERT(gamepad_axis_names, SDL_arraysize(gamepad_axis_names) == SDL_GAMEPAD_AXIS_COUNT);
719
720struct GamepadDisplay
721{
722 SDL_Renderer *renderer;
723 SDL_Texture *button_texture;
724 SDL_Texture *arrow_texture;
725 float button_width;
726 float button_height;
727 float arrow_width;
728 float arrow_height;
729
730 float accel_data[3];
731 float gyro_data[3];
732 Uint64 last_sensor_update;
733
734 ControllerDisplayMode display_mode;
735 int element_highlighted;
736 bool element_pressed;
737 int element_selected;
738
739 SDL_FRect area;
740};
741
742GamepadDisplay *CreateGamepadDisplay(SDL_Renderer *renderer)
743{
744 GamepadDisplay *ctx = SDL_calloc(1, sizeof(*ctx));
745 if (ctx) {
746 ctx->renderer = renderer;
747
748 ctx->button_texture = CreateTexture(renderer, gamepad_button_small_bmp, gamepad_button_small_bmp_len);
749 SDL_GetTextureSize(ctx->button_texture, &ctx->button_width, &ctx->button_height);
750
751 ctx->arrow_texture = CreateTexture(renderer, gamepad_axis_arrow_bmp, gamepad_axis_arrow_bmp_len);
752 SDL_GetTextureSize(ctx->arrow_texture, &ctx->arrow_width, &ctx->arrow_height);
753
754 ctx->element_highlighted = SDL_GAMEPAD_ELEMENT_INVALID;
755 ctx->element_selected = SDL_GAMEPAD_ELEMENT_INVALID;
756 }
757 return ctx;
758}
759
760void SetGamepadDisplayDisplayMode(GamepadDisplay *ctx, ControllerDisplayMode display_mode)
761{
762 if (!ctx) {
763 return;
764 }
765
766 ctx->display_mode = display_mode;
767}
768
769void SetGamepadDisplayArea(GamepadDisplay *ctx, const SDL_FRect *area)
770{
771 if (!ctx) {
772 return;
773 }
774
775 SDL_copyp(&ctx->area, area);
776}
777
778static bool GetBindingString(const char *label, const char *mapping, char *text, size_t size)
779{
780 char *key;
781 char *value, *end;
782 size_t length;
783 bool found = false;
784
785 *text = '\0';
786
787 if (!mapping) {
788 return false;
789 }
790
791 key = SDL_strstr(mapping, label);
792 while (key && size > 1) {
793 if (found) {
794 *text++ = ',';
795 *text = '\0';
796 --size;
797 } else {
798 found = true;
799 }
800 value = key + SDL_strlen(label);
801 end = SDL_strchr(value, ',');
802 if (end) {
803 length = (end - value);
804 } else {
805 length = SDL_strlen(value);
806 }
807 if (length >= size) {
808 length = size - 1;
809 }
810 SDL_memcpy(text, value, length);
811 text[length] = '\0';
812 text += length;
813 size -= length;
814 key = SDL_strstr(value, label);
815 }
816 return found;
817}
818
819static bool GetButtonBindingString(SDL_GamepadButton button, const char *mapping, char *text, size_t size)
820{
821 char label[32];
822 bool baxy_mapping = false;
823
824 if (!mapping) {
825 return false;
826 }
827
828 SDL_snprintf(label, sizeof(label), ",%s:", SDL_GetGamepadStringForButton(button));
829 if (GetBindingString(label, mapping, text, size)) {
830 return true;
831 }
832
833 /* Try the legacy button names */
834 if (SDL_strstr(mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) {
835 baxy_mapping = true;
836 }
837 switch (button) {
838 case SDL_GAMEPAD_BUTTON_SOUTH:
839 if (baxy_mapping) {
840 return GetBindingString(",b:", mapping, text, size);
841 } else {
842 return GetBindingString(",a:", mapping, text, size);
843 }
844 case SDL_GAMEPAD_BUTTON_EAST:
845 if (baxy_mapping) {
846 return GetBindingString(",a:", mapping, text, size);
847 } else {
848 return GetBindingString(",b:", mapping, text, size);
849 }
850 case SDL_GAMEPAD_BUTTON_WEST:
851 if (baxy_mapping) {
852 return GetBindingString(",y:", mapping, text, size);
853 } else {
854 return GetBindingString(",x:", mapping, text, size);
855 }
856 case SDL_GAMEPAD_BUTTON_NORTH:
857 if (baxy_mapping) {
858 return GetBindingString(",x:", mapping, text, size);
859 } else {
860 return GetBindingString(",y:", mapping, text, size);
861 }
862 default:
863 return false;
864 }
865}
866
867static bool GetAxisBindingString(SDL_GamepadAxis axis, int direction, const char *mapping, char *text, size_t size)
868{
869 char label[32];
870
871 /* Check for explicit half-axis */
872 if (direction < 0) {
873 SDL_snprintf(label, sizeof(label), ",-%s:", SDL_GetGamepadStringForAxis(axis));
874 } else {
875 SDL_snprintf(label, sizeof(label), ",+%s:", SDL_GetGamepadStringForAxis(axis));
876 }
877 if (GetBindingString(label, mapping, text, size)) {
878 return true;
879 }
880
881 /* Get the binding for the whole axis and split it if necessary */
882 SDL_snprintf(label, sizeof(label), ",%s:", SDL_GetGamepadStringForAxis(axis));
883 if (!GetBindingString(label, mapping, text, size)) {
884 return false;
885 }
886 if (axis != SDL_GAMEPAD_AXIS_LEFT_TRIGGER && axis != SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
887 if (*text == 'a') {
888 /* Split the axis */
889 size_t length = SDL_strlen(text) + 1;
890 if ((length + 1) <= size) {
891 SDL_memmove(text + 1, text, length);
892 if (text[SDL_strlen(text) - 1] == '~') {
893 direction *= -1;
894 text[SDL_strlen(text) - 1] = '\0';
895 }
896 if (direction > 0) {
897 *text = '+';
898 } else {
899 *text = '-';
900 }
901 }
902 }
903 }
904 return true;
905}
906
907void SetGamepadDisplayHighlight(GamepadDisplay *ctx, int element, bool pressed)
908{
909 if (!ctx) {
910 return;
911 }
912
913 ctx->element_highlighted = element;
914 ctx->element_pressed = pressed;
915}
916
917void SetGamepadDisplaySelected(GamepadDisplay *ctx, int element)
918{
919 if (!ctx) {
920 return;
921 }
922
923 ctx->element_selected = element;
924}
925
926int GetGamepadDisplayElementAt(GamepadDisplay *ctx, SDL_Gamepad *gamepad, float x, float y)
927{
928 int i;
929 const float margin = 8.0f;
930 const float center = ctx->area.w / 2.0f;
931 const float arrow_extent = 48.0f;
932 SDL_FPoint point;
933 SDL_FRect rect;
934
935 if (!ctx) {
936 return SDL_GAMEPAD_ELEMENT_INVALID;
937 }
938
939 point.x = x;
940 point.y = y;
941
942 rect.x = ctx->area.x + margin;
943 rect.y = ctx->area.y + margin + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
944 rect.w = ctx->area.w - (margin * 2);
945 rect.h = ctx->button_height;
946
947 for (i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) {
948 SDL_GamepadButton button = (SDL_GamepadButton)i;
949
950 if (ctx->display_mode == CONTROLLER_MODE_TESTING &&
951 !SDL_GamepadHasButton(gamepad, button)) {
952 continue;
953 }
954
955
956 if (SDL_PointInRectFloat(&point, &rect)) {
957 return i;
958 }
959
960 rect.y += ctx->button_height + 2.0f;
961 }
962
963 for (i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) {
964 SDL_GamepadAxis axis = (SDL_GamepadAxis)i;
965 SDL_FRect area;
966
967 if (ctx->display_mode == CONTROLLER_MODE_TESTING &&
968 !SDL_GamepadHasAxis(gamepad, axis)) {
969 continue;
970 }
971
972 area.x = rect.x + center + 2.0f;
973 area.y = rect.y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
974 area.w = ctx->arrow_width + arrow_extent;
975 area.h = ctx->button_height;
976
977 if (SDL_PointInRectFloat(&point, &area)) {
978 switch (axis) {
979 case SDL_GAMEPAD_AXIS_LEFTX:
980 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE;
981 case SDL_GAMEPAD_AXIS_LEFTY:
982 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE;
983 case SDL_GAMEPAD_AXIS_RIGHTX:
984 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE;
985 case SDL_GAMEPAD_AXIS_RIGHTY:
986 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE;
987 default:
988 break;
989 }
990 }
991
992 area.x += area.w;
993
994 if (SDL_PointInRectFloat(&point, &area)) {
995 switch (axis) {
996 case SDL_GAMEPAD_AXIS_LEFTX:
997 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE;
998 case SDL_GAMEPAD_AXIS_LEFTY:
999 return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE;
1000 case SDL_GAMEPAD_AXIS_RIGHTX:
1001 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE;
1002 case SDL_GAMEPAD_AXIS_RIGHTY:
1003 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE;
1004 case SDL_GAMEPAD_AXIS_LEFT_TRIGGER:
1005 return SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER;
1006 case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
1007 return SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER;
1008 default:
1009 break;
1010 }
1011 }
1012
1013 rect.y += ctx->button_height + 2.0f;
1014 }
1015 return SDL_GAMEPAD_ELEMENT_INVALID;
1016}
1017
1018static void RenderGamepadElementHighlight(GamepadDisplay *ctx, int element, const SDL_FRect *area)
1019{
1020 if (element == ctx->element_highlighted || element == ctx->element_selected) {
1021 Uint8 r, g, b, a;
1022
1023 SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a);
1024
1025 if (element == ctx->element_highlighted) {
1026 if (ctx->element_pressed) {
1027 SDL_SetRenderDrawColor(ctx->renderer, PRESSED_COLOR);
1028 } else {
1029 SDL_SetRenderDrawColor(ctx->renderer, HIGHLIGHT_COLOR);
1030 }
1031 } else {
1032 SDL_SetRenderDrawColor(ctx->renderer, SELECTED_COLOR);
1033 }
1034 SDL_RenderFillRect(ctx->renderer, area);
1035
1036 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
1037 }
1038}
1039
1040void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
1041{
1042 float x, y;
1043 int i;
1044 char text[128], binding[32];
1045 const float margin = 8.0f;
1046 const float center = ctx->area.w / 2.0f;
1047 const float arrow_extent = 48.0f;
1048 SDL_FRect dst, rect, highlight;
1049 Uint8 r, g, b, a;
1050 char *mapping;
1051 bool has_accel;
1052 bool has_gyro;
1053
1054 if (!ctx) {
1055 return;
1056 }
1057
1058 SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a);
1059
1060 mapping = SDL_GetGamepadMapping(gamepad);
1061
1062 x = ctx->area.x + margin;
1063 y = ctx->area.y + margin;
1064
1065 for (i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) {
1066 SDL_GamepadButton button = (SDL_GamepadButton)i;
1067
1068 if (ctx->display_mode == CONTROLLER_MODE_TESTING &&
1069 !SDL_GamepadHasButton(gamepad, button)) {
1070 continue;
1071 }
1072
1073 highlight.x = x;
1074 highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
1075 highlight.w = ctx->area.w - (margin * 2);
1076 highlight.h = ctx->button_height;
1077 RenderGamepadElementHighlight(ctx, i, &highlight);
1078
1079 SDL_snprintf(text, sizeof(text), "%s:", gamepad_button_names[i]);
1080 SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
1081
1082 if (SDL_GetGamepadButton(gamepad, button)) {
1083 SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21);
1084 } else {
1085 SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255);
1086 }
1087
1088 dst.x = x + center + 2.0f;
1089 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
1090 dst.w = ctx->button_width;
1091 dst.h = ctx->button_height;
1092 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst);
1093
1094 if (ctx->display_mode == CONTROLLER_MODE_BINDING) {
1095 if (GetButtonBindingString(button, mapping, binding, sizeof(binding))) {
1096 dst.x += dst.w + 2 * margin;
1097 SDLTest_DrawString(ctx->renderer, dst.x, y, binding);
1098 }
1099 }
1100
1101 y += ctx->button_height + 2.0f;
1102 }
1103
1104 for (i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) {
1105 SDL_GamepadAxis axis = (SDL_GamepadAxis)i;
1106 bool has_negative = (axis != SDL_GAMEPAD_AXIS_LEFT_TRIGGER && axis != SDL_GAMEPAD_AXIS_RIGHT_TRIGGER);
1107 Sint16 value;
1108
1109 if (ctx->display_mode == CONTROLLER_MODE_TESTING &&
1110 !SDL_GamepadHasAxis(gamepad, axis)) {
1111 continue;
1112 }
1113
1114 value = SDL_GetGamepadAxis(gamepad, axis);
1115
1116 SDL_snprintf(text, sizeof(text), "%s:", gamepad_axis_names[i]);
1117 SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
1118
1119 highlight.x = x + center + 2.0f;
1120 highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
1121 highlight.w = ctx->arrow_width + arrow_extent;
1122 highlight.h = ctx->button_height;
1123
1124 switch (axis) {
1125 case SDL_GAMEPAD_AXIS_LEFTX:
1126 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE, &highlight);
1127 break;
1128 case SDL_GAMEPAD_AXIS_LEFTY:
1129 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE, &highlight);
1130 break;
1131 case SDL_GAMEPAD_AXIS_RIGHTX:
1132 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE, &highlight);
1133 break;
1134 case SDL_GAMEPAD_AXIS_RIGHTY:
1135 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE, &highlight);
1136 break;
1137 default:
1138 break;
1139 }
1140
1141 highlight.x += highlight.w;
1142
1143 switch (axis) {
1144 case SDL_GAMEPAD_AXIS_LEFTX:
1145 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE, &highlight);
1146 break;
1147 case SDL_GAMEPAD_AXIS_LEFTY:
1148 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE, &highlight);
1149 break;
1150 case SDL_GAMEPAD_AXIS_RIGHTX:
1151 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE, &highlight);
1152 break;
1153 case SDL_GAMEPAD_AXIS_RIGHTY:
1154 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE, &highlight);
1155 break;
1156 case SDL_GAMEPAD_AXIS_LEFT_TRIGGER:
1157 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER, &highlight);
1158 break;
1159 case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
1160 RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER, &highlight);
1161 break;
1162 default:
1163 break;
1164 }
1165
1166 dst.x = x + center + 2.0f;
1167 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->arrow_height / 2;
1168 dst.w = ctx->arrow_width;
1169 dst.h = ctx->arrow_height;
1170
1171 if (has_negative) {
1172 if (value == SDL_MIN_SINT16) {
1173 SDL_SetTextureColorMod(ctx->arrow_texture, 10, 255, 21);
1174 } else {
1175 SDL_SetTextureColorMod(ctx->arrow_texture, 255, 255, 255);
1176 }
1177 SDL_RenderTextureRotated(ctx->renderer, ctx->arrow_texture, NULL, &dst, 0.0f, NULL, SDL_FLIP_HORIZONTAL);
1178 }
1179
1180 dst.x += ctx->arrow_width;
1181
1182 SDL_SetRenderDrawColor(ctx->renderer, 200, 200, 200, SDL_ALPHA_OPAQUE);
1183 rect.x = dst.x + arrow_extent - 2.0f;
1184 rect.y = dst.y;
1185 rect.w = 4.0f;
1186 rect.h = ctx->arrow_height;
1187 SDL_RenderFillRect(ctx->renderer, &rect);
1188 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
1189
1190 if (value < 0) {
1191 SDL_SetRenderDrawColor(ctx->renderer, 8, 200, 16, SDL_ALPHA_OPAQUE);
1192 rect.w = ((float)value / SDL_MIN_SINT16) * arrow_extent;
1193 rect.x = dst.x + arrow_extent - rect.w;
1194 rect.y = dst.y + ctx->arrow_height * 0.25f;
1195 rect.h = ctx->arrow_height / 2.0f;
1196 SDL_RenderFillRect(ctx->renderer, &rect);
1197 }
1198
1199 if (ctx->display_mode == CONTROLLER_MODE_BINDING && has_negative) {
1200 if (GetAxisBindingString(axis, -1, mapping, binding, sizeof(binding))) {
1201 float text_x;
1202
1203 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
1204 text_x = dst.x + arrow_extent / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(binding)) / 2;
1205 SDLTest_DrawString(ctx->renderer, text_x, y, binding);
1206 }
1207 }
1208
1209 dst.x += arrow_extent;
1210
1211 if (value > 0) {
1212 SDL_SetRenderDrawColor(ctx->renderer, 8, 200, 16, SDL_ALPHA_OPAQUE);
1213 rect.w = ((float)value / SDL_MAX_SINT16) * arrow_extent;
1214 rect.x = dst.x;
1215 rect.y = dst.y + ctx->arrow_height * 0.25f;
1216 rect.h = ctx->arrow_height / 2.0f;
1217 SDL_RenderFillRect(ctx->renderer, &rect);
1218 }
1219
1220 if (ctx->display_mode == CONTROLLER_MODE_BINDING) {
1221 if (GetAxisBindingString(axis, 1, mapping, binding, sizeof(binding))) {
1222 float text_x;
1223
1224 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
1225 text_x = dst.x + arrow_extent / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(binding)) / 2;
1226 SDLTest_DrawString(ctx->renderer, text_x, y, binding);
1227 }
1228 }
1229
1230 dst.x += arrow_extent;
1231
1232 if (value == SDL_MAX_SINT16) {
1233 SDL_SetTextureColorMod(ctx->arrow_texture, 10, 255, 21);
1234 } else {
1235 SDL_SetTextureColorMod(ctx->arrow_texture, 255, 255, 255);
1236 }
1237 SDL_RenderTexture(ctx->renderer, ctx->arrow_texture, NULL, &dst);
1238
1239 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
1240
1241 y += ctx->button_height + 2.0f;
1242 }
1243
1244 if (ctx->display_mode == CONTROLLER_MODE_TESTING) {
1245 if (SDL_GetNumGamepadTouchpads(gamepad) > 0) {
1246 int num_fingers = SDL_GetNumGamepadTouchpadFingers(gamepad, 0);
1247 for (i = 0; i < num_fingers; ++i) {
1248 bool down;
1249 float finger_x, finger_y, finger_pressure;
1250
1251 if (!SDL_GetGamepadTouchpadFinger(gamepad, 0, i, &down, &finger_x, &finger_y, &finger_pressure)) {
1252 continue;
1253 }
1254
1255 SDL_snprintf(text, sizeof(text), "Touch finger %d:", i);
1256 SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
1257
1258 if (down) {
1259 SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21);
1260 } else {
1261 SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255);
1262 }
1263
1264 dst.x = x + center + 2.0f;
1265 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
1266 dst.w = ctx->button_width;
1267 dst.h = ctx->button_height;
1268 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst);
1269
1270 if (down) {
1271 SDL_snprintf(text, sizeof(text), "(%.2f,%.2f)", finger_x, finger_y);
1272 SDLTest_DrawString(ctx->renderer, x + center + ctx->button_width + 4.0f, y, text);
1273 }
1274
1275 y += ctx->button_height + 2.0f;
1276 }
1277 }
1278
1279 has_accel = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL);
1280 has_gyro = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO);
1281 if (has_accel || has_gyro) {
1282 const int SENSOR_UPDATE_INTERVAL_MS = 100;
1283 Uint64 now = SDL_GetTicks();
1284
1285 if (now >= ctx->last_sensor_update + SENSOR_UPDATE_INTERVAL_MS) {
1286 if (has_accel) {
1287 SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_ACCEL, ctx->accel_data, SDL_arraysize(ctx->accel_data));
1288 }
1289 if (has_gyro) {
1290 SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_GYRO, ctx->gyro_data, SDL_arraysize(ctx->gyro_data));
1291 }
1292 ctx->last_sensor_update = now;
1293 }
1294
1295 if (has_accel) {
1296 SDL_strlcpy(text, "Accelerometer:", sizeof(text));
1297 SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
1298 SDL_snprintf(text, sizeof(text), "(%.2f,%.2f,%.2f)", ctx->accel_data[0], ctx->accel_data[1], ctx->accel_data[2]);
1299 SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text);
1300
1301 y += ctx->button_height + 2.0f;
1302 }
1303
1304 if (has_gyro) {
1305 SDL_strlcpy(text, "Gyro:", sizeof(text));
1306 SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
1307 SDL_snprintf(text, sizeof(text), "(%.2f,%.2f,%.2f)", ctx->gyro_data[0], ctx->gyro_data[1], ctx->gyro_data[2]);
1308 SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text);
1309
1310 y += ctx->button_height + 2.0f;
1311 }
1312 }
1313 }
1314 SDL_free(mapping);
1315}
1316
1317void DestroyGamepadDisplay(GamepadDisplay *ctx)
1318{
1319 if (!ctx) {
1320 return;
1321 }
1322
1323 SDL_DestroyTexture(ctx->button_texture);
1324 SDL_DestroyTexture(ctx->arrow_texture);
1325 SDL_free(ctx);
1326}
1327
1328struct GamepadTypeDisplay
1329{
1330 SDL_Renderer *renderer;
1331
1332 int type_highlighted;
1333 bool type_pressed;
1334 int type_selected;
1335 SDL_GamepadType real_type;
1336
1337 SDL_FRect area;
1338};
1339
1340GamepadTypeDisplay *CreateGamepadTypeDisplay(SDL_Renderer *renderer)
1341{
1342 GamepadTypeDisplay *ctx = SDL_calloc(1, sizeof(*ctx));
1343 if (ctx) {
1344 ctx->renderer = renderer;
1345
1346 ctx->type_highlighted = SDL_GAMEPAD_TYPE_UNSELECTED;
1347 ctx->type_selected = SDL_GAMEPAD_TYPE_UNSELECTED;
1348 ctx->real_type = SDL_GAMEPAD_TYPE_UNKNOWN;
1349 }
1350 return ctx;
1351}
1352
1353void SetGamepadTypeDisplayArea(GamepadTypeDisplay *ctx, const SDL_FRect *area)
1354{
1355 if (!ctx) {
1356 return;
1357 }
1358
1359 SDL_copyp(&ctx->area, area);
1360}
1361
1362void SetGamepadTypeDisplayHighlight(GamepadTypeDisplay *ctx, int type, bool pressed)
1363{
1364 if (!ctx) {
1365 return;
1366 }
1367
1368 ctx->type_highlighted = type;
1369 ctx->type_pressed = pressed;
1370}
1371
1372void SetGamepadTypeDisplaySelected(GamepadTypeDisplay *ctx, int type)
1373{
1374 if (!ctx) {
1375 return;
1376 }
1377
1378 ctx->type_selected = type;
1379}
1380
1381void SetGamepadTypeDisplayRealType(GamepadTypeDisplay *ctx, SDL_GamepadType type)
1382{
1383 if (!ctx) {
1384 return;
1385 }
1386
1387 ctx->real_type = type;
1388}
1389
1390int GetGamepadTypeDisplayAt(GamepadTypeDisplay *ctx, float x, float y)
1391{
1392 int i;
1393 const float margin = 8.0f;
1394 const float line_height = 16.0f;
1395 SDL_FRect highlight;
1396 SDL_FPoint point;
1397
1398 if (!ctx) {
1399 return SDL_GAMEPAD_TYPE_UNSELECTED;
1400 }
1401
1402 point.x = x;
1403 point.y = y;
1404
1405 x = ctx->area.x + margin;
1406 y = ctx->area.y + margin;
1407
1408 for (i = SDL_GAMEPAD_TYPE_UNKNOWN; i < SDL_GAMEPAD_TYPE_COUNT; ++i) {
1409 highlight.x = x;
1410 highlight.y = y;
1411 highlight.w = ctx->area.w - (margin * 2);
1412 highlight.h = line_height;
1413
1414 if (SDL_PointInRectFloat(&point, &highlight)) {
1415 return i;
1416 }
1417
1418 y += line_height;
1419 }
1420 return SDL_GAMEPAD_TYPE_UNSELECTED;
1421}
1422
1423static void RenderGamepadTypeHighlight(GamepadTypeDisplay *ctx, int type, const SDL_FRect *area)
1424{
1425 if (type == ctx->type_highlighted || type == ctx->type_selected) {
1426 Uint8 r, g, b, a;
1427
1428 SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a);
1429
1430 if (type == ctx->type_highlighted) {
1431 if (ctx->type_pressed) {
1432 SDL_SetRenderDrawColor(ctx->renderer, PRESSED_COLOR);
1433 } else {
1434 SDL_SetRenderDrawColor(ctx->renderer, HIGHLIGHT_COLOR);
1435 }
1436 } else {
1437 SDL_SetRenderDrawColor(ctx->renderer, SELECTED_COLOR);
1438 }
1439 SDL_RenderFillRect(ctx->renderer, area);
1440
1441 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
1442 }
1443}
1444
1445void RenderGamepadTypeDisplay(GamepadTypeDisplay *ctx)
1446{
1447 float x, y;
1448 int i;
1449 char text[128];
1450 const float margin = 8.0f;
1451 const float line_height = 16.0f;
1452 SDL_FPoint dst;
1453 SDL_FRect highlight;
1454
1455 if (!ctx) {
1456 return;
1457 }
1458
1459 x = ctx->area.x + margin;
1460 y = ctx->area.y + margin;
1461
1462 for (i = SDL_GAMEPAD_TYPE_UNKNOWN; i < SDL_GAMEPAD_TYPE_COUNT; ++i) {
1463 highlight.x = x;
1464 highlight.y = y;
1465 highlight.w = ctx->area.w - (margin * 2);
1466 highlight.h = line_height;
1467 RenderGamepadTypeHighlight(ctx, i, &highlight);
1468
1469 if (i == SDL_GAMEPAD_TYPE_UNKNOWN) {
1470 if (ctx->real_type == SDL_GAMEPAD_TYPE_UNKNOWN ||
1471 ctx->real_type == SDL_GAMEPAD_TYPE_STANDARD) {
1472 SDL_strlcpy(text, "Auto (Standard)", sizeof(text));
1473 } else {
1474 SDL_snprintf(text, sizeof(text), "Auto (%s)", GetGamepadTypeString(ctx->real_type));
1475 }
1476 } else if (i == SDL_GAMEPAD_TYPE_STANDARD) {
1477 SDL_strlcpy(text, "Standard", sizeof(text));
1478 } else {
1479 SDL_strlcpy(text, GetGamepadTypeString((SDL_GamepadType)i), sizeof(text));
1480 }
1481
1482 dst.x = x + margin;
1483 dst.y = y + line_height / 2 - FONT_CHARACTER_SIZE / 2;
1484 SDLTest_DrawString(ctx->renderer, dst.x, dst.y, text);
1485
1486 y += line_height;
1487 }
1488}
1489
1490void DestroyGamepadTypeDisplay(GamepadTypeDisplay *ctx)
1491{
1492 if (!ctx) {
1493 return;
1494 }
1495
1496 SDL_free(ctx);
1497}
1498
1499
1500struct JoystickDisplay
1501{
1502 SDL_Renderer *renderer;
1503 SDL_Texture *button_texture;
1504 SDL_Texture *arrow_texture;
1505 float button_width;
1506 float button_height;
1507 float arrow_width;
1508 float arrow_height;
1509
1510 SDL_FRect area;
1511
1512 char *element_highlighted;
1513 bool element_pressed;
1514};
1515
1516JoystickDisplay *CreateJoystickDisplay(SDL_Renderer *renderer)
1517{
1518 JoystickDisplay *ctx = SDL_calloc(1, sizeof(*ctx));
1519 if (ctx) {
1520 ctx->renderer = renderer;
1521
1522 ctx->button_texture = CreateTexture(renderer, gamepad_button_small_bmp, gamepad_button_small_bmp_len);
1523 SDL_GetTextureSize(ctx->button_texture, &ctx->button_width, &ctx->button_height);
1524
1525 ctx->arrow_texture = CreateTexture(renderer, gamepad_axis_arrow_bmp, gamepad_axis_arrow_bmp_len);
1526 SDL_GetTextureSize(ctx->arrow_texture, &ctx->arrow_width, &ctx->arrow_height);
1527 }
1528 return ctx;
1529}
1530
1531void SetJoystickDisplayArea(JoystickDisplay *ctx, const SDL_FRect *area)
1532{
1533 if (!ctx) {
1534 return;
1535 }
1536
1537 SDL_copyp(&ctx->area, area);
1538}
1539
1540char *GetJoystickDisplayElementAt(JoystickDisplay *ctx, SDL_Joystick *joystick, float x, float y)
1541{
1542 SDL_FPoint point;
1543 int i;
1544 int nbuttons = SDL_GetNumJoystickButtons(joystick);
1545 int naxes = SDL_GetNumJoystickAxes(joystick);
1546 int nhats = SDL_GetNumJoystickHats(joystick);
1547 char text[32];
1548 const float margin = 8.0f;
1549 const float center = 80.0f;
1550 const float arrow_extent = 48.0f;
1551 SDL_FRect dst, highlight;
1552 char *element = NULL;
1553
1554 if (!ctx) {
1555 return NULL;
1556 }
1557
1558 point.x = x;
1559 point.y = y;
1560
1561 x = ctx->area.x + margin;
1562 y = ctx->area.y + margin;
1563
1564 if (nbuttons > 0) {
1565 y += FONT_LINE_HEIGHT + 2;
1566
1567 for (i = 0; i < nbuttons; ++i) {
1568 highlight.x = x;
1569 highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
1570 highlight.w = center - (margin * 2);
1571 highlight.h = ctx->button_height;
1572 if (SDL_PointInRectFloat(&point, &highlight)) {
1573 SDL_asprintf(&element, "b%d", i);
1574 return element;
1575 }
1576
1577 y += ctx->button_height + 2;
1578 }
1579 }
1580
1581 x = ctx->area.x + margin + center + margin;
1582 y = ctx->area.y + margin;
1583
1584 if (naxes > 0) {
1585 y += FONT_LINE_HEIGHT + 2;
1586
1587 for (i = 0; i < naxes; ++i) {
1588 SDL_snprintf(text, sizeof(text), "%d:", i);
1589
1590 highlight.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2.0f;
1591 highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
1592 highlight.w = ctx->arrow_width + arrow_extent;
1593 highlight.h = ctx->button_height;
1594 if (SDL_PointInRectFloat(&point, &highlight)) {
1595 SDL_asprintf(&element, "-a%d", i);
1596 return element;
1597 }
1598
1599 highlight.x += highlight.w;
1600 if (SDL_PointInRectFloat(&point, &highlight)) {
1601 SDL_asprintf(&element, "+a%d", i);
1602 return element;
1603 }
1604
1605 y += ctx->button_height + 2;
1606 }
1607 }
1608
1609 y += FONT_LINE_HEIGHT + 2;
1610
1611 if (nhats > 0) {
1612 y += FONT_LINE_HEIGHT + 2 + 1.5f * ctx->button_height - FONT_CHARACTER_SIZE / 2;
1613
1614 for (i = 0; i < nhats; ++i) {
1615 SDL_snprintf(text, sizeof(text), "%d:", i);
1616
1617 dst.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2;
1618 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
1619 dst.w = ctx->button_width;
1620 dst.h = ctx->button_height;
1621 if (SDL_PointInRectFloat(&point, &dst)) {
1622 SDL_asprintf(&element, "h%d.%d", i, SDL_HAT_LEFT);
1623 return element;
1624 }
1625
1626 dst.x += ctx->button_width;
1627 dst.y -= ctx->button_height;
1628 if (SDL_PointInRectFloat(&point, &dst)) {
1629 SDL_asprintf(&element, "h%d.%d", i, SDL_HAT_UP);
1630 return element;
1631 }
1632
1633 dst.y += ctx->button_height * 2;
1634 if (SDL_PointInRectFloat(&point, &dst)) {
1635 SDL_asprintf(&element, "h%d.%d", i, SDL_HAT_DOWN);
1636 return element;
1637 }
1638
1639 dst.x += ctx->button_width;
1640 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
1641 if (SDL_PointInRectFloat(&point, &dst)) {
1642 SDL_asprintf(&element, "h%d.%d", i, SDL_HAT_RIGHT);
1643 return element;
1644 }
1645
1646 y += 3 * ctx->button_height + 2;
1647 }
1648 }
1649 return NULL;
1650}
1651
1652void SetJoystickDisplayHighlight(JoystickDisplay *ctx, const char *element, bool pressed)
1653{
1654 if (ctx->element_highlighted) {
1655 SDL_free(ctx->element_highlighted);
1656 ctx->element_highlighted = NULL;
1657 ctx->element_pressed = false;
1658 }
1659
1660 if (element) {
1661 ctx->element_highlighted = SDL_strdup(element);
1662 ctx->element_pressed = pressed;
1663 }
1664}
1665
1666static void RenderJoystickButtonHighlight(JoystickDisplay *ctx, int button, const SDL_FRect *area)
1667{
1668 if (!ctx->element_highlighted || *ctx->element_highlighted != 'b') {
1669 return;
1670 }
1671
1672 if (SDL_atoi(ctx->element_highlighted + 1) == button) {
1673 Uint8 r, g, b, a;
1674
1675 SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a);
1676
1677 if (ctx->element_pressed) {
1678 SDL_SetRenderDrawColor(ctx->renderer, PRESSED_COLOR);
1679 } else {
1680 SDL_SetRenderDrawColor(ctx->renderer, HIGHLIGHT_COLOR);
1681 }
1682 SDL_RenderFillRect(ctx->renderer, area);
1683
1684 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
1685 }
1686}
1687
1688static void RenderJoystickAxisHighlight(JoystickDisplay *ctx, int axis, int direction, const SDL_FRect *area)
1689{
1690 char prefix = (direction < 0 ? '-' : '+');
1691
1692 if (!ctx->element_highlighted ||
1693 ctx->element_highlighted[0] != prefix ||
1694 ctx->element_highlighted[1] != 'a') {
1695 return;
1696 }
1697
1698 if (SDL_atoi(ctx->element_highlighted + 2) == axis) {
1699 Uint8 r, g, b, a;
1700
1701 SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a);
1702
1703 if (ctx->element_pressed) {
1704 SDL_SetRenderDrawColor(ctx->renderer, PRESSED_COLOR);
1705 } else {
1706 SDL_SetRenderDrawColor(ctx->renderer, HIGHLIGHT_COLOR);
1707 }
1708 SDL_RenderFillRect(ctx->renderer, area);
1709
1710 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
1711 }
1712}
1713
1714static bool SetupJoystickHatHighlight(JoystickDisplay *ctx, int hat, int direction)
1715{
1716 if (!ctx->element_highlighted || *ctx->element_highlighted != 'h') {
1717 return false;
1718 }
1719
1720 if (SDL_atoi(ctx->element_highlighted + 1) == hat &&
1721 ctx->element_highlighted[2] == '.' &&
1722 SDL_atoi(ctx->element_highlighted + 3) == direction) {
1723 if (ctx->element_pressed) {
1724 SDL_SetTextureColorMod(ctx->button_texture, PRESSED_TEXTURE_MOD);
1725 } else {
1726 SDL_SetTextureColorMod(ctx->button_texture, HIGHLIGHT_TEXTURE_MOD);
1727 }
1728 return true;
1729 }
1730 return false;
1731}
1732
1733void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick)
1734{
1735 float x, y;
1736 int i;
1737 int nbuttons = SDL_GetNumJoystickButtons(joystick);
1738 int naxes = SDL_GetNumJoystickAxes(joystick);
1739 int nhats = SDL_GetNumJoystickHats(joystick);
1740 char text[32];
1741 const float margin = 8.0f;
1742 const float center = 80.0f;
1743 const float arrow_extent = 48.0f;
1744 SDL_FRect dst, rect, highlight;
1745 Uint8 r, g, b, a;
1746
1747 if (!ctx) {
1748 return;
1749 }
1750
1751 SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a);
1752
1753 x = ctx->area.x + margin;
1754 y = ctx->area.y + margin;
1755
1756 if (nbuttons > 0) {
1757 SDLTest_DrawString(ctx->renderer, x, y, "BUTTONS");
1758 y += FONT_LINE_HEIGHT + 2;
1759
1760 for (i = 0; i < nbuttons; ++i) {
1761 highlight.x = x;
1762 highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
1763 highlight.w = center - (margin * 2);
1764 highlight.h = ctx->button_height;
1765 RenderJoystickButtonHighlight(ctx, i, &highlight);
1766
1767 SDL_snprintf(text, sizeof(text), "%2d:", i);
1768 SDLTest_DrawString(ctx->renderer, x, y, text);
1769
1770 if (SDL_GetJoystickButton(joystick, (Uint8)i)) {
1771 SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21);
1772 } else {
1773 SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255);
1774 }
1775
1776 dst.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2;
1777 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
1778 dst.w = ctx->button_width;
1779 dst.h = ctx->button_height;
1780 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst);
1781
1782 y += ctx->button_height + 2;
1783 }
1784 }
1785
1786 x = ctx->area.x + margin + center + margin;
1787 y = ctx->area.y + margin;
1788
1789 if (naxes > 0) {
1790 SDLTest_DrawString(ctx->renderer, x, y, "AXES");
1791 y += FONT_LINE_HEIGHT + 2;
1792
1793 for (i = 0; i < naxes; ++i) {
1794 Sint16 value = SDL_GetJoystickAxis(joystick, i);
1795
1796 SDL_snprintf(text, sizeof(text), "%d:", i);
1797 SDLTest_DrawString(ctx->renderer, x, y, text);
1798
1799 highlight.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2.0f;
1800 highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
1801 highlight.w = ctx->arrow_width + arrow_extent;
1802 highlight.h = ctx->button_height;
1803 RenderJoystickAxisHighlight(ctx, i, -1, &highlight);
1804
1805 highlight.x += highlight.w;
1806 RenderJoystickAxisHighlight(ctx, i, 1, &highlight);
1807
1808 dst.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2.0f;
1809 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->arrow_height / 2;
1810 dst.w = ctx->arrow_width;
1811 dst.h = ctx->arrow_height;
1812
1813 if (value == SDL_MIN_SINT16) {
1814 SDL_SetTextureColorMod(ctx->arrow_texture, 10, 255, 21);
1815 } else {
1816 SDL_SetTextureColorMod(ctx->arrow_texture, 255, 255, 255);
1817 }
1818 SDL_RenderTextureRotated(ctx->renderer, ctx->arrow_texture, NULL, &dst, 0.0f, NULL, SDL_FLIP_HORIZONTAL);
1819
1820 dst.x += ctx->arrow_width;
1821
1822 SDL_SetRenderDrawColor(ctx->renderer, 200, 200, 200, SDL_ALPHA_OPAQUE);
1823 rect.x = dst.x + arrow_extent - 2.0f;
1824 rect.y = dst.y;
1825 rect.w = 4.0f;
1826 rect.h = ctx->arrow_height;
1827 SDL_RenderFillRect(ctx->renderer, &rect);
1828 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
1829
1830 if (value < 0) {
1831 SDL_SetRenderDrawColor(ctx->renderer, 8, 200, 16, SDL_ALPHA_OPAQUE);
1832 rect.w = ((float)value / SDL_MIN_SINT16) * arrow_extent;
1833 rect.x = dst.x + arrow_extent - rect.w;
1834 rect.y = dst.y + ctx->arrow_height * 0.25f;
1835 rect.h = ctx->arrow_height / 2.0f;
1836 SDL_RenderFillRect(ctx->renderer, &rect);
1837 }
1838
1839 dst.x += arrow_extent;
1840
1841 if (value > 0) {
1842 SDL_SetRenderDrawColor(ctx->renderer, 8, 200, 16, SDL_ALPHA_OPAQUE);
1843 rect.w = ((float)value / SDL_MAX_SINT16) * arrow_extent;
1844 rect.x = dst.x;
1845 rect.y = dst.y + ctx->arrow_height * 0.25f;
1846 rect.h = ctx->arrow_height / 2.0f;
1847 SDL_RenderFillRect(ctx->renderer, &rect);
1848 }
1849
1850 dst.x += arrow_extent;
1851
1852 if (value == SDL_MAX_SINT16) {
1853 SDL_SetTextureColorMod(ctx->arrow_texture, 10, 255, 21);
1854 } else {
1855 SDL_SetTextureColorMod(ctx->arrow_texture, 255, 255, 255);
1856 }
1857 SDL_RenderTexture(ctx->renderer, ctx->arrow_texture, NULL, &dst);
1858
1859 SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a);
1860
1861 y += ctx->button_height + 2;
1862 }
1863 }
1864
1865 y += FONT_LINE_HEIGHT + 2;
1866
1867 if (nhats > 0) {
1868 SDLTest_DrawString(ctx->renderer, x, y, "HATS");
1869 y += FONT_LINE_HEIGHT + 2 + 1.5f * ctx->button_height - FONT_CHARACTER_SIZE / 2;
1870
1871 for (i = 0; i < nhats; ++i) {
1872 Uint8 value = SDL_GetJoystickHat(joystick, i);
1873
1874 SDL_snprintf(text, sizeof(text), "%d:", i);
1875 SDLTest_DrawString(ctx->renderer, x, y, text);
1876
1877 if (value & SDL_HAT_LEFT) {
1878 SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21);
1879 } else if (!SetupJoystickHatHighlight(ctx, i, SDL_HAT_LEFT)) {
1880 SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255);
1881 }
1882
1883 dst.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2;
1884 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
1885 dst.w = ctx->button_width;
1886 dst.h = ctx->button_height;
1887 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst);
1888
1889 if (value & SDL_HAT_UP) {
1890 SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21);
1891 } else if (!SetupJoystickHatHighlight(ctx, i, SDL_HAT_UP)) {
1892 SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255);
1893 }
1894
1895 dst.x += ctx->button_width;
1896 dst.y -= ctx->button_height;
1897 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst);
1898
1899 if (value & SDL_HAT_DOWN) {
1900 SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21);
1901 } else if (!SetupJoystickHatHighlight(ctx, i, SDL_HAT_DOWN)) {
1902 SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255);
1903 }
1904
1905 dst.y += ctx->button_height * 2;
1906 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst);
1907
1908 if (value & SDL_HAT_RIGHT) {
1909 SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21);
1910 } else if (!SetupJoystickHatHighlight(ctx, i, SDL_HAT_RIGHT)) {
1911 SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255);
1912 }
1913
1914 dst.x += ctx->button_width;
1915 dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
1916 SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst);
1917
1918 y += 3 * ctx->button_height + 2;
1919 }
1920 }
1921}
1922
1923void DestroyJoystickDisplay(JoystickDisplay *ctx)
1924{
1925 if (!ctx) {
1926 return;
1927 }
1928
1929 SDL_DestroyTexture(ctx->button_texture);
1930 SDL_DestroyTexture(ctx->arrow_texture);
1931 SDL_free(ctx);
1932}
1933
1934
1935struct GamepadButton
1936{
1937 SDL_Renderer *renderer;
1938 SDL_Texture *background;
1939 float background_width;
1940 float background_height;
1941
1942 SDL_FRect area;
1943
1944 char *label;
1945 float label_width;
1946 float label_height;
1947
1948 bool highlight;
1949 bool pressed;
1950};
1951
1952GamepadButton *CreateGamepadButton(SDL_Renderer *renderer, const char *label)
1953{
1954 GamepadButton *ctx = SDL_calloc(1, sizeof(*ctx));
1955 if (ctx) {
1956 ctx->renderer = renderer;
1957
1958 ctx->background = CreateTexture(renderer, gamepad_button_background_bmp, gamepad_button_background_bmp_len);
1959 SDL_GetTextureSize(ctx->background, &ctx->background_width, &ctx->background_height);
1960
1961 ctx->label = SDL_strdup(label);
1962 ctx->label_width = (float)(FONT_CHARACTER_SIZE * SDL_strlen(label));
1963 ctx->label_height = (float)FONT_CHARACTER_SIZE;
1964 }
1965 return ctx;
1966}
1967
1968void SetGamepadButtonArea(GamepadButton *ctx, const SDL_FRect *area)
1969{
1970 if (!ctx) {
1971 return;
1972 }
1973
1974 SDL_copyp(&ctx->area, area);
1975}
1976
1977void GetGamepadButtonArea(GamepadButton *ctx, SDL_FRect *area)
1978{
1979 if (!ctx) {
1980 SDL_zerop(area);
1981 return;
1982 }
1983
1984 SDL_copyp(area, &ctx->area);
1985}
1986
1987void SetGamepadButtonHighlight(GamepadButton *ctx, bool highlight, bool pressed)
1988{
1989 if (!ctx) {
1990 return;
1991 }
1992
1993 ctx->highlight = highlight;
1994 if (highlight) {
1995 ctx->pressed = pressed;
1996 } else {
1997 ctx->pressed = false;
1998 }
1999}
2000
2001float GetGamepadButtonLabelWidth(GamepadButton *ctx)
2002{
2003 if (!ctx) {
2004 return 0;
2005 }
2006
2007 return ctx->label_width;
2008}
2009
2010float GetGamepadButtonLabelHeight(GamepadButton *ctx)
2011{
2012 if (!ctx) {
2013 return 0;
2014 }
2015
2016 return ctx->label_height;
2017}
2018
2019bool GamepadButtonContains(GamepadButton *ctx, float x, float y)
2020{
2021 SDL_FPoint point;
2022
2023 if (!ctx) {
2024 return false;
2025 }
2026
2027 point.x = x;
2028 point.y = y;
2029 return SDL_PointInRectFloat(&point, &ctx->area);
2030}
2031
2032void RenderGamepadButton(GamepadButton *ctx)
2033{
2034 SDL_FRect src, dst;
2035 float one_third_src_width;
2036 float one_third_src_height;
2037
2038 if (!ctx) {
2039 return;
2040 }
2041
2042 one_third_src_width = ctx->background_width / 3;
2043 one_third_src_height = ctx->background_height / 3;
2044
2045 if (ctx->pressed) {
2046 SDL_SetTextureColorMod(ctx->background, PRESSED_TEXTURE_MOD);
2047 } else if (ctx->highlight) {
2048 SDL_SetTextureColorMod(ctx->background, HIGHLIGHT_TEXTURE_MOD);
2049 } else {
2050 SDL_SetTextureColorMod(ctx->background, 255, 255, 255);
2051 }
2052
2053 /* Top left */
2054 src.x = 0.0f;
2055 src.y = 0.0f;
2056 src.w = one_third_src_width;
2057 src.h = one_third_src_height;
2058 dst.x = ctx->area.x;
2059 dst.y = ctx->area.y;
2060 dst.w = src.w;
2061 dst.h = src.h;
2062 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
2063
2064 /* Bottom left */
2065 src.y = ctx->background_height - src.h;
2066 dst.y = ctx->area.y + ctx->area.h - dst.h;
2067 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
2068
2069 /* Bottom right */
2070 src.x = ctx->background_width - src.w;
2071 dst.x = ctx->area.x + ctx->area.w - dst.w;
2072 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
2073
2074 /* Top right */
2075 src.y = 0.0f;
2076 dst.y = ctx->area.y;
2077 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
2078
2079 /* Left */
2080 src.x = 0.0f;
2081 src.y = one_third_src_height;
2082 dst.x = ctx->area.x;
2083 dst.y = ctx->area.y + one_third_src_height;
2084 dst.w = one_third_src_width;
2085 dst.h = ctx->area.h - 2 * one_third_src_height;
2086 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
2087
2088 /* Right */
2089 src.x = ctx->background_width - one_third_src_width;
2090 dst.x = ctx->area.x + ctx->area.w - one_third_src_width;
2091 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
2092
2093 /* Top */
2094 src.x = one_third_src_width;
2095 src.y = 0.0f;
2096 dst.x = ctx->area.x + one_third_src_width;
2097 dst.y = ctx->area.y;
2098 dst.w = ctx->area.w - 2 * one_third_src_width;
2099 dst.h = one_third_src_height;
2100 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
2101
2102 /* Bottom */
2103 src.y = ctx->background_height - src.h;
2104 dst.y = ctx->area.y + ctx->area.h - one_third_src_height;
2105 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
2106
2107 /* Center */
2108 src.x = one_third_src_width;
2109 src.y = one_third_src_height;
2110 dst.x = ctx->area.x + one_third_src_width;
2111 dst.y = ctx->area.y + one_third_src_height;
2112 dst.w = ctx->area.w - 2 * one_third_src_width;
2113 dst.h = ctx->area.h - 2 * one_third_src_height;
2114 SDL_RenderTexture(ctx->renderer, ctx->background, &src, &dst);
2115
2116 /* Label */
2117 dst.x = ctx->area.x + ctx->area.w / 2 - ctx->label_width / 2;
2118 dst.y = ctx->area.y + ctx->area.h / 2 - ctx->label_height / 2;
2119 SDLTest_DrawString(ctx->renderer, dst.x, dst.y, ctx->label);
2120}
2121
2122void DestroyGamepadButton(GamepadButton *ctx)
2123{
2124 if (!ctx) {
2125 return;
2126 }
2127
2128 SDL_DestroyTexture(ctx->background);
2129 SDL_free(ctx->label);
2130 SDL_free(ctx);
2131}
2132
2133
2134typedef struct
2135{
2136 char *guid;
2137 char *name;
2138 int num_elements;
2139 char **keys;
2140 char **values;
2141} MappingParts;
2142
2143static bool AddMappingKeyValue(MappingParts *parts, char *key, char *value);
2144
2145static bool AddMappingHalfAxisValue(MappingParts *parts, const char *key, const char *value, char sign)
2146{
2147 char *new_key, *new_value;
2148
2149 if (SDL_asprintf(&new_key, "%c%s", sign, key) < 0) {
2150 return false;
2151 }
2152
2153 if (*value && value[SDL_strlen(value) - 1] == '~') {
2154 /* Invert the sign of the bound axis */
2155 if (sign == '+') {
2156 sign = '-';
2157 } else {
2158 sign = '+';
2159 }
2160 }
2161
2162 if (SDL_asprintf(&new_value, "%c%s", sign, value) < 0) {
2163 SDL_free(new_key);
2164 return false;
2165 }
2166 if (new_value[SDL_strlen(new_value) - 1] == '~') {
2167 new_value[SDL_strlen(new_value) - 1] = '\0';
2168 }
2169
2170 return AddMappingKeyValue(parts, new_key, new_value);
2171}
2172
2173static bool AddMappingKeyValue(MappingParts *parts, char *key, char *value)
2174{
2175 int axis;
2176 char **new_keys, **new_values;
2177
2178 if (!key || !value) {
2179 SDL_free(key);
2180 SDL_free(value);
2181 return false;
2182 }
2183
2184 /* Split axis values for easy binding purposes */
2185 for (axis = 0; axis < SDL_GAMEPAD_AXIS_LEFT_TRIGGER; ++axis) {
2186 if (SDL_strcmp(key, SDL_GetGamepadStringForAxis((SDL_GamepadAxis)axis)) == 0) {
2187 bool result;
2188
2189 result = AddMappingHalfAxisValue(parts, key, value, '-') &&
2190 AddMappingHalfAxisValue(parts, key, value, '+');
2191 SDL_free(key);
2192 SDL_free(value);
2193 return result;
2194 }
2195 }
2196
2197 new_keys = (char **)SDL_realloc(parts->keys, (parts->num_elements + 1) * sizeof(*new_keys));
2198 if (!new_keys) {
2199 return false;
2200 }
2201 parts->keys = new_keys;
2202
2203 new_values = (char **)SDL_realloc(parts->values, (parts->num_elements + 1) * sizeof(*new_values));
2204 if (!new_values) {
2205 return false;
2206 }
2207 parts->values = new_values;
2208
2209 new_keys[parts->num_elements] = key;
2210 new_values[parts->num_elements] = value;
2211 ++parts->num_elements;
2212 return true;
2213}
2214
2215static void SplitMapping(const char *mapping, MappingParts *parts)
2216{
2217 const char *current, *comma, *colon, *key, *value;
2218 char *new_key, *new_value;
2219
2220 SDL_zerop(parts);
2221
2222 if (!mapping || !*mapping) {
2223 return;
2224 }
2225
2226 /* Get the guid */
2227 current = mapping;
2228 comma = SDL_strchr(current, ',');
2229 if (!comma) {
2230 parts->guid = SDL_strdup(current);
2231 return;
2232 }
2233 parts->guid = SDL_strndup(current, (comma - current));
2234 current = comma + 1;
2235
2236 /* Get the name */
2237 comma = SDL_strchr(current, ',');
2238 if (!comma) {
2239 parts->name = SDL_strdup(current);
2240 return;
2241 }
2242 if (*current != '*') {
2243 parts->name = SDL_strndup(current, (comma - current));
2244 }
2245 current = comma + 1;
2246
2247 for (;;) {
2248 colon = SDL_strchr(current, ':');
2249 if (!colon) {
2250 break;
2251 }
2252
2253 key = current;
2254 value = colon + 1;
2255 comma = SDL_strchr(value, ',');
2256
2257 new_key = SDL_strndup(key, (colon - key));
2258 if (comma) {
2259 new_value = SDL_strndup(value, (comma - value));
2260 } else {
2261 new_value = SDL_strdup(value);
2262 }
2263 if (!AddMappingKeyValue(parts, new_key, new_value)) {
2264 break;
2265 }
2266
2267 if (comma) {
2268 current = comma + 1;
2269 } else {
2270 break;
2271 }
2272 }
2273}
2274
2275static int FindMappingKey(const MappingParts *parts, const char *key)
2276{
2277 int i;
2278
2279 if (key) {
2280 for (i = 0; i < parts->num_elements; ++i) {
2281 if (SDL_strcmp(key, parts->keys[i]) == 0) {
2282 return i;
2283 }
2284 }
2285 }
2286 return -1;
2287}
2288
2289static void RemoveMappingValueAt(MappingParts *parts, int index)
2290{
2291 SDL_free(parts->keys[index]);
2292 SDL_free(parts->values[index]);
2293 --parts->num_elements;
2294 if (index < parts->num_elements) {
2295 SDL_memmove(&parts->keys[index], &parts->keys[index] + 1, (parts->num_elements - index) * sizeof(parts->keys[index]));
2296 SDL_memmove(&parts->values[index], &parts->values[index] + 1, (parts->num_elements - index) * sizeof(parts->values[index]));
2297 }
2298}
2299
2300static void ConvertBAXYMapping(MappingParts *parts)
2301{
2302 int i;
2303 bool baxy_mapping = false;
2304
2305 for (i = 0; i < parts->num_elements; ++i) {
2306 const char *key = parts->keys[i];
2307 const char *value = parts->values[i];
2308
2309 if (SDL_strcmp(key, "hint") == 0 &&
2310 SDL_strcmp(value, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") == 0) {
2311 baxy_mapping = true;
2312 }
2313 }
2314
2315 if (!baxy_mapping) {
2316 return;
2317 }
2318
2319 /* Swap buttons, invert hint */
2320 for (i = 0; i < parts->num_elements; ++i) {
2321 char *key = parts->keys[i];
2322 char *value = parts->values[i];
2323
2324 if (SDL_strcmp(key, "a") == 0) {
2325 parts->keys[i] = SDL_strdup("b");
2326 SDL_free(key);
2327 } else if (SDL_strcmp(key, "b") == 0) {
2328 parts->keys[i] = SDL_strdup("a");
2329 SDL_free(key);
2330 } else if (SDL_strcmp(key, "x") == 0) {
2331 parts->keys[i] = SDL_strdup("y");
2332 SDL_free(key);
2333 } else if (SDL_strcmp(key, "y") == 0) {
2334 parts->keys[i] = SDL_strdup("x");
2335 SDL_free(key);
2336 } else if (SDL_strcmp(key, "hint") == 0 &&
2337 SDL_strcmp(value, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") == 0) {
2338 parts->values[i] = SDL_strdup("!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1");
2339 SDL_free(value);
2340 }
2341 }
2342}
2343
2344static void UpdateLegacyElements(MappingParts *parts)
2345{
2346 ConvertBAXYMapping(parts);
2347}
2348
2349static bool CombineMappingAxes(MappingParts *parts)
2350{
2351 int i, matching, axis;
2352
2353 for (i = 0; i < parts->num_elements; ++i) {
2354 char *key = parts->keys[i];
2355 char *current_value;
2356 char *matching_key;
2357 char *matching_value;
2358
2359 if (*key != '-' && *key != '+') {
2360 continue;
2361 }
2362
2363 for (axis = 0; axis < SDL_GAMEPAD_AXIS_LEFT_TRIGGER; ++axis) {
2364 if (SDL_strcmp(key + 1, SDL_GetGamepadStringForAxis((SDL_GamepadAxis)axis)) == 0) {
2365 /* Look for a matching axis with the opposite sign */
2366 if (SDL_asprintf(&matching_key, "%c%s", (*key == '-' ? '+' : '-'), key + 1) < 0) {
2367 return false;
2368 }
2369 matching = FindMappingKey(parts, matching_key);
2370 if (matching >= 0) {
2371 /* Check to see if they're bound to the same axis */
2372 current_value = parts->values[i];
2373 matching_value = parts->values[matching];
2374 if (((*current_value == '-' && *matching_value == '+') ||
2375 (*current_value == '+' && *matching_value == '-')) &&
2376 SDL_strcmp(current_value + 1, matching_value + 1) == 0) {
2377 /* Combine these axes */
2378 if (*key == *current_value) {
2379 SDL_memmove(current_value, current_value + 1, SDL_strlen(current_value));
2380 } else {
2381 /* Invert the bound axis */
2382 SDL_memmove(current_value, current_value + 1, SDL_strlen(current_value)-1);
2383 current_value[SDL_strlen(current_value) - 1] = '~';
2384 }
2385 SDL_memmove(key, key + 1, SDL_strlen(key));
2386 RemoveMappingValueAt(parts, matching);
2387 }
2388 }
2389 SDL_free(matching_key);
2390 break;
2391 }
2392 }
2393 }
2394 return true;
2395}
2396
2397typedef struct
2398{
2399 MappingParts *parts;
2400 int index;
2401} MappingSortEntry;
2402
2403static int SDLCALL SortMapping(const void *a, const void *b)
2404{
2405 MappingSortEntry *A = (MappingSortEntry *)a;
2406 MappingSortEntry *B = (MappingSortEntry *)b;
2407 const char *keyA = A->parts->keys[A->index];
2408 const char *keyB = B->parts->keys[B->index];
2409
2410 return SDL_strcmp(keyA, keyB);
2411}
2412
2413static void MoveSortedEntry(const char *key, MappingSortEntry *sort_order, int num_elements, bool front)
2414{
2415 int i;
2416
2417 for (i = 0; i < num_elements; ++i) {
2418 MappingSortEntry *entry = &sort_order[i];
2419 if (SDL_strcmp(key, entry->parts->keys[entry->index]) == 0) {
2420 if (front && i != 0) {
2421 MappingSortEntry tmp = sort_order[i];
2422 SDL_memmove(&sort_order[1], &sort_order[0], sizeof(*sort_order)*i);
2423 sort_order[0] = tmp;
2424 } else if (!front && i != (num_elements - 1)) {
2425 MappingSortEntry tmp = sort_order[i];
2426 SDL_memmove(&sort_order[i], &sort_order[i + 1], sizeof(*sort_order)*(num_elements - i - 1));
2427 sort_order[num_elements - 1] = tmp;
2428 }
2429 break;
2430 }
2431 }
2432}
2433
2434static char *JoinMapping(MappingParts *parts)
2435{
2436 int i;
2437 size_t length;
2438 char *mapping;
2439 const char *guid;
2440 const char *name;
2441 MappingSortEntry *sort_order;
2442
2443 UpdateLegacyElements(parts);
2444 CombineMappingAxes(parts);
2445
2446 guid = parts->guid;
2447 if (!guid || !*guid) {
2448 guid = "*";
2449 }
2450
2451 name = parts->name;
2452 if (!name || !*name) {
2453 name = "*";
2454 }
2455
2456 length = SDL_strlen(guid) + 1 + SDL_strlen(name) + 1;
2457 for (i = 0; i < parts->num_elements; ++i) {
2458 length += SDL_strlen(parts->keys[i]) + 1;
2459 length += SDL_strlen(parts->values[i]) + 1;
2460 }
2461 length += 1;
2462
2463 /* The sort order is: crc, platform, type, *, sdk, hint */
2464 sort_order = SDL_stack_alloc(MappingSortEntry, parts->num_elements);
2465 for (i = 0; i < parts->num_elements; ++i) {
2466 sort_order[i].parts = parts;
2467 sort_order[i].index = i;
2468 }
2469 SDL_qsort(sort_order, parts->num_elements, sizeof(*sort_order), SortMapping);
2470 MoveSortedEntry("type", sort_order, parts->num_elements, true);
2471 MoveSortedEntry("platform", sort_order, parts->num_elements, true);
2472 MoveSortedEntry("crc", sort_order, parts->num_elements, true);
2473 MoveSortedEntry("sdk>=", sort_order, parts->num_elements, false);
2474 MoveSortedEntry("sdk<=", sort_order, parts->num_elements, false);
2475 MoveSortedEntry("hint", sort_order, parts->num_elements, false);
2476
2477 /* Move platform to the front */
2478
2479 mapping = (char *)SDL_malloc(length);
2480 if (mapping) {
2481 *mapping = '\0';
2482 SDL_strlcat(mapping, guid, length);
2483 SDL_strlcat(mapping, ",", length);
2484 SDL_strlcat(mapping, name, length);
2485 SDL_strlcat(mapping, ",", length);
2486 for (i = 0; i < parts->num_elements; ++i) {
2487 int next = sort_order[i].index;
2488 SDL_strlcat(mapping, parts->keys[next], length);
2489 SDL_strlcat(mapping, ":", length);
2490 SDL_strlcat(mapping, parts->values[next], length);
2491 SDL_strlcat(mapping, ",", length);
2492 }
2493 }
2494
2495 SDL_stack_free(sort_order);
2496
2497 return mapping;
2498}
2499
2500static void FreeMappingParts(MappingParts *parts)
2501{
2502 int i;
2503
2504 SDL_free(parts->guid);
2505 SDL_free(parts->name);
2506 for (i = 0; i < parts->num_elements; ++i) {
2507 SDL_free(parts->keys[i]);
2508 SDL_free(parts->values[i]);
2509 }
2510 SDL_free(parts->keys);
2511 SDL_free(parts->values);
2512 SDL_zerop(parts);
2513}
2514
2515/* Create a new mapping from the parts and free the old mapping and parts */
2516static char *RecreateMapping(MappingParts *parts, char *mapping)
2517{
2518 char *new_mapping = JoinMapping(parts);
2519 if (new_mapping) {
2520 SDL_free(mapping);
2521 mapping = new_mapping;
2522 }
2523 FreeMappingParts(parts);
2524 return mapping;
2525}
2526
2527static const char *GetLegacyKey(const char *key, bool baxy)
2528{
2529 if (SDL_strcmp(key, SDL_GetGamepadStringForButton(SDL_GAMEPAD_BUTTON_SOUTH)) == 0) {
2530 if (baxy) {
2531 return "b";
2532 } else {
2533 return "a";
2534 }
2535 }
2536
2537 if (SDL_strcmp(key, SDL_GetGamepadStringForButton(SDL_GAMEPAD_BUTTON_EAST)) == 0) {
2538 if (baxy) {
2539 return "a";
2540 } else {
2541 return "b";
2542 }
2543 }
2544
2545 if (SDL_strcmp(key, SDL_GetGamepadStringForButton(SDL_GAMEPAD_BUTTON_WEST)) == 0) {
2546 if (baxy) {
2547 return "y";
2548 } else {
2549 return "x";
2550 }
2551 }
2552
2553 if (SDL_strcmp(key, SDL_GetGamepadStringForButton(SDL_GAMEPAD_BUTTON_NORTH)) == 0) {
2554 if (baxy) {
2555 return "y";
2556 } else {
2557 return "x";
2558 }
2559 }
2560
2561 return key;
2562}
2563
2564static bool MappingHasKey(const char *mapping, const char *key)
2565{
2566 int i;
2567 MappingParts parts;
2568 bool result = false;
2569
2570 SplitMapping(mapping, &parts);
2571 i = FindMappingKey(&parts, key);
2572 if (i < 0) {
2573 bool baxy_mapping = false;
2574
2575 if (mapping && SDL_strstr(mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) {
2576 baxy_mapping = true;
2577 }
2578 i = FindMappingKey(&parts, GetLegacyKey(key, baxy_mapping));
2579 }
2580 if (i >= 0) {
2581 result = true;
2582 }
2583 FreeMappingParts(&parts);
2584
2585 return result;
2586}
2587
2588static char *GetMappingValue(const char *mapping, const char *key)
2589{
2590 int i;
2591 MappingParts parts;
2592 char *value = NULL;
2593
2594 SplitMapping(mapping, &parts);
2595 i = FindMappingKey(&parts, key);
2596 if (i < 0) {
2597 bool baxy_mapping = false;
2598
2599 if (mapping && SDL_strstr(mapping, ",hint:SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1") != NULL) {
2600 baxy_mapping = true;
2601 }
2602 i = FindMappingKey(&parts, GetLegacyKey(key, baxy_mapping));
2603 }
2604 if (i >= 0) {
2605 value = parts.values[i];
2606 parts.values[i] = NULL; /* So we don't free it */
2607 }
2608 FreeMappingParts(&parts);
2609
2610 return value;
2611}
2612
2613static char *SetMappingValue(char *mapping, const char *key, const char *value)
2614{
2615 MappingParts parts;
2616 int i;
2617 char *new_key = NULL;
2618 char *new_value = NULL;
2619 char **new_keys = NULL;
2620 char **new_values = NULL;
2621 bool result = false;
2622
2623 if (!key) {
2624 return mapping;
2625 }
2626
2627 SplitMapping(mapping, &parts);
2628 i = FindMappingKey(&parts, key);
2629 if (i >= 0) {
2630 new_value = SDL_strdup(value);
2631 if (new_value) {
2632 SDL_free(parts.values[i]);
2633 parts.values[i] = new_value;
2634 result = true;
2635 }
2636 } else {
2637 int count = parts.num_elements;
2638
2639 new_key = SDL_strdup(key);
2640 if (new_key) {
2641 new_value = SDL_strdup(value);
2642 if (new_value) {
2643 new_keys = (char **)SDL_realloc(parts.keys, (count + 1) * sizeof(*new_keys));
2644 if (new_keys) {
2645 new_values = (char **)SDL_realloc(parts.values, (count + 1) * sizeof(*new_values));
2646 if (new_values) {
2647 new_keys[count] = new_key;
2648 new_values[count] = new_value;
2649 parts.num_elements = (count + 1);
2650 parts.keys = new_keys;
2651 parts.values = new_values;
2652 result = true;
2653 }
2654 }
2655 }
2656 }
2657 }
2658
2659 if (result) {
2660 mapping = RecreateMapping(&parts, mapping);
2661 } else {
2662 SDL_free(new_key);
2663 SDL_free(new_value);
2664 SDL_free(new_keys);
2665 SDL_free(new_values);
2666 }
2667 return mapping;
2668}
2669
2670static char *RemoveMappingValue(char *mapping, const char *key)
2671{
2672 MappingParts parts;
2673 int i;
2674
2675 SplitMapping(mapping, &parts);
2676 i = FindMappingKey(&parts, key);
2677 if (i >= 0) {
2678 RemoveMappingValueAt(&parts, i);
2679 }
2680 return RecreateMapping(&parts, mapping);
2681}
2682
2683bool MappingHasBindings(const char *mapping)
2684{
2685 MappingParts parts;
2686 int i;
2687 bool result = false;
2688
2689 if (!mapping || !*mapping) {
2690 return false;
2691 }
2692
2693 SplitMapping(mapping, &parts);
2694 for (i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) {
2695 if (FindMappingKey(&parts, SDL_GetGamepadStringForButton((SDL_GamepadButton)i)) >= 0) {
2696 result = true;
2697 break;
2698 }
2699 }
2700 if (!result) {
2701 for (i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) {
2702 if (FindMappingKey(&parts, SDL_GetGamepadStringForAxis((SDL_GamepadAxis)i)) >= 0) {
2703 result = true;
2704 break;
2705 }
2706 }
2707 }
2708 FreeMappingParts(&parts);
2709
2710 return result;
2711}
2712
2713bool MappingHasName(const char *mapping)
2714{
2715 MappingParts parts;
2716 bool result;
2717
2718 SplitMapping(mapping, &parts);
2719 result = parts.name ? true : false;
2720 FreeMappingParts(&parts);
2721 return result;
2722}
2723
2724char *GetMappingName(const char *mapping)
2725{
2726 MappingParts parts;
2727 char *name;
2728
2729 SplitMapping(mapping, &parts);
2730 name = parts.name;
2731 parts.name = NULL; /* Don't free the name we're about to return */
2732 FreeMappingParts(&parts);
2733 return name;
2734}
2735
2736char *SetMappingName(char *mapping, const char *name)
2737{
2738 MappingParts parts;
2739 char *new_name, *spot;
2740 size_t length;
2741
2742 if (!name) {
2743 return mapping;
2744 }
2745
2746 /* Remove any leading whitespace */
2747 while (*name && SDL_isspace(*name)) {
2748 ++name;
2749 }
2750
2751 new_name = SDL_strdup(name);
2752 if (!new_name) {
2753 return mapping;
2754 }
2755
2756 /* Remove any commas, which are field separators in the mapping */
2757 length = SDL_strlen(new_name);
2758 while ((spot = SDL_strchr(new_name, ',')) != NULL) {
2759 SDL_memmove(spot, spot + 1, length - (spot - new_name) + 1);
2760 --length;
2761 }
2762
2763 /* Remove any trailing whitespace */
2764 while (length > 0 && SDL_isspace(new_name[length - 1])) {
2765 --length;
2766 }
2767
2768 /* See if we have anything left */
2769 if (length == 0) {
2770 SDL_free(new_name);
2771 return mapping;
2772 }
2773
2774 /* null terminate to cut off anything we've trimmed */
2775 new_name[length] = '\0';
2776
2777 SplitMapping(mapping, &parts);
2778 SDL_free(parts.name);
2779 parts.name = new_name;
2780 return RecreateMapping(&parts, mapping);
2781}
2782
2783
2784const char *GetGamepadTypeString(SDL_GamepadType type)
2785{
2786 switch (type) {
2787 case SDL_GAMEPAD_TYPE_XBOX360:
2788 return "Xbox 360";
2789 case SDL_GAMEPAD_TYPE_XBOXONE:
2790 return "Xbox One";
2791 case SDL_GAMEPAD_TYPE_PS3:
2792 return "PS3";
2793 case SDL_GAMEPAD_TYPE_PS4:
2794 return "PS4";
2795 case SDL_GAMEPAD_TYPE_PS5:
2796 return "PS5";
2797 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:
2798 return "Nintendo Switch";
2799 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT:
2800 return "Joy-Con (L)";
2801 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT:
2802 return "Joy-Con (R)";
2803 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR:
2804 return "Joy-Con Pair";
2805 default:
2806 return "";
2807 }
2808}
2809
2810SDL_GamepadType GetMappingType(const char *mapping)
2811{
2812 return SDL_GetGamepadTypeFromString(GetMappingValue(mapping, "type"));
2813}
2814
2815char *SetMappingType(char *mapping, SDL_GamepadType type)
2816{
2817 const char *type_string = SDL_GetGamepadStringForType(type);
2818 if (!type_string || type == SDL_GAMEPAD_TYPE_UNKNOWN) {
2819 return RemoveMappingValue(mapping, "type");
2820 } else {
2821 return SetMappingValue(mapping, "type", type_string);
2822 }
2823}
2824
2825static const char *GetElementKey(int element)
2826{
2827 if (element < SDL_GAMEPAD_BUTTON_COUNT) {
2828 return SDL_GetGamepadStringForButton((SDL_GamepadButton)element);
2829 } else {
2830 static char key[16];
2831
2832 switch (element) {
2833 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE:
2834 SDL_snprintf(key, sizeof(key), "-%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFTX));
2835 break;
2836 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE:
2837 SDL_snprintf(key, sizeof(key), "+%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFTX));
2838 break;
2839 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE:
2840 SDL_snprintf(key, sizeof(key), "-%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFTY));
2841 break;
2842 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE:
2843 SDL_snprintf(key, sizeof(key), "+%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFTY));
2844 break;
2845 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE:
2846 SDL_snprintf(key, sizeof(key), "-%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHTX));
2847 break;
2848 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE:
2849 SDL_snprintf(key, sizeof(key), "+%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHTX));
2850 break;
2851 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE:
2852 SDL_snprintf(key, sizeof(key), "-%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHTY));
2853 break;
2854 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE:
2855 SDL_snprintf(key, sizeof(key), "+%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHTY));
2856 break;
2857 case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER:
2858 return SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFT_TRIGGER);
2859 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER:
2860 return SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHT_TRIGGER);
2861 default:
2862 return NULL;
2863 }
2864 return key;
2865 }
2866}
2867
2868bool MappingHasElement(const char *mapping, int element)
2869{
2870 const char *key;
2871
2872 key = GetElementKey(element);
2873 if (!key) {
2874 return false;
2875 }
2876 return MappingHasKey(mapping, key);
2877}
2878
2879char *GetElementBinding(const char *mapping, int element)
2880{
2881 const char *key;
2882
2883 key = GetElementKey(element);
2884 if (!key) {
2885 return NULL;
2886 }
2887 return GetMappingValue(mapping, key);
2888}
2889
2890char *SetElementBinding(char *mapping, int element, const char *binding)
2891{
2892 if (binding) {
2893 return SetMappingValue(mapping, GetElementKey(element), binding);
2894 } else {
2895 return RemoveMappingValue(mapping, GetElementKey(element));
2896 }
2897}
2898
2899int GetElementForBinding(char *mapping, const char *binding)
2900{
2901 MappingParts parts;
2902 int i, element;
2903 int result = SDL_GAMEPAD_ELEMENT_INVALID;
2904
2905 if (!binding) {
2906 return SDL_GAMEPAD_ELEMENT_INVALID;
2907 }
2908
2909 SplitMapping(mapping, &parts);
2910 for (i = 0; i < parts.num_elements; ++i) {
2911 if (SDL_strcmp(binding, parts.values[i]) == 0) {
2912 for (element = 0; element < SDL_GAMEPAD_ELEMENT_MAX; ++element) {
2913 const char *key = GetElementKey(element);
2914 if (key && SDL_strcmp(key, parts.keys[i]) == 0) {
2915 result = element;
2916 break;
2917 }
2918 }
2919 break;
2920 }
2921 }
2922 FreeMappingParts(&parts);
2923
2924 return result;
2925}
2926
2927bool MappingHasBinding(const char *mapping, const char *binding)
2928{
2929 MappingParts parts;
2930 int i;
2931 bool result = false;
2932
2933 if (!binding) {
2934 return false;
2935 }
2936
2937 SplitMapping(mapping, &parts);
2938 for (i = 0; i < parts.num_elements; ++i) {
2939 if (SDL_strcmp(binding, parts.values[i]) == 0) {
2940 result = true;
2941 break;
2942 }
2943 }
2944 FreeMappingParts(&parts);
2945
2946 return result;
2947}
2948
2949char *ClearMappingBinding(char *mapping, const char *binding)
2950{
2951 MappingParts parts;
2952 int i;
2953 bool modified = false;
2954
2955 if (!binding) {
2956 return mapping;
2957 }
2958
2959 SplitMapping(mapping, &parts);
2960 for (i = parts.num_elements - 1; i >= 0; --i) {
2961 if (SDL_strcmp(binding, parts.values[i]) == 0) {
2962 RemoveMappingValueAt(&parts, i);
2963 modified = true;
2964 }
2965 }
2966 if (modified) {
2967 return RecreateMapping(&parts, mapping);
2968 } else {
2969 FreeMappingParts(&parts);
2970 return mapping;
2971 }
2972}