2 * Copyright (C) 2013-2015 Canonical, Ltd.
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.
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.
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/>.
17#define ACTIVETOUCHESINFO_DEBUG 0
18#define DIRECTIONALDRAGAREA_DEBUG 0
20#include "DirectionalDragArea.h"
22#include <QQuickWindow>
23#include <QtCore/qmath.h>
27#pragma GCC diagnostic push
28#pragma GCC diagnostic ignored "-pedantic"
29#include <private/qquickwindow_p.h>
30#pragma GCC diagnostic pop
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"
46const char *statusToString(DirectionalDragAreaPrivate::Status status)
48 if (status == DirectionalDragAreaPrivate::WaitingForTouch) {
49 return "WaitingForTouch";
50 } else if (status == DirectionalDragAreaPrivate::Undecided) {
58#else // DIRECTIONALDRAGAREA_DEBUG
59#define ddaDebug(params) ((void)0)
60#endif // DIRECTIONALDRAGAREA_DEBUG
62DirectionalDragArea::DirectionalDragArea(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
79void DirectionalDragArea::setDirection(Direction::Type direction)
81 if (direction != d->direction) {
82 d->direction = direction;
83 Q_EMIT directionChanged(d->direction);
87void DirectionalDragAreaPrivate::setDistanceThreshold(qreal value)
89 if (distanceThreshold != value) {
90 distanceThreshold = value;
91 distanceThresholdSquared = distanceThreshold * distanceThreshold;
95void DirectionalDragAreaPrivate::setMaxTime(int value)
97 if (maxTime != value) {
99 recognitionTimer->setInterval(maxTime);
103void DirectionalDragAreaPrivate::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
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;
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();
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();
139 return d->publicPos.y() - d->startPos.y();
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::immediateRecognition() const
181 return d->immediateRecognition;
184void DirectionalDragArea::setImmediateRecognition(bool enabled)
186 if (d->immediateRecognition != enabled) {
187 d->immediateRecognition = enabled;
188 Q_EMIT immediateRecognitionChanged(enabled);
192bool DirectionalDragArea::event(QEvent *event)
194 if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
195 d->touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
197 } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
198 d->unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
201 return QQuickItem::event(event);
205void DirectionalDragAreaPrivate::touchOwnershipEvent(TouchOwnershipEvent *event)
207 if (event->gained()) {
209 ids.append(event->touchId());
210 ddaDebug("grabbing touch");
211 q->grabTouchPoints(ids);
213 // Work around for Qt bug. If we grab a touch that is being used for mouse pointer
214 // emulation it will cause the emulation logic to go nuts.
215 // Thus we have to also grab the mouse in this case.
217 // The fix for this bug has landed in Qt 5.4 (https://codereview.qt-project.org/96887)
218 // TODO: Remove this workaround once we start using Qt 5.4
220 QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(q->window());
221 if (windowPrivate->touchMouseId == event->touchId() && q->window()->mouseGrabberItem()) {
222 ddaDebug("removing mouse grabber");
223 q->window()->mouseGrabberItem()->ungrabMouse();
227 // We still wanna know when it ends for keeping the composition time window up-to-date
228 TouchRegistry::instance()->addTouchWatcher(touchId, q);
230 setStatus(WaitingForTouch);
234void DirectionalDragAreaPrivate::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
236 QTouchEvent *event = unownedTouchEvent->touchEvent();
238 Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
240 ddaDebug("Unowned " << timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
243 case WaitingForTouch:
247 Q_ASSERT(q->isEnabled() && q->isVisible());
248 unownedTouchEvent_undecided(unownedTouchEvent);
250 default: // Recognized:
255 activeTouches.update(event);
258void DirectionalDragAreaPrivate::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
260 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
262 qCritical() << "DirectionalDragArea[status=Undecided]: touch " << touchId
263 << "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
264 "Considering it as released.";
266 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
267 setStatus(WaitingForTouch);
271 const QPointF &touchScenePos = touchPoint->scenePos();
273 if (touchPoint->state() == Qt::TouchPointReleased) {
274 // touch has ended before recognition concluded
275 ddaDebug("Touch has ended before recognition concluded");
276 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
277 setStatus(WaitingForTouch);
281 previousDampedScenePos.setX(dampedScenePos.x());
282 previousDampedScenePos.setY(dampedScenePos.y());
283 dampedScenePos.update(touchScenePos);
285 if (!movingInRightDirection()) {
286 ddaDebug("Rejecting gesture because touch point is moving in the wrong direction.");
287 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
288 // We still wanna know when it ends for keeping the composition time window up-to-date
289 TouchRegistry::instance()->addTouchWatcher(touchId, q);
290 setStatus(WaitingForTouch);
294 if (isWithinTouchCompositionWindow()) {
295 // There's still time for some new touch to appear and ruin our party as it would be combined
296 // with our touchId one and therefore deny the possibility of a single-finger gesture.
297 ddaDebug("Sill within composition window. Let's wait more.");
301 if (movedFarEnoughAlongGestureAxis()) {
302 TouchRegistry::instance()->requestTouchOwnership(touchId, q);
303 setStatus(Recognized);
304 setPublicPos(touchPoint->pos());
305 setPublicScenePos(touchScenePos);
306 } else if (isPastMaxDistance()) {
307 ddaDebug("Rejecting gesture because it went farther than maxDistance without getting recognized.");
308 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
309 // We still wanna know when it ends for keeping the composition time window up-to-date
310 TouchRegistry::instance()->addTouchWatcher(touchId, q);
311 setStatus(WaitingForTouch);
313 ddaDebug("Didn't move far enough yet. Let's wait more.");
317void DirectionalDragArea::touchEvent(QTouchEvent *event)
319 // TODO: Consider when more than one touch starts in the same event (although it's not possible
320 // with Mir's android-input). Have to track them all. Consider it a plus/bonus.
322 ddaDebug(d->timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
324 if (!isEnabled() || !isVisible()) {
325 QQuickItem::touchEvent(event);
330 case DirectionalDragAreaPrivate::WaitingForTouch:
331 d->touchEvent_absent(event);
333 case DirectionalDragAreaPrivate::Undecided:
334 d->touchEvent_undecided(event);
336 default: // Recognized:
337 d->touchEvent_recognized(event);
341 d->activeTouches.update(event);
344void DirectionalDragAreaPrivate::touchEvent_absent(QTouchEvent *event)
346 // TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
348 if (!event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
349 // Nothing to see here. No touch starting in this event.
353 // to be proven wrong, if that's the case
356 if (isWithinTouchCompositionWindow()) {
357 // too close to the last touch start. So we consider them as starting roughly at the same time.
358 // Can't be a single-touch gesture.
359 ddaDebug("A new touch point came in but we're still within time composition window. Ignoring it.");
363 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
365 const QTouchEvent::TouchPoint *newTouchPoint = nullptr;
366 for (int i = 0; i < touchPoints.count() && allGood; ++i) {
367 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
368 if (touchPoint.state() == Qt::TouchPointPressed) {
370 // more than one touch starting in this QTouchEvent. Can't be a single-touch gesture
373 // that's our candidate
374 touchId = touchPoint.id();
375 newTouchPoint = &touchPoint;
381 allGood = sanityCheckRecognitionProperties();
383 qWarning("DirectionalDragArea: recognition properties are wrongly set. Gesture recognition"
389 Q_ASSERT(newTouchPoint);
391 startPos = newTouchPoint->pos();
392 startScenePos = newTouchPoint->scenePos();
393 touchId = newTouchPoint->id();
394 dampedScenePos.reset(startScenePos);
395 setPublicPos(startPos);
397 setPublicScenePos(startScenePos);
398 updateSceneDirectionVector();
400 if (recognitionIsDisabled()) {
401 // Behave like a dumb TouchArea
402 ddaDebug("Gesture recognition is disabled. Requesting touch ownership immediately.");
403 TouchRegistry::instance()->requestTouchOwnership(touchId, q);
404 setStatus(Recognized);
407 // just monitor the touch points for now.
408 TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, q);
410 setStatus(Undecided);
411 // Let the item below have it. We will monitor it and grab it later if a gesture
416 watchPressedTouchPoints(touchPoints);
421void DirectionalDragAreaPrivate::touchEvent_undecided(QTouchEvent *event)
423 Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
425 // We're not interested in new touch points. We already have our candidate (touchId).
426 // But we do want to know when those new touches end for keeping the composition time
429 watchPressedTouchPoints(event->touchPoints());
431 if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) {
432 // multi-finger drags are not accepted
433 ddaDebug("Multi-finger drags are not accepted");
435 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
436 // We still wanna know when it ends for keeping the composition time window up-to-date
437 TouchRegistry::instance()->addTouchWatcher(touchId, q);
439 setStatus(WaitingForTouch);
443void DirectionalDragAreaPrivate::touchEvent_recognized(QTouchEvent *event)
445 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
448 qCritical() << "DirectionalDragArea[status=Recognized]: touch " << touchId
449 << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
450 "Considering it as released.";
451 setStatus(WaitingForTouch);
453 setPublicPos(touchPoint->pos());
454 setPublicScenePos(touchPoint->scenePos());
456 if (touchPoint->state() == Qt::TouchPointReleased) {
457 setStatus(WaitingForTouch);
462void DirectionalDragAreaPrivate::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
464 for (int i = 0; i < touchPoints.count(); ++i) {
465 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
466 if (touchPoint.state() == Qt::TouchPointPressed) {
467 TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), q);
472bool DirectionalDragAreaPrivate::recognitionIsDisabled() const
474 return immediateRecognition || (distanceThreshold <= 0 && compositionTime <= 0);
477bool DirectionalDragAreaPrivate::sanityCheckRecognitionProperties()
479 return recognitionIsDisabled()
480 || (distanceThreshold < maxDistance && compositionTime < maxTime);
483const QTouchEvent::TouchPoint *DirectionalDragAreaPrivate::fetchTargetTouchPoint(QTouchEvent *event)
485 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
486 const QTouchEvent::TouchPoint *touchPoint = 0;
487 for (int i = 0; i < touchPoints.size(); ++i) {
488 if (touchPoints.at(i).id() == touchId) {
489 touchPoint = &touchPoints.at(i);
496bool DirectionalDragAreaPrivate::movingInRightDirection() const
498 if (direction == Direction::Horizontal) {
501 QPointF movementVector(dampedScenePos.x() - previousDampedScenePos.x(),
502 dampedScenePos.y() - previousDampedScenePos.y());
504 qreal scalarProjection = projectOntoDirectionVector(movementVector);
506 return scalarProjection >= 0.;
510bool DirectionalDragAreaPrivate::movedFarEnoughAlongGestureAxis() const
512 if (distanceThreshold <= 0.) {
513 // distance threshold check is disabled
516 QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
517 dampedScenePos.y() - startScenePos.y());
519 qreal scalarProjection = projectOntoDirectionVector(totalMovement);
521 ddaDebug(" movedFarEnoughAlongGestureAxis: scalarProjection=" << scalarProjection
522 << ", distanceThreshold=" << distanceThreshold);
524 if (direction == Direction::Horizontal) {
525 return qAbs(scalarProjection) > distanceThreshold;
527 return scalarProjection > distanceThreshold;
532bool DirectionalDragAreaPrivate::isPastMaxDistance() const
534 QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
535 dampedScenePos.y() - startScenePos.y());
537 qreal squaredDistance = totalMovement.x()*totalMovement.x() + totalMovement.y()*totalMovement.y();
538 return squaredDistance > maxDistance*maxDistance;
541void DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible()
543 if (!q->isEnabled() || !q->isVisible()) {
544 if (status == Undecided) {
545 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
546 // We still wanna know when it ends for keeping the composition time window up-to-date
547 TouchRegistry::instance()->addTouchWatcher(touchId, q);
550 if (status != WaitingForTouch) {
551 ddaDebug("Resetting status because got disabled or made invisible");
552 setStatus(WaitingForTouch);
557void DirectionalDragAreaPrivate::rejectGesture()
559 if (status == Undecided) {
560 ddaDebug("Rejecting gesture because it's taking too long to drag beyond the threshold.");
562 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
563 // We still wanna know when it ends for keeping the composition time window up-to-date
564 TouchRegistry::instance()->addTouchWatcher(touchId, q);
566 setStatus(WaitingForTouch);
570void DirectionalDragAreaPrivate::setStatus(Status newStatus)
572 if (newStatus == status)
575 Status oldStatus = status;
577 if (oldStatus == Undecided) {
578 recognitionTimer->stop();
582 Q_EMIT statusChanged(status);
584 ddaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
587 case WaitingForTouch:
588 if (oldStatus == Recognized) {
589 Q_EMIT q->draggingChanged(false);
593 recognitionTimer->start();
596 Q_EMIT q->draggingChanged(true);
604void DirectionalDragAreaPrivate::setPublicPos(const QPointF &point)
606 bool xChanged = publicPos.x() != point.x();
607 bool yChanged = publicPos.y() != point.y();
609 // Public position should not get updated while the gesture is still being recognized
610 // (ie, Undecided status).
611 Q_ASSERT(status == WaitingForTouch || status == Recognized);
613 if (status == Recognized && !recognitionIsDisabled()) {
614 // When the gesture finally gets recognized, the finger will likely be
615 // reasonably far from the edge. If we made the contentX immediately
616 // follow the finger position it would be visually unpleasant as it
617 // would appear right next to the user's finger out of nowhere (ie,
618 // it would jump). Instead, we make contentX go towards the user's
619 // finger in several steps. ie., in an animated way.
620 QPointF delta = point - publicPos;
621 // the trick is not to go all the way (1.0) as it would cause a sudden jump
622 publicPos.rx() += 0.4 * delta.x();
623 publicPos.ry() += 0.4 * delta.y();
625 // no smoothing when initializing or if gesture recognition was immediate as there will
631 Q_EMIT q->touchXChanged(publicPos.x());
632 if (Direction::isHorizontal(direction))
633 Q_EMIT q->distanceChanged(q->distance());
637 Q_EMIT q->touchYChanged(publicPos.y());
638 if (Direction::isVertical(direction))
639 Q_EMIT q->distanceChanged(q->distance());
643void DirectionalDragAreaPrivate::setPublicScenePos(const QPointF &point)
645 bool xChanged = publicScenePos.x() != point.x();
646 bool yChanged = publicScenePos.y() != point.y();
648 if (!xChanged && !yChanged)
651 // Public position should not get updated while the gesture is still being recognized
652 // (ie, Undecided status).
653 Q_ASSERT(status == WaitingForTouch || status == Recognized);
655 qreal oldSceneDistance = sceneDistance;
657 if (status == Recognized && !recognitionIsDisabled()) {
658 // When the gesture finally gets recognized, the finger will likely be
659 // reasonably far from the edge. If we made the contentX immediately
660 // follow the finger position it would be visually unpleasant as it
661 // would appear right next to the user's finger out of nowhere (ie,
662 // it would jump). Instead, we make contentX go towards the user's
663 // finger in several steps. ie., in an animated way.
664 QPointF delta = point - publicScenePos;
665 // the trick is not to go all the way (1.0) as it would cause a sudden jump
666 publicScenePos.rx() += 0.4 * delta.x();
667 publicScenePos.ry() += 0.4 * delta.y();
669 // no smoothing when initializing or if gesture recognition was immediate as there will
671 publicScenePos = point;
674 updateSceneDistance();
676 if (oldSceneDistance != sceneDistance) {
677 Q_EMIT q->sceneDistanceChanged(sceneDistance);
681 Q_EMIT q->touchSceneXChanged(publicScenePos.x());
685 Q_EMIT q->touchSceneYChanged(publicScenePos.y());
689bool DirectionalDragAreaPrivate::isWithinTouchCompositionWindow()
692 compositionTime > 0 &&
693 !activeTouches.isEmpty() &&
694 timeSource->msecsSinceReference() <=
695 activeTouches.mostRecentStartTime() + (qint64)compositionTime;
698void DirectionalDragArea::itemChange(ItemChange change, const ItemChangeData &value)
700 if (change == QQuickItem::ItemSceneChange) {
701 if (value.window != nullptr) {
702 // TODO: Handle window->screen() changes (ie window changing screens)
703 qreal pixelsPerMm = value.window->screen()->physicalDotsPerInch() / 25.4;
704 d->setPixelsPerMm(pixelsPerMm);
709void DirectionalDragAreaPrivate::setPixelsPerMm(qreal pixelsPerMm)
711 dampedScenePos.setMaxDelta(1. * pixelsPerMm);
712 setDistanceThreshold(4. * pixelsPerMm);
713 maxDistance = 10. * pixelsPerMm;
716//************************** ActiveTouchesInfo **************************
718ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
719 : m_timeSource(timeSource)
723void ActiveTouchesInfo::update(QTouchEvent *event)
725 if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
727 #if ACTIVETOUCHESINFO_DEBUG
728 qDebug("[DDA::ActiveTouchesInfo] Nothing to Update");
733 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
734 for (int i = 0; i < touchPoints.count(); ++i) {
735 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
736 if (touchPoint.state() == Qt::TouchPointPressed) {
737 addTouchPoint(touchPoint.id());
738 } else if (touchPoint.state() == Qt::TouchPointReleased) {
739 removeTouchPoint(touchPoint.id());
744#if ACTIVETOUCHESINFO_DEBUG
745QString ActiveTouchesInfo::toString()
747 QString string = "(";
750 QTextStream stream(&string);
751 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
752 stream << "(id=" << touchInfo->id << ",startTime=" << touchInfo->startTime << ")";
761#endif // ACTIVETOUCHESINFO_DEBUG
763void ActiveTouchesInfo::addTouchPoint(int touchId)
765 ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
766 activeTouchInfo.id = touchId;
767 activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
769 #if ACTIVETOUCHESINFO_DEBUG
770 qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
774qint64 ActiveTouchesInfo::touchStartTime(int touchId)
778 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
779 if (touchId == touchInfo->id) {
780 result = touchInfo->startTime;
787 Q_ASSERT(result != -1);
791void ActiveTouchesInfo::removeTouchPoint(int touchId)
793 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
794 if (touchId == touchInfo->id) {
795 m_touchInfoPool.freeSlot(touchInfo);
802 #if ACTIVETOUCHESINFO_DEBUG
803 qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
807qint64 ActiveTouchesInfo::mostRecentStartTime()
809 Q_ASSERT(!m_touchInfoPool.isEmpty());
811 qint64 highestStartTime = -1;
813 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &activeTouchInfo) {
814 if (activeTouchInfo->startTime > highestStartTime) {
815 highestStartTime = activeTouchInfo->startTime;
820 return highestStartTime;
823void DirectionalDragAreaPrivate::updateSceneDirectionVector()
825 QPointF localOrigin(0., 0.);
826 QPointF localDirection;
828 case Direction::Upwards:
829 localDirection.rx() = 0.;
830 localDirection.ry() = -1.;
832 case Direction::Downwards:
833 localDirection.rx() = 0.;
834 localDirection.ry() = 1;
836 case Direction::Leftwards:
837 localDirection.rx() = -1.;
838 localDirection.ry() = 0.;
840 default: // Direction::Rightwards || Direction.Horizontal
841 localDirection.rx() = 1.;
842 localDirection.ry() = 0.;
845 QPointF sceneOrigin = q->mapToScene(localOrigin);
846 QPointF sceneDirection = q->mapToScene(localDirection);
847 sceneDirectionVector = sceneDirection - sceneOrigin;
850qreal DirectionalDragAreaPrivate::projectOntoDirectionVector(const QPointF &sceneVector) const
852 // same as dot product as sceneDirectionVector is a unit vector
853 return sceneVector.x() * sceneDirectionVector.x() +
854 sceneVector.y() * sceneDirectionVector.y();
857DirectionalDragAreaPrivate::DirectionalDragAreaPrivate(DirectionalDragArea *q)
859 , status(WaitingForTouch)
862 , direction(Direction::Rightwards)
863 , distanceThreshold(0)
864 , distanceThresholdSquared(0.)
866 , compositionTime(60)
867 , immediateRecognition(false)
868 , recognitionTimer(nullptr)
869 , timeSource(new RealTimeSource)
870 , activeTouches(timeSource)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list