Browse Source

Platform: new TwoFingerGesture helper.

pull/651/head
Vladimír Vondruš 2 years ago
parent
commit
69d02ad59b
  1. 2
      doc/changelog.dox
  2. 6
      doc/platform.dox
  3. 49
      doc/snippets/Platform.cpp
  4. 4
      src/Magnum/Platform/AndroidApplication.h
  5. 1
      src/Magnum/Platform/CMakeLists.txt
  6. 4
      src/Magnum/Platform/EmscriptenApplication.h
  7. 361
      src/Magnum/Platform/Gesture.h
  8. 2
      src/Magnum/Platform/Platform.h
  9. 4
      src/Magnum/Platform/Sdl2Application.h
  10. 2
      src/Magnum/Platform/Test/CMakeLists.txt
  11. 444
      src/Magnum/Platform/Test/GestureTest.cpp

2
doc/changelog.dox

@ -352,6 +352,8 @@ See also:
touch-to-mouse event passthrough needs to be implemented, it works out of
the box with the new pointer events. The Android implementation is
building upon [mosra/magnum#527](https://github.com/mosra/magnum/pull/527).
There's also a new @ref Platform::TwoFingerGesture helper for recognition
of common two-finger gestures for zoom, rotation and pan.
@subsubsection changelog-latest-new-scenegraph SceneGraph library

6
doc/platform.dox

@ -90,6 +90,12 @@ In addition to the above generalized interface, there's
@relativeref{Platform::Sdl2Application,scrollEvent()} providing 2D scroll
events from a mouse wheel, trackball or touchpad.
See @ref Platform-Sdl2Application-touch,
@ref Platform-EmscriptenApplication-touch and
@ref Platform-AndroidApplication-touch for additional tookit-specific
information. A @ref Platform::TwoFingerGesture helper provides recognition of
common two-finger gestures for zoom, rotation and pan.
@subsection platform-windowed-key-events Attaching to keyboard and text input
The @relativeref{Platform::Sdl2Application,keyPressEvent()} and

49
doc/snippets/Platform.cpp

@ -31,6 +31,8 @@
#include <Corrade/Utility/Tweakable.h>
#endif
#include "Magnum/Platform/Gesture.h"
/* [windowed] */
#include <Magnum/GL/DefaultFramebuffer.h>
#include <Magnum/GL/Renderer.h>
@ -346,3 +348,50 @@ void MyApplication::tickEvent() {
}
#endif
namespace K {
struct MyApplication: Platform::Application {
void pointerPressEvent(PointerEvent& event) override;
void pointerReleaseEvent(PointerEvent& event) override;
void pointerMoveEvent(PointerMoveEvent& event) override;
void translateSomething(const Vector2&) {}
void rotateSomething(const Complex&) {}
void scaleSomething(Float) {}
Platform::TwoFingerGesture _gesture;
};
/* [TwoFingerGesture] */
void MyApplication::pointerPressEvent(PointerEvent& event) {
_gesture.pressEvent(event);
DOXYGEN_ELLIPSIS()
}
void MyApplication::pointerReleaseEvent(PointerEvent& event) {
_gesture.releaseEvent(event);
DOXYGEN_ELLIPSIS()
}
void MyApplication::pointerMoveEvent(PointerMoveEvent& event) {
_gesture.moveEvent(event);
/* A gesture is recognized, perform appropriate action */
if(_gesture) {
translateSomething(_gesture.relativeTranslation());
rotateSomething(_gesture.relativeRotation());
scaleSomething(_gesture.relativeScaling());
event.setAccepted();
redraw();
return;
}
DOXYGEN_ELLIPSIS()
}
/* [TwoFingerGesture] */
}

4
src/Magnum/Platform/AndroidApplication.h

@ -194,7 +194,9 @@ and @ref PointerEventSource::Pen the ID is a constant, as there's always just
a single mouse cursor or a pen stylus.
See also @ref platform-windowed-pointer-events for general information about
handling pointer input in a portable way.
handling pointer input in a portable way. There's also a
@ref Platform::TwoFingerGesture helper for recognition of common two-finger
gestures for zoom, rotation and pan.
*/
class AndroidApplication {
public:

1
src/Magnum/Platform/CMakeLists.txt

@ -33,6 +33,7 @@ set(CMAKE_FOLDER "Magnum/Platform")
set(MagnumPlatform_SRCS )
set(MagnumPlatform_HEADERS
Gesture.h
Platform.h
Screen.h
ScreenedApplication.h

4
src/Magnum/Platform/EmscriptenApplication.h

@ -219,7 +219,9 @@ only for the period given finger is pressed. For @ref PointerEventSource::Mouse
the ID is a constant, as there's always just a single mouse cursor.
See also @ref platform-windowed-pointer-events for general information about
handling pointer input in a portable way.
handling pointer input in a portable way. There's also a
@ref Platform::TwoFingerGesture helper for recognition of common two-finger
gestures for zoom, rotation and pan.
@section Platform-EmscriptenApplication-browser Browser-specific behavior

361
src/Magnum/Platform/Gesture.h

@ -0,0 +1,361 @@
#ifndef Magnum_Platform_Gesture_h
#define Magnum_Platform_Gesture_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023, 2024
Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
/** @file
* @brief Class @ref Magnum::Platform::TwoFingerGesture
* @m_since_latest
*/
#include "Magnum/Magnum.h"
#include "Magnum/Math/Functions.h" /* Math::sqrt() */
#include "Magnum/Math/Complex.h"
namespace Magnum { namespace Platform {
/**
@brief Two-finger gesture recognition
@m_since_latest
Tracks position of a primary finger and an arbitrary secondary finger based on
pointer events passed to @ref pressEvent(), @ref releaseEvent() and
@ref moveEvent(). Once two fingers are pressed, the instance is contextually
convertible to @cpp true @ce, and @ref position(), @ref direction(),
@ref relativeTranslation(), @ref relativeRotation() and @ref relativeScaling()
contain gesture properties. Example usage:
@snippet Platform.cpp TwoFingerGesture
The interface is designed primarily for @ref Sdl2Application "*Application"
subclasses and their @relativeref{Sdl2Application,PointerEvent} and
@relativeref{Sdl2Application,PointerMoveEvent} instances, but works also with
any other types that provide appropriate
@relativeref{Sdl2Application::PointerEvent,source()},
@relativeref{Sdl2Application::PointerEvent,isPrimary()},
@relativeref{Sdl2Application::PointerEvent,id()} and @relativeref{Sdl2Application::PointerEvent,position()} members.
@experimental
*/
class TwoFingerGesture {
public:
/**
* @brief Handle a press event
*
* Accepts a pointer event instance such as the one coming from
* @ref Platform::Sdl2Application::pointerPressEvent(). If the event
* comes from a primary finger, replaces the internal state with it,
* waiting for the secondary finger press to happen. If the event comes
* from a secondary finger, it's used only a primary finger is known,
* there's no known secondary finger ID yet. or the ID matches the
* known secondary finger ID. Events that don't come from a touch
* source are ignored. Returns @cpp true @ce if the event was used,
* @cpp false @ce if not.
*
* The function doesn't modify the event in any way. If needed, it's up
* to the caller to call
* @relativeref{Sdl2Application::PointerEvent,setAccepted()}.
*/
template<class PointerEvent> bool pressEvent(const PointerEvent& event);
/**
* @brief Handle a release event
*
* Accepts a pointer event instance such as the one coming from
* @ref Platform::Sdl2Application::pointerReleaseEvent(). If the
* release comes from a primary finger whose ID is known, resets the
* state for both the primary and secondary touch, waiting for a
* primary finger press to happen again. Otherwise, if the release
* comes from a secondary finger whose ID is known. resets just the
* secondary finger state, waiting for a different secondary finger
* press to happen. Events that don't come from a touch source are
* ignored. Returns @cpp true @ce if the event was used, @cpp false @ce
* if not.
*
* The function doesn't modify the event in any way. If needed, it's up
* to the caller to call
* @relativeref{Sdl2Application::PointerEvent,setAccepted()}.
*/
template<class PointerEvent> bool releaseEvent(const PointerEvent& event);
/**
* @brief Handle a move event
*
* Accepts a pointer move event instance such as the one coming from
* @ref Platform::Sdl2Application::pointerMoveEvent(). If the move
* comes from a primary finger whose ID is known or from a secondary
* finger whose ID is known, updates given finger state. Events that
* don't come from a touch source are ignored. Returns @cpp true @ce if
* the event was used, @cpp false @ce if not.
*
* The function doesn't modify the event in any way. If needed, it's up
* to the caller to call
* @relativeref{Sdl2Application::PointerEvent,setAccepted()}.
*/
template<class PointerMoveEvent> bool moveEvent(const PointerMoveEvent& event);
/**
* @brief Count of known pressed fingers
*
* Is @cpp 0 @ce if @ref pressEvent() wasn't called yet or
* @ref releaseEvent() happened for the primary finger, @cpp 1 @ce
* if only the primary finger is pressed or a secondary finger was
* released and @cpp 2 @ce if both the primary and a secondary finger
* is currently pressed.
* @see @ref isGesture()
*/
UnsignedInt fingerCount() const {
return (_primaryTouchId != NoTouchId) +
(_secondaryTouchId != NoTouchId);
}
/**
* @brief Whether the internal state represents a two-finger gesture
*
* Returns @cpp true @ce if both the primary and a secondary finger
* are pressed, @cpp false @ce otherwise. Same as @ref operator bool().
*/
bool isGesture() const {
return fingerCount() == 2;
}
/**
* @brief Whether the internal state represents a two-finger gesture
*
* Returns @cpp true @ce if both the primary and a secondary finger
* are pressed, @cpp false @ce otherwise. Same as @ref operator bool().
*/
explicit operator bool() const {
return fingerCount() == 2;
}
/**
* @brief Centroid between the two known pressed finger positions
*
* If only one or no fingers are pressed --- i.e., @ref isGesture() is
* @cpp false @ce --- returns a NaN vector.
* @see @ref direction(), @ref relativeTranslation(),
* @ref Math::isNan()
*/
Vector2 position() const {
return (_primaryTouchPosition + _secondaryTouchPosition)*0.5f;
}
/**
* @brief Direction from the center to the primary finger position
*
* Negate the return value to get direction from the center to the
* secondary finger. If only one or no fingers are pressed --- i.e.,
* @ref isGesture() is @cpp false @ce --- returns a NaN vector.
* @see @ref position(), @ref relativeRotation(),
* @ref relativeScaling(), @ref Math::isNan()
*/
Vector2 direction() const {
return (_primaryTouchPosition - _secondaryTouchPosition)*0.5f;
}
/**
* @brief Translation of the centroid relative to the previous finger positions
*
* If there was no movement since the press, returns a zero vector. If
* only one or no fingers are pressed --- i.e., @ref isGesture() is
* @cpp false @ce --- returns a NaN vector.
* @see @ref position(), @ref relativeRotation(),
* @ref relativeScaling(), @ref Math::isNan()
*/
Vector2 relativeTranslation() const {
return (_primaryTouchPosition - _primaryPreviousTouchPosition +
_secondaryTouchPosition - _secondaryPreviousTouchPosition)*0.5f;
}
/**
* @brief Rotation relative to the previous finger positions
*
* Note that given the event coordinates are in a Y down coordinate,
* positive rotation angle is clockwise. If there was no movement since
* the press, returns an identity rotation. If only one or no fingers
* are pressed --- i.e., @ref isGesture() is @cpp false @ce --- returns
* a complex NaN.
*
* The function returns a @relativeref{Math,Complex} instead of an
* angle as the angle would likely be converted back to a rotation
* representation anyway. Use @ref Complex::toMatrix(),
* @ref Complex::transformVector() or @ref Complex::angle() if a
* different representation is needed.
* @see @ref direction(), @ref relativeTranslation(),
* @ref relativeScaling(), @ref Math::isNan()
*/
Complex relativeRotation() const {
/* prev * rot = cur
prev^-1 * prev * rot = prev^-1 * cur
rot = prev^-1 * cur */
return Complex{(_primaryPreviousTouchPosition - _secondaryPreviousTouchPosition).normalized()}.inverted()*Complex{(_primaryTouchPosition - _secondaryTouchPosition).normalized()};
}
/**
* @brief Scaling relative to the previous finger positions
*
* The returned value is always positive. Values less than
* @cpp 1.0f @ce are when the points are getting closer, values larger
* than @cpp 1.0f @ce are when the points are getting further apart. If
* there was no movement since the press, returns @cpp 1.0f @ce. If
* only one or no fingers are pressed --- i.e., @ref isGesture() is
* @cpp false @ce --- returns a NaN.
* @see @ref direction(), @ref relativeTranslation(),
* @ref relativeRotation(), @ref Math::isNan()
*/
Float relativeScaling() const {
return Math::sqrt(
(_secondaryTouchPosition - _primaryTouchPosition).dot()/
(_secondaryPreviousTouchPosition - _primaryPreviousTouchPosition).dot());
}
private:
struct Touch {
Long id;
Vector2 position;
};
/* ~Long{} is -1, which may collide with actual pointer IDs (SDL uses
it to denote a mouse, for example) */
constexpr static Long NoTouchId = 1ll << 63;
Long _primaryTouchId = NoTouchId;
Vector2 _primaryTouchPosition{Constants::nan()},
_primaryPreviousTouchPosition{Constants::nan()};
Long _secondaryTouchId = NoTouchId;
Vector2 _secondaryTouchPosition{Constants::nan()},
_secondaryPreviousTouchPosition{Constants::nan()};
};
namespace Implementation {
/* Not all Application::PointerEventSource enums have a Touch member (e.g.
GlfwApplication doesn't), this makes it return false for such types, instead
of failing to compile */
template<class PointerEventSource> constexpr bool isTouchPointerEventSource(PointerEventSource p, decltype(PointerEventSource::Touch)* = nullptr) {
return p == PointerEventSource::Touch;
}
constexpr bool isTouchPointerEventSource(...) { return false; }
}
template<class PointerEvent> bool TwoFingerGesture::pressEvent(const PointerEvent& event) {
/* Filter away non-touch sources. Other than that just assume it's a finger
or something equivalent, capable of multi-touch -- i.e., don't even
check pointers(). */
if(!Implementation::isTouchPointerEventSource(event.source()))
return false;
/* If this is the primary finger, unconditionally replace the primary touch
with it, and reset everything else */
if(event.isPrimary()) {
_primaryTouchId = event.id();
_primaryTouchPosition = event.position();
_primaryPreviousTouchPosition = event.position();
_secondaryTouchId = NoTouchId;
_secondaryTouchPosition = Vector2{Constants::nan()};
_secondaryPreviousTouchPosition = Vector2{Constants::nan()};
return true;
}
/* If this is a secondary finger and a primary finger is already known,
remember it either if it has a matching ID or there's no recorded second
touch yet */
if(_primaryTouchId != NoTouchId && (_secondaryTouchId == NoTouchId || event.id() == _secondaryTouchId)) {
_secondaryTouchId = event.id();
_secondaryTouchPosition = event.position();
_secondaryPreviousTouchPosition = event.position();
return true;
}
/* If this is a secondary finger and a primary finger is not known yet, or
if this is a secondary finger with a different ID, do nothing */
return false;
}
template<class PointerEvent> bool TwoFingerGesture::releaseEvent(const PointerEvent& event) {
/* Filter away non-touch sources. Other than that just assume it's a finger
or something equivalent, capable of multi-touch -- i.e., don't even
check pointers(). */
if(!Implementation::isTouchPointerEventSource(event.source()))
return false;
/* If the primary finger is lifted, reset everything and wait for the next
time a primary finger is pressed again */
if(event.isPrimary() && event.id() == _primaryTouchId) {
_primaryTouchId = NoTouchId;
_primaryTouchPosition = Vector2{Constants::nan()};
_primaryPreviousTouchPosition = Vector2{Constants::nan()};
_secondaryTouchId = NoTouchId;
_secondaryTouchPosition = Vector2{Constants::nan()};
_secondaryPreviousTouchPosition = Vector2{Constants::nan()};
return true;
}
/* If this is a secondary finger, reset just that one, and wait for another
secondary finger press to take up its place */
if(!event.isPrimary() && event.id() == _secondaryTouchId) {
_secondaryTouchId = NoTouchId;
_secondaryTouchPosition = Vector2{Constants::nan()};
_secondaryPreviousTouchPosition = Vector2{Constants::nan()};
return true;
}
/* If the IDs don't match or their primary/secondary state doesn't match,
do nothing */
return false;
}
template<class PointerMoveEvent> bool TwoFingerGesture::moveEvent(const PointerMoveEvent& event) {
/* Filter away non-touch sources. Other than that just assume it's a finger
or something equivalent, capable of multi-touch -- i.e., don't even
check pointers(). */
if(!Implementation::isTouchPointerEventSource(event.source()))
return false;
/* If the event matches any of the recorded IDs, update the corresponding
values */
if(event.isPrimary() && event.id() == _primaryTouchId) {
_primaryPreviousTouchPosition = _primaryTouchPosition;
_primaryTouchPosition = event.position();
return true;
}
if(!event.isPrimary() && event.id() == _secondaryTouchId) {
_secondaryPreviousTouchPosition = _secondaryTouchPosition;
_secondaryTouchPosition = event.position();
return true;
}
/* If the IDs don't match or their primary/secondary state doesn't match,
do nothing */
return false;
}
}}
#endif

2
src/Magnum/Platform/Platform.h

@ -45,6 +45,8 @@ namespace Implementation {
}
#endif
class TwoFingerGesture;
#ifdef MAGNUM_TARGET_GL
class GLContext;
#endif

4
src/Magnum/Platform/Sdl2Application.h

@ -331,7 +331,9 @@ lifted, and then never again. For @ref PointerEventSource::Mouse the ID is a
constant, as there's always just a single mouse cursor.
See also @ref platform-windowed-pointer-events for general information about
handling pointer input in a portable way.
handling pointer input in a portable way. There's also a
@ref Platform::TwoFingerGesture helper for recognition of common two-finger
gestures for zoom, rotation and pan.
@section Platform-Sdl2Application-platform-specific Platform-specific behavior

2
src/Magnum/Platform/Test/CMakeLists.txt

@ -31,6 +31,8 @@ set(CMAKE_FOLDER "Magnum/Platform/Test")
find_package(Corrade REQUIRED Main)
corrade_add_test(PlatformGestureTest GestureTest.cpp LIBRARIES Magnum)
# Icons for SDL/GLFW
if(NOT CORRADE_TARGET_EMSCRIPTEN AND (MAGNUM_WITH_SDL2APPLICATION OR MAGNUM_WITH_GLFWAPPLICATION))
corrade_add_resource(Platform_RESOURCES resources.conf)

444
src/Magnum/Platform/Test/GestureTest.cpp

@ -0,0 +1,444 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021, 2022, 2023, 2024
Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include <Corrade/TestSuite/Tester.h>
#include "Magnum/Platform/Gesture.h"
namespace Magnum { namespace Platform { namespace Test { namespace {
struct GestureTest: TestSuite::Tester {
explicit GestureTest();
void twoFinger();
void twoFingerPressPrimaryAgain();
void twoFingerPressPrimaryAfterSecondary();
void twoFingerSecondaryWithoutPrimary();
void twoFingerUnknownSecondary();
void twoFingerReleasePrimary();
void twoFingerReleaseSecondary();
template<class T> void twoFingerNonTouchEvents();
};
using namespace Math::Literals;
enum class PointerEventSource {
Mouse = -1337,
Touch = 12
};
class PointerEvent {
public:
explicit PointerEvent(bool primary, Long id, const Vector2& position): _primary{primary}, _id{id}, _position{position} {}
PointerEventSource source() const { return PointerEventSource::Touch; }
bool isPrimary() const { return _primary; }
Long id() const { return _id; }
Vector2 position() const { return _position; }
private:
bool _primary;
Long _id;
Vector2 _position;
};
enum class PointerEventSourceMouseOnly {
Mouse = -1337
};
GestureTest::GestureTest() {
addTests({&GestureTest::twoFinger,
&GestureTest::twoFingerPressPrimaryAgain,
&GestureTest::twoFingerPressPrimaryAfterSecondary,
&GestureTest::twoFingerSecondaryWithoutPrimary,
&GestureTest::twoFingerUnknownSecondary,
&GestureTest::twoFingerReleasePrimary,
&GestureTest::twoFingerReleaseSecondary,
&GestureTest::twoFingerNonTouchEvents<PointerEventSource>,
&GestureTest::twoFingerNonTouchEvents<PointerEventSourceMouseOnly>});
}
void GestureTest::twoFinger() {
/* Initially there's nothing */
TwoFingerGesture g;
CORRADE_COMPARE(g.fingerCount(), 0);
CORRADE_VERIFY(!g.isGesture());
CORRADE_VERIFY(!g);
CORRADE_COMPARE(Math::isNan(g.position()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.direction()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.relativeTranslation()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(Vector2{g.relativeRotation()}), BitVector2{3});
CORRADE_VERIFY(Math::isNan(g.relativeScaling()));
/* After pressing just the primary there's no gesture yet. Using large IDs
to verify they're stored as full 64-bit numbers */
CORRADE_VERIFY(g.pressEvent(PointerEvent{true, 1ll << 37, {10.0f, 20.0f}}));
CORRADE_COMPARE(g.fingerCount(), 1);
CORRADE_VERIFY(!g.isGesture());
CORRADE_VERIFY(!g);
CORRADE_COMPARE(Math::isNan(g.position()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.direction()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.relativeTranslation()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(Vector2{g.relativeRotation()}), BitVector2{3});
CORRADE_VERIFY(Math::isNan(g.relativeScaling()));
/* With a secondary press there's a gesture. We don't check the ID in this
case, just the primary/secondary distinction, so it's fine if both are
the same. */
CORRADE_VERIFY(g.pressEvent(PointerEvent{false, 1ll << 37, {20.0f, 10.0f}}));
CORRADE_COMPARE(g.fingerCount(), 2);
CORRADE_VERIFY(g.isGesture());
CORRADE_VERIFY(g);
CORRADE_COMPARE(g.position(), Vector2{15.0f});
CORRADE_COMPARE(g.direction(), (Vector2{-5.0f, 5.0f}));
/* Positive direction should point to the primary event, negative to the
secondary */
CORRADE_COMPARE(g.position() + g.direction(), (Vector2{10.0f, 20.0f}));
CORRADE_COMPARE(g.position() - g.direction(), (Vector2{20.0f, 10.0f}));
/* No movement yet, so default values */
CORRADE_COMPARE(g.relativeTranslation(), Vector2{});
CORRADE_COMPARE(g.relativeRotation(), Complex{});
CORRADE_COMPARE(g.relativeScaling(), 1.0f);
/* Move primary finger to the other side of the secondary, forming a
translation and a 180° rotation */
CORRADE_VERIFY(g.moveEvent(PointerEvent{true, 1ll << 37, {30.0f, 0.0f}}));
CORRADE_COMPARE(g.fingerCount(), 2);
CORRADE_VERIFY(g.isGesture());
CORRADE_VERIFY(g);
CORRADE_COMPARE(g.position(), (Vector2{25.0f, 5.0f}));
CORRADE_COMPARE(g.direction(), (Vector2{5.0f, -5.0f}));
CORRADE_COMPARE(g.relativeTranslation(), (Vector2{10.0f, -10.0f}));
CORRADE_COMPARE(g.relativeRotation(), Complex::rotation(180.0_degf));
CORRADE_COMPARE(g.relativeScaling(), 1.0f);
/* Move secondary finger slightly to the right, forming a counterclockwise
rotation, thus less than 180° */
CORRADE_VERIFY(g.moveEvent(PointerEvent{false, 1ll << 37, {25.0f, 10.0f}}));
CORRADE_COMPARE(g.fingerCount(), 2);
CORRADE_VERIFY(g.isGesture());
CORRADE_VERIFY(g);
CORRADE_COMPARE(g.position(), (Vector2{27.5f, 5.0f}));
CORRADE_COMPARE(g.direction(), (Vector2{2.5f, -5.0f}));
CORRADE_COMPARE(g.relativeTranslation(), (Vector2{12.5f, -10.0f}));
CORRADE_COMPARE(g.relativeRotation(), Complex::rotation(161.565001424104_degf));
CORRADE_COMPARE(g.relativeScaling(), 0.790569f);
/* Moving primary and secondary fingers back results in the same absolute
values as initially, and relative values inverted compared to above */
CORRADE_VERIFY(g.moveEvent(PointerEvent{true, 1ll << 37, {10.0f, 20.0f}}));
CORRADE_VERIFY(g.moveEvent(PointerEvent{false, 1ll << 37, {20.0f, 10.0f}}));
CORRADE_COMPARE(g.fingerCount(), 2);
CORRADE_VERIFY(g.isGesture());
CORRADE_VERIFY(g);
CORRADE_COMPARE(g.position(), Vector2{15.0f});
CORRADE_COMPARE(g.direction(), (Vector2{-5.0f, 5.0f}));
CORRADE_COMPARE(g.relativeTranslation(), (Vector2{-12.5f, 10.0f}));
CORRADE_COMPARE(g.relativeRotation(), Complex::rotation(-161.565001424104_degf));
CORRADE_COMPARE(g.relativeScaling(), 1.0f/0.790569f);
}
void GestureTest::twoFingerPressPrimaryAgain() {
TwoFingerGesture g;
CORRADE_VERIFY(g.pressEvent(PointerEvent{true, 37, {10.0f, 20.0f}}));
CORRADE_COMPARE(g.fingerCount(), 1);
CORRADE_VERIFY(!g.isGesture());
CORRADE_VERIFY(!g);
CORRADE_COMPARE(Math::isNan(g.position()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.direction()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.relativeTranslation()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(Vector2{g.relativeRotation()}), BitVector2{3});
CORRADE_VERIFY(Math::isNan(g.relativeScaling()));
/* Another primary press replaces the original */
CORRADE_VERIFY(g.pressEvent(PointerEvent{true, 76, {10.0f, 20.0f}}));
CORRADE_COMPARE(g.fingerCount(), 1);
CORRADE_VERIFY(!g.isGesture());
CORRADE_VERIFY(!g);
CORRADE_COMPARE(Math::isNan(g.position()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.direction()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.relativeTranslation()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(Vector2{g.relativeRotation()}), BitVector2{3});
CORRADE_VERIFY(Math::isNan(g.relativeScaling()));
}
void GestureTest::twoFingerPressPrimaryAfterSecondary() {
TwoFingerGesture g;
CORRADE_VERIFY(g.pressEvent(PointerEvent{true, 37, {10.0f, 20.0f}}));
CORRADE_VERIFY(g.pressEvent(PointerEvent{false, 26, {20.0f, 10.0f}}));
CORRADE_COMPARE(g.fingerCount(), 2);
CORRADE_VERIFY(g.isGesture());
CORRADE_VERIFY(g);
CORRADE_COMPARE(g.position(), Vector2{15.0f});
CORRADE_COMPARE(g.direction(), (Vector2{-5.0f, 5.0f}));
CORRADE_COMPARE(g.relativeTranslation(), Vector2{});
CORRADE_COMPARE(g.relativeRotation(), Complex{});
CORRADE_COMPARE(g.relativeScaling(), 1.0f);
/* Another primary press replaces both */
CORRADE_VERIFY(g.pressEvent(PointerEvent{true, 76, {10.0f, 20.0f}}));
CORRADE_COMPARE(g.fingerCount(), 1);
CORRADE_VERIFY(!g.isGesture());
CORRADE_VERIFY(!g);
CORRADE_COMPARE(Math::isNan(g.position()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.direction()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.relativeTranslation()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(Vector2{g.relativeRotation()}), BitVector2{3});
CORRADE_VERIFY(Math::isNan(g.relativeScaling()));
}
void GestureTest::twoFingerSecondaryWithoutPrimary() {
TwoFingerGesture g;
/* Pressing a secondary pointer without a primary being recorded first
does nothing, neither does move or release */
CORRADE_VERIFY(!g.pressEvent(PointerEvent{false, 26, {20.0f, 10.0f}}));
CORRADE_COMPARE(g.fingerCount(), 0);
CORRADE_VERIFY(!g.isGesture());
CORRADE_VERIFY(!g);
CORRADE_COMPARE(Math::isNan(g.position()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.direction()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.relativeTranslation()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(Vector2{g.relativeRotation()}), BitVector2{3});
CORRADE_VERIFY(Math::isNan(g.relativeScaling()));
CORRADE_VERIFY(!g.moveEvent(PointerEvent{false, 26, {20.0f, 10.0f}}));
CORRADE_COMPARE(g.fingerCount(), 0);
CORRADE_VERIFY(!g.isGesture());
CORRADE_VERIFY(!g);
CORRADE_COMPARE(Math::isNan(g.position()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.direction()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.relativeTranslation()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(Vector2{g.relativeRotation()}), BitVector2{3});
CORRADE_VERIFY(Math::isNan(g.relativeScaling()));
CORRADE_VERIFY(!g.releaseEvent(PointerEvent{false, 26, {20.0f, 10.0f}}));
CORRADE_COMPARE(g.fingerCount(), 0);
CORRADE_VERIFY(!g.isGesture());
CORRADE_VERIFY(!g);
CORRADE_COMPARE(Math::isNan(g.position()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.direction()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.relativeTranslation()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(Vector2{g.relativeRotation()}), BitVector2{3});
CORRADE_VERIFY(Math::isNan(g.relativeScaling()));
}
void GestureTest::twoFingerUnknownSecondary() {
TwoFingerGesture g;
/* Using large IDs to verify they're stored as full 64-bit numbers */
CORRADE_VERIFY(g.pressEvent(PointerEvent{true, 1ll << 39, {10.0f, 20.0f}}));
CORRADE_VERIFY(g.pressEvent(PointerEvent{false, 1ll << 37, {20.0f, 10.0f}}));
CORRADE_COMPARE(g.fingerCount(), 2);
CORRADE_VERIFY(g.isGesture());
CORRADE_VERIFY(g);
CORRADE_COMPARE(g.position(), Vector2{15.0f});
CORRADE_COMPARE(g.direction(), (Vector2{-5.0f, 5.0f}));
CORRADE_COMPARE(g.relativeTranslation(), Vector2{});
CORRADE_COMPARE(g.relativeRotation(), Complex{});
CORRADE_COMPARE(g.relativeScaling(), 1.0f);
/* Neither of these should affect the internal state in any way as it has a
different ID than the first recorded secondary press */
CORRADE_VERIFY(!g.pressEvent(PointerEvent{false, 1ll << 39, {0.0f, 0.0f}}));
CORRADE_COMPARE(g.fingerCount(), 2);
CORRADE_VERIFY(g.isGesture());
CORRADE_VERIFY(g);
CORRADE_COMPARE(g.position(), Vector2{15.0f});
CORRADE_COMPARE(g.direction(), (Vector2{-5.0f, 5.0f}));
CORRADE_COMPARE(g.relativeTranslation(), Vector2{});
CORRADE_COMPARE(g.relativeRotation(), Complex{});
CORRADE_COMPARE(g.relativeScaling(), 1.0f);
CORRADE_VERIFY(!g.moveEvent(PointerEvent{false, 1ll << 39, {0.0f, 0.0f}}));
CORRADE_COMPARE(g.fingerCount(), 2);
CORRADE_VERIFY(g.isGesture());
CORRADE_VERIFY(g);
CORRADE_COMPARE(g.position(), Vector2{15.0f});
CORRADE_COMPARE(g.direction(), (Vector2{-5.0f, 5.0f}));
CORRADE_COMPARE(g.relativeTranslation(), Vector2{});
CORRADE_COMPARE(g.relativeRotation(), Complex{});
CORRADE_COMPARE(g.relativeScaling(), 1.0f);
CORRADE_VERIFY(!g.releaseEvent(PointerEvent{false, 1ll << 39, {0.0f, 0.0f}}));
CORRADE_COMPARE(g.fingerCount(), 2);
CORRADE_VERIFY(g.isGesture());
CORRADE_VERIFY(g);
CORRADE_COMPARE(g.position(), Vector2{15.0f});
CORRADE_COMPARE(g.direction(), (Vector2{-5.0f, 5.0f}));
CORRADE_COMPARE(g.relativeTranslation(), Vector2{});
CORRADE_COMPARE(g.relativeRotation(), Complex{});
CORRADE_COMPARE(g.relativeScaling(), 1.0f);
}
void GestureTest::twoFingerReleasePrimary() {
TwoFingerGesture g;
/* Using large IDs to verify they're stored as full 64-bit numbers */
CORRADE_VERIFY(g.pressEvent(PointerEvent{true, 1ll << 37, {10.0f, 20.0f}}));
CORRADE_VERIFY(g.pressEvent(PointerEvent{false, 1ll << 26, {20.0f, 10.0f}}));
CORRADE_COMPARE(g.fingerCount(), 2);
CORRADE_VERIFY(g.isGesture());
CORRADE_VERIFY(g);
CORRADE_COMPARE(g.position(), Vector2{15.0f});
CORRADE_COMPARE(g.direction(), (Vector2{-5.0f, 5.0f}));
CORRADE_COMPARE(g.relativeTranslation(), Vector2{});
CORRADE_COMPARE(g.relativeRotation(), Complex{});
CORRADE_COMPARE(g.relativeScaling(), 1.0f);
/* Releasing a primary pointer with a different ID shouldn't affect
anything */
CORRADE_VERIFY(!g.releaseEvent(PointerEvent{true, 1ll << 26, {10.0f, 20.0f}}));
CORRADE_COMPARE(g.fingerCount(), 2);
CORRADE_VERIFY(g.isGesture());
CORRADE_VERIFY(g);
CORRADE_COMPARE(g.position(), Vector2{15.0f});
CORRADE_COMPARE(g.direction(), (Vector2{-5.0f, 5.0f}));
CORRADE_COMPARE(g.relativeTranslation(), Vector2{});
CORRADE_COMPARE(g.relativeRotation(), Complex{});
CORRADE_COMPARE(g.relativeScaling(), 1.0f);
/* Releasing the recorded primary pointer resets everything, it'll wait for
a new primary touch */
CORRADE_VERIFY(g.releaseEvent(PointerEvent{true, 1ll << 37, {10.0f, 20.0f}}));
CORRADE_COMPARE(g.fingerCount(), 0);
CORRADE_VERIFY(!g.isGesture());
CORRADE_VERIFY(!g);
CORRADE_COMPARE(Math::isNan(g.position()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.direction()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.relativeTranslation()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(Vector2{g.relativeRotation()}), BitVector2{3});
CORRADE_VERIFY(Math::isNan(g.relativeScaling()));
}
void GestureTest::twoFingerReleaseSecondary() {
TwoFingerGesture g;
/* Using large IDs to verify they're stored as full 64-bit numbers */
CORRADE_VERIFY(g.pressEvent(PointerEvent{true, 1ll << 37, {10.0f, 20.0f}}));
CORRADE_VERIFY(g.pressEvent(PointerEvent{false, 1ll << 26, {20.0f, 10.0f}}));
CORRADE_COMPARE(g.fingerCount(), 2);
CORRADE_VERIFY(g.isGesture());
CORRADE_VERIFY(g);
CORRADE_COMPARE(g.position(), Vector2{15.0f});
CORRADE_COMPARE(g.direction(), (Vector2{-5.0f, 5.0f}));
CORRADE_COMPARE(g.relativeTranslation(), Vector2{});
CORRADE_COMPARE(g.relativeRotation(), Complex{});
CORRADE_COMPARE(g.relativeScaling(), 1.0f);
/* Releasing a secondary pointer with a different ID shouldn't affect
anything */
CORRADE_VERIFY(!g.releaseEvent(PointerEvent{false, 1ll << 37, {10.0f, 20.0f}}));
CORRADE_COMPARE(g.fingerCount(), 2);
CORRADE_VERIFY(g.isGesture());
CORRADE_VERIFY(g);
CORRADE_COMPARE(g.position(), Vector2{15.0f});
CORRADE_COMPARE(g.direction(), (Vector2{-5.0f, 5.0f}));
CORRADE_COMPARE(g.relativeTranslation(), Vector2{});
CORRADE_COMPARE(g.relativeRotation(), Complex{});
CORRADE_COMPARE(g.relativeScaling(), 1.0f);
/* Releasing the recorded secondary pointer resets just the secondary
pointer */
CORRADE_VERIFY(g.releaseEvent(PointerEvent{false, 1ll << 26, {10.0f, 20.0f}}));
CORRADE_COMPARE(g.fingerCount(), 1);
CORRADE_VERIFY(!g.isGesture());
CORRADE_VERIFY(!g);
CORRADE_COMPARE(Math::isNan(g.position()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.direction()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.relativeTranslation()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(Vector2{g.relativeRotation()}), BitVector2{3});
CORRADE_VERIFY(Math::isNan(g.relativeScaling()));
/* Press another secondary pointer (though with the same ID), but at other
side of the primary */
CORRADE_VERIFY(g.pressEvent(PointerEvent{false, 1ll << 26, {0.0f, 30.0f}}));
CORRADE_COMPARE(g.fingerCount(), 2);
CORRADE_VERIFY(g.isGesture());
CORRADE_VERIFY(g);
CORRADE_COMPARE(g.position(), (Vector2{5.0f, 25.0f}));
CORRADE_COMPARE(g.direction(), (Vector2{5.0f, -5.0f}));
/* Positive direction should point to the primary event, negative to the
secondary */
CORRADE_COMPARE(g.position() + g.direction(), (Vector2{10.0f, 20.0f}));
CORRADE_COMPARE(g.position() - g.direction(), (Vector2{0.0f, 30.0f}));
/* The relative values shouldn't take the previous press into account,
should be identities. */
CORRADE_COMPARE(g.relativeTranslation(), Vector2{});
CORRADE_COMPARE(g.relativeRotation(), Complex{});
CORRADE_COMPARE(g.relativeScaling(), 1.0f);
}
template<class T> void GestureTest::twoFingerNonTouchEvents() {
setTestCaseTemplateName(std::is_same<T, PointerEventSourceMouseOnly>::value ? "PointerEventSourceMouseOnly" : "PointerEventSource");
TwoFingerGesture g;
struct {
T source() const { return T::Mouse; }
bool isPrimary() const { return true; }
Long id() const { return 0; }
Vector2 position() const { return {}; }
} event;
/* The event should be ignored by all APIs because it's not a touch one */
CORRADE_VERIFY(!g.pressEvent(event));
CORRADE_COMPARE(g.fingerCount(), 0);
CORRADE_VERIFY(!g.isGesture());
CORRADE_VERIFY(!g);
CORRADE_COMPARE(Math::isNan(g.position()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.direction()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.relativeTranslation()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(Vector2{g.relativeRotation()}), BitVector2{3});
CORRADE_VERIFY(Math::isNan(g.relativeScaling()));
CORRADE_VERIFY(!g.moveEvent(event));
CORRADE_COMPARE(g.fingerCount(), 0);
CORRADE_VERIFY(!g.isGesture());
CORRADE_VERIFY(!g);
CORRADE_COMPARE(Math::isNan(g.position()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.direction()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.relativeTranslation()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(Vector2{g.relativeRotation()}), BitVector2{3});
CORRADE_VERIFY(Math::isNan(g.relativeScaling()));
CORRADE_VERIFY(!g.releaseEvent(event));
CORRADE_COMPARE(g.fingerCount(), 0);
CORRADE_VERIFY(!g.isGesture());
CORRADE_VERIFY(!g);
CORRADE_COMPARE(Math::isNan(g.position()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.direction()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(g.relativeTranslation()), BitVector2{3});
CORRADE_COMPARE(Math::isNan(Vector2{g.relativeRotation()}), BitVector2{3});
CORRADE_VERIFY(Math::isNan(g.relativeScaling()));
}
}}}}
CORRADE_TEST_MAIN(Magnum::Platform::Test::GestureTest)
Loading…
Cancel
Save