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"
11 #include "base/logging.h"
12 #include "ui/events/gesture_detection/motion_event.h"
13 #include "ui/events/gesture_detection/scale_gesture_listeners.h"
15 using base::TimeDelta
;
16 using base::TimeTicks
;
21 // Using a small epsilon when comparing slop distances allows pixel perfect
22 // slop determination when using fractional DPI coordinates (assuming the slop
23 // region and DPI scale are reasonably proportioned).
24 const float kSlopEpsilon
= .05f
;
26 const int kTouchStabilizeTimeMs
= 128;
28 const float kScaleFactor
= .5f
;
32 // Note: These constants were taken directly from the default (unscaled)
33 // versions found in Android's ViewConfiguration. Do not change these default
34 // values without explicitly consulting an OWNER.
35 ScaleGestureDetector::Config::Config()
37 min_scaling_touch_major(48),
38 min_scaling_span(200),
39 min_pinch_update_span_delta(0) {
42 ScaleGestureDetector::Config::~Config() {}
44 ScaleGestureDetector::ScaleGestureDetector(const Config
& config
,
45 ScaleGestureListener
* listener
)
46 : listener_(listener
),
61 touch_history_last_accepted_(0),
62 touch_history_direction_(0),
65 double_tap_focus_x_(0),
66 double_tap_focus_y_(0),
67 double_tap_mode_(DOUBLE_TAP_MODE_NONE
),
68 event_before_or_above_starting_gesture_event_(false) {
70 span_slop_
= config
.span_slop
+ kSlopEpsilon
;
71 touch_min_major_
= config
.min_scaling_touch_major
;
72 touch_max_major_
= std::min(config
.min_scaling_span
/ std::sqrt(2.f
),
73 2.f
* touch_min_major_
);
74 min_span_
= config
.min_scaling_span
+ kSlopEpsilon
;
78 ScaleGestureDetector::~ScaleGestureDetector() {}
80 bool ScaleGestureDetector::OnTouchEvent(const MotionEvent
& event
) {
81 curr_time_
= event
.GetEventTime();
83 const int action
= event
.GetAction();
85 const bool stream_complete
=
86 action
== MotionEvent::ACTION_UP
||
87 action
== MotionEvent::ACTION_CANCEL
||
88 (action
== MotionEvent::ACTION_POINTER_DOWN
&& InDoubleTapMode());
90 if (action
== MotionEvent::ACTION_DOWN
|| stream_complete
) {
91 // Reset any scale in progress with the listener.
92 // If it's an ACTION_DOWN we're beginning a new event stream.
93 // This means the app probably didn't give us all the events. Shame on it.
95 listener_
->OnScaleEnd(*this, event
);
96 ResetScaleWithSpan(0);
97 } else if (InDoubleTapMode() && stream_complete
) {
98 ResetScaleWithSpan(0);
101 if (stream_complete
) {
107 const bool config_changed
= action
== MotionEvent::ACTION_DOWN
||
108 action
== MotionEvent::ACTION_POINTER_UP
||
109 action
== MotionEvent::ACTION_POINTER_DOWN
;
111 const bool pointer_up
= action
== MotionEvent::ACTION_POINTER_UP
;
112 const int skip_index
= pointer_up
? event
.GetActionIndex() : -1;
114 // Determine focal point.
115 float sum_x
= 0, sum_y
= 0;
116 const int count
= static_cast<int>(event
.GetPointerCount());
117 const int unreleased_point_count
= pointer_up
? count
- 1 : count
;
118 const float inverse_unreleased_point_count
= 1.0f
/ unreleased_point_count
;
122 if (InDoubleTapMode()) {
123 // In double tap mode, the focal pt is always where the double tap
125 focus_x
= double_tap_focus_x_
;
126 focus_y
= double_tap_focus_y_
;
127 if (event
.GetY() < focus_y
) {
128 event_before_or_above_starting_gesture_event_
= true;
130 event_before_or_above_starting_gesture_event_
= false;
133 for (int i
= 0; i
< count
; i
++) {
136 sum_x
+= event
.GetX(i
);
137 sum_y
+= event
.GetY(i
);
140 focus_x
= sum_x
* inverse_unreleased_point_count
;
141 focus_y
= sum_y
* inverse_unreleased_point_count
;
144 AddTouchHistory(event
);
146 // Determine average deviation from focal point.
147 float dev_sum_x
= 0, dev_sum_y
= 0;
148 for (int i
= 0; i
< count
; i
++) {
152 dev_sum_x
+= std::abs(event
.GetX(i
) - focus_x
);
153 dev_sum_y
+= std::abs(event
.GetY(i
) - focus_y
);
155 // Convert the resulting diameter into a radius, to include touch
156 // radius in overall deviation.
157 const float touch_radius
= touch_history_last_accepted_
/ 2;
159 const float dev_x
= dev_sum_x
* inverse_unreleased_point_count
+ touch_radius
;
160 const float dev_y
= dev_sum_y
* inverse_unreleased_point_count
+ touch_radius
;
162 // Span is the average distance between touch points through the focal point;
163 // i.e. the diameter of the circle with a radius of the average deviation from
165 const float span_x
= dev_x
* 2;
166 const float span_y
= dev_y
* 2;
168 if (InDoubleTapMode()) {
171 span
= std::sqrt(span_x
* span_x
+ span_y
* span_y
);
174 // Dispatch begin/end events as needed.
175 // If the configuration changes, notify the app to reset its current state by
176 // beginning a fresh scale event stream.
177 const bool was_in_progress
= in_progress_
;
180 if (!InDoubleTapMode() && in_progress_
&&
181 (span
< min_span_
|| config_changed
)) {
182 listener_
->OnScaleEnd(*this, event
);
183 ResetScaleWithSpan(span
);
185 if (config_changed
) {
186 prev_span_x_
= curr_span_x_
= span_x
;
187 prev_span_y_
= curr_span_y_
= span_y
;
188 initial_span_
= prev_span_
= curr_span_
= span
;
191 const float min_span
= InDoubleTapMode() ? span_slop_
: min_span_
;
192 if (!in_progress_
&& span
>= min_span
&&
193 (was_in_progress
|| std::abs(span
- initial_span_
) > span_slop_
)) {
194 prev_span_x_
= curr_span_x_
= span_x
;
195 prev_span_y_
= curr_span_y_
= span_y
;
196 prev_span_
= curr_span_
= span
;
197 prev_time_
= curr_time_
;
198 in_progress_
= listener_
->OnScaleBegin(*this, event
);
201 // Handle motion; focal point and span/scale factor are changing.
202 if (action
== MotionEvent::ACTION_MOVE
) {
203 curr_span_x_
= span_x
;
204 curr_span_y_
= span_y
;
207 bool update_prev
= true;
210 update_prev
= listener_
->OnScale(*this, event
);
214 prev_span_x_
= curr_span_x_
;
215 prev_span_y_
= curr_span_y_
;
216 prev_span_
= curr_span_
;
217 prev_time_
= curr_time_
;
224 bool ScaleGestureDetector::IsInProgress() const { return in_progress_
; }
226 bool ScaleGestureDetector::InDoubleTapMode() const {
227 return double_tap_mode_
== DOUBLE_TAP_MODE_IN_PROGRESS
;
230 float ScaleGestureDetector::GetFocusX() const { return focus_x_
; }
232 float ScaleGestureDetector::GetFocusY() const { return focus_y_
; }
234 float ScaleGestureDetector::GetCurrentSpan() const { return curr_span_
; }
236 float ScaleGestureDetector::GetCurrentSpanX() const { return curr_span_x_
; }
238 float ScaleGestureDetector::GetCurrentSpanY() const { return curr_span_y_
; }
240 float ScaleGestureDetector::GetPreviousSpan() const { return prev_span_
; }
242 float ScaleGestureDetector::GetPreviousSpanX() const { return prev_span_x_
; }
244 float ScaleGestureDetector::GetPreviousSpanY() const { return prev_span_y_
; }
246 float ScaleGestureDetector::GetScaleFactor() const {
247 if (InDoubleTapMode()) {
248 // Drag is moving up; the further away from the gesture start, the smaller
249 // the span should be, the closer, the larger the span, and therefore the
251 const bool scale_up
= (event_before_or_above_starting_gesture_event_
&&
252 (curr_span_
< prev_span_
)) ||
253 (!event_before_or_above_starting_gesture_event_
&&
254 (curr_span_
> prev_span_
));
255 const float span_diff
=
256 (std::abs(1.f
- (curr_span_
/ prev_span_
)) * kScaleFactor
);
257 return prev_span_
<= 0 ? 1.f
258 : (scale_up
? (1.f
+ span_diff
) : (1.f
- span_diff
));
260 return prev_span_
> 0 ? curr_span_
/ prev_span_
: 1;
263 base::TimeDelta
ScaleGestureDetector::GetTimeDelta() const {
264 return curr_time_
- prev_time_
;
267 base::TimeTicks
ScaleGestureDetector::GetEventTime() const {
271 bool ScaleGestureDetector::OnDoubleTap(const MotionEvent
& ev
) {
272 // Double tap: start watching for a swipe.
273 double_tap_focus_x_
= ev
.GetX();
274 double_tap_focus_y_
= ev
.GetY();
275 double_tap_mode_
= DOUBLE_TAP_MODE_IN_PROGRESS
;
279 void ScaleGestureDetector::AddTouchHistory(const MotionEvent
& ev
) {
280 const base::TimeTicks current_time
= ev
.GetEventTime();
281 DCHECK(!current_time
.is_null());
282 const int count
= static_cast<int>(ev
.GetPointerCount());
283 bool accept
= touch_history_last_accepted_time_
.is_null() ||
284 (current_time
- touch_history_last_accepted_time_
) >=
285 base::TimeDelta::FromMilliseconds(kTouchStabilizeTimeMs
);
287 int sample_count
= 0;
288 for (int i
= 0; i
< count
; i
++) {
289 const bool has_last_accepted
= !std::isnan(touch_history_last_accepted_
);
290 const int history_size
= static_cast<int>(ev
.GetHistorySize());
291 const int pointersample_count
= history_size
+ 1;
292 for (int h
= 0; h
< pointersample_count
; h
++) {
294 if (h
< history_size
) {
295 major
= ev
.GetHistoricalTouchMajor(i
, h
);
297 major
= ev
.GetTouchMajor(i
);
299 if (major
< touch_min_major_
)
300 major
= touch_min_major_
;
301 if (major
> touch_max_major_
)
302 major
= touch_max_major_
;
305 if (std::isnan(touch_upper_
) || major
> touch_upper_
) {
306 touch_upper_
= major
;
308 if (std::isnan(touch_lower_
) || major
< touch_lower_
) {
309 touch_lower_
= major
;
312 if (has_last_accepted
) {
313 const float major_delta
= major
- touch_history_last_accepted_
;
314 const int direction_sig
=
315 major_delta
> 0 ? 1 : (major_delta
< 0 ? -1 : 0);
316 if (direction_sig
!= touch_history_direction_
||
317 (direction_sig
== 0 && touch_history_direction_
== 0)) {
318 touch_history_direction_
= direction_sig
;
319 touch_history_last_accepted_time_
= h
< history_size
320 ? ev
.GetHistoricalEventTime(h
)
326 sample_count
+= pointersample_count
;
329 const float avg
= total
/ sample_count
;
332 float new_accepted
= (touch_upper_
+ touch_lower_
+ avg
) / 3;
333 touch_upper_
= (touch_upper_
+ new_accepted
) / 2;
334 touch_lower_
= (touch_lower_
+ new_accepted
) / 2;
335 touch_history_last_accepted_
= new_accepted
;
336 touch_history_direction_
= 0;
337 touch_history_last_accepted_time_
= ev
.GetEventTime();
341 void ScaleGestureDetector::ResetTouchHistory() {
342 touch_upper_
= std::numeric_limits
<float>::quiet_NaN();
343 touch_lower_
= std::numeric_limits
<float>::quiet_NaN();
344 touch_history_last_accepted_
= std::numeric_limits
<float>::quiet_NaN();
345 touch_history_direction_
= 0;
346 touch_history_last_accepted_time_
= base::TimeTicks();
349 void ScaleGestureDetector::ResetScaleWithSpan(float span
) {
350 in_progress_
= false;
351 initial_span_
= span
;
352 double_tap_mode_
= DOUBLE_TAP_MODE_NONE
;