Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / ui / touch_selection / touch_handle.cc
blob1925a1a474f09d793b6a983a061a13c3246851f3
1 // Copyright 2014 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/touch_handle.h"
7 #include <cmath>
9 namespace ui {
11 namespace {
13 // Maximum duration of a fade sequence.
14 const double kFadeDurationMs = 200;
16 // Maximum amount of travel for a fade sequence. This avoids handle "ghosting"
17 // when the handle is moving rapidly while the fade is active.
18 const double kFadeDistanceSquared = 20.f * 20.f;
20 // Avoid using an empty touch rect, as it may fail the intersection test event
21 // if it lies within the other rect's bounds.
22 const float kMinTouchMajorForHitTesting = 1.f;
24 // The maximum touch size to use when computing whether a touch point is
25 // targetting a touch handle. This is necessary for devices that misreport
26 // touch radii, preventing inappropriately largely touch sizes from completely
27 // breaking handle dragging behavior.
28 const float kMaxTouchMajorForHitTesting = 36.f;
30 // Note that the intersection region is boundary *exclusive*.
31 bool RectIntersectsCircle(const gfx::RectF& rect,
32 const gfx::PointF& circle_center,
33 float circle_radius) {
34 DCHECK_GT(circle_radius, 0.f);
35 // An intersection occurs if the closest point between the rect and the
36 // circle's center is less than the circle's radius.
37 gfx::PointF closest_point_in_rect(circle_center);
38 closest_point_in_rect.SetToMax(rect.origin());
39 closest_point_in_rect.SetToMin(rect.bottom_right());
41 gfx::Vector2dF distance = circle_center - closest_point_in_rect;
42 return distance.LengthSquared() < (circle_radius * circle_radius);
45 } // namespace
47 // TODO(AviD): Remove this once logging(DCHECK) supports enum class.
48 static std::ostream& operator<<(std::ostream& os,
49 const TouchHandleOrientation& orientation) {
50 switch (orientation) {
51 case TouchHandleOrientation::LEFT:
52 return os << "LEFT";
53 case TouchHandleOrientation::RIGHT:
54 return os << "RIGHT";
55 case TouchHandleOrientation::CENTER:
56 return os << "CENTER";
57 case TouchHandleOrientation::UNDEFINED:
58 return os << "UNDEFINED";
59 default:
60 return os << "INVALID: " << static_cast<int>(orientation);
64 // Responsible for rendering a selection or insertion handle for text editing.
65 TouchHandle::TouchHandle(TouchHandleClient* client,
66 TouchHandleOrientation orientation)
67 : drawable_(client->CreateDrawable()),
68 client_(client),
69 orientation_(orientation),
70 deferred_orientation_(TouchHandleOrientation::UNDEFINED),
71 alpha_(0.f),
72 animate_deferred_fade_(false),
73 enabled_(true),
74 is_visible_(false),
75 is_dragging_(false),
76 is_drag_within_tap_region_(false) {
77 DCHECK_NE(orientation, TouchHandleOrientation::UNDEFINED);
78 drawable_->SetEnabled(enabled_);
79 drawable_->SetOrientation(orientation_);
80 drawable_->SetAlpha(alpha_);
81 drawable_->SetFocus(position_);
84 TouchHandle::~TouchHandle() {
87 void TouchHandle::SetEnabled(bool enabled) {
88 if (enabled_ == enabled)
89 return;
90 if (!enabled) {
91 EndDrag();
92 EndFade();
94 enabled_ = enabled;
95 drawable_->SetEnabled(enabled);
98 void TouchHandle::SetVisible(bool visible, AnimationStyle animation_style) {
99 DCHECK(enabled_);
100 if (is_visible_ == visible)
101 return;
103 is_visible_ = visible;
105 // Handle repositioning may have been deferred while previously invisible.
106 if (visible)
107 drawable_->SetFocus(position_);
109 bool animate = animation_style != ANIMATION_NONE;
110 if (is_dragging_) {
111 animate_deferred_fade_ = animate;
112 return;
115 if (animate)
116 BeginFade();
117 else
118 EndFade();
121 void TouchHandle::SetPosition(const gfx::PointF& position) {
122 DCHECK(enabled_);
123 if (position_ == position)
124 return;
125 position_ = position;
126 // Suppress repositioning a handle while invisible or fading out to prevent it
127 // from "ghosting" outside the visible bounds. The position will be pushed to
128 // the drawable when the handle regains visibility (see |SetVisible()|).
129 if (is_visible_)
130 drawable_->SetFocus(position_);
133 void TouchHandle::SetOrientation(TouchHandleOrientation orientation) {
134 DCHECK(enabled_);
135 DCHECK_NE(orientation, TouchHandleOrientation::UNDEFINED);
136 if (is_dragging_) {
137 deferred_orientation_ = orientation;
138 return;
140 DCHECK_EQ(deferred_orientation_, TouchHandleOrientation::UNDEFINED);
141 if (orientation_ == orientation)
142 return;
144 orientation_ = orientation;
145 drawable_->SetOrientation(orientation);
148 bool TouchHandle::WillHandleTouchEvent(const MotionEvent& event) {
149 if (!enabled_)
150 return false;
152 if (!is_dragging_ && event.GetAction() != MotionEvent::ACTION_DOWN)
153 return false;
155 switch (event.GetAction()) {
156 case MotionEvent::ACTION_DOWN: {
157 if (!is_visible_)
158 return false;
159 const gfx::PointF touch_point(event.GetX(), event.GetY());
160 const float touch_radius = std::max(
161 kMinTouchMajorForHitTesting,
162 std::min(kMaxTouchMajorForHitTesting, event.GetTouchMajor())) * 0.5f;
163 const gfx::RectF drawable_bounds = drawable_->GetVisibleBounds();
164 // Only use the touch radius for targetting if the touch is at or below
165 // the drawable area. This makes it easier to interact with the line of
166 // text above the drawable.
167 if (touch_point.y() < drawable_bounds.y() ||
168 !RectIntersectsCircle(drawable_bounds, touch_point, touch_radius)) {
169 EndDrag();
170 return false;
172 touch_down_position_ = touch_point;
173 touch_drag_offset_ = position_ - touch_down_position_;
174 touch_down_time_ = event.GetEventTime();
175 BeginDrag();
176 } break;
178 case MotionEvent::ACTION_MOVE: {
179 gfx::PointF touch_move_position(event.GetX(), event.GetY());
180 is_drag_within_tap_region_ &=
181 client_->IsWithinTapSlop(touch_down_position_ - touch_move_position);
183 // Note that we signal drag update even if we're inside the tap region,
184 // as there are cases where characters are narrower than the slop length.
185 client_->OnDragUpdate(*this, touch_move_position + touch_drag_offset_);
186 } break;
188 case MotionEvent::ACTION_UP: {
189 if (is_drag_within_tap_region_ &&
190 (event.GetEventTime() - touch_down_time_) <
191 client_->GetMaxTapDuration()) {
192 client_->OnHandleTapped(*this);
195 EndDrag();
196 } break;
198 case MotionEvent::ACTION_CANCEL:
199 EndDrag();
200 break;
202 default:
203 break;
205 return true;
208 bool TouchHandle::IsActive() const {
209 return is_dragging_;
212 bool TouchHandle::Animate(base::TimeTicks frame_time) {
213 if (fade_end_time_ == base::TimeTicks())
214 return false;
216 DCHECK(enabled_);
218 float time_u =
219 1.f - (fade_end_time_ - frame_time).InMillisecondsF() / kFadeDurationMs;
220 float position_u =
221 (position_ - fade_start_position_).LengthSquared() / kFadeDistanceSquared;
222 float u = std::max(time_u, position_u);
223 SetAlpha(is_visible_ ? u : 1.f - u);
225 if (u >= 1.f) {
226 EndFade();
227 return false;
230 return true;
233 gfx::RectF TouchHandle::GetVisibleBounds() const {
234 if (!is_visible_ || !enabled_)
235 return gfx::RectF();
237 return drawable_->GetVisibleBounds();
240 void TouchHandle::BeginDrag() {
241 DCHECK(enabled_);
242 if (is_dragging_)
243 return;
244 EndFade();
245 is_dragging_ = true;
246 is_drag_within_tap_region_ = true;
247 client_->OnDragBegin(*this, position());
250 void TouchHandle::EndDrag() {
251 DCHECK(enabled_);
252 if (!is_dragging_)
253 return;
255 is_dragging_ = false;
256 is_drag_within_tap_region_ = false;
257 client_->OnDragEnd(*this);
259 if (deferred_orientation_ != TouchHandleOrientation::UNDEFINED) {
260 TouchHandleOrientation deferred_orientation = deferred_orientation_;
261 deferred_orientation_ = TouchHandleOrientation::UNDEFINED;
262 SetOrientation(deferred_orientation);
265 if (animate_deferred_fade_) {
266 BeginFade();
267 } else {
268 // As drawable visibility assignment is deferred while dragging, push the
269 // change by forcing fade completion.
270 EndFade();
274 void TouchHandle::BeginFade() {
275 DCHECK(enabled_);
276 DCHECK(!is_dragging_);
277 animate_deferred_fade_ = false;
278 const float target_alpha = is_visible_ ? 1.f : 0.f;
279 if (target_alpha == alpha_) {
280 EndFade();
281 return;
284 fade_end_time_ = base::TimeTicks::Now() +
285 base::TimeDelta::FromMillisecondsD(
286 kFadeDurationMs * std::abs(target_alpha - alpha_));
287 fade_start_position_ = position_;
288 client_->SetNeedsAnimate();
291 void TouchHandle::EndFade() {
292 DCHECK(enabled_);
293 animate_deferred_fade_ = false;
294 fade_end_time_ = base::TimeTicks();
295 SetAlpha(is_visible_ ? 1.f : 0.f);
298 void TouchHandle::SetAlpha(float alpha) {
299 alpha = std::max(0.f, std::min(1.f, alpha));
300 if (alpha_ == alpha)
301 return;
302 alpha_ = alpha;
303 drawable_->SetAlpha(alpha);
306 } // namespace ui