longpress_drag_selector.cc 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // Copyright 2015 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. #include "ui/touch_selection/longpress_drag_selector.h"
  5. #include "base/auto_reset.h"
  6. #include "ui/events/gesture_detection/motion_event.h"
  7. namespace ui {
  8. namespace {
  9. gfx::Vector2dF SafeNormalize(const gfx::Vector2dF& v) {
  10. return v.IsZero() ? v : ScaleVector2d(v, 1.f / v.Length());
  11. }
  12. } // namespace
  13. LongPressDragSelector::LongPressDragSelector(
  14. LongPressDragSelectorClient* client)
  15. : client_(client),
  16. state_(INACTIVE),
  17. has_longpress_drag_start_anchor_(false) {
  18. }
  19. LongPressDragSelector::~LongPressDragSelector() {
  20. }
  21. bool LongPressDragSelector::WillHandleTouchEvent(const MotionEvent& event) {
  22. switch (event.GetAction()) {
  23. case MotionEvent::Action::DOWN:
  24. touch_down_position_.SetPoint(event.GetX(), event.GetY());
  25. touch_down_time_ = event.GetEventTime();
  26. has_longpress_drag_start_anchor_ = false;
  27. SetState(LONGPRESS_PENDING);
  28. return false;
  29. case MotionEvent::Action::UP:
  30. case MotionEvent::Action::CANCEL:
  31. SetState(INACTIVE);
  32. return false;
  33. case MotionEvent::Action::MOVE:
  34. break;
  35. default:
  36. return false;
  37. }
  38. if (state_ != DRAG_PENDING && state_ != DRAGGING)
  39. return false;
  40. gfx::PointF position(event.GetX(), event.GetY());
  41. if (state_ == DRAGGING) {
  42. gfx::PointF drag_position = position + longpress_drag_selection_offset_;
  43. client_->OnDragUpdate(*this, drag_position);
  44. return true;
  45. }
  46. // We can't use |touch_down_position_| as the offset anchor, as
  47. // showing the selection UI may have shifted the motion coordinates.
  48. if (!has_longpress_drag_start_anchor_) {
  49. has_longpress_drag_start_anchor_ = true;
  50. longpress_drag_start_anchor_ = position;
  51. return true;
  52. }
  53. // Allow an additional slop affordance after the longpress occurs.
  54. gfx::Vector2dF delta = position - longpress_drag_start_anchor_;
  55. if (client_->IsWithinTapSlop(delta))
  56. return true;
  57. gfx::PointF selection_start = client_->GetSelectionStart();
  58. gfx::PointF selection_end = client_->GetSelectionEnd();
  59. bool extend_selection_start = false;
  60. if (std::abs(delta.y()) > std::abs(delta.x())) {
  61. // If initial motion is up/down, extend the start/end selection bound.
  62. extend_selection_start = delta.y() < 0;
  63. } else {
  64. // Otherwise extend the selection bound toward which we're moving, or
  65. // the closest bound if motion is already away from both bounds.
  66. // Note that, for mixed RTL text, or for multiline selections triggered
  67. // by longpress, this may not pick the most suitable drag target
  68. gfx::Vector2dF start_delta = selection_start - longpress_drag_start_anchor_;
  69. gfx::Vector2dF end_delta = selection_end - longpress_drag_start_anchor_;
  70. // The vectors must be normalized to make dot product comparison meaningful.
  71. gfx::Vector2dF normalized_start_delta = SafeNormalize(start_delta);
  72. gfx::Vector2dF normalized_end_delta = SafeNormalize(end_delta);
  73. double start_dot_product = gfx::DotProduct(normalized_start_delta, delta);
  74. double end_dot_product = gfx::DotProduct(normalized_end_delta, delta);
  75. if (start_dot_product >= 0 || end_dot_product >= 0) {
  76. // The greater the dot product the more similar the direction.
  77. extend_selection_start = start_dot_product > end_dot_product;
  78. } else {
  79. // If we're already moving away from both endpoints, pick the closest.
  80. extend_selection_start =
  81. start_delta.LengthSquared() < end_delta.LengthSquared();
  82. }
  83. }
  84. gfx::PointF extent = extend_selection_start ? selection_start : selection_end;
  85. longpress_drag_selection_offset_ = extent - position;
  86. client_->OnDragBegin(*this, extent);
  87. SetState(DRAGGING);
  88. return true;
  89. }
  90. bool LongPressDragSelector::IsActive() const {
  91. return state_ == DRAG_PENDING || state_ == DRAGGING;
  92. }
  93. void LongPressDragSelector::OnLongPressEvent(base::TimeTicks event_time,
  94. const gfx::PointF& position) {
  95. // We have no guarantees that the current gesture stream is aligned with the
  96. // observed touch stream. We only know that the gesture sequence is downstream
  97. // from the touch sequence. Using a time/distance heuristic helps ensure that
  98. // the observed longpress corresponds to the active touch sequence.
  99. if (state_ == LONGPRESS_PENDING &&
  100. // Ensure the down event occurs *before* the longpress event. Use a
  101. // small time epsilon to account for floating point time conversion.
  102. (touch_down_time_ < event_time + base::Microseconds(10)) &&
  103. client_->IsWithinTapSlop(touch_down_position_ - position)) {
  104. SetState(SELECTION_PENDING);
  105. }
  106. }
  107. void LongPressDragSelector::OnScrollBeginEvent() {
  108. SetState(INACTIVE);
  109. }
  110. void LongPressDragSelector::OnSelectionActivated() {
  111. if (state_ == SELECTION_PENDING)
  112. SetState(DRAG_PENDING);
  113. }
  114. void LongPressDragSelector::OnSelectionDeactivated() {
  115. SetState(INACTIVE);
  116. }
  117. void LongPressDragSelector::SetState(SelectionState state) {
  118. if (state_ == state)
  119. return;
  120. const bool was_dragging = state_ == DRAGGING;
  121. const bool was_active = IsActive();
  122. state_ = state;
  123. // TODO(jdduke): Add UMA for tracking relative longpress drag frequency.
  124. if (was_dragging)
  125. client_->OnDragEnd(*this);
  126. if (was_active != IsActive())
  127. client_->OnLongPressDragActiveStateChanged();
  128. }
  129. } // namespace ui