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 {
this.onEnter,
this.onExit,
this.cursor = MouseCursor.defer,
this.validForMouseTracker = true,
}) : assert(cursor != null);
/// 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
/// the region, either due to a pointer event, or due to the movement or
......@@ -72,7 +73,7 @@ class MouseTrackerAnnotation with Diagnosticable {
final PointerEnterEventListener? onEnter;
/// 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
/// by the region, either due to a pointer event, or due to the movement or
......@@ -100,6 +101,15 @@ class MouseTrackerAnnotation with Diagnosticable {
/// * [MouseRegion.cursor], which provide values to this field.
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
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
......@@ -487,7 +497,7 @@ mixin _MouseTrackerEventMixin on BaseMouseTracker {
final PointerExitEvent baseExitEvent = PointerExitEvent.fromMouseEvent(latestEvent);
lastAnnotations.forEach((MouseTrackerAnnotation annotation, Matrix4 transform) {
if (!nextAnnotations.containsKey(annotation))
if (annotation.onExit != null)
if (annotation.validForMouseTracker && annotation.onExit != null)
annotation.onExit!(baseExitEvent.transformed(lastAnnotations[annotation]));
});
......@@ -498,7 +508,7 @@ mixin _MouseTrackerEventMixin on BaseMouseTracker {
).toList();
final PointerEnterEvent baseEnterEvent = PointerEnterEvent.fromMouseEvent(latestEvent);
for (final MouseTrackerAnnotation annotation in enteringAnnotations.reversed) {
if (annotation.onEnter != null)
if (annotation.validForMouseTracker && annotation.onEnter != null)
annotation.onEnter!(baseEnterEvent.transformed(nextAnnotations[annotation]));
}
}
......
......@@ -732,6 +732,9 @@ mixin _PlatformViewGestureMixin on RenderBox implements MouseTrackerAnnotation {
@override
MouseCursor get cursor => MouseCursor.uncontrolled;
@override
bool get validForMouseTracker => true;
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
if (event is PointerDownEvent) {
......
......@@ -2777,11 +2777,13 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
this.onHover,
this.onExit,
MouseCursor cursor = MouseCursor.defer,
bool validForMouseTracker = true,
bool opaque = true,
RenderBox? child,
}) : assert(opaque != null),
assert(cursor != null),
_cursor = cursor,
_validForMouseTracker = validForMouseTracker,
_opaque = opaque,
super(child);
......@@ -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
void performResize() {
size = constraints.biggest;
......@@ -2868,6 +2889,7 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
));
properties.add(DiagnosticsProperty<MouseCursor>('cursor', cursor, defaultValue: MouseCursor.defer));
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
// An object that mocks the behavior of a render object with [MouseTrackerAnnotation].
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
final PointerEnterEventListener? onEnter;
......@@ -85,6 +85,9 @@ class TestAnnotationTarget with Diagnosticable implements MouseTrackerAnnotation
@override
final MouseCursor cursor;
@override
final bool validForMouseTracker;
@override
void handleEvent(PointerEvent event, HitTestEntry entry) {
if (event is PointerHoverEvent)
......
......@@ -1695,6 +1695,7 @@ void main() {
onExit: (PointerExitEvent event) {},
onHover: (PointerHoverEvent event) {},
cursor: SystemMouseCursors.click,
validForMouseTracker: false,
child: RenderErrorBox(),
).debugFillProperties(builder);
......@@ -1706,6 +1707,7 @@ void main() {
'size: MISSING',
'listeners: enter, hover, exit',
'cursor: SystemMouseCursor(click)',
'invalid for MouseTracker',
]);
});
......@@ -1728,6 +1730,37 @@ void main() {
await gesture.moveBy(const Offset(10.0, 10.0));
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
......
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