gosu 0.15.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (242) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/COPYING +1 -1
  4. data/dependencies/SDL/include/SDL.h +138 -0
  5. data/dependencies/SDL/include/SDL_assert.h +293 -0
  6. data/dependencies/SDL/include/SDL_atomic.h +295 -0
  7. data/dependencies/SDL/include/SDL_audio.h +859 -0
  8. data/dependencies/SDL/include/SDL_bits.h +121 -0
  9. data/dependencies/SDL/include/SDL_blendmode.h +123 -0
  10. data/dependencies/SDL/include/SDL_clipboard.h +71 -0
  11. data/dependencies/SDL/include/SDL_config.h +55 -0
  12. data/dependencies/SDL/include/SDL_config_android.h +182 -0
  13. data/dependencies/SDL/include/SDL_config_iphoneos.h +207 -0
  14. data/dependencies/SDL/include/SDL_config_macosx.h +266 -0
  15. data/dependencies/SDL/include/SDL_config_minimal.h +85 -0
  16. data/dependencies/SDL/include/SDL_config_os2.h +188 -0
  17. data/dependencies/SDL/include/SDL_config_pandora.h +135 -0
  18. data/dependencies/SDL/include/SDL_config_psp.h +165 -0
  19. data/dependencies/SDL/include/SDL_config_windows.h +288 -0
  20. data/dependencies/SDL/include/SDL_config_winrt.h +243 -0
  21. data/dependencies/SDL/include/SDL_config_wiz.h +149 -0
  22. data/dependencies/SDL/include/SDL_copying.h +20 -0
  23. data/dependencies/SDL/include/SDL_cpuinfo.h +299 -0
  24. data/dependencies/SDL/include/SDL_egl.h +1676 -0
  25. data/dependencies/SDL/include/SDL_endian.h +263 -0
  26. data/dependencies/SDL/include/SDL_error.h +112 -0
  27. data/dependencies/SDL/include/SDL_events.h +827 -0
  28. data/dependencies/SDL/include/SDL_filesystem.h +136 -0
  29. data/dependencies/SDL/include/SDL_gamecontroller.h +541 -0
  30. data/dependencies/SDL/include/SDL_gesture.h +87 -0
  31. data/dependencies/SDL/include/SDL_haptic.h +1247 -0
  32. data/dependencies/SDL/include/SDL_hints.h +1578 -0
  33. data/dependencies/SDL/include/SDL_joystick.h +499 -0
  34. data/dependencies/SDL/include/SDL_keyboard.h +217 -0
  35. data/dependencies/SDL/include/SDL_keycode.h +351 -0
  36. data/dependencies/SDL/include/SDL_loadso.h +81 -0
  37. data/dependencies/SDL/include/SDL_locale.h +101 -0
  38. data/dependencies/SDL/include/SDL_log.h +211 -0
  39. data/dependencies/SDL/include/SDL_main.h +180 -0
  40. data/dependencies/SDL/include/SDL_messagebox.h +146 -0
  41. data/dependencies/SDL/include/SDL_metal.h +117 -0
  42. data/dependencies/SDL/include/SDL_misc.h +75 -0
  43. data/dependencies/SDL/include/SDL_mouse.h +302 -0
  44. data/dependencies/SDL/include/SDL_mutex.h +251 -0
  45. data/dependencies/SDL/include/SDL_name.h +33 -0
  46. data/dependencies/SDL/include/SDL_opengl.h +2183 -0
  47. data/dependencies/SDL/include/SDL_opengl_glext.h +11180 -0
  48. data/dependencies/SDL/include/SDL_opengles.h +39 -0
  49. data/dependencies/SDL/include/SDL_opengles2.h +52 -0
  50. data/dependencies/SDL/include/SDL_opengles2_gl2.h +621 -0
  51. data/dependencies/SDL/include/SDL_opengles2_gl2ext.h +2050 -0
  52. data/dependencies/SDL/include/SDL_opengles2_gl2platform.h +30 -0
  53. data/dependencies/SDL/include/SDL_opengles2_khrplatform.h +282 -0
  54. data/dependencies/SDL/include/SDL_pixels.h +479 -0
  55. data/dependencies/SDL/include/SDL_platform.h +198 -0
  56. data/dependencies/SDL/include/SDL_power.h +75 -0
  57. data/dependencies/SDL/include/SDL_quit.h +58 -0
  58. data/dependencies/SDL/include/SDL_rect.h +174 -0
  59. data/dependencies/SDL/include/SDL_render.h +1158 -0
  60. data/dependencies/SDL/include/SDL_revision.h +2 -0
  61. data/dependencies/SDL/include/SDL_rwops.h +283 -0
  62. data/dependencies/SDL/include/SDL_scancode.h +413 -0
  63. data/dependencies/SDL/include/SDL_sensor.h +267 -0
  64. data/dependencies/SDL/include/SDL_shape.h +144 -0
  65. data/dependencies/SDL/include/SDL_stdinc.h +647 -0
  66. data/dependencies/SDL/include/SDL_surface.h +563 -0
  67. data/dependencies/SDL/include/SDL_system.h +325 -0
  68. data/dependencies/SDL/include/SDL_syswm.h +354 -0
  69. data/dependencies/SDL/include/SDL_test.h +69 -0
  70. data/dependencies/SDL/include/SDL_test_assert.h +105 -0
  71. data/dependencies/SDL/include/SDL_test_common.h +218 -0
  72. data/dependencies/SDL/include/SDL_test_compare.h +69 -0
  73. data/dependencies/SDL/include/SDL_test_crc32.h +124 -0
  74. data/dependencies/SDL/include/SDL_test_font.h +81 -0
  75. data/dependencies/SDL/include/SDL_test_fuzzer.h +384 -0
  76. data/dependencies/SDL/include/SDL_test_harness.h +134 -0
  77. data/dependencies/SDL/include/SDL_test_images.h +78 -0
  78. data/dependencies/SDL/include/SDL_test_log.h +67 -0
  79. data/dependencies/SDL/include/SDL_test_md5.h +129 -0
  80. data/dependencies/SDL/include/SDL_test_memory.h +63 -0
  81. data/dependencies/SDL/include/SDL_test_random.h +115 -0
  82. data/dependencies/SDL/include/SDL_thread.h +366 -0
  83. data/dependencies/SDL/include/SDL_timer.h +115 -0
  84. data/dependencies/SDL/include/SDL_touch.h +102 -0
  85. data/dependencies/SDL/include/SDL_types.h +29 -0
  86. data/dependencies/SDL/include/SDL_version.h +162 -0
  87. data/dependencies/SDL/include/SDL_video.h +1282 -0
  88. data/dependencies/SDL/include/SDL_vulkan.h +276 -0
  89. data/dependencies/SDL/include/begin_code.h +166 -0
  90. data/dependencies/SDL/include/close_code.h +40 -0
  91. data/dependencies/SDL/lib/x64/libSDL2.dll.a +0 -0
  92. data/dependencies/SDL/lib/x86/libSDL2.dll.a +0 -0
  93. data/dependencies/SDL_sound/SDL_sound.c +795 -0
  94. data/dependencies/SDL_sound/SDL_sound.h +725 -0
  95. data/dependencies/SDL_sound/SDL_sound_aiff.c +537 -0
  96. data/dependencies/SDL_sound/SDL_sound_au.c +352 -0
  97. data/dependencies/SDL_sound/SDL_sound_coreaudio.c +747 -0
  98. data/dependencies/SDL_sound/SDL_sound_flac.c +182 -0
  99. data/dependencies/SDL_sound/SDL_sound_internal.h +304 -0
  100. data/dependencies/SDL_sound/SDL_sound_modplug.c +228 -0
  101. data/dependencies/SDL_sound/SDL_sound_mp3.c +184 -0
  102. data/dependencies/SDL_sound/SDL_sound_raw.c +164 -0
  103. data/dependencies/SDL_sound/SDL_sound_shn.c +1309 -0
  104. data/dependencies/SDL_sound/SDL_sound_voc.c +550 -0
  105. data/dependencies/SDL_sound/SDL_sound_vorbis.c +223 -0
  106. data/dependencies/SDL_sound/SDL_sound_wav.c +783 -0
  107. data/dependencies/SDL_sound/dr_flac.h +5906 -0
  108. data/dependencies/SDL_sound/dr_mp3.h +2832 -0
  109. data/dependencies/SDL_sound/libmodplug/fastmix.c +1748 -0
  110. data/dependencies/SDL_sound/libmodplug/libmodplug.h +1001 -0
  111. data/dependencies/SDL_sound/libmodplug/load_669.c +188 -0
  112. data/dependencies/SDL_sound/libmodplug/load_abc.c +4725 -0
  113. data/dependencies/SDL_sound/libmodplug/load_amf.c +403 -0
  114. data/dependencies/SDL_sound/libmodplug/load_ams.c +587 -0
  115. data/dependencies/SDL_sound/libmodplug/load_dbm.c +357 -0
  116. data/dependencies/SDL_sound/libmodplug/load_dmf.c +531 -0
  117. data/dependencies/SDL_sound/libmodplug/load_dsm.c +232 -0
  118. data/dependencies/SDL_sound/libmodplug/load_far.c +253 -0
  119. data/dependencies/SDL_sound/libmodplug/load_it.c +796 -0
  120. data/dependencies/SDL_sound/libmodplug/load_mdl.c +488 -0
  121. data/dependencies/SDL_sound/libmodplug/load_med.c +757 -0
  122. data/dependencies/SDL_sound/libmodplug/load_mid.c +1405 -0
  123. data/dependencies/SDL_sound/libmodplug/load_mod.c +269 -0
  124. data/dependencies/SDL_sound/libmodplug/load_mt2.c +546 -0
  125. data/dependencies/SDL_sound/libmodplug/load_mtm.c +142 -0
  126. data/dependencies/SDL_sound/libmodplug/load_okt.c +192 -0
  127. data/dependencies/SDL_sound/libmodplug/load_pat.c +1143 -0
  128. data/dependencies/SDL_sound/libmodplug/load_pat.h +25 -0
  129. data/dependencies/SDL_sound/libmodplug/load_psm.c +350 -0
  130. data/dependencies/SDL_sound/libmodplug/load_ptm.c +204 -0
  131. data/dependencies/SDL_sound/libmodplug/load_s3m.c +325 -0
  132. data/dependencies/SDL_sound/libmodplug/load_stm.c +180 -0
  133. data/dependencies/SDL_sound/libmodplug/load_ult.c +206 -0
  134. data/dependencies/SDL_sound/libmodplug/load_umx.c +51 -0
  135. data/dependencies/SDL_sound/libmodplug/load_xm.c +554 -0
  136. data/dependencies/SDL_sound/libmodplug/mmcmp.c +382 -0
  137. data/dependencies/SDL_sound/libmodplug/modplug.c +170 -0
  138. data/dependencies/SDL_sound/libmodplug/modplug.h +90 -0
  139. data/dependencies/SDL_sound/libmodplug/snd_dsp.c +301 -0
  140. data/dependencies/SDL_sound/libmodplug/snd_flt.c +63 -0
  141. data/dependencies/SDL_sound/libmodplug/snd_fx.c +2350 -0
  142. data/dependencies/SDL_sound/libmodplug/sndfile.c +1169 -0
  143. data/dependencies/SDL_sound/libmodplug/sndmix.c +1034 -0
  144. data/dependencies/SDL_sound/libmodplug/tables.h +371 -0
  145. data/{src/stb_vorbis.c → dependencies/SDL_sound/stb_vorbis.h} +128 -23
  146. data/dependencies/al_soft/AL/al.h +655 -0
  147. data/dependencies/al_soft/AL/alc.h +270 -0
  148. data/dependencies/al_soft/AL/alext.h +585 -0
  149. data/dependencies/al_soft/AL/efx-creative.h +3 -0
  150. data/dependencies/al_soft/AL/efx-presets.h +402 -0
  151. data/dependencies/al_soft/AL/efx.h +762 -0
  152. data/dependencies/al_soft/x64/libOpenAL32.dll.a +0 -0
  153. data/dependencies/al_soft/x86/libOpenAL32.dll.a +0 -0
  154. data/{src → dependencies/stb}/stb_image.h +476 -176
  155. data/{src → dependencies/stb}/stb_image_write.h +253 -131
  156. data/{src → dependencies/stb}/stb_truetype.h +262 -104
  157. data/{src → dependencies/utf8proc}/utf8proc.c +47 -29
  158. data/{src → dependencies/utf8proc}/utf8proc.h +46 -24
  159. data/{src → dependencies/utf8proc}/utf8proc_data.h +10043 -9609
  160. data/ext/gosu/extconf.rb +53 -39
  161. data/{Gosu → include/Gosu}/Audio.hpp +6 -8
  162. data/include/Gosu/Bitmap.hpp +100 -0
  163. data/{Gosu → include/Gosu}/Buttons.hpp +104 -44
  164. data/include/Gosu/Channel.h +25 -0
  165. data/include/Gosu/Color.h +38 -0
  166. data/{Gosu → include/Gosu}/Color.hpp +0 -0
  167. data/{Gosu → include/Gosu}/Directories.hpp +0 -0
  168. data/include/Gosu/Font.h +36 -0
  169. data/{Gosu → include/Gosu}/Font.hpp +1 -1
  170. data/{Gosu → include/Gosu}/Fwd.hpp +0 -5
  171. data/include/Gosu/Gosu.h +82 -0
  172. data/{Gosu → include/Gosu}/Gosu.hpp +0 -0
  173. data/{Gosu → include/Gosu}/Graphics.hpp +0 -0
  174. data/{Gosu → include/Gosu}/GraphicsBase.hpp +0 -0
  175. data/{Gosu → include/Gosu}/IO.hpp +0 -0
  176. data/include/Gosu/Image.h +54 -0
  177. data/{Gosu → include/Gosu}/Image.hpp +7 -6
  178. data/{Gosu → include/Gosu}/ImageData.hpp +0 -0
  179. data/{Gosu → include/Gosu}/Input.hpp +39 -51
  180. data/{Gosu → include/Gosu}/Inspection.hpp +0 -0
  181. data/{Gosu → include/Gosu}/Math.hpp +0 -0
  182. data/{Gosu → include/Gosu}/Platform.hpp +0 -0
  183. data/include/Gosu/Sample.h +19 -0
  184. data/include/Gosu/Song.h +24 -0
  185. data/{Gosu → include/Gosu}/Text.hpp +0 -0
  186. data/include/Gosu/TextInput.h +30 -0
  187. data/{Gosu → include/Gosu}/TextInput.hpp +0 -0
  188. data/{Gosu → include/Gosu}/Timing.hpp +0 -0
  189. data/{Gosu → include/Gosu}/Utility.hpp +15 -4
  190. data/{Gosu → include/Gosu}/Version.hpp +2 -2
  191. data/include/Gosu/Window.h +63 -0
  192. data/{Gosu → include/Gosu}/Window.hpp +23 -25
  193. data/lib/OpenAL32.dll +0 -0
  194. data/lib/SDL2.dll +0 -0
  195. data/lib/gosu.rb +0 -3
  196. data/lib/gosu/patches.rb +0 -23
  197. data/lib/gosu/preview.rb +1 -3
  198. data/lib/gosu/swig_patches.rb +3 -2
  199. data/lib64/OpenAL32.dll +0 -0
  200. data/lib64/SDL2.dll +0 -0
  201. data/rdoc/gosu.rb +98 -22
  202. data/src/Audio.cpp +50 -224
  203. data/src/AudioFile.hpp +20 -37
  204. data/src/AudioFileAudioToolbox.cpp +237 -0
  205. data/src/AudioFileSDLSound.cpp +147 -0
  206. data/src/AudioImpl.cpp +3 -12
  207. data/src/AudioImpl.hpp +3 -1
  208. data/src/Bitmap.cpp +85 -83
  209. data/src/BitmapIO.cpp +52 -58
  210. data/src/ChannelWrapper.cpp +50 -0
  211. data/src/ColorWrapper.cpp +126 -0
  212. data/src/Constants.cpp +338 -0
  213. data/src/Font.cpp +4 -1
  214. data/src/FontWrapper.cpp +74 -0
  215. data/src/GosuWrapper.cpp +251 -0
  216. data/src/Graphics.cpp +7 -4
  217. data/src/Image.cpp +13 -16
  218. data/src/ImageWrapper.cpp +168 -0
  219. data/src/Input.cpp +412 -164
  220. data/src/LargeImageData.cpp +2 -1
  221. data/src/MarkupParser.cpp +2 -1
  222. data/src/RubyGosu.cxx +912 -172
  223. data/src/RubyGosu.h +4 -2
  224. data/src/SampleWrapper.cpp +30 -0
  225. data/src/SongWrapper.cpp +52 -0
  226. data/src/TexChunk.cpp +1 -1
  227. data/src/Text.cpp +1 -0
  228. data/src/TextBuilder.cpp +3 -1
  229. data/src/TextInputWrapper.cpp +101 -0
  230. data/src/Texture.cpp +1 -1
  231. data/src/TrueTypeFont.cpp +2 -1
  232. data/src/Utility.cpp +11 -7
  233. data/src/Window.cpp +30 -39
  234. data/src/WindowWrapper.cpp +317 -0
  235. metadata +212 -43
  236. data/Gosu/AutoLink.hpp +0 -14
  237. data/Gosu/Bitmap.hpp +0 -113
  238. data/lib/gosu/zen.rb +0 -89
  239. data/src/AudioToolboxFile.hpp +0 -210
  240. data/src/OggFile.hpp +0 -92
  241. data/src/SndFile.hpp +0 -174
  242. data/src/WinMain.cpp +0 -64
