Implements RenderBox.computeDryBaseline for material render boxes (#146027)

`RenderChip` and `RenderInputDecorator` changes are larger so they are not included.
This commit is contained in:
LongCatIsLooong
2024-04-02 11:37:05 -07:00
committed by GitHub
parent 06106901d4
commit fd98a2f70b
11 changed files with 387 additions and 266 deletions

View File

@@ -2077,6 +2077,21 @@ class _RenderAppBarTitleBox extends RenderAligningShiftedBox {
return constraints.constrain(childSize);
}
@override
double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
final BoxConstraints innerConstraints = constraints.copyWith(maxHeight: double.infinity);
final RenderBox? child = this.child;
if (child == null) {
return null;
}
final double? result = child.getDryBaseline(innerConstraints, baseline);
if (result == null) {
return null;
}
final Size childSize = child.getDryLayout(innerConstraints);
return result + resolvedAlignment.alongOffset(getDryLayout(constraints) - childSize as Offset).dy;
}
@override
void performLayout() {
final BoxConstraints innerConstraints = constraints.copyWith(maxHeight: double.infinity);
@@ -2253,28 +2268,11 @@ class _RenderExpandedTitleBox extends RenderShiftedBox {
return child == null ? 0.0 : child.getMinIntrinsicWidth(double.infinity) + padding.horizontal;
}
Size _computeSize(BoxConstraints constraints, ChildLayouter layoutChild) {
final RenderBox? child = this.child;
if (child == null) {
return Size.zero;
}
layoutChild(child, constraints.widthConstraints().deflate(padding));
return constraints.biggest;
}
@override
Size computeDryLayout(BoxConstraints constraints) => _computeSize(constraints, ChildLayoutHelper.dryLayoutChild);
@override
void performLayout() {
final RenderBox? child = this.child;
if (child == null) {
this.size = constraints.smallest;
return;
}
final Size size = this.size = _computeSize(constraints, ChildLayoutHelper.layoutChild);
final Size childSize = child.size;
Size computeDryLayout(BoxConstraints constraints) => child == null ? Size.zero : constraints.biggest;
Offset _childOffsetFromSize(Size childSize, Size size) {
assert(child != null);
assert(padding.isNonNegative);
assert(titleAlignment.y == 1.0);
// yAdjustment is the minimum additional y offset to shift the child in
@@ -2284,11 +2282,34 @@ class _RenderExpandedTitleBox extends RenderShiftedBox {
// top padding is basically ignored since the expanded title is
// bottom-aligned).
final double yAdjustment = clampDouble(childSize.height + padding.bottom - maxExtent, 0, padding.bottom);
final double offsetY = size.height - childSize.height - padding.bottom + yAdjustment;
final double offsetX = (titleAlignment.x + 1) / 2 * (size.width - padding.horizontal - childSize.width) + padding.left;
final double offsetY = size.height - childSize.height - padding.bottom + yAdjustment;
return Offset(offsetX, offsetY);
}
@override
double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
final RenderBox? child = this.child;
if (child == null) {
return null;
}
final BoxConstraints childConstraints = constraints.widthConstraints().deflate(padding);
final BaselineOffset result = BaselineOffset(child.getDryBaseline(childConstraints, baseline))
+ _childOffsetFromSize(child.getDryLayout(childConstraints), getDryLayout(constraints)).dy;
return result.offset;
}
@override
void performLayout() {
final RenderBox? child = this.child;
if (child == null) {
size = constraints.smallest;
return;
}
size = constraints.biggest;
child.layout(constraints.widthConstraints().deflate(padding), parentUsesSize: true);
final BoxParentData childParentData = child.parentData! as BoxParentData;
childParentData.offset = Offset(offsetX, offsetY);
childParentData.offset = _childOffsetFromSize(child.size, size);
}
}

View File

@@ -615,6 +615,21 @@ class _RenderBottomSheetLayoutWithSizeListener 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 = _getConstraintsForChild(constraints);
final double? result = child.getDryBaseline(childConstraints, baseline);
if (result == null) {
return null;
}
final Size childSize = childConstraints.isTight ? childConstraints.smallest : child.getDryLayout(childConstraints);
return result + _getPositionForChild(_getSize(constraints), childSize).dy;
}
BoxConstraints _getConstraintsForChild(BoxConstraints constraints) {
return BoxConstraints(
minWidth: constraints.maxWidth,
@@ -632,18 +647,21 @@ class _RenderBottomSheetLayoutWithSizeListener extends RenderShiftedBox {
@override
void performLayout() {
size = _getSize(constraints);
if (child != null) {
final BoxConstraints childConstraints = _getConstraintsForChild(constraints);
assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true));
child!.layout(childConstraints, parentUsesSize: !childConstraints.isTight);
final BoxParentData childParentData = child!.parentData! as BoxParentData;
childParentData.offset = _getPositionForChild(size, childConstraints.isTight ? childConstraints.smallest : child!.size);
final Size childSize = childConstraints.isTight ? childConstraints.smallest : child!.size;
final RenderBox? child = this.child;
if (child == null) {
return;
}
if (_lastSize != childSize) {
_lastSize = childSize;
_onChildSizeChanged.call(_lastSize);
}
final BoxConstraints childConstraints = _getConstraintsForChild(constraints);
assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true));
child.layout(childConstraints, parentUsesSize: !childConstraints.isTight);
final BoxParentData childParentData = child.parentData! as BoxParentData;
final Size childSize = childConstraints.isTight ? childConstraints.smallest : child.size;
childParentData.offset = _getPositionForChild(size, childSize);
if (_lastSize != childSize) {
_lastSize = childSize;
_onChildSizeChanged.call(_lastSize);
}
}
}

