Revert "Text inline widgets, TextSpan rework (#33794)" (#33928)

This reverts commit 86862c1e62.
This commit is contained in:
Gary Qian
2019-06-05 11:35:46 -07:00
committed by GitHub
parent 5a6a664094
commit 2db7918eb5
27 changed files with 245 additions and 2441 deletions
+1 -1
View File
@@ -1 +1 @@
eb9c1d66709a1f8a0291076865fe387ceed96dca
041efaf483a1cd011f4b4f6dcd04e0d5cad9436e
+2 -2
View File
@@ -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>[
TextSpan(
+1 -1
View File
@@ -145,7 +145,7 @@ class _FuzzerState extends State<Fuzzer> with SingleTickerProviderStateMixin {
return TextSpan(
text: _fiddleWithText(node.text),
style: _fiddleWithStyle(node.style),
children: _fiddleWithChildren(node.children?.map((InlineSpan child) => _fiddleWith(child))?.toList() ?? <InlineSpan>[]),
children: _fiddleWithChildren(node.children?.map((TextSpan child) => _fiddleWith(child))?.toList() ?? <TextSpan>[]),
);
}
+1 -3
View File
@@ -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';
@@ -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,
),
@@ -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: <InlineSpan>[
/// 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<PlaceholderDimensions> 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<int> semanticsOffsets, List<dynamic> 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);
}
}
}
@@ -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<int> semanticsOffsets, List<dynamic> 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<ui.PlaceholderAlignment>('alignment', alignment, defaultValue: null));
properties.add(EnumProperty<TextBaseline>('baseline', baseline, defaultValue: null));
}
}
@@ -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,49 +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<TextBox> get inlinePlaceholderBoxes => _inlinePlaceholderBoxes;
List<TextBox> _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<double> get inlinePlaceholderScales => _inlinePlaceholderScales;
List<double> _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.
///
/// 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<PlaceholderDimensions> value) {
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> _placeholderDimensions;
ui.ParagraphStyle _createParagraphStyle([ TextDirection defaultTextDirection ]) {
// The defaultTextDirection argument is used for preferredLineHeight in case
// textDirection hasn't yet been set.
@@ -529,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;
@@ -538,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.
@@ -604,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;
@@ -620,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
@@ -653,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)
@@ -669,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
+120 -119
View File
@@ -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<InlineSpan> children;
final List<TextSpan> 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: <InlineSpan>[
/// children: <TextSpan>[
/// 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<PlaceholderDimensions> 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<int> semanticsOffsets, List<dynamic> 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<InlineSpan>(typedOther.children, children);
&& listEquals<TextSpan>(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<GestureRecognizer>(
'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<DiagnosticsNode> debugDescribeChildren() {
if (children == null)
return const <DiagnosticsNode>[];
return children.map<DiagnosticsNode>((InlineSpan child) {
return children.map<DiagnosticsNode>((TextSpan child) {
if (child != null) {
return child.toDiagnosticsNode();
} else {
+1 -4
View File
@@ -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].
@@ -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,
File diff suppressed because it is too large Load Diff
+4 -20
View File
@@ -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<Widget> _extractChildren(InlineSpan span) {
final List<Widget> result = <Widget>[];
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;
+3 -10
View File
@@ -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.
///
@@ -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
@@ -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: <InlineSpan>[
/// 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<PlaceholderDimensions> 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;
}
}
-1
View File
@@ -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';
@@ -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<TextBox>> list = <List<TextBox>>[];
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 <List<TextBox>>[
<TextBox>[], // 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) {
@@ -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: <InlineSpan>[
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>[
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));
});
}
@@ -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: <TextSpan>[ a1 ]);
const TextSpan b2 = TextSpan(children: <TextSpan>[ 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: <TextSpan>[ a1 ]);
final TextSpan b2 = TextSpan(children: <TextSpan>[ 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: <InlineSpan>[
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: <InlineSpan>[
TextSpan(text: 'b'),
WidgetSpan(child: SizedBox(width: 10, height: 10)),
TextSpan(text: 'c'),
],
);
const TextSpan textSpan2 = TextSpan(
text: 'a',
children: <InlineSpan>[
TextSpan(text: 'b'),
WidgetSpan(child: SizedBox(width: 10, height: 10)),
TextSpan(text: 'c'),
],
);
const TextSpan textSpan3 = TextSpan(
text: 'a',
children: <InlineSpan>[
TextSpan(text: 'b'),
WidgetSpan(child: SizedBox(width: 11, height: 10)),
TextSpan(text: 'c'),
],
);
const TextSpan textSpan4 = TextSpan(
text: 'a',
children: <InlineSpan>[
TextSpan(text: 'b'),
WidgetSpan(child: Text('test')),
TextSpan(text: 'c'),
],
);
const TextSpan textSpan5 = TextSpan(
text: 'a',
children: <InlineSpan>[
TextSpan(text: 'b'),
WidgetSpan(child: Text('different!')),
TextSpan(text: 'c'),
],
);
const TextSpan textSpan6 = TextSpan(
text: 'a',
children: <InlineSpan>[
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: <InlineSpan>[
TextSpan(text: 'b'),
WidgetSpan(
child: Text.rich(
TextSpan(
children: <InlineSpan>[
WidgetSpan(child: SizedBox(width: 10, height: 10)),
TextSpan(text: 'The sky is falling :)')
],
)
),
),
TextSpan(text: 'c'),
],
);
const TextSpan textSpan2 = TextSpan(
text: 'a',
children: <InlineSpan>[
TextSpan(text: 'b'),
WidgetSpan(
child: Text.rich(
TextSpan(
children: <InlineSpan>[
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);
});
}

Some files were not shown because too many files have changed in this diff Show More