From ef7019e801b3ca2f47e665dce8b54ff52a58e857 Mon Sep 17 00:00:00 2001 From: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Wed, 3 Apr 2024 11:36:19 -0700 Subject: [PATCH] `computeDryBaseline` for rendering / widgets RenderBoxes (#146143) RenderWrap, Table, Overlay / Stack are not included --- .../flutter/lib/src/rendering/editable.dart | 50 ++--- .../flutter/lib/src/rendering/list_body.dart | 33 ++++ .../flutter/lib/src/rendering/paragraph.dart | 113 ++++------- .../flutter/lib/src/rendering/proxy_box.dart | 91 +++++---- .../lib/src/rendering/shifted_box.dart | 175 ++++++++++++------ .../lib/src/widgets/layout_builder.dart | 15 +- .../flutter/lib/src/widgets/overflow_bar.dart | 38 ++++ .../flutter/lib/src/widgets/widget_span.dart | 6 + .../custom_single_child_layout_test.dart | 12 +- 9 files changed, 321 insertions(+), 212 deletions(-) diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index becab81f7a..41a13c6f38 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -4,7 +4,7 @@ import 'dart:collection'; import 'dart:math' as math; -import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle, LineMetrics, PlaceholderAlignment, TextBox; +import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle, LineMetrics, TextBox; import 'package:characters/characters.dart'; import 'package:flutter/foundation.dart'; @@ -777,7 +777,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, _textPainter.text = value; _cachedAttributedValue = null; _cachedCombinedSemanticsInfos = null; - _canComputeIntrinsicsCached = null; markNeedsLayout(); markNeedsSemanticsUpdate(); } @@ -1828,10 +1827,11 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, @override double computeMinIntrinsicWidth(double height) { - if (!_canComputeIntrinsics) { - return 0.0; - } - final List placeholderDimensions = layoutInlineChildren(double.infinity, (RenderBox child, BoxConstraints constraints) => Size(child.getMinIntrinsicWidth(double.infinity), 0.0)); + final List placeholderDimensions = layoutInlineChildren( + double.infinity, + (RenderBox child, BoxConstraints constraints) => Size(child.getMinIntrinsicWidth(double.infinity), 0.0), + ChildLayoutHelper.getDryBaseline, + ); final (double minWidth, double maxWidth) = _adjustConstraints(); return (_textIntrinsics ..setPlaceholderDimensions(placeholderDimensions) @@ -1841,14 +1841,12 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, @override double computeMaxIntrinsicWidth(double height) { - if (!_canComputeIntrinsics) { - return 0.0; - } final List placeholderDimensions = layoutInlineChildren( double.infinity, // Height and baseline is irrelevant as all text will be laid // out in a single line. Therefore, using 0.0 as a dummy for the height. (RenderBox child, BoxConstraints constraints) => Size(child.getMaxIntrinsicWidth(double.infinity), 0.0), + ChildLayoutHelper.getDryBaseline, ); final (double minWidth, double maxWidth) = _adjustConstraints(); return (_textIntrinsics @@ -1926,10 +1924,9 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, @override double computeMaxIntrinsicHeight(double width) { - if (!_canComputeIntrinsics) { - return 0.0; - } - _textIntrinsics.setPlaceholderDimensions(layoutInlineChildren(width, ChildLayoutHelper.dryLayoutChild)); + _textIntrinsics.setPlaceholderDimensions( + layoutInlineChildren(width, ChildLayoutHelper.dryLayoutChild, ChildLayoutHelper.getDryBaseline), + ); return _preferredHeight(width); } @@ -2291,35 +2288,12 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ); } - bool _canComputeDryLayoutForInlineWidgets() { - return text?.visitChildren((InlineSpan span) { - return (span is! PlaceholderSpan) || switch (span.alignment) { - ui.PlaceholderAlignment.baseline || - ui.PlaceholderAlignment.aboveBaseline || - ui.PlaceholderAlignment.belowBaseline => false, - ui.PlaceholderAlignment.top || - ui.PlaceholderAlignment.middle || - ui.PlaceholderAlignment.bottom => true, - }; - }) ?? true; - } - - bool? _canComputeIntrinsicsCached; - bool get _canComputeIntrinsics => _canComputeIntrinsicsCached ??= _canComputeDryLayoutForInlineWidgets(); - @override @protected Size computeDryLayout(covariant BoxConstraints constraints) { - if (!_canComputeIntrinsics) { - assert(debugCannotComputeDryLayout( - reason: 'Dry layout not available for alignments that require baseline.', - )); - return Size.zero; - } - final (double minWidth, double maxWidth) = _adjustConstraints(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth); _textIntrinsics - ..setPlaceholderDimensions(layoutInlineChildren(constraints.maxWidth, ChildLayoutHelper.dryLayoutChild)) + ..setPlaceholderDimensions(layoutInlineChildren(constraints.maxWidth, ChildLayoutHelper.dryLayoutChild, ChildLayoutHelper.getDryBaseline)) ..layout(minWidth: minWidth, maxWidth: maxWidth); final double width = forceLine ? constraints.maxWidth @@ -2330,7 +2304,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, @override void performLayout() { final BoxConstraints constraints = this.constraints; - _placeholderDimensions = layoutInlineChildren(constraints.maxWidth, ChildLayoutHelper.layoutChild); + _placeholderDimensions = layoutInlineChildren(constraints.maxWidth, ChildLayoutHelper.layoutChild, ChildLayoutHelper.getBaseline); final (double minWidth, double maxWidth) = _adjustConstraints(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth); _textPainter ..setPlaceholderDimensions(_placeholderDimensions) diff --git a/packages/flutter/lib/src/rendering/list_body.dart b/packages/flutter/lib/src/rendering/list_body.dart index e143e8c23a..753be8570c 100644 --- a/packages/flutter/lib/src/rendering/list_body.dart +++ b/packages/flutter/lib/src/rendering/list_body.dart @@ -63,6 +63,39 @@ class RenderListBody extends RenderBox /// [axisDirection]. Axis get mainAxis => axisDirectionToAxis(axisDirection); + @override + double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) { + assert(_debugCheckConstraints(constraints)); + RenderBox? child; + final RenderBox? Function(RenderBox) nextChild; + switch (axisDirection) { + case AxisDirection.right: + case AxisDirection.left: + final BoxConstraints childConstraints = BoxConstraints.tightFor(height: constraints.maxHeight); + BaselineOffset baselineOffset = BaselineOffset.noBaseline; + for (child = firstChild; child != null; child = childAfter(child)) { + baselineOffset = baselineOffset.minOf(BaselineOffset(child.getDryBaseline(childConstraints, baseline))); + } + return baselineOffset.offset; + case AxisDirection.up: + child = lastChild; + nextChild = childBefore; + case AxisDirection.down: + child = firstChild; + nextChild = childAfter; + } + final BoxConstraints childConstraints = BoxConstraints.tightFor(width: constraints.maxWidth); + double mainAxisExtent = 0.0; + for (; child != null; child = nextChild(child)) { + final double? childBaseline = child.getDryBaseline(childConstraints, baseline); + if (childBaseline != null) { + return childBaseline + mainAxisExtent; + } + mainAxisExtent += child.getDryLayout(childConstraints).height; + } + return null; + } + @override @protected Size computeDryLayout(covariant BoxConstraints constraints) { diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart index f91343b2d0..16233a28b2 100644 --- a/packages/flutter/lib/src/rendering/paragraph.dart +++ b/packages/flutter/lib/src/rendering/paragraph.dart @@ -125,14 +125,14 @@ mixin RenderInlineChildrenContainerDefaults on RenderBox, ContainerRenderObjectM } } - static PlaceholderDimensions _layoutChild(RenderBox child, double maxWidth, ChildLayouter layoutChild) { + static PlaceholderDimensions _layoutChild(RenderBox child, BoxConstraints childConstraints, ChildLayouter layoutChild, ChildBaselineGetter getBaseline) { final TextParentData parentData = child.parentData! as TextParentData; final PlaceholderSpan? span = parentData.span; assert(span != null); return span == null ? PlaceholderDimensions.empty : PlaceholderDimensions( - size: layoutChild(child, BoxConstraints(maxWidth: maxWidth)), + size: layoutChild(child, childConstraints), alignment: span.alignment, baseline: span.baseline, baselineOffset: switch (span.alignment) { @@ -141,17 +141,21 @@ mixin RenderInlineChildrenContainerDefaults on RenderBox, ContainerRenderObjectM ui.PlaceholderAlignment.bottom || ui.PlaceholderAlignment.middle || ui.PlaceholderAlignment.top => null, - ui.PlaceholderAlignment.baseline => child.getDistanceToBaseline(span.baseline!), + ui.PlaceholderAlignment.baseline => getBaseline(child, childConstraints, span.baseline!), }, ); } - /// Computes the layout for every inline child using the given `layoutChild` - /// function and the `maxWidth` constraint. + /// Computes the layout for every inline child using the `maxWidth` constraint. /// /// Returns a list of [PlaceholderDimensions], representing the layout results /// for each child managed by the [ContainerRenderObjectMixin] mixin. /// + /// The `getChildBaseline` parameter and the `layoutChild` parameter must be + /// consistent: if `layoutChild` computes the size of the child without + /// modifying the actual layout of that child, then `getChildBaseline` must + /// also be "dry", and vice versa. + /// /// Since this method does not impose a maximum height constraint on the /// inline children, some children may become taller than this [RenderBox]. /// @@ -160,10 +164,11 @@ mixin RenderInlineChildrenContainerDefaults on RenderBox, ContainerRenderObjectM /// * [TextPainter.setPlaceholderDimensions], the method that usually takes /// the layout results from this method as the input. @protected - List layoutInlineChildren(double maxWidth, ChildLayouter layoutChild) { + List layoutInlineChildren(double maxWidth, ChildLayouter layoutChild, ChildBaselineGetter getChildBaseline) { + final BoxConstraints constraints = BoxConstraints(maxWidth: maxWidth); return [ for (RenderBox? child = firstChild; child != null; child = childAfter(child)) - _layoutChild(child, maxWidth, layoutChild), + _layoutChild(child, constraints, layoutChild, getChildBaseline), ]; } @@ -352,7 +357,6 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin placeholderDimensions = layoutInlineChildren( double.infinity, (RenderBox child, BoxConstraints constraints) => Size(child.getMinIntrinsicWidth(double.infinity), 0.0), + ChildLayoutHelper.getDryBaseline, ); return (_textIntrinsics..setPlaceholderDimensions(placeholderDimensions)..layout()) .minIntrinsicWidth; @@ -684,25 +685,20 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin placeholderDimensions = layoutInlineChildren( double.infinity, // Height and baseline is irrelevant as all text will be laid // out in a single line. Therefore, using 0.0 as a dummy for the height. (RenderBox child, BoxConstraints constraints) => Size(child.getMaxIntrinsicWidth(double.infinity), 0.0), + ChildLayoutHelper.getDryBaseline, ); return (_textIntrinsics..setPlaceholderDimensions(placeholderDimensions)..layout()) .maxIntrinsicWidth; } double _computeIntrinsicHeight(double width) { - if (!_canComputeIntrinsics()) { - return 0.0; - } return (_textIntrinsics - ..setPlaceholderDimensions(layoutInlineChildren(width, ChildLayoutHelper.dryLayoutChild)) + ..setPlaceholderDimensions(layoutInlineChildren(width, ChildLayoutHelper.dryLayoutChild, ChildLayoutHelper.getDryBaseline)) ..layout(minWidth: width, maxWidth: _adjustMaxWidth(width))) .height; } @@ -717,52 +713,6 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin false, - ui.PlaceholderAlignment.top || - ui.PlaceholderAlignment.middle || - ui.PlaceholderAlignment.bottom => true, - }; - }); - } - - bool? _canComputeIntrinsicsCached; - // Intrinsics cannot be calculated without a full layout for - // alignments that require the baseline (baseline, aboveBaseline, - // belowBaseline). - bool _canComputeIntrinsics() { - final bool returnValue = _canComputeIntrinsicsCached ??= _canComputeDryLayoutForInlineWidgets(); - assert( - returnValue || RenderObject.debugCheckingIntrinsics, - 'Intrinsics are not available for PlaceholderAlignment.baseline, ' - 'PlaceholderAlignment.aboveBaseline, or PlaceholderAlignment.belowBaseline.', - ); - return returnValue; - } - @override bool hitTestSelf(Offset position) => true; @@ -822,23 +772,40 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin on RenderBox, RenderObjectWithChi ?? super.computeDistanceToActualBaseline(baseline); } + @override + @protected + double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) { + return child?.getDryBaseline(constraints, baseline); + } + @override @protected Size computeDryLayout(covariant BoxConstraints constraints) { @@ -292,11 +298,8 @@ class RenderConstrainedBox extends RenderProxyBox { @override @protected Size computeDryLayout(covariant BoxConstraints constraints) { - if (child != null) { - return child!.getDryLayout(_additionalConstraints.enforce(constraints)); - } else { - return _additionalConstraints.enforce(constraints).constrain(Size.zero); - } + return child?.getDryLayout(_additionalConstraints.enforce(constraints)) + ?? _additionalConstraints.enforce(constraints).constrain(Size.zero); } @override @@ -566,6 +569,11 @@ class RenderAspectRatio extends RenderProxyBox { return _applyAspectRatio(constraints); } + @override + double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) { + return super.computeDryBaseline(BoxConstraints.tight(getDryLayout(constraints)), baseline); + } + @override void performLayout() { size = getDryLayout(constraints); @@ -702,22 +710,16 @@ class RenderIntrinsicWidth extends RenderProxyBox { return _applyStep(height, _stepHeight); } + BoxConstraints _childConstraints(RenderBox child, BoxConstraints constraints) { + return constraints.tighten( + width: constraints.hasTightWidth ? null : _applyStep(child.getMaxIntrinsicWidth(constraints.maxHeight), _stepWidth), + height: stepHeight == null ? null : _applyStep(child.getMaxIntrinsicHeight(constraints.maxWidth), _stepHeight), + ); + } + Size _computeSize({required ChildLayouter layoutChild, required BoxConstraints constraints}) { - if (child != null) { - if (!constraints.hasTightWidth) { - final double width = child!.getMaxIntrinsicWidth(constraints.maxHeight); - assert(width.isFinite); - constraints = constraints.tighten(width: _applyStep(width, _stepWidth)); - } - if (_stepHeight != null) { - final double height = child!.getMaxIntrinsicHeight(constraints.maxWidth); - assert(height.isFinite); - constraints = constraints.tighten(height: _applyStep(height, _stepHeight)); - } - return layoutChild(child!, constraints); - } else { - return constraints.smallest; - } + final RenderBox? child = this.child; + return child == null ? constraints.smallest : layoutChild(child, _childConstraints(child, constraints)); } @override @@ -729,6 +731,12 @@ class RenderIntrinsicWidth extends RenderProxyBox { ); } + @override + double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) { + final RenderBox? child = this.child; + return child?.getDryBaseline(_childConstraints(child, constraints), baseline); + } + @override void performLayout() { size = _computeSize( @@ -808,17 +816,15 @@ class RenderIntrinsicHeight extends RenderProxyBox { return getMaxIntrinsicHeight(width); } + BoxConstraints _childConstraints(RenderBox child, BoxConstraints constraints) { + return constraints.hasTightHeight + ? constraints + : constraints.tighten(height: child.getMaxIntrinsicHeight(constraints.maxWidth)); + } + Size _computeSize({required ChildLayouter layoutChild, required BoxConstraints constraints}) { - if (child != null) { - if (!constraints.hasTightHeight) { - final double height = child!.getMaxIntrinsicHeight(constraints.maxWidth); - assert(height.isFinite); - constraints = constraints.tighten(height: height); - } - return layoutChild(child!, constraints); - } else { - return constraints.smallest; - } + final RenderBox? child = this.child; + return child == null ? constraints.smallest : layoutChild(child, _childConstraints(child, constraints)); } @override @@ -830,6 +836,12 @@ class RenderIntrinsicHeight extends RenderProxyBox { ); } + @override + double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) { + final RenderBox? child = this.child; + return child?.getDryBaseline(_childConstraints(child, constraints), baseline); + } + @override void performLayout() { size = _computeSize( @@ -2596,15 +2608,9 @@ class RenderFittedBox extends RenderProxyBox { _clipBehavior = clipBehavior, super(child); + Alignment _resolve() => _resolvedAlignment ??= alignment.resolve(textDirection); Alignment? _resolvedAlignment; - void _resolve() { - if (_resolvedAlignment != null) { - return; - } - _resolvedAlignment = alignment.resolve(textDirection); - } - void _markNeedResolution() { _resolvedAlignment = null; markNeedsPaint(); @@ -2772,13 +2778,13 @@ class RenderFittedBox extends RenderProxyBox { _hasVisualOverflow = false; _transform = Matrix4.identity(); } else { - _resolve(); + final Alignment resolvedAlignment = _resolve(); final Size childSize = child!.size; final FittedSizes sizes = applyBoxFit(_fit, childSize, size); final double scaleX = sizes.destination.width / sizes.source.width; final double scaleY = sizes.destination.height / sizes.source.height; - final Rect sourceRect = _resolvedAlignment!.inscribe(sizes.source, Offset.zero & childSize); - final Rect destinationRect = _resolvedAlignment!.inscribe(sizes.destination, Offset.zero & size); + final Rect sourceRect = resolvedAlignment.inscribe(sizes.source, Offset.zero & childSize); + final Rect destinationRect = resolvedAlignment.inscribe(sizes.destination, Offset.zero & size); _hasVisualOverflow = sourceRect.width < childSize.width || sourceRect.height < childSize.height; assert(scaleX.isFinite && scaleY.isFinite); _transform = Matrix4.translationValues(destinationRect.left, destinationRect.top, 0.0) @@ -3699,6 +3705,11 @@ class RenderOffstage extends RenderProxyBox { @override bool get sizedByParent => offstage; + @override + double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) { + return offstage ? null : super.computeDryBaseline(constraints, baseline); + } + @override @protected Size computeDryLayout(covariant BoxConstraints constraints) { diff --git a/packages/flutter/lib/src/rendering/shifted_box.dart b/packages/flutter/lib/src/rendering/shifted_box.dart index 659eb17ab1..4b8ec8b5c0 100644 --- a/packages/flutter/lib/src/rendering/shifted_box.dart +++ b/packages/flutter/lib/src/rendering/shifted_box.dart @@ -114,18 +114,15 @@ class RenderPadding extends RenderShiftedBox { _padding = padding, super(child); - EdgeInsets? _resolvedPadding; - - void _resolve() { - if (_resolvedPadding != null) { - return; - } - _resolvedPadding = padding.resolve(textDirection); - assert(_resolvedPadding!.isNonNegative); + EdgeInsets? _resolvedPaddingCache; + EdgeInsets get _resolvedPadding { + final EdgeInsets returnValue = _resolvedPaddingCache ??= padding.resolve(textDirection); + assert(returnValue.isNonNegative); + return returnValue; } void _markNeedResolution() { - _resolvedPadding = null; + _resolvedPaddingCache = null; markNeedsLayout(); } @@ -160,90 +157,86 @@ class RenderPadding extends RenderShiftedBox { @override double computeMinIntrinsicWidth(double height) { - _resolve(); - final double totalHorizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right; - final double totalVerticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom; + final EdgeInsets padding = _resolvedPadding; if (child != null) { // Relies on double.infinity absorption. - return child!.getMinIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding; + return child!.getMinIntrinsicWidth(math.max(0.0, height - padding.vertical)) + padding.horizontal; } - return totalHorizontalPadding; + return padding.horizontal; } @override double computeMaxIntrinsicWidth(double height) { - _resolve(); - final double totalHorizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right; - final double totalVerticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom; + final EdgeInsets padding = _resolvedPadding; if (child != null) { // Relies on double.infinity absorption. - return child!.getMaxIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding; + return child!.getMaxIntrinsicWidth(math.max(0.0, height - padding.vertical)) + padding.horizontal; } - return totalHorizontalPadding; + return padding.horizontal; } @override double computeMinIntrinsicHeight(double width) { - _resolve(); - final double totalHorizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right; - final double totalVerticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom; + final EdgeInsets padding = _resolvedPadding; if (child != null) { // Relies on double.infinity absorption. - return child!.getMinIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding; + return child!.getMinIntrinsicHeight(math.max(0.0, width - padding.horizontal)) + padding.vertical; } - return totalVerticalPadding; + return padding.vertical; } @override double computeMaxIntrinsicHeight(double width) { - _resolve(); - final double totalHorizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right; - final double totalVerticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom; + final EdgeInsets padding = _resolvedPadding; if (child != null) { // Relies on double.infinity absorption. - return child!.getMaxIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding; + return child!.getMaxIntrinsicHeight(math.max(0.0, width - padding.horizontal)) + padding.vertical; } - return totalVerticalPadding; + return padding.vertical; } @override @protected Size computeDryLayout(covariant BoxConstraints constraints) { - _resolve(); - assert(_resolvedPadding != null); + final EdgeInsets padding = _resolvedPadding; if (child == null) { - return constraints.constrain(Size( - _resolvedPadding!.left + _resolvedPadding!.right, - _resolvedPadding!.top + _resolvedPadding!.bottom, - )); + return constraints.constrain(Size(padding.horizontal, padding.vertical)); } - final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding!); + final BoxConstraints innerConstraints = constraints.deflate(padding); final Size childSize = child!.getDryLayout(innerConstraints); return constraints.constrain(Size( - _resolvedPadding!.left + childSize.width + _resolvedPadding!.right, - _resolvedPadding!.top + childSize.height + _resolvedPadding!.bottom, + padding.horizontal + childSize.width, + padding.vertical + childSize.height, )); } + @override + double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) { + final RenderBox? child = this.child; + if (child == null) { + return null; + } + final EdgeInsets padding = _resolvedPadding; + final BoxConstraints innerConstraints = constraints.deflate(padding); + final BaselineOffset result = BaselineOffset(child.getDryBaseline(innerConstraints, baseline)) + padding.top; + return result.offset; + } + @override void performLayout() { final BoxConstraints constraints = this.constraints; - _resolve(); - assert(_resolvedPadding != null); + final EdgeInsets padding = _resolvedPadding; if (child == null) { - size = constraints.constrain(Size( - _resolvedPadding!.left + _resolvedPadding!.right, - _resolvedPadding!.top + _resolvedPadding!.bottom, - )); + size = constraints.constrain(Size(padding.horizontal, padding.vertical)); return; } - final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding!); + final BoxConstraints innerConstraints = constraints.deflate(padding); child!.layout(innerConstraints, parentUsesSize: true); final BoxParentData childParentData = child!.parentData! as BoxParentData; - childParentData.offset = Offset(_resolvedPadding!.left, _resolvedPadding!.top); + childParentData.offset = Offset(padding.left, padding.top); size = constraints.constrain(Size( - _resolvedPadding!.left + child!.size.width + _resolvedPadding!.right, - _resolvedPadding!.top + child!.size.height + _resolvedPadding!.bottom, + padding.horizontal + child!.size.width, + padding.vertical + child!.size.height, )); } @@ -252,7 +245,7 @@ class RenderPadding extends RenderShiftedBox { super.debugPaintSize(context, offset); assert(() { final Rect outerRect = offset & size; - debugPaintPadding(context.canvas, outerRect, child != null ? _resolvedPadding!.deflateRect(outerRect) : null); + debugPaintPadding(context.canvas, outerRect, child != null ? _resolvedPaddingCache!.deflateRect(outerRect) : null); return true; }()); } @@ -690,6 +683,22 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox { }; } + @override + double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) { + final RenderBox? child = this.child; + if (child == null) { + return null; + } + final BoxConstraints childConstraints = _getInnerConstraints(constraints); + final double? result = child.getDryBaseline(childConstraints, baseline); + if (result == null) { + return null; + } + final Size childSize = child.getDryLayout(childConstraints); + final Size size = getDryLayout(constraints); + return result + resolvedAlignment.alongOffset(size - childSize as Offset).dy; + } + @override void performLayout() { if (child != null) { @@ -841,6 +850,22 @@ class RenderConstraintsTransformBox extends RenderAligningShiftedBox with DebugO return childSize == null ? constraints.smallest : constraints.constrain(childSize); } + @override + double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) { + final RenderBox? child = this.child; + if (child == null) { + return null; + } + final BoxConstraints childConstraints = constraintsTransform(constraints); + final double? result = child.getDryBaseline(childConstraints, baseline); + if (result == null) { + return null; + } + final Size childSize = child.getDryLayout(childConstraints); + final Size size = constraints.constrain(childSize); + return result + resolvedAlignment.alongOffset(size - childSize as Offset).dy; + } + Rect _overflowContainerRect = Rect.zero; Rect _overflowChildRect = Rect.zero; bool _isOverflowing = false; @@ -997,10 +1022,23 @@ class RenderSizedOverflowBox extends RenderAligningShiftedBox { @override double? computeDistanceToActualBaseline(TextBaseline baseline) { - if (child != null) { - return child!.getDistanceToActualBaseline(baseline); + return child?.getDistanceToActualBaseline(baseline) + ?? super.computeDistanceToActualBaseline(baseline); + } + + @override + double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) { + final RenderBox? child = this.child; + if (child == null) { + return null; } - return super.computeDistanceToActualBaseline(baseline); + final double? result = child.getDryBaseline(constraints, baseline); + if (result == null) { + return null; + } + final Size childSize = child.getDryLayout(constraints); + final Size size = getDryLayout(constraints); + return result + resolvedAlignment.alongOffset(size - childSize as Offset).dy; } @override @@ -1163,6 +1201,22 @@ class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox { return constraints.constrain(_getInnerConstraints(constraints).constrain(Size.zero)); } + @override + double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) { + final RenderBox? child = this.child; + if (child == null) { + return null; + } + final BoxConstraints childConstraints = _getInnerConstraints(constraints); + final double? result = child.getDryBaseline(childConstraints, baseline); + if (result == null) { + return null; + } + final Size childSize = child.getDryLayout(childConstraints); + final Size size = getDryLayout(constraints); + return result + resolvedAlignment.alongOffset(size - childSize as Offset).dy; + } + @override void performLayout() { if (child != null) { @@ -1359,6 +1413,23 @@ class RenderCustomSingleChildLayoutBox extends RenderShiftedBox { return _getSize(constraints); } + @override + double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) { + final RenderBox? child = this.child; + if (child == null) { + return null; + } + final BoxConstraints childConstraints = delegate.getConstraintsForChild(constraints); + final double? result = child.getDryBaseline(childConstraints, baseline); + if (result == null) { + return null; + } + return result + delegate.getPositionForChild( + _getSize(constraints), + childConstraints.isTight ? childConstraints.smallest : child.getDryLayout(childConstraints), + ).dy; + } + @override void performLayout() { size = _getSize(constraints); diff --git a/packages/flutter/lib/src/widgets/layout_builder.dart b/packages/flutter/lib/src/widgets/layout_builder.dart index df503e720c..766b4bede1 100644 --- a/packages/flutter/lib/src/widgets/layout_builder.dart +++ b/packages/flutter/lib/src/widgets/layout_builder.dart @@ -325,6 +325,15 @@ class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin (childAfter, firstChild), + VerticalDirection.up => (childBefore, lastChild), + }; + + double maxChildHeight = 0.0; + double y = 0.0; + double childrenWidth = 0.0; + BaselineOffset minHorizontalBaseline = BaselineOffset.noBaseline; + BaselineOffset verticalBaseline = BaselineOffset.noBaseline; + + for (RenderBox? child = startChild; child != null; child = next(child)) { + final Size childSize = child.getDryLayout(childConstraints); + final double heightDiff = childSize.height - maxChildHeight; + if (heightDiff > 0) { + minHorizontalBaseline += heightDiff / 2; + maxChildHeight = childSize.height; + } + + final BaselineOffset baselineOffset = BaselineOffset(child.getDryBaseline(childConstraints, baseline)); + if (baselineOffset != null) { + verticalBaseline ??= baselineOffset + y; + minHorizontalBaseline = minHorizontalBaseline.minOf(baselineOffset + (maxChildHeight - childSize.height)); + } + y += childSize.height + overflowSpacing; + childrenWidth += childSize.width; + } + + assert((verticalBaseline == null) == (minHorizontalBaseline == null)); + return childrenWidth + spacing * (childCount - 1) > constraints.maxWidth + ? verticalBaseline.offset + : minHorizontalBaseline.offset; + } + @override Size computeDryLayout(BoxConstraints constraints) { RenderBox? child = firstChild; diff --git a/packages/flutter/lib/src/widgets/widget_span.dart b/packages/flutter/lib/src/widgets/widget_span.dart index 5989f270d6..fddc0e3b13 100644 --- a/packages/flutter/lib/src/widgets/widget_span.dart +++ b/packages/flutter/lib/src/widgets/widget_span.dart @@ -379,6 +379,12 @@ class _RenderScaledInlineWidget extends RenderBox with RenderObjectWithChildMixi }; } + @override + double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) { + final double? distance = child?.getDryBaseline(BoxConstraints(maxWidth: constraints.maxWidth / scale), baseline); + return distance == null ? null : scale * distance; + } + @override Size computeDryLayout(BoxConstraints constraints) { assert(!constraints.hasBoundedHeight); diff --git a/packages/flutter/test/widgets/custom_single_child_layout_test.dart b/packages/flutter/test/widgets/custom_single_child_layout_test.dart index a276a32e83..660b7472ce 100644 --- a/packages/flutter/test/widgets/custom_single_child_layout_test.dart +++ b/packages/flutter/test/widgets/custom_single_child_layout_test.dart @@ -21,16 +21,18 @@ class TestSingleChildLayoutDelegate extends SingleChildLayoutDelegate { @override BoxConstraints getConstraintsForChild(BoxConstraints constraints) { - assert(!RenderObject.debugCheckingIntrinsics); - constraintsFromGetConstraintsForChild = constraints; + if (!RenderObject.debugCheckingIntrinsics) { + constraintsFromGetConstraintsForChild = constraints; + } return const BoxConstraints(minWidth: 100.0, maxWidth: 150.0, minHeight: 200.0, maxHeight: 400.0); } @override Offset getPositionForChild(Size size, Size childSize) { - assert(!RenderObject.debugCheckingIntrinsics); - sizeFromGetPositionForChild = size; - childSizeFromGetPositionForChild = childSize; + if (!RenderObject.debugCheckingIntrinsics) { + sizeFromGetPositionForChild = size; + childSizeFromGetPositionForChild = childSize; + } return Offset.zero; }