diff --git a/examples/layers/rendering/flex_layout.dart b/examples/layers/rendering/flex_layout.dart index 945c83ea340e6..adaa4bf6db8c0 100644 --- a/examples/layers/rendering/flex_layout.dart +++ b/examples/layers/rendering/flex_layout.dart @@ -14,24 +14,24 @@ void main() { void addAlignmentRow(FlexAlignItems alignItems) { TextStyle style = const TextStyle(color: const Color(0xFF000000)); - RenderParagraph paragraph = new RenderParagraph(new StyledTextSpan(style, [new PlainTextSpan('$alignItems')])); + RenderParagraph paragraph = new RenderParagraph(new TextSpan(style: style, text: '$alignItems')); table.add(new RenderPadding(child: paragraph, padding: new EdgeDims.only(top: 20.0))); RenderFlex row = new RenderFlex(alignItems: alignItems, textBaseline: TextBaseline.alphabetic); style = new TextStyle(fontSize: 15.0, color: const Color(0xFF000000)); row.add(new RenderDecoratedBox( decoration: new BoxDecoration(backgroundColor: const Color(0x7FFFCCCC)), - child: new RenderParagraph(new StyledTextSpan(style, [new PlainTextSpan('foo foo foo')])) + child: new RenderParagraph(new TextSpan(style: style, text: 'foo foo foo')) )); style = new TextStyle(fontSize: 10.0, color: const Color(0xFF000000)); row.add(new RenderDecoratedBox( decoration: new BoxDecoration(backgroundColor: const Color(0x7FCCFFCC)), - child: new RenderParagraph(new StyledTextSpan(style, [new PlainTextSpan('foo foo foo')])) + child: new RenderParagraph(new TextSpan(style: style, text: 'foo foo foo')) )); RenderFlex subrow = new RenderFlex(alignItems: alignItems, textBaseline: TextBaseline.alphabetic); style = new TextStyle(fontSize: 25.0, color: const Color(0xFF000000)); subrow.add(new RenderDecoratedBox( decoration: new BoxDecoration(backgroundColor: const Color(0x7FCCCCFF)), - child: new RenderParagraph(new StyledTextSpan(style, [new PlainTextSpan('foo foo foo foo')])) + child: new RenderParagraph(new TextSpan(style: style, text: 'foo foo foo foo')) )); subrow.add(new RenderSolidColorBox(const Color(0x7FCCFFFF), desiredSize: new Size(30.0, 40.0))); row.add(subrow); @@ -48,7 +48,7 @@ void main() { void addJustificationRow(FlexJustifyContent justify) { const TextStyle style = const TextStyle(color: const Color(0xFF000000)); - RenderParagraph paragraph = new RenderParagraph(new StyledTextSpan(style, [new PlainTextSpan('$justify')])); + RenderParagraph paragraph = new RenderParagraph(new TextSpan(style: style, text: '$justify')); table.add(new RenderPadding(child: paragraph, padding: new EdgeDims.only(top: 20.0))); RenderFlex row = new RenderFlex(direction: FlexDirection.horizontal); row.add(new RenderSolidColorBox(const Color(0xFFFFCCCC), desiredSize: new Size(80.0, 60.0))); diff --git a/examples/layers/rendering/hello_world.dart b/examples/layers/rendering/hello_world.dart index 6716b0dd9e6e0..50b6709d56d84 100644 --- a/examples/layers/rendering/hello_world.dart +++ b/examples/layers/rendering/hello_world.dart @@ -16,7 +16,7 @@ void main() { alignment: const FractionalOffset(0.5, 0.5), // We use a RenderParagraph to display the text 'Hello, world.' without // any explicit styling. - child: new RenderParagraph(new PlainTextSpan('Hello, world.')) + child: new RenderParagraph(new TextSpan(text: 'Hello, world.')) ) ); } diff --git a/examples/layers/rendering/touch_input.dart b/examples/layers/rendering/touch_input.dart index 68f442122eae5..05a50fcabb094 100644 --- a/examples/layers/rendering/touch_input.dart +++ b/examples/layers/rendering/touch_input.dart @@ -97,9 +97,9 @@ class RenderDots extends RenderBox { void main() { // Create some styled text to tell the user to interact with the app. RenderParagraph paragraph = new RenderParagraph( - new StyledTextSpan( - new TextStyle(color: Colors.black87), - [ new PlainTextSpan("Touch me!") ] + new TextSpan( + style: new TextStyle(color: Colors.black87), + text: "Touch me!" ) ); // A stack is a render object that layers its children on top of each other. diff --git a/examples/layers/widgets/styled_text.dart b/examples/layers/widgets/styled_text.dart index 646f6e52e713d..abd2ec9bc4cef 100644 --- a/examples/layers/widgets/styled_text.dart +++ b/examples/layers/widgets/styled_text.dart @@ -34,9 +34,24 @@ final TextStyle _kUnderline = const TextStyle( Widget toStyledText(String name, String text) { TextStyle lineStyle = (name == "Dave") ? _kDaveStyle : _kHalStyle; - return new StyledText( + return new RichText( key: new Key(text), - elements: [lineStyle, [_kBold, [_kUnderline, name], ":"], text] + text: new TextSpan( + style: lineStyle, + children: [ + new TextSpan( + style: _kBold, + children: [ + new TextSpan( + style: _kUnderline, + text: name + ), + new TextSpan(text: ':') + ] + ), + new TextSpan(text: text) + ] + ) ); } diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart index e4a262d35396f..59294d39fc706 100644 --- a/packages/flutter/lib/src/material/time_picker.dart +++ b/packages/flutter/lib/src/material/time_picker.dart @@ -240,9 +240,7 @@ List _initPainters(List labels) { for (int i = 0; i < painters.length; ++i) { String label = labels[i]; TextPainter painter = new TextPainter( - new StyledTextSpan(style, [ - new PlainTextSpan(label) - ]) + new TextSpan(style: style, text: label) ); painter ..maxWidth = double.INFINITY diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart index 77234741604fa..c91f21dfd9758 100644 --- a/packages/flutter/lib/src/painting/text_painter.dart +++ b/packages/flutter/lib/src/painting/text_painter.dart @@ -9,92 +9,86 @@ import 'text_editing.dart'; import 'text_style.dart'; /// An immutable span of text. -abstract class TextSpan { - // This class must be immutable, because we won't notice when it changes. - const TextSpan(); - void build(ui.ParagraphBuilder builder); - ui.ParagraphStyle get paragraphStyle => null; - String toPlainText(); // for semantics - String toString([String prefix = '']); // for debugging -} - -/// An immutable span of unstyled text. -class PlainTextSpan extends TextSpan { - const PlainTextSpan(this.text); +class TextSpan { + const TextSpan({ + this.style, + this.text, + this.children + }); + + /// The style to apply to the text and the children. + final TextStyle style; /// The text contained in the span. + /// + /// If both text and children are non-null, the text will preceed the + /// children. final String text; - void build(ui.ParagraphBuilder builder) { - assert(text != null); - builder.addText(text); - } - - bool operator ==(dynamic other) { - if (other is! PlainTextSpan) - return false; - final PlainTextSpan typedOther = other; - return text == typedOther.text; - } - - int get hashCode => text.hashCode; - - String toPlainText() => text; - String toString([String prefix = '']) => '$prefix$runtimeType: "$text"'; -} - -/// An immutable text span that applies a style to a list of children. -class StyledTextSpan extends TextSpan { - const StyledTextSpan(this.style, this.children); - - /// The style to apply to the children. - final TextStyle style; - - /// The children to which the style is applied. + /// Additional spans to include as children. + /// + /// If both text and children are non-null, the text will preceed the + /// children. final List children; void build(ui.ParagraphBuilder builder) { - assert(style != null); - assert(children != null); - builder.pushStyle(style.textStyle); - for (TextSpan child in children) { - assert(child != null); - child.build(builder); + final bool hasStyle = style != null; + if (hasStyle) + builder.pushStyle(style.textStyle); + if (text != null) + builder.addText(text); + if (children != null) { + for (TextSpan child in children) { + assert(child != null); + child.build(builder); + } + } + if (hasStyle) + builder.pop(); + } + + void writePlainText(StringBuffer result) { + if (text != null) + result.write(text); + if (children != null) { + for (TextSpan child in children) + child.writePlainText(result); } - builder.pop(); } - ui.ParagraphStyle get paragraphStyle => style.paragraphStyle; + String toString([String prefix = '']) { + StringBuffer buffer = new StringBuffer(); + buffer.writeln('$prefix$runtimeType:'); + String indent = '$prefix '; + buffer.writeln(style.toString(indent)); + if (text != null) + buffer.writeln('$indent"$text"'); + for (TextSpan child in children) + buffer.writeln(child.toString(indent)); + return buffer.toString(); + } bool operator ==(dynamic other) { if (identical(this, other)) return true; - if (other is! StyledTextSpan) + if (other is! TextSpan) return false; - final StyledTextSpan typedOther = other; - if (style != typedOther.style || - children.length != typedOther.children.length) + final TextSpan typedOther = other; + if (typedOther.text != text) return false; - for (int i = 0; i < children.length; ++i) { - if (children[i] != typedOther.children[i]) - return false; + if (typedOther.style != style) + return false; + if ((typedOther.children == null) != (children == null)) + return false; + if (children != null) { + for (int i = 0; i < children.length; ++i) { + if (typedOther.children[i] != children[i]) + return false; + } } return true; } - - int get hashCode => hashValues(style, hashList(children)); - - String toPlainText() => children.map((TextSpan child) => child.toPlainText()).join(); - - String toString([String prefix = '']) { - List result = []; - result.add('$prefix$runtimeType:'); - var indent = '$prefix '; - result.add('${style.toString(indent)}'); - for (TextSpan child in children) - result.add(child.toString(indent)); - return result.join('\n'); - } + int get hashCode => hashValues(style, text, hashList(children)); } /// An object that paints a [TextSpan] into a canvas. @@ -115,7 +109,7 @@ class TextPainter { _text = value; ui.ParagraphBuilder builder = new ui.ParagraphBuilder(); _text.build(builder); - _paragraph = builder.build(_text.paragraphStyle ?? new ui.ParagraphStyle()); + _paragraph = builder.build(_text.style?.paragraphStyle ?? new ui.ParagraphStyle()); _needsLayout = true; } diff --git a/packages/flutter/lib/src/rendering/editable_line.dart b/packages/flutter/lib/src/rendering/editable_line.dart index bc4ad25cae9cb..05e7c21df7373 100644 --- a/packages/flutter/lib/src/rendering/editable_line.dart +++ b/packages/flutter/lib/src/rendering/editable_line.dart @@ -19,7 +19,7 @@ final String _kZeroWidthSpace = new String.fromCharCode(0x200B); /// A single line of editable text. class RenderEditableLine extends RenderBox { RenderEditableLine({ - StyledTextSpan text, + TextSpan text, Color cursorColor, bool showCursor: false, Color selectionColor, @@ -49,12 +49,12 @@ class RenderEditableLine extends RenderBox { ValueChanged onSelectionChanged; /// The text to display - StyledTextSpan get text => _textPainter.text; + TextSpan get text => _textPainter.text; final TextPainter _textPainter; - void set text(StyledTextSpan value) { + void set text(TextSpan value) { if (_textPainter.text == value) return; - StyledTextSpan oldStyledText = _textPainter.text; + TextSpan oldStyledText = _textPainter.text; if (oldStyledText.style != value.style) _layoutTemplate = null; _textPainter.text = value; diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart index 330af42451bcd..b622c3f4b1e35 100644 --- a/packages/flutter/lib/src/rendering/paragraph.dart +++ b/packages/flutter/lib/src/rendering/paragraph.dart @@ -100,7 +100,9 @@ class RenderParagraph extends RenderBox { Iterable getSemanticAnnotators() sync* { yield (SemanticsNode node) { - node.label = text.toPlainText(); + StringBuffer buffer = new StringBuffer(); + text.writePlainText(buffer); + node.label = buffer.toString(); }; } diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 94141b55464b4..cbd126a4ee5bc 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -1405,12 +1405,12 @@ class Flexible extends ParentDataWidget { } } -/// A raw paragraph of text. +/// A paragraph of rich text. /// /// This class is rarely used directly. Instead, consider using [Text], which /// integrates with [DefaultTextStyle]. -class RawText extends LeafRenderObjectWidget { - RawText({ Key key, this.text }) : super(key: key) { +class RichText extends LeafRenderObjectWidget { + RichText({ Key key, this.text }) : super(key: key) { assert(text != null); } @@ -1418,45 +1418,11 @@ class RawText extends LeafRenderObjectWidget { RenderParagraph createRenderObject() => new RenderParagraph(text); - void updateRenderObject(RenderParagraph renderObject, RawText oldWidget) { + void updateRenderObject(RenderParagraph renderObject, RichText oldWidget) { renderObject.text = text; } } -/// A convience widget for paragraphs of text with heterogeneous style. -/// -/// The elements parameter is a recursive list of lists that matches the -/// following grammar: -/// -/// `elements ::= "string" | [ *]`` -/// -/// Where "string" is text to display and text-style is an instance of -/// TextStyle. The text-style applies to all of the elements that follow. -class StyledText extends StatelessComponent { - StyledText({ this.elements, Key key }) : super(key: key) { - assert(_toSpan(elements) != null); - } - - /// The recursive list of lists that describes the text and style to paint. - final dynamic elements; - - TextSpan _toSpan(dynamic element) { - if (element is String) - return new PlainTextSpan(element); - if (element is Iterable) { - dynamic first = element.first; - if (first is! TextStyle) - throw new ArgumentError("First element of Iterable is a ${first.runtimeType} not a TextStyle"); - return new StyledTextSpan(first, element.skip(1).map(_toSpan).toList()); - } - throw new ArgumentError("Element is ${element.runtimeType} not a String or an Iterable"); - } - - Widget build(BuildContext context) { - return new RawText(text: _toSpan(elements)); - } -} - /// The text style to apply to descendant [Text] widgets without explicit style. class DefaultTextStyle extends InheritedWidget { DefaultTextStyle({ @@ -1504,17 +1470,20 @@ class Text extends StatelessComponent { /// replace the closest enclosing [DefaultTextStyle]. final TextStyle style; + TextStyle _getEffectiveStyle(BuildContext context) { + if (style == null || style.inherit) + return DefaultTextStyle.of(context)?.merge(style) ?? style; + else + return style; + } + Widget build(BuildContext context) { - TextSpan text = new PlainTextSpan(data); - TextStyle combinedStyle; - if (style == null || style.inherit) { - combinedStyle = DefaultTextStyle.of(context)?.merge(style) ?? style; - } else { - combinedStyle = style; - } - if (combinedStyle != null) - text = new StyledTextSpan(combinedStyle, [text]); - return new RawText(text: text); + return new RichText( + text: new TextSpan( + style: _getEffectiveStyle(context), + text: data + ) + ); } void debugFillDescription(List description) { diff --git a/packages/flutter/lib/src/widgets/checked_mode_banner.dart b/packages/flutter/lib/src/widgets/checked_mode_banner.dart index c92318f8dce63..2aa8af634bd33 100644 --- a/packages/flutter/lib/src/widgets/checked_mode_banner.dart +++ b/packages/flutter/lib/src/widgets/checked_mode_banner.dart @@ -25,7 +25,7 @@ class _CheckedModeBannerPainter extends CustomPainter { ); static final TextPainter textPainter = new TextPainter() - ..text = new StyledTextSpan(kTextStyles, [new PlainTextSpan('SLOW MODE')]) + ..text = new TextSpan(style: kTextStyles, text: 'SLOW MODE') ..maxWidth = kOffset * 2.0 ..maxHeight = kHeight ..layout(); diff --git a/packages/flutter/lib/src/widgets/editable.dart b/packages/flutter/lib/src/widgets/editable.dart index b1bf089253f4f..a0ed888550123 100644 --- a/packages/flutter/lib/src/widgets/editable.dart +++ b/packages/flutter/lib/src/widgets/editable.dart @@ -112,7 +112,7 @@ class InputValue { return typedOther.text == text && typedOther.selection == selection && typedOther.composing == composing; - } + } int get hashCode => hashValues( text.hashCode, @@ -126,7 +126,7 @@ class InputValue { TextRange composing }) { return new InputValue ( - text: text ?? this.text, + text: text ?? this.text, selection: selection ?? this.selection, composing: composing ?? this.composing ); @@ -394,24 +394,27 @@ class _EditableLineWidget extends LeafRenderObjectWidget { ..paintOffset = paintOffset; } - StyledTextSpan get _styledTextSpan { + TextSpan get _styledTextSpan { if (!hideText && value.composing.isValid) { TextStyle composingStyle = style.merge( const TextStyle(decoration: TextDecoration.underline) ); - return new StyledTextSpan(style, [ - new PlainTextSpan(value.composing.textBefore(value.text)), - new StyledTextSpan(composingStyle, [ - new PlainTextSpan(value.composing.textInside(value.text)) - ]), - new PlainTextSpan(value.composing.textAfter(value.text)) + return new TextSpan( + style: style, + children: [ + new TextSpan(text: value.composing.textBefore(value.text)), + new TextSpan( + style: composingStyle, + text: value.composing.textInside(value.text) + ), + new TextSpan(text: value.composing.textAfter(value.text)) ]); } String text = value.text; if (hideText) text = new String.fromCharCodes(new List.filled(text.length, 0x2022)); - return new StyledTextSpan(style, [ new PlainTextSpan(text) ]); + return new TextSpan(style: style, text: text); } } diff --git a/packages/flutter/lib/src/widgets/semantics_debugger.dart b/packages/flutter/lib/src/widgets/semantics_debugger.dart index 0d4cefa1efad6..45e92506eee84 100644 --- a/packages/flutter/lib/src/widgets/semantics_debugger.dart +++ b/packages/flutter/lib/src/widgets/semantics_debugger.dart @@ -172,7 +172,7 @@ class _SemanticsDebuggerEntry { message = message.trim(); if (message != '') { textPainter ??= new TextPainter(); - textPainter.text = new StyledTextSpan(textStyles, [new PlainTextSpan(message)]); + textPainter.text = new TextSpan(style: textStyles, text: message); textPainter.maxWidth = rect.width; textPainter.maxHeight = rect.height; textPainter.layout(); diff --git a/packages/flutter/test/rendering/block_test.dart b/packages/flutter/test/rendering/block_test.dart index 11d23b450bb78..aa4b98f9400f1 100644 --- a/packages/flutter/test/rendering/block_test.dart +++ b/packages/flutter/test/rendering/block_test.dart @@ -15,11 +15,9 @@ class TestBlockPainter extends Painter { void main() { test('block intrinsics', () { RenderParagraph paragraph = new RenderParagraph( - new StyledTextSpan( - new TextStyle( - height: 1.0 - ), - [new PlainTextSpan('Hello World')] + new TextSpan( + style: new TextStyle(height: 1.0), + text: 'Hello World' ) ); const BoxConstraints unconstrained = const BoxConstraints(); diff --git a/packages/flutter/test/rendering/overflow_test.dart b/packages/flutter/test/rendering/overflow_test.dart index d8fbacdd903db..187fb441740fa 100644 --- a/packages/flutter/test/rendering/overflow_test.dart +++ b/packages/flutter/test/rendering/overflow_test.dart @@ -15,7 +15,7 @@ void main() { root = new RenderPositionedBox( child: new RenderCustomPaint( - child: child = text = new RenderParagraph(new PlainTextSpan('Hello World')), + child: child = text = new RenderParagraph(new TextSpan(text: 'Hello World')), painter: new TestCallbackPainter( onPaint: () { baseline1 = child.getDistanceToBaseline(TextBaseline.alphabetic); @@ -29,7 +29,7 @@ void main() { root = new RenderPositionedBox( child: new RenderCustomPaint( child: child = new RenderOverflowBox( - child: text = new RenderParagraph(new PlainTextSpan('Hello World')), + child: text = new RenderParagraph(new TextSpan(text: 'Hello World')), maxHeight: height1 / 2.0, alignment: const FractionalOffset(0.0, 0.0) ), diff --git a/packages/flutter_sprites/lib/src/label.dart b/packages/flutter_sprites/lib/src/label.dart index 69b8f09d7f477..09bfa552c5823 100644 --- a/packages/flutter_sprites/lib/src/label.dart +++ b/packages/flutter_sprites/lib/src/label.dart @@ -35,9 +35,7 @@ class Label extends Node { void paint(Canvas canvas) { if (_painter == null) { - PlainTextSpan textSpan = new PlainTextSpan(_text); - StyledTextSpan styledTextSpan = new StyledTextSpan(_textStyle, [textSpan]); - _painter = new TextPainter(styledTextSpan); + _painter = new TextPainter(new TextSpan(style: _textStyle, text: _text)); _painter.maxWidth = double.INFINITY; _painter.minWidth = 0.0; diff --git a/packages/playfair/lib/src/base.dart b/packages/playfair/lib/src/base.dart index e69edced7ac4c..9da7512d5a7e0 100644 --- a/packages/playfair/lib/src/base.dart +++ b/packages/playfair/lib/src/base.dart @@ -166,9 +166,9 @@ class ChartPainter { ..value = _roundToPlaces(data.startY + stepSize * i, data.roundToPlaces); if (gridline.value < data.startY || gridline.value > data.endY) continue; // TODO(jackson): Align things so this doesn't ever happen - TextSpan text = new StyledTextSpan( - _textTheme.body1, - [new PlainTextSpan("${gridline.value}")] + TextSpan text = new TextSpan( + style: _textTheme.body1, + text: '${gridline.value}' ); gridline.labelPainter = new TextPainter(text) ..maxWidth = _rect.width @@ -213,9 +213,9 @@ class ChartPainter { ..start = _convertPointToRectSpace(new Point(data.startX, data.indicatorLine), markerRect) ..end = _convertPointToRectSpace(new Point(data.endX, data.indicatorLine), markerRect); if (data.indicatorText != null) { - TextSpan text = new StyledTextSpan( - _textTheme.body1, - [new PlainTextSpan("${data.indicatorText}")] + TextSpan text = new TextSpan( + style: _textTheme.body1, + text: '${data.indicatorText}' ); _indicator.labelPainter = new TextPainter(text) ..maxWidth = markerRect.width