Commit 7bafe54a authored by Hans Muller's avatar Hans Muller

Added LayoutId widget, MultiChildLayoutDelegate.isChild()

parent 9bc64540
...@@ -14,15 +14,10 @@ import 'tool_bar.dart'; ...@@ -14,15 +14,10 @@ import 'tool_bar.dart';
const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be device dependent const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be device dependent
const int _kBodyIndex = 0; enum _Child { body, toolBar, bottomSheet, snackBar, floatingActionButton }
const int _kToolBarIndex = 1;
const int _kBottomSheetIndex = 2;
const int _kSnackBarIndex = 3;
const int _kFloatingActionButtonIndex = 4;
class _ScaffoldLayout extends MultiChildLayoutDelegate { class _ScaffoldLayout extends MultiChildLayoutDelegate {
void performLayout(Size size, BoxConstraints constraints, int childCount) { void performLayout(Size size, BoxConstraints constraints) {
assert(childCount == 5);
// This part of the layout has the same effect as putting the toolbar and // This part of the layout has the same effect as putting the toolbar and
// body in a column and making the body flexible. What's different is that // body in a column and making the body flexible. What's different is that
...@@ -30,12 +25,19 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { ...@@ -30,12 +25,19 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
// so the toolbar's shadow is drawn on top of the body. // so the toolbar's shadow is drawn on top of the body.
final BoxConstraints toolBarConstraints = constraints.loosen().tightenWidth(size.width); final BoxConstraints toolBarConstraints = constraints.loosen().tightenWidth(size.width);
final Size toolBarSize = layoutChild(_kToolBarIndex, toolBarConstraints); Size toolBarSize = Size.zero;
final double bodyHeight = size.height - toolBarSize.height;
final BoxConstraints bodyConstraints = toolBarConstraints.tightenHeight(bodyHeight); if (isChild(_Child.toolBar)) {
layoutChild(_kBodyIndex, bodyConstraints); toolBarSize = layoutChild(_Child.toolBar, toolBarConstraints);
positionChild(_kToolBarIndex, Point.origin); positionChild(_Child.toolBar, Point.origin);
positionChild(_kBodyIndex, new Point(0.0, toolBarSize.height)); }
if (isChild(_Child.body)) {
final double bodyHeight = size.height - toolBarSize.height;
final BoxConstraints bodyConstraints = toolBarConstraints.tightenHeight(bodyHeight);
layoutChild(_Child.body, bodyConstraints);
positionChild(_Child.body, new Point(0.0, toolBarSize.height));
}
// The BottomSheet and the SnackBar are anchored to the bottom of the parent, // The BottomSheet and the SnackBar are anchored to the bottom of the parent,
// they're as wide as the parent and are given their intrinsic height. // they're as wide as the parent and are given their intrinsic height.
...@@ -47,24 +49,39 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { ...@@ -47,24 +49,39 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
// by _kFloatingActionButtonMargin. // by _kFloatingActionButtonMargin.
final BoxConstraints fullWidthConstraints = constraints.loosen().tightenWidth(size.width); final BoxConstraints fullWidthConstraints = constraints.loosen().tightenWidth(size.width);
final Size bottomSheetSize = layoutChild(_kBottomSheetIndex, fullWidthConstraints); Size bottomSheetSize = Size.zero;
final Size snackBarSize = layoutChild(_kSnackBarIndex, fullWidthConstraints); Size snackBarSize = Size.zero;
final Size fabSize = layoutChild(_kFloatingActionButtonIndex, constraints.loosen());
positionChild(_kBottomSheetIndex, new Point(0.0, size.height - bottomSheetSize.height)); if (isChild(_Child.bottomSheet)) {
positionChild(_kSnackBarIndex, new Point(0.0, size.height - snackBarSize.height)); bottomSheetSize = layoutChild(_Child.bottomSheet, fullWidthConstraints);
positionChild(_Child.bottomSheet, new Point(0.0, size.height - bottomSheetSize.height));
final double fabX = size.width - fabSize.width - _kFloatingActionButtonMargin; }
double fabY = size.height - fabSize.height - _kFloatingActionButtonMargin;
if (snackBarSize.height > 0.0) if (isChild(_Child.snackBar)) {
fabY = math.min(fabY, size.height - snackBarSize.height - fabSize.height - _kFloatingActionButtonMargin); snackBarSize = layoutChild(_Child.snackBar, fullWidthConstraints);
if (bottomSheetSize.height > 0.0) positionChild(_Child.snackBar, new Point(0.0, size.height - snackBarSize.height));
fabY = math.min(fabY, size.height - bottomSheetSize.height - fabSize.height / 2.0); }
positionChild(_kFloatingActionButtonIndex, new Point(fabX, fabY));
if (isChild(_Child.floatingActionButton)) {
final Size fabSize = layoutChild(_Child.floatingActionButton, constraints.loosen());
final double fabX = size.width - fabSize.width - _kFloatingActionButtonMargin;
double fabY = size.height - fabSize.height - _kFloatingActionButtonMargin;
if (snackBarSize.height > 0.0)
fabY = math.min(fabY, size.height - snackBarSize.height - fabSize.height - _kFloatingActionButtonMargin);
if (bottomSheetSize.height > 0.0)
fabY = math.min(fabY, size.height - bottomSheetSize.height - fabSize.height / 2.0);
positionChild(_Child.floatingActionButton, new Point(fabX, fabY));
}
} }
} }
final _ScaffoldLayout _scaffoldLayout = new _ScaffoldLayout(); final _ScaffoldLayout _scaffoldLayout = new _ScaffoldLayout();
void _addIfNonNull(List<LayoutId> children, Widget child, Object childId) {
if (child != null)
children.add(new LayoutId(child: child, id: childId));
}
class Scaffold extends StatelessComponent { class Scaffold extends StatelessComponent {
Scaffold({ Scaffold({
Key key, Key key,
...@@ -92,14 +109,14 @@ class Scaffold extends StatelessComponent { ...@@ -92,14 +109,14 @@ class Scaffold extends StatelessComponent {
child: snackBar child: snackBar
); );
} }
return new CustomMultiChildLayout(<Widget>[
materialBody ?? new Container(height: 0.0), final List<LayoutId>children = new List<LayoutId>();
paddedToolBar ?? new Container(height: 0.0), _addIfNonNull(children, materialBody, _Child.body);
bottomSheet ?? new Container(height: 0.0), _addIfNonNull(children, paddedToolBar, _Child.toolBar);
constrainedSnackBar ?? new Container(height: 0.0), _addIfNonNull(children, bottomSheet, _Child.bottomSheet);
floatingActionButton ?? new Container(height: 0.0) _addIfNonNull(children, constrainedSnackBar, _Child.snackBar);
], _addIfNonNull(children, floatingActionButton, _Child.floatingActionButton);
delegate: _scaffoldLayout
); return new CustomMultiChildLayout(children, delegate: _scaffoldLayout);
} }
} }
...@@ -5,10 +5,12 @@ ...@@ -5,10 +5,12 @@
import 'box.dart'; import 'box.dart';
import 'object.dart'; import 'object.dart';
class _MultiChildParentData extends ContainerBoxParentDataMixin<RenderBox> { } class MultiChildLayoutParentData extends ContainerBoxParentDataMixin<RenderBox> {
Object id;
}
abstract class MultiChildLayoutDelegate { abstract class MultiChildLayoutDelegate {
final List<RenderBox> _indexToChild = <RenderBox>[]; final Map<Object, RenderBox> _idToChild = new Map<Object, RenderBox>();
/// Returns the size of this object given the incomming constraints. /// Returns the size of this object given the incomming constraints.
/// The size cannot reflect the instrinsic sizes of the children. /// The size cannot reflect the instrinsic sizes of the children.
...@@ -16,41 +18,47 @@ abstract class MultiChildLayoutDelegate { ...@@ -16,41 +18,47 @@ abstract class MultiChildLayoutDelegate {
/// can reflect that. /// can reflect that.
Size getSize(BoxConstraints constraints) => constraints.biggest; Size getSize(BoxConstraints constraints) => constraints.biggest;
/// True if a non-null LayoutChild was provided for the specified id.
bool isChild(Object childId) => _idToChild[childId] != null;
/// Ask the child to update its layout within the limits specified by /// Ask the child to update its layout within the limits specified by
/// the constraints parameter. The child's size is returned. /// the constraints parameter. The child's size is returned.
Size layoutChild(int childIndex, BoxConstraints constraints) { Size layoutChild(Object childId, BoxConstraints constraints) {
final RenderBox child = _indexToChild[childIndex]; final RenderBox child = _idToChild[childId];
assert(child != null);
child.layout(constraints, parentUsesSize: true); child.layout(constraints, parentUsesSize: true);
return child.size; return child.size;
} }
/// Specify the child's origin relative to this origin. /// Specify the child's origin relative to this origin.
void positionChild(int childIndex, Point position) { void positionChild(Object childId, Point position) {
final RenderBox child = _indexToChild[childIndex]; final RenderBox child = _idToChild[childId];
final _MultiChildParentData childParentData = child.parentData; assert(child != null);
final MultiChildLayoutParentData childParentData = child.parentData;
childParentData.position = position; childParentData.position = position;
} }
void _callPerformLayout(Size size, BoxConstraints constraints, RenderBox firstChild) { void _callPerformLayout(Size size, BoxConstraints constraints, RenderBox firstChild) {
RenderBox child = firstChild; RenderBox child = firstChild;
while (child != null) { while (child != null) {
_indexToChild.add(child); final MultiChildLayoutParentData childParentData = child.parentData;
final _MultiChildParentData childParentData = child.parentData; assert(childParentData.id != null);
_idToChild[childParentData.id] = child;
child = childParentData.nextSibling; child = childParentData.nextSibling;
} }
performLayout(size, constraints, _indexToChild.length); performLayout(size, constraints);
_indexToChild.clear(); _idToChild.clear();
} }
/// Layout and position all children given this widget's size and the specified /// Layout and position all children given this widget's size and the specified
/// constraints. This method must apply layoutChild() to each child. It should /// constraints. This method must apply layoutChild() to each child. It should
/// specify the final position of each child with positionChild(). /// specify the final position of each child with positionChild().
void performLayout(Size size, BoxConstraints constraints, int childCount); void performLayout(Size size, BoxConstraints constraints);
} }
class RenderCustomMultiChildLayoutBox extends RenderBox class RenderCustomMultiChildLayoutBox extends RenderBox
with ContainerRenderObjectMixin<RenderBox, _MultiChildParentData>, with ContainerRenderObjectMixin<RenderBox, MultiChildLayoutParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, _MultiChildParentData> { RenderBoxContainerDefaultsMixin<RenderBox, MultiChildLayoutParentData> {
RenderCustomMultiChildLayoutBox({ RenderCustomMultiChildLayoutBox({
List<RenderBox> children, List<RenderBox> children,
MultiChildLayoutDelegate delegate MultiChildLayoutDelegate delegate
...@@ -60,8 +68,8 @@ class RenderCustomMultiChildLayoutBox extends RenderBox ...@@ -60,8 +68,8 @@ class RenderCustomMultiChildLayoutBox extends RenderBox
} }
void setupParentData(RenderBox child) { void setupParentData(RenderBox child) {
if (child.parentData is! _MultiChildParentData) if (child.parentData is! MultiChildLayoutParentData)
child.parentData = new _MultiChildParentData(); child.parentData = new MultiChildLayoutParentData();
} }
MultiChildLayoutDelegate get delegate => _delegate; MultiChildLayoutDelegate get delegate => _delegate;
......
...@@ -288,6 +288,39 @@ class CustomOneChildLayout extends OneChildRenderObjectWidget { ...@@ -288,6 +288,39 @@ class CustomOneChildLayout extends OneChildRenderObjectWidget {
} }
} }
class LayoutId extends ParentDataWidget {
LayoutId({
Key key,
Widget child,
this.id
}) : super(key: key, child: child);
final Object id;
void debugValidateAncestor(Widget ancestor) {
assert(() {
'LayoutId must placed inside a CustomMultiChildLayout';
return ancestor is CustomMultiChildLayout;
});
}
void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is MultiChildLayoutParentData);
final MultiChildLayoutParentData parentData = renderObject.parentData;
if (parentData.id != id) {
parentData.id = id;
AbstractNode targetParent = renderObject.parent;
if (targetParent is RenderObject)
targetParent.markNeedsLayout();
}
}
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('id: $id');
}
}
class CustomMultiChildLayout extends MultiChildRenderObjectWidget { class CustomMultiChildLayout extends MultiChildRenderObjectWidget {
CustomMultiChildLayout(List<Widget> children, { CustomMultiChildLayout(List<Widget> children, {
Key key, Key key,
......
...@@ -14,16 +14,16 @@ class TestMultiChildLayoutDelegate extends MultiChildLayoutDelegate { ...@@ -14,16 +14,16 @@ class TestMultiChildLayoutDelegate extends MultiChildLayoutDelegate {
Size performLayoutSize; Size performLayoutSize;
BoxConstraints performLayoutConstraints; BoxConstraints performLayoutConstraints;
int performLayoutChildCount;
Size performLayoutSize0; Size performLayoutSize0;
Size performLayoutSize1; Size performLayoutSize1;
bool performLayoutIsChild;
void performLayout(Size size, BoxConstraints constraints, int childCount) { void performLayout(Size size, BoxConstraints constraints) {
performLayoutSize = size; performLayoutSize = size;
performLayoutConstraints = constraints; performLayoutConstraints = constraints;
performLayoutChildCount = childCount;
performLayoutSize0 = layoutChild(0, constraints); performLayoutSize0 = layoutChild(0, constraints);
performLayoutSize1 = layoutChild(1, constraints); performLayoutSize1 = layoutChild(1, constraints);
performLayoutIsChild = isChild('fred');
} }
} }
...@@ -33,8 +33,8 @@ void main() { ...@@ -33,8 +33,8 @@ void main() {
TestMultiChildLayoutDelegate delegate = new TestMultiChildLayoutDelegate(); TestMultiChildLayoutDelegate delegate = new TestMultiChildLayoutDelegate();
tester.pumpWidget(new Center( tester.pumpWidget(new Center(
child: new CustomMultiChildLayout([ child: new CustomMultiChildLayout([
new Container(width: 150.0, height: 100.0), new LayoutId(id: 0, child: new Container(width: 150.0, height: 100.0)),
new Container(width: 100.0, height: 200.0) new LayoutId(id: 1, child: new Container(width: 100.0, height: 200.0))
], ],
delegate: delegate delegate: delegate
) )
...@@ -51,11 +51,11 @@ void main() { ...@@ -51,11 +51,11 @@ void main() {
expect(delegate.performLayoutConstraints.maxWidth, 800.0); expect(delegate.performLayoutConstraints.maxWidth, 800.0);
expect(delegate.performLayoutConstraints.minHeight, 0.0); expect(delegate.performLayoutConstraints.minHeight, 0.0);
expect(delegate.performLayoutConstraints.maxHeight, 600.0); expect(delegate.performLayoutConstraints.maxHeight, 600.0);
expect(delegate.performLayoutChildCount, 2);
expect(delegate.performLayoutSize0.width, 150.0); expect(delegate.performLayoutSize0.width, 150.0);
expect(delegate.performLayoutSize0.height, 100.0); expect(delegate.performLayoutSize0.height, 100.0);
expect(delegate.performLayoutSize1.width, 100.0); expect(delegate.performLayoutSize1.width, 100.0);
expect(delegate.performLayoutSize1.height, 200.0); expect(delegate.performLayoutSize1.height, 200.0);
expect(delegate.performLayoutIsChild, false);
}); });
}); });
} }
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