forked from firka/flutter
Implements RenderBox.computeDryBaseline for material render boxes (#146027)
`RenderChip` and `RenderInputDecorator` changes are larger so they are not included.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user