diff options
author | sadrul@chromium.org <sadrul@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-05 22:14:49 +0000 |
---|---|---|
committer | sadrul@chromium.org <sadrul@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-05 22:14:49 +0000 |
commit | 81675c914ddcaaf9f8f8ff2a8db1c5783223fd49 (patch) | |
tree | eccb226b79f2bbb98d4ec0514db5fede5931654d /ui/base | |
parent | 19ce5c5150f0900e0365720bb2c9ccde589b07b7 (diff) | |
download | chromium_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/OWNERS | 3 | ||||
-rw-r--r-- | ui/base/gestures/gesture_configuration.cc | 33 | ||||
-rw-r--r-- | ui/base/gestures/gesture_configuration.h | 131 | ||||
-rw-r--r-- | ui/base/gestures/gesture_point.cc | 171 | ||||
-rw-r--r-- | ui/base/gestures/gesture_point.h | 108 | ||||
-rw-r--r-- | ui/base/gestures/gesture_recognizer.h | 67 | ||||
-rw-r--r-- | ui/base/gestures/gesture_recognizer_impl.cc | 240 | ||||
-rw-r--r-- | ui/base/gestures/gesture_recognizer_impl.h | 80 | ||||
-rw-r--r-- | ui/base/gestures/gesture_sequence.cc | 572 | ||||
-rw-r--r-- | ui/base/gestures/gesture_sequence.h | 163 | ||||
-rw-r--r-- | ui/base/gestures/gesture_types.h | 65 | ||||
-rw-r--r-- | ui/base/gestures/gestures.dot | 32 | ||||
-rw-r--r-- | ui/base/gestures/velocity_calculator.cc | 109 | ||||
-rw-r--r-- | ui/base/gestures/velocity_calculator.h | 51 | ||||
-rw-r--r-- | ui/base/gestures/velocity_calculator_unittest.cc | 143 |
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 |