diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index 0cbafe1a1d8..4faea3dc900 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -803,9 +803,6 @@ class _RenderDecoration extends RenderBox { + aboveBaseline + belowBaseline + contentPadding.bottom; - containerHeight = math.max( - containerHeight, - math.max(_boxSize(suffixIcon).height, _boxSize(prefixIcon).height)); if (label != null) { // floatingLabelHeight includes the vertical gap between the inline @@ -814,6 +811,12 @@ class _RenderDecoration extends RenderBox { inputBaseline += decoration.floatingLabelHeight; } + containerHeight = math.max( + containerHeight, + math.max( + _boxSize(suffixIcon).height, + _boxSize(prefixIcon).height)); + // Inline text within an outline border is centered within the container // less 2.0 dps at the top to account for the vertical space occupied // by the floating label. @@ -982,8 +985,10 @@ class _RenderDecoration extends RenderBox { case TextDirection.rtl: { double start = right - _boxSize(icon).width; double end = left; - if (prefixIcon != null) + if (prefixIcon != null) { + start += contentPadding.left; start -= centerLayout(prefixIcon, start - prefixIcon.size.width); + } if (label != null) centerLayout(label, start - label.size.width); if (prefix != null) @@ -992,8 +997,10 @@ class _RenderDecoration extends RenderBox { baselineLayout(input, start - input.size.width); if (hint != null) baselineLayout(hint, start - hint.size.width); - if (suffixIcon != null) + if (suffixIcon != null) { + end -= contentPadding.left; end += centerLayout(suffixIcon, end); + } if (suffix != null) end += baselineLayout(suffix, end); break; @@ -1001,8 +1008,10 @@ class _RenderDecoration extends RenderBox { case TextDirection.ltr: { double start = left + _boxSize(icon).width; double end = right; - if (prefixIcon != null) + if (prefixIcon != null) { + start -= contentPadding.left; start += centerLayout(prefixIcon, start); + } if (label != null) centerLayout(label, start); if (prefix != null) @@ -1011,8 +1020,10 @@ class _RenderDecoration extends RenderBox { baselineLayout(input, start); if (hint != null) baselineLayout(hint, start); - if (suffixIcon != null) + if (suffixIcon != null) { + end += contentPadding.right; end -= centerLayout(suffixIcon, end - suffixIcon.size.width); + } if (suffix != null) end -= baselineLayout(suffix, end - suffix.size.width); break; @@ -1691,26 +1702,34 @@ class _InputDecoratorState extends State with TickerProviderStat ); final Widget prefixIcon = decoration.prefixIcon == null ? null : - new ConstrainedBox( - constraints: const BoxConstraints(minWidth: 48.0, minHeight: 48.0), - child: IconTheme.merge( - data: new IconThemeData( - color: iconColor, - size: iconSize, + new Center( + widthFactor: 1.0, + heightFactor: 1.0, + child: new ConstrainedBox( + constraints: const BoxConstraints(minWidth: 48.0, minHeight: 48.0), + child: IconTheme.merge( + data: new IconThemeData( + color: iconColor, + size: iconSize, + ), + child: decoration.prefixIcon, ), - child: decoration.prefixIcon, ), ); final Widget suffixIcon = decoration.suffixIcon == null ? null : - new ConstrainedBox( - constraints: const BoxConstraints(minWidth: 48.0, minHeight: 48.0), - child: IconTheme.merge( - data: new IconThemeData( - color: iconColor, - size: iconSize, + new Center( + widthFactor: 1.0, + heightFactor: 1.0, + child: ConstrainedBox( + constraints: const BoxConstraints(minWidth: 48.0, minHeight: 48.0), + child: IconTheme.merge( + data: new IconThemeData( + color: iconColor, + size: iconSize, + ), + child: decoration.suffixIcon, ), - child: decoration.suffixIcon, ), ); @@ -1737,8 +1756,6 @@ class _InputDecoratorState extends State with TickerProviderStat EdgeInsets contentPadding; double floatingLabelHeight; - final double leftInset = decoration.prefixIcon == null ? 12.0 : 0.0; - final double rightInset = decoration.suffixIcon == null ? 12.0 : 0.0; if (decoration.isCollapsed) { floatingLabelHeight = 0.0; contentPadding = decorationContentPadding ?? EdgeInsets.zero; @@ -1747,8 +1764,8 @@ class _InputDecoratorState extends State with TickerProviderStat floatingLabelHeight = 4.0 + 0.75 * inlineLabelStyle.fontSize; if (decoration.filled == true) { // filled == null same as filled == false contentPadding = decorationContentPadding ?? (decorationIsDense - ? new EdgeInsets.fromLTRB(leftInset, 8.0, rightInset, 8.0) - : new EdgeInsets.fromLTRB(leftInset, 12.0, rightInset, 12.0)); + ? const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 8.0) + : const EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 12.0)); } else { // Not left or right padding for underline borders that aren't filled // is a small concession to backwards compatibility. This eliminates @@ -1760,10 +1777,9 @@ class _InputDecoratorState extends State with TickerProviderStat } else { floatingLabelHeight = 0.0; contentPadding = decorationContentPadding ?? (decorationIsDense - ? new EdgeInsets.fromLTRB(leftInset, 20.0, rightInset, 12.0) - : new EdgeInsets.fromLTRB(leftInset, 24.0, rightInset, 16.0)); + ? const EdgeInsets.fromLTRB(12.0, 20.0, 12.0, 12.0) + : const EdgeInsets.fromLTRB(12.0, 24.0, 12.0, 16.0)); } - return new _Decorator( decoration: new _Decoration( contentPadding: contentPadding, diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index dc4ddfef7f0..b082442ce5a 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -763,12 +763,11 @@ void main() { ), ); - // Overall height for this InputDecorator is 48dps: - // 12 - top padding - // 16 - input text (ahem font size 16dps) - // 12 - bottom padding - // 48 - prefix icon - // 48 - suffix icon + // Overall height for this InputDecorator is 48dps because the prefix icon's minimum size + // is 48x48 and the rest of the elements only require 40dps: + // 12 - top padding + // 16 - input text (ahem font size 16dps) + // 12 - bottom padding expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 48.0)); expect(tester.getSize(find.text('text')).height, 16.0); @@ -786,6 +785,132 @@ void main() { expect(tester.getTopRight(find.text('text')).dx, lessThanOrEqualTo(tester.getTopLeft(find.byIcon(Icons.satellite)).dx)); }); + testWidgets('prefix/suffix icons are centered when smaller than 48 by 48', (WidgetTester tester) async { + const Key prefixKey = const Key('prefix'); + await tester.pumpWidget( + buildInputDecorator( + decoration: const InputDecoration( + prefixIcon: const Padding( + padding: const EdgeInsets.all(16.0), + child: const SizedBox(width: 8.0, height: 8.0, key: prefixKey), + ), + filled: true, + ), + ), + ); + + // Overall height for this InputDecorator is 48dps because the prefix icon's minimum size + // is 48x48 and the rest of the elements only require 40dps: + // 12 - top padding + // 16 - input text (ahem font size 16dps) + // 12 - bottom padding + + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 48.0)); + expect(tester.getSize(find.byKey(prefixKey)).height, 16.0); + expect(tester.getTopLeft(find.byKey(prefixKey)).dy, 16.0); + }); + + testWidgets('prefix/suffix icons increase height of decoration when larger than 48 by 48', (WidgetTester tester) async { + const Key prefixKey = const Key('prefix'); + await tester.pumpWidget( + buildInputDecorator( + decoration: const InputDecoration( + prefixIcon: const SizedBox(width: 100.0, height: 100.0, key: prefixKey), + filled: true, + ), + ), + ); + + // Overall height for this InputDecorator is 100dps because the prefix icon's size + // is 100x100 and the rest of the elements only require 40dps: + // 12 - top padding + // 16 - input text (ahem font size 16dps) + // 12 - bottom padding + + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 100.0)); + expect(tester.getSize(find.byKey(prefixKey)).height, 100.0); + expect(tester.getTopLeft(find.byKey(prefixKey)).dy, 0.0); + }); + + + testWidgets('counter text has correct right margin - LTR, not dense', (WidgetTester tester) async { + await tester.pumpWidget( + buildInputDecorator( + // isEmpty: false (default) + // isFocused: false (default) + decoration: const InputDecoration( + counterText: 'test', + filled: true, + ), + ), + ); + + // Margin for text decoration is 12 when filled + // (dx) - 12 = (text offset)x. + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 60.0)); + final double dx = tester.getRect(find.byType(InputDecorator)).right; + expect(tester.getRect(find.text('test')).right, dx - 12.0); + }); + + testWidgets('counter text has correct right margin - RTL, not dense', (WidgetTester tester) async { + await tester.pumpWidget( + buildInputDecorator( + textDirection: TextDirection.rtl, + // isEmpty: false (default) + // isFocused: false (default) + decoration: const InputDecoration( + counterText: 'test', + filled: true, + ), + ), + ); + + // Margin for text decoration is 12 when filled and top left offset is (0, 0) + // 0 + 12 = 12. + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 60.0)); + expect(tester.getRect(find.text('test')).left, 12.0); + }); + + testWidgets('counter text has correct right margin - LTR, dense', (WidgetTester tester) async { + await tester.pumpWidget( + buildInputDecorator( + // isEmpty: false (default) + // isFocused: false (default) + decoration: const InputDecoration( + counterText: 'test', + filled: true, + isDense: true, + ), + ), + ); + + // Margin for text decoration is 12 when filled + // (dx) - 12 = (text offset)x. + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 52.0)); + final double dx = tester.getRect(find.byType(InputDecorator)).right; + expect(tester.getRect(find.text('test')).right, dx - 12.0); + }); + + testWidgets('counter text has correct right margin - RTL, dense', (WidgetTester tester) async { + await tester.pumpWidget( + buildInputDecorator( + textDirection: TextDirection.rtl, + // isEmpty: false (default) + // isFocused: false (default) + decoration: const InputDecoration( + counterText: 'test', + filled: true, + isDense: true, + ), + ), + ); + + // Margin for text decoration is 12 when filled and top left offset is (0, 0) + // 0 + 12 = 12. + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 52.0)); + expect(tester.getRect(find.text('test')).left, 12.0); + }); + testWidgets('InputDecorator error/helper/counter RTL layout', (WidgetTester tester) async { await tester.pumpWidget( buildInputDecorator(