Unverified Commit eca93640 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Add support for Tooltip hover (#31561)

Adds support for mouse pointer hovering to trigger tooltips, as well as custom timeouts for the tooltip durations, and a custom decoration. It also makes the tooltip be fully opaque when shown, and fade in over 150ms, and fade out over 75ms, and draw a 4.0 corner radius, all to conform with the material spec. Prior to this change, it was using a corner radius of 2.0 when shown, and faded in and out over 200ms.

Fixes #22817
parent a21a1f41
......@@ -11,6 +11,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart';
import 'feedback_tester.dart';
......@@ -67,7 +68,7 @@ void main() {
),
),
);
(key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
(key.currentState as dynamic).ensureTooltipVisible(); // Before using "as dynamic" in your code, see note at the top of the file.
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
/********************* 800x600 screen
......@@ -123,7 +124,7 @@ void main() {
),
),
);
(key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
(key.currentState as dynamic).ensureTooltipVisible(); // Before using "as dynamic" in your code, see note at the top of the file.
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
/********************* 800x600 screen
......@@ -175,7 +176,7 @@ void main() {
),
),
);
(key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
(key.currentState as dynamic).ensureTooltipVisible(); // Before using "as dynamic" in your code, see note at the top of the file.
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
/********************* 800x600 screen
......@@ -229,7 +230,7 @@ void main() {
),
),
);
(key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
(key.currentState as dynamic).ensureTooltipVisible(); // Before using "as dynamic" in your code, see note at the top of the file.
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
// we try to put it here but it doesn't fit:
......@@ -294,7 +295,7 @@ void main() {
),
),
);
(key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
(key.currentState as dynamic).ensureTooltipVisible(); // Before using "as dynamic" in your code, see note at the top of the file.
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
/********************* 800x600 screen
......@@ -347,7 +348,7 @@ void main() {
),
),
);
(key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
(key.currentState as dynamic).ensureTooltipVisible(); // Before using "as dynamic" in your code, see note at the top of the file.
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
/********************* 800x600 screen
......@@ -402,7 +403,7 @@ void main() {
),
),
);
(key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
(key.currentState as dynamic).ensureTooltipVisible(); // Before using "as dynamic" in your code, see note at the top of the file.
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
/********************* 800x600 screen
......@@ -422,6 +423,82 @@ void main() {
expect(tip.localToGlobal(tip.size.bottomRight(Offset.zero)).dy, equals(324.0));
});
testWidgets('Does tooltip end up with the right default size, shape, and color', (WidgetTester tester) async {
final GlobalKey key = GlobalKey();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Overlay(
initialEntries: <OverlayEntry>[
OverlayEntry(
builder: (BuildContext context) {
return Tooltip(
key: key,
message: tooltipText,
child: Container(
width: 0.0,
height: 0.0,
),
);
},
),
],
),
),
);
(key.currentState as dynamic).ensureTooltipVisible(); // Before using "as dynamic" in your code, see note at the top of the file.
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
final RenderBox tip = tester.renderObject(find.text(tooltipText)).parent.parent.parent.parent;
expect(tip.size.height, equals(32.0));
expect(tip.size.width, equals(74.0));
expect(tip, paints..rrect(
rrect: RRect.fromRectAndRadius(tip.paintBounds, const Radius.circular(4.0)),
color: const Color(0xe6616161),
));
});
testWidgets('Can tooltip decoration be customized', (WidgetTester tester) async {
final GlobalKey key = GlobalKey();
const Decoration customDecoration = ShapeDecoration(
shape: StadiumBorder(),
color: Color(0x80800000),
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Overlay(
initialEntries: <OverlayEntry>[
OverlayEntry(
builder: (BuildContext context) {
return Tooltip(
key: key,
decoration: customDecoration,
message: tooltipText,
child: Container(
width: 0.0,
height: 0.0,
),
);
},
),
],
),
),
);
(key.currentState as dynamic).ensureTooltipVisible(); // Before using "as dynamic" in your code, see note at the top of the file.
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
final RenderBox tip = tester.renderObject(find.text(tooltipText)).parent.parent.parent.parent;
expect(tip.size.height, equals(32.0));
expect(tip.size.width, equals(74.0));
expect(tip, paints..path(
color: const Color(0x80800000),
));
});
testWidgets('Tooltip stays around', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
......@@ -457,6 +534,50 @@ void main() {
gesture.up();
});
testWidgets('Tooltip shows/hides when hovered', (WidgetTester tester) async {
const Duration waitDuration = Duration(milliseconds: 0);
const Duration showDuration = Duration(milliseconds: 1500);
await tester.pumpWidget(
MaterialApp(
home: Center(
child: Tooltip(
message: tooltipText,
showDuration: showDuration,
waitDuration: waitDuration,
child: Container(
width: 100.0,
height: 100.0,
),
),
),
)
);
final Finder tooltip = find.byType(Tooltip);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.moveTo(Offset.zero);
await tester.pump();
await gesture.moveTo(tester.getCenter(tooltip));
await tester.pump();
// Wait for it to appear.
await tester.pump(waitDuration);
expect(find.text(tooltipText), findsOneWidget);
// Wait a looong time to make sure that it doesn't go away if the mouse is
// still over the widget.
await tester.pump(const Duration(days: 1));
await tester.pumpAndSettle();
expect(find.text(tooltipText), findsOneWidget);
await gesture.moveTo(Offset.zero);
await tester.pump();
// Wait for it to disappear.
await tester.pump(showDuration);
await tester.pumpAndSettle();
expect(find.text(tooltipText), findsNothing);
});
testWidgets('Does tooltip contribute semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
......@@ -500,7 +621,7 @@ void main() {
expect(semantics, hasSemantics(expected, ignoreTransform: true, ignoreRect: true));
// before using "as dynamic" in your code, see note top of file
// Before using "as dynamic" in your code, see note at the top of the file.
(key.currentState as dynamic).ensureTooltipVisible(); // this triggers a rebuild of the semantics because the tree changes
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
......
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