Unverified Commit 7a3a29e7 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Fixes Intrinsics for RenderParagraph and RenderWrap (#70656)

parent 63e328f7
......@@ -43,6 +43,7 @@ export 'src/rendering/flex.dart';
export 'src/rendering/flow.dart';
export 'src/rendering/image.dart';
export 'src/rendering/layer.dart';
export 'src/rendering/layout_helper.dart';
export 'src/rendering/list_body.dart';
export 'src/rendering/list_wheel_viewport.dart';
export 'src/rendering/mouse_cursor.dart';
......
......@@ -721,40 +721,58 @@ class _RenderCupertinoAlert extends RenderBox {
return 0.0;
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
double _computeDividerThickness(BoxConstraints constraints) {
final bool hasDivider = contentSection!.getMaxIntrinsicHeight(constraints.maxWidth) > 0.0
&& actionsSection!.getMaxIntrinsicHeight(constraints.maxWidth) > 0.0;
final double dividerThickness = hasDivider ? _dividerThickness : 0.0;
return hasDivider ? _dividerThickness : 0.0;
}
_AlertSizes _computeSizes({required BoxConstraints constraints, required ChildLayouter layoutChild, required double dividerThickness}) {
final double minActionsHeight = actionsSection!.getMinIntrinsicHeight(constraints.maxWidth);
// Size alert content.
contentSection!.layout(
final Size contentSize = layoutChild(
contentSection!,
constraints.deflate(EdgeInsets.only(bottom: minActionsHeight + dividerThickness)),
parentUsesSize: true,
);
final Size contentSize = contentSection!.size;
// Size alert actions.
actionsSection!.layout(
final Size actionsSize = layoutChild(
actionsSection!,
constraints.deflate(EdgeInsets.only(top: contentSize.height + dividerThickness)),
parentUsesSize: true,
);
final Size actionsSize = actionsSection!.size;
// Calculate overall alert height.
final double actionSheetHeight = contentSize.height + dividerThickness + actionsSize.height;
return _AlertSizes(
size: Size(constraints.maxWidth, actionSheetHeight),
contentHeight: contentSize.height,
);
}
// Set our size now that layout calculations are complete.
size = Size(constraints.maxWidth, actionSheetHeight);
@override
Size computeDryLayout(BoxConstraints constraints) {
return _computeSizes(
constraints: constraints,
layoutChild: ChildLayoutHelper.dryLayoutChild,
dividerThickness: _computeDividerThickness(constraints),
).size;
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
final double dividerThickness = _computeDividerThickness(constraints);
final _AlertSizes alertSizes = _computeSizes(
constraints: constraints,
layoutChild: ChildLayoutHelper.layoutChild,
dividerThickness: dividerThickness,
);
size = alertSizes.size;
// Set the position of the actions box to sit at the bottom of the alert.
// The content box defaults to the top left, which is where we want it.
assert(actionsSection!.parentData is MultiChildLayoutParentData);
final MultiChildLayoutParentData actionParentData = actionsSection!.parentData! as MultiChildLayoutParentData;
actionParentData.offset = Offset(0.0, contentSize.height + dividerThickness);
actionParentData.offset = Offset(0.0, alertSizes.contentHeight + dividerThickness);
}
@override
......@@ -806,6 +824,13 @@ class _RenderCupertinoAlert extends RenderBox {
}
}
class _AlertSizes {
const _AlertSizes({required this.size, required this.contentHeight});
final Size size;
final double contentHeight;
}
// Visual components of an alert that need to be explicitly sized and
// laid out at runtime.
enum _AlertSections {
......@@ -1264,8 +1289,17 @@ class _RenderCupertinoAlertActions extends RenderBox
return heightAccumulation;
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return _performLayout(constraints, dry: true);
}
@override
void performLayout() {
size = _performLayout(constraints, dry: false);
}
Size _performLayout(BoxConstraints constraints, {bool dry = false}) {
final BoxConstraints perButtonConstraints = constraints.copyWith(
minHeight: 0.0,
maxHeight: double.infinity,
......@@ -1275,16 +1309,21 @@ class _RenderCupertinoAlertActions extends RenderBox
int index = 0;
double verticalOffset = 0.0;
while (child != null) {
child.layout(
perButtonConstraints,
parentUsesSize: true,
);
assert(child.parentData is MultiChildLayoutParentData);
final MultiChildLayoutParentData parentData = child.parentData! as MultiChildLayoutParentData;
parentData.offset = Offset(0.0, verticalOffset);
final Size childSize;
if (!dry) {
child.layout(
perButtonConstraints,
parentUsesSize: true,
);
childSize = child.size;
assert(child.parentData is MultiChildLayoutParentData);
final MultiChildLayoutParentData parentData = child.parentData! as MultiChildLayoutParentData;
parentData.offset = Offset(0.0, verticalOffset);
} else {
childSize = child.getDryLayout(constraints);
}
verticalOffset += child.size.height;
verticalOffset += childSize.height;
if (index < childCount - 1) {
// Add a gap for the next divider.
verticalOffset += dividerThickness;
......@@ -1294,7 +1333,7 @@ class _RenderCupertinoAlertActions extends RenderBox
child = childAfter(child);
}
size = constraints.constrain(
return constraints.constrain(
Size(constraints.maxWidth, verticalOffset)
);
}
......
......@@ -627,32 +627,45 @@ class _RenderSegmentedControl<T> extends RenderBox
}
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
Size _calculateChildSize(BoxConstraints constraints) {
double maxHeight = _kMinSegmentedControlHeight;
double childWidth = constraints.minWidth / childCount;
for (final RenderBox child in getChildrenAsList()) {
RenderBox? child = firstChild;
while (child != null) {
childWidth = math.max(childWidth, child.getMaxIntrinsicWidth(double.infinity));
child = childAfter(child);
}
childWidth = math.min(childWidth, constraints.maxWidth / childCount);
RenderBox? child = firstChild;
child = firstChild;
while (child != null) {
final double boxHeight = child.getMaxIntrinsicHeight(childWidth);
maxHeight = math.max(maxHeight, boxHeight);
child = childAfter(child);
}
return Size(childWidth, maxHeight);
}
constraints.constrainHeight(maxHeight);
Size _computeOverallSizeFromChildSize(Size childSize) {
return constraints.constrain(Size(childSize.width * childCount, childSize.height));
}
@override
Size computeDryLayout(BoxConstraints constraints) {
final Size childSize = _calculateChildSize(constraints);
return _computeOverallSizeFromChildSize(childSize);
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
final Size childSize = _calculateChildSize(constraints);
final BoxConstraints childConstraints = BoxConstraints.tightFor(
width: childWidth,
height: maxHeight,
width: childSize.width,
height: childSize.height,
);
child = firstChild;
RenderBox? child = firstChild;
while (child != null) {
child.layout(childConstraints, parentUsesSize: true);
child = childAfter(child);
......@@ -675,7 +688,7 @@ class _RenderSegmentedControl<T> extends RenderBox
break;
}
size = constraints.constrain(Size(childWidth * childCount, maxHeight));
size = _computeOverallSizeFromChildSize(childSize);
}
@override
......
......@@ -826,37 +826,49 @@ class _RenderSegmentedControl<T> extends RenderBox
}
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
Size _calculateChildSize(BoxConstraints constraints) {
double childWidth = (constraints.minWidth - totalSeparatorWidth) / childCount;
double maxHeight = _kMinSegmentedControlHeight;
for (final RenderBox child in getChildrenAsList()) {
RenderBox? child = firstChild;
while (child != null) {
childWidth = math.max(childWidth, child.getMaxIntrinsicWidth(double.infinity) + 2 * _kSegmentMinPadding);
child = childAfter(child);
}
childWidth = math.min(
childWidth,
(constraints.maxWidth - totalSeparatorWidth) / childCount,
);
RenderBox? child = firstChild;
child = firstChild;
while (child != null) {
final double boxHeight = child.getMaxIntrinsicHeight(childWidth);
maxHeight = math.max(maxHeight, boxHeight);
child = childAfter(child);
}
return Size(childWidth, maxHeight);
}
Size _computeOverallSizeFromChildSize(Size childSize) {
return constraints.constrain(Size(childSize.width * childCount + totalSeparatorWidth, childSize.height));
}
constraints.constrainHeight(maxHeight);
@override
Size computeDryLayout(BoxConstraints constraints) {
final Size childSize = _calculateChildSize(constraints);
return _computeOverallSizeFromChildSize(childSize);
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
final Size childSize = _calculateChildSize(constraints);
final BoxConstraints childConstraints = BoxConstraints.tightFor(
width: childWidth,
height: maxHeight,
width: childSize.width,
height: childSize.height,
);
// Layout children.
child = firstChild;
RenderBox? child = firstChild;
while (child != null) {
child.layout(childConstraints, parentUsesSize: true);
child = childAfter(child);
......@@ -874,7 +886,7 @@ class _RenderSegmentedControl<T> extends RenderBox
child = childAfter(child);
}
size = constraints.constrain(Size(childWidth * childCount + totalSeparatorWidth, maxHeight));
size = _computeOverallSizeFromChildSize(childSize);
}
@override
......
......@@ -311,6 +311,11 @@ class _ToolbarRenderBox extends RenderShiftedBox {
}
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return constraints.biggest;
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
......@@ -968,7 +973,7 @@ class _CupertinoTextSelectionToolbarItemsRenderBox extends RenderBox with Contai
@override
void performLayout() {
if (firstChild == null) {
performResize();
size = constraints.smallest;
return;
}
......
......@@ -1520,6 +1520,13 @@ class _RenderAppBarTitleBox extends RenderAligningShiftedBox {
TextDirection? textDirection,
}) : super(child: child, alignment: Alignment.center, textDirection: textDirection);
@override
Size computeDryLayout(BoxConstraints constraints) {
final BoxConstraints innerConstraints = constraints.copyWith(maxHeight: double.infinity);
final Size childSize = child!.getDryLayout(innerConstraints);
return constraints.constrain(childSize);
}
@override
void performLayout() {
final BoxConstraints innerConstraints = constraints.copyWith(maxHeight: double.infinity);
......
......@@ -543,17 +543,33 @@ class _RenderInputPadding extends RenderShiftedBox {
return 0.0;
}
Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
if (child != null) {
final Size childSize = layoutChild(child!, constraints);
final double height = math.max(childSize.width, minSize.width);
final double width = math.max(childSize.height, minSize.height);
return constraints.constrain(Size(height, width));
}
return Size.zero;
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return _computeSize(
constraints: constraints,
layoutChild: ChildLayoutHelper.dryLayoutChild,
);
}
@override
void performLayout() {
size = _computeSize(
constraints: constraints,
layoutChild: ChildLayoutHelper.layoutChild,
);
if (child != null) {
child!.layout(constraints, parentUsesSize: true);
final double height = math.max(child!.size.width, minSize.width);
final double width = math.max(child!.size.height, minSize.height);
size = constraints.constrain(Size(height, width));
final BoxParentData childParentData = child!.parentData! as BoxParentData;
childParentData.offset = Alignment.center.alongOffset(size - child!.size as Offset);
} else {
size = Size.zero;
}
}
......
......@@ -335,6 +335,25 @@ class _RenderButtonBarRow extends RenderFlex {
return super.constraints.copyWith(maxWidth: double.infinity);
}
@override
Size computeDryLayout(BoxConstraints constraints) {
final Size size = super.computeDryLayout(constraints.copyWith(maxWidth: double.infinity));
if (size.width <= constraints.maxWidth) {
return super.computeDryLayout(constraints);
}
double currentHeight = 0.0;
RenderBox? child = firstChild;
while (child != null) {
final BoxConstraints childConstraints = constraints.copyWith(minWidth: 0.0);
final Size childSize = child.getDryLayout(childConstraints);
currentHeight += childSize.height;
child = childAfter(child);
if (overflowButtonSpacing != null && child != null)
currentHeight += overflowButtonSpacing!;
}
return constraints.constrain(Size(constraints.maxWidth, currentHeight));
}
@override
void performLayout() {
// Set check layout width to false in reload or update cases.
......
......@@ -475,18 +475,33 @@ class _RenderInputPadding extends RenderShiftedBox {
return 0.0;
}
Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
if (child != null) {
final Size childSize = layoutChild(child!, constraints);
final double height = math.max(childSize.width, minSize.width);
final double width = math.max(childSize.height, minSize.height);
return constraints.constrain(Size(height, width));
}
return Size.zero;
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return _computeSize(
constraints: constraints,
layoutChild: ChildLayoutHelper.dryLayoutChild,
);
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
size = _computeSize(
constraints: constraints,
layoutChild: ChildLayoutHelper.layoutChild,
);
if (child != null) {
child!.layout(constraints, parentUsesSize: true);
final double height = math.max(child!.size.width, minSize.width);
final double width = math.max(child!.size.height, minSize.height);
size = constraints.constrain(Size(height, width));
final BoxParentData childParentData = child!.parentData! as BoxParentData;
childParentData.offset = Alignment.center.alongOffset(size - child!.size as Offset);
} else {
size = Size.zero;
}
}
......
This diff is collapsed.
......@@ -585,6 +585,19 @@ class _RenderChildOverflowBox extends RenderAligningShiftedBox {
@override
double computeMinIntrinsicHeight(double width) => 0.0;
@override
Size computeDryLayout(BoxConstraints constraints) {
if (child != null) {
final Size childSize = child!.getDryLayout(const BoxConstraints());
return Size(
math.max(constraints.minWidth, math.min(constraints.maxWidth, childSize.width)),
math.max(constraints.minHeight, math.min(constraints.maxHeight, childSize.height)),
);
} else {
return constraints.biggest;
}
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
......
......@@ -1279,6 +1279,14 @@ class _RenderDecoration extends RenderBox {
// Records where the label was painted.
Matrix4? _labelTransform;
@override
Size computeDryLayout(BoxConstraints constraints) {
assert(debugCannotComputeDryLayout(
reason: 'Layout requires baseline metrics, which are only available after a full layout.',
));
return const Size(0, 0);
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
......
......@@ -1669,6 +1669,14 @@ class _RenderListTile extends RenderBox {
parentData.offset = offset;
}
@override
Size computeDryLayout(BoxConstraints constraints) {
assert(debugCannotComputeDryLayout(
reason: 'Layout requires baseline metrics, which are only available after a full layout.'
));
return const Size(0, 0);
}
// All of the dimensions below were taken from the Material Design spec:
// https://material.io/design/components/lists.html#specs
@override
......
......@@ -149,6 +149,14 @@ class _RenderMenuItem extends RenderShiftedBox {
ValueChanged<Size> onLayout;
@override
Size computeDryLayout(BoxConstraints constraints) {
if (child == null) {
return Size.zero;
}
return child!.getDryLayout(constraints);
}
@override
void performLayout() {
if (child == null) {
......
......@@ -1265,8 +1265,8 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
bool get sizedByParent => true;
@override
void performResize() {
size = Size(
Size computeDryLayout(BoxConstraints constraints) {
return Size(
constraints.hasBoundedWidth ? constraints.maxWidth : _minPreferredTrackWidth + _maxSliderPartWidth,
constraints.hasBoundedHeight ? constraints.maxHeight : math.max(_minPreferredTrackHeight!, _maxSliderPartHeight),
);
......@@ -1719,4 +1719,9 @@ class _RenderValueIndicator extends RenderBox with RelayoutWhenSystemFontsChange
_state.paintTopValueIndicator!(context, offset);
}
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return constraints.smallest;
}
}
......@@ -1334,8 +1334,8 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
bool get sizedByParent => true;
@override
void performResize() {
size = Size(
Size computeDryLayout(BoxConstraints constraints) {
return Size(
constraints.hasBoundedWidth ? constraints.maxWidth : _minPreferredTrackWidth + _maxSliderPartWidth,
constraints.hasBoundedHeight ? constraints.maxHeight : math.max(_minPreferredTrackHeight, _maxSliderPartHeight),
);
......@@ -1589,4 +1589,9 @@ class _RenderValueIndicator extends RenderBox with RelayoutWhenSystemFontsChange
_state.paintValueIndicator!(context, offset);
}
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return constraints.smallest;
}
}
......@@ -576,7 +576,7 @@ class _TextSelectionToolbarItemsRenderBox extends RenderBox with ContainerRender
void performLayout() {
_lastIndexThatFits = -1;
if (firstChild == null) {
performResize();
size = constraints.smallest;
return;
}
......
......@@ -733,17 +733,33 @@ class _RenderInputPadding extends RenderShiftedBox {
return 0.0;
}
Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
if (child != null) {
final Size childSize = layoutChild(child!, constraints);
final double width = math.max(childSize.width, minSize.width);
final double height = math.max(childSize.height, minSize.height);
return constraints.constrain(Size(width, height));
}
return Size.zero;
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return _computeSize(
constraints: constraints,
layoutChild: ChildLayoutHelper.dryLayoutChild,
);
}
@override
void performLayout() {
size = _computeSize(
constraints: constraints,
layoutChild: ChildLayoutHelper.layoutChild,
);
if (child != null) {
child!.layout(constraints, parentUsesSize: true);
final double width = math.max(child!.size.width, minSize.width);
final double height = math.max(child!.size.height, minSize.height);
size = constraints.constrain(Size(width, height));
final BoxParentData childParentData = child!.parentData! as BoxParentData;
childParentData.offset = Alignment.center.alongOffset(size - child!.size as Offset);
} else {
size = Size.zero;
}
}
......
......@@ -1042,23 +1042,55 @@ class _SelectToggleButtonRenderObject extends RenderShiftedBox {
trailingBorderSide.width;
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return _computeSize(
constraints: constraints,
layoutChild: ChildLayoutHelper.dryLayoutChild,
);
}
@override
void performLayout() {
size = _computeSize(
constraints: constraints,
layoutChild: ChildLayoutHelper.layoutChild,
);
if (child == null) {
size = constraints.constrain(Size(
return;
}
switch (textDirection) {
case TextDirection.ltr:
final BoxParentData childParentData = child!.parentData! as BoxParentData;
childParentData.offset = Offset(leadingBorderSide.width, leadingBorderSide.width);
break;
case TextDirection.rtl:
final BoxParentData childParentData = child!.parentData! as BoxParentData;
if (isLastButton) {
childParentData.offset = Offset(_trailingBorderOffset, _trailingBorderOffset);
} else {
childParentData.offset = Offset(0, horizontalBorderSide.width);
}
break;
}
}
double get _trailingBorderOffset => isLastButton ? trailingBorderSide.width : 0.0;
Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
if (child == null) {
return constraints.constrain(Size(
leadingBorderSide.width + trailingBorderSide.width,
horizontalBorderSide.width * 2.0,
));
return;
}
final double trailingBorderOffset = isLastButton ? trailingBorderSide.width : 0.0;
final double leftConstraint;
final double rightConstraint;
switch (textDirection) {
case TextDirection.ltr:
rightConstraint = trailingBorderOffset;
rightConstraint = _trailingBorderOffset;
leftConstraint = leadingBorderSide.width;
final BoxConstraints innerConstraints = constraints.deflate(
......@@ -1070,18 +1102,15 @@ class _SelectToggleButtonRenderObject extends RenderShiftedBox {
),
);
child!.layout(innerConstraints, parentUsesSize: true);
final BoxParentData childParentData = child!.parentData! as BoxParentData;
childParentData.offset = Offset(leadingBorderSide.width, leadingBorderSide.width);
final Size childSize = layoutChild(child!, innerConstraints);
size = constraints.constrain(Size(
leftConstraint + child!.size.width + rightConstraint,
horizontalBorderSide.width * 2.0 + child!.size.height,
return constraints.constrain(Size(
leftConstraint + childSize.width + rightConstraint,
horizontalBorderSide.width * 2.0 + childSize.height,
));
break;
case TextDirection.rtl:
rightConstraint = leadingBorderSide.width;
leftConstraint = trailingBorderOffset;
leftConstraint = _trailingBorderOffset;
final BoxConstraints innerConstraints = constraints.deflate(
EdgeInsets.only(
......@@ -1092,20 +1121,12 @@ class _SelectToggleButtonRenderObject extends RenderShiftedBox {
),
);
child!.layout(innerConstraints, parentUsesSize: true);
final BoxParentData childParentData = child!.parentData! as BoxParentData;
if (isLastButton) {
childParentData.offset = Offset(trailingBorderOffset, trailingBorderOffset);
} else {
childParentData.offset = Offset(0, horizontalBorderSide.width);
}
final Size childSize = layoutChild(child!, innerConstraints);
size = constraints.constrain(Size(
leftConstraint + child!.size.width + rightConstraint,
horizontalBorderSide.width * 2.0 + child!.size.height,
return constraints.constrain(Size(
leftConstraint + childSize.width + rightConstraint,
horizontalBorderSide.width * 2.0 + childSize.height,
));
break;
}
}
......
......@@ -1183,16 +1183,16 @@ class _IntrinsicDimensionsCacheEntry {
///
/// Sizing purely based on the constraints allows the system to make some
/// significant optimizations. Classes that use this approach should override
/// [sizedByParent] to return true, and then override [performResize] to set the
/// [size] using nothing but the constraints, e.g.:
/// [sizedByParent] to return true, and then override [computeDryLayout] to
/// compute the [Size] using nothing but the constraints, e.g.:
///
/// ```dart
/// @override
/// bool get sizedByParent => true;
///
/// @override
/// void performResize() {
/// size = constraints.smallest;
/// Size computeDryLayout(BoxConstraints constraints) {
/// return constraints.smallest;
/// }
/// ```
///
......@@ -1768,6 +1768,124 @@ abstract class RenderBox extends RenderObject {
return 0.0;
}
Map<BoxConstraints, Size>? _cachedDryLayoutSizes;
/// Returns the [Size] that this [RenderBox] would like to be given the
/// provided [BoxConstraints].
///
/// The size returned by this method is guaranteed to be the same size that
/// this [RenderBox] computes for itself during layout given the same
/// constraints.
///
/// This function should only be called on one's children. Calling this
/// function couples the child with the parent so that when the child's layout
/// changes, the parent is notified (via [markNeedsLayout]).
///
/// This layout is called "dry" layout as opposed to the regular "wet" layout
/// run performed by [performLayout] because it computes the desired size for
/// the given constraints without changing any internal state.
///
/// Calling this function is expensive as it can result in O(N^2) behavior.
///
/// Do not override this method. Instead, implement [computeDryLayout].
@mustCallSuper
Size getDryLayout(BoxConstraints constraints) {
bool shouldCache = true;
assert(() {
// we don't want the checked-mode intrinsic tests to affect
// who gets marked dirty, etc.
if (RenderObject.debugCheckingIntrinsics)
shouldCache = false;
return true;
}());
if (shouldCache) {
_cachedDryLayoutSizes ??= <BoxConstraints, Size>{};
return _cachedDryLayoutSizes!.putIfAbsent(constraints, () => computeDryLayout(constraints));
}
return computeDryLayout(constraints);
}
/// Computes the value returned by [getDryLayout]. Do not call this
/// function directly, instead, call [getDryLayout].
///
/// Override in subclasses that implement [performLayout] or [performResize].
/// This method should return the [Size] that this [RenderBox] would like to
/// be given the provided [BoxConstraints].
///
/// The size returned by this method must match the [size] that the
/// [RenderBox] will compute for itself in [performLayout] (or
/// [performResize], if [sizedByParent] is true).
///
/// If this algorithm depends on the size of a child, the size of that child
/// should be obtained using its [getDryLayout] method.
///
/// This layout is called "dry" layout as opposed to the regular "wet" layout
/// run performed by [performLayout] because it computes the desired size for
/// the given constraints without changing any internal state.
///
/// ### When the size cannot be known
///
/// There are cases where render objects do not have an efficient way to
/// compute their size without doing a full layout. For example, the size
/// may depend on the baseline of a child (which is not available without
/// doing a full layout), it may be computed by a callback about which the
/// render object cannot reason, or the layout is so complex that it
/// is simply impractical to calculate the size in an efficient way.
///
/// In such cases, it may be impossible (or at least impractical) to actually
/// return a valid answer. In such cases, the function should call
/// [debugCannotComputeDryLayout] from within an assert and and return a dummy
/// value of `const Size(0, 0)`.
@protected
Size computeDryLayout(BoxConstraints constraints) {
assert(debugCannotComputeDryLayout(
error: FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('The ${objectRuntimeType(this, 'RenderBox')} class does not implement "computeDryLayout".'),
ErrorHint(
'If you are not writing your own RenderBox subclass, then this is not\n'
'your fault. Contact support: https://github.com/flutter/flutter/issues/new?template=2_bug.md'
),
]),
));
return const Size(0, 0);
}
static bool _dryLayoutCalculationValid = true;
/// Called from [computeDryLayout] within an assert if the given [RenderBox]
/// subclass does not support calculating a dry layout.
///
/// When asserts are enabled and [debugCheckingIntrinsics] is not true, this
/// method will either throw the provided [FlutterError] or it will create and
/// throw a [FlutterError] with the provided `reason`. Otherwise, it will
/// simply return true.
///
/// One of the arguments has to be provided.
///
/// See also:
///
/// * [computeDryLayout], which lists some reasons why it may not be feasible
/// to compute the dry layout.
bool debugCannotComputeDryLayout({String? reason, FlutterError? error}) {
assert((reason == null) != (error == null));
assert(() {
if (!RenderObject.debugCheckingIntrinsics) {
if (reason != null) {
assert(error ==null);
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('The ${objectRuntimeType(this, 'RenderBox')} class does not support dry layout.'),
if (reason.isNotEmpty) ErrorDescription(reason),
]);
}
assert(error != null);
throw error!;
}
_dryLayoutCalculationValid = false;
return true;
}());
return true;
}
/// Whether this render object has undergone layout and has a [size].
bool get hasSize => _size != null;
......@@ -2123,6 +2241,32 @@ abstract class RenderBox extends RenderObject {
),
]);
}
// Checking that getDryLayout computes the same size.
_dryLayoutCalculationValid = true;
RenderObject.debugCheckingIntrinsics = true;
late Size dryLayoutSize;
try {
dryLayoutSize = getDryLayout(constraints);
} finally {
RenderObject.debugCheckingIntrinsics = false;
}
if (_dryLayoutCalculationValid && dryLayoutSize != size) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('The size given to the ${objectRuntimeType(this, 'RenderBox')} class differs from the size computed by computeDryLayout.'),
ErrorDescription(
'The size computed in ${sizedByParent ? 'performResize' : 'performLayout'} '
'is $size, which is different from $dryLayoutSize, which was computed by computeDryLayout.'
),
ErrorDescription(
'The constraints used were $constraints.',
),
ErrorHint(
'If you are not writing your own RenderBox subclass, then this is not\n'
'your fault. Contact support: https://github.com/flutter/flutter/issues/new?template=2_bug.md'
),
]);
}
}
return true;
}());
......@@ -2131,7 +2275,8 @@ abstract class RenderBox extends RenderObject {
@override
void markNeedsLayout() {
if ((_cachedBaselines != null && _cachedBaselines!.isNotEmpty) ||
(_cachedIntrinsicDimensions != null && _cachedIntrinsicDimensions!.isNotEmpty)) {
(_cachedIntrinsicDimensions != null && _cachedIntrinsicDimensions!.isNotEmpty) ||
(_cachedDryLayoutSizes != null && _cachedDryLayoutSizes!.isNotEmpty)) {
// If we have cached data, then someone must have used our data.
// Since the parent will shortly be marked dirty, we can forget that they
// used the baseline and/or intrinsic dimensions. If they use them again,
......@@ -2139,6 +2284,7 @@ abstract class RenderBox extends RenderObject {
// notify them again.
_cachedBaselines?.clear();
_cachedIntrinsicDimensions?.clear();
_cachedDryLayoutSizes?.clear();
if (parent is RenderObject) {
markParentNeedsLayout();
return;
......@@ -2147,10 +2293,15 @@ abstract class RenderBox extends RenderObject {
super.markNeedsLayout();
}
/// {@macro flutter.rendering.RenderObject.performResize}
///
/// By default this method calls [getDryLayout] with the current
/// [constraints]. Instead of overriding this method, consider overriding
/// [computeDryLayout] (the backend implementation of [getDryLayout]).
@override
void performResize() {
// default behavior for subclasses that have sizedByParent = true
size = constraints.smallest;
size = computeDryLayout(constraints);
assert(size.isFinite);
}
......
......@@ -396,6 +396,11 @@ class RenderCustomMultiChildLayoutBox extends RenderBox
return 0.0;
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return _getSize(constraints);
}
@override
void performLayout() {
size = _getSize(constraints);
......
......@@ -514,11 +514,16 @@ class RenderCustomPaint extends RenderProxyBox {
}
@override
void performResize() {
size = constraints.constrain(preferredSize);
void performLayout() {
super.performLayout();
markNeedsSemanticsUpdate();
}
@override
Size computeSizeForNoChild(BoxConstraints constraints) {
return constraints.constrain(preferredSize);
}
void _paintWithPainter(Canvas canvas, Offset offset, CustomPainter painter) {
late int debugPreviousCanvasSaveCount;
canvas.save();
......
......@@ -2030,6 +2030,14 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
}
}
@override
Size computeDryLayout(BoxConstraints constraints) {
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
final double width = forceLine ? constraints.maxWidth : constraints
.constrainWidth(_textPainter.size.width + _caretMargin);
return Size(width, constraints.constrainHeight(_preferredHeight(constraints.maxWidth)));
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
......
......@@ -76,8 +76,8 @@ class RenderErrorBox extends RenderBox {
bool hitTestSelf(Offset position) => true;
@override
void performResize() {
size = constraints.constrain(const Size(_kMaxWidth, _kMaxHeight));
Size computeDryLayout(BoxConstraints constraints) {
return constraints.constrain(const Size(_kMaxWidth, _kMaxHeight));
}
/// The distance to place around the text.
......
......@@ -295,6 +295,11 @@ class RenderFlow extends RenderBox
return 0.0;
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return _getSize(constraints);
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
......
......@@ -371,6 +371,11 @@ class RenderImage extends RenderBox {
@override
bool hitTestSelf(Offset position) => true;
@override
Size computeDryLayout(BoxConstraints constraints) {
return _sizeForConstraints(constraints);
}
@override
void performLayout() {
size = _sizeForConstraints(constraints);
......
// 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 'dart:ui';
import 'box.dart';
/// Signature for a function that takes a [RenderBox] and returns the [Size]
/// that the [RenderBox] would have if it were laid out with the given
/// [BoxConstraints].
///
/// The methods of [ChildLayoutHelper] adhere to this signature.
typedef ChildLayouter = Size Function(RenderBox child, BoxConstraints constraints);
/// A collection of static functions to layout a [RenderBox] child with the
/// given set of [BoxConstraints].
///
/// All of the functions adhere to the [ChildLayouter] signature.
class ChildLayoutHelper {
const ChildLayoutHelper._();
/// Returns the [Size] that the [RenderBox] would have if it were to
/// be layed out with the given [BoxConstraints].
///
/// This method calls [RenderBox.getDryLayout] on the given [RenderBox].
///
/// This method should only be called by the parent of the provided
/// [RenderBox] child as it bounds parent and child together (if the child
/// is marked as dirty, the child will also be marked as dirty).
///
/// See also:
///
/// * [layoutChild], which actually lays out the child with the given
/// constraints.
static Size dryLayoutChild(RenderBox child, BoxConstraints constrains) {
return child.getDryLayout(constrains);
}
/// Lays out the [RenderBox] with the given constraints and returns its
/// [Size].
///
/// This method calls [RenderBox.layout] on the given [RenderBox] with
/// `parentUsesSize` set to true to receive its [Size].
///
/// This method should only be called by the parent of the provided
/// [RenderBox] child as it bounds parent and child together (if the child
/// is marked as dirty, the child will also be marked as dirty).
///
/// See also:
///
/// * [dryLayoutChild], which does not perform a real layout of the child.
static Size layoutChild(RenderBox child, BoxConstraints constraints) {
child.layout(constraints, parentUsesSize: true);
return child.size;
}
}
......@@ -62,8 +62,33 @@ class RenderListBody extends RenderBox
Axis get mainAxis => axisDirectionToAxis(axisDirection);
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
Size computeDryLayout(BoxConstraints constraints) {
assert(_debugCheckConstraints(constraints));
double mainAxisExtent = 0.0;
RenderBox? child = firstChild;
switch (axisDirection) {
case AxisDirection.right:
case AxisDirection.left:
final BoxConstraints innerConstraints = BoxConstraints.tightFor(height: constraints.maxHeight);
while (child != null) {
final Size childSize = child.getDryLayout(innerConstraints);
mainAxisExtent += childSize.width;
child = childAfter(child);
}
return constraints.constrain(Size(mainAxisExtent, constraints.maxHeight));
case AxisDirection.up:
case AxisDirection.down:
final BoxConstraints innerConstraints = BoxConstraints.tightFor(width: constraints.maxWidth);
while (child != null) {
final Size childSize = child.getDryLayout(innerConstraints);
mainAxisExtent += childSize.height;
child = childAfter(child);
}
return constraints.constrain(Size(constraints.maxWidth, mainAxisExtent));
}
}
bool _debugCheckConstraints(BoxConstraints constraints) {
assert(() {
switch (mainAxis) {
case Axis.horizontal:
......@@ -120,6 +145,13 @@ class RenderListBody extends RenderBox
)
]);
}());
return true;
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
assert(_debugCheckConstraints(constraints));
double mainAxisExtent = 0.0;
RenderBox? child = firstChild;
switch (axisDirection) {
......
......@@ -609,8 +609,8 @@ class RenderListWheelViewport
bool get sizedByParent => true;
@override
void performResize() {
size = constraints.biggest;
Size computeDryLayout(BoxConstraints constraints) {
return constraints.biggest;
}
/// Gets the index of a child by looking at its [parentData].
......
......@@ -1822,6 +1822,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
@protected
bool get sizedByParent => false;
/// {@template flutter.rendering.RenderObject.performResize}
/// Updates the render objects size using only the constraints.
///
/// Do not call this function directly: call [layout] instead. This function
......@@ -1829,10 +1830,12 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
/// render object during layout. The layout constraints provided by your
/// parent are available via the [constraints] getter.
///
/// Subclasses that set [sizedByParent] to true should override this method
/// to compute their size.
///
/// This function is called only if [sizedByParent] is true.
/// {@endtemplate}
///
/// Subclasses that set [sizedByParent] to true should override this method to
/// compute their size. Subclasses of [RenderBox] should consider overriding
/// [RenderBox.computeDryLayout] instead.
@protected
void performResize();
......
......@@ -435,10 +435,9 @@ class RenderParagraph extends RenderBox
// span will be scale up when it paints.
width = width / textScaleFactor;
while (child != null) {
final double intrinsicHeight = child.getMinIntrinsicHeight(width);
final double intrinsicWidth = child.getMinIntrinsicWidth(intrinsicHeight);
final Size size = child.getDryLayout(BoxConstraints(maxWidth: width));
placeholderDimensions[childIndex] = PlaceholderDimensions(
size: Size(intrinsicWidth, intrinsicHeight),
size: size,
alignment: _placeholderSpans[childIndex].alignment,
baseline: _placeholderSpans[childIndex].baseline,
);
......@@ -546,40 +545,47 @@ class RenderParagraph extends RenderBox
// children to _textPainter so that appropriate placeholders can be inserted
// into the LibTxt layout. This does not do anything if no inline widgets were
// specified.
void _layoutChildren(BoxConstraints constraints) {
List<PlaceholderDimensions> _layoutChildren(BoxConstraints constraints, {bool dry = false}) {
if (childCount == 0) {
return;
return <PlaceholderDimensions>[];
}
RenderBox? child = firstChild;
final List<PlaceholderDimensions> placeholderDimensions = List<PlaceholderDimensions>.filled(childCount, PlaceholderDimensions.empty, growable: false);
int childIndex = 0;
// Only constrain the width to the maximum width of the paragraph.
// Leave height unconstrained, which will overflow if expanded past.
BoxConstraints boxConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
// The content will be enlarged by textScaleFactor during painting phase.
// We reduce constraint by textScaleFactor so that the content will fit
// into the box once it is enlarged.
boxConstraints = boxConstraints / textScaleFactor;
while (child != null) {
// Only constrain the width to the maximum width of the paragraph.
// Leave height unconstrained, which will overflow if expanded past.
child.layout(
boxConstraints,
parentUsesSize: true,
);
double? baselineOffset;
switch (_placeholderSpans[childIndex].alignment) {
case ui.PlaceholderAlignment.baseline: {
baselineOffset = child.getDistanceToBaseline(
_placeholderSpans[childIndex].baseline!
);
break;
}
default: {
baselineOffset = null;
break;
final Size childSize;
if (!dry) {
child.layout(
boxConstraints,
parentUsesSize: true,
);
childSize = child.size;
switch (_placeholderSpans[childIndex].alignment) {
case ui.PlaceholderAlignment.baseline: {
baselineOffset = child.getDistanceToBaseline(
_placeholderSpans[childIndex].baseline!
);
break;
}
default: {
baselineOffset = null;
break;
}
}
} else {
assert(_placeholderSpans[childIndex].alignment != ui.PlaceholderAlignment.baseline);
childSize = child.getDryLayout(boxConstraints);
}
placeholderDimensions[childIndex] = PlaceholderDimensions(
size: child.size,
size: childSize,
alignment: _placeholderSpans[childIndex].alignment,
baseline: _placeholderSpans[childIndex].baseline,
baselineOffset: baselineOffset,
......@@ -587,7 +593,7 @@ class RenderParagraph extends RenderBox
child = childAfter(child);
childIndex += 1;
}
_placeholderDimensions = placeholderDimensions;
return placeholderDimensions;
}
// Iterate through the laid-out children and set the parentData offsets based
......@@ -607,10 +613,44 @@ class RenderParagraph extends RenderBox
}
}
bool _canComputeDryLayout() {
// Dry layout cannot be calculated without a full layout for
// alignments that require the baseline (baseline, aboveBaseline,
// belowBaseline).
for (final PlaceholderSpan span in _placeholderSpans) {
switch (span.alignment) {
case ui.PlaceholderAlignment.baseline:
case ui.PlaceholderAlignment.aboveBaseline:
case ui.PlaceholderAlignment.belowBaseline: {
return false;
}
case ui.PlaceholderAlignment.top:
case ui.PlaceholderAlignment.middle:
case ui.PlaceholderAlignment.bottom: {
continue;
}
}
}
return true;
}
@override
Size computeDryLayout(BoxConstraints constraints) {
if (!_canComputeDryLayout()) {
assert(debugCannotComputeDryLayout(
reason: 'Dry layout not available for alignments that require baseline.',
));
return const Size(0, 0);
}
_textPainter.setPlaceholderDimensions(_layoutChildren(constraints, dry: true));
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
return constraints.constrain(_textPainter.size);
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
_layoutChildren(constraints);
_placeholderDimensions = _layoutChildren(constraints);
_layoutTextWithConstraints(constraints);
_setParentData();
......
......@@ -162,8 +162,8 @@ class RenderPerformanceOverlay extends RenderBox {
}
@override
void performResize() {
size = constraints.constrain(Size(double.infinity, _intrinsicHeight));
Size computeDryLayout(BoxConstraints constraints) {
return constraints.constrain(Size(double.infinity, _intrinsicHeight));
}
@override
......
......@@ -165,9 +165,14 @@ class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
@override
bool get isRepaintBoundary => true;
@override
Size computeDryLayout(BoxConstraints constraints) {
return constraints.biggest;
}
@override
void performResize() {
size = constraints.biggest;
super.performResize();
_sizePlatformView();
}
......@@ -332,8 +337,8 @@ class RenderUiKitView extends RenderBox {
PointerEvent? _lastPointerDownEvent;
@override
void performResize() {
size = constraints.biggest;
Size computeDryLayout(BoxConstraints constraints) {
return constraints.biggest;
}
@override
......@@ -653,8 +658,8 @@ class PlatformViewRenderBox extends RenderBox with _PlatformViewGestureMixin {
bool get isRepaintBoundary => true;
@override
void performResize() {
size = constraints.biggest;
Size computeDryLayout(BoxConstraints constraints) {
return constraints.biggest;
}
@override
......
......@@ -16,6 +16,7 @@ import 'package:vector_math/vector_math_64.dart';
import 'binding.dart';
import 'box.dart';
import 'layer.dart';
import 'layout_helper.dart';
import 'mouse_cursor.dart';
import 'mouse_tracking.dart';
import 'object.dart';
......@@ -105,16 +106,30 @@ mixin RenderProxyBoxMixin<T extends RenderBox> on RenderBox, RenderObjectWithChi
return super.computeDistanceToActualBaseline(baseline);
}
@override
Size computeDryLayout(BoxConstraints constraints) {
if (child != null) {
return child!.getDryLayout(constraints);
}
return computeSizeForNoChild(constraints);
}
@override
void performLayout() {
if (child != null) {
child!.layout(constraints, parentUsesSize: true);
size = child!.size;
} else {
performResize();
size = computeSizeForNoChild(constraints);
}
}
/// Calculate the size the [RenderProxyBox] would have under the given
/// [BoxConstraints] for the case where it does not have a child.
Size computeSizeForNoChild(BoxConstraints constraints) {
return constraints.smallest;
}
@override
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
return child?.hitTest(result, position: position) ?? false;
......@@ -270,6 +285,15 @@ class RenderConstrainedBox extends RenderProxyBox {
}
}
@override
Size computeDryLayout(BoxConstraints constraints) {
if (child != null) {
return child!.getDryLayout(_additionalConstraints.enforce(constraints));
} else {
return _additionalConstraints.enforce(constraints).constrain(Size.zero);
}
}
@override
void debugPaintSize(PaintingContext context, Offset offset) {
super.debugPaintSize(context, offset);
......@@ -350,15 +374,28 @@ class RenderLimitedBox extends RenderProxyBox {
);
}
@override
void performLayout() {
Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild }) {
if (child != null) {
final BoxConstraints constraints = this.constraints;
child!.layout(_limitConstraints(constraints), parentUsesSize: true);
size = constraints.constrain(child!.size);
} else {
size = _limitConstraints(constraints).constrain(Size.zero);
final Size childSize = layoutChild(child!, _limitConstraints(constraints));
return constraints.constrain(childSize);
}
return _limitConstraints(constraints).constrain(Size.zero);
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return _computeSize(
constraints: constraints,
layoutChild: ChildLayoutHelper.dryLayoutChild,
);
}
@override
void performLayout() {
size = _computeSize(
constraints: constraints,
layoutChild: ChildLayoutHelper.layoutChild,
);
}
@override
......@@ -519,9 +556,14 @@ class RenderAspectRatio extends RenderProxyBox {
return constraints.constrain(Size(width, height));
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return _applyAspectRatio(constraints);
}
@override
void performLayout() {
size = _applyAspectRatio(constraints);
size = computeDryLayout(constraints);
if (child != null)
child!.layout(BoxConstraints.tight(size));
}
......@@ -648,27 +690,40 @@ class RenderIntrinsicWidth extends RenderProxyBox {
return _applyStep(height, _stepHeight);
}
@override
void performLayout() {
Size _computeSize({required ChildLayouter layoutChild, required BoxConstraints constraints}) {
if (child != null) {
BoxConstraints childConstraints = constraints;
if (!childConstraints.hasTightWidth) {
final double width = child!.getMaxIntrinsicWidth(childConstraints.maxHeight);
if (!constraints.hasTightWidth) {
final double width = child!.getMaxIntrinsicWidth(constraints.maxHeight);
assert(width.isFinite);
childConstraints = childConstraints.tighten(width: _applyStep(width, _stepWidth));
constraints = constraints.tighten(width: _applyStep(width, _stepWidth));
}
if (_stepHeight != null) {
final double height = child!.getMaxIntrinsicHeight(childConstraints.maxWidth);
final double height = child!.getMaxIntrinsicHeight(constraints.maxWidth);
assert(height.isFinite);
childConstraints = childConstraints.tighten(height: _applyStep(height, _stepHeight));
constraints = constraints.tighten(height: _applyStep(height, _stepHeight));
}
child!.layout(childConstraints, parentUsesSize: true);
size = child!.size;
return layoutChild(child!, constraints);
} else {
performResize();
return constraints.smallest;
}
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return _computeSize(
layoutChild: ChildLayoutHelper.dryLayoutChild,
constraints: constraints,
);
}
@override
void performLayout() {
size = _computeSize(
layoutChild: ChildLayoutHelper.layoutChild,
constraints: constraints,
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
......@@ -736,22 +791,34 @@ class RenderIntrinsicHeight extends RenderProxyBox {
return computeMaxIntrinsicHeight(width);
}
@override
void performLayout() {
Size _computeSize({required ChildLayouter layoutChild, required BoxConstraints constraints}) {
if (child != null) {
BoxConstraints childConstraints = constraints;
if (!childConstraints.hasTightHeight) {
final double height = child!.getMaxIntrinsicHeight(childConstraints.maxWidth);
if (!constraints.hasTightHeight) {
final double height = child!.getMaxIntrinsicHeight(constraints.maxWidth);
assert(height.isFinite);
childConstraints = childConstraints.tighten(height: height);
constraints = constraints.tighten(height: height);
}
child!.layout(childConstraints, parentUsesSize: true);
size = child!.size;
return layoutChild(child!, constraints);
} else {
performResize();
return constraints.smallest;
}
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return _computeSize(
layoutChild: ChildLayoutHelper.dryLayoutChild,
constraints: constraints,
);
}
@override
void performLayout() {
size = _computeSize(
layoutChild: ChildLayoutHelper.layoutChild,
constraints: constraints,
);
}
}
/// Makes its child partially transparent.
......@@ -2417,6 +2484,42 @@ class RenderFittedBox extends RenderProxyBox {
// TODO(ianh): The intrinsic dimensions of this box are wrong.
@override
Size computeDryLayout(BoxConstraints constraints) {
if (child != null) {
final Size childSize = child!.getDryLayout(const BoxConstraints());
// During [RenderObject.debugCheckingIntrinsics] a child that doesn't
// support dry layout may provide us with an invalid size that triggers
// assertions if we try to work with it. Instead of throwing, we bail
// out early in that case.
bool invalidChildSize = false;
assert(() {
if (RenderObject.debugCheckingIntrinsics && childSize.width * childSize.height == 0.0) {
invalidChildSize = true;
}
return true;
}());
if (invalidChildSize) {
assert(debugCannotComputeDryLayout(
reason: 'Child provided invalid size of $childSize.',
));
return const Size(0, 0);
}
switch (fit) {
case BoxFit.scaleDown:
final BoxConstraints sizeConstraints = constraints.loosen();
final Size unconstrainedSize = sizeConstraints.constrainSizeAndAttemptToPreserveAspectRatio(childSize);
return constraints.constrain(unconstrainedSize);
default:
return constraints.constrainSizeAndAttemptToPreserveAspectRatio(childSize);
}
} else {
return constraints.smallest;
}
}
@override
void performLayout() {
if (child != null) {
......@@ -2709,8 +2812,8 @@ class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
PointerSignalEventListener? onPointerSignal;
@override
void performResize() {
size = constraints.biggest;
Size computeSizeForNoChild(BoxConstraints constraints) {
return constraints.biggest;
}
@override
......@@ -2871,8 +2974,8 @@ class RenderMouseRegion extends RenderProxyBox implements MouseTrackerAnnotation
}
@override
void performResize() {
size = constraints.biggest;
Size computeSizeForNoChild(BoxConstraints constraints) {
return constraints.biggest;
}
@override
......@@ -3238,10 +3341,19 @@ class RenderOffstage extends RenderProxyBox {
@override
bool get sizedByParent => offstage;
@override
Size computeDryLayout(BoxConstraints constraints) {
if (offstage) {
return constraints.smallest;
}
return super.computeDryLayout(constraints);
}
@override
void performResize() {
assert(offstage);
size = constraints.smallest;
super.performResize();
}
@override
......
......@@ -74,6 +74,15 @@ class RenderRotatedBox extends RenderBox with RenderObjectWithChildMixin<RenderB
Matrix4? _paintTransform;
@override
Size computeDryLayout(BoxConstraints constraints) {
if (child == null) {
return constraints.smallest;
}
final Size childSize = child!.getDryLayout(_isVertical ? constraints.flipped : constraints);
return _isVertical ? Size(childSize.height, childSize.width) : childSize;
}
@override
void performLayout() {
_paintTransform = null;
......@@ -85,7 +94,7 @@ class RenderRotatedBox extends RenderBox with RenderObjectWithChildMixin<RenderB
..rotateZ(_kQuarterTurnsInRadians * (quarterTurns % 4))
..translate(-child!.size.width / 2.0, -child!.size.height / 2.0);
} else {
performResize();
size = constraints.smallest;
}
}
......
......@@ -10,6 +10,7 @@ import 'box.dart';
import 'debug.dart';
import 'debug_overflow_indicator.dart';
import 'layer.dart';
import 'layout_helper.dart';
import 'object.dart';
import 'stack.dart' show RelativeRect;
......@@ -87,7 +88,6 @@ abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixi
}
return false;
}
}
/// Insets its child by the given padding.
......@@ -192,6 +192,24 @@ class RenderPadding extends RenderShiftedBox {
return totalVerticalPadding;
}
@override
Size computeDryLayout(BoxConstraints constraints) {
_resolve();
assert(_resolvedPadding != null);
if (child == null) {
return constraints.constrain(Size(
_resolvedPadding!.left + _resolvedPadding!.right,
_resolvedPadding!.top + _resolvedPadding!.bottom,
));
}
final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding!);
final Size childSize = child!.getDryLayout(innerConstraints);
return constraints.constrain(Size(
_resolvedPadding!.left + childSize.width + _resolvedPadding!.right,
_resolvedPadding!.top + childSize.height + _resolvedPadding!.bottom,
));
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
......@@ -385,6 +403,23 @@ class RenderPositionedBox extends RenderAligningShiftedBox {
markNeedsLayout();
}
@override
Size computeDryLayout(BoxConstraints constraints) {
final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity;
final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity;
if (child != null) {
final Size childSize = child!.getDryLayout(constraints.loosen());
return constraints.constrain(Size(
shrinkWrapWidth ? childSize.width * (_widthFactor ?? 1.0) : double.infinity,
shrinkWrapHeight ? childSize.height * (_heightFactor ?? 1.0) : double.infinity),
);
}
return constraints.constrain(Size(
shrinkWrapWidth ? 0.0 : double.infinity,
shrinkWrapHeight ? 0.0 : double.infinity,
));
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
......@@ -571,8 +606,8 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
bool get sizedByParent => true;
@override
void performResize() {
size = constraints.biggest;
Size computeDryLayout(BoxConstraints constraints) {
return constraints.biggest;
}
@override
......@@ -667,27 +702,45 @@ class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflow
}
}
Size _calculateSizeWithChild({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
assert(child != null);
// Let the child lay itself out at it's "natural" size, but if
// constrainedAxis is non-null, keep any constraints on that axis.
final BoxConstraints childConstraints;
if (constrainedAxis != null) {
switch (constrainedAxis!) {
case Axis.horizontal:
childConstraints = BoxConstraints(maxWidth: constraints.maxWidth, minWidth: constraints.minWidth);
break;
case Axis.vertical:
childConstraints = BoxConstraints(maxHeight: constraints.maxHeight, minHeight: constraints.minHeight);
break;
}
} else {
childConstraints = const BoxConstraints();
}
return constraints.constrain(layoutChild(child!, childConstraints));
}
@override
Size computeDryLayout(BoxConstraints constraints) {
if (child == null) {
return constraints.smallest;
}
return _calculateSizeWithChild(
constraints: constraints,
layoutChild: ChildLayoutHelper.dryLayoutChild,
);
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
if (child != null) {
// Let the child lay itself out at it's "natural" size, but if
// constrainedAxis is non-null, keep any constraints on that axis.
final BoxConstraints childConstraints;
if (constrainedAxis != null) {
switch (constrainedAxis!) {
case Axis.horizontal:
childConstraints = BoxConstraints(maxWidth: constraints.maxWidth, minWidth: constraints.minWidth);
break;
case Axis.vertical:
childConstraints = BoxConstraints(maxHeight: constraints.maxHeight, minHeight: constraints.minHeight);
break;
}
} else {
childConstraints = const BoxConstraints();
}
child!.layout(childConstraints, parentUsesSize: true);
size = constraints.constrain(child!.size);
size = _calculateSizeWithChild(
constraints: constraints,
layoutChild: ChildLayoutHelper.layoutChild,
);
alignChild();
final BoxParentData childParentData = child!.parentData! as BoxParentData;
_overflowContainerRect = Offset.zero & size;
......@@ -812,6 +865,11 @@ class RenderSizedOverflowBox extends RenderAligningShiftedBox {
return super.computeDistanceToActualBaseline(baseline);
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return constraints.constrain(_requestedSize);
}
@override
void performLayout() {
size = constraints.constrain(_requestedSize);
......@@ -957,6 +1015,15 @@ class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox {
return result / (_heightFactor ?? 1.0);
}
@override
Size computeDryLayout(BoxConstraints constraints) {
if (child != null) {
final Size childSize = child!.getDryLayout(_getInnerConstraints(constraints));
return constraints.constrain(childSize);
}
return constraints.constrain(_getInnerConstraints(constraints).constrain(Size.zero));
}
@override
void performLayout() {
if (child != null) {
......@@ -1143,6 +1210,11 @@ class RenderCustomSingleChildLayoutBox extends RenderShiftedBox {
return 0.0;
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return _getSize(constraints);
}
@override
void performLayout() {
size = _getSize(constraints);
......@@ -1209,6 +1281,17 @@ class RenderBaseline extends RenderShiftedBox {
markNeedsLayout();
}
@override
Size computeDryLayout(BoxConstraints constraints) {
if (child != null) {
assert(debugCannotComputeDryLayout(
reason: 'Baseline metrics are only available after a full layout.',
));
return const Size(0, 0);
}
return constraints.smallest;
}
@override
void performLayout() {
if (child != null) {
......@@ -1222,7 +1305,7 @@ class RenderBaseline extends RenderShiftedBox {
final Size childSize = child!.size;
size = constraints.constrain(Size(childSize.width, top + childSize.height));
} else {
performResize();
size = constraints.smallest;
}
}
......
......@@ -9,6 +9,7 @@ import 'package:flutter/foundation.dart';
import 'box.dart';
import 'layer.dart';
import 'layout_helper.dart';
import 'object.dart';
/// An immutable 2D, axis-aligned, floating-point rectangle whose coordinates
......@@ -525,16 +526,20 @@ class RenderStack extends RenderBox
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
Size computeDryLayout(BoxConstraints constraints) {
return _computeSize(
constraints: constraints,
layoutChild: ChildLayoutHelper.dryLayoutChild,
);
}
Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
_resolve();
assert(_resolvedAlignment != null);
_hasVisualOverflow = false;
bool hasNonPositionedChildren = false;
if (childCount == 0) {
size = constraints.biggest;
assert(size.isFinite);
return;
assert(constraints.biggest.isFinite);
return constraints.biggest;
}
double width = constraints.minWidth;
......@@ -562,9 +567,8 @@ class RenderStack extends RenderBox
if (!childParentData.isPositioned) {
hasNonPositionedChildren = true;
child.layout(nonPositionedConstraints, parentUsesSize: true);
final Size childSize = layoutChild(child, nonPositionedConstraints);
final Size childSize = child.size;
width = math.max(width, childSize.width);
height = math.max(height, childSize.height);
}
......@@ -572,6 +576,7 @@ class RenderStack extends RenderBox
child = childParentData.nextSibling;
}
final Size size;
if (hasNonPositionedChildren) {
size = Size(width, height);
assert(size.width == constraints.constrainWidth(width));
......@@ -581,8 +586,21 @@ class RenderStack extends RenderBox
}
assert(size.isFinite);
return size;
}
child = firstChild;
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
_hasVisualOverflow = false;
size = _computeSize(
constraints: constraints,
layoutChild: ChildLayoutHelper.layoutChild,
);
assert(_resolvedAlignment != null);
RenderBox? child = firstChild;
while (child != null) {
final StackParentData childParentData = child.parentData! as StackParentData;
......
......@@ -999,6 +999,44 @@ class RenderTable extends RenderBox {
return Rect.fromLTRB(0.0, _rowTops[row], size.width, _rowTops[row + 1]);
}
@override
Size computeDryLayout(BoxConstraints constraints) {
if (rows * columns == 0) {
return constraints.constrain(const Size(0.0, 0.0));
}
final List<double> widths = _computeColumnWidths(constraints);
final double tableWidth = widths.fold(0.0, (double a, double b) => a + b);
double rowTop = 0.0;
for (int y = 0; y < rows; y += 1) {
double rowHeight = 0.0;
for (int x = 0; x < columns; x += 1) {
final int xy = x + y * columns;
final RenderBox? child = _children[xy];
if (child != null) {
final TableCellParentData childParentData = child.parentData! as TableCellParentData;
assert(childParentData != null);
switch (childParentData.verticalAlignment ?? defaultVerticalAlignment) {
case TableCellVerticalAlignment.baseline:
assert(debugCannotComputeDryLayout(
reason: 'TableCellVerticalAlignment.baseline requires a full layout for baseline metrics to be available.'
));
return const Size(0 ,0);
case TableCellVerticalAlignment.top:
case TableCellVerticalAlignment.middle:
case TableCellVerticalAlignment.bottom:
final Size childSize = child.getDryLayout(BoxConstraints.tightFor(width: widths[x]));
rowHeight = math.max(rowHeight, childSize.height);
break;
case TableCellVerticalAlignment.fill:
break;
}
}
}
rowTop += rowHeight;
}
return constraints.constrain(Size(tableWidth, rowTop));
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
......
......@@ -75,8 +75,8 @@ class TextureBox extends RenderBox {
bool get isRepaintBoundary => true;
@override
void performResize() {
size = constraints.biggest;
Size computeDryLayout(BoxConstraints constraints) {
return constraints.biggest;
}
@override
......
......@@ -1357,7 +1357,7 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat
bool get sizedByParent => true;
@override
void performResize() {
Size computeDryLayout(BoxConstraints constraints) {
assert(() {
if (!constraints.hasBoundedHeight || !constraints.hasBoundedWidth) {
switch (axis) {
......@@ -1425,7 +1425,7 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat
}
return true;
}());
size = constraints.biggest;
return constraints.biggest;
}
static const int _maxLayoutCycles = 10;
......
......@@ -6,6 +6,7 @@ import 'dart:math' as math;
import 'box.dart';
import 'layer.dart';
import 'layout_helper.dart';
import 'object.dart';
/// How [Wrap] should align objects.
......@@ -400,66 +401,6 @@ class RenderWrap extends RenderBox
child.parentData = WrapParentData();
}
double _computeIntrinsicHeightForWidth(double width) {
assert(direction == Axis.horizontal);
double height = 0.0;
double runWidth = 0.0;
double runHeight = 0.0;
int childCount = 0;
RenderBox? child = firstChild;
while (child != null) {
// TODO(chunhtai): use the new intrinsic API to calculate child sizes
// once https://github.com/flutter/flutter/issues/48679 is fixed.
final double childWidth = math.min(child.getMaxIntrinsicWidth(double.infinity), width);
final double childHeight = child.getMaxIntrinsicHeight(childWidth);
// There must be at least one child before we move on to the next run.
if (childCount > 0 && runWidth + childWidth + spacing > width) {
height += runHeight + runSpacing;
runWidth = 0.0;
runHeight = 0.0;
childCount = 0;
}
runWidth += childWidth;
runHeight = math.max(runHeight, childHeight);
if (childCount > 0)
runWidth += spacing;
childCount += 1;
child = childAfter(child);
}
height += runHeight;
return height;
}
double _computeIntrinsicWidthForHeight(double height) {
assert(direction == Axis.vertical);
double width = 0.0;
double runHeight = 0.0;
double runWidth = 0.0;
int childCount = 0;
RenderBox? child = firstChild;
while (child != null) {
// TODO(chunhtai): use the new intrinsic API to calculate child sizes
// once https://github.com/flutter/flutter/issues/48679 is fixed.
final double childHeight = math.min(child.getMaxIntrinsicHeight(double.infinity), height);
final double childWidth = child.getMaxIntrinsicWidth(childHeight);
// There must be at least one child before we move on to the next run.
if (childCount > 0 && runHeight + childHeight + spacing > height) {
width += runWidth + runSpacing;
runHeight = 0.0;
runWidth = 0.0;
childCount = 0;
}
runHeight += childHeight;
runWidth = math.max(runWidth, childWidth);
if (childCount > 0)
runHeight += spacing;
childCount += 1;
child = childAfter(child);
}
width += runWidth;
return width;
}
@override
double computeMinIntrinsicWidth(double height) {
switch (direction) {
......@@ -472,7 +413,7 @@ class RenderWrap extends RenderBox
}
return width;
case Axis.vertical:
return _computeIntrinsicWidthForHeight(height);
return computeDryLayout(BoxConstraints(maxHeight: height)).width;
}
}
......@@ -488,7 +429,7 @@ class RenderWrap extends RenderBox
}
return width;
case Axis.vertical:
return _computeIntrinsicWidthForHeight(height);
return computeDryLayout(BoxConstraints(maxHeight: height)).width;
}
}
......@@ -496,7 +437,7 @@ class RenderWrap extends RenderBox
double computeMinIntrinsicHeight(double width) {
switch (direction) {
case Axis.horizontal:
return _computeIntrinsicHeightForWidth(width);
return computeDryLayout(BoxConstraints(maxWidth: width)).height;
case Axis.vertical:
double height = 0.0;
RenderBox? child = firstChild;
......@@ -512,7 +453,7 @@ class RenderWrap extends RenderBox
double computeMaxIntrinsicHeight(double width) {
switch (direction) {
case Axis.horizontal:
return _computeIntrinsicHeightForWidth(width);
return computeDryLayout(BoxConstraints(maxWidth: width)).height;
case Axis.vertical:
double height = 0.0;
RenderBox? child = firstChild;
......@@ -529,21 +470,21 @@ class RenderWrap extends RenderBox
return defaultComputeDistanceToHighestActualBaseline(baseline);
}
double _getMainAxisExtent(RenderBox child) {
double _getMainAxisExtent(Size childSize) {
switch (direction) {
case Axis.horizontal:
return child.size.width;
return childSize.width;
case Axis.vertical:
return child.size.height;
return childSize.height;
}
}
double _getCrossAxisExtent(RenderBox child) {
double _getCrossAxisExtent(Size childSize) {
switch (direction) {
case Axis.horizontal:
return child.size.height;
return childSize.height;
case Axis.vertical:
return child.size.width;
return childSize.width;
}
}
......@@ -570,6 +511,61 @@ class RenderWrap extends RenderBox
bool _hasVisualOverflow = false;
@override
Size computeDryLayout(BoxConstraints constraints) {
return _computeDryLayout(constraints);
}
Size _computeDryLayout(BoxConstraints constraints, [ChildLayouter layoutChild = ChildLayoutHelper.dryLayoutChild]) {
final BoxConstraints childConstraints;
double mainAxisLimit = 0.0;
switch (direction) {
case Axis.horizontal:
childConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
mainAxisLimit = constraints.maxWidth;
break;
case Axis.vertical:
childConstraints = BoxConstraints(maxHeight: constraints.maxHeight);
mainAxisLimit = constraints.maxHeight;
break;
}
double mainAxisExtent = 0.0;
double crossAxisExtent = 0.0;
double runMainAxisExtent = 0.0;
double runCrossAxisExtent = 0.0;
int childCount = 0;
RenderBox? child = firstChild;
while (child != null) {
final Size childSize = layoutChild(child, childConstraints);
final double childMainAxisExtent = _getMainAxisExtent(childSize);
final double childCrossAxisExtent = _getCrossAxisExtent(childSize);
// There must be at least one child before we move on to the next run.
if (childCount > 0 && runMainAxisExtent + childMainAxisExtent + spacing > mainAxisLimit) {
mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
crossAxisExtent += runCrossAxisExtent + runSpacing;
runMainAxisExtent = 0.0;
runCrossAxisExtent = 0.0;
childCount = 0;
}
runMainAxisExtent += childMainAxisExtent;
runCrossAxisExtent = math.max(runCrossAxisExtent, childCrossAxisExtent);
if (childCount > 0)
runMainAxisExtent += spacing;
childCount += 1;
child = childAfter(child);
}
crossAxisExtent += runCrossAxisExtent;
mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
switch (direction) {
case Axis.horizontal:
return constraints.constrain(Size(mainAxisExtent, crossAxisExtent));
case Axis.vertical:
return constraints.constrain(Size(crossAxisExtent, mainAxisExtent));
}
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
......@@ -614,8 +610,8 @@ class RenderWrap extends RenderBox
int childCount = 0;
while (child != null) {
child.layout(childConstraints, parentUsesSize: true);
final double childMainAxisExtent = _getMainAxisExtent(child);
final double childCrossAxisExtent = _getCrossAxisExtent(child);
final double childMainAxisExtent = _getMainAxisExtent(child.size);
final double childCrossAxisExtent = _getCrossAxisExtent(child.size);
if (childCount > 0 && runMainAxisExtent + spacing + childMainAxisExtent > mainAxisLimit) {
mainAxisExtent = math.max(mainAxisExtent, runMainAxisExtent);
crossAxisExtent += runCrossAxisExtent;
......@@ -735,8 +731,8 @@ class RenderWrap extends RenderBox
final WrapParentData childParentData = child.parentData! as WrapParentData;
if (childParentData._runIndex != i)
break;
final double childMainAxisExtent = _getMainAxisExtent(child);
final double childCrossAxisExtent = _getCrossAxisExtent(child);
final double childMainAxisExtent = _getMainAxisExtent(child.size);
final double childCrossAxisExtent = _getCrossAxisExtent(child.size);
final double childCrossAxisOffset = _getChildCrossAxisOffset(flipCrossAxis, runCrossAxisExtent, childCrossAxisExtent);
if (flipMainAxis)
childMainPosition -= childMainAxisExtent;
......
......@@ -345,6 +345,15 @@ class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<Ren
return 0.0;
}
@override
Size computeDryLayout(BoxConstraints constraints) {
assert(debugCannotComputeDryLayout(reason:
'Calculating the dry layout would require running the layout callback '
'speculatively, which might mutate the live render object tree.',
));
return const Size(0, 0);
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
......
......@@ -436,6 +436,31 @@ class _RenderOverflowBar extends RenderBox
return defaultComputeDistanceToHighestActualBaseline(baseline);
}
@override
Size computeDryLayout(BoxConstraints constraints) {
RenderBox? child = firstChild;
if (child == null) {
return constraints.smallest;
}
final BoxConstraints childConstraints = constraints.loosen();
double childrenWidth = 0.0;
double maxChildHeight = 0.0;
double y = 0.0;
while (child != null) {
final Size childSize = child.getDryLayout(childConstraints);
childrenWidth += childSize.width;
maxChildHeight = math.max(maxChildHeight, childSize.height);
y += childSize.height + overflowSpacing;
child = childAfter(child);
}
final double actualWidth = childrenWidth + spacing * (childCount - 1);
if (actualWidth > constraints.maxWidth) {
return constraints.constrain(Size(constraints.maxWidth, y - overflowSpacing));
} else {
return constraints.constrain(Size(actualWidth, maxChildHeight));
}
}
@override
void performLayout() {
RenderBox? child = firstChild;
......
......@@ -716,9 +716,9 @@ class _RenderTheatre extends RenderBox with ContainerRenderObjectMixin<RenderBox
bool get sizedByParent => true;
@override
void performResize() {
size = constraints.biggest;
assert(size.isFinite);
Size computeDryLayout(BoxConstraints constraints) {
assert(constraints.biggest.isFinite);
return constraints.biggest;
}
@override
......
......@@ -540,6 +540,15 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
// scroll, it would shift in its parent if the parent was baseline-aligned,
// which makes no sense.
@override
Size computeDryLayout(BoxConstraints constraints) {
if (child == null) {
return constraints.smallest;
}
final Size childSize = child!.getDryLayout(_getInnerConstraints(constraints));
return constraints.constrain(childSize);
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
......
......@@ -2446,8 +2446,8 @@ class _RenderInspectorOverlay extends RenderBox {
bool get alwaysNeedsCompositing => true;
@override
void performResize() {
size = constraints.constrain(const Size(double.infinity, double.infinity));
Size computeDryLayout(BoxConstraints constraints) {
return constraints.constrain(const Size(double.infinity, double.infinity));
}
@override
......
......@@ -159,8 +159,8 @@ class RenderBaselineDetector extends RenderBox {
}
@override
void performResize() {
size = constraints.smallest;
Size computeDryLayout(BoxConstraints constraints) {
return constraints.smallest;
}
@override
......
......@@ -112,6 +112,9 @@ void main() {
Duration.zero,
EnginePhase.layout,
);
// Turn off intrinsics checking, which also fails with the same exception.
debugCheckIntrinsicSizes = false;
await tester.pumpWidget(
Column(
children: <Widget>[
......@@ -125,6 +128,7 @@ void main() {
Duration.zero,
EnginePhase.layout,
);
debugCheckIntrinsicSizes = true;
final String message = tester.takeException().toString();
expect(message, contains('\nSee also:'));
});
......
......@@ -1804,6 +1804,11 @@ class RenderObjectWidgetSpy extends LeafRenderObjectWidget {
}
class FakeLeafRenderObject extends RenderBox {
@override
Size computeDryLayout(BoxConstraints constraints) {
return constraints.biggest;
}
@override
void performLayout() {
size = constraints.biggest;
......
......@@ -715,6 +715,11 @@ class _RenderTestLayoutPerformer extends RenderBox {
final VoidCallback _performLayout;
@override
Size computeDryLayout(BoxConstraints constraints) {
return const Size(1, 1);
}
@override
void performLayout() {
size = const Size(1, 1);
......
......@@ -690,6 +690,11 @@ class _RenderLayoutSpy extends RenderBox {
size = constraints.biggest;
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return constraints.biggest;
}
@override
void performLayout() {
performLayoutCount += 1;
......
......@@ -230,6 +230,11 @@ class RenderSwapper extends RenderBox {
visitChildren((RenderObject child) => child.detach());
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return constraints.biggest;
}
@override
void performLayout() {
assert(constraints.hasBoundedWidth);
......
......@@ -54,6 +54,11 @@ class TestNonVisitingWidget extends SingleChildRenderObjectWidget {
}
class TestNonVisitingRenderObject extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
@override
Size computeDryLayout(BoxConstraints constraints) {
return child!.getDryLayout(constraints);
}
@override
void performLayout() {
child!.layout(constraints, parentUsesSize: true);
......
......@@ -47,4 +47,37 @@ void main() {
],
));
});
testWidgets('WidgetSpan calculate correct intrinsic heights', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/48679.
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Container(
color: Colors.green,
child: IntrinsicHeight(
child: RichText(
text: TextSpan(
children: <InlineSpan>[
const TextSpan(text: 'Start\n', style: TextStyle(height: 1.0, fontSize: 16)),
WidgetSpan(
child: Row(
children: const <Widget>[
SizedBox(height: 16, width: 16,),
],
),
),
const TextSpan(text: 'End', style: TextStyle(height: 1.0, fontSize: 16)),
],
),
),
),
),
),
),
);
expect(tester.getSize(find.byType(IntrinsicHeight)).height, 3 * 16);
});
}
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
......@@ -905,4 +906,69 @@ void main() {
await tester.pumpWidget(Wrap(textDirection: TextDirection.ltr, clipBehavior: Clip.antiAlias));
expect(renderObject.clipBehavior, equals(Clip.antiAlias));
});
testWidgets('Horizontal wrap - IntrinsicsHeight', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/48679.
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: IntrinsicHeight(
child: Container(
color: Colors.green,
child: Wrap(
children: <Widget>[
const Text('Start', style: TextStyle(height: 1.0, fontSize: 16)),
Row(
children: const <Widget>[
SizedBox(height: 40, width: 60),
],
),
const Text('End', style: TextStyle(height: 1.0, fontSize: 16)),
],
),
),
),
),
),
);
// The row takes up the full width, therefore the "Start" and "End" text
// are placed before and after it and the total height is the sum of the
// individual heights.
expect(tester.getSize(find.byType(IntrinsicHeight)).height, 2 * 16 + 40);
});
testWidgets('Vertical wrap - IntrinsicsWidth', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/48679.
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: IntrinsicWidth(
child: Container(
color: Colors.green,
child: Wrap(
direction: Axis.vertical,
children: <Widget>[
const Text('Start', style: TextStyle(height: 1.0, fontSize: 16)),
Column(
children: const <Widget>[
SizedBox(height: 40, width: 60),
],
),
const Text('End', style: TextStyle(height: 1.0, fontSize: 16)),
],
),
),
),
),
),
);
// The column takes up the full height, therefore the "Start" and "End" text
// are placed to the left and right of it and the total width is the sum of
// the individual widths.
expect(tester.getSize(find.byType(IntrinsicWidth)).width, 5 * 16 + 60 + 3 * 16);
});
}
......@@ -198,6 +198,11 @@ class SimpleCustomSemanticsRenderObject extends RenderBox {
@override
bool get sizedByParent => true;
@override
Size computeDryLayout(BoxConstraints constraints) {
return constraints.smallest;
}
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
......
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