@@ -1,929 +0,0 @@
1−/*
2− * Copyright (C) 2013-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−#define ACTIVETOUCHESINFO_DEBUG 0
18−#define DIRECTIONALDRAGAREA_DEBUG 0
19−
20−#include "DirectionalDragArea.h"
21−
22−#include <QQuickWindow>
23−#include <QtCore/qmath.h>
24−#include <QScreen>
25−#include <QDebug>
26−
27−#include <private/qquickwindow_p.h>
28−
29−// local
30−#include "TouchOwnershipEvent.h"
31−#include "TouchRegistry.h"
32−#include "UnownedTouchEvent.h"
33−
34−#include "DirectionalDragArea_p.h"
35−
36−using namespace UbuntuGestures;
37−
38−#if DIRECTIONALDRAGAREA_DEBUG
39−#define ddaDebug(params) qDebug().nospace() << "[DDA(" << qPrintable(objectName()) << ")] " << params
40−#include "DebugHelpers.h"
41−
42−namespace {
43−const char *statusToString(DirectionalDragAreaPrivate::Status status)
44−{
45− if (status == DirectionalDragAreaPrivate::WaitingForTouch) {
46− return "WaitingForTouch";
47− } else if (status == DirectionalDragAreaPrivate::Undecided) {
48− return "Undecided";
49− } else {
50− return "Recognized";
51− }
52−}
53−
54−} // namespace {
55−#else // DIRECTIONALDRAGAREA_DEBUG
56−#define ddaDebug(params) ((void)0)
57−#endif // DIRECTIONALDRAGAREA_DEBUG
58−
59−DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
60− : QQuickItem(parent)
61− , d(new DirectionalDragAreaPrivate(this))
62−{
63− d->setRecognitionTimer(new Timer(this));
64− d->recognitionTimer->setInterval(d->maxTime);
65− d->recognitionTimer->setSingleShot(true);
66−
67− connect(this, &QQuickItem::enabledChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
68− connect(this, &QQuickItem::visibleChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
69−}
70−
71−Direction::Type DirectionalDragArea::direction() const
72−{
73− return d->direction;
74−}
75−
76−void DirectionalDragArea::setDirection(Direction::Type direction)
77−{
78− if (direction != d->direction) {
79− d->direction = direction;
80− Q_EMIT directionChanged(d->direction);
81− }
82−}
83−
84−void DirectionalDragAreaPrivate::setDistanceThreshold(qreal value)
85−{
86− if (distanceThreshold != value) {
87− distanceThreshold = value;
88− distanceThresholdSquared = distanceThreshold * distanceThreshold;
89− }
90−}
91−
92−void DirectionalDragAreaPrivate::setMaxTime(int value)
93−{
94− if (maxTime != value) {
95− maxTime = value;
96− recognitionTimer->setInterval(maxTime);
97− }
98−}
99−
100−void DirectionalDragAreaPrivate::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
101−{
102− int interval = 0;
103− bool timerWasRunning = false;
104− bool wasSingleShot = false;
105−
106− // can be null when called from the constructor
107− if (recognitionTimer) {
108− interval = recognitionTimer->interval();
109− timerWasRunning = recognitionTimer->isRunning();
110− if (recognitionTimer->parent() == this) {
111− delete recognitionTimer;
112− }
113− }
114−
115− recognitionTimer = timer;
116− timer->setInterval(interval);
117− timer->setSingleShot(wasSingleShot);
118− connect(timer, &UbuntuGestures::AbstractTimer::timeout,
119− this, &DirectionalDragAreaPrivate::rejectGesture);
120− if (timerWasRunning) {
121− recognitionTimer->start();
122− }
123−}
124−
125−void DirectionalDragAreaPrivate::setTimeSource(const SharedTimeSource &timeSource)
126−{
127− this->timeSource = timeSource;
128− activeTouches.m_timeSource = timeSource;
129−}
130−
131−qreal DirectionalDragArea::distance() const
132−{
133− if (Direction::isHorizontal(d->direction)) {
134− return d->publicPos.x() - d->startPos.x();
135− } else {
136− return d->publicPos.y() - d->startPos.y();
137− }
138−}
139−
140−void DirectionalDragAreaPrivate::updateSceneDistance()
141−{
142− QPointF totalMovement = publicScenePos - startScenePos;
143− sceneDistance = projectOntoDirectionVector(totalMovement);
144−}
145−
146−qreal DirectionalDragArea::sceneDistance() const
147−{
148− return d->sceneDistance;
149−}
150−
151−qreal DirectionalDragArea::touchX() const
152−{
153− return d->publicPos.x();
154−}
155−
156−qreal DirectionalDragArea::touchY() const
157−{
158− return d->publicPos.y();
159−}
160−
161−qreal DirectionalDragArea::touchSceneX() const
162−{
163− return d->publicScenePos.x();
164−}
165−
166−qreal DirectionalDragArea::touchSceneY() const
167−{
168− return d->publicScenePos.y();
169−}
170−
171−bool DirectionalDragArea::dragging() const
172−{
173− return d->status == DirectionalDragAreaPrivate::Recognized;
174−}
175−
176−bool DirectionalDragArea::pressed() const
177−{
178− return d->status != DirectionalDragAreaPrivate::WaitingForTouch;
179−}
180−
181−bool DirectionalDragArea::immediateRecognition() const
182−{
183− return d->immediateRecognition;
184−}
185−
186−void DirectionalDragArea::setImmediateRecognition(bool enabled)
187−{
188− if (d->immediateRecognition != enabled) {
189− d->immediateRecognition = enabled;
190− Q_EMIT immediateRecognitionChanged(enabled);
191− }
192−}
193−
194−bool DirectionalDragArea::monitorOnly() const
195−{
196− return d->monitorOnly;
197−}
198−
199−void DirectionalDragArea::setMonitorOnly(bool monitorOnly)
200−{
201− if (d->monitorOnly != monitorOnly) {
202− d->monitorOnly = monitorOnly;
203−
204− if (monitorOnly && d->status == DirectionalDragAreaPrivate::Undecided) {
205− TouchRegistry::instance()->removeCandidateOwnerForTouch(d->touchId, this);
206− // We still wanna know when it ends for keeping the composition time window up-to-date
207− TouchRegistry::instance()->addTouchWatcher(d->touchId, this);
208− }
209−
210− Q_EMIT monitorOnlyChanged(monitorOnly);
211− }
212−}
213−
214−void DirectionalDragArea::removeTimeConstraints()
215−{
216− d->setMaxTime(60 * 60 * 1000);
217− d->compositionTime = 0;
218− ddaDebug("removed time constraints");
219−}
220−
221−bool DirectionalDragArea::event(QEvent *event)
222−{
223− if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
224− d->touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
225− return true;
226− } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
227− d->unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
228− return true;
229− } else {
230− return QQuickItem::event(event);
231− }
232−}
233−
234−void DirectionalDragAreaPrivate::touchOwnershipEvent(TouchOwnershipEvent *event)
235−{
236− if (event->gained()) {
237− QVector<int> ids;
238− ids.append(event->touchId());
239− ddaDebug("grabbing touch");
240− q->grabTouchPoints(ids);
241−
242− // Work around for Qt bug. If we grab a touch that is being used for mouse pointer
243− // emulation it will cause the emulation logic to go nuts.
244− // Thus we have to also grab the mouse in this case.
245− //
246− // The fix for this bug has landed in Qt 5.4 (https://codereview.qt-project.org/96887)
247− // TODO: Remove this workaround once we start using Qt 5.4
248− if (q->window()) {
249− QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(q->window());
250− if (windowPrivate->touchMouseId == event->touchId() && q->window()->mouseGrabberItem()) {
251− ddaDebug("removing mouse grabber");
252− q->window()->mouseGrabberItem()->ungrabMouse();
253− }
254− }
255− } else {
256− // We still wanna know when it ends for keeping the composition time window up-to-date
257− TouchRegistry::instance()->addTouchWatcher(touchId, q);
258−
259− setStatus(WaitingForTouch);
260− }
261−}
262−
263−void DirectionalDragAreaPrivate::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
264−{
265− QTouchEvent *event = unownedTouchEvent->touchEvent();
266−
267− Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
268−
269− ddaDebug("Unowned " << timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
270−
271− switch (status) {
272− case WaitingForTouch:
273− // do nothing
274− break;
275− case Undecided:
276− Q_ASSERT(q->isEnabled() && q->isVisible());
277− unownedTouchEvent_undecided(unownedTouchEvent);
278− break;
279− default: // Recognized:
280− if (monitorOnly) {
281− // Treat unowned event as if we owned it, but we are really just watching it
282− touchEvent_recognized(event);
283− }
284− break;
285− }
286−
287− activeTouches.update(event);
288−}
289−
290−void DirectionalDragAreaPrivate::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
291−{
292− const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
293− if (!touchPoint) {
294− qCritical() << "DirectionalDragArea[status=Undecided]: touch " << touchId
295− << "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
296− "Considering it as released.";
297−
298− TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
299− setStatus(WaitingForTouch);
300− return;
301− }
302−
303− const QPointF &touchScenePos = touchPoint->scenePos();
304−
305− if (touchPoint->state() == Qt::TouchPointReleased) {
306− // touch has ended before recognition concluded
307− ddaDebug("Touch has ended before recognition concluded");
308− TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
309− setStatus(WaitingForTouch);
310− return;
311− }
312−
313− previousDampedScenePos.setX(dampedScenePos.x());
314− previousDampedScenePos.setY(dampedScenePos.y());
315− dampedScenePos.update(touchScenePos);
316−
317− if (!movingInRightDirection()) {
318− ddaDebug("Rejecting gesture because touch point is moving in the wrong direction.");
319− TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
320− // We still wanna know when it ends for keeping the composition time window up-to-date
321− TouchRegistry::instance()->addTouchWatcher(touchId, q);
322− setStatus(WaitingForTouch);
323− return;
324− }
325−
326− if (isWithinTouchCompositionWindow()) {
327− // There's still time for some new touch to appear and ruin our party as it would be combined
328− // with our touchId one and therefore deny the possibility of a single-finger gesture.
329− ddaDebug("Sill within composition window. Let's wait more.");
330− return;
331− }
332−
333− if (movedFarEnoughAlongGestureAxis()) {
334− if (!monitorOnly) {
335− TouchRegistry::instance()->requestTouchOwnership(touchId, q);
336− }
337− setStatus(Recognized);
338− setPublicPos(touchPoint->pos());
339− setPublicScenePos(touchScenePos);
340− } else if (isPastMaxDistance()) {
341− ddaDebug("Rejecting gesture because it went farther than maxDistance without getting recognized.");
342− TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
343− // We still wanna know when it ends for keeping the composition time window up-to-date
344− TouchRegistry::instance()->addTouchWatcher(touchId, q);
345− setStatus(WaitingForTouch);
346− } else {
347− ddaDebug("Didn't move far enough yet. Let's wait more.");
348− }
349−}
350−
351−void DirectionalDragArea::touchEvent(QTouchEvent *event)
352−{
353− // TODO: Consider when more than one touch starts in the same event (although it's not possible
354− // with Mir's android-input). Have to track them all. Consider it a plus/bonus.
355−
356− ddaDebug(d->timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
357−
358− if (!isEnabled() || !isVisible()) {
359− QQuickItem::touchEvent(event);
360− return;
361− }
362−
363− switch (d->status) {
364− case DirectionalDragAreaPrivate::WaitingForTouch:
365− d->touchEvent_absent(event);
366− break;
367− case DirectionalDragAreaPrivate::Undecided:
368− d->touchEvent_undecided(event);
369− break;
370− default: // Recognized:
371− d->touchEvent_recognized(event);
372− break;
373− }
374−
375− d->activeTouches.update(event);
376−}
377−
378−void DirectionalDragAreaPrivate::touchEvent_absent(QTouchEvent *event)
379−{
380− // TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
381−
382− if (!event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
383− // Nothing to see here. No touch starting in this event.
384− return;
385− }
386−
387− // to be proven wrong, if that's the case
388− bool allGood = true;
389−
390− if (isWithinTouchCompositionWindow()) {
391− // too close to the last touch start. So we consider them as starting roughly at the same time.
392− // Can't be a single-touch gesture.
393− ddaDebug("A new touch point came in but we're still within time composition window. Ignoring it.");
394− allGood = false;
395− }
396−
397− const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
398−
399− const QTouchEvent::TouchPoint *newTouchPoint = nullptr;
400− for (int i = 0; i < touchPoints.count() && allGood; ++i) {
401− const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
402− if (touchPoint.state() == Qt::TouchPointPressed) {
403− if (newTouchPoint) {
404− // more than one touch starting in this QTouchEvent. Can't be a single-touch gesture
405− allGood = false;
406− } else {
407− // that's our candidate
408− newTouchPoint = &touchPoint;
409− }
410− }
411− }
412−
413− if (allGood) {
414− allGood = sanityCheckRecognitionProperties();
415− if (!allGood) {
416− qWarning("DirectionalDragArea: recognition properties are wrongly set. Gesture recognition"
417− " is impossible");
418− }
419− }
420−
421− if (allGood) {
422− Q_ASSERT(newTouchPoint);
423−
424− startPos = newTouchPoint->pos();
425− startScenePos = newTouchPoint->scenePos();
426− touchId = newTouchPoint->id();
427− dampedScenePos.reset(startScenePos);
428− setPublicPos(startPos);
429−
430− setPublicScenePos(startScenePos);
431− updateSceneDirectionVector();
432−
433− if (recognitionIsDisabled()) {
434− // Behave like a dumb TouchArea
435− ddaDebug("Gesture recognition is disabled. Requesting touch ownership immediately.");
436− setStatus(Recognized);
437− if (monitorOnly) {
438− watchPressedTouchPoints(touchPoints);
439− event->ignore();
440− } else {
441− TouchRegistry::instance()->requestTouchOwnership(touchId, q);
442− event->accept();
443− }
444− } else {
445− // just monitor the touch points for now.
446− if (monitorOnly) {
447− watchPressedTouchPoints(touchPoints);
448− } else {
449− TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, q);
450− }
451−
452− setStatus(Undecided);
453− // Let the item below have it. We will monitor it and grab it later if a gesture
454− // gets recognized.
455− event->ignore();
456− }
457− } else {
458− watchPressedTouchPoints(touchPoints);
459− event->ignore();
460− }
461−}
462−
463−void DirectionalDragAreaPrivate::touchEvent_undecided(QTouchEvent *event)
464−{
465− Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
466−
467− // We're not interested in new touch points. We already have our candidate (touchId).
468− // But we do want to know when those new touches end for keeping the composition time
469− // window up-to-date
470− event->ignore();
471− watchPressedTouchPoints(event->touchPoints());
472−
473− if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) {
474− // multi-finger drags are not accepted
475− ddaDebug("Multi-finger drags are not accepted");
476−
477− TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
478− // We still wanna know when it ends for keeping the composition time window up-to-date
479− TouchRegistry::instance()->addTouchWatcher(touchId, q);
480−
481− setStatus(WaitingForTouch);
482− }
483−}
484−
485−void DirectionalDragAreaPrivate::touchEvent_recognized(QTouchEvent *event)
486−{
487− const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
488−
489− if (!touchPoint) {
490− qCritical() << "DirectionalDragArea[status=Recognized]: touch " << touchId
491− << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
492− "Considering it as released.";
493− setStatus(WaitingForTouch);
494− } else {
495− setPublicPos(touchPoint->pos());
496− setPublicScenePos(touchPoint->scenePos());
497−
498− if (touchPoint->state() == Qt::TouchPointReleased) {
499− setStatus(WaitingForTouch);
500− }
501− }
502−}
503−
504−void DirectionalDragAreaPrivate::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
505−{
506− for (int i = 0; i < touchPoints.count(); ++i) {
507− const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
508− if (touchPoint.state() == Qt::TouchPointPressed) {
509− TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), q);
510− }
511− }
512−}
513−
514−bool DirectionalDragAreaPrivate::recognitionIsDisabled() const
515−{
516− return immediateRecognition || (distanceThreshold <= 0 && compositionTime <= 0);
517−}
518−
519−bool DirectionalDragAreaPrivate::sanityCheckRecognitionProperties()
520−{
521− return recognitionIsDisabled()
522− || (distanceThreshold < maxDistance && compositionTime < maxTime);
523−}
524−
525−const QTouchEvent::TouchPoint *DirectionalDragAreaPrivate::fetchTargetTouchPoint(QTouchEvent *event)
526−{
527− const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
528− const QTouchEvent::TouchPoint *touchPoint = 0;
529− for (int i = 0; i < touchPoints.size(); ++i) {
530− if (touchPoints.at(i).id() == touchId) {
531− touchPoint = &touchPoints.at(i);
532− break;
533− }
534− }
535− return touchPoint;
536−}
537−
538−bool DirectionalDragAreaPrivate::movingInRightDirection() const
539−{
540− if (direction == Direction::Horizontal || direction == Direction::Vertical) {
541− return true;
542− } else {
543− QPointF movementVector(dampedScenePos.x() - previousDampedScenePos.x(),
544− dampedScenePos.y() - previousDampedScenePos.y());
545−
546− qreal scalarProjection = projectOntoDirectionVector(movementVector);
547−
548− return scalarProjection >= 0.;
549− }
550−}
551−
552−bool DirectionalDragAreaPrivate::movedFarEnoughAlongGestureAxis() const
553−{
554− if (distanceThreshold <= 0.) {
555− // distance threshold check is disabled
556− return true;
557− } else {
558− QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
559− dampedScenePos.y() - startScenePos.y());
560−
561− qreal scalarProjection = projectOntoDirectionVector(totalMovement);
562−
563− ddaDebug(" movedFarEnoughAlongGestureAxis: scalarProjection=" << scalarProjection
564− << ", distanceThreshold=" << distanceThreshold);
565−
566− if (direction == Direction::Horizontal || direction == Direction::Vertical) {
567− return qAbs(scalarProjection) > distanceThreshold;
568− } else {
569− return scalarProjection > distanceThreshold;
570− }
571− }
572−}
573−
574−bool DirectionalDragAreaPrivate::isPastMaxDistance() const
575−{
576− QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
577− dampedScenePos.y() - startScenePos.y());
578−
579− qreal squaredDistance = totalMovement.x()*totalMovement.x() + totalMovement.y()*totalMovement.y();
580− return squaredDistance > maxDistance*maxDistance;
581−}
582−
583−void DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible()
584−{
585− if (!q->isEnabled() || !q->isVisible()) {
586− if (status == Undecided) {
587− TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
588− // We still wanna know when it ends for keeping the composition time window up-to-date
589− TouchRegistry::instance()->addTouchWatcher(touchId, q);
590− }
591−
592− if (status != WaitingForTouch) {
593− ddaDebug("Resetting status because got disabled or made invisible");
594− setStatus(WaitingForTouch);
595− }
596− }
597−}
598−
599−void DirectionalDragAreaPrivate::rejectGesture()
600−{
601− if (status == Undecided) {
602− ddaDebug("Rejecting gesture because it's taking too long to drag beyond the threshold.");
603−
604− TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
605− // We still wanna know when it ends for keeping the composition time window up-to-date
606− TouchRegistry::instance()->addTouchWatcher(touchId, q);
607−
608− setStatus(WaitingForTouch);
609− }
610−}
611−
612−void DirectionalDragAreaPrivate::setStatus(Status newStatus)
613−{
614− if (newStatus == status)
615− return;
616−
617− Status oldStatus = status;
618−
619− if (oldStatus == Undecided) {
620− recognitionTimer->stop();
621− }
622−
623− status = newStatus;
624− Q_EMIT statusChanged(status);
625−
626− ddaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
627−
628− switch (newStatus) {
629− case WaitingForTouch:
630− if (oldStatus == Recognized) {
631− Q_EMIT q->draggingChanged(false);
632− }
633− Q_EMIT q->pressedChanged(false);
634− break;
635− case Undecided:
636− recognitionTimer->start();
637− Q_EMIT q->pressedChanged(true);
638− break;
639− case Recognized:
640− if (oldStatus == WaitingForTouch) { // for immediate recognition
641− Q_EMIT q->pressedChanged(true);
642− }
643− Q_EMIT q->draggingChanged(true);
644− break;
645− default:
646− // no-op
647− break;
648− }
649−}
650−
651−void DirectionalDragAreaPrivate::setPublicPos(const QPointF point)
652−{
653− bool xChanged = publicPos.x() != point.x();
654− bool yChanged = publicPos.y() != point.y();
655−
656− // Public position should not get updated while the gesture is still being recognized
657− // (ie, Undecided status).
658− Q_ASSERT(status == WaitingForTouch || status == Recognized);
659−
660− if (status == Recognized && !recognitionIsDisabled()) {
661− // When the gesture finally gets recognized, the finger will likely be
662− // reasonably far from the edge. If we made the contentX immediately
663− // follow the finger position it would be visually unpleasant as it
664− // would appear right next to the user's finger out of nowhere (ie,
665− // it would jump). Instead, we make contentX go towards the user's
666− // finger in several steps. ie., in an animated way.
667− QPointF delta = point - publicPos;
668− // the trick is not to go all the way (1.0) as it would cause a sudden jump
669− publicPos.rx() += 0.4 * delta.x();
670− publicPos.ry() += 0.4 * delta.y();
671− } else {
672− // no smoothing when initializing or if gesture recognition was immediate as there will
673− // be no jump.
674− publicPos = point;
675− }
676−
677− if (xChanged) {
678− Q_EMIT q->touchXChanged(publicPos.x());
679− if (Direction::isHorizontal(direction))
680− Q_EMIT q->distanceChanged(q->distance());
681− }
682−
683− if (yChanged) {
684− Q_EMIT q->touchYChanged(publicPos.y());
685− if (Direction::isVertical(direction))
686− Q_EMIT q->distanceChanged(q->distance());
687− }
688−}
689−
690−void DirectionalDragAreaPrivate::setPublicScenePos(const QPointF point)
691−{
692− bool xChanged = publicScenePos.x() != point.x();
693− bool yChanged = publicScenePos.y() != point.y();
694−
695− if (!xChanged && !yChanged)
696− return;
697−
698− // Public position should not get updated while the gesture is still being recognized
699− // (ie, Undecided status).
700− Q_ASSERT(status == WaitingForTouch || status == Recognized);
701−
702− qreal oldSceneDistance = sceneDistance;
703−
704− if (status == Recognized && !recognitionIsDisabled()) {
705− // When the gesture finally gets recognized, the finger will likely be
706− // reasonably far from the edge. If we made the contentX immediately
707− // follow the finger position it would be visually unpleasant as it
708− // would appear right next to the user's finger out of nowhere (ie,
709− // it would jump). Instead, we make contentX go towards the user's
710− // finger in several steps. ie., in an animated way.
711− QPointF delta = point - publicScenePos;
712− // the trick is not to go all the way (1.0) as it would cause a sudden jump
713− publicScenePos.rx() += 0.4 * delta.x();
714− publicScenePos.ry() += 0.4 * delta.y();
715− } else {
716− // no smoothing when initializing or if gesture recognition was immediate as there will
717− // be no jump.
718− publicScenePos = point;
719− }
720−
721− updateSceneDistance();
722−
723− if (oldSceneDistance != sceneDistance) {
724− Q_EMIT q->sceneDistanceChanged(sceneDistance);
725− }
726−
727− if (xChanged) {
728− Q_EMIT q->touchSceneXChanged(publicScenePos.x());
729− }
730−
731− if (yChanged) {
732− Q_EMIT q->touchSceneYChanged(publicScenePos.y());
733− }
734−}
735−
736−bool DirectionalDragAreaPrivate::isWithinTouchCompositionWindow()
737−{
738− return
739− compositionTime > 0 &&
740− !activeTouches.isEmpty() &&
741− timeSource->msecsSinceReference() <=
742− activeTouches.mostRecentStartTime() + (qint64)compositionTime;
743−}
744−
745−void DirectionalDragArea::itemChange(ItemChange change, const ItemChangeData &value)
746−{
747− if (change == QQuickItem::ItemSceneChange) {
748− if (value.window != nullptr) {
749− value.window->installEventFilter(TouchRegistry::instance());
750−
751− // TODO: Handle window->screen() changes (ie window changing screens)
752−
753− qreal pixelsPerInch = value.window->screen()->physicalDotsPerInch();
754− if (pixelsPerInch < 0) {
755− // It can return garbage when run in a XVFB server (Virtual Framebuffer 'fake' X server)
756− pixelsPerInch = 72;
757− }
758−
759− d->setPixelsPerMm(pixelsPerInch / 25.4);
760− }
761− }
762−}
763−
764−void DirectionalDragAreaPrivate::setPixelsPerMm(qreal pixelsPerMm)
765−{
766− dampedScenePos.setMaxDelta(1. * pixelsPerMm);
767− setDistanceThreshold(4. * pixelsPerMm);
768− maxDistance = 10. * pixelsPerMm;
769−}
770−
771−//************************** ActiveTouchesInfo **************************
772−
773−ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
774− : m_timeSource(timeSource)
775−{
776−}
777−
778−void ActiveTouchesInfo::update(QTouchEvent *event)
779−{
780− if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
781− // nothing to update
782− #if ACTIVETOUCHESINFO_DEBUG
783− qDebug("[DDA::ActiveTouchesInfo] Nothing to Update");
784− #endif
785− return;
786− }
787−
788− const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
789− for (int i = 0; i < touchPoints.count(); ++i) {
790− const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
791− if (touchPoint.state() == Qt::TouchPointPressed) {
792− addTouchPoint(touchPoint.id());
793− } else if (touchPoint.state() == Qt::TouchPointReleased) {
794− removeTouchPoint(touchPoint.id());
795− }
796− }
797−}
798−
799−#if ACTIVETOUCHESINFO_DEBUG
800−QString ActiveTouchesInfo::toString()
801−{
802− QString string = "(";
803−
804− {
805− QTextStream stream(&string);
806− m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
807− stream << "(id=" << touchInfo->id << ",startTime=" << touchInfo->startTime << ")";
808− return true;
809− });
810− }
811−
812− string.append(")");
813−
814− return string;
815−}
816−#endif // ACTIVETOUCHESINFO_DEBUG
817−
818−void ActiveTouchesInfo::addTouchPoint(int touchId)
819−{
820− ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
821− activeTouchInfo.id = touchId;
822− activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
823−
824− #if ACTIVETOUCHESINFO_DEBUG
825− qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
826− #endif
827−}
828−
829−qint64 ActiveTouchesInfo::touchStartTime(int touchId)
830−{
831− qint64 result = -1;
832−
833− m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
834− if (touchId == touchInfo->id) {
835− result = touchInfo->startTime;
836− return false;
837− } else {
838− return true;
839− }
840− });
841−
842− Q_ASSERT(result != -1);
843− return result;
844−}
845−
846−void ActiveTouchesInfo::removeTouchPoint(int touchId)
847−{
848− m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
849− if (touchId == touchInfo->id) {
850− m_touchInfoPool.freeSlot(touchInfo);
851− return false;
852− } else {
853− return true;
854− }
855− });
856−
857− #if ACTIVETOUCHESINFO_DEBUG
858− qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
859− #endif
860−}
861−
862−qint64 ActiveTouchesInfo::mostRecentStartTime()
863−{
864− Q_ASSERT(!m_touchInfoPool.isEmpty());
865−
866− qint64 highestStartTime = -1;
867−
868− m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &activeTouchInfo) {
869− if (activeTouchInfo->startTime > highestStartTime) {
870− highestStartTime = activeTouchInfo->startTime;
871− }
872− return true;
873− });
874−
875− return highestStartTime;
876−}
877−
878−void DirectionalDragAreaPrivate::updateSceneDirectionVector()
879−{
880− QPointF localOrigin(0., 0.);
881− QPointF localDirection;
882− switch (direction) {
883− case Direction::Upwards:
884− localDirection.rx() = 0.;
885− localDirection.ry() = -1.;
886− break;
887− case Direction::Downwards:
888− case Direction::Vertical:
889− localDirection.rx() = 0.;
890− localDirection.ry() = 1;
891− break;
892− case Direction::Leftwards:
893− localDirection.rx() = -1.;
894− localDirection.ry() = 0.;
895− break;
896− default: // Direction::Rightwards || Direction.Horizontal
897− localDirection.rx() = 1.;
898− localDirection.ry() = 0.;
899− break;
900− }
901− QPointF sceneOrigin = q->mapToScene(localOrigin);
902− QPointF sceneDirection = q->mapToScene(localDirection);
903− sceneDirectionVector = sceneDirection - sceneOrigin;
904−}
905−
906−qreal DirectionalDragAreaPrivate::projectOntoDirectionVector(const QPointF sceneVector) const
907−{
908− // same as dot product as sceneDirectionVector is a unit vector
909− return sceneVector.x() * sceneDirectionVector.x() +
910− sceneVector.y() * sceneDirectionVector.y();
911−}
912−
913−DirectionalDragAreaPrivate::DirectionalDragAreaPrivate(DirectionalDragArea *q)
914− : q(q)
915− , status(WaitingForTouch)
916− , sceneDistance(0)
917− , touchId(-1)
918− , direction(Direction::Rightwards)
919− , distanceThreshold(0)
920− , distanceThresholdSquared(0.)
921− , maxTime(400)
922− , compositionTime(60)
923− , immediateRecognition(false)
924− , recognitionTimer(nullptr)
925− , timeSource(new RealTimeSource)
926− , activeTouches(timeSource)
927− , monitorOnly(false)
928−{
929−}