diff --git a/dev/benchmarks/complex_layout/lib/main.dart b/dev/benchmarks/complex_layout/lib/main.dart index 9e386bafcae..c99261e1995 100644 --- a/dev/benchmarks/complex_layout/lib/main.dart +++ b/dev/benchmarks/complex_layout/lib/main.dart @@ -324,9 +324,9 @@ class MiniIconWithText extends StatelessWidget { child: Container( width: 16.0, height: 16.0, - decoration: BoxDecoration( + decoration: ShapeDecoration( color: Theme.of(context).primaryColor, - shape: BoxShape.circle, + shape: const CircleBorder(), ), child: Icon(icon, color: Colors.white, size: 12.0), ), diff --git a/packages/flutter/lib/src/material/input_border.dart b/packages/flutter/lib/src/material/input_border.dart index 5df3d8e31f6..b5651be62c7 100644 --- a/packages/flutter/lib/src/material/input_border.dart +++ b/packages/flutter/lib/src/material/input_border.dart @@ -108,6 +108,14 @@ class _NoInputBorder extends InputBorder { return Path()..addRect(rect); } + @override + void paintInterior(Canvas canvas, Rect rect, Paint paint, { TextDirection? textDirection }) { + canvas.drawRect(rect, paint); + } + + @override + bool get preferPaintInterior => true; + @override void paint( Canvas canvas, @@ -194,6 +202,14 @@ class UnderlineInputBorder extends InputBorder { return Path()..addRRect(borderRadius.resolve(textDirection).toRRect(rect)); } + @override + void paintInterior(Canvas canvas, Rect rect, Paint paint, { TextDirection? textDirection }) { + canvas.drawRRect(borderRadius.resolve(textDirection).toRRect(rect), paint); + } + + @override + bool get preferPaintInterior => true; + @override ShapeBorder? lerpFrom(ShapeBorder? a, double t) { if (a is UnderlineInputBorder) { @@ -387,6 +403,14 @@ class OutlineInputBorder extends InputBorder { ..addRRect(borderRadius.resolve(textDirection).toRRect(rect)); } + @override + void paintInterior(Canvas canvas, Rect rect, Paint paint, { TextDirection? textDirection }) { + canvas.drawRRect(borderRadius.resolve(textDirection).toRRect(rect), paint); + } + + @override + bool get preferPaintInterior => true; + 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 diff --git a/packages/flutter/lib/src/painting/borders.dart b/packages/flutter/lib/src/painting/borders.dart index 9d8666d403f..f112465d71e 100644 --- a/packages/flutter/lib/src/painting/borders.dart +++ b/packages/flutter/lib/src/painting/borders.dart @@ -533,6 +533,78 @@ abstract class ShapeBorder { /// * [Path.contains], which can tell if an [Offset] is within a [Path]. Path getInnerPath(Rect rect, { TextDirection? textDirection }); + /// Paint a canvas with the appropriate shape. + /// + /// On [ShapeBorder] subclasses whose [preferPaintInterior] method returns + /// true, this should be faster than using [Canvas.drawPath] with the path + /// provided by [getOuterPath]. (If [preferPaintInterior] returns false, + /// then this method asserts in debug mode and does nothing in release mode.) + /// + /// Subclasses are expected to implement this method when the [Canvas] API + /// has a dedicated method to draw the relevant shape. For example, + /// [CircleBorder] uses this to call [Canvas.drawCircle], and + /// [RoundedRectangleBorder] uses this to call [Canvas.drawRRect]. + /// + /// Subclasses that implement this must ensure that calling [paintInterior] + /// is semantically equivalent to (i.e. renders the same pixels as) calling + /// [Canvas.drawPath] with the same [Paint] and the [Path] returned from + /// [getOuterPath], and must also override [preferPaintInterior] to + /// return true. + /// + /// For example, a shape that draws a rectangle might implement + /// [getOuterPath], [paintInterior], and [preferPaintInterior] as follows: + /// + /// ```dart + /// class RectangleBorder extends OutlinedBorder { + /// // ... + /// + /// @override + /// Path getOuterPath(Rect rect, { TextDirection? textDirection }) { + /// return Path() + /// ..addRect(rect); + /// } + /// + /// @override + /// void paintInterior(Canvas canvas, Rect rect, Paint paint, {TextDirection? textDirection}) { + /// canvas.drawRect(rect, paint); + /// } + /// + /// @override + /// bool get preferPaintInterior => true; + /// + /// // ... + /// } + /// ``` + /// + /// When a shape can only be drawn using path, [preferPaintInterior] must + /// return false. In that case, classes such as [ShapeDecoration] will cache + /// the path from [getOuterPath] and call [Canvas.drawPath] directly. + void paintInterior(Canvas canvas, Rect rect, Paint paint, {TextDirection? textDirection}) { + assert(!preferPaintInterior, '$runtimeType.preferPaintInterior returns true but $runtimeType.paintInterior is not implemented.'); + assert(false, '$runtimeType.preferPaintInterior returns false, so it is an error to call its paintInterior method.'); + } + + /// Reports whether [paintInterior] is implemented. + /// + /// Classes such as [ShapeDecoration] prefer to use [paintInterior] if this + /// getter returns true. This is intended to enable faster painting; instead + /// of computing a shape using [getOuterPath] and then drawing it using + /// [Canvas.drawPath], the path can be drawn directly to the [Canvas] using + /// dedicated methods such as [Canvas.drawRect] or [Canvas.drawCircle]. + /// + /// By default, this getter returns false. + /// + /// Subclasses that implement [paintInterior] should override this to return + /// true. Subclasses should only override [paintInterior] if doing so enables + /// faster rendering than is possible with [Canvas.drawPath] (so, in + /// particular, subclasses should not call [Canvas.drawPath] in + /// [paintInterior]). + /// + /// See also: + /// + /// * [paintInterior], whose API documentation has an example implementation. + bool get preferPaintInterior => false; + /// Paints the border within the given [Rect] on the given [Canvas]. /// /// The `textDirection` argument must be provided and non-null if the border @@ -719,6 +791,14 @@ class _CompoundBorder extends ShapeBorder { return borders.first.getOuterPath(rect, textDirection: textDirection); } + @override + void paintInterior(Canvas canvas, Rect rect, Paint paint, { TextDirection? textDirection }) { + borders.first.paintInterior(canvas, rect, paint, textDirection: textDirection); + } + + @override + bool get preferPaintInterior => true; + @override void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) { for (final ShapeBorder border in borders) { diff --git a/packages/flutter/lib/src/painting/box_border.dart b/packages/flutter/lib/src/painting/box_border.dart index 1c6b54b8a0c..bf6fb1d733f 100644 --- a/packages/flutter/lib/src/painting/box_border.dart +++ b/packages/flutter/lib/src/painting/box_border.dart @@ -180,6 +180,18 @@ abstract class BoxBorder extends ShapeBorder { ..addRect(rect); } + @override + void paintInterior(Canvas canvas, Rect rect, Paint paint, { TextDirection? textDirection }) { + // For `ShapeDecoration(shape: Border.all())`, a rectangle with sharp edges + // is always painted. There is no borderRadius parameter for + // ShapeDecoration or Border, only for BoxDecoration, which doesn't call + // this method. + canvas.drawRect(rect, paint); + } + + @override + bool get preferPaintInterior => true; + /// Paints the border within the given [Rect] on the given [Canvas]. /// /// This is an extension of the [ShapeBorder.paint] method. It allows diff --git a/packages/flutter/lib/src/painting/circle_border.dart b/packages/flutter/lib/src/painting/circle_border.dart index 24b225160d4..730cde691be 100644 --- a/packages/flutter/lib/src/painting/circle_border.dart +++ b/packages/flutter/lib/src/painting/circle_border.dart @@ -105,6 +105,18 @@ class CircleBorder extends OutlinedBorder { return Path()..addOval(_adjustRect(rect)); } + @override + void paintInterior(Canvas canvas, Rect rect, Paint paint, { TextDirection? textDirection }) { + if (eccentricity == 0.0) { + canvas.drawCircle(rect.center, rect.shortestSide / 2.0, paint); + } else { + canvas.drawOval(_adjustRect(rect), paint); + } + } + + @override + bool get preferPaintInterior => true; + @override CircleBorder copyWith({ BorderSide? side, double? eccentricity }) { return CircleBorder(side: side ?? this.side, eccentricity: eccentricity ?? this.eccentricity); diff --git a/packages/flutter/lib/src/painting/rounded_rectangle_border.dart b/packages/flutter/lib/src/painting/rounded_rectangle_border.dart index 98d042865d4..fd8ef67cecc 100644 --- a/packages/flutter/lib/src/painting/rounded_rectangle_border.dart +++ b/packages/flutter/lib/src/painting/rounded_rectangle_border.dart @@ -132,6 +132,18 @@ class RoundedRectangleBorder extends OutlinedBorder { ..addRRect(borderRadius.resolve(textDirection).toRRect(rect)); } + @override + void paintInterior(Canvas canvas, Rect rect, Paint paint, { TextDirection? textDirection }) { + if (borderRadius == BorderRadius.zero) { + canvas.drawRect(rect, paint); + } else { + canvas.drawRRect(borderRadius.resolve(textDirection).toRRect(rect), paint); + } + } + + @override + bool get preferPaintInterior => true; + @override void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) { switch (side.style) { @@ -352,6 +364,19 @@ class _RoundedRectangleToCircleBorder extends OutlinedBorder { ..addRRect(_adjustBorderRadius(rect, textDirection)!.toRRect(_adjustRect(rect))); } + @override + void paintInterior(Canvas canvas, Rect rect, Paint paint, { TextDirection? textDirection }) { + final BorderRadius adjustedBorderRadius = _adjustBorderRadius(rect, textDirection)!; + if (adjustedBorderRadius == BorderRadius.zero) { + canvas.drawRect(_adjustRect(rect), paint); + } else { + canvas.drawRRect(adjustedBorderRadius.toRRect(_adjustRect(rect)), paint); + } + } + + @override + bool get preferPaintInterior => true; + @override _RoundedRectangleToCircleBorder copyWith({ BorderSide? side, BorderRadiusGeometry? borderRadius, double? circleness, double? eccentricity }) { return _RoundedRectangleToCircleBorder( diff --git a/packages/flutter/lib/src/painting/shape_decoration.dart b/packages/flutter/lib/src/painting/shape_decoration.dart index 0367de282a7..3144428289b 100644 --- a/packages/flutter/lib/src/painting/shape_decoration.dart +++ b/packages/flutter/lib/src/painting/shape_decoration.dart @@ -310,6 +310,7 @@ class _ShapeDecorationPainter extends BoxPainter { Path? _innerPath; Paint? _interiorPaint; int? _shadowCount; + late List _shadowBounds; late List _shadowPaths; late List _shadowPaints; @@ -342,13 +343,21 @@ class _ShapeDecorationPainter extends BoxPainter { ..._decoration.shadows!.map((BoxShadow shadow) => shadow.toPaint()), ]; } - _shadowPaths = [ - ..._decoration.shadows!.map((BoxShadow shadow) { - return _decoration.shape.getOuterPath(rect.shift(shadow.offset).inflate(shadow.spreadRadius), textDirection: textDirection); - }), - ]; + if (_decoration.shape.preferPaintInterior) { + _shadowBounds = [ + ..._decoration.shadows!.map((BoxShadow shadow) { + return rect.shift(shadow.offset).inflate(shadow.spreadRadius); + }), + ]; + } else { + _shadowPaths = [ + ..._decoration.shadows!.map((BoxShadow shadow) { + return _decoration.shape.getOuterPath(rect.shift(shadow.offset).inflate(shadow.spreadRadius), textDirection: textDirection); + }), + ]; + } } - if (_interiorPaint != null || _shadowCount != null) { + if (!_decoration.shape.preferPaintInterior && (_interiorPaint != null || _shadowCount != null)) { _outerPath = _decoration.shape.getOuterPath(rect, textDirection: textDirection); } if (_decoration.image != null) { @@ -359,17 +368,27 @@ class _ShapeDecorationPainter extends BoxPainter { _lastTextDirection = textDirection; } - void _paintShadows(Canvas canvas) { + void _paintShadows(Canvas canvas, Rect rect, TextDirection? textDirection) { if (_shadowCount != null) { - for (int index = 0; index < _shadowCount!; index += 1) { - canvas.drawPath(_shadowPaths[index], _shadowPaints[index]); + if (_decoration.shape.preferPaintInterior) { + for (int index = 0; index < _shadowCount!; index += 1) { + _decoration.shape.paintInterior(canvas, _shadowBounds[index], _shadowPaints[index], textDirection: textDirection); + } + } else { + for (int index = 0; index < _shadowCount!; index += 1) { + canvas.drawPath(_shadowPaths[index], _shadowPaints[index]); + } } } } - void _paintInterior(Canvas canvas) { + void _paintInterior(Canvas canvas, Rect rect, TextDirection? textDirection) { if (_interiorPaint != null) { - canvas.drawPath(_outerPath, _interiorPaint!); + if (_decoration.shape.preferPaintInterior) { + _decoration.shape.paintInterior(canvas, rect, _interiorPaint!, textDirection: textDirection); + } else { + canvas.drawPath(_outerPath, _interiorPaint!); + } } } @@ -395,8 +414,8 @@ class _ShapeDecorationPainter extends BoxPainter { final Rect rect = offset & configuration.size!; final TextDirection? textDirection = configuration.textDirection; _precache(rect, textDirection); - _paintShadows(canvas); - _paintInterior(canvas); + _paintShadows(canvas, rect, textDirection); + _paintInterior(canvas, rect, textDirection); _paintImage(canvas, configuration); _decoration.shape.paint(canvas, rect, textDirection: textDirection); } diff --git a/packages/flutter/lib/src/painting/stadium_border.dart b/packages/flutter/lib/src/painting/stadium_border.dart index cda9d91e6f1..22317e580b0 100644 --- a/packages/flutter/lib/src/painting/stadium_border.dart +++ b/packages/flutter/lib/src/painting/stadium_border.dart @@ -123,6 +123,15 @@ class StadiumBorder extends OutlinedBorder { ..addRRect(RRect.fromRectAndRadius(rect, radius)); } + @override + void paintInterior(Canvas canvas, Rect rect, Paint paint, { TextDirection? textDirection }) { + final Radius radius = Radius.circular(rect.shortestSide / 2.0); + canvas.drawRRect(RRect.fromRectAndRadius(rect, radius), paint); + } + + @override + bool get preferPaintInterior => true; + @override void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) { switch (side.style) { @@ -312,6 +321,14 @@ class _StadiumToCircleBorder extends OutlinedBorder { ..addRRect(_adjustBorderRadius(rect).toRRect(_adjustRect(rect))); } + @override + void paintInterior(Canvas canvas, Rect rect, Paint paint, { TextDirection? textDirection }) { + canvas.drawRRect(_adjustBorderRadius(rect).toRRect(_adjustRect(rect)), paint); + } + + @override + bool get preferPaintInterior => true; + @override _StadiumToCircleBorder copyWith({ BorderSide? side, double? circleness, double? eccentricity }) { return _StadiumToCircleBorder( @@ -486,6 +503,19 @@ class _StadiumToRoundedRectangleBorder extends OutlinedBorder { ..addRRect(_adjustBorderRadius(rect).toRRect(rect)); } + @override + void paintInterior(Canvas canvas, Rect rect, Paint paint, { TextDirection? textDirection }) { + final BorderRadius adjustedBorderRadius = _adjustBorderRadius(rect); + if (adjustedBorderRadius == BorderRadius.zero) { + canvas.drawRect(rect, paint); + } else { + canvas.drawRRect(adjustedBorderRadius.toRRect(rect), paint); + } + } + + @override + bool get preferPaintInterior => true; + @override _StadiumToRoundedRectangleBorder copyWith({ BorderSide? side, BorderRadius? borderRadius, double? rectness }) { return _StadiumToRoundedRectangleBorder( diff --git a/packages/flutter/test/material/checkbox_list_tile_test.dart b/packages/flutter/test/material/checkbox_list_tile_test.dart index 90b4213d87e..caaef61793b 100644 --- a/packages/flutter/test/material/checkbox_list_tile_test.dart +++ b/packages/flutter/test/material/checkbox_list_tile_test.dart @@ -269,7 +269,7 @@ void main() { ), ); - expect(find.byType(Material), paints..path(color: tileColor)); + expect(find.byType(Material), paints..rect(color: tileColor)); }); testWidgets('CheckboxListTile respects selectedTileColor', (WidgetTester tester) async { @@ -289,7 +289,7 @@ void main() { ), ); - expect(find.byType(Material), paints..path(color: selectedTileColor)); + expect(find.byType(Material), paints..rect(color: selectedTileColor)); }); testWidgets('CheckboxListTile selected item text Color', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/chip_choice_test.dart b/packages/flutter/test/material/chip_choice_test.dart index fe2af679ab6..57593e8defb 100644 --- a/packages/flutter/test/material/chip_choice_test.dart +++ b/packages/flutter/test/material/chip_choice_test.dart @@ -79,7 +79,7 @@ void main() { } await tester.pumpWidget(buildFrame(Brightness.light)); - expect(getMaterialBox(tester), paints..path(color: const Color(0x3d000000))); + expect(getMaterialBox(tester), paints..rrect(color: const Color(0x3d000000))); expect(tester.getSize(find.byType(ChoiceChip)), const Size(108.0, 48.0)); expect(getMaterial(tester).color, null); expect(getMaterial(tester).elevation, 0); @@ -88,7 +88,7 @@ void main() { await tester.pumpWidget(buildFrame(Brightness.dark)); await tester.pumpAndSettle(); // Theme transition animation - expect(getMaterialBox(tester), paints..path(color: const Color(0x3dffffff))); + expect(getMaterialBox(tester), paints..rrect(color: const Color(0x3dffffff))); expect(tester.getSize(find.byType(ChoiceChip)), const Size(108.0, 48.0)); expect(getMaterial(tester).color, null); expect(getMaterial(tester).elevation, 0); diff --git a/packages/flutter/test/material/chip_filter_test.dart b/packages/flutter/test/material/chip_filter_test.dart index 773aadae187..47e3dcefd64 100644 --- a/packages/flutter/test/material/chip_filter_test.dart +++ b/packages/flutter/test/material/chip_filter_test.dart @@ -66,11 +66,11 @@ void expectCheckmarkColor(Finder finder, Color color) { paints // Physical model path ..path() - // The first path that is painted is the selection overlay. We do not care + // The first layer that is painted is the selection overlay. We do not care // how it is painted but it has to be added it to this pattern so that the // check mark can be checked next. - ..path() - // The second path that is painted is the check mark. + ..rrect() + // The second layer that is painted is the check mark. ..path(color: color), ); } diff --git a/packages/flutter/test/material/chip_input_test.dart b/packages/flutter/test/material/chip_input_test.dart index 6c6ca97bf0b..89973e61cd6 100644 --- a/packages/flutter/test/material/chip_input_test.dart +++ b/packages/flutter/test/material/chip_input_test.dart @@ -66,11 +66,11 @@ void expectCheckmarkColor(Finder finder, Color color) { paints // Physical model layer path ..path() - // The first path that is painted is the selection overlay. We do not care + // The first layer that is painted is the selection overlay. We do not care // how it is painted but it has to be added it to this pattern so that the // check mark can be checked next. - ..path() - // The second path that is painted is the check mark. + ..rrect() + // The second layer that is painted is the check mark. ..path(color: color), ); } diff --git a/packages/flutter/test/material/chip_test.dart b/packages/flutter/test/material/chip_test.dart index d7d11e04818..8ce03453c58 100644 --- a/packages/flutter/test/material/chip_test.dart +++ b/packages/flutter/test/material/chip_test.dart @@ -239,7 +239,7 @@ void main() { } await tester.pumpWidget(buildFrame(Brightness.light)); - expect(getMaterialBox(tester), paints..path(color: const Color(0x1f000000))); + expect(getMaterialBox(tester), paints..rrect()..circle(color: const Color(0xff1976d2))); expect(tester.getSize(find.byType(Chip)), const Size(156.0, 48.0)); expect(getMaterial(tester).color, null); expect(getMaterial(tester).elevation, 0); @@ -266,7 +266,7 @@ void main() { await tester.pumpWidget(buildFrame(Brightness.dark)); await tester.pumpAndSettle(); // Theme transition animation - expect(getMaterialBox(tester), paints..path(color: const Color(0x1fffffff))); + expect(getMaterialBox(tester), paints..rrect(color: const Color(0x1fffffff))); expect(tester.getSize(find.byType(Chip)), const Size(156.0, 48.0)); expect(getMaterial(tester).color, null); expect(getMaterial(tester).elevation, 0); @@ -1650,7 +1650,7 @@ void main() { ), ); - expect(materialBox, paints..path(color: chipTheme.disabledColor)); + expect(materialBox, paints..rrect(color: chipTheme.disabledColor)); }); testWidgets('Chip merges ChipThemeData label style with the provided label style', (WidgetTester tester) async { @@ -1823,13 +1823,13 @@ void main() { DefaultTextStyle labelStyle = getLabelStyle(tester, 'false'); // Check default theme for enabled widget. - expect(materialBox, paints..path(color: defaultChipTheme.backgroundColor)); + expect(materialBox, paints..rrect(color: defaultChipTheme.backgroundColor)); expect(iconData.color, equals(const Color(0xde000000))); expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde))); await tester.tap(find.byType(RawChip)); await tester.pumpAndSettle(); materialBox = getMaterialBox(tester); - expect(materialBox, paints..path(color: defaultChipTheme.selectedColor)); + expect(materialBox, paints..rrect(color: defaultChipTheme.selectedColor)); await tester.tap(find.byType(RawChip)); await tester.pumpAndSettle(); @@ -1838,7 +1838,7 @@ void main() { await tester.pumpAndSettle(); materialBox = getMaterialBox(tester); labelStyle = getLabelStyle(tester, 'false'); - expect(materialBox, paints..path(color: defaultChipTheme.disabledColor)); + expect(materialBox, paints..rrect(color: defaultChipTheme.disabledColor)); expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde))); // Apply a custom theme. @@ -1860,13 +1860,13 @@ void main() { labelStyle = getLabelStyle(tester, 'false'); // Check custom theme for enabled widget. - expect(materialBox, paints..path(color: customTheme.backgroundColor)); + expect(materialBox, paints..rrect(color: customTheme.backgroundColor)); expect(iconData.color, equals(customTheme.deleteIconColor)); expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde))); await tester.tap(find.byType(RawChip)); await tester.pumpAndSettle(); materialBox = getMaterialBox(tester); - expect(materialBox, paints..path(color: customTheme.selectedColor)); + expect(materialBox, paints..rrect(color: customTheme.selectedColor)); await tester.tap(find.byType(RawChip)); await tester.pumpAndSettle(); @@ -1878,7 +1878,7 @@ void main() { await tester.pumpAndSettle(); materialBox = getMaterialBox(tester); labelStyle = getLabelStyle(tester, 'false'); - expect(materialBox, paints..path(color: customTheme.disabledColor)); + expect(materialBox, paints..rrect(color: customTheme.disabledColor)); expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde))); }); @@ -2677,17 +2677,17 @@ void main() { // Default, not disabled. await tester.pumpWidget(chipWidget()); - expect(find.byType(RawChip), paints..rrect(color: defaultColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: defaultColor)); // Selected. await tester.pumpWidget(chipWidget(selected: true)); - expect(find.byType(RawChip), paints..rrect(color: selectedColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: selectedColor)); // Focused. final FocusNode chipFocusNode = focusNode.children.first; chipFocusNode.requestFocus(); await tester.pumpAndSettle(); - expect(find.byType(RawChip), paints..rrect(color: focusedColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: focusedColor)); // Hovered. final Offset center = tester.getCenter(find.byType(ChoiceChip)); @@ -2697,17 +2697,17 @@ void main() { await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); - expect(find.byType(RawChip), paints..rrect(color: hoverColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: hoverColor)); // Pressed. await gesture.down(center); await tester.pumpAndSettle(); - expect(find.byType(RawChip), paints..rrect(color: pressedColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: pressedColor)); // Disabled. await tester.pumpWidget(chipWidget(enabled: false)); await tester.pumpAndSettle(); - expect(find.byType(RawChip), paints..rrect(color: disabledColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: disabledColor)); }); testWidgets('Chip uses stateful border side color from resolveWith', (WidgetTester tester) async { @@ -2756,17 +2756,17 @@ void main() { // Default, not disabled. await tester.pumpWidget(chipWidget()); - expect(find.byType(RawChip), paints..rrect(color: defaultColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: defaultColor)); // Selected. await tester.pumpWidget(chipWidget(selected: true)); - expect(find.byType(RawChip), paints..rrect(color: selectedColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: selectedColor)); // Focused. final FocusNode chipFocusNode = focusNode.children.first; chipFocusNode.requestFocus(); await tester.pumpAndSettle(); - expect(find.byType(RawChip), paints..rrect(color: focusedColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: focusedColor)); // Hovered. final Offset center = tester.getCenter(find.byType(ChoiceChip)); @@ -2776,17 +2776,17 @@ void main() { await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); - expect(find.byType(RawChip), paints..rrect(color: hoverColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: hoverColor)); // Pressed. await gesture.down(center); await tester.pumpAndSettle(); - expect(find.byType(RawChip), paints..rrect(color: pressedColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: pressedColor)); // Disabled. await tester.pumpWidget(chipWidget(enabled: false)); await tester.pumpAndSettle(); - expect(find.byType(RawChip), paints..rrect(color: disabledColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: disabledColor)); }); @@ -2843,19 +2843,19 @@ void main() { // Default, not disabled. await tester.pumpWidget(chipWidget()); - expect(find.byType(RawChip), paints..rrect(color: defaultColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: defaultColor)); // Selected. await tester.pumpWidget(chipWidget(selected: true)); // Because the resolver returns `null` for this value, we should fall back // to the theme - expect(find.byType(RawChip), paints..rrect(color: fallbackThemeColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: fallbackThemeColor)); // Focused. final FocusNode chipFocusNode = focusNode.children.first; chipFocusNode.requestFocus(); await tester.pumpAndSettle(); - expect(find.byType(RawChip), paints..rrect(color: focusedColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: focusedColor)); // Hovered. final Offset center = tester.getCenter(find.byType(ChoiceChip)); @@ -2865,17 +2865,17 @@ void main() { await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); - expect(find.byType(RawChip), paints..rrect(color: hoverColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: hoverColor)); // Pressed. await gesture.down(center); await tester.pumpAndSettle(); - expect(find.byType(RawChip), paints..rrect(color: pressedColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: pressedColor)); // Disabled. await tester.pumpWidget(chipWidget(enabled: false)); await tester.pumpAndSettle(); - expect(find.byType(RawChip), paints..rrect(color: disabledColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: disabledColor)); }); testWidgets('Chip uses stateful shape in different states', (WidgetTester tester) async { @@ -2991,12 +2991,12 @@ void main() { // Default, not disabled. Defer to theme. await tester.pumpWidget(chipWidget()); expect(getMaterial(tester).shape, isA()); - expect(find.byType(RawChip), paints..rrect(color: themeBorderSide.color)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: themeBorderSide.color)); // Selected. await tester.pumpWidget(chipWidget(selected: true)); expect(getMaterial(tester).shape, isA()); - expect(find.byType(RawChip), paints..drrect(color: selectedBorderSide.color)); + expect(find.byType(RawChip), paints..rect()..drrect(color: selectedBorderSide.color)); }); testWidgets('Chip responds to density changes.', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/chip_theme_test.dart b/packages/flutter/test/material/chip_theme_test.dart index 80a95c49ea4..ec0a8f22321 100644 --- a/packages/flutter/test/material/chip_theme_test.dart +++ b/packages/flutter/test/material/chip_theme_test.dart @@ -159,7 +159,7 @@ void main() { ); final RenderBox materialBox = getMaterialBox(tester); - expect(materialBox, paints..path(color: chipTheme.backgroundColor)); + expect(materialBox, paints..rect(color: chipTheme.backgroundColor)); expect(getMaterial(tester).elevation, chipTheme.elevation); expect(tester.getSize(find.byType(RawChip)), const Size(250, 250)); // label + padding + labelPadding expect(getMaterial(tester).shape, chipTheme.shape); @@ -212,7 +212,7 @@ void main() { ); final RenderBox materialBox = getMaterialBox(tester); - expect(materialBox, paints..path(color: chipTheme.backgroundColor)); + expect(materialBox, paints..rect(color: chipTheme.backgroundColor)); expect(tester.getSize(find.byType(RawChip)), const Size(250, 250)); // label + padding + labelPadding expect(getMaterial(tester).elevation, chipTheme.elevation); expect(getMaterial(tester).shape, chipTheme.shape); @@ -264,7 +264,7 @@ void main() { ); final RenderBox materialBox = getMaterialBox(tester); - expect(materialBox, paints..path(color: backgroundColor)); + expect(materialBox, paints..circle(color: backgroundColor)); expect(tester.getSize(find.byType(RawChip)), const Size(250, 250)); // label + padding + labelPadding expect(getMaterial(tester).elevation, elevation); expect(getMaterial(tester).shape, shape); @@ -660,11 +660,11 @@ void main() { // Default. await tester.pumpWidget(chipWidget()); - expect(find.byType(RawChip), paints..rrect(color: defaultColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: defaultColor)); // Selected. await tester.pumpWidget(chipWidget(selected: true)); - expect(find.byType(RawChip), paints..rrect(color: selectedColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: selectedColor)); }); testWidgets('Chip uses stateful border side from chip theme', (WidgetTester tester) async { @@ -702,11 +702,11 @@ void main() { // Default. await tester.pumpWidget(chipWidget()); - expect(find.byType(RawChip), paints..rrect(color: defaultColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: defaultColor)); // Selected. await tester.pumpWidget(chipWidget(selected: true)); - expect(find.byType(RawChip), paints..rrect(color: selectedColor)); + expect(find.byType(RawChip), paints..rrect()..rrect(color: selectedColor)); }); testWidgets('Chip uses stateful shape from chip theme', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/list_tile_test.dart b/packages/flutter/test/material/list_tile_test.dart index 8aaca6f2111..5edd20c4b08 100644 --- a/packages/flutter/test/material/list_tile_test.dart +++ b/packages/flutter/test/material/list_tile_test.dart @@ -1149,6 +1149,7 @@ void main() { expect( find.byType(Material), paints + ..rect() ..rect( color: Colors.orange[500], rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), @@ -1166,6 +1167,7 @@ void main() { expect( find.byType(Material), paints + ..rect() ..rect( color: const Color(0xffffffff), rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), @@ -1202,6 +1204,7 @@ void main() { expect( find.byType(Material), paints + ..rect() ..rect( color: const Color(0x1f000000), rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), @@ -1222,6 +1225,7 @@ void main() { expect( find.byType(Material), paints + ..rect() ..rect( color: const Color(0x1f000000), rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), @@ -1242,6 +1246,7 @@ void main() { expect( find.byType(Material), paints + ..rect() ..rect( color: Colors.orange[500], rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), @@ -1349,51 +1354,22 @@ void main() { await tester.pumpWidget(buildListTile(rectShape)); Rect rect = tester.getRect(find.byType(ListTile)); - // Check if a path was painted with the correct color and shape + // Check if a rounded rectangle was painted with the correct color and shape expect( find.byType(Material), - paints..path( - color: tileColor, - // Corners should be included - includes: [ - Offset(rect.left, rect.top), - Offset(rect.right, rect.top), - Offset(rect.left, rect.bottom), - Offset(rect.right, rect.bottom), - ], - // Points outside rect should be excluded - excludes: [ - Offset(rect.left - 1, rect.top - 1), - Offset(rect.right + 1, rect.top - 1), - Offset(rect.left - 1, rect.bottom + 1), - Offset(rect.right + 1, rect.bottom + 1), - ], - ), + paints..rect(color: tileColor, rect: rect), ); // Test stadium shape await tester.pumpWidget(buildListTile(stadiumShape)); rect = tester.getRect(find.byType(ListTile)); - // Check if a path was painted with the correct color and shape + // Check if a rounded rectangle was painted with the correct color and shape expect( find.byType(Material), - paints..path( + paints..clipRect()..rrect( color: tileColor, - // Center points of sides should be included - includes: [ - Offset(rect.left + rect.width / 2, rect.top), - Offset(rect.left, rect.top + rect.height / 2), - Offset(rect.right, rect.top + rect.height / 2), - Offset(rect.left + rect.width / 2, rect.bottom), - ], - // Corners should be excluded - excludes: [ - Offset(rect.left, rect.top), - Offset(rect.right, rect.top), - Offset(rect.left, rect.bottom), - Offset(rect.right, rect.bottom), - ], + rrect: RRect.fromRectAndRadius(rect, Radius.circular(rect.shortestSide / 2.0)), ), ); }); @@ -1504,14 +1480,14 @@ void main() { ); // Initially, when isSelected is false, the ListTile should respect tileColor. - expect(find.byType(Material), paints..path(color: tileColor)); + expect(find.byType(Material), paints..rect(color: tileColor)); // Tap on tile to change isSelected. await tester.tap(find.byType(ListTile)); await tester.pumpAndSettle(); // When isSelected is true, the ListTile should respect selectedTileColor. - expect(find.byType(Material), paints..path(color: selectedTileColor)); + expect(find.byType(Material), paints..rect(color: selectedTileColor)); }); testWidgets('ListTile shows Material ripple effects on top of tileColor', (WidgetTester tester) async { @@ -1533,7 +1509,7 @@ void main() { ); // Before ListTile is tapped, it should be tileColor - expect(find.byType(Material), paints..path(color: tileColor)); + expect(find.byType(Material), paints..rect(color: tileColor)); // Tap on tile to trigger ink effect and wait for it to be underway. await tester.tap(find.byType(ListTile)); @@ -1543,7 +1519,7 @@ void main() { expect( find.byType(Material), paints - ..path(color: tileColor) + ..rect(color: tileColor) ..circle(), ); }); @@ -1572,13 +1548,13 @@ void main() { ), ); - expect(find.byType(Material), paints..path(color: defaultColor)); + expect(find.byType(Material), paints..rect(color: defaultColor)); // Tap on tile to change isSelected. await tester.tap(find.byType(ListTile)); await tester.pumpAndSettle(); - expect(find.byType(Material), paints..path(color: defaultColor)); + expect(find.byType(Material), paints..rect(color: defaultColor)); }); testWidgets('ListTile layout at zero size', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/list_tile_theme_test.dart b/packages/flutter/test/material/list_tile_theme_test.dart index bed87072fde..66fdb094ee3 100644 --- a/packages/flutter/test/material/list_tile_theme_test.dart +++ b/packages/flutter/test/material/list_tile_theme_test.dart @@ -396,13 +396,13 @@ void main() { ), ); - expect(find.byType(Material), paints..path(color: theme.tileColor)); + expect(find.byType(Material), paints..rect(color: theme.tileColor)); // Tap on tile to change isSelected. await tester.tap(find.byType(ListTile)); await tester.pumpAndSettle(); - expect(find.byType(Material), paints..path(color: theme.selectedTileColor)); + expect(find.byType(Material), paints..rect(color: theme.selectedTileColor)); }); testWidgets("ListTileTheme's tileColor & selectedTileColor are overridden by ListTile properties", (WidgetTester tester) async { @@ -438,13 +438,13 @@ void main() { ), ); - expect(find.byType(Material), paints..path(color: tileColor)); + expect(find.byType(Material), paints..rect(color: tileColor)); // Tap on tile to change isSelected. await tester.tap(find.byType(ListTile)); await tester.pumpAndSettle(); - expect(find.byType(Material), paints..path(color: selectedTileColor)); + expect(find.byType(Material), paints..rect(color: selectedTileColor)); }); testWidgets('ListTile uses ListTileTheme shape in a drawer', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/radio_list_tile_test.dart b/packages/flutter/test/material/radio_list_tile_test.dart index 7e720d5981e..02a181ed209 100644 --- a/packages/flutter/test/material/radio_list_tile_test.dart +++ b/packages/flutter/test/material/radio_list_tile_test.dart @@ -681,7 +681,7 @@ void main() { ), ); - expect(find.byType(Material), paints..path(color: tileColor)); + expect(find.byType(Material), paints..rect(color: tileColor)); }); testWidgets('RadioListTile respects selectedTileColor', (WidgetTester tester) async { @@ -702,7 +702,7 @@ void main() { ), ); - expect(find.byType(Material), paints..path(color: selectedTileColor)); + expect(find.byType(Material), paints..rect(color: selectedTileColor)); }); testWidgets('RadioListTile selected item text Color', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/switch_list_tile_test.dart b/packages/flutter/test/material/switch_list_tile_test.dart index 25294090859..aa732eb03be 100644 --- a/packages/flutter/test/material/switch_list_tile_test.dart +++ b/packages/flutter/test/material/switch_list_tile_test.dart @@ -372,7 +372,7 @@ void main() { ), ); - expect(find.byType(Material), paints..path(color: tileColor)); + expect(find.byType(Material), paints..rect(color: tileColor)); }); testWidgets('SwitchListTile respects selectedTileColor', (WidgetTester tester) async { @@ -392,7 +392,7 @@ void main() { ), ); - expect(find.byType(Material), paints..path(color: selectedTileColor)); + expect(find.byType(Material), paints..rect(color: selectedTileColor)); }); testWidgets('SwitchListTile selected item text Color', (WidgetTester tester) async { @@ -553,6 +553,7 @@ void main() { expect( Material.of(tester.element(find.byKey(key))), paints + ..rect() ..rect( color: Colors.orange[500], rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), diff --git a/packages/flutter/test/material/tooltip_test.dart b/packages/flutter/test/material/tooltip_test.dart index da4f23e72a6..f02a8155621 100644 --- a/packages/flutter/test/material/tooltip_test.dart +++ b/packages/flutter/test/material/tooltip_test.dart @@ -878,9 +878,7 @@ void main() { ); expect(tip.size.height, equals(32.0)); expect(tip.size.width, equals(74.0)); - expect(tip, paints..path( - color: const Color(0x80800000), - )); + expect(tip, paints..rrect(color: const Color(0x80800000))); }); testWidgets('Tooltip stays after long press', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/tooltip_theme_test.dart b/packages/flutter/test/material/tooltip_theme_test.dart index c8ef44f1812..064a2f12bc0 100644 --- a/packages/flutter/test/material/tooltip_theme_test.dart +++ b/packages/flutter/test/material/tooltip_theme_test.dart @@ -734,9 +734,7 @@ void main() { expect(tip.size.height, equals(32.0)); expect(tip.size.width, equals(74.0)); - expect(tip, paints..path( - color: const Color(0x80800000), - )); + expect(tip, paints..rrect(color: const Color(0x80800000))); }); testWidgets('Tooltip decoration - TooltipTheme', (WidgetTester tester) async { @@ -776,9 +774,7 @@ void main() { expect(tip.size.height, equals(32.0)); expect(tip.size.width, equals(74.0)); - expect(tip, paints..path( - color: const Color(0x80800000), - )); + expect(tip, paints..rrect(color: const Color(0x80800000))); }); testWidgets('Tooltip height and padding - ThemeData.tooltipTheme', (WidgetTester tester) async { diff --git a/packages/flutter/test/widgets/scrollbar_test.dart b/packages/flutter/test/widgets/scrollbar_test.dart index bf044ed3dd7..2c320426e7b 100644 --- a/packages/flutter/test/widgets/scrollbar_test.dart +++ b/packages/flutter/test/widgets/scrollbar_test.dart @@ -2024,7 +2024,7 @@ void main() { await tester.pumpWidget(buildFrame(600.1)); await tester.pumpAndSettle(); - expect(find.byType(RawScrollbar), paints..rect()..rect()); // Show the bar. + expect(find.byType(RawScrollbar), paints..rect()); // Show the bar. await tester.pumpWidget(buildFrame(600.0)); await tester.pumpAndSettle(); diff --git a/packages/flutter/test/widgets/shape_decoration_test.dart b/packages/flutter/test/widgets/shape_decoration_test.dart index 0173bb66deb..36a0b4b4362 100644 --- a/packages/flutter/test/widgets/shape_decoration_test.dart +++ b/packages/flutter/test/widgets/shape_decoration_test.dart @@ -55,7 +55,7 @@ Future main() async { expect( find.byType(DecoratedBox), paints - ..path(color: Color(Colors.blue.value)) + ..rect(color: Color(Colors.blue.value)) ..rect(color: Colors.black) ..rect(color: Colors.white), );