diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 64e6f5760b..76c84d9ddf 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -1580,8 +1580,12 @@ abstract class _RenderPhysicalModelBase extends _RenderCustomClip { markNeedsPaint(); } + static final Paint _transparentPaint = Paint()..color = const Color(0x00000000); + + // On Fuchsia, the system compositor is responsible for drawing shadows + // for physical model layers with non-zero elevation. @override - bool get alwaysNeedsCompositing => _elevation != 0.0; + bool get alwaysNeedsCompositing => _elevation != 0.0 && defaultTargetPlatform == TargetPlatform.fuchsia; @override void describeSemanticsConfiguration(SemanticsConfiguration config) { @@ -1711,14 +1715,37 @@ class RenderPhysicalModel extends _RenderPhysicalModelBase { } return true; }()); - final PhysicalModelLayer physicalModel = PhysicalModelLayer( - clipPath: offsetRRectAsPath, - clipBehavior: clipBehavior, - elevation: paintShadows ? elevation : 0.0, - color: color, - shadowColor: shadowColor, - ); - context.pushLayer(physicalModel, super.paint, offset, childPaintBounds: offsetBounds); + if (needsCompositing) { + final PhysicalModelLayer physicalModel = PhysicalModelLayer( + clipPath: offsetRRectAsPath, + clipBehavior: clipBehavior, + elevation: paintShadows ? elevation : 0.0, + color: color, + shadowColor: shadowColor, + ); + context.pushLayer(physicalModel, super.paint, offset, childPaintBounds: offsetBounds); + } else { + final Canvas canvas = context.canvas; + if (elevation != 0.0 && paintShadows) { + // The drawShadow call doesn't add the region of the shadow to the + // picture's bounds, so we draw a hardcoded amount of extra space to + // account for the maximum potential area of the shadow. + // TODO(jsimmons): remove this when Skia does it for us. + canvas.drawRect( + offsetBounds.inflate(20.0), + _RenderPhysicalModelBase._transparentPaint, + ); + canvas.drawShadow( + offsetRRectAsPath, + shadowColor, + elevation, + color.alpha != 0xFF, + ); + } + canvas.drawRRect(offsetRRect, Paint()..color = color); + context.clipRRectAndPaint(offsetRRect, clipBehavior, offsetBounds, () => super.paint(context, offset)); + assert(context.canvas == canvas, 'canvas changed even though needsCompositing was false'); + } } } @@ -1801,14 +1828,37 @@ class RenderPhysicalShape extends _RenderPhysicalModelBase { } return true; }()); - final PhysicalModelLayer physicalModel = PhysicalModelLayer( - clipPath: offsetPath, - clipBehavior: clipBehavior, - elevation: paintShadows ? elevation : 0.0, - color: color, - shadowColor: shadowColor, - ); - context.pushLayer(physicalModel, super.paint, offset, childPaintBounds: offsetBounds); + if (needsCompositing) { + final PhysicalModelLayer physicalModel = PhysicalModelLayer( + clipPath: offsetPath, + clipBehavior: clipBehavior, + elevation: paintShadows ? elevation : 0.0, + color: color, + shadowColor: shadowColor, + ); + context.pushLayer(physicalModel, super.paint, offset, childPaintBounds: offsetBounds); + } else { + final Canvas canvas = context.canvas; + if (elevation != 0.0 && paintShadows) { + // The drawShadow call doesn't add the region of the shadow to the + // picture's bounds, so we draw a hardcoded amount of extra space to + // account for the maximum potential area of the shadow. + // TODO(jsimmons): remove this when Skia does it for us. + canvas.drawRect( + offsetBounds.inflate(20.0), + _RenderPhysicalModelBase._transparentPaint, + ); + canvas.drawShadow( + offsetPath, + shadowColor, + elevation, + color.alpha != 0xFF, + ); + } + canvas.drawPath(offsetPath, Paint()..color = color..style = PaintingStyle.fill); + context.clipPathAndPaint(offsetPath, clipBehavior, offsetBounds, () => super.paint(context, offset)); + assert(context.canvas == canvas, 'canvas changed even though needsCompositing was false'); + } } } diff --git a/packages/flutter/test/material/bottom_app_bar_test.dart b/packages/flutter/test/material/bottom_app_bar_test.dart index 92929cfe40..5455c4f0a6 100644 --- a/packages/flutter/test/material/bottom_app_bar_test.dart +++ b/packages/flutter/test/material/bottom_app_bar_test.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:io'; - import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -81,7 +79,7 @@ void main() { find.byKey(key), matchesGoldenFile('bottom_app_bar.custom_shape.2.png'), ); - }, skip: !Platform.isLinux); + }); testWidgets('color defaults to Theme.bottomAppBarColor', (WidgetTester tester) async { await tester.pumpWidget( diff --git a/packages/flutter/test/material/outline_button_test.dart b/packages/flutter/test/material/outline_button_test.dart index 6b253198c0..dedfc571fe 100644 --- a/packages/flutter/test/material/outline_button_test.dart +++ b/packages/flutter/test/material/outline_button_test.dart @@ -10,27 +10,6 @@ import '../rendering/mock_canvas.dart'; import '../widgets/semantics_tester.dart'; void main() { - PhysicalModelLayer findPhysicalLayer(Element element) { - expect(element, isNotNull); - RenderObject object = element.renderObject; - while (object != null && object is! RenderRepaintBoundary && object is! RenderView) { - object = object.parent; - } - expect(object.debugLayer, isNotNull); - expect(object.debugLayer.firstChild, isInstanceOf()); - final PhysicalModelLayer layer = object.debugLayer.firstChild; - return layer.firstChild is PhysicalModelLayer ? layer.firstChild : layer; - } - - void checkPhysicalLayer(Element element, Color expectedColor, {Path clipPath, Rect clipRect}) { - final PhysicalModelLayer expectedLayer = findPhysicalLayer(element); - expect(expectedLayer.elevation, 0.0); - expect(expectedLayer.color, expectedColor); - if (clipPath != null) { - expect(clipRect, isNotNull); - expect(expectedLayer.clipPath, coversSameAreaAs(clipPath, areaToCompare: clipRect.inflate(10.0))); - } - } testWidgets('Outline button responds to tap when enabled', (WidgetTester tester) async { int pressedCount = 0; @@ -135,13 +114,8 @@ void main() { expect( outlineButton, paints + ..clipPath(pathMatcher: coversSameAreaAs(clipPath, areaToCompare: clipRect.inflate(10.0))) ..path(color: disabledBorderColor, strokeWidth: borderWidth)); - checkPhysicalLayer( - tester.element(outlineButton), - const Color(0), - clipPath: clipPath, - clipRect: clipRect, - ); // Pump a new button with a no-op onPressed callback to make it enabled. await tester.pumpWidget( @@ -156,14 +130,10 @@ void main() { expect( outlineButton, paints + // initially the interior of the button is transparent + ..path(color: fillColor.withAlpha(0x00)) + ..clipPath(pathMatcher: coversSameAreaAs(clipPath, areaToCompare: clipRect.inflate(10.0))) ..path(color: borderColor, strokeWidth: borderWidth)); - // initially, the interior of the button is transparent - checkPhysicalLayer( - tester.element(outlineButton), - fillColor.withAlpha(0x00), - clipPath: clipPath, - clipRect: clipRect, - ); final Offset center = tester.getCenter(outlineButton); final TestGesture gesture = await tester.startGesture(center); @@ -174,13 +144,9 @@ void main() { expect( outlineButton, paints + ..path(color: fillColor.withAlpha(0xFF)) + ..clipPath(pathMatcher: coversSameAreaAs(clipPath, areaToCompare: clipRect.inflate(10.0))) ..path(color: highlightedBorderColor, strokeWidth: borderWidth)); - checkPhysicalLayer( - tester.element(outlineButton), - fillColor.withAlpha(0xFF), - clipPath: clipPath, - clipRect: clipRect, - ); // Tap gesture completes, button returns to its initial configuration. await gesture.up(); @@ -188,13 +154,9 @@ void main() { expect( outlineButton, paints + ..path(color: fillColor.withAlpha(0x00)) + ..clipPath(pathMatcher: coversSameAreaAs(clipPath, areaToCompare: clipRect.inflate(10.0))) ..path(color: borderColor, strokeWidth: borderWidth)); - checkPhysicalLayer( - tester.element(outlineButton), - fillColor.withAlpha(0x00), - clipPath: clipPath, - clipRect: clipRect, - ); }); testWidgets('OutlineButton has no clip by default', (WidgetTester tester) async { @@ -260,6 +222,7 @@ void main() { semantics.dispose(); }); + testWidgets('OutlineButton scales textScaleFactor', (WidgetTester tester) async { await tester.pumpWidget( Directionality( @@ -377,7 +340,6 @@ void main() { await tester.pumpWidget(buildFrame(ThemeData.dark())); final Finder button = find.byType(OutlineButton); - final Element buttonElement = tester.element(button); final Offset center = tester.getCenter(button); // Default value for dark Theme.of(context).canvasColor as well as @@ -385,18 +347,18 @@ void main() { Color fillColor = Colors.grey[850]; // Initially the interior of the button is transparent. - checkPhysicalLayer(buttonElement, fillColor.withAlpha(0x00)); + expect(button, paints..path(color: fillColor.withAlpha(0x00))); // Tap-press gesture on the button triggers the fill animation. TestGesture gesture = await tester.startGesture(center); await tester.pump(); // Start the button fill animation. await tester.pump(const Duration(milliseconds: 200)); // Animation is complete. - checkPhysicalLayer(buttonElement, fillColor.withAlpha(0xFF)); + expect(button, paints..path(color: fillColor.withAlpha(0xFF))); // Tap gesture completes, button returns to its initial configuration. await gesture.up(); await tester.pumpAndSettle(); - checkPhysicalLayer(buttonElement, fillColor.withAlpha(0x00)); + expect(button, paints..path(color: fillColor.withAlpha(0x00))); await tester.pumpWidget(buildFrame(ThemeData.light())); await tester.pumpAndSettle(); // Finish the theme change animation. @@ -406,17 +368,17 @@ void main() { fillColor = Colors.grey[50]; // Initially the interior of the button is transparent. - // expect(button, paints..path(color: fillColor.withAlpha(0x00))); + expect(button, paints..path(color: fillColor.withAlpha(0x00))); // Tap-press gesture on the button triggers the fill animation. gesture = await tester.startGesture(center); await tester.pump(); // Start the button fill animation. await tester.pump(const Duration(milliseconds: 200)); // Animation is complete. - checkPhysicalLayer(buttonElement, fillColor.withAlpha(0xFF)); + expect(button, paints..path(color: fillColor.withAlpha(0xFF))); // Tap gesture completes, button returns to its initial configuration. await gesture.up(); await tester.pumpAndSettle(); - checkPhysicalLayer(buttonElement, fillColor.withAlpha(0x00)); + expect(button, paints..path(color: fillColor.withAlpha(0x00))); }); } diff --git a/packages/flutter/test/rendering/proxy_box_test.dart b/packages/flutter/test/rendering/proxy_box_test.dart index f79b412600..dd19dc79fa 100644 --- a/packages/flutter/test/rendering/proxy_box_test.dart +++ b/packages/flutter/test/rendering/proxy_box_test.dart @@ -64,10 +64,10 @@ void main() { layout(root, phase: EnginePhase.composite); expect(root.needsCompositing, isFalse); - // Flutter now composites physical shapes on all platforms. + // On non-Fuchsia platforms, Flutter draws its own shadows. root.elevation = 1.0; pumpFrame(phase: EnginePhase.composite); - expect(root.needsCompositing, isTrue); + expect(root.needsCompositing, isFalse); root.elevation = 0.0; pumpFrame(phase: EnginePhase.composite); @@ -127,10 +127,10 @@ void main() { layout(root, phase: EnginePhase.composite); expect(root.needsCompositing, isFalse); - // On non-Fuchsia platforms, we composite physical shape layers + // On non-Fuchsia platforms, Flutter draws its own shadows. root.elevation = 1.0; pumpFrame(phase: EnginePhase.composite); - expect(root.needsCompositing, isTrue); + expect(root.needsCompositing, isFalse); root.elevation = 0.0; pumpFrame(phase: EnginePhase.composite); diff --git a/packages/flutter/test/widgets/nested_scroll_view_test.dart b/packages/flutter/test/widgets/nested_scroll_view_test.dart index c61725b87d..1d45aa7337 100644 --- a/packages/flutter/test/widgets/nested_scroll_view_test.dart +++ b/packages/flutter/test/widgets/nested_scroll_view_test.dart @@ -6,7 +6,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/gestures.dart' show DragStartBehavior; -import 'package:flutter/rendering.dart'; + +import '../rendering/mock_canvas.dart'; class _CustomPhysics extends ClampingScrollPhysics { const _CustomPhysics({ ScrollPhysics parent }) : super(parent: parent); @@ -468,38 +469,12 @@ void main() { // END )), ); - - PhysicalModelLayer _dfsFindPhysicalLayer(ContainerLayer layer) { - expect(layer, isNotNull); - Layer child = layer.firstChild; - while (child != null) { - if (child is PhysicalModelLayer) { - return child; - } - if (child is ContainerLayer) { - final PhysicalModelLayer candidate = _dfsFindPhysicalLayer(child); - if (candidate != null) { - return candidate; - } - } - child = child.nextSibling; - } - return null; - } - - final ContainerLayer nestedScrollViewLayer = find.byType(NestedScrollView).evaluate().first.renderObject.debugLayer; - void _checkPhysicalLayer({@required double elevation}) { - final PhysicalModelLayer layer = _dfsFindPhysicalLayer(nestedScrollViewLayer); - expect(layer, isNotNull); - expect(layer.elevation, equals(elevation)); - } - int expectedBuildCount = 0; expectedBuildCount += 1; expect(buildCount, expectedBuildCount); expect(find.text('Item 2'), findsOneWidget); expect(find.text('Item 18'), findsNothing); - _checkPhysicalLayer(elevation: 0); + expect(find.byType(NestedScrollView), isNot(paints..shadow())); // scroll down final TestGesture gesture0 = await tester.startGesture(tester.getCenter(find.text('Item 2'))); await gesture0.moveBy(const Offset(0.0, -120.0)); // tiny bit more than the pinned app bar height (56px * 2) @@ -513,22 +488,22 @@ void main() { expect(buildCount, expectedBuildCount); await tester.pump(const Duration(milliseconds: 1)); // during shadow animation expect(buildCount, expectedBuildCount); - _checkPhysicalLayer(elevation: 0.00018262863159179688); + expect(find.byType(NestedScrollView), paints..shadow()); await tester.pump(const Duration(seconds: 1)); // end shadow animation expect(buildCount, expectedBuildCount); - _checkPhysicalLayer(elevation: 4); + expect(find.byType(NestedScrollView), paints..shadow()); // scroll down final TestGesture gesture1 = await tester.startGesture(tester.getCenter(find.text('Item 2'))); await gesture1.moveBy(const Offset(0.0, -800.0)); await tester.pump(); expect(buildCount, expectedBuildCount); - _checkPhysicalLayer(elevation: 4); + expect(find.byType(NestedScrollView), paints..shadow()); expect(find.text('Item 2'), findsNothing); expect(find.text('Item 18'), findsOneWidget); await gesture1.up(); await tester.pump(const Duration(seconds: 1)); expect(buildCount, expectedBuildCount); - _checkPhysicalLayer(elevation: 4); + expect(find.byType(NestedScrollView), paints..shadow()); // swipe left to bring in tap on the right final TestGesture gesture2 = await tester.startGesture(tester.getCenter(find.byType(NestedScrollView))); await gesture2.moveBy(const Offset(-400.0, 0.0)); @@ -539,7 +514,7 @@ void main() { expect(find.text('Item 0'), findsOneWidget); expect(tester.getTopLeft(find.ancestor(of: find.text('Item 0'), matching: find.byType(ListTile))).dy, tester.getBottomLeft(find.byType(AppBar)).dy + 8.0); - _checkPhysicalLayer(elevation: 4); + expect(find.byType(NestedScrollView), paints..shadow()); await gesture2.up(); await tester.pump(); // start sideways scroll await tester.pump(const Duration(seconds: 1)); // end sideways scroll, triggers shadow going away @@ -551,7 +526,7 @@ void main() { expect(buildCount, expectedBuildCount); expect(find.text('Item 18'), findsNothing); expect(find.text('Item 2'), findsOneWidget); - _checkPhysicalLayer(elevation: 0); + expect(find.byType(NestedScrollView), isNot(paints..shadow())); await tester.pump(const Duration(seconds: 1)); // just checking we don't rebuild... expect(buildCount, expectedBuildCount); // peek left to see it's still in the right place @@ -564,10 +539,10 @@ void main() { expect(buildCount, expectedBuildCount); expect(find.text('Item 18'), findsOneWidget); expect(find.text('Item 2'), findsOneWidget); - _checkPhysicalLayer(elevation: 0); + expect(find.byType(NestedScrollView), isNot(paints..shadow())); await tester.pump(const Duration(seconds: 1)); // shadow finishes coming back expect(buildCount, expectedBuildCount); - _checkPhysicalLayer(elevation: 4); + expect(find.byType(NestedScrollView), paints..shadow()); await gesture3.moveBy(const Offset(-400.0, 0.0)); await gesture3.up(); await tester.pump(); // left tab view goes away @@ -575,10 +550,10 @@ void main() { await tester.pump(); // shadow goes away starting here expectedBuildCount += 1; expect(buildCount, expectedBuildCount); - _checkPhysicalLayer(elevation: 4); + expect(find.byType(NestedScrollView), paints..shadow()); await tester.pump(const Duration(seconds: 1)); // shadow finishes going away expect(buildCount, expectedBuildCount); - _checkPhysicalLayer(elevation: 0); + expect(find.byType(NestedScrollView), isNot(paints..shadow())); // scroll back up final TestGesture gesture4 = await tester.startGesture(tester.getCenter(find.byType(NestedScrollView))); await gesture4.moveBy(const Offset(0.0, 200.0)); // expands the appbar again @@ -586,11 +561,11 @@ void main() { expect(buildCount, expectedBuildCount); expect(find.text('Item 2'), findsOneWidget); expect(find.text('Item 18'), findsNothing); - _checkPhysicalLayer(elevation: 0); + expect(find.byType(NestedScrollView), isNot(paints..shadow())); await gesture4.up(); await tester.pump(const Duration(seconds: 1)); expect(buildCount, expectedBuildCount); - _checkPhysicalLayer(elevation: 0); + expect(find.byType(NestedScrollView), isNot(paints..shadow())); // peek left to see it's now back at zero final TestGesture gesture5 = await tester.startGesture(tester.getCenter(find.byType(NestedScrollView))); await gesture5.moveBy(const Offset(400.0, 0.0)); @@ -599,14 +574,14 @@ void main() { expect(buildCount, expectedBuildCount); expect(find.text('Item 18'), findsNothing); expect(find.text('Item 2'), findsNWidgets(2)); - _checkPhysicalLayer(elevation: 0); + expect(find.byType(NestedScrollView), isNot(paints..shadow())); await tester.pump(const Duration(seconds: 1)); // shadow would be finished coming back - _checkPhysicalLayer(elevation: 0); + expect(find.byType(NestedScrollView), isNot(paints..shadow())); await gesture5.up(); await tester.pump(); // right tab view goes away await tester.pumpAndSettle(); expect(buildCount, expectedBuildCount); - _checkPhysicalLayer(elevation: 0); + expect(find.byType(NestedScrollView), isNot(paints..shadow())); debugDisableShadows = true; });