From c7695b4ad48c8e019250c99f24af4c159f0b1439 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Tue, 28 Feb 2017 15:50:10 -0800 Subject: [PATCH] Render the Material widget with the physical model layer (#8471) --- .../flutter/lib/src/material/material.dart | 30 ++++-- packages/flutter/lib/src/rendering/layer.dart | 42 ++++++++ .../flutter/lib/src/rendering/object.dart | 27 +++++ .../flutter/lib/src/rendering/proxy_box.dart | 102 ++++++++++++++++++ packages/flutter/lib/src/widgets/basic.dart | 45 ++++++++ 5 files changed, 239 insertions(+), 7 deletions(-) diff --git a/packages/flutter/lib/src/material/material.dart b/packages/flutter/lib/src/material/material.dart index 13ea57867b..025168c6b6 100644 --- a/packages/flutter/lib/src/material/material.dart +++ b/packages/flutter/lib/src/material/material.dart @@ -7,7 +7,6 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'constants.dart'; -import 'shadows.dart'; import 'theme.dart'; /// Signature for the callback used by ink effects to obtain the rectangle for the effect. @@ -205,6 +204,7 @@ class _MaterialState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { Color backgroundColor = _getBackgroundColor(context); + assert(backgroundColor != null || config.type == MaterialType.transparency); Widget contents = config.child; BorderRadius radius = config.borderRadius ?? kMaterialEdges[config.type]; if (contents != null) { @@ -228,11 +228,28 @@ class _MaterialState extends State with TickerProviderStateMixin { ) ); if (config.type == MaterialType.circle) { - contents = new ClipOval(child: contents); - } else if (kMaterialEdges[config.type] != null) { - contents = new ClipRRect( - borderRadius: radius, - child: contents + contents = new PhysicalModel( + shape: BoxShape.circle, + elevation: config.elevation, + color: backgroundColor, + 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) { @@ -241,7 +258,6 @@ class _MaterialState extends State with TickerProviderStateMixin { duration: kThemeChangeDuration, decoration: new BoxDecoration( borderRadius: radius, - boxShadow: config.elevation == 0 ? null : kElevationToShadow[config.elevation], shape: config.type == MaterialType.circle ? BoxShape.circle : BoxShape.rectangle ), child: new Container( diff --git a/packages/flutter/lib/src/rendering/layer.dart b/packages/flutter/lib/src/rendering/layer.dart index d115fca4da..815a950c82 100644 --- a/packages/flutter/lib/src/rendering/layer.dart +++ b/packages/flutter/lib/src/rendering/layer.dart @@ -521,3 +521,45 @@ class BackdropFilterLayer extends ContainerLayer { 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 description) { + super.debugFillDescription(description); + description.add('clipRRect: $clipRRect'); + } +} diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index bb94eafee6..67dc347455 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -429,6 +429,33 @@ class PaintingContext { painter(childContext, offset); 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. diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 3b4a3ccc21..2db9df2c20 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -1181,6 +1181,108 @@ class RenderClipPath extends _RenderCustomClip { } } +/// Creates a physical model layer that clips its children to a rounded +/// rectangle. +class RenderPhysicalModel extends _RenderCustomClip { + /// 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. enum DecorationPosition { /// Paint the box decoration behind the children. diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index e296d9abf7..267a785b4c 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -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