Skip to content

Commit c318d4b

Browse files
committed
Support ignoring pointer events on tooltip overlay (#142465)
1 parent 4115a78 commit c318d4b

File tree

2 files changed

+190
-3
lines changed

2 files changed

+190
-3
lines changed

packages/flutter/lib/src/material/tooltip.dart

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ class Tooltip extends StatefulWidget {
186186
this.enableFeedback,
187187
this.onTriggered,
188188
this.mouseCursor,
189+
this.ignorePointer,
189190
this.child,
190191
}) : assert(
191192
(message == null) != (richMessage == null),
@@ -363,6 +364,17 @@ class Tooltip extends StatefulWidget {
363364
/// If this property is null, [MouseCursor.defer] will be used.
364365
final MouseCursor? mouseCursor;
365366

367+
/// Whether this tooltip should be visible to hit testing.
368+
///
369+
/// If no value is passed, pointer events are ignored unless the tooltip has a
370+
/// [richMessage].
371+
///
372+
/// See also:
373+
///
374+
/// * [IgnorePointer], for more information about how pointer events are
375+
/// handled or ignored.
376+
final bool? ignorePointer;
377+
366378
static final List<TooltipState> _openedTooltips = <TooltipState>[];
367379

368380
/// Dismiss all of the tooltips that are currently shown on the screen,
@@ -846,6 +858,7 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
846858
verticalOffset:
847859
widget.verticalOffset ?? tooltipTheme.verticalOffset ?? _defaultVerticalOffset,
848860
preferBelow: widget.preferBelow ?? tooltipTheme.preferBelow ?? _defaultPreferBelow,
861+
ignorePointer: widget.ignorePointer ?? widget.message != null,
849862
);
850863

851864
return SelectionContainer.maybeOf(context) == null
@@ -971,6 +984,7 @@ class _TooltipOverlay extends StatelessWidget {
971984
required this.target,
972985
required this.verticalOffset,
973986
required this.preferBelow,
987+
required this.ignorePointer,
974988
this.onEnter,
975989
this.onExit,
976990
});
@@ -988,6 +1002,7 @@ class _TooltipOverlay extends StatelessWidget {
9881002
final bool preferBelow;
9891003
final PointerEnterEventListener? onEnter;
9901004
final PointerExitEventListener? onExit;
1005+
final bool ignorePointer;
9911006

9921007
@override
9931008
Widget build(BuildContext context) {
@@ -1024,7 +1039,7 @@ class _TooltipOverlay extends StatelessWidget {
10241039
verticalOffset: verticalOffset,
10251040
preferBelow: preferBelow,
10261041
),
1027-
child: result,
1042+
child: IgnorePointer(ignoring: ignorePointer, child: result),
10281043
),
10291044
);
10301045
}

packages/flutter/test/material/tooltip_test.dart

Lines changed: 174 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,7 +1406,8 @@ void main() {
14061406
});
14071407