@@ -6,89 +6,79 @@
6
6
  #include <Gosu/Utility.hpp>
7
7
 
8
8
  #include <SDL.h>
9
- #include "utf8proc.h"
9
+ #include <utf8proc.h>
10
10
 
11
11
  #include <cwctype>
12
12
  #include <cstdlib>
13
13
  #include <algorithm>
14
14
  #include <array>
15
+ #include <mutex>
15
16
  using namespace std;
16
17
 
17
- // Workaround for broken SDL_GetGlobalMouseState, see below.
18
- #ifdef GOSU_IS_MAC
19
- #import <CoreGraphics/CoreGraphics.h>
20
- #import <AppKit/AppKit.h>
21
- #endif
22
-
23
18
  static void require_sdl_video()
24
19
  {
25
- static bool initialized = false;
26
- if (!initialized) {
20
+ static std::once_flag initialized;
21
+
22
+ std::call_once(initialized, [] {
27
23
  SDL_InitSubSystem(SDL_INIT_VIDEO);
28
- initialized = true;
29
- atexit([] { SDL_QuitSubSystem(SDL_INIT_VIDEO); });
30
- }
24
+ });
31
25
  }
32
26
 
33
- static array<bool, Gosu::NUM_BUTTONS> button_states = { { false } };
27
+ static const unsigned NUM_BUTTONS_PER_GAMEPAD =
28
+ (Gosu::GP_RANGE_END - Gosu::GP_RANGE_BEGIN + 1 - 4) / (Gosu::NUM_GAMEPADS + 1) - 3;
29
+ static const unsigned NUM_AXES_PER_GAMEPAD = Gosu::NUM_AXES / (Gosu::NUM_GAMEPADS + 1);
30
+ static const unsigned NUM_BUTTONS = Gosu::GP_RANGE_END + 1;
31
+
32
+ static array<bool, NUM_BUTTONS> button_states = {false};
33
+ static array<double, NUM_AXES_PER_GAMEPAD * (Gosu::NUM_GAMEPADS + 1)> axis_states = {0.0};
34
+ static vector<shared_ptr<SDL_Joystick>> open_joysticks;
35
+ static vector<shared_ptr<SDL_GameController>> open_game_controllers;
36
+ // Stores joystick instance id or -1 if empty
37
+ static array<int, Gosu::NUM_GAMEPADS> gamepad_slots = {-1, -1, -1, -1};
34
38
 
