| 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
|
|
|