Index: content/browser/renderer_host/input/gestures/gesture_detector.cc |
diff --git a/content/browser/renderer_host/input/gestures/gesture_detector.cc b/content/browser/renderer_host/input/gestures/gesture_detector.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6b43b5e17868d585b2afd45a677aa817a1e9e9fc |
--- /dev/null |
+++ b/content/browser/renderer_host/input/gestures/gesture_detector.cc |
@@ -0,0 +1,377 @@ |
+// Copyright 2013 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "content/browser/renderer_host/input/gestures/gesture_detector.h" |
+ |
+#include "content/browser/renderer_host/input/gestures/motion_event.h" |
+#include "content/browser/renderer_host/input/timeout_monitor.h" |
+ |
+namespace content { |
+ |
+GestureDetector::Config::Config() |
+ : scaled_touch_slop(0), |
+ scaled_double_tap_slop(0), |
+ scaled_minimum_fling_velocity(1), |
+ scaled_maximum_fling_velocity(10000) {} |
+ |
+GestureDetector::Config::~Config() {} |
+ |
+bool GestureDetector::SimpleOnGestureListener::OnDown(const MotionEvent& e) { |
+ return false; |
+} |
+ |
+void GestureDetector::SimpleOnGestureListener::OnShowPress( |
+ const MotionEvent& e) {} |
+ |
+bool GestureDetector::SimpleOnGestureListener::OnSingleTapUp( |
+ const MotionEvent& e) { |
+ return false; |
+} |
+ |
+void GestureDetector::SimpleOnGestureListener::OnLongPress( |
+ const MotionEvent& e) {} |
+ |
+bool GestureDetector::SimpleOnGestureListener::OnScroll(const MotionEvent& e1, |
+ const MotionEvent& e2, |
+ float distance_x, |
+ float distance_y) { |
+ return false; |
+} |
+ |
+bool GestureDetector::SimpleOnGestureListener::OnFling(const MotionEvent& e1, |
+ const MotionEvent& e2, |
+ float velocity_x, |
+ float velocity_y) { |
+ return false; |
+} |
+ |
+bool GestureDetector::SimpleOnGestureListener::OnSingleTapConfirmed( |
+ const MotionEvent& e) { |
+ return false; |
+} |
+ |
+bool GestureDetector::SimpleOnGestureListener::OnDoubleTap( |
+ const MotionEvent& e) { |
+ return false; |
+} |
+ |
+bool GestureDetector::SimpleOnGestureListener::OnDoubleTapEvent( |
+ const MotionEvent& e) { |
+ return false; |
+} |
+ |
+class GestureDetector::GestureHandler { |
+ public: |
+ GestureHandler(Config config, GestureDetector* gesture_detector) { |
+ timeout_timers_[SHOW_PRESS].reset(new TimeoutMonitor( |
+ base::Bind(&GestureDetector::OnShowPressTimeout, |
+ base::Unretained(gesture_detector)))); |
+ timeout_times_[SHOW_PRESS] = config.tap_timeout; |
+ |
+ timeout_timers_[LONG_PRESS].reset(new TimeoutMonitor( |
+ base::Bind(&GestureDetector::OnLongPressTimeout, |
+ base::Unretained(gesture_detector)))); |
+ timeout_times_[LONG_PRESS] = |
+ config.longpress_timeout + config.tap_timeout; |
+ |
+ timeout_timers_[TAP].reset(new TimeoutMonitor( |
+ base::Bind(&GestureDetector::OnTapTimeout, |
+ base::Unretained(gesture_detector)))); |
+ timeout_times_[TAP] = config.double_tap_timeout; |
+ } |
+ |
+ void StartTimeout(TimeoutEvent event) { |
+ timeout_timers_[event]->Start(timeout_times_[event]); |
+ } |
+ |
+ void StopTimeout(TimeoutEvent event) { |
+ timeout_timers_[event]->Stop(); |
+ } |
+ |
+ void Stop() { |
+ for (size_t i = SHOW_PRESS; i < TIMEOUT_EVENT_COUNT; ++i) |
+ timeout_timers_[i]->Stop(); |
+ } |
+ |
+ bool HasTimeout(TimeoutEvent event) const { |
+ return timeout_timers_[event]->IsRunning(); |
+ } |
+ |
+ private: |
+ scoped_ptr<TimeoutMonitor> timeout_timers_[TIMEOUT_EVENT_COUNT]; |
+ base::TimeDelta timeout_times_[TIMEOUT_EVENT_COUNT]; |
+}; |
+ |
+ |
+GestureDetector::GestureDetector( |
+ Config config, |
+ OnGestureListener* listener, |
+ OnDoubleTapListener* optional_double_tap_listener) |
+ : handler_(new GestureHandler(config, this)), |
+ listener_(listener), |
+ double_tap_listener_(optional_double_tap_listener), |
+ is_longpress_enabled_(true) { |
+ DCHECK(listener_); |
+ Init(config); |
+} |
+ |
+GestureDetector::~GestureDetector() {} |
+ |
+bool GestureDetector::OnTouchEvent(const MotionEvent& ev) { |
+ const MotionEvent::Action action = ev.GetActionMasked(); |
+ |
+ velocity_tracker_.AddMovement(ev); |
+ |
+ const bool pointerUp = action == MotionEvent::ACTION_POINTER_UP; |
+ const int skipIndex = pointerUp ? ev.GetActionIndex() : -1; |
+ |
+ // Determine focal point |
+ float sum_x = 0, sum_y = 0; |
+ const int count = ev.GetPointerCount(); |
+ for (int i = 0; i < count; i++) { |
+ if (skipIndex == i) |
+ continue; |
+ sum_x += ev.GetX(i); |
+ sum_y += ev.GetY(i); |
+ } |
+ const int div = pointerUp ? count - 1 : count; |
+ const float focus_x = sum_x / div; |
+ const float focus_y = sum_y / div; |
+ |
+ bool handled = false; |
+ |
+ switch (action) { |
+ case MotionEvent::ACTION_POINTER_DOWN: |
+ down_focus_x_ = last_focus_x_ = focus_x; |
+ down_focus_y_ = last_focus_y_ = focus_y; |
+ // Cancel long press and taps |
+ CancelTaps(); |
+ break; |
+ |
+ case MotionEvent::ACTION_POINTER_UP: |
+ down_focus_x_ = last_focus_x_ = focus_x; |
+ down_focus_y_ = last_focus_y_ = focus_y; |
+ |
+ // Check the dot product of current velocities. |
+ // If the pointer that left was opposing another velocity vector, clear. |
+ velocity_tracker_.ComputeCurrentVelocity(1000, max_fling_velocity_); |
+ { |
+ const int upIndex = ev.GetActionIndex(); |
+ const int id1 = ev.GetPointerId(upIndex); |
+ const float x1 = velocity_tracker_.GetXVelocity(id1); |
+ const float y1 = velocity_tracker_.GetYVelocity(id1); |
+ for (int i = 0; i < count; i++) { |
+ if (i == upIndex) |
+ continue; |
+ |
+ const int id2 = ev.GetPointerId(i); |
+ const float x = x1 * velocity_tracker_.GetXVelocity(id2); |
+ const float y = y1 * velocity_tracker_.GetYVelocity(id2); |
+ |
+ const float dot = x + y; |
+ if (dot < 0) { |
+ velocity_tracker_.Clear(); |
+ break; |
+ } |
+ } |
+ } |
+ break; |
+ |
+ case MotionEvent::ACTION_DOWN: |
+ if (double_tap_listener_) { |
+ bool hadTapMessage = handler_->HasTimeout(TAP); |
+ if (hadTapMessage) handler_->StopTimeout(TAP); |
+ if (current_down_event_ && previous_up_event_ && hadTapMessage && |
+ IsConsideredDoubleTap( |
+ *current_down_event_, *previous_up_event_, ev)) { |
+ // This is a second tap |
+ is_double_tapping_ = true; |
+ // Give a callback with the first tap of the double-tap |
+ handled |= double_tap_listener_->OnDoubleTap(*current_down_event_); |
+ // Give a callback with down event of the double-tap |
+ handled |= double_tap_listener_->OnDoubleTapEvent(ev); |
+ } else { |
+ // This is a first tap |
+ handler_->StartTimeout(TAP); |
+ } |
+ } |
+ |
+ down_focus_x_ = last_focus_x_ = focus_x; |
+ down_focus_y_ = last_focus_y_ = focus_y; |
+ if (!current_down_event_) |
+ current_down_event_.reset(new MotionEvent(ev.event)); |
+ else |
+ current_down_event_->event = ev.event; |
+ |
+ always_in_tap_region_ = true; |
+ always_in_bigger_tap_region_ = true; |
+ still_down_ = true; |
+ in_long_press_ = false; |
+ defer_confirm_single_tap_ = false; |
+ |
+ if (is_longpress_enabled_) |
+ handler_->StartTimeout(LONG_PRESS); |
+ handler_->StartTimeout(SHOW_PRESS); |
+ handled |= listener_->OnDown(ev); |
+ break; |
+ |
+ case MotionEvent::ACTION_MOVE: |
+ if (in_long_press_) |
+ break; |
+ |
+ { |
+ const float scrollX = last_focus_x_ - focus_x; |
+ const float scrollY = last_focus_y_ - focus_y; |
+ if (is_double_tapping_) { |
+ // Give the move events of the double-tap |
+ handled |= double_tap_listener_->OnDoubleTapEvent(ev); |
+ } else if (always_in_tap_region_) { |
+ const int deltaX = (int)(focus_x - down_focus_x_); |
+ const int deltaY = (int)(focus_y - down_focus_y_); |
+ int distance = (deltaX * deltaX) + (deltaY * deltaY); |
+ if (distance > touch_slop_square_) { |
+ handled = |
+ listener_->OnScroll(*current_down_event_, ev, scrollX, scrollY); |
+ last_focus_x_ = focus_x; |
+ last_focus_y_ = focus_y; |
+ always_in_tap_region_ = false; |
+ handler_->Stop(); |
+ } |
+ if (distance > double_tap_touch_slop_square_) { |
+ always_in_bigger_tap_region_ = false; |
+ } |
+ } else if ((std::abs(scrollX) >= 1) || (std::abs(scrollY) >= 1)) { |
+ handled = |
+ listener_->OnScroll(*current_down_event_, ev, scrollX, scrollY); |
+ last_focus_x_ = focus_x; |
+ last_focus_y_ = focus_y; |
+ } |
+ |
+ } |
+ break; |
+ |
+ case MotionEvent::ACTION_UP: |
+ still_down_ = false; |
+ { |
+ if (is_double_tapping_) { |
+ // Finally, give the up event of the double-tap |
+ handled |= double_tap_listener_->OnDoubleTapEvent(ev); |
+ } else if (in_long_press_) { |
+ handler_->StopTimeout(TAP); |
+ in_long_press_ = false; |
+ } else if (always_in_tap_region_) { |
+ handled = listener_->OnSingleTapUp(ev); |
+ if (defer_confirm_single_tap_ && double_tap_listener_ != NULL) { |
+ double_tap_listener_->OnSingleTapConfirmed(ev); |
+ } |
+ } else { |
+ |
+ // A fling must travel the minimum tap distance |
+ const int pointerId = ev.GetPointerId(0); |
+ velocity_tracker_.ComputeCurrentVelocity(1000, max_fling_velocity_); |
+ const float velocity_y = velocity_tracker_.GetYVelocity(pointerId); |
+ const float velocity_x = velocity_tracker_.GetXVelocity(pointerId); |
+ |
+ if ((std::abs(velocity_y) > min_fling_velocity_) || |
+ (std::abs(velocity_x) > min_fling_velocity_)) { |
+ handled = listener_->OnFling( |
+ *current_down_event_, ev, velocity_x, velocity_y); |
+ } |
+ } |
+ |
+ if (!previous_up_event_) |
+ previous_up_event_.reset(new MotionEvent(ev.event)); |
+ else |
+ previous_up_event_->event = ev.event; |
+ |
+ velocity_tracker_.Clear(); |
+ is_double_tapping_ = false; |
+ defer_confirm_single_tap_ = false; |
+ handler_->StopTimeout(SHOW_PRESS); |
+ handler_->StopTimeout(LONG_PRESS); |
+ } |
+ break; |
+ |
+ case MotionEvent::ACTION_CANCEL: |
+ Cancel(); |
+ break; |
+ |
+ case MotionEvent::ACTION_TYPE_COUNT: |
+ NOTREACHED() << "Invalid MotionEvent type."; |
+ break; |
+ } |
+ |
+ return handled; |
+} |
+ |
+void GestureDetector::Init(Config config) { |
+ DCHECK(listener_); |
+ |
+ const int touch_slop = config.scaled_touch_slop; |
+ const int double_tap_touch_slop = touch_slop; |
+ const int double_tap_slop = config.scaled_double_tap_slop; |
+ min_fling_velocity_ = config.scaled_minimum_fling_velocity; |
+ max_fling_velocity_ = config.scaled_maximum_fling_velocity; |
+ touch_slop_square_ = touch_slop * touch_slop; |
+ double_tap_touch_slop_square_ = double_tap_touch_slop * double_tap_touch_slop; |
+ double_tap_slop_square_ = double_tap_slop * double_tap_slop; |
+ double_tap_timeout_ = config.double_tap_timeout; |
+} |
+ |
+void GestureDetector::OnShowPressTimeout() { |
+ listener_->OnShowPress(*current_down_event_); |
+} |
+ |
+void GestureDetector::OnLongPressTimeout() { |
+ handler_->StopTimeout(TAP); |
+ defer_confirm_single_tap_ = false; |
+ in_long_press_ = true; |
+ listener_->OnLongPress(*current_down_event_); |
+} |
+ |
+void GestureDetector::OnTapTimeout() { |
+ if (!double_tap_listener_) |
+ return; |
+ if (!still_down_) |
+ double_tap_listener_->OnSingleTapConfirmed(*current_down_event_); |
+ else |
+ defer_confirm_single_tap_ = true; |
+} |
+ |
+void GestureDetector::Cancel() { |
+ handler_->Stop(); |
+ velocity_tracker_.Clear(); |
+ is_double_tapping_ = false; |
+ still_down_ = false; |
+ always_in_tap_region_ = false; |
+ always_in_bigger_tap_region_ = false; |
+ defer_confirm_single_tap_ = false; |
+ in_long_press_ = false; |
+} |
+ |
+void GestureDetector::CancelTaps() { |
+ handler_->Stop(); |
+ is_double_tapping_ = false; |
+ always_in_tap_region_ = false; |
+ always_in_bigger_tap_region_ = false; |
+ defer_confirm_single_tap_ = false; |
+ in_long_press_ = false; |
+} |
+ |
+bool GestureDetector::IsConsideredDoubleTap(const MotionEvent& first_down, |
+ const MotionEvent& first_up, |
+ const MotionEvent& second_down) { |
+ if (!always_in_bigger_tap_region_) |
+ return false; |
+ |
+ if (second_down.GetEventTime() - first_up.GetEventTime() > |
+ double_tap_timeout_) |
+ return false; |
+ |
+ int deltaX = (int)first_down.GetX() - (int)second_down.GetX(); |
+ int deltaY = (int)first_down.GetY() - (int)second_down.GetY(); |
+ return (deltaX * deltaX + deltaY * deltaY < double_tap_slop_square_); |
+} |
+ |
+} // namespace content |