Commit 0c0a866c authored by Adam Barth's avatar Adam Barth

Add the ability to create global pointer routes (#4239)

We'll use this functionality to implement some subtle behaviors for tooltips.
parent 989a5303
......@@ -14,11 +14,15 @@ typedef void PointerRoute(PointerEvent event);
/// A routing table for [PointerEvent] events.
class PointerRouter {
final Map<int, LinkedHashSet<PointerRoute>> _routeMap = new Map<int, LinkedHashSet<PointerRoute>>();
final LinkedHashSet<PointerRoute> _globalRoutes = new LinkedHashSet<PointerRoute>();
/// Adds a route to the routing table.
///
/// Whenever this object routes a [PointerEvent] corresponding to
/// pointer, call route.
///
/// Routes added reentrantly within [PointerRouter.route] will take effect when
/// routing the next event.
void addRoute(int pointer, PointerRoute route) {
LinkedHashSet<PointerRoute> routes = _routeMap.putIfAbsent(pointer, () => new LinkedHashSet<PointerRoute>());
assert(!routes.contains(route));
......@@ -29,6 +33,9 @@ class PointerRouter {
///
/// No longer call route when routing a [PointerEvent] corresponding to
/// pointer. Requires that this route was previously added to the router.
///
/// Routes removed reentrantly within [PointerRouter.route] will take effect
/// immediately.
void removeRoute(int pointer, PointerRoute route) {
assert(_routeMap.containsKey(pointer));
LinkedHashSet<PointerRoute> routes = _routeMap[pointer];
......@@ -38,35 +45,66 @@ class PointerRouter {
_routeMap.remove(pointer);
}
/// Adds a route to the global entry in the routing table.
///
/// Whenever this object routes a [PointerEvent], call route.
///
/// Routes added reentrantly within [PointerRouter.route] will take effect when
/// routing the next event.
void addGlobalRoute(PointerRoute route) {
assert(!_globalRoutes.contains(route));
_globalRoutes.add(route);
}
/// Removes a route from the global entry in the routing table.
///
/// No longer call route when routing a [PointerEvent]. Requires that this
/// route was previously added via [addGlobalRoute].
///
/// Routes removed reentrantly within [PointerRouter.route] will take effect
/// immediately.
void removeGlobalRoute(PointerRoute route) {
assert(_globalRoutes.contains(route));
_globalRoutes.remove(route);
}
void _dispatch(PointerEvent event, PointerRoute route) {
try {
route(event);
} catch (exception, stack) {
FlutterError.reportError(new FlutterErrorDetailsForPointerRouter(
exception: exception,
stack: stack,
library: 'gesture library',
context: 'while routing a pointer event',
router: this,
route: route,
event: event,
informationCollector: (StringBuffer information) {
information.writeln('Event:');
information.write(' $event');
}
));
}
}
/// Calls the routes registered for this pointer event.
///
/// Routes are called in the order in which they were added to the
/// PointerRouter object.
void route(PointerEvent event) {
LinkedHashSet<PointerRoute> routes = _routeMap[event.pointer];
if (routes == null)
return;
for (PointerRoute route in new List<PointerRoute>.from(routes)) {
if (!routes.contains(route))
continue;
try {
route(event);
} catch (exception, stack) {
FlutterError.reportError(new FlutterErrorDetailsForPointerRouter(
exception: exception,
stack: stack,
library: 'gesture library',
context: 'while routing a pointer event',
router: this,
route: route,
event: event,
informationCollector: (StringBuffer information) {
information.writeln('Event:');
information.write(' $event');
}
));
List<PointerRoute> globalRoutes = new List<PointerRoute>.from(_globalRoutes);
if (routes != null) {
for (PointerRoute route in new List<PointerRoute>.from(routes)) {
if (routes.contains(route))
_dispatch(event, route);
}
}
for (PointerRoute route in globalRoutes) {
if (_globalRoutes.contains(route))
_dispatch(event, route);
}
}
}
......@@ -112,4 +150,3 @@ class FlutterErrorDetailsForPointerRouter extends FlutterErrorDetails {
/// The pointer event that was being routed when the exception was raised.
final PointerEvent event;
}
......@@ -41,4 +41,80 @@ void main() {
router.route(pointer2.down(Point.origin));
expect(callbackRan, isFalse);
});
test('Supports global callbacks', () {
bool secondCallbackRan = false;
void secondCallback(PointerEvent event) {
secondCallbackRan = true;
}
bool firstCallbackRan = false;
PointerRouter router = new PointerRouter();
router.addGlobalRoute((PointerEvent event) {
firstCallbackRan = true;
router.addGlobalRoute(secondCallback);
});
TestPointer pointer2 = new TestPointer(2);
router.route(pointer2.down(Point.origin));
expect(firstCallbackRan, isTrue);
expect(secondCallbackRan, isFalse);
});
test('Supports re-entrant global cancellation', () {
bool callbackRan = false;
void callback(PointerEvent event) {
callbackRan = true;
}
PointerRouter router = new PointerRouter();
router.addGlobalRoute((PointerEvent event) {
router.removeGlobalRoute(callback);
});
router.addGlobalRoute(callback);
TestPointer pointer2 = new TestPointer(2);
router.route(pointer2.down(Point.origin));
expect(callbackRan, isFalse);
});
test('Per-pointer callbacks cannot re-entrantly add global routes', () {
bool callbackRan = false;
void callback(PointerEvent event) {
callbackRan = true;
}
PointerRouter router = new PointerRouter();
bool perPointerCallbackRan = false;
router.addRoute(2, (PointerEvent event) {
perPointerCallbackRan = true;
router.addGlobalRoute(callback);
});
TestPointer pointer2 = new TestPointer(2);
router.route(pointer2.down(Point.origin));
expect(perPointerCallbackRan, isTrue);
expect(callbackRan, isFalse);
});
test('Per-pointer callbacks happen before global callbacks', () {
List<String> log = <String>[];
PointerRouter router = new PointerRouter();
router.addGlobalRoute((PointerEvent event) {
log.add('global 1');
});
router.addRoute(2, (PointerEvent event) {
log.add('per-pointer 1');
});
router.addGlobalRoute((PointerEvent event) {
log.add('global 2');
});
router.addRoute(2, (PointerEvent event) {
log.add('per-pointer 2');
});
TestPointer pointer2 = new TestPointer(2);
router.route(pointer2.down(Point.origin));
expect(log, equals(<String>[
'per-pointer 1',
'per-pointer 2',
'global 1',
'global 2',
]));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment