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