Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / ui / events / gesture_detection / gesture_detector.cc
blob4180eb3338085423cc09ae1549493f58e06c26a6
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.
5 // MSVC++ requires this to be set before any other includes to get M_PI.
6 #define _USE_MATH_DEFINES
8 #include "ui/events/gesture_detection/gesture_detector.h"
10 #include <cmath>
12 #include "base/timer/timer.h"
13 #include "ui/events/gesture_detection/gesture_listeners.h"
14 #include "ui/events/gesture_detection/motion_event.h"
16 namespace ui {
17 namespace {
19 // Using a small epsilon when comparing slop distances allows pixel perfect
20 // slop determination when using fractional DIP coordinates (assuming the slop
21 // region and DPI scale are reasonably proportioned).
22 const float kSlopEpsilon = .05f;
24 // Minimum distance a scroll must have traveled from the last scroll/focal point
25 // to trigger an |OnScroll| callback.
26 const float kScrollEpsilon = .1f;
28 const float kDegreesToRadians = static_cast<float>(M_PI) / 180.0f;
30 // Constants used by TimeoutGestureHandler.
31 enum TimeoutEvent {
32 SHOW_PRESS = 0,
33 LONG_PRESS,
34 TAP,
35 TIMEOUT_EVENT_COUNT
38 } // namespace
40 // Note: These constants were taken directly from the default (unscaled)
41 // versions found in Android's ViewConfiguration. Do not change these default
42 // values without explicitly consulting an OWNER.
43 GestureDetector::Config::Config()
44 : longpress_timeout(base::TimeDelta::FromMilliseconds(500)),
45 showpress_timeout(base::TimeDelta::FromMilliseconds(180)),
46 double_tap_timeout(base::TimeDelta::FromMilliseconds(300)),
47 double_tap_min_time(base::TimeDelta::FromMilliseconds(40)),
48 touch_slop(8),
49 double_tap_slop(100),
50 minimum_fling_velocity(50),
51 maximum_fling_velocity(8000),
52 swipe_enabled(false),
53 minimum_swipe_velocity(20),
54 maximum_swipe_deviation_angle(20.f),
55 two_finger_tap_enabled(false),
56 two_finger_tap_max_separation(300),
57 two_finger_tap_timeout(base::TimeDelta::FromMilliseconds(700)),
58 velocity_tracker_strategy(VelocityTracker::Strategy::STRATEGY_DEFAULT) {
61 GestureDetector::Config::~Config() {}
63 class GestureDetector::TimeoutGestureHandler {
64 public:
65 TimeoutGestureHandler(const Config& config, GestureDetector* gesture_detector)
66 : gesture_detector_(gesture_detector) {
67 DCHECK(config.showpress_timeout <= config.longpress_timeout);
69 timeout_callbacks_[SHOW_PRESS] = &GestureDetector::OnShowPressTimeout;
70 timeout_delays_[SHOW_PRESS] = config.showpress_timeout;
72 timeout_callbacks_[LONG_PRESS] = &GestureDetector::OnLongPressTimeout;
73 timeout_delays_[LONG_PRESS] =
74 config.longpress_timeout + config.showpress_timeout;
76 timeout_callbacks_[TAP] = &GestureDetector::OnTapTimeout;
77 timeout_delays_[TAP] = config.double_tap_timeout;
80 ~TimeoutGestureHandler() {
81 Stop();
84 void StartTimeout(TimeoutEvent event) {
85 timeout_timers_[event].Start(FROM_HERE,
86 timeout_delays_[event],
87 gesture_detector_,
88 timeout_callbacks_[event]);
91 void StopTimeout(TimeoutEvent event) { timeout_timers_[event].Stop(); }
93 void Stop() {
94 for (size_t i = SHOW_PRESS; i < TIMEOUT_EVENT_COUNT; ++i)
95 timeout_timers_[i].Stop();
98 bool HasTimeout(TimeoutEvent event) const {
99 return timeout_timers_[event].IsRunning();
102 private:
103 typedef void (GestureDetector::*ReceiverMethod)();
105 GestureDetector* const gesture_detector_;
106 base::OneShotTimer<GestureDetector> timeout_timers_[TIMEOUT_EVENT_COUNT];
107 ReceiverMethod timeout_callbacks_[TIMEOUT_EVENT_COUNT];
108 base::TimeDelta timeout_delays_[TIMEOUT_EVENT_COUNT];
111 GestureDetector::GestureDetector(
112 const Config& config,
113 GestureListener* listener,
114 DoubleTapListener* optional_double_tap_listener)
115 : timeout_handler_(new TimeoutGestureHandler(config, this)),
116 listener_(listener),
117 double_tap_listener_(optional_double_tap_listener),
118 touch_slop_square_(0),
119 double_tap_touch_slop_square_(0),
120 double_tap_slop_square_(0),
121 two_finger_tap_distance_square_(0),
122 min_fling_velocity_(1),
123 max_fling_velocity_(1),
124 min_swipe_velocity_(0),
125 min_swipe_direction_component_ratio_(0),
126 still_down_(false),
127 defer_confirm_single_tap_(false),
128 always_in_tap_region_(false),
129 always_in_bigger_tap_region_(false),
130 two_finger_tap_allowed_for_gesture_(false),
131 is_double_tapping_(false),
132 last_focus_x_(0),
133 last_focus_y_(0),
134 down_focus_x_(0),
135 down_focus_y_(0),
136 longpress_enabled_(true),
137 showpress_enabled_(true),
138 swipe_enabled_(false),
139 two_finger_tap_enabled_(false),
140 velocity_tracker_(config.velocity_tracker_strategy) {
141 DCHECK(listener_);
142 Init(config);
145 GestureDetector::~GestureDetector() {}
147 bool GestureDetector::OnTouchEvent(const MotionEvent& ev) {
148 const MotionEvent::Action action = ev.GetAction();
150 velocity_tracker_.AddMovement(ev);
152 const bool pointer_up = action == MotionEvent::ACTION_POINTER_UP;
153 const int skip_index = pointer_up ? ev.GetActionIndex() : -1;
155 // Determine focal point.
156 float sum_x = 0, sum_y = 0;
157 const int count = static_cast<int>(ev.GetPointerCount());
158 for (int i = 0; i < count; i++) {
159 if (skip_index == i)
160 continue;
161 sum_x += ev.GetX(i);
162 sum_y += ev.GetY(i);
164 const int div = pointer_up ? count - 1 : count;
165 const float focus_x = sum_x / div;
166 const float focus_y = sum_y / div;
168 bool handled = false;
170 switch (action) {
171 case MotionEvent::ACTION_NONE:
172 NOTREACHED();
173 return handled;
175 case MotionEvent::ACTION_POINTER_DOWN: {
176 down_focus_x_ = last_focus_x_ = focus_x;
177 down_focus_y_ = last_focus_y_ = focus_y;
178 // Cancel long press and taps.
179 CancelTaps();
181 if (!two_finger_tap_allowed_for_gesture_)
182 break;
184 const int action_index = ev.GetActionIndex();
185 const float dx = ev.GetX(action_index) - current_down_event_->GetX();
186 const float dy = ev.GetY(action_index) - current_down_event_->GetY();
188 if (ev.GetPointerCount() == 2 &&
189 dx * dx + dy * dy < two_finger_tap_distance_square_) {
190 secondary_pointer_down_event_ = ev.Clone();
191 } else {
192 two_finger_tap_allowed_for_gesture_ = false;
194 } break;
196 case MotionEvent::ACTION_POINTER_UP: {
197 down_focus_x_ = last_focus_x_ = focus_x;
198 down_focus_y_ = last_focus_y_ = focus_y;
200 // Check the dot product of current velocities.
201 // If the pointer that left was opposing another velocity vector, clear.
202 velocity_tracker_.ComputeCurrentVelocity(1000, max_fling_velocity_);
203 const int up_index = ev.GetActionIndex();
204 const int id1 = ev.GetPointerId(up_index);
205 const float vx1 = velocity_tracker_.GetXVelocity(id1);
206 const float vy1 = velocity_tracker_.GetYVelocity(id1);
207 float vx_total = vx1;
208 float vy_total = vy1;
209 for (int i = 0; i < count; i++) {
210 if (i == up_index)
211 continue;
213 const int id2 = ev.GetPointerId(i);
214 const float vx2 = velocity_tracker_.GetXVelocity(id2);
215 const float vy2 = velocity_tracker_.GetYVelocity(id2);
216 const float dot = vx1 * vx2 + vy1 * vy2;
217 if (dot < 0) {
218 vx_total = 0;
219 vy_total = 0;
220 velocity_tracker_.Clear();
221 break;
223 vx_total += vx2;
224 vy_total += vy2;
227 handled = HandleSwipeIfNeeded(ev, vx_total / count, vy_total / count);
229 if (two_finger_tap_allowed_for_gesture_ && ev.GetPointerCount() == 2 &&
230 (ev.GetEventTime() - secondary_pointer_down_event_->GetEventTime() <=
231 two_finger_tap_timeout_)) {
232 handled = listener_->OnTwoFingerTap(*current_down_event_, ev);
234 two_finger_tap_allowed_for_gesture_ = false;
235 } break;
237 case MotionEvent::ACTION_DOWN:
238 if (double_tap_listener_) {
239 bool had_tap_message = timeout_handler_->HasTimeout(TAP);
240 if (had_tap_message)
241 timeout_handler_->StopTimeout(TAP);
242 if (current_down_event_ && previous_up_event_ && had_tap_message &&
243 IsConsideredDoubleTap(
244 *current_down_event_, *previous_up_event_, ev)) {
245 // This is a second tap.
246 is_double_tapping_ = true;
247 // Give a callback with the first tap of the double-tap.
248 handled |= double_tap_listener_->OnDoubleTap(*current_down_event_);
249 // Give a callback with down event of the double-tap.
250 handled |= double_tap_listener_->OnDoubleTapEvent(ev);
251 } else {
252 // This is a first tap.
253 DCHECK(double_tap_timeout_ > base::TimeDelta());
254 timeout_handler_->StartTimeout(TAP);
258 down_focus_x_ = last_focus_x_ = focus_x;
259 down_focus_y_ = last_focus_y_ = focus_y;
260 current_down_event_ = ev.Clone();
262 secondary_pointer_down_event_.reset();
263 always_in_tap_region_ = true;
264 always_in_bigger_tap_region_ = true;
265 still_down_ = true;
266 defer_confirm_single_tap_ = false;
267 two_finger_tap_allowed_for_gesture_ = two_finger_tap_enabled_;
269 // Always start the SHOW_PRESS timer before the LONG_PRESS timer to ensure
270 // proper timeout ordering.
271 if (showpress_enabled_)
272 timeout_handler_->StartTimeout(SHOW_PRESS);
273 if (longpress_enabled_)
274 timeout_handler_->StartTimeout(LONG_PRESS);
275 handled |= listener_->OnDown(ev);
276 break;
278 case MotionEvent::ACTION_MOVE:
280 const float scroll_x = last_focus_x_ - focus_x;
281 const float scroll_y = last_focus_y_ - focus_y;
282 if (is_double_tapping_) {
283 // Give the move events of the double-tap.
284 DCHECK(double_tap_listener_);
285 handled |= double_tap_listener_->OnDoubleTapEvent(ev);
286 } else if (always_in_tap_region_) {
287 const float delta_x = focus_x - down_focus_x_;
288 const float delta_y = focus_y - down_focus_y_;
289 const float distance_square = delta_x * delta_x + delta_y * delta_y;
290 if (distance_square > touch_slop_square_) {
291 handled = listener_->OnScroll(
292 *current_down_event_, ev, scroll_x, scroll_y);
293 last_focus_x_ = focus_x;
294 last_focus_y_ = focus_y;
295 always_in_tap_region_ = false;
296 timeout_handler_->Stop();
298 if (distance_square > double_tap_touch_slop_square_)
299 always_in_bigger_tap_region_ = false;
300 } else if (std::abs(scroll_x) > kScrollEpsilon ||
301 std::abs(scroll_y) > kScrollEpsilon) {
302 // We should eventually apply touch slop for multi-finger
303 // scrolls as well as single finger scrolls. See
304 // crbug.com/492185 for details.
305 handled =
306 listener_->OnScroll(*current_down_event_, ev, scroll_x, scroll_y);
307 last_focus_x_ = focus_x;
308 last_focus_y_ = focus_y;
311 if (!two_finger_tap_allowed_for_gesture_)
312 break;
314 // Two-finger tap should be prevented if either pointer exceeds its
315 // (independent) slop region.
316 const int id0 = current_down_event_->GetPointerId(0);
317 const int ev_idx0 = ev.GetPointerId(0) == id0 ? 0 : 1;
319 // Check if the primary pointer exceeded the slop region.
320 float dx = current_down_event_->GetX() - ev.GetX(ev_idx0);
321 float dy = current_down_event_->GetY() - ev.GetY(ev_idx0);
322 if (dx * dx + dy * dy > touch_slop_square_) {
323 two_finger_tap_allowed_for_gesture_ = false;
324 break;
326 if (ev.GetPointerCount() == 2) {
327 // Check if the secondary pointer exceeded the slop region.
328 const int ev_idx1 = ev_idx0 == 0 ? 1 : 0;
329 const int idx1 = secondary_pointer_down_event_->GetActionIndex();
330 dx = secondary_pointer_down_event_->GetX(idx1) - ev.GetX(ev_idx1);
331 dy = secondary_pointer_down_event_->GetY(idx1) - ev.GetY(ev_idx1);
332 if (dx * dx + dy * dy > touch_slop_square_)
333 two_finger_tap_allowed_for_gesture_ = false;
336 break;
338 case MotionEvent::ACTION_UP:
339 still_down_ = false;
341 if (is_double_tapping_) {
342 // Finally, give the up event of the double-tap.
343 DCHECK(double_tap_listener_);
344 handled |= double_tap_listener_->OnDoubleTapEvent(ev);
345 } else if (always_in_tap_region_) {
346 handled = listener_->OnSingleTapUp(ev);
347 if (defer_confirm_single_tap_ && double_tap_listener_ != NULL) {
348 double_tap_listener_->OnSingleTapConfirmed(ev);
350 } else {
352 // A fling must travel the minimum tap distance.
353 const int pointer_id = ev.GetPointerId(0);
354 velocity_tracker_.ComputeCurrentVelocity(1000, max_fling_velocity_);
355 const float velocity_y = velocity_tracker_.GetYVelocity(pointer_id);
356 const float velocity_x = velocity_tracker_.GetXVelocity(pointer_id);
358 if ((std::abs(velocity_y) > min_fling_velocity_) ||
359 (std::abs(velocity_x) > min_fling_velocity_)) {
360 handled = listener_->OnFling(
361 *current_down_event_, ev, velocity_x, velocity_y);
364 handled |= HandleSwipeIfNeeded(ev, velocity_x, velocity_y);
367 previous_up_event_ = ev.Clone();
369 velocity_tracker_.Clear();
370 is_double_tapping_ = false;
371 defer_confirm_single_tap_ = false;
372 timeout_handler_->StopTimeout(SHOW_PRESS);
373 timeout_handler_->StopTimeout(LONG_PRESS);
375 break;
377 case MotionEvent::ACTION_CANCEL:
378 Cancel();
379 break;
382 return handled;
385 void GestureDetector::SetDoubleTapListener(
386 DoubleTapListener* double_tap_listener) {
387 if (double_tap_listener == double_tap_listener_)
388 return;
390 DCHECK(!is_double_tapping_);
392 // Null'ing the double-tap listener should flush an active tap timeout.
393 if (!double_tap_listener) {
394 if (timeout_handler_->HasTimeout(TAP)) {
395 timeout_handler_->StopTimeout(TAP);
396 OnTapTimeout();
400 double_tap_listener_ = double_tap_listener;
403 void GestureDetector::Init(const Config& config) {
404 DCHECK(listener_);
406 const float touch_slop = config.touch_slop + kSlopEpsilon;
407 const float double_tap_touch_slop = touch_slop;
408 const float double_tap_slop = config.double_tap_slop + kSlopEpsilon;
409 touch_slop_square_ = touch_slop * touch_slop;
410 double_tap_touch_slop_square_ = double_tap_touch_slop * double_tap_touch_slop;
411 double_tap_slop_square_ = double_tap_slop * double_tap_slop;
412 double_tap_timeout_ = config.double_tap_timeout;
413 double_tap_min_time_ = config.double_tap_min_time;
414 DCHECK(double_tap_min_time_ < double_tap_timeout_);
415 min_fling_velocity_ = config.minimum_fling_velocity;
416 max_fling_velocity_ = config.maximum_fling_velocity;
418 swipe_enabled_ = config.swipe_enabled;
419 min_swipe_velocity_ = config.minimum_swipe_velocity;
420 DCHECK_GT(config.maximum_swipe_deviation_angle, 0);
421 DCHECK_LE(config.maximum_swipe_deviation_angle, 45);
422 const float maximum_swipe_deviation_angle =
423 std::min(45.f, std::max(0.001f, config.maximum_swipe_deviation_angle));
424 min_swipe_direction_component_ratio_ =
425 1.f / tan(maximum_swipe_deviation_angle * kDegreesToRadians);
427 two_finger_tap_enabled_ = config.two_finger_tap_enabled;
428 two_finger_tap_distance_square_ = config.two_finger_tap_max_separation *
429 config.two_finger_tap_max_separation;
430 two_finger_tap_timeout_ = config.two_finger_tap_timeout;
433 void GestureDetector::OnShowPressTimeout() {
434 listener_->OnShowPress(*current_down_event_);
437 void GestureDetector::OnLongPressTimeout() {
438 timeout_handler_->StopTimeout(TAP);
439 defer_confirm_single_tap_ = false;
440 listener_->OnLongPress(*current_down_event_);
443 void GestureDetector::OnTapTimeout() {
444 if (!double_tap_listener_)
445 return;
446 if (!still_down_) {
447 CHECK(previous_up_event_);
448 double_tap_listener_->OnSingleTapConfirmed(*previous_up_event_);
449 } else {
450 defer_confirm_single_tap_ = true;
454 void GestureDetector::Cancel() {
455 CancelTaps();
456 velocity_tracker_.Clear();
457 still_down_ = false;
460 void GestureDetector::CancelTaps() {
461 timeout_handler_->Stop();
462 is_double_tapping_ = false;
463 always_in_tap_region_ = false;
464 always_in_bigger_tap_region_ = false;
465 defer_confirm_single_tap_ = false;
468 bool GestureDetector::IsConsideredDoubleTap(
469 const MotionEvent& first_down,
470 const MotionEvent& first_up,
471 const MotionEvent& second_down) const {
472 if (!always_in_bigger_tap_region_)
473 return false;
475 const base::TimeDelta delta_time =
476 second_down.GetEventTime() - first_up.GetEventTime();
477 if (delta_time < double_tap_min_time_ || delta_time > double_tap_timeout_)
478 return false;
480 const float delta_x = first_down.GetX() - second_down.GetX();
481 const float delta_y = first_down.GetY() - second_down.GetY();
482 return (delta_x * delta_x + delta_y * delta_y < double_tap_slop_square_);
485 bool GestureDetector::HandleSwipeIfNeeded(const MotionEvent& up,
486 float vx,
487 float vy) {
488 if (!swipe_enabled_ || (!vx && !vy))
489 return false;
490 float vx_abs = std::abs(vx);
491 float vy_abs = std::abs(vy);
493 if (vx_abs < min_swipe_velocity_)
494 vx_abs = vx = 0;
495 if (vy_abs < min_swipe_velocity_)
496 vy_abs = vy = 0;
498 // Note that the ratio will be 0 if both velocites are below the min.
499 float ratio = vx_abs > vy_abs ? vx_abs / std::max(vy_abs, 0.001f)
500 : vy_abs / std::max(vx_abs, 0.001f);
502 if (ratio < min_swipe_direction_component_ratio_)
503 return false;
505 if (vx_abs > vy_abs)
506 vy = 0;
507 else
508 vx = 0;
509 return listener_->OnSwipe(*current_down_event_, up, vx, vy);
512 } // namespace ui