mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
193 lines
5.7 KiB
Markdown
193 lines
5.7 KiB
Markdown
Sky Event Model
|
|
===============
|
|
|
|
```dart
|
|
import 'dart:collection';
|
|
import 'dart:async';
|
|
|
|
class ExceptionAndStackTrace<T> {
|
|
const ExceptionAndStackTrace(this.exception, this.stackTrace);
|
|
final T exception;
|
|
final StackTrace stackTrace;
|
|
}
|
|
|
|
class ExceptionListException<T> extends IterableMixin<ExceptionAndStackTrace<T>> implements Exception {
|
|
List<ExceptionAndStackTrace<T>> _exceptions;
|
|
void add(T exception, [StackTrace stackTrace = null]) {
|
|
if (_exceptions == null)
|
|
_exceptions = new List<ExceptionAndStackTrace<T>>();
|
|
_exceptions.add(new ExceptionAndStackTrace<T>(exception, stackTrace));
|
|
}
|
|
int get length => _exceptions == null ? 0 : _exceptions.length;
|
|
Iterator<ExceptionAndStackTrace<T>> get iterator => _exceptions.iterator;
|
|
}
|
|
|
|
typedef bool Filter<T>(T t);
|
|
typedef void Handler<T>(T t);
|
|
|
|
class DispatcherController<T> {
|
|
DispatcherController() : dispatcher = new Dispatcher<T>();
|
|
final Dispatcher<T> dispatcher;
|
|
void add(T data) => dispatcher._add(data);
|
|
}
|
|
|
|
class Dispatcher<T> {
|
|
List<Pair<Handler, ZoneUnaryCallback>> _listeners;
|
|
void listen(Handler<T> handler) {
|
|
// you should not throw out of this handler
|
|
if (_listeners == null)
|
|
_listeners = new List<Pair<Handler, ZoneUnaryCallback>>();
|
|
_listeners.add(new Pair<Handler, ZoneUnaryCallback>(handler, Zone.current.bindUnaryCallback(handler)));
|
|
}
|
|
bool unlisten(Handler<T> handler) {
|
|
if (_listeners == null)
|
|
return false;
|
|
var target = _listeners.lastWhere((v) => v.a == handler, orElse: () => null);
|
|
if (target == null)
|
|
return false;
|
|
_listeners.removeAt(_listeners.lastIndexOf(target));
|
|
return true;
|
|
}
|
|
void _add(T data) {
|
|
if (_listeners == null)
|
|
return;
|
|
ExceptionListException exceptions = new ExceptionListException();
|
|
// we make a copy of the list here so that the listeners can
|
|
// mutate our list without worry
|
|
_listeners.toList().forEach((Pair<Handler, ZoneUnaryCallback> item) {
|
|
try {
|
|
item.b(data);
|
|
} catch (exception, stackTrace) {
|
|
exceptions.add(exception, stackTrace);
|
|
}
|
|
});
|
|
if (exceptions.length > 0)
|
|
throw exceptions;
|
|
}
|
|
|
|
Dispatcher<T> where(Filter<T> filter) => new WhereDispatcher<T>(this, filter);
|
|
|
|
Dispatcher<T> until(Filter<T> filter) {
|
|
var subdispatcher = new Dispatcher<T>();
|
|
Handler handler;
|
|
handler = (T data) {
|
|
if (filter(data))
|
|
unlisten(handler);
|
|
else
|
|
subdispatcher._add(data);
|
|
};
|
|
listen(handler);
|
|
return subdispatcher;
|
|
}
|
|
|
|
Future<T> firstWhere(Filter<T> filter) {
|
|
Completer completer = new Completer();
|
|
Handler handler;
|
|
handler = (T data) {
|
|
if (filter(data)) {
|
|
completer.complete(data);
|
|
unlisten(handler);
|
|
}
|
|
};
|
|
listen(handler);
|
|
return completer.future;
|
|
}
|
|
}
|
|
|
|
class WhereDispatcher<T> extends Dispatcher {
|
|
WhereDispatcher(this.parent, this.filter) : super();
|
|
Dispatcher parent;
|
|
Filter filter;
|
|
|
|
void listen(Handler<T> handler) {
|
|
if (_listeners == null || _listeners.length == 0)
|
|
parent.listen(_handler);
|
|
super.listen(handler);
|
|
}
|
|
bool unlisten(Handler<T> handler) {
|
|
var result = super.unlisten(handler);
|
|
if (result && _listeners.length == 0)
|
|
parent.unlisten(_handler);
|
|
return result;
|
|
}
|
|
void _handler(T data) {
|
|
if (filter(data))
|
|
_add(data);
|
|
}
|
|
}
|
|
|
|
abstract class Event<ReturnType> {
|
|
Event() { init(); }
|
|
void init() { }
|
|
|
|
bool get bubbles;
|
|
|
|
EventTarget _target;
|
|
EventTarget get target => _target;
|
|
|
|
EventTarget _currentTarget;
|
|
EventTarget get currentTarget => _currentTarget;
|
|
|
|
bool handled; // precise semantics depend on the event type, but in general, set this when you set result
|
|
ReturnType result;
|
|
|
|
bool resultIsCompatible(dynamic candidate) => candidate is ReturnType;
|
|
|
|
// TODO(ianh): abstract API for doing things at shadow tree boundaries
|
|
// TODO(ianh): do events get blocked at scope boundaries, e.g. focus events when both sides are in the scope?
|
|
// TODO(ianh): do events get retargetted, e.g. focus when leaving a custom element?
|
|
// e.g. sent from inside a shadow tree, when exiting the shadow tree, focus event should:
|
|
// - disappear if we're moving from one to another element
|
|
// - be targetted if it's going to another node in a different scope
|
|
}
|
|
|
|
class EventTarget {
|
|
EventTarget() : _eventsController = new DispatcherController<Event>();
|
|
|
|
Dispatcher get events => _eventsController.dispatcher;
|
|
EventTarget get parentNode;
|
|
|
|
List<EventTarget> getEventDispatchChain() {
|
|
if (this.parentNode == null) {
|
|
return [this];
|
|
} else {
|
|
var result = this.parentNode.getEventDispatchChain();
|
|
result.insert(0, this);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
final DispatcherController _eventsController;
|
|
|
|
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 per link in the chain
|
|
// note: this will throw an ExceptionListException<ExceptionListException> if any of the listeners threw
|
|
assert(event != null); // event must be non-null
|
|
event.handled = false;
|
|
assert(event.resultIsCompatible(defaultResult));
|
|
event.result = defaultResult;
|
|
event._target = this;
|
|
var chain;
|
|
if (event.bubbles)
|
|
chain = this.getEventDispatchChain();
|
|
else
|
|
chain = [this];
|
|
var exceptions = new ExceptionListException<ExceptionListException>();
|
|
for (var link in chain) {
|
|
try {
|
|
link._dispatchEventLocally(event);
|
|
} on ExceptionListException catch (e) {
|
|
exceptions.add(e);
|
|
}
|
|
}
|
|
if (exceptions.length > 0)
|
|
throw exceptions;
|
|
return event.result;
|
|
}
|
|
|
|
void _dispatchEventLocally(Event event) {
|
|
event._currentTarget = this;
|
|
_eventsController.add(event);
|
|
}
|
|
}
|
|
```
|