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