summaryrefslogtreecommitdiffstats
path: root/src/location/declarativemaps/qquickgeomapgesturearea.cpp
diff options
context:
space:
mode:
authorPaolo Angelelli <paolo.angelelli@qt.io>2017-01-24 16:16:20 +0100
committerPaolo Angelelli <paolo.angelelli@qt.io>2017-01-30 11:45:49 +0000
commit4f338577a8dba92e69e8eea23d255d46e52c9c85 (patch)
tree89e8b12550b49de544ccef17e638152f24229832 /src/location/declarativemaps/qquickgeomapgesturearea.cpp
parent19fa507fc6c75e472d88f97353ded9340c084de8 (diff)
Add tilt and bearing gestures to QQuickGeoMapGestureArea
This patch adds two new gestures to the Map gesture area, that are two finger rotation and two finger parallel vertical sliding. The first gesture changes the bearing of the map, while the second gesture changes the tilt angle. The rotation gesture can co-exist with both pan and pinch. In other words it's possible to put down two fingers on the map, and rotate, pinch and pan at the same time. The tilt gesture, on the other hand, excludes the others when initiated, and also does not start, if any of the other actions is in progress. Change-Id: I0ef003caf0efe4addcf2e5ad563212f3c53db9ba Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
Diffstat (limited to 'src/location/declarativemaps/qquickgeomapgesturearea.cpp')
-rw-r--r--src/location/declarativemaps/qquickgeomapgesturearea.cpp594
1 files changed, 537 insertions, 57 deletions
diff --git a/src/location/declarativemaps/qquickgeomapgesturearea.cpp b/src/location/declarativemaps/qquickgeomapgesturearea.cpp
index a2b02261..e7242fd1 100644
--- a/src/location/declarativemaps/qquickgeomapgesturearea.cpp
+++ b/src/location/declarativemaps/qquickgeomapgesturearea.cpp
@@ -65,7 +65,13 @@
// before we perform a flick.
static const int FlickThreshold = 20;
// Really slow flicks can be annoying.
-const qreal MinimumFlickVelocity = 75.0;
+static const qreal MinimumFlickVelocity = 75.0;
+// Tolerance for detecting two finger sliding start
+static const qreal MaximumParallelPosition = 40.0; // in degrees
+// Tolerance for detecting parallel sliding
+static const qreal MaximumParallelSlidingAngle = 5.0; // in degrees
+// Tolerance for starting rotation
+static const qreal MinimumRotationStartingAngle = 15.0; // in degrees
// Returns the new map center after anchoring coordinate to anchorPoint on the screen
// Approach: find the displacement in (wrapped) mercator space, and apply that to the center
@@ -79,6 +85,61 @@ static QGeoCoordinate anchorCoordinateToPoint(QGeoMap &map, const QGeoCoordinate
return map.geoProjection().wrappedMapProjectionToGeo(centerProj + coordProj - anchorProj);
}
+// Keeps it in +- 180
+static qreal touchAngle(const QPointF &p1, const QPointF &p2)
+{
+ qreal angle = QLineF(p1, p2).angle();
+ if (angle > 180)
+ angle -= 360;
+ return angle;
+}
+
+// Deals with angles crossing the +-180 edge, assumes that the delta can't be > 180
+static qreal angleDelta(const qreal angle1, const qreal angle2)
+{
+ qreal delta = angle1 - angle2;
+ if (delta > 180.0) // detect crossing angle1 positive, angle2 negative, rotation counterclockwise, difference negative
+ delta = angle1 - angle2 - 360.0;
+ else if (delta < -180.0) // detect crossing angle1 negative, angle2 positive, rotation clockwise, difference positive
+ delta = angle1 - angle2 + 360.0;
+
+ return delta;
+}
+
+static bool pointDragged(const QPointF &pOld, const QPointF &pNew)
+{
+ static const int startDragDistance = qApp->styleHints()->startDragDistance();
+ return ( qAbs(pNew.x() - pOld.x()) > startDragDistance
+ || qAbs(pNew.y() - pOld.y()) > startDragDistance);
+}
+
+static qreal vectorSize(const QPointF &vector)
+{
+ return std::sqrt(vector.x() * vector.x() + vector.y() * vector.y());
+}
+
+static bool movingParallel(const QPointF &p1old, const QPointF &p1new, const QPointF &p2old, const QPointF &p2new)
+{
+ if (!pointDragged(p1old, p1new) || !pointDragged(p2old, p2new))
+ return false;
+
+ QPointF v1 = p1new - p1old;
+ QPointF v2 = p2new - p2old;
+ qreal v1v2size = vectorSize(v1 + v2);
+
+ if (v1v2size < vectorSize(v1) || v1v2size < vectorSize(v2)) // going in opposite directions
+ return false;
+
+ const qreal newAngle = touchAngle(p1new, p2new);
+ const qreal oldAngle = touchAngle(p1old, p2old);
+ const qreal angleDiff = angleDelta(newAngle, oldAngle);
+
+ if (qAbs(angleDiff) > MaximumParallelSlidingAngle)
+ return false;
+
+ return true;
+}
+
QT_BEGIN_NAMESPACE
@@ -166,7 +227,8 @@ QT_BEGIN_NAMESPACE
\brief The MapGestureArea type provides Map gesture interaction.
MapGestureArea objects are used as part of a Map, to provide for panning,
- flicking and pinch-to-zoom gesture used on touch displays.
+ flicking and pinch-to-zoom gesture used on touch displays, as well as two finger rotation
+ and two finger parallel vertical sliding to tilt the map.
A MapGestureArea is automatically created with a new Map and available with
the \l{Map::gesture}{gesture} property. This is the only way
@@ -222,6 +284,22 @@ QT_BEGIN_NAMESPACE
*/
/*!
+ \qmlproperty bool QtLocation::MapGestureArea::rotationActive
+
+ This read-only property holds whether the two-finger rotation gesture is active.
+
+ \since Qt Location 5.9
+*/
+
+/*!
+ \qmlproperty bool QtLocation::MapGestureArea::tiltActive
+
+ This read-only property holds whether the two-finger tilt gesture is active.
+
+ \since Qt Location 5.9
+*/
+
+/*!
\qmlproperty real QtLocation::MapGestureArea::maximumZoomLevelChange
This property holds the maximum zoom level change per pinch, essentially
@@ -312,22 +390,93 @@ QT_BEGIN_NAMESPACE
The corresponding handler is \c onFlickFinished.
*/
+/*!
+ \qmlsignal QtLocation::MapGestureArea::rotationStarted(PinchEvent event)
+
+ This signal is emitted when a two-finger rotation gesture is started.
+
+ The corresponding handler is \c onRotationStarted.
+
+ \sa rotationUpdated, rotationFinished
+
+ \since Qt Location 5.9
+*/
+
+/*!
+ \qmlsignal QtLocation::MapGestureArea::rotationUpdated(PinchEvent event)
+
+ This signal is emitted as the user's fingers move across the map,
+ after the \l rotationStarted signal is emitted.
+
+ The corresponding handler is \c onRotationUpdated.
+
+ \sa rotationStarted, rotationFinished
+
+ \since Qt Location 5.9
+*/
+
+/*!
+ \qmlsignal QtLocation::MapGestureArea::rotationFinished(PinchEvent event)
+
+ This signal is emitted at the end of a two-finger rotation gesture.
+
+ The corresponding handler is \c onRotationFinished.
+
+ \sa rotationStarted, rotationUpdated
+
+ \since Qt Location 5.9
+*/
+
+/*!
+ \qmlsignal QtLocation::MapGestureArea::tiltStarted(PinchEvent event)
+
+ This signal is emitted when a two-finger tilt gesture is started.
+
+ The corresponding handler is \c onTiltStarted.
+
+ \sa tiltUpdated, tiltFinished
+
+ \since Qt Location 5.9
+*/
+
+/*!
+ \qmlsignal QtLocation::MapGestureArea::tiltUpdated(PinchEvent event)
+
+ This signal is emitted as the user's fingers move across the map,
+ after the \l tiltStarted signal is emitted.
+
+ The corresponding handler is \c onTiltUpdated.
+
+ \sa tiltStarted, tiltFinished
+
+ \since Qt Location 5.9
+*/
+
+/*!
+ \qmlsignal QtLocation::MapGestureArea::tiltFinished(PinchEvent event)
+
+ This signal is emitted at the end of a two-finger tilt gesture.
+
+ The corresponding handler is \c onTiltFinished.
+
+ \sa tiltStarted, tiltUpdated
+
+ \since Qt Location 5.9
+*/
+
QQuickGeoMapGestureArea::QQuickGeoMapGestureArea(QDeclarativeGeoMap *map)
: QQuickItem(map),
m_map(0),
m_declarativeMap(map),
m_enabled(true),
- m_acceptedGestures(PinchGesture | PanGesture | FlickGesture),
- m_preventStealing(false),
- m_panEnabled(true)
-{
- m_flick.m_enabled = true,
- m_flick.m_maxVelocity = QML_MAP_FLICK_DEFAULTMAXVELOCITY;
- m_flick.m_deceleration = QML_MAP_FLICK_DEFAULTDECELERATION;
- m_flick.m_animation = 0;
+ m_acceptedGestures(PinchGesture | PanGesture | FlickGesture | RotationGesture | TiltGesture),
+ m_preventStealing(false)
+{
m_touchPointState = touchPoints0;
m_pinchState = pinchInactive;
m_flickState = flickInactive;
+ m_rotationState = rotationInactive;
+ m_tiltState = tiltInactive;
}
/*!
@@ -393,6 +542,8 @@ QQuickGeoMapGestureArea::~QQuickGeoMapGestureArea()
\li MapGestureArea.PinchGesture - Support the map pinch gesture (value: 0x0001).
\li MapGestureArea.PanGesture - Support the map pan gesture (value: 0x0002).
\li MapGestureArea.FlickGesture - Support the map flick gesture (value: 0x0004).
+ \li MapGestureArea.RotationGesture - Support the map rotation gesture (value: 0x0008).
+ \li MapGestureArea.TiltGesture - Support the map tilt gesture (value: 0x0010).
\endlist
*/
@@ -408,9 +559,13 @@ void QQuickGeoMapGestureArea::setAcceptedGestures(AcceptedGestures acceptedGestu
return;
m_acceptedGestures = acceptedGestures;
- setPanEnabled(acceptedGestures & PanGesture);
- setFlickEnabled(acceptedGestures & FlickGesture);
- setPinchEnabled(acceptedGestures & PinchGesture);
+ if (enabled()) {
+ setPanEnabled(acceptedGestures & PanGesture);
+ setFlickEnabled(acceptedGestures & FlickGesture);
+ setPinchEnabled(acceptedGestures & PinchGesture);
+ setRotationEnabled(acceptedGestures & RotationGesture);
+ setTiltEnabled(acceptedGestures & TiltGesture);
+ }
emit acceptedGesturesChanged();
}
@@ -426,6 +581,22 @@ bool QQuickGeoMapGestureArea::isPinchActive() const
/*!
\internal
*/
+bool QQuickGeoMapGestureArea::isRotationActive() const
+{
+ return m_rotationState == rotationActive;
+}
+
+/*!
+ \internal
+*/
+bool QQuickGeoMapGestureArea::isTiltActive() const
+{
+ return m_tiltState == tiltActive;
+}
+
+/*!
+ \internal
+*/
bool QQuickGeoMapGestureArea::isPanActive() const
{
return m_flickState == panActive || m_flickState == flickActive;
@@ -452,22 +623,25 @@ void QQuickGeoMapGestureArea::setEnabled(bool enabled)
setPanEnabled(m_acceptedGestures & PanGesture);
setFlickEnabled(m_acceptedGestures & FlickGesture);
setPinchEnabled(m_acceptedGestures & PinchGesture);
+ setRotationEnabled(m_acceptedGestures & RotationGesture);
+ setTiltEnabled(m_acceptedGestures & TiltGesture);
} else {
setPanEnabled(false);
setFlickEnabled(false);
setPinchEnabled(false);
+ setRotationEnabled(false);
+ setTiltEnabled(false);
}
emit enabledChanged();
}
-
/*!
\internal
*/
bool QQuickGeoMapGestureArea::pinchEnabled() const
{
- return m_pinch.m_enabled;
+ return m_pinch.m_pinchEnabled;
}
/*!
@@ -475,7 +649,39 @@ bool QQuickGeoMapGestureArea::pinchEnabled() const
*/
void QQuickGeoMapGestureArea::setPinchEnabled(bool enabled)
{
- m_pinch.m_enabled = enabled;
+ m_pinch.m_pinchEnabled = enabled;
+}
+
+/*!
+ \internal
+*/
+bool QQuickGeoMapGestureArea::rotationEnabled() const
+{
+ return m_pinch.m_rotationEnabled;
+}
+
+/*!
+ \internal
+*/
+void QQuickGeoMapGestureArea::setRotationEnabled(bool enabled)
+{
+ m_pinch.m_rotationEnabled = enabled;
+}
+
+/*!
+ \internal
+*/
+bool QQuickGeoMapGestureArea::tiltEnabled() const
+{
+ return m_pinch.m_tiltEnabled;
+}
+
+/*!
+ \internal
+*/
+void QQuickGeoMapGestureArea::setTiltEnabled(bool enabled)
+{
+ m_pinch.m_tiltEnabled = enabled;
}
/*!
@@ -483,7 +689,7 @@ void QQuickGeoMapGestureArea::setPinchEnabled(bool enabled)
*/
bool QQuickGeoMapGestureArea::panEnabled() const
{
- return m_panEnabled;
+ return m_flick.m_panEnabled;
}
/*!
@@ -491,9 +697,9 @@ bool QQuickGeoMapGestureArea::panEnabled() const
*/
void QQuickGeoMapGestureArea::setPanEnabled(bool enabled)
{
- if (enabled == m_flick.m_enabled)
+ if (enabled == m_flick.m_panEnabled)
return;
- m_panEnabled = enabled;
+ m_flick.m_panEnabled = enabled;
// unlike the pinch, the pan existing functionality is to stop immediately
if (!enabled)
@@ -505,7 +711,7 @@ void QQuickGeoMapGestureArea::setPanEnabled(bool enabled)
*/
bool QQuickGeoMapGestureArea::flickEnabled() const
{
- return m_flick.m_enabled;
+ return m_flick.m_flickEnabled;
}
/*!
@@ -513,9 +719,9 @@ bool QQuickGeoMapGestureArea::flickEnabled() const
*/
void QQuickGeoMapGestureArea::setFlickEnabled(bool enabled)
{
- if (enabled == m_flick.m_enabled)
+ if (enabled == m_flick.m_flickEnabled)
return;
- m_flick.m_enabled = enabled;
+ m_flick.m_flickEnabled = enabled;
// unlike the pinch, the flick existing functionality is to stop immediately
if (!enabled) {
stopFlick();
@@ -726,8 +932,8 @@ void QQuickGeoMapGestureArea::clearTouchData()
{
m_velocityX = 0;
m_velocityY = 0;
- m_sceneCenter.setX(0);
- m_sceneCenter.setY(0);
+ m_touchPointsCentroid.setX(0);
+ m_touchPointsCentroid.setY(0);
m_touchCenterCoord.setLongitude(0);
m_touchCenterCoord.setLatitude(0);
m_startCoord.setLongitude(0);
@@ -763,7 +969,7 @@ void QQuickGeoMapGestureArea::updateVelocityList(const QPointF &pos)
bool QQuickGeoMapGestureArea::isActive() const
{
- return isPanActive() || isPinchActive();
+ return isPanActive() || isPinchActive() || isRotationActive() || isTiltActive();
}
/*!
@@ -774,7 +980,6 @@ void QQuickGeoMapGestureArea::update()
{
if (!m_map)
return;
-
// First state machine is for the number of touch points
//combine touch with mouse event
@@ -785,16 +990,27 @@ void QQuickGeoMapGestureArea::update()
touchPointStateMachine();
+ // Parallel state machine for tilt. Tilt goes first as it blocks anything else, when started.
+ // But tilting can also only start if nothing else is active.
+ if (isTiltActive() || m_pinch.m_tiltEnabled)
+ tiltStateMachine();
+
// Parallel state machine for pinch
- if (isPinchActive() || (m_enabled && m_pinch.m_enabled && (m_acceptedGestures & (PinchGesture))))
+ if (isPinchActive() || m_pinch.m_pinchEnabled)
pinchStateMachine();
// Parallel state machine for pan (since you can pan at the same time as pinching)
// The stopPan function ensures that pan stops immediately when disabled,
- // but the line below allows pan continue its current gesture if you disable
- // the whole gesture (enabled_ flag), this keeps the enabled_ consistent with the pinch
- if (isPanActive() || (m_enabled && m_flick.m_enabled && (m_acceptedGestures & (PanGesture | FlickGesture))))
+ // but the isPanActive() below allows pan continue its current gesture if you disable
+ // the whole gesture.
+ if (isPanActive() || m_flick.m_flickEnabled || m_flick.m_panEnabled)
panStateMachine();
+
+ // Parallel state machine for rotation.
+ // Rotation goes last because when panning and rotating, first the new center has to be set,
+ // then the rotation has to be applied
+ if (isRotationActive() || m_pinch.m_rotationEnabled)
+ rotationStateMachine();
}
/*!
@@ -819,7 +1035,7 @@ void QQuickGeoMapGestureArea::touchPointStateMachine()
if (m_allPoints.count() == 0) {
m_touchPointState = touchPoints0;
} else if (m_allPoints.count() == 2) {
- m_touchCenterCoord = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(m_sceneCenter), false);
+ m_touchCenterCoord = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(m_touchPointsCentroid), false);
startTwoTouchPoints();
m_touchPointState = touchPoints2;
}
@@ -828,7 +1044,7 @@ void QQuickGeoMapGestureArea::touchPointStateMachine()
if (m_allPoints.count() == 0) {
m_touchPointState = touchPoints0;
} else if (m_allPoints.count() == 1) {
- m_touchCenterCoord = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(m_sceneCenter), false);
+ m_touchCenterCoord = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(m_touchPointsCentroid), false);
startOneTouchPoint();
m_touchPointState = touchPoints1;
}
@@ -869,11 +1085,10 @@ void QQuickGeoMapGestureArea::startOneTouchPoint()
*/
void QQuickGeoMapGestureArea::updateOneTouchPoint()
{
- m_sceneCenter = mapFromScene(m_allPoints.at(0).scenePos());
- updateVelocityList(m_sceneCenter);
+ m_touchPointsCentroid = mapFromScene(m_allPoints.at(0).scenePos());
+ updateVelocityList(m_touchPointsCentroid);
}
-
/*!
\internal
*/
@@ -889,6 +1104,7 @@ void QQuickGeoMapGestureArea::startTwoTouchPoints()
m_touchCenterCoord.longitude());
m_startCoord.setLatitude(m_startCoord.latitude() + startCoord.latitude() -
m_touchCenterCoord.latitude());
+ m_twoTouchAngleStart = touchAngle(m_sceneStartPoint1, m_sceneStartPoint2); // Initial angle used for calculating rotation
}
/*!
@@ -901,12 +1117,280 @@ void QQuickGeoMapGestureArea::updateTwoTouchPoints()
qreal dx = p1.x() - p2.x();
qreal dy = p1.y() - p2.y();
m_distanceBetweenTouchPoints = sqrt(dx * dx + dy * dy);
- m_sceneCenter = (p1 + p2) / 2;
- updateVelocityList(m_sceneCenter);
+ m_touchPointsCentroid = (p1 + p2) / 2;
+ updateVelocityList(m_touchPointsCentroid);
- m_twoTouchAngle = QLineF(p1, p2).angle();
- if (m_twoTouchAngle > 180)
- m_twoTouchAngle -= 360;
+ m_twoTouchAngle = touchAngle(p1, p2);
+}
+
+/*!
+ \internal
+*/
+void QQuickGeoMapGestureArea::tiltStateMachine()
+{
+ TiltState lastState = m_tiltState;
+ // Transitions:
+ switch (m_tiltState) {
+ case tiltInactive:
+ if (m_allPoints.count() >= 2) {
+ if (!isRotationActive() && !isPanActive() && !isPinchActive() && canStartTilt()) {
+ m_declarativeMap->setKeepMouseGrab(true);
+ m_declarativeMap->setKeepTouchGrab(true);
+ startTilt();
+ m_tiltState = tiltActive;
+ } else {
+ m_tiltState = tiltInactiveTwoPoints;
+ }
+ }
+ break;
+ case tiltInactiveTwoPoints:
+ if (m_allPoints.count() <= 1) {
+ m_tiltState = tiltInactive;
+ } else {
+ if (!isRotationActive() && !isPanActive() && !isPinchActive() && canStartTilt()) {
+ m_declarativeMap->setKeepMouseGrab(true);
+ m_declarativeMap->setKeepTouchGrab(true);
+ startTilt();
+ m_tiltState = tiltActive;
+ }
+ }
+ break;
+ case tiltActive:
+ if (m_allPoints.count() <= 1) {
+ m_tiltState = tiltInactive;
+ m_declarativeMap->setKeepMouseGrab(m_preventStealing);
+ m_declarativeMap->setKeepTouchGrab(m_preventStealing);
+ endTilt();
+ }
+ break;
+ }
+ // This line implements an exclusive state machine, where the transitions and updates don't
+ // happen on the same frame
+ if (m_tiltState != lastState) {
+ emit tiltActiveChanged();
+ return;
+ }
+
+ // Update
+ switch (m_tiltState) {
+ case tiltInactive:
+ case tiltInactiveTwoPoints:
+ break; // do nothing
+ case tiltActive:
+ updateTilt();
+ break;
+ }
+}
+
+/*!
+ \internal
+*/
+bool QQuickGeoMapGestureArea::canStartTilt()
+{
+ if (m_allPoints.count() >= 2) {
+ QPointF p1 = mapFromScene(m_allPoints.at(0).scenePos());
+ QPointF p2 = mapFromScene(m_allPoints.at(1).scenePos());
+ if (qAbs(m_twoTouchAngle) < MaximumParallelPosition
+ && movingParallel(m_sceneStartPoint1, p1, m_sceneStartPoint2, p2)) {
+ m_pinch.m_event.setCenter(mapFromScene(m_touchPointsCentroid));
+ m_pinch.m_event.setAngle(m_twoTouchAngle);
+ m_pinch.m_event.setPoint1(p1);
+ m_pinch.m_event.setPoint2(p2);
+ m_pinch.m_event.setPointCount(m_allPoints.count());
+ m_pinch.m_event.setAccepted(true);
+ emit tiltStarted(&m_pinch.m_event);
+ return m_pinch.m_event.accepted();
+ }
+ }
+ return false;
+}
+
+/*!
+ \internal
+*/
+void QQuickGeoMapGestureArea::startTilt()
+{
+ m_pinch.m_tilt.m_startTouchCentroid = m_touchPointsCentroid;
+ m_pinch.m_tilt.m_startTilt = m_declarativeMap->tilt();
+}
+
+/*!
+ \internal
+*/
+void QQuickGeoMapGestureArea::updateTilt()
+{
+ // Calculate the new tilt
+ qreal verticalDisplacement = (m_touchPointsCentroid - m_pinch.m_tilt.m_startTouchCentroid).y();
+
+ // Approach: 10pixel = 1 degree.
+ qreal tilt = verticalDisplacement / 10.0;
+ qreal newTilt = m_pinch.m_tilt.m_startTilt - tilt;
+ m_declarativeMap->setTilt(newTilt);
+
+ m_pinch.m_event.setCenter(mapFromScene(m_touchPointsCentroid));
+ m_pinch.m_event.setAngle(m_twoTouchAngle);
+ m_pinch.m_lastPoint1 = mapFromScene(m_allPoints.at(0).scenePos());
+ m_pinch.m_lastPoint2 = mapFromScene(m_allPoints.at(1).scenePos());
+ m_pinch.m_event.setPoint1(m_pinch.m_lastPoint1);
+ m_pinch.m_event.setPoint2(m_pinch.m_lastPoint2);
+ m_pinch.m_event.setPointCount(m_allPoints.count());
+ m_pinch.m_event.setAccepted(true);
+
+ emit tiltUpdated(&m_pinch.m_event);
+}
+
+/*!
+ \internal
+*/
+void QQuickGeoMapGestureArea::endTilt()
+{
+ QPointF p1 = mapFromScene(m_pinch.m_lastPoint1);
+ QPointF p2 = mapFromScene(m_pinch.m_lastPoint2);
+ m_pinch.m_event.setCenter((p1 + p2) / 2);
+ m_pinch.m_event.setAngle(m_pinch.m_lastAngle);
+ m_pinch.m_event.setPoint1(p1);
+ m_pinch.m_event.setPoint2(p2);
+ m_pinch.m_event.setAccepted(true);
+ m_pinch.m_event.setPointCount(0);
+ emit tiltFinished(&m_pinch.m_event);
+}
+
+/*!
+ \internal
+*/
+void QQuickGeoMapGestureArea::rotationStateMachine()
+{
+ RotationState lastState = m_rotationState;
+ // Transitions:
+ switch (m_rotationState) {
+ case rotationInactive:
+ if (m_allPoints.count() >= 2) {
+ if (!isTiltActive() && canStartRotation()) {
+ m_declarativeMap->setKeepMouseGrab(true);
+ m_declarativeMap->setKeepTouchGrab(true);
+ startRotation();
+ m_rotationState = rotationActive;
+ } else {
+ m_rotationState = rotationInactiveTwoPoints;
+ }
+ }
+ break;
+ case rotationInactiveTwoPoints:
+ if (m_allPoints.count() <= 1) {
+ m_rotationState = rotationInactive;
+ } else {
+ if (!isTiltActive() && canStartRotation()) {
+ m_declarativeMap->setKeepMouseGrab(true);
+ m_declarativeMap->setKeepTouchGrab(true);
+ startRotation();
+ m_rotationState = rotationActive;
+ }
+ }
+ break;
+ case rotationActive:
+ if (m_allPoints.count() <= 1) {
+ m_rotationState = rotationInactive;
+ m_declarativeMap->setKeepMouseGrab(m_preventStealing);
+ m_declarativeMap->setKeepTouchGrab(m_preventStealing);
+ endRotation();
+ }
+ break;
+ }
+ // This line implements an exclusive state machine, where the transitions and updates don't
+ // happen on the same frame
+ if (m_rotationState != lastState) {
+ emit rotationActiveChanged();
+ return;
+ }
+
+ // Update
+ switch (m_rotationState) {
+ case rotationInactive:
+ case rotationInactiveTwoPoints:
+ break; // do nothing
+ case rotationActive:
+ updateRotation();
+ break;
+ }
+}
+
+/*!
+ \internal
+*/
+bool QQuickGeoMapGestureArea::canStartRotation()
+{
+ if (m_allPoints.count() >= 2) {
+ QPointF p1 = mapFromScene(m_allPoints.at(0).scenePos());
+ QPointF p2 = mapFromScene(m_allPoints.at(1).scenePos());
+ if (pointDragged(m_sceneStartPoint1, p1) || pointDragged(m_sceneStartPoint2, p2)) {
+ qreal delta = angleDelta(m_twoTouchAngleStart, m_twoTouchAngle);
+ if (qAbs(delta) < MinimumRotationStartingAngle) {
+ return false;
+ }
+ m_pinch.m_event.setCenter(mapFromScene(m_touchPointsCentroid));
+ m_pinch.m_event.setAngle(m_twoTouchAngle);
+ m_pinch.m_event.setPoint1(p1);
+ m_pinch.m_event.setPoint2(p2);
+ m_pinch.m_event.setPointCount(m_allPoints.count());
+ m_pinch.m_event.setAccepted(true);
+ emit rotationStarted(&m_pinch.m_event);
+ return m_pinch.m_event.accepted();
+ }
+ }
+ return false;
+}
+
+/*!
+ \internal
+*/
+void QQuickGeoMapGestureArea::startRotation()
+{
+ m_pinch.m_rotation.m_startBearing = m_declarativeMap->bearing();
+ m_pinch.m_rotation.m_previousTouchAngle = m_twoTouchAngleStart;
+ m_pinch.m_rotation.m_totalAngle = 0.0;
+}
+
+/*!
+ \internal
+*/
+void QQuickGeoMapGestureArea::updateRotation()
+{
+ // Calculate the new bearing
+ qreal angle = angleDelta(m_pinch.m_rotation.m_previousTouchAngle, m_twoTouchAngle);
+ if (qAbs(angle) < 0.2) // avoiding too many updates
+ return;
+
+ m_pinch.m_rotation.m_previousTouchAngle = m_twoTouchAngle;
+ m_pinch.m_rotation.m_totalAngle += angle;
+ qreal newBearing = m_pinch.m_rotation.m_startBearing - m_pinch.m_rotation.m_totalAngle;
+ m_declarativeMap->setBearing(newBearing);
+
+ m_pinch.m_event.setCenter(mapFromScene(m_touchPointsCentroid));
+ m_pinch.m_event.setAngle(m_twoTouchAngle);
+ m_pinch.m_lastPoint1 = mapFromScene(m_allPoints.at(0).scenePos());
+ m_pinch.m_lastPoint2 = mapFromScene(m_allPoints.at(1).scenePos());
+ m_pinch.m_event.setPoint1(m_pinch.m_lastPoint1);
+ m_pinch.m_event.setPoint2(m_pinch.m_lastPoint2);
+ m_pinch.m_event.setPointCount(m_allPoints.count());
+ m_pinch.m_event.setAccepted(true);
+
+ emit rotationUpdated(&m_pinch.m_event);
+}
+
+/*!
+ \internal
+*/
+void QQuickGeoMapGestureArea::endRotation()
+{
+ QPointF p1 = mapFromScene(m_pinch.m_lastPoint1);
+ QPointF p2 = mapFromScene(m_pinch.m_lastPoint2);
+ m_pinch.m_event.setCenter((p1 + p2) / 2);
+ m_pinch.m_event.setAngle(m_pinch.m_lastAngle);
+ m_pinch.m_event.setPoint1(p1);
+ m_pinch.m_event.setPoint2(p2);
+ m_pinch.m_event.setAccepted(true);
+ m_pinch.m_event.setPointCount(0);
+ emit rotationFinished(&m_pinch.m_event);
}
/*!
@@ -919,7 +1403,7 @@ void QQuickGeoMapGestureArea::pinchStateMachine()
switch (m_pinchState) {
case pinchInactive:
if (m_allPoints.count() >= 2) {
- if (canStartPinch()) {
+ if (!isTiltActive() && canStartPinch()) {
m_declarativeMap->setKeepMouseGrab(true);
m_declarativeMap->setKeepTouchGrab(true);
startPinch();
@@ -933,7 +1417,7 @@ void QQuickGeoMapGestureArea::pinchStateMachine()
if (m_allPoints.count() <= 1) {
m_pinchState = pinchInactive;
} else {
- if (canStartPinch()) {
+ if (!isTiltActive() && canStartPinch()) {
m_declarativeMap->setKeepMouseGrab(true);
m_declarativeMap->setKeepTouchGrab(true);
startPinch();
@@ -942,7 +1426,7 @@ void QQuickGeoMapGestureArea::pinchStateMachine()
}
break;
case pinchActive:
- if (m_allPoints.count() <= 1) {
+ if (m_allPoints.count() <= 1) { // Once started, pinch goes off only when finger(s) are release
m_pinchState = pinchInactive;
m_declarativeMap->setKeepMouseGrab(m_preventStealing);
m_declarativeMap->setKeepTouchGrab(m_preventStealing);
@@ -973,16 +1457,12 @@ void QQuickGeoMapGestureArea::pinchStateMachine()
*/
bool QQuickGeoMapGestureArea::canStartPinch()
{
- const int startDragDistance = qApp->styleHints()->startDragDistance();
-
if (m_allPoints.count() >= 2) {
QPointF p1 = mapFromScene(m_allPoints.at(0).scenePos());
QPointF p2 = mapFromScene(m_allPoints.at(1).scenePos());
- if (qAbs(p1.x()-m_sceneStartPoint1.x()) > startDragDistance
- || qAbs(p1.y()-m_sceneStartPoint1.y()) > startDragDistance
- || qAbs(p2.x()-m_sceneStartPoint2.x()) > startDragDistance
- || qAbs(p2.y()-m_sceneStartPoint2.y()) > startDragDistance) {
- m_pinch.m_event.setCenter(mapFromScene(m_sceneCenter));
+ if (pointDragged(m_sceneStartPoint1, p1)
+ || pointDragged(m_sceneStartPoint2, p2)) {
+ m_pinch.m_event.setCenter(mapFromScene(m_touchPointsCentroid));
m_pinch.m_event.setAngle(m_twoTouchAngle);
m_pinch.m_event.setPoint1(p1);
m_pinch.m_event.setPoint2(p2);
@@ -1027,7 +1507,7 @@ void QQuickGeoMapGestureArea::updatePinch()
m_pinch.m_zoom.m_start;
}
- m_pinch.m_event.setCenter(mapFromScene(m_sceneCenter));
+ m_pinch.m_event.setCenter(mapFromScene(m_touchPointsCentroid));
m_pinch.m_event.setAngle(m_twoTouchAngle);
m_pinch.m_lastPoint1 = mapFromScene(m_allPoints.at(0).scenePos());
@@ -1077,9 +1557,9 @@ void QQuickGeoMapGestureArea::panStateMachine()
// Transitions
switch (m_flickState) {
case flickInactive:
- if (canStartPan()) {
+ if (!isTiltActive() && canStartPan()) {
// Update startCoord_ to ensure smooth start for panning when going over startDragDistance
- QGeoCoordinate newStartCoord = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(m_sceneCenter), false);
+ QGeoCoordinate newStartCoord = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(m_touchPointsCentroid), false);
m_startCoord.setLongitude(newStartCoord.longitude());
m_startCoord.setLatitude(newStartCoord.latitude());
m_declarativeMap->setKeepMouseGrab(true);
@@ -1092,7 +1572,7 @@ void QQuickGeoMapGestureArea::panStateMachine()
{
m_flickState = flickInactive;
// mark as inactive for use by camera
- if (m_pinchState == pinchInactive) {
+ if (m_pinchState == pinchInactive && m_rotationState == rotationInactive && m_tiltState == tiltInactive) {
m_declarativeMap->setKeepMouseGrab(m_preventStealing);
m_map->prefetchData();
}
@@ -1154,7 +1634,7 @@ bool QQuickGeoMapGestureArea::canStartPan()
*/
void QQuickGeoMapGestureArea::updatePan()
{
- QGeoCoordinate animationStartCoordinate = anchorCoordinateToPoint(*m_map, m_startCoord, m_sceneCenter);
+ QGeoCoordinate animationStartCoordinate = anchorCoordinateToPoint(*m_map, m_startCoord, m_touchPointsCentroid);
m_declarativeMap->setCenter(animationStartCoordinate);
}
@@ -1176,7 +1656,7 @@ bool QQuickGeoMapGestureArea::tryStartFlick()
int flickTimeX = 0;
int flickPixelsX = 0;
int flickPixelsY = 0;
- if (qAbs(velocityY) > MinimumFlickVelocity && qAbs(m_sceneCenter.y() - m_sceneStartPoint1.y()) > FlickThreshold) {
+ if (qAbs(velocityY) > MinimumFlickVelocity && qAbs(m_touchPointsCentroid.y() - m_sceneStartPoint1.y()) > FlickThreshold) {
// calculate Y flick animation values
qreal acceleration = m_flick.m_deceleration;
if ((velocityY > 0.0f) == (m_flick.m_deceleration > 0.0f))
@@ -1184,7 +1664,7 @@ bool QQuickGeoMapGestureArea::tryStartFlick()
flickTimeY = static_cast<int>(-1000 * velocityY / acceleration);
flickPixelsY = (flickTimeY * velocityY) / (1000.0 * 2);
}
- if (qAbs(velocityX) > MinimumFlickVelocity && qAbs(m_sceneCenter.x() - m_sceneStartPoint1.x()) > FlickThreshold) {
+ if (qAbs(velocityX) > MinimumFlickVelocity && qAbs(m_touchPointsCentroid.x() - m_sceneStartPoint1.x()) > FlickThreshold) {
// calculate X flick animation values
qreal acceleration = m_flick.m_deceleration;
if ((velocityX > 0.0f) == (m_flick.m_deceleration > 0.0f))