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