on xenial help sign in
DirectionalDragArea & friends: various improvements & API grooming

-Simplified DirectionalDragArea gesture recognition * The widening angle property was dropped. Didn't really work as in real life swipes might start up in very different directions before finally turning to the final overall direction. That's specially bad when you have to conclude the recognition in a short amount of time so that you can still play animations to follow the user's finger * minSpeed as well. Went for a simpler way of expressing and evaluating it which is having maxTime and maxDistance. -EdgeDragArea got absorbed by DirectionalDragArea -Privatized all gesture recognition parameters. -Privatized DirectionalDragArea.status property. All apps need is DirectionalDragArea.dragging. Modified qml code using DDA accordingly. -Modified DirectionalDragArea.dragging semantics a bit. It goes to true only once a gesture is recognized -Moved code that smooths out touch movement into DirectionalDragArea so all users get it for free. -Cleaned up DirectionalDragArea.h of all implementation details, getting it ready to be moved out of unity8, into the SDK. -Removed DirectionalDragArea.tapped() signal as it doesn't belong to it. -Fine-tuned gesture recognition parameters and made them based on physical size. -Added tests with input from real gestures to catch those annoying false-negatives and false-positives introduced by badly set recognition parameters -Improved debug output -UnownedTouchEvents are no longer sent to interim owners -Fixed issue when TouchGate got disabled while holding an active touch -Made "make tryDirectionalDragArea" and "make tryDragHandle" work with mouse again.

commit792c5f3fece1c52f1738a457e55b474fa61c8585 authorDaniel d'Andrada <daniel.dandrada@canonical.com> dateFri 10 Apr 2015, 18:16 (12 years ago) parentd638819
48 files changed+1650−1434
libs/UbuntuGestures/CMakeLists.txt+1 libs/UbuntuGestures/CandidateInactivityTimer.h+1−1 plugins/Ubuntu/Gestures/TimeSource.cpp → libs/UbuntuGestures/TimeSource.cpp plugins/Ubuntu/Gestures/TimeSource.h → libs/UbuntuGestures/TimeSource.h+14−4 libs/UbuntuGestures/Timer.cpp+63−10 libs/UbuntuGestures/Timer.h+15−3 libs/UbuntuGestures/TouchRegistry.cpp+12−14 libs/UbuntuGestures/TouchRegistry.h+18−4 plugins/Ubuntu/Gestures/CMakeLists.txt+1−1 plugins/Ubuntu/Gestures/Damper.cpp+24 plugins/Ubuntu/Gestures/Damper.h+2 plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+309−323 plugins/Ubuntu/Gestures/DirectionalDragArea.h+16−205 plugins/Ubuntu/Gestures/DirectionalDragArea_p.h+167 plugins/Ubuntu/Gestures/TouchDispatcher.cpp+88−53 plugins/Ubuntu/Gestures/TouchDispatcher.h+16−9 plugins/Ubuntu/Gestures/TouchGate.cpp+84−60 plugins/Ubuntu/Gestures/TouchGate.h+18−5 qml/Components/DragHandle.qml+18−33 qml/Components/EdgeDragArea.qml−46 qml/Dash/Dash.qml+5−12 qml/Greeter/CoverPage.qml+1 qml/Launcher/Launcher.qml+6−14 qml/Panel/IndicatorsMenu.qml+12−2 qml/Stages/PhoneStage.qml+31−41 qml/Stages/TabletStage.qml+18−20 qml/Tutorial/TutorialBottom.qml+1−1 tests/libs/UbuntuGestures/tst_TouchRegistry.cpp+68−2 tests/plugins/Ubuntu/Gestures/CMakeLists.txt+3−1 tests/plugins/Ubuntu/Gestures/DownwardsLauncher.qml+5−19 tests/plugins/Ubuntu/Gestures/GestureTest.cpp+13−3 tests/plugins/Ubuntu/Gestures/GestureTest.h+4 tests/plugins/Ubuntu/Gestures/LeftwardsLauncher.qml+6−19 tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml+5−19 tests/plugins/Ubuntu/Gestures/UpwardsLauncher.qml+6−19 tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+480−445 tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.qml+8−1 tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cpp+5−6 tests/plugins/Ubuntu/Gestures/tst_TouchGate.cpp+56−1 tests/qmltests/Components/CMakeLists.txt+3−1 tests/qmltests/Components/tst_DragHandle.cpp+15−13 tests/qmltests/Components/tst_DragHandle.qml+7 tests/qmltests/Components/tst_DragHandle/BidirectionalShowable.qml+2−2 tests/qmltests/Components/tst_DragHandle/HorizontalShowable.qml+4−4 tests/qmltests/Components/tst_DragHandle/VerticalShowable.qml+4−4 tests/qmltests/Stages/tst_PhoneStage.qml+8−8 tests/qmltests/Tutorial/tst_Tutorial.qml+5−1 tests/utils/modules/Unity/Test/UnityTestCase.qml+2−5
libs/UbuntuGestures/CMakeLists.txt+1
@@ -5,6 +5,7 @@ set(UbuntuGestures_SOURCES
55 CandidateInactivityTimer.cpp
66 DebugHelpers.cpp
77 Timer.cpp
8+ TimeSource.cpp
89 TouchOwnershipEvent.cpp
910 TouchRegistry.cpp
1011 UnownedTouchEvent.cpp
libs/UbuntuGestures/CandidateInactivityTimer.h+1−1
@@ -32,7 +32,7 @@ public:
3232 AbstractTimerFactory &timerFactory,
3333 QObject *parent = nullptr);
3434
35− const int durationMs = 350;
35+ const int durationMs = 1000;
3636
3737Q_SIGNALS:
3838 void candidateDefaulted(int touchId, QQuickItem *candidate);
plugins/Ubuntu/Gestures/TimeSource.cpp → libs/UbuntuGestures/TimeSource.cpprenamed

The content is the same.

