• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
14 using base::TimeDelta;
15 using base::TimeTicks;
16 
17 namespace ui {
18 namespace {
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).
23 const float kSlopEpsilon = .05f;
24 
25 const int kTouchStabilizeTimeMs = 128;
26 
27 const 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.
Config()33 ScaleGestureDetector::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 
~Config()40 ScaleGestureDetector::Config::~Config() {}
41 
OnScale(const ScaleGestureDetector &,const MotionEvent &)42 bool ScaleGestureDetector::SimpleScaleGestureListener::OnScale(
43     const ScaleGestureDetector&, const MotionEvent&) {
44   return false;
45 }
46 
OnScaleBegin(const ScaleGestureDetector &,const MotionEvent &)47 bool ScaleGestureDetector::SimpleScaleGestureListener::OnScaleBegin(
48     const ScaleGestureDetector&, const MotionEvent&) {
49   return true;
50 }
51 
OnScaleEnd(const ScaleGestureDetector &,const MotionEvent &)52 void ScaleGestureDetector::SimpleScaleGestureListener::OnScaleEnd(
53     const ScaleGestureDetector&, const MotionEvent&) {}
54 
ScaleGestureDetector(const Config & config,ScaleGestureListener * listener)55 ScaleGestureDetector::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 
~ScaleGestureDetector()90 ScaleGestureDetector::~ScaleGestureDetector() {}
91 
OnTouchEvent(const MotionEvent & event)92 bool 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 
SetQuickScaleEnabled(bool scales)242 void 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 
IsQuickScaleEnabled() const250 bool ScaleGestureDetector::IsQuickScaleEnabled() const {
251   return quick_scale_enabled_;
252 }
253 
IsInProgress() const254 bool ScaleGestureDetector::IsInProgress() const { return in_progress_; }
255 
InDoubleTapMode() const256 bool ScaleGestureDetector::InDoubleTapMode() const {
257   return double_tap_mode_ == DOUBLE_TAP_MODE_IN_PROGRESS;
258 }
259 
GetFocusX() const260 float ScaleGestureDetector::GetFocusX() const { return focus_x_; }
261 
GetFocusY() const262 float ScaleGestureDetector::GetFocusY() const { return focus_y_; }
263 
GetCurrentSpan() const264 float ScaleGestureDetector::GetCurrentSpan() const { return curr_span_; }
265 
GetCurrentSpanX() const266 float ScaleGestureDetector::GetCurrentSpanX() const { return curr_span_x_; }
267 
GetCurrentSpanY() const268 float ScaleGestureDetector::GetCurrentSpanY() const { return curr_span_y_; }
269 
GetPreviousSpan() const270 float ScaleGestureDetector::GetPreviousSpan() const { return prev_span_; }
271 
GetPreviousSpanX() const272 float ScaleGestureDetector::GetPreviousSpanX() const { return prev_span_x_; }
273 
GetPreviousSpanY() const274 float ScaleGestureDetector::GetPreviousSpanY() const { return prev_span_y_; }
275 
GetScaleFactor() const276 float 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 
GetTimeDelta() const293 base::TimeDelta ScaleGestureDetector::GetTimeDelta() const {
294   return curr_time_ - prev_time_;
295 }
296 
GetEventTime() const297 base::TimeTicks ScaleGestureDetector::GetEventTime() const {
298   return curr_time_;
299 }
300 
OnDoubleTap(const MotionEvent & ev)301 bool 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 
AddTouchHistory(const MotionEvent & ev)309 void 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 
ResetTouchHistory()369 void 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 
ResetScaleWithSpan(float span)377 void 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