From 4e5cf5efb058a784b6aa330d6730a7583018dc54 Mon Sep 17 00:00:00 2001 From: Gary Qian Date: Thu, 6 Jun 2019 18:25:36 -0700 Subject: [PATCH] Revert "Reland "Text inline widgets, TextSpan rework" (#33946)" (#34002) This reverts commit 14414f350ac040f9626f1aa0b3071f42cc9136a6. --- dev/benchmarks/complex_layout/lib/main.dart | 4 +- dev/manual_tests/lib/text.dart | 2 +- packages/flutter/lib/painting.dart | 4 +- .../flutter/lib/src/material/time_picker.dart | 3 +- .../flutter/lib/src/painting/inline_span.dart | 249 ------ .../lib/src/painting/placeholder_span.dart | 85 -- .../lib/src/painting/text_painter.dart | 147 +--- .../flutter/lib/src/painting/text_span.dart | 239 ++--- packages/flutter/lib/src/rendering/box.dart | 5 +- .../rendering/debug_overflow_indicator.dart | 4 +- .../flutter/lib/src/rendering/paragraph.dart | 395 ++------- packages/flutter/lib/src/widgets/basic.dart | 24 +- packages/flutter/lib/src/widgets/text.dart | 13 +- .../lib/src/widgets/widget_inspector.dart | 3 +- .../flutter/lib/src/widgets/widget_span.dart | 198 ----- packages/flutter/lib/widgets.dart | 1 - .../test/painting/text_painter_rtl_test.dart | 12 +- .../test/painting/text_painter_test.dart | 95 -- .../flutter/test/painting/text_span_test.dart | 139 +-- .../test/rendering/paragraph_test.dart | 90 -- .../test/widgets/backdrop_filter_test.dart | 1 - packages/flutter/test/widgets/basic_test.dart | 1 + .../test/widgets/editable_text_test.dart | 5 +- .../test/widgets/text_golden_test.dart | 827 +----------------- packages/flutter/test/widgets/text_test.dart | 140 +-- .../test/widgets/widget_inspector_test.dart | 5 +- 26 files changed, 244 insertions(+), 2447 deletions(-) delete mode 100644 packages/flutter/lib/src/painting/inline_span.dart delete mode 100644 packages/flutter/lib/src/painting/placeholder_span.dart delete mode 100644 packages/flutter/lib/src/widgets/widget_span.dart diff --git a/dev/benchmarks/complex_layout/lib/main.dart b/dev/benchmarks/complex_layout/lib/main.dart index 634be4517c9..2d349aea536 100644 --- a/dev/benchmarks/complex_layout/lib/main.dart +++ b/dev/benchmarks/complex_layout/lib/main.dart @@ -438,8 +438,8 @@ class ItemImageBox extends StatelessWidget { borderRadius: BorderRadius.circular(2.0), ), padding: const EdgeInsets.all(4.0), - child: RichText( - text: const TextSpan( + child: const RichText( + text: TextSpan( style: TextStyle(color: Colors.white), children: [ TextSpan( diff --git a/dev/manual_tests/lib/text.dart b/dev/manual_tests/lib/text.dart index 3d5f482bd5d..6382fb3b8fb 100644 --- a/dev/manual_tests/lib/text.dart +++ b/dev/manual_tests/lib/text.dart @@ -145,7 +145,7 @@ class _FuzzerState extends State with SingleTickerProviderStateMixin { return TextSpan( text: _fiddleWithText(node.text), style: _fiddleWithStyle(node.style), - children: _fiddleWithChildren(node.children?.map((InlineSpan child) => _fiddleWith(child))?.toList() ?? []), + children: _fiddleWithChildren(node.children?.map((TextSpan child) => _fiddleWith(child))?.toList() ?? []), ); } diff --git a/packages/flutter/lib/painting.dart b/packages/flutter/lib/painting.dart index a22d5bf25c8..fe432cb9093 100644 --- a/packages/flutter/lib/painting.dart +++ b/packages/flutter/lib/painting.dart @@ -17,7 +17,7 @@ /// painting boxes. library painting; -export 'dart:ui' show Shadow, PlaceholderAlignment; +export 'dart:ui' show Shadow; export 'src/painting/alignment.dart'; export 'src/painting/basic_types.dart'; @@ -46,11 +46,9 @@ export 'src/painting/image_decoder.dart'; export 'src/painting/image_provider.dart'; export 'src/painting/image_resolution.dart'; export 'src/painting/image_stream.dart'; -export 'src/painting/inline_span.dart'; export 'src/painting/matrix_utils.dart'; export 'src/painting/notched_shapes.dart'; export 'src/painting/paint_utilities.dart'; -export 'src/painting/placeholder_span.dart'; export 'src/painting/rounded_rectangle_border.dart'; export 'src/painting/shader_warm_up.dart'; export 'src/painting/shape_decoration.dart'; diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart index 8eb322b8ba7..9d720aaf398 100644 --- a/packages/flutter/lib/src/material/time_picker.dart +++ b/packages/flutter/lib/src/material/time_picker.dart @@ -993,7 +993,6 @@ class _DialPainter extends CustomPainter { final double width = labelPainter.width * _semanticNodeSizeScale; final double height = labelPainter.height * _semanticNodeSizeScale; final Offset nodeOffset = getOffsetForTheta(labelTheta, ring) + Offset(-width / 2.0, -height / 2.0); - final TextSpan textSpan = labelPainter.text; final CustomPainterSemantics node = CustomPainterSemantics( rect: Rect.fromLTRB( nodeOffset.dx - 24.0 + width / 2, @@ -1004,7 +1003,7 @@ class _DialPainter extends CustomPainter { properties: SemanticsProperties( sortKey: OrdinalSortKey(i.toDouble() + ordinalOffset), selected: label.value == selectedValue, - value: textSpan?.text, + value: labelPainter.text.text, textDirection: textDirection, onTap: label.onTap, ), diff --git a/packages/flutter/lib/src/painting/inline_span.dart b/packages/flutter/lib/src/painting/inline_span.dart deleted file mode 100644 index ac09ded5008..00000000000 --- a/packages/flutter/lib/src/painting/inline_span.dart +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:ui' as ui show ParagraphBuilder; - -import 'package:flutter/foundation.dart'; - -import 'basic_types.dart'; -import 'text_painter.dart'; -import 'text_style.dart'; - -/// Mutable wrapper of an integer that can be passed by reference to track a -/// value across a recursive stack. -class Accumulator { - /// [Accumulator] may be initialized with a specified value, otherwise, it will - /// initialize to zero. - Accumulator([this._value = 0]); - - /// The integer stored in this [Accumulator]. - int get value => _value; - int _value; - - /// Increases the [value] by the `addend`. - void increment(int addend) { - assert(addend >= 0); - _value += addend; - } -} -/// Called on each span as [InlineSpan.visitChildren] walks the [InlineSpan] tree. -/// -/// Returns true when the walk should continue, and false to stop visiting further -/// [InlineSpan]s. -typedef InlineSpanVisitor = bool Function(InlineSpan span); - -/// An immutable span of inline content which forms part of a paragraph. -/// -/// * The subclass [TextSpan] specifies text and may contain child [InlineSpan]s. -/// * The subclass [PlaceholderSpan] represents a placeholder that may be -/// filled with non-text content. [PlaceholderSpan] itself defines a -/// [ui.PlaceholderAlignemnt] and a [TextBaseline]. To be useful, -/// [PlaceholderSpan] must be extended to define content. An instance of -/// this is the [WidgetSpan] class in the widgets library. -/// * The subclass [WidgetSpan] specifies embedded inline widgets. -/// -/// {@tool sample} -/// -/// This example shows a tree of [InlineSpan]s that make a query asking for a -/// name with a [TextField] embedded inline. -/// -/// ```dart -/// Text.rich( -/// TextSpan( -/// text: 'My name is ', -/// style: TextStyle(color: Colors.black), -/// children: [ -/// WidgetSpan( -/// alignment: PlaceholderAlignment.baseline, -/// baseline: TextBaseline.alphabetic, -/// child: ConstrainedBox( -/// constraints: BoxConstraints(maxWidth: 100), -/// child: TextField(), -/// ) -/// ), -/// TextSpan( -/// text: '.', -/// ), -/// ], -/// ), -/// ) -/// ``` -/// {@end-tool} -/// -/// See also: -/// -/// * [Text], a widget for showing uniformly-styled text. -/// * [RichText], a widget for finer control of text rendering. -/// * [TextPainter], a class for painting [InlineSpan] objects on a [Canvas]. -@immutable -abstract class InlineSpan extends DiagnosticableTree { - /// Creates an [InlineSpan] with the given values. - const InlineSpan({ - this.style, - }); - - /// The [TextStyle] to apply to this span. - /// - /// The [style] is also applied to any child spans when this is an instance - /// of [TextSpan]. - final TextStyle style; - - /// Apply the properties of this object to the given [ParagraphBuilder], from - /// which a [Paragraph] can be obtained. - /// - /// The `textScaleFactor` parameter specifies a scale that the text and - /// placeholders will be scaled by. The scaling is performed before layout, - /// so the text will be laid out with the scaled glyphs and placeholders. - /// - /// The `dimensions` parameter specifies the sizes of the placeholders. - /// Each [PlaceholderSpan] must be paired with a [PlaceholderDimensions] - /// in the same order as defined in the [InlineSpan] tree. - /// - /// [Paragraph] objects can be drawn on [Canvas] objects. - void build(ui.ParagraphBuilder builder, { double textScaleFactor = 1.0, List dimensions }); - - /// Walks this [InlineSpan] and any descendants in pre-order and calls `visitor` - /// for each span that has content. - /// - /// When `visitor` returns true, the walk will continue. When `visitor` returns - /// false, then the walk will end. - bool visitChildren(InlineSpanVisitor visitor); - - /// Returns the text span that contains the given position in the text. - InlineSpan getSpanForPosition(TextPosition position) { - assert(debugAssertIsValid()); - final Accumulator offset = Accumulator(); - InlineSpan result; - visitChildren((InlineSpan span) { - result = span.getSpanForPositionVisitor(position, offset); - return result == null; - }); - return result; - } - - /// Performs the check at each [InlineSpan] for if the `position` falls within the range - /// of the span and returns the span if it does. - /// - /// The `offset` parameter tracks the current index offset in the text buffer formed - /// if the contents of the [InlineSpan] tree were concatenated together starting - /// from the root [InlineSpan]. - /// - /// This method should not be directly called. Use [getSpanForPosition] instead. - @protected - InlineSpan getSpanForPositionVisitor(TextPosition position, Accumulator offset); - - /// Flattens the [InlineSpan] tree into a single string. - /// - /// Styles are not honored in this process. If `includeSemanticsLabels` is - /// true, then the text returned will include the [TextSpan.semanticsLabel]s - /// instead of the text contents for [TextSpan]s. - /// - /// When `includePlaceholders` is true, [PlaceholderSpan]s in the tree will be - /// represented as a 0xFFFC 'object replacement character'. - String toPlainText({bool includeSemanticsLabels = true, bool includePlaceholders = true}) { - final StringBuffer buffer = StringBuffer(); - computeToPlainText(buffer, includeSemanticsLabels: includeSemanticsLabels, includePlaceholders: includePlaceholders); - return buffer.toString(); - } - - /// Walks the [InlineSpan] tree and writes the plain text representation to `buffer`. - /// - /// This method should not be directly called. Use [toPlainText] instead. - /// - /// Styles are not honored in this process. If `includeSemanticsLabels` is - /// true, then the text returned will include the [TextSpan.semanticsLabel]s - /// instead of the text contents for [TextSpan]s. - /// - /// When `includePlaceholders` is true, [PlaceholderSpan]s in the tree will be - /// represented as a 0xFFFC 'object replacement character'. - /// - /// The plain-text representation of this [InlineSpan] is written into the `buffer`. - /// This method will then recursively call [computeToPlainText] on its childen - /// [InlineSpan]s if available. - @protected - void computeToPlainText(StringBuffer buffer, {bool includeSemanticsLabels = true, bool includePlaceholders = true}); - - /// Returns the UTF-16 code unit at the given `index` in the flattened string. - /// - /// This only accounts for the [TextSpan.text] values and ignores [PlaceholderSpans]. - /// - /// Returns null if the `index` is out of bounds. - int codeUnitAt(int index) { - if (index < 0) - return null; - final Accumulator offset = Accumulator(); - int result; - visitChildren((InlineSpan span) { - result = span.codeUnitAtVisitor(index, offset); - return result == null; - }); - return result; - } - - /// Performs the check at each [InlineSpan] for if the `index` falls within the range - /// of the span and returns the corresponding code unit. Returns null otherwise. - /// - /// The `offset` parameter tracks the current index offset in the text buffer formed - /// if the contents of the [InlineSpan] tree were concatenated together starting - /// from the root [InlineSpan]. - /// - /// This method should not be directly called. Use [codeUnitAt] instead. - @protected - int codeUnitAtVisitor(int index, Accumulator offset); - - /// Populates the `semanticsOffsets` and `semanticsElements` with the appropriate data - /// to be able to construct a [SemanticsNode]. - /// - /// If applicable, the beginning and end text offset are added to [semanticsOffsets]. - /// [PlaceholderSpan]s have a text length of 1, which corresponds to the object - /// replacement character (0xFFFC) that is inserted to represent it. - /// - /// Any [GestureRecognizer]s are added to `semanticsElements`. Null is added to - /// `semanticsElements` for [PlaceholderSpan]s. - void describeSemantics(Accumulator offset, List semanticsOffsets, List semanticsElements); - - /// In checked mode, throws an exception if the object is not in a - /// valid configuration. Otherwise, returns true. - /// - /// This is intended to be used as follows: - /// - /// ```dart - /// assert(myInlineSpan.debugAssertIsValid()); - /// ``` - bool debugAssertIsValid() => true; - - /// Describe the difference between this span and another, in terms of - /// how much damage it will make to the rendering. The comparison is deep. - /// - /// Comparing [InlineSpan] objects of different types, for example, comparing - /// a [TextSpan] to a [WidgetSpan], always results in [RenderComparison.layout]. - /// - /// See also: - /// - /// * [TextStyle.compareTo], which does the same thing for [TextStyle]s. - RenderComparison compareTo(InlineSpan other); - - @override - bool operator ==(dynamic other) { - if (identical(this, other)) - return true; - if (other.runtimeType != runtimeType) - return false; - final InlineSpan typedOther = other; - return typedOther.style == style; - } - - @override - int get hashCode => style.hashCode; - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.whitespace; - - if (style != null) { - style.debugFillProperties(properties); - } - } -} diff --git a/packages/flutter/lib/src/painting/placeholder_span.dart b/packages/flutter/lib/src/painting/placeholder_span.dart deleted file mode 100644 index e052ea68255..00000000000 --- a/packages/flutter/lib/src/painting/placeholder_span.dart +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:ui' as ui show PlaceholderAlignment; - -import 'package:flutter/foundation.dart'; - -import 'basic_types.dart'; -import 'inline_span.dart'; -import 'text_painter.dart'; -import 'text_span.dart'; -import 'text_style.dart'; - -/// An immutable placeholder that is embedded inline within text. -/// -/// [PlaceholderSpan] represents a placeholder that acts as a stand-in for other -/// content. A [PlaceholderSpan] by itself does not contain useful -/// information to change a [TextSpan]. Instead, this class must be extended -/// to define contents. -/// -/// [WidgetSpan] from the widgets library extends [PlaceholderSpan] and may be -/// used instead to specify a widget as the contents of the placeholder. -/// -/// See also: -/// -/// * [WidgetSpan], a leaf node that represents an embedded inline widget. -/// * [TextSpan], a node that represents text in a [TextSpan] tree. -/// * [Text], a widget for showing uniformly-styled text. -/// * [RichText], a widget for finer control of text rendering. -/// * [TextPainter], a class for painting [TextSpan] objects on a [Canvas]. -abstract class PlaceholderSpan extends InlineSpan { - /// Creates a [PlaceholderSpan] with the given values. - /// - /// A [TextStyle] may be provided with the [style] property, but only the - /// decoration, foreground, background, and spacing options will be used. - const PlaceholderSpan({ - this.alignment = ui.PlaceholderAlignment.bottom, - this.baseline, - TextStyle style, - }) : super(style: style,); - - /// How the placeholder aligns vertically with the text. - /// - /// See [ui.PlaceholderAlignment] for details on each mode. - final ui.PlaceholderAlignment alignment; - - /// The [TextBaseline] to align against when using [ui.PlaceholderAlignment.baseline], - /// [ui.PlaceholderAlignment.aboveBaseline], and [ui.PlaceholderAlignment.belowBaseline]. - /// - /// This is ignored when using other alignment modes. - final TextBaseline baseline; - - /// [PlaceholderSpan]s are flattened to a `0xFFFC` object replacement character in the - /// plain text representation when `includePlaceholders` is true. - @override - void computeToPlainText(StringBuffer buffer, {bool includeSemanticsLabels = true, bool includePlaceholders = true}) { - if (includePlaceholders) { - buffer.write('\uFFFC'); - } - } - - /// Populates the `semanticsOffsets` and `semanticsElements` with the appropriate data - /// to be able to construct a [SemanticsNode]. - /// - /// [PlaceholderSpan]s have a text length of 1, which corresponds to the object - /// replacement character (0xFFFC) that is inserted to represent it. - /// - /// Null is added to `semanticsElements` for [PlaceholderSpan]s. - @override - void describeSemantics(Accumulator offset, List semanticsOffsets, List semanticsElements) { - semanticsOffsets.add(offset.value); - semanticsOffsets.add(offset.value + 1); - semanticsElements.add(null); // null indicates this is a placeholder. - offset.increment(1); - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - - properties.add(EnumProperty('alignment', alignment, defaultValue: null)); - properties.add(EnumProperty('baseline', baseline, defaultValue: null)); - } -} diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart index 0c619592c94..b4f20cb15a3 100644 --- a/packages/flutter/lib/src/painting/text_painter.dart +++ b/packages/flutter/lib/src/painting/text_painter.dart @@ -3,82 +3,18 @@ // found in the LICENSE file. import 'dart:math' show min, max; -import 'dart:ui' as ui show Paragraph, ParagraphBuilder, ParagraphConstraints, ParagraphStyle, PlaceholderAlignment; +import 'dart:ui' as ui show Paragraph, ParagraphBuilder, ParagraphConstraints, ParagraphStyle; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'basic_types.dart'; -import 'inline_span.dart'; -import 'placeholder_span.dart'; import 'strut_style.dart'; import 'text_span.dart'; export 'package:flutter/services.dart' show TextRange, TextSelection; -/// Holds the [Size] and baseline required to represent the dimensions of -/// a placeholder in text. -/// -/// Placeholders specify an empty space in the text layout, which is used -/// to later render arbitrary inline widgets into defined by a [WidgetSpan]. -/// -/// The [size] and [alignment] properties are required and cannot be null. -/// -/// See also: -/// -/// * [WidgetSpan], a subclass of [InlineSpan] and [PlaceholderSpan] that -/// represents an inline widget embedded within text. The space this -/// widget takes is indicated by a placeholder. -/// * [RichText], a text widget that supports text inline widgets. -@immutable -class PlaceholderDimensions { - /// Constructs a [PlaceholderDimensions] with the specified parameters. - /// - /// The `size` and `alignment` are required as a placeholder's dimensions - /// require at least `size` and `alignment` to be fully defined. - const PlaceholderDimensions({ - @required this.size, - @required this.alignment, - this.baseline, - this.baselineOffset, - }) : assert(size != null), - assert(alignment != null); - - /// Width and height dimensions of the placeholder. - final Size size; - - /// How to align the placeholder with the text. - /// - /// See also: - /// - /// * [baseline], the baseline to align to when using - /// [ui.PlaceholderAlignment.baseline], - /// [ui.PlaceholderAlignment.aboveBaseline], - /// or [ui.PlaceholderAlignment.underBaseline]. - /// * [baselineOffset], the distance of the alphabetic baseline from the upper - /// edge of the placeholder. - final ui.PlaceholderAlignment alignment; - - /// Distance of the [baseline] from the upper edge of the placeholder. - /// - /// Only used when [alignment] is [ui.PlaceholderAlignment.baseline]. - final double baselineOffset; - - /// The [TextBaseline] to align to. Used with: - /// - /// * [ui.PlaceholderAlignment.baseline] - /// * [ui.PlaceholderAlignment.aboveBaseline] - /// * [ui.PlaceholderAlignment.underBaseline] - /// * [ui.PlaceholderAlignment.middle] - final TextBaseline baseline; - - @override - String toString() { - return 'PlaceholderDimensions($size, $baseline)'; - } -} - /// The different ways of considering the width of one or more lines of text. /// /// See [Text.widthType]. @@ -94,9 +30,6 @@ enum TextWidthBasis { longestLine, } -/// This is used to cache and pass the computed metrics regarding the -/// caret's size and position. This is preferred due to the expensive -/// nature of the calculation. class _CaretMetrics { const _CaretMetrics({this.offset, this.fullHeight}); /// The offset of the top left corner of the caret from the top left @@ -134,7 +67,7 @@ class TextPainter { /// /// The [maxLines] property, if non-null, must be greater than zero. TextPainter({ - InlineSpan text, + TextSpan text, TextAlign textAlign = TextAlign.start, TextDirection textDirection, double textScaleFactor = 1.0, @@ -166,9 +99,9 @@ class TextPainter { /// After this is set, you must call [layout] before the next call to [paint]. /// /// This and [textDirection] must be non-null before you call [layout]. - InlineSpan get text => _text; - InlineSpan _text; - set text(InlineSpan value) { + TextSpan get text => _text; + TextSpan _text; + set text(TextSpan value) { assert(value == null || value.debugAssertIsValid()); if (_text == value) return; @@ -333,53 +266,6 @@ class TextPainter { ui.Paragraph _layoutTemplate; - /// An ordered list of [TextBox]es that bound the positions of the placeholders - /// in the paragraph. - /// - /// Each box corresponds to a [PlaceholderSpan] in the order they were defined - /// in the [InlineSpan] tree. - List get inlinePlaceholderBoxes => _inlinePlaceholderBoxes; - List _inlinePlaceholderBoxes; - - /// An ordered list of scales for each placeholder in the paragraph. - /// - /// The scale is used as a multiplier on the height, width and baselineOffset of - /// the placeholder. Scale is primarily used to handle accessibility scaling. - /// - /// Each scale corresponds to a [PlaceholderSpan] in the order they were defined - /// in the [InlineSpan] tree. - List get inlinePlaceholderScales => _inlinePlaceholderScales; - List _inlinePlaceholderScales; - - /// Sets the dimensions of each placeholder in [text]. - /// - /// The number of [PlaceholderDimensions] provided should be the same as the - /// number of [PlaceholderSpan]s in text. Passing in an empty or null `value` - /// will do nothing. - /// - /// If [layout] is attempted without setting the placeholder dimensions, the - /// placeholders will be ignored in the text layout and no valid - /// [inlinePlaceholderBoxes] will be returned. - void setPlaceholderDimensions(List value) { - if (value == null || value.isEmpty || listEquals(value, _placeholderDimensions)) { - return; - } - assert(() { - int placeholderCount = 0; - text.visitChildren((InlineSpan span) { - if (span is PlaceholderSpan) { - placeholderCount += 1; - } - return true; - }); - return placeholderCount; - }() == value.length); - _placeholderDimensions = value; - _needsLayout = true; - _paragraph = null; - } - List _placeholderDimensions; - ui.ParagraphStyle _createParagraphStyle([ TextDirection defaultTextDirection ]) { // The defaultTextDirection argument is used for preferredLineHeight in case // textDirection hasn't yet been set. @@ -533,8 +419,7 @@ class TextPainter { _needsLayout = false; if (_paragraph == null) { final ui.ParagraphBuilder builder = ui.ParagraphBuilder(_createParagraphStyle()); - _text.build(builder, textScaleFactor: textScaleFactor, dimensions: _placeholderDimensions); - _inlinePlaceholderScales = builder.placeholderScales; + _text.build(builder, textScaleFactor: textScaleFactor); _paragraph = builder.build(); } _lastMinWidth = minWidth; @@ -542,11 +427,9 @@ class TextPainter { _paragraph.layout(ui.ParagraphConstraints(width: maxWidth)); if (minWidth != maxWidth) { final double newWidth = maxIntrinsicWidth.clamp(minWidth, maxWidth); - if (newWidth != width) { + if (newWidth != width) _paragraph.layout(ui.ParagraphConstraints(width: newWidth)); - } } - _inlinePlaceholderBoxes = _paragraph.getBoxesForPlaceholders(); } /// Paints the text onto the given canvas at the given offset. @@ -608,7 +491,7 @@ class TextPainter { // TODO(garyq): Use actual extended grapheme cluster length instead of // an increasing cluster length amount to achieve deterministic performance. Rect _getRectFromUpstream(int offset, Rect caretPrototype) { - final String flattenedText = _text.toPlainText(includePlaceholders: false); + final String flattenedText = _text.toPlainText(); final int prevCodeUnit = _text.codeUnitAt(max(0, offset - 1)); if (prevCodeUnit == null) return null; @@ -624,12 +507,10 @@ class TextPainter { if (boxes.isEmpty) { // When we are at the beginning of the line, a non-surrogate position will // return empty boxes. We break and try from downstream instead. - if (!needsSearch) { + if (!needsSearch) break; // Only perform one iteration if no search is required. - } - if (prevRuneOffset < -flattenedText.length) { + if (prevRuneOffset < -flattenedText.length) break; // Stop iterating when beyond the max length of the text. - } // Multiply by two to log(n) time cover the entire text span. This allows // faster discovery of very long clusters and reduces the possibility // of certain large clusters taking much longer than others, which can @@ -657,7 +538,7 @@ class TextPainter { // TODO(garyq): Use actual extended grapheme cluster length instead of // an increasing cluster length amount to achieve deterministic performance. Rect _getRectFromDownstream(int offset, Rect caretPrototype) { - final String flattenedText = _text.toPlainText(includePlaceholders: false); + final String flattenedText = _text.toPlainText(); // We cap the offset at the final index of the _text. final int nextCodeUnit = _text.codeUnitAt(min(offset, flattenedText == null ? 0 : flattenedText.length - 1)); if (nextCodeUnit == null) @@ -673,12 +554,10 @@ class TextPainter { if (boxes.isEmpty) { // When we are at the end of the line, a non-surrogate position will // return empty boxes. We break and try from upstream instead. - if (!needsSearch) { + if (!needsSearch) break; // Only perform one iteration if no search is required. - } - if (nextRuneOffset >= flattenedText.length << 1) { + if (nextRuneOffset >= flattenedText.length << 1) break; // Stop iterating when beyond the max length of the text. - } // Multiply by two to log(n) time cover the entire text span. This allows // faster discovery of very long clusters and reduces the possibility // of certain large clusters taking much longer than others, which can diff --git a/packages/flutter/lib/src/painting/text_span.dart b/packages/flutter/lib/src/painting/text_span.dart index 31baf09f602..bba61a7d093 100644 --- a/packages/flutter/lib/src/painting/text_span.dart +++ b/packages/flutter/lib/src/painting/text_span.dart @@ -9,8 +9,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'basic_types.dart'; -import 'inline_span.dart'; -import 'text_painter.dart'; import 'text_style.dart'; /// An immutable span of text. @@ -23,9 +21,7 @@ import 'text_style.dart'; /// only partially) override the [style] of this object. If a /// [TextSpan] has both [text] and [children], then the [text] is /// treated as if it was an unstyled [TextSpan] at the start of the -/// [children] list. Leaving the [TextSpan.text] field null results -/// in the [TextSpan] acting as an empty node in the [InlineSpan] -/// tree with a list of children. +/// [children] list. /// /// To paint a [TextSpan] on a [Canvas], use a [TextPainter]. To display a text /// span in a widget, use a [RichText]. For text with a single style, consider @@ -46,33 +42,27 @@ import 'text_style.dart'; /// _There is some more detailed sample code in the documentation for the /// [recognizer] property._ /// -/// The [TextSpan.text] will be used as the semantics label unless overriden -/// by the [TextSpan.semanticsLabel] property. Any [PlaceholderSpan]s in the -/// [TextSpan.children] list will separate the text before and after it into -/// two semantics nodes. -/// /// See also: /// -/// * [WidgetSpan], a leaf node that represents an embedded inline widget -/// in an [InlineSpan] tree. Specify a widget within the [children] -/// list by wrapping the widget with a [WidgetSpan]. The widget will be -/// laid out inline within the paragraph. /// * [Text], a widget for showing uniformly-styled text. /// * [RichText], a widget for finer control of text rendering. /// * [TextPainter], a class for painting [TextSpan] objects on a [Canvas]. @immutable -class TextSpan extends InlineSpan { +class TextSpan extends DiagnosticableTree { /// Creates a [TextSpan] with the given values. /// /// For the object to be useful, at least one of [text] or /// [children] should be set. const TextSpan({ + this.style, this.text, this.children, - TextStyle style, this.recognizer, this.semanticsLabel, - }) : super(style: style,); + }); + + /// The style to apply to the [text] and the [children]. + final TextStyle style; /// The text contained in the span. /// @@ -89,26 +79,26 @@ class TextSpan extends InlineSpan { /// supported and may have unexpected results. /// /// The list must not contain any nulls. - final List children; + final List children; - /// A gesture recognizer that will receive events that hit this span. + /// A gesture recognizer that will receive events that hit this text span. /// - /// [InlineSpan] itself does not implement hit testing or event dispatch. The - /// object that manages the [InlineSpan] painting is also responsible for + /// [TextSpan] itself does not implement hit testing or event dispatch. The + /// object that manages the [TextSpan] painting is also responsible for /// dispatching events. In the rendering library, that is the /// [RenderParagraph] object, which corresponds to the [RichText] widget in - /// the widgets layer; these objects do not bubble events in [InlineSpan]s, so a + /// the widgets layer; these objects do not bubble events in [TextSpan]s, so a /// [recognizer] is only effective for events that directly hit the [text] of - /// that [InlineSpan], not any of its [children]. + /// that [TextSpan], not any of its [children]. /// - /// [InlineSpan] also does not manage the lifetime of the gesture recognizer. + /// [TextSpan] also does not manage the lifetime of the gesture recognizer. /// The code that owns the [GestureRecognizer] object must call - /// [GestureRecognizer.dispose] when the [InlineSpan] object is no longer used. + /// [GestureRecognizer.dispose] when the [TextSpan] object is no longer used. /// /// {@tool sample} /// /// This example shows how to manage the lifetime of a gesture recognizer - /// provided to an [InlineSpan] object. It defines a `BuzzingText` widget which + /// provided to a [TextSpan] object. It defines a `BuzzingText` widget which /// uses the [HapticFeedback] class to vibrate the device when the user /// long-presses the "find the" span, which is underlined in wavy green. The /// hit-testing is handled by the [RichText] widget. @@ -141,11 +131,11 @@ class TextSpan extends InlineSpan { /// /// @override /// Widget build(BuildContext context) { - /// return Text.rich( - /// TextSpan( + /// return RichText( + /// text: TextSpan( /// text: 'Can you ', /// style: TextStyle(color: Colors.black), - /// children: [ + /// children: [ /// TextSpan( /// text: 'find the', /// style: TextStyle( @@ -167,7 +157,7 @@ class TextSpan extends InlineSpan { /// {@end-tool} final GestureRecognizer recognizer; - /// An alternative semantics label for this [TextSpan]. + /// An alternative semantics label for this text. /// /// If present, the semantics of this span will contain this value instead /// of the actual text. @@ -187,8 +177,7 @@ class TextSpan extends InlineSpan { /// Rather than using this directly, it's simpler to use the /// [TextPainter] class to paint [TextSpan] objects onto [Canvas] /// objects. - @override - void build(ui.ParagraphBuilder builder, { double textScaleFactor = 1.0, List dimensions }) { + void build(ui.ParagraphBuilder builder, { double textScaleFactor = 1.0 }) { assert(debugAssertIsValid()); final bool hasStyle = style != null; if (hasStyle) @@ -196,9 +185,9 @@ class TextSpan extends InlineSpan { if (text != null) builder.addText(text); if (children != null) { - for (InlineSpan child in children) { + for (TextSpan child in children) { assert(child != null); - child.build(builder, textScaleFactor: textScaleFactor, dimensions: dimensions); + child.build(builder, textScaleFactor: textScaleFactor); } } if (hasStyle) @@ -207,15 +196,14 @@ class TextSpan extends InlineSpan { /// Walks this text span and its descendants in pre-order and calls [visitor] /// for each span that has text. - @override - bool visitChildren(InlineSpanVisitor visitor) { + bool visitTextSpan(bool visitor(TextSpan span)) { if (text != null) { if (!visitor(this)) return false; } if (children != null) { - for (InlineSpan child in children) { - if (!child.visitChildren(visitor)) + for (TextSpan child in children) { + if (!child.visitTextSpan(visitor)) return false; } } @@ -223,62 +211,63 @@ class TextSpan extends InlineSpan { } /// Returns the text span that contains the given position in the text. - @override - InlineSpan getSpanForPositionVisitor(TextPosition position, Accumulator offset) { - if (text == null) { - return null; - } + TextSpan getSpanForPosition(TextPosition position) { + assert(debugAssertIsValid()); final TextAffinity affinity = position.affinity; final int targetOffset = position.offset; - final int endOffset = offset.value + text.length; - if (offset.value == targetOffset && affinity == TextAffinity.downstream || - offset.value < targetOffset && targetOffset < endOffset || - endOffset == targetOffset && affinity == TextAffinity.upstream) { - return this; - } - offset.increment(text.length); - return null; - } - - @override - void computeToPlainText(StringBuffer buffer, {bool includeSemanticsLabels = true, bool includePlaceholders = true}) { - assert(debugAssertIsValid()); - if (semanticsLabel != null && includeSemanticsLabels) { - buffer.write(semanticsLabel); - } else if (text != null) { - buffer.write(text); - } - if (children != null) { - for (InlineSpan child in children) { - child.computeToPlainText(buffer, - includeSemanticsLabels: includeSemanticsLabels, - includePlaceholders: includePlaceholders, - ); + int offset = 0; + TextSpan result; + visitTextSpan((TextSpan span) { + assert(result == null); + final int endOffset = offset + span.text.length; + if (targetOffset == offset && affinity == TextAffinity.downstream || + targetOffset > offset && targetOffset < endOffset || + targetOffset == endOffset && affinity == TextAffinity.upstream) { + result = span; + return false; } - } + offset = endOffset; + return true; + }); + return result; } - @override - int codeUnitAtVisitor(int index, Accumulator offset) { - if (text == null) { + /// Flattens the [TextSpan] tree into a single string. + /// + /// Styles are not honored in this process. If `includeSemanticsLabels` is + /// 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()); + final StringBuffer buffer = StringBuffer(); + visitTextSpan((TextSpan span) { + if (span.semanticsLabel != null && includeSemanticsLabels) { + buffer.write(span.semanticsLabel); + } else { + buffer.write(span.text); + } + return true; + }); + return buffer.toString(); + } + + /// Returns the UTF-16 code unit at the given index in the flattened string. + /// + /// Returns null if the index is out of bounds. + int codeUnitAt(int index) { + if (index < 0) return null; - } - if (index - offset.value < text.length) { - return text.codeUnitAt(index - offset.value); - } - offset.increment(text.length); - return null; - } - - @override - void describeSemantics(Accumulator offset, List semanticsOffsets, List semanticsElements) { - if (recognizer != null && (recognizer is TapGestureRecognizer || recognizer is LongPressGestureRecognizer)) { - final int length = semanticsLabel?.length ?? text.length; - semanticsOffsets.add(offset.value); - semanticsOffsets.add(offset.value + length); - semanticsElements.add(recognizer); - } - offset.increment(text != null ? text.length : 0); + int offset = 0; + int result; + visitTextSpan((TextSpan span) { + if (index - offset < span.text.length) { + result = span.text.codeUnitAt(index - offset); + return false; + } + offset += span.text.length; + return true; + }); + return result; } /// In checked mode, throws an exception if the object is not in a @@ -289,39 +278,45 @@ class TextSpan extends InlineSpan { /// ```dart /// assert(myTextSpan.debugAssertIsValid()); /// ``` - @override bool debugAssertIsValid() { assert(() { - if (children != null) { - for (InlineSpan child in children) { - assert(child != null, - 'TextSpan contains a null child.\n...' - 'A TextSpan object with a non-null child list should not have any nulls in its child list.\n' - 'The full text in question was:\n' - '${toStringDeep(prefixLineOne: ' ')}' - ); - assert(child.debugAssertIsValid()); + if (!visitTextSpan((TextSpan span) { + if (span.children != null) { + for (TextSpan child in span.children) { + if (child == null) + return false; + } } + return true; + })) { + throw FlutterError( + 'TextSpan contains a null child.\n' + 'A TextSpan object with a non-null child list should not have any nulls in its child list.\n' + 'The full text in question was:\n' + '${toStringDeep(prefixLineOne: ' ')}' + ); } return true; }()); - return super.debugAssertIsValid(); + return true; } - @override - RenderComparison compareTo(InlineSpan other) { + /// Describe the difference between this text span and another, in terms of + /// how much damage it will make to the rendering. The comparison is deep. + /// + /// See also: + /// + /// * [TextStyle.compareTo], which does the same thing for [TextStyle]s. + RenderComparison compareTo(TextSpan other) { if (identical(this, other)) return RenderComparison.identical; - if (other.runtimeType != runtimeType) + if (other.text != text || + children?.length != other.children?.length || + (style == null) != (other.style == null)) return RenderComparison.layout; - final TextSpan textSpan = other; - if (textSpan.text != text || - children?.length != textSpan.children?.length || - (style == null) != (textSpan.style == null)) - return RenderComparison.layout; - RenderComparison result = recognizer == textSpan.recognizer ? RenderComparison.identical : RenderComparison.metadata; + RenderComparison result = recognizer == other.recognizer ? RenderComparison.identical : RenderComparison.metadata; if (style != null) { - final RenderComparison candidate = style.compareTo(textSpan.style); + final RenderComparison candidate = style.compareTo(other.style); if (candidate.index > result.index) result = candidate; if (result == RenderComparison.layout) @@ -329,7 +324,7 @@ class TextSpan extends InlineSpan { } if (children != null) { for (int index = 0; index < children.length; index += 1) { - final RenderComparison candidate = children[index].compareTo(textSpan.children[index]); + final RenderComparison candidate = children[index].compareTo(other.children[index]); if (candidate.index > result.index) result = candidate; if (result == RenderComparison.layout) @@ -345,17 +340,16 @@ class TextSpan extends InlineSpan { return true; if (other.runtimeType != runtimeType) return false; - if (super != other) - return false; final TextSpan typedOther = other; return typedOther.text == text + && typedOther.style == style && typedOther.recognizer == recognizer && typedOther.semanticsLabel == semanticsLabel - && listEquals(typedOther.children, children); + && listEquals(typedOther.children, children); } @override - int get hashCode => hashValues(super.hashCode, text, recognizer, semanticsLabel, hashList(children)); + int get hashCode => hashValues(style, text, recognizer, semanticsLabel, hashList(children)); @override String toStringShort() => '$runtimeType'; @@ -363,10 +357,11 @@ class TextSpan extends InlineSpan { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - - properties.add(StringProperty('text', text, showName: false, defaultValue: null)); - if (style == null && text == null && children == null) - properties.add(DiagnosticsNode.message('(empty)')); + properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.whitespace; + // Properties on style are added as if they were properties directly on + // this TextSpan. + if (style != null) + style.debugFillProperties(properties); properties.add(DiagnosticsProperty( 'recognizer', recognizer, @@ -374,16 +369,22 @@ class TextSpan extends InlineSpan { defaultValue: null, )); + if (semanticsLabel != null) { properties.add(StringProperty('semanticsLabel', semanticsLabel)); } + + + properties.add(StringProperty('text', text, showName: false, defaultValue: null)); + if (style == null && text == null && children == null) + properties.add(DiagnosticsNode.message('(empty)')); } @override List debugDescribeChildren() { if (children == null) return const []; - return children.map((InlineSpan child) { + return children.map((TextSpan child) { if (child != null) { return child.toDiagnosticsNode(); } else { diff --git a/packages/flutter/lib/src/rendering/box.dart b/packages/flutter/lib/src/rendering/box.dart index bcff74ce8f7..0b829471f31 100644 --- a/packages/flutter/lib/src/rendering/box.dart +++ b/packages/flutter/lib/src/rendering/box.dart @@ -1728,10 +1728,7 @@ abstract class RenderBox extends RenderObject { return true; }()); _size = value; - assert(() { - debugAssertDoesMeetConstraints(); - return true; - }()); + assert(() { debugAssertDoesMeetConstraints(); return true; }()); } /// Claims ownership of the given [Size]. diff --git a/packages/flutter/lib/src/rendering/debug_overflow_indicator.dart b/packages/flutter/lib/src/rendering/debug_overflow_indicator.dart index be8790a31da..d7a0d6d0f25 100644 --- a/packages/flutter/lib/src/rendering/debug_overflow_indicator.dart +++ b/packages/flutter/lib/src/rendering/debug_overflow_indicator.dart @@ -284,8 +284,8 @@ mixin DebugOverflowIndicatorMixin on RenderObject { final List<_OverflowRegionData> overflowRegions = _calculateOverflowRegions(overflow, containerRect); for (_OverflowRegionData region in overflowRegions) { context.canvas.drawRect(region.rect.shift(offset), _indicatorPaint); - final TextSpan textSpan = _indicatorLabel[region.side.index].text; - if (textSpan?.text != region.label) { + + if (_indicatorLabel[region.side.index].text?.text != region.label) { _indicatorLabel[region.side.index].text = TextSpan( text: region.label, style: _indicatorTextStyle, diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart index b0f44bb2ef3..151f0adfe32 100644 --- a/packages/flutter/lib/src/rendering/paragraph.dart +++ b/packages/flutter/lib/src/rendering/paragraph.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ui' as ui show Gradient, Shader, TextBox, PlaceholderAlignment; +import 'dart:ui' as ui show Gradient, Shader, TextBox; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -10,7 +10,6 @@ import 'package:flutter/painting.dart'; import 'package:flutter/semantics.dart'; import 'package:flutter/services.dart'; -import 'package:vector_math/vector_math_64.dart'; import 'box.dart'; import 'debug.dart'; @@ -36,27 +35,8 @@ enum TextOverflow { const String _kEllipsis = '\u2026'; -/// Parent data for use with [RenderParagraph]. -class TextParentData extends ContainerBoxParentData { - /// The scaling of the text. - double scale; - - @override - String toString() { - final List values = []; - if (offset != null) - values.add('offset=$offset'); - if (scale != null) - values.add('scale=$scale'); - values.add(super.toString()); - return values.join('; '); - } -} - -/// A render object that displays a paragraph of text. -class RenderParagraph extends RenderBox - with ContainerRenderObjectMixin, - RenderBoxContainerDefaultsMixin { +/// A render object that displays a paragraph of text +class RenderParagraph extends RenderBox { /// Creates a paragraph render object. /// /// The [text], [textAlign], [textDirection], [overflow], [softWrap], and @@ -64,7 +44,8 @@ class RenderParagraph extends RenderBox /// /// The [maxLines] property may be null (and indeed defaults to null), but if /// it is not null, it must be greater than zero. - RenderParagraph(InlineSpan text, { + RenderParagraph( + TextSpan text, { TextAlign textAlign = TextAlign.start, @required TextDirection textDirection, bool softWrap = true, @@ -74,7 +55,6 @@ class RenderParagraph extends RenderBox TextWidthBasis textWidthBasis = TextWidthBasis.parent, Locale locale, StrutStyle strutStyle, - List children, }) : assert(text != null), assert(text.debugAssertIsValid()), assert(textAlign != null), @@ -96,22 +76,13 @@ class RenderParagraph extends RenderBox locale: locale, strutStyle: strutStyle, textWidthBasis: textWidthBasis, - ) { - addAll(children); - _extractPlaceholderSpans(text); - } - - @override - void setupParentData(RenderBox child) { - if (child.parentData is! TextParentData) - child.parentData = TextParentData(); - } + ); final TextPainter _textPainter; /// The text to display - InlineSpan get text => _textPainter.text; - set text(InlineSpan value) { + TextSpan get text => _textPainter.text; + set text(TextSpan value) { assert(value != null); switch (_textPainter.text.compareTo(value)) { case RenderComparison.identical: @@ -119,31 +90,17 @@ class RenderParagraph extends RenderBox return; case RenderComparison.paint: _textPainter.text = value; - _extractPlaceholderSpans(value); markNeedsPaint(); markNeedsSemanticsUpdate(); break; case RenderComparison.layout: _textPainter.text = value; _overflowShader = null; - _extractPlaceholderSpans(value); markNeedsLayout(); break; } } - List _placeholderSpans; - void _extractPlaceholderSpans(InlineSpan span) { - _placeholderSpans = []; - span.visitChildren((InlineSpan span) { - if (span is PlaceholderSpan) { - final PlaceholderSpan placeholderSpan = span; - _placeholderSpans.add(placeholderSpan); - } - return true; - }); - } - /// How the text should be aligned horizontally. TextAlign get textAlign => _textPainter.textAlign; set textAlign(TextAlign value) { @@ -272,31 +229,28 @@ class RenderParagraph extends RenderBox markNeedsLayout(); } + void _layoutText({ double minWidth = 0.0, double maxWidth = double.infinity }) { + final bool widthMatters = softWrap || overflow == TextOverflow.ellipsis; + _textPainter.layout(minWidth: minWidth, maxWidth: widthMatters ? maxWidth : double.infinity); + } + + void _layoutTextWithConstraints(BoxConstraints constraints) { + _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth); + } + @override double computeMinIntrinsicWidth(double height) { - if (!_canComputeIntrinsics()) { - return 0.0; - } - _computeChildrenWidthWithMinIntrinsics(height); - _layoutText(); // layout with infinite width. + _layoutText(); return _textPainter.minIntrinsicWidth; } @override double computeMaxIntrinsicWidth(double height) { - if (!_canComputeIntrinsics()) { - return 0.0; - } - _computeChildrenWidthWithMaxIntrinsics(height); - _layoutText(); // layout with infinite width. + _layoutText(); return _textPainter.maxIntrinsicWidth; } double _computeIntrinsicHeight(double width) { - if (!_canComputeIntrinsics()) { - return 0.0; - } - _computeChildrenHeightWithMinIntrinsics(width); _layoutText(minWidth: width, maxWidth: width); return _textPainter.height; } @@ -320,114 +274,9 @@ class RenderParagraph extends RenderBox return _textPainter.computeDistanceToActualBaseline(baseline); } - // Intrinsics cannot be calculated without a full layout for - // alignments that require the baseline (baseline, aboveBaseline, - // belowBaseline). - bool _canComputeIntrinsics() { - for (PlaceholderSpan span in _placeholderSpans) { - switch (span.alignment) { - case ui.PlaceholderAlignment.baseline: - case ui.PlaceholderAlignment.aboveBaseline: - case ui.PlaceholderAlignment.belowBaseline: { - assert(RenderObject.debugCheckingIntrinsics, - 'Intrinsics are not available for PlaceholderAlignment.baseline, ' - 'PlaceholderAlignment.aboveBaseline, or PlaceholderAlignment.belowBaseline,'); - return false; - } - case ui.PlaceholderAlignment.top: - case ui.PlaceholderAlignment.middle: - case ui.PlaceholderAlignment.bottom: { - continue; - } - } - } - return true; - } - - void _computeChildrenWidthWithMaxIntrinsics(double height) { - RenderBox child = firstChild; - final List placeholderDimensions = List(childCount); - int childIndex = 0; - while (child != null) { - // Height and baseline is irrelevant as all text will be laid - // out in a single line. - placeholderDimensions[childIndex] = PlaceholderDimensions( - size: Size(child.getMaxIntrinsicWidth(height), height), - alignment: _placeholderSpans[childIndex].alignment, - baseline: _placeholderSpans[childIndex].baseline, - ); - child = childAfter(child); - childIndex += 1; - } - _textPainter.setPlaceholderDimensions(placeholderDimensions); - } - - void _computeChildrenWidthWithMinIntrinsics(double height) { - RenderBox child = firstChild; - final List placeholderDimensions = List(childCount); - int childIndex = 0; - while (child != null) { - final double intrinsicWidth = child.getMinIntrinsicWidth(height); - final double intrinsicHeight = child.getMinIntrinsicHeight(intrinsicWidth); - placeholderDimensions[childIndex] = PlaceholderDimensions( - size: Size(intrinsicWidth, intrinsicHeight), - alignment: _placeholderSpans[childIndex].alignment, - baseline: _placeholderSpans[childIndex].baseline, - ); - child = childAfter(child); - childIndex += 1; - } - _textPainter.setPlaceholderDimensions(placeholderDimensions); - } - - void _computeChildrenHeightWithMinIntrinsics(double width) { - RenderBox child = firstChild; - final List placeholderDimensions = List(childCount); - int childIndex = 0; - while (child != null) { - final double intrinsicHeight = child.getMinIntrinsicHeight(width); - final double intrinsicWidth = child.getMinIntrinsicWidth(intrinsicHeight); - placeholderDimensions[childIndex] = PlaceholderDimensions( - size: Size(intrinsicWidth, intrinsicHeight), - alignment: _placeholderSpans[childIndex].alignment, - baseline: _placeholderSpans[childIndex].baseline, - ); - child = childAfter(child); - childIndex += 1; - } - _textPainter.setPlaceholderDimensions(placeholderDimensions); - } - @override bool hitTestSelf(Offset position) => true; - @override - bool hitTestChildren(BoxHitTestResult result, { Offset position }) { - RenderBox child = firstChild; - while (child != null) { - final TextParentData textParentData = child.parentData; - final Matrix4 transform = Matrix4.translationValues(textParentData.offset.dx, textParentData.offset.dy, 0.0) - ..scale(textParentData.scale, textParentData.scale, textParentData.scale); - final bool isHit = result.addWithPaintTransform( - transform: transform, - position: position, - hitTest: (BoxHitTestResult result, Offset transformed) { - assert(() { - final Offset manualPosition = (position - textParentData.offset) / textParentData.scale; - return (transformed.dx - manualPosition.dx).abs() < precisionErrorTolerance - && (transformed.dy - manualPosition.dy).abs() < precisionErrorTolerance; - }()); - return child.hitTest(result, position: transformed); - }, - ); - if (isHit) { - return true; - } - child = childAfter(child); - } - return false; - } - @override void handleEvent(PointerEvent event, BoxHitTestEntry entry) { assert(debugHandleEvent(event, entry)); @@ -450,81 +299,9 @@ class RenderParagraph extends RenderBox @visibleForTesting bool get debugHasOverflowShader => _overflowShader != null; - void _layoutText({ double minWidth = 0.0, double maxWidth = double.infinity }) { - final bool widthMatters = softWrap || overflow == TextOverflow.ellipsis; - _textPainter.layout(minWidth: minWidth, maxWidth: widthMatters ? maxWidth : double.infinity); - } - - void _layoutTextWithConstraints(BoxConstraints constraints) { - _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth); - } - - // Layout the child inline widgets. We then pass the dimensions of the - // children to _textPainter so that appropriate placeholders can be inserted - // into the LibTxt layout. This does not do anything if no inline widgets were - // specified. - void _layoutChildren(BoxConstraints constraints) { - if (childCount == 0) { - return; - } - RenderBox child = firstChild; - final List placeholderDimensions = List(childCount); - int childIndex = 0; - while (child != null) { - // Only constrain the width to the maximum width of the paragraph. - // Leave height unconstrained, which will overflow if expanded past. - child.layout( - BoxConstraints( - maxWidth: constraints.maxWidth, - ), - parentUsesSize: true - ); - double baselineOffset; - switch (_placeholderSpans[childIndex].alignment) { - case ui.PlaceholderAlignment.baseline: { - baselineOffset = child.getDistanceToBaseline(_placeholderSpans[childIndex].baseline); - break; - } - default: { - baselineOffset = null; - break; - } - } - placeholderDimensions[childIndex] = PlaceholderDimensions( - size: child.size, - alignment: _placeholderSpans[childIndex].alignment, - baseline: _placeholderSpans[childIndex].baseline, - baselineOffset: baselineOffset, - ); - child = childAfter(child); - childIndex += 1; - } - _textPainter.setPlaceholderDimensions(placeholderDimensions); - } - - // Iterate through the laid-out children and set the parentData offsets based - // off of the placeholders inserted for each child. - void _setParentData() { - RenderBox child = firstChild; - int childIndex = 0; - while (child != null) { - final TextParentData textParentData = child.parentData; - textParentData.offset = Offset( - _textPainter.inlinePlaceholderBoxes[childIndex].left, - _textPainter.inlinePlaceholderBoxes[childIndex].top - ); - textParentData.scale = _textPainter.inlinePlaceholderScales[childIndex]; - child = childAfter(child); - childIndex += 1; - } - } - @override void performLayout() { - _layoutChildren(constraints); _layoutTextWithConstraints(constraints); - _setParentData(); - // We grab _textPainter.size and _textPainter.didExceedMaxLines here because // assigning to `size` will trigger us to validate our intrinsic sizes, // which will change _textPainter's layout because the intrinsic size @@ -609,12 +386,13 @@ class RenderParagraph extends RenderBox // If you remove this call, make sure that changing the textAlign still // works properly. _layoutTextWithConstraints(constraints); + final Canvas canvas = context.canvas; assert(() { if (debugRepaintTextRainbowEnabled) { final Paint paint = Paint() ..color = debugCurrentRepaintColor.toColor(); - context.canvas.drawRect(offset & size, paint); + canvas.drawRect(offset & size, paint); } return true; }()); @@ -624,44 +402,22 @@ class RenderParagraph extends RenderBox if (_overflowShader != null) { // This layer limits what the shader below blends with to be just the text // (as opposed to the text and its background). - context.canvas.saveLayer(bounds, Paint()); + canvas.saveLayer(bounds, Paint()); } else { - context.canvas.save(); + canvas.save(); } - context.canvas.clipRect(bounds); - } - _textPainter.paint(context.canvas, offset); - - RenderBox child = firstChild; - int childIndex = 0; - while (child != null) { - assert(childIndex < _textPainter.inlinePlaceholderBoxes.length); - final TextParentData textParentData = child.parentData; - - final double scale = textParentData.scale; - context.pushTransform( - needsCompositing, - offset + textParentData.offset, - Matrix4.diagonal3Values(scale, scale, scale), - (PaintingContext context, Offset offset) { - context.paintChild( - child, - offset, - ); - }, - ); - child = childAfter(child); - childIndex += 1; + canvas.clipRect(bounds); } + _textPainter.paint(canvas, offset); if (_needsClipping) { if (_overflowShader != null) { - context.canvas.translate(offset.dx, offset.dy); + canvas.translate(offset.dx, offset.dy); final Paint paint = Paint() ..blendMode = BlendMode.modulate ..shader = _overflowShader; - context.canvas.drawRect(Offset.zero & size, paint); + canvas.drawRect(Offset.zero & size, paint); } - context.canvas.restore(); + canvas.restore(); } } @@ -725,23 +481,26 @@ class RenderParagraph extends RenderBox return _textPainter.size; } - // The offsets for each span that requires custom semantics. - final List _inlineSemanticsOffsets = []; - // Holds either [GestureRecognizer] or null (for placeholders) to generate - // proper semnatics configurations. - final List _inlineSemanticsElements = []; + final List _recognizerOffsets = []; + final List _recognizers = []; @override void describeSemanticsConfiguration(SemanticsConfiguration config) { super.describeSemanticsConfiguration(config); - _inlineSemanticsOffsets.clear(); - _inlineSemanticsElements.clear(); - final Accumulator offset = Accumulator(); - text.visitChildren((InlineSpan span) { - span.describeSemantics(offset, _inlineSemanticsOffsets, _inlineSemanticsElements); + _recognizerOffsets.clear(); + _recognizers.clear(); + int offset = 0; + text.visitTextSpan((TextSpan span) { + 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 + length); + _recognizers.add(span.recognizer); + } + offset += span.text.length; return true; }); - if (_inlineSemanticsOffsets.isNotEmpty) { + if (_recognizerOffsets.isNotEmpty) { config.explicitChildNodes = true; config.isSemanticBoundary = true; } else { @@ -752,9 +511,10 @@ class RenderParagraph extends RenderBox @override void assembleSemanticsNode(SemanticsNode node, SemanticsConfiguration config, Iterable children) { - assert(_inlineSemanticsOffsets.isNotEmpty); - assert(_inlineSemanticsOffsets.length.isEven); - assert(_inlineSemanticsElements.isNotEmpty); + assert(_recognizerOffsets.isNotEmpty); + assert(_recognizerOffsets.length.isEven); + assert(_recognizers.isNotEmpty); + assert(children.isEmpty); final List newChildren = []; final String rawLabel = text.toPlainText(); int current = 0; @@ -762,7 +522,7 @@ class RenderParagraph extends RenderBox TextDirection currentDirection = textDirection; Rect currentRect; - SemanticsConfiguration buildSemanticsConfig(int start, int end, { bool includeText = true }) { + SemanticsConfiguration buildSemanticsConfig(int start, int end) { final TextDirection initialDirection = currentDirection; final TextSelection selection = TextSelection(baseOffset: start, extentOffset: end); final List rects = getBoxesForSelection(selection); @@ -782,21 +542,15 @@ class RenderParagraph extends RenderBox rect.bottom.ceilToDouble() + 4.0, ); order += 1; - final SemanticsConfiguration configuration = SemanticsConfiguration() + return SemanticsConfiguration() ..sortKey = OrdinalSortKey(order) - ..textDirection = initialDirection; - if (includeText) { - configuration.label = rawLabel.substring(start, end); - } - return configuration; + ..textDirection = initialDirection + ..label = rawLabel.substring(start, end); } - int childIndex = 0; - RenderBox child = firstChild; - for (int i = 0, j = 0; i < _inlineSemanticsOffsets.length; i += 2, j++) { - final int start = _inlineSemanticsOffsets[i]; - final int end = _inlineSemanticsOffsets[i + 1]; - // Add semantics for any text between the previous recognizer/widget and this one. + for (int i = 0, j = 0; i < _recognizerOffsets.length; i += 2, j++) { + final int start = _recognizerOffsets[i]; + final int end = _recognizerOffsets[i + 1]; if (current != start) { final SemanticsNode node = SemanticsNode(); final SemanticsConfiguration configuration = buildSemanticsConfig(current, start); @@ -804,38 +558,19 @@ class RenderParagraph extends RenderBox node.rect = currentRect; newChildren.add(node); } - final dynamic inlineElement = _inlineSemanticsElements[j]; - final SemanticsConfiguration configuration = buildSemanticsConfig(start, end, includeText: false); - if (inlineElement != null) { - // Add semantics for this recognizer. - final SemanticsNode node = SemanticsNode(); - if (inlineElement is TapGestureRecognizer) { - final TapGestureRecognizer recognizer = inlineElement; - configuration.onTap = recognizer.onTap; - } else if (inlineElement is LongPressGestureRecognizer) { - final LongPressGestureRecognizer recognizer = inlineElement; - configuration.onLongPress = recognizer.onLongPress; - } else { - assert(false); - } - node.updateWith(config: configuration); - node.rect = currentRect; - newChildren.add(node); - } else if (childIndex < children.length) { - // Add semantics for this placeholder. Semantics are precomputed in the children - // argument. - final SemanticsNode childNode = children.elementAt(childIndex); - final TextParentData parentData = child.parentData; - childNode.rect = Rect.fromLTWH( - childNode.rect.left, - childNode.rect.top, - childNode.rect.width * parentData.scale, - childNode.rect.height * parentData.scale, - ); - newChildren.add(children.elementAt(childIndex)); - childIndex += 1; - child = childAfter(child); + final SemanticsNode node = SemanticsNode(); + final SemanticsConfiguration configuration = buildSemanticsConfig(start, end); + final GestureRecognizer recognizer = _recognizers[j]; + if (recognizer is TapGestureRecognizer) { + configuration.onTap = recognizer.onTap; + } else if (recognizer is LongPressGestureRecognizer) { + configuration.onLongPress = recognizer.onLongPress; + } else { + assert(false); } + node.updateWith(config: configuration); + node.rect = currentRect; + newChildren.add(node); current = end; } if (current < rawLabel.length) { diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 596b883d057..e627b3ea91b 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -12,7 +12,6 @@ import 'package:flutter/services.dart'; import 'debug.dart'; import 'framework.dart'; import 'localizations.dart'; -import 'widget_span.dart'; export 'package:flutter/animation.dart'; export 'package:flutter/foundation.dart' show @@ -4914,9 +4913,7 @@ class Flow extends MultiChildRenderObjectWidget { /// * [TextSpan], which is used to describe the text in a paragraph. /// * [Text], which automatically applies the ambient styles described by a /// [DefaultTextStyle] to a single string. -/// * [Text.rich], a const text widget that provides similar functionality -/// as [RichText]. [Text.rich] will inherit [TextStyle] from [DefaultTextStyle]. -class RichText extends MultiChildRenderObjectWidget { +class RichText extends LeafRenderObjectWidget { /// Creates a paragraph of rich text. /// /// The [text], [textAlign], [softWrap], [overflow], and [textScaleFactor] @@ -4927,7 +4924,7 @@ class RichText extends MultiChildRenderObjectWidget { /// /// The [textDirection], if null, defaults to the ambient [Directionality], /// which in that case must not be null. - RichText({ + const RichText({ Key key, @required this.text, this.textAlign = TextAlign.start, @@ -4946,23 +4943,10 @@ class RichText extends MultiChildRenderObjectWidget { assert(textScaleFactor != null), assert(maxLines == null || maxLines > 0), assert(textWidthBasis != null), - super(key: key, children: _extractChildren(text)); - - // Traverses the InlineSpan tree and depth-first collects the list of - // child widgets that are created in WidgetSpans. - static List _extractChildren(InlineSpan span) { - final List result = []; - span.visitChildren((InlineSpan span) { - if (span is WidgetSpan) { - result.add(span.child); - } - return true; - }); - return result; - } + super(key: key); /// The text to display in this widget. - final InlineSpan text; + final TextSpan text; /// How the text should be aligned horizontally. final TextAlign textAlign; diff --git a/packages/flutter/lib/src/widgets/text.dart b/packages/flutter/lib/src/widgets/text.dart index 18637dd8e99..ee3233f8daa 100644 --- a/packages/flutter/lib/src/widgets/text.dart +++ b/packages/flutter/lib/src/widgets/text.dart @@ -256,16 +256,9 @@ class Text extends StatelessWidget { textSpan = null, super(key: key); - /// Creates a text widget with a [InlineSpan]. - /// - /// The following subclasses of [InlineSpan] may be used to build rich text: - /// - /// * [TextSpan]s define text and children [InlineSpan]s. - /// * [WidgetSpan]s define embedded inline widgets. + /// Creates a text widget with a [TextSpan]. /// /// The [textSpan] parameter must not be null. - /// - /// See [RichText] which provides a lower-level way to draw text. const Text.rich( this.textSpan, { Key key, @@ -292,10 +285,10 @@ class Text extends StatelessWidget { /// This will be null if a [textSpan] is provided instead. final String data; - /// The text to display as a [InlineSpan]. + /// The text to display as a [TextSpan]. /// /// This will be null if [data] is provided instead. - final InlineSpan textSpan; + final TextSpan textSpan; /// If non-null, the style to use for this text. /// diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart index ab9a1bee778..ab178a03c1e 100644 --- a/packages/flutter/lib/src/widgets/widget_inspector.dart +++ b/packages/flutter/lib/src/widgets/widget_inspector.dart @@ -2752,8 +2752,7 @@ class _InspectorOverlayLayer extends Layer { ) { canvas.save(); final double maxWidth = size.width - 2 * (_kScreenEdgeMargin + _kTooltipPadding); - final TextSpan textSpan = _textPainter?.text; - if (_textPainter == null || textSpan.text != message || _textPainterMaxWidth != maxWidth) { + if (_textPainter == null || _textPainter.text.text != message || _textPainterMaxWidth != maxWidth) { _textPainterMaxWidth = maxWidth; _textPainter = TextPainter() ..maxLines = _kMaxTooltipLines diff --git a/packages/flutter/lib/src/widgets/widget_span.dart b/packages/flutter/lib/src/widgets/widget_span.dart deleted file mode 100644 index a24c04a0909..00000000000 --- a/packages/flutter/lib/src/widgets/widget_span.dart +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:ui' as ui show ParagraphBuilder, PlaceholderAlignment; - -import 'package:flutter/painting.dart'; - -import 'framework.dart'; - -/// An immutable widget that is embedded inline within text. -/// -/// The [child] property is the widget that will be embedded. Children are -/// constrained by the width of the paragraph. -/// -/// The [child] property may contain its own [Widget] children (if applicable), -/// including [Text] and [RichText] widgets which may include additional -/// [WidgetSpan]s. Child [Text] and [RichText] widgets will be laid out -/// independently and occupy a rectangular space in the parent text layout. -/// -/// [WidgetSpan]s will be ignored when passed into a [TextPainter] directly. -/// To properly layout and paint the [child] widget, [WidgetSpan] should be -/// passed into a [Text.rich] widget. -/// -/// {@tool sample} -/// -/// A card with `Hello World!` embedded inline within a TextSpan tree. -/// -/// ```dart -/// Text.rich( -/// TextSpan( -/// children: [ -/// TextSpan(text: 'Flutter is'), -/// WidgetSpan( -/// child: SizedBox( -/// width: 120, -/// height: 50, -/// child: Card( -/// child: Center( -/// child: Text('Hello World!') -/// ) -/// ), -/// ) -/// ), -/// TextSpan(text: 'the best!'), -/// ], -/// ) -/// ) -/// ``` -/// {@end-tool} -/// -/// [WidgetSpan] contributes the semantics of the [WidgetSpan.child] to the -/// semantics tree. -/// -/// See also: -/// -/// * [TextSpan], a node that represents text in an [InlineSpan] tree. -/// * [Text], a widget for showing uniformly-styled text. -/// * [RichText], a widget for finer control of text rendering. -/// * [TextPainter], a class for painting [InlineSpan] objects on a [Canvas]. -@immutable -class WidgetSpan extends PlaceholderSpan { - /// Creates a [WidgetSpan] with the given values. - /// - /// The [child] property must be non-null. [WidgetSpan] is a leaf node in - /// the [InlineSpan] tree. Child widgets are constrained by the width of the - /// paragraph they occupy. Child widget heights are unconstrained, and may - /// cause the text to overflow and be ellipsized/truncated. - /// - /// A [TextStyle] may be provided with the [style] property, but only the - /// decoration, foreground, background, and spacing options will be used. - const WidgetSpan({ - @required this.child, - ui.PlaceholderAlignment alignment = ui.PlaceholderAlignment.bottom, - TextBaseline baseline, - TextStyle style, - }) : assert(child != null), - assert((identical(alignment, ui.PlaceholderAlignment.aboveBaseline) || - identical(alignment, ui.PlaceholderAlignment.belowBaseline) || - identical(alignment, ui.PlaceholderAlignment.baseline)) ? baseline != null : true), - super( - alignment: alignment, - baseline: baseline, - style: style, - ); - - /// The widget to embed inline within text. - final Widget child; - - /// Adds a placeholder box to the paragraph builder if a size has been - /// calculated for the widget. - /// - /// Sizes are provided through `dimensions`, which should contain a 1:1 - /// in-order mapping of widget to laid-out dimensions. If no such dimension - /// is provided, the widget will be skipped. - /// - /// The `textScaleFactor` will be applied to the laid-out size of the widget. - @override - void build(ui.ParagraphBuilder builder, { double textScaleFactor = 1.0, @required List dimensions }) { - assert(debugAssertIsValid()); - assert(dimensions != null); - final bool hasStyle = style != null; - if (hasStyle) { - builder.pushStyle(style.getTextStyle(textScaleFactor: textScaleFactor)); - } - assert(builder.placeholderCount < dimensions.length); - final PlaceholderDimensions currentDimensions = dimensions[builder.placeholderCount]; - builder.addPlaceholder( - currentDimensions.size.width, - currentDimensions.size.height, - alignment, - scale: textScaleFactor, - baseline: currentDimensions.baseline, - baselineOffset: currentDimensions.baselineOffset, - ); - if (hasStyle) { - builder.pop(); - } - } - - /// Calls `visitor` on this [WidgetSpan]. There are no children spans to walk. - @override - bool visitChildren(InlineSpanVisitor visitor) { - return visitor(this); - } - - @override - InlineSpan getSpanForPositionVisitor(TextPosition position, Accumulator offset) { - return null; - } - - @override - int codeUnitAtVisitor(int index, Accumulator offset) { - return null; - } - - @override - RenderComparison compareTo(InlineSpan other) { - if (identical(this, other)) - return RenderComparison.identical; - if (other.runtimeType != runtimeType) - return RenderComparison.layout; - if ((style == null) != (other.style == null)) - return RenderComparison.layout; - final WidgetSpan typedOther = other; - if (child != typedOther.child || alignment != typedOther.alignment) { - return RenderComparison.layout; - } - RenderComparison result = RenderComparison.identical; - if (style != null) { - final RenderComparison candidate = style.compareTo(other.style); - if (candidate.index > result.index) - result = candidate; - if (result == RenderComparison.layout) - return result; - } - return result; - } - - @override - bool operator ==(dynamic other) { - if (identical(this, other)) - return true; - if (other.runtimeType != runtimeType) - return false; - if (super != other) - return false; - final WidgetSpan typedOther = other; - return typedOther.child == child - && typedOther.alignment == alignment - && typedOther.baseline == baseline; - } - - @override - int get hashCode => hashValues(super.hashCode, child, alignment, baseline); - - /// Returns the text span that contains the given position in the text. - @override - InlineSpan getSpanForPosition(TextPosition position) { - assert(debugAssertIsValid()); - return null; - } - - /// In debug mode, throws an exception if the object is not in a - /// valid configuration. Otherwise, returns true. - /// - /// This is intended to be used as follows: - /// - /// ```dart - /// assert(myWidgetSpan.debugAssertIsValid()); - /// ``` - @override - bool debugAssertIsValid() { - // WidgetSpans are always valid as asserts prevent invalid WidgetSpans - // from being constructed. - return true; - } -} diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart index d0871cfa1ae..744a3fba75e 100644 --- a/packages/flutter/lib/widgets.dart +++ b/packages/flutter/lib/widgets.dart @@ -108,5 +108,4 @@ export 'src/widgets/value_listenable_builder.dart'; export 'src/widgets/viewport.dart'; export 'src/widgets/visibility.dart'; export 'src/widgets/widget_inspector.dart'; -export 'src/widgets/widget_span.dart'; export 'src/widgets/will_pop_scope.dart'; diff --git a/packages/flutter/test/painting/text_painter_rtl_test.dart b/packages/flutter/test/painting/text_painter_rtl_test.dart index e44cfe09b90..5f3f02bc900 100644 --- a/packages/flutter/test/painting/text_painter_rtl_test.dart +++ b/packages/flutter/test/painting/text_painter_rtl_test.dart @@ -43,8 +43,7 @@ void main() { // 0 12345678 9 101234567 18 90123456 27 style: TextStyle(fontFamily: 'Ahem', fontSize: 10.0), ); - TextSpan textSpan = painter.text; - expect(textSpan.text.length, 28); + expect(painter.text.text.length, 28); painter.layout(); // The skips here are because the old rendering code considers the bidi formatting characters @@ -128,8 +127,7 @@ void main() { ); final List> list = >[]; - textSpan = painter.text; - for (int index = 0; index < textSpan.text.length; index += 1) + for (int index = 0; index < painter.text.text.length; index += 1) list.add(painter.getBoxesForSelection(TextSelection(baseOffset: index, extentOffset: index + 1))); expect(list, const >[ [], // U+202E, non-printing Unicode bidi formatting character @@ -174,8 +172,7 @@ void main() { // 0 12345678 9 101234567 18 90123456 27 style: TextStyle(fontFamily: 'Ahem', fontSize: 10.0), ); - final TextSpan textSpan = painter.text; - expect(textSpan.text.length, 28); + expect(painter.text.text.length, 28); painter.layout(); final TextRange hebrew1 = painter.getWordBoundary(const TextPosition(offset: 4, affinity: TextAffinity.downstream)); @@ -264,8 +261,7 @@ void main() { text: 'A\u05D0', // A, Alef style: TextStyle(fontFamily: 'Ahem', fontSize: 10.0), ); - final TextSpan textSpan = painter.text; - expect(textSpan.text.length, 2); + expect(painter.text.text.length, 2); painter.layout(maxWidth: 10.0); for (int index = 0; index <= 2; index += 1) { diff --git a/packages/flutter/test/painting/text_painter_test.dart b/packages/flutter/test/painting/text_painter_test.dart index cfa5f56883f..80d0c2228a0 100644 --- a/packages/flutter/test/painting/text_painter_test.dart +++ b/packages/flutter/test/painting/text_painter_test.dart @@ -5,7 +5,6 @@ import 'dart:ui' as ui; import 'package:flutter/painting.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { @@ -635,98 +634,4 @@ void main() { expect(caretOffset.dx, closeTo(0.0, 0.0001)); expect(caretOffset.dy, closeTo(0.0, 0.0001)); }); - - test('TextPainter widget span', () { - final TextPainter painter = TextPainter() - ..textDirection = TextDirection.ltr; - - const String text = 'test'; - painter.text = const TextSpan( - text: text, - children: [ - WidgetSpan(child: SizedBox(width: 50, height: 30)), - TextSpan(text: text), - WidgetSpan(child: SizedBox(width: 50, height: 30)), - WidgetSpan(child: SizedBox(width: 50, height: 30)), - TextSpan(text: text), - WidgetSpan(child: SizedBox(width: 50, height: 30)), - WidgetSpan(child: SizedBox(width: 50, height: 30)), - WidgetSpan(child: SizedBox(width: 50, height: 30)), - WidgetSpan(child: SizedBox(width: 50, height: 30)), - WidgetSpan(child: SizedBox(width: 50, height: 30)), - WidgetSpan(child: SizedBox(width: 50, height: 30)), - WidgetSpan(child: SizedBox(width: 50, height: 30)), - WidgetSpan(child: SizedBox(width: 50, height: 30)), - WidgetSpan(child: SizedBox(width: 50, height: 30)), - WidgetSpan(child: SizedBox(width: 50, height: 30)), - WidgetSpan(child: SizedBox(width: 50, height: 30)), - ] - ); - - // We provide dimensions for the widgets - painter.setPlaceholderDimensions(const [ - PlaceholderDimensions(size: Size(50, 30), baselineOffset: 25, alignment: ui.PlaceholderAlignment.bottom), - PlaceholderDimensions(size: Size(50, 30), baselineOffset: 25, alignment: ui.PlaceholderAlignment.bottom), - PlaceholderDimensions(size: Size(50, 30), baselineOffset: 25, alignment: ui.PlaceholderAlignment.bottom), - PlaceholderDimensions(size: Size(50, 30), baselineOffset: 25, alignment: ui.PlaceholderAlignment.bottom), - PlaceholderDimensions(size: Size(50, 30), baselineOffset: 25, alignment: ui.PlaceholderAlignment.bottom), - PlaceholderDimensions(size: Size(50, 30), baselineOffset: 25, alignment: ui.PlaceholderAlignment.bottom), - PlaceholderDimensions(size: Size(50, 30), baselineOffset: 25, alignment: ui.PlaceholderAlignment.bottom), - PlaceholderDimensions(size: Size(50, 30), baselineOffset: 25, alignment: ui.PlaceholderAlignment.bottom), - PlaceholderDimensions(size: Size(50, 30), baselineOffset: 25, alignment: ui.PlaceholderAlignment.bottom), - PlaceholderDimensions(size: Size(50, 30), baselineOffset: 25, alignment: ui.PlaceholderAlignment.bottom), - PlaceholderDimensions(size: Size(50, 30), baselineOffset: 25, alignment: ui.PlaceholderAlignment.bottom), - PlaceholderDimensions(size: Size(50, 30), baselineOffset: 25, alignment: ui.PlaceholderAlignment.bottom), - PlaceholderDimensions(size: Size(51, 30), baselineOffset: 25, alignment: ui.PlaceholderAlignment.bottom), - PlaceholderDimensions(size: Size(50, 30), baselineOffset: 25, alignment: ui.PlaceholderAlignment.bottom), - ]); - - painter.layout(maxWidth: 500); - - // Now, each of the WidgetSpans will have their own placeholder 'hole'. - Offset caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 1), ui.Rect.zero); - expect(caretOffset.dx, 14); - caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 4), ui.Rect.zero); - expect(caretOffset.dx, 56); - caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 5), ui.Rect.zero); - expect(caretOffset.dx, 106); - caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 6), ui.Rect.zero); - expect(caretOffset.dx, 120); - caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 10), ui.Rect.zero); - expect(caretOffset.dx, 212); - caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 11), ui.Rect.zero); - expect(caretOffset.dx, 262); - caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 12), ui.Rect.zero); - expect(caretOffset.dx, 276); - caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 13), ui.Rect.zero); - expect(caretOffset.dx, 290); - caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 14), ui.Rect.zero); - expect(caretOffset.dx, 304); - caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 15), ui.Rect.zero); - expect(caretOffset.dx, 318); - caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 16), ui.Rect.zero); - expect(caretOffset.dx, 368); - caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 17), ui.Rect.zero); - expect(caretOffset.dx, 418); - caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 18), ui.Rect.zero); - expect(caretOffset.dx, 0); - caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 19), ui.Rect.zero); - expect(caretOffset.dx, 50); - caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 23), ui.Rect.zero); - expect(caretOffset.dx, 250); - - expect(painter.inlinePlaceholderBoxes.length, 14); - expect(painter.inlinePlaceholderBoxes[0], const TextBox.fromLTRBD(56, 0, 106, 30, TextDirection.ltr)); - expect(painter.inlinePlaceholderBoxes[2], const TextBox.fromLTRBD(212, 0, 262, 30, TextDirection.ltr)); - expect(painter.inlinePlaceholderBoxes[3], const TextBox.fromLTRBD(318, 0, 368, 30, TextDirection.ltr)); - expect(painter.inlinePlaceholderBoxes[4], const TextBox.fromLTRBD(368, 0, 418, 30, TextDirection.ltr)); - expect(painter.inlinePlaceholderBoxes[5], const TextBox.fromLTRBD(418, 0, 468, 30, TextDirection.ltr)); - // line should break here - expect(painter.inlinePlaceholderBoxes[6], const TextBox.fromLTRBD(0, 30, 50, 60, TextDirection.ltr)); - expect(painter.inlinePlaceholderBoxes[7], const TextBox.fromLTRBD(50, 30, 100, 60, TextDirection.ltr)); - expect(painter.inlinePlaceholderBoxes[10], const TextBox.fromLTRBD(200, 30, 250, 60, TextDirection.ltr)); - expect(painter.inlinePlaceholderBoxes[11], const TextBox.fromLTRBD(250, 30, 300, 60, TextDirection.ltr)); - expect(painter.inlinePlaceholderBoxes[12], const TextBox.fromLTRBD(300, 30, 351, 60, TextDirection.ltr)); - expect(painter.inlinePlaceholderBoxes[13], const TextBox.fromLTRBD(351, 30, 401, 60, TextDirection.ltr)); - }); } diff --git a/packages/flutter/test/painting/text_span_test.dart b/packages/flutter/test/painting/text_span_test.dart index 2f7e9757c92..072ea5f4cbf 100644 --- a/packages/flutter/test/painting/text_span_test.dart +++ b/packages/flutter/test/painting/text_span_test.dart @@ -3,17 +3,17 @@ // found in the LICENSE file. import 'package:flutter/painting.dart'; -import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart' show nonconst; import '../flutter_test_alternative.dart'; void main() { test('TextSpan equals', () { - const TextSpan a1 = TextSpan(text: 'a'); - const TextSpan a2 = TextSpan(text: 'a'); - const TextSpan b1 = TextSpan(children: [ a1 ]); - const TextSpan b2 = TextSpan(children: [ a2 ]); - const TextSpan c1 = TextSpan(text: null); - const TextSpan c2 = TextSpan(text: null); + final TextSpan a1 = TextSpan(text: nonconst('a')); + final TextSpan a2 = TextSpan(text: nonconst('a')); + final TextSpan b1 = TextSpan(children: [ a1 ]); + final TextSpan b2 = TextSpan(children: [ a2 ]); + final TextSpan c1 = TextSpan(text: nonconst(null)); + final TextSpan c2 = TextSpan(text: nonconst(null)); expect(a1 == a2, isTrue); expect(b1 == b2, isTrue); @@ -73,18 +73,6 @@ void main() { expect(textSpan.toPlainText(), 'abc'); }); - test('WidgetSpan toPlainText', () { - const TextSpan textSpan = TextSpan( - text: 'a', - children: [ - TextSpan(text: 'b'), - WidgetSpan(child: SizedBox(width: 10, height: 10)), - TextSpan(text: 'c'), - ], - ); - expect(textSpan.toPlainText(), 'ab\uFFFCc'); - }); - test('TextSpan toPlainText with semanticsLabel', () { const TextSpan textSpan = TextSpan( text: 'a', @@ -96,117 +84,4 @@ void main() { expect(textSpan.toPlainText(), 'afooc'); expect(textSpan.toPlainText(includeSemanticsLabels: false), 'abc'); }); - - test('TextSpan widget change test', () { - const TextSpan textSpan1 = TextSpan( - text: 'a', - children: [ - TextSpan(text: 'b'), - WidgetSpan(child: SizedBox(width: 10, height: 10)), - TextSpan(text: 'c'), - ], - ); - - const TextSpan textSpan2 = TextSpan( - text: 'a', - children: [ - TextSpan(text: 'b'), - WidgetSpan(child: SizedBox(width: 10, height: 10)), - TextSpan(text: 'c'), - ], - ); - - const TextSpan textSpan3 = TextSpan( - text: 'a', - children: [ - TextSpan(text: 'b'), - WidgetSpan(child: SizedBox(width: 11, height: 10)), - TextSpan(text: 'c'), - ], - ); - - const TextSpan textSpan4 = TextSpan( - text: 'a', - children: [ - TextSpan(text: 'b'), - WidgetSpan(child: Text('test')), - TextSpan(text: 'c'), - ], - ); - - const TextSpan textSpan5 = TextSpan( - text: 'a', - children: [ - TextSpan(text: 'b'), - WidgetSpan(child: Text('different!')), - TextSpan(text: 'c'), - ], - ); - - const TextSpan textSpan6 = TextSpan( - text: 'a', - children: [ - TextSpan(text: 'b'), - WidgetSpan( - child: SizedBox(width: 10, height: 10), - alignment: PlaceholderAlignment.top, - ), - TextSpan(text: 'c'), - ], - ); - - expect(textSpan1.compareTo(textSpan3), RenderComparison.layout); - expect(textSpan1.compareTo(textSpan4), RenderComparison.layout); - expect(textSpan1.compareTo(textSpan1), RenderComparison.identical); - expect(textSpan2.compareTo(textSpan2), RenderComparison.identical); - expect(textSpan3.compareTo(textSpan3), RenderComparison.identical); - expect(textSpan2.compareTo(textSpan3), RenderComparison.layout); - expect(textSpan4.compareTo(textSpan5), RenderComparison.layout); - expect(textSpan3.compareTo(textSpan5), RenderComparison.layout); - expect(textSpan2.compareTo(textSpan5), RenderComparison.layout); - expect(textSpan1.compareTo(textSpan5), RenderComparison.layout); - expect(textSpan1.compareTo(textSpan6), RenderComparison.layout); - }); - - test('TextSpan nested widget change test', () { - const TextSpan textSpan1 = TextSpan( - text: 'a', - children: [ - TextSpan(text: 'b'), - WidgetSpan( - child: Text.rich( - TextSpan( - children: [ - WidgetSpan(child: SizedBox(width: 10, height: 10)), - TextSpan(text: 'The sky is falling :)') - ], - ) - ), - ), - TextSpan(text: 'c'), - ], - ); - - const TextSpan textSpan2 = TextSpan( - text: 'a', - children: [ - TextSpan(text: 'b'), - WidgetSpan( - child: Text.rich( - TextSpan( - children: [ - WidgetSpan(child: SizedBox(width: 10, height: 11)), - TextSpan(text: 'The sky is falling :)') - ], - ) - ), - ), - TextSpan(text: 'c'), - ], - ); - - expect(textSpan1.compareTo(textSpan2), RenderComparison.layout); - expect(textSpan1.compareTo(textSpan1), RenderComparison.identical); - expect(textSpan2.compareTo(textSpan2), RenderComparison.identical); - }); } diff --git a/packages/flutter/test/rendering/paragraph_test.dart b/packages/flutter/test/rendering/paragraph_test.dart index 356b6d8cbe8..c998d4e684a 100644 --- a/packages/flutter/test/rendering/paragraph_test.dart +++ b/packages/flutter/test/rendering/paragraph_test.dart @@ -5,7 +5,6 @@ import 'dart:ui' as ui show TextBox; import 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -325,93 +324,4 @@ void main() { expect(paragraph.locale, const Locale('ja', 'JP')); }); - test('inline widgets test', () { - const TextSpan text = TextSpan( - text: 'a', - style: TextStyle(fontSize: 10.0), - children: [ - WidgetSpan(child: SizedBox(width: 21, height: 21)), - WidgetSpan(child: SizedBox(width: 21, height: 21)), - TextSpan(text: 'a'), - WidgetSpan(child: SizedBox(width: 21, height: 21)), - ], - ); - // Fake the render boxes that correspond to the WidgetSpans. We use - // RenderParagraph to reduce dependencies this test has. - final List renderBoxes = []; - renderBoxes.add(RenderParagraph(const TextSpan(text: 'b'), textDirection: TextDirection.ltr)); - renderBoxes.add(RenderParagraph(const TextSpan(text: 'b'), textDirection: TextDirection.ltr)); - renderBoxes.add(RenderParagraph(const TextSpan(text: 'b'), textDirection: TextDirection.ltr)); - - final RenderParagraph paragraph = RenderParagraph( - text, - textDirection: TextDirection.ltr, - children: renderBoxes, - ); - layout(paragraph, constraints: const BoxConstraints(maxWidth: 100.0)); - - final List boxes = paragraph.getBoxesForSelection( - const TextSelection(baseOffset: 0, extentOffset: 8) - ); - - expect(boxes.length, equals(5)); - expect(boxes[0], const TextBox.fromLTRBD(0.0, 4.0, 10.0, 14.0, TextDirection.ltr)); - expect(boxes[1], const TextBox.fromLTRBD(10.0, 0.0, 24.0, 14.0, TextDirection.ltr)); - expect(boxes[2], const TextBox.fromLTRBD(24.0, 0.0, 38.0, 14.0, TextDirection.ltr)); - expect(boxes[3], const TextBox.fromLTRBD(38.0, 4.0, 48.0, 14.0, TextDirection.ltr)); - expect(boxes[4], const TextBox.fromLTRBD(48.0, 0.0, 62.0, 14.0, TextDirection.ltr)); - // Ahem-based tests don't yet quite work on Windows or some MacOS environments - }, skip: isWindows || isMacOS); - - test('inline widgets multiline test', () { - const TextSpan text = TextSpan( - text: 'a', - style: TextStyle(fontSize: 10.0), - children: [ - WidgetSpan(child: SizedBox(width: 21, height: 21)), - WidgetSpan(child: SizedBox(width: 21, height: 21)), - TextSpan(text: 'a'), - WidgetSpan(child: SizedBox(width: 21, height: 21)), - WidgetSpan(child: SizedBox(width: 21, height: 21)), - WidgetSpan(child: SizedBox(width: 21, height: 21)), - WidgetSpan(child: SizedBox(width: 21, height: 21)), - WidgetSpan(child: SizedBox(width: 21, height: 21)), - ], - ); - // Fake the render boxes that correspond to the WidgetSpans. We use - // RenderParagraph to reduce dependencies this test has. - final List renderBoxes = []; - renderBoxes.add(RenderParagraph(const TextSpan(text: 'b'), textDirection: TextDirection.ltr)); - renderBoxes.add(RenderParagraph(const TextSpan(text: 'b'), textDirection: TextDirection.ltr)); - renderBoxes.add(RenderParagraph(const TextSpan(text: 'b'), textDirection: TextDirection.ltr)); - renderBoxes.add(RenderParagraph(const TextSpan(text: 'b'), textDirection: TextDirection.ltr)); - renderBoxes.add(RenderParagraph(const TextSpan(text: 'b'), textDirection: TextDirection.ltr)); - renderBoxes.add(RenderParagraph(const TextSpan(text: 'b'), textDirection: TextDirection.ltr)); - renderBoxes.add(RenderParagraph(const TextSpan(text: 'b'), textDirection: TextDirection.ltr)); - - final RenderParagraph paragraph = RenderParagraph( - text, - textDirection: TextDirection.ltr, - children: renderBoxes, - ); - layout(paragraph, constraints: const BoxConstraints(maxWidth: 50.0)); - - final List boxes = paragraph.getBoxesForSelection( - const TextSelection(baseOffset: 0, extentOffset: 12) - ); - - expect(boxes.length, equals(9)); - expect(boxes[0], const TextBox.fromLTRBD(0.0, 4.0, 10.0, 14.0, TextDirection.ltr)); - expect(boxes[1], const TextBox.fromLTRBD(10.0, 0.0, 24.0, 14.0, TextDirection.ltr)); - expect(boxes[2], const TextBox.fromLTRBD(24.0, 0.0, 38.0, 14.0, TextDirection.ltr)); - expect(boxes[3], const TextBox.fromLTRBD(38.0, 4.0, 48.0, 14.0, TextDirection.ltr)); - // Wraps - expect(boxes[4], const TextBox.fromLTRBD(0.0, 14.0, 14.0, 28.0 , TextDirection.ltr)); - expect(boxes[5], const TextBox.fromLTRBD(14.0, 14.0, 28.0, 28.0, TextDirection.ltr)); - expect(boxes[6], const TextBox.fromLTRBD(28.0, 14.0, 42.0, 28.0, TextDirection.ltr)); - // Wraps - expect(boxes[7], const TextBox.fromLTRBD(0.0, 28.0, 14.0, 42.0, TextDirection.ltr)); - expect(boxes[8], const TextBox.fromLTRBD(14.0, 28.0, 28.0, 42.0 , TextDirection.ltr)); - // Ahem-based tests don't yet quite work on Windows or some MacOS environments - }, skip: isWindows || isMacOS); } diff --git a/packages/flutter/test/widgets/backdrop_filter_test.dart b/packages/flutter/test/widgets/backdrop_filter_test.dart index aa27ee3ebd9..c0cff7310ae 100644 --- a/packages/flutter/test/widgets/backdrop_filter_test.dart +++ b/packages/flutter/test/widgets/backdrop_filter_test.dart @@ -10,7 +10,6 @@ import 'package:flutter/material.dart'; void main() { testWidgets('BackdropFilter\'s cull rect does not shrink', (WidgetTester tester) async { - tester.binding.addTime(const Duration(seconds: 15)); await tester.pumpWidget( MaterialApp( home: Scaffold( diff --git a/packages/flutter/test/widgets/basic_test.dart b/packages/flutter/test/widgets/basic_test.dart index 15c69966f81..41e218c4ad7 100644 --- a/packages/flutter/test/widgets/basic_test.dart +++ b/packages/flutter/test/widgets/basic_test.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter/rendering.dart'; + void main() { group('PhysicalShape', () { testWidgets('properties', (WidgetTester tester) async { diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index 015795c489f..ec9748b752d 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -1870,9 +1870,8 @@ void main() { final RenderEditable renderEditable = findRenderEditable(tester); // The actual text span is split into 3 parts with the middle part underlined. expect(renderEditable.text.children.length, 3); - final TextSpan textSpan = renderEditable.text.children[1]; - expect(textSpan.text, 'composing'); - expect(textSpan.style.decoration, TextDecoration.underline); + expect(renderEditable.text.children[1].text, 'composing'); + expect(renderEditable.text.children[1].style.decoration, TextDecoration.underline); focusNode.unfocus(); await tester.pump(); diff --git a/packages/flutter/test/widgets/text_golden_test.dart b/packages/flutter/test/widgets/text_golden_test.dart index bdc0294a46c..5627dcf600c 100644 --- a/packages/flutter/test/widgets/text_golden_test.dart +++ b/packages/flutter/test/widgets/text_golden_test.dart @@ -151,15 +151,16 @@ void main() { decoration: const BoxDecoration( color: Colors.green, ), - child: Text.rich( - TextSpan( + child: RichText( + textDirection: TextDirection.ltr, + text: TextSpan( text: 'text1 ', style: TextStyle( color: translucentGreen, background: Paint() ..color = red.withOpacity(0.5), ), - children: [ + children: [ TextSpan( text: 'text2', style: TextStyle( @@ -170,7 +171,6 @@ void main() { ), ], ), - textDirection: TextDirection.ltr, ), ), ), @@ -242,7 +242,7 @@ void main() { find.byType(Container), matchesGoldenFile('text_golden.StrutDefault.png'), ); - }, skip: true); // Should only be on linux (skip: !isLinux). + }, skip: true); // Should only be on linux (skip: !Platform.isLinux). // Disabled for now until font inconsistency is resolved. testWidgets('Strut text 1', (WidgetTester tester) async { @@ -270,7 +270,7 @@ void main() { find.byType(Container), matchesGoldenFile('text_golden.Strut.1.1.png'), ); - }, skip: true); // Should only be on linux (skip: !isLinux). + }, skip: true); // Should only be on linux (skip: !Platform.isLinux). // Disabled for now until font inconsistency is resolved. testWidgets('Strut text 2', (WidgetTester tester) async { @@ -299,7 +299,7 @@ void main() { find.byType(Container), matchesGoldenFile('text_golden.Strut.2.1.png'), ); - }, skip: true); // Should only be on linux (skip: !isLinux). + }, skip: true); // Should only be on linux (skip: !Platform.isLinux). // Disabled for now until font inconsistency is resolved. testWidgets('Strut text rich', (WidgetTester tester) async { @@ -319,7 +319,7 @@ void main() { color: Colors.red, fontSize: 30, ), - children: [ + children: [ TextSpan( text: 'Second line!\n', style: TextStyle( @@ -351,7 +351,7 @@ void main() { find.byType(Container), matchesGoldenFile('text_golden.Strut.3.1.png'), ); - }, skip: true); // Should only be on linux (skip: !isLinux). + }, skip: true); // Should only be on linux (skip: !Platform.isLinux). // Disabled for now until font inconsistency is resolved. testWidgets('Strut text font fallback', (WidgetTester tester) async { @@ -387,7 +387,7 @@ void main() { find.byType(Container), matchesGoldenFile('text_golden.Strut.4.1.png'), ); - }, skip: true); // Should only be on linux (skip: !isLinux). + }, skip: true); // Should only be on linux (skip: !Platform.isLinux). // Disabled for now until font inconsistency is resolved. testWidgets('Strut text rich forceStrutHeight', (WidgetTester tester) async { @@ -407,7 +407,7 @@ void main() { color: Colors.red, fontSize: 30, ), - children: [ + children: [ TextSpan( text: 'Second line!\n', style: TextStyle( @@ -439,7 +439,7 @@ void main() { find.byType(Container), matchesGoldenFile('text_golden.StrutForce.1.1.png'), ); - }, skip: true); // Should only be on linux (skip: !isLinux). + }, skip: true); // Should only be on linux (skip: !Platform.isLinux). // Disabled for now until font inconsistency is resolved. testWidgets('Decoration thickness', (WidgetTester tester) async { @@ -518,807 +518,4 @@ void main() { matchesGoldenFile('text_golden.DecorationThickness.1.0.png'), ); }, skip: !isLinux); // Coretext uses different thicknesses for decoration - - testWidgets('Text Inline widget', (WidgetTester tester) async { - await tester.pumpWidget( - Center( - child: RepaintBoundary( - child: Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Container( - width: 400.0, - height: 200.0, - decoration: const BoxDecoration( - color: Color(0xff00ff00), - ), - child: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 0, maxWidth: 200, minHeight: 0, maxHeight: 100), - child: const Text.rich( - TextSpan( - text: 'C ', - style: TextStyle( - fontSize: 16, - ), - children: [ - WidgetSpan( - child: Checkbox(value: true, onChanged: null), - ), - WidgetSpan( - child: Checkbox(value: false, onChanged: null), - ), - TextSpan(text: 'He ', style: TextStyle(fontSize: 20)), - WidgetSpan( - child: SizedBox( - width: 50.0, - height: 55.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffffff00), - ), - child: Center( - child:SizedBox( - width: 10.0, - height: 15.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffff0000), - ), - ) - ), - ), - ) - ), - ), - TextSpan(text: 'hello world! sieze the day!'), - WidgetSpan( - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), - ) - ), - WidgetSpan( - child: Checkbox(value: false, onChanged: null), - alignment: PlaceholderAlignment.baseline, - baseline: TextBaseline.alphabetic - ), - WidgetSpan( - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), - ) - ), - WidgetSpan( - child: Text('embedded'), - ), - ], - ), - textDirection: TextDirection.ltr, - ), - ), - ), - ), - ), - ), - ), - ); - await expectLater( - find.byType(Container), - matchesGoldenFile('text_golden.TextInlineWidget.1.1.png'), - ); - }, skip: !isLinux); // Coretext uses different thicknesses for decoration - - testWidgets('Text Inline widget textfield', (WidgetTester tester) async { - await tester.pumpWidget( - Center( - child: MaterialApp( - home: RepaintBoundary( - child: Material( - child: Container( - width: 400.0, - height: 200.0, - decoration: const BoxDecoration( - color: Color(0xff00ff00), - ), - child: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 0, maxWidth: 200, minHeight: 0, maxHeight: 100), - child: const Text.rich( - TextSpan( - text: 'My name is: ', - style: TextStyle( - fontSize: 20, - ), - children: [ - WidgetSpan( - child: SizedBox(width: 70, height: 25, child: TextField()), - ), - TextSpan(text: ', and my favorite city is: ', style: TextStyle(fontSize: 20)), - WidgetSpan( - child: SizedBox(width: 70, height: 25, child: TextField()), - ), - ], - ), - textDirection: TextDirection.ltr, - ), - ), - ), - ), - ), - ), - ), - ); - await expectLater( - find.byType(Container), - matchesGoldenFile('text_golden.TextInlineWidget.2.2.png'), - ); - }, skip: !isLinux); // Coretext uses different thicknesses for decoration - - // This tests if multiple Text.rich widgets are able to inline nest within each other. - testWidgets('Text Inline widget nesting', (WidgetTester tester) async { - await tester.pumpWidget( - Center( - child: MaterialApp( - home: RepaintBoundary( - child: Material( - child: Container( - width: 400.0, - height: 200.0, - decoration: const BoxDecoration( - color: Color(0xff00ff00), - ), - child: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 0, maxWidth: 200, minHeight: 0, maxHeight: 100), - child: const Text.rich( - TextSpan( - text: 'outer', - style: TextStyle( - fontSize: 20, - ), - children: [ - WidgetSpan( - child: Text.rich( - TextSpan( - text: 'inner', - style: TextStyle(color: Color(0xff402f4ff)), - children: [ - WidgetSpan( - child: Text.rich( - TextSpan( - text: 'inner2', - style: TextStyle(color: Color(0xff003ffff)), - children: [ - WidgetSpan( - child: SizedBox( - width: 50.0, - height: 55.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffffff30), - ), - child: Center( - child:SizedBox( - width: 10.0, - height: 15.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xff5f00f0), - ), - ) - ), - ), - ) - ), - ), - ], - ), - ), - ), - WidgetSpan( - child: SizedBox( - width: 50.0, - height: 55.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xff5fff00), - ), - child: Center( - child:SizedBox( - width: 10.0, - height: 15.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xff5f0000), - ), - ) - ), - ), - ) - ), - ), - ], - ), - ), - ), - TextSpan(text: 'outer', style: TextStyle(fontSize: 20)), - WidgetSpan( - child: SizedBox(width: 70, height: 25, child: TextField()), - ), - WidgetSpan( - child: SizedBox( - width: 50.0, - height: 55.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffff00ff), - ), - child: Center( - child:SizedBox( - width: 10.0, - height: 15.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xff0000ff), - ), - ) - ), - ), - ) - ), - ), - ], - ), - textDirection: TextDirection.ltr, - ), - ), - ), - ), - ), - ), - ), - ); - await expectLater( - find.byType(Container), - matchesGoldenFile('text_golden.TextInlineWidgetNest.1.2.png'), - ); - }, skip: !isLinux); // Coretext uses different thicknesses for decoration - - testWidgets('Text Inline widget baseline', (WidgetTester tester) async { - await tester.pumpWidget( - Center( - child: RepaintBoundary( - child: Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Container( - width: 400.0, - height: 200.0, - decoration: const BoxDecoration( - color: Color(0xff00ff00), - ), - child: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 0, maxWidth: 200, minHeight: 0, maxHeight: 100), - child: const Text.rich( - TextSpan( - text: 'C ', - style: TextStyle( - fontSize: 16, - ), - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.baseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: true, onChanged: null), - ), - WidgetSpan( - child: Checkbox(value: false, onChanged: null), - ), - TextSpan(text: 'He ', style: TextStyle(fontSize: 20)), - WidgetSpan( - alignment: PlaceholderAlignment.baseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 50.0, - height: 55.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffffff00), - ), - child: Center( - child:SizedBox( - width: 10.0, - height: 15.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffff0000), - ), - ) - ), - ), - ) - ), - ), - TextSpan(text: 'hello world! sieze the day!'), - WidgetSpan( - alignment: PlaceholderAlignment.baseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.baseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), - ) - ), - WidgetSpan( - alignment: PlaceholderAlignment.baseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.baseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), - ) - ), - WidgetSpan( - alignment: PlaceholderAlignment.baseline, - baseline: TextBaseline.alphabetic, - child: Text('embedded'), - ), - TextSpan(text: 'ref'), - ], - ), - textDirection: TextDirection.ltr, - ), - ), - ), - ), - ), - ), - ), - ); - await expectLater( - find.byType(Container), - matchesGoldenFile('text_golden.TextInlineWidgetBaseline.1.1.png'), - ); - }, skip: !isLinux); // Coretext uses different thicknesses for decoration - - testWidgets('Text Inline widget aboveBaseline', (WidgetTester tester) async { - await tester.pumpWidget( - Center( - child: RepaintBoundary( - child: Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Container( - width: 400.0, - height: 200.0, - decoration: const BoxDecoration( - color: Color(0xff00ff00), - ), - child: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 0, maxWidth: 200, minHeight: 0, maxHeight: 100), - child: const Text.rich( - TextSpan( - text: 'C ', - style: TextStyle( - fontSize: 16, - ), - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.aboveBaseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: true, onChanged: null), - ), - WidgetSpan( - child: Checkbox(value: false, onChanged: null), - ), - TextSpan(text: 'He ', style: TextStyle(fontSize: 20)), - WidgetSpan( - alignment: PlaceholderAlignment.aboveBaseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 50.0, - height: 55.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffffff00), - ), - child: Center( - child:SizedBox( - width: 10.0, - height: 15.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffff0000), - ), - ) - ), - ), - ) - ), - ), - TextSpan(text: 'hello world! sieze the day!'), - WidgetSpan( - alignment: PlaceholderAlignment.aboveBaseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.aboveBaseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), - ) - ), - WidgetSpan( - alignment: PlaceholderAlignment.aboveBaseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.aboveBaseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), - ) - ), - WidgetSpan( - alignment: PlaceholderAlignment.aboveBaseline, - baseline: TextBaseline.alphabetic, - child: Text('embedded'), - ), - TextSpan(text: 'ref'), - ], - ), - textDirection: TextDirection.ltr, - ), - ), - ), - ), - ), - ), - ), - ); - await expectLater( - find.byType(Container), - matchesGoldenFile('text_golden.TextInlineWidgetAboveBaseline.1.1.png'), - ); - }, skip: !isLinux); // Coretext uses different thicknesses for decoration - - testWidgets('Text Inline widget belowBaseline', (WidgetTester tester) async { - await tester.pumpWidget( - Center( - child: RepaintBoundary( - child: Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Container( - width: 400.0, - height: 200.0, - decoration: const BoxDecoration( - color: Color(0xff00ff00), - ), - child: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 0, maxWidth: 200, minHeight: 0, maxHeight: 100), - child: const Text.rich( - TextSpan( - text: 'C ', - style: TextStyle( - fontSize: 16, - ), - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.belowBaseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: true, onChanged: null), - ), - WidgetSpan( - child: Checkbox(value: false, onChanged: null), - ), - TextSpan(text: 'He ', style: TextStyle(fontSize: 20)), - WidgetSpan( - alignment: PlaceholderAlignment.belowBaseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 50.0, - height: 55.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffffff00), - ), - child: Center( - child:SizedBox( - width: 10.0, - height: 15.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffff0000), - ), - ) - ), - ), - ) - ), - ), - TextSpan(text: 'hello world! sieze the day!'), - WidgetSpan( - alignment: PlaceholderAlignment.belowBaseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.belowBaseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), - ) - ), - WidgetSpan( - alignment: PlaceholderAlignment.belowBaseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.belowBaseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), - ) - ), - WidgetSpan( - alignment: PlaceholderAlignment.belowBaseline, - baseline: TextBaseline.alphabetic, - child: Text('embedded'), - ), - TextSpan(text: 'ref'), - ], - ), - textDirection: TextDirection.ltr, - ), - ), - ), - ), - ), - ), - ), - ); - await expectLater( - find.byType(Container), - matchesGoldenFile('text_golden.TextInlineWidgetBelowBaseline.1.1.png'), - ); - }, skip: !isLinux); // Coretext uses different thicknesses for decoration - - testWidgets('Text Inline widget top', (WidgetTester tester) async { - await tester.pumpWidget( - Center( - child: RepaintBoundary( - child: Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Container( - width: 400.0, - height: 200.0, - decoration: const BoxDecoration( - color: Color(0xff00ff00), - ), - child: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 0, maxWidth: 200, minHeight: 0, maxHeight: 100), - child: const Text.rich( - TextSpan( - text: 'C ', - style: TextStyle( - fontSize: 16, - ), - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.top, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: true, onChanged: null), - ), - WidgetSpan( - child: Checkbox(value: false, onChanged: null), - ), - TextSpan(text: 'He ', style: TextStyle(fontSize: 20)), - WidgetSpan( - alignment: PlaceholderAlignment.top, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 50.0, - height: 55.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffffff00), - ), - child: Center( - child:SizedBox( - width: 10.0, - height: 15.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffff0000), - ), - ) - ), - ), - ) - ), - ), - TextSpan(text: 'hello world! sieze the day!'), - WidgetSpan( - alignment: PlaceholderAlignment.top, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.top, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), - ) - ), - WidgetSpan( - alignment: PlaceholderAlignment.top, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.top, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), - ) - ), - WidgetSpan( - alignment: PlaceholderAlignment.top, - baseline: TextBaseline.alphabetic, - child: Text('embedded'), - ), - TextSpan(text: 'ref'), - ], - ), - textDirection: TextDirection.ltr, - ), - ), - ), - ), - ), - ), - ), - ); - await expectLater( - find.byType(Container), - matchesGoldenFile('text_golden.TextInlineWidgetTop.1.1.png'), - ); - }, skip: !isLinux); // Coretext uses different thicknesses for decoration - - testWidgets('Text Inline widget middle', (WidgetTester tester) async { - await tester.pumpWidget( - Center( - child: RepaintBoundary( - child: Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Container( - width: 400.0, - height: 200.0, - decoration: const BoxDecoration( - color: Color(0xff00ff00), - ), - child: ConstrainedBox( - constraints: const BoxConstraints(minWidth: 0, maxWidth: 200, minHeight: 0, maxHeight: 100), - child: const Text.rich( - TextSpan( - text: 'C ', - style: TextStyle( - fontSize: 16, - ), - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.middle, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: true, onChanged: null), - ), - WidgetSpan( - child: Checkbox(value: false, onChanged: null), - ), - TextSpan(text: 'He ', style: TextStyle(fontSize: 20)), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 50.0, - height: 55.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffffff00), - ), - child: Center( - child:SizedBox( - width: 10.0, - height: 15.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffff0000), - ), - ) - ), - ), - ) - ), - ), - TextSpan(text: 'hello world! sieze the day!'), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), - ) - ), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), - ) - ), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - baseline: TextBaseline.alphabetic, - child: Text('embedded'), - ), - TextSpan(text: 'ref'), - ], - ), - textDirection: TextDirection.ltr, - ), - ), - ), - ), - ), - ), - ), - ); - await expectLater( - find.byType(Container), - matchesGoldenFile('text_golden.TextInlineWidgetMiddle.1.1.png'), - ); - }, skip: !isLinux); // Coretext uses different thicknesses for decoration } diff --git a/packages/flutter/test/widgets/text_test.dart b/packages/flutter/test/widgets/text_test.dart index 269263bd8bb..48a89ba1fdc 100644 --- a/packages/flutter/test/widgets/text_test.dart +++ b/packages/flutter/test/widgets/text_test.dart @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import '../rendering/mock_canvas.dart'; import 'semantics_tester.dart'; @@ -294,140 +294,6 @@ void main() { semantics.dispose(); }, skip: true); // TODO(jonahwilliams): correct once https://github.com/flutter/flutter/issues/20891 is resolved. - testWidgets('inline widgets generate semantic nodes', (WidgetTester tester) async { - final SemanticsTester semantics = SemanticsTester(tester); - const TextStyle textStyle = TextStyle(fontFamily: 'Ahem'); - await tester.pumpWidget( - Text.rich( - TextSpan( - children: [ - const TextSpan(text: 'a '), - TextSpan(text: 'pebble', recognizer: TapGestureRecognizer()..onTap = () { }), - const TextSpan(text: ' in the '), - WidgetSpan( - child: SizedBox( - width: 20, - height: 40, - child: Card( - child: RichText( - text: const TextSpan(text: 'INTERRUPTION'), - textDirection: TextDirection.rtl, - ), - ), - ), - ), - const TextSpan(text: 'sky'), - ], - style: textStyle, - ), - textDirection: TextDirection.ltr, - ), - ); - final TestSemantics expectedSemantics = TestSemantics.root( - children: [ - TestSemantics.rootChild( - children: [ - TestSemantics( - label: 'a ', - textDirection: TextDirection.ltr, - ), - TestSemantics( - label: 'pebble', - textDirection: TextDirection.ltr, - actions: [ - SemanticsAction.tap, - ], - ), - TestSemantics( - label: ' in the ', - textDirection: TextDirection.ltr, - ), - TestSemantics( - label: 'INTERRUPTION', - textDirection: TextDirection.rtl, - ), - TestSemantics( - label: 'sky', - textDirection: TextDirection.ltr, - ), - ], - ), - ], - ); - expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreId: true, ignoreRect: true)); - semantics.dispose(); - }); - - testWidgets('inline widgets semantic nodes scale', (WidgetTester tester) async { - final SemanticsTester semantics = SemanticsTester(tester); - const TextStyle textStyle = TextStyle(fontFamily: 'Ahem'); - await tester.pumpWidget( - Text.rich( - TextSpan( - children: [ - const TextSpan(text: 'a '), - TextSpan(text: 'pebble', recognizer: TapGestureRecognizer()..onTap = () { }), - const TextSpan(text: ' in the '), - WidgetSpan( - child: SizedBox( - width: 20, - height: 40, - child: Card( - child: RichText( - text: const TextSpan(text: 'INTERRUPTION'), - textDirection: TextDirection.rtl, - ), - ), - ), - ), - const TextSpan(text: 'sky'), - ], - style: textStyle, - ), - textDirection: TextDirection.ltr, - textScaleFactor: 2, - ), - ); - final TestSemantics expectedSemantics = TestSemantics.root( - children: [ - TestSemantics.rootChild( - rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 600.0), - children: [ - TestSemantics( - label: 'a ', - textDirection: TextDirection.ltr, - rect: const Rect.fromLTRB(-4.0, 48.0, 60.0, 84.0), - ), - TestSemantics( - label: 'pebble', - textDirection: TextDirection.ltr, - actions: [ - SemanticsAction.tap, - ], - rect: const Rect.fromLTRB(52.0, 48.0, 228.0, 84.0), - ), - TestSemantics( - label: ' in the ', - textDirection: TextDirection.ltr, - rect: const Rect.fromLTRB(220.0, 48.0, 452.0, 84.0), - ), - TestSemantics( - label: 'INTERRUPTION', - textDirection: TextDirection.rtl, - rect: const Rect.fromLTRB(448.0, 0.0, 488.0, 80.0), - ), - TestSemantics( - label: 'sky', - textDirection: TextDirection.ltr, - rect: const Rect.fromLTRB(484.0, 48.0, 576.0, 84.0), - ), - ], - ), - ], - ); - expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreId: true,)); - semantics.dispose(); - }); testWidgets('Overflow is clipping correctly - short text with overflow: clip', (WidgetTester tester) async { await _pumpTextWidget( diff --git a/packages/flutter/test/widgets/widget_inspector_test.dart b/packages/flutter/test/widgets/widget_inspector_test.dart index 8db54de1654..046528a64ac 100644 --- a/packages/flutter/test/widgets/widget_inspector_test.dart +++ b/packages/flutter/test/widgets/widget_inspector_test.dart @@ -329,10 +329,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService { } // State type is private, hence using dynamic. dynamic getInspectorState() => inspectorKey.currentState; - String paragraphText(RenderParagraph paragraph) { - final TextSpan textSpan = paragraph.text; - return textSpan.text; - } + String paragraphText(RenderParagraph paragraph) => paragraph.text.text; await tester.pumpWidget( Directionality(