Add the ability to create global pointer routes (#4239)
We'll use this functionality to implement some subtle behaviors for tooltips.
This commit is contained in:
parent
989a530350
commit
0c0a866c79
@ -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,17 +45,30 @@ class PointerRouter {
|
||||
_routeMap.remove(pointer);
|
||||
}
|
||||
|
||||
/// Calls the routes registered for this pointer event.
|
||||
/// Adds a route to the global entry in the routing table.
|
||||
///
|
||||
/// 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;
|
||||
/// 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) {
|
||||
@ -67,6 +87,24 @@ class PointerRouter {
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// 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];
|
||||
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',
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user