Commit e41120bc authored by Adam Barth's avatar Adam Barth

Improve the TextPainter API (#3621)

Instead of using properties, TextPainter now receives min and max width as
parameters to layout. Also, this patch integrates the intrinsic sizing logic
into the main layout function, which satisfies all the existing uses cases.
parent 53db3949
......@@ -3,7 +3,7 @@
// found in the LICENSE file.
/// The Flutter painting library.
///
///
/// To use, import `package:flutter/painting.dart`.
///
/// This library includes a variety of classes that wrap the Flutter
......@@ -24,5 +24,6 @@ export 'src/painting/decoration.dart';
export 'src/painting/edge_insets.dart';
export 'src/painting/text_editing.dart';
export 'src/painting/text_painter.dart';
export 'src/painting/text_span.dart';
export 'src/painting/text_style.dart';
export 'src/painting/transforms.dart';
......@@ -253,7 +253,7 @@ class _RenderSlider extends RenderConstrainedBox {
style: Typography.white.body1.copyWith(fontSize: 10.0),
text: newLabel
)
..layoutToMaxIntrinsicWidth();
..layout();
} else {
_labelPainter.text = null;
}
......
......@@ -265,7 +265,7 @@ List<TextPainter> _initPainters(List<String> labels) {
String label = labels[i];
painters[i] = new TextPainter(
new TextSpan(style: style, text: label)
)..layoutToMaxIntrinsicWidth();
)..layout();
}
return painters;
}
......
......@@ -2,233 +2,14 @@
// 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 Paragraph, ParagraphBuilder, ParagraphStyle, TextBox;
import 'dart:ui' as ui show Paragraph, ParagraphBuilder, ParagraphConstraints, ParagraphStyle, TextBox;
import 'package:flutter/gestures.dart';
import 'package:flutter/foundation.dart';
import 'basic_types.dart';
import 'text_editing.dart';
import 'text_style.dart';
// TODO(abarth): Should this be somewhere more general?
bool _deepEquals(List<Object> a, List<Object> b) {
if (a == null)
return b == null;
if (b == null || a.length != b.length)
return false;
for (int i = 0; i < a.length; ++i) {
if (a[i] != b[i])
return false;
}
return true;
}
/// An immutable span of text.
///
/// A [TextSpan] object can be styled using its [style] property.
/// The style will be applied to the [text] and the [children].
///
/// A [TextSpan] object can just have plain text, or it can have
/// children [TextSpan] objects with their own styles that (possibly
/// 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.
///
/// 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
/// using the [Text] widget.
///
/// See also:
///
/// * [Text]
/// * [RichText]
/// * [TextPainter]
class TextSpan {
/// 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,
this.recognizer
});
/// 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;
/// Additional spans to include as children.
///
/// If both [text] and [children] are non-null, the text will preceed the
/// children.
///
/// Modifying the list after the [TextSpan] has been created is not
/// supported and may have unexpected results.
///
/// The list must not contain any nulls.
final List<TextSpan> children;
/// A gesture recognizer that will receive events that hit this text span.
///
/// [TextSpan] itself does not implement hit testing or event
/// dispatch. The owner of the [TextSpan] tree to which the object
/// belongs is responsible for dispatching events.
///
/// For an example, see [RenderParagraph] in the Flutter rendering library.
final GestureRecognizer recognizer;
/// Apply the [style], [text], and [children] of this object to the
/// given [ParagraphBuilder], from which a [Paragraph] can be obtained.
/// [Paragraph] objects can be drawn on [Canvas] objects.
///
/// Rather than using this directly, it's simpler to use the
/// [TextPainter] class to paint [TextSpan] objects onto [Canvas]
/// objects.
void build(ui.ParagraphBuilder builder) {
assert(debugAssertValid());
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();
}
/// Walks this text span and its decendants in pre-order and calls [visitor] for each span that has text.
bool visitTextSpan(bool visitor(TextSpan span)) {
if (text != null) {
if (!visitor(this))
return false;
}
if (children != null) {
for (TextSpan child in children) {
if (!child.visitTextSpan(visitor))
return false;
}
}
return true;
}
/// Returns the text span that contains the given position in the text.
TextSpan getSpanForPosition(TextPosition position) {
assert(debugAssertValid());
TextAffinity affinity = position.affinity;
int targetOffset = position.offset;
int offset = 0;
TextSpan result;
visitTextSpan((TextSpan span) {
assert(result == null);
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;
}
/// Flattens the [TextSpan] tree into a single string.
///
/// Styles are not honored in this process.
String toPlainText() {
assert(debugAssertValid());
StringBuffer buffer = new StringBuffer();
visitTextSpan((TextSpan span) {
buffer.write(span.text);
return true;
});
return buffer.toString();
}
@override
String toString([String prefix = '']) {
StringBuffer buffer = new StringBuffer();
buffer.writeln('$prefix$runtimeType:');
String indent = '$prefix ';
if (style != null)
buffer.writeln(style.toString(indent));
if (text != null)
buffer.writeln('$indent"$text"');
if (children != null) {
for (TextSpan child in children) {
if (child != null) {
buffer.write(child.toString(indent));
} else {
buffer.writeln('$indent<null>');
}
}
}
if (style == null && text == null && children == null)
buffer.writeln('$indent(empty)');
return buffer.toString();
}
/// 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(myTextSpan.debugAssertValid());
/// ```
bool debugAssertValid() {
assert(() {
if (!visitTextSpan((TextSpan span) {
if (span.children != null) {
for (TextSpan child in span.children) {
if (child == null)
return false;
}
}
return true;
})) {
throw new 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'
'${toString(" ")}'
);
}
return true;
});
return true;
}
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! TextSpan)
return false;
final TextSpan typedOther = other;
return typedOther.text == text
&& typedOther.style == style
&& typedOther.recognizer == recognizer
&& _deepEquals(typedOther.children, children);
}
@override
int get hashCode => hashValues(style, text, recognizer, hashList(children));
}
import 'text_span.dart';
/// An object that paints a [TextSpan] tree into a [Canvas].
///
......@@ -237,12 +18,9 @@ class TextSpan {
/// 1. Create a [TextSpan] tree and pass it to the [TextPainter]
/// constructor.
///
/// 2. Set the [maxWidth] property of the [TextPainter] to the width
/// of the area into which the text should be painted.
/// 2. Call [layout] to prepare the paragraph.
///
/// 3. Call [layout] to prepare the paragraph.
///
/// 4. Call [paint] as often as desired to paint the paragraph.
/// 3. Call [paint] as often as desired to paint the paragraph.
///
/// If the width of the area into which the text is being painted
/// changes, return to step 2. If the text to be painted changes,
......@@ -251,7 +29,7 @@ class TextPainter {
/// Creates a text painter that paints the given text.
///
/// The text argument is optional but [text] must be non-null before calling
/// [layout] or [layoutToMaxIntrinsicWidth].
/// [layout].
TextPainter([ TextSpan text ]) {
this.text = text;
}
......@@ -278,49 +56,6 @@ class TextPainter {
}
}
/// The minimum width at which to layout the text.
///
/// Requires [layout] to be called again before painting.
double get minWidth => _paragraph.minWidth;
void set minWidth(double value) {
if (_paragraph.minWidth == value)
return;
_paragraph.minWidth = value;
_needsLayout = true;
}
/// The maximum width at which to layout the text.
///
/// Requires [layout] to be called again before painting.
double get maxWidth => _paragraph.maxWidth;
void set maxWidth(double value) {
if (_paragraph.maxWidth == value)
return;
_paragraph.maxWidth = value;
_needsLayout = true;
}
/// The minimum height at which to layout the text.
///
/// Requires [layout] or [layoutToMaxIntrinsicWidth] to be called again before painting.
double get minHeight => _paragraph.minHeight;
void set minHeight(double value) {
if (_paragraph.minHeight == value)
return;
_paragraph.minHeight = value;
_needsLayout = true;
}
/// The maximum height at which to layout the text.
///
/// Requires [layout] or [layoutToMaxIntrinsicWidth] to be called again before painting.
double get maxHeight => _paragraph.maxHeight;
void set maxHeight(double value) {
if (_paragraph.maxHeight == value)
return;
_paragraph.maxHeight = value;
}
// Unfortunately, using full precision floating point here causes bad layouts
// because floating point math isn't associative. If we add and subtract
// padding, for example, we'll get different values when we estimate sizes and
......@@ -334,7 +69,7 @@ class TextPainter {
/// The width at which decreasing the width of the text would prevent it from painting itself completely within its bounds.
///
/// Valid only after [layout] or [layoutToMaxIntrinsicWidth] has been called.
/// Valid only after [layout] has been called.
double get minIntrinsicWidth {
assert(!_needsLayout);
return _applyFloatingPointHack(_paragraph.minIntrinsicWidth);
......@@ -342,7 +77,7 @@ class TextPainter {
/// The width at which increasing the width of the text no longer decreases the height.
///
/// Valid only after [layout] or [layoutToMaxIntrinsicWidth] has been called.
/// Valid only after [layout] has been called.
double get maxIntrinsicWidth {
assert(!_needsLayout);
return _applyFloatingPointHack(_paragraph.maxIntrinsicWidth);
......@@ -350,7 +85,7 @@ class TextPainter {
/// The horizontal space required to paint this text.
///
/// Valid only after [layout] or [layoutToMaxIntrinsicWidth] has been called.
/// Valid only after [layout] has been called.
double get width {
assert(!_needsLayout);
return _applyFloatingPointHack(_paragraph.width);
......@@ -358,7 +93,7 @@ class TextPainter {
/// The vertical space required to paint this text.
///
/// Valid only after [layout] or [layoutToMaxIntrinsicWidth] has been called.
/// Valid only after [layout] has been called.
double get height {
assert(!_needsLayout);
return _applyFloatingPointHack(_paragraph.height);
......@@ -366,7 +101,7 @@ class TextPainter {
/// The amount of space required to paint this text.
///
/// Valid only after [layout] or [layoutToMaxIntrinsicWidth] has been called.
/// Valid only after [layout] has been called.
Size get size {
assert(!_needsLayout);
return new Size(width, height);
......@@ -374,7 +109,7 @@ class TextPainter {
/// Returns the distance from the top of the text to the first baseline of the given type.
///
/// Valid only after [layout] or [layoutToMaxIntrinsicWidth] has been called.
/// Valid only after [layout] has been called.
double computeDistanceToActualBaseline(TextBaseline baseline) {
assert(!_needsLayout);
switch (baseline) {
......@@ -385,40 +120,31 @@ class TextPainter {
}
}
bool _lastLayoutWasToMaxIntrinsicWidth = false;
double _lastMinWidth;
double _lastMaxWidth;
/// Computes the visual position of the glyphs for painting the text.
void layout() {
if (!_needsLayout)
return;
_paragraph.layout();
_needsLayout = false;
_lastLayoutWasToMaxIntrinsicWidth = false;
}
/// Computes the visual position of the glyphs using the unconstrainted max intrinsic width.
///
/// Overwrites the previously configured [minWidth] and [maxWidth] values.
void layoutToMaxIntrinsicWidth() {
if (!_needsLayout && _lastLayoutWasToMaxIntrinsicWidth && width == maxIntrinsicWidth)
/// The text will layout with a width that's as close to its max intrinsic
/// width as possible while still being greater than or equal to minWidth and
/// less than or equal to maxWidth.
void layout({ double minWidth: 0.0, double maxWidth: double.INFINITY }) {
if (!_needsLayout && minWidth == _lastMinWidth && maxWidth == _lastMaxWidth)
return;
_needsLayout = false;
_lastLayoutWasToMaxIntrinsicWidth = true;
_paragraph
..minWidth = 0.0
..maxWidth = double.INFINITY
..layout();
final double newMaxIntrinsicWidth = maxIntrinsicWidth;
_paragraph
..minWidth = newMaxIntrinsicWidth
..maxWidth = newMaxIntrinsicWidth
..layout();
assert(width == maxIntrinsicWidth);
_lastMinWidth = minWidth;
_lastMaxWidth = maxWidth;
_paragraph.layout(new ui.ParagraphConstraints(width: maxWidth));
if (minWidth != maxWidth) {
final double newWidth = maxIntrinsicWidth.clamp(minWidth, maxWidth);
if (newWidth != width)
_paragraph.layout(new ui.ParagraphConstraints(width: newWidth));
}
}
/// Paints the text onto the given canvas at the given offset.
///
/// Valid only after [layout] or [layoutToMaxIntrinsicWidth] has been called.
/// Valid only after [layout] has been called.
void paint(Canvas canvas, Offset offset) {
assert(() {
if (_needsLayout) {
......@@ -454,7 +180,7 @@ class TextPainter {
/// Returns the offset at which to paint the caret.
///
/// Valid only after [layout] or [layoutToMaxIntrinsicWidth] has been called.
/// Valid only after [layout] has been called.
Offset getOffsetForCaret(TextPosition position, Rect caretPrototype) {
assert(!_needsLayout);
int offset = position.offset;
......
// 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/gestures.dart';
import 'package:flutter/foundation.dart';
import 'basic_types.dart';
import 'text_editing.dart';
import 'text_style.dart';
// TODO(abarth): Should this be somewhere more general?
bool _deepEquals(List<Object> a, List<Object> b) {
if (a == null)
return b == null;
if (b == null || a.length != b.length)
return false;
for (int i = 0; i < a.length; ++i) {
if (a[i] != b[i])
return false;
}
return true;
}
/// An immutable span of text.
///
/// A [TextSpan] object can be styled using its [style] property.
/// The style will be applied to the [text] and the [children].
///
/// A [TextSpan] object can just have plain text, or it can have
/// children [TextSpan] objects with their own styles that (possibly
/// 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.
///
/// 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
/// using the [Text] widget.
///
/// See also:
///
/// * [Text]
/// * [RichText]
/// * [TextPainter]
class TextSpan {
/// 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,
this.recognizer
});
/// 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;
/// Additional spans to include as children.
///
/// If both [text] and [children] are non-null, the text will preceed the
/// children.
///
/// Modifying the list after the [TextSpan] has been created is not
/// supported and may have unexpected results.
///
/// The list must not contain any nulls.
final List<TextSpan> children;
/// A gesture recognizer that will receive events that hit this text span.
///
/// [TextSpan] itself does not implement hit testing or event
/// dispatch. The owner of the [TextSpan] tree to which the object
/// belongs is responsible for dispatching events.
///
/// For an example, see [RenderParagraph] in the Flutter rendering library.
final GestureRecognizer recognizer;
/// Apply the [style], [text], and [children] of this object to the
/// given [ParagraphBuilder], from which a [Paragraph] can be obtained.
/// [Paragraph] objects can be drawn on [Canvas] objects.
///
/// Rather than using this directly, it's simpler to use the
/// [TextPainter] class to paint [TextSpan] objects onto [Canvas]
/// objects.
void build(ui.ParagraphBuilder builder) {
assert(debugAssertValid());
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();
}
/// Walks this text span and its decendants in pre-order and calls [visitor] for each span that has text.
bool visitTextSpan(bool visitor(TextSpan span)) {
if (text != null) {
if (!visitor(this))
return false;
}
if (children != null) {
for (TextSpan child in children) {
if (!child.visitTextSpan(visitor))
return false;
}
}
return true;
}
/// Returns the text span that contains the given position in the text.
TextSpan getSpanForPosition(TextPosition position) {
assert(debugAssertValid());
TextAffinity affinity = position.affinity;
int targetOffset = position.offset;
int offset = 0;
TextSpan result;
visitTextSpan((TextSpan span) {
assert(result == null);
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;
}
/// Flattens the [TextSpan] tree into a single string.
///
/// Styles are not honored in this process.
String toPlainText() {
assert(debugAssertValid());
StringBuffer buffer = new StringBuffer();
visitTextSpan((TextSpan span) {
buffer.write(span.text);
return true;
});
return buffer.toString();
}
@override
String toString([String prefix = '']) {
StringBuffer buffer = new StringBuffer();
buffer.writeln('$prefix$runtimeType:');
String indent = '$prefix ';
if (style != null)
buffer.writeln(style.toString(indent));
if (text != null)
buffer.writeln('$indent"$text"');
if (children != null) {
for (TextSpan child in children) {
if (child != null) {
buffer.write(child.toString(indent));
} else {
buffer.writeln('$indent<null>');
}
}
}
if (style == null && text == null && children == null)
buffer.writeln('$indent(empty)');
return buffer.toString();
}
/// 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(myTextSpan.debugAssertValid());
/// ```
bool debugAssertValid() {
assert(() {
if (!visitTextSpan((TextSpan span) {
if (span.children != null) {
for (TextSpan child in span.children) {
if (child == null)
return false;
}
}
return true;
})) {
throw new 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'
'${toString(" ")}'
);
}
return true;
});
return true;
}
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! TextSpan)
return false;
final TextSpan typedOther = other;
return typedOther.text == text
&& typedOther.style == style
&& typedOther.recognizer == recognizer
&& _deepEquals(typedOther.children, children);
}
@override
int get hashCode => hashValues(style, text, recognizer, hashList(children));
}
......@@ -289,12 +289,7 @@ class RenderChildView extends RenderBox {
if (_view == null) {
_debugErrorMessage ??= new TextPainter()
..text = new TextSpan(text: 'Child view are supported only when running in Mojo shell.');
_debugErrorMessage
..minWidth = size.width
..maxWidth = size.width
..minHeight = size.height
..maxHeight = size.height
..layout();
_debugErrorMessage.layout(minWidth: size.width, maxWidth: size.width);
}
return true;
});
......
......@@ -48,12 +48,6 @@ class RenderEditableLine extends RenderBox {
_selection = selection,
_paintOffset = paintOffset {
assert(!showCursor || cursorColor != null);
// TODO(abarth): These min/max values should be the default for TextPainter.
_textPainter
..minWidth = 0.0
..maxWidth = double.INFINITY
..minHeight = 0.0
..maxHeight = double.INFINITY;
_tap = new TapGestureRecognizer()
..onTapDown = _handleTapDown
..onTap = _handleTap
......@@ -75,7 +69,6 @@ class RenderEditableLine extends RenderBox {
if (oldStyledText.style != value.style)
_layoutTemplate = null;
_textPainter.text = value;
_constraintsForCurrentLayout = null;
markNeedsLayout();
}
......@@ -258,27 +251,6 @@ class RenderEditableLine extends RenderBox {
return new TextSelection(baseOffset: start, extentOffset: end);
}
BoxConstraints _constraintsForCurrentLayout; // when null, we don't have a current layout
// TODO(abarth): This logic should live in TextPainter and be shared with RenderParagraph.
void _layoutText(BoxConstraints constraints) {
assert(constraints != null);
assert(constraints.debugAssertIsValid());
if (_constraintsForCurrentLayout == constraints)
return; // already cached this layout
_textPainter.maxWidth = constraints.maxWidth;
_textPainter.minWidth = constraints.minWidth;
_textPainter.minHeight = constraints.minHeight;
_textPainter.maxHeight = constraints.maxHeight;
_textPainter.layout();
// By default, we shrinkwrap to the intrinsic width.
double width = constraints.constrainWidth(_textPainter.maxIntrinsicWidth);
_textPainter.minWidth = width;
_textPainter.maxWidth = width;
_textPainter.layout();
_constraintsForCurrentLayout = constraints;
}
Rect _caretPrototype;
@override
......@@ -287,7 +259,7 @@ class RenderEditableLine extends RenderBox {
size = new Size(constraints.maxWidth, constraints.constrainHeight(_preferredHeight));
_caretPrototype = new Rect.fromLTWH(0.0, _kCaretHeightOffset, _kCaretWidth, size.height - 2.0 * _kCaretHeightOffset);
_selectionRects = null;
_layoutText(new BoxConstraints(minHeight: constraints.minHeight, maxHeight: constraints.maxHeight));
_textPainter.layout();
Size contentSize = new Size(_textPainter.width + _kCaretGap + _kCaretWidth, _textPainter.height);
if (onPaintOffsetUpdateNeeded != null && (size != oldSize || contentSize != _contentSize))
onPaintOffsetUpdateNeeded(new ViewportDimensions(containerSize: size, contentSize: contentSize));
......
......@@ -20,8 +20,6 @@ class RenderParagraph extends RenderBox {
final TextPainter _textPainter;
BoxConstraints _constraintsForCurrentLayout; // when null, we don't have a current layout
/// The text to display
TextSpan get text => _textPainter.text;
void set text(TextSpan value) {
......@@ -29,27 +27,13 @@ class RenderParagraph extends RenderBox {
if (_textPainter.text == value)
return;
_textPainter.text = value;
_constraintsForCurrentLayout = null;
markNeedsLayout();
}
// TODO(abarth): This logic should live in TextPainter and be shared with RenderEditableLine.
void _layoutText(BoxConstraints constraints) {
assert(constraints != null);
assert(constraints.debugAssertIsValid());
if (_constraintsForCurrentLayout == constraints)
return; // already cached this layout
_textPainter.maxWidth = constraints.maxWidth;
_textPainter.minWidth = constraints.minWidth;
_textPainter.minHeight = constraints.minHeight;
_textPainter.maxHeight = constraints.maxHeight;
_textPainter.layout();
// By default, we shrinkwrap to the intrinsic width.
double width = constraints.constrainWidth(_textPainter.maxIntrinsicWidth);
_textPainter.minWidth = width;
_textPainter.maxWidth = width;
_textPainter.layout();
_constraintsForCurrentLayout = constraints;
_textPainter.layout(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
}
@override
......@@ -66,7 +50,7 @@ class RenderParagraph extends RenderBox {
double _getIntrinsicHeight(BoxConstraints constraints) {
_layoutText(constraints);
return constraints.constrainHeight(_textPainter.size.height);
return constraints.constrainHeight(_textPainter.height);
}
@override
......
......@@ -48,8 +48,7 @@ class BannerPainter extends CustomPainter {
final TextPainter textPainter = new TextPainter()
..text = new TextSpan(style: kTextStyles, text: message)
..maxWidth = kOffset * 2.0
..layout();
..layout(maxWidth: kOffset * 2.0);
textPainter.paint(canvas, kRect.topLeft.toOffset() + new Offset(0.0, (kRect.height - textPainter.height) / 2.0));
}
......
......@@ -183,10 +183,9 @@ class _SemanticsDebuggerEntry {
message = message.trim();
if (message != '') {
textPainter ??= new TextPainter();
textPainter.text = new TextSpan(style: textStyles, text: message);
textPainter.maxWidth = rect.width;
textPainter.maxHeight = rect.height;
textPainter.layout();
textPainter
..text = new TextSpan(style: textStyles, text: message)
..layout(maxWidth: rect.width);
} else {
textPainter = null;
}
......
......@@ -36,17 +36,8 @@ class Label extends Node {
@override
void paint(Canvas canvas) {
if (_painter == null) {
_painter = new TextPainter(new TextSpan(style: _textStyle, text: _text));
_painter.maxWidth = double.INFINITY;
_painter.minWidth = 0.0;
_painter.layout();
_width = _painter.maxIntrinsicWidth.ceil().toDouble();
_painter.maxWidth = _width;
_painter.minWidth = _width;
_painter.layout();
_painter = new TextPainter(new TextSpan(style: _textStyle, text: _text))
..layout();
}
Offset offset = Offset.zero;
......
......@@ -175,8 +175,7 @@ class ChartPainter {
text: '${gridline.value}'
);
gridline.labelPainter = new TextPainter(text)
..maxWidth = _rect.width
..layout();
..layout(maxWidth: _rect.width);
_horizontalGridlines.add(gridline);
yScaleWidth = math.max(yScaleWidth, gridline.labelPainter.maxIntrinsicWidth);
}
......@@ -222,8 +221,7 @@ class ChartPainter {
text: '${data.indicatorText}'
);
_indicator.labelPainter = new TextPainter(text)
..maxWidth = markerRect.width
..layout();
..layout(maxWidth: markerRect.width);
_indicator.labelPosition = new Point(
((_indicator.start.x + _indicator.end.x) / 2.0) - _indicator.labelPainter.maxIntrinsicWidth / 2.0,
_indicator.start.y - _indicator.labelPainter.size.height - kIndicatorMargin
......
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