Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
97dfafbb
Unverified
Commit
97dfafbb
authored
Jun 04, 2021
by
chunhtai
Committed by
GitHub
Jun 04, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Make tooltip hoverable and dismissible (#83830)
parent
92992550
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
252 additions
and
42 deletions
+252
-42
app.dart
packages/flutter/lib/src/material/app.dart
+11
-1
tooltip.dart
packages/flutter/lib/src/material/tooltip.dart
+70
-38
debug_test.dart
packages/flutter/test/material/debug_test.dart
+4
-1
tooltip_test.dart
packages/flutter/test/material/tooltip_test.dart
+165
-0
tooltip_theme_test.dart
packages/flutter/test/material/tooltip_theme_test.dart
+2
-2
No files found.
packages/flutter/lib/src/material/app.dart
View file @
97dfafbb
...
...
@@ -6,6 +6,7 @@ import 'dart:ui' as ui;
import
'package:flutter/cupertino.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/services.dart'
;
import
'arc.dart'
;
import
'colors.dart'
;
...
...
@@ -16,6 +17,7 @@ import 'page.dart';
import
'scaffold.dart'
show
ScaffoldMessenger
,
ScaffoldMessengerState
;
import
'scrollbar.dart'
;
import
'theme.dart'
;
import
'tooltip.dart'
;
/// [MaterialApp] uses this [TextStyle] as its [DefaultTextStyle] to encourage
/// developers to be intentional about their [DefaultTextStyle].
...
...
@@ -896,7 +898,15 @@ class _MaterialAppState extends State<MaterialApp> {
@override
Widget
build
(
BuildContext
context
)
{
Widget
result
=
_buildWidgetApp
(
context
);
result
=
Focus
(
canRequestFocus:
false
,
onKey:
(
FocusNode
node
,
RawKeyEvent
event
)
{
if
(
event
is
!
RawKeyDownEvent
||
event
.
logicalKey
!=
LogicalKeyboardKey
.
escape
)
return
KeyEventResult
.
ignored
;
return
Tooltip
.
dismissAllToolTips
()
?
KeyEventResult
.
handled
:
KeyEventResult
.
ignored
;
},
child:
result
,
);
assert
(()
{
if
(
widget
.
debugShowMaterialGrid
)
{
result
=
GridPaper
(
...
...
packages/flutter/lib/src/material/tooltip.dart
View file @
97dfafbb
...
...
@@ -6,6 +6,7 @@ import 'dart:async';
import
'package:flutter/gestures.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/widgets.dart'
;
import
'colors.dart'
;
...
...
@@ -56,7 +57,7 @@ import 'tooltip_theme.dart';
/// above the widget.
/// `textStyle` has been used to set the font size of the 'message'.
/// `showDuration` accepts a Duration to continue showing the message after the long
/// press has been released.
/// press has been released
or the mouse pointer exits the child widget
.
/// `waitDuration` accepts a Duration for which a mouse pointer has to hover over the child
/// widget before the tooltip is shown.
///
...
...
@@ -190,18 +191,34 @@ class Tooltip extends StatefulWidget {
/// The length of time that a pointer must hover over a tooltip's widget
/// before the tooltip will be shown.
///
/// Once the pointer leaves the widget, the tooltip will immediately
/// disappear.
///
/// Defaults to 0 milliseconds (tooltips are shown immediately upon hover).
final
Duration
?
waitDuration
;
/// The length of time that the tooltip will be shown after a long press
/// is released.
/// is released
or mouse pointer exits the widget
.
///
/// Defaults to 1.5 seconds.
/// Defaults to 1.5 seconds for long press released or 0.1 seconds for mouse
/// pointer exits the widget.
final
Duration
?
showDuration
;
static
final
Set
<
_TooltipState
>
_openedToolTips
=
<
_TooltipState
>{};
/// Dismiss all of the tooltips that are currently shown on the screen.
///
/// This method returns true if it successfully dismisses the tooltips. It
/// returns false if there is no tooltip shown on the screen.
static
bool
dismissAllToolTips
()
{
if
(
_openedToolTips
.
isNotEmpty
)
{
// Avoid concurrent modification.
final
List
<
_TooltipState
>
openedToolTips
=
List
<
_TooltipState
>.
from
(
_openedToolTips
);
for
(
final
_TooltipState
state
in
openedToolTips
)
{
state
.
_hideTooltip
(
immediately:
true
);
}
return
true
;
}
return
false
;
}
@override
State
<
Tooltip
>
createState
()
=>
_TooltipState
();
...
...
@@ -227,6 +244,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
static
const
Duration
_fadeInDuration
=
Duration
(
milliseconds:
150
);
static
const
Duration
_fadeOutDuration
=
Duration
(
milliseconds:
75
);
static
const
Duration
_defaultShowDuration
=
Duration
(
milliseconds:
1500
);
static
const
Duration
_defaultHoverShowDuration
=
Duration
(
milliseconds:
100
);
static
const
Duration
_defaultWaitDuration
=
Duration
.
zero
;
static
const
bool
_defaultExcludeFromSemantics
=
false
;
...
...
@@ -243,6 +261,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
Timer
?
_hideTimer
;
Timer
?
_showTimer
;
late
Duration
showDuration
;
late
Duration
hoverShowDuration
;
late
Duration
waitDuration
;
late
bool
_mouseIsConnected
;
bool
_longPressActivated
=
false
;
...
...
@@ -328,12 +347,9 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
return
;
}
if
(
_longPressActivated
)
{
// Tool tips activated by long press should stay around for the showDuration.
_hideTimer
??=
Timer
(
showDuration
,
_controller
.
reverse
);
}
else
{
// Tool tips activated by hover should disappear as soon as the mouse
// leaves the control.
_controller
.
reverse
();
_hideTimer
??=
Timer
(
hoverShowDuration
,
_controller
.
reverse
);
}
_longPressActivated
=
false
;
}
...
...
@@ -389,6 +405,8 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
height:
height
,
padding:
padding
,
margin:
margin
,
onEnter:
_mouseIsConnected
?
(
PointerEnterEvent
event
)
=>
_showTooltip
()
:
null
,
onExit:
_mouseIsConnected
?
(
PointerExitEvent
event
)
=>
_hideTooltip
()
:
null
,
decoration:
decoration
,
textStyle:
textStyle
,
animation:
CurvedAnimation
(
...
...
@@ -403,9 +421,11 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
_entry
=
OverlayEntry
(
builder:
(
BuildContext
context
)
=>
overlay
);
overlayState
.
insert
(
_entry
!);
SemanticsService
.
tooltip
(
widget
.
message
);
Tooltip
.
_openedToolTips
.
add
(
this
);
}
void
_removeEntry
()
{
Tooltip
.
_openedToolTips
.
remove
(
this
);
_hideTimer
?.
cancel
();
_hideTimer
=
null
;
_showTimer
?.
cancel
();
...
...
@@ -438,8 +458,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
void
dispose
()
{
GestureBinding
.
instance
!.
pointerRouter
.
removeGlobalRoute
(
_handlePointerEvent
);
RendererBinding
.
instance
!.
mouseTracker
.
removeListener
(
_handleMouseTrackerChange
);
if
(
_entry
!=
null
)
_removeEntry
();
_removeEntry
();
_controller
.
dispose
();
super
.
dispose
();
}
...
...
@@ -488,6 +507,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
textStyle
=
widget
.
textStyle
??
tooltipTheme
.
textStyle
??
defaultTextStyle
;
waitDuration
=
widget
.
waitDuration
??
tooltipTheme
.
waitDuration
??
_defaultWaitDuration
;
showDuration
=
widget
.
showDuration
??
tooltipTheme
.
showDuration
??
_defaultShowDuration
;
hoverShowDuration
=
widget
.
showDuration
??
tooltipTheme
.
showDuration
??
_defaultHoverShowDuration
;
Widget
result
=
GestureDetector
(
behavior:
HitTestBehavior
.
opaque
,
...
...
@@ -575,6 +595,8 @@ class _TooltipOverlay extends StatelessWidget {
required
this
.
target
,
required
this
.
verticalOffset
,
required
this
.
preferBelow
,
this
.
onEnter
,
this
.
onExit
,
})
:
super
(
key:
key
);
final
String
message
;
...
...
@@ -587,40 +609,50 @@ class _TooltipOverlay extends StatelessWidget {
final
Offset
target
;
final
double
verticalOffset
;
final
bool
preferBelow
;
final
PointerEnterEventListener
?
onEnter
;
final
PointerExitEventListener
?
onExit
;
@override
Widget
build
(
BuildContext
context
)
{
return
Positioned
.
fill
(
child:
IgnorePointer
(
child:
CustomSingleChildLayout
(
delegate:
_TooltipPositionDelegate
(
target:
target
,
verticalOffset:
verticalOffset
,
preferBelow:
preferBelow
,
),
child:
FadeTransition
(
opacity:
animation
,
child:
ConstrainedBox
(
constraints:
BoxConstraints
(
minHeight:
height
),
child:
DefaultTextStyle
(
style:
Theme
.
of
(
context
).
textTheme
.
bodyText2
!,
child:
Container
(
decoration:
decoration
,
padding:
padding
,
margin:
margin
,
child:
Center
(
widthFactor:
1.0
,
heightFactor:
1.0
,
child:
Text
(
message
,
style:
textStyle
,
),
),
Widget
result
=
IgnorePointer
(
child:
FadeTransition
(
opacity:
animation
,
child:
ConstrainedBox
(
constraints:
BoxConstraints
(
minHeight:
height
),
child:
DefaultTextStyle
(
style:
Theme
.
of
(
context
).
textTheme
.
bodyText2
!,
child:
Container
(
decoration:
decoration
,
padding:
padding
,
margin:
margin
,
child:
Center
(
widthFactor:
1.0
,
heightFactor:
1.0
,
child:
Text
(
message
,
style:
textStyle
,
),
),
),
),
),
)
);
if
(
onEnter
!=
null
||
onExit
!=
null
)
{
result
=
MouseRegion
(
onEnter:
onEnter
,
onExit:
onExit
,
child:
result
,
);
}
return
Positioned
.
fill
(
child:
CustomSingleChildLayout
(
delegate:
_TooltipPositionDelegate
(
target:
target
,
verticalOffset:
verticalOffset
,
preferBelow:
preferBelow
,
),
child:
result
,
),
);
}
...
...
packages/flutter/test/material/debug_test.dart
View file @
97dfafbb
...
...
@@ -206,12 +206,15 @@ void main() {
' UnmanagedRestorationScope
\n
'
' RootRestorationScope
\n
'
' WidgetsApp-[GlobalObjectKey _MaterialAppState#00000]
\n
'
' Semantics
\n
'
' _FocusMarker
\n
'
' Focus
\n
'
' HeroControllerScope
\n
'
' ScrollConfiguration
\n
'
' MaterialApp
\n
'
' [root]
\n
'
' Typically, the Scaffold widget is introduced by the MaterialApp
\n
'
' or WidgetsApp widget at the top of your application widget tree.
\n
'
,
' or WidgetsApp widget at the top of your application widget tree.
\n
'
));
});
...
...
packages/flutter/test/material/tooltip_test.dart
View file @
97dfafbb
...
...
@@ -912,6 +912,171 @@ void main() {
expect
(
find
.
text
(
tooltipText
),
findsNothing
);
});
testWidgets
(
'Tooltip text is also hoverable'
,
(
WidgetTester
tester
)
async
{
const
Duration
waitDuration
=
Duration
.
zero
;
TestGesture
?
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
addTearDown
(()
async
{
if
(
gesture
!=
null
)
return
gesture
.
removePointer
();
});
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
const
Offset
(
1.0
,
1.0
));
await
tester
.
pump
();
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pumpWidget
(
const
MaterialApp
(
home:
Center
(
child:
Tooltip
(
message:
tooltipText
,
waitDuration:
waitDuration
,
child:
Text
(
'I am tool tip'
),
),
),
),
);
final
Finder
tooltip
=
find
.
byType
(
Tooltip
);
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
);
// Hover to the tool tip text and verify the tooltip doesn't go away.
await
gesture
.
moveTo
(
tester
.
getTopLeft
(
find
.
text
(
tooltipText
)));
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
.
pumpAndSettle
();
await
gesture
.
removePointer
();
gesture
=
null
;
expect
(
find
.
text
(
tooltipText
),
findsNothing
);
});
testWidgets
(
'Tooltip can be dismissed by escape key'
,
(
WidgetTester
tester
)
async
{
const
Duration
waitDuration
=
Duration
.
zero
;
TestGesture
?
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
addTearDown
(()
async
{
if
(
gesture
!=
null
)
return
gesture
.
removePointer
();
});
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
const
Offset
(
1.0
,
1.0
));
await
tester
.
pump
();
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pumpWidget
(
const
MaterialApp
(
home:
Center
(
child:
Tooltip
(
message:
tooltipText
,
waitDuration:
waitDuration
,
child:
Text
(
'I am tool tip'
),
),
),
),
);
final
Finder
tooltip
=
find
.
byType
(
Tooltip
);
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
);
// Try to dismiss the tooltip with the shortcut key
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
escape
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
tooltipText
),
findsNothing
);
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pumpAndSettle
();
await
gesture
.
removePointer
();
gesture
=
null
;
});
testWidgets
(
'Multiple Tooltips are dismissed by escape key'
,
(
WidgetTester
tester
)
async
{
const
Duration
waitDuration
=
Duration
.
zero
;
TestGesture
?
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
addTearDown
(()
async
{
if
(
gesture
!=
null
)
return
gesture
.
removePointer
();
});
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
const
Offset
(
1.0
,
1.0
));
await
tester
.
pump
();
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Center
(
child:
Column
(
children:
const
<
Widget
>[
Tooltip
(
message:
'message1'
,
waitDuration:
waitDuration
,
showDuration:
Duration
(
days:
1
),
child:
Text
(
'tooltip1'
),
),
Spacer
(
flex:
2
),
Tooltip
(
message:
'message2'
,
waitDuration:
waitDuration
,
showDuration:
Duration
(
days:
1
),
child:
Text
(
'tooltip2'
),
)
],
),
),
),
);
final
Finder
tooltip
=
find
.
text
(
'tooltip1'
);
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pump
();
await
gesture
.
moveTo
(
tester
.
getCenter
(
tooltip
));
await
tester
.
pump
();
await
tester
.
pump
(
waitDuration
);
expect
(
find
.
text
(
'message1'
),
findsOneWidget
);
final
Finder
secondTooltip
=
find
.
text
(
'tooltip2'
);
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pump
();
await
gesture
.
moveTo
(
tester
.
getCenter
(
secondTooltip
));
await
tester
.
pump
();
await
tester
.
pump
(
waitDuration
);
// Make sure both messages are on the screen.
expect
(
find
.
text
(
'message1'
),
findsOneWidget
);
expect
(
find
.
text
(
'message2'
),
findsOneWidget
);
// Try to dismiss the tooltip with the shortcut key
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
escape
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'message1'
),
findsNothing
);
expect
(
find
.
text
(
'message2'
),
findsNothing
);
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pumpAndSettle
();
await
gesture
.
removePointer
();
gesture
=
null
;
});
testWidgets
(
'Tooltip does not attempt to show after unmount'
,
(
WidgetTester
tester
)
async
{
// Regression test for https://github.com/flutter/flutter/issues/54096.
const
Duration
waitDuration
=
Duration
(
seconds:
1
);
...
...
packages/flutter/test/material/tooltip_theme_test.dart
View file @
97dfafbb
...
...
@@ -865,7 +865,7 @@ void main() {
await
tester
.
pump
();
// Wait for it to disappear.
await
tester
.
pump
(
Duration
.
zero
);
// Should immediately disappear
await
tester
.
pump
(
customWaitDuration
);
expect
(
find
.
text
(
tooltipText
),
findsNothing
);
});
...
...
@@ -909,7 +909,7 @@ void main() {
await
tester
.
pump
();
// Wait for it to disappear.
await
tester
.
pump
(
Duration
.
zero
);
// Should immediately disappear
await
tester
.
pump
(
customWaitDuration
);
// Should disappear after customWaitDuration
expect
(
find
.
text
(
tooltipText
),
findsNothing
);
});
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment