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 "content/browser/renderer_host/input/touch_selection_controller.h"
7 #include "base/auto_reset.h"
8 #include "base/logging.h"
9 #include "third_party/WebKit/public/web/WebInputEvent.h"
14 TouchHandleOrientation
ToTouchHandleOrientation(cc::SelectionBoundType type
) {
16 case cc::SELECTION_BOUND_LEFT
:
17 return TOUCH_HANDLE_LEFT
;
18 case cc::SELECTION_BOUND_RIGHT
:
19 return TOUCH_HANDLE_RIGHT
;
20 case cc::SELECTION_BOUND_CENTER
:
21 return TOUCH_HANDLE_CENTER
;
22 case cc::SELECTION_BOUND_EMPTY
:
23 return TOUCH_HANDLE_ORIENTATION_UNDEFINED
;
25 NOTREACHED() << "Invalid selection bound type: " << type
;
26 return TOUCH_HANDLE_ORIENTATION_UNDEFINED
;
31 TouchSelectionController::TouchSelectionController(
32 TouchSelectionControllerClient
* client
)
34 response_pending_input_event_(INPUT_EVENT_TYPE_NONE
),
35 start_orientation_(TOUCH_HANDLE_ORIENTATION_UNDEFINED
),
36 end_orientation_(TOUCH_HANDLE_ORIENTATION_UNDEFINED
),
37 is_insertion_active_(false),
38 activate_insertion_automatically_(false),
39 is_selection_active_(false),
40 activate_selection_automatically_(false),
41 selection_empty_(false),
42 selection_editable_(false),
43 temporarily_hidden_(false) {
45 HideAndDisallowShowingAutomatically();
48 TouchSelectionController::~TouchSelectionController() {
51 void TouchSelectionController::OnSelectionBoundsChanged(
52 const cc::ViewportSelectionBound
& start
,
53 const cc::ViewportSelectionBound
& end
) {
54 if (!activate_selection_automatically_
&&
55 !activate_insertion_automatically_
) {
56 DCHECK_EQ(INPUT_EVENT_TYPE_NONE
, response_pending_input_event_
);
60 if (start
== start_
&& end_
== end
)
65 start_orientation_
= ToTouchHandleOrientation(start_
.type
);
66 end_orientation_
= ToTouchHandleOrientation(end_
.type
);
68 // Ensure that |response_pending_input_event_| is cleared after the method
69 // completes, while also making its current value available for the duration
71 InputEventType causal_input_event
= response_pending_input_event_
;
72 response_pending_input_event_
= INPUT_EVENT_TYPE_NONE
;
73 base::AutoReset
<InputEventType
> auto_reset_response_pending_input_event(
74 &response_pending_input_event_
, causal_input_event
);
76 const bool is_selection_dragging
=
77 is_selection_active_
&& (start_selection_handle_
->is_dragging() ||
78 end_selection_handle_
->is_dragging());
80 // It's possible that the bounds temporarily overlap while a selection handle
81 // is being dragged, incorrectly reporting a CENTER orientation.
82 // TODO(jdduke): This safeguard is racy, as it's possible the delayed response
83 // from handle positioning occurs *after* the handle dragging has ceased.
84 // Instead, prevent selection -> insertion transitions without an intervening
85 // action or selection clearing of some sort, crbug.com/392696.
86 if (is_selection_dragging
) {
87 if (start_orientation_
== TOUCH_HANDLE_CENTER
)
88 start_orientation_
= start_selection_handle_
->orientation();
89 if (end_orientation_
== TOUCH_HANDLE_CENTER
)
90 end_orientation_
= end_selection_handle_
->orientation();
93 if (GetStartPosition() != GetEndPosition() ||
94 (is_selection_dragging
&&
95 start_orientation_
!= TOUCH_HANDLE_ORIENTATION_UNDEFINED
&&
96 end_orientation_
!= TOUCH_HANDLE_ORIENTATION_UNDEFINED
)) {
101 if (start_orientation_
== TOUCH_HANDLE_CENTER
&& selection_editable_
) {
102 OnInsertionChanged();
106 HideAndDisallowShowingAutomatically();
109 bool TouchSelectionController::WillHandleTouchEvent(
110 const ui::MotionEvent
& event
) {
111 if (is_insertion_active_
) {
112 DCHECK(insertion_handle_
);
113 return insertion_handle_
->WillHandleTouchEvent(event
);
116 if (is_selection_active_
) {
117 DCHECK(start_selection_handle_
);
118 DCHECK(end_selection_handle_
);
119 if (start_selection_handle_
->is_dragging())
120 return start_selection_handle_
->WillHandleTouchEvent(event
);
122 if (end_selection_handle_
->is_dragging())
123 return end_selection_handle_
->WillHandleTouchEvent(event
);
125 const gfx::PointF
event_pos(event
.GetX(), event
.GetY());
126 if ((event_pos
- GetStartPosition()).LengthSquared() <=
127 (event_pos
- GetEndPosition()).LengthSquared())
128 return start_selection_handle_
->WillHandleTouchEvent(event
);
130 return end_selection_handle_
->WillHandleTouchEvent(event
);
136 void TouchSelectionController::OnLongPressEvent() {
137 response_pending_input_event_
= LONG_PRESS
;
138 ShowSelectionHandlesAutomatically();
139 ShowInsertionHandleAutomatically();
140 ResetCachedValuesIfInactive();
143 void TouchSelectionController::OnTapEvent() {
144 response_pending_input_event_
= TAP
;
145 ShowInsertionHandleAutomatically();
146 if (selection_empty_
)
147 DeactivateInsertion();
148 ResetCachedValuesIfInactive();
151 void TouchSelectionController::HideAndDisallowShowingAutomatically() {
152 response_pending_input_event_
= INPUT_EVENT_TYPE_NONE
;
153 DeactivateInsertion();
154 DeactivateSelection();
155 activate_insertion_automatically_
= false;
156 activate_selection_automatically_
= false;
159 void TouchSelectionController::SetTemporarilyHidden(bool hidden
) {
160 if (temporarily_hidden_
== hidden
)
162 temporarily_hidden_
= hidden
;
164 TouchHandle::AnimationStyle animation_style
= GetAnimationStyle(true);
165 if (is_selection_active_
) {
166 start_selection_handle_
->SetVisible(GetStartVisible(), animation_style
);
167 end_selection_handle_
->SetVisible(GetEndVisible(), animation_style
);
169 if (is_insertion_active_
)
170 insertion_handle_
->SetVisible(GetStartVisible(), animation_style
);
173 void TouchSelectionController::OnSelectionEditable(bool editable
) {
174 if (selection_editable_
== editable
)
176 selection_editable_
= editable
;
177 ResetCachedValuesIfInactive();
178 if (!selection_editable_
)
179 DeactivateInsertion();
182 void TouchSelectionController::OnSelectionEmpty(bool empty
) {
183 if (selection_empty_
== empty
)
185 selection_empty_
= empty
;
186 ResetCachedValuesIfInactive();
189 bool TouchSelectionController::Animate(base::TimeTicks frame_time
) {
190 if (is_insertion_active_
)
191 return insertion_handle_
->Animate(frame_time
);
193 if (is_selection_active_
) {
194 bool needs_animate
= start_selection_handle_
->Animate(frame_time
);
195 needs_animate
|= end_selection_handle_
->Animate(frame_time
);
196 return needs_animate
;
202 void TouchSelectionController::OnHandleDragBegin(const TouchHandle
& handle
) {
203 if (&handle
== insertion_handle_
.get()) {
204 client_
->OnSelectionEvent(INSERTION_DRAG_STARTED
, handle
.position());
208 if (&handle
== start_selection_handle_
.get()) {
209 fixed_handle_position_
=
210 end_selection_handle_
->position() + GetEndLineOffset();
212 fixed_handle_position_
=
213 start_selection_handle_
->position() + GetStartLineOffset();
215 client_
->OnSelectionEvent(SELECTION_DRAG_STARTED
, handle
.position());
218 void TouchSelectionController::OnHandleDragUpdate(const TouchHandle
& handle
,
219 const gfx::PointF
& position
) {
220 // As the position corresponds to the bottom left point of the selection
221 // bound, offset it by half the corresponding line height.
222 gfx::Vector2dF line_offset
= &handle
== end_selection_handle_
.get()
223 ? GetStartLineOffset()
224 : GetEndLineOffset();
225 gfx::PointF line_position
= position
+ line_offset
;
226 if (&handle
== insertion_handle_
.get()) {
227 client_
->MoveCaret(line_position
);
229 client_
->SelectBetweenCoordinates(fixed_handle_position_
, line_position
);
233 void TouchSelectionController::OnHandleDragEnd(const TouchHandle
& handle
) {
234 if (&handle
!= insertion_handle_
.get())
235 client_
->OnSelectionEvent(SELECTION_DRAG_STOPPED
, handle
.position());
238 void TouchSelectionController::OnHandleTapped(const TouchHandle
& handle
) {
239 if (insertion_handle_
&& &handle
== insertion_handle_
.get())
240 client_
->OnSelectionEvent(INSERTION_TAPPED
, handle
.position());
243 void TouchSelectionController::SetNeedsAnimate() {
244 client_
->SetNeedsAnimate();
247 scoped_ptr
<TouchHandleDrawable
> TouchSelectionController::CreateDrawable() {
248 return client_
->CreateDrawable();
251 void TouchSelectionController::ShowInsertionHandleAutomatically() {
252 if (activate_insertion_automatically_
)
254 activate_insertion_automatically_
= true;
255 ResetCachedValuesIfInactive();
258 void TouchSelectionController::ShowSelectionHandlesAutomatically() {
259 if (activate_selection_automatically_
)
261 activate_selection_automatically_
= true;
262 ResetCachedValuesIfInactive();
265 void TouchSelectionController::OnInsertionChanged() {
266 DeactivateSelection();
268 if (response_pending_input_event_
== TAP
&& selection_empty_
) {
269 HideAndDisallowShowingAutomatically();
273 if (!activate_insertion_automatically_
)
276 const bool was_active
= is_insertion_active_
;
277 const gfx::PointF position
= GetStartPosition();
278 if (!is_insertion_active_
)
281 client_
->OnSelectionEvent(INSERTION_MOVED
, position
);
283 insertion_handle_
->SetVisible(GetStartVisible(),
284 GetAnimationStyle(was_active
));
285 insertion_handle_
->SetPosition(position
);
288 void TouchSelectionController::OnSelectionChanged() {
289 DeactivateInsertion();
291 if (!activate_selection_automatically_
)
294 const bool was_active
= is_selection_active_
;
297 const TouchHandle::AnimationStyle animation
= GetAnimationStyle(was_active
);
298 start_selection_handle_
->SetVisible(GetStartVisible(), animation
);
299 end_selection_handle_
->SetVisible(GetEndVisible(), animation
);
301 start_selection_handle_
->SetPosition(GetStartPosition());
302 end_selection_handle_
->SetPosition(GetEndPosition());
305 void TouchSelectionController::ActivateInsertion() {
306 DCHECK(!is_selection_active_
);
308 if (!insertion_handle_
)
309 insertion_handle_
.reset(new TouchHandle(this, TOUCH_HANDLE_CENTER
));
311 if (!is_insertion_active_
) {
312 is_insertion_active_
= true;
313 insertion_handle_
->SetEnabled(true);
314 client_
->OnSelectionEvent(INSERTION_SHOWN
, GetStartPosition());
318 void TouchSelectionController::DeactivateInsertion() {
319 if (!is_insertion_active_
)
321 DCHECK(insertion_handle_
);
322 is_insertion_active_
= false;
323 insertion_handle_
->SetEnabled(false);
324 client_
->OnSelectionEvent(INSERTION_CLEARED
, gfx::PointF());
327 void TouchSelectionController::ActivateSelection() {
328 DCHECK(!is_insertion_active_
);
330 if (!start_selection_handle_
) {
331 start_selection_handle_
.reset(new TouchHandle(this, start_orientation_
));
333 start_selection_handle_
->SetEnabled(true);
334 start_selection_handle_
->SetOrientation(start_orientation_
);
337 if (!end_selection_handle_
) {
338 end_selection_handle_
.reset(new TouchHandle(this, end_orientation_
));
340 end_selection_handle_
->SetEnabled(true);
341 end_selection_handle_
->SetOrientation(end_orientation_
);
344 // As a long press received while a selection is already active may trigger
345 // an entirely new selection, notify the client but avoid sending an
346 // intervening SELECTION_CLEARED update to avoid unnecessary state changes.
347 if (!is_selection_active_
|| response_pending_input_event_
== LONG_PRESS
) {
348 is_selection_active_
= true;
349 response_pending_input_event_
= INPUT_EVENT_TYPE_NONE
;
350 client_
->OnSelectionEvent(SELECTION_SHOWN
, GetStartPosition());
354 void TouchSelectionController::DeactivateSelection() {
355 if (!is_selection_active_
)
357 DCHECK(start_selection_handle_
);
358 DCHECK(end_selection_handle_
);
359 start_selection_handle_
->SetEnabled(false);
360 end_selection_handle_
->SetEnabled(false);
361 is_selection_active_
= false;
362 client_
->OnSelectionEvent(SELECTION_CLEARED
, gfx::PointF());
365 void TouchSelectionController::ResetCachedValuesIfInactive() {
366 if (is_selection_active_
|| is_insertion_active_
)
368 start_
= cc::ViewportSelectionBound();
369 end_
= cc::ViewportSelectionBound();
370 start_orientation_
= TOUCH_HANDLE_ORIENTATION_UNDEFINED
;
371 end_orientation_
= TOUCH_HANDLE_ORIENTATION_UNDEFINED
;
374 const gfx::PointF
& TouchSelectionController::GetStartPosition() const {
375 return start_
.edge_bottom
;
378 const gfx::PointF
& TouchSelectionController::GetEndPosition() const {
379 return end_
.edge_bottom
;
382 gfx::Vector2dF
TouchSelectionController::GetStartLineOffset() const {
383 return gfx::ScaleVector2d(start_
.edge_top
- start_
.edge_bottom
, 0.5f
);
386 gfx::Vector2dF
TouchSelectionController::GetEndLineOffset() const {
387 return gfx::ScaleVector2d(end_
.edge_top
- end_
.edge_bottom
, 0.5f
);
390 bool TouchSelectionController::GetStartVisible() const {
391 return start_
.visible
&& !temporarily_hidden_
;
394 bool TouchSelectionController::GetEndVisible() const {
395 return end_
.visible
&& !temporarily_hidden_
;
398 TouchHandle::AnimationStyle
TouchSelectionController::GetAnimationStyle(
399 bool was_active
) const {
400 return was_active
&& client_
->SupportsAnimation()
401 ? TouchHandle::ANIMATION_SMOOTH
402 : TouchHandle::ANIMATION_NONE
;
405 } // namespace content