on 724dc49 help sign in
unity8/tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp
1271 lines 46 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>
23#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
24 #include <private/qquickwindow_p.h>
25#endif
28#include <DirectionalDragArea.h>
29#include <TouchRegistry.h>
31#include "GestureTest.h"
33using namespace UbuntuGestures;
36class ComplexFakeTimer : public FakeTimer
38 Q_OBJECT
39public:
40 ComplexFakeTimer(const SharedTimeSource &timeSource, QObject *parent = 0)
41 : FakeTimer(parent),
42 m_timeSource(timeSource)
43 {}
45 void start() override {
46 AbstractTimer::start();
47 m_nextTimeoutTime = m_timeSource->msecsSinceReference() + (qint64)interval();
48 }
50 void emitTimeout() {
51 m_nextTimeoutTime += interval();
52 Q_EMIT timeout();
53 }
55 qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
57private:
58 SharedTimeSource m_timeSource;
59 qint64 m_nextTimeoutTime;
60};
62class FakeTimeSource : public UbuntuGestures::TimeSource
64public:
65 FakeTimeSource() { m_msecsSinceReference = 0; }
66 virtual qint64 msecsSinceReference() {return m_msecsSinceReference;}
67 qint64 m_msecsSinceReference;
68};
70/*
71 QQuickMouseArea::canceled() signal is not registered in the meta object system.
72 So using a QSignalSpy to track it won't work. Thus the only way to connect to it
73 is using its method address directly.
74 */
75class MouseAreaSpy : public QObject
77 Q_OBJECT
78public:
79 MouseAreaSpy(QQuickMouseArea *mouseArea)
80 : canceledCount(0)
81 {
82 connect(mouseArea, &QQuickMouseArea::canceled,
83 this, &MouseAreaSpy::onMouseAreaCanceled);
84 }
86 int canceledCount;
88private Q_SLOTS:
89 void onMouseAreaCanceled() {
90 ++canceledCount;
91 }
92};
94class tst_DirectionalDragArea: public GestureTest
96 Q_OBJECT
97public:
98 tst_DirectionalDragArea();
99private Q_SLOTS:
100 void init(); // called right before each and every test function is executed
101 void cleanup(); // called right after each and every test function is executed
103 void edgeDrag();
104 void edgeDrag_data();
105 void dragWithShortDirectionChange();
106 void minSpeed();
107 void minSpeed_data();
108 void recognitionTimerUsage();
109 void maxSilenceTime();
110 void sceneXAndX();
111 void sceneYAndY();
112 void twoFingerTap();
113 void movingDDA();
114 void ignoreOldFinger();
115 void rotated();
116 void sceneDistance();
117 void sceneDistance_data();
118 void disabledWhileDragging();
119 void oneFingerDownFollowedByLateSecondFingerDown();
120 void givesUpWhenLosesTouch();
121 void threeFingerDrag();
122 void immediateRecognitionWhenConstraintsDisabled();
123 void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition();
124 void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition_data();
125 void tappedSignal();
126 void tappedSignal_data();
127 void gettingTouchOwnershipMakesMouseAreaBehindGetCanceled();
129private:
130 void passTime(qint64 timeSpanMs);
131 ComplexFakeTimer *fakeTimer;
132 QSharedPointer<FakeTimeSource> fakeTimeSource;
133};
135tst_DirectionalDragArea::tst_DirectionalDragArea()
136 : GestureTest(QStringLiteral("tst_DirectionalDragArea.qml"))
140void tst_DirectionalDragArea::init()
142 GestureTest::init();
144 // We shouldn't need the three lines below, but a compiz/unity7
145 // regression means we don't pass the test without them because
146 // the window doesn't have the proper size. Can be removed in the
147 // future if the regression is fixed and tests pass again
148 m_view->resize(m_view->rootObject()->width(), m_view->rootObject()->height());
149 QTRY_COMPARE(m_view->width(), (int)m_view->rootObject()->width());
150 QTRY_COMPARE(m_view->height(), (int)m_view->rootObject()->height());
152 fakeTimeSource.reset(new FakeTimeSource);
153 fakeTimer = new ComplexFakeTimer(fakeTimeSource);
156void tst_DirectionalDragArea::cleanup()
158 delete fakeTimer;
159 fakeTimer = 0;
161 fakeTimeSource.reset();
163 GestureTest::cleanup();
166void tst_DirectionalDragArea::passTime(qint64 timeSpanMs)
168 qint64 finalTime = fakeTimeSource->m_msecsSinceReference + timeSpanMs;
170 if (fakeTimer->isRunning() && finalTime >= fakeTimer->nextTimeoutTime()) {
171 fakeTimeSource->m_msecsSinceReference = fakeTimer->nextTimeoutTime();
172 fakeTimer->emitTimeout();
174 qint64 timeSpanRemainder = finalTime - fakeTimeSource->m_msecsSinceReference;
175 if (timeSpanRemainder > 0) {
176 passTime(timeSpanRemainder);
177 }
178 } else {
179 fakeTimeSource->m_msecsSinceReference = finalTime;
180 }
183namespace {
184QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea)
186 QPointF localCenter(edgeDragArea->width() / 2., edgeDragArea->height() / 2.);
187 return edgeDragArea->mapToScene(localCenter);
190QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea,
191 qreal wideningAngleMultiplier)
193 qreal angleRadians = edgeDragArea->wideningAngle() * wideningAngleMultiplier
194 * M_PI / 180.0;
196 qreal angleCos = qCos(angleRadians);
197 qreal angleSin = qSin(angleRadians);
199 switch (edgeDragArea->direction()) {
200 case Direction::Upwards:
201 return QPointF(angleSin, -angleCos);
202 case Direction::Downwards:
203 return QPointF(angleSin, angleCos);
204 case Direction::Leftwards:
205 return QPointF(-angleCos, angleSin);
206 default: // Direction::Rightwards:
207 return QPointF(angleCos, angleSin);
208 }
211QPointF createTouchDeviation(DirectionalDragArea *edgeDragArea)
213 qreal deviation = edgeDragArea->maxDeviation() * 0.8;
215 if (Direction::isHorizontal(edgeDragArea->direction())) {
216 return QPointF(0, deviation);
217 } else {
218 return QPointF(deviation, 0);
219 }
223void tst_DirectionalDragArea::edgeDrag()
225 QFETCH(QString, dragAreaObjectName);
226 QFETCH(qreal, wideningAngleMultiplier);
227 QFETCH(qreal, dragDistanceFactor);
228 QFETCH(bool, expectGestureRecognition);
230 DirectionalDragArea *edgeDragArea =
231 m_view->rootObject()->findChild<DirectionalDragArea*>(dragAreaObjectName);
232 QVERIFY(edgeDragArea != 0);
233 edgeDragArea->setRecognitionTimer(fakeTimer);
234 edgeDragArea->setTimeSource(fakeTimeSource);
236 QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
238 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
239 QPointF touchPoint = initialTouchPos;
241 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*dragDistanceFactor;
242 QPointF dragDirectionVector = calculateDirectionVector(edgeDragArea,
243 wideningAngleMultiplier);
244 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
245 QPointF touchMovement = dragDirectionVector * movementStepDistance;
246 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
247 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
249 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
251 QCOMPARE(draggingSpy.count(), 1);
252 QCOMPARE(edgeDragArea->dragging(), true);
254 if (wideningAngleMultiplier > 0) {
255 // go close to the border of the valid area for this touch point
256 // in order to make it easier to leave it by dragging at an angle
257 // slightly bigger than the widening angle
258 touchPoint += createTouchDeviation(edgeDragArea);
259 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
260 }
262 for (int i = 0; i < totalMovementSteps; ++i) {
263 touchPoint += touchMovement;
264 passTime(movementTimeStepMs);
265 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
266 }
268 if (expectGestureRecognition)
269 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
271 if (edgeDragArea->status() == DirectionalDragArea::WaitingForTouch) {
272 QCOMPARE(edgeDragArea->dragging(), false);
273 QCOMPARE(draggingSpy.count(), 2);
274 }
276 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
278 QCOMPARE(draggingSpy.count(), 2);
279 QCOMPARE(edgeDragArea->dragging(), false);
282void tst_DirectionalDragArea::edgeDrag_data()
284 QTest::addColumn<QString>("dragAreaObjectName");
285 QTest::addColumn<qreal>("wideningAngleMultiplier");
286 QTest::addColumn<qreal>("dragDistanceFactor");
287 QTest::addColumn<bool>("expectGestureRecognition");
289 QTest::newRow("rightwards, tiny drag")
290 << "hpDragArea" << 0.0 << 0.2 << false;
292 QTest::newRow("rightwards, straight drag")
293 << "hpDragArea" << 0.0 << 3.0 << true;
295 QTest::newRow("rightwards, diagonal drag")
296 << "hpDragArea" << 0.9 << 3.0 << true;
298 QTest::newRow("rightwards, overly diagonal drag")
299 << "hpDragArea" << 2.0 << 3.0 << false;
301 QTest::newRow("leftwards, tiny drag")
302 << "hnDragArea" << 0.0 << 0.2 << false;
304 QTest::newRow("leftwards, straight drag")
305 << "hnDragArea" << 0.0 << 3.0 << true;
307 QTest::newRow("leftwards, diagonal drag")
308 << "hnDragArea" << 0.9 << 3.0 << true;
310 QTest::newRow("downwards, tiny drag")
311 << "vpDragArea" << 0.0 << 0.2 << false;
313 QTest::newRow("downwards, straight drag")
314 << "vpDragArea" << 0.0 << 3.0 << true;
316 QTest::newRow("downwards, diagonal drag")
317 << "vpDragArea" << 0.9 << 3.0 << true;
319 QTest::newRow("upwards, tiny drag")
320 << "vnDragArea" << 0.0 << 0.2 << false;
322 QTest::newRow("upwards, straight drag")
323 << "vnDragArea" << 0.0 << 3.0 << true;
325 QTest::newRow("upwards, diagonal drag")
326 << "vnDragArea" << 0.9 << 3.0 << true;
328 QTest::newRow("upwards, overly diagonal drag")
329 << "vnDragArea" << 2.0 << 3.0 << false;
332/*
333 A directional drag should still be recognized if there is a momentaneous, small,
334 change in the direction of a drag. That should be accounted as input noise and
335 therefore ignored.
336 */
337void tst_DirectionalDragArea::dragWithShortDirectionChange()
339 DirectionalDragArea *edgeDragArea =
340 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
341 QVERIFY(edgeDragArea != 0);
342 edgeDragArea->setRecognitionTimer(fakeTimer);
343 edgeDragArea->setTimeSource(fakeTimeSource);
345 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
346 QPointF touchPoint = initialTouchPos;
348 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0;
349 QPointF dragDirectionVector(1.0, 0.0);
350 qreal touchStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
351 // make sure we are above minimum speed
352 int touchStepTimeMs = (touchStepDistance / (edgeDragArea->minSpeed() * 5.0f)) * 1000.0f;
353 QPointF touchMovement = dragDirectionVector * touchStepDistance;
355 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
357 // Move a bit in the proper direction
358 for (int i=0; i < 3; ++i) {
359 touchPoint += touchMovement;
360 passTime(touchStepTimeMs);
361 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
362 }
364 // Then a sudden and small movement to the opposite direction
365 touchPoint -= touchMovement*0.2;
366 passTime(touchStepTimeMs*0.2);
367 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
369 // And then resume movment in the correct direction until it crosses the distance and time
370 // thresholds.
371 do {
372 touchPoint += touchMovement;
373 passTime(touchStepTimeMs);
374 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
375 } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance
376 || fakeTimeSource->m_msecsSinceReference < (edgeDragArea->compositionTime() * 1.5f));
378 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
380 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
383/*
384 Checks that a gesture will be rejected if it's slower than minSpeed while
385 status is Undecided.
386 */
387void tst_DirectionalDragArea::minSpeed()
389 QFETCH(qreal, minSpeed);
390 QFETCH(qreal, speed);
391 QFETCH(int, expectedStatusAfterSpeedCheck);
393 DirectionalDragArea *edgeDragArea =
394 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
395 QVERIFY(edgeDragArea != 0);
396 edgeDragArea->setRecognitionTimer(fakeTimer);
397 edgeDragArea->setTimeSource(fakeTimeSource);
399 // A really long, unattainable, number. We don't want it getting recognized before
400 // the speed checks we want have been performed
401 edgeDragArea->setDistanceThreshold(500000);
403 edgeDragArea->setMinSpeed(minSpeed);
405 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
406 QPointF touchPoint = initialTouchPos;
408 QPointF dragDirectionVector(1.0, 0.0);
409 qint64 timeStepMsecs = 10;
410 qreal distanceStep = (speed / 1000.0f) * timeStepMsecs;
411 QPointF touchMovement = dragDirectionVector * distanceStep;
413 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
415 // Move for a while to ensure our speed check is performed a couple of times
416 for (int i=0; i < 20; ++i) {
417 touchPoint += touchMovement;
418 passTime(timeStepMsecs);
419 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
420 }
422 QCOMPARE((int)edgeDragArea->status(), expectedStatusAfterSpeedCheck);
425void tst_DirectionalDragArea::minSpeed_data()
427 QTest::addColumn<qreal>("minSpeed");
428 QTest::addColumn<qreal>("speed");
429 QTest::addColumn<int>("expectedStatusAfterSpeedCheck");
431 QTest::newRow("slower than minSpeed") << 100.0 << 50.0 << (int)DirectionalDragArea::WaitingForTouch;
432 QTest::newRow("faster than minSpeed") << 100.0 << 150.0 << (int)DirectionalDragArea::Undecided;
435/*
436 Checks that the recognition timer is started and stopped appropriately.
437 I.e., check that it's running only while gesture recognition is taking place
438 (status == Undecided)
439 */
440void tst_DirectionalDragArea::recognitionTimerUsage()
442 DirectionalDragArea *edgeDragArea =
443 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
444 QVERIFY(edgeDragArea != 0);
445 edgeDragArea->setRecognitionTimer(fakeTimer);
446 edgeDragArea->setTimeSource(fakeTimeSource);
448 // don't let it interfere with our test
449 edgeDragArea->setMinSpeed(0.0);
451 int timeStepMs = 5; // some arbitrary small value.
453 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
454 QPointF touchPoint = initialTouchPos;
456 QPointF dragDirectionVector(1.0, 0.0);
457 QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.2f);
459 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
460 QVERIFY(!fakeTimer->isRunning());
462 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
464 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
465 QVERIFY(fakeTimer->isRunning());
467 // Move beyond distance threshold and composition time to ensure recognition
468 while (fakeTimeSource->m_msecsSinceReference <= edgeDragArea->compositionTime() ||
469 (touchPoint - initialTouchPos).manhattanLength() <= edgeDragArea->distanceThreshold()) {
471 QCOMPARE(edgeDragArea->status() == DirectionalDragArea::Undecided, fakeTimer->isRunning());
473 touchPoint += touchMovement;
474 passTime(timeStepMs);
475 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
476 }
478 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
479 QVERIFY(!fakeTimer->isRunning());
482/*
483 A gesture should be rejected if too much time has passed without any new input
484 events from it.
485 */
486void tst_DirectionalDragArea::maxSilenceTime()
488 DirectionalDragArea *edgeDragArea =
489 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
490 QVERIFY(edgeDragArea != 0);
491 edgeDragArea->setRecognitionTimer(fakeTimer);
492 edgeDragArea->setTimeSource(fakeTimeSource);
494 // Make sure this property is not disabled
495 edgeDragArea->setMaxSilenceTime(100);
497 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
498 QPointF touchPoint = initialTouchPos;
500 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
502 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
503 QVERIFY(fakeTimer->isRunning());
505 // Force timer to timeout until after maxSilenceTime has been reached
506 while (fakeTimeSource->m_msecsSinceReference < edgeDragArea->maxSilenceTime()) {
507 passTime(fakeTimer->interval());
508 }
510 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
513/*
514 Checks that it informs the X coordinate of the touch point in local and scene coordinates
515 correctly.
516 */
517void tst_DirectionalDragArea::sceneXAndX()
519 DirectionalDragArea *edgeDragArea =
520 m_view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
521 QVERIFY(edgeDragArea != 0);
522 edgeDragArea->setRecognitionTimer(fakeTimer);
523 edgeDragArea->setTimeSource(fakeTimeSource);
525 QPointF touchScenePos(m_view->width() - (edgeDragArea->width()/2.0f), m_view->height()/2.0f);
527 QTest::touchEvent(m_view, m_device).press(0, touchScenePos.toPoint());
529 QSignalSpy touchXSpy(edgeDragArea, SIGNAL(touchXChanged(qreal)));
530 QSignalSpy touchSceneXSpy(edgeDragArea, SIGNAL(touchSceneXChanged(qreal)));
532 touchScenePos.rx() = m_view->width() / 2;
533 QTest::touchEvent(m_view, m_device).move(0, touchScenePos.toPoint());
535 QCOMPARE(touchXSpy.count(), 1);
536 QCOMPARE(touchSceneXSpy.count(), 1);
537 QCOMPARE(edgeDragArea->touchX(), touchScenePos.x() - edgeDragArea->x());
538 QCOMPARE(edgeDragArea->touchSceneX(), touchScenePos.x());
541/*
542 Checks that it informs the Y coordinate of the touch point in local and scene coordinates
543 correctly.
544 */
545void tst_DirectionalDragArea::sceneYAndY()
547 DirectionalDragArea *edgeDragArea =
548 m_view->rootObject()->findChild<DirectionalDragArea*>("vnDragArea");
549 QVERIFY(edgeDragArea != 0);
550 edgeDragArea->setRecognitionTimer(fakeTimer);
551 edgeDragArea->setTimeSource(fakeTimeSource);
553 QPointF touchScenePos(m_view->width()/2.0f, m_view->height() - (edgeDragArea->height()/2.0f));
555 QTest::touchEvent(m_view, m_device).press(0, touchScenePos.toPoint());
557 QSignalSpy touchYSpy(edgeDragArea, SIGNAL(touchYChanged(qreal)));
558 QSignalSpy touchSceneYSpy(edgeDragArea, SIGNAL(touchSceneYChanged(qreal)));
560 touchScenePos.ry() = m_view->height() / 2;
561 QTest::touchEvent(m_view, m_device).move(0, touchScenePos.toPoint());
563 QCOMPARE(touchYSpy.count(), 1);
564 QCOMPARE(touchSceneYSpy.count(), 1);
565 QCOMPARE(edgeDragArea->touchY(), touchScenePos.y() - edgeDragArea->y());
566 QCOMPARE(edgeDragArea->touchSceneY(), touchScenePos.y());
569/*
570 Regression test for https://bugs.launchpad.net/bugs/1228336
572 Perform a quick two-finger double-tap and check that DirectionalDragArea properly
573 rejects those touch points. In the bug above it got confused and crashed.
574 */
575void tst_DirectionalDragArea::twoFingerTap()
577 DirectionalDragArea *edgeDragArea =
578 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
579 QVERIFY(edgeDragArea != 0);
580 edgeDragArea->setRecognitionTimer(fakeTimer);
581 edgeDragArea->setTimeSource(fakeTimeSource);
583 // Make touches evenly spaced along the edgeDragArea
584 QPoint touchAPos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
585 QPoint touchBPos(edgeDragArea->width()/2.0f, m_view->height()*0.66f);
587 qint64 timeStepMsecs = 5; // some arbitrary, small value
589 // Perform the first two-finger tap
590 // NB: using move() instead of stationary() becasue in the latter you cannot provide
591 // the touch position and therefore it's left with some garbage numbers.
592 QTest::touchEvent(m_view, m_device)
593 .press(0, touchAPos);
595 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
597 passTime(timeStepMsecs);
598 QTest::touchEvent(m_view, m_device)
599 .move(0, touchAPos)
600 .press(1, touchBPos);
602 // A second touch point appeared during recognition, reject immediately as this
603 // can't be a single-touch gesture anymore.
604 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
606 passTime(timeStepMsecs);
607 QTest::touchEvent(m_view, m_device)
608 .release(0, touchAPos)
609 .move(1, touchBPos);
611 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
613 passTime(timeStepMsecs);
614 QTest::touchEvent(m_view, m_device)
615 .release(1, touchBPos);
617 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
619 // Perform the second two-finger tap
621 passTime(timeStepMsecs);
622 QTest::touchEvent(m_view, m_device)
623 .press(0, touchAPos);
625 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
627 passTime(timeStepMsecs);
628 QTest::touchEvent(m_view, m_device)
629 .move(0, touchAPos)
630 .press(1, touchBPos);
632 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
634 passTime(timeStepMsecs);
635 QTest::touchEvent(m_view, m_device)
636 .release(0, touchAPos)
637 .move(1, touchBPos);
639 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
641 passTime(timeStepMsecs);
642 QTest::touchEvent(m_view, m_device)
643 .release(1, touchBPos);
645 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
648/*
649 Tests that gesture recognition works normally even if the DirectionalDragArea moves
650 during recognition.
651 This effectively means that we have to do gesture recognition with scene coordinates
652 instead of local item coordinates.
653 */
654void tst_DirectionalDragArea::movingDDA()
656 QQuickItem *rightwardsLauncher = m_view->rootObject()->findChild<QQuickItem*>("rightwardsLauncher");
657 Q_ASSERT(rightwardsLauncher != 0);
659 DirectionalDragArea *edgeDragArea =
660 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
661 Q_ASSERT(edgeDragArea != 0);
662 edgeDragArea->setRecognitionTimer(fakeTimer);
663 edgeDragArea->setTimeSource(fakeTimeSource);
665 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
666 QPointF touchPoint = initialTouchPos;
668 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
669 QPointF dragDirectionVector(1.0f, 0.0f);
671 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
672 QPointF touchMovement = dragDirectionVector * movementStepDistance;
673 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
674 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
676 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
678 // Move it far ahead along the direction of the gesture
679 // rightwardsLauncher is a parent of our DirectionalDragArea. So moving it will move our DDA
680 rightwardsLauncher->setX(rightwardsLauncher->x() + edgeDragArea->distanceThreshold() * 5.0f);
682 for (int i = 0; i < totalMovementSteps; ++i) {
683 touchPoint += touchMovement;
684 passTime(movementTimeStepMs);
685 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
686 }
688 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
690 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
693/*
694 The presence of and old, rejected, touch point lying on the DirectionalDragArea
695 shouldn't impede the recognition of a gesture from a new touch point.
696 */
697void tst_DirectionalDragArea::ignoreOldFinger()
699 DirectionalDragArea *edgeDragArea =
700 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
701 Q_ASSERT(edgeDragArea != 0);
702 edgeDragArea->setRecognitionTimer(fakeTimer);
703 edgeDragArea->setTimeSource(fakeTimeSource);
705 // Make touches evenly spaced along the edgeDragArea
706 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
707 QPoint touch1Pos(edgeDragArea->width()/2.0f, m_view->height()*0.66f);
709 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
711 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
713 // leave it lying around for some time
714 passTime(qMax(edgeDragArea->maxSilenceTime(), edgeDragArea->compositionTime()) * 10);
716 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
718 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
719 QPointF dragDirectionVector(1.0f, 0.0f);
721 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
722 QPointF touchMovement = dragDirectionVector * movementStepDistance;
723 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
724 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
726 QTest::touchEvent(m_view, m_device)
727 .move(0, touch0Pos)
728 .press(1, touch1Pos);
730 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
732 for (int i = 0; i < totalMovementSteps; ++i) {
733 touch1Pos += touchMovement.toPoint();
734 passTime(movementTimeStepMs);
735 QTest::touchEvent(m_view, m_device)
736 .move(0, touch0Pos)
737 .move(1, touch1Pos);
738 }
740 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
742 QTest::touchEvent(m_view, m_device)
743 .move(0, touch0Pos)
744 .release(1, touch1Pos);
746 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
749/*
750 A Rightwards DDA that is rotated 90 degrees clockwise should recognize gestures
751 that are done downwards in scene coordinates. I.e. the gesture recognition direction
752 should be in local coordinates, not scene coordinates.
753 */
754void tst_DirectionalDragArea::rotated()
756 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
757 baseItem->setRotation(90.);
759 QQuickItem *rightwardsLauncher = m_view->rootObject()->findChild<QQuickItem*>("rightwardsLauncher");
760 Q_ASSERT(rightwardsLauncher != 0);
762 DirectionalDragArea *edgeDragArea =
763 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
764 Q_ASSERT(edgeDragArea != 0);
765 edgeDragArea->setRecognitionTimer(fakeTimer);
766 edgeDragArea->setTimeSource(fakeTimeSource);
768 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
769 QPointF touchPoint = initialTouchPos;
771 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
772 QPointF dragDirectionVector(0.0f, 1.0f);
774 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
775 QPointF touchMovement = dragDirectionVector * movementStepDistance;
776 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
777 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
779 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
781 for (int i = 0; i < totalMovementSteps; ++i) {
782 touchPoint += touchMovement;
783 passTime(movementTimeStepMs);
784 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
785 }
787 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
789 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
792void tst_DirectionalDragArea::sceneDistance()
794 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
795 QFETCH(qreal, rotation);
796 QFETCH(QPointF, dragDirectionVector);
797 baseItem->setRotation(rotation);
799 QQuickItem *rightwardsLauncher = m_view->rootObject()->findChild<QQuickItem*>("rightwardsLauncher");
800 Q_ASSERT(rightwardsLauncher != 0);
802 DirectionalDragArea *edgeDragArea =
803 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
804 Q_ASSERT(edgeDragArea != 0);
805 edgeDragArea->setRecognitionTimer(fakeTimer);
806 edgeDragArea->setTimeSource(fakeTimeSource);
808 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
809 QPointF touchPoint = initialTouchPos;
811 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
813 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
814 QPointF touchMovement = dragDirectionVector * movementStepDistance;
815 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
816 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
818 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
820#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
821 QQuickWindowPrivate *wp = QQuickWindowPrivate::get(m_view);
822#endif
823 for (int i = 0; i < totalMovementSteps; ++i) {
824 touchPoint += touchMovement;
825 passTime(movementTimeStepMs);
826 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
827#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
828 wp->flushDelayedTouchEvent();
829#endif
830 }
832 qreal actualDragDistance = ((qreal)totalMovementSteps) * movementStepDistance;
834 // DirectionalDragArea::sceneDistance() must match the actual drag distance as the
835 // drag was aligned with the gesture direction
836 // NB: qFuzzyCompare(), used internally by QCOMPARE(), is broken.
837 QVERIFY(qAbs(edgeDragArea->sceneDistance() - actualDragDistance) < 0.001);
839 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
842void tst_DirectionalDragArea::sceneDistance_data()
844 QTest::addColumn<qreal>("rotation");
845 QTest::addColumn<QPointF>("dragDirectionVector");
847 QTest::newRow("not rotated") << 0. << QPointF(1., 0.);
848 QTest::newRow("rotated by 90 degrees") << 90. << QPointF(0., 1.);
851/*
852 Regression test for https://bugs.launchpad.net/unity8/+bug/1276122
854 The bug:
855 If setting "enabled: false" while the DirectionalDragArea's (DDA) dragging
856 property is true, the DDA stays in that state and doesn't recover any more.
857*/
858void tst_DirectionalDragArea::disabledWhileDragging()
860 DirectionalDragArea *edgeDragArea =
861 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
862 Q_ASSERT(edgeDragArea != 0);
863 edgeDragArea->setRecognitionTimer(fakeTimer);
864 edgeDragArea->setTimeSource(fakeTimeSource);
866 QPointF touchPoint = calculateInitialTouchPos(edgeDragArea);
868 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
869 QPointF dragDirectionVector(1., 0.); // horizontal positive
871 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
872 QPointF touchMovement = dragDirectionVector * movementStepDistance;
873 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
874 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
876 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
878 for (int i = 0; i < totalMovementSteps; ++i) {
879 touchPoint += touchMovement;
880 passTime(movementTimeStepMs);
881 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
882 }
884 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
885 QCOMPARE(edgeDragArea->dragging(), true);
887 // disable the dragArea while it's being dragged.
888 edgeDragArea->setEnabled(false);
890 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
891 QCOMPARE(edgeDragArea->dragging(), false);
893 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
896void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
898 DirectionalDragArea *edgeDragArea =
899 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
900 Q_ASSERT(edgeDragArea != 0);
901 edgeDragArea->setRecognitionTimer(fakeTimer);
902 edgeDragArea->setTimeSource(fakeTimeSource);
904 // Disable some constraints we're not interested in
905 edgeDragArea->setMaxSilenceTime(60 * 1000);
906 edgeDragArea->setMinSpeed(0);
908 // And ensure others have the values we want
909 edgeDragArea->setCompositionTime(60);
911 // Put an item right behind edgeDragArea to receive the touches ignored by it
912 DummyItem *dummyItem = new DummyItem;
913 dummyItem->setParentItem(edgeDragArea->parentItem());
914 dummyItem->setX(edgeDragArea->x());
915 dummyItem->setY(edgeDragArea->y());
916 dummyItem->setZ(edgeDragArea->z() - 1.0);
917 dummyItem->setWidth(edgeDragArea->width());
918 dummyItem->setHeight(edgeDragArea->height());
920 // Make touches evenly spaced along the edgeDragArea
921 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
922 QPoint touch1Pos(edgeDragArea->width()/2.0f, m_view->height()*0.66f);
924 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
926 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
928 // We are now going to be way beyond compositionTime
929 passTime(edgeDragArea->compositionTime()*3);
931 QTest::touchEvent(m_view, m_device)
932 .move(0, touch0Pos)
933 .press(1, touch1Pos);
935 // A new touch has come, but as it can't be composed with touch 0, it should be
936 // ignored/rejected by the DirectionalDragArea
937 // Therefore the last event received by dummyItem must have both touch points (0 and 1)
938 {
939 TouchMemento &touchMemento = dummyItem->touchEvents.last();
940 QCOMPARE(touchMemento.touchPoints.count(), 2);
941 QVERIFY(touchMemento.containsTouchWithId(0));
942 QVERIFY(touchMemento.containsTouchWithId(1));
943 }
945 passTime(30);
947 QTest::touchEvent(m_view, m_device)
948 .move(0, touch0Pos)
949 .move(1, touch1Pos);
951 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
953 passTime(5);
955 QTest::touchEvent(m_view, m_device)
956 .release(0, touch0Pos)
957 .move(1, touch1Pos);
959 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
961 passTime(5);
963 QTest::touchEvent(m_view, m_device)
964 .release(1, touch1Pos);
966 // Shouldn't be keepping info about touches that no longer exist or interest us
967 QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
969 delete dummyItem;
972void tst_DirectionalDragArea::givesUpWhenLosesTouch()
974 DirectionalDragArea *edgeDragArea =
975 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
976 Q_ASSERT(edgeDragArea != 0);
977 edgeDragArea->setRecognitionTimer(fakeTimer);
978 edgeDragArea->setTimeSource(fakeTimeSource);
980 // Disable some constraints we're not interested in
981 edgeDragArea->setMaxSilenceTime(60 * 1000);
982 edgeDragArea->setMinSpeed(0);
984 // Put an item right in front of edgeDragArea
985 DummyItem *dummyItem = new DummyItem(edgeDragArea->parentItem());
986 dummyItem->setX(edgeDragArea->x());
987 dummyItem->setY(edgeDragArea->y());
988 dummyItem->setZ(edgeDragArea->z() + 1.0);
989 dummyItem->setWidth(edgeDragArea->width());
990 dummyItem->setHeight(edgeDragArea->height());
992 QPoint touchPos(edgeDragArea->width()/2.0f, m_view->height()/2.0f);
994 dummyItem->touchEventHandler = [&](QTouchEvent *event) {
995 m_touchRegistry->addCandidateOwnerForTouch(0, dummyItem);
996 event->ignore();
997 };
999 QTest::touchEvent(m_view, m_device).press(0, touchPos);
1001 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1003 m_touchRegistry->requestTouchOwnership(0, dummyItem);
1005 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1007 dummyItem->grabTouchPoints({0});
1008 dummyItem->touchEventHandler = [&](QTouchEvent *event) { event->accept(); };
1010 passTime(5);
1011 QTest::touchEvent(m_view, m_device).release(0, touchPos);
1013 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1015 QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
1018void tst_DirectionalDragArea::threeFingerDrag()
1020 DirectionalDragArea *edgeDragArea =
1021 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1022 Q_ASSERT(edgeDragArea != 0);
1023 edgeDragArea->setRecognitionTimer(fakeTimer);
1024 edgeDragArea->setTimeSource(fakeTimeSource);
1026 // Disable some constraints we're not interested in
1027 edgeDragArea->setMaxSilenceTime(60 * 1000);
1028 edgeDragArea->setMinSpeed(0);
1030 // And ensure others have the values we want
1031 edgeDragArea->setCompositionTime(60);
1033 // Make touches evenly spaced along the edgeDragArea
1034 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.25f);
1035 QPoint touch1Pos(edgeDragArea->width()/2.0f, m_view->height()*0.50f);
1036 QPoint touch2Pos(edgeDragArea->width()/2.0f, m_view->height()*0.75f);
1038 QTest::touchEvent(m_view, m_device)
1039 .press(0, touch0Pos);
1041 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1043 passTime(5);
1044 QTest::touchEvent(m_view, m_device)
1045 .move(0, touch0Pos)
1046 .press(1, touch1Pos);
1048 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1050 passTime(5);
1051 QTest::touchEvent(m_view, m_device)
1052 .move(0, touch0Pos)
1053 .move(1, touch1Pos)
1054 .press(2, touch2Pos);
1056 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1058 passTime(10);
1059 QTest::touchEvent(m_view, m_device)
1060 .move(0, touch0Pos)
1061 .move(1, touch1Pos)
1062 .move(2, touch2Pos);
1064 passTime(200);
1065 QTest::touchEvent(m_view, m_device)
1066 .move(0, touch0Pos)
1067 .release(1, touch1Pos)
1068 .move(2, touch2Pos);
1070 passTime(10);
1071 QTest::touchEvent(m_view, m_device)
1072 .move(0, touch0Pos)
1073 .release(2, touch2Pos);
1075 passTime(5);
1076 QTest::touchEvent(m_view, m_device)
1077 .release(0, touch0Pos);
1079 // Shouldn't be keepping info about touches that no longer exist or interest us
1080 QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
1084 If all the relevant gesture recognition constraints/parameters have been disabled,
1085 it means that the gesture recognition itself has been disabled and DirectionalDragArea
1086 will therefore work like a simple touch area, merely reporting touch movement.
1087 */
1088void tst_DirectionalDragArea::immediateRecognitionWhenConstraintsDisabled()
1090 DirectionalDragArea *edgeDragArea =
1091 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1092 Q_ASSERT(edgeDragArea != 0);
1093 edgeDragArea->setRecognitionTimer(fakeTimer);
1094 edgeDragArea->setTimeSource(fakeTimeSource);
1096 // Disable the minimum amount of constraints to ensure immediate recognition
1097 edgeDragArea->setDistanceThreshold(0);
1098 edgeDragArea->setCompositionTime(0);
1100 // Put an item right behind edgeDragArea to receive the touches ignored by it
1101 DummyItem *dummyItem = new DummyItem;
1102 dummyItem->setParentItem(edgeDragArea->parentItem());
1103 dummyItem->setX(edgeDragArea->x());
1104 dummyItem->setY(edgeDragArea->y());
1105 dummyItem->setZ(edgeDragArea->z() - 1.0);
1106 dummyItem->setWidth(edgeDragArea->width());
1107 dummyItem->setHeight(edgeDragArea->height());
1109 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()/2.0f);
1111 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
1113 // check for immediate recognition
1114 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
1116 // and therefore it should have immediately grabbed the touch point,
1117 // not letting it leak to items behind him.
1118 QCOMPARE(dummyItem->touchEvents.count(), 0);
1121void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition()
1123 DirectionalDragArea *edgeDragArea =
1124 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1125 Q_ASSERT(edgeDragArea != 0);
1126 edgeDragArea->setRecognitionTimer(fakeTimer);
1127 edgeDragArea->setTimeSource(fakeTimeSource);
1129 QPointF touchPoint = calculateInitialTouchPos(edgeDragArea);
1131 // Move less than the minimum needed for the drag gesture recognition
1132 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*0.5f;
1133 QPointF dragDirectionVector(1., 0.); // horizontal positive
1135 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
1136 QPointF touchMovement = dragDirectionVector * movementStepDistance;
1137 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
1138 int movementTimeStepMs = (edgeDragArea->compositionTime() * 0.8f) / totalMovementSteps;
1140 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
1142 for (int i = 0; i < totalMovementSteps; ++i) {
1143 touchPoint += touchMovement;
1144 passTime(movementTimeStepMs);
1145 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
1148 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
1150 // edgeDragArea should be an undecided candidate
1152 auto touchInfo = m_touchRegistry->findTouchInfo(0);
1153 QCOMPARE(touchInfo->candidates.size(), 1);
1154 QCOMPARE(touchInfo->candidates.at(0).item.data(), edgeDragArea);
1155 QCOMPARE(touchInfo->candidates.at(0).undecided, true);
1158 // disable the dragArea while it's still recognizing a possible drag gesture.
1159 QFETCH(bool, disable);
1160 if (disable) {
1161 edgeDragArea->setEnabled(false);
1162 } else {
1163 edgeDragArea->setVisible(false);
1166 // edgeDragArea should no longer be a candidate
1168 auto touchInfo = m_touchRegistry->findTouchInfo(0);
1169 QCOMPARE(touchInfo->candidates.size(), 0);
1172 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
1174 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
1177void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition_data()
1179 QTest::addColumn<bool>("disable");
1181 QTest::newRow("disabled") << true;
1182 QTest::newRow("invisible") << false;
1185void tst_DirectionalDragArea::tappedSignal()
1187 DirectionalDragArea *edgeDragArea =
1188 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1189 QVERIFY(edgeDragArea != 0);
1190 edgeDragArea->setRecognitionTimer(fakeTimer);
1191 edgeDragArea->setTimeSource(fakeTimeSource);
1193 QFETCH(bool, immediateGestureRecognition);
1194 if (immediateGestureRecognition) {
1195 // Disable the minimum amount of constraints to ensure immediate recognition
1196 edgeDragArea->setDistanceThreshold(0);
1197 edgeDragArea->setCompositionTime(0);
1200 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()/2.0f);
1202 QSignalSpy tappedSpy(edgeDragArea, SIGNAL(tapped()));
1204 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
1205 passTime(edgeDragArea->maxTapDuration() / 2);
1206 QTest::touchEvent(m_view, m_device).release(0, touch0Pos);
1208 QCOMPARE(tappedSpy.count(), 1);
1211void tst_DirectionalDragArea::tappedSignal_data()
1213 QTest::addColumn<bool>("immediateGestureRecognition");
1215 QTest::newRow("immediate gesture recognition") << true;
1216 QTest::newRow("default gesture recognition") << false;
1219void tst_DirectionalDragArea::gettingTouchOwnershipMakesMouseAreaBehindGetCanceled()
1221 DirectionalDragArea *edgeDragArea =
1222 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1223 QVERIFY(edgeDragArea != nullptr);
1224 edgeDragArea->setRecognitionTimer(fakeTimer);
1225 edgeDragArea->setTimeSource(fakeTimeSource);
1227 QQuickMouseArea *mouseArea =
1228 m_view->rootObject()->findChild<QQuickMouseArea*>("mouseArea");
1229 QVERIFY(mouseArea != nullptr);
1231 MouseAreaSpy mouseAreaSpy(mouseArea);
1233 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
1234 QPointF touchPoint = initialTouchPos;
1236 qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2;
1237 QPointF dragDirectionVector(1.0f, 0.0f); // rightwards
1238 qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
1239 QPointF touchMovement = dragDirectionVector * movementStepDistance;
1240 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
1241 int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
1243 QCOMPARE(mouseArea->pressed(), false);
1245 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
1247 // The TouchBegin passes through the DirectionalDragArea and reaches the MouseArea behind it,
1248 // where it's converted to a MouseEvent by QQuickWindow and sent to the MouseArea which then
1249 // accepts it. Making it get pressed.
1250 QCOMPARE(mouseArea->pressed(), true);
1251 QCOMPARE(mouseAreaSpy.canceledCount, 0);
1253 for (int i = 0; i < totalMovementSteps; ++i) {
1254 touchPoint += touchMovement;
1255 passTime(movementTimeStepMs);
1256 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
1259 // As the DirectionalDragArea recognizes the gesture, it grabs the touch from the MouseArea,
1260 // which should make the MouseArea get a cancelation event, which will then cause it to
1261 // reset its state (going back to "unpressed"/"released").
1262 QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
1263 QCOMPARE(mouseArea->pressed(), false);
1264 QCOMPARE(mouseAreaSpy.canceledCount, 1);
1266 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
1269QTEST_MAIN(tst_DirectionalDragArea)
1271#include "tst_DirectionalDragArea.moc"