1 // Copyright (c) 2012 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 "ui/base/gestures/gesture_sequence.h" 6 7 #include <cmath> 8 #include <stdlib.h> 9 10 #include "base/command_line.h" 11 #include "base/logging.h" 12 #include "base/memory/scoped_ptr.h" 13 #include "base/strings/string_number_conversions.h" 14 #include "base/time/time.h" 15 #include "ui/base/events/event.h" 16 #include "ui/base/events/event_constants.h" 17 #include "ui/base/gestures/gesture_configuration.h" 18 #include "ui/base/gestures/gesture_util.h" 19 #include "ui/base/ui_base_switches.h" 20 #include "ui/gfx/rect.h" 21 22 namespace ui { 23 24 namespace { 25 26 // ui::EventType is mapped to TouchState so it can fit into 3 bits of 27 // Signature. 28 enum TouchState { 29 TS_RELEASED, 30 TS_PRESSED, 31 TS_MOVED, 32 TS_STATIONARY, 33 TS_CANCELLED, 34 TS_UNKNOWN, 35 }; 36 37 // ui::EventResult is mapped to TouchStatusInternal to simply indicate whether a 38 // processed touch-event should affect gesture-recognition or not. 39 enum TouchStatusInternal { 40 TSI_NOT_PROCESSED, // The touch-event should take-part into 41 // gesture-recognition only if the touch-event has not 42 // been processed. 43 44 TSI_PROCESSED, // The touch-event should affect gesture-recognition only 45 // if the touch-event has been processed. For example,, 46 // this means that a JavaScript touch handler called 47 // |preventDefault| on the associated touch event 48 // or was processed by an aura-window or views-view. 49 50 TSI_ALWAYS // The touch-event should always affect gesture 51 // recognition. 52 }; 53 54 // Get equivalent TouchState from EventType |type|. 55 TouchState TouchEventTypeToTouchState(ui::EventType type) { 56 switch (type) { 57 case ui::ET_TOUCH_RELEASED: 58 return TS_RELEASED; 59 case ui::ET_TOUCH_PRESSED: 60 return TS_PRESSED; 61 case ui::ET_TOUCH_MOVED: 62 return TS_MOVED; 63 case ui::ET_TOUCH_STATIONARY: 64 return TS_STATIONARY; 65 case ui::ET_TOUCH_CANCELLED: 66 return TS_CANCELLED; 67 default: 68 DVLOG(1) << "Unknown Touch Event type"; 69 } 70 return TS_UNKNOWN; 71 } 72 73 // Gesture signature types for different values of combination (GestureState, 74 // touch_id, ui::EventType, touch_handled), see Signature for more info. 75 // 76 // Note: New addition of types should be placed as per their Signature value. 77 #define G(gesture_state, id, touch_state, handled) 1 + ( \ 78 (((touch_state) & 0x7) << 1) | \ 79 ((handled & 0x3) << 4) | \ 80 (((id) & 0xfff) << 6) | \ 81 ((gesture_state) << 18)) 82 83 enum EdgeStateSignatureType { 84 GST_INVALID = -1, 85 86 GST_NO_GESTURE_FIRST_PRESSED = 87 G(GS_NO_GESTURE, 0, TS_PRESSED, TSI_NOT_PROCESSED), 88 89 GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED = 90 G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_RELEASED, TSI_NOT_PROCESSED), 91 92 GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED_HANDLED = 93 G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_RELEASED, TSI_PROCESSED), 94 95 // Ignore processed touch-move events until gesture-scroll starts. 96 GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED = 97 G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_MOVED, TSI_NOT_PROCESSED), 98 99 GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED_PROCESSED = 100 G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_MOVED, TSI_PROCESSED), 101 102 GST_PENDING_SYNTHETIC_CLICK_FIRST_STATIONARY = 103 G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_STATIONARY, TSI_NOT_PROCESSED), 104 105 GST_PENDING_SYNTHETIC_CLICK_FIRST_CANCELLED = 106 G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_CANCELLED, TSI_ALWAYS), 107 108 GST_PENDING_SYNTHETIC_CLICK_SECOND_PRESSED = 109 G(GS_PENDING_SYNTHETIC_CLICK, 1, TS_PRESSED, TSI_NOT_PROCESSED), 110 111 GST_SCROLL_FIRST_RELEASED = 112 G(GS_SCROLL, 0, TS_RELEASED, TSI_ALWAYS), 113 114 // Once scroll has started, process all touch-move events. 115 GST_SCROLL_FIRST_MOVED = 116 G(GS_SCROLL, 0, TS_MOVED, TSI_ALWAYS), 117 118 GST_SCROLL_FIRST_CANCELLED = 119 G(GS_SCROLL, 0, TS_CANCELLED, TSI_ALWAYS), 120 121 GST_SCROLL_SECOND_PRESSED = 122 G(GS_SCROLL, 1, TS_PRESSED, TSI_NOT_PROCESSED), 123 124 GST_PENDING_TWO_FINGER_TAP_FIRST_RELEASED = 125 G(GS_PENDING_TWO_FINGER_TAP, 0, TS_RELEASED, TSI_NOT_PROCESSED), 126 127 GST_PENDING_TWO_FINGER_TAP_FIRST_RELEASED_HANDLED = 128 G(GS_PENDING_TWO_FINGER_TAP, 0, TS_RELEASED, TSI_PROCESSED), 129 130 GST_PENDING_TWO_FINGER_TAP_SECOND_RELEASED = 131 G(GS_PENDING_TWO_FINGER_TAP, 1, TS_RELEASED, TSI_NOT_PROCESSED), 132 133 GST_PENDING_TWO_FINGER_TAP_SECOND_RELEASED_HANDLED = 134 G(GS_PENDING_TWO_FINGER_TAP, 1, TS_RELEASED, TSI_PROCESSED), 135 136 GST_PENDING_TWO_FINGER_TAP_FIRST_MOVED = 137 G(GS_PENDING_TWO_FINGER_TAP, 0, TS_MOVED, TSI_ALWAYS), 138 139 GST_PENDING_TWO_FINGER_TAP_SECOND_MOVED = 140 G(GS_PENDING_TWO_FINGER_TAP, 1, TS_MOVED, TSI_ALWAYS), 141 142 GST_PENDING_TWO_FINGER_TAP_FIRST_CANCELLED = 143 G(GS_PENDING_TWO_FINGER_TAP, 0, TS_CANCELLED, TSI_ALWAYS), 144 145 GST_PENDING_TWO_FINGER_TAP_SECOND_CANCELLED = 146 G(GS_PENDING_TWO_FINGER_TAP, 1, TS_CANCELLED, TSI_ALWAYS), 147 148 GST_PENDING_TWO_FINGER_TAP_THIRD_PRESSED = 149 G(GS_PENDING_TWO_FINGER_TAP, 2, TS_PRESSED, TSI_NOT_PROCESSED), 150 151 GST_PINCH_FIRST_MOVED = 152 G(GS_PINCH, 0, TS_MOVED, TSI_NOT_PROCESSED), 153 154 GST_PINCH_FIRST_MOVED_HANDLED = 155 G(GS_PINCH, 0, TS_MOVED, TSI_PROCESSED), 156 157 GST_PINCH_SECOND_MOVED = 158 G(GS_PINCH, 1, TS_MOVED, TSI_NOT_PROCESSED), 159 160 GST_PINCH_SECOND_MOVED_HANDLED = 161 G(GS_PINCH, 1, TS_MOVED, TSI_PROCESSED), 162 163 GST_PINCH_FIRST_RELEASED = 164 G(GS_PINCH, 0, TS_RELEASED, TSI_ALWAYS), 165 166 GST_PINCH_SECOND_RELEASED = 167 G(GS_PINCH, 1, TS_RELEASED, TSI_ALWAYS), 168 169 GST_PINCH_FIRST_CANCELLED = 170 G(GS_PINCH, 0, TS_CANCELLED, TSI_ALWAYS), 171 172 GST_PINCH_SECOND_CANCELLED = 173 G(GS_PINCH, 1, TS_CANCELLED, TSI_ALWAYS), 174 175 GST_PINCH_THIRD_PRESSED = 176 G(GS_PINCH, 2, TS_PRESSED, TSI_NOT_PROCESSED), 177 178 GST_PINCH_THIRD_MOVED = 179 G(GS_PINCH, 2, TS_MOVED, TSI_NOT_PROCESSED), 180 181 GST_PINCH_THIRD_MOVED_HANDLED = 182 G(GS_PINCH, 2, TS_MOVED, TSI_PROCESSED), 183 184 GST_PINCH_THIRD_RELEASED = 185 G(GS_PINCH, 2, TS_RELEASED, TSI_ALWAYS), 186 187 GST_PINCH_THIRD_CANCELLED = 188 G(GS_PINCH, 2, TS_CANCELLED, TSI_ALWAYS), 189 190 GST_PINCH_FOURTH_PRESSED = 191 G(GS_PINCH, 3, TS_PRESSED, TSI_NOT_PROCESSED), 192 193 GST_PINCH_FOURTH_MOVED = 194 G(GS_PINCH, 3, TS_MOVED, TSI_NOT_PROCESSED), 195 196 GST_PINCH_FOURTH_MOVED_HANDLED = 197 G(GS_PINCH, 3, TS_MOVED, TSI_PROCESSED), 198 199 GST_PINCH_FOURTH_RELEASED = 200 G(GS_PINCH, 3, TS_RELEASED, TSI_ALWAYS), 201 202 GST_PINCH_FOURTH_CANCELLED = 203 G(GS_PINCH, 3, TS_CANCELLED, TSI_ALWAYS), 204 205 GST_PINCH_FIFTH_PRESSED = 206 G(GS_PINCH, 4, TS_PRESSED, TSI_NOT_PROCESSED), 207 208 GST_PINCH_FIFTH_MOVED = 209 G(GS_PINCH, 4, TS_MOVED, TSI_NOT_PROCESSED), 210 211 GST_PINCH_FIFTH_MOVED_HANDLED = 212 G(GS_PINCH, 4, TS_MOVED, TSI_PROCESSED), 213 214 GST_PINCH_FIFTH_RELEASED = 215 G(GS_PINCH, 4, TS_RELEASED, TSI_ALWAYS), 216 217 GST_PINCH_FIFTH_CANCELLED = 218 G(GS_PINCH, 4, TS_CANCELLED, TSI_ALWAYS), 219 }; 220 221 // Builds a signature. Signatures are assembled by joining together 222 // multiple bits. 223 // 1 LSB bit so that the computed signature is always greater than 0 224 // 3 bits for the |type|. 225 // 2 bit for |touch_status| 226 // 12 bits for |touch_id| 227 // 14 bits for the |gesture_state|. 228 EdgeStateSignatureType Signature(GestureState gesture_state, 229 unsigned int touch_id, 230 ui::EventType type, 231 TouchStatusInternal touch_status) { 232 CHECK((touch_id & 0xfff) == touch_id); 233 TouchState touch_state = TouchEventTypeToTouchState(type); 234 EdgeStateSignatureType signature = static_cast<EdgeStateSignatureType> 235 (G(gesture_state, touch_id, touch_state, touch_status)); 236 237 switch (signature) { 238 case GST_NO_GESTURE_FIRST_PRESSED: 239 case GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED: 240 case GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED_HANDLED: 241 case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED: 242 case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED_PROCESSED: 243 case GST_PENDING_SYNTHETIC_CLICK_FIRST_STATIONARY: 244 case GST_PENDING_SYNTHETIC_CLICK_FIRST_CANCELLED: 245 case GST_PENDING_SYNTHETIC_CLICK_SECOND_PRESSED: 246 case GST_SCROLL_FIRST_RELEASED: 247 case GST_SCROLL_FIRST_MOVED: 248 case GST_SCROLL_FIRST_CANCELLED: 249 case GST_SCROLL_SECOND_PRESSED: 250 case GST_PENDING_TWO_FINGER_TAP_FIRST_RELEASED: 251 case GST_PENDING_TWO_FINGER_TAP_FIRST_RELEASED_HANDLED: 252 case GST_PENDING_TWO_FINGER_TAP_SECOND_RELEASED: 253 case GST_PENDING_TWO_FINGER_TAP_SECOND_RELEASED_HANDLED: 254 case GST_PENDING_TWO_FINGER_TAP_FIRST_MOVED: 255 case GST_PENDING_TWO_FINGER_TAP_SECOND_MOVED: 256 case GST_PENDING_TWO_FINGER_TAP_FIRST_CANCELLED: 257 case GST_PENDING_TWO_FINGER_TAP_SECOND_CANCELLED: 258 case GST_PENDING_TWO_FINGER_TAP_THIRD_PRESSED: 259 case GST_PINCH_FIRST_MOVED: 260 case GST_PINCH_FIRST_MOVED_HANDLED: 261 case GST_PINCH_SECOND_MOVED: 262 case GST_PINCH_SECOND_MOVED_HANDLED: 263 case GST_PINCH_FIRST_RELEASED: 264 case GST_PINCH_SECOND_RELEASED: 265 case GST_PINCH_FIRST_CANCELLED: 266 case GST_PINCH_SECOND_CANCELLED: 267 case GST_PINCH_THIRD_PRESSED: 268 case GST_PINCH_THIRD_MOVED: 269 case GST_PINCH_THIRD_MOVED_HANDLED: 270 case GST_PINCH_THIRD_RELEASED: 271 case GST_PINCH_THIRD_CANCELLED: 272 case GST_PINCH_FOURTH_PRESSED: 273 case GST_PINCH_FOURTH_MOVED: 274 case GST_PINCH_FOURTH_MOVED_HANDLED: 275 case GST_PINCH_FOURTH_RELEASED: 276 case GST_PINCH_FOURTH_CANCELLED: 277 case GST_PINCH_FIFTH_PRESSED: 278 case GST_PINCH_FIFTH_MOVED: 279 case GST_PINCH_FIFTH_MOVED_HANDLED: 280 case GST_PINCH_FIFTH_RELEASED: 281 case GST_PINCH_FIFTH_CANCELLED: 282 break; 283 default: 284 signature = GST_INVALID; 285 break; 286 } 287 288 return signature; 289 } 290 #undef G 291 292 float BoundingBoxDiagonal(const gfx::Rect& rect) { 293 float width = rect.width() * rect.width(); 294 float height = rect.height() * rect.height(); 295 return sqrt(width + height); 296 } 297 298 unsigned int ComputeTouchBitmask(const GesturePoint* points) { 299 unsigned int touch_bitmask = 0; 300 for (int i = 0; i < GestureSequence::kMaxGesturePoints; ++i) { 301 if (points[i].in_use()) 302 touch_bitmask |= 1 << points[i].touch_id(); 303 } 304 return touch_bitmask; 305 } 306 307 const float kFlingCurveNormalization = 1.0f / 1875.f; 308 309 float CalibrateFlingVelocity(float velocity) { 310 const unsigned last_coefficient = 311 GestureConfiguration::NumAccelParams - 1; 312 float normalized_velocity = fabs(velocity * kFlingCurveNormalization); 313 float nu = 0.0f, x = 1.f; 314 315 for (int i = last_coefficient ; i >= 0; i--) { 316 float a = GestureConfiguration::fling_acceleration_curve_coefficients(i); 317 nu += x * a; 318 x *= normalized_velocity; 319 } 320 if (velocity < 0.f) 321 return std::max(nu * velocity, -GestureConfiguration::fling_velocity_cap()); 322 else 323 return std::min(nu * velocity, GestureConfiguration::fling_velocity_cap()); 324 } 325 326 } // namespace 327 328 //////////////////////////////////////////////////////////////////////////////// 329 // GestureSequence Public: 330 331 GestureSequence::GestureSequence(GestureEventHelper* helper) 332 : state_(GS_NO_GESTURE), 333 flags_(0), 334 pinch_distance_start_(0.f), 335 pinch_distance_current_(0.f), 336 scroll_type_(ST_FREE), 337 point_count_(0), 338 helper_(helper) { 339 } 340 341 GestureSequence::~GestureSequence() { 342 } 343 344 GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture( 345 const TouchEvent& event, 346 EventResult result) { 347 StopLongPressTimerIfRequired(event); 348 last_touch_location_ = event.location(); 349 if (result & ER_CONSUMED) 350 return NULL; 351 352 // Set a limit on the number of simultaneous touches in a gesture. 353 if (event.touch_id() >= kMaxGesturePoints) 354 return NULL; 355 356 if (event.type() == ui::ET_TOUCH_PRESSED) { 357 if (point_count_ == kMaxGesturePoints) 358 return NULL; 359 GesturePoint* new_point = &points_[event.touch_id()]; 360 // We shouldn't be able to get two PRESSED events from the same 361 // finger without either a RELEASE or CANCEL in between. But let's not crash 362 // in a release build. 363 if (new_point->in_use()) { 364 LOG(ERROR) << "Received a second press for a point: " << event.touch_id(); 365 new_point->ResetVelocity(); 366 new_point->UpdateValues(event); 367 return NULL; 368 } 369 new_point->set_point_id(point_count_++); 370 new_point->set_touch_id(event.touch_id()); 371 } 372 373 GestureState last_state = state_; 374 375 // NOTE: when modifying these state transitions, also update gestures.dot 376 scoped_ptr<Gestures> gestures(new Gestures()); 377 GesturePoint& point = GesturePointForEvent(event); 378 point.UpdateValues(event); 379 RecreateBoundingBox(); 380 flags_ = event.flags(); 381 const int point_id = point.point_id(); 382 if (point_id < 0) 383 return NULL; 384 385 // Send GESTURE_BEGIN for any touch pressed. 386 if (event.type() == ui::ET_TOUCH_PRESSED) 387 AppendBeginGestureEvent(point, gestures.get()); 388 389 TouchStatusInternal status_internal = (result == ER_UNHANDLED) ? 390 TSI_NOT_PROCESSED : TSI_PROCESSED; 391 392 EdgeStateSignatureType signature = Signature(state_, point_id, 393 event.type(), status_internal); 394 395 if (signature == GST_INVALID) 396 signature = Signature(state_, point_id, event.type(), TSI_ALWAYS); 397 398 switch (signature) { 399 case GST_INVALID: 400 break; 401 402 case GST_NO_GESTURE_FIRST_PRESSED: 403 TouchDown(event, point, gestures.get()); 404 set_state(GS_PENDING_SYNTHETIC_CLICK); 405 break; 406 case GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED: 407 if (Click(event, point, gestures.get())) 408 point.UpdateForTap(); 409 else 410 PrependTapCancelGestureEvent(point, gestures.get()); 411 set_state(GS_NO_GESTURE); 412 break; 413 case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED: 414 case GST_PENDING_SYNTHETIC_CLICK_FIRST_STATIONARY: 415 if (ScrollStart(event, point, gestures.get())) { 416 PrependTapCancelGestureEvent(point, gestures.get()); 417 set_state(GS_SCROLL); 418 if (ScrollUpdate(event, point, gestures.get())) 419 point.UpdateForScroll(); 420 } 421 break; 422 case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED_PROCESSED: 423 // TODO(rbyers): This should be able to trigger a TapCancel 424 // if we moved far enough. crbug.com/146397 425 break; 426 case GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED_HANDLED: 427 case GST_PENDING_SYNTHETIC_CLICK_FIRST_CANCELLED: 428 PrependTapCancelGestureEvent(point, gestures.get()); 429 set_state(GS_NO_GESTURE); 430 break; 431 case GST_SCROLL_FIRST_MOVED: 432 if (scroll_type_ == ST_VERTICAL || 433 scroll_type_ == ST_HORIZONTAL) 434 BreakRailScroll(event, point, gestures.get()); 435 if (ScrollUpdate(event, point, gestures.get())) 436 point.UpdateForScroll(); 437 break; 438 case GST_SCROLL_FIRST_RELEASED: 439 case GST_SCROLL_FIRST_CANCELLED: 440 ScrollEnd(event, point, gestures.get()); 441 set_state(GS_NO_GESTURE); 442 break; 443 case GST_PENDING_SYNTHETIC_CLICK_SECOND_PRESSED: 444 PrependTapCancelGestureEvent(point, gestures.get()); 445 if (IsSecondTouchDownCloseEnoughForTwoFingerTap()) { 446 TwoFingerTouchDown(event, point, gestures.get()); 447 set_state(GS_PENDING_TWO_FINGER_TAP); 448 break; 449 } 450 // fall through 451 case GST_SCROLL_SECOND_PRESSED: 452 PinchStart(event, point, gestures.get()); 453 set_state(GS_PINCH); 454 break; 455 case GST_PENDING_TWO_FINGER_TAP_FIRST_RELEASED: 456 case GST_PENDING_TWO_FINGER_TAP_SECOND_RELEASED: 457 TwoFingerTouchReleased(event, point, gestures.get()); 458 set_state(GS_SCROLL); 459 break; 460 case GST_PENDING_TWO_FINGER_TAP_FIRST_MOVED: 461 case GST_PENDING_TWO_FINGER_TAP_SECOND_MOVED: 462 if (TwoFingerTouchMove(event, point, gestures.get())) 463 set_state(GS_PINCH); 464 break; 465 case GST_PENDING_TWO_FINGER_TAP_FIRST_RELEASED_HANDLED: 466 case GST_PENDING_TWO_FINGER_TAP_SECOND_RELEASED_HANDLED: 467 case GST_PENDING_TWO_FINGER_TAP_FIRST_CANCELLED: 468 case GST_PENDING_TWO_FINGER_TAP_SECOND_CANCELLED: 469 scroll_type_ = ST_FREE; 470 set_state(GS_SCROLL); 471 break; 472 case GST_PENDING_TWO_FINGER_TAP_THIRD_PRESSED: 473 PinchStart(event, point, gestures.get()); 474 set_state(GS_PINCH); 475 break; 476 case GST_PINCH_FIRST_MOVED_HANDLED: 477 case GST_PINCH_SECOND_MOVED_HANDLED: 478 case GST_PINCH_THIRD_MOVED_HANDLED: 479 case GST_PINCH_FOURTH_MOVED_HANDLED: 480 case GST_PINCH_FIFTH_MOVED_HANDLED: 481 break; 482 case GST_PINCH_FIRST_MOVED: 483 case GST_PINCH_SECOND_MOVED: 484 case GST_PINCH_THIRD_MOVED: 485 case GST_PINCH_FOURTH_MOVED: 486 case GST_PINCH_FIFTH_MOVED: 487 if (PinchUpdate(event, point, gestures.get())) { 488 for (int i = 0; i < point_count_; ++i) 489 GetPointByPointId(i)->UpdateForScroll(); 490 } 491 break; 492 case GST_PINCH_FIRST_RELEASED: 493 case GST_PINCH_SECOND_RELEASED: 494 case GST_PINCH_THIRD_RELEASED: 495 case GST_PINCH_FOURTH_RELEASED: 496 case GST_PINCH_FIFTH_RELEASED: 497 case GST_PINCH_FIRST_CANCELLED: 498 case GST_PINCH_SECOND_CANCELLED: 499 case GST_PINCH_THIRD_CANCELLED: 500 case GST_PINCH_FOURTH_CANCELLED: 501 case GST_PINCH_FIFTH_CANCELLED: 502 // Was it a swipe? i.e. were all the fingers moving in the same 503 // direction? 504 MaybeSwipe(event, point, gestures.get()); 505 506 if (point_count_ == 2) { 507 PinchEnd(event, point, gestures.get()); 508 509 // Once pinch ends, it should still be possible to scroll with the 510 // remaining finger on the screen. 511 set_state(GS_SCROLL); 512 } else { 513 // Nothing else to do if we have more than 2 fingers active, since after 514 // the release/cancel, there are still enough fingers to do pinch. 515 // pinch_distance_current_ and pinch_distance_start_ will be updated 516 // when the bounding-box is updated. 517 } 518 ResetVelocities(); 519 break; 520 case GST_PINCH_THIRD_PRESSED: 521 case GST_PINCH_FOURTH_PRESSED: 522 case GST_PINCH_FIFTH_PRESSED: 523 pinch_distance_current_ = BoundingBoxDiagonal(bounding_box_); 524 pinch_distance_start_ = pinch_distance_current_; 525 break; 526 } 527 528 if (event.type() == ui::ET_TOUCH_RELEASED || 529 event.type() == ui::ET_TOUCH_CANCELLED) 530 AppendEndGestureEvent(point, gestures.get()); 531 532 if (state_ != last_state) 533 DVLOG(4) << "Gesture Sequence" 534 << " State: " << state_ 535 << " touch id: " << event.touch_id(); 536 537 if (last_state == GS_PENDING_SYNTHETIC_CLICK && state_ != last_state) 538 GetLongPressTimer()->Stop(); 539 540 // The set of point_ids must be contiguous and include 0. 541 // When a touch point is released, all points with ids greater than the 542 // released point must have their ids decremented, or the set of point_ids 543 // could end up with gaps. 544 if (event.type() == ui::ET_TOUCH_RELEASED || 545 event.type() == ui::ET_TOUCH_CANCELLED) { 546 for (int i = 0; i < kMaxGesturePoints; ++i) { 547 GesturePoint& iter_point = points_[i]; 548 if (iter_point.point_id() > point.point_id()) 549 iter_point.set_point_id(iter_point.point_id() - 1); 550 } 551 552 point.Reset(); 553 --point_count_; 554 CHECK_GE(point_count_, 0); 555 RecreateBoundingBox(); 556 if (state_ == GS_PINCH) { 557 pinch_distance_current_ = BoundingBoxDiagonal(bounding_box_); 558 pinch_distance_start_ = pinch_distance_current_; 559 } 560 } 561 562 const ui::LatencyInfo* touch_latency = event.latency(); 563 Gestures::iterator it = gestures->begin(); 564 for (; it != gestures->end(); it++) { 565 (*it)->latency()->MergeWith(*touch_latency); 566 } 567 568 return gestures.release(); 569 } 570 571 void GestureSequence::RecreateBoundingBox() { 572 // TODO(sad): Recreating the bounding box at every touch-event is not very 573 // efficient. This should be made better. 574 if (point_count_ == 0) { 575 bounding_box_.SetRect(0, 0, 0, 0); 576 } else if (point_count_ == 1) { 577 bounding_box_ = GetPointByPointId(0)->enclosing_rectangle(); 578 } else { 579 int left = INT_MAX / 20, top = INT_MAX / 20; 580 int right = INT_MIN / 20, bottom = INT_MIN / 20; 581 for (int i = 0; i < kMaxGesturePoints; ++i) { 582 if (!points_[i].in_use()) 583 continue; 584 // Using the |enclosing_rectangle()| for the touch-points would be ideal. 585 // However, this becomes brittle especially when a finger is in motion 586 // because the change in radius can overshadow the actual change in 587 // position. So the actual position of the point is used instead. 588 const gfx::Point& point = points_[i].last_touch_position(); 589 left = std::min(left, point.x()); 590 right = std::max(right, point.x()); 591 top = std::min(top, point.y()); 592 bottom = std::max(bottom, point.y()); 593 } 594 bounding_box_.SetRect(left, top, right - left, bottom - top); 595 } 596 } 597 598 void GestureSequence::ResetVelocities() { 599 for (int i = 0; i < kMaxGesturePoints; ++i) { 600 if (points_[i].in_use()) 601 points_[i].ResetVelocity(); 602 } 603 } 604 605 //////////////////////////////////////////////////////////////////////////////// 606 // GestureSequence Protected: 607 608 base::OneShotTimer<GestureSequence>* GestureSequence::CreateTimer() { 609 return new base::OneShotTimer<GestureSequence>(); 610 } 611 612 base::OneShotTimer<GestureSequence>* GestureSequence::GetLongPressTimer() { 613 if (!long_press_timer_.get()) 614 long_press_timer_.reset(CreateTimer()); 615 return long_press_timer_.get(); 616 } 617 618 //////////////////////////////////////////////////////////////////////////////// 619 // GestureSequence Private: 620 621 GesturePoint& GestureSequence::GesturePointForEvent( 622 const TouchEvent& event) { 623 return points_[event.touch_id()]; 624 } 625 626 GesturePoint* GestureSequence::GetPointByPointId(int point_id) { 627 DCHECK(0 <= point_id && point_id < kMaxGesturePoints); 628 for (int i = 0; i < kMaxGesturePoints; ++i) { 629 GesturePoint& point = points_[i]; 630 if (point.in_use() && point.point_id() == point_id) 631 return &point; 632 } 633 NOTREACHED(); 634 return NULL; 635 } 636 637 bool GestureSequence::IsSecondTouchDownCloseEnoughForTwoFingerTap() { 638 gfx::Point p1 = GetPointByPointId(0)->last_touch_position(); 639 gfx::Point p2 = GetPointByPointId(1)->last_touch_position(); 640 double max_distance = 641 GestureConfiguration::max_distance_for_two_finger_tap_in_pixels(); 642 double distance = (p1.x() - p2.x()) * (p1.x() - p2.x()) + 643 (p1.y() - p2.y()) * (p1.y() - p2.y()); 644 if (distance < max_distance * max_distance) 645 return true; 646 return false; 647 } 648 649 GestureEvent* GestureSequence::CreateGestureEvent( 650 const GestureEventDetails& details, 651 const gfx::Point& location, 652 int flags, 653 base::Time timestamp, 654 unsigned int touch_id_bitmask) { 655 GestureEventDetails gesture_details(details); 656 gesture_details.set_touch_points(point_count_); 657 gesture_details.set_bounding_box(bounding_box_); 658 base::TimeDelta time_stamp = 659 base::TimeDelta::FromMicroseconds(timestamp.ToDoubleT() * 1000000); 660 return new GestureEvent(gesture_details.type(), location.x(), location.y(), 661 flags, time_stamp, gesture_details, 662 touch_id_bitmask); 663 } 664 665 void GestureSequence::AppendTapDownGestureEvent(const GesturePoint& point, 666 Gestures* gestures) { 667 gestures->push_back(CreateGestureEvent( 668 GestureEventDetails(ui::ET_GESTURE_TAP_DOWN, 0, 0), 669 point.first_touch_position(), 670 flags_, 671 base::Time::FromDoubleT(point.last_touch_time()), 672 1 << point.touch_id())); 673 } 674 675 void GestureSequence::PrependTapCancelGestureEvent(const GesturePoint& point, 676 Gestures* gestures) { 677 gestures->insert(gestures->begin(), CreateGestureEvent( 678 GestureEventDetails(ui::ET_GESTURE_TAP_CANCEL, 0, 0), 679 point.first_touch_position(), 680 flags_, 681 base::Time::FromDoubleT(point.last_touch_time()), 682 1 << point.touch_id())); 683 } 684 685 void GestureSequence::AppendBeginGestureEvent(const GesturePoint& point, 686 Gestures* gestures) { 687 gestures->push_back(CreateGestureEvent( 688 GestureEventDetails(ui::ET_GESTURE_BEGIN, 0, 0), 689 point.first_touch_position(), 690 flags_, 691 base::Time::FromDoubleT(point.last_touch_time()), 692 1 << point.touch_id())); 693 } 694 695 void GestureSequence::AppendEndGestureEvent(const GesturePoint& point, 696 Gestures* gestures) { 697 gestures->push_back(CreateGestureEvent( 698 GestureEventDetails(ui::ET_GESTURE_END, 0, 0), 699 point.first_touch_position(), 700 flags_, 701 base::Time::FromDoubleT(point.last_touch_time()), 702 1 << point.touch_id())); 703 } 704 705 void GestureSequence::AppendClickGestureEvent(const GesturePoint& point, 706 int tap_count, 707 Gestures* gestures) { 708 gfx::Rect er = point.enclosing_rectangle(); 709 gfx::Point center = er.CenterPoint(); 710 gestures->push_back(CreateGestureEvent( 711 GestureEventDetails(ui::ET_GESTURE_TAP, tap_count, 0), 712 center, 713 flags_, 714 base::Time::FromDoubleT(point.last_touch_time()), 715 1 << point.touch_id())); 716 } 717 718 void GestureSequence::AppendScrollGestureBegin(const GesturePoint& point, 719 const gfx::Point& location, 720 Gestures* gestures) { 721 gestures->push_back(CreateGestureEvent( 722 GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN, 0, 0), 723 location, 724 flags_, 725 base::Time::FromDoubleT(point.last_touch_time()), 726 1 << point.touch_id())); 727 } 728 729 void GestureSequence::AppendScrollGestureEnd(const GesturePoint& point, 730 const gfx::Point& location, 731 Gestures* gestures, 732 float x_velocity, 733 float y_velocity) { 734 float railed_x_velocity = x_velocity; 735 float railed_y_velocity = y_velocity; 736 last_scroll_prediction_offset_.set_x(0); 737 last_scroll_prediction_offset_.set_y(0); 738 739 if (scroll_type_ == ST_HORIZONTAL) 740 railed_y_velocity = 0; 741 else if (scroll_type_ == ST_VERTICAL) 742 railed_x_velocity = 0; 743 744 if (railed_x_velocity != 0 || railed_y_velocity != 0) { 745 746 gestures->push_back(CreateGestureEvent( 747 GestureEventDetails(ui::ET_SCROLL_FLING_START, 748 CalibrateFlingVelocity(railed_x_velocity), 749 CalibrateFlingVelocity(railed_y_velocity), 750 CalibrateFlingVelocity(x_velocity), 751 CalibrateFlingVelocity(y_velocity)), 752 location, 753 flags_, 754 base::Time::FromDoubleT(point.last_touch_time()), 755 1 << point.touch_id())); 756 } else { 757 gestures->push_back(CreateGestureEvent( 758 GestureEventDetails(ui::ET_GESTURE_SCROLL_END, 0, 0), 759 location, 760 flags_, 761 base::Time::FromDoubleT(point.last_touch_time()), 762 1 << point.touch_id())); 763 } 764 } 765 766 void GestureSequence::AppendScrollGestureUpdate(GesturePoint& point, 767 Gestures* gestures) { 768 static bool use_scroll_prediction = CommandLine::ForCurrentProcess()-> 769 HasSwitch(switches::kEnableScrollPrediction); 770 gfx::Vector2dF d; 771 gfx::Point location; 772 if (point_count_ == 1) { 773 d = point.ScrollDelta(); 774 location = point.last_touch_position(); 775 } else { 776 location = bounding_box_.CenterPoint(); 777 d = location - latest_multi_scroll_update_location_; 778 latest_multi_scroll_update_location_ = location; 779 } 780 781 if (use_scroll_prediction) { 782 // Remove the extra distance added by the last scroll prediction and add 783 // the new prediction offset. 784 d -= last_scroll_prediction_offset_; 785 last_scroll_prediction_offset_.set_x( 786 GestureConfiguration::scroll_prediction_seconds() * point.XVelocity()); 787 last_scroll_prediction_offset_.set_y( 788 GestureConfiguration::scroll_prediction_seconds() * point.YVelocity()); 789 d += last_scroll_prediction_offset_; 790 location += gfx::Vector2d(last_scroll_prediction_offset_.x(), 791 last_scroll_prediction_offset_.y()); 792 } 793 794 gfx::Vector2dF o = d; 795 796 if (scroll_type_ == ST_HORIZONTAL) 797 d.set_y(0); 798 else if (scroll_type_ == ST_VERTICAL) 799 d.set_x(0); 800 if (d.IsZero()) 801 return; 802 803 GestureEventDetails details(ui::ET_GESTURE_SCROLL_UPDATE, 804 d.x(), d.y(), o.x(), o.y()); 805 details.SetScrollVelocity( 806 scroll_type_ == ST_VERTICAL ? 0 : point.XVelocity(), 807 scroll_type_ == ST_HORIZONTAL ? 0 : point.YVelocity(), 808 point.XVelocity(), 809 point.YVelocity()); 810 gestures->push_back(CreateGestureEvent( 811 details, 812 location, 813 flags_, 814 base::Time::FromDoubleT(point.last_touch_time()), 815 ComputeTouchBitmask(points_))); 816 } 817 818 void GestureSequence::AppendPinchGestureBegin(const GesturePoint& p1, 819 const GesturePoint& p2, 820 Gestures* gestures) { 821 gfx::Point center = bounding_box_.CenterPoint(); 822 gestures->push_back(CreateGestureEvent( 823 GestureEventDetails(ui::ET_GESTURE_PINCH_BEGIN, 0, 0), 824 center, 825 flags_, 826 base::Time::FromDoubleT(p1.last_touch_time()), 827 1 << p1.touch_id() | 1 << p2.touch_id())); 828 } 829 830 void GestureSequence::AppendPinchGestureEnd(const GesturePoint& p1, 831 const GesturePoint& p2, 832 float scale, 833 Gestures* gestures) { 834 gfx::Point center = bounding_box_.CenterPoint(); 835 gestures->push_back(CreateGestureEvent( 836 GestureEventDetails(ui::ET_GESTURE_PINCH_END, 0, 0), 837 center, 838 flags_, 839 base::Time::FromDoubleT(p1.last_touch_time()), 840 1 << p1.touch_id() | 1 << p2.touch_id())); 841 } 842 843 void GestureSequence::AppendPinchGestureUpdate(const GesturePoint& point, 844 float scale, 845 Gestures* gestures) { 846 // TODO(sad): Compute rotation and include it in delta_y. 847 // http://crbug.com/113145 848 gestures->push_back(CreateGestureEvent( 849 GestureEventDetails(ui::ET_GESTURE_PINCH_UPDATE, scale, 0), 850 bounding_box_.CenterPoint(), 851 flags_, 852 base::Time::FromDoubleT(point.last_touch_time()), 853 ComputeTouchBitmask(points_))); 854 } 855 856 void GestureSequence::AppendSwipeGesture(const GesturePoint& point, 857 int swipe_x, 858 int swipe_y, 859 Gestures* gestures) { 860 gestures->push_back(CreateGestureEvent( 861 GestureEventDetails(ui::ET_GESTURE_MULTIFINGER_SWIPE, swipe_x, swipe_y), 862 bounding_box_.CenterPoint(), 863 flags_, 864 base::Time::FromDoubleT(point.last_touch_time()), 865 ComputeTouchBitmask(points_))); 866 } 867 868 void GestureSequence::AppendTwoFingerTapGestureEvent(Gestures* gestures) { 869 const GesturePoint* point = GetPointByPointId(0); 870 const gfx::Rect rect = point->enclosing_rectangle(); 871 gestures->push_back(CreateGestureEvent( 872 GestureEventDetails(ui::ET_GESTURE_TWO_FINGER_TAP, 873 rect.width(), 874 rect.height()), 875 point->enclosing_rectangle().CenterPoint(), 876 flags_, 877 base::Time::FromDoubleT(point->last_touch_time()), 878 1 << point->touch_id())); 879 } 880 881 bool GestureSequence::Click(const TouchEvent& event, 882 const GesturePoint& point, 883 Gestures* gestures) { 884 DCHECK(state_ == GS_PENDING_SYNTHETIC_CLICK); 885 if (point.IsInClickWindow(event)) { 886 int tap_count = 1; 887 if (point.IsInTripleClickWindow(event)) 888 tap_count = 3; 889 else if (point.IsInDoubleClickWindow(event)) 890 tap_count = 2; 891 AppendClickGestureEvent(point, tap_count, gestures); 892 return true; 893 } else if (point.IsInsideManhattanSquare(event) && 894 !GetLongPressTimer()->IsRunning()) { 895 AppendLongTapGestureEvent(point, gestures); 896 } 897 return false; 898 } 899 900 bool GestureSequence::ScrollStart(const TouchEvent& event, 901 GesturePoint& point, 902 Gestures* gestures) { 903 DCHECK(state_ == GS_PENDING_SYNTHETIC_CLICK); 904 if (!point.IsConsistentScrollingActionUnderway() && 905 !point.IsInScrollWindow(event)) 906 return false; 907 AppendScrollGestureBegin(point, point.first_touch_position(), gestures); 908 if (point.IsInHorizontalRailWindow()) 909 scroll_type_ = ST_HORIZONTAL; 910 else if (point.IsInVerticalRailWindow()) 911 scroll_type_ = ST_VERTICAL; 912 else 913 scroll_type_ = ST_FREE; 914 return true; 915 } 916 917 void GestureSequence::BreakRailScroll(const TouchEvent& event, 918 GesturePoint& point, 919 Gestures* gestures) { 920 DCHECK(state_ == GS_SCROLL); 921 if (scroll_type_ == ST_HORIZONTAL && 922 point.BreaksHorizontalRail()) 923 scroll_type_ = ST_FREE; 924 else if (scroll_type_ == ST_VERTICAL && 925 point.BreaksVerticalRail()) 926 scroll_type_ = ST_FREE; 927 } 928 929 bool GestureSequence::ScrollUpdate(const TouchEvent& event, 930 GesturePoint& point, 931 Gestures* gestures) { 932 DCHECK(state_ == GS_SCROLL); 933 if (!point.DidScroll(event, 0)) 934 return false; 935 AppendScrollGestureUpdate(point, gestures); 936 return true; 937 } 938 939 bool GestureSequence::TouchDown(const TouchEvent& event, 940 const GesturePoint& point, 941 Gestures* gestures) { 942 DCHECK(state_ == GS_NO_GESTURE); 943 AppendTapDownGestureEvent(point, gestures); 944 GetLongPressTimer()->Start( 945 FROM_HERE, 946 base::TimeDelta::FromMilliseconds( 947 GestureConfiguration::long_press_time_in_seconds() * 1000), 948 this, 949 &GestureSequence::AppendLongPressGestureEvent); 950 return true; 951 } 952 953 bool GestureSequence::TwoFingerTouchDown(const TouchEvent& event, 954 const GesturePoint& point, 955 Gestures* gestures) { 956 DCHECK(state_ == GS_PENDING_SYNTHETIC_CLICK || state_ == GS_SCROLL); 957 if (state_ == GS_SCROLL) { 958 AppendScrollGestureEnd(point, point.last_touch_position(), gestures, 959 0.f, 0.f); 960 } 961 second_touch_time_ = event.time_stamp(); 962 return true; 963 } 964 965 bool GestureSequence::TwoFingerTouchMove(const TouchEvent& event, 966 const GesturePoint& point, 967 Gestures* gestures) { 968 DCHECK(state_ == GS_PENDING_TWO_FINGER_TAP); 969 970 base::TimeDelta time_delta = event.time_stamp() - second_touch_time_; 971 base::TimeDelta max_delta = base::TimeDelta::FromMilliseconds(1000 * 972 ui::GestureConfiguration::max_touch_down_duration_in_seconds_for_click()); 973 if (time_delta > max_delta || !point.IsInsideManhattanSquare(event)) { 974 PinchStart(event, point, gestures); 975 return true; 976 } 977 return false; 978 } 979 980 bool GestureSequence::TwoFingerTouchReleased(const TouchEvent& event, 981 const GesturePoint& point, 982 Gestures* gestures) { 983 DCHECK(state_ == GS_PENDING_TWO_FINGER_TAP); 984 base::TimeDelta time_delta = event.time_stamp() - second_touch_time_; 985 base::TimeDelta max_delta = base::TimeDelta::FromMilliseconds(1000 * 986 ui::GestureConfiguration::max_touch_down_duration_in_seconds_for_click()); 987 if (time_delta < max_delta && point.IsInsideManhattanSquare(event)) 988 AppendTwoFingerTapGestureEvent(gestures); 989 return true; 990 } 991 992 void GestureSequence::AppendLongPressGestureEvent() { 993 const GesturePoint* point = GetPointByPointId(0); 994 scoped_ptr<GestureEvent> gesture(CreateGestureEvent( 995 GestureEventDetails(ui::ET_GESTURE_LONG_PRESS, 0, 0), 996 point->first_touch_position(), 997 flags_, 998 base::Time::FromDoubleT(point->last_touch_time()), 999 1 << point->touch_id())); 1000 helper_->DispatchLongPressGestureEvent(gesture.get()); 1001 } 1002 1003 void GestureSequence::AppendLongTapGestureEvent(const GesturePoint& point, 1004 Gestures* gestures) { 1005 gfx::Rect er = point.enclosing_rectangle(); 1006 gfx::Point center = er.CenterPoint(); 1007 gestures->push_back(CreateGestureEvent( 1008 GestureEventDetails(ui::ET_GESTURE_LONG_TAP, 0, 0), 1009 center, 1010 flags_, 1011 base::Time::FromDoubleT(point.last_touch_time()), 1012 1 << point.touch_id())); 1013 } 1014 1015 bool GestureSequence::ScrollEnd(const TouchEvent& event, 1016 GesturePoint& point, 1017 Gestures* gestures) { 1018 DCHECK(state_ == GS_SCROLL); 1019 if (point.IsInFlickWindow(event)) { 1020 AppendScrollGestureEnd(point, point.last_touch_position(), gestures, 1021 point.XVelocity(), point.YVelocity()); 1022 } else { 1023 AppendScrollGestureEnd(point, point.last_touch_position(), gestures, 1024 0.f, 0.f); 1025 } 1026 return true; 1027 } 1028 1029 bool GestureSequence::PinchStart(const TouchEvent& event, 1030 const GesturePoint& point, 1031 Gestures* gestures) { 1032 DCHECK(state_ == GS_SCROLL || 1033 state_ == GS_PENDING_SYNTHETIC_CLICK || 1034 state_ == GS_PENDING_TWO_FINGER_TAP); 1035 1036 // Once pinch starts, we immediately break rail scroll. 1037 scroll_type_ = ST_FREE; 1038 1039 const GesturePoint* point1 = GetPointByPointId(0); 1040 const GesturePoint* point2 = GetPointByPointId(1); 1041 1042 pinch_distance_current_ = BoundingBoxDiagonal(bounding_box_); 1043 pinch_distance_start_ = pinch_distance_current_; 1044 latest_multi_scroll_update_location_ = bounding_box_.CenterPoint(); 1045 AppendPinchGestureBegin(*point1, *point2, gestures); 1046 1047 if (state_ == GS_PENDING_SYNTHETIC_CLICK || 1048 state_ == GS_PENDING_TWO_FINGER_TAP) { 1049 gfx::Point center = bounding_box_.CenterPoint(); 1050 AppendScrollGestureBegin(point, center, gestures); 1051 } 1052 1053 return true; 1054 } 1055 1056 bool GestureSequence::PinchUpdate(const TouchEvent& event, 1057 GesturePoint& point, 1058 Gestures* gestures) { 1059 DCHECK(state_ == GS_PINCH); 1060 1061 // It is possible that the none of the touch-points changed their position, 1062 // but their radii changed, and that caused the bounding box to also change. 1063 // But in such cases, we do not want to either pinch or scroll. 1064 // To avoid small jiggles, it is also necessary to make sure that at least one 1065 // of the fingers moved enough before a pinch or scroll update is created. 1066 bool did_scroll = false; 1067 for (int i = 0; i < kMaxGesturePoints; ++i) { 1068 if (!points_[i].in_use() || !points_[i].DidScroll(event, 0)) 1069 continue; 1070 did_scroll = true; 1071 break; 1072 } 1073 1074 if (!did_scroll) 1075 return false; 1076 1077 float distance = BoundingBoxDiagonal(bounding_box_); 1078 1079 if (abs(distance - pinch_distance_current_) >= 1080 GestureConfiguration::min_pinch_update_distance_in_pixels()) { 1081 AppendPinchGestureUpdate(point, 1082 distance / pinch_distance_current_, gestures); 1083 pinch_distance_current_ = distance; 1084 } 1085 AppendScrollGestureUpdate(point, gestures); 1086 1087 return true; 1088 } 1089 1090 bool GestureSequence::PinchEnd(const TouchEvent& event, 1091 const GesturePoint& point, 1092 Gestures* gestures) { 1093 DCHECK(state_ == GS_PINCH); 1094 1095 GesturePoint* point1 = GetPointByPointId(0); 1096 GesturePoint* point2 = GetPointByPointId(1); 1097 1098 float distance = BoundingBoxDiagonal(bounding_box_); 1099 AppendPinchGestureEnd(*point1, *point2, 1100 distance / pinch_distance_start_, gestures); 1101 1102 pinch_distance_start_ = 0; 1103 pinch_distance_current_ = 0; 1104 return true; 1105 } 1106 1107 bool GestureSequence::MaybeSwipe(const TouchEvent& event, 1108 const GesturePoint& point, 1109 Gestures* gestures) { 1110 DCHECK(state_ == GS_PINCH); 1111 float velocity_x = 0.f, velocity_y = 0.f; 1112 bool swipe_x = true, swipe_y = true; 1113 int sign_x = 0, sign_y = 0; 1114 int i = 0; 1115 1116 for (i = 0; i < kMaxGesturePoints; ++i) { 1117 if (points_[i].in_use()) 1118 break; 1119 } 1120 DCHECK(i < kMaxGesturePoints); 1121 1122 velocity_x = points_[i].XVelocity(); 1123 velocity_y = points_[i].YVelocity(); 1124 sign_x = velocity_x < 0.f ? -1 : 1; 1125 sign_y = velocity_y < 0.f ? -1 : 1; 1126 1127 for (++i; i < kMaxGesturePoints; ++i) { 1128 if (!points_[i].in_use()) 1129 continue; 1130 1131 if (sign_x * points_[i].XVelocity() < 0) 1132 swipe_x = false; 1133 1134 if (sign_y * points_[i].YVelocity() < 0) 1135 swipe_y = false; 1136 1137 velocity_x += points_[i].XVelocity(); 1138 velocity_y += points_[i].YVelocity(); 1139 } 1140 1141 float min_velocity = GestureConfiguration::min_swipe_speed(); 1142 min_velocity *= min_velocity; 1143 1144 velocity_x = fabs(velocity_x / point_count_); 1145 velocity_y = fabs(velocity_y / point_count_); 1146 if (velocity_x < min_velocity) 1147 swipe_x = false; 1148 if (velocity_y < min_velocity) 1149 swipe_y = false; 1150 1151 if (!swipe_x && !swipe_y) 1152 return false; 1153 1154 if (!swipe_x) 1155 velocity_x = 0.001f; 1156 if (!swipe_y) 1157 velocity_y = 0.001f; 1158 1159 float ratio = velocity_x > velocity_y ? velocity_x / velocity_y : 1160 velocity_y / velocity_x; 1161 if (ratio < GestureConfiguration::max_swipe_deviation_ratio()) 1162 return false; 1163 1164 if (velocity_x > velocity_y) 1165 sign_y = 0; 1166 else 1167 sign_x = 0; 1168 1169 AppendSwipeGesture(point, sign_x, sign_y, gestures); 1170 1171 return true; 1172 } 1173 1174 void GestureSequence::StopLongPressTimerIfRequired(const TouchEvent& event) { 1175 if (!GetLongPressTimer()->IsRunning() || 1176 event.type() != ui::ET_TOUCH_MOVED) 1177 return; 1178 1179 // Since long press timer has been started, there should be a non-NULL point. 1180 const GesturePoint* point = GetPointByPointId(0); 1181 if (!ui::gestures::IsInsideManhattanSquare(point->first_touch_position(), 1182 event.location())) 1183 GetLongPressTimer()->Stop(); 1184 } 1185 1186 } // namespace ui 1187