Unverified Commit 63438b92 authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

Fix Vertical Alignment Regression (#34859)

Change the way outlined inputs vertically align their text to be more similar to how it used to be before a refactor. Fixes an edge case uncovered by a SCUBA test.
parent 2dd1418c
......@@ -574,6 +574,7 @@ class _RenderDecorationLayout {
const _RenderDecorationLayout({
this.boxToBaseline,
this.inputBaseline, // for InputBorderType.underline
this.outlineBaseline, // for InputBorderType.outline
this.subtextBaseline,
this.containerHeight,
this.subtextHeight,
......@@ -581,6 +582,7 @@ class _RenderDecorationLayout {
final Map<RenderBox, double> boxToBaseline;
final double inputBaseline;
final double outlineBaseline;
final double subtextBaseline; // helper/error counter
final double containerHeight;
final double subtextHeight;
......@@ -1055,13 +1057,32 @@ class _RenderDecoration extends RenderBox {
- topHeight
- contentPadding.bottom;
final double alignableHeight = fixAboveInput + inputHeight + fixBelowInput;
// When outline aligned, the baseline is vertically centered by default, and
// outlinePadding is used to account for the presence of the border and
// floating label.
final double outlinePadding = _isOutlineAligned ? 10.0 : 0;
final double textAlignVerticalOffset = (maxContentHeight - alignableHeight - outlinePadding) * textAlignVerticalFactor;
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 - (2.0 + 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;
......@@ -1090,11 +1111,41 @@ class _RenderDecoration extends RenderBox {
boxToBaseline: boxToBaseline,
containerHeight: containerHeight,
inputBaseline: inputBaseline,
outlineBaseline: outlineBaseline,
subtextBaseline: subtextBaseline,
subtextHeight: subtextHeight,
);
}
// Interpolate between three stops using textAlignVertical. This is used to
// calculate the outline baseline, which ignores padding when the alignment is
// middle. When the alignment is less than zero, it interpolates between the
// centered text box's top and the top of the content padding. When the
// 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;
}
@override
double computeMinIntrinsicWidth(double height) {
return _minWidth(icon, height)
......@@ -1199,7 +1250,7 @@ class _RenderDecoration extends RenderBox {
final double right = overallWidth - contentPadding.right;
height = layout.containerHeight;
baseline = layout.inputBaseline;
baseline = _isOutlineAligned ? layout.outlineBaseline : layout.inputBaseline;
if (icon != null) {
double x;
......
......@@ -1495,7 +1495,7 @@ void main() {
);
// Below the center aligned case.
expect(tester.getTopLeft(find.text(text)).dy, closeTo(554.0, .0001));
expect(tester.getTopLeft(find.text(text)).dy, closeTo(564.0, .0001));
});
});
......@@ -1680,8 +1680,8 @@ void main() {
);
// Below the center example.
expect(tester.getTopLeft(find.text(text)).dy, closeTo(554.0, .0001));
expect(tester.getTopLeft(find.byKey(pKey)).dy, closeTo(470.0, .0001));
expect(tester.getTopLeft(find.text(text)).dy, closeTo(564.0, .0001));
expect(tester.getTopLeft(find.byKey(pKey)).dy, closeTo(480.0, .0001));
});
testWidgets('InputDecorator tall prefix with border align double', (WidgetTester tester) async {
......@@ -1711,8 +1711,8 @@ void main() {
);
// Between the top and center examples.
expect(tester.getTopLeft(find.text(text)).dy, closeTo(353.3, .0001));
expect(tester.getTopLeft(find.byKey(pKey)).dy, closeTo(269.3, .0001));
expect(tester.getTopLeft(find.text(text)).dy, closeTo(354.3, .0001));
expect(tester.getTopLeft(find.byKey(pKey)).dy, closeTo(270.3, .0001));
});
});
......@@ -1794,6 +1794,199 @@ void main() {
});
});
group('OutlineInputBorder', () {
group('default alignment', () {
testWidgets('Centers when border', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0));
expect(tester.getTopLeft(find.text('text')).dy, 19.0);
expect(tester.getBottomLeft(find.text('text')).dy, 35.0);
expect(getBorderBottom(tester), 56.0);
expect(getBorderWeight(tester), 1.0);
});
testWidgets('Centers when border and label', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
decoration: const InputDecoration(
labelText: 'label',
border: OutlineInputBorder(),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 56.0));
expect(tester.getTopLeft(find.text('text')).dy, 19.0);
expect(tester.getBottomLeft(find.text('text')).dy, 35.0);
expect(getBorderBottom(tester), 56.0);
expect(getBorderWeight(tester), 1.0);
});
testWidgets('Centers when border and contentPadding', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
decoration: const InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.fromLTRB(
12.0, 14.0,
8.0, 14.0,
),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 44.0));
expect(tester.getTopLeft(find.text('text')).dy, 13.0);
expect(tester.getBottomLeft(find.text('text')).dy, 29.0);
expect(getBorderBottom(tester), 44.0);
expect(getBorderWeight(tester), 1.0);
});
testWidgets('Centers when border and contentPadding and label', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
decoration: const InputDecoration(
labelText: 'label',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.fromLTRB(
12.0, 14.0,
8.0, 14.0,
),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 44.0));
expect(tester.getTopLeft(find.text('text')).dy, 13.0);
expect(tester.getBottomLeft(find.text('text')).dy, 29.0);
expect(getBorderBottom(tester), 44.0);
expect(getBorderWeight(tester), 1.0);
});
testWidgets('Centers when border and lopsided contentPadding and label', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
decoration: const InputDecoration(
labelText: 'label',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.fromLTRB(
12.0, 104.0,
8.0, 0.0,
),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 120.0));
expect(tester.getTopLeft(find.text('text')).dy, 51.0);
expect(tester.getBottomLeft(find.text('text')).dy, 67.0);
expect(getBorderBottom(tester), 120.0);
expect(getBorderWeight(tester), 1.0);
});
});
group('3 point interpolation alignment', () {
testWidgets('top align includes padding', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
expands: true,
textAlignVertical: TextAlignVertical.top,
decoration: const InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.fromLTRB(
12.0, 24.0,
8.0, 2.0,
),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 600.0));
// Aligned to the top including the 24px padding.
expect(tester.getTopLeft(find.text('text')).dy, 24.0);
expect(tester.getBottomLeft(find.text('text')).dy, 40.0);
expect(getBorderBottom(tester), 600.0);
expect(getBorderWeight(tester), 1.0);
});
testWidgets('center align ignores padding', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
expands: true,
textAlignVertical: TextAlignVertical.center,
decoration: const InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.fromLTRB(
12.0, 24.0,
8.0, 2.0,
),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 600.0));
// Baseline is on the center of the 600px high input.
expect(tester.getTopLeft(find.text('text')).dy, 291.0);
expect(tester.getBottomLeft(find.text('text')).dy, 307.0);
expect(getBorderBottom(tester), 600.0);
expect(getBorderWeight(tester), 1.0);
});
testWidgets('bottom align includes padding', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
expands: true,
textAlignVertical: TextAlignVertical.bottom,
decoration: const InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.fromLTRB(
12.0, 24.0,
8.0, 2.0,
),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 600.0));
// Includes bottom padding of 2px.
expect(tester.getTopLeft(find.text('text')).dy, 582.0);
expect(tester.getBottomLeft(find.text('text')).dy, 598.0);
expect(getBorderBottom(tester), 600.0);
expect(getBorderWeight(tester), 1.0);
});
testWidgets('padding exceeds middle keeps top at middle', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
expands: true,
textAlignVertical: TextAlignVertical.top,
decoration: const InputDecoration(
border: OutlineInputBorder(),
contentPadding: EdgeInsets.fromLTRB(
12.0, 504.0,
8.0, 0.0,
),
),
),
);
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 600.0));
// Same position as the center example above.
expect(tester.getTopLeft(find.text('text')).dy, 291.0);
expect(tester.getBottomLeft(find.text('text')).dy, 307.0);
expect(getBorderBottom(tester), 600.0);
expect(getBorderWeight(tester), 1.0);
});
});
});
testWidgets('counter text has correct right margin - LTR, not dense', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment