summaryrefslogtreecommitdiffstats
path: root/ui/aura
diff options
context:
space:
mode:
authorsadrul@chromium.org <sadrul@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-01-19 00:48:01 +0000
committersadrul@chromium.org <sadrul@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-01-19 00:48:01 +0000
commit912b6f3be7f1e31b4107ecb8b572245d9f65ff47 (patch)
treed43a9f10d01316cc817cd7619f1561e0ed279e04 /ui/aura
parent7d692835b4257b2621f969037f52e1dca67d833e (diff)
downloadchromium_src-912b6f3be7f1e31b4107ecb8b572245d9f65ff47.zip
chromium_src-912b6f3be7f1e31b4107ecb8b572245d9f65ff47.tar.gz
chromium_src-912b6f3be7f1e31b4107ecb8b572245d9f65ff47.tar.bz2
aura: Move GestureRecognizer from views into aura.
Remove deprecated GestureManager, and move the functional GestureRecognizer from views into aura. BUG=110227 TEST=views_unittests:GestureEvent, aura_unittests:GestureRecognizerTest Review URL: https://chromiumcodereview.appspot.com/9255019 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@118200 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui/aura')
-rw-r--r--ui/aura/aura.gyp4
-rw-r--r--ui/aura/event.cc22
-rw-r--r--ui/aura/event.h25
-rw-r--r--ui/aura/gestures/gesture_recognizer.cc289
-rw-r--r--ui/aura/gestures/gesture_recognizer.h176
-rw-r--r--ui/aura/gestures/gesture_recognizer_unittest.cc266
-rw-r--r--ui/aura/root_window.cc78
-rw-r--r--ui/aura/root_window.h10
8 files changed, 862 insertions, 8 deletions
diff --git a/ui/aura/aura.gyp b/ui/aura/aura.gyp
index 90a5007..db1ad70 100644
--- a/ui/aura/aura.gyp
+++ b/ui/aura/aura.gyp
@@ -13,6 +13,7 @@
'dependencies': [
'../../base/base.gyp:base',
'../../base/base.gyp:base_i18n',
+ '../../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
'../../skia/skia.gyp:skia',
'../gfx/compositor/compositor.gyp:compositor',
'../ui.gyp:gfx_resources',
@@ -43,6 +44,8 @@
'client/window_move_client.h',
'client/window_types.h',
'cursor.h',
+ 'gestures/gesture_recognizer.cc',
+ 'gestures/gesture_recognizer.h',
'root_window_host.h',
'root_window_host_linux.cc',
'root_window_host_win.cc',
@@ -145,6 +148,7 @@
'..',
],
'sources': [
+ 'gestures/gesture_recognizer_unittest.cc',
'test/run_all_unittests.cc',
'test/test_suite.cc',
'test/test_suite.h',
diff --git a/ui/aura/event.cc b/ui/aura/event.cc
index 57a0f31..c62ff40 100644
--- a/ui/aura/event.cc
+++ b/ui/aura/event.cc
@@ -352,4 +352,26 @@ ScrollEvent::ScrollEvent(const base::NativeEvent& native_event)
ui::GetScrollOffsets(native_event, &x_offset_, &y_offset_);
}
+GestureEvent::GestureEvent(ui::EventType type,
+ int x,
+ int y,
+ int flags,
+ base::Time time_stamp,
+ float delta_x,
+ float delta_y)
+ : LocatedEvent(type, gfx::Point(x, y), flags),
+ delta_x_(delta_x),
+ delta_y_(delta_y) {
+ // XXX: Why is aura::Event::time_stamp_ a TimeDelta instead of a Time?
+ set_time_stamp(base::TimeDelta::FromSeconds(time_stamp.ToDoubleT()));
+}
+
+GestureEvent::GestureEvent(const GestureEvent& model,
+ Window* source,
+ Window* target)
+ : LocatedEvent(model, source, target),
+ delta_x_(model.delta_x_),
+ delta_y_(model.delta_y_) {
+}
+
} // namespace aura
diff --git a/ui/aura/event.h b/ui/aura/event.h
index c840f43..9f97661 100644
--- a/ui/aura/event.h
+++ b/ui/aura/event.h
@@ -66,6 +66,7 @@ class AURA_EXPORT Event {
void set_delete_native_event(bool delete_native_event) {
delete_native_event_ = delete_native_event;
}
+ void set_time_stamp(base::TimeDelta time_stamp) { time_stamp_ = time_stamp; }
private:
void operator=(const Event&);
@@ -137,7 +138,7 @@ class AURA_EXPORT MouseEvent : public LocatedEvent {
ui::EventType type,
int flags);
- // Used for synthetic events in testing.
+ // Used for synthetic events in testing and by the gesture recognizer.
MouseEvent(ui::EventType type, const gfx::Point& location, int flags);
// Compares two mouse down events and returns true if the second one should
@@ -288,6 +289,28 @@ class AURA_EXPORT ScrollEvent : public MouseEvent {
};
class AURA_EXPORT GestureEvent : public LocatedEvent {
+ public:
+ GestureEvent(ui::EventType type,
+ int x,
+ int y,
+ int flags,
+ base::Time time_stamp,
+ float delta_x,
+ float delta_y);
+
+ // Create a new TouchEvent which is identical to the provided model.
+ // If source / target windows are provided, the model location will be
+ // converted from |source| coordinate system to |target| coordinate system.
+ GestureEvent(const GestureEvent& model, Window* source, Window* target);
+
+ float delta_x() const { return delta_x_; }
+ float delta_y() const { return delta_y_; }
+
+ private:
+ float delta_x_;
+ float delta_y_;
+
+ DISALLOW_COPY_AND_ASSIGN(GestureEvent);
};
} // namespace aura
diff --git a/ui/aura/gestures/gesture_recognizer.cc b/ui/aura/gestures/gesture_recognizer.cc
new file mode 100644
index 0000000..8787282
--- /dev/null
+++ b/ui/aura/gestures/gesture_recognizer.cc
@@ -0,0 +1,289 @@
+// 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/aura/gestures/gesture_recognizer.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time.h"
+#include "ui/aura/event.h"
+#include "ui/base/events.h"
+
+namespace {
+// TODO(Gajen): Make these configurable in sync with this CL http://code.google.
+// com/p/chromium/issues/detail?id=100773.
+const double kMaximumTouchDownDurationInSecondsForClick = 0.8;
+const double kMinimumTouchDownDurationInSecondsForClick = 0.01;
+const double kMaximumSecondsBetweenDoubleClick = 0.7;
+const int kMaximumTouchMoveInPixelsForClick = 20;
+const float kMinFlickSpeedSquared = 550.f * 550.f;
+
+} // namespace
+
+namespace aura {
+
+////////////////////////////////////////////////////////////////////////////////
+// GestureRecognizer Public:
+
+GestureRecognizer::GestureRecognizer()
+ : first_touch_time_(0.0),
+ state_(GestureRecognizer::GS_NO_GESTURE),
+ last_touch_time_(0.0),
+ last_click_time_(0.0),
+ x_velocity_(0.0),
+ y_velocity_(0.0),
+ flags_(0) {
+}
+
+GestureRecognizer::~GestureRecognizer() {
+}
+
+GestureRecognizer* GestureRecognizer::GetInstance() {
+ return Singleton<GestureRecognizer>::get();
+}
+
+GestureRecognizer::Gestures* GestureRecognizer::ProcessTouchEventForGesture(
+ const TouchEvent& event,
+ ui::TouchStatus status) {
+ if (status != ui::TOUCH_STATUS_UNKNOWN)
+ return NULL; // The event was consumed by a touch sequence.
+
+ scoped_ptr<Gestures> gestures(new Gestures());
+ UpdateValues(event);
+ switch (Signature(state_, event.touch_id(), event.type(), false)) {
+ case GST_NO_GESTURE_FIRST_PRESSED:
+ TouchDown(event, gestures.get());
+ break;
+ case GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED:
+ Click(event, gestures.get());
+ break;
+ case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED:
+ case GST_PENDING_SYNTHETIC_CLICK_FIRST_STATIONARY:
+ InClickOrScroll(event, gestures.get());
+ break;
+ case GST_PENDING_SYNTHETIC_CLICK_FIRST_CANCELLED:
+ NoGesture(event, gestures.get());
+ break;
+ case GST_SCROLL_FIRST_MOVED:
+ InScroll(event, gestures.get());
+ break;
+ case GST_SCROLL_FIRST_RELEASED:
+ case GST_SCROLL_FIRST_CANCELLED:
+ ScrollEnd(event, gestures.get());
+ break;
+ }
+ return gestures.release();
+}
+
+void GestureRecognizer::Reset() {
+ first_touch_time_ = 0.0;
+ state_ = GestureRecognizer::GS_NO_GESTURE;
+ last_touch_time_ = 0.0;
+ last_touch_position_.SetPoint(0, 0);
+ x_velocity_ = 0.0;
+ y_velocity_ = 0.0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// GestureRecognizer Private:
+
+// static
+GestureRecognizer::TouchState GestureRecognizer::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;
+}
+
+unsigned int GestureRecognizer::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 1 + ((touch_state & 0x7) << 1 | (touch_handled ? 1 << 4 : 0) |
+ ((touch_id & 0xfff) << 5) | (gesture_state << 17));
+}
+
+bool GestureRecognizer::IsInClickTimeWindow() {
+ double duration(last_touch_time_ - first_touch_time_);
+ return duration >= kMinimumTouchDownDurationInSecondsForClick &&
+ duration < kMaximumTouchDownDurationInSecondsForClick;
+}
+
+bool GestureRecognizer::IsInSecondClickTimeWindow() {
+ double duration(last_touch_time_ - last_click_time_);
+ return duration < kMaximumSecondsBetweenDoubleClick;
+}
+
+bool GestureRecognizer::IsInsideManhattanSquare(const TouchEvent& event) {
+ int manhattanDistance = abs(event.x() - first_touch_position_.x()) +
+ abs(event.y() - first_touch_position_.y());
+ return manhattanDistance < kMaximumTouchMoveInPixelsForClick;
+}
+
+bool GestureRecognizer::IsSecondClickInsideManhattanSquare(
+ const TouchEvent& event) {
+ int manhattanDistance = abs(event.x() - last_click_position_.x()) +
+ abs(event.y() - last_click_position_.y());
+ return manhattanDistance < kMaximumTouchMoveInPixelsForClick;
+}
+
+bool GestureRecognizer::IsOverMinFlickSpeed() {
+ return (x_velocity_ * x_velocity_ + y_velocity_ * y_velocity_) >
+ kMinFlickSpeedSquared;
+}
+
+void GestureRecognizer::AppendTapDownGestureEvent(const TouchEvent& event,
+ Gestures* gestures) {
+ gestures->push_back(linked_ptr<GestureEvent>(new GestureEvent(
+ ui::ET_GESTURE_TAP_DOWN,
+ first_touch_position_.x(),
+ first_touch_position_.y(),
+ event.flags(),
+ base::Time::FromDoubleT(last_touch_time_),
+ 0.f, 0.f)));
+}
+
+void GestureRecognizer::AppendClickGestureEvent(const TouchEvent& event,
+ Gestures* gestures) {
+ gestures->push_back(linked_ptr<GestureEvent>(new GestureEvent(
+ ui::ET_GESTURE_TAP,
+ first_touch_position_.x(),
+ first_touch_position_.y(),
+ event.flags(),
+ base::Time::FromDoubleT(last_touch_time_),
+ 0.f, 0.f)));
+}
+
+void GestureRecognizer::AppendDoubleClickGestureEvent(const TouchEvent& event,
+ Gestures* gestures) {
+ gestures->push_back(linked_ptr<GestureEvent>(new GestureEvent(
+ ui::ET_GESTURE_DOUBLE_TAP,
+ first_touch_position_.x(),
+ first_touch_position_.y(),
+ event.flags(),
+ base::Time::FromDoubleT(last_touch_time_),
+ 0.f, 0.f)));
+}
+
+void GestureRecognizer::AppendScrollGestureBegin(const TouchEvent& event,
+ Gestures* gestures) {
+ gestures->push_back(linked_ptr<GestureEvent>(new GestureEvent(
+ ui::ET_GESTURE_SCROLL_BEGIN,
+ event.x(),
+ event.y(),
+ event.flags(),
+ base::Time::FromDoubleT(last_touch_time_),
+ 0.f, 0.f)));
+}
+
+void GestureRecognizer::AppendScrollGestureEnd(const TouchEvent& event,
+ Gestures* gestures,
+ float x_velocity,
+ float y_velocity) {
+ gestures->push_back(linked_ptr<GestureEvent>(new GestureEvent(
+ ui::ET_GESTURE_SCROLL_END,
+ event.x(),
+ event.y(),
+ event.flags(),
+ base::Time::FromDoubleT(last_touch_time_),
+ x_velocity, y_velocity)));
+}
+
+void GestureRecognizer:: AppendScrollGestureUpdate(const TouchEvent& event,
+ Gestures* gestures) {
+ float delta_x(event.x() - first_touch_position_.x());
+ float delta_y(event.y() - first_touch_position_.y());
+
+ gestures->push_back(linked_ptr<GestureEvent>(new GestureEvent(
+ ui::ET_GESTURE_SCROLL_UPDATE,
+ event.x(),
+ event.y(),
+ event.flags(),
+ base::Time::FromDoubleT(last_touch_time_),
+ delta_x, delta_y)));
+
+ first_touch_position_ = event.location();
+}
+
+void GestureRecognizer::UpdateValues(const TouchEvent& event) {
+ if (state_ != GS_NO_GESTURE && event.type() == ui::ET_TOUCH_MOVED) {
+ double interval(event.time_stamp().InSecondsF() - last_touch_time_);
+ x_velocity_ = (event.x() - last_touch_position_.x()) / interval;
+ y_velocity_ = (event.y() - last_touch_position_.y()) / interval;
+ }
+ last_touch_time_ = event.time_stamp().InSecondsF();
+ last_touch_position_ = event.location();
+ if (state_ == GS_NO_GESTURE) {
+ first_touch_time_ = last_touch_time_;
+ first_touch_position_ = event.location();
+ x_velocity_ = 0.0;
+ y_velocity_ = 0.0;
+ }
+}
+
+bool GestureRecognizer::Click(const TouchEvent& event, Gestures* gestures) {
+ bool gesture_added = false;
+ if (IsInClickTimeWindow() && IsInsideManhattanSquare(event)) {
+ gesture_added = true;
+ AppendClickGestureEvent(event, gestures);
+ if (IsInSecondClickTimeWindow() &&
+ IsSecondClickInsideManhattanSquare(event))
+ AppendDoubleClickGestureEvent(event, gestures);
+ last_click_time_ = last_touch_time_;
+ last_click_position_ = last_touch_position_;
+ }
+ Reset();
+ return gesture_added;
+}
+
+bool GestureRecognizer::InClickOrScroll(const TouchEvent& event,
+ Gestures* gestures) {
+ if (IsInClickTimeWindow() && IsInsideManhattanSquare(event)) {
+ SetState(GS_PENDING_SYNTHETIC_CLICK);
+ return false;
+ }
+ if (event.type() == ui::ET_TOUCH_MOVED && !IsInsideManhattanSquare(event)) {
+ AppendScrollGestureBegin(event, gestures);
+ AppendScrollGestureUpdate(event, gestures);
+ SetState(GS_SCROLL);
+ return true;
+ }
+ return false;
+}
+
+bool GestureRecognizer::InScroll(const TouchEvent& event, Gestures* gestures) {
+ AppendScrollGestureUpdate(event, gestures);
+ return true;
+}
+
+bool GestureRecognizer::NoGesture(const TouchEvent&, Gestures*) {
+ Reset();
+ return false;
+}
+
+bool GestureRecognizer::TouchDown(const TouchEvent& event, Gestures* gestures) {
+ AppendTapDownGestureEvent(event, gestures);
+ SetState(GS_PENDING_SYNTHETIC_CLICK);
+ return false;
+}
+
+bool GestureRecognizer::ScrollEnd(const TouchEvent& event, Gestures* gestures) {
+ if (IsOverMinFlickSpeed() && event.type() != ui::ET_TOUCH_CANCELLED)
+ AppendScrollGestureEnd(event, gestures, x_velocity_, y_velocity_);
+ else
+ AppendScrollGestureEnd(event, gestures, 0.f, 0.f);
+ SetState(GS_NO_GESTURE);
+ Reset();
+ return false;
+}
+
+} // namespace aura
diff --git a/ui/aura/gestures/gesture_recognizer.h b/ui/aura/gestures/gesture_recognizer.h
new file mode 100644
index 0000000..4edd1b8
--- /dev/null
+++ b/ui/aura/gestures/gesture_recognizer.h
@@ -0,0 +1,176 @@
+// 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_AURA_GESTURES_GESTURE_RECOGNIZER_H_
+#define UI_AURA_GESTURES_GESTURE_RECOGNIZER_H_
+#pragma once
+
+#include <map>
+#include <vector>
+
+#include "base/memory/linked_ptr.h"
+#include "base/memory/singleton.h"
+#include "ui/aura/aura_export.h"
+#include "ui/base/events.h"
+#include "ui/gfx/point.h"
+
+namespace aura {
+class TouchEvent;
+class GestureEvent;
+
+// A GestureRecognizer recognizes gestures from touch sequences.
+class AURA_EXPORT GestureRecognizer {
+ public:
+ // Gesture state.
+ enum GestureState {
+ GS_NO_GESTURE,
+ GS_PENDING_SYNTHETIC_CLICK,
+ GS_SCROLL,
+ };
+
+ // 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,
+ };
+
+ // List of GestureEvent*.
+ typedef std::vector<linked_ptr<GestureEvent> > Gestures;
+
+ GestureRecognizer();
+ virtual ~GestureRecognizer();
+
+ static GestureRecognizer* GetInstance();
+
+ // 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);
+
+ // Clears the GestureRecognizer to its initial state.
+ virtual void Reset();
+
+ // Accessor function.
+ GestureState GetState() const { return state_; }
+
+ private:
+ friend struct DefaultSingletonTraits<GestureRecognizer>;
+
+ // Gesture signature types for different values of combination (GestureState,
+ // touch_id, ui::EventType, touch_handled), see GestureRecognizer::Signature()
+ // for more info.
+ //
+ // Note: New addition of types should be placed as per their Signature value.
+ enum GestureSignatureType {
+ // For input combination (GS_NO_GESTURE, 0, ui::ET_TOUCH_PRESSED, false).
+ GST_NO_GESTURE_FIRST_PRESSED = 0x00000003,
+
+ // (GS_PENDING_SYNTHETIC_CLICK, 0, ui::ET_TOUCH_RELEASED, false).
+ GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED = 0x00020001,
+
+ // (GS_PENDING_SYNTHETIC_CLICK, 0, ui::ET_TOUCH_MOVED, false).
+ GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED = 0x00020005,
+
+ // (GS_PENDING_SYNTHETIC_CLICK, 0, ui::ET_TOUCH_STATIONARY, false).
+ GST_PENDING_SYNTHETIC_CLICK_FIRST_STATIONARY = 0x00020007,
+
+ // (GS_PENDING_SYNTHETIC_CLICK, 0, ui::ET_TOUCH_CANCELLED, false).
+ GST_PENDING_SYNTHETIC_CLICK_FIRST_CANCELLED = 0x00020009,
+
+ // (GS_SCROLL, 0, ui::ET_TOUCH_RELEASED, false).
+ GST_SCROLL_FIRST_RELEASED = 0x00040001,
+
+ // (GS_SCROLL, 0, ui::ET_TOUCH_MOVED, false).
+ GST_SCROLL_FIRST_MOVED = 0x00040005,
+
+ // (GS_SCROLL, 0, ui::ET_TOUCH_CANCELLED, false).
+ GST_SCROLL_FIRST_CANCELLED = 0x00040009,
+ };
+
+ // Get equivalent TouchState from EventType |type|.
+ static TouchState TouchEventTypeToTouchState(ui::EventType type);
+
+ // 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|.
+ static unsigned int Signature(GestureState state,
+ unsigned int touch_id, ui::EventType type,
+ bool touch_handled);
+
+ // Various statistical functions to manipulate gestures.
+ bool IsInClickTimeWindow();
+ bool IsInSecondClickTimeWindow();
+ bool IsInsideManhattanSquare(const TouchEvent& event);
+ bool IsSecondClickInsideManhattanSquare(const TouchEvent& event);
+ bool IsOverMinFlickSpeed();
+
+ // Functions to be called to add GestureEvents, after succesful recognition.
+ void AppendTapDownGestureEvent(const TouchEvent& event, Gestures* gestures);
+ void AppendClickGestureEvent(const TouchEvent& event, Gestures* gestures);
+ void AppendDoubleClickGestureEvent(const TouchEvent& event,
+ Gestures* gestures);
+ void AppendScrollGestureBegin(const TouchEvent& event, Gestures* gestures);
+ void AppendScrollGestureEnd(const TouchEvent& event,
+ Gestures* gestures,
+ float x_velocity, float y_velocity);
+ void AppendScrollGestureUpdate(const TouchEvent& event, Gestures* gestures);
+
+ void UpdateValues(const TouchEvent& event);
+ void SetState(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, Gestures* gestures);
+ bool InClickOrScroll(const TouchEvent& event, Gestures* gestures);
+ bool InScroll(const TouchEvent& event, Gestures* gestures);
+ bool NoGesture(const TouchEvent& event, Gestures* gestures);
+ bool TouchDown(const TouchEvent& event, Gestures* gestures);
+ bool ScrollEnd(const TouchEvent& event, Gestures* gestures);
+
+ // Location of first touch event in a touch sequence.
+ gfx::Point first_touch_position_;
+
+ // Time of first touch event in a touch sequence.
+ double first_touch_time_;
+
+ // Current state of gesture recognizer.
+ GestureState state_;
+
+ // Time of current touch event in a touch sequence.
+ double last_touch_time_;
+
+ // Time of click gesture.
+ double last_click_time_;
+
+ // Location of click gesture.
+ gfx::Point last_click_position_;
+
+ // Location of current touch event in a touch sequence.
+ gfx::Point last_touch_position_;
+
+ // Velocity in x and y direction.
+ float x_velocity_;
+ float y_velocity_;
+
+ // ui::EventFlags.
+ int flags_;
+
+ DISALLOW_COPY_AND_ASSIGN(GestureRecognizer);
+};
+
+} // namespace aura
+
+#endif // UI_AURA_GESTURES_GESTURE_RECOGNIZER_H_
diff --git a/ui/aura/gestures/gesture_recognizer_unittest.cc b/ui/aura/gestures/gesture_recognizer_unittest.cc
new file mode 100644
index 0000000..bc65b71
--- /dev/null
+++ b/ui/aura/gestures/gesture_recognizer_unittest.cc
@@ -0,0 +1,266 @@
+// 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 "testing/gtest/include/gtest/gtest.h"
+#include "ui/aura/event.h"
+#include "ui/aura/root_window.h"
+#include "ui/aura/test/aura_test_base.h"
+#include "ui/aura/test/test_window_delegate.h"
+#include "ui/aura/test/test_windows.h"
+#include "ui/base/hit_test.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect.h"
+
+namespace aura {
+namespace test {
+
+namespace {
+
+// A delegate that keeps track of gesture events.
+class GestureEventConsumeDelegate : public TestWindowDelegate {
+ public:
+ GestureEventConsumeDelegate()
+ : tap_(false),
+ tap_down_(false),
+ double_tap_(false),
+ scroll_begin_(false),
+ scroll_update_(false),
+ scroll_end_(false),
+ scroll_x_(0),
+ scroll_y_(0) {
+ }
+
+ virtual ~GestureEventConsumeDelegate() {}
+
+ void Reset() {
+ tap_ = false;
+ tap_down_ = false;
+ double_tap_ = false;
+ scroll_begin_ = false;
+ scroll_update_ = false;
+ scroll_end_ = false;
+
+ scroll_x_ = 0;
+ scroll_y_ = 0;
+ }
+
+ bool tap() const { return tap_; }
+ bool tap_down() const { return tap_down_; }
+ bool double_tap() const { return double_tap_; }
+ bool scroll_begin() const { return scroll_begin_; }
+ bool scroll_update() const { return scroll_update_; }
+ bool scroll_end() const { return scroll_end_; }
+
+ float scroll_x() const { return scroll_x_; }
+ float scroll_y() const { return scroll_y_; }
+
+ virtual ui::GestureStatus OnGestureEvent(GestureEvent* gesture) OVERRIDE {
+ switch (gesture->type()) {
+ case ui::ET_GESTURE_TAP:
+ tap_ = true;
+ break;
+ case ui::ET_GESTURE_TAP_DOWN:
+ tap_down_ = true;
+ break;
+ case ui::ET_GESTURE_DOUBLE_TAP:
+ double_tap_ = true;
+ break;
+ case ui::ET_GESTURE_SCROLL_BEGIN:
+ scroll_begin_ = true;
+ break;
+ case ui::ET_GESTURE_SCROLL_UPDATE:
+ scroll_update_ = true;
+ scroll_x_ += gesture->delta_x();
+ scroll_y_ += gesture->delta_y();
+ break;
+ case ui::ET_GESTURE_SCROLL_END:
+ scroll_end_ = true;
+ break;
+ default:
+ NOTREACHED();
+ }
+ return ui::GESTURE_STATUS_CONSUMED;
+ }
+
+ private:
+ bool tap_;
+ bool tap_down_;
+ bool double_tap_;
+ bool scroll_begin_;
+ bool scroll_update_;
+ bool scroll_end_;
+
+ float scroll_x_;
+ float scroll_y_;
+
+ DISALLOW_COPY_AND_ASSIGN(GestureEventConsumeDelegate);
+};
+
+// A delegate that ignores gesture events but keeps track of [synthetic] mouse
+// events.
+class GestureEventSynthDelegate : public TestWindowDelegate {
+ public:
+ GestureEventSynthDelegate()
+ : mouse_enter_(false),
+ mouse_exit_(false),
+ mouse_press_(false),
+ mouse_release_(false),
+ mouse_move_(false) {
+ }
+
+ void Reset() {
+ mouse_enter_ = false;
+ mouse_exit_ = false;
+ mouse_press_ = false;
+ mouse_release_ = false;
+ mouse_move_ = false;
+ }
+
+ bool mouse_enter() const { return mouse_enter_; }
+ bool mouse_exit() const { return mouse_exit_; }
+ bool mouse_press() const { return mouse_press_; }
+ bool mouse_move() const { return mouse_move_; }
+ bool mouse_release() const { return mouse_release_; }
+
+ virtual bool OnMouseEvent(MouseEvent* event) OVERRIDE {
+ switch (event->type()) {
+ case ui::ET_MOUSE_PRESSED:
+ mouse_press_ = true;
+ break;
+ case ui::ET_MOUSE_RELEASED:
+ mouse_release_ = true;
+ break;
+ case ui::ET_MOUSE_MOVED:
+ mouse_move_ = true;
+ break;
+ case ui::ET_MOUSE_ENTERED:
+ mouse_enter_ = true;
+ break;
+ case ui::ET_MOUSE_EXITED:
+ mouse_exit_ = true;
+ break;
+ default:
+ NOTREACHED();
+ }
+ return true;
+ }
+
+ private:
+ bool mouse_enter_;
+ bool mouse_exit_;
+ bool mouse_press_;
+ bool mouse_release_;
+ bool mouse_move_;
+
+ DISALLOW_COPY_AND_ASSIGN(GestureEventSynthDelegate);
+};
+
+} // namespace
+
+typedef AuraTestBase GestureRecognizerTest;
+
+// Check that appropriate touch events generate tap gesture events.
+TEST_F(GestureRecognizerTest, GestureEventTap) {
+ scoped_ptr<GestureEventConsumeDelegate> delegate(
+ new GestureEventConsumeDelegate());
+ const int kWindowWidth = 123;
+ const int kWindowHeight = 45;
+ gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight);
+ scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate(
+ delegate.get(), -1234, bounds, NULL));
+
+ delegate->Reset();
+ TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), 0);
+ RootWindow::GetInstance()->DispatchTouchEvent(&press);
+ EXPECT_FALSE(delegate->tap());
+ EXPECT_TRUE(delegate->tap_down());
+ EXPECT_FALSE(delegate->double_tap());
+ EXPECT_FALSE(delegate->scroll_begin());
+ EXPECT_FALSE(delegate->scroll_update());
+ EXPECT_FALSE(delegate->scroll_end());
+
+ // Make sure there is enough delay before the touch is released so that it is
+ // recognized as a tap.
+ delegate->Reset();
+ TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), 0);
+ Event::TestApi test_release(&release);
+ test_release.set_time_stamp(press.time_stamp() +
+ base::TimeDelta::FromMilliseconds(50));
+ RootWindow::GetInstance()->DispatchTouchEvent(&release);
+ EXPECT_TRUE(delegate->tap());
+ EXPECT_FALSE(delegate->tap_down());
+ EXPECT_FALSE(delegate->double_tap());
+ EXPECT_FALSE(delegate->scroll_begin());
+ EXPECT_FALSE(delegate->scroll_update());
+ EXPECT_FALSE(delegate->scroll_end());
+}
+
+// Check that appropriate touch events generate scroll gesture events.
+TEST_F(GestureRecognizerTest, GestureEventScroll) {
+ scoped_ptr<GestureEventConsumeDelegate> delegate(
+ new GestureEventConsumeDelegate());
+ const int kWindowWidth = 123;
+ const int kWindowHeight = 45;
+ gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight);
+ scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate(
+ delegate.get(), -1234, bounds, NULL));
+
+ delegate->Reset();
+ TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), 0);
+ RootWindow::GetInstance()->DispatchTouchEvent(&press);
+ EXPECT_FALSE(delegate->tap());
+ EXPECT_TRUE(delegate->tap_down());
+ EXPECT_FALSE(delegate->double_tap());
+ EXPECT_FALSE(delegate->scroll_begin());
+ EXPECT_FALSE(delegate->scroll_update());
+ EXPECT_FALSE(delegate->scroll_end());
+
+ // Move the touch-point enough so that it is considered as a scroll. This
+ // should generate both SCROLL_BEGIN and SCROLL_UPDATE gestures.
+ delegate->Reset();
+ TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(130, 201), 0);
+ RootWindow::GetInstance()->DispatchTouchEvent(&move);
+ EXPECT_FALSE(delegate->tap());
+ EXPECT_FALSE(delegate->tap_down());
+ EXPECT_FALSE(delegate->double_tap());
+ EXPECT_TRUE(delegate->scroll_begin());
+ EXPECT_TRUE(delegate->scroll_update());
+ EXPECT_FALSE(delegate->scroll_end());
+ EXPECT_EQ(29, delegate->scroll_x());
+ EXPECT_EQ(0, delegate->scroll_y());
+
+ // Release the touch. This should end the scroll.
+ delegate->Reset();
+ TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), 0);
+ Event::TestApi test_release(&release);
+ test_release.set_time_stamp(press.time_stamp() +
+ base::TimeDelta::FromMilliseconds(50));
+ RootWindow::GetInstance()->DispatchTouchEvent(&release);
+ EXPECT_FALSE(delegate->tap());
+ EXPECT_FALSE(delegate->tap_down());
+ EXPECT_FALSE(delegate->double_tap());
+ EXPECT_FALSE(delegate->scroll_begin());
+ EXPECT_FALSE(delegate->scroll_update());
+ EXPECT_TRUE(delegate->scroll_end());
+}
+
+// Check that unprocessed gesture events generate appropriate synthetic mouse
+// events.
+TEST_F(GestureRecognizerTest, GestureTapSyntheticMouse) {
+ scoped_ptr<GestureEventSynthDelegate> delegate(
+ new GestureEventSynthDelegate());
+ scoped_ptr<Window> window(CreateTestWindowWithDelegate(delegate.get(), -1234,
+ gfx::Rect(0, 0, 123, 45), NULL));
+
+ delegate->Reset();
+ GestureEvent tap(ui::ET_GESTURE_TAP, 20, 20, 0, base::Time::Now(), 0, 0);
+ RootWindow::GetInstance()->DispatchGestureEvent(&tap);
+ EXPECT_TRUE(delegate->mouse_enter());
+ EXPECT_TRUE(delegate->mouse_press());
+ EXPECT_TRUE(delegate->mouse_release());
+ EXPECT_TRUE(delegate->mouse_exit());
+}
+
+} // namespace test
+} // namespace aura
diff --git a/ui/aura/root_window.cc b/ui/aura/root_window.cc
index 6ce24d4..7909239 100644
--- a/ui/aura/root_window.cc
+++ b/ui/aura/root_window.cc
@@ -20,6 +20,7 @@
#include "ui/aura/event.h"
#include "ui/aura/event_filter.h"
#include "ui/aura/focus_manager.h"
+#include "ui/aura/gestures/gesture_recognizer.h"
#include "ui/aura/screen_aura.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
@@ -207,9 +208,11 @@ bool RootWindow::DispatchTouchEvent(TouchEvent* event) {
touch_event_handler_ ? touch_event_handler_ : capture_window_;
if (!target)
target = GetEventHandlerForPoint(event->location());
+
+ ui::TouchStatus status = ui::TOUCH_STATUS_UNKNOWN;
if (target) {
TouchEvent translated_event(*event, this, target);
- ui::TouchStatus status = ProcessTouchEvent(target, &translated_event);
+ status = ProcessTouchEvent(target, &translated_event);
if (status == ui::TOUCH_STATUS_START)
touch_event_handler_ = target;
else if (status == ui::TOUCH_STATUS_END ||
@@ -218,15 +221,32 @@ bool RootWindow::DispatchTouchEvent(TouchEvent* event) {
handled = status != ui::TOUCH_STATUS_UNKNOWN;
}
- if (!handled) {
- // TODO(sad): Send the touch to the gesture recognizer.
+ // Get the list of GestureEvents from GestureRecognizer.
+ scoped_ptr<GestureRecognizer::Gestures> gestures;
+ gestures.reset(gesture_recognizer_->ProcessTouchEventForGesture(*event,
+ status));
+ if (gestures.get()) {
+ for (unsigned int i = 0; i < gestures->size(); i++) {
+ GestureEvent* gesture = gestures->at(i).get();
+ if (DispatchGestureEvent(gesture) != ui::GESTURE_STATUS_UNKNOWN)
+ handled = true;
+ }
}
return handled;
}
bool RootWindow::DispatchGestureEvent(GestureEvent* event) {
- // TODO(sad):
+ event->UpdateForTransform(layer()->transform());
+ Window* target = gesture_handler_ ? gesture_handler_ : capture_window_;
+ if (!target)
+ target = GetEventHandlerForPoint(event->location());
+ if (target) {
+ GestureEvent translated_event(*event, this, target);
+ ui::GestureStatus status = ProcessGestureEvent(target, &translated_event);
+ return status != ui::GESTURE_STATUS_UNKNOWN;
+ }
+
return false;
}
@@ -269,6 +289,8 @@ void RootWindow::WindowDestroying(Window* window) {
capture_window_ = NULL;
if (touch_event_handler_ == window)
touch_event_handler_ = NULL;
+ if (gesture_handler_ == window)
+ gesture_handler_ = NULL;
}
MessageLoop::Dispatcher* RootWindow::GetDispatcher() {
@@ -312,10 +334,13 @@ void RootWindow::SetCapture(Window* window) {
touch_event_handler_ = capture_window_;
if (mouse_moved_handler_ || mouse_button_flags_ != 0)
mouse_moved_handler_ = capture_window_;
+ if (gesture_handler_)
+ gesture_handler_ = capture_window_;
} else {
// When capture is lost, we must reset the event handlers.
touch_event_handler_ = NULL;
mouse_moved_handler_ = NULL;
+ gesture_handler_ = NULL;
}
mouse_pressed_handler_ = NULL;
}
@@ -355,7 +380,9 @@ RootWindow::RootWindow()
mouse_pressed_handler_(NULL),
mouse_moved_handler_(NULL),
focused_window_(NULL),
- touch_event_handler_(NULL) {
+ touch_event_handler_(NULL),
+ gesture_handler_(NULL),
+ gesture_recognizer_(GestureRecognizer::GetInstance()) {
SetName("RootWindow");
gfx::Screen::SetInstance(screen_);
host_->SetRootWindow(this);
@@ -371,6 +398,8 @@ RootWindow::RootWindow()
host_->GetSize());
}
DCHECK(compositor_.get());
+
+ gesture_recognizer_->Reset();
}
RootWindow::~RootWindow() {
@@ -460,14 +489,49 @@ ui::GestureStatus RootWindow::ProcessGestureEvent(Window* target,
EventFilters filters;
GetEventFiltersToNotify(target, &filters);
+ ui::GestureStatus status = ui::GESTURE_STATUS_UNKNOWN;
for (EventFilters::const_reverse_iterator it = filters.rbegin();
it != filters.rend(); ++it) {
- ui::GestureStatus status = (*it)->PreHandleGestureEvent(target, event);
+ status = (*it)->PreHandleGestureEvent(target, event);
if (status != ui::GESTURE_STATUS_UNKNOWN)
return status;
}
- return target->delegate()->OnGestureEvent(event);
+ status = target->delegate()->OnGestureEvent(event);
+ if (status == ui::GESTURE_STATUS_UNKNOWN) {
+ // The gesture was unprocessed. Generate corresponding mouse events here
+ // (e.g. tap to click).
+ switch (event->type()) {
+ case ui::ET_GESTURE_TAP: {
+ // Tap should be processed as a click. So generate the following
+ // sequence of mouse events: MOUSE_ENTERED, MOUSE_PRESSED,
+ // MOUSE_RELEASED and MOUSE_EXITED.
+ ui::EventType types[] = { ui::ET_MOUSE_ENTERED,
+ ui::ET_MOUSE_PRESSED,
+ ui::ET_MOUSE_RELEASED,
+ ui::ET_MOUSE_EXITED,
+ ui::ET_UNKNOWN
+ };
+ gesture_handler_ = target;
+ for (ui::EventType* type = types; *type != ui::ET_UNKNOWN; ++type) {
+ MouseEvent synth(*type, event->location(), event->flags());
+ if (gesture_handler_->delegate()->OnMouseEvent(&synth))
+ status = ui::GESTURE_STATUS_SYNTH_MOUSE;
+ // The window that was receiving the gestures may have closed/hidden
+ // itself in response to one of the synthetic events. Stop sending
+ // subsequent synthetic events if that happens.
+ if (!gesture_handler_)
+ break;
+ }
+ gesture_handler_ = NULL;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ return status;
}
void RootWindow::ScheduleDraw() {
diff --git a/ui/aura/root_window.h b/ui/aura/root_window.h
index b4dbd2e..8e59c4f 100644
--- a/ui/aura/root_window.h
+++ b/ui/aura/root_window.h
@@ -40,6 +40,7 @@ class StackingClient;
class ScrollEvent;
class TouchEvent;
class GestureEvent;
+class GestureRecognizer;
// RootWindow is responsible for hosting a set of windows.
class AURA_EXPORT RootWindow : public ui::CompositorDelegate,
@@ -151,6 +152,11 @@ class AURA_EXPORT RootWindow : public ui::CompositorDelegate,
void ToggleFullScreen();
#endif
+ // Provided only for testing:
+ void SetGestureRecognizerForTesting(GestureRecognizer* gr) {
+ gesture_recognizer_ = gr;
+ }
+
// Overridden from ui::CompositorDelegate:
virtual void ScheduleDraw() OVERRIDE;
@@ -229,6 +235,10 @@ class AURA_EXPORT RootWindow : public ui::CompositorDelegate,
Window* mouse_moved_handler_;
Window* focused_window_;
Window* touch_event_handler_;
+ Window* gesture_handler_;
+
+ // The gesture_recognizer_ for this.
+ GestureRecognizer* gesture_recognizer_;
DISALLOW_COPY_AND_ASSIGN(RootWindow);
};