diff options
author | sadrul@chromium.org <sadrul@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-19 00:48:01 +0000 |
---|---|---|
committer | sadrul@chromium.org <sadrul@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-19 00:48:01 +0000 |
commit | 912b6f3be7f1e31b4107ecb8b572245d9f65ff47 (patch) | |
tree | d43a9f10d01316cc817cd7619f1561e0ed279e04 /ui/aura | |
parent | 7d692835b4257b2621f969037f52e1dca67d833e (diff) | |
download | chromium_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.gyp | 4 | ||||
-rw-r--r-- | ui/aura/event.cc | 22 | ||||
-rw-r--r-- | ui/aura/event.h | 25 | ||||
-rw-r--r-- | ui/aura/gestures/gesture_recognizer.cc | 289 | ||||
-rw-r--r-- | ui/aura/gestures/gesture_recognizer.h | 176 | ||||
-rw-r--r-- | ui/aura/gestures/gesture_recognizer_unittest.cc | 266 | ||||
-rw-r--r-- | ui/aura/root_window.cc | 78 | ||||
-rw-r--r-- | ui/aura/root_window.h | 10 |
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); }; |