From a76b7646f08b7d9c1e9e419c8490b646a999b102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 18 Oct 2024 22:45:24 +0200 Subject: [PATCH] Platform: handle (multi-)touch events in Sdl2Application. This makes 2.0.6 as the oldest supported because in older versions it's not possible to disable touch to mouse event translation, and it'd be too annoying to have it special-cased there. The version bump should be fine as Ubuntu 18.04 has 2.0.8. --- doc/changelog.dox | 4 + package/ci/appveyor-rt.bat | 9 +- src/Magnum/Platform/Sdl2Application.cpp | 158 ++++++++++++- src/Magnum/Platform/Sdl2Application.h | 220 +++++++++++++++--- .../Platform/Test/Sdl2ApplicationTest.cpp | 27 ++- 5 files changed, 376 insertions(+), 42 deletions(-) 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();