diff --git a/doc/changelog.dox b/doc/changelog.dox index 2ba29a20c..e62015c2b 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -337,6 +337,10 @@ See also: @relativeref{Platform::GlfwApplication,tickEvent()} to match the interface of @ref Platform::Sdl2Application (see [mosra/magnum#577](https://github.com/mosra/magnum/issues/577) and [mosra/magnum#580](https://github.com/mosra/magnum/pull/580)) +- Multi-touch support in @ref Platform::Sdl2Application through new + @relativeref{Platform::Sdl2Application,PointerEvent} and + @relativeref{Platform::Sdl2Application,PointerMoveEvent} that unify mouse + and touch input events @subsubsection changelog-latest-new-scenegraph SceneGraph library diff --git a/package/ci/appveyor-rt.bat b/package/ci/appveyor-rt.bat index dbacf65ee..bf81363e4 100644 --- a/package/ci/appveyor-rt.bat +++ b/package/ci/appveyor-rt.bat @@ -7,10 +7,11 @@ rem hell breaks loose. Thus also not passing CORRADE_RC_EXECUTABLE anywhere rem below to ensure this doesn't regress. set PATH=%APPVEYOR_BUILD_FOLDER%\deps-native\bin;%PATH% -rem Build SDL -appveyor DownloadFile https://www.libsdl.org/release/SDL2-2.0.4.zip || exit /b -7z x SDL2-2.0.4.zip || exit /b -ren SDL2-2.0.4 SDL || exit /b +rem Build SDL, 2.0.6 is the oldest release that has SDL_HINT_TOUCH_MOUSE_EVENTS +rem https://github.com/libsdl-org/SDL/commit/56cab6d45280fbb4b645083eceeaa8f474c0aac3 +appveyor DownloadFile https://www.libsdl.org/release/SDL2-2.0.6.zip || exit /b +7z x SDL2-2.0.6.zip || exit /b +ren SDL2-2.0.6 SDL || exit /b cd SDL/VisualC-WinRT/UWP_VS2015 || exit/b msbuild /p:Configuration=Release || exit /b cd ..\..\.. diff --git a/src/Magnum/Platform/Sdl2Application.cpp b/src/Magnum/Platform/Sdl2Application.cpp index 761a98f74..626c18376 100644 --- a/src/Magnum/Platform/Sdl2Application.cpp +++ b/src/Magnum/Platform/Sdl2Application.cpp @@ -144,6 +144,39 @@ Sdl2Application::Sdl2Application(const Arguments& arguments, NoCreateT): .parse(arguments.argc, arguments.argv); #endif + #ifndef CORRADE_TARGET_EMSCRIPTEN + /* Disable translation of touch events to mouse events and vice versa as + that's a very poor way of freeing users from having to implement + separate event handling for mouse and touch (and, in SDL3, pen). Instead + the Sdl2Application is providing a PointerEvent abstracting all of + those, so no event translation needs to take place anymore. + + Though, just for historical records, what is quite funny / strange about + the SDL's translation, is that when the touch goes out of the window, + translated mouse events get clamped to the window size and thus also not + even being reported if the clamped value doesn't change. On the other + hand, with a regular mouse event, if a drag goes out of the window, it's + still reported correctly, with the coordinates being either larger than + the window size or negative. No idea why the SDL touch->mouse emulation + doesn't do this -- maybe because having a touchscreen device with a + window manager is still relatively rare so nobody reported that? Heh. + + These enums are not exposed in the minimal Emscripten SDL implementation + which in turn means touch support there isn't implemented, because I + don't want to filter duplicate events by hand. Use EmscriptenApplication + instead, please. */ + /* Added in 2.0.6, before it was apparently impossible to turn off the + event translation altogether. I could also make the touch available only + on 2.0.6+, but 2.0.6 is from 2017 and I don't think it makes sense to + bother with support for older versions. Ubuntu 18.04 has 2.0.8. + https://github.com/libsdl-org/SDL/commit/56cab6d45280fbb4b645083eceeaa8f474c0aac3 */ + SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); + /* Added in 2.0.10, before mouse events don't generate touch events + https://github.com/libsdl-org/SDL/commit/e41576188d17fd09c95777d665f6c4532574f8ac */ + #ifdef SDL_HINT_MOUSE_TOUCH_EVENTS + SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0"); + #endif + #endif /* Available since 2.0.4, disables interception of SIGINT and SIGTERM so it's possible to Ctrl-C the application even if exitEvent() doesn't set event.setAccepted(). */ @@ -1028,10 +1061,28 @@ bool Sdl2Application::mainLoopIteration() { if((event.type == SDL_MOUSEBUTTONDOWN && (buttons & ~SDL_BUTTON(event.button.button))) || (event.type == SDL_MOUSEBUTTONUP && buttons)) { Pointers pointers = buttonsToPointers(buttons); - PointerMoveEvent e{event, pointer, pointers, position, {}}; + PointerMoveEvent e{event, PointerEventSource::Mouse, pointer, pointers, true, + #ifdef CORRADE_TARGET_EMSCRIPTEN + 0, + /* Since 2.0.22, added w/ SDL_HINT_MOUSE_TOUCH_EVENTS */ + #elif defined(SDL_MOUSE_TOUCHID) + SDL_MOUSE_TOUCHID, + #else + -1, + #endif + position, {}}; pointerMoveEvent(e); } else { - PointerEvent e{event, pointer, position + PointerEvent e{event, PointerEventSource::Mouse, pointer, true, + #ifdef CORRADE_TARGET_EMSCRIPTEN + 0, + /* Since 2.0.22, added w/ SDL_HINT_MOUSE_TOUCH_EVENTS */ + #elif defined(SDL_MOUSE_TOUCHID) + SDL_MOUSE_TOUCHID, + #else + -1, + #endif + position #ifndef CORRADE_TARGET_EMSCRIPTEN , event.button.clicks #endif @@ -1047,12 +1098,95 @@ bool Sdl2Application::mainLoopIteration() { } break; case SDL_MOUSEMOTION: { - PointerMoveEvent e{event, {}, buttonsToPointers(event.motion.state), + PointerMoveEvent e{event, PointerEventSource::Mouse, {}, buttonsToPointers(event.motion.state), true, + #ifdef CORRADE_TARGET_EMSCRIPTEN + 0, + /* Since 2.0.22, added w/ SDL_HINT_MOUSE_TOUCH_EVENTS */ + #elif defined(SDL_MOUSE_TOUCHID) + SDL_MOUSE_TOUCHID, + #else + -1, + #endif {Float(event.motion.x), Float(event.motion.y)}, {Float(event.motion.xrel), Float(event.motion.yrel)}}; pointerMoveEvent(e); } break; + #ifndef CORRADE_TARGET_EMSCRIPTEN + case SDL_FINGERDOWN: + case SDL_FINGERUP: { + /* Scale the event from useless [0, 1] to the actual window + size, not sure why is it so weird. Also let's hope the + SDL_GetWindowSize() call isn't too demanding, I don't want + to be caching this value, it's bad enough to have to track + that on Emscripten. */ + Vector2i windowSize{NoInit}; + SDL_GetWindowSize(_window, &windowSize.x(), &windowSize.y()); + + /* Update primary finger info. If there's no primary finger yet + and this is the first finger pressed, it becomes the primary + finger. If the primary finger is lifted, no other finger + becomes primary until all others are lifted as well. This + was empirically verified by looking at behavior of a mouse + cursor on a multi-touch screen under X11, it's possible that + other systems do it differently. Also, right now there's an + assumption that there is just one touch device, fingers from + different touch devices would steal the primary bit from + each other on every press. */ + bool primary; + if(_primaryFingerId == ~Long{} && event.type == SDL_FINGERDOWN && SDL_GetNumTouchFingers(event.tfinger.touchId) == 1) { + primary = true; + _primaryFingerId = event.tfinger.fingerId; + /* Otherwise, if this is the primary finger, mark it as such */ + } else if(_primaryFingerId == event.tfinger.fingerId) { + primary = true; + /* ... but if it's a release, it's no longer primary */ + if(event.type == SDL_FINGERUP) + _primaryFingerId = ~Long{}; + /* Otherwise this is not the primary finger */ + } else primary = false; + + /* Make it so that value of 0 is reported as 0 and 1 is + reported as the rightmost / bottommost pixel, i.e. 799 / 599 + for 800x600. This matches with what SDL itself does for the + touch event translation. */ + const Vector2 scale = Vector2{windowSize - Vector2i{1}}; + PointerEvent e{event, PointerEventSource::Touch, + Pointer::Finger, primary, event.tfinger.fingerId, + Vector2{event.tfinger.x, event.tfinger.y}*scale, 1}; + event.type == SDL_FINGERDOWN ? + pointerPressEvent(e) : pointerReleaseEvent(e); + } break; + + case SDL_FINGERMOTION: { + /* Scale the event from useless [0, 1] to the actual window + size, not sure why is it so weird. Also let's hope the + SDL_GetWindowSize() call isn't too demanding, I don't want + to be caching this value, it's bad enough to have to track + that on Emscripten. */ + Vector2i windowSize{NoInit}; + SDL_GetWindowSize(_window, &windowSize.x(), &windowSize.y()); + + /* In this case, it's a primary finger only if it was + registered as such during the last press. If the primary + finger was lifted, no other finger will step into its place + until all others are lifted as well. */ + const bool primary = _primaryFingerId == event.tfinger.fingerId; + + /* Make it so that value of 0 is reported as 0 and 1 is + reported as the rightmost / bottommost pixel, i.e. 799 / 599 + for 800x600. This matches with what SDL itself does for the + touch event translation. */ + const Vector2 scale = Vector2{windowSize - Vector2i{1}}; + PointerMoveEvent e{event, PointerEventSource::Touch, {}, + Pointer::Finger, primary, event.tfinger.fingerId, + Vector2{event.tfinger.x, event.tfinger.y}*scale, + Vector2{event.tfinger.dx, event.tfinger.dy}*scale}; + pointerMoveEvent(e); + break; + } + #endif + case SDL_MULTIGESTURE: { MultiGestureEvent e{event, {event.mgesture.x, event.mgesture.y}, event.mgesture.dTheta, event.mgesture.dDist, event.mgesture.numFingers}; multiGestureEvent(e); @@ -1284,6 +1418,9 @@ CORRADE_IGNORE_DEPRECATED_PUSH Sdl2Application::MouseEvent::Button pointerToButton(const Sdl2Application::Pointer pointer) { switch(pointer) { case Sdl2Application::Pointer::MouseLeft: + #ifndef CORRADE_TARGET_EMSCRIPTEN + case Sdl2Application::Pointer::Finger: + #endif return Sdl2Application::MouseEvent::Button::Left; case Sdl2Application::Pointer::MouseMiddle: return Sdl2Application::MouseEvent::Button::Middle; @@ -1304,6 +1441,9 @@ CORRADE_IGNORE_DEPRECATED_POP void Sdl2Application::pointerPressEvent(PointerEvent& event) { #ifdef MAGNUM_BUILD_DEPRECATED + if(!event.isPrimary()) + return; + CORRADE_IGNORE_DEPRECATED_PUSH MouseEvent mouseEvent{event.event(), pointerToButton(event.pointer()), Vector2i{Math::round(event.position())} #ifndef CORRADE_TARGET_EMSCRIPTEN @@ -1325,6 +1465,9 @@ CORRADE_IGNORE_DEPRECATED_POP void Sdl2Application::pointerReleaseEvent(PointerEvent& event) { #ifdef MAGNUM_BUILD_DEPRECATED + if(!event.isPrimary()) + return; + CORRADE_IGNORE_DEPRECATED_PUSH MouseEvent mouseEvent{event.event(), pointerToButton(event.pointer()), Vector2i{Math::round(event.position())} #ifndef CORRADE_TARGET_EMSCRIPTEN @@ -1346,6 +1489,9 @@ CORRADE_IGNORE_DEPRECATED_POP void Sdl2Application::pointerMoveEvent(PointerMoveEvent& event) { #ifdef MAGNUM_BUILD_DEPRECATED + if(!event.isPrimary()) + return; + const Vector2i roundedPosition{Math::round(event.position())}; CORRADE_IGNORE_DEPRECATED_PUSH @@ -1366,7 +1512,11 @@ void Sdl2Application::pointerMoveEvent(PointerMoveEvent& event) { mousePressEvent(mouseEvent) : mouseReleaseEvent(mouseEvent); } else { MouseMoveEvent::Buttons buttons; - if(event.pointers() & Pointer::MouseLeft) + if(event.pointers() & (Pointer::MouseLeft + #ifndef CORRADE_TARGET_EMSCRIPTEN + |Pointer::Finger + #endif + )) buttons |= MouseMoveEvent::Button::Left; if(event.pointers() & Pointer::MouseMiddle) buttons |= MouseMoveEvent::Button::Middle; diff --git a/src/Magnum/Platform/Sdl2Application.h b/src/Magnum/Platform/Sdl2Application.h index c89e89ac6..0424b14be 100644 --- a/src/Magnum/Platform/Sdl2Application.h +++ b/src/Magnum/Platform/Sdl2Application.h @@ -285,6 +285,37 @@ If no other application header is included, this class is also aliased to @cpp Platform::Application @ce and the macro is aliased to @cpp MAGNUM_APPLICATION_MAIN() @ce to simplify porting. +@section Platform-Sdl2Application-touch Touch input + +The application recognizes touch input and reports it as @ref Pointer::Finger +and @ref PointerEventSource::Touch. Because both mouse and touch events are +exposed through a unified @ref PointerEvent / @ref PointerMoveEvent interface, +there's no need for compatibility mouse events to be synthesized from touch +events and vice versa, and thus given behavior is disabled in SDL. Pen input is +still reported as a mouse because SDL has dedicated support for pen stylus only +since SDL 3. + +In case of a multi-touch scenario, @ref PointerEvent::isPrimary() / +@ref PointerMoveEvent::isPrimary() can be used to distinguish the primary touch +from secondary. For example, if an application doesn't need to recognize +gestures like pinch to zoom or rotate, it can ignore all non-primary pointer +events. @ref PointerEventSource::Mouse events are always marked as primary, +for touch input the first pressed finger is marked as primary and all following +pressed fingers are non-primary. Note that there can be up to one primary +pointer for each pointer event source, e.g. a finger and a mouse press may both +be marked as primary. On the other hand, in a multi-touch scenario, if the +first (and thus primary) finger is lifted, no other finger becomes primary +until all others are lifted as well. + +If gesture recognition is desirable, @ref PointerEvent::id() / +@ref PointerMoveEvent::id() contains a pointer ID that's unique among all +pointer event sources, which can be used to track movements of secondary, +tertiary and further touch points. The ID allocation is platform-specific and +you can't rely on it to be contiguous or in any bounded range --- for example, +each new touch may generate a new ID that's only used until given finger is +lifted, and then never again. For @ref PointerEventSource::Mouse the ID is a +constant, as there's always just a single mouse cursor. + @section Platform-Sdl2Application-platform-specific Platform-specific behavior @subsection Platform-Sdl2Application-platform-specific-power Power management @@ -514,6 +545,7 @@ class Sdl2Application { /* The damn thing cannot handle forward enum declarations */ #ifndef DOXYGEN_GENERATING_OUTPUT + enum class PointerEventSource: UnsignedByte; enum class Pointer: UnsignedByte; #endif @@ -1103,15 +1135,16 @@ class Sdl2Application { * @brief Pointer press event * @m_since_latest * - * Called when a mouse is pressed. Note that if at least one mouse - * button is already pressed and another button gets pressed in - * addition, @ref pointerMoveEvent() with the new combination is - * called, not this function. + * Called when either a mouse or a finger is pressed. Note that if at + * least one mouse button is already pressed and another button gets + * pressed in addition, @ref pointerMoveEvent() with the new + * combination is called, not this function. * - * On builds with @ref MAGNUM_BUILD_DEPRECATED enabled, default - * implementation delegates to @ref mousePressEvent(). On builds with - * deprecated functionality disabled, default implementation does - * nothing. + * On builds with @ref MAGNUM_BUILD_DEPRECATED enabled, if the pointer + * is primary, default implementation delegates to + * @ref mousePressEvent(), interpreting @ref Pointer::Finger as + * @ref MouseEvent::Button::Left. On builds with deprecated + * functionality disabled, default implementation does nothing. */ virtual void pointerPressEvent(PointerEvent& event); @@ -1131,14 +1164,16 @@ class Sdl2Application { * @brief Pointer release event * @m_since_latest * - * Called when a mouse is released. Note that if multiple mouse buttons - * are pressed and one of these is released, @ref pointerMoveEvent() - * with the new combination is called, not this function. + * Called when either a mouse or a finger is released. Note that if + * multiple mouse buttons are pressed and one of these is released, + * @ref pointerMoveEvent() with the new combination is called, not this + * function. * - * On builds with @ref MAGNUM_BUILD_DEPRECATED enabled, default - * implementation delegates to @ref mouseReleaseEvent(). On builds with - * deprecated functionality disabled, default implementation does - * nothing. + * On builds with @ref MAGNUM_BUILD_DEPRECATED enabled, if the pointer + * is primary, default implementation delegates to + * @ref mouseReleaseEvent(), interpreting @ref Pointer::Finger as + * @ref MouseEvent::Button::Left. On builds with deprecated + * functionality disabled, default implementation does nothing. */ virtual void pointerReleaseEvent(PointerEvent& event); @@ -1162,13 +1197,14 @@ class Sdl2Application { * changes its properties. Gets called also if the set of pressed mouse * buttons changes. * - * On builds with @ref MAGNUM_BUILD_DEPRECATED enabled, default - * implementation delegates to @ref mouseMoveEvent(), or if - * @ref PointerMoveEvent::pointer() is not + * On builds with @ref MAGNUM_BUILD_DEPRECATED enabled, if the pointer + * is primary, default implementation delegates to + * @ref mouseMoveEvent(), or if @ref PointerMoveEvent::pointer() is not * @relativeref{Corrade,Containers::NullOpt}, to either - * @ref mousePressEvent() or @ref mouseReleaseEvent(). On builds with - * deprecated functionality disabled, default implementation does - * nothing. + * @ref mousePressEvent() or @ref mouseReleaseEvent(). + * @ref Pointer::Finger is interpreted as @ref MouseEvent::Button::Left. + * On builds with deprecated functionality disabled, default + * implementation does nothing. */ virtual void pointerMoveEvent(PointerMoveEvent& event); @@ -1368,6 +1404,7 @@ class Sdl2Application { #ifndef CORRADE_TARGET_EMSCRIPTEN SDL_Window* _window{}; + Long _primaryFingerId = ~Long{}; /* Not using Nanoseconds as that would require including Time.h */ UnsignedInt _minimalLoopPeriodMilliseconds{}; #else @@ -1390,6 +1427,36 @@ class Sdl2Application { int _exitCode = 0; }; +/** +@brief Pointer event source +@m_since_latest + +@see @ref PointerEvent::source(), @ref PointerMoveEvent::source() +*/ +enum class Sdl2Application::PointerEventSource: UnsignedByte { + /** + * The event is coming from a mouse. Corresponds to the + * `SDL_MOUSEBUTTONDOWN`, `SDL_MOUSEBUTTONUP` and `SDL_MOUSEMOTION` events. + * @see @ref Pointer::MouseLeft, @ref Pointer::MouseMiddle, + * @ref Pointer::MouseRight, @ref Pointer::MouseButton4, + * @ref Pointer::MouseButton5 + */ + Mouse, + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /** + * The event is coming from a touch contact. Corresponds to the + * `SDL_FINGERDOWN`, `SDL_FINGERUP` and `SDL_FINGERMOTION` events. + * @note Not available on @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten" as + * the minimal SDL2 implementation there doesn't expose a way to + * disable touch to mouse event translation, which would lead to + * duplicate events being emitted. + * @see @ref Pointer::Finger + */ + Touch + #endif +}; + /** @brief Pointer type @m_since_latest @@ -1401,32 +1468,51 @@ enum class Sdl2Application::Pointer: UnsignedByte { /** * Left mouse button. Corresponds to `SDL_BUTTON_LEFT` / * `SDL_BUTTON_LMASK`. + * @see @ref PointerEventSource::Mouse */ MouseLeft = 1 << 0, /** * Middle mouse button. Corresponds to `SDL_BUTTON_MIDDLE` / * `SDL_BUTTON_MMASK`. + * @see @ref PointerEventSource::Mouse */ MouseMiddle = 1 << 1, /** * Right mouse button. Corresponds to `SDL_BUTTON_RIGHT` / * `SDL_BUTTON_RMASK`. + * @see @ref PointerEventSource::Mouse */ MouseRight = 1 << 2, /** * Fourth mouse button, such as wheel left. Corresponds to `SDL_BUTTON_X1` * / `SDL_BUTTON_X1MASK`. + * @see @ref PointerEventSource::Mouse */ MouseButton4 = 1 << 3, /** * Fifth mouse button, such as wheel right. Corresponds to `SDL_BUTTON_X2` * / `SDL_BUTTON_X2MASK`. + * @see @ref PointerEventSource::Mouse */ MouseButton5 = 1 << 4, + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /** + * Finger + * @note Not available on @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten" as + * the minimal SDL2 implementation there doesn't expose a way to + * disable touch to mouse event translation, which would lead to + * duplicate events being emitted. + * @see @ref PointerEventSource::Touch + */ + Finger = 1 << 5, + #endif + + /* Pen support is since SDL3 only */ }; CORRADE_ENUMSET_OPERATORS(Sdl2Application::Pointers) @@ -2382,9 +2468,10 @@ class Sdl2Application::InputEvent { * @brief Underlying SDL event * * Of type `SDL_KEYDOWN` / `SDL_KEYUP` for @ref KeyEvent, - * `SDL_MOUSEBUTTONDOWN` / `SDL_MOUSEBUTTONUP` for @ref PointerEvent, - * `SDL_MOUSEMOTION` for @ref PointerMoveEvent and `SDL_MOUSEWHEEL` for - * @ref ScrollEvent. + * `SDL_MOUSEBUTTONDOWN` / `SDL_MOUSEBUTTONUP` or `SDL_FINGERDOWN` / + * `SDL_FINGERUP` for @ref PointerEvent, `SDL_MOUSEMOTION` or + * `SDL_FINGERMOTION` for @ref PointerMoveEvent and `SDL_MOUSEWHEEL` + * for @ref ScrollEvent. * @see @ref Sdl2Application::anyEvent() */ const SDL_Event& event() const { return _event; } @@ -2746,13 +2833,39 @@ class Sdl2Application::PointerEvent: public InputEvent { /** @brief Moving is not allowed */ PointerEvent& operator=(PointerEvent&&) = delete; + /** @brief Pointer event source */ + PointerEventSource source() const { return _source; } + /** @brief Pointer type that was pressed or released */ Pointer pointer() const { return _pointer; } + /** + * @brief Whether the pointer is primary + * + * Useful to distinguish among multiple pointers in a multi-touch + * scenario. See @ref Platform-Sdl2Application-touch for more + * information. + */ + bool isPrimary() const { return _primary; } + + /** + * @brief Pointer ID + * + * Useful to distinguish among multiple pointers in a multi-touch + * scenario. See @ref Platform-Sdl2Application-touch for more + * information. + */ + Long id() const { return _id; } + /** * @brief Position * - * For mouse input the position is always reported in whole pixels. + * For mouse input the position is always reported in whole pixels. For + * @ref Pointer::Finger the events may be reported with a subpixel + * precision, use @ref Math::round() to snap them to the nearest pixel. + * Note that, unlike the `SDL_TouchFingerEvent`, which is normalized in + * the @f$ [0, 1] @f$ range, the position for touch events is in the + * same coordinate system as mouse events. */ Vector2 position() const { return _position; } @@ -2760,6 +2873,7 @@ class Sdl2Application::PointerEvent: public InputEvent { /** * @brief Click count * + * For @ref Pointer::Finger is always @cpp 1 @ce. * @note Not available on @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". */ Int clickCount() const { return _clickCount; } @@ -2775,18 +2889,21 @@ class Sdl2Application::PointerEvent: public InputEvent { private: friend Sdl2Application; - explicit PointerEvent(const SDL_Event& event, Pointer pointer, const Vector2& position + explicit PointerEvent(const SDL_Event& event, PointerEventSource source, Pointer pointer, bool primary, Long id, const Vector2& position #ifndef CORRADE_TARGET_EMSCRIPTEN , Int clickCount #endif - ): InputEvent{event}, _pointer(pointer), _position{position} + ): InputEvent{event}, _source{source}, _pointer{pointer}, _primary{primary}, _id{id}, _position{position} #ifndef CORRADE_TARGET_EMSCRIPTEN , _clickCount{clickCount} #endif {} + const PointerEventSource _source; const Pointer _pointer; Containers::Optional _modifiers; + const bool _primary; + const Long _id; const Vector2 _position; #ifndef CORRADE_TARGET_EMSCRIPTEN const Int _clickCount; @@ -2886,6 +3003,15 @@ class Sdl2Application::PointerMoveEvent: public InputEvent { /** @brief Moving is not allowed */ PointerMoveEvent& operator=(PointerMoveEvent&&) = delete; + /** + * @brief Pointer event source + * + * Can be used to distinguish which source the event is coming from in + * case it's a movement with both @ref pointer() and @ref pointers() + * being empty. + */ + PointerEventSource source() const { return _source; } + /** * @brief Pointer type that was added or removed from the set of pressed pointers * @@ -2894,6 +3020,7 @@ class Sdl2Application::PointerMoveEvent: public InputEvent { * pressed buttons was released. If non-empty and @ref pointers() don't * contain given @ref Pointer value, the button was released, if they * contain given value, the button was pressed. + * @see @ref source() */ Containers::Optional pointer() const { return _pointer; } @@ -2906,17 +3033,45 @@ class Sdl2Application::PointerMoveEvent: public InputEvent { */ Pointers pointers() const { return _pointers; } + /** + * @brief Whether the pointer is primary + * + * Useful to distinguish among multiple pointers in a multi-touch + * scenario. See @ref Platform-Sdl2Application-touch for more + * information. + */ + bool isPrimary() const { return _primary; } + + /** + * @brief Pointer ID + * + * Useful to distinguish among multiple pointers in a multi-touch + * scenario. See @ref Platform-Sdl2Application-touch for more + * information. + */ + Long id() const { return _id; } + /** * @brief Position * - * For mouse input the position is always reported in whole pixels. + * For mouse input the position is always reported in whole pixels. For + * @ref Pointer::Finger the events may be reported with a subpixel + * precision. Use @ref Math::round() to snap them to the nearest pixel. + * Note that, unlike the `SDL_TouchFingerEvent`, which is normalized in + * the @f$ [0, 1] @f$ range, the position for touch events is in the + * same coordinate system as mouse events. */ Vector2 position() const { return _position; } /** * @brief Position relative to the previous touch event * - * For mouse input the position is always reported in whole pixels. + * For mouse input the position is always reported in whole pixels. For + * @ref Pointer::Finger the events may be reported with a subpixel + * precision, meaning that the relative position might be less than a + * pixel. Note that, unlike the `SDL_TouchFingerEvent`, which is + * normalized in the @f$ [0, 1] @f$ range, the position for touch + * events is in the same coordinate system as mouse events. */ Vector2 relativePosition() const { return _relativePosition; } @@ -2930,12 +3085,15 @@ class Sdl2Application::PointerMoveEvent: public InputEvent { private: friend Sdl2Application; - explicit PointerMoveEvent(const SDL_Event& event, Containers::Optional pointer, Pointers pointers, const Vector2& position, const Vector2& relativePosition): InputEvent{event}, _pointer{pointer}, _pointers{pointers}, _position{position}, _relativePosition{relativePosition} {} + explicit PointerMoveEvent(const SDL_Event& event, PointerEventSource source, Containers::Optional pointer, Pointers pointers, bool primary, Long id, const Vector2& position, const Vector2& relativePosition): InputEvent{event}, _source{source}, _pointer{pointer}, _pointers{pointers}, _primary{primary}, _id{id}, _position{position}, _relativePosition{relativePosition} {} + const PointerEventSource _source; const Containers::Optional _pointer; const Pointers _pointers; - const Vector2 _position, _relativePosition; + const bool _primary; Containers::Optional _modifiers; + const Long _id; + const Vector2 _position, _relativePosition; }; #ifdef MAGNUM_BUILD_DEPRECATED diff --git a/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp b/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp index 1dccaf7ae..ed7ecf5be 100644 --- a/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp +++ b/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp @@ -87,6 +87,9 @@ static Debug& operator<<(Debug& debug, Application::Pointer value) { _c(MouseRight) _c(MouseButton4) _c(MouseButton5) + #ifndef CORRADE_TARGET_EMSCRIPTEN + _c(Finger) + #endif #undef _c } @@ -115,6 +118,21 @@ CORRADE_IGNORE_DEPRECATED_POP namespace Test { namespace { +static Debug& operator<<(Debug& debug, Application::PointerEventSource value) { + debug << "PointerEventSource" << Debug::nospace; + + switch(value) { + #define _c(value) case Application::PointerEventSource::value: return debug << "::" #value; + _c(Mouse) + #ifndef CORRADE_TARGET_EMSCRIPTEN + _c(Touch) + #endif + #undef _c + } + + return debug << "(" << Debug::nospace << UnsignedInt(value) << Debug::nospace << ")"; +} + Debug& operator<<(Debug& debug, Application::InputEvent::Modifiers value) { return Containers::enumSetDebugOutput(debug, value, "Modifiers{}", { Application::InputEvent::Modifier::Shift, @@ -134,6 +152,9 @@ Debug& operator<<(Debug& debug, Application::Pointers value) { Application::Pointer::MouseRight, Application::Pointer::MouseButton4, Application::Pointer::MouseButton5, + #ifndef CORRADE_TARGET_EMSCRIPTEN + Application::Pointer::Finger, + #endif }); } @@ -322,17 +343,17 @@ struct Sdl2ApplicationTest: Platform::Application { /* Set to 0 to test the deprecated mouse events instead */ #if 1 void pointerPressEvent(PointerEvent& event) override { - Debug{} << "pointer press:" << event.pointer() << event.modifiers() << Debug::packed << event.position(); + Debug{} << "pointer press:" << event.source() << event.pointer() << (event.isPrimary() ? "primary" : "secondary") << event.id() << event.modifiers() << Debug::packed << event.position(); _gestureDistance = {}; _gestureRotation = {}; } void pointerReleaseEvent(PointerEvent& event) override { - Debug{} << "pointer release:" << event.pointer() << event.modifiers() << Debug::packed << event.position(); + Debug{} << "pointer release:" << event.source() << event.pointer() << (event.isPrimary() ? "primary" : "secondary") << event.id() << event.modifiers() << Debug::packed << event.position(); _gestureDistance = {}; _gestureRotation = {}; } void pointerMoveEvent(PointerMoveEvent& event) override { - Debug{} << "pointer move:" << event.pointer() << event.pointers() << event.modifiers() << Debug::packed << event.position() << Debug::packed << event.relativePosition(); + Debug{} << "pointer move:" << event.source() << event.pointer() << event.pointers() << (event.isPrimary() ? "primary" : "secondary") << event.id() << event.modifiers() << Debug::packed << event.position() << Debug::packed << event.relativePosition(); } void scrollEvent(ScrollEvent& event) override { Debug{} << "scroll:" << event.modifiers() << Debug::packed << event.offset() << Debug::packed << event.position();