summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz/src/GestureEventListener.h
blob: bf50d4f40a951bb424829376a0e72d9d3c2aeeb5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_layers_GestureEventListener_h
#define mozilla_layers_GestureEventListener_h

#include <iosfwd>
#include "InputData.h"  // for MultiTouchInput, etc
#include "Units.h"
#include "mozilla/EventForwards.h"  // for nsEventStatus
#include "mozilla/RefPtr.h"         // for RefPtr
#include "nsISupportsImpl.h"
#include "nsTArray.h"  // for nsTArray

namespace mozilla {

class CancelableRunnable;

namespace layers {

class AsyncPanZoomController;

/**
 * Platform-non-specific, generalized gesture event listener. This class
 * intercepts all touches events on their way to AsyncPanZoomController and
 * determines whether or not they are part of a gesture.
 *
 * For example, seeing that two fingers are on the screen means that the user
 * wants to do a pinch gesture, so we don't forward the touches along to
 * AsyncPanZoomController since it will think that they are just trying to pan
 * the screen. Instead, we generate a PinchGestureInput and send that. If the
 * touch event is not part of a gesture, we just return nsEventStatus_eIgnore
 * and AsyncPanZoomController is expected to handle it.
 */
class GestureEventListener final {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GestureEventListener)

  explicit GestureEventListener(
      AsyncPanZoomController* aAsyncPanZoomController);

  // --------------------------------------------------------------------------
  // These methods must only be called on the controller/UI thread.
  //

  /**
   * General input handler for a touch event. If the touch event is not a part
   * of a gesture, then we pass it along to AsyncPanZoomController. Otherwise,
   * it gets consumed here and never forwarded along.
   */
  nsEventStatus HandleInputEvent(const MultiTouchInput& aEvent);

  /**
   * Returns the identifier of the touch in the last touch event processed by
   * this GestureEventListener. This should only be called when the last touch
   * event contained only one touch.
   */
  int32_t GetLastTouchIdentifier() const;

  /**
   * Function used to disable long tap gestures.
   *
   * On slow running tests, drags and touch events can be misinterpreted
   * as a long tap. This allows tests to disable long tap gesture detection.
   */
  static void SetLongTapEnabled(bool aLongTapEnabled);
  static bool IsLongTapEnabled();

 private:
  // Private destructor, to discourage deletion outside of Release():
  ~GestureEventListener();

  /**
   * States of GEL finite-state machine.
   */
  enum GestureState {
    // This is the initial and final state of any gesture.
    // In this state there's no gesture going on, and we don't think we're
    // about to enter one.
    // Allowed next states: GESTURE_FIRST_SINGLE_TOUCH_DOWN,
    // GESTURE_MULTI_TOUCH_DOWN.
    GESTURE_NONE,

    // A touch start with a single touch point has just happened.
    // After having gotten into this state we start timers for MAX_TAP_TIME and
    // StaticPrefs::ui_click_hold_context_menus_delay().
    // Allowed next states: GESTURE_MULTI_TOUCH_DOWN, GESTURE_NONE,
    //                      GESTURE_FIRST_SINGLE_TOUCH_UP,
    //                      GESTURE_LONG_TOUCH_DOWN,
    //                      GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN.
    GESTURE_FIRST_SINGLE_TOUCH_DOWN,

    // While in GESTURE_FIRST_SINGLE_TOUCH_DOWN state a MAX_TAP_TIME timer got
    // triggered. Now we'll trigger either a single tap if a user lifts her
    // finger or a long tap if StaticPrefs::ui_click_hold_context_menus_delay()
    // happens first.
    // Allowed next states: GESTURE_MULTI_TOUCH_DOWN, GESTURE_NONE,
    //                      GESTURE_LONG_TOUCH_DOWN.
    GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN,

    // A user put her finger down and lifted it up quickly enough.
    // After having gotten into this state we clear the timer for MAX_TAP_TIME.
    // Allowed next states: GESTURE_SECOND_SINGLE_TOUCH_DOWN, GESTURE_NONE,
    //                      GESTURE_MULTI_TOUCH_DOWN.
    GESTURE_FIRST_SINGLE_TOUCH_UP,

    // A user put down her finger again right after a single tap thus the
    // gesture can't be a single tap, but rather a double tap. But we're
    // still not sure about that until the user lifts her finger again.
    // Allowed next states: GESTURE_MULTI_TOUCH_DOWN, GESTURE_ONE_TOUCH_PINCH,
    //                      GESTURE_NONE.
    GESTURE_SECOND_SINGLE_TOUCH_DOWN,

    // A long touch has happened, but the user still keeps her finger down.
    // We'll trigger a "long tap up" event when the finger is up.
    // Allowed next states: GESTURE_NONE, GESTURE_MULTI_TOUCH_DOWN.
    GESTURE_LONG_TOUCH_DOWN,

    // We have detected that two or more fingers are on the screen, but there
    // hasn't been enough movement yet to make us start actually zooming the
    // screen.
    // Allowed next states: GESTURE_PINCH, GESTURE_NONE
    GESTURE_MULTI_TOUCH_DOWN,

    // There are two or more fingers on the screen, and the user has already
    // pinched enough for us to start zooming the screen.
    // Allowed next states: GESTURE_NONE
    GESTURE_PINCH,

    // The user has double tapped, but not lifted her finger, and moved her
    // finger more than PINCH_START_THRESHOLD.
    // Allowed next states: GESTURE_NONE.
    GESTURE_ONE_TOUCH_PINCH
  };

