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.
4
5#include "content/browser/renderer_host/input/touch_handle.h"
6
7#include <cmath>
8
9namespace content {
10
11namespace {
12
13// Maximum duration of a fade sequence.
14const double kFadeDurationMs = 200;
15
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.
18const double kFadeDistanceSquared = 20.f * 20.f;
19
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.
22const float kMinTouchMajorForHitTesting = 1.f;
23
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.
28const float kMaxTouchMajorForHitTesting = 36.f;
29
30}  // namespace
31
32// Responsible for rendering a selection or insertion handle for text editing.
33TouchHandle::TouchHandle(TouchHandleClient* client,
34                         TouchHandleOrientation orientation)
35    : drawable_(client->CreateDrawable()),
36      client_(client),
37      orientation_(orientation),
38      deferred_orientation_(TOUCH_HANDLE_ORIENTATION_UNDEFINED),
39      alpha_(0.f),
40      animate_deferred_fade_(false),
41      enabled_(true),
42      is_visible_(false),
43      is_dragging_(false),
44      is_drag_within_tap_region_(false) {
45  DCHECK_NE(orientation, TOUCH_HANDLE_ORIENTATION_UNDEFINED);
46  drawable_->SetEnabled(enabled_);
47  drawable_->SetOrientation(orientation_);
48  drawable_->SetAlpha(alpha_);
49  drawable_->SetVisible(is_visible_);
50  drawable_->SetFocus(position_);
51}
52
53TouchHandle::~TouchHandle() {
54}
55
56void TouchHandle::SetEnabled(bool enabled) {
57  if (enabled_ == enabled)
58    return;
59  if (!enabled) {
60    EndDrag();
61    EndFade();
62  }
63  enabled_ = enabled;
64  drawable_->SetEnabled(enabled);
65}
66
67void TouchHandle::SetVisible(bool visible, AnimationStyle animation_style) {
68  DCHECK(enabled_);
69  if (is_visible_ == visible)
70    return;
71
72  is_visible_ = visible;
73
74  // Handle repositioning may have been deferred while previously invisible.
75  if (visible)
76    drawable_->SetFocus(position_);
77
78  bool animate = animation_style != ANIMATION_NONE;
79  if (is_dragging_) {
80    animate_deferred_fade_ = animate;
81    return;
82  }
83
84  if (animate)
85    BeginFade();
86  else
87    EndFade();
88}
89
90void TouchHandle::SetPosition(const gfx::PointF& position) {
91  DCHECK(enabled_);
92  if (position_ == position)
93    return;
94  position_ = position;
95  // Suppress repositioning a handle while invisible or fading out to prevent it
96  // from "ghosting" outside the visible bounds. The position will be pushed to
97  // the drawable when the handle regains visibility (see |SetVisible()|).
98  if (is_visible_)
99    drawable_->SetFocus(position_);
100}
101
102void TouchHandle::SetOrientation(TouchHandleOrientation orientation) {
103  DCHECK(enabled_);
104  DCHECK_NE(orientation, TOUCH_HANDLE_ORIENTATION_UNDEFINED);
105  if (is_dragging_) {
106    deferred_orientation_ = orientation;
107    return;
108  }
109  DCHECK_EQ(deferred_orientation_, TOUCH_HANDLE_ORIENTATION_UNDEFINED);
110  if (orientation_ == orientation)
111    return;
112
113  orientation_ = orientation;
114  drawable_->SetOrientation(orientation);
115}
116
117bool TouchHandle::WillHandleTouchEvent(const ui::MotionEvent& event) {
118  if (!enabled_)
119    return false;
120
121  if (!is_dragging_ && event.GetAction() != ui::MotionEvent::ACTION_DOWN)
122    return false;
123
124  switch (event.GetAction()) {
125    case ui::MotionEvent::ACTION_DOWN: {
126      if (!is_visible_)
127        return false;
128      const float touch_size = std::max(
129          kMinTouchMajorForHitTesting,
130          std::min(kMaxTouchMajorForHitTesting, event.GetTouchMajor()));
131      const gfx::RectF touch_rect(event.GetX() - touch_size * .5f,
132                                  event.GetY() - touch_size * .5f,
133                                  touch_size,
134                                  touch_size);
135      if (!drawable_->IntersectsWith(touch_rect))
136        return false;
137      touch_down_position_ = gfx::PointF(event.GetX(), event.GetY());
138      touch_to_focus_offset_ = position_ - touch_down_position_;
139      touch_down_time_ = event.GetEventTime();
140      BeginDrag();
141    } break;
142
143    case ui::MotionEvent::ACTION_MOVE: {
144      gfx::PointF touch_move_position(event.GetX(), event.GetY());
145      if (is_drag_within_tap_region_) {
146        const float tap_slop = client_->GetTapSlop();
147        is_drag_within_tap_region_ =
148            (touch_move_position - touch_down_position_).LengthSquared() <
149            tap_slop * tap_slop;
150      }
151
152      // Note that we signal drag update even if we're inside the tap region,
153      // as there are cases where characters are narrower than the slop length.
154      client_->OnHandleDragUpdate(*this,
155                                  touch_move_position + touch_to_focus_offset_);
156    } break;
157
158    case ui::MotionEvent::ACTION_UP: {
159      if (is_drag_within_tap_region_ &&
160          (event.GetEventTime() - touch_down_time_) <
161              client_->GetTapTimeout()) {
162        client_->OnHandleTapped(*this);
163      }
164
165      EndDrag();
166    } break;
167
168    case ui::MotionEvent::ACTION_CANCEL:
169      EndDrag();
170      break;
171
172    default:
173      break;
174  };
175  return true;
176}
177
178bool TouchHandle::Animate(base::TimeTicks frame_time) {
179  if (fade_end_time_ == base::TimeTicks())
180    return false;
181
182  DCHECK(enabled_);
183
184  float time_u =
185      1.f - (fade_end_time_ - frame_time).InMillisecondsF() / kFadeDurationMs;
186  float position_u =
187      (position_ - fade_start_position_).LengthSquared() / kFadeDistanceSquared;
188  float u = std::max(time_u, position_u);
189  SetAlpha(is_visible_ ? u : 1.f - u);
190
191  if (u >= 1.f) {
192    EndFade();
193    return false;
194  }
195
196  return true;
197}
198
199void TouchHandle::BeginDrag() {
200  DCHECK(enabled_);
201  if (is_dragging_)
202    return;
203  EndFade();
204  is_dragging_ = true;
205  is_drag_within_tap_region_ = true;
206  client_->OnHandleDragBegin(*this);
207}
208
209void TouchHandle::EndDrag() {
210  DCHECK(enabled_);
211  if (!is_dragging_)
212    return;
213
214  is_dragging_ = false;
215  is_drag_within_tap_region_ = false;
216  client_->OnHandleDragEnd(*this);
217
218  if (deferred_orientation_ != TOUCH_HANDLE_ORIENTATION_UNDEFINED) {
219    TouchHandleOrientation deferred_orientation = deferred_orientation_;
220    deferred_orientation_ = TOUCH_HANDLE_ORIENTATION_UNDEFINED;
221    SetOrientation(deferred_orientation);
222  }
223
224  if (animate_deferred_fade_) {
225    BeginFade();
226  } else {
227    // As drawable visibility assignment is deferred while dragging, push the
228    // change by forcing fade completion.
229    EndFade();
230  }
231}
232
233void TouchHandle::BeginFade() {
234  DCHECK(enabled_);
235  DCHECK(!is_dragging_);
236  animate_deferred_fade_ = false;
237  const float target_alpha = is_visible_ ? 1.f : 0.f;
238  if (target_alpha == alpha_) {
239    EndFade();
240    return;
241  }
242
243  drawable_->SetVisible(true);
244  fade_end_time_ = base::TimeTicks::Now() +
245                   base::TimeDelta::FromMillisecondsD(
246                       kFadeDurationMs * std::abs(target_alpha - alpha_));
247  fade_start_position_ = position_;
248  client_->SetNeedsAnimate();
249}
250
251void TouchHandle::EndFade() {
252  DCHECK(enabled_);
253  animate_deferred_fade_ = false;
254  fade_end_time_ = base::TimeTicks();
255  SetAlpha(is_visible_ ? 1.f : 0.f);
256  drawable_->SetVisible(is_visible_);
257}
258
259void TouchHandle::SetAlpha(float alpha) {
260  alpha = std::max(0.f, std::min(1.f, alpha));
261  if (alpha_ == alpha)
262    return;
263  alpha_ = alpha;
264  drawable_->SetAlpha(alpha);
265}
266
267}  // namespace content
268