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() {
|
void _updateAnnotations() {
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
|
final bool hadHoverAnnotation = _hoverAnnotation != null;
|
||||||
if (_hoverAnnotation != null && attached) {
|
if (_hoverAnnotation != null && attached) {
|
||||||
RendererBinding.instance.mouseTracker.detachAnnotation(_hoverAnnotation);
|
RendererBinding.instance.mouseTracker.detachAnnotation(_hoverAnnotation);
|
||||||
changed = true;
|
changed = true;
|
||||||
@ -2587,6 +2588,10 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
|
|||||||
if (changed) {
|
if (changed) {
|
||||||
markNeedsPaint();
|
markNeedsPaint();
|
||||||
}
|
}
|
||||||
|
final bool hasHoverAnnotation = _hoverAnnotation != null;
|
||||||
|
if (hadHoverAnnotation != hasHoverAnnotation) {
|
||||||
|
markNeedsCompositingBitsUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -2608,6 +2613,9 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
|
|||||||
super.detach();
|
super.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get needsCompositing => _hoverAnnotation != null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(PaintingContext context, Offset offset) {
|
void paint(PaintingContext context, Offset offset) {
|
||||||
if (_hoverAnnotation != null) {
|
if (_hoverAnnotation != null) {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter/widgets.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(renderListener1.hoverAnnotation), isFalse);
|
||||||
expect(tester.binding.mouseTracker.isAnnotationAttached(renderListener2.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