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

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

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

Powered by Google App Engine
This is Rietveld 408576698