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 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
)) {
172 touch_down_position_
= touch_point
;
173 touch_drag_offset_
= position_
- touch_down_position_
;
174 touch_down_time_
= event
.GetEventTime();
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_
);
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);
198 case MotionEvent::ACTION_CANCEL
:
208 bool TouchHandle::IsActive() const {
212 bool TouchHandle::Animate(base::TimeTicks frame_time
) {
213 if (fade_end_time_
== base::TimeTicks())
219 1.f
- (fade_end_time_
- frame_time
).InMillisecondsF() / kFadeDurationMs
;
221 (position_
- fade_start_position_
).LengthSquared() / kFadeDistanceSquared
;
222 float u
= std::max(time_u
, position_u
);
223 SetAlpha(is_visible_
? u
: 1.f
- u
);
233 gfx::RectF
TouchHandle::GetVisibleBounds() const {
234 if (!is_visible_
|| !enabled_
)
237 return drawable_
->GetVisibleBounds();
240 void TouchHandle::BeginDrag() {
246 is_drag_within_tap_region_
= true;
247 client_
->OnDragBegin(*this, position());
250 void TouchHandle::EndDrag() {
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_
) {
268 // As drawable visibility assignment is deferred while dragging, push the
269 // change by forcing fade completion.
274 void TouchHandle::BeginFade() {
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_
) {
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() {
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
));
303 drawable_
->SetAlpha(alpha
);