Make Hover Listener respect transforms (#32025)
This commit is contained in:
parent
ea03ac2b84
commit
cc239580d3
@ -2566,6 +2566,7 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
|
||||
|
||||
void _updateAnnotations() {
|
||||
bool changed = false;
|
||||
final bool hadHoverAnnotation = _hoverAnnotation != null;
|
||||
if (_hoverAnnotation != null && attached) {
|
||||
RendererBinding.instance.mouseTracker.detachAnnotation(_hoverAnnotation);
|
||||
changed = true;
|
||||
@ -2587,6 +2588,10 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
|
||||
if (changed) {
|
||||
markNeedsPaint();
|
||||
}
|
||||
final bool hasHoverAnnotation = _hoverAnnotation != null;
|
||||
if (hadHoverAnnotation != hasHoverAnnotation) {
|
||||
markNeedsCompositingBitsUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@ -2608,6 +2613,9 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
|
||||
super.detach();
|
||||
}
|
||||
|
||||
@override
|
||||
bool get needsCompositing => _hoverAnnotation != null;
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
if (_hoverAnnotation != null) {
|
||||
|
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@ -232,5 +233,120 @@ void main() {
|
||||
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener1.hoverAnnotation), isFalse);
|
||||
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener2.hoverAnnotation), isFalse);
|
||||
});
|
||||
|
||||
testWidgets('works with transform', (WidgetTester tester) async {
|
||||
// Regression test for https://github.com/flutter/flutter/issues/31986.
|
||||
final Key key = UniqueKey();
|
||||
const double scaleFactor = 2.0;
|
||||
const double localWidth = 150.0;
|
||||
const double localHeight = 100.0;
|
||||
final List<PointerEvent> events = <PointerEvent>[];
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Center(
|
||||
child: Transform.scale(
|
||||
scale: scaleFactor,
|
||||
child: Listener(
|
||||
onPointerEnter: (PointerEnterEvent event) {
|
||||
events.add(event);
|
||||
},
|
||||
onPointerHover: (PointerHoverEvent event) {
|
||||
events.add(event);
|
||||
},
|
||||
onPointerExit: (PointerExitEvent event) {
|
||||
events.add(event);
|
||||
},
|
||||
child: Container(
|
||||
key: key,
|
||||
color: Colors.blue,
|
||||
height: localHeight,
|
||||
width: localWidth,
|
||||
child: const Text('Hi'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final Offset topLeft = tester.getTopLeft(find.byKey(key));
|
||||
final Offset topRight = tester.getTopRight(find.byKey(key));
|
||||
final Offset bottomLeft = tester.getBottomLeft(find.byKey(key));
|
||||
expect(topRight.dx - topLeft.dx, scaleFactor * localWidth);
|
||||
expect(bottomLeft.dy - topLeft.dy, scaleFactor * localHeight);
|
||||
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.moveTo(topLeft - const Offset(1, 1));
|
||||
await tester.pump();
|
||||
expect(events, isEmpty);
|
||||
|
||||
await gesture.moveTo(topLeft + const Offset(1, 1));
|
||||
await tester.pump();
|
||||
expect(events, hasLength(2));
|
||||
expect(events.first, isA<PointerEnterEvent>());
|
||||
expect(events.last, isA<PointerHoverEvent>());
|
||||
events.clear();
|
||||
|
||||
await gesture.moveTo(bottomLeft + const Offset(1, -1));
|
||||
await tester.pump();
|
||||
expect(events.single, isA<PointerHoverEvent>());
|
||||
expect(events.single.delta, const Offset(0.0, scaleFactor * localHeight - 2));
|
||||
events.clear();
|
||||
|
||||
await gesture.moveTo(bottomLeft + const Offset(1, 1));
|
||||
await tester.pump();
|
||||
expect(events.single, isA<PointerExitEvent>());
|
||||
events.clear();
|
||||
});
|
||||
|
||||
testWidgets('needsCompositing updates correctly and is respected', (WidgetTester tester) async {
|
||||
// Pretend that we have a mouse connected.
|
||||
final TestGesture gesture = await tester.startGesture(Offset.zero, kind: PointerDeviceKind.mouse);
|
||||
await gesture.up();
|
||||
|
||||
await tester.pumpWidget(
|
||||
Transform.scale(
|
||||
scale: 2.0,
|
||||
child: Listener(
|
||||
onPointerDown: (PointerDownEvent _) { },
|
||||
),
|
||||
),
|
||||
);
|
||||
final RenderPointerListener listener = tester.renderObject(find.byType(Listener));
|
||||
expect(listener.needsCompositing, isFalse);
|
||||
// No TransformLayer for `Transform.scale` is added because composting is
|
||||
// not required and therefore the transform is executed on the canvas
|
||||
// directly. (One TransformLayer is always present for the root
|
||||
// transform.)
|
||||
expect(tester.layers.whereType<TransformLayer>(), hasLength(1));
|
||||
|
||||
await tester.pumpWidget(
|
||||
Transform.scale(
|
||||
scale: 2.0,
|
||||
child: Listener(
|
||||
onPointerDown: (PointerDownEvent _) { },
|
||||
onPointerHover: (PointerHoverEvent _) { },
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(listener.needsCompositing, isTrue);
|
||||
// Composting is required, therefore a dedicated TransformLayer for
|
||||
// `Transform.scale` is added.
|
||||
expect(tester.layers.whereType<TransformLayer>(), hasLength(2));
|
||||
|
||||
await tester.pumpWidget(
|
||||
Transform.scale(
|
||||
scale: 2.0,
|
||||
child: Listener(
|
||||
onPointerDown: (PointerDownEvent _) { },
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(listener.needsCompositing, isFalse);
|
||||
// TransformLayer for `Transform.scale` is removed again as transform is
|
||||
// executed directly on the canvas.
|
||||
expect(tester.layers.whereType<TransformLayer>(), hasLength(1));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user