mirror of
https://github.com/flutter/flutter
synced 2024-08-25 11:07:06 +00:00
Use a separate TextPainter
for intrinsics calculation in RenderEditable
and RenderParagraph
(#144577)
Use a dedicated `TextPainter` for intrinsic size calculation in `RenderEditable` and `RenderParagraph`.
This is an implementation detail so the change should be covered by existing tests. Performance wise this shouldn't be significantly slower since SkParagraph [caches the result of slower operations across different paragraphs](9c62e7b382/modules/skparagraph/src/ParagraphCache.cpp (L254-L272)
). Existing benchmarks should be able to catch potential regressions (??).
The reason for making this change is to make sure that intrinsic size computations don't destroy text layout artifacts, so I can expose the text layout as a stream of immutable `TextLayout` objects, to signify other render objects that text-layout-dependent-cache (such as caches for `getBoxesForRange` which can be relatively slow to compute) should be invalidated and `markNeedsPaint` needs to be called if the painting logic depended on text layout.
Without this change, the intrinsics/dry layout calculations will add additional events to the text layout stream, which violates the "dry"/non-destructive contract.
This commit is contained in:
parent
15e90ad224
commit
cc01701781
|
@ -410,6 +410,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
_selectionPainter.dispose();
|
_selectionPainter.dispose();
|
||||||
_caretPainter.dispose();
|
_caretPainter.dispose();
|
||||||
_textPainter.dispose();
|
_textPainter.dispose();
|
||||||
|
_textIntrinsicsCache?.dispose();
|
||||||
if (_disposeShowCursor) {
|
if (_disposeShowCursor) {
|
||||||
_showCursor.dispose();
|
_showCursor.dispose();
|
||||||
_disposeShowCursor = false;
|
_disposeShowCursor = false;
|
||||||
|
@ -510,18 +511,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
double? _textLayoutLastMaxWidth;
|
|
||||||
double? _textLayoutLastMinWidth;
|
|
||||||
|
|
||||||
/// Assert that the last layout still matches the constraints.
|
|
||||||
void debugAssertLayoutUpToDate() {
|
|
||||||
assert(
|
|
||||||
_textLayoutLastMaxWidth == constraints.maxWidth &&
|
|
||||||
_textLayoutLastMinWidth == constraints.minWidth,
|
|
||||||
'Last width ($_textLayoutLastMinWidth, $_textLayoutLastMaxWidth) not the same as max width constraint (${constraints.minWidth}, ${constraints.maxWidth}).',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the [handleEvent] will propagate pointer events to selection
|
/// Whether the [handleEvent] will propagate pointer events to selection
|
||||||
/// handlers.
|
/// handlers.
|
||||||
///
|
///
|
||||||
|
@ -542,7 +531,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_textPainter.textHeightBehavior = value;
|
_textPainter.textHeightBehavior = value;
|
||||||
markNeedsTextLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// {@macro flutter.painting.textPainter.textWidthBasis}
|
/// {@macro flutter.painting.textPainter.textWidthBasis}
|
||||||
|
@ -552,7 +541,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_textPainter.textWidthBasis = value;
|
_textPainter.textWidthBasis = value;
|
||||||
markNeedsTextLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The pixel ratio of the current device.
|
/// The pixel ratio of the current device.
|
||||||
|
@ -565,7 +554,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_devicePixelRatio = value;
|
_devicePixelRatio = value;
|
||||||
markNeedsTextLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Character used for obscuring text if [obscureText] is true.
|
/// Character used for obscuring text if [obscureText] is true.
|
||||||
|
@ -655,7 +644,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
/// {@macro flutter.services.TextLayoutMetrics.getLineAtOffset}
|
/// {@macro flutter.services.TextLayoutMetrics.getLineAtOffset}
|
||||||
@override
|
@override
|
||||||
TextSelection getLineAtOffset(TextPosition position) {
|
TextSelection getLineAtOffset(TextPosition position) {
|
||||||
debugAssertLayoutUpToDate();
|
|
||||||
final TextRange line = _textPainter.getLineBoundary(position);
|
final TextRange line = _textPainter.getLineBoundary(position);
|
||||||
// If text is obscured, the entire string should be treated as one line.
|
// If text is obscured, the entire string should be treated as one line.
|
||||||
if (obscureText) {
|
if (obscureText) {
|
||||||
|
@ -761,23 +749,10 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
_backgroundRenderObject?.markNeedsPaint();
|
_backgroundRenderObject?.markNeedsPaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marks the render object as needing to be laid out again and have its text
|
|
||||||
/// metrics recomputed.
|
|
||||||
///
|
|
||||||
/// Implies [markNeedsLayout].
|
|
||||||
@protected
|
|
||||||
void markNeedsTextLayout() {
|
|
||||||
_textLayoutLastMaxWidth = null;
|
|
||||||
_textLayoutLastMinWidth = null;
|
|
||||||
markNeedsLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void systemFontsDidChange() {
|
void systemFontsDidChange() {
|
||||||
super.systemFontsDidChange();
|
super.systemFontsDidChange();
|
||||||
_textPainter.markNeedsLayout();
|
_textPainter.markNeedsLayout();
|
||||||
_textLayoutLastMaxWidth = null;
|
|
||||||
_textLayoutLastMinWidth = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a plain text version of the text in [TextPainter].
|
/// Returns a plain text version of the text in [TextPainter].
|
||||||
|
@ -803,10 +778,25 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
_cachedAttributedValue = null;
|
_cachedAttributedValue = null;
|
||||||
_cachedCombinedSemanticsInfos = null;
|
_cachedCombinedSemanticsInfos = null;
|
||||||
_canComputeIntrinsicsCached = null;
|
_canComputeIntrinsicsCached = null;
|
||||||
markNeedsTextLayout();
|
markNeedsLayout();
|
||||||
markNeedsSemanticsUpdate();
|
markNeedsSemanticsUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextPainter? _textIntrinsicsCache;
|
||||||
|
TextPainter get _textIntrinsics {
|
||||||
|
return (_textIntrinsicsCache ??= TextPainter())
|
||||||
|
..text = _textPainter.text
|
||||||
|
..textAlign = _textPainter.textAlign
|
||||||
|
..textDirection = _textPainter.textDirection
|
||||||
|
..textScaler = _textPainter.textScaler
|
||||||
|
..maxLines = _textPainter.maxLines
|
||||||
|
..ellipsis = _textPainter.ellipsis
|
||||||
|
..locale = _textPainter.locale
|
||||||
|
..strutStyle = _textPainter.strutStyle
|
||||||
|
..textWidthBasis = _textPainter.textWidthBasis
|
||||||
|
..textHeightBehavior = _textPainter.textHeightBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
/// How the text should be aligned horizontally.
|
/// How the text should be aligned horizontally.
|
||||||
TextAlign get textAlign => _textPainter.textAlign;
|
TextAlign get textAlign => _textPainter.textAlign;
|
||||||
set textAlign(TextAlign value) {
|
set textAlign(TextAlign value) {
|
||||||
|
@ -814,7 +804,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_textPainter.textAlign = value;
|
_textPainter.textAlign = value;
|
||||||
markNeedsTextLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The directionality of the text.
|
/// The directionality of the text.
|
||||||
|
@ -837,7 +827,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_textPainter.textDirection = value;
|
_textPainter.textDirection = value;
|
||||||
markNeedsTextLayout();
|
markNeedsLayout();
|
||||||
markNeedsSemanticsUpdate();
|
markNeedsSemanticsUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -857,7 +847,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_textPainter.locale = value;
|
_textPainter.locale = value;
|
||||||
markNeedsTextLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The [StrutStyle] used by the renderer's internal [TextPainter] to
|
/// The [StrutStyle] used by the renderer's internal [TextPainter] to
|
||||||
|
@ -868,7 +858,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_textPainter.strutStyle = value;
|
_textPainter.strutStyle = value;
|
||||||
markNeedsTextLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The color to use when painting the cursor.
|
/// The color to use when painting the cursor.
|
||||||
|
@ -977,7 +967,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
// height of the first line in case there are hard line breaks in the text.
|
// height of the first line in case there are hard line breaks in the text.
|
||||||
// See the `_preferredHeight` method.
|
// See the `_preferredHeight` method.
|
||||||
_textPainter.maxLines = value == 1 ? 1 : null;
|
_textPainter.maxLines = value == 1 ? 1 : null;
|
||||||
markNeedsTextLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.minLines}
|
/// {@macro flutter.widgets.editableText.minLines}
|
||||||
|
@ -990,7 +980,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_minLines = value;
|
_minLines = value;
|
||||||
markNeedsTextLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// {@macro flutter.widgets.editableText.expands}
|
/// {@macro flutter.widgets.editableText.expands}
|
||||||
|
@ -1001,7 +991,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_expands = value;
|
_expands = value;
|
||||||
markNeedsTextLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The color to use when painting the selection.
|
/// The color to use when painting the selection.
|
||||||
|
@ -1039,7 +1029,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_textPainter.textScaler = value;
|
_textPainter.textScaler = value;
|
||||||
markNeedsTextLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The region of text that is selected, if any.
|
/// The region of text that is selected, if any.
|
||||||
|
@ -1215,7 +1205,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_enableInteractiveSelection = value;
|
_enableInteractiveSelection = value;
|
||||||
markNeedsTextLayout();
|
markNeedsLayout();
|
||||||
markNeedsSemanticsUpdate();
|
markNeedsSemanticsUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1841,12 +1831,12 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
if (!_canComputeIntrinsics) {
|
if (!_canComputeIntrinsics) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
_textPainter.setPlaceholderDimensions(layoutInlineChildren(
|
final List<PlaceholderDimensions> placeholderDimensions = layoutInlineChildren(double.infinity, (RenderBox child, BoxConstraints constraints) => Size(child.getMinIntrinsicWidth(double.infinity), 0.0));
|
||||||
double.infinity,
|
final (double minWidth, double maxWidth) = _adjustConstraints();
|
||||||
(RenderBox child, BoxConstraints constraints) => Size(child.getMinIntrinsicWidth(double.infinity), 0.0),
|
return (_textIntrinsics
|
||||||
));
|
..setPlaceholderDimensions(placeholderDimensions)
|
||||||
_layoutText();
|
..layout(minWidth: minWidth, maxWidth: maxWidth))
|
||||||
return _textPainter.minIntrinsicWidth;
|
.minIntrinsicWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1854,14 +1844,17 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
if (!_canComputeIntrinsics) {
|
if (!_canComputeIntrinsics) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
_textPainter.setPlaceholderDimensions(layoutInlineChildren(
|
final List<PlaceholderDimensions> placeholderDimensions = layoutInlineChildren(
|
||||||
double.infinity,
|
double.infinity,
|
||||||
// Height and baseline is irrelevant as all text will be laid
|
// Height and baseline is irrelevant as all text will be laid
|
||||||
// out in a single line. Therefore, using 0.0 as a dummy for the height.
|
// out in a single line. Therefore, using 0.0 as a dummy for the height.
|
||||||
(RenderBox child, BoxConstraints constraints) => Size(child.getMaxIntrinsicWidth(double.infinity), 0.0),
|
(RenderBox child, BoxConstraints constraints) => Size(child.getMaxIntrinsicWidth(double.infinity), 0.0),
|
||||||
));
|
);
|
||||||
_layoutText();
|
final (double minWidth, double maxWidth) = _adjustConstraints();
|
||||||
return _textPainter.maxIntrinsicWidth + _caretMargin;
|
return (_textIntrinsics
|
||||||
|
..setPlaceholderDimensions(placeholderDimensions)
|
||||||
|
..layout(minWidth: minWidth, maxWidth: maxWidth))
|
||||||
|
.maxIntrinsicWidth + _caretMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An estimate of the height of a line in the text. See [TextPainter.preferredLineHeight].
|
/// An estimate of the height of a line in the text. See [TextPainter.preferredLineHeight].
|
||||||
|
@ -1869,8 +1862,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
double get preferredLineHeight => _textPainter.preferredLineHeight;
|
double get preferredLineHeight => _textPainter.preferredLineHeight;
|
||||||
|
|
||||||
int? _cachedLineBreakCount;
|
int? _cachedLineBreakCount;
|
||||||
// TODO(LongCatIsLooong): see if we can let ui.Paragraph estimate the number
|
|
||||||
// of lines
|
|
||||||
int _countHardLineBreaks(String text) {
|
int _countHardLineBreaks(String text) {
|
||||||
final int? cachedValue = _cachedLineBreakCount;
|
final int? cachedValue = _cachedLineBreakCount;
|
||||||
if (cachedValue != null) {
|
if (cachedValue != null) {
|
||||||
|
@ -1895,14 +1886,15 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
final int? maxLines = this.maxLines;
|
final int? maxLines = this.maxLines;
|
||||||
final int? minLines = this.minLines ?? maxLines;
|
final int? minLines = this.minLines ?? maxLines;
|
||||||
final double minHeight = preferredLineHeight * (minLines ?? 0);
|
final double minHeight = preferredLineHeight * (minLines ?? 0);
|
||||||
|
assert(maxLines != 1 || _textIntrinsics.maxLines == 1);
|
||||||
|
|
||||||
if (maxLines == null) {
|
if (maxLines == null) {
|
||||||
final double estimatedHeight;
|
final double estimatedHeight;
|
||||||
if (width == double.infinity) {
|
if (width == double.infinity) {
|
||||||
estimatedHeight = preferredLineHeight * (_countHardLineBreaks(plainText) + 1);
|
estimatedHeight = preferredLineHeight * (_countHardLineBreaks(plainText) + 1);
|
||||||
} else {
|
} else {
|
||||||
_layoutText(maxWidth: width);
|
final (double minWidth, double maxWidth) = _adjustConstraints(maxWidth: width);
|
||||||
estimatedHeight = _textPainter.height;
|
estimatedHeight = (_textIntrinsics..layout(minWidth: minWidth, maxWidth: maxWidth)).height;
|
||||||
}
|
}
|
||||||
return math.max(estimatedHeight, minHeight);
|
return math.max(estimatedHeight, minHeight);
|
||||||
}
|
}
|
||||||
|
@ -1914,16 +1906,19 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
// The _layoutText call lays out the paragraph using infinite width when
|
// The _layoutText call lays out the paragraph using infinite width when
|
||||||
// maxLines == 1. Also _textPainter.maxLines will be set to 1 so should
|
// maxLines == 1. Also _textPainter.maxLines will be set to 1 so should
|
||||||
// there be any line breaks only the first line is shown.
|
// there be any line breaks only the first line is shown.
|
||||||
assert(_textPainter.maxLines == 1);
|
final (double minWidth, double maxWidth) = _adjustConstraints(maxWidth: width);
|
||||||
_layoutText(maxWidth: width);
|
return (_textIntrinsics..layout(minWidth: minWidth, maxWidth: maxWidth)).height;
|
||||||
return _textPainter.height;
|
|
||||||
}
|
}
|
||||||
if (minLines == maxLines) {
|
if (minLines == maxLines) {
|
||||||
return minHeight;
|
return minHeight;
|
||||||
}
|
}
|
||||||
_layoutText(maxWidth: width);
|
|
||||||
final double maxHeight = preferredLineHeight * maxLines;
|
final double maxHeight = preferredLineHeight * maxLines;
|
||||||
return clampDouble(_textPainter.height, minHeight, maxHeight);
|
final (double minWidth, double maxWidth) = _adjustConstraints(maxWidth: width);
|
||||||
|
return clampDouble(
|
||||||
|
(_textIntrinsics..layout(minWidth: minWidth, maxWidth: maxWidth)).height,
|
||||||
|
minHeight,
|
||||||
|
maxHeight,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1934,7 +1929,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
if (!_canComputeIntrinsics) {
|
if (!_canComputeIntrinsics) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
_textPainter.setPlaceholderDimensions(layoutInlineChildren(width, ChildLayoutHelper.dryLayoutChild));
|
_textIntrinsics.setPlaceholderDimensions(layoutInlineChildren(width, ChildLayoutHelper.dryLayoutChild));
|
||||||
return _preferredHeight(width);
|
return _preferredHeight(width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2075,7 +2070,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
/// [from] corresponds to the [TextSelection.baseOffset], and [to] corresponds
|
/// [from] corresponds to the [TextSelection.baseOffset], and [to] corresponds
|
||||||
/// to the [TextSelection.extentOffset].
|
/// to the [TextSelection.extentOffset].
|
||||||
void selectPositionAt({ required Offset from, Offset? to, required SelectionChangedCause cause }) {
|
void selectPositionAt({ required Offset from, Offset? to, required SelectionChangedCause cause }) {
|
||||||
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
|
_computeTextMetricsIfNeeded();
|
||||||
final TextPosition fromPosition = _textPainter.getPositionForOffset(globalToLocal(from - _paintOffset));
|
final TextPosition fromPosition = _textPainter.getPositionForOffset(globalToLocal(from - _paintOffset));
|
||||||
final TextPosition? toPosition = to == null
|
final TextPosition? toPosition = to == null
|
||||||
? null
|
? null
|
||||||
|
@ -2150,7 +2145,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
/// [TextPosition].
|
/// [TextPosition].
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
TextSelection getWordAtOffset(TextPosition position) {
|
TextSelection getWordAtOffset(TextPosition position) {
|
||||||
debugAssertLayoutUpToDate();
|
|
||||||
// When long-pressing past the end of the text, we want a collapsed cursor.
|
// When long-pressing past the end of the text, we want a collapsed cursor.
|
||||||
if (position.offset >= plainText.length) {
|
if (position.offset >= plainText.length) {
|
||||||
return TextSelection.fromPosition(
|
return TextSelection.fromPosition(
|
||||||
|
@ -2229,17 +2223,13 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
// restored to the original values before final layout and painting.
|
// restored to the original values before final layout and painting.
|
||||||
List<PlaceholderDimensions>? _placeholderDimensions;
|
List<PlaceholderDimensions>? _placeholderDimensions;
|
||||||
|
|
||||||
void _layoutText({ double minWidth = 0.0, double maxWidth = double.infinity }) {
|
(double minWidth, double maxWidth) _adjustConstraints({ double minWidth = 0.0, double maxWidth = double.infinity }) {
|
||||||
final double availableMaxWidth = math.max(0.0, maxWidth - _caretMargin);
|
final double availableMaxWidth = math.max(0.0, maxWidth - _caretMargin);
|
||||||
final double availableMinWidth = math.min(minWidth, availableMaxWidth);
|
final double availableMinWidth = math.min(minWidth, availableMaxWidth);
|
||||||
final double textMaxWidth = _isMultiline ? availableMaxWidth : double.infinity;
|
return (
|
||||||
final double textMinWidth = forceLine ? availableMaxWidth : availableMinWidth;
|
forceLine ? availableMaxWidth : availableMinWidth,
|
||||||
_textPainter.layout(
|
_isMultiline ? availableMaxWidth : double.infinity,
|
||||||
minWidth: textMinWidth,
|
|
||||||
maxWidth: textMaxWidth,
|
|
||||||
);
|
);
|
||||||
_textLayoutLastMinWidth = minWidth;
|
|
||||||
_textLayoutLastMaxWidth = maxWidth;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computes the text metrics if `_textPainter`'s layout information was marked
|
// Computes the text metrics if `_textPainter`'s layout information was marked
|
||||||
|
@ -2262,7 +2252,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
// the constraints used to layout the `_textPainter` is different. See
|
// the constraints used to layout the `_textPainter` is different. See
|
||||||
// `TextPainter.layout`.
|
// `TextPainter.layout`.
|
||||||
void _computeTextMetricsIfNeeded() {
|
void _computeTextMetricsIfNeeded() {
|
||||||
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
|
final (double minWidth, double maxWidth) = _adjustConstraints(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
|
||||||
|
_textPainter.layout(minWidth: minWidth, maxWidth: maxWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
late Rect _caretPrototype;
|
late Rect _caretPrototype;
|
||||||
|
@ -2325,10 +2316,14 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
));
|
));
|
||||||
return Size.zero;
|
return Size.zero;
|
||||||
}
|
}
|
||||||
_textPainter.setPlaceholderDimensions(layoutInlineChildren(constraints.maxWidth, ChildLayoutHelper.dryLayoutChild));
|
|
||||||
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
|
final (double minWidth, double maxWidth) = _adjustConstraints(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
|
||||||
final double width = forceLine ? constraints.maxWidth : constraints
|
_textIntrinsics
|
||||||
.constrainWidth(_textPainter.size.width + _caretMargin);
|
..setPlaceholderDimensions(layoutInlineChildren(constraints.maxWidth, ChildLayoutHelper.dryLayoutChild))
|
||||||
|
..layout(minWidth: minWidth, maxWidth: maxWidth);
|
||||||
|
final double width = forceLine
|
||||||
|
? constraints.maxWidth
|
||||||
|
: constraints.constrainWidth(_textIntrinsics.size.width + _caretMargin);
|
||||||
return Size(width, constraints.constrainHeight(_preferredHeight(constraints.maxWidth)));
|
return Size(width, constraints.constrainHeight(_preferredHeight(constraints.maxWidth)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2336,8 +2331,10 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
void performLayout() {
|
void performLayout() {
|
||||||
final BoxConstraints constraints = this.constraints;
|
final BoxConstraints constraints = this.constraints;
|
||||||
_placeholderDimensions = layoutInlineChildren(constraints.maxWidth, ChildLayoutHelper.layoutChild);
|
_placeholderDimensions = layoutInlineChildren(constraints.maxWidth, ChildLayoutHelper.layoutChild);
|
||||||
_textPainter.setPlaceholderDimensions(_placeholderDimensions);
|
final (double minWidth, double maxWidth) = _adjustConstraints(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
|
||||||
_computeTextMetricsIfNeeded();
|
_textPainter
|
||||||
|
..setPlaceholderDimensions(_placeholderDimensions)
|
||||||
|
..layout(minWidth: minWidth, maxWidth: maxWidth);
|
||||||
positionInlineChildren(_textPainter.inlinePlaceholderBoxes!);
|
positionInlineChildren(_textPainter.inlinePlaceholderBoxes!);
|
||||||
_computeCaretPrototype();
|
_computeCaretPrototype();
|
||||||
// We grab _textPainter.size here because assigning to `size` on the next
|
// We grab _textPainter.size here because assigning to `size` on the next
|
||||||
|
@ -2349,9 +2346,20 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
// though we currently don't use those here.
|
// though we currently don't use those here.
|
||||||
// See also RenderParagraph which has a similar issue.
|
// See also RenderParagraph which has a similar issue.
|
||||||
final Size textPainterSize = _textPainter.size;
|
final Size textPainterSize = _textPainter.size;
|
||||||
final double width = forceLine ? constraints.maxWidth : constraints
|
final double width = forceLine
|
||||||
.constrainWidth(_textPainter.size.width + _caretMargin);
|
? constraints.maxWidth
|
||||||
final double preferredHeight = _preferredHeight(constraints.maxWidth);
|
: constraints.constrainWidth(_textPainter.size.width + _caretMargin);
|
||||||
|
assert(maxLines != 1 || _textPainter.maxLines == 1);
|
||||||
|
final double preferredHeight = switch (maxLines) {
|
||||||
|
null => math.max(_textPainter.height, preferredLineHeight * (minLines ?? 0)),
|
||||||
|
1 => _textPainter.height,
|
||||||
|
final int maxLines => clampDouble(
|
||||||
|
_textPainter.height,
|
||||||
|
preferredLineHeight * (minLines ?? maxLines),
|
||||||
|
preferredLineHeight * maxLines,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
size = Size(width, constraints.constrainHeight(preferredHeight));
|
size = Size(width, constraints.constrainHeight(preferredHeight));
|
||||||
final Size contentSize = Size(textPainterSize.width + _caretMargin, textPainterSize.height);
|
final Size contentSize = Size(textPainterSize.width + _caretMargin, textPainterSize.height);
|
||||||
|
|
||||||
|
@ -2521,7 +2529,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
|
||||||
}
|
}
|
||||||
|
|
||||||
void _paintContents(PaintingContext context, Offset offset) {
|
void _paintContents(PaintingContext context, Offset offset) {
|
||||||
debugAssertLayoutUpToDate();
|
|
||||||
final Offset effectiveOffset = offset + _paintOffset;
|
final Offset effectiveOffset = offset + _paintOffset;
|
||||||
|
|
||||||
if (selection != null && !_floatingCursorOn) {
|
if (selection != null && !_floatingCursorOn) {
|
||||||
|
|
|
@ -306,6 +306,27 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin<RenderBo
|
||||||
|
|
||||||
final TextPainter _textPainter;
|
final TextPainter _textPainter;
|
||||||
|
|
||||||
|
// Currently, computing min/max intrinsic width/height will destroy state
|
||||||
|
// inside the painter. Instead of calling _layout again to get back the correct
|
||||||
|
// state, use a separate TextPainter for intrinsics calculation.
|
||||||
|
//
|
||||||
|
// TODO(abarth): Make computing the min/max intrinsic width/height a
|
||||||
|
// non-destructive operation.
|
||||||
|
TextPainter? _textIntrinsicsCache;
|
||||||
|
TextPainter get _textIntrinsics {
|
||||||
|
return (_textIntrinsicsCache ??= TextPainter())
|
||||||
|
..text = _textPainter.text
|
||||||
|
..textAlign = _textPainter.textAlign
|
||||||
|
..textDirection = _textPainter.textDirection
|
||||||
|
..textScaler = _textPainter.textScaler
|
||||||
|
..maxLines = _textPainter.maxLines
|
||||||
|
..ellipsis = _textPainter.ellipsis
|
||||||
|
..locale = _textPainter.locale
|
||||||
|
..strutStyle = _textPainter.strutStyle
|
||||||
|
..textWidthBasis = _textPainter.textWidthBasis
|
||||||
|
..textHeightBehavior = _textPainter.textHeightBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
List<AttributedString>? _cachedAttributedLabels;
|
List<AttributedString>? _cachedAttributedLabels;
|
||||||
|
|
||||||
List<InlineSpanSemanticsInformation>? _cachedCombinedSemanticsInfos;
|
List<InlineSpanSemanticsInformation>? _cachedCombinedSemanticsInfos;
|
||||||
|
@ -448,6 +469,7 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin<RenderBo
|
||||||
_removeSelectionRegistrarSubscription();
|
_removeSelectionRegistrarSubscription();
|
||||||
_disposeSelectableFragments();
|
_disposeSelectableFragments();
|
||||||
_textPainter.dispose();
|
_textPainter.dispose();
|
||||||
|
_textIntrinsicsCache?.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -630,21 +652,17 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin<RenderBo
|
||||||
return getOffsetForCaret(position, Rect.zero) + Offset(0, getFullHeightForCaret(position) ?? 0.0);
|
return getOffsetForCaret(position, Rect.zero) + Offset(0, getFullHeightForCaret(position) ?? 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ui.LineMetrics> _computeLineMetrics() {
|
|
||||||
return _textPainter.computeLineMetrics();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
double computeMinIntrinsicWidth(double height) {
|
double computeMinIntrinsicWidth(double height) {
|
||||||
if (!_canComputeIntrinsics()) {
|
if (!_canComputeIntrinsics()) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
_textPainter.setPlaceholderDimensions(layoutInlineChildren(
|
final List<PlaceholderDimensions> placeholderDimensions = layoutInlineChildren(
|
||||||
double.infinity,
|
double.infinity,
|
||||||
(RenderBox child, BoxConstraints constraints) => Size(child.getMinIntrinsicWidth(double.infinity), 0.0),
|
(RenderBox child, BoxConstraints constraints) => Size(child.getMinIntrinsicWidth(double.infinity), 0.0),
|
||||||
));
|
);
|
||||||
_layoutText(); // layout with infinite width.
|
return (_textIntrinsics..setPlaceholderDimensions(placeholderDimensions)..layout())
|
||||||
return _textPainter.minIntrinsicWidth;
|
.minIntrinsicWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -652,23 +670,24 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin<RenderBo
|
||||||
if (!_canComputeIntrinsics()) {
|
if (!_canComputeIntrinsics()) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
_textPainter.setPlaceholderDimensions(layoutInlineChildren(
|
final List<PlaceholderDimensions> placeholderDimensions = layoutInlineChildren(
|
||||||
double.infinity,
|
double.infinity,
|
||||||
// Height and baseline is irrelevant as all text will be laid
|
// Height and baseline is irrelevant as all text will be laid
|
||||||
// out in a single line. Therefore, using 0.0 as a dummy for the height.
|
// out in a single line. Therefore, using 0.0 as a dummy for the height.
|
||||||
(RenderBox child, BoxConstraints constraints) => Size(child.getMaxIntrinsicWidth(double.infinity), 0.0),
|
(RenderBox child, BoxConstraints constraints) => Size(child.getMaxIntrinsicWidth(double.infinity), 0.0),
|
||||||
));
|
);
|
||||||
_layoutText(); // layout with infinite width.
|
return (_textIntrinsics..setPlaceholderDimensions(placeholderDimensions)..layout())
|
||||||
return _textPainter.maxIntrinsicWidth;
|
.maxIntrinsicWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
double _computeIntrinsicHeight(double width) {
|
double _computeIntrinsicHeight(double width) {
|
||||||
if (!_canComputeIntrinsics()) {
|
if (!_canComputeIntrinsics()) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
_textPainter.setPlaceholderDimensions(layoutInlineChildren(width, ChildLayoutHelper.dryLayoutChild));
|
return (_textIntrinsics
|
||||||
_layoutText(minWidth: width, maxWidth: width);
|
..setPlaceholderDimensions(layoutInlineChildren(width, ChildLayoutHelper.dryLayoutChild))
|
||||||
return _textPainter.height;
|
..layout(minWidth: width, maxWidth: _adjustMaxWidth(width)))
|
||||||
|
.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -761,14 +780,6 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin<RenderBo
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
bool get debugHasOverflowShader => _overflowShader != null;
|
bool get debugHasOverflowShader => _overflowShader != null;
|
||||||
|
|
||||||
void _layoutText({ double minWidth = 0.0, double maxWidth = double.infinity }) {
|
|
||||||
final bool widthMatters = softWrap || overflow == TextOverflow.ellipsis;
|
|
||||||
_textPainter.layout(
|
|
||||||
minWidth: minWidth,
|
|
||||||
maxWidth: widthMatters ? maxWidth : double.infinity,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void systemFontsDidChange() {
|
void systemFontsDidChange() {
|
||||||
super.systemFontsDidChange();
|
super.systemFontsDidChange();
|
||||||
|
@ -782,9 +793,13 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin<RenderBo
|
||||||
// restored to the original values before final layout and painting.
|
// restored to the original values before final layout and painting.
|
||||||
List<PlaceholderDimensions>? _placeholderDimensions;
|
List<PlaceholderDimensions>? _placeholderDimensions;
|
||||||
|
|
||||||
|
double _adjustMaxWidth(double maxWidth) {
|
||||||
|
return softWrap || overflow == TextOverflow.ellipsis ? maxWidth : double.infinity;
|
||||||
|
}
|
||||||
void _layoutTextWithConstraints(BoxConstraints constraints) {
|
void _layoutTextWithConstraints(BoxConstraints constraints) {
|
||||||
_textPainter.setPlaceholderDimensions(_placeholderDimensions);
|
_textPainter
|
||||||
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
|
..setPlaceholderDimensions(_placeholderDimensions)
|
||||||
|
..layout(minWidth: constraints.minWidth, maxWidth: _adjustMaxWidth(constraints.maxWidth));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -796,9 +811,11 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin<RenderBo
|
||||||
));
|
));
|
||||||
return Size.zero;
|
return Size.zero;
|
||||||
}
|
}
|
||||||
_textPainter.setPlaceholderDimensions(layoutInlineChildren(constraints.maxWidth, ChildLayoutHelper.dryLayoutChild));
|
final Size size = (_textIntrinsics
|
||||||
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
|
..setPlaceholderDimensions(layoutInlineChildren(constraints.maxWidth, ChildLayoutHelper.dryLayoutChild))
|
||||||
return constraints.constrain(_textPainter.size);
|
..layout(minWidth: constraints.minWidth, maxWidth: _adjustMaxWidth(constraints.maxWidth)))
|
||||||
|
.size;
|
||||||
|
return constraints.constrain(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -876,18 +893,10 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin<RenderBo
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(PaintingContext context, Offset offset) {
|
void paint(PaintingContext context, Offset offset) {
|
||||||
// Ideally we could compute the min/max intrinsic width/height with a
|
// Text alignment only triggers repaint so it's possible the text layout has
|
||||||
// non-destructive operation. However, currently, computing these values
|
// been invalidated but performLayout wasn't called at this point. Make sure
|
||||||
// will destroy state inside the painter. If that happens, we need to get
|
// the TextPainter has a valid layout.
|
||||||
// back the correct state by calling _layout again.
|
|
||||||
//
|
|
||||||
// TODO(abarth): Make computing the min/max intrinsic width/height a
|
|
||||||
// non-destructive operation.
|
|
||||||
//
|
|
||||||
// If you remove this call, make sure that changing the textAlign still
|
|
||||||
// works properly.
|
|
||||||
_layoutTextWithConstraints(constraints);
|
_layoutTextWithConstraints(constraints);
|
||||||
|
|
||||||
assert(() {
|
assert(() {
|
||||||
if (debugRepaintTextRainbowEnabled) {
|
if (debugRepaintTextRainbowEnabled) {
|
||||||
final Paint paint = Paint()
|
final Paint paint = Paint()
|
||||||
|
@ -1919,7 +1928,7 @@ class _SelectableFragment with Selectable, Diagnosticable, ChangeNotifier implem
|
||||||
}
|
}
|
||||||
|
|
||||||
MapEntry<TextPosition, SelectionResult> _handleVerticalMovement(TextPosition position, {required double horizontalBaselineInParagraphCoordinates, required bool below}) {
|
MapEntry<TextPosition, SelectionResult> _handleVerticalMovement(TextPosition position, {required double horizontalBaselineInParagraphCoordinates, required bool below}) {
|
||||||
final List<ui.LineMetrics> lines = paragraph._computeLineMetrics();
|
final List<ui.LineMetrics> lines = paragraph._textPainter.computeLineMetrics();
|
||||||
final Offset offset = paragraph.getOffsetForCaret(position, Rect.zero);
|
final Offset offset = paragraph.getOffsetForCaret(position, Rect.zero);
|
||||||
int currentLine = lines.length - 1;
|
int currentLine = lines.length - 1;
|
||||||
for (final ui.LineMetrics lineMetrics in lines) {
|
for (final ui.LineMetrics lineMetrics in lines) {
|
||||||
|
|
147
packages/flutter/test/rendering/editable_intrinsics_test.dart
Normal file
147
packages/flutter/test/rendering/editable_intrinsics_test.dart
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
// Copyright 2014 The Flutter 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 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final TextSelectionDelegate delegate = _FakeEditableTextState();
|
||||||
|
|
||||||
|
test('editable intrinsics', () {
|
||||||
|
final RenderEditable editable = RenderEditable(
|
||||||
|
text: const TextSpan(
|
||||||
|
style: TextStyle(height: 1.0, fontSize: 10.0),
|
||||||
|
text: '12345',
|
||||||
|
),
|
||||||
|
startHandleLayerLink: LayerLink(),
|
||||||
|
endHandleLayerLink: LayerLink(),
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
locale: const Locale('ja', 'JP'),
|
||||||
|
offset: ViewportOffset.zero(),
|
||||||
|
textSelectionDelegate: delegate,
|
||||||
|
);
|
||||||
|
expect(editable.getMinIntrinsicWidth(double.infinity), 50.0);
|
||||||
|
// The width includes the width of the cursor (1.0).
|
||||||
|
expect(editable.getMaxIntrinsicWidth(double.infinity), 52.0);
|
||||||
|
expect(editable.getMinIntrinsicHeight(double.infinity), 10.0);
|
||||||
|
expect(editable.getMaxIntrinsicHeight(double.infinity), 10.0);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
editable.toStringDeep(minLevel: DiagnosticLevel.info),
|
||||||
|
equalsIgnoringHashCodes(
|
||||||
|
'RenderEditable#00000 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE DETACHED\n'
|
||||||
|
' │ parentData: MISSING\n'
|
||||||
|
' │ constraints: MISSING\n'
|
||||||
|
' │ size: MISSING\n'
|
||||||
|
' │ cursorColor: null\n'
|
||||||
|
' │ showCursor: ValueNotifier<bool>#00000(false)\n'
|
||||||
|
' │ maxLines: 1\n'
|
||||||
|
' │ minLines: null\n'
|
||||||
|
' │ selectionColor: null\n'
|
||||||
|
' │ locale: ja_JP\n'
|
||||||
|
' │ selection: null\n'
|
||||||
|
' │ offset: _FixedViewportOffset#00000(offset: 0.0)\n'
|
||||||
|
' ╘═╦══ text ═══\n'
|
||||||
|
' ║ TextSpan:\n'
|
||||||
|
' ║ inherit: true\n'
|
||||||
|
' ║ size: 10.0\n'
|
||||||
|
' ║ height: 1.0x\n'
|
||||||
|
' ║ "12345"\n'
|
||||||
|
' ╚═══════════\n',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('textScaler affects intrinsics', () {
|
||||||
|
final RenderEditable editable = RenderEditable(
|
||||||
|
text: const TextSpan(
|
||||||
|
style: TextStyle(fontSize: 10),
|
||||||
|
text: 'Hello World',
|
||||||
|
),
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
startHandleLayerLink: LayerLink(),
|
||||||
|
endHandleLayerLink: LayerLink(),
|
||||||
|
offset: ViewportOffset.zero(),
|
||||||
|
textSelectionDelegate: delegate,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(editable.getMaxIntrinsicWidth(double.infinity), 110 + 2);
|
||||||
|
|
||||||
|
editable.textScaler = const TextScaler.linear(2);
|
||||||
|
expect(editable.getMaxIntrinsicWidth(double.infinity), 220 + 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('maxLines affects intrinsics', () {
|
||||||
|
final RenderEditable editable = RenderEditable(
|
||||||
|
text: TextSpan(
|
||||||
|
style: const TextStyle(fontSize: 10),
|
||||||
|
text: List<String>.filled(5, 'A').join('\n'),
|
||||||
|
),
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
startHandleLayerLink: LayerLink(),
|
||||||
|
endHandleLayerLink: LayerLink(),
|
||||||
|
offset: ViewportOffset.zero(),
|
||||||
|
textSelectionDelegate: delegate,
|
||||||
|
maxLines: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(editable.getMaxIntrinsicHeight(double.infinity), 50);
|
||||||
|
|
||||||
|
editable.maxLines = 1;
|
||||||
|
expect(editable.getMaxIntrinsicHeight(double.infinity), 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('strutStyle affects intrinsics', () {
|
||||||
|
final RenderEditable editable = RenderEditable(
|
||||||
|
text: const TextSpan(
|
||||||
|
style: TextStyle(fontSize: 10),
|
||||||
|
text: 'Hello World',
|
||||||
|
),
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
startHandleLayerLink: LayerLink(),
|
||||||
|
endHandleLayerLink: LayerLink(),
|
||||||
|
offset: ViewportOffset.zero(),
|
||||||
|
textSelectionDelegate: delegate,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(editable.getMaxIntrinsicHeight(double.infinity), 10);
|
||||||
|
|
||||||
|
editable.strutStyle = const StrutStyle(fontSize: 100, forceStrutHeight: true);
|
||||||
|
expect(editable.getMaxIntrinsicHeight(double.infinity), 100);
|
||||||
|
}, skip: kIsWeb && !isCanvasKit); // [intended] strut spport for HTML renderer https://github.com/flutter/flutter/issues/32243.
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FakeEditableTextState with TextSelectionDelegate {
|
||||||
|
@override
|
||||||
|
TextEditingValue textEditingValue = TextEditingValue.empty;
|
||||||
|
|
||||||
|
TextSelection? selection;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void hideToolbar([bool hideHandles = true]) { }
|
||||||
|
|
||||||
|
@override
|
||||||
|
void userUpdateTextEditingValue(TextEditingValue value, SelectionChangedCause cause) {
|
||||||
|
selection = value.selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void bringIntoView(TextPosition position) { }
|
||||||
|
|
||||||
|
@override
|
||||||
|
void cutSelection(SelectionChangedCause cause) { }
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> pasteText(SelectionChangedCause cause) {
|
||||||
|
return Future<void>.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void selectAll(SelectionChangedCause cause) { }
|
||||||
|
|
||||||
|
@override
|
||||||
|
void copySelection(SelectionChangedCause cause) { }
|
||||||
|
}
|
|
@ -201,52 +201,6 @@ void main() {
|
||||||
expect(leaderLayers.single.offset, endpoint + paintOffset, reason: 'offset should respect paintOffset');
|
expect(leaderLayers.single.offset, endpoint + paintOffset, reason: 'offset should respect paintOffset');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('editable intrinsics', () {
|
|
||||||
final TextSelectionDelegate delegate = _FakeEditableTextState();
|
|
||||||
final RenderEditable editable = RenderEditable(
|
|
||||||
text: const TextSpan(
|
|
||||||
style: TextStyle(height: 1.0, fontSize: 10.0),
|
|
||||||
text: '12345',
|
|
||||||
),
|
|
||||||
startHandleLayerLink: LayerLink(),
|
|
||||||
endHandleLayerLink: LayerLink(),
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
locale: const Locale('ja', 'JP'),
|
|
||||||
offset: ViewportOffset.zero(),
|
|
||||||
textSelectionDelegate: delegate,
|
|
||||||
);
|
|
||||||
expect(editable.getMinIntrinsicWidth(double.infinity), 50.0);
|
|
||||||
// The width includes the width of the cursor (1.0).
|
|
||||||
expect(editable.getMaxIntrinsicWidth(double.infinity), 52.0);
|
|
||||||
expect(editable.getMinIntrinsicHeight(double.infinity), 10.0);
|
|
||||||
expect(editable.getMaxIntrinsicHeight(double.infinity), 10.0);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
editable.toStringDeep(minLevel: DiagnosticLevel.info),
|
|
||||||
equalsIgnoringHashCodes(
|
|
||||||
'RenderEditable#00000 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE DETACHED\n'
|
|
||||||
' │ parentData: MISSING\n'
|
|
||||||
' │ constraints: MISSING\n'
|
|
||||||
' │ size: MISSING\n'
|
|
||||||
' │ cursorColor: null\n'
|
|
||||||
' │ showCursor: ValueNotifier<bool>#00000(false)\n'
|
|
||||||
' │ maxLines: 1\n'
|
|
||||||
' │ minLines: null\n'
|
|
||||||
' │ selectionColor: null\n'
|
|
||||||
' │ locale: ja_JP\n'
|
|
||||||
' │ selection: null\n'
|
|
||||||
' │ offset: _FixedViewportOffset#00000(offset: 0.0)\n'
|
|
||||||
' ╘═╦══ text ═══\n'
|
|
||||||
' ║ TextSpan:\n'
|
|
||||||
' ║ inherit: true\n'
|
|
||||||
' ║ size: 10.0\n'
|
|
||||||
' ║ height: 1.0x\n'
|
|
||||||
' ║ "12345"\n'
|
|
||||||
' ╚═══════════\n',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test that clipping will be used even when the text fits within the visible
|
// Test that clipping will be used even when the text fits within the visible
|
||||||
// region if the start position of the text is offset (e.g. during scrolling
|
// region if the start position of the text is offset (e.g. during scrolling
|
||||||
// animation).
|
// animation).
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
@ -66,4 +67,49 @@ void main() {
|
||||||
expect(testBlock.getMinIntrinsicHeight(0.0), equals(manyLinesTextHeight));
|
expect(testBlock.getMinIntrinsicHeight(0.0), equals(manyLinesTextHeight));
|
||||||
expect(testBlock.getMaxIntrinsicHeight(0.0), equals(manyLinesTextHeight));
|
expect(testBlock.getMaxIntrinsicHeight(0.0), equals(manyLinesTextHeight));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('textScaler affects intrinsics', () {
|
||||||
|
final RenderParagraph paragraph = RenderParagraph(
|
||||||
|
const TextSpan(
|
||||||
|
style: TextStyle(fontSize: 10),
|
||||||
|
text: 'Hello World',
|
||||||
|
),
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(paragraph.getMaxIntrinsicWidth(double.infinity), 110);
|
||||||
|
|
||||||
|
paragraph.textScaler = const TextScaler.linear(2);
|
||||||
|
expect(paragraph.getMaxIntrinsicWidth(double.infinity), 220);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('maxLines affects intrinsics', () {
|
||||||
|
final RenderParagraph paragraph = RenderParagraph(
|
||||||
|
TextSpan(
|
||||||
|
style: const TextStyle(fontSize: 10),
|
||||||
|
text: List<String>.filled(5, 'A').join('\n'),
|
||||||
|
),
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(paragraph.getMaxIntrinsicHeight(double.infinity), 50);
|
||||||
|
|
||||||
|
paragraph.maxLines = 1;
|
||||||
|
expect(paragraph.getMaxIntrinsicHeight(double.infinity), 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('strutStyle affects intrinsics', () {
|
||||||
|
final RenderParagraph paragraph = RenderParagraph(
|
||||||
|
const TextSpan(
|
||||||
|
style: TextStyle(fontSize: 10),
|
||||||
|
text: 'Hello World',
|
||||||
|
),
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(paragraph.getMaxIntrinsicHeight(double.infinity), 10);
|
||||||
|
|
||||||
|
paragraph.strutStyle = const StrutStyle(fontSize: 100, forceStrutHeight: true);
|
||||||
|
expect(paragraph.getMaxIntrinsicHeight(double.infinity), 100);
|
||||||
|
}, skip: kIsWeb && !isCanvasKit); // [intended] strut spport for HTML renderer https://github.com/flutter/flutter/issues/32243.
|
||||||
}
|
}
|
||||||
|
|
|
@ -375,6 +375,27 @@ void main() {
|
||||||
expect(paragraph.size.height, 30.0);
|
expect(paragraph.size.height, 30.0);
|
||||||
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/61018
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/61018
|
||||||
|
|
||||||
|
test('textAlign triggers TextPainter relayout in the paint method', () {
|
||||||
|
final RenderParagraph paragraph = RenderParagraph(
|
||||||
|
const TextSpan(text: 'A', style: TextStyle(fontSize: 10.0)),
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
);
|
||||||
|
|
||||||
|
Rect getRectForA() => paragraph.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: 1)).single.toRect();
|
||||||
|
|
||||||
|
layout(paragraph, constraints: const BoxConstraints.tightFor(width: 100.0));
|
||||||
|
|
||||||
|
expect(getRectForA(), const Rect.fromLTWH(0, 0, 10, 10));
|
||||||
|
|
||||||
|
paragraph.textAlign = TextAlign.right;
|
||||||
|
expect(paragraph.debugNeedsLayout, isFalse);
|
||||||
|
expect(paragraph.debugNeedsPaint, isTrue);
|
||||||
|
|
||||||
|
paragraph.paint(MockPaintingContext(), Offset.zero);
|
||||||
|
expect(getRectForA(), const Rect.fromLTWH(90, 0, 10, 10));
|
||||||
|
});
|
||||||
|
|
||||||
group('didExceedMaxLines', () {
|
group('didExceedMaxLines', () {
|
||||||
RenderParagraph createRenderParagraph({
|
RenderParagraph createRenderParagraph({
|
||||||
int? maxLines,
|
int? maxLines,
|
||||||
|
|
Loading…
Reference in a new issue