diff options
Diffstat (limited to 'src/contrib/SDL-3.2.20/test/gamepadutils.c')
-rw-r--r-- | src/contrib/SDL-3.2.20/test/gamepadutils.c | 2972 |
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 */ | ||
34 | static 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 */ | ||
62 | static 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 | |||
80 | static SDL_FRect touchpad_area = { | ||
81 | 148.0f, 20.0f, 216.0f, 118.0f | ||
82 | }; | ||
83 | |||
84 | typedef struct | ||
85 | { | ||
86 | bool down; | ||
87 | float x; | ||
88 | float y; | ||
89 | float pressure; | ||
90 | } GamepadTouchpadFinger; | ||
91 | |||
92 | struct 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 | |||
137 | static 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 | |||
152 | GamepadImage *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 | |||
190 | void 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 | |||
200 | void 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 | |||
216 | void 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 | |||
229 | void SetGamepadImageShowingFront(GamepadImage *ctx, bool showing_front) | ||
230 | { | ||
231 | if (!ctx) { | ||
232 | return; | ||
233 | } | ||
234 | |||
235 | ctx->showing_front = showing_front; | ||
236 | } | ||
237 | |||
238 | SDL_GamepadType GetGamepadImageType(GamepadImage *ctx) | ||
239 | { | ||
240 | if (!ctx) { | ||
241 | return SDL_GAMEPAD_TYPE_UNKNOWN; | ||
242 | } | ||
243 | |||
244 | return ctx->type; | ||
245 | } | ||
246 | |||
247 | void SetGamepadImageDisplayMode(GamepadImage *ctx, ControllerDisplayMode display_mode) | ||
248 | { | ||
249 | if (!ctx) { | ||
250 | return; | ||
251 | } | ||
252 | |||
253 | ctx->display_mode = display_mode; | ||
254 | } | ||
255 | |||
256 | float GetGamepadImageButtonWidth(GamepadImage *ctx) | ||
257 | { | ||
258 | if (!ctx) { | ||
259 | return 0; | ||
260 | } | ||
261 | |||
262 | return ctx->button_width; | ||
263 | } | ||
264 | |||
265 | float GetGamepadImageButtonHeight(GamepadImage *ctx) | ||
266 | { | ||
267 | if (!ctx) { | ||
268 | return 0; | ||
269 | } | ||
270 | |||
271 | return ctx->button_height; | ||
272 | } | ||
273 | |||
274 | float GetGamepadImageAxisWidth(GamepadImage *ctx) | ||
275 | { | ||
276 | if (!ctx) { | ||
277 | return 0; | ||
278 | } | ||
279 | |||
280 | return ctx->axis_width; | ||
281 | } | ||
282 | |||
283 | float GetGamepadImageAxisHeight(GamepadImage *ctx) | ||
284 | { | ||
285 | if (!ctx) { | ||
286 | return 0; | ||
287 | } | ||
288 | |||
289 | return ctx->axis_height; | ||
290 | } | ||
291 | |||
292 | int 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 | |||
402 | void ClearGamepadImage(GamepadImage *ctx) | ||
403 | { | ||
404 | if (!ctx) { | ||
405 | return; | ||
406 | } | ||
407 | |||
408 | SDL_zeroa(ctx->elements); | ||
409 | } | ||
410 | |||
411 | void SetGamepadImageElement(GamepadImage *ctx, int element, bool active) | ||
412 | { | ||
413 | if (!ctx) { | ||
414 | return; | ||
415 | } | ||
416 | |||
417 | ctx->elements[element] = active; | ||
418 | } | ||
419 | |||
420 | void 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 | |||
506 | void 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 | |||
659 | void 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 | |||
680 | static 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 | }; | ||
708 | SDL_COMPILE_TIME_ASSERT(gamepad_button_names, SDL_arraysize(gamepad_button_names) == SDL_GAMEPAD_BUTTON_COUNT); | ||
709 | |||
710 | static const char *gamepad_axis_names[] = { | ||
711 | "LeftX", | ||
712 | "LeftY", | ||
713 | "RightX", | ||
714 | "RightY", | ||
715 | "Left Trigger", | ||
716 | "Right Trigger", | ||
717 | }; | ||
718 | SDL_COMPILE_TIME_ASSERT(gamepad_axis_names, SDL_arraysize(gamepad_axis_names) == SDL_GAMEPAD_AXIS_COUNT); | ||
719 | |||
720 | struct 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 | |||
742 | GamepadDisplay *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 | |||
760 | void SetGamepadDisplayDisplayMode(GamepadDisplay *ctx, ControllerDisplayMode display_mode) | ||
761 | { | ||
762 | if (!ctx) { | ||
763 | return; | ||
764 | } | ||
765 | |||
766 | ctx->display_mode = display_mode; | ||
767 | } | ||
768 | |||
769 | void SetGamepadDisplayArea(GamepadDisplay *ctx, const SDL_FRect *area) | ||
770 | { | ||
771 | if (!ctx) { | ||
772 | return; | ||
773 | } | ||
774 | |||
775 | SDL_copyp(&ctx->area, area); | ||
776 | } | ||
777 | |||
778 | static 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 | |||
819 | static 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 | |||
867 | static 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 | |||
907 | void 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 | |||
917 | void SetGamepadDisplaySelected(GamepadDisplay *ctx, int element) | ||
918 | { | ||
919 | if (!ctx) { | ||
920 | return; | ||
921 | } | ||
922 | |||
923 | ctx->element_selected = element; | ||
924 | } | ||
925 | |||
926 | int 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 | |||
1018 | static 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 | |||
1040 | void 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 | |||
1317 | void 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 | |||
1328 | struct 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 | |||
1340 | GamepadTypeDisplay *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 | |||
1353 | void SetGamepadTypeDisplayArea(GamepadTypeDisplay *ctx, const SDL_FRect *area) | ||
1354 | { | ||
1355 | if (!ctx) { | ||
1356 | return; | ||
1357 | } | ||
1358 | |||
1359 | SDL_copyp(&ctx->area, area); | ||
1360 | } | ||
1361 | |||
1362 | void 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 | |||
1372 | void SetGamepadTypeDisplaySelected(GamepadTypeDisplay *ctx, int type) | ||
1373 | { | ||
1374 | if (!ctx) { | ||
1375 | return; | ||
1376 | } | ||
1377 | |||
1378 | ctx->type_selected = type; | ||
1379 | } | ||
1380 | |||
1381 | void SetGamepadTypeDisplayRealType(GamepadTypeDisplay *ctx, SDL_GamepadType type) | ||
1382 | { | ||
1383 | if (!ctx) { | ||
1384 | return; | ||
1385 | } | ||
1386 | |||
1387 | ctx->real_type = type; | ||
1388 | } | ||
1389 | |||
1390 | int 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 | |||
1423 | static 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 | |||
1445 | void 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 | |||
1490 | void DestroyGamepadTypeDisplay(GamepadTypeDisplay *ctx) | ||
1491 | { | ||
1492 | if (!ctx) { | ||
1493 | return; | ||
1494 | } | ||
1495 | |||
1496 | SDL_free(ctx); | ||
1497 | } | ||
1498 | |||
1499 | |||
1500 | struct 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 | |||
1516 | JoystickDisplay *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 | |||
1531 | void SetJoystickDisplayArea(JoystickDisplay *ctx, const SDL_FRect *area) | ||
1532 | { | ||
1533 | if (!ctx) { | ||
1534 | return; | ||
1535 | } | ||
1536 | |||
1537 | SDL_copyp(&ctx->area, area); | ||
1538 | } | ||
1539 | |||
1540 | char *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 | |||
1652 | void 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 | |||
1666 | static 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 | |||
1688 | static 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 | |||
1714 | static 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 | |||
1733 | void 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 | |||
1923 | void 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 | |||
1935 | struct 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 | |||
1952 | GamepadButton *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 | |||
1968 | void SetGamepadButtonArea(GamepadButton *ctx, const SDL_FRect *area) | ||
1969 | { | ||
1970 | if (!ctx) { | ||
1971 | return; | ||
1972 | } | ||
1973 | |||
1974 | SDL_copyp(&ctx->area, area); | ||
1975 | } | ||
1976 | |||
1977 | void 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 | |||
1987 | void 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 | |||
2001 | float GetGamepadButtonLabelWidth(GamepadButton *ctx) | ||
2002 | { | ||
2003 | if (!ctx) { | ||
2004 | return 0; | ||
2005 | } | ||
2006 | |||
2007 | return ctx->label_width; | ||
2008 | } | ||
2009 | |||
2010 | float GetGamepadButtonLabelHeight(GamepadButton *ctx) | ||
2011 | { | ||
2012 | if (!ctx) { | ||
2013 | return 0; | ||
2014 | } | ||
2015 | |||
2016 | return ctx->label_height; | ||
2017 | } | ||
2018 | |||
2019 | bool 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 | |||
2032 | void 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 | |||
2122 | void 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 | |||
2134 | typedef struct | ||
2135 | { | ||
2136 | char *guid; | ||
2137 | char *name; | ||
2138 | int num_elements; | ||
2139 | char **keys; | ||
2140 | char **values; | ||
2141 | } MappingParts; | ||
2142 | |||
2143 | static bool AddMappingKeyValue(MappingParts *parts, char *key, char *value); | ||
2144 | |||
2145 | static 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 | |||
2173 | static 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 | |||
2215 | static 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 | |||
2275 | static 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 | |||
2289 | static 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 | |||
2300 | static 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 | |||
2344 | static void UpdateLegacyElements(MappingParts *parts) | ||
2345 | { | ||
2346 | ConvertBAXYMapping(parts); | ||
2347 | } | ||
2348 | |||
2349 | static 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 | |||
2397 | typedef struct | ||
2398 | { | ||
2399 | MappingParts *parts; | ||
2400 | int index; | ||
2401 | } MappingSortEntry; | ||
2402 | |||
2403 | static 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 | |||
2413 | static 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 | |||
2434 | static 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 | |||
2500 | static 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 */ | ||
2516 | static 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 | |||
2527 | static 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 | |||
2564 | static 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 | |||
2588 | static 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 | |||
2613 | static 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 | |||
2670 | static 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 | |||
2683 | bool 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 | |||
2713 | bool 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 | |||
2724 | char *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 | |||
2736 | char *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 | |||
2784 | const 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 | |||
2810 | SDL_GamepadType GetMappingType(const char *mapping) | ||
2811 | { | ||
2812 | return SDL_GetGamepadTypeFromString(GetMappingValue(mapping, "type")); | ||
2813 | } | ||
2814 | |||
2815 | char *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 | |||
2825 | static 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 | |||
2868 | bool 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 | |||
2879 | char *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 | |||
2890 | char *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 | |||
2899 | int 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 | |||
2927 | bool 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 | |||
2949 | char *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 | } | ||