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 9 namespace content { 10 11 namespace { 12 13 // Maximum duration of a fade sequence. 14 const 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. 18 const 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. 22 const 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. 28 const float kMaxTouchMajorForHitTesting = 36.f; 29 30 } // namespace 31 32 // Responsible for rendering a selection or insertion handle for text editing. 33 TouchHandle::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 53 TouchHandle::~TouchHandle() { 54 } 55 56 void 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 67 void 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 90 void 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 102 void 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 117 bool 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 178 bool 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 199 void 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 209 void 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 233 void 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 251 void 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 259 void 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