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 }; }}}}