| 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..c60d73f764b489fd6d36e584b8139badbfd5ea70
|
| --- /dev/null
|
| +++ b/views/touchui/gesture_recognizer.cc
|
| @@ -0,0 +1,308 @@
|
| +// 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;
|
| +double minimumTouchDownDurationInSecondsForClick = 0.01;
|
| +double maximumSecondsBetweenDoubleClick = 0.7;
|
| +int maximumTouchMoveInPixelsForClick = 20;
|
| +float minFlickSpeedSquared = 550.f * 550.f;
|
| +
|
| +} // namespace
|
| +
|
| +namespace views {
|
| +
|
| +GestureRecognizer::PassGestureEvent::PassGestureEvent(GestureEvent* event)
|
| + : event_(event) {
|
| +}
|
| +
|
| +GestureRecognizer::PassGestureEvent::~PassGestureEvent() {
|
| + free(event_);
|
| + event_ = NULL;
|
| +}
|
| +
|
| +////////////////////////////////////////////////////////////////////////////////
|
| +// 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(PassGestureEvent(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(PassGestureEvent(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(PassGestureEvent(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(PassGestureEvent(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(PassGestureEvent(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(PassGestureEvent(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
|
|
|