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

Unified Diff: sky/framework/layout.dart

Issue 1093633002: Layout API prototype, for discussion (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: remove some casting for clarity Created 5 years, 8 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 | « no previous file | sky/framework/node.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: sky/framework/layout.dart
diff --git a/sky/framework/layout.dart b/sky/framework/layout.dart
new file mode 100644
index 0000000000000000000000000000000000000000..70f4001caba6930166dd765bbac8b37cebf755bf
--- /dev/null
+++ b/sky/framework/layout.dart
@@ -0,0 +1,799 @@
+library layout;
+
+import 'node.dart';
+
+// VALUE TYPES
+
+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 Matrix {
+ external static Matrix get IDENTITY;
+ external void rotate(double cx, double cy, double theta); // rotate theta radians clockwise around cx,cy
+}
+
+
+// PAINTING
+
+class DisplayList {
+ List<RenderNode> _children;
+ void paintChild(RenderNode child, double x, double y) {
+ _addChildToDisplayList(child, x, y);
+ if (_children == null)
+ _children = new List<RenderNode>();
+ assert(!_children.contains(child));
+ _children.add(child);
+ }
+ external void _addChildToDisplayList(RenderNode child, double x, double y);
+}
+
+
+// ABSTRACT LAYOUT
+
+class ParentData {
+ void detach() {
+ detachSiblings();
+ }
+ void detachSiblings() { } // workaround for lack of inter-class mixins in Dart
+}
+
+class PaintOptions {
+ Matrix transform = Matrix.IDENTITY;
+}
+
+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;
+}
+
+abstract class RenderNode extends Node {
+
+ // LAYOUT
+
+ // pos 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 pos;
+ void setupPos(RenderNode child) {
+ // override this to setup .pos correctly for your class
+ if (child.pos is! ParentData)
+ child.pos = new ParentData();
+ }
+
+ void setAsChild(RenderNode child) { // only for use by subclasses
+ // call this whenever you decide a node is a child
+ assert(child != null);
+ setupPos(child);
+ super.setAsChild(child);
+ }
+ void dropChild(RenderNode child) { // only for use by subclasses
+ assert(child != null);
+ assert(child.pos != null);
+ child.pos.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) {
+ assert(_relayoutSubtreeRoot._relayoutSubtreeRoot == null);
+ _relayoutSubtreeRoot = relayoutSubtreeRoot;
+ }
+ void markNeedsLayout() {
+ assert(!_debugDoingLayout);
+ assert(!_debugDoingPaint);
+ if (_needsLayout) return;
+ _needsLayout = true;
+ _nodesNeedingLayout.add(this);
+ }
+ static void flushLayout() {
+ _debugDoingLayout = true;
+ List<RenderNode> dirtyNodes = _nodesNeedingLayout;
+ _nodesNeedingLayout = new List<RenderNode>();
+ dirtyNodes..sort((a, b) => a.order - b.order)..forEach((node) {
+ if (node._needsLayout && node.attached)
+ node._doLayout();
+ });
+ _debugDoingLayout = false;
+ }
+ void _doLayout() {
+ try {
+ if (_relayoutSubtreeRoot != null) {
+ assert(_relayoutSubtreeRoot._relayoutSubtreeRoot == null);
+ _relayoutSubtreeRoot._doLayout();
+ } else {
+ relayout();
ojan 2015/05/09 01:23:34 I was talking Elliott through this design and we r
Hixie 2015/05/13 20:29:21 Hm, yeah. I don't see a way to work around this.
+ }
+ } 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}) {
+ 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:
+ 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:
+ 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:
+ child.layout(... relayoutSubtreeRoot: null); // or just omit relayoutSubtreeRoot
+ layoutDone();
+ return result;
+ }
+ */
+ 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.
+ 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 hitTest(double x, double y, List<RenderNode> targets) {
+ // override this if you have children
+ // if any of your children cover x,y, call the top-most such
+ // child's hitTest().
+ // then, call this superclass hitTest().
+ targets.add(this);
+ }
+
+
+ // PAINTING
+
+ PaintOptions paintOptions;
+ DisplayList _cachedPaint;
+ // only set this to a subclass of PaintOptions that this class of RenderNode knows how to deal with
+
+ static List<RenderNode> _nodesNeedingPaint = new List<RenderNode>();
+ static bool _debugDoingPaint = false;
+ bool _needsPaint = true;
+ bool get needsPaint => _needsPaint;
+ void markNeedsPaint() {
+ assert(!_debugDoingPaint);
+ if (_needsPaint) return;
+ _needsPaint = true;
+ _nodesNeedingPaint.add(this);
+ }
+ static void flushPaint() {
+ List<RenderNode> dirtyNodes = _nodesNeedingPaint;
+ _nodesNeedingPaint = new List<RenderNode>();
+ dirtyNodes..sort((a, b) => a.order - b.order)..forEach((node) {
+ if (node._needsPaint && node.attached)
+ node._doPaint();
+ });
+ }
+ void _doPaint() {
+ assert(!_needsLayout);
+ DisplayList newPaint = new DisplayList();
+ _needsPaint = false;
+ try {
+ paint(newPaint);
+ } catch (e, stack) {
+ print('Exception raised during paint of ${this}: ${e}');
+ print(stack);
+ return;
+ }
+ assert(!_needsLayout); // check that the paint() method didn't mark us dirty again
+ assert(!_needsPaint); // check that the paint() method didn't mark us dirty again
+ if (newPaint._children != null)
+ newPaint._children.forEach((node) {
+ assert(node.attached == attached);
+ if (node._needsPaint)
+ node._doPaint();
+ });
+ _cachedPaint = newPaint;
+ }
+
+ void paint(DisplayList 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.pos is ContainerParentDataMixin<ChildType>);
+ previousSibling.pos.nextSibling = nextSibling;
+ }
+ if (nextSibling != null) {
+ assert(nextSibling.pos is ContainerParentDataMixin<ChildType>);
+ nextSibling.pos.previousSibling = previousSibling;
+ }
+ previousSibling = null;
+ nextSibling = null;
+ }
+}
+
+abstract class ContainerRenderNodeMixin<ChildType extends RenderNode, ParentDataType extends ContainerParentDataMixin<ChildType>> {
+ // abstract class that has only InlineNode children
+
+ bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) {
+ assert(child.pos is ParentDataType);
+ while (child.pos.previousSibling != null) {
+ child = child.pos.previousSibling;
+ assert(child.pos is ParentDataType);
+ }
+ return child == equals;
+ }
+ bool _debugUltimateNextSiblingOf(ChildType child, { ChildType equals }) {
+ assert(child.pos is ParentDataType);
+ while (child.pos.nextSibling != null) {
+ child = child.pos.nextSibling;
+ assert(child.pos is ParentDataType);
+ }
+ return child == equals;
+ }
+
+ ChildType _firstChild;
+ ChildType _lastChild;
+ void add(ChildType child, { ChildType before }) {
+ setAsChild(child);
+ assert(child.pos is ParentDataType);
+ if (before == null) {
+ // append at the end (_lastChild)
+ child.pos.previousSibling = _lastChild;
+ if (_lastChild != null) {
+ assert(_lastChild.pos is ParentDataType);
+ _lastChild.pos.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.pos is ParentDataType);
+ if (before.pos.previousSibling == null) {
+ // insert at the start (_firstChild); we'll end up with two or more children
+ assert(before == _firstChild);
+ child.pos.nextSibling = before;
+ before.pos.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.pos.previousSibling = before.pos.previousSibling;
+ child.pos.nextSibling = before;
+ // set up links from siblings to child
+ assert(child.pos.previousSibling.pos is ParentDataType);
+ assert(child.pos.nextSibling.pos is ParentDataType);
+ child.pos.previousSibling.pos.nextSibling = child;
+ child.pos.nextSibling.pos.previousSibling = child;
+ }
+ }
+ markNeedsLayout();
+ }
+ void remove(ChildType child) {
+ assert(child.pos is ParentDataType);
+ assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild));
+ assert(_debugUltimateNextSiblingOf(child, equals: _lastChild));
+ if (child.pos.previousSibling == null) {
+ _firstChild = child.pos.nextSibling;
+ } else {
+ assert(child.pos.previousSibling.pos is ParentDataType);
+ child.pos.previousSibling.pos.nextSibling = child.pos.nextSibling;
+ }
+ if (child.pos.nextSibling == null) {
+ _lastChild = child.pos.previousSibling;
+ } else {
+ assert(child.pos.nextSibling.pos is ParentDataType);
+ child.pos.nextSibling.pos.previousSibling = child.pos.previousSibling;
+ }
+ dropChild(child);
+ markNeedsLayout();
+ }
+ void reorderChildren() {
+ ChildType child = _firstChild;
+ while (child != null) {
+ reorderChild(child);
+ assert(child.pos is ParentDataType);
+ child = child.pos.nextSibling;
+ }
+ }
+ void attachChildren() {
+ ChildType child = _firstChild;
+ while (child != null) {
+ child.attach();
+ assert(child.pos is ParentDataType);
+ child = child.pos.nextSibling;
+ }
+ }
+ void detachChildren() {
+ ChildType child = _firstChild;
+ while (child != null) {
+ child.detach();
+ assert(child.pos is ParentDataType);
+ child = child.pos.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.pos is! BoxParentData)
+ child.pos = 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;
+ setAsChild(_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(DisplayList canvas) {
+ canvas.paintChild(root, 0.0, 0.0);
+ }
+
+}
+
+
+// BLOCK LAYOUT MANAGER
+
+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.pos is! BlockParentData)
+ child.pos = 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.pos is BlockParentData);
+ child = child.pos.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.pos is BlockParentData);
+ child.pos.x = 0.0;
+ child.pos.y = y;
+ y += child.height;
+ child = child.pos.nextSibling;
+ }
+ height = clamp(min: _minHeight, value: y + _padding.bottom, max: _maxHeight);
+ layoutDone();
+ }
+
+ void hitTest(double x, double y, List<RenderNode> targets) {
+ RenderBox child = _lastChild;
+ while (child != null) {
+ assert(child.pos is BlockParentData);
+ if ((x >= child.pos.x) && (x < child.pos.x + child.width) &&
+ (y >= child.pos.y) && (y < child.pos.y + child.height)) {
+ child.hitTest(x, y, targets);
+ break;
+ }
+ child = child.pos.previousSibling;
+ }
+ super.hitTest(x, y, targets);
+ }
+
+ void paint(DisplayList canvas) {
+ RenderBox child = _firstChild;
+ while (child != null) {
+ assert(child.pos is BlockParentData);
+ canvas.paintChild(child, child.pos.x, child.pos.y);
+ child = child.pos.nextSibling;
+ }
+ }
+
+}
+
+
+// PARAGRAPH LAYOUT MANAGER
+
+class InlineParentData extends BoxParentData with ContainerParentDataMixin<InlineNode> { }
+
+class ParagraphBox extends RenderBox with ContainerRenderNodeMixin<InlineNode, InlineParentData> {
+
+ void setupPos(InlineNode child) {
+ if (child.pos is! InlineParentData)
+ child.pos = new InlineParentData();
+ }
+
+ BoxDimensions getIntrinsicDimensions({
+ double minWidth: 0.0,
+ double maxWidth: double.INFINITY,
+ double minHeight: 0.0,
+ double maxHeight: double.INFINITY
+ }) {
+ // ...compute intrinsic dimension given these constraints...
+ }
+
+ double _minWidth; // value cached from parent for relayout call
+ double _maxWidth; // value cached from parent for relayout call
+ 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;
+ _minWidth = minWidth;
+ _maxWidth = maxWidth;
+ _minHeight = minHeight;
+ _maxHeight = maxHeight;
+ internalLayout(relayoutSubtreeRoot);
+ layoutDone();
+ }
+
+ void relayout() {
+ internalLayout(this);
+ layoutDone();
+ }
+
+ external void internalLayout(RenderNode relayoutSubtreeRoot);
+ external void hitTest(double x, double y, List<RenderNode> targets);
+ external void paint(DisplayList canvas);
+
+}
+
+class InlineNode extends RenderNode with ContainerRenderNodeMixin<InlineNode, InlineParentData> {
+ // ...
+}
+
+
+// InlineBox wraps a RenderBox, i.e. it's a box that fits into inline
+// layout without breaking into multiple lines. This allows you to put
+// images, form controls, inline blocks, etc, into paragraphs
+class InlineBox extends InlineNode {
+ InlineBox(this.child);
+ final RenderBox child;
+
+ // ...
+}
+
+
+// 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.pos is BoxParentData);
+ toolbar.pos.x = 0.0;
+ toolbar.pos.y = 0.0;
+ bodyHeight -= kToolbarHeight;
+ }
+ if (statusbar != null) {
+ statusbar.layout(minWidth: width, maxWidth: width, minHeight: kStatusbarHeight, maxHeight: kStatusbarHeight);
+ assert(statusbar.pos is BoxParentData);
+ statusbar.pos.x = 0.0;
+ statusbar.pos.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 hitTest(double x, double y, List<RenderNode> targets) {
+ if ((drawer != null) && (x < drawer.width)) {
+ drawer.hitTest(x, y, targets);
+ } else if ((toolbar != null) && (y < toolbar.height)) {
+ toolbar.hitTest(x, y, targets);
+ } else if ((statusbar != null) && (y > (statusbar.pos as BoxParentData).y)) {
+ statusbar.hitTest(x, y, targets);
+ } else {
+ body.hitTest(x, y, targets);
+ }
+ super.hitTest(x, y, targets);
+ }
+
+ void paint(DisplayList canvas) {
+ canvas.paintChild(body, (body.pos as BoxParentData).x, (body.pos as BoxParentData).y);
+ if (statusbar != null)
+ canvas.paintChild(statusbar, (statusbar.pos as BoxParentData).x, (statusbar.pos as BoxParentData).y);
+ if (toolbar != null)
+ canvas.paintChild(toolbar, (toolbar.pos as BoxParentData).x, (toolbar.pos as BoxParentData).y);
+ if (drawer != null)
+ canvas.paintChild(drawer, (drawer.pos as BoxParentData).x, (drawer.pos as BoxParentData).y);
+ }
+
+}
« no previous file with comments | « no previous file | sky/framework/node.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698