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 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:sky' as sky;
import 'package:sky/animation.dart';
import 'package:sky/material.dart';
import 'package:sky/painting.dart';
......@@ -25,6 +27,9 @@ class CardCollectionAppState extends State<CardCollectionApp> {
static const TextStyle cardLabelStyle =
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 =
Typography.white.title.copyWith(textAlign: TextAlign.center);
......@@ -33,6 +38,7 @@ class CardCollectionAppState extends State<CardCollectionApp> {
bool _snapToCenter = false;
bool _fixedSizeCards = false;
bool _drawerShowing = false;
bool _sunshine = false;
AnimationStatus _drawerStatus = AnimationStatus.dismissed;
InvalidatorCallback _invalidator;
Size _cardCollectionSize = new Size(200.0, 200.0);
......@@ -139,6 +145,12 @@ class CardCollectionAppState extends State<CardCollectionApp> {
});
}
void _toggleSunshine() {
setState(() {
_sunshine = !_sunshine;
});
}
_changeDismissDirection(DismissDirection newDismissDirection) {
setState(() {
_dismissDirection = newDismissDirection;
......@@ -185,6 +197,7 @@ class CardCollectionAppState extends State<CardCollectionApp> {
new DrawerHeader(child: new Text('Options')),
buildDrawerCheckbox("Snap fling scrolls to center", _snapToCenter, _toggleSnapToCenter),
buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards),
buildDrawerCheckbox("Let the sun shine", _sunshine, _toggleSunshine),
new DrawerDivider(),
buildDrawerRadioItem(DismissDirection.horizontal, 'action/code'),
buildDrawerRadioItem(DismissDirection.left, 'navigation/arrow_back'),
......@@ -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 cardCollection;
......@@ -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(
callback: _updateCardCollectionSize,
child: new Container(
......
......@@ -16,6 +16,8 @@ import 'package:vector_math/vector_math_64.dart';
export 'dart:sky' show Point, Offset, Size, Rect, Color, Paint, Path;
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
///
/// Some render objects wish to store data on their children, such as their
......@@ -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
///
/// Do not call directly. This function is visible so that it can be
......
......@@ -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
///
/// Prevents its child from painting outside its bounds.
......
......@@ -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 {
DecoratedBox({
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