on 67edfcc help sign in
unity8/tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp
854 lines 31 kB history raw
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 */
17#include <QtTest/QtTest>
18#include <QtCore/QObject>
19#include <QtQuick/QQuickView>
20#include <QtQml/QQmlEngine>
21#include <QPointer>
23#include <DirectionalDragArea.h>
25#include "GestureTest.h"
27using namespace UbuntuGestures;
29class FakeTimer : public AbstractTimer
31 Q_OBJECT
32public:
33 FakeTimer(const SharedTimeSource &timeSource, QObject *parent = 0)
34 : UbuntuGestures::AbstractTimer(parent),
35 m_timeSource(timeSource)
36 {}
38 int interval() const override { return m_interval; }
39 void setInterval(int msecs) override { m_interval = msecs; }
40 void start() override {
41 AbstractTimer::start();
42 m_nextTimeoutTime = m_timeSource->msecsSinceReference() + (qint64)m_interval;
43 }
45 void emitTimeout() {
46 m_nextTimeoutTime += m_interval;
47 Q_EMIT timeout();
48 }
50 qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
51private:
52 int m_interval;
53 SharedTimeSource m_timeSource;
54 qint64 m_nextTimeoutTime;
55};
57class FakeTimeSource : public UbuntuGestures::TimeSource
59public:
60 FakeTimeSource() { m_msecsSinceReference = 0; }
61 virtual qint64 msecsSinceReference() {return m_msecsSinceReference;}
62 qint64 m_msecsSinceReference;
63};
65class tst_DirectionalDragArea: public GestureTest
67 Q_OBJECT
68public:
69 tst_DirectionalDragArea();
70private Q_SLOTS:
71 void init(); // called right before each and every test function is executed
72 void cleanup(); // called right after each and every test function is executed
74 void edgeDrag();
75 void edgeDrag_data();
76 void dragWithShortDirectionChange();
77 void minSpeed();
78 void minSpeed_data();
79 void recognitionTimerUsage();
80 void maxSilenceTime();
81 void sceneXAndX();
82 void sceneYAndY();
83 void twoFingerTap();
84 void movingDDA();
85 void ignoreOldFinger();
86 void rotated();
87 void sceneDistance();
88 void sceneDistance_data();
89 void disabledWhileDragging();
91private:
92 void passTime(qint64 timeSpan);
93 FakeTimer *fakeTimer;
94 QSharedPointer<FakeTimeSource> fakeTimeSource;
95};
97tst_DirectionalDragArea::tst_DirectionalDragArea()
98 : GestureTest(QStringLiteral("tst_DirectionalDragArea.qml"))
102void tst_DirectionalDragArea::init()
104 GestureTest::init();
106 // We shouldn't need the three lines below, but a compiz/unity7
107 // regression means we don't pass the test without them because
108 // the window doesn't have the proper size. Can be removed in the
109 // future if the regression is fixed and tests pass again
110 m_view->resize(m_view->rootObject()->width(), m_view->rootObject()->height());
111 QTRY_COMPARE(m_view->width(), (int)m_view->rootObject()->width());
112 QTRY_COMPARE(m_view->height(), (int)m_view->rootObject()->height());
114 fakeTimeSource.reset(new FakeTimeSource);
115 fakeTimer = new FakeTimer(fakeTimeSource);
118void tst_DirectionalDragArea::cleanup()
120 delete fakeTimer;
121 fakeTimer = 0;
123 fakeTimeSource.reset();
125 GestureTest::cleanup();
128void tst_DirectionalDragArea::passTime(qint64 timeSpan)
130 qint64 finalTime = fakeTimeSource->m_msecsSinceReference + timeSpan;
132 if (fakeTimer->isRunning() && finalTime >= fakeTimer->nextTimeoutTime()) {
133 fakeTimeSource->m_msecsSinceReference = fakeTimer->nextTimeoutTime();
134 fakeTimer->emitTimeout();
136 qint64 timeSpanRemainder = finalTime - fakeTimeSource->m_msecsSinceReference;
137 if (timeSpanRemainder > 0) {
138 passTime(timeSpanRemainder);
139 }
140 } else {
141 fakeTimeSource->m_msecsSinceReference = finalTime;
142 }
145namespace {
146QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea)
148 QPointF localCenter(edgeDragArea->width() / 2., edgeDragArea->height() / 2.);
149 return edgeDragArea->mapToScene(localCenter);
152QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea,
153 qreal wideningAngleMultiplier)
155 qreal angleRadians = edgeDragArea->wideningAngle() * wideningAngleMultiplier
156 * M_PI / 180.0;
158 qreal angleCos = qCos(angleRadians);
159 qreal angleSin = qSin(angleRadians);
161 switch (edgeDragArea->direction()) {
162 case Direction::Upwards:
163 return QPointF(angleSin, -angleCos);
164 case Direction::Downwards:
165 return QPointF(angleSin, angleCos);
166 case Direction::Leftwards:
167 return QPointF(-angleCos, angleSin);
168 default: // Direction::Rightwards:
169 return QPointF(angleCos, angleSin);
170 }
173QPointF createTouchDeviation(DirectionalDragArea *edgeDragArea)
175 qreal deviation = edgeDragArea->maxDeviation() * 0.8;
177 if (Direction::isHorizontal(edgeDragArea->direction())) {
178 return QPointF(0, deviation);
179 } else {
180 return QPointF(deviation, 0);
181 }
185void tst_DirectionalDragArea::edgeDrag()
187 QFETCH(QString, dragAreaObjectName);
188 QFETCH(qreal, wideningAngleMultiplier);
189 QFETCH(qreal, dragDistanceFactor);
190 QFETCH(bool, expectGestureRecognition);
192 DirectionalDragArea *edgeDragArea =
193 m_view->rootObject()->findChild<DirectionalDragArea*>(dragAreaObjectName);
194 QVERIFY(edgeDragArea != 0);
195 edgeDragArea->setRecognitionTimer(fakeTimer);
196 edgeDragArea->setTimeSource(fakeTimeSource);
198 QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
200 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
201 QPointF touchPoint = initialTouchPos;
203 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*dragDistanceFactor;
204 QPointF dragDirectionVector = calculateDirectionVector(edgeDragArea,
205 wideningAngleMultiplier);
206 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
207 QPointF touchMovement = dragDirectionVector * movementStepDistance;
208 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
209 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
211 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
213 QCOMPARE(draggingSpy.count(), 1);
214 QCOMPARE(edgeDragArea->dragging(), true);
216 if (wideningAngleMultiplier > 0) {
217 // go close to the border of the valid area for this touch point
218 // in order to make it easier to leave it by dragging at an angle
219 // slightly bigger than the widening angle
220 touchPoint += createTouchDeviation(edgeDragArea);
221 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
222 }
224 for (int i = 0; i < totalMovementSteps; ++i) {
225 touchPoint += touchMovement;
226 passTime(movementTimeStepMs);
227 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
228 }
230 if (expectGestureRecognition)
231 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
233 if (edgeDragArea->status() == DirectionalDragArea::WaitingForTouch) {
234 QCOMPARE(edgeDragArea->dragging(), false);
235 QCOMPARE(draggingSpy.count(), 2);
236 }
238 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
240 QCOMPARE(draggingSpy.count(), 2);
241 QCOMPARE(edgeDragArea->dragging(), false);
244void tst_DirectionalDragArea::edgeDrag_data()
246 QTest::addColumn<QString>("dragAreaObjectName");
247 QTest::addColumn<qreal>("wideningAngleMultiplier");
248 QTest::addColumn<qreal>("dragDistanceFactor");
249 QTest::addColumn<bool>("expectGestureRecognition");
251 QTest::newRow("rightwards, tiny drag")
252 << "hpDragArea" << 0.0 << 0.2 << false;
254 QTest::newRow("rightwards, straight drag")
255 << "hpDragArea" << 0.0 << 3.0 << true;
257 QTest::newRow("rightwards, diagonal drag")
258 << "hpDragArea" << 0.9 << 3.0 << true;
260 QTest::newRow("rightwards, overly diagonal drag")
261 << "hpDragArea" << 2.0 << 3.0 << false;
263 QTest::newRow("leftwards, tiny drag")
264 << "hnDragArea" << 0.0 << 0.2 << false;
266 QTest::newRow("leftwards, straight drag")
267 << "hnDragArea" << 0.0 << 3.0 << true;
269 QTest::newRow("leftwards, diagonal drag")
270 << "hnDragArea" << 0.9 << 3.0 << true;
272 QTest::newRow("downwards, tiny drag")
273 << "vpDragArea" << 0.0 << 0.2 << false;
275 QTest::newRow("downwards, straight drag")
276 << "vpDragArea" << 0.0 << 3.0 << true;
278 QTest::newRow("downwards, diagonal drag")
279 << "vpDragArea" << 0.9 << 3.0 << true;
281 QTest::newRow("upwards, tiny drag")
282 << "vnDragArea" << 0.0 << 0.2 << false;
284 QTest::newRow("upwards, straight drag")
285 << "vnDragArea" << 0.0 << 3.0 << true;
287 QTest::newRow("upwards, diagonal drag")
288 << "vnDragArea" << 0.9 << 3.0 << true;
290 QTest::newRow("upwards, overly diagonal drag")
291 << "vnDragArea" << 2.0 << 3.0 << false;
294/*
295 A directional drag should still be recognized if there is a momentaneous, small,
296 change in the direction of a drag. That should be accounted as input noise and
297 therefore ignored.
298 */
299void tst_DirectionalDragArea::dragWithShortDirectionChange()
301 DirectionalDragArea *edgeDragArea =
302 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
303 QVERIFY(edgeDragArea != 0);
304 edgeDragArea->setRecognitionTimer(fakeTimer);
305 edgeDragArea->setTimeSource(fakeTimeSource);
307 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
308 QPointF touchPoint = initialTouchPos;
310 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0;
311 QPointF dragDirectionVector(1.0, 0.0);
312 qreal touchStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
313 // make sure we are above minimum speed
314 int touchStepTimeMs = (touchStepDistance / (edgeDragArea->minSpeed() * 5.0f)) * 1000.0f;
315 QPointF touchMovement = dragDirectionVector * touchStepDistance;
317 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
319 // Move a bit in the proper direction
320 for (int i=0; i < 3; ++i) {
321 touchPoint += touchMovement;
322 passTime(touchStepTimeMs);
323 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
324 }
326 // Then a sudden and small movement to the opposite direction
327 touchPoint -= touchMovement*0.2;
328 passTime(touchStepTimeMs*0.2);
329 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
331 // And then resume movment in the correct direction until it crosses the distance and time
332 // thresholds.
333 do {
334 touchPoint += touchMovement;
335 passTime(touchStepTimeMs);
336 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
337 } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance
338 || fakeTimeSource->m_msecsSinceReference < (edgeDragArea->compositionTime() * 1.5f));
340 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
342 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
345/*
346 Checks that a gesture will be rejected if it's slower than minSpeed while
347 status is Undecided.
348 */
349void tst_DirectionalDragArea::minSpeed()
351 QFETCH(qreal, minSpeed);
352 QFETCH(qreal, speed);
353 QFETCH(int, expectedStatusAfterSpeedCheck);
355 DirectionalDragArea *edgeDragArea =
356 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
357 QVERIFY(edgeDragArea != 0);
358 edgeDragArea->setRecognitionTimer(fakeTimer);
359 edgeDragArea->setTimeSource(fakeTimeSource);
361 // A really long, unattainable, number. We don't want it getting recognized before
362 // the speed checks we want have been performed
363 edgeDragArea->setDistanceThreshold(500000);
365 edgeDragArea->setMinSpeed(minSpeed);
367 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
368 QPointF touchPoint = initialTouchPos;
370 QPointF dragDirectionVector(1.0, 0.0);
371 qint64 timeStepMsecs = 10;
372 qreal distanceStep = (speed / 1000.0f) * timeStepMsecs;
373 QPointF touchMovement = dragDirectionVector * distanceStep;
375 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
377 // Move for a while to ensure our speed check is performed a couple of times
378 for (int i=0; i < 20; ++i) {
379 touchPoint += touchMovement;
380 passTime(timeStepMsecs);
381 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
382 }
384 QCOMPARE((int)edgeDragArea->status(), expectedStatusAfterSpeedCheck);
387void tst_DirectionalDragArea::minSpeed_data()
389 QTest::addColumn<qreal>("minSpeed");
390 QTest::addColumn<qreal>("speed");
391 QTest::addColumn<int>("expectedStatusAfterSpeedCheck");
393 QTest::newRow("slower than minSpeed") << 100.0 << 50.0 << (int)DirectionalDragArea::WaitingForTouch;
394 QTest::newRow("faster than minSpeed") << 100.0 << 150.0 << (int)DirectionalDragArea::Undecided;
397/*
398 Checks that the recognition timer is started and stopped appropriately.
399 I.e., check that it's running only while gesture recognition is taking place
400 (status == Undecided)
401 */
402void tst_DirectionalDragArea::recognitionTimerUsage()
404 DirectionalDragArea *edgeDragArea =
405 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
406 QVERIFY(edgeDragArea != 0);
407 edgeDragArea->setRecognitionTimer(fakeTimer);
408 edgeDragArea->setTimeSource(fakeTimeSource);
410 // don't let it interfere with our test
411 edgeDragArea->setMinSpeed(0.0);
413 int timeStepMs = 5; // some arbitrary small value.
415 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
416 QPointF touchPoint = initialTouchPos;
418 QPointF dragDirectionVector(1.0, 0.0);
419 QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.2f);
421 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
422 QVERIFY(!fakeTimer->isRunning());
424 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
426 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
427 QVERIFY(fakeTimer->isRunning());
429 // Move beyond distance threshold and composition time to ensure recognition
430 while (fakeTimeSource->m_msecsSinceReference <= edgeDragArea->compositionTime() ||
431 (touchPoint - initialTouchPos).manhattanLength() <= edgeDragArea->distanceThreshold()) {
433 QCOMPARE(edgeDragArea->status() == DirectionalDragArea::Undecided, fakeTimer->isRunning());
435 touchPoint += touchMovement;
436 passTime(timeStepMs);
437 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
438 }
440 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
441 QVERIFY(!fakeTimer->isRunning());
444/*
445 A gesture should be rejected if too much time has passed without any new input
446 events from it.
447 */
448void tst_DirectionalDragArea::maxSilenceTime()
450 DirectionalDragArea *edgeDragArea =
451 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
452 QVERIFY(edgeDragArea != 0);
453 edgeDragArea->setRecognitionTimer(fakeTimer);
454 edgeDragArea->setTimeSource(fakeTimeSource);
456 // Make sure this property is not disabled
457 edgeDragArea->setMaxSilenceTime(100);
459 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
460 QPointF touchPoint = initialTouchPos;
462 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
464 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
465 QVERIFY(fakeTimer->isRunning());
467 // Force timer to timeout until after maxSilenceTime has been reached
468 while (fakeTimeSource->m_msecsSinceReference < edgeDragArea->maxSilenceTime()) {
469 passTime(fakeTimer->interval());
470 }
472 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
475/*
476 Checks that it informs the X coordinate of the touch point in local and scene coordinates
477 correctly.
478 */
479void tst_DirectionalDragArea::sceneXAndX()
481 DirectionalDragArea *edgeDragArea =
482 m_view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
483 QVERIFY(edgeDragArea != 0);
484 edgeDragArea->setRecognitionTimer(fakeTimer);
485 edgeDragArea->setTimeSource(fakeTimeSource);
487 QPointF touchScenePos(m_view->width() - (edgeDragArea->width()/2.0f), m_view->height()/2.0f);
489 QTest::touchEvent(m_view, m_device).press(0, touchScenePos.toPoint());
491 QSignalSpy touchXSpy(edgeDragArea, SIGNAL(touchXChanged(qreal)));
492 QSignalSpy touchSceneXSpy(edgeDragArea, SIGNAL(touchSceneXChanged(qreal)));
494 touchScenePos.rx() = m_view->width() / 2;
495 QTest::touchEvent(m_view, m_device).move(0, touchScenePos.toPoint());
497 QCOMPARE(touchXSpy.count(), 1);
498 QCOMPARE(touchSceneXSpy.count(), 1);
499 QCOMPARE(edgeDragArea->touchX(), touchScenePos.x() - edgeDragArea->x());
500 QCOMPARE(edgeDragArea->touchSceneX(), touchScenePos.x());
503/*
504 Checks that it informs the Y coordinate of the touch point in local and scene coordinates
505 correctly.
506 */
507void tst_DirectionalDragArea::sceneYAndY()
509 DirectionalDragArea *edgeDragArea =
510 m_view->rootObject()->findChild<DirectionalDragArea*>("vnDragArea");
511 QVERIFY(edgeDragArea != 0);
512 edgeDragArea->setRecognitionTimer(fakeTimer);
513 edgeDragArea->setTimeSource(fakeTimeSource);
515 QPointF touchScenePos(m_view->width()/2.0f, m_view->height() - (edgeDragArea->height()/2.0f));
517 QTest::touchEvent(m_view, m_device).press(0, touchScenePos.toPoint());
519 QSignalSpy touchYSpy(edgeDragArea, SIGNAL(touchYChanged(qreal)));
520 QSignalSpy touchSceneYSpy(edgeDragArea, SIGNAL(touchSceneYChanged(qreal)));
522 touchScenePos.ry() = m_view->height() / 2;
523 QTest::touchEvent(m_view, m_device).move(0, touchScenePos.toPoint());
525 QCOMPARE(touchYSpy.count(), 1);
526 QCOMPARE(touchSceneYSpy.count(), 1);
527 QCOMPARE(edgeDragArea->touchY(), touchScenePos.y() - edgeDragArea->y());
528 QCOMPARE(edgeDragArea->touchSceneY(), touchScenePos.y());
531/*
532 Regression test for https://bugs.launchpad.net/bugs/1228336
534 Perform a quick two-finger double-tap and check that DirectionalDragArea properly
535 rejects those touch points. In the bug above it got confused and crashed.
536 */
537void tst_DirectionalDragArea::twoFingerTap()
539 DirectionalDragArea *edgeDragArea =
540 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
541 QVERIFY(edgeDragArea != 0);
542 edgeDragArea->setRecognitionTimer(fakeTimer);
543 edgeDragArea->setTimeSource(fakeTimeSource);
545 // Make touches evenly spaced along the edgeDragArea
546 QPoint touchAPos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
547 QPoint touchBPos(edgeDragArea->width()/2.0f, m_view->height()*0.66f);
549 qint64 timeStepMsecs = 5; // some arbitrary, small value
551 // Perform the first two-finger tap
552 // NB: using move() instead of stationary() becasue in the latter you cannot provide
553 // the touch position and therefore it's left with some garbage numbers.
554 QTest::touchEvent(m_view, m_device)
555 .press(0, touchAPos);
557 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
559 passTime(timeStepMsecs);
560 QTest::touchEvent(m_view, m_device)
561 .move(0, touchAPos)
562 .press(1, touchBPos);
564 // A second touch point appeared during recognition, reject immediately as this
565 // can't be a single-touch gesture anymore.
566 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
568 passTime(timeStepMsecs);
569 QTest::touchEvent(m_view, m_device)
570 .release(0, touchAPos)
571 .move(1, touchBPos);
573 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
575 passTime(timeStepMsecs);
576 QTest::touchEvent(m_view, m_device)
577 .release(1, touchBPos);
579 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
581 // Perform the second two-finger tap
583 passTime(timeStepMsecs);
584 QTest::touchEvent(m_view, m_device)
585 .press(0, touchAPos);
587 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
589 passTime(timeStepMsecs);
590 QTest::touchEvent(m_view, m_device)
591 .move(0, touchAPos)
592 .press(1, touchBPos);
594 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
596 passTime(timeStepMsecs);
597 QTest::touchEvent(m_view, m_device)
598 .release(0, touchAPos)
599 .move(1, touchBPos);
601 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
603 passTime(timeStepMsecs);
604 QTest::touchEvent(m_view, m_device)
605 .release(1, touchBPos);
607 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
610/*
611 Tests that gesture recognition works normally even if the DirectionalDragArea moves
612 during recognition.
613 This effectively means that we have to do gesture recognition with scene coordinates
614 instead of local item coordinates.
615 */
616void tst_DirectionalDragArea::movingDDA()
618 QQuickItem *rightwardsLauncher = m_view->rootObject()->findChild<QQuickItem*>("rightwardsLauncher");
619 Q_ASSERT(rightwardsLauncher != 0);
621 DirectionalDragArea *edgeDragArea =
622 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
623 Q_ASSERT(edgeDragArea != 0);
624 edgeDragArea->setRecognitionTimer(fakeTimer);
625 edgeDragArea->setTimeSource(fakeTimeSource);
627 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
628 QPointF touchPoint = initialTouchPos;
630 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
631 QPointF dragDirectionVector(1.0f, 0.0f);
633 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
634 QPointF touchMovement = dragDirectionVector * movementStepDistance;
635 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
636 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
638 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
640 // Move it far ahead along the direction of the gesture
641 // rightwardsLauncher is a parent of our DirectionalDragArea. So moving it will move our DDA
642 rightwardsLauncher->setX(rightwardsLauncher->x() + edgeDragArea->distanceThreshold() * 5.0f);
644 for (int i = 0; i < totalMovementSteps; ++i) {
645 touchPoint += touchMovement;
646 passTime(movementTimeStepMs);
647 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
648 }
650 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
652 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
655/*
656 The presence of and old, rejected, touch point lying on the DirectionalDragArea
657 shouldn't impede the recognition of a gesture from a new touch point.
658 */
659void tst_DirectionalDragArea::ignoreOldFinger()
661 DirectionalDragArea *edgeDragArea =
662 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
663 Q_ASSERT(edgeDragArea != 0);
664 edgeDragArea->setRecognitionTimer(fakeTimer);
665 edgeDragArea->setTimeSource(fakeTimeSource);
667 // Make touches evenly spaced along the edgeDragArea
668 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
669 QPoint touch1Pos(edgeDragArea->width()/2.0f, m_view->height()*0.66f);
671 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
673 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
675 // leave it lying around for some time
676 passTime(qMax(edgeDragArea->maxSilenceTime(), edgeDragArea->compositionTime()) * 10);
678 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
680 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
681 QPointF dragDirectionVector(1.0f, 0.0f);
683 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
684 QPointF touchMovement = dragDirectionVector * movementStepDistance;
685 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
686 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
688 QTest::touchEvent(m_view, m_device)
689 .move(0, touch0Pos)
690 .press(1, touch1Pos);
692 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
694 for (int i = 0; i < totalMovementSteps; ++i) {
695 touch1Pos += touchMovement.toPoint();
696 passTime(movementTimeStepMs);
697 QTest::touchEvent(m_view, m_device)
698 .move(0, touch0Pos)
699 .move(1, touch1Pos);
700 }
702 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
704 QTest::touchEvent(m_view, m_device)
705 .move(0, touch0Pos)
706 .release(1, touch1Pos);
708 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
711/*
712 A Rightwards DDA that is rotated 90 degrees clockwise should recognize gestures
713 that are done downwards in scene coordinates. I.e. the gesture recognition direction
714 should be in local coordinates, not scene coordinates.
715 */
716void tst_DirectionalDragArea::rotated()
718 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
719 baseItem->setRotation(90.);
721 QQuickItem *rightwardsLauncher = m_view->rootObject()->findChild<QQuickItem*>("rightwardsLauncher");
722 Q_ASSERT(rightwardsLauncher != 0);
724 DirectionalDragArea *edgeDragArea =
725 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
726 Q_ASSERT(edgeDragArea != 0);
727 edgeDragArea->setRecognitionTimer(fakeTimer);
728 edgeDragArea->setTimeSource(fakeTimeSource);
730 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
731 QPointF touchPoint = initialTouchPos;
733 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
734 QPointF dragDirectionVector(0.0f, 1.0f);
736 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
737 QPointF touchMovement = dragDirectionVector * movementStepDistance;
738 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
739 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
741 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
743 for (int i = 0; i < totalMovementSteps; ++i) {
744 touchPoint += touchMovement;
745 passTime(movementTimeStepMs);
746 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
747 }
749 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
751 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
754void tst_DirectionalDragArea::sceneDistance()
756 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
757 QFETCH(qreal, rotation);
758 QFETCH(QPointF, dragDirectionVector);
759 baseItem->setRotation(rotation);
761 QQuickItem *rightwardsLauncher = m_view->rootObject()->findChild<QQuickItem*>("rightwardsLauncher");
762 Q_ASSERT(rightwardsLauncher != 0);
764 DirectionalDragArea *edgeDragArea =
765 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
766 Q_ASSERT(edgeDragArea != 0);
767 edgeDragArea->setRecognitionTimer(fakeTimer);
768 edgeDragArea->setTimeSource(fakeTimeSource);
770 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
771 QPointF touchPoint = initialTouchPos;
773 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
775 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
776 QPointF touchMovement = dragDirectionVector * movementStepDistance;
777 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
778 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
780 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
782 for (int i = 0; i < totalMovementSteps; ++i) {
783 touchPoint += touchMovement;
784 passTime(movementTimeStepMs);
785 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
786 }
788 qreal actualDragDistance = ((qreal)totalMovementSteps) * movementStepDistance;
790 // DirectionalDragArea::sceneDistance() must match the actual drag distance as the
791 // drag was aligned with the gesture direction
792 // NB: qFuzzyCompare(), used internally by QCOMPARE(), is broken.
793 QVERIFY(qAbs(edgeDragArea->sceneDistance() - actualDragDistance) < 0.001);
795 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
798void tst_DirectionalDragArea::sceneDistance_data()
800 QTest::addColumn<qreal>("rotation");
801 QTest::addColumn<QPointF>("dragDirectionVector");
803 QTest::newRow("not rotated") << 0. << QPointF(1., 0.);
804 QTest::newRow("rotated by 90 degrees") << 90. << QPointF(0., 1.);
807/*
808 Regression test for https://bugs.launchpad.net/unity8/+bug/1276122
810 The bug:
811 If setting "enabled: false" while the DirectionalDragArea's (DDA) dragging
812 property is true, the DDA stays in that state and doesn't recover any more.
813*/
814void tst_DirectionalDragArea::disabledWhileDragging()
816 DirectionalDragArea *edgeDragArea =
817 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
818 Q_ASSERT(edgeDragArea != 0);
819 edgeDragArea->setRecognitionTimer(fakeTimer);
820 edgeDragArea->setTimeSource(fakeTimeSource);
822 QPointF touchPoint = calculateInitialTouchPos(edgeDragArea);
824 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
825 QPointF dragDirectionVector(1., 0.); // horizontal positive
827 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
828 QPointF touchMovement = dragDirectionVector * movementStepDistance;
829 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
830 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
832 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
834 for (int i = 0; i < totalMovementSteps; ++i) {
835 touchPoint += touchMovement;
836 passTime(movementTimeStepMs);
837 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
838 }
840 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
841 QCOMPARE(edgeDragArea->dragging(), true);
843 // disable the dragArea while it's being dragged.
844 edgeDragArea->setEnabled(false);
846 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
847 QCOMPARE(edgeDragArea->dragging(), false);
849 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
852QTEST_MAIN(tst_DirectionalDragArea)
854#include "tst_DirectionalDragArea.moc"