From fc6c76726d2782076771b127680879dc6643c982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 12 Oct 2024 18:24:15 +0200 Subject: [PATCH] Platform: replace mouse events with pointer events in Sdl2Application. Pointer events are an unified abstraction over mouse, touch, pen and potential other yet-to-be-invented pointer-like input methods. Their goal is to expose all such input methods under a single interface so the application side doesn't need to explicitly make sure that it's touch-aware or pen-aware. This abstraction is already present in HTML5, in Qt6 and in WINAPI as well, and is also what I adopted for the new UI library because it *just makes sense*. Unfortunately not even SDL3 took the opportunity to introduce that and instead added a *third* separate event type for pen input in SDL3. At first I thought that I wouldn't introduce any extra abstractions in the Application classes (because that's what they are designed to be, as lightweight as possible), but midway through introducing TouchEvent classes and fighting SDL's touch->mouse and mouse->touch compatibility translation (yes, it's both ways, depending on the platform) I realized that a much simpler solution that doesn't require any event translation or the users duplicating their event handling logic for several possible input types is to introduce a single new event type that covers all. Which is what this commit does -- it doesn't introduce anything touch-related so far, just creates a new PointerEvent and PointerMoveEvent class and corresponding virtual functions. Additionally, I took this as an opportunity to make the position floating-point, since that's what SDL3 does now as well, and GLFW did so since ever. Plus, the Pointer and Pointers enums are directly on the Sdl2Application class, to allow me to *finally* introduce pointer state queries. Which weren't possible until now, because there were mutually incompatible MouseEvent::Button and MouseMoveEvent::Button enums and putting them on the base class would mean one would have to be translated and the other not. With Pointer it's translated always, because there isn't any similar enumeration in SDL that would cover mouse, touch and pen at the same time. --- doc/changelog.dox | 9 + doc/snippets/SceneGraph-gl.cpp | 6 +- src/Magnum/Platform/Sdl2Application.cpp | 212 +++++++++++- src/Magnum/Platform/Sdl2Application.h | 325 ++++++++++++++++-- .../Platform/Test/Sdl2ApplicationTest.cpp | 62 +++- 5 files changed, 568 insertions(+), 46 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 69464350f..236dc9aeb 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -1386,6 +1386,15 @@ See also: - @cpp Platform::Sdl2Application::setMinimalLoopPeriod(UnsignedInt) @ce taking an untyped millisecond value is deprecated in favor of @relativeref{Platform::Sdl2Application,setMinimalLoopPeriod(Nanoseconds)} +- @cpp Platform::Sdl2Application::mousePressEvent() @ce, + @cpp mouseReleaseEvent() @ce, @cpp mouseMoveEvent() @ce, + @cpp MouseEvent @ce and @cpp MouseMoveEvent @ce are deprecated in favor of + new @ref Platform::Sdl2Application::pointerPressEvent(), + @relativeref{Platform::Sdl2Application,pointerReleaseEvent()}, + @relativeref{Platform::Sdl2Application,pointerMoveEvent()}, + @relativeref{Platform::Sdl2Application,PointerEvent} and + @relativeref{Platform::Sdl2Application,PointerMoveEvent} APIs that provide + a better abstraction over general pointer input, not just a mouse alone - @cpp Shaders::DistanceFieldVector @ce, @cpp Shaders::Flat @ce, @cpp Shaders::Generic @ce, @cpp Shaders::MeshVisualizer2D @ce, @cpp Shaders::MeshVisualizer3D @ce, @cpp Shaders::Phong @ce, diff --git a/doc/snippets/SceneGraph-gl.cpp b/doc/snippets/SceneGraph-gl.cpp index 0f048965c..58a7dd243 100644 --- a/doc/snippets/SceneGraph-gl.cpp +++ b/doc/snippets/SceneGraph-gl.cpp @@ -51,7 +51,7 @@ struct MyApplication: Platform::Application { explicit MyApplication(const Arguments& arguments); void drawEvent() override; - void mousePressEvent(MouseEvent& event) override; + void pointerPressEvent(PointerEvent& event) override; SceneGraph::AnimableGroup3D animables; Timeline timeline; @@ -75,9 +75,9 @@ void MyApplication::drawEvent() { } /* [Animable-usage-timeline] */ -void MyApplication::mousePressEvent(MouseEvent& event) { +void MyApplication::pointerPressEvent(PointerEvent& event) { /* [Camera-projectionSize] */ -Vector2 position = (Vector2{event.position()}/Vector2{GL::defaultFramebuffer.viewport().size()} +Vector2 position = (event.position()/Vector2{framebufferSize()} - Vector2{0.5f})*Vector2::yScale(-1.0f)*camera.projectionSize(); /* [Camera-projectionSize] */ diff --git a/src/Magnum/Platform/Sdl2Application.cpp b/src/Magnum/Platform/Sdl2Application.cpp index 7e2ddb234..cd1c4eace 100644 --- a/src/Magnum/Platform/Sdl2Application.cpp +++ b/src/Magnum/Platform/Sdl2Application.cpp @@ -893,6 +893,42 @@ void Sdl2Application::exit(const int exitCode) { _exitCode = exitCode; } +namespace { + +Sdl2Application::Pointer buttonToPointer(const Uint8 button) { + switch(button) { + case SDL_BUTTON_LEFT: + return Sdl2Application::Pointer::MouseLeft; + case SDL_BUTTON_MIDDLE: + return Sdl2Application::Pointer::MouseMiddle; + case SDL_BUTTON_RIGHT: + return Sdl2Application::Pointer::MouseRight; + case SDL_BUTTON_X1: + return Sdl2Application::Pointer::MouseButton4; + case SDL_BUTTON_X2: + return Sdl2Application::Pointer::MouseButton5; + } + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); +} + +Sdl2Application::Pointers buttonsToPointers(const Uint32 buttons) { + Sdl2Application::Pointers pointers; + if(buttons & SDL_BUTTON_LMASK) + pointers |= Sdl2Application::Pointer::MouseLeft; + if(buttons & SDL_BUTTON_MMASK) + pointers |= Sdl2Application::Pointer::MouseMiddle; + if(buttons & SDL_BUTTON_RMASK) + pointers |= Sdl2Application::Pointer::MouseRight; + if(buttons & SDL_BUTTON_X1MASK) + pointers |= Sdl2Application::Pointer::MouseButton4; + if(buttons & SDL_BUTTON_X2MASK) + pointers |= Sdl2Application::Pointer::MouseButton5; + return pointers; +} + +} + bool Sdl2Application::mainLoopIteration() { /* If exit was requested directly in the constructor, exit immediately without calling anything else */ @@ -982,12 +1018,27 @@ bool Sdl2Application::mainLoopIteration() { case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: { - MouseEvent e{event, static_cast(event.button.button), {event.button.x, event.button.y} - #ifndef CORRADE_TARGET_EMSCRIPTEN - , event.button.clicks - #endif + const Pointer pointer = buttonToPointer(event.button.button); + const Vector2 position{Float(event.button.x), Float(event.button.y)}; + + /* If an additional mouse button was pressed or some buttons + are still left pressed after a release, call a move event + instead */ + const Uint32 buttons = SDL_GetMouseState(nullptr, nullptr); + 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); + } else { + PointerEvent e{event, pointer, position + #ifndef CORRADE_TARGET_EMSCRIPTEN + , event.button.clicks + #endif }; - event.type == SDL_MOUSEBUTTONDOWN ? mousePressEvent(e) : mouseReleaseEvent(e); + event.type == SDL_MOUSEBUTTONDOWN ? + pointerPressEvent(e) : pointerReleaseEvent(e); + } } break; case SDL_MOUSEWHEEL: { @@ -996,10 +1047,11 @@ bool Sdl2Application::mainLoopIteration() { } break; case SDL_MOUSEMOTION: { - MouseMoveEvent e{event, {event.motion.x, event.motion.y}, {event.motion.xrel, event.motion.yrel}, static_cast(event.motion.state)}; - mouseMoveEvent(e); - break; - } + PointerMoveEvent e{event, {}, buttonsToPointers(event.motion.state), + {Float(event.motion.x), Float(event.motion.y)}, + {Float(event.motion.xrel), Float(event.motion.yrel)}}; + pointerMoveEvent(e); + } break; case SDL_MULTIGESTURE: { MultiGestureEvent e{event, {event.mgesture.x, event.mgesture.y}, event.mgesture.dTheta, event.mgesture.dDist, event.mgesture.numFingers}; @@ -1224,9 +1276,131 @@ void Sdl2Application::anyEvent(SDL_Event&) { void Sdl2Application::viewportEvent(ViewportEvent&) {} void Sdl2Application::keyPressEvent(KeyEvent&) {} void Sdl2Application::keyReleaseEvent(KeyEvent&) {} + +#ifdef MAGNUM_BUILD_DEPRECATED +namespace { + +CORRADE_IGNORE_DEPRECATED_PUSH +Sdl2Application::MouseEvent::Button pointerToButton(const Sdl2Application::Pointer pointer) { + switch(pointer) { + case Sdl2Application::Pointer::MouseLeft: + return Sdl2Application::MouseEvent::Button::Left; + case Sdl2Application::Pointer::MouseMiddle: + return Sdl2Application::MouseEvent::Button::Middle; + case Sdl2Application::Pointer::MouseRight: + return Sdl2Application::MouseEvent::Button::Right; + case Sdl2Application::Pointer::MouseButton4: + return Sdl2Application::MouseEvent::Button::X1; + case Sdl2Application::Pointer::MouseButton5: + return Sdl2Application::MouseEvent::Button::X2; + } + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); +} +CORRADE_IGNORE_DEPRECATED_POP + +} +#endif + +void Sdl2Application::pointerPressEvent(PointerEvent& event) { + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_PUSH + MouseEvent mouseEvent{event.event(), pointerToButton(event.pointer()), Vector2i{Math::round(event.position())} + #ifndef CORRADE_TARGET_EMSCRIPTEN + , event.clickCount() + #endif + }; + mousePressEvent(mouseEvent); + CORRADE_IGNORE_DEPRECATED_POP + #else + static_cast(event); + #endif +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH void Sdl2Application::mousePressEvent(MouseEvent&) {} +CORRADE_IGNORE_DEPRECATED_POP +#endif + +void Sdl2Application::pointerReleaseEvent(PointerEvent& event) { + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_PUSH + MouseEvent mouseEvent{event.event(), pointerToButton(event.pointer()), Vector2i{Math::round(event.position())} + #ifndef CORRADE_TARGET_EMSCRIPTEN + , event.clickCount() + #endif + }; + mouseReleaseEvent(mouseEvent); + CORRADE_IGNORE_DEPRECATED_POP + #else + static_cast(event); + #endif +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH void Sdl2Application::mouseReleaseEvent(MouseEvent&) {} +CORRADE_IGNORE_DEPRECATED_POP +#endif + +void Sdl2Application::pointerMoveEvent(PointerMoveEvent& event) { + #ifdef MAGNUM_BUILD_DEPRECATED + const Vector2i roundedPosition{Math::round(event.position())}; + + CORRADE_IGNORE_DEPRECATED_PUSH + /* If the event is due to some button being additionally pressed or one + button from a larger set being released, delegate to a press/release + event instead */ + if(event.pointer()) { + /* SDL2 reports either a move or a press/release, so there shouldn't be + any move in this case */ + CORRADE_INTERNAL_ASSERT(event.relativePosition() == Vector2{}); + MouseEvent mouseEvent{event.event(), pointerToButton(*event.pointer()), + roundedPosition + #ifndef CORRADE_TARGET_EMSCRIPTEN + , 1 + #endif + }; + event.pointers() >= *event.pointer() ? + mousePressEvent(mouseEvent) : mouseReleaseEvent(mouseEvent); + } else { + MouseMoveEvent::Buttons buttons; + if(event.pointers() & Pointer::MouseLeft) + buttons |= MouseMoveEvent::Button::Left; + if(event.pointers() & Pointer::MouseMiddle) + buttons |= MouseMoveEvent::Button::Middle; + if(event.pointers() & Pointer::MouseRight) + buttons |= MouseMoveEvent::Button::Right; + if(event.pointers() & Pointer::MouseButton4) + buttons |= MouseMoveEvent::Button::X1; + if(event.pointers() & Pointer::MouseButton5) + buttons |= MouseMoveEvent::Button::X2; + + /* Can't do just Math::round(event.relativePosition()) because if the + previous position was 4.6 and the new 5.3, they both round to 5 but + the relativePosition is 0.6 and rounds to 1. Conversely, if it'd be + 5.3 and 5.6, the positions round to 5 and 6 but relative position + stays 0. */ + const Vector2i previousRoundedPosition{Math::round(event.position() - event.relativePosition())}; + /* Call the event only if the integer values actually changed */ + if(roundedPosition != previousRoundedPosition) { + MouseMoveEvent mouseEvent{event.event(), roundedPosition, roundedPosition - previousRoundedPosition, buttons}; + mouseMoveEvent(mouseEvent); + } + } + CORRADE_IGNORE_DEPRECATED_POP + #else + static_cast(event); + #endif +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH void Sdl2Application::mouseMoveEvent(MouseMoveEvent&) {} +CORRADE_IGNORE_DEPRECATED_POP +#endif + void Sdl2Application::mouseScrollEvent(MouseScrollEvent&) {} void Sdl2Application::multiGestureEvent(MultiGestureEvent&) {} void Sdl2Application::textInputEvent(TextInputEvent&) {} @@ -1269,15 +1443,35 @@ Containers::StringView Sdl2Application::KeyEvent::keyName() const { return keyName(_key); } +Sdl2Application::InputEvent::Modifiers Sdl2Application::PointerEvent::modifiers() { + if(!_modifiers) + _modifiers = fixedModifiers(Uint16(SDL_GetModState())); + return *_modifiers; +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH Sdl2Application::InputEvent::Modifiers Sdl2Application::MouseEvent::modifiers() { if(_modifiers) return *_modifiers; return *(_modifiers = fixedModifiers(Uint16(SDL_GetModState()))); } +CORRADE_IGNORE_DEPRECATED_POP +#endif +Sdl2Application::InputEvent::Modifiers Sdl2Application::PointerMoveEvent::modifiers() { + if(!_modifiers) + _modifiers = fixedModifiers(Uint16(SDL_GetModState())); + return *_modifiers; +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH Sdl2Application::InputEvent::Modifiers Sdl2Application::MouseMoveEvent::modifiers() { if(_modifiers) return *_modifiers; return *(_modifiers = fixedModifiers(Uint16(SDL_GetModState()))); } +CORRADE_IGNORE_DEPRECATED_POP +#endif Vector2i Sdl2Application::MouseScrollEvent::position() { if(_position) return *_position; diff --git a/src/Magnum/Platform/Sdl2Application.h b/src/Magnum/Platform/Sdl2Application.h index 6cc2f1325..0f0ac9a41 100644 --- a/src/Magnum/Platform/Sdl2Application.h +++ b/src/Magnum/Platform/Sdl2Application.h @@ -500,13 +500,30 @@ class Sdl2Application { class ViewportEvent; class InputEvent; class KeyEvent; + class PointerEvent; + class PointerMoveEvent; + #ifdef MAGNUM_BUILD_DEPRECATED class MouseEvent; class MouseMoveEvent; + #endif class MouseScrollEvent; class MultiGestureEvent; class TextInputEvent; class TextEditingEvent; + /* The damn thing cannot handle forward enum declarations */ + #ifndef DOXYGEN_GENERATING_OUTPUT + enum class Pointer: UnsignedByte; + #endif + + /** + * @brief Set of pointer types + * @m_since_latest + * + * @see @ref PointerMoveEvent::pointers() + */ + typedef Containers::EnumSet Pointers; + #ifdef MAGNUM_TARGET_GL /** * @brief Construct with an OpenGL context @@ -1001,7 +1018,7 @@ class Sdl2Application { * @} */ - /** @{ @name Mouse handling */ + /** @{ @name Pointer handling */ public: /** @@ -1081,28 +1098,90 @@ class Sdl2Application { #endif private: + /** + * @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. + * + * On builds with @ref MAGNUM_BUILD_DEPRECATED enabled, default + * implementation delegates to @ref mousePressEvent(). On builds with + * deprecated functionality disabled, default implementation does + * nothing. + */ + virtual void pointerPressEvent(PointerEvent& event); + + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Mouse press event + * @m_deprecated_since_latest Use @ref pointerPressEvent() instead, + * which is a better abstraction for covering both mouse and touch + * / pen input. + * + * Default implementation does nothing. + */ + virtual CORRADE_DEPRECATED("use pointerPressEvent() instead") void mousePressEvent(MouseEvent& event); + #endif + + /** + * @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 mouse button is pressed. Default implementation does + * On builds with @ref MAGNUM_BUILD_DEPRECATED enabled, default + * implementation delegates to @ref mouseReleaseEvent(). On builds with + * deprecated functionality disabled, default implementation does * nothing. */ - virtual void mousePressEvent(MouseEvent& event); + virtual void pointerReleaseEvent(PointerEvent& event); + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Mouse release event + * @m_deprecated_since_latest Use @ref pointerReleaseEvent() instead, + * which is a better abstraction for covering both mouse and touch + * / pen input. * - * Called when mouse button is released. Default implementation does + * Default implementation does nothing. + */ + virtual CORRADE_DEPRECATED("use pointerReleaseEvent() instead") void mouseReleaseEvent(MouseEvent& event); + #endif + + /** + * @brief Pointer move event + * @m_since_latest + * + * Called when any of the currently pressed pointers is moved or + * 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 + * @relativeref{Corrade,Containers::NullOpt}, to either + * @ref mousePressEvent() or @ref mouseReleaseEvent(). On builds with + * deprecated functionality disabled, default implementation does * nothing. */ - virtual void mouseReleaseEvent(MouseEvent& event); + virtual void pointerMoveEvent(PointerMoveEvent& event); + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Mouse move event + * @m_deprecated_since_latest Use @ref pointerMoveEvent() instead, + * which is a better abstraction for covering both mouse and touch + * / pen input. * - * Called when mouse is moved. Default implementation does nothing. + * Default implementation does nothing. */ - virtual void mouseMoveEvent(MouseMoveEvent& event); + virtual CORRADE_DEPRECATED("use pointerMoveEvent() instead") void mouseMoveEvent(MouseMoveEvent& event); + #endif /** * @brief Mouse scroll event @@ -1112,14 +1191,6 @@ class Sdl2Application { */ virtual void mouseScrollEvent(MouseScrollEvent& event); - /* Since 1.8.17, the original short-hand group closing doesn't work - anymore. FFS. */ - /** - * @} - */ - - /** @{ @name Touch gesture handling */ - /** * @brief Multi gesture event * @@ -1294,6 +1365,47 @@ class Sdl2Application { int _exitCode = 0; }; +/** +@brief Pointer type +@m_since_latest + +@see @ref Pointers, @ref PointerEvent::pointer(), + @ref PointerMoveEvent::pointer(), @ref PointerMoveEvent::pointers() +*/ +enum class Sdl2Application::Pointer: UnsignedByte { + /** + * Left mouse button. Corresponds to `SDL_BUTTON_LEFT` / + * `SDL_BUTTON_LMASK`. + */ + MouseLeft = 1 << 0, + + /** + * Middle mouse button. Corresponds to `SDL_BUTTON_MIDDLE` / + * `SDL_BUTTON_MMASK`. + */ + MouseMiddle = 1 << 1, + + /** + * Right mouse button. Corresponds to `SDL_BUTTON_RIGHT` / + * `SDL_BUTTON_RMASK`. + */ + MouseRight = 1 << 2, + + /** + * Fourth mouse button, such as wheel left. Corresponds to `SDL_BUTTON_X1` + * / `SDL_BUTTON_X1MASK`. + */ + MouseButton4 = 1 << 3, + + /** + * Fifth mouse button, such as wheel right. Corresponds to `SDL_BUTTON_X2` + * / `SDL_BUTTON_X2MASK`. + */ + MouseButton5 = 1 << 4, +}; + +CORRADE_ENUMSET_OPERATORS(Sdl2Application::Pointers) + #ifdef MAGNUM_TARGET_GL /** @brief OpenGL context configuration @@ -2141,9 +2253,10 @@ class Sdl2Application::ViewportEvent { /** @brief Base for input events -@see @ref KeyEvent, @ref MouseEvent, @ref MouseMoveEvent, @ref keyPressEvent(), - @ref keyReleaseEvent(), @ref mousePressEvent(), @ref mouseReleaseEvent(), - @ref mouseMoveEvent() +@see @ref KeyEvent, @ref PointerEvent, @ref PointerMoveEvent, + @ref MouseScrollEvent, @ref keyPressEvent(), @ref keyReleaseEvent(), + @ref pointerPressEvent(), @ref pointerReleaseEvent(), + @ref pointerMoveEvent(), @ref mouseScrollEvent() */ class Sdl2Application::InputEvent { public: @@ -2151,7 +2264,9 @@ class Sdl2Application::InputEvent { * @brief Modifier * * @see @ref Modifiers, @ref KeyEvent::modifiers(), - * @ref MouseEvent::modifiers(), @ref MouseMoveEvent::modifiers() + * @ref PointerEvent::modifiers(), + * @ref PointerMoveEvent::modifiers(), + * @ref MouseScrollEvent::modifiers() */ enum class Modifier: Uint16 { /** @@ -2208,8 +2323,9 @@ class Sdl2Application::InputEvent { /** * @brief Set of modifiers * - * @see @ref KeyEvent::modifiers(), @ref MouseEvent::modifiers(), - * @ref MouseMoveEvent::modifiers() + * @see @ref KeyEvent::modifiers(), @ref PointerEvent::modifiers(), + * @ref PointerMoveEvent::modifiers(), + * @ref MouseScrollEvent::modifiers() */ typedef Containers::EnumSet Modifiers; @@ -2242,9 +2358,9 @@ class Sdl2Application::InputEvent { * @brief Underlying SDL event * * Of type `SDL_KEYDOWN` / `SDL_KEYUP` for @ref KeyEvent, - * `SDL_MOUSEBUTTONUP` / `SDL_MOUSEBUTTONDOWN` for @ref MouseEvent, - * `SDL_MOUSEWHEEL` for @ref MouseScrollEvent and `SDL_MOUSEMOTION` for - * @ref MouseMoveEvent. + * `SDL_MOUSEBUTTONDOWN` / `SDL_MOUSEBUTTONUP` for @ref PointerEvent, + * `SDL_MOUSEMOTION` for @ref PointerMoveEvent and `SDL_MOUSEWHEEL` for + * @ref MouseScrollEvent. * @see @ref Sdl2Application::anyEvent() */ const SDL_Event& event() const { return _event; } @@ -2585,13 +2701,85 @@ class Sdl2Application::KeyEvent: public Sdl2Application::InputEvent { const bool _repeated; }; +/** +@brief Pointer event +@m_since_latest + +@see @ref PointerMoveEvent, @ref pointerPressEvent(), + @ref pointerReleaseEvent() +*/ +class Sdl2Application::PointerEvent: public InputEvent { + public: + /** @brief Copying is not allowed */ + PointerEvent(const PointerEvent&) = delete; + + /** @brief Moving is not allowed */ + PointerEvent(PointerEvent&&) = delete; + + /** @brief Copying is not allowed */ + PointerEvent& operator=(const PointerEvent&) = delete; + + /** @brief Moving is not allowed */ + PointerEvent& operator=(PointerEvent&&) = delete; + + /** @brief Pointer type that was pressed or released */ + Pointer pointer() const { return _pointer; } + + /** + * @brief Position + * + * For mouse input the position is always reported in whole pixels. + */ + Vector2 position() const { return _position; } + + #ifndef CORRADE_TARGET_EMSCRIPTEN + /** + * @brief Click count + * + * @note Not available on @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten". + */ + Int clickCount() const { return _clickCount; } + #endif + + /** + * @brief Modifiers + * + * Lazily populated on first request. + */ + Modifiers modifiers(); + + private: + friend Sdl2Application; + + explicit PointerEvent(const SDL_Event& event, Pointer pointer, const Vector2& position + #ifndef CORRADE_TARGET_EMSCRIPTEN + , Int clickCount + #endif + ): InputEvent{event}, _pointer(pointer), _position{position} + #ifndef CORRADE_TARGET_EMSCRIPTEN + , _clickCount{clickCount} + #endif + {} + + const Pointer _pointer; + Containers::Optional _modifiers; + const Vector2 _position; + #ifndef CORRADE_TARGET_EMSCRIPTEN + const Int _clickCount; + #endif +}; + +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief Mouse event +@m_deprecated_since_latest Use @ref PointerEvent, @ref pointerPressEvent() and + @ref pointerReleaseEvent() instead, which is a better abstraction for + covering both mouse and touch / pen input. @see @ref MouseMoveEvent, @ref MouseScrollEvent, @ref mousePressEvent(), @ref mouseReleaseEvent() */ -class Sdl2Application::MouseEvent: public Sdl2Application::InputEvent { +class CORRADE_DEPRECATED("use PointerEvent, pointerPressEvent() and pointerReleaseEvent() instead") Sdl2Application::MouseEvent: public InputEvent { public: /** * @brief Mouse button @@ -2652,13 +2840,90 @@ class Sdl2Application::MouseEvent: public Sdl2Application::InputEvent { #endif Containers::Optional _modifiers; }; +#endif +/** +@brief Pointer move event +@m_since_latest + +@see @ref PointerEvent, @ref pointerMoveEvent() +*/ +class Sdl2Application::PointerMoveEvent: public InputEvent { + public: + /** @brief Copying is not allowed */ + PointerMoveEvent(const PointerMoveEvent&) = delete; + + /** @brief Moving is not allowed */ + PointerMoveEvent(PointerMoveEvent&&) = delete; + + /** @brief Copying is not allowed */ + PointerMoveEvent& operator=(const PointerMoveEvent&) = delete; + + /** @brief Moving is not allowed */ + PointerMoveEvent& operator=(PointerMoveEvent&&) = delete; + + /** + * @brief Pointer type that was added or removed from the set of pressed pointers + * + * Is non-empty only in case a mouse button was pressed in addition to + * an already pressed button, or if one mouse button from multiple + * 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. + */ + Containers::Optional pointer() const { return _pointer; } + + /** + * @brief Pointer types pressed in this event + * + * Returns an empty set if no pointers are pressed, which happens for + * example when a mouse is just moved around. + * @see @ref pointer() + */ + Pointers pointers() const { return _pointers; } + + /** + * @brief Position + * + * For mouse input the position is always reported in whole pixels. + */ + Vector2 position() const { return _position; } + + /** + * @brief Position relative to the previous touch event + * + * For mouse input the position is always reported in whole pixels. + */ + Vector2 relativePosition() const { return _relativePosition; } + + /** + * @brief Modifiers + * + * Lazily populated on first request. + */ + Modifiers modifiers(); + + 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} {} + + const Containers::Optional _pointer; + const Pointers _pointers; + const Vector2 _position, _relativePosition; + Containers::Optional _modifiers; +}; + +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief Mouse move event +@m_deprecated_since_latest Use @ref PointerMoveEvent and + @ref pointerMoveEvent() instead, which is a better abstraction for covering + both mouse and touch / pen input. @see @ref MouseEvent, @ref MouseScrollEvent, @ref mouseMoveEvent() */ -class Sdl2Application::MouseMoveEvent: public Sdl2Application::InputEvent { +class CORRADE_DEPRECATED("use PointerMoveEvent and pointerMoveEvent() instead") Sdl2Application::MouseMoveEvent: public InputEvent { public: /** * @brief Mouse button @@ -2714,10 +2979,15 @@ class Sdl2Application::MouseMoveEvent: public Sdl2Application::InputEvent { Containers::Optional _modifiers; }; +CORRADE_IGNORE_DEPRECATED_PUSH +CORRADE_ENUMSET_OPERATORS(Sdl2Application::MouseMoveEvent::Buttons) +CORRADE_IGNORE_DEPRECATED_POP +#endif + /** @brief Mouse scroll event -@see @ref MouseEvent, @ref MouseMoveEvent, @ref mouseScrollEvent() +@see @ref PointerEvent, @ref PointerMoveEvent, @ref mouseScrollEvent() */ class Sdl2Application::MouseScrollEvent: public Sdl2Application::InputEvent { public: @@ -3007,7 +3277,6 @@ typedef BasicScreenedApplication ScreenedApplication; CORRADE_ENUMSET_OPERATORS(Sdl2Application::Configuration::WindowFlags) CORRADE_ENUMSET_OPERATORS(Sdl2Application::InputEvent::Modifiers) -CORRADE_ENUMSET_OPERATORS(Sdl2Application::MouseMoveEvent::Buttons) }} diff --git a/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp b/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp index a579d5483..ee5627b28 100644 --- a/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp +++ b/src/Magnum/Platform/Test/Sdl2ApplicationTest.cpp @@ -77,7 +77,25 @@ static Debug& operator<<(Debug& debug, Application::InputEvent::Modifier value) return debug << "(" << Debug::nospace << UnsignedInt(value) << Debug::nospace << ")"; } -static Debug& operator<<(Debug& debug, Application::MouseMoveEvent::Button value) { +static Debug& operator<<(Debug& debug, Application::Pointer value) { + debug << "Pointer" << Debug::nospace; + + switch(value) { + #define _c(value) case Application::Pointer::value: return debug << "::" #value; + _c(MouseLeft) + _c(MouseMiddle) + _c(MouseRight) + _c(MouseButton4) + _c(MouseButton5) + #undef _c + } + + return debug << "(" << Debug::nospace << UnsignedInt(value) << Debug::nospace << ")"; +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH +CORRADE_UNUSED static Debug& operator<<(Debug& debug, Application::MouseMoveEvent::Button value) { debug << "Button" << Debug::nospace; switch(value) { @@ -92,6 +110,8 @@ static Debug& operator<<(Debug& debug, Application::MouseMoveEvent::Button value return debug << "(" << Debug::nospace << UnsignedInt(value) << Debug::nospace << ")"; } +CORRADE_IGNORE_DEPRECATED_POP +#endif namespace Test { namespace { @@ -107,7 +127,19 @@ Debug& operator<<(Debug& debug, Application::InputEvent::Modifiers value) { }); } -Debug& operator<<(Debug& debug, Application::MouseEvent::Button value) { +Debug& operator<<(Debug& debug, Application::Pointers value) { + return Containers::enumSetDebugOutput(debug, value, "Pointers{}", { + Application::Pointer::MouseLeft, + Application::Pointer::MouseMiddle, + Application::Pointer::MouseRight, + Application::Pointer::MouseButton4, + Application::Pointer::MouseButton5, + }); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH +CORRADE_UNUSED Debug& operator<<(Debug& debug, Application::MouseEvent::Button value) { debug << "Button" << Debug::nospace; switch(value) { @@ -123,7 +155,7 @@ Debug& operator<<(Debug& debug, Application::MouseEvent::Button value) { return debug << "(" << Debug::nospace << UnsignedInt(value) << Debug::nospace << ")"; } -Debug& operator<<(Debug& debug, Application::MouseMoveEvent::Buttons value) { +CORRADE_UNUSED Debug& operator<<(Debug& debug, Application::MouseMoveEvent::Buttons value) { return Containers::enumSetDebugOutput(debug, value, "Buttons{}", { Application::MouseMoveEvent::Button::Left, Application::MouseMoveEvent::Button::Middle, @@ -132,6 +164,8 @@ Debug& operator<<(Debug& debug, Application::MouseMoveEvent::Buttons value) { Application::MouseMoveEvent::Button::X2, }); } +CORRADE_IGNORE_DEPRECATED_POP +#endif Debug& operator<<(Debug& debug, Application::KeyEvent::Key value) { debug << "Key" << Debug::nospace; @@ -285,22 +319,38 @@ struct Sdl2ApplicationTest: Platform::Application { redraw(); } - /* For testing event coordinates */ + /* 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(); + _gestureDistance = {}; + _gestureRotation = {}; + } + void pointerReleaseEvent(PointerEvent& event) override { + Debug{} << "pointer release:" << event.pointer() << 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(); + } + #else + CORRADE_IGNORE_DEPRECATED_PUSH void mousePressEvent(MouseEvent& event) override { Debug{} << "mouse press:" << event.button() << Debug::packed << event.position() << event.modifiers(); _gestureDistance = {}; _gestureRotation = {}; } - void mouseReleaseEvent(MouseEvent& event) override { Debug{} << "mouse release:" << event.button() << Debug::packed << event.position() << event.modifiers(); _gestureDistance = {}; _gestureRotation = {}; } - void mouseMoveEvent(MouseMoveEvent& event) override { Debug{} << "mouse move:" << event.buttons() << Debug::packed << event.position() << Debug::packed << event.relativePosition() << event.modifiers(); } + CORRADE_IGNORE_DEPRECATED_POP + #endif void mouseScrollEvent(MouseScrollEvent& event) override { Debug{} << "mouse scroll:" << event.modifiers() << Debug::packed << event.offset() << Debug::packed << event.position();