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: content/browser/renderer_host/input/gestures/gesture_detector.cc

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

Powered by Google App Engine
This is Rietveld 408576698