// 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/src/rendering/box.dart'; import 'package:sky/src/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 _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 _constructImplicitConstraints(); void _setupImplicitConstraints(al.Solver solver) { List 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, _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 _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, RenderBoxContainerDefaultsMixin, _AutoLayoutParamMixin { RenderAutoLayout({ List children }) { _setupLayoutParameters(this); _setupEditVariablesInSolver(_solver, al.Priority.required - 1); addAll(children); } final al.Solver _solver = new al.Solver(); List _explicitConstraints = new List(); /// Adds all the given constraints to the solver. Either all constraints are /// added or none al.Result addConstraints(List 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(); } 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 _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; } }