gesture_detector.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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// MSVC++ requires this to be set before any other includes to get M_PI.
6#define _USE_MATH_DEFINES
7
8#include "ui/events/gesture_detection/gesture_detector.h"
9
10#include <cmath>
11
12#include "base/timer/timer.h"
13#include "ui/events/gesture_detection/motion_event.h"
14
15namespace ui {
16namespace {
17
18// Using a small epsilon when comparing slop distances allows pixel perfect
19// slop determination when using fractional DIP coordinates (assuming the slop
20// region and DPI scale are reasonably proportioned).
21const float kSlopEpsilon = .05f;
22
23// Minimum distance a scroll must have traveled from the last scroll/focal point
24// to trigger an |OnScroll| callback.
25const float kScrollEpsilon = .1f;
26
27const float kDegreesToRadians = static_cast<float>(M_PI) / 180.0f;
28
29// Constants used by TimeoutGestureHandler.
30enum TimeoutEvent {
31  SHOW_PRESS = 0,
32  LONG_PRESS,
33  TAP,
34  TIMEOUT_EVENT_COUNT
35};
36
37}  // namespace
38
39// Note: These constants were taken directly from the default (unscaled)
40// versions found in Android's ViewConfiguration.
41GestureDetector::Config::Config()
42    : longpress_timeout(base::TimeDelta::FromMilliseconds(500)),
43      showpress_timeout(base::TimeDelta::FromMilliseconds(180)),
44      double_tap_timeout(base::TimeDelta::FromMilliseconds(300)),
45      double_tap_min_time(base::TimeDelta::FromMilliseconds(40)),
46      touch_slop(8),
47      double_tap_slop(100),
48      minimum_fling_velocity(50),
49      maximum_fling_velocity(8000),
50      swipe_enabled(false),
51      minimum_swipe_velocity(20),
52      maximum_swipe_deviation_angle(20.f),
53      two_finger_tap_enabled(false),
54      two_finger_tap_max_separation(300),
55      two_finger_tap_timeout(base::TimeDelta::FromMilliseconds(700)) {
56}
57
58GestureDetector::Config::~Config() {}
59
60bool GestureDetector::SimpleGestureListener::OnDown(const MotionEvent& e) {
61  return false;
62}
63
64void GestureDetector::SimpleGestureListener::OnShowPress(const MotionEvent& e) {
65}
66
67bool GestureDetector::SimpleGestureListener::OnSingleTapUp(
68    const MotionEvent& e) {
69  return false;
70}
71
72void GestureDetector::SimpleGestureListener::OnLongPress(const MotionEvent& e) {
73}
74
75bool GestureDetector::SimpleGestureListener::OnScroll(const MotionEvent& e1,
76                                                      const MotionEvent& e2,
77                                                      float distance_x,
78                                                      float distance_y) {
79  return false;
80}
81
82bool GestureDetector::SimpleGestureListener::OnFling(const MotionEvent& e1,
83                                                     const MotionEvent& e2,
84                                                     float velocity_x,
85                                                     float velocity_y) {
86  return false;
87}
88
89bool GestureDetector::SimpleGestureListener::OnSwipe(const MotionEvent& e1,
90                                                     const MotionEvent& e2,
91                                                     float velocity_x,
92                                                     float velocity_y) {
93  return false;
94}
95
96bool GestureDetector::SimpleGestureListener::OnTwoFingerTap(
97    const MotionEvent& e1,
98    const MotionEvent& e2) {
99  return false;
100}
101
102bool GestureDetector::SimpleGestureListener::OnSingleTapConfirmed(
103    const MotionEvent& e) {
104  return false;
105}
106
107bool GestureDetector::SimpleGestureListener::OnDoubleTap(const MotionEvent& e) {
108  return false;
109}
110
111bool GestureDetector::SimpleGestureListener::OnDoubleTapEvent(
112    const MotionEvent& e) {
113  return false;
114}
115
116class GestureDetector::TimeoutGestureHandler {
117 public:
118  TimeoutGestureHandler(const Config& config, GestureDetector* gesture_detector)
119      : gesture_detector_(gesture_detector) {
120    DCHECK(config.showpress_timeout <= config.longpress_timeout);
121
122    timeout_callbacks_[SHOW_PRESS] = &GestureDetector::OnShowPressTimeout;
123    timeout_delays_[SHOW_PRESS] = config.showpress_timeout;
124
125    timeout_callbacks_[LONG_PRESS] = &GestureDetector::OnLongPressTimeout;
126    timeout_delays_[LONG_PRESS] =
127        config.longpress_timeout + config.showpress_timeout;
128
129    timeout_callbacks_[TAP] = &GestureDetector::OnTapTimeout;
130    timeout_delays_[TAP] = config.double_tap_timeout;
131  }
132
133  ~TimeoutGestureHandler() {
134    Stop();
135  }
136
137  void StartTimeout(TimeoutEvent event) {
138    timeout_timers_[event].Start(FROM_HERE,
139                                 timeout_delays_[event],
140                                 gesture_detector_,
141                                 timeout_callbacks_[event]);
142  }
143
144  void StopTimeout(TimeoutEvent event) { timeout_timers_[event].Stop(); }
145
146  void Stop() {
147    for (size_t i = SHOW_PRESS; i < TIMEOUT_EVENT_COUNT; ++i)
148      timeout_timers_[i].Stop();
149  }
150
151  bool HasTimeout(TimeoutEvent event) const {
152    return timeout_timers_[event].IsRunning();
153  }
154
155 private:
156  typedef void (GestureDetector::*ReceiverMethod)();
157
158  GestureDetector* const gesture_detector_;
159  base::OneShotTimer<GestureDetector> timeout_timers_[TIMEOUT_EVENT_COUNT];
160  ReceiverMethod timeout_callbacks_[TIMEOUT_EVENT_COUNT];
161  base::TimeDelta timeout_delays_[TIMEOUT_EVENT_COUNT];
162};
163
164GestureDetector::GestureDetector(
165    const Config& config,
166    GestureListener* listener,
167    DoubleTapListener* optional_double_tap_listener)
168    : timeout_handler_(new TimeoutGestureHandler(config, this)),
169      listener_(listener),
170      double_tap_listener_(optional_double_tap_listener),
171      touch_slop_square_(0),
172      double_tap_touch_slop_square_(0),
173      double_tap_slop_square_(0),
174      two_finger_tap_distance_square_(0),
175      min_fling_velocity_(1),
176      max_fling_velocity_(1),
177      min_swipe_velocity_(0),
178      min_swipe_direction_component_ratio_(0),
179      still_down_(false),
180      defer_confirm_single_tap_(false),
181      always_in_tap_region_(false),
182      always_in_bigger_tap_region_(false),
183      two_finger_tap_allowed_for_gesture_(false),
184      is_double_tapping_(false),
185      last_focus_x_(0),
186      last_focus_y_(0),
187      down_focus_x_(0),
188      down_focus_y_(0),
189      longpress_enabled_(true),
190      swipe_enabled_(false),
191      two_finger_tap_enabled_(false) {
192  DCHECK(listener_);
193  Init(config);
194}
195
196GestureDetector::~GestureDetector() {}
197
198bool GestureDetector::OnTouchEvent(const MotionEvent& ev) {
199  const MotionEvent::Action action = ev.GetAction();
200
201  velocity_tracker_.AddMovement(ev);
202
203  const bool pointer_up = action == MotionEvent::ACTION_POINTER_UP;
204  const int skip_index = pointer_up ? ev.GetActionIndex() : -1;
205
206  // Determine focal point.
207  float sum_x = 0, sum_y = 0;
208  const int count = static_cast<int>(ev.GetPointerCount());
209  for (int i = 0; i < count; i++) {
210    if (skip_index == i)
211      continue;
212    sum_x += ev.GetX(i);
213    sum_y += ev.GetY(i);
214  }
215  const int div = pointer_up ? count - 1 : count;
216  const float focus_x = sum_x / div;
217  const float focus_y = sum_y / div;
218
219  bool handled = false;
220
221  switch (action) {
222    case MotionEvent::ACTION_POINTER_DOWN: {
223      down_focus_x_ = last_focus_x_ = focus_x;
224      down_focus_y_ = last_focus_y_ = focus_y;
225      // Cancel long press and taps.
226      CancelTaps();
227
228      if (!two_finger_tap_allowed_for_gesture_)
229        break;
230
231      const int action_index = ev.GetActionIndex();
232      const float dx = ev.GetX(action_index) - current_down_event_->GetX();
233      const float dy = ev.GetY(action_index) - current_down_event_->GetY();
234
235      if (ev.GetPointerCount() == 2 &&
236          dx * dx + dy * dy < two_finger_tap_distance_square_) {
237        secondary_pointer_down_event_ = ev.Clone();
238      } else {
239        two_finger_tap_allowed_for_gesture_ = false;
240      }
241    } break;
242
243    case MotionEvent::ACTION_POINTER_UP: {
244      down_focus_x_ = last_focus_x_ = focus_x;
245      down_focus_y_ = last_focus_y_ = focus_y;
246
247      // Check the dot product of current velocities.
248      // If the pointer that left was opposing another velocity vector, clear.
249      velocity_tracker_.ComputeCurrentVelocity(1000, max_fling_velocity_);
250      const int up_index = ev.GetActionIndex();
251      const int id1 = ev.GetPointerId(up_index);
252      const float vx1 = velocity_tracker_.GetXVelocity(id1);
253      const float vy1 = velocity_tracker_.GetYVelocity(id1);
254      float vx_total = vx1;
255      float vy_total = vy1;
256      for (int i = 0; i < count; i++) {
257        if (i == up_index)
258          continue;
259
260        const int id2 = ev.GetPointerId(i);
261        const float vx2 = velocity_tracker_.GetXVelocity(id2);
262        const float vy2 = velocity_tracker_.GetYVelocity(id2);
263        const float dot = vx1 * vx2 + vy1 * vy2;
264        if (dot < 0) {
265          vx_total = 0;
266          vy_total = 0;
267          velocity_tracker_.Clear();
268          break;
269        }
270        vx_total += vx2;
271        vy_total += vy2;
272      }
273
274      handled = HandleSwipeIfNeeded(ev, vx_total / count, vy_total / count);
275
276      if (two_finger_tap_allowed_for_gesture_ && ev.GetPointerCount() == 2 &&
277          (ev.GetEventTime() - secondary_pointer_down_event_->GetEventTime() <=
278           two_finger_tap_timeout_)) {
279        handled = listener_->OnTwoFingerTap(*current_down_event_, ev);
280      }
281      two_finger_tap_allowed_for_gesture_ = false;
282    } break;
283
284    case MotionEvent::ACTION_DOWN:
285      if (double_tap_listener_) {
286        bool had_tap_message = timeout_handler_->HasTimeout(TAP);
287        if (had_tap_message)
288          timeout_handler_->StopTimeout(TAP);
289        if (current_down_event_ && previous_up_event_ && had_tap_message &&
290            IsConsideredDoubleTap(
291                *current_down_event_, *previous_up_event_, ev)) {
292          // This is a second tap.
293          is_double_tapping_ = true;
294          // Give a callback with the first tap of the double-tap.
295          handled |= double_tap_listener_->OnDoubleTap(*current_down_event_);
296          // Give a callback with down event of the double-tap.
297          handled |= double_tap_listener_->OnDoubleTapEvent(ev);
298        } else {
299          // This is a first tap.
300          DCHECK(double_tap_timeout_ > base::TimeDelta());
301          timeout_handler_->StartTimeout(TAP);
302        }
303      }
304
305      down_focus_x_ = last_focus_x_ = focus_x;
306      down_focus_y_ = last_focus_y_ = focus_y;
307      current_down_event_ = ev.Clone();
308
309      secondary_pointer_down_event_.reset();
310      always_in_tap_region_ = true;
311      always_in_bigger_tap_region_ = true;
312      still_down_ = true;
313      defer_confirm_single_tap_ = false;
314      two_finger_tap_allowed_for_gesture_ = two_finger_tap_enabled_;
315
316      // Always start the SHOW_PRESS timer before the LONG_PRESS timer to ensure
317      // proper timeout ordering.
318      timeout_handler_->StartTimeout(SHOW_PRESS);
319      if (longpress_enabled_)
320        timeout_handler_->StartTimeout(LONG_PRESS);
321      handled |= listener_->OnDown(ev);
322      break;
323
324    case MotionEvent::ACTION_MOVE:
325      {
326        const float scroll_x = last_focus_x_ - focus_x;
327        const float scroll_y = last_focus_y_ - focus_y;
328        if (is_double_tapping_) {
329          // Give the move events of the double-tap.
330          DCHECK(double_tap_listener_);
331          handled |= double_tap_listener_->OnDoubleTapEvent(ev);
332        } else if (always_in_tap_region_) {
333          const float delta_x = focus_x - down_focus_x_;
334          const float delta_y = focus_y - down_focus_y_;
335          const float distance_square = delta_x * delta_x + delta_y * delta_y;
336          if (distance_square > touch_slop_square_) {
337            handled = listener_->OnScroll(
338                *current_down_event_, ev, scroll_x, scroll_y);
339            last_focus_x_ = focus_x;
340            last_focus_y_ = focus_y;
341            always_in_tap_region_ = false;
342            timeout_handler_->Stop();
343          }
344          if (distance_square > double_tap_touch_slop_square_)
345            always_in_bigger_tap_region_ = false;
346        } else if (std::abs(scroll_x) > kScrollEpsilon ||
347                   std::abs(scroll_y) > kScrollEpsilon) {
348          handled =
349              listener_->OnScroll(*current_down_event_, ev, scroll_x, scroll_y);
350          last_focus_x_ = focus_x;
351          last_focus_y_ = focus_y;
352        }
353
354        if (!two_finger_tap_allowed_for_gesture_)
355          break;
356
357        // Two-finger tap should be prevented if either pointer exceeds its
358        // (independent) slop region.
359        const int id0 = current_down_event_->GetPointerId(0);
360        const int ev_idx0 = ev.GetPointerId(0) == id0 ? 0 : 1;
361
362        // Check if the primary pointer exceeded the slop region.
363        float dx = current_down_event_->GetX() - ev.GetX(ev_idx0);
364        float dy = current_down_event_->GetY() - ev.GetY(ev_idx0);
365        if (dx * dx + dy * dy > touch_slop_square_) {
366          two_finger_tap_allowed_for_gesture_ = false;
367          break;
368        }
369        if (ev.GetPointerCount() == 2) {
370          // Check if the secondary pointer exceeded the slop region.
371          const int ev_idx1 = ev_idx0 == 0 ? 1 : 0;
372          const int idx1 = secondary_pointer_down_event_->GetActionIndex();
373          dx = secondary_pointer_down_event_->GetX(idx1) - ev.GetX(ev_idx1);
374          dy = secondary_pointer_down_event_->GetY(idx1) - ev.GetY(ev_idx1);
375          if (dx * dx + dy * dy > touch_slop_square_)
376            two_finger_tap_allowed_for_gesture_ = false;
377        }
378      }
379      break;
380
381    case MotionEvent::ACTION_UP:
382      still_down_ = false;
383      {
384        if (is_double_tapping_) {
385          // Finally, give the up event of the double-tap.
386          DCHECK(double_tap_listener_);
387          handled |= double_tap_listener_->OnDoubleTapEvent(ev);
388        } else if (always_in_tap_region_) {
389          handled = listener_->OnSingleTapUp(ev);
390          if (defer_confirm_single_tap_ && double_tap_listener_ != NULL) {
391            double_tap_listener_->OnSingleTapConfirmed(ev);
392          }
393        } else {
394
395          // A fling must travel the minimum tap distance.
396          const int pointer_id = ev.GetPointerId(0);
397          velocity_tracker_.ComputeCurrentVelocity(1000, max_fling_velocity_);
398          const float velocity_y = velocity_tracker_.GetYVelocity(pointer_id);
399          const float velocity_x = velocity_tracker_.GetXVelocity(pointer_id);
400
401          if ((std::abs(velocity_y) > min_fling_velocity_) ||
402              (std::abs(velocity_x) > min_fling_velocity_)) {
403            handled = listener_->OnFling(
404                *current_down_event_, ev, velocity_x, velocity_y);
405          }
406
407          handled |= HandleSwipeIfNeeded(ev, velocity_x, velocity_y);
408        }
409
410        previous_up_event_ = ev.Clone();
411
412        velocity_tracker_.Clear();
413        is_double_tapping_ = false;
414        defer_confirm_single_tap_ = false;
415        timeout_handler_->StopTimeout(SHOW_PRESS);
416        timeout_handler_->StopTimeout(LONG_PRESS);
417      }
418      break;
419
420    case MotionEvent::ACTION_CANCEL:
421      Cancel();
422      break;
423  }
424
425  return handled;
426}
427
428void GestureDetector::SetDoubleTapListener(
429    DoubleTapListener* double_tap_listener) {
430  if (double_tap_listener == double_tap_listener_)
431    return;
432
433  DCHECK(!is_double_tapping_);
434
435  // Null'ing the double-tap listener should flush an active tap timeout.
436  if (!double_tap_listener) {
437    if (timeout_handler_->HasTimeout(TAP)) {
438      timeout_handler_->StopTimeout(TAP);
439      OnTapTimeout();
440    }
441  }
442
443  double_tap_listener_ = double_tap_listener;
444}
445
446void GestureDetector::Init(const Config& config) {
447  DCHECK(listener_);
448
449  const float touch_slop = config.touch_slop + kSlopEpsilon;
450  const float double_tap_touch_slop = touch_slop;
451  const float double_tap_slop = config.double_tap_slop + kSlopEpsilon;
452  touch_slop_square_ = touch_slop * touch_slop;
453  double_tap_touch_slop_square_ = double_tap_touch_slop * double_tap_touch_slop;
454  double_tap_slop_square_ = double_tap_slop * double_tap_slop;
455  double_tap_timeout_ = config.double_tap_timeout;
456  double_tap_min_time_ = config.double_tap_min_time;
457  DCHECK(double_tap_min_time_ < double_tap_timeout_);
458  min_fling_velocity_ = config.minimum_fling_velocity;
459  max_fling_velocity_ = config.maximum_fling_velocity;
460
461  swipe_enabled_ = config.swipe_enabled;
462  min_swipe_velocity_ = config.minimum_swipe_velocity;
463  DCHECK_GT(config.maximum_swipe_deviation_angle, 0);
464  DCHECK_LE(config.maximum_swipe_deviation_angle, 45);
465  const float maximum_swipe_deviation_angle =
466      std::min(45.f, std::max(0.001f, config.maximum_swipe_deviation_angle));
467  min_swipe_direction_component_ratio_ =
468      1.f / tan(maximum_swipe_deviation_angle * kDegreesToRadians);
469
470  two_finger_tap_enabled_ = config.two_finger_tap_enabled;
471  two_finger_tap_distance_square_ = config.two_finger_tap_max_separation *
472                                    config.two_finger_tap_max_separation;
473  two_finger_tap_timeout_ = config.two_finger_tap_timeout;
474}
475
476void GestureDetector::OnShowPressTimeout() {
477  listener_->OnShowPress(*current_down_event_);
478}
479
480void GestureDetector::OnLongPressTimeout() {
481  timeout_handler_->StopTimeout(TAP);
482  defer_confirm_single_tap_ = false;
483  listener_->OnLongPress(*current_down_event_);
484}
485
486void GestureDetector::OnTapTimeout() {
487  if (!double_tap_listener_)
488    return;
489  if (!still_down_)
490    double_tap_listener_->OnSingleTapConfirmed(*current_down_event_);
491  else
492    defer_confirm_single_tap_ = true;
493}
494
495void GestureDetector::Cancel() {
496  CancelTaps();
497  velocity_tracker_.Clear();
498  still_down_ = false;
499}
500
501void GestureDetector::CancelTaps() {
502  timeout_handler_->Stop();
503  is_double_tapping_ = false;
504  always_in_tap_region_ = false;
505  always_in_bigger_tap_region_ = false;
506  defer_confirm_single_tap_ = false;
507}
508
509bool GestureDetector::IsConsideredDoubleTap(
510    const MotionEvent& first_down,
511    const MotionEvent& first_up,
512    const MotionEvent& second_down) const {
513  if (!always_in_bigger_tap_region_)
514    return false;
515
516  const base::TimeDelta delta_time =
517      second_down.GetEventTime() - first_up.GetEventTime();
518  if (delta_time < double_tap_min_time_ || delta_time > double_tap_timeout_)
519    return false;
520
521  const float delta_x = first_down.GetX() - second_down.GetX();
522  const float delta_y = first_down.GetY() - second_down.GetY();
523  return (delta_x * delta_x + delta_y * delta_y < double_tap_slop_square_);
524}
525
526bool GestureDetector::HandleSwipeIfNeeded(const MotionEvent& up,
527                                          float vx,
528                                          float vy) {
529  if (!swipe_enabled_ || (!vx && !vy))
530    return false;
531  float vx_abs = std::abs(vx);
532  float vy_abs = std::abs(vy);
533
534  if (vx_abs < min_swipe_velocity_)
535    vx_abs = vx = 0;
536  if (vy_abs < min_swipe_velocity_)
537    vy_abs = vy = 0;
538
539  // Note that the ratio will be 0 if both velocites are below the min.
540  float ratio = vx_abs > vy_abs ? vx_abs / std::max(vy_abs, 0.001f)
541                                : vy_abs / std::max(vx_abs, 0.001f);
542
543  if (ratio < min_swipe_direction_component_ratio_)
544    return false;
545
546  if (vx_abs > vy_abs)
547    vy = 0;
548  else
549    vx = 0;
550  return listener_->OnSwipe(*current_down_event_, up, vx, vy);
551}
552
553}  // namespace ui
554