gesture_detector.cc revision a02191e04bc25c4935f804f2c080ae28663d096d
1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/events/gesture_detection/gesture_detector.h"
6
7#include <cmath>
8
9#include "base/timer/timer.h"
10#include "ui/events/gesture_detection/motion_event.h"
11
12namespace ui {
13namespace {
14
15// Using a small epsilon when comparing slop distances allows pixel perfect
16// slop determination when using fractional DIP coordinates (assuming the slop
17// region and DPI scale are reasonably proportioned).
18const float kSlopEpsilon = .05f;
19
20// Minimum distance a scroll must have traveled from the last scroll/focal point
21// to trigger an |OnScroll| callback.
22const float kScrollEpsilon = .1f;
23
24// Constants used by TimeoutGestureHandler.
25enum TimeoutEvent {
26  SHOW_PRESS = 0,
27  LONG_PRESS,
28  TAP,
29  TIMEOUT_EVENT_COUNT
30};
31
32}  // namespace
33
34// Note: These constants were taken directly from the default (unscaled)
35// versions found in Android's ViewConfiguration.
36GestureDetector::Config::Config()
37    : longpress_timeout(base::TimeDelta::FromMilliseconds(500)),
38      showpress_timeout(base::TimeDelta::FromMilliseconds(180)),
39      double_tap_timeout(base::TimeDelta::FromMilliseconds(300)),
40      touch_slop(8),
41      double_tap_slop(100),
42      minimum_fling_velocity(50),
43      maximum_fling_velocity(8000) {}
44
45GestureDetector::Config::~Config() {}
46
47bool GestureDetector::SimpleGestureListener::OnDown(const MotionEvent& e) {
48  return false;
49}
50
51void GestureDetector::SimpleGestureListener::OnShowPress(const MotionEvent& e) {
52}
53
54bool GestureDetector::SimpleGestureListener::OnSingleTapUp(
55    const MotionEvent& e) {
56  return false;
57}
58
59bool GestureDetector::SimpleGestureListener::OnLongPress(const MotionEvent& e) {
60  return false;
61}
62
63bool GestureDetector::SimpleGestureListener::OnScroll(const MotionEvent& e1,
64                                                      const MotionEvent& e2,
65                                                      float distance_x,
66                                                      float distance_y) {
67  return false;
68}
69
70bool GestureDetector::SimpleGestureListener::OnFling(const MotionEvent& e1,
71                                                     const MotionEvent& e2,
72                                                     float velocity_x,
73                                                     float velocity_y) {
74  return false;
75}
76
77bool GestureDetector::SimpleGestureListener::OnSingleTapConfirmed(
78    const MotionEvent& e) {
79  return false;
80}
81
82bool GestureDetector::SimpleGestureListener::OnDoubleTap(const MotionEvent& e) {
83  return false;
84}
85
86bool GestureDetector::SimpleGestureListener::OnDoubleTapEvent(
87    const MotionEvent& e) {
88  return false;
89}
90
91class GestureDetector::TimeoutGestureHandler {
92 public:
93  TimeoutGestureHandler(const Config& config, GestureDetector* gesture_detector)
94      : gesture_detector_(gesture_detector) {
95    DCHECK(config.showpress_timeout <= config.longpress_timeout);
96
97    timeout_callbacks_[SHOW_PRESS] = &GestureDetector::OnShowPressTimeout;
98    timeout_delays_[SHOW_PRESS] = config.showpress_timeout;
99
100    timeout_callbacks_[LONG_PRESS] = &GestureDetector::OnLongPressTimeout;
101    timeout_delays_[LONG_PRESS] =
102        config.longpress_timeout + config.showpress_timeout;
103
104    timeout_callbacks_[TAP] = &GestureDetector::OnTapTimeout;
105    timeout_delays_[TAP] = config.double_tap_timeout;
106  }
107
108  ~TimeoutGestureHandler() {
109    Stop();
110  }
111
112  void StartTimeout(TimeoutEvent event) {
113    timeout_timers_[event].Start(FROM_HERE,
114                                 timeout_delays_[event],
115                                 gesture_detector_,
116                                 timeout_callbacks_[event]);
117  }
118
119  void StopTimeout(TimeoutEvent event) { timeout_timers_[event].Stop(); }
120
121  void Stop() {
122    for (size_t i = SHOW_PRESS; i < TIMEOUT_EVENT_COUNT; ++i)
123      timeout_timers_[i].Stop();
124  }
125
126  bool HasTimeout(TimeoutEvent event) const {
127    return timeout_timers_[event].IsRunning();
128  }
129
130 private:
131  typedef void (GestureDetector::*ReceiverMethod)();
132
133  GestureDetector* const gesture_detector_;
134  base::OneShotTimer<GestureDetector> timeout_timers_[TIMEOUT_EVENT_COUNT];
135  ReceiverMethod timeout_callbacks_[TIMEOUT_EVENT_COUNT];
136  base::TimeDelta timeout_delays_[TIMEOUT_EVENT_COUNT];
137};
138
139GestureDetector::GestureDetector(
140    const Config& config,
141    GestureListener* listener,
142    DoubleTapListener* optional_double_tap_listener)
143    : timeout_handler_(new TimeoutGestureHandler(config, this)),
144      listener_(listener),
145      double_tap_listener_(optional_double_tap_listener),
146      touch_slop_square_(0),
147      double_tap_touch_slop_square_(0),
148      double_tap_slop_square_(0),
149      min_fling_velocity_(1),
150      max_fling_velocity_(1),
151      still_down_(false),
152      defer_confirm_single_tap_(false),
153      in_longpress_(false),
154      always_in_tap_region_(false),
155      always_in_bigger_tap_region_(false),
156      is_double_tapping_(false),
157      last_focus_x_(0),
158      last_focus_y_(0),
159      down_focus_x_(0),
160      down_focus_y_(0),
161      is_longpress_enabled_(true) {
162  DCHECK(listener_);
163  Init(config);
164}
165
166GestureDetector::~GestureDetector() {}
167
168bool GestureDetector::OnTouchEvent(const MotionEvent& ev) {
169  const MotionEvent::Action action = ev.GetAction();
170
171  velocity_tracker_.AddMovement(ev);
172
173  const bool pointer_up = action == MotionEvent::ACTION_POINTER_UP;
174  const int skip_index = pointer_up ? ev.GetActionIndex() : -1;
175
176  // Determine focal point.
177  float sum_x = 0, sum_y = 0;
178  const int count = static_cast<int>(ev.GetPointerCount());
179  for (int i = 0; i < count; i++) {
180    if (skip_index == i)
181      continue;
182    sum_x += ev.GetX(i);
183    sum_y += ev.GetY(i);
184  }
185  const int div = pointer_up ? count - 1 : count;
186  const float focus_x = sum_x / div;
187  const float focus_y = sum_y / div;
188
189  bool handled = false;
190
191  switch (action) {
192    case MotionEvent::ACTION_POINTER_DOWN:
193      down_focus_x_ = last_focus_x_ = focus_x;
194      down_focus_y_ = last_focus_y_ = focus_y;
195      // Cancel long press and taps.
196      CancelTaps();
197      break;
198
199    case MotionEvent::ACTION_POINTER_UP:
200      down_focus_x_ = last_focus_x_ = focus_x;
201      down_focus_y_ = last_focus_y_ = focus_y;
202
203      // Check the dot product of current velocities.
204      // If the pointer that left was opposing another velocity vector, clear.
205      velocity_tracker_.ComputeCurrentVelocity(1000, max_fling_velocity_);
206      {
207        const int up_index = ev.GetActionIndex();
208        const int id1 = ev.GetPointerId(up_index);
209        const float x1 = velocity_tracker_.GetXVelocity(id1);
210        const float y1 = velocity_tracker_.GetYVelocity(id1);
211        for (int i = 0; i < count; i++) {
212          if (i == up_index)
213            continue;
214
215          const int id2 = ev.GetPointerId(i);
216          const float x = x1 * velocity_tracker_.GetXVelocity(id2);
217          const float y = y1 * velocity_tracker_.GetYVelocity(id2);
218
219          const float dot = x + y;
220          if (dot < 0) {
221            velocity_tracker_.Clear();
222            break;
223          }
224        }
225      }
226      break;
227
228    case MotionEvent::ACTION_DOWN:
229      if (double_tap_listener_) {
230        bool had_tap_message = timeout_handler_->HasTimeout(TAP);
231        if (had_tap_message)
232          timeout_handler_->StopTimeout(TAP);
233        if (current_down_event_ && previous_up_event_ && had_tap_message &&
234            IsConsideredDoubleTap(
235                *current_down_event_, *previous_up_event_, ev)) {
236          // This is a second tap.
237          is_double_tapping_ = true;
238          // Give a callback with the first tap of the double-tap.
239          handled |= double_tap_listener_->OnDoubleTap(*current_down_event_);
240          // Give a callback with down event of the double-tap.
241          handled |= double_tap_listener_->OnDoubleTapEvent(ev);
242        } else {
243          // This is a first tap.
244          DCHECK(double_tap_timeout_ > base::TimeDelta());
245          timeout_handler_->StartTimeout(TAP);
246        }
247      }
248
249      down_focus_x_ = last_focus_x_ = focus_x;
250      down_focus_y_ = last_focus_y_ = focus_y;
251      current_down_event_ = ev.Clone();
252
253      always_in_tap_region_ = true;
254      always_in_bigger_tap_region_ = true;
255      still_down_ = true;
256      in_longpress_ = false;
257      defer_confirm_single_tap_ = false;
258
259      // Always start the SHOW_PRESS timer before the LONG_PRESS timer to ensure
260      // proper timeout ordering.
261      timeout_handler_->StartTimeout(SHOW_PRESS);
262      if (is_longpress_enabled_)
263        timeout_handler_->StartTimeout(LONG_PRESS);
264      handled |= listener_->OnDown(ev);
265      break;
266
267    case MotionEvent::ACTION_MOVE:
268      if (in_longpress_)
269        break;
270
271      {
272        const float scroll_x = last_focus_x_ - focus_x;
273        const float scroll_y = last_focus_y_ - focus_y;
274        if (is_double_tapping_) {
275          // Give the move events of the double-tap.
276          DCHECK(double_tap_listener_);
277          handled |= double_tap_listener_->OnDoubleTapEvent(ev);
278        } else if (always_in_tap_region_) {
279          const float delta_x = focus_x - down_focus_x_;
280          const float delta_y = focus_y - down_focus_y_;
281          const float distance_square = delta_x * delta_x + delta_y * delta_y;
282          if (distance_square > touch_slop_square_) {
283            handled = listener_->OnScroll(
284                *current_down_event_, ev, scroll_x, scroll_y);
285            last_focus_x_ = focus_x;
286            last_focus_y_ = focus_y;
287            always_in_tap_region_ = false;
288            timeout_handler_->Stop();
289          }
290          if (distance_square > double_tap_touch_slop_square_)
291            always_in_bigger_tap_region_ = false;
292        } else if (std::abs(scroll_x) > kScrollEpsilon ||
293                   std::abs(scroll_y) > kScrollEpsilon) {
294          handled =
295              listener_->OnScroll(*current_down_event_, ev, scroll_x, scroll_y);
296          last_focus_x_ = focus_x;
297          last_focus_y_ = focus_y;
298        }
299      }
300      break;
301
302    case MotionEvent::ACTION_UP:
303      still_down_ = false;
304      {
305        if (is_double_tapping_) {
306          // Finally, give the up event of the double-tap.
307          DCHECK(double_tap_listener_);
308          handled |= double_tap_listener_->OnDoubleTapEvent(ev);
309        } else if (in_longpress_) {
310          timeout_handler_->StopTimeout(TAP);
311          in_longpress_ = false;
312        } else if (always_in_tap_region_) {
313          handled = listener_->OnSingleTapUp(ev);
314          if (defer_confirm_single_tap_ && double_tap_listener_ != NULL) {
315            double_tap_listener_->OnSingleTapConfirmed(ev);
316          }
317        } else {
318
319          // A fling must travel the minimum tap distance.
320          const int pointer_id = ev.GetPointerId(0);
321          velocity_tracker_.ComputeCurrentVelocity(1000, max_fling_velocity_);
322          const float velocity_y = velocity_tracker_.GetYVelocity(pointer_id);
323          const float velocity_x = velocity_tracker_.GetXVelocity(pointer_id);
324
325          if ((std::abs(velocity_y) > min_fling_velocity_) ||
326              (std::abs(velocity_x) > min_fling_velocity_)) {
327            handled = listener_->OnFling(
328                *current_down_event_, ev, velocity_x, velocity_y);
329          }
330        }
331
332        previous_up_event_ = ev.Clone();
333
334        velocity_tracker_.Clear();
335        is_double_tapping_ = false;
336        defer_confirm_single_tap_ = false;
337        timeout_handler_->StopTimeout(SHOW_PRESS);
338        timeout_handler_->StopTimeout(LONG_PRESS);
339      }
340      break;
341
342    case MotionEvent::ACTION_CANCEL:
343      Cancel();
344      break;
345  }
346
347  return handled;
348}
349
350void GestureDetector::Init(const Config& config) {
351  DCHECK(listener_);
352
353  const float touch_slop = config.touch_slop + kSlopEpsilon;
354  const float double_tap_touch_slop = touch_slop;
355  const float double_tap_slop = config.double_tap_slop + kSlopEpsilon;
356  touch_slop_square_ = touch_slop * touch_slop;
357  double_tap_touch_slop_square_ = double_tap_touch_slop * double_tap_touch_slop;
358  double_tap_slop_square_ = double_tap_slop * double_tap_slop;
359  double_tap_timeout_ = config.double_tap_timeout;
360  min_fling_velocity_ = config.minimum_fling_velocity;
361  max_fling_velocity_ = config.maximum_fling_velocity;
362}
363
364void GestureDetector::OnShowPressTimeout() {
365  listener_->OnShowPress(*current_down_event_);
366}
367
368void GestureDetector::OnLongPressTimeout() {
369  timeout_handler_->StopTimeout(TAP);
370  defer_confirm_single_tap_ = false;
371  in_longpress_ = listener_->OnLongPress(*current_down_event_);
372}
373
374void GestureDetector::OnTapTimeout() {
375  if (!double_tap_listener_)
376    return;
377  if (!still_down_)
378    double_tap_listener_->OnSingleTapConfirmed(*current_down_event_);
379  else
380    defer_confirm_single_tap_ = true;
381}
382
383void GestureDetector::Cancel() {
384  timeout_handler_->Stop();
385  velocity_tracker_.Clear();
386  is_double_tapping_ = false;
387  still_down_ = false;
388  always_in_tap_region_ = false;
389  always_in_bigger_tap_region_ = false;
390  defer_confirm_single_tap_ = false;
391  in_longpress_ = false;
392}
393
394void GestureDetector::CancelTaps() {
395  timeout_handler_->Stop();
396  is_double_tapping_ = false;
397  always_in_tap_region_ = false;
398  always_in_bigger_tap_region_ = false;
399  defer_confirm_single_tap_ = false;
400  in_longpress_ = false;
401}
402
403bool GestureDetector::IsConsideredDoubleTap(
404    const MotionEvent& first_down,
405    const MotionEvent& first_up,
406    const MotionEvent& second_down) const {
407  if (!always_in_bigger_tap_region_)
408    return false;
409
410  if (second_down.GetEventTime() - first_up.GetEventTime() >
411      double_tap_timeout_)
412    return false;
413
414  const float delta_x = first_down.GetX() - second_down.GetX();
415  const float delta_y = first_down.GetY() - second_down.GetY();
416  return (delta_x * delta_x + delta_y * delta_y < double_tap_slop_square_);
417}
418
419}  // namespace ui
420