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