|  | // Copyright 2015 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. | 
|  |  | 
|  | import 'package:cassowary/cassowary.dart' as al; | 
|  | import 'package:sky/rendering/box.dart'; | 
|  | import 'package:sky/rendering/object.dart'; | 
|  |  | 
|  | /// Hosts the edge parameters and vends useful methods to construct expressions | 
|  | /// for constraints. Also sets up and manages implicit constraints and edit | 
|  | /// variables. Used as a mixin by layout containers and parent data instances | 
|  | /// of render boxes taking part in auto layout | 
|  | abstract class _AutoLayoutParamMixin { | 
|  | // Ideally, the edges would all be final, but then they would have to be | 
|  | // initialized before the constructor. Not sure how to do that using a Mixin | 
|  | al.Param _leftEdge; | 
|  | al.Param _rightEdge; | 
|  | al.Param _topEdge; | 
|  | al.Param _bottomEdge; | 
|  |  | 
|  | List<al.Constraint> _implicitConstraints; | 
|  |  | 
|  | al.Param get leftEdge => _leftEdge; | 
|  | al.Param get rightEdge => _rightEdge; | 
|  | al.Param get topEdge => _topEdge; | 
|  | al.Param get bottomEdge => _bottomEdge; | 
|  |  | 
|  | al.Expression get width => _rightEdge - _leftEdge; | 
|  | al.Expression get height => _bottomEdge - _topEdge; | 
|  |  | 
|  | al.Expression get horizontalCenter => (_leftEdge + _rightEdge) / al.cm(2.0); | 
|  | al.Expression get verticalCenter => (_topEdge + _bottomEdge) / al.cm(2.0); | 
|  |  | 
|  | void _setupLayoutParameters(dynamic context) { | 
|  | _leftEdge = new al.Param.withContext(context); | 
|  | _rightEdge = new al.Param.withContext(context); | 
|  | _topEdge = new al.Param.withContext(context); | 
|  | _bottomEdge = new al.Param.withContext(context); | 
|  | } | 
|  |  | 
|  | void _setupEditVariablesInSolver(al.Solver solver, double priority) { | 
|  | solver.addEditVariables([ | 
|  | _leftEdge.variable, | 
|  | _rightEdge.variable, | 
|  | _topEdge.variable, | 
|  | _bottomEdge.variable], priority); | 
|  | } | 
|  |  | 
|  | void _applyEditsAtSize(al.Solver solver, Size size) { | 
|  | solver.suggestValueForVariable(_leftEdge.variable, 0.0); | 
|  | solver.suggestValueForVariable(_topEdge.variable, 0.0); | 
|  | solver.suggestValueForVariable(_bottomEdge.variable, size.height); | 
|  | solver.suggestValueForVariable(_rightEdge.variable, size.width); | 
|  | } | 
|  |  | 
|  | /// Called when the solver has updated at least one of the layout parameters | 
|  | /// of this object. The object is now responsible for applying this update to | 
|  | /// it other properties (if necessary) | 
|  | void _applyAutolayoutParameterUpdates(); | 
|  |  | 
|  | /// 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<al.Constraint> _constructImplicitConstraints(); | 
|  |  | 
|  | void _setupImplicitConstraints(al.Solver solver) { | 
|  | List<al.Constraint> implicit = _constructImplicitConstraints(); | 
|  |  | 
|  | if (implicit == null || implicit.length == 0) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | al.Result result = solver.addConstraints(implicit); | 
|  | assert(result == al.Result.success); | 
|  |  | 
|  | _implicitConstraints = implicit; | 
|  | } | 
|  |  | 
|  | void _removeImplicitConstraints(al.Solver solver) { | 
|  | if (_implicitConstraints == null || _implicitConstraints.length == 0) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | al.Result result = solver.removeConstraints(_implicitConstraints); | 
|  | assert(result == al.Result.success); | 
|  |  | 
|  | _implicitConstraints = null; | 
|  | } | 
|  | } | 
|  |  | 
|  | class AutoLayoutParentData extends BoxParentData | 
|  | with ContainerParentDataMixin<RenderBox>, _AutoLayoutParamMixin { | 
|  |  | 
|  | AutoLayoutParentData(this._renderBox) { | 
|  | _setupLayoutParameters(this); | 
|  | } | 
|  |  | 
|  | final RenderBox _renderBox; | 
|  |  | 
|  | void _applyAutolayoutParameterUpdates() { | 
|  | // This is called by the parent's layout function | 
|  | // to lay our box out. | 
|  | assert(_renderBox.parentData == this); | 
|  | assert(_renderBox.parent is RenderAutoLayout); | 
|  | assert((_renderBox.parent as RenderAutoLayout).debugDoingThisLayout); // TODO(ianh): Remove cast once the analyzer is cleverer | 
|  | BoxConstraints size = new BoxConstraints.tightFor( | 
|  | width: _rightEdge.value - _leftEdge.value, | 
|  | height: _bottomEdge.value - _topEdge.value | 
|  | ); | 
|  | _renderBox.layout(size); | 
|  | position = new Point(_leftEdge.value, _topEdge.value); | 
|  | } | 
|  |  | 
|  | List<al.Constraint> _constructImplicitConstraints() { | 
|  | return [ | 
|  | _leftEdge >= al.cm(0.0), // The left edge must be positive. | 
|  | _rightEdge >= _leftEdge, // Width must be positive. | 
|  | ]; | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | class RenderAutoLayout extends RenderBox | 
|  | with ContainerRenderObjectMixin<RenderBox, AutoLayoutParentData>, | 
|  | RenderBoxContainerDefaultsMixin<RenderBox, AutoLayoutParentData>, | 
|  | _AutoLayoutParamMixin { | 
|  |  | 
|  | RenderAutoLayout({ List<RenderBox> children }) { | 
|  | _setupLayoutParameters(this); | 
|  | _setupEditVariablesInSolver(_solver, al.Priority.required - 1); | 
|  | addAll(children); | 
|  | } | 
|  |  | 
|  | final al.Solver _solver = new al.Solver(); | 
|  | List<al.Constraint> _explicitConstraints = new List<al.Constraint>(); | 
|  |  | 
|  | /// Adds all the given constraints to the solver. Either all constraints are | 
|  | /// added or none | 
|  | al.Result addConstraints(List<al.Constraint> constraints) { | 
|  | al.Result result = _solver.addConstraints(constraints); | 
|  | if (result == al.Result.success) { | 
|  | markNeedsLayout(); | 
|  | _explicitConstraints.addAll(constraints); | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /// Add the given constraint to the solver. | 
|  | al.Result addConstraint(al.Constraint constraint) { | 
|  | al.Result result = _solver.addConstraint(constraint); | 
|  |  | 
|  | if (result == al.Result.success) { | 
|  | markNeedsLayout(); | 
|  | _explicitConstraints.add(constraint); | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /// Removes all explicitly added constraints. | 
|  | al.Result clearAllConstraints() { | 
|  | al.Result result = _solver.removeConstraints(_explicitConstraints); | 
|  |  | 
|  | if (result == al.Result.success) { | 
|  | markNeedsLayout(); | 
|  | _explicitConstraints = new List<al.Constraint>(); | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | void adoptChild(RenderObject child) { | 
|  | // Make sure to call super first to setup the parent data | 
|  | super.adoptChild(child); | 
|  | child.parentData._setupImplicitConstraints(_solver); | 
|  | } | 
|  |  | 
|  | void dropChild(RenderObject child) { | 
|  | child.parentData._removeImplicitConstraints(_solver); | 
|  | super.dropChild(child); | 
|  | } | 
|  |  | 
|  | void setupParentData(RenderObject child) { | 
|  | if (child.parentData is! AutoLayoutParentData) | 
|  | child.parentData = new AutoLayoutParentData(child); | 
|  | } | 
|  |  | 
|  | bool get sizedByParent => true; | 
|  |  | 
|  | void performResize() { | 
|  | size = constraints.biggest; | 
|  | } | 
|  |  | 
|  | void performLayout() { | 
|  | // Step 1: Update dimensions of self | 
|  | _applyEditsAtSize(_solver, size); | 
|  |  | 
|  | // Step 2: 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 _AutoLayoutParamMixin instances. | 
|  | for (_AutoLayoutParamMixin update in _solver.flushUpdates()) { | 
|  | update._applyAutolayoutParameterUpdates(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void _applyAutolayoutParameterUpdates() { | 
|  | // Nothing to do since the size update has already been presented to the | 
|  | // solver as an edit variable modification. The invokation of this method | 
|  | // only indicates that the value has been flushed to the variable. | 
|  | } | 
|  |  | 
|  | void hitTestChildren(HitTestResult result, {Point position}) { | 
|  | defaultHitTestChildren(result, position: position); | 
|  | } | 
|  |  | 
|  | void paint(PaintingCanvas canvas, Offset offset) { | 
|  | defaultPaint(canvas, offset); | 
|  | } | 
|  |  | 
|  | List<al.Constraint> _constructImplicitConstraints() { | 
|  | // Only edits variables are present on layout containers. If, in the future, | 
|  | // implicit constraints (for say margins, padding, etc.) need to be added, | 
|  | // they must be returned from here. | 
|  | return null; | 
|  | } | 
|  | } |