Chesto 0.9
A declarative and element-based library for creating GUIs on homebrew'd consoles
InputEvents.cpp
1#include "InputEvents.hpp"
2#include "RootDisplay.hpp"
3#include <map>
4#include <algorithm>
5#include <limits>
6
7namespace Chesto {
8
9// computer key mappings
10CST_Keycode key_buttons[] = { SDLK_a, SDLK_b, SDLK_x, SDLK_y, SDLK_UP, SDLK_DOWN, SDLK_LEFT, SDLK_RIGHT, SDLK_RETURN, SDLK_l, SDLK_r, SDLK_z, SDLK_BACKSPACE, SDLK_UP, SDLK_DOWN, SDLK_LEFT, SDLK_RIGHT, SDLK_q };
11
12SDL_GameControllerButton pad_buttons[] = { SDL_A, SDL_B, SDL_X, SDL_Y, SDL_UP, SDL_DOWN, SDL_LEFT, SDL_RIGHT, SDL_PLUS, SDL_L, SDL_R, SDL_ZL, SDL_MINUS, SDL_UP_STICK, SDL_DOWN_STICK, SDL_LEFT_STICK, SDL_RIGHT_STICK, SDL_ZR, };
13
14#if defined(__WIIU__) && defined(USE_KEYBOARD)
15#include "../libs/wiiu_kbd/keybdwrapper.h"
16#endif
17
18// our own "buttons" that correspond to the above SDL ones
19unsigned int nintendo_buttons[] = { A_BUTTON, B_BUTTON, X_BUTTON, Y_BUTTON, UP_BUTTON, DOWN_BUTTON, LEFT_BUTTON, RIGHT_BUTTON, START_BUTTON, L_BUTTON, R_BUTTON, ZL_BUTTON, SELECT_BUTTON, UP_BUTTON, DOWN_BUTTON, LEFT_BUTTON, RIGHT_BUTTON, ZR_BUTTON };
20
21// human readable lowercase names for the buttons (used by the UI)
22std::string nintendoButtonNames[] = { "a", "b", "x", "y", "up", "down", "left", "right", "plus", "l", "r", "zl", "minus", "up", "down", "left", "right", "zr" };
23
24// human readable lowercase keyboard buttons
25std::string keyButtonNames[] = { "a", "b", "x", "y", "up", "down", "left", "right", "return", "l", "r", "z", "backspace", "up", "down", "left", "right", "q" };
26
27// wii remote (alone) buttons, a smaller set of actions
28// (buttons that aren't available will need to be pressed using IR sensor)
29unsigned int wii_buttons[] = { A_BUTTON, B_BUTTON, L_BUTTON, R_BUTTON, UP_BUTTON, DOWN_BUTTON, LEFT_BUTTON, RIGHT_BUTTON, START_BUTTON, 0, 0, 0, SELECT_BUTTON, 0, 0, 0, 0, 0 };
30
31std::string wiiButtonNames[] = { "a", "b", "1", "2", "up", "down", "left", "right", "plus", "", "", "", "minus", "", "", "", "", "" };
32
33// wii remote and nunchuk, separate and more actions (but still not all) available
34unsigned int nunchuk_buttons[] = { A_BUTTON, B_BUTTON, L_BUTTON, R_BUTTON, UP_BUTTON, DOWN_BUTTON, LEFT_BUTTON, RIGHT_BUTTON, START_BUTTON, 0, 0, X_BUTTON, SELECT_BUTTON, UP_BUTTON, DOWN_BUTTON, LEFT_BUTTON, RIGHT_BUTTON, Y_BUTTON };
35
36std::string nunchukButtonNames[] = { "a", "b", "1", "2", "up", "down", "left", "right", "plus", "", "", "z", "minus", "up", "down", "left", "right", "c" };
37
38// if true, don't count key inputs (PC/usb keyboard) as button events for us
39bool InputEvents::bypassKeyEvents = false;
40
41auto defaultKeyName = "WiiU Gamepad";
42std::string InputEvents::lastGamepadKey = defaultKeyName;
43unsigned int* currentButtons = nintendo_buttons;
44std::string* currentButtonNames = nintendoButtonNames;
45
46// map of controller name to buttons, names, prefix, and controller type
47std::map<std::string, GamepadInfo> gamepadMap = {
48 /* Non-controller types, like keyboard */
49 { "Keyboard", GamepadInfo(nintendo_buttons, keyButtonNames, "keyboard", "key") },
50 /* These 5 names are returned by the wiiu SDL2 port */
51 { "WiiU Gamepad", GamepadInfo(nintendo_buttons, nintendoButtonNames, "wiiu_button", "gamepad") },
52 { "WiiU Pro Controller", { nintendo_buttons, nintendoButtonNames, "wiiu_button", "pro" } },
53 { "Wii Remote", { wii_buttons, wiiButtonNames, "wii_button", "remote" } },
54 { "Wii Remote and Nunchuk", { nunchuk_buttons, nunchukButtonNames, "wii_button", "nunchuk" } },
55 { "Wii Classic Controller", { nintendo_buttons, nintendoButtonNames, "wii_button", "classic"} },
56 /* The switch SDL2 port only returns this string for all controller types*/
57 { "Switch Controller", { nintendo_buttons, nintendoButtonNames, "switch_button", "pro" } },
58 /* For PC platforms, more specific Switch controller types can be recognized */
59 // { "Pro Controller", { nintendo_buttons, nintendoButtonNames, "switch" } },
60 // { "Joy-Con (L)", { nintendo_buttons, nintendoButtonNames, "switch" } },
61 // { "Joy-Con (R)", { nintendo_buttons, nintendoButtonNames, "switch" } },
62 // { "Switch Pro Controller", { nintendo_buttons, nintendoButtonNames, "switch" } },
63 /* Other controller types */
64 // { "Xbox 360 Controller", { xbox_buttons, xboxButtonNames, "xbox" } },
65 // { "Xbox One Controller", { xbox_buttons, xboxButtonNames, "xbox" } },
66 // { "Xbox Series X Controller", { xbox_buttons, xboxButtonNames, "xbox" } },
67 // { "PS4 Controller", { ps_buttons, psButtonNames, "playstation" } },
68 // { "PS5 Controller", { ps_buttons, psButtonNames, "playstation" } }
69};
70
71InputEvents::InputEvents()
72{
73#if defined(__WIIU__) && defined(USE_KEYBOARD)
74 // hook up keyboard events for wiiu and SDL (TODO: have these fired by SDL2 port itself)
75 KBWrapper* kbdwrapper = new KBWrapper(true, true);
76#endif
77}
78
80{
81 // get an event from SDL
82 if (!SDL_PollEvent(&event))
83 return false;
84
85 // update our variables
86 this->type = event.type;
87 this->noop = false;
88
89 // process joystick hotplugging events
91
92 std::string curControllerName= lastGamepadKey;
93
94 // get the controller name
95 if (this->type == SDL_KEYDOWN || this->type == SDL_KEYUP) {
96 // keyboard event
97 lastGamepadKey = "Keyboard";
98 } else if (this->type == SDL_JOYBUTTONDOWN || this->type == SDL_JOYBUTTONUP) {
99 SDL_Joystick* joystickId = SDL_JoystickFromInstanceID(event.jbutton.which);
100 if (joystickId != NULL) {
101 std::string controllerName = SDL_JoystickName(joystickId);
102 lastGamepadKey = defaultKeyName; // default in case no match is found
103 if (!controllerName.empty() && gamepadMap.find(controllerName) != gamepadMap.end()){
104 lastGamepadKey = controllerName;
105 }
106 }
107 }
108 if (curControllerName != lastGamepadKey) {
109 printf("Switched to controller profile: %s\n", lastGamepadKey.c_str());
110 GamepadInfo& gamepadInfo = gamepadMap[lastGamepadKey];
111 if (gamepadInfo.buttons != nullptr) {
112 currentButtons = gamepadInfo.buttons;
113 }
114 // keyButtonNames = gamepadInfo.names;
115 // TODO: callback to update all buttons on the UI
116 }
117
118 this->isScrolling = false;
119
120#ifdef PC
121 this->allowTouch = false;
122 if (event.type == SDL_MOUSEWHEEL) {
123 this->wheelScroll = event.wheel.y;
124 this->isScrolling = true;
125 }
126#endif
127
128 if (this->type == SDL_QUIT)
129 {
130 return false; //Quitting overrides all other events.
131 }
132 else if (event.key.repeat == 0 && (this->type == SDL_KEYDOWN || this->type == SDL_KEYUP))
133 {
134 this->keyCode = event.key.keysym.sym;
135 this->mod = event.key.keysym.mod;
136 }
137 else if (this->type == SDL_JOYBUTTONDOWN || this->type == SDL_JOYBUTTONUP)
138 {
139 this->keyCode = event.jbutton.button;
140 }
141 else if (this->type == SDL_MOUSEMOTION || this->type == SDL_MOUSEBUTTONUP || this->type == SDL_MOUSEBUTTONDOWN)
142 {
143 bool isMotion = this->type == SDL_MOUSEMOTION;
144
145 this->yPos = isMotion ? event.motion.y : event.button.y;
146 this->xPos = isMotion ? event.motion.x : event.button.x;
147 }
148 else if (allowTouch && (this->type == SDL_FINGERMOTION || this->type == SDL_FINGERUP || this->type == SDL_FINGERDOWN))
149 {
150 this->yPos = event.tfinger.y * SCREEN_HEIGHT;
151 this->xPos = event.tfinger.x * SCREEN_WIDTH;
152 }
153
154 if (this->type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
155 // printf("Window resized to %dx%d\n", event.window.data1, event.window.data2);
156 RootDisplay::mainDisplay->setScreenResolution(
157 event.window.data1 * RootDisplay::dpiScale,
158 event.window.data2 * RootDisplay::dpiScale
159 );
160
161 // callback to alert the app that the window changed resolution
162 if (RootDisplay::mainDisplay->windowResizeCallback)
163 RootDisplay::mainDisplay->windowResizeCallback();
164
165 RootDisplay::mainDisplay->needsRedraw = true;
166 }
167
168 // offset the x, y positions by the dpi scale
169 // clamp to prevent overflow when casting float to int
170 float scaledX = (float)this->xPos * RootDisplay::dpiScale;
171 float scaledY = (float)this->yPos * RootDisplay::dpiScale;
172
173 // clamp to int range to avoid undefined behavior
174 scaledX = std::max(std::min(scaledX, (float)std::numeric_limits<int>::max()), (float)std::numeric_limits<int>::min());
175 scaledY = std::max(std::min(scaledY, (float)std::numeric_limits<int>::max()), (float)std::numeric_limits<int>::min());
176
177 this->xPos = (int)scaledX;
178 this->yPos = (int)scaledY;
179
180 toggleHeldButtons();
181
182 return true;
183}
184
185bool InputEvents::update()
186{
187 this->type = 0;
188 this->keyCode = -1;
189 this->noop = true;
190
191 // process SDL or directional events
192 return processSDLEvents() || processDirectionalButtons();
193}
194
195void InputEvents::toggleHeldButtons()
196{
197 int directionCode = directionForKeycode();
198
199 if (directionCode >= 0)
200 {
201 if (isKeyDown())
202 {
203 // make sure it's not already down
204 if (!held_directions[directionCode])
205 {
206 // on key down, set the corresponding held boolean to true
207 held_directions[directionCode] = true;
208 held_type = this->type;
209
210 // reset the frame counter so we don't fire on this frame
211 // (initial reset is lower to add a slight delay when they first start holding)
212 curFrame = -25;
213 }
214 }
215
216 if (isKeyUp())
217 {
218 // release the corresponding key too
219 held_directions[directionCode] = false;
220 }
221 }
222}
223
224// returns true if a directional event was fire (so that we know to keep consuming later)
225bool InputEvents::processDirectionalButtons()
226{
227 // up the counter
228 curFrame++;
229
230 // if one of the four direction keys is true, fire off repeat events for it
231 // (when rapidFire lines up only)
232 if (curFrame > 0 && curFrame % rapidFireRate == 0)
233 {
234 for (int x = 0; x < 4; x++)
235 {
236 if (!held_directions[x])
237 continue;
238
239 // send a corresponding directional event
240 this->type = held_type;
241 bool isGamepad = (this->type == SDL_JOYBUTTONDOWN || this->type == SDL_JOYBUTTONUP);
242 this->keyCode = isGamepad ? pad_buttons[4 + x] : key_buttons[4 + x]; // send up through right directions
243 this->noop = false;
244
245 return true;
246 }
247 }
248
249 return false;
250}
251
252int InputEvents::directionForKeycode()
253{
254 // this keycode overlaps with some other constants, so just return asap
255 if (this->type == SDL_KEYDOWN && this->keyCode == SDLK_RETURN)
256 return -1;
257
258 // returns 0 1 2 or 3 for up down left or right
259 switch (this->keyCode)
260 {
261 case SDL_UP_STICK:
262 case SDL_UP:
263 case SDLK_UP:
264 return 0;
265 case SDL_DOWN_STICK:
266 case SDL_DOWN:
267 case SDLK_DOWN:
268 return 1;
269 case SDL_LEFT_STICK:
270 case SDL_LEFT:
271 case SDLK_LEFT:
272 return 2;
273 case SDL_RIGHT_STICK:
274 case SDL_RIGHT:
275 case SDLK_RIGHT:
276 return 3;
277 default:
278 return -1;
279 }
280 return -1;
281}
282
283bool InputEvents::held(int buttons)
284{
285 // if it's a key event
286 if ((this->type == SDL_KEYDOWN || this->type == SDL_KEYUP) && !InputEvents::bypassKeyEvents)
287 {
288 for (int x = 0; x < TOTAL_BUTTONS; x++)
289 if (key_buttons[x] == keyCode && (buttons & currentButtons[x]))
290 return true;
291 }
292
293 // if it's a controller event
294 else if (this->type == SDL_JOYBUTTONDOWN || this->type == SDL_JOYBUTTONUP)
295 {
296 for (int x = 0; x < TOTAL_BUTTONS; x++)
297 if (pad_buttons[x] == keyCode && (buttons & currentButtons[x]))
298 return true;
299 }
300
301 return false;
302}
303
304bool InputEvents::pressed(int buttons)
305{
306 return isKeyDown() && held(buttons);
307}
308
309bool InputEvents::released(int buttons)
310{
311 return isKeyUp() && held(buttons);
312}
313
314bool InputEvents::touchIn(int x, int y, int width, int height)
315{
316 return (this->xPos >= x && this->xPos <= x + width && this->yPos >= y && this->yPos <= y + height);
317}
318
319bool InputEvents::isTouchDown()
320{
321 return this->type == SDL_MOUSEBUTTONDOWN || (allowTouch && this->type == SDL_FINGERDOWN);
322}
323
324bool InputEvents::isTouchDrag()
325{
326 return this->type == SDL_MOUSEMOTION || (allowTouch && this->type == SDL_FINGERMOTION);
327}
328
329bool InputEvents::isTouchUp()
330{
331 return this->type == SDL_MOUSEBUTTONUP || (allowTouch && this->type == SDL_FINGERUP);
332}
333
334bool InputEvents::isTouch()
335{
336 return isTouchDown() || isTouchDrag() || isTouchUp();
337}
338
339bool InputEvents::isScroll()
340{
341 return this->isScrolling;
342}
343
344bool InputEvents::isKeyDown()
345{
346 return this->type == SDL_KEYDOWN || this->type == SDL_JOYBUTTONDOWN;
347}
348
349bool InputEvents::isKeyUp()
350{
351 return this->type == SDL_KEYUP || this->type == SDL_JOYBUTTONUP;
352}
353
355{
356 SDL_Joystick *j;
357 switch(event->type)
358 {
359 case SDL_JOYDEVICEADDED:
360 j = SDL_JoystickOpen(event->jdevice.which);
361 // if (j)
362 // printf("Added joystick device: %s, with ID %d\n", SDL_JoystickName(j), SDL_JoystickInstanceID(j));
363 break;
364 case SDL_JOYDEVICEREMOVED:
365 j = SDL_JoystickFromInstanceID(event->jdevice.which);
366 if (j && SDL_JoystickGetAttached(j))
367 {
368 // printf("Removed joystick device: %s\n", SDL_JoystickName(j));
369 SDL_JoystickClose(j);
370 }
371 break;
372 default:
373 break;
374 }
375}
376
377GamepadInfo& InputEvents::getLastGamepadInfo()
378{
379 return gamepadMap[lastGamepadKey];
380}
381
382} // namespace Chesto
bool processSDLEvents()
update which buttons are pressed
Definition: InputEvents.cpp:79
bool touchIn(int x, int width, int y, int height)
whether or not a touch is detected within the specified rect in this cycle
void processJoystickHotplugging(SDL_Event *event)
joystick device events processing
bool held(int buttons)
whether or not a button is pressed during this cycle