Prepare for RenderDecorator.computeBaseline changes. (#146363)
Minor changes to make the `RenderDecorator.computeBaseline` change a bit easier to make. No semantic changes.
diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart
index c5638e6..dca8bb2 100644
--- a/packages/flutter/lib/src/material/input_decorator.dart
+++ b/packages/flutter/lib/src/material/input_decorator.dart
@@ -30,6 +30,9 @@
const Curve _kTransitionCurve = Curves.fastOutSlowIn;
const double _kFinalLabelScale = 0.75;
+typedef _SubtextSize = ({ double ascent, double bottomHeight, double subtextHeight });
+typedef _ChildBaselineGetter = double Function(RenderBox child, BoxConstraints constraints);
+
// The default duration for hint fade in/out transitions.
//
// Animating hint is not mentioned in the Material specification.
@@ -614,7 +617,7 @@
this.container,
});
- final EdgeInsetsGeometry contentPadding;
+ final EdgeInsetsDirectional contentPadding;
final bool isCollapsed;
final double floatingLabelHeight;
final double floatingLabelProgress;
@@ -698,20 +701,16 @@
// all of the renderer children of a _RenderDecoration.
class _RenderDecorationLayout {
const _RenderDecorationLayout({
- required this.boxToBaseline,
- required this.inputBaseline, // for InputBorderType.underline
- required this.outlineBaseline, // for InputBorderType.outline
- required this.subtextBaseline,
+ required this.baseline,
required this.containerHeight,
- required this.subtextHeight,
+ required this.subtextSize,
+ required this.size,
});
- final Map<RenderBox?, double> boxToBaseline;
- final double inputBaseline;
- final double outlineBaseline;
- final double subtextBaseline; // helper/error counter
+ final double baseline;
final double containerHeight;
- final double subtextHeight;
+ final _SubtextSize? subtextSize;
+ final Size size;
}
// The workhorse: layout and paint a _Decorator widget's _Decoration.
@@ -742,13 +741,14 @@
RenderBox? get suffix => childForSlot(_DecorationSlot.suffix);
RenderBox? get prefixIcon => childForSlot(_DecorationSlot.prefixIcon);
RenderBox? get suffixIcon => childForSlot(_DecorationSlot.suffixIcon);
- RenderBox? get helperError => childForSlot(_DecorationSlot.helperError);
+ RenderBox get helperError => childForSlot(_DecorationSlot.helperError)!;
RenderBox? get counter => childForSlot(_DecorationSlot.counter);
RenderBox? get container => childForSlot(_DecorationSlot.container);
// The returned list is ordered for hit testing.
@override
Iterable<RenderBox> get children {
+ final RenderBox? helperError = childForSlot(_DecorationSlot.helperError);
return <RenderBox>[
if (icon != null)
icon!,
@@ -767,7 +767,7 @@
if (hint != null)
hint!,
if (helperError != null)
- helperError!,
+ helperError,
if (counter != null)
counter!,
if (container != null)
@@ -894,70 +894,62 @@
if (container != null) {
visitor(container!);
}
- if (helperError != null) {
- visitor(helperError!);
- }
+ visitor(helperError);
if (counter != null) {
visitor(counter!);
}
}
- @override
- bool get sizedByParent => false;
-
- static double _minWidth(RenderBox? box, double height) {
- return box == null ? 0.0 : box.getMinIntrinsicWidth(height);
+ static double _minWidth(RenderBox? box, double height) => box?.getMinIntrinsicWidth(height) ?? 0.0;
+ static double _maxWidth(RenderBox? box, double height) => box?.getMaxIntrinsicWidth(height) ?? 0.0 ;
+ static double _minHeight(RenderBox? box, double width) => box?.getMinIntrinsicHeight(width) ?? 0.0;
+ static Size _boxSize(RenderBox? box) => box?.size ?? Size.zero;
+ static double _getBaseline(RenderBox box, BoxConstraints boxConstraints) {
+ return ChildLayoutHelper.getBaseline(box, boxConstraints, TextBaseline.alphabetic) ?? box.size.height;
}
- static double _maxWidth(RenderBox? box, double height) {
- return box == null ? 0.0 : box.getMaxIntrinsicWidth(height);
- }
-
- static double _minHeight(RenderBox? box, double width) {
- return box == null ? 0.0 : box.getMinIntrinsicHeight(width);
- }
-
- static Size _boxSize(RenderBox? box) => box == null ? Size.zero : box.size;
-
static BoxParentData _boxParentData(RenderBox box) => box.parentData! as BoxParentData;
- EdgeInsets get contentPadding => decoration.contentPadding as EdgeInsets;
+ EdgeInsetsDirectional get contentPadding => decoration.contentPadding;
- // Lay out the given box if needed, and return its baseline.
- double _layoutLineBox(RenderBox? box, BoxConstraints constraints) {
- if (box == null) {
- return 0.0;
+ _SubtextSize? _computeSubtextSizes({
+ required BoxConstraints constraints,
+ required ChildLayouter layoutChild,
+ required _ChildBaselineGetter getBaseline,
+ }) {
+ final RenderBox? counter = this.counter;
+ Size counterSize;
+ final double counterAscent;
+ if (counter != null) {
+ counterSize = layoutChild(counter, constraints);
+ counterAscent = getBaseline(counter, constraints);
+ } else {
+ counterSize = Size.zero;
+ counterAscent = 0.0;
}
- box.layout(constraints, parentUsesSize: true);
- // Since internally, all layout is performed against the alphabetic baseline,
- // (eg, ascents/descents are all relative to alphabetic, even if the font is
- // an ideographic or hanging font), we should always obtain the reference
- // baseline from the alphabetic baseline. The ideographic baseline is for
- // use post-layout and is derived from the alphabetic baseline combined with
- // the font metrics.
- final double baseline = box.getDistanceToBaseline(TextBaseline.alphabetic)!;
- assert(() {
- if (baseline >= 0) {
- return true;
- }
- throw FlutterError.fromParts(<DiagnosticsNode>[
- ErrorSummary("One of InputDecorator's children reported a negative baseline offset."),
- ErrorDescription(
- '${box.runtimeType}, of size ${box.size}, reported a negative '
- 'alphabetic baseline of $baseline.',
- ),
- ]);
- }());
- return baseline;
+ final BoxConstraints helperErrorConstraints = constraints.deflate(EdgeInsets.only(left: counterSize.width));
+ final double helperErrorHeight = layoutChild(helperError, helperErrorConstraints).height;
+
+ if (helperErrorHeight == 0.0 && counterSize.height == 0.0) {
+ return null;
+ }
+
+ // TODO(LongCatIsLooong): the bottomHeight expression doesn't make much sense.
+ // Use the real descent and make sure the subtext line box is tall enough for both children.
+ // See https://github.com/flutter/flutter/issues/13715
+ final double ascent = math.max(counterAscent, getBaseline(helperError, helperErrorConstraints)) + subtextGap;
+ final double bottomHeight = math.max(counterAscent, helperErrorHeight) + subtextGap;
+ final double subtextHeight = math.max(counterSize.height, helperErrorHeight) + subtextGap;
+ return (ascent: ascent, bottomHeight: bottomHeight, subtextHeight: subtextHeight);
}
// Returns a value used by performLayout to position all of the renderers.
// This method applies layout to all of the renderers except the container.
// For convenience, the container is laid out in performLayout().
- _RenderDecorationLayout _layout(BoxConstraints layoutConstraints) {
+ _RenderDecorationLayout _layout(BoxConstraints constraints) {
assert(
- layoutConstraints.maxWidth < double.infinity,
+ constraints.maxWidth < double.infinity,
'An InputDecorator, which is typically created by a TextField, cannot '
'have an unbounded width.\n'
'This happens when the parent widget does not provide a finite width '
@@ -967,122 +959,82 @@
'TextField that contains it.',
);
- // Margin on each side of subtext (counter and helperError)
- final Map<RenderBox?, double> boxToBaseline = <RenderBox?, double>{};
- final BoxConstraints boxConstraints = layoutConstraints.loosen();
+ final BoxConstraints boxConstraints = constraints.loosen();
// Layout all the widgets used by InputDecorator
- boxToBaseline[icon] = _layoutLineBox(icon, boxConstraints);
- final BoxConstraints containerConstraints = boxConstraints.copyWith(
- maxWidth: boxConstraints.maxWidth - _boxSize(icon).width,
- );
- boxToBaseline[prefixIcon] = _layoutLineBox(prefixIcon, containerConstraints);
- boxToBaseline[suffixIcon] = _layoutLineBox(suffixIcon, containerConstraints);
- final BoxConstraints contentConstraints = containerConstraints.copyWith(
- maxWidth: math.max(0.0, containerConstraints.maxWidth - contentPadding.horizontal),
- );
- boxToBaseline[prefix] = _layoutLineBox(prefix, contentConstraints);
- boxToBaseline[suffix] = _layoutLineBox(suffix, contentConstraints);
-
- final double inputWidth = math.max(
- 0.0,
- constraints.maxWidth - (
- _boxSize(icon).width
- + (prefixIcon != null ? 0 : (textDirection == TextDirection.ltr ? contentPadding.left : contentPadding.right))
- + _boxSize(prefixIcon).width
- + _boxSize(prefix).width
- + _boxSize(suffix).width
- + _boxSize(suffixIcon).width
- + (suffixIcon != null ? 0 : (textDirection == TextDirection.ltr ? contentPadding.right : contentPadding.left))),
- );
- // Increase the available width for the label when it is scaled down.
- final double invertedLabelScale = lerpDouble(1.00, 1 / _kFinalLabelScale, decoration.floatingLabelProgress)!;
- double suffixIconWidth = _boxSize(suffixIcon).width;
- if (decoration.border.isOutline) {
- suffixIconWidth = lerpDouble(suffixIconWidth, 0.0, decoration.floatingLabelProgress)!;
- }
- final double labelWidth = math.max(
- 0.0,
- constraints.maxWidth - (
- _boxSize(icon).width
- + contentPadding.left
- + _boxSize(prefixIcon).width
- + suffixIconWidth
- + contentPadding.right),
- );
- boxToBaseline[label] = _layoutLineBox(
- label,
- boxConstraints.copyWith(maxWidth: labelWidth * invertedLabelScale),
- );
- boxToBaseline[hint] = _layoutLineBox(
- hint,
- boxConstraints.copyWith(minWidth: inputWidth, maxWidth: inputWidth),
- );
- boxToBaseline[counter] = _layoutLineBox(counter, contentConstraints);
+ final double iconWidth = (icon?..layout(boxConstraints, parentUsesSize: true))?.size.width ?? 0.0;
+ final BoxConstraints containerConstraints = boxConstraints.deflate(EdgeInsets.only(left: iconWidth));
+ final BoxConstraints contentConstraints = containerConstraints.deflate(EdgeInsets.only(left: contentPadding.horizontal));
// The helper or error text can occupy the full width less the space
// occupied by the icon and counter.
- boxToBaseline[helperError] = _layoutLineBox(
- helperError,
- contentConstraints.copyWith(
- maxWidth: math.max(0.0, contentConstraints.maxWidth - _boxSize(counter).width),
- ),
+ final _SubtextSize? subtextSize = _computeSubtextSizes(
+ constraints: contentConstraints,
+ layoutChild: ChildLayoutHelper.layoutChild,
+ getBaseline: _getBaseline,
);
+ final RenderBox? prefixIcon = this.prefixIcon;
+ final RenderBox? suffixIcon = this.suffixIcon;
+ final Size prefixIconSize = (prefixIcon?..layout(containerConstraints, parentUsesSize: true))?.size ?? Size.zero;
+ final Size suffixIconSize = (suffixIcon?..layout(containerConstraints, parentUsesSize: true))?.size ?? Size.zero;
+ final RenderBox? prefix = this.prefix;
+ final RenderBox? suffix = this.suffix;
+ final Size prefixSize = (prefix?..layout(contentConstraints, parentUsesSize: true))?.size ?? Size.zero;
+ final Size suffixSize = (suffix?..layout(contentConstraints, parentUsesSize: true))?.size ?? Size.zero;
+
+ final EdgeInsetsDirectional accessoryHorizontalInsets = EdgeInsetsDirectional.only(
+ start: iconWidth + prefixSize.width + (prefixIcon == null ? contentPadding.start : prefixIcon.size.width),
+ end: suffixSize.width + (suffixIcon == null ? contentPadding.end : suffixIcon.size.width),
+ );
+
+ final double inputWidth = math.max(0.0, constraints.maxWidth - accessoryHorizontalInsets.horizontal);
+ final RenderBox? label = this.label;
+ if (label != null) {
+ final double suffixIconSpace = decoration.border.isOutline
+ ? lerpDouble(suffixIconSize.width, 0.0, decoration.floatingLabelProgress)!
+ : suffixIconSize.width;
+ final double labelWidth = math.max(
+ 0.0,
+ constraints.maxWidth - (iconWidth + contentPadding.horizontal + prefixIconSize.width + suffixIconSpace),
+ );
+
+ // Increase the available width for the label when it is scaled down.
+ final double invertedLabelScale = lerpDouble(1.00, 1 / _kFinalLabelScale, decoration.floatingLabelProgress)!;
+ final BoxConstraints labelConstraints = boxConstraints.copyWith(maxWidth: labelWidth * invertedLabelScale);
+ label.layout(labelConstraints, parentUsesSize: true);
+ }
+
// The height of the input needs to accommodate label above and counter and
// helperError below, when they exist.
- final double labelHeight = label == null
- ? 0
- : decoration.floatingLabelHeight;
+ final double labelHeight = label == null ? 0 : decoration.floatingLabelHeight;
final double topHeight = decoration.border.isOutline
- ? math.max(labelHeight - boxToBaseline[label]!, 0)
+ ? math.max(labelHeight - (label?.getDistanceToBaseline(TextBaseline.alphabetic) ?? 0.0), 0.0)
: labelHeight;
- final double counterHeight = counter == null
- ? 0
- : boxToBaseline[counter]! + subtextGap;
- final bool helperErrorExists = helperError?.size != null
- && helperError!.size.height > 0;
- final double helperErrorHeight = !helperErrorExists
- ? 0
- : helperError!.size.height + subtextGap;
- final double bottomHeight = math.max(
- counterHeight,
- helperErrorHeight,
- );
+ final double bottomHeight = subtextSize?.bottomHeight ?? 0.0;
final Offset densityOffset = decoration.visualDensity.baseSizeAdjustment;
- boxToBaseline[input] = _layoutLineBox(
- input,
- boxConstraints.deflate(EdgeInsets.only(
- top: contentPadding.top + topHeight + densityOffset.dy / 2,
- bottom: contentPadding.bottom + bottomHeight + densityOffset.dy / 2,
- )).copyWith(
- minWidth: inputWidth,
- maxWidth: inputWidth,
- ),
- );
+ final BoxConstraints inputConstraints = boxConstraints
+ .deflate(EdgeInsets.only(top: contentPadding.vertical + topHeight + bottomHeight + densityOffset.dy))
+ .tighten(width: inputWidth);
+
+ final RenderBox? input = this.input;
+ final RenderBox? hint = this.hint;
+ final Size inputSize = (input?..layout(inputConstraints, parentUsesSize: true))?.size ?? Size.zero;
+ final Size hintSize = (hint?..layout(boxConstraints.tighten(width: inputWidth), parentUsesSize: true))?.size ?? Size.zero;
+ final double inputBaseline = input == null ? 0.0 : _getBaseline(input, inputConstraints);
+ final double hintBaseline = hint == null ? 0.0 : _getBaseline(hint, boxConstraints.tighten(width: inputWidth));
// The field can be occupied by a hint or by the input itself
- final double hintHeight = hint?.size.height ?? 0;
- final double inputDirectHeight = input?.size.height ?? 0;
- final double inputHeight = math.max(hintHeight, inputDirectHeight);
- final double inputInternalBaseline = math.max(
- boxToBaseline[input]!,
- boxToBaseline[hint]!,
- );
+ final double inputHeight = math.max(hintSize.height, inputSize.height);
+ final double inputInternalBaseline = math.max(inputBaseline, hintBaseline);
+ final double prefixBaseline = prefix == null ? 0.0 : _getBaseline(prefix, contentConstraints);
+ final double suffixBaseline = suffix == null ? 0.0 : _getBaseline(suffix, contentConstraints);
// Calculate the amount that prefix/suffix affects height above and below
// the input.
- final double prefixHeight = prefix?.size.height ?? 0;
- final double suffixHeight = suffix?.size.height ?? 0;
- final double fixHeight = math.max(
- boxToBaseline[prefix]!,
- boxToBaseline[suffix]!,
- );
+ final double fixHeight = math.max(prefixBaseline, suffixBaseline);
final double fixAboveInput = math.max(0, fixHeight - inputInternalBaseline);
- final double fixBelowBaseline = math.max(
- prefixHeight - boxToBaseline[prefix]!,
- suffixHeight - boxToBaseline[suffix]!,
- );
+ final double fixBelowBaseline = math.max(prefixSize.height - prefixBaseline, suffixSize.height - suffixBaseline);
// TODO(justinmc): fixBelowInput should have no effect when there is no
// prefix/suffix below the input.
// https://github.com/flutter/flutter/issues/66050
@@ -1092,9 +1044,7 @@
);
// Calculate the height of the input text container.
- final double prefixIconHeight = prefixIcon?.size.height ?? 0;
- final double suffixIconHeight = suffixIcon?.size.height ?? 0;
- final double fixIconHeight = math.max(prefixIconHeight, suffixIconHeight);
+ final double fixIconHeight = math.max(prefixIconSize.height, suffixIconSize.height);
final double contentHeight = math.max(
fixIconHeight,
topHeight
@@ -1141,62 +1091,40 @@
final double maxContentHeight = containerHeight - contentPadding.vertical - topHeight - densityOffset.dy;
final double alignableHeight = fixAboveInput + inputHeight + fixBelowInput;
final double maxVerticalOffset = maxContentHeight - alignableHeight;
- final double textAlignVerticalOffset = maxVerticalOffset * textAlignVerticalFactor;
- final double inputBaseline = topInputBaseline + textAlignVerticalOffset;
- // The three main alignments for the baseline when an outline is present are
- //
- // * top (-1.0): topmost point considering padding.
- // * center (0.0): the absolute center of the input ignoring padding but
- // accommodating the border and floating label.
- // * bottom (1.0): bottommost point considering padding.
- //
- // That means that if the padding is uneven, center is not the exact
- // midpoint of top and bottom. To account for this, the above center and
- // below center alignments are interpolated independently.
- final double outlineCenterBaseline = inputInternalBaseline
- + baselineAdjustment / 2.0
- + (containerHeight - inputHeight) / 2.0;
- final double outlineTopBaseline = topInputBaseline;
- final double outlineBottomBaseline = topInputBaseline + maxVerticalOffset;
- final double outlineBaseline = _interpolateThree(
- outlineTopBaseline,
- outlineCenterBaseline,
- outlineBottomBaseline,
- textAlignVertical,
- );
-
- // Find the positions of the text below the input when it exists.
- double subtextCounterBaseline = 0;
- double subtextHelperBaseline = 0;
- double subtextCounterHeight = 0;
- double subtextHelperHeight = 0;
- if (counter != null) {
- subtextCounterBaseline =
- containerHeight + subtextGap + boxToBaseline[counter]!;
- subtextCounterHeight = counter!.size.height + subtextGap;
+ final double baseline;
+ if (_isOutlineAligned) {
+ // The three main alignments for the baseline when an outline is present are
+ //
+ // * top (-1.0): topmost point considering padding.
+ // * center (0.0): the absolute center of the input ignoring padding but
+ // accommodating the border and floating label.
+ // * bottom (1.0): bottommost point considering padding.
+ //
+ // That means that if the padding is uneven, center is not the exact
+ // midpoint of top and bottom. To account for this, the above center and
+ // below center alignments are interpolated independently.
+ final double outlineCenterBaseline = inputInternalBaseline
+ + baselineAdjustment / 2.0
+ + (containerHeight - inputHeight) / 2.0;
+ final double outlineTopBaseline = topInputBaseline;
+ final double outlineBottomBaseline = topInputBaseline + maxVerticalOffset;
+ baseline = _interpolateThree(
+ outlineTopBaseline,
+ outlineCenterBaseline,
+ outlineBottomBaseline,
+ textAlignVertical,
+ );
+ } else {
+ final double textAlignVerticalOffset = maxVerticalOffset * textAlignVerticalFactor;
+ baseline = topInputBaseline + textAlignVerticalOffset;
}
- if (helperErrorExists) {
- subtextHelperBaseline =
- containerHeight + subtextGap + boxToBaseline[helperError]!;
- subtextHelperHeight = helperErrorHeight;
- }
- final double subtextBaseline = math.max(
- subtextCounterBaseline,
- subtextHelperBaseline,
- );
- final double subtextHeight = math.max(
- subtextCounterHeight,
- subtextHelperHeight,
- );
return _RenderDecorationLayout(
- boxToBaseline: boxToBaseline,
containerHeight: containerHeight,
- inputBaseline: inputBaseline,
- outlineBaseline: outlineBaseline,
- subtextBaseline: subtextBaseline,
- subtextHeight: subtextHeight,
+ baseline: baseline,
+ subtextSize: subtextSize,
+ size: Size(constraints.maxWidth, containerHeight + (subtextSize?.subtextHeight ?? 0.0)),
);
}
@@ -1207,50 +1135,37 @@
// alignment is greater than zero, it interpolates between the centered box's
// top and the position that would align the bottom of the box with the bottom
// padding.
- double _interpolateThree(double begin, double middle, double end, TextAlignVertical textAlignVertical) {
- if (textAlignVertical.y <= 0) {
- // It's possible for begin, middle, and end to not be in order because of
- // excessive padding. Those cases are handled by using middle.
- if (begin >= middle) {
- return middle;
- }
- // Do a standard linear interpolation on the first half, between begin and
- // middle.
- final double t = textAlignVertical.y + 1;
- return begin + (middle - begin) * t;
- }
-
- if (middle >= end) {
- return middle;
- }
- // Do a standard linear interpolation on the second half, between middle and
- // end.
- final double t = textAlignVertical.y;
- return middle + (end - middle) * t;
+ static double _interpolateThree(double begin, double middle, double end, TextAlignVertical textAlignVertical) {
+ // It's possible for begin, middle, and end to not be in order because of
+ // excessive padding. Those cases are handled by using middle.
+ final double basis = textAlignVertical.y <= 0
+ ? math.max(middle - begin, 0)
+ : math.max(end - middle, 0);
+ return middle + basis * textAlignVertical.y;
}
@override
double computeMinIntrinsicWidth(double height) {
return _minWidth(icon, height)
- + (prefixIcon != null ? 0.0 : (textDirection == TextDirection.ltr ? contentPadding.left : contentPadding.right))
+ + (prefixIcon != null ? 0.0 : contentPadding.start)
+ _minWidth(prefixIcon, height)
+ _minWidth(prefix, height)
+ math.max(_minWidth(input, height), _minWidth(hint, height))
+ _minWidth(suffix, height)
+ _minWidth(suffixIcon, height)
- + (suffixIcon != null ? 0.0 : (textDirection == TextDirection.ltr ? contentPadding.right : contentPadding.left));
+ + (suffixIcon != null ? 0.0 : contentPadding.end);
}
@override
double computeMaxIntrinsicWidth(double height) {
return _maxWidth(icon, height)
- + (prefixIcon != null ? 0.0 : (textDirection == TextDirection.ltr ? contentPadding.left : contentPadding.right))
+ + (prefixIcon != null ? 0.0 : contentPadding.start)
+ _maxWidth(prefixIcon, height)
+ _maxWidth(prefix, height)
+ math.max(_maxWidth(input, height), _maxWidth(hint, height))
+ _maxWidth(suffix, height)
+ _maxWidth(suffixIcon, height)
- + (suffixIcon != null ? 0.0 : (textDirection == TextDirection.ltr ? contentPadding.right : contentPadding.left));
+ + (suffixIcon != null ? 0.0 : contentPadding.end);
}
double _lineHeight(double width, List<RenderBox?> boxes) {
@@ -1282,6 +1197,8 @@
width = math.max(width - contentPadding.horizontal, 0.0);
+ // TODO(LongCatIsLooong): use _computeSubtextSizes for subtext intrinsic sizes.
+ // See https://github.com/flutter/flutter/issues/13715.
final double counterHeight = _minHeight(counter, width);
final double counterWidth = _minWidth(counter, counterHeight);
@@ -1312,6 +1229,7 @@
final double minContainerHeight = decoration.isDense! || expands
? 0.0
: kMinInteractiveDimension;
+
return math.max(containerHeight, minContainerHeight) + subtextHeight;
}
@@ -1339,43 +1257,16 @@
return Size.zero;
}
- ChildSemanticsConfigurationsResult _childSemanticsConfigurationDelegate(List<SemanticsConfiguration> childConfigs) {
- final ChildSemanticsConfigurationsResultBuilder builder = ChildSemanticsConfigurationsResultBuilder();
- List<SemanticsConfiguration>? prefixMergeGroup;
- List<SemanticsConfiguration>? suffixMergeGroup;
- for (final SemanticsConfiguration childConfig in childConfigs) {
- if (childConfig.tagsChildrenWith(_InputDecoratorState._kPrefixSemanticsTag)) {
- prefixMergeGroup ??= <SemanticsConfiguration>[];
- prefixMergeGroup.add(childConfig);
- } else if (childConfig.tagsChildrenWith(_InputDecoratorState._kSuffixSemanticsTag)) {
- suffixMergeGroup ??= <SemanticsConfiguration>[];
- suffixMergeGroup.add(childConfig);
- } else {
- builder.markAsMergeUp(childConfig);
- }
- }
- if (prefixMergeGroup != null) {
- builder.markAsSiblingMergeGroup(prefixMergeGroup);
- }
- if (suffixMergeGroup != null) {
- builder.markAsSiblingMergeGroup(suffixMergeGroup);
- }
- return builder.build();
- }
-
- @override
- void describeSemanticsConfiguration(SemanticsConfiguration config) {
- config.childConfigurationsDelegate = _childSemanticsConfigurationDelegate;
- }
-
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
_labelTransform = null;
final _RenderDecorationLayout layout = _layout(constraints);
+ size = constraints.constrain(layout.size);
+ assert(size.width == constraints.constrainWidth(layout.size.width));
+ assert(size.height == constraints.constrainHeight(layout.size.height));
- final double overallWidth = constraints.maxWidth;
- final double overallHeight = layout.containerHeight + layout.subtextHeight;
+ final double overallWidth = layout.size.width;
final RenderBox? container = this.container;
if (container != null) {
@@ -1391,24 +1282,12 @@
_boxParentData(container).offset = Offset(x, 0.0);
}
- late double height;
+ final double height = layout.containerHeight;
double centerLayout(RenderBox box, double x) {
_boxParentData(box).offset = Offset(x, (height - box.size.height) / 2.0);
return box.size.width;
}
- late double baseline;
- double baselineLayout(RenderBox box, double x) {
- _boxParentData(box).offset = Offset(x, baseline - layout.boxToBaseline[box]!);
- return box.size.width;
- }
-
- final double left = contentPadding.left;
- final double right = overallWidth - contentPadding.right;
-
- height = layout.containerHeight;
- baseline = _isOutlineAligned ? layout.outlineBaseline : layout.inputBaseline;
-
if (icon != null) {
final double x = switch (textDirection) {
TextDirection.rtl => overallWidth - icon!.size.width,
@@ -1417,12 +1296,39 @@
centerLayout(icon!, x);
}
+ final double subtextBaseline = (layout.subtextSize?.ascent ?? 0.0) + layout.containerHeight;
+ final RenderBox? counter = this.counter;
+ final double helperErrorBaseline = helperError.getDistanceToBaseline(TextBaseline.alphabetic)!;
+ final double counterBaseline = counter?.getDistanceToBaseline(TextBaseline.alphabetic)! ?? 0.0;
+
+ double start, end;
+ switch (textDirection) {
+ case TextDirection.ltr:
+ start = contentPadding.start + _boxSize(icon).width;
+ end = overallWidth - contentPadding.end;
+ _boxParentData(helperError).offset = Offset(start, subtextBaseline - helperErrorBaseline);
+ if (counter != null) {
+ _boxParentData(counter).offset = Offset(end - counter.size.width, subtextBaseline - counterBaseline);
+ }
+ case TextDirection.rtl:
+ start = overallWidth - contentPadding.start - _boxSize(icon).width;
+ end = contentPadding.end;
+ _boxParentData(helperError).offset = Offset(start - helperError.size.width, subtextBaseline - helperErrorBaseline);
+ if (counter != null) {
+ _boxParentData(counter).offset = Offset(end, subtextBaseline - counterBaseline);
+ }
+ }
+
+ final double baseline = layout.baseline;
+ double baselineLayout(RenderBox box, double x) {
+ _boxParentData(box).offset = Offset(x, baseline - box.getDistanceToBaseline(TextBaseline.alphabetic)!);
+ return box.size.width;
+ }
+
switch (textDirection) {
case TextDirection.rtl: {
- double start = right - _boxSize(icon).width;
- double end = left;
if (prefixIcon != null) {
- start += contentPadding.right;
+ start += contentPadding.start;
start -= centerLayout(prefixIcon!, start - prefixIcon!.size.width);
}
if (label != null) {
@@ -1442,7 +1348,7 @@
baselineLayout(hint!, start - hint!.size.width);
}
if (suffixIcon != null) {
- end -= contentPadding.left;
+ end -= contentPadding.end;
end += centerLayout(suffixIcon!, end);
}
if (suffix != null) {
@@ -1451,10 +1357,8 @@
break;
}
case TextDirection.ltr: {
- double start = left + _boxSize(icon).width;
- double end = right;
if (prefixIcon != null) {
- start -= contentPadding.left;
+ start -= contentPadding.start;
start += centerLayout(prefixIcon!, start);
}
if (label != null) {
@@ -1474,7 +1378,7 @@
baselineLayout(hint!, start);
}
if (suffixIcon != null) {
- end += contentPadding.right;
+ end += contentPadding.end;
end -= centerLayout(suffixIcon!, end - suffixIcon!.size.width);
}
if (suffix != null) {
@@ -1484,28 +1388,6 @@
}
}
- if (helperError != null || counter != null) {
- height = layout.subtextHeight;
- baseline = layout.subtextBaseline;
-
- switch (textDirection) {
- case TextDirection.rtl:
- if (helperError != null) {
- baselineLayout(helperError!, right - helperError!.size.width - _boxSize(icon).width);
- }
- if (counter != null) {
- baselineLayout(counter!, left);
- }
- case TextDirection.ltr:
- if (helperError != null) {
- baselineLayout(helperError!, left + _boxSize(icon).width);
- }
- if (counter != null) {
- baselineLayout(counter!, right - counter!.size.width);
- }
- }
- }
-
if (label != null) {
final double labelX = _boxParentData(label!).offset.dx;
// +1 shifts the range of x from (-1.0, 1.0) to (0.0, 2.0).
@@ -1517,7 +1399,7 @@
case TextDirection.rtl:
double offsetToPrefixIcon = 0.0;
if (prefixIcon != null && !decoration.alignLabelWithHint) {
- offsetToPrefixIcon = material3 ? _boxSize(prefixIcon).width - left : 0;
+ offsetToPrefixIcon = material3 ? _boxSize(prefixIcon).width - contentPadding.end : 0;
}
decoration.borderGap.start = lerpDouble(labelX + _boxSize(label).width + offsetToPrefixIcon,
_boxSize(container).width / 2.0 + floatWidth / 2.0,
@@ -1529,7 +1411,7 @@
// floating label is centered, it's already relative to _BorderContainer.
double offsetToPrefixIcon = 0.0;
if (prefixIcon != null && !decoration.alignLabelWithHint) {
- offsetToPrefixIcon = material3 ? (-_boxSize(prefixIcon).width + left) : 0;
+ offsetToPrefixIcon = material3 ? (-_boxSize(prefixIcon).width + contentPadding.start) : 0;
}
decoration.borderGap.start = lerpDouble(labelX - _boxSize(icon).width + offsetToPrefixIcon,
_boxSize(container).width / 2.0 - floatWidth / 2.0,
@@ -1540,10 +1422,6 @@
decoration.borderGap.start = null;
decoration.borderGap.extent = 0.0;
}
-
- size = constraints.constrain(Size(overallWidth, overallHeight));
- assert(size.width == constraints.constrainWidth(overallWidth));
- assert(size.height == constraints.constrainHeight(overallHeight));
}
void _paintLabel(PaintingContext context, Offset offset) {
@@ -1584,13 +1462,13 @@
startX = labelOffset.dx + labelWidth * (1.0 - scale);
floatStartX = startX;
if (prefixIcon != null && !decoration.alignLabelWithHint && isOutlineBorder) {
- floatStartX += material3 ? _boxSize(prefixIcon).width - contentPadding.left : 0.0;
+ floatStartX += material3 ? _boxSize(prefixIcon).width - contentPadding.end : 0.0;
}
case TextDirection.ltr: // origin on the left
startX = labelOffset.dx;
floatStartX = startX;
if (prefixIcon != null && !decoration.alignLabelWithHint && isOutlineBorder) {
- floatStartX += material3 ? -_boxSize(prefixIcon).width + contentPadding.left : 0.0;
+ floatStartX += material3 ? -_boxSize(prefixIcon).width + contentPadding.start : 0.0;
}
}
final double floatEndX = lerpDouble(floatStartX, centeredFloatX, floatAlign)!;
@@ -1622,6 +1500,17 @@
}
@override
+ void applyPaintTransform(RenderObject child, Matrix4 transform) {
+ if (child == label && _labelTransform != null) {
+ final Offset labelOffset = _boxParentData(label!).offset;
+ transform
+ ..multiply(_labelTransform!)
+ ..translate(-labelOffset.dx, -labelOffset.dy);
+ }
+ super.applyPaintTransform(child, transform);
+ }
+
+ @override
bool hitTestSelf(Offset position) => true;
@override
@@ -1644,15 +1533,33 @@
return false;
}
- @override
- void applyPaintTransform(RenderObject child, Matrix4 transform) {
- if (child == label && _labelTransform != null) {
- final Offset labelOffset = _boxParentData(label!).offset;
- transform
- ..multiply(_labelTransform!)
- ..translate(-labelOffset.dx, -labelOffset.dy);
+ ChildSemanticsConfigurationsResult _childSemanticsConfigurationDelegate(List<SemanticsConfiguration> childConfigs) {
+ final ChildSemanticsConfigurationsResultBuilder builder = ChildSemanticsConfigurationsResultBuilder();
+ List<SemanticsConfiguration>? prefixMergeGroup;
+ List<SemanticsConfiguration>? suffixMergeGroup;
+ for (final SemanticsConfiguration childConfig in childConfigs) {
+ if (childConfig.tagsChildrenWith(_InputDecoratorState._kPrefixSemanticsTag)) {
+ prefixMergeGroup ??= <SemanticsConfiguration>[];
+ prefixMergeGroup.add(childConfig);
+ } else if (childConfig.tagsChildrenWith(_InputDecoratorState._kSuffixSemanticsTag)) {
+ suffixMergeGroup ??= <SemanticsConfiguration>[];
+ suffixMergeGroup.add(childConfig);
+ } else {
+ builder.markAsMergeUp(childConfig);
+ }
}
- super.applyPaintTransform(child, transform);
+ if (prefixMergeGroup != null) {
+ builder.markAsSiblingMergeGroup(prefixMergeGroup);
+ }
+ if (suffixMergeGroup != null) {
+ builder.markAsSiblingMergeGroup(suffixMergeGroup);
+ }
+ return builder.build();
+ }
+
+ @override
+ void describeSemanticsConfiguration(SemanticsConfiguration config) {
+ config.childConfigurationsDelegate = _childSemanticsConfigurationDelegate;
}
}
@@ -2416,46 +2323,58 @@
// The _Decoration widget and _RenderDecoration assume that contentPadding
// has been resolved to EdgeInsets.
final TextDirection textDirection = Directionality.of(context);
- final EdgeInsets? decorationContentPadding = decoration.contentPadding?.resolve(textDirection);
+ final bool flipHorizontal = switch (textDirection) {
+ TextDirection.ltr => false,
+ TextDirection.rtl => true,
+ };
+ final EdgeInsets? resolvedPadding = decoration.contentPadding?.resolve(textDirection);
+ final EdgeInsetsDirectional? decorationContentPadding = resolvedPadding == null
+ ? null
+ : EdgeInsetsDirectional.fromSTEB(
+ flipHorizontal ? resolvedPadding.right : resolvedPadding.left,
+ resolvedPadding.top,
+ flipHorizontal ? resolvedPadding.left : resolvedPadding.right,
+ resolvedPadding.bottom,
+ );
- final EdgeInsets contentPadding;
+ final EdgeInsetsDirectional contentPadding;
final double floatingLabelHeight;
if (decoration.isCollapsed ?? themeData.inputDecorationTheme.isCollapsed) {
floatingLabelHeight = 0.0;
- contentPadding = decorationContentPadding ?? EdgeInsets.zero;
+ contentPadding = decorationContentPadding ?? EdgeInsetsDirectional.zero;
} else if (!border.isOutline) {
// 4.0: the vertical gap between the inline elements and the floating label.
floatingLabelHeight = MediaQuery.textScalerOf(context).scale(4.0 + 0.75 * labelStyle.fontSize!);
if (decoration.filled ?? false) {
contentPadding = decorationContentPadding ?? (Theme.of(context).useMaterial3
? decorationIsDense
- ? const EdgeInsets.fromLTRB(12.0, 4.0, 12.0, 4.0)
- : const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 8.0)
+ ? const EdgeInsetsDirectional.fromSTEB(12.0, 4.0, 12.0, 4.0)
+ : const EdgeInsetsDirectional.fromSTEB(12.0, 8.0, 12.0, 8.0)
: decorationIsDense
- ? const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 8.0)
- : const EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 12.0));
+ ? const EdgeInsetsDirectional.fromSTEB(12.0, 8.0, 12.0, 8.0)
+ : const EdgeInsetsDirectional.fromSTEB(12.0, 12.0, 12.0, 12.0));
} else {
// No left or right padding for underline borders that aren't filled
// is a small concession to backwards compatibility. This eliminates
// the most noticeable layout change introduced by #13734.
contentPadding = decorationContentPadding ?? (Theme.of(context).useMaterial3
? decorationIsDense
- ? const EdgeInsets.fromLTRB(0.0, 4.0, 0.0, 4.0)
- : const EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 8.0)
+ ? const EdgeInsetsDirectional.fromSTEB(0.0, 4.0, 0.0, 4.0)
+ : const EdgeInsetsDirectional.fromSTEB(0.0, 8.0, 0.0, 8.0)
: decorationIsDense
- ? const EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 8.0)
- : const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 12.0));
+ ? const EdgeInsetsDirectional.fromSTEB(0.0, 8.0, 0.0, 8.0)
+ : const EdgeInsetsDirectional.fromSTEB(0.0, 12.0, 0.0, 12.0));
}
} else {
floatingLabelHeight = 0.0;
contentPadding = decorationContentPadding ?? (Theme.of(context).useMaterial3
? decorationIsDense
- ? const EdgeInsets.fromLTRB(12.0, 16.0, 12.0, 8.0)
- : const EdgeInsets.fromLTRB(12.0, 20.0, 12.0, 12.0)
+ ? const EdgeInsetsDirectional.fromSTEB(12.0, 16.0, 12.0, 8.0)
+ : const EdgeInsetsDirectional.fromSTEB(12.0, 20.0, 12.0, 12.0)
: decorationIsDense
- ? const EdgeInsets.fromLTRB(12.0, 20.0, 12.0, 12.0)
- : const EdgeInsets.fromLTRB(12.0, 24.0, 12.0, 16.0));
+ ? const EdgeInsetsDirectional.fromSTEB(12.0, 20.0, 12.0, 12.0)
+ : const EdgeInsetsDirectional.fromSTEB(12.0, 24.0, 12.0, 16.0));
}
final _Decorator decorator = _Decorator(
diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart
index e2a6c7f..9e443d9 100644
--- a/packages/flutter/test/material/input_decorator_test.dart
+++ b/packages/flutter/test/material/input_decorator_test.dart
@@ -4533,43 +4533,6 @@
expect(intrinsicHeight, equals(height));
});
- testWidgets('Error message for negative baseline', (WidgetTester tester) async {
- FlutterErrorDetails? errorDetails;
- final FlutterExceptionHandler? oldHandler = FlutterError.onError;
- FlutterError.onError = (FlutterErrorDetails details) {
- errorDetails ??= details;
- };
- try {
- await tester.pumpWidget(
- const MaterialApp(
- home: Center(
- child: Directionality(
- textDirection: TextDirection.ltr,
- child: InputDecorator(
- decoration: InputDecoration(),
- child: Stack(
- children: <Widget>[
- SizedBox(height: 0),
- Positioned(
- bottom: 5,
- child: Text('ok'),
- ),
- ],
- ),
- ),
- ),
- ),
- ),
- phase: EnginePhase.layout,
- );
- } finally {
- FlutterError.onError = oldHandler;
- }
-
- expect(errorDetails?.toString(), contains("InputDecorator's children reported a negative baseline"));
- expect(errorDetails?.toString(), contains('RenderStack'));
- });
-
testWidgets('Min intrinsic height for TextField with no content padding', (WidgetTester tester) async {
// Regression test for: https://github.com/flutter/flutter/issues/75509
await tester.pumpWidget(const MaterialApp(