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/motion_event.h"
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).
21 const float kSlopEpsilon
= .05f
;
23 // Minimum distance a scroll must have traveled from the last scroll/focal point
24 // to trigger an |OnScroll| callback.
25 const float kScrollEpsilon
= .1f
;
27 const float kDegreesToRadians
= static_cast<float>(M_PI
) / 180.0f
;
29 // Constants used by TimeoutGestureHandler.
39 // Note: These constants were taken directly from the default (unscaled)
40 // versions found in Android's ViewConfiguration.
41 GestureDetector::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)),
48 minimum_fling_velocity(50),
49 maximum_fling_velocity(8000),
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)) {
58 GestureDetector::Config::~Config() {}
60 bool GestureDetector::SimpleGestureListener::OnDown(const MotionEvent
& e
) {
64 void GestureDetector::SimpleGestureListener::OnShowPress(const MotionEvent
& e
) {
67 bool GestureDetector::SimpleGestureListener::OnSingleTapUp(
68 const MotionEvent
& e
) {
72 void GestureDetector::SimpleGestureListener::OnLongPress(const MotionEvent
& e
) {
75 bool GestureDetector::SimpleGestureListener::OnScroll(const MotionEvent
& e1
,
76 const MotionEvent
& e2
,
82 bool GestureDetector::SimpleGestureListener::OnFling(const MotionEvent
& e1
,
83 const MotionEvent
& e2
,
89 bool GestureDetector::SimpleGestureListener::OnSwipe(const MotionEvent
& e1
,
90 const MotionEvent
& e2
,
96 bool GestureDetector::SimpleGestureListener::OnTwoFingerTap(
97 const MotionEvent
& e1
,
98 const MotionEvent
& e2
) {
102 bool GestureDetector::SimpleGestureListener::OnSingleTapConfirmed(
103 const MotionEvent
& e
) {
107 bool GestureDetector::SimpleGestureListener::OnDoubleTap(const MotionEvent
& e
) {
111 bool GestureDetector::SimpleGestureListener::OnDoubleTapEvent(
112 const MotionEvent
& e
) {
116 class GestureDetector::TimeoutGestureHandler
{
118 TimeoutGestureHandler(const Config
& config
, GestureDetector
* gesture_detector
)
119 : gesture_detector_(gesture_detector
) {
120 DCHECK(config
.showpress_timeout
<= config
.longpress_timeout
);
122 timeout_callbacks_
[SHOW_PRESS
] = &GestureDetector::OnShowPressTimeout
;
123 timeout_delays_
[SHOW_PRESS
] = config
.showpress_timeout
;
125 timeout_callbacks_
[LONG_PRESS
] = &GestureDetector::OnLongPressTimeout
;
126 timeout_delays_
[LONG_PRESS
] =
127 config
.longpress_timeout
+ config
.showpress_timeout
;
129 timeout_callbacks_
[TAP
] = &GestureDetector::OnTapTimeout
;
130 timeout_delays_
[TAP
] = config
.double_tap_timeout
;
133 ~TimeoutGestureHandler() {
137 void StartTimeout(TimeoutEvent event
) {
138 timeout_timers_
[event
].Start(FROM_HERE
,
139 timeout_delays_
[event
],
141 timeout_callbacks_
[event
]);
144 void StopTimeout(TimeoutEvent event
) { timeout_timers_
[event
].Stop(); }
147 for (size_t i
= SHOW_PRESS
; i
< TIMEOUT_EVENT_COUNT
; ++i
)
148 timeout_timers_
[i
].Stop();
151 bool HasTimeout(TimeoutEvent event
) const {
152 return timeout_timers_
[event
].IsRunning();
156 typedef void (GestureDetector::*ReceiverMethod
)();
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
];
164 GestureDetector::GestureDetector(
165 const Config
& config
,
166 GestureListener
* listener
,
167 DoubleTapListener
* optional_double_tap_listener
)
168 : timeout_handler_(new TimeoutGestureHandler(config
, this)),
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),
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),
189 longpress_enabled_(true),
190 swipe_enabled_(false),
191 two_finger_tap_enabled_(false) {
196 GestureDetector::~GestureDetector() {}
198 bool GestureDetector::OnTouchEvent(const MotionEvent
& ev
) {
199 const MotionEvent::Action action
= ev
.GetAction();
201 velocity_tracker_
.AddMovement(ev
);
203 const bool pointer_up
= action
== MotionEvent::ACTION_POINTER_UP
;
204 const int skip_index
= pointer_up
? ev
.GetActionIndex() : -1;
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
++) {
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
;
219 bool handled
= false;
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.
228 if (!two_finger_tap_allowed_for_gesture_
)
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();
235 if (ev
.GetPointerCount() == 2 &&
236 dx
* dx
+ dy
* dy
< two_finger_tap_distance_square_
) {
237 secondary_pointer_down_event_
= ev
.Clone();
239 two_finger_tap_allowed_for_gesture_
= false;
243 case MotionEvent::ACTION_POINTER_UP
: {
244 down_focus_x_
= last_focus_x_
= focus_x
;
245 down_focus_y_
= last_focus_y_
= focus_y
;
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
++) {
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
;
267 velocity_tracker_
.Clear();
274 handled
= HandleSwipeIfNeeded(ev
, vx_total
/ count
, vy_total
/ count
);
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
);
281 two_finger_tap_allowed_for_gesture_
= false;
284 case MotionEvent::ACTION_DOWN
:
285 if (double_tap_listener_
) {
286 bool had_tap_message
= timeout_handler_
->HasTimeout(TAP
);
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
);
299 // This is a first tap.
300 DCHECK(double_tap_timeout_
> base::TimeDelta());
301 timeout_handler_
->StartTimeout(TAP
);
305 down_focus_x_
= last_focus_x_
= focus_x
;
306 down_focus_y_
= last_focus_y_
= focus_y
;
307 current_down_event_
= ev
.Clone();
309 secondary_pointer_down_event_
.reset();
310 always_in_tap_region_
= true;
311 always_in_bigger_tap_region_
= true;
313 defer_confirm_single_tap_
= false;
314 two_finger_tap_allowed_for_gesture_
= two_finger_tap_enabled_
;
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
);
324 case MotionEvent::ACTION_MOVE
:
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();
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
) {
349 listener_
->OnScroll(*current_down_event_
, ev
, scroll_x
, scroll_y
);
350 last_focus_x_
= focus_x
;
351 last_focus_y_
= focus_y
;
354 if (!two_finger_tap_allowed_for_gesture_
)
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;
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;
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;
381 case MotionEvent::ACTION_UP
:
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
);
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
);
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
);
407 handled
|= HandleSwipeIfNeeded(ev
, velocity_x
, velocity_y
);
410 previous_up_event_
= ev
.Clone();
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
);
420 case MotionEvent::ACTION_CANCEL
:
428 void GestureDetector::SetDoubleTapListener(
429 DoubleTapListener
* double_tap_listener
) {
430 if (double_tap_listener
== double_tap_listener_
)
433 DCHECK(!is_double_tapping_
);
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
);
443 double_tap_listener_
= double_tap_listener
;
446 void GestureDetector::Init(const Config
& config
) {
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
;
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
);
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
;
476 void GestureDetector::OnShowPressTimeout() {
477 listener_
->OnShowPress(*current_down_event_
);
480 void GestureDetector::OnLongPressTimeout() {
481 timeout_handler_
->StopTimeout(TAP
);
482 defer_confirm_single_tap_
= false;
483 listener_
->OnLongPress(*current_down_event_
);
486 void GestureDetector::OnTapTimeout() {
487 if (!double_tap_listener_
)
490 double_tap_listener_
->OnSingleTapConfirmed(*current_down_event_
);
492 defer_confirm_single_tap_
= true;
495 void GestureDetector::Cancel() {
497 velocity_tracker_
.Clear();
501 void 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;
509 bool 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_
)
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_
)
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_
);
526 bool GestureDetector::HandleSwipeIfNeeded(const MotionEvent
& up
,
529 if (!swipe_enabled_
|| (!vx
&& !vy
))
531 float vx_abs
= std::abs(vx
);
532 float vy_abs
= std::abs(vy
);
534 if (vx_abs
< min_swipe_velocity_
)
536 if (vy_abs
< min_swipe_velocity_
)
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
);
543 if (ratio
< min_swipe_direction_component_ratio_
)
550 return listener_
->OnSwipe(*current_down_event_
, up
, vx
, vy
);