1 //
2 // Copyright (c) 2008-2017 the Urho3D project.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22
23 #include "../Precompiled.h"
24
25 #include "../Core/Context.h"
26 #include "../Core/CoreEvents.h"
27 #include "../Core/Mutex.h"
28 #include "../Core/ProcessUtils.h"
29 #include "../Core/Profiler.h"
30 #include "../Core/StringUtils.h"
31 #include "../Graphics/Graphics.h"
32 #include "../Graphics/GraphicsEvents.h"
33 #include "../Input/Input.h"
34 #include "../IO/FileSystem.h"
35 #include "../IO/Log.h"
36 #include "../IO/RWOpsWrapper.h"
37 #include "../Resource/ResourceCache.h"
38 #include "../UI/Text.h"
39 #include "../UI/UI.h"
40
41 #include <SDL/SDL.h>
42
43 #ifdef __EMSCRIPTEN__
44 #include <emscripten/html5.h>
45 #endif
46
47 #include "../DebugNew.h"
48
49 extern "C" int SDL_AddTouch(SDL_TouchID touchID, const char* name);
50
51 // Use a "click inside window to focus" mechanism on desktop platforms when the mouse cursor is hidden
52 #if defined(_WIN32) || (defined(__APPLE__) && !defined(IOS) && !defined(TVOS)) || (defined(__linux__) && !defined(__ANDROID__))
53 #define REQUIRE_CLICK_TO_FOCUS
54 #endif
55
56 namespace Urho3D
57 {
58
59 const int SCREEN_JOYSTICK_START_ID = 0x40000000;
60 const StringHash VAR_BUTTON_KEY_BINDING("VAR_BUTTON_KEY_BINDING");
61 const StringHash VAR_BUTTON_MOUSE_BUTTON_BINDING("VAR_BUTTON_MOUSE_BUTTON_BINDING");
62 const StringHash VAR_LAST_KEYSYM("VAR_LAST_KEYSYM");
63 const StringHash VAR_SCREEN_JOYSTICK_ID("VAR_SCREEN_JOYSTICK_ID");
64
65 const unsigned TOUCHID_MAX = 32;
66
67 /// Convert SDL keycode if necessary.
ConvertSDLKeyCode(int keySym,int scanCode)68 int ConvertSDLKeyCode(int keySym, int scanCode)
69 {
70 if (scanCode == SCANCODE_AC_BACK)
71 return KEY_ESCAPE;
72 else
73 return SDL_tolower(keySym);
74 }
75
GetTouchedElement()76 UIElement* TouchState::GetTouchedElement()
77 {
78 return touchedElement_.Get();
79 }
80
81 #ifdef __EMSCRIPTEN__
82 #define EM_TRUE 1
83 #define EM_FALSE 0
84
85 /// Glue between Urho Input and Emscripten HTML5
86 /** HTML5 (Emscripten) is limited in the way it handles input. The EmscriptenInput class attempts to provide the glue between Urho3D Input behavior and HTML5, where SDL currently fails to do so.
87 *
88 * Mouse Input:
89 * - The OS mouse cursor position can't be set.
90 * - The mouse can be trapped within play area via 'PointerLock API', which requires a request and interaction between the user and browser.
91 * - To request mouse lock, call SetMouseMode(MM_RELATIVE). The E_MOUSEMODECHANGED event will be sent if/when the user confirms the request.
92 * NOTE: The request must be initiated by the user (eg: on mouse button down/up, key down/up).
93 * - The user can press 'escape' key and browser will force user out of pointer lock. Urho will send the E_MOUSEMODECHANGED event.
94 * - SetMouseMode(MM_ABSOLUTE) will leave pointer lock.
95 * - MM_WRAP is unsupported.
96 */
97 /// % Emscripten Input glue. Intended to be used by the Input subsystem only.
98 class EmscriptenInput
99 {
100 friend class Input;
101 public:
102 /// Constructor, expecting pointer to constructing Input instance.
103 EmscriptenInput(Input* inputInst);
104
105 /// Static callback method for Pointer Lock API. Handles change in Pointer Lock state and sends events for mouse mode change.
106 static EM_BOOL HandlePointerLockChange(int eventType, const EmscriptenPointerlockChangeEvent* keyEvent, void* userData);
107 /// Static callback method for tracking focus change events.
108 static EM_BOOL HandleFocusChange(int eventType, const EmscriptenFocusEvent* keyEvent, void* userData);
109 /// Static callback method for suppressing mouse jump.
110 static EM_BOOL HandleMouseJump(int eventType, const EmscriptenMouseEvent * mouseEvent, void* userData);
111
112 /// Static callback method to handle SDL events.
113 static int HandleSDLEvents(void* userData, SDL_Event* event);
114
115 /// Send request to user to gain pointer lock. This requires a user-browser interaction on the first call.
116 void RequestPointerLock(MouseMode mode, bool suppressEvent = false);
117 /// Send request to exit pointer lock. This has the benefit of not requiring the user-browser interaction on the next pointer lock request.
118 void ExitPointerLock(bool suppressEvent = false);
119 /// Returns whether the page is visible.
120 bool IsVisible();
121
122 private:
123 /// Instance of Input subsystem that constructed this instance.
124 Input* inputInst_;
125 /// The mouse mode being requested for pointer-lock.
126 static MouseMode requestedMouseMode_;
127 /// Flag indicating whether to suppress the next mouse mode change event.
128 static bool suppressMouseModeEvent_;
129
130 /// The mouse mode of the previous request for pointer-lock.
131 static MouseMode invalidatedRequestedMouseMode_;
132 /// Flag indicating the previous request to suppress the next mouse mode change event.
133 static bool invalidatedSuppressMouseModeEvent_;
134 };
135
136 bool EmscriptenInput::suppressMouseModeEvent_ = false;
137 MouseMode EmscriptenInput::requestedMouseMode_ = MM_INVALID;
138 bool EmscriptenInput::invalidatedSuppressMouseModeEvent_ = false;
139 MouseMode EmscriptenInput::invalidatedRequestedMouseMode_ = MM_INVALID;
140
EmscriptenInput(Input * inputInst)141 EmscriptenInput::EmscriptenInput(Input* inputInst) :
142 inputInst_(inputInst)
143 {
144 void* vInputInst = (void*)inputInst;
145
146 // Handle pointer lock
147 emscripten_set_pointerlockchange_callback(NULL, vInputInst, false, EmscriptenInput::HandlePointerLockChange);
148
149 // Handle mouse events to prevent mouse jumps
150 emscripten_set_mousedown_callback(NULL, vInputInst, true, EmscriptenInput::HandleMouseJump);
151 emscripten_set_mousemove_callback(NULL, vInputInst, true, EmscriptenInput::HandleMouseJump);
152
153 // Handle focus changes
154 emscripten_set_focusout_callback(NULL, vInputInst, false, EmscriptenInput::HandleFocusChange);
155 emscripten_set_focus_callback(NULL, vInputInst, false, EmscriptenInput::HandleFocusChange);
156
157 // Handle SDL events
158 SDL_AddEventWatch(EmscriptenInput::HandleSDLEvents, vInputInst);
159 }
160
RequestPointerLock(MouseMode mode,bool suppressEvent)161 void EmscriptenInput::RequestPointerLock(MouseMode mode, bool suppressEvent)
162 {
163 requestedMouseMode_ = mode;
164 suppressMouseModeEvent_ = suppressEvent;
165 emscripten_request_pointerlock(NULL, true);
166 }
167
ExitPointerLock(bool suppressEvent)168 void EmscriptenInput::ExitPointerLock(bool suppressEvent)
169 {
170 if (requestedMouseMode_ != MM_INVALID)
171 {
172 invalidatedRequestedMouseMode_ = requestedMouseMode_;
173 invalidatedSuppressMouseModeEvent_ = suppressMouseModeEvent_;
174 }
175 requestedMouseMode_ = MM_INVALID;
176 suppressMouseModeEvent_ = suppressEvent;
177
178 if (inputInst_->IsMouseLocked())
179 {
180 inputInst_->emscriptenExitingPointerLock_ = true;
181 emscripten_exit_pointerlock();
182 }
183 }
184
IsVisible()185 bool EmscriptenInput::IsVisible()
186 {
187 EmscriptenVisibilityChangeEvent visibilityStatus;
188 if (emscripten_get_visibility_status(&visibilityStatus) >= EMSCRIPTEN_RESULT_SUCCESS)
189 return visibilityStatus.hidden >= EM_TRUE ? false : true;
190
191 // Assume visible
192 URHO3D_LOGWARNING("Could not determine visibility status.");
193 return true;
194 }
195
HandlePointerLockChange(int eventType,const EmscriptenPointerlockChangeEvent * keyEvent,void * userData)196 EM_BOOL EmscriptenInput::HandlePointerLockChange(int eventType, const EmscriptenPointerlockChangeEvent* keyEvent, void* userData)
197 {
198 Input* const inputInst = (Input*)userData;
199
200 bool invalid = false;
201 const bool suppress = suppressMouseModeEvent_;
202 if (requestedMouseMode_ == MM_INVALID && invalidatedRequestedMouseMode_ != MM_INVALID)
203 {
204 invalid = true;
205 requestedMouseMode_ = invalidatedRequestedMouseMode_;
206 suppressMouseModeEvent_ = invalidatedSuppressMouseModeEvent_;
207 invalidatedRequestedMouseMode_ = MM_INVALID;
208 invalidatedSuppressMouseModeEvent_ = false;
209 }
210
211 if (keyEvent->isActive >= EM_TRUE)
212 {
213 // Pointer Lock is now active
214 inputInst->emscriptenPointerLock_ = true;
215 inputInst->emscriptenEnteredPointerLock_ = true;
216 inputInst->SetMouseModeEmscriptenFinal(requestedMouseMode_, suppressMouseModeEvent_);
217 }
218 else
219 {
220 // Pointer Lock is now inactive
221 inputInst->emscriptenPointerLock_ = false;
222
223 if (inputInst->mouseMode_ == MM_RELATIVE)
224 inputInst->SetMouseModeEmscriptenFinal(MM_FREE, suppressMouseModeEvent_);
225 else if (inputInst->mouseMode_ == MM_ABSOLUTE)
226 inputInst->SetMouseModeEmscriptenFinal(MM_ABSOLUTE, suppressMouseModeEvent_);
227
228 inputInst->emscriptenExitingPointerLock_ = false;
229 }
230
231 if (invalid)
232 {
233 if (keyEvent->isActive >= EM_TRUE)
234 {
235 // ExitPointerLock was called before the pointer-lock request was accepted.
236 // Exit from pointer-lock to avoid unexpected behavior.
237 invalidatedRequestedMouseMode_ = MM_INVALID;
238 inputInst->emscriptenInput_->ExitPointerLock(suppress);
239 return EM_TRUE;
240 }
241 }
242
243 requestedMouseMode_ = MM_INVALID;
244 suppressMouseModeEvent_ = false;
245
246 invalidatedRequestedMouseMode_ = MM_INVALID;
247 invalidatedSuppressMouseModeEvent_ = false;
248
249 return EM_TRUE;
250 }
251
HandleFocusChange(int eventType,const EmscriptenFocusEvent * keyEvent,void * userData)252 EM_BOOL EmscriptenInput::HandleFocusChange(int eventType, const EmscriptenFocusEvent* keyEvent, void* userData)
253 {
254 Input* const inputInst = (Input*)userData;
255
256 inputInst->SuppressNextMouseMove();
257
258 if (eventType == EMSCRIPTEN_EVENT_FOCUSOUT)
259 inputInst->LoseFocus();
260 else if (eventType == EMSCRIPTEN_EVENT_FOCUS)
261 inputInst->GainFocus();
262
263 return EM_TRUE;
264 }
265
HandleMouseJump(int eventType,const EmscriptenMouseEvent * mouseEvent,void * userData)266 EM_BOOL EmscriptenInput::HandleMouseJump(int eventType, const EmscriptenMouseEvent * mouseEvent, void* userData)
267 {
268 // Suppress mouse jump on pointer-lock change
269 Input* const inputInst = (Input*)userData;
270 bool suppress = false;
271 if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN && inputInst->emscriptenEnteredPointerLock_)
272 {
273 suppress = true;
274 inputInst->emscriptenEnteredPointerLock_ = false;
275 }
276 else if (eventType == EMSCRIPTEN_EVENT_MOUSEMOVE && inputInst->emscriptenExitingPointerLock_)
277 {
278 suppress = true;
279 }
280
281 if (suppress)
282 inputInst->SuppressNextMouseMove();
283
284 return EM_FALSE;
285 }
286
HandleSDLEvents(void * userData,SDL_Event * event)287 int EmscriptenInput::HandleSDLEvents(void* userData, SDL_Event* event)
288 {
289 Input* const inputInst = (Input*)userData;
290
291 inputInst->HandleSDLEvent(event);
292
293 return 0;
294 }
295
296 #endif
297
Initialize(unsigned numButtons,unsigned numAxes,unsigned numHats)298 void JoystickState::Initialize(unsigned numButtons, unsigned numAxes, unsigned numHats)
299 {
300 buttons_.Resize(numButtons);
301 buttonPress_.Resize(numButtons);
302 axes_.Resize(numAxes);
303 hats_.Resize(numHats);
304
305 Reset();
306 }
307
Reset()308 void JoystickState::Reset()
309 {
310 for (unsigned i = 0; i < buttons_.Size(); ++i)
311 {
312 buttons_[i] = false;
313 buttonPress_[i] = false;
314 }
315 for (unsigned i = 0; i < axes_.Size(); ++i)
316 axes_[i] = 0.0f;
317 for (unsigned i = 0; i < hats_.Size(); ++i)
318 hats_[i] = HAT_CENTER;
319 }
320
Input(Context * context)321 Input::Input(Context* context) :
322 Object(context),
323 mouseButtonDown_(0),
324 mouseButtonPress_(0),
325 lastVisibleMousePosition_(MOUSE_POSITION_OFFSCREEN),
326 mouseMoveWheel_(0),
327 inputScale_(Vector2::ONE),
328 windowID_(0),
329 toggleFullscreen_(true),
330 mouseVisible_(false),
331 lastMouseVisible_(false),
332 mouseGrabbed_(false),
333 lastMouseGrabbed_(false),
334 mouseMode_(MM_ABSOLUTE),
335 lastMouseMode_(MM_ABSOLUTE),
336 #ifndef __EMSCRIPTEN__
337 sdlMouseRelative_(false),
338 #else
339 emscriptenPointerLock_(false),
340 emscriptenEnteredPointerLock_(false),
341 emscriptenExitingPointerLock_(false),
342 #endif
343 touchEmulation_(false),
344 inputFocus_(false),
345 minimized_(false),
346 focusedThisFrame_(false),
347 suppressNextMouseMove_(false),
348 mouseMoveScaled_(false),
349 initialized_(false)
350 {
351 context_->RequireSDL(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
352
353 for (int i = 0; i < TOUCHID_MAX; i++)
354 availableTouchIDs_.Push(i);
355
356 SubscribeToEvent(E_SCREENMODE, URHO3D_HANDLER(Input, HandleScreenMode));
357
358 #if defined(__ANDROID__)
359 SDL_SetHint(SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH, "1");
360 #elif defined(__EMSCRIPTEN__)
361 emscriptenInput_ = new EmscriptenInput(this);
362 #endif
363
364 // Try to initialize right now, but skip if screen mode is not yet set
365 Initialize();
366 }
367
~Input()368 Input::~Input()
369 {
370 context_->ReleaseSDL();
371 }
372
Update()373 void Input::Update()
374 {
375 assert(initialized_);
376
377 URHO3D_PROFILE(UpdateInput);
378
379 #ifndef __EMSCRIPTEN__
380 bool mouseMoved = false;
381 if (mouseMove_ != IntVector2::ZERO)
382 mouseMoved = true;
383
384 ResetInputAccumulation();
385
386 SDL_Event evt;
387 while (SDL_PollEvent(&evt))
388 HandleSDLEvent(&evt);
389
390 if (suppressNextMouseMove_ && (mouseMove_ != IntVector2::ZERO || mouseMoved))
391 UnsuppressMouseMove();
392 #endif
393
394 // Check for focus change this frame
395 SDL_Window* window = graphics_->GetWindow();
396 unsigned flags = window ? SDL_GetWindowFlags(window) & (SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS) : 0;
397 #ifndef __EMSCRIPTEN__
398 if (window)
399 {
400 #ifdef REQUIRE_CLICK_TO_FOCUS
401 // When using the "click to focus" mechanism, only focus automatically in fullscreen or non-hidden mouse mode
402 if (!inputFocus_ && ((mouseVisible_ || mouseMode_ == MM_FREE) || graphics_->GetFullscreen()) && (flags & SDL_WINDOW_INPUT_FOCUS))
403 #else
404 if (!inputFocus_ && (flags & SDL_WINDOW_INPUT_FOCUS))
405 #endif
406 focusedThisFrame_ = true;
407
408 if (focusedThisFrame_)
409 GainFocus();
410
411 // Check for losing focus. The window flags are not reliable when using an external window, so prevent losing focus in that case
412 if (inputFocus_ && !graphics_->GetExternalWindow() && (flags & SDL_WINDOW_INPUT_FOCUS) == 0)
413 LoseFocus();
414 }
415 else
416 return;
417
418 // Handle mouse mode MM_WRAP
419 if (mouseVisible_ && mouseMode_ == MM_WRAP)
420 {
421 IntVector2 windowPos = graphics_->GetWindowPosition();
422 IntVector2 mpos;
423 SDL_GetGlobalMouseState(&mpos.x_, &mpos.y_);
424 mpos -= windowPos;
425
426 const int buffer = 5;
427 const int width = graphics_->GetWidth() - buffer * 2;
428 const int height = graphics_->GetHeight() - buffer * 2;
429
430 // SetMousePosition utilizes backbuffer coordinate system, scale now from window coordinates
431 mpos.x_ = (int)(mpos.x_ * inputScale_.x_);
432 mpos.y_ = (int)(mpos.y_ * inputScale_.y_);
433
434 bool warp = false;
435 if (mpos.x_ < buffer)
436 {
437 warp = true;
438 mpos.x_ += width;
439 }
440
441 if (mpos.x_ > buffer + width)
442 {
443 warp = true;
444 mpos.x_ -= width;
445 }
446
447 if (mpos.y_ < buffer)
448 {
449 warp = true;
450 mpos.y_ += height;
451 }
452
453 if (mpos.y_ > buffer + height)
454 {
455 warp = true;
456 mpos.y_ -= height;
457 }
458
459 if (warp)
460 {
461 SetMousePosition(mpos);
462 SuppressNextMouseMove();
463 }
464 }
465 #else
466 if (!window)
467 return;
468 #endif
469
470 #ifndef __EMSCRIPTEN__
471 if (!touchEmulation_ && (graphics_->GetExternalWindow() || ((!sdlMouseRelative_ && !mouseVisible_ && mouseMode_ != MM_FREE) && inputFocus_ && (flags & SDL_WINDOW_MOUSE_FOCUS))))
472 #else
473 if (!touchEmulation_ && !emscriptenPointerLock_ && (graphics_->GetExternalWindow() || (!mouseVisible_ && inputFocus_ && (flags & SDL_WINDOW_MOUSE_FOCUS))))
474 #endif
475 {
476 const IntVector2 mousePosition = GetMousePosition();
477 mouseMove_ = mousePosition - lastMousePosition_;
478 mouseMoveScaled_ = true; // Already in backbuffer scale, since GetMousePosition() operates in that
479
480 #ifndef __EMSCRIPTEN__
481 if (graphics_->GetExternalWindow())
482 lastMousePosition_ = mousePosition;
483 else
484 {
485 // Recenter the mouse cursor manually after move
486 CenterMousePosition();
487 }
488 #else
489 if (mouseMode_ == MM_ABSOLUTE || mouseMode_ == MM_FREE)
490 lastMousePosition_ = mousePosition;
491
492 if (emscriptenExitingPointerLock_)
493 SuppressNextMouseMove();
494 #endif
495 // Send mouse move event if necessary
496 if (mouseMove_ != IntVector2::ZERO)
497 {
498 if (!suppressNextMouseMove_)
499 {
500 using namespace MouseMove;
501
502 VariantMap& eventData = GetEventDataMap();
503
504 eventData[P_X] = mousePosition.x_;
505 eventData[P_Y] = mousePosition.y_;
506 eventData[P_DX] = mouseMove_.x_;
507 eventData[P_DY] = mouseMove_.y_;
508 eventData[P_BUTTONS] = mouseButtonDown_;
509 eventData[P_QUALIFIERS] = GetQualifiers();
510 SendEvent(E_MOUSEMOVE, eventData);
511 }
512 }
513 }
514 #ifndef __EMSCRIPTEN__
515 else if (!touchEmulation_ && !mouseVisible_ && sdlMouseRelative_ && inputFocus_ && (flags & SDL_WINDOW_MOUSE_FOCUS))
516 {
517 // Keep the cursor trapped in window.
518 CenterMousePosition();
519 }
520 #endif
521 }
522
SetMouseVisible(bool enable,bool suppressEvent)523 void Input::SetMouseVisible(bool enable, bool suppressEvent)
524 {
525 const bool startMouseVisible = mouseVisible_;
526
527 // In touch emulation mode only enabled mouse is allowed
528 if (touchEmulation_)
529 enable = true;
530
531 // In mouse mode relative, the mouse should be invisible
532 if (mouseMode_ == MM_RELATIVE)
533 {
534 if (!suppressEvent)
535 lastMouseVisible_ = enable;
536
537 enable = false;
538 }
539
540 // SDL Raspberry Pi "video driver" does not have proper OS mouse support yet, so no-op for now
541 #ifndef RPI
542 if (enable != mouseVisible_)
543 {
544 if (initialized_)
545 {
546 // External windows can only support visible mouse cursor
547 if (graphics_->GetExternalWindow())
548 {
549 mouseVisible_ = true;
550 if (!suppressEvent)
551 lastMouseVisible_ = true;
552 return;
553 }
554
555 if (!enable && inputFocus_)
556 {
557 #ifndef __EMSCRIPTEN__
558 if (mouseVisible_)
559 lastVisibleMousePosition_ = GetMousePosition();
560
561 if (mouseMode_ == MM_ABSOLUTE)
562 SetMouseModeAbsolute(SDL_TRUE);
563 #else
564 if (mouseMode_ == MM_ABSOLUTE && !emscriptenPointerLock_)
565 emscriptenInput_->RequestPointerLock(MM_ABSOLUTE, suppressEvent);
566 #endif
567 SDL_ShowCursor(SDL_FALSE);
568 mouseVisible_ = false;
569 }
570 else if (mouseMode_ != MM_RELATIVE)
571 {
572 SetMouseGrabbed(false, suppressEvent);
573
574 SDL_ShowCursor(SDL_TRUE);
575 mouseVisible_ = true;
576
577 #ifndef __EMSCRIPTEN__
578 if (mouseMode_ == MM_ABSOLUTE)
579 SetMouseModeAbsolute(SDL_FALSE);
580
581 // Update cursor position
582 UI* ui = GetSubsystem<UI>();
583 Cursor* cursor = ui->GetCursor();
584 // If the UI Cursor was visible, use that position instead of last visible OS cursor position
585 if (cursor && cursor->IsVisible())
586 {
587 IntVector2 pos = cursor->GetScreenPosition();
588 if (pos != MOUSE_POSITION_OFFSCREEN)
589 {
590 SetMousePosition(pos);
591 lastMousePosition_ = pos;
592 }
593 }
594 else
595 {
596 if (lastVisibleMousePosition_ != MOUSE_POSITION_OFFSCREEN)
597 {
598 SetMousePosition(lastVisibleMousePosition_);
599 lastMousePosition_ = lastVisibleMousePosition_;
600 }
601 }
602 #else
603 if (mouseMode_ == MM_ABSOLUTE && emscriptenPointerLock_)
604 emscriptenInput_->ExitPointerLock(suppressEvent);
605 #endif
606 }
607 }
608 else
609 {
610 // Allow to set desired mouse visibility before initialization
611 mouseVisible_ = enable;
612 }
613
614 if (mouseVisible_ != startMouseVisible)
615 {
616 SuppressNextMouseMove();
617 if (!suppressEvent)
618 {
619 lastMouseVisible_ = mouseVisible_;
620 using namespace MouseVisibleChanged;
621
622 VariantMap& eventData = GetEventDataMap();
623 eventData[P_VISIBLE] = mouseVisible_;
624 SendEvent(E_MOUSEVISIBLECHANGED, eventData);
625 }
626 }
627 }
628 #endif
629 }
630
ResetMouseVisible()631 void Input::ResetMouseVisible()
632 {
633 #ifndef __EMSCRIPTEN__
634 SetMouseVisible(lastMouseVisible_, false);
635 #else
636 SetMouseVisibleEmscripten(lastMouseVisible_, false);
637 #endif
638 }
639
640 #ifdef __EMSCRIPTEN__
SetMouseVisibleEmscripten(bool enable,bool suppressEvent)641 void Input::SetMouseVisibleEmscripten(bool enable, bool suppressEvent)
642 {
643 if (enable != mouseVisible_)
644 {
645 if (mouseMode_ == MM_ABSOLUTE)
646 {
647 if (enable)
648 {
649 mouseVisible_ = true;
650 SDL_ShowCursor(SDL_TRUE);
651 emscriptenInput_->ExitPointerLock(suppressEvent);
652 }
653 else
654 {
655 if (emscriptenPointerLock_)
656 {
657 mouseVisible_ = false;
658 SDL_ShowCursor(SDL_FALSE);
659 }
660 else
661 emscriptenInput_->RequestPointerLock(MM_ABSOLUTE, suppressEvent);
662 }
663 }
664 else
665 {
666 mouseVisible_ = enable;
667 SDL_ShowCursor(enable ? SDL_TRUE : SDL_FALSE);
668 }
669 }
670
671 if (!suppressEvent)
672 lastMouseVisible_ = mouseVisible_;
673 }
674
SetMouseModeEmscriptenFinal(MouseMode mode,bool suppressEvent)675 void Input::SetMouseModeEmscriptenFinal(MouseMode mode, bool suppressEvent)
676 {
677 if (!suppressEvent)
678 lastMouseMode_ = mode;
679
680 mouseMode_ = mode;
681
682 if (mode == MM_ABSOLUTE)
683 {
684 if (emscriptenPointerLock_)
685 {
686 SetMouseVisibleEmscripten(false, suppressEvent);
687 }
688 else
689 {
690 SetMouseVisibleEmscripten(true, suppressEvent);
691 }
692
693 UI* const ui = GetSubsystem<UI>();
694 Cursor* const cursor = ui->GetCursor();
695 SetMouseGrabbed(!(mouseVisible_ || (cursor && cursor->IsVisible())), suppressEvent);
696 }
697 else if (mode == MM_RELATIVE && emscriptenPointerLock_)
698 {
699 SetMouseGrabbed(true, suppressEvent);
700 SetMouseVisibleEmscripten(false, suppressEvent);
701 }
702 else
703 {
704 SetMouseGrabbed(false, suppressEvent);
705 }
706
707 SuppressNextMouseMove();
708
709 if (!suppressEvent)
710 {
711 VariantMap& eventData = GetEventDataMap();
712 eventData[MouseModeChanged::P_MODE] = mode;
713 eventData[MouseModeChanged::P_MOUSELOCKED] = IsMouseLocked();
714 SendEvent(E_MOUSEMODECHANGED, eventData);
715 }
716 }
717
SetMouseModeEmscripten(MouseMode mode,bool suppressEvent)718 void Input::SetMouseModeEmscripten(MouseMode mode, bool suppressEvent)
719 {
720 if (mode != mouseMode_)
721 SuppressNextMouseMove();
722
723 const MouseMode previousMode = mouseMode_;
724 mouseMode_ = mode;
725
726 UI* const ui = GetSubsystem<UI>();
727 Cursor* const cursor = ui->GetCursor();
728
729 // Handle changing from previous mode
730 if (previousMode == MM_RELATIVE)
731 ResetMouseVisible();
732
733 // Handle changing to new mode
734 if (mode == MM_FREE)
735 {
736 // Attempt to cancel pending pointer-lock requests
737 emscriptenInput_->ExitPointerLock(suppressEvent);
738 SetMouseGrabbed(!(mouseVisible_ || (cursor && cursor->IsVisible())), suppressEvent);
739 }
740 else if (mode == MM_ABSOLUTE)
741 {
742 if (!mouseVisible_)
743 {
744 if (emscriptenPointerLock_)
745 {
746 SetMouseVisibleEmscripten(false, suppressEvent);
747 }
748 else
749 {
750 if (!cursor)
751 SetMouseVisible(true, suppressEvent);
752 // Deferred mouse mode change to pointer-lock callback
753 mouseMode_ = previousMode;
754 emscriptenInput_->RequestPointerLock(MM_ABSOLUTE, suppressEvent);
755 }
756
757 SetMouseGrabbed(!(mouseVisible_ || (cursor && cursor->IsVisible())), suppressEvent);
758 }
759 }
760 else if (mode == MM_RELATIVE)
761 {
762 if (emscriptenPointerLock_)
763 {
764 SetMouseVisibleEmscripten(false, true);
765 SetMouseGrabbed(!(cursor && cursor->IsVisible()), suppressEvent);
766 }
767 else
768 {
769 // Defer mouse mode change to pointer-lock callback
770 SetMouseGrabbed(false, true);
771 mouseMode_ = previousMode;
772 emscriptenInput_->RequestPointerLock(MM_RELATIVE, suppressEvent);
773 }
774 }
775 }
776 #endif
777
SetMouseGrabbed(bool grab,bool suppressEvent)778 void Input::SetMouseGrabbed(bool grab, bool suppressEvent)
779 {
780 // To not interfere with touch UI operation, never report the mouse as grabbed on Android / iOS
781 #if !defined(__ANDROID__) && !defined(IOS)
782 mouseGrabbed_ = grab;
783
784 if (!suppressEvent)
785 lastMouseGrabbed_ = grab;
786 #endif
787 }
788
ResetMouseGrabbed()789 void Input::ResetMouseGrabbed()
790 {
791 SetMouseGrabbed(lastMouseGrabbed_, true);
792 }
793
794
795 #ifndef __EMSCRIPTEN__
SetMouseModeAbsolute(SDL_bool enable)796 void Input::SetMouseModeAbsolute(SDL_bool enable)
797 {
798 SDL_Window* const window = graphics_->GetWindow();
799
800 SDL_SetWindowGrab(window, enable);
801 }
802
SetMouseModeRelative(SDL_bool enable)803 void Input::SetMouseModeRelative(SDL_bool enable)
804 {
805 SDL_Window* const window = graphics_->GetWindow();
806
807 int result = SDL_SetRelativeMouseMode(enable);
808 sdlMouseRelative_ = enable && (result == 0);
809
810 if (result == -1)
811 SDL_SetWindowGrab(window, enable);
812 }
813 #endif
814
SetMouseMode(MouseMode mode,bool suppressEvent)815 void Input::SetMouseMode(MouseMode mode, bool suppressEvent)
816 {
817 const MouseMode previousMode = mouseMode_;
818
819 #ifdef __EMSCRIPTEN__
820 SetMouseModeEmscripten(mode, suppressEvent);
821 #else
822 if (mode != mouseMode_)
823 {
824 if (initialized_)
825 {
826 SuppressNextMouseMove();
827
828 mouseMode_ = mode;
829 SDL_Window* const window = graphics_->GetWindow();
830
831 UI* const ui = GetSubsystem<UI>();
832 Cursor* const cursor = ui->GetCursor();
833
834 // Handle changing from previous mode
835 if (previousMode == MM_ABSOLUTE)
836 {
837 if (!mouseVisible_)
838 SetMouseModeAbsolute(SDL_FALSE);
839 }
840 if (previousMode == MM_RELATIVE)
841 {
842 SetMouseModeRelative(SDL_FALSE);
843 ResetMouseVisible();
844 }
845 else if (previousMode == MM_WRAP)
846 SDL_SetWindowGrab(window, SDL_FALSE);
847
848 // Handle changing to new mode
849 if (mode == MM_ABSOLUTE)
850 {
851 if (!mouseVisible_)
852 SetMouseModeAbsolute(SDL_TRUE);
853 }
854 else if (mode == MM_RELATIVE)
855 {
856 SetMouseVisible(false, true);
857 SetMouseModeRelative(SDL_TRUE);
858 }
859 else if (mode == MM_WRAP)
860 {
861 SetMouseGrabbed(true, suppressEvent);
862 SDL_SetWindowGrab(window, SDL_TRUE);
863 }
864
865 if (mode != MM_WRAP)
866 SetMouseGrabbed(!(mouseVisible_ || (cursor && cursor->IsVisible())), suppressEvent);
867 }
868 else
869 {
870 // Allow to set desired mouse mode before initialization
871 mouseMode_ = mode;
872 }
873 }
874 #endif
875
876 if (!suppressEvent)
877 {
878 lastMouseMode_ = mode;
879
880 if (mouseMode_ != previousMode)
881 {
882 VariantMap& eventData = GetEventDataMap();
883 eventData[MouseModeChanged::P_MODE] = mode;
884 eventData[MouseModeChanged::P_MOUSELOCKED] = IsMouseLocked();
885 SendEvent(E_MOUSEMODECHANGED, eventData);
886 }
887 }
888 }
889
ResetMouseMode()890 void Input::ResetMouseMode()
891 {
892 SetMouseMode(lastMouseMode_, false);
893 }
894
SetToggleFullscreen(bool enable)895 void Input::SetToggleFullscreen(bool enable)
896 {
897 toggleFullscreen_ = enable;
898 }
899
PopulateKeyBindingMap(HashMap<String,int> & keyBindingMap)900 static void PopulateKeyBindingMap(HashMap<String, int>& keyBindingMap)
901 {
902 if (keyBindingMap.Empty())
903 {
904 keyBindingMap.Insert(MakePair<String, int>("SPACE", KEY_SPACE));
905 keyBindingMap.Insert(MakePair<String, int>("LCTRL", KEY_LCTRL));
906 keyBindingMap.Insert(MakePair<String, int>("RCTRL", KEY_RCTRL));
907 keyBindingMap.Insert(MakePair<String, int>("LSHIFT", KEY_LSHIFT));
908 keyBindingMap.Insert(MakePair<String, int>("RSHIFT", KEY_RSHIFT));
909 keyBindingMap.Insert(MakePair<String, int>("LALT", KEY_LALT));
910 keyBindingMap.Insert(MakePair<String, int>("RALT", KEY_RALT));
911 keyBindingMap.Insert(MakePair<String, int>("LGUI", KEY_LGUI));
912 keyBindingMap.Insert(MakePair<String, int>("RGUI", KEY_RGUI));
913 keyBindingMap.Insert(MakePair<String, int>("TAB", KEY_TAB));
914 keyBindingMap.Insert(MakePair<String, int>("RETURN", KEY_RETURN));
915 keyBindingMap.Insert(MakePair<String, int>("RETURN2", KEY_RETURN2));
916 keyBindingMap.Insert(MakePair<String, int>("ENTER", KEY_KP_ENTER));
917 keyBindingMap.Insert(MakePair<String, int>("SELECT", KEY_SELECT));
918 keyBindingMap.Insert(MakePair<String, int>("LEFT", KEY_LEFT));
919 keyBindingMap.Insert(MakePair<String, int>("RIGHT", KEY_RIGHT));
920 keyBindingMap.Insert(MakePair<String, int>("UP", KEY_UP));
921 keyBindingMap.Insert(MakePair<String, int>("DOWN", KEY_DOWN));
922 keyBindingMap.Insert(MakePair<String, int>("PAGEUP", KEY_PAGEUP));
923 keyBindingMap.Insert(MakePair<String, int>("PAGEDOWN", KEY_PAGEDOWN));
924 keyBindingMap.Insert(MakePair<String, int>("F1", KEY_F1));
925 keyBindingMap.Insert(MakePair<String, int>("F2", KEY_F2));
926 keyBindingMap.Insert(MakePair<String, int>("F3", KEY_F3));
927 keyBindingMap.Insert(MakePair<String, int>("F4", KEY_F4));
928 keyBindingMap.Insert(MakePair<String, int>("F5", KEY_F5));
929 keyBindingMap.Insert(MakePair<String, int>("F6", KEY_F6));
930 keyBindingMap.Insert(MakePair<String, int>("F7", KEY_F7));
931 keyBindingMap.Insert(MakePair<String, int>("F8", KEY_F8));
932 keyBindingMap.Insert(MakePair<String, int>("F9", KEY_F9));
933 keyBindingMap.Insert(MakePair<String, int>("F10", KEY_F10));
934 keyBindingMap.Insert(MakePair<String, int>("F11", KEY_F11));
935 keyBindingMap.Insert(MakePair<String, int>("F12", KEY_F12));
936 }
937 }
938
PopulateMouseButtonBindingMap(HashMap<String,int> & mouseButtonBindingMap)939 static void PopulateMouseButtonBindingMap(HashMap<String, int>& mouseButtonBindingMap)
940 {
941 if (mouseButtonBindingMap.Empty())
942 {
943 mouseButtonBindingMap.Insert(MakePair<String, int>("LEFT", SDL_BUTTON_LEFT));
944 mouseButtonBindingMap.Insert(MakePair<String, int>("MIDDLE", SDL_BUTTON_MIDDLE));
945 mouseButtonBindingMap.Insert(MakePair<String, int>("RIGHT", SDL_BUTTON_RIGHT));
946 mouseButtonBindingMap.Insert(MakePair<String, int>("X1", SDL_BUTTON_X1));
947 mouseButtonBindingMap.Insert(MakePair<String, int>("X2", SDL_BUTTON_X2));
948 }
949 }
950
AddScreenJoystick(XMLFile * layoutFile,XMLFile * styleFile)951 SDL_JoystickID Input::AddScreenJoystick(XMLFile* layoutFile, XMLFile* styleFile)
952 {
953 static HashMap<String, int> keyBindingMap;
954 static HashMap<String, int> mouseButtonBindingMap;
955
956 if (!graphics_)
957 {
958 URHO3D_LOGWARNING("Cannot add screen joystick in headless mode");
959 return -1;
960 }
961
962 // If layout file is not given, use the default screen joystick layout
963 if (!layoutFile)
964 {
965 ResourceCache* cache = GetSubsystem<ResourceCache>();
966 layoutFile = cache->GetResource<XMLFile>("UI/ScreenJoystick.xml");
967 if (!layoutFile) // Error is already logged
968 return -1;
969 }
970
971 UI* ui = GetSubsystem<UI>();
972 SharedPtr<UIElement> screenJoystick = ui->LoadLayout(layoutFile, styleFile);
973 if (!screenJoystick) // Error is already logged
974 return -1;
975
976 screenJoystick->SetSize(ui->GetRoot()->GetSize());
977 ui->GetRoot()->AddChild(screenJoystick);
978
979 // Get an unused ID for the screen joystick
980 /// \todo After a real joystick has been plugged in 1073741824 times, the ranges will overlap
981 SDL_JoystickID joystickID = SCREEN_JOYSTICK_START_ID;
982 while (joysticks_.Contains(joystickID))
983 ++joystickID;
984
985 JoystickState& state = joysticks_[joystickID];
986 state.joystickID_ = joystickID;
987 state.name_ = screenJoystick->GetName();
988 state.screenJoystick_ = screenJoystick;
989
990 unsigned numButtons = 0;
991 unsigned numAxes = 0;
992 unsigned numHats = 0;
993 const Vector<SharedPtr<UIElement> >& children = state.screenJoystick_->GetChildren();
994 for (Vector<SharedPtr<UIElement> >::ConstIterator iter = children.Begin(); iter != children.End(); ++iter)
995 {
996 UIElement* element = iter->Get();
997 String name = element->GetName();
998 if (name.StartsWith("Button"))
999 {
1000 ++numButtons;
1001
1002 // Check whether the button has key binding
1003 Text* text = element->GetChildDynamicCast<Text>("KeyBinding", false);
1004 if (text)
1005 {
1006 text->SetVisible(false);
1007 const String& key = text->GetText();
1008 int keyBinding;
1009 if (key.Length() == 1)
1010 keyBinding = key[0];
1011 else
1012 {
1013 PopulateKeyBindingMap(keyBindingMap);
1014
1015 HashMap<String, int>::Iterator i = keyBindingMap.Find(key);
1016 if (i != keyBindingMap.End())
1017 keyBinding = i->second_;
1018 else
1019 {
1020 URHO3D_LOGERRORF("Unsupported key binding: %s", key.CString());
1021 keyBinding = M_MAX_INT;
1022 }
1023 }
1024
1025 if (keyBinding != M_MAX_INT)
1026 element->SetVar(VAR_BUTTON_KEY_BINDING, keyBinding);
1027 }
1028
1029 // Check whether the button has mouse button binding
1030 text = element->GetChildDynamicCast<Text>("MouseButtonBinding", false);
1031 if (text)
1032 {
1033 text->SetVisible(false);
1034 const String& mouseButton = text->GetText();
1035 PopulateMouseButtonBindingMap(mouseButtonBindingMap);
1036
1037 HashMap<String, int>::Iterator i = mouseButtonBindingMap.Find(mouseButton);
1038 if (i != mouseButtonBindingMap.End())
1039 element->SetVar(VAR_BUTTON_MOUSE_BUTTON_BINDING, i->second_);
1040 else
1041 URHO3D_LOGERRORF("Unsupported mouse button binding: %s", mouseButton.CString());
1042 }
1043 }
1044 else if (name.StartsWith("Axis"))
1045 {
1046 ++numAxes;
1047
1048 ///\todo Axis emulation for screen joystick is not fully supported yet.
1049 URHO3D_LOGWARNING("Axis emulation for screen joystick is not fully supported yet");
1050 }
1051 else if (name.StartsWith("Hat"))
1052 {
1053 ++numHats;
1054
1055 Text* text = element->GetChildDynamicCast<Text>("KeyBinding", false);
1056 if (text)
1057 {
1058 text->SetVisible(false);
1059 String keyBinding = text->GetText();
1060 int mappedKeyBinding[4] = {KEY_W, KEY_S, KEY_A, KEY_D};
1061 Vector<String> keyBindings;
1062 if (keyBinding.Contains(' ')) // e.g.: "UP DOWN LEFT RIGHT"
1063 keyBindings = keyBinding.Split(' '); // Attempt to split the text using ' ' as separator
1064 else if (keyBinding.Length() == 4)
1065 {
1066 keyBindings.Resize(4); // e.g.: "WSAD"
1067 for (unsigned i = 0; i < 4; ++i)
1068 keyBindings[i] = keyBinding.Substring(i, 1);
1069 }
1070 if (keyBindings.Size() == 4)
1071 {
1072 PopulateKeyBindingMap(keyBindingMap);
1073
1074 for (unsigned j = 0; j < 4; ++j)
1075 {
1076 if (keyBindings[j].Length() == 1)
1077 mappedKeyBinding[j] = keyBindings[j][0];
1078 else
1079 {
1080 HashMap<String, int>::Iterator i = keyBindingMap.Find(keyBindings[j]);
1081 if (i != keyBindingMap.End())
1082 mappedKeyBinding[j] = i->second_;
1083 else
1084 URHO3D_LOGERRORF("%s - %s cannot be mapped, fallback to '%c'", name.CString(), keyBindings[j].CString(),
1085 mappedKeyBinding[j]);
1086 }
1087 }
1088 }
1089 else
1090 URHO3D_LOGERRORF("%s has invalid key binding %s, fallback to WSAD", name.CString(), keyBinding.CString());
1091 element->SetVar(VAR_BUTTON_KEY_BINDING, IntRect(mappedKeyBinding));
1092 }
1093 }
1094
1095 element->SetVar(VAR_SCREEN_JOYSTICK_ID, joystickID);
1096 }
1097
1098 // Make sure all the children are non-focusable so they do not mistakenly to be considered as active UI input controls by application
1099 PODVector<UIElement*> allChildren;
1100 state.screenJoystick_->GetChildren(allChildren, true);
1101 for (PODVector<UIElement*>::Iterator iter = allChildren.Begin(); iter != allChildren.End(); ++iter)
1102 (*iter)->SetFocusMode(FM_NOTFOCUSABLE);
1103
1104 state.Initialize(numButtons, numAxes, numHats);
1105
1106 // There could be potentially more than one screen joystick, however they all will be handled by a same handler method
1107 // So there is no harm to replace the old handler with the new handler in each call to SubscribeToEvent()
1108 SubscribeToEvent(E_TOUCHBEGIN, URHO3D_HANDLER(Input, HandleScreenJoystickTouch));
1109 SubscribeToEvent(E_TOUCHMOVE, URHO3D_HANDLER(Input, HandleScreenJoystickTouch));
1110 SubscribeToEvent(E_TOUCHEND, URHO3D_HANDLER(Input, HandleScreenJoystickTouch));
1111
1112 return joystickID;
1113 }
1114
RemoveScreenJoystick(SDL_JoystickID id)1115 bool Input::RemoveScreenJoystick(SDL_JoystickID id)
1116 {
1117 if (!joysticks_.Contains(id))
1118 {
1119 URHO3D_LOGERRORF("Failed to remove non-existing screen joystick ID #%d", id);
1120 return false;
1121 }
1122
1123 JoystickState& state = joysticks_[id];
1124 if (!state.screenJoystick_)
1125 {
1126 URHO3D_LOGERRORF("Failed to remove joystick with ID #%d which is not a screen joystick", id);
1127 return false;
1128 }
1129
1130 state.screenJoystick_->Remove();
1131 joysticks_.Erase(id);
1132
1133 return true;
1134 }
1135
SetScreenJoystickVisible(SDL_JoystickID id,bool enable)1136 void Input::SetScreenJoystickVisible(SDL_JoystickID id, bool enable)
1137 {
1138 if (joysticks_.Contains(id))
1139 {
1140 JoystickState& state = joysticks_[id];
1141
1142 if (state.screenJoystick_)
1143 state.screenJoystick_->SetVisible(enable);
1144 }
1145 }
1146
SetScreenKeyboardVisible(bool enable)1147 void Input::SetScreenKeyboardVisible(bool enable)
1148 {
1149 if (enable != SDL_IsTextInputActive())
1150 {
1151 if (enable)
1152 SDL_StartTextInput();
1153 else
1154 SDL_StopTextInput();
1155 }
1156 }
1157
SetTouchEmulation(bool enable)1158 void Input::SetTouchEmulation(bool enable)
1159 {
1160 #if !defined(__ANDROID__) && !defined(IOS)
1161 if (enable != touchEmulation_)
1162 {
1163 if (enable)
1164 {
1165 // Touch emulation needs the mouse visible
1166 if (!mouseVisible_)
1167 SetMouseVisible(true);
1168
1169 // Add a virtual touch device the first time we are enabling emulated touch
1170 if (!SDL_GetNumTouchDevices())
1171 SDL_AddTouch(0, "Emulated Touch");
1172 }
1173 else
1174 ResetTouches();
1175
1176 touchEmulation_ = enable;
1177 }
1178 #endif
1179 }
1180
RecordGesture()1181 bool Input::RecordGesture()
1182 {
1183 // If have no touch devices, fail
1184 if (!SDL_GetNumTouchDevices())
1185 {
1186 URHO3D_LOGERROR("Can not record gesture: no touch devices");
1187 return false;
1188 }
1189
1190 return SDL_RecordGesture(-1) != 0;
1191 }
1192
SaveGestures(Serializer & dest)1193 bool Input::SaveGestures(Serializer& dest)
1194 {
1195 RWOpsWrapper<Serializer> wrapper(dest);
1196 return SDL_SaveAllDollarTemplates(wrapper.GetRWOps()) != 0;
1197 }
1198
SaveGesture(Serializer & dest,unsigned gestureID)1199 bool Input::SaveGesture(Serializer& dest, unsigned gestureID)
1200 {
1201 RWOpsWrapper<Serializer> wrapper(dest);
1202 return SDL_SaveDollarTemplate(gestureID, wrapper.GetRWOps()) != 0;
1203 }
1204
LoadGestures(Deserializer & source)1205 unsigned Input::LoadGestures(Deserializer& source)
1206 {
1207 // If have no touch devices, fail
1208 if (!SDL_GetNumTouchDevices())
1209 {
1210 URHO3D_LOGERROR("Can not load gestures: no touch devices");
1211 return 0;
1212 }
1213
1214 RWOpsWrapper<Deserializer> wrapper(source);
1215 return (unsigned)SDL_LoadDollarTemplates(-1, wrapper.GetRWOps());
1216 }
1217
1218
RemoveGesture(unsigned gestureID)1219 bool Input::RemoveGesture(unsigned gestureID)
1220 {
1221 #ifdef __EMSCRIPTEN__
1222 return false;
1223 #else
1224 return SDL_RemoveDollarTemplate(gestureID) != 0;
1225 #endif
1226 }
1227
RemoveAllGestures()1228 void Input::RemoveAllGestures()
1229 {
1230 #ifndef __EMSCRIPTEN__
1231 SDL_RemoveAllDollarTemplates();
1232 #endif
1233 }
1234
OpenJoystick(unsigned index)1235 SDL_JoystickID Input::OpenJoystick(unsigned index)
1236 {
1237 SDL_Joystick* joystick = SDL_JoystickOpen(index);
1238 if (!joystick)
1239 {
1240 URHO3D_LOGERRORF("Cannot open joystick #%d", index);
1241 return -1;
1242 }
1243
1244 // Create joystick state for the new joystick
1245 int joystickID = SDL_JoystickInstanceID(joystick);
1246 JoystickState& state = joysticks_[joystickID];
1247 state.joystick_ = joystick;
1248 state.joystickID_ = joystickID;
1249 state.name_ = SDL_JoystickName(joystick);
1250 if (SDL_IsGameController(index))
1251 state.controller_ = SDL_GameControllerOpen(index);
1252
1253 unsigned numButtons = (unsigned)SDL_JoystickNumButtons(joystick);
1254 unsigned numAxes = (unsigned)SDL_JoystickNumAxes(joystick);
1255 unsigned numHats = (unsigned)SDL_JoystickNumHats(joystick);
1256
1257 // When the joystick is a controller, make sure there's enough axes & buttons for the standard controller mappings
1258 if (state.controller_)
1259 {
1260 if (numButtons < SDL_CONTROLLER_BUTTON_MAX)
1261 numButtons = SDL_CONTROLLER_BUTTON_MAX;
1262 if (numAxes < SDL_CONTROLLER_AXIS_MAX)
1263 numAxes = SDL_CONTROLLER_AXIS_MAX;
1264 }
1265
1266 state.Initialize(numButtons, numAxes, numHats);
1267
1268 return joystickID;
1269 }
1270
GetKeyFromName(const String & name) const1271 int Input::GetKeyFromName(const String& name) const
1272 {
1273 return SDL_GetKeyFromName(name.CString());
1274 }
1275
GetKeyFromScancode(int scancode) const1276 int Input::GetKeyFromScancode(int scancode) const
1277 {
1278 return SDL_GetKeyFromScancode((SDL_Scancode)scancode);
1279 }
1280
GetKeyName(int key) const1281 String Input::GetKeyName(int key) const
1282 {
1283 return String(SDL_GetKeyName(key));
1284 }
1285
GetScancodeFromKey(int key) const1286 int Input::GetScancodeFromKey(int key) const
1287 {
1288 return SDL_GetScancodeFromKey(key);
1289 }
1290
GetScancodeFromName(const String & name) const1291 int Input::GetScancodeFromName(const String& name) const
1292 {
1293 return SDL_GetScancodeFromName(name.CString());
1294 }
1295
GetScancodeName(int scancode) const1296 String Input::GetScancodeName(int scancode) const
1297 {
1298 return SDL_GetScancodeName((SDL_Scancode)scancode);
1299 }
1300
GetKeyDown(int key) const1301 bool Input::GetKeyDown(int key) const
1302 {
1303 return keyDown_.Contains(SDL_tolower(key));
1304 }
1305
GetKeyPress(int key) const1306 bool Input::GetKeyPress(int key) const
1307 {
1308 return keyPress_.Contains(SDL_tolower(key));
1309 }
1310
GetScancodeDown(int scancode) const1311 bool Input::GetScancodeDown(int scancode) const
1312 {
1313 return scancodeDown_.Contains(scancode);
1314 }
1315
GetScancodePress(int scancode) const1316 bool Input::GetScancodePress(int scancode) const
1317 {
1318 return scancodePress_.Contains(scancode);
1319 }
1320
GetMouseButtonDown(int button) const1321 bool Input::GetMouseButtonDown(int button) const
1322 {
1323 return (mouseButtonDown_ & button) != 0;
1324 }
1325
GetMouseButtonPress(int button) const1326 bool Input::GetMouseButtonPress(int button) const
1327 {
1328 return (mouseButtonPress_ & button) != 0;
1329 }
1330
GetQualifierDown(int qualifier) const1331 bool Input::GetQualifierDown(int qualifier) const
1332 {
1333 if (qualifier == QUAL_SHIFT)
1334 return GetKeyDown(KEY_LSHIFT) || GetKeyDown(KEY_RSHIFT);
1335 if (qualifier == QUAL_CTRL)
1336 return GetKeyDown(KEY_LCTRL) || GetKeyDown(KEY_RCTRL);
1337 if (qualifier == QUAL_ALT)
1338 return GetKeyDown(KEY_LALT) || GetKeyDown(KEY_RALT);
1339
1340 return false;
1341 }
1342
GetQualifierPress(int qualifier) const1343 bool Input::GetQualifierPress(int qualifier) const
1344 {
1345 if (qualifier == QUAL_SHIFT)
1346 return GetKeyPress(KEY_LSHIFT) || GetKeyPress(KEY_RSHIFT);
1347 if (qualifier == QUAL_CTRL)
1348 return GetKeyPress(KEY_LCTRL) || GetKeyPress(KEY_RCTRL);
1349 if (qualifier == QUAL_ALT)
1350 return GetKeyPress(KEY_LALT) || GetKeyPress(KEY_RALT);
1351
1352 return false;
1353 }
1354
GetQualifiers() const1355 int Input::GetQualifiers() const
1356 {
1357 int ret = 0;
1358 if (GetQualifierDown(QUAL_SHIFT))
1359 ret |= QUAL_SHIFT;
1360 if (GetQualifierDown(QUAL_CTRL))
1361 ret |= QUAL_CTRL;
1362 if (GetQualifierDown(QUAL_ALT))
1363 ret |= QUAL_ALT;
1364
1365 return ret;
1366 }
1367
GetMousePosition() const1368 IntVector2 Input::GetMousePosition() const
1369 {
1370 IntVector2 ret = IntVector2::ZERO;
1371
1372 if (!initialized_)
1373 return ret;
1374
1375 SDL_GetMouseState(&ret.x_, &ret.y_);
1376 ret.x_ = (int)(ret.x_ * inputScale_.x_);
1377 ret.y_ = (int)(ret.y_ * inputScale_.y_);
1378
1379 return ret;
1380 }
1381
GetMouseMove() const1382 IntVector2 Input::GetMouseMove() const
1383 {
1384 if (!suppressNextMouseMove_)
1385 return mouseMoveScaled_ ? mouseMove_ : IntVector2((int)(mouseMove_.x_ * inputScale_.x_), (int)(mouseMove_.y_ * inputScale_.y_));
1386 else
1387 return IntVector2::ZERO;
1388 }
1389
GetMouseMoveX() const1390 int Input::GetMouseMoveX() const
1391 {
1392 if (!suppressNextMouseMove_)
1393 return mouseMoveScaled_ ? mouseMove_.x_ : (int)(mouseMove_.x_ * inputScale_.x_);
1394 else
1395 return 0;
1396 }
1397
GetMouseMoveY() const1398 int Input::GetMouseMoveY() const
1399 {
1400 if (!suppressNextMouseMove_)
1401 return mouseMoveScaled_ ? mouseMove_.y_ : mouseMove_.y_ * inputScale_.y_;
1402 else
1403 return 0;
1404 }
1405
GetTouch(unsigned index) const1406 TouchState* Input::GetTouch(unsigned index) const
1407 {
1408 if (index >= touches_.Size())
1409 return 0;
1410
1411 HashMap<int, TouchState>::ConstIterator i = touches_.Begin();
1412 while (index--)
1413 ++i;
1414
1415 return const_cast<TouchState*>(&i->second_);
1416 }
1417
GetJoystickByIndex(unsigned index)1418 JoystickState* Input::GetJoystickByIndex(unsigned index)
1419 {
1420 unsigned compare = 0;
1421 for (HashMap<SDL_JoystickID, JoystickState>::Iterator i = joysticks_.Begin(); i != joysticks_.End(); ++i)
1422 {
1423 if (compare++ == index)
1424 return &(i->second_);
1425 }
1426
1427 return 0;
1428 }
1429
GetJoystickByName(const String & name)1430 JoystickState* Input::GetJoystickByName(const String& name)
1431 {
1432 for (HashMap<SDL_JoystickID, JoystickState>::Iterator i = joysticks_.Begin(); i != joysticks_.End(); ++i)
1433 {
1434 if (i->second_.name_ == name)
1435 return &(i->second_);
1436 }
1437
1438 return 0;
1439 }
1440
GetJoystick(SDL_JoystickID id)1441 JoystickState* Input::GetJoystick(SDL_JoystickID id)
1442 {
1443 HashMap<SDL_JoystickID, JoystickState>::Iterator i = joysticks_.Find(id);
1444 return i != joysticks_.End() ? &(i->second_) : 0;
1445 }
1446
IsScreenJoystickVisible(SDL_JoystickID id) const1447 bool Input::IsScreenJoystickVisible(SDL_JoystickID id) const
1448 {
1449 HashMap<SDL_JoystickID, JoystickState>::ConstIterator i = joysticks_.Find(id);
1450 return i != joysticks_.End() && i->second_.screenJoystick_ && i->second_.screenJoystick_->IsVisible();
1451 }
1452
GetScreenKeyboardSupport() const1453 bool Input::GetScreenKeyboardSupport() const
1454 {
1455 return SDL_HasScreenKeyboardSupport();
1456 }
1457
IsScreenKeyboardVisible() const1458 bool Input::IsScreenKeyboardVisible() const
1459 {
1460 return SDL_IsTextInputActive();
1461 }
1462
IsMouseLocked() const1463 bool Input::IsMouseLocked() const
1464 {
1465 #ifdef __EMSCRIPTEN__
1466 return emscriptenPointerLock_;
1467 #else
1468 return !((mouseMode_ == MM_ABSOLUTE && mouseVisible_) || mouseMode_ == MM_FREE);
1469 #endif
1470 }
1471
IsMinimized() const1472 bool Input::IsMinimized() const
1473 {
1474 // Return minimized state also when unfocused in fullscreen
1475 if (!inputFocus_ && graphics_ && graphics_->GetFullscreen())
1476 return true;
1477 else
1478 return minimized_;
1479 }
1480
Initialize()1481 void Input::Initialize()
1482 {
1483 Graphics* graphics = GetSubsystem<Graphics>();
1484 if (!graphics || !graphics->IsInitialized())
1485 return;
1486
1487 graphics_ = graphics;
1488
1489 // In external window mode only visible mouse is supported
1490 if (graphics_->GetExternalWindow())
1491 mouseVisible_ = true;
1492
1493 // Set the initial activation
1494 initialized_ = true;
1495 #ifndef __EMSCRIPTEN__
1496 GainFocus();
1497 #else
1498 // Note: Page visibility and focus are slightly different, however we can't query last focus with Emscripten (1.29.0)
1499 if (emscriptenInput_->IsVisible())
1500 GainFocus();
1501 else
1502 LoseFocus();
1503 #endif
1504
1505 ResetJoysticks();
1506 ResetState();
1507
1508 SubscribeToEvent(E_BEGINFRAME, URHO3D_HANDLER(Input, HandleBeginFrame));
1509 #ifdef __EMSCRIPTEN__
1510 SubscribeToEvent(E_ENDFRAME, URHO3D_HANDLER(Input, HandleEndFrame));
1511 #endif
1512
1513 URHO3D_LOGINFO("Initialized input");
1514 }
1515
ResetJoysticks()1516 void Input::ResetJoysticks()
1517 {
1518 joysticks_.Clear();
1519
1520 // Open each detected joystick automatically on startup
1521 unsigned size = static_cast<unsigned>(SDL_NumJoysticks());
1522 for (unsigned i = 0; i < size; ++i)
1523 OpenJoystick(i);
1524 }
1525
ResetInputAccumulation()1526 void Input::ResetInputAccumulation()
1527 {
1528 // Reset input accumulation for this frame
1529 keyPress_.Clear();
1530 scancodePress_.Clear();
1531 mouseButtonPress_ = 0;
1532 mouseMove_ = IntVector2::ZERO;
1533 mouseMoveWheel_ = 0;
1534 for (HashMap<SDL_JoystickID, JoystickState>::Iterator i = joysticks_.Begin(); i != joysticks_.End(); ++i)
1535 {
1536 for (unsigned j = 0; j < i->second_.buttonPress_.Size(); ++j)
1537 i->second_.buttonPress_[j] = false;
1538 }
1539
1540 // Reset touch delta movement
1541 for (HashMap<int, TouchState>::Iterator i = touches_.Begin(); i != touches_.End(); ++i)
1542 {
1543 TouchState& state = i->second_;
1544 state.lastPosition_ = state.position_;
1545 state.delta_ = IntVector2::ZERO;
1546 }
1547 }
1548
GainFocus()1549 void Input::GainFocus()
1550 {
1551 ResetState();
1552
1553 inputFocus_ = true;
1554 focusedThisFrame_ = false;
1555
1556 // Restore mouse mode
1557 #ifndef __EMSCRIPTEN__
1558 const MouseMode mm = mouseMode_;
1559 mouseMode_ = MM_FREE;
1560 SetMouseMode(mm, true);
1561 #endif
1562
1563 SuppressNextMouseMove();
1564
1565 // Re-establish mouse cursor hiding as necessary
1566 if (!mouseVisible_)
1567 SDL_ShowCursor(SDL_FALSE);
1568
1569 SendInputFocusEvent();
1570 }
1571
LoseFocus()1572 void Input::LoseFocus()
1573 {
1574 ResetState();
1575
1576 inputFocus_ = false;
1577 focusedThisFrame_ = false;
1578
1579 // Show the mouse cursor when inactive
1580 SDL_ShowCursor(SDL_TRUE);
1581
1582 // Change mouse mode -- removing any cursor grabs, etc.
1583 #ifndef __EMSCRIPTEN__
1584 const MouseMode mm = mouseMode_;
1585 SetMouseMode(MM_FREE, true);
1586 // Restore flags to reflect correct mouse state.
1587 mouseMode_ = mm;
1588 #endif
1589
1590 SendInputFocusEvent();
1591 }
1592
ResetState()1593 void Input::ResetState()
1594 {
1595 keyDown_.Clear();
1596 keyPress_.Clear();
1597 scancodeDown_.Clear();
1598 scancodePress_.Clear();
1599
1600 /// \todo Check if resetting joystick state on input focus loss is even necessary
1601 for (HashMap<SDL_JoystickID, JoystickState>::Iterator i = joysticks_.Begin(); i != joysticks_.End(); ++i)
1602 i->second_.Reset();
1603
1604 ResetTouches();
1605
1606 // Use SetMouseButton() to reset the state so that mouse events will be sent properly
1607 SetMouseButton(MOUSEB_LEFT, false);
1608 SetMouseButton(MOUSEB_RIGHT, false);
1609 SetMouseButton(MOUSEB_MIDDLE, false);
1610
1611 mouseMove_ = IntVector2::ZERO;
1612 mouseMoveWheel_ = 0;
1613 mouseButtonPress_ = 0;
1614 }
1615
ResetTouches()1616 void Input::ResetTouches()
1617 {
1618 for (HashMap<int, TouchState>::Iterator i = touches_.Begin(); i != touches_.End(); ++i)
1619 {
1620 TouchState& state = i->second_;
1621
1622 using namespace TouchEnd;
1623
1624 VariantMap& eventData = GetEventDataMap();
1625 eventData[P_TOUCHID] = state.touchID_;
1626 eventData[P_X] = state.position_.x_;
1627 eventData[P_Y] = state.position_.y_;
1628 SendEvent(E_TOUCHEND, eventData);
1629 }
1630
1631 touches_.Clear();
1632 touchIDMap_.Clear();
1633 availableTouchIDs_.Clear();
1634 for (int i = 0; i < TOUCHID_MAX; i++)
1635 availableTouchIDs_.Push(i);
1636
1637 }
1638
GetTouchIndexFromID(int touchID)1639 unsigned Input::GetTouchIndexFromID(int touchID)
1640 {
1641 HashMap<int, int>::ConstIterator i = touchIDMap_.Find(touchID);
1642 if (i != touchIDMap_.End())
1643 {
1644 return (unsigned)i->second_;
1645 }
1646
1647 unsigned index = PopTouchIndex();
1648 touchIDMap_[touchID] = index;
1649 return index;
1650 }
1651
PopTouchIndex()1652 unsigned Input::PopTouchIndex()
1653 {
1654 if (availableTouchIDs_.Empty())
1655 return 0;
1656
1657 unsigned index = (unsigned)availableTouchIDs_.Front();
1658 availableTouchIDs_.PopFront();
1659 return index;
1660 }
1661
PushTouchIndex(int touchID)1662 void Input::PushTouchIndex(int touchID)
1663 {
1664 HashMap<int, int>::ConstIterator ci = touchIDMap_.Find(touchID);
1665 if (ci == touchIDMap_.End())
1666 return;
1667
1668 int index = touchIDMap_[touchID];
1669 touchIDMap_.Erase(touchID);
1670
1671 // Sorted insertion
1672 bool inserted = false;
1673 for (List<int>::Iterator i = availableTouchIDs_.Begin(); i != availableTouchIDs_.End(); ++i)
1674 {
1675 if (*i == index)
1676 {
1677 // This condition can occur when TOUCHID_MAX is reached.
1678 inserted = true;
1679 break;
1680 }
1681
1682 if (*i > index)
1683 {
1684 availableTouchIDs_.Insert(i, index);
1685 inserted = true;
1686 break;
1687 }
1688 }
1689
1690 // If empty, or the lowest value then insert at end.
1691 if (!inserted)
1692 availableTouchIDs_.Push(index);
1693 }
1694
SendInputFocusEvent()1695 void Input::SendInputFocusEvent()
1696 {
1697 using namespace InputFocus;
1698
1699 VariantMap& eventData = GetEventDataMap();
1700 eventData[P_FOCUS] = HasFocus();
1701 eventData[P_MINIMIZED] = IsMinimized();
1702 SendEvent(E_INPUTFOCUS, eventData);
1703 }
1704
SetMouseButton(int button,bool newState)1705 void Input::SetMouseButton(int button, bool newState)
1706 {
1707 if (newState)
1708 {
1709 if (!(mouseButtonDown_ & button))
1710 mouseButtonPress_ |= button;
1711
1712 mouseButtonDown_ |= button;
1713 }
1714 else
1715 {
1716 if (!(mouseButtonDown_ & button))
1717 return;
1718
1719 mouseButtonDown_ &= ~button;
1720 }
1721
1722 using namespace MouseButtonDown;
1723
1724 VariantMap& eventData = GetEventDataMap();
1725 eventData[P_BUTTON] = button;
1726 eventData[P_BUTTONS] = mouseButtonDown_;
1727 eventData[P_QUALIFIERS] = GetQualifiers();
1728 SendEvent(newState ? E_MOUSEBUTTONDOWN : E_MOUSEBUTTONUP, eventData);
1729 }
1730
SetKey(int key,int scancode,bool newState)1731 void Input::SetKey(int key, int scancode, bool newState)
1732 {
1733 bool repeat = false;
1734
1735 if (newState)
1736 {
1737 scancodeDown_.Insert(scancode);
1738 scancodePress_.Insert(scancode);
1739
1740 if (!keyDown_.Contains(key))
1741 {
1742 keyDown_.Insert(key);
1743 keyPress_.Insert(key);
1744 }
1745 else
1746 repeat = true;
1747 }
1748 else
1749 {
1750 scancodeDown_.Erase(scancode);
1751
1752 if (!keyDown_.Erase(key))
1753 return;
1754 }
1755
1756 using namespace KeyDown;
1757
1758 VariantMap& eventData = GetEventDataMap();
1759 eventData[P_KEY] = key;
1760 eventData[P_SCANCODE] = scancode;
1761 eventData[P_BUTTONS] = mouseButtonDown_;
1762 eventData[P_QUALIFIERS] = GetQualifiers();
1763 if (newState)
1764 eventData[P_REPEAT] = repeat;
1765 SendEvent(newState ? E_KEYDOWN : E_KEYUP, eventData);
1766
1767 if ((key == KEY_RETURN || key == KEY_RETURN2 || key == KEY_KP_ENTER) && newState && !repeat && toggleFullscreen_ &&
1768 (GetKeyDown(KEY_LALT) || GetKeyDown(KEY_RALT)))
1769 graphics_->ToggleFullscreen();
1770 }
1771
SetMouseWheel(int delta)1772 void Input::SetMouseWheel(int delta)
1773 {
1774 if (delta)
1775 {
1776 mouseMoveWheel_ += delta;
1777
1778 using namespace MouseWheel;
1779
1780 VariantMap& eventData = GetEventDataMap();
1781 eventData[P_WHEEL] = delta;
1782 eventData[P_BUTTONS] = mouseButtonDown_;
1783 eventData[P_QUALIFIERS] = GetQualifiers();
1784 SendEvent(E_MOUSEWHEEL, eventData);
1785 }
1786 }
1787
SetMousePosition(const IntVector2 & position)1788 void Input::SetMousePosition(const IntVector2& position)
1789 {
1790 if (!graphics_)
1791 return;
1792
1793 SDL_WarpMouseInWindow(graphics_->GetWindow(), (int)(position.x_ / inputScale_.x_), (int)(position.y_ / inputScale_.y_));
1794 }
1795
CenterMousePosition()1796 void Input::CenterMousePosition()
1797 {
1798 const IntVector2 center(graphics_->GetWidth() / 2, graphics_->GetHeight() / 2);
1799 if (GetMousePosition() != center)
1800 {
1801 SetMousePosition(center);
1802 lastMousePosition_ = center;
1803 }
1804 }
1805
SuppressNextMouseMove()1806 void Input::SuppressNextMouseMove()
1807 {
1808 suppressNextMouseMove_ = true;
1809 mouseMove_ = IntVector2::ZERO;
1810 }
1811
UnsuppressMouseMove()1812 void Input::UnsuppressMouseMove()
1813 {
1814 suppressNextMouseMove_ = false;
1815 mouseMove_ = IntVector2::ZERO;
1816 lastMousePosition_ = GetMousePosition();
1817 }
1818
HandleSDLEvent(void * sdlEvent)1819 void Input::HandleSDLEvent(void* sdlEvent)
1820 {
1821 SDL_Event& evt = *static_cast<SDL_Event*>(sdlEvent);
1822
1823 // While not having input focus, skip key/mouse/touch/joystick events, except for the "click to focus" mechanism
1824 if (!inputFocus_ && evt.type >= SDL_KEYDOWN && evt.type <= SDL_MULTIGESTURE)
1825 {
1826 #ifdef REQUIRE_CLICK_TO_FOCUS
1827 // Require the click to be at least 1 pixel inside the window to disregard clicks in the title bar
1828 if (evt.type == SDL_MOUSEBUTTONDOWN && evt.button.x > 0 && evt.button.y > 0 && evt.button.x < graphics_->GetWidth() - 1 &&
1829 evt.button.y < graphics_->GetHeight() - 1)
1830 {
1831 focusedThisFrame_ = true;
1832 // Do not cause the click to actually go throughfin
1833 return;
1834 }
1835 else if (evt.type == SDL_FINGERDOWN)
1836 {
1837 // When focusing by touch, call GainFocus() immediately as it resets the state; a touch has sustained state
1838 // which should be kept
1839 GainFocus();
1840 }
1841 else
1842 #endif
1843 return;
1844 }
1845
1846 // Possibility for custom handling or suppression of default handling for the SDL event
1847 {
1848 using namespace SDLRawInput;
1849
1850 VariantMap eventData = GetEventDataMap();
1851 eventData[P_SDLEVENT] = &evt;
1852 eventData[P_CONSUMED] = false;
1853 SendEvent(E_SDLRAWINPUT, eventData);
1854
1855 if (eventData[P_CONSUMED].GetBool())
1856 return;
1857 }
1858
1859 switch (evt.type)
1860 {
1861 case SDL_KEYDOWN:
1862 SetKey(ConvertSDLKeyCode(evt.key.keysym.sym, evt.key.keysym.scancode), evt.key.keysym.scancode, true);
1863 break;
1864
1865 case SDL_KEYUP:
1866 SetKey(ConvertSDLKeyCode(evt.key.keysym.sym, evt.key.keysym.scancode), evt.key.keysym.scancode, false);
1867 break;
1868
1869 case SDL_TEXTINPUT:
1870 {
1871 using namespace TextInput;
1872
1873 VariantMap textInputEventData;
1874 textInputEventData[P_TEXT] = textInput_ = &evt.text.text[0];
1875 SendEvent(E_TEXTINPUT, textInputEventData);
1876 }
1877 break;
1878
1879 case SDL_TEXTEDITING:
1880 {
1881 using namespace TextEditing;
1882
1883 VariantMap textEditingEventData;
1884 textEditingEventData[P_COMPOSITION] = &evt.edit.text[0];
1885 textEditingEventData[P_CURSOR] = evt.edit.start;
1886 textEditingEventData[P_SELECTION_LENGTH] = evt.edit.length;
1887 SendEvent(E_TEXTEDITING, textEditingEventData);
1888 }
1889 break;
1890
1891 case SDL_MOUSEBUTTONDOWN:
1892 if (!touchEmulation_)
1893 SetMouseButton(1 << (evt.button.button - 1), true);
1894 else
1895 {
1896 int x, y;
1897 SDL_GetMouseState(&x, &y);
1898 x = (int)(x * inputScale_.x_);
1899 y = (int)(y * inputScale_.y_);
1900
1901 SDL_Event event;
1902 event.type = SDL_FINGERDOWN;
1903 event.tfinger.touchId = 0;
1904 event.tfinger.fingerId = evt.button.button - 1;
1905 event.tfinger.pressure = 1.0f;
1906 event.tfinger.x = (float)x / (float)graphics_->GetWidth();
1907 event.tfinger.y = (float)y / (float)graphics_->GetHeight();
1908 event.tfinger.dx = 0;
1909 event.tfinger.dy = 0;
1910 SDL_PushEvent(&event);
1911 }
1912 break;
1913
1914 case SDL_MOUSEBUTTONUP:
1915 if (!touchEmulation_)
1916 SetMouseButton(1 << (evt.button.button - 1), false);
1917 else
1918 {
1919 int x, y;
1920 SDL_GetMouseState(&x, &y);
1921 x = (int)(x * inputScale_.x_);
1922 y = (int)(y * inputScale_.y_);
1923
1924 SDL_Event event;
1925 event.type = SDL_FINGERUP;
1926 event.tfinger.touchId = 0;
1927 event.tfinger.fingerId = evt.button.button - 1;
1928 event.tfinger.pressure = 0.0f;
1929 event.tfinger.x = (float)x / (float)graphics_->GetWidth();
1930 event.tfinger.y = (float)y / (float)graphics_->GetHeight();
1931 event.tfinger.dx = 0;
1932 event.tfinger.dy = 0;
1933 SDL_PushEvent(&event);
1934 }
1935 break;
1936
1937 case SDL_MOUSEMOTION:
1938 #ifndef __EMSCRIPTEN__
1939 if ((sdlMouseRelative_ || mouseVisible_ || mouseMode_ == MM_FREE) && !touchEmulation_)
1940 #else
1941 if ((mouseVisible_ || emscriptenPointerLock_ || mouseMode_ == MM_FREE) && !touchEmulation_)
1942 #endif
1943 {
1944 #ifdef __EMSCRIPTEN__
1945 if (emscriptenExitingPointerLock_)
1946 {
1947 SuppressNextMouseMove();
1948 break;
1949 }
1950 #endif
1951
1952 // Accumulate without scaling for accuracy, needs to be scaled to backbuffer coordinates when asked
1953 mouseMove_.x_ += evt.motion.xrel;
1954 mouseMove_.y_ += evt.motion.yrel;
1955 mouseMoveScaled_ = false;
1956
1957 if (!suppressNextMouseMove_)
1958 {
1959 using namespace MouseMove;
1960
1961 VariantMap& eventData = GetEventDataMap();
1962 eventData[P_X] = (int)(evt.motion.x * inputScale_.x_);
1963 eventData[P_Y] = (int)(evt.motion.y * inputScale_.y_);
1964 // The "on-the-fly" motion data needs to be scaled now, though this may reduce accuracy
1965 eventData[P_DX] = (int)(evt.motion.xrel * inputScale_.x_);
1966 eventData[P_DY] = (int)(evt.motion.yrel * inputScale_.y_);
1967 eventData[P_BUTTONS] = mouseButtonDown_;
1968 eventData[P_QUALIFIERS] = GetQualifiers();
1969 SendEvent(E_MOUSEMOVE, eventData);
1970 }
1971 }
1972 // Only the left mouse button "finger" moves along with the mouse movement
1973 else if (touchEmulation_ && touches_.Contains(0))
1974 {
1975 int x, y;
1976 SDL_GetMouseState(&x, &y);
1977 x = (int)(x * inputScale_.x_);
1978 y = (int)(y * inputScale_.y_);
1979
1980 SDL_Event event;
1981 event.type = SDL_FINGERMOTION;
1982 event.tfinger.touchId = 0;
1983 event.tfinger.fingerId = 0;
1984 event.tfinger.pressure = 1.0f;
1985 event.tfinger.x = (float)x / (float)graphics_->GetWidth();
1986 event.tfinger.y = (float)y / (float)graphics_->GetHeight();
1987 event.tfinger.dx = (float)evt.motion.xrel * inputScale_.x_ / (float)graphics_->GetWidth();
1988 event.tfinger.dy = (float)evt.motion.yrel * inputScale_.y_ / (float)graphics_->GetHeight();
1989 SDL_PushEvent(&event);
1990 }
1991 break;
1992
1993 case SDL_MOUSEWHEEL:
1994 if (!touchEmulation_)
1995 SetMouseWheel(evt.wheel.y);
1996 break;
1997
1998 case SDL_FINGERDOWN:
1999 if (evt.tfinger.touchId != SDL_TOUCH_MOUSEID)
2000 {
2001 int touchID = GetTouchIndexFromID(evt.tfinger.fingerId & 0x7ffffff);
2002 TouchState& state = touches_[touchID];
2003 state.touchID_ = touchID;
2004 state.lastPosition_ = state.position_ = IntVector2((int)(evt.tfinger.x * graphics_->GetWidth()),
2005 (int)(evt.tfinger.y * graphics_->GetHeight()));
2006 state.delta_ = IntVector2::ZERO;
2007 state.pressure_ = evt.tfinger.pressure;
2008
2009 using namespace TouchBegin;
2010
2011 VariantMap& eventData = GetEventDataMap();
2012 eventData[P_TOUCHID] = touchID;
2013 eventData[P_X] = state.position_.x_;
2014 eventData[P_Y] = state.position_.y_;
2015 eventData[P_PRESSURE] = state.pressure_;
2016 SendEvent(E_TOUCHBEGIN, eventData);
2017
2018 // Finger touch may move the mouse cursor. Suppress next mouse move when cursor hidden to prevent jumps
2019 if (!mouseVisible_)
2020 SuppressNextMouseMove();
2021 }
2022 break;
2023
2024 case SDL_FINGERUP:
2025 if (evt.tfinger.touchId != SDL_TOUCH_MOUSEID)
2026 {
2027 int touchID = GetTouchIndexFromID(evt.tfinger.fingerId & 0x7ffffff);
2028 TouchState& state = touches_[touchID];
2029
2030 using namespace TouchEnd;
2031
2032 VariantMap& eventData = GetEventDataMap();
2033 // Do not trust the position in the finger up event. Instead use the last position stored in the
2034 // touch structure
2035 eventData[P_TOUCHID] = touchID;
2036 eventData[P_X] = state.position_.x_;
2037 eventData[P_Y] = state.position_.y_;
2038 SendEvent(E_TOUCHEND, eventData);
2039
2040 // Add touch index back to list of available touch Ids
2041 PushTouchIndex(evt.tfinger.fingerId & 0x7ffffff);
2042
2043 touches_.Erase(touchID);
2044 }
2045 break;
2046
2047 case SDL_FINGERMOTION:
2048 if (evt.tfinger.touchId != SDL_TOUCH_MOUSEID)
2049 {
2050 int touchID = GetTouchIndexFromID(evt.tfinger.fingerId & 0x7ffffff);
2051 // We don't want this event to create a new touches_ event if it doesn't exist (touchEmulation)
2052 if (touchEmulation_ && !touches_.Contains(touchID))
2053 break;
2054 TouchState& state = touches_[touchID];
2055 state.touchID_ = touchID;
2056 state.position_ = IntVector2((int)(evt.tfinger.x * graphics_->GetWidth()),
2057 (int)(evt.tfinger.y * graphics_->GetHeight()));
2058 state.delta_ = state.position_ - state.lastPosition_;
2059 state.pressure_ = evt.tfinger.pressure;
2060
2061 using namespace TouchMove;
2062
2063 VariantMap& eventData = GetEventDataMap();
2064 eventData[P_TOUCHID] = touchID;
2065 eventData[P_X] = state.position_.x_;
2066 eventData[P_Y] = state.position_.y_;
2067 eventData[P_DX] = (int)(evt.tfinger.dx * graphics_->GetWidth());
2068 eventData[P_DY] = (int)(evt.tfinger.dy * graphics_->GetHeight());
2069 eventData[P_PRESSURE] = state.pressure_;
2070 SendEvent(E_TOUCHMOVE, eventData);
2071
2072 // Finger touch may move the mouse cursor. Suppress next mouse move when cursor hidden to prevent jumps
2073 if (!mouseVisible_)
2074 SuppressNextMouseMove();
2075 }
2076 break;
2077
2078 case SDL_DOLLARRECORD:
2079 {
2080 using namespace GestureRecorded;
2081
2082 VariantMap& eventData = GetEventDataMap();
2083 eventData[P_GESTUREID] = (int)evt.dgesture.gestureId;
2084 SendEvent(E_GESTURERECORDED, eventData);
2085 }
2086 break;
2087
2088 case SDL_DOLLARGESTURE:
2089 {
2090 using namespace GestureInput;
2091
2092 VariantMap& eventData = GetEventDataMap();
2093 eventData[P_GESTUREID] = (int)evt.dgesture.gestureId;
2094 eventData[P_CENTERX] = (int)(evt.dgesture.x * graphics_->GetWidth());
2095 eventData[P_CENTERY] = (int)(evt.dgesture.y * graphics_->GetHeight());
2096 eventData[P_NUMFINGERS] = (int)evt.dgesture.numFingers;
2097 eventData[P_ERROR] = evt.dgesture.error;
2098 SendEvent(E_GESTUREINPUT, eventData);
2099 }
2100 break;
2101
2102 case SDL_MULTIGESTURE:
2103 {
2104 using namespace MultiGesture;
2105
2106 VariantMap& eventData = GetEventDataMap();
2107 eventData[P_CENTERX] = (int)(evt.mgesture.x * graphics_->GetWidth());
2108 eventData[P_CENTERY] = (int)(evt.mgesture.y * graphics_->GetHeight());
2109 eventData[P_NUMFINGERS] = (int)evt.mgesture.numFingers;
2110 eventData[P_DTHETA] = M_RADTODEG * evt.mgesture.dTheta;
2111 eventData[P_DDIST] = evt.mgesture.dDist;
2112 SendEvent(E_MULTIGESTURE, eventData);
2113 }
2114 break;
2115
2116 case SDL_JOYDEVICEADDED:
2117 {
2118 using namespace JoystickConnected;
2119
2120 SDL_JoystickID joystickID = OpenJoystick((unsigned)evt.jdevice.which);
2121
2122 VariantMap& eventData = GetEventDataMap();
2123 eventData[P_JOYSTICKID] = joystickID;
2124 SendEvent(E_JOYSTICKCONNECTED, eventData);
2125 }
2126 break;
2127
2128 case SDL_JOYDEVICEREMOVED:
2129 {
2130 using namespace JoystickDisconnected;
2131
2132 joysticks_.Erase(evt.jdevice.which);
2133
2134 VariantMap& eventData = GetEventDataMap();
2135 eventData[P_JOYSTICKID] = evt.jdevice.which;
2136 SendEvent(E_JOYSTICKDISCONNECTED, eventData);
2137 }
2138 break;
2139
2140 case SDL_JOYBUTTONDOWN:
2141 {
2142 using namespace JoystickButtonDown;
2143
2144 unsigned button = evt.jbutton.button;
2145 SDL_JoystickID joystickID = evt.jbutton.which;
2146 JoystickState& state = joysticks_[joystickID];
2147
2148 // Skip ordinary joystick event for a controller
2149 if (!state.controller_)
2150 {
2151 VariantMap& eventData = GetEventDataMap();
2152 eventData[P_JOYSTICKID] = joystickID;
2153 eventData[P_BUTTON] = button;
2154
2155 if (button < state.buttons_.Size())
2156 {
2157 state.buttons_[button] = true;
2158 state.buttonPress_[button] = true;
2159 SendEvent(E_JOYSTICKBUTTONDOWN, eventData);
2160 }
2161 }
2162 }
2163 break;
2164
2165 case SDL_JOYBUTTONUP:
2166 {
2167 using namespace JoystickButtonUp;
2168
2169 unsigned button = evt.jbutton.button;
2170 SDL_JoystickID joystickID = evt.jbutton.which;
2171 JoystickState& state = joysticks_[joystickID];
2172
2173 if (!state.controller_)
2174 {
2175 VariantMap& eventData = GetEventDataMap();
2176 eventData[P_JOYSTICKID] = joystickID;
2177 eventData[P_BUTTON] = button;
2178
2179 if (button < state.buttons_.Size())
2180 {
2181 if (!state.controller_)
2182 state.buttons_[button] = false;
2183 SendEvent(E_JOYSTICKBUTTONUP, eventData);
2184 }
2185 }
2186 }
2187 break;
2188
2189 case SDL_JOYAXISMOTION:
2190 {
2191 using namespace JoystickAxisMove;
2192
2193 SDL_JoystickID joystickID = evt.jaxis.which;
2194 JoystickState& state = joysticks_[joystickID];
2195
2196 if (!state.controller_)
2197 {
2198 VariantMap& eventData = GetEventDataMap();
2199 eventData[P_JOYSTICKID] = joystickID;
2200 eventData[P_AXIS] = evt.jaxis.axis;
2201 eventData[P_POSITION] = Clamp((float)evt.jaxis.value / 32767.0f, -1.0f, 1.0f);
2202
2203 if (evt.jaxis.axis < state.axes_.Size())
2204 {
2205 // If the joystick is a controller, only use the controller axis mappings
2206 // (we'll also get the controller event)
2207 if (!state.controller_)
2208 state.axes_[evt.jaxis.axis] = eventData[P_POSITION].GetFloat();
2209 SendEvent(E_JOYSTICKAXISMOVE, eventData);
2210 }
2211 }
2212 }
2213 break;
2214
2215 case SDL_JOYHATMOTION:
2216 {
2217 using namespace JoystickHatMove;
2218
2219 SDL_JoystickID joystickID = evt.jaxis.which;
2220 JoystickState& state = joysticks_[joystickID];
2221
2222 VariantMap& eventData = GetEventDataMap();
2223 eventData[P_JOYSTICKID] = joystickID;
2224 eventData[P_HAT] = evt.jhat.hat;
2225 eventData[P_POSITION] = evt.jhat.value;
2226
2227 if (evt.jhat.hat < state.hats_.Size())
2228 {
2229 state.hats_[evt.jhat.hat] = evt.jhat.value;
2230 SendEvent(E_JOYSTICKHATMOVE, eventData);
2231 }
2232 }
2233 break;
2234
2235 case SDL_CONTROLLERBUTTONDOWN:
2236 {
2237 using namespace JoystickButtonDown;
2238
2239 unsigned button = evt.cbutton.button;
2240 SDL_JoystickID joystickID = evt.cbutton.which;
2241 JoystickState& state = joysticks_[joystickID];
2242
2243 VariantMap& eventData = GetEventDataMap();
2244 eventData[P_JOYSTICKID] = joystickID;
2245 eventData[P_BUTTON] = button;
2246
2247 if (button < state.buttons_.Size())
2248 {
2249 state.buttons_[button] = true;
2250 state.buttonPress_[button] = true;
2251 SendEvent(E_JOYSTICKBUTTONDOWN, eventData);
2252 }
2253 }
2254 break;
2255
2256 case SDL_CONTROLLERBUTTONUP:
2257 {
2258 using namespace JoystickButtonUp;
2259
2260 unsigned button = evt.cbutton.button;
2261 SDL_JoystickID joystickID = evt.cbutton.which;
2262 JoystickState& state = joysticks_[joystickID];
2263
2264 VariantMap& eventData = GetEventDataMap();
2265 eventData[P_JOYSTICKID] = joystickID;
2266 eventData[P_BUTTON] = button;
2267
2268 if (button < state.buttons_.Size())
2269 {
2270 state.buttons_[button] = false;
2271 SendEvent(E_JOYSTICKBUTTONUP, eventData);
2272 }
2273 }
2274 break;
2275
2276 case SDL_CONTROLLERAXISMOTION:
2277 {
2278 using namespace JoystickAxisMove;
2279
2280 SDL_JoystickID joystickID = evt.caxis.which;
2281 JoystickState& state = joysticks_[joystickID];
2282
2283 VariantMap& eventData = GetEventDataMap();
2284 eventData[P_JOYSTICKID] = joystickID;
2285 eventData[P_AXIS] = evt.caxis.axis;
2286 eventData[P_POSITION] = Clamp((float)evt.caxis.value / 32767.0f, -1.0f, 1.0f);
2287
2288 if (evt.caxis.axis < state.axes_.Size())
2289 {
2290 state.axes_[evt.caxis.axis] = eventData[P_POSITION].GetFloat();
2291 SendEvent(E_JOYSTICKAXISMOVE, eventData);
2292 }
2293 }
2294 break;
2295
2296 case SDL_WINDOWEVENT:
2297 {
2298 switch (evt.window.event)
2299 {
2300 case SDL_WINDOWEVENT_MINIMIZED:
2301 minimized_ = true;
2302 SendInputFocusEvent();
2303 break;
2304
2305 case SDL_WINDOWEVENT_MAXIMIZED:
2306 case SDL_WINDOWEVENT_RESTORED:
2307 #if defined(IOS) || defined(TVOS) || defined (__ANDROID__)
2308 // On iOS/tvOS we never lose the GL context, but may have done GPU object changes that could not be applied yet. Apply them now
2309 // On Android the old GL context may be lost already, restore GPU objects to the new GL context
2310 graphics_->Restore();
2311 #endif
2312 minimized_ = false;
2313 SendInputFocusEvent();
2314 break;
2315
2316 case SDL_WINDOWEVENT_RESIZED:
2317 graphics_->OnWindowResized();
2318 break;
2319 case SDL_WINDOWEVENT_MOVED:
2320 graphics_->OnWindowMoved();
2321 break;
2322
2323 default: break;
2324 }
2325 }
2326 break;
2327
2328 case SDL_DROPFILE:
2329 {
2330 using namespace DropFile;
2331
2332 VariantMap& eventData = GetEventDataMap();
2333 eventData[P_FILENAME] = GetInternalPath(String(evt.drop.file));
2334 SDL_free(evt.drop.file);
2335
2336 SendEvent(E_DROPFILE, eventData);
2337 }
2338 break;
2339
2340 case SDL_QUIT:
2341 SendEvent(E_EXITREQUESTED);
2342 break;
2343
2344 default: break;
2345 }
2346 }
2347
HandleScreenMode(StringHash eventType,VariantMap & eventData)2348 void Input::HandleScreenMode(StringHash eventType, VariantMap& eventData)
2349 {
2350 if (!initialized_)
2351 Initialize();
2352
2353 // Re-enable cursor clipping, and re-center the cursor (if needed) to the new screen size, so that there is no erroneous
2354 // mouse move event. Also get new window ID if it changed
2355 SDL_Window* window = graphics_->GetWindow();
2356 windowID_ = SDL_GetWindowID(window);
2357
2358 // Resize screen joysticks to new screen size
2359 for (HashMap<SDL_JoystickID, JoystickState>::Iterator i = joysticks_.Begin(); i != joysticks_.End(); ++i)
2360 {
2361 UIElement* screenjoystick = i->second_.screenJoystick_;
2362 if (screenjoystick)
2363 screenjoystick->SetSize(graphics_->GetWidth(), graphics_->GetHeight());
2364 }
2365
2366 if (graphics_->GetFullscreen() || !mouseVisible_)
2367 focusedThisFrame_ = true;
2368
2369 // After setting a new screen mode we should not be minimized
2370 minimized_ = (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) != 0;
2371
2372 // Calculate input coordinate scaling from SDL window to backbuffer ratio
2373 int winWidth, winHeight;
2374 int gfxWidth = graphics_->GetWidth();
2375 int gfxHeight = graphics_->GetHeight();
2376 SDL_GetWindowSize(window, &winWidth, &winHeight);
2377 if (winWidth > 0 && winHeight > 0 && gfxWidth > 0 && gfxHeight > 0)
2378 {
2379 inputScale_.x_ = (float)gfxWidth / (float)winWidth;
2380 inputScale_.y_ = (float)gfxHeight / (float)winHeight;
2381 }
2382 else
2383 inputScale_ = Vector2::ONE;
2384 }
2385
HandleBeginFrame(StringHash eventType,VariantMap & eventData)2386 void Input::HandleBeginFrame(StringHash eventType, VariantMap& eventData)
2387 {
2388 // Update input right at the beginning of the frame
2389 SendEvent(E_INPUTBEGIN);
2390 Update();
2391 SendEvent(E_INPUTEND);
2392 }
2393
2394 #ifdef __EMSCRIPTEN__
HandleEndFrame(StringHash eventType,VariantMap & eventData)2395 void Input::HandleEndFrame(StringHash eventType, VariantMap& eventData)
2396 {
2397 if (suppressNextMouseMove_ && mouseMove_ != IntVector2::ZERO)
2398 UnsuppressMouseMove();
2399
2400 ResetInputAccumulation();
2401 }
2402 #endif
2403
HandleScreenJoystickTouch(StringHash eventType,VariantMap & eventData)2404 void Input::HandleScreenJoystickTouch(StringHash eventType, VariantMap& eventData)
2405 {
2406 using namespace TouchBegin;
2407
2408 // Only interested in events from screen joystick(s)
2409 TouchState& state = touches_[eventData[P_TOUCHID].GetInt()];
2410 IntVector2 position(int(state.position_.x_ / GetSubsystem<UI>()->GetScale()), int(state.position_.y_ / GetSubsystem<UI>()->GetScale()));
2411 UIElement* element = eventType == E_TOUCHBEGIN ? GetSubsystem<UI>()->GetElementAt(position) : state.touchedElement_;
2412 if (!element)
2413 return;
2414 Variant variant = element->GetVar(VAR_SCREEN_JOYSTICK_ID);
2415 if (variant.IsEmpty())
2416 return;
2417 SDL_JoystickID joystickID = variant.GetInt();
2418
2419 if (eventType == E_TOUCHEND)
2420 state.touchedElement_.Reset();
2421 else
2422 state.touchedElement_ = element;
2423
2424 // Prepare a fake SDL event
2425 SDL_Event evt;
2426
2427 const String& name = element->GetName();
2428 if (name.StartsWith("Button"))
2429 {
2430 if (eventType == E_TOUCHMOVE)
2431 return;
2432
2433 // Determine whether to inject a joystick event or keyboard/mouse event
2434 Variant keyBindingVar = element->GetVar(VAR_BUTTON_KEY_BINDING);
2435 Variant mouseButtonBindingVar = element->GetVar(VAR_BUTTON_MOUSE_BUTTON_BINDING);
2436 if (keyBindingVar.IsEmpty() && mouseButtonBindingVar.IsEmpty())
2437 {
2438 evt.type = eventType == E_TOUCHBEGIN ? SDL_JOYBUTTONDOWN : SDL_JOYBUTTONUP;
2439 evt.jbutton.which = joystickID;
2440 evt.jbutton.button = (Uint8)ToUInt(name.Substring(6));
2441 }
2442 else
2443 {
2444 if (!keyBindingVar.IsEmpty())
2445 {
2446 evt.type = eventType == E_TOUCHBEGIN ? SDL_KEYDOWN : SDL_KEYUP;
2447 evt.key.keysym.sym = ToLower(keyBindingVar.GetInt());
2448 evt.key.keysym.scancode = SDL_SCANCODE_UNKNOWN;
2449 }
2450 if (!mouseButtonBindingVar.IsEmpty())
2451 {
2452 // Mouse button are sent as extra events besides key events
2453 // Disable touch emulation handling during this to prevent endless loop
2454 bool oldTouchEmulation = touchEmulation_;
2455 touchEmulation_ = false;
2456
2457 SDL_Event mouseEvent;
2458 mouseEvent.type = eventType == E_TOUCHBEGIN ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP;
2459 mouseEvent.button.button = (Uint8)mouseButtonBindingVar.GetInt();
2460 HandleSDLEvent(&mouseEvent);
2461
2462 touchEmulation_ = oldTouchEmulation;
2463 }
2464 }
2465 }
2466 else if (name.StartsWith("Hat"))
2467 {
2468 Variant keyBindingVar = element->GetVar(VAR_BUTTON_KEY_BINDING);
2469 if (keyBindingVar.IsEmpty())
2470 {
2471 evt.type = SDL_JOYHATMOTION;
2472 evt.jaxis.which = joystickID;
2473 evt.jhat.hat = (Uint8)ToUInt(name.Substring(3));
2474 evt.jhat.value = HAT_CENTER;
2475 if (eventType != E_TOUCHEND)
2476 {
2477 IntVector2 relPosition = position - element->GetScreenPosition() - element->GetSize() / 2;
2478 if (relPosition.y_ < 0 && Abs(relPosition.x_ * 3 / 2) < Abs(relPosition.y_))
2479 evt.jhat.value |= HAT_UP;
2480 if (relPosition.y_ > 0 && Abs(relPosition.x_ * 3 / 2) < Abs(relPosition.y_))
2481 evt.jhat.value |= HAT_DOWN;
2482 if (relPosition.x_ < 0 && Abs(relPosition.y_ * 3 / 2) < Abs(relPosition.x_))
2483 evt.jhat.value |= HAT_LEFT;
2484 if (relPosition.x_ > 0 && Abs(relPosition.y_ * 3 / 2) < Abs(relPosition.x_))
2485 evt.jhat.value |= HAT_RIGHT;
2486 }
2487 }
2488 else
2489 {
2490 // Hat is binded by 4 integers representing keysyms for 'w', 's', 'a', 'd' or something similar
2491 IntRect keyBinding = keyBindingVar.GetIntRect();
2492
2493 if (eventType == E_TOUCHEND)
2494 {
2495 evt.type = SDL_KEYUP;
2496 evt.key.keysym.sym = element->GetVar(VAR_LAST_KEYSYM).GetInt();
2497 if (!evt.key.keysym.sym)
2498 return;
2499
2500 element->SetVar(VAR_LAST_KEYSYM, 0);
2501 }
2502 else
2503 {
2504 evt.type = SDL_KEYDOWN;
2505 IntVector2 relPosition = position - element->GetScreenPosition() - element->GetSize() / 2;
2506 if (relPosition.y_ < 0 && Abs(relPosition.x_ * 3 / 2) < Abs(relPosition.y_))
2507 evt.key.keysym.sym = keyBinding.left_; // The integers are encoded in WSAD order to l-t-r-b
2508 else if (relPosition.y_ > 0 && Abs(relPosition.x_ * 3 / 2) < Abs(relPosition.y_))
2509 evt.key.keysym.sym = keyBinding.top_;
2510 else if (relPosition.x_ < 0 && Abs(relPosition.y_ * 3 / 2) < Abs(relPosition.x_))
2511 evt.key.keysym.sym = keyBinding.right_;
2512 else if (relPosition.x_ > 0 && Abs(relPosition.y_ * 3 / 2) < Abs(relPosition.x_))
2513 evt.key.keysym.sym = keyBinding.bottom_;
2514 else
2515 return;
2516
2517 if (eventType == E_TOUCHMOVE && evt.key.keysym.sym != element->GetVar(VAR_LAST_KEYSYM).GetInt())
2518 {
2519 // Dragging past the directional boundary will cause an additional key up event for previous key symbol
2520 SDL_Event keyEvent;
2521 keyEvent.type = SDL_KEYUP;
2522 keyEvent.key.keysym.sym = element->GetVar(VAR_LAST_KEYSYM).GetInt();
2523 if (keyEvent.key.keysym.sym)
2524 {
2525 keyEvent.key.keysym.scancode = SDL_SCANCODE_UNKNOWN;
2526 HandleSDLEvent(&keyEvent);
2527 }
2528
2529 element->SetVar(VAR_LAST_KEYSYM, 0);
2530 }
2531
2532 evt.key.keysym.scancode = SDL_SCANCODE_UNKNOWN;
2533
2534 element->SetVar(VAR_LAST_KEYSYM, evt.key.keysym.sym);
2535 }
2536 }
2537 }
2538 else
2539 return;
2540
2541 // Handle the fake SDL event to turn it into Urho3D genuine event
2542 HandleSDLEvent(&evt);
2543 }
2544
2545 }
2546