/*
* Copyright (C) 2013-2015 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#define ACTIVETOUCHESINFO_DEBUG 0
#define DIRECTIONALDRAGAREA_DEBUG 0
#include "DirectionalDragArea.h"
#include
#include
#include
#include
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-pedantic"
#include
#pragma GCC diagnostic pop
// local
#include "TouchOwnershipEvent.h"
#include "TouchRegistry.h"
#include "UnownedTouchEvent.h"
#include "DirectionalDragArea_p.h"
using namespace UbuntuGestures;
#if DIRECTIONALDRAGAREA_DEBUG
#define ddaDebug(params) qDebug().nospace() << "[DDA(" << qPrintable(objectName()) << ")] " << params
#include "DebugHelpers.h"
namespace {
const char *statusToString(DirectionalDragAreaPrivate::Status status)
{
if (status == DirectionalDragAreaPrivate::WaitingForTouch) {
return "WaitingForTouch";
} else if (status == DirectionalDragAreaPrivate::Undecided) {
return "Undecided";
} else {
return "Recognized";
}
}
} // namespace {
#else // DIRECTIONALDRAGAREA_DEBUG
#define ddaDebug(params) ((void)0)
#endif // DIRECTIONALDRAGAREA_DEBUG
DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
: QQuickItem(parent)
, d(new DirectionalDragAreaPrivate(this))
{
d->setRecognitionTimer(new Timer(this));
d->recognitionTimer->setInterval(d->maxTime);
d->recognitionTimer->setSingleShot(true);
connect(this, &QQuickItem::enabledChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
connect(this, &QQuickItem::visibleChanged, d, &DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible);
}
Direction::Type DirectionalDragArea::direction() const
{
return d->direction;
}
void DirectionalDragArea::setDirection(Direction::Type direction)
{
if (direction != d->direction) {
d->direction = direction;
Q_EMIT directionChanged(d->direction);
}
}
void DirectionalDragAreaPrivate::setDistanceThreshold(qreal value)
{
if (distanceThreshold != value) {
distanceThreshold = value;
distanceThresholdSquared = distanceThreshold * distanceThreshold;
}
}
void DirectionalDragAreaPrivate::setMaxTime(int value)
{
if (maxTime != value) {
maxTime = value;
recognitionTimer->setInterval(maxTime);
}
}
void DirectionalDragAreaPrivate::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
{
int interval = 0;
bool timerWasRunning = false;
bool wasSingleShot = false;
// can be null when called from the constructor
if (recognitionTimer) {
interval = recognitionTimer->interval();
timerWasRunning = recognitionTimer->isRunning();
if (recognitionTimer->parent() == this) {
delete recognitionTimer;
}
}
recognitionTimer = timer;
timer->setInterval(interval);
timer->setSingleShot(wasSingleShot);
connect(timer, &UbuntuGestures::AbstractTimer::timeout,
this, &DirectionalDragAreaPrivate::rejectGesture);
if (timerWasRunning) {
recognitionTimer->start();
}
}
void DirectionalDragAreaPrivate::setTimeSource(const SharedTimeSource &timeSource)
{
this->timeSource = timeSource;
activeTouches.m_timeSource = timeSource;
}
qreal DirectionalDragArea::distance() const
{
if (Direction::isHorizontal(d->direction)) {
return d->publicPos.x() - d->startPos.x();
} else {
return d->publicPos.y() - d->startPos.y();
}
}
void DirectionalDragAreaPrivate::updateSceneDistance()
{
QPointF totalMovement = publicScenePos - startScenePos;
sceneDistance = projectOntoDirectionVector(totalMovement);
}
qreal DirectionalDragArea::sceneDistance() const
{
return d->sceneDistance;
}
qreal DirectionalDragArea::touchX() const
{
return d->publicPos.x();
}
qreal DirectionalDragArea::touchY() const
{
return d->publicPos.y();
}
qreal DirectionalDragArea::touchSceneX() const
{
return d->publicScenePos.x();
}
qreal DirectionalDragArea::touchSceneY() const
{
return d->publicScenePos.y();
}
bool DirectionalDragArea::dragging() const
{
return d->status == DirectionalDragAreaPrivate::Recognized;
}
bool DirectionalDragArea::pressed() const
{
return d->status != DirectionalDragAreaPrivate::WaitingForTouch;
}
bool DirectionalDragArea::immediateRecognition() const
{
return d->immediateRecognition;
}
void DirectionalDragArea::setImmediateRecognition(bool enabled)
{
if (d->immediateRecognition != enabled) {
d->immediateRecognition = enabled;
Q_EMIT immediateRecognitionChanged(enabled);
}
}
void DirectionalDragArea::removeTimeConstraints()
{
d->setMaxTime(60 * 60 * 1000);
d->compositionTime = 0;
ddaDebug("removed time constraints");
}
bool DirectionalDragArea::event(QEvent *event)
{
if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
d->touchOwnershipEvent(static_cast(event));
return true;
} else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
d->unownedTouchEvent(static_cast(event));
return true;
} else {
return QQuickItem::event(event);
}
}
void DirectionalDragAreaPrivate::touchOwnershipEvent(TouchOwnershipEvent *event)
{
if (event->gained()) {
QVector ids;
ids.append(event->touchId());
ddaDebug("grabbing touch");
q->grabTouchPoints(ids);
// Work around for Qt bug. If we grab a touch that is being used for mouse pointer
// emulation it will cause the emulation logic to go nuts.
// Thus we have to also grab the mouse in this case.
//
// The fix for this bug has landed in Qt 5.4 (https://codereview.qt-project.org/96887)
// TODO: Remove this workaround once we start using Qt 5.4
if (q->window()) {
QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(q->window());
if (windowPrivate->touchMouseId == event->touchId() && q->window()->mouseGrabberItem()) {
ddaDebug("removing mouse grabber");
q->window()->mouseGrabberItem()->ungrabMouse();
}
}
} else {
// We still wanna know when it ends for keeping the composition time window up-to-date
TouchRegistry::instance()->addTouchWatcher(touchId, q);
setStatus(WaitingForTouch);
}
}
void DirectionalDragAreaPrivate::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
{
QTouchEvent *event = unownedTouchEvent->touchEvent();
Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
ddaDebug("Unowned " << timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
switch (status) {
case WaitingForTouch:
// do nothing
break;
case Undecided:
Q_ASSERT(q->isEnabled() && q->isVisible());
unownedTouchEvent_undecided(unownedTouchEvent);
break;
default: // Recognized:
// do nothing
break;
}
activeTouches.update(event);
}
void DirectionalDragAreaPrivate::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
{
const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
if (!touchPoint) {
qCritical() << "DirectionalDragArea[status=Undecided]: touch " << touchId
<< "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
"Considering it as released.";
TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
setStatus(WaitingForTouch);
return;
}
const QPointF &touchScenePos = touchPoint->scenePos();
if (touchPoint->state() == Qt::TouchPointReleased) {
// touch has ended before recognition concluded
ddaDebug("Touch has ended before recognition concluded");
TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
setStatus(WaitingForTouch);
return;
}
previousDampedScenePos.setX(dampedScenePos.x());
previousDampedScenePos.setY(dampedScenePos.y());
dampedScenePos.update(touchScenePos);
if (!movingInRightDirection()) {
ddaDebug("Rejecting gesture because touch point is moving in the wrong direction.");
TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
// We still wanna know when it ends for keeping the composition time window up-to-date
TouchRegistry::instance()->addTouchWatcher(touchId, q);
setStatus(WaitingForTouch);
return;
}
if (isWithinTouchCompositionWindow()) {
// There's still time for some new touch to appear and ruin our party as it would be combined
// with our touchId one and therefore deny the possibility of a single-finger gesture.
ddaDebug("Sill within composition window. Let's wait more.");
return;
}
if (movedFarEnoughAlongGestureAxis()) {
TouchRegistry::instance()->requestTouchOwnership(touchId, q);
setStatus(Recognized);
setPublicPos(touchPoint->pos());
setPublicScenePos(touchScenePos);
} else if (isPastMaxDistance()) {
ddaDebug("Rejecting gesture because it went farther than maxDistance without getting recognized.");
TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
// We still wanna know when it ends for keeping the composition time window up-to-date
TouchRegistry::instance()->addTouchWatcher(touchId, q);
setStatus(WaitingForTouch);
} else {
ddaDebug("Didn't move far enough yet. Let's wait more.");
}
}
void DirectionalDragArea::touchEvent(QTouchEvent *event)
{
// TODO: Consider when more than one touch starts in the same event (although it's not possible
// with Mir's android-input). Have to track them all. Consider it a plus/bonus.
ddaDebug(d->timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
if (!isEnabled() || !isVisible()) {
QQuickItem::touchEvent(event);
return;
}
switch (d->status) {
case DirectionalDragAreaPrivate::WaitingForTouch:
d->touchEvent_absent(event);
break;
case DirectionalDragAreaPrivate::Undecided:
d->touchEvent_undecided(event);
break;
default: // Recognized:
d->touchEvent_recognized(event);
break;
}
d->activeTouches.update(event);
}
void DirectionalDragAreaPrivate::touchEvent_absent(QTouchEvent *event)
{
// TODO: accept/reject is for the whole event, not per touch id. See how that affects us.
if (!event->touchPointStates().testFlag(Qt::TouchPointPressed)) {
// Nothing to see here. No touch starting in this event.
return;
}
// to be proven wrong, if that's the case
bool allGood = true;
if (isWithinTouchCompositionWindow()) {
// too close to the last touch start. So we consider them as starting roughly at the same time.
// Can't be a single-touch gesture.
ddaDebug("A new touch point came in but we're still within time composition window. Ignoring it.");
allGood = false;
}
const QList &touchPoints = event->touchPoints();
const QTouchEvent::TouchPoint *newTouchPoint = nullptr;
for (int i = 0; i < touchPoints.count() && allGood; ++i) {
const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
if (touchPoint.state() == Qt::TouchPointPressed) {
if (newTouchPoint) {
// more than one touch starting in this QTouchEvent. Can't be a single-touch gesture
allGood = false;
} else {
// that's our candidate
newTouchPoint = &touchPoint;
}
}
}
if (allGood) {
allGood = sanityCheckRecognitionProperties();
if (!allGood) {
qWarning("DirectionalDragArea: recognition properties are wrongly set. Gesture recognition"
" is impossible");
}
}
if (allGood) {
Q_ASSERT(newTouchPoint);
startPos = newTouchPoint->pos();
startScenePos = newTouchPoint->scenePos();
touchId = newTouchPoint->id();
dampedScenePos.reset(startScenePos);
setPublicPos(startPos);
setPublicScenePos(startScenePos);
updateSceneDirectionVector();
if (recognitionIsDisabled()) {
// Behave like a dumb TouchArea
ddaDebug("Gesture recognition is disabled. Requesting touch ownership immediately.");
TouchRegistry::instance()->requestTouchOwnership(touchId, q);
setStatus(Recognized);
event->accept();
} else {
// just monitor the touch points for now.
TouchRegistry::instance()->addCandidateOwnerForTouch(touchId, q);
setStatus(Undecided);
// Let the item below have it. We will monitor it and grab it later if a gesture
// gets recognized.
event->ignore();
}
} else {
watchPressedTouchPoints(touchPoints);
event->ignore();
}
}
void DirectionalDragAreaPrivate::touchEvent_undecided(QTouchEvent *event)
{
Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
// We're not interested in new touch points. We already have our candidate (touchId).
// But we do want to know when those new touches end for keeping the composition time
// window up-to-date
event->ignore();
watchPressedTouchPoints(event->touchPoints());
if (event->touchPointStates().testFlag(Qt::TouchPointPressed) && isWithinTouchCompositionWindow()) {
// multi-finger drags are not accepted
ddaDebug("Multi-finger drags are not accepted");
TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
// We still wanna know when it ends for keeping the composition time window up-to-date
TouchRegistry::instance()->addTouchWatcher(touchId, q);
setStatus(WaitingForTouch);
}
}
void DirectionalDragAreaPrivate::touchEvent_recognized(QTouchEvent *event)
{
const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
if (!touchPoint) {
qCritical() << "DirectionalDragArea[status=Recognized]: touch " << touchId
<< "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
"Considering it as released.";
setStatus(WaitingForTouch);
} else {
setPublicPos(touchPoint->pos());
setPublicScenePos(touchPoint->scenePos());
if (touchPoint->state() == Qt::TouchPointReleased) {
setStatus(WaitingForTouch);
}
}
}
void DirectionalDragAreaPrivate::watchPressedTouchPoints(const QList &touchPoints)
{
for (int i = 0; i < touchPoints.count(); ++i) {
const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
if (touchPoint.state() == Qt::TouchPointPressed) {
TouchRegistry::instance()->addTouchWatcher(touchPoint.id(), q);
}
}
}
bool DirectionalDragAreaPrivate::recognitionIsDisabled() const
{
return immediateRecognition || (distanceThreshold <= 0 && compositionTime <= 0);
}
bool DirectionalDragAreaPrivate::sanityCheckRecognitionProperties()
{
return recognitionIsDisabled()
|| (distanceThreshold < maxDistance && compositionTime < maxTime);
}
const QTouchEvent::TouchPoint *DirectionalDragAreaPrivate::fetchTargetTouchPoint(QTouchEvent *event)
{
const QList &touchPoints = event->touchPoints();
const QTouchEvent::TouchPoint *touchPoint = 0;
for (int i = 0; i < touchPoints.size(); ++i) {
if (touchPoints.at(i).id() == touchId) {
touchPoint = &touchPoints.at(i);
break;
}
}
return touchPoint;
}
bool DirectionalDragAreaPrivate::movingInRightDirection() const
{
if (direction == Direction::Horizontal || direction == Direction::Vertical) {
return true;
} else {
QPointF movementVector(dampedScenePos.x() - previousDampedScenePos.x(),
dampedScenePos.y() - previousDampedScenePos.y());
qreal scalarProjection = projectOntoDirectionVector(movementVector);
return scalarProjection >= 0.;
}
}
bool DirectionalDragAreaPrivate::movedFarEnoughAlongGestureAxis() const
{
if (distanceThreshold <= 0.) {
// distance threshold check is disabled
return true;
} else {
QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
dampedScenePos.y() - startScenePos.y());
qreal scalarProjection = projectOntoDirectionVector(totalMovement);
ddaDebug(" movedFarEnoughAlongGestureAxis: scalarProjection=" << scalarProjection
<< ", distanceThreshold=" << distanceThreshold);
if (direction == Direction::Horizontal || direction == Direction::Vertical) {
return qAbs(scalarProjection) > distanceThreshold;
} else {
return scalarProjection > distanceThreshold;
}
}
}
bool DirectionalDragAreaPrivate::isPastMaxDistance() const
{
QPointF totalMovement(dampedScenePos.x() - startScenePos.x(),
dampedScenePos.y() - startScenePos.y());
qreal squaredDistance = totalMovement.x()*totalMovement.x() + totalMovement.y()*totalMovement.y();
return squaredDistance > maxDistance*maxDistance;
}
void DirectionalDragAreaPrivate::giveUpIfDisabledOrInvisible()
{
if (!q->isEnabled() || !q->isVisible()) {
if (status == Undecided) {
TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
// We still wanna know when it ends for keeping the composition time window up-to-date
TouchRegistry::instance()->addTouchWatcher(touchId, q);
}
if (status != WaitingForTouch) {
ddaDebug("Resetting status because got disabled or made invisible");
setStatus(WaitingForTouch);
}
}
}
void DirectionalDragAreaPrivate::rejectGesture()
{
if (status == Undecided) {
ddaDebug("Rejecting gesture because it's taking too long to drag beyond the threshold.");
TouchRegistry::instance()->removeCandidateOwnerForTouch(touchId, q);
// We still wanna know when it ends for keeping the composition time window up-to-date
TouchRegistry::instance()->addTouchWatcher(touchId, q);
setStatus(WaitingForTouch);
}
}
void DirectionalDragAreaPrivate::setStatus(Status newStatus)
{
if (newStatus == status)
return;
Status oldStatus = status;
if (oldStatus == Undecided) {
recognitionTimer->stop();
}
status = newStatus;
Q_EMIT statusChanged(status);
ddaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
switch (newStatus) {
case WaitingForTouch:
if (oldStatus == Recognized) {
Q_EMIT q->draggingChanged(false);
}
Q_EMIT q->pressedChanged(false);
break;
case Undecided:
recognitionTimer->start();
Q_EMIT q->pressedChanged(true);
break;
case Recognized:
Q_EMIT q->draggingChanged(true);
break;
default:
// no-op
break;
}
}
void DirectionalDragAreaPrivate::setPublicPos(const QPointF point)
{
bool xChanged = publicPos.x() != point.x();
bool yChanged = publicPos.y() != point.y();
// Public position should not get updated while the gesture is still being recognized
// (ie, Undecided status).
Q_ASSERT(status == WaitingForTouch || status == Recognized);
if (status == Recognized && !recognitionIsDisabled()) {
// When the gesture finally gets recognized, the finger will likely be
// reasonably far from the edge. If we made the contentX immediately
// follow the finger position it would be visually unpleasant as it
// would appear right next to the user's finger out of nowhere (ie,
// it would jump). Instead, we make contentX go towards the user's
// finger in several steps. ie., in an animated way.
QPointF delta = point - publicPos;
// the trick is not to go all the way (1.0) as it would cause a sudden jump
publicPos.rx() += 0.4 * delta.x();
publicPos.ry() += 0.4 * delta.y();
} else {
// no smoothing when initializing or if gesture recognition was immediate as there will
// be no jump.
publicPos = point;
}
if (xChanged) {
Q_EMIT q->touchXChanged(publicPos.x());
if (Direction::isHorizontal(direction))
Q_EMIT q->distanceChanged(q->distance());
}
if (yChanged) {
Q_EMIT q->touchYChanged(publicPos.y());
if (Direction::isVertical(direction))
Q_EMIT q->distanceChanged(q->distance());
}
}
void DirectionalDragAreaPrivate::setPublicScenePos(const QPointF point)
{
bool xChanged = publicScenePos.x() != point.x();
bool yChanged = publicScenePos.y() != point.y();
if (!xChanged && !yChanged)
return;
// Public position should not get updated while the gesture is still being recognized
// (ie, Undecided status).
Q_ASSERT(status == WaitingForTouch || status == Recognized);
qreal oldSceneDistance = sceneDistance;
if (status == Recognized && !recognitionIsDisabled()) {
// When the gesture finally gets recognized, the finger will likely be
// reasonably far from the edge. If we made the contentX immediately
// follow the finger position it would be visually unpleasant as it
// would appear right next to the user's finger out of nowhere (ie,
// it would jump). Instead, we make contentX go towards the user's
// finger in several steps. ie., in an animated way.
QPointF delta = point - publicScenePos;
// the trick is not to go all the way (1.0) as it would cause a sudden jump
publicScenePos.rx() += 0.4 * delta.x();
publicScenePos.ry() += 0.4 * delta.y();
} else {
// no smoothing when initializing or if gesture recognition was immediate as there will
// be no jump.
publicScenePos = point;
}
updateSceneDistance();
if (oldSceneDistance != sceneDistance) {
Q_EMIT q->sceneDistanceChanged(sceneDistance);
}
if (xChanged) {
Q_EMIT q->touchSceneXChanged(publicScenePos.x());
}
if (yChanged) {
Q_EMIT q->touchSceneYChanged(publicScenePos.y());
}
}
bool DirectionalDragAreaPrivate::isWithinTouchCompositionWindow()
{
return
compositionTime > 0 &&
!activeTouches.isEmpty() &&
timeSource->msecsSinceReference() <=
activeTouches.mostRecentStartTime() + (qint64)compositionTime;
}
void DirectionalDragArea::itemChange(ItemChange change, const ItemChangeData &value)
{
if (change == QQuickItem::ItemSceneChange) {
if (value.window != nullptr) {
value.window->installEventFilter(TouchRegistry::instance());
// TODO: Handle window->screen() changes (ie window changing screens)
qreal pixelsPerMm = value.window->screen()->physicalDotsPerInch() / 25.4;
d->setPixelsPerMm(pixelsPerMm);
}
}
}
void DirectionalDragAreaPrivate::setPixelsPerMm(qreal pixelsPerMm)
{
dampedScenePos.setMaxDelta(1. * pixelsPerMm);
setDistanceThreshold(4. * pixelsPerMm);
maxDistance = 10. * pixelsPerMm;
}
//************************** ActiveTouchesInfo **************************
ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
: m_timeSource(timeSource)
{
}
void ActiveTouchesInfo::update(QTouchEvent *event)
{
if (!(event->touchPointStates() & (Qt::TouchPointPressed | Qt::TouchPointReleased))) {
// nothing to update
#if ACTIVETOUCHESINFO_DEBUG
qDebug("[DDA::ActiveTouchesInfo] Nothing to Update");
#endif
return;
}
const QList &touchPoints = event->touchPoints();
for (int i = 0; i < touchPoints.count(); ++i) {
const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
if (touchPoint.state() == Qt::TouchPointPressed) {
addTouchPoint(touchPoint.id());
} else if (touchPoint.state() == Qt::TouchPointReleased) {
removeTouchPoint(touchPoint.id());
}
}
}
#if ACTIVETOUCHESINFO_DEBUG
QString ActiveTouchesInfo::toString()
{
QString string = "(";
{
QTextStream stream(&string);
m_touchInfoPool.forEach([&](Pool::Iterator &touchInfo) {
stream << "(id=" << touchInfo->id << ",startTime=" << touchInfo->startTime << ")";
return true;
});
}
string.append(")");
return string;
}
#endif // ACTIVETOUCHESINFO_DEBUG
void ActiveTouchesInfo::addTouchPoint(int touchId)
{
ActiveTouchInfo &activeTouchInfo = m_touchInfoPool.getEmptySlot();
activeTouchInfo.id = touchId;
activeTouchInfo.startTime = m_timeSource->msecsSinceReference();
#if ACTIVETOUCHESINFO_DEBUG
qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
#endif
}
qint64 ActiveTouchesInfo::touchStartTime(int touchId)
{
qint64 result = -1;
m_touchInfoPool.forEach([&](Pool::Iterator &touchInfo) {
if (touchId == touchInfo->id) {
result = touchInfo->startTime;
return false;
} else {
return true;
}
});
Q_ASSERT(result != -1);
return result;
}
void ActiveTouchesInfo::removeTouchPoint(int touchId)
{
m_touchInfoPool.forEach([&](Pool::Iterator &touchInfo) {
if (touchId == touchInfo->id) {
m_touchInfoPool.freeSlot(touchInfo);
return false;
} else {
return true;
}
});
#if ACTIVETOUCHESINFO_DEBUG
qDebug() << "[DDA::ActiveTouchesInfo]" << qPrintable(toString());
#endif
}
qint64 ActiveTouchesInfo::mostRecentStartTime()
{
Q_ASSERT(!m_touchInfoPool.isEmpty());
qint64 highestStartTime = -1;
m_touchInfoPool.forEach([&](Pool::Iterator &activeTouchInfo) {
if (activeTouchInfo->startTime > highestStartTime) {
highestStartTime = activeTouchInfo->startTime;
}
return true;
});
return highestStartTime;
}
void DirectionalDragAreaPrivate::updateSceneDirectionVector()
{
QPointF localOrigin(0., 0.);
QPointF localDirection;
switch (direction) {
case Direction::Upwards:
localDirection.rx() = 0.;
localDirection.ry() = -1.;
break;
case Direction::Downwards:
case Direction::Vertical:
localDirection.rx() = 0.;
localDirection.ry() = 1;
break;
case Direction::Leftwards:
localDirection.rx() = -1.;
localDirection.ry() = 0.;
break;
default: // Direction::Rightwards || Direction.Horizontal
localDirection.rx() = 1.;
localDirection.ry() = 0.;
break;
}
QPointF sceneOrigin = q->mapToScene(localOrigin);
QPointF sceneDirection = q->mapToScene(localDirection);
sceneDirectionVector = sceneDirection - sceneOrigin;
}
qreal DirectionalDragAreaPrivate::projectOntoDirectionVector(const QPointF sceneVector) const
{
// same as dot product as sceneDirectionVector is a unit vector
return sceneVector.x() * sceneDirectionVector.x() +
sceneVector.y() * sceneDirectionVector.y();
}
DirectionalDragAreaPrivate::DirectionalDragAreaPrivate(DirectionalDragArea *q)
: q(q)
, status(WaitingForTouch)
, sceneDistance(0)
, touchId(-1)
, direction(Direction::Rightwards)
, distanceThreshold(0)
, distanceThresholdSquared(0.)
, maxTime(400)
, compositionTime(60)
, immediateRecognition(false)
, recognitionTimer(nullptr)
, timeSource(new RealTimeSource)
, activeTouches(timeSource)
{
}