Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(333)

Side by Side Diff: ui/events/gesture_detection/gesture_detector.cc

Issue 171773012: Port of Android platform gesture detection code to C++ (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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/gesture_detector.h"
6
7 #include <cmath>
8
9 #include "base/timer/timer.h"
10 #include "ui/events/gesture_detection/motion_event.h"
11
12 namespace ui {
13 namespace {
14
15 // Constants used by TimeoutGestureHandler.
16 enum TimeoutEvent {
17 SHOW_PRESS = 0,
18 LONG_PRESS,
19 TAP,
20 TIMEOUT_EVENT_COUNT
21 };
22
23 } // namespace
24
25 // Note: These constants were taken directly from the default (unscaled)
26 // versions found in Android's ViewConfiguration.
27 GestureDetector::Config::Config()
28 : longpress_timeout(base::TimeDelta::FromMilliseconds(500)),
29 tap_timeout(base::TimeDelta::FromMilliseconds(180)),
30 double_tap_timeout(base::TimeDelta::FromMilliseconds(300)),
31 scaled_touch_slop(8),
32 scaled_double_tap_slop(100),
33 scaled_minimum_fling_velocity(50),
34 scaled_maximum_fling_velocity(8000) {}
35
36 GestureDetector::Config::~Config() {}
37
38 bool GestureDetector::SimpleGestureListener::OnDown(const MotionEvent& e) {
39 return false;
40 }
41
42 void GestureDetector::SimpleGestureListener::OnShowPress(const MotionEvent& e) {
43 }
44
45 bool GestureDetector::SimpleGestureListener::OnSingleTapUp(
46 const MotionEvent& e) {
47 return false;
48 }
49
50 bool GestureDetector::SimpleGestureListener::OnLongPress(const MotionEvent& e) {
51 return false;
52 }
53
54 bool GestureDetector::SimpleGestureListener::OnScroll(const MotionEvent& e1,
55 const MotionEvent& e2,
56 float distance_x,
57 float distance_y) {
58 return false;
59 }
60
61 bool GestureDetector::SimpleGestureListener::OnFling(const MotionEvent& e1,
62 const MotionEvent& e2,
63 float velocity_x,
64 float velocity_y) {
65 return false;
66 }
67
68 bool GestureDetector::SimpleGestureListener::OnSingleTapConfirmed(
69 const MotionEvent& e) {
70 return false;
71 }
72
73 bool GestureDetector::SimpleGestureListener::OnDoubleTap(const MotionEvent& e) {
74 return false;
75 }
76
77 bool GestureDetector::SimpleGestureListener::OnDoubleTapEvent(
78 const MotionEvent& e) {
79 return false;
80 }
81
82 class GestureDetector::TimeoutGestureHandler {
83 public:
84 TimeoutGestureHandler(const Config& config, GestureDetector* gesture_detector)
85 : gesture_detector_(gesture_detector) {
86 timeout_callbacks_[SHOW_PRESS] = &GestureDetector::OnShowPressTimeout;
87 timeout_delays_[SHOW_PRESS] = config.tap_timeout;
88
89 timeout_callbacks_[LONG_PRESS] = &GestureDetector::OnLongPressTimeout;
90 timeout_delays_[LONG_PRESS] = config.longpress_timeout + config.tap_timeout;
91
92 timeout_callbacks_[TAP] = &GestureDetector::OnTapTimeout;
93 timeout_delays_[TAP] = config.double_tap_timeout;
94 }
95
96 ~TimeoutGestureHandler() {
97 Stop();
98 }
99
100 void StartTimeout(TimeoutEvent event) {
101 timeout_timers_[event].Start(FROM_HERE,
102 timeout_delays_[event],
103 gesture_detector_,
104 timeout_callbacks_[event]);
105 }
106
107 void StopTimeout(TimeoutEvent event) { timeout_timers_[event].Stop(); }
108
109 void Stop() {
110 for (size_t i = SHOW_PRESS; i < TIMEOUT_EVENT_COUNT; ++i)
111 timeout_timers_[i].Stop();
112 }
113
114 bool HasTimeout(TimeoutEvent event) const {
115 return timeout_timers_[event].IsRunning();
116 }
117
118 private:
119 typedef void (GestureDetector::*ReceiverMethod)();
120
121 GestureDetector* const gesture_detector_;
122 base::OneShotTimer<GestureDetector> timeout_timers_[TIMEOUT_EVENT_COUNT];
123 ReceiverMethod timeout_callbacks_[TIMEOUT_EVENT_COUNT];
124 base::TimeDelta timeout_delays_[TIMEOUT_EVENT_COUNT];
125 };
126
127 GestureDetector::GestureDetector(
128 const Config& config,
129 GestureListener* listener,
130 DoubleTapListener* optional_double_tap_listener)
131 : timeout_handler_(new TimeoutGestureHandler(config, this)),
132 listener_(listener),
133 double_tap_listener_(optional_double_tap_listener),
134 touch_slop_square_(0),
135 double_tap_touch_slop_square_(0),
136 double_tap_slop_square_(0),
137 min_fling_velocity_(1),
138 max_fling_velocity_(1),
139 still_down_(false),
140 defer_confirm_single_tap_(false),
141 in_longpress_(false),
142 always_in_tap_region_(false),
143 always_in_bigger_tap_region_(false),
144 is_double_tapping_(false),
145 last_focus_x_(0),
146 last_focus_y_(0),
147 down_focus_x_(0),
148 down_focus_y_(0),
149 is_longpress_enabled_(true) {
150 DCHECK(listener_);
151 Init(config);
152 }
153
154 GestureDetector::~GestureDetector() {}
155
156 bool GestureDetector::OnTouchEvent(const MotionEvent& ev) {
157 const MotionEvent::Action action = ev.GetAction();
158
159 velocity_tracker_.AddMovement(ev);
160
161 const bool pointer_up = action == MotionEvent::ACTION_POINTER_UP;
162 const int skip_index = pointer_up ? ev.GetActionIndex() : -1;
163
164 // Determine focal point.
165 float sum_x = 0, sum_y = 0;
166 const int count = static_cast<int>(ev.GetPointerCount());
167 for (int i = 0; i < count; i++) {
168 if (skip_index == i)
169 continue;
170 sum_x += ev.GetX(i);
171 sum_y += ev.GetY(i);
172 }
173 const int div = pointer_up ? count - 1 : count;
174 const float focus_x = sum_x / div;
175 const float focus_y = sum_y / div;
176
177 bool handled = false;
178
179 switch (action) {
180 case MotionEvent::ACTION_POINTER_DOWN:
181 down_focus_x_ = last_focus_x_ = focus_x;
182 down_focus_y_ = last_focus_y_ = focus_y;
183 // Cancel long press and taps.
184 CancelTaps();
185 break;
186
187 case MotionEvent::ACTION_POINTER_UP:
188 down_focus_x_ = last_focus_x_ = focus_x;
189 down_focus_y_ = last_focus_y_ = focus_y;
190
191 // Check the dot product of current velocities.
192 // If the pointer that left was opposing another velocity vector, clear.
193 velocity_tracker_.ComputeCurrentVelocity(1000, max_fling_velocity_);
194 {
195 const int up_index = ev.GetActionIndex();
196 const int id1 = ev.GetPointerId(up_index);
197 const float x1 = velocity_tracker_.GetXVelocity(id1);
198 const float y1 = velocity_tracker_.GetYVelocity(id1);
199 for (int i = 0; i < count; i++) {
200 if (i == up_index)
201 continue;
202
203 const int id2 = ev.GetPointerId(i);
204 const float x = x1 * velocity_tracker_.GetXVelocity(id2);
205 const float y = y1 * velocity_tracker_.GetYVelocity(id2);
206
207 const float dot = x + y;
208 if (dot < 0) {
209 velocity_tracker_.Clear();
210 break;
211 }
212 }
213 }
214 break;
215
216 case MotionEvent::ACTION_DOWN:
217 if (double_tap_listener_) {
218 bool had_tap_message = timeout_handler_->HasTimeout(TAP);
219 if (had_tap_message)
220 timeout_handler_->StopTimeout(TAP);
221 if (current_down_event_ && previous_up_event_ && had_tap_message &&
222 IsConsideredDoubleTap(
223 *current_down_event_, *previous_up_event_, ev)) {
224 // This is a second tap.
225 is_double_tapping_ = true;
226 // Give a callback with the first tap of the double-tap.
227 handled |= double_tap_listener_->OnDoubleTap(*current_down_event_);
228 // Give a callback with down event of the double-tap.
229 handled |= double_tap_listener_->OnDoubleTapEvent(ev);
230 } else {
231 // This is a first tap.
232 timeout_handler_->StartTimeout(TAP);
233 }
234 }
235
236 down_focus_x_ = last_focus_x_ = focus_x;
237 down_focus_y_ = last_focus_y_ = focus_y;
238 current_down_event_ = ev.Clone();
239
240 always_in_tap_region_ = true;
241 always_in_bigger_tap_region_ = true;
242 still_down_ = true;
243 in_longpress_ = false;
244 defer_confirm_single_tap_ = false;
245
246 if (is_longpress_enabled_)
247 timeout_handler_->StartTimeout(LONG_PRESS);
248 timeout_handler_->StartTimeout(SHOW_PRESS);
249 handled |= listener_->OnDown(ev);
250 break;
251
252 case MotionEvent::ACTION_MOVE:
253 if (in_longpress_)
254 break;
255
256 {
257 const float scroll_x = last_focus_x_ - focus_x;
258 const float scroll_y = last_focus_y_ - focus_y;
259 if (is_double_tapping_) {
260 // Give the move events of the double-tap.
261 handled |= double_tap_listener_->OnDoubleTapEvent(ev);
262 } else if (always_in_tap_region_) {
263 const int delta_x = static_cast<int>(focus_x - down_focus_x_);
264 const int delta_y = static_cast<int>(focus_y - down_focus_y_);
265 int distance = (delta_x * delta_x) + (delta_y * delta_y);
266 if (distance > touch_slop_square_) {
267 handled = listener_->OnScroll(
268 *current_down_event_, ev, scroll_x, scroll_y);
269 last_focus_x_ = focus_x;
270 last_focus_y_ = focus_y;
271 always_in_tap_region_ = false;
272 timeout_handler_->Stop();
273 }
274 if (distance > double_tap_touch_slop_square_)
275 always_in_bigger_tap_region_ = false;
276 } else if ((std::abs(scroll_x) >= 1) || (std::abs(scroll_y) >= 1)) {
277 handled =
278 listener_->OnScroll(*current_down_event_, ev, scroll_x, scroll_y);
279 last_focus_x_ = focus_x;
280 last_focus_y_ = focus_y;
281 }
282 }
283 break;
284
285 case MotionEvent::ACTION_UP:
286 still_down_ = false;
287 {
288 if (is_double_tapping_) {
289 // Finally, give the up event of the double-tap.
290 handled |= double_tap_listener_->OnDoubleTapEvent(ev);
291 } else if (in_longpress_) {
292 timeout_handler_->StopTimeout(TAP);
293 in_longpress_ = false;
294 } else if (always_in_tap_region_) {
295 handled = listener_->OnSingleTapUp(ev);
296 if (defer_confirm_single_tap_ && double_tap_listener_ != NULL) {
297 double_tap_listener_->OnSingleTapConfirmed(ev);
298 }
299 } else {
300
301 // A fling must travel the minimum tap distance.
302 const int pointer_id = ev.GetPointerId(0);
303 velocity_tracker_.ComputeCurrentVelocity(1000, max_fling_velocity_);
304 const float velocity_y = velocity_tracker_.GetYVelocity(pointer_id);
305 const float velocity_x = velocity_tracker_.GetXVelocity(pointer_id);
306
307 if ((std::abs(velocity_y) > min_fling_velocity_) ||
308 (std::abs(velocity_x) > min_fling_velocity_)) {
309 handled = listener_->OnFling(
310 *current_down_event_, ev, velocity_x, velocity_y);
311 }
312 }
313
314 previous_up_event_ = ev.Clone();
315
316 velocity_tracker_.Clear();
317 is_double_tapping_ = false;
318 defer_confirm_single_tap_ = false;
319 timeout_handler_->StopTimeout(SHOW_PRESS);
320 timeout_handler_->StopTimeout(LONG_PRESS);
321 }
322 break;
323
324 case MotionEvent::ACTION_CANCEL:
325 Cancel();
326 break;
327 }
328
329 return handled;
330 }
331
332 void GestureDetector::Init(const Config& config) {
333 DCHECK(listener_);
334
335 const int touch_slop = config.scaled_touch_slop;
336 const int double_tap_touch_slop = touch_slop;
337 const int double_tap_slop = config.scaled_double_tap_slop;
338 min_fling_velocity_ = config.scaled_minimum_fling_velocity;
339 max_fling_velocity_ = config.scaled_maximum_fling_velocity;
340 touch_slop_square_ = touch_slop * touch_slop;
341 double_tap_touch_slop_square_ = double_tap_touch_slop * double_tap_touch_slop;
342 double_tap_slop_square_ = double_tap_slop * double_tap_slop;
343 double_tap_timeout_ = config.double_tap_timeout;
344 }
345
346 void GestureDetector::OnShowPressTimeout() {
347 listener_->OnShowPress(*current_down_event_);
348 }
349
350 void GestureDetector::OnLongPressTimeout() {
351 timeout_handler_->StopTimeout(TAP);
352 defer_confirm_single_tap_ = false;
353 in_longpress_ = listener_->OnLongPress(*current_down_event_);
354 }
355
356 void GestureDetector::OnTapTimeout() {
357 if (!double_tap_listener_)
358 return;
359 if (!still_down_)
360 double_tap_listener_->OnSingleTapConfirmed(*current_down_event_);
361 else
362 defer_confirm_single_tap_ = true;
363 }
364
365 void GestureDetector::Cancel() {
366 timeout_handler_->Stop();
367 velocity_tracker_.Clear();
368 is_double_tapping_ = false;
369 still_down_ = false;
370 always_in_tap_region_ = false;
371 always_in_bigger_tap_region_ = false;
372 defer_confirm_single_tap_ = false;
373 in_longpress_ = false;
374 }
375
376 void GestureDetector::CancelTaps() {
377 timeout_handler_->Stop();
378 is_double_tapping_ = false;
379 always_in_tap_region_ = false;
380 always_in_bigger_tap_region_ = false;
381 defer_confirm_single_tap_ = false;
382 in_longpress_ = false;
383 }
384
385 bool GestureDetector::IsConsideredDoubleTap(
386 const MotionEvent& first_down,
387 const MotionEvent& first_up,
388 const MotionEvent& second_down) const {
389 if (!always_in_bigger_tap_region_)
390 return false;
391
392 if (second_down.GetEventTime() - first_up.GetEventTime() >
393 double_tap_timeout_)
394 return false;
395
396 int delta_x = static_cast<int>(first_down.GetX() - second_down.GetX());
397 int delta_y = static_cast<int>(first_down.GetY() - second_down.GetY());
398 return (delta_x * delta_x + delta_y * delta_y < double_tap_slop_square_);
399 }
400
401 } // namespace ui
OLDNEW
« no previous file with comments | « ui/events/gesture_detection/gesture_detector.h ('k') | ui/events/gesture_detection/gesture_event_params.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698