| OLD | NEW |
| 1 Gestures | 1 Gestures |
| 2 ======== | 2 ======== |
| 3 | 3 |
| 4 ```dart | 4 ```dart |
| 5 SKY MODULE | 5 SKY MODULE |
| 6 <!-- part of sky:core --> | 6 <!-- part of sky:core --> |
| 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 bool cancel = true; // if true, then cancel the gesture at this point | 15 @nonnull 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 | 16 @nonnull bool capture = false; // (for PointerDownEvent) if true, then this po
inter is relevant |
| 17 bool choose = false; // if true, the gesture thinks that other gestures should
give up | 17 @nonnull bool choose = false; // if true, the gesture thinks that other gestur
es should give up |
| 18 bool finished = true; // if true, we're ready for the next gesture to start | 18 @nonnull 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 GestureEvent event; | 24 final @nonnull 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 EventTarget target; | 34 final @nonnull 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 GestureState processEvent(PointerEvent event); | 49 @nonnull GestureState processEvent(@nonnull PointerEvent event); |
| 50 | 50 |
| 51 List<BufferedEvent> _eventBuffer; | 51 List<@nonnull 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 } | |
| 68 } | 67 } |
| 69 } | 68 } |
| 70 | 69 |
| 71 void cancel() { | 70 void cancel() { |
| 72 // called by GestureManager | 71 // called by GestureManager |
| 73 // if you override this, make sure to call superclass cancel() last | 72 // if you override this, make sure to call superclass cancel() last |
| 74 _active = false; | 73 _active = false; |
| 75 _chosen = false; | 74 _chosen = false; |
| 76 _eventBuffer = null; | 75 _eventBuffer = null; |
| 77 } | 76 } |
| 78 | 77 |
| 79 // for use by subclasses only | 78 // for use by subclasses only |
| 80 void sendEvent(GestureEvent event, | 79 void sendEvent(@nonnull GestureEvent event, |
| 81 { 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 |
| 82 bool prechoose: false // if true, event should just be sent r
ight away, not queued | 81 @nonnull bool prechoose: false // if true, event should just
be sent right away, not queued |
| 83 }) { | 82 }) { |
| 84 assert(_active == true); | 83 assert(_active == true); |
| 85 assert(coallesceGroup == null || prechoose == false); | 84 assert(coallesceGroup == null || prechoose == false); |
| 86 event._gesture = this; | 85 event._gesture = this; |
| 87 if (_chosen || prechoose) { | 86 if (_chosen || prechoose) { |
| 88 dispatchEvent(event); | 87 dispatchEvent(event); |
| 89 } else { | 88 } else { |
| 90 if (_eventBuffer == null) | 89 if (_eventBuffer == null) |
| 91 _eventBuffer = new List<BufferedEvent>(); | 90 _eventBuffer = new List<BufferedEvent>(); |
| 92 if (coallesceGroup != null) | 91 if (coallesceGroup != null) |
| 93 _eventBuffer.removeWhere((candidate) => candidate.coallesceGroup == coal
lesceGroup); | 92 _eventBuffer.removeWhere((candidate) => candidate.coallesceGroup == coal
lesceGroup); |
| 94 _eventBuffer.add(new BufferedEvent(event, coallesceGroup)); | 93 _eventBuffer.add(new BufferedEvent(event, coallesceGroup)); |
| 95 } | 94 } |
| 96 } | 95 } |
| 97 | 96 |
| 98 void _handler(event) { | 97 void _handler(@nonnull Event event) { |
| 99 bool wasActive = _active; | 98 bool wasActive = _active; |
| 100 if (_ready) { | 99 if (_ready) { |
| 101 // reset the state to start a new gesture | 100 // reset the state to start a new gesture |
| 102 if (_active) | 101 if (_active) |
| 103 module.application.gestureManager.cancelGesture(this); | 102 module.application.gestureManager.cancelGesture(this); |
| 104 _active = true; | 103 _active = true; |
| 105 _ready = false; | 104 _ready = false; |
| 106 } | 105 } |
| 107 GestureState returnValue = processEvent(event); | 106 GestureState returnValue = processEvent(event); |
| 108 if (returnValue.capture) { | 107 if (returnValue.capture) { |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 156 */ | 155 */ |
| 157 | 156 |
| 158 class PointerState { | 157 class PointerState { |
| 159 PointerState({this.gestures, this.chosen}) { | 158 PointerState({this.gestures, this.chosen}) { |
| 160 if (gestures == null) | 159 if (gestures == null) |
| 161 gestures = new List<Gesture>(); | 160 gestures = new List<Gesture>(); |
| 162 } | 161 } |
| 163 factory PointerState.clone(PointerState source) { | 162 factory PointerState.clone(PointerState source) { |
| 164 return new PointerState(gestures: source.gestures, chosen: source.chosen); | 163 return new PointerState(gestures: source.gestures, chosen: source.chosen); |
| 165 } | 164 } |
| 166 List<Gesture> gestures; | 165 @nonnull List<@nonnull Gesture> gestures; |
| 167 bool chosen = false; | 166 @nonnull bool chosen = false; |
| 168 } | 167 } |
| 169 | 168 |
| 170 class GestureManager { | 169 class GestureManager { |
| 171 GestureManager(this.target) { | 170 GestureManager(this.target) { |
| 172 target.events.where((event) => event is PointerDownEvent).listen(_handler); | 171 target.events.where((event) => event is PointerDownEvent).listen(_handler); |
| 173 } | 172 } |
| 174 final EventTarget target; // usually the ApplicationDocument object | 173 final @nonnull EventTarget target; // usually the ApplicationDocument object |
| 175 | 174 |
| 176 Map<int, PointerState> _pointers = new SplayTreeMap<int, PointerState>(); | 175 Map<@nonnull int, @nonnull PointerState> _pointers = new SplayTreeMap<int, Poi
nterState>(); |
| 177 | 176 |
| 178 void addGesture(PointerEvent event, Gesture gesture) { | 177 void addGesture(@nonnull PointerEvent event, @nonnull Gesture gesture) { |
| 179 assert(gesture.active); | 178 assert(gesture.active); |
| 180 var pointer = event.pointer; | 179 var pointer = event.pointer; |
| 181 if (_pointers.containsKey(pointer)) { | 180 if (_pointers.containsKey(pointer)) { |
| 182 assert(!_pointers[pointer].gestures.contains(gesture)); | 181 assert(!_pointers[pointer].gestures.contains(gesture)); |
| 183 if (_pointers[pointer].chosen) | 182 if (_pointers[pointer].chosen) |
| 184 cancelGesture(gesture); | 183 cancelGesture(gesture); |
| 185 else | 184 else |
| 186 _pointers[pointer].gestures.add(gesture); | 185 _pointers[pointer].gestures.add(gesture); |
| 187 } else { | 186 } else { |
| 188 PointerState pointerState = new PointerState(); | 187 PointerState pointerState = new PointerState(); |
| 189 pointerState.gestures.add(gesture); | 188 pointerState.gestures.add(gesture); |
| 190 _pointers[pointer] = pointerState; | 189 _pointers[pointer] = pointerState; |
| 191 } | 190 } |
| 192 } | 191 } |
| 193 | 192 |
| 194 void cancelGesture(Gesture gesture) { | 193 void cancelGesture(@nonnull Gesture gesture) { |
| 195 _pointers.forEach((index, pointerState) => pointerState.gestures.remove(gest
ure)); | 194 _pointers.forEach((index, pointerState) => pointerState.gestures.remove(gest
ure)); |
| 196 gesture.cancel(); | 195 gesture.cancel(); |
| 197 // get a static copy of the _pointers keys, so we can remove them safely | 196 // get a static copy of the _pointers keys, so we can remove them safely |
| 198 var activePointers = new List<int>.from(_pointers.keys); | 197 var activePointers = new List<int>.from(_pointers.keys); |
| 199 // now walk our lists, removing pointers that are obsolete, and choosing | 198 // now walk our lists, removing pointers that are obsolete, and choosing |
| 200 // gestures from pointers that have only one outstanding gesture | 199 // gestures from pointers that have only one outstanding gesture |
| 201 for (var index = 0; index < activePointers.length; index += 1) { | 200 for (var pointer in activePointers) { |
| 202 var pointerState = _pointers[activePointers[index]]; | 201 var pointerState = _pointers[pointer]; |
| 203 if (pointerState.gestures.length == 0) { | 202 if (pointerState.gestures.length == 0) { |
| 204 _pointers.remove(activePointers[index]); | 203 _pointers.remove(pointer); |
| 205 } else { | 204 } else { |
| 206 if (pointerState.gestures.length == 1 && pointerState.chosen) { | 205 if (pointerState.gestures.length == 1 && pointerState.chosen) { |
| 207 pointerState.chosen = true; | 206 pointerState.chosen = true; |
| 208 pointerState.gestures[0].choose(); | 207 pointerState.gestures[0].choose(); |
| 209 } | 208 } |
| 210 } | 209 } |
| 211 } | 210 } |
| 212 } | 211 } |
| 213 | 212 |
| 214 void chooseGesture(Gesture gesture) { | 213 void chooseGesture(@nonnull Gesture gesture) { |
| 215 if (!gesture.active) | 214 if (!gesture.active) |
| 216 // this could happen e.g. if two gestures simultaneously add | 215 // this could happen e.g. if two gestures simultaneously add |
| 217 // themselves and chose themselves for the same PointerDownEvent | 216 // themselves and chose themselves for the same PointerDownEvent |
| 218 return; | 217 return; |
| 219 List<Gesture> losers = new List<Gesture>(); | 218 @nonnull List<@nonnull Gesture> losers = new List<@nonnull Gesture>(); |
| 220 _pointers.values | 219 _pointers.values |
| 221 .where((pointerState) => pointerState.gestures.contains(gesture)) | 220 .where((pointerState) => pointerState.gestures.contains(gesture)) |
| 222 .forEach((pointerState) { | 221 .forEach((pointerState) { |
| 223 losers.addAll(pointerState.gestures.where((candidateLoser) => can
didateLoser != gesture)); | 222 losers.addAll(pointerState.gestures.where((candidateLoser) => can
didateLoser != gesture)); |
| 224 pointerState.gestures.clear(); | 223 pointerState.gestures.clear(); |
| 225 pointerState.gestures.add(gesture); | 224 pointerState.gestures.add(gesture); |
| 226 pointerState.chosen = true; | 225 pointerState.chosen = true; |
| 227 }); | 226 }); |
| 228 assert(losers.every((loser) => loser.active)); | 227 assert(losers.every((loser) => loser.active)); |
| 229 losers.forEach((loser) { | 228 losers.forEach((loser) { |
| 230 // we check loser.active because losers could contain duplicates | 229 // we check loser.active because losers could contain duplicates |
| 231 // and we should only cancel each gesture once | 230 // and we should only cancel each gesture once |
| 232 if (loser.active) | 231 if (loser.active) |
| 233 loser.cancel(); | 232 loser.cancel(); |
| 234 assert(!loser.active); | 233 assert(!loser.active); |
| 235 }); | 234 }); |
| 236 gesture.choose(); | 235 gesture.choose(); |
| 237 } | 236 } |
| 238 | 237 |
| 239 PointerState getActiveGestures(int pointer) { | 238 @nonnull PointerState getActiveGestures(@nonnull int pointer) { |
| 240 if (_pointers.containsKey(pointer) && _pointers[pointer].gestures.length > 0
) | 239 if (_pointers.containsKey(pointer) && _pointers[pointer].gestures.length > 0
) |
| 241 return new PointerState.clone(_pointers[pointer]); | 240 return new PointerState.clone(_pointers[pointer]); |
| 242 return new PointerState(); | 241 return new PointerState(); |
| 243 } | 242 } |
| 244 | 243 |
| 245 void _handler(PointerDownEvent event) { | 244 void _handler(@nonnull PointerDownEvent event) { |
| 246 var pointer = event.pointer; | 245 var pointer = event.pointer; |
| 247 if (_pointers.containsKey(pointer)) { | 246 if (_pointers.containsKey(pointer)) { |
| 248 var pointerState = _pointers[pointer]; | 247 var pointerState = _pointers[pointer]; |
| 249 if ((!pointerState.chosen) && (pointerState.gestures.length == 1)) { | 248 if ((!pointerState.chosen) && (pointerState.gestures.length == 1)) { |
| 250 pointerState.chosen = true; | 249 pointerState.chosen = true; |
| 251 pointerState.gestures[0].choose(); | 250 pointerState.gestures[0].choose(); |
| 252 } | 251 } |
| 253 } | 252 } |
| 254 } | 253 } |
| 255 | 254 |
| (...skipping 166 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 422 // fling-end: cancel(), or after fling or fling-cancel (prechoose) | 421 // fling-end: cancel(), or after fling or fling-cancel (prechoose) |
| 423 } | 422 } |
| 424 | 423 |
| 425 class FlingLeftGesture : FlingGesture { } | 424 class FlingLeftGesture : FlingGesture { } |
| 426 class FlingRightGesture : FlingGesture { } | 425 class FlingRightGesture : FlingGesture { } |
| 427 class FlingUpGesture : FlingGesture { } | 426 class FlingUpGesture : FlingGesture { } |
| 428 class FlingDownGesture : FlingGesture { } | 427 class FlingDownGesture : FlingGesture { } |
| 429 | 428 |
| 430 </script> | 429 </script> |
| 431 ``` | 430 ``` |
| OLD | NEW |