Commit c7695b4a authored by Jason Simmons's avatar Jason Simmons Committed by GitHub

Render the Material widget with the physical model layer (#8471)

parent 9a83659d
...@@ -7,7 +7,6 @@ import 'package:flutter/rendering.dart'; ...@@ -7,7 +7,6 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'constants.dart'; import 'constants.dart';
import 'shadows.dart';
import 'theme.dart'; import 'theme.dart';
/// Signature for the callback used by ink effects to obtain the rectangle for the effect. /// Signature for the callback used by ink effects to obtain the rectangle for the effect.
...@@ -205,6 +204,7 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin { ...@@ -205,6 +204,7 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color backgroundColor = _getBackgroundColor(context); Color backgroundColor = _getBackgroundColor(context);
assert(backgroundColor != null || config.type == MaterialType.transparency);
Widget contents = config.child; Widget contents = config.child;
BorderRadius radius = config.borderRadius ?? kMaterialEdges[config.type]; BorderRadius radius = config.borderRadius ?? kMaterialEdges[config.type];
if (contents != null) { if (contents != null) {
...@@ -228,11 +228,28 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin { ...@@ -228,11 +228,28 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
) )
); );
if (config.type == MaterialType.circle) { if (config.type == MaterialType.circle) {
contents = new ClipOval(child: contents); contents = new PhysicalModel(
} else if (kMaterialEdges[config.type] != null) { shape: BoxShape.circle,
contents = new ClipRRect( elevation: config.elevation,
borderRadius: radius, color: backgroundColor,
child: contents child: contents,
);
} else if (config.type == MaterialType.transparency) {
if (radius == null) {
contents = new ClipRect(child: contents);
} else {
contents = new ClipRRect(
borderRadius: radius,
child: contents
);
}
} else {
contents = new PhysicalModel(
shape: BoxShape.rectangle,
borderRadius: radius ?? BorderRadius.zero,
elevation: config.elevation,
color: backgroundColor,
child: contents,
); );
} }
if (config.type != MaterialType.transparency) { if (config.type != MaterialType.transparency) {
...@@ -241,7 +258,6 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin { ...@@ -241,7 +258,6 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
duration: kThemeChangeDuration, duration: kThemeChangeDuration,
decoration: new BoxDecoration( decoration: new BoxDecoration(
borderRadius: radius, borderRadius: radius,
boxShadow: config.elevation == 0 ? null : kElevationToShadow[config.elevation],
shape: config.type == MaterialType.circle ? BoxShape.circle : BoxShape.rectangle shape: config.type == MaterialType.circle ? BoxShape.circle : BoxShape.rectangle
), ),
child: new Container( child: new Container(
......
...@@ -521,3 +521,45 @@ class BackdropFilterLayer extends ContainerLayer { ...@@ -521,3 +521,45 @@ class BackdropFilterLayer extends ContainerLayer {
builder.pop(); builder.pop();
} }
} }
class PhysicalModelLayer extends ContainerLayer {
/// Creates a layer with a rounded-rectangular clip.
///
/// The [clipRRect] property must be non-null before the compositing phase of
/// the pipeline.
PhysicalModelLayer({
@required this.clipRRect,
@required this.elevation,
@required this.color,
}) {
assert(clipRRect != null);
assert(elevation != null);
assert(color != null);
}
/// The rounded-rect to clip in the parent's coordinate system
RRect clipRRect;
/// The z-coordinate at which to place this physical object.
int elevation;
/// The background color.
Color color;
@override
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
builder.pushPhysicalModel(
rrect: clipRRect.shift(layerOffset),
elevation: elevation,
color: color,
);
addChildrenToScene(builder, layerOffset);
builder.pop();
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('clipRRect: $clipRRect');
}
}
...@@ -429,6 +429,33 @@ class PaintingContext { ...@@ -429,6 +429,33 @@ class PaintingContext {
painter(childContext, offset); painter(childContext, offset);
childContext._stopRecordingIfNeeded(); childContext._stopRecordingIfNeeded();
} }
/// Clip using a physical model layer.
///
/// * `offset` is the offset from the origin of the canvas' coordinate system
/// to the origin of the caller's coordinate system.
/// * `bounds` is the region of the canvas (in the caller's coodinate system)
/// into which `painter` will paint in.
/// * `clipRRect` is the rounded-rectangle (in the caller's coodinate system)
/// to use to clip the painting done by `painter`.
/// * `elevation` is the z-coordinate at which to place this material.
/// * `color` is the background color.
/// * `painter` is a callback that will paint with the `clipRRect` applied. This
/// function calls the `painter` synchronously.
void pushPhysicalModel(Offset offset, Rect bounds, RRect clipRRect, int elevation, Color color, PaintingContextCallback painter) {
final Rect offsetBounds = bounds.shift(offset);
final RRect offsetClipRRect = clipRRect.shift(offset);
_stopRecordingIfNeeded();
final PhysicalModelLayer physicalModel = new PhysicalModelLayer(
clipRRect: offsetClipRRect,
elevation: elevation,
color: color,
);
_appendLayer(physicalModel);
final PaintingContext childContext = new PaintingContext._(physicalModel, offsetBounds);
painter(childContext, offset);
childContext._stopRecordingIfNeeded();
}
} }
/// An abstract set of layout constraints. /// An abstract set of layout constraints.
......
...@@ -1181,6 +1181,108 @@ class RenderClipPath extends _RenderCustomClip<Path> { ...@@ -1181,6 +1181,108 @@ class RenderClipPath extends _RenderCustomClip<Path> {
} }
} }
/// Creates a physical model layer that clips its children to a rounded
/// rectangle.
class RenderPhysicalModel extends _RenderCustomClip<RRect> {
/// Creates a rounded-rectangular clip.
///
/// The [borderRadius] defaults to [BorderRadius.zero], i.e. a rectangle with
/// right-angled corners.
RenderPhysicalModel({
RenderBox child,
BoxShape shape,
BorderRadius borderRadius: BorderRadius.zero,
int elevation,
Color color,
}) : _shape = shape,
_borderRadius = borderRadius,
_elevation = elevation,
_color = color,
super(child: child) {
if (shape == BoxShape.rectangle)
assert(_borderRadius != null);
}
@override
bool get alwaysNeedsCompositing => true;
/// The shape of the layer.
BoxShape get shape => _shape;
BoxShape _shape;
set shape (BoxShape value) {
assert(value != null);
if (_shape == value)
return;
_shape = value;
_markNeedsClip();
}
/// The border radius of the rounded corners.
///
/// Values are clamped so that horizontal and vertical radii sums do not
/// exceed width/height.
BorderRadius get borderRadius => _borderRadius;
BorderRadius _borderRadius;
set borderRadius (BorderRadius value) {
assert(value != null);
if (_borderRadius == value)
return;
_borderRadius = value;
_markNeedsClip();
}
/// The z-coordinate at which to place this material.
int get elevation => _elevation;
int _elevation;
set elevation (int value) {
assert(value != null);
if (_elevation == value)
return;
_elevation = value;
markNeedsPaint();
}
/// The background color.
Color get color => _color;
Color _color;
set color (Color value) {
assert(value != null);
if (_color == value)
return;
_color = value;
markNeedsPaint();
}
@override
RRect get _defaultClip {
if (_shape == BoxShape.rectangle) {
return _borderRadius.toRRect(Point.origin & size);
} else {
Rect rect = Point.origin & size;
return new RRect.fromRectXY(rect, rect.width / 2, rect.height / 2);
}
}
@override
bool hitTest(HitTestResult result, { Point position }) {
if (_clipper != null) {
_updateClip();
assert(_clip != null);
if (!_clip.contains(position))
return false;
}
return super.hitTest(result, position: position);
}
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
_updateClip();
context.pushPhysicalModel(offset, _clip.outerRect, _clip, _elevation, _color, super.paint);
}
}
}
/// Where to paint a box decoration. /// Where to paint a box decoration.
enum DecorationPosition { enum DecorationPosition {
/// Paint the box decoration behind the children. /// Paint the box decoration behind the children.
......
...@@ -398,6 +398,51 @@ class ClipPath extends SingleChildRenderObjectWidget { ...@@ -398,6 +398,51 @@ class ClipPath extends SingleChildRenderObjectWidget {
} }
} }
/// A widget representing a physical layer that clips its children to a shape.
class PhysicalModel extends SingleChildRenderObjectWidget {
/// Creates a physical model with a rounded-rectangular clip.
PhysicalModel({
Key key,
@required this.shape,
this.borderRadius: BorderRadius.zero,
@required this.elevation,
@required this.color,
Widget child,
}) : super(key: key, child: child) {
if (shape == BoxShape.rectangle)
assert(borderRadius != null);
assert(shape != null);
assert(elevation != null);
assert(color != null);
}
/// The type of shape.
final BoxShape shape;
/// The border radius of the rounded corners.
///
/// Values are clamped so that horizontal and vertical radii sums do not
/// exceed width/height.
final BorderRadius borderRadius;
/// The z-coordinate at which to place this physical object.
final int elevation;
/// The background color.
final Color color;
@override
RenderPhysicalModel createRenderObject(BuildContext context) => new RenderPhysicalModel(shape: shape, borderRadius: borderRadius, elevation: elevation, color: color);
@override
void updateRenderObject(BuildContext context, RenderPhysicalModel renderObject) {
renderObject
..shape = shape
..borderRadius = borderRadius
..elevation = elevation
..color = color;
}
}
// POSITIONING AND SIZING NODES // POSITIONING AND SIZING NODES
......
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