Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(606)

Unified Diff: sky/sdk/lib/framework/layout2.dart

Issue 1093633002: Layout API prototype, for discussion (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: git cl land Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « sky/sdk/lib/framework/layout.dart ('k') | sky/sdk/lib/framework/node.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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);
+ }
+
+}
« no previous file with comments | « sky/sdk/lib/framework/layout.dart ('k') | sky/sdk/lib/framework/node.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698