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