diff --git a/packages/flutter/lib/src/material/input_border.dart b/packages/flutter/lib/src/material/input_border.dart index b5651be62c..9b8dc4ce10 100644 --- a/packages/flutter/lib/src/material/input_border.dart +++ b/packages/flutter/lib/src/material/input_border.dart @@ -439,44 +439,63 @@ class OutlineInputBorder extends InputBorder { scaledRRect.left, scaledRRect.bottom - scaledRRect.blRadiusY * 2.0, scaledRRect.blRadiusX * 2.0, - scaledRRect.blRadiusX * 2.0, + scaledRRect.blRadiusY * 2.0, ); // This assumes that the radius is circular (x and y radius are equal). // Currently, BorderRadius only supports circular radii. const double cornerArcSweep = math.pi / 2.0; - final double tlCornerArcSweep = math.acos( - clampDouble(1 - start / scaledRRect.tlRadiusX, 0.0, 1.0), - ); + final Path path = Path(); - final Path path = Path() - ..addArc(tlCorner, math.pi, tlCornerArcSweep); + // Top left corner + if (scaledRRect.tlRadius != Radius.zero) { + final double tlCornerArcSweep = math.acos(clampDouble(1 - start / scaledRRect.tlRadiusX, 0.0, 1.0)); + path.addArc(tlCorner, math.pi, tlCornerArcSweep); + } else { + // Because the path is painted with Paint.strokeCap = StrokeCap.butt, horizontal coordinate is moved + // to the left using borderSide.width / 2. + path.moveTo(scaledRRect.left - borderSide.width / 2, scaledRRect.top); + } + // Draw top border from top left corner to gap start. if (start > scaledRRect.tlRadiusX) { path.lineTo(scaledRRect.left + start, scaledRRect.top); } + // Draw top border from gap end to top right corner and draw top right corner. const double trCornerArcStart = (3 * math.pi) / 2.0; const double trCornerArcSweep = cornerArcSweep; if (start + extent < scaledRRect.width - scaledRRect.trRadiusX) { path.moveTo(scaledRRect.left + start + extent, scaledRRect.top); path.lineTo(scaledRRect.right - scaledRRect.trRadiusX, scaledRRect.top); - path.addArc(trCorner, trCornerArcStart, trCornerArcSweep); + if (scaledRRect.trRadius != Radius.zero) { + path.addArc(trCorner, trCornerArcStart, trCornerArcSweep); + } } else if (start + extent < scaledRRect.width) { final double dx = scaledRRect.width - (start + extent); - final double sweep = math.asin( - clampDouble(1 - dx / scaledRRect.trRadiusX, 0.0, 1.0), - ); + final double sweep = math.asin(clampDouble(1 - dx / scaledRRect.trRadiusX, 0.0, 1.0)); path.addArc(trCorner, trCornerArcStart + sweep, trCornerArcSweep - sweep); } - return path - ..moveTo(scaledRRect.right, scaledRRect.top + scaledRRect.trRadiusY) - ..lineTo(scaledRRect.right, scaledRRect.bottom - scaledRRect.brRadiusY) - ..addArc(brCorner, 0.0, cornerArcSweep) - ..lineTo(scaledRRect.left + scaledRRect.blRadiusX, scaledRRect.bottom) - ..addArc(blCorner, math.pi / 2.0, cornerArcSweep) - ..lineTo(scaledRRect.left, scaledRRect.top + scaledRRect.tlRadiusY); + // Draw right border and bottom right corner. + if (scaledRRect.brRadius != Radius.zero) { + path.moveTo(scaledRRect.right, scaledRRect.top + scaledRRect.trRadiusY); + } + path.lineTo(scaledRRect.right, scaledRRect.bottom - scaledRRect.brRadiusY); + if (scaledRRect.brRadius != Radius.zero) { + path.addArc(brCorner, 0.0, cornerArcSweep); + } + + // Draw bottom border and bottom left corner. + path.lineTo(scaledRRect.left + scaledRRect.blRadiusX, scaledRRect.bottom); + if (scaledRRect.blRadius != Radius.zero) { + path.addArc(blCorner, math.pi / 2.0, cornerArcSweep); + } + + // Draw left border + path.lineTo(scaledRRect.left, scaledRRect.top + scaledRRect.tlRadiusY); + + return path; } /// 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 23b3285afe..ac6fed3fce 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -4992,6 +4992,70 @@ void main() { ); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/55317 + testWidgets('OutlineInputBorder with BorderRadius.zero should draw a rectangular border', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/78855 + const String labelText = 'Flutter'; + + // 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; + const double borderWidth = 4.0; + + await tester.pumpWidget( + buildInputDecorator( + isFocused: true, + decoration: const InputDecoration( + filled: false, + labelText: labelText, + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.zero, + borderSide: BorderSide(width: borderWidth, color: Colors.red), + ), + ), + ), + ); + + expect(find.text(labelText), findsOneWidget); + expect(findBorderPainter(), paints + ..save() + ..path( + includes: const [ + // Corner points in the middle of the border line should be in the path. + // The path is not filled and borderWidth is 4.0 so Offset(2.0, 2.0) is in the path and Offset(1.0, 1.0) is not. + // See Skia SkPath::contains method. + + // Top-left + Offset(borderWidth / 2, borderWidth / 2), + // Top-right + Offset(inputDecoratorWidth - 1 - borderWidth / 2, borderWidth / 2), + // Bottom-left + Offset(borderWidth / 2, inputDecoratorHeight - 1 - borderWidth / 2), + // Bottom-right + Offset(inputDecoratorWidth - 1 - borderWidth / 2, inputDecoratorHeight - 1 - borderWidth / 2), + ], + excludes: const [ + // The path is not filled and borderWidth is 4.0 so the path should not contains the corner points. + // See Skia SkPath::contains method. + + // Top-left + Offset.zero, + // // Top-right + Offset(inputDecoratorWidth - 1, 0), + // // Bottom-left + Offset(0, inputDecoratorHeight - 1), + // // Bottom-right + Offset(inputDecoratorWidth - 1, inputDecoratorHeight - 1), + ], + ) + ..restore(), + ); + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/55317 + 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'); @@ -5085,7 +5149,6 @@ void main() { expect(underlineInputBorder, isNot(const UnderlineInputBorder())); }); - test('InputBorder hashCodes', () { // OutlineInputBorder's hashCode is defined by the borderRadius, borderSide, & gapPadding const OutlineInputBorder outlineInputBorder = OutlineInputBorder(