From b0abae88d1b44e0c4c5e33afea2dc7850f7fb0ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 17 Oct 2024 23:42:23 +0200 Subject: [PATCH] Platform: replace mouse events with pointer events in AndroidApp. And thus also add actual support for finger and pen input, instead of reporting them as Button::None. Something that needed fixing ever since the initial implementation in 2014. I *really* wanted to make a setup where mouse input would be recognized as such and reported in the app, but wasted the whole day on that and got only as far as having it recognized as a stylus input with hover (!!!), when using some Android Desktop image. Not sure if that's some stupid mislabeling (because middle and right mouse buttons are reported as such) or it's just the emulation layer being crap. While at it, I at least added support for hover events. I still have to document all the newly found warts and hard-to-remember workflows with getting a simulator running. --- doc/changelog.dox | 3 +- src/Magnum/Platform/AndroidApplication.cpp | 258 +++++++++++++++- src/Magnum/Platform/AndroidApplication.h | 292 +++++++++++++++++- .../Platform/Test/AndroidApplicationTest.cpp | 66 +++- 4 files changed, 582 insertions(+), 37 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 11e25a15f..f35fe803c 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -1399,7 +1399,8 @@ See also: @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::AbstractXApplication, - @ref Platform::EmscriptenApplication and @ref Platform::GlfwApplication. + @ref Platform::AndroidApplication, @ref Platform::EmscriptenApplication 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 diff --git a/src/Magnum/Platform/AndroidApplication.cpp b/src/Magnum/Platform/AndroidApplication.cpp index 03ac3e6a8..fccd14816 100644 --- a/src/Magnum/Platform/AndroidApplication.cpp +++ b/src/Magnum/Platform/AndroidApplication.cpp @@ -31,6 +31,7 @@ #include #include "Magnum/GL/Version.h" +#include "Magnum/Math/Functions.h" #include "Magnum/Platform/ScreenedApplication.hpp" #include "Implementation/Egl.h" @@ -188,9 +189,6 @@ void AndroidApplication::redraw() { } void AndroidApplication::viewportEvent(ViewportEvent&) {} -void AndroidApplication::mousePressEvent(MouseEvent&) {} -void AndroidApplication::mouseReleaseEvent(MouseEvent&) {} -void AndroidApplication::mouseMoveEvent(MouseMoveEvent&) {} namespace { struct Data { @@ -241,6 +239,47 @@ void AndroidApplication::commandEvent(android_app* state, int32_t cmd) { } } +namespace { + +AndroidApplication::Pointers motionEventButtons(AInputEvent* event) { + const std::int32_t buttons = AMotionEvent_getButtonState(event); + AndroidApplication::Pointers pointers; + if(buttons & AMOTION_EVENT_BUTTON_PRIMARY) + pointers |= AndroidApplication::Pointer::MouseLeft; + if(buttons & AMOTION_EVENT_BUTTON_TERTIARY) + pointers |= AndroidApplication::Pointer::MouseMiddle; + if(buttons & AMOTION_EVENT_BUTTON_SECONDARY) + pointers |= AndroidApplication::Pointer::MouseRight; + /** @todo AMOTION_EVENT_BUTTON_BACK, AMOTION_EVENT_BUTTON_FORWARD once it's + possible to verify they match MouseButton4 / MouseButton5 in + GlfwApplication and Sdl2Application */ + return pointers; +} + +AndroidApplication::Pointers motionEventPointers(AInputEvent* event, const AndroidApplication::Pointers pressedButtons) { + switch(AMotionEvent_getToolType(event, 0)) { + case AMOTION_EVENT_TOOL_TYPE_MOUSE: + /** @todo MouseButton4 / MouseButton5, once they're added & + tested */ + return (AndroidApplication::Pointer::MouseLeft| + AndroidApplication::Pointer::MouseMiddle| + AndroidApplication::Pointer::MouseRight) & pressedButtons; + case AMOTION_EVENT_TOOL_TYPE_FINGER: + return AndroidApplication::Pointer::Finger; + case AMOTION_EVENT_TOOL_TYPE_STYLUS: + /** @todo use pressedButtonsPointers once there's additional pen + button enum values */ + return AndroidApplication::Pointer::Pen; + case AMOTION_EVENT_TOOL_TYPE_ERASER: + return AndroidApplication::Pointer::Eraser; + case AMOTION_EVENT_TOOL_TYPE_UNKNOWN: + default: + return AndroidApplication::Pointer::Unknown; + } +} + +} + std::int32_t AndroidApplication::inputEvent(android_app* state, AInputEvent* event) { CORRADE_INTERNAL_ASSERT(static_cast(state->userData)->instance); AndroidApplication& app = *static_cast(state->userData)->instance; @@ -249,24 +288,132 @@ std::int32_t AndroidApplication::inputEvent(android_app* state, AInputEvent* eve switch(action) { case AMOTION_EVENT_ACTION_DOWN: case AMOTION_EVENT_ACTION_UP: { - /* On a touch screen move events aren't reported when the - finger is moving above (of course), so remember the position - always */ - app._previousMouseMovePosition = {Int(AMotionEvent_getX(event, 0)), Int(AMotionEvent_getY(event, 0))}; - MouseEvent e(event); - action == AMOTION_EVENT_ACTION_DOWN ? app.mousePressEvent(e) : app.mouseReleaseEvent(e); - return e.isAccepted() ? 1 : 0; + const Vector2 position{AMotionEvent_getX(event, 0), + AMotionEvent_getY(event, 0)}; + + /* Query the currently pressed buttons. If this is not a mouse + event, it'll give back garbage, but that's fine as we won't + use it in that case. Then, based on whether it's a press or + a release, use the previously recorded pointers to figure + out what was actually pressed. */ + const Pointers pressedButtons = motionEventButtons(event); + const Pointers pointers = motionEventPointers(event, + action == AMOTION_EVENT_ACTION_DOWN ? + pressedButtons & ~app._previousPressedButtons : + ~pressedButtons & app._previousPressedButtons); + + /* The expectation is that the difference betweeen the + previously recorded set of pointers and current one will be + exactly one bit for a pointer type that got either pressed + or released. If it's not, it means we lost some events, and + until API 33+ and AMotionEvent_getActionButton() on + AMOTION_EVENT_BUTTON_PRESS / AMOTION_EVENT_BUTTON_RELEASE, + there's no way to reliably know what concrete mouse / pen + button caused the event. */ + Pointer pointer; + /* http://www.graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 */ + if(pointers && !(UnsignedByte(pointers) & (UnsignedByte(pointers) - 1))) + pointer = Pointer(UnsignedByte(pointers)); + else + pointer = Pointer::Unknown; + + /** @todo Once there's an ability to actually *know* what + button was pressed or released (API 33+), implement + translation to move events like in GlfwApplication, + Sdl2Application and EmscriptenApplication. With my emulator + testing, where a mouse was interpreted as a stylus (?!), + multiple buttons being pressed didn't even trigger a press + or release event, so this scenario is seemingly impossible + to happen. */ + PointerEvent e{event, pointer}; + action == AMOTION_EVENT_ACTION_DOWN ? + app.pointerPressEvent(e) : app.pointerReleaseEvent(e); + + /* Remember the currently pressed pointers for the next time */ + app._previousPressedButtons = pressedButtons; + /* A touch screen doesn't have hover events, so remember the + position here as well. See below for why this has to be + remembered at all. */ + app._previousPointerPosition = position; + + return e.isAccepted(); } case AMOTION_EVENT_ACTION_MOVE: { - Vector2i position{Int(AMotionEvent_getX(event, 0)), Int(AMotionEvent_getY(event, 0))}; - MouseMoveEvent e{event, - app._previousMouseMovePosition == Vector2i{-1} ? Vector2i{} : - position - app._previousMouseMovePosition}; - app._previousMouseMovePosition = position; - app.mouseMoveEvent(e); - return e.isAccepted() ? 1 : 0; + const Pointers pressedButtons = motionEventButtons(event); + const Pointers pointers = motionEventPointers(event, pressedButtons); + const Vector2 position{AMotionEvent_getX(event, 0), + AMotionEvent_getY(event, 0)}; + const Vector2 relativePosition = + Math::isNan(app._previousPointerPosition).all() ? + Vector2{} : position - app._previousPointerPosition; + + /* The thing fires move events right after press events, with + the exact same position, for (emulated?) events at least. I + suppose that's some sort of unasked-for misfeature for + "improving" UX or fixing broken apps. Not interested, filter + those out if the relative position is zero and the set of + pressed buttons is the same. Hopefully not accepting those + doesn't lead to some strange behavior. */ + bool accepted = false; + if(relativePosition != Vector2{} || pressedButtons != app._previousPressedButtons) { + PointerMoveEvent e{event, {}, pointers, relativePosition}; + app.pointerMoveEvent(e); + accepted = e.isAccepted(); + } + + /* Remember the currently pressed buttons for the next time. + Ideally should only be needed for AMOTION_EVENT_ACTION_DOWN + and AMOTION_EVENT_ACTION_UP, but if some events get lost, we + have a chance to resynchronize here. */ + app._previousPressedButtons = pressedButtons; + + /* Remember also the current position. There's + AMotionEvent_getHistoricalX()/Y(), but those are coalesced + events between the previous and currently fired move events, + i.e. not the full delta. Documented here: + https://developer.android.com/reference/android/view/MotionEvent#batching + There's also AMOTION_EVENT_AXIS_RELATIVE_X/_Y, but based on + https://developer.android.com/reference/android/view/MotionEvent#AXIS_X + the coordinate system is different for each event type, and + the last thing I want to do is adding special handling for + things the damn platform API should be doing for me. */ + app._previousPointerPosition = position; + + return accepted; + } + + /* Like AMOTION_EVENT_ACTION_MOVE, but without anything pressed */ + case AMOTION_EVENT_ACTION_HOVER_MOVE: { + const Vector2 position{AMotionEvent_getX(event, 0), AMotionEvent_getY(event, 0)}; + const Vector2 relativePosition = + Math::isNan(app._previousPointerPosition).all() ? + Vector2{} : position - app._previousPointerPosition; + + /* Similarly as with AMOTION_EVENT_ACTION_MOVE, the damn thing + fires hover events with zero position delta when scrolling + the mouse wheel. Useless, filter those away. */ + bool accepted = false; + if(relativePosition != Vector2{}) { + PointerMoveEvent e{event, {}, {}, relativePosition}; + app.pointerMoveEvent(e); + accepted = e.isAccepted(); + } + + /* Reset the currently pressed buttons, since there should be + none if we're just hovering */ + app._previousPressedButtons = {}; + /* Remember the current position. See above for why + AMotionEvent_getHistoricalX()/Y() is useless. */ + app._previousPointerPosition = position; + + return accepted; } + + /** @todo there's AMOTION_EVENT_ACTION_HOVER_ENTER and + AMOTION_EVENT_ACTION_HOVER_EXIT, implement once other apps get + something similar */ + /** @todo AMOTION_EVENT_ACTION_SCROLL */ } /** @todo Implement also other input events */ @@ -324,6 +471,83 @@ void AndroidApplication::exec(android_app* state, Containers::PointeruserData = nullptr; } +void AndroidApplication::pointerPressEvent(PointerEvent& event) { + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_PUSH + MouseEvent mouseEvent{event._event}; + mousePressEvent(mouseEvent); + CORRADE_IGNORE_DEPRECATED_POP + #else + static_cast(event); + #endif +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH +void AndroidApplication::mousePressEvent(MouseEvent&) {} +CORRADE_IGNORE_DEPRECATED_POP +#endif + +void AndroidApplication::pointerReleaseEvent(PointerEvent& event) { + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_PUSH + MouseEvent mouseEvent{event._event}; + mouseReleaseEvent(mouseEvent); + CORRADE_IGNORE_DEPRECATED_POP + #else + static_cast(event); + #endif +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH +void AndroidApplication::mouseReleaseEvent(MouseEvent&) {} +CORRADE_IGNORE_DEPRECATED_POP +#endif + +void AndroidApplication::pointerMoveEvent(PointerMoveEvent& event) { + #ifdef MAGNUM_BUILD_DEPRECATED + const Vector2i roundedPosition{Math::round(event.position())}; + + /* 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 */ + /** @todo This codepath is never used as move events with pointer() being + set aren't emitted at all above. Keeping it here as that may change + with API 33+. */ + CORRADE_IGNORE_DEPRECATED_PUSH + if(event.pointer()) { + /* Android 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}; + event.pointers() >= *event.pointer() ? + mousePressEvent(mouseEvent) : mouseReleaseEvent(mouseEvent); + } else { + /* 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 - previousRoundedPosition}; + mouseMoveEvent(mouseEvent); + } + } + CORRADE_IGNORE_DEPRECATED_POP + #else + static_cast(event); + #endif +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH +void AndroidApplication::mouseMoveEvent(MouseMoveEvent&) {} +CORRADE_IGNORE_DEPRECATED_POP +#endif + template class BasicScreen; template class BasicScreenedApplication; diff --git a/src/Magnum/Platform/AndroidApplication.h b/src/Magnum/Platform/AndroidApplication.h index 6918b4a73..8cfd9bcfd 100644 --- a/src/Magnum/Platform/AndroidApplication.h +++ b/src/Magnum/Platform/AndroidApplication.h @@ -170,8 +170,25 @@ class AndroidApplication { class GLConfiguration; class ViewportEvent; class InputEvent; + class PointerEvent; + class PointerMoveEvent; + #ifdef MAGNUM_BUILD_DEPRECATED class MouseEvent; class MouseMoveEvent; + #endif + + /* 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; /** * @brief Execute the application @@ -383,31 +400,93 @@ class AndroidApplication { * @} */ - /** @{ @name Mouse handling */ + /** @{ @name Pointer handling */ 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. + * + * 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. * - * Called when mouse button is released. Default implementation does + * 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 /* Since 1.8.17, the original short-hand group closing doesn't work anymore. FFS. */ @@ -430,7 +509,10 @@ class AndroidApplication { EGLDisplay _display; EGLSurface _surface; EGLContext _glContext; - Vector2i _previousMouseMovePosition{-1}; + + Vector2 _previousPointerPosition{Constants::nan()}; + /* Contains just the Mouse* values */ + Pointers _previousPressedButtons; /* Has to be in an Optional because it gets explicitly destroyed before the GL context */ @@ -440,6 +522,63 @@ class AndroidApplication { CORRADE_ENUMSET_FRIEND_OPERATORS(Flags) }; +/** +@brief Pointer type +@m_since_latest + +@see @ref Pointers, @ref PointerEvent::pointer(), + @ref PointerMoveEvent::pointer(), @ref PointerMoveEvent::pointers() +*/ +enum class AndroidApplication::Pointer: UnsignedByte { + /** + * Unknown. Corresponds to `AMOTION_EVENT_TOOL_TYPE_UNKNOWN` and other + * types not listed below. + */ + Unknown = 1 << 0, + + /** + * Left mouse button. Corresponds to `AMOTION_EVENT_TOOL_TYPE_MOUSE` and + * `AMOTION_EVENT_BUTTON_PRIMARY`. + */ + MouseLeft = 1 << 1, + + /** + * Middle mouse button. Corresponds to `AMOTION_EVENT_TOOL_TYPE_MOUSE` and + * `AMOTION_EVENT_BUTTON_SECONDARY`. + */ + MouseMiddle = 1 << 2, + + /** + * Right mouse button. Corresponds to `AMOTION_EVENT_TOOL_TYPE_MOUSE` and + * `AMOTION_EVENT_BUTTON_TERTIARY`. + */ + MouseRight = 1 << 3, + + /** @todo AMOTION_EVENT_BUTTON_BACK, AMOTION_EVENT_BUTTON_FORWARD once it's + possible to verify they match MouseButton4 / MouseButton5 in + GlfwApplication and Sdl2Application */ + + /** Finger. Corresponds to `AMOTION_EVENT_TOOL_TYPE_FINGER`. */ + Finger = 1 << 4, + + /** @todo There's AMOTION_EVENT_TOOL_TYPE_PALM, but no corresponding + constant on the Java MotionEvent class, and all links to it broken. + Accidental omission? Some scrapped feature with leftover traces? */ + + /** Pen. Corresponds to `AMOTION_EVENT_TOOL_TYPE_STYLUS`. */ + Pen = 1 << 5, + + /** @todo There's AMOTION_EVENT_BUTTON_STYLUS_PRIMARY and + AMOTION_EVENT_BUTTON_STYLUS_SECONDARY, expose once similar constants + exist for EmscriptenApplication / Sdl3Application; implement chorded + behavior for those like w/ mouse buttons */ + + /** Eraser. Corresponds to `AMOTION_EVENT_TOOL_TYPE_ERASER`. */ + Eraser = 1 << 6 +}; + +CORRADE_ENUMSET_OPERATORS(AndroidApplication::Pointers) + /** @brief OpenGL context configuration @@ -691,8 +830,8 @@ class AndroidApplication::ViewportEvent { /** @brief Base for input events -@see @ref MouseEvent, @ref MouseMoveEvent, @ref mousePressEvent(), - @ref mouseReleaseEvent(), @ref mouseMoveEvent() +@see @ref PointerEvent, @ref PointerMoveEvent, @ref pointerPressEvent(), + @ref pointerReleaseEvent(), @ref pointerMoveEvent() */ class AndroidApplication::InputEvent { public: @@ -734,12 +873,60 @@ class AndroidApplication::InputEvent { bool _accepted; }; +/** +@brief Pointer event +@m_since_latest + +@see @ref PointerMoveEvent, @ref pointerPressEvent(), + @ref pointerReleaseEvent() +*/ +class AndroidApplication::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 + * + * May return fractional values if the touch hardware has sub-pixel + * precision. Use @ref Math::round() to snap them to the nearest window + * pixel. + */ + Vector2 position() const { + return {AMotionEvent_getX(_event, 0), + AMotionEvent_getY(_event, 0)}; + } + + private: + friend AndroidApplication; + + explicit PointerEvent(AInputEvent* event, Pointer pointer): InputEvent(event), _pointer{pointer} {} + + const Pointer _pointer; +}; + +#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 mousePressEvent(), @ref mouseReleaseEvent() */ -class AndroidApplication::MouseEvent: public InputEvent { +class CORRADE_DEPRECATED("use PointerEvent, pointerPressEvent() and pointerReleaseEvent() instead") AndroidApplication::MouseEvent: public InputEvent { friend AndroidApplication; public: @@ -779,13 +966,91 @@ class AndroidApplication::MouseEvent: public InputEvent { private: explicit MouseEvent(AInputEvent* event): InputEvent(event) {} }; +#endif +/** +@brief Pointer move event +@m_since_latest + +@see @ref PointerEvent, @ref pointerMoveEvent() +*/ +class AndroidApplication::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 + * + * May return fractional values if the touch hardware has sub-pixel + * precision. Use @ref Math::round() to snap them to the nearest window + * pixel. + */ + Vector2 position() const { + return {AMotionEvent_getX(_event, 0), + AMotionEvent_getY(_event, 0)}; + } + + /** + * @brief Position relative to the previous touch event + * + * May return fractional values if the touch hardware has sub-pixel + * precision. Use @ref Math::round() to snap them to the nearest window + * pixel. Unlike @ref Sdl2Application, Android APIs don't provide + * relative position directly, so this is calculated explicitly as a + * delta from previous move event position. + */ + Vector2 relativePosition() const { return _relativePosition; } + + private: + friend AndroidApplication; + + explicit PointerMoveEvent(AInputEvent* event, Containers::Optional pointer, Pointers pointers, const Vector2& relativePosition): InputEvent{event}, _pointer{pointer}, _pointers{pointers}, _relativePosition{relativePosition} {} + + const Containers::Optional _pointer; + const Pointers _pointers; + const Vector2 _relativePosition; +}; + +#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 mouseMoveEvent() */ -class AndroidApplication::MouseMoveEvent: public InputEvent { +class CORRADE_DEPRECATED("use PointerMoveEvent and pointerMoveEvent() instead") AndroidApplication::MouseMoveEvent: public InputEvent { friend AndroidApplication; public: @@ -843,7 +1108,10 @@ class AndroidApplication::MouseMoveEvent: public InputEvent { const Vector2i _relativePosition; }; +CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_ENUMSET_OPERATORS(AndroidApplication::MouseMoveEvent::Buttons) +CORRADE_IGNORE_DEPRECATED_POP +#endif /** @hideinitializer @brief Entry point for Android applications diff --git a/src/Magnum/Platform/Test/AndroidApplicationTest.cpp b/src/Magnum/Platform/Test/AndroidApplicationTest.cpp index aa86380ec..b6c320d26 100644 --- a/src/Magnum/Platform/Test/AndroidApplicationTest.cpp +++ b/src/Magnum/Platform/Test/AndroidApplicationTest.cpp @@ -31,9 +31,30 @@ namespace Magnum { namespace Platform { -/* Cannot be in an anonymous namespace as enumSetDebugOutput() below wouldn't - be able to pick it up */ -static Debug& operator<<(Debug& debug, Application::MouseMoveEvent::Button value) { +/* These cannot be in an anonymous namespace as enumSetDebugOutput() below + wouldn't be able to pick them up */ + +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(Unknown) + _c(MouseLeft) + _c(MouseMiddle) + _c(MouseRight) + _c(Finger) + _c(Pen) + _c(Eraser) + #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) { @@ -46,10 +67,26 @@ 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 { -Debug& operator<<(Debug& debug, Application::MouseEvent::Button value) { +Debug& operator<<(Debug& debug, Application::Pointers value) { + return Containers::enumSetDebugOutput(debug, value, "Pointers{}", { + Application::Pointer::Unknown, + Application::Pointer::MouseLeft, + Application::Pointer::MouseMiddle, + Application::Pointer::MouseRight, + Application::Pointer::Finger, + Application::Pointer::Pen, + Application::Pointer::Eraser + }); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH +CORRADE_UNUSED Debug& operator<<(Debug& debug, Application::MouseEvent::Button value) { debug << "Button" << Debug::nospace; switch(value) { @@ -64,13 +101,15 @@ 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, Application::MouseMoveEvent::Button::Right, }); } +CORRADE_IGNORE_DEPRECATED_POP +#endif struct AndroidApplicationTest: Platform::Application { explicit AndroidApplicationTest(const Arguments& arguments): Platform::Application{arguments} { @@ -88,17 +127,30 @@ struct AndroidApplicationTest: Platform::Application { Debug{} << "viewport:" << event.windowSize() << event.framebufferSize() << event.dpiScaling(); } + /* Set to 0 to test the deprecated mouse events instead */ + #if 1 + void pointerPressEvent(PointerEvent& event) override { + Debug{} << "pointer press:" << event.pointer() << Debug::packed << event.position(); + } + void pointerReleaseEvent(PointerEvent& event) override { + Debug{} << "pointer release:" << event.pointer() << Debug::packed << event.position(); + } + void pointerMoveEvent(PointerMoveEvent& event) override { + Debug{} << "pointer move:" << event.pointer() << event.pointers() << 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(); } - void mouseReleaseEvent(MouseEvent& event) override { Debug{} << "mouse release:" << event.button() << Debug::packed << event.position(); } - void mouseMoveEvent(MouseMoveEvent& event) override { Debug{} << "mouse move:" << event.buttons() << Debug::packed << event.position() << Debug::packed << event.relativePosition(); } + CORRADE_IGNORE_DEPRECATED_POP + #endif }; }}}}