35
39
  struct Gosu::Input::Impl
36
40
  {
41
+ struct InputEvent
42
+ {
43
+ enum {
44
+ ButtonUp,
45
+ ButtonDown,
46
+ GamepadConnected,
47
+ GamepadDisconnected
48
+ } type;
49
+ int id = -1;
50
+ int gamepad_instance_id = -1;
51
+ };
52
+
37
53
  Input& input;
38
54
  SDL_Window* window;
39
-
55
+
40
56
  TextInput* text_input = nullptr;
41
57
  double mouse_x, mouse_y;
42
58
  double mouse_scale_x = 1;
43
59
  double mouse_scale_y = 1;
44
60
  double mouse_offset_x = 0;
45
61
  double mouse_offset_y = 0;
46
-
62
+
47
63
  Impl(Input& input, SDL_Window* window)
48
64
  : input(input), window(window)
49
65
  {
50
66
  require_sdl_video();
51
-
67
+
52
68
  SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER);
53
-
54
- int num_gamepads = min<int>(Gosu::NUM_GAMEPADS, SDL_NumJoysticks());
55
-
56
- for (int i = 0; i < num_gamepads; ++i) {
57
- // Prefer the SDL_GameController API...
58
- if (SDL_IsGameController(i)) {
59
- if (SDL_GameController* game_controller = SDL_GameControllerOpen(i)) {
60
- game_controllers.push_back(game_controller);
61
- continue;
62
- }
63
- }
64
- // ...but fall back on the good, old SDL_Joystick API.
65
- if (SDL_Joystick* joystick = SDL_JoystickOpen(i)) {
66
- joysticks.push_back(joystick);
67
- }
68
- }
69
69
  }
