Commit 4de0a99b authored by Hans Muller's avatar Hans Muller

ShaderMask

The ShaderMask widget enables rendering its child with an alpha channel defined by a Shader. For example if the Shader was a linear gradient in alpha then the component behind the ShaderMask's child would appear wherever the gradient's alpha value was not fully opaque.

The card_collection.dart example demonstrates this. Select the "Let the sun shine" checkbox in the app's drawer.
parent 274d2986
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:sky' as sky;
import 'package:sky/animation.dart'; import 'package:sky/animation.dart';
import 'package:sky/material.dart'; import 'package:sky/material.dart';
import 'package:sky/painting.dart'; import 'package:sky/painting.dart';
...@@ -25,6 +27,9 @@ class CardCollectionAppState extends State<CardCollectionApp> { ...@@ -25,6 +27,9 @@ class CardCollectionAppState extends State<CardCollectionApp> {
static const TextStyle cardLabelStyle = static const TextStyle cardLabelStyle =
const TextStyle(color: Colors.white, fontSize: 18.0, fontWeight: bold); const TextStyle(color: Colors.white, fontSize: 18.0, fontWeight: bold);
// TODO(hansmuller): need a local image asset
static const _sunshineURL = "http://www.walltor.com/images/wallpaper/good-morning-sunshine-58540.jpg";
final TextStyle backgroundTextStyle = final TextStyle backgroundTextStyle =
Typography.white.title.copyWith(textAlign: TextAlign.center); Typography.white.title.copyWith(textAlign: TextAlign.center);
...@@ -33,6 +38,7 @@ class CardCollectionAppState extends State<CardCollectionApp> { ...@@ -33,6 +38,7 @@ class CardCollectionAppState extends State<CardCollectionApp> {
bool _snapToCenter = false; bool _snapToCenter = false;
bool _fixedSizeCards = false; bool _fixedSizeCards = false;
bool _drawerShowing = false; bool _drawerShowing = false;
bool _sunshine = false;
AnimationStatus _drawerStatus = AnimationStatus.dismissed; AnimationStatus _drawerStatus = AnimationStatus.dismissed;
InvalidatorCallback _invalidator; InvalidatorCallback _invalidator;
Size _cardCollectionSize = new Size(200.0, 200.0); Size _cardCollectionSize = new Size(200.0, 200.0);
...@@ -139,6 +145,12 @@ class CardCollectionAppState extends State<CardCollectionApp> { ...@@ -139,6 +145,12 @@ class CardCollectionAppState extends State<CardCollectionApp> {
}); });
} }
void _toggleSunshine() {
setState(() {
_sunshine = !_sunshine;
});
}
_changeDismissDirection(DismissDirection newDismissDirection) { _changeDismissDirection(DismissDirection newDismissDirection) {
setState(() { setState(() {
_dismissDirection = newDismissDirection; _dismissDirection = newDismissDirection;
...@@ -185,6 +197,7 @@ class CardCollectionAppState extends State<CardCollectionApp> { ...@@ -185,6 +197,7 @@ class CardCollectionAppState extends State<CardCollectionApp> {
new DrawerHeader(child: new Text('Options')), new DrawerHeader(child: new Text('Options')),
buildDrawerCheckbox("Snap fling scrolls to center", _snapToCenter, _toggleSnapToCenter), buildDrawerCheckbox("Snap fling scrolls to center", _snapToCenter, _toggleSnapToCenter),
buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards), buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards),
buildDrawerCheckbox("Let the sun shine", _sunshine, _toggleSunshine),
new DrawerDivider(), new DrawerDivider(),
buildDrawerRadioItem(DismissDirection.horizontal, 'action/code'), buildDrawerRadioItem(DismissDirection.horizontal, 'action/code'),
buildDrawerRadioItem(DismissDirection.left, 'navigation/arrow_back'), buildDrawerRadioItem(DismissDirection.left, 'navigation/arrow_back'),
...@@ -285,6 +298,16 @@ class CardCollectionAppState extends State<CardCollectionApp> { ...@@ -285,6 +298,16 @@ class CardCollectionAppState extends State<CardCollectionApp> {
}); });
} }
sky.Shader _createShader(Rect bounds) {
return new LinearGradient(
begin: Point.origin,
end: new Point(0.0, bounds.height),
colors: [const Color(0x00FFFFFF), const Color(0xFFFFFFFF)],
stops: [0.1, 0.35]
)
.createShader();
}
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget cardCollection; Widget cardCollection;
...@@ -306,6 +329,12 @@ class CardCollectionAppState extends State<CardCollectionApp> { ...@@ -306,6 +329,12 @@ class CardCollectionAppState extends State<CardCollectionApp> {
); );
} }
if (_sunshine)
cardCollection = new Stack([
new Column([new NetworkImage(src: _sunshineURL)]),
new ShaderMask(child: cardCollection, shaderCallback: _createShader)
]);
Widget body = new SizeObserver( Widget body = new SizeObserver(
callback: _updateCardCollectionSize, callback: _updateCardCollectionSize,
child: new Container( child: new Container(
......
...@@ -16,6 +16,8 @@ import 'package:vector_math/vector_math_64.dart'; ...@@ -16,6 +16,8 @@ import 'package:vector_math/vector_math_64.dart';
export 'dart:sky' show Point, Offset, Size, Rect, Color, Paint, Path; export 'dart:sky' show Point, Offset, Size, Rect, Color, Paint, Path;
export 'package:sky/src/rendering/hit_test.dart' show HitTestTarget, HitTestEntry, HitTestResult; export 'package:sky/src/rendering/hit_test.dart' show HitTestTarget, HitTestEntry, HitTestResult;
typedef sky.Shader ShaderCallback(Rect bounds);
/// Base class for data associated with a [RenderObject] by its parent /// Base class for data associated with a [RenderObject] by its parent
/// ///
/// Some render objects wish to store data on their children, such as their /// Some render objects wish to store data on their children, such as their
...@@ -300,6 +302,34 @@ class PaintingContext { ...@@ -300,6 +302,34 @@ class PaintingContext {
} }
} }
static Paint _getPaintForShaderMask(Rect bounds,
ShaderCallback shaderCallback,
sky.TransferMode transferMode) {
return new Paint()
..transferMode = transferMode
..shader = shaderCallback(bounds);
}
void paintChildWithShaderMask(RenderObject child,
Point childPosition,
Rect bounds,
ShaderCallback shaderCallback,
sky.TransferMode transferMode) {
assert(debugCanPaintChild(child));
final Offset childOffset = childPosition.toOffset();
if (!child.needsCompositing) {
canvas.saveLayer(bounds, new Paint());
canvas.translate(childOffset.dx, childOffset.dy);
insertChild(child, Offset.zero);
Paint shaderPaint = _getPaintForShaderMask(bounds, shaderCallback, transferMode);
canvas.drawRect(Offset.zero & new Size(bounds.width, bounds.height), shaderPaint);
canvas.restore();
} else {
// TODO(hansmuller) support compositing ShaderMasks
assert('Support for compositing ShaderMasks is TBD' is String);
}
}
/// Instructs the child to draw itself onto this context at the given offset /// Instructs the child to draw itself onto this context at the given offset
/// ///
/// Do not call directly. This function is visible so that it can be /// Do not call directly. This function is visible so that it can be
......
...@@ -659,6 +659,37 @@ class RenderColorFilter extends RenderProxyBox { ...@@ -659,6 +659,37 @@ class RenderColorFilter extends RenderProxyBox {
} }
} }
class RenderShaderMask extends RenderProxyBox {
RenderShaderMask({ RenderBox child, ShaderCallback shaderCallback, sky.TransferMode transferMode })
: _shaderCallback = shaderCallback, _transferMode = transferMode, super(child) {
}
ShaderCallback get shaderCallback => _shaderCallback;
ShaderCallback _shaderCallback;
void set shaderCallback (ShaderCallback newShaderCallback) {
assert(newShaderCallback != null);
if (_shaderCallback == newShaderCallback)
return;
_shaderCallback = newShaderCallback;
markNeedsPaint();
}
sky.TransferMode get transferMode => _transferMode;
sky.TransferMode _transferMode;
void set transferMode (sky.TransferMode newTransferMode) {
assert(newTransferMode != null);
if (_transferMode == newTransferMode)
return;
_transferMode = newTransferMode;
markNeedsPaint();
}
void paint(PaintingContext context, Offset offset) {
if (child != null)
context.paintChildWithShaderMask(child, offset.toPoint(), offset & size, _shaderCallback, _transferMode);
}
}
/// Clips its child using a rectangle /// Clips its child using a rectangle
/// ///
/// Prevents its child from painting outside its bounds. /// Prevents its child from painting outside its bounds.
......
...@@ -70,6 +70,33 @@ class ColorFilter extends OneChildRenderObjectWidget { ...@@ -70,6 +70,33 @@ class ColorFilter extends OneChildRenderObjectWidget {
} }
} }
class ShaderMask extends OneChildRenderObjectWidget {
ShaderMask({
Key key,
this.shaderCallback,
this.transferMode: sky.TransferMode.modulate,
Widget child
}) : super(key: key, child: child) {
assert(shaderCallback != null);
assert(transferMode != null);
}
final ShaderCallback shaderCallback;
final sky.TransferMode transferMode;
RenderShaderMask createRenderObject() {
return new RenderShaderMask(
shaderCallback: shaderCallback,
transferMode: transferMode
);
}
void updateRenderObject(RenderShaderMask renderObject, ShaderMask oldWidget) {
renderObject.shaderCallback = shaderCallback;
renderObject.transferMode = transferMode;
}
}
class DecoratedBox extends OneChildRenderObjectWidget { class DecoratedBox extends OneChildRenderObjectWidget {
DecoratedBox({ DecoratedBox({
Key key, Key key,
......
import 'dart:sky' as sky;
import 'package:sky/painting.dart';
import 'package:sky/widgets.dart';
import 'package:test/test.dart';
import 'widget_tester.dart';
sky.Shader createShader(Rect bounds) {
return new LinearGradient(
begin: Point.origin,
end: new Point(0.0, bounds.height),
colors: [const Color(0x00FFFFFF), const Color(0xFFFFFFFF)],
stops: [0.1, 0.35]
)
.createShader();
}
void main() {
test('Can be constructed', () {
testWidgets((WidgetTester tester) {
Widget child = new Container(width: 100.0, height: 100.0);
tester.pumpWidget(new ShaderMask(child: child, shaderCallback: createShader));
});
});
}
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