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 <QtCore/qmath.h>
26#include "TouchOwnershipEvent.h"
27#include "TouchRegistry.h"
28#include "UnownedTouchEvent.h"
30using namespace UbuntuGestures;
32#if DIRECTIONALDRAGAREA_DEBUG
33#define DDA_DEBUG(msg) qDebug("[DDA] " msg)
34#include "DebugHelpers.h"
37const char *statusToString(DirectionalDragArea::Status status)
39 if (status == DirectionalDragArea::WaitingForTouch) {
40 return "WaitingForTouch";
41 } else if (status == DirectionalDragArea::Undecided) {
49#else // DIRECTIONALDRAGAREA_DEBUG
50#define DDA_DEBUG(msg) do{}while(0)
51#endif // DIRECTIONALDRAGAREA_DEBUG
54DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
56 , m_status(WaitingForTouch)
59 , m_direction(Direction::Rightwards)
62 , m_distanceThreshold(0)
63 , m_distanceThresholdSquared(0.)
65 , m_maxSilenceTime(200)
67 , m_compositionTime(60)
68 , m_numSamplesOnLastSpeedCheck(0)
69 , m_recognitionTimer(0)
70 , m_velocityCalculator(0)
71 , m_timeSource(new RealTimeSource)
72 , m_activeTouches(m_timeSource)
74 setRecognitionTimer(new Timer(this));
75 m_recognitionTimer->setInterval(60);
76 m_recognitionTimer->setSingleShot(false);
78 m_velocityCalculator = new AxisVelocityCalculator(this);
80 connect(this, &QQuickItem::enabledChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
81 connect(this, &QQuickItem::visibleChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
84Direction::Type DirectionalDragArea::direction() const
89void DirectionalDragArea::setDirection(Direction::Type direction)
91 if (direction != m_direction) {
92 m_direction = direction;
93 Q_EMIT directionChanged(m_direction);
97void DirectionalDragArea::setMaxDeviation(qreal value)
99 if (m_dampedScenePos.maxDelta() != value) {
100 m_dampedScenePos.setMaxDelta(value);
101 Q_EMIT maxDeviationChanged(value);
105qreal DirectionalDragArea::wideningAngle() const
107 return m_wideningAngle;
110void DirectionalDragArea::setWideningAngle(qreal angle)
112 if (angle == m_wideningAngle)
115 m_wideningAngle = angle;
117 // wideningFactor = pow(cosine(angle), 2)
119 qreal angleRadians = angle * M_PI / 180.0;
120 m_wideningFactor = qCos(angleRadians);
121 m_wideningFactor = m_wideningFactor * m_wideningFactor;
124 Q_EMIT wideningAngleChanged(angle);
127void DirectionalDragArea::setDistanceThreshold(qreal value)
129 if (m_distanceThreshold != value) {
130 m_distanceThreshold = value;
131 m_distanceThresholdSquared = m_distanceThreshold * m_distanceThreshold;
132 Q_EMIT distanceThresholdChanged(value);
136void DirectionalDragArea::setMinSpeed(qreal value)
138 if (m_minSpeed != value) {
140 Q_EMIT minSpeedChanged(value);
144void DirectionalDragArea::setMaxSilenceTime(int value)
146 if (m_maxSilenceTime != value) {
147 m_maxSilenceTime = value;
148 Q_EMIT maxSilenceTimeChanged(value);
152void DirectionalDragArea::setCompositionTime(int value)
154 if (m_compositionTime != value) {
155 m_compositionTime = value;
156 Q_EMIT compositionTimeChanged(value);
160void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
163 bool timerWasRunning = false;
164 bool wasSingleShot = false;
166 // can be null when called from the constructor
167 if (m_recognitionTimer) {
168 interval = m_recognitionTimer->interval();
169 timerWasRunning = m_recognitionTimer->isRunning();
170 if (m_recognitionTimer->parent() == this) {
171 delete m_recognitionTimer;
175 m_recognitionTimer = timer;
176 timer->setInterval(interval);
177 timer->setSingleShot(wasSingleShot);
178 connect(timer, &UbuntuGestures::AbstractTimer::timeout,
179 this, &DirectionalDragArea::checkSpeed);
180 if (timerWasRunning) {
181 m_recognitionTimer->start();
185void DirectionalDragArea::setTimeSource(const SharedTimeSource &timeSource)
187 m_timeSource = timeSource;
188 m_velocityCalculator->setTimeSource(timeSource);
189 m_activeTouches.m_timeSource = timeSource;
192qreal DirectionalDragArea::distance() const
194 if (Direction::isHorizontal(m_direction)) {
195 return m_previousPos.x() - m_startPos.x();
197 return m_previousPos.y() - m_startPos.y();
201void DirectionalDragArea::updateSceneDistance()
203 QPointF totalMovement = m_previousScenePos - m_startScenePos;
204 m_sceneDistance = projectOntoDirectionVector(totalMovement);
207qreal DirectionalDragArea::sceneDistance() const
209 return m_sceneDistance;
212qreal DirectionalDragArea::touchX() const
214 return m_previousPos.x();
217qreal DirectionalDragArea::touchY() const
219 return m_previousPos.y();
222qreal DirectionalDragArea::touchSceneX() const
224 return m_previousScenePos.x();
227qreal DirectionalDragArea::touchSceneY() const
229 return m_previousScenePos.y();
232bool DirectionalDragArea::event(QEvent *event)
234 if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
235 touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
237 } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
238 unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
241 return QQuickItem::event(event);
245void DirectionalDragArea::touchOwnershipEvent(TouchOwnershipEvent *event)
247 if (event->gained()) {
249 ids.append(event->touchId());
250 grabTouchPoints(ids);
252 // We still wanna know when it ends for keeping the composition time window up-to-date
253 TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
255 setStatus(WaitingForTouch);
259void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
261 QTouchEvent *event = unownedTouchEvent->touchEvent();
263 Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
265 #if DIRECTIONALDRAGAREA_DEBUG
266 // TODO Consider using qCDebug() when available (Qt 5.2)
267 qDebug() << "[DDA] Unowned" << m_timeSource->msecsSinceReference()
268 << qPrintable(touchEventToString(event));
272 case WaitingForTouch:
276 Q_ASSERT(isEnabled() && isVisible());
277 unownedTouchEvent_undecided(unownedTouchEvent);
279 default: // Recognized:
284 m_activeTouches.update(event);
287void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
289 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
291 qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId
292 << "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
293 "Considering it as released.";
295 TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
296 setStatus(WaitingForTouch);
300 const QPointF &touchScenePos = touchPoint->scenePos();
302 if (touchPoint->state() == Qt::TouchPointReleased) {
303 // touch has ended before recognition concluded
304 DDA_DEBUG("Touch has ended before recognition concluded");
305 TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
306 emitSignalIfTapped();
307 setStatus(WaitingForTouch);
311 m_previousDampedScenePos.setX(m_dampedScenePos.x());
312 m_previousDampedScenePos.setY(m_dampedScenePos.y());
313 m_dampedScenePos.update(touchScenePos);
314 updateVelocityCalculator(touchScenePos);
316 if (!pointInsideAllowedArea()) {
317 DDA_DEBUG("Rejecting gesture because touch point is outside allowed area.");
318 TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
319 // We still wanna know when it ends for keeping the composition time window up-to-date
320 TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
321 setStatus(WaitingForTouch);
325 if (!movingInRightDirection()) {
326 DDA_DEBUG("Rejecting gesture because touch point is moving in the wrong direction.");
327 TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
328 // We still wanna know when it ends for keeping the composition time window up-to-date
329 TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
330 setStatus(WaitingForTouch);
334 setPreviousPos(touchPoint->pos());
335 setPreviousScenePos(touchScenePos);
337 if (isWithinTouchCompositionWindow()) {
338 // There's still time for some new touch to appear and ruin our party as it would be combined
339 // with our m_touchId one and therefore deny the possibility of a single-finger gesture.
340 DDA_DEBUG("Sill within composition window. Let's wait more.");
344 if (movedFarEnough(touchScenePos)) {
345 TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
346 setStatus(Recognized);
348 DDA_DEBUG("Didn't move far enough yet. Let's wait more.");
352void DirectionalDragArea::touchEvent(QTouchEvent *event)
354 // TODO: Consider when more than one touch starts in the same event (although it's not possible
355 // with Mir's android-input). Have to track them all. Consider it a plus/bonus.
357 #if DIRECTIONALDRAGAREA_DEBUG
358 // TODO Consider using qCDebug() when available (Qt 5.2)
359 qDebug() << "[DDA]" << m_timeSource->msecsSinceReference()
360 << qPrintable(touchEventToString(event));
363 if (!isEnabled() || !isVisible()) {
364 QQuickItem::touchEvent(event);
369 case WaitingForTouch:
370 touchEvent_absent(event);
373 touchEvent_undecided(event);
375 default: // Recognized:
376 touchEvent_recognized(event);
380 m_activeTouches.update(event);
383void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
385 // TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
387 if (!event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
388 // Nothing to see here. No touch starting in this event.
392 // to be proven wrong, if that's the case
395 if (isWithinTouchCompositionWindow()) {
396 // too close to the last touch start. So we consider them as starting roughly at the same time.
397 // Can't be a single-touch gesture.
398 #if DIRECTIONALDRAGAREA_DEBUG
399 qDebug("[DDA] A new touch point came in but we're still within time composition window. Ignoring it.");
404 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
406 const QTouchEvent::TouchPoint *newTouchPoint = nullptr;
407 for (int i = 0; i < touchPoints.count() && allGood; ++i) {
408 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
409 if (touchPoint.state() == Qt::TouchPointPressed) {
411 // more than one touch starting in this QTouchEvent. Can't be a single-touch gesture
414 // that's our candidate
415 m_touchId = touchPoint.id();
416 newTouchPoint = &touchPoint;
422 Q_ASSERT(newTouchPoint);
424 m_startPos = newTouchPoint->pos();
425 m_startScenePos = newTouchPoint->scenePos();
426 m_touchId = newTouchPoint->id();
427 m_dampedScenePos.reset(m_startScenePos);
428 m_velocityCalculator->setTrackedPosition(0.);
429 m_velocityCalculator->reset();
430 m_numSamplesOnLastSpeedCheck = 0;
432 setPreviousPos(m_startPos);
433 setPreviousScenePos(m_startScenePos);
434 updateSceneDirectionVector();
436 if (recognitionIsDisabled()) {
437 // Behave like a dumb TouchArea
438 TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
439 setStatus(Recognized);
442 // just monitor the touch points for now.
443 TouchRegistry::instance()->addCandidateOwnerForTouch(m_touchId, this);
445 setStatus(Undecided);
446 // Let the item below have it. We will monitor it and grab it later if a gesture
451 watchPressedTouchPoints(touchPoints);
456void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
458 Q_ASSERT(event->type() == QEvent::TouchBegin);
459 Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
461 // We're not interested in new touch points. We already have our candidate (m_touchId).
462 // But we do want to know when those new touches end for keeping the composition time
465 watchPressedTouchPoints(event->touchPoints());
467 if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) {
468 // multi-finger drags are not accepted
469 DDA_DEBUG("Multi-finger drags are not accepted");
471 TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
472 // We still wanna know when it ends for keeping the composition time window up-to-date
473 TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
475 setStatus(WaitingForTouch);
480void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
482 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
485 qCritical() << "DirectionalDragArea[status=Recognized]: touch " << m_touchId
486 << "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
487 "Considering it as released.";
488 setStatus(WaitingForTouch);
490 setPreviousPos(touchPoint->pos());
491 setPreviousScenePos(touchPoint->scenePos());
493 if (touchPoint->state() == Qt::TouchPointReleased) {
494 emitSignalIfTapped();
495 setStatus(WaitingForTouch);
500void DirectionalDragArea::watchPressedTouchPoints(const QList<QTouchEvent::TouchPoint> &touchPoints)
502 for (int i = 0; i < touchPoints.count(); ++i) {
503 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
504 if (touchPoint.state() == Qt::TouchPointPressed) {
505 TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), this);
510bool DirectionalDragArea::recognitionIsDisabled() const
512 return distanceThreshold() <= 0 && compositionTime() <= 0;
515void DirectionalDragArea::emitSignalIfTapped()
517 qint64 touchDuration = m_timeSource->msecsSinceReference() - m_activeTouches.touchStartTime(m_touchId);
518 if (touchDuration <= maxTapDuration()) {
523const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event)
525 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
526 const QTouchEvent::TouchPoint *touchPoint = 0;
527 for (int i = 0; i < touchPoints.size(); ++i) {
528 if (touchPoints.at(i).id() == m_touchId) {
529 touchPoint = &touchPoints.at(i);
536bool DirectionalDragArea::pointInsideAllowedArea() const
538 // NB: Using squared values to avoid computing the square root to find
539 // the length totalMovement
541 QPointF totalMovement(m_dampedScenePos.x() - m_startScenePos.x(),
542 m_dampedScenePos.y() - m_startScenePos.y());
544 qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
545 totalMovement.y() * totalMovement.y();
547 if (squaredTotalMovSize == 0.) {
552 qreal projectedMovement = projectOntoDirectionVector(totalMovement);
555 qreal cosineAngleSquared = (projectedMovement * projectedMovement) / squaredTotalMovSize;
558 // angle_between_movement_vector_and_gesture_direction_vector <= widening_angle
559 return cosineAngleSquared >= m_wideningFactor;
562bool DirectionalDragArea::movingInRightDirection() const
564 QPointF movementVector(m_dampedScenePos.x() - m_previousDampedScenePos.x(),
565 m_dampedScenePos.y() - m_previousDampedScenePos.y());
567 qreal scalarProjection = projectOntoDirectionVector(movementVector);
569 return scalarProjection >= 0.;
572bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
574 if (m_distanceThreshold <= 0.) {
575 // distance threshold check is disabled
578 QPointF totalMovement(point.x() - m_startScenePos.x(),
579 point.y() - m_startScenePos.y());
581 qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
582 totalMovement.y() * totalMovement.y();
584 return squaredTotalMovSize > m_distanceThresholdSquared;
588void DirectionalDragArea::checkSpeed()
590 Q_ASSERT(m_status == Undecided);
592 if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
593 qreal speed = qFabs(m_velocityCalculator->calculate());
594 qreal minSpeedMsecs = m_minSpeed / 1000.0;
596 if (speed < minSpeedMsecs) {
597 DDA_DEBUG("Rejecting gesture because it's below minimum speed.");
598 TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
599 TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
600 setStatus(WaitingForTouch);
604 if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) {
605 m_silenceTime += m_recognitionTimer->interval();
607 if (m_silenceTime > m_maxSilenceTime) {
608 DDA_DEBUG("Rejecting gesture because its silence time has been exceeded.");
609 TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
610 TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
611 setStatus(WaitingForTouch);
616 m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
619void DirectionalDragArea::giveUpIfDisabledOrInvisible()
621 if (!isEnabled() || !isVisible()) {
622 if (m_status == Undecided) {
623 TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
624 // We still wanna know when it ends for keeping the composition time window up-to-date
625 TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
628 if (m_status != WaitingForTouch) {
629 DDA_DEBUG("Resetting status because got disabled or made invisible");
630 setStatus(WaitingForTouch);
635void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
637 if (newStatus == m_status)
640 DirectionalDragArea::Status oldStatus = m_status;
642 if (oldStatus == Undecided) {
643 m_recognitionTimer->stop();
646 m_status = newStatus;
647 Q_EMIT statusChanged(m_status);
649 #if DIRECTIONALDRAGAREA_DEBUG
650 qDebug() << "[DDA]" << statusToString(oldStatus) << "->" << statusToString(newStatus);
654 case WaitingForTouch:
655 Q_EMIT draggingChanged(false);
656 if (oldStatus == Undecided) {
660 m_recognitionTimer->start();
661 Q_EMIT draggingChanged(true);
664 if (oldStatus == WaitingForTouch)
665 Q_EMIT draggingChanged(true);
673void DirectionalDragArea::setPreviousPos(const QPointF &point)
675 bool xChanged = m_previousPos.x() != point.x();
676 bool yChanged = m_previousPos.y() != point.y();
678 m_previousPos = point;
681 Q_EMIT touchXChanged(point.x());
682 if (Direction::isHorizontal(m_direction))
683 Q_EMIT distanceChanged(distance());
687 Q_EMIT touchYChanged(point.y());
688 if (Direction::isVertical(m_direction))
689 Q_EMIT distanceChanged(distance());
693void DirectionalDragArea::setPreviousScenePos(const QPointF &point)
695 bool xChanged = m_previousScenePos.x() != point.x();
696 bool yChanged = m_previousScenePos.y() != point.y();
698 if (!xChanged && !yChanged)
701 qreal oldSceneDistance = sceneDistance();
702 m_previousScenePos = point;
703 updateSceneDistance();
705 if (oldSceneDistance != sceneDistance()) {
706 Q_EMIT sceneDistanceChanged(sceneDistance());
710 Q_EMIT touchSceneXChanged(point.x());
714 Q_EMIT touchSceneYChanged(point.y());
718void DirectionalDragArea::updateVelocityCalculator(const QPointF &scenePos)
720 QPointF totalSceneMovement = scenePos - m_startScenePos;
722 qreal scalarProjection = projectOntoDirectionVector(totalSceneMovement);
724 m_velocityCalculator->setTrackedPosition(scalarProjection);
727bool DirectionalDragArea::isWithinTouchCompositionWindow()
730 compositionTime() > 0 &&
731 !m_activeTouches.isEmpty() &&
732 m_timeSource->msecsSinceReference() <=
733 m_activeTouches.mostRecentStartTime() + (qint64)compositionTime();
736//************************** ActiveTouchesInfo **************************
738DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
739 : m_timeSource(timeSource)
743void DirectionalDragArea::ActiveTouchesInfo::update(QTouchEvent *event)
745 if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
747 #if ACTIVETOUCHESINFO_DEBUG
748 qDebug("[DDA::ActiveTouchesInfo] Nothing to Update");
753 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
754 for (int i = 0; i < touchPoints.count(); ++i) {
755 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
756 if (touchPoint.state() == Qt::TouchPointPressed) {
757 addTouchPoint(touchPoint.id());
758 } else if (touchPoint.state() == Qt::TouchPointReleased) {
759 removeTouchPoint(touchPoint.id());
764#if ACTIVETOUCHESINFO_DEBUG
765QString DirectionalDragArea::ActiveTouchesInfo::toString()
767 QString string = "(";
770 QTextStream stream(&string);
771 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
772 stream << "(id=" << touchInfo->id << ",startTime=" << touchInfo->startTime << ")";
781#endif // ACTIVETOUCHESINFO_DEBUG
783void DirectionalDragArea::ActiveTouchesInfo::addTouchPoint(int touchId)
785 ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
786 activeTouchInfo.id = touchId;
787 activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
789 #if ACTIVETOUCHESINFO_DEBUG
790 qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
794qint64 DirectionalDragArea::ActiveTouchesInfo::touchStartTime(int touchId)
798 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
799 if (touchId == touchInfo->id) {
800 result = touchInfo->startTime;
807 Q_ASSERT(result != -1);
811void DirectionalDragArea::ActiveTouchesInfo::removeTouchPoint(int touchId)
813 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
814 if (touchId == touchInfo->id) {
815 m_touchInfoPool.freeSlot(touchInfo);
822 #if ACTIVETOUCHESINFO_DEBUG
823 qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
827qint64 DirectionalDragArea::ActiveTouchesInfo::mostRecentStartTime()
829 Q_ASSERT(!m_touchInfoPool.isEmpty());
831 qint64 highestStartTime = -1;
833 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &activeTouchInfo) {
834 if (activeTouchInfo->startTime > highestStartTime) {
835 highestStartTime = activeTouchInfo->startTime;
840 return highestStartTime;
843void DirectionalDragArea::updateSceneDirectionVector()
845 QPointF localOrigin(0., 0.);
846 QPointF localDirection;
847 switch (m_direction) {
848 case Direction::Upwards:
849 localDirection.rx() = 0.;
850 localDirection.ry() = -1.;
852 case Direction::Downwards:
853 localDirection.rx() = 0.;
854 localDirection.ry() = 1;
856 case Direction::Leftwards:
857 localDirection.rx() = -1.;
858 localDirection.ry() = 0.;
860 default: // Direction::Rightwards:
861 localDirection.rx() = 1.;
862 localDirection.ry() = 0.;
865 QPointF sceneOrigin = mapToScene(localOrigin);
866 QPointF sceneDirection = mapToScene(localDirection);
867 m_sceneDirectionVector = sceneDirection - sceneOrigin;
870qreal DirectionalDragArea::projectOntoDirectionVector(const QPointF &sceneVector) const
872 // same as dot product as m_sceneDirectionVector is a unit vector
873 return sceneVector.x() * m_sceneDirectionVector.x() +
874 sceneVector.y() * m_sceneDirectionVector.y();
877// Because we are defining a new QObject-based class (RecognitionTimer) here.
878#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