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