From c7d71d8ab1b7eea58e7741cde9e65d96ff36f308 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Mon, 15 Feb 2016 15:25:21 -0800 Subject: [PATCH] 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. --- examples/layers/rendering/autolayout.dart | 26 +-- examples/layers/widgets/autolayout.dart | 72 ++++--- packages/cassowary/lib/equation_member.dart | 2 +- packages/cassowary/lib/expression.dart | 4 +- .../lib/src/rendering/auto_layout.dart | 202 +++++++++--------- .../flutter/lib/src/widgets/auto_layout.dart | 4 +- 6 files changed, 158 insertions(+), 152 deletions(-) diff --git a/examples/layers/rendering/autolayout.dart b/examples/layers/rendering/autolayout.dart index f89f4bf948..3fafe45372 100644 --- a/examples/layers/rendering/autolayout.dart +++ b/examples/layers/rendering/autolayout.dart @@ -14,33 +14,33 @@ class _MyAutoLayoutDelegate extends AutoLayoutDelegate { AutoLayoutParams p3 = new AutoLayoutParams(); AutoLayoutParams p4 = new AutoLayoutParams(); - List getConstraints(AutoLayoutParams parentParams) { + List getConstraints(AutoLayoutParams parent) { return [ // 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 - p1.rightEdge <= p2.leftEdge, - p2.rightEdge <= p3.leftEdge, + p1.right <= p2.left, + p2.right <= p3.left, // 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 // 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 - (p1.height == p2.height) as al.Constraint, - (p2.height == p3.height) as al.Constraint, - (p3.height == parentParams.height) as al.Constraint, + p1.height.equals(p2.height), + p2.height.equals(p3.height), + p3.height.equals(parent.height), // 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) - (p4.width == p2.width / al.cm(2.0)) as al.Constraint, - (p4.height == al.cm(50.0)) as al.Constraint, - (p4.horizontalCenter == p2.rightEdge) as al.Constraint, - (p4.verticalCenter == p2.height / al.cm(2.0)) as al.Constraint, + p4.width.equals(p2.width / al.cm(2.0)), + p4.height.equals(al.cm(50.0)), + p4.horizontalCenter.equals(p2.right), + p4.verticalCenter.equals(p2.height / al.cm(2.0)), ]; } diff --git a/examples/layers/widgets/autolayout.dart b/examples/layers/widgets/autolayout.dart index d7e81f158e..c43558b239 100644 --- a/examples/layers/widgets/autolayout.dart +++ b/examples/layers/widgets/autolayout.dart @@ -13,53 +13,37 @@ class _MyAutoLayoutDelegate extends AutoLayoutDelegate { AutoLayoutParams p3 = new AutoLayoutParams(); AutoLayoutParams p4 = new AutoLayoutParams(); - List getConstraints(AutoLayoutParams parentParams) { + List getConstraints(AutoLayoutParams parent) { return [ // 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 - p1.rightEdge <= p2.leftEdge, - p2.rightEdge <= p3.leftEdge, + p1.right <= p2.left, + p2.right <= p3.left, // 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 // 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 - (p1.height == p2.height) as al.Constraint, - (p2.height == p3.height) as al.Constraint, - (p3.height == parentParams.height) as al.Constraint, + p1.height.equals(p2.height), + p2.height.equals(p3.height), + p3.height.equals(parent.height), // 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) - (p4.width == p2.width / al.cm(2.0)) as al.Constraint, - (p4.height == al.cm(50.0)) as al.Constraint, - (p4.horizontalCenter == p2.rightEdge) as al.Constraint, - (p4.verticalCenter == p2.height / al.cm(2.0)) as al.Constraint, + p4.width.equals(p2.width / al.cm(2.0)), + p4.height.equals(al.cm(50.0)), + p4.horizontalCenter.equals(p2.right), + p4.verticalCenter.equals(p2.height / al.cm(2.0)), ]; } - bool shouldUpdateConstraints(AutoLayoutDelegate 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) - ) - ); - } + bool shouldUpdateConstraints(_MyAutoLayoutDelegate oldDelegate) => true; } class ColoredBoxes extends StatefulComponent { @@ -73,10 +57,30 @@ class _ColoredBoxesState extends State { return new AutoLayout( delegate: delegate, children: [ - new ColoredBox(params: delegate.p1, color: const Color(0xFFFF0000)), - new ColoredBox(params: delegate.p2, color: const Color(0xFF00FF00)), - new ColoredBox(params: delegate.p3, color: const Color(0xFF0000FF)), - new ColoredBox(params: delegate.p4, color: const Color(0xFFFFFFFF)), + new AutoLayoutChild( + params: delegate.p1, + child: new DecoratedBox( + 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)) + ) + ), ] ); } diff --git a/packages/cassowary/lib/equation_member.dart b/packages/cassowary/lib/equation_member.dart index 247710d624..8f2a5591d3 100644 --- a/packages/cassowary/lib/equation_member.dart +++ b/packages/cassowary/lib/equation_member.dart @@ -15,7 +15,7 @@ abstract class _EquationMember { Constraint operator <=(_EquationMember m) => asExpression() <= m; - operator ==(_EquationMember m) => asExpression() == m; + Constraint equals(_EquationMember m) => asExpression().equals(m); Expression operator +(_EquationMember m) => asExpression() + m; diff --git a/packages/cassowary/lib/expression.dart b/packages/cassowary/lib/expression.dart index 44fee17512..ab79491258 100644 --- a/packages/cassowary/lib/expression.dart +++ b/packages/cassowary/lib/expression.dart @@ -57,8 +57,8 @@ class Expression extends _EquationMember { Constraint operator <=(_EquationMember value) => _createConstraint(value, Relation.lessThanOrEqualTo); - operator ==(_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 '=='" + Constraint equals(_EquationMember value) => + _createConstraint(value, Relation.equalTo); Expression operator +(_EquationMember m) { if (m is ConstantMember) { diff --git a/packages/flutter/lib/src/rendering/auto_layout.dart b/packages/flutter/lib/src/rendering/auto_layout.dart index 156621d6fb..b901007b99 100644 --- a/packages/flutter/lib/src/rendering/auto_layout.dart +++ b/packages/flutter/lib/src/rendering/auto_layout.dart @@ -1,4 +1,4 @@ -// 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 // found in the LICENSE file. @@ -12,61 +12,35 @@ import 'object.dart'; /// variables. class AutoLayoutParams { AutoLayoutParams() { - _leftEdge = new al.Param.withContext(this); - _rightEdge = new al.Param.withContext(this); - _topEdge = new al.Param.withContext(this); - _bottomEdge = new al.Param.withContext(this); + _left = new al.Param(); + _right = new al.Param(); + _top = new al.Param(); + _bottom = new al.Param(); } - /// The render box with which these parameters are associated. - RenderBox _renderBox; + al.Param _left; + al.Param _right; + al.Param _top; + al.Param _bottom; - al.Param _leftEdge; - al.Param _rightEdge; - al.Param _topEdge; - al.Param _bottomEdge; + al.Param get left => _left; + al.Param get right => _right; + al.Param get top => _top; + al.Param get bottom => _bottom; - al.Param get leftEdge => _leftEdge; - al.Param get rightEdge => _rightEdge; - al.Param get topEdge => _topEdge; - al.Param get bottomEdge => _bottomEdge; + al.Expression get width => _right - _left; + al.Expression get height => _bottom - _top; - al.Expression get width => _rightEdge - _leftEdge; - al.Expression get height => _bottomEdge - _topEdge; + al.Expression get horizontalCenter => (_left + _right) / al.cm(2.0); + al.Expression get verticalCenter => (_top + _bottom) / al.cm(2.0); - al.Expression get horizontalCenter => (_leftEdge + _rightEdge) / al.cm(2.0); - al.Expression get verticalCenter => (_topEdge + _bottomEdge) / al.cm(2.0); - - List _implicitConstraints; - - void _addImplicitConstraints() { - assert(_renderBox != null); - if (_renderBox.parent == null) - return; - assert(_renderBox.parent is RenderAutoLayout); - final RenderAutoLayout parent = _renderBox.parent; - final AutoLayoutParentData parentData = _renderBox.parentData; - final List implicit = parentData._constructImplicitConstraints(); - if (implicit == null || implicit.isEmpty) - return; - final al.Result result = parent._solver.addConstraints(implicit); - assert(result == al.Result.success); - parent.markNeedsLayout(); - _implicitConstraints = implicit; - } - - void _removeImplicitConstraints() { - assert(_renderBox != null); - if (_renderBox.parent == null) - return; - if (_implicitConstraints == null || _implicitConstraints.isEmpty) - return; - assert(_renderBox.parent is RenderAutoLayout); - final RenderAutoLayout parent = _renderBox.parent; - final al.Result result = parent._solver.removeConstraints(_implicitConstraints); - assert(result == al.Result.success); - parent.markNeedsLayout(); - _implicitConstraints = null; + List contains(AutoLayoutParams other) { + return [ + other.left >= left, + other.right <= right, + other.top >= top, + other.bottom <= bottom, + ]; } } @@ -80,33 +54,60 @@ class AutoLayoutParentData extends ContainerBoxParentDataMixin { void set params(AutoLayoutParams value) { if (_params == value) return; - if (_params != null) { - _params._removeImplicitConstraints(); - _params._renderBox = null; - } + if (_params != null) + _removeImplicitConstraints(); _params = value; - if (_params != null) { - assert(_params._renderBox == null); - _params._renderBox = _renderBox; - _params._addImplicitConstraints(); - } + if (_params != null) + _addImplicitConstraints(); } - BoxConstraints get _constraints { + BoxConstraints get _constraintsFromSolver { return new BoxConstraints.tightFor( - width: _params._rightEdge.value - _params._leftEdge.value, - height: _params._bottomEdge.value - _params._topEdge.value + 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 _implicitConstraints; + + void _addImplicitConstraints() { + assert(_renderBox != null); + if (_renderBox.parent == null || _params == null) + return; + final List implicit = _constructImplicitConstraints(); + assert(implicit != null && implicit.isNotEmpty); + assert(_renderBox.parent is RenderAutoLayout); + final RenderAutoLayout parent = _renderBox.parent; + final al.Result result = parent._solver.addConstraints(implicit); + assert(result == al.Result.success); + parent.markNeedsLayout(); + _implicitConstraints = implicit; + } + + void _removeImplicitConstraints() { + assert(_renderBox != null); + if (_renderBox.parent == null || _implicitConstraints == null || _implicitConstraints.isEmpty) + return; + assert(_renderBox.parent is RenderAutoLayout); + final RenderAutoLayout parent = _renderBox.parent; + final al.Result result = parent._solver.removeConstraints(_implicitConstraints); + assert(result == al.Result.success); + parent.markNeedsLayout(); + _implicitConstraints = null; + } + /// 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 /// active solver. If no implicit constraints needs to be applied, the object /// may return null. List _constructImplicitConstraints() { return [ - _params._leftEdge >= al.cm(0.0), // The left edge must be positive. - _params._rightEdge >= _params._leftEdge, // Width must be positive. + _params._left >= al.cm(0.0), // The left edge must be positive. + _params._right >= _params._left, // Width must be positive. ]; } } @@ -114,7 +115,7 @@ class AutoLayoutParentData extends ContainerBoxParentDataMixin { abstract class AutoLayoutDelegate { const AutoLayoutDelegate(); - List getConstraints(AutoLayoutParams parentParams); + List getConstraints(AutoLayoutParams parent); bool shouldUpdateConstraints(AutoLayoutDelegate oldDelegate); } @@ -127,10 +128,10 @@ class RenderAutoLayout extends RenderBox List children }) : _delegate = delegate, _needToUpdateConstraints = (delegate != null) { _solver.addEditVariables([ - _params._leftEdge.variable, - _params._rightEdge.variable, - _params._topEdge.variable, - _params._bottomEdge.variable + _params._left.variable, + _params._right.variable, + _params._top.variable, + _params._bottom.variable ], al.Priority.required - 1); addAll(children); @@ -162,14 +163,17 @@ class RenderAutoLayout extends RenderBox final al.Solver _solver = new al.Solver(); final List _explicitConstraints = new List(); - void _addExplicitConstraints(List constraints) { - if (constraints == null || constraints.isEmpty) + void _setExplicitConstraints(List constraints) { + assert(constraints != null); + if (constraints.isEmpty) return; if (_solver.addConstraints(constraints) == al.Result.success) _explicitConstraints.addAll(constraints); } void _clearExplicitConstraints() { + if (_explicitConstraints.isEmpty) + return; if (_solver.removeConstraints(_explicitConstraints) == al.Result.success) _explicitConstraints.clear(); } @@ -178,13 +182,13 @@ class RenderAutoLayout extends RenderBox // Make sure to call super first to setup the parent data super.adoptChild(child); final AutoLayoutParentData childParentData = child.parentData; - childParentData._params?._addImplicitConstraints(); + childParentData._addImplicitConstraints(); assert(child.parentData == childParentData); } void dropChild(RenderObject child) { final AutoLayoutParentData childParentData = child.parentData; - childParentData._params?._removeImplicitConstraints(); + childParentData._removeImplicitConstraints(); assert(child.parentData == childParentData); super.dropChild(child); } @@ -200,42 +204,40 @@ class RenderAutoLayout extends RenderBox size = constraints.biggest; } + Size _previousSize; + void performLayout() { - // Step 1: Update constraints if needed. + bool needToFlushUpdates = false; + if (_needToUpdateConstraints) { _clearExplicitConstraints(); if (_delegate != null) - _addExplicitConstraints(_delegate.getConstraints(_params)); + _setExplicitConstraints(_delegate.getConstraints(_params)); _needToUpdateConstraints = false; + needToFlushUpdates = true; } - // Step 2: Update dimensions of this render object. - _solver - ..suggestValueForVariable(_params._leftEdge.variable, 0.0) - ..suggestValueForVariable(_params._topEdge.variable, 0.0) - ..suggestValueForVariable(_params._bottomEdge.variable, size.height) - ..suggestValueForVariable(_params._rightEdge.variable, size.width); - - // Step 3: Resolve solver updates and flush parameters - - // 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); + if (size != _previousSize) { + _solver + ..suggestValueForVariable(_params._left.variable, 0.0) + ..suggestValueForVariable(_params._top.variable, 0.0) + ..suggestValueForVariable(_params._bottom.variable, size.height) + ..suggestValueForVariable(_params._right.variable, size.width); + _previousSize = size; + needToFlushUpdates = true; } - } - void _layoutChild(RenderBox child) { - assert(debugDoingThisLayout); - assert(child.parent == this); - final AutoLayoutParentData childParentData = child.parentData; - child.layout(childParentData._constraints); - childParentData.offset = new Offset(childParentData._params._leftEdge.value, - childParentData._params._topEdge.value); - assert(child.parentData == childParentData); + if (needToFlushUpdates) + _solver.flushUpdates(); + + RenderBox child = firstChild; + while (child != null) { + final AutoLayoutParentData childParentData = child.parentData; + child.layout(childParentData._constraintsFromSolver); + childParentData.offset = childParentData._offsetFromSolver; + assert(child.parentData == childParentData); + child = childParentData.nextSibling; + } } bool hitTestChildren(HitTestResult result, { Point position }) { diff --git a/packages/flutter/lib/src/widgets/auto_layout.dart b/packages/flutter/lib/src/widgets/auto_layout.dart index b915d10493..0f29c92c9b 100644 --- a/packages/flutter/lib/src/widgets/auto_layout.dart +++ b/packages/flutter/lib/src/widgets/auto_layout.dart @@ -27,8 +27,8 @@ class AutoLayout extends MultiChildRenderObjectWidget { } class AutoLayoutChild extends ParentDataWidget { - AutoLayoutChild({ Key key, this.params, Widget child }) - : super(key: key, child: child); + AutoLayoutChild({ AutoLayoutParams params, Widget child }) + : params = params, super(key: new ObjectKey(params), child: child); final AutoLayoutParams params;