| OLD | NEW |
| 1 Sky Event Model | 1 Sky Event Model |
| 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 import 'dart:collection'; |
| 10 import 'dart:async'; |
| 11 |
| 12 class ExceptionAndStackTrace<T> { |
| 13 const ExceptionAndStackTrace(this.exception, this.stackTrace); |
| 14 final T exception; |
| 15 final StackTrace stackTrace; |
| 16 } |
| 17 |
| 18 class ExceptionListException<T> extends IterableMixin<ExceptionAndStackTrace<T>>
implements Exception { |
| 19 List<ExceptionAndStackTrace<T>> _exceptions; |
| 20 void add(T exception, [StackTrace stackTrace = null]) { |
| 21 if (_exceptions == null) |
| 22 _exceptions = new List<ExceptionAndStackTrace<T>>(); |
| 23 _exceptions.add(new ExceptionAndStackTrace<T>(exception, stackTrace)); |
| 24 } |
| 25 int get length => _exceptions == null ? 0 : _exceptions.length; |
| 26 Iterator<ExceptionAndStackTrace<T>> get iterator => _exceptions.iterator; |
| 27 } |
| 28 |
| 29 typedef bool Filter<T>(T t); |
| 30 typedef void Handler<T>(T t); |
| 31 |
| 32 class DispatcherController<T> { |
| 33 DispatcherController() : dispatcher = new Dispatcher<T>(); |
| 34 final Dispatcher<T> dispatcher; |
| 35 void add(T data) => dispatcher._add(data); |
| 36 } |
| 37 |
| 38 class Pair<A, B> { |
| 39 const Pair(this.a, this.b); |
| 40 final A a; |
| 41 final B b; |
| 42 } |
| 43 |
| 44 class Dispatcher<T> { |
| 45 List<Pair<Handler, ZoneUnaryCallback>> _listeners; |
| 46 void listen(Handler<T> handler) { |
| 47 // you should not throw out of this handler |
| 48 if (_listeners == null) |
| 49 _listeners = new List<Pair<Handler, ZoneUnaryCallback>>(); |
| 50 _listeners.add(new Pair<Handler, ZoneUnaryCallback>(handler, Zone.current.bi
ndUnaryCallback(handler))); |
| 51 } |
| 52 bool unlisten(Handler<T> handler) { |
| 53 if (_listeners == null) |
| 54 return false; |
| 55 var target = _listeners.lastWhere((v) => v.a == handler, orElse: () => null)
; |
| 56 if (target == null) |
| 57 return false; |
| 58 _listeners.remove(target); |
| 59 return true; |
| 60 } |
| 61 void _add(T data) { |
| 62 if (_listeners == null) |
| 63 return; |
| 64 ExceptionListException exceptions = new ExceptionListException(); |
| 65 // we make a copy of the list here so that the listeners can |
| 66 // mutate our list without worry |
| 67 _listeners.toList().forEach((Pair<Handler, ZoneUnaryCallback> item) { |
| 68 try { |
| 69 item.b(data); |
| 70 } catch (exception, stackTrace) { |
| 71 exceptions.add(exception, stackTrace); |
| 72 } |
| 73 }); |
| 74 if (exceptions.length > 0) |
| 75 throw exceptions; |
| 76 } |
| 77 |
| 78 Dispatcher<T> where(Filter<T> filter) { |
| 79 var subdispatcher = new Dispatcher<T>(); |
| 80 listen((T data) { |
| 81 if (filter(data)) |
| 82 subdispatcher._add(data); |
| 83 }); |
| 84 return subdispatcher; |
| 85 } |
| 86 |
| 87 Dispatcher<T> until(Filter<T> filter) { |
| 88 var subdispatcher = new Dispatcher<T>(); |
| 89 Handler handler; |
| 90 handler = (T data) { |
| 91 if (filter(data)) |
| 92 unlisten(handler); |
| 93 else |
| 94 subdispatcher._add(data); |
| 95 }; |
| 96 listen(handler); |
| 97 return subdispatcher; |
| 98 } |
| 99 |
| 100 Future<T> firstWhere(Filter<T> filter) { |
| 101 Completer completer = new Completer(); |
| 102 Handler handler; |
| 103 handler = (T data) { |
| 104 if (filter(data)) { |
| 105 completer.complete(data); |
| 106 unlisten(handler); |
| 107 } |
| 108 }; |
| 109 listen(handler); |
| 110 return completer.future; |
| 111 } |
| 112 } |
| 113 |
| 9 abstract class Event<ReturnType> { | 114 abstract class Event<ReturnType> { |
| 10 Event() { init(); } | 115 Event() { init(); } |
| 11 void init() { } | 116 void init() { } |
| 12 | 117 |
| 13 bool get bubbles; | 118 bool get bubbles; |
| 14 | 119 |
| 15 EventTarget _target; | 120 EventTarget _target; |
| 16 EventTarget get target => _target; | 121 EventTarget get target => _target; |
| 17 | 122 |
| 18 EventTarget _currentTarget; | 123 EventTarget _currentTarget; |
| 19 EventTarget get currentTarget => _currentTarget; | 124 EventTarget get currentTarget => _currentTarget; |
| 20 | 125 |
| 21 bool handled; // precise semantics depend on the event type, but in general, s
et this when you set result | 126 bool handled; // precise semantics depend on the event type, but in general, s
et this when you set result |
| 22 ReturnType result; | 127 ReturnType result; |
| 23 | 128 |
| 24 bool resultIsCompatible(dynamic candidate) => candidate is ReturnType; | 129 bool resultIsCompatible(dynamic candidate) => candidate is ReturnType; |
| 25 | 130 |
| 26 // TODO(ianh): abstract API for doing things at shadow tree boundaries | 131 // TODO(ianh): abstract API for doing things at shadow tree boundaries |
| 27 // TODO(ianh): do events get blocked at scope boundaries, e.g. focus events wh
en both sides are in the scope? | 132 // TODO(ianh): do events get blocked at scope boundaries, e.g. focus events wh
en both sides are in the scope? |
| 28 // TODO(ianh): do events get retargetted, e.g. focus when leaving a custom ele
ment? | 133 // TODO(ianh): do events get retargetted, e.g. focus when leaving a custom ele
ment? |
| 29 } | 134 } |
| 30 | 135 |
| 31 class EventTarget { | 136 class EventTarget { |
| 32 EventTarget() : _eventsController = new DispatcherController<@nonnull Event>()
; | 137 EventTarget() : _eventsController = new DispatcherController<Event>(); |
| 33 | 138 |
| 34 Dispatcher get events => _eventsController.dispatcher; | 139 Dispatcher get events => _eventsController.dispatcher; |
| 35 EventTarget parentNode; | 140 EventTarget parentNode; |
| 36 | 141 |
| 37 List<@nonnull EventTarget> getEventDispatchChain() { | 142 List<EventTarget> getEventDispatchChain() { |
| 38 if (this.parentNode == null) { | 143 if (this.parentNode == null) { |
| 39 return [this]; | 144 return [this]; |
| 40 } else { | 145 } else { |
| 41 var result = this.parentNode.getEventDispatchChain(); | 146 var result = this.parentNode.getEventDispatchChain(); |
| 42 result.insert(0, this); | 147 result.insert(0, this); |
| 43 return result; | 148 return result; |
| 44 } | 149 } |
| 45 } | 150 } |
| 46 | 151 |
| 47 final DispatcherController _eventsController; | 152 final DispatcherController _eventsController; |
| 48 | 153 |
| 49 dynamic dispatchEvent(@nonnull Event event, { dynamic defaultResult: null }) {
// O(N*M) where N is the length of the chain and M is the average number of lis
teners per link in the chain | 154 dynamic dispatchEvent(Event event, { dynamic defaultResult: null }) { // O(N*M
) where N is the length of the chain and M is the average number of listeners pe
r link in the chain |
| 50 // note: this will throw an ExceptionListException<ExceptionListException> i
f any of the listeners threw | 155 // note: this will throw an ExceptionListException<ExceptionListException> i
f any of the listeners threw |
| 51 assert(event != null); // event must be non-null | 156 assert(event != null); // event must be non-null |
| 52 event.handled = false; | 157 event.handled = false; |
| 53 assert(event.resultIsCompatible(defaultResult)); | 158 assert(event.resultIsCompatible(defaultResult)); |
| 54 event.result = defaultResult; | 159 event.result = defaultResult; |
| 55 event._target = this; | 160 event._target = this; |
| 56 var chain; | 161 var chain; |
| 57 if (event.bubbles) | 162 if (event.bubbles) |
| 58 chain = this.getEventDispatchChain(); | 163 chain = this.getEventDispatchChain(); |
| 59 else | 164 else |
| 60 chain = [this]; | 165 chain = [this]; |
| 61 var exceptions = new ExceptionListException<ExceptionListException>(); | 166 var exceptions = new ExceptionListException<ExceptionListException>(); |
| 62 for (var link in chain) { | 167 for (var link in chain) { |
| 63 try { | 168 try { |
| 64 link._dispatchEventLocally(event); | 169 link._dispatchEventLocally(event); |
| 65 } on ExceptionListException catch (e) { | 170 } on ExceptionListException catch (e) { |
| 66 exceptions.add(e); | 171 exceptions.add(e); |
| 67 } | 172 } |
| 68 } | 173 } |
| 69 if (exceptions.length > 0) | 174 if (exceptions.length > 0) |
| 70 throw exceptions; | 175 throw exceptions; |
| 71 return event.result; | 176 return event.result; |
| 72 } | 177 } |
| 73 | 178 |
| 74 void _dispatchEventLocally(@nonnull Event event) { | 179 void _dispatchEventLocally(Event event) { |
| 75 event._currentTarget = this; | 180 event._currentTarget = this; |
| 76 _eventsController.add(event); | 181 _eventsController.add(event); |
| 77 } | 182 } |
| 78 } | 183 } |
| 79 </script> | 184 </script> |
| 80 ``` | 185 ``` |
| OLD | NEW |