14081408
testWidgets('Tooltip is dismissed after tap to dismiss immediately', (WidgetTester tester) async {
1409-
await setWidgetForTooltipMode(tester, TooltipTriggerMode.tap);
1409+
// This test relies on not ignoring pointer events.
1410+
await setWidgetForTooltipMode(tester, TooltipTriggerMode.tap, ignorePointer: false);
14101411

14111412
final Finder tooltip = find.byType(Tooltip);
14121413
expect(find.text(tooltipText), findsNothing);
@@ -1424,7 +1425,13 @@ void main() {
14241425
testWidgets('Tooltip is not dismissed after tap if enableTapToDismiss is false', (
14251426
WidgetTester tester,
14261427
) async {
1427-
await setWidgetForTooltipMode(tester, TooltipTriggerMode.tap, enableTapToDismiss: false);
1428+
// This test relies on not ignoring pointer events.
1429+
await setWidgetForTooltipMode(
1430+
tester,
1431+
TooltipTriggerMode.tap,
1432+
enableTapToDismiss: false,
1433+
ignorePointer: false,
1434+
);
14281435

14291436
final Finder tooltip = find.byType(Tooltip);
14301437
expect(find.text(tooltipText), findsNothing);
@@ -1730,6 +1737,8 @@ void main() {
17301737
const MaterialApp(
17311738
home: Center(
17321739
child: Tooltip(
1740+
// This test relies on not ignoring pointer events.
1741+
ignorePointer: false,
17331742
message: tooltipText,
17341743
waitDuration: waitDuration,
17351744
child: Text('I am tool tip'),
@@ -3223,6 +3232,167 @@ void main() {
32233232
await tester.pump();
32243233
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), customCursor);
32253234
});
3235+
3236+
testWidgets('Tooltip overlay ignores pointer by default when passing simple message', (
3237+
WidgetTester tester,
3238+
) async {
3239+
const String tooltipMessage = 'Tooltip message';
3240+
3241+
await tester.pumpWidget(
3242+
MaterialApp(
3243+
home: Scaffold(
3244+
body: Center(
3245+
child: Tooltip(
3246+
message: tooltipMessage,
3247+
child: ElevatedButton(onPressed: () {}, child: const Text('Hover me')),
3248+
),
3249+
),
3250+
),
3251+
),
3252+
);
3253+
3254+
final Finder buttonFinder = find.text('Hover me');
3255+
expect(buttonFinder, findsOneWidget);
3256+
3257+
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
3258+
await gesture.addPointer();
3259+
await gesture.moveTo(tester.getCenter(buttonFinder));
3260+
await tester.pumpAndSettle();
3261+
3262+
final Finder tooltipFinder = find.text(tooltipMessage);
3263+
expect(tooltipFinder, findsOneWidget);
3264+
3265+
final Finder ignorePointerFinder = find.byType(IgnorePointer);
3266+
3267+
final IgnorePointer ignorePointer = tester.widget<IgnorePointer>(ignorePointerFinder.last);
3268+
expect(ignorePointer.ignoring, isTrue);
3269+
3270+
await gesture.removePointer();
3271+
});
3272+
3273+
testWidgets(
3274+
"Tooltip overlay with simple message doesn't ignore pointer when passing ignorePointer: false",
3275+
(WidgetTester tester) async {
3276+
const String tooltipMessage = 'Tooltip message';
3277+
3278+
await tester.pumpWidget(
3279+
MaterialApp(
3280+
home: Scaffold(
3281+
body: Center(
3282+
child: Tooltip(
3283+
ignorePointer: false,
3284+
message: tooltipMessage,
3285+
child: ElevatedButton(onPressed: () {}, child: const Text('Hover me')),
3286+
),
3287+
),
3288+
),
3289+
),
3290+
);
3291+
3292+
final Finder buttonFinder = find.text('Hover me');
3293+
expect(buttonFinder, findsOneWidget);
3294+
3295+
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
3296+
await gesture.addPointer();
3297+
await gesture.moveTo(tester.getCenter(buttonFinder));
3298+
await tester.pumpAndSettle();
3299+
3300+
final Finder tooltipFinder = find.text(tooltipMessage);
3301+
expect(tooltipFinder, findsOneWidget);
3302+
3303+
final Finder ignorePointerFinder = find.byType(IgnorePointer);
3304+
3305+
final IgnorePointer ignorePointer = tester.widget<IgnorePointer>(ignorePointerFinder.last);
3306+
expect(ignorePointer.ignoring, isFalse);
3307+
3308+
await gesture.removePointer();
3309+
},
3310+
);
3311+
3312+
testWidgets("Tooltip overlay doesn't ignore pointer by default when passing rich message", (
3313+
WidgetTester tester,
3314+
) async {
3315+
const InlineSpan richMessage = TextSpan(
3316+
children: <InlineSpan>[
3317+
TextSpan(text: 'Rich ', style: TextStyle(fontWeight: FontWeight.bold)),
3318+
TextSpan(text: 'Tooltip'),
3319+
],
3320+
);
3321+
3322+
await tester.pumpWidget(
3323+
MaterialApp(
3324+
home: Scaffold(
3325+
body: Center(
3326+
child: Tooltip(
3327+
richMessage: richMessage,
3328+
child: ElevatedButton(onPressed: () {}, child: const Text('Hover me')),
3329+
),
3330+
),
3331+
),
3332+
),
3333+
);
3334+
3335+
final Finder buttonFinder = find.text('Hover me');
3336+
expect(buttonFinder, findsOneWidget);
3337+
3338+
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
3339+
await gesture.addPointer();
3340+
await gesture.moveTo(tester.getCenter(buttonFinder));
3341+
await tester.pumpAndSettle();
3342+
3343+
final Finder tooltipFinder = find.textContaining('Rich Tooltip');
3344+
expect(tooltipFinder, findsOneWidget);
3345+
3346+
final Finder ignorePointerFinder = find.byType(IgnorePointer);
3347+
3348+
final IgnorePointer ignorePointer = tester.widget<IgnorePointer>(ignorePointerFinder.last);
3349+
expect(ignorePointer.ignoring, isFalse);
3350+
3351+
await gesture.removePointer();
3352+
});
3353+
3354+
testWidgets('Tooltip overlay with richMessage ignores pointer when passing ignorePointer: true', (
3355+
WidgetTester tester,
3356+
) async {
3357+
const InlineSpan richMessage = TextSpan(
3358+
children: <InlineSpan>[
3359+
TextSpan(text: 'Rich ', style: TextStyle(fontWeight: FontWeight.bold)),
3360+
TextSpan(text: 'Tooltip'),
3361+
],
3362+
);
3363+
3364+
await tester.pumpWidget(
3365+
MaterialApp(
3366+
home: Scaffold(
3367+
body: Center(
3368+
child: Tooltip(
3369+
ignorePointer: true,
3370+
richMessage: richMessage,
3371+
child: ElevatedButton(onPressed: () {}, child: const Text('Hover me')),
3372+
),
3373+
),
3374+
),
3375+
),
3376+
);
3377+
3378+
final Finder buttonFinder = find.text('Hover me');
3379+
expect(buttonFinder, findsOneWidget);
3380+
3381+
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
3382+
await gesture.addPointer();
3383+
await gesture.moveTo(tester.getCenter(buttonFinder));
3384+
await tester.pumpAndSettle();
3385+
3386+
final Finder tooltipFinder = find.textContaining('Rich Tooltip');
3387+
expect(tooltipFinder, findsOneWidget);
3388+
3389+
final Finder ignorePointerFinder = find.byType(IgnorePointer);
3390+
3391+
final IgnorePointer ignorePointer = tester.widget<IgnorePointer>(ignorePointerFinder.last);
3392+
expect(ignorePointer.ignoring, isTrue);
3393+
3394+
await gesture.removePointer();
3395+
});
32263396
}
32273397

32283398
Future<void> setWidgetForTooltipMode(
@@ -3231,6 +3401,7 @@ Future<void> setWidgetForTooltipMode(
32313401
Duration? showDuration,
32323402
bool? enableTapToDismiss,
32333403
TooltipTriggeredCallback? onTriggered,
3404+
bool? ignorePointer,
32343405
}) async {
32353406
await tester.pumpWidget(
32363407
MaterialApp(
@@ -3240,6 +3411,7 @@ Future<void> setWidgetForTooltipMode(
32403411
onTriggered: onTriggered,
32413412
showDuration: showDuration,
32423413
enableTapToDismiss: enableTapToDismiss ?? true,
3414+
ignorePointer: ignorePointer,
32433415
child: const SizedBox(width: 100.0, height: 100.0),
32443416
),
32453417
),

0 commit comments

Comments
 (0)