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