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"
12 #include "base/timer/timer.h"
13 #include "ui/events/gesture_detection/gesture_listeners.h"
14 #include "ui/events/gesture_detection/motion_event.h"
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.
40 // Note: These constants were taken directly from the default (unscaled)
41 // versions found in Android's ViewConfiguration.
42 GestureDetector::Config::Config()
43 : longpress_timeout(base::TimeDelta::FromMilliseconds(500)),
44 showpress_timeout(base::TimeDelta::FromMilliseconds(180)),
45 double_tap_timeout(base::TimeDelta::FromMilliseconds(300)),
46 double_tap_min_time(base::TimeDelta::FromMilliseconds(40)),
49 minimum_fling_velocity(50),
50 maximum_fling_velocity(8000),
52 minimum_swipe_velocity(20),
53 maximum_swipe_deviation_angle(20.f),
54 two_finger_tap_enabled(false),
55 two_finger_tap_max_separation(300),
56 two_finger_tap_timeout(base::TimeDelta::FromMilliseconds(700)),
57 velocity_tracker_strategy(VelocityTracker::Strategy::STRATEGY_DEFAULT) {
60 GestureDetector::Config::~Config() {}
62 class GestureDetector::TimeoutGestureHandler {
64 TimeoutGestureHandler(const Config& config, GestureDetector* gesture_detector)
65 : gesture_detector_(gesture_detector) {
66 DCHECK(config.showpress_timeout <= config.longpress_timeout);
68 timeout_callbacks_[SHOW_PRESS] = &GestureDetector::OnShowPressTimeout;
69 timeout_delays_[SHOW_PRESS] = config.showpress_timeout;
71 timeout_callbacks_[LONG_PRESS] = &GestureDetector::OnLongPressTimeout;
72 timeout_delays_[LONG_PRESS] =
73 config.longpress_timeout + config.showpress_timeout;
75 timeout_callbacks_[TAP] = &GestureDetector::OnTapTimeout;
76 timeout_delays_[TAP] = config.double_tap_timeout;
79 ~TimeoutGestureHandler() {
83 void StartTimeout(TimeoutEvent event) {
84 timeout_timers_[event].Start(FROM_HERE,
85 timeout_delays_[event],
87 timeout_callbacks_[event]);
90 void StopTimeout(TimeoutEvent event) { timeout_timers_[event].Stop(); }
93 for (size_t i = SHOW_PRESS; i < TIMEOUT_EVENT_COUNT; ++i)
94 timeout_timers_[i].Stop();
97 bool HasTimeout(TimeoutEvent event) const {
98 return timeout_timers_[event].IsRunning();
102 typedef void (GestureDetector::*ReceiverMethod)();
104 GestureDetector* const gesture_detector_;
105 base::OneShotTimer<GestureDetector> timeout_timers_[TIMEOUT_EVENT_COUNT];
106 ReceiverMethod timeout_callbacks_[TIMEOUT_EVENT_COUNT];
107 base::TimeDelta timeout_delays_[TIMEOUT_EVENT_COUNT];
110 GestureDetector::GestureDetector(
111 const Config& config,
112 GestureListener* listener,
113 DoubleTapListener* optional_double_tap_listener)
114 : timeout_handler_(new TimeoutGestureHandler(config, this)),
116 double_tap_listener_(optional_double_tap_listener),
117 touch_slop_square_(0),
118 double_tap_touch_slop_square_(0),
119 double_tap_slop_square_(0),
120 two_finger_tap_distance_square_(0),
121 min_fling_velocity_(1),
122 max_fling_velocity_(1),
123 min_swipe_velocity_(0),
124 min_swipe_direction_component_ratio_(0),
126 defer_confirm_single_tap_(false),
127 always_in_tap_region_(false),
128 always_in_bigger_tap_region_(false),
129 two_finger_tap_allowed_for_gesture_(false),
130 is_double_tapping_(false),
135 longpress_enabled_(true),
136 showpress_enabled_(true),
137 swipe_enabled_(false),
138 two_finger_tap_enabled_(false),
139 velocity_tracker_(config.velocity_tracker_strategy) {
144 GestureDetector::~GestureDetector() {}
146 bool GestureDetector::OnTouchEvent(const MotionEvent& ev) {
147 const MotionEvent::Action action = ev.GetAction();
149 velocity_tracker_.AddMovement(ev);
151 const bool pointer_up = action == MotionEvent::ACTION_POINTER_UP;
152 const int skip_index = pointer_up ? ev.GetActionIndex() : -1;
154 // Determine focal point.
155 float sum_x = 0, sum_y = 0;
156 const int count = static_cast<int>(ev.GetPointerCount());
157 for (int i = 0; i < count; i++) {
163 const int div = pointer_up ? count - 1 : count;
164 const float focus_x = sum_x / div;
165 const float focus_y = sum_y / div;
167 bool handled = false;
170 case MotionEvent::ACTION_POINTER_DOWN: {
171 down_focus_x_ = last_focus_x_ = focus_x;
172 down_focus_y_ = last_focus_y_ = focus_y;
173 // Cancel long press and taps.
176 if (!two_finger_tap_allowed_for_gesture_)
179 const int action_index = ev.GetActionIndex();
180 const float dx = ev.GetX(action_index) - current_down_event_->GetX();
181 const float dy = ev.GetY(action_index) - current_down_event_->GetY();
183 if (ev.GetPointerCount() == 2 &&
184 dx * dx + dy * dy < two_finger_tap_distance_square_) {
185 secondary_pointer_down_event_ = ev.Clone();
187 two_finger_tap_allowed_for_gesture_ = false;
191 case MotionEvent::ACTION_POINTER_UP: {
192 down_focus_x_ = last_focus_x_ = focus_x;
193 down_focus_y_ = last_focus_y_ = focus_y;
195 // Check the dot product of current velocities.
196 // If the pointer that left was opposing another velocity vector, clear.
197 velocity_tracker_.ComputeCurrentVelocity(1000, max_fling_velocity_);
198 const int up_index = ev.GetActionIndex();
199 const int id1 = ev.GetPointerId(up_index);
200 const float vx1 = velocity_tracker_.GetXVelocity(id1);
201 const float vy1 = velocity_tracker_.GetYVelocity(id1);
202 float vx_total = vx1;
203 float vy_total = vy1;
204 for (int i = 0; i < count; i++) {
208 const int id2 = ev.GetPointerId(i);
209 const float vx2 = velocity_tracker_.GetXVelocity(id2);
210 const float vy2 = velocity_tracker_.GetYVelocity(id2);
211 const float dot = vx1 * vx2 + vy1 * vy2;
215 velocity_tracker_.Clear();
222 handled = HandleSwipeIfNeeded(ev, vx_total / count, vy_total / count);
224 if (two_finger_tap_allowed_for_gesture_ && ev.GetPointerCount() == 2 &&
225 (ev.GetEventTime() - secondary_pointer_down_event_->GetEventTime() <=
226 two_finger_tap_timeout_)) {
227 handled = listener_->OnTwoFingerTap(*current_down_event_, ev);
229 two_finger_tap_allowed_for_gesture_ = false;
232 case MotionEvent::ACTION_DOWN:
233 if (double_tap_listener_) {
234 bool had_tap_message = timeout_handler_->HasTimeout(TAP);
236 timeout_handler_->StopTimeout(TAP);
237 if (current_down_event_ && previous_up_event_ && had_tap_message &&
238 IsConsideredDoubleTap(
239 *current_down_event_, *previous_up_event_, ev)) {
240 // This is a second tap.
241 is_double_tapping_ = true;
242 // Give a callback with the first tap of the double-tap.
243 handled |= double_tap_listener_->OnDoubleTap(*current_down_event_);
244 // Give a callback with down event of the double-tap.
245 handled |= double_tap_listener_->OnDoubleTapEvent(ev);
247 // This is a first tap.
248 DCHECK(double_tap_timeout_ > base::TimeDelta());
249 timeout_handler_->StartTimeout(TAP);
253 down_focus_x_ = last_focus_x_ = focus_x;
254 down_focus_y_ = last_focus_y_ = focus_y;
255 current_down_event_ = ev.Clone();
257 secondary_pointer_down_event_.reset();
258 always_in_tap_region_ = true;
259 always_in_bigger_tap_region_ = true;
261 defer_confirm_single_tap_ = false;
262 two_finger_tap_allowed_for_gesture_ = two_finger_tap_enabled_;
264 // Always start the SHOW_PRESS timer before the LONG_PRESS timer to ensure
265 // proper timeout ordering.
266 if (showpress_enabled_)
267 timeout_handler_->StartTimeout(SHOW_PRESS);
268 if (longpress_enabled_)
269 timeout_handler_->StartTimeout(LONG_PRESS);
270 handled |= listener_->OnDown(ev);
273 case MotionEvent::ACTION_MOVE:
275 const float scroll_x = last_focus_x_ - focus_x;
276 const float scroll_y = last_focus_y_ - focus_y;
277 if (is_double_tapping_) {
278 // Give the move events of the double-tap.
279 DCHECK(double_tap_listener_);
280 handled |= double_tap_listener_->OnDoubleTapEvent(ev);
281 } else if (always_in_tap_region_) {
282 const float delta_x = focus_x - down_focus_x_;
283 const float delta_y = focus_y - down_focus_y_;
284 const float distance_square = delta_x * delta_x + delta_y * delta_y;
285 if (distance_square > touch_slop_square_) {
286 handled = listener_->OnScroll(
287 *current_down_event_, ev, scroll_x, scroll_y);
288 last_focus_x_ = focus_x;
289 last_focus_y_ = focus_y;
290 always_in_tap_region_ = false;
291 timeout_handler_->Stop();
293 if (distance_square > double_tap_touch_slop_square_)
294 always_in_bigger_tap_region_ = false;
295 } else if (std::abs(scroll_x) > kScrollEpsilon ||
296 std::abs(scroll_y) > kScrollEpsilon) {
298 listener_->OnScroll(*current_down_event_, ev, scroll_x, scroll_y);
299 last_focus_x_ = focus_x;
300 last_focus_y_ = focus_y;
303 if (!two_finger_tap_allowed_for_gesture_)
306 // Two-finger tap should be prevented if either pointer exceeds its
307 // (independent) slop region.
308 const int id0 = current_down_event_->GetPointerId(0);
309 const int ev_idx0 = ev.GetPointerId(0) == id0 ? 0 : 1;
311 // Check if the primary pointer exceeded the slop region.
312 float dx = current_down_event_->GetX() - ev.GetX(ev_idx0);
313 float dy = current_down_event_->GetY() - ev.GetY(ev_idx0);
314 if (dx * dx + dy * dy > touch_slop_square_) {
315 two_finger_tap_allowed_for_gesture_ = false;
318 if (ev.GetPointerCount() == 2) {
319 // Check if the secondary pointer exceeded the slop region.
320 const int ev_idx1 = ev_idx0 == 0 ? 1 : 0;
321 const int idx1 = secondary_pointer_down_event_->GetActionIndex();
322 dx = secondary_pointer_down_event_->GetX(idx1) - ev.GetX(ev_idx1);
323 dy = secondary_pointer_down_event_->GetY(idx1) - ev.GetY(ev_idx1);
324 if (dx * dx + dy * dy > touch_slop_square_)
325 two_finger_tap_allowed_for_gesture_ = false;
330 case MotionEvent::ACTION_UP:
333 if (is_double_tapping_) {
334 // Finally, give the up event of the double-tap.
335 DCHECK(double_tap_listener_);
336 handled |= double_tap_listener_->OnDoubleTapEvent(ev);
337 } else if (always_in_tap_region_) {
338 handled = listener_->OnSingleTapUp(ev);
339 if (defer_confirm_single_tap_ && double_tap_listener_ != NULL) {
340 double_tap_listener_->OnSingleTapConfirmed(ev);
344 // A fling must travel the minimum tap distance.
345 const int pointer_id = ev.GetPointerId(0);
346 velocity_tracker_.ComputeCurrentVelocity(1000, max_fling_velocity_);
347 const float velocity_y = velocity_tracker_.GetYVelocity(pointer_id);
348 const float velocity_x = velocity_tracker_.GetXVelocity(pointer_id);
350 if ((std::abs(velocity_y) > min_fling_velocity_) ||
351 (std::abs(velocity_x) > min_fling_velocity_)) {
352 handled = listener_->OnFling(
353 *current_down_event_, ev, velocity_x, velocity_y);
356 handled |= HandleSwipeIfNeeded(ev, velocity_x, velocity_y);
359 previous_up_event_ = ev.Clone();
361 velocity_tracker_.Clear();
362 is_double_tapping_ = false;
363 defer_confirm_single_tap_ = false;
364 timeout_handler_->StopTimeout(SHOW_PRESS);
365 timeout_handler_->StopTimeout(LONG_PRESS);
369 case MotionEvent::ACTION_CANCEL:
377 void GestureDetector::SetDoubleTapListener(
378 DoubleTapListener* double_tap_listener) {
379 if (double_tap_listener == double_tap_listener_)
382 DCHECK(!is_double_tapping_);
384 // Null'ing the double-tap listener should flush an active tap timeout.
385 if (!double_tap_listener) {
386 if (timeout_handler_->HasTimeout(TAP)) {
387 timeout_handler_->StopTimeout(TAP);
392 double_tap_listener_ = double_tap_listener;
395 void GestureDetector::Init(const Config& config) {
398 const float touch_slop = config.touch_slop + kSlopEpsilon;
399 const float double_tap_touch_slop = touch_slop;
400 const float double_tap_slop = config.double_tap_slop + kSlopEpsilon;
401 touch_slop_square_ = touch_slop * touch_slop;
402 double_tap_touch_slop_square_ = double_tap_touch_slop * double_tap_touch_slop;
403 double_tap_slop_square_ = double_tap_slop * double_tap_slop;
404 double_tap_timeout_ = config.double_tap_timeout;
405 double_tap_min_time_ = config.double_tap_min_time;
406 DCHECK(double_tap_min_time_ < double_tap_timeout_);
407 min_fling_velocity_ = config.minimum_fling_velocity;
408 max_fling_velocity_ = config.maximum_fling_velocity;
410 swipe_enabled_ = config.swipe_enabled;
411 min_swipe_velocity_ = config.minimum_swipe_velocity;
412 DCHECK_GT(config.maximum_swipe_deviation_angle, 0);
413 DCHECK_LE(config.maximum_swipe_deviation_angle, 45);
414 const float maximum_swipe_deviation_angle =
415 std::min(45.f, std::max(0.001f, config.maximum_swipe_deviation_angle));
416 min_swipe_direction_component_ratio_ =
417 1.f / tan(maximum_swipe_deviation_angle * kDegreesToRadians);
419 two_finger_tap_enabled_ = config.two_finger_tap_enabled;
420 two_finger_tap_distance_square_ = config.two_finger_tap_max_separation *
421 config.two_finger_tap_max_separation;
422 two_finger_tap_timeout_ = config.two_finger_tap_timeout;
425 void GestureDetector::OnShowPressTimeout() {
426 listener_->OnShowPress(*current_down_event_);
429 void GestureDetector::OnLongPressTimeout() {
430 timeout_handler_->StopTimeout(TAP);
431 defer_confirm_single_tap_ = false;
432 listener_->OnLongPress(*current_down_event_);
435 void GestureDetector::OnTapTimeout() {
436 if (!double_tap_listener_)
439 double_tap_listener_->OnSingleTapConfirmed(*current_down_event_);
441 defer_confirm_single_tap_ = true;
444 void GestureDetector::Cancel() {
446 velocity_tracker_.Clear();
450 void GestureDetector::CancelTaps() {
451 timeout_handler_->Stop();
452 is_double_tapping_ = false;
453 always_in_tap_region_ = false;
454 always_in_bigger_tap_region_ = false;
455 defer_confirm_single_tap_ = false;
458 bool GestureDetector::IsConsideredDoubleTap(
459 const MotionEvent& first_down,
460 const MotionEvent& first_up,
461 const MotionEvent& second_down) const {
462 if (!always_in_bigger_tap_region_)
465 const base::TimeDelta delta_time =
466 second_down.GetEventTime() - first_up.GetEventTime();
467 if (delta_time < double_tap_min_time_ || delta_time > double_tap_timeout_)
470 const float delta_x = first_down.GetX() - second_down.GetX();
471 const float delta_y = first_down.GetY() - second_down.GetY();
472 return (delta_x * delta_x + delta_y * delta_y < double_tap_slop_square_);
475 bool GestureDetector::HandleSwipeIfNeeded(const MotionEvent& up,
478 if (!swipe_enabled_ || (!vx && !vy))
480 float vx_abs = std::abs(vx);
481 float vy_abs = std::abs(vy);
483 if (vx_abs < min_swipe_velocity_)
485 if (vy_abs < min_swipe_velocity_)
488 // Note that the ratio will be 0 if both velocites are below the min.
489 float ratio = vx_abs > vy_abs ? vx_abs / std::max(vy_abs, 0.001f)
490 : vy_abs / std::max(vx_abs, 0.001f);
492 if (ratio < min_swipe_direction_component_ratio_)
499 return listener_->OnSwipe(*current_down_event_, up, vx, vy);