|
|
@@ -40,7 +40,9 @@
|
|
|
#include <cstring>
|
|
|
|
|
|
#include <SDL/SDL.h>
|
|
|
-
|
|
|
+#ifdef EMSCRIPTEN
|
|
|
+#include <emscripten/html5.h>
|
|
|
+#endif
|
|
|
#include "../DebugNew.h"
|
|
|
|
|
|
extern "C" int SDL_AddTouch(SDL_TouchID touchID, const char *name);
|
|
|
@@ -76,6 +78,100 @@ UIElement* TouchState::GetTouchedElement()
|
|
|
return touchedElement_.Get();
|
|
|
}
|
|
|
|
|
|
+#ifdef EMSCRIPTEN
|
|
|
+/// Glue between Urho Input and Emscripten HTML5
|
|
|
+/** 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.
|
|
|
+ *
|
|
|
+ * Mouse Input:
|
|
|
+ * - The OS mouse cursor position can't be set.
|
|
|
+ * - The mouse can be trapped within play area via 'PointerLock API', which requires a request and interaction between the user and browser.
|
|
|
+ * - To request mouse lock, call SetMouseMode(MM_RELATIVE). The E_MOUSEMODECHANGED event will be sent if/when the user confirms the request.
|
|
|
+ * - The user can press 'escape' key and browser will force user out of pointer lock. Urho will send the E_MOUSEMODECHANGED event.
|
|
|
+ * - SetMouseMode(MM_ABSOLUTE) will leave pointer lock.
|
|
|
+ * - MM_WRAP is unsupported.
|
|
|
+ *
|
|
|
+ * Touch Input:
|
|
|
+ */
|
|
|
+/// % Emscripten Input glue. Intended to be used by the Input subsystem only.
|
|
|
+class EmscriptenInput
|
|
|
+{
|
|
|
+public:
|
|
|
+ /// Constructor, expecting pointer to constructing Input instance.
|
|
|
+ EmscriptenInput(Input* inputInst);
|
|
|
+
|
|
|
+ /// Static callback method for Pointer Lock API. Handles change in Pointer Lock state and sends events for mouse mode change.
|
|
|
+ static EM_BOOL PointerLockCallback(int eventType, const EmscriptenPointerlockChangeEvent* keyEvent, void* userData);
|
|
|
+ /// Static callback method for tracking lose focus event.
|
|
|
+ static EM_BOOL LoseFocus(int eventType, const EmscriptenFocusEvent* keyEvent, void* userData);
|
|
|
+ /// Static callback method for tracking gain focus event.
|
|
|
+ static EM_BOOL GainFocus(int eventType, const EmscriptenFocusEvent* keyEvent, void* userData);
|
|
|
+
|
|
|
+ /// Send request to user to gain pointer lock.
|
|
|
+ void RequestPointerLock();
|
|
|
+ void ExitPointerLock();
|
|
|
+
|
|
|
+private:
|
|
|
+ /// Instance of Input subsystem that constructed this instance.
|
|
|
+ Input* inputInst;
|
|
|
+};
|
|
|
+
|
|
|
+EmscriptenInput::EmscriptenInput(Input* inputInst)
|
|
|
+{
|
|
|
+ inputInst = inputInst;
|
|
|
+ emscripten_set_pointerlockchange_callback(
|
|
|
+ NULL, (void*)inputInst, false, EmscriptenInput::PointerLockCallback);
|
|
|
+
|
|
|
+ // Handle focus changes:
|
|
|
+ emscripten_set_blur_callback(NULL, (void*)inputInst, false, EmscriptenInput::LoseFocus);
|
|
|
+ emscripten_set_focus_callback(NULL, (void*)inputInst, false, EmscriptenInput::GainFocus);
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+void EmscriptenInput::RequestPointerLock()
|
|
|
+{
|
|
|
+ emscripten_request_pointerlock(NULL, true);
|
|
|
+}
|
|
|
+
|
|
|
+void EmscriptenInput::ExitPointerLock()
|
|
|
+{
|
|
|
+ emscripten_exit_pointerlock();
|
|
|
+}
|
|
|
+
|
|
|
+EM_BOOL EmscriptenInput::PointerLockCallback(int eventType, const EmscriptenPointerlockChangeEvent* keyEvent, void* userData)
|
|
|
+{
|
|
|
+ Input* inputInst = (Input*)userData;
|
|
|
+ if (keyEvent->isActive > 0)
|
|
|
+ {
|
|
|
+ // Pointer Lock is now active
|
|
|
+ inputInst->SetMouseVisibleEmscripten(false);
|
|
|
+ inputInst->SetMouseModeEmscripten(MM_RELATIVE);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Pointer Lock is now inactive
|
|
|
+ inputInst->SetMouseVisibleEmscripten(true);
|
|
|
+ inputInst->SetMouseModeEmscripten(MM_ABSOLUTE);
|
|
|
+ }
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+EM_BOOL EmscriptenInput::LoseFocus(int eventType, const EmscriptenFocusEvent* keyEvent, void* userData)
|
|
|
+{
|
|
|
+ Input* inputInst = (Input*)userData;
|
|
|
+ if (eventType == EMSCRIPTEN_EVENT_BLUR)
|
|
|
+ inputInst->LoseFocus();
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+EM_BOOL EmscriptenInput::GainFocus(int eventType, const EmscriptenFocusEvent* keyEvent, void* userData)
|
|
|
+{
|
|
|
+ Input* inputInst = (Input*)userData;
|
|
|
+ if (eventType == EMSCRIPTEN_EVENT_FOCUS)
|
|
|
+ inputInst->GainFocus();
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
void JoystickState::Initialize(unsigned numButtons, unsigned numAxes, unsigned numHats)
|
|
|
{
|
|
|
buttons_.Resize(numButtons);
|
|
|
@@ -106,11 +202,7 @@ Input::Input(Context* context) :
|
|
|
mouseMoveWheel_(0),
|
|
|
windowID_(0),
|
|
|
toggleFullscreen_(true),
|
|
|
-#if defined(EMSCRIPTEN) // TODO: properly implement relative mouse motion with emscripten
|
|
|
- mouseVisible_(true),
|
|
|
-#else
|
|
|
mouseVisible_(false),
|
|
|
-#endif
|
|
|
lastMouseVisible_(false),
|
|
|
mouseGrabbed_(false),
|
|
|
mouseMode_(MM_ABSOLUTE),
|
|
|
@@ -127,12 +219,19 @@ Input::Input(Context* context) :
|
|
|
|
|
|
SubscribeToEvent(E_SCREENMODE, HANDLER(Input, HandleScreenMode));
|
|
|
|
|
|
+ #ifdef EMSCRIPTEN
|
|
|
+ emscriptenInput = new EmscriptenInput(this);
|
|
|
+ #endif
|
|
|
+
|
|
|
// Try to initialize right now, but skip if screen mode is not yet set
|
|
|
Initialize();
|
|
|
}
|
|
|
|
|
|
Input::~Input()
|
|
|
{
|
|
|
+ #ifdef EMSCRIPTEN
|
|
|
+ delete emscriptenInput;
|
|
|
+ #endif
|
|
|
}
|
|
|
|
|
|
void Input::Update()
|
|
|
@@ -166,12 +265,34 @@ void Input::Update()
|
|
|
while (SDL_PollEvent(&evt))
|
|
|
HandleSDLEvent(&evt);
|
|
|
|
|
|
+ SDL_Window* window = graphics_->GetImpl()->GetWindow();
|
|
|
+ unsigned flags = window ? SDL_GetWindowFlags(window) & (SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS) : 0;
|
|
|
+#ifndef EMSCRIPTEN
|
|
|
+ if (window)
|
|
|
+ {
|
|
|
+#if defined(REQUIRE_CLICK_TO_FOCUS)
|
|
|
+ if (!inputFocus_ && (graphics_->GetFullscreen() || mouseVisible_) && flags == (SDL_WINDOW_INPUT_FOCUS |
|
|
|
+ SDL_WINDOW_MOUSE_FOCUS))
|
|
|
+#else
|
|
|
+ if (!inputFocus_ && (flags & SDL_WINDOW_INPUT_FOCUS))
|
|
|
+#endif
|
|
|
+ focusedThisFrame_ = true;
|
|
|
+
|
|
|
+ if (focusedThisFrame_)
|
|
|
+ GainFocus();
|
|
|
+
|
|
|
+ if (inputFocus_ && (flags & SDL_WINDOW_INPUT_FOCUS) == 0)
|
|
|
+ LoseFocus();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ return;
|
|
|
+
|
|
|
if (mouseVisible_ && mouseMode_ == MM_WRAP)
|
|
|
{
|
|
|
IntVector2 mpos;
|
|
|
SDL_GetMouseState(&mpos.x_, &mpos.y_);
|
|
|
|
|
|
- int buffer = 5;
|
|
|
+ const int buffer = 5;
|
|
|
int width = graphics_->GetWidth() - buffer * 2;
|
|
|
int height = graphics_->GetHeight() - buffer * 2;
|
|
|
|
|
|
@@ -206,36 +327,17 @@ void Input::Update()
|
|
|
SDL_FlushEvent(SDL_MOUSEMOTION);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- // Check for activation and inactivation from SDL window flags. Must nullcheck the window pointer because it may have
|
|
|
- // been closed due to input events
|
|
|
- SDL_Window* window = graphics_->GetImpl()->GetWindow();
|
|
|
- unsigned flags = window ? SDL_GetWindowFlags(window) & (SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS) : 0;
|
|
|
- if (window)
|
|
|
- {
|
|
|
-#ifdef REQUIRE_CLICK_TO_FOCUS
|
|
|
- if (!inputFocus_ && (graphics_->GetFullscreen() || mouseVisible_) && flags == (SDL_WINDOW_INPUT_FOCUS |
|
|
|
- SDL_WINDOW_MOUSE_FOCUS))
|
|
|
-#else
|
|
|
- if (!inputFocus_ && (flags & SDL_WINDOW_INPUT_FOCUS))
|
|
|
-#endif
|
|
|
- focusedThisFrame_ = true;
|
|
|
-
|
|
|
- if (focusedThisFrame_)
|
|
|
- GainFocus();
|
|
|
-
|
|
|
- if (inputFocus_ && (flags & SDL_WINDOW_INPUT_FOCUS) == 0)
|
|
|
- LoseFocus();
|
|
|
- }
|
|
|
- else
|
|
|
+ #else
|
|
|
+ if (!window)
|
|
|
return;
|
|
|
+ #endif
|
|
|
|
|
|
// Check for relative mode mouse move
|
|
|
- if (!touchEmulation_ && (graphics_->GetExternalWindow() || (!mouseVisible_ && inputFocus_ && (flags & SDL_WINDOW_MOUSE_FOCUS))))
|
|
|
+ if (!touchEmulation_ && mouseMode_ != MM_RELATIVE && (graphics_->GetExternalWindow() || (!mouseVisible_ && inputFocus_ && (flags & SDL_WINDOW_MOUSE_FOCUS))))
|
|
|
{
|
|
|
IntVector2 mousePosition = GetMousePosition();
|
|
|
mouseMove_ = mousePosition - lastMousePosition_;
|
|
|
-
|
|
|
+ #ifndef EMSCRIPTEN
|
|
|
if (graphics_->GetExternalWindow())
|
|
|
lastMousePosition_ = mousePosition;
|
|
|
else
|
|
|
@@ -248,7 +350,12 @@ void Input::Update()
|
|
|
lastMousePosition_ = center;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+ #else
|
|
|
+ if (mouseMode_ == MM_ABSOLUTE)
|
|
|
+ {
|
|
|
+ lastMousePosition_ = mousePosition;
|
|
|
+ }
|
|
|
+ #endif
|
|
|
// Send mouse move event if necessary
|
|
|
if (mouseMove_ != IntVector2::ZERO)
|
|
|
{
|
|
|
@@ -275,6 +382,17 @@ void Input::Update()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ if (mouseMode_ == MM_RELATIVE)
|
|
|
+ {
|
|
|
+ IntVector2 mousePosition = GetMousePosition();
|
|
|
+ IntVector2 center(graphics_->GetWidth() / 2, graphics_->GetHeight() / 2);
|
|
|
+ if (mousePosition != center)
|
|
|
+ {
|
|
|
+ SetMousePosition(center);
|
|
|
+ lastMousePosition_ = center;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
void Input::SetMouseVisible(bool enable, bool suppressEvent)
|
|
|
@@ -307,17 +425,25 @@ void Input::SetMouseVisible(bool enable, bool suppressEvent)
|
|
|
if (!mouseVisible_ && inputFocus_)
|
|
|
{
|
|
|
SDL_ShowCursor(SDL_FALSE);
|
|
|
+ #ifndef EMSCRIPTEN
|
|
|
// Recenter the mouse cursor manually when hiding it to avoid erratic mouse move for one frame
|
|
|
lastVisibleMousePosition_ = GetMousePosition();
|
|
|
IntVector2 center(graphics_->GetWidth() / 2, graphics_->GetHeight() / 2);
|
|
|
SetMousePosition(center);
|
|
|
lastMousePosition_ = center;
|
|
|
+ #else
|
|
|
+ lastVisibleMousePosition_ = GetMousePosition();
|
|
|
+ lastMousePosition_ = lastVisibleMousePosition_;
|
|
|
+ #endif
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
SDL_ShowCursor(SDL_TRUE);
|
|
|
+ #ifndef EMSCRIPTEN
|
|
|
if (lastVisibleMousePosition_.x_ != MOUSE_POSITION_OFFSCREEN.x_ && lastVisibleMousePosition_.y_ != MOUSE_POSITION_OFFSCREEN.y_)
|
|
|
SetMousePosition(lastVisibleMousePosition_);
|
|
|
+ lastMousePosition_ = lastVisibleMousePosition_;
|
|
|
+ #endif
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -337,6 +463,41 @@ void Input::SetMouseVisible(bool enable, bool suppressEvent)
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
+void Input::ResetMouseVisible()
|
|
|
+{
|
|
|
+ #ifndef EMSCRIPTEN
|
|
|
+ SetMouseVisible(lastMouseVisible_, true);
|
|
|
+ #else
|
|
|
+ SetMouseVisibleEmscripten(lastMouseVisible_);
|
|
|
+ #endif
|
|
|
+}
|
|
|
+
|
|
|
+#ifdef EMSCRIPTEN
|
|
|
+void Input::SetMouseVisibleEmscripten(bool enable)
|
|
|
+{
|
|
|
+ if (enable != mouseVisible_)
|
|
|
+ {
|
|
|
+ mouseVisible_ = enable;
|
|
|
+ SDL_ShowCursor(enable ? SDL_TRUE : SDL_FALSE);
|
|
|
+
|
|
|
+ if (!mouseVisible_)
|
|
|
+ lastVisibleMousePosition_ = GetMousePosition();
|
|
|
+
|
|
|
+ lastMousePosition_ = lastVisibleMousePosition_;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void Input::SetMouseModeEmscripten(MouseMode mode)
|
|
|
+{
|
|
|
+ mouseMode_ = mode;
|
|
|
+ suppressNextMouseMove_ = true;
|
|
|
+
|
|
|
+ VariantMap& eventData = GetEventDataMap();
|
|
|
+ eventData[MouseModeChanged::P_MODE] = mode;
|
|
|
+ SendEvent(E_MOUSEMODECHANGED, eventData);
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
void Input::SetMouseGrabbed(bool grab)
|
|
|
{
|
|
|
mouseGrabbed_ = grab;
|
|
|
@@ -348,47 +509,70 @@ void Input::SetMouseMode(MouseMode mode)
|
|
|
{
|
|
|
MouseMode previousMode = mouseMode_;
|
|
|
mouseMode_ = mode;
|
|
|
+ suppressNextMouseMove_ = true;
|
|
|
+ SDL_Window* window = graphics_->GetImpl()->GetWindow();
|
|
|
// Handle changing away from previous mode
|
|
|
if (previousMode == MM_RELATIVE)
|
|
|
{
|
|
|
/// \todo Use SDL_SetRelativeMouseMode() for MM_RELATIVE mode
|
|
|
ResetMouseVisible();
|
|
|
|
|
|
- // Send updated mouse position:
|
|
|
- using namespace MouseMove;
|
|
|
+ #ifdef EMSCRIPTEN
|
|
|
+ emscriptenInput->ExitPointerLock();
|
|
|
+ #endif
|
|
|
|
|
|
- VariantMap& eventData = GetEventDataMap();
|
|
|
- eventData[P_X] = lastVisibleMousePosition_.x_;
|
|
|
- eventData[P_Y] = lastVisibleMousePosition_.y_;
|
|
|
- eventData[P_DX] = mouseMove_.x_;
|
|
|
- eventData[P_DY] = mouseMove_.y_;
|
|
|
- eventData[P_BUTTONS] = mouseButtonDown_;
|
|
|
- eventData[P_QUALIFIERS] = GetQualifiers();
|
|
|
- SendEvent(E_MOUSEMOVE, eventData);
|
|
|
+ SDL_SetWindowGrab(window, SDL_FALSE);
|
|
|
}
|
|
|
+ #ifndef EMSCRIPTEN
|
|
|
else if (previousMode == MM_WRAP)
|
|
|
{
|
|
|
- SDL_Window* window = graphics_->GetImpl()->GetWindow();
|
|
|
SDL_SetWindowGrab(window, SDL_FALSE);
|
|
|
}
|
|
|
+ #endif
|
|
|
|
|
|
// Handle changing to new mode
|
|
|
if (mode == MM_ABSOLUTE)
|
|
|
+ {
|
|
|
+ #ifndef EMSCRIPTEN
|
|
|
SetMouseGrabbed(false);
|
|
|
+ VariantMap& eventData = GetEventDataMap();
|
|
|
+ eventData[MouseModeChanged::P_MODE] = mode;
|
|
|
+ SendEvent(E_MOUSEMODECHANGED, eventData);
|
|
|
+ #else
|
|
|
+ // Deferred mouse mode change to PointerLock callback
|
|
|
+ #endif
|
|
|
+ }
|
|
|
else
|
|
|
{
|
|
|
SetMouseGrabbed(true);
|
|
|
|
|
|
if (mode == MM_RELATIVE)
|
|
|
{
|
|
|
+ SDL_SetWindowGrab(window, SDL_TRUE);
|
|
|
+ #ifndef EMSCRIPTEN
|
|
|
SetMouseVisible(false, true);
|
|
|
+
|
|
|
+ VariantMap& eventData = GetEventDataMap();
|
|
|
+ eventData[MouseModeChanged::P_MODE] = mode;
|
|
|
+ SendEvent(E_MOUSEMODECHANGED, eventData);
|
|
|
+ #else
|
|
|
+ // Defer mouse mode change to PointerLock callback
|
|
|
+ mouseMode_ = previousMode;
|
|
|
+ emscriptenInput->RequestPointerLock();
|
|
|
+ #endif
|
|
|
+
|
|
|
}
|
|
|
+ #ifndef EMSCRIPTEN
|
|
|
else if (mode == MM_WRAP)
|
|
|
{
|
|
|
/// \todo When SDL 2.0.4 is integrated, use SDL_CaptureMouse() and global mouse functions for MM_WRAP mode.
|
|
|
- SDL_Window* window = graphics_->GetImpl()->GetWindow();
|
|
|
SDL_SetWindowGrab(window, SDL_TRUE);
|
|
|
+
|
|
|
+ VariantMap& eventData = GetEventDataMap();
|
|
|
+ eventData[MouseModeChanged::P_MODE] = mode;
|
|
|
+ SendEvent(E_MOUSEMODECHANGED, eventData);
|
|
|
}
|
|
|
+ #endif
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -961,8 +1145,12 @@ void Input::Initialize()
|
|
|
mouseVisible_ = true;
|
|
|
|
|
|
// Set the initial activation
|
|
|
- focusedThisFrame_ = true;
|
|
|
initialized_ = true;
|
|
|
+#ifndef EMSCRIPTEN
|
|
|
+ focusedThisFrame_ = true;
|
|
|
+#else
|
|
|
+ GainFocus();
|
|
|
+#endif
|
|
|
|
|
|
ResetJoysticks();
|
|
|
ResetState();
|