From f5bb35675647d5fdfa0eb8e4ab3f01fc1fc7f481 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 8 Jul 2015 16:11:30 -0700 Subject: [PATCH] Integrate the linear constraint solver into Sky as a RenderBox subclass. R=abarth@chromium.org Review URL: https://codereview.chromium.org/1230583003 . --- sdk/BUILD.gn | 1 + sdk/example/rendering/simple_autolayout.dart | 65 ++++++ sdk/lib/rendering/auto_layout.dart | 225 +++++++++++++++++++ sdk/pubspec.yaml | 1 + 4 files changed, 292 insertions(+) create mode 100644 sdk/example/rendering/simple_autolayout.dart create mode 100644 sdk/lib/rendering/auto_layout.dart diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index b1fc2a3eac7..e571a2bbc84 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn @@ -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", diff --git a/sdk/example/rendering/simple_autolayout.dart b/sdk/example/rendering/simple_autolayout.dart new file mode 100644 index 00000000000..4fe2526e4b5 --- /dev/null +++ b/sdk/example/rendering/simple_autolayout.dart @@ -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([ + // 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); +} diff --git a/sdk/lib/rendering/auto_layout.dart b/sdk/lib/rendering/auto_layout.dart new file mode 100644 index 00000000000..04b952d1b0f --- /dev/null +++ b/sdk/lib/rendering/auto_layout.dart @@ -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 _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 _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 _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, _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 _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, + RenderBoxContainerDefaultsMixin, + _AutoLayoutParamMixin { + + final al.Solver _solver = new al.Solver(); + List _explicitConstraints = new List(); + + RenderAutoLayout({List 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 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; + } + + @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 _constructImplicitConstraints() { + // Only edits are present on layout containers + return null; + } +} diff --git a/sdk/pubspec.yaml b/sdk/pubspec.yaml index 111e6a87eb5..86d8e0e1bec 100644 --- a/sdk/pubspec.yaml +++ b/sdk/pubspec.yaml @@ -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