70
-
70
+
71
71
  ~Impl()
72
72
  {
73
- for_each(joysticks.begin(), joysticks.end(), &SDL_JoystickClose);
74
- joysticks.clear();
75
- for_each(game_controllers.begin(), game_controllers.end(), &SDL_GameControllerClose);
76
- game_controllers.clear();
77
-
73
+ open_joysticks.clear();
74
+ open_game_controllers.clear();
75
+
78
76
  SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
79
77
  }
80
-
78
+
81
79
  void update_mouse_position()
82
80
  {
83
- #if defined(GOSU_IS_MAC)
84
- // Avoid SDL_GetGlobalMouseState on macOS until this bug is fixed:
85
- // https://bugzilla.libsdl.org/show_bug.cgi?id=4255
86
- int window_x, window_y;
87
- SDL_GetWindowPosition(window, &window_x, &window_y);
88
- auto mouse_position = NSEvent.mouseLocation;
89
- mouse_x = mouse_position.x - window_x;
90
- mouse_y = (CGDisplayPixelsHigh(kCGDirectMainDisplay) - mouse_position.y) - window_y;
91
- #elif SDL_VERSION_ATLEAST(2, 0, 5)
81
+ #if SDL_VERSION_ATLEAST(2, 0, 5)
92
82
  // SDL_GetGlobalMouseState was added in SDL 2.0.4, but it only started using the same
93
83
  // coordinate system as SDL_GetWindowPosition on X11 in 2.0.5.
94
84
  int x, y, window_x, window_y;
@@ -103,7 +93,7 @@ struct Gosu::Input::Impl
103
93
  mouse_y = y;
104
94
  #endif
105
95
  }
106
-
96
+
107
97
  void set_mouse_position(double x, double y)
108
98
  {
109
99
  SDL_WarpMouseInWindow(window,
@@ -120,13 +110,13 @@ struct Gosu::Input::Impl
120
110
  mouse_x = x, mouse_y = y;
121
111
  #endif
122
112
  }
123
-
113
+
124
114
  bool feed_sdl_event(const SDL_Event* e)
125
115
  {
126
116
  switch (e->type) {
127
117
  case SDL_KEYDOWN:
128
118
  case SDL_KEYUP: {
129
- if (e->key.repeat == 0 && e->key.keysym.scancode <= KB_RANGE_END) {
119
+ if (e->key.repeat == 0 && e->key.keysym.scancode <= static_cast<int>(KB_RANGE_END)) {
130
120
  enqueue_event(e->key.keysym.scancode, e->type == SDL_KEYDOWN);
131
121
  return true;
132
122
  }
@@ -153,160 +143,377 @@ struct Gosu::Input::Impl
153
143
  }
154
144
  break;
155
145
  }
146
+ case SDL_JOYDEVICEADDED: {
147
+ if (available_gamepad_slot_index() == -1) {
148
+ break;
149
+ }
150
+ int gamepad_slot = -1;
151
+ int joystick_instance_id = -1;
152
+
153
+ // Loop through attached gamepads as e->jdevice.which cannot be trusted (always 0)
154
+ for (int i = 0; i < SDL_NumJoysticks(); i++) {
155
+ // Prefer the SDL_GameController API...
156
+ if (SDL_IsGameController(i)) {
157
+ if (SDL_GameController *game_controller = SDL_GameControllerOpen(i)) {
158
+ gamepad_slot = available_gamepad_slot_index();
159
+ joystick_instance_id = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(game_controller));
160
+ if (gamepad_instance_id_is_known(joystick_instance_id)) {
161
+ continue;
162
+ }
163
+ open_game_controllers.emplace_back(
164
+ shared_ptr<SDL_GameController>(game_controller, SDL_GameControllerClose)
165
+ );
166
+ }
167
+ }
168
+ // ...but fall back on the good, old SDL_Joystick API.
169
+ else if (SDL_Joystick *joystick = SDL_JoystickOpen(i)) {
170
+ gamepad_slot = available_gamepad_slot_index();
171
+ joystick_instance_id = SDL_JoystickInstanceID(joystick);
172
+ if (gamepad_instance_id_is_known(joystick_instance_id)) {
173
+ continue;
174
+ }
175
+ open_joysticks.emplace_back(
176
+ shared_ptr<SDL_Joystick>(joystick, SDL_JoystickClose)
177
+ );
178
+ }
179
+
180
+ // Reserve gamepad slot and issue gamepad connection event
181
+ if (gamepad_slot >= 0 && joystick_instance_id >= 0) {
182
+ gamepad_slots[gamepad_slot] = joystick_instance_id;
183
+ enqueue_gamepad_connection_event(gamepad_slot, true, -1);
184
+ }
185
+ }
186
+ break;
187
+ }
188
+ case SDL_JOYDEVICEREMOVED: {
189
+ int gamepad_slot = gamepad_slot_index(e->jdevice.which);
190
+ if (gamepad_slot >= 0) {
191
+ enqueue_gamepad_connection_event(gamepad_slot, false, e->jdevice.which);
192
+ }
193
+ break;
194
+ }
156
195
  }
157
196
  return false;
158
197
  }
