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.
5 #include "ui/events/gesture_detection/scale_gesture_detector.h"
10 #include "base/float_util.h"
11 #include "base/logging.h"
12 #include "ui/events/gesture_detection/motion_event.h"
14 using base::TimeDelta;
15 using base::TimeTicks;
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;
25 const int kTouchStabilizeTimeMs = 128;
27 const float kScaleFactor = .5f;
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) {
40 ScaleGestureDetector::Config::~Config() {}
42 bool ScaleGestureDetector::SimpleScaleGestureListener::OnScale(
43 const ScaleGestureDetector&, const MotionEvent&) {
47 bool ScaleGestureDetector::SimpleScaleGestureListener::OnScaleBegin(
48 const ScaleGestureDetector&, const MotionEvent&) {
52 void ScaleGestureDetector::SimpleScaleGestureListener::OnScaleEnd(
53 const ScaleGestureDetector&, const MotionEvent&) {}
55 ScaleGestureDetector::ScaleGestureDetector(const Config& config,
56 ScaleGestureListener* listener)
57 : listener_(listener),
61 quick_scale_enabled_(false),
74 touch_history_last_accepted_(0),
75 touch_history_direction_(0),
78 double_tap_focus_x_(0),
79 double_tap_focus_y_(0),
80 double_tap_mode_(DOUBLE_TAP_MODE_NONE),
81 event_before_or_above_starting_gesture_event_(false) {
83 span_slop_ = (config.gesture_detector_config.touch_slop + kSlopEpsilon) * 2;
84 touch_min_major_ = config.min_scaling_touch_major;
85 touch_max_major_ = std::min(config.min_scaling_span / std::sqrt(2.f),
86 2.f * touch_min_major_);
87 min_span_ = config.min_scaling_span + kSlopEpsilon;
89 SetQuickScaleEnabled(config.quick_scale_enabled);
92 ScaleGestureDetector::~ScaleGestureDetector() {}
94 bool ScaleGestureDetector::OnTouchEvent(const MotionEvent& event) {
95 curr_time_ = event.GetEventTime();
97 const int action = event.GetAction();
99 // Forward the event to check for double tap gesture.
100 if (quick_scale_enabled_) {
101 DCHECK(gesture_detector_);
102 gesture_detector_->OnTouchEvent(event);
105 const bool stream_complete =
106 action == MotionEvent::ACTION_UP ||
107 action == MotionEvent::ACTION_CANCEL ||
108 (action == MotionEvent::ACTION_POINTER_DOWN && InDoubleTapMode());
110 if (action == MotionEvent::ACTION_DOWN || stream_complete) {
111 // Reset any scale in progress with the listener.
112 // If it's an ACTION_DOWN we're beginning a new event stream.
113 // This means the app probably didn't give us all the events. Shame on it.
115 listener_->OnScaleEnd(*this, event);
116 ResetScaleWithSpan(0);
117 } else if (InDoubleTapMode() && stream_complete) {
118 ResetScaleWithSpan(0);
121 if (stream_complete) {
127 const bool config_changed = action == MotionEvent::ACTION_DOWN ||
128 action == MotionEvent::ACTION_POINTER_UP ||
129 action == MotionEvent::ACTION_POINTER_DOWN;
131 const bool pointer_up = action == MotionEvent::ACTION_POINTER_UP;
132 const int skip_index = pointer_up ? event.GetActionIndex() : -1;
134 // Determine focal point.
135 float sum_x = 0, sum_y = 0;
136 const int count = static_cast<int>(event.GetPointerCount());
137 const int unreleased_point_count = pointer_up ? count - 1 : count;
138 const float inverse_unreleased_point_count = 1.0f / unreleased_point_count;
142 if (InDoubleTapMode()) {
143 // In double tap mode, the focal pt is always where the double tap
145 focus_x = double_tap_focus_x_;
146 focus_y = double_tap_focus_y_;
147 if (event.GetY() < focus_y) {
148 event_before_or_above_starting_gesture_event_ = true;
150 event_before_or_above_starting_gesture_event_ = false;
153 for (int i = 0; i < count; i++) {
156 sum_x += event.GetX(i);
157 sum_y += event.GetY(i);
160 focus_x = sum_x * inverse_unreleased_point_count;
161 focus_y = sum_y * inverse_unreleased_point_count;
164 AddTouchHistory(event);
166 // Determine average deviation from focal point.
167 float dev_sum_x = 0, dev_sum_y = 0;
168 for (int i = 0; i < count; i++) {
172 dev_sum_x += std::abs(event.GetX(i) - focus_x);
173 dev_sum_y += std::abs(event.GetY(i) - focus_y);
175 // Convert the resulting diameter into a radius, to include touch
176 // radius in overall deviation.
177 const float touch_radius = touch_history_last_accepted_ / 2;
179 const float dev_x = dev_sum_x * inverse_unreleased_point_count + touch_radius;
180 const float dev_y = dev_sum_y * inverse_unreleased_point_count + touch_radius;
182 // Span is the average distance between touch points through the focal point;
183 // i.e. the diameter of the circle with a radius of the average deviation from
185 const float span_x = dev_x * 2;
186 const float span_y = dev_y * 2;
188 if (InDoubleTapMode()) {
191 span = std::sqrt(span_x * span_x + span_y * span_y);
194 // Dispatch begin/end events as needed.
195 // If the configuration changes, notify the app to reset its current state by
196 // beginning a fresh scale event stream.
197 const bool was_in_progress = in_progress_;
200 if (!InDoubleTapMode() && in_progress_ &&
201 (span < min_span_ || config_changed)) {
202 listener_->OnScaleEnd(*this, event);
203 ResetScaleWithSpan(span);
205 if (config_changed) {
206 prev_span_x_ = curr_span_x_ = span_x;
207 prev_span_y_ = curr_span_y_ = span_y;
208 initial_span_ = prev_span_ = curr_span_ = span;
211 const float min_span = InDoubleTapMode() ? span_slop_ : min_span_;
212 if (!in_progress_ && span >= min_span &&
213 (was_in_progress || std::abs(span - initial_span_) > span_slop_)) {
214 prev_span_x_ = curr_span_x_ = span_x;
215 prev_span_y_ = curr_span_y_ = span_y;
216 prev_span_ = curr_span_ = span;
217 prev_time_ = curr_time_;
218 in_progress_ = listener_->OnScaleBegin(*this, event);
221 // Handle motion; focal point and span/scale factor are changing.
222 if (action == MotionEvent::ACTION_MOVE) {
223 curr_span_x_ = span_x;
224 curr_span_y_ = span_y;
227 bool update_prev = true;
230 update_prev = listener_->OnScale(*this, event);
234 prev_span_x_ = curr_span_x_;
235 prev_span_y_ = curr_span_y_;
236 prev_span_ = curr_span_;
237 prev_time_ = curr_time_;
244 void ScaleGestureDetector::SetQuickScaleEnabled(bool scales) {
245 quick_scale_enabled_ = scales;
246 if (quick_scale_enabled_ && !gesture_detector_) {
247 gesture_detector_.reset(
248 new GestureDetector(config_.gesture_detector_config, this, this));
252 bool ScaleGestureDetector::IsQuickScaleEnabled() const {
253 return quick_scale_enabled_;
256 bool ScaleGestureDetector::IsInProgress() const { return in_progress_; }
258 bool ScaleGestureDetector::InDoubleTapMode() const {
259 return double_tap_mode_ == DOUBLE_TAP_MODE_IN_PROGRESS;
262 float ScaleGestureDetector::GetFocusX() const { return focus_x_; }
264 float ScaleGestureDetector::GetFocusY() const { return focus_y_; }
266 float ScaleGestureDetector::GetCurrentSpan() const { return curr_span_; }
268 float ScaleGestureDetector::GetCurrentSpanX() const { return curr_span_x_; }
270 float ScaleGestureDetector::GetCurrentSpanY() const { return curr_span_y_; }
272 float ScaleGestureDetector::GetPreviousSpan() const { return prev_span_; }
274 float ScaleGestureDetector::GetPreviousSpanX() const { return prev_span_x_; }
276 float ScaleGestureDetector::GetPreviousSpanY() const { return prev_span_y_; }
278 float ScaleGestureDetector::GetScaleFactor() const {
279 if (InDoubleTapMode()) {
280 // Drag is moving up; the further away from the gesture start, the smaller
281 // the span should be, the closer, the larger the span, and therefore the
283 const bool scale_up = (event_before_or_above_starting_gesture_event_ &&
284 (curr_span_ < prev_span_)) ||
285 (!event_before_or_above_starting_gesture_event_ &&
286 (curr_span_ > prev_span_));
287 const float span_diff =
288 (std::abs(1.f - (curr_span_ / prev_span_)) * kScaleFactor);
289 return prev_span_ <= 0 ? 1.f
290 : (scale_up ? (1.f + span_diff) : (1.f - span_diff));
292 return prev_span_ > 0 ? curr_span_ / prev_span_ : 1;
295 base::TimeDelta ScaleGestureDetector::GetTimeDelta() const {
296 return curr_time_ - prev_time_;
299 base::TimeTicks ScaleGestureDetector::GetEventTime() const {
303 bool ScaleGestureDetector::OnDoubleTap(const MotionEvent& ev) {
304 // Double tap: start watching for a swipe.
305 double_tap_focus_x_ = ev.GetX();
306 double_tap_focus_y_ = ev.GetY();
307 double_tap_mode_ = DOUBLE_TAP_MODE_IN_PROGRESS;
311 void ScaleGestureDetector::AddTouchHistory(const MotionEvent& ev) {
312 const base::TimeTicks current_time = ev.GetEventTime();
313 DCHECK(!current_time.is_null());
314 const int count = static_cast<int>(ev.GetPointerCount());
315 bool accept = touch_history_last_accepted_time_.is_null() ||
316 (current_time - touch_history_last_accepted_time_) >=
317 base::TimeDelta::FromMilliseconds(kTouchStabilizeTimeMs);
319 int sample_count = 0;
320 for (int i = 0; i < count; i++) {
321 const bool has_last_accepted = !base::IsNaN(touch_history_last_accepted_);
322 const int history_size = static_cast<int>(ev.GetHistorySize());
323 const int pointersample_count = history_size + 1;
324 for (int h = 0; h < pointersample_count; h++) {
326 if (h < history_size) {
327 major = ev.GetHistoricalTouchMajor(i, h);
329 major = ev.GetTouchMajor(i);
331 if (major < touch_min_major_)
332 major = touch_min_major_;
333 if (major > touch_max_major_)
334 major = touch_max_major_;
337 if (base::IsNaN(touch_upper_) || major > touch_upper_) {
338 touch_upper_ = major;
340 if (base::IsNaN(touch_lower_) || major < touch_lower_) {
341 touch_lower_ = major;
344 if (has_last_accepted) {
345 const float major_delta = major - touch_history_last_accepted_;
346 const int direction_sig =
347 major_delta > 0 ? 1 : (major_delta < 0 ? -1 : 0);
348 if (direction_sig != touch_history_direction_ ||
349 (direction_sig == 0 && touch_history_direction_ == 0)) {
350 touch_history_direction_ = direction_sig;
351 touch_history_last_accepted_time_ = h < history_size
352 ? ev.GetHistoricalEventTime(h)
358 sample_count += pointersample_count;
361 const float avg = total / sample_count;
364 float new_accepted = (touch_upper_ + touch_lower_ + avg) / 3;
365 touch_upper_ = (touch_upper_ + new_accepted) / 2;
366 touch_lower_ = (touch_lower_ + new_accepted) / 2;
367 touch_history_last_accepted_ = new_accepted;
368 touch_history_direction_ = 0;
369 touch_history_last_accepted_time_ = ev.GetEventTime();
373 void ScaleGestureDetector::ResetTouchHistory() {
374 touch_upper_ = std::numeric_limits<float>::quiet_NaN();
375 touch_lower_ = std::numeric_limits<float>::quiet_NaN();
376 touch_history_last_accepted_ = std::numeric_limits<float>::quiet_NaN();
377 touch_history_direction_ = 0;
378 touch_history_last_accepted_time_ = base::TimeTicks();
381 void ScaleGestureDetector::ResetScaleWithSpan(float span) {
382 in_progress_ = false;
383 initial_span_ = span;
384 double_tap_mode_ = DOUBLE_TAP_MODE_NONE;