diff --git a/packages/flutter/lib/rendering/flex.dart b/packages/flutter/lib/rendering/flex.dart index f0980a811f..0308ab2196 100644 --- a/packages/flutter/lib/rendering/flex.dart +++ b/packages/flutter/lib/rendering/flex.dart @@ -289,38 +289,49 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin 0) { + // Flexible children can only be used when the RenderFlex box's container has a finite size. + // When the container is infinite, for example if you are in a scrollable viewport, then + // it wouldn't make any sense to have a flexible child. + assert(canFlex && 'See https://github.com/domokit/sky_engine/blob/master/sky/packages/sky/lib/widgets/flex.md' is String); totalFlex += child.parentData.flex; } else { BoxConstraints innerConstraints; if (alignItems == FlexAlignItems.stretch) { switch (_direction) { case FlexDirection.horizontal: - innerConstraints = new BoxConstraints(maxWidth: constraints.maxWidth, - minHeight: constraints.minHeight, + innerConstraints = new BoxConstraints(minHeight: constraints.minHeight, maxHeight: constraints.maxHeight); break; case FlexDirection.vertical: innerConstraints = new BoxConstraints(minWidth: constraints.minWidth, - maxWidth: constraints.maxWidth, - maxHeight: constraints.maxHeight); + maxWidth: constraints.maxWidth); break; } } else { - innerConstraints = constraints.loosen(); + switch (_direction) { + case FlexDirection.horizontal: + innerConstraints = new BoxConstraints(maxHeight: constraints.maxHeight); + break; + case FlexDirection.vertical: + innerConstraints = new BoxConstraints(maxWidth: constraints.maxWidth); + break; + } } child.layout(innerConstraints, parentUsesSize: true); freeSpace -= _getMainSize(child); @@ -331,63 +342,98 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin 0 ? (freeSpace / totalFlex) : 0.0; - double usedSpace = 0.0; + // Distribute remaining space to flexible children, and determine baseline. double maxBaselineDistance = 0.0; - child = firstChild; - while (child != null) { - int flex = _getFlex(child); - if (flex > 0) { - double spaceForChild = spacePerFlex * flex; - BoxConstraints innerConstraints; - if (alignItems == FlexAlignItems.stretch) { - switch (_direction) { - case FlexDirection.horizontal: - innerConstraints = new BoxConstraints(minWidth: spaceForChild, - maxWidth: spaceForChild, - minHeight: constraints.maxHeight, - maxHeight: constraints.maxHeight); - break; - case FlexDirection.vertical: - innerConstraints = new BoxConstraints(minWidth: constraints.maxWidth, - maxWidth: constraints.maxWidth, - minHeight: spaceForChild, - maxHeight: spaceForChild); - break; - } - } else { - switch (_direction) { - case FlexDirection.horizontal: - innerConstraints = new BoxConstraints(minWidth: spaceForChild, - maxWidth: spaceForChild, - maxHeight: constraints.maxHeight); - break; - case FlexDirection.vertical: - innerConstraints = new BoxConstraints(maxWidth: constraints.maxWidth, - minHeight: spaceForChild, - maxHeight: spaceForChild); - break; + double usedSpace = 0.0; + if (totalFlex > 0 || alignItems == FlexAlignItems.baseline) { + double spacePerFlex = totalFlex > 0 ? (freeSpace / totalFlex) : 0.0; + child = firstChild; + while (child != null) { + int flex = _getFlex(child); + if (flex > 0) { + double spaceForChild = spacePerFlex * flex; + BoxConstraints innerConstraints; + if (alignItems == FlexAlignItems.stretch) { + switch (_direction) { + case FlexDirection.horizontal: + innerConstraints = new BoxConstraints(minWidth: spaceForChild, + maxWidth: spaceForChild, + minHeight: constraints.maxHeight, + maxHeight: constraints.maxHeight); + break; + case FlexDirection.vertical: + innerConstraints = new BoxConstraints(minWidth: constraints.maxWidth, + maxWidth: constraints.maxWidth, + minHeight: spaceForChild, + maxHeight: spaceForChild); + break; + } + } else { + switch (_direction) { + case FlexDirection.horizontal: + innerConstraints = new BoxConstraints(minWidth: spaceForChild, + maxWidth: spaceForChild, + maxHeight: constraints.maxHeight); + break; + case FlexDirection.vertical: + innerConstraints = new BoxConstraints(maxWidth: constraints.maxWidth, + minHeight: spaceForChild, + maxHeight: spaceForChild); + break; + } } + child.layout(innerConstraints, parentUsesSize: true); + usedSpace += _getMainSize(child); + crossSize = math.max(crossSize, _getCrossSize(child)); } - child.layout(innerConstraints, parentUsesSize: true); - usedSpace += _getMainSize(child); - crossSize = math.max(crossSize, _getCrossSize(child)); + if (alignItems == FlexAlignItems.baseline) { + assert(textBaseline != null); + double distance = child.getDistanceToBaseline(textBaseline, onlyReal: true); + if (distance != null) + maxBaselineDistance = math.max(maxBaselineDistance, distance); + } + assert(child.parentData is FlexBoxParentData); + child = child.parentData.nextSibling; } - if (alignItems == FlexAlignItems.baseline) { - assert(textBaseline != null); - double distance = child.getDistanceToBaseline(textBaseline, onlyReal: true); - if (distance != null) - maxBaselineDistance = math.max(maxBaselineDistance, distance); - } - assert(child.parentData is FlexBoxParentData); - child = child.parentData.nextSibling; } - // Section 8.2: Main Axis Alignment using the justify-content property - double remainingSpace = math.max(0.0, freeSpace - usedSpace); + // Align items along the main axis. double leadingSpace; double betweenSpace; + double remainingSpace; + if (canFlex) { + remainingSpace = math.max(0.0, freeSpace - usedSpace); + switch (_direction) { + case FlexDirection.horizontal: + size = constraints.constrain(new Size(mainSize, crossSize)); + crossSize = size.height; + assert(size.width == mainSize); + break; + case FlexDirection.vertical: + size = constraints.constrain(new Size(crossSize, mainSize)); + crossSize = size.width; + assert(size.height == mainSize); + break; + } + } else { + leadingSpace = 0.0; + betweenSpace = 0.0; + switch (_direction) { + case FlexDirection.horizontal: + size = constraints.constrain(new Size(-_overflow, crossSize)); + crossSize = size.height; + assert(size.width >= -_overflow); + remainingSpace = size.width - -_overflow; + break; + case FlexDirection.vertical: + size = constraints.constrain(new Size(crossSize, -_overflow)); + crossSize = size.width; + assert(size.height >= -_overflow); + remainingSpace = size.height - -_overflow; + break; + } + _overflow = 0.0; + } switch (_justifyContent) { case FlexJustifyContent.start: leadingSpace = 0.0; @@ -411,17 +457,6 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin '${super.debugDescribeSettings(prefix)}${prefix}direction: ${_direction}\n${prefix}justifyContent: ${_justifyContent}\n${prefix}alignItems: ${_alignItems}\n${prefix}textBaseline: ${_textBaseline}\n'; + } diff --git a/packages/flutter/lib/widgets/dialog.dart b/packages/flutter/lib/widgets/dialog.dart index 608f9f507b..a42f9fe6ca 100644 --- a/packages/flutter/lib/widgets/dialog.dart +++ b/packages/flutter/lib/widgets/dialog.dart @@ -79,7 +79,11 @@ class Dialog extends Component { } if (actions != null) - dialogBody.add(new Flex(actions, justifyContent: FlexJustifyContent.end)); + dialogBody.add(new Container( + child: new Flex(actions, + justifyContent: FlexJustifyContent.end + ) + )); return new Stack([ new Listener( diff --git a/packages/flutter/lib/widgets/flex.md b/packages/flutter/lib/widgets/flex.md new file mode 100644 index 0000000000..6dc81abc1c --- /dev/null +++ b/packages/flutter/lib/widgets/flex.md @@ -0,0 +1,70 @@ +How To Use Flex In Sky +====================== + +Background +---------- + +In Sky, widgets are rendered by render boxes. Render boxes are given +constraints by their parent, and size themselves within those +constraints. Constraints consist of minimum and maximum widths and +heights; sizes consist of a specific width and height. + +Generally, there are three kinds of boxes, in terms of how they handle +their constraints: + +- Those that try to be as big as possible. + For example, the boxes used by `Center` and `Block`. +- Those that try to be the same size as their children. + For example, the boxes used by `Transform` and `Opacity`. +- Those that try to be a particular size. + For example, the boxes used by `Image` and `Text`. + +Some widgets, for example `Container`, vary from type to type based on +their constructor arguments. In the case of `Container`, it defaults +to trying to be as big as possible, but if you give it a `width`, for +instance, it tries to honor that and be that particular size. + +The constraints are sometimes "tight", meaning that they leave no room +for the render box to decide on a size (e.g. if the minimum and +maximum width are the same, it is said to have a tight width). The +main example of this is the `App` widget, which is contained by the +`RenderView` class: the box used by the child returned by the +application's `build` function is given a constraint that forces it to +exactly fill the application's content area (typically, the entire +screen). + +Unbounded constraints +--------------------- + +In certain situations, the constraint that is given to a box will be +_unbounded_, or infinite. This means that either the maximum width or +the maximum height is set to `double.INFINITY`. + +A box that tries to be as big as possible won't function usefully when +given an unbounded constraint, and in checked mode, will assert. + +The most common cases where a render box finds itself with unbounded +constraints are within flex boxes (`Row` and `Column`), and **within +scrollable regions** (mainly `Block`, `ScollableList`, and +`ScrollableMixedWidgetList`). + +Flex +---- + +Flex boxes themselves (`Row` and `Column`) behave differently based on +whether they are in a bounded constraints or unbounded constraints in +their given direction. + +In bounded constraints, they try to be as big as possible in that +direction. + +In unbounded constraints, they try to fit their children in that +direction. In this case, you cannot set `flex` on the children to +anything other than 0 (the default). In the widget hierarchy, this +means that you cannot use `Flexible` when the flex box is inside +another flex box or inside a scrollable. + +In the _cross_ direction, i.e. in their width for `Column` (vertical +flex) and in their height for `Row` (horizontal flex), they must never +be unbounded, otherwise they would not be able to reasonably align +their children. diff --git a/packages/flutter/lib/widgets/framework.dart b/packages/flutter/lib/widgets/framework.dart index 5e24e760f3..1e4ead33e7 100644 --- a/packages/flutter/lib/widgets/framework.dart +++ b/packages/flutter/lib/widgets/framework.dart @@ -6,7 +6,6 @@ import 'dart:async'; import 'dart:collection'; import 'dart:sky' as sky; -import 'package:sky/base/debug.dart'; import 'package:sky/base/hit_test.dart'; import 'package:sky/base/scheduler.dart' as scheduler; import 'package:sky/mojo/activity.dart'; diff --git a/packages/flutter/lib/widgets/tool_bar.dart b/packages/flutter/lib/widgets/tool_bar.dart index 55beb632ce..57ac48b11b 100644 --- a/packages/flutter/lib/widgets/tool_bar.dart +++ b/packages/flutter/lib/widgets/tool_bar.dart @@ -46,9 +46,12 @@ class ToolBar extends Component { } List children = new List(); + + // left children if (left != null) children.add(left); + // center children (left-aligned, but takes all remaining space) children.add( new Flexible( child: new Padding( @@ -58,15 +61,21 @@ class ToolBar extends Component { ) ); + // right children if (right != null) children.addAll(right); Widget content = new Container( child: new DefaultTextStyle( style: sideStyle, - child: new Flex( - [new Container(child: new Flex(children), height: kToolBarHeight)], - alignItems: FlexAlignItems.end + child: new Flex([ + new Container( + child: new Flex(children), + height: kToolBarHeight + ), + ], + direction: FlexDirection.vertical, + justifyContent: FlexJustifyContent.end ) ), padding: new EdgeDims.symmetric(horizontal: 8.0),