159
-
160
- typedef array<bool, GP_NUM_PER_GAMEPAD> GamepadBuffer;
161
-
198
+
199
+ double scale_axis(double value)
200
+ {
201
+ return value >= 0 ? value / SDL_JOYSTICK_AXIS_MAX : -value / SDL_JOYSTICK_AXIS_MIN;
202
+ }
203
+
204
+ // returns the gamepad slot index (0..NUM_GAMEPADS - 1) for the joystick instance id or -1 if not found
205
+ int gamepad_slot_index(int joystick_instance_id) const
206
+ {
207
+ for (int i = 0; i < gamepad_slots.size(); i++) {
208
+ if (gamepad_slots[i] == joystick_instance_id) {
209
+ return i;
210
+ }
211
+ }
212
+
213
+ return -1;
214
+ }
215
+
216
+ // returns first available gamepad slot or -1 if non are available
217
+ int available_gamepad_slot_index() const
218
+ {
219
+ for (int i = 0; i < gamepad_slots.size(); i++) {
220
+ if (gamepad_slots[i] == -1) {
221
+ return i;
222
+ }
223
+ }
224
+
225
+ return -1;
226
+ }
227
+
228
+ // returns whether the gamepad using the joystick instance id is known
229
+ bool gamepad_instance_id_is_known(int id)
230
+ {
231
+ for (int j = 0; j < gamepad_slots.size(); j++) {
232
+ if (gamepad_slots[j] == id) {
233
+ return true;
234
+ }
235
+ }
236
+
237
+ return false;
238
+ }
239
+
240
+ // frees the gamepad slot associated with this joystick instance id
241
+ // and frees the SDL_GameController/SDL_Joystick
242
+ void free_gamepad_slot(int instance_id)
243
+ {
244
+ int index = gamepad_slot_index(instance_id);
245
+
246
+ for (int i = 0; i < open_game_controllers.size(); i++) {
247
+ SDL_Joystick* joystick = SDL_GameControllerGetJoystick(open_game_controllers[i].get());
248
+ if (SDL_JoystickInstanceID(joystick) == instance_id) {
249
+ open_game_controllers.erase(open_game_controllers.begin() + i);
250
+ gamepad_slots[index] = -1;
251
+ return;
252
+ }
253
+ }
254
+
255
+ for (int i = 0; i < open_joysticks.size(); i++) {
256
+ if (SDL_JoystickInstanceID(open_joysticks[i].get()) == instance_id) {
257
+ open_joysticks.erase(open_joysticks.begin() + i);
258
+ gamepad_slots[index] = -1;
259
+ return;
260
+ }
261
+ }
262
+ }
263
+
264
+ struct GamepadBuffer
265
+ {
266
+ array<bool, NUM_BUTTONS_PER_GAMEPAD> buttons = { false };
267
+ array<bool, 4> directions = { false };
268
+ array<double, NUM_AXES_PER_GAMEPAD> axes = { 0.0 };
269
+ };
270
+
162
271
  void poll_gamepads()
