Skip to content

Commit 2ebc7be

Browse files
authored
Adds tooltip to semantics node (#87684)
1 parent ec6481b commit 2ebc7be

20 files changed

+154
-25
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,7 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
698698
_enableFeedback = widget.enableFeedback ?? tooltipTheme.enableFeedback ?? _defaultEnableFeedback;
699699

700700
Widget result = Semantics(
701-
label: _excludeFromSemantics
701+
tooltip: _excludeFromSemantics
702702
? null
703703
: _tooltipMessage,
704704
child: widget.child,

packages/flutter/lib/src/rendering/proxy_box.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3859,6 +3859,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
38593859
AttributedString? attributedIncreasedValue,
38603860
AttributedString? attributedDecreasedValue,
38613861
AttributedString? attributedHint,
3862+
String? tooltip,
38623863
SemanticsHintOverrides? hintOverrides,
38633864
TextDirection? textDirection,
38643865
SemanticsSortKey? sortKey,
@@ -3917,6 +3918,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
39173918
_attributedIncreasedValue = attributedIncreasedValue,
39183919
_attributedDecreasedValue = attributedDecreasedValue,
39193920
_attributedHint = attributedHint,
3921+
_tooltip = tooltip,
39203922
_hintOverrides = hintOverrides,
39213923
_textDirection = textDirection,
39223924
_sortKey = sortKey,
@@ -4311,6 +4313,18 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
43114313
markNeedsSemanticsUpdate();
43124314
}
43134315

4316+
/// If non-null, sets the [SemanticsNode.tooltip] semantic to the given value.
4317+
///
4318+
/// The reading direction is given by [textDirection].
4319+
String? get tooltip => _tooltip;
4320+
String? _tooltip;
4321+
set tooltip(String? value) {
4322+
if (_tooltip == value)
4323+
return;
4324+
_tooltip = value;
4325+
markNeedsSemanticsUpdate();
4326+
}
4327+
43144328
/// If non-null, sets the [SemanticsConfiguration.hintOverrides] to the given value.
43154329
SemanticsHintOverrides? get hintOverrides => _hintOverrides;
43164330
SemanticsHintOverrides? _hintOverrides;
@@ -4843,6 +4857,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
48434857
config.attributedDecreasedValue = attributedDecreasedValue!;
48444858
if (attributedHint != null)
48454859
config.attributedHint = attributedHint!;
4860+
if (tooltip != null)
4861+
config.tooltip = tooltip!;
48464862
if (hintOverrides != null && hintOverrides!.isNotEmpty)
48474863
config.hintOverrides = hintOverrides;
48484864
if (scopesRoute != null)

packages/flutter/lib/src/semantics/semantics.dart

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ class SemanticsData with Diagnosticable {
316316
required this.attributedIncreasedValue,
317317
required this.attributedDecreasedValue,
318318
required this.attributedHint,
319+
required this.tooltip,
319320
required this.textDirection,
320321
required this.rect,
321322
required this.elevation,
@@ -339,6 +340,7 @@ class SemanticsData with Diagnosticable {
339340
assert(attributedDecreasedValue != null),
340341
assert(attributedIncreasedValue != null),
341342
assert(attributedHint != null),
343+
assert(tooltip == '' || textDirection != null, 'A SemanticsData object with tooltip "$tooltip" had a null textDirection.'),
342344
assert(attributedLabel.string == '' || textDirection != null, 'A SemanticsData object with label "${attributedLabel.string}" had a null textDirection.'),
343345
assert(attributedValue.string == '' || textDirection != null, 'A SemanticsData object with value "${attributedValue.string}" had a null textDirection.'),
344346
assert(attributedDecreasedValue.string == '' || textDirection != null, 'A SemanticsData object with decreasedValue "${attributedDecreasedValue.string}" had a null textDirection.'),
@@ -429,6 +431,11 @@ class SemanticsData with Diagnosticable {
429431
/// See also [hint], which exposes just the raw text.
430432
final AttributedString attributedHint;
431433

434+
/// A textual description of the widget's tooltip.
435+
///
436+
/// The reading direction is given by [textDirection].
437+
final String tooltip;
438+
432439
/// The reading direction for the text in [label], [value],
433440
/// [increasedValue], [decreasedValue], and [hint].
434441
final TextDirection? textDirection;
@@ -587,6 +594,7 @@ class SemanticsData with Diagnosticable {
587594
properties.add(AttributedStringProperty('increasedValue', attributedIncreasedValue));
588595
properties.add(AttributedStringProperty('decreasedValue', attributedDecreasedValue));
589596
properties.add(AttributedStringProperty('hint', attributedHint));
597+
properties.add(StringProperty('tooltip', tooltip, defaultValue: ''));
590598
properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
591599
if (textSelection?.isValid ?? false)
592600
properties.add(MessageProperty('textSelection', '[${textSelection!.start}, ${textSelection!.end}]'));
@@ -610,6 +618,7 @@ class SemanticsData with Diagnosticable {
610618
&& other.attributedIncreasedValue == attributedIncreasedValue
611619
&& other.attributedDecreasedValue == attributedDecreasedValue
612620
&& other.attributedHint == attributedHint
621+
&& other.tooltip == tooltip
613622
&& other.textDirection == textDirection
614623
&& other.rect == rect
615624
&& setEquals(other.tags, tags)
@@ -637,6 +646,7 @@ class SemanticsData with Diagnosticable {
637646
attributedIncreasedValue,
638647
attributedDecreasedValue,
639648
attributedHint,
649+
tooltip,
640650
textDirection,
641651
rect,
642652
tags,
@@ -648,8 +658,8 @@ class SemanticsData with Diagnosticable {
648658
scrollExtentMin,
649659
platformViewId,
650660
maxValueLength,
651-
currentValueLength,
652661
Object.hash(
662+
currentValueLength,
653663
transform,
654664
elevation,
655665
thickness,
@@ -785,6 +795,7 @@ class SemanticsProperties extends DiagnosticableTree {
785795
this.decreasedValue,
786796
this.attributedDecreasedValue,
787797
this.hint,
798+
this.tooltip,
788799
this.attributedHint,
789800
this.hintOverrides,
790801
this.textDirection,
@@ -1178,6 +1189,16 @@ class SemanticsProperties extends DiagnosticableTree {
11781189
/// * [hint] for a plain string version of this property.
11791190
final AttributedString? attributedHint;
11801191

1192+
/// Provides a textual description of the widget's tooltip.
1193+
///
1194+
/// In Android, this property sets the `AccessibilityNodeInfo.setTooltipText`.
1195+
/// In iOS, this property is appended to the end of the
1196+
/// `UIAccessibilityElement.accessibilityLabel`.
1197+
///
1198+
/// If a [tooltip] is provided, there must either by an ambient
1199+
/// [Directionality] or an explicit [textDirection] should be provided.
1200+
final String? tooltip;
1201+
11811202
/// Provides hint values which override the default hints on supported
11821203
/// platforms.
11831204
///
@@ -1469,6 +1490,7 @@ class SemanticsProperties extends DiagnosticableTree {
14691490
properties.add(AttributedStringProperty('attributedDecreasedValue', attributedDecreasedValue, defaultValue: null));
14701491
properties.add(StringProperty('hint', hint, defaultValue: null));
14711492
properties.add(AttributedStringProperty('attributedHint', attributedHint, defaultValue: null));
1493+
properties.add(StringProperty('tooltip', tooltip));
14721494
properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
14731495
properties.add(DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
14741496
properties.add(DiagnosticsProperty<SemanticsHintOverrides>('hintOverrides', hintOverrides, defaultValue: null));
@@ -1898,6 +1920,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
18981920
|| _attributedValue != config.attributedValue
18991921
|| _attributedIncreasedValue != config.attributedIncreasedValue
19001922
|| _attributedDecreasedValue != config.attributedDecreasedValue
1923+
|| _tooltip != config.tooltip
19011924
|| _flags != config._flags
19021925
|| _textDirection != config.textDirection
19031926
|| _sortKey != config._sortKey
@@ -2027,6 +2050,12 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
20272050
AttributedString get attributedHint => _attributedHint;
20282051
AttributedString _attributedHint = _kEmptyConfig.attributedHint;
20292052

2053+
/// A textual description of the widget's tooltip.
2054+
///
2055+
/// The reading direction is given by [textDirection].
2056+
String get tooltip => _tooltip;
2057+
String _tooltip = _kEmptyConfig.tooltip;
2058+
20302059
/// The elevation along the z-axis at which the [rect] of this [SemanticsNode]
20312060
/// is located above its parent.
20322061
///
@@ -2235,6 +2264,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
22352264
_attributedIncreasedValue = config.attributedIncreasedValue;
22362265
_attributedDecreasedValue = config.attributedDecreasedValue;
22372266
_attributedHint = config.attributedHint;
2267+
_tooltip = config.tooltip;
22382268
_hintOverrides = config.hintOverrides;
22392269
_elevation = config.elevation;
22402270
_thickness = config.thickness;
@@ -2282,6 +2312,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
22822312
AttributedString attributedIncreasedValue = _attributedIncreasedValue;
22832313
AttributedString attributedDecreasedValue = _attributedDecreasedValue;
22842314
AttributedString attributedHint = _attributedHint;
2315+
String tooltip = _tooltip;
22852316
TextDirection? textDirection = _textDirection;
22862317
Set<SemanticsTag>? mergedTags = tags == null ? null : Set<SemanticsTag>.of(tags!);
22872318
TextSelection? textSelection = _textSelection;
@@ -2336,6 +2367,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
23362367
attributedIncreasedValue = node._attributedIncreasedValue;
23372368
if (attributedDecreasedValue == null || attributedDecreasedValue.string == '')
23382369
attributedDecreasedValue = node._attributedDecreasedValue;
2370+
if (tooltip == '')
2371+
tooltip = node._tooltip;
23392372
if (node.tags != null) {
23402373
mergedTags ??= <SemanticsTag>{};
23412374
mergedTags!.addAll(node.tags!);
@@ -2385,6 +2418,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
23852418
attributedIncreasedValue: attributedIncreasedValue,
23862419
attributedDecreasedValue: attributedDecreasedValue,
23872420
attributedHint: attributedHint,
2421+
tooltip: tooltip,
23882422
textDirection: textDirection,
23892423
rect: rect,
23902424
transform: transform,
@@ -2457,6 +2491,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
24572491
decreasedValueAttributes: data.attributedDecreasedValue.attributes,
24582492
hint: data.attributedHint.string,
24592493
hintAttributes: data.attributedHint.attributes,
2494+
tooltip: data.tooltip,
24602495
textDirection: data.textDirection,
24612496
textSelectionBase: data.textSelection != null ? data.textSelection!.baseOffset : -1,
24622497
textSelectionExtent: data.textSelection != null ? data.textSelection!.extentOffset : -1,
@@ -2595,6 +2630,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
25952630
properties.add(AttributedStringProperty('increasedValue', _attributedIncreasedValue));
25962631
properties.add(AttributedStringProperty('decreasedValue', _attributedDecreasedValue));
25972632
properties.add(AttributedStringProperty('hint', _attributedHint));
2633+
properties.add(StringProperty('tooltip', _tooltip, defaultValue: ''));
25982634
properties.add(EnumProperty<TextDirection>('textDirection', _textDirection, defaultValue: null));
25992635
properties.add(DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
26002636
if (_textSelection?.isValid ?? false)
@@ -3955,6 +3991,16 @@ class SemanticsConfiguration {
39553991
_hasBeenAnnotated = true;
39563992
}
39573993

3994+
/// A textual description of the widget's tooltip.
3995+
///
3996+
/// The reading direction is given by [textDirection].
3997+
String get tooltip => _tooltip;
3998+
String _tooltip = '';
3999+
set tooltip(String tooltip) {
4000+
_tooltip = tooltip;
4001+
_hasBeenAnnotated = true;
4002+
}
4003+
39584004
/// Provides hint values which override the default hints on supported
39594005
/// platforms.
39604006
SemanticsHintOverrides? get hintOverrides => _hintOverrides;
@@ -4420,6 +4466,8 @@ class SemanticsConfiguration {
44204466
otherAttributedString: child._attributedHint,
44214467
otherTextDirection: child.textDirection,
44224468
);
4469+
if (_tooltip == '')
4470+
_tooltip = child._tooltip;
44234471

44244472
_thickness = math.max(_thickness, child._thickness + child._elevation);
44254473

@@ -4442,6 +4490,7 @@ class SemanticsConfiguration {
44424490
.._attributedDecreasedValue = _attributedDecreasedValue
44434491
.._attributedHint = _attributedHint
44444492
.._hintOverrides = _hintOverrides
4493+
.._tooltip = _tooltip
44454494
.._elevation = _elevation
44464495
.._thickness = _thickness
44474496
.._flags = _flags

packages/flutter/lib/src/widgets/basic.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6695,6 +6695,7 @@ class Semantics extends SingleChildRenderObjectWidget {
66956695
AttributedString? attributedDecreasedValue,
66966696
String? hint,
66976697
AttributedString? attributedHint,
6698+
String? tooltip,
66986699
String? onTapHint,
66996700
String? onLongPressHint,
67006701
TextDirection? textDirection,
@@ -6759,6 +6760,7 @@ class Semantics extends SingleChildRenderObjectWidget {
67596760
attributedDecreasedValue: attributedDecreasedValue,
67606761
hint: hint,
67616762
attributedHint: attributedHint,
6763+
tooltip: tooltip,
67626764
textDirection: textDirection,
67636765
sortKey: sortKey,
67646766
tagForChildren: tagForChildren,
@@ -6901,6 +6903,7 @@ class Semantics extends SingleChildRenderObjectWidget {
69016903
attributedIncreasedValue: _effectiveAttributedIncreasedValue,
69026904
attributedDecreasedValue: _effectiveAttributedDecreasedValue,
69036905
attributedHint: _effectiveAttributedHint,
6906+
tooltip: properties.tooltip,
69046907
hintOverrides: properties.hintOverrides,
69056908
textDirection: _getTextDirection(context),
69066909
sortKey: properties.sortKey,
@@ -6936,7 +6939,8 @@ class Semantics extends SingleChildRenderObjectWidget {
69366939
final bool containsText = properties.attributedLabel != null ||
69376940
properties.label != null ||
69386941
properties.value != null ||
6939-
properties.hint != null;
6942+
properties.hint != null ||
6943+
properties.tooltip != null;
69406944

69416945
if (!containsText)
69426946
return null;
@@ -6977,6 +6981,7 @@ class Semantics extends SingleChildRenderObjectWidget {
69776981
..attributedIncreasedValue = _effectiveAttributedIncreasedValue
69786982
..attributedDecreasedValue = _effectiveAttributedDecreasedValue
69796983
..attributedHint = _effectiveAttributedHint
6984+
..tooltip = properties.tooltip
69806985
..hintOverrides = properties.hintOverrides
69816986
..namesRoute = properties.namesRoute
69826987
..textDirection = _getTextDirection(context)

packages/flutter/lib/src/widgets/semantics_debugger.dart

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -281,27 +281,33 @@ class _SemanticsDebuggerPainter extends CustomPainter {
281281

282282
assert(data.attributedLabel != null);
283283
final String message;
284-
if (data.attributedLabel.string.isEmpty) {
284+
final String tooltipAndLabel = <String>[
285+
if (data.tooltip.isNotEmpty)
286+
data.tooltip,
287+
if (data.attributedLabel.string.isNotEmpty)
288+
data.attributedLabel.string,
289+
].join('\n');
290+
if (tooltipAndLabel.isEmpty) {
285291
message = annotations.join('; ');
286292
} else {
287-
final String label;
293+
final String effectivelabel;
288294
if (data.textDirection == null) {
289-
label = '${Unicode.FSI}${data.attributedLabel.string}${Unicode.PDI}';
295+
effectivelabel = '${Unicode.FSI}$tooltipAndLabel${Unicode.PDI}';
290296
annotations.insert(0, 'MISSING TEXT DIRECTION');
291297
} else {
292298
switch (data.textDirection!) {
293299
case TextDirection.rtl:
294-
label = '${Unicode.RLI}${data.attributedLabel.string}${Unicode.PDF}';
300+
effectivelabel = '${Unicode.RLI}$tooltipAndLabel${Unicode.PDF}';
295301
break;
296302
case TextDirection.ltr:
297-
label = data.attributedLabel.string;
303+
effectivelabel = tooltipAndLabel;
298304
break;
299305
}
300306
}
301307
if (annotations.isEmpty) {
302-
message = label;
308+
message = effectivelabel;
303309
} else {
304-
message = '$label (${annotations.join('; ')})';
310+
message = '$effectivelabel (${annotations.join('; ')})';
305311
}
306312
}
307313

packages/flutter/test/material/back_button_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ void main() {
154154
await tester.pumpAndSettle();
155155

156156
expect(tester.getSemantics(find.byType(BackButton)), matchesSemantics(
157-
label: 'Back',
157+
tooltip: 'Back',
158158
isButton: true,
159159
hasEnabledState: true,
160160
isEnabled: true,

packages/flutter/test/material/calendar_date_picker_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -649,15 +649,15 @@ void main() {
649649

650650
// Prev/Next month buttons.
651651
expect(tester.getSemantics(previousMonthIcon), matchesSemantics(
652-
label: 'Previous month',
652+
tooltip: 'Previous month',
653653
isButton: true,
654654
hasTapAction: true,
655655
isEnabled: true,
656656
hasEnabledState: true,
657657
isFocusable: true,
658658
));
659659
expect(tester.getSemantics(nextMonthIcon), matchesSemantics(
660-
label: 'Next month',
660+
tooltip: 'Next month',
661661
isButton: true,
662662
hasTapAction: true,
663663
isEnabled: true,

packages/flutter/test/material/chip_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1964,7 +1964,7 @@ void main() {
19641964
],
19651965
children: <TestSemantics>[
19661966
TestSemantics(
1967-
label: 'Delete',
1967+
tooltip: 'Delete',
19681968
actions: <SemanticsAction>[SemanticsAction.tap],
19691969
textDirection: TextDirection.ltr,
19701970
flags: <SemanticsFlag>[

packages/flutter/test/material/date_picker_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,7 @@ void main() {
817817

818818
// Input mode toggle button
819819
expect(tester.getSemantics(switchToInputIcon), matchesSemantics(
820-
label: 'Switch to input',
820+
tooltip: 'Switch to input',
821821
isButton: true,
822822
hasTapAction: true,
823823
isEnabled: true,
@@ -860,7 +860,7 @@ void main() {
860860

861861
// Input mode toggle button
862862
expect(tester.getSemantics(switchToCalendarIcon), matchesSemantics(
863-
label: 'Switch to calendar',
863+
tooltip: 'Switch to calendar',
864864
isButton: true,
865865
hasTapAction: true,
866866
isEnabled: true,

0 commit comments

Comments
 (0)