Unverified Commit 05097916 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Fix mouse hover to not schedule a frame for every mouse move. (#41014)

This fixes the mouse hover code to not schedule frames with every mouse move.

Before this, it would schedule a post frame callback, and then schedule a frame immediately, even if there was nothing that needed to be updated. Now it will schedule checks for mouse position updates synchronously, unless there's a new annotation, and skip scheduling a new frame in all cases. It has to be async in the case of a new annotation (i.e. a new MouseRegion is added), since when the annotation is added, it hasn't yet painted, and it can't hit test against the new layer until after the paint, so in that case it schedules a post frame callback, but since it's already building a frame when it does that, it doesn't need to schedule a frame.

The code also used to do mouse position checks for all mice if only one mouse changed position. I fixed this part too, so that it will only check position for the mouse that changed.
parent 75552684
...@@ -92,12 +92,21 @@ class MouseTracker extends ChangeNotifier { ...@@ -92,12 +92,21 @@ class MouseTracker extends ChangeNotifier {
/// Creates a mouse tracker to keep track of mouse locations. /// Creates a mouse tracker to keep track of mouse locations.
/// ///
/// All of the parameters must not be null. /// All of the parameters must not be null.
MouseTracker(PointerRouter router, this.annotationFinder) MouseTracker(this._router, this.annotationFinder)
: assert(router != null), : assert(_router != null),
assert(annotationFinder != null) { assert(annotationFinder != null) {
router.addGlobalRoute(_handleEvent); _router.addGlobalRoute(_handleEvent);
} }
@override
void dispose() {
super.dispose();
_router.removeGlobalRoute(_handleEvent);
}
// The pointer router that the mouse tracker listens to for events.
final PointerRouter _router;
/// Used to find annotations at a given logical coordinate. /// Used to find annotations at a given logical coordinate.
final MouseDetectorAnnotationFinder annotationFinder; final MouseDetectorAnnotationFinder annotationFinder;
...@@ -111,16 +120,19 @@ class MouseTracker extends ChangeNotifier { ...@@ -111,16 +120,19 @@ class MouseTracker extends ChangeNotifier {
/// annotation has been added to the layer tree. /// annotation has been added to the layer tree.
void attachAnnotation(MouseTrackerAnnotation annotation) { void attachAnnotation(MouseTrackerAnnotation annotation) {
_trackedAnnotations[annotation] = _TrackedAnnotation(annotation); _trackedAnnotations[annotation] = _TrackedAnnotation(annotation);
// Schedule a check so that we test this new annotation to see if the mouse // Schedule a check so that we test this new annotation to see if any mouse
// is currently inside its region. // is currently inside its region. It has to happen after the frame is
_scheduleMousePositionCheck(); // complete so that the annotation layer has been added before the check.
if (mouseIsConnected) {
_scheduleMousePositionCheck();
}
} }
/// Stops tracking an annotation, indicating that it has been removed from the /// Stops tracking an annotation, indicating that it has been removed from the
/// layer tree. /// layer tree.
/// ///
/// If the associated layer is not removed, and receives a hit, then /// If the associated layer is not removed, and receives a hit, then
/// [collectMousePositions] will assert the next time it is called. /// [sendMouseNotifications] will assert the next time it is called.
void detachAnnotation(MouseTrackerAnnotation annotation) { void detachAnnotation(MouseTrackerAnnotation annotation) {
final _TrackedAnnotation trackedAnnotation = _findAnnotation(annotation); final _TrackedAnnotation trackedAnnotation = _findAnnotation(annotation);
for (int deviceId in trackedAnnotation.activeDevices) { for (int deviceId in trackedAnnotation.activeDevices) {
...@@ -133,18 +145,19 @@ class MouseTracker extends ChangeNotifier { ...@@ -133,18 +145,19 @@ class MouseTracker extends ChangeNotifier {
_trackedAnnotations.remove(annotation); _trackedAnnotations.remove(annotation);
} }
bool _postFrameCheckScheduled = false; bool _scheduledPostFramePositionCheck = false;
// Schedules a position check at the end of this frame for those annotations
// that have been added.
void _scheduleMousePositionCheck() { void _scheduleMousePositionCheck() {
// If we're not tracking anything, then there is no point in registering a // If we're not tracking anything, then there is no point in registering a
// frame callback or scheduling a frame. By definition there are no active // frame callback or scheduling a frame. By definition there are no active
// annotations that need exiting, either. // annotations that need exiting, either.
if (_trackedAnnotations.isNotEmpty && !_postFrameCheckScheduled) { if (_trackedAnnotations.isNotEmpty && !_scheduledPostFramePositionCheck) {
_postFrameCheckScheduled = true; _scheduledPostFramePositionCheck = true;
SchedulerBinding.instance.addPostFrameCallback((Duration _) { SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
_postFrameCheckScheduled = false; sendMouseNotifications(_lastMouseEvent.keys);
collectMousePositions(); _scheduledPostFramePositionCheck = false;
}); });
SchedulerBinding.instance.scheduleFrame();
} }
} }
...@@ -158,21 +171,24 @@ class MouseTracker extends ChangeNotifier { ...@@ -158,21 +171,24 @@ class MouseTracker extends ChangeNotifier {
// If we are adding the device again, then we're not removing it anymore. // If we are adding the device again, then we're not removing it anymore.
_pendingRemovals.remove(deviceId); _pendingRemovals.remove(deviceId);
_addMouseEvent(deviceId, event); _addMouseEvent(deviceId, event);
sendMouseNotifications(<int>{deviceId});
return; return;
} }
if (event is PointerRemovedEvent) { if (event is PointerRemovedEvent) {
_removeMouseEvent(deviceId, event); _removeMouseEvent(deviceId, event);
// If the mouse was removed, then we need to schedule one more check to // If the mouse was removed, then we need to schedule one more check to
// exit any annotations that were active. // exit any annotations that were active.
_scheduleMousePositionCheck(); sendMouseNotifications(<int>{deviceId});
} else { } else {
if (event is PointerMoveEvent || event is PointerHoverEvent || event is PointerDownEvent) { if (event is PointerMoveEvent || event is PointerHoverEvent || event is PointerDownEvent) {
if (!_lastMouseEvent.containsKey(deviceId) || _lastMouseEvent[deviceId].position != event.position) { final PointerEvent lastEvent = _lastMouseEvent[deviceId];
_addMouseEvent(deviceId, event);
if (lastEvent == null ||
lastEvent is PointerAddedEvent || lastEvent.position != event.position) {
// Only schedule a frame if we have our first event, or if the // Only schedule a frame if we have our first event, or if the
// location of the mouse has changed, and only if there are tracked annotations. // location of the mouse has changed, and only if there are tracked annotations.
_scheduleMousePositionCheck(); sendMouseNotifications(<int>{deviceId});
} }
_addMouseEvent(deviceId, event);
} }
} }
} }
...@@ -206,14 +222,18 @@ class MouseTracker extends ChangeNotifier { ...@@ -206,14 +222,18 @@ class MouseTracker extends ChangeNotifier {
/// This function is only public to allow for proper testing of the /// This function is only public to allow for proper testing of the
/// MouseTracker. Do not call in other contexts. /// MouseTracker. Do not call in other contexts.
@visibleForTesting @visibleForTesting
void collectMousePositions() { void sendMouseNotifications(Iterable<int> deviceIds) {
if (_trackedAnnotations.isEmpty) {
return;
}
void exitAnnotation(_TrackedAnnotation trackedAnnotation, int deviceId) { void exitAnnotation(_TrackedAnnotation trackedAnnotation, int deviceId) {
if (trackedAnnotation.annotation?.onExit != null && trackedAnnotation.activeDevices.contains(deviceId)) { if (trackedAnnotation.annotation?.onExit != null && trackedAnnotation.activeDevices.contains(deviceId)) {
final PointerEvent event = _lastMouseEvent[deviceId] ?? _pendingRemovals[deviceId]; final PointerEvent event = _lastMouseEvent[deviceId] ?? _pendingRemovals[deviceId];
assert(event != null); assert(event != null);
trackedAnnotation.annotation.onExit(PointerExitEvent.fromMouseEvent(event)); trackedAnnotation.annotation.onExit(PointerExitEvent.fromMouseEvent(event));
trackedAnnotation.activeDevices.remove(deviceId);
} }
trackedAnnotation.activeDevices.remove(deviceId);
} }
void exitAllDevices(_TrackedAnnotation trackedAnnotation) { void exitAllDevices(_TrackedAnnotation trackedAnnotation) {
...@@ -234,8 +254,9 @@ class MouseTracker extends ChangeNotifier { ...@@ -234,8 +254,9 @@ class MouseTracker extends ChangeNotifier {
return; return;
} }
for (int deviceId in _lastMouseEvent.keys) { for (int deviceId in deviceIds) {
final PointerEvent lastEvent = _lastMouseEvent[deviceId]; final PointerEvent lastEvent = _lastMouseEvent[deviceId];
assert(lastEvent != null);
final Iterable<MouseTrackerAnnotation> hits = annotationFinder(lastEvent.position); final Iterable<MouseTrackerAnnotation> hits = annotationFinder(lastEvent.position);
// No annotations were found at this position for this deviceId, so send an // No annotations were found at this position for this deviceId, so send an
...@@ -311,7 +332,6 @@ class MouseTracker extends ChangeNotifier { ...@@ -311,7 +332,6 @@ class MouseTracker extends ChangeNotifier {
/// The most recent mouse event observed for each mouse device ID observed. /// The most recent mouse event observed for each mouse device ID observed.
/// ///
/// May be null if no mouse is connected, or hasn't produced an event yet. /// May be null if no mouse is connected, or hasn't produced an event yet.
/// Will not be updated unless there is at least one tracked annotation.
final Map<int, PointerEvent> _lastMouseEvent = <int, PointerEvent>{}; final Map<int, PointerEvent> _lastMouseEvent = <int, PointerEvent>{};
/// Whether or not a mouse is connected and has produced events. /// Whether or not a mouse is connected and has produced events.
......
...@@ -43,7 +43,7 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture ...@@ -43,7 +43,7 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
_handleSemanticsEnabledChanged(); _handleSemanticsEnabledChanged();
assert(renderView != null); assert(renderView != null);
addPersistentFrameCallback(_handlePersistentFrameCallback); addPersistentFrameCallback(_handlePersistentFrameCallback);
_mouseTracker = _createMouseTracker(); initMouseTracker();
} }
/// The current [RendererBinding], if one has been created. /// The current [RendererBinding], if one has been created.
...@@ -238,10 +238,14 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture ...@@ -238,10 +238,14 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
SemanticsHandle _semanticsHandle; SemanticsHandle _semanticsHandle;
// Creates a [MouseTracker] which manages state about currently connected /// Creates a [MouseTracker] which manages state about currently connected
// mice, for hover notification. /// mice, for hover notification.
MouseTracker _createMouseTracker() { ///
return MouseTracker(pointerRouter, renderView.hitTestMouseTrackers); /// Used by testing framework to reinitialize the mouse tracker between tests.
@visibleForTesting
void initMouseTracker([MouseTracker tracker]) {
_mouseTracker?.dispose();
_mouseTracker = tracker ?? MouseTracker(pointerRouter, renderView.hitTestMouseTrackers);
} }
void _handleSemanticsEnabledChanged() { void _handleSemanticsEnabledChanged() {
......
...@@ -5833,7 +5833,7 @@ class MouseRegion extends SingleChildRenderObjectWidget { ...@@ -5833,7 +5833,7 @@ class MouseRegion extends SingleChildRenderObjectWidget {
final PointerExitEventListener onExit; final PointerExitEventListener onExit;
@override @override
_ListenerElement createElement() => _ListenerElement(this); _MouseRegionElement createElement() => _MouseRegionElement(this);
@override @override
RenderMouseRegion createRenderObject(BuildContext context) { RenderMouseRegion createRenderObject(BuildContext context) {
...@@ -5866,20 +5866,20 @@ class MouseRegion extends SingleChildRenderObjectWidget { ...@@ -5866,20 +5866,20 @@ class MouseRegion extends SingleChildRenderObjectWidget {
} }
} }
class _ListenerElement extends SingleChildRenderObjectElement { class _MouseRegionElement extends SingleChildRenderObjectElement {
_ListenerElement(SingleChildRenderObjectWidget widget) : super(widget); _MouseRegionElement(SingleChildRenderObjectWidget widget) : super(widget);
@override @override
void activate() { void activate() {
super.activate(); super.activate();
final RenderMouseRegion renderMouseListener = renderObject; final RenderMouseRegion renderMouseRegion = renderObject;
renderMouseListener.postActivate(); renderMouseRegion.postActivate();
} }
@override @override
void deactivate() { void deactivate() {
final RenderMouseRegion renderMouseListener = renderObject; final RenderMouseRegion renderMouseRegion = renderObject;
renderMouseListener.preDeactivate(); renderMouseRegion.preDeactivate();
super.deactivate(); super.deactivate();
} }
} }
......
...@@ -388,7 +388,7 @@ void main() { ...@@ -388,7 +388,7 @@ void main() {
final TestGesture gesture = await tester.createGesture( final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse, kind: PointerDeviceKind.mouse,
); );
await gesture.addPointer(); await gesture.addPointer(location: Offset.zero);
addTearDown(gesture.removePointer); addTearDown(gesture.removePointer);
await gesture.moveTo(center); await gesture.moveTo(center);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
......
...@@ -147,6 +147,7 @@ void main() { ...@@ -147,6 +147,7 @@ void main() {
TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer(); await gesture.addPointer();
addTearDown(() => gesture?.removePointer()); addTearDown(() => gesture?.removePointer());
await tester.pumpAndSettle();
await gesture.moveTo(tester.getCenter(find.byType(FloatingActionButton))); await gesture.moveTo(tester.getCenter(find.byType(FloatingActionButton)));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
......
...@@ -8,7 +8,6 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -8,7 +8,6 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
// The tests in this file are moved from listener_test.dart, which tests several // The tests in this file are moved from listener_test.dart, which tests several
// deprecated APIs. The file should be removed once these parameters are. // deprecated APIs. The file should be removed once these parameters are.
...@@ -82,7 +81,7 @@ void main() { ...@@ -82,7 +81,7 @@ void main() {
// onPointer{Enter,Hover,Exit} are removed. They were kept for compatibility, // onPointer{Enter,Hover,Exit} are removed. They were kept for compatibility,
// and the tests have been copied to mouse_region_test. // and the tests have been copied to mouse_region_test.
// https://github.com/flutter/flutter/issues/36085 // https://github.com/flutter/flutter/issues/36085
setUp((){ setUp(() {
HoverClientState.numExits = 0; HoverClientState.numExits = 0;
HoverClientState.numEntries = 0; HoverClientState.numEntries = 0;
}); });
...@@ -91,19 +90,22 @@ void main() { ...@@ -91,19 +90,22 @@ void main() {
PointerEnterEvent enter; PointerEnterEvent enter;
PointerHoverEvent move; PointerHoverEvent move;
PointerExitEvent exit; PointerExitEvent exit;
await tester.pumpWidget(Center( await tester.pumpWidget(
child: Listener( Center(
child: Container( child: Listener(
color: const Color.fromARGB(0xff, 0xff, 0x00, 0x00), child: Container(
width: 100.0, color: const Color.fromARGB(0xff, 0xff, 0x00, 0x00),
height: 100.0, width: 100.0,
height: 100.0,
),
onPointerEnter: (PointerEnterEvent details) => enter = details,
onPointerHover: (PointerHoverEvent details) => move = details,
onPointerExit: (PointerExitEvent details) => exit = details,
), ),
onPointerEnter: (PointerEnterEvent details) => enter = details,
onPointerHover: (PointerHoverEvent details) => move = details,
onPointerExit: (PointerExitEvent details) => exit = details,
), ),
)); );
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer(location: Offset.zero);
addTearDown(gesture.removePointer); addTearDown(gesture.removePointer);
await gesture.moveTo(const Offset(400.0, 300.0)); await gesture.moveTo(const Offset(400.0, 300.0));
await tester.pump(); await tester.pump();
...@@ -117,20 +119,22 @@ void main() { ...@@ -117,20 +119,22 @@ void main() {
PointerEnterEvent enter; PointerEnterEvent enter;
PointerHoverEvent move; PointerHoverEvent move;
PointerExitEvent exit; PointerExitEvent exit;
await tester.pumpWidget(Center( await tester.pumpWidget(
child: Listener( Center(
child: Container( child: Listener(
width: 100.0, child: Container(
height: 100.0, width: 100.0,
height: 100.0,
),
onPointerEnter: (PointerEnterEvent details) => enter = details,
onPointerHover: (PointerHoverEvent details) => move = details,
onPointerExit: (PointerExitEvent details) => exit = details,
), ),
onPointerEnter: (PointerEnterEvent details) => enter = details,
onPointerHover: (PointerHoverEvent details) => move = details,
onPointerExit: (PointerExitEvent details) => exit = details,
), ),
)); );
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer(location: const Offset(400.0, 300.0));
addTearDown(gesture.removePointer); addTearDown(gesture.removePointer);
await gesture.moveTo(const Offset(400.0, 300.0));
await tester.pump(); await tester.pump();
move = null; move = null;
enter = null; enter = null;
...@@ -145,24 +149,25 @@ void main() { ...@@ -145,24 +149,25 @@ void main() {
PointerEnterEvent enter; PointerEnterEvent enter;
PointerHoverEvent move; PointerHoverEvent move;
PointerExitEvent exit; PointerExitEvent exit;
await tester.pumpWidget(Center( await tester.pumpWidget(
child: Listener( Center(
child: Container( child: Listener(
width: 100.0, child: Container(
height: 100.0, width: 100.0,
height: 100.0,
),
onPointerEnter: (PointerEnterEvent details) => enter = details,
onPointerHover: (PointerHoverEvent details) => move = details,
onPointerExit: (PointerExitEvent details) => exit = details,
), ),
onPointerEnter: (PointerEnterEvent details) => enter = details,
onPointerHover: (PointerHoverEvent details) => move = details,
onPointerExit: (PointerExitEvent details) => exit = details,
), ),
)); );
final RenderMouseRegion renderListener = tester.renderObject(find.byType(MouseRegion)); final RenderMouseRegion renderListener = tester.renderObject(find.byType(MouseRegion));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer(location: const Offset(400.0, 300.0));
addTearDown(gesture.removePointer); addTearDown(gesture.removePointer);
await gesture.moveTo(const Offset(400.0, 300.0));
await tester.pump(); await tester.pump();
expect(move, isNotNull); expect(move, isNull);
expect(move.position, equals(const Offset(400.0, 300.0)));
expect(enter, isNotNull); expect(enter, isNotNull);
expect(enter.position, equals(const Offset(400.0, 300.0))); expect(enter.position, equals(const Offset(400.0, 300.0)));
expect(exit, isNull); expect(exit, isNull);
...@@ -197,7 +202,7 @@ void main() { ...@@ -197,7 +202,7 @@ void main() {
await tester.pumpWidget(Container()); await tester.pumpWidget(Container());
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
addTearDown(gesture.removePointer); addTearDown(gesture.removePointer);
await gesture.moveTo(const Offset(400.0, 0.0)); await gesture.addPointer(location: const Offset(400.0, 0.0));
await tester.pump(); await tester.pump();
await tester.pumpWidget( await tester.pumpWidget(
Column( Column(
...@@ -430,9 +435,8 @@ void main() { ...@@ -430,9 +435,8 @@ void main() {
expect(bottomLeft.dy - topLeft.dy, scaleFactor * localHeight); expect(bottomLeft.dy - topLeft.dy, scaleFactor * localHeight);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer(); await gesture.addPointer(location: topLeft - const Offset(1, 1));
addTearDown(gesture.removePointer); addTearDown(gesture.removePointer);
await gesture.moveTo(topLeft - const Offset(1, 1));
await tester.pump(); await tester.pump();
expect(events, isEmpty); expect(events, isEmpty);
...@@ -458,7 +462,7 @@ void main() { ...@@ -458,7 +462,7 @@ void main() {
testWidgets('needsCompositing updates correctly and is respected', (WidgetTester tester) async { testWidgets('needsCompositing updates correctly and is respected', (WidgetTester tester) async {
// Pretend that we have a mouse connected. // Pretend that we have a mouse connected.
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer(); await gesture.addPointer(location: Offset.zero);
addTearDown(gesture.removePointer); addTearDown(gesture.removePointer);
await tester.pumpWidget( await tester.pumpWidget(
...@@ -507,7 +511,7 @@ void main() { ...@@ -507,7 +511,7 @@ void main() {
testWidgets("Callbacks aren't called during build", (WidgetTester tester) async { testWidgets("Callbacks aren't called during build", (WidgetTester tester) async {
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer(); await gesture.addPointer(location: Offset.zero);
addTearDown(gesture.removePointer); addTearDown(gesture.removePointer);
await tester.pumpWidget( await tester.pumpWidget(
...@@ -538,7 +542,7 @@ void main() { ...@@ -538,7 +542,7 @@ void main() {
testWidgets("Listener activate/deactivate don't duplicate annotations", (WidgetTester tester) async { testWidgets("Listener activate/deactivate don't duplicate annotations", (WidgetTester tester) async {
final GlobalKey feedbackKey = GlobalKey(); final GlobalKey feedbackKey = GlobalKey();
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer(); await gesture.addPointer(location: Offset.zero);
addTearDown(gesture.removePointer); addTearDown(gesture.removePointer);
await tester.pumpWidget( await tester.pumpWidget(
...@@ -586,11 +590,8 @@ void main() { ...@@ -586,11 +590,8 @@ void main() {
// Plug-in a mouse and move it to the center of the container. // Plug-in a mouse and move it to the center of the container.
TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer(); await gesture.addPointer(location: Offset.zero);
addTearDown(() async { addTearDown(() => gesture?.removePointer());
if (gesture != null)
return gesture.removePointer();
});
await gesture.moveTo(tester.getCenter(find.byType(Container))); await gesture.moveTo(tester.getCenter(find.byType(Container)));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
...@@ -613,7 +614,7 @@ void main() { ...@@ -613,7 +614,7 @@ void main() {
expect(hover.length, 0); expect(hover.length, 0);
expect(exit.length, 1); expect(exit.length, 1);
expect(exit.single.position, const Offset(400.0, 300.0)); expect(exit.single.position, const Offset(400.0, 300.0));
expect(exit.single.delta, const Offset(0.0, 0.0)); expect(exit.single.delta, Offset.zero);
}); });
}); });
} }
...@@ -805,6 +805,10 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -805,6 +805,10 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
'The MouseTracker thinks that there is still a mouse connected, which indicates that a ' 'The MouseTracker thinks that there is still a mouse connected, which indicates that a '
'test has not removed the mouse pointer which it added. Call removePointer on the ' 'test has not removed the mouse pointer which it added. Call removePointer on the '
'active mouse gesture to remove the mouse pointer.'); 'active mouse gesture to remove the mouse pointer.');
// ignore: invalid_use_of_visible_for_testing_member
RendererBinding.instance.initMouseTracker();
// ignore: invalid_use_of_visible_for_testing_member
PointerEventConverter.clearPointers();
} }
} }
......
...@@ -373,16 +373,16 @@ class TestGesture { ...@@ -373,16 +373,16 @@ class TestGesture {
} }
/// In a test, send a pointer add event for this pointer. /// In a test, send a pointer add event for this pointer.
Future<void> addPointer({ Duration timeStamp = Duration.zero }) { Future<void> addPointer({ Duration timeStamp = Duration.zero, Offset location }) {
return TestAsyncUtils.guard<void>(() { return TestAsyncUtils.guard<void>(() {
return _dispatcher(_pointer.addPointer(timeStamp: timeStamp, location: _pointer.location), null); return _dispatcher(_pointer.addPointer(timeStamp: timeStamp, location: location ?? _pointer.location), null);
}); });
} }
/// In a test, send a pointer remove event for this pointer. /// In a test, send a pointer remove event for this pointer.
Future<void> removePointer({ Duration timeStamp = Duration.zero}) { Future<void> removePointer({ Duration timeStamp = Duration.zero, Offset location }) {
return TestAsyncUtils.guard<void>(() { return TestAsyncUtils.guard<void>(() {
return _dispatcher(_pointer.removePointer(timeStamp: timeStamp, location: _pointer.location), null); return _dispatcher(_pointer.removePointer(timeStamp: timeStamp, location: location ?? _pointer.location), null);
}); });
} }
......
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