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.
5 #include "ui/events/gesture_detection/scale_gesture_detector.h"
10 #include "base/float_util.h"
11 #include "base/logging.h"
12 #include "ui/events/gesture_detection/motion_event.h"
14 using base::TimeDelta
;
15 using base::TimeTicks
;
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
;
25 const int kTouchStabilizeTimeMs
= 128;
27 const float kScaleFactor
= .5f
;
31 // Note: These constants were taken directly from the default (unscaled)
32 // versions found in Android's ViewConfiguration.
33 ScaleGestureDetector::Config::Config()
35 min_scaling_touch_major(48),
36 min_scaling_span(200),
37 min_pinch_update_span_delta(0) {
40 ScaleGestureDetector::Config::~Config() {}
42 bool ScaleGestureDetector::SimpleScaleGestureListener::OnScale(
43 const ScaleGestureDetector
&, const MotionEvent
&) {
47 bool ScaleGestureDetector::SimpleScaleGestureListener::OnScaleBegin(
48 const ScaleGestureDetector
&, const MotionEvent
&) {
52 void ScaleGestureDetector::SimpleScaleGestureListener::OnScaleEnd(
53 const ScaleGestureDetector
&, const MotionEvent
&) {}
55 ScaleGestureDetector::ScaleGestureDetector(const Config
& config
,
56 ScaleGestureListener
* listener
)
57 : listener_(listener
),
72 touch_history_last_accepted_(0),
73 touch_history_direction_(0),
76 double_tap_focus_x_(0),
77 double_tap_focus_y_(0),
78 double_tap_mode_(DOUBLE_TAP_MODE_NONE
),
79 event_before_or_above_starting_gesture_event_(false) {
81 span_slop_
= config
.span_slop
+ kSlopEpsilon
;
82 touch_min_major_
= config
.min_scaling_touch_major
;
83 touch_max_major_
= std::min(config
.min_scaling_span
/ std::sqrt(2.f
),
84 2.f
* touch_min_major_
);
85 min_span_
= config
.min_scaling_span
+ kSlopEpsilon
;
89 ScaleGestureDetector::~ScaleGestureDetector() {}
91 bool ScaleGestureDetector::OnTouchEvent(const MotionEvent
& event
) {
92 curr_time_
= event
.GetEventTime();
94 const int action
= event
.GetAction();
96 const bool stream_complete
=
97 action
== MotionEvent::ACTION_UP
||
98 action
== MotionEvent::ACTION_CANCEL
||
99 (action
== MotionEvent::ACTION_POINTER_DOWN
&& InDoubleTapMode());
101 if (action
== MotionEvent::ACTION_DOWN
|| stream_complete
) {
102 // Reset any scale in progress with the listener.
103 // If it's an ACTION_DOWN we're beginning a new event stream.
104 // This means the app probably didn't give us all the events. Shame on it.
106 listener_
->OnScaleEnd(*this, event
);
107 ResetScaleWithSpan(0);
108 } else if (InDoubleTapMode() && stream_complete
) {
109 ResetScaleWithSpan(0);
112 if (stream_complete
) {
118 const bool config_changed
= action
== MotionEvent::ACTION_DOWN
||
119 action
== MotionEvent::ACTION_POINTER_UP
||
120 action
== MotionEvent::ACTION_POINTER_DOWN
;
122 const bool pointer_up
= action
== MotionEvent::ACTION_POINTER_UP
;
123 const int skip_index
= pointer_up
? event
.GetActionIndex() : -1;
125 // Determine focal point.
126 float sum_x
= 0, sum_y
= 0;
127 const int count
= static_cast<int>(event
.GetPointerCount());
128 const int unreleased_point_count
= pointer_up
? count
- 1 : count
;
129 const float inverse_unreleased_point_count
= 1.0f
/ unreleased_point_count
;
133 if (InDoubleTapMode()) {
134 // In double tap mode, the focal pt is always where the double tap
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;
141 event_before_or_above_starting_gesture_event_
= false;
144 for (int i
= 0; i
< count
; i
++) {
147 sum_x
+= event
.GetX(i
);
148 sum_y
+= event
.GetY(i
);
151 focus_x
= sum_x
* inverse_unreleased_point_count
;
152 focus_y
= sum_y
* inverse_unreleased_point_count
;
155 AddTouchHistory(event
);
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
++) {
163 dev_sum_x
+= std::abs(event
.GetX(i
) - focus_x
);
164 dev_sum_y
+= std::abs(event
.GetY(i
) - focus_y
);
166 // Convert the resulting diameter into a radius, to include touch
167 // radius in overall deviation.
168 const float touch_radius
= touch_history_last_accepted_
/ 2;
170 const float dev_x
= dev_sum_x
* inverse_unreleased_point_count
+ touch_radius
;
171 const float dev_y
= dev_sum_y
* inverse_unreleased_point_count
+ touch_radius
;
173 // Span is the average distance between touch points through the focal point;
174 // i.e. the diameter of the circle with a radius of the average deviation from
176 const float span_x
= dev_x
* 2;
177 const float span_y
= dev_y
* 2;
179 if (InDoubleTapMode()) {
182 span
= std::sqrt(span_x
* span_x
+ span_y
* span_y
);
185 // Dispatch begin/end events as needed.
186 // If the configuration changes, notify the app to reset its current state by
187 // beginning a fresh scale event stream.
188 const bool was_in_progress
= in_progress_
;
191 if (!InDoubleTapMode() && in_progress_
&&
192 (span
< min_span_
|| config_changed
)) {
193 listener_
->OnScaleEnd(*this, event
);
194 ResetScaleWithSpan(span
);
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
;
202 const float 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
);
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
;
218 bool update_prev
= true;
221 update_prev
= listener_
->OnScale(*this, event
);
225 prev_span_x_
= curr_span_x_
;
226 prev_span_y_
= curr_span_y_
;
227 prev_span_
= curr_span_
;
228 prev_time_
= curr_time_
;
235 bool ScaleGestureDetector::IsInProgress() const { return in_progress_
; }
237 bool ScaleGestureDetector::InDoubleTapMode() const {
238 return double_tap_mode_
== DOUBLE_TAP_MODE_IN_PROGRESS
;
241 float ScaleGestureDetector::GetFocusX() const { return focus_x_
; }
243 float ScaleGestureDetector::GetFocusY() const { return focus_y_
; }
245 float ScaleGestureDetector::GetCurrentSpan() const { return curr_span_
; }
247 float ScaleGestureDetector::GetCurrentSpanX() const { return curr_span_x_
; }
249 float ScaleGestureDetector::GetCurrentSpanY() const { return curr_span_y_
; }
251 float ScaleGestureDetector::GetPreviousSpan() const { return prev_span_
; }
253 float ScaleGestureDetector::GetPreviousSpanX() const { return prev_span_x_
; }
255 float ScaleGestureDetector::GetPreviousSpanY() const { return prev_span_y_
; }
257 float ScaleGestureDetector::GetScaleFactor() const {
258 if (InDoubleTapMode()) {
259 // Drag is moving up; the further away from the gesture start, the smaller
260 // the span should be, the closer, the larger the span, and therefore the
262 const bool scale_up
= (event_before_or_above_starting_gesture_event_
&&
263 (curr_span_
< prev_span_
)) ||
264 (!event_before_or_above_starting_gesture_event_
&&
265 (curr_span_
> prev_span_
));
266 const float span_diff
=
267 (std::abs(1.f
- (curr_span_
/ prev_span_
)) * kScaleFactor
);
268 return prev_span_
<= 0 ? 1.f
269 : (scale_up
? (1.f
+ span_diff
) : (1.f
- span_diff
));
271 return prev_span_
> 0 ? curr_span_
/ prev_span_
: 1;
274 base::TimeDelta
ScaleGestureDetector::GetTimeDelta() const {
275 return curr_time_
- prev_time_
;
278 base::TimeTicks
ScaleGestureDetector::GetEventTime() const {
282 bool ScaleGestureDetector::OnDoubleTap(const MotionEvent
& ev
) {
283 // Double tap: start watching for a swipe.
284 double_tap_focus_x_
= ev
.GetX();
285 double_tap_focus_y_
= ev
.GetY();
286 double_tap_mode_
= DOUBLE_TAP_MODE_IN_PROGRESS
;
290 void ScaleGestureDetector::AddTouchHistory(const MotionEvent
& ev
) {
291 const base::TimeTicks current_time
= ev
.GetEventTime();
292 DCHECK(!current_time
.is_null());
293 const int count
= static_cast<int>(ev
.GetPointerCount());
294 bool accept
= touch_history_last_accepted_time_
.is_null() ||
295 (current_time
- touch_history_last_accepted_time_
) >=
296 base::TimeDelta::FromMilliseconds(kTouchStabilizeTimeMs
);
298 int sample_count
= 0;
299 for (int i
= 0; i
< count
; i
++) {
300 const bool has_last_accepted
= !base::IsNaN(touch_history_last_accepted_
);
301 const int history_size
= static_cast<int>(ev
.GetHistorySize());
302 const int pointersample_count
= history_size
+ 1;
303 for (int h
= 0; h
< pointersample_count
; h
++) {
305 if (h
< history_size
) {
306 major
= ev
.GetHistoricalTouchMajor(i
, h
);
308 major
= ev
.GetTouchMajor(i
);
310 if (major
< touch_min_major_
)
311 major
= touch_min_major_
;
312 if (major
> touch_max_major_
)
313 major
= touch_max_major_
;
316 if (base::IsNaN(touch_upper_
) || major
> touch_upper_
) {
317 touch_upper_
= major
;
319 if (base::IsNaN(touch_lower_
) || major
< touch_lower_
) {
320 touch_lower_
= major
;
323 if (has_last_accepted
) {
324 const float major_delta
= major
- touch_history_last_accepted_
;
325 const int direction_sig
=
326 major_delta
> 0 ? 1 : (major_delta
< 0 ? -1 : 0);
327 if (direction_sig
!= touch_history_direction_
||
328 (direction_sig
== 0 && touch_history_direction_
== 0)) {
329 touch_history_direction_
= direction_sig
;
330 touch_history_last_accepted_time_
= h
< history_size
331 ? ev
.GetHistoricalEventTime(h
)
337 sample_count
+= pointersample_count
;
340 const float avg
= total
/ sample_count
;
343 float new_accepted
= (touch_upper_
+ touch_lower_
+ avg
) / 3;
344 touch_upper_
= (touch_upper_
+ new_accepted
) / 2;
345 touch_lower_
= (touch_lower_
+ new_accepted
) / 2;
346 touch_history_last_accepted_
= new_accepted
;
347 touch_history_direction_
= 0;
348 touch_history_last_accepted_time_
= ev
.GetEventTime();
352 void ScaleGestureDetector::ResetTouchHistory() {
353 touch_upper_
= std::numeric_limits
<float>::quiet_NaN();
354 touch_lower_
= std::numeric_limits
<float>::quiet_NaN();
355 touch_history_last_accepted_
= std::numeric_limits
<float>::quiet_NaN();
356 touch_history_direction_
= 0;
357 touch_history_last_accepted_time_
= base::TimeTicks();
360 void ScaleGestureDetector::ResetScaleWithSpan(float span
) {
361 in_progress_
= false;
362 initial_span_
= span
;
363 double_tap_mode_
= DOUBLE_TAP_MODE_NONE
;