blob: 82ea25573822c3b2174910012fc65109fb6048cc [file] [log] [blame]
// 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;
}
}