diff --git a/packages/flutter/lib/src/material/input_border.dart b/packages/flutter/lib/src/material/input_border.dart index fa1866c284..251033b4f9 100644 --- a/packages/flutter/lib/src/material/input_border.dart +++ b/packages/flutter/lib/src/material/input_border.dart @@ -385,63 +385,68 @@ class OutlineInputBorder extends InputBorder { } Path _gapBorderPath(Canvas canvas, RRect center, double start, double extent) { + // When the corner radii on any side add up to be greater than the + // given height, each radius has to be scaled to not exceed the + // size of the width/height of the RRect. + final RRect scaledRRect = center.scaleRadii(); + final Rect tlCorner = Rect.fromLTWH( - center.left, - center.top, - center.tlRadiusX * 2.0, - center.tlRadiusY * 2.0, + scaledRRect.left, + scaledRRect.top, + scaledRRect.tlRadiusX * 2.0, + scaledRRect.tlRadiusY * 2.0, ); final Rect trCorner = Rect.fromLTWH( - center.right - center.trRadiusX * 2.0, - center.top, - center.trRadiusX * 2.0, - center.trRadiusY * 2.0, + scaledRRect.right - scaledRRect.trRadiusX * 2.0, + scaledRRect.top, + scaledRRect.trRadiusX * 2.0, + scaledRRect.trRadiusY * 2.0, ); final Rect brCorner = Rect.fromLTWH( - center.right - center.brRadiusX * 2.0, - center.bottom - center.brRadiusY * 2.0, - center.brRadiusX * 2.0, - center.brRadiusY * 2.0, + scaledRRect.right - scaledRRect.brRadiusX * 2.0, + scaledRRect.bottom - scaledRRect.brRadiusY * 2.0, + scaledRRect.brRadiusX * 2.0, + scaledRRect.brRadiusY * 2.0, ); final Rect blCorner = Rect.fromLTWH( - center.left, - center.bottom - center.brRadiusY * 2.0, - center.blRadiusX * 2.0, - center.blRadiusY * 2.0, + scaledRRect.left, + scaledRRect.bottom - scaledRRect.blRadiusY * 2.0, + scaledRRect.blRadiusX * 2.0, + scaledRRect.blRadiusX * 2.0, ); const double cornerArcSweep = math.pi / 2.0; - final double tlCornerArcSweep = start < center.tlRadiusX - ? math.asin((start / center.tlRadiusX).clamp(-1.0, 1.0)) + final double tlCornerArcSweep = start < scaledRRect.tlRadiusX + ? math.asin((start / scaledRRect.tlRadiusX).clamp(-1.0, 1.0)) : math.pi / 2.0; final Path path = Path() ..addArc(tlCorner, math.pi, tlCornerArcSweep) - ..moveTo(center.left + center.tlRadiusX, center.top); + ..moveTo(scaledRRect.left + scaledRRect.tlRadiusX, scaledRRect.top); - if (start > center.tlRadiusX) - path.lineTo(center.left + start, center.top); + if (start > scaledRRect.tlRadiusX) + path.lineTo(scaledRRect.left + start, scaledRRect.top); const double trCornerArcStart = (3 * math.pi) / 2.0; const double trCornerArcSweep = cornerArcSweep; - if (start + extent < center.width - center.trRadiusX) { + if (start + extent < scaledRRect.width - scaledRRect.trRadiusX) { path ..relativeMoveTo(extent, 0.0) - ..lineTo(center.right - center.trRadiusX, center.top) + ..lineTo(scaledRRect.right - scaledRRect.trRadiusX, scaledRRect.top) ..addArc(trCorner, trCornerArcStart, trCornerArcSweep); - } else if (start + extent < center.width) { - final double dx = center.width - (start + extent); - final double sweep = math.acos(dx / center.trRadiusX); + } else if (start + extent < scaledRRect.width) { + final double dx = scaledRRect.width - (start + extent); + final double sweep = math.acos(dx / scaledRRect.trRadiusX); path.addArc(trCorner, trCornerArcStart + sweep, trCornerArcSweep - sweep); } return path - ..moveTo(center.right, center.top + center.trRadiusY) - ..lineTo(center.right, center.bottom - center.brRadiusY) + ..moveTo(scaledRRect.right, scaledRRect.top + scaledRRect.trRadiusY) + ..lineTo(scaledRRect.right, scaledRRect.bottom - scaledRRect.brRadiusY) ..addArc(brCorner, 0.0, cornerArcSweep) - ..lineTo(center.left + center.blRadiusX, center.bottom) + ..lineTo(scaledRRect.left + scaledRRect.blRadiusX, scaledRRect.bottom) ..addArc(blCorner, math.pi / 2.0, cornerArcSweep) - ..lineTo(center.left, center.top + center.trRadiusY); + ..lineTo(scaledRRect.left, scaledRRect.top + scaledRRect.tlRadiusY); } /// Draw a rounded rectangle around [rect] using [borderRadius]. diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index 86ca4ccbed..0a15dc7070 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -3170,6 +3170,110 @@ void main() { expect(getBorder(tester), disabledBorder); }); + testWidgets('OutlineInputBorder borders scale down to fit when large values are passed in', (WidgetTester tester) async { + // This is a regression test for https://github.com/flutter/flutter/issues/34327 + const double largerBorderRadius = 200.0; + const double smallerBorderRadius = 100.0; + + // Overall height for this InputDecorator is 56dps: + // 12 - top padding + // 12 - floating label (ahem font size 16dps * 0.75 = 12) + // 4 - floating label / input text gap + // 16 - input text (ahem font size 16dps) + // 12 - bottom padding + const double inputDecoratorHeight = 56.0; + const double inputDecoratorWidth = 800.0; + + await tester.pumpWidget( + buildInputDecorator( + decoration: const InputDecoration( + filled: true, + fillColor: Color(0xFF00FF00), + labelText: 'label text', + border: OutlineInputBorder( + borderRadius: BorderRadius.only( + // Intentionally large values that are larger than the InputDecorator + topLeft: Radius.circular(smallerBorderRadius), + bottomLeft: Radius.circular(smallerBorderRadius), + topRight: Radius.circular(largerBorderRadius), + bottomRight: Radius.circular(largerBorderRadius), + ), + ), + ), + ), + ); + + // Skia determines the scale based on the ratios of radii to the total + // height or width allowed. In this case, it is the right side of the + // border, which have two corners with largerBorderRadius that add up + // to be 400.0. + const double denominator = largerBorderRadius * 2.0; + + const double largerBorderRadiusScaled = largerBorderRadius / denominator * inputDecoratorHeight; + const double smallerBorderRadiusScaled = smallerBorderRadius / denominator * inputDecoratorHeight; + + expect(findBorderPainter(), paints + ..save() + ..path( + style: PaintingStyle.fill, + color: const Color(0xFF00FF00), + includes: const [ + // The border should draw along the four edges of the + // InputDecorator. + + // Top center + Offset(inputDecoratorWidth / 2.0, 0.0), + // Bottom center + Offset(inputDecoratorWidth / 2.0, inputDecoratorHeight), + // Left center + Offset(0.0, inputDecoratorHeight / 2.0), + // Right center + Offset(inputDecoratorWidth, inputDecoratorHeight / 2.0), + + // The border path should contain points where each rounded corner + // ends. + + // Bottom-right arc + Offset(inputDecoratorWidth, inputDecoratorHeight - largerBorderRadiusScaled), + Offset(inputDecoratorWidth - largerBorderRadiusScaled, inputDecoratorHeight), + // Top-right arc + Offset(inputDecoratorWidth,0.0 + largerBorderRadiusScaled), + Offset(inputDecoratorWidth - largerBorderRadiusScaled, 0.0), + // Bottom-left arc + Offset(0.0, inputDecoratorHeight - smallerBorderRadiusScaled), + Offset(0.0 + smallerBorderRadiusScaled, inputDecoratorHeight), + // Top-left arc + Offset(0.0,0.0 + smallerBorderRadiusScaled), + Offset(0.0 + smallerBorderRadiusScaled, 0.0), + ], + excludes: const [ + // The border should not contain the corner points, since the border + // is rounded. + + // Top-left + Offset(0.0, 0.0), + // Top-right + Offset(inputDecoratorWidth, 0.0), + // Bottom-left + Offset(0.0, inputDecoratorHeight), + // Bottom-right + Offset(inputDecoratorWidth, inputDecoratorHeight), + + // Corners with larger border ratio should not contain points outside + // of the larger radius. + + // Bottom-right arc + Offset(inputDecoratorWidth, inputDecoratorHeight - smallerBorderRadiusScaled), + Offset(inputDecoratorWidth - smallerBorderRadiusScaled, inputDecoratorWidth), + // Top-left arc + Offset(inputDecoratorWidth, 0.0 + smallerBorderRadiusScaled), + Offset(inputDecoratorWidth - smallerBorderRadiusScaled, 0.0), + ], + ) + ..restore() + ); + }); + testWidgets('OutlineInputBorder radius carries over when lerping', (WidgetTester tester) async { // This is a regression test for https://github.com/flutter/flutter/issues/23982 const Key key = Key('textField');