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,
33 base::TimeDelta tap_timeout,
36 tap_timeout_(tap_timeout),
38 response_pending_input_event_(INPUT_EVENT_TYPE_NONE),
39 start_orientation_(TOUCH_HANDLE_ORIENTATION_UNDEFINED),
40 end_orientation_(TOUCH_HANDLE_ORIENTATION_UNDEFINED),
41 is_insertion_active_(false),
42 activate_insertion_automatically_(false),
43 is_selection_active_(false),
44 activate_selection_automatically_(false),
45 selection_empty_(false),
46 selection_editable_(false),
47 temporarily_hidden_(false) {
49 HideAndDisallowShowingAutomatically();
52 TouchSelectionController::~TouchSelectionController() {
55 void TouchSelectionController::OnSelectionBoundsChanged(
56 const cc::ViewportSelectionBound& start,
57 const cc::ViewportSelectionBound& end) {
58 if (!activate_selection_automatically_ &&
59 !activate_insertion_automatically_) {
60 DCHECK_EQ(INPUT_EVENT_TYPE_NONE, response_pending_input_event_);
64 if (start == start_ && end_ == end)
69 start_orientation_ = ToTouchHandleOrientation(start_.type);
70 end_orientation_ = ToTouchHandleOrientation(end_.type);
72 // Ensure that |response_pending_input_event_| is cleared after the method
73 // completes, while also making its current value available for the duration
75 InputEventType causal_input_event = response_pending_input_event_;
76 response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
77 base::AutoReset<InputEventType> auto_reset_response_pending_input_event(
78 &response_pending_input_event_, causal_input_event);
80 const bool is_selection_dragging =
81 is_selection_active_ && (start_selection_handle_->is_dragging() ||
82 end_selection_handle_->is_dragging());
84 // It's possible that the bounds temporarily overlap while a selection handle
85 // is being dragged, incorrectly reporting a CENTER orientation.
86 // TODO(jdduke): This safeguard is racy, as it's possible the delayed response
87 // from handle positioning occurs *after* the handle dragging has ceased.
88 // Instead, prevent selection -> insertion transitions without an intervening
89 // action or selection clearing of some sort, crbug.com/392696.
90 if (is_selection_dragging) {
91 if (start_orientation_ == TOUCH_HANDLE_CENTER)
92 start_orientation_ = start_selection_handle_->orientation();
93 if (end_orientation_ == TOUCH_HANDLE_CENTER)
94 end_orientation_ = end_selection_handle_->orientation();
97 if (GetStartPosition() != GetEndPosition() ||
98 (is_selection_dragging &&
99 start_orientation_ != TOUCH_HANDLE_ORIENTATION_UNDEFINED &&
100 end_orientation_ != TOUCH_HANDLE_ORIENTATION_UNDEFINED)) {
101 OnSelectionChanged();
105 if (start_orientation_ == TOUCH_HANDLE_CENTER && selection_editable_) {
106 OnInsertionChanged();
110 HideAndDisallowShowingAutomatically();
113 bool TouchSelectionController::WillHandleTouchEvent(
114 const ui::MotionEvent& event) {
115 if (is_insertion_active_) {
116 DCHECK(insertion_handle_);
117 return insertion_handle_->WillHandleTouchEvent(event);
120 if (is_selection_active_) {
121 DCHECK(start_selection_handle_);
122 DCHECK(end_selection_handle_);
123 if (start_selection_handle_->is_dragging())
124 return start_selection_handle_->WillHandleTouchEvent(event);
126 if (end_selection_handle_->is_dragging())
127 return end_selection_handle_->WillHandleTouchEvent(event);
129 const gfx::PointF event_pos(event.GetX(), event.GetY());
130 if ((event_pos - GetStartPosition()).LengthSquared() <=
131 (event_pos - GetEndPosition()).LengthSquared())
132 return start_selection_handle_->WillHandleTouchEvent(event);
134 return end_selection_handle_->WillHandleTouchEvent(event);
140 void TouchSelectionController::OnLongPressEvent() {
141 response_pending_input_event_ = LONG_PRESS;
142 ShowSelectionHandlesAutomatically();
143 ShowInsertionHandleAutomatically();
144 ResetCachedValuesIfInactive();
147 void TouchSelectionController::OnTapEvent() {
148 response_pending_input_event_ = TAP;
149 ShowInsertionHandleAutomatically();
150 if (selection_empty_)
151 DeactivateInsertion();
152 ResetCachedValuesIfInactive();
155 void TouchSelectionController::HideAndDisallowShowingAutomatically() {
156 response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
157 DeactivateInsertion();
158 DeactivateSelection();
159 activate_insertion_automatically_ = false;
160 activate_selection_automatically_ = false;
163 void TouchSelectionController::SetTemporarilyHidden(bool hidden) {
164 if (temporarily_hidden_ == hidden)
166 temporarily_hidden_ = hidden;
168 TouchHandle::AnimationStyle animation_style = GetAnimationStyle(true);
169 if (is_selection_active_) {
170 start_selection_handle_->SetVisible(GetStartVisible(), animation_style);
171 end_selection_handle_->SetVisible(GetEndVisible(), animation_style);
173 if (is_insertion_active_)
174 insertion_handle_->SetVisible(GetStartVisible(), animation_style);
177 void TouchSelectionController::OnSelectionEditable(bool editable) {
178 if (selection_editable_ == editable)
180 selection_editable_ = editable;
181 ResetCachedValuesIfInactive();
182 if (!selection_editable_)
183 DeactivateInsertion();
186 void TouchSelectionController::OnSelectionEmpty(bool empty) {
187 if (selection_empty_ == empty)
189 selection_empty_ = empty;
190 ResetCachedValuesIfInactive();
193 bool TouchSelectionController::Animate(base::TimeTicks frame_time) {
194 if (is_insertion_active_)
195 return insertion_handle_->Animate(frame_time);
197 if (is_selection_active_) {
198 bool needs_animate = start_selection_handle_->Animate(frame_time);
199 needs_animate |= end_selection_handle_->Animate(frame_time);
200 return needs_animate;
206 void TouchSelectionController::OnHandleDragBegin(const TouchHandle& handle) {
207 if (&handle == insertion_handle_.get()) {
208 client_->OnSelectionEvent(INSERTION_DRAG_STARTED, handle.position());
212 gfx::PointF base, extent;
213 if (&handle == start_selection_handle_.get()) {
214 base = end_selection_handle_->position() + GetEndLineOffset();
215 extent = start_selection_handle_->position() + GetStartLineOffset();
217 base = start_selection_handle_->position() + GetStartLineOffset();
218 extent = end_selection_handle_->position() + GetEndLineOffset();
221 // When moving the handle we want to move only the extent point. Before doing
222 // so we must make sure that the base point is set correctly.
223 client_->SelectBetweenCoordinates(base, extent);
225 client_->OnSelectionEvent(SELECTION_DRAG_STARTED, handle.position());
228 void TouchSelectionController::OnHandleDragUpdate(const TouchHandle& handle,
229 const gfx::PointF& position) {
230 // As the position corresponds to the bottom left point of the selection
231 // bound, offset it by half the corresponding line height.
232 gfx::Vector2dF line_offset = &handle == end_selection_handle_.get()
233 ? GetStartLineOffset()
234 : GetEndLineOffset();
235 gfx::PointF line_position = position + line_offset;
236 if (&handle == insertion_handle_.get()) {
237 client_->MoveCaret(line_position);
239 client_->MoveRangeSelectionExtent(line_position);
243 void TouchSelectionController::OnHandleDragEnd(const TouchHandle& handle) {
244 if (&handle != insertion_handle_.get())
245 client_->OnSelectionEvent(SELECTION_DRAG_STOPPED, handle.position());
248 void TouchSelectionController::OnHandleTapped(const TouchHandle& handle) {
249 if (insertion_handle_ && &handle == insertion_handle_.get())
250 client_->OnSelectionEvent(INSERTION_TAPPED, handle.position());
253 void TouchSelectionController::SetNeedsAnimate() {
254 client_->SetNeedsAnimate();
257 scoped_ptr<TouchHandleDrawable> TouchSelectionController::CreateDrawable() {
258 return client_->CreateDrawable();
261 base::TimeDelta TouchSelectionController::GetTapTimeout() const {
265 float TouchSelectionController::GetTapSlop() const {
269 void TouchSelectionController::ShowInsertionHandleAutomatically() {
270 if (activate_insertion_automatically_)
272 activate_insertion_automatically_ = true;
273 ResetCachedValuesIfInactive();
276 void TouchSelectionController::ShowSelectionHandlesAutomatically() {
277 if (activate_selection_automatically_)
279 activate_selection_automatically_ = true;
280 ResetCachedValuesIfInactive();
283 void TouchSelectionController::OnInsertionChanged() {
284 DeactivateSelection();
286 if (response_pending_input_event_ == TAP && selection_empty_) {
287 HideAndDisallowShowingAutomatically();
291 if (!activate_insertion_automatically_)
294 const bool was_active = is_insertion_active_;
295 const gfx::PointF position = GetStartPosition();
296 if (!is_insertion_active_)
299 client_->OnSelectionEvent(INSERTION_MOVED, position);
301 insertion_handle_->SetVisible(GetStartVisible(),
302 GetAnimationStyle(was_active));
303 insertion_handle_->SetPosition(position);
306 void TouchSelectionController::OnSelectionChanged() {
307 DeactivateInsertion();
309 if (!activate_selection_automatically_)
312 const bool was_active = is_selection_active_;
315 const TouchHandle::AnimationStyle animation = GetAnimationStyle(was_active);
316 start_selection_handle_->SetVisible(GetStartVisible(), animation);
317 end_selection_handle_->SetVisible(GetEndVisible(), animation);
319 start_selection_handle_->SetPosition(GetStartPosition());
320 end_selection_handle_->SetPosition(GetEndPosition());
323 void TouchSelectionController::ActivateInsertion() {
324 DCHECK(!is_selection_active_);
326 if (!insertion_handle_)
327 insertion_handle_.reset(new TouchHandle(this, TOUCH_HANDLE_CENTER));
329 if (!is_insertion_active_) {
330 is_insertion_active_ = true;
331 insertion_handle_->SetEnabled(true);
332 client_->OnSelectionEvent(INSERTION_SHOWN, GetStartPosition());
336 void TouchSelectionController::DeactivateInsertion() {
337 if (!is_insertion_active_)
339 DCHECK(insertion_handle_);
340 is_insertion_active_ = false;
341 insertion_handle_->SetEnabled(false);
342 client_->OnSelectionEvent(INSERTION_CLEARED, gfx::PointF());
345 void TouchSelectionController::ActivateSelection() {
346 DCHECK(!is_insertion_active_);
348 if (!start_selection_handle_) {
349 start_selection_handle_.reset(new TouchHandle(this, start_orientation_));
351 start_selection_handle_->SetEnabled(true);
352 start_selection_handle_->SetOrientation(start_orientation_);
355 if (!end_selection_handle_) {
356 end_selection_handle_.reset(new TouchHandle(this, end_orientation_));
358 end_selection_handle_->SetEnabled(true);
359 end_selection_handle_->SetOrientation(end_orientation_);
362 // As a long press received while a selection is already active may trigger
363 // an entirely new selection, notify the client but avoid sending an
364 // intervening SELECTION_CLEARED update to avoid unnecessary state changes.
365 if (!is_selection_active_ || response_pending_input_event_ == LONG_PRESS) {
366 is_selection_active_ = true;
367 response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
368 client_->OnSelectionEvent(SELECTION_SHOWN, GetStartPosition());
372 void TouchSelectionController::DeactivateSelection() {
373 if (!is_selection_active_)
375 DCHECK(start_selection_handle_);
376 DCHECK(end_selection_handle_);
377 start_selection_handle_->SetEnabled(false);
378 end_selection_handle_->SetEnabled(false);
379 is_selection_active_ = false;
380 client_->OnSelectionEvent(SELECTION_CLEARED, gfx::PointF());
383 void TouchSelectionController::ResetCachedValuesIfInactive() {
384 if (is_selection_active_ || is_insertion_active_)
386 start_ = cc::ViewportSelectionBound();
387 end_ = cc::ViewportSelectionBound();
388 start_orientation_ = TOUCH_HANDLE_ORIENTATION_UNDEFINED;
389 end_orientation_ = TOUCH_HANDLE_ORIENTATION_UNDEFINED;
392 const gfx::PointF& TouchSelectionController::GetStartPosition() const {
393 return start_.edge_bottom;
396 const gfx::PointF& TouchSelectionController::GetEndPosition() const {
397 return end_.edge_bottom;
400 gfx::Vector2dF TouchSelectionController::GetStartLineOffset() const {
401 return gfx::ScaleVector2d(start_.edge_top - start_.edge_bottom, 0.5f);
404 gfx::Vector2dF TouchSelectionController::GetEndLineOffset() const {
405 return gfx::ScaleVector2d(end_.edge_top - end_.edge_bottom, 0.5f);
408 bool TouchSelectionController::GetStartVisible() const {
409 return start_.visible && !temporarily_hidden_;
412 bool TouchSelectionController::GetEndVisible() const {
413 return end_.visible && !temporarily_hidden_;
416 TouchHandle::AnimationStyle TouchSelectionController::GetAnimationStyle(
417 bool was_active) const {
418 return was_active && client_->SupportsAnimation()
419 ? TouchHandle::ANIMATION_SMOOTH
420 : TouchHandle::ANIMATION_NONE;
423 } // namespace content