mirror of https://github.com/mosra/magnum.git
11 changed files with 876 additions and 3 deletions
@ -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 |
||||
@ -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…
Reference in new issue