| // 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(PaintingContext context, Offset offset) { |
| defaultPaint(context, 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; |
| } |
| } |