on 5602cfa help sign in
unity8/plugins/Ubuntu/Gestures/DirectionalDragArea.cpp
410 lines 12 kB history raw
1/*
2 * Copyright (C) 2013 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#include "DirectionalDragArea.h"
19#include <QtCore/qmath.h>
20#include <QtCore/QTimer>
22// Essentially a QTimer wrapper
23class RecognitionTimer : public UbuntuGestures::AbstractTimer
25 Q_OBJECT
26public:
27 RecognitionTimer(QObject *parent) : UbuntuGestures::AbstractTimer(parent) {
28 m_timer.setSingleShot(false);
29 connect(&m_timer, &QTimer::timeout,
30 this, &UbuntuGestures::AbstractTimer::timeout);
31 }
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(); }
36private:
37 QTimer m_timer;
38};
40DirectionalDragArea::DirectionalDragArea(QQuickItem *parent)
41 : QQuickItem(parent)
42 , m_status(WaitingForTouch)
43 , m_touchId(-1)
44 , m_direction(Direction::Rightwards)
45 , m_wideningAngle(0)
46 , m_wideningFactor(0)
47 , m_distanceThreshold(0)
48 , m_minSpeed(0)
49 , m_maxSilenceTime(200)
50 , m_silenceTime(0)
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
63 return m_direction;
66void DirectionalDragArea::setDirection(Direction::Type direction)
68 if (direction != m_direction) {
69 m_direction = direction;
70 Q_EMIT directionChanged(m_direction);
71 }
74void DirectionalDragArea::setMaxDeviation(qreal value)
76 if (m_dampedPos.maxDelta() != value) {
77 m_dampedPos.setMaxDelta(value);
78 Q_EMIT maxDeviationChanged(value);
79 }
82qreal DirectionalDragArea::wideningAngle() const
84 return m_wideningAngle;
87void DirectionalDragArea::setWideningAngle(qreal angle)
89 if (angle == m_wideningAngle)
90 return;
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);
102 }
105void DirectionalDragArea::setMinSpeed(qreal value)
107 if (m_minSpeed != value) {
108 m_minSpeed = value;
109 Q_EMIT minSpeedChanged(value);
110 }
113void DirectionalDragArea::setMaxSilenceTime(int value)
115 if (m_maxSilenceTime != value) {
116 m_maxSilenceTime = value;
117 Q_EMIT maxSilenceTimeChanged(value);
118 }
121void DirectionalDragArea::setRecognitionTimer(UbuntuGestures::AbstractTimer *timer)
123 int interval = 0;
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;
132 }
133 }
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();
141 }
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 (Direction::isHorizontal(m_direction)) {
155 return m_previousPos.x() - m_startPos.x();
156 } else {
157 return m_previousPos.y() - m_startPos.y();
158 }
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);
175 return;
176 }
178 switch (m_status) {
179 case WaitingForTouch:
180 touchEvent_absent(event);
181 break;
182 case Undecided:
183 touchEvent_undecided(event);
184 break;
185 case Recognized:
186 touchEvent_recognized(event);
187 break;
188 default: // Rejected
189 touchEvent_rejected(event);
190 break;
191 }
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;
204 m_silenceTime = 0;
205 setPreviousPos(m_startPos);
206 setStatus(Undecided);
207 }
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);
218 return;
219 }
221 if (event->touchPointStates().testFlag(Qt::TouchPointPressed)
222 || event->touchPoints().count() > 1) {
223 // multi-finger drags are not accepted
224 setStatus(Rejected);
225 return;
226 }
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()) {
234 setStatus(Rejected);
235 return;
236 }
238 if (!movingInRightDirection()) {
239 setStatus(Rejected);
240 return;
241 }
243 setPreviousPos(touchPos);
245 if (movedFarEnough(touchPos)) {
246 setStatus(Recognized);
247 }
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);
258 }
261void DirectionalDragArea::touchEvent_rejected(QTouchEvent *event)
263 const QTouchEvent::TouchPoint *touchPoint = fetchTargetTouchPoint(event);
265 if (!touchPoint || touchPoint->state() == Qt::TouchPointReleased) {
266 setStatus(WaitingForTouch);
267 }
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);
277 break;
278 }
279 }
280 return touchPoint;
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) {
289 case Direction::Upwards:
290 return dY <= 0 && qFabs(dX) <= qFabs(dY) * m_wideningFactor;
291 case Direction::Downwards:
292 return dY >= 0 && qFabs(dX) <= dY * m_wideningFactor;
293 case Direction::Leftwards:
294 return dX <= 0 && qFabs(dY) <= qFabs(dX) * m_wideningFactor;
295 default: // Direction::Rightwards:
296 return dX >= 0 && qFabs(dY) <= dX * m_wideningFactor;
297 }
300bool DirectionalDragArea::movingInRightDirection() const
302 switch (m_direction) {
303 case Direction::Upwards:
304 return m_dampedPos.y() <= m_previousDampedPos.y();
305 case Direction::Downwards:
306 return m_dampedPos.y() >= m_previousDampedPos.y();
307 case Direction::Leftwards:
308 return m_dampedPos.x() <= m_previousDampedPos.x();
309 default: // Direction::Rightwards:
310 return m_dampedPos.x() >= m_previousDampedPos.x();
311 }
314bool DirectionalDragArea::movedFarEnough(const QPointF &point) const
316 if (Direction::isHorizontal(m_direction))
317 return qFabs(point.x() - m_startPos.x()) > m_distanceThreshold;
318 else
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) {
329 setStatus(Rejected);
330 }
331 }
333 if (m_velocityCalculator->numSamples() == m_numSamplesOnLastSpeedCheck) {
334 m_silenceTime += m_recognitionTimer->interval();
336 if (m_silenceTime > m_maxSilenceTime) {
337 setStatus(Rejected);
338 }
339 } else {
340 m_silenceTime = 0;
341 }
342 m_numSamplesOnLastSpeedCheck = m_velocityCalculator->numSamples();
345void DirectionalDragArea::setStatus(DirectionalDragArea::Status newStatus)
347 if (newStatus == m_status)
348 return;
350 DirectionalDragArea::Status oldStatus = m_status;
352 if (oldStatus == Undecided) {
353 m_recognitionTimer->stop();
354 }
356 m_status = newStatus;
357 Q_EMIT statusChanged(m_status);
359 switch (newStatus) {
360 case WaitingForTouch:
361 if (oldStatus != DirectionalDragArea::Rejected) {
362 Q_EMIT draggingChanged(false);
363 }
364 break;
365 case Undecided:
366 m_recognitionTimer->start();
367 Q_EMIT draggingChanged(true);
368 break;
369 case Rejected:
370 Q_EMIT draggingChanged(false);
371 break;
372 default:
373 // no-op
374 break;
375 }
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;
387 if (xChanged) {
388 Q_EMIT touchXChanged(point.x());
389 if (Direction::isHorizontal(m_direction))
390 Q_EMIT distanceChanged(distance());
391 }
393 if (yChanged) {
394 Q_EMIT touchYChanged(point.y());
395 if (Direction::isVertical(m_direction))
396 Q_EMIT distanceChanged(distance());
397 }
400void DirectionalDragArea::updateVelocityCalculator(QPointF point)
402 if (Direction::isHorizontal(m_direction)) {
403 m_velocityCalculator->setTrackedPosition(point.x());
404 } else {
405 m_velocityCalculator->setTrackedPosition(point.y());
406 }
409// Because we are defining a new QObject-based class (RecognitionTimer) here.
410#include "DirectionalDragArea.moc"