Unverified Commit 01f3fca3 authored by Bernardo Ferrari's avatar Bernardo Ferrari Committed by GitHub

Improve ShapeDecoration performance. (#108648)

parent 0de1ca27
......@@ -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),
),
......
......@@ -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
......
......@@ -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) {
......
......@@ -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
......
......@@ -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);
......
......@@ -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(
......
......@@ -310,6 +310,7 @@ class _ShapeDecorationPainter extends BoxPainter {
Path? _innerPath;
Paint? _interiorPaint;
int? _shadowCount;
late List<Rect> _shadowBounds;
late List<Path> _shadowPaths;
late List<Paint> _shadowPaints;
......@@ -342,13 +343,21 @@ class _ShapeDecorationPainter extends BoxPainter {
..._decoration.shadows!.map((BoxShadow shadow) => shadow.toPaint()),
];
}
_shadowPaths = <Path>[
..._decoration.shadows!.map((BoxShadow shadow) {
return _decoration.shape.getOuterPath(rect.shift(shadow.offset).inflate(shadow.spreadRadius), textDirection: textDirection);
}),
];
if (_decoration.shape.preferPaintInterior) {
_shadowBounds = <Rect>[
..._decoration.shadows!.map((BoxShadow shadow) {
return rect.shift(shadow.offset).inflate(shadow.spreadRadius);
}),
];
} else {
_shadowPaths = <Path>[
..._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);
}
......
......@@ -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(
......
......@@ -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 {
......
......@@ -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);
......
......@@ -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),
);
}
......
......@@ -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),
);
}
......
......@@ -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<StadiumBorder>());
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<RoundedRectangleBorder>());
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 {
......
......@@ -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 {
......
......@@ -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>[
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>[
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>[
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>[
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 {
......
......@@ -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 {
......
......@@ -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 {
......
......@@ -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),
......
......@@ -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 {
......
......@@ -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 {
......
......@@ -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();
......
......@@ -55,7 +55,7 @@ Future<void> 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),
);
......
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