Chromium Code Reviews| Index: views/touchui/gesture_recognizer.cc |
| diff --git a/views/touchui/gesture_recognizer.cc b/views/touchui/gesture_recognizer.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..7cd811985d21a057d9c93f8bddbb8c01dcdc71a8 |
| --- /dev/null |
| +++ b/views/touchui/gesture_recognizer.cc |
| @@ -0,0 +1,295 @@ |
| +// Copyright (c) 2011 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 "views/touchui/gesture_recognizer.h" |
| + |
| +#include "base/logging.h" |
| +#include "ui/base/events.h" |
| +#include "views/events/event.h" |
| + |
| +namespace { |
| + |
| +double maximumTouchDownDurationInSecondsForClick = 0.8; |
|
rjkroege
2011/10/21 20:02:10
A subsequent CL should connect these changes with
Gajen
2011/10/25 14:32:29
Done.
|
| +double minimumTouchDownDurationInSecondsForClick = 0.01; |
|
rjkroege
2011/10/21 20:02:10
These ought to be const at the moment. They need
Gajen
2011/10/25 14:32:29
Done.
|
| +double maximumSecondsBetweenDoubleClick = 0.7; |
| +int maximumTouchMoveInPixelsForClick = 20; |
| +float minFlickSpeedSquared = 550.f * 550.f; |
| + |
| +} // namespace |
| + |
| +namespace views { |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// 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) { |
| + const unsigned int first_finger = 0; |
| + const ui::EventType released = ui::ET_TOUCH_RELEASED; |
| + const ui::EventType pressed = ui::ET_TOUCH_PRESSED; |
| + const ui::EventType moved = ui::ET_TOUCH_MOVED; |
| + const ui::EventType stationary = ui::ET_TOUCH_STATIONARY; |
| + const ui::EventType cancelled = ui::ET_TOUCH_CANCELLED; |
| + |
| + AddEdgeFunction(GS_NO_GESTURE, first_finger, pressed, false, |
| + &GestureRecognizer::TouchDown); |
| + AddEdgeFunction(GS_PENDING_SYNTHETIC_CLICK, first_finger, cancelled, false, |
| + &GestureRecognizer::NoGesture); |
| + AddEdgeFunction(GS_PENDING_SYNTHETIC_CLICK, first_finger, released, false, |
| + &GestureRecognizer::Click); |
| + AddEdgeFunction(GS_PENDING_SYNTHETIC_CLICK, first_finger, moved, false, |
| + &GestureRecognizer::IsClickOrScroll); |
| + AddEdgeFunction(GS_PENDING_SYNTHETIC_CLICK, first_finger, stationary, false, |
| + &GestureRecognizer::IsClickOrScroll); |
| + AddEdgeFunction(GS_SCROLL, first_finger, moved, false, |
| + &GestureRecognizer::InScroll); |
| + AddEdgeFunction(GS_SCROLL, first_finger, released, false, |
| + &GestureRecognizer::ScrollEnd); |
| + AddEdgeFunction(GS_SCROLL, first_finger, cancelled, false, |
| + &GestureRecognizer::ScrollEnd); |
| +} |
| + |
| +GestureRecognizer::~GestureRecognizer() { |
| +} |
| + |
| +GestureRecognizer::Gestures* GestureRecognizer::ProcessTouchEventForGesture( |
| + const TouchEvent& event, |
| + bool touch_handled) { |
| + flags_ = event.flags(); |
| + scoped_ptr<Gestures> gestures(new Gestures()); |
| + UpdateValues(event); |
| + GestureTransitionFunctionMap::iterator it = edge_functions_.find( |
| + Signature(state_, event.identity(), event.type(), touch_handled)); |
| + if (edge_functions_.end() != it) { |
| + GestureTransitionFunction ef = it->second; |
| + ((*this).*ef)(event, gestures.get()); |
| + } |
| + 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: |
| + |
| +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; |
| +} |
| + |
| +// 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|. |
| +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)); |
| +} |
| + |
| +void GestureRecognizer::AddEdgeFunction(GestureState state, |
| + unsigned int touch_id, |
| + ui::EventType type, |
| + bool touch_handled, |
| + GestureTransitionFunction function) { |
| + edge_functions_[Signature(state, touch_id, type, touch_handled)] = function; |
| +} |
| + |
| +bool GestureRecognizer::IsInClickTimeWindow() { |
| + double duration(last_touch_time_ - first_touch_time_); |
| + return duration >= minimumTouchDownDurationInSecondsForClick && |
| + duration < maximumTouchDownDurationInSecondsForClick; |
| +} |
| + |
| +bool GestureRecognizer::IsInSecondClickTimeWindow() { |
| + double duration(last_touch_time_ - last_click_time_); |
| + return duration < maximumSecondsBetweenDoubleClick; |
| +} |
| + |
| +bool GestureRecognizer::IsInsideManhattanSquare(const TouchEvent& event) { |
| + int manhattanDistance = abs(event.x() - first_touch_position_.x()) + |
| + abs(event.y() - first_touch_position_.y()); |
| + return manhattanDistance < maximumTouchMoveInPixelsForClick; |
| +} |
| + |
| +bool GestureRecognizer::IsSecondClickInsideManhattanSquare( |
| + const TouchEvent& event) { |
| + int manhattanDistance = abs(event.x() - last_click_position_.x()) + |
| + abs(event.y() - last_click_position_.y()); |
| + return manhattanDistance < maximumTouchMoveInPixelsForClick; |
| +} |
| + |
| +bool GestureRecognizer::IsOverMinFlickSpeed() { |
| + return (x_velocity_ * x_velocity_ + y_velocity_ * y_velocity_) > |
| + minFlickSpeedSquared; |
| +} |
| + |
| +void GestureRecognizer::AppendTapDownGestureEvent(const TouchEvent& event, |
| + Gestures* gestures) { |
| + gestures->push_back(new GestureEvent(ui::ET_GESTURE_TAP_DOWN, |
| + first_touch_position_.x(), |
|
rjkroege
2011/10/21 20:02:10
Bad indent I think.
Gajen
2011/10/25 14:32:29
Done.
Gajen
2011/10/25 14:32:29
Done.
|
| + 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(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(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(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(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(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().ToDoubleT() - 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().ToDoubleT(); |
| + 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::IsClickOrScroll(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 views |