| Index: sky/sdk/lib/framework/layout2.dart
|
| diff --git a/sky/sdk/lib/framework/layout2.dart b/sky/sdk/lib/framework/layout2.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..e3773e2bcc67bea48bdb19bafce41577409a2637
|
| --- /dev/null
|
| +++ b/sky/sdk/lib/framework/layout2.dart
|
| @@ -0,0 +1,757 @@
|
| +library layout;
|
| +
|
| +// This version of layout.dart is an update to the other one, this one using new APIs.
|
| +// It will not work in a stock Sky setup currently.
|
| +
|
| +import 'node.dart';
|
| +
|
| +import 'dart:sky' as sky;
|
| +
|
| +// ABSTRACT LAYOUT
|
| +
|
| +class ParentData {
|
| + void detach() {
|
| + detachSiblings();
|
| + }
|
| + void detachSiblings() { } // workaround for lack of inter-class mixins in Dart
|
| + void merge(ParentData other) {
|
| + // override this in subclasses to merge in data from other into this
|
| + assert(other.runtimeType == this.runtimeType);
|
| + }
|
| +}
|
| +
|
| +const kLayoutDirections = 4;
|
| +
|
| +double clamp({double min: 0.0, double value: 0.0, double max: double.INFINITY}) {
|
| + if (value > max)
|
| + value = max;
|
| + if (value < min)
|
| + value = min;
|
| + return value;
|
| +}
|
| +
|
| +class RenderNodeDisplayList extends sky.PictureRecorder {
|
| + RenderNodeDisplayList(double width, double height) : super(width, height);
|
| + void paintChild(RenderNode child, double x, double y) {
|
| + save();
|
| + translate(x, y);
|
| + child.paint(this);
|
| + restore();
|
| + }
|
| +}
|
| +
|
| +abstract class RenderNode extends AbstractNode {
|
| +
|
| + // LAYOUT
|
| +
|
| + // parentData is only for use by the RenderNode that actually lays this
|
| + // node out, and any other nodes who happen to know exactly what
|
| + // kind of node that is.
|
| + ParentData parentData;
|
| + void setupPos(RenderNode child) {
|
| + // override this to setup .parentData correctly for your class
|
| + if (child.parentData is! ParentData)
|
| + child.parentData = new ParentData();
|
| + }
|
| +
|
| + void adoptChild(RenderNode child) { // only for use by subclasses
|
| + // call this whenever you decide a node is a child
|
| + assert(child != null);
|
| + setupPos(child);
|
| + super.adoptChild(child);
|
| + }
|
| + void dropChild(RenderNode child) { // only for use by subclasses
|
| + assert(child != null);
|
| + assert(child.parentData != null);
|
| + child.parentData.detach();
|
| + super.dropChild(child);
|
| + }
|
| +
|
| + static List<RenderNode> _nodesNeedingLayout = new List<RenderNode>();
|
| + static bool _debugDoingLayout = false;
|
| + bool _needsLayout = true;
|
| + bool get needsLayout => _needsLayout;
|
| + RenderNode _relayoutSubtreeRoot;
|
| + void saveRelayoutSubtreeRoot(RenderNode relayoutSubtreeRoot) {
|
| + _relayoutSubtreeRoot = relayoutSubtreeRoot;
|
| + assert(_relayoutSubtreeRoot == null || _relayoutSubtreeRoot._relayoutSubtreeRoot == null);
|
| + assert(_relayoutSubtreeRoot == null || _relayoutSubtreeRoot == parent || _relayoutSubtreeRoot == parent._relayoutSubtreeRoot);
|
| + }
|
| + bool debugAncestorsAlreadyMarkedNeedsLayout() {
|
| + if (_relayoutSubtreeRoot == null)
|
| + return true;
|
| + RenderNode node = this;
|
| + while (node != _relayoutSubtreeRoot) {
|
| + assert(node._relayoutSubtreeRoot == _relayoutSubtreeRoot);
|
| + assert(node.parent != null);
|
| + node = node.parent as RenderNode;
|
| + if (!node._needsLayout)
|
| + return false;
|
| + }
|
| + assert(node._relayoutSubtreeRoot == null);
|
| + return true;
|
| + }
|
| + void markNeedsLayout() {
|
| + assert(!_debugDoingLayout);
|
| + assert(!_debugDoingPaint);
|
| + if (_needsLayout) {
|
| + assert(debugAncestorsAlreadyMarkedNeedsLayout());
|
| + return;
|
| + }
|
| + _needsLayout = true;
|
| + if (_relayoutSubtreeRoot != null)
|
| + parent.markNeedsLayout();
|
| + else
|
| + _nodesNeedingLayout.add(this);
|
| + }
|
| + static void flushLayout() {
|
| + _debugDoingLayout = true;
|
| + List<RenderNode> dirtyNodes = _nodesNeedingLayout;
|
| + _nodesNeedingLayout = new List<RenderNode>();
|
| + dirtyNodes..sort((a, b) => a.depth - b.depth)..forEach((node) {
|
| + if (node._needsLayout && node.attached)
|
| + node._doLayout();
|
| + });
|
| + _debugDoingLayout = false;
|
| + }
|
| + void _doLayout() {
|
| + try {
|
| + assert(_relayoutSubtreeRoot == null);
|
| + relayout();
|
| + } catch (e, stack) {
|
| + print('Exception raised during layout of ${this}: ${e}');
|
| + print(stack);
|
| + return;
|
| + }
|
| + assert(!_needsLayout); // check that the relayout() method marked us "not dirty"
|
| + }
|
| + /* // this method's signature is subclass-specific, but will exist in
|
| + // some form in all subclasses:
|
| + void layout({arguments..., RenderNode relayoutSubtreeRoot}) {
|
| + bool childArgumentsChanged = ...; // true if arguments we're going to pass to the children are different than last time, false otherwise
|
| + if (this node has an opinion about its size, e.g. because it autosizes based on kids, or has an intrinsic dimension) {
|
| + if (relayoutSubtreeRoot != null) {
|
| + saveRelayoutSubtreeRoot(relayoutSubtreeRoot);
|
| + // for each child, if we are going to size ourselves around them:
|
| + if (child.needsLayout || childArgumentsChanged)
|
| + child.layout(... relayoutSubtreeRoot: relayoutSubtreeRoot);
|
| + width = ...;
|
| + height = ...;
|
| + } else {
|
| + saveRelayoutSubtreeRoot(null); // you can skip this if there's no way you would ever have called saveRelayoutSubtreeRoot() before
|
| + // we're the root of the relayout subtree
|
| + // for each child, if we are going to size ourselves around them:
|
| + if (child.needsLayout || childArgumentsChanged)
|
| + child.layout(... relayoutSubtreeRoot: this);
|
| + width = ...;
|
| + height = ...;
|
| + }
|
| + } else {
|
| + // we're sizing ourselves exclusively on input from the parent (arguments to this function)
|
| + // ignore relayoutSubtreeRoot
|
| + saveRelayoutSubtreeRoot(null); // you can skip this if there's no way you would ever have called saveRelayoutSubtreeRoot() before
|
| + width = ...; // based on input from arguments only
|
| + height = ...; // based on input from arguments only
|
| + }
|
| + // for each child whose size we'll ignore when deciding ours:
|
| + if (child.needsLayout || childArgumentsChanged)
|
| + child.layout(... relayoutSubtreeRoot: null); // or just omit relayoutSubtreeRoot
|
| + layoutDone();
|
| + return;
|
| + }
|
| + */
|
| + void relayout() {
|
| + // Override this to perform relayout without your parent's
|
| + // involvement.
|
| + //
|
| + // This is what is called after the first layout(), if you mark
|
| + // yourself dirty and don't have a _relayoutSubtreeRoot set; in
|
| + // other words, either if your parent doesn't care what size you
|
| + // are (and thus didn't pass a relayoutSubtreeRoot to your
|
| + // layout() method) or if you sized yourself entirely based on
|
| + // what your parents told you, and not based on your children (and
|
| + // thus you never called saveRelayoutSubtreeRoot()).
|
| + //
|
| + // In the former case, you can resize yourself here at will. In
|
| + // the latter case, just leave your dimensions unchanged.
|
| + //
|
| + // If _relayoutSubtreeRoot is set (i.e. you called saveRelayout-
|
| + // SubtreeRoot() in your layout(), with a relayoutSubtreeRoot
|
| + // argument that was non-null), then if you mark yourself as dirty
|
| + // then we'll tell that subtree root instead, and the layout will
|
| + // occur via the layout() tree rather than starting from this
|
| + // relayout() method.
|
| + //
|
| + // when calling children's layout() methods, skip any children
|
| + // that have needsLayout == false unless the arguments you are
|
| + // passing in have changed since the last time
|
| + assert(_relayoutSubtreeRoot == null);
|
| + layoutDone();
|
| + }
|
| + void layoutDone({bool needsPaint: true}) {
|
| + // make sure to call this at the end of your layout() or relayout()
|
| + _needsLayout = false;
|
| + if (needsPaint)
|
| + markNeedsPaint();
|
| + }
|
| +
|
| + // when the parent has rotated (e.g. when the screen has been turned
|
| + // 90 degrees), immediately prior to layout() being called for the
|
| + // new dimensions, rotate() is called with the old and new angles.
|
| + // The next time paint() is called, the coordinate space will have
|
| + // been rotated N quarter-turns clockwise, where:
|
| + // N = newAngle-oldAngle
|
| + // ...but the rendering is expected to remain the same, pixel for
|
| + // pixel, on the output device. Then, the layout() method or
|
| + // equivalent will be invoked.
|
| +
|
| + void rotate({
|
| + int oldAngle, // 0..3
|
| + int newAngle, // 0..3
|
| + Duration time
|
| + }) { }
|
| +
|
| +
|
| + // HIT TESTING
|
| +
|
| + void handlePointer(sky.PointerEvent event) {
|
| + // override this if you have children, to hand it to the appropriate child
|
| + // override this if you want to do anything with the pointer event
|
| + }
|
| +
|
| +
|
| + // PAINTING
|
| +
|
| + static bool _debugDoingPaint = false;
|
| + void markNeedsPaint() {
|
| + assert(!_debugDoingPaint);
|
| + var ancestor = this;
|
| + while (ancestor.parent != null)
|
| + ancestor = ancestor.parent;
|
| + assert(ancestor is Screen);
|
| + ancestor.paintFrame();
|
| + }
|
| + void paint(RenderNodeDisplayList canvas) { }
|
| +
|
| +}
|
| +
|
| +
|
| +// GENERIC MIXIN FOR RENDER NODES THAT TAKE A LIST OF CHILDREN
|
| +
|
| +abstract class ContainerParentDataMixin<ChildType extends RenderNode> {
|
| + ChildType previousSibling;
|
| + ChildType nextSibling;
|
| + void detachSiblings() {
|
| + if (previousSibling != null) {
|
| + assert(previousSibling.parentData is ContainerParentDataMixin<ChildType>);
|
| + assert(previousSibling != this);
|
| + assert(previousSibling.parentData.nextSibling == this);
|
| + previousSibling.parentData.nextSibling = nextSibling;
|
| + }
|
| + if (nextSibling != null) {
|
| + assert(nextSibling.parentData is ContainerParentDataMixin<ChildType>);
|
| + assert(nextSibling != this);
|
| + assert(nextSibling.parentData.previousSibling == this);
|
| + nextSibling.parentData.previousSibling = previousSibling;
|
| + }
|
| + previousSibling = null;
|
| + nextSibling = null;
|
| + }
|
| +}
|
| +
|
| +abstract class ContainerRenderNodeMixin<ChildType extends RenderNode, ParentDataType extends ContainerParentDataMixin<ChildType>> implements RenderNode {
|
| + // abstract class that has only InlineNode children
|
| +
|
| + bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) {
|
| + assert(child.parentData is ParentDataType);
|
| + while (child.parentData.previousSibling != null) {
|
| + assert(child.parentData.previousSibling != child);
|
| + child = child.parentData.previousSibling;
|
| + assert(child.parentData is ParentDataType);
|
| + }
|
| + return child == equals;
|
| + }
|
| + bool _debugUltimateNextSiblingOf(ChildType child, { ChildType equals }) {
|
| + assert(child.parentData is ParentDataType);
|
| + while (child.parentData.nextSibling != null) {
|
| + assert(child.parentData.nextSibling != child);
|
| + child = child.parentData.nextSibling;
|
| + assert(child.parentData is ParentDataType);
|
| + }
|
| + return child == equals;
|
| + }
|
| +
|
| + ChildType _firstChild;
|
| + ChildType _lastChild;
|
| + void add(ChildType child, { ChildType before }) {
|
| + assert(child != this);
|
| + assert(before != this);
|
| + assert(child != before);
|
| + assert(child != _firstChild);
|
| + assert(child != _lastChild);
|
| + adoptChild(child);
|
| + assert(child.parentData is ParentDataType);
|
| + assert(child.parentData.nextSibling == null);
|
| + assert(child.parentData.previousSibling == null);
|
| + if (before == null) {
|
| + // append at the end (_lastChild)
|
| + child.parentData.previousSibling = _lastChild;
|
| + if (_lastChild != null) {
|
| + assert(_lastChild.parentData is ParentDataType);
|
| + _lastChild.parentData.nextSibling = child;
|
| + }
|
| + _lastChild = child;
|
| + if (_firstChild == null)
|
| + _firstChild = child;
|
| + } else {
|
| + assert(_firstChild != null);
|
| + assert(_lastChild != null);
|
| + assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild));
|
| + assert(_debugUltimateNextSiblingOf(before, equals: _lastChild));
|
| + assert(before.parentData is ParentDataType);
|
| + if (before.parentData.previousSibling == null) {
|
| + // insert at the start (_firstChild); we'll end up with two or more children
|
| + assert(before == _firstChild);
|
| + child.parentData.nextSibling = before;
|
| + before.parentData.previousSibling = child;
|
| + _firstChild = child;
|
| + } else {
|
| + // insert in the middle; we'll end up with three or more children
|
| + // set up links from child to siblings
|
| + child.parentData.previousSibling = before.parentData.previousSibling;
|
| + child.parentData.nextSibling = before;
|
| + // set up links from siblings to child
|
| + assert(child.parentData.previousSibling.parentData is ParentDataType);
|
| + assert(child.parentData.nextSibling.parentData is ParentDataType);
|
| + child.parentData.previousSibling.parentData.nextSibling = child;
|
| + child.parentData.nextSibling.parentData.previousSibling = child;
|
| + assert(before.parentData.previousSibling == child);
|
| + }
|
| + }
|
| + markNeedsLayout();
|
| + }
|
| + void remove(ChildType child) {
|
| + assert(child.parentData is ParentDataType);
|
| + assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild));
|
| + assert(_debugUltimateNextSiblingOf(child, equals: _lastChild));
|
| + if (child.parentData.previousSibling == null) {
|
| + assert(_firstChild == child);
|
| + _firstChild = child.parentData.nextSibling;
|
| + } else {
|
| + assert(child.parentData.previousSibling.parentData is ParentDataType);
|
| + child.parentData.previousSibling.parentData.nextSibling = child.parentData.nextSibling;
|
| + }
|
| + if (child.parentData.nextSibling == null) {
|
| + assert(_lastChild == child);
|
| + _lastChild = child.parentData.previousSibling;
|
| + } else {
|
| + assert(child.parentData.nextSibling.parentData is ParentDataType);
|
| + child.parentData.nextSibling.parentData.previousSibling = child.parentData.previousSibling;
|
| + }
|
| + child.parentData.previousSibling = null;
|
| + child.parentData.nextSibling = null;
|
| + dropChild(child);
|
| + markNeedsLayout();
|
| + }
|
| + void redepthChildren() {
|
| + ChildType child = _firstChild;
|
| + while (child != null) {
|
| + redepthChild(child);
|
| + assert(child.parentData is ParentDataType);
|
| + child = child.parentData.nextSibling;
|
| + }
|
| + }
|
| + void attachChildren() {
|
| + ChildType child = _firstChild;
|
| + while (child != null) {
|
| + child.attach();
|
| + assert(child.parentData is ParentDataType);
|
| + child = child.parentData.nextSibling;
|
| + }
|
| + }
|
| + void detachChildren() {
|
| + ChildType child = _firstChild;
|
| + while (child != null) {
|
| + child.detach();
|
| + assert(child.parentData is ParentDataType);
|
| + child = child.parentData.nextSibling;
|
| + }
|
| + }
|
| +
|
| + ChildType get firstChild => _firstChild;
|
| + ChildType get lastChild => _lastChild;
|
| + ChildType childAfter(ChildType child) {
|
| + assert(child.parentData is ParentDataType);
|
| + return child.parentData.nextSibling;
|
| + }
|
| +
|
| +}
|
| +
|
| +
|
| +// GENERIC BOX RENDERING
|
| +// Anything that has a concept of x, y, width, height is going to derive from this
|
| +
|
| +class BoxDimensions {
|
| + const BoxDimensions({this.width, this.height});
|
| + final double width;
|
| + final double height;
|
| +}
|
| +
|
| +class BoxParentData extends ParentData {
|
| + double x = 0.0;
|
| + double y = 0.0;
|
| +}
|
| +
|
| +abstract class RenderBox extends RenderNode {
|
| +
|
| + void setupPos(RenderNode child) {
|
| + if (child.parentData is! BoxParentData)
|
| + child.parentData = new BoxParentData();
|
| + }
|
| +
|
| + // override this to report what dimensions you would have if you
|
| + // were laid out with the given constraints this can walk the tree
|
| + // if it must, but it should be as cheap as possible; just get the
|
| + // dimensions and nothing else (e.g. don't calculate hypothetical
|
| + // child positions if they're not needed to determine dimensions)
|
| + BoxDimensions getIntrinsicDimensions({
|
| + double minWidth: 0.0,
|
| + double maxWidth: double.INFINITY,
|
| + double minHeight: 0.0,
|
| + double maxHeight: double.INFINITY
|
| + }) {
|
| + return new BoxDimensions(
|
| + width: clamp(min: minWidth, max: maxWidth),
|
| + height: clamp(min: minHeight, max: maxHeight)
|
| + );
|
| + }
|
| +
|
| + void layout({
|
| + double minWidth: 0.0,
|
| + double maxWidth: double.INFINITY,
|
| + double minHeight: 0.0,
|
| + double maxHeight: double.INFINITY,
|
| + RenderNode relayoutSubtreeRoot
|
| + }) {
|
| + width = clamp(min: minWidth, max: maxWidth);
|
| + height = clamp(min: minHeight, max: maxHeight);
|
| + layoutDone();
|
| + }
|
| +
|
| + double width;
|
| + double height;
|
| +
|
| + void rotate({
|
| + int oldAngle, // 0..3
|
| + int newAngle, // 0..3
|
| + Duration time
|
| + }) { }
|
| +
|
| +}
|
| +
|
| +
|
| +// SCREEN LAYOUT MANAGER
|
| +
|
| +class Screen extends RenderNode {
|
| +
|
| + Screen({
|
| + RenderBox root,
|
| + this.timeForRotation: const Duration(microseconds: 83333)
|
| + }) {
|
| + assert(root != null);
|
| + this.root = root;
|
| + }
|
| +
|
| + double _width;
|
| + double get width => _width;
|
| + double _height;
|
| + double get height => _height;
|
| +
|
| + int _orientation; // 0..3
|
| + int get orientation => _orientation;
|
| + Duration timeForRotation;
|
| +
|
| + RenderBox _root;
|
| + RenderBox get root => _root;
|
| + void set root (RenderBox value) {
|
| + assert(root != null);
|
| + _root = value;
|
| + adoptChild(_root);
|
| + markNeedsLayout();
|
| + }
|
| +
|
| + void layout({
|
| + double newWidth,
|
| + double newHeight,
|
| + int newOrientation
|
| + }) {
|
| + assert(root != null);
|
| + if (newOrientation != orientation) {
|
| + if (orientation != null)
|
| + root.rotate(oldAngle: orientation, newAngle: newOrientation, time: timeForRotation);
|
| + _orientation = newOrientation;
|
| + }
|
| + if ((newWidth != width) || (newHeight != height)) {
|
| + _width = newWidth;
|
| + _height = newHeight;
|
| + relayout();
|
| + }
|
| + }
|
| +
|
| + void relayout() {
|
| + assert(root != null);
|
| + root.layout(
|
| + minWidth: width,
|
| + maxWidth: width,
|
| + minHeight: height,
|
| + maxHeight: height
|
| + );
|
| + assert(root.width == width);
|
| + assert(root.height == height);
|
| + }
|
| +
|
| + void rotate({ int oldAngle, int newAngle, Duration time }) {
|
| + assert(false); // nobody tells the screen to rotate, the whole rotate() dance is started from our layout()
|
| + }
|
| +
|
| + void paint(RenderNodeDisplayList canvas) {
|
| + canvas.paintChild(root, 0.0, 0.0);
|
| + }
|
| +
|
| + void paintFrame() {
|
| + RenderNode._debugDoingPaint = true;
|
| + var canvas = new RenderNodeDisplayList(sky.view.width, sky.view.height);
|
| + paint(canvas);
|
| + sky.view.picture = canvas.endRecording();
|
| + sky.view.schedulePaint();
|
| + RenderNode._debugDoingPaint = false;
|
| + }
|
| +
|
| +}
|
| +
|
| +
|
| +// BLOCK LAYOUT MANAGER
|
| +
|
| +class EdgeDims {
|
| + // used for e.g. padding
|
| + const EdgeDims(this.top, this.right, this.bottom, this.left);
|
| + final double top;
|
| + final double right;
|
| + final double bottom;
|
| + final double left;
|
| + operator ==(EdgeDims other) => (top == other.top) ||
|
| + (right == other.right) ||
|
| + (bottom == other.bottom) ||
|
| + (left == other.left);
|
| +}
|
| +
|
| +class BlockParentData extends BoxParentData with ContainerParentDataMixin<RenderBox> { }
|
| +
|
| +class BlockBox extends RenderBox with ContainerRenderNodeMixin<RenderBox, BlockParentData> {
|
| + // lays out RenderBox children in a vertical stack
|
| + // uses the maximum width provided by the parent
|
| + // sizes itself to the height of its child stack
|
| +
|
| + BlockBox({
|
| + EdgeDims padding: const EdgeDims(0.0, 0.0, 0.0, 0.0)
|
| + }) {
|
| + _padding = padding;
|
| + }
|
| +
|
| + EdgeDims _padding;
|
| + EdgeDims get padding => _padding;
|
| + void set padding(EdgeDims value) {
|
| + assert(value != null);
|
| + if (_padding != value) {
|
| + _padding = value;
|
| + markNeedsLayout();
|
| + }
|
| + }
|
| +
|
| + void setupPos(RenderBox child) {
|
| + if (child.parentData is! BlockParentData)
|
| + child.parentData = new BlockParentData();
|
| + }
|
| +
|
| + // override this to report what dimensions you would have if you
|
| + // were laid out with the given constraints this can walk the tree
|
| + // if it must, but it should be as cheap as possible; just get the
|
| + // dimensions and nothing else (e.g. don't calculate hypothetical
|
| + // child positions if they're not needed to determine dimensions)
|
| + BoxDimensions getIntrinsicDimensions({
|
| + double minWidth: 0.0,
|
| + double maxWidth: double.INFINITY,
|
| + double minHeight: 0.0,
|
| + double maxHeight: double.INFINITY
|
| + }) {
|
| + double outerHeight = _padding.top + _padding.bottom;
|
| + double outerWidth = clamp(min: minWidth, max: maxWidth);
|
| + double innerWidth = outerWidth - (_padding.left + _padding.right);
|
| + RenderBox child = _firstChild;
|
| + while (child != null) {
|
| + outerHeight += child.getIntrinsicDimensions(minWidth: innerWidth, maxWidth: innerWidth).height;
|
| + assert(child.parentData is BlockParentData);
|
| + child = child.parentData.nextSibling;
|
| + }
|
| + return new BoxDimensions(
|
| + width: outerWidth,
|
| + height: clamp(min: minHeight, max: maxHeight, value: outerHeight)
|
| + );
|
| + }
|
| +
|
| + double _minHeight; // value cached from parent for relayout call
|
| + double _maxHeight; // value cached from parent for relayout call
|
| + void layout({
|
| + double minWidth: 0.0,
|
| + double maxWidth: double.INFINITY,
|
| + double minHeight: 0.0,
|
| + double maxHeight: double.INFINITY,
|
| + RenderNode relayoutSubtreeRoot
|
| + }) {
|
| + if (relayoutSubtreeRoot != null)
|
| + saveRelayoutSubtreeRoot(relayoutSubtreeRoot);
|
| + relayoutSubtreeRoot = relayoutSubtreeRoot == null ? this : relayoutSubtreeRoot;
|
| + width = clamp(min: minWidth, max: maxWidth);
|
| + _minHeight = minHeight;
|
| + _maxHeight = maxHeight;
|
| + internalLayout(relayoutSubtreeRoot);
|
| + }
|
| +
|
| + void relayout() {
|
| + internalLayout(this);
|
| + }
|
| +
|
| + void internalLayout(RenderNode relayoutSubtreeRoot) {
|
| + assert(_minHeight != null);
|
| + assert(_maxHeight != null);
|
| + double y = _padding.top;
|
| + double innerWidth = width - (_padding.left + _padding.right);
|
| + RenderBox child = _firstChild;
|
| + while (child != null) {
|
| + child.layout(minWidth: innerWidth, maxWidth: innerWidth, relayoutSubtreeRoot: relayoutSubtreeRoot);
|
| + assert(child.parentData is BlockParentData);
|
| + child.parentData.x = 0.0;
|
| + child.parentData.y = y;
|
| + y += child.height;
|
| + child = child.parentData.nextSibling;
|
| + }
|
| + height = clamp(min: _minHeight, value: y + _padding.bottom, max: _maxHeight);
|
| + layoutDone();
|
| + }
|
| +
|
| + void handlePointer(sky.PointerEvent event, { double x: 0.0, double y: 0.0 }) {
|
| + // the x, y parameters have the top left of the node's box as the origin
|
| + RenderBox child = _lastChild;
|
| + while (child != null) {
|
| + assert(child.parentData is BlockParentData);
|
| + if ((x >= child.parentData.x) && (x < child.parentData.x + child.width) &&
|
| + (y >= child.parentData.y) && (y < child.parentData.y + child.height)) {
|
| + child.handlePointer(event, x: x-child.parentData.x, y: y-child.parentData.y);
|
| + break;
|
| + }
|
| + child = child.parentData.previousSibling;
|
| + }
|
| + super.handlePointer(event);
|
| + }
|
| +
|
| + void paint(RenderNodeDisplayList canvas) {
|
| + RenderBox child = _firstChild;
|
| + while (child != null) {
|
| + assert(child.parentData is BlockParentData);
|
| + canvas.paintChild(child, child.parentData.x, child.parentData.y);
|
| + child = child.parentData.nextSibling;
|
| + }
|
| + }
|
| +
|
| +}
|
| +
|
| +class FlexBoxParentData extends BoxParentData {
|
| + int flex;
|
| + void merge(FlexBoxParentData other) {
|
| + if (other.flex != null)
|
| + flex = other.flex;
|
| + super.merge(other);
|
| + }
|
| +}
|
| +
|
| +enum FlexDirection { Row, Column }
|
| +
|
| +// TODO(ianh): FlexBox
|
| +
|
| +
|
| +// SCAFFOLD LAYOUT MANAGER
|
| +
|
| +// a sample special-purpose layout manager
|
| +
|
| +class ScaffoldBox extends RenderBox {
|
| +
|
| + ScaffoldBox(this.toolbar, this.body, this.statusbar, this.drawer) {
|
| + assert(body != null);
|
| + }
|
| +
|
| + final RenderBox toolbar;
|
| + final RenderBox body;
|
| + final RenderBox statusbar;
|
| + final RenderBox drawer;
|
| +
|
| + void layout({
|
| + double minWidth: 0.0,
|
| + double maxWidth: double.INFINITY,
|
| + double minHeight: 0.0,
|
| + double maxHeight: double.INFINITY,
|
| + RenderNode relayoutSubtreeRoot
|
| + }) {
|
| + width = clamp(min: minWidth, max: maxWidth);
|
| + height = clamp(min: minHeight, max: maxHeight);
|
| + relayout();
|
| + }
|
| +
|
| + static const kToolbarHeight = 100.0;
|
| + static const kStatusbarHeight = 50.0;
|
| +
|
| + void relayout() {
|
| + double bodyHeight = height;
|
| + if (toolbar != null) {
|
| + toolbar.layout(minWidth: width, maxWidth: width, minHeight: kToolbarHeight, maxHeight: kToolbarHeight);
|
| + assert(toolbar.parentData is BoxParentData);
|
| + toolbar.parentData.x = 0.0;
|
| + toolbar.parentData.y = 0.0;
|
| + bodyHeight -= kToolbarHeight;
|
| + }
|
| + if (statusbar != null) {
|
| + statusbar.layout(minWidth: width, maxWidth: width, minHeight: kStatusbarHeight, maxHeight: kStatusbarHeight);
|
| + assert(statusbar.parentData is BoxParentData);
|
| + statusbar.parentData.x = 0.0;
|
| + statusbar.parentData.y = height - kStatusbarHeight;
|
| + bodyHeight -= kStatusbarHeight;
|
| + }
|
| + body.layout(minWidth: width, maxWidth: width, minHeight: bodyHeight, maxHeight: bodyHeight);
|
| + if (drawer != null)
|
| + drawer.layout(minWidth: 0.0, maxWidth: width, minHeight: height, maxHeight: height);
|
| + layoutDone();
|
| + }
|
| +
|
| + void handlePointer(sky.PointerEvent event, { double x: 0.0, double y: 0.0 }) {
|
| + if ((drawer != null) && (x < drawer.width)) {
|
| + drawer.handlePointer(event, x: x, y: y);
|
| + } else if ((toolbar != null) && (y < toolbar.height)) {
|
| + toolbar.handlePointer(event, x: x, y: y);
|
| + } else if ((statusbar != null) && (y > (statusbar.parentData as BoxParentData).y)) {
|
| + statusbar.handlePointer(event, x: x, y: y-(statusbar.parentData as BoxParentData).y);
|
| + } else {
|
| + body.handlePointer(event, x: x, y: y-(body.parentData as BoxParentData).y);
|
| + }
|
| + super.handlePointer(event, x: x, y: y);
|
| + }
|
| +
|
| + void paint(RenderNodeDisplayList canvas) {
|
| + canvas.paintChild(body, (body.parentData as BoxParentData).x, (body.parentData as BoxParentData).y);
|
| + if (statusbar != null)
|
| + canvas.paintChild(statusbar, (statusbar.parentData as BoxParentData).x, (statusbar.parentData as BoxParentData).y);
|
| + if (toolbar != null)
|
| + canvas.paintChild(toolbar, (toolbar.parentData as BoxParentData).x, (toolbar.parentData as BoxParentData).y);
|
| + if (drawer != null)
|
| + canvas.paintChild(drawer, (drawer.parentData as BoxParentData).x, (drawer.parentData as BoxParentData).y);
|
| + }
|
| +
|
| +}
|
|
|