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