on xenial help sign in
Make DirectionalDragArea work when rotated
8142dd4Daniel d'Andrada
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+104−44
@@ -114,6 +114,7 @@ DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
114 , m_wideningAngle(0)
114 , m_wideningAngle(0)
115 , m_wideningFactor(0)
115 , m_wideningFactor(0)
116 , m_distanceThreshold(0)
116 , m_distanceThreshold(0)
117+ , m_distanceThresholdSquared(0.)
117 , m_minSpeed(0)
118 , m_minSpeed(0)
118 , m_maxSilenceTime(200)
119 , m_maxSilenceTime(200)
119 , m_silenceTime(0)
120 , m_silenceTime(0)
@@ -162,7 +163,14 @@ void DirectionalDragArea::setWideningAngle(qreal angle)
162 return;
163 return;
163
164
164 m_wideningAngle = angle;
165 m_wideningAngle = angle;
166+
167+ // wideningFactor = pow(cosine(angle), 2)
168+ {
165− m_wideningFactor = qTan(angle * M_PI / 180.0);
169+ qreal angleRadians = angle * M_PI / 180.0;
170+ m_wideningFactor = qCos(angleRadians);
171+ m_wideningFactor = m_wideningFactor * m_wideningFactor;
172+ }
173+
166 Q_EMIT wideningAngleChanged(angle);
174 Q_EMIT wideningAngleChanged(angle);
167}
175}
168
176
@@ -170,6 +178,7 @@ void DirectionalDragArea::setDistanceThreshold(qreal value)
170{
178{
171 if (m_distanceThreshold != value) {
179 if (m_distanceThreshold != value) {
172 m_distanceThreshold = value;
180 m_distanceThreshold = value;
181+ m_distanceThresholdSquared = m_distanceThreshold * m_distanceThreshold;
173 Q_EMIT distanceThresholdChanged(value);
182 Q_EMIT distanceThresholdChanged(value);
174 }
183 }
175}
184}
@@ -237,13 +246,15 @@ qreal DirectionalDragArea::distance() const
237 }
246 }
238}
247}
239
248
249+void DirectionalDragArea::updateSceneDistance()
250+{
251+ QPointF totalMovement = m_previousScenePos - m_startScenePos;
252+ m_sceneDistance = projectOntoDirectionVector(totalMovement);
253+}
254+
240qreal DirectionalDragArea::sceneDistance() const
255qreal DirectionalDragArea::sceneDistance() const
241{
256{
242− if (Direction::isHorizontal(m_direction)) {
257+ return m_sceneDistance;
243− return m_previousScenePos.x() - m_startScenePos.x();
244− } else {
245− return m_previousScenePos.y() - m_startScenePos.y();
246− }
247}
258}
248
259
249qreal DirectionalDragArea::touchX() const
260qreal DirectionalDragArea::touchX() const
@@ -335,12 +346,13 @@ void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
335 m_startScenePos = newTouchPoint->scenePos();
346 m_startScenePos = newTouchPoint->scenePos();
336 m_touchId = newTouchPoint->id();
347 m_touchId = newTouchPoint->id();
337 m_dampedScenePos.reset(m_startScenePos);
348 m_dampedScenePos.reset(m_startScenePos);
338− updateVelocityCalculator(m_startScenePos);
349+ m_velocityCalculator->setTrackedPosition(0.);
339 m_velocityCalculator->reset();
350 m_velocityCalculator->reset();
340 m_numSamplesOnLastSpeedCheck = 0;
351 m_numSamplesOnLastSpeedCheck = 0;
341 m_silenceTime = 0;
352 m_silenceTime = 0;
342 setPreviousPos(m_startPos);
353 setPreviousPos(m_startPos);
343 setPreviousScenePos(m_startScenePos);
354 setPreviousScenePos(m_startScenePos);
355+ updateSceneDirectionVector();
344
356
345 setStatus(Undecided);
357 setStatus(Undecided);
346}
358}
@@ -441,44 +453,53 @@ const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouch
441
453
442bool DirectionalDragArea::pointInsideAllowedArea() const
454bool DirectionalDragArea::pointInsideAllowedArea() const
443{
455{
444− qreal dX = m_dampedScenePos.x() - m_startScenePos.x();
456+ // NB: Using squared values to avoid computing the square root to find
445− qreal dY = m_dampedScenePos.y() - m_startScenePos.y();
457+ // the length totalMovement
446
458
447− switch (m_direction) {
459+ QPointF totalMovement(m_dampedScenePos.x() - m_startScenePos.x(),
448− case Direction::Upwards:
460+ m_dampedScenePos.y() - m_startScenePos.y());
449− return dY <= 0 && qFabs(dX) <= qFabs(dY) * m_wideningFactor;
461+
450− case Direction::Downwards:
462+ qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
451− return dY >= 0 && qFabs(dX) <= dY * m_wideningFactor;
463+ totalMovement.y() * totalMovement.y();
452− case Direction::Leftwards:
464+
453− return dX <= 0 && qFabs(dY) <= qFabs(dX) * m_wideningFactor;
465+ if (squaredTotalMovSize == 0.) {
454− default: // Direction::Rightwards:
466+ // didn't move
455− return dX >= 0 && qFabs(dY) <= dX * m_wideningFactor;
467+ return true;
456 }
468 }
469+
470+ qreal projectedMovement = projectOntoDirectionVector(totalMovement);
471+
472+
473+ qreal cosineAngleSquared = (projectedMovement * projectedMovement) / squaredTotalMovSize;
474+
475+ // Same as:
476+ // angle_between_movement_vector_and_gesture_direction_vector <= widening_angle
477+ return cosineAngleSquared >= m_wideningFactor;
457}
478}
458
479
459bool DirectionalDragArea::movingInRightDirection() const
480bool DirectionalDragArea::movingInRightDirection() const
460{
481{
461− switch (m_direction) {
462− case Direction::Upwards:
463− return m_dampedScenePos.y() <= m_previousDampedScenePos.y();
482+ QPointF movementVector(m_dampedScenePos.x() - m_previousDampedScenePos.x(),
464− case Direction::Downwards:
465− return m_dampedScenePos.y() >= m_previousDampedScenePos.y();
483+ m_dampedScenePos.y() - m_previousDampedScenePos.y());
466− case Direction::Leftwards:
484+
467− return m_dampedScenePos.x() <= m_previousDampedScenePos.x();
485+ qreal scalarProjection = projectOntoDirectionVector(movementVector);
468− default: // Direction::Rightwards:
486+
469− return m_dampedScenePos.x() >= m_previousDampedScenePos.x();
487+ return scalarProjection >= 0.;
470− }
471}
488}
472
489
473bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
490bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
474{
491{
475− if (m_distanceThreshold > 0) {
492+ if (m_distanceThreshold <= 0.) {
476− if (Direction::isHorizontal(m_direction))
493+ // distance threshold check is disabled
477− return qFabs(point.x() - m_startScenePos.x()) > m_distanceThreshold;
478− else
479− return qFabs(point.y() - m_startScenePos.y()) > m_distanceThreshold;
480− } else {
481 return true;
494 return true;
495+ } else {
496+ QPointF totalMovement(point.x() - m_startScenePos.x(),
497+ point.y() - m_startScenePos.y());
498+
499+ qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
500+ totalMovement.y() * totalMovement.y();
501+
502+ return squaredTotalMovSize > m_distanceThresholdSquared;
482 }
503 }
483}
504}
484
505
@@ -568,28 +589,33 @@ void DirectionalDragArea::setPreviousScenePos(const QPointF &point)
568 bool xChanged = m_previousScenePos.x() != point.x();
589 bool xChanged = m_previousScenePos.x() != point.x();
569 bool yChanged = m_previousScenePos.y() != point.y();
590 bool yChanged = m_previousScenePos.y() != point.y();
570
591
592+ if (!xChanged && !yChanged)
593+ return;
594+
595+ qreal oldSceneDistance = sceneDistance();
571 m_previousScenePos = point;
596 m_previousScenePos = point;
597+ updateSceneDistance();
598+
599+ if (oldSceneDistance != sceneDistance()) {
600+ Q_EMIT sceneDistanceChanged(sceneDistance());
601+ }
572
602
573 if (xChanged) {
603 if (xChanged) {
574 Q_EMIT touchSceneXChanged(point.x());
604 Q_EMIT touchSceneXChanged(point.x());
575− if (Direction::isHorizontal(m_direction))
576− Q_EMIT sceneDistanceChanged(sceneDistance());
577 }
605 }
578
606
579 if (yChanged) {
607 if (yChanged) {
580 Q_EMIT touchSceneYChanged(point.y());
608 Q_EMIT touchSceneYChanged(point.y());
581− if (Direction::isVertical(m_direction))
582− Q_EMIT sceneDistanceChanged(sceneDistance());
583 }
609 }
584}
610}
585
611
586−void DirectionalDragArea::updateVelocityCalculator(const QPointF &point)
612+void DirectionalDragArea::updateVelocityCalculator(const QPointF &scenePos)
587{
613{
588− if (Direction::isHorizontal(m_direction)) {
614+ QPointF totalSceneMovement = scenePos - m_startScenePos;
589− m_velocityCalculator->setTrackedPosition(point.x());
615+
590− } else {
616+ qreal scalarProjection = projectOntoDirectionVector(totalSceneMovement);
617+
591− m_velocityCalculator->setTrackedPosition(point.y());
618+ m_velocityCalculator->setTrackedPosition(scalarProjection);
592− }
593}
619}
594
620
595bool DirectionalDragArea::isWithinTouchCompositionWindow()
621bool DirectionalDragArea::isWithinTouchCompositionWindow()
@@ -693,5 +719,39 @@ qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
693 return highestStartTime;
719 return highestStartTime;
694}
720}
695
721
722+void DirectionalDragArea::updateSceneDirectionVector()
723+{
724+ QPointF localOrigin(0., 0.);
725+ QPointF localDirection;
726+ switch (m_direction) {
727+ case Direction::Upwards:
728+ localDirection.rx() = 0.;
729+ localDirection.ry() = -1.;
730+ break;
731+ case Direction::Downwards:
732+ localDirection.rx() = 0.;
733+ localDirection.ry() = 1;
734+ break;
735+ case Direction::Leftwards:
736+ localDirection.rx() = -1.;
737+ localDirection.ry() = 0.;
738+ break;
739+ default: // Direction::Rightwards:
740+ localDirection.rx() = 1.;
741+ localDirection.ry() = 0.;
742+ break;
743+ }
744+ QPointF sceneOrigin = mapToScene(localOrigin);
745+ QPointF sceneDirection = mapToScene(localDirection);
746+ m_sceneDirectionVector = sceneDirection - sceneOrigin;
747+}
748+
749+qreal DirectionalDragArea::projectOntoDirectionVector(const QPointF &sceneVector) const
750+{
751+ // same as dot product as m_sceneDirectionVector is a unit vector
752+ return sceneVector.x() * m_sceneDirectionVector.x() +
753+ sceneVector.y() * m_sceneDirectionVector.y();
754+}
755+
696// Because we are defining a new QObject-based class (RecognitionTimer) here.
756// Because we are defining a new QObject-based class (RecognitionTimer) here.
697#include "DirectionalDragArea.moc"
757#include "DirectionalDragArea.moc"
plugins/Ubuntu/Gestures/DirectionalDragArea.h+11−1
@@ -160,6 +160,7 @@ public:
160
160
161 qreal distance() const;
161 qreal distance() const;
162 qreal sceneDistance() const;
162 qreal sceneDistance() const;
163+ void updateSceneDistance();
163
164
164 qreal touchX() const;
165 qreal touchX() const;
165 qreal touchY() const;
166 qreal touchY() const;
@@ -231,6 +232,10 @@ private:
231 void setPreviousScenePos(const QPointF &point);
232 void setPreviousScenePos(const QPointF &point);
232 void updateVelocityCalculator(const QPointF &point);
233 void updateVelocityCalculator(const QPointF &point);
233 bool isWithinTouchCompositionWindow();
234 bool isWithinTouchCompositionWindow();
235+ void updateSceneDirectionVector();
236+ // returns the scalar projection between the given vector (in scene coordinates)
237+ // and m_sceneDirectionVector
238+ qreal projectOntoDirectionVector(const QPointF &sceneVector) const;
234
239
235 Status m_status;
240 Status m_status;
236
241
@@ -238,6 +243,7 @@ private:
238 QPointF m_startScenePos;
243 QPointF m_startScenePos;
239 QPointF m_previousPos;
244 QPointF m_previousPos;
240 QPointF m_previousScenePos;
245 QPointF m_previousScenePos;
246+ qreal m_sceneDistance;
241 int m_touchId;
247 int m_touchId;
242
248
243 // A movement damper is used in some of the gesture recognition calculations
249 // A movement damper is used in some of the gesture recognition calculations
@@ -245,10 +251,14 @@ private:
245 DampedPointF m_dampedScenePos;
251 DampedPointF m_dampedScenePos;
246 QPointF m_previousDampedScenePos;
252 QPointF m_previousDampedScenePos;
247
253
254+ // Unit vector in scene coordinates describing the direction of the gesture recognition
255+ QPointF m_sceneDirectionVector;
256+
248 Direction::Type m_direction;
257 Direction::Type m_direction;
249 qreal m_wideningAngle; // in degrees
258 qreal m_wideningAngle; // in degrees
250− qreal m_wideningFactor; // it's tan(degreesToRadian(m_wideningAngle))
259+ qreal m_wideningFactor; // it's pow(cosine(m_wideningAngle), 2)
251 qreal m_distanceThreshold;
260 qreal m_distanceThreshold;
261+ qreal m_distanceThresholdSquared; // it's pow(m_distanceThreshold, 2)
252 qreal m_minSpeed;
262 qreal m_minSpeed;
253 int m_maxSilenceTime; // in milliseconds
263 int m_maxSilenceTime; // in milliseconds
254 int m_silenceTime; // in milliseconds
264 int m_silenceTime; // in milliseconds
qml/Components/DragHandle.qml+2−1
@@ -167,7 +167,8 @@ EdgeDragArea {
167 EdgeDragEvaluator {
167 EdgeDragEvaluator {
168 objectName: "edgeDragEvaluator"
168 objectName: "edgeDragEvaluator"
169 id: dragEvaluator
169 id: dragEvaluator
170+ // Effectively convert distance into the drag position projected onto the gesture direction axis
170− trackedPosition: sceneDistance
171+ trackedPosition: Direction.isPositive(dragArea.direction) ? sceneDistance : -sceneDistance
171 maxDragDistance: maxTotalDragDistance
172 maxDragDistance: maxTotalDragDistance
172 direction: dragArea.direction
173 direction: dragArea.direction
173 }
174 }
tests/plugins/Ubuntu/Gestures/DownwardsLauncher.qml+8−1
@@ -63,7 +63,7 @@ Item {
63 dragAreaRect.opacity = 0.3
63 dragAreaRect.opacity = 0.3
64 launcher.y = Qt.binding(launcher.followDragArea)
64 launcher.y = Qt.binding(launcher.followDragArea)
65 break;
65 break;
66− defaut: // DirectionalDragArea.Recognized:
66+ default: // DirectionalDragArea.Recognized:
67 dragAreaRect.color = "green"
67 dragAreaRect.color = "green"
68 dragAreaRect.opacity = 0.5
68 dragAreaRect.opacity = 0.5
69 break;
69 break;
@@ -76,4 +76,11 @@ Item {
76 top: parent.top
76 top: parent.top
77 }
77 }
78 }
78 }
79+
80+ Label {
81+ text: "Downwards"
82+ anchors.top: parent.top
83+ anchors.horizontalCenter: parent.horizontalCenter
84+ anchors.topMargin: units.gu(1)
85+ }
79}
86}
tests/plugins/Ubuntu/Gestures/LeftwardsLauncher.qml+7
@@ -79,4 +79,11 @@ Item {
79 bottom: parent.bottom
79 bottom: parent.bottom
80 }
80 }
81 }
81 }
82+
83+ Label {
84+ text: "Leftwards"
85+ anchors.right: parent.right
86+ anchors.verticalCenter: parent.verticalCenter
87+ anchors.rightMargin: units.gu(1)
88+ }
82}
89}
tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml+7
@@ -77,4 +77,11 @@ Item {
77 bottom: parent.bottom
77 bottom: parent.bottom
78 }
78 }
79 }
79 }
80+
81+ Label {
82+ text: "Rightwards"
83+ anchors.left: parent.left
84+ anchors.verticalCenter: parent.verticalCenter
85+ anchors.leftMargin: units.gu(1)
86+ }
80}
87}
tests/plugins/Ubuntu/Gestures/UpwardsLauncher.qml+7
@@ -79,4 +79,11 @@ Item {
79 bottom: parent.bottom
79 bottom: parent.bottom
80 }
80 }
81 }
81 }
82+
83+ Label {
84+ text: "Upwards"
85+ anchors.bottom: parent.bottom
86+ anchors.horizontalCenter: parent.horizontalCenter
87+ anchors.bottomMargin: units.gu(1)
88+ }
82}
89}
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+108−17
@@ -83,6 +83,9 @@ private Q_SLOTS:
83 void twoFingerTap();
83 void twoFingerTap();
84 void movingDDA();
84 void movingDDA();
85 void ignoreOldFinger();
85 void ignoreOldFinger();
86+ void rotated();
87+ void sceneDistance();
88+ void sceneDistance_data();
86
89
87private:
90private:
88 void passTime(qint64 timeSpan);
91 void passTime(qint64 timeSpan);
@@ -131,18 +134,10 @@ void tst_DirectionalDragArea::passTime(qint64 timeSpan)
131}
134}
132
135
133namespace {
136namespace {
134−QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea, QQuickView *view)
137+QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea)
135{
138{
136− switch (edgeDragArea->direction()) {
139+ QPointF localCenter(edgeDragArea->width() / 2., edgeDragArea->height() / 2.);
137− case Direction::Upwards:
138− return QPointF(view->width()/2.0f, view->height() - (edgeDragArea->height()/2.0f));
139− case Direction::Downwards:
140− return QPointF(view->width()/2.0f, edgeDragArea->height()/2.0f);
141− case Direction::Leftwards:
142− return QPointF(view->width() - (edgeDragArea->width()/2.0f), view->height()/2.0f);
143− default: // Direction::Rightwards:
144− return QPointF(edgeDragArea->width()/2.0f, view->height()/2.0f);
140+ return edgeDragArea->mapToScene(localCenter);
145− }
146}
141}
147
142
148QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea,
143QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea,
@@ -193,7 +188,7 @@ void tst_DirectionalDragArea::edgeDrag()
193
188
194 QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
189 QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
195
190
196− QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, m_view);
191+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
197 QPointF touchPoint = initialTouchPos;
192 QPointF touchPoint = initialTouchPos;
198
193
199 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*dragDistanceFactor;
194 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*dragDistanceFactor;
@@ -300,7 +295,7 @@ void tst_DirectionalDragArea::dragWithShortDirectionChange()
300 edgeDragArea->setRecognitionTimer(fakeTimer);
295 edgeDragArea->setRecognitionTimer(fakeTimer);
301 edgeDragArea->setTimeSource(fakeTimeSource);
296 edgeDragArea->setTimeSource(fakeTimeSource);
302
297
303− QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, m_view);
298+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
304 QPointF touchPoint = initialTouchPos;
299 QPointF touchPoint = initialTouchPos;
305
300
306 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0;
301 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0;
@@ -360,7 +355,7 @@ void tst_DirectionalDragArea::minSpeed()
360
355
361 edgeDragArea->setMinSpeed(minSpeed);
356 edgeDragArea->setMinSpeed(minSpeed);
362
357
363− QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, m_view);
358+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
364 QPointF touchPoint = initialTouchPos;
359 QPointF touchPoint = initialTouchPos;
365
360
366 QPointF dragDirectionVector(1.0, 0.0);
361 QPointF dragDirectionVector(1.0, 0.0);
@@ -408,7 +403,7 @@ void tst_DirectionalDragArea::recognitionTimerUsage()
408
403
409 int timeStepMs = 5; // some arbitrary small value.
404 int timeStepMs = 5; // some arbitrary small value.
410
405
411− QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, m_view);
406+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
412 QPointF touchPoint = initialTouchPos;
407 QPointF touchPoint = initialTouchPos;
413
408
414 QPointF dragDirectionVector(1.0, 0.0);
409 QPointF dragDirectionVector(1.0, 0.0);
@@ -452,7 +447,7 @@ void tst_DirectionalDragArea::maxSilenceTime()
452 // Make sure this property is not disabled
447 // Make sure this property is not disabled
453 edgeDragArea->setMaxSilenceTime(100);
448 edgeDragArea->setMaxSilenceTime(100);
454
449
455− QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, m_view);
450+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
456 QPointF touchPoint = initialTouchPos;
451 QPointF touchPoint = initialTouchPos;
457
452
458 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
453 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
@@ -620,7 +615,7 @@ void tst_DirectionalDragArea::movingDDA()
620 edgeDragArea->setRecognitionTimer(fakeTimer);
615 edgeDragArea->setRecognitionTimer(fakeTimer);
621 edgeDragArea->setTimeSource(fakeTimeSource);
616 edgeDragArea->setTimeSource(fakeTimeSource);
622
617
623− QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea, m_view);
618+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
624 QPointF touchPoint = initialTouchPos;
619 QPointF touchPoint = initialTouchPos;
625
620
626 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
621 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
@@ -704,6 +699,102 @@ void tst_DirectionalDragArea::ignoreOldFinger()
704 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
699 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
705}
700}
706
701
702+/*
703+ A Rightwards DDA that is rotated 90 degrees clockwise should recognize gestures
704+ that are done downwards in scene coordinates. I.e. the gesture recognition direction
705+ should be in local coordinates, not scene coordinates.
706+ */
707+void tst_DirectionalDragArea::rotated()
708+{
709+ QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
710+ baseItem->setRotation(90.);
711+
712+ QQuickItem *rightwardsLauncher = m_view->rootObject()->findChild<QQuickItem*>("rightwardsLauncher");
713+ Q_ASSERT(rightwardsLauncher != 0);
714+
715+ DirectionalDragArea *edgeDragArea =
716+ rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
717+ Q_ASSERT(edgeDragArea != 0);
718+ edgeDragArea->setRecognitionTimer(fakeTimer);
719+ edgeDragArea->setTimeSource(fakeTimeSource);
720+
721+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
722+ QPointF touchPoint = initialTouchPos;
723+
724+ qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
725+ QPointF dragDirectionVector(0.0f, 1.0f);
726+
727+ qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
728+ QPointF touchMovement = dragDirectionVector * movementStepDistance;
729+ int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
730+ int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
731+
732+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
733+
734+ for (int i = 0; i < totalMovementSteps; ++i) {
735+ touchPoint += touchMovement;
736+ passTime(movementTimeStepMs);
737+ QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
738+ }
739+
740+ QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
741+
742+ QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
743+}
744+
745+void tst_DirectionalDragArea::sceneDistance()
746+{
747+ QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
748+ QFETCH(qreal, rotation);
749+ QFETCH(QPointF, dragDirectionVector);
750+ baseItem->setRotation(rotation);
751+
752+ QQuickItem *rightwardsLauncher = m_view->rootObject()->findChild<QQuickItem*>("rightwardsLauncher");
753+ Q_ASSERT(rightwardsLauncher != 0);
754+
755+ DirectionalDragArea *edgeDragArea =
756+ rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
757+ Q_ASSERT(edgeDragArea != 0);
758+ edgeDragArea->setRecognitionTimer(fakeTimer);
759+ edgeDragArea->setTimeSource(fakeTimeSource);
760+
761+ QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
762+ QPointF touchPoint = initialTouchPos;
763+
764+ qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
765+
766+ qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
767+ QPointF touchMovement = dragDirectionVector * movementStepDistance;
768+ int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
769+ int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
770+
771+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
772+
773+ for (int i = 0; i < totalMovementSteps; ++i) {
774+ touchPoint += touchMovement;
775+ passTime(movementTimeStepMs);
776+ QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
777+ }
778+
779+ qreal actualDragDistance = ((qreal)totalMovementSteps) * movementStepDistance;
780+
781+ // DirectionalDragArea::sceneDistance() must match the actual drag distance as the
782+ // drag was aligned with the gesture direction
783+ // NB: qFuzzyCompare(), used internally by QCOMPARE(), is broken.
784+ QVERIFY(qAbs(edgeDragArea->sceneDistance() - actualDragDistance) < 0.001);
785+
786+ QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
787+}
788+
789+void tst_DirectionalDragArea::sceneDistance_data()
790+{
791+ QTest::addColumn<qreal>("rotation");
792+ QTest::addColumn<QPointF>("dragDirectionVector");
793+
794+ QTest::newRow("not rotated") << 0. << QPointF(1., 0.);
795+ QTest::newRow("rotated by 90 degrees") << 90. << QPointF(0., 1.);
796+}
797+
707QTEST_MAIN(tst_DirectionalDragArea)
798QTEST_MAIN(tst_DirectionalDragArea)
708
799
709#include "tst_DirectionalDragArea.moc"
800#include "tst_DirectionalDragArea.moc"
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.qml+30−8
@@ -18,8 +18,8 @@ import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19
19
20Rectangle {
20Rectangle {
21− width: units.gu(40)
21+ width: units.gu(60)
22− height: units.gu(71)
22+ height: units.gu(60)
23 color: "white"
23 color: "white"
24
24
25 MouseArea {
25 MouseArea {
@@ -33,14 +33,36 @@ Rectangle {
33 }
33 }
34 }
34 }
35
35
36− // NB: Do not anchor it as we will move it programmatically from the test
36+ Item {
37− RightwardsLauncher {
37+ id: baseItem
38− id: hpLauncher;
38+ objectName: "baseItem"
39 width: parent.width
39 width: parent.width
40 height: parent.height
40 height: parent.height
41+
42+ // NB: Do not anchor it as we will move it programmatically from the test
43+ RightwardsLauncher {
44+ id: hpLauncher;
45+ width: parent.width
46+ height: parent.height
47+ }
48+
49+ LeftwardsLauncher { id: hnLauncher; anchors.fill: parent }
50+ DownwardsLauncher { id: vpLauncher; anchors.fill: parent }
51+ UpwardsLauncher { id: vnLauncher; anchors.fill: parent }
41 }
52 }
42
53
43− LeftwardsLauncher { id: hnLauncher; anchors.fill: parent }
54+ Button {
44− DownwardsLauncher { id: vpLauncher; anchors.fill: parent }
55+ anchors.bottom: parent.bottom
45− UpwardsLauncher { id: vnLauncher; anchors.fill: parent }
56+ anchors.left: parent.left
57+ anchors.margins: units.gu(1)
58+
59+ text: "rotation: " + baseItem.rotation
60+ onClicked: {
61+ if (baseItem.rotation === 0.0) {
62+ baseItem.rotation = 90.0
63+ } else {
64+ baseItem.rotation = 0.0
65+ }
66+ }
67+ }
46}
68}
tests/qmltests/Components/tst_DragHandle.cpp+45−5
@@ -58,7 +58,9 @@ private Q_SLOTS:
58 void cleanup(); // called right after each and every test function is executed
58 void cleanup(); // called right after each and every test function is executed
59
59
60 void dragThreshold_horizontal();
60 void dragThreshold_horizontal();
61+ void dragThreshold_horizontal_data();
61 void dragThreshold_vertical();
62 void dragThreshold_vertical();
63+ void dragThreshold_vertical_data();
62 void stretch_horizontal();
64 void stretch_horizontal();
63 void stretch_vertical();
65 void stretch_vertical();
64 void hintingAnimation();
66 void hintingAnimation();
@@ -145,17 +147,29 @@ void tst_DragHandle::tryCompare(std::function<qreal ()> actualFunc,
145namespace {
147namespace {
146QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea)
148QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea)
147{
149{
148−
150+ QPointF localOrigin(0., 0.);
151+ QPointF localDirection;
149 switch (edgeDragArea->direction()) {
152 switch (edgeDragArea->direction()) {
150 case Direction::Upwards:
153 case Direction::Upwards:
151− return QPointF(0, -1);
154+ localDirection.rx() = 0.;
155+ localDirection.ry() = -1.;
156+ break;
152 case Direction::Downwards:
157 case Direction::Downwards:
153− return QPointF(0, 1);
158+ localDirection.rx() = 0.;
159+ localDirection.ry() = 1;
160+ break;
154 case Direction::Leftwards:
161 case Direction::Leftwards:
155− return QPointF(-1, 0);
162+ localDirection.rx() = -1.;
163+ localDirection.ry() = 0.;
164+ break;
156 default: // Direction::Rightwards:
165 default: // Direction::Rightwards:
157− return QPointF(1, 0);
166+ localDirection.rx() = 1.;
167+ localDirection.ry() = 0.;
168+ break;
158 }
169 }
170+ QPointF sceneOrigin = edgeDragArea->mapToScene(localOrigin);
171+ QPointF sceneDirection = edgeDragArea->mapToScene(localDirection);
172+ return sceneDirection - sceneOrigin;
159}
173}
160}
174}
161
175
@@ -222,6 +236,11 @@ qreal tst_DragHandle::fetchDragThreshold(DirectionalDragArea *dragHandle)
222 */
236 */
223void tst_DragHandle::dragThreshold_horizontal()
237void tst_DragHandle::dragThreshold_horizontal()
224{
238{
239+ QFETCH(qreal, rotation);
240+
241+ QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
242+ baseItem->setRotation(rotation);
243+
225 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
244 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
226
245
227 qreal dragThreshold = fetchDragThreshold(dragHandle);
246 qreal dragThreshold = fetchDragThreshold(dragHandle);
@@ -260,8 +279,21 @@ void tst_DragHandle::dragThreshold_horizontal()
260 QCOMPARE(parentItem->property("shown").toBool(), false);
279 QCOMPARE(parentItem->property("shown").toBool(), false);
261}
280}
262
281
282+void tst_DragHandle::dragThreshold_horizontal_data()
283+{
284+ QTest::addColumn<qreal>("rotation");
285+
286+ QTest::newRow("not rotated") << 0.;
287+ QTest::newRow("rotated 90") << 90.;
288+}
289+
263void tst_DragHandle::dragThreshold_vertical()
290void tst_DragHandle::dragThreshold_vertical()
264{
291{
292+ QFETCH(qreal, rotation);
293+
294+ QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
295+ baseItem->setRotation(rotation);
296+
265 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
297 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
266
298
267 qreal dragThreshold = fetchDragThreshold(dragHandle);
299 qreal dragThreshold = fetchDragThreshold(dragHandle);
@@ -300,6 +332,14 @@ void tst_DragHandle::dragThreshold_vertical()
300 QCOMPARE(parentItem->property("shown").toBool(), false);
332 QCOMPARE(parentItem->property("shown").toBool(), false);
301}
333}
302
334
335+void tst_DragHandle::dragThreshold_vertical_data()
336+{
337+ QTest::addColumn<qreal>("rotation");
338+
339+ QTest::newRow("not rotated") << 0.;
340+ QTest::newRow("rotated 90") << 90.;
341+}
342+
303/*
343/*
304 Checks that when the stretch property is true, dragging the DragHandle increases
344 Checks that when the stretch property is true, dragging the DragHandle increases
305 the width or height (depending on its direction) of its parent Showable
345 the width or height (depending on its direction) of its parent Showable
tests/qmltests/Components/tst_DragHandle.qml+72−56
@@ -29,62 +29,68 @@ import Ubuntu.Gestures 0.1
29 */
29 */
30Item {
30Item {
31 id: root
31 id: root
32− width: 700
32+ width: units.gu(70)
33− height: 500
33+ height: units.gu(70)
34
34
35 property var dragHandle
35 property var dragHandle
36
36
37 property bool stretch: false
37 property bool stretch: false
38 property real hintDisplacement: 0
38 property real hintDisplacement: 0
39
39
40− VerticalShowable {
40+ Item {
41− onDragHandleRecognizedGesture: { root.dragHandle = dragHandle }
41+ id: baseItem
42− stretch: root.stretch
42+ objectName: "baseItem"
43− hintDisplacement: root.hintDisplacement
43+ anchors.fill: parent
44− }
45−
46− HorizontalShowable {
47− onDragHandleRecognizedGesture: { root.dragHandle = dragHandle }
48− stretch: root.stretch
49− hintDisplacement: root.hintDisplacement
50− }
51
44
52− // Visually mark drag threshold
45+ VerticalShowable {
53− Rectangle {
46+ onDragHandleRecognizedGesture: { root.dragHandle = dragHandle }
54− color: "black"
47+ stretch: root.stretch
55− width: 2
48+ hintDisplacement: root.hintDisplacement
56− height: parent.height
49+ }
57
50
58− visible: dragHandle !== undefined && Direction.isHorizontal(dragHandle.direction)
51+ HorizontalShowable {
52+ onDragHandleRecognizedGesture: { root.dragHandle = dragHandle }
53+ stretch: root.stretch
54+ hintDisplacement: root.hintDisplacement
55+ }
59
56
57+ // Visually mark drag threshold
58+ Rectangle {
59+ color: "black"
60+ width: 2
61+ height: parent.height
62+
63+ visible: dragHandle !== undefined && Direction.isHorizontal(dragHandle.direction)
64+
60− x: {
65+ x: {
61− if (dragHandle) {
66+ if (dragHandle) {
62− if (dragHandle.direction === Direction.Rightwards) {
67+ if (dragHandle.direction === Direction.Rightwards) {
68+ dragHandle.edgeDragEvaluator.dragThreshold;
69+ } else {
63− dragHandle.edgeDragEvaluator.dragThreshold;
70+ parent.width - dragHandle.edgeDragEvaluator.dragThreshold;
71+ }
64 } else {
72 } else {
65− parent.width - dragHandle.edgeDragEvaluator.dragThreshold;
73+ 0
66 }
74 }
67− } else {
68− 0
69 }
75 }
70 }
76 }
71− }
72− Rectangle {
77+ Rectangle {
73− color: "black";
78+ color: "black";
74− height: 2
79+ height: 2
75− width: parent.width
80+ width: parent.width
76−
81+
77− visible: dragHandle !== undefined && Direction.isVertical(dragHandle.direction)
82+ visible: dragHandle !== undefined && Direction.isVertical(dragHandle.direction)
78−
83+
79− y: {
84+ y: {
80− if (dragHandle) {
85+ if (dragHandle) {
81− if (dragHandle.direction === Direction.Downwards) {
86+ if (dragHandle.direction === Direction.Downwards) {
87+ dragHandle.edgeDragEvaluator.dragThreshold;
88+ } else {
82− dragHandle.edgeDragEvaluator.dragThreshold;
89+ parent.height - dragHandle.edgeDragEvaluator.dragThreshold;
90+ }
83 } else {
91 } else {
84− parent.height - dragHandle.edgeDragEvaluator.dragThreshold;
92+ 0
85 }
93 }
86− } else {
87− 0
88 }
94 }
89 }
95 }
90 }
96 }
@@ -119,27 +125,37 @@ Item {
119 }
125 }
120 }
126 }
121
127
122− TestButton {
128+ Row {
123− id: stretchButton
124 anchors.bottom: parent.bottom
129 anchors.bottom: parent.bottom
125 anchors.left: parent.left
130 anchors.left: parent.left
126 anchors.margins: units.gu(1)
131 anchors.margins: units.gu(1)
132+ spacing: units.gu(1)
127
133
134+ Button {
135+ id: stretchButton
128− text: root.stretch ? "stretch" : "move"
136+ text: root.stretch ? "stretch" : "move"
129− onClicked: { root.stretch = !root.stretch; }
137+ onClicked: { root.stretch = !root.stretch; }
130− }
138+ }
131
139
132− TestButton {
140+ Button {
133− anchors.bottom: parent.bottom
141+ text: root.hintDisplacement > 0 ? "hint" : "no hint"
134− anchors.left: stretchButton.right
142+ onClicked: {
135− anchors.margins: units.gu(1)
143+ if (root.hintDisplacement > 0) {
144+ root.hintDisplacement = 0;
145+ } else {
146+ root.hintDisplacement = units.gu(6);
147+ }
148+ }
149+ }
136
150
137− text: root.hintDisplacement > 0 ? "hint" : "no hint"
151+ Button {
152+ text: "rotation: " + baseItem.rotation
138− onClicked: {
153+ onClicked: {
139− if (root.hintDisplacement > 0) {
154+ if (baseItem.rotation === 0.0) {
140− root.hintDisplacement = 0;
155+ baseItem.rotation = 90.0
141− } else {
156+ } else {
142− root.hintDisplacement = units.gu(6);
157+ baseItem.rotation = 0.0
158+ }
143 }
159 }
144 }
160 }
145 }
161 }
tests/qmltests/Components/tst_DragHandle/TestButton.qmldeleted−41
@@ -1,41 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−
20−Rectangle {
21− id: rect
22− width: childrenRect.width
23− height: childrenRect.height
24−
25− property alias text: textComponent.text
26−
27− signal clicked
28−
29− color: "yellow"
30− Text {
31− id: textComponent
32− font.pixelSize: units.gu(2)
33− text: root.stretch ? "stretch" : "move"
34− MouseArea {
35− anchors.fill: parent
36− onClicked: {
37− rect.clicked()
38− }
39− }
40− }
41−}