diff --git a/examples/widgets/card_collection.dart b/examples/widgets/card_collection.dart index 3ce3f3beba4..a3726b4d1c6 100644 --- a/examples/widgets/card_collection.dart +++ b/examples/widgets/card_collection.dart @@ -393,8 +393,8 @@ class CardCollectionState extends State { ui.Shader _createShader(Rect bounds) { return new LinearGradient( - begin: Point.origin, - end: new Point(0.0, bounds.height), + begin: bounds.topLeft, + end: bounds.bottomLeft, colors: [const Color(0x00FFFFFF), const Color(0xFFFFFFFF)], stops: [0.1, 0.35] ) diff --git a/packages/flutter/lib/src/rendering/layer.dart b/packages/flutter/lib/src/rendering/layer.dart index 0b23599b990..a058413c4d4 100644 --- a/packages/flutter/lib/src/rendering/layer.dart +++ b/packages/flutter/lib/src/rendering/layer.dart @@ -392,3 +392,31 @@ class OpacityLayer extends ContainerLayer { settings.add('alpha: $alpha'); } } + +/// A composited layer that applies a shader to hits children. +class ShaderMaskLayer extends ContainerLayer { + ShaderMaskLayer({ Offset offset: Offset.zero, this.shader, this.maskRect, this.transferMode }) : super(offset: offset); + + /// The shader to apply to the children. + ui.Shader shader; + + /// The size of the shader. + Rect maskRect; + + /// The tranfer mode to apply when blending the shader with the children. + TransferMode transferMode; + + void addToScene(ui.SceneBuilder builder, Offset layerOffset) { + Offset childOffset = offset + layerOffset; + builder.pushShaderMask(shader, maskRect.shift(childOffset), transferMode); + addChildrenToScene(builder, childOffset); + builder.pop(); + } + + void debugDescribeSettings(List settings) { + super.debugDescribeSettings(settings); + settings.add('shader: $shader'); + settings.add('maskRect: $maskRect'); + settings.add('transferMode: $transferMode'); + } +} diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 27cc2ab21ae..c992e0e0ff2 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -18,8 +18,6 @@ import 'node.dart'; export 'layer.dart'; export 'package:flutter/gestures.dart' show HitTestEntry, HitTestResult; -typedef ui.Shader ShaderCallback(Rect bounds); - /// Base class for data associated with a [RenderObject] by its parent. /// /// Some render objects wish to store data on their children, such as their @@ -298,27 +296,21 @@ class PaintingContext { childContext._stopRecordingIfNeeded(); } - static Paint _getPaintForShaderMask(Rect bounds, - ShaderCallback shaderCallback, - TransferMode transferMode) { - return new Paint() - ..transferMode = transferMode - ..shader = shaderCallback(bounds); - } - /// Push a shader mask. /// /// This function will call painter synchronously with a painting context that /// will be masked with the given shader. - /// - /// WARNING: This function does not yet support compositing. - void pushShaderMask(bool needsCompositing, Offset offset, Rect bounds, ShaderCallback shaderCallback, TransferMode transferMode, PaintingContextCallback painter) { - assert(!needsCompositing); // TODO(abarth): Implement compositing for shader masks. - canvas.saveLayer(bounds.shift(offset), _disableAntialias); - painter(this, offset); - Paint shaderPaint = _getPaintForShaderMask(bounds, shaderCallback, transferMode); - canvas.drawRect(bounds, shaderPaint); - canvas.restore(); + void pushShaderMask(Offset offset, ui.Shader shader, Rect maskRect, TransferMode transferMode, PaintingContextCallback painter) { + _stopRecordingIfNeeded(); + ShaderMaskLayer shaderLayer = new ShaderMaskLayer( + shader: shader, + maskRect: maskRect, + transferMode: transferMode + ); + _appendLayer(shaderLayer, offset); + PaintingContext childContext = new PaintingContext._(shaderLayer, _paintBounds); + painter(childContext, Offset.zero); + childContext._stopRecordingIfNeeded(); } } diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index abf1d0eab51..07b1855a12e 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -540,7 +540,7 @@ class RenderOpacity extends RenderProxyBox { assert(opacity >= 0.0 && opacity <= 1.0); } - bool get alwaysNeedsCompositing => _alpha != 0 && _alpha != 255; + bool get alwaysNeedsCompositing => child != null && (_alpha != 0 && _alpha != 255); /// The fraction to scale the child's alpha value. /// @@ -580,6 +580,8 @@ class RenderOpacity extends RenderProxyBox { } } +typedef ui.Shader ShaderCallback(Rect bounds); + class RenderShaderMask extends RenderProxyBox { RenderShaderMask({ RenderBox child, ShaderCallback shaderCallback, TransferMode transferMode }) : _shaderCallback = shaderCallback, _transferMode = transferMode, super(child); @@ -604,9 +606,14 @@ class RenderShaderMask extends RenderProxyBox { markNeedsPaint(); } + bool get alwaysNeedsCompositing => child != null; + void paint(PaintingContext context, Offset offset) { - if (child != null) - context.pushShaderMask(needsCompositing, offset, Point.origin & size, _shaderCallback, _transferMode, super.paint); + if (child != null) { + assert(needsCompositing); + Rect rect = Point.origin & size; + context.pushShaderMask(offset, _shaderCallback(rect), rect, _transferMode, super.paint); + } } } diff --git a/packages/flutter/test/widget/shader_mask_test.dart b/packages/flutter/test/widget/shader_mask_test.dart index 51df395b245..513f57703cc 100644 --- a/packages/flutter/test/widget/shader_mask_test.dart +++ b/packages/flutter/test/widget/shader_mask_test.dart @@ -11,8 +11,8 @@ import 'package:test/test.dart'; ui.Shader createShader(Rect bounds) { return new LinearGradient( - begin: Point.origin, - end: new Point(0.0, bounds.height), + begin: bounds.topLeft, + end: bounds.bottomLeft, colors: [const Color(0x00FFFFFF), const Color(0xFFFFFFFF)], stops: [0.1, 0.35] )