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" 41 #if DIRECTIONALDRAGAREA_DEBUG 42 #define ddaDebug(params) qDebug().nospace() << "[DDA(" << qPrintable(objectName()) << ")] " << params 43 #include "DebugHelpers.h" 46 const 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 62 DirectionalDragArea::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);
74 Direction::Type DirectionalDragArea::direction()
const 79 void DirectionalDragArea::setDirection(Direction::Type direction)
81 if (direction != d->direction) {
82 d->direction = direction;
83 Q_EMIT directionChanged(d->direction);
87 void DirectionalDragAreaPrivate::setDistanceThreshold(qreal value)
89 if (distanceThreshold != value) {
90 distanceThreshold = value;
91 distanceThresholdSquared = distanceThreshold * distanceThreshold;
95 void DirectionalDragAreaPrivate::setMaxTime(
int value)
97 if (maxTime != value) {
99 recognitionTimer->setInterval(maxTime);
103 void DirectionalDragAreaPrivate::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
106 bool timerWasRunning =
false;
107 bool wasSingleShot =
false;
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();
128 void DirectionalDragAreaPrivate::setTimeSource(
const SharedTimeSource &timeSource)
130 this->timeSource = timeSource;
131 activeTouches.m_timeSource = timeSource;
134 qreal 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();
143 void DirectionalDragAreaPrivate::updateSceneDistance()
145 QPointF totalMovement = publicScenePos - startScenePos;
146 sceneDistance = projectOntoDirectionVector(totalMovement);
149 qreal DirectionalDragArea::sceneDistance()
const 151 return d->sceneDistance;
154 qreal DirectionalDragArea::touchX()
const 156 return d->publicPos.x();
159 qreal DirectionalDragArea::touchY()
const 161 return d->publicPos.y();
164 qreal DirectionalDragArea::touchSceneX()
const 166 return d->publicScenePos.x();
169 qreal DirectionalDragArea::touchSceneY()
const 171 return d->publicScenePos.y();
174 bool DirectionalDragArea::dragging()
const 176 return d->status == DirectionalDragAreaPrivate::Recognized;
179 bool DirectionalDragArea::pressed()
const 181 return d->status != DirectionalDragAreaPrivate::WaitingForTouch;
184 bool DirectionalDragArea::immediateRecognition()
const 186 return d->immediateRecognition;
189 void DirectionalDragArea::setImmediateRecognition(
bool enabled)
191 if (d->immediateRecognition != enabled) {
192 d->immediateRecognition = enabled;
193 Q_EMIT immediateRecognitionChanged(enabled);
197 bool DirectionalDragArea::monitorOnly()
const 199 return d->monitorOnly;
202 void DirectionalDragArea::setMonitorOnly(
bool monitorOnly)
204 if (d->monitorOnly != monitorOnly) {
205 d->monitorOnly = monitorOnly;
207 if (monitorOnly && d->status == DirectionalDragAreaPrivate::Undecided) {
208 TouchRegistry::instance()->removeCandidateOwnerForTouch(d->touchId,
this);
210 TouchRegistry::instance()->addTouchWatcher(d->touchId,
this);
213 Q_EMIT monitorOnlyChanged(monitorOnly);
217 void DirectionalDragArea::removeTimeConstraints()
219 d->setMaxTime(60 * 60 * 1000);
220 d->compositionTime = 0;
221 ddaDebug(
"removed time constraints");
224 bool DirectionalDragArea::event(QEvent *event)
226 if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
227 d->touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
229 }
else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
230 d->unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
233 return QQuickItem::event(event);
237 void DirectionalDragAreaPrivate::touchOwnershipEvent(TouchOwnershipEvent *event)
239 if (event->gained()) {
241 ids.append(event->touchId());
242 ddaDebug(
"grabbing touch");
243 q->grabTouchPoints(ids);
252 QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(q->window());
253 if (windowPrivate->touchMouseId == event->touchId() && q->window()->mouseGrabberItem()) {
254 ddaDebug(
"removing mouse grabber");
255 q->window()->mouseGrabberItem()->ungrabMouse();
260 TouchRegistry::instance()->addTouchWatcher(touchId, q);
262 setStatus(WaitingForTouch);
266 void DirectionalDragAreaPrivate::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
268 QTouchEvent *
event = unownedTouchEvent->touchEvent();
270 Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
272 ddaDebug(
"Unowned " << timeSource->msecsSinceReference() <<
" " << qPrintable(touchEventToString(event)));
275 case WaitingForTouch:
279 Q_ASSERT(q->isEnabled() && q->isVisible());
280 unownedTouchEvent_undecided(unownedTouchEvent);
285 touchEvent_recognized(event);
290 activeTouches.update(event);
293 void DirectionalDragAreaPrivate::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
295 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
297 qCritical() <<
"DirectionalDragArea[status=Undecided]: touch " << touchId
298 <<
"missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. " 299 "Considering it as released.";
301 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
302 setStatus(WaitingForTouch);
306 const QPointF &touchScenePos = touchPoint->scenePos();
308 if (touchPoint->state() == Qt::TouchPointReleased) {
310 ddaDebug(
"Touch has ended before recognition concluded");
311 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
312 setStatus(WaitingForTouch);
316 previousDampedScenePos.setX(dampedScenePos.x());
317 previousDampedScenePos.setY(dampedScenePos.y());
318 dampedScenePos.update(touchScenePos);
320 if (!movingInRightDirection()) {
321 ddaDebug(
"Rejecting gesture because touch point is moving in the wrong direction.");
322 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
324 TouchRegistry::instance()->addTouchWatcher(touchId, q);
325 setStatus(WaitingForTouch);
329 if (isWithinTouchCompositionWindow()) {
332 ddaDebug(
"Sill within composition window. Let's wait more.");
336 if (movedFarEnoughAlongGestureAxis()) {
338 TouchRegistry::instance()->requestTouchOwnership(touchId, q);
340 setStatus(Recognized);
341 setPublicPos(touchPoint->pos());
342 setPublicScenePos(touchScenePos);
343 }
else if (isPastMaxDistance()) {
344 ddaDebug(
"Rejecting gesture because it went farther than maxDistance without getting recognized.");
345 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
347 TouchRegistry::instance()->addTouchWatcher(touchId, q);
348 setStatus(WaitingForTouch);
350 ddaDebug(
"Didn't move far enough yet. Let's wait more.");
354 void DirectionalDragArea::touchEvent(QTouchEvent *event)
359 ddaDebug(d->timeSource->msecsSinceReference() <<
" " << qPrintable(touchEventToString(event)));
361 if (!isEnabled() || !isVisible()) {
362 QQuickItem::touchEvent(event);
367 case DirectionalDragAreaPrivate::WaitingForTouch:
368 d->touchEvent_absent(event);
370 case DirectionalDragAreaPrivate::Undecided:
371 d->touchEvent_undecided(event);
374 d->touchEvent_recognized(event);
378 d->activeTouches.update(event);
381 void DirectionalDragAreaPrivate::touchEvent_absent(QTouchEvent *event)
385 if (!event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
393 if (isWithinTouchCompositionWindow()) {
396 ddaDebug(
"A new touch point came in but we're still within time composition window. Ignoring it.");
400 const QList<QTouchEvent::TouchPoint> &touchPoints =
event->touchPoints();
402 const QTouchEvent::TouchPoint *newTouchPoint =
nullptr;
403 for (
int i = 0; i < touchPoints.count() && allGood; ++i) {
404 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
405 if (touchPoint.state() == Qt::TouchPointPressed) {
411 newTouchPoint = &touchPoint;
417 allGood = sanityCheckRecognitionProperties();
419 qWarning(
"DirectionalDragArea: recognition properties are wrongly set. Gesture recognition" 425 Q_ASSERT(newTouchPoint);
427 startPos = newTouchPoint->pos();
428 startScenePos = newTouchPoint->scenePos();
429 touchId = newTouchPoint->id();
430 dampedScenePos.reset(startScenePos);
431 setPublicPos(startPos);
433 setPublicScenePos(startScenePos);
434 updateSceneDirectionVector();
436 if (recognitionIsDisabled()) {
438 ddaDebug(
"Gesture recognition is disabled. Requesting touch ownership immediately.");
439 setStatus(Recognized);
441 watchPressedTouchPoints(touchPoints);
444 TouchRegistry::instance()->requestTouchOwnership(touchId, q);
450 watchPressedTouchPoints(touchPoints);
452 TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, q);
455 setStatus(Undecided);
461 watchPressedTouchPoints(touchPoints);
466 void DirectionalDragAreaPrivate::touchEvent_undecided(QTouchEvent *event)
468 Q_ASSERT(fetchTargetTouchPoint(event) ==
nullptr);
474 watchPressedTouchPoints(event->touchPoints());
476 if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) {
478 ddaDebug(
"Multi-finger drags are not accepted");
480 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
482 TouchRegistry::instance()->addTouchWatcher(touchId, q);
484 setStatus(WaitingForTouch);
488 void DirectionalDragAreaPrivate::touchEvent_recognized(QTouchEvent *event)
490 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
493 qCritical() <<
"DirectionalDragArea[status=Recognized]: touch " << touchId
494 <<
"missing from QTouchEvent without first reaching state Qt::TouchPointReleased. " 495 "Considering it as released.";
496 setStatus(WaitingForTouch);
498 setPublicPos(touchPoint->pos());
499 setPublicScenePos(touchPoint->scenePos());
501 if (touchPoint->state() == Qt::TouchPointReleased) {
502 setStatus(WaitingForTouch);
507 void DirectionalDragAreaPrivate::watchPressedTouchPoints(
const QList<QTouchEvent::TouchPoint> &touchPoints)
509 for (
int i = 0; i < touchPoints.count(); ++i) {
510 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
511 if (touchPoint.state() == Qt::TouchPointPressed) {
512 TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), q);
517 bool DirectionalDragAreaPrivate::recognitionIsDisabled()
const 519 return immediateRecognition || (distanceThreshold <= 0 && compositionTime <= 0);
522 bool DirectionalDragAreaPrivate::sanityCheckRecognitionProperties()
524 return recognitionIsDisabled()
525 || (distanceThreshold < maxDistance && compositionTime < maxTime);
528 const QTouchEvent::TouchPoint *DirectionalDragAreaPrivate::fetchTargetTouchPoint(QTouchEvent *event)
530 const QList<QTouchEvent::TouchPoint> &touchPoints =
event->touchPoints();
531 const QTouchEvent::TouchPoint *touchPoint = 0;
532 for (
int i = 0; i < touchPoints.size(); ++i) {
533 if (touchPoints.at(i).id() == touchId) {
534 touchPoint = &touchPoints.at(i);
541 bool DirectionalDragAreaPrivate::movingInRightDirection()
const 543 if (direction == Direction::Horizontal || direction == Direction::Vertical) {
546 QPointF movementVector(dampedScenePos.x() - previousDampedScenePos.x(),
547 dampedScenePos.y() - previousDampedScenePos.y());
549 qreal scalarProjection = projectOntoDirectionVector(movementVector);
551 return scalarProjection >= 0.;
555 bool DirectionalDragAreaPrivate::movedFarEnoughAlongGestureAxis()
const 557 if (distanceThreshold <= 0.) {
561 QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
562 dampedScenePos.y() - startScenePos.y());
564 qreal scalarProjection = projectOntoDirectionVector(totalMovement);
566 ddaDebug(
" movedFarEnoughAlongGestureAxis: scalarProjection=" << scalarProjection
567 <<
", distanceThreshold=" << distanceThreshold);
569 if (direction == Direction::Horizontal || direction == Direction::Vertical) {
570 return qAbs(scalarProjection) > distanceThreshold;
572 return scalarProjection > distanceThreshold;
577 bool DirectionalDragAreaPrivate::isPastMaxDistance()
const 579 QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
580 dampedScenePos.y() - startScenePos.y());
582 qreal squaredDistance = totalMovement.x()*totalMovement.x() + totalMovement.y()*totalMovement.y();
583 return squaredDistance > maxDistance*maxDistance;
586 void DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible()
588 if (!q->isEnabled() || !q->isVisible()) {
589 if (status == Undecided) {
590 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
592 TouchRegistry::instance()->addTouchWatcher(touchId, q);
595 if (status != WaitingForTouch) {
596 ddaDebug(
"Resetting status because got disabled or made invisible");
597 setStatus(WaitingForTouch);
602 void DirectionalDragAreaPrivate::rejectGesture()
604 if (status == Undecided) {
605 ddaDebug(
"Rejecting gesture because it's taking too long to drag beyond the threshold.");
607 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
609 TouchRegistry::instance()->addTouchWatcher(touchId, q);
611 setStatus(WaitingForTouch);
615 void DirectionalDragAreaPrivate::setStatus(Status newStatus)
617 if (newStatus == status)
620 Status oldStatus = status;
622 if (oldStatus == Undecided) {
623 recognitionTimer->stop();
627 Q_EMIT statusChanged(status);
629 ddaDebug(statusToString(oldStatus) <<
" -> " << statusToString(newStatus));
632 case WaitingForTouch:
633 if (oldStatus == Recognized) {
634 Q_EMIT q->draggingChanged(
false);
636 Q_EMIT q->pressedChanged(
false);
639 recognitionTimer->start();
640 Q_EMIT q->pressedChanged(
true);
643 if (oldStatus == WaitingForTouch) {
644 Q_EMIT q->pressedChanged(
true);
646 Q_EMIT q->draggingChanged(
true);
654 void DirectionalDragAreaPrivate::setPublicPos(
const QPointF point)
656 bool xChanged = publicPos.x() != point.x();
657 bool yChanged = publicPos.y() != point.y();
661 Q_ASSERT(status == WaitingForTouch || status == Recognized);
663 if (status == Recognized && !recognitionIsDisabled()) {
670 QPointF delta = point - publicPos;
672 publicPos.rx() += 0.4 * delta.x();
673 publicPos.ry() += 0.4 * delta.y();
681 Q_EMIT q->touchXChanged(publicPos.x());
682 if (Direction::isHorizontal(direction))
683 Q_EMIT q->distanceChanged(q->distance());
687 Q_EMIT q->touchYChanged(publicPos.y());
688 if (Direction::isVertical(direction))
689 Q_EMIT q->distanceChanged(q->distance());
693 void DirectionalDragAreaPrivate::setPublicScenePos(
const QPointF point)
695 bool xChanged = publicScenePos.x() != point.x();
696 bool yChanged = publicScenePos.y() != point.y();
698 if (!xChanged && !yChanged)
703 Q_ASSERT(status == WaitingForTouch || status == Recognized);
705 qreal oldSceneDistance = sceneDistance;
707 if (status == Recognized && !recognitionIsDisabled()) {
714 QPointF delta = point - publicScenePos;
716 publicScenePos.rx() += 0.4 * delta.x();
717 publicScenePos.ry() += 0.4 * delta.y();
721 publicScenePos = point;
724 updateSceneDistance();
726 if (oldSceneDistance != sceneDistance) {
727 Q_EMIT q->sceneDistanceChanged(sceneDistance);
731 Q_EMIT q->touchSceneXChanged(publicScenePos.x());
735 Q_EMIT q->touchSceneYChanged(publicScenePos.y());
739 bool DirectionalDragAreaPrivate::isWithinTouchCompositionWindow()
742 compositionTime > 0 &&
743 !activeTouches.isEmpty() &&
744 timeSource->msecsSinceReference() <=
745 activeTouches.mostRecentStartTime() + (qint64)compositionTime;
748 void DirectionalDragArea::itemChange(ItemChange change,
const ItemChangeData &value)
750 if (change == QQuickItem::ItemSceneChange) {
751 if (value.window !=
nullptr) {
752 value.window->installEventFilter(TouchRegistry::instance());
756 qreal pixelsPerInch = value.window->screen()->physicalDotsPerInch();
757 if (pixelsPerInch < 0) {
762 d->setPixelsPerMm(pixelsPerInch / 25.4);
767 void DirectionalDragAreaPrivate::setPixelsPerMm(qreal pixelsPerMm)
769 dampedScenePos.setMaxDelta(1. * pixelsPerMm);
770 setDistanceThreshold(4. * pixelsPerMm);
771 maxDistance = 10. * pixelsPerMm;
776 ActiveTouchesInfo::ActiveTouchesInfo(
const SharedTimeSource &timeSource)
777 : m_timeSource(timeSource)
781 void ActiveTouchesInfo::update(QTouchEvent *event)
783 if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
785 #if ACTIVETOUCHESINFO_DEBUG 786 qDebug(
"[DDA::ActiveTouchesInfo] Nothing to Update");
791 const QList<QTouchEvent::TouchPoint> &touchPoints =
event->touchPoints();
792 for (
int i = 0; i < touchPoints.count(); ++i) {
793 const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
794 if (touchPoint.state() == Qt::TouchPointPressed) {
795 addTouchPoint(touchPoint.id());
796 }
else if (touchPoint.state() == Qt::TouchPointReleased) {
797 removeTouchPoint(touchPoint.id());
802 #if ACTIVETOUCHESINFO_DEBUG 803 QString ActiveTouchesInfo::toString()
805 QString
string =
"(";
808 QTextStream stream(&
string);
809 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
810 stream <<
"(id=" << touchInfo->id <<
",startTime=" << touchInfo->startTime <<
")";
819 #endif // ACTIVETOUCHESINFO_DEBUG 821 void ActiveTouchesInfo::addTouchPoint(
int touchId)
823 ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
824 activeTouchInfo.id = touchId;
825 activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
827 #if ACTIVETOUCHESINFO_DEBUG 828 qDebug() <<
"[DDA::ActiveTouchesInfo]" << qPrintable(toString());
832 qint64 ActiveTouchesInfo::touchStartTime(
int touchId)
836 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
837 if (touchId == touchInfo->id) {
838 result = touchInfo->startTime;
845 Q_ASSERT(result != -1);
849 void ActiveTouchesInfo::removeTouchPoint(
int touchId)
851 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
852 if (touchId == touchInfo->id) {
853 m_touchInfoPool.freeSlot(touchInfo);
860 #if ACTIVETOUCHESINFO_DEBUG 861 qDebug() <<
"[DDA::ActiveTouchesInfo]" << qPrintable(toString());
865 qint64 ActiveTouchesInfo::mostRecentStartTime()
867 Q_ASSERT(!m_touchInfoPool.isEmpty());
869 qint64 highestStartTime = -1;
871 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &activeTouchInfo) {
872 if (activeTouchInfo->startTime > highestStartTime) {
873 highestStartTime = activeTouchInfo->startTime;
878 return highestStartTime;
881 void DirectionalDragAreaPrivate::updateSceneDirectionVector()
883 QPointF localOrigin(0., 0.);
884 QPointF localDirection;
886 case Direction::Upwards:
887 localDirection.rx() = 0.;
888 localDirection.ry() = -1.;
890 case Direction::Downwards:
891 case Direction::Vertical:
892 localDirection.rx() = 0.;
893 localDirection.ry() = 1;
895 case Direction::Leftwards:
896 localDirection.rx() = -1.;
897 localDirection.ry() = 0.;
900 localDirection.rx() = 1.;
901 localDirection.ry() = 0.;
904 QPointF sceneOrigin = q->mapToScene(localOrigin);
905 QPointF sceneDirection = q->mapToScene(localDirection);
906 sceneDirectionVector = sceneDirection - sceneOrigin;
909 qreal DirectionalDragAreaPrivate::projectOntoDirectionVector(
const QPointF sceneVector)
const 912 return sceneVector.x() * sceneDirectionVector.x() +
913 sceneVector.y() * sceneDirectionVector.y();
916 DirectionalDragAreaPrivate::DirectionalDragAreaPrivate(DirectionalDragArea *q)
918 , status(WaitingForTouch)
921 , direction(Direction::Rightwards)
922 , distanceThreshold(0)
923 , distanceThresholdSquared(0.)
925 , compositionTime(60)
926 , immediateRecognition(false)
927 , recognitionTimer(nullptr)
928 , timeSource(new RealTimeSource)
929 , activeTouches(timeSource)