plugins/Ubuntu/Gestures/TimeSource.h → libs/UbuntuGestures/TimeSource.hrenamed+14−4
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 - Canonical Ltd.
2+ * Copyright (C) 2013,2015 Canonical Ltd.
33 *
44 * This program is free software: you can redistribute it and/or modify it
55 * under the terms of the GNU Lesser General Public License, as
@@ -21,14 +21,14 @@
2121#ifndef UBUNTUGESTURES_TIMESOURCE_H
2222#define UBUNTUGESTURES_TIMESOURCE_H
2323
24−#include "UbuntuGesturesQmlGlobal.h"
24+#include "UbuntuGesturesGlobal.h"
2525#include <QSharedPointer>
2626
2727namespace UbuntuGestures {
2828/*
2929 Interface for a time source.
3030 */
31−class UBUNTUGESTURESQML_EXPORT TimeSource {
31+class UBUNTUGESTURES_EXPORT TimeSource {
3232public:
3333 virtual ~TimeSource() {}
3434 /* Returns the current time in milliseconds since some reference time in the past. */
@@ -40,7 +40,7 @@ typedef QSharedPointer<TimeSource> SharedTimeSource;
4040 Implementation of a time source
4141 */
4242class RealTimeSourcePrivate;
43−class RealTimeSource : public TimeSource {
43+class UBUNTUGESTURES_EXPORT RealTimeSource : public TimeSource {
4444public:
4545 RealTimeSource();
4646 virtual ~RealTimeSource();
@@ -49,6 +49,16 @@ private:
4949 RealTimeSourcePrivate *d;
5050};
5151
52+/*
53+ A fake time source, useful for tests
54+ */
55+class FakeTimeSource : public TimeSource {
56+public:
57+ FakeTimeSource() { m_msecsSinceReference = 0; }
58+ qint64 msecsSinceReference() override { return m_msecsSinceReference; }
59+ qint64 m_msecsSinceReference;
60+};
61+
5262} // namespace UbuntuGestures
5363
5464#endif // UBUNTUGESTURES_TIMESOURCE_H
libs/UbuntuGestures/Timer.cpp+63−10
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -58,13 +58,36 @@ void Timer::setSingleShot(bool value)
5858
5959/////////////////////////////////// FakeTimer //////////////////////////////////
6060
61−FakeTimer::FakeTimer(QObject *parent)
61+FakeTimer::FakeTimer(const SharedTimeSource &timeSource, QObject *parent)
6262 : UbuntuGestures::AbstractTimer(parent)
6363 , m_interval(0)
6464 , m_singleShot(false)
65+ , m_timeSource(timeSource)
6566{
6667}
6768
69+void FakeTimer::update()
70+{
71+ if (!isRunning()) {
72+ return;
73+ }
74+
75+ if (m_nextTimeoutTime <= m_timeSource->msecsSinceReference()) {
76+ if (isSingleShot()) {
77+ stop();
78+ } else {
79+ m_nextTimeoutTime += interval();
80+ }
81+ Q_EMIT timeout();
82+ }
83+}
84+
85+void FakeTimer::start()
86+{
87+ AbstractTimer::start();
88+ m_nextTimeoutTime = m_timeSource->msecsSinceReference() + (qint64)interval();
89+}
90+
6891int FakeTimer::interval() const
6992{
7093 return m_interval;
@@ -87,23 +110,53 @@ void FakeTimer::setSingleShot(bool value)
87110
88111/////////////////////////////////// FakeTimerFactory //////////////////////////////////
89112
90−AbstractTimer *FakeTimerFactory::createTimer(QObject *parent)
113+FakeTimerFactory::FakeTimerFactory()
91114{
92− FakeTimer *fakeTimer = new FakeTimer(parent);
93−
94− timers.append(fakeTimer);
115+ m_timeSource.reset(new FakeTimeSource);
116+}
95117
96− return fakeTimer;
118+FakeTimerFactory::~FakeTimerFactory()
119+{
120+ for (int i = 0; i < timers.count(); ++i) {
121+ FakeTimer *timer = timers[i].data();
122+ if (timer) {
123+ delete timer;
124+ }
125+ }
97126}
98127
99−void FakeTimerFactory::makeRunningTimersTimeout()
128+void FakeTimerFactory::updateTime(qint64 targetTime)
100129{
130+ qint64 minTimeoutTime = targetTime;
131+
132+ for (int i = 0; i < timers.count(); ++i) {
133+ FakeTimer *timer = timers[i].data();
134+ if (timer && timer->isRunning() && timer->nextTimeoutTime() < minTimeoutTime) {
135+ minTimeoutTime = timer->nextTimeoutTime();
136+ }
137+ }
138+
139+ m_timeSource->m_msecsSinceReference = minTimeoutTime;
140+
101141 for (int i = 0; i < timers.count(); ++i) {
102142 FakeTimer *timer = timers[i].data();
103− if (timer && timer->isRunning()) {
104− timer->emitTimeout();
143+ if (timer) {
144+ timer->update();
105145 }
106146 }
147+
148+ if (m_timeSource->msecsSinceReference() < targetTime) {
149+ updateTime(targetTime);
150+ }
151+}
152+
153+AbstractTimer *FakeTimerFactory::createTimer(QObject *parent)
154+{
155+ FakeTimer *fakeTimer = new FakeTimer(m_timeSource, parent);
156+
157+ timers.append(fakeTimer);
158+
159+ return fakeTimer;
107160}
108161
109162} // namespace UbuntuGestures
libs/UbuntuGestures/Timer.h+15−3
@@ -18,6 +18,7 @@
1818#define UBUNTUGESTURES_TIMER_H
1919
2020#include "UbuntuGesturesGlobal.h"
21+#include "TimeSource.h"
2122
2223#include <QObject>
2324#include <QPointer>
@@ -66,17 +67,21 @@ class UBUNTUGESTURES_EXPORT FakeTimer : public AbstractTimer
6667{
6768 Q_OBJECT
6869public:
69− FakeTimer(QObject *parent = nullptr);
70+ FakeTimer(const SharedTimeSource &timeSource, QObject *parent = nullptr);
7071
71− virtual void emitTimeout() { Q_EMIT timeout(); }
72+ void update();
73+ qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
7274
7375 int interval() const override;
7476 void setInterval(int msecs) override;
77+ void start() override;
7578 bool isSingleShot() const override;
7679 void setSingleShot(bool value) override;
7780private:
7881 int m_interval;
7982 bool m_singleShot;
83+ SharedTimeSource m_timeSource;
84+ qint64 m_nextTimeoutTime;
8085};
8186
8287class UBUNTUGESTURES_EXPORT AbstractTimerFactory
@@ -95,9 +100,16 @@ public:
95100class UBUNTUGESTURES_EXPORT FakeTimerFactory : public AbstractTimerFactory
96101{
97102public:
103+ FakeTimerFactory();
104+ virtual ~FakeTimerFactory();
105+
106+ void updateTime(qint64 msecsSinceReference);
107+ QSharedPointer<TimeSource> timeSource() { return m_timeSource; }
108+
98109 AbstractTimer *createTimer(QObject *parent = nullptr) override;
99− void makeRunningTimersTimeout();
100110 QList<QPointer<FakeTimer>> timers;
111+private:
112+ QSharedPointer<FakeTimeSource> m_timeSource;
101113};
102114
103115} // namespace UbuntuGestures
libs/UbuntuGestures/TouchRegistry.cpp+12−14
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -95,10 +95,6 @@ void TouchRegistry::deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QT
9595 // for each point and there should not be many active points at any given moment.
9696 // But having three nested for-loops does scare.
9797
98− // TODO: Don't send it to the object that is already receiving the regular event
99− // because QQuickWindow is sending it to him (i.e., he's the touch owner from Qt's point of view)
100− // Problem is, we cannnot easily get this information.
101−
10298 const QList<QTouchEvent::TouchPoint> &updatedTouchPoints = event->touchPoints();
10399
104100 // Maps an item to the touches in this event he should be informed about.
@@ -118,7 +114,9 @@ void TouchRegistry::deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QT
118114 for (int i = 0; i < touchInfo->candidates.count(); ++i) {
119115 CandidateInfo &candidate = touchInfo->candidates[i];
120116 Q_ASSERT(!candidate.item.isNull());
121− touchIdsForItems[candidate.item.data()].append(touchInfo->id);
117+ if (candidate.state != CandidateInfo::InterimOwner) {
118+ touchIdsForItems[candidate.item.data()].append(touchInfo->id);
119+ }
122120 }
123121 }
124122
@@ -260,7 +258,7 @@ void TouchRegistry::addCandidateOwnerForTouch(int id, QQuickItem *candidate)
260258 // TODO: Check if candidate already exists
261259
262260 CandidateInfo candidateInfo;
263− candidateInfo.undecided = true;
261+ candidateInfo.state = CandidateInfo::Undecided;
264262 candidateInfo.item = candidate;
265263 candidateInfo.inactivityTimer = new CandidateInactivityTimer(id, candidate,
266264 *m_timerFactory,
@@ -301,8 +299,8 @@ void TouchRegistry::removeCandidateOwnerForTouch(int id, QQuickItem *candidate)
301299 for (int i = 0; i < touchInfo->candidates.count() && indexRemoved == -1; ++i) {
302300 CandidateInfo &candidateInfo = touchInfo->candidates[i];
303301 if (candidateInfo.item == candidate) {
304− Q_ASSERT(i > 0 || candidateInfo.undecided);
305− if (i == 0 && !candidateInfo.undecided) {
302+ Q_ASSERT(i > 0 || candidateInfo.state == CandidateInfo::Undecided);
303+ if (i == 0 && candidateInfo.state != CandidateInfo::Undecided) {
306304 qCritical("TouchRegistry: touch owner is being removed.");
307305 }
308306 delete candidateInfo.inactivityTimer;
@@ -340,7 +338,7 @@ void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
340338 for (int i = 0; i < touchInfo->candidates.count(); ++i) {
341339 CandidateInfo &candidateInfo = touchInfo->candidates[i];
342340 if (candidateInfo.item == candidate) {
343− candidateInfo.undecided = false;
341+ candidateInfo.state = CandidateInfo::Requested;
344342 delete candidateInfo.inactivityTimer;
345343 candidateInfo.inactivityTimer = nullptr;
346344 candidateIndex = i;
@@ -351,7 +349,7 @@ void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
351349 // add it as a candidate if not present yet
352350 if (candidateIndex < 0) {
353351 CandidateInfo candidateInfo;
354− candidateInfo.undecided = false;
352+ candidateInfo.state = CandidateInfo::InterimOwner;
355353 candidateInfo.item = candidate;
356354 candidateInfo.inactivityTimer = nullptr;
357355 touchInfo->candidates.append(candidateInfo);
@@ -407,8 +405,8 @@ void TouchRegistry::rejectCandidateOwnerForTouch(int id, QQuickItem *candidate)
407405 for (int i = 0; i < touchInfo->candidates.count() && rejectedCandidateIndex == -1; ++i) {
408406 CandidateInfo &candidateInfo = touchInfo->candidates[i];
409407 if (candidateInfo.item == candidate) {
410− Q_ASSERT(i > 0 || candidateInfo.undecided);
411− if (i == 0 && !candidateInfo.undecided) {
408+ Q_ASSERT(i > 0 || candidateInfo.state == CandidateInfo::Undecided);
409+ if (i == 0 && candidateInfo.state != CandidateInfo::Undecided) {
412410 qCritical() << "TouchRegistry: Can't reject item (" << (void*)candidate
413411 << ") as it already owns touch" << id;
414412 return;
@@ -467,7 +465,7 @@ void TouchRegistry::TouchInfo::init(int id)
467465
468466bool TouchRegistry::TouchInfo::isOwned() const
469467{
470− return !candidates.isEmpty() && !candidates.first().undecided;
468+ return !candidates.isEmpty() && candidates.first().state != CandidateInfo::Undecided;
471469}
472470
473471bool TouchRegistry::TouchInfo::ended() const
libs/UbuntuGestures/TouchRegistry.h+18−4
@@ -79,9 +79,11 @@ namespace UbuntuGestures {
7979 If an item wants ownership over touches as soon as he receives the TouchBegin for them, his step 1
8080 would be instead:
8181 TouchRegistry::instance()->requestTouchOwnership(touchId, this);
82− return true;
83− He would then be notified once ownership has been granted to him, from which point onwards he could
84− safely assume other TouchRegistry users wouldn't snatch this touch away from him.
82+ touchEvent->accept();
83+ He won't get any UnownedTouchEvent for that touch as he is already the interim owner (ie, QQuickWindow
84+ will keep sending touch updates to him already). Eventually he will be notified once ownership has
85+ been granted to him (from TouchRegistry perspective), from which point onwards he could safely assume
86+ other TouchRegistry users wouldn't snatch this touch away from him.
8587
8688 Items oblivious to TouchRegistry will lose their touch points without warning, just like in plain Qt.
8789
@@ -132,7 +134,19 @@ private Q_SLOTS:
132134private:
133135 class CandidateInfo {
134136 public:
135− bool undecided;
137+ enum {
138+ // A candidate owner that doesn't yet know for sure whether he wants the touch point
139+ // (gesture recognition is stilll going on)
140+ Undecided = 0,
141+ // A candidate owner that wants the touch but hasn't been granted it yet,
142+ // most likely because there's an undecided candidate with higher priority
143+ Requested = 1,
144+ // An item that is the interim owner of the touch, receiving QTouchEvents of it
145+ // from QQuickWindow. Ie, it's the actual touch owner from Qt's point of view.
146+ // It wants to keep its touch ownership but hasn't been granted it by TouchRegistry
147+ // yet because of undecided candidates higher up.
148+ InterimOwner = 2
149+ } state;
136150 // TODO: Prune candidates that become null and resolve ownership accordingly.
137151 QPointer<QQuickItem> item;
138152 QPointer<UbuntuGestures::CandidateInactivityTimer> inactivityTimer;
plugins/Ubuntu/Gestures/CMakeLists.txt+1−1
@@ -4,10 +4,10 @@ remove_definitions(-DQT_NO_KEYWORDS)
44set(UbuntuGesturesQml_SOURCES
55 plugin.cpp
66 AxisVelocityCalculator.cpp
7+ Damper.cpp
78 Direction.cpp
89 DirectionalDragArea.cpp
910 PressedOutsideNotifier.cpp
10− TimeSource.cpp
1111 TouchDispatcher.cpp
1212 TouchGate.cpp
1313)
plugins/Ubuntu/Gestures/Damper.cppnew file+24
@@ -0,0 +1,24 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "Damper.h"
18+#include <QDebug>
19+
20+QDebug operator<<(QDebug dbg, const DampedPointF &p)
21+{
22+ dbg.nospace() << "(" << p.x() << ", " << p.y() << ")";
23+ return dbg.space();
24+}
plugins/Ubuntu/Gestures/Damper.h+2
@@ -84,4 +84,6 @@ private:
8484 Damper<qreal> m_y;
8585};
8686
87+QDebug operator<<(QDebug dbg, const DampedPointF &p);
88+
8789#endif // UBUNTU_GESTURES_DAMPER_H
plugins/Ubuntu/Gestures/DirectionalDragArea.cpp+309−323
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013-2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@
2121
2222#include <QQuickWindow>
2323#include <QtCore/qmath.h>
24+#include <QScreen>
2425#include <QDebug>
2526
2627#pragma GCC diagnostic push
@@ -33,6 +34,8 @@
3334#include "TouchRegistry.h"
3435#include "UnownedTouchEvent.h"
3536
37+#include "DirectionalDragArea_p.h"
38+
3639using namespace UbuntuGestures;
3740
3841#if DIRECTIONALDRAGAREA_DEBUG
@@ -40,11 +43,11 @@ using namespace UbuntuGestures;
4043#include "DebugHelpers.h"
4144
4245namespace {
43−const char *statusToString(DirectionalDragArea::Status status)
46+const char *statusToString(DirectionalDragAreaPrivate::Status status)
4447{
45− if (status == DirectionalDragArea::WaitingForTouch) {
48+ if (status == DirectionalDragAreaPrivate::WaitingForTouch) {
4649 return "WaitingForTouch";
47− } else if (status == DirectionalDragArea::Undecided) {
50+ } else if (status == DirectionalDragAreaPrivate::Undecided) {
4851 return "Undecided";
4952 } else {
5053 return "Recognized";
@@ -56,205 +59,156 @@ const char *statusToString(DirectionalDragArea::Status status)
5659#define ddaDebug(params) ((void)0)
5760#endif // DIRECTIONALDRAGAREA_DEBUG
5861
59−
6062DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
6163 : QQuickItem(parent)
62− , m_status(WaitingForTouch)
63− , m_sceneDistance(0)
64− , m_touchId(-1)
65− , m_direction(Direction::Rightwards)
66− , m_wideningAngle(0)
67− , m_wideningFactor(0)
68− , m_distanceThreshold(0)
69− , m_distanceThresholdSquared(0.)
70− , m_minSpeed(0)
71− , m_maxSilenceTime(200)
72− , m_silenceTime(0)
73− , m_compositionTime(60)
74− , m_numSamplesOnLastSpeedCheck(0)
75− , m_recognitionTimer(0)
76− , m_velocityCalculator(0)
77− , m_timeSource(new RealTimeSource)
78− , m_activeTouches(m_timeSource)
79−{
80− setRecognitionTimer(new Timer(this));
81− m_recognitionTimer->setInterval(60);
82− m_recognitionTimer->setSingleShot(false);
83−
84− m_velocityCalculator = new AxisVelocityCalculator(this);
85−
86− connect(this, &QQuickItem::enabledChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
87− connect(this, &QQuickItem::visibleChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
88−}
89−
90−Direction::Type DirectionalDragArea::direction() const
91−{
92− return m_direction;
93−}
94−
95−void DirectionalDragArea::setDirection(Direction::Type direction)
96−{
97− if (direction != m_direction) {
98− m_direction = direction;
99− Q_EMIT directionChanged(m_direction);
100− }
101−}
102−
103−void DirectionalDragArea::setMaxDeviation(qreal value)
64+ , d(new DirectionalDragAreaPrivate(this))
10465{
105− if (m_dampedScenePos.maxDelta() != value) {
106− m_dampedScenePos.setMaxDelta(value);
107− Q_EMIT maxDeviationChanged(value);
108− }
109−}
66+ d->setRecognitionTimer(new Timer(this));
67+ d->recognitionTimer->setInterval(d->maxTime);
68+ d->recognitionTimer->setSingleShot(true);
11069
111−qreal DirectionalDragArea::wideningAngle() const
112−{
113− return m_wideningAngle;
70+ connect(this, &QQuickItem::enabledChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
71+ connect(this, &QQuickItem::visibleChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
11472}
11573
116−void DirectionalDragArea::setWideningAngle(qreal angle)
74+Direction::Type DirectionalDragArea::direction() const
11775{
118− if (angle == m_wideningAngle)
119− return;
120−
121− m_wideningAngle = angle;
122−
123− // wideningFactor = pow(cosine(angle), 2)
124− {
125− qreal angleRadians = angle * M_PI / 180.0;
126− m_wideningFactor = qCos(angleRadians);
127− m_wideningFactor = m_wideningFactor * m_wideningFactor;
128− }
129−
130− Q_EMIT wideningAngleChanged(angle);
76+ return d->direction;
13177}
13278
133−void DirectionalDragArea::setDistanceThreshold(qreal value)
134−{
135− if (m_distanceThreshold != value) {
136− m_distanceThreshold = value;
137− m_distanceThresholdSquared = m_distanceThreshold * m_distanceThreshold;
138− Q_EMIT distanceThresholdChanged(value);
139− }
140−}
141−
142−void DirectionalDragArea::setMinSpeed(qreal value)
79+void DirectionalDragArea::setDirection(Direction::Type direction)
14380{
144− if (m_minSpeed != value) {
145− m_minSpeed = value;
146− Q_EMIT minSpeedChanged(value);
81+ if (direction != d->direction) {
82+ d->direction = direction;
83+ Q_EMIT directionChanged(d->direction);
14784 }
14885}
14986
150−void DirectionalDragArea::setMaxSilenceTime(int value)
87+void DirectionalDragAreaPrivate::setDistanceThreshold(qreal value)
15188{
152− if (m_maxSilenceTime != value) {
153− m_maxSilenceTime = value;
154− Q_EMIT maxSilenceTimeChanged(value);
89+ if (distanceThreshold != value) {
90+ distanceThreshold = value;
91+ distanceThresholdSquared = distanceThreshold * distanceThreshold;
15592 }
15693}
15794
158−void DirectionalDragArea::setCompositionTime(int value)
95+void DirectionalDragAreaPrivate::setMaxTime(int value)
15996{
160− if (m_compositionTime != value) {
161− m_compositionTime = value;
162− Q_EMIT compositionTimeChanged(value);
97+ if (maxTime != value) {
98+ maxTime = value;
99+ recognitionTimer->setInterval(maxTime);
163100 }
164101}
165102
166−void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
103+void DirectionalDragAreaPrivate::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
167104{
168105 int interval = 0;
169106 bool timerWasRunning = false;
170107 bool wasSingleShot = false;
171108
172109 // can be null when called from the constructor
173− if (m_recognitionTimer) {
174− interval = m_recognitionTimer->interval();
175− timerWasRunning = m_recognitionTimer->isRunning();
176− if (m_recognitionTimer->parent() == this) {
177− delete m_recognitionTimer;
110+ if (recognitionTimer) {
111+ interval = recognitionTimer->interval();
112+ timerWasRunning = recognitionTimer->isRunning();
113+ if (recognitionTimer->parent() == this) {
114+ delete recognitionTimer;
178115 }
179116 }
180117
181− m_recognitionTimer = timer;
118+ recognitionTimer = timer;
182119 timer->setInterval(interval);
183120 timer->setSingleShot(wasSingleShot);
184121 connect(timer, &UbuntuGestures::AbstractTimer::timeout,
185− this, &DirectionalDragArea::checkSpeed);
122+ this, &DirectionalDragAreaPrivate::rejectGesture);
186123 if (timerWasRunning) {
187− m_recognitionTimer->start();
124+ recognitionTimer->start();
188125 }
189126}
190127
191−void DirectionalDragArea::setTimeSource(const SharedTimeSource &timeSource)
128+void DirectionalDragAreaPrivate::setTimeSource(const SharedTimeSource &timeSource)
192129{
193− m_timeSource = timeSource;
194− m_velocityCalculator->setTimeSource(timeSource);
195− m_activeTouches.m_timeSource = timeSource;
130+ this->timeSource = timeSource;
131+ activeTouches.m_timeSource = timeSource;
196132}
197133
198134qreal DirectionalDragArea::distance() const
199135{
200− if (Direction::isHorizontal(m_direction)) {
201− return m_previousPos.x() - m_startPos.x();
136+ if (Direction::isHorizontal(d->direction)) {
137+ return d->publicPos.x() - d->startPos.x();
202138 } else {
203− return m_previousPos.y() - m_startPos.y();
139+ return d->publicPos.y() - d->startPos.y();
204140 }
205141}
206142
207−void DirectionalDragArea::updateSceneDistance()
143+void DirectionalDragAreaPrivate::updateSceneDistance()
208144{
209− QPointF totalMovement = m_previousScenePos - m_startScenePos;
210− m_sceneDistance = projectOntoDirectionVector(totalMovement);
145+ QPointF totalMovement = publicScenePos - startScenePos;
146+ sceneDistance = projectOntoDirectionVector(totalMovement);
211147}
212148
213149qreal DirectionalDragArea::sceneDistance() const
214150{
215− return m_sceneDistance;
151+ return d->sceneDistance;
216152}
217153
218154qreal DirectionalDragArea::touchX() const
219155{
220− return m_previousPos.x();
156+ return d->publicPos.x();
221157}
222158
223159qreal DirectionalDragArea::touchY() const
224160{
225− return m_previousPos.y();
161+ return d->publicPos.y();
226162}
227163
228164qreal DirectionalDragArea::touchSceneX() const
229165{
230− return m_previousScenePos.x();
166+ return d->publicScenePos.x();
231167}
232168
233169qreal DirectionalDragArea::touchSceneY() const
234170{
235− return m_previousScenePos.y();
171+ return d->publicScenePos.y();
172+}
173+
174+bool DirectionalDragArea::dragging() const
175+{
176+ return d->status == DirectionalDragAreaPrivate::Recognized;
177+}
178+
179+bool DirectionalDragArea::immediateRecognition() const
180+{
181+ return d->immediateRecognition;
182+}
183+
184+void DirectionalDragArea::setImmediateRecognition(bool enabled)
185+{
186+ if (d->immediateRecognition != enabled) {
187+ d->immediateRecognition = enabled;
188+ Q_EMIT immediateRecognitionChanged(enabled);
189+ }
236190}
237191
238192bool DirectionalDragArea::event(QEvent *event)
239193{
240194 if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
241− touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
195+ d->touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
242196 return true;
243197 } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
244− unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
198+ d->unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
245199 return true;
246200 } else {
247201 return QQuickItem::event(event);
248202 }
249203}
250204
251−void DirectionalDragArea::touchOwnershipEvent(TouchOwnershipEvent *event)
205+void DirectionalDragAreaPrivate::touchOwnershipEvent(TouchOwnershipEvent *event)
252206{
253207 if (event->gained()) {
254208 QVector<int> ids;
255209 ids.append(event->touchId());
256210 ddaDebug("grabbing touch");
257− grabTouchPoints(ids);
211+ q->grabTouchPoints(ids);
258212
259213 // Work around for Qt bug. If we grab a touch that is being used for mouse pointer
260214 // emulation it will cause the emulation logic to go nuts.
@@ -262,35 +216,35 @@ void DirectionalDragArea::touchOwnershipEvent(TouchOwnershipEvent *event)
262216 //
263217 // The fix for this bug has landed in Qt 5.4 (https://codereview.qt-project.org/96887)
264218 // TODO: Remove this workaround once we start using Qt 5.4
265− if (window()) {
266− QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(window());
267− if (windowPrivate->touchMouseId == event->touchId() && window()->mouseGrabberItem()) {
219+ if (q->window()) {
220+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(q->window());
221+ if (windowPrivate->touchMouseId == event->touchId() && q->window()->mouseGrabberItem()) {
268222 ddaDebug("removing mouse grabber");
269− window()->mouseGrabberItem()->ungrabMouse();
223+ q->window()->mouseGrabberItem()->ungrabMouse();
270224 }
271225 }
272226 } else {
273227 // We still wanna know when it ends for keeping the composition time window up-to-date
274− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
228+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
275229
276230 setStatus(WaitingForTouch);
277231 }
278232}
279233
280−void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
234+void DirectionalDragAreaPrivate::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
281235{
282236 QTouchEvent *event = unownedTouchEvent->touchEvent();
283237
284238 Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
285239
286− ddaDebug("Unowned " << m_timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
240+ ddaDebug("Unowned " << timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
287241
288− switch (m_status) {
242+ switch (status) {
289243 case WaitingForTouch:
290244 // do nothing
291245 break;
292246 case Undecided:
293− Q_ASSERT(isEnabled() && isVisible());
247+ Q_ASSERT(q->isEnabled() && q->isVisible());
294248 unownedTouchEvent_undecided(unownedTouchEvent);
295249 break;
296250 default: // Recognized:
@@ -298,18 +252,18 @@ void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent
298252 break;
299253 }
300254
301− m_activeTouches.update(event);
255+ activeTouches.update(event);
302256}
303257
304−void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
258+void DirectionalDragAreaPrivate::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
305259{
306260 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
307261 if (!touchPoint) {
308− qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId
262+ qCritical() << "DirectionalDragArea[status=Undecided]: touch " << touchId
309263 << "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
310264 "Considering it as released.";
311265
312− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
266+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
313267 setStatus(WaitingForTouch);
314268 return;
315269 }
@@ -319,48 +273,42 @@ void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unowned
319273 if (touchPoint->state() == Qt::TouchPointReleased) {
320274 // touch has ended before recognition concluded
321275 ddaDebug("Touch has ended before recognition concluded");
322− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
323− emitSignalIfTapped();
276+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
324277 setStatus(WaitingForTouch);
325278 return;
326279 }
327280
328− m_previousDampedScenePos.setX(m_dampedScenePos.x());
329− m_previousDampedScenePos.setY(m_dampedScenePos.y());
330− m_dampedScenePos.update(touchScenePos);
331− updateVelocityCalculator(touchScenePos);
332−
333− if (!pointInsideAllowedArea()) {
334− ddaDebug("Rejecting gesture because touch point is outside allowed area.");
335− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
336− // We still wanna know when it ends for keeping the composition time window up-to-date
337− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
338− setStatus(WaitingForTouch);
339− return;
340− }
281+ previousDampedScenePos.setX(dampedScenePos.x());
282+ previousDampedScenePos.setY(dampedScenePos.y());
283+ dampedScenePos.update(touchScenePos);
341284
342285 if (!movingInRightDirection()) {
343286 ddaDebug("Rejecting gesture because touch point is moving in the wrong direction.");
344− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
287+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
345288 // We still wanna know when it ends for keeping the composition time window up-to-date
346− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
289+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
347290 setStatus(WaitingForTouch);
348291 return;
349292 }
350293
351− setPreviousPos(touchPoint->pos());
352− setPreviousScenePos(touchScenePos);
353−
354294 if (isWithinTouchCompositionWindow()) {
355295 // There's still time for some new touch to appear and ruin our party as it would be combined
356− // with our m_touchId one and therefore deny the possibility of a single-finger gesture.
296+ // with our touchId one and therefore deny the possibility of a single-finger gesture.
357297 ddaDebug("Sill within composition window. Let's wait more.");
358298 return;
359299 }
360300
361− if (movedFarEnough(touchScenePos)) {
362− TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
301+ if (movedFarEnoughAlongGestureAxis()) {
302+ TouchRegistry::instance()->requestTouchOwnership(touchId, q);
363303 setStatus(Recognized);
304+ setPublicPos(touchPoint->pos());
305+ setPublicScenePos(touchScenePos);
306+ } else if (isPastMaxDistance()) {
307+ ddaDebug("Rejecting gesture because it went farther than maxDistance without getting recognized.");
308+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
309+ // We still wanna know when it ends for keeping the composition time window up-to-date
310+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
311+ setStatus(WaitingForTouch);
364312 } else {
365313 ddaDebug("Didn't move far enough yet. Let's wait more.");
366314 }
@@ -371,29 +319,29 @@ void DirectionalDragArea::touchEvent(QTouchEvent *event)
371319 // TODO: Consider when more than one touch starts in the same event (although it's not possible
372320 // with Mir's android-input). Have to track them all. Consider it a plus/bonus.
373321
374− ddaDebug(m_timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
322+ ddaDebug(d->timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
375323
376324 if (!isEnabled() || !isVisible()) {
377325 QQuickItem::touchEvent(event);
378326 return;
379327 }
380328
381− switch (m_status) {
382− case WaitingForTouch:
383− touchEvent_absent(event);
329+ switch (d->status) {
330+ case DirectionalDragAreaPrivate::WaitingForTouch:
331+ d->touchEvent_absent(event);
384332 break;
385− case Undecided:
386− touchEvent_undecided(event);
333+ case DirectionalDragAreaPrivate::Undecided:
334+ d->touchEvent_undecided(event);
387335 break;
388336 default: // Recognized:
389− touchEvent_recognized(event);
337+ d->touchEvent_recognized(event);
390338 break;
391339 }
392340
393− m_activeTouches.update(event);
341+ d->activeTouches.update(event);
394342}
395343
396−void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
344+void DirectionalDragAreaPrivate::touchEvent_absent(QTouchEvent *event)
397345{
398346 // TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
399347
@@ -423,36 +371,41 @@ void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
423371 allGood = false;
424372 } else {
425373 // that's our candidate
426− m_touchId = touchPoint.id();
374+ touchId = touchPoint.id();
427375 newTouchPoint = &touchPoint;
428376 }
429377 }
430378 }
431379
380+ if (allGood) {
381+ allGood = sanityCheckRecognitionProperties();
382+ if (!allGood) {
383+ qWarning("DirectionalDragArea: recognition properties are wrongly set. Gesture recognition"
384+ " is impossible");
385+ }
386+ }
387+
432388 if (allGood) {
433389 Q_ASSERT(newTouchPoint);
434390
435− m_startPos = newTouchPoint->pos();
436− m_startScenePos = newTouchPoint->scenePos();
437− m_touchId = newTouchPoint->id();
438− m_dampedScenePos.reset(m_startScenePos);
439− m_velocityCalculator->setTrackedPosition(0.);
440− m_velocityCalculator->reset();
441− m_numSamplesOnLastSpeedCheck = 0;
442− m_silenceTime = 0;
443− setPreviousPos(m_startPos);
444− setPreviousScenePos(m_startScenePos);
391+ startPos = newTouchPoint->pos();
392+ startScenePos = newTouchPoint->scenePos();
393+ touchId = newTouchPoint->id();
394+ dampedScenePos.reset(startScenePos);
395+ setPublicPos(startPos);
396+
397+ setPublicScenePos(startScenePos);
445398 updateSceneDirectionVector();
446399
447400 if (recognitionIsDisabled()) {
448401 // Behave like a dumb TouchArea
449402 ddaDebug("Gesture recognition is disabled. Requesting touch ownership immediately.");
450− TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
403+ TouchRegistry::instance()->requestTouchOwnership(touchId, q);
451404 setStatus(Recognized);
452405 event->accept();
453406 } else {
454407 // just monitor the touch points for now.
455− TouchRegistry::instance()->addCandidateOwnerForTouch(m_touchId, this);
408+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, q);
456409
457410 setStatus(Undecided);
458411 // Let the item below have it. We will monitor it and grab it later if a gesture
@@ -465,12 +418,11 @@ void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
465418 }
466419}
467420
468−void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
421+void DirectionalDragAreaPrivate::touchEvent_undecided(QTouchEvent *event)
469422{
470− Q_ASSERT(event->type() == QEvent::TouchBegin);
471423 Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
472424
473− // We're not interested in new touch points. We already have our candidate (m_touchId).
425+ // We're not interested in new touch points. We already have our candidate (touchId).
474426 // But we do want to know when those new touches end for keeping the composition time
475427 // window up-to-date
476428 event->ignore();
@@ -480,63 +432,60 @@ void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
480432 // multi-finger drags are not accepted
481433 ddaDebug("Multi-finger drags are not accepted");
482434
483− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
435+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
484436 // We still wanna know when it ends for keeping the composition time window up-to-date
485− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
437+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
486438
487439 setStatus(WaitingForTouch);
488440 }
489441}
490442
491−void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
443+void DirectionalDragAreaPrivate::touchEvent_recognized(QTouchEvent *event)
492444{
493445 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
494446
495447 if (!touchPoint) {
496− qCritical() << "DirectionalDragArea[status=Recognized]: touch " << m_touchId
448+ qCritical() << "DirectionalDragArea[status=Recognized]: touch " << touchId
497449 << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
498450 "Considering it as released.";
499451 setStatus(WaitingForTouch);
500452 } else {
501− setPreviousPos(touchPoint->pos());
502− setPreviousScenePos(touchPoint->scenePos());
453+ setPublicPos(touchPoint->pos());
454+ setPublicScenePos(touchPoint->scenePos());
503455
504456 if (touchPoint->state() == Qt::TouchPointReleased) {
505− emitSignalIfTapped();
506457 setStatus(WaitingForTouch);
507458 }
508459 }
509460}
510461
511−void DirectionalDragArea::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
462+void DirectionalDragAreaPrivate::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
512463{
513464 for (int i = 0; i < touchPoints.count(); ++i) {
514465 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
515466 if (touchPoint.state() == Qt::TouchPointPressed) {
516− TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), this);
467+ TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), q);
517468 }
518469 }
519470}
520471
521−bool DirectionalDragArea::recognitionIsDisabled() const
472+bool DirectionalDragAreaPrivate::recognitionIsDisabled() const
522473{
523− return distanceThreshold() <= 0 && compositionTime() <= 0;
474+ return immediateRecognition || (distanceThreshold <= 0 && compositionTime <= 0);
524475}
525476
526−void DirectionalDragArea::emitSignalIfTapped()
477+bool DirectionalDragAreaPrivate::sanityCheckRecognitionProperties()
527478{
528− qint64 touchDuration = m_timeSource->msecsSinceReference() - m_activeTouches.touchStartTime(m_touchId);
529− if (touchDuration <= maxTapDuration()) {
530− Q_EMIT tapped();
531− }
479+ return recognitionIsDisabled()
480+ || (distanceThreshold < maxDistance && compositionTime < maxTime);
532481}
533482
534−const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event)
483+const QTouchEvent::TouchPoint *DirectionalDragAreaPrivate::fetchTargetTouchPoint(QTouchEvent *event)
535484{
536485 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
537486 const QTouchEvent::TouchPoint *touchPoint = 0;
538487 for (int i = 0; i < touchPoints.size(); ++i) {
539− if (touchPoints.at(i).id() == m_touchId) {
488+ if (touchPoints.at(i).id() == touchId) {
540489 touchPoint = &touchPoints.at(i);
541490 break;
542491 }
@@ -544,39 +493,13 @@ const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouch
544493 return touchPoint;
545494}
546495
547−bool DirectionalDragArea::pointInsideAllowedArea() const
548−{
549− // NB: Using squared values to avoid computing the square root to find
550− // the length totalMovement
551−
552− QPointF totalMovement(m_dampedScenePos.x() - m_startScenePos.x(),
553− m_dampedScenePos.y() - m_startScenePos.y());
554−
555− qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
556− totalMovement.y() * totalMovement.y();
557−
558− if (squaredTotalMovSize == 0.) {
559− // didn't move
560− return true;
561− }
562−
563− qreal projectedMovement = projectOntoDirectionVector(totalMovement);
564−
565−
566− qreal cosineAngleSquared = (projectedMovement * projectedMovement) / squaredTotalMovSize;
567−
568− // Same as:
569− // angle_between_movement_vector_and_gesture_direction_vector <= widening_angle
570− return cosineAngleSquared >= m_wideningFactor;
571−}
572−
573−bool DirectionalDragArea::movingInRightDirection() const
496+bool DirectionalDragAreaPrivate::movingInRightDirection() const
574497{
575− if (m_direction == Direction::Horizontal) {
498+ if (direction == Direction::Horizontal) {
576499 return true;
577500 } else {
578− QPointF movementVector(m_dampedScenePos.x() - m_previousDampedScenePos.x(),
579− m_dampedScenePos.y() - m_previousDampedScenePos.y());
501+ QPointF movementVector(dampedScenePos.x() - previousDampedScenePos.x(),
502+ dampedScenePos.y() - previousDampedScenePos.y());
580503
581504 qreal scalarProjection = projectOntoDirectionVector(movementVector);
582505
@@ -584,96 +507,93 @@ bool DirectionalDragArea::movingInRightDirection() const
584507 }
585508}
586509
587−bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
510+bool DirectionalDragAreaPrivate::movedFarEnoughAlongGestureAxis() const
588511{
589− if (m_distanceThreshold <= 0.) {
512+ if (distanceThreshold <= 0.) {
590513 // distance threshold check is disabled
591514 return true;
592515 } else {
593− QPointF totalMovement(point.x() - m_startScenePos.x(),
594− point.y() - m_startScenePos.y());
516+ QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
517+ dampedScenePos.y() - startScenePos.y());
518+
519+ qreal scalarProjection = projectOntoDirectionVector(totalMovement);
595520
596− qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
597− totalMovement.y() * totalMovement.y();
521+ ddaDebug(" movedFarEnoughAlongGestureAxis: scalarProjection=" << scalarProjection
522+ << ", distanceThreshold=" << distanceThreshold);
598523
599− return squaredTotalMovSize > m_distanceThresholdSquared;
524+ if (direction == Direction::Horizontal) {
525+ return qAbs(scalarProjection) > distanceThreshold;
526+ } else {
527+ return scalarProjection > distanceThreshold;
528+ }
600529 }
601530}
602531
603−void DirectionalDragArea::checkSpeed()
532+bool DirectionalDragAreaPrivate::isPastMaxDistance() const
604533{
605− Q_ASSERT(m_status == Undecided);
534+ QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
535+ dampedScenePos.y() - startScenePos.y());
606536
607− if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
608− qreal speed = qFabs(m_velocityCalculator->calculate());
609− qreal minSpeedMsecs = m_minSpeed / 1000.0;
537+ qreal squaredDistance = totalMovement.x()*totalMovement.x() + totalMovement.y()*totalMovement.y();
538+ return squaredDistance > maxDistance*maxDistance;
539+}
610540
611− if (speed < minSpeedMsecs) {
612− ddaDebug("Rejecting gesture because it's below minimum speed.");
613− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
614− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
615− setStatus(WaitingForTouch);
541+void DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible()
542+{
543+ if (!q->isEnabled() || !q->isVisible()) {
544+ if (status == Undecided) {
545+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
546+ // We still wanna know when it ends for keeping the composition time window up-to-date
547+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
616548 }
617− }
618−
619− if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) {
620− m_silenceTime += m_recognitionTimer->interval();
621549
622− if (m_silenceTime > m_maxSilenceTime) {
623− ddaDebug("Rejecting gesture because its silence time has been exceeded.");
624− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
625− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
550+ if (status != WaitingForTouch) {
551+ ddaDebug("Resetting status because got disabled or made invisible");
626552 setStatus(WaitingForTouch);
627553 }
628− } else {
629− m_silenceTime = 0;
630554 }
631− m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
632555}
633556
634−void DirectionalDragArea::giveUpIfDisabledOrInvisible()
557+void DirectionalDragAreaPrivate::rejectGesture()
635558{
636− if (!isEnabled() || !isVisible()) {
637− if (m_status == Undecided) {
638− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
639− // We still wanna know when it ends for keeping the composition time window up-to-date
640− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
641− }
559+ if (status == Undecided) {
560+ ddaDebug("Rejecting gesture because it's taking too long to drag beyond the threshold.");
642561
643− if (m_status != WaitingForTouch) {
644− ddaDebug("Resetting status because got disabled or made invisible");
645− setStatus(WaitingForTouch);
646− }
562+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
563+ // We still wanna know when it ends for keeping the composition time window up-to-date
564+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
565+
566+ setStatus(WaitingForTouch);
647567 }
648568}
649569
650−void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
570+void DirectionalDragAreaPrivate::setStatus(Status newStatus)
651571{
652− if (newStatus == m_status)
572+ if (newStatus == status)
653573 return;
654574
655− DirectionalDragArea::Status oldStatus = m_status;
575+ Status oldStatus = status;
656576
657577 if (oldStatus == Undecided) {
658− m_recognitionTimer->stop();
578+ recognitionTimer->stop();
659579 }
660580
661− m_status = newStatus;
662− Q_EMIT statusChanged(m_status);
581+ status = newStatus;
582+ Q_EMIT statusChanged(status);
663583
664584 ddaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
665585
666586 switch (newStatus) {
667587 case WaitingForTouch:
668− Q_EMIT draggingChanged(false);
588+ if (oldStatus == Recognized) {
589+ Q_EMIT q->draggingChanged(false);
590+ }
669591 break;
670592 case Undecided:
671− m_recognitionTimer->start();
672− Q_EMIT draggingChanged(true);
593+ recognitionTimer->start();
673594 break;
674595 case Recognized:
675− if (oldStatus == WaitingForTouch)
676− Q_EMIT draggingChanged(true);
596+ Q_EMIT q->draggingChanged(true);
677597 break;
678598 default:
679599 // no-op
@@ -681,77 +601,126 @@ void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
681601 }
682602}
683603
684−void DirectionalDragArea::setPreviousPos(const QPointF &point)
604+void DirectionalDragAreaPrivate::setPublicPos(const QPointF &point)
685605{
686− bool xChanged = m_previousPos.x() != point.x();
687− bool yChanged = m_previousPos.y() != point.y();
606+ bool xChanged = publicPos.x() != point.x();
607+ bool yChanged = publicPos.y() != point.y();
608+
609+ // Public position should not get updated while the gesture is still being recognized
610+ // (ie, Undecided status).
611+ Q_ASSERT(status == WaitingForTouch || status == Recognized);
688612
689− m_previousPos = point;
613+ if (status == Recognized && !recognitionIsDisabled()) {
614+ // When the gesture finally gets recognized, the finger will likely be
615+ // reasonably far from the edge. If we made the contentX immediately
616+ // follow the finger position it would be visually unpleasant as it
617+ // would appear right next to the user's finger out of nowhere (ie,
618+ // it would jump). Instead, we make contentX go towards the user's
619+ // finger in several steps. ie., in an animated way.
620+ QPointF delta = point - publicPos;
621+ // the trick is not to go all the way (1.0) as it would cause a sudden jump
622+ publicPos.rx() += 0.4 * delta.x();
623+ publicPos.ry() += 0.4 * delta.y();
624+ } else {
625+ // no smoothing when initializing or if gesture recognition was immediate as there will
626+ // be no jump.
627+ publicPos = point;
628+ }
690629
691630 if (xChanged) {
692− Q_EMIT touchXChanged(point.x());
693− if (Direction::isHorizontal(m_direction))
694− Q_EMIT distanceChanged(distance());
631+ Q_EMIT q->touchXChanged(publicPos.x());
632+ if (Direction::isHorizontal(direction))
633+ Q_EMIT q->distanceChanged(q->distance());
695634 }
696635
697636 if (yChanged) {
698− Q_EMIT touchYChanged(point.y());
699− if (Direction::isVertical(m_direction))
700− Q_EMIT distanceChanged(distance());
637+ Q_EMIT q->touchYChanged(publicPos.y());
638+ if (Direction::isVertical(direction))
639+ Q_EMIT q->distanceChanged(q->distance());
701640 }
702641}
703642
704−void DirectionalDragArea::setPreviousScenePos(const QPointF &point)
643+void DirectionalDragAreaPrivate::setPublicScenePos(const QPointF &point)
705644{
706− bool xChanged = m_previousScenePos.x() != point.x();
707− bool yChanged = m_previousScenePos.y() != point.y();
645+ bool xChanged = publicScenePos.x() != point.x();
646+ bool yChanged = publicScenePos.y() != point.y();
708647
709648 if (!xChanged && !yChanged)
710649 return;
711650
712− qreal oldSceneDistance = sceneDistance();
713− m_previousScenePos = point;
651+ // Public position should not get updated while the gesture is still being recognized
652+ // (ie, Undecided status).
653+ Q_ASSERT(status == WaitingForTouch || status == Recognized);
654+
655+ qreal oldSceneDistance = sceneDistance;
656+
657+ if (status == Recognized && !recognitionIsDisabled()) {
658+ // When the gesture finally gets recognized, the finger will likely be
659+ // reasonably far from the edge. If we made the contentX immediately
660+ // follow the finger position it would be visually unpleasant as it
661+ // would appear right next to the user's finger out of nowhere (ie,
662+ // it would jump). Instead, we make contentX go towards the user's
663+ // finger in several steps. ie., in an animated way.
664+ QPointF delta = point - publicScenePos;
665+ // the trick is not to go all the way (1.0) as it would cause a sudden jump
666+ publicScenePos.rx() += 0.4 * delta.x();
667+ publicScenePos.ry() += 0.4 * delta.y();
668+ } else {
669+ // no smoothing when initializing or if gesture recognition was immediate as there will
670+ // be no jump.
671+ publicScenePos = point;
672+ }
673+
714674 updateSceneDistance();
715675
716− if (oldSceneDistance != sceneDistance()) {
717− Q_EMIT sceneDistanceChanged(sceneDistance());
676+ if (oldSceneDistance != sceneDistance) {
677+ Q_EMIT q->sceneDistanceChanged(sceneDistance);
718678 }
719679
720680 if (xChanged) {
721− Q_EMIT touchSceneXChanged(point.x());
681+ Q_EMIT q->touchSceneXChanged(publicScenePos.x());
722682 }
723683
724684 if (yChanged) {
725− Q_EMIT touchSceneYChanged(point.y());
685+ Q_EMIT q->touchSceneYChanged(publicScenePos.y());
726686 }
727687}
728688
729−void DirectionalDragArea::updateVelocityCalculator(const QPointF &scenePos)
689+bool DirectionalDragAreaPrivate::isWithinTouchCompositionWindow()
730690{
731− QPointF totalSceneMovement = scenePos - m_startScenePos;
732−
733− qreal scalarProjection = projectOntoDirectionVector(totalSceneMovement);
691+ return
692+ compositionTime > 0 &&
693+ !activeTouches.isEmpty() &&
694+ timeSource->msecsSinceReference() <=
695+ activeTouches.mostRecentStartTime() + (qint64)compositionTime;
696+}
734697
735− m_velocityCalculator->setTrackedPosition(scalarProjection);
698+void DirectionalDragArea::itemChange(ItemChange change, const ItemChangeData &value)
699+{
700+ if (change == QQuickItem::ItemSceneChange) {
701+ if (value.window != nullptr) {
702+ // TODO: Handle window->screen() changes (ie window changing screens)
703+ qreal pixelsPerMm = value.window->screen()->physicalDotsPerInch() / 25.4;
704+ d->setPixelsPerMm(pixelsPerMm);
705+ }
706+ }
736707}
737708
738−bool DirectionalDragArea::isWithinTouchCompositionWindow()
709+void DirectionalDragAreaPrivate::setPixelsPerMm(qreal pixelsPerMm)
739710{
740− return
741− compositionTime() > 0 &&
742− !m_activeTouches.isEmpty() &&
743− m_timeSource->msecsSinceReference() <=
744− m_activeTouches.mostRecentStartTime() + (qint64)compositionTime();
711+ dampedScenePos.setMaxDelta(1. * pixelsPerMm);
712+ setDistanceThreshold(4. * pixelsPerMm);
713+ maxDistance = 10. * pixelsPerMm;
745714}
746715
747716//************************** ActiveTouchesInfo **************************
748717
749−DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
718+ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
750719 : m_timeSource(timeSource)
751720{
752721}
753722
754−void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
723+void ActiveTouchesInfo::update(QTouchEvent *event)
755724{
756725 if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
757726 // nothing to update
@@ -773,7 +742,7 @@ void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
773742}
774743
775744#if ACTIVETOUCHESINFO_DEBUG
776−QString DirectionalDragArea::ActiveTouchesInfo::toString()
745+QString ActiveTouchesInfo::toString()
777746{
778747 QString string = "(";
779748
@@ -791,7 +760,7 @@ QString DirectionalDragArea::ActiveTouchesInfo::toString()
791760}
792761#endif // ACTIVETOUCHESINFO_DEBUG
793762
794−void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(int touchId)
763+void ActiveTouchesInfo::addTouchPoint(int touchId)
795764{
796765 ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
797766 activeTouchInfo.id = touchId;
@@ -802,7 +771,7 @@ void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(int touchId)
802771 #endif
803772}
804773
805−qint64 DirectionalDragArea::ActiveTouchesInfo::touchStartTime(int touchId)
774+qint64 ActiveTouchesInfo::touchStartTime(int touchId)
806775{
807776 qint64 result = -1;
808777
@@ -819,7 +788,7 @@ qint64 DirectionalDragArea::ActiveTouchesInfo::touchStartTime(int touchId)
819788 return result;
820789}
821790
822−void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(int touchId)
791+void ActiveTouchesInfo::removeTouchPoint(int touchId)
823792{
824793 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
825794 if (touchId == touchInfo->id) {
@@ -835,7 +804,7 @@ void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(int touchId)
835804 #endif
836805}
837806
838−qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
807+qint64 ActiveTouchesInfo::mostRecentStartTime()
839808{
840809 Q_ASSERT(!m_touchInfoPool.isEmpty());
841810
@@ -851,11 +820,11 @@ qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
851820 return highestStartTime;
852821}
853822
854−void DirectionalDragArea::updateSceneDirectionVector()
823+void DirectionalDragAreaPrivate::updateSceneDirectionVector()
855824{
856825 QPointF localOrigin(0., 0.);
857826 QPointF localDirection;
858− switch (m_direction) {
827+ switch (direction) {
859828 case Direction::Upwards:
860829 localDirection.rx() = 0.;
861830 localDirection.ry() = -1.;
@@ -873,14 +842,31 @@ void DirectionalDragArea::updateSceneDirectionVector()
873842 localDirection.ry() = 0.;
874843 break;
875844 }
876− QPointF sceneOrigin = mapToScene(localOrigin);
877− QPointF sceneDirection = mapToScene(localDirection);
878− m_sceneDirectionVector = sceneDirection - sceneOrigin;
845+ QPointF sceneOrigin = q->mapToScene(localOrigin);
846+ QPointF sceneDirection = q->mapToScene(localDirection);
847+ sceneDirectionVector = sceneDirection - sceneOrigin;
848+}
849+
850+qreal DirectionalDragAreaPrivate::projectOntoDirectionVector(const QPointF &sceneVector) const
851+{
852+ // same as dot product as sceneDirectionVector is a unit vector
853+ return sceneVector.x() * sceneDirectionVector.x() +
854+ sceneVector.y() * sceneDirectionVector.y();
879855}
880856
881−qreal DirectionalDragArea::projectOntoDirectionVector(const QPointF &sceneVector) const
857+DirectionalDragAreaPrivate::DirectionalDragAreaPrivate(DirectionalDragArea *q)
858+ : q(q)
859+ , status(WaitingForTouch)
860+ , sceneDistance(0)
861+ , touchId(-1)
862+ , direction(Direction::Rightwards)
863+ , distanceThreshold(0)
864+ , distanceThresholdSquared(0.)
865+ , maxTime(400)
866+ , compositionTime(60)
867+ , immediateRecognition(false)
868+ , recognitionTimer(nullptr)
869+ , timeSource(new RealTimeSource)
870+ , activeTouches(timeSource)
882871{
883− // same as dot product as m_sceneDirectionVector is a unit vector
884− return sceneVector.x() * m_sceneDirectionVector.x() +
885− sceneVector.y() * m_sceneDirectionVector.y();
886872}
plugins/Ubuntu/Gestures/DirectionalDragArea.h+16−205
@@ -18,7 +18,6 @@
1818#define DIRECTIONAL_DRAG_AREA_H
1919
2020#include <QtQuick/QQuickItem>
21−#include "AxisVelocityCalculator.h"
2221#include "UbuntuGesturesQmlGlobal.h"
2322#include "Damper.h"
2423#include "Direction.h"
@@ -29,6 +28,7 @@
2928
3029class TouchOwnershipEvent;
3130class UnownedTouchEvent;
31+class DirectionalDragAreaPrivate;
3232
3333/*
3434 An area that detects axis-aligned single-finger drag gestures
@@ -61,95 +61,28 @@ class UBUNTUGESTURESQML_EXPORT DirectionalDragArea : public QQuickItem {
6161 Q_PROPERTY(qreal touchSceneX READ touchSceneX NOTIFY touchSceneXChanged)
6262 Q_PROPERTY(qreal touchSceneY READ touchSceneY NOTIFY touchSceneYChanged)
6363
64− // The current status of the directional drag gesture area.
65− Q_PROPERTY(Status status READ status NOTIFY statusChanged)
66−
6764 // Whether a drag gesture is taking place
68− // This will be true as long as status is Undecided or Recognized
69− // When a gesture gets rejected, dragging turns to false.
7065 Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
7166
72− /////
73− // stuff that will be set in stone at some point
74−
75− // How far the touch point can move away from its expected position before
76− // it causes a rejection in the gesture recognition. This is to compensate
77− // for both noise in the touch input signal and for the natural irregularities
78− // in the finger movement.
79− // Proper value is likely device-specific.
80− Q_PROPERTY(qreal maxDeviation READ maxDeviation WRITE setMaxDeviation NOTIFY maxDeviationChanged)
81−
82− // Widening angle, in degrees
83− // It's roughly the maximum angle a touch point can make relative to the
84− // axis defined by the compoment's direction for it to be recognized as a
85− // directional drag.
86− Q_PROPERTY(qreal wideningAngle READ wideningAngle WRITE setWideningAngle
87− NOTIFY wideningAngleChanged)
88−
89− // How far a touch point has to move from its initial position in order for
90− // it to be recognized as a directional drag.
91− Q_PROPERTY(qreal distanceThreshold READ distanceThreshold WRITE setDistanceThreshold
92− NOTIFY distanceThresholdChanged)
93−
94− // Minimum speed a gesture needs to have in order to be recognized as a
95− // directional drag.
96− // In pixels per second
97− Q_PROPERTY(qreal minSpeed READ minSpeed WRITE setMinSpeed NOTIFY minSpeedChanged)
98−
99− // A gesture will be rejected if more than maxSilenceTime milliseconds has
100− // passed since we last got an input event from it (during Undecided state).
101− //
102− // Silence (i.e., lack of new input events) doesn't necessarily mean that the user's
103− // finger is still (zero drag speed). In some cases the finger might be moving but
104− // the driver's high noise filtering might cause those silence periods, specially
105− // in the moments succeeding a press (talking about Galaxy Nexus here).
106− Q_PROPERTY(int maxSilenceTime READ maxSilenceTime
107− WRITE setMaxSilenceTime
108− NOTIFY maxSilenceTimeChanged)
109−
67+ // Whether a gesture should be Recognized as soon a touch lands on the area.
68+ // With this property enabled it will work pretty much like a MultiPointTouchArea,
69+ // just with a different API.
11070 //
111− /////
112−
113− // Maximum time (in milliseconds) after the start of a given touch point where
114− // subsequent touch starts are grouped with the first one into an N-touches gesture
115− // (e.g. a two-fingers tap or drag).
116− Q_PROPERTY(int compositionTime READ compositionTime
117− WRITE setCompositionTime
118− NOTIFY compositionTimeChanged)
71+ // It's false by default. In most cases you will not want that enabled.
72+ Q_PROPERTY(bool immediateRecognition
73+ READ immediateRecognition
74+ WRITE setImmediateRecognition
75+ NOTIFY immediateRecognitionChanged)
11976
12077 Q_ENUMS(Direction)
121− Q_ENUMS(Status)
12278public:
12379 DirectionalDragArea(QQuickItem *parent = 0);
12480
12581 Direction::Type direction() const;
12682 void setDirection(Direction::Type);
12783
128− // Describes the state of the directional drag gesture.
129− enum Status {
130− // Waiting for a new touch point to land on this area. No gesture is being processed
131− // or tracked.
132− WaitingForTouch,
133−
134− // A touch point has landed on this area but it's not know yet whether it is
135− // performing a drag in the correct direction.
136− // If it's decided that the touch point is not performing a directional drag gesture,
137− // it will be rejected/ignored and status will return to WaitingForTouch.
138− Undecided, //Recognizing,
139−
140− // There's a touch point in this area and it performed a drag in the correct
141− // direction.
142− //
143− // Once recognized, the gesture state will move back to WaitingForTouch only once
144− // that touch point ends. The gesture will remain in the Recognized state even if
145− // the touch point starts moving in other directions or halts.
146− Recognized,
147− };
148− Status status() const { return m_status; }
149−
15084 qreal distance() const;
15185 qreal sceneDistance() const;
152− void updateSceneDistance();
15386
15487 qreal touchX() const;
15588 qreal touchY() const;
@@ -157,152 +90,30 @@ public:
15790 qreal touchSceneX() const;
15891 qreal touchSceneY() const;
15992
160− bool dragging() const { return (m_status == Undecided) || (m_status == Recognized); }
161−
162− qreal maxDeviation() const { return m_dampedScenePos.maxDelta(); }
163− void setMaxDeviation(qreal value);
164−
165− qreal wideningAngle() const;
166− void setWideningAngle(qreal value);
167−
168− qreal distanceThreshold() const { return m_distanceThreshold; }
169− void setDistanceThreshold(qreal value);
170−
171− qreal minSpeed() const { return m_minSpeed; }
172− void setMinSpeed(qreal value);
93+ bool dragging() const;
17394
174− int maxSilenceTime() const { return m_maxSilenceTime; }
175− void setMaxSilenceTime(int value);
176−
177− int compositionTime() const { return m_compositionTime; }
178− void setCompositionTime(int value);
179−
180− // Replaces the existing Timer with the given one.
181− //
182− // Useful for providing a fake timer when testing.
183− void setRecognitionTimer(UbuntuGestures::AbstractTimer *timer);
184−
185− // Useful for testing, where a fake time source can be supplied
186− void setTimeSource(const UbuntuGestures::SharedTimeSource &timeSource);
95+ bool immediateRecognition() const;
96+ void setImmediateRecognition(bool enabled);
18797
18898 bool event(QEvent *e) override;
18999
190− // Maximum time, in milliseconds, between a press and a release, for a touch
191− // sequence to be considered a tap.
192− int maxTapDuration() const { return 300; }
193−
194100Q_SIGNALS:
195101 void directionChanged(Direction::Type direction);
196− void statusChanged(Status value);
197102 void draggingChanged(bool value);
198103 void distanceChanged(qreal value);
199104 void sceneDistanceChanged(qreal value);
200− void maxDeviationChanged(qreal value);
201− void wideningAngleChanged(qreal value);
202− void distanceThresholdChanged(qreal value);
203− void minSpeedChanged(qreal value);
204− void maxSilenceTimeChanged(int value);
205− void compositionTimeChanged(int value);
206105 void touchXChanged(qreal value);
207106 void touchYChanged(qreal value);
208107 void touchSceneXChanged(qreal value);
209108 void touchSceneYChanged(qreal value);
210−
211− // TODO: I would rather not have such signal as it has nothing to do with drag gestures.
212− // Remove when no longer used or move its implementation to the QML code that uses it
213− // See maxTapDuration()
214− void tapped();
109+ void immediateRecognitionChanged(bool value);
215110
216111protected:
217112 virtual void touchEvent(QTouchEvent *event);
113+ virtual void itemChange(ItemChange change, const ItemChangeData &value);
218114
219−private Q_SLOTS:
220− void checkSpeed();
221− void giveUpIfDisabledOrInvisible();
222−
223−private:
224− void touchEvent_absent(QTouchEvent *event);
225− void touchEvent_undecided(QTouchEvent *event);
226− void touchEvent_recognized(QTouchEvent *event);
227− bool pointInsideAllowedArea() const;
228− bool movingInRightDirection() const;
229− bool movedFarEnough(const QPointF &point) const;
230− const QTouchEvent::TouchPoint *fetchTargetTouchPoint(QTouchEvent *event);
231− void setStatus(Status newStatus);
232− void setPreviousPos(const QPointF &point);
233− void setPreviousScenePos(const QPointF &point);
234− void updateVelocityCalculator(const QPointF &point);
235− bool isWithinTouchCompositionWindow();
236− void updateSceneDirectionVector();
237− // returns the scalar projection between the given vector (in scene coordinates)
238− // and m_sceneDirectionVector
239− qreal projectOntoDirectionVector(const QPointF &sceneVector) const;
240− void touchOwnershipEvent(TouchOwnershipEvent *event);
241− void unownedTouchEvent(UnownedTouchEvent *event);
242− void unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent);
243− void watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints);
244− bool recognitionIsDisabled() const;
245− void emitSignalIfTapped();
246−
247− Status m_status;
248−
249− QPointF m_startPos;
250− QPointF m_startScenePos;
251− QPointF m_previousPos;
252− QPointF m_previousScenePos;
253− qreal m_sceneDistance;
254− int m_touchId;
255−
256− // A movement damper is used in some of the gesture recognition calculations
257− // to get rid of noise or small oscillations in the touch position.
258− DampedPointF m_dampedScenePos;
259− QPointF m_previousDampedScenePos;
260−
261− // Unit vector in scene coordinates describing the direction of the gesture recognition
262− QPointF m_sceneDirectionVector;
263−
264− Direction::Type m_direction;
265− qreal m_wideningAngle; // in degrees
266− qreal m_wideningFactor; // it's pow(cosine(m_wideningAngle), 2)
267− qreal m_distanceThreshold;
268− qreal m_distanceThresholdSquared; // it's pow(m_distanceThreshold, 2)
269− qreal m_minSpeed;
270− int m_maxSilenceTime; // in milliseconds
271− int m_silenceTime; // in milliseconds
272− int m_compositionTime; // in milliseconds
273− int m_numSamplesOnLastSpeedCheck;
274− UbuntuGestures::AbstractTimer *m_recognitionTimer;
275− AxisVelocityCalculator *m_velocityCalculator;
276−
277− UbuntuGestures::SharedTimeSource m_timeSource;
278−
279− // Information about an active touch point
280− struct ActiveTouchInfo {
281− ActiveTouchInfo() : id(-1), startTime(-1) {}
282− bool isValid() const { return id != -1; }
283− void reset() { id = -1; }
284− int id;
285− qint64 startTime;
286− };
287− class ActiveTouchesInfo {
288− public:
289− ActiveTouchesInfo(const UbuntuGestures::SharedTimeSource &timeSource);
290− void update(QTouchEvent *event);
291− qint64 touchStartTime(int id);
292− bool isEmpty() const { return m_touchInfoPool.isEmpty(); }
293− qint64 mostRecentStartTime();
294− UbuntuGestures::SharedTimeSource m_timeSource;
295− private:
296− void addTouchPoint(int touchId);
297− void removeTouchPoint(int touchId);
298− #if ACTIVETOUCHESINFO_DEBUG
299− QString toString();
300− #endif
301−
302− Pool<ActiveTouchInfo> m_touchInfoPool;
303− } m_activeTouches;
304−
305− friend class tst_DirectionalDragArea;
115+public: // so tests can access it
116+ DirectionalDragAreaPrivate *d;
306117};
307118
308119#endif // DIRECTIONAL_DRAG_AREA_H
plugins/Ubuntu/Gestures/DirectionalDragArea_p.hnew file+167
@@ -0,0 +1,167 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef DIRECTIONAL_DRAG_AREA_PRIV_H
18+#define DIRECTIONAL_DRAG_AREA_PRIV_H
19+
20+// Information about an active touch point
21+struct UBUNTUGESTURESQML_EXPORT ActiveTouchInfo {
22+ ActiveTouchInfo() : id(-1), startTime(-1) {}
23+ bool isValid() const { return id != -1; }
24+ void reset() { id = -1; }
25+ int id;
26+ qint64 startTime;
27+};
28+class UBUNTUGESTURESQML_EXPORT ActiveTouchesInfo {
29+public:
30+ ActiveTouchesInfo(const UbuntuGestures::SharedTimeSource &timeSource);
31+ void update(QTouchEvent *event);
32+ qint64 touchStartTime(int id);
33+ bool isEmpty() const { return m_touchInfoPool.isEmpty(); }
34+ qint64 mostRecentStartTime();
35+ UbuntuGestures::SharedTimeSource m_timeSource;
36+private:
37+ void addTouchPoint(int touchId);
38+ void removeTouchPoint(int touchId);
39+ #if ACTIVETOUCHESINFO_DEBUG
40+ QString toString();
41+ #endif
42+
43+ Pool<ActiveTouchInfo> m_touchInfoPool;
44+};
45+
46+class UBUNTUGESTURESQML_EXPORT DirectionalDragAreaPrivate : public QObject {
47+ Q_OBJECT
48+
49+ Q_ENUMS(Status)
50+public:
51+ DirectionalDragAreaPrivate(DirectionalDragArea *q);
52+
53+public Q_SLOTS:
54+ void giveUpIfDisabledOrInvisible();
55+ void rejectGesture();
56+
57+public:
58+ // Describes the state of the directional drag gesture.
59+ enum Status {
60+ // Waiting for a new touch point to land on this area. No gesture is being processed
61+ // or tracked.
62+ WaitingForTouch,
63+
64+ // A touch point has landed on this area but it's not know yet whether it is
65+ // performing a drag in the correct direction.
66+ // If it's decided that the touch point is not performing a directional drag gesture,
67+ // it will be rejected/ignored and status will return to WaitingForTouch.
68+ Undecided, //Recognizing,
69+
70+ // There's a touch point in this area and it performed a drag in the correct
71+ // direction.
72+ //
73+ // Once recognized, the gesture state will move back to WaitingForTouch only once
74+ // that touch point ends. The gesture will remain in the Recognized state even if
75+ // the touch point starts moving in other directions or halts.
76+ Recognized,
77+ };
78+
79+ void touchEvent_absent(QTouchEvent *event);
80+ void touchEvent_undecided(QTouchEvent *event);
81+ void touchEvent_recognized(QTouchEvent *event);
82+ bool movingInRightDirection() const;
83+ bool movedFarEnoughAlongGestureAxis() const;
84+ bool isPastMaxDistance() const;
85+ const QTouchEvent::TouchPoint *fetchTargetTouchPoint(QTouchEvent *event);
86+ void setStatus(Status newStatus);
87+ void setPublicPos(const QPointF &point);
88+ void setPublicScenePos(const QPointF &point);
89+ bool isWithinTouchCompositionWindow();
90+ void updateSceneDirectionVector();
91+ // returns the scalar projection between the given vector (in scene coordinates)
92+ // and m_sceneDirectionVector
93+ qreal projectOntoDirectionVector(const QPointF &sceneVector) const;
94+ void touchOwnershipEvent(TouchOwnershipEvent *event);
95+ void unownedTouchEvent(UnownedTouchEvent *event);
96+ void unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent);
97+ void watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints);
98+ bool recognitionIsDisabled() const;
99+ bool sanityCheckRecognitionProperties();
100+ void updateSceneDistance();
101+ void setMaxTime(int value);
102+ void setDistanceThreshold(qreal value);
103+ void setPixelsPerMm(qreal pixelsPerMm);
104+ QString objectName() const { return q->objectName(); }
105+
106+ // Replaces the existing Timer with the given one.
107+ //
108+ // Useful for providing a fake timer when testing.
109+ void setRecognitionTimer(UbuntuGestures::AbstractTimer *timer);
110+
111+ // Useful for testing, where a fake time source can be supplied
112+ void setTimeSource(const UbuntuGestures::SharedTimeSource &timeSource);
113+
114+ DirectionalDragArea *q;
115+
116+ // The current status of the directional drag gesture area.
117+ Status status;
118+
119+ QPointF startPos;
120+ QPointF startScenePos;
121+ qreal sceneDistance;
122+ int touchId;
123+
124+ // The touch position exposed in the public API.
125+ // It only starts to move once the gesture gets recognized.
126+ QPointF publicPos;
127+ QPointF publicScenePos;
128+
129+ // A movement damper is used in some of the gesture recognition calculations
130+ // to get rid of noise or small oscillations in the touch position.
131+ DampedPointF dampedScenePos;
132+ QPointF previousDampedScenePos;
133+
134+ // Unit vector in scene coordinates describing the direction of the gesture recognition
135+ QPointF sceneDirectionVector;
136+
137+ Direction::Type direction;
138+
139+ // How far a touch point has to move from its initial position along the gesture axis in order
140+ // for it to be recognized as a directional drag.
141+ qreal distanceThreshold;
142+ qreal distanceThresholdSquared; // it's pow(distanceThreshold, 2)
143+
144+ // Maximum time (in milliseconds) the gesture can take to go beyond the distance threshold
145+ int maxTime;
146+
147+ // Maximum distance the gesture can go without crossing the axis-aligned distance threshold
148+ qreal maxDistance;
149+
150+ // Maximum time (in milliseconds) after the start of a given touch point where
151+ // subsequent touch starts are grouped with the first one into an N-touches gesture
152+ // (e.g. a two-fingers tap or drag).
153+ int compositionTime;
154+
155+ bool immediateRecognition;
156+
157+ UbuntuGestures::AbstractTimer *recognitionTimer;
158+
159+ UbuntuGestures::SharedTimeSource timeSource;
160+
161+ ActiveTouchesInfo activeTouches;
162+
163+Q_SIGNALS:
164+ void statusChanged(Status value);
165+};
166+
167+#endif // DIRECTIONAL_DRAG_AREA_PRIV_H
plugins/Ubuntu/Gestures/TouchDispatcher.cpp+88−53
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -28,8 +28,11 @@
2828#define TOUCHDISPATCHER_DEBUG 0
2929
3030#if TOUCHDISPATCHER_DEBUG
31+#define ugDebug(params) qDebug().nospace() << "[TouchDispatcher(" << this << ")] " << params
3132#include <DebugHelpers.h>
32−#endif
33+#else // TOUCHDISPATCHER_DEBUG
34+#define ugDebug(params) ((void)0)
35+#endif // TOUCHDISPATCHER_DEBUG
3336
3437TouchDispatcher::TouchDispatcher()
3538 : m_status(NoActiveTouch)
@@ -44,13 +47,12 @@ void TouchDispatcher::setTargetItem(QQuickItem *target)
4447 m_targetItem = target;
4548 if (m_status != NoActiveTouch) {
4649 qWarning("[TouchDispatcher] Changing target item in the middle of a touch stream");
47− m_status = TargetRejectedTouches;
50+ setStatus(TargetRejectedTouches);
4851 }
4952 }
5053}
5154
52−void TouchDispatcher::dispatch(QEvent::Type eventType,
53− QTouchDevice *device,
55+void TouchDispatcher::dispatch(QTouchDevice *device,
5456 Qt::KeyboardModifiers modifiers,
5557 const QList<QTouchEvent::TouchPoint> &touchPoints,
5658 QWindow *window,
@@ -61,6 +63,8 @@ void TouchDispatcher::dispatch(QEvent::Type eventType,
6163 return;
6264 }
6365
66+ QEvent::Type eventType = resolveEventType(touchPoints);
67+
6468 if (eventType == QEvent::TouchBegin) {
6569 dispatchTouchBegin(device, modifiers, touchPoints, window, timestamp);
6670
@@ -72,15 +76,13 @@ void TouchDispatcher::dispatch(QEvent::Type eventType,
7276 dispatchAsMouse(device, modifiers, touchPoints, timestamp);
7377 } else {
7478 Q_ASSERT(m_status == TargetRejectedTouches);
75− #if TOUCHDISPATCHER_DEBUG
76− qDebug() << "[TouchDispatcher] Not dispatching touch event to" << m_targetItem.data()
77− << "because it already rejected the touch stream.";
78− #endif
79+ ugDebug("Not dispatching touch event to " << m_targetItem.data()
80+ << "because it already rejected the touch stream.");
7981 // Do nothing
8082 }
8183
8284 if (eventType == QEvent::TouchEnd) {
83− m_status = NoActiveTouch;
85+ setStatus(NoActiveTouch);
8486 m_touchMouseId = -1;
8587 }
8688
@@ -103,10 +105,7 @@ void TouchDispatcher::dispatchTouchBegin(
103105 QQuickItem *targetItem = m_targetItem.data();
104106
105107 if (!targetItem->isEnabled() || !targetItem->isVisible()) {
106− #if TOUCHDISPATCHER_DEBUG
107− qDebug() << "[TouchDispatcher] Cannot dispatch touch event to" << targetItem
108− << "because it's disabled or invisible.";
109− #endif
108+ ugDebug("Cannot dispatch touch event to " << targetItem << " because it's disabled or invisible.");
110109 return;
111110 }
112111
@@ -118,62 +117,46 @@ void TouchDispatcher::dispatchTouchBegin(
118117 createQTouchEvent(QEvent::TouchBegin, device, modifiers, targetTouchPoints, window, timestamp));
119118
120119
121− #if TOUCHDISPATCHER_DEBUG
122− qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(touchEvent.data()))
123− << "to" << targetItem;
124− #endif
120+ ugDebug("dispatching " << qPrintable(touchEventToString(touchEvent.data()))
121+ << " to " << targetItem);
125122 QCoreApplication::sendEvent(targetItem, touchEvent.data());
126123
127124
128125 if (touchEvent->isAccepted()) {
129− #if TOUCHDISPATCHER_DEBUG
130− qDebug() << "[TouchDispatcher] Item accepted the touch event.";
131− #endif
132− m_status = DeliveringTouchEvents;
126+ ugDebug("Item accepted the touch event.");
127+ setStatus(DeliveringTouchEvents);
133128 } else if (targetItem->acceptedMouseButtons() & Qt::LeftButton) {
134− #if TOUCHDISPATCHER_DEBUG
135− qDebug() << "[TouchDispatcher] Item rejected the touch event. Trying a QMouseEvent";
136− #endif
129+ ugDebug("Item rejected the touch event. Trying a QMouseEvent");
137130 // NB: Arbitrarily chose the first touch point to emulate the mouse pointer
138131 QScopedPointer<QMouseEvent> mouseEvent(
139132 touchToMouseEvent(QEvent::MouseButtonPress, targetTouchPoints.at(0), timestamp,
140133 modifiers, false /* transformNeeded */));
141134 Q_ASSERT(targetTouchPoints.at(0).state() == Qt::TouchPointPressed);
142135
143− #if TOUCHDISPATCHER_DEBUG
144− qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
145− << "to" << m_targetItem.data();
146− #endif
136+ ugDebug("dispatching " << qPrintable(mouseEventToString(mouseEvent.data()))
137+ << " to " << m_targetItem.data());
147138 QCoreApplication::sendEvent(targetItem, mouseEvent.data());
148139 if (mouseEvent->isAccepted()) {
149− #if TOUCHDISPATCHER_DEBUG
150− qDebug() << "[TouchDispatcher] Item accepted the QMouseEvent.";
151− #endif
152− m_status = DeliveringMouseEvents;
140+ ugDebug("Item accepted the QMouseEvent.");
141+ setStatus(DeliveringMouseEvents);
153142 m_touchMouseId = targetTouchPoints.at(0).id();
154143
155144 if (checkIfDoubleClicked(timestamp)) {
156145 QScopedPointer<QMouseEvent> doubleClickEvent(
157146 touchToMouseEvent(QEvent::MouseButtonDblClick, targetTouchPoints.at(0), timestamp,
158147 modifiers, false /* transformNeeded */));
159− #if TOUCHDISPATCHER_DEBUG
160− qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(doubleClickEvent.data()))
161− << "to" << m_targetItem.data();
162− #endif
148+ ugDebug("dispatching " << qPrintable(mouseEventToString(doubleClickEvent.data()))
149+ << " to " << m_targetItem.data());
163150 QCoreApplication::sendEvent(targetItem, doubleClickEvent.data());
164151 }
165152
166153 } else {
167− #if TOUCHDISPATCHER_DEBUG
168− qDebug() << "[TouchDispatcher] Item rejected the QMouseEvent.";
169− #endif
170− m_status = TargetRejectedTouches;
154+ ugDebug("Item rejected the QMouseEvent.");
155+ setStatus(TargetRejectedTouches);
171156 }
172157 } else {
173− #if TOUCHDISPATCHER_DEBUG
174− qDebug() << "[TouchDispatcher] Item rejected the touch event and does not accept mouse buttons.";
175− #endif
176− m_status = TargetRejectedTouches;
158+ ugDebug("Item rejected the touch event and does not accept mouse buttons.");
159+ setStatus(TargetRejectedTouches);
177160 }
178161}
179162
@@ -194,10 +177,8 @@ void TouchDispatcher::dispatchAsTouch(QEvent::Type eventType,
194177 createQTouchEvent(eventType, device, modifiers, targetTouchPoints, window, timestamp));
195178
196179
197− #if TOUCHDISPATCHER_DEBUG
198− qDebug() << "[TouchDispatcher] dispatching" << qPrintable(touchEventToString(eventForTargetItem.data()))
199− << "to" << targetItem;
200− #endif
180+ ugDebug("dispatching " << qPrintable(touchEventToString(eventForTargetItem.data()))
181+ << " to " << targetItem);
201182 QCoreApplication::sendEvent(targetItem, eventForTargetItem.data());
202183}
203184
@@ -254,10 +235,8 @@ void TouchDispatcher::dispatchAsMouse(
254235 QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(eventType, *touchMouse, timestamp, modifiers,
255236 true /* transformNeeded */));
256237
257− #if TOUCHDISPATCHER_DEBUG
258− qDebug() << "[TouchDispatcher] dispatching" << qPrintable(mouseEventToString(mouseEvent.data()))
259− << "to" << m_targetItem.data();
260− #endif
238+ ugDebug("dispatching " << qPrintable(mouseEventToString(mouseEvent.data()))
239+ << " to " << m_targetItem.data());
261240 QCoreApplication::sendEvent(m_targetItem.data(), mouseEvent.data());
262241 }
263242}
@@ -365,3 +344,59 @@ bool TouchDispatcher::checkIfDoubleClicked(ulong newPressEventTimestamp)
365344
366345 return doubleClicked;
367346}
347+
348+void TouchDispatcher::setStatus(Status status)
349+{
350+ if (status != m_status) {
351+ #if TOUCHDISPATCHER_DEBUG
352+ switch (status) {
353+ case NoActiveTouch:
354+ ugDebug("status = NoActiveTouch");
355+ break;
356+ case DeliveringTouchEvents:
357+ ugDebug("status = DeliveringTouchEvents");
358+ break;
359+ case DeliveringMouseEvents:
360+ ugDebug("status = DeliveringMouseEvents");
361+ break;
362+ case TargetRejectedTouches:
363+ ugDebug("status = TargetRejectedTouches");
364+ break;
365+ default:
366+ ugDebug("status = " << status);
367+ break;
368+ }
369+ #endif
370+ m_status = status;
371+ }
372+}
373+
374+void TouchDispatcher::reset()
375+{
376+ setStatus(NoActiveTouch);
377+ m_touchMouseId = -1;
378+ m_touchMousePressTimestamp =0;
379+}
380+
381+QEvent::Type TouchDispatcher::resolveEventType(const QList<QTouchEvent::TouchPoint> &touchPoints)
382+{
383+ QEvent::Type eventType;
384+
385+ Qt::TouchPointStates eventStates = 0;
386+ for (int i = 0; i < touchPoints.count(); i++)
387+ eventStates |= touchPoints[i].state();
388+
389+ switch (eventStates) {
390+ case Qt::TouchPointPressed:
391+ eventType = QEvent::TouchBegin;
392+ break;
393+ case Qt::TouchPointReleased:
394+ eventType = QEvent::TouchEnd;
395+ break;
396+ default:
397+ eventType = QEvent::TouchUpdate;
398+ break;
399+ }
400+
401+ return eventType;
402+}
plugins/Ubuntu/Gestures/TouchDispatcher.h+16−9
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -36,12 +36,20 @@ public:
3636 void setTargetItem(QQuickItem *target);
3737 QQuickItem *targetItem() { return m_targetItem; }
3838
39− void dispatch(QEvent::Type eventType,
40− QTouchDevice *device,
39+ void dispatch(QTouchDevice *device,
4140 Qt::KeyboardModifiers modifiers,
4241 const QList<QTouchEvent::TouchPoint> &touchPoints,
4342 QWindow *window,
4443 ulong timestamp);
44+
45+ void reset();
46+
47+ enum Status {
48+ NoActiveTouch,
49+ DeliveringTouchEvents,
50+ DeliveringMouseEvents,
51+ TargetRejectedTouches
52+ };
4553private:
4654 void dispatchTouchBegin(
4755 QTouchDevice *device,
@@ -73,14 +81,13 @@ private:
7381
7482 bool checkIfDoubleClicked(ulong newPressEventTimestamp);
7583
84+ void setStatus(Status status);
85+
86+ static QEvent::Type resolveEventType(const QList<QTouchEvent::TouchPoint> &touchPoints);
87+
7688 QPointer<QQuickItem> m_targetItem;
7789
78− enum {
79− NoActiveTouch,
80− DeliveringTouchEvents,
81− DeliveringMouseEvents,
82− TargetRejectedTouches
83− } m_status;
90+ Status m_status;
8491
8592 int m_touchMouseId;
8693 ulong m_touchMousePressTimestamp;
plugins/Ubuntu/Gestures/TouchGate.cpp+84−60
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -23,8 +23,18 @@
2323#include <TouchRegistry.h>
2424
2525#if TOUCHGATE_DEBUG
26+#define ugDebug(params) qDebug().nospace() << "[TouchGate(" << (void*)this << ")] " << params
2627#include <DebugHelpers.h>
27−#endif
28+#else // TOUCHGATE_DEBUG
29+#define ugDebug(params) ((void)0)
30+#endif // TOUCHGATE_DEBUG
31+
32+TouchGate::TouchGate(QQuickItem *parent)
33+ : QQuickItem(parent)
34+{
35+ connect(this, &QQuickItem::enabledChanged,
36+ this, &TouchGate::onEnabledChanged);
37+}
2838
2939bool TouchGate::event(QEvent *e)
3040{
@@ -38,13 +48,12 @@ bool TouchGate::event(QEvent *e)
3848
3949void TouchGate::touchEvent(QTouchEvent *event)
4050{
41− #if TOUCHGATE_DEBUG
42− qDebug() << "[TouchGate] got touch event" << qPrintable(touchEventToString(event));
43− #endif
51+ ugDebug("got touch event" << qPrintable(touchEventToString(event)));
4452 event->accept();
4553
4654 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
47− bool goodToGo = true;
55+ QList<QTouchEvent::TouchPoint> validTouchPoints;
56+ bool ownsAllTouches = true;
4857 for (int i = 0; i < touchPoints.count(); ++i) {
4958 const QTouchEvent::TouchPoint &touchPoint = touchPoints[i];
5059
@@ -57,31 +66,41 @@ void TouchGate::touchEvent(QTouchEvent *event)
5766 Q_EMIT pressed();
5867 }
5968
60− goodToGo &= m_touchInfoMap.contains(touchPoint.id())
61− && m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted;
69+ if (m_touchInfoMap.contains(touchPoint.id())) {
70+ validTouchPoints.append(touchPoint);
71+
72+ ownsAllTouches &= m_touchInfoMap[touchPoint.id()].ownership == OwnershipGranted;
6273
63− if (touchPoint.state() == Qt::TouchPointReleased && m_touchInfoMap.contains(touchPoint.id())) {
64− m_touchInfoMap[touchPoint.id()].ended = true;
74+ if (touchPoint.state() == Qt::TouchPointReleased) {
75+ m_touchInfoMap[touchPoint.id()].ended = true;
76+ }
6577 }
6678
6779 }
6880
69− if (goodToGo) {
81+ if (validTouchPoints.isEmpty()) {
82+ // nothing to do.
83+ return;
84+ }
85+
86+ if (ownsAllTouches) {
7087 if (m_storedEvents.isEmpty()) {
7188 // let it pass through
72− dispatchTouchEventToTarget(event);
89+ removeTouchInfoForEndedTouches(validTouchPoints);
90+ m_dispatcher.dispatch(event->device(), event->modifiers(), validTouchPoints,
91+ event->window(), event->timestamp());
7392 } else {
7493 // Retain the event to ensure TouchGate dispatches them in order.
7594 // Otherwise the current event would come before the stored ones, which are older.
76− #if TOUCHGATE_DEBUG
77− qDebug("[TouchGate] Storing event because thouches %s are still pending ownership.",
78− qPrintable(oldestPendingTouchIdsString()));
79− #endif
80− storeTouchEvent(event);
95+ ugDebug("Storing event because thouches " << qPrintable(oldestPendingTouchIdsString())
96+ << " are still pending ownership.");
97+ storeTouchEvent(event->device(), event->modifiers(), validTouchPoints,
98+ event->window(), event->timestamp());
8199 }
82100 } else {
83101 // Retain events that have unowned touches
84− storeTouchEvent(event);
102+ storeTouchEvent(event->device(), event->modifiers(), validTouchPoints,
103+ event->window(), event->timestamp());
85104 }
86105}
87106
@@ -90,24 +109,23 @@ void TouchGate::touchOwnershipEvent(TouchOwnershipEvent *event)
90109 // TODO: Optimization: batch those actions as TouchOwnershipEvents
91110 // might come one right after the other.
92111
93− Q_ASSERT(m_touchInfoMap.contains(event->touchId()));
112+ if (m_touchInfoMap.contains(event->touchId())) {
113+ TouchInfo &touchInfo = m_touchInfoMap[event->touchId()];
94114
95− TouchInfo &touchInfo = m_touchInfoMap[event->touchId()];
115+ if (event->gained()) {
116+ ugDebug("Got ownership of touch " << event->touchId());
117+ touchInfo.ownership = OwnershipGranted;
118+ } else {
119+ ugDebug("Lost ownership of touch " << event->touchId());
120+ m_touchInfoMap.remove(event->touchId());
121+ removeTouchFromStoredEvents(event->touchId());
122+ }
96123
97− if (event->gained()) {
98− #if TOUCHGATE_DEBUG
99− qDebug() << "[TouchGate] Got ownership of touch " << event->touchId();
100− #endif
101− touchInfo.ownership = OwnershipGranted;
124+ dispatchFullyOwnedEvents();
102125 } else {
103− #if TOUCHGATE_DEBUG
104− qDebug() << "[TouchGate] Lost ownership of touch " << event->touchId();
105− #endif
106− m_touchInfoMap.remove(event->touchId());
107− removeTouchFromStoredEvents(event->touchId());
126+ // Ignore it. It probably happened because the TouchGate got disabled
127+ // between the time it requested ownership and the time it got it.
108128 }
109−
110− dispatchFullyOwnedEvents();
111129}
112130
113131bool TouchGate::isTouchPointOwned(int touchId) const
@@ -115,14 +133,15 @@ bool TouchGate::isTouchPointOwned(int touchId) const
115133 return m_touchInfoMap[touchId].ownership == OwnershipGranted;
116134}
117135
118−void TouchGate::storeTouchEvent(const QTouchEvent *event)
136+void TouchGate::storeTouchEvent(QTouchDevice *device,
137+ Qt::KeyboardModifiers modifiers,
138+ const QList<QTouchEvent::TouchPoint> &touchPoints,
139+ QWindow *window,
140+ ulong timestamp)
119141{
120− #if TOUCHGATE_DEBUG
121− qDebug() << "[TouchGate] Storing" << qPrintable(touchEventToString(event));
122− #endif
123−
124− TouchEvent clonedEvent(event);
125− m_storedEvents.append(std::move(clonedEvent));
142+ ugDebug("Storing" << touchPoints);
143+ TouchEvent event(device, modifiers, touchPoints, window, timestamp);
144+ m_storedEvents.append(std::move(event));
126145}
127146
128147void TouchGate::removeTouchFromStoredEvents(int touchId)
@@ -195,25 +214,13 @@ void TouchGate::setTargetItem(QQuickItem *item)
195214void TouchGate::dispatchTouchEventToTarget(const TouchEvent &event)
196215{
197216 removeTouchInfoForEndedTouches(event.touchPoints);
198− m_dispatcher.dispatch(event.eventType,
199− event.device,
217+ m_dispatcher.dispatch(event.device,
200218 event.modifiers,
201219 event.touchPoints,
202220 event.window,
203221 event.timestamp);
204222}
205223
206−void TouchGate::dispatchTouchEventToTarget(QTouchEvent* event)
207−{
208− removeTouchInfoForEndedTouches(event->touchPoints());
209− m_dispatcher.dispatch(event->type(),
210− event->device(),
211− event->modifiers(),
212− event->touchPoints(),
213− event->window(),
214− event->timestamp());
215−}
216−
217224void TouchGate::removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints)
218225{
219226 for (int i = 0; i < touchPoints.size(); ++i) {\
@@ -228,14 +235,31 @@ void TouchGate::removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoi
228235 }
229236}
230237
231−TouchGate::TouchEvent::TouchEvent(const QTouchEvent *event)
232− : eventType(event->type())
233− , device(event->device())
234− , modifiers(event->modifiers())
235− , touchPoints(event->touchPoints())
236− , target(qobject_cast<QQuickItem*>(event->target()))
237− , window(event->window())
238− , timestamp(event->timestamp())
238+void TouchGate::onEnabledChanged()
239+{
240+ ugDebug(" enabled = " << isEnabled());
241+ if (!isEnabled()) {
242+ reset();
243+ }
244+}
245+
246+void TouchGate::reset()
247+{
248+ m_storedEvents.clear();
249+ m_touchInfoMap.clear();
250+ m_dispatcher.reset();
251+}
252+
253+TouchGate::TouchEvent::TouchEvent(QTouchDevice *device,
254+ Qt::KeyboardModifiers modifiers,
255+ const QList<QTouchEvent::TouchPoint> &touchPoints,
256+ QWindow *window,
257+ ulong timestamp)
258+ : device(device)
259+ , modifiers(modifiers)
260+ , touchPoints(touchPoints)
261+ , window(window)
262+ , timestamp(timestamp)
239263{
240264}
241265
plugins/Ubuntu/Gestures/TouchGate.h+18−5
@@ -45,6 +45,8 @@ class UBUNTUGESTURESQML_EXPORT TouchGate : public QQuickItem {
4545 Q_PROPERTY(QQuickItem* targetItem READ targetItem WRITE setTargetItem NOTIFY targetItemChanged)
4646
4747public:
48+ TouchGate(QQuickItem *parent = nullptr);
49+
4850 bool event(QEvent *e) override;
4951
5052 QQuickItem *targetItem() { return m_dispatcher.targetItem(); }
@@ -57,31 +59,42 @@ Q_SIGNALS:
5759
5860protected:
5961 void touchEvent(QTouchEvent *event) override;
62+
63+private Q_SLOTS:
64+ void onEnabledChanged();
65+
6066private:
67+ void reset();
68+
6169 class TouchEvent {
6270 public:
63− TouchEvent(const QTouchEvent *event);
71+ TouchEvent(QTouchDevice *device,
72+ Qt::KeyboardModifiers modifiers,
73+ const QList<QTouchEvent::TouchPoint> &touchPoints,
74+ QWindow *window,
75+ ulong timestamp);
6476
6577 bool removeTouch(int touchId);
6678
67− QEvent::Type eventType;
6879 QTouchDevice *device;
6980 Qt::KeyboardModifiers modifiers;
7081 QList<QTouchEvent::TouchPoint> touchPoints;
71− QQuickItem *target;
7282 QWindow *window;
7383 ulong timestamp;
7484 };
7585
7686 void touchOwnershipEvent(TouchOwnershipEvent *event);
7787 bool isTouchPointOwned(int touchId) const;
78− void storeTouchEvent(const QTouchEvent *event);
88+ void storeTouchEvent(QTouchDevice *device,
89+ Qt::KeyboardModifiers modifiers,
90+ const QList<QTouchEvent::TouchPoint> &touchPoints,
91+ QWindow *window,
92+ ulong timestamp);
7993 void removeTouchFromStoredEvents(int touchId);
8094 void dispatchFullyOwnedEvents();
8195 bool eventIsFullyOwned(const TouchEvent &event) const;
8296
8397 void dispatchTouchEventToTarget(const TouchEvent &event);
84− void dispatchTouchEventToTarget(QTouchEvent* event);
8598
8699 void removeTouchInfoForEndedTouches(const QList<QTouchEvent::TouchPoint> &touchPoints);
87100
qml/Components/DragHandle.qml+18−33
@@ -43,16 +43,8 @@ import Ubuntu.Gestures 0.1
4343 }
4444
4545 */
46−EdgeDragArea {
46+DirectionalDragArea {
4747 id: dragArea
48− objectName: "dragHandle"
49−
50− // Disable gesture recognition by default when hinting is used as
51− // it conflicts with the hinting idea.
52− distanceThreshold: hintDisplacement > 0 ? 0 : defaultDistanceThreshold
53− maxSilenceTime: hintDisplacement > 0 ? 60*60*1000 : defaultMaxSilenceTime
54− maxDeviation: hintDisplacement > 0 ? 999999 : defaultMaxDeviation
55− compositionTime: hintDisplacement > 0 ? 0 : defaultCompositionTime
5648
5749 property bool stretch: false
5850
@@ -68,6 +60,9 @@ EdgeDragArea {
6860 }
6961
7062 property real hintDisplacement: 0
63+
64+ immediateRecognition: hintDisplacement > 0
65+
7166 property var overrideStartValue: undefined
7267 SmoothedAnimation {
7368 id: hintingAnimation
@@ -98,7 +93,6 @@ EdgeDragArea {
9893 // Private stuff
9994 QtObject {
10095 id: d
101− property var previousStatus: DirectionalDragArea.WaitingForTouch
10296 property real startValue
10397 property real minValue: {
10498 if (direction == Direction.Horizontal) {
@@ -183,7 +177,7 @@ EdgeDragArea {
183177 }
184178
185179 onDistanceChanged: {
186− if (status === DirectionalDragArea.Recognized) {
180+ if (dragging) {
187181 // don't go the whole distance in order to smooth out the movement
188182 var step = distance * 0.3;
189183
@@ -193,31 +187,22 @@ EdgeDragArea {
193187 }
194188 }
195189
196− onStatusChanged: {
197− if (status === DirectionalDragArea.WaitingForTouch) {
198− hintingAnimation.stop();
199− if (d.previousStatus === DirectionalDragArea.Recognized) {
200− d.onFinishedRecognizedGesture();
201− } else /* d.previousStatus === DirectionalDragArea.Undecided */ {
202− // Gesture was rejected.
203− d.rollbackDrag();
190+ onDraggingChanged: {
191+ if (dragging) {
192+ dragEvaluator.reset();
193+ if (overrideStartValue !== undefined) {
194+ d.startValue = overrideStartValue;
195+ } else {
196+ d.startValue = parent[d.targetProp];
204197 }
205− } else /* Undecided || Recognized */ {
206− if (d.previousStatus === DirectionalDragArea.WaitingForTouch) {
207− dragEvaluator.reset();
208− if (overrideStartValue !== undefined) {
209− d.startValue = overrideStartValue;
210− } else {
211− d.startValue = parent[d.targetProp];
212− }
213198
214− if (hintDisplacement > 0) {
215− hintingAnimation.targetValue = d.startValue;
216− hintingAnimation.start();
217− }
199+ if (hintDisplacement > 0) {
200+ hintingAnimation.targetValue = d.startValue;
201+ hintingAnimation.start();
218202 }
203+ } else {
204+ hintingAnimation.stop();
205+ d.onFinishedRecognizedGesture();
219206 }
220−
221− d.previousStatus = status;
222207 }
223208}
qml/Components/EdgeDragArea.qmldeleted−46
@@ -1,46 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import Ubuntu.Components 0.1
19−import Ubuntu.Gestures 0.1
20−
21−/*
22− A DirectionalDragArea wrapper that provides some well-chosen defaults
23− for its gesture recognition parameters.
24−*/
25−DirectionalDragArea {
26−
27− // TODO: Re-evaluate those or even the recognition heuristics itself once
28− // we have gesture cancelling/forwarding in place.
29− //
30− // The idea here is that it's better having lax rules than false negatives.
31− // False negatives are very frustrating to the user.
32− maxDeviation: defaultMaxDeviation
33− wideningAngle: defaultWideningAngle
34− distanceThreshold: defaultDistanceThreshold
35− minSpeed: defaultMinSpeed
36− maxSilenceTime: defaultMaxSilenceTime
37− compositionTime: defaultCompositionTime
38−
39− readonly property real defaultMaxDeviation: units.gu(3)
40− readonly property real defaultWideningAngle: 50
41− readonly property real defaultDistanceThreshold: units.gu(1.5)
42− // some people were getting false negatives with it enabled.
43− readonly property real defaultMinSpeed: units.gu(0)
44− readonly property int defaultMaxSilenceTime: 200
45− readonly property int defaultCompositionTime: 60
46−}
qml/Dash/Dash.qml+5−12
@@ -140,7 +140,7 @@ Showable {
140140 // (as expected) but would also cause the dash content flickable to move a bit, because
141141 // that flickable was getting the touch events while overviewDragHandle was still undecided
142142 // about whether that touch was indeed performing a directional drag gesture.
143− forceNonInteractive: overviewDragHandle.status != DirectionalDragArea.WaitingForTouch
143+ forceNonInteractive: overviewDragHandle.dragging
144144
145145 enabled: bottomEdgeController.progress == 0
146146 }
@@ -317,7 +317,7 @@ Showable {
317317 }
318318 }
319319
320− EdgeDragArea {
320+ DirectionalDragArea {
321321 id: overviewDragHandle
322322 objectName: "overviewDragHandle"
323323 z: 1
@@ -333,21 +333,14 @@ Showable {
333333 height: units.gu(2)
334334
335335 onSceneDistanceChanged: {
336− if (status == DirectionalDragArea.Recognized) {
336+ if (dragging) {
337337 bottomEdgeController.enableAnimation = false;
338338 bottomEdgeController.progress = Math.max(0, Math.min(1, sceneDistance / fullMovement));
339339 }
340340 }
341341
342− property int previousStatus: -1
343− property int currentStatus: DirectionalDragArea.WaitingForTouch
344−
345− onStatusChanged: {
346− previousStatus = currentStatus;
347− currentStatus = status;
348−
349− if (status == DirectionalDragArea.WaitingForTouch &&
350− previousStatus == DirectionalDragArea.Recognized) {
342+ onDraggingChanged: {
343+ if (!dragging) {
351344 bottomEdgeController.enableAnimation = true;
352345 bottomEdgeController.progress = (bottomEdgeController.progress > 0.2) ? 1 : 0;
353346 }
qml/Greeter/CoverPage.qml+1
@@ -129,6 +129,7 @@ Showable {
129129
130130 DragHandle {
131131 id: dragHandle
132+ objectName: "coverPageDragHandle"
132133 anchors.fill: parent
133134 anchors.leftMargin: root.dragHandleLeftMargin
134135 enabled: root.draggable
qml/Launcher/Launcher.qml+6−14
@@ -210,7 +210,7 @@ Item {
210210 bottom: parent.bottom
211211 }
212212 x: -width
213− visible: root.x > 0 || x > -width || dragArea.status === DirectionalDragArea.Undecided
213+ visible: root.x > 0 || x > -width
214214 model: LauncherModel
215215
216216 property bool animate: true
@@ -272,7 +272,7 @@ Item {
272272 }
273273 }
274274
275− EdgeDragArea {
275+ DirectionalDragArea {
276276 id: dragArea
277277 objectName: "launcherDragArea"
278278
@@ -284,19 +284,10 @@ Item {
284284 height: root.height
285285
286286 onTouchXChanged: {
287− if (status !== DirectionalDragArea.Recognized || launcher.state == "visible")
287+ if (!dragging || launcher.state == "visible")
288288 return;
289289
290− // When the gesture finally gets recognized, the finger will likely be
291− // reasonably far from the edge. If we made the panel immediately
292− // follow the finger position it would be visually unpleasant as it
293− // would appear right next to the user's finger out of nowhere.
294− // Instead, we make the panel go towards the user's finger in several
295− // steps. ie., in an animated way.
296− var targetPanelX = Math.min(0, touchX - panel.width) - root.x
297− var delta = targetPanelX - panel.x
298− // the trick is not to go all the way (1.0) as it would cause a sudden jump
299− panel.x += 0.4 * delta
290+ panel.x = Math.min(0, touchX - panel.width) - root.x
300291 }
301292
302293 onDraggingChanged: {
@@ -306,7 +297,8 @@ Item {
306297 if (distance > minimizeDistance) {
307298 root.dash();
308299 }
309− } else {
300+ } else if (root.state === "") {
301+ // didn't drag far enough. rollback
310302 root.switchToNextState("")
311303 }
312304 }
qml/Panel/IndicatorsMenu.qml+12−2
@@ -182,9 +182,18 @@ Showable {
182182 enabled: !root.shown && root.available
183183 autoCompleteDragThreshold: maxTotalDragDistance / 2
184184 stretch: true
185− distanceThreshold: enableHint ? 0 : minimizedPanelHeight
186185
187− onTapped: showTapped(Qt.point(touchSceneX, touchSceneY));
186+ onDraggingChanged: {
187+ if (dragging) {
188+ touchPressTime = new Date().getTime();
189+ } else {
190+ var touchReleaseTime = new Date().getTime();
191+ if (touchReleaseTime - touchPressTime <= 300) {
192+ root.showTapped(Qt.point(touchSceneX, touchSceneY));
193+ }
194+ }
195+ }
196+ property var touchPressTime
188197
189198 // using hint regulates minimum to hint displacement, but in fullscreen mode, we need to do it manually.
190199 overrideStartValue: enableHint ? minimizedPanelHeight : expandedPanelHeight + handle.height
@@ -200,6 +209,7 @@ Showable {
200209
201210 DragHandle {
202211 id: __hideDragHandle
212+ objectName: "hideDragHandle"
203213 anchors.fill: handle
204214 direction: Direction.Upwards
205215 enabled: root.shown && root.available
qml/Stages/PhoneStage.qml+31−41
@@ -146,14 +146,13 @@ Rectangle {
146146 id: spreadView
147147 objectName: "spreadView"
148148 anchors.fill: parent
149− interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1)
150− && draggedDelegateCount === 0
149+ interactive: (spreadDragArea.dragging || phase > 1) && draggedDelegateCount === 0
151150 contentWidth: spreadRow.width - shift
152151 contentX: -shift
153152
154153 // This indicates when the spreadView is active. That means, all the animations
155154 // are activated and tiles need to line up for the spread.
156− readonly property bool active: shiftedContentX > 0 || spreadDragArea.status === DirectionalDragArea.Recognized || !root.focusFirstApp
155+ readonly property bool active: shiftedContentX > 0 || spreadDragArea.dragging || !root.focusFirstApp
157156
158157 // The flickable needs to fill the screen in order to get touch events all over.
159158 // However, we don't want to the user to be able to scroll back all the way. For
@@ -450,7 +449,7 @@ Rectangle {
450449 }
451450 }
452451
453− EdgeDragArea {
452+ DirectionalDragArea {
454453 id: spreadDragArea
455454 objectName: "spreadDragArea"
456455 direction: Direction.Leftwards
@@ -462,59 +461,50 @@ Rectangle {
462461 property var gesturePoints: new Array()
463462
464463 onTouchXChanged: {
465− if (!dragging) {
466− // Initial touch. Let's reset the spreadView to the starting position.
467− spreadView.phase = 0;
468− spreadView.contentX = -spreadView.shift;
469− }
470− if (dragging && status == DirectionalDragArea.Recognized) {
464+ if (dragging) {
471465 // Gesture recognized. Let's move the spreadView with the finger
472466 var dragX = Math.min(touchX + width, width); // Prevent dragging rightwards
473467 dragX = -dragX + spreadDragArea.width - spreadView.shift;
474468 // Don't allow dragging further than the animation crossing with phase2's animation
475469 var maxMovement = spreadView.width * spreadView.positionMarker4 - spreadView.shift;
470+
476471 spreadView.contentX = Math.min(dragX, maxMovement);
472+ } else {
473+ // Initial touch. Let's reset the spreadView to the starting position.
474+ spreadView.phase = 0;
475+ spreadView.contentX = -spreadView.shift;
477476 }
478− gesturePoints.push(touchX);
479− }
480−
481− property int previousStatus: -1
482− property int currentStatus: DirectionalDragArea.WaitingForTouch
483477
484− onStatusChanged: {
485− previousStatus = currentStatus;
486− currentStatus = status;
478+ gesturePoints.push(touchX);
487479 }
488480
489481 onDraggingChanged: {
490482 if (dragging) {
491483 // A potential edge-drag gesture has started. Start recording it
492484 gesturePoints = [];
493− return;
494− }
485+ } else {
486+ // Ok. The user released. Find out if it was a one-way movement.
487+ var oneWayFlick = true;
488+ var smallestX = spreadDragArea.width;
489+ for (var i = 0; i < gesturePoints.length; i++) {
490+ if (gesturePoints[i] >= smallestX) {
491+ oneWayFlick = false;
492+ break;
493+ }
494+ smallestX = gesturePoints[i];
495+ }
496+ gesturePoints = [];
495497
496− // Ok. The user released. Find out if it was a one-way movement.
497− var oneWayFlick = true;
498− var smallestX = spreadDragArea.width;
499− for (var i = 0; i < gesturePoints.length; i++) {
500− if (gesturePoints[i] >= smallestX) {
501− oneWayFlick = false;
502− break;
498+ if (oneWayFlick && spreadView.shiftedContentX > units.gu(2) &&
499+ spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
500+ // If it was a short one-way movement, do the Alt+Tab switch
501+ // no matter if we didn't cross positionMarker1 yet.
502+ spreadView.snapTo(1);
503+ } else if (!dragging) {
504+ // otherwise snap to the closest snap position we can find
505+ // (might be back to start, to app 1 or to spread)
506+ spreadView.snap();
503507 }
504− smallestX = gesturePoints[i];
505− }
506− gesturePoints = [];
507−
508− if (previousStatus == DirectionalDragArea.Recognized &&
509− oneWayFlick && spreadView.shiftedContentX > units.gu(2) &&
510− spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
511− // If it was a short one-way movement, do the Alt+Tab switch
512− // no matter if we didn't cross positionMarker1 yet.
513− spreadView.snapTo(1);
514− } else if (!dragging) {
515− // otherwise snap to the closest snap position we can find
516− // (might be back to start, to app 1 or to spread)
517− spreadView.snap();
518508 }
519509 }
520510 }
qml/Stages/TabletStage.qml+18−20
@@ -170,8 +170,7 @@ Rectangle {
170170 Flickable {
171171 id: spreadView
172172 anchors.fill: parent
173− interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1)
174− && draggedDelegateCount === 0
173+ interactive: (spreadDragArea.dragging || phase > 1) && draggedDelegateCount === 0
175174 contentWidth: spreadRow.width - shift
176175 contentX: -shift
177176
@@ -598,7 +597,7 @@ Rectangle {
598597 }
599598 }
600599
601− EdgeDragArea {
600+ DirectionalDragArea {
602601 id: spreadDragArea
603602 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
604603 width: root.dragAreaWidth
@@ -624,26 +623,25 @@ Rectangle {
624623 if (dragging) {
625624 // Gesture recognized. Start recording this gesture
626625 gesturePoints = [];
627− return;
628− }
629−
630− // Ok. The user released. Find out if it was a one-way movement.
631− var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
632− gesturePoints = [];
626+ } else {
627+ // Ok. The user released. Find out if it was a one-way movement.
628+ var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
629+ gesturePoints = [];
633630
634− if (oneWayFlick && spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
635− // If it was a short one-way movement, do the Alt+Tab switch
636− // no matter if we didn't cross positionMarker1 yet.
637− spreadView.snapTo(spreadView.nextInStack);
638− } else if (!dragging) {
639− if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker1) {
640− spreadView.snap();
641− } else if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
631+ if (oneWayFlick && spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
632+ // If it was a short one-way movement, do the Alt+Tab switch
633+ // no matter if we didn't cross positionMarker1 yet.
642634 spreadView.snapTo(spreadView.nextInStack);
643635 } else {
644− // otherwise snap to the closest snap position we can find
645− // (might be back to start, to app 1 or to spread)
646− spreadView.snap();
636+ if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker1) {
637+ spreadView.snap();
638+ } else if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
639+ spreadView.snapTo(spreadView.nextInStack);
640+ } else {
641+ // otherwise snap to the closest snap position we can find
642+ // (might be back to start, to app 1 or to spread)
643+ spreadView.snap();
644+ }
647645 }
648646 }
649647 }
qml/Tutorial/TutorialBottom.qml+1−1
@@ -73,7 +73,7 @@ TutorialPage {
7373 ]
7474 }
7575
76− EdgeDragArea {
76+ DirectionalDragArea {
7777 id: dragArea
7878 direction: Direction.Upwards
7979 anchors {
tests/libs/UbuntuGestures/tst_TouchRegistry.cpp+68−2
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -66,6 +66,7 @@ private Q_SLOTS:
6666 void candidatesAndWatchers_2();
6767 void rejectingTouchfterItsEnd();
6868 void removeOldUndecidedCandidates();
69+ void interimOwnerWontGetUnownedTouchEvents();
6970};
7071
7172void tst_TouchRegistry::requestWithNoCandidates()
@@ -742,7 +743,7 @@ void tst_TouchRegistry::removeOldUndecidedCandidates()
742743
743744 // Simulate that enough time has passed to cause the CandidateInactivityTimer to timeout,
744745 // making TouchRegistry consider that undecidedCantidate defaulted.
745− fakeTimerFactory->makeRunningTimersTimeout();
746+ fakeTimerFactory->updateTime(10000);
746747
747748 QVERIFY(undecidedCandidate.ownedTouches.isEmpty());
748749 QVERIFY(undecidedCandidate.lostTouches.contains(0));
@@ -752,6 +753,71 @@ void tst_TouchRegistry::removeOldUndecidedCandidates()
752753 delete touchRegistry;
753754}
754755
756+/*
757+ An item that calls requestTouchOwnership() without first having called addCandidateOwnerForTouch()
758+ is assumed to be the interim owner of the touch point, thus there's no point in sending
759+ UnownedTouchEvents to him as he already gets proper QTouchEvents from QQuickWindow because
760+ he didn't ignore the first QTouchEvent with that touch point.
761+ */
762+void tst_TouchRegistry::interimOwnerWontGetUnownedTouchEvents()
763+{
764+ FakeTimerFactory *fakeTimerFactory = new FakeTimerFactory;
765+ TouchRegistry *touchRegistry = new TouchRegistry(nullptr, fakeTimerFactory);
766+
767+ DummyCandidate undecidedCandidate;
768+ undecidedCandidate.setObjectName("undecided");
769+
770+ DummyCandidate interimOwner;
771+ interimOwner.setObjectName("interimOwner");
772+
773+ {
774+ QList<QTouchEvent::TouchPoint> touchPoints;
775+ touchPoints.append(QTouchEvent::TouchPoint(0));
776+ touchPoints[0].setState(Qt::TouchPointPressed);
777+ QTouchEvent touchEvent(QEvent::TouchBegin,
778+ 0 /* device */,
779+ Qt::NoModifier,
780+ Qt::TouchPointPressed,
781+ touchPoints);
782+ touchRegistry->update(&touchEvent);
783+ }
784+
785+ touchRegistry->addCandidateOwnerForTouch(0, &undecidedCandidate);
786+ touchRegistry->requestTouchOwnership(0, &interimOwner);
787+
788+ {
789+ QList<QTouchEvent::TouchPoint> touchPoints;
790+ touchPoints.append(QTouchEvent::TouchPoint(0));
791+ touchPoints[0].setState(Qt::TouchPointMoved);
792+ QTouchEvent touchEvent(QEvent::TouchUpdate,
793+ 0 /* device */,
794+ Qt::NoModifier,
795+ Qt::TouchPointMoved,
796+ touchPoints);
797+ touchRegistry->update(&touchEvent);
798+ }
799+
800+ QCOMPARE(undecidedCandidate.unownedTouchEvents.count(), 1);
801+ QCOMPARE(interimOwner.unownedTouchEvents.count(), 0);
802+
803+ {
804+ QList<QTouchEvent::TouchPoint> touchPoints;
805+ touchPoints.append(QTouchEvent::TouchPoint(0));
806+ touchPoints[0].setState(Qt::TouchPointMoved);
807+ QTouchEvent touchEvent(QEvent::TouchUpdate,
808+ 0 /* device */,
809+ Qt::NoModifier,
810+ Qt::TouchPointMoved,
811+ touchPoints);
812+ touchRegistry->update(&touchEvent);
813+ }
814+
815+ QCOMPARE(undecidedCandidate.unownedTouchEvents.count(), 2);
816+ QCOMPARE(interimOwner.unownedTouchEvents.count(), 0);
817+
818+ delete touchRegistry;
819+}
820+
755821////////////// TouchMemento //////////
756822
757823TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
tests/plugins/Ubuntu/Gestures/CMakeLists.txt+3−1
@@ -19,6 +19,7 @@ add_custom_target(UbuntuGesturesTestQmlFiles ALL
1919)
2020
2121add_definitions(-DUBUNTU_GESTURES_PLUGIN_DIR="${CMAKE_BINARY_DIR}/plugins")
22+add_definitions(-DTESTS_UTILS_MODULES_DIR="${CMAKE_BINARY_DIR}/tests/utils/modules")
2223
2324macro(add_gesture_ui_test CLASSNAME)
2425 add_executable(${CLASSNAME}TestExec tst_${CLASSNAME}.cpp GestureTest.cpp)
@@ -26,7 +27,8 @@ macro(add_gesture_ui_test CLASSNAME)
2627 target_link_libraries(${CLASSNAME}TestExec UbuntuGesturesQml UbuntuGestures)
2728
2829 add_binary_qml_test(${CLASSNAME} ${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures UbuntuGesturesTestQmlFiles "")
29− add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
30+ add_manual_qml_test(. ${CLASSNAME} IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins
31+ ${CMAKE_BINARY_DIR}/tests/utils/modules)
3032endmacro(add_gesture_ui_test)
3133
3234macro(add_gesture_test CLASSNAME)
tests/plugins/Ubuntu/Gestures/DownwardsLauncher.qml+5−19
@@ -37,8 +37,8 @@ Item {
3737
3838 Rectangle {
3939 id: dragAreaRect
40− color: "yellow"
41− opacity: 0.0
40+ opacity: dragArea.dragging ? 0.5 : 0.0
41+ color: "green"
4242 anchors.fill: dragArea
4343 }
4444
@@ -49,24 +49,10 @@ Item {
4949 height: units.gu(5)
5050
5151 direction: Direction.Downwards
52− maxDeviation: units.gu(2)
53− wideningAngle: 10
54− distanceThreshold: units.gu(4)
5552
56− onStatusChanged: {
57− switch (status) {
58− case DirectionalDragArea.WaitingForTouch:
59− dragAreaRect.opacity = 0.0
60− break;
61− case DirectionalDragArea.Undecided:
62− dragAreaRect.color = "yellow"
63− dragAreaRect.opacity = 0.3
64− launcher.y = Qt.binding(launcher.followDragArea)
65− break;
66− default: // DirectionalDragArea.Recognized:
67− dragAreaRect.color = "green"
68− dragAreaRect.opacity = 0.5
69− break;
53+ onDraggingChanged: {
54+ if (dragging) {
55+ launcher.y = Qt.binding(launcher.followDragArea)
7056 }
7157 }
7258
tests/plugins/Ubuntu/Gestures/GestureTest.cpp+13−3
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013,2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,11 @@
2121#include <QQuickView>
2222#include <QtTest>
2323
24+#include <Timer.h>
2425#include <TouchRegistry.h>
2526
27+using namespace UbuntuGestures;
28+
2629GestureTest::GestureTest(const QString &qmlFilename)
2730 : QObject(), m_device(nullptr), m_view(nullptr), m_qmlFilename(qmlFilename)
2831{
@@ -40,14 +43,17 @@ void GestureTest::initTestCase()
4043void GestureTest::init()
4144{
4245 m_view = new QQuickView;
43− m_view->setResizeMode(QQuickView::SizeViewToRootObject);
46+ m_view->setResizeMode(QQuickView::SizeRootObjectToView);
4447 m_view->engine()->addImportPath(QStringLiteral(UBUNTU_GESTURES_PLUGIN_DIR));
48+ m_view->engine()->addImportPath(QStringLiteral(TESTS_UTILS_MODULES_DIR));
4549 m_view->setSource(QUrl::fromLocalFile(m_qmlFilename));
4650 m_view->show();
4751 QVERIFY(QTest::qWaitForWindowExposed(m_view));
4852 QVERIFY(m_view->rootObject() != 0);
4953
50− m_touchRegistry = new TouchRegistry;
54+ m_fakeTimerFactory = new FakeTimerFactory;
55+
56+ m_touchRegistry = new TouchRegistry(nullptr, m_fakeTimerFactory);
5157 m_view->installEventFilter(m_touchRegistry);
5258
5359 qApp->processEvents();
@@ -59,6 +65,10 @@ void GestureTest::cleanup()
5965 delete m_touchRegistry;
6066 m_touchRegistry = nullptr;
6167
68+ // TouchRegistry will take down the timer factory along with him
69+ // delete m_fakeTimerFactory;
70+ m_fakeTimerFactory = nullptr;
71+
6272 delete m_view;
6373 m_view = nullptr;
6474}
tests/plugins/Ubuntu/Gestures/GestureTest.h+4
@@ -23,6 +23,9 @@
2323class QQuickView;
2424class QTouchDevice;
2525
26+namespace UbuntuGestures {
27+ class FakeTimerFactory;
28+}
2629class TouchRegistry;
2730
2831// C++ std lib
@@ -81,6 +84,7 @@ protected:
8184 QTouchDevice *m_device;
8285 QQuickView *m_view;
8386 TouchRegistry *m_touchRegistry;
87+ UbuntuGestures::FakeTimerFactory *m_fakeTimerFactory;
8488 QString m_qmlFilename;
8589};
8690
tests/plugins/Ubuntu/Gestures/LeftwardsLauncher.qml+6−19
@@ -21,7 +21,7 @@ import Ubuntu.Components 0.1
2121Item {
2222 id: root
2323
24− function reset() { launcher.x = root.width }
24+ function reset() { launcher.x = Qt.binding(function(){return root.width;}); }
2525
2626 Rectangle {
2727 id: launcher
@@ -41,7 +41,8 @@ Item {
4141
4242 Rectangle {
4343 id: dragAreaRect
44− opacity: 0.0
44+ opacity: dragArea.dragging ? 0.5 : 0.0
45+ color: "green"
4546 anchors.fill: dragArea
4647 }
4748
@@ -52,24 +53,10 @@ Item {
5253 width: units.gu(5)
5354
5455 direction: Direction.Leftwards
55− maxDeviation: units.gu(2)
56− wideningAngle: 10
57− distanceThreshold: units.gu(4)
5856
59− onStatusChanged: {
60− switch (status) {
61− case DirectionalDragArea.WaitingForTouch:
62− dragAreaRect.opacity = 0.0
63− break;
64− case DirectionalDragArea.Undecided:
65− dragAreaRect.color = "yellow"
66− dragAreaRect.opacity = 0.3
67− launcher.x = Qt.binding(launcher.followDragArea)
68− break;
69− default: //case DirectionalDragArea.Recognized:
70− dragAreaRect.color = "green"
71− dragAreaRect.opacity = 0.5
72− break;
57+ onDraggingChanged: {
58+ if (dragging) {
59+ launcher.x = Qt.binding(launcher.followDragArea)
7360 }
7461 }
7562
tests/plugins/Ubuntu/Gestures/RightwardsLauncher.qml+5−19
@@ -38,7 +38,8 @@ Item {
3838
3939 Rectangle {
4040 id: dragAreaRect
41− opacity: 0.0
41+ opacity: dragArea.dragging ? 0.5 : 0.0
42+ color: "green"
4243 anchors.fill: dragArea
4344 }
4445
@@ -52,25 +53,10 @@ Item {
5253 width: units.gu(5)
5354
5455 direction: Direction.Rightwards
55− maxDeviation: units.gu(2)
56− wideningAngle: 10
57− distanceThreshold: units.gu(4)
58− minSpeed: 50
5956
60− onStatusChanged: {
61− switch (status) {
62− case DirectionalDragArea.WaitingForTouch:
63− dragAreaRect.opacity = 0.0
64− break;
65− case DirectionalDragArea.Undecided:
66− dragAreaRect.color = "yellow"
67− dragAreaRect.opacity = 0.3
68− launcher.x = Qt.binding(launcher.followDragArea)
69− break;
70− default: //case DirectionalDragArea.Recognized:
71− dragAreaRect.color = "green"
72− dragAreaRect.opacity = 0.5
73− break;
57+ onDraggingChanged: {
58+ if (dragging) {
59+ launcher.x = Qt.binding(launcher.followDragArea)
7460 }
7561 }
7662
tests/plugins/Ubuntu/Gestures/UpwardsLauncher.qml+6−19
@@ -21,7 +21,7 @@ import Ubuntu.Components 0.1
2121Item {
2222 id: root
2323
24− function reset() { launcher.y = root.height }
24+ function reset() { launcher.y = Qt.binding(function(){return root.height;}); }
2525
2626 Rectangle {
2727 id: launcher
@@ -41,7 +41,8 @@ Item {
4141
4242 Rectangle {
4343 id: dragAreaRect
44− opacity: 0.0
44+ opacity: dragArea.dragging ? 0.5 : 0.0
45+ color: "green"
4546 anchors.fill: dragArea
4647 }
4748
@@ -52,24 +53,10 @@ Item {
5253 height: units.gu(5)
5354
5455 direction: Direction.Upwards
55− maxDeviation: units.gu(2)
56− wideningAngle: 10
57− distanceThreshold: units.gu(4)
5856
59− onStatusChanged: {
60− switch (status) {
61− case DirectionalDragArea.WaitingForTouch:
62− dragAreaRect.opacity = 0.0
63− break;
64− case DirectionalDragArea.Undecided:
65− dragAreaRect.color = "yellow"
66− dragAreaRect.opacity = 0.3
67− launcher.y = Qt.binding(launcher.followDragArea)
68− break;
69− default: //case DirectionalDragArea.Recognized:
70− dragAreaRect.color = "green"
71− dragAreaRect.opacity = 0.5
72− break;
57+ onDraggingChanged: {
58+ if (dragging) {
59+ launcher.y = Qt.binding(launcher.followDragArea)
7360 }
7461 }
7562
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.cpp+480−445
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013-2014 Canonical, Ltd.
2+ * Copyright (C) 2013-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -20,51 +20,38 @@
2020#include <QtQml/QQmlEngine>
2121#include <QPointer>
2222#include <private/qquickmousearea_p.h>
23−#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
24− #include <private/qquickwindow_p.h>
25−#endif
23+#include <private/qquickwindow_p.h>
2624
2725
2826#include <DirectionalDragArea.h>
27+#include <DirectionalDragArea_p.h>
2928#include <TouchRegistry.h>
3029
3130#include "GestureTest.h"
3231
3332using namespace UbuntuGestures;
3433
35−
36−class ComplexFakeTimer : public FakeTimer
37−{
34+// Because QSignalSpy(directionalDragArea, SIGNAL(DirectionalDragArea::Status)) simply
35+// doesn't work
36+class StatusSpy : public QObject {
3837 Q_OBJECT
3938public:
40− ComplexFakeTimer(const SharedTimeSource &timeSource, QObject *parent = 0)
41− : FakeTimer(parent),
42− m_timeSource(timeSource)
43− {}
44−
45− void start() override {
46− AbstractTimer::start();
47− m_nextTimeoutTime = m_timeSource->msecsSinceReference() + (qint64)interval();
39+ StatusSpy(DirectionalDragArea *edgeDragArea) {
40+ m_recognized = false;
41+ connect(edgeDragArea->d, &DirectionalDragAreaPrivate::statusChanged,
42+ this, &StatusSpy::onStatusChanged);
4843 }
49−
50− void emitTimeout() {
51− m_nextTimeoutTime += interval();
52− Q_EMIT timeout();
44+ bool recognized() {
45+ return m_recognized;
5346 }
5447
55− qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
48+private Q_SLOTS:
49+ void onStatusChanged(DirectionalDragAreaPrivate::Status status) {
50+ m_recognized |= status == DirectionalDragAreaPrivate::Recognized;
51+ }
5652
5753private:
58− SharedTimeSource m_timeSource;
59− qint64 m_nextTimeoutTime;
60−};
61−
62−class FakeTimeSource : public UbuntuGestures::TimeSource
63−{
64−public:
65− FakeTimeSource() { m_msecsSinceReference = 0; }
66− virtual qint64 msecsSinceReference() {return m_msecsSinceReference;}
67− qint64 m_msecsSinceReference;
54+ bool m_recognized;
6855};
6956
7057/*
@@ -98,15 +85,9 @@ public:
9885 tst_DirectionalDragArea();
9986private Q_SLOTS:
10087 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
10288
103− void edgeDrag();
104− void edgeDrag_data();
10589 void dragWithShortDirectionChange();
106− void minSpeed();
107− void minSpeed_data();
10890 void recognitionTimerUsage();
109− void maxSilenceTime();
11091 void sceneXAndX();
11192 void sceneYAndY();
11293 void twoFingerTap();
@@ -122,14 +103,24 @@ private Q_SLOTS:
122103 void immediateRecognitionWhenConstraintsDisabled();
123104 void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition();
124105 void withdrawTouchOwnershipCandidacyIfDisabledDuringRecognition_data();
125− void tappedSignal();
126− void tappedSignal_data();
127106 void gettingTouchOwnershipMakesMouseAreaBehindGetCanceled();
107+ void interleavedTouches();
108+ void makoRightEdgeDrag();
109+ void makoRightEdgeDrag_verticalDownwards();
110+ void makoLeftEdgeDrag_slowStart();
111+ void makoLeftEdgeDrag_movesSlightlyBackwardsOnStart();
128112
129113private:
114+ // QTest::touchEvent takes QPoint instead of QPointF and I don't want to
115+ // lose precision due to rounding.
116+ // Besides, those helper functions lead to more compact code.
117+ void sendTouchPress(qint64 timestamp, int id, QPointF pos);
118+ void sendTouchUpdate(qint64 timestamp, int id, QPointF pos);
119+ void sendTouchRelease(qint64 timestamp, int id, QPointF pos);
120+ void sendTouch(qint64 timestamp, int id, QPointF pos,
121+ Qt::TouchPointState pointState, QEvent::Type eventType);
122+
130123 void passTime(qint64 timeSpanMs);
131− ComplexFakeTimer *fakeTimer;
132− QSharedPointer<FakeTimeSource> fakeTimeSource;
133124};
134125
135126tst_DirectionalDragArea::tst_DirectionalDragArea()
@@ -148,185 +139,57 @@ void tst_DirectionalDragArea::init()
148139 m_view->resize(m_view->rootObject()->width(), m_view->rootObject()->height());
149140 QTRY_COMPARE(m_view->width(), (int)m_view->rootObject()->width());
150141 QTRY_COMPARE(m_view->height(), (int)m_view->rootObject()->height());
151−
152− fakeTimeSource.reset(new FakeTimeSource);
153− fakeTimer = new ComplexFakeTimer(fakeTimeSource);
154−}
155−
156−void tst_DirectionalDragArea::cleanup()
157−{
158− delete fakeTimer;
159− fakeTimer = 0;
160−
161− fakeTimeSource.reset();
162−
163− GestureTest::cleanup();
164−}
165−
166−void tst_DirectionalDragArea::passTime(qint64 timeSpanMs)
167−{
168− qint64 finalTime = fakeTimeSource->m_msecsSinceReference + timeSpanMs;
169−
170− if (fakeTimer->isRunning() && finalTime >= fakeTimer->nextTimeoutTime()) {
171− fakeTimeSource->m_msecsSinceReference = fakeTimer->nextTimeoutTime();
172− fakeTimer->emitTimeout();
173−
174− qint64 timeSpanRemainder = finalTime - fakeTimeSource->m_msecsSinceReference;
175− if (timeSpanRemainder > 0) {
176− passTime(timeSpanRemainder);
177− }
178− } else {
179− fakeTimeSource->m_msecsSinceReference = finalTime;
180− }
181142}
182143
183−namespace {
184−QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea)
144+void tst_DirectionalDragArea::sendTouchPress(qint64 timestamp, int id, QPointF pos)
185145{
186− QPointF localCenter(edgeDragArea->width() / 2., edgeDragArea->height() / 2.);
187− return edgeDragArea->mapToScene(localCenter);
146+ sendTouch(timestamp, id, pos, Qt::TouchPointPressed, QEvent::TouchBegin);
188147}
189148
190−QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea,
191− qreal wideningAngleMultiplier)
149+void tst_DirectionalDragArea::sendTouchUpdate(qint64 timestamp, int id, QPointF pos)
192150{
193− qreal angleRadians = edgeDragArea->wideningAngle() * wideningAngleMultiplier
194− * M_PI / 180.0;
195−
196− qreal angleCos = qCos(angleRadians);
197− qreal angleSin = qSin(angleRadians);
198−
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− }
151+ sendTouch(timestamp, id, pos, Qt::TouchPointMoved, QEvent::TouchUpdate);
209152}
210153
211−QPointF createTouchDeviation(DirectionalDragArea *edgeDragArea)
154+void tst_DirectionalDragArea::sendTouchRelease(qint64 timestamp, int id, QPointF pos)
212155{
213− qreal deviation = edgeDragArea->maxDeviation() * 0.8;
214−
215− if (Direction::isHorizontal(edgeDragArea->direction())) {
216− return QPointF(0, deviation);
217− } else {
218− return QPointF(deviation, 0);
219− }
220−}
156+ sendTouch(timestamp, id, pos, Qt::TouchPointReleased, QEvent::TouchEnd);
221157}
222158
223−void tst_DirectionalDragArea::edgeDrag()
159+void tst_DirectionalDragArea::sendTouch(qint64 timestamp, int id, QPointF pos,
160+ Qt::TouchPointState pointState, QEvent::Type eventType)
224161{
225− QFETCH(QString, dragAreaObjectName);
226− QFETCH(qreal, wideningAngleMultiplier);
227− QFETCH(qreal, dragDistanceFactor);
228− QFETCH(bool, expectGestureRecognition);
229−
230− DirectionalDragArea *edgeDragArea =
231− m_view->rootObject()->findChild<DirectionalDragArea*>(dragAreaObjectName);
232− QVERIFY(edgeDragArea != 0);
233− edgeDragArea->setRecognitionTimer(fakeTimer);
234− edgeDragArea->setTimeSource(fakeTimeSource);
235−
236− QSignalSpy draggingSpy(edgeDragArea, SIGNAL(draggingChanged(bool)));
237−
238− QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
239− QPointF touchPoint = initialTouchPos;
240−
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;
248−
249− QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
250−
251− QCOMPARE(draggingSpy.count(), 1);
252− QCOMPARE(edgeDragArea->dragging(), true);
253−
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− }
162+ m_fakeTimerFactory->updateTime(timestamp);
261163
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− }
164+ QTouchEvent::TouchPoint point;
267165
268− if (expectGestureRecognition)
269− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
166+ point.setState(pointState);
167+ point.setId(id);
168+ point.setScenePos(pos);
169+ point.setPos(pos);
270170
271− if (edgeDragArea->status() == DirectionalDragArea::WaitingForTouch) {
272− QCOMPARE(edgeDragArea->dragging(), false);
273− QCOMPARE(draggingSpy.count(), 2);
274− }
171+ QList<QTouchEvent::TouchPoint> points;
172+ points << point;
275173
276− QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
174+ QTouchEvent touchEvent(eventType, m_device, Qt::NoModifier, Qt::TouchPointPressed, points);
175+ QCoreApplication::sendEvent(m_view, &touchEvent);
277176
278− QCOMPARE(draggingSpy.count(), 2);
279− QCOMPARE(edgeDragArea->dragging(), false);
177+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
178+ windowPrivate->flushDelayedTouchEvent();
280179}
281180
282−void tst_DirectionalDragArea::edgeDrag_data()
181+void tst_DirectionalDragArea::passTime(qint64 timeSpanMs)
283182{
284− QTest::addColumn<QString>("dragAreaObjectName");
285− QTest::addColumn<qreal>("wideningAngleMultiplier");
286− QTest::addColumn<qreal>("dragDistanceFactor");
287− QTest::addColumn<bool>("expectGestureRecognition");
288−
289− QTest::newRow("rightwards, tiny drag")
290− << "hpDragArea" << 0.0 << 0.2 << false;
291−
292− QTest::newRow("rightwards, straight drag")
293− << "hpDragArea" << 0.0 << 3.0 << true;
294−
295− QTest::newRow("rightwards, diagonal drag")
296− << "hpDragArea" << 0.9 << 3.0 << true;
297−
298− QTest::newRow("rightwards, overly diagonal drag")
299− << "hpDragArea" << 2.0 << 3.0 << false;
300−
301− QTest::newRow("leftwards, tiny drag")
302− << "hnDragArea" << 0.0 << 0.2 << false;
303−
304− QTest::newRow("leftwards, straight drag")
305− << "hnDragArea" << 0.0 << 3.0 << true;
306−
307− QTest::newRow("leftwards, diagonal drag")
308− << "hnDragArea" << 0.9 << 3.0 << true;
309−
310− QTest::newRow("downwards, tiny drag")
311− << "vpDragArea" << 0.0 << 0.2 << false;
312−
313− QTest::newRow("downwards, straight drag")
314− << "vpDragArea" << 0.0 << 3.0 << true;
315−
316− QTest::newRow("downwards, diagonal drag")
317− << "vpDragArea" << 0.9 << 3.0 << true;
318−
319− QTest::newRow("upwards, tiny drag")
320− << "vnDragArea" << 0.0 << 0.2 << false;
321−
322− QTest::newRow("upwards, straight drag")
323− << "vnDragArea" << 0.0 << 3.0 << true;
324−
325− QTest::newRow("upwards, diagonal drag")
326− << "vnDragArea" << 0.9 << 3.0 << true;
183+ qint64 finalTime = m_fakeTimerFactory->timeSource()->msecsSinceReference() + timeSpanMs;
184+ m_fakeTimerFactory->updateTime(finalTime);
185+}
327186
328− QTest::newRow("upwards, overly diagonal drag")
329− << "vnDragArea" << 2.0 << 3.0 << false;
187+namespace {
188+QPointF calculateInitialTouchPos(DirectionalDragArea *edgeDragArea)
189+{
190+ QPointF localCenter(edgeDragArea->width() / 2., edgeDragArea->height() / 2.);
191+ return edgeDragArea->mapToScene(localCenter);
192+}
330193}
331194
332195/*
@@ -339,17 +202,17 @@ void tst_DirectionalDragArea::dragWithShortDirectionChange()
339202 DirectionalDragArea *edgeDragArea =
340203 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
341204 QVERIFY(edgeDragArea != 0);
342− edgeDragArea->setRecognitionTimer(fakeTimer);
343− edgeDragArea->setTimeSource(fakeTimeSource);
205+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
206+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
344207
345208 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
346209 QPointF touchPoint = initialTouchPos;
347210
348− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0;
211+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0;
349212 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;
213+ qreal touchStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
214+ // make sure we are above maximum time
215+ int touchStepTimeMs = edgeDragArea->d->maxTime / 20. ;
353216 QPointF touchMovement = dragDirectionVector * touchStepDistance;
354217
355218 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
@@ -373,65 +236,13 @@ void tst_DirectionalDragArea::dragWithShortDirectionChange()
373236 passTime(touchStepTimeMs);
374237 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
375238 } while ((touchPoint - initialTouchPos).manhattanLength() < desiredDragDistance
376− || fakeTimeSource->m_msecsSinceReference < (edgeDragArea->compositionTime() * 1.5f));
239+ || m_fakeTimerFactory->timeSource()->msecsSinceReference() < (edgeDragArea->d->compositionTime * 1.5f));
377240
378− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
241+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
379242
380243 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
381244}
382245
383−/*
384− Checks that a gesture will be rejected if it's slower than minSpeed while
385− status is Undecided.
386− */
387−void tst_DirectionalDragArea::minSpeed()
388−{
389− QFETCH(qreal, minSpeed);
390− QFETCH(qreal, speed);
391− QFETCH(int, expectedStatusAfterSpeedCheck);
392−
393− DirectionalDragArea *edgeDragArea =
394− m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
395− QVERIFY(edgeDragArea != 0);
396− edgeDragArea->setRecognitionTimer(fakeTimer);
397− edgeDragArea->setTimeSource(fakeTimeSource);
398−
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);
402−
403− edgeDragArea->setMinSpeed(minSpeed);
404−
405− QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
406− QPointF touchPoint = initialTouchPos;
407−
408− QPointF dragDirectionVector(1.0, 0.0);
409− qint64 timeStepMsecs = 10;
410− qreal distanceStep = (speed / 1000.0f) * timeStepMsecs;
411− QPointF touchMovement = dragDirectionVector * distanceStep;
412−
413− QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
414−
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− }
421−
422− QCOMPARE((int)edgeDragArea->status(), expectedStatusAfterSpeedCheck);
423−}
424−
425−void tst_DirectionalDragArea::minSpeed_data()
426−{
427− QTest::addColumn<qreal>("minSpeed");
428− QTest::addColumn<qreal>("speed");
429− QTest::addColumn<int>("expectedStatusAfterSpeedCheck");
430−
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;
433−}
434−
435246/*
436247 Checks that the recognition timer is started and stopped appropriately.
437248 I.e., check that it's running only while gesture recognition is taking place
@@ -442,11 +253,9 @@ void tst_DirectionalDragArea::recognitionTimerUsage()
442253 DirectionalDragArea *edgeDragArea =
443254 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
444255 QVERIFY(edgeDragArea != 0);
445− edgeDragArea->setRecognitionTimer(fakeTimer);
446− edgeDragArea->setTimeSource(fakeTimeSource);
447−
448− // don't let it interfere with our test
449− edgeDragArea->setMinSpeed(0.0);
256+ AbstractTimer *fakeTimer = m_fakeTimerFactory->createTimer();
257+ edgeDragArea->d->setRecognitionTimer(fakeTimer);
258+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
450259
451260 int timeStepMs = 5; // some arbitrary small value.
452261
@@ -454,62 +263,31 @@ void tst_DirectionalDragArea::recognitionTimerUsage()
454263 QPointF touchPoint = initialTouchPos;
455264
456265 QPointF dragDirectionVector(1.0, 0.0);
457− QPointF touchMovement = dragDirectionVector * (edgeDragArea->distanceThreshold() * 0.2f);
266+ QPointF touchMovement = dragDirectionVector * (edgeDragArea->d->distanceThreshold * 0.2f);
458267
459− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
268+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
460269 QVERIFY(!fakeTimer->isRunning());
461270
462271 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
463272
464− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
273+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
465274 QVERIFY(fakeTimer->isRunning());
466275
467276 // Move beyond distance threshold and composition time to ensure recognition
468− while (fakeTimeSource->m_msecsSinceReference <= edgeDragArea->compositionTime() ||
469− (touchPoint - initialTouchPos).manhattanLength() <= edgeDragArea->distanceThreshold()) {
277+ while (m_fakeTimerFactory->timeSource()->msecsSinceReference() <= edgeDragArea->d->compositionTime ||
278+ (touchPoint - initialTouchPos).manhattanLength() <= edgeDragArea->d->distanceThreshold) {
470279
471− QCOMPARE(edgeDragArea->status() == DirectionalDragArea::Undecided, fakeTimer->isRunning());
280+ QCOMPARE(edgeDragArea->d->status == DirectionalDragAreaPrivate::Undecided, fakeTimer->isRunning());
472281
473282 touchPoint += touchMovement;
474283 passTime(timeStepMs);
475284 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
476285 }
477286
478− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
287+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
479288 QVERIFY(!fakeTimer->isRunning());
480289}
481290
482−/*
483− A gesture should be rejected if too much time has passed without any new input
484− events from it.
485− */
486−void tst_DirectionalDragArea::maxSilenceTime()
487−{
488− DirectionalDragArea *edgeDragArea =
489− m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
490− QVERIFY(edgeDragArea != 0);
491− edgeDragArea->setRecognitionTimer(fakeTimer);
492− edgeDragArea->setTimeSource(fakeTimeSource);
493−
494− // Make sure this property is not disabled
495− edgeDragArea->setMaxSilenceTime(100);
496−
497− QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
498− QPointF touchPoint = initialTouchPos;
499−
500− QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
501−
502− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
503− QVERIFY(fakeTimer->isRunning());
504−
505− // Force timer to timeout until after maxSilenceTime has been reached
506− while (fakeTimeSource->m_msecsSinceReference < edgeDragArea->maxSilenceTime()) {
507− passTime(fakeTimer->interval());
508− }
509−
510− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
511−}
512−
513291/*
514292 Checks that it informs the X coordinate of the touch point in local and scene coordinates
515293 correctly.
@@ -519,18 +297,19 @@ void tst_DirectionalDragArea::sceneXAndX()
519297 DirectionalDragArea *edgeDragArea =
520298 m_view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
521299 QVERIFY(edgeDragArea != 0);
522− edgeDragArea->setRecognitionTimer(fakeTimer);
523− edgeDragArea->setTimeSource(fakeTimeSource);
300+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
301+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
302+ edgeDragArea->setImmediateRecognition(true);
524303
525304 QPointF touchScenePos(m_view->width() - (edgeDragArea->width()/2.0f), m_view->height()/2.0f);
526305
527− QTest::touchEvent(m_view, m_device).press(0, touchScenePos.toPoint());
306+ sendTouchPress(0 /* timestamp */, 0 /* id */, touchScenePos);
528307
529308 QSignalSpy touchXSpy(edgeDragArea, SIGNAL(touchXChanged(qreal)));
530309 QSignalSpy touchSceneXSpy(edgeDragArea, SIGNAL(touchSceneXChanged(qreal)));
531310
532311 touchScenePos.rx() = m_view->width() / 2;
533− QTest::touchEvent(m_view, m_device).move(0, touchScenePos.toPoint());
312+ sendTouchUpdate(50 /* timestamp */, 0 /* id */, touchScenePos);
534313
535314 QCOMPARE(touchXSpy.count(), 1);
536315 QCOMPARE(touchSceneXSpy.count(), 1);
@@ -547,18 +326,19 @@ void tst_DirectionalDragArea::sceneYAndY()
547326 DirectionalDragArea *edgeDragArea =
548327 m_view->rootObject()->findChild<DirectionalDragArea*>("vnDragArea");
549328 QVERIFY(edgeDragArea != 0);
550− edgeDragArea->setRecognitionTimer(fakeTimer);
551− edgeDragArea->setTimeSource(fakeTimeSource);
329+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
330+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
331+ edgeDragArea->setImmediateRecognition(true);
552332
553333 QPointF touchScenePos(m_view->width()/2.0f, m_view->height() - (edgeDragArea->height()/2.0f));
554334
555− QTest::touchEvent(m_view, m_device).press(0, touchScenePos.toPoint());
335+ sendTouchPress(0 /* timestamp */, 0 /* id */, touchScenePos);
556336
557337 QSignalSpy touchYSpy(edgeDragArea, SIGNAL(touchYChanged(qreal)));
558338 QSignalSpy touchSceneYSpy(edgeDragArea, SIGNAL(touchSceneYChanged(qreal)));
559339
560340 touchScenePos.ry() = m_view->height() / 2;
561− QTest::touchEvent(m_view, m_device).move(0, touchScenePos.toPoint());
341+ sendTouchUpdate(50 /* timestamp */, 0 /* id */, touchScenePos);
562342
563343 QCOMPARE(touchYSpy.count(), 1);
564344 QCOMPARE(touchSceneYSpy.count(), 1);
@@ -577,8 +357,8 @@ void tst_DirectionalDragArea::twoFingerTap()
577357 DirectionalDragArea *edgeDragArea =
578358 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
579359 QVERIFY(edgeDragArea != 0);
580− edgeDragArea->setRecognitionTimer(fakeTimer);
581− edgeDragArea->setTimeSource(fakeTimeSource);
360+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
361+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
582362
583363 // Make touches evenly spaced along the edgeDragArea
584364 QPoint touchAPos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
@@ -592,7 +372,7 @@ void tst_DirectionalDragArea::twoFingerTap()
592372 QTest::touchEvent(m_view, m_device)
593373 .press(0, touchAPos);
594374
595− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
375+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
596376
597377 passTime(timeStepMsecs);
598378 QTest::touchEvent(m_view, m_device)
@@ -601,20 +381,20 @@ void tst_DirectionalDragArea::twoFingerTap()
601381
602382 // A second touch point appeared during recognition, reject immediately as this
603383 // can't be a single-touch gesture anymore.
604− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
384+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
605385
606386 passTime(timeStepMsecs);
607387 QTest::touchEvent(m_view, m_device)
608388 .release(0, touchAPos)
609389 .move(1, touchBPos);
610390
611− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
391+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
612392
613393 passTime(timeStepMsecs);
614394 QTest::touchEvent(m_view, m_device)
615395 .release(1, touchBPos);
616396
617− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
397+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
618398
619399 // Perform the second two-finger tap
620400
@@ -622,27 +402,27 @@ void tst_DirectionalDragArea::twoFingerTap()
622402 QTest::touchEvent(m_view, m_device)
623403 .press(0, touchAPos);
624404
625− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
405+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
626406
627407 passTime(timeStepMsecs);
628408 QTest::touchEvent(m_view, m_device)
629409 .move(0, touchAPos)
630410 .press(1, touchBPos);
631411
632− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
412+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
633413
634414 passTime(timeStepMsecs);
635415 QTest::touchEvent(m_view, m_device)
636416 .release(0, touchAPos)
637417 .move(1, touchBPos);
638418
639− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
419+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
640420
641421 passTime(timeStepMsecs);
642422 QTest::touchEvent(m_view, m_device)
643423 .release(1, touchBPos);
644424
645− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
425+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
646426}
647427
648428/*
@@ -659,25 +439,25 @@ void tst_DirectionalDragArea::movingDDA()
659439 DirectionalDragArea *edgeDragArea =
660440 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
661441 Q_ASSERT(edgeDragArea != 0);
662− edgeDragArea->setRecognitionTimer(fakeTimer);
663− edgeDragArea->setTimeSource(fakeTimeSource);
442+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
443+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
664444
665445 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
666446 QPointF touchPoint = initialTouchPos;
667447
668− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
448+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
669449 QPointF dragDirectionVector(1.0f, 0.0f);
670450
671− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
451+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
672452 QPointF touchMovement = dragDirectionVector * movementStepDistance;
673453 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
674− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
454+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
675455
676456 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
677457
678458 // Move it far ahead along the direction of the gesture
679459 // rightwardsLauncher is a parent of our DirectionalDragArea. So moving it will move our DDA
680− rightwardsLauncher->setX(rightwardsLauncher->x() + edgeDragArea->distanceThreshold() * 5.0f);
460+ rightwardsLauncher->setX(rightwardsLauncher->x() + edgeDragArea->d->distanceThreshold * 5.0f);
681461
682462 for (int i = 0; i < totalMovementSteps; ++i) {
683463 touchPoint += touchMovement;
@@ -685,7 +465,7 @@ void tst_DirectionalDragArea::movingDDA()
685465 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
686466 }
687467
688− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
468+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
689469
690470 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
691471}
@@ -699,8 +479,8 @@ void tst_DirectionalDragArea::ignoreOldFinger()
699479 DirectionalDragArea *edgeDragArea =
700480 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
701481 Q_ASSERT(edgeDragArea != 0);
702− edgeDragArea->setRecognitionTimer(fakeTimer);
703− edgeDragArea->setTimeSource(fakeTimeSource);
482+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
483+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
704484
705485 // Make touches evenly spaced along the edgeDragArea
706486 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.33f);
@@ -708,26 +488,26 @@ void tst_DirectionalDragArea::ignoreOldFinger()
708488
709489 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
710490
711− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
491+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
712492
713493 // leave it lying around for some time
714− passTime(qMax(edgeDragArea->maxSilenceTime(), edgeDragArea->compositionTime()) * 10);
494+ passTime(edgeDragArea->d->maxTime * 10);
715495
716− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
496+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
717497
718− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
498+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
719499 QPointF dragDirectionVector(1.0f, 0.0f);
720500
721− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
501+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
722502 QPointF touchMovement = dragDirectionVector * movementStepDistance;
723503 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
724− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
504+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
725505
726506 QTest::touchEvent(m_view, m_device)
727507 .move(0, touch0Pos)
728508 .press(1, touch1Pos);
729509
730− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
510+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
731511
732512 for (int i = 0; i < totalMovementSteps; ++i) {
733513 touch1Pos += touchMovement.toPoint();
@@ -737,13 +517,13 @@ void tst_DirectionalDragArea::ignoreOldFinger()
737517 .move(1, touch1Pos);
738518 }
739519
740− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
520+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
741521
742522 QTest::touchEvent(m_view, m_device)
743523 .move(0, touch0Pos)
744524 .release(1, touch1Pos);
745525
746− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
526+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
747527}
748528
749529/*
@@ -762,19 +542,19 @@ void tst_DirectionalDragArea::rotated()
762542 DirectionalDragArea *edgeDragArea =
763543 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
764544 Q_ASSERT(edgeDragArea != 0);
765− edgeDragArea->setRecognitionTimer(fakeTimer);
766− edgeDragArea->setTimeSource(fakeTimeSource);
545+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
546+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
767547
768548 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
769549 QPointF touchPoint = initialTouchPos;
770550
771− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
551+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
772552 QPointF dragDirectionVector(0.0f, 1.0f);
773553
774− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
554+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
775555 QPointF touchMovement = dragDirectionVector * movementStepDistance;
776556 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
777− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
557+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
778558
779559 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
780560
@@ -784,7 +564,7 @@ void tst_DirectionalDragArea::rotated()
784564 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
785565 }
786566
787− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
567+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
788568
789569 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
790570}
@@ -802,31 +582,30 @@ void tst_DirectionalDragArea::sceneDistance()
802582 DirectionalDragArea *edgeDragArea =
803583 rightwardsLauncher->findChild<DirectionalDragArea*>("hpDragArea");
804584 Q_ASSERT(edgeDragArea != 0);
805− edgeDragArea->setRecognitionTimer(fakeTimer);
806− edgeDragArea->setTimeSource(fakeTimeSource);
585+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
586+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
587+
588+ // to disable the position smoothing so that we can more easily check sceneDistance values
589+ edgeDragArea->setImmediateRecognition(true);
807590
808591 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
809592 QPointF touchPoint = initialTouchPos;
810593
811− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
594+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
812595
813− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
596+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
814597 QPointF touchMovement = dragDirectionVector * movementStepDistance;
815598 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
816− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
599+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
817600
818− QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
601+ qint64 timestamp = 0;
602+
603+ sendTouchPress(timestamp, 0, touchPoint);
819604
820−#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
821− QQuickWindowPrivate *wp = QQuickWindowPrivate::get(m_view);
822−#endif
823605 for (int i = 0; i < totalMovementSteps; ++i) {
824606 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
607+ timestamp += movementTimeStepMs;
608+ sendTouchUpdate(timestamp, 0, touchPoint);
830609 }
831610
832611 qreal actualDragDistance = ((qreal)totalMovementSteps) * movementStepDistance;
@@ -836,7 +615,8 @@ void tst_DirectionalDragArea::sceneDistance()
836615 // NB: qFuzzyCompare(), used internally by QCOMPARE(), is broken.
837616 QVERIFY(qAbs(edgeDragArea->sceneDistance() - actualDragDistance) < 0.001);
838617
839− QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
618+ timestamp += movementTimeStepMs;
619+ sendTouchRelease(timestamp, 0, touchPoint);
840620}
841621
842622void tst_DirectionalDragArea::sceneDistance_data()
@@ -860,18 +640,18 @@ void tst_DirectionalDragArea::disabledWhileDragging()
860640 DirectionalDragArea *edgeDragArea =
861641 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
862642 Q_ASSERT(edgeDragArea != 0);
863− edgeDragArea->setRecognitionTimer(fakeTimer);
864− edgeDragArea->setTimeSource(fakeTimeSource);
643+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
644+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
865645
866646 QPointF touchPoint = calculateInitialTouchPos(edgeDragArea);
867647
868− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2.0f;
648+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2.0f;
869649 QPointF dragDirectionVector(1., 0.); // horizontal positive
870650
871− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
651+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
872652 QPointF touchMovement = dragDirectionVector * movementStepDistance;
873653 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
874− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
654+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
875655
876656 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
877657
@@ -881,13 +661,13 @@ void tst_DirectionalDragArea::disabledWhileDragging()
881661 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
882662 }
883663
884− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
664+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
885665 QCOMPARE(edgeDragArea->dragging(), true);
886666
887667 // disable the dragArea while it's being dragged.
888668 edgeDragArea->setEnabled(false);
889669
890− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
670+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
891671 QCOMPARE(edgeDragArea->dragging(), false);
892672
893673 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
@@ -898,15 +678,14 @@ void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
898678 DirectionalDragArea *edgeDragArea =
899679 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
900680 Q_ASSERT(edgeDragArea != 0);
901− edgeDragArea->setRecognitionTimer(fakeTimer);
902− edgeDragArea->setTimeSource(fakeTimeSource);
681+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
682+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
903683
904684 // Disable some constraints we're not interested in
905− edgeDragArea->setMaxSilenceTime(60 * 1000);
906− edgeDragArea->setMinSpeed(0);
685+ edgeDragArea->d->setMaxTime(60 * 1000);
907686
908687 // And ensure others have the values we want
909− edgeDragArea->setCompositionTime(60);
688+ edgeDragArea->d->compositionTime = 60;
910689
911690 // Put an item right behind edgeDragArea to receive the touches ignored by it
912691 DummyItem *dummyItem = new DummyItem;
@@ -923,10 +702,10 @@ void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
923702
924703 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
925704
926− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
705+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
927706
928707 // We are now going to be way beyond compositionTime
929− passTime(edgeDragArea->compositionTime()*3);
708+ passTime(edgeDragArea->d->compositionTime*3);
930709
931710 QTest::touchEvent(m_view, m_device)
932711 .move(0, touch0Pos)
@@ -948,7 +727,7 @@ void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
948727 .move(0, touch0Pos)
949728 .move(1, touch1Pos);
950729
951− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
730+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
952731
953732 passTime(5);
954733
@@ -956,7 +735,7 @@ void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
956735 .release(0, touch0Pos)
957736 .move(1, touch1Pos);
958737
959− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
738+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
960739
961740 passTime(5);
962741
@@ -964,7 +743,7 @@ void tst_DirectionalDragArea::oneFingerDownFollowedByLateSecondFingerDown()
964743 .release(1, touch1Pos);
965744
966745 // Shouldn't be keepping info about touches that no longer exist or interest us
967− QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
746+ QVERIFY(edgeDragArea->d->activeTouches.isEmpty());
968747
969748 delete dummyItem;
970749}
@@ -974,12 +753,11 @@ void tst_DirectionalDragArea::givesUpWhenLosesTouch()
974753 DirectionalDragArea *edgeDragArea =
975754 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
976755 Q_ASSERT(edgeDragArea != 0);
977− edgeDragArea->setRecognitionTimer(fakeTimer);
978− edgeDragArea->setTimeSource(fakeTimeSource);
756+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
757+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
979758
980759 // Disable some constraints we're not interested in
981− edgeDragArea->setMaxSilenceTime(60 * 1000);
982− edgeDragArea->setMinSpeed(0);
760+ edgeDragArea->d->setMaxTime(60 * 1000);
983761
984762 // Put an item right in front of edgeDragArea
985763 DummyItem *dummyItem = new DummyItem(edgeDragArea->parentItem());
@@ -998,11 +776,11 @@ void tst_DirectionalDragArea::givesUpWhenLosesTouch()
998776
999777 QTest::touchEvent(m_view, m_device).press(0, touchPos);
1000778
1001− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
779+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
1002780
1003781 m_touchRegistry->requestTouchOwnership(0, dummyItem);
1004782
1005− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
783+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1006784
1007785 dummyItem->grabTouchPoints({0});
1008786 dummyItem->touchEventHandler = [&](QTouchEvent *event) { event->accept(); };
@@ -1010,9 +788,9 @@ void tst_DirectionalDragArea::givesUpWhenLosesTouch()
1010788 passTime(5);
1011789 QTest::touchEvent(m_view, m_device).release(0, touchPos);
1012790
1013− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
791+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1014792
1015− QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
793+ QVERIFY(edgeDragArea->d->activeTouches.isEmpty());
1016794}
1017795
1018796void tst_DirectionalDragArea::threeFingerDrag()
@@ -1020,15 +798,14 @@ void tst_DirectionalDragArea::threeFingerDrag()
1020798 DirectionalDragArea *edgeDragArea =
1021799 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1022800 Q_ASSERT(edgeDragArea != 0);
1023− edgeDragArea->setRecognitionTimer(fakeTimer);
1024− edgeDragArea->setTimeSource(fakeTimeSource);
801+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
802+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1025803
1026804 // Disable some constraints we're not interested in
1027− edgeDragArea->setMaxSilenceTime(60 * 1000);
1028− edgeDragArea->setMinSpeed(0);
805+ edgeDragArea->d->setMaxTime(60 * 1000);
1029806
1030807 // And ensure others have the values we want
1031− edgeDragArea->setCompositionTime(60);
808+ edgeDragArea->d->compositionTime = 60;
1032809
1033810 // Make touches evenly spaced along the edgeDragArea
1034811 QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()*0.25f);
@@ -1038,14 +815,14 @@ void tst_DirectionalDragArea::threeFingerDrag()
1038815 QTest::touchEvent(m_view, m_device)
1039816 .press(0, touch0Pos);
1040817
1041− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
818+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
1042819
1043820 passTime(5);
1044821 QTest::touchEvent(m_view, m_device)
1045822 .move(0, touch0Pos)
1046823 .press(1, touch1Pos);
1047824
1048− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
825+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1049826
1050827 passTime(5);
1051828 QTest::touchEvent(m_view, m_device)
@@ -1053,7 +830,7 @@ void tst_DirectionalDragArea::threeFingerDrag()
1053830 .move(1, touch1Pos)
1054831 .press(2, touch2Pos);
1055832
1056− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
833+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1057834
1058835 passTime(10);
1059836 QTest::touchEvent(m_view, m_device)
@@ -1077,7 +854,7 @@ void tst_DirectionalDragArea::threeFingerDrag()
1077854 .release(0, touch0Pos);
1078855
1079856 // Shouldn't be keepping info about touches that no longer exist or interest us
1080− QVERIFY(edgeDragArea->m_activeTouches.isEmpty());
857+ QVERIFY(edgeDragArea->d->activeTouches.isEmpty());
1081858}
1082859
1083860/*
@@ -1090,12 +867,12 @@ void tst_DirectionalDragArea::immediateRecognitionWhenConstraintsDisabled()
1090867 DirectionalDragArea *edgeDragArea =
1091868 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1092869 Q_ASSERT(edgeDragArea != 0);
1093− edgeDragArea->setRecognitionTimer(fakeTimer);
1094− edgeDragArea->setTimeSource(fakeTimeSource);
870+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
871+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1095872
1096873 // Disable the minimum amount of constraints to ensure immediate recognition
1097− edgeDragArea->setDistanceThreshold(0);
1098− edgeDragArea->setCompositionTime(0);
874+ edgeDragArea->d->setDistanceThreshold(0);
875+ edgeDragArea->d->compositionTime = 0;
1099876
1100877 // Put an item right behind edgeDragArea to receive the touches ignored by it
1101878 DummyItem *dummyItem = new DummyItem;
@@ -1111,7 +888,7 @@ void tst_DirectionalDragArea::immediateRecognitionWhenConstraintsDisabled()
1111888 QTest::touchEvent(m_view, m_device).press(0, touch0Pos);
1112889
1113890 // check for immediate recognition
1114− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
891+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
1115892
1116893 // and therefore it should have immediately grabbed the touch point,
1117894 // not letting it leak to items behind him.
@@ -1123,19 +900,19 @@ void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRec
1123900 DirectionalDragArea *edgeDragArea =
1124901 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1125902 Q_ASSERT(edgeDragArea != 0);
1126− edgeDragArea->setRecognitionTimer(fakeTimer);
1127− edgeDragArea->setTimeSource(fakeTimeSource);
903+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
904+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1128905
1129906 QPointF touchPoint = calculateInitialTouchPos(edgeDragArea);
1130907
1131908 // Move less than the minimum needed for the drag gesture recognition
1132− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*0.5f;
909+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 0.5f;
1133910 QPointF dragDirectionVector(1., 0.); // horizontal positive
1134911
1135− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
912+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
1136913 QPointF touchMovement = dragDirectionVector * movementStepDistance;
1137914 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
1138− int movementTimeStepMs = (edgeDragArea->compositionTime() * 0.8f) / totalMovementSteps;
915+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 0.8f) / totalMovementSteps;
1139916
1140917 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
1141918
@@ -1145,14 +922,14 @@ void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRec
1145922 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
1146923 }
1147924
1148− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Undecided);
925+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
1149926
1150927 // edgeDragArea should be an undecided candidate
1151928 {
1152929 auto touchInfo = m_touchRegistry->findTouchInfo(0);
1153930 QCOMPARE(touchInfo->candidates.size(), 1);
1154931 QCOMPARE(touchInfo->candidates.at(0).item.data(), edgeDragArea);
1155− QCOMPARE(touchInfo->candidates.at(0).undecided, true);
932+ QCOMPARE(touchInfo->candidates.at(0).state, TouchRegistry::CandidateInfo::Undecided);
1156933 }
1157934
1158935 // disable the dragArea while it's still recognizing a possible drag gesture.
@@ -1169,7 +946,7 @@ void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRec
1169946 QCOMPARE(touchInfo->candidates.size(), 0);
1170947 }
1171948
1172− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::WaitingForTouch);
949+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1173950
1174951 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
1175952}
@@ -1182,47 +959,13 @@ void tst_DirectionalDragArea::withdrawTouchOwnershipCandidacyIfDisabledDuringRec
1182959 QTest::newRow("invisible") << false;
1183960}
1184961
1185−void tst_DirectionalDragArea::tappedSignal()
1186−{
1187− DirectionalDragArea *edgeDragArea =
1188− m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1189− QVERIFY(edgeDragArea != 0);
1190− edgeDragArea->setRecognitionTimer(fakeTimer);
1191− edgeDragArea->setTimeSource(fakeTimeSource);
1192−
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);
1198− }
1199−
1200− QPoint touch0Pos(edgeDragArea->width()/2.0f, m_view->height()/2.0f);
1201−
1202− QSignalSpy tappedSpy(edgeDragArea, SIGNAL(tapped()));
1203−
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);
1207−
1208− QCOMPARE(tappedSpy.count(), 1);
1209−}
1210−
1211−void tst_DirectionalDragArea::tappedSignal_data()
1212−{
1213− QTest::addColumn<bool>("immediateGestureRecognition");
1214−
1215− QTest::newRow("immediate gesture recognition") << true;
1216− QTest::newRow("default gesture recognition") << false;
1217−}
1218−
1219962void tst_DirectionalDragArea::gettingTouchOwnershipMakesMouseAreaBehindGetCanceled()
1220963{
1221964 DirectionalDragArea *edgeDragArea =
1222965 m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1223966 QVERIFY(edgeDragArea != nullptr);
1224− edgeDragArea->setRecognitionTimer(fakeTimer);
1225− edgeDragArea->setTimeSource(fakeTimeSource);
967+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
968+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1226969
1227970 QQuickMouseArea *mouseArea =
1228971 m_view->rootObject()->findChild<QQuickMouseArea*>("mouseArea");
@@ -1233,12 +976,12 @@ void tst_DirectionalDragArea::gettingTouchOwnershipMakesMouseAreaBehindGetCancel
1233976 QPointF initialTouchPos = calculateInitialTouchPos(edgeDragArea);
1234977 QPointF touchPoint = initialTouchPos;
1235978
1236− qreal desiredDragDistance = edgeDragArea->distanceThreshold()*2;
979+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2;
1237980 QPointF dragDirectionVector(1.0f, 0.0f); // rightwards
1238− qreal movementStepDistance = edgeDragArea->distanceThreshold() * 0.1f;
981+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
1239982 QPointF touchMovement = dragDirectionVector * movementStepDistance;
1240983 int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
1241− int movementTimeStepMs = (edgeDragArea->compositionTime() * 1.5f) / totalMovementSteps;
984+ int movementTimeStepMs = (edgeDragArea->d->compositionTime * 1.5f) / totalMovementSteps;
1242985
1243986 QCOMPARE(mouseArea->pressed(), false);
1244987
@@ -1259,13 +1002,305 @@ void tst_DirectionalDragArea::gettingTouchOwnershipMakesMouseAreaBehindGetCancel
12591002 // As the DirectionalDragArea recognizes the gesture, it grabs the touch from the MouseArea,
12601003 // which should make the MouseArea get a cancelation event, which will then cause it to
12611004 // reset its state (going back to "unpressed"/"released").
1262− QCOMPARE((int)edgeDragArea->status(), (int)DirectionalDragArea::Recognized);
1005+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
12631006 QCOMPARE(mouseArea->pressed(), false);
12641007 QCOMPARE(mouseAreaSpy.canceledCount, 1);
12651008
12661009 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
12671010}
12681011
1012+void tst_DirectionalDragArea::interleavedTouches()
1013+{
1014+ DirectionalDragArea *edgeDragArea =
1015+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1016+ QVERIFY(edgeDragArea != 0);
1017+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
1018+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1019+
1020+ QPointF touch0 = edgeDragArea->mapToScene(
1021+ QPointF(edgeDragArea->width()*0.5, edgeDragArea->height()*0.3));
1022+
1023+ qreal desiredDragDistance = edgeDragArea->d->distanceThreshold * 2;
1024+ QPointF dragDirectionVector(1.0f, 0.0f); // rightwards
1025+ qreal movementStepDistance = edgeDragArea->d->distanceThreshold * 0.1f;
1026+ QPointF touchMovement = dragDirectionVector * movementStepDistance;
1027+ int totalMovementSteps = qCeil(desiredDragDistance / movementStepDistance);
1028+ int movementTimeStepMs = (edgeDragArea->d->maxTime * 0.4f) / totalMovementSteps;
1029+
1030+ QTest::touchEvent(m_view, m_device).press(0, touch0.toPoint());
1031+ for (int i = 0; i < totalMovementSteps; ++i) {
1032+ touch0 += touchMovement;
1033+ passTime(movementTimeStepMs);
1034+ QTest::touchEvent(m_view, m_device).move(0, touch0.toPoint());
1035+ }
1036+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
1037+
1038+ QPointF touch1 = edgeDragArea->mapToScene(
1039+ QPointF(edgeDragArea->width()*0.5, edgeDragArea->height()*0.6));
1040+
1041+ QTest::touchEvent(m_view, m_device)
1042+ .move(0, touch0.toPoint())
1043+ .press(1, touch1.toPoint());
1044+
1045+ touch1 += touchMovement;
1046+ passTime(movementTimeStepMs);
1047+ QTest::touchEvent(m_view, m_device)
1048+ .move(0, touch0.toPoint())
1049+ .move(1, touch1.toPoint());
1050+
1051+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Recognized);
1052+
1053+ QTest::touchEvent(m_view, m_device)
1054+ .release(0, touch0.toPoint())
1055+ .move(1, touch1.toPoint());
1056+
1057+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1058+
1059+ touch1 += touchMovement;
1060+ passTime(movementTimeStepMs);
1061+ QTest::touchEvent(m_view, m_device)
1062+ .move(1, touch1.toPoint());
1063+
1064+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1065+
1066+ QPointF touch2 = edgeDragArea->mapToScene(
1067+ QPointF(edgeDragArea->width()*0.5, edgeDragArea->height()*0.9));
1068+
1069+ passTime(edgeDragArea->d->compositionTime + movementTimeStepMs);
1070+ QTest::touchEvent(m_view, m_device)
1071+ .move(1, touch1.toPoint())
1072+ .press(2, touch2.toPoint());
1073+
1074+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::Undecided);
1075+ QCOMPARE(edgeDragArea->d->touchId, 2);
1076+
1077+ touch2 += touchMovement;
1078+ passTime(movementTimeStepMs);
1079+ QTest::touchEvent(m_view, m_device)
1080+ .move(1, touch1.toPoint())
1081+ .move(2, touch2.toPoint());
1082+
1083+ touch1 += touchMovement;
1084+ passTime(movementTimeStepMs);
1085+ QTest::touchEvent(m_view, m_device)
1086+ .move(1, touch1.toPoint())
1087+ .move(2, touch2.toPoint());
1088+
1089+ passTime(movementTimeStepMs);
1090+ QTest::touchEvent(m_view, m_device)
1091+ .release(1, touch1.toPoint())
1092+ .move(2, touch2.toPoint());
1093+
1094+ passTime(movementTimeStepMs);
1095+ QTest::touchEvent(m_view, m_device)
1096+ .release(2, touch2.toPoint());
1097+
1098+ QCOMPARE((int)edgeDragArea->d->status, (int)DirectionalDragAreaPrivate::WaitingForTouch);
1099+}
1100+
1101+/*
1102+ A valid right-edge drag performed on mako
1103+ */
1104+void tst_DirectionalDragArea::makoRightEdgeDrag()
1105+{
1106+ m_view->resize(768, 1280);
1107+ QTest::qWait(300);
1108+
1109+ DirectionalDragArea *edgeDragArea =
1110+ m_view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
1111+ QVERIFY(edgeDragArea != nullptr);
1112+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
1113+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1114+
1115+ StatusSpy *statusSpy = new StatusSpy(edgeDragArea);
1116+
1117+ edgeDragArea->d->setPixelsPerMm(320.0 /*mako ppi*/ * 0.03937 /* inches per mm*/);
1118+
1119+ sendTouchPress(319744, 0, QPointF(767.001, 719.719));
1120+ sendTouchUpdate(319765, 0, QPointF(765.744,729.973));
1121+ sendTouchUpdate(319784, 0, QPointF(740.879,752.182));
1122+ sendTouchUpdate(319803, 0, QPointF(689.698,795.795));
1123+ sendTouchUpdate(319826, 0, QPointF(616.978,856.212));
1124+ sendTouchUpdate(319845, 0, QPointF(558.769,906.157));
1125+ sendTouchUpdate(319859, 0, QPointF(513.219,945.266));
1126+ sendTouchUpdate(319878, 0, QPointF(481.31,975.496));
1127+ sendTouchUpdate(319902, 0, QPointF(460.016,997.439));
1128+ sendTouchUpdate(319920, 0, QPointF(449.761,1008.6));
1129+ sendTouchUpdate(319929, 0, QPointF(445.891,1012.42));
1130+ sendTouchUpdate(319947, 0, QPointF(444.884,1013.93));
1131+ sendTouchUpdate(319965, 0, QPointF(444.461,1014.35));
1132+ sendTouchUpdate(320057, 0, QPointF(444.71,1013.56));
1133+ sendTouchUpdate(320138, 0, QPointF(445.434,1013.6));
1134+ sendTouchUpdate(320154, 0, QPointF(446.338,1012.98));
1135+ sendTouchUpdate(320171, 0, QPointF(447.232,1012.08));
1136+ sendTouchRelease(320171, 0, QPointF(447.232,1012.08));
1137+
1138+ QCOMPARE(statusSpy->recognized(), true);
1139+
1140+ delete statusSpy;
1141+}
1142+
1143+/*
1144+ A vertical, downwards swipe performed on mako near its right edge.
1145+
1146+ The DirectionalDragArea on the right edge must not recognize this
1147+ gesture.
1148+ */
1149+void tst_DirectionalDragArea::makoRightEdgeDrag_verticalDownwards()
1150+{
1151+ m_view->resize(768, 1280);
1152+ QTest::qWait(300);
1153+
1154+ DirectionalDragArea *edgeDragArea =
1155+ m_view->rootObject()->findChild<DirectionalDragArea*>("hnDragArea");
1156+ QVERIFY(edgeDragArea != nullptr);
1157+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
1158+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1159+
1160+ edgeDragArea->d->setPixelsPerMm(320.0 /*mako ppi*/ * 0.03937 /* inches per mm*/);
1161+
1162+ StatusSpy *statusSpy = new StatusSpy(edgeDragArea);
1163+
1164+ sendTouchPress(12012445, 26, QPointF(767.001,461.82));
1165+ sendTouchUpdate(12012472, 26, QPointF(767.001,462.569));
1166+ sendTouchUpdate(12012528, 26, QPointF(767.001,463.334));
1167+ sendTouchUpdate(12012546, 26, QPointF(767.001,466.856));
1168+ sendTouchUpdate(12012571, 26, QPointF(767.001,473.291));
1169+ sendTouchUpdate(12012587, 26, QPointF(767.001,487.31));
1170+ sendTouchUpdate(12012604, 26, QPointF(765.364,507.521));
1171+ sendTouchUpdate(12012618, 26, QPointF(765.364,507.521));
1172+ sendTouchUpdate(12012627, 26, QPointF(762.642,534.317));
1173+ sendTouchUpdate(12012655, 26, QPointF(760.846,573.406));
1174+ sendTouchUpdate(12012667, 26, QPointF(759.838,625.295));
1175+ sendTouchUpdate(12012675, 26, QPointF(758.875,703.207));
1176+ sendTouchUpdate(12012696, 26, QPointF(761.52,777.015));
1177+ sendTouchUpdate(12012713, 26, QPointF(765.659,835.591));
1178+ sendTouchUpdate(12012731, 26, QPointF(766.778,883.206));
1179+ sendTouchUpdate(12012748, 26, QPointF(767.001,922.937));
1180+ sendTouchUpdate(12012779, 26, QPointF(767.001,967.558));
1181+ sendTouchUpdate(12012798, 26, QPointF(767.001,1006.12));
1182+ sendTouchUpdate(12012809, 26, QPointF(767.001,1033.1));
1183+ sendTouchRelease(12012810, 26, QPointF(767.001,1033.1));
1184+
1185+ QCOMPARE(statusSpy->recognized(), false);
1186+
1187+ delete statusSpy;
1188+}
1189+
1190+/*
1191+ A valid left-edge drag performed on mako. This one starts a bit slow than speeds up
1192+ */
1193+void tst_DirectionalDragArea::makoLeftEdgeDrag_slowStart()
1194+{
1195+ m_view->resize(768, 1280);
1196+ QTest::qWait(300);
1197+
1198+ DirectionalDragArea *edgeDragArea =
1199+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1200+ QVERIFY(edgeDragArea != nullptr);
1201+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
1202+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1203+
1204+ edgeDragArea->d->setPixelsPerMm(320.0 /*mako ppi*/ * 0.03937 /* inches per mm*/);
1205+
1206+ StatusSpy *statusSpy = new StatusSpy(edgeDragArea);
1207+
1208+ sendTouchPress(4002267, 77, QPointF(0,885.154));
1209+ sendTouchUpdate(4002275, 77, QPointF(0,886.214));
1210+ sendTouchUpdate(4002311, 77, QPointF(1.09568,887.75));
1211+ sendTouchUpdate(4002329, 77, QPointF(3.53647,890.191));
1212+ sendTouchUpdate(4002347, 77, QPointF(7.87434,892.879));
1213+ sendTouchUpdate(4002366, 77, QPointF(12.3036,895.075));
1214+ sendTouchUpdate(4002384, 77, QPointF(15.8885,896.849));
1215+ sendTouchUpdate(4002406, 77, QPointF(18.4504,897.88));
1216+ sendTouchUpdate(4002420, 77, QPointF(20.2429,898.149));
1217+ sendTouchUpdate(4002439, 77, QPointF(20.9945,898.149));
1218+ sendTouchUpdate(4002457, 77, QPointF(21.8819,898.149));
1219+ sendTouchUpdate(4002480, 77, QPointF(22.7454,897.389));
1220+ sendTouchUpdate(4002493, 77, QPointF(23.5456,896.589));
1221+ sendTouchUpdate(4002511, 77, QPointF(24.5435,895.031));
1222+ sendTouchUpdate(4002529, 77, QPointF(25.4271,892.32));
1223+ sendTouchUpdate(4002548, 77, QPointF(26.3145,889.658));
1224+ sendTouchUpdate(4002566, 77, QPointF(27.2004,886.999));
1225+ sendTouchUpdate(4002584, 77, QPointF(28.035,885.048));
1226+ sendTouchUpdate(4002603, 77, QPointF(29.9684,883.167));
1227+ sendTouchUpdate(4002620, 77, QPointF(33.3591,881.403));
1228+ sendTouchUpdate(4002639, 77, QPointF(44.1017,879.642));
1229+ sendTouchUpdate(4002657, 77, QPointF(64.828,878.502));
1230+ sendTouchUpdate(4002675, 77, QPointF(87.9486,878.157));
1231+ sendTouchUpdate(4002693, 77, QPointF(112.96,877.742));
1232+ sendTouchUpdate(4002711, 77, QPointF(138.903,877.157));
1233+ sendTouchUpdate(4002729, 77, QPointF(163.204,877.157));
1234+ sendTouchUpdate(4002747, 77, QPointF(182.127,877.157));
1235+ sendTouchUpdate(4002765, 77, QPointF(194.478,877.657));
1236+ sendTouchUpdate(4002785, 77, QPointF(201.474,878.508));
1237+ sendTouchUpdate(4002803, 77, QPointF(204.855,879.401));
1238+ sendTouchUpdate(4002822, 77, QPointF(206.616,880.281));
1239+ sendTouchUpdate(4002839, 77, QPointF(207.115,880.906));
1240+ sendTouchUpdate(4002894, 77, QPointF(206.865,881.184));
1241+ sendTouchUpdate(4002912, 77, QPointF(206.865,882.143));
1242+ sendTouchUpdate(4002930, 77, QPointF(206.865,883.106));
1243+ sendTouchUpdate(4002949, 77, QPointF(206.526,883.994));
1244+ sendTouchUpdate(4002967, 77, QPointF(205.866,884.88));
1245+ sendTouchUpdate(4002985, 77, QPointF(205.866,885.766));
1246+ sendTouchUpdate(4003005, 77, QPointF(205.866,886.654));
1247+ sendTouchUpdate(4003021, 77, QPointF(205.366,887.537));
1248+ sendTouchUpdate(4003039, 77, QPointF(204.592,888.428));
1249+ sendTouchUpdate(4003050, 77, QPointF(204.367,888.653));
1250+ sendTouchRelease(4003050, 77, QPointF(204.367,888.653));
1251+
1252+ QCOMPARE(statusSpy->recognized(), true);
1253+
1254+ delete statusSpy;
1255+}
1256+
1257+void tst_DirectionalDragArea::makoLeftEdgeDrag_movesSlightlyBackwardsOnStart()
1258+{
1259+ m_view->resize(768, 1280);
1260+ QTest::qWait(300);
1261+
1262+ DirectionalDragArea *edgeDragArea =
1263+ m_view->rootObject()->findChild<DirectionalDragArea*>("hpDragArea");
1264+ QVERIFY(edgeDragArea != nullptr);
1265+ edgeDragArea->d->setRecognitionTimer(m_fakeTimerFactory->createTimer());
1266+ edgeDragArea->d->setTimeSource(m_fakeTimerFactory->timeSource());
1267+
1268+ edgeDragArea->d->setPixelsPerMm(320.0 /*mako ppi*/ * 0.03937 /* inches per mm*/);
1269+
1270+ StatusSpy *statusSpy = new StatusSpy(edgeDragArea);
1271+
1272+ sendTouchPress(41097, 24, QPointF(13.9909,827.177));
1273+ sendTouchUpdate(41120, 24, QPointF(19.2375,825.677));
1274+ sendTouchUpdate(41138, 24, QPointF(18.4057,826.177));
1275+ sendTouchUpdate(41161, 24, QPointF(20.1067,825.867));
1276+ sendTouchUpdate(41177, 24, QPointF(21.8869,824.977));
1277+ sendTouchUpdate(41193, 24, QPointF(24.7603,823.494));
1278+ sendTouchUpdate(41211, 24, QPointF(28.3889,821.725));
1279+ sendTouchUpdate(41229, 24, QPointF(32.2909,819.955));
1280+ sendTouchUpdate(41247, 24, QPointF(38.2251,817.431));
1281+ sendTouchUpdate(41266, 24, QPointF(52.4182,814.223));
1282+ sendTouchUpdate(41284, 24, QPointF(85.8465,809.483));
1283+ sendTouchUpdate(41302, 24, QPointF(126.091,802.741));
1284+ sendTouchUpdate(41320, 24, QPointF(153.171,797.977));
1285+ sendTouchUpdate(41338, 24, QPointF(170.565,795.077));
1286+ sendTouchUpdate(41356, 24, QPointF(178.685,794.101));
1287+ sendTouchUpdate(41375, 24, QPointF(183.706,793.225));
1288+ sendTouchUpdate(41393, 24, QPointF(186.112,793.19));
1289+ sendTouchUpdate(41411, 24, QPointF(187.634,793.19));
1290+ sendTouchUpdate(41429, 24, QPointF(188.505,793.19));
1291+ sendTouchUpdate(41532, 24, QPointF(187.816,793.19));
1292+ sendTouchUpdate(41538, 24, QPointF(186.902,793.19));
1293+ sendTouchUpdate(41557, 24, QPointF(186.01,793.19));
1294+ sendTouchUpdate(41575, 24, QPointF(185.125,793.444));
1295+ sendTouchUpdate(41593, 24, QPointF(184.229,793.69));
1296+ sendTouchUpdate(41605, 24, QPointF(183.88,793.69));
1297+ sendTouchRelease(41607, 24, QPointF(183.88,793.69));
1298+
1299+ QCOMPARE(statusSpy->recognized(), true);
1300+
1301+ delete statusSpy;
1302+}
1303+
12691304QTEST_MAIN(tst_DirectionalDragArea)
12701305
12711306#include "tst_DirectionalDragArea.moc"
tests/plugins/Ubuntu/Gestures/tst_DirectionalDragArea.qml+8−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2013 Canonical, Ltd.
2+ * Copyright (C) 2013,2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -16,12 +16,19 @@
1616
1717import QtQuick 2.0
1818import Ubuntu.Components 0.1
19+import Unity.Test 0.1
1920
2021Rectangle {
2122 width: units.gu(60)
2223 height: units.gu(60)
2324 color: "white"
2425
26+ Binding {
27+ target: MouseTouchAdaptor
28+ property: "enabled"
29+ value: true
30+ }
31+
2532 MouseArea {
2633 id: mouseArea
2734 objectName: "mouseArea"
tests/plugins/Ubuntu/Gestures/tst_TouchDispatcher.cpp+5−6
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -81,8 +81,7 @@ void tst_TouchDispatcher::sendMouseEventIfTouchIgnored()
8181 event->accept();
8282 };
8383
84− touchDispatcher.dispatch(QEvent::TouchBegin, m_device, Qt::NoModifier, touchPoints, m_view,
85− 12345 /* timestamp */);
84+ touchDispatcher.dispatch(m_device, Qt::NoModifier, touchPoints, m_view, 12345 /* timestamp */);
8685
8786 QVERIFY(gotTouchEvent);
8887 QCOMPARE(gotMousePressEvent, shouldGetMousePress);
@@ -128,18 +127,18 @@ void tst_TouchDispatcher::mouseDoubleClick()
128127 }
129128 ulong timestamp = 12345;
130129
131− touchDispatcher.dispatch(QEvent::TouchBegin, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
130+ touchDispatcher.dispatch(m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
132131
133132 touchPoints[0].setState(Qt::TouchPointReleased);
134133 timestamp += doubleClickInterval / 10;
135134
136− touchDispatcher.dispatch(QEvent::TouchEnd, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
135+ touchDispatcher.dispatch(m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
137136
138137 touchPoints[0].setId(1);
139138 touchPoints[0].setState(Qt::TouchPointPressed);
140139 timestamp += timeBetweenClicks;
141140
142− touchDispatcher.dispatch(QEvent::TouchBegin, m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
141+ touchDispatcher.dispatch(m_device, Qt::NoModifier, touchPoints, m_view, timestamp);
143142
144143 QCOMPARE(gotDoubleClickEvent, shouldSendDoubleClick);
145144}
tests/plugins/Ubuntu/Gestures/tst_TouchGate.cpp+56−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2015 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -63,6 +63,7 @@ private Q_SLOTS:
6363 void init(); // called right before each and every test function is executed
6464 void cleanup(); // called right after each and every test function is executed
6565
66+private:
6667 void onlyCandidate_passThrough();
6768 void holdsEventsUntilGainsOwnership();
6869 void holdsEventsUntilGainsOwnership_data();
@@ -70,6 +71,9 @@ private Q_SLOTS:
7071 void dontDispacthToDisabledOrInvisibleTarget();
7172 void dontDispacthToDisabledOrInvisibleTarget_data();
7273
74+private Q_SLOTS:
75+ void disabledWhileHoldingTouch();
76+
7377private:
7478 QQuickView *createView();
7579 TouchRegistry *touchRegistry;
@@ -326,6 +330,57 @@ void tst_TouchGate::dontDispacthToDisabledOrInvisibleTarget_data()
326330 QTest::newRow("disabled invisible") << false << false;
327331}
328332
333+void tst_TouchGate::disabledWhileHoldingTouch()
334+{
335+ TouchGate *touchGate = view->rootObject()->findChild<TouchGate*>("touchGate");
336+ Q_ASSERT(touchGate);
337+
338+ TestItem *testItem = new TestItem;
339+ testItem->setWidth(touchGate->width());
340+ testItem->setHeight(touchGate->height());
341+ testItem->setParentItem(view->rootObject());
342+ testItem->setZ(0.0);
343+
344+ touchGate->setZ(1.0);
345+ touchGate->setTargetItem(testItem);
346+
347+ QTest::touchEvent(view, device)
348+ .press(0, QPoint(100,100));
349+
350+ QCOMPARE(testItem->touchEventsReceived.count(), 1);
351+ testItem->touchEventsReceived.clear();
352+
353+ touchGate->setEnabled(false);
354+
355+ QTest::touchEvent(view, device)
356+ .move(0, QPoint(101,101));
357+
358+ // Nothing new came as TouchGate didn't even get it because it's disabled
359+ QCOMPARE(testItem->touchEventsReceived.count(), 0);
360+
361+ touchGate->setEnabled(true);
362+
363+ QTest::touchEvent(view, device)
364+ .move(0, QPoint(102,102));
365+
366+ QTest::touchEvent(view, device)
367+ .release(0, QPoint(102,102));
368+
369+ // Nothing new came because TouchGate has already discarded touch 0
370+ QCOMPARE(testItem->touchEventsReceived.count(), 0);
371+
372+ QTest::touchEvent(view, device)
373+ .press(1, QPoint(200,200));
374+
375+ QCOMPARE(testItem->touchEventsReceived.count(), 1);
376+ {
377+ // it got only the new touch point.
378+ QSharedPointer<QTouchEvent> touchEvent = testItem->touchEventsReceived[0];
379+ QCOMPARE(touchEvent->touchPoints().count(), 1);
380+ QCOMPARE(touchEvent->touchPoints()[0].id(), 1);
381+ }
382+}
383+
329384///////////// TestItem /////////////////////////////////////////////////////////////
330385
331386void TestItem::touchEvent(QTouchEvent *event)
tests/qmltests/Components/CMakeLists.txt+3−1
@@ -5,6 +5,7 @@ include_directories(
55 ${CMAKE_SOURCE_DIR}/libs/UbuntuGestures
66 ${CMAKE_CURRENT_BINARY_DIR}
77 ${Qt5Gui_PRIVATE_INCLUDE_DIRS}
8+ ${Qt5Quick_PRIVATE_INCLUDE_DIRS}
89 )
910
1011add_executable(DragHandleTestExec tst_DragHandle.cpp)
@@ -12,7 +13,8 @@ qt5_use_modules(DragHandleTestExec Test Core Qml Gui Quick)
1213target_link_libraries(DragHandleTestExec UbuntuGesturesQml UbuntuGestures)
1314
1415add_binary_qml_test(DragHandle ${CMAKE_BINARY_DIR}/plugins/Ubuntu/Gestures "" "")
15−add_manual_qml_test(. DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins)
16+add_manual_qml_test(. DragHandle IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${CMAKE_BINARY_DIR}/tests/utils/modules)
1617
1718add_definitions(-DUBUNTU_GESTURES_PLUGIN_DIR="${CMAKE_BINARY_DIR}/plugins")
1819add_definitions(-DTEST_DIR="${CMAKE_CURRENT_LIST_DIR}")
20+add_definitions(-DTESTS_UTILS_MODULES_DIR="${CMAKE_BINARY_DIR}/tests/utils/modules")
tests/qmltests/Components/tst_DragHandle.cpp+15−13
@@ -20,21 +20,16 @@
2020#include <qpa/qwindowsysteminterface.h>
2121#include <QtQuick/QQuickView>
2222#include <QtQml/QQmlEngine>
23+#include <private/qquickwindow_p.h>
2324
25+#include <AxisVelocityCalculator.h>
2426#include <DirectionalDragArea.h>
27+#include <DirectionalDragArea_p.h>
2528#include <Timer.h>
2629#include <TouchRegistry.h>
2730
2831using namespace UbuntuGestures;
2932
30−class FakeTimeSource : public TimeSource
31−{
32−public:
33− FakeTimeSource() { m_msecsSinceReference = 0; }
34− qint64 msecsSinceReference() override {return m_msecsSinceReference;}
35− qint64 m_msecsSinceReference;
36−};
37−
3833class tst_DragHandle: public QObject
3934{
4035 Q_OBJECT
@@ -56,7 +51,6 @@ private Q_SLOTS:
5651 void hintingAnimation();
5752 void hintingAnimation_dontRestartAfterFinishedAndStillPressed();
5853
59−
6054private:
6155 void flickAndHold(DirectionalDragArea *dragHandle, qreal distance);
6256 void drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
@@ -105,8 +99,8 @@ void tst_DragHandle::init()
10599 QQuickItem *controls = m_view->rootObject()->findChild<QQuickItem*>("controls");
106100 controls->setVisible(false);
107101
108− m_fakeTimer = new FakeTimer;
109102 m_fakeTimeSource.reset(new FakeTimeSource);
103+ m_fakeTimer = new FakeTimer(m_fakeTimeSource);
110104}
111105
112106void tst_DragHandle::cleanup()
@@ -129,6 +123,7 @@ QQuickView *tst_DragHandle::createView()
129123 window->setResizeMode(QQuickView::SizeRootObjectToView);
130124 window->engine()->addImportPath(QLatin1String(UBUNTU_GESTURES_PLUGIN_DIR));
131125 window->engine()->addImportPath(QLatin1String(TEST_DIR));
126+ window->engine()->addImportPath(QStringLiteral(TESTS_UTILS_MODULES_DIR));
132127
133128 return window;
134129}
@@ -190,17 +185,24 @@ void tst_DragHandle::flickAndHold(DirectionalDragArea *dragHandle,
190185 // speed.
191186 m_fakeTimeSource->m_msecsSinceReference += 5000;
192187 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
188+
189+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
190+ windowPrivate->flushDelayedTouchEvent();
193191}
194192
195193void tst_DragHandle::drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
196194 int numSteps, qint64 timeMs)
197195{
196+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
197+
198198 qint64 timeStep = timeMs / numSteps;
199199 QPointF touchMovement = direction * (distance / (qreal)numSteps);
200200 for (int i = 0; i < numSteps; ++i) {
201201 touchPoint += touchMovement;
202202 m_fakeTimeSource->m_msecsSinceReference += timeStep;
203203 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
204+
205+ windowPrivate->flushDelayedTouchEvent();
204206 }
205207}
206208
@@ -209,8 +211,8 @@ DirectionalDragArea *tst_DragHandle::fetchAndSetupDragHandle(const char *objectN
209211 DirectionalDragArea *dragHandle =
210212 m_view->rootObject()->findChild<DirectionalDragArea*>(objectName);
211213 Q_ASSERT(dragHandle != 0);
212− dragHandle->setRecognitionTimer(m_fakeTimer);
213− dragHandle->setTimeSource(m_fakeTimeSource);
214+ dragHandle->d->setRecognitionTimer(m_fakeTimer);
215+ dragHandle->d->setTimeSource(m_fakeTimeSource);
214216
215217 AxisVelocityCalculator *edgeDragEvaluator =
216218 dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
@@ -242,6 +244,7 @@ void tst_DragHandle::dragThreshold_horizontal()
242244 baseItem->setRotation(rotation);
243245
244246 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
247+ QQuickItem *parentItem = dragHandle->parentItem();
245248
246249 qreal dragThreshold = fetchDragThreshold(dragHandle);
247250
@@ -249,7 +252,6 @@ void tst_DragHandle::dragThreshold_horizontal()
249252 flickAndHold(dragHandle, dragThreshold * 0.7);
250253
251254 // should rollback
252− QQuickItem *parentItem = dragHandle->parentItem();
253255 tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
254256 QCOMPARE(parentItem->property("shown").toBool(), false);
255257
tests/qmltests/Components/tst_DragHandle.qml+7
@@ -19,6 +19,7 @@ import "../../../qml/Components"
1919import "tst_DragHandle"
2020import Ubuntu.Components 0.1
2121import Ubuntu.Gestures 0.1
22+import Unity.Test 0.1
2223
2324/*
2425 Two blue fullscreen Showables (a vertical and a horizontal one) with red handles you
@@ -33,6 +34,12 @@ Rectangle {
3334 width: units.gu(70)
3435 height: units.gu(70)
3536
37+ Binding {
38+ target: MouseTouchAdaptor
39+ property: "enabled"
40+ value: true
41+ }
42+
3643 property var dragHandle
3744
3845 property bool stretch: false
tests/qmltests/Components/tst_DragHandle/BidirectionalShowable.qml+2−2
@@ -60,8 +60,8 @@ Item {
6060 maxTotalDragDistance: root.parent.width
6161 autoCompleteDragThreshold: parent.width / 3
6262
63− onStatusChanged: {
64− if (status === DirectionalDragArea.Recognized) {
63+ onDraggingChanged: {
64+ if (dragging) {
6565 dragHandleRecognizedGesture(dragHandle);
6666 }
6767 }
tests/qmltests/Components/tst_DragHandle/HorizontalShowable.qml+4−4
@@ -65,8 +65,8 @@ Showable {
6565 maxTotalDragDistance: showable.parent.width
6666 hintDisplacement: showable.hintDisplacement
6767
68− onStatusChanged: {
69− if (status === DirectionalDragArea.Recognized) {
68+ onDraggingChanged: {
69+ if (dragging) {
7070 dragHandleRecognizedGesture(leftwardsDragHandle);
7171 }
7272 }
@@ -88,8 +88,8 @@ Showable {
8888 maxTotalDragDistance: showable.parent.width
8989 hintDisplacement: showable.hintDisplacement
9090
91− onStatusChanged: {
92− if (status === DirectionalDragArea.Recognized) {
91+ onDraggingChanged: {
92+ if (dragging) {
9393 dragHandleRecognizedGesture(rightwardsDragHandle);
9494 }
9595 }
tests/qmltests/Components/tst_DragHandle/VerticalShowable.qml+4−4
@@ -65,8 +65,8 @@ Showable {
6565 maxTotalDragDistance: showable.parent.height
6666 hintDisplacement: showable.hintDisplacement
6767
68− onStatusChanged: {
69− if (status === DirectionalDragArea.Recognized) {
68+ onDraggingChanged: {
69+ if (dragging) {
7070 dragHandleRecognizedGesture(upwardsDragHandle);
7171 }
7272 }
@@ -88,8 +88,8 @@ Showable {
8888 maxTotalDragDistance: showable.parent.height
8989 hintDisplacement: showable.hintDisplacement
9090
91− onStatusChanged: {
92− if (status === DirectionalDragArea.Recognized) {
91+ onDraggingChanged: {
92+ if (dragging) {
9393 dragHandleRecognizedGesture(downwardsDragHandle);
9494 }
9595 }
tests/qmltests/Stages/tst_PhoneStage.qml+8−8
@@ -164,14 +164,14 @@ Item {
164164
165165 function test_enterSpread_data() {
166166 return [
167− {tag: "<position1 (linear movement)", positionMarker: "positionMarker1", linear: true, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
168− {tag: "<position1 (non-linear movement)", positionMarker: "positionMarker1", linear: false, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 0 },
169− {tag: ">position1", positionMarker: "positionMarker1", linear: true, offset: +1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
170− {tag: "<position2 (linear)", positionMarker: "positionMarker2", linear: true, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
171− {tag: "<position2 (non-linear)", positionMarker: "positionMarker2", linear: false, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
172− {tag: ">position2", positionMarker: "positionMarker2", linear: true, offset: +1, endPhase: 1, targetPhase: 0, newFocusedIndex: 1 },
173− {tag: "<position3", positionMarker: "positionMarker3", linear: true, offset: -1, endPhase: 1, targetPhase: 0, newFocusedIndex: 1 },
174− {tag: ">position3", positionMarker: "positionMarker3", linear: true, offset: +1, endPhase: 1, targetPhase: 2, newFocusedIndex: 2 },
167+ {tag: "<position1 (linear movement)", positionMarker: "positionMarker1", linear: true, offset: 0, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
168+ {tag: "<position1 (non-linear movement)", positionMarker: "positionMarker1", linear: false, offset: 0, endPhase: 0, targetPhase: 0, newFocusedIndex: 0 },
169+ {tag: ">position1", positionMarker: "positionMarker1", linear: true, offset: +5, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
170+ {tag: "<position2 (linear)", positionMarker: "positionMarker2", linear: true, offset: 0, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
171+ {tag: "<position2 (non-linear)", positionMarker: "positionMarker2", linear: false, offset: 0, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
172+ {tag: ">position2", positionMarker: "positionMarker2", linear: true, offset: +5, endPhase: 1, targetPhase: 0, newFocusedIndex: 1 },
173+ {tag: "<position3", positionMarker: "positionMarker3", linear: true, offset: 0, endPhase: 1, targetPhase: 0, newFocusedIndex: 1 },
174+ {tag: ">position3", positionMarker: "positionMarker3", linear: true, offset: +5, endPhase: 1, targetPhase: 2, newFocusedIndex: 2 },
175175 ];
176176 }
177177
tests/qmltests/Tutorial/tst_Tutorial.qml+5−1
@@ -226,7 +226,11 @@ Item {
226226 checkLeftEdge();
227227 checkBottomEdge();
228228 if (name === "tutorialRight") return page;
229− touchFlick(shell, shell.width, halfHeight, halfWidth, halfHeight);
229+ touchFlick(shell,
230+ shell.width, halfHeight,
231+ halfWidth, halfHeight,
232+ true /* beginTouch */, true /* endTouch */,
233+ 20 /* speed */, 50 /* iterations */);
230234 var overlay = findChild(page, "overlay");
231235 tryCompare(overlay, "shown", true);
232236 var tick = findChild(page, "tick");
tests/utils/modules/Unity/Test/UnityTestCase.qml+2−5
@@ -403,11 +403,8 @@ TestCase {
403403 function removeTimeConstraintsFromDirectionalDragAreas(item) {
404404
405405 // use duck-typing to identify a DirectionalDragArea
406− if (item.minSpeed != undefined
407− && item.maxSilenceTime != undefined
408− && item.compositionTime != undefined) {
409− item.minSpeed = 0;
410− item.maxSilenceTime = 60 * 60 * 1000;
406+ if (item.maxTime != undefined && item.compositionTime != undefined) {
407+ item.maxTime = 60 * 60 * 1000;
411408 item.compositionTime = 0;
412409 } else {
413410 for (var i in item.children) {