mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
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:
parent
1bc13e1c63
commit
f5bb356756
@ -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",
|
||||
|
||||
65
sdk/example/rendering/simple_autolayout.dart
Normal file
65
sdk/example/rendering/simple_autolayout.dart
Normal 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);
|
||||
}
|
||||
225
sdk/lib/rendering/auto_layout.dart
Normal file
225
sdk/lib/rendering/auto_layout.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user