diff --git a/examples/stocks/lib/stock_home.dart b/examples/stocks/lib/stock_home.dart index ee47f6e92f..bab4db64a6 100644 --- a/examples/stocks/lib/stock_home.dart +++ b/examples/stocks/lib/stock_home.dart @@ -236,7 +236,6 @@ class StockHomeState extends State { if (_snackBarStatus == AnimationStatus.dismissed) return null; return new SnackBar( - transitionKey: snackBarKey, showing: _isSnackBarShowing, content: new Text("Stock purchased!"), actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)], diff --git a/packages/flutter/lib/src/fn3/basic.dart b/packages/flutter/lib/src/fn3/basic.dart index 4dd60a3464..c561629c79 100644 --- a/packages/flutter/lib/src/fn3/basic.dart +++ b/packages/flutter/lib/src/fn3/basic.dart @@ -211,7 +211,9 @@ class SizedBox extends OneChildRenderObjectWidget { final double width; final double height; - RenderConstrainedBox createRenderObject() => new RenderConstrainedBox(additionalConstraints: _additionalConstraints); + RenderConstrainedBox createRenderObject() => new RenderConstrainedBox( + additionalConstraints: _additionalConstraints + ); BoxConstraints get _additionalConstraints { BoxConstraints result = const BoxConstraints(); @@ -227,6 +229,24 @@ class SizedBox extends OneChildRenderObjectWidget { } } +class OverflowBox extends OneChildRenderObjectWidget { + OverflowBox({ Key key, this.width, this.height, Widget child }) + : super(key: key, child: child); + + final double width; + final double height; + + RenderOverflowBox createRenderObject() => new RenderOverflowBox( + innerWidth: width, + innerHeight: height + ); + + void updateRenderObject(RenderOverflowBox renderObject, OverflowBox oldWidget) { + renderObject.innerWidth = width; + renderObject.innerHeight = height; + } +} + class ConstrainedBox extends OneChildRenderObjectWidget { ConstrainedBox({ Key key, this.constraints, Widget child }) : super(key: key, child: child) { diff --git a/packages/flutter/lib/src/fn3/scaffold.dart b/packages/flutter/lib/src/fn3/scaffold.dart index 6d0935e25a..77a72c4d22 100644 --- a/packages/flutter/lib/src/fn3/scaffold.dart +++ b/packages/flutter/lib/src/fn3/scaffold.dart @@ -102,6 +102,7 @@ class RenderScaffold extends RenderBox { void performLayout() { double bodyHeight = size.height; double bodyPosition = 0.0; + double fabOffset = 0.0; if (_slots[ScaffoldSlots.statusBar] != null) { RenderBox statusBar = _slots[ScaffoldSlots.statusBar]; statusBar.layout(new BoxConstraints.tight(new Size(size.width, kStatusBarHeight))); @@ -127,18 +128,20 @@ class RenderScaffold extends RenderBox { if (_slots[ScaffoldSlots.snackBar] != null) { RenderBox snackBar = _slots[ScaffoldSlots.snackBar]; // TODO(jackson): On tablet/desktop, minWidth = 288, maxWidth = 568 - snackBar.layout(new BoxConstraints(minWidth: size.width, maxWidth: size.width, minHeight: 0.0, maxHeight: size.height), - parentUsesSize: true); + snackBar.layout( + new BoxConstraints(minWidth: size.width, maxWidth: size.width, minHeight: 0.0, maxHeight: bodyHeight), + parentUsesSize: true + ); assert(snackBar.parentData is BoxParentData); - // Position it off-screen. SnackBar slides in with an animation. - snackBar.parentData.position = new Point(0.0, size.height); + snackBar.parentData.position = new Point(0.0, bodyPosition + bodyHeight - snackBar.size.height); + fabOffset += snackBar.size.height; } if (_slots[ScaffoldSlots.floatingActionButton] != null) { RenderBox floatingActionButton = _slots[ScaffoldSlots.floatingActionButton]; Size area = new Size(size.width - kButtonX, size.height - kButtonY); floatingActionButton.layout(new BoxConstraints.loose(area), parentUsesSize: true); assert(floatingActionButton.parentData is BoxParentData); - floatingActionButton.parentData.position = (area - floatingActionButton.size).toPoint(); + floatingActionButton.parentData.position = (area - floatingActionButton.size).toPoint() + new Offset(0.0, -fabOffset); } if (_slots[ScaffoldSlots.drawer] != null) { RenderBox drawer = _slots[ScaffoldSlots.drawer]; diff --git a/packages/flutter/lib/src/fn3/snack_bar.dart b/packages/flutter/lib/src/fn3/snack_bar.dart index 2c745b3aa6..2d9b1b1dbc 100644 --- a/packages/flutter/lib/src/fn3/snack_bar.dart +++ b/packages/flutter/lib/src/fn3/snack_bar.dart @@ -17,7 +17,10 @@ import 'package:sky/src/fn3/transitions.dart'; typedef void SnackBarDismissedCallback(); const Duration _kSlideInDuration = const Duration(milliseconds: 200); -// TODO(ianh): factor out some of the constants below +const double kSnackHeight = 52.0; +const double kSideMargins = 24.0; +const double kVerticalPadding = 14.0; +const Color kSnackBackground = const Color(0xFF323232); class SnackBarAction extends StatelessComponent { SnackBarAction({Key key, this.label, this.onPressed }) : super(key: key) { @@ -31,8 +34,8 @@ class SnackBarAction extends StatelessComponent { return new GestureDetector( onTap: onPressed, child: new Container( - margin: const EdgeDims.only(left: 24.0), - padding: const EdgeDims.only(top: 14.0, bottom: 14.0), + margin: const EdgeDims.only(left: kSideMargins), + padding: const EdgeDims.symmetric(vertical: kVerticalPadding), child: new Text(label) ) ); @@ -42,7 +45,6 @@ class SnackBarAction extends StatelessComponent { class SnackBar extends AnimatedComponent { SnackBar({ Key key, - this.transitionKey, this.content, this.actions, bool showing, @@ -51,7 +53,6 @@ class SnackBar extends AnimatedComponent { assert(content != null); } - final Key transitionKey; final Widget content; final List actions; final SnackBarDismissedCallback onDismissed; @@ -69,7 +70,7 @@ class SnackBarState extends AnimatedState { List children = [ new Flexible( child: new Container( - margin: const EdgeDims.symmetric(vertical: 14.0), + margin: const EdgeDims.symmetric(vertical: kVerticalPadding), child: new DefaultTextStyle( style: Typography.white.subhead, child: config.content @@ -79,24 +80,28 @@ class SnackBarState extends AnimatedState { ]; if (config.actions != null) children.addAll(config.actions); - return new SlideTransition( - key: config.transitionKey, + return new SquashTransition( performance: performance.view, - position: new AnimatedValue( - Point.origin, - end: const Point(0.0, -52.0), + height: new AnimatedValue( + 0.0, + end: kSnackHeight, curve: easeIn, reverseCurve: easeOut ), - child: new Material( - level: 2, - color: const Color(0xFF323232), - type: MaterialType.canvas, - child: new Container( - margin: const EdgeDims.symmetric(horizontal: 24.0), - child: new DefaultTextStyle( - style: new TextStyle(color: Theme.of(context).accentColor), - child: new Row(children) + child: new ClipRect( + child: new OverflowBox( + height: kSnackHeight, + child: new Material( + level: 2, + color: kSnackBackground, + type: MaterialType.canvas, + child: new Container( + margin: const EdgeDims.symmetric(horizontal: kSideMargins), + child: new DefaultTextStyle( + style: new TextStyle(color: Theme.of(context).accentColor), + child: new Row(children) + ) + ) ) ) ) diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index cbe3253ab9..6c802efbff 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -83,7 +83,7 @@ class RenderProxyBox extends RenderBox with RenderObjectWithChildMixin '${super.debugDescribeSettings(prefix)}${prefix}additionalConstraints: ${additionalConstraints}\n'; } +/// A render object that imposes different constraints on its child than it gets +/// from its parent, possibly allowing the child to overflow the parent. +/// +/// A render overflow box proxies most functions in the render box protocol to +/// its child, except that when laying out its child, it passes constraints +/// based on the innerWidth and innerHeight fields instead of just passing the +/// parent's constraints in. It then sizes itself based on the parent's +/// constraints' maxWidth and maxHeight, ignoring the child's dimensions. +/// +/// For example, if you wanted a box to always render 50x50, regardless of where +/// it was rendered, you would wrap it in a RenderOverflow with innerWidth and +/// innerHeight members set to 50.0. Generally speaking, to avoid confusing +/// behaviour around hit testing, a RenderOverflowBox should usually be wrapped +/// in a RenderClipRect. +/// +/// The child is positioned at the top left of the box. To position a smaller +/// child inside a larger parent, use [RenderPositionedBox] and +/// [RenderConstrainedBox] rather than RenderOverflowBox. +/// +/// If you pass null for innerWidth or innerHeight, the constraints from the +/// parent are passed instead. +class RenderOverflowBox extends RenderProxyBox { + RenderOverflowBox({ + RenderBox child, + double innerWidth, + double innerHeight + }) : _innerWidth = innerWidth, _innerHeight = innerHeight, super(child); + + /// The tight width constraint to give the child. Set this to null (the + /// default) to use the constraints from the parent instead. + double get innerWidth => _innerWidth; + double _innerWidth; + void set innerWidth (double value) { + if (_innerWidth == value) + return; + _innerWidth = value; + markNeedsLayout(); + } + + /// The tight height constraint to give the child. Set this to null (the + /// default) to use the constraints from the parent instead. + double get innerHeight => _innerHeight; + double _innerHeight; + void set innerHeight (double value) { + if (_innerHeight == value) + return; + _innerHeight = value; + markNeedsLayout(); + } + + BoxConstraints childConstraints(BoxConstraints constraints) { + return new BoxConstraints( + minWidth: _innerWidth ?? constraints.minWidth, + maxWidth: _innerWidth ?? constraints.maxWidth, + minHeight: _innerHeight ?? constraints.minHeight, + maxHeight: _innerHeight ?? constraints.maxHeight + ); + } + + double getMinIntrinsicWidth(BoxConstraints constraints) { + return constraints.constrainWidth(); + } + + double getMaxIntrinsicWidth(BoxConstraints constraints) { + return constraints.constrainWidth(); + } + + double getMinIntrinsicHeight(BoxConstraints constraints) { + return constraints.constrainHeight(); + } + + double getMaxIntrinsicHeight(BoxConstraints constraints) { + return constraints.constrainHeight(); + } + + bool get sizedByParent => true; + + void performResize() { + size = constraints.biggest; + } + + void performLayout() { + if (child != null) + child.layout(childConstraints(constraints)); + } + + String debugDescribeSettings(String prefix) { + return '${super.debugDescribeSettings(prefix)}' + + '${prefix}innerWidth: ${innerWidth ?? "use parent width constraints"}\n' + + '${prefix}innerHeight: ${innerHeight ?? "use parent height constraints"}\n'; + } +} + /// Forces child to layout at a specific aspect ratio /// /// The width of this render object is the largest width permited by the layout