  friend std::ostream& operator<<(std::ostream& os, GestureState aState);

  /**
   * These HandleInput* functions comprise input alphabet of the GEL
   * finite-state machine triggering state transitions.
   */
  nsEventStatus HandleInputTouchSingleStart();
  nsEventStatus HandleInputTouchMultiStart();
  nsEventStatus HandleInputTouchEnd();
  nsEventStatus HandleInputTouchMove();
  nsEventStatus HandleInputTouchCancel();
  void HandleInputTimeoutLongTap();
  void HandleInputTimeoutMaxTap(bool aDuringFastFling);

  void EnterFirstSingleTouchDown();

  void TriggerSingleTapConfirmedEvent();

  bool MoveDistanceExceeds(ScreenCoord aThreshold) const;
  bool MoveDistanceIsLarge() const;
  bool SecondTapIsFar() const;

  /**
   * Returns current vertical span, counting from the where the gesture first
   * began (after a brief delay detecting the gesture from first touch).
   */
  ScreenCoord GetYSpanFromGestureStartPoint();

  /**
   * Do actual state transition and reset substates.
   */
  void SetState(GestureState aState);

  RefPtr<AsyncPanZoomController> mAsyncPanZoomController;

  /**
   * Array containing all active touches. When a touch happens it, gets added to
   * this array, even if we choose not to handle it. When it ends, we remove it.
   * We need to maintain this array in order to detect the end of the
   * "multitouch" states because touch start events contain all current touches,
   * but touch end events contain only those touches that have gone.
   */
  nsTArray<SingleTouchData> mTouches;

  /**
   * Current state we're dealing with.
   */
  GestureState mState;

  /**
   * Total change in span since we detected a pinch gesture. Only used when we
   * are in the |GESTURE_WAITING_PINCH| state and need to know how far zoomed
   * out we are compared to our original pinch span. Note that this does _not_
   * continue to be updated once we jump into the |GESTURE_PINCH| state.
   */
  ScreenCoord mSpanChange;

  /**
   * Previous span calculated for the purposes of setting inside a
   * PinchGestureInput.
   */
  ScreenCoord mPreviousSpan;

  /* Properties similar to mSpanChange and mPreviousSpan, but for the focus */
  ScreenCoord mFocusChange;
  ScreenPoint mPreviousFocus;

  /**
   * Cached copy of the last touch input.
   */
  MultiTouchInput mLastTouchInput;

  /**
   * Cached copy of the last tap gesture input.
   * In the situation when we have a tap followed by a pinch we lose info
   * about tap since we keep only last input and to dispatch it correctly
   * we save last tap copy into this variable.
   * For more info see bug 947892.
   */
  MultiTouchInput mLastTapInput;

  /**
   * Position of the last touch that exceeds the GetTouchStartTolerance when
   * performing a one-touch-pinch gesture; using the mTouchStartPosition is
   * slightly inaccurate because by the time the touch position has passed
   * the threshold for the gesture, there is already a span that the zoom
   * is calculated from, instead of starting at 1.0 when the threshold gets
   * passed.
   */
  ScreenPoint mOneTouchPinchStartPosition;

  /**
   * Position of the last touch starting. This is only valid during an attempt
   * to determine if a touch is a tap. If a touch point moves away from
   * mTouchStartPosition to the distance greater than
   * AsyncPanZoomController::GetTouchStartTolerance() while in
   * GESTURE_FIRST_SINGLE_TOUCH_DOWN, GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN
   * or GESTURE_SECOND_SINGLE_TOUCH_DOWN then we're certain the gesture is
   * not tap.
   */
  ScreenPoint mTouchStartPosition;

  /**
   * We store the window/GeckoView's display offset as well, so we can
   * track the user's physical touch movements in absolute display coordinates.
   */
  ExternalPoint mTouchStartOffset;

  /**
   * Task used to timeout a long tap. This gets posted to the UI thread such
   * that it runs a time when a single tap happens. We cache it so that
   * we can cancel it if any other touch event happens.
   *
   * The task is supposed to be non-null if in GESTURE_FIRST_SINGLE_TOUCH_DOWN
   * and GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN states.
   *
   * CancelLongTapTimeoutTask: Cancel the mLongTapTimeoutTask and also set
   * it to null.
   */
  RefPtr<CancelableRunnable> mLongTapTimeoutTask;
  void CancelLongTapTimeoutTask();
  void CreateLongTapTimeoutTask();

  /**
   * Task used to timeout a single tap or a double tap.
   *
   * The task is supposed to be non-null if in GESTURE_FIRST_SINGLE_TOUCH_DOWN,
   * GESTURE_FIRST_SINGLE_TOUCH_UP and GESTURE_SECOND_SINGLE_TOUCH_DOWN states.
   *
   * CancelMaxTapTimeoutTask: Cancel the mMaxTapTimeoutTask and also set
   * it to null.
   */
  RefPtr<CancelableRunnable> mMaxTapTimeoutTask;
  void CancelMaxTapTimeoutTask();
  void CreateMaxTapTimeoutTask();

  /**
   * Tracks whether the single-tap event was already sent to content. This is
   * needed because it affects how the double-tap gesture, if detected, is
   * handled. The value is only valid in states GESTURE_FIRST_SINGLE_TOUCH_UP
   * and GESTURE_SECOND_SINGLE_TOUCH_DOWN; to more easily catch violations it is
   * stored in a Maybe which is set to Nothing() at all other times.
   */
  Maybe<bool> mSingleTapSent;
};

}  // namespace layers
}  // namespace mozilla

#endif