Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(129)

Unified Diff: ui/events/gesture_detection/gesture_detector.cc

Issue 171773012: Port of Android platform gesture detection code to C++ (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « ui/events/gesture_detection/gesture_detector.h ('k') | ui/events/gesture_detection/gesture_event_params.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « ui/events/gesture_detection/gesture_detector.h ('k') | ui/events/gesture_detection/gesture_event_params.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698