Unverified Commit 543d3cfa authored by xubaolin's avatar xubaolin Committed by GitHub

Add a [valid] property of [MouseTrackerAnnotation] indicates the annotation states. (#69866)

parent e803b13f
...@@ -55,10 +55,11 @@ class MouseTrackerAnnotation with Diagnosticable { ...@@ -55,10 +55,11 @@ class MouseTrackerAnnotation with Diagnosticable {
this.onEnter, this.onEnter,
this.onExit, this.onExit,
this.cursor = MouseCursor.defer, this.cursor = MouseCursor.defer,
this.validForMouseTracker = true,
}) : assert(cursor != null); }) : assert(cursor != null);
/// Triggered when a mouse pointer, with or without buttons pressed, has /// Triggered when a mouse pointer, with or without buttons pressed, has
/// entered the region. /// entered the region and [validForMouseTracker] is true.
/// ///
/// This callback is triggered when the pointer has started to be contained by /// This callback is triggered when the pointer has started to be contained by
/// the region, either due to a pointer event, or due to the movement or /// the region, either due to a pointer event, or due to the movement or
...@@ -72,7 +73,7 @@ class MouseTrackerAnnotation with Diagnosticable { ...@@ -72,7 +73,7 @@ class MouseTrackerAnnotation with Diagnosticable {
final PointerEnterEventListener? onEnter; final PointerEnterEventListener? onEnter;
/// Triggered when a mouse pointer, with or without buttons pressed, has /// Triggered when a mouse pointer, with or without buttons pressed, has
/// exited the region. /// exited the region and [validForMouseTracker] is true.
/// ///
/// This callback is triggered when the pointer has stopped being contained /// This callback is triggered when the pointer has stopped being contained
/// by the region, either due to a pointer event, or due to the movement or /// by the region, either due to a pointer event, or due to the movement or
...@@ -100,6 +101,15 @@ class MouseTrackerAnnotation with Diagnosticable { ...@@ -100,6 +101,15 @@ class MouseTrackerAnnotation with Diagnosticable {
/// * [MouseRegion.cursor], which provide values to this field. /// * [MouseRegion.cursor], which provide values to this field.
final MouseCursor cursor; final MouseCursor cursor;
/// Whether this is included when [MouseTracker] collects the list of annotations.
///
/// If [validForMouseTracker] is false, this object is excluded from the current annotation list
/// even if it's included in the hit test, affecting mouse-related behavior such as enter events,
/// exit events, and mouse cursors. The [validForMouseTracker] does not affect hit testing.
///
/// The [validForMouseTracker] is true for [MouseTrackerAnnotation]s built by the constructor.
final bool validForMouseTracker;
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
...@@ -487,7 +497,7 @@ mixin _MouseTrackerEventMixin on BaseMouseTracker { ...@@ -487,7 +497,7 @@ mixin _MouseTrackerEventMixin on BaseMouseTracker {
final PointerExitEvent baseExitEvent = PointerExitEvent.fromMouseEvent(latestEvent); final PointerExitEvent baseExitEvent = PointerExitEvent.fromMouseEvent(latestEvent);
lastAnnotations.forEach((MouseTrackerAnnotation annotation, Matrix4 transform) { lastAnnotations.forEach((MouseTrackerAnnotation annotation, Matrix4 transform) {
if (!nextAnnotations.containsKey(annotation)) if (!nextAnnotations.containsKey(annotation))
if (annotation.onExit != null) if (annotation.validForMouseTracker && annotation.onExit != null)
annotation.onExit!(baseExitEvent.transformed(lastAnnotations[annotation])); annotation.onExit!(baseExitEvent.transformed(lastAnnotations[annotation]));
}); });
...@@ -498,7 +508,7 @@ mixin _MouseTrackerEventMixin on BaseMouseTracker { ...@@ -498,7 +508,7 @@ mixin _MouseTrackerEventMixin on BaseMouseTracker {
).toList(); ).toList();
final PointerEnterEvent baseEnterEvent = PointerEnterEvent.fromMouseEvent(latestEvent); final PointerEnterEvent baseEnterEvent = PointerEnterEvent.fromMouseEvent(latestEvent);
for (final MouseTrackerAnnotation annotation in enteringAnnotations.reversed) { for (final MouseTrackerAnnotation annotation in enteringAnnotations.reversed) {
if (annotation.onEnter != null) if (annotation.validForMouseTracker && annotation.onEnter != null)
annotation.onEnter!(baseEnterEvent.transformed(nextAnnotations[annotation])); annotation.onEnter!(baseEnterEvent.transformed(nextAnnotations[annotation]));
} }
} }
......
...@@ -732,6 +732,9 @@ mixin _PlatformViewGestureMixin on RenderBox implements MouseTrackerAnnotation { ...@@ -732,6 +732,9 @@ mixin _PlatformViewGestureMixin on RenderBox implements MouseTrackerAnnotation {
@override @override
MouseCursor get cursor => MouseCursor.uncontrolled; MouseCursor get cursor => MouseCursor.uncontrolled;
@override
bool get validForMouseTracker => true;
@override @override
void handleEvent(PointerEvent event, HitTestEntry entry) { void handleEvent(PointerEvent event, HitTestEntry entry) {
if (event is PointerDownEvent) { if (event is PointerDownEvent) {
......
...@@ -2777,11 +2777,13 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation ...@@ -2777,11 +2777,13 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
this.onHover, this.onHover,
this.onExit, this.onExit,
MouseCursor cursor = MouseCursor.defer, MouseCursor cursor = MouseCursor.defer,
bool validForMouseTracker = true,
bool opaque = true, bool opaque = true,
RenderBox? child, RenderBox? child,
}) : assert(opaque != null), }) : assert(opaque != null),
assert(cursor != null), assert(cursor != null),
_cursor = cursor, _cursor = cursor,
_validForMouseTracker = validForMouseTracker,
_opaque = opaque, _opaque = opaque,
super(child); super(child);
...@@ -2849,6 +2851,25 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation ...@@ -2849,6 +2851,25 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
} }
} }
@override
bool get validForMouseTracker => _validForMouseTracker;
bool _validForMouseTracker;
@override
void attach(PipelineOwner owner) {
super.attach(owner);
_validForMouseTracker = true;
}
@override
void detach() {
// It's possible that the renderObject be detached during mouse events
// dispatching, set the [MouseTrackerAnnotation.validForMouseTracker] false to prevent
// the callbacks from being called.
_validForMouseTracker = false;
super.detach();
}
@override @override
void performResize() { void performResize() {
size = constraints.biggest; size = constraints.biggest;
...@@ -2868,6 +2889,7 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation ...@@ -2868,6 +2889,7 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
)); ));
properties.add(DiagnosticsProperty<MouseCursor>('cursor', cursor, defaultValue: MouseCursor.defer)); properties.add(DiagnosticsProperty<MouseCursor>('cursor', cursor, defaultValue: MouseCursor.defer));
properties.add(DiagnosticsProperty<bool>('opaque', opaque, defaultValue: true)); properties.add(DiagnosticsProperty<bool>('opaque', opaque, defaultValue: true));
properties.add(FlagProperty('validForMouseTracker', value: validForMouseTracker, defaultValue: true, ifFalse: 'invalid for MouseTracker'));
} }
} }
......
...@@ -72,7 +72,7 @@ class TestMouseTrackerFlutterBinding extends BindingBase ...@@ -72,7 +72,7 @@ class TestMouseTrackerFlutterBinding extends BindingBase
// An object that mocks the behavior of a render object with [MouseTrackerAnnotation]. // An object that mocks the behavior of a render object with [MouseTrackerAnnotation].
class TestAnnotationTarget with Diagnosticable implements MouseTrackerAnnotation, HitTestTarget { class TestAnnotationTarget with Diagnosticable implements MouseTrackerAnnotation, HitTestTarget {
const TestAnnotationTarget({this.onEnter, this.onHover, this.onExit, this.cursor = MouseCursor.defer}); const TestAnnotationTarget({this.onEnter, this.onHover, this.onExit, this.cursor = MouseCursor.defer, this.validForMouseTracker = true});
@override @override
final PointerEnterEventListener? onEnter; final PointerEnterEventListener? onEnter;
...@@ -85,6 +85,9 @@ class TestAnnotationTarget with Diagnosticable implements MouseTrackerAnnotation ...@@ -85,6 +85,9 @@ class TestAnnotationTarget with Diagnosticable implements MouseTrackerAnnotation
@override @override
final MouseCursor cursor; final MouseCursor cursor;
@override
final bool validForMouseTracker;
@override @override
void handleEvent(PointerEvent event, HitTestEntry entry) { void handleEvent(PointerEvent event, HitTestEntry entry) {
if (event is PointerHoverEvent) if (event is PointerHoverEvent)
......
...@@ -1695,6 +1695,7 @@ void main() { ...@@ -1695,6 +1695,7 @@ void main() {
onExit: (PointerExitEvent event) {}, onExit: (PointerExitEvent event) {},
onHover: (PointerHoverEvent event) {}, onHover: (PointerHoverEvent event) {},
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
validForMouseTracker: false,
child: RenderErrorBox(), child: RenderErrorBox(),
).debugFillProperties(builder); ).debugFillProperties(builder);
...@@ -1706,6 +1707,7 @@ void main() { ...@@ -1706,6 +1707,7 @@ void main() {
'size: MISSING', 'size: MISSING',
'listeners: enter, hover, exit', 'listeners: enter, hover, exit',
'cursor: SystemMouseCursor(click)', 'cursor: SystemMouseCursor(click)',
'invalid for MouseTracker',
]); ]);
}); });
...@@ -1728,6 +1730,37 @@ void main() { ...@@ -1728,6 +1730,37 @@ void main() {
await gesture.moveBy(const Offset(10.0, 10.0)); await gesture.moveBy(const Offset(10.0, 10.0));
expect(tester.binding.hasScheduledFrame, isFalse); expect(tester.binding.hasScheduledFrame, isFalse);
}); });
// Regression test for https://github.com/flutter/flutter/issues/67044
testWidgets('Handle mouse events should ignore the detached MouseTrackerAnnotation', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
home: Center(
child: Draggable<int>(
feedback: Container(width: 20, height: 20, color: Colors.blue),
childWhenDragging: Container(width: 20, height: 20, color: Colors.yellow),
child: RaisedButton(child: const Text('Drag me'), onPressed: (){}),
),
),
));
// Long press the button with mouse.
final Offset textFieldPos = tester.getCenter(find.byType(Text));
final TestGesture gesture = await tester.startGesture(
textFieldPos,
kind: PointerDeviceKind.mouse,
);
addTearDown(gesture.removePointer);
await tester.pump(const Duration(seconds: 2));
await tester.pumpAndSettle();
// Drag the Draggable Widget will replace the child with [childWhenDragging].
await gesture.moveBy(const Offset(10.0, 10.0));
await tester.pump(); // Trigger detach the button.
// Continue drag mouse should not trigger any assert.
await gesture.moveBy(const Offset(10.0, 10.0));
expect(tester.takeException(), isNull);
});
} }
// Render widget `topLeft` at the top-left corner, stacking on top of the widget // Render widget `topLeft` at the top-left corner, stacking on top of the widget
......
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