gesture_detector.cc revision 0529e5d033099cbfc42635f6f6183833b09dff6e
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::SetDoubleTapListener(
351    DoubleTapListener* double_tap_listener) {
352  if (double_tap_listener == double_tap_listener_)
353    return;
354
355  DCHECK(!is_double_tapping_);
356
357  // Null'ing the double-tap listener should flush an active tap timeout.
358  if (!double_tap_listener) {
359    if (timeout_handler_->HasTimeout(TAP)) {
360      timeout_handler_->StopTimeout(TAP);
361      OnTapTimeout();
362    }
363  }
364
365  double_tap_listener_ = double_tap_listener;
366}
367
368void GestureDetector::Init(const Config& config) {
369  DCHECK(listener_);
370
371  const float touch_slop = config.touch_slop + kSlopEpsilon;
372  const float double_tap_touch_slop = touch_slop;
373  const float double_tap_slop = config.double_tap_slop + kSlopEpsilon;
374  touch_slop_square_ = touch_slop * touch_slop;
375  double_tap_touch_slop_square_ = double_tap_touch_slop * double_tap_touch_slop;
376  double_tap_slop_square_ = double_tap_slop * double_tap_slop;
377  double_tap_timeout_ = config.double_tap_timeout;
378  min_fling_velocity_ = config.minimum_fling_velocity;
379  max_fling_velocity_ = config.maximum_fling_velocity;
380}
381
382void GestureDetector::OnShowPressTimeout() {
383  listener_->OnShowPress(*current_down_event_);
384}
385
386void GestureDetector::OnLongPressTimeout() {
387  timeout_handler_->StopTimeout(TAP);
388  defer_confirm_single_tap_ = false;
389  in_longpress_ = listener_->OnLongPress(*current_down_event_);
390}
391
392void GestureDetector::OnTapTimeout() {
393  if (!double_tap_listener_)
394    return;
395  if (!still_down_)
396    double_tap_listener_->OnSingleTapConfirmed(*current_down_event_);
397  else
398    defer_confirm_single_tap_ = true;
399}
400
401void GestureDetector::Cancel() {
402  timeout_handler_->Stop();
403  velocity_tracker_.Clear();
404  is_double_tapping_ = false;
405  still_down_ = false;
406  always_in_tap_region_ = false;
407  always_in_bigger_tap_region_ = false;
408  defer_confirm_single_tap_ = false;
409  in_longpress_ = false;
410}
411
412void GestureDetector::CancelTaps() {
413  timeout_handler_->Stop();
414  is_double_tapping_ = false;
415  always_in_tap_region_ = false;
416  always_in_bigger_tap_region_ = false;
417  defer_confirm_single_tap_ = false;
418  in_longpress_ = false;
419}
420
421bool GestureDetector::IsConsideredDoubleTap(
422    const MotionEvent& first_down,
423    const MotionEvent& first_up,
424    const MotionEvent& second_down) const {
425  if (!always_in_bigger_tap_region_)
426    return false;
427
428  if (second_down.GetEventTime() - first_up.GetEventTime() >
429      double_tap_timeout_)
430    return false;
431
432  const float delta_x = first_down.GetX() - second_down.GetX();
433  const float delta_y = first_down.GetY() - second_down.GetY();
434  return (delta_x * delta_x + delta_y * delta_y < double_tap_slop_square_);
435}
436
437}  // namespace ui
438