on 7f16e39 help sign in
unity8/tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp
1331 lines 48 kB history raw
1/*
2 * Copyright (C) 2013-2014 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>
22#include <private/qquickmousearea_p.h>
24// C++ std lib
25#include <functional>
27#include <DirectionalDragArea.h>
28#include <TouchRegistry.h>
30#include "GestureTest.h"
32using namespace UbuntuGestures;
34class TouchMemento {
35public:
36 TouchMemento(const QTouchEvent *touchEvent);
37 Qt::TouchPointStates touchPointStates;
38 QList<QTouchEvent::TouchPoint> touchPoints;
40 bool containsTouchWithId(int touchId) const;
41};
43class DummyItem : public QQuickItem
45 Q_OBJECT
46public:
47 DummyItem(QQuickItem *parent = 0);
49 QList<TouchMemento> touchEvents;
50 std::function<void(QTouchEvent*)> touchEventHandler;
51protected:
52 void touchEvent(QTouchEvent *event) override;
53private:
54 static void defaultTouchEventHandler(QTouchEvent *event);
55};
57class ComplexFakeTimer : public FakeTimer
59 Q_OBJECT
60public:
61 ComplexFakeTimer(const SharedTimeSource &timeSource, QObject *parent = 0)
62 : FakeTimer(parent),
63 m_timeSource(timeSource)
64 {}
66 void start() override {
67 AbstractTimer::start();
68 m_nextTimeoutTime = m_timeSource->msecsSinceReference() + (qint64)interval();
69 }
71 void emitTimeout() {
72 m_nextTimeoutTime += interval();
73 Q_EMIT timeout();
74 }
76 qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
78private:
79 SharedTimeSource m_timeSource;
80 qint64 m_nextTimeoutTime;
81};
83class FakeTimeSource : public UbuntuGestures::TimeSource
85public:
86 FakeTimeSource() { m_msecsSinceReference = 0; }
87 virtual qint64 msecsSinceReference() {return m_msecsSinceReference;}
88 qint64 m_msecsSinceReference;
89};
91/*
92 QQuickMouseArea::canceled() signal is not registered in the meta object system.
93 So using a QSignalSpy to track it won't work. Thus the only way to connect to it
94 is using its method address directly.
95 */
96class MouseAreaSpy : public QObject
98 Q_OBJECT
99public:
100 MouseAreaSpy(QQuickMouseArea *mouseArea)
101 : canceledCount(0)
102 {
103 connect(mouseArea, &QQuickMouseArea::canceled,
104 this, &MouseAreaSpy::onMouseAreaCanceled);
105 }
107 int canceledCount;
109private Q_SLOTS:
110 void onMouseAreaCanceled() {
111 ++canceledCount;
112 }
113};
115class tst_DirectionalDragArea: public GestureTest
117 Q_OBJECT
118public:
119 tst_DirectionalDragArea();
120private Q_SLOTS:
121 void init(); // called right before each and every test function is executed
122 void cleanup(); // called right after each and every test function is executed
124 void edgeDrag();
125 void edgeDrag_data();
126 void dragWithShortDirectionChange();
127 void minSpeed();
128 void minSpeed_data();
129 void recognitionTimerUsage();
130 void maxSilenceTime();
131 void sceneXAndX();
132 void sceneYAndY();
133 void twoFingerTap();
134 void movingDDA();
135 void ignoreOldFinger();
136 void rotated();
137 void sceneDistance();
138 void sceneDistance_data();
139 void disabledWhileDragging();
140 void oneFingerDownFollowedByLateSecondFingerDown();
141 void givesUpWhenLosesTouch();
142 void threeFingerDrag();
143 void immediateRecognitionWhenConstraintsDisabled();
144 void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition();
145 void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition_data();
146 void tappedSignal();
147 void tappedSignal_data();
148 void gettingTouchOwnershipMakesMouseAreaBehindGetCanceled();
150private:
151 void passTime(qint64 timeSpanMs);
152 TouchRegistry *touchRegistry;
153 ComplexFakeTimer *fakeTimer;
154 QSharedPointer<FakeTimeSource> fakeTimeSource;
155};
157tst_DirectionalDragArea::tst_DirectionalDragArea()
158 : GestureTest(QStringLiteral("tst_DirectionalDragArea.qml"))
162void tst_DirectionalDragArea::init()
164 GestureTest::init();
166 // We shouldn't need the three lines below, but a compiz/unity7
167 // regression means we don't pass the test without them because
168 // the window doesn't have the proper size. Can be removed in the
169 // future if the regression is fixed and tests pass again
170 m_view->resize(m_view->rootObject()->width(), m_view->rootObject()->height());
171 QTRY_COMPARE(m_view->width(), (int)m_view->rootObject()->width());
172 QTRY_COMPARE(m_view->height(), (int)m_view->rootObject()->height());
174 touchRegistry = new TouchRegistry;
175 m_view->installEventFilter(touchRegistry);
177 fakeTimeSource.reset(new FakeTimeSource);
178 fakeTimer = new ComplexFakeTimer(fakeTimeSource);
181void tst_DirectionalDragArea::cleanup()
183 m_view->removeEventFilter(touchRegistry);
184 delete touchRegistry;
185 touchRegistry = nullptr;
187 delete fakeTimer;
188 fakeTimer = 0;
190 fakeTimeSource.reset();
192 GestureTest::cleanup();
195void tst_DirectionalDragArea::passTime(qint64 timeSpanMs)
197 qint64 finalTime = fakeTimeSource->m_msecsSinceReference + timeSpanMs;
199 if (fakeTimer->isRunning() && finalTime >= fakeTimer->nextTimeoutTime()) {
200 fakeTimeSource->m_msecsSinceReference = fakeTimer->nextTimeoutTime();
201 fakeTimer->emitTimeout();
203 qint64 timeSpanRemainder = finalTime - fakeTimeSource->m_msecsSinceReference;
204 if (timeSpanRemainder > 0) {
205 passTime(timeSpanRemainder);
206 }
207 } else {
208 fakeTimeSource->m_msecsSinceReference = finalTime;
209 }
212namespace {
213QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea)
215 QPointF localCenter(edgeDragArea->width() / 2., edgeDragArea->height() / 2.);
216 return edgeDragArea->mapToScene(localCenter);
219QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea,
220 qreal wideningAngleMultiplier)
222 qreal angleRadians = edgeDragArea->wideningAngle() * wideningAngleMultiplier
223 * M_PI / 180.0;
225 qreal angleCos = qCos(angleRadians);
226 qreal angleSin = qSin(angleRadians);
228 switch (edgeDragArea->direction()) {
229 case Direction::Upwards:
230 return QPointF(angleSin, -angleCos);
231 case Direction::Downwards:
232 return QPointF(angleSin, angleCos);
233 case Direction::Leftwards:
234 return QPointF(-angleCos, angleSin);
235 default: // Direction::Rightwards:
236 return QPointF(angleCos, angleSin);
237 }
240QPointF createTouchDeviation(DirectionalDragArea *edgeDragArea)
242 qreal deviation = edgeDragArea->maxDeviation() * 0.8;
244 if (Direction::isHorizontal(edgeDragArea->direction())) {
245 return QPointF(0, deviation);
246 } else {
247 return QPointF(deviation, 0);
248 }
252void tst_DirectionalDragArea::edgeDrag()
254 QFETCH(QString, dragAreaObjectName);
255 QFETCH(qreal, wideningAngleMultiplier);
256 QFETCH(qreal, dragDistanceFactor);
257 QFETCH(bool, expectGestureRecognition);
259 DirectionalDragArea *edgeDragArea =
260 m_view->rootObject()->findChild<DirectionalDragArea*>(dragAreaObjectName);
261 QVERIFY(edgeDragArea != 0);
262 edgeDragArea->setRecognitionTimer(fakeTimer);
263 edgeDragArea->setTimeSource(fakeTimeSource);
265 QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
267 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
268 QPointF touchPoint = initialTouchPos;
270 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*dragDistanceFactor;
271 QPointF dragDirectionVector = calculateDirectionVector(edgeDragArea,
272 wideningAngleMultiplier);
273 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
274 QPointF touchMovement = dragDirectionVector * movementStepDistance;
275 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
276 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
278 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
280 QCOMPARE(draggingSpy.count(), 1);
281 QCOMPARE(edgeDragArea->dragging(), true);
283 if (wideningAngleMultiplier > 0) {
284 // go close to the border of the valid area for this touch point
285 // in order to make it easier to leave it by dragging at an angle
286 // slightly bigger than the widening angle
287 touchPoint += createTouchDeviation(edgeDragArea);
288 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
289 }
291 for (int i = 0; i < totalMovementSteps; ++i) {
292 touchPoint += touchMovement;
293 passTime(movementTimeStepMs);
294 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
295 }
297 if (expectGestureRecognition)
298 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
300 if (edgeDragArea->status() == DirectionalDragArea::WaitingForTouch) {
301 QCOMPARE(edgeDragArea->dragging(), false);
302 QCOMPARE(draggingSpy.count(), 2);
303 }
305 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
307 QCOMPARE(draggingSpy.count(), 2);
308 QCOMPARE(edgeDragArea->dragging(), false);
311void tst_DirectionalDragArea::edgeDrag_data()
313 QTest::addColumn<QString>("dragAreaObjectName");
314 QTest::addColumn<qreal>("wideningAngleMultiplier");
315 QTest::addColumn<qreal>("dragDistanceFactor");
316 QTest::addColumn<bool>("expectGestureRecognition");
318 QTest::newRow("rightwards, tiny drag")
319 << "hpDragArea" << 0.0 << 0.2 << false;
321 QTest::newRow("rightwards, straight drag")
322 << "hpDragArea" << 0.0 << 3.0 << true;
324 QTest::newRow("rightwards, diagonal drag")
325 << "hpDragArea" << 0.9 << 3.0 << true;
327 QTest::newRow("rightwards, overly diagonal drag")
328 << "hpDragArea" << 2.0 << 3.0 << false;
330 QTest::newRow("leftwards, tiny drag")
331 << "hnDragArea" << 0.0 << 0.2 << false;
333 QTest::newRow("leftwards, straight drag")
334 << "hnDragArea" << 0.0 << 3.0 << true;
336 QTest::newRow("leftwards, diagonal drag")
337 << "hnDragArea" << 0.9 << 3.0 << true;
339 QTest::newRow("downwards, tiny drag")
340 << "vpDragArea" << 0.0 << 0.2 << false;
342 QTest::newRow("downwards, straight drag")
343 << "vpDragArea" << 0.0 << 3.0 << true;
345 QTest::newRow("downwards, diagonal drag")
346 << "vpDragArea" << 0.9 << 3.0 << true;
348 QTest::newRow("upwards, tiny drag")
349 << "vnDragArea" << 0.0 << 0.2 << false;
351 QTest::newRow("upwards, straight drag")
352 << "vnDragArea" << 0.0 << 3.0 << true;
354 QTest::newRow("upwards, diagonal drag")
355 << "vnDragArea" << 0.9 << 3.0 << true;
357 QTest::newRow("upwards, overly diagonal drag")
358 << "vnDragArea" << 2.0 << 3.0 << false;
361/*
362 A directional drag should still be recognized if there is a momentaneous, small,
363 change in the direction of a drag. That should be accounted as input noise and
364 therefore ignored.
365 */
366void tst_DirectionalDragArea::dragWithShortDirectionChange()
368 DirectionalDragArea *edgeDragArea =
369 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
370 QVERIFY(edgeDragArea != 0);
371 edgeDragArea->setRecognitionTimer(fakeTimer);
372 edgeDragArea->setTimeSource(fakeTimeSource);
374 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
375 QPointF touchPoint = initialTouchPos;
377 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0;
378 QPointF dragDirectionVector(1.0, 0.0);
379 qreal touchStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
380 // make sure we are above minimum speed
381 int touchStepTimeMs = (touchStepDistance / (edgeDragArea->minSpeed() * 5.0f)) * 1000.0f;
382 QPointF touchMovement = dragDirectionVector * touchStepDistance;
384 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
386 // Move a bit in the proper direction
387 for (int i=0; i < 3; ++i) {
388 touchPoint += touchMovement;
389 passTime(touchStepTimeMs);
390 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
391 }
393 // Then a sudden and small movement to the opposite direction
394 touchPoint -= touchMovement*0.2;
395 passTime(touchStepTimeMs*0.2);
396 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
398 // And then resume movment in the correct direction until it crosses the distance and time
399 // thresholds.
400 do {
401 touchPoint += touchMovement;
402 passTime(touchStepTimeMs);
403 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
404 } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance
405 || fakeTimeSource->m_msecsSinceReference < (edgeDragArea->compositionTime() * 1.5f));
407 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
409 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
412/*
413 Checks that a gesture will be rejected if it's slower than minSpeed while
414 status is Undecided.
415 */
416void tst_DirectionalDragArea::minSpeed()
418 QFETCH(qreal, minSpeed);
419 QFETCH(qreal, speed);
420 QFETCH(int, expectedStatusAfterSpeedCheck);
422 DirectionalDragArea *edgeDragArea =
423 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
424 QVERIFY(edgeDragArea != 0);
425 edgeDragArea->setRecognitionTimer(fakeTimer);
426 edgeDragArea->setTimeSource(fakeTimeSource);
428 // A really long, unattainable, number. We don't want it getting recognized before
429 // the speed checks we want have been performed
430 edgeDragArea->setDistanceThreshold(500000);
432 edgeDragArea->setMinSpeed(minSpeed);
434 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
435 QPointF touchPoint = initialTouchPos;
437 QPointF dragDirectionVector(1.0, 0.0);
438 qint64 timeStepMsecs = 10;
439 qreal distanceStep = (speed / 1000.0f) * timeStepMsecs;
440 QPointF touchMovement = dragDirectionVector * distanceStep;
442 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
444 // Move for a while to ensure our speed check is performed a couple of times
445 for (int i=0; i < 20; ++i) {
446 touchPoint += touchMovement;
447 passTime(timeStepMsecs);
448 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
449 }
451 QCOMPARE((int)edgeDragArea->status(), expectedStatusAfterSpeedCheck);
454void tst_DirectionalDragArea::minSpeed_data()
456 QTest::addColumn<qreal>("minSpeed");
457 QTest::addColumn<qreal>("speed");
458 QTest::addColumn<int>("expectedStatusAfterSpeedCheck");
460 QTest::newRow("slower than minSpeed") << 100.0 << 50.0 << (int)DirectionalDragArea::WaitingForTouch;
461 QTest::newRow("faster than minSpeed") << 100.0 << 150.0 << (int)DirectionalDragArea::Undecided;
464/*
465 Checks that the recognition timer is started and stopped appropriately.
466 I.e., check that it's running only while gesture recognition is taking place
467 (status == Undecided)
468 */
469void tst_DirectionalDragArea::recognitionTimerUsage()
471 DirectionalDragArea *edgeDragArea =
472 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
473 QVERIFY(edgeDragArea != 0);
474 edgeDragArea->setRecognitionTimer(fakeTimer);
475 edgeDragArea->setTimeSource(fakeTimeSource);
477 // don't let it interfere with our test
478 edgeDragArea->setMinSpeed(0.0);
480 int timeStepMs = 5; // some arbitrary small value.
482 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
483 QPointF touchPoint = initialTouchPos;
485 QPointF dragDirectionVector(1.0, 0.0);
486 QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.2f);
488 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
489 QVERIFY(!fakeTimer->isRunning());
491 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
493 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
494 QVERIFY(fakeTimer->isRunning());
496 // Move beyond distance threshold and composition time to ensure recognition
497 while (fakeTimeSource->m_msecsSinceReference <= edgeDragArea->compositionTime() ||
498 (touchPoint - initialTouchPos).manhattanLength() <= edgeDragArea->distanceThreshold()) {
500 QCOMPARE(edgeDragArea->status() == DirectionalDragArea::Undecided, fakeTimer->isRunning());
502 touchPoint += touchMovement;
503 passTime(timeStepMs);
504 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
505 }
507 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
508 QVERIFY(!fakeTimer->isRunning());
511/*
512 A gesture should be rejected if too much time has passed without any new input
513 events from it.
514 */
515void tst_DirectionalDragArea::maxSilenceTime()
517 DirectionalDragArea *edgeDragArea =
518 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
519 QVERIFY(edgeDragArea != 0);
520 edgeDragArea->setRecognitionTimer(fakeTimer);
521 edgeDragArea->setTimeSource(fakeTimeSource);
523 // Make sure this property is not disabled
524 edgeDragArea->setMaxSilenceTime(100);
526 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
527 QPointF touchPoint = initialTouchPos;
529 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
531 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
532 QVERIFY(fakeTimer->isRunning());
534 // Force timer to timeout until after maxSilenceTime has been reached
535 while (fakeTimeSource->m_msecsSinceReference < edgeDragArea->maxSilenceTime()) {
536 passTime(fakeTimer->interval());
537 }
539 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
542/*
543 Checks that it informs the X coordinate of the touch point in local and scene coordinates
544 correctly.
545 */
546void tst_DirectionalDragArea::sceneXAndX()
548 DirectionalDragArea *edgeDragArea =
549 m_view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
550 QVERIFY(edgeDragArea != 0);
551 edgeDragArea->setRecognitionTimer(fakeTimer);
552 edgeDragArea->setTimeSource(fakeTimeSource);
554 QPointF touchScenePos(m_view->width() - (edgeDragArea->width()/2.0f), m_view->height()/2.0f);
556 QTest::touchEvent(m_view, m_device).press(0, touchScenePos.toPoint());
558 QSignalSpy touchXSpy(edgeDragArea, SIGNAL(touchXChanged(qreal)));
559 QSignalSpy touchSceneXSpy(edgeDragArea, SIGNAL(touchSceneXChanged(qreal)));
561 touchScenePos.rx() = m_view->width() / 2;
562 QTest::touchEvent(m_view, m_device).move(0, touchScenePos.toPoint());
564 QCOMPARE(touchXSpy.count(), 1);
565 QCOMPARE(touchSceneXSpy.count(), 1);
566 QCOMPARE(edgeDragArea->touchX(), touchScenePos.x() - edgeDragArea->x());
567 QCOMPARE(edgeDragArea->touchSceneX(), touchScenePos.x());
570/*
571 Checks that it informs the Y coordinate of the touch point in local and scene coordinates
572 correctly.
573 */
574void tst_DirectionalDragArea::sceneYAndY()
576 DirectionalDragArea *edgeDragArea =
577 m_view->rootObject()->findChild<DirectionalDragArea*>("vnDragArea");
578 QVERIFY(edgeDragArea != 0);
579 edgeDragArea->setRecognitionTimer(fakeTimer);
580 edgeDragArea->setTimeSource(fakeTimeSource);
582 QPointF touchScenePos(m_view->width()/2.0f, m_view->height() - (edgeDragArea->height()/2.0f));
584 QTest::touchEvent(m_view, m_device).press(0, touchScenePos.toPoint());
586 QSignalSpy touchYSpy(edgeDragArea, SIGNAL(touchYChanged(qreal)));
587 QSignalSpy touchSceneYSpy(edgeDragArea, SIGNAL(touchSceneYChanged(qreal)));
589 touchScenePos.ry() = m_view->height() / 2;
590 QTest::touchEvent(m_view, m_device).move(0, touchScenePos.toPoint());
592 QCOMPARE(touchYSpy.count(), 1);
593 QCOMPARE(touchSceneYSpy.count(), 1);
594 QCOMPARE(edgeDragArea->touchY(), touchScenePos.y() - edgeDragArea->y());
595 QCOMPARE(edgeDragArea->touchSceneY(), touchScenePos.y());
598/*
599 Regression test for https://bugs.launchpad.net/bugs/1228336
601 Perform a quick two-finger double-tap and check that DirectionalDragArea properly
602 rejects those touch points. In the bug above it got confused and crashed.
603 */
604void tst_DirectionalDragArea::twoFingerTap()
606 DirectionalDragArea *edgeDragArea =
607 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
608 QVERIFY(edgeDragArea != 0);
609 edgeDragArea->setRecognitionTimer(fakeTimer);
610 edgeDragArea->setTimeSource(fakeTimeSource);
612 // Make touches evenly spaced along the edgeDragArea
613 QPoint touchAPos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
614 QPoint touchBPos(edgeDragArea->width()/2.0f, m_view->height()*0.66f);
616 qint64 timeStepMsecs = 5; // some arbitrary, small value
618 // Perform the first two-finger tap
619 // NB: using move() instead of stationary() becasue in the latter you cannot provide
620 // the touch position and therefore it's left with some garbage numbers.
621 QTest::touchEvent(m_view, m_device)
622 .press(0, touchAPos);
624 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
626 passTime(timeStepMsecs);
627 QTest::touchEvent(m_view, m_device)
628 .move(0, touchAPos)
629 .press(1, touchBPos);
631 // A second touch point appeared during recognition, reject immediately as this
632 // can't be a single-touch gesture anymore.
633 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
635 passTime(timeStepMsecs);
636 QTest::touchEvent(m_view, m_device)
637 .release(0, touchAPos)
638 .move(1, touchBPos);
640 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
642 passTime(timeStepMsecs);
643 QTest::touchEvent(m_view, m_device)
644 .release(1, touchBPos);
646 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
648 // Perform the second two-finger tap
650 passTime(timeStepMsecs);
651 QTest::touchEvent(m_view, m_device)
652 .press(0, touchAPos);
654 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
656 passTime(timeStepMsecs);
657 QTest::touchEvent(m_view, m_device)
658 .move(0, touchAPos)
659 .press(1, touchBPos);
661 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
663 passTime(timeStepMsecs);
664 QTest::touchEvent(m_view, m_device)
665 .release(0, touchAPos)
666 .move(1, touchBPos);
668 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
670 passTime(timeStepMsecs);
671 QTest::touchEvent(m_view, m_device)
672 .release(1, touchBPos);
674 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
677/*
678 Tests that gesture recognition works normally even if the DirectionalDragArea moves
679 during recognition.
680 This effectively means that we have to do gesture recognition with scene coordinates
681 instead of local item coordinates.
682 */
683void tst_DirectionalDragArea::movingDDA()
685 QQuickItem *rightwardsLauncher = m_view->rootObject()->findChild<QQuickItem*>("rightwardsLauncher");
686 Q_ASSERT(rightwardsLauncher != 0);
688 DirectionalDragArea *edgeDragArea =
689 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
690 Q_ASSERT(edgeDragArea != 0);
691 edgeDragArea->setRecognitionTimer(fakeTimer);
692 edgeDragArea->setTimeSource(fakeTimeSource);
694 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
695 QPointF touchPoint = initialTouchPos;
697 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
698 QPointF dragDirectionVector(1.0f, 0.0f);
700 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
701 QPointF touchMovement = dragDirectionVector * movementStepDistance;
702 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
703 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
705 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
707 // Move it far ahead along the direction of the gesture
708 // rightwardsLauncher is a parent of our DirectionalDragArea. So moving it will move our DDA
709 rightwardsLauncher->setX(rightwardsLauncher->x() + edgeDragArea->distanceThreshold() * 5.0f);
711 for (int i = 0; i < totalMovementSteps; ++i) {
712 touchPoint += touchMovement;
713 passTime(movementTimeStepMs);
714 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
715 }
717 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
719 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
722/*
723 The presence of and old, rejected, touch point lying on the DirectionalDragArea
724 shouldn't impede the recognition of a gesture from a new touch point.
725 */
726void tst_DirectionalDragArea::ignoreOldFinger()
728 DirectionalDragArea *edgeDragArea =
729 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
730 Q_ASSERT(edgeDragArea != 0);
731 edgeDragArea->setRecognitionTimer(fakeTimer);
732 edgeDragArea->setTimeSource(fakeTimeSource);
734 // Make touches evenly spaced along the edgeDragArea
735 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
736 QPoint touch1Pos(edgeDragArea->width()/2.0f, m_view->height()*0.66f);
738 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
740 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
742 // leave it lying around for some time
743 passTime(qMax(edgeDragArea->maxSilenceTime(), edgeDragArea->compositionTime()) * 10);
745 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
747 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
748 QPointF dragDirectionVector(1.0f, 0.0f);
750 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
751 QPointF touchMovement = dragDirectionVector * movementStepDistance;
752 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
753 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
755 QTest::touchEvent(m_view, m_device)
756 .move(0, touch0Pos)
757 .press(1, touch1Pos);
759 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
761 for (int i = 0; i < totalMovementSteps; ++i) {
762 touch1Pos += touchMovement.toPoint();
763 passTime(movementTimeStepMs);
764 QTest::touchEvent(m_view, m_device)
765 .move(0, touch0Pos)
766 .move(1, touch1Pos);
767 }
769 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
771 QTest::touchEvent(m_view, m_device)
772 .move(0, touch0Pos)
773 .release(1, touch1Pos);
775 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
778/*
779 A Rightwards DDA that is rotated 90 degrees clockwise should recognize gestures
780 that are done downwards in scene coordinates. I.e. the gesture recognition direction
781 should be in local coordinates, not scene coordinates.
782 */
783void tst_DirectionalDragArea::rotated()
785 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
786 baseItem->setRotation(90.);
788 QQuickItem *rightwardsLauncher = m_view->rootObject()->findChild<QQuickItem*>("rightwardsLauncher");
789 Q_ASSERT(rightwardsLauncher != 0);
791 DirectionalDragArea *edgeDragArea =
792 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
793 Q_ASSERT(edgeDragArea != 0);
794 edgeDragArea->setRecognitionTimer(fakeTimer);
795 edgeDragArea->setTimeSource(fakeTimeSource);
797 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
798 QPointF touchPoint = initialTouchPos;
800 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
801 QPointF dragDirectionVector(0.0f, 1.0f);
803 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
804 QPointF touchMovement = dragDirectionVector * movementStepDistance;
805 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
806 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
808 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
810 for (int i = 0; i < totalMovementSteps; ++i) {
811 touchPoint += touchMovement;
812 passTime(movementTimeStepMs);
813 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
814 }
816 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
818 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
821void tst_DirectionalDragArea::sceneDistance()
823 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
824 QFETCH(qreal, rotation);
825 QFETCH(QPointF, dragDirectionVector);
826 baseItem->setRotation(rotation);
828 QQuickItem *rightwardsLauncher = m_view->rootObject()->findChild<QQuickItem*>("rightwardsLauncher");
829 Q_ASSERT(rightwardsLauncher != 0);
831 DirectionalDragArea *edgeDragArea =
832 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
833 Q_ASSERT(edgeDragArea != 0);
834 edgeDragArea->setRecognitionTimer(fakeTimer);
835 edgeDragArea->setTimeSource(fakeTimeSource);
837 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
838 QPointF touchPoint = initialTouchPos;
840 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
842 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
843 QPointF touchMovement = dragDirectionVector * movementStepDistance;
844 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
845 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
847 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
849 for (int i = 0; i < totalMovementSteps; ++i) {
850 touchPoint += touchMovement;
851 passTime(movementTimeStepMs);
852 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
853 }
855 qreal actualDragDistance = ((qreal)totalMovementSteps) * movementStepDistance;
857 // DirectionalDragArea::sceneDistance() must match the actual drag distance as the
858 // drag was aligned with the gesture direction
859 // NB: qFuzzyCompare(), used internally by QCOMPARE(), is broken.
860 QVERIFY(qAbs(edgeDragArea->sceneDistance() - actualDragDistance) < 0.001);
862 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
865void tst_DirectionalDragArea::sceneDistance_data()
867 QTest::addColumn<qreal>("rotation");
868 QTest::addColumn<QPointF>("dragDirectionVector");
870 QTest::newRow("not rotated") << 0. << QPointF(1., 0.);
871 QTest::newRow("rotated by 90 degrees") << 90. << QPointF(0., 1.);
874/*
875 Regression test for https://bugs.launchpad.net/unity8/+bug/1276122
877 The bug:
878 If setting "enabled: false" while the DirectionalDragArea's (DDA) dragging
879 property is true, the DDA stays in that state and doesn't recover any more.
880*/
881void tst_DirectionalDragArea::disabledWhileDragging()
883 DirectionalDragArea *edgeDragArea =
884 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
885 Q_ASSERT(edgeDragArea != 0);
886 edgeDragArea->setRecognitionTimer(fakeTimer);
887 edgeDragArea->setTimeSource(fakeTimeSource);
889 QPointF touchPoint = calculateInitialTouchPos(edgeDragArea);
891 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
892 QPointF dragDirectionVector(1., 0.); // horizontal positive
894 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
895 QPointF touchMovement = dragDirectionVector * movementStepDistance;
896 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
897 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
899 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
901 for (int i = 0; i < totalMovementSteps; ++i) {
902 touchPoint += touchMovement;
903 passTime(movementTimeStepMs);
904 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
905 }
907 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
908 QCOMPARE(edgeDragArea->dragging(), true);
910 // disable the dragArea while it's being dragged.
911 edgeDragArea->setEnabled(false);
913 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
914 QCOMPARE(edgeDragArea->dragging(), false);
916 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
919void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
921 DirectionalDragArea *edgeDragArea =
922 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
923 Q_ASSERT(edgeDragArea != 0);
924 edgeDragArea->setRecognitionTimer(fakeTimer);
925 edgeDragArea->setTimeSource(fakeTimeSource);
927 // Disable some constraints we're not interested in
928 edgeDragArea->setMaxSilenceTime(60 * 1000);
929 edgeDragArea->setMinSpeed(0);
931 // And ensure others have the values we want
932 edgeDragArea->setCompositionTime(60);
934 // Put an item right behind edgeDragArea to receive the touches ignored by it
935 DummyItem *dummyItem = new DummyItem;
936 dummyItem->setParentItem(edgeDragArea->parentItem());
937 dummyItem->setX(edgeDragArea->x());
938 dummyItem->setY(edgeDragArea->y());
939 dummyItem->setZ(edgeDragArea->z() - 1.0);
940 dummyItem->setWidth(edgeDragArea->width());
941 dummyItem->setHeight(edgeDragArea->height());
943 // Make touches evenly spaced along the edgeDragArea
944 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
945 QPoint touch1Pos(edgeDragArea->width()/2.0f, m_view->height()*0.66f);
947 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
949 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
951 // We are now going to be way beyond compositionTime
952 passTime(edgeDragArea->compositionTime()*3);
954 QTest::touchEvent(m_view, m_device)
955 .move(0, touch0Pos)
956 .press(1, touch1Pos);
958 // A new touch has come, but as it can't be composed with touch 0, it should be
959 // ignored/rejected by the DirectionalDragArea
960 // Therefore the last event received by dummyItem must have both touch points (0 and 1)
961 {
962 TouchMemento &touchMemento = dummyItem->touchEvents.last();
963 QCOMPARE(touchMemento.touchPoints.count(), 2);
964 QVERIFY(touchMemento.containsTouchWithId(0));
965 QVERIFY(touchMemento.containsTouchWithId(1));
966 }
968 passTime(30);
970 QTest::touchEvent(m_view, m_device)
971 .move(0, touch0Pos)
972 .move(1, touch1Pos);
974 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
976 passTime(5);
978 QTest::touchEvent(m_view, m_device)
979 .release(0, touch0Pos)
980 .move(1, touch1Pos);
982 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
984 passTime(5);
986 QTest::touchEvent(m_view, m_device)
987 .release(1, touch1Pos);
989 // Shouldn't be keepping info about touches that no longer exist or interest us
990 QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
992 delete dummyItem;
995void tst_DirectionalDragArea::givesUpWhenLosesTouch()
997 DirectionalDragArea *edgeDragArea =
998 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
999 Q_ASSERT(edgeDragArea != 0);
1000 edgeDragArea->setRecognitionTimer(fakeTimer);
1001 edgeDragArea->setTimeSource(fakeTimeSource);
1003 // Disable some constraints we're not interested in
1004 edgeDragArea->setMaxSilenceTime(60 * 1000);
1005 edgeDragArea->setMinSpeed(0);
1007 // Put an item right in front of edgeDragArea
1008 DummyItem *dummyItem = new DummyItem(edgeDragArea->parentItem());
1009 dummyItem->setX(edgeDragArea->x());
1010 dummyItem->setY(edgeDragArea->y());
1011 dummyItem->setZ(edgeDragArea->z() + 1.0);
1012 dummyItem->setWidth(edgeDragArea->width());
1013 dummyItem->setHeight(edgeDragArea->height());
1015 QPoint touchPos(edgeDragArea->width()/2.0f, m_view->height()/2.0f);
1017 dummyItem->touchEventHandler = [&](QTouchEvent *event) {
1018 touchRegistry->addCandidateOwnerForTouch(0, dummyItem);
1019 event->ignore();
1020 };
1022 QTest::touchEvent(m_view, m_device).press(0, touchPos);
1024 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1026 touchRegistry->requestTouchOwnership(0, dummyItem);
1028 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1030 dummyItem->grabTouchPoints({0});
1031 dummyItem->touchEventHandler = [&](QTouchEvent *event) { event->accept(); };
1033 passTime(5);
1034 QTest::touchEvent(m_view, m_device).release(0, touchPos);
1036 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1038 QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
1041void tst_DirectionalDragArea::threeFingerDrag()
1043 DirectionalDragArea *edgeDragArea =
1044 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1045 Q_ASSERT(edgeDragArea != 0);
1046 edgeDragArea->setRecognitionTimer(fakeTimer);
1047 edgeDragArea->setTimeSource(fakeTimeSource);
1049 // Disable some constraints we're not interested in
1050 edgeDragArea->setMaxSilenceTime(60 * 1000);
1051 edgeDragArea->setMinSpeed(0);
1053 // And ensure others have the values we want
1054 edgeDragArea->setCompositionTime(60);
1056 // Make touches evenly spaced along the edgeDragArea
1057 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.25f);
1058 QPoint touch1Pos(edgeDragArea->width()/2.0f, m_view->height()*0.50f);
1059 QPoint touch2Pos(edgeDragArea->width()/2.0f, m_view->height()*0.75f);
1061 QTest::touchEvent(m_view, m_device)
1062 .press(0, touch0Pos);
1064 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1066 passTime(5);
1067 QTest::touchEvent(m_view, m_device)
1068 .move(0, touch0Pos)
1069 .press(1, touch1Pos);
1071 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1073 passTime(5);
1074 QTest::touchEvent(m_view, m_device)
1075 .move(0, touch0Pos)
1076 .move(1, touch1Pos)
1077 .press(2, touch2Pos);
1079 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1081 passTime(10);
1082 QTest::touchEvent(m_view, m_device)
1083 .move(0, touch0Pos)
1084 .move(1, touch1Pos)
1085 .move(2, touch2Pos);
1087 passTime(200);
1088 QTest::touchEvent(m_view, m_device)
1089 .move(0, touch0Pos)
1090 .release(1, touch1Pos)
1091 .move(2, touch2Pos);
1093 passTime(10);
1094 QTest::touchEvent(m_view, m_device)
1095 .move(0, touch0Pos)
1096 .release(2, touch2Pos);
1098 passTime(5);
1099 QTest::touchEvent(m_view, m_device)
1100 .release(0, touch0Pos);
1102 // Shouldn't be keepping info about touches that no longer exist or interest us
1103 QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
1107 If all the relevant gesture recognition constraints/parameters have been disabled,
1108 it means that the gesture recognition itself has been disabled and DirectionalDragArea
1109 will therefore work like a simple touch area, merely reporting touch movement.
1110 */
1111void tst_DirectionalDragArea::immediateRecognitionWhenConstraintsDisabled()
1113 DirectionalDragArea *edgeDragArea =
1114 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1115 Q_ASSERT(edgeDragArea != 0);
1116 edgeDragArea->setRecognitionTimer(fakeTimer);
1117 edgeDragArea->setTimeSource(fakeTimeSource);
1119 // Disable the minimum amount of constraints to ensure immediate recognition
1120 edgeDragArea->setDistanceThreshold(0);
1121 edgeDragArea->setCompositionTime(0);
1123 // Put an item right behind edgeDragArea to receive the touches ignored by it
1124 DummyItem *dummyItem = new DummyItem;
1125 dummyItem->setParentItem(edgeDragArea->parentItem());
1126 dummyItem->setX(edgeDragArea->x());
1127 dummyItem->setY(edgeDragArea->y());
1128 dummyItem->setZ(edgeDragArea->z() - 1.0);
1129 dummyItem->setWidth(edgeDragArea->width());
1130 dummyItem->setHeight(edgeDragArea->height());
1132 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()/2.0f);
1134 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
1136 // check for immediate recognition
1137 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
1139 // and therefore it should have immediately grabbed the touch point,
1140 // not letting it leak to items behind him.
1141 QCOMPARE(dummyItem->touchEvents.count(), 0);
1144void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition()
1146 DirectionalDragArea *edgeDragArea =
1147 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1148 Q_ASSERT(edgeDragArea != 0);
1149 edgeDragArea->setRecognitionTimer(fakeTimer);
1150 edgeDragArea->setTimeSource(fakeTimeSource);
1152 QPointF touchPoint = calculateInitialTouchPos(edgeDragArea);
1154 // Move less than the minimum needed for the drag gesture recognition
1155 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*0.5f;
1156 QPointF dragDirectionVector(1., 0.); // horizontal positive
1158 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
1159 QPointF touchMovement = dragDirectionVector * movementStepDistance;
1160 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
1161 int movementTimeStepMs = (edgeDragArea->compositionTime() * 0.8f) / totalMovementSteps;
1163 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
1165 for (int i = 0; i < totalMovementSteps; ++i) {
1166 touchPoint += touchMovement;
1167 passTime(movementTimeStepMs);
1168 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
1171 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1173 // edgeDragArea should be an undecided candidate
1175 auto touchInfo = touchRegistry->findTouchInfo(0);
1176 QCOMPARE(touchInfo->candidates.size(), 1);
1177 QCOMPARE(touchInfo->candidates.at(0).item.data(), edgeDragArea);
1178 QCOMPARE(touchInfo->candidates.at(0).undecided, true);
1181 // disable the dragArea while it's still recognizing a possible drag gesture.
1182 QFETCH(bool, disable);
1183 if (disable) {
1184 edgeDragArea->setEnabled(false);
1185 } else {
1186 edgeDragArea->setVisible(false);
1189 // edgeDragArea should no longer be a candidate
1191 auto touchInfo = touchRegistry->findTouchInfo(0);
1192 QCOMPARE(touchInfo->candidates.size(), 0);
1195 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1197 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
1200void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition_data()
1202 QTest::addColumn<bool>("disable");
1204 QTest::newRow("disabled") << true;
1205 QTest::newRow("invisible") << false;
1208void tst_DirectionalDragArea::tappedSignal()
1210 DirectionalDragArea *edgeDragArea =
1211 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1212 QVERIFY(edgeDragArea != 0);
1213 edgeDragArea->setRecognitionTimer(fakeTimer);
1214 edgeDragArea->setTimeSource(fakeTimeSource);
1216 QFETCH(bool, immediateGestureRecognition);
1217 if (immediateGestureRecognition) {
1218 // Disable the minimum amount of constraints to ensure immediate recognition
1219 edgeDragArea->setDistanceThreshold(0);
1220 edgeDragArea->setCompositionTime(0);
1223 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()/2.0f);
1225 QSignalSpy tappedSpy(edgeDragArea, SIGNAL(tapped()));
1227 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
1228 passTime(edgeDragArea->maxTapDuration() / 2);
1229 QTest::touchEvent(m_view, m_device).release(0, touch0Pos);
1231 QCOMPARE(tappedSpy.count(), 1);
1234void tst_DirectionalDragArea::tappedSignal_data()
1236 QTest::addColumn<bool>("immediateGestureRecognition");
1238 QTest::newRow("immediate gesture recognition") << true;
1239 QTest::newRow("default gesture recognition") << false;
1242void tst_DirectionalDragArea::gettingTouchOwnershipMakesMouseAreaBehindGetCanceled()
1244 DirectionalDragArea *edgeDragArea =
1245 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1246 QVERIFY(edgeDragArea != nullptr);
1247 edgeDragArea->setRecognitionTimer(fakeTimer);
1248 edgeDragArea->setTimeSource(fakeTimeSource);
1250 QQuickMouseArea *mouseArea =
1251 m_view->rootObject()->findChild<QQuickMouseArea*>("mouseArea");
1252 QVERIFY(mouseArea != nullptr);
1254 MouseAreaSpy mouseAreaSpy(mouseArea);
1256 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
1257 QPointF touchPoint = initialTouchPos;
1259 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2;
1260 QPointF dragDirectionVector(1.0f, 0.0f); // rightwards
1261 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
1262 QPointF touchMovement = dragDirectionVector * movementStepDistance;
1263 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
1264 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
1266 QCOMPARE(mouseArea->pressed(), false);
1268 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
1270 // The TouchBegin passes through the DirectionalDragArea and reaches the MouseArea behind it,
1271 // where it's converted to a MouseEvent by QQuickWindow and sent to the MouseArea which then
1272 // accepts it. Making it get pressed.
1273 QCOMPARE(mouseArea->pressed(), true);
1274 QCOMPARE(mouseAreaSpy.canceledCount, 0);
1276 for (int i = 0; i < totalMovementSteps; ++i) {
1277 touchPoint += touchMovement;
1278 passTime(movementTimeStepMs);
1279 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
1282 // As the DirectionalDragArea recognizes the gesture, it grabs the touch from the MouseArea,
1283 // which should make the MouseArea get a cancelation event, which will then cause it to
1284 // reset its state (going back to "unpressed"/"released").
1285 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
1286 QCOMPARE(mouseArea->pressed(), false);
1287 QCOMPARE(mouseAreaSpy.canceledCount, 1);
1289 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
1292////////////////////////// TouchMemento /////////////////////////////
1294TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
1295 : touchPointStates(touchEvent->touchPointStates()), touchPoints(touchEvent->touchPoints())
1300bool TouchMemento::containsTouchWithId(int touchId) const
1302 for (int i = 0; i < touchPoints.count(); ++i) {
1303 if (touchPoints.at(i).id() == touchId) {
1304 return true;
1307 return false;
1310////////////////////////// DummyItem /////////////////////////////
1312DummyItem::DummyItem(QQuickItem *parent)
1313 : QQuickItem(parent)
1315 touchEventHandler = defaultTouchEventHandler;
1318void DummyItem::touchEvent(QTouchEvent *event)
1320 touchEvents.append(TouchMemento(event));
1321 touchEventHandler(event);
1324void DummyItem::defaultTouchEventHandler(QTouchEvent *event)
1326 event->accept();
1329QTEST_MAIN(tst_DirectionalDragArea)
1331#include "tst_DirectionalDragArea.moc"