/*
* Copyright (C) 2013-2014 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
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-pedantic"
#include
#pragma GCC diagnostic pop
// local
#include "TouchOwnershipEvent.h"
#include "TouchRegistry.h"
#include "UnownedTouchEvent.h"
using namespace UbuntuGestures;
#if DIRECTIONALDRAGAREA_DEBUG
#define ddaDebug(params) qDebug().nospace() << "[DDA(" << qPrintable(objectName()) << ")] " << params
#include "DebugHelpers.h"
namespace {
const char *statusToString(DirectionalDragArea::Status status)
{
if (status == DirectionalDragArea::WaitingForTouch) {
return "WaitingForTouch";
} else if (status == DirectionalDragArea::Undecided) {
return "Undecided";
} else {
return "Recognized";
}
}
} // namespace {
#else // DIRECTIONALDRAGAREA_DEBUG
#define ddaDebug(params) ((void)0)
#endif // DIRECTIONALDRAGAREA_DEBUG
DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
: QQuickItem(parent)
, m_status(WaitingForTouch)
, m_sceneDistance(0)
, m_touchId(-1)
, m_direction(Direction::Rightwards)
, m_wideningAngle(0)
, m_wideningFactor(0)
, m_distanceThreshold(0)
, m_distanceThresholdSquared(0.)
, m_minSpeed(0)
, m_maxSilenceTime(200)
, m_silenceTime(0)
, m_compositionTime(60)
, m_numSamplesOnLastSpeedCheck(0)
, m_recognitionTimer(0)
, m_velocityCalculator(0)
, m_timeSource(new RealTimeSource)
, m_activeTouches(m_timeSource)
{
setRecognitionTimer(new Timer(this));
m_recognitionTimer->setInterval(60);
m_recognitionTimer->setSingleShot(false);
m_velocityCalculator = new AxisVelocityCalculator(this);
connect(this, &QQuickItem::enabledChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
connect(this, &QQuickItem::visibleChanged, this, &DirectionalDragArea::giveUpIfDisabledOrInvisible);
}
Direction::Type DirectionalDragArea::direction() const
{
return m_direction;
}
void DirectionalDragArea::setDirection(Direction::Type direction)
{
if (direction != m_direction) {
m_direction = direction;
Q_EMIT directionChanged(m_direction);
}
}
void DirectionalDragArea::setMaxDeviation(qreal value)
{
if (m_dampedScenePos.maxDelta() != value) {
m_dampedScenePos.setMaxDelta(value);
Q_EMIT maxDeviationChanged(value);
}
}
qreal DirectionalDragArea::wideningAngle() const
{
return m_wideningAngle;
}
void DirectionalDragArea::setWideningAngle(qreal angle)
{
if (angle == m_wideningAngle)
return;
m_wideningAngle = angle;
// wideningFactor = pow(cosine(angle), 2)
{
qreal angleRadians = angle * M_PI / 180.0;
m_wideningFactor = qCos(angleRadians);
m_wideningFactor = m_wideningFactor * m_wideningFactor;
}
Q_EMIT wideningAngleChanged(angle);
}
void DirectionalDragArea::setDistanceThreshold(qreal value)
{
if (m_distanceThreshold != value) {
m_distanceThreshold = value;
m_distanceThresholdSquared = m_distanceThreshold * m_distanceThreshold;
Q_EMIT distanceThresholdChanged(value);
}
}
void DirectionalDragArea::setMinSpeed(qreal value)
{
if (m_minSpeed != value) {
m_minSpeed = value;
Q_EMIT minSpeedChanged(value);
}
}
void DirectionalDragArea::setMaxSilenceTime(int value)
{
if (m_maxSilenceTime != value) {
m_maxSilenceTime = value;
Q_EMIT maxSilenceTimeChanged(value);
}
}
void DirectionalDragArea::setCompositionTime(int value)
{
if (m_compositionTime != value) {
m_compositionTime = value;
Q_EMIT compositionTimeChanged(value);
}
}
void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
{
int interval = 0;
bool timerWasRunning = false;
bool wasSingleShot = false;
// can be null when called from the constructor
if (m_recognitionTimer) {
interval = m_recognitionTimer->interval();
timerWasRunning = m_recognitionTimer->isRunning();
if (m_recognitionTimer->parent() == this) {
delete m_recognitionTimer;
}
}
m_recognitionTimer = timer;
timer->setInterval(interval);
timer->setSingleShot(wasSingleShot);
connect(timer, &UbuntuGestures::AbstractTimer::timeout,
this, &DirectionalDragArea::checkSpeed);
if (timerWasRunning) {
m_recognitionTimer->start();
}
}
void DirectionalDragArea::setTimeSource(const SharedTimeSource &timeSource)
{
m_timeSource = timeSource;
m_velocityCalculator->setTimeSource(timeSource);
m_activeTouches.m_timeSource = timeSource;
}
qreal DirectionalDragArea::distance() const
{
if (Direction::isHorizontal(m_direction)) {
return m_previousPos.x() - m_startPos.x();
} else {
return m_previousPos.y() - m_startPos.y();
}
}
void DirectionalDragArea::updateSceneDistance()
{
QPointF totalMovement = m_previousScenePos - m_startScenePos;
m_sceneDistance = projectOntoDirectionVector(totalMovement);
}
qreal DirectionalDragArea::sceneDistance() const
{
return m_sceneDistance;
}
qreal DirectionalDragArea::touchX() const
{
return m_previousPos.x();
}
qreal DirectionalDragArea::touchY() const
{
return m_previousPos.y();
}
qreal DirectionalDragArea::touchSceneX() const
{
return m_previousScenePos.x();
}
qreal DirectionalDragArea::touchSceneY() const
{
return m_previousScenePos.y();
}
bool DirectionalDragArea::event(QEvent *event)
{
if (event->type() == TouchOwnershipEvent::touchOwnershipEventType()) {
touchOwnershipEvent(static_cast(event));
return true;
} else if (event->type() == UnownedTouchEvent::unownedTouchEventType()) {
unownedTouchEvent(static_cast(event));
return true;
} else {
return QQuickItem::event(event);
}
}
void DirectionalDragArea::touchOwnershipEvent(TouchOwnershipEvent *event)
{
if (event->gained()) {
QVector ids;
ids.append(event->touchId());
ddaDebug("grabbing touch");
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 (window()) {
QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(window());
if (windowPrivate->touchMouseId == event->touchId() && window()->mouseGrabberItem()) {
ddaDebug("removing mouse grabber");
window()->mouseGrabberItem()->ungrabMouse();
}
}
} else {
// We still wanna know when it ends for keeping the composition time window up-to-date
TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
setStatus(WaitingForTouch);
}
}
void DirectionalDragArea::unownedTouchEvent(UnownedTouchEvent *unownedTouchEvent)
{
QTouchEvent *event = unownedTouchEvent->touchEvent();
Q_ASSERT(!event->touchPointStates().testFlag(Qt::TouchPointPressed));
ddaDebug("Unowned " << m_timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
switch (m_status) {
case WaitingForTouch:
// do nothing
break;
case Undecided:
Q_ASSERT(isEnabled() && isVisible());
unownedTouchEvent_undecided(unownedTouchEvent);
break;
default: // Recognized:
// do nothing
break;
}
m_activeTouches.update(event);
}
void DirectionalDragArea::unownedTouchEvent_undecided(UnownedTouchEvent *unownedTouchEvent)
{
const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(unownedTouchEvent->touchEvent());
if (!touchPoint) {
qCritical() << "DirectionalDragArea[status=Undecided]: touch " << m_touchId
<< "missing from UnownedTouchEvent without first reaching state Qt::TouchPointReleased. "
"Considering it as released.";
TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
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(m_touchId, this);
emitSignalIfTapped();
setStatus(WaitingForTouch);
return;
}
m_previousDampedScenePos.setX(m_dampedScenePos.x());
m_previousDampedScenePos.setY(m_dampedScenePos.y());
m_dampedScenePos.update(touchScenePos);
updateVelocityCalculator(touchScenePos);
if (!pointInsideAllowedArea()) {
ddaDebug("Rejecting gesture because touch point is outside allowed area.");
TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
// We still wanna know when it ends for keeping the composition time window up-to-date
TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
setStatus(WaitingForTouch);
return;
}
if (!movingInRightDirection()) {
ddaDebug("Rejecting gesture because touch point is moving in the wrong direction.");
TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
// We still wanna know when it ends for keeping the composition time window up-to-date
TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
setStatus(WaitingForTouch);
return;
}
setPreviousPos(touchPoint->pos());
setPreviousScenePos(touchScenePos);
if (isWithinTouchCompositionWindow()) {
// There's still time for some new touch to appear and ruin our party as it would be combined
// with our m_touchId one and therefore deny the possibility of a single-finger gesture.
ddaDebug("Sill within composition window. Let's wait more.");
return;
}
if (movedFarEnough(touchScenePos)) {
TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
setStatus(Recognized);
} 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(m_timeSource->msecsSinceReference() << " " << qPrintable(touchEventToString(event)));
if (!isEnabled() || !isVisible()) {
QQuickItem::touchEvent(event);
return;
}
switch (m_status) {
case WaitingForTouch:
touchEvent_absent(event);
break;
case Undecided:
touchEvent_undecided(event);
break;
default: // Recognized:
touchEvent_recognized(event);
break;
}
m_activeTouches.update(event);
}
void DirectionalDragArea::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
m_touchId = touchPoint.id();
newTouchPoint = &touchPoint;
}
}
}
if (allGood) {
Q_ASSERT(newTouchPoint);
m_startPos = newTouchPoint->pos();
m_startScenePos = newTouchPoint->scenePos();
m_touchId = newTouchPoint->id();
m_dampedScenePos.reset(m_startScenePos);
m_velocityCalculator->setTrackedPosition(0.);
m_velocityCalculator->reset();
m_numSamplesOnLastSpeedCheck = 0;
m_silenceTime = 0;
setPreviousPos(m_startPos);
setPreviousScenePos(m_startScenePos);
updateSceneDirectionVector();
if (recognitionIsDisabled()) {
// Behave like a dumb TouchArea
ddaDebug("Gesture recognition is disabled. Requesting touch ownership immediately.");
TouchRegistry::instance()->requestTouchOwnership(m_touchId, this);
setStatus(Recognized);
event->accept();
} else {
// just monitor the touch points for now.
TouchRegistry::instance()->addCandidateOwnerForTouch(m_touchId, this);
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 DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
{
Q_ASSERT(event->type() == QEvent::TouchBegin);
Q_ASSERT(fetchTargetTouchPoint(event) == nullptr);
// We're not interested in new touch points. We already have our candidate (m_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(m_touchId, this);
// We still wanna know when it ends for keeping the composition time window up-to-date
TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
setStatus(WaitingForTouch);
}
}
void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
{
const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
if (!touchPoint) {
qCritical() << "DirectionalDragArea[status=Recognized]: touch " << m_touchId
<< "missing from QTouchEvent without first reaching state Qt::TouchPointReleased. "
"Considering it as released.";
setStatus(WaitingForTouch);
} else {
setPreviousPos(touchPoint->pos());
setPreviousScenePos(touchPoint->scenePos());
if (touchPoint->state() == Qt::TouchPointReleased) {
emitSignalIfTapped();
setStatus(WaitingForTouch);
}
}
}
void DirectionalDragArea::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(), this);
}
}
}
bool DirectionalDragArea::recognitionIsDisabled() const
{
return distanceThreshold() <= 0 && compositionTime() <= 0;
}
void DirectionalDragArea::emitSignalIfTapped()
{
qint64 touchDuration = m_timeSource->msecsSinceReference() - m_activeTouches.touchStartTime(m_touchId);
if (touchDuration <= maxTapDuration()) {
Q_EMIT tapped();
}
}
const QTouchEvent::TouchPoint *DirectionalDragArea::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() == m_touchId) {
touchPoint = &touchPoints.at(i);
break;
}
}
return touchPoint;
}
bool DirectionalDragArea::pointInsideAllowedArea() const
{
// NB: Using squared values to avoid computing the square root to find
// the length totalMovement
QPointF totalMovement(m_dampedScenePos.x() - m_startScenePos.x(),
m_dampedScenePos.y() - m_startScenePos.y());
qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
totalMovement.y() * totalMovement.y();
if (squaredTotalMovSize == 0.) {
// didn't move
return true;
}
qreal projectedMovement = projectOntoDirectionVector(totalMovement);
qreal cosineAngleSquared = (projectedMovement * projectedMovement) / squaredTotalMovSize;
// Same as:
// angle_between_movement_vector_and_gesture_direction_vector <= widening_angle
return cosineAngleSquared >= m_wideningFactor;
}
bool DirectionalDragArea::movingInRightDirection() const
{
if (m_direction == Direction::Horizontal) {
return true;
} else {
QPointF movementVector(m_dampedScenePos.x() - m_previousDampedScenePos.x(),
m_dampedScenePos.y() - m_previousDampedScenePos.y());
qreal scalarProjection = projectOntoDirectionVector(movementVector);
return scalarProjection >= 0.;
}
}
bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
{
if (m_distanceThreshold <= 0.) {
// distance threshold check is disabled
return true;
} else {
QPointF totalMovement(point.x() - m_startScenePos.x(),
point.y() - m_startScenePos.y());
qreal squaredTotalMovSize = totalMovement.x() * totalMovement.x() +
totalMovement.y() * totalMovement.y();
return squaredTotalMovSize > m_distanceThresholdSquared;
}
}
void DirectionalDragArea::checkSpeed()
{
Q_ASSERT(m_status == Undecided);
if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
qreal speed = qFabs(m_velocityCalculator->calculate());
qreal minSpeedMsecs = m_minSpeed / 1000.0;
if (speed < minSpeedMsecs) {
ddaDebug("Rejecting gesture because it's below minimum speed.");
TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
setStatus(WaitingForTouch);
}
}
if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) {
m_silenceTime += m_recognitionTimer->interval();
if (m_silenceTime > m_maxSilenceTime) {
ddaDebug("Rejecting gesture because its silence time has been exceeded.");
TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
setStatus(WaitingForTouch);
}
} else {
m_silenceTime = 0;
}
m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
}
void DirectionalDragArea::giveUpIfDisabledOrInvisible()
{
if (!isEnabled() || !isVisible()) {
if (m_status == Undecided) {
TouchRegistry::instance()->removeCandidateOwnerForTouch(m_touchId, this);
// We still wanna know when it ends for keeping the composition time window up-to-date
TouchRegistry::instance()->addTouchWatcher(m_touchId, this);
}
if (m_status != WaitingForTouch) {
ddaDebug("Resetting status because got disabled or made invisible");
setStatus(WaitingForTouch);
}
}
}
void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
{
if (newStatus == m_status)
return;
DirectionalDragArea::Status oldStatus = m_status;
if (oldStatus == Undecided) {
m_recognitionTimer->stop();
}
m_status = newStatus;
Q_EMIT statusChanged(m_status);
ddaDebug(statusToString(oldStatus) << " -> " << statusToString(newStatus));
switch (newStatus) {
case WaitingForTouch:
Q_EMIT draggingChanged(false);
break;
case Undecided:
m_recognitionTimer->start();
Q_EMIT draggingChanged(true);
break;
case Recognized:
if (oldStatus == WaitingForTouch)
Q_EMIT draggingChanged(true);
break;
default:
// no-op
break;
}
}
void DirectionalDragArea::setPreviousPos(const QPointF &point)
{
bool xChanged = m_previousPos.x() != point.x();
bool yChanged = m_previousPos.y() != point.y();
m_previousPos = point;
if (xChanged) {
Q_EMIT touchXChanged(point.x());
if (Direction::isHorizontal(m_direction))
Q_EMIT distanceChanged(distance());
}
if (yChanged) {
Q_EMIT touchYChanged(point.y());
if (Direction::isVertical(m_direction))
Q_EMIT distanceChanged(distance());
}
}
void DirectionalDragArea::setPreviousScenePos(const QPointF &point)
{
bool xChanged = m_previousScenePos.x() != point.x();
bool yChanged = m_previousScenePos.y() != point.y();
if (!xChanged && !yChanged)
return;
qreal oldSceneDistance = sceneDistance();
m_previousScenePos = point;
updateSceneDistance();
if (oldSceneDistance != sceneDistance()) {
Q_EMIT sceneDistanceChanged(sceneDistance());
}
if (xChanged) {
Q_EMIT touchSceneXChanged(point.x());
}
if (yChanged) {
Q_EMIT touchSceneYChanged(point.y());
}
}
void DirectionalDragArea::updateVelocityCalculator(const QPointF &scenePos)
{
QPointF totalSceneMovement = scenePos - m_startScenePos;
qreal scalarProjection = projectOntoDirectionVector(totalSceneMovement);
m_velocityCalculator->setTrackedPosition(scalarProjection);
}
bool DirectionalDragArea::isWithinTouchCompositionWindow()
{
return
compositionTime() > 0 &&
!m_activeTouches.isEmpty() &&
m_timeSource->msecsSinceReference() <=
m_activeTouches.mostRecentStartTime() + (qint64)compositionTime();
}
//************************** ActiveTouchesInfo **************************
DirectionalDragArea::ActiveTouchesInfo::ActiveTouchesInfo(const SharedTimeSource &timeSource)
: m_timeSource(timeSource)
{
}
void DirectionalDragArea::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 DirectionalDragArea::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 DirectionalDragArea::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 DirectionalDragArea::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 DirectionalDragArea::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 DirectionalDragArea::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 DirectionalDragArea::updateSceneDirectionVector()
{
QPointF localOrigin(0., 0.);
QPointF localDirection;
switch (m_direction) {
case Direction::Upwards:
localDirection.rx() = 0.;
localDirection.ry() = -1.;
break;
case Direction::Downwards:
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 = mapToScene(localOrigin);
QPointF sceneDirection = mapToScene(localDirection);
m_sceneDirectionVector = sceneDirection - sceneOrigin;
}
qreal DirectionalDragArea::projectOntoDirectionVector(const QPointF &sceneVector) const
{
// same as dot product as m_sceneDirectionVector is a unit vector
return sceneVector.x() * m_sceneDirectionVector.x() +
sceneVector.y() * m_sceneDirectionVector.y();
}