Index: sky/sdk/lib/framework/rendering/render_node.dart |
diff --git a/sky/sdk/lib/framework/rendering/render_node.dart b/sky/sdk/lib/framework/rendering/render_node.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..29863a84ffac30f6160b87342fb8b636d466e716 |
--- /dev/null |
+++ b/sky/sdk/lib/framework/rendering/render_node.dart |
@@ -0,0 +1,390 @@ |
+// 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 '../node.dart'; |
+import 'dart:sky' as sky; |
+ |
+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}) { |
+ assert(min != null); |
+ assert(value != null); |
+ assert(max != null); |
+ |
+ 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, sky.Point position) { |
+ save(); |
+ translate(position.x, position.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 setParentData(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); |
+ setParentData(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; |
+ dynamic _constraints; |
+ dynamic get constraints => _constraints; |
+ bool debugAncestorsAlreadyMarkedNeedsLayout() { |
+ if (_relayoutSubtreeRoot == null) |
+ return true; // we haven't yet done layout even once, so there's nothing for us to do |
+ 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 == node); |
+ return true; |
+ } |
+ void markNeedsLayout() { |
+ assert(!_debugDoingLayout); |
+ assert(!debugDoingPaint); |
+ if (_needsLayout) { |
+ assert(debugAncestorsAlreadyMarkedNeedsLayout()); |
+ return; |
+ } |
+ _needsLayout = true; |
+ assert(_relayoutSubtreeRoot != null); |
+ if (_relayoutSubtreeRoot != this) { |
+ assert(parent is RenderNode); |
+ 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 == this); |
+ performLayout(); |
+ } catch (e, stack) { |
+ print('Exception raised during layout of ${this}: ${e}'); |
+ print(stack); |
+ return; |
+ } |
+ _needsLayout = false; |
+ } |
+ void layout(dynamic constraints, { bool parentUsesSize: false }) { |
+ RenderNode relayoutSubtreeRoot; |
+ if (!parentUsesSize || sizedByParent || parent is! RenderNode) |
+ relayoutSubtreeRoot = this; |
+ else |
+ relayoutSubtreeRoot = parent._relayoutSubtreeRoot; |
+ if (!needsLayout && constraints == _constraints && relayoutSubtreeRoot == _relayoutSubtreeRoot) |
+ return; |
+ _constraints = constraints; |
+ _relayoutSubtreeRoot = relayoutSubtreeRoot; |
+ if (sizedByParent) |
+ performResize(); |
+ performLayout(); |
+ _needsLayout = false; |
+ markNeedsPaint(); |
+ } |
+ bool get sizedByParent => false; // return true if the constraints are the only input to the sizing algorithm (in particular, child nodes have no impact) |
+ void performResize(); // set the local dimensions, using only the constraints (only called if sizedByParent is true) |
+ void performLayout(); |
+ // Override this to perform relayout without your parent's |
+ // involvement. |
+ // |
+ // This is called during layout. If sizedByParent is true, then |
+ // performLayout() should not change your dimensions, only do that |
+ // in performResize(). If sizedByParent is false, then set both |
+ // your dimensions and do your children's layout here. |
+ // |
+ // When calling layout() on your children, pass in |
+ // "parentUsesSize: true" if your size or layout is dependent on |
+ // your child's size. |
+ |
+ // 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 |
+ }) { } |
+ |
+ |
+ // PAINTING |
+ |
+ static bool debugDoingPaint = false; |
+ void markNeedsPaint() { |
+ assert(!debugDoingPaint); |
+ // TODO(abarth): It's very redundant to call this for every node in the |
+ // render tree during layout. We should instead compute a summary bit and |
+ // call it once at the end of layout. |
+ sky.view.scheduleFrame(); |
+ } |
+ void paint(RenderNodeDisplayList canvas) { } |
+ |
+ |
+ // HIT TESTING |
+ |
+ void handlePointer(sky.PointerEvent event) { |
+ // override this if you have a client, to hand it to the client |
+ // override this if you want to do anything with the pointer event |
+ } |
+ |
+ // RenderNode subclasses are expected to have a method like the |
+ // following (with the signature being whatever passes for coordinates |
+ // for this particular class): |
+ // bool hitTest(HitTestResult result, { sky.Point position }) { |
+ // // If (x,y) is not inside this node, then return false. (You |
+ // // can assume that the given coordinate is inside your |
+ // // dimensions. You only need to check this if you're an |
+ // // irregular shape, e.g. if you have a hole.) |
+ // // Otherwise: |
+ // // For each child that intersects x,y, in z-order starting from the top, |
+ // // call hitTest() for that child, passing it /result/, and the coordinates |
+ // // converted to the child's coordinate origin, and stop at the first child |
+ // // that returns true. |
+ // // Then, add yourself to /result/, and return true. |
+ // } |
+ // You must not add yourself to /result/ if you return false. |
+ |
+} |
+ |
+class HitTestResult { |
+ final List<RenderNode> path = new List<RenderNode>(); |
+ |
+ RenderNode get result => path.first; |
+ |
+ void add(RenderNode node) { |
+ path.add(node); |
+ } |
+} |
+ |
+ |
+// GENERIC MIXIN FOR RENDER NODES WITH ONE CHILD |
+ |
+abstract class RenderNodeWithChildMixin<ChildType extends RenderNode> { |
+ ChildType _child; |
+ ChildType get child => _child; |
+ void set child (ChildType value) { |
+ if (_child != null) |
+ dropChild(_child); |
+ _child = value; |
+ if (_child != null) |
+ adoptChild(_child); |
+ markNeedsLayout(); |
+ } |
+} |
+ |
+ |
+// GENERIC MIXIN FOR RENDER NODES WITH 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; |
+ } |
+} |