Unverified Commit 8bea3fb2 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Keep hover annotation layers in sync with the mouse detector. (#30829)

Adds a paint after detaching/attaching hover annotations to keep the annotation layers in sync with the annotations attached to the mouse detector.

Fixes #30744
parent e7c7a58d
...@@ -120,7 +120,6 @@ class MouseTracker { ...@@ -120,7 +120,6 @@ class MouseTracker {
/// [collectMousePositions] will assert the next time it is called. /// [collectMousePositions] 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);
assert(trackedAnnotation != null, "Tried to detach an annotation that wasn't attached: $annotation");
for (int deviceId in trackedAnnotation.activeDevices) { for (int deviceId in trackedAnnotation.activeDevices) {
annotation.onExit(PointerExitEvent.fromMouseEvent(_lastMouseEvent[deviceId])); annotation.onExit(PointerExitEvent.fromMouseEvent(_lastMouseEvent[deviceId]));
} }
...@@ -178,7 +177,7 @@ class MouseTracker { ...@@ -178,7 +177,7 @@ class MouseTracker {
/// MouseTracker. Do not call in other contexts. /// MouseTracker. Do not call in other contexts.
@visibleForTesting @visibleForTesting
bool isAnnotationAttached(MouseTrackerAnnotation annotation) { bool isAnnotationAttached(MouseTrackerAnnotation annotation) {
return _trackedAnnotations[annotation] != null; return _trackedAnnotations.containsKey(annotation);
} }
/// Tells interested objects that a mouse has entered, exited, or moved, given /// Tells interested objects that a mouse has entered, exited, or moved, given
......
...@@ -2543,7 +2543,7 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior { ...@@ -2543,7 +2543,7 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
/// no longer directed towards this receiver. /// no longer directed towards this receiver.
PointerCancelEventListener onPointerCancel; PointerCancelEventListener onPointerCancel;
/// Called when a pointer signal occures over this object. /// Called when a pointer signal occurs over this object.
PointerSignalEventListener onPointerSignal; PointerSignalEventListener onPointerSignal;
// Object used for annotation of the layer used for hover hit detection. // Object used for annotation of the layer used for hover hit detection.
...@@ -2557,6 +2557,8 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior { ...@@ -2557,6 +2557,8 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
MouseTrackerAnnotation get hoverAnnotation => _hoverAnnotation; MouseTrackerAnnotation get hoverAnnotation => _hoverAnnotation;
void _updateAnnotations() { void _updateAnnotations() {
assert(_onPointerEnter != _hoverAnnotation.onEnter || _onPointerHover != _hoverAnnotation.onHover || _onPointerExit != _hoverAnnotation.onExit,
"Shouldn't call _updateAnnotations if nothing has changed.");
if (_hoverAnnotation != null && attached) { if (_hoverAnnotation != null && attached) {
RendererBinding.instance.mouseTracker.detachAnnotation(_hoverAnnotation); RendererBinding.instance.mouseTracker.detachAnnotation(_hoverAnnotation);
} }
...@@ -2572,6 +2574,9 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior { ...@@ -2572,6 +2574,9 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
} else { } else {
_hoverAnnotation = null; _hoverAnnotation = null;
} }
// Needs to paint in any case, in order to insert/remove the annotation
// layer associated with the updated _hoverAnnotation.
markNeedsPaint();
} }
@override @override
......
...@@ -129,5 +129,108 @@ void main() { ...@@ -129,5 +129,108 @@ void main() {
expect(exit.position, equals(const Offset(400.0, 300.0))); expect(exit.position, equals(const Offset(400.0, 300.0)));
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener.hoverAnnotation), isFalse); expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener.hoverAnnotation), isFalse);
}); });
testWidgets('Hover transfers between two listeners', (WidgetTester tester) async {
final UniqueKey key1 = UniqueKey();
final UniqueKey key2 = UniqueKey();
final List<PointerEnterEvent> enter1 = <PointerEnterEvent>[];
final List<PointerHoverEvent> move1 = <PointerHoverEvent>[];
final List<PointerExitEvent> exit1 = <PointerExitEvent>[];
final List<PointerEnterEvent> enter2 = <PointerEnterEvent>[];
final List<PointerHoverEvent> move2 = <PointerHoverEvent>[];
final List<PointerExitEvent> exit2 = <PointerExitEvent>[];
void clearLists() {
enter1.clear();
move1.clear();
exit1.clear();
enter2.clear();
move2.clear();
exit2.clear();
}
await tester.pumpWidget(Container());
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.moveTo(const Offset(400.0, 0.0));
await tester.pump();
await tester.pumpWidget(
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Listener(
key: key1,
child: Container(
width: 100.0,
height: 100.0,
),
onPointerEnter: (PointerEnterEvent details) => enter1.add(details),
onPointerHover: (PointerHoverEvent details) => move1.add(details),
onPointerExit: (PointerExitEvent details) => exit1.add(details),
),
Listener(
key: key2,
child: Container(
width: 100.0,
height: 100.0,
),
onPointerEnter: (PointerEnterEvent details) => enter2.add(details),
onPointerHover: (PointerHoverEvent details) => move2.add(details),
onPointerExit: (PointerExitEvent details) => exit2.add(details),
),
],
),
);
final RenderPointerListener renderListener1 = tester.renderObject(find.byKey(key1));
final RenderPointerListener renderListener2 = tester.renderObject(find.byKey(key2));
final Offset center1 = tester.getCenter(find.byKey(key1));
final Offset center2 = tester.getCenter(find.byKey(key2));
await gesture.moveTo(center1);
await tester.pump();
expect(move1, isNotEmpty);
expect(move1.last.position, equals(center1));
expect(enter1, isNotEmpty);
expect(enter1.last.position, equals(center1));
expect(exit1, isEmpty);
expect(move2, isEmpty);
expect(enter2, isEmpty);
expect(exit2, isEmpty);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener1.hoverAnnotation), isTrue);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener2.hoverAnnotation), isTrue);
clearLists();
await gesture.moveTo(center2);
await tester.pump();
expect(move1, isEmpty);
expect(enter1, isEmpty);
expect(exit1, isNotEmpty);
expect(exit1.last.position, equals(center2));
expect(move2, isNotEmpty);
expect(move2.last.position, equals(center2));
expect(enter2, isNotEmpty);
expect(enter2.last.position, equals(center2));
expect(exit2, isEmpty);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener1.hoverAnnotation), isTrue);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener2.hoverAnnotation), isTrue);
clearLists();
await gesture.moveTo(const Offset(400.0, 450.0));
await tester.pump();
expect(move1, isEmpty);
expect(enter1, isEmpty);
expect(exit1, isEmpty);
expect(move2, isEmpty);
expect(enter2, isEmpty);
expect(exit2, isNotEmpty);
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();
await tester.pumpWidget(Container());
expect(move1, isEmpty);
expect(enter1, isEmpty);
expect(exit1, isEmpty);
expect(move2, isEmpty);
expect(enter2, isEmpty);
expect(exit2, isEmpty);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener1.hoverAnnotation), isFalse);
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener2.hoverAnnotation), isFalse);
});
}); });
} }
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