@@ -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,168 @@ 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)
64+ , d(new DirectionalDragAreaPrivate(this))
9665{
97− if (direction != m_direction) {
98− m_direction = direction;
99− Q_EMIT directionChanged(m_direction);
100− }
101−}
66+ d->setRecognitionTimer(new Timer(this));
67+ d->recognitionTimer->setInterval(d->maxTime);
68+ d->recognitionTimer->setSingleShot(true);
10269
103−void DirectionalDragArea::setMaxDeviation(qreal value)
104−{
105− if (m_dampedScenePos.maxDelta() != value) {
106− m_dampedScenePos.setMaxDelta(value);
107− Q_EMIT maxDeviationChanged(value);
108− }
70+ connect(this, &QQuickItem::enabledChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
71+ connect(this, &QQuickItem::visibleChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
10972}
11073
111−qreal DirectionalDragArea::wideningAngle() const
112−{
113− return m_wideningAngle;
114−}
115−
116−void DirectionalDragArea::setWideningAngle(qreal angle)
117−{
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);
131−}
132−
133−void DirectionalDragArea::setDistanceThreshold(qreal value)
74+Direction::Type DirectionalDragArea::direction() const
13475{
135− if (m_distanceThreshold != value) {
136− m_distanceThreshold = value;
137− m_distanceThresholdSquared = m_distanceThreshold * m_distanceThreshold;
138− Q_EMIT distanceThresholdChanged(value);
139− }
76+ return d->direction;
14077}
14178
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::pressed() const
180+{
181+ return d->status != DirectionalDragAreaPrivate::WaitingForTouch;
182+}
183+
184+bool DirectionalDragArea::immediateRecognition() const
185+{
186+ return d->immediateRecognition;
187+}
188+
189+void DirectionalDragArea::setImmediateRecognition(bool enabled)
190+{
191+ if (d->immediateRecognition != enabled) {
192+ d->immediateRecognition = enabled;
193+ Q_EMIT immediateRecognitionChanged(enabled);
194+ }
195+}
196+
197+void DirectionalDragArea::removeTimeConstraints()
198+{
199+ d->setMaxTime(60 * 60 * 1000);
200+ d->compositionTime = 0;
201+ ddaDebug("removed time constraints");
236202}
237203
238204bool DirectionalDragArea::event(QEvent *event)
239205{
240206 if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
241− touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
207+ d->touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
242208 return true;
243209 } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
244− unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
210+ d->unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
245211 return true;
246212 } else {
247213 return QQuickItem::event(event);
248214 }
249215}
250216
251−void DirectionalDragArea::touchOwnershipEvent(TouchOwnershipEvent *event)
217+void DirectionalDragAreaPrivate::touchOwnershipEvent(TouchOwnershipEvent *event)
252218{
253219 if (event->gained()) {
254220 QVector<int> ids;
255221 ids.append(event->touchId());
256222 ddaDebug("grabbing touch");
257− grabTouchPoints(ids);
223+ q->grabTouchPoints(ids);
258224
259225 // Work around for Qt bug. If we grab a touch that is being used for mouse pointer
260226 // emulation it will cause the emulation logic to go nuts.
@@ -262,35 +228,35 @@ void DirectionalDragArea::touchOwnershipEvent(TouchOwnershipEvent *event)
262228 //
263229 // The fix for this bug has landed in Qt 5.4 (https://codereview.qt-project.org/96887)
264230 // 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()) {
231+ if (q->window()) {
232+ QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(q->window());
233+ if (windowPrivate->touchMouseId == event->touchId() && q->window()->mouseGrabberItem()) {
268234 ddaDebug("removing mouse grabber");
269− window()->mouseGrabberItem()->ungrabMouse();
235+ q->window()->mouseGrabberItem()->ungrabMouse();
270236 }
271237 }
272238 } else {
273239 // We still wanna know when it ends for keeping the composition time window up-to-date
274− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
240+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
275241
276242 setStatus(WaitingForTouch);
277243 }
278244}
279245
280−void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
246+void DirectionalDragAreaPrivate::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
281247{
282248 QTouchEvent *event = unownedTouchEvent->touchEvent();
283249
284250 Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
285251
286− ddaDebug("Unowned " << m_timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
252+ ddaDebug("Unowned " << timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
287253
288− switch (m_status) {
254+ switch (status) {
289255 case WaitingForTouch:
290256 // do nothing
291257 break;
292258 case Undecided:
293− Q_ASSERT(isEnabled() && isVisible());
259+ Q_ASSERT(q->isEnabled() && q->isVisible());
294260 unownedTouchEvent_undecided(unownedTouchEvent);
295261 break;
296262 default: // Recognized:
@@ -298,18 +264,18 @@ void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent
298264 break;
299265 }
300266
301− m_activeTouches.update(event);
267+ activeTouches.update(event);
302268}
303269
304−void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
270+void DirectionalDragAreaPrivate::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
305271{
306272 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
307273 if (!touchPoint) {
308− qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId
274+ qCritical() << "DirectionalDragArea[status=Undecided]: touch " << touchId
309275 << "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
310276 "Considering it as released.";
311277
312− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
278+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
313279 setStatus(WaitingForTouch);
314280 return;
315281 }
@@ -319,48 +285,42 @@ void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unowned
319285 if (touchPoint->state() == Qt::TouchPointReleased) {
320286 // touch has ended before recognition concluded
321287 ddaDebug("Touch has ended before recognition concluded");
322− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
323− emitSignalIfTapped();
288+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
324289 setStatus(WaitingForTouch);
325290 return;
326291 }
327292
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− }
293+ previousDampedScenePos.setX(dampedScenePos.x());
294+ previousDampedScenePos.setY(dampedScenePos.y());
295+ dampedScenePos.update(touchScenePos);
341296
342297 if (!movingInRightDirection()) {
343298 ddaDebug("Rejecting gesture because touch point is moving in the wrong direction.");
344− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
299+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
345300 // We still wanna know when it ends for keeping the composition time window up-to-date
346− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
301+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
347302 setStatus(WaitingForTouch);
348303 return;
349304 }
350305
351− setPreviousPos(touchPoint->pos());
352− setPreviousScenePos(touchScenePos);
353−
354306 if (isWithinTouchCompositionWindow()) {
355307 // 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.
308+ // with our touchId one and therefore deny the possibility of a single-finger gesture.
357309 ddaDebug("Sill within composition window. Let's wait more.");
358310 return;
359311 }
360312
361− if (movedFarEnough(touchScenePos)) {
362− TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
313+ if (movedFarEnoughAlongGestureAxis()) {
314+ TouchRegistry::instance()->requestTouchOwnership(touchId, q);
363315 setStatus(Recognized);
316+ setPublicPos(touchPoint->pos());
317+ setPublicScenePos(touchScenePos);
318+ } else if (isPastMaxDistance()) {
319+ ddaDebug("Rejecting gesture because it went farther than maxDistance without getting recognized.");
320+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
321+ // We still wanna know when it ends for keeping the composition time window up-to-date
322+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
323+ setStatus(WaitingForTouch);
364324 } else {
365325 ddaDebug("Didn't move far enough yet. Let's wait more.");
366326 }
@@ -371,29 +331,29 @@ void DirectionalDragArea::touchEvent(QTouchEvent *event)
371331 // TODO: Consider when more than one touch starts in the same event (although it's not possible
372332 // with Mir's android-input). Have to track them all. Consider it a plus/bonus.
373333
374− ddaDebug(m_timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
334+ ddaDebug(d->timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
375335
376336 if (!isEnabled() || !isVisible()) {
377337 QQuickItem::touchEvent(event);
378338 return;
379339 }
380340
381− switch (m_status) {
382− case WaitingForTouch:
383− touchEvent_absent(event);
341+ switch (d->status) {
342+ case DirectionalDragAreaPrivate::WaitingForTouch:
343+ d->touchEvent_absent(event);
384344 break;
385− case Undecided:
386− touchEvent_undecided(event);
345+ case DirectionalDragAreaPrivate::Undecided:
346+ d->touchEvent_undecided(event);
387347 break;
388348 default: // Recognized:
389− touchEvent_recognized(event);
349+ d->touchEvent_recognized(event);
390350 break;
391351 }
392352
393− m_activeTouches.update(event);
353+ d->activeTouches.update(event);
394354}
395355
396−void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
356+void DirectionalDragAreaPrivate::touchEvent_absent(QTouchEvent *event)
397357{
398358 // TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
399359
@@ -423,36 +383,40 @@ void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
423383 allGood = false;
424384 } else {
425385 // that's our candidate
426− m_touchId = touchPoint.id();
427386 newTouchPoint = &touchPoint;
428387 }
429388 }
430389 }
431390
391+ if (allGood) {
392+ allGood = sanityCheckRecognitionProperties();
393+ if (!allGood) {
394+ qWarning("DirectionalDragArea: recognition properties are wrongly set. Gesture recognition"
395+ " is impossible");
396+ }
397+ }
398+
432399 if (allGood) {
433400 Q_ASSERT(newTouchPoint);
434401
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);
402+ startPos = newTouchPoint->pos();
403+ startScenePos = newTouchPoint->scenePos();
404+ touchId = newTouchPoint->id();
405+ dampedScenePos.reset(startScenePos);
406+ setPublicPos(startPos);
407+
408+ setPublicScenePos(startScenePos);
445409 updateSceneDirectionVector();
446410
447411 if (recognitionIsDisabled()) {
448412 // Behave like a dumb TouchArea
449413 ddaDebug("Gesture recognition is disabled. Requesting touch ownership immediately.");
450− TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
414+ TouchRegistry::instance()->requestTouchOwnership(touchId, q);
451415 setStatus(Recognized);
452416 event->accept();
453417 } else {
454418 // just monitor the touch points for now.
455− TouchRegistry::instance()->addCandidateOwnerForTouch(m_touchId, this);
419+ TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, q);
456420
457421 setStatus(Undecided);
458422 // Let the item below have it. We will monitor it and grab it later if a gesture
@@ -465,12 +429,11 @@ void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
465429 }
466430}
467431
468−void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
432+void DirectionalDragAreaPrivate::touchEvent_undecided(QTouchEvent *event)
469433{
470− Q_ASSERT(event->type() == QEvent::TouchBegin);
471434 Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
472435
473− // We're not interested in new touch points. We already have our candidate (m_touchId).
436+ // We're not interested in new touch points. We already have our candidate (touchId).
474437 // But we do want to know when those new touches end for keeping the composition time
475438 // window up-to-date
476439 event->ignore();
@@ -480,63 +443,60 @@ void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
480443 // multi-finger drags are not accepted
481444 ddaDebug("Multi-finger drags are not accepted");
482445
483− TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
446+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
484447 // We still wanna know when it ends for keeping the composition time window up-to-date
485− TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
448+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
486449
487450 setStatus(WaitingForTouch);
488451 }
489452}
490453
491−void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
454+void DirectionalDragAreaPrivate::touchEvent_recognized(QTouchEvent *event)
492455{
493456 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
494457
495458 if (!touchPoint) {
496− qCritical() << "DirectionalDragArea[status=Recognized]: touch " << m_touchId
459+ qCritical() << "DirectionalDragArea[status=Recognized]: touch " << touchId
497460 << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
498461 "Considering it as released.";
499462 setStatus(WaitingForTouch);
500463 } else {
501− setPreviousPos(touchPoint->pos());
502− setPreviousScenePos(touchPoint->scenePos());
464+ setPublicPos(touchPoint->pos());
465+ setPublicScenePos(touchPoint->scenePos());
503466
504467 if (touchPoint->state() == Qt::TouchPointReleased) {
505− emitSignalIfTapped();
506468 setStatus(WaitingForTouch);
507469 }
508470 }
509471}
510472
511−void DirectionalDragArea::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
473+void DirectionalDragAreaPrivate::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
512474{
513475 for (int i = 0; i < touchPoints.count(); ++i) {
514476 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
515477 if (touchPoint.state() == Qt::TouchPointPressed) {
516− TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), this);
478+ TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), q);
517479 }
518480 }
519481}
520482
521−bool DirectionalDragArea::recognitionIsDisabled() const
483+bool DirectionalDragAreaPrivate::recognitionIsDisabled() const
522484{
523− return distanceThreshold() <= 0 && compositionTime() <= 0;
485+ return immediateRecognition || (distanceThreshold <= 0 && compositionTime <= 0);
524486}
525487
526−void DirectionalDragArea::emitSignalIfTapped()
488+bool DirectionalDragAreaPrivate::sanityCheckRecognitionProperties()
527489{
528− qint64 touchDuration = m_timeSource->msecsSinceReference() - m_activeTouches.touchStartTime(m_touchId);
529− if (touchDuration <= maxTapDuration()) {
530− Q_EMIT tapped();
531− }
490+ return recognitionIsDisabled()
491+ || (distanceThreshold < maxDistance && compositionTime < maxTime);
532492}
533493
534−const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event)
494+const QTouchEvent::TouchPoint *DirectionalDragAreaPrivate::fetchTargetTouchPoint(QTouchEvent *event)
535495{
536496 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
537497 const QTouchEvent::TouchPoint *touchPoint = 0;
538498 for (int i = 0; i < touchPoints.size(); ++i) {
539− if (touchPoints.at(i).id() == m_touchId) {
499+ if (touchPoints.at(i).id() == touchId) {
540500 touchPoint = &touchPoints.at(i);
541501 break;
542502 }
@@ -544,39 +504,13 @@ const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouch
544504 return touchPoint;
545505}
546506
547−bool DirectionalDragArea::pointInsideAllowedArea() const
507+bool DirectionalDragAreaPrivate::movingInRightDirection() const
548508{
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
574−{
575− if (m_direction == Direction::Horizontal) {
509+ if (direction == Direction::Horizontal || direction == Direction::Vertical) {
576510 return true;
577511 } else {
578− QPointF movementVector(m_dampedScenePos.x() - m_previousDampedScenePos.x(),
579− m_dampedScenePos.y() - m_previousDampedScenePos.y());
512+ QPointF movementVector(dampedScenePos.x() - previousDampedScenePos.x(),
513+ dampedScenePos.y() - previousDampedScenePos.y());
580514
581515 qreal scalarProjection = projectOntoDirectionVector(movementVector);
582516
@@ -584,96 +518,95 @@ bool DirectionalDragArea::movingInRightDirection() const
584518 }
585519}
586520
587−bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
521+bool DirectionalDragAreaPrivate::movedFarEnoughAlongGestureAxis() const
588522{
589− if (m_distanceThreshold <= 0.) {
523+ if (distanceThreshold <= 0.) {
590524 // distance threshold check is disabled
591525 return true;
592526 } else {
593− QPointF totalMovement(point.x() - m_startScenePos.x(),
594− point.y() - m_startScenePos.y());
527+ QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
528+ dampedScenePos.y() - startScenePos.y());
595529
596− qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
597− totalMovement.y() * totalMovement.y();
530+ qreal scalarProjection = projectOntoDirectionVector(totalMovement);
598531
599− return squaredTotalMovSize > m_distanceThresholdSquared;
532+ ddaDebug(" movedFarEnoughAlongGestureAxis: scalarProjection=" << scalarProjection
533+ << ", distanceThreshold=" << distanceThreshold);
534+
535+ if (direction == Direction::Horizontal || direction == Direction::Vertical) {
536+ return qAbs(scalarProjection) > distanceThreshold;
537+ } else {
538+ return scalarProjection > distanceThreshold;
539+ }
600540 }
601541}
602542
603−void DirectionalDragArea::checkSpeed()
543+bool DirectionalDragAreaPrivate::isPastMaxDistance() const
604544{
605− Q_ASSERT(m_status == Undecided);
545+ QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
546+ dampedScenePos.y() - startScenePos.y());
606547
607− if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
608− qreal speed = qFabs(m_velocityCalculator->calculate());
609− qreal minSpeedMsecs = m_minSpeed / 1000.0;
548+ qreal squaredDistance = totalMovement.x()*totalMovement.x() + totalMovement.y()*totalMovement.y();
549+ return squaredDistance > maxDistance*maxDistance;
550+}
610551
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);
552+void DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible()
553+{
554+ if (!q->isEnabled() || !q->isVisible()) {
555+ if (status == Undecided) {
556+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
557+ // We still wanna know when it ends for keeping the composition time window up-to-date
558+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
616559 }
617− }
618−
619− if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) {
620− m_silenceTime += m_recognitionTimer->interval();
621560
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);
561+ if (status != WaitingForTouch) {
562+ ddaDebug("Resetting status because got disabled or made invisible");
626563 setStatus(WaitingForTouch);
627564 }
628− } else {
629− m_silenceTime = 0;
630565 }
631− m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
632566}
633567
634−void DirectionalDragArea::giveUpIfDisabledOrInvisible()
568+void DirectionalDragAreaPrivate::rejectGesture()
635569{
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− }
570+ if (status == Undecided) {
571+ ddaDebug("Rejecting gesture because it's taking too long to drag beyond the threshold.");
642572
643− if (m_status != WaitingForTouch) {
644− ddaDebug("Resetting status because got disabled or made invisible");
645− setStatus(WaitingForTouch);
646− }
573+ TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
574+ // We still wanna know when it ends for keeping the composition time window up-to-date
575+ TouchRegistry::instance()->addTouchWatcher(touchId, q);
576+
577+ setStatus(WaitingForTouch);
647578 }
648579}
649580
650−void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
581+void DirectionalDragAreaPrivate::setStatus(Status newStatus)
651582{
652− if (newStatus == m_status)
583+ if (newStatus == status)
653584 return;
654585
655− DirectionalDragArea::Status oldStatus = m_status;
586+ Status oldStatus = status;
656587
657588 if (oldStatus == Undecided) {
658− m_recognitionTimer->stop();
589+ recognitionTimer->stop();
659590 }
660591
661− m_status = newStatus;
662− Q_EMIT statusChanged(m_status);
592+ status = newStatus;
593+ Q_EMIT statusChanged(status);
663594
664595 ddaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
665596
666597 switch (newStatus) {
667598 case WaitingForTouch:
668− Q_EMIT draggingChanged(false);
599+ if (oldStatus == Recognized) {
600+ Q_EMIT q->draggingChanged(false);
601+ }
602+ Q_EMIT q->pressedChanged(false);
669603 break;
670604 case Undecided:
671− m_recognitionTimer->start();
672− Q_EMIT draggingChanged(true);
605+ recognitionTimer->start();
606+ Q_EMIT q->pressedChanged(true);
673607 break;
674608 case Recognized:
675− if (oldStatus == WaitingForTouch)
676− Q_EMIT draggingChanged(true);
609+ Q_EMIT q->draggingChanged(true);
677610 break;
678611 default:
679612 // no-op
@@ -681,77 +614,128 @@ void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
681614 }
682615}
683616
684−void DirectionalDragArea::setPreviousPos(const QPointF &point)
617+void DirectionalDragAreaPrivate::setPublicPos(const QPointF &point)
685618{
686− bool xChanged = m_previousPos.x() != point.x();
687− bool yChanged = m_previousPos.y() != point.y();
619+ bool xChanged = publicPos.x() != point.x();
620+ bool yChanged = publicPos.y() != point.y();
688621
689− m_previousPos = point;
622+ // Public position should not get updated while the gesture is still being recognized
623+ // (ie, Undecided status).
624+ Q_ASSERT(status == WaitingForTouch || status == Recognized);
625+
626+ if (status == Recognized && !recognitionIsDisabled()) {
627+ // When the gesture finally gets recognized, the finger will likely be
628+ // reasonably far from the edge. If we made the contentX immediately
629+ // follow the finger position it would be visually unpleasant as it
630+ // would appear right next to the user's finger out of nowhere (ie,
631+ // it would jump). Instead, we make contentX go towards the user's
632+ // finger in several steps. ie., in an animated way.
633+ QPointF delta = point - publicPos;
634+ // the trick is not to go all the way (1.0) as it would cause a sudden jump
635+ publicPos.rx() += 0.4 * delta.x();
636+ publicPos.ry() += 0.4 * delta.y();
637+ } else {
638+ // no smoothing when initializing or if gesture recognition was immediate as there will
639+ // be no jump.
640+ publicPos = point;
641+ }
690642
691643 if (xChanged) {
692− Q_EMIT touchXChanged(point.x());
693− if (Direction::isHorizontal(m_direction))
694− Q_EMIT distanceChanged(distance());
644+ Q_EMIT q->touchXChanged(publicPos.x());
645+ if (Direction::isHorizontal(direction))
646+ Q_EMIT q->distanceChanged(q->distance());
695647 }
696648
697649 if (yChanged) {
698− Q_EMIT touchYChanged(point.y());
699− if (Direction::isVertical(m_direction))
700− Q_EMIT distanceChanged(distance());
650+ Q_EMIT q->touchYChanged(publicPos.y());
651+ if (Direction::isVertical(direction))
652+ Q_EMIT q->distanceChanged(q->distance());
701653 }
702654}
703655
704−void DirectionalDragArea::setPreviousScenePos(const QPointF &point)
656+void DirectionalDragAreaPrivate::setPublicScenePos(const QPointF &point)
705657{
706− bool xChanged = m_previousScenePos.x() != point.x();
707− bool yChanged = m_previousScenePos.y() != point.y();
658+ bool xChanged = publicScenePos.x() != point.x();
659+ bool yChanged = publicScenePos.y() != point.y();
708660
709661 if (!xChanged && !yChanged)
710662 return;
711663
712− qreal oldSceneDistance = sceneDistance();
713− m_previousScenePos = point;
664+ // Public position should not get updated while the gesture is still being recognized
665+ // (ie, Undecided status).
666+ Q_ASSERT(status == WaitingForTouch || status == Recognized);
667+
668+ qreal oldSceneDistance = sceneDistance;
669+
670+ if (status == Recognized && !recognitionIsDisabled()) {
671+ // When the gesture finally gets recognized, the finger will likely be
672+ // reasonably far from the edge. If we made the contentX immediately
673+ // follow the finger position it would be visually unpleasant as it
674+ // would appear right next to the user's finger out of nowhere (ie,
675+ // it would jump). Instead, we make contentX go towards the user's
676+ // finger in several steps. ie., in an animated way.
677+ QPointF delta = point - publicScenePos;
678+ // the trick is not to go all the way (1.0) as it would cause a sudden jump
679+ publicScenePos.rx() += 0.4 * delta.x();
680+ publicScenePos.ry() += 0.4 * delta.y();
681+ } else {
682+ // no smoothing when initializing or if gesture recognition was immediate as there will
683+ // be no jump.
684+ publicScenePos = point;
685+ }
686+
714687 updateSceneDistance();
715688
716− if (oldSceneDistance != sceneDistance()) {
717− Q_EMIT sceneDistanceChanged(sceneDistance());
689+ if (oldSceneDistance != sceneDistance) {
690+ Q_EMIT q->sceneDistanceChanged(sceneDistance);
718691 }
719692
720693 if (xChanged) {
721− Q_EMIT touchSceneXChanged(point.x());
694+ Q_EMIT q->touchSceneXChanged(publicScenePos.x());
722695 }
723696
724697 if (yChanged) {
725− Q_EMIT touchSceneYChanged(point.y());
698+ Q_EMIT q->touchSceneYChanged(publicScenePos.y());
726699 }
727700}
728701
729−void DirectionalDragArea::updateVelocityCalculator(const QPointF &scenePos)
702+bool DirectionalDragAreaPrivate::isWithinTouchCompositionWindow()
730703{
731− QPointF totalSceneMovement = scenePos - m_startScenePos;
704+ return
705+ compositionTime > 0 &&
706+ !activeTouches.isEmpty() &&
707+ timeSource->msecsSinceReference() <=
708+ activeTouches.mostRecentStartTime() + (qint64)compositionTime;
709+}
732710
733− qreal scalarProjection = projectOntoDirectionVector(totalSceneMovement);
711+void DirectionalDragArea::itemChange(ItemChange change, const ItemChangeData &value)
712+{
713+ if (change == QQuickItem::ItemSceneChange) {
714+ if (value.window != nullptr) {
715+ value.window->installEventFilter(TouchRegistry::instance());
734716
735− m_velocityCalculator->setTrackedPosition(scalarProjection);
717+ // TODO: Handle window->screen() changes (ie window changing screens)
718+ qreal pixelsPerMm = value.window->screen()->physicalDotsPerInch() / 25.4;
719+ d->setPixelsPerMm(pixelsPerMm);
720+ }
721+ }
736722}
737723
738−bool DirectionalDragArea::isWithinTouchCompositionWindow()
724+void DirectionalDragAreaPrivate::setPixelsPerMm(qreal pixelsPerMm)
739725{
740− return
741− compositionTime() > 0 &&
742− !m_activeTouches.isEmpty() &&
743− m_timeSource->msecsSinceReference() <=
744− m_activeTouches.mostRecentStartTime() + (qint64)compositionTime();
726+ dampedScenePos.setMaxDelta(1. * pixelsPerMm);
727+ setDistanceThreshold(4. * pixelsPerMm);
728+ maxDistance = 10. * pixelsPerMm;
745729}
746730
747731//************************** ActiveTouchesInfo **************************
748732
749−DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
733+ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
750734 : m_timeSource(timeSource)
751735{
752736}
753737
754−void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
738+void ActiveTouchesInfo::update(QTouchEvent *event)
755739{
756740 if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
757741 // nothing to update
@@ -773,7 +757,7 @@ void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
773757}
774758
775759#if ACTIVETOUCHESINFO_DEBUG
776−QString DirectionalDragArea::ActiveTouchesInfo::toString()
760+QString ActiveTouchesInfo::toString()
777761{
778762 QString string = "(";
779763
@@ -791,7 +775,7 @@ QString DirectionalDragArea::ActiveTouchesInfo::toString()
791775}
792776#endif // ACTIVETOUCHESINFO_DEBUG
793777
794−void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(int touchId)
778+void ActiveTouchesInfo::addTouchPoint(int touchId)
795779{
796780 ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
797781 activeTouchInfo.id = touchId;
@@ -802,7 +786,7 @@ void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(int touchId)
802786 #endif
803787}
804788
805−qint64 DirectionalDragArea::ActiveTouchesInfo::touchStartTime(int touchId)
789+qint64 ActiveTouchesInfo::touchStartTime(int touchId)
806790{
807791 qint64 result = -1;
808792
@@ -819,7 +803,7 @@ qint64 DirectionalDragArea::ActiveTouchesInfo::touchStartTime(int touchId)
819803 return result;
820804}
821805
822−void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(int touchId)
806+void ActiveTouchesInfo::removeTouchPoint(int touchId)
823807{
824808 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
825809 if (touchId == touchInfo->id) {
@@ -835,7 +819,7 @@ void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(int touchId)
835819 #endif
836820}
837821
838−qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
822+qint64 ActiveTouchesInfo::mostRecentStartTime()
839823{
840824 Q_ASSERT(!m_touchInfoPool.isEmpty());
841825
@@ -851,16 +835,17 @@ qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
851835 return highestStartTime;
852836}
853837
854−void DirectionalDragArea::updateSceneDirectionVector()
838+void DirectionalDragAreaPrivate::updateSceneDirectionVector()
855839{
856840 QPointF localOrigin(0., 0.);
857841 QPointF localDirection;
858− switch (m_direction) {
842+ switch (direction) {
859843 case Direction::Upwards:
860844 localDirection.rx() = 0.;
861845 localDirection.ry() = -1.;
862846 break;
863847 case Direction::Downwards:
848+ case Direction::Vertical:
864849 localDirection.rx() = 0.;
865850 localDirection.ry() = 1;
866851 break;
@@ -873,14 +858,31 @@ void DirectionalDragArea::updateSceneDirectionVector()
873858 localDirection.ry() = 0.;
874859 break;
875860 }
876− QPointF sceneOrigin = mapToScene(localOrigin);
877− QPointF sceneDirection = mapToScene(localDirection);
878− m_sceneDirectionVector = sceneDirection - sceneOrigin;
861+ QPointF sceneOrigin = q->mapToScene(localOrigin);
862+ QPointF sceneDirection = q->mapToScene(localDirection);
863+ sceneDirectionVector = sceneDirection - sceneOrigin;
864+}
865+
866+qreal DirectionalDragAreaPrivate::projectOntoDirectionVector(const QPointF &sceneVector) const
867+{
868+ // same as dot product as sceneDirectionVector is a unit vector
869+ return sceneVector.x() * sceneDirectionVector.x() +
870+ sceneVector.y() * sceneDirectionVector.y();
879871}
880872
881−qreal DirectionalDragArea::projectOntoDirectionVector(const QPointF &sceneVector) const
873+DirectionalDragAreaPrivate::DirectionalDragAreaPrivate(DirectionalDragArea *q)
874+ : q(q)
875+ , status(WaitingForTouch)
876+ , sceneDistance(0)
877+ , touchId(-1)
878+ , direction(Direction::Rightwards)
879+ , distanceThreshold(0)
880+ , distanceThresholdSquared(0.)
881+ , maxTime(400)
882+ , compositionTime(60)
883+ , immediateRecognition(false)
884+ , recognitionTimer(nullptr)
885+ , timeSource(new RealTimeSource)
886+ , activeTouches(timeSource)
882887{
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();
886888}