163
272
  {
164
- // This gamepad is an OR-ed version of all the other gamepads. If button
165
- // 3 is pressed on any attached gamepad, down(GP_BUTTON_3) will return
166
- // true. This is handy for singleplayer games.
167
- GamepadBuffer any_gamepad = { false };
168
-
169
- size_t available_gamepads = game_controllers.size() + joysticks.size();
170
-
171
- for (int i = 0; i < available_gamepads; ++i) {
172
- GamepadBuffer current_gamepad = { false };
173
-
273
+ // This gamepad is an OR-ed version of all the other gamepads.
274
+ // If button3 is pressed on any attached gamepad, down(GP_BUTTON_3) will return true.
275
+ // This is handy for singleplayer games where you don't care which gamepad that player uses.
276
+ GamepadBuffer any_gamepad;
277
+
278
+ // Reset all axes values, they will be recalculated below.
279
+ axis_states.fill(0.0);
280
+
281
+ for (int i = 0; i < gamepad_slots.size(); ++i) {
282
+ if (gamepad_slots[i] == -1) {
283
+ continue;
284
+ }
285
+
174
286
  // Poll data from SDL, using either of two API interfaces.
175
- if (i < game_controllers.size()) {
176
- SDL_GameController* game_controller = game_controllers[i];
177
- poll_game_controller(game_controller, current_gamepad);
287
+ GamepadBuffer current_gamepad;
288
+ if (SDL_GameController* game_controller = SDL_GameControllerFromInstanceID(gamepad_slots[i])) {
289
+ current_gamepad = poll_game_controller(game_controller);
178
290
  }
179
291
  else {
180
- SDL_Joystick* joystick = joysticks[i - game_controllers.size()];
181
- poll_joystick(joystick, current_gamepad);
292
+ current_gamepad = poll_joystick(SDL_JoystickFromInstanceID(gamepad_slots[i]));
182
293
  }
183
-
294
+
295
+ int axis_offset = NUM_AXES_PER_GAMEPAD * (i + 1);
296
+ for (int a = 0; a < NUM_AXES_PER_GAMEPAD; ++a) {
297
+ // Transfer the axes values into the global axis_state array..
298
+ axis_states[a + axis_offset] = current_gamepad.axes[a];
299
+ // Also transfer the gamepad-specific axes values into the "any gamepad" slots.
300
+ // (Values with a higher amplitude override smaller ones.)
301
+ if (abs(current_gamepad.axes[a]) > abs(axis_states[a])) {
302
+ axis_states[a] = current_gamepad.axes[a];
303
+ }
304
+ }
305
+
184
306
  // Now at the same time, enqueue all events for this particular
185
307
  // gamepad, and OR the keyboard state into any_gamepad.
186
- int offset = GP_RANGE_BEGIN + GP_NUM_PER_GAMEPAD * (i + 1);
187
- for (int j = 0; j < current_gamepad.size(); ++j) {
188
- any_gamepad[j] = any_gamepad[j] || current_gamepad[j];
189
-
190
- if (current_gamepad[j] && !button_states[j + offset]) {
308
+ int offset = GP_RANGE_BEGIN + NUM_BUTTONS_PER_GAMEPAD * (i + 1);
309
+ for (int j = 0; j < current_gamepad.buttons.size(); ++j) {
310
+ any_gamepad.buttons[j] = any_gamepad.buttons[j] || current_gamepad.buttons[j];
311
+
312
+ if (current_gamepad.buttons[j] && !button_states[j + offset]) {
191
313
  button_states[j + offset] = true;
192
314
  enqueue_event(j + offset, true);
193
315
  }
194
- else if (!current_gamepad[j] && button_states[j + offset]) {
316
+ else if (!current_gamepad.buttons[j] && button_states[j + offset]) {
195
317
  button_states[j + offset] = false;
196
318
  enqueue_event(j + offset, false);
197
319
  }
198
320
  }
321
+ int direction_offset = GP_LEFT + 4 * (i + 1);
322
+ for (int d = 0; d < 4; ++d) {
323
+ any_gamepad.directions[d] = any_gamepad.directions[d] || current_gamepad.directions[d];
324
+
325
+ if (current_gamepad.directions[d] && !button_states[d + direction_offset]) {
326
+ button_states[d + direction_offset] = true;
327
+ enqueue_event(d + direction_offset, true);
328
+ }
329
+ else if (!current_gamepad.directions[d] && button_states[d + direction_offset]) {
330
+ button_states[d + direction_offset] = false;
331
+ enqueue_event(d + direction_offset, false);
332
+ }
333
+ }
199
334
  }
200
-
335
+
201
336
  // And lastly, enqueue events for the virtual "any" gamepad.
202
- for (int j = 0; j < any_gamepad.size(); ++j) {
203
- if (any_gamepad[j] && !button_states[j + GP_RANGE_BEGIN]) {
337
+ for (int j = 0; j < any_gamepad.buttons.size(); ++j) {
338
+ if (any_gamepad.buttons[j] && !button_states[j + GP_RANGE_BEGIN]) {
204
339
  button_states[j + GP_RANGE_BEGIN] = true;
205
340
  enqueue_event(j + GP_RANGE_BEGIN, true);
206
341
  }
207
- else if (!any_gamepad[j] && button_states[j + GP_RANGE_BEGIN]) {
342
+ else if (!any_gamepad.buttons[j] && button_states[j + GP_RANGE_BEGIN]) {
208
343
  button_states[j + GP_RANGE_BEGIN] = false;
209
344
  enqueue_event(j + GP_RANGE_BEGIN, false);
210
345
  }
211
346
  }
347
+ for (int d = 0; d < 4; ++d) {
348
+ if (any_gamepad.directions[d] && !button_states[d + GP_LEFT]) {
349
+ button_states[d + GP_LEFT] = true;
350
+ enqueue_event(d + GP_LEFT, true);
351
+ }
352
+ else if (!any_gamepad.directions[d] && button_states[d + GP_LEFT]) {
353
+ button_states[d + GP_LEFT] = false;
354
+ enqueue_event(d + GP_LEFT, false);
355
+ }
356
+ }
212
357
  }
213
-
358
+
214
359
  void dispatch_enqueued_events()
215
360
  {
216
- for (int event : event_queue) {
217
- bool down = (event >= 0);
218
- Button button(down ? event : ~event);
219
-
220
- button_states[button.id()] = down;
221
- if (down && input.on_button_down) {
222
- input.on_button_down(button);
223
- }
224
- else if (!down && input.on_button_up) {
225
- input.on_button_up(button);
361
+ for (const InputEvent& event : event_queue) {
362
+ switch (event.type) {
363
+ case InputEvent::ButtonDown:
364
+ button_states[event.id] = true;
365
+ if (input.on_button_down) {
366
+ input.on_button_down(Button(event.id));
367
+ }
368
+ break;
369
+ case InputEvent::ButtonUp:
370
+ button_states[event.id] = false;
371
+ if (input.on_button_up) {
372
+ input.on_button_up(Button(event.id));
373
+ }
374
+ break;
375
+ case InputEvent::GamepadConnected:
376
+ if (input.on_gamepad_connected) {
377
+ input.on_gamepad_connected(event.id);
378
+ }
379
+ break;
380
+ case InputEvent::GamepadDisconnected:
381
+ if (input.on_gamepad_disconnected) {
382
+ input.on_gamepad_disconnected(event.id);
383
+ }
384
+ free_gamepad_slot(event.gamepad_instance_id);
385
+ break;
226
386
  }
227
387
  }
228
388
  event_queue.clear();
229
389
  }
230
-
390
+
231
391
  private:
232
392
  // For button down event: Button name value (>= 0)
233
393
  // For button up event: ~Button name value (< 0)
234
- vector<int> event_queue;
394
+ vector<InputEvent> event_queue;
395
+
396
+ void enqueue_event(unsigned id, bool down)
397
+ {
398
+ InputEvent event;
399
+ event.type = down ? InputEvent::ButtonDown : InputEvent::ButtonUp;
400
+ event.id = id;
401
+ event_queue.push_back(event);
402
+ }
235
403
 
236
- void enqueue_event(int id, bool down)
404
+ void enqueue_gamepad_connection_event(int gamepad_index_id, bool connected, int instance_id)
237
405
  {
238
- event_queue.push_back(down ? id : ~id);
406
+ InputEvent event;
407
+ event.type = connected ? InputEvent::GamepadConnected : InputEvent::GamepadDisconnected;
408
+ event.id = gamepad_index_id;
409
+ event.gamepad_instance_id = instance_id;
410
+ event_queue.push_back(event);
239
411
  }
240
-
241
- vector<SDL_Joystick*> joysticks;
242
- vector<SDL_GameController*> game_controllers;
243
-
244
- // SDL returns axis values in the range -2^15 through 2^15-1, so we consider -2^14 through
245
- // 2^14 (half of that range) the dead zone.
246
- enum { DEAD_ZONE = (1 << 14) };
247
-
248
- void poll_game_controller(SDL_GameController* controller, GamepadBuffer& gamepad)
412
+
413
+ GamepadBuffer poll_game_controller(SDL_GameController* controller)
249
414
  {
250
- gamepad[GP_LEFT - GP_RANGE_BEGIN] =
251
- SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_LEFT) ||
252
- SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX) < -DEAD_ZONE ||
253
- SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX) < -DEAD_ZONE;
254
-
255
- gamepad[GP_RIGHT - GP_RANGE_BEGIN] =
256
- SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT) ||
257
- SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX) > +DEAD_ZONE ||
258
- SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX) > +DEAD_ZONE;
259
-
260
- gamepad[GP_UP - GP_RANGE_BEGIN] =
261
- SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_UP) ||
262
- SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY) < -DEAD_ZONE ||
263
- SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY) < -DEAD_ZONE;
264
-
265
- gamepad[GP_DOWN - GP_RANGE_BEGIN] =
266
- SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_DOWN) ||
267
- SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY) > +DEAD_ZONE ||
268
- SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY) > +DEAD_ZONE;
269
-
415
+ GamepadBuffer gamepad;
416
+
417
+ // Poll axes first.
418
+ gamepad.axes[GP_LEFT_STICK_X_AXIS] =
419
+ scale_axis(SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX));
420
+ gamepad.axes[GP_LEFT_STICK_Y_AXIS] =
421
+ scale_axis(SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY));
422
+ gamepad.axes[GP_RIGHT_STICK_X_AXIS] =
423
+ scale_axis(SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX));
424
+ gamepad.axes[GP_RIGHT_STICK_Y_AXIS] =
425
+ scale_axis(SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY));
426
+ gamepad.axes[GP_LEFT_TRIGGER_AXIS] =
427
+ scale_axis(SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT));
428
+ gamepad.axes[GP_RIGHT_TRIGGER_AXIS] =
429
+ scale_axis(SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT));
430
+
431
+ gamepad.buttons[GP_DPAD_LEFT - GP_RANGE_BEGIN] =
432
+ SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_LEFT);
433
+ gamepad.buttons[GP_DPAD_RIGHT - GP_RANGE_BEGIN] =
434
+ SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
435
+ gamepad.buttons[GP_DPAD_UP - GP_RANGE_BEGIN] =
436
+ SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_UP);
437
+ gamepad.buttons[GP_DPAD_DOWN - GP_RANGE_BEGIN] =
438
+ SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_DOWN);
439
+
270
440
  int button = 0;
