| OLD | NEW |
| 1 Gestures | 1 Gestures |
| 2 ======== | 2 ======== |
| 3 | 3 |
| 4 ```dart | 4 ```dart |
| 5 SKY MODULE | 5 SKY MODULE |
| 6 <!-- part of dart:sky --> | 6 <!-- part of dart:sky --> |
| 7 | 7 |
| 8 <script> | 8 <script> |
| 9 abstract class GestureEvent extends Event { | 9 abstract class GestureEvent extends Event { |
| 10 Gesture _gesture; | 10 Gesture _gesture; |
| 11 Gesture get gesture => _gesture; | 11 Gesture get gesture => _gesture; |
| 12 } | 12 } |
| 13 | 13 |
| 14 class GestureState { | 14 class GestureState { |
| 15 @nonnull bool cancel = true; // if true, then cancel the gesture at this point | 15 bool cancel = true; // if true, then cancel the gesture at this point |
| 16 @nonnull bool capture = false; // (for PointerDownEvent) if true, then this po
inter is relevant | 16 bool capture = false; // (for PointerDownEvent) if true, then this pointer is
relevant |
| 17 @nonnull bool choose = false; // if true, the gesture thinks that other gestur
es should give up | 17 bool choose = false; // if true, the gesture thinks that other gestures should
give up |
| 18 @nonnull bool finished = true; // if true, we're ready for the next gesture to
start | 18 bool finished = true; // if true, we're ready for the next gesture to start |
| 19 // choose and cancel are mutually exclusive | 19 // choose and cancel are mutually exclusive |
| 20 } | 20 } |
| 21 | 21 |
| 22 class BufferedEvent { | 22 class BufferedEvent { |
| 23 const BufferedEvent(this.event, this.coallesceGroup); | 23 const BufferedEvent(this.event, this.coallesceGroup); |
| 24 final @nonnull GestureEvent event; | 24 final GestureEvent event; |
| 25 final int coallesceGroup; | 25 final int coallesceGroup; |
| 26 } | 26 } |
| 27 | 27 |
| 28 abstract class Gesture extends EventTarget { | 28 abstract class Gesture extends EventTarget { |
| 29 Gesture(this.target) : super() { | 29 Gesture(this.target) : super() { |
| 30 target.events.where((event) => event is PointerDownEvent || | 30 target.events.where((event) => event is PointerDownEvent || |
| 31 event is PointerMovedEvent || | 31 event is PointerMovedEvent || |
| 32 event is PointerUpEvent).listen(_handler); | 32 event is PointerUpEvent).listen(_handler); |
| 33 } | 33 } |
| 34 final @nonnull EventTarget target; | 34 final EventTarget target; |
| 35 | 35 |
| 36 bool _ready = true; // last event, we were finished | 36 bool _ready = true; // last event, we were finished |
| 37 bool get ready => _ready; | 37 bool get ready => _ready; |
| 38 bool _active = false; // we have not yet been canceled since we last started l
istening to a pointer | 38 bool _active = false; // we have not yet been canceled since we last started l
istening to a pointer |
| 39 bool get active => _active; | 39 bool get active => _active; |
| 40 bool _chosen = false; // we're the only possible gesture at this point | 40 bool _chosen = false; // we're the only possible gesture at this point |
| 41 bool get chosen => _chosen; | 41 bool get chosen => _chosen; |
| 42 | 42 |
| 43 // (!ready && !active) means we're discarding events until the user | 43 // (!ready && !active) means we're discarding events until the user |
| 44 // gets to a state where a new gesture can begin | 44 // gets to a state where a new gesture can begin |
| 45 | 45 |
| 46 // (active && !chosen) means we're collecting events until no other | 46 // (active && !chosen) means we're collecting events until no other |
| 47 // gesture is valid, or until we take command | 47 // gesture is valid, or until we take command |
| 48 | 48 |
| 49 @nonnull GestureState processEvent(@nonnull PointerEvent event); | 49 GestureState processEvent(PointerEvent event); |
| 50 | 50 |
| 51 List<@nonnull BufferedEvent> _eventBuffer; | 51 List<BufferedEvent> _eventBuffer; |
| 52 | 52 |
| 53 void choose() { | 53 void choose() { |
| 54 // called by GestureManager | 54 // called by GestureManager |
| 55 // if you override this, make sure to call superclass choose() first | 55 // if you override this, make sure to call superclass choose() first |
| 56 assert(_active == true); | 56 assert(_active == true); |
| 57 assert(_chosen == false); | 57 assert(_chosen == false); |
| 58 _chosen = true; | 58 _chosen = true; |
| 59 // if there are any buffered events, dispatch them on this | 59 // if there are any buffered events, dispatch them on this |
| 60 if ((_eventBuffer != null) && (_eventBuffer.length > 0)) { | 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 | 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 | 62 // while we are doing this |
| 63 var events = _eventBuffer; | 63 var events = _eventBuffer; |
| 64 _eventBuffer = null; | 64 _eventBuffer = null; |
| 65 for (var item in events) | 65 for (var item in events) |
| 66 dispatchEvent(item.event); | 66 dispatchEvent(item.event); |
| 67 } | 67 } |
| 68 } | 68 } |
| 69 | 69 |
| 70 void cancel() { | 70 void cancel() { |
| 71 // called by GestureManager | 71 // called by GestureManager |
| 72 // if you override this, make sure to call superclass cancel() last | 72 // if you override this, make sure to call superclass cancel() last |
| 73 _active = false; | 73 _active = false; |
| 74 _chosen = false; | 74 _chosen = false; |
| 75 _eventBuffer = null; | 75 _eventBuffer = null; |
| 76 } | 76 } |
| 77 | 77 |
| 78 // for use by subclasses only | 78 // for use by subclasses only |
| 79 void sendEvent(@nonnull GestureEvent event, | 79 void sendEvent(GestureEvent event, |
| 80 { int coallesceGroup, // when queuing events, only the last eve
nt with each group is kept | 80 { int coallesceGroup, // when queuing events, only the last eve
nt with each group is kept |
| 81 @nonnull bool prechoose: false // if true, event should just
be sent right away, not queued | 81 bool prechoose: false // if true, event should just be sent r
ight away, not queued |
| 82 }) { | 82 }) { |
| 83 assert(_active == true); | 83 assert(_active == true); |
| 84 assert(coallesceGroup == null || prechoose == false); | 84 assert(coallesceGroup == null || prechoose == false); |
| 85 event._gesture = this; | 85 event._gesture = this; |
| 86 if (_chosen || prechoose) { | 86 if (_chosen || prechoose) { |
| 87 dispatchEvent(event); | 87 dispatchEvent(event); |
| 88 } else { | 88 } else { |
| 89 if (_eventBuffer == null) | 89 if (_eventBuffer == null) |
| 90 _eventBuffer = new List<BufferedEvent>(); | 90 _eventBuffer = new List<BufferedEvent>(); |
| 91 if (coallesceGroup != null) | 91 if (coallesceGroup != null) |
| 92 _eventBuffer.removeWhere((candidate) => candidate.coallesceGroup == coal
lesceGroup); | 92 _eventBuffer.removeWhere((candidate) => candidate.coallesceGroup == coal
lesceGroup); |
| 93 _eventBuffer.add(new BufferedEvent(event, coallesceGroup)); | 93 _eventBuffer.add(new BufferedEvent(event, coallesceGroup)); |
| 94 } | 94 } |
| 95 } | 95 } |
| 96 | 96 |
| 97 void _handler(@nonnull Event event) { | 97 void _handler(Event event) { |
| 98 bool wasActive = _active; | 98 bool wasActive = _active; |
| 99 if (_ready) { | 99 if (_ready) { |
| 100 // reset the state to start a new gesture | 100 // reset the state to start a new gesture |
| 101 if (_active) | 101 if (_active) |
| 102 module.application.gestureManager.cancelGesture(this); | 102 module.application.gestureManager.cancelGesture(this); |
| 103 _active = true; | 103 _active = true; |
| 104 _ready = false; | 104 _ready = false; |
| 105 } | 105 } |
| 106 GestureState returnValue = processEvent(event); | 106 GestureState returnValue = processEvent(event); |
| 107 if (returnValue.capture) { | 107 if (returnValue.capture) { |
| (...skipping 10 matching lines...) Expand all Loading... |
| 118 } else if (active == true) { | 118 } else if (active == true) { |
| 119 if (wasActive == false || event is PointerDownEvent) | 119 if (wasActive == false || event is PointerDownEvent) |
| 120 module.application.addGesture(event, this); | 120 module.application.addGesture(event, this); |
| 121 if (returnValue.choose == true) | 121 if (returnValue.choose == true) |
| 122 module.application.chooseGesture(this); | 122 module.application.chooseGesture(this); |
| 123 } | 123 } |
| 124 _ready = returnValue.finished; | 124 _ready = returnValue.finished; |
| 125 } | 125 } |
| 126 } | 126 } |
| 127 | 127 |
| 128 /* | |
| 129 ``` | 128 ``` |
| 130 Subclasses should override ``processEvent()``: | 129 Subclasses should override ``processEvent()``: |
| 131 - as the events are received, they get examined to see if they | 130 - as the events are received, they get examined to see if they |
| 132 fit the pattern for the gesture; if they do, then return an | 131 fit the pattern for the gesture; if they do, then return an |
| 133 object with valid=true; if more events for this gesture could | 132 object with valid=true; if more events for this gesture could |
| 134 still come in, return finished=false. | 133 still come in, return finished=false. |
| 135 - if you returned valid=false finished=false, then the next call | 134 - if you returned valid=false finished=false, then the next call |
| 136 to this must not return valid=true | 135 to this must not return valid=true |
| 137 - doing anything with the event or target other than reading | 136 - doing anything with the event or target other than reading |
| 138 state is a contract violation | 137 state is a contract violation |
| 139 - you are allowed to call sendEvent() at any time during a | 138 - you are allowed to call sendEvent() at any time during a |
| 140 processEventInternal() call, or after a call to | 139 processEventInternal() call, or after a call to |
| 141 processEventInternal(), assuming that the last such call returned | 140 processEventInternal(), assuming that the last such call returned |
| 142 valid=true, until the next call to processEventInternal() or | 141 valid=true, until the next call to processEventInternal() or |
| 143 cancel(). | 142 cancel(). |
| 144 - set forceChoose=true on the return value if you are confident | 143 - set forceChoose=true on the return value if you are confident |
| 145 that this is the gesture the user meant, even if it's possible | 144 that this is the gesture the user meant, even if it's possible |
| 146 that another gesture is still claiming it's valid (e.g. a long | 145 that another gesture is still claiming it's valid (e.g. a long |
| 147 press might forceChoose to override a scroll, if the user | 146 press might forceChoose to override a scroll, if the user |
| 148 hasn't moved for a while) | 147 hasn't moved for a while) |
| 149 - if you send events, you can set prechoose=true to send the | 148 - if you send events, you can set prechoose=true to send the |
| 150 event even before the gesture has been chosen | 149 event even before the gesture has been chosen |
| 151 - if you send prechoose events, make sure to send corresponding | 150 - if you send prechoose events, make sure to send corresponding |
| 152 "cancel" events if cancel() is called | 151 "cancel" events if cancel() is called |
| 153 | 152 |
| 154 ```dart | 153 ```dart |
| 155 */ | |
| 156 | 154 |
| 157 class PointerState { | 155 class PointerState { |
| 158 PointerState({this.gestures, this.chosen}) { | 156 PointerState({this.gestures, this.chosen}) { |
| 159 if (gestures == null) | 157 if (gestures == null) |
| 160 gestures = new List<Gesture>(); | 158 gestures = new List<Gesture>(); |
| 161 } | 159 } |
| 162 factory PointerState.clone(PointerState source) { | 160 factory PointerState.clone(PointerState source) { |
| 163 return new PointerState(gestures: source.gestures, chosen: source.chosen); | 161 return new PointerState(gestures: source.gestures, chosen: source.chosen); |
| 164 } | 162 } |
| 165 @nonnull List<@nonnull Gesture> gestures; | 163 List<Gesture> gestures; |
| 166 @nonnull bool chosen = false; | 164 bool chosen = false; |
| 167 } | 165 } |
| 168 | 166 |
| 169 class GestureManager { | 167 class GestureManager { |
| 170 GestureManager(this.target) { | 168 GestureManager(this.target) { |
| 171 target.events.where((event) => event is PointerDownEvent).listen(_handler); | 169 target.events.where((event) => event is PointerDownEvent).listen(_handler); |
| 172 } | 170 } |
| 173 final @nonnull EventTarget target; // usually the ApplicationRoot object | 171 final EventTarget target; // usually the ApplicationRoot object |
| 174 | 172 |
| 175 Map<@nonnull int, @nonnull PointerState> _pointers = new SplayTreeMap<int, Poi
nterState>(); | 173 Map<int, PointerState> _pointers = new SplayTreeMap<int, PointerState>(); |
| 176 | 174 |
| 177 void addGesture(@nonnull PointerEvent event, @nonnull Gesture gesture) { | 175 void addGesture(PointerEvent event, Gesture gesture) { |
| 178 assert(gesture.active); | 176 assert(gesture.active); |
| 179 var pointer = event.pointer; | 177 var pointer = event.pointer; |
| 180 if (_pointers.containsKey(pointer)) { | 178 if (_pointers.containsKey(pointer)) { |
| 181 assert(!_pointers[pointer].gestures.contains(gesture)); | 179 assert(!_pointers[pointer].gestures.contains(gesture)); |
| 182 if (_pointers[pointer].chosen) | 180 if (_pointers[pointer].chosen) |
| 183 cancelGesture(gesture); | 181 cancelGesture(gesture); |
| 184 else | 182 else |
| 185 _pointers[pointer].gestures.add(gesture); | 183 _pointers[pointer].gestures.add(gesture); |
| 186 } else { | 184 } else { |
| 187 PointerState pointerState = new PointerState(); | 185 PointerState pointerState = new PointerState(); |
| 188 pointerState.gestures.add(gesture); | 186 pointerState.gestures.add(gesture); |
| 189 _pointers[pointer] = pointerState; | 187 _pointers[pointer] = pointerState; |
| 190 } | 188 } |
| 191 } | 189 } |
| 192 | 190 |
| 193 void cancelGesture(@nonnull Gesture gesture) { | 191 void cancelGesture(Gesture gesture) { |
| 194 _pointers.forEach((index, pointerState) => pointerState.gestures.remove(gest
ure)); | 192 _pointers.forEach((index, pointerState) => pointerState.gestures.remove(gest
ure)); |
| 195 gesture.cancel(); | 193 gesture.cancel(); |
| 196 // get a static copy of the _pointers keys, so we can remove them safely | 194 // get a static copy of the _pointers keys, so we can remove them safely |
| 197 var activePointers = new List<int>.from(_pointers.keys); | 195 var activePointers = new List<int>.from(_pointers.keys); |
| 198 // now walk our lists, removing pointers that are obsolete, and choosing | 196 // now walk our lists, removing pointers that are obsolete, and choosing |
| 199 // gestures from pointers that have only one outstanding gesture | 197 // gestures from pointers that have only one outstanding gesture |
| 200 for (var pointer in activePointers) { | 198 for (var pointer in activePointers) { |
| 201 var pointerState = _pointers[pointer]; | 199 var pointerState = _pointers[pointer]; |
| 202 if (pointerState.gestures.length == 0) { | 200 if (pointerState.gestures.length == 0) { |
| 203 _pointers.remove(pointer); | 201 _pointers.remove(pointer); |
| 204 } else { | 202 } else { |
| 205 if (pointerState.gestures.length == 1 && pointerState.chosen) { | 203 if (pointerState.gestures.length == 1 && pointerState.chosen) { |
| 206 pointerState.chosen = true; | 204 pointerState.chosen = true; |
| 207 pointerState.gestures[0].choose(); | 205 pointerState.gestures[0].choose(); |
| 208 } | 206 } |
| 209 } | 207 } |
| 210 } | 208 } |
| 211 } | 209 } |
| 212 | 210 |
| 213 void chooseGesture(@nonnull Gesture gesture) { | 211 void chooseGesture(Gesture gesture) { |
| 214 if (!gesture.active) | 212 if (!gesture.active) |
| 215 // this could happen e.g. if two gestures simultaneously add | 213 // this could happen e.g. if two gestures simultaneously add |
| 216 // themselves and chose themselves for the same PointerDownEvent | 214 // themselves and chose themselves for the same PointerDownEvent |
| 217 return; | 215 return; |
| 218 @nonnull List<@nonnull Gesture> losers = new List<@nonnull Gesture>(); | 216 List<Gesture> losers = new List<Gesture>(); |
| 219 _pointers.values | 217 _pointers.values |
| 220 .where((pointerState) => pointerState.gestures.contains(gesture)) | 218 .where((pointerState) => pointerState.gestures.contains(gesture)) |
| 221 .forEach((pointerState) { | 219 .forEach((pointerState) { |
| 222 losers.addAll(pointerState.gestures.where((candidateLoser) => can
didateLoser != gesture)); | 220 losers.addAll(pointerState.gestures.where((candidateLoser) => can
didateLoser != gesture)); |
| 223 pointerState.gestures.clear(); | 221 pointerState.gestures.clear(); |
| 224 pointerState.gestures.add(gesture); | 222 pointerState.gestures.add(gesture); |
| 225 pointerState.chosen = true; | 223 pointerState.chosen = true; |
| 226 }); | 224 }); |
| 227 assert(losers.every((loser) => loser.active)); | 225 assert(losers.every((loser) => loser.active)); |
| 228 losers.forEach((loser) { | 226 losers.forEach((loser) { |
| 229 // we check loser.active because losers could contain duplicates | 227 // we check loser.active because losers could contain duplicates |
| 230 // and we should only cancel each gesture once | 228 // and we should only cancel each gesture once |
| 231 if (loser.active) | 229 if (loser.active) |
| 232 loser.cancel(); | 230 loser.cancel(); |
| 233 assert(!loser.active); | 231 assert(!loser.active); |
| 234 }); | 232 }); |
| 235 gesture.choose(); | 233 gesture.choose(); |
| 236 } | 234 } |
| 237 | 235 |
| 238 @nonnull PointerState getActiveGestures(@nonnull int pointer) { | 236 PointerState getActiveGestures(int pointer) { |
| 239 if (_pointers.containsKey(pointer) && _pointers[pointer].gestures.length > 0
) | 237 if (_pointers.containsKey(pointer) && _pointers[pointer].gestures.length > 0
) |
| 240 return new PointerState.clone(_pointers[pointer]); | 238 return new PointerState.clone(_pointers[pointer]); |
| 241 return new PointerState(); | 239 return new PointerState(); |
| 242 } | 240 } |
| 243 | 241 |
| 244 void _handler(@nonnull PointerDownEvent event) { | 242 void _handler(PointerDownEvent event) { |
| 245 var pointer = event.pointer; | 243 var pointer = event.pointer; |
| 246 if (_pointers.containsKey(pointer)) { | 244 if (_pointers.containsKey(pointer)) { |
| 247 var pointerState = _pointers[pointer]; | 245 var pointerState = _pointers[pointer]; |
| 248 if ((!pointerState.chosen) && (pointerState.gestures.length == 1)) { | 246 if ((!pointerState.chosen) && (pointerState.gestures.length == 1)) { |
| 249 pointerState.chosen = true; | 247 pointerState.chosen = true; |
| 250 pointerState.gestures[0].choose(); | 248 pointerState.gestures[0].choose(); |
| 251 } | 249 } |
| 252 } | 250 } |
| 253 } | 251 } |
| 254 | 252 |
| (...skipping 166 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 421 // fling-end: cancel(), or after fling or fling-cancel (prechoose) | 419 // fling-end: cancel(), or after fling or fling-cancel (prechoose) |
| 422 } | 420 } |
| 423 | 421 |
| 424 class FlingLeftGesture : FlingGesture { } | 422 class FlingLeftGesture : FlingGesture { } |
| 425 class FlingRightGesture : FlingGesture { } | 423 class FlingRightGesture : FlingGesture { } |
| 426 class FlingUpGesture : FlingGesture { } | 424 class FlingUpGesture : FlingGesture { } |
| 427 class FlingDownGesture : FlingGesture { } | 425 class FlingDownGesture : FlingGesture { } |
| 428 | 426 |
| 429 </script> | 427 </script> |
| 430 ``` | 428 ``` |
| OLD | NEW |