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