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