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"
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
);
47 // Responsible for rendering a selection or insertion handle for text editing.
48 TouchHandle::TouchHandle(TouchHandleClient
* client
,
49 TouchHandleOrientation orientation
)
50 : drawable_(client
->CreateDrawable()),
52 orientation_(orientation
),
53 deferred_orientation_(TOUCH_HANDLE_ORIENTATION_UNDEFINED
),
55 animate_deferred_fade_(false),
59 is_drag_within_tap_region_(false) {
60 DCHECK_NE(orientation
, TOUCH_HANDLE_ORIENTATION_UNDEFINED
);
61 drawable_
->SetEnabled(enabled_
);
62 drawable_
->SetOrientation(orientation_
);
63 drawable_
->SetAlpha(alpha_
);
64 drawable_
->SetFocus(position_
);
67 TouchHandle::~TouchHandle() {
70 void TouchHandle::SetEnabled(bool enabled
) {
71 if (enabled_
== enabled
)
78 drawable_
->SetEnabled(enabled
);
81 void TouchHandle::SetVisible(bool visible
, AnimationStyle animation_style
) {
83 if (is_visible_
== visible
)
86 is_visible_
= visible
;
88 // Handle repositioning may have been deferred while previously invisible.
90 drawable_
->SetFocus(position_
);
92 bool animate
= animation_style
!= ANIMATION_NONE
;
94 animate_deferred_fade_
= animate
;
104 void TouchHandle::SetPosition(const gfx::PointF
& position
) {
106 if (position_
== position
)
108 position_
= position
;
109 // Suppress repositioning a handle while invisible or fading out to prevent it
110 // from "ghosting" outside the visible bounds. The position will be pushed to
111 // the drawable when the handle regains visibility (see |SetVisible()|).
113 drawable_
->SetFocus(position_
);
116 void TouchHandle::SetOrientation(TouchHandleOrientation orientation
) {
118 DCHECK_NE(orientation
, TOUCH_HANDLE_ORIENTATION_UNDEFINED
);
120 deferred_orientation_
= orientation
;
123 DCHECK_EQ(deferred_orientation_
, TOUCH_HANDLE_ORIENTATION_UNDEFINED
);
124 if (orientation_
== orientation
)
127 orientation_
= orientation
;
128 drawable_
->SetOrientation(orientation
);
131 bool TouchHandle::WillHandleTouchEvent(const MotionEvent
& event
) {
135 if (!is_dragging_
&& event
.GetAction() != MotionEvent::ACTION_DOWN
)
138 switch (event
.GetAction()) {
139 case MotionEvent::ACTION_DOWN
: {
142 const gfx::PointF
touch_point(event
.GetX(), event
.GetY());
143 const float touch_radius
= std::max(
144 kMinTouchMajorForHitTesting
,
145 std::min(kMaxTouchMajorForHitTesting
, event
.GetTouchMajor())) * 0.5f
;
146 if (!RectIntersectsCircle(drawable_
->GetVisibleBounds(),
152 touch_down_position_
= touch_point
;
153 touch_to_focus_offset_
= position_
- touch_down_position_
;
154 touch_down_time_
= event
.GetEventTime();
158 case MotionEvent::ACTION_MOVE
: {
159 gfx::PointF
touch_move_position(event
.GetX(), event
.GetY());
160 if (is_drag_within_tap_region_
) {
161 const float tap_slop
= client_
->GetTapSlop();
162 is_drag_within_tap_region_
=
163 (touch_move_position
- touch_down_position_
).LengthSquared() <
167 // Note that we signal drag update even if we're inside the tap region,
168 // as there are cases where characters are narrower than the slop length.
169 client_
->OnHandleDragUpdate(*this,
170 touch_move_position
+ touch_to_focus_offset_
);
173 case MotionEvent::ACTION_UP
: {
174 if (is_drag_within_tap_region_
&&
175 (event
.GetEventTime() - touch_down_time_
) <
176 client_
->GetTapTimeout()) {
177 client_
->OnHandleTapped(*this);
183 case MotionEvent::ACTION_CANCEL
:
193 bool TouchHandle::Animate(base::TimeTicks frame_time
) {
194 if (fade_end_time_
== base::TimeTicks())
200 1.f
- (fade_end_time_
- frame_time
).InMillisecondsF() / kFadeDurationMs
;
202 (position_
- fade_start_position_
).LengthSquared() / kFadeDistanceSquared
;
203 float u
= std::max(time_u
, position_u
);
204 SetAlpha(is_visible_
? u
: 1.f
- u
);
214 void TouchHandle::BeginDrag() {
220 is_drag_within_tap_region_
= true;
221 client_
->OnHandleDragBegin(*this);
224 void TouchHandle::EndDrag() {
229 is_dragging_
= false;
230 is_drag_within_tap_region_
= false;
231 client_
->OnHandleDragEnd(*this);
233 if (deferred_orientation_
!= TOUCH_HANDLE_ORIENTATION_UNDEFINED
) {
234 TouchHandleOrientation deferred_orientation
= deferred_orientation_
;
235 deferred_orientation_
= TOUCH_HANDLE_ORIENTATION_UNDEFINED
;
236 SetOrientation(deferred_orientation
);
239 if (animate_deferred_fade_
) {
242 // As drawable visibility assignment is deferred while dragging, push the
243 // change by forcing fade completion.
248 void TouchHandle::BeginFade() {
250 DCHECK(!is_dragging_
);
251 animate_deferred_fade_
= false;
252 const float target_alpha
= is_visible_
? 1.f
: 0.f
;
253 if (target_alpha
== alpha_
) {
258 fade_end_time_
= base::TimeTicks::Now() +
259 base::TimeDelta::FromMillisecondsD(
260 kFadeDurationMs
* std::abs(target_alpha
- alpha_
));
261 fade_start_position_
= position_
;
262 client_
->SetNeedsAnimate();
265 void TouchHandle::EndFade() {
267 animate_deferred_fade_
= false;
268 fade_end_time_
= base::TimeTicks();
269 SetAlpha(is_visible_
? 1.f
: 0.f
);
272 void TouchHandle::SetAlpha(float alpha
) {
273 alpha
= std::max(0.f
, std::min(1.f
, alpha
));
277 drawable_
->SetAlpha(alpha
);