diff --git a/bin/internal/goldens.version b/bin/internal/goldens.version index 935ebb2df6..f756bbed28 100644 --- a/bin/internal/goldens.version +++ b/bin/internal/goldens.version @@ -1 +1 @@ -d0800cf4170e77682173c19703f605c6795e0ff8 +cc98e28c974eea0bd9a8e24591857ae6b5479795 diff --git a/packages/flutter/lib/src/material/button.dart b/packages/flutter/lib/src/material/button.dart index 6bf3891851..bf6c743b40 100644 --- a/packages/flutter/lib/src/material/button.dart +++ b/packages/flutter/lib/src/material/button.dart @@ -189,6 +189,7 @@ class _RawMaterialButtonState extends State { splashColor: widget.splashColor, highlightColor: widget.highlightColor, onTap: widget.onPressed, + customBorder: widget.shape, child: IconTheme.merge( data: new IconThemeData(color: widget.textStyle?.color), child: new Container( diff --git a/packages/flutter/lib/src/material/ink_highlight.dart b/packages/flutter/lib/src/material/ink_highlight.dart index 17ca41a17a..dd9372968e 100644 --- a/packages/flutter/lib/src/material/ink_highlight.dart +++ b/packages/flutter/lib/src/material/ink_highlight.dart @@ -41,12 +41,14 @@ class InkHighlight extends InteractiveInkFeature { @required Color color, BoxShape shape = BoxShape.rectangle, BorderRadius borderRadius, + ShapeBorder customBorder, RectCallback rectCallback, VoidCallback onRemoved, }) : assert(color != null), assert(shape != null), _shape = shape, _borderRadius = borderRadius ?? BorderRadius.zero, + _customBorder = customBorder, _rectCallback = rectCallback, super(controller: controller, referenceBox: referenceBox, color: color, onRemoved: onRemoved) { _alphaController = new AnimationController(duration: _kHighlightFadeDuration, vsync: controller.vsync) @@ -63,6 +65,7 @@ class InkHighlight extends InteractiveInkFeature { final BoxShape _shape; final BorderRadius _borderRadius; + final ShapeBorder _customBorder; final RectCallback _rectCallback; Animation _alpha; @@ -97,6 +100,10 @@ class InkHighlight extends InteractiveInkFeature { void _paintHighlight(Canvas canvas, Rect rect, Paint paint) { assert(_shape != null); + canvas.save(); + if (_customBorder != null) { + canvas.clipPath(_customBorder.getOuterPath(rect)); + } switch (_shape) { case BoxShape.circle: canvas.drawCircle(rect.center, Material.defaultSplashRadius, paint); @@ -114,6 +121,7 @@ class InkHighlight extends InteractiveInkFeature { } break; } + canvas.restore(); } @override diff --git a/packages/flutter/lib/src/material/ink_ripple.dart b/packages/flutter/lib/src/material/ink_ripple.dart index 0e897b7e85..584d844a8c 100644 --- a/packages/flutter/lib/src/material/ink_ripple.dart +++ b/packages/flutter/lib/src/material/ink_ripple.dart @@ -48,6 +48,7 @@ class _InkRippleFactory extends InteractiveInkFeatureFactory { bool containedInkWell = false, RectCallback rectCallback, BorderRadius borderRadius, + ShapeBorder customBorder, double radius, VoidCallback onRemoved, }) { @@ -59,6 +60,7 @@ class _InkRippleFactory extends InteractiveInkFeatureFactory { containedInkWell: containedInkWell, rectCallback: rectCallback, borderRadius: borderRadius, + customBorder: customBorder, radius: radius, onRemoved: onRemoved, ); @@ -115,12 +117,14 @@ class InkRipple extends InteractiveInkFeature { bool containedInkWell = false, RectCallback rectCallback, BorderRadius borderRadius, + ShapeBorder customBorder, double radius, VoidCallback onRemoved, }) : assert(color != null), assert(position != null), _position = position, _borderRadius = borderRadius ?? BorderRadius.zero, + _customBorder = customBorder, _targetRadius = radius ?? _getTargetRadius(referenceBox, containedInkWell, rectCallback, position), _clipCallback = _getClipCallback(referenceBox, containedInkWell, rectCallback), super(controller: controller, referenceBox: referenceBox, color: color, onRemoved: onRemoved) @@ -172,6 +176,7 @@ class InkRipple extends InteractiveInkFeature { final Offset _position; final BorderRadius _borderRadius; + final ShapeBorder _customBorder; final double _targetRadius; final RectCallback _clipCallback; @@ -220,26 +225,6 @@ class InkRipple extends InteractiveInkFeature { super.dispose(); } - RRect _clipRRectFromRect(Rect rect) { - return new RRect.fromRectAndCorners( - rect, - topLeft: _borderRadius.topLeft, topRight: _borderRadius.topRight, - bottomLeft: _borderRadius.bottomLeft, bottomRight: _borderRadius.bottomRight, - ); - } - - void _clipCanvasWithRect(Canvas canvas, Rect rect, {Offset offset}) { - Rect clipRect = rect; - if (offset != null) { - clipRect = clipRect.shift(offset); - } - if (_borderRadius != BorderRadius.zero) { - canvas.clipRRect(_clipRRectFromRect(clipRect)); - } else { - canvas.clipRect(clipRect); - } - } - @override void paintFeature(Canvas canvas, Matrix4 transform) { final int alpha = _fadeInController.isAnimating ? _fadeIn.value : _fadeOut.value; @@ -251,22 +236,27 @@ class InkRipple extends InteractiveInkFeature { Curves.ease.transform(_radiusController.value), ); final Offset originOffset = MatrixUtils.getAsTranslation(transform); + canvas.save(); if (originOffset == null) { - canvas.save(); canvas.transform(transform.storage); - if (_clipCallback != null) { - _clipCanvasWithRect(canvas, _clipCallback()); - } - canvas.drawCircle(center, _radius.value, paint); - canvas.restore(); } else { - if (_clipCallback != null) { - canvas.save(); - _clipCanvasWithRect(canvas, _clipCallback(), offset: originOffset); - } - canvas.drawCircle(center + originOffset, _radius.value, paint); - if (_clipCallback != null) - canvas.restore(); + canvas.translate(originOffset.dx, originOffset.dy); } + if (_clipCallback != null) { + final Rect rect = _clipCallback(); + if (_customBorder != null) { + canvas.clipPath(_customBorder.getOuterPath(rect)); + } else if (_borderRadius != BorderRadius.zero) { + canvas.clipRRect(new RRect.fromRectAndCorners( + rect, + topLeft: _borderRadius.topLeft, topRight: _borderRadius.topRight, + bottomLeft: _borderRadius.bottomLeft, bottomRight: _borderRadius.bottomRight, + )); + } else { + canvas.clipRect(rect); + } + } + canvas.drawCircle(center, _radius.value, paint); + canvas.restore(); } } diff --git a/packages/flutter/lib/src/material/ink_splash.dart b/packages/flutter/lib/src/material/ink_splash.dart index 72549d397a..2b884d71ff 100644 --- a/packages/flutter/lib/src/material/ink_splash.dart +++ b/packages/flutter/lib/src/material/ink_splash.dart @@ -54,6 +54,7 @@ class _InkSplashFactory extends InteractiveInkFeatureFactory { bool containedInkWell = false, RectCallback rectCallback, BorderRadius borderRadius, + ShapeBorder customBorder, double radius, VoidCallback onRemoved, }) { @@ -65,6 +66,7 @@ class _InkSplashFactory extends InteractiveInkFeatureFactory { containedInkWell: containedInkWell, rectCallback: rectCallback, borderRadius: borderRadius, + customBorder: customBorder, radius: radius, onRemoved: onRemoved, ); @@ -119,10 +121,12 @@ class InkSplash extends InteractiveInkFeature { bool containedInkWell = false, RectCallback rectCallback, BorderRadius borderRadius, + ShapeBorder customBorder, double radius, VoidCallback onRemoved, }) : _position = position, _borderRadius = borderRadius ?? BorderRadius.zero, + _customBorder = customBorder, _targetRadius = radius ?? _getTargetRadius(referenceBox, containedInkWell, rectCallback, position), _clipCallback = _getClipCallback(referenceBox, containedInkWell, rectCallback), _repositionToReferenceBox = !containedInkWell, @@ -148,6 +152,7 @@ class InkSplash extends InteractiveInkFeature { final Offset _position; final BorderRadius _borderRadius; + final ShapeBorder _customBorder; final double _targetRadius; final RectCallback _clipCallback; final bool _repositionToReferenceBox; @@ -185,26 +190,6 @@ class InkSplash extends InteractiveInkFeature { super.dispose(); } - RRect _clipRRectFromRect(Rect rect) { - return new RRect.fromRectAndCorners( - rect, - topLeft: _borderRadius.topLeft, topRight: _borderRadius.topRight, - bottomLeft: _borderRadius.bottomLeft, bottomRight: _borderRadius.bottomRight, - ); - } - - void _clipCanvasWithRect(Canvas canvas, Rect rect, {Offset offset}) { - Rect clipRect = rect; - if (offset != null) { - clipRect = clipRect.shift(offset); - } - if (_borderRadius != BorderRadius.zero) { - canvas.clipRRect(_clipRRectFromRect(clipRect)); - } else { - canvas.clipRect(clipRect); - } - } - @override void paintFeature(Canvas canvas, Matrix4 transform) { final Paint paint = new Paint()..color = color.withAlpha(_alpha.value); @@ -212,22 +197,27 @@ class InkSplash extends InteractiveInkFeature { if (_repositionToReferenceBox) center = Offset.lerp(center, referenceBox.size.center(Offset.zero), _radiusController.value); final Offset originOffset = MatrixUtils.getAsTranslation(transform); + canvas.save(); if (originOffset == null) { - canvas.save(); canvas.transform(transform.storage); - if (_clipCallback != null) { - _clipCanvasWithRect(canvas, _clipCallback()); - } - canvas.drawCircle(center, _radius.value, paint); - canvas.restore(); } else { - if (_clipCallback != null) { - canvas.save(); - _clipCanvasWithRect(canvas, _clipCallback(), offset: originOffset); - } - canvas.drawCircle(center + originOffset, _radius.value, paint); - if (_clipCallback != null) - canvas.restore(); + canvas.translate(originOffset.dx, originOffset.dy); } + if (_clipCallback != null) { + final Rect rect = _clipCallback(); + if (_customBorder != null) { + canvas.clipPath(_customBorder.getOuterPath(rect)); + } else if (_borderRadius != BorderRadius.zero) { + canvas.clipRRect(new RRect.fromRectAndCorners( + rect, + topLeft: _borderRadius.topLeft, topRight: _borderRadius.topRight, + bottomLeft: _borderRadius.bottomLeft, bottomRight: _borderRadius.bottomRight, + )); + } else { + canvas.clipRect(rect); + } + } + canvas.drawCircle(center, _radius.value, paint); + canvas.restore(); } } diff --git a/packages/flutter/lib/src/material/ink_well.dart b/packages/flutter/lib/src/material/ink_well.dart index dd8a4007c9..04b2eda083 100644 --- a/packages/flutter/lib/src/material/ink_well.dart +++ b/packages/flutter/lib/src/material/ink_well.dart @@ -95,6 +95,7 @@ abstract class InteractiveInkFeatureFactory { bool containedInkWell = false, RectCallback rectCallback, BorderRadius borderRadius, + ShapeBorder customBorder, double radius, VoidCallback onRemoved, }); @@ -201,6 +202,7 @@ class InkResponse extends StatefulWidget { this.highlightShape = BoxShape.circle, this.radius, this.borderRadius, + this.customBorder, this.highlightColor, this.splashColor, this.splashFactory, @@ -285,11 +287,15 @@ class InkResponse extends StatefulWidget { /// * [splashFactory], which defines the appearance of the splash. final double radius; - /// The clipping radius of the containing rect. + /// The clipping radius of the containing rect. This is effective only if + /// [customBorder] is null. /// /// If this is null, it is interpreted as [BorderRadius.zero]. final BorderRadius borderRadius; + /// The custom clip border which overrides [borderRadius]. + final ShapeBorder customBorder; + /// The highlight color of the ink response. If this property is null then the /// highlight color of the theme, [ThemeData.highlightColor], will be used. /// @@ -417,6 +423,7 @@ class _InkResponseState extends State with AutomaticKe color: widget.highlightColor ?? Theme.of(context).highlightColor, shape: widget.highlightShape, borderRadius: widget.borderRadius, + customBorder: widget.customBorder, rectCallback: widget.getRectCallback(referenceBox), onRemoved: _handleInkHighlightRemoval, ); @@ -445,6 +452,7 @@ class _InkResponseState extends State with AutomaticKe final Color color = widget.splashColor ?? Theme.of(context).splashColor; final RectCallback rectCallback = widget.containedInkWell ? widget.getRectCallback(referenceBox) : null; final BorderRadius borderRadius = widget.borderRadius; + final ShapeBorder customBorder = widget.customBorder; InteractiveInkFeature splash; void onRemoved() { @@ -466,6 +474,7 @@ class _InkResponseState extends State with AutomaticKe rectCallback: rectCallback, radius: widget.radius, borderRadius: borderRadius, + customBorder: customBorder, onRemoved: onRemoved, ); @@ -626,6 +635,7 @@ class InkWell extends InkResponse { InteractiveInkFeatureFactory splashFactory, double radius, BorderRadius borderRadius, + ShapeBorder customBorder, bool enableFeedback = true, bool excludeFromSemantics = false, }) : super( @@ -644,6 +654,7 @@ class InkWell extends InkResponse { splashFactory: splashFactory, radius: radius, borderRadius: borderRadius, + customBorder: customBorder, enableFeedback: enableFeedback, excludeFromSemantics: excludeFromSemantics, ); diff --git a/packages/flutter/test/material/bottom_navigation_bar_test.dart b/packages/flutter/test/material/bottom_navigation_bar_test.dart index 0f4aca54b6..7e70e22cac 100644 --- a/packages/flutter/test/material/bottom_navigation_bar_test.dart +++ b/packages/flutter/test/material/bottom_navigation_bar_test.dart @@ -457,7 +457,7 @@ void main() { await tester.tap(find.text('B')); await tester.pump(); await tester.pump(const Duration(milliseconds: 20)); - expect(box, paints..circle(x: 200.0)..circle(x: 600.0)); + expect(box, paints..circle(x: 200.0)..translate(x: 400.0)..circle(x: 200.0)); // Now we flip the directionality and verify that the circles switch positions. await tester.pumpWidget( @@ -478,12 +478,23 @@ void main() { ), ); - expect(box, paints..circle(x: 600.0)..circle(x: 200.0)); + expect(box, paints..translate()..save()..translate(x: 400.0)..circle(x: 200.0)..restore()..circle(x: 200.0)); await tester.tap(find.text('A')); await tester.pump(); await tester.pump(const Duration(milliseconds: 20)); - expect(box, paints..circle(x: 600.0)..circle(x: 200.0)..circle(x: 600.0)); + expect( + box, + paints + ..translate(x: 0.0, y: 0.0) + ..save() + ..translate(x: 400.0) + ..circle(x: 200.0) + ..restore() + ..circle(x: 200.0) + ..translate(x: 400.0) + ..circle(x: 200.0) + ); }); testWidgets('BottomNavigationBar inactiveIcon shown', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/ink_paint_test.dart b/packages/flutter/test/material/ink_paint_test.dart index cde881c7d9..eea0020ce9 100644 --- a/packages/flutter/test/material/ink_paint_test.dart +++ b/packages/flutter/test/material/ink_paint_test.dart @@ -40,8 +40,12 @@ void main() { expect( box, paints - ..clipRRect(rrect: new RRect.fromLTRBR(300.0, 270.0, 500.0, 330.0, const Radius.circular(6.0))) - ..circle(x: 400.0, y: 300.0, radius: 21.0, color: splashColor) + ..translate(x: 0.0, y: 0.0) + ..save() + ..translate(x: 300.0, y: 270.0) + ..clipRRect(rrect: new RRect.fromLTRBR(0.0, 0.0, 200.0, 60.0, const Radius.circular(6.0))) + ..circle(x: 100.0, y: 30.0, radius: 21.0, color: splashColor) + ..restore() ..rrect( rrect: new RRect.fromLTRBR(300.0, 270.0, 500.0, 330.0, const Radius.circular(6.0)), color: highlightColor, @@ -86,93 +90,50 @@ void main() { bool offsetsAreClose(Offset a, Offset b) => (a - b).distance < 1.0; bool radiiAreClose(double a, double b) => (a - b).abs() < 1.0; - // Initially the ripple's center is where the tap occurred, - expect(box, paints..something((Symbol method, List arguments) { - if (method != #drawCircle) - return false; - final Offset center = arguments[0]; - final double radius = arguments[1]; - final Paint paint = arguments[2]; - if (offsetsAreClose(center, tapDownOffset) && radius == 30.0 && paint.color.alpha == 0) - return true; - throw ''' - Expected: center == $tapDownOffset, radius == 30.0, alpha == 0 - Found: center == $center radius == $radius alpha == ${paint.color.alpha}'''; - })); + PaintPattern ripplePattern(Offset expectedCenter, double expectedRadius, int expectedAlpha) { + return paints + ..translate(x: 0.0, y: 0.0) + ..translate(x: tapDownOffset.dx, y: tapDownOffset.dy) + ..something((Symbol method, List arguments) { + if (method != #drawCircle) + return false; + final Offset center = arguments[0]; + final double radius = arguments[1]; + final Paint paint = arguments[2]; + if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == expectedAlpha) + return true; + throw ''' + Expected: center == $expectedCenter, radius == $expectedRadius, alpha == $expectedAlpha + Found: center == $center radius == $radius alpha == ${paint.color.alpha}'''; + } + ); + } + + // Initially the ripple's center is where the tap occurred. Note that + // ripplePattern always add a translation of tapDownOffset. + expect(box, ripplePattern(Offset.zero, 30.0, 0)); // The ripple fades in for 75ms. During that time its alpha is eased from // 0 to the splashColor's alpha value and its center moves towards the // center of the ink well. await tester.pump(const Duration(milliseconds: 50)); - expect(box, paints..something((Symbol method, List arguments) { - if (method != #drawCircle) - return false; - final Offset center = arguments[0]; - final double radius = arguments[1]; - final Paint paint = arguments[2]; - final Offset expectedCenter = tapDownOffset + const Offset(17.0, 17.0); - const double expectedRadius = 56.0; - if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == 120) - return true; - throw ''' - Expected: center == $expectedCenter, radius == $expectedRadius, alpha == 120 - Found: center == $center radius == $radius alpha == ${paint.color.alpha}'''; - })); + expect(box, ripplePattern(const Offset(17.0, 17.0), 56.0, 120)); // At 75ms the ripple has fade in: it's alpha matches the splashColor's // alpha and its center has moved closer to the ink well's center. await tester.pump(const Duration(milliseconds: 25)); - expect(box, paints..something((Symbol method, List arguments) { - if (method != #drawCircle) - return false; - final Offset center = arguments[0]; - final double radius = arguments[1]; - final Paint paint = arguments[2]; - final Offset expectedCenter = tapDownOffset + const Offset(29.0, 29.0); - const double expectedRadius = 73.0; - if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == 180) - return true; - throw ''' - Expected: center == $expectedCenter, radius == $expectedRadius, alpha == 180 - Found: center == $center radius == $radius alpha == ${paint.color.alpha}'''; - })); + expect(box, ripplePattern(const Offset(29.0, 29.0), 73.0, 180)); // At this point the splash radius has expanded to its limit: 5 past the // ink well's radius parameter. The splash center has moved to its final // location at the inkwell's center and the fade-out is about to start. // The fade-out begins at 225ms = 50ms + 25ms + 150ms. await tester.pump(const Duration(milliseconds: 150)); - expect(box, paints..something((Symbol method, List arguments) { - if (method != #drawCircle) - return false; - final Offset center = arguments[0]; - final double radius = arguments[1]; - final Paint paint = arguments[2]; - final Offset expectedCenter = inkWellCenter; - const double expectedRadius = 105.0; - if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == 180) - return true; - throw ''' - Expected: center == $expectedCenter, radius == $expectedRadius, alpha == 180 - Found: center == $center radius == $radius alpha == ${paint.color.alpha}'''; - })); + expect(box, ripplePattern(inkWellCenter - tapDownOffset, 105.0, 180)); // After another 150ms the fade-out is complete. await tester.pump(const Duration(milliseconds: 150)); - expect(box, paints..something((Symbol method, List arguments) { - if (method != #drawCircle) - return false; - final Offset center = arguments[0]; - final double radius = arguments[1]; - final Paint paint = arguments[2]; - final Offset expectedCenter = inkWellCenter; - const double expectedRadius = 105.0; - if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius) && paint.color.alpha == 0) - return true; - throw ''' - Expected: center == $expectedCenter, radius == $expectedRadius, alpha == 0 - Found: center == $center radius == $radius alpha == ${paint.color.alpha}'''; - })); + expect(box, ripplePattern(inkWellCenter - tapDownOffset, 105.0, 0)); }); testWidgets('Does the Ink widget render anything', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/text_field_splash_test.dart b/packages/flutter/test/material/text_field_splash_test.dart index 9418ae58ee..d0c259c057 100644 --- a/packages/flutter/test/material/text_field_splash_test.dart +++ b/packages/flutter/test/material/text_field_splash_test.dart @@ -19,6 +19,7 @@ class TestInkSplash extends InkSplash { bool containedInkWell = false, RectCallback rectCallback, BorderRadius borderRadius, + ShapeBorder customBorder, double radius, VoidCallback onRemoved, }) : super( @@ -29,6 +30,7 @@ class TestInkSplash extends InkSplash { containedInkWell: containedInkWell, rectCallback: rectCallback, borderRadius: borderRadius, + customBorder: customBorder, radius: radius, onRemoved: onRemoved, ); @@ -58,6 +60,7 @@ class TestInkSplashFactory extends InteractiveInkFeatureFactory { bool containedInkWell = false, RectCallback rectCallback, BorderRadius borderRadius, + ShapeBorder customBorder, double radius, VoidCallback onRemoved, }) { @@ -69,6 +72,7 @@ class TestInkSplashFactory extends InteractiveInkFeatureFactory { containedInkWell: containedInkWell, rectCallback: rectCallback, borderRadius: borderRadius, + customBorder: customBorder, radius: radius, onRemoved: onRemoved, );