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