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';
const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be device dependent
const int _kBodyIndex = 0;
const int _kToolBarIndex = 1;
const int _kBottomSheetIndex = 2;
const int _kSnackBarIndex = 3;
const int _kFloatingActionButtonIndex = 4;
enum _Child { body, toolBar, bottomSheet, snackBar, floatingActionButton }
class _ScaffoldLayout extends MultiChildLayoutDelegate {
void performLayout(Size size, BoxConstraints constraints, int childCount) {
assert(childCount == 5);
void performLayout(Size size, BoxConstraints constraints) {
// 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
......@@ -30,12 +25,19 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
// so the toolbar's shadow is drawn on top of the body.
final BoxConstraints toolBarConstraints = constraints.loosen().tightenWidth(size.width);
final Size toolBarSize = layoutChild(_kToolBarIndex, toolBarConstraints);
final double bodyHeight = size.height - toolBarSize.height;
final BoxConstraints bodyConstraints = toolBarConstraints.tightenHeight(bodyHeight);
layoutChild(_kBodyIndex, bodyConstraints);
positionChild(_kToolBarIndex, Point.origin);
positionChild(_kBodyIndex, new Point(0.0, toolBarSize.height));
Size toolBarSize = Size.zero;
if (isChild(_Child.toolBar)) {
toolBarSize = layoutChild(_Child.toolBar, toolBarConstraints);
positionChild(_Child.toolBar, Point.origin);
}
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,
// they're as wide as the parent and are given their intrinsic height.
......@@ -47,24 +49,39 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate {
// by _kFloatingActionButtonMargin.
final BoxConstraints fullWidthConstraints = constraints.loosen().tightenWidth(size.width);
final Size bottomSheetSize = layoutChild(_kBottomSheetIndex, fullWidthConstraints);
final Size snackBarSize = layoutChild(_kSnackBarIndex, fullWidthConstraints);
final Size fabSize = layoutChild(_kFloatingActionButtonIndex, constraints.loosen());
positionChild(_kBottomSheetIndex, new Point(0.0, size.height - bottomSheetSize.height));
positionChild(_kSnackBarIndex, new Point(0.0, size.height - snackBarSize.height));
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(_kFloatingActionButtonIndex, new Point(fabX, fabY));
Size bottomSheetSize = Size.zero;
Size snackBarSize = Size.zero;
if (isChild(_Child.bottomSheet)) {
bottomSheetSize = layoutChild(_Child.bottomSheet, fullWidthConstraints);
positionChild(_Child.bottomSheet, new Point(0.0, size.height - bottomSheetSize.height));
}
if (isChild(_Child.snackBar)) {
snackBarSize = layoutChild(_Child.snackBar, fullWidthConstraints);
positionChild(_Child.snackBar, new Point(0.0, size.height - snackBarSize.height));
}
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();
void _addIfNonNull(List<LayoutId> children, Widget child, Object childId) {
if (child != null)
children.add(new LayoutId(child: child, id: childId));
}
class Scaffold extends StatelessComponent {
Scaffold({
Key key,
......@@ -92,14 +109,14 @@ class Scaffold extends StatelessComponent {
child: snackBar
);
}
return new CustomMultiChildLayout(<Widget>[
materialBody ?? new Container(height: 0.0),
paddedToolBar ?? new Container(height: 0.0),
bottomSheet ?? new Container(height: 0.0),
constrainedSnackBar ?? new Container(height: 0.0),
floatingActionButton ?? new Container(height: 0.0)
],
delegate: _scaffoldLayout
);
final List<LayoutId>children = new List<LayoutId>();
_addIfNonNull(children, materialBody, _Child.body);
_addIfNonNull(children, paddedToolBar, _Child.toolBar);
_addIfNonNull(children, bottomSheet, _Child.bottomSheet);
_addIfNonNull(children, constrainedSnackBar, _Child.snackBar);
_addIfNonNull(children, floatingActionButton, _Child.floatingActionButton);
return new CustomMultiChildLayout(children, delegate: _scaffoldLayout);
}
}
......@@ -5,10 +5,12 @@
import 'box.dart';
import 'object.dart';
class _MultiChildParentData extends ContainerBoxParentDataMixin<RenderBox> { }
class MultiChildLayoutParentData extends ContainerBoxParentDataMixin<RenderBox> {
Object id;
}
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.
/// The size cannot reflect the instrinsic sizes of the children.
......@@ -16,41 +18,47 @@ abstract class MultiChildLayoutDelegate {
/// can reflect that.
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
/// the constraints parameter. The child's size is returned.
Size layoutChild(int childIndex, BoxConstraints constraints) {
final RenderBox child = _indexToChild[childIndex];
Size layoutChild(Object childId, BoxConstraints constraints) {
final RenderBox child = _idToChild[childId];
assert(child != null);
child.layout(constraints, parentUsesSize: true);
return child.size;
}
/// Specify the child's origin relative to this origin.
void positionChild(int childIndex, Point position) {
final RenderBox child = _indexToChild[childIndex];
final _MultiChildParentData childParentData = child.parentData;
void positionChild(Object childId, Point position) {
final RenderBox child = _idToChild[childId];
assert(child != null);
final MultiChildLayoutParentData childParentData = child.parentData;
childParentData.position = position;
}
void _callPerformLayout(Size size, BoxConstraints constraints, RenderBox firstChild) {
RenderBox child = firstChild;
while (child != null) {
_indexToChild.add(child);
final _MultiChildParentData childParentData = child.parentData;
final MultiChildLayoutParentData childParentData = child.parentData;
assert(childParentData.id != null);
_idToChild[childParentData.id] = child;
child = childParentData.nextSibling;
}
performLayout(size, constraints, _indexToChild.length);
_indexToChild.clear();
performLayout(size, constraints);
_idToChild.clear();
}
/// Layout and position all children given this widget's size and the specified
/// constraints. This method must apply layoutChild() to each child. It should
/// 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
with ContainerRenderObjectMixin<RenderBox, _MultiChildParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, _MultiChildParentData> {
with ContainerRenderObjectMixin<RenderBox, MultiChildLayoutParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, MultiChildLayoutParentData> {
RenderCustomMultiChildLayoutBox({
List<RenderBox> children,
MultiChildLayoutDelegate delegate
......@@ -60,8 +68,8 @@ class RenderCustomMultiChildLayoutBox extends RenderBox
}
void setupParentData(RenderBox child) {
if (child.parentData is! _MultiChildParentData)
child.parentData = new _MultiChildParentData();
if (child.parentData is! MultiChildLayoutParentData)
child.parentData = new MultiChildLayoutParentData();
}
MultiChildLayoutDelegate get delegate => _delegate;
......
......@@ -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 {
CustomMultiChildLayout(List<Widget> children, {
Key key,
......
......@@ -14,16 +14,16 @@ class TestMultiChildLayoutDelegate extends MultiChildLayoutDelegate {
Size performLayoutSize;
BoxConstraints performLayoutConstraints;
int performLayoutChildCount;
Size performLayoutSize0;
Size performLayoutSize1;
bool performLayoutIsChild;
void performLayout(Size size, BoxConstraints constraints, int childCount) {
void performLayout(Size size, BoxConstraints constraints) {
performLayoutSize = size;
performLayoutConstraints = constraints;
performLayoutChildCount = childCount;
performLayoutSize0 = layoutChild(0, constraints);
performLayoutSize1 = layoutChild(1, constraints);
performLayoutIsChild = isChild('fred');
}
}
......@@ -33,8 +33,8 @@ void main() {
TestMultiChildLayoutDelegate delegate = new TestMultiChildLayoutDelegate();
tester.pumpWidget(new Center(
child: new CustomMultiChildLayout([
new Container(width: 150.0, height: 100.0),
new Container(width: 100.0, height: 200.0)
new LayoutId(id: 0, child: new Container(width: 150.0, height: 100.0)),
new LayoutId(id: 1, child: new Container(width: 100.0, height: 200.0))
],
delegate: delegate
)
......@@ -51,11 +51,11 @@ void main() {
expect(delegate.performLayoutConstraints.maxWidth, 800.0);
expect(delegate.performLayoutConstraints.minHeight, 0.0);
expect(delegate.performLayoutConstraints.maxHeight, 600.0);
expect(delegate.performLayoutChildCount, 2);
expect(delegate.performLayoutSize0.width, 150.0);
expect(delegate.performLayoutSize0.height, 100.0);
expect(delegate.performLayoutSize1.width, 100.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