From 4b76918c367a12e2879a9fa9a26d45e9aba651e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 14 Oct 2024 12:56:14 +0200 Subject: [PATCH] Platform: replace mouse events with pointer events in XApplication. A special case here is that the event `state` doesn't yet include the currently pressed button on press, and still includes the currently release button on release. Which is the inverse of what the other toolkits do, and contrary to the docs, so I patch it. Furthermore, the buttons were originally reported on all input events, but as the PointerEvent is now fired only when the first ever button is pressed or the last remaining button is released, it doesn't make sense to have a PointerEvent::pointers() getter, as it'd return always either pointer() itself on pressed, or nothing at all on release. So the pointers() getter is now moved directly to a KeyEvent, PointerMoveEvent and MouseScrollEvent. --- doc/changelog.dox | 5 +- src/Magnum/Platform/AbstractXApplication.cpp | 164 +++++++++- src/Magnum/Platform/AbstractXApplication.h | 303 ++++++++++++++++-- .../Test/AbstractXApplicationTest.cpp | 60 +++- 4 files changed, 494 insertions(+), 38 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 7b6a293c5..c0b4c2508 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -1396,8 +1396,9 @@ See also: @relativeref{Platform::Sdl2Application,PointerEvent} and @relativeref{Platform::Sdl2Application,PointerMoveEvent} APIs that provide a better abstraction over general pointer input, not just a mouse alone. - The same change is done in @ref Platform::GlfwApplication. -- @ref Platform::AbstractXApplication::MouseEvent::Button `WheelUp` and + The same change is done in @ref Platform::AbstractXApplication and + @ref Platform::GlfwApplication. +- @cpp Platform::AbstractXApplication::MouseEvent::Button::WheelUp @ce and `WheelDown` members are deprecated in favor of a dedicated @ref Platform::AbstractXApplication::mouseScrollEvent(). Similar change was done for all other application classes in 2016 already. diff --git a/src/Magnum/Platform/AbstractXApplication.cpp b/src/Magnum/Platform/AbstractXApplication.cpp index 43e7e5f94..1eed0d9bf 100644 --- a/src/Magnum/Platform/AbstractXApplication.cpp +++ b/src/Magnum/Platform/AbstractXApplication.cpp @@ -140,6 +140,34 @@ int AbstractXApplication::exec() { return _exitCode; } +namespace { + +AbstractXApplication::Pointer buttonToPointer(const unsigned int button) { + switch(button) { + case 1 /*Button1*/: + return AbstractXApplication::Pointer::MouseLeft; + case 2 /*Button2*/: + return AbstractXApplication::Pointer::MouseMiddle; + case 3 /*Button3*/: + return AbstractXApplication::Pointer::MouseRight; + } + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); +} + +AbstractXApplication::Pointers buttonsToPointers(const unsigned int state) { + AbstractXApplication::Pointers pointers; + if(state & Button1Mask) + pointers |= AbstractXApplication::Pointer::MouseLeft; + if(state & Button2Mask) + pointers |= AbstractXApplication::Pointer::MouseMiddle; + if(state & Button3Mask) + pointers |= AbstractXApplication::Pointer::MouseRight; + return pointers; +} + +} + bool AbstractXApplication::mainLoopIteration() { /* If exit was requested directly in the constructor, exit immediately without calling anything else */ @@ -184,15 +212,52 @@ bool AbstractXApplication::mainLoopIteration() { if(event.type == ButtonPress) mouseScrollEvent(e); } else { - MouseEvent e(MouseEvent::Button(event.xbutton.button), event.xkey.state, {event.xbutton.x, event.xbutton.y}); - event.type == ButtonPress ? mousePressEvent(e) : mouseReleaseEvent(e); + const Pointer pointer = buttonToPointer(event.xbutton.button); + Pointers pointers = buttonsToPointers(event.xbutton.state); + /* Compared to other toolkits, the `pointers` don't include + the currently pressed button on press yet, and still + include the currently released button on release. Make + it consistent. */ + if(event.type == ButtonPress) { + CORRADE_INTERNAL_ASSERT(!(pointers & pointer)); + pointers |= pointer; + } else { + CORRADE_INTERNAL_ASSERT(pointers & pointer); + pointers &= ~pointer; + } + + /* If an additional mouse button was pressed or some other + buttons are still left pressed after a release, call a + move event instead */ + if((event.type == ButtonPress && (pointers & ~pointer)) || + (event.type == ButtonRelease && pointers)) { + /* As we are patching up the set of currently pressed + pointers, the move event can't just figure that + out from the state */ + PointerMoveEvent e{pointer, pointers, + {Float(event.xbutton.x), Float(event.xbutton.y)}, + event.xbutton.state}; + pointerMoveEvent(e); + } else { + PointerEvent e(pointer, + {Float(event.xbutton.x), Float(event.xbutton.y)}, + event.xbutton.state); + event.type == ButtonPress ? + pointerPressEvent(e) : pointerReleaseEvent(e); + } } } break; /* Mouse move events */ case MotionNotify: { - MouseMoveEvent e(event.xmotion.state, {event.xmotion.x, event.xmotion.y}); - mouseMoveEvent(e); + /* Because for the move-from-press/release above we're patching + up the set of pressed pointers, we need to explicitly pass + it in here as well. No need to patch anything in this case + tho -- the set should be up-to-date. */ + PointerMoveEvent e({}, buttonsToPointers(event.xmotion.state), + {Float(event.xmotion.x), Float(event.xmotion.y)}, + event.xmotion.state); + pointerMoveEvent(e); } break; } } @@ -208,9 +273,92 @@ bool AbstractXApplication::mainLoopIteration() { void AbstractXApplication::viewportEvent(ViewportEvent&) {} void AbstractXApplication::keyPressEvent(KeyEvent&) {} void AbstractXApplication::keyReleaseEvent(KeyEvent&) {} + +#ifdef MAGNUM_BUILD_DEPRECATED +namespace { + +CORRADE_IGNORE_DEPRECATED_PUSH +AbstractXApplication::MouseEvent::Button pointerToButton(const AbstractXApplication::Pointer pointer) { + switch(pointer) { + case AbstractXApplication::Pointer::MouseLeft: + return AbstractXApplication::MouseEvent::Button::Left; + case AbstractXApplication::Pointer::MouseMiddle: + return AbstractXApplication::MouseEvent::Button::Middle; + case AbstractXApplication::Pointer::MouseRight: + return AbstractXApplication::MouseEvent::Button::Right; + } + + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); +} +CORRADE_IGNORE_DEPRECATED_POP + +} +#endif + +void AbstractXApplication::pointerPressEvent(PointerEvent& event) { + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_PUSH + /* The positions are reported in integers in the first place, no need to + round anything */ + MouseEvent mouseEvent{pointerToButton(event.pointer()), event._modifiers, Vector2i{event.position()}}; + mousePressEvent(mouseEvent); + CORRADE_IGNORE_DEPRECATED_POP + #else + static_cast(event); + #endif +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH void AbstractXApplication::mousePressEvent(MouseEvent&) {} +CORRADE_IGNORE_DEPRECATED_POP +#endif + +void AbstractXApplication::pointerReleaseEvent(PointerEvent& event) { + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_PUSH + /* The positions are reported in integers in the first place, no need to + round anything */ + MouseEvent mouseEvent{pointerToButton(event.pointer()), event._modifiers, Vector2i{event.position()}}; + mouseReleaseEvent(mouseEvent); + CORRADE_IGNORE_DEPRECATED_POP + #else + static_cast(event); + #endif +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH void AbstractXApplication::mouseReleaseEvent(MouseEvent&) {} +CORRADE_IGNORE_DEPRECATED_POP +#endif + +void AbstractXApplication::pointerMoveEvent(PointerMoveEvent& event) { + #ifdef MAGNUM_BUILD_DEPRECATED + 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()) { + MouseEvent mouseEvent{pointerToButton(*event.pointer()), event._modifiers, + Vector2i{event.position()}}; + event.pointers() >= *event.pointer() ? + mousePressEvent(mouseEvent) : mouseReleaseEvent(mouseEvent); + } else { + MouseMoveEvent mouseEvent{event._modifiers, Vector2i{event.position()}}; + mouseMoveEvent(mouseEvent); + } + CORRADE_IGNORE_DEPRECATED_POP + #else + static_cast(event); + #endif +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH void AbstractXApplication::mouseMoveEvent(MouseMoveEvent&) {} +CORRADE_IGNORE_DEPRECATED_POP +#endif void AbstractXApplication::mouseScrollEvent(MouseScrollEvent& event) { #ifdef MAGNUM_BUILD_DEPRECATED @@ -232,4 +380,12 @@ AbstractXApplication::Configuration::Configuration(): _size(800, 600) {} AbstractXApplication::Configuration::~Configuration() = default; +AbstractXApplication::Pointers AbstractXApplication::KeyEvent::pointers() const { + return buttonsToPointers(_modifiers); +} + +AbstractXApplication::Pointers AbstractXApplication::MouseScrollEvent::pointers() const { + return buttonsToPointers(_modifiers); +} + }} diff --git a/src/Magnum/Platform/AbstractXApplication.h b/src/Magnum/Platform/AbstractXApplication.h index c9474efe7..072faffa5 100644 --- a/src/Magnum/Platform/AbstractXApplication.h +++ b/src/Magnum/Platform/AbstractXApplication.h @@ -104,10 +104,28 @@ class AbstractXApplication { class ViewportEvent; class InputEvent; class KeyEvent; + class PointerEvent; + class PointerMoveEvent; + #ifdef MAGNUM_BUILD_DEPRECATED class MouseEvent; class MouseMoveEvent; + #endif class MouseScrollEvent; + /* 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 KeyEvent::pointers(), @ref PointerMoveEvent::pointers(), + * @ref MouseScrollEvent::pointers() + */ + typedef Containers::EnumSet Pointers; + /** @brief Copying is not allowed */ AbstractXApplication(const AbstractXApplication&) = delete; @@ -286,16 +304,92 @@ class AbstractXApplication { * @} */ - /** @{ @name Mouse handling */ + /** @{ @name Pointer handling */ + + /** + * @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 - /** @copydoc Sdl2Application::mousePressEvent() */ - virtual void mousePressEvent(MouseEvent& event); + /** + * @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. + * + * 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 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. + * + * Default implementation does nothing. + */ + virtual CORRADE_DEPRECATED("use pointerReleaseEvent() instead") void mouseReleaseEvent(MouseEvent& event); + #endif - /** @copydoc Sdl2Application::mouseReleaseEvent() */ - virtual void mouseReleaseEvent(MouseEvent& event); + /** + * @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 pointerMoveEvent(PointerMoveEvent& event); - /** @copydoc Sdl2Application::mouseMoveEvent() */ - virtual void mouseMoveEvent(MouseMoveEvent& 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. + * + * Default implementation does nothing. + */ + virtual CORRADE_DEPRECATED("use pointerMoveEvent() instead") void mouseMoveEvent(MouseMoveEvent& event); + #endif /** * @brief Mouse scroll event @@ -351,6 +445,31 @@ class AbstractXApplication { Flags _flags; }; +/** +@brief Pointer type +@m_since_latest + +@see @ref Pointers, @ref KeyEvent::pointers(), @ref PointerEvent::pointer(), + @ref PointerMoveEvent::pointer(), @ref PointerMoveEvent::pointers(), + @ref MouseScrollEvent::pointers() +*/ +enum class AbstractXApplication::Pointer: UnsignedByte { + /** Left mouse button. Corresponds to `Button1` / `Button1Mask`. */ + MouseLeft = 1 << 0, + + /** + * Middle mouse button. Corresponds to `Button2` / `Button2Mask`. + */ + MouseMiddle = 1 << 1, + + /** + * Right mouse button. Corresponds to `Button3` / `Button3Mask`. + */ + MouseRight = 1 << 2 +}; + +CORRADE_ENUMSET_OPERATORS(AbstractXApplication::Pointers) + /** @brief OpenGL context configuration @@ -563,9 +682,10 @@ class AbstractXApplication::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 AbstractXApplication::InputEvent { public: @@ -621,12 +741,12 @@ class AbstractXApplication::InputEvent { */ typedef Containers::EnumSet Modifiers; + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Mouse button - * - * @see @ref Buttons, @ref buttons() + * @m_deprecated_since_latest Use @ref Pointer instead. */ - enum class Button: unsigned int { + enum class CORRADE_DEPRECATED_ENUM("use Pointer instead") Button: unsigned int { Left = Button1Mask, /**< Left button */ Middle = Button2Mask, /**< Middle button */ Right = Button3Mask /**< Right button */ @@ -634,10 +754,12 @@ class AbstractXApplication::InputEvent { /** * @brief Set of mouse buttons - * - * @see @ref buttons() + * @m_deprecated_since_latest Use @ref Pointers instead. */ - typedef Containers::EnumSet