mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Trigger MouseRegion.toHover only on hover events (#47403)
This commit is contained in:
parent
3ae0345e72
commit
0450e7c0ff
@ -289,7 +289,6 @@ class MouseTracker extends ChangeNotifier {
|
||||
return;
|
||||
|
||||
final PointerEvent previousEvent = existingState?.latestEvent;
|
||||
final Offset lastHoverPosition = previousEvent is! PointerHoverEvent ? null : previousEvent.position;
|
||||
_updateDevices(
|
||||
targetEvent: event,
|
||||
handleUpdatedDevice: (_MouseState mouseState, LinkedHashSet<MouseTrackerAnnotation> previousAnnotations) {
|
||||
@ -297,7 +296,7 @@ class MouseTracker extends ChangeNotifier {
|
||||
_dispatchDeviceCallbacks(
|
||||
lastAnnotations: previousAnnotations,
|
||||
nextAnnotations: mouseState.annotations,
|
||||
lastHoverPosition: lastHoverPosition,
|
||||
previousEvent: previousEvent,
|
||||
unhandledEvent: event,
|
||||
trackedAnnotations: _trackedAnnotations,
|
||||
);
|
||||
@ -328,13 +327,11 @@ class MouseTracker extends ChangeNotifier {
|
||||
void _updateAllDevices() {
|
||||
_updateDevices(
|
||||
handleUpdatedDevice: (_MouseState mouseState, LinkedHashSet<MouseTrackerAnnotation> previousAnnotations) {
|
||||
final PointerEvent latestEvent = mouseState.latestEvent;
|
||||
final Offset lastHoverPosition = latestEvent is PointerHoverEvent ? latestEvent.position : null;
|
||||
_dispatchDeviceCallbacks(
|
||||
lastAnnotations: previousAnnotations,
|
||||
nextAnnotations: mouseState.annotations,
|
||||
lastHoverPosition: lastHoverPosition,
|
||||
unhandledEvent: mouseState.latestEvent,
|
||||
previousEvent: mouseState.latestEvent,
|
||||
unhandledEvent: null,
|
||||
trackedAnnotations: _trackedAnnotations,
|
||||
);
|
||||
}
|
||||
@ -427,20 +424,22 @@ class MouseTracker extends ChangeNotifier {
|
||||
// Dispatch callbacks related to a device after all necessary information
|
||||
// has been collected.
|
||||
//
|
||||
// The `lastHoverPosition` can be null, which means the last event is not a
|
||||
// hover. Other arguments must not be null.
|
||||
// The `previousEvent` is the latest event before `unhandledEvent`. It might be
|
||||
// null, which means the update is triggered by a new event.
|
||||
// The `unhandledEvent` can be null, which means the update is not triggered
|
||||
// by an event.
|
||||
static void _dispatchDeviceCallbacks({
|
||||
@required LinkedHashSet<MouseTrackerAnnotation> lastAnnotations,
|
||||
@required LinkedHashSet<MouseTrackerAnnotation> nextAnnotations,
|
||||
@required Offset lastHoverPosition,
|
||||
@required PointerEvent previousEvent,
|
||||
@required PointerEvent unhandledEvent,
|
||||
@required Set<MouseTrackerAnnotation> trackedAnnotations,
|
||||
}) {
|
||||
assert(lastAnnotations != null);
|
||||
assert(nextAnnotations != null);
|
||||
// lastHoverPosition can be null
|
||||
assert(unhandledEvent != null);
|
||||
assert(trackedAnnotations != null);
|
||||
final PointerEvent latestEvent = unhandledEvent ?? previousEvent;
|
||||
assert(latestEvent != null);
|
||||
// Order is important for mouse event callbacks. The `findAnnotations`
|
||||
// returns annotations in the visual order from front to back. We call
|
||||
// it the "visual order", and the opposite one "reverse visual order".
|
||||
@ -456,7 +455,7 @@ class MouseTracker extends ChangeNotifier {
|
||||
// trigger may cause exceptions and has safer alternatives. See
|
||||
// [MouseRegion.onExit] for details.
|
||||
if (annotation.onExit != null && attached) {
|
||||
annotation.onExit(PointerExitEvent.fromMouseEvent(unhandledEvent));
|
||||
annotation.onExit(PointerExitEvent.fromMouseEvent(latestEvent));
|
||||
}
|
||||
}
|
||||
|
||||
@ -466,7 +465,7 @@ class MouseTracker extends ChangeNotifier {
|
||||
for (final MouseTrackerAnnotation annotation in enteringAnnotations) {
|
||||
assert(trackedAnnotations.contains(annotation));
|
||||
if (annotation.onEnter != null) {
|
||||
annotation.onEnter(PointerEnterEvent.fromMouseEvent(unhandledEvent));
|
||||
annotation.onEnter(PointerEnterEvent.fromMouseEvent(latestEvent));
|
||||
}
|
||||
}
|
||||
|
||||
@ -476,6 +475,7 @@ class MouseTracker extends ChangeNotifier {
|
||||
if (unhandledEvent is PointerHoverEvent) {
|
||||
final Iterable<MouseTrackerAnnotation> hoveringAnnotations =
|
||||
nextAnnotations.toList().reversed;
|
||||
final Offset lastHoverPosition = previousEvent is PointerHoverEvent ? previousEvent.position : null;
|
||||
for (final MouseTrackerAnnotation annotation in hoveringAnnotations) {
|
||||
// Deduplicate: Trigger hover if it's a newly hovered annotation
|
||||
// or the position has changed
|
||||
|
@ -107,8 +107,8 @@ void main() {
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer(location: Offset.zero);
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.moveTo(const Offset(400.0, 300.0));
|
||||
await tester.pump();
|
||||
await gesture.moveTo(const Offset(400.0, 300.0));
|
||||
expect(move, isNotNull);
|
||||
expect(move.position, equals(const Offset(400.0, 300.0)));
|
||||
expect(enter, isNotNull);
|
||||
@ -593,8 +593,8 @@ void main() {
|
||||
TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer(location: Offset.zero);
|
||||
addTearDown(() => gesture?.removePointer());
|
||||
await gesture.moveTo(tester.getCenter(find.byType(Container)));
|
||||
await tester.pumpAndSettle();
|
||||
await gesture.moveTo(tester.getCenter(find.byType(Container)));
|
||||
|
||||
expect(enter.length, 1);
|
||||
expect(enter.single.position, const Offset(400.0, 300.0));
|
||||
|
@ -101,8 +101,11 @@ void main() {
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer(location: Offset.zero);
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.moveTo(const Offset(400.0, 300.0));
|
||||
await tester.pump();
|
||||
move = null;
|
||||
enter = null;
|
||||
exit = null;
|
||||
await gesture.moveTo(const Offset(400.0, 300.0));
|
||||
expect(move, isNotNull);
|
||||
expect(move.position, equals(const Offset(400.0, 300.0)));
|
||||
expect(enter, isNotNull);
|
||||
@ -132,15 +135,118 @@ void main() {
|
||||
await tester.pump();
|
||||
move = null;
|
||||
enter = null;
|
||||
exit = null;
|
||||
await gesture.moveTo(const Offset(1.0, 1.0));
|
||||
await tester.pump();
|
||||
expect(move, isNull);
|
||||
expect(enter, isNull);
|
||||
expect(exit, isNotNull);
|
||||
expect(exit.position, equals(const Offset(1.0, 1.0)));
|
||||
});
|
||||
|
||||
testWidgets('detects pointer exit when widget disappears', (WidgetTester tester) async {
|
||||
testWidgets('triggers pointer enter when a mouse is connected', (WidgetTester tester) async {
|
||||
PointerEnterEvent enter;
|
||||
PointerHoverEvent move;
|
||||
PointerExitEvent exit;
|
||||
await tester.pumpWidget(Center(
|
||||
child: MouseRegion(
|
||||
child: Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
),
|
||||
onEnter: (PointerEnterEvent details) => enter = details,
|
||||
onHover: (PointerHoverEvent details) => move = details,
|
||||
onExit: (PointerExitEvent details) => exit = details,
|
||||
),
|
||||
));
|
||||
await tester.pump();
|
||||
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer(location: const Offset(400, 300));
|
||||
addTearDown(gesture.removePointer);
|
||||
expect(move, isNull);
|
||||
expect(enter, isNull);
|
||||
expect(exit, isNull);
|
||||
await tester.pump();
|
||||
expect(move, isNull);
|
||||
expect(enter, isNotNull);
|
||||
expect(enter.position, equals(const Offset(400.0, 300.0)));
|
||||
expect(exit, isNull);
|
||||
});
|
||||
|
||||
testWidgets('triggers pointer exit when a mouse is disconnected', (WidgetTester tester) async {
|
||||
PointerEnterEvent enter;
|
||||
PointerHoverEvent move;
|
||||
PointerExitEvent exit;
|
||||
await tester.pumpWidget(Center(
|
||||
child: MouseRegion(
|
||||
child: Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
),
|
||||
onEnter: (PointerEnterEvent details) => enter = details,
|
||||
onHover: (PointerHoverEvent details) => move = details,
|
||||
onExit: (PointerExitEvent details) => exit = details,
|
||||
),
|
||||
));
|
||||
await tester.pump();
|
||||
|
||||
TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer(location: const Offset(400, 300));
|
||||
addTearDown(() => gesture?.removePointer);
|
||||
await tester.pump();
|
||||
move = null;
|
||||
enter = null;
|
||||
exit = null;
|
||||
await gesture.removePointer();
|
||||
gesture = null;
|
||||
expect(move, isNull);
|
||||
expect(enter, isNull);
|
||||
expect(exit, isNotNull);
|
||||
expect(exit.position, equals(const Offset(400.0, 300.0)));
|
||||
exit = null;
|
||||
await tester.pump();
|
||||
expect(move, isNull);
|
||||
expect(enter, isNull);
|
||||
expect(exit, isNull);
|
||||
});
|
||||
|
||||
testWidgets('triggers pointer enter when widget appears', (WidgetTester tester) async {
|
||||
PointerEnterEvent enter;
|
||||
PointerHoverEvent move;
|
||||
PointerExitEvent exit;
|
||||
await tester.pumpWidget(Center(
|
||||
child: Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
),
|
||||
));
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer(location: Offset.zero);
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.moveTo(const Offset(400.0, 300.0));
|
||||
await tester.pump();
|
||||
expect(enter, isNull);
|
||||
expect(move, isNull);
|
||||
expect(exit, isNull);
|
||||
await tester.pumpWidget(Center(
|
||||
child: MouseRegion(
|
||||
child: Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
),
|
||||
onEnter: (PointerEnterEvent details) => enter = details,
|
||||
onHover: (PointerHoverEvent details) => move = details,
|
||||
onExit: (PointerExitEvent details) => exit = details,
|
||||
),
|
||||
));
|
||||
await tester.pump();
|
||||
expect(move, isNull);
|
||||
expect(enter, isNotNull);
|
||||
expect(enter.position, equals(const Offset(400.0, 300.0)));
|
||||
expect(exit, isNull);
|
||||
});
|
||||
|
||||
testWidgets("doesn't trigger pointer exit when widget disappears", (WidgetTester tester) async {
|
||||
PointerEnterEvent enter;
|
||||
PointerHoverEvent move;
|
||||
PointerExitEvent exit;
|
||||
@ -161,21 +267,105 @@ void main() {
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.moveTo(const Offset(400.0, 300.0));
|
||||
await tester.pump();
|
||||
expect(move, isNotNull);
|
||||
expect(move.position, equals(const Offset(400.0, 300.0)));
|
||||
expect(enter, isNotNull);
|
||||
expect(enter.position, equals(const Offset(400.0, 300.0)));
|
||||
expect(exit, isNull);
|
||||
move = null;
|
||||
enter = null;
|
||||
exit = null;
|
||||
await tester.pumpWidget(Center(
|
||||
child: Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
),
|
||||
));
|
||||
expect(enter, isNull);
|
||||
expect(move, isNull);
|
||||
expect(exit, isNull);
|
||||
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener.hoverAnnotation), isFalse);
|
||||
});
|
||||
|
||||
testWidgets('triggers pointer enter when widget moves in', (WidgetTester tester) async {
|
||||
PointerEnterEvent enter;
|
||||
PointerHoverEvent move;
|
||||
PointerExitEvent exit;
|
||||
await tester.pumpWidget(Container(
|
||||
alignment: Alignment.center,
|
||||
child: MouseRegion(
|
||||
child: Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
),
|
||||
onEnter: (PointerEnterEvent details) => enter = details,
|
||||
onHover: (PointerHoverEvent details) => move = details,
|
||||
onExit: (PointerExitEvent details) => exit = details,
|
||||
),
|
||||
));
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer(location: const Offset(1.0, 1.0));
|
||||
addTearDown(gesture.removePointer);
|
||||
await tester.pump();
|
||||
expect(enter, isNull);
|
||||
expect(move, isNull);
|
||||
expect(exit, isNull);
|
||||
await tester.pumpWidget(Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: MouseRegion(
|
||||
child: Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
),
|
||||
onEnter: (PointerEnterEvent details) => enter = details,
|
||||
onHover: (PointerHoverEvent details) => move = details,
|
||||
onExit: (PointerExitEvent details) => exit = details,
|
||||
),
|
||||
));
|
||||
await tester.pump();
|
||||
expect(enter, isNotNull);
|
||||
expect(enter.position, equals(const Offset(1.0, 1.0)));
|
||||
expect(move, isNull);
|
||||
expect(exit, isNull);
|
||||
});
|
||||
|
||||
testWidgets('triggers pointer exit when widget moves out', (WidgetTester tester) async {
|
||||
PointerEnterEvent enter;
|
||||
PointerHoverEvent move;
|
||||
PointerExitEvent exit;
|
||||
await tester.pumpWidget(Container(
|
||||
alignment: Alignment.center,
|
||||
child: MouseRegion(
|
||||
child: Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
),
|
||||
onEnter: (PointerEnterEvent details) => enter = details,
|
||||
onHover: (PointerHoverEvent details) => move = details,
|
||||
onExit: (PointerExitEvent details) => exit = details,
|
||||
),
|
||||
));
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer(location: const Offset(400, 300));
|
||||
addTearDown(gesture.removePointer);
|
||||
await tester.pump();
|
||||
enter = null;
|
||||
move = null;
|
||||
exit = null;
|
||||
await tester.pumpWidget(Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: MouseRegion(
|
||||
child: Container(
|
||||
width: 100.0,
|
||||
height: 100.0,
|
||||
),
|
||||
onEnter: (PointerEnterEvent details) => enter = details,
|
||||
onHover: (PointerHoverEvent details) => move = details,
|
||||
onExit: (PointerExitEvent details) => exit = details,
|
||||
),
|
||||
));
|
||||
await tester.pump();
|
||||
expect(enter, isNull);
|
||||
expect(move, isNull);
|
||||
expect(exit, isNotNull);
|
||||
expect(exit.position, equals(const Offset(400, 300)));
|
||||
});
|
||||
|
||||
testWidgets('Hover works with nested listeners', (WidgetTester tester) async {
|
||||
final UniqueKey key1 = UniqueKey();
|
||||
final UniqueKey key2 = UniqueKey();
|
||||
@ -680,8 +870,8 @@ void main() {
|
||||
TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer(location: Offset.zero);
|
||||
addTearDown(() => gesture?.removePointer());
|
||||
await gesture.moveTo(tester.getCenter(find.byType(Container)));
|
||||
await tester.pumpAndSettle();
|
||||
await gesture.moveTo(tester.getCenter(find.byType(Container)));
|
||||
|
||||
expect(enter.length, 1);
|
||||
expect(enter.single.position, const Offset(400.0, 300.0));
|
||||
|
Loading…
Reference in New Issue
Block a user