Unverified Commit da0a7d8b authored by Tong Mu's avatar Tong Mu Committed by GitHub

MouseTracker no longer requires annotations attached (#48453)

parent dd98046f
...@@ -10,7 +10,6 @@ import 'package:flutter/gestures.dart'; ...@@ -10,7 +10,6 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/semantics.dart'; import 'package:flutter/semantics.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'binding.dart';
import 'box.dart'; import 'box.dart';
import 'layer.dart'; import 'layer.dart';
import 'object.dart'; import 'object.dart';
...@@ -838,13 +837,11 @@ mixin _PlatformViewGestureMixin on RenderBox { ...@@ -838,13 +837,11 @@ mixin _PlatformViewGestureMixin on RenderBox {
if (_handlePointerEvent != null) if (_handlePointerEvent != null)
_handlePointerEvent(event); _handlePointerEvent(event);
}); });
RendererBinding.instance.mouseTracker.attachAnnotation(_hoverAnnotation);
} }
@override @override
void detach() { void detach() {
_gestureRecognizer.reset(); _gestureRecognizer.reset();
RendererBinding.instance.mouseTracker.detachAnnotation(_hoverAnnotation);
_hoverAnnotation = null; _hoverAnnotation = null;
super.detach(); super.detach();
} }
......
...@@ -2659,6 +2659,9 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior { ...@@ -2659,6 +2659,9 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
/// [RenderMouseRegion]. /// [RenderMouseRegion].
class RenderMouseRegion extends RenderProxyBox { class RenderMouseRegion extends RenderProxyBox {
/// Creates a render object that forwards pointer events to callbacks. /// Creates a render object that forwards pointer events to callbacks.
///
/// All parameters are optional. By default this method creates an opaque
/// mouse region with no callbacks.
RenderMouseRegion({ RenderMouseRegion({
PointerEnterEventListener onEnter, PointerEnterEventListener onEnter,
PointerHoverEventListener onHover, PointerHoverEventListener onHover,
...@@ -2698,17 +2701,23 @@ class RenderMouseRegion extends RenderProxyBox { ...@@ -2698,17 +2701,23 @@ class RenderMouseRegion extends RenderProxyBox {
set opaque(bool value) { set opaque(bool value) {
if (_opaque != value) { if (_opaque != value) {
_opaque = value; _opaque = value;
_updateAnnotations(); _markPropertyUpdated(mustRepaint: true);
} }
} }
/// Called when a mouse pointer enters the region (with or without buttons /// Called when a mouse pointer starts being contained by the region (with or
/// pressed). /// without buttons pressed) for any reason.
///
/// This callback is always matched by a later [onExit].
///
/// See also:
///
/// * [MouseRegion.onEnter], which uses this callback.
PointerEnterEventListener get onEnter => _onEnter; PointerEnterEventListener get onEnter => _onEnter;
set onEnter(PointerEnterEventListener value) { set onEnter(PointerEnterEventListener value) {
if (_onEnter != value) { if (_onEnter != value) {
_onEnter = value; _onEnter = value;
_updateAnnotations(); _markPropertyUpdated(mustRepaint: false);
} }
} }
PointerEnterEventListener _onEnter; PointerEnterEventListener _onEnter;
...@@ -2723,7 +2732,7 @@ class RenderMouseRegion extends RenderProxyBox { ...@@ -2723,7 +2732,7 @@ class RenderMouseRegion extends RenderProxyBox {
set onHover(PointerHoverEventListener value) { set onHover(PointerHoverEventListener value) {
if (_onHover != value) { if (_onHover != value) {
_onHover = value; _onHover = value;
_updateAnnotations(); _markPropertyUpdated(mustRepaint: false);
} }
} }
PointerHoverEventListener _onHover; PointerHoverEventListener _onHover;
...@@ -2732,13 +2741,20 @@ class RenderMouseRegion extends RenderProxyBox { ...@@ -2732,13 +2741,20 @@ class RenderMouseRegion extends RenderProxyBox {
_onHover(event); _onHover(event);
} }
/// Called when a pointer leaves the region (with or without buttons pressed) /// Called when a pointer is no longer contained by the region (with or
/// and the annotation is still attached. /// without buttons pressed) for any reason.
///
/// This callback is always matched by an earlier [onEnter].
///
/// See also:
///
/// * [MouseRegion.onExit], which uses this callback, but is not triggered in
/// certain cases and does not always match its earier [MouseRegion.onEnter].
PointerExitEventListener get onExit => _onExit; PointerExitEventListener get onExit => _onExit;
set onExit(PointerExitEventListener value) { set onExit(PointerExitEventListener value) {
if (_onExit != value) { if (_onExit != value) {
_onExit = value; _onExit = value;
_updateAnnotations(); _markPropertyUpdated(mustRepaint: false);
} }
} }
PointerExitEventListener _onExit; PointerExitEventListener _onExit;
...@@ -2757,64 +2773,52 @@ class RenderMouseRegion extends RenderProxyBox { ...@@ -2757,64 +2773,52 @@ class RenderMouseRegion extends RenderProxyBox {
@visibleForTesting @visibleForTesting
MouseTrackerAnnotation get hoverAnnotation => _hoverAnnotation; MouseTrackerAnnotation get hoverAnnotation => _hoverAnnotation;
void _updateAnnotations() { // Call this method when a property has changed and might affect the
final bool annotationWasActive = _annotationIsActive; // `_annotationIsActive` bit.
final bool annotationWillBeActive = ( //
// If `mustRepaint` is false, this method does NOT call `markNeedsPaint`
// unless the `_annotationIsActive` bit is changed. If there is a property
// that needs updating while `_annotationIsActive` stays true, make
// `mustRepaint` true.
//
// This method must not be called during `paint`.
void _markPropertyUpdated({@required bool mustRepaint}) {
assert(owner == null || !owner.debugDoingPaint);
final bool newAnnotationIsActive = (
_onEnter != null || _onEnter != null ||
_onHover != null || _onHover != null ||
_onExit != null || _onExit != null ||
opaque opaque
) && ) && RendererBinding.instance.mouseTracker.mouseIsConnected;
RendererBinding.instance.mouseTracker.mouseIsConnected; _setAnnotationIsActive(newAnnotationIsActive);
if (annotationWasActive != annotationWillBeActive) { if (mustRepaint)
markNeedsPaint();
}
void _setAnnotationIsActive(bool value) {
final bool annotationWasActive = _annotationIsActive;
_annotationIsActive = value;
if (annotationWasActive != value) {
markNeedsPaint(); markNeedsPaint();
markNeedsCompositingBitsUpdate(); markNeedsCompositingBitsUpdate();
if (annotationWillBeActive) {
RendererBinding.instance.mouseTracker.attachAnnotation(_hoverAnnotation);
} else {
RendererBinding.instance.mouseTracker.detachAnnotation(_hoverAnnotation);
} }
_annotationIsActive = annotationWillBeActive;
} }
void _handleUpdatedMouseIsConnected() {
_markPropertyUpdated(mustRepaint: false);
} }
@override @override
void attach(PipelineOwner owner) { void attach(PipelineOwner owner) {
super.attach(owner); super.attach(owner);
// Add a listener to listen for changes in mouseIsConnected. // Add a listener to listen for changes in mouseIsConnected.
RendererBinding.instance.mouseTracker.addListener(_updateAnnotations); RendererBinding.instance.mouseTracker.addListener(_handleUpdatedMouseIsConnected);
_updateAnnotations(); _markPropertyUpdated(mustRepaint: false);
}
/// Attaches the annotation for this render object, if any.
///
/// This is called by the [MouseRegion]'s [Element] to tell this
/// [RenderMouseRegion] that it has transitioned from "inactive"
/// state to "active". We call it here so that
/// [MouseTrackerAnnotation.onEnter] isn't called during the build step for
/// the widget that provided the callback, and [State.setState] can safely be
/// called within that callback.
void postActivate() {
if (_annotationIsActive)
RendererBinding.instance.mouseTracker.attachAnnotation(_hoverAnnotation);
}
/// Detaches the annotation for this render object, if any.
///
/// This is called by the [MouseRegion]'s [Element] to tell this
/// [RenderMouseRegion] that it will shortly be transitioned from "active"
/// state to "inactive". We call it here so that
/// [MouseTrackerAnnotation.onExit] isn't called during the build step for the
/// widget that provided the callback, and [State.setState] can safely be
/// called within that callback.
void preDeactivate() {
if (_annotationIsActive)
RendererBinding.instance.mouseTracker.detachAnnotation(_hoverAnnotation);
} }
@override @override
void detach() { void detach() {
RendererBinding.instance.mouseTracker.removeListener(_updateAnnotations); RendererBinding.instance.mouseTracker.removeListener(_handleUpdatedMouseIsConnected);
super.detach(); super.detach();
} }
......
This diff is collapsed.
...@@ -467,6 +467,20 @@ void main() { ...@@ -467,6 +467,20 @@ void main() {
// transform -> clip // transform -> clip
_testFittedBoxWithClipRectLayer(); _testFittedBoxWithClipRectLayer();
}); });
test('RenderMouseRegion can change properties when detached', () {
renderer.initMouseTracker(MouseTracker(
renderer.pointerRouter,
(_) => <MouseTrackerAnnotation>[],
));
final RenderMouseRegion object = RenderMouseRegion();
object
..opaque = false
..onEnter = (_) {}
..onExit = (_) {}
..onHover = (_) {};
// Passes if no error is thrown
});
} }
class _TestRectClipper extends CustomClipper<Rect> { class _TestRectClipper extends CustomClipper<Rect> {
......
...@@ -162,7 +162,6 @@ void main() { ...@@ -162,7 +162,6 @@ void main() {
), ),
), ),
); );
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)); await gesture.addPointer(location: const Offset(400.0, 300.0));
addTearDown(gesture.removePointer); addTearDown(gesture.removePointer);
...@@ -178,7 +177,6 @@ void main() { ...@@ -178,7 +177,6 @@ void main() {
), ),
)); ));
expect(exit, isNull); expect(exit, isNull);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener.hoverAnnotation), isFalse);
}); });
testWidgets('Hover works with nested listeners', (WidgetTester tester) async { testWidgets('Hover works with nested listeners', (WidgetTester tester) async {
final UniqueKey key1 = UniqueKey(); final UniqueKey key1 = UniqueKey();
...@@ -229,9 +227,6 @@ void main() { ...@@ -229,9 +227,6 @@ void main() {
], ],
), ),
); );
final List<RenderObject> listeners = tester.renderObjectList(find.byType(MouseRegion)).toList();
final RenderMouseRegion renderListener1 = listeners[0] as RenderMouseRegion;
final RenderMouseRegion renderListener2 = listeners[1] as RenderMouseRegion;
Offset center = tester.getCenter(find.byKey(key2)); Offset center = tester.getCenter(find.byKey(key2));
await gesture.moveTo(center); await gesture.moveTo(center);
await tester.pump(); await tester.pump();
...@@ -243,8 +238,6 @@ void main() { ...@@ -243,8 +238,6 @@ void main() {
expect(enter1, isNotEmpty); expect(enter1, isNotEmpty);
expect(enter1.last.position, equals(center)); expect(enter1.last.position, equals(center));
expect(exit1, isEmpty); expect(exit1, isEmpty);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener1.hoverAnnotation), isTrue);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener2.hoverAnnotation), isTrue);
clearLists(); clearLists();
// Now make sure that exiting the child only triggers the child exit, not // Now make sure that exiting the child only triggers the child exit, not
...@@ -259,8 +252,6 @@ void main() { ...@@ -259,8 +252,6 @@ void main() {
expect(move1.last.position, equals(center)); expect(move1.last.position, equals(center));
expect(enter1, isEmpty); expect(enter1, isEmpty);
expect(exit1, isEmpty); expect(exit1, isEmpty);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener1.hoverAnnotation), isTrue);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener2.hoverAnnotation), isTrue);
clearLists(); clearLists();
}); });
testWidgets('Hover transfers between two listeners', (WidgetTester tester) async { testWidgets('Hover transfers between two listeners', (WidgetTester tester) async {
...@@ -314,9 +305,6 @@ void main() { ...@@ -314,9 +305,6 @@ void main() {
], ],
), ),
); );
final List<RenderObject> listeners = tester.renderObjectList(find.byType(MouseRegion)).toList();
final RenderMouseRegion renderListener1 = listeners[0] as RenderMouseRegion;
final RenderMouseRegion renderListener2 = listeners[1] as RenderMouseRegion;
final Offset center1 = tester.getCenter(find.byKey(key1)); final Offset center1 = tester.getCenter(find.byKey(key1));
final Offset center2 = tester.getCenter(find.byKey(key2)); final Offset center2 = tester.getCenter(find.byKey(key2));
await gesture.moveTo(center1); await gesture.moveTo(center1);
...@@ -329,8 +317,6 @@ void main() { ...@@ -329,8 +317,6 @@ void main() {
expect(move2, isEmpty); expect(move2, isEmpty);
expect(enter2, isEmpty); expect(enter2, isEmpty);
expect(exit2, isEmpty); expect(exit2, isEmpty);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener1.hoverAnnotation), isTrue);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener2.hoverAnnotation), isTrue);
clearLists(); clearLists();
await gesture.moveTo(center2); await gesture.moveTo(center2);
await tester.pump(); await tester.pump();
...@@ -343,8 +329,6 @@ void main() { ...@@ -343,8 +329,6 @@ void main() {
expect(enter2, isNotEmpty); expect(enter2, isNotEmpty);
expect(enter2.last.position, equals(center2)); expect(enter2.last.position, equals(center2));
expect(exit2, isEmpty); expect(exit2, isEmpty);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener1.hoverAnnotation), isTrue);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener2.hoverAnnotation), isTrue);
clearLists(); clearLists();
await gesture.moveTo(const Offset(400.0, 450.0)); await gesture.moveTo(const Offset(400.0, 450.0));
await tester.pump(); await tester.pump();
...@@ -355,8 +339,6 @@ void main() { ...@@ -355,8 +339,6 @@ void main() {
expect(enter2, isEmpty); expect(enter2, isEmpty);
expect(exit2, isNotEmpty); expect(exit2, isNotEmpty);
expect(exit2.last.position, equals(const Offset(400.0, 450.0))); expect(exit2.last.position, equals(const Offset(400.0, 450.0)));
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener1.hoverAnnotation), isTrue);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener2.hoverAnnotation), isTrue);
clearLists(); clearLists();
await tester.pumpWidget(Container()); await tester.pumpWidget(Container());
expect(move1, isEmpty); expect(move1, isEmpty);
...@@ -365,8 +347,6 @@ void main() { ...@@ -365,8 +347,6 @@ void main() {
expect(move2, isEmpty); expect(move2, isEmpty);
expect(enter2, isEmpty); expect(enter2, isEmpty);
expect(exit2, isEmpty); expect(exit2, isEmpty);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener1.hoverAnnotation), isFalse);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener2.hoverAnnotation), isFalse);
}); });
testWidgets('needsCompositing set when parent class needsCompositing is set', (WidgetTester tester) async { testWidgets('needsCompositing set when parent class needsCompositing is set', (WidgetTester tester) async {
......
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