Commit 2514d8a6 authored by Adam Barth's avatar Adam Barth

Merge pull request #2135 from abarth/improve_text_span

Improve TextSpan
parents 586ab30c fb4dbf45
......@@ -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, <TextSpan>[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, <TextSpan>[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, <TextSpan>[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, <TextSpan>[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, <TextSpan>[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)));
......
......@@ -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.'))
)
);
}
......@@ -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),
<TextSpan>[ 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.
......
......@@ -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: <TextSpan>[
new TextSpan(
style: _kBold,
children: <TextSpan>[
new TextSpan(
style: _kUnderline,
text: name
),
new TextSpan(text: ':')
]
),
new TextSpan(text: text)
]
)
);
}
......
......@@ -240,9 +240,7 @@ List<TextPainter> _initPainters(List<String> 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
......
......@@ -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<TextSpan> 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<String> result = <String>[];
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;
}
......
......@@ -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<TextSelection> 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;
......
......@@ -100,7 +100,9 @@ class RenderParagraph extends RenderBox {
Iterable<SemanticAnnotator> getSemanticAnnotators() sync* {
yield (SemanticsNode node) {
node.label = text.toPlainText();
StringBuffer buffer = new StringBuffer();
text.writePlainText(buffer);
node.label = buffer.toString();
};
}
......
......@@ -1405,12 +1405,12 @@ class Flexible extends ParentDataWidget<Flex> {
}
}
/// 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" | [<text-style> <elements>*]``
///
/// 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, <TextSpan>[text]);
return new RawText(text: text);
return new RichText(
text: new TextSpan(
style: _getEffectiveStyle(context),
text: data
)
);
}
void debugFillDescription(List<String> description) {
......
......@@ -25,7 +25,7 @@ class _CheckedModeBannerPainter extends CustomPainter {
);
static final TextPainter textPainter = new TextPainter()
..text = new StyledTextSpan(kTextStyles, <TextSpan>[new PlainTextSpan('SLOW MODE')])
..text = new TextSpan(style: kTextStyles, text: 'SLOW MODE')
..maxWidth = kOffset * 2.0
..maxHeight = kHeight
..layout();
......
......@@ -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, <TextSpan>[
new PlainTextSpan(value.composing.textBefore(value.text)),
new StyledTextSpan(composingStyle, <TextSpan>[
new PlainTextSpan(value.composing.textInside(value.text))
]),
new PlainTextSpan(value.composing.textAfter(value.text))
return new TextSpan(
style: style,
children: <TextSpan>[
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<int>.filled(text.length, 0x2022));
return new StyledTextSpan(style, <TextSpan>[ new PlainTextSpan(text) ]);
return new TextSpan(style: style, text: text);
}
}
......@@ -172,7 +172,7 @@ class _SemanticsDebuggerEntry {
message = message.trim();
if (message != '') {
textPainter ??= new TextPainter();
textPainter.text = new StyledTextSpan(textStyles, <TextSpan>[new PlainTextSpan(message)]);
textPainter.text = new TextSpan(style: textStyles, text: message);
textPainter.maxWidth = rect.width;
textPainter.maxHeight = rect.height;
textPainter.layout();
......
......@@ -15,11 +15,9 @@ class TestBlockPainter extends Painter {
void main() {
test('block intrinsics', () {
RenderParagraph paragraph = new RenderParagraph(
new StyledTextSpan(
new TextStyle(
height: 1.0
),
<TextSpan>[new PlainTextSpan('Hello World')]
new TextSpan(
style: new TextStyle(height: 1.0),
text: 'Hello World'
)
);
const BoxConstraints unconstrained = const BoxConstraints();
......
......@@ -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)
),
......
......@@ -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>[textSpan]);
_painter = new TextPainter(styledTextSpan);
_painter = new TextPainter(new TextSpan(style: _textStyle, text: _text));
_painter.maxWidth = double.INFINITY;
_painter.minWidth = 0.0;
......
......@@ -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,
<TextSpan>[new PlainTextSpan("${data.indicatorText}")]
TextSpan text = new TextSpan(
style: _textTheme.body1,
text: '${data.indicatorText}'
);
_indicator.labelPainter = new TextPainter(text)
..maxWidth = markerRect.width
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment