Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "views/touchui/gesture_recognizer.h" | |
| 6 | |
| 7 #include "base/logging.h" | |
| 8 #include "base/memory/scoped_ptr.h" | |
| 9 #include "views/events/event.h" | |
| 10 | |
| 11 namespace { | |
| 12 // TODO(Gajen): Make these configurable in sync with this CL http://code.google. | |
| 13 // com/p/chromium/issues/detail?id=100773. | |
| 14 const double kMaximumTouchDownDurationInSecondsForClick = 0.8; | |
| 15 const double kMinimumTouchDownDurationInSecondsForClick = 0.01; | |
| 16 const double kMaximumSecondsBetweenDoubleClick = 0.7; | |
| 17 const int kMaximumTouchMoveInPixelsForClick = 20; | |
| 18 const float kMinFlickSpeedSquared = 550.f * 550.f; | |
| 19 | |
| 20 } // namespace | |
| 21 | |
| 22 namespace views { | |
| 23 | |
| 24 //////////////////////////////////////////////////////////////////////////////// | |
| 25 // GestureRecognizer Public: | |
| 26 | |
| 27 GestureRecognizer::GestureRecognizer() | |
| 28 : first_touch_time_(0.0), | |
| 29 state_(GestureRecognizer::GS_NO_GESTURE), | |
| 30 last_touch_time_(0.0), | |
| 31 last_click_time_(0.0), | |
| 32 x_velocity_(0.0), | |
| 33 y_velocity_(0.0), | |
| 34 flags_(0) { | |
| 35 const unsigned int first_finger = 0; | |
| 36 const ui::EventType released = ui::ET_TOUCH_RELEASED; | |
| 37 const ui::EventType pressed = ui::ET_TOUCH_PRESSED; | |
| 38 const ui::EventType moved = ui::ET_TOUCH_MOVED; | |
| 39 const ui::EventType stationary = ui::ET_TOUCH_STATIONARY; | |
| 40 const ui::EventType cancelled = ui::ET_TOUCH_CANCELLED; | |
| 41 | |
| 42 AddEdgeFunction(GS_NO_GESTURE, first_finger, pressed, false, | |
| 43 &GestureRecognizer::TouchDown); | |
| 44 AddEdgeFunction(GS_PENDING_SYNTHETIC_CLICK, first_finger, cancelled, false, | |
| 45 &GestureRecognizer::NoGesture); | |
| 46 AddEdgeFunction(GS_PENDING_SYNTHETIC_CLICK, first_finger, released, false, | |
| 47 &GestureRecognizer::Click); | |
| 48 AddEdgeFunction(GS_PENDING_SYNTHETIC_CLICK, first_finger, moved, false, | |
| 49 &GestureRecognizer::InClickOrScroll); | |
| 50 AddEdgeFunction(GS_PENDING_SYNTHETIC_CLICK, first_finger, stationary, false, | |
| 51 &GestureRecognizer::InClickOrScroll); | |
| 52 AddEdgeFunction(GS_SCROLL, first_finger, moved, false, | |
| 53 &GestureRecognizer::InScroll); | |
| 54 AddEdgeFunction(GS_SCROLL, first_finger, released, false, | |
| 55 &GestureRecognizer::ScrollEnd); | |
| 56 AddEdgeFunction(GS_SCROLL, first_finger, cancelled, false, | |
| 57 &GestureRecognizer::ScrollEnd); | |
| 58 } | |
| 59 | |
| 60 GestureRecognizer::~GestureRecognizer() { | |
| 61 } | |
| 62 | |
| 63 GestureRecognizer* GestureRecognizer::GetInstance() { | |
| 64 return Singleton<GestureRecognizer>::get(); | |
| 65 } | |
| 66 | |
| 67 GestureRecognizer::Gestures* GestureRecognizer::ProcessTouchEventForGesture( | |
| 68 const TouchEvent& event, | |
| 69 ui::TouchStatus status) { | |
| 70 if (status != ui::TOUCH_STATUS_UNKNOWN) | |
| 71 return false; // The event was consumed by a touch sequence. | |
| 72 | |
| 73 flags_ = event.flags(); | |
| 74 scoped_ptr<Gestures> gestures(new Gestures()); | |
| 75 UpdateValues(event); | |
| 76 GestureTransitionFunctionMap::iterator it = edge_functions_.find( | |
|
sky
2011/11/15 16:54:44
Is there a reason we need this level of complexity
Gajen
2011/11/16 15:08:41
log n to O(1) can be achieved in switch case, but
sky
2011/11/16 16:27:34
Theoretically we could, but will we? The current c
| |
| 77 Signature(state_, event.identity(), event.type(), false)); | |
| 78 if (edge_functions_.end() != it) { | |
| 79 GestureTransitionFunction ef = it->second; | |
| 80 ((*this).*ef)(event, gestures.get()); | |
| 81 } | |
| 82 return gestures.release(); | |
| 83 } | |
| 84 | |
| 85 void GestureRecognizer::Reset() { | |
| 86 first_touch_time_ = 0.0; | |
| 87 state_ = GestureRecognizer::GS_NO_GESTURE; | |
| 88 last_touch_time_ = 0.0; | |
| 89 last_touch_position_.SetPoint(0, 0); | |
| 90 x_velocity_ = 0.0; | |
| 91 y_velocity_ = 0.0; | |
| 92 } | |
| 93 | |
| 94 //////////////////////////////////////////////////////////////////////////////// | |
| 95 // GestureRecognizer Private: | |
| 96 | |
| 97 GestureRecognizer::TouchState GestureRecognizer::TouchEventTypeToTouchState( | |
|
sky
2011/11/15 16:54:44
// static
Gajen
2011/11/16 15:08:41
Done.
| |
| 98 ui::EventType type) { | |
| 99 switch (type) { | |
| 100 case ui::ET_TOUCH_RELEASED: return TS_RELEASED; | |
| 101 case ui::ET_TOUCH_PRESSED: return TS_PRESSED; | |
| 102 case ui::ET_TOUCH_MOVED: return TS_MOVED; | |
| 103 case ui::ET_TOUCH_STATIONARY: return TS_STATIONARY; | |
| 104 case ui::ET_TOUCH_CANCELLED: return TS_CANCELLED; | |
| 105 default: | |
| 106 VLOG(1) << "Unknown Touch Event type"; | |
| 107 } | |
| 108 return TS_UNKNOWN; | |
| 109 } | |
| 110 | |
| 111 // Builds a signature. Signatures are assembled by joining together | |
| 112 // multiple bits. | |
| 113 // 1 LSB bit so that the computed signature is always greater than 0 | |
| 114 // 3 bits for the |type|. | |
| 115 // 1 bit for |touch_handled| | |
| 116 // 12 bits for |touch_id| | |
| 117 // 15 bits for the |gesture_state|. | |
| 118 unsigned int GestureRecognizer::Signature(GestureState gesture_state, | |
| 119 unsigned int touch_id, ui::EventType type, | |
| 120 bool touch_handled) { | |
| 121 CHECK((touch_id & 0xfff) == touch_id); | |
| 122 TouchState touch_state = TouchEventTypeToTouchState(type); | |
| 123 return 1 + ((touch_state & 0x7) << 1 | (touch_handled ? 1 << 4 : 0) | | |
| 124 ((touch_id & 0xfff) << 5) | (gesture_state << 17)); | |
| 125 } | |
| 126 | |
| 127 void GestureRecognizer::AddEdgeFunction(GestureState state, | |
| 128 unsigned int touch_id, | |
| 129 ui::EventType type, | |
| 130 bool touch_handled, | |
| 131 GestureTransitionFunction function) { | |
| 132 edge_functions_[Signature(state, touch_id, type, touch_handled)] = function; | |
| 133 } | |
| 134 | |
| 135 bool GestureRecognizer::IsInClickTimeWindow() { | |
| 136 double duration(last_touch_time_ - first_touch_time_); | |
| 137 return duration >= kMinimumTouchDownDurationInSecondsForClick && | |
| 138 duration < kMaximumTouchDownDurationInSecondsForClick; | |
| 139 } | |
| 140 | |
| 141 bool GestureRecognizer::IsInSecondClickTimeWindow() { | |
| 142 double duration(last_touch_time_ - last_click_time_); | |
| 143 return duration < kMaximumSecondsBetweenDoubleClick; | |
| 144 } | |
| 145 | |
| 146 bool GestureRecognizer::IsInsideManhattanSquare(const TouchEvent& event) { | |
| 147 int manhattanDistance = abs(event.x() - first_touch_position_.x()) + | |
| 148 abs(event.y() - first_touch_position_.y()); | |
| 149 return manhattanDistance < kMaximumTouchMoveInPixelsForClick; | |
| 150 } | |
| 151 | |
| 152 bool GestureRecognizer::IsSecondClickInsideManhattanSquare( | |
| 153 const TouchEvent& event) { | |
| 154 int manhattanDistance = abs(event.x() - last_click_position_.x()) + | |
| 155 abs(event.y() - last_click_position_.y()); | |
| 156 return manhattanDistance < kMaximumTouchMoveInPixelsForClick; | |
| 157 } | |
| 158 | |
| 159 bool GestureRecognizer::IsOverMinFlickSpeed() { | |
| 160 return (x_velocity_ * x_velocity_ + y_velocity_ * y_velocity_) > | |
| 161 kMinFlickSpeedSquared; | |
| 162 } | |
| 163 | |
| 164 void GestureRecognizer::AppendTapDownGestureEvent(const TouchEvent& event, | |
| 165 Gestures* gestures) { | |
| 166 gestures->push_back(linked_ptr<GestureEvent>(new GestureEvent( | |
| 167 ui::ET_GESTURE_TAP_DOWN, | |
| 168 first_touch_position_.x(), | |
| 169 first_touch_position_.y(), | |
| 170 event.flags(), | |
| 171 base::Time::FromDoubleT(last_touch_time_), | |
| 172 0.f, 0.f))); | |
| 173 } | |
| 174 | |
| 175 void GestureRecognizer::AppendClickGestureEvent(const TouchEvent& event, | |
| 176 Gestures* gestures) { | |
| 177 gestures->push_back(linked_ptr<GestureEvent>(new GestureEvent( | |
| 178 ui::ET_GESTURE_TAP, | |
| 179 first_touch_position_.x(), | |
| 180 first_touch_position_.y(), | |
| 181 event.flags(), | |
| 182 base::Time::FromDoubleT(last_touch_time_), | |
| 183 0.f, 0.f))); | |
| 184 } | |
| 185 | |
| 186 void GestureRecognizer::AppendDoubleClickGestureEvent(const TouchEvent& event, | |
| 187 Gestures* gestures) { | |
| 188 gestures->push_back(linked_ptr<GestureEvent>(new GestureEvent( | |
| 189 ui::ET_GESTURE_DOUBLE_TAP, | |
| 190 first_touch_position_.x(), | |
| 191 first_touch_position_.y(), | |
| 192 event.flags(), | |
| 193 base::Time::FromDoubleT(last_touch_time_), | |
| 194 0.f, 0.f))); | |
| 195 } | |
| 196 | |
| 197 void GestureRecognizer::AppendScrollGestureBegin(const TouchEvent& event, | |
| 198 Gestures* gestures) { | |
| 199 gestures->push_back(linked_ptr<GestureEvent>(new GestureEvent( | |
| 200 ui::ET_GESTURE_SCROLL_BEGIN, | |
| 201 event.x(), | |
| 202 event.y(), | |
| 203 event.flags(), | |
| 204 base::Time::FromDoubleT(last_touch_time_), | |
| 205 0.f, 0.f))); | |
| 206 } | |
| 207 | |
| 208 void GestureRecognizer::AppendScrollGestureEnd(const TouchEvent& event, | |
| 209 Gestures* gestures, | |
| 210 float x_velocity, | |
| 211 float y_velocity) { | |
| 212 gestures->push_back(linked_ptr<GestureEvent>(new GestureEvent( | |
| 213 ui::ET_GESTURE_SCROLL_END, | |
| 214 event.x(), | |
| 215 event.y(), | |
| 216 event.flags(), | |
| 217 base::Time::FromDoubleT(last_touch_time_), | |
| 218 x_velocity, y_velocity))); | |
| 219 } | |
| 220 | |
| 221 void GestureRecognizer:: AppendScrollGestureUpdate(const TouchEvent& event, | |
| 222 Gestures* gestures) { | |
| 223 float delta_x(event.x() - first_touch_position_.x()); | |
| 224 float delta_y(event.y() - first_touch_position_.y()); | |
| 225 | |
| 226 gestures->push_back(linked_ptr<GestureEvent>(new GestureEvent( | |
| 227 ui::ET_GESTURE_SCROLL_UPDATE, | |
| 228 event.x(), | |
| 229 event.y(), | |
| 230 event.flags(), | |
| 231 base::Time::FromDoubleT(last_touch_time_), | |
| 232 delta_x, delta_y))); | |
| 233 | |
| 234 first_touch_position_ = event.location(); | |
| 235 } | |
| 236 | |
| 237 void GestureRecognizer::UpdateValues(const TouchEvent& event) { | |
| 238 if (state_ != GS_NO_GESTURE && event.type() == ui::ET_TOUCH_MOVED) { | |
| 239 double interval(event.time_stamp().ToDoubleT() - last_touch_time_); | |
| 240 x_velocity_ = (event.x() - last_touch_position_.x()) / interval; | |
| 241 y_velocity_ = (event.y() - last_touch_position_.y()) / interval; | |
| 242 } | |
| 243 last_touch_time_ = event.time_stamp().ToDoubleT(); | |
| 244 last_touch_position_ = event.location(); | |
| 245 if (state_ == GS_NO_GESTURE) { | |
| 246 first_touch_time_ = last_touch_time_; | |
| 247 first_touch_position_ = event.location(); | |
| 248 x_velocity_ = 0.0; | |
| 249 y_velocity_ = 0.0; | |
| 250 } | |
| 251 } | |
| 252 | |
| 253 bool GestureRecognizer::Click(const TouchEvent& event, Gestures* gestures) { | |
| 254 bool gesture_added = false; | |
| 255 if (IsInClickTimeWindow() && IsInsideManhattanSquare(event)) { | |
| 256 gesture_added = true; | |
| 257 AppendClickGestureEvent(event, gestures); | |
| 258 if (IsInSecondClickTimeWindow() && | |
| 259 IsSecondClickInsideManhattanSquare(event)) | |
| 260 AppendDoubleClickGestureEvent(event, gestures); | |
| 261 last_click_time_ = last_touch_time_; | |
| 262 last_click_position_ = last_touch_position_; | |
| 263 } | |
| 264 Reset(); | |
| 265 return gesture_added; | |
| 266 } | |
| 267 | |
| 268 bool GestureRecognizer::InClickOrScroll(const TouchEvent& event, | |
| 269 Gestures* gestures) { | |
| 270 if (IsInClickTimeWindow() && IsInsideManhattanSquare(event)) { | |
| 271 SetState(GS_PENDING_SYNTHETIC_CLICK); | |
| 272 return false; | |
| 273 } | |
| 274 if (event.type() == ui::ET_TOUCH_MOVED && !IsInsideManhattanSquare(event)) { | |
| 275 AppendScrollGestureBegin(event, gestures); | |
| 276 AppendScrollGestureUpdate(event, gestures); | |
| 277 SetState(GS_SCROLL); | |
| 278 return true; | |
| 279 } | |
| 280 return false; | |
| 281 } | |
| 282 | |
| 283 bool GestureRecognizer::InScroll(const TouchEvent& event, Gestures* gestures) { | |
| 284 AppendScrollGestureUpdate(event, gestures); | |
| 285 return true; | |
| 286 } | |
| 287 | |
| 288 bool GestureRecognizer::NoGesture(const TouchEvent&, Gestures*) { | |
| 289 Reset(); | |
| 290 return false; | |
| 291 } | |
| 292 | |
| 293 bool GestureRecognizer::TouchDown(const TouchEvent& event, Gestures* gestures) { | |
| 294 AppendTapDownGestureEvent(event, gestures); | |
| 295 SetState(GS_PENDING_SYNTHETIC_CLICK); | |
| 296 return false; | |
| 297 } | |
| 298 | |
| 299 bool GestureRecognizer::ScrollEnd(const TouchEvent& event, Gestures* gestures) { | |
| 300 if (IsOverMinFlickSpeed() && event.type() != ui::ET_TOUCH_CANCELLED) | |
| 301 AppendScrollGestureEnd(event, gestures, x_velocity_, y_velocity_); | |
| 302 else | |
| 303 AppendScrollGestureEnd(event, gestures, 0.f, 0.f); | |
| 304 SetState(GS_NO_GESTURE); | |
| 305 Reset(); | |
| 306 return false; | |
| 307 } | |
| 308 | |
| 309 } // namespace views | |
| OLD | NEW |