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
15967669
Unverified
Commit
15967669
authored
Sep 29, 2021
by
Kenzie (Schmoll) Davisson
Committed by
GitHub
Sep 29, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add `richMessage` parameter to the `Tooltip` widget. (#88539)
parent
f95e18ae
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
223 additions
and
26 deletions
+223
-26
tooltip.2.dart
examples/api/lib/material/tooltip/tooltip.2.dart
+74
-0
floating_action_button.dart
...ages/flutter/lib/src/material/floating_action_button.dart
+1
-1
icon_button.dart
packages/flutter/lib/src/material/icon_button.dart
+1
-1
navigation_bar.dart
packages/flutter/lib/src/material/navigation_bar.dart
+2
-4
tooltip.dart
packages/flutter/lib/src/material/tooltip.dart
+63
-18
tooltip_test.dart
packages/flutter/test/material/tooltip_test.dart
+79
-0
widget_tester.dart
packages/flutter_test/lib/src/widget_tester.dart
+3
-2
No files found.
examples/api/lib/material/tooltip/tooltip.2.dart
0 → 100644
View file @
15967669
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Template: dev/snippets/config/templates/stateless_widget_scaffold_center.tmpl
//
// Comment lines marked with "▼▼▼" and "▲▲▲" are used for authoring
// of samples, and may be ignored if you are just exploring the sample.
// Flutter code sample for Tooltip
//
//***************************************************************************
//* ▼▼▼▼▼▼▼▼ description ▼▼▼▼▼▼▼▼ (do not modify or remove section marker)
// This example shows a rich [Tooltip] that specifies the [richMessage]
// parameter instead of the [message] parameter (only one of these may be
// non-null. Any [InlineSpan] can be specified for the [richMessage] attribute,
// including [WidgetSpan].
//* ▲▲▲▲▲▲▲▲ description ▲▲▲▲▲▲▲▲ (do not modify or remove section marker)
//***************************************************************************
import
'package:flutter/material.dart'
;
void
main
(
)
=>
runApp
(
const
MyApp
());
/// This is the main application widget.
class
MyApp
extends
StatelessWidget
{
const
MyApp
({
Key
?
key
})
:
super
(
key:
key
);
static
const
String
_title
=
'Flutter Code Sample'
;
@override
Widget
build
(
BuildContext
context
)
{
return
MaterialApp
(
title:
_title
,
home:
Scaffold
(
appBar:
AppBar
(
title:
const
Text
(
_title
)),
body:
const
Center
(
child:
MyStatelessWidget
(),
),
),
);
}
}
/// This is the stateless widget that the main application instantiates.
class
MyStatelessWidget
extends
StatelessWidget
{
const
MyStatelessWidget
({
Key
?
key
})
:
super
(
key:
key
);
@override
//********************************************************************
//* ▼▼▼▼▼▼▼▼ code ▼▼▼▼▼▼▼▼ (do not modify or remove section marker)
Widget
build
(
BuildContext
context
)
{
return
const
Tooltip
(
richMessage:
TextSpan
(
text:
'I am a rich tooltip. '
,
style:
TextStyle
(
color:
Colors
.
red
),
children:
<
InlineSpan
>[
TextSpan
(
text:
'I am another span of this rich tooltip'
,
style:
TextStyle
(
fontWeight:
FontWeight
.
bold
),
),
],
),
child:
Text
(
'Tap this text and hold down to show a tooltip.'
),
);
}
//* ▲▲▲▲▲▲▲▲ code ▲▲▲▲▲▲▲▲ (do not modify or remove section marker)
//********************************************************************
}
packages/flutter/lib/src/material/floating_action_button.dart
View file @
15967669
...
...
@@ -628,7 +628,7 @@ class FloatingActionButton extends StatelessWidget {
if
(
tooltip
!=
null
)
{
result
=
Tooltip
(
message:
tooltip
!
,
message:
tooltip
,
child:
result
,
);
}
...
...
packages/flutter/lib/src/material/icon_button.dart
View file @
15967669
...
...
@@ -323,7 +323,7 @@ class IconButton extends StatelessWidget {
if
(
tooltip
!=
null
)
{
result
=
Tooltip
(
message:
tooltip
!
,
message:
tooltip
,
child:
result
,
);
}
...
...
packages/flutter/lib/src/material/navigation_bar.dart
View file @
15967669
...
...
@@ -759,9 +759,7 @@ class _NavigationBarDestinationTooltip extends StatelessWidget {
})
:
super
(
key:
key
);
/// The text that is rendered in the tooltip when it appears.
///
/// If [message] is null, no tooltip will be used.
final
String
?
message
;
final
String
message
;
/// The widget that, when pressed, will show a tooltip.
final
Widget
child
;
...
...
@@ -772,7 +770,7 @@ class _NavigationBarDestinationTooltip extends StatelessWidget {
return
child
;
}
return
Tooltip
(
message:
message
!
,
message:
message
,
// TODO(johnsonmh): Make this value configurable/themable.
verticalOffset:
42
,
excludeFromSemantics:
true
,
...
...
packages/flutter/lib/src/material/tooltip.dart
View file @
15967669
...
...
@@ -4,6 +4,7 @@
import
'dart:async'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
...
...
@@ -55,6 +56,15 @@ import 'tooltip_theme.dart';
/// ** See code in examples/api/lib/material/tooltip/tooltip.1.dart **
/// {@end-tool}
///
/// {@tool dartpad --template=stateless_widget_scaffold_center}
/// This example shows a rich [Tooltip] that specifies the [richMessage]
/// parameter instead of the [message] parameter (only one of these may be
/// non-null. Any [InlineSpan] can be specified for the [richMessage] attribute,
/// including [WidgetSpan].
///
/// ** See code in examples/api/lib/material/tooltip/tooltip.2.dart **
/// {@end-tool}
///
/// See also:
///
/// * <https://material.io/design/components/tooltips.html>
...
...
@@ -70,9 +80,12 @@ class Tooltip extends StatefulWidget {
///
/// All parameters that are defined in the constructor will
/// override the default values _and_ the values in [TooltipTheme.of].
///
/// Only one of [message] and [richMessage] may be non-null.
const
Tooltip
({
Key
?
key
,
required
this
.
message
,
this
.
message
,
this
.
richMessage
,
this
.
height
,
this
.
padding
,
this
.
margin
,
...
...
@@ -86,11 +99,24 @@ class Tooltip extends StatefulWidget {
this
.
child
,
this
.
triggerMode
,
this
.
enableFeedback
,
})
:
assert
(
message
!=
null
),
super
(
key:
key
);
})
:
assert
((
message
==
null
)
!=
(
richMessage
==
null
),
'Either `message` or `richMessage` must be specified'
),
assert
(
richMessage
==
null
||
textStyle
==
null
,
'If `richMessage` is specified, `textStyle` will have no effect. '
'If you wish to provide a `textStyle` for a rich tooltip, add the '
'`textStyle` directly to the `richMessage` InlineSpan.'
,
),
super
(
key:
key
);
/// The text to display in the tooltip.
final
String
message
;
///
/// Only one of [message] and [richMessage] may be non-null.
final
String
?
message
;
/// The rich text to display in the tooltip.
///
/// Only one of [message] and [richMessage] may be non-null.
final
InlineSpan
?
richMessage
;
/// The height of the tooltip's [child].
///
...
...
@@ -131,12 +157,13 @@ class Tooltip extends StatefulWidget {
/// direction.
final
bool
?
preferBelow
;
/// Whether the tooltip's [message]
should be excluded from the semantics
/// tree.
/// Whether the tooltip's [message]
or [richMessage] should be excluded from
/// t
he semantics t
ree.
///
/// Defaults to false. A tooltip will add a [Semantics] label that is set to
/// [Tooltip.message]. Set this property to true if the app is going to
/// provide its own custom semantics label.
/// [Tooltip.message] if non-null, or the plain text value of
/// [Tooltip.richMessage] otherwise. Set this property to true if the app is
/// going to provide its own custom semantics label.
final
bool
?
excludeFromSemantics
;
/// The widget below this widget in the tree.
...
...
@@ -241,7 +268,18 @@ class Tooltip extends StatefulWidget {
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
StringProperty
(
'message'
,
message
,
showName:
false
));
properties
.
add
(
StringProperty
(
'message'
,
message
,
showName:
message
==
null
,
defaultValue:
message
==
null
?
null
:
kNoDefaultValue
,
));
properties
.
add
(
StringProperty
(
'richMessage'
,
richMessage
?.
toPlainText
(),
showName:
richMessage
==
null
,
defaultValue:
richMessage
==
null
?
null
:
kNoDefaultValue
,
));
properties
.
add
(
DoubleProperty
(
'height'
,
height
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
EdgeInsetsGeometry
>(
'padding'
,
padding
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
EdgeInsetsGeometry
>(
'margin'
,
margin
,
defaultValue:
null
));
...
...
@@ -290,6 +328,11 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
late
bool
_isConcealed
;
late
bool
_forceRemoval
;
/// The plain text message for this tooltip.
///
/// This value will either come from [widget.message] or [widget.richMessage].
String
get
_tooltipMessage
=>
widget
.
message
??
widget
.
richMessage
!.
toPlainText
();
@override
void
initState
()
{
super
.
initState
();
...
...
@@ -428,7 +471,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
)!;
overlayState
.
insert
(
_entry
!);
}
SemanticsService
.
tooltip
(
widget
.
m
essage
);
SemanticsService
.
tooltip
(
_tooltipM
essage
);
_controller
.
forward
();
}
...
...
@@ -487,7 +530,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
final
Widget
overlay
=
Directionality
(
textDirection:
Directionality
.
of
(
context
),
child:
_TooltipOverlay
(
message:
widget
.
message
,
richMessage:
widget
.
richMessage
??
TextSpan
(
text:
widget
.
message
)
,
height:
height
,
padding:
padding
,
margin:
margin
,
...
...
@@ -507,7 +550,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
_entry
=
OverlayEntry
(
builder:
(
BuildContext
context
)
=>
overlay
);
_isConcealed
=
false
;
overlayState
.
insert
(
_entry
!);
SemanticsService
.
tooltip
(
widget
.
m
essage
);
SemanticsService
.
tooltip
(
_tooltipM
essage
);
if
(
_mouseIsConnected
)
{
// Hovered tooltips shouldn't show more than one at once. For example, a chip with
// a delete icon shouldn't show both the delete icon tooltip and the chip tooltip
...
...
@@ -580,7 +623,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
// If message is empty then no need to create a tooltip overlay to show
// the empty black container so just return the wrapped child as is or
// empty container if child is not specified.
if
(
widget
.
m
essage
.
isEmpty
)
{
if
(
_tooltipM
essage
.
isEmpty
)
{
return
widget
.
child
??
const
SizedBox
();
}
assert
(
Overlay
.
of
(
context
,
debugRequiredFor:
widget
)
!=
null
);
...
...
@@ -629,7 +672,9 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
onTap:
(
triggerMode
==
TooltipTriggerMode
.
tap
)
?
_handlePress
:
null
,
excludeFromSemantics:
true
,
child:
Semantics
(
label:
excludeFromSemantics
?
null
:
widget
.
message
,
label:
excludeFromSemantics
?
null
:
_tooltipMessage
,
child:
widget
.
child
,
),
);
...
...
@@ -700,8 +745,8 @@ class _TooltipPositionDelegate extends SingleChildLayoutDelegate {
class
_TooltipOverlay
extends
StatelessWidget
{
const
_TooltipOverlay
({
Key
?
key
,
required
this
.
message
,
required
this
.
height
,
required
this
.
richMessage
,
this
.
padding
,
this
.
margin
,
this
.
decoration
,
...
...
@@ -714,7 +759,7 @@ class _TooltipOverlay extends StatelessWidget {
this
.
onExit
,
})
:
super
(
key:
key
);
final
String
m
essage
;
final
InlineSpan
richM
essage
;
final
double
height
;
final
EdgeInsetsGeometry
?
padding
;
final
EdgeInsetsGeometry
?
margin
;
...
...
@@ -743,8 +788,8 @@ class _TooltipOverlay extends StatelessWidget {
child:
Center
(
widthFactor:
1.0
,
heightFactor:
1.0
,
child:
Text
(
m
essage
,
child:
Text
.
rich
(
richM
essage
,
style:
textStyle
,
),
),
...
...
packages/flutter/test/material/tooltip_test.dart
View file @
15967669
...
...
@@ -1319,6 +1319,63 @@ void main() {
expect
(
tip
.
size
.
height
,
equals
(
56.0
));
});
testWidgets
(
'Tooltip text displays with richMessage'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key
=
GlobalKey
();
const
String
textSpan1Text
=
'I am a rich tooltip message. '
;
const
String
textSpan2Text
=
'I am another span of a rich tooltip message'
;
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Tooltip
(
key:
key
,
richMessage:
const
TextSpan
(
text:
textSpan1Text
,
children:
<
InlineSpan
>[
TextSpan
(
text:
textSpan2Text
,
),
],
),
child:
Container
(
width:
100.0
,
height:
100.0
,
color:
Colors
.
green
[
500
],
),
),
),
);
_ensureTooltipVisible
(
key
);
await
tester
.
pump
(
const
Duration
(
seconds:
2
));
// faded in, show timer started (and at 0.0)
final
RichText
richText
=
tester
.
widget
<
RichText
>(
find
.
byType
(
RichText
));
expect
(
richText
.
text
.
toPlainText
(),
equals
(
'
$textSpan1Text$textSpan2Text
'
));
});
testWidgets
(
'Tooltip throws assertion error when both message and richMessage are specified'
,
(
WidgetTester
tester
)
async
{
expect
(
()
{
MaterialApp
(
home:
Tooltip
(
message:
'I am a tooltip message.'
,
richMessage:
const
TextSpan
(
text:
'I am a rich tooltip.'
,
children:
<
InlineSpan
>[
TextSpan
(
text:
'I am another span of a rich tooltip.'
,
),
],
),
child:
Container
(
width:
100.0
,
height:
100.0
,
color:
Colors
.
green
[
500
],
),
),
);
},
throwsA
(
const
TypeMatcher
<
AssertionError
>()),
);
});
testWidgets
(
'Haptic feedback'
,
(
WidgetTester
tester
)
async
{
final
FeedbackTester
feedback
=
FeedbackTester
();
await
tester
.
pumpWidget
(
...
...
@@ -1476,6 +1533,28 @@ void main() {
'"message"'
,
]);
});
testWidgets
(
'default Tooltip debugFillProperties with richMessage'
,
(
WidgetTester
tester
)
async
{
final
DiagnosticPropertiesBuilder
builder
=
DiagnosticPropertiesBuilder
();
const
Tooltip
(
richMessage:
TextSpan
(
text:
'This is a '
,
children:
<
InlineSpan
>[
TextSpan
(
text:
'richMessage'
,
),
],
),
).
debugFillProperties
(
builder
);
final
List
<
String
>
description
=
builder
.
properties
.
where
((
DiagnosticsNode
node
)
=>
!
node
.
isFiltered
(
DiagnosticLevel
.
info
))
.
map
((
DiagnosticsNode
node
)
=>
node
.
toString
()).
toList
();
expect
(
description
,
<
String
>[
'"This is a richMessage"'
,
]);
});
testWidgets
(
'Tooltip implements debugFillProperties'
,
(
WidgetTester
tester
)
async
{
final
DiagnosticPropertiesBuilder
builder
=
DiagnosticPropertiesBuilder
();
...
...
packages/flutter_test/lib/src/widget_tester.dart
View file @
15967669
...
...
@@ -841,9 +841,10 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
final
Widget
widget
=
element
.
widget
;
if
(
widget
is
Tooltip
)
{
final
Iterable
<
Element
>
matches
=
find
.
byTooltip
(
widget
.
message
).
evaluate
();
final
String
message
=
widget
.
message
??
widget
.
richMessage
!.
toPlainText
();
final
Iterable
<
Element
>
matches
=
find
.
byTooltip
(
message
).
evaluate
();
if
(
matches
.
length
==
1
)
{
printToConsole
(
" find.byTooltip('
$
{widget.message}
')"
);
printToConsole
(
" find.byTooltip('
$
message
')"
);
continue
;
}
}
...
...
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