on 8a93021 help sign in
unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp
885 lines 29 kB history raw
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 */
17#define ACTIVETOUCHESINFO_DEBUG 0
18#define DIRECTIONALDRAGAREA_DEBUG 0
20#include "DirectionalDragArea.h"
22#include <QQuickWindow>
23#include <QtCore/qmath.h>
24#include <QScreen>
25#include <QDebug>
27#pragma GCC diagnostic push
28#pragma GCC diagnostic ignored "-pedantic"
29#include <private/qquickwindow_p.h>
30#pragma GCC diagnostic pop
32// local
33#include "TouchOwnershipEvent.h"
34#include "TouchRegistry.h"
35#include "UnownedTouchEvent.h"
37#include "DirectionalDragArea_p.h"
39using namespace UbuntuGestures;
41#if DIRECTIONALDRAGAREA_DEBUG
42#define ddaDebug(params) qDebug().nospace() << "[DDA(" << qPrintable(objectName()) << ")] " << params
43#include "DebugHelpers.h"
45namespace {
46const char *statusToString(DirectionalDragAreaPrivate::Status status)
48 if (status == DirectionalDragAreaPrivate::WaitingForTouch) {
49 return "WaitingForTouch";
50 } else if (status == DirectionalDragAreaPrivate::Undecided) {
51 return "Undecided";
52 } else {
53 return "Recognized";
54 }
57} // namespace {
58#else // DIRECTIONALDRAGAREA_DEBUG
59#define ddaDebug(params) ((void)0)
60#endif // DIRECTIONALDRAGAREA_DEBUG
62DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
63 : QQuickItem(parent)
64 , d(new DirectionalDragAreaPrivate(this))
66 d->setRecognitionTimer(new Timer(this));
67 d->recognitionTimer->setInterval(d->maxTime);
68 d->recognitionTimer->setSingleShot(true);
70 connect(this, &QQuickItem::enabledChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
71 connect(this, &QQuickItem::visibleChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
74Direction::Type DirectionalDragArea::direction() const
76 return d->direction;
79void DirectionalDragArea::setDirection(Direction::Type direction)
81 if (direction != d->direction) {
82 d->direction = direction;
83 Q_EMIT directionChanged(d->direction);
84 }
87void DirectionalDragAreaPrivate::setDistanceThreshold(qreal value)
89 if (distanceThreshold != value) {
90 distanceThreshold = value;
91 distanceThresholdSquared = distanceThreshold * distanceThreshold;
92 }
95void DirectionalDragAreaPrivate::setMaxTime(int value)
97 if (maxTime != value) {
98 maxTime = value;
99 recognitionTimer->setInterval(maxTime);
100 }
103void DirectionalDragAreaPrivate::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
105 int interval = 0;
106 bool timerWasRunning = false;
107 bool wasSingleShot = false;
109 // can be null when called from the constructor
110 if (recognitionTimer) {
111 interval = recognitionTimer->interval();
112 timerWasRunning = recognitionTimer->isRunning();
113 if (recognitionTimer->parent() == this) {
114 delete recognitionTimer;
115 }
116 }
118 recognitionTimer = timer;
119 timer->setInterval(interval);
120 timer->setSingleShot(wasSingleShot);
121 connect(timer, &UbuntuGestures::AbstractTimer::timeout,
122 this, &DirectionalDragAreaPrivate::rejectGesture);
123 if (timerWasRunning) {
124 recognitionTimer->start();
125 }
128void DirectionalDragAreaPrivate::setTimeSource(const SharedTimeSource &timeSource)
130 this->timeSource = timeSource;
131 activeTouches.m_timeSource = timeSource;
134qreal DirectionalDragArea::distance() const
136 if (Direction::isHorizontal(d->direction)) {
137 return d->publicPos.x() - d->startPos.x();
138 } else {
139 return d->publicPos.y() - d->startPos.y();
140 }
143void DirectionalDragAreaPrivate::updateSceneDistance()
145 QPointF totalMovement = publicScenePos - startScenePos;
146 sceneDistance = projectOntoDirectionVector(totalMovement);
149qreal DirectionalDragArea::sceneDistance() const
151 return d->sceneDistance;
154qreal DirectionalDragArea::touchX() const
156 return d->publicPos.x();
159qreal DirectionalDragArea::touchY() const
161 return d->publicPos.y();
164qreal DirectionalDragArea::touchSceneX() const
166 return d->publicScenePos.x();
169qreal DirectionalDragArea::touchSceneY() const
171 return d->publicScenePos.y();
174bool DirectionalDragArea::dragging() const
176 return d->status == DirectionalDragAreaPrivate::Recognized;
179bool DirectionalDragArea::pressed() const
181 return d->status != DirectionalDragAreaPrivate::WaitingForTouch;
184bool DirectionalDragArea::immediateRecognition() const
186 return d->immediateRecognition;
189void DirectionalDragArea::setImmediateRecognition(bool enabled)
191 if (d->immediateRecognition != enabled) {
192 d->immediateRecognition = enabled;
193 Q_EMIT immediateRecognitionChanged(enabled);
194 }
197void DirectionalDragArea::removeTimeConstraints()
199 d->setMaxTime(60 * 60 * 1000);
200 d->compositionTime = 0;
201 ddaDebug("removed time constraints");
204bool DirectionalDragArea::event(QEvent *event)
206 if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
207 d->touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
208 return true;
209 } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
210 d->unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
211 return true;
212 } else {
213 return QQuickItem::event(event);
214 }
217void DirectionalDragAreaPrivate::touchOwnershipEvent(TouchOwnershipEvent *event)
219 if (event->gained()) {
220 QVector<int> ids;
221 ids.append(event->touchId());
222 ddaDebug("grabbing touch");
223 q->grabTouchPoints(ids);
225 // Work around for Qt bug. If we grab a touch that is being used for mouse pointer
226 // emulation it will cause the emulation logic to go nuts.
227 // Thus we have to also grab the mouse in this case.
228 //
229 // The fix for this bug has landed in Qt 5.4 (https://codereview.qt-project.org/96887)
230 // TODO: Remove this workaround once we start using Qt 5.4
231 if (q->window()) {
232 QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(q->window());
233 if (windowPrivate->touchMouseId == event->touchId() && q->window()->mouseGrabberItem()) {
234 ddaDebug("removing mouse grabber");
235 q->window()->mouseGrabberItem()->ungrabMouse();
236 }
237 }
238 } else {
239 // We still wanna know when it ends for keeping the composition time window up-to-date
240 TouchRegistry::instance()->addTouchWatcher(touchId, q);
242 setStatus(WaitingForTouch);
243 }
246void DirectionalDragAreaPrivate::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
248 QTouchEvent *event = unownedTouchEvent->touchEvent();
250 Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
252 ddaDebug("Unowned " << timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
254 switch (status) {
255 case WaitingForTouch:
256 // do nothing
257 break;
258 case Undecided:
259 Q_ASSERT(q->isEnabled() && q->isVisible());
260 unownedTouchEvent_undecided(unownedTouchEvent);
261 break;
262 default: // Recognized:
263 // do nothing
264 break;
265 }
267 activeTouches.update(event);
270void DirectionalDragAreaPrivate::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
272 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
273 if (!touchPoint) {
274 qCritical() << "DirectionalDragArea[status=Undecided]: touch " << touchId
275 << "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
276 "Considering it as released.";
278 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
279 setStatus(WaitingForTouch);
280 return;
281 }
283 const QPointF &touchScenePos = touchPoint->scenePos();
285 if (touchPoint->state() == Qt::TouchPointReleased) {
286 // touch has ended before recognition concluded
287 ddaDebug("Touch has ended before recognition concluded");
288 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
289 setStatus(WaitingForTouch);
290 return;
291 }
293 previousDampedScenePos.setX(dampedScenePos.x());
294 previousDampedScenePos.setY(dampedScenePos.y());
295 dampedScenePos.update(touchScenePos);
297 if (!movingInRightDirection()) {
298 ddaDebug("Rejecting gesture because touch point is moving in the wrong direction.");
299 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
300 // We still wanna know when it ends for keeping the composition time window up-to-date
301 TouchRegistry::instance()->addTouchWatcher(touchId, q);
302 setStatus(WaitingForTouch);
303 return;
304 }
306 if (isWithinTouchCompositionWindow()) {
307 // There's still time for some new touch to appear and ruin our party as it would be combined
308 // with our touchId one and therefore deny the possibility of a single-finger gesture.
309 ddaDebug("Sill within composition window. Let's wait more.");
310 return;
311 }
313 if (movedFarEnoughAlongGestureAxis()) {
314 TouchRegistry::instance()->requestTouchOwnership(touchId, q);
315 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);
324 } else {
325 ddaDebug("Didn't move far enough yet. Let's wait more.");
326 }
329void DirectionalDragArea::touchEvent(QTouchEvent *event)
331 // TODO: Consider when more than one touch starts in the same event (although it's not possible
332 // with Mir's android-input). Have to track them all. Consider it a plus/bonus.
334 ddaDebug(d->timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
336 if (!isEnabled() || !isVisible()) {
337 QQuickItem::touchEvent(event);
338 return;
339 }
341 switch (d->status) {
342 case DirectionalDragAreaPrivate::WaitingForTouch:
343 d->touchEvent_absent(event);
344 break;
345 case DirectionalDragAreaPrivate::Undecided:
346 d->touchEvent_undecided(event);
347 break;
348 default: // Recognized:
349 d->touchEvent_recognized(event);
350 break;
351 }
353 d->activeTouches.update(event);
356void DirectionalDragAreaPrivate::touchEvent_absent(QTouchEvent *event)
358 // TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
360 if (!event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
361 // Nothing to see here. No touch starting in this event.
362 return;
363 }
365 // to be proven wrong, if that's the case
366 bool allGood = true;
368 if (isWithinTouchCompositionWindow()) {
369 // too close to the last touch start. So we consider them as starting roughly at the same time.
370 // Can't be a single-touch gesture.
371 ddaDebug("A new touch point came in but we're still within time composition window. Ignoring it.");
372 allGood = false;
373 }
375 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
377 const QTouchEvent::TouchPoint *newTouchPoint = nullptr;
378 for (int i = 0; i < touchPoints.count() && allGood; ++i) {
379 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
380 if (touchPoint.state() == Qt::TouchPointPressed) {
381 if (newTouchPoint) {
382 // more than one touch starting in this QTouchEvent. Can't be a single-touch gesture
383 allGood = false;
384 } else {
385 // that's our candidate
386 newTouchPoint = &touchPoint;
387 }
388 }
389 }
391 if (allGood) {
392 allGood = sanityCheckRecognitionProperties();
393 if (!allGood) {
394 qWarning("DirectionalDragArea: recognition properties are wrongly set. Gesture recognition"
395 " is impossible");
396 }
397 }
399 if (allGood) {
400 Q_ASSERT(newTouchPoint);
402 startPos = newTouchPoint->pos();
403 startScenePos = newTouchPoint->scenePos();
404 touchId = newTouchPoint->id();
405 dampedScenePos.reset(startScenePos);
406 setPublicPos(startPos);
408 setPublicScenePos(startScenePos);
409 updateSceneDirectionVector();
411 if (recognitionIsDisabled()) {
412 // Behave like a dumb TouchArea
413 ddaDebug("Gesture recognition is disabled. Requesting touch ownership immediately.");
414 TouchRegistry::instance()->requestTouchOwnership(touchId, q);
415 setStatus(Recognized);
416 event->accept();
417 } else {
418 // just monitor the touch points for now.
419 TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, q);
421 setStatus(Undecided);
422 // Let the item below have it. We will monitor it and grab it later if a gesture
423 // gets recognized.
424 event->ignore();
425 }
426 } else {
427 watchPressedTouchPoints(touchPoints);
428 event->ignore();
429 }
432void DirectionalDragAreaPrivate::touchEvent_undecided(QTouchEvent *event)
434 Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
436 // We're not interested in new touch points. We already have our candidate (touchId).
437 // But we do want to know when those new touches end for keeping the composition time
438 // window up-to-date
439 event->ignore();
440 watchPressedTouchPoints(event->touchPoints());
442 if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) {
443 // multi-finger drags are not accepted
444 ddaDebug("Multi-finger drags are not accepted");
446 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
447 // We still wanna know when it ends for keeping the composition time window up-to-date
448 TouchRegistry::instance()->addTouchWatcher(touchId, q);
450 setStatus(WaitingForTouch);
451 }
454void DirectionalDragAreaPrivate::touchEvent_recognized(QTouchEvent *event)
456 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
458 if (!touchPoint) {
459 qCritical() << "DirectionalDragArea[status=Recognized]: touch " << touchId
460 << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
461 "Considering it as released.";
462 setStatus(WaitingForTouch);
463 } else {
464 setPublicPos(touchPoint->pos());
465 setPublicScenePos(touchPoint->scenePos());
467 if (touchPoint->state() == Qt::TouchPointReleased) {
468 setStatus(WaitingForTouch);
469 }
470 }
473void DirectionalDragAreaPrivate::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
475 for (int i = 0; i < touchPoints.count(); ++i) {
476 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
477 if (touchPoint.state() == Qt::TouchPointPressed) {
478 TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), q);
479 }
480 }
483bool DirectionalDragAreaPrivate::recognitionIsDisabled() const
485 return immediateRecognition || (distanceThreshold <= 0 && compositionTime <= 0);
488bool DirectionalDragAreaPrivate::sanityCheckRecognitionProperties()
490 return recognitionIsDisabled()
491 || (distanceThreshold < maxDistance && compositionTime < maxTime);
494const QTouchEvent::TouchPoint *DirectionalDragAreaPrivate::fetchTargetTouchPoint(QTouchEvent *event)
496 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
497 const QTouchEvent::TouchPoint *touchPoint = 0;
498 for (int i = 0; i < touchPoints.size(); ++i) {
499 if (touchPoints.at(i).id() == touchId) {
500 touchPoint = &touchPoints.at(i);
501 break;
502 }
503 }
504 return touchPoint;
507bool DirectionalDragAreaPrivate::movingInRightDirection() const
509 if (direction == Direction::Horizontal) {
510 return true;
511 } else {
512 QPointF movementVector(dampedScenePos.x() - previousDampedScenePos.x(),
513 dampedScenePos.y() - previousDampedScenePos.y());
515 qreal scalarProjection = projectOntoDirectionVector(movementVector);
517 return scalarProjection >= 0.;
518 }
521bool DirectionalDragAreaPrivate::movedFarEnoughAlongGestureAxis() const
523 if (distanceThreshold <= 0.) {
524 // distance threshold check is disabled
525 return true;
526 } else {
527 QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
528 dampedScenePos.y() - startScenePos.y());
530 qreal scalarProjection = projectOntoDirectionVector(totalMovement);
532 ddaDebug(" movedFarEnoughAlongGestureAxis: scalarProjection=" << scalarProjection
533 << ", distanceThreshold=" << distanceThreshold);
535 if (direction == Direction::Horizontal) {
536 return qAbs(scalarProjection) > distanceThreshold;
537 } else {
538 return scalarProjection > distanceThreshold;
539 }
540 }
543bool DirectionalDragAreaPrivate::isPastMaxDistance() const
545 QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
546 dampedScenePos.y() - startScenePos.y());
548 qreal squaredDistance = totalMovement.x()*totalMovement.x() + totalMovement.y()*totalMovement.y();
549 return squaredDistance > maxDistance*maxDistance;
552void DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible()
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);
559 }
561 if (status != WaitingForTouch) {
562 ddaDebug("Resetting status because got disabled or made invisible");
563 setStatus(WaitingForTouch);
564 }
565 }
568void DirectionalDragAreaPrivate::rejectGesture()
570 if (status == Undecided) {
571 ddaDebug("Rejecting gesture because it's taking too long to drag beyond the threshold.");
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);
577 setStatus(WaitingForTouch);
578 }
581void DirectionalDragAreaPrivate::setStatus(Status newStatus)
583 if (newStatus == status)
584 return;
586 Status oldStatus = status;
588 if (oldStatus == Undecided) {
589 recognitionTimer->stop();
590 }
592 status = newStatus;
593 Q_EMIT statusChanged(status);
595 ddaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
597 switch (newStatus) {
598 case WaitingForTouch:
599 if (oldStatus == Recognized) {
600 Q_EMIT q->draggingChanged(false);
601 }
602 Q_EMIT q->pressedChanged(false);
603 break;
604 case Undecided:
605 recognitionTimer->start();
606 Q_EMIT q->pressedChanged(true);
607 break;
608 case Recognized:
609 Q_EMIT q->draggingChanged(true);
610 break;
611 default:
612 // no-op
613 break;
614 }
617void DirectionalDragAreaPrivate::setPublicPos(const QPointF &point)
619 bool xChanged = publicPos.x() != point.x();
620 bool yChanged = publicPos.y() != point.y();
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);
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 }
643 if (xChanged) {
644 Q_EMIT q->touchXChanged(publicPos.x());
645 if (Direction::isHorizontal(direction))
646 Q_EMIT q->distanceChanged(q->distance());
647 }
649 if (yChanged) {
650 Q_EMIT q->touchYChanged(publicPos.y());
651 if (Direction::isVertical(direction))
652 Q_EMIT q->distanceChanged(q->distance());
653 }
656void DirectionalDragAreaPrivate::setPublicScenePos(const QPointF &point)
658 bool xChanged = publicScenePos.x() != point.x();
659 bool yChanged = publicScenePos.y() != point.y();
661 if (!xChanged && !yChanged)
662 return;
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);
668 qreal oldSceneDistance = sceneDistance;
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 }
687 updateSceneDistance();
689 if (oldSceneDistance != sceneDistance) {
690 Q_EMIT q->sceneDistanceChanged(sceneDistance);
691 }
693 if (xChanged) {
694 Q_EMIT q->touchSceneXChanged(publicScenePos.x());
695 }
697 if (yChanged) {
698 Q_EMIT q->touchSceneYChanged(publicScenePos.y());
699 }
702bool DirectionalDragAreaPrivate::isWithinTouchCompositionWindow()
704 return
705 compositionTime > 0 &&
706 !activeTouches.isEmpty() &&
707 timeSource->msecsSinceReference() <=
708 activeTouches.mostRecentStartTime() + (qint64)compositionTime;
711void DirectionalDragArea::itemChange(ItemChange change, const ItemChangeData &value)
713 if (change == QQuickItem::ItemSceneChange) {
714 if (value.window != nullptr) {
715 // TODO: Handle window->screen() changes (ie window changing screens)
716 qreal pixelsPerMm = value.window->screen()->physicalDotsPerInch() / 25.4;
717 d->setPixelsPerMm(pixelsPerMm);
718 }
719 }
722void DirectionalDragAreaPrivate::setPixelsPerMm(qreal pixelsPerMm)
724 dampedScenePos.setMaxDelta(1. * pixelsPerMm);
725 setDistanceThreshold(4. * pixelsPerMm);
726 maxDistance = 10. * pixelsPerMm;
729//************************** ActiveTouchesInfo **************************
731ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
732 : m_timeSource(timeSource)
736void ActiveTouchesInfo::update(QTouchEvent *event)
738 if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
739 // nothing to update
740 #if ACTIVETOUCHESINFO_DEBUG
741 qDebug("[DDA::ActiveTouchesInfo] Nothing to Update");
742 #endif
743 return;
744 }
746 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
747 for (int i = 0; i < touchPoints.count(); ++i) {
748 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
749 if (touchPoint.state() == Qt::TouchPointPressed) {
750 addTouchPoint(touchPoint.id());
751 } else if (touchPoint.state() == Qt::TouchPointReleased) {
752 removeTouchPoint(touchPoint.id());
753 }
754 }
757#if ACTIVETOUCHESINFO_DEBUG
758QString ActiveTouchesInfo::toString()
760 QString string = "(";
762 {
763 QTextStream stream(&string);
764 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
765 stream << "(id=" << touchInfo->id << ",startTime=" << touchInfo->startTime << ")";
766 return true;
767 });
768 }
770 string.append(")");
772 return string;
774#endif // ACTIVETOUCHESINFO_DEBUG
776void ActiveTouchesInfo::addTouchPoint(int touchId)
778 ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
779 activeTouchInfo.id = touchId;
780 activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
782 #if ACTIVETOUCHESINFO_DEBUG
783 qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
784 #endif
787qint64 ActiveTouchesInfo::touchStartTime(int touchId)
789 qint64 result = -1;
791 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
792 if (touchId == touchInfo->id) {
793 result = touchInfo->startTime;
794 return false;
795 } else {
796 return true;
797 }
798 });
800 Q_ASSERT(result != -1);
801 return result;
804void ActiveTouchesInfo::removeTouchPoint(int touchId)
806 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
807 if (touchId == touchInfo->id) {
808 m_touchInfoPool.freeSlot(touchInfo);
809 return false;
810 } else {
811 return true;
812 }
813 });
815 #if ACTIVETOUCHESINFO_DEBUG
816 qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
817 #endif
820qint64 ActiveTouchesInfo::mostRecentStartTime()
822 Q_ASSERT(!m_touchInfoPool.isEmpty());
824 qint64 highestStartTime = -1;
826 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &activeTouchInfo) {
827 if (activeTouchInfo->startTime > highestStartTime) {
828 highestStartTime = activeTouchInfo->startTime;
829 }
830 return true;
831 });
833 return highestStartTime;
836void DirectionalDragAreaPrivate::updateSceneDirectionVector()
838 QPointF localOrigin(0., 0.);
839 QPointF localDirection;
840 switch (direction) {
841 case Direction::Upwards:
842 localDirection.rx() = 0.;
843 localDirection.ry() = -1.;
844 break;
845 case Direction::Downwards:
846 localDirection.rx() = 0.;
847 localDirection.ry() = 1;
848 break;
849 case Direction::Leftwards:
850 localDirection.rx() = -1.;
851 localDirection.ry() = 0.;
852 break;
853 default: // Direction::Rightwards || Direction.Horizontal
854 localDirection.rx() = 1.;
855 localDirection.ry() = 0.;
856 break;
857 }
858 QPointF sceneOrigin = q->mapToScene(localOrigin);
859 QPointF sceneDirection = q->mapToScene(localDirection);
860 sceneDirectionVector = sceneDirection - sceneOrigin;
863qreal DirectionalDragAreaPrivate::projectOntoDirectionVector(const QPointF &sceneVector) const
865 // same as dot product as sceneDirectionVector is a unit vector
866 return sceneVector.x() * sceneDirectionVector.x() +
867 sceneVector.y() * sceneDirectionVector.y();
870DirectionalDragAreaPrivate::DirectionalDragAreaPrivate(DirectionalDragArea *q)
871 : q(q)
872 , status(WaitingForTouch)
873 , sceneDistance(0)
874 , touchId(-1)
875 , direction(Direction::Rightwards)
876 , distanceThreshold(0)
877 , distanceThresholdSquared(0.)
878 , maxTime(400)
879 , compositionTime(60)
880 , immediateRecognition(false)
881 , recognitionTimer(nullptr)
882 , timeSource(new RealTimeSource)
883 , activeTouches(timeSource)