2 * Copyright (C) 2013-2014 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>
26#pragma GCC diagnostic push
27#pragma GCC diagnostic ignored "-pedantic"
28#include <private/qquickwindow_p.h>
29#pragma GCC diagnostic pop
32#include "TouchOwnershipEvent.h"
33#include "TouchRegistry.h"
34#include "UnownedTouchEvent.h"
36using namespace UbuntuGestures;
38#if DIRECTIONALDRAGAREA_DEBUG
39#define DDA_DEBUG(msg) qDebug("[DDA] " msg)
40#include "DebugHelpers.h"
43const char *statusToString(DirectionalDragArea::Status status)
45 if (status == DirectionalDragArea::WaitingForTouch) {
46 return "WaitingForTouch";
47 } else if (status == DirectionalDragArea::Undecided) {
55#else // DIRECTIONALDRAGAREA_DEBUG
56#define DDA_DEBUG(msg) do{}while(0)
57#endif // DIRECTIONALDRAGAREA_DEBUG
60DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
62 , m_status(WaitingForTouch)
65 , m_direction(Direction::Rightwards)
68 , m_distanceThreshold(0)
69 , m_distanceThresholdSquared(0.)
71 , m_maxSilenceTime(200)
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)
80 setRecognitionTimer(new Timer(this));
81 m_recognitionTimer->setInterval(60);
82 m_recognitionTimer->setSingleShot(false);
84 m_velocityCalculator = new AxisVelocityCalculator(this);
86 connect(this, &QQuickItem::enabledChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
87 connect(this, &QQuickItem::visibleChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
90Direction::Type DirectionalDragArea::direction() const
95void DirectionalDragArea::setDirection(Direction::Type direction)
97 if (direction != m_direction) {
98 m_direction = direction;
99 Q_EMIT directionChanged(m_direction);
103void DirectionalDragArea::setMaxDeviation(qreal value)
105 if (m_dampedScenePos.maxDelta() != value) {
106 m_dampedScenePos.setMaxDelta(value);
107 Q_EMIT maxDeviationChanged(value);
111qreal DirectionalDragArea::wideningAngle() const
113 return m_wideningAngle;
116void DirectionalDragArea::setWideningAngle(qreal angle)
118 if (angle == m_wideningAngle)
121 m_wideningAngle = angle;
123 // wideningFactor = pow(cosine(angle), 2)
125 qreal angleRadians = angle * M_PI / 180.0;
126 m_wideningFactor = qCos(angleRadians);
127 m_wideningFactor = m_wideningFactor * m_wideningFactor;
130 Q_EMIT wideningAngleChanged(angle);
133void DirectionalDragArea::setDistanceThreshold(qreal value)
135 if (m_distanceThreshold != value) {
136 m_distanceThreshold = value;
137 m_distanceThresholdSquared = m_distanceThreshold * m_distanceThreshold;
138 Q_EMIT distanceThresholdChanged(value);
142void DirectionalDragArea::setMinSpeed(qreal value)
144 if (m_minSpeed != value) {
146 Q_EMIT minSpeedChanged(value);
150void DirectionalDragArea::setMaxSilenceTime(int value)
152 if (m_maxSilenceTime != value) {
153 m_maxSilenceTime = value;
154 Q_EMIT maxSilenceTimeChanged(value);
158void DirectionalDragArea::setCompositionTime(int value)
160 if (m_compositionTime != value) {
161 m_compositionTime = value;
162 Q_EMIT compositionTimeChanged(value);
166void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
169 bool timerWasRunning = false;
170 bool wasSingleShot = false;
172 // 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;
181 m_recognitionTimer = timer;
182 timer->setInterval(interval);
183 timer->setSingleShot(wasSingleShot);
184 connect(timer, &UbuntuGestures::AbstractTimer::timeout,
185 this, &DirectionalDragArea::checkSpeed);
186 if (timerWasRunning) {
187 m_recognitionTimer->start();
191void DirectionalDragArea::setTimeSource(const SharedTimeSource &timeSource)
193 m_timeSource = timeSource;
194 m_velocityCalculator->setTimeSource(timeSource);
195 m_activeTouches.m_timeSource = timeSource;
198qreal DirectionalDragArea::distance() const
200 if (Direction::isHorizontal(m_direction)) {
201 return m_previousPos.x() - m_startPos.x();
203 return m_previousPos.y() - m_startPos.y();
207void DirectionalDragArea::updateSceneDistance()
209 QPointF totalMovement = m_previousScenePos - m_startScenePos;
210 m_sceneDistance = projectOntoDirectionVector(totalMovement);
213qreal DirectionalDragArea::sceneDistance() const
215 return m_sceneDistance;
218qreal DirectionalDragArea::touchX() const
220 return m_previousPos.x();
223qreal DirectionalDragArea::touchY() const
225 return m_previousPos.y();
228qreal DirectionalDragArea::touchSceneX() const
230 return m_previousScenePos.x();
233qreal DirectionalDragArea::touchSceneY() const
235 return m_previousScenePos.y();
238bool DirectionalDragArea::event(QEvent *event)
240 if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
241 touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
243 } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
244 unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
247 return QQuickItem::event(event);
251void DirectionalDragArea::touchOwnershipEvent(TouchOwnershipEvent *event)
253 if (event->gained()) {
255 ids.append(event->touchId());
256 DDA_DEBUG("grabbing touch");
257 grabTouchPoints(ids);
259 // Work around for Qt bug. If we grab a touch that is being used for mouse pointer
260 // emulation it will cause the emulation logic to go nuts.
261 // Thus we have to also grab the mouse in this case.
263 // The fix for this bug has landed in Qt 5.4 (https://codereview.qt-project.org/96887)
264 // TODO: Remove this workaround once we start using Qt 5.4
266 QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(window());
267 if (windowPrivate->touchMouseId == event->touchId() && window()->mouseGrabberItem()) {
268 DDA_DEBUG("removing mouse grabber");
269 window()->mouseGrabberItem()->ungrabMouse();
273 // We still wanna know when it ends for keeping the composition time window up-to-date
274 TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
276 setStatus(WaitingForTouch);
280void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
282 QTouchEvent *event = unownedTouchEvent->touchEvent();
284 Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
286 #if DIRECTIONALDRAGAREA_DEBUG
287 // TODO Consider using qCDebug() when available (Qt 5.2)
288 qDebug() << "[DDA] Unowned" << m_timeSource->msecsSinceReference()
289 << qPrintable(touchEventToString(event));
293 case WaitingForTouch:
297 Q_ASSERT(isEnabled() && isVisible());
298 unownedTouchEvent_undecided(unownedTouchEvent);
300 default: // Recognized:
305 m_activeTouches.update(event);
308void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
310 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
312 qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId
313 << "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
314 "Considering it as released.";
316 TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
317 setStatus(WaitingForTouch);
321 const QPointF &touchScenePos = touchPoint->scenePos();
323 if (touchPoint->state() == Qt::TouchPointReleased) {
324 // touch has ended before recognition concluded
325 DDA_DEBUG("Touch has ended before recognition concluded");
326 TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
327 emitSignalIfTapped();
328 setStatus(WaitingForTouch);
332 m_previousDampedScenePos.setX(m_dampedScenePos.x());
333 m_previousDampedScenePos.setY(m_dampedScenePos.y());
334 m_dampedScenePos.update(touchScenePos);
335 updateVelocityCalculator(touchScenePos);
337 if (!pointInsideAllowedArea()) {
338 DDA_DEBUG("Rejecting gesture because touch point is outside allowed area.");
339 TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
340 // We still wanna know when it ends for keeping the composition time window up-to-date
341 TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
342 setStatus(WaitingForTouch);
346 if (!movingInRightDirection()) {
347 DDA_DEBUG("Rejecting gesture because touch point is moving in the wrong direction.");
348 TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
349 // We still wanna know when it ends for keeping the composition time window up-to-date
350 TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
351 setStatus(WaitingForTouch);
355 setPreviousPos(touchPoint->pos());
356 setPreviousScenePos(touchScenePos);
358 if (isWithinTouchCompositionWindow()) {
359 // There's still time for some new touch to appear and ruin our party as it would be combined
360 // with our m_touchId one and therefore deny the possibility of a single-finger gesture.
361 DDA_DEBUG("Sill within composition window. Let's wait more.");
365 if (movedFarEnough(touchScenePos)) {
366 TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
367 setStatus(Recognized);
369 DDA_DEBUG("Didn't move far enough yet. Let's wait more.");
373void DirectionalDragArea::touchEvent(QTouchEvent *event)
375 // TODO: Consider when more than one touch starts in the same event (although it's not possible
376 // with Mir's android-input). Have to track them all. Consider it a plus/bonus.
378 #if DIRECTIONALDRAGAREA_DEBUG
379 // TODO Consider using qCDebug() when available (Qt 5.2)
380 qDebug() << "[DDA]" << m_timeSource->msecsSinceReference()
381 << qPrintable(touchEventToString(event));
384 if (!isEnabled() || !isVisible()) {
385 QQuickItem::touchEvent(event);
390 case WaitingForTouch:
391 touchEvent_absent(event);
394 touchEvent_undecided(event);
396 default: // Recognized:
397 touchEvent_recognized(event);
401 m_activeTouches.update(event);
404void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
406 // TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
408 if (!event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
409 // Nothing to see here. No touch starting in this event.
413 // to be proven wrong, if that's the case
416 if (isWithinTouchCompositionWindow()) {
417 // too close to the last touch start. So we consider them as starting roughly at the same time.
418 // Can't be a single-touch gesture.
419 #if DIRECTIONALDRAGAREA_DEBUG
420 qDebug("[DDA] A new touch point came in but we're still within time composition window. Ignoring it.");
425 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
427 const QTouchEvent::TouchPoint *newTouchPoint = nullptr;
428 for (int i = 0; i < touchPoints.count() && allGood; ++i) {
429 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
430 if (touchPoint.state() == Qt::TouchPointPressed) {
432 // more than one touch starting in this QTouchEvent. Can't be a single-touch gesture
435 // that's our candidate
436 m_touchId = touchPoint.id();
437 newTouchPoint = &touchPoint;
443 Q_ASSERT(newTouchPoint);
445 m_startPos = newTouchPoint->pos();
446 m_startScenePos = newTouchPoint->scenePos();
447 m_touchId = newTouchPoint->id();
448 m_dampedScenePos.reset(m_startScenePos);
449 m_velocityCalculator->setTrackedPosition(0.);
450 m_velocityCalculator->reset();
451 m_numSamplesOnLastSpeedCheck = 0;
453 setPreviousPos(m_startPos);
454 setPreviousScenePos(m_startScenePos);
455 updateSceneDirectionVector();
457 if (recognitionIsDisabled()) {
458 // Behave like a dumb TouchArea
459 TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
460 setStatus(Recognized);
463 // just monitor the touch points for now.
464 TouchRegistry::instance()->addCandidateOwnerForTouch(m_touchId, this);
466 setStatus(Undecided);
467 // Let the item below have it. We will monitor it and grab it later if a gesture
472 watchPressedTouchPoints(touchPoints);
477void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
479 Q_ASSERT(event->type() == QEvent::TouchBegin);
480 Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
482 // We're not interested in new touch points. We already have our candidate (m_touchId).
483 // But we do want to know when those new touches end for keeping the composition time
486 watchPressedTouchPoints(event->touchPoints());
488 if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) {
489 // multi-finger drags are not accepted
490 DDA_DEBUG("Multi-finger drags are not accepted");
492 TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
493 // We still wanna know when it ends for keeping the composition time window up-to-date
494 TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
496 setStatus(WaitingForTouch);
500void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
502 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
505 qCritical() << "DirectionalDragArea[status=Recognized]: touch " << m_touchId
506 << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
507 "Considering it as released.";
508 setStatus(WaitingForTouch);
510 setPreviousPos(touchPoint->pos());
511 setPreviousScenePos(touchPoint->scenePos());
513 if (touchPoint->state() == Qt::TouchPointReleased) {
514 emitSignalIfTapped();
515 setStatus(WaitingForTouch);
520void DirectionalDragArea::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
522 for (int i = 0; i < touchPoints.count(); ++i) {
523 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
524 if (touchPoint.state() == Qt::TouchPointPressed) {
525 TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), this);
530bool DirectionalDragArea::recognitionIsDisabled() const
532 return distanceThreshold() <= 0 && compositionTime() <= 0;
535void DirectionalDragArea::emitSignalIfTapped()
537 qint64 touchDuration = m_timeSource->msecsSinceReference() - m_activeTouches.touchStartTime(m_touchId);
538 if (touchDuration <= maxTapDuration()) {
543const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event)
545 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
546 const QTouchEvent::TouchPoint *touchPoint = 0;
547 for (int i = 0; i < touchPoints.size(); ++i) {
548 if (touchPoints.at(i).id() == m_touchId) {
549 touchPoint = &touchPoints.at(i);
556bool DirectionalDragArea::pointInsideAllowedArea() const
558 // NB: Using squared values to avoid computing the square root to find
559 // the length totalMovement
561 QPointF totalMovement(m_dampedScenePos.x() - m_startScenePos.x(),
562 m_dampedScenePos.y() - m_startScenePos.y());
564 qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
565 totalMovement.y() * totalMovement.y();
567 if (squaredTotalMovSize == 0.) {
572 qreal projectedMovement = projectOntoDirectionVector(totalMovement);
575 qreal cosineAngleSquared = (projectedMovement * projectedMovement) / squaredTotalMovSize;
578 // angle_between_movement_vector_and_gesture_direction_vector <= widening_angle
579 return cosineAngleSquared >= m_wideningFactor;
582bool DirectionalDragArea::movingInRightDirection() const
584 QPointF movementVector(m_dampedScenePos.x() - m_previousDampedScenePos.x(),
585 m_dampedScenePos.y() - m_previousDampedScenePos.y());
587 qreal scalarProjection = projectOntoDirectionVector(movementVector);
589 return scalarProjection >= 0.;
592bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
594 if (m_distanceThreshold <= 0.) {
595 // distance threshold check is disabled
598 QPointF totalMovement(point.x() - m_startScenePos.x(),
599 point.y() - m_startScenePos.y());
601 qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
602 totalMovement.y() * totalMovement.y();
604 return squaredTotalMovSize > m_distanceThresholdSquared;
608void DirectionalDragArea::checkSpeed()
610 Q_ASSERT(m_status == Undecided);
612 if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
613 qreal speed = qFabs(m_velocityCalculator->calculate());
614 qreal minSpeedMsecs = m_minSpeed / 1000.0;
616 if (speed < minSpeedMsecs) {
617 DDA_DEBUG("Rejecting gesture because it's below minimum speed.");
618 TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
619 TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
620 setStatus(WaitingForTouch);
624 if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) {
625 m_silenceTime += m_recognitionTimer->interval();
627 if (m_silenceTime > m_maxSilenceTime) {
628 DDA_DEBUG("Rejecting gesture because its silence time has been exceeded.");
629 TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
630 TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
631 setStatus(WaitingForTouch);
636 m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
639void DirectionalDragArea::giveUpIfDisabledOrInvisible()
641 if (!isEnabled() || !isVisible()) {
642 if (m_status == Undecided) {
643 TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
644 // We still wanna know when it ends for keeping the composition time window up-to-date
645 TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
648 if (m_status != WaitingForTouch) {
649 DDA_DEBUG("Resetting status because got disabled or made invisible");
650 setStatus(WaitingForTouch);
655void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
657 if (newStatus == m_status)
660 DirectionalDragArea::Status oldStatus = m_status;
662 if (oldStatus == Undecided) {
663 m_recognitionTimer->stop();
666 m_status = newStatus;
667 Q_EMIT statusChanged(m_status);
669 #if DIRECTIONALDRAGAREA_DEBUG
670 qDebug() << "[DDA]" << statusToString(oldStatus) << "->" << statusToString(newStatus);
674 case WaitingForTouch:
675 Q_EMIT draggingChanged(false);
678 m_recognitionTimer->start();
679 Q_EMIT draggingChanged(true);
682 if (oldStatus == WaitingForTouch)
683 Q_EMIT draggingChanged(true);
691void DirectionalDragArea::setPreviousPos(const QPointF &point)
693 bool xChanged = m_previousPos.x() != point.x();
694 bool yChanged = m_previousPos.y() != point.y();
696 m_previousPos = point;
699 Q_EMIT touchXChanged(point.x());
700 if (Direction::isHorizontal(m_direction))
701 Q_EMIT distanceChanged(distance());
705 Q_EMIT touchYChanged(point.y());
706 if (Direction::isVertical(m_direction))
707 Q_EMIT distanceChanged(distance());
711void DirectionalDragArea::setPreviousScenePos(const QPointF &point)
713 bool xChanged = m_previousScenePos.x() != point.x();
714 bool yChanged = m_previousScenePos.y() != point.y();
716 if (!xChanged && !yChanged)
719 qreal oldSceneDistance = sceneDistance();
720 m_previousScenePos = point;
721 updateSceneDistance();
723 if (oldSceneDistance != sceneDistance()) {
724 Q_EMIT sceneDistanceChanged(sceneDistance());
728 Q_EMIT touchSceneXChanged(point.x());
732 Q_EMIT touchSceneYChanged(point.y());
736void DirectionalDragArea::updateVelocityCalculator(const QPointF &scenePos)
738 QPointF totalSceneMovement = scenePos - m_startScenePos;
740 qreal scalarProjection = projectOntoDirectionVector(totalSceneMovement);
742 m_velocityCalculator->setTrackedPosition(scalarProjection);
745bool DirectionalDragArea::isWithinTouchCompositionWindow()
748 compositionTime() > 0 &&
749 !m_activeTouches.isEmpty() &&
750 m_timeSource->msecsSinceReference() <=
751 m_activeTouches.mostRecentStartTime() + (qint64)compositionTime();
754//************************** ActiveTouchesInfo **************************
756DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
757 : m_timeSource(timeSource)
761void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
763 if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
765 #if ACTIVETOUCHESINFO_DEBUG
766 qDebug("[DDA::ActiveTouchesInfo] Nothing to Update");
771 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
772 for (int i = 0; i < touchPoints.count(); ++i) {
773 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
774 if (touchPoint.state() == Qt::TouchPointPressed) {
775 addTouchPoint(touchPoint.id());
776 } else if (touchPoint.state() == Qt::TouchPointReleased) {
777 removeTouchPoint(touchPoint.id());
782#if ACTIVETOUCHESINFO_DEBUG
783QString DirectionalDragArea::ActiveTouchesInfo::toString()
785 QString string = "(";
788 QTextStream stream(&string);
789 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
790 stream << "(id=" << touchInfo->id << ",startTime=" << touchInfo->startTime << ")";
799#endif // ACTIVETOUCHESINFO_DEBUG
801void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(int touchId)
803 ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
804 activeTouchInfo.id = touchId;
805 activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
807 #if ACTIVETOUCHESINFO_DEBUG
808 qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
812qint64 DirectionalDragArea::ActiveTouchesInfo::touchStartTime(int touchId)
816 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
817 if (touchId == touchInfo->id) {
818 result = touchInfo->startTime;
825 Q_ASSERT(result != -1);
829void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(int touchId)
831 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
832 if (touchId == touchInfo->id) {
833 m_touchInfoPool.freeSlot(touchInfo);
840 #if ACTIVETOUCHESINFO_DEBUG
841 qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
845qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
847 Q_ASSERT(!m_touchInfoPool.isEmpty());
849 qint64 highestStartTime = -1;
851 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &activeTouchInfo) {
852 if (activeTouchInfo->startTime > highestStartTime) {
853 highestStartTime = activeTouchInfo->startTime;
858 return highestStartTime;
861void DirectionalDragArea::updateSceneDirectionVector()
863 QPointF localOrigin(0., 0.);
864 QPointF localDirection;
865 switch (m_direction) {
866 case Direction::Upwards:
867 localDirection.rx() = 0.;
868 localDirection.ry() = -1.;
870 case Direction::Downwards:
871 localDirection.rx() = 0.;
872 localDirection.ry() = 1;
874 case Direction::Leftwards:
875 localDirection.rx() = -1.;
876 localDirection.ry() = 0.;
878 default: // Direction::Rightwards:
879 localDirection.rx() = 1.;
880 localDirection.ry() = 0.;
883 QPointF sceneOrigin = mapToScene(localOrigin);
884 QPointF sceneDirection = mapToScene(localDirection);
885 m_sceneDirectionVector = sceneDirection - sceneOrigin;
888qreal DirectionalDragArea::projectOntoDirectionVector(const QPointF &sceneVector) const
890 // same as dot product as m_sceneDirectionVector is a unit vector
891 return sceneVector.x() * m_sceneDirectionVector.x() +
892 sceneVector.y() * m_sceneDirectionVector.y();
895// Because we are defining a new QObject-based class (RecognitionTimer) here.
896#include "DirectionalDragArea.moc"
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