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

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

Issue 128613003: [Tracking Patch] Unified gesture detection (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Address tdresser@ comments and run clang-format on everything 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
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..79c1a92b539b2ca623da50f6ec236f55b95d1564
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_detector.cc
@@ -0,0 +1,394 @@
+// 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 "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
+
+GestureDetector::Config::Config()
Sami 2014/02/20 14:59:05 Could you add a comment saying which device these
jdduke (slow) 2014/02/20 18:01:09 Done.
+ : 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::SimpleOnGestureListener::OnDown(const MotionEvent& e) {
+ return false;
+}
+
+void GestureDetector::SimpleOnGestureListener::OnShowPress(
+ const MotionEvent& e) {}
+
+bool GestureDetector::SimpleOnGestureListener::OnSingleTapUp(
+ const MotionEvent& e) {
+ return false;
+}
+
+bool GestureDetector::SimpleOnGestureListener::OnLongPress(
+ const MotionEvent& e) {
+ return false;
+}
+
+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::TimeoutGestureHandler {
+ public:
+ typedef void (GestureDetector::*ReceiverMethod)();
Sami 2014/02/20 14:59:05 This could be private, right?
jdduke (slow) 2014/02/20 18:01:09 Done.
+
+ 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;
+ }
+
Sami 2014/02/20 14:59:05 Should we have a destructor that calls Stop() auto
jdduke (slow) 2014/02/20 18:01:09 I believe the |timeout_timer_[i]| destructor will
+ 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:
+ 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,
+ OnGestureListener* listener,
+ OnDoubleTapListener* 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_long_press_(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
Sami 2014/02/20 14:59:05 nit: Comments should end with a period.
jdduke (slow) 2014/02/20 18:01:09 Done.
+ float sum_x = 0, sum_y = 0;
+ const int count = 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
Sami 2014/02/20 14:59:05 Ditto.
jdduke (slow) 2014/02/20 18:01:09 Done.
+ 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();
Sami 2014/02/20 14:59:05 up_index
jdduke (slow) 2014/02/20 18:01:09 Done.
+ 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 = timeout_handler_->HasTimeout(TAP);
Sami 2014/02/20 14:59:05 had_tap_message
jdduke (slow) 2014/02/20 18:01:09 Done.
+ if (hadTapMessage)
+ timeout_handler_->StopTimeout(TAP);
+ if (current_down_event_ && previous_up_event_ && hadTapMessage &&
+ IsConsideredDoubleTap(
+ *current_down_event_, *previous_up_event_, ev)) {
+ // This is a second tap
Sami 2014/02/20 14:59:05 Add '.'
jdduke (slow) 2014/02/20 18:01:09 Done.
+ is_double_tapping_ = true;
+ // Give a callback with the first tap of the double-tap
Sami 2014/02/20 14:59:05 Ditto.
jdduke (slow) 2014/02/20 18:01:09 Done.
+ handled |= double_tap_listener_->OnDoubleTap(*current_down_event_);
+ // Give a callback with down event of the double-tap
Sami 2014/02/20 14:59:05 Ditto.
jdduke (slow) 2014/02/20 18:01:09 Done.
+ handled |= double_tap_listener_->OnDoubleTapEvent(ev);
+ } else {
+ // This is a first tap
Sami 2014/02/20 14:59:05 Ditto.
jdduke (slow) 2014/02/20 18:01:09 Done.
+ 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_long_press_ = 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_long_press_)
+ 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
Sami 2014/02/20 14:59:05 Add '.'
jdduke (slow) 2014/02/20 18:01:09 Done.
+ handled |= double_tap_listener_->OnDoubleTapEvent(ev);
+ } else if (always_in_tap_region_) {
+ const int delta_x = (int)(focus_x - down_focus_x_);
Sami 2014/02/20 14:59:05 static_cast<int> instead of C-casts.
jdduke (slow) 2014/02/20 18:01:09 Done.
+ const int delta_y = (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
Sami 2014/02/20 14:59:05 Add '.'
jdduke (slow) 2014/02/20 18:01:09 Done.
+ handled |= double_tap_listener_->OnDoubleTapEvent(ev);
+ } else if (in_long_press_) {
+ timeout_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
Sami 2014/02/20 14:59:05 Add '.'
jdduke (slow) 2014/02/20 18:01:09 Done.
+ 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(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_long_press_ = 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_long_press_ = 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_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 delta_x = (int)first_down.GetX() - (int)second_down.GetX();
Sami 2014/02/20 14:59:05 Use static_cast<int> instead of C-style casts.
jdduke (slow) 2014/02/20 18:01:09 Done.
+ int delta_y = (int)first_down.GetY() - (int)second_down.GetY();
+ return (delta_x * delta_x + delta_y * delta_y < double_tap_slop_square_);
+}
+
+} // namespace ui

Powered by Google App Engine
This is Rietveld 408576698