Commit c7d71d8a authored by Adam Barth's avatar Adam Barth

Simplify the AutoLayout API

This patch makes it easier to use the auto layout API:

* We no longer use operator== because that requires an ugly cast by the
  API user.
* Also, "leftEdge" is now just "left" for less verbosity.
* AutoLayoutChild not implies its key from the AutoLayoutParam object.
* We now correctly layout every child of a RenderAutoLayout object even
  if the solver doesn't flush any updates to that child.
parent 6e14bd2e
...@@ -14,33 +14,33 @@ class _MyAutoLayoutDelegate extends AutoLayoutDelegate { ...@@ -14,33 +14,33 @@ class _MyAutoLayoutDelegate extends AutoLayoutDelegate {
AutoLayoutParams p3 = new AutoLayoutParams(); AutoLayoutParams p3 = new AutoLayoutParams();
AutoLayoutParams p4 = new AutoLayoutParams(); AutoLayoutParams p4 = new AutoLayoutParams();
List<al.Constraint> getConstraints(AutoLayoutParams parentParams) { List<al.Constraint> getConstraints(AutoLayoutParams parent) {
return <al.Constraint>[ return <al.Constraint>[
// Sum of widths of each box must be equal to that of the container // Sum of widths of each box must be equal to that of the container
(p1.width + p2.width + p3.width == parentParams.width) as al.Constraint, parent.width.equals(p1.width + p2.width + p3.width),
// The boxes must be stacked left to right // The boxes must be stacked left to right
p1.rightEdge <= p2.leftEdge, p1.right <= p2.left,
p2.rightEdge <= p3.leftEdge, p2.right <= p3.left,
// The widths of the first and the third boxes should be equal // The widths of the first and the third boxes should be equal
(p1.width == p3.width) as al.Constraint, p1.width.equals(p3.width),
// The width of the second box should be twice as much as that of the first // The width of the second box should be twice as much as that of the first
// and third // and third
(p2.width * al.cm(2.0) == p1.width) as al.Constraint, p1.width.equals(p2.width * al.cm(2.0)),
// The height of the three boxes should be equal to that of the container // The height of the three boxes should be equal to that of the container
(p1.height == p2.height) as al.Constraint, p1.height.equals(p2.height),
(p2.height == p3.height) as al.Constraint, p2.height.equals(p3.height),
(p3.height == parentParams.height) as al.Constraint, p3.height.equals(parent.height),
// The fourth box should be half as wide as the second and must be attached // The fourth box should be half as wide as the second and must be attached
// to the right edge of the same (by its center) // to the right edge of the same (by its center)
(p4.width == p2.width / al.cm(2.0)) as al.Constraint, p4.width.equals(p2.width / al.cm(2.0)),
(p4.height == al.cm(50.0)) as al.Constraint, p4.height.equals(al.cm(50.0)),
(p4.horizontalCenter == p2.rightEdge) as al.Constraint, p4.horizontalCenter.equals(p2.right),
(p4.verticalCenter == p2.height / al.cm(2.0)) as al.Constraint, p4.verticalCenter.equals(p2.height / al.cm(2.0)),
]; ];
} }
......
...@@ -13,53 +13,37 @@ class _MyAutoLayoutDelegate extends AutoLayoutDelegate { ...@@ -13,53 +13,37 @@ class _MyAutoLayoutDelegate extends AutoLayoutDelegate {
AutoLayoutParams p3 = new AutoLayoutParams(); AutoLayoutParams p3 = new AutoLayoutParams();
AutoLayoutParams p4 = new AutoLayoutParams(); AutoLayoutParams p4 = new AutoLayoutParams();
List<al.Constraint> getConstraints(AutoLayoutParams parentParams) { List<al.Constraint> getConstraints(AutoLayoutParams parent) {
return <al.Constraint>[ return <al.Constraint>[
// Sum of widths of each box must be equal to that of the container // Sum of widths of each box must be equal to that of the container
(p1.width + p2.width + p3.width == parentParams.width) as al.Constraint, parent.width.equals(p1.width + p2.width + p3.width),
// The boxes must be stacked left to right // The boxes must be stacked left to right
p1.rightEdge <= p2.leftEdge, p1.right <= p2.left,
p2.rightEdge <= p3.leftEdge, p2.right <= p3.left,
// The widths of the first and the third boxes should be equal // The widths of the first and the third boxes should be equal
(p1.width == p3.width) as al.Constraint, p1.width.equals(p3.width),
// The width of the second box should be twice as much as that of the first // The width of the second box should be twice as much as that of the first
// and third // and third
(p2.width * al.cm(2.0) == p1.width) as al.Constraint, p1.width.equals(p2.width * al.cm(2.0)),
// The height of the three boxes should be equal to that of the container // The height of the three boxes should be equal to that of the container
(p1.height == p2.height) as al.Constraint, p1.height.equals(p2.height),
(p2.height == p3.height) as al.Constraint, p2.height.equals(p3.height),
(p3.height == parentParams.height) as al.Constraint, p3.height.equals(parent.height),
// The fourth box should be half as wide as the second and must be attached // The fourth box should be half as wide as the second and must be attached
// to the right edge of the same (by its center) // to the right edge of the same (by its center)
(p4.width == p2.width / al.cm(2.0)) as al.Constraint, p4.width.equals(p2.width / al.cm(2.0)),
(p4.height == al.cm(50.0)) as al.Constraint, p4.height.equals(al.cm(50.0)),
(p4.horizontalCenter == p2.rightEdge) as al.Constraint, p4.horizontalCenter.equals(p2.right),
(p4.verticalCenter == p2.height / al.cm(2.0)) as al.Constraint, p4.verticalCenter.equals(p2.height / al.cm(2.0)),
]; ];
} }
bool shouldUpdateConstraints(AutoLayoutDelegate oldDelegate) => true; bool shouldUpdateConstraints(_MyAutoLayoutDelegate oldDelegate) => true;
}
class ColoredBox extends StatelessComponent {
ColoredBox({ Key key, this.params, this.color }) : super(key: key);
final AutoLayoutParams params;
final Color color;
Widget build(BuildContext context) {
return new AutoLayoutChild(
params: params,
child: new DecoratedBox(
decoration: new BoxDecoration(backgroundColor: color)
)
);
}
} }
class ColoredBoxes extends StatefulComponent { class ColoredBoxes extends StatefulComponent {
...@@ -73,10 +57,30 @@ class _ColoredBoxesState extends State<ColoredBoxes> { ...@@ -73,10 +57,30 @@ class _ColoredBoxesState extends State<ColoredBoxes> {
return new AutoLayout( return new AutoLayout(
delegate: delegate, delegate: delegate,
children: <Widget>[ children: <Widget>[
new ColoredBox(params: delegate.p1, color: const Color(0xFFFF0000)), new AutoLayoutChild(
new ColoredBox(params: delegate.p2, color: const Color(0xFF00FF00)), params: delegate.p1,
new ColoredBox(params: delegate.p3, color: const Color(0xFF0000FF)), child: new DecoratedBox(
new ColoredBox(params: delegate.p4, color: const Color(0xFFFFFFFF)), decoration: new BoxDecoration(backgroundColor: const Color(0xFFFF0000))
)
),
new AutoLayoutChild(
params: delegate.p2,
child: new DecoratedBox(
decoration: new BoxDecoration(backgroundColor: const Color(0xFF00FF00))
)
),
new AutoLayoutChild(
params: delegate.p3,
child: new DecoratedBox(
decoration: new BoxDecoration(backgroundColor: const Color(0xFF0000FF))
)
),
new AutoLayoutChild(
params: delegate.p4,
child: new DecoratedBox(
decoration: new BoxDecoration(backgroundColor: const Color(0xFFFFFFFF))
)
),
] ]
); );
} }
......
...@@ -15,7 +15,7 @@ abstract class _EquationMember { ...@@ -15,7 +15,7 @@ abstract class _EquationMember {
Constraint operator <=(_EquationMember m) => asExpression() <= m; Constraint operator <=(_EquationMember m) => asExpression() <= m;
operator ==(_EquationMember m) => asExpression() == m; Constraint equals(_EquationMember m) => asExpression().equals(m);
Expression operator +(_EquationMember m) => asExpression() + m; Expression operator +(_EquationMember m) => asExpression() + m;
......
...@@ -57,8 +57,8 @@ class Expression extends _EquationMember { ...@@ -57,8 +57,8 @@ class Expression extends _EquationMember {
Constraint operator <=(_EquationMember value) => Constraint operator <=(_EquationMember value) =>
_createConstraint(value, Relation.lessThanOrEqualTo); _createConstraint(value, Relation.lessThanOrEqualTo);
operator ==(_EquationMember value) => Constraint equals(_EquationMember value) =>
_createConstraint(value, Relation.equalTo); // analyzer says "Type check failed" // analyzer says "The return type 'Constraint' is not a 'bool', as defined by the method '=='" _createConstraint(value, Relation.equalTo);
Expression operator +(_EquationMember m) { Expression operator +(_EquationMember m) {
if (m is ConstantMember) { if (m is ConstantMember) {
......
// Copyright 2015 The Chromium Authors. All rights reserved. // Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
...@@ -12,43 +12,76 @@ import 'object.dart'; ...@@ -12,43 +12,76 @@ import 'object.dart';
/// variables. /// variables.
class AutoLayoutParams { class AutoLayoutParams {
AutoLayoutParams() { AutoLayoutParams() {
_leftEdge = new al.Param.withContext(this); _left = new al.Param();
_rightEdge = new al.Param.withContext(this); _right = new al.Param();
_topEdge = new al.Param.withContext(this); _top = new al.Param();
_bottomEdge = new al.Param.withContext(this); _bottom = new al.Param();
} }
/// The render box with which these parameters are associated. al.Param _left;
RenderBox _renderBox; al.Param _right;
al.Param _top;
al.Param _bottom;
al.Param _leftEdge; al.Param get left => _left;
al.Param _rightEdge; al.Param get right => _right;
al.Param _topEdge; al.Param get top => _top;
al.Param _bottomEdge; al.Param get bottom => _bottom;
al.Param get leftEdge => _leftEdge; al.Expression get width => _right - _left;
al.Param get rightEdge => _rightEdge; al.Expression get height => _bottom - _top;
al.Param get topEdge => _topEdge;
al.Param get bottomEdge => _bottomEdge;
al.Expression get width => _rightEdge - _leftEdge; al.Expression get horizontalCenter => (_left + _right) / al.cm(2.0);
al.Expression get height => _bottomEdge - _topEdge; al.Expression get verticalCenter => (_top + _bottom) / al.cm(2.0);
al.Expression get horizontalCenter => (_leftEdge + _rightEdge) / al.cm(2.0); List<al.Constraint> contains(AutoLayoutParams other) {
al.Expression get verticalCenter => (_topEdge + _bottomEdge) / al.cm(2.0); return <al.Constraint>[
other.left >= left,
other.right <= right,
other.top >= top,
other.bottom <= bottom,
];
}
}
class AutoLayoutParentData extends ContainerBoxParentDataMixin<RenderBox> {
AutoLayoutParentData(this._renderBox);
final RenderBox _renderBox;
AutoLayoutParams get params => _params;
AutoLayoutParams _params;
void set params(AutoLayoutParams value) {
if (_params == value)
return;
if (_params != null)
_removeImplicitConstraints();
_params = value;
if (_params != null)
_addImplicitConstraints();
}
BoxConstraints get _constraintsFromSolver {
return new BoxConstraints.tightFor(
width: _params._right.value - _params._left.value,
height: _params._bottom.value - _params._top.value
);
}
Offset get _offsetFromSolver {
return new Offset(_params._left.value, _params._top.value);
}
List<al.Constraint> _implicitConstraints; List<al.Constraint> _implicitConstraints;
void _addImplicitConstraints() { void _addImplicitConstraints() {
assert(_renderBox != null); assert(_renderBox != null);
if (_renderBox.parent == null) if (_renderBox.parent == null || _params == null)
return; return;
final List<al.Constraint> implicit = _constructImplicitConstraints();
assert(implicit != null && implicit.isNotEmpty);
assert(_renderBox.parent is RenderAutoLayout); assert(_renderBox.parent is RenderAutoLayout);
final RenderAutoLayout parent = _renderBox.parent; final RenderAutoLayout parent = _renderBox.parent;
final AutoLayoutParentData parentData = _renderBox.parentData;
final List<al.Constraint> implicit = parentData._constructImplicitConstraints();
if (implicit == null || implicit.isEmpty)
return;
final al.Result result = parent._solver.addConstraints(implicit); final al.Result result = parent._solver.addConstraints(implicit);
assert(result == al.Result.success); assert(result == al.Result.success);
parent.markNeedsLayout(); parent.markNeedsLayout();
...@@ -57,9 +90,7 @@ class AutoLayoutParams { ...@@ -57,9 +90,7 @@ class AutoLayoutParams {
void _removeImplicitConstraints() { void _removeImplicitConstraints() {
assert(_renderBox != null); assert(_renderBox != null);
if (_renderBox.parent == null) if (_renderBox.parent == null || _implicitConstraints == null || _implicitConstraints.isEmpty)
return;
if (_implicitConstraints == null || _implicitConstraints.isEmpty)
return; return;
assert(_renderBox.parent is RenderAutoLayout); assert(_renderBox.parent is RenderAutoLayout);
final RenderAutoLayout parent = _renderBox.parent; final RenderAutoLayout parent = _renderBox.parent;
...@@ -68,36 +99,6 @@ class AutoLayoutParams { ...@@ -68,36 +99,6 @@ class AutoLayoutParams {
parent.markNeedsLayout(); parent.markNeedsLayout();
_implicitConstraints = null; _implicitConstraints = null;
} }
}
class AutoLayoutParentData extends ContainerBoxParentDataMixin<RenderBox> {
AutoLayoutParentData(this._renderBox);
final RenderBox _renderBox;
AutoLayoutParams get params => _params;
AutoLayoutParams _params;
void set params(AutoLayoutParams value) {
if (_params == value)
return;
if (_params != null) {
_params._removeImplicitConstraints();
_params._renderBox = null;
}
_params = value;
if (_params != null) {
assert(_params._renderBox == null);
_params._renderBox = _renderBox;
_params._addImplicitConstraints();
}
}
BoxConstraints get _constraints {
return new BoxConstraints.tightFor(
width: _params._rightEdge.value - _params._leftEdge.value,
height: _params._bottomEdge.value - _params._topEdge.value
);
}
/// Returns the set of implicit constraints that need to be applied to all /// Returns the set of implicit constraints that need to be applied to all
/// instances of this class when they are moved into a render object with an /// instances of this class when they are moved into a render object with an
...@@ -105,8 +106,8 @@ class AutoLayoutParentData extends ContainerBoxParentDataMixin<RenderBox> { ...@@ -105,8 +106,8 @@ class AutoLayoutParentData extends ContainerBoxParentDataMixin<RenderBox> {
/// may return null. /// may return null.
List<al.Constraint> _constructImplicitConstraints() { List<al.Constraint> _constructImplicitConstraints() {
return <al.Constraint>[ return <al.Constraint>[
_params._leftEdge >= al.cm(0.0), // The left edge must be positive. _params._left >= al.cm(0.0), // The left edge must be positive.
_params._rightEdge >= _params._leftEdge, // Width must be positive. _params._right >= _params._left, // Width must be positive.
]; ];
} }
} }
...@@ -114,7 +115,7 @@ class AutoLayoutParentData extends ContainerBoxParentDataMixin<RenderBox> { ...@@ -114,7 +115,7 @@ class AutoLayoutParentData extends ContainerBoxParentDataMixin<RenderBox> {
abstract class AutoLayoutDelegate { abstract class AutoLayoutDelegate {
const AutoLayoutDelegate(); const AutoLayoutDelegate();
List<al.Constraint> getConstraints(AutoLayoutParams parentParams); List<al.Constraint> getConstraints(AutoLayoutParams parent);
bool shouldUpdateConstraints(AutoLayoutDelegate oldDelegate); bool shouldUpdateConstraints(AutoLayoutDelegate oldDelegate);
} }
...@@ -127,10 +128,10 @@ class RenderAutoLayout extends RenderBox ...@@ -127,10 +128,10 @@ class RenderAutoLayout extends RenderBox
List<RenderBox> children List<RenderBox> children
}) : _delegate = delegate, _needToUpdateConstraints = (delegate != null) { }) : _delegate = delegate, _needToUpdateConstraints = (delegate != null) {
_solver.addEditVariables(<al.Variable>[ _solver.addEditVariables(<al.Variable>[
_params._leftEdge.variable, _params._left.variable,
_params._rightEdge.variable, _params._right.variable,
_params._topEdge.variable, _params._top.variable,
_params._bottomEdge.variable _params._bottom.variable
], al.Priority.required - 1); ], al.Priority.required - 1);
addAll(children); addAll(children);
...@@ -162,14 +163,17 @@ class RenderAutoLayout extends RenderBox ...@@ -162,14 +163,17 @@ class RenderAutoLayout extends RenderBox
final al.Solver _solver = new al.Solver(); final al.Solver _solver = new al.Solver();
final List<al.Constraint> _explicitConstraints = new List<al.Constraint>(); final List<al.Constraint> _explicitConstraints = new List<al.Constraint>();
void _addExplicitConstraints(List<al.Constraint> constraints) { void _setExplicitConstraints(List<al.Constraint> constraints) {
if (constraints == null || constraints.isEmpty) assert(constraints != null);
if (constraints.isEmpty)
return; return;
if (_solver.addConstraints(constraints) == al.Result.success) if (_solver.addConstraints(constraints) == al.Result.success)
_explicitConstraints.addAll(constraints); _explicitConstraints.addAll(constraints);
} }
void _clearExplicitConstraints() { void _clearExplicitConstraints() {
if (_explicitConstraints.isEmpty)
return;
if (_solver.removeConstraints(_explicitConstraints) == al.Result.success) if (_solver.removeConstraints(_explicitConstraints) == al.Result.success)
_explicitConstraints.clear(); _explicitConstraints.clear();
} }
...@@ -178,13 +182,13 @@ class RenderAutoLayout extends RenderBox ...@@ -178,13 +182,13 @@ class RenderAutoLayout extends RenderBox
// Make sure to call super first to setup the parent data // Make sure to call super first to setup the parent data
super.adoptChild(child); super.adoptChild(child);
final AutoLayoutParentData childParentData = child.parentData; final AutoLayoutParentData childParentData = child.parentData;
childParentData._params?._addImplicitConstraints(); childParentData._addImplicitConstraints();
assert(child.parentData == childParentData); assert(child.parentData == childParentData);
} }
void dropChild(RenderObject child) { void dropChild(RenderObject child) {
final AutoLayoutParentData childParentData = child.parentData; final AutoLayoutParentData childParentData = child.parentData;
childParentData._params?._removeImplicitConstraints(); childParentData._removeImplicitConstraints();
assert(child.parentData == childParentData); assert(child.parentData == childParentData);
super.dropChild(child); super.dropChild(child);
} }
...@@ -200,42 +204,40 @@ class RenderAutoLayout extends RenderBox ...@@ -200,42 +204,40 @@ class RenderAutoLayout extends RenderBox
size = constraints.biggest; size = constraints.biggest;
} }
Size _previousSize;
void performLayout() { void performLayout() {
// Step 1: Update constraints if needed. bool needToFlushUpdates = false;
if (_needToUpdateConstraints) { if (_needToUpdateConstraints) {
_clearExplicitConstraints(); _clearExplicitConstraints();
if (_delegate != null) if (_delegate != null)
_addExplicitConstraints(_delegate.getConstraints(_params)); _setExplicitConstraints(_delegate.getConstraints(_params));
_needToUpdateConstraints = false; _needToUpdateConstraints = false;
needToFlushUpdates = true;
} }
// Step 2: Update dimensions of this render object. if (size != _previousSize) {
_solver _solver
..suggestValueForVariable(_params._leftEdge.variable, 0.0) ..suggestValueForVariable(_params._left.variable, 0.0)
..suggestValueForVariable(_params._topEdge.variable, 0.0) ..suggestValueForVariable(_params._top.variable, 0.0)
..suggestValueForVariable(_params._bottomEdge.variable, size.height) ..suggestValueForVariable(_params._bottom.variable, size.height)
..suggestValueForVariable(_params._rightEdge.variable, size.width); ..suggestValueForVariable(_params._right.variable, size.width);
_previousSize = size;
// Step 3: Resolve solver updates and flush parameters needToFlushUpdates = true;
// We don't iterate over the children, instead, we ask the solver to tell
// us the updated parameters. Attached to the parameters (via the context)
// are the AutoLayoutParams instances.
for (AutoLayoutParams update in _solver.flushUpdates()) {
RenderBox child = update._renderBox;
if (child != null)
_layoutChild(child);
} }
}
void _layoutChild(RenderBox child) { if (needToFlushUpdates)
assert(debugDoingThisLayout); _solver.flushUpdates();
assert(child.parent == this);
final AutoLayoutParentData childParentData = child.parentData; RenderBox child = firstChild;
child.layout(childParentData._constraints); while (child != null) {
childParentData.offset = new Offset(childParentData._params._leftEdge.value, final AutoLayoutParentData childParentData = child.parentData;
childParentData._params._topEdge.value); child.layout(childParentData._constraintsFromSolver);
assert(child.parentData == childParentData); childParentData.offset = childParentData._offsetFromSolver;
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
} }
bool hitTestChildren(HitTestResult result, { Point position }) { bool hitTestChildren(HitTestResult result, { Point position }) {
......
...@@ -27,8 +27,8 @@ class AutoLayout extends MultiChildRenderObjectWidget { ...@@ -27,8 +27,8 @@ class AutoLayout extends MultiChildRenderObjectWidget {
} }
class AutoLayoutChild extends ParentDataWidget<AutoLayout> { class AutoLayoutChild extends ParentDataWidget<AutoLayout> {
AutoLayoutChild({ Key key, this.params, Widget child }) AutoLayoutChild({ AutoLayoutParams params, Widget child })
: super(key: key, child: child); : params = params, super(key: new ObjectKey(params), child: child);
final AutoLayoutParams params; final AutoLayoutParams params;
......
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