on f20bd96 help sign in
unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp
932 lines 30 kB history raw
1/*
2 * Copyright (C) 2013-2015 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
17#define ACTIVETOUCHESINFO_DEBUG 0
18#define DIRECTIONALDRAGAREA_DEBUG 0
20#include "DirectionalDragArea.h"
22#include <QQuickWindow>
23#include <QtCore/qmath.h>
24#include <QScreen>
25#include <QDebug>
27#pragma GCC diagnostic push
28#pragma GCC diagnostic ignored "-pedantic"
29#include <private/qquickwindow_p.h>
30#pragma GCC diagnostic pop
32// local
33#include "TouchOwnershipEvent.h"
34#include "TouchRegistry.h"
35#include "UnownedTouchEvent.h"
37#include "DirectionalDragArea_p.h"
39using namespace UbuntuGestures;
41#if DIRECTIONALDRAGAREA_DEBUG
42#define ddaDebug(params) qDebug().nospace() << "[DDA(" << qPrintable(objectName()) << ")] " << params
43#include "DebugHelpers.h"
45namespace {
46const char *statusToString(DirectionalDragAreaPrivate::Status status)
48 if (status == DirectionalDragAreaPrivate::WaitingForTouch) {
49 return "WaitingForTouch";
50 } else if (status == DirectionalDragAreaPrivate::Undecided) {
51 return "Undecided";
52 } else {
53 return "Recognized";
54 }
57} // namespace {
58#else // DIRECTIONALDRAGAREA_DEBUG
59#define ddaDebug(params) ((void)0)
60#endif // DIRECTIONALDRAGAREA_DEBUG
62DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
63 : QQuickItem(parent)
64 , d(new DirectionalDragAreaPrivate(this))
66 d->setRecognitionTimer(new Timer(this));
67 d->recognitionTimer->setInterval(d->maxTime);
68 d->recognitionTimer->setSingleShot(true);
70 connect(this, &QQuickItem::enabledChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
71 connect(this, &QQuickItem::visibleChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
74Direction::Type DirectionalDragArea::direction() const
76 return d->direction;
79void DirectionalDragArea::setDirection(Direction::Type direction)
81 if (direction != d->direction) {
82 d->direction = direction;
83 Q_EMIT directionChanged(d->direction);
84 }
87void DirectionalDragAreaPrivate::setDistanceThreshold(qreal value)
89 if (distanceThreshold != value) {
90 distanceThreshold = value;
91 distanceThresholdSquared = distanceThreshold * distanceThreshold;
92 }
95void DirectionalDragAreaPrivate::setMaxTime(int value)
97 if (maxTime != value) {
98 maxTime = value;
99 recognitionTimer->setInterval(maxTime);
100 }
103void DirectionalDragAreaPrivate::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
105 int interval = 0;
106 bool timerWasRunning = false;
107 bool wasSingleShot = false;
109 // can be null when called from the constructor
110 if (recognitionTimer) {
111 interval = recognitionTimer->interval();
112 timerWasRunning = recognitionTimer->isRunning();
113 if (recognitionTimer->parent() == this) {
114 delete recognitionTimer;
115 }
116 }
118 recognitionTimer = timer;
119 timer->setInterval(interval);
120 timer->setSingleShot(wasSingleShot);
121 connect(timer, &UbuntuGestures::AbstractTimer::timeout,
122 this, &DirectionalDragAreaPrivate::rejectGesture);
123 if (timerWasRunning) {
124 recognitionTimer->start();
125 }
128void DirectionalDragAreaPrivate::setTimeSource(const SharedTimeSource &timeSource)
130 this->timeSource = timeSource;
131 activeTouches.m_timeSource = timeSource;
134qreal DirectionalDragArea::distance() const
136 if (Direction::isHorizontal(d->direction)) {
137 return d->publicPos.x() - d->startPos.x();
138 } else {
139 return d->publicPos.y() - d->startPos.y();
140 }
143void DirectionalDragAreaPrivate::updateSceneDistance()
145 QPointF totalMovement = publicScenePos - startScenePos;
146 sceneDistance = projectOntoDirectionVector(totalMovement);
149qreal DirectionalDragArea::sceneDistance() const
151 return d->sceneDistance;
154qreal DirectionalDragArea::touchX() const
156 return d->publicPos.x();
159qreal DirectionalDragArea::touchY() const
161 return d->publicPos.y();
164qreal DirectionalDragArea::touchSceneX() const
166 return d->publicScenePos.x();
169qreal DirectionalDragArea::touchSceneY() const
171 return d->publicScenePos.y();
174bool DirectionalDragArea::dragging() const
176 return d->status == DirectionalDragAreaPrivate::Recognized;
179bool DirectionalDragArea::pressed() const
181 return d->status != DirectionalDragAreaPrivate::WaitingForTouch;
184bool DirectionalDragArea::immediateRecognition() const
186 return d->immediateRecognition;
189void DirectionalDragArea::setImmediateRecognition(bool enabled)
191 if (d->immediateRecognition != enabled) {
192 d->immediateRecognition = enabled;
193 Q_EMIT immediateRecognitionChanged(enabled);
194 }
197bool DirectionalDragArea::monitorOnly() const
199 return d->monitorOnly;
202void 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);
209 // We still wanna know when it ends for keeping the composition time window up-to-date
210 TouchRegistry::instance()->addTouchWatcher(d->touchId, this);
211 }
213 Q_EMIT monitorOnlyChanged(monitorOnly);
214 }
217void DirectionalDragArea::removeTimeConstraints()
219 d->setMaxTime(60 * 60 * 1000);
220 d->compositionTime = 0;
221 ddaDebug("removed time constraints");
224bool DirectionalDragArea::event(QEvent *event)
226 if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
227 d->touchOwnershipEvent(static_cast<TouchOwnershipEvent *>(event));
228 return true;
229 } else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
230 d->unownedTouchEvent(static_cast<UnownedTouchEvent *>(event));
231 return true;
232 } else {
233 return QQuickItem::event(event);
234 }
237void DirectionalDragAreaPrivate::touchOwnershipEvent(TouchOwnershipEvent *event)
239 if (event->gained()) {
240 QVector<int> ids;
241 ids.append(event->touchId());
242 ddaDebug("grabbing touch");
243 q->grabTouchPoints(ids);
245 // Work around for Qt bug. If we grab a touch that is being used for mouse pointer
246 // emulation it will cause the emulation logic to go nuts.
247 // Thus we have to also grab the mouse in this case.
248 //
249 // The fix for this bug has landed in Qt 5.4 (https://codereview.qt-project.org/96887)
250 // TODO: Remove this workaround once we start using Qt 5.4
251 if (q->window()) {
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();
256 }
257 }
258 } else {
259 // We still wanna know when it ends for keeping the composition time window up-to-date
260 TouchRegistry::instance()->addTouchWatcher(touchId, q);
262 setStatus(WaitingForTouch);
263 }
266void 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)));
274 switch (status) {
275 case WaitingForTouch:
276 // do nothing
277 break;
278 case Undecided:
279 Q_ASSERT(q->isEnabled() && q->isVisible());
280 unownedTouchEvent_undecided(unownedTouchEvent);
281 break;
282 default: // Recognized:
283 if (monitorOnly) {
284 // Treat unowned event as if we owned it, but we are really just watching it
285 touchEvent_recognized(event);
286 }
287 break;
288 }
290 activeTouches.update(event);
293void DirectionalDragAreaPrivate::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
295 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
296 if (!touchPoint) {
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);
303 return;
304 }
306 const QPointF &touchScenePos = touchPoint->scenePos();
308 if (touchPoint->state() == Qt::TouchPointReleased) {
309 // touch has ended before recognition concluded
310 ddaDebug("Touch has ended before recognition concluded");
311 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
312 setStatus(WaitingForTouch);
313 return;
314 }
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);
323 // We still wanna know when it ends for keeping the composition time window up-to-date
324 TouchRegistry::instance()->addTouchWatcher(touchId, q);
325 setStatus(WaitingForTouch);
326 return;
327 }
329 if (isWithinTouchCompositionWindow()) {
330 // There's still time for some new touch to appear and ruin our party as it would be combined
331 // with our touchId one and therefore deny the possibility of a single-finger gesture.
332 ddaDebug("Sill within composition window. Let's wait more.");
333 return;
334 }
336 if (movedFarEnoughAlongGestureAxis()) {
337 if (!monitorOnly) {
338 TouchRegistry::instance()->requestTouchOwnership(touchId, q);
339 }
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);
346 // We still wanna know when it ends for keeping the composition time window up-to-date
347 TouchRegistry::instance()->addTouchWatcher(touchId, q);
348 setStatus(WaitingForTouch);
349 } else {
350 ddaDebug("Didn't move far enough yet. Let's wait more.");
351 }
354void DirectionalDragArea::touchEvent(QTouchEvent *event)
356 // TODO: Consider when more than one touch starts in the same event (although it's not possible
357 // with Mir's android-input). Have to track them all. Consider it a plus/bonus.
359 ddaDebug(d->timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
361 if (!isEnabled() || !isVisible()) {
362 QQuickItem::touchEvent(event);
363 return;
364 }
366 switch (d->status) {
367 case DirectionalDragAreaPrivate::WaitingForTouch:
368 d->touchEvent_absent(event);
369 break;
370 case DirectionalDragAreaPrivate::Undecided:
371 d->touchEvent_undecided(event);
372 break;
373 default: // Recognized:
374 d->touchEvent_recognized(event);
375 break;
376 }
378 d->activeTouches.update(event);
381void DirectionalDragAreaPrivate::touchEvent_absent(QTouchEvent *event)
383 // TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
385 if (!event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
386 // Nothing to see here. No touch starting in this event.
387 return;
388 }
390 // to be proven wrong, if that's the case
391 bool allGood = true;
393 if (isWithinTouchCompositionWindow()) {
394 // too close to the last touch start. So we consider them as starting roughly at the same time.
395 // Can't be a single-touch gesture.
396 ddaDebug("A new touch point came in but we're still within time composition window. Ignoring it.");
397 allGood = false;
398 }
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) {
406 if (newTouchPoint) {
407 // more than one touch starting in this QTouchEvent. Can't be a single-touch gesture
408 allGood = false;
409 } else {
410 // that's our candidate
411 newTouchPoint = &touchPoint;
412 }
413 }
414 }
416 if (allGood) {
417 allGood = sanityCheckRecognitionProperties();
418 if (!allGood) {
419 qWarning("DirectionalDragArea: recognition properties are wrongly set. Gesture recognition"
420 " is impossible");
421 }
422 }
424 if (allGood) {
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()) {
437 // Behave like a dumb TouchArea
438 ddaDebug("Gesture recognition is disabled. Requesting touch ownership immediately.");
439 setStatus(Recognized);
440 if (monitorOnly) {
441 watchPressedTouchPoints(touchPoints);
442 event->ignore();
443 } else {
444 TouchRegistry::instance()->requestTouchOwnership(touchId, q);
445 event->accept();
446 }
447 } else {
448 // just monitor the touch points for now.
449 if (monitorOnly) {
450 watchPressedTouchPoints(touchPoints);
451 } else {
452 TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, q);
453 }
455 setStatus(Undecided);
456 // Let the item below have it. We will monitor it and grab it later if a gesture
457 // gets recognized.
458 event->ignore();
459 }
460 } else {
461 watchPressedTouchPoints(touchPoints);
462 event->ignore();
463 }
466void DirectionalDragAreaPrivate::touchEvent_undecided(QTouchEvent *event)
468 Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
470 // We're not interested in new touch points. We already have our candidate (touchId).
471 // But we do want to know when those new touches end for keeping the composition time
472 // window up-to-date
473 event->ignore();
474 watchPressedTouchPoints(event->touchPoints());
476 if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) {
477 // multi-finger drags are not accepted
478 ddaDebug("Multi-finger drags are not accepted");
480 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
481 // We still wanna know when it ends for keeping the composition time window up-to-date
482 TouchRegistry::instance()->addTouchWatcher(touchId, q);
484 setStatus(WaitingForTouch);
485 }
488void DirectionalDragAreaPrivate::touchEvent_recognized(QTouchEvent *event)
490 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
492 if (!touchPoint) {
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);
497 } else {
498 setPublicPos(touchPoint->pos());
499 setPublicScenePos(touchPoint->scenePos());
501 if (touchPoint->state() == Qt::TouchPointReleased) {
502 setStatus(WaitingForTouch);
503 }
504 }
507void 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);
513 }
514 }
517bool DirectionalDragAreaPrivate::recognitionIsDisabled() const
519 return immediateRecognition || (distanceThreshold <= 0 && compositionTime <= 0);
522bool DirectionalDragAreaPrivate::sanityCheckRecognitionProperties()
524 return recognitionIsDisabled()
525 || (distanceThreshold < maxDistance && compositionTime < maxTime);
528const 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);
535 break;
536 }
537 }
538 return touchPoint;
541bool DirectionalDragAreaPrivate::movingInRightDirection() const
543 if (direction == Direction::Horizontal || direction == Direction::Vertical) {
544 return true;
545 } else {
546 QPointF movementVector(dampedScenePos.x() - previousDampedScenePos.x(),
547 dampedScenePos.y() - previousDampedScenePos.y());
549 qreal scalarProjection = projectOntoDirectionVector(movementVector);
551 return scalarProjection >= 0.;
552 }
555bool DirectionalDragAreaPrivate::movedFarEnoughAlongGestureAxis() const
557 if (distanceThreshold <= 0.) {
558 // distance threshold check is disabled
559 return true;
560 } else {
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;
571 } else {
572 return scalarProjection > distanceThreshold;
573 }
574 }
577bool 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;
586void DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible()
588 if (!q->isEnabled() || !q->isVisible()) {
589 if (status == Undecided) {
590 TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
591 // We still wanna know when it ends for keeping the composition time window up-to-date
592 TouchRegistry::instance()->addTouchWatcher(touchId, q);
593 }
595 if (status != WaitingForTouch) {
596 ddaDebug("Resetting status because got disabled or made invisible");
597 setStatus(WaitingForTouch);
598 }
599 }
602void 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);
608 // We still wanna know when it ends for keeping the composition time window up-to-date
609 TouchRegistry::instance()->addTouchWatcher(touchId, q);
611 setStatus(WaitingForTouch);
612 }
615void DirectionalDragAreaPrivate::setStatus(Status newStatus)
617 if (newStatus == status)
618 return;
620 Status oldStatus = status;
622 if (oldStatus == Undecided) {
623 recognitionTimer->stop();
624 }
626 status = newStatus;
627 Q_EMIT statusChanged(status);
629 ddaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
631 switch (newStatus) {
632 case WaitingForTouch:
633 if (oldStatus == Recognized) {
634 Q_EMIT q->draggingChanged(false);
635 }
636 Q_EMIT q->pressedChanged(false);
637 break;
638 case Undecided:
639 recognitionTimer->start();
640 Q_EMIT q->pressedChanged(true);
641 break;
642 case Recognized:
643 if (oldStatus == WaitingForTouch) { // for immediate recognition
644 Q_EMIT q->pressedChanged(true);
645 }
646 Q_EMIT q->draggingChanged(true);
647 break;
648 default:
649 // no-op
650 break;
651 }
654void DirectionalDragAreaPrivate::setPublicPos(const QPointF point)
656 bool xChanged = publicPos.x() != point.x();
657 bool yChanged = publicPos.y() != point.y();
659 // Public position should not get updated while the gesture is still being recognized
660 // (ie, Undecided status).
661 Q_ASSERT(status == WaitingForTouch || status == Recognized);
663 if (status == Recognized && !recognitionIsDisabled()) {
664 // When the gesture finally gets recognized, the finger will likely be
665 // reasonably far from the edge. If we made the contentX immediately
666 // follow the finger position it would be visually unpleasant as it
667 // would appear right next to the user's finger out of nowhere (ie,
668 // it would jump). Instead, we make contentX go towards the user's
669 // finger in several steps. ie., in an animated way.
670 QPointF delta = point - publicPos;
671 // the trick is not to go all the way (1.0) as it would cause a sudden jump
672 publicPos.rx() += 0.4 * delta.x();
673 publicPos.ry() += 0.4 * delta.y();
674 } else {
675 // no smoothing when initializing or if gesture recognition was immediate as there will
676 // be no jump.
677 publicPos = point;
678 }
680 if (xChanged) {
681 Q_EMIT q->touchXChanged(publicPos.x());
682 if (Direction::isHorizontal(direction))
683 Q_EMIT q->distanceChanged(q->distance());
684 }
686 if (yChanged) {
687 Q_EMIT q->touchYChanged(publicPos.y());
688 if (Direction::isVertical(direction))
689 Q_EMIT q->distanceChanged(q->distance());
690 }
693void DirectionalDragAreaPrivate::setPublicScenePos(const QPointF point)
695 bool xChanged = publicScenePos.x() != point.x();
696 bool yChanged = publicScenePos.y() != point.y();
698 if (!xChanged && !yChanged)
699 return;
701 // Public position should not get updated while the gesture is still being recognized
702 // (ie, Undecided status).
703 Q_ASSERT(status == WaitingForTouch || status == Recognized);
705 qreal oldSceneDistance = sceneDistance;
707 if (status == Recognized && !recognitionIsDisabled()) {
708 // When the gesture finally gets recognized, the finger will likely be
709 // reasonably far from the edge. If we made the contentX immediately
710 // follow the finger position it would be visually unpleasant as it
711 // would appear right next to the user's finger out of nowhere (ie,
712 // it would jump). Instead, we make contentX go towards the user's
713 // finger in several steps. ie., in an animated way.
714 QPointF delta = point - publicScenePos;
715 // the trick is not to go all the way (1.0) as it would cause a sudden jump
716 publicScenePos.rx() += 0.4 * delta.x();
717 publicScenePos.ry() += 0.4 * delta.y();
718 } else {
719 // no smoothing when initializing or if gesture recognition was immediate as there will
720 // be no jump.
721 publicScenePos = point;
722 }
724 updateSceneDistance();
726 if (oldSceneDistance != sceneDistance) {
727 Q_EMIT q->sceneDistanceChanged(sceneDistance);
728 }
730 if (xChanged) {
731 Q_EMIT q->touchSceneXChanged(publicScenePos.x());
732 }
734 if (yChanged) {
735 Q_EMIT q->touchSceneYChanged(publicScenePos.y());
736 }
739bool DirectionalDragAreaPrivate::isWithinTouchCompositionWindow()
741 return
742 compositionTime > 0 &&
743 !activeTouches.isEmpty() &&
744 timeSource->msecsSinceReference() <=
745 activeTouches.mostRecentStartTime() + (qint64)compositionTime;
748void DirectionalDragArea::itemChange(ItemChange change, const ItemChangeData &value)
750 if (change == QQuickItem::ItemSceneChange) {
751 if (value.window != nullptr) {
752 value.window->installEventFilter(TouchRegistry::instance());
754 // TODO: Handle window->screen() changes (ie window changing screens)
756 qreal pixelsPerInch = value.window->screen()->physicalDotsPerInch();
757 if (pixelsPerInch < 0) {
758 // It can return garbage when run in a XVFB server (Virtual Framebuffer 'fake' X server)
759 pixelsPerInch = 72;
760 }
762 d->setPixelsPerMm(pixelsPerInch / 25.4);
763 }
764 }
767void DirectionalDragAreaPrivate::setPixelsPerMm(qreal pixelsPerMm)
769 dampedScenePos.setMaxDelta(1. * pixelsPerMm);
770 setDistanceThreshold(4. * pixelsPerMm);
771 maxDistance = 10. * pixelsPerMm;
774//************************** ActiveTouchesInfo **************************
776ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
777 : m_timeSource(timeSource)
781void ActiveTouchesInfo::update(QTouchEvent *event)
783 if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
784 // nothing to update
785 #if ACTIVETOUCHESINFO_DEBUG
786 qDebug("[DDA::ActiveTouchesInfo] Nothing to Update");
787 #endif
788 return;
789 }
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());
798 }
799 }
802#if ACTIVETOUCHESINFO_DEBUG
803QString ActiveTouchesInfo::toString()
805 QString string = "(";
807 {
808 QTextStream stream(&string);
809 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
810 stream << "(id=" << touchInfo->id << ",startTime=" << touchInfo->startTime << ")";
811 return true;
812 });
813 }
815 string.append(")");
817 return string;
819#endif // ACTIVETOUCHESINFO_DEBUG
821void 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());
829 #endif
832qint64 ActiveTouchesInfo::touchStartTime(int touchId)
834 qint64 result = -1;
836 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
837 if (touchId == touchInfo->id) {
838 result = touchInfo->startTime;
839 return false;
840 } else {
841 return true;
842 }
843 });
845 Q_ASSERT(result != -1);
846 return result;
849void ActiveTouchesInfo::removeTouchPoint(int touchId)
851 m_touchInfoPool.forEach([&](Pool<ActiveTouchInfo>::Iterator &touchInfo) {
852 if (touchId == touchInfo->id) {
853 m_touchInfoPool.freeSlot(touchInfo);
854 return false;
855 } else {
856 return true;
857 }
858 });
860 #if ACTIVETOUCHESINFO_DEBUG
861 qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
862 #endif
865qint64 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;
874 }
875 return true;
876 });
878 return highestStartTime;
881void DirectionalDragAreaPrivate::updateSceneDirectionVector()
883 QPointF localOrigin(0., 0.);
884 QPointF localDirection;
885 switch (direction) {
886 case Direction::Upwards:
887 localDirection.rx() = 0.;
888 localDirection.ry() = -1.;
889 break;
890 case Direction::Downwards:
891 case Direction::Vertical:
892 localDirection.rx() = 0.;
893 localDirection.ry() = 1;
894 break;
895 case Direction::Leftwards:
896 localDirection.rx() = -1.;
897 localDirection.ry() = 0.;
898 break;
899 default: // Direction::Rightwards || Direction.Horizontal
900 localDirection.rx() = 1.;
901 localDirection.ry() = 0.;
902 break;
903 }
904 QPointF sceneOrigin = q->mapToScene(localOrigin);
905 QPointF sceneDirection = q->mapToScene(localDirection);
906 sceneDirectionVector = sceneDirection - sceneOrigin;
909qreal DirectionalDragAreaPrivate::projectOntoDirectionVector(const QPointF sceneVector) const
911 // same as dot product as sceneDirectionVector is a unit vector
912 return sceneVector.x() * sceneDirectionVector.x() +
913 sceneVector.y() * sceneDirectionVector.y();
916DirectionalDragAreaPrivate::DirectionalDragAreaPrivate(DirectionalDragArea *q)
917 : q(q)
918 , status(WaitingForTouch)
919 , sceneDistance(0)
920 , touchId(-1)
921 , direction(Direction::Rightwards)
922 , distanceThreshold(0)
923 , distanceThresholdSquared(0.)
924 , maxTime(400)
925 , compositionTime(60)
926 , immediateRecognition(false)
927 , recognitionTimer(nullptr)
928 , timeSource(new RealTimeSource)
929 , activeTouches(timeSource)
930 , monitorOnly(false)