Unity 8
 All Classes Functions Properties
DirectionalDragArea.h
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  */
16 
17 #ifndef DIRECTIONAL_DRAG_AREA_H
18 #define DIRECTIONAL_DRAG_AREA_H
19 
20 #include <QtQuick/QQuickItem>
21 #include "AxisVelocityCalculator.h"
22 #include "UbuntuGesturesGlobal.h"
23 #include "Damper.h"
24 #include "Direction.h"
25 
26 namespace UbuntuGestures {
27 /* Defines an interface for a Timer. */
28 class UBUNTUGESTURES_EXPORT AbstractTimer : public QObject {
29  Q_OBJECT
30 public:
31  AbstractTimer(QObject *parent) : QObject(parent), m_isRunning(false) {}
32  virtual int interval() const = 0;
33  virtual void setInterval(int msecs) = 0;
34  virtual void start() { m_isRunning = true; };
35  virtual void stop() { m_isRunning = false; }
36  bool isRunning() const { return m_isRunning; }
37 Q_SIGNALS:
38  void timeout();
39 private:
40  bool m_isRunning;
41 };
42 }
43 
44 /*
45  An area that detects axis-aligned single-finger drag gestures
46 
47  If a drag deviates too much from the components' direction recognition will
48  fail. It will also fail if the drag or flick is too short. E.g. a noisy or
49  fidgety click
50 
51  See doc/DirectionalDragArea.svg
52  */
53 class UBUNTUGESTURES_EXPORT DirectionalDragArea : public QQuickItem {
54  Q_OBJECT
55 
56  // The direction in which the gesture should move in order to be recognized.
57  Q_PROPERTY(Direction::Type direction READ direction WRITE setDirection NOTIFY directionChanged)
58 
59  // The distance travelled by the finger along the axis specified by
60  // DirectionalDragArea's direction.
61  Q_PROPERTY(qreal distance READ distance NOTIFY distanceChanged)
62 
63  // The distance travelled by the finger along the axis specified by
64  // DirectionalDragArea's direction in scene coordinates
65  Q_PROPERTY(qreal sceneDistance READ sceneDistance NOTIFY sceneDistanceChanged)
66 
67  // Position of the touch point performing the drag relative to this item.
68  Q_PROPERTY(qreal touchX READ touchX NOTIFY touchXChanged)
69  Q_PROPERTY(qreal touchY READ touchY NOTIFY touchYChanged)
70 
71  // Position of the touch point performing the drag, in scene's coordinate system
72  Q_PROPERTY(qreal touchSceneX READ touchSceneX NOTIFY touchSceneXChanged)
73  Q_PROPERTY(qreal touchSceneY READ touchSceneY NOTIFY touchSceneYChanged)
74 
75  // The current status of the directional drag gesture area.
76  Q_PROPERTY(Status status READ status NOTIFY statusChanged)
77 
78  // Whether a drag gesture is taking place
79  // This will be true as long as status is Undecided or Recognized
80  // When a gesture gets rejected, dragging turns to false.
81  Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
82 
83 
84  // stuff that will be set in stone at some point
85 
86  // How far the touch point can move away from its expected position before
87  // it causes a rejection in the gesture recognition. This is to compensate
88  // for both noise in the touch input signal and for the natural irregularities
89  // in the finger movement.
90  // Proper value is likely device-specific.
91  Q_PROPERTY(qreal maxDeviation READ maxDeviation WRITE setMaxDeviation NOTIFY maxDeviationChanged)
92 
93  // Widening angle, in degrees
94  // It's roughly the maximum angle a touch point can make relative to the
95  // axis defined by the compoment's direction for it to be recognized as a
96  // directional drag.
97  Q_PROPERTY(qreal wideningAngle READ wideningAngle WRITE setWideningAngle
98  NOTIFY wideningAngleChanged)
99 
100  // How far a touch point has to move from its initial position in order for
101  // it to be recognized as a directional drag.
102  Q_PROPERTY(qreal distanceThreshold READ distanceThreshold WRITE setDistanceThreshold
103  NOTIFY distanceThresholdChanged)
104 
105  // Minimum speed a gesture needs to have in order to be recognized as a
106  // directional drag.
107  // In pixels per second
108  Q_PROPERTY(qreal minSpeed READ minSpeed WRITE setMinSpeed NOTIFY minSpeedChanged)
109 
110  // A gesture will be rejected if more than maxSilenceTime milliseconds has
111  // passed since we last got an input event from it (during Undecided state).
112  //
113  // Silence (i.e., lack of new input events) doesn't necessarily mean that the user's
114  // finger is still (zero drag speed). In some cases the finger might be moving but
115  // the driver's high noise filtering might cause those silence periods, specially
116  // in the moments succeeding a press (talking about Galaxy Nexus here).
117  Q_PROPERTY(int maxSilenceTime READ maxSilenceTime
118  WRITE setMaxSilenceTime
119  NOTIFY maxSilenceTimeChanged)
120 
121  //
123 
124  // Maximum time (in milliseconds) after the start of a given touch point where
125  // subsequent touch starts are grouped with the first one into an N-touches gesture
126  // (e.g. a two-fingers tap or drag).
127  Q_PROPERTY(int compositionTime READ compositionTime
128  WRITE setCompositionTime
129  NOTIFY compositionTimeChanged)
130 
131  Q_ENUMS(Direction)
132  Q_ENUMS(Status)
133 public:
134  DirectionalDragArea(QQuickItem *parent = 0);
135 
136  Direction::Type direction() const;
137  void setDirection(Direction::Type);
138 
139  // Describes the state of the directional drag gesture.
140  enum Status {
141  // Waiting for a new touch point to land on this area. No gesture is being processed
142  // or tracked.
143  WaitingForTouch,
144 
145  // A touch point has landed on this area but it's not know yet whether it is
146  // performing a drag in the correct direction.
147  // If it's decided that the touch point is not performing a directional drag gesture,
148  // it will be rejected/ignored and status will return to WaitingForTouch.
149  Undecided, //Recognizing,
150 
151  // There's a touch point in this area and it performed a drag in the correct
152  // direction.
153  //
154  // Once recognized, the gesture state will move back to WaitingForTouch only once
155  // that touch point ends. The gesture will remain in the Recognized state even if
156  // the touch point starts moving in other directions or halts.
157  Recognized,
158  };
159  Status status() const { return m_status; }
160 
161  qreal distance() const;
162  qreal sceneDistance() const;
163  void updateSceneDistance();
164 
165  qreal touchX() const;
166  qreal touchY() const;
167 
168  qreal touchSceneX() const;
169  qreal touchSceneY() const;
170 
171  bool dragging() const { return (m_status == Undecided) || (m_status == Recognized); }
172 
173  qreal maxDeviation() const { return m_dampedScenePos.maxDelta(); }
174  void setMaxDeviation(qreal value);
175 
176  qreal wideningAngle() const;
177  void setWideningAngle(qreal value);
178 
179  qreal distanceThreshold() const { return m_distanceThreshold; }
180  void setDistanceThreshold(qreal value);
181 
182  qreal minSpeed() const { return m_minSpeed; }
183  void setMinSpeed(qreal value);
184 
185  int maxSilenceTime() const { return m_maxSilenceTime; }
186  void setMaxSilenceTime(int value);
187 
188  int compositionTime() const { return m_compositionTime; }
189  void setCompositionTime(int value);
190 
191  // Replaces the existing Timer with the given one.
192  //
193  // Useful for providing a fake timer when testing.
194  void setRecognitionTimer(UbuntuGestures::AbstractTimer *timer);
195 
196  // Useful for testing, where a fake time source can be supplied
197  void setTimeSource(const UbuntuGestures::SharedTimeSource &timeSource);
198 
199 Q_SIGNALS:
200  void directionChanged(Direction::Type direction);
201  void statusChanged(Status value);
202  void draggingChanged(bool value);
203  void distanceChanged(qreal value);
204  void sceneDistanceChanged(qreal value);
205  void maxDeviationChanged(qreal value);
206  void wideningAngleChanged(qreal value);
207  void distanceThresholdChanged(qreal value);
208  void minSpeedChanged(qreal value);
209  void maxSilenceTimeChanged(int value);
210  void compositionTimeChanged(int value);
211  void touchXChanged(qreal value);
212  void touchYChanged(qreal value);
213  void touchSceneXChanged(qreal value);
214  void touchSceneYChanged(qreal value);
215 
216 protected:
217  virtual void touchEvent(QTouchEvent *event);
218 
219 private Q_SLOTS:
220  void checkSpeed();
221  void onEnabledChanged();
222 
223 private:
224  void touchEvent_absent(QTouchEvent *event);
225  void touchEvent_undecided(QTouchEvent *event);
226  void touchEvent_recognized(QTouchEvent *event);
227  bool pointInsideAllowedArea() const;
228  bool movingInRightDirection() const;
229  bool movedFarEnough(const QPointF &point) const;
230  const QTouchEvent::TouchPoint *fetchTargetTouchPoint(QTouchEvent *event);
231  void setStatus(Status newStatus);
232  void setPreviousPos(const QPointF &point);
233  void setPreviousScenePos(const QPointF &point);
234  void updateVelocityCalculator(const QPointF &point);
235  bool isWithinTouchCompositionWindow();
236  void updateSceneDirectionVector();
237  // returns the scalar projection between the given vector (in scene coordinates)
238  // and m_sceneDirectionVector
239  qreal projectOntoDirectionVector(const QPointF &sceneVector) const;
240 
241  Status m_status;
242 
243  QPointF m_startPos;
244  QPointF m_startScenePos;
245  QPointF m_previousPos;
246  QPointF m_previousScenePos;
247  qreal m_sceneDistance;
248  int m_touchId;
249 
250  // A movement damper is used in some of the gesture recognition calculations
251  // to get rid of noise or small oscillations in the touch position.
252  DampedPointF m_dampedScenePos;
253  QPointF m_previousDampedScenePos;
254 
255  // Unit vector in scene coordinates describing the direction of the gesture recognition
256  QPointF m_sceneDirectionVector;
257 
258  Direction::Type m_direction;
259  qreal m_wideningAngle; // in degrees
260  qreal m_wideningFactor; // it's pow(cosine(m_wideningAngle), 2)
261  qreal m_distanceThreshold;
262  qreal m_distanceThresholdSquared; // it's pow(m_distanceThreshold, 2)
263  qreal m_minSpeed;
264  int m_maxSilenceTime; // in milliseconds
265  int m_silenceTime; // in milliseconds
266  int m_compositionTime; // in milliseconds
267  int m_numSamplesOnLastSpeedCheck;
268  UbuntuGestures::AbstractTimer *m_recognitionTimer;
269  AxisVelocityCalculator *m_velocityCalculator;
270 
271  UbuntuGestures::SharedTimeSource m_timeSource;
272 
273  // Information about an active touch point
274  struct ActiveTouchInfo {
275  ActiveTouchInfo() : id(-1), startTime(-1) {}
276  bool isValid() const { return id != -1; }
277  void reset() { id = -1; }
278  int id;
279  qint64 startTime;
280  };
281  class ActiveTouchesInfo {
282  public:
283  ActiveTouchesInfo(const UbuntuGestures::SharedTimeSource &timeSource);
284  void update(QTouchEvent *event);
285  ActiveTouchInfo &touchInfo(int id);
286  qint64 mostRecentStartTime();
287  UbuntuGestures::SharedTimeSource m_timeSource;
288  bool isEmpty() const { return m_lastUsedIndex == -1; }
289  private:
290  void addTouchPoint(const QTouchEvent::TouchPoint &touchPoint);
291  ActiveTouchInfo &getEmptySlot();
292  void freeSlot(int index);
293  void removeTouchPoint(const QTouchEvent::TouchPoint &touchPoint);
294  QVector<struct ActiveTouchInfo> m_vector;
295  int m_lastUsedIndex;
296  } m_activeTouches;
297 };
298 
299 #endif // DIRECTIONAL_DRAG_AREA_H