Add intrinsic dimensions to RenderEditable (#10093)
Also: * Make TextPainter.preferredLineHeight honour root fontSize * Remove bogus docs. * More aggressively track dirty state for RenderEditable. * Some tests.
This commit is contained in:
parent
b232a84b0d
commit
abeb164fe2
@ -136,15 +136,30 @@ class TextPainter {
|
|||||||
|
|
||||||
ui.Paragraph _layoutTemplate;
|
ui.Paragraph _layoutTemplate;
|
||||||
|
|
||||||
|
ui.ParagraphStyle _createParagraphStyle() {
|
||||||
|
return _text.style?.getParagraphStyle(
|
||||||
|
textAlign: textAlign,
|
||||||
|
textScaleFactor: textScaleFactor,
|
||||||
|
maxLines: _maxLines,
|
||||||
|
ellipsis: _ellipsis,
|
||||||
|
) ?? new ui.ParagraphStyle(
|
||||||
|
textAlign: textAlign,
|
||||||
|
maxLines: maxLines,
|
||||||
|
ellipsis: ellipsis,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// The height of a zero-width space in [text] in logical pixels.
|
/// The height of a zero-width space in [text] in logical pixels.
|
||||||
///
|
///
|
||||||
/// Not every line of text in [text] will have this height, but this height
|
/// Not every line of text in [text] will have this height, but this height
|
||||||
/// is "typical" for text in [text] and useful for sizing other objects
|
/// is "typical" for text in [text] and useful for sizing other objects
|
||||||
/// relative a typical line of text.
|
/// relative a typical line of text.
|
||||||
|
///
|
||||||
|
/// Obtaining this value does not require calling [layout].
|
||||||
double get preferredLineHeight {
|
double get preferredLineHeight {
|
||||||
assert(text != null);
|
assert(text != null);
|
||||||
if (_layoutTemplate == null) {
|
if (_layoutTemplate == null) {
|
||||||
final ui.ParagraphBuilder builder = new ui.ParagraphBuilder(new ui.ParagraphStyle());
|
final ui.ParagraphBuilder builder = new ui.ParagraphBuilder(_createParagraphStyle());
|
||||||
if (text.style != null)
|
if (text.style != null)
|
||||||
builder.pushStyle(text.style.getTextStyle(textScaleFactor: textScaleFactor));
|
builder.pushStyle(text.style.getTextStyle(textScaleFactor: textScaleFactor));
|
||||||
builder.addText(_kZeroWidthSpace);
|
builder.addText(_kZeroWidthSpace);
|
||||||
@ -165,7 +180,8 @@ class TextPainter {
|
|||||||
return layoutValue.ceilToDouble();
|
return layoutValue.ceilToDouble();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The width at which decreasing the width of the text would prevent it from painting itself completely within its bounds.
|
/// The width at which decreasing the width of the text would prevent it from
|
||||||
|
/// painting itself completely within its bounds.
|
||||||
///
|
///
|
||||||
/// Valid only after [layout] has been called.
|
/// Valid only after [layout] has been called.
|
||||||
double get minIntrinsicWidth {
|
double get minIntrinsicWidth {
|
||||||
@ -251,18 +267,7 @@ class TextPainter {
|
|||||||
return;
|
return;
|
||||||
_needsLayout = false;
|
_needsLayout = false;
|
||||||
if (_paragraph == null) {
|
if (_paragraph == null) {
|
||||||
ui.ParagraphStyle paragraphStyle = _text.style?.getParagraphStyle(
|
final ui.ParagraphBuilder builder = new ui.ParagraphBuilder(_createParagraphStyle());
|
||||||
textAlign: textAlign,
|
|
||||||
textScaleFactor: textScaleFactor,
|
|
||||||
maxLines: _maxLines,
|
|
||||||
ellipsis: _ellipsis,
|
|
||||||
);
|
|
||||||
paragraphStyle ??= new ui.ParagraphStyle(
|
|
||||||
textAlign: textAlign,
|
|
||||||
maxLines: maxLines,
|
|
||||||
ellipsis: ellipsis,
|
|
||||||
);
|
|
||||||
final ui.ParagraphBuilder builder = new ui.ParagraphBuilder(paragraphStyle);
|
|
||||||
_text.build(builder, textScaleFactor: textScaleFactor);
|
_text.build(builder, textScaleFactor: textScaleFactor);
|
||||||
_paragraph = builder.build();
|
_paragraph = builder.build();
|
||||||
}
|
}
|
||||||
|
@ -53,9 +53,7 @@ class TextSelectionPoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single line of editable text.
|
|
||||||
class RenderEditable extends RenderBox {
|
class RenderEditable extends RenderBox {
|
||||||
/// Creates a render object for a single line of editable text.
|
|
||||||
RenderEditable({
|
RenderEditable({
|
||||||
TextSpan text,
|
TextSpan text,
|
||||||
TextAlign textAlign,
|
TextAlign textAlign,
|
||||||
@ -89,6 +87,18 @@ class RenderEditable extends RenderBox {
|
|||||||
/// Called when the selection changes.
|
/// Called when the selection changes.
|
||||||
SelectionChangedHandler onSelectionChanged;
|
SelectionChangedHandler onSelectionChanged;
|
||||||
|
|
||||||
|
double _textLayoutLastWidth;
|
||||||
|
|
||||||
|
/// Marks the render object as needing to be laid out again and have its text
|
||||||
|
/// metrics recomputed.
|
||||||
|
///
|
||||||
|
/// Implies [markNeedsLayout].
|
||||||
|
@protected
|
||||||
|
void markNeedsTextLayout() {
|
||||||
|
_textLayoutLastWidth = null;
|
||||||
|
markNeedsLayout();
|
||||||
|
}
|
||||||
|
|
||||||
/// The text to display
|
/// The text to display
|
||||||
TextSpan get text => _textPainter.text;
|
TextSpan get text => _textPainter.text;
|
||||||
final TextPainter _textPainter;
|
final TextPainter _textPainter;
|
||||||
@ -96,7 +106,7 @@ class RenderEditable extends RenderBox {
|
|||||||
if (_textPainter.text == value)
|
if (_textPainter.text == value)
|
||||||
return;
|
return;
|
||||||
_textPainter.text = value;
|
_textPainter.text = value;
|
||||||
markNeedsLayout();
|
markNeedsTextLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How the text should be aligned horizontally.
|
/// How the text should be aligned horizontally.
|
||||||
@ -143,7 +153,7 @@ class RenderEditable extends RenderBox {
|
|||||||
if (_maxLines == value)
|
if (_maxLines == value)
|
||||||
return;
|
return;
|
||||||
_maxLines = value;
|
_maxLines = value;
|
||||||
markNeedsLayout();
|
markNeedsTextLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The color to use when painting the selection.
|
/// The color to use when painting the selection.
|
||||||
@ -166,7 +176,7 @@ class RenderEditable extends RenderBox {
|
|||||||
if (_textPainter.textScaleFactor == value)
|
if (_textPainter.textScaleFactor == value)
|
||||||
return;
|
return;
|
||||||
_textPainter.textScaleFactor = value;
|
_textPainter.textScaleFactor = value;
|
||||||
markNeedsLayout();
|
markNeedsTextLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ui.TextBox> _selectionRects;
|
List<ui.TextBox> _selectionRects;
|
||||||
@ -261,10 +271,8 @@ class RenderEditable extends RenderBox {
|
|||||||
/// points might actually be co-located (e.g., because of a bidirectional
|
/// points might actually be co-located (e.g., because of a bidirectional
|
||||||
/// selection that contains some text but whose ends meet in the middle).
|
/// selection that contains some text but whose ends meet in the middle).
|
||||||
List<TextSelectionPoint> getEndpointsForSelection(TextSelection selection) {
|
List<TextSelectionPoint> getEndpointsForSelection(TextSelection selection) {
|
||||||
// TODO(mpcomplete): We should be more disciplined about when we dirty the
|
assert(constraints != null);
|
||||||
// layout state of the text painter so that we can know that the layout is
|
_layoutText(constraints.maxWidth);
|
||||||
// clean at this point.
|
|
||||||
_layoutText();
|
|
||||||
|
|
||||||
final Offset paintOffset = _paintOffset;
|
final Offset paintOffset = _paintOffset;
|
||||||
|
|
||||||
@ -286,6 +294,7 @@ class RenderEditable extends RenderBox {
|
|||||||
|
|
||||||
/// Returns the position in the text for the given global coordinate.
|
/// Returns the position in the text for the given global coordinate.
|
||||||
TextPosition getPositionForPoint(Offset globalPosition) {
|
TextPosition getPositionForPoint(Offset globalPosition) {
|
||||||
|
_layoutText(constraints.maxWidth);
|
||||||
globalPosition += -_paintOffset;
|
globalPosition += -_paintOffset;
|
||||||
return _textPainter.getPositionForOffset(globalToLocal(globalPosition));
|
return _textPainter.getPositionForOffset(globalToLocal(globalPosition));
|
||||||
}
|
}
|
||||||
@ -293,11 +302,25 @@ class RenderEditable extends RenderBox {
|
|||||||
/// Returns the Rect in local coordinates for the caret at the given text
|
/// Returns the Rect in local coordinates for the caret at the given text
|
||||||
/// position.
|
/// position.
|
||||||
Rect getLocalRectForCaret(TextPosition caretPosition) {
|
Rect getLocalRectForCaret(TextPosition caretPosition) {
|
||||||
|
_layoutText(constraints.maxWidth);
|
||||||
final Offset caretOffset = _textPainter.getOffsetForCaret(caretPosition, _caretPrototype);
|
final Offset caretOffset = _textPainter.getOffsetForCaret(caretPosition, _caretPrototype);
|
||||||
// This rect is the same as _caretPrototype but without the vertical padding.
|
// This rect is the same as _caretPrototype but without the vertical padding.
|
||||||
return new Rect.fromLTWH(0.0, 0.0, _kCaretWidth, _preferredLineHeight).shift(caretOffset + _paintOffset);
|
return new Rect.fromLTWH(0.0, 0.0, _kCaretWidth, _preferredLineHeight).shift(caretOffset + _paintOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
double computeMinIntrinsicWidth(double height) {
|
||||||
|
_layoutText(double.INFINITY);
|
||||||
|
return _textPainter.minIntrinsicWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
double computeMaxIntrinsicWidth(double height) {
|
||||||
|
_layoutText(double.INFINITY);
|
||||||
|
return _textPainter.maxIntrinsicWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This does not required the layout to be updated.
|
||||||
double get _preferredLineHeight => _textPainter.preferredLineHeight;
|
double get _preferredLineHeight => _textPainter.preferredLineHeight;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -332,6 +355,7 @@ class RenderEditable extends RenderBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleTap() {
|
void _handleTap() {
|
||||||
|
_layoutText(constraints.maxWidth);
|
||||||
assert(_lastTapDownPosition != null);
|
assert(_lastTapDownPosition != null);
|
||||||
final Offset globalPosition = _lastTapDownPosition;
|
final Offset globalPosition = _lastTapDownPosition;
|
||||||
_lastTapDownPosition = null;
|
_lastTapDownPosition = null;
|
||||||
@ -348,6 +372,7 @@ class RenderEditable extends RenderBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleLongPress() {
|
void _handleLongPress() {
|
||||||
|
_layoutText(constraints.maxWidth);
|
||||||
final Offset globalPosition = _longPressPosition;
|
final Offset globalPosition = _longPressPosition;
|
||||||
_longPressPosition = null;
|
_longPressPosition = null;
|
||||||
if (onSelectionChanged != null) {
|
if (onSelectionChanged != null) {
|
||||||
@ -357,6 +382,7 @@ class RenderEditable extends RenderBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextSelection _selectWordAtOffset(TextPosition position) {
|
TextSelection _selectWordAtOffset(TextPosition position) {
|
||||||
|
assert(_textLayoutLastWidth == constraints.maxWidth);
|
||||||
final TextRange word = _textPainter.getWordBoundary(position);
|
final TextRange word = _textPainter.getWordBoundary(position);
|
||||||
// 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 >= word.end)
|
if (position.offset >= word.end)
|
||||||
@ -366,18 +392,22 @@ class RenderEditable extends RenderBox {
|
|||||||
|
|
||||||
Rect _caretPrototype;
|
Rect _caretPrototype;
|
||||||
|
|
||||||
void _layoutText() {
|
void _layoutText(double constraintWidth) {
|
||||||
|
assert(constraintWidth != null);
|
||||||
|
if (_textLayoutLastWidth == constraintWidth)
|
||||||
|
return;
|
||||||
final double caretMargin = _kCaretGap + _kCaretWidth;
|
final double caretMargin = _kCaretGap + _kCaretWidth;
|
||||||
final double availableWidth = math.max(0.0, constraints.maxWidth - caretMargin);
|
final double availableWidth = math.max(0.0, constraintWidth - caretMargin);
|
||||||
final double maxWidth = _maxLines > 1 ? availableWidth : double.INFINITY;
|
final double maxWidth = _maxLines > 1 ? availableWidth : double.INFINITY;
|
||||||
_textPainter.layout(minWidth: availableWidth, maxWidth: maxWidth);
|
_textPainter.layout(minWidth: availableWidth, maxWidth: maxWidth);
|
||||||
|
_textLayoutLastWidth = constraintWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void performLayout() {
|
void performLayout() {
|
||||||
|
_layoutText(constraints.maxWidth);
|
||||||
_caretPrototype = new Rect.fromLTWH(0.0, _kCaretHeightOffset, _kCaretWidth, _preferredLineHeight - 2.0 * _kCaretHeightOffset);
|
_caretPrototype = new Rect.fromLTWH(0.0, _kCaretHeightOffset, _kCaretWidth, _preferredLineHeight - 2.0 * _kCaretHeightOffset);
|
||||||
_selectionRects = null;
|
_selectionRects = null;
|
||||||
_layoutText();
|
|
||||||
size = new Size(constraints.maxWidth, constraints.constrainHeight(
|
size = new Size(constraints.maxWidth, constraints.constrainHeight(
|
||||||
_textPainter.height.clamp(_preferredLineHeight, _preferredLineHeight * _maxLines)
|
_textPainter.height.clamp(_preferredLineHeight, _preferredLineHeight * _maxLines)
|
||||||
));
|
));
|
||||||
@ -389,12 +419,14 @@ class RenderEditable extends RenderBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _paintCaret(Canvas canvas, Offset effectiveOffset) {
|
void _paintCaret(Canvas canvas, Offset effectiveOffset) {
|
||||||
|
assert(_textLayoutLastWidth == constraints.maxWidth);
|
||||||
final Offset caretOffset = _textPainter.getOffsetForCaret(_selection.extent, _caretPrototype);
|
final Offset caretOffset = _textPainter.getOffsetForCaret(_selection.extent, _caretPrototype);
|
||||||
final Paint paint = new Paint()..color = _cursorColor;
|
final Paint paint = new Paint()..color = _cursorColor;
|
||||||
canvas.drawRect(_caretPrototype.shift(caretOffset + effectiveOffset), paint);
|
canvas.drawRect(_caretPrototype.shift(caretOffset + effectiveOffset), paint);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _paintSelection(Canvas canvas, Offset effectiveOffset) {
|
void _paintSelection(Canvas canvas, Offset effectiveOffset) {
|
||||||
|
assert(_textLayoutLastWidth == constraints.maxWidth);
|
||||||
assert(_selectionRects != null);
|
assert(_selectionRects != null);
|
||||||
final Paint paint = new Paint()..color = _selectionColor;
|
final Paint paint = new Paint()..color = _selectionColor;
|
||||||
for (ui.TextBox box in _selectionRects)
|
for (ui.TextBox box in _selectionRects)
|
||||||
@ -402,6 +434,7 @@ class RenderEditable extends RenderBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _paintContents(PaintingContext context, Offset offset) {
|
void _paintContents(PaintingContext context, Offset offset) {
|
||||||
|
assert(_textLayoutLastWidth == constraints.maxWidth);
|
||||||
final Offset effectiveOffset = offset + _paintOffset;
|
final Offset effectiveOffset = offset + _paintOffset;
|
||||||
|
|
||||||
if (_selection != null) {
|
if (_selection != null) {
|
||||||
@ -418,6 +451,7 @@ class RenderEditable extends RenderBox {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void paint(PaintingContext context, Offset offset) {
|
void paint(PaintingContext context, Offset offset) {
|
||||||
|
_layoutText(constraints.maxWidth);
|
||||||
if (_hasVisualOverflow)
|
if (_hasVisualOverflow)
|
||||||
context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintContents);
|
context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintContents);
|
||||||
else
|
else
|
||||||
|
@ -48,4 +48,18 @@ void main() {
|
|||||||
painter.layout();
|
painter.layout();
|
||||||
expect(painter.size, const Size(123.0, 123.0));
|
expect(painter.size, const Size(123.0, 123.0));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('TextPainter default text height is 14 pixels', () {
|
||||||
|
final TextPainter painter = new TextPainter(text: const TextSpan(text: 'x'));
|
||||||
|
painter.layout();
|
||||||
|
expect(painter.preferredLineHeight, 14.0);
|
||||||
|
expect(painter.size, const Size(14.0, 14.0));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TextPainter sets paragraph size from root', () {
|
||||||
|
final TextPainter painter = new TextPainter(text: const TextSpan(text: 'x', style: const TextStyle(fontSize: 100.0)));
|
||||||
|
painter.layout();
|
||||||
|
expect(painter.preferredLineHeight, 100.0);
|
||||||
|
expect(painter.size, const Size(100.0, 100.0));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
22
packages/flutter/test/rendering/editable_test.dart
Normal file
22
packages/flutter/test/rendering/editable_test.dart
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2017 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 'package:flutter/rendering.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('editable intrinsics', () {
|
||||||
|
final RenderEditable editable = new RenderEditable(
|
||||||
|
text: const TextSpan(
|
||||||
|
style: const TextStyle(height: 1.0, fontSize: 10.0, fontFamily: 'Ahem'),
|
||||||
|
text: '12345',
|
||||||
|
),
|
||||||
|
offset: new ViewportOffset.zero(),
|
||||||
|
);
|
||||||
|
expect(editable.getMinIntrinsicWidth(double.INFINITY), 50.0);
|
||||||
|
expect(editable.getMaxIntrinsicWidth(double.INFINITY), 50.0);
|
||||||
|
expect(editable.getMinIntrinsicHeight(double.INFINITY), 10.0);
|
||||||
|
expect(editable.getMaxIntrinsicHeight(double.INFINITY), 10.0);
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user