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