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