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