2 * Copyright (C) 2013 Canonical, Ltd.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17#include "DirectionalDragArea.h"
19#include <QtCore/qmath.h>
20#include <QtCore/QTimer>
22// Essentially a QTimer wrapper
23class RecognitionTimer : public UbuntuGestures::AbstractTimer
27 RecognitionTimer(QObject *parent) : UbuntuGestures::AbstractTimer(parent) {
28 m_timer.setSingleShot(false);
29 connect(&m_timer, &QTimer::timeout,
30 this, &UbuntuGestures::AbstractTimer::timeout);
32 virtual int interval() const { return m_timer.interval(); }
33 virtual void setInterval(int msecs) { m_timer.setInterval(msecs); }
34 virtual void start() { m_timer.start(); UbuntuGestures::AbstractTimer::start(); }
35 virtual void stop() { m_timer.stop(); UbuntuGestures::AbstractTimer::stop(); }
40DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
42 , m_status(WaitingForTouch)
44 , m_direction(DirectionalDragArea::Rightwards)
47 , m_distanceThreshold(0)
49 , m_maxSilenceTime(200)
51 , m_numSamplesOnLastSpeedCheck(0)
52 , m_recognitionTimer(0)
53 , m_velocityCalculator(0)
55 setRecognitionTimer(new RecognitionTimer(this));
56 m_recognitionTimer->setInterval(60);
58 m_velocityCalculator = new AxisVelocityCalculator(this);
61DirectionalDragArea::Direction DirectionalDragArea::direction() const
66void DirectionalDragArea::setDirection(DirectionalDragArea::Direction direction)
68 if (direction != m_direction) {
69 m_direction = direction;
70 Q_EMIT directionChanged(m_direction);
74void DirectionalDragArea::setMaxDeviation(qreal value)
76 if (m_dampedPos.maxDelta() != value) {
77 m_dampedPos.setMaxDelta(value);
78 Q_EMIT maxDeviationChanged(value);
82qreal DirectionalDragArea::wideningAngle() const
84 return m_wideningAngle;
87void DirectionalDragArea::setWideningAngle(qreal angle)
89 if (angle == m_wideningAngle)
92 m_wideningAngle = angle;
93 m_wideningFactor = qTan(angle * M_PI / 180.0);
94 Q_EMIT wideningAngleChanged(angle);
97void DirectionalDragArea::setDistanceThreshold(qreal value)
99 if (m_distanceThreshold != value) {
100 m_distanceThreshold = value;
101 Q_EMIT distanceThresholdChanged(value);
105void DirectionalDragArea::setMinSpeed(qreal value)
107 if (m_minSpeed != value) {
109 Q_EMIT minSpeedChanged(value);
113void DirectionalDragArea::setMaxSilenceTime(int value)
115 if (m_maxSilenceTime != value) {
116 m_maxSilenceTime = value;
117 Q_EMIT maxSilenceTimeChanged(value);
121void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
124 bool timerWasRunning = false;
126 // can be null when called from the constructor
127 if (m_recognitionTimer) {
128 interval = m_recognitionTimer->interval();
129 timerWasRunning = m_recognitionTimer->isRunning();
130 if (m_recognitionTimer->parent() == this) {
131 delete m_recognitionTimer;
135 m_recognitionTimer = timer;
136 timer->setInterval(interval);
137 connect(timer, &UbuntuGestures::AbstractTimer::timeout,
138 this, &DirectionalDragArea::checkSpeed);
139 if (timerWasRunning) {
140 m_recognitionTimer->start();
144void DirectionalDragArea::setAxisVelocityCalculator(AxisVelocityCalculator *newVelCalc)
146 if (m_velocityCalculator->parent() == this)
147 delete m_velocityCalculator;
149 m_velocityCalculator = newVelCalc;
152qreal DirectionalDragArea::distance() const
154 if (directionIsHorizontal()) {
155 return m_previousPos.x() - m_startPos.x();
157 return m_previousPos.y() - m_startPos.y();
161qreal DirectionalDragArea::touchX() const
163 return m_previousPos.x();
166qreal DirectionalDragArea::touchY() const
168 return m_previousPos.y();
171void DirectionalDragArea::touchEvent(QTouchEvent *event)
173 if (!isEnabled() || !isVisible()) {
174 QQuickItem::touchEvent(event);
179 case WaitingForTouch:
180 touchEvent_absent(event);
183 touchEvent_undecided(event);
186 touchEvent_recognized(event);
189 touchEvent_rejected(event);
194void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
196 if ((event->touchPointStates() && (Qt::TouchPointPressed || Qt::TouchPointMoved))
197 && event->touchPoints().count() == 1) {
198 m_startPos = event->touchPoints()[0].pos();
199 m_touchId = event->touchPoints()[0].id();
200 m_dampedPos.reset(m_startPos);
201 updateVelocityCalculator(m_startPos);
202 m_velocityCalculator->reset();
203 m_numSamplesOnLastSpeedCheck = 0;
205 setPreviousPos(m_startPos);
206 setStatus(Undecided);
210void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
212 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
213 const QPointF &touchPos = touchPoint->pos();
215 if (touchPoint->state() == Qt::TouchPointReleased) {
216 // touch has ended before recognition concluded
217 setStatus(WaitingForTouch);
221 if (event->touchPointStates().testFlag(Qt::TouchPointPressed)
222 || event->touchPoints().count() > 1) {
223 // multi-finger drags are not accepted
228 m_previousDampedPos.setX(m_dampedPos.x());
229 m_previousDampedPos.setY(m_dampedPos.y());
230 m_dampedPos.update(touchPos);
231 updateVelocityCalculator(touchPos);
233 if (!pointInsideAllowedArea()) {
238 if (!movingInRightDirection()) {
243 setPreviousPos(touchPos);
245 if (movedFarEnough(touchPos)) {
246 setStatus(Recognized);
250void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
252 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
254 setPreviousPos(touchPoint->pos());
256 if (touchPoint->state() == Qt::TouchPointReleased) {
257 setStatus(WaitingForTouch);
261void DirectionalDragArea::touchEvent_rejected(QTouchEvent *event)
263 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
265 if (!touchPoint || touchPoint->state() == Qt::TouchPointReleased) {
266 setStatus(WaitingForTouch);
270const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event)
272 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
273 const QTouchEvent::TouchPoint *touchPoint = 0;
274 for (int i = 0; i < touchPoints.size(); ++i) {
275 if (touchPoints.at(i).id() == m_touchId) {
276 touchPoint = &touchPoints.at(i);
283bool DirectionalDragArea::pointInsideAllowedArea() const
285 qreal dX = m_dampedPos.x() - m_startPos.x();
286 qreal dY = m_dampedPos.y() - m_startPos.y();
288 switch (m_direction) {
290 return dY <= 0 && qFabs(dX) <= qFabs(dY) * m_wideningFactor;
292 return dY >= 0 && qFabs(dX) <= dY * m_wideningFactor;
294 return dX <= 0 && qFabs(dY) <= qFabs(dX) * m_wideningFactor;
295 default: // Rightwards:
296 return dX >= 0 && qFabs(dY) <= dX * m_wideningFactor;
300bool DirectionalDragArea::movingInRightDirection() const
302 switch (m_direction) {
304 return m_dampedPos.y() <= m_previousDampedPos.y();
306 return m_dampedPos.y() >= m_previousDampedPos.y();
308 return m_dampedPos.x() <= m_previousDampedPos.x();
309 default: // Rightwards:
310 return m_dampedPos.x() >= m_previousDampedPos.x();
314bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
316 if (directionIsHorizontal())
317 return qFabs(point.x() - m_startPos.x()) > m_distanceThreshold;
319 return qFabs(point.y() - m_startPos.y()) > m_distanceThreshold;
322void DirectionalDragArea::checkSpeed()
324 if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
325 qreal speed = qFabs(m_velocityCalculator->calculate());
326 qreal minSpeedMsecs = m_minSpeed / 1000.0;
328 if (speed < minSpeedMsecs) {
333 if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) {
334 m_silenceTime += m_recognitionTimer->interval();
336 if (m_silenceTime > m_maxSilenceTime) {
342 m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
345void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
347 if (newStatus == m_status)
350 DirectionalDragArea::Status oldStatus = m_status;
352 if (oldStatus == Undecided) {
353 m_recognitionTimer->stop();
356 m_status = newStatus;
357 Q_EMIT statusChanged(m_status);
360 case WaitingForTouch:
361 if (oldStatus != DirectionalDragArea::Rejected) {
362 Q_EMIT draggingChanged(false);
366 m_recognitionTimer->start();
367 Q_EMIT draggingChanged(true);
370 Q_EMIT draggingChanged(false);
378void DirectionalDragArea::setPreviousPos(QPointF point)
380 Q_ASSERT(m_status != Rejected);
382 bool xChanged = m_previousPos.x() != point.x();
383 bool yChanged = m_previousPos.y() != point.y();
385 m_previousPos = point;
388 Q_EMIT touchXChanged(point.x());
389 if (directionIsHorizontal())
390 Q_EMIT distanceChanged(distance());
394 Q_EMIT touchYChanged(point.y());
395 if (directionIsVertical())
396 Q_EMIT distanceChanged(distance());
400void DirectionalDragArea::updateVelocityCalculator(QPointF point)
402 if (directionIsHorizontal()) {
403 m_velocityCalculator->setTrackedPosition(point.x());
405 m_velocityCalculator->setTrackedPosition(point.y());
409bool DirectionalDragArea::directionIsHorizontal() const
411 return m_direction == Leftwards || m_direction == Rightwards;
414bool DirectionalDragArea::directionIsVertical() const
416 return m_direction == Upwards || m_direction == Downwards;
419// Because we are defining a new QObject-based class (RecognitionTimer) here.
420#include "DirectionalDragArea.moc"
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list