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 // 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
:
53 case TouchHandleOrientation::RIGHT
:
55 case TouchHandleOrientation::CENTER
:
56 return os
<< "CENTER";
57 case TouchHandleOrientation::UNDEFINED
:
58 return os
<< "UNDEFINED";
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()),
69 orientation_(orientation
),
70 deferred_orientation_(TouchHandleOrientation::UNDEFINED
),
72 animate_deferred_fade_(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
)
95 drawable_
->SetEnabled(enabled
);
98 void TouchHandle::SetVisible(bool visible
, AnimationStyle animation_style
) {
100 if (is_visible_
== visible
)
103 is_visible_
= visible
;
105 // Handle repositioning may have been deferred while previously invisible.
107 drawable_
->SetFocus(position_
);
109 bool animate
= animation_style
!= ANIMATION_NONE
;
111 animate_deferred_fade_
= animate
;
121 void TouchHandle::SetPosition(const gfx::PointF
& position
) {
123 if (position_
== position
)
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()|).
130 drawable_
->SetFocus(position_
);
133 void TouchHandle::SetOrientation(TouchHandleOrientation orientation
) {
135 DCHECK_NE(orientation
, TouchHandleOrientation::UNDEFINED
);
137 deferred_orientation_
= orientation
;
140 DCHECK_EQ(deferred_orientation_
, TouchHandleOrientation::UNDEFINED
);
141 if (orientation_
== orientation
)
144 orientation_
= orientation
;
145 drawable_
->SetOrientation(orientation
);
148 bool TouchHandle::WillHandleTouchEvent(const MotionEvent
& event
) {
152 if (!is_dragging_
&& event
.GetAction() != MotionEvent::ACTION_DOWN
)
155 switch (event
.GetAction()) {
156 case MotionEvent::ACTION_DOWN
: {
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 if (!RectIntersectsCircle(drawable_
->GetVisibleBounds(),
169 touch_down_position_
= touch_point
;
170 touch_drag_offset_
= position_
- touch_down_position_
;
171 touch_down_time_
= event
.GetEventTime();
175 case MotionEvent::ACTION_MOVE
: {
176 gfx::PointF
touch_move_position(event
.GetX(), event
.GetY());
177 is_drag_within_tap_region_
&=
178 client_
->IsWithinTapSlop(touch_down_position_
- touch_move_position
);
180 // Note that we signal drag update even if we're inside the tap region,
181 // as there are cases where characters are narrower than the slop length.
182 client_
->OnDragUpdate(*this, touch_move_position
+ touch_drag_offset_
);
185 case MotionEvent::ACTION_UP
: {
186 if (is_drag_within_tap_region_
&&
187 (event
.GetEventTime() - touch_down_time_
) <
188 client_
->GetTapTimeout()) {
189 client_
->OnHandleTapped(*this);
195 case MotionEvent::ACTION_CANCEL
:
205 bool TouchHandle::IsActive() const {
209 bool TouchHandle::Animate(base::TimeTicks frame_time
) {
210 if (fade_end_time_
== base::TimeTicks())
216 1.f
- (fade_end_time_
- frame_time
).InMillisecondsF() / kFadeDurationMs
;
218 (position_
- fade_start_position_
).LengthSquared() / kFadeDistanceSquared
;
219 float u
= std::max(time_u
, position_u
);
220 SetAlpha(is_visible_
? u
: 1.f
- u
);
230 gfx::RectF
TouchHandle::GetVisibleBounds() const {
231 if (!is_visible_
|| !enabled_
)
234 return drawable_
->GetVisibleBounds();
237 void TouchHandle::BeginDrag() {
243 is_drag_within_tap_region_
= true;
244 client_
->OnDragBegin(*this, position());
247 void TouchHandle::EndDrag() {
252 is_dragging_
= false;
253 is_drag_within_tap_region_
= false;
254 client_
->OnDragEnd(*this);
256 if (deferred_orientation_
!= TouchHandleOrientation::UNDEFINED
) {
257 TouchHandleOrientation deferred_orientation
= deferred_orientation_
;
258 deferred_orientation_
= TouchHandleOrientation::UNDEFINED
;
259 SetOrientation(deferred_orientation
);
262 if (animate_deferred_fade_
) {
265 // As drawable visibility assignment is deferred while dragging, push the
266 // change by forcing fade completion.
271 void TouchHandle::BeginFade() {
273 DCHECK(!is_dragging_
);
274 animate_deferred_fade_
= false;
275 const float target_alpha
= is_visible_
? 1.f
: 0.f
;
276 if (target_alpha
== alpha_
) {
281 fade_end_time_
= base::TimeTicks::Now() +
282 base::TimeDelta::FromMillisecondsD(
283 kFadeDurationMs
* std::abs(target_alpha
- alpha_
));
284 fade_start_position_
= position_
;
285 client_
->SetNeedsAnimate();
288 void TouchHandle::EndFade() {
290 animate_deferred_fade_
= false;
291 fade_end_time_
= base::TimeTicks();
292 SetAlpha(is_visible_
? 1.f
: 0.f
);
295 void TouchHandle::SetAlpha(float alpha
) {
296 alpha
= std::max(0.f
, std::min(1.f
, alpha
));
300 drawable_
->SetAlpha(alpha
);