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 if (&handle == start_selection_handle_.get()) {
213 fixed_handle_position_ =
214 end_selection_handle_->position() + GetEndLineOffset();
216 fixed_handle_position_ =
217 start_selection_handle_->position() + GetStartLineOffset();
219 client_->OnSelectionEvent(SELECTION_DRAG_STARTED, handle.position());
222 void TouchSelectionController::OnHandleDragUpdate(const TouchHandle& handle,
223 const gfx::PointF& position) {
224 // As the position corresponds to the bottom left point of the selection
225 // bound, offset it by half the corresponding line height.
226 gfx::Vector2dF line_offset = &handle == end_selection_handle_.get()
227 ? GetStartLineOffset()
228 : GetEndLineOffset();
229 gfx::PointF line_position = position + line_offset;
230 if (&handle == insertion_handle_.get()) {
231 client_->MoveCaret(line_position);
233 client_->SelectBetweenCoordinates(fixed_handle_position_, line_position);
237 void TouchSelectionController::OnHandleDragEnd(const TouchHandle& handle) {
238 if (&handle != insertion_handle_.get())
239 client_->OnSelectionEvent(SELECTION_DRAG_STOPPED, handle.position());
242 void TouchSelectionController::OnHandleTapped(const TouchHandle& handle) {
243 if (insertion_handle_ && &handle == insertion_handle_.get())
244 client_->OnSelectionEvent(INSERTION_TAPPED, handle.position());
247 void TouchSelectionController::SetNeedsAnimate() {
248 client_->SetNeedsAnimate();
251 scoped_ptr<TouchHandleDrawable> TouchSelectionController::CreateDrawable() {
252 return client_->CreateDrawable();
255 base::TimeDelta TouchSelectionController::GetTapTimeout() const {
259 float TouchSelectionController::GetTapSlop() const {
263 void TouchSelectionController::ShowInsertionHandleAutomatically() {
264 if (activate_insertion_automatically_)
266 activate_insertion_automatically_ = true;
267 ResetCachedValuesIfInactive();
270 void TouchSelectionController::ShowSelectionHandlesAutomatically() {
271 if (activate_selection_automatically_)
273 activate_selection_automatically_ = true;
274 ResetCachedValuesIfInactive();
277 void TouchSelectionController::OnInsertionChanged() {
278 DeactivateSelection();
280 if (response_pending_input_event_ == TAP && selection_empty_) {
281 HideAndDisallowShowingAutomatically();
285 if (!activate_insertion_automatically_)
288 const bool was_active = is_insertion_active_;
289 const gfx::PointF position = GetStartPosition();
290 if (!is_insertion_active_)
293 client_->OnSelectionEvent(INSERTION_MOVED, position);
295 insertion_handle_->SetVisible(GetStartVisible(),
296 GetAnimationStyle(was_active));
297 insertion_handle_->SetPosition(position);
300 void TouchSelectionController::OnSelectionChanged() {
301 DeactivateInsertion();
303 if (!activate_selection_automatically_)
306 const bool was_active = is_selection_active_;
309 const TouchHandle::AnimationStyle animation = GetAnimationStyle(was_active);
310 start_selection_handle_->SetVisible(GetStartVisible(), animation);
311 end_selection_handle_->SetVisible(GetEndVisible(), animation);
313 start_selection_handle_->SetPosition(GetStartPosition());
314 end_selection_handle_->SetPosition(GetEndPosition());
317 void TouchSelectionController::ActivateInsertion() {
318 DCHECK(!is_selection_active_);
320 if (!insertion_handle_)
321 insertion_handle_.reset(new TouchHandle(this, TOUCH_HANDLE_CENTER));
323 if (!is_insertion_active_) {
324 is_insertion_active_ = true;
325 insertion_handle_->SetEnabled(true);
326 client_->OnSelectionEvent(INSERTION_SHOWN, GetStartPosition());
330 void TouchSelectionController::DeactivateInsertion() {
331 if (!is_insertion_active_)
333 DCHECK(insertion_handle_);
334 is_insertion_active_ = false;
335 insertion_handle_->SetEnabled(false);
336 client_->OnSelectionEvent(INSERTION_CLEARED, gfx::PointF());
339 void TouchSelectionController::ActivateSelection() {
340 DCHECK(!is_insertion_active_);
342 if (!start_selection_handle_) {
343 start_selection_handle_.reset(new TouchHandle(this, start_orientation_));
345 start_selection_handle_->SetEnabled(true);
346 start_selection_handle_->SetOrientation(start_orientation_);
349 if (!end_selection_handle_) {
350 end_selection_handle_.reset(new TouchHandle(this, end_orientation_));
352 end_selection_handle_->SetEnabled(true);
353 end_selection_handle_->SetOrientation(end_orientation_);
356 // As a long press received while a selection is already active may trigger
357 // an entirely new selection, notify the client but avoid sending an
358 // intervening SELECTION_CLEARED update to avoid unnecessary state changes.
359 if (!is_selection_active_ || response_pending_input_event_ == LONG_PRESS) {
360 is_selection_active_ = true;
361 response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
362 client_->OnSelectionEvent(SELECTION_SHOWN, GetStartPosition());
366 void TouchSelectionController::DeactivateSelection() {
367 if (!is_selection_active_)
369 DCHECK(start_selection_handle_);
370 DCHECK(end_selection_handle_);
371 start_selection_handle_->SetEnabled(false);
372 end_selection_handle_->SetEnabled(false);
373 is_selection_active_ = false;
374 client_->OnSelectionEvent(SELECTION_CLEARED, gfx::PointF());
377 void TouchSelectionController::ResetCachedValuesIfInactive() {
378 if (is_selection_active_ || is_insertion_active_)
380 start_ = cc::ViewportSelectionBound();
381 end_ = cc::ViewportSelectionBound();
382 start_orientation_ = TOUCH_HANDLE_ORIENTATION_UNDEFINED;
383 end_orientation_ = TOUCH_HANDLE_ORIENTATION_UNDEFINED;
386 const gfx::PointF& TouchSelectionController::GetStartPosition() const {
387 return start_.edge_bottom;
390 const gfx::PointF& TouchSelectionController::GetEndPosition() const {
391 return end_.edge_bottom;
394 gfx::Vector2dF TouchSelectionController::GetStartLineOffset() const {
395 return gfx::ScaleVector2d(start_.edge_top - start_.edge_bottom, 0.5f);
398 gfx::Vector2dF TouchSelectionController::GetEndLineOffset() const {
399 return gfx::ScaleVector2d(end_.edge_top - end_.edge_bottom, 0.5f);
402 bool TouchSelectionController::GetStartVisible() const {
403 return start_.visible && !temporarily_hidden_;
406 bool TouchSelectionController::GetEndVisible() const {
407 return end_.visible && !temporarily_hidden_;
410 TouchHandle::AnimationStyle TouchSelectionController::GetAnimationStyle(
411 bool was_active) const {
412 return was_active && client_->SupportsAnimation()
413 ? TouchHandle::ANIMATION_SMOOTH
414 : TouchHandle::ANIMATION_NONE;
417 } // namespace content