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