View File

@@ -515,6 +515,20 @@ class _RenderInputPadding extends RenderShiftedBox {
);
}
@override
double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
final RenderBox? child = this.child;
if (child == null) {
return null;
}
final double? result = child.getDryBaseline(constraints, baseline);
if (result == null) {
return null;
}
final Size childSize = child.getDryLayout(constraints);
return result + Alignment.center.alongOffset(getDryLayout(constraints) - childSize as Offset).dy;
}
@override
void performLayout() {
size = _computeSize(

View File

@@ -604,6 +604,20 @@ class _RenderInputPadding extends RenderShiftedBox {
);
}
@override
double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
final RenderBox? child = this.child;
if (child == null) {
return null;
}
final double? result = child.getDryBaseline(constraints, baseline);
if (result == null) {
return null;
}
final Size childSize = child.getDryLayout(constraints);
return result + Alignment.center.alongOffset(getDryLayout(constraints) - childSize as Offset).dy;
}
@override
void performLayout() {
size = _computeSize(

View File

@@ -1658,9 +1658,9 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip
markNeedsLayout();
}
TextDirection? get textDirection => _textDirection;
TextDirection? _textDirection;
set textDirection(TextDirection? value) {
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
if (_textDirection == value) {
return;
}
@@ -1855,7 +1855,7 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip
tapPosition: position,
chipSize: size,
deleteButtonSize: deleteIcon!.size,
textDirection: textDirection!,
textDirection: textDirection,
);
final RenderBox? hitTestChild = hitIsOnDeleteIcon
? (deleteIcon ?? label ?? avatar)
@@ -1932,7 +1932,7 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip
Offset centerLayout(Size boxSize, double x) {
assert(sizes.content >= boxSize.height);
switch (textDirection!) {
switch (textDirection) {
case TextDirection.rtl:
x -= boxSize.width;
case TextDirection.ltr:
@@ -1947,7 +1947,7 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip
Offset avatarOffset = Offset.zero;
Offset labelOffset = Offset.zero;
Offset deleteIconOffset = Offset.zero;
switch (textDirection!) {
switch (textDirection) {
case TextDirection.rtl:
double start = right;
if (theme.showCheckmark || theme.showAvatar) {

View File

@@ -25,6 +25,9 @@ import 'theme_data.dart';
// Examples can assume:
// int _act = 1;
typedef _Sizes = ({ double titleY, BoxConstraints textConstraints, Size tileSize });
typedef _PositionChild = void Function(RenderBox child, Offset offset);
/// Defines the title font used for [ListTile] descendants of a [ListTileTheme].
///
/// List tiles that appear in a [Drawer] use the theme's [TextTheme.bodyLarge]
@@ -85,9 +88,12 @@ enum ListTileTitleAlignment {
threeLine,
/// The tops of the [ListTile.leading] and [ListTile.trailing] widgets are
/// placed 16 units below the top of the [ListTile.title]
/// if the titles' overall height is greater than 72, otherwise they're
/// centered relative to the [ListTile.title] and [ListTile.subtitle] widgets.
/// placed 16 pixels below the top of the [ListTile.title] widget,
/// if the [ListTile]'s overall height is greater than 72, otherwise the
/// [ListTile.trailing] widget is centered relative to the [ListTile.title] and
/// [ListTile.subtitle] widgets, and the [ListTile.leading] widget is 16 pixels
/// below the top of [ListTile.title], or center-aligned with [ListTile.title],
/// whichever makes the [ListTile.leading] closer to the top edge of [ListTile.title].
///
/// This is the default when [ThemeData.useMaterial3] is false.
titleHeight,
@@ -103,7 +109,31 @@ enum ListTileTitleAlignment {
/// The bottoms of the [ListTile.leading] and [ListTile.trailing] widgets are
/// placed [ListTile.minVerticalPadding] above the bottom of the [ListTile]'s
/// titles.
bottom,
bottom;
// If isLeading is true the y offset is for the leading widget, otherwise it's
// for the trailing child.
double _yOffsetFor(double childHeight, double tileHeight, _RenderListTile listTile, bool isLeading) {
return switch (this) {
ListTileTitleAlignment.threeLine => listTile.isThreeLine
? ListTileTitleAlignment.top._yOffsetFor(childHeight, tileHeight, listTile, isLeading)
: ListTileTitleAlignment.center._yOffsetFor(childHeight, tileHeight, listTile, isLeading),
// This attempts to implement the redlines for the vertical position of the
// leading and trailing icons on the spec page:
// https://m2.material.io/components/lists#specs
//
// For large tiles (> 72dp), both leading and trailing controls should be
// a fixed distance from top. As per guidelines this is set to 16dp.
ListTileTitleAlignment.titleHeight when tileHeight > 72.0 => 16.0,
// For smaller tiles, trailing should always be centered. Leading can be
// centered or closer to the top. It should never be further than 16dp
// to the top.
ListTileTitleAlignment.titleHeight => isLeading ? math.min((tileHeight - childHeight) / 2.0, 16.0) : (tileHeight - childHeight) / 2.0,
ListTileTitleAlignment.top => listTile.minVerticalPadding,
ListTileTitleAlignment.center => (tileHeight - childHeight) / 2.0,
ListTileTitleAlignment.bottom => tileHeight - childHeight - listTile.minVerticalPadding,
};
}
}
/// A single fixed-height row that typically contains some text as well as
@@ -1084,18 +1114,19 @@ class _RenderListTile extends RenderBox with SlottedContainerRenderObjectMixin<_
_titleAlignment = titleAlignment;
RenderBox? get leading => childForSlot(_ListTileSlot.leading);
RenderBox? get title => childForSlot(_ListTileSlot.title);
RenderBox get title => childForSlot(_ListTileSlot.title)!;
RenderBox? get subtitle => childForSlot(_ListTileSlot.subtitle);
RenderBox? get trailing => childForSlot(_ListTileSlot.trailing);
// The returned list is ordered for hit testing.
@override
Iterable<RenderBox> get children {
final RenderBox? title = childForSlot(_ListTileSlot.title);
return <RenderBox>[
if (leading != null)
leading!,
if (title != null)
title!,
title,
if (subtitle != null)
subtitle!,
if (trailing != null)
@@ -1248,26 +1279,23 @@ class _RenderListTile extends RenderBox with SlottedContainerRenderObjectMixin<_
+ _maxWidth(trailing, height);
}
// The target tile height to use if _minTileHeight is not specified.
double get _defaultTileHeight {
final bool hasSubtitle = subtitle != null;
final bool isTwoLine = !isThreeLine && hasSubtitle;
final bool isOneLine = !isThreeLine && !hasSubtitle;
final Offset baseDensity = visualDensity.baseSizeAdjustment;
if (isOneLine) {
return (isDense ? 48.0 : 56.0) + baseDensity.dy;
}
if (isTwoLine) {
return (isDense ? 64.0 : 72.0) + baseDensity.dy;
}
return (isDense ? 76.0 : 88.0) + baseDensity.dy;
final Offset baseDensity = visualDensity.baseSizeAdjustment;
return baseDensity.dy + switch ((isThreeLine, subtitle != null)) {
(true, _) => isDense ? 76.0 : 88.0, // 3 lines,
(false, true) => isDense ? 64.0 : 72.0, // 2 lines
(false, false) => isDense ? 48.0 : 56.0, // 1 line,
};
}
double get _targetTileHeight => _minTileHeight ?? _defaultTileHeight;
@override
double computeMinIntrinsicHeight(double width) {
return math.max(
minTileHeight ?? _defaultTileHeight,
title!.getMinIntrinsicHeight(width) + (subtitle?.getMinIntrinsicHeight(width) ?? 0.0),
_targetTileHeight,
title.getMinIntrinsicHeight(width) + (subtitle?.getMinIntrinsicHeight(width) ?? 0.0),
);
}
@@ -1278,187 +1306,166 @@ class _RenderListTile extends RenderBox with SlottedContainerRenderObjectMixin<_
@override
double? computeDistanceToActualBaseline(TextBaseline baseline) {
assert(title != null);
final BoxParentData parentData = title!.parentData! as BoxParentData;
final BaselineOffset offset = BaselineOffset(title!.getDistanceToActualBaseline(baseline))
final BoxParentData parentData = title.parentData! as BoxParentData;
final BaselineOffset offset = BaselineOffset(title.getDistanceToActualBaseline(baseline))
+ parentData.offset.dy;
return offset.offset;
}
static double? _boxBaseline(RenderBox box, TextBaseline baseline) {
return box.getDistanceToBaseline(baseline);
}
static Size _layoutBox(RenderBox? box, BoxConstraints constraints) {
if (box == null) {
return Size.zero;
}
box.layout(constraints, parentUsesSize: true);
return box.size;
}
BoxConstraints get maxIconHeightConstraint => BoxConstraints(
// One-line trailing and leading widget heights do not follow
// Material specifications, but this sizing is required to adhere
// to accessibility requirements for smallest tappable widget.
// Two- and three-line trailing widget heights are constrained
// properly according to the Material spec.
maxHeight: (isDense ? 48.0 : 56.0) + visualDensity.baseSizeAdjustment.dy,
);
static void _positionBox(RenderBox box, Offset offset) {
final BoxParentData parentData = box.parentData! as BoxParentData;
parentData.offset = offset;
}
@override
Size computeDryLayout(BoxConstraints constraints) {
assert(debugCannotComputeDryLayout(
reason: 'Layout requires baseline metrics, which are only available after a full layout.',
));
return Size.zero;
}
// Implements _RenderListTile's layout algorithm. If `positionChild` is not null,
// it will be called on each child with that child's layout offset.
//
// All of the dimensions below were taken from the Material Design spec:
// https://material.io/design/components/lists.html#specs
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
final bool hasLeading = leading != null;
final bool hasSubtitle = subtitle != null;
final bool hasTrailing = trailing != null;
final bool isTwoLine = !isThreeLine && hasSubtitle;
final bool isOneLine = !isThreeLine && !hasSubtitle;
final Offset densityAdjustment = visualDensity.baseSizeAdjustment;
final BoxConstraints maxIconHeightConstraint = BoxConstraints(
// One-line trailing and leading widget heights do not follow
// Material specifications, but this sizing is required to adhere
// to accessibility requirements for smallest tappable widget.
// Two- and three-line trailing widget heights are constrained
// properly according to the Material spec.
maxHeight: (isDense ? 48.0 : 56.0) + densityAdjustment.dy,
);
_Sizes _computeSizes(
ChildBaselineGetter getBaseline,
ChildLayouter getSize,
BoxConstraints constraints, {
_PositionChild? positionChild,
}) {
final BoxConstraints looseConstraints = constraints.loosen();
final BoxConstraints iconConstraints = looseConstraints.enforce(maxIconHeightConstraint);
final double tileWidth = looseConstraints.maxWidth;
final Size leadingSize = _layoutBox(leading, iconConstraints);
final Size trailingSize = _layoutBox(trailing, iconConstraints);
final BoxConstraints iconConstraints = looseConstraints.enforce(maxIconHeightConstraint);
final RenderBox? leading = this.leading;
final RenderBox? trailing = this.trailing;
final Size? leadingSize = leading == null ? null : getSize(leading, iconConstraints);
final Size? trailingSize = trailing == null ? null : getSize(trailing, iconConstraints);
assert(
tileWidth != leadingSize.width || tileWidth == 0.0,
tileWidth != leadingSize?.width || tileWidth == 0.0,
'Leading widget consumes entire tile width. Please use a sized widget, '
'or consider replacing ListTile with a custom widget '
'(see https://api.flutter.dev/flutter/material/ListTile-class.html#material.ListTile.4)',
);
assert(
tileWidth != trailingSize.width || tileWidth == 0.0,
tileWidth != trailingSize?.width || tileWidth == 0.0,
'Trailing widget consumes entire tile width. Please use a sized widget, '
'or consider replacing ListTile with a custom widget '
'(see https://api.flutter.dev/flutter/material/ListTile-class.html#material.ListTile.4)',
);
final double titleStart = hasLeading
? math.max(_minLeadingWidth, leadingSize.width) + _effectiveHorizontalTitleGap
: 0.0;
final double adjustedTrailingWidth = hasTrailing
? math.max(trailingSize.width + _effectiveHorizontalTitleGap, 32.0)
: 0.0;
final double titleStart = leadingSize == null
? 0.0
: math.max(_minLeadingWidth, leadingSize.width) + _effectiveHorizontalTitleGap;
final double adjustedTrailingWidth = trailingSize == null
? 0.0
: math.max(trailingSize.width + _effectiveHorizontalTitleGap, 32.0);
final BoxConstraints textConstraints = looseConstraints.tighten(
width: tileWidth - titleStart - adjustedTrailingWidth,
);
final Size titleSize = _layoutBox(title, textConstraints);
final Size subtitleSize = _layoutBox(subtitle, textConstraints);
double? titleBaseline;
double? subtitleBaseline;
if (isTwoLine) {
titleBaseline = isDense ? 28.0 : 32.0;
subtitleBaseline = isDense ? 48.0 : 52.0;
} else if (isThreeLine) {
titleBaseline = isDense ? 22.0 : 28.0;
subtitleBaseline = isDense ? 42.0 : 48.0;
} else {
assert(isOneLine);
}
final RenderBox? subtitle = this.subtitle;
final double titleHeight = getSize(title, textConstraints).height;
double tileHeight;
double titleY;
double? subtitleY;
if (!hasSubtitle) {
tileHeight = math.max(minTileHeight ?? _defaultTileHeight, titleSize.height + 2.0 * _minVerticalPadding);
titleY = (tileHeight - titleSize.height) / 2.0;
} else {
assert(subtitleBaselineType != null);
titleY = titleBaseline! - _boxBaseline(title!, titleBaselineType)!;
subtitleY = subtitleBaseline! - _boxBaseline(subtitle!, subtitleBaselineType!)! + visualDensity.vertical * 2.0;
tileHeight = minTileHeight ?? _defaultTileHeight;
// If the title and subtitle overlap, move the title upwards by half
// the overlap and the subtitle down by the same amount, and adjust
// tileHeight so that both titles fit.
final double titleOverlap = titleY + titleSize.height - subtitleY;
if (titleOverlap > 0.0) {
titleY -= titleOverlap / 2.0;
subtitleY += titleOverlap / 2.0;
}
// If the title or subtitle overflow tileHeight then punt: title
// and subtitle are arranged in a column, tileHeight = column height plus
// _minVerticalPadding on top and bottom.
if (titleY < _minVerticalPadding ||
(subtitleY + subtitleSize.height + _minVerticalPadding) > tileHeight) {
tileHeight = titleSize.height + subtitleSize.height + 2.0 * _minVerticalPadding;
titleY = _minVerticalPadding;
subtitleY = titleSize.height + _minVerticalPadding;
}
}
final double leadingDiff = tileHeight - leadingSize.height;
final double trailingDiff = tileHeight - trailingSize.height;
final (double leadingY, double trailingY) = switch (titleAlignment) {
ListTileTitleAlignment.threeLine when isThreeLine => (_minVerticalPadding, _minVerticalPadding),
ListTileTitleAlignment.threeLine => (leadingDiff / 2.0, trailingDiff / 2.0),
// This attempts to implement the redlines for the vertical position of the
// leading and trailing icons on the spec page:
// https://m2.material.io/components/lists#specs
//
// For large tiles (> 72dp), both leading and trailing controls should be
// a fixed distance from top. As per guidelines this is set to 16dp.
ListTileTitleAlignment.titleHeight when tileHeight > 72.0 => (16.0, 16.0),
// For smaller tiles, trailing should always be centered. Leading can be
// centered or closer to the top. It should never be further than 16dp
// to the top.
ListTileTitleAlignment.titleHeight => (math.min(leadingDiff / 2.0, 16.0), trailingDiff / 2.0),
ListTileTitleAlignment.top => (_minVerticalPadding, _minVerticalPadding),
ListTileTitleAlignment.center => (leadingDiff / 2.0, trailingDiff / 2.0),
ListTileTitleAlignment.bottom => (leadingDiff - _minVerticalPadding, trailingDiff - _minVerticalPadding),
final bool isLTR = switch (textDirection) {
TextDirection.ltr => true,
TextDirection.rtl => false,
};
switch (textDirection) {
case TextDirection.rtl: {
if (hasLeading) {
_positionBox(leading!, Offset(tileWidth - leadingSize.width, leadingY));
}
_positionBox(title!, Offset(adjustedTrailingWidth, titleY));
if (hasSubtitle) {
_positionBox(subtitle!, Offset(adjustedTrailingWidth, subtitleY!));
}
if (hasTrailing) {
_positionBox(trailing!, Offset(0.0, trailingY));
}
break;
final double titleY;
final double tileHeight;
if (subtitle == null) {
tileHeight = math.max(_targetTileHeight, titleHeight + 2.0 * _minVerticalPadding);
titleY = (tileHeight - titleHeight) / 2.0;
} else {
final double subtitleHeight = getSize(subtitle, textConstraints).height;
final double titleBaseline = getBaseline(title, textConstraints, titleBaselineType) ?? titleHeight;
final double subtitleBaseline = getBaseline(subtitle, textConstraints, subtitleBaselineType!) ?? subtitleHeight;
final double targetTitleY = (isThreeLine ? (isDense ? 22.0 : 28.0) : (isDense ? 28.0 : 32.0)) - titleBaseline;
final double targetSubtitleY = (isThreeLine ? (isDense ? 42.0 : 48.0) : (isDense ? 48.0 : 52.0)) + visualDensity.vertical * 2.0 - subtitleBaseline;
// Prevent the title and the subtitle from overlapping by moving them away from
// each other by the same distance.
final double halfOverlap = math.max(targetTitleY + titleHeight - targetSubtitleY, 0) / 2;
final double idealTitleY = targetTitleY - halfOverlap;
final double idealSubtitleY = targetSubtitleY + halfOverlap;
// However if either component can't maintain the minimal padding from the top/bottom edges, the ListTile enters "compat mode".
final bool compact = idealTitleY < minVerticalPadding || idealSubtitleY + subtitleHeight + minVerticalPadding > _targetTileHeight;
// Position subtitle.
positionChild?.call(subtitle, Offset(
isLTR ? titleStart : adjustedTrailingWidth,
compact ? minVerticalPadding + titleHeight : idealSubtitleY,
));
tileHeight = compact ? 2 * _minVerticalPadding + titleHeight + subtitleHeight : _targetTileHeight;
titleY = compact ? minVerticalPadding : idealTitleY;
}
if (positionChild != null) {
positionChild(title, Offset(
isLTR ? titleStart : adjustedTrailingWidth,
titleY,
));
if (leading != null && leadingSize != null) {
positionChild(leading, Offset(
isLTR ? 0.0 : tileWidth - leadingSize.width,
titleAlignment._yOffsetFor(leadingSize.height, tileHeight, this, true),
));
}
case TextDirection.ltr: {
if (hasLeading) {
_positionBox(leading!, Offset(0.0, leadingY));
}
_positionBox(title!, Offset(titleStart, titleY));
if (hasSubtitle) {
_positionBox(subtitle!, Offset(titleStart, subtitleY!));
}
if (hasTrailing) {
_positionBox(trailing!, Offset(tileWidth - trailingSize.width, trailingY));
}
break;
if (trailing != null && trailingSize != null) {
positionChild(trailing, Offset(
isLTR ? tileWidth - trailingSize.width : 0.0,
titleAlignment._yOffsetFor(trailingSize.height, tileHeight, this, false),
));
}
}
size = constraints.constrain(Size(tileWidth, tileHeight));
assert(size.width == constraints.constrainWidth(tileWidth));
assert(size.height == constraints.constrainHeight(tileHeight));
return (titleY: titleY, textConstraints: textConstraints, tileSize: Size(tileWidth, tileHeight));
}
@override
double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
final _Sizes sizes = _computeSizes(
ChildLayoutHelper.getDryBaseline,
ChildLayoutHelper.dryLayoutChild,
constraints,
);
final BaselineOffset titleBaseline = BaselineOffset(title.getDryBaseline(sizes.textConstraints, baseline)) + sizes.titleY;
return titleBaseline.offset;
}
@override
Size computeDryLayout(BoxConstraints constraints) {
return constraints.constrain(
_computeSizes(
ChildLayoutHelper.getDryBaseline,
ChildLayoutHelper.dryLayoutChild,
constraints,
).tileSize,
);
}
@override
void performLayout() {
final Size tileSize = _computeSizes(
ChildLayoutHelper.getBaseline,
ChildLayoutHelper.layoutChild,
constraints,
positionChild: _positionBox,
).tileSize;
size = constraints.constrain(tileSize);
assert(size.width == constraints.constrainWidth(tileSize.width));
assert(size.height == constraints.constrainHeight(tileSize.height));
}
@override

View File

@@ -159,6 +159,11 @@ class _RenderMenuItem extends RenderShiftedBox {
return child?.getDryLayout(constraints) ?? Size.zero;
}
@override
double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
return child?.getDryBaseline(constraints, baseline);
}
@override
void performLayout() {
if (child == null) {

View File

@@ -796,6 +796,18 @@ class _RenderSegmentedButton<T> extends RenderBox with
return _computeOverallSizeFromChildSize(childSize);
}
@override
double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
final Size childSize = _calculateChildSize(constraints);
final BoxConstraints childConstraints = BoxConstraints.tight(childSize);
BaselineOffset baselineOffset = BaselineOffset.noBaseline;
for (RenderBox? child = firstChild; child != null; child = childAfter(child)) {
baselineOffset = baselineOffset.minOf(BaselineOffset(child.getDryBaseline(childConstraints, baseline)));
}
return baselineOffset.offset;
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
@@ -851,9 +863,9 @@ class _RenderSegmentedButton<T> extends RenderBox with
context.canvas.restore();
// Compute a clip rect for the outer border of the child.
late final double segmentLeft;
late final double segmentRight;
late final double dividerPos;
final double segmentLeft;
final double segmentRight;
final double dividerPos;
final double borderOutset = math.max(enabledBorder.side.strokeOutset, disabledBorder.side.strokeOutset);
switch (textDirection) {
case TextDirection.rtl:

View File

@@ -821,6 +821,20 @@ class _RenderInputPadding extends RenderShiftedBox {
);
}
@override
double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
final RenderBox? child = this.child;
if (child == null) {
return null;
}
final double? result = child.getDryBaseline(constraints, baseline);
if (result == null) {
return null;
}
final Size childSize = child.getDryLayout(constraints);
return result + Alignment.center.alongOffset(getDryLayout(constraints) - childSize as Offset).dy;
}
@override
void performLayout() {
size = _computeSize(

View File

@@ -1159,19 +1159,19 @@ class _SelectToggleButtonRenderObject extends RenderShiftedBox {
}
static double _maxHeight(RenderBox? box, double width) {
return box == null ? 0.0 : box.getMaxIntrinsicHeight(width);
return box?.getMaxIntrinsicHeight(width) ?? 0.0;
}
static double _minHeight(RenderBox? box, double width) {
return box == null ? 0.0 : box.getMinIntrinsicHeight(width);
return box?.getMinIntrinsicHeight(width) ?? 0.0;
}
static double _minWidth(RenderBox? box, double height) {
return box == null ? 0.0 : box.getMinIntrinsicWidth(height);
return box?.getMinIntrinsicWidth(height) ?? 0.0;
}
static double _maxWidth(RenderBox? box, double height) {
return box == null ? 0.0 : box.getMaxIntrinsicWidth(height);
return box?.getMaxIntrinsicWidth(height) ?? 0.0;
}
@override
@@ -1220,6 +1220,42 @@ class _SelectToggleButtonRenderObject extends RenderShiftedBox {
);
}
EdgeInsetsDirectional get _childPadding {
assert(child != null);
// It does not matter what [textDirection] or [verticalDirection] is,
// since deflating the size constraints horizontally/vertically
// and the returned size accounts for the width of both sides.
return switch (direction) {
Axis.horizontal => EdgeInsetsDirectional.only(
start: leadingBorderSide.width,
end: trailingBorderSide.width,
top: borderSide.width,
bottom: borderSide.width,
),
Axis.vertical => EdgeInsetsDirectional.only(
start: borderSide.width,
end: borderSide.width,
top: leadingBorderSide.width,
bottom: trailingBorderSide.width,
),
};
}
@override
double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) {
final double? childBaseline = child?.getDryBaseline(constraints.deflate(_childPadding), baseline);
if (childBaseline == null) {
return null;
}
return childBaseline + switch (direction) {
Axis.horizontal => borderSide.width,
Axis.vertical => switch (verticalDirection) {
VerticalDirection.down => leadingBorderSide.width,
VerticalDirection.up => trailingBorderSide.width,
},
};
}
@override
void performLayout() {
size = _computeSize(
@@ -1244,53 +1280,18 @@ class _SelectToggleButtonRenderObject extends RenderShiftedBox {
}
Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
final RenderBox? child = this.child;
if (child == null) {
if (direction == Axis.horizontal) {
return constraints.constrain(Size(
leadingBorderSide.width + trailingBorderSide.width,
borderSide.width * 2.0,
));
} else {
return constraints.constrain(Size(
borderSide.width * 2.0,
leadingBorderSide.width + trailingBorderSide.width,
));
}
final Size horizontalSize = Size(leadingBorderSide.width + trailingBorderSide.width, borderSide.width * 2.0);
return switch (direction) {
Axis.horizontal => constraints.constrain(horizontalSize),
Axis.vertical => constraints.constrain(horizontalSize.flipped),
};
}
final double leftConstraint;
final double rightConstraint;
final double topConstraint;
final double bottomConstraint;
// It does not matter what [textDirection] or [verticalDirection] is,
// since deflating the size constraints horizontally/vertically
// and the returned size accounts for the width of both sides.
if (direction == Axis.horizontal) {
rightConstraint = trailingBorderSide.width;
leftConstraint = leadingBorderSide.width;
topConstraint = borderSide.width;
bottomConstraint = borderSide.width;
} else {
rightConstraint = borderSide.width;
leftConstraint = borderSide.width;
topConstraint = leadingBorderSide.width;
bottomConstraint = trailingBorderSide.width;
}
final BoxConstraints innerConstraints = constraints.deflate(
EdgeInsets.only(
left: leftConstraint,
top: topConstraint,
right: rightConstraint,
bottom: bottomConstraint,
),
);
final Size childSize = layoutChild(child!, innerConstraints);
return constraints.constrain(Size(
leftConstraint + childSize.width + rightConstraint,
topConstraint + childSize.height + bottomConstraint,
));
final EdgeInsetsDirectional childPadding = _childPadding;
final BoxConstraints innerConstraints = constraints.deflate(childPadding);
return constraints.constrain(childPadding.inflateSize(layoutChild(child, innerConstraints)));
}
@override
@@ -1621,6 +1622,20 @@ class _RenderInputPadding extends RenderShiftedBox {
);
}
@override
double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
final RenderBox? child = this.child;
if (child == null) {
return null;
}
final double? result = child.getDryBaseline(constraints, baseline);
if (result == null) {
return null;
}
final Size childSize = child.getDryLayout(constraints);
return result + Alignment.center.alongOffset(getDryLayout(constraints) - childSize as Offset).dy;
}
@override
void performLayout() {
size = _computeSize(

View File

@@ -54,8 +54,9 @@ abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixi
double? computeDistanceToActualBaseline(TextBaseline baseline) {
double? result;
final RenderBox? child = this.child;
assert(!debugNeedsLayout);
if (child != null) {
assert(!debugNeedsLayout);
assert(!child.debugNeedsLayout);
result = child.getDistanceToActualBaseline(baseline);
final BoxParentData childParentData = child.parentData! as BoxParentData;
if (result != null) {
@@ -279,15 +280,17 @@ abstract class RenderAligningShiftedBox extends RenderShiftedBox {
_textDirection = textDirection,
super(child);
/// The [Alignment] to use for aligning the child.
///
/// This is the [alignment] resolved against [textDirection]. Subclasses should
/// use [resolvedAlignment] instead of [alignment] directly, for computing the
/// child's offset.
///
/// The [performLayout] method will be called when the value changes.
@protected
Alignment get resolvedAlignment => _resolvedAlignment ??= alignment.resolve(textDirection);
Alignment? _resolvedAlignment;
void _resolve() {
if (_resolvedAlignment != null) {
return;
}
_resolvedAlignment = alignment.resolve(textDirection);
}
void _markNeedResolution() {
_resolvedAlignment = null;
markNeedsLayout();
@@ -340,14 +343,12 @@ abstract class RenderAligningShiftedBox extends RenderShiftedBox {
/// this object's own size has been set.
@protected
void alignChild() {
_resolve();
assert(child != null);
assert(!child!.debugNeedsLayout);
assert(child!.hasSize);
assert(hasSize);
assert(_resolvedAlignment != null);
final BoxParentData childParentData = child!.parentData! as BoxParentData;
childParentData.offset = _resolvedAlignment!.alongOffset(size - child!.size as Offset);
childParentData.offset = resolvedAlignment.alongOffset(size - child!.size as Offset);
}
@override