OLD | NEW |
(Empty) | |
| 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_provider.h" |
| 6 |
| 7 #include <cmath> |
| 8 |
| 9 #include "base/auto_reset.h" |
| 10 #include "base/debug/trace_event.h" |
| 11 #include "ui/events/gesture_detection/gesture_event_params.h" |
| 12 #include "ui/events/gesture_detection/motion_event.h" |
| 13 |
| 14 namespace ui { |
| 15 namespace { |
| 16 |
| 17 // Double-tap drag zoom sensitivity (speed). |
| 18 const float kDoubleTapDragZoomSpeed = 0.005f; |
| 19 |
| 20 const char* GetMotionEventActionName(MotionEvent::Action action) { |
| 21 switch(action) { |
| 22 case MotionEvent::ACTION_POINTER_DOWN: return "ACTION_POINTER_DOWN"; |
| 23 case MotionEvent::ACTION_POINTER_UP: return "ACTION_POINTER_UP"; |
| 24 case MotionEvent::ACTION_DOWN: return "ACTION_DOWN"; |
| 25 case MotionEvent::ACTION_UP: return "ACTION_UP"; |
| 26 case MotionEvent::ACTION_CANCEL: return "ACTION_CANCEL"; |
| 27 case MotionEvent::ACTION_MOVE: return "ACTION_MOVE"; |
| 28 } |
| 29 return ""; |
| 30 } |
| 31 |
| 32 GestureEventParams CreateGesture(GestureEventType type, |
| 33 base::TimeTicks time, |
| 34 float x, |
| 35 float y, |
| 36 const GestureEventParams::Data& extra_data) { |
| 37 return GestureEventParams(type, time, x, y, extra_data); |
| 38 } |
| 39 |
| 40 GestureEventParams CreateGesture(GestureEventType type, |
| 41 base::TimeTicks time, |
| 42 float x, |
| 43 float y) { |
| 44 return CreateGesture(type, time, x, y, GestureEventParams::Data()); |
| 45 } |
| 46 |
| 47 GestureEventParams CreateGesture(GestureEventType type, |
| 48 const MotionEvent& event, |
| 49 const GestureEventParams::Data& extra_data) { |
| 50 return CreateGesture( |
| 51 type, event.GetEventTime(), event.GetX(), event.GetY(), extra_data); |
| 52 } |
| 53 |
| 54 GestureEventParams CreateGesture(GestureEventType type, |
| 55 const MotionEvent& event) { |
| 56 return CreateGesture(type, event, GestureEventParams::Data()); |
| 57 } |
| 58 |
| 59 float Round(float f) { |
| 60 return (f > 0.f) ? std::floor(f + 0.5f) : std::ceil(f - 0.5f); |
| 61 } |
| 62 |
| 63 } // namespace |
| 64 |
| 65 // GestureProvider:::Config |
| 66 |
| 67 GestureProvider::Config::Config() : disable_click_delay(false) {} |
| 68 |
| 69 GestureProvider::Config::~Config() {} |
| 70 |
| 71 // GestureProvider::ScaleGestureListener |
| 72 |
| 73 class GestureProvider::ScaleGestureListenerImpl |
| 74 : public ScaleGestureDetector::ScaleGestureListener { |
| 75 public: |
| 76 ScaleGestureListenerImpl(const ScaleGestureDetector::Config& config, |
| 77 GestureProvider* provider) |
| 78 : scale_gesture_detector_(config, this), |
| 79 provider_(provider), |
| 80 ignore_detector_events_(false), |
| 81 pinch_event_sent_(false) {} |
| 82 |
| 83 bool OnTouchEvent(const MotionEvent& event) { |
| 84 // TODO: Need to deal with multi-touch transition. |
| 85 const bool in_scale_gesture = IsScaleGestureDetectionInProgress(); |
| 86 bool handled = scale_gesture_detector_.OnTouchEvent(event); |
| 87 if (!in_scale_gesture && |
| 88 (event.GetAction() == MotionEvent::ACTION_UP || |
| 89 event.GetAction() == MotionEvent::ACTION_CANCEL)) { |
| 90 return false; |
| 91 } |
| 92 return handled; |
| 93 } |
| 94 |
| 95 // ScaleGestureDetector::ScaleGestureListener implementation. |
| 96 virtual bool OnScaleBegin(const ScaleGestureDetector& detector) OVERRIDE { |
| 97 if (ignore_detector_events_) |
| 98 return false; |
| 99 pinch_event_sent_ = false; |
| 100 return true; |
| 101 } |
| 102 |
| 103 virtual void OnScaleEnd(const ScaleGestureDetector& detector) OVERRIDE { |
| 104 if (!pinch_event_sent_) |
| 105 return; |
| 106 provider_->Send( |
| 107 CreateGesture(GESTURE_PINCH_END, detector.GetEventTime(), 0, 0)); |
| 108 pinch_event_sent_ = false; |
| 109 } |
| 110 |
| 111 virtual bool OnScale(const ScaleGestureDetector& detector) OVERRIDE { |
| 112 if (ignore_detector_events_) |
| 113 return false; |
| 114 if (!pinch_event_sent_) { |
| 115 pinch_event_sent_ = true; |
| 116 provider_->Send(CreateGesture(GESTURE_PINCH_BEGIN, |
| 117 detector.GetEventTime(), |
| 118 detector.GetFocusX(), |
| 119 detector.GetFocusY())); |
| 120 } |
| 121 GestureEventParams::Data pinch_data; |
| 122 pinch_data.pinch_update.scale = detector.GetScaleFactor(); |
| 123 provider_->Send(CreateGesture(GESTURE_PINCH_UPDATE, |
| 124 detector.GetEventTime(), |
| 125 detector.GetFocusX(), |
| 126 detector.GetFocusY(), |
| 127 pinch_data)); |
| 128 return true; |
| 129 } |
| 130 |
| 131 bool IsScaleGestureDetectionInProgress() const { |
| 132 return !ignore_detector_events_ && scale_gesture_detector_.IsInProgress(); |
| 133 } |
| 134 |
| 135 void set_ignore_detector_events(bool value) { |
| 136 // Note that returning false from OnScaleBegin / OnScale makes the |
| 137 // gesture detector not to emit further scaling notifications |
| 138 // related to this gesture. Thus, if detector events are enabled in |
| 139 // the middle of the gesture, we don't need to do anything. |
| 140 ignore_detector_events_ = value; |
| 141 } |
| 142 |
| 143 private: |
| 144 ScaleGestureDetector scale_gesture_detector_; |
| 145 |
| 146 GestureProvider* const provider_; |
| 147 |
| 148 // Completely silence scaling events. Used in WebView when zoom support |
| 149 // is turned off. |
| 150 bool ignore_detector_events_; |
| 151 |
| 152 // Whether any pinch zoom event has been sent to native. |
| 153 bool pinch_event_sent_; |
| 154 |
| 155 DISALLOW_COPY_AND_ASSIGN(ScaleGestureListenerImpl); |
| 156 }; |
| 157 |
| 158 // GestureProvider::GestureListener |
| 159 |
| 160 class GestureProvider::GestureListenerImpl |
| 161 : public GestureDetector::GestureListener, |
| 162 public GestureDetector::DoubleTapListener { |
| 163 public: |
| 164 GestureListenerImpl( |
| 165 const GestureDetector::Config& gesture_detector_config, |
| 166 const SnapScrollController::Config& snap_scroll_controller_config, |
| 167 bool disable_click_delay, |
| 168 GestureProvider* provider) |
| 169 : gesture_detector_(gesture_detector_config, this, this), |
| 170 snap_scroll_controller_(snap_scroll_controller_config), |
| 171 provider_(provider), |
| 172 px_to_dp_(1.0f / snap_scroll_controller_config.device_scale_factor), |
| 173 disable_click_delay_(disable_click_delay), |
| 174 scaled_touch_slop_(gesture_detector_config.scaled_touch_slop), |
| 175 scaled_touch_slop_square_(scaled_touch_slop_ * scaled_touch_slop_), |
| 176 double_tap_timeout_(gesture_detector_config.double_tap_timeout), |
| 177 ignore_single_tap_(false), |
| 178 seen_first_scroll_event_(false), |
| 179 double_tap_mode_(DOUBLE_TAP_MODE_NONE), |
| 180 double_tap_y_(0), |
| 181 support_double_tap_(true), |
| 182 double_tap_drag_zoom_anchor_x_(0), |
| 183 double_tap_drag_zoom_anchor_y_(0), |
| 184 last_raw_x_(0), |
| 185 last_raw_y_(0), |
| 186 accumulated_scroll_error_x_(0), |
| 187 accumulated_scroll_error_y_(0) { |
| 188 UpdateDoubleTapListener(); |
| 189 } |
| 190 |
| 191 virtual ~GestureListenerImpl() {} |
| 192 |
| 193 bool OnTouchEvent(const MotionEvent& e, |
| 194 bool is_scale_gesture_detection_in_progress) { |
| 195 snap_scroll_controller_.SetSnapScrollingMode( |
| 196 e, is_scale_gesture_detection_in_progress); |
| 197 |
| 198 if (is_scale_gesture_detection_in_progress) |
| 199 SetIgnoreSingleTap(true); |
| 200 |
| 201 if (e.GetAction() == MotionEvent::ACTION_POINTER_DOWN || |
| 202 e.GetAction() == MotionEvent::ACTION_CANCEL) { |
| 203 EndDoubleTapDragIfNecessary(e); |
| 204 } else if (e.GetAction() == MotionEvent::ACTION_DOWN) { |
| 205 gesture_detector_.set_is_longpress_enabled(true); |
| 206 } |
| 207 |
| 208 return gesture_detector_.OnTouchEvent(e); |
| 209 } |
| 210 |
| 211 // GestureDetector::GestureListener implementation. |
| 212 virtual bool OnDown(const MotionEvent& e) OVERRIDE { |
| 213 current_down_time_ = e.GetEventTime(); |
| 214 ignore_single_tap_ = false; |
| 215 seen_first_scroll_event_ = false; |
| 216 last_raw_x_ = e.GetRawX(); |
| 217 last_raw_y_ = e.GetRawY(); |
| 218 accumulated_scroll_error_x_ = 0; |
| 219 accumulated_scroll_error_y_ = 0; |
| 220 |
| 221 GestureEventParams::Data tap_down_data; |
| 222 tap_down_data.tap.width = tap_down_data.tap.height = e.GetTouchMajor(); |
| 223 provider_->Send(CreateGesture(GESTURE_TAP_DOWN, e, tap_down_data)); |
| 224 |
| 225 // Return true to indicate that we want to handle touch. |
| 226 return true; |
| 227 } |
| 228 |
| 229 virtual bool OnScroll(const MotionEvent& e1, |
| 230 const MotionEvent& e2, |
| 231 float raw_distance_x, |
| 232 float raw_distance_y) OVERRIDE { |
| 233 float distance_x = raw_distance_x; |
| 234 float distance_y = raw_distance_y; |
| 235 if (!seen_first_scroll_event_) { |
| 236 // Remove the touch slop region from the first scroll event to avoid a |
| 237 // jump. |
| 238 seen_first_scroll_event_ = true; |
| 239 double distance = |
| 240 std::sqrt(distance_x * distance_x + distance_y * distance_y); |
| 241 double epsilon = 1e-3; |
| 242 if (distance > epsilon) { |
| 243 double ratio = std::max(0., distance - scaled_touch_slop_) / distance; |
| 244 distance_x *= ratio; |
| 245 distance_y *= ratio; |
| 246 } |
| 247 } |
| 248 snap_scroll_controller_.UpdateSnapScrollMode(distance_x, distance_y); |
| 249 if (snap_scroll_controller_.IsSnappingScrolls()) { |
| 250 if (snap_scroll_controller_.IsSnapHorizontal()) { |
| 251 distance_y = 0; |
| 252 } else { |
| 253 distance_x = 0; |
| 254 } |
| 255 } |
| 256 |
| 257 last_raw_x_ = e2.GetRawX(); |
| 258 last_raw_y_ = e2.GetRawY(); |
| 259 if (!provider_->IsScrollInProgress()) { |
| 260 // Note that scroll start hints are in distance traveled, where |
| 261 // scroll deltas are in the opposite direction. |
| 262 GestureEventParams::Data scroll_data; |
| 263 scroll_data.scroll_begin.delta_x_hint = -raw_distance_x; |
| 264 scroll_data.scroll_begin.delta_y_hint = -raw_distance_y; |
| 265 provider_->Send(CreateGesture(GESTURE_SCROLL_BEGIN, |
| 266 e2.GetEventTime(), |
| 267 e1.GetX(), |
| 268 e1.GetY(), |
| 269 scroll_data)); |
| 270 } |
| 271 |
| 272 // distance_x and distance_y is the scrolling offset since last OnScroll. |
| 273 // Because we are passing integers to Blink, this could introduce |
| 274 // rounding errors. The rounding errors will accumulate overtime. |
| 275 // To solve this, we should be adding back the rounding errors each time |
| 276 // when we calculate the new offset. |
| 277 // TODO(jdduke): Determine if we can simpy use floating point deltas, as |
| 278 // WebGestureEvent also takes floating point deltas for GestureScrollUpdate. |
| 279 int dx = (int)(distance_x + accumulated_scroll_error_x_); |
| 280 int dy = (int)(distance_y + accumulated_scroll_error_y_); |
| 281 accumulated_scroll_error_x_ += (distance_x - dx); |
| 282 accumulated_scroll_error_y_ += (distance_y - dy); |
| 283 |
| 284 if (dx || dy) { |
| 285 GestureEventParams::Data scroll_data; |
| 286 scroll_data.scroll_update.delta_x = -dx; |
| 287 scroll_data.scroll_update.delta_y = -dy; |
| 288 provider_->Send(CreateGesture(GESTURE_SCROLL_UPDATE, e2, scroll_data)); |
| 289 } |
| 290 |
| 291 return true; |
| 292 } |
| 293 |
| 294 virtual bool OnFling(const MotionEvent& e1, |
| 295 const MotionEvent& e2, |
| 296 float velocity_x, |
| 297 float velocity_y) OVERRIDE { |
| 298 if (snap_scroll_controller_.IsSnappingScrolls()) { |
| 299 if (snap_scroll_controller_.IsSnapHorizontal()) { |
| 300 velocity_y = 0; |
| 301 } else { |
| 302 velocity_x = 0; |
| 303 } |
| 304 } |
| 305 |
| 306 provider_->Fling( |
| 307 e2.GetEventTime(), e1.GetX(), e1.GetY(), velocity_x, velocity_y); |
| 308 return true; |
| 309 } |
| 310 |
| 311 virtual void OnShowPress(const MotionEvent& e) OVERRIDE { |
| 312 GestureEventParams::Data show_press_data; |
| 313 // TODO(jdduke): Expose minor axis length and rotation in |MotionEvent|. |
| 314 show_press_data.show_press.width = e.GetTouchMajor(); |
| 315 show_press_data.show_press.height = show_press_data.show_press.width; |
| 316 provider_->Send(CreateGesture(GESTURE_SHOW_PRESS, e, show_press_data)); |
| 317 } |
| 318 |
| 319 virtual bool OnSingleTapUp(const MotionEvent& e) OVERRIDE { |
| 320 if (IsPointOutsideCurrentSlopRegion(e.GetRawX(), e.GetRawY())) { |
| 321 provider_->SendTapCancelIfNecessary(e); |
| 322 ignore_single_tap_ = true; |
| 323 return true; |
| 324 } |
| 325 // This is a hack to address the issue where user hovers |
| 326 // over a link for longer than double_tap_timeout_, then |
| 327 // OnSingleTapConfirmed() is not triggered. But we still |
| 328 // want to trigger the tap event at UP. So we override |
| 329 // OnSingleTapUp() in this case. This assumes singleTapUp |
| 330 // gets always called before singleTapConfirmed. |
| 331 if (!ignore_single_tap_) { |
| 332 if (e.GetEventTime() - current_down_time_ > double_tap_timeout_) { |
| 333 return OnSingleTapConfirmed(e); |
| 334 } else if (IsDoubleTapDisabled() || disable_click_delay_) { |
| 335 // If double-tap has been disabled, there is no need to wait |
| 336 // for the double-tap timeout. |
| 337 return OnSingleTapConfirmed(e); |
| 338 } else { |
| 339 // Notify Blink about this tapUp event anyway, |
| 340 // when none of the above conditions applied. |
| 341 provider_->Send(CreateGesture(GESTURE_SINGLE_TAP_UNCONFIRMED, e)); |
| 342 } |
| 343 } |
| 344 |
| 345 return provider_->SendLongTapIfNecessary(e); |
| 346 } |
| 347 |
| 348 // GestureDetector::DoubleTapListener implementation. |
| 349 virtual bool OnSingleTapConfirmed(const MotionEvent& e) OVERRIDE { |
| 350 // Long taps in the edges of the screen have their events delayed by |
| 351 // ContentViewHolder for tab swipe operations. As a consequence of the delay |
| 352 // this method might be called after receiving the up event. |
| 353 // These corner cases should be ignored. |
| 354 if (ignore_single_tap_) |
| 355 return true; |
| 356 |
| 357 ignore_single_tap_ = true; |
| 358 |
| 359 GestureEventParams::Data tap_data; |
| 360 tap_data.tap.tap_count = 1; |
| 361 tap_data.tap.width = tap_data.tap.height = e.GetTouchMajor(); |
| 362 provider_->Send(CreateGesture(GESTURE_SINGLE_TAP_CONFIRMED, e, tap_data)); |
| 363 return true; |
| 364 } |
| 365 |
| 366 virtual bool OnDoubleTap(const MotionEvent& e) OVERRIDE { return false; } |
| 367 |
| 368 virtual bool OnDoubleTapEvent(const MotionEvent& e) OVERRIDE { |
| 369 switch (e.GetAction()) { |
| 370 case MotionEvent::ACTION_DOWN: |
| 371 // Note that this will be called before the corresponding |onDown()| |
| 372 // of the same ACTION_DOWN event. Thus, the preceding TAP_DOWN |
| 373 // should be cancelled prior to sending a new one (in |onDown()|). |
| 374 double_tap_drag_zoom_anchor_x_ = e.GetX(); |
| 375 double_tap_drag_zoom_anchor_y_ = e.GetY(); |
| 376 double_tap_mode_ = DOUBLE_TAP_MODE_DRAG_DETECTION_IN_PROGRESS; |
| 377 // If a long-press fires during a double-tap, the GestureDetector |
| 378 // will stop feeding MotionEvents to |onDoubleTapEvent()|, |
| 379 // preventing double-tap drag zoom. Long press detection will be |
| 380 // re-enabled on the next ACTION_DOWN. |
| 381 gesture_detector_.set_is_longpress_enabled(false); |
| 382 break; |
| 383 case MotionEvent::ACTION_MOVE: |
| 384 if (double_tap_mode_ == DOUBLE_TAP_MODE_DRAG_DETECTION_IN_PROGRESS) { |
| 385 float distance_x = double_tap_drag_zoom_anchor_x_ - e.GetX(); |
| 386 float distance_y = double_tap_drag_zoom_anchor_y_ - e.GetY(); |
| 387 |
| 388 // Begin double-tap drag zoom mode if the move distance is |
| 389 // further than the threshold. |
| 390 if (IsDistanceGreaterThanTouchSlop(distance_x, distance_y)) { |
| 391 GestureEventParams::Data scroll_data; |
| 392 scroll_data.scroll_begin.delta_x_hint = -distance_x; |
| 393 scroll_data.scroll_begin.delta_y_hint = -distance_y; |
| 394 provider_->Send( |
| 395 CreateGesture(GESTURE_SCROLL_BEGIN, e, scroll_data)); |
| 396 provider_->Send( |
| 397 CreateGesture(GESTURE_PINCH_BEGIN, |
| 398 e.GetEventTime(), |
| 399 Round(double_tap_drag_zoom_anchor_x_), |
| 400 Round(double_tap_drag_zoom_anchor_y_))); |
| 401 double_tap_mode_ = DOUBLE_TAP_MODE_DRAG_ZOOM; |
| 402 } |
| 403 } else if (double_tap_mode_ == DOUBLE_TAP_MODE_DRAG_ZOOM) { |
| 404 provider_->Send(CreateGesture(GESTURE_SCROLL_UPDATE, e)); |
| 405 |
| 406 float dy = double_tap_y_ - e.GetY(); |
| 407 GestureEventParams::Data pinch_data; |
| 408 pinch_data.pinch_update.scale = |
| 409 std::pow(dy > 0 ? 1.0f - kDoubleTapDragZoomSpeed |
| 410 : 1.0f + kDoubleTapDragZoomSpeed, |
| 411 std::abs(dy * px_to_dp_)); |
| 412 provider_->Send(CreateGesture(GESTURE_PINCH_UPDATE, |
| 413 e.GetEventTime(), |
| 414 Round(double_tap_drag_zoom_anchor_x_), |
| 415 Round(double_tap_drag_zoom_anchor_y_), |
| 416 pinch_data)); |
| 417 } |
| 418 break; |
| 419 case MotionEvent::ACTION_UP: |
| 420 if (double_tap_mode_ != DOUBLE_TAP_MODE_DRAG_ZOOM) { |
| 421 // Normal double-tap gesture. |
| 422 provider_->Send(CreateGesture(GESTURE_DOUBLE_TAP, e)); |
| 423 } |
| 424 EndDoubleTapDragIfNecessary(e); |
| 425 break; |
| 426 case MotionEvent::ACTION_CANCEL: |
| 427 EndDoubleTapDragIfNecessary(e); |
| 428 break; |
| 429 default: |
| 430 NOTREACHED() << "Invalid double-tap event."; |
| 431 break; |
| 432 } |
| 433 double_tap_y_ = e.GetY(); |
| 434 return true; |
| 435 } |
| 436 |
| 437 virtual bool OnLongPress(const MotionEvent& e) OVERRIDE { |
| 438 DCHECK(!IsDoubleTapInProgress()); |
| 439 SetIgnoreSingleTap(true); |
| 440 |
| 441 GestureEventParams::Data long_press_data; |
| 442 long_press_data.long_press.width = e.GetTouchMajor(); |
| 443 long_press_data.long_press.height = long_press_data.long_press.width; |
| 444 provider_->Send(CreateGesture(GESTURE_LONG_PRESS, e, long_press_data)); |
| 445 |
| 446 // Returning true puts the GestureDetector in "longpress" mode, disabling |
| 447 // further scrolling. This is undesirable, as it is quite common for a |
| 448 // longpress gesture to fire on content that won't trigger a context menu. |
| 449 return false; |
| 450 } |
| 451 |
| 452 void UpdateDoubleTapSupportForPlatform(bool support_double_tap) { |
| 453 DCHECK(!IsDoubleTapInProgress()); |
| 454 DoubleTapMode double_tap_mode = |
| 455 support_double_tap ? DOUBLE_TAP_MODE_NONE : DOUBLE_TAP_MODE_DISABLED; |
| 456 if (double_tap_mode_ == double_tap_mode) |
| 457 return; |
| 458 double_tap_mode_ = double_tap_mode; |
| 459 UpdateDoubleTapListener(); |
| 460 } |
| 461 |
| 462 void UpdateDoubleTapSupportForPage(bool support_double_tap) { |
| 463 if (support_double_tap_ == support_double_tap) |
| 464 return; |
| 465 support_double_tap_ = support_double_tap; |
| 466 UpdateDoubleTapListener(); |
| 467 } |
| 468 |
| 469 bool IsDoubleTapDisabled() const { |
| 470 return double_tap_mode_ == DOUBLE_TAP_MODE_DISABLED || !support_double_tap_; |
| 471 } |
| 472 |
| 473 bool IsClickDelayDisabled() const { return disable_click_delay_; } |
| 474 |
| 475 bool IsDoubleTapInProgress() const { |
| 476 return double_tap_mode_ != DOUBLE_TAP_MODE_DISABLED && |
| 477 double_tap_mode_ != DOUBLE_TAP_MODE_NONE; |
| 478 } |
| 479 |
| 480 private: |
| 481 enum DoubleTapMode { |
| 482 DOUBLE_TAP_MODE_NONE, |
| 483 DOUBLE_TAP_MODE_DRAG_DETECTION_IN_PROGRESS, |
| 484 DOUBLE_TAP_MODE_DRAG_ZOOM, |
| 485 DOUBLE_TAP_MODE_DISABLED |
| 486 }; |
| 487 |
| 488 bool IsPointOutsideCurrentSlopRegion(float x, float y) const { |
| 489 return IsDistanceGreaterThanTouchSlop(last_raw_x_ - x, last_raw_y_ - y); |
| 490 } |
| 491 |
| 492 bool IsDistanceGreaterThanTouchSlop(float distance_x, |
| 493 float distance_y) const { |
| 494 return distance_x * distance_x + distance_y * distance_y > |
| 495 scaled_touch_slop_square_; |
| 496 } |
| 497 |
| 498 void SetIgnoreSingleTap(bool value) { ignore_single_tap_ = value; } |
| 499 |
| 500 void EndDoubleTapDragIfNecessary(const MotionEvent& event) { |
| 501 if (!IsDoubleTapInProgress()) |
| 502 return; |
| 503 if (double_tap_mode_ == DOUBLE_TAP_MODE_DRAG_ZOOM) { |
| 504 provider_->Send(CreateGesture(GESTURE_PINCH_END, event)); |
| 505 provider_->Send(CreateGesture(GESTURE_SCROLL_END, event)); |
| 506 } |
| 507 double_tap_mode_ = DOUBLE_TAP_MODE_NONE; |
| 508 UpdateDoubleTapListener(); |
| 509 } |
| 510 |
| 511 void UpdateDoubleTapListener() { |
| 512 if (IsDoubleTapDisabled()) { |
| 513 // Defer nulling the DoubleTapListener until the double-tap gesture is |
| 514 // complete. |
| 515 if (IsDoubleTapInProgress()) |
| 516 return; |
| 517 gesture_detector_.set_doubletap_listener(NULL); |
| 518 } else { |
| 519 gesture_detector_.set_doubletap_listener(this); |
| 520 } |
| 521 } |
| 522 |
| 523 GestureDetector gesture_detector_; |
| 524 SnapScrollController snap_scroll_controller_; |
| 525 |
| 526 GestureProvider* const provider_; |
| 527 |
| 528 const float px_to_dp_; |
| 529 |
| 530 // Whether the click delay should always be disabled by sending clicks for |
| 531 // double-tap gestures. |
| 532 const bool disable_click_delay_; |
| 533 |
| 534 const int scaled_touch_slop_; |
| 535 |
| 536 // Cache of square of the scaled touch slop so we don't have to calculate it |
| 537 // on every touch. |
| 538 const int scaled_touch_slop_square_; |
| 539 |
| 540 const base::TimeDelta double_tap_timeout_; |
| 541 |
| 542 base::TimeTicks current_down_time_; |
| 543 |
| 544 // TODO(klobag): This is to avoid a bug in GestureDetector. With multi-touch, |
| 545 // always_in_tap_region_ is not reset. So when the last finger is up, |
| 546 // OnSingleTapUp() will be mistakenly fired. |
| 547 bool ignore_single_tap_; |
| 548 |
| 549 // Used to remove the touch slop from the initial scroll event in a scroll |
| 550 // gesture. |
| 551 bool seen_first_scroll_event_; |
| 552 |
| 553 // Indicate current double-tap mode state. |
| 554 int double_tap_mode_; |
| 555 |
| 556 // On double-tap this will store the y coordinates of the touch. |
| 557 float double_tap_y_; |
| 558 |
| 559 // The page's viewport and scale sometimes allow us to disable double-tap |
| 560 // gesture detection, |
| 561 // according to the logic in ContentViewCore.onRenderCoordinatesUpdated(). |
| 562 bool support_double_tap_; |
| 563 |
| 564 // x, y coordinates for an Anchor on double-tap drag zoom. |
| 565 float double_tap_drag_zoom_anchor_x_; |
| 566 float double_tap_drag_zoom_anchor_y_; |
| 567 |
| 568 // Used to track the last rawX/Y coordinates for moves. This gives absolute |
| 569 // scroll distance. |
| 570 // Useful for full screen tracking. |
| 571 float last_raw_x_; |
| 572 float last_raw_y_; |
| 573 |
| 574 // Used to track the accumulated scroll error over time. This is used to |
| 575 // remove the |
| 576 // rounding error we introduced by passing integers to webkit. |
| 577 float accumulated_scroll_error_x_; |
| 578 float accumulated_scroll_error_y_; |
| 579 |
| 580 DISALLOW_COPY_AND_ASSIGN(GestureListenerImpl); |
| 581 }; |
| 582 |
| 583 // GestureProvider |
| 584 |
| 585 GestureProvider::GestureProvider(const Config& config, |
| 586 GestureProviderClient* client) |
| 587 : client_(client), |
| 588 needs_show_press_event_(false), |
| 589 needs_tap_ending_event_(false), |
| 590 touch_scroll_in_progress_(false), |
| 591 pinch_in_progress_(false) { |
| 592 DCHECK(client); |
| 593 InitGestureDetectors(config); |
| 594 } |
| 595 |
| 596 GestureProvider::~GestureProvider() {} |
| 597 |
| 598 bool GestureProvider::OnTouchEvent(const MotionEvent& event) { |
| 599 TRACE_EVENT1("input", "GestureProvider::OnTouchEvent", |
| 600 "action", GetMotionEventActionName(event.GetAction())); |
| 601 if (!CanHandle(event)) |
| 602 return false; |
| 603 |
| 604 const bool was_touch_scrolling_ = touch_scroll_in_progress_; |
| 605 const bool in_scale_gesture = |
| 606 scale_gesture_listener_->IsScaleGestureDetectionInProgress(); |
| 607 |
| 608 if (event.GetAction() == MotionEvent::ACTION_DOWN) { |
| 609 current_down_event_ = event.Clone(); |
| 610 touch_scroll_in_progress_ = false; |
| 611 needs_show_press_event_ = true; |
| 612 current_longpress_time_ = base::TimeTicks(); |
| 613 SendTapCancelIfNecessary(event); |
| 614 } |
| 615 |
| 616 bool handled = gesture_listener_->OnTouchEvent(event, in_scale_gesture); |
| 617 handled |= scale_gesture_listener_->OnTouchEvent(event); |
| 618 |
| 619 if (event.GetAction() == MotionEvent::ACTION_UP || |
| 620 event.GetAction() == MotionEvent::ACTION_CANCEL) { |
| 621 // "Last finger raised" could be an end to movement, but it should |
| 622 // only terminate scrolling if the event did not cause a fling. |
| 623 if (was_touch_scrolling_ && !handled) |
| 624 EndTouchScrollIfNecessary(event.GetEventTime(), true); |
| 625 |
| 626 // We shouldn't necessarily cancel a tap on ACTION_UP, as the double-tap |
| 627 // timeout may yet trigger a SINGLE_TAP_CONFIRMED. |
| 628 if (event.GetAction() == MotionEvent::ACTION_CANCEL) |
| 629 SendTapCancelIfNecessary(event); |
| 630 |
| 631 current_down_event_.reset(); |
| 632 } |
| 633 |
| 634 return true; |
| 635 } |
| 636 |
| 637 void GestureProvider::ResetGestureDetectors() { |
| 638 if (!current_down_event_) |
| 639 return; |
| 640 scoped_ptr<MotionEvent> cancel_event = current_down_event_->Cancel(); |
| 641 gesture_listener_->OnTouchEvent(*cancel_event, false); |
| 642 scale_gesture_listener_->OnTouchEvent(*cancel_event); |
| 643 } |
| 644 |
| 645 void GestureProvider::CancelActiveTouchSequence() { |
| 646 if (!current_down_event_) |
| 647 return; |
| 648 OnTouchEvent(*current_down_event_->Cancel()); |
| 649 current_down_event_.reset(); |
| 650 } |
| 651 |
| 652 void GestureProvider::UpdateMultiTouchSupport(bool support_multi_touch_zoom) { |
| 653 scale_gesture_listener_->set_ignore_detector_events( |
| 654 !support_multi_touch_zoom); |
| 655 } |
| 656 |
| 657 void GestureProvider::UpdateDoubleTapSupportForPlatform( |
| 658 bool support_double_tap) { |
| 659 gesture_listener_->UpdateDoubleTapSupportForPlatform(support_double_tap); |
| 660 } |
| 661 |
| 662 void GestureProvider::UpdateDoubleTapSupportForPage(bool support_double_tap) { |
| 663 gesture_listener_->UpdateDoubleTapSupportForPage(support_double_tap); |
| 664 } |
| 665 |
| 666 bool GestureProvider::IsScrollInProgress() const { |
| 667 // TODO(wangxianzhu): Also return true when fling is active once the UI knows |
| 668 // exactly when the fling ends. |
| 669 return touch_scroll_in_progress_; |
| 670 } |
| 671 |
| 672 bool GestureProvider::IsPinchInProgress() const { return pinch_in_progress_; } |
| 673 |
| 674 bool GestureProvider::IsDoubleTapInProgress() const { |
| 675 return gesture_listener_->IsDoubleTapInProgress(); |
| 676 } |
| 677 |
| 678 void GestureProvider::InitGestureDetectors(const Config& config) { |
| 679 TRACE_EVENT0("input", "GestureProvider::InitGestureDetectors"); |
| 680 gesture_listener_.reset( |
| 681 new GestureListenerImpl(config.gesture_detector_config, |
| 682 config.snap_scroll_controller_config, |
| 683 config.disable_click_delay, |
| 684 this)); |
| 685 |
| 686 scale_gesture_listener_.reset( |
| 687 new ScaleGestureListenerImpl(config.scale_gesture_detector_config, this)); |
| 688 } |
| 689 |
| 690 bool GestureProvider::CanHandle(const MotionEvent& event) const { |
| 691 return event.GetAction() == MotionEvent::ACTION_DOWN || current_down_event_; |
| 692 } |
| 693 |
| 694 void GestureProvider::Fling(base::TimeTicks time, |
| 695 float x, |
| 696 float y, |
| 697 float velocity_x, |
| 698 float velocity_y) { |
| 699 if (!velocity_x && !velocity_y) { |
| 700 EndTouchScrollIfNecessary(time, true); |
| 701 return; |
| 702 } |
| 703 |
| 704 if (!touch_scroll_in_progress_) { |
| 705 // The native side needs a GESTURE_SCROLL_BEGIN before GESTURE_FLING_START |
| 706 // to send the fling to the correct target. Send if it has not sent. |
| 707 // The distance traveled in one second is a reasonable scroll start hint. |
| 708 GestureEventParams::Data scroll_data; |
| 709 scroll_data.scroll_begin.delta_x_hint = velocity_x; |
| 710 scroll_data.scroll_begin.delta_y_hint = velocity_y; |
| 711 Send(CreateGesture(GESTURE_SCROLL_BEGIN, time, x, y, scroll_data)); |
| 712 } |
| 713 EndTouchScrollIfNecessary(time, false); |
| 714 |
| 715 GestureEventParams::Data fling_data; |
| 716 fling_data.fling_start.velocity_x = velocity_x; |
| 717 fling_data.fling_start.velocity_y = velocity_y; |
| 718 Send(CreateGesture(GESTURE_FLING_START, time, x, y, fling_data)); |
| 719 } |
| 720 |
| 721 void GestureProvider::Send(const GestureEventParams& gesture) { |
| 722 DCHECK(!gesture.time.is_null()); |
| 723 // The only valid events that should be sent without an active touch sequence |
| 724 // are SHOW_PRESS and TAP_CONFIRMED, potentially triggered by the double-tap |
| 725 // delay timing out. |
| 726 DCHECK(current_down_event_ || gesture.type == GESTURE_SINGLE_TAP_CONFIRMED || |
| 727 gesture.type == GESTURE_SHOW_PRESS); |
| 728 |
| 729 switch (gesture.type) { |
| 730 case GESTURE_TAP_DOWN: |
| 731 needs_tap_ending_event_ = true; |
| 732 break; |
| 733 case GESTURE_SINGLE_TAP_UNCONFIRMED: |
| 734 needs_show_press_event_ = false; |
| 735 break; |
| 736 case GESTURE_SINGLE_TAP_CONFIRMED: |
| 737 if (needs_show_press_event_) |
| 738 Send(CreateGesture( |
| 739 GESTURE_SHOW_PRESS, gesture.time, gesture.x, gesture.y)); |
| 740 needs_tap_ending_event_ = false; |
| 741 break; |
| 742 case GESTURE_DOUBLE_TAP: |
| 743 needs_tap_ending_event_ = false; |
| 744 break; |
| 745 case GESTURE_TAP_CANCEL: |
| 746 if (!needs_tap_ending_event_) |
| 747 return; |
| 748 needs_tap_ending_event_ = false; |
| 749 break; |
| 750 case GESTURE_SHOW_PRESS: |
| 751 needs_show_press_event_ = false; |
| 752 break; |
| 753 case GESTURE_LONG_TAP: |
| 754 needs_tap_ending_event_ = false; |
| 755 current_longpress_time_ = base::TimeTicks(); |
| 756 break; |
| 757 case GESTURE_LONG_PRESS: |
| 758 DCHECK(!scale_gesture_listener_->IsScaleGestureDetectionInProgress()); |
| 759 current_longpress_time_ = gesture.time; |
| 760 break; |
| 761 case GESTURE_PINCH_UPDATE: |
| 762 pinch_in_progress_ = true; |
| 763 break; |
| 764 case GESTURE_PINCH_END: |
| 765 pinch_in_progress_ = false; |
| 766 break; |
| 767 case GESTURE_SCROLL_BEGIN: |
| 768 touch_scroll_in_progress_ = true; |
| 769 SendTapCancelIfNecessary(*current_down_event_); |
| 770 break; |
| 771 case GESTURE_SCROLL_END: |
| 772 touch_scroll_in_progress_ = false; |
| 773 break; |
| 774 default: |
| 775 break; |
| 776 }; |
| 777 |
| 778 client_->OnGestureEvent(gesture); |
| 779 } |
| 780 |
| 781 void GestureProvider::SendTapCancelIfNecessary(const MotionEvent& event) { |
| 782 if (!needs_tap_ending_event_) |
| 783 return; |
| 784 current_longpress_time_ = base::TimeTicks(); |
| 785 Send(CreateGesture(GESTURE_TAP_CANCEL, event)); |
| 786 } |
| 787 |
| 788 bool GestureProvider::SendLongTapIfNecessary(const MotionEvent& event) { |
| 789 if (event.GetAction() == MotionEvent::ACTION_UP && |
| 790 !current_longpress_time_.is_null() && |
| 791 !scale_gesture_listener_->IsScaleGestureDetectionInProgress()) { |
| 792 SendTapCancelIfNecessary(event); |
| 793 GestureEventParams::Data long_tap_data; |
| 794 long_tap_data.long_press.width = event.GetTouchMajor(); |
| 795 long_tap_data.long_press.height = long_tap_data.long_press.width; |
| 796 Send(CreateGesture(GESTURE_LONG_TAP, event, long_tap_data)); |
| 797 return true; |
| 798 } |
| 799 return false; |
| 800 } |
| 801 |
| 802 void GestureProvider::EndTouchScrollIfNecessary(base::TimeTicks time, |
| 803 bool send_scroll_end_event) { |
| 804 if (!touch_scroll_in_progress_) |
| 805 return; |
| 806 touch_scroll_in_progress_ = false; |
| 807 if (send_scroll_end_event) |
| 808 Send(CreateGesture(GESTURE_SCROLL_END, time, 0, 0)); |
| 809 } |
| 810 |
| 811 } // namespace ui |
OLD | NEW |