summaryrefslogtreecommitdiffstats
path: root/ui/base
diff options
context:
space:
mode:
authorsadrul@chromium.org <sadrul@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-05 22:14:49 +0000
committersadrul@chromium.org <sadrul@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-05 22:14:49 +0000
commit81675c914ddcaaf9f8f8ff2a8db1c5783223fd49 (patch)
treeeccb226b79f2bbb98d4ec0514db5fede5931654d /ui/base
parent19ce5c5150f0900e0365720bb2c9ccde589b07b7 (diff)
downloadchromium_src-81675c914ddcaaf9f8f8ff2a8db1c5783223fd49.zip
chromium_src-81675c914ddcaaf9f8f8ff2a8db1c5783223fd49.tar.gz
chromium_src-81675c914ddcaaf9f8f8ff2a8db1c5783223fd49.tar.bz2
ui: Move gesture-recognizer into ui/base/.
Moving the gesture-recognizer into ui/base/ makes it possible to be used by both aura and views independently. This is the first step: add an interface for touch-events and gesture-events so that the gesture recognizer does not have to depend on aura or views events. BUG=121744 TEST=none Review URL: https://chromiumcodereview.appspot.com/9958136 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@131023 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui/base')
-rw-r--r--ui/base/gestures/OWNERS3
-rw-r--r--ui/base/gestures/gesture_configuration.cc33
-rw-r--r--ui/base/gestures/gesture_configuration.h131
-rw-r--r--ui/base/gestures/gesture_point.cc171
-rw-r--r--ui/base/gestures/gesture_point.h108
-rw-r--r--ui/base/gestures/gesture_recognizer.h67
-rw-r--r--ui/base/gestures/gesture_recognizer_impl.cc240
-rw-r--r--ui/base/gestures/gesture_recognizer_impl.h80
-rw-r--r--ui/base/gestures/gesture_sequence.cc572
-rw-r--r--ui/base/gestures/gesture_sequence.h163
-rw-r--r--ui/base/gestures/gesture_types.h65
-rw-r--r--ui/base/gestures/gestures.dot32
-rw-r--r--ui/base/gestures/velocity_calculator.cc109
-rw-r--r--ui/base/gestures/velocity_calculator.h51
-rw-r--r--ui/base/gestures/velocity_calculator_unittest.cc143
15 files changed, 1968 insertions, 0 deletions
diff --git a/ui/base/gestures/OWNERS b/ui/base/gestures/OWNERS
new file mode 100644
index 0000000..a324de8
--- /dev/null
+++ b/ui/base/gestures/OWNERS
@@ -0,0 +1,3 @@
+# Do not set noparent
+rjkroege@chromium.org
+sadrul@chromium.org
diff --git a/ui/base/gestures/gesture_configuration.cc b/ui/base/gestures/gesture_configuration.cc
new file mode 100644
index 0000000..9128aa5
--- /dev/null
+++ b/ui/base/gestures/gesture_configuration.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 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/base/gestures/gesture_configuration.h"
+
+namespace ui {
+
+// Ordered alphabetically ignoring underscores, to align with the
+// associated list of prefs in gesture_prefs_aura.cc.
+double GestureConfiguration::long_press_time_in_seconds_ = 0.5;
+double GestureConfiguration::max_seconds_between_double_click_ = 0.7;
+double
+ GestureConfiguration::max_separation_for_gesture_touches_in_pixels_ = 150;
+double
+ GestureConfiguration::max_touch_down_duration_in_seconds_for_click_ = 0.8;
+double GestureConfiguration::max_touch_move_in_pixels_for_click_ = 20;
+double GestureConfiguration::min_distance_for_pinch_scroll_in_pixels_ = 20;
+double GestureConfiguration::min_flick_speed_squared_ = 550.f * 550.f;
+double GestureConfiguration::min_pinch_update_distance_in_pixels_ = 5;
+double GestureConfiguration::min_rail_break_velocity_ = 200;
+double GestureConfiguration::min_scroll_delta_squared_ = 5 * 5;
+double
+ GestureConfiguration::min_touch_down_duration_in_seconds_for_click_ = 0.01;
+
+// The number of points used in the linear regression which determines
+// touch velocity. If fewer than this number of points have been seen,
+// velocity is reported as 0.
+int GestureConfiguration::points_buffered_for_velocity_ = 10;
+double GestureConfiguration::rail_break_proportion_ = 15;
+double GestureConfiguration::rail_start_proportion_ = 2;
+
+} // namespace ui
diff --git a/ui/base/gestures/gesture_configuration.h b/ui/base/gestures/gesture_configuration.h
new file mode 100644
index 0000000..83f953b
--- /dev/null
+++ b/ui/base/gestures/gesture_configuration.h
@@ -0,0 +1,131 @@
+// Copyright (c) 2012 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.
+
+#ifndef UI_BASE_GESTURES_GESTURE_CONFIGURATION_H_
+#define UI_BASE_GESTURES_GESTURE_CONFIGURATION_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "ui/base/ui_export.h"
+
+namespace ui {
+
+// TODO: Expand this design to support multiple OS configuration
+// approaches (windows, chrome, others). This would turn into an
+// abstract base class.
+
+class UI_EXPORT GestureConfiguration {
+ public:
+ // Ordered alphabetically ignoring underscores, to align with the
+ // associated list of prefs in gesture_prefs_aura.cc.
+ static double long_press_time_in_seconds() {
+ return long_press_time_in_seconds_;
+ }
+ static void set_long_press_time_in_seconds(double val) {
+ long_press_time_in_seconds_ = val;
+ }
+ static double max_seconds_between_double_click() {
+ return max_seconds_between_double_click_;
+ }
+ static void set_max_seconds_between_double_click(double val) {
+ max_seconds_between_double_click_ = val;
+ }
+ static int max_separation_for_gesture_touches_in_pixels() {
+ return max_separation_for_gesture_touches_in_pixels_;
+ }
+ static void set_max_separation_for_gesture_touches_in_pixels(int val) {
+ max_separation_for_gesture_touches_in_pixels_ = val;
+ }
+ static double max_touch_down_duration_in_seconds_for_click() {
+ return max_touch_down_duration_in_seconds_for_click_;
+ }
+ static void set_max_touch_down_duration_in_seconds_for_click(double val) {
+ max_touch_down_duration_in_seconds_for_click_ = val;
+ }
+ static double max_touch_move_in_pixels_for_click() {
+ return max_touch_move_in_pixels_for_click_;
+ }
+ static void set_max_touch_move_in_pixels_for_click(double val) {
+ max_touch_move_in_pixels_for_click_ = val;
+ }
+ static double min_distance_for_pinch_scroll_in_pixels() {
+ return min_distance_for_pinch_scroll_in_pixels_;
+ }
+ static void set_min_distance_for_pinch_scroll_in_pixels(double val) {
+ min_distance_for_pinch_scroll_in_pixels_ = val;
+ }
+ static double min_flick_speed_squared() {
+ return min_flick_speed_squared_;
+ }
+ static void set_min_flick_speed_squared(double val) {
+ min_flick_speed_squared_ = val;
+ }
+ static double min_pinch_update_distance_in_pixels() {
+ return min_pinch_update_distance_in_pixels_;
+ }
+ static void set_min_pinch_update_distance_in_pixels(double val) {
+ min_pinch_update_distance_in_pixels_ = val;
+ }
+ static double min_rail_break_velocity() {
+ return min_rail_break_velocity_;
+ }
+ static void set_min_rail_break_velocity(double val) {
+ min_rail_break_velocity_ = val;
+ }
+ static double min_scroll_delta_squared() {
+ return min_scroll_delta_squared_;
+ }
+ static void set_min_scroll_delta_squared(double val) {
+ min_scroll_delta_squared_ = val;
+ }
+ static double min_touch_down_duration_in_seconds_for_click() {
+ return min_touch_down_duration_in_seconds_for_click_;
+ }
+ static void set_min_touch_down_duration_in_seconds_for_click(double val) {
+ min_touch_down_duration_in_seconds_for_click_ = val;
+ }
+ static int points_buffered_for_velocity() {
+ return points_buffered_for_velocity_;
+ }
+ static void set_points_buffered_for_velocity(int val) {
+ points_buffered_for_velocity_ = val;
+ }
+ static double rail_break_proportion() {
+ return rail_break_proportion_;
+ }
+ static void set_rail_break_proportion(double val) {
+ rail_break_proportion_ = val;
+ }
+ static double rail_start_proportion() {
+ return rail_start_proportion_;
+ }
+ static void set_rail_start_proportion(double val) {
+ rail_start_proportion_ = val;
+ }
+
+ private:
+ // These are listed in alphabetical order ignoring underscores, to
+ // align with the associated list of preferences in
+ // gesture_prefs_aura.cc. These two lists should be kept in sync.
+ static double long_press_time_in_seconds_;
+ static double max_seconds_between_double_click_;
+ static double max_separation_for_gesture_touches_in_pixels_;
+ static double max_touch_down_duration_in_seconds_for_click_;
+ static double max_touch_move_in_pixels_for_click_;
+ static double min_distance_for_pinch_scroll_in_pixels_;
+ static double min_flick_speed_squared_;
+ static double min_pinch_update_distance_in_pixels_;
+ static double min_rail_break_velocity_;
+ static double min_scroll_delta_squared_;
+ static double min_touch_down_duration_in_seconds_for_click_;
+ static int points_buffered_for_velocity_;
+ static double rail_break_proportion_;
+ static double rail_start_proportion_;
+
+ DISALLOW_COPY_AND_ASSIGN(GestureConfiguration);
+};
+
+} // namespace ui
+
+#endif // UI_BASE_GESTURES_GESTURE_CONFIGURATION_H_
diff --git a/ui/base/gestures/gesture_point.cc b/ui/base/gestures/gesture_point.cc
new file mode 100644
index 0000000..fc94f72
--- /dev/null
+++ b/ui/base/gestures/gesture_point.cc
@@ -0,0 +1,171 @@
+// Copyright (c) 2012 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/base/gestures/gesture_point.h"
+
+#include <cmath>
+
+#include "base/basictypes.h"
+#include "ui/base/events.h"
+#include "ui/base/gestures/gesture_configuration.h"
+#include "ui/base/gestures/gesture_types.h"
+
+namespace ui {
+
+GesturePoint::GesturePoint()
+ : first_touch_time_(0.0),
+ last_touch_time_(0.0),
+ last_tap_time_(0.0),
+ velocity_calculator_(
+ GestureConfiguration::points_buffered_for_velocity()),
+ point_id_(-1) {
+}
+
+GesturePoint::~GesturePoint() {}
+
+void GesturePoint::Reset() {
+ first_touch_time_ = last_touch_time_ = 0.0;
+ velocity_calculator_.ClearHistory();
+ point_id_ = -1;
+}
+
+void GesturePoint::UpdateValues(const TouchEvent& event) {
+ const int64 event_timestamp_microseconds =
+ event.GetTimestamp().InMicroseconds();
+ if (event.GetEventType() == ui::ET_TOUCH_MOVED) {
+ velocity_calculator_.PointSeen(event.GetLocation().x(),
+ event.GetLocation().y(),
+ event_timestamp_microseconds);
+ }
+
+ last_touch_time_ = event.GetTimestamp().InSecondsF();
+ last_touch_position_ = event.GetLocation();
+
+ if (event.GetEventType() == ui::ET_TOUCH_PRESSED) {
+ first_touch_time_ = last_touch_time_;
+ first_touch_position_ = event.GetLocation();
+ velocity_calculator_.ClearHistory();
+ velocity_calculator_.PointSeen(event.GetLocation().x(),
+ event.GetLocation().y(),
+ event_timestamp_microseconds);
+ }
+}
+
+void GesturePoint::UpdateForTap() {
+ // Update the tap-position and time, and reset every other state.
+ last_tap_time_ = last_touch_time_;
+ last_tap_position_ = last_touch_position_;
+ Reset();
+}
+
+void GesturePoint::UpdateForScroll() {
+ // Update the first-touch position and time so that the scroll-delta and
+ // scroll-velocity can be computed correctly for the next scroll gesture
+ // event.
+ first_touch_position_ = last_touch_position_;
+ first_touch_time_ = last_touch_time_;
+}
+
+bool GesturePoint::IsInClickWindow(const TouchEvent& event) const {
+ return IsInClickTimeWindow() && IsInsideManhattanSquare(event);
+}
+
+bool GesturePoint::IsInDoubleClickWindow(const TouchEvent& event) const {
+ return IsInSecondClickTimeWindow() &&
+ IsSecondClickInsideManhattanSquare(event);
+}
+
+bool GesturePoint::IsInScrollWindow(const TouchEvent& event) const {
+ return event.GetEventType() == ui::ET_TOUCH_MOVED &&
+ !IsInsideManhattanSquare(event);
+}
+
+bool GesturePoint::IsInFlickWindow(const TouchEvent& event) {
+ return IsOverMinFlickSpeed() &&
+ event.GetEventType() != ui::ET_TOUCH_CANCELLED;
+}
+
+bool GesturePoint::DidScroll(const TouchEvent& event, int dist) const {
+ return abs(last_touch_position_.x() - first_touch_position_.x()) > dist ||
+ abs(last_touch_position_.y() - first_touch_position_.y()) > dist;
+}
+
+float GesturePoint::Distance(const GesturePoint& point) const {
+ float x_diff = point.last_touch_position_.x() - last_touch_position_.x();
+ float y_diff = point.last_touch_position_.y() - last_touch_position_.y();
+ return sqrt(x_diff * x_diff + y_diff * y_diff);
+}
+
+bool GesturePoint::HasEnoughDataToEstablishRail() const {
+ int dx = x_delta();
+ int dy = y_delta();
+
+ int delta_squared = dx * dx + dy * dy;
+ return delta_squared > GestureConfiguration::min_scroll_delta_squared();
+}
+
+bool GesturePoint::IsInHorizontalRailWindow() const {
+ int dx = x_delta();
+ int dy = y_delta();
+ return abs(dx) > GestureConfiguration::rail_start_proportion() * abs(dy);
+}
+
+bool GesturePoint::IsInVerticalRailWindow() const {
+ int dx = x_delta();
+ int dy = y_delta();
+ return abs(dy) > GestureConfiguration::rail_start_proportion() * abs(dx);
+}
+
+bool GesturePoint::BreaksHorizontalRail() {
+ float vx = XVelocity();
+ float vy = YVelocity();
+ return fabs(vy) > GestureConfiguration::rail_break_proportion() * fabs(vx) +
+ GestureConfiguration::min_rail_break_velocity();
+}
+
+bool GesturePoint::BreaksVerticalRail() {
+ float vx = XVelocity();
+ float vy = YVelocity();
+ return fabs(vx) > GestureConfiguration::rail_break_proportion() * fabs(vy) +
+ GestureConfiguration::min_rail_break_velocity();
+}
+
+bool GesturePoint::IsInClickTimeWindow() const {
+ double duration = last_touch_time_ - first_touch_time_;
+ return duration >=
+ GestureConfiguration::min_touch_down_duration_in_seconds_for_click() &&
+ duration <
+ GestureConfiguration::max_touch_down_duration_in_seconds_for_click();
+}
+
+bool GesturePoint::IsInSecondClickTimeWindow() const {
+ double duration = last_touch_time_ - last_tap_time_;
+ return duration < GestureConfiguration::max_seconds_between_double_click();
+}
+
+bool GesturePoint::IsInsideManhattanSquare(const TouchEvent& event) const {
+ int manhattanDistance = abs(event.GetLocation().x() -
+ first_touch_position_.x()) +
+ abs(event.GetLocation().y() -
+ first_touch_position_.y());
+ return manhattanDistance <
+ GestureConfiguration::max_touch_move_in_pixels_for_click();
+}
+
+bool GesturePoint::IsSecondClickInsideManhattanSquare(
+ const TouchEvent& event) const {
+ int manhattanDistance = abs(event.GetLocation().x() -
+ last_tap_position_.x()) +
+ abs(event.GetLocation().y() -
+ last_tap_position_.y());
+ return manhattanDistance <
+ GestureConfiguration::max_touch_move_in_pixels_for_click();
+}
+
+bool GesturePoint::IsOverMinFlickSpeed() {
+ return velocity_calculator_.VelocitySquared() >
+ GestureConfiguration::min_flick_speed_squared();
+}
+
+} // namespace ui
diff --git a/ui/base/gestures/gesture_point.h b/ui/base/gestures/gesture_point.h
new file mode 100644
index 0000000..9eee5b8
--- /dev/null
+++ b/ui/base/gestures/gesture_point.h
@@ -0,0 +1,108 @@
+// Copyright (c) 2012 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.
+
+#ifndef UI_BASE_GESTURES_GESTURE_POINT_H_
+#define UI_BASE_GESTURES_GESTURE_POINT_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "ui/base/gestures/velocity_calculator.h"
+#include "ui/gfx/point.h"
+
+namespace ui {
+class TouchEvent;
+
+// A GesturePoint represents a single touch-point/finger during a gesture
+// recognition process.
+class GesturePoint {
+ public:
+ GesturePoint();
+ ~GesturePoint();
+
+ // Resets various states.
+ void Reset();
+
+ // Updates some states when a Tap gesture has been recognized for this point.
+ void UpdateForTap();
+
+ // Updates some states when a Scroll gesture has been recognized for this
+ // point.
+ void UpdateForScroll();
+
+ // Updates states depending on the event and the gesture-state.
+ void UpdateValues(const TouchEvent& event);
+
+ // Responds according to the state of the gesture point (i.e. the point can
+ // represent a click or scroll etc.)
+ bool IsInClickWindow(const TouchEvent& event) const;
+ bool IsInDoubleClickWindow(const TouchEvent& event) const;
+ bool IsInScrollWindow(const TouchEvent& event) const;
+ bool IsInFlickWindow(const TouchEvent& event);
+ bool IsInHorizontalRailWindow() const;
+ bool IsInVerticalRailWindow() const;
+ bool HasEnoughDataToEstablishRail() const;
+ bool BreaksHorizontalRail();
+ bool BreaksVerticalRail();
+ bool DidScroll(const TouchEvent& event, int distance) const;
+
+ const gfx::Point& first_touch_position() const {
+ return first_touch_position_;
+ }
+
+ double last_touch_time() const { return last_touch_time_; }
+ const gfx::Point& last_touch_position() const { return last_touch_position_; }
+
+ // point_id_ is used to drive GestureSequence::ProcessTouchEventForGesture.
+ // point_ids are maintained such that the set of point_ids is always
+ // contiguous, from 0 to the number of current touches.
+ // A lower point_id indicates that a touch occurred first.
+ // A negative point_id indicates that the GesturePoint is not currently
+ // associated with a touch.
+ void set_point_id(int point_id) { point_id_ = point_id; }
+ int point_id() const { return point_id_; }
+
+ void set_touch_id(int touch_id) { touch_id_ = touch_id; }
+ int touch_id() const { return touch_id_; }
+
+ bool in_use() const { return point_id_ >= 0; }
+
+ double x_delta() const {
+ return last_touch_position_.x() - first_touch_position_.x();
+ }
+
+ double y_delta() const {
+ return last_touch_position_.y() - first_touch_position_.y();
+ }
+
+ float XVelocity() { return velocity_calculator_.XVelocity(); }
+ float YVelocity() { return velocity_calculator_.YVelocity(); }
+
+ float Distance(const GesturePoint& point) const;
+
+ private:
+ // Various statistical functions to manipulate gestures.
+ bool IsInClickTimeWindow() const;
+ bool IsInSecondClickTimeWindow() const;
+ bool IsInsideManhattanSquare(const TouchEvent& event) const;
+ bool IsSecondClickInsideManhattanSquare(const TouchEvent& event) const;
+ bool IsOverMinFlickSpeed();
+
+ gfx::Point first_touch_position_;
+ double first_touch_time_;
+ gfx::Point last_touch_position_;
+ double last_touch_time_;
+
+ double last_tap_time_;
+ gfx::Point last_tap_position_;
+
+ VelocityCalculator velocity_calculator_;
+
+ int point_id_;
+ int touch_id_;
+ DISALLOW_COPY_AND_ASSIGN(GesturePoint);
+};
+
+} // namespace ui
+
+#endif // UI_BASE_GESTURES_GESTURE_POINT_H_
diff --git a/ui/base/gestures/gesture_recognizer.h b/ui/base/gestures/gesture_recognizer.h
new file mode 100644
index 0000000..541d823
--- /dev/null
+++ b/ui/base/gestures/gesture_recognizer.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 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.
+
+#ifndef UI_BASE_GESTURES_GESTURE_RECOGNIZER_H_
+#define UI_BASE_GESTURES_GESTURE_RECOGNIZER_H_
+#pragma once
+
+#include <vector>
+
+#include "base/memory/scoped_vector.h"
+#include "ui/base/events.h"
+#include "ui/base/gestures/gesture_types.h"
+#include "ui/base/ui_export.h"
+
+namespace ui {
+// A GestureRecognizer is an abstract base class for conversion of touch events
+// into gestures.
+class UI_EXPORT GestureRecognizer {
+ public:
+ static GestureRecognizer* Create(GestureEventHelper* helper);
+
+ // List of GestureEvent*.
+ typedef ScopedVector<GestureEvent> Gestures;
+
+ virtual ~GestureRecognizer() {}
+
+ // Invoked for each touch event that could contribute to the current gesture.
+ // Returns list of zero or more GestureEvents identified after processing
+ // TouchEvent.
+ // Caller would be responsible for freeing up Gestures.
+ virtual Gestures* ProcessTouchEventForGesture(const TouchEvent& event,
+ ui::TouchStatus status,
+ GestureConsumer* consumer) = 0;
+
+ // Touch-events can be queued to be played back at a later time. The queues
+ // are identified by the target window.
+ virtual void QueueTouchEventForGesture(GestureConsumer* consumer,
+ const TouchEvent& event) = 0;
+
+ // Process the touch-event in the queue for the window. Returns a list of
+ // zero or more GestureEvents identified after processing the queueud
+ // TouchEvent. Caller is responsible for freeing up Gestures.
+ virtual Gestures* AdvanceTouchQueue(GestureConsumer* consumer,
+ bool processed) = 0;
+
+ // Flushes the touch event queue (or removes the queue) for the window.
+ virtual void FlushTouchQueue(GestureConsumer* consumer) = 0;
+
+ // Return the window which should handle this TouchEvent, in the case where
+ // the touch is already associated with a target, or the touch occurs
+ // near another touch.
+ // Otherwise, returns null.
+ virtual GestureConsumer* GetTargetForTouchEvent(TouchEvent* event) = 0;
+
+ // Return the window which should handle this GestureEvent.
+ virtual GestureConsumer* GetTargetForGestureEvent(GestureEvent* event) = 0;
+
+ // If there is an active touch within
+ // GestureConfiguration::max_separation_for_gesture_touches_in_pixels,
+ // of |location|, returns the target of the nearest active touch.
+ virtual GestureConsumer* GetTargetForLocation(const gfx::Point& location) = 0;
+};
+
+} // namespace ui
+
+#endif // UI_BASE_GESTURES_GESTURE_RECOGNIZER_H_
diff --git a/ui/base/gestures/gesture_recognizer_impl.cc b/ui/base/gestures/gesture_recognizer_impl.cc
new file mode 100644
index 0000000..7019581
--- /dev/null
+++ b/ui/base/gestures/gesture_recognizer_impl.cc
@@ -0,0 +1,240 @@
+// Copyright (c) 2012 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/base/gestures/gesture_recognizer_impl.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time.h"
+#include "ui/base/events.h"
+#include "ui/base/gestures/gesture_configuration.h"
+#include "ui/base/gestures/gesture_sequence.h"
+#include "ui/base/gestures/gesture_types.h"
+
+namespace {
+// This is used to pop a std::queue when returning from a function.
+class ScopedPop {
+ public:
+ explicit ScopedPop(std::queue<ui::TouchEvent*>* queue) : queue_(queue) {
+ }
+
+ ~ScopedPop() {
+ delete queue_->front();
+ queue_->pop();
+ }
+
+ private:
+ std::queue<ui::TouchEvent*>* queue_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedPop);
+};
+
+// CancelledTouchEvent mirrors a ui::TouchEvent object, except for the
+// type, which is always ET_TOUCH_CANCELLED.
+class CancelledTouchEvent : public ui::TouchEvent {
+ public:
+ explicit CancelledTouchEvent(ui::TouchEvent* real)
+ : src_event_(real) {
+ }
+
+ virtual ~CancelledTouchEvent() {
+ }
+
+ private:
+ // Overridden from ui::TouchEvent.
+ virtual ui::EventType GetEventType() const OVERRIDE {
+ return ui::ET_TOUCH_CANCELLED;
+ }
+
+ virtual gfx::Point GetLocation() const OVERRIDE {
+ return src_event_->GetLocation();
+ }
+
+ virtual int GetTouchId() const OVERRIDE {
+ return src_event_->GetTouchId();
+ }
+
+ virtual int GetEventFlags() const OVERRIDE {
+ return src_event_->GetEventFlags();
+ }
+
+ virtual base::TimeDelta GetTimestamp() const OVERRIDE {
+ return src_event_->GetTimestamp();
+ }
+
+ virtual ui::TouchEvent* Copy() const OVERRIDE {
+ return NULL;
+ }
+
+ ui::TouchEvent* src_event_;
+ DISALLOW_COPY_AND_ASSIGN(CancelledTouchEvent);
+};
+
+} // namespace
+
+namespace ui {
+
+////////////////////////////////////////////////////////////////////////////////
+// GestureRecognizerAura, public:
+
+GestureRecognizerAura::GestureRecognizerAura(GestureEventHelper* helper)
+ : helper_(helper) {
+}
+
+GestureRecognizerAura::~GestureRecognizerAura() {
+}
+
+GestureConsumer* GestureRecognizerAura::GetTargetForTouchEvent(
+ TouchEvent* event) {
+ GestureConsumer* target = touch_id_target_[event->GetTouchId()];
+ if (!target)
+ target = GetTargetForLocation(event->GetLocation());
+ return target;
+}
+
+GestureConsumer* GestureRecognizerAura::GetTargetForGestureEvent(
+ GestureEvent* event) {
+ GestureConsumer* target = NULL;
+ int touch_id = event->GetLowestTouchId();
+ target = touch_id_target_for_gestures_[touch_id];
+ return target;
+}
+
+GestureConsumer* GestureRecognizerAura::GetTargetForLocation(
+ const gfx::Point& location) {
+ const GesturePoint* closest_point = NULL;
+ int closest_distance_squared = 0;
+ std::map<GestureConsumer*, GestureSequence*>::iterator i;
+ for (i = consumer_sequence_.begin(); i != consumer_sequence_.end(); ++i) {
+ const GesturePoint* points = i->second->points();
+ for (int j = 0; j < GestureSequence::kMaxGesturePoints; ++j) {
+ if (!points[j].in_use())
+ continue;
+ gfx::Point delta =
+ points[j].last_touch_position().Subtract(location);
+ int distance = delta.x() * delta.x() + delta.y() * delta.y();
+ if ( !closest_point || distance < closest_distance_squared ) {
+ closest_point = &points[j];
+ closest_distance_squared = distance;
+ }
+ }
+ }
+
+ const int max_distance =
+ GestureConfiguration::max_separation_for_gesture_touches_in_pixels();
+
+ if (closest_distance_squared < max_distance * max_distance && closest_point)
+ return touch_id_target_[closest_point->touch_id()];
+ else
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// GestureRecognizerAura, protected:
+
+GestureSequence* GestureRecognizerAura::CreateSequence(
+ GestureEventHelper* helper) {
+ return new GestureSequence(helper);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// GestureRecognizerAura, private:
+
+GestureSequence* GestureRecognizerAura::GetGestureSequenceForConsumer(
+ GestureConsumer* consumer) {
+ GestureSequence* gesture_sequence = consumer_sequence_[consumer];
+ if (!gesture_sequence) {
+ gesture_sequence = CreateSequence(helper_);
+ consumer_sequence_[consumer] = gesture_sequence;
+ }
+ return gesture_sequence;
+}
+
+GestureSequence::Gestures* GestureRecognizerAura::ProcessTouchEventForGesture(
+ const TouchEvent& event,
+ ui::TouchStatus status,
+ GestureConsumer* target) {
+ if (event.GetEventType() == ui::ET_TOUCH_RELEASED ||
+ event.GetEventType() == ui::ET_TOUCH_CANCELLED) {
+ touch_id_target_[event.GetTouchId()] = NULL;
+ } else {
+ touch_id_target_[event.GetTouchId()] = target;
+ if (target)
+ touch_id_target_for_gestures_[event.GetTouchId()] = target;
+ }
+
+ GestureSequence* gesture_sequence = GetGestureSequenceForConsumer(target);
+ return gesture_sequence->ProcessTouchEventForGesture(event, status);
+}
+
+void GestureRecognizerAura::QueueTouchEventForGesture(GestureConsumer* consumer,
+ const TouchEvent& event) {
+ if (!event_queue_[consumer])
+ event_queue_[consumer] = new std::queue<TouchEvent*>();
+ event_queue_[consumer]->push(event.Copy());
+}
+
+GestureSequence::Gestures* GestureRecognizerAura::AdvanceTouchQueue(
+ GestureConsumer* consumer,
+ bool processed) {
+ if (!event_queue_[consumer] || event_queue_[consumer]->empty()) {
+ LOG(ERROR) << "Trying to advance an empty gesture queue for " << consumer;
+ return NULL;
+ }
+
+ ScopedPop pop(event_queue_[consumer]);
+ TouchEvent* event = event_queue_[consumer]->front();
+
+ GestureSequence* sequence = GetGestureSequenceForConsumer(consumer);
+
+ if (processed && event->GetEventType() == ui::ET_TOUCH_RELEASED) {
+ // A touch release was was processed (e.g. preventDefault()ed by a
+ // web-page), but we still need to process a touch cancel.
+ CancelledTouchEvent cancelled(event);
+ return sequence->ProcessTouchEventForGesture(cancelled,
+ ui::TOUCH_STATUS_UNKNOWN);
+ }
+
+ return sequence->ProcessTouchEventForGesture(
+ *event,
+ processed ? ui::TOUCH_STATUS_CONTINUE : ui::TOUCH_STATUS_UNKNOWN);
+}
+
+void GestureRecognizerAura::FlushTouchQueue(GestureConsumer* consumer) {
+ if (consumer_sequence_.count(consumer)) {
+ delete consumer_sequence_[consumer];
+ consumer_sequence_.erase(consumer);
+ }
+
+ if (event_queue_.count(consumer)) {
+ delete event_queue_[consumer];
+ event_queue_.erase(consumer);
+ }
+
+ int touch_id = -1;
+ std::map<int, GestureConsumer*>::iterator i;
+ for (i = touch_id_target_.begin(); i != touch_id_target_.end(); ++i) {
+ if (i->second == consumer)
+ touch_id = i->first;
+ }
+
+ if (touch_id_target_.count(touch_id))
+ touch_id_target_.erase(touch_id);
+
+ for (i = touch_id_target_for_gestures_.begin();
+ i != touch_id_target_for_gestures_.end();
+ ++i) {
+ if (i->second == consumer)
+ touch_id = i->first;
+ }
+
+ if (touch_id_target_for_gestures_.count(touch_id))
+ touch_id_target_for_gestures_.erase(touch_id);
+}
+
+// GestureRecognizer, static
+GestureRecognizer* GestureRecognizer::Create(GestureEventHelper* helper) {
+ return new GestureRecognizerAura(helper);
+}
+
+} // namespace ui
diff --git a/ui/base/gestures/gesture_recognizer_impl.h b/ui/base/gestures/gesture_recognizer_impl.h
new file mode 100644
index 0000000..e93933c
--- /dev/null
+++ b/ui/base/gestures/gesture_recognizer_impl.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2012 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.
+
+#ifndef UI_BASE_GESTURES_GESTURE_RECOGNIZER_IMPL_H_
+#define UI_BASE_GESTURES_GESTURE_RECOGNIZER_IMPL_H_
+#pragma once
+
+#include <map>
+#include <queue>
+#include <vector>
+
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/base/events.h"
+#include "ui/base/gestures/gesture_recognizer.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/point.h"
+
+namespace ui {
+class TouchEvent;
+class GestureEvent;
+class GestureSequence;
+class GestureConsumer;
+class GestureEventHelper;
+
+class UI_EXPORT GestureRecognizerAura : public GestureRecognizer {
+ public:
+ explicit GestureRecognizerAura(GestureEventHelper* helper);
+ virtual ~GestureRecognizerAura();
+
+ // Checks if this finger is already down, if so, returns the current target.
+ // Otherwise, if the finger is within
+ // GestureConfiguration::max_separation_for_gesture_touches_in_pixels
+ // of another touch, returns the target of the closest touch.
+ // If there is no nearby touch, return null.
+ virtual GestureConsumer* GetTargetForTouchEvent(TouchEvent* event) OVERRIDE;
+
+ // Returns the target of the touches the gesture is composed of.
+ virtual GestureConsumer* GetTargetForGestureEvent(
+ GestureEvent* event) OVERRIDE;
+
+ virtual GestureConsumer* GetTargetForLocation(
+ const gfx::Point& location) OVERRIDE;
+
+ protected:
+ virtual GestureSequence* CreateSequence(GestureEventHelper* helper);
+ virtual GestureSequence* GetGestureSequenceForConsumer(GestureConsumer* c);
+
+ private:
+ // Overridden from GestureRecognizer
+ virtual Gestures* ProcessTouchEventForGesture(
+ const TouchEvent& event,
+ ui::TouchStatus status,
+ GestureConsumer* target) OVERRIDE;
+ virtual void QueueTouchEventForGesture(GestureConsumer* consumer,
+ const TouchEvent& event) OVERRIDE;
+ virtual Gestures* AdvanceTouchQueue(GestureConsumer* consumer,
+ bool processed) OVERRIDE;
+ virtual void FlushTouchQueue(GestureConsumer* consumer) OVERRIDE;
+
+ typedef std::queue<TouchEvent*> TouchEventQueue;
+ std::map<GestureConsumer*, TouchEventQueue*> event_queue_;
+ std::map<GestureConsumer*, GestureSequence*> consumer_sequence_;
+
+ // Both touch_id_target_ and touch_id_target_for_gestures_
+ // map a touch-id to its target window.
+ // touch_ids are removed from touch_id_target_ on ET_TOUCH_RELEASE
+ // and ET_TOUCH_CANCEL. touch_id_target_for_gestures_ never has touch_ids
+ // removed.
+ std::map<int, GestureConsumer*> touch_id_target_;
+ std::map<int, GestureConsumer*> touch_id_target_for_gestures_;
+ GestureEventHelper* helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(GestureRecognizerAura);
+};
+
+} // namespace ui
+
+#endif // UI_BASE_GESTURES_GESTURE_RECOGNIZER_IMPL_H_
diff --git a/ui/base/gestures/gesture_sequence.cc b/ui/base/gestures/gesture_sequence.cc
new file mode 100644
index 0000000..69af3f7
--- /dev/null
+++ b/ui/base/gestures/gesture_sequence.cc
@@ -0,0 +1,572 @@
+// Copyright (c) 2012 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/base/gestures/gesture_sequence.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time.h"
+#include "ui/base/events.h"
+#include "ui/base/gestures/gesture_configuration.h"
+
+namespace ui {
+
+namespace {
+
+// ui::EventType is mapped to TouchState so it can fit into 3 bits of
+// Signature.
+enum TouchState {
+ TS_RELEASED,
+ TS_PRESSED,
+ TS_MOVED,
+ TS_STATIONARY,
+ TS_CANCELLED,
+ TS_UNKNOWN,
+};
+
+// Get equivalent TouchState from EventType |type|.
+TouchState TouchEventTypeToTouchState(ui::EventType type) {
+ switch (type) {
+ case ui::ET_TOUCH_RELEASED:
+ return TS_RELEASED;
+ case ui::ET_TOUCH_PRESSED:
+ return TS_PRESSED;
+ case ui::ET_TOUCH_MOVED:
+ return TS_MOVED;
+ case ui::ET_TOUCH_STATIONARY:
+ return TS_STATIONARY;
+ case ui::ET_TOUCH_CANCELLED:
+ return TS_CANCELLED;
+ default:
+ VLOG(1) << "Unknown Touch Event type";
+ }
+ return TS_UNKNOWN;
+}
+
+// Gesture signature types for different values of combination (GestureState,
+// touch_id, ui::EventType, touch_handled), see Signature for more info.
+//
+// Note: New addition of types should be placed as per their Signature value.
+#define G(gesture_state, id, touch_state, handled) 1 + ( \
+ (((touch_state) & 0x7) << 1) | \
+ ((handled) ? (1 << 4) : 0) | \
+ (((id) & 0xfff) << 5) | \
+ ((gesture_state) << 17))
+
+enum EdgeStateSignatureType {
+ GST_NO_GESTURE_FIRST_PRESSED =
+ G(GS_NO_GESTURE, 0, TS_PRESSED, false),
+
+ GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED =
+ G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_RELEASED, false),
+
+ GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED =
+ G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_MOVED, false),
+
+ GST_PENDING_SYNTHETIC_CLICK_FIRST_STATIONARY =
+ G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_STATIONARY, false),
+
+ GST_PENDING_SYNTHETIC_CLICK_FIRST_CANCELLED =
+ G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_CANCELLED, false),
+
+ GST_PENDING_SYNTHETIC_CLICK_SECOND_PRESSED =
+ G(GS_PENDING_SYNTHETIC_CLICK, 1, TS_PRESSED, false),
+
+ GST_SCROLL_FIRST_RELEASED =
+ G(GS_SCROLL, 0, TS_RELEASED, false),
+
+ GST_SCROLL_FIRST_MOVED =
+ G(GS_SCROLL, 0, TS_MOVED, false),
+
+ GST_SCROLL_FIRST_CANCELLED =
+ G(GS_SCROLL, 0, TS_CANCELLED, false),
+
+ GST_SCROLL_SECOND_PRESSED =
+ G(GS_SCROLL, 1, TS_PRESSED, false),
+
+ GST_PINCH_FIRST_MOVED =
+ G(GS_PINCH, 0, TS_MOVED, false),
+
+ GST_PINCH_SECOND_MOVED =
+ G(GS_PINCH, 1, TS_MOVED, false),
+
+ GST_PINCH_FIRST_RELEASED =
+ G(GS_PINCH, 0, TS_RELEASED, false),
+
+ GST_PINCH_SECOND_RELEASED =
+ G(GS_PINCH, 1, TS_RELEASED, false),
+
+ GST_PINCH_FIRST_CANCELLED =
+ G(GS_PINCH, 0, TS_CANCELLED, false),
+
+ GST_PINCH_SECOND_CANCELLED =
+ G(GS_PINCH, 1, TS_CANCELLED, false),
+};
+
+// Builds a signature. Signatures are assembled by joining together
+// multiple bits.
+// 1 LSB bit so that the computed signature is always greater than 0
+// 3 bits for the |type|.
+// 1 bit for |touch_handled|
+// 12 bits for |touch_id|
+// 15 bits for the |gesture_state|.
+EdgeStateSignatureType Signature(GestureState gesture_state,
+ unsigned int touch_id,
+ ui::EventType type,
+ bool touch_handled) {
+ CHECK((touch_id & 0xfff) == touch_id);
+ TouchState touch_state = TouchEventTypeToTouchState(type);
+ return static_cast<EdgeStateSignatureType>
+ (G(gesture_state, touch_id, touch_state, touch_handled));
+}
+#undef G
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// GestureSequence Public:
+
+GestureSequence::GestureSequence(GestureEventHelper* helper)
+ : state_(GS_NO_GESTURE),
+ flags_(0),
+ pinch_distance_start_(0.f),
+ pinch_distance_current_(0.f),
+ long_press_timer_(CreateTimer()),
+ point_count_(0),
+ helper_(helper) {
+}
+
+GestureSequence::~GestureSequence() {
+}
+
+GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture(
+ const TouchEvent& event,
+ ui::TouchStatus status) {
+ if (status != ui::TOUCH_STATUS_UNKNOWN)
+ return NULL; // The event was consumed by a touch sequence.
+
+ // Set a limit on the number of simultaneous touches in a gesture.
+ if (event.GetTouchId() >= kMaxGesturePoints)
+ return NULL;
+
+ if (event.GetEventType() == ui::ET_TOUCH_PRESSED) {
+ if (point_count_ == kMaxGesturePoints)
+ return NULL;
+ GesturePoint* new_point = &points_[event.GetTouchId()];
+ // We shouldn't be able to get two PRESSED events from the same
+ // finger without either a RELEASE or CANCEL in between.
+ DCHECK(!points_[event.GetTouchId()].in_use());
+ new_point->set_point_id(point_count_++);
+ new_point->set_touch_id(event.GetTouchId());
+ }
+
+ GestureState last_state = state_;
+
+ // NOTE: when modifying these state transitions, also update gestures.dot
+ scoped_ptr<Gestures> gestures(new Gestures());
+ GesturePoint& point = GesturePointForEvent(event);
+ point.UpdateValues(event);
+ flags_ = event.GetEventFlags();
+ const int point_id = points_[event.GetTouchId()].point_id();
+ if (point_id < 0)
+ return NULL;
+ switch (Signature(state_, point_id, event.GetEventType(), false)) {
+ case GST_NO_GESTURE_FIRST_PRESSED:
+ TouchDown(event, point, gestures.get());
+ set_state(GS_PENDING_SYNTHETIC_CLICK);
+ break;
+ case GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED:
+ if (Click(event, point, gestures.get()))
+ point.UpdateForTap();
+ set_state(GS_NO_GESTURE);
+ break;
+ case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED:
+ case GST_PENDING_SYNTHETIC_CLICK_FIRST_STATIONARY:
+ if (ScrollStart(event, point, gestures.get())) {
+ set_state(GS_SCROLL);
+ if (ScrollUpdate(event, point, gestures.get()))
+ point.UpdateForScroll();
+ }
+ break;
+ case GST_PENDING_SYNTHETIC_CLICK_FIRST_CANCELLED:
+ NoGesture(event, point, gestures.get());
+ break;
+ case GST_SCROLL_FIRST_MOVED:
+ if (scroll_type_ == ST_VERTICAL ||
+ scroll_type_ == ST_HORIZONTAL)
+ BreakRailScroll(event, point, gestures.get());
+ if (ScrollUpdate(event, point, gestures.get()))
+ point.UpdateForScroll();
+ break;
+ case GST_SCROLL_FIRST_RELEASED:
+ case GST_SCROLL_FIRST_CANCELLED:
+ ScrollEnd(event, point, gestures.get());
+ set_state(GS_NO_GESTURE);
+ break;
+ case GST_SCROLL_SECOND_PRESSED:
+ case GST_PENDING_SYNTHETIC_CLICK_SECOND_PRESSED:
+ PinchStart(event, point, gestures.get());
+ set_state(GS_PINCH);
+ break;
+ case GST_PINCH_FIRST_MOVED:
+ case GST_PINCH_SECOND_MOVED:
+ if (PinchUpdate(event, point, gestures.get())) {
+ GetPointByPointId(0)->UpdateForScroll();
+ GetPointByPointId(1)->UpdateForScroll();
+ }
+ break;
+ case GST_PINCH_FIRST_RELEASED:
+ case GST_PINCH_SECOND_RELEASED:
+ case GST_PINCH_FIRST_CANCELLED:
+ case GST_PINCH_SECOND_CANCELLED:
+ PinchEnd(event, point, gestures.get());
+
+ // Once pinch ends, it should still be possible to scroll with the
+ // remaining finger on the screen.
+ scroll_type_ = ST_FREE;
+ set_state(GS_SCROLL);
+ break;
+ }
+
+ if (state_ != last_state)
+ VLOG(4) << "Gesture Sequence"
+ << " State: " << state_
+ << " touch id: " << event.GetTouchId();
+
+ if (last_state == GS_PENDING_SYNTHETIC_CLICK && state_ != last_state)
+ long_press_timer_->Stop();
+
+ // The set of point_ids must be contiguous and include 0.
+ // When a touch point is released, all points with ids greater than the
+ // released point must have their ids decremented, or the set of point_ids
+ // could end up with gaps.
+ if (event.GetEventType() == ui::ET_TOUCH_RELEASED ||
+ event.GetEventType() == ui::ET_TOUCH_CANCELLED) {
+ GesturePoint& old_point = points_[event.GetTouchId()];
+ for (int i = 0; i < kMaxGesturePoints; ++i) {
+ GesturePoint& point = points_[i];
+ if (point.point_id() > old_point.point_id())
+ point.set_point_id(point.point_id() - 1);
+ }
+ old_point.Reset();
+ --point_count_;
+ }
+
+ return gestures.release();
+}
+
+void GestureSequence::Reset() {
+ set_state(GS_NO_GESTURE);
+ for (int i = 0; i < kMaxGesturePoints; ++i)
+ points_[i].Reset();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// GestureSequence Protected:
+
+base::OneShotTimer<GestureSequence>* GestureSequence::CreateTimer() {
+ return new base::OneShotTimer<GestureSequence>();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// GestureSequence Private:
+
+GesturePoint& GestureSequence::GesturePointForEvent(
+ const TouchEvent& event) {
+ return points_[event.GetTouchId()];
+}
+
+GesturePoint* GestureSequence::GetPointByPointId(int point_id) {
+ DCHECK(0 <= point_id && point_id < kMaxGesturePoints);
+ for (int i = 0; i < kMaxGesturePoints; ++i) {
+ GesturePoint& point = points_[i];
+ if (point.in_use() && point.point_id() == point_id)
+ return &point;
+ }
+ NOTREACHED();
+ return NULL;
+}
+
+void GestureSequence::AppendTapDownGestureEvent(const GesturePoint& point,
+ Gestures* gestures) {
+ gestures->push_back((helper_->CreateGestureEvent(
+ ui::ET_GESTURE_TAP_DOWN,
+ point.first_touch_position(),
+ flags_,
+ base::Time::FromDoubleT(point.last_touch_time()),
+ 0.f, 0.f, 1 << point.touch_id())));
+}
+
+void GestureSequence::AppendClickGestureEvent(const GesturePoint& point,
+ Gestures* gestures) {
+ gestures->push_back((helper_->CreateGestureEvent(
+ ui::ET_GESTURE_TAP,
+ point.first_touch_position(),
+ flags_,
+ base::Time::FromDoubleT(point.last_touch_time()),
+ 0.f, 0.f, 1 << point.touch_id())));
+}
+
+void GestureSequence::AppendDoubleClickGestureEvent(const GesturePoint& point,
+ Gestures* gestures) {
+ gestures->push_back((helper_->CreateGestureEvent(
+ ui::ET_GESTURE_DOUBLE_TAP,
+ point.first_touch_position(),
+ flags_,
+ base::Time::FromDoubleT(point.last_touch_time()),
+ 0.f, 0.f, 1 << point.touch_id())));
+}
+
+void GestureSequence::AppendScrollGestureBegin(const GesturePoint& point,
+ const gfx::Point& location,
+ Gestures* gestures) {
+ gestures->push_back((helper_->CreateGestureEvent(
+ ui::ET_GESTURE_SCROLL_BEGIN,
+ location,
+ flags_,
+ base::Time::FromDoubleT(point.last_touch_time()),
+ 0.f, 0.f, 1 << point.touch_id())));
+}
+
+void GestureSequence::AppendScrollGestureEnd(const GesturePoint& point,
+ const gfx::Point& location,
+ Gestures* gestures,
+ float x_velocity,
+ float y_velocity) {
+ float railed_x_velocity = x_velocity;
+ float railed_y_velocity = y_velocity;
+
+ if (scroll_type_ == ST_HORIZONTAL)
+ railed_y_velocity = 0;
+ else if (scroll_type_ == ST_VERTICAL)
+ railed_x_velocity = 0;
+
+ gestures->push_back((helper_->CreateGestureEvent(
+ ui::ET_GESTURE_SCROLL_END,
+ location,
+ flags_,
+ base::Time::FromDoubleT(point.last_touch_time()),
+ railed_x_velocity, railed_y_velocity, 1 << point.touch_id())));
+}
+
+void GestureSequence::AppendScrollGestureUpdate(const GesturePoint& point,
+ const gfx::Point& location,
+ Gestures* gestures) {
+ int dx = point.x_delta();
+ int dy = point.y_delta();
+
+ if (scroll_type_ == ST_HORIZONTAL)
+ dy = 0;
+ else if (scroll_type_ == ST_VERTICAL)
+ dx = 0;
+
+ gestures->push_back((helper_->CreateGestureEvent(
+ ui::ET_GESTURE_SCROLL_UPDATE,
+ location,
+ flags_,
+ base::Time::FromDoubleT(point.last_touch_time()),
+ dx, dy, 1 << point.touch_id())));
+}
+
+void GestureSequence::AppendPinchGestureBegin(const GesturePoint& p1,
+ const GesturePoint& p2,
+ Gestures* gestures) {
+ gfx::Point center = p1.last_touch_position().Middle(p2.last_touch_position());
+ gestures->push_back((helper_->CreateGestureEvent(
+ ui::ET_GESTURE_PINCH_BEGIN,
+ center,
+ flags_,
+ base::Time::FromDoubleT(p1.last_touch_time()),
+ 0.f, 0.f, 1 << p1.touch_id() | 1 << p2.touch_id())));
+}
+
+void GestureSequence::AppendPinchGestureEnd(const GesturePoint& p1,
+ const GesturePoint& p2,
+ float scale,
+ Gestures* gestures) {
+ gfx::Point center = p1.last_touch_position().Middle(p2.last_touch_position());
+ gestures->push_back((helper_->CreateGestureEvent(
+ ui::ET_GESTURE_PINCH_END,
+ center,
+ flags_,
+ base::Time::FromDoubleT(p1.last_touch_time()),
+ scale, 0.f, 1 << p1.touch_id() | 1 << p2.touch_id())));
+}
+
+void GestureSequence::AppendPinchGestureUpdate(const GesturePoint& p1,
+ const GesturePoint& p2,
+ float scale,
+ Gestures* gestures) {
+ // TODO(sad): Compute rotation and include it in delta_y.
+ // http://crbug.com/113145
+ gfx::Point center = p1.last_touch_position().Middle(p2.last_touch_position());
+ gestures->push_back((helper_->CreateGestureEvent(
+ ui::ET_GESTURE_PINCH_UPDATE,
+ center,
+ flags_,
+ base::Time::FromDoubleT(p1.last_touch_time()),
+ scale, 0.f, 1 << p1.touch_id() | 1 << p2.touch_id())));
+}
+
+bool GestureSequence::Click(const TouchEvent& event,
+ const GesturePoint& point, Gestures* gestures) {
+ DCHECK(state_ == GS_PENDING_SYNTHETIC_CLICK);
+ if (point.IsInClickWindow(event)) {
+ AppendClickGestureEvent(point, gestures);
+ if (point.IsInDoubleClickWindow(event))
+ AppendDoubleClickGestureEvent(point, gestures);
+ return true;
+ }
+ return false;
+}
+
+bool GestureSequence::ScrollStart(const TouchEvent& event,
+ GesturePoint& point, Gestures* gestures) {
+ DCHECK(state_ == GS_PENDING_SYNTHETIC_CLICK);
+ if (point.IsInClickWindow(event) ||
+ !point.IsInScrollWindow(event) ||
+ !point.HasEnoughDataToEstablishRail())
+ return false;
+ AppendScrollGestureBegin(point, point.first_touch_position(), gestures);
+ if (point.IsInHorizontalRailWindow())
+ scroll_type_ = ST_HORIZONTAL;
+ else if (point.IsInVerticalRailWindow())
+ scroll_type_ = ST_VERTICAL;
+ else
+ scroll_type_ = ST_FREE;
+ return true;
+}
+
+void GestureSequence::BreakRailScroll(const TouchEvent& event,
+ GesturePoint& point, Gestures* gestures) {
+ DCHECK(state_ == GS_SCROLL);
+ if (scroll_type_ == ST_HORIZONTAL &&
+ point.BreaksHorizontalRail())
+ scroll_type_ = ST_FREE;
+ else if (scroll_type_ == ST_VERTICAL &&
+ point.BreaksVerticalRail())
+ scroll_type_ = ST_FREE;
+}
+
+bool GestureSequence::ScrollUpdate(const TouchEvent& event,
+ const GesturePoint& point, Gestures* gestures) {
+ DCHECK(state_ == GS_SCROLL);
+ if (!point.DidScroll(event, 0))
+ return false;
+ AppendScrollGestureUpdate(point, point.last_touch_position(), gestures);
+ return true;
+}
+
+bool GestureSequence::NoGesture(const TouchEvent&,
+ const GesturePoint& point, Gestures*) {
+ Reset();
+ return false;
+}
+
+bool GestureSequence::TouchDown(const TouchEvent& event,
+ const GesturePoint& point, Gestures* gestures) {
+ DCHECK(state_ == GS_NO_GESTURE);
+ AppendTapDownGestureEvent(point, gestures);
+ long_press_timer_->Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(
+ GestureConfiguration::long_press_time_in_seconds() * 1000),
+ this,
+ &GestureSequence::AppendLongPressGestureEvent);
+ return true;
+}
+
+void GestureSequence::AppendLongPressGestureEvent() {
+ const GesturePoint* point = GetPointByPointId(0);
+ scoped_ptr<GestureEvent> gesture(helper_->CreateGestureEvent(
+ ui::ET_GESTURE_LONG_PRESS,
+ point->first_touch_position(),
+ flags_,
+ base::Time::FromDoubleT(point->last_touch_time()),
+ point->point_id(), 0.f, 1 << point->touch_id()));
+ helper_->DispatchLongPressGestureEvent(gesture.get());
+}
+
+bool GestureSequence::ScrollEnd(const TouchEvent& event,
+ GesturePoint& point, Gestures* gestures) {
+ DCHECK(state_ == GS_SCROLL);
+ if (point.IsInFlickWindow(event)) {
+ AppendScrollGestureEnd(point, point.last_touch_position(), gestures,
+ point.XVelocity(), point.YVelocity());
+ } else {
+ AppendScrollGestureEnd(point, point.last_touch_position(), gestures,
+ 0.f, 0.f);
+ }
+ return true;
+}
+
+bool GestureSequence::PinchStart(const TouchEvent& event,
+ const GesturePoint& point, Gestures* gestures) {
+ DCHECK(state_ == GS_SCROLL ||
+ state_ == GS_PENDING_SYNTHETIC_CLICK);
+ AppendTapDownGestureEvent(point, gestures);
+
+ const GesturePoint* point1 = GetPointByPointId(0);
+ const GesturePoint* point2 = GetPointByPointId(1);
+
+ pinch_distance_current_ = point1->Distance(*point2);
+ pinch_distance_start_ = pinch_distance_current_;
+ AppendPinchGestureBegin(*point1, *point2, gestures);
+
+ if (state_ == GS_PENDING_SYNTHETIC_CLICK) {
+ gfx::Point center = point1->last_touch_position().Middle(
+ point2->last_touch_position());
+ AppendScrollGestureBegin(point, center, gestures);
+ }
+
+ return true;
+}
+
+bool GestureSequence::PinchUpdate(const TouchEvent& event,
+ const GesturePoint& point, Gestures* gestures) {
+ DCHECK(state_ == GS_PINCH);
+
+ const GesturePoint* point1 = GetPointByPointId(0);
+ const GesturePoint* point2 = GetPointByPointId(1);
+
+ float distance = point1->Distance(*point2);
+ if (abs(distance - pinch_distance_current_) <
+ GestureConfiguration::min_pinch_update_distance_in_pixels()) {
+ // The fingers didn't move towards each other, or away from each other,
+ // enough to constitute a pinch. But perhaps they moved enough in the same
+ // direction to do a two-finger scroll.
+ if (!point1->DidScroll(event,
+ GestureConfiguration::min_distance_for_pinch_scroll_in_pixels()) ||
+ !point2->DidScroll(event,
+ GestureConfiguration::min_distance_for_pinch_scroll_in_pixels()))
+ return false;
+
+ gfx::Point center = point1->last_touch_position().Middle(
+ point2->last_touch_position());
+ AppendScrollGestureUpdate(point, center, gestures);
+ } else {
+ AppendPinchGestureUpdate(*point1, *point2,
+ distance / pinch_distance_current_, gestures);
+ pinch_distance_current_ = distance;
+ }
+ return true;
+}
+
+bool GestureSequence::PinchEnd(const TouchEvent& event,
+ const GesturePoint& point, Gestures* gestures) {
+ DCHECK(state_ == GS_PINCH);
+
+ const GesturePoint* point1 = GetPointByPointId(0);
+ const GesturePoint* point2 = GetPointByPointId(1);
+
+ float distance = point1->Distance(*point2);
+ AppendPinchGestureEnd(*point1, *point2,
+ distance / pinch_distance_start_, gestures);
+
+ pinch_distance_start_ = 0;
+ pinch_distance_current_ = 0;
+ return true;
+}
+
+} // namespace ui
diff --git a/ui/base/gestures/gesture_sequence.h b/ui/base/gestures/gesture_sequence.h
new file mode 100644
index 0000000..de829d5
--- /dev/null
+++ b/ui/base/gestures/gesture_sequence.h
@@ -0,0 +1,163 @@
+// Copyright (c) 2012 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.
+
+#ifndef UI_BASE_GESTURES_GESTURE_SEQUENCE_H_
+#define UI_BASE_GESTURES_GESTURE_SEQUENCE_H_
+#pragma once
+
+#include "base/timer.h"
+#include "ui/base/events.h"
+#include "ui/base/gestures/gesture_point.h"
+#include "ui/base/gestures/gesture_recognizer.h"
+
+namespace ui {
+class TouchEvent;
+class GestureEvent;
+
+// Gesture state.
+enum GestureState {
+ GS_NO_GESTURE,
+ GS_PENDING_SYNTHETIC_CLICK,
+ GS_SCROLL,
+ GS_PINCH
+};
+
+enum ScrollType {
+ ST_FREE,
+ ST_HORIZONTAL,
+ ST_VERTICAL,
+};
+
+// A GestureSequence recognizes gestures from touch sequences.
+class UI_EXPORT GestureSequence {
+ public:
+ explicit GestureSequence(GestureEventHelper* consumer);
+ virtual ~GestureSequence();
+
+ typedef GestureRecognizer::Gestures Gestures;
+
+ // Invoked for each touch event that could contribute to the current gesture.
+ // Returns list of zero or more GestureEvents identified after processing
+ // TouchEvent.
+ // Caller would be responsible for freeing up Gestures.
+ virtual Gestures* ProcessTouchEventForGesture(const TouchEvent& event,
+ ui::TouchStatus status);
+ const GesturePoint* points() const { return points_; }
+ int point_count() const { return point_count_; }
+
+ // Maximum number of points in a single gesture.
+ static const int kMaxGesturePoints = 12;
+
+ protected:
+ virtual base::OneShotTimer<GestureSequence>* CreateTimer();
+ base::OneShotTimer<GestureSequence>* long_press_timer() {
+ return long_press_timer_.get();
+ }
+
+ private:
+ void Reset();
+
+ GesturePoint& GesturePointForEvent(const TouchEvent& event);
+
+ // Do a linear scan through points_ to find the GesturePoint
+ // with id |point_id|.
+ GesturePoint* GetPointByPointId(int point_id);
+
+ // Functions to be called to add GestureEvents, after successful recognition.
+
+ // Tap gestures.
+ void AppendTapDownGestureEvent(const GesturePoint& point, Gestures* gestures);
+ void AppendClickGestureEvent(const GesturePoint& point, Gestures* gestures);
+ void AppendDoubleClickGestureEvent(const GesturePoint& point,
+ Gestures* gestures);
+ void AppendLongPressGestureEvent();
+
+ // Scroll gestures.
+ void AppendScrollGestureBegin(const GesturePoint& point,
+ const gfx::Point& location,
+ Gestures* gestures);
+ void AppendScrollGestureEnd(const GesturePoint& point,
+ const gfx::Point& location,
+ Gestures* gestures,
+ float x_velocity,
+ float y_velocity);
+ void AppendScrollGestureUpdate(const GesturePoint& point,
+ const gfx::Point& location,
+ Gestures* gestures);
+
+ // Pinch gestures.
+ void AppendPinchGestureBegin(const GesturePoint& p1,
+ const GesturePoint& p2,
+ Gestures* gestures);
+ void AppendPinchGestureEnd(const GesturePoint& p1,
+ const GesturePoint& p2,
+ float scale,
+ Gestures* gestures);
+ void AppendPinchGestureUpdate(const GesturePoint& p1,
+ const GesturePoint& p2,
+ float scale,
+ Gestures* gestures);
+
+ void set_state(const GestureState state ) { state_ = state; }
+
+ // Various GestureTransitionFunctions for a signature.
+ // There is, 1:many mapping from GestureTransitionFunction to Signature
+ // But a Signature have only one GestureTransitionFunction.
+ bool Click(const TouchEvent& event,
+ const GesturePoint& point,
+ Gestures* gestures);
+ bool ScrollStart(const TouchEvent& event,
+ GesturePoint& point,
+ Gestures* gestures);
+ void BreakRailScroll(const TouchEvent& event,
+ GesturePoint& point,
+ Gestures* gestures);
+ bool ScrollUpdate(const TouchEvent& event,
+ const GesturePoint& point,
+ Gestures* gestures);
+ bool NoGesture(const TouchEvent& event,
+ const GesturePoint& point,
+ Gestures* gestures);
+ bool TouchDown(const TouchEvent& event,
+ const GesturePoint& point,
+ Gestures* gestures);
+ bool ScrollEnd(const TouchEvent& event,
+ GesturePoint& point,
+ Gestures* gestures);
+ bool PinchStart(const TouchEvent& event,
+ const GesturePoint& point,
+ Gestures* gestures);
+ bool PinchUpdate(const TouchEvent& event,
+ const GesturePoint& point,
+ Gestures* gestures);
+ bool PinchEnd(const TouchEvent& event,
+ const GesturePoint& point,
+ Gestures* gestures);
+
+ // Current state of gesture recognizer.
+ GestureState state_;
+
+ // ui::EventFlags.
+ int flags_;
+
+ // The distance between the two points at PINCH_START.
+ float pinch_distance_start_;
+
+ // This distance is updated after each PINCH_UPDATE.
+ float pinch_distance_current_;
+
+ ScrollType scroll_type_;
+ scoped_ptr<base::OneShotTimer<GestureSequence> > long_press_timer_;
+
+ GesturePoint points_[kMaxGesturePoints];
+ int point_count_;
+
+ GestureEventHelper* helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(GestureSequence);
+};
+
+} // namespace ui
+
+#endif // UI_BASE_GESTURES_GESTURE_SEQUENCE_H_
diff --git a/ui/base/gestures/gesture_types.h b/ui/base/gestures/gesture_types.h
new file mode 100644
index 0000000..27e04d8
--- /dev/null
+++ b/ui/base/gestures/gesture_types.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2012 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.
+
+#ifndef UI_BASE_GESTURES_GESTURE_TYPES_H_
+#define UI_BASE_GESTURES_GESTURE_TYPES_H_
+#pragma once
+
+#include "base/time.h"
+#include "ui/base/events.h"
+
+namespace ui {
+
+// An abstract type to represent touch-events. The gesture-recognizer uses this
+// interface to communicate with the touch-events.
+class UI_EXPORT TouchEvent {
+ public:
+ virtual ~TouchEvent() {}
+
+ virtual EventType GetEventType() const = 0;
+ virtual gfx::Point GetLocation() const = 0;
+ virtual int GetTouchId() const = 0;
+ virtual int GetEventFlags() const = 0;
+ virtual base::TimeDelta GetTimestamp() const = 0;
+
+ // Returns a copy of this touch event. Used when queueing events for
+ // asynchronous gesture recognition.
+ virtual TouchEvent* Copy() const = 0;
+};
+
+// An abstract type to represent gesture-events.
+class UI_EXPORT GestureEvent {
+ public:
+ virtual ~GestureEvent() {}
+
+ // A gesture event can have multiple touches. This function should return the
+ // lowest ID of the touches in this gesture.
+ virtual int GetLowestTouchId() const = 0;
+};
+
+// An abstract type for consumers of gesture-events created by the
+// gesture-recognizer.
+class UI_EXPORT GestureConsumer {
+};
+
+// GestureEventHelper creates implementation-specific gesture events and
+// can dispatch them.
+class UI_EXPORT GestureEventHelper {
+ public:
+ // |flags| is ui::EventFlags. The meaning of |param_first| and |param_second|
+ // depends on the specific gesture type (|type|).
+ virtual GestureEvent* CreateGestureEvent(EventType type,
+ const gfx::Point& location,
+ int flags,
+ const base::Time time,
+ float param_first,
+ float param_second,
+ unsigned int touch_id_bitfield) = 0;
+
+ virtual bool DispatchLongPressGestureEvent(GestureEvent* event) = 0;
+};
+
+} // namespace ui
+
+#endif // UI_BASE_GESTURES_GESTURE_TYPES_H_
diff --git a/ui/base/gestures/gestures.dot b/ui/base/gestures/gestures.dot
new file mode 100644
index 0000000..a095575
--- /dev/null
+++ b/ui/base/gestures/gestures.dot
@@ -0,0 +1,32 @@
+// A diagram of the state machine found in gesture_sequence.cc
+// To generate a pdf:
+// dot -Tpdf -ooutput.pdf gestures.dot
+//
+// If you alter this diagram, please update:
+// sites.google.com/a/chromium.org/dev/developers/design-documents/aura/gesture-recognizer
+
+digraph G {
+ratio = 1;
+
+legend[label = "{ \
+M : Move \l\
+D : Down \l\
+S : Stationary \l\
+C : Cancel \l\
+R : Release \l}"
+shape = record]
+
+subgraph none_pending {
+GS_NO_GESTURE -> GS_PENDING_SYNTHETIC_CLICK [label= "D0"];
+GS_PENDING_SYNTHETIC_CLICK -> GS_SCROLL [label= "M0\n S0"];
+GS_PENDING_SYNTHETIC_CLICK -> GS_PENDING_SYNTHETIC_CLICK [label= "M0\n S0"];
+GS_PENDING_SYNTHETIC_CLICK -> GS_NO_GESTURE [label= "C0\n R0"];
+}
+
+GS_SCROLL -> GS_SCROLL [label= "M0"];
+GS_SCROLL -> GS_NO_GESTURE [label= "C0\n R0\n"];
+GS_PENDING_SYNTHETIC_CLICK -> GS_PINCH [label= "D1"];
+GS_SCROLL -> GS_PINCH [label= "D1"];
+GS_PINCH -> GS_PINCH [label= "M0\n M1"];
+GS_PINCH -> GS_SCROLL [label= "C0\n R0\n C1\n R1"];
+}
diff --git a/ui/base/gestures/velocity_calculator.cc b/ui/base/gestures/velocity_calculator.cc
new file mode 100644
index 0000000..ca862cd
--- /dev/null
+++ b/ui/base/gestures/velocity_calculator.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 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/base/gestures/velocity_calculator.h"
+
+namespace ui {
+
+VelocityCalculator::VelocityCalculator(int buffer_size)
+ : buffer_(new Point[buffer_size]) ,
+ index_(0),
+ num_valid_entries_(0),
+ buffer_size_(buffer_size),
+ x_velocity_(0),
+ y_velocity_(0),
+ velocities_stale_(false) {
+}
+
+VelocityCalculator::~VelocityCalculator() {}
+
+float VelocityCalculator::XVelocity() {
+ if (velocities_stale_)
+ UpdateVelocity();
+ return x_velocity_;
+}
+
+float VelocityCalculator::YVelocity() {
+ if (velocities_stale_)
+ UpdateVelocity();
+ return y_velocity_;
+}
+
+void VelocityCalculator::PointSeen(int x, int y, int64 time) {
+ buffer_[index_].x = x;
+ buffer_[index_].y = y;
+ buffer_[index_].time = time;
+
+ index_ = (index_ + 1) % buffer_size_;
+ if (num_valid_entries_ < buffer_size_)
+ ++num_valid_entries_;
+
+ velocities_stale_ = true;
+}
+
+float VelocityCalculator::VelocitySquared() {
+ if (velocities_stale_)
+ UpdateVelocity();
+ return x_velocity_ * x_velocity_ + y_velocity_ * y_velocity_;
+}
+
+void VelocityCalculator::UpdateVelocity() {
+ // We don't have enough data to make a good estimate of the velocity.
+ if (num_valid_entries_ < buffer_size_)
+ return;
+
+ // Where A_i = A[i] - mean(A)
+ // x velocity = sum_i(x_i * t_i) / sum_i(t_i * t_i)
+ // This is an Ordinary Least Squares Regression.
+
+ float mean_x = 0;
+ float mean_y = 0;
+ int64 mean_time = 0;
+
+ for (size_t i = 0; i < buffer_size_; ++i) {
+ mean_x += buffer_[i].x;
+ mean_y += buffer_[i].y;
+ mean_time += buffer_[i].time;
+ }
+
+ // Minimize number of divides.
+ const float buffer_size_i = 1.0f / buffer_size_;
+
+ mean_x *= buffer_size_i;
+ mean_y *= buffer_size_i;
+
+ // The loss in accuracy due to rounding is insignificant compared to
+ // the error due to the resolution of the timer.
+ // Use integer division to avoid the cast to double, which would cause
+ // VelocityCalculatorTest.IsAccurateWithLargeTimes to fail.
+ mean_time /= buffer_size_;
+
+ float xt = 0; // sum_i(x_i * t_i)
+ float yt = 0; // sum_i(y_i * t_i)
+ int64 tt = 0; // sum_i(t_i * t_i)
+
+ int64 t_i;
+
+ for (size_t i = 0; i < buffer_size_; ++i) {
+ t_i = (buffer_[i].time - mean_time);
+ xt += (buffer_[i].x - mean_x) * t_i;
+ yt += (buffer_[i].y - mean_y) * t_i;
+ tt += t_i * t_i;
+ }
+
+ // Convert time from microseconds to seconds.
+ x_velocity_ = xt / (tt / 1000000.0f);
+ y_velocity_ = yt / (tt / 1000000.0f);
+ velocities_stale_ = false;
+}
+
+void VelocityCalculator::ClearHistory() {
+ index_ = 0;
+ num_valid_entries_ = 0;
+ x_velocity_ = 0;
+ y_velocity_ = 0;
+ velocities_stale_ = false;
+}
+
+} // namespace ui
diff --git a/ui/base/gestures/velocity_calculator.h b/ui/base/gestures/velocity_calculator.h
new file mode 100644
index 0000000..1029af3
--- /dev/null
+++ b/ui/base/gestures/velocity_calculator.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 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.
+
+#ifndef UI_BASE_GESTURES_VELOCITY_CALCULATOR_H_
+#define UI_BASE_GESTURES_VELOCITY_CALCULATOR_H_
+#pragma once
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/base/ui_export.h"
+
+namespace ui {
+
+class UI_EXPORT VelocityCalculator {
+ public:
+ explicit VelocityCalculator(int bufferSize);
+ ~VelocityCalculator();
+ void PointSeen(int x, int y, int64 time);
+ float XVelocity();
+ float YVelocity();
+ float VelocitySquared();
+ void ClearHistory();
+
+ private:
+ struct Point {
+ int x;
+ int y;
+ int64 time;
+ };
+
+ void UpdateVelocity();
+
+ typedef scoped_array<Point> HistoryBuffer;
+ HistoryBuffer buffer_;
+
+ // index_ points directly after the last point added.
+ int index_;
+ size_t num_valid_entries_;
+ const size_t buffer_size_;
+ float x_velocity_;
+ float y_velocity_;
+ bool velocities_stale_;
+ DISALLOW_COPY_AND_ASSIGN(VelocityCalculator);
+};
+
+} // namespace ui
+
+#endif // UI_BASE_GESTURES_VELOCITY_CALCULATOR_H_
diff --git a/ui/base/gestures/velocity_calculator_unittest.cc b/ui/base/gestures/velocity_calculator_unittest.cc
new file mode 100644
index 0000000..a6d0c46
--- /dev/null
+++ b/ui/base/gestures/velocity_calculator_unittest.cc
@@ -0,0 +1,143 @@
+// Copyright (c) 2012 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 "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/gestures/velocity_calculator.h"
+
+namespace ui {
+namespace test {
+
+namespace {
+
+static void AddPoints(VelocityCalculator* velocity_calculator,
+ float x_increment,
+ float y_increment,
+ float time_increment_seconds,
+ int num_points) {
+ float x = 0;
+ float y = 0;
+ double time = 0;
+
+ for (int i = 0; i < num_points; ++i) {
+ velocity_calculator->PointSeen(x, y, time);
+ x += x_increment;
+ y += y_increment;
+ time += time_increment_seconds * 1000000;
+ }
+}
+
+} // namespace
+
+// Test that the velocity returned is reasonable
+TEST(VelocityCalculatorTest, ReturnsReasonableVelocity) {
+ VelocityCalculator velocity_calculator(5);
+ AddPoints(&velocity_calculator, 10, -10, 1, 7);
+
+ EXPECT_GT(velocity_calculator.XVelocity(), 9.9);
+ EXPECT_LT(velocity_calculator.XVelocity(), 10.1);
+ EXPECT_GT(velocity_calculator.YVelocity(), -10.1);
+ EXPECT_LT(velocity_calculator.YVelocity(), -9.9);
+
+ velocity_calculator.PointSeen(9, -11, 5500000);
+ velocity_calculator.PointSeen(21, -19, 6000000);
+ velocity_calculator.PointSeen(30, -32, 6500000);
+ velocity_calculator.PointSeen(38, -40, 7000000);
+ velocity_calculator.PointSeen(50, -51, 7500000);
+
+ EXPECT_GT(velocity_calculator.XVelocity(), 19);
+ EXPECT_LT(velocity_calculator.XVelocity(), 21);
+ EXPECT_GT(velocity_calculator.YVelocity(), -21);
+ EXPECT_LT(velocity_calculator.YVelocity(), -19);
+
+ // Significantly larger difference in position
+ velocity_calculator.PointSeen(70, -70, 8000000);
+
+ EXPECT_GT(velocity_calculator.XVelocity(), 20);
+ EXPECT_LT(velocity_calculator.XVelocity(), 25);
+ EXPECT_GT(velocity_calculator.YVelocity(), -25);
+ EXPECT_LT(velocity_calculator.YVelocity(), -20);
+}
+
+TEST(VelocityCalculatorTest, IsAccurateWithLargeTimes) {
+ VelocityCalculator velocity_calculator(5);
+ int64 start_time = 0;
+ velocity_calculator.PointSeen(9, -11, start_time);
+ velocity_calculator.PointSeen(21, -19, start_time + 8);
+ velocity_calculator.PointSeen(30, -32, start_time + 16);
+ velocity_calculator.PointSeen(38, -40, start_time + 24);
+ velocity_calculator.PointSeen(50, -51, start_time + 32);
+
+ EXPECT_GT(velocity_calculator.XVelocity(), 1230000);
+ EXPECT_LT(velocity_calculator.XVelocity(), 1260000);
+ EXPECT_GT(velocity_calculator.YVelocity(), -1270000);
+ EXPECT_LT(velocity_calculator.YVelocity(), -1240000);
+
+ start_time = GG_LONGLONG(1223372036800000000);
+ velocity_calculator.PointSeen(9, -11, start_time);
+ velocity_calculator.PointSeen(21, -19, start_time + 8);
+ velocity_calculator.PointSeen(30, -32, start_time + 16);
+ velocity_calculator.PointSeen(38, -40, start_time + 24);
+ velocity_calculator.PointSeen(50, -51, start_time + 32);
+
+ EXPECT_GT(velocity_calculator.XVelocity(), 1230000);
+ EXPECT_LT(velocity_calculator.XVelocity(), 1260000);
+ EXPECT_GT(velocity_calculator.YVelocity(), -1270000);
+ EXPECT_LT(velocity_calculator.YVelocity(), -124000);
+}
+
+// Check that the velocity returned is 0 if the velocity calculator
+// doesn't have enough data
+TEST(VelocityCalculatorTest, RequiresEnoughData) {
+ VelocityCalculator velocity_calculator(5);
+ EXPECT_EQ(velocity_calculator.XVelocity(), 0);
+ EXPECT_EQ(velocity_calculator.YVelocity(), 0);
+
+ AddPoints(&velocity_calculator, 10, 10, 1, 4);
+
+ // We've only seen 4 points, the buffer size is 5
+ // Since the buffer isn't full, return 0
+ EXPECT_EQ(velocity_calculator.XVelocity(), 0);
+ EXPECT_EQ(velocity_calculator.YVelocity(), 0);
+
+ AddPoints(&velocity_calculator, 10, 10, 1, 1);
+
+ EXPECT_GT(velocity_calculator.XVelocity(), 9.9);
+ EXPECT_GT(velocity_calculator.YVelocity(), 9.9);
+}
+
+// Ensures ClearHistory behaves correctly
+TEST(VelocityCalculatorTest, ClearsHistory) {
+ VelocityCalculator velocity_calculator(5);
+ AddPoints(&velocity_calculator, 10, -10, 1, 7);
+
+ EXPECT_GT(velocity_calculator.XVelocity(), 9.9);
+ EXPECT_LT(velocity_calculator.XVelocity(), 10.1);
+ EXPECT_GT(velocity_calculator.YVelocity(), -10.1);
+ EXPECT_LT(velocity_calculator.YVelocity(), -9.9);
+
+ velocity_calculator.ClearHistory();
+
+ EXPECT_EQ(velocity_calculator.XVelocity(), 0);
+ EXPECT_EQ(velocity_calculator.YVelocity(), 0);
+}
+
+// Ensure data older than the buffer size is ignored
+TEST(VelocityCalculatorTest, IgnoresOldData) {
+ VelocityCalculator velocity_calculator(5);
+ AddPoints(&velocity_calculator, 10, -10, 1, 7);
+
+ EXPECT_GT(velocity_calculator.XVelocity(), 9.9);
+ EXPECT_LT(velocity_calculator.XVelocity(), 10.1);
+ EXPECT_GT(velocity_calculator.YVelocity(), -10.1);
+ EXPECT_LT(velocity_calculator.YVelocity(), -9.9);
+
+ AddPoints(&velocity_calculator, 0, 0, 1, 5);
+
+ EXPECT_EQ(velocity_calculator.XVelocity(), 0);
+ EXPECT_EQ(velocity_calculator.YVelocity(), 0);
+}
+
+} // namespace test
+} // namespace ui