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.
|
/// A routing table for [PointerEvent] events.
|
||||||
class PointerRouter {
|
class PointerRouter {
|
||||||
final Map<int, LinkedHashSet<PointerRoute>> _routeMap = new Map<int, LinkedHashSet<PointerRoute>>();
|
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.
|
/// Adds a route to the routing table.
|
||||||
///
|
///
|
||||||
/// Whenever this object routes a [PointerEvent] corresponding to
|
/// Whenever this object routes a [PointerEvent] corresponding to
|
||||||
/// pointer, call route.
|
/// pointer, call route.
|
||||||
|
///
|
||||||
|
/// Routes added reentrantly within [PointerRouter.route] will take effect when
|
||||||
|
/// routing the next event.
|
||||||
void addRoute(int pointer, PointerRoute route) {
|
void addRoute(int pointer, PointerRoute route) {
|
||||||
LinkedHashSet<PointerRoute> routes = _routeMap.putIfAbsent(pointer, () => new LinkedHashSet<PointerRoute>());
|
LinkedHashSet<PointerRoute> routes = _routeMap.putIfAbsent(pointer, () => new LinkedHashSet<PointerRoute>());
|
||||||
assert(!routes.contains(route));
|
assert(!routes.contains(route));
|
||||||
@ -29,6 +33,9 @@ class PointerRouter {
|
|||||||
///
|
///
|
||||||
/// No longer call route when routing a [PointerEvent] corresponding to
|
/// No longer call route when routing a [PointerEvent] corresponding to
|
||||||
/// pointer. Requires that this route was previously added to the router.
|
/// 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) {
|
void removeRoute(int pointer, PointerRoute route) {
|
||||||
assert(_routeMap.containsKey(pointer));
|
assert(_routeMap.containsKey(pointer));
|
||||||
LinkedHashSet<PointerRoute> routes = _routeMap[pointer];
|
LinkedHashSet<PointerRoute> routes = _routeMap[pointer];
|
||||||
@ -38,35 +45,66 @@ class PointerRouter {
|
|||||||
_routeMap.remove(pointer);
|
_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.
|
/// Calls the routes registered for this pointer event.
|
||||||
///
|
///
|
||||||
/// Routes are called in the order in which they were added to the
|
/// Routes are called in the order in which they were added to the
|
||||||
/// PointerRouter object.
|
/// PointerRouter object.
|
||||||
void route(PointerEvent event) {
|
void route(PointerEvent event) {
|
||||||
LinkedHashSet<PointerRoute> routes = _routeMap[event.pointer];
|
LinkedHashSet<PointerRoute> routes = _routeMap[event.pointer];
|
||||||
if (routes == null)
|
List<PointerRoute> globalRoutes = new List<PointerRoute>.from(_globalRoutes);
|
||||||
return;
|
if (routes != null) {
|
||||||
for (PointerRoute route in new List<PointerRoute>.from(routes)) {
|
for (PointerRoute route in new List<PointerRoute>.from(routes)) {
|
||||||
if (!routes.contains(route))
|
if (routes.contains(route))
|
||||||
continue;
|
_dispatch(event, 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');
|
|
||||||
}
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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.
|
/// The pointer event that was being routed when the exception was raised.
|
||||||
final PointerEvent event;
|
final PointerEvent event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,4 +41,80 @@ void main() {
|
|||||||
router.route(pointer2.down(Point.origin));
|
router.route(pointer2.down(Point.origin));
|
||||||
expect(callbackRan, isFalse);
|
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