Unverified Commit f66ee3e4 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Add semanticsLabel parameter to TextSpan (#30837)

parent 676fcc4b
...@@ -58,6 +58,7 @@ class TextSpan extends DiagnosticableTree { ...@@ -58,6 +58,7 @@ class TextSpan extends DiagnosticableTree {
this.text, this.text,
this.children, this.children,
this.recognizer, this.recognizer,
this.semanticsLabel,
}); });
/// The style to apply to the [text] and the [children]. /// The style to apply to the [text] and the [children].
...@@ -156,6 +157,19 @@ class TextSpan extends DiagnosticableTree { ...@@ -156,6 +157,19 @@ class TextSpan extends DiagnosticableTree {
/// {@end-tool} /// {@end-tool}
final GestureRecognizer recognizer; final GestureRecognizer recognizer;
/// An alternative semantics label for this text.
///
/// If present, the semantics of this span will contain this value instead
/// of the actual text.
///
/// This is useful for replacing abbreviations or shorthands with the full
/// text value:
///
/// ```dart
/// TextSpan(text: r'$$', semanticsLabel: 'Double dollars')
/// ```
final String semanticsLabel;
/// Apply the [style], [text], and [children] of this object to the /// Apply the [style], [text], and [children] of this object to the
/// given [ParagraphBuilder], from which a [Paragraph] can be obtained. /// given [ParagraphBuilder], from which a [Paragraph] can be obtained.
/// [Paragraph] objects can be drawn on [Canvas] objects. /// [Paragraph] objects can be drawn on [Canvas] objects.
...@@ -220,12 +234,18 @@ class TextSpan extends DiagnosticableTree { ...@@ -220,12 +234,18 @@ class TextSpan extends DiagnosticableTree {
/// Flattens the [TextSpan] tree into a single string. /// Flattens the [TextSpan] tree into a single string.
/// ///
/// Styles are not honored in this process. /// Styles are not honored in this process. If `includeSemanticsLabels` is
String toPlainText() { /// true, then the text returned will include the [semanticsLabel]s instead of
/// the text contents when they are present.
String toPlainText({bool includeSemanticsLabels = true}) {
assert(debugAssertIsValid()); assert(debugAssertIsValid());
final StringBuffer buffer = StringBuffer(); final StringBuffer buffer = StringBuffer();
visitTextSpan((TextSpan span) { visitTextSpan((TextSpan span) {
buffer.write(span.text); if (span.semanticsLabel != null && includeSemanticsLabels) {
buffer.write(span.semanticsLabel);
} else {
buffer.write(span.text);
}
return true; return true;
}); });
return buffer.toString(); return buffer.toString();
...@@ -324,11 +344,12 @@ class TextSpan extends DiagnosticableTree { ...@@ -324,11 +344,12 @@ class TextSpan extends DiagnosticableTree {
return typedOther.text == text return typedOther.text == text
&& typedOther.style == style && typedOther.style == style
&& typedOther.recognizer == recognizer && typedOther.recognizer == recognizer
&& typedOther.semanticsLabel == semanticsLabel
&& listEquals<TextSpan>(typedOther.children, children); && listEquals<TextSpan>(typedOther.children, children);
} }
@override @override
int get hashCode => hashValues(style, text, recognizer, hashList(children)); int get hashCode => hashValues(style, text, recognizer, semanticsLabel, hashList(children));
@override @override
String toStringShort() => '$runtimeType'; String toStringShort() => '$runtimeType';
...@@ -348,6 +369,12 @@ class TextSpan extends DiagnosticableTree { ...@@ -348,6 +369,12 @@ class TextSpan extends DiagnosticableTree {
defaultValue: null, defaultValue: null,
)); ));
if (semanticsLabel != null) {
properties.add(StringProperty('semanticsLabel', semanticsLabel));
}
properties.add(StringProperty('text', text, showName: false, defaultValue: null)); properties.add(StringProperty('text', text, showName: false, defaultValue: null));
if (style == null && text == null && children == null) if (style == null && text == null && children == null)
properties.add(DiagnosticsNode.message('(empty)')); properties.add(DiagnosticsNode.message('(empty)'));
......
...@@ -475,8 +475,9 @@ class RenderParagraph extends RenderBox { ...@@ -475,8 +475,9 @@ class RenderParagraph extends RenderBox {
int offset = 0; int offset = 0;
text.visitTextSpan((TextSpan span) { text.visitTextSpan((TextSpan span) {
if (span.recognizer != null && (span.recognizer is TapGestureRecognizer || span.recognizer is LongPressGestureRecognizer)) { if (span.recognizer != null && (span.recognizer is TapGestureRecognizer || span.recognizer is LongPressGestureRecognizer)) {
final int length = span.semanticsLabel?.length ?? span.text.length;
_recognizerOffsets.add(offset); _recognizerOffsets.add(offset);
_recognizerOffsets.add(offset + span.text.length); _recognizerOffsets.add(offset + length);
_recognizers.add(span.recognizer); _recognizers.add(span.recognizer);
} }
offset += span.text.length; offset += span.text.length;
......
...@@ -348,7 +348,8 @@ class Text extends StatelessWidget { ...@@ -348,7 +348,8 @@ class Text extends StatelessWidget {
/// An alternative semantics label for this text. /// An alternative semantics label for this text.
/// ///
/// If present, the semantics of this widget will contain this value instead /// If present, the semantics of this widget will contain this value instead
/// of the actual text. /// of the actual text. This will overwrite any of the semantics labels applied
/// directly to the [TextSpan]s.
/// ///
/// This is useful for replacing abbreviations or shorthands with the full /// This is useful for replacing abbreviations or shorthands with the full
/// text value: /// text value:
......
...@@ -61,4 +61,27 @@ void main() { ...@@ -61,4 +61,27 @@ void main() {
' "c"\n' ' "c"\n'
)); ));
}); });
test('TextSpan toPlainText', () {
const TextSpan textSpan = TextSpan(
text: 'a',
children: <TextSpan>[
TextSpan(text: 'b'),
TextSpan(text: 'c'),
],
);
expect(textSpan.toPlainText(), 'abc');
});
test('TextSpan toPlainText with semanticsLabel', () {
const TextSpan textSpan = TextSpan(
text: 'a',
children: <TextSpan>[
TextSpan(text: 'b', semanticsLabel: 'foo'),
TextSpan(text: 'c'),
],
);
expect(textSpan.toPlainText(), 'afooc');
expect(textSpan.toPlainText(includeSemanticsLabels: false), 'abc');
});
} }
...@@ -184,6 +184,51 @@ void main() { ...@@ -184,6 +184,51 @@ void main() {
semantics.dispose(); semantics.dispose();
}); });
testWidgets('recognizers split semantic nodes with text span labels', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
const TextStyle textStyle = TextStyle(fontFamily: 'Ahem');
await tester.pumpWidget(
Text.rich(
TextSpan(
children: <TextSpan>[
const TextSpan(text: 'hello '),
TextSpan(text: 'world', recognizer: TapGestureRecognizer()..onTap = () { }),
const TextSpan(text: ' this is a '),
const TextSpan(text: 'cat-astrophe', semanticsLabel: 'regrettable event'),
],
style: textStyle,
),
textDirection: TextDirection.ltr,
),
);
final TestSemantics expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
children: <TestSemantics>[
TestSemantics(
label: 'hello ',
textDirection: TextDirection.ltr,
),
TestSemantics(
label: 'world',
textDirection: TextDirection.ltr,
actions: <SemanticsAction>[
SemanticsAction.tap,
],
),
TestSemantics(
label: ' regrettable event',
textDirection: TextDirection.ltr,
),
],
),
],
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreId: true, ignoreRect: true));
semantics.dispose();
});
testWidgets('recognizers split semantic node - bidi', (WidgetTester tester) async { testWidgets('recognizers split semantic node - bidi', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
const TextStyle textStyle = TextStyle(fontFamily: 'Ahem'); const TextStyle textStyle = TextStyle(fontFamily: 'Ahem');
......
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