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

Side by Side Diff: client/touch/ClickBuster.dart

Issue 9382027: Move client/{base, observable, layout, touch, util, view} to samples/ui_lib . (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: '' Created 8 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 | Annotate | Revision Log
« no previous file with comments | « client/touch/BezierPhysics.dart ('k') | client/touch/EventUtil.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 /**
6 * Click buster implementation, which is a behavior that prevents native clicks
7 * from firing at undesirable times. There are two scenarios where we may want
8 * to 'bust' a click.
9 *
10 * Buttons implemented with touch events usually have click handlers as well.
11 * This is because sometimes touch events stop working, and the click handler
12 * serves as a fallback. Here we use a click buster to prevent the native click
13 * from firing if the touchend event was succesfully handled.
14 *
15 * When native scrolling behavior is disabled (see Scroller), click events will
16 * fire after the touchend event when the drag sequence is complete. The click
17 * event also happens to fire at the location of the touchstart event which can
18 * lead to some very strange behavior.
19 *
20 * This class puts a single click handler on the body, and calls preventDefault
21 * on the click event if we detect that there was a touchend event that already
22 * fired in the same spot recently.
23 */
24 class ClickBuster {
25 /**
26 * The threshold for how long we allow a click to occur after a touchstart.
27 */
28 static final _TIME_THRESHOLD = 2500;
29
30 /**
31 * The threshold for how close a click has to be to the saved coordinate for
32 * us to allow it.
33 */
34 static final _DISTANCE_THRESHOLD = 25;
35
36 /**
37 * The list of coordinates that we use to measure the distance of clicks from.
38 * If a click is within the distance threshold of any of these coordinates
39 * then we allow the click.
40 * TODO(ngeoffray): Should be DoubleLinkedQueue<num>
41 */
42 static var _coordinates;
43
44 /** The last time preventGhostClick was called. */
45 static int _lastPreventedTime;
46
47 /**
48 * This handler will prevent the default behavior for any clicks unless the
49 * click is within the distance threshold of one of the temporary allowed
50 * coordinates.
51 */
52 static void _onClick(Event e) {
53 if (TimeUtil.now() - _lastPreventedTime > _TIME_THRESHOLD) {
54 return;
55 }
56 final coord = new Coordinate.fromClient(e);
57 // TODO(rnystrom): On Android, we get spurious click events at (0, 0). We
58 // *do* want those clicks to be busted, so commenting this out fixes it.
59 // Leaving it commented out instead of just deleting it because I'm not sure
60 // what this code was intended to do to begin with.
61 /*
62 if (coord.x < 1 && coord.y < 1) {
63 // TODO(jacobr): implement a configurable logging framework.
64 // _logger.warning(
65 // "Not busting click on label elem at(${coord.x}, ${coord.y})");
66 return;
67 }
68 */
69 var entry = _coordinates.firstEntry();
70 while (entry != null) {
71 if (_hitTest(entry.element,
72 entry.nextEntry().element,
73 coord.x,
74 coord.y)) {
75 entry.nextEntry().remove();
76 entry.remove();
77 return;
78 } else {
79 entry = entry.nextEntry().nextEntry();
80 }
81 }
82
83 // TODO(jacobr): implement a configurable logging framework.
84 // _logger.warning("busting click at ${coord.x}, ${coord.y}");
85 e.stopPropagation();
86 e.preventDefault();
87 }
88
89 /**
90 * This handler will temporarily allow a click to occur near the touch event's
91 * coordinates.
92 */
93 static void _onTouchStart(Event e) {
94 TouchEvent te = e;
95 final coord = new Coordinate.fromClient(te.touches[0]);
96 _coordinates.add(coord.x);
97 _coordinates.add(coord.y);
98 window.setTimeout(() {
99 _removeCoordinate(coord.x, coord.y);
100 }, _TIME_THRESHOLD);
101 _toggleTapHighlights(true);
102 }
103
104 /**
105 * Hit test for whether a coordinate is within the distance threshold of an
106 * event.
107 */
108 static bool _hitTest(num x, num y, num eventX, num eventY) {
109 return (eventX - x).abs() < _DISTANCE_THRESHOLD &&
110 (eventY - y).abs() < _DISTANCE_THRESHOLD;
111 }
112
113 /**
114 * Remove one specified coordinate from the coordinates list.
115 */
116 static void _removeCoordinate(num x, num y) {
117 var entry = _coordinates.firstEntry();
118 while (entry != null) {
119 if (entry.element == x && entry.nextEntry().element == y) {
120 entry.nextEntry().remove();
121 entry.remove();
122 return;
123 } else {
124 entry = entry.nextEntry().nextEntry();
125 }
126 }
127 }
128
129 /**
130 * Enable or disable tap highlights. They are disabled when preventGhostClick
131 * is called so that the flicker on links is not invoked when the ghost click
132 * does fire. This is due to a bug: links get highlighted even if the click
133 * event has preventDefault called on it.
134 */
135 static void _toggleTapHighlights(bool enable) {
136 document.body.style.setProperty(
137 "-webkit-tap-highlight-color", enable ? "" : "rgba(0,0,0,0)", "");
138 }
139
140 /**
141 * Registers new touches to create temporary "allowable zones" and registers
142 * new clicks to be prevented unless they fall in one of the current
143 * "allowable zones". Note that if the touchstart and touchend locations are
144 * different, it is still possible for a ghost click to be fired if you
145 * called preventDefault on all touchmove events. In this case the ghost
146 * click will be fired at the location of the touchstart event, so the
147 * coordinate you pass in should be the coordinate of the touchstart.
148 */
149 static void preventGhostClick(num x, num y) {
150 // First time this is called the following occurs:
151 // 1) Attaches a handler to touchstart events so that each touch will
152 // temporarily create an "allowable zone" for clicks to occur in.
153 // 2) Attaches a handler to click events so that each click will be
154 // prevented unless it is in an "allowable zone".
155 //
156 // Every time this is called (including the first) the following occurs:
157 // 1) Removes an allowable zone that contains the specified coordinate.
158 //
159 // How this enables click busting:
160 // 1) User performs first click.
161 // - No attached touchstart handler yet.
162 // - preventGhostClick is called before the click event occurs, it
163 // attaches the touchstart and click handlers.
164 // - The click handler captures the user's click event and prevents it
165 // from propagating since there is no "allowable zone".
166 //
167 // 2) User performs subsequent, to-be-busted click.
168 // - touchstart event triggers the attached handler and creates a
169 // temporary "allowable zone".
170 // - preventGhostClick is called and removes the "allowable zone".
171 // - The click handler captures the user's click event and prevents it
172 // from propagating since there is no "allowable zone".
173 //
174 // 3) User performs a should-not-be-busted click.
175 // - touchstart event triggers the attached handler and creates a
176 // temporary "allowable zone".
177 // - The click handler captures the user's click event and allows it to
178 // propagate since the click falls in the "allowable zone".
179 if (_coordinates === null) {
180 // Listen to clicks on capture phase so they can be busted before anything
181 // else gets a chance to handle them.
182 document.on.click.add((e) { _onClick(e); }, true);
183 document.on.focus.add((e) { _lastPreventedTime = 0; }, true);
184
185 // Listen to touchstart on capture phase since it must be called prior to
186 // every click or else we will accidentally prevent the click even if we
187 // don't call preventGhostClick.
188 Function startFn = (e) { _onTouchStart(e); };
189 if (!Device.supportsTouch) {
190 startFn = mouseToTouchCallback(startFn);
191 }
192 EventUtil.observe(document,
193 Device.supportsTouch ? document.on.touchStart : document.on.mouseDown,
194 startFn, true, true);
195 _coordinates = new Queue<num>();
196 }
197
198 // Turn tap highlights off until we know the ghost click has fired.
199 _toggleTapHighlights(false);
200
201 // Above all other rules, we won't bust any clicks if there wasn't some call
202 // to preventGhostClick in the last time threshold.
203 _lastPreventedTime = TimeUtil.now();
204 var entry = _coordinates.firstEntry();
205 while (entry != null) {
206 if (_hitTest(entry.element, entry.nextEntry().element, x, y)) {
207 entry.nextEntry().remove();
208 entry.remove();
209 return;
210 } else {
211 entry = entry.nextEntry().nextEntry();
212 }
213 }
214 }
215 }
OLDNEW
« no previous file with comments | « client/touch/BezierPhysics.dart ('k') | client/touch/EventUtil.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698