Integrate the linear constraint solver into Sky as a RenderBox subclass.

R=abarth@chromium.org

Review URL: https://codereview.chromium.org/1230583003 .
This commit is contained in:
Chinmay Garde 2015-07-08 16:11:30 -07:00
parent 1bc13e1c63
commit f5bb356756
4 changed files with 292 additions and 0 deletions

View File

@ -37,6 +37,7 @@ dart_pkg("sky") {
"lib/painting/box_painter.dart",
"lib/painting/shadows.dart",
"lib/painting/text_style.dart",
"lib/rendering/auto_layout.dart",
"lib/rendering/block.dart",
"lib/rendering/box.dart",
"lib/rendering/flex.dart",

View File

@ -0,0 +1,65 @@
// 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 'dart:sky';
import 'package:sky/rendering/box.dart';
import 'package:sky/rendering/object.dart';
import 'package:sky/rendering/sky_binding.dart';
import 'package:sky/rendering/auto_layout.dart';
import 'package:cassowary/cassowary.dart' as al;
void main() {
var c1 = new RenderDecoratedBox(
decoration: new BoxDecoration(backgroundColor: const Color(0xFFFF0000))
);
var c2 = new RenderDecoratedBox(
decoration: new BoxDecoration(backgroundColor: const Color(0xFF00FF00))
);
var c3 = new RenderDecoratedBox(
decoration: new BoxDecoration(backgroundColor: const Color(0xFF0000FF))
);
var c4 = new RenderDecoratedBox(
decoration: new BoxDecoration(backgroundColor: const Color(0xFFFFFFFF))
);
var root = new RenderAutoLayout(children: [c1, c2, c3, c4]);
AutoLayoutParentData p1 = c1.parentData;
AutoLayoutParentData p2 = c2.parentData;
AutoLayoutParentData p3 = c3.parentData;
AutoLayoutParentData p4 = c4.parentData;
root.addConstraints(<al.Constraint>[
// Sum of widths of each box must be equal to that of the container
(p1.width + p2.width + p3.width == root.width) as al.Constraint,
// The boxes must be stacked left to right
p1.rightEdge <= p2.leftEdge,
p2.rightEdge <= p3.leftEdge,
// The widths of the first and the third boxes should be equal
(p1.width == p3.width) as al.Constraint,
// 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,
// 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 == root.height) as al.Constraint,
// 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,
]);
new SkyBinding(root: root);
}

View File

@ -0,0 +1,225 @@
// 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 'box.dart';
import 'object.dart';
import 'package:cassowary/cassowary.dart' as al;
/// 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);
}
void _applyAutolayoutParameterUpdates();
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 _collectImplicitConstraints(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 {
final RenderBox _renderBox;
AutoLayoutParentData(this._renderBox) {
_setupLayoutParameters(this);
}
@override
void _applyAutolayoutParameterUpdates() {
BoxConstraints box = new BoxConstraints.tightFor(
width: _rightEdge.value - _leftEdge.value,
height: _bottomEdge.value - _topEdge.value);
_renderBox.layout(box, parentUsesSize: false);
position = new Point(_leftEdge.value, _topEdge.value);
}
@override
List<al.Constraint> _constructImplicitConstraints() {
return [
// The left edge must be positive
_leftEdge >= al.CM(0.0),
// Width must be positive
_rightEdge >= _leftEdge,
];
}
}
class RenderAutoLayout extends RenderBox
with ContainerRenderObjectMixin<RenderBox, AutoLayoutParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, AutoLayoutParentData>,
_AutoLayoutParamMixin {
final al.Solver _solver = new al.Solver();
List<al.Constraint> _explicitConstraints = new List<al.Constraint>();
RenderAutoLayout({List<RenderBox> children}) {
_setupLayoutParameters(this);
_setupEditVariablesInSolver(_solver, al.Priority.required - 1);
addAll(children);
}
/// 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;
}
@override
void setupParentData(RenderObject child) {
if (child.parentData is! AutoLayoutParentData) {
child.parentData = new AutoLayoutParentData(child);
}
}
@override
void performLayout() {
// Step 1: Update dimensions of self
size = constraints.biggest;
_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();
}
}
@override
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.
}
@override
void hitTestChildren(HitTestResult result, {Point position}) =>
defaultHitTestChildren(result, position: position);
@override
void paint(PaintingCanvas canvas, Offset offset) =>
defaultPaint(canvas, offset);
@override
void adoptChild(RenderObject child) {
// Make sure to call super first to setup the parent data
super.adoptChild(child);
child.parentData._setupImplicitConstraints(_solver);
}
@override
void dropChild(RenderObject child) {
child.parentData._collectImplicitConstraints(_solver);
// Call super last as this collects parent data
super.dropChild(child);
}
@override
List<al.Constraint> _constructImplicitConstraints() {
// Only edits are present on layout containers
return null;
}
}

View File

@ -4,6 +4,7 @@ dependencies:
mojo_services: '>=0.0.4 <1.0.0'
mojom: '>=0.0.4 <1.0.0'
vector_math: '>=1.4.3 <2.0.0'
cassowary: '^0.1.6'
description: Dart files to support executing inside Sky.
homepage: https://github.com/domokit/mojo/tree/master/sky
name: sky