271
441
  for (; button < SDL_CONTROLLER_BUTTON_DPAD_UP; ++button) {
272
- gamepad[GP_BUTTON_0 + button - GP_RANGE_BEGIN] =
442
+ gamepad.buttons[GP_BUTTON_0 + button - GP_RANGE_BEGIN] =
273
443
  SDL_GameControllerGetButton(controller, (SDL_GameControllerButton) button);
274
444
  }
275
- gamepad[GP_BUTTON_0 + button++ - GP_RANGE_BEGIN] =
276
- SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) > +DEAD_ZONE;
277
- gamepad[GP_BUTTON_0 + button++ - GP_RANGE_BEGIN] =
278
- SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) > +DEAD_ZONE;
445
+ // Represent the triggers as buttons in addition to them being axes.
446
+ gamepad.buttons[GP_BUTTON_0 + button++ - GP_RANGE_BEGIN] =
447
+ gamepad.axes[GP_LEFT_TRIGGER_AXIS] >= 0.5;
448
+ gamepad.buttons[GP_BUTTON_0 + button++ - GP_RANGE_BEGIN] =
449
+ gamepad.axes[GP_RIGHT_TRIGGER_AXIS] >= 0.5;
450
+
451
+ merge_directions(gamepad);
452
+ return gamepad;
279
453
  }
280
-
281
- void poll_joystick(SDL_Joystick* joystick, GamepadBuffer& gamepad)
454
+
455
+ GamepadBuffer poll_joystick(SDL_Joystick* joystick)
282
456
  {
457
+ GamepadBuffer gamepad;
458
+
459
+ // Just guess that the first four axes are equivalent to two analog sticks.
283
460
  int axes = SDL_JoystickNumAxes(joystick);
284
- for (int axis = 0; axis < axes; ++axis) {
285
- Sint16 value = SDL_JoystickGetAxis(joystick, axis);
286
-
287
- if (value < -DEAD_ZONE) {
288
- gamepad[(axis % 2 ? GP_UP : GP_LEFT) - GP_RANGE_BEGIN] = true;
289
- }
290
- else if (value > +DEAD_ZONE) {
291
- gamepad[(axis % 2 ? GP_DOWN : GP_RIGHT) - GP_RANGE_BEGIN] = true;
292
- }
461
+ if (axes > 0) {
462
+ gamepad.axes[GP_LEFT_STICK_X_AXIS] =
463
+ scale_axis(SDL_JoystickGetAxis(joystick, 0));
293
464
  }
294
-
465
+ if (axes > 1) {
466
+ gamepad.axes[GP_LEFT_STICK_Y_AXIS] =
467
+ scale_axis(SDL_JoystickGetAxis(joystick, 1));
468
+ }
469
+ if (axes > 2) {
470
+ gamepad.axes[GP_RIGHT_STICK_X_AXIS] =
471
+ scale_axis(SDL_JoystickGetAxis(joystick, 2));
472
+ }
473
+ if (axes > 3) {
474
+ gamepad.axes[GP_RIGHT_STICK_Y_AXIS] =
475
+ scale_axis(SDL_JoystickGetAxis(joystick, 3));
476
+ }
477
+
478
+ // All hats are merged into the DPad.
295
479
  int hats = SDL_JoystickNumHats(joystick);
296
480
  for (int hat = 0; hat < hats; ++hat) {
297
481
  Uint8 value = SDL_JoystickGetHat(joystick, hat);
298
- if (value & SDL_HAT_LEFT) gamepad[GP_LEFT - GP_RANGE_BEGIN] = true;
299
- if (value & SDL_HAT_RIGHT) gamepad[GP_RIGHT - GP_RANGE_BEGIN] = true;
300
- if (value & SDL_HAT_UP) gamepad[GP_UP - GP_RANGE_BEGIN] = true;
301
- if (value & SDL_HAT_DOWN) gamepad[GP_DOWN - GP_RANGE_BEGIN] = true;
482
+ if (value & SDL_HAT_LEFT) gamepad.buttons[GP_DPAD_LEFT - GP_RANGE_BEGIN] = true;
483
+ if (value & SDL_HAT_RIGHT) gamepad.buttons[GP_DPAD_RIGHT - GP_RANGE_BEGIN] = true;
484
+ if (value & SDL_HAT_UP) gamepad.buttons[GP_DPAD_UP - GP_RANGE_BEGIN] = true;
485
+ if (value & SDL_HAT_DOWN) gamepad.buttons[GP_DPAD_DOWN - GP_RANGE_BEGIN] = true;
302
486
  }
303
-
304
- int buttons = min<int>(GP_NUM_PER_GAMEPAD - 4, SDL_JoystickNumButtons(joystick));
487
+
488
+ int buttons = min<int>(NUM_BUTTONS_PER_GAMEPAD - 4, SDL_JoystickNumButtons(joystick));
305
489
  for (int button = 0; button < buttons; ++button) {
306
490
  if (SDL_JoystickGetButton(joystick, button)) {
307
- gamepad[GP_BUTTON_0 + button - GP_RANGE_BEGIN] = true;
491
+ gamepad.buttons[GP_BUTTON_0 + button - GP_RANGE_BEGIN] = true;
308
492
  }
309
493
  }
494
+
495
+ merge_directions(gamepad);
496
+ return gamepad;
497
+ }
498
+
499
+ void merge_directions(GamepadBuffer& gamepad)
500
+ {
501
+ gamepad.directions[0] =
502
+ gamepad.buttons[GP_DPAD_LEFT - GP_RANGE_BEGIN] ||
503
+ gamepad.axes[GP_LEFT_STICK_X_AXIS] <= -0.5 ||
504
+ gamepad.axes[GP_RIGHT_STICK_X_AXIS] <= -0.5;
505
+ gamepad.directions[1] =
506
+ gamepad.buttons[GP_DPAD_RIGHT - GP_RANGE_BEGIN] ||
507
+ gamepad.axes[GP_LEFT_STICK_X_AXIS] >= +0.5 ||
508
+ gamepad.axes[GP_RIGHT_STICK_X_AXIS] >= +0.5;
509
+ gamepad.directions[2] =
510
+ gamepad.buttons[GP_DPAD_UP - GP_RANGE_BEGIN] ||
511
+ gamepad.axes[GP_LEFT_STICK_Y_AXIS] <= -0.5 ||
512
+ gamepad.axes[GP_RIGHT_STICK_Y_AXIS] <= -0.5;
513
+ gamepad.directions[3] =
514
+ gamepad.buttons[GP_DPAD_DOWN - GP_RANGE_BEGIN] ||
515
+ gamepad.axes[GP_LEFT_STICK_Y_AXIS] >= +0.5 ||
516
+ gamepad.axes[GP_RIGHT_STICK_Y_AXIS] >= +0.5;
310
517
  }
311
518
  };
312
519
 
@@ -328,23 +535,23 @@ bool Gosu::Input::feed_sdl_event(void* event)
328
535
  string Gosu::Input::id_to_char(Button btn)
329
536
  {
330
537
  require_sdl_video();
331
-
332
- if (btn.id() > KB_RANGE_END) return "";
333
-
538
+
539
+ if (btn > KB_RANGE_END) return "";
540
+
334
541
  // SDL_GetKeyName returns "Space" for this value, but we want the character value.
335
- if (btn.id() == KB_SPACE) return " ";
336
-
337
- SDL_Keycode keycode = SDL_GetKeyFromScancode(static_cast<SDL_Scancode>(btn.id()));
542
+ if (btn == KB_SPACE) return " ";
543
+
544
+ SDL_Keycode keycode = SDL_GetKeyFromScancode(static_cast<SDL_Scancode>(btn));
338
545
  if (keycode == SDLK_UNKNOWN) return "";
339
-
546
+
340
547
  const char* name = SDL_GetKeyName(keycode);
341
548
  if (name == nullptr) return "";
342
-
549
+
343
550
  u32string codepoints = utf8_to_composed_utc4(name);
344
-
551
+
345
552
  // Filter out names that are more than one logical character.
346
553
  if (codepoints.length() != 1) return "";
347
-
554
+
348
555
  // Always return lower case to be consistent with previous versions of Gosu.
349
556
  codepoints[0] = utf8proc_tolower(codepoints[0]);
350
557
  // Convert back to UTF-8.
@@ -356,16 +563,57 @@ string Gosu::Input::id_to_char(Button btn)
356
563
  Gosu::Button Gosu::Input::char_to_id(string ch)
357
564
  {
358
565
  require_sdl_video();
359
-
566
+
360
567
  SDL_Keycode keycode = SDL_GetKeyFromName(ch.c_str());
361
568
  return keycode == SDLK_UNKNOWN ? NO_BUTTON : Button(SDL_GetScancodeFromKey(keycode));
362
569
  }
363
570
 
571
+ std::string Gosu::Input::button_name(Button btn)
572
+ {
573
+ require_sdl_video();
574
+
575
+ SDL_Keycode keycode = SDL_GetKeyFromScancode(static_cast<SDL_Scancode>(btn));
576
+ return SDL_GetKeyName(keycode);
577
+ }
578
+
579
+ std::string Gosu::Input::gamepad_name(int index)
580
+ {
581
+ if (index < 0 || index > gamepad_slots.size() - 1) {
582
+ return "";
583
+ }
584
+
585
+ int instance_id = gamepad_slots[index];
586
+
587
+ if (instance_id == -1) {
588
+ return "";
589
+ }
590
+
591
+ if (SDL_GameController* game_controller = SDL_GameControllerFromInstanceID(instance_id)) {
592
+ return SDL_GameControllerName(game_controller);
593
+ }
594
+ else if (SDL_Joystick* joystick = SDL_JoystickFromInstanceID(instance_id)) {
595
+ return SDL_JoystickName(joystick);
596
+ }
597
+
598
+ return "";
599
+ }
600
+
364
601
  bool Gosu::Input::down(Gosu::Button btn)
365
602
  {
366
- if (btn == NO_BUTTON || btn.id() >= NUM_BUTTONS) return false;
367
-
368
- return button_states[btn.id()];
603
+ if (btn == NO_BUTTON || btn >= NUM_BUTTONS) return false;
604
+
605
+ return button_states[btn];
606
+ }
607
+
608
+ double Gosu::Input::axis(Gosu::Button btn)
609
+ {
610
+ unsigned axis_id = btn - GP_LEFT_STICK_X_AXIS;
611
+
612
+ if (axis_id >= axis_states.size()) {
613
+ throw std::out_of_range("Invalid axis ID: " + std::to_string(btn));
614
+ }
615
+
616
+ return axis_states[axis_id];
369
617
  }
370
618
 
371
619
  double Gosu::Input::mouse_x() const
@@ -395,7 +643,7 @@ void Gosu::Input::set_mouse_factors(double scale_x, double scale_y,
395
643
  const Gosu::Touches& Gosu::Input::current_touches() const
396
644
  {
397
645
  // We could use the SDL 2 touch API to implement this.
398
-
646
+
399
647
  static Gosu::Touches none;
400
648
  return none;
401
649
  }
@@ -435,7 +683,7 @@ void Gosu::Input::set_text_input(TextInput* text_input)
435
683
  else if (pimpl->text_input == nullptr && text_input) {
436
684
  SDL_StartTextInput();
437
685
  }
438
-
686
+
439
687
  pimpl->text_input = text_input;
440
688
  }
441
689