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

Side by Side Diff: sky/specs/gestures.md

Issue 901493005: Specs: dartification of gestures; move GestureManager from ApplicationDocument to Application; actu… (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: Created 5 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
« no previous file with comments | « sky/specs/events.md ('k') | sky/specs/modules.md » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 Gestures 1 Gestures
2 ======== 2 ========
3 3
4 ```javascript 4 ```dart
5 typedef PointerID Integer; 5 SKY MODULE
6 <!-- part of sky:core -->
6 7
7 dictionary GestureState { 8 <script>
8 Boolean cancel = true; // if true, then cancel the gesture at this point 9 abstract class GestureEvent extends Event {
9 Boolean capture = false; // (for pointer-down) if true, then this pointer is r elevant 10 Gesture _gesture;
10 Boolean choose = false; // if true, the gesture thinks that other gestures sho uld give up 11 Gesture get gesture => _gesture;
11 Boolean finished = true; // if true, we're ready for the next gesture to start 12 }
12 13
14 class GestureState {
15 bool cancel = true; // if true, then cancel the gesture at this point
16 bool capture = false; // (for PointerDownEvent) if true, then this pointer is relevant
17 bool choose = false; // if true, the gesture thinks that other gestures should give up
18 bool finished = true; // if true, we're ready for the next gesture to start
13 // choose and cancel are mutually exclusive 19 // choose and cancel are mutually exclusive
14 } 20 }
15 21
16 dictionary SendEventOptions { 22 class BufferedEvent {
17 Integer? coallesceGroup = null; // when queuing events, only the last event wi th each group is kept 23 const BufferedEvent(this.event, this.coallesceGroup);
18 Boolean prechoose = false; // if true, event should just be sent right away, n ot queued 24 final GestureEvent event;
25 final int coallesceGroup;
19 } 26 }
20 27
21 abstract class Gesture : EventTarget { 28 abstract class Gesture extends EventTarget {
22 constructor (EventTarget target); 29 Gesture(this.target) : super() {
23 readonly attribute EventTarget target; 30 target.events.where((event) => event is PointerDownEvent ||
31 event is PointerMovedEvent ||
32 event is PointerUpEvent).listen(_handler);
33 }
34 final EventTarget target;
24 35
25 virtual GestureState processEvent(Event event); 36 bool _ready = true; // last event, we were finished
26 // return {} 37 bool get ready => _ready;
27 virtual void choose(); // called by GestureManager // make sure to call superc lass choose() before 38 bool _active = false; // we have not yet been canceled since we last started l istening to a pointer
28 // - assert: this.active == true 39 bool get active => _active;
29 // - assert: this.chosen == false 40 bool _chosen = false; // we're the only possible gesture at this point
30 // - set this.chosen = true 41 bool get chosen => _chosen;
31 // - if there are any buffered events, dispatch them on this
32 virtual void cancel(); // called by GestureManager // make sure to call superc lass cancel() after
33 // - set active and chosen to false, clear the event buffer
34 42
35 readonly attribute Boolean ready; // last event, we were finished 43 // (!ready && !active) means we're discarding events until the user
36 readonly attribute Boolean active; // we have not yet been canceled since we l ast captured a pointer 44 // gets to a state where a new gesture can begin
37 readonly attribute Boolean chosen; // we're the only possible gesture at this point
38 45
39 // !ready && !active => we're discarding events until the user gets to a state where a new gesture can begin 46 // (active && !chosen) means we're collecting events until no other
40 // active && !chosen => we're collecting events until no other gesture is vali d, or until we take command 47 // gesture is valid, or until we take command
41 48
42 void sendEvent(Event event, SendEventOptions options); 49 GestureState processEvent(PointerEvent event);
43 // used internally to queue up or send events 50
44 // - assert: this.active == true 51 List<BufferedEvent> _eventBuffer;
45 // - assert: options.prechoose is false or options.coallesceGroup 52
46 // is null 53 void choose() {
47 // - set event.gesture = this 54 // called by GestureManager
48 // - if this.chosen is true or if options.prechoose is true, then 55 // if you override this, make sure to call superclass choose() first
49 // send the event straight to the callback 56 assert(_active == true);
50 // - otherwise: 57 assert(_chosen == false);
51 // - if the event buffer has an entry with the same 58 _chosen = true;
52 // coallesceGroup identifier, drop it 59 // if there are any buffered events, dispatch them on this
53 // - add the event to the event buffer 60 if ((_eventBuffer != null) && (_eventBuffer.length > 0)) {
61 // we make a copy of the event buffer first so that the array isn't mutate d out from under us
62 // while we are doing this
63 var events = _eventBuffer;
64 _eventBuffer = null;
65 for (var item in events) {
66 dispatchEvent(item.event);
67 }
68 }
69 }
70
71 void cancel() {
72 // called by GestureManager
73 // if you override this, make sure to call superclass cancel() last
74 _active = false;
75 _chosen = false;
76 _eventBuffer = null;
77 }
78
79 // for use by subclasses only
80 void sendEvent(GestureEvent event,
81 { int coallesceGroup, // when queuing events, only the last eve nt with each group is kept
82 bool prechoose: false // if true, event should just be sent r ight away, not queued
83 }) {
84 assert(_active == true);
85 assert(coallesceGroup == null || prechoose == false);
86 event._gesture = this;
87 if (_chosen || prechoose) {
88 dispatchEvent(event);
89 } else {
90 if (_eventBuffer == null)
91 _eventBuffer = new List<BufferedEvent>();
92 if (coallesceGroup != null)
93 _eventBuffer.removeWhere((candidate) => candidate.coallesceGroup == coal lesceGroup);
94 _eventBuffer.add(new BufferedEvent(event, coallesceGroup));
95 }
96 }
97
98 void _handler(event) {
99 bool wasActive = _active;
100 if (_ready) {
101 // reset the state to start a new gesture
102 if (_active)
103 module.application.gestureManager.cancelGesture(this);
104 _active = true;
105 _ready = false;
106 }
107 GestureState returnValue = processEvent(event);
108 if (returnValue.capture) {
109 assert(event is PointerDownEvent);
110 if (event is PointerDownEvent)
111 event.result.add(this);
112 }
113 if (returnValue.cancel) {
114 assert(returnValue.choose == false);
115 if (wasActive)
116 module.application.cancelGesture(this);
117 // if we never became active, then we never called addGesture() below
118 _active = false;
119 } else if (active == true) {
120 if (wasActive == false || event is PointerDownEvent)
121 module.application.addGesture(event, this);
122 if (returnValue.choose == true)
123 module.application.chooseGesture(this);
124 }
125 _ready = returnValue.finished;
126 }
54 } 127 }
128
129 /*
55 ``` 130 ```
56
57 ``Gesture`` objects have an Event buffer, initially empty. Each Event
58 in this buffer can be associated with a coallesceGroup, which is
59 identified by integer.
60
61 When created, ``Gesture`` objects register themselves as pointer-down,
62 pointer-move, and pointer-up event handlers on their target, with the
63 same event handler. That event handler runs the following steps:
64 - let wasActive = this.active
65 - if this.ready == true, then:
66 - // reset the state to start a new gesture
67 - if this.active == true, then:
68 - call application.document.cancelGesture(this)
69 - set this.active = true
70 - set this.ready = false
71 - let returnValue be the result of calling ``processEvent()`` with
72 the Event object
73 - if returnValue.capture == true:
74 - assert: the event is a pointer-down event
75 - if the event is a pointer-down event:
76 - push this onto the event's return value
77 - if returnValue.cancel == true:
78 - assert: returnValue.choose == false
79 - if wasActive == true:
80 - call application.document.cancelGesture(this)
81 - // if wasActive == false, then no need to cancel, since we never added ourselves
82 - if returnValue.cancel == false and this.active == true:
83 - if wasActive == false or if event is a pointer-down event:
84 - call application.document.addGesture(event, this)
85 - if returnValue.choose == true:
86 - call application.document.chooseGesture(this)
87 - set this.ready = returnValue.finished
88 - set this.active = returnValue.valid
89
90 Subclasses should override ``processEvent()``: 131 Subclasses should override ``processEvent()``:
91 - as the events are received, they get examined to see if they 132 - as the events are received, they get examined to see if they
92 fit the pattern for the gesture; if they do, then return an 133 fit the pattern for the gesture; if they do, then return an
93 object with valid=true; if more events for this gesture could 134 object with valid=true; if more events for this gesture could
94 still come in, return finished=false. 135 still come in, return finished=false.
95 - if you returned valid=false finished=false, then the next call 136 - if you returned valid=false finished=false, then the next call
96 to this must not return valid=true 137 to this must not return valid=true
97 - doing anything with the event or target other than reading 138 - doing anything with the event or target other than reading
98 state is a contract violation 139 state is a contract violation
99 - you are allowed to call sendEvent() at any time during a 140 - you are allowed to call sendEvent() at any time during a
100 processEventInternal() call, or after a call to 141 processEventInternal() call, or after a call to
101 processEventInternal(), assuming that the last such call returned 142 processEventInternal(), assuming that the last such call returned
102 valid=true, until the next call to processEventInternal() or 143 valid=true, until the next call to processEventInternal() or
103 cancel(). 144 cancel().
104 - set forceChoose=true on the return value if you are confident 145 - set forceChoose=true on the return value if you are confident
105 that this is the gesture the user meant, even if it's possible 146 that this is the gesture the user meant, even if it's possible
106 that another gesture is still claiming it's valid (e.g. a long 147 that another gesture is still claiming it's valid (e.g. a long
107 press might forceChoose to override a scroll, if the user 148 press might forceChoose to override a scroll, if the user
108 hasn't moved for a while) 149 hasn't moved for a while)
109 - if you send events, you can set prechoose=true to send the 150 - if you send events, you can set prechoose=true to send the
110 event even before the gesture has been chosen 151 event even before the gesture has been chosen
111 - if you send prechoose events, make sure to send corresponding 152 - if you send prechoose events, make sure to send corresponding
112 "cancel" events if cancel() is called 153 "cancel" events if cancel() is called
113 154
114 ```javascript 155 ```dart
115 dictionary GestureList { 156 */
116 Array<Gesture> gestures; 157
117 Boolean chosen; 158 class PointerState {
159 PointerState({this.gestures, this.chosen}) {
160 if (gestures == null)
161 gestures = new List<Gesture>();
162 }
163 factory PointerState.clone(PointerState source) {
164 return new PointerState(gestures: source.gestures, chosen: source.chosen);
165 }
166 List<Gesture> gestures;
167 bool chosen = false;
118 } 168 }
119 169
120 class GestureManager { 170 class GestureManager {
121 constructor (EventTarget target); 171 GestureManager(this.target) {
122 readonly attribute EventTarget target; // the ApplicationDocument, normally 172 target.events.where((event) => event is PointerDownEvent).listen(_handler);
173 }
174 final EventTarget target; // usually the ApplicationDocument object
123 175
124 void addGesture(Event event, Gesture gesture); 176 Map<int, PointerState> _pointers = new SplayTreeMap<int, PointerState>();
125 void cancelGesture(Gesture gesture);
126 void chooseGesture(Gesture gesture);
127 177
128 GestureList getActiveGestures(PointerID pointer); 178 void addGesture(PointerEvent event, Gesture gesture) {
179 assert(gesture.active);
180 var pointer = event.pointer;
181 if (_pointers.containsKey(pointer)) {
182 assert(!_pointers[pointer].gestures.contains(gesture));
183 if (_pointers[pointer].chosen)
184 cancelGesture(gesture);
185 else
186 _pointers[pointer].gestures.add(gesture);
187 } else {
188 PointerState pointerState = new PointerState();
189 pointerState.gestures.add(gesture);
190 _pointers[pointer] = pointerState;
191 }
192 }
193
194 void cancelGesture(Gesture gesture) {
195 _pointers.forEach((index, pointerState) => pointerState.gestures.remove(gest ure));
196 gesture.cancel();
197 // get a static copy of the _pointers keys, so we can remove them safely
198 var activePointers = new List<int>.from(_pointers.keys);
199 // now walk our lists, removing pointers that are obsolete, and choosing
200 // gestures from pointers that have only one outstanding gesture
201 for (var index = 0; index < activePointers.length; index += 1) {
202 var pointerState = _pointers[activePointers[index]];
203 if (pointerState.gestures.length == 0) {
204 _pointers.remove(activePointers[index]);
205 } else {
206 if (pointerState.gestures.length == 1 && pointerState.chosen) {
207 pointerState.chosen = true;
208 pointerState.gestures[0].choose();
209 }
210 }
211 }
212 }
213
214 void chooseGesture(Gesture gesture) {
215 if (!gesture.active)
216 // this could happen e.g. if two gestures simultaneously add
217 // themselves and chose themselves for the same PointerDownEvent
218 return;
219 List<Gesture> losers = new List<Gesture>();
220 _pointers.values
221 .where((pointerState) => pointerState.gestures.contains(gesture))
222 .forEach((pointerState) {
223 losers.addAll(pointerState.gestures.where((candidateLoser) => can didateLoser != gesture));
224 pointerState.gestures.clear();
225 pointerState.gestures.add(gesture);
226 pointerState.chosen = true;
227 });
228 assert(losers.every((loser) => loser.active));
229 losers.forEach((loser) {
230 // we check loser.active because losers could contain duplicates
231 // and we should only cancel each gesture once
232 if (loser.active)
233 loser.cancel();
234 assert(!loser.active);
235 });
236 gesture.choose();
237 }
238
239 PointerState getActiveGestures(int pointer) {
240 if (_pointers.containsKey(pointer) && _pointers[pointer].gestures.length > 0 )
241 return new PointerState.clone(_pointers[pointer]);
242 return new PointerState();
243 }
244
245 void _handler(PointerDownEvent event) {
246 var pointer = event.pointer;
247 if (_pointers.containsKey(pointer)) {
248 var pointerState = _pointers[pointer];
249 if ((!pointerState.chosen) && (pointerState.gestures.length == 1)) {
250 pointerState.chosen = true;
251 pointerState.gestures[0].choose();
252 }
253 }
254 }
255
129 } 256 }
257 </script>
130 ``` 258 ```
131 259
132 ``GestureManager`` objects have a map of lists of Gesture objects, 260 Gestures defined in the framework
133 keyed on pointer IDs, and with each list associated with a "chosen" 261 ---------------------------------
134 flag indicating if an entry in the list has already been chosen.
135 Initially the map is empty. It is exposed by the
136 ``getActiveGestures()`` method, which returns the list and flag.
137 262
138 When addGesture() is called with an event and a Gesture, it runs the 263 ```dart
139 following steps: 264 SKY MODULE
140 - let pointer be the value of the event's pointer field 265 <!-- not in sky:core -->
141 - assert: pointer is an integer 266 <!-- note: this hasn't been dartified yet -->
142 - if we already have an entry for pointer:
143 - assert: this Gesture isn't already on the list for pointer
144 - if the list's "chosen" flag is set, then call
145 ``cancelGesture()`` with this Gesture
146 - otherwise, add this Gesture to the list for pointer
147 - otherwise, we don't have an entry for this pointer:
148 - create a list for pointer
149 - add this Gesture to the list for pointer
150 267
151 A ``GestureManager``, when created, starts listening to 268 <script>
152 ``pointer-down`` events on its target. The listener acts as follows: 269 class TapGesture extends Gesture {
153 - assert: event is a ``pointer-down`` event
154 - let pointer be the value of the event's pointer field
155 - if we have an entry for this pointer, and the "chosen" flag isn't
156 set, and there is just one Gesture in the list, then set the flag
157 on the list and call the Gesture's ``choose()`` method.
158
159 When ``cancelGesture()`` is called with a Gesture:
160 - for each pointer list:
161 - if the pointer list has this Gesture, remove it
162 - call cancel() on the Gesture
163 - for each pointer list:
164 - if the pointer list has no entries, forget it
165 - if the pointer list has one Gesture and the "chosen" flag isn't
166 set, set it and call that Gesture's ``choose()`` method.
167
168 When ``chooseGesture()`` is called with a Gesture:
169 - if this Gesture is not active, then return silently
170 // this could happen e.g. if two gestures simultaneously add themselves
171 // and chose themselves for the same pointer-down
172 - let losers be an empty list of Gestures
173 - for each pointer list:
174 - if the pointer list has this Gesture, add all the other Gestures
175 in the list to losers, remove them from the list, and set the
176 "chosen" flag on that list
177 - remove duplicates from losers
178 - call ``cancel()`` on each entry in losers
179 - call ``choose()`` on the Gesture
180
181
182 ```javascript
183 class TapGesture : Gesture {
184 270
185 // internal state: 271 // internal state:
186 // Integer numButtons = 0; 272 // Integer numButtons = 0;
187 // Boolean primaryDown = false; 273 // Boolean primaryDown = false;
188 274
189 virtual GestureState processEvent(Event event); 275 virtual GestureState processEvent(Event event);
190 // - let returnValue = { finished = false } 276 // - let returnValue = { finished = false }
191 // - if the event is a pointer-down: 277 // - if the event is a pointer-down:
192 // - increment this.numButtons 278 // - increment this.numButtons
193 // - set returnValue.capture = true 279 // - set returnValue.capture = true
(...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after
334 // fling: the user has released the pointer and the decision is it was in fact flung 420 // fling: the user has released the pointer and the decision is it was in fact flung
335 // fling-cancel: cancel(), or the user has released the pointer and the decisi on is it was not flung (prechoose) 421 // fling-cancel: cancel(), or the user has released the pointer and the decisi on is it was not flung (prechoose)
336 // fling-end: cancel(), or after fling or fling-cancel (prechoose) 422 // fling-end: cancel(), or after fling or fling-cancel (prechoose)
337 } 423 }
338 424
339 class FlingLeftGesture : FlingGesture { } 425 class FlingLeftGesture : FlingGesture { }
340 class FlingRightGesture : FlingGesture { } 426 class FlingRightGesture : FlingGesture { }
341 class FlingUpGesture : FlingGesture { } 427 class FlingUpGesture : FlingGesture { }
342 class FlingDownGesture : FlingGesture { } 428 class FlingDownGesture : FlingGesture { }
343 429
430 </script>
344 ``` 431 ```
OLDNEW
« no previous file with comments | « sky/specs/events.md ('k') | sky/specs/modules.md » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698