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 "ui/events/gesture_detection/scale_gesture_detector.h" 6 7 #include <limits.h> 8 #include <cmath> 9 10 #include "base/float_util.h" 11 #include "base/logging.h" 12 #include "ui/events/gesture_detection/motion_event.h" 13 14 using base::TimeDelta; 15 using base::TimeTicks; 16 17 namespace ui { 18 namespace { 19 20 // Using a small epsilon when comparing slop distances allows pixel perfect 21 // slop determination when using fractional DPI coordinates (assuming the slop 22 // region and DPI scale are reasonably proportioned). 23 const float kSlopEpsilon = .05f; 24 25 const int kTouchStabilizeTimeMs = 128; 26 27 const float kScaleFactor = .5f; 28 29 } // namespace 30 31 // Note: These constants were taken directly from the default (unscaled) 32 // versions found in Android's ViewConfiguration. 33 ScaleGestureDetector::Config::Config() 34 : min_scaling_touch_major(48), 35 min_scaling_span(200), 36 quick_scale_enabled(true), 37 min_pinch_update_span_delta(0) { 38 } 39 40 ScaleGestureDetector::Config::~Config() {} 41 42 bool ScaleGestureDetector::SimpleScaleGestureListener::OnScale( 43 const ScaleGestureDetector&, const MotionEvent&) { 44 return false; 45 } 46 47 bool ScaleGestureDetector::SimpleScaleGestureListener::OnScaleBegin( 48 const ScaleGestureDetector&, const MotionEvent&) { 49 return true; 50 } 51 52 void ScaleGestureDetector::SimpleScaleGestureListener::OnScaleEnd( 53 const ScaleGestureDetector&, const MotionEvent&) {} 54 55 ScaleGestureDetector::ScaleGestureDetector(const Config& config, 56 ScaleGestureListener* listener) 57 : listener_(listener), 58 config_(config), 59 focus_x_(0), 60 focus_y_(0), 61 quick_scale_enabled_(false), 62 curr_span_(0), 63 prev_span_(0), 64 initial_span_(0), 65 curr_span_x_(0), 66 curr_span_y_(0), 67 prev_span_x_(0), 68 prev_span_y_(0), 69 in_progress_(0), 70 span_slop_(0), 71 min_span_(0), 72 touch_upper_(0), 73 touch_lower_(0), 74 touch_history_last_accepted_(0), 75 touch_history_direction_(0), 76 touch_min_major_(0), 77 double_tap_focus_x_(0), 78 double_tap_focus_y_(0), 79 double_tap_mode_(DOUBLE_TAP_MODE_NONE), 80 event_before_or_above_starting_gesture_event_(false) { 81 DCHECK(listener_); 82 span_slop_ = 83 (config.gesture_detector_config.touch_slop + kSlopEpsilon) * 2; 84 touch_min_major_ = config.min_scaling_touch_major; 85 min_span_ = config.min_scaling_span + kSlopEpsilon; 86 ResetTouchHistory(); 87 SetQuickScaleEnabled(config.quick_scale_enabled); 88 } 89 90 ScaleGestureDetector::~ScaleGestureDetector() {} 91 92 bool ScaleGestureDetector::OnTouchEvent(const MotionEvent& event) { 93 curr_time_ = event.GetEventTime(); 94 95 const int action = event.GetAction(); 96 97 // Forward the event to check for double tap gesture. 98 if (quick_scale_enabled_) { 99 DCHECK(gesture_detector_); 100 gesture_detector_->OnTouchEvent(event); 101 } 102 103 const bool stream_complete = 104 action == MotionEvent::ACTION_UP || 105 action == MotionEvent::ACTION_CANCEL || 106 (action == MotionEvent::ACTION_POINTER_DOWN && InDoubleTapMode()); 107 108 if (action == MotionEvent::ACTION_DOWN || stream_complete) { 109 // Reset any scale in progress with the listener. 110 // If it's an ACTION_DOWN we're beginning a new event stream. 111 // This means the app probably didn't give us all the events. Shame on it. 112 if (in_progress_) { 113 listener_->OnScaleEnd(*this, event); 114 ResetScaleWithSpan(0); 115 } else if (InDoubleTapMode() && stream_complete) { 116 ResetScaleWithSpan(0); 117 } 118 119 if (stream_complete) { 120 ResetTouchHistory(); 121 return true; 122 } 123 } 124 125 const bool config_changed = action == MotionEvent::ACTION_DOWN || 126 action == MotionEvent::ACTION_POINTER_UP || 127 action == MotionEvent::ACTION_POINTER_DOWN; 128 129 const bool pointer_up = action == MotionEvent::ACTION_POINTER_UP; 130 const int skip_index = pointer_up ? event.GetActionIndex() : -1; 131 132 // Determine focal point. 133 float sum_x = 0, sum_y = 0; 134 const int count = static_cast<int>(event.GetPointerCount()); 135 const int unreleased_point_count = pointer_up ? count - 1 : count; 136 const float inverse_unreleased_point_count = 1.0f / unreleased_point_count; 137 138 float focus_x; 139 float focus_y; 140 if (InDoubleTapMode()) { 141 // In double tap mode, the focal pt is always where the double tap 142 // gesture started. 143 focus_x = double_tap_focus_x_; 144 focus_y = double_tap_focus_y_; 145 if (event.GetY() < focus_y) { 146 event_before_or_above_starting_gesture_event_ = true; 147 } else { 148 event_before_or_above_starting_gesture_event_ = false; 149 } 150 } else { 151 for (int i = 0; i < count; i++) { 152 if (skip_index == i) 153 continue; 154 sum_x += event.GetX(i); 155 sum_y += event.GetY(i); 156 } 157 158 focus_x = sum_x * inverse_unreleased_point_count; 159 focus_y = sum_y * inverse_unreleased_point_count; 160 } 161 162 AddTouchHistory(event); 163 164 // Determine average deviation from focal point. 165 float dev_sum_x = 0, dev_sum_y = 0; 166 for (int i = 0; i < count; i++) { 167 if (skip_index == i) 168 continue; 169 170 dev_sum_x += std::abs(event.GetX(i) - focus_x); 171 dev_sum_y += std::abs(event.GetY(i) - focus_y); 172 } 173 // Convert the resulting diameter into a radius, to include touch 174 // radius in overall deviation. 175 const float touch_size = touch_history_last_accepted_ / 2; 176 177 const float dev_x = (dev_sum_x * inverse_unreleased_point_count) + touch_size; 178 const float dev_y = (dev_sum_y * inverse_unreleased_point_count) + touch_size; 179 180 // Span is the average distance between touch points through the focal point; 181 // i.e. the diameter of the circle with a radius of the average deviation from 182 // the focal point. 183 const float span_x = dev_x * 2; 184 const float span_y = dev_y * 2; 185 float span; 186 if (InDoubleTapMode()) { 187 span = span_y; 188 } else { 189 span = std::sqrt(span_x * span_x + span_y * span_y); 190 } 191 192 // Dispatch begin/end events as needed. 193 // If the configuration changes, notify the app to reset its current state by 194 // beginning a fresh scale event stream. 195 const bool was_in_progress = in_progress_; 196 focus_x_ = focus_x; 197 focus_y_ = focus_y; 198 if (!InDoubleTapMode() && in_progress_ && 199 (span < min_span_ || config_changed)) { 200 listener_->OnScaleEnd(*this, event); 201 ResetScaleWithSpan(span); 202 } 203 if (config_changed) { 204 prev_span_x_ = curr_span_x_ = span_x; 205 prev_span_y_ = curr_span_y_ = span_y; 206 initial_span_ = prev_span_ = curr_span_ = span; 207 } 208 209 const float min_span = InDoubleTapMode() ? span_slop_ : min_span_; 210 if (!in_progress_ && span >= min_span && (InDoubleTapMode() || count > 1) && 211 (was_in_progress || std::abs(span - initial_span_) > span_slop_)) { 212 prev_span_x_ = curr_span_x_ = span_x; 213 prev_span_y_ = curr_span_y_ = span_y; 214 prev_span_ = curr_span_ = span; 215 prev_time_ = curr_time_; 216 in_progress_ = listener_->OnScaleBegin(*this, event); 217 } 218 219 // Handle motion; focal point and span/scale factor are changing. 220 if (action == MotionEvent::ACTION_MOVE) { 221 curr_span_x_ = span_x; 222 curr_span_y_ = span_y; 223 curr_span_ = span; 224 225 bool update_prev = true; 226 227 if (in_progress_) { 228 update_prev = listener_->OnScale(*this, event); 229 } 230 231 if (update_prev) { 232 prev_span_x_ = curr_span_x_; 233 prev_span_y_ = curr_span_y_; 234 prev_span_ = curr_span_; 235 prev_time_ = curr_time_; 236 } 237 } 238 239 return true; 240 } 241 242 void ScaleGestureDetector::SetQuickScaleEnabled(bool scales) { 243 quick_scale_enabled_ = scales; 244 if (quick_scale_enabled_ && !gesture_detector_) { 245 gesture_detector_.reset( 246 new GestureDetector(config_.gesture_detector_config, this, this)); 247 } 248 } 249 250 bool ScaleGestureDetector::IsQuickScaleEnabled() const { 251 return quick_scale_enabled_; 252 } 253 254 bool ScaleGestureDetector::IsInProgress() const { return in_progress_; } 255 256 bool ScaleGestureDetector::InDoubleTapMode() const { 257 return double_tap_mode_ == DOUBLE_TAP_MODE_IN_PROGRESS; 258 } 259 260 float ScaleGestureDetector::GetFocusX() const { return focus_x_; } 261 262 float ScaleGestureDetector::GetFocusY() const { return focus_y_; } 263 264 float ScaleGestureDetector::GetCurrentSpan() const { return curr_span_; } 265 266 float ScaleGestureDetector::GetCurrentSpanX() const { return curr_span_x_; } 267 268 float ScaleGestureDetector::GetCurrentSpanY() const { return curr_span_y_; } 269 270 float ScaleGestureDetector::GetPreviousSpan() const { return prev_span_; } 271 272 float ScaleGestureDetector::GetPreviousSpanX() const { return prev_span_x_; } 273 274 float ScaleGestureDetector::GetPreviousSpanY() const { return prev_span_y_; } 275 276 float ScaleGestureDetector::GetScaleFactor() const { 277 if (InDoubleTapMode()) { 278 // Drag is moving up; the further away from the gesture start, the smaller 279 // the span should be, the closer, the larger the span, and therefore the 280 // larger the scale. 281 const bool scale_up = (event_before_or_above_starting_gesture_event_ && 282 (curr_span_ < prev_span_)) || 283 (!event_before_or_above_starting_gesture_event_ && 284 (curr_span_ > prev_span_)); 285 const float span_diff = 286 (std::abs(1.f - (curr_span_ / prev_span_)) * kScaleFactor); 287 return prev_span_ <= 0 ? 1.f 288 : (scale_up ? (1.f + span_diff) : (1.f - span_diff)); 289 } 290 return prev_span_ > 0 ? curr_span_ / prev_span_ : 1; 291 } 292 293 base::TimeDelta ScaleGestureDetector::GetTimeDelta() const { 294 return curr_time_ - prev_time_; 295 } 296 297 base::TimeTicks ScaleGestureDetector::GetEventTime() const { 298 return curr_time_; 299 } 300 301 bool ScaleGestureDetector::OnDoubleTap(const MotionEvent& ev) { 302 // Double tap: start watching for a swipe. 303 double_tap_focus_x_ = ev.GetX(); 304 double_tap_focus_y_ = ev.GetY(); 305 double_tap_mode_ = DOUBLE_TAP_MODE_IN_PROGRESS; 306 return true; 307 } 308 309 void ScaleGestureDetector::AddTouchHistory(const MotionEvent& ev) { 310 const base::TimeTicks current_time = ev.GetEventTime(); 311 DCHECK(!current_time.is_null()); 312 const int count = static_cast<int>(ev.GetPointerCount()); 313 bool accept = touch_history_last_accepted_time_.is_null() || 314 (current_time - touch_history_last_accepted_time_) >= 315 base::TimeDelta::FromMilliseconds(kTouchStabilizeTimeMs); 316 float total = 0; 317 int sample_count = 0; 318 for (int i = 0; i < count; i++) { 319 const bool has_last_accepted = !base::IsNaN(touch_history_last_accepted_); 320 const int history_size = static_cast<int>(ev.GetHistorySize()); 321 const int pointersample_count = history_size + 1; 322 for (int h = 0; h < pointersample_count; h++) { 323 float major; 324 if (h < history_size) { 325 major = ev.GetHistoricalTouchMajor(i, h); 326 } else { 327 major = ev.GetTouchMajor(i); 328 } 329 if (major < touch_min_major_) 330 major = touch_min_major_; 331 total += major; 332 333 if (base::IsNaN(touch_upper_) || major > touch_upper_) { 334 touch_upper_ = major; 335 } 336 if (base::IsNaN(touch_lower_) || major < touch_lower_) { 337 touch_lower_ = major; 338 } 339 340 if (has_last_accepted) { 341 const float major_delta = major - touch_history_last_accepted_; 342 const int direction_sig = 343 major_delta > 0 ? 1 : (major_delta < 0 ? -1 : 0); 344 if (direction_sig != touch_history_direction_ || 345 (direction_sig == 0 && touch_history_direction_ == 0)) { 346 touch_history_direction_ = direction_sig; 347 touch_history_last_accepted_time_ = h < history_size 348 ? ev.GetHistoricalEventTime(h) 349 : ev.GetEventTime(); 350 accept = false; 351 } 352 } 353 } 354 sample_count += pointersample_count; 355 } 356 357 const float avg = total / sample_count; 358 359 if (accept) { 360 float new_accepted = (touch_upper_ + touch_lower_ + avg) / 3; 361 touch_upper_ = (touch_upper_ + new_accepted) / 2; 362 touch_lower_ = (touch_lower_ + new_accepted) / 2; 363 touch_history_last_accepted_ = new_accepted; 364 touch_history_direction_ = 0; 365 touch_history_last_accepted_time_ = ev.GetEventTime(); 366 } 367 } 368 369 void ScaleGestureDetector::ResetTouchHistory() { 370 touch_upper_ = std::numeric_limits<float>::quiet_NaN(); 371 touch_lower_ = std::numeric_limits<float>::quiet_NaN(); 372 touch_history_last_accepted_ = std::numeric_limits<float>::quiet_NaN(); 373 touch_history_direction_ = 0; 374 touch_history_last_accepted_time_ = base::TimeTicks(); 375 } 376 377 void ScaleGestureDetector::ResetScaleWithSpan(float span) { 378 in_progress_ = false; 379 initial_span_ = span; 380 double_tap_mode_ = DOUBLE_TAP_MODE_NONE; 381 } 382 383 } // namespace ui 384