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(Direction::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);
61Direction::Type DirectionalDragArea::direction() const
66void DirectionalDragArea::setDirection(Direction::Type 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::setTimeSource(UbuntuGestures::TimeSource *timeSource)
146 m_velocityCalculator->setTimeSource(timeSource);
149qreal DirectionalDragArea::distance() const
151 if (Direction::isHorizontal(m_direction)) {
152 return m_previousPos.x() - m_startPos.x();
154 return m_previousPos.y() - m_startPos.y();
158qreal DirectionalDragArea::sceneDistance() const
160 if (Direction::isHorizontal(m_direction)) {
161 return m_previousScenePos.x() - m_startScenePos.x();
163 return m_previousScenePos.y() - m_startScenePos.y();
167qreal DirectionalDragArea::touchX() const
169 return m_previousPos.x();
172qreal DirectionalDragArea::touchY() const
174 return m_previousPos.y();
177qreal DirectionalDragArea::touchSceneX() const
179 return m_previousScenePos.x();
182qreal DirectionalDragArea::touchSceneY() const
184 return m_previousScenePos.y();
187void DirectionalDragArea::touchEvent(QTouchEvent *event)
189 if (!isEnabled() || !isVisible()) {
190 QQuickItem::touchEvent(event);
195 case WaitingForTouch:
196 touchEvent_absent(event);
199 touchEvent_undecided(event);
202 touchEvent_recognized(event);
205 touchEvent_rejected(event);
210void DirectionalDragArea::touchEvent_absent(QTouchEvent *event)
212 if ((event->touchPointStates() && (Qt::TouchPointPressed || Qt::TouchPointMoved))
213 && event->touchPoints().count() == 1) {
214 const QTouchEvent::TouchPoint &touchPoint = event->touchPoints()[0];
215 m_startPos = touchPoint.pos();
216 m_startScenePos = touchPoint.scenePos();
217 m_touchId = touchPoint.id();
218 m_dampedPos.reset(m_startPos);
219 updateVelocityCalculator(m_startPos);
220 m_velocityCalculator->reset();
221 m_numSamplesOnLastSpeedCheck = 0;
223 setPreviousPos(m_startPos);
224 setPreviousScenePos(m_startScenePos);
226 if (m_distanceThreshold > 0)
227 setStatus(Undecided);
229 setStatus(Recognized);
233void DirectionalDragArea::touchEvent_undecided(QTouchEvent *event)
235 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
236 const QPointF &touchPos = touchPoint->pos();
238 if (touchPoint->state() == Qt::TouchPointReleased) {
239 // touch has ended before recognition concluded
240 setStatus(WaitingForTouch);
244 if (event->touchPointStates().testFlag(Qt::TouchPointPressed)
245 || event->touchPoints().count() > 1) {
246 // multi-finger drags are not accepted
251 m_previousDampedPos.setX(m_dampedPos.x());
252 m_previousDampedPos.setY(m_dampedPos.y());
253 m_dampedPos.update(touchPos);
254 updateVelocityCalculator(touchPos);
256 if (!pointInsideAllowedArea()) {
261 if (!movingInRightDirection()) {
266 setPreviousPos(touchPos);
267 setPreviousScenePos(touchPoint->scenePos());
269 if (movedFarEnough(touchPos)) {
270 setStatus(Recognized);
274void DirectionalDragArea::touchEvent_recognized(QTouchEvent *event)
276 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
278 setPreviousPos(touchPoint->pos());
279 setPreviousScenePos(touchPoint->scenePos());
281 if (touchPoint->state() == Qt::TouchPointReleased) {
282 setStatus(WaitingForTouch);
286void DirectionalDragArea::touchEvent_rejected(QTouchEvent *event)
288 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
290 if (!touchPoint || touchPoint->state() == Qt::TouchPointReleased) {
291 setStatus(WaitingForTouch);
295const QTouchEvent::TouchPoint *DirectionalDragArea::fetchTargetTouchPoint(QTouchEvent *event)
297 const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
298 const QTouchEvent::TouchPoint *touchPoint = 0;
299 for (int i = 0; i < touchPoints.size(); ++i) {
300 if (touchPoints.at(i).id() == m_touchId) {
301 touchPoint = &touchPoints.at(i);
308bool DirectionalDragArea::pointInsideAllowedArea() const
310 qreal dX = m_dampedPos.x() - m_startPos.x();
311 qreal dY = m_dampedPos.y() - m_startPos.y();
313 switch (m_direction) {
314 case Direction::Upwards:
315 return dY <= 0 && qFabs(dX) <= qFabs(dY) * m_wideningFactor;
316 case Direction::Downwards:
317 return dY >= 0 && qFabs(dX) <= dY * m_wideningFactor;
318 case Direction::Leftwards:
319 return dX <= 0 && qFabs(dY) <= qFabs(dX) * m_wideningFactor;
320 default: // Direction::Rightwards:
321 return dX >= 0 && qFabs(dY) <= dX * m_wideningFactor;
325bool DirectionalDragArea::movingInRightDirection() const
327 switch (m_direction) {
328 case Direction::Upwards:
329 return m_dampedPos.y() <= m_previousDampedPos.y();
330 case Direction::Downwards:
331 return m_dampedPos.y() >= m_previousDampedPos.y();
332 case Direction::Leftwards:
333 return m_dampedPos.x() <= m_previousDampedPos.x();
334 default: // Direction::Rightwards:
335 return m_dampedPos.x() >= m_previousDampedPos.x();
339bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
341 if (Direction::isHorizontal(m_direction))
342 return qFabs(point.x() - m_startPos.x()) > m_distanceThreshold;
344 return qFabs(point.y() - m_startPos.y()) > m_distanceThreshold;
347void DirectionalDragArea::checkSpeed()
349 if (m_velocityCalculator->numSamples() >= AxisVelocityCalculator::MIN_SAMPLES_NEEDED) {
350 qreal speed = qFabs(m_velocityCalculator->calculate());
351 qreal minSpeedMsecs = m_minSpeed / 1000.0;
353 if (speed < minSpeedMsecs) {
358 if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) {
359 m_silenceTime += m_recognitionTimer->interval();
361 if (m_silenceTime > m_maxSilenceTime) {
367 m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
370void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
372 if (newStatus == m_status)
375 DirectionalDragArea::Status oldStatus = m_status;
377 if (oldStatus == Undecided) {
378 m_recognitionTimer->stop();
381 m_status = newStatus;
382 Q_EMIT statusChanged(m_status);
385 case WaitingForTouch:
386 if (oldStatus != DirectionalDragArea::Rejected) {
387 Q_EMIT draggingChanged(false);
391 m_recognitionTimer->start();
392 Q_EMIT draggingChanged(true);
395 if (oldStatus == WaitingForTouch)
396 Q_EMIT draggingChanged(true);
399 Q_EMIT draggingChanged(false);
407void DirectionalDragArea::setPreviousPos(const QPointF &point)
409 Q_ASSERT(m_status != Rejected);
411 bool xChanged = m_previousPos.x() != point.x();
412 bool yChanged = m_previousPos.y() != point.y();
414 m_previousPos = point;
417 Q_EMIT touchXChanged(point.x());
418 if (Direction::isHorizontal(m_direction))
419 Q_EMIT distanceChanged(distance());
423 Q_EMIT touchYChanged(point.y());
424 if (Direction::isVertical(m_direction))
425 Q_EMIT distanceChanged(distance());
429void DirectionalDragArea::setPreviousScenePos(const QPointF &point)
431 Q_ASSERT(m_status != Rejected);
433 bool xChanged = m_previousScenePos.x() != point.x();
434 bool yChanged = m_previousScenePos.y() != point.y();
436 m_previousScenePos = point;
439 Q_EMIT touchSceneXChanged(point.x());
440 if (Direction::isHorizontal(m_direction))
441 Q_EMIT sceneDistanceChanged(sceneDistance());
445 Q_EMIT touchSceneYChanged(point.y());
446 if (Direction::isVertical(m_direction))
447 Q_EMIT sceneDistanceChanged(sceneDistance());
451void DirectionalDragArea::updateVelocityCalculator(const QPointF &point)
453 if (Direction::isHorizontal(m_direction)) {
454 m_velocityCalculator->setTrackedPosition(point.x());
456 m_velocityCalculator->setTrackedPosition(point.y());
460// Because we are defining a new QObject-based class (RecognitionTimer) here.
461#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