| Index: ui/events/gesture_detection/gesture_detector.cc
|
| diff --git a/ui/events/gesture_detection/gesture_detector.cc b/ui/events/gesture_detection/gesture_detector.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9698a37523a936243559bcd7cd0cdaad66f9eaf1
|
| --- /dev/null
|
| +++ b/ui/events/gesture_detection/gesture_detector.cc
|
| @@ -0,0 +1,401 @@
|
| +// Copyright 2014 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 "ui/events/gesture_detection/gesture_detector.h"
|
| +
|
| +#include <cmath>
|
| +
|
| +#include "base/timer/timer.h"
|
| +#include "ui/events/gesture_detection/motion_event.h"
|
| +
|
| +namespace ui {
|
| +namespace {
|
| +
|
| +// Constants used by TimeoutGestureHandler.
|
| +enum TimeoutEvent {
|
| + SHOW_PRESS = 0,
|
| + LONG_PRESS,
|
| + TAP,
|
| + TIMEOUT_EVENT_COUNT
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +// Note: These constants were taken directly from the default (unscaled)
|
| +// versions found in Android's ViewConfiguration.
|
| +GestureDetector::Config::Config()
|
| + : longpress_timeout(base::TimeDelta::FromMilliseconds(500)),
|
| + tap_timeout(base::TimeDelta::FromMilliseconds(180)),
|
| + double_tap_timeout(base::TimeDelta::FromMilliseconds(300)),
|
| + scaled_touch_slop(8),
|
| + scaled_double_tap_slop(100),
|
| + scaled_minimum_fling_velocity(50),
|
| + scaled_maximum_fling_velocity(8000) {}
|
| +
|
| +GestureDetector::Config::~Config() {}
|
| +
|
| +bool GestureDetector::SimpleGestureListener::OnDown(const MotionEvent& e) {
|
| + return false;
|
| +}
|
| +
|
| +void GestureDetector::SimpleGestureListener::OnShowPress(const MotionEvent& e) {
|
| +}
|
| +
|
| +bool GestureDetector::SimpleGestureListener::OnSingleTapUp(
|
| + const MotionEvent& e) {
|
| + return false;
|
| +}
|
| +
|
| +bool GestureDetector::SimpleGestureListener::OnLongPress(const MotionEvent& e) {
|
| + return false;
|
| +}
|
| +
|
| +bool GestureDetector::SimpleGestureListener::OnScroll(const MotionEvent& e1,
|
| + const MotionEvent& e2,
|
| + float distance_x,
|
| + float distance_y) {
|
| + return false;
|
| +}
|
| +
|
| +bool GestureDetector::SimpleGestureListener::OnFling(const MotionEvent& e1,
|
| + const MotionEvent& e2,
|
| + float velocity_x,
|
| + float velocity_y) {
|
| + return false;
|
| +}
|
| +
|
| +bool GestureDetector::SimpleGestureListener::OnSingleTapConfirmed(
|
| + const MotionEvent& e) {
|
| + return false;
|
| +}
|
| +
|
| +bool GestureDetector::SimpleGestureListener::OnDoubleTap(const MotionEvent& e) {
|
| + return false;
|
| +}
|
| +
|
| +bool GestureDetector::SimpleGestureListener::OnDoubleTapEvent(
|
| + const MotionEvent& e) {
|
| + return false;
|
| +}
|
| +
|
| +class GestureDetector::TimeoutGestureHandler {
|
| + public:
|
| + TimeoutGestureHandler(const Config& config, GestureDetector* gesture_detector)
|
| + : gesture_detector_(gesture_detector) {
|
| + timeout_callbacks_[SHOW_PRESS] = &GestureDetector::OnShowPressTimeout;
|
| + timeout_delays_[SHOW_PRESS] = config.tap_timeout;
|
| +
|
| + timeout_callbacks_[LONG_PRESS] = &GestureDetector::OnLongPressTimeout;
|
| + timeout_delays_[LONG_PRESS] = config.longpress_timeout + config.tap_timeout;
|
| +
|
| + timeout_callbacks_[TAP] = &GestureDetector::OnTapTimeout;
|
| + timeout_delays_[TAP] = config.double_tap_timeout;
|
| + }
|
| +
|
| + ~TimeoutGestureHandler() {
|
| + Stop();
|
| + }
|
| +
|
| + void StartTimeout(TimeoutEvent event) {
|
| + timeout_timers_[event].Start(FROM_HERE,
|
| + timeout_delays_[event],
|
| + gesture_detector_,
|
| + timeout_callbacks_[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:
|
| + typedef void (GestureDetector::*ReceiverMethod)();
|
| +
|
| + GestureDetector* const gesture_detector_;
|
| + base::OneShotTimer<GestureDetector> timeout_timers_[TIMEOUT_EVENT_COUNT];
|
| + ReceiverMethod timeout_callbacks_[TIMEOUT_EVENT_COUNT];
|
| + base::TimeDelta timeout_delays_[TIMEOUT_EVENT_COUNT];
|
| +};
|
| +
|
| +GestureDetector::GestureDetector(
|
| + const Config& config,
|
| + GestureListener* listener,
|
| + DoubleTapListener* optional_double_tap_listener)
|
| + : timeout_handler_(new TimeoutGestureHandler(config, this)),
|
| + listener_(listener),
|
| + double_tap_listener_(optional_double_tap_listener),
|
| + touch_slop_square_(0),
|
| + double_tap_touch_slop_square_(0),
|
| + double_tap_slop_square_(0),
|
| + min_fling_velocity_(1),
|
| + max_fling_velocity_(1),
|
| + still_down_(false),
|
| + defer_confirm_single_tap_(false),
|
| + in_longpress_(false),
|
| + always_in_tap_region_(false),
|
| + always_in_bigger_tap_region_(false),
|
| + is_double_tapping_(false),
|
| + last_focus_x_(0),
|
| + last_focus_y_(0),
|
| + down_focus_x_(0),
|
| + down_focus_y_(0),
|
| + is_longpress_enabled_(true) {
|
| + DCHECK(listener_);
|
| + Init(config);
|
| +}
|
| +
|
| +GestureDetector::~GestureDetector() {}
|
| +
|
| +bool GestureDetector::OnTouchEvent(const MotionEvent& ev) {
|
| + const MotionEvent::Action action = ev.GetAction();
|
| +
|
| + velocity_tracker_.AddMovement(ev);
|
| +
|
| + const bool pointer_up = action == MotionEvent::ACTION_POINTER_UP;
|
| + const int skip_index = pointer_up ? ev.GetActionIndex() : -1;
|
| +
|
| + // Determine focal point.
|
| + float sum_x = 0, sum_y = 0;
|
| + const int count = static_cast<int>(ev.GetPointerCount());
|
| + for (int i = 0; i < count; i++) {
|
| + if (skip_index == i)
|
| + continue;
|
| + sum_x += ev.GetX(i);
|
| + sum_y += ev.GetY(i);
|
| + }
|
| + const int div = pointer_up ? 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 up_index = ev.GetActionIndex();
|
| + const int id1 = ev.GetPointerId(up_index);
|
| + const float x1 = velocity_tracker_.GetXVelocity(id1);
|
| + const float y1 = velocity_tracker_.GetYVelocity(id1);
|
| + for (int i = 0; i < count; i++) {
|
| + if (i == up_index)
|
| + 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 had_tap_message = timeout_handler_->HasTimeout(TAP);
|
| + if (had_tap_message)
|
| + timeout_handler_->StopTimeout(TAP);
|
| + if (current_down_event_ && previous_up_event_ && had_tap_message &&
|
| + 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.
|
| + timeout_handler_->StartTimeout(TAP);
|
| + }
|
| + }
|
| +
|
| + down_focus_x_ = last_focus_x_ = focus_x;
|
| + down_focus_y_ = last_focus_y_ = focus_y;
|
| + current_down_event_ = ev.Clone();
|
| +
|
| + always_in_tap_region_ = true;
|
| + always_in_bigger_tap_region_ = true;
|
| + still_down_ = true;
|
| + in_longpress_ = false;
|
| + defer_confirm_single_tap_ = false;
|
| +
|
| + if (is_longpress_enabled_)
|
| + timeout_handler_->StartTimeout(LONG_PRESS);
|
| + timeout_handler_->StartTimeout(SHOW_PRESS);
|
| + handled |= listener_->OnDown(ev);
|
| + break;
|
| +
|
| + case MotionEvent::ACTION_MOVE:
|
| + if (in_longpress_)
|
| + break;
|
| +
|
| + {
|
| + const float scroll_x = last_focus_x_ - focus_x;
|
| + const float scroll_y = 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 delta_x = static_cast<int>(focus_x - down_focus_x_);
|
| + const int delta_y = static_cast<int>(focus_y - down_focus_y_);
|
| + int distance = (delta_x * delta_x) + (delta_y * delta_y);
|
| + if (distance > touch_slop_square_) {
|
| + handled = listener_->OnScroll(
|
| + *current_down_event_, ev, scroll_x, scroll_y);
|
| + last_focus_x_ = focus_x;
|
| + last_focus_y_ = focus_y;
|
| + always_in_tap_region_ = false;
|
| + timeout_handler_->Stop();
|
| + }
|
| + if (distance > double_tap_touch_slop_square_)
|
| + always_in_bigger_tap_region_ = false;
|
| + } else if ((std::abs(scroll_x) >= 1) || (std::abs(scroll_y) >= 1)) {
|
| + handled =
|
| + listener_->OnScroll(*current_down_event_, ev, scroll_x, scroll_y);
|
| + 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_longpress_) {
|
| + timeout_handler_->StopTimeout(TAP);
|
| + in_longpress_ = 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 pointer_id = ev.GetPointerId(0);
|
| + velocity_tracker_.ComputeCurrentVelocity(1000, max_fling_velocity_);
|
| + const float velocity_y = velocity_tracker_.GetYVelocity(pointer_id);
|
| + const float velocity_x = velocity_tracker_.GetXVelocity(pointer_id);
|
| +
|
| + 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);
|
| + }
|
| + }
|
| +
|
| + previous_up_event_ = ev.Clone();
|
| +
|
| + velocity_tracker_.Clear();
|
| + is_double_tapping_ = false;
|
| + defer_confirm_single_tap_ = false;
|
| + timeout_handler_->StopTimeout(SHOW_PRESS);
|
| + timeout_handler_->StopTimeout(LONG_PRESS);
|
| + }
|
| + break;
|
| +
|
| + case MotionEvent::ACTION_CANCEL:
|
| + Cancel();
|
| + break;
|
| + }
|
| +
|
| + return handled;
|
| +}
|
| +
|
| +void GestureDetector::Init(const 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() {
|
| + timeout_handler_->StopTimeout(TAP);
|
| + defer_confirm_single_tap_ = false;
|
| + in_longpress_ = 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() {
|
| + timeout_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_longpress_ = false;
|
| +}
|
| +
|
| +void GestureDetector::CancelTaps() {
|
| + timeout_handler_->Stop();
|
| + is_double_tapping_ = false;
|
| + always_in_tap_region_ = false;
|
| + always_in_bigger_tap_region_ = false;
|
| + defer_confirm_single_tap_ = false;
|
| + in_longpress_ = false;
|
| +}
|
| +
|
| +bool GestureDetector::IsConsideredDoubleTap(
|
| + const MotionEvent& first_down,
|
| + const MotionEvent& first_up,
|
| + const MotionEvent& second_down) const {
|
| + if (!always_in_bigger_tap_region_)
|
| + return false;
|
| +
|
| + if (second_down.GetEventTime() - first_up.GetEventTime() >
|
| + double_tap_timeout_)
|
| + return false;
|
| +
|
| + int delta_x = static_cast<int>(first_down.GetX() - second_down.GetX());
|
| + int delta_y = static_cast<int>(first_down.GetY() - second_down.GetY());
|
| + return (delta_x * delta_x + delta_y * delta_y < double_tap_slop_square_);
|
| +}
|
| +
|
| +} // namespace ui
|
|
|