library layout; import 'node.dart'; import 'dart:sky' as sky; import 'dart:collection'; // UTILS // Bridge to legacy CSS-like style specification // Eventually we'll replace this with something else class Style { final String _className; static final Map _cache = new HashMap(); static int _nextStyleId = 1; static String _getNextClassName() { return "style${_nextStyleId++}"; } Style extend(Style other) { var className = "$_className ${other._className}"; return _cache.putIfAbsent(className, () { return new Style._construct(className); }); } factory Style(String styles) { assert(!styles.contains(new RegExp('\\b(display|flex|flex-direction)\\b'))); return new Style._addToCache(styles); } factory Style._addToCache(String styles) { return _cache.putIfAbsent(styles, () { var className = _getNextClassName(); sky.Element styleNode = sky.document.createElement('style'); styleNode.setChild(new sky.Text(".$className { $styles }")); sky.document.appendChild(styleNode); return new Style._construct(className); }); } Style._construct(this._className); } class Rect { const Rect(this.x, this.y, this.width, this.height); final double x; final double y; final double width; final double height; } // 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); } } abstract class RenderNode extends Node { // 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 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.parentData != null); child.parentData.detach(); super.dropChild(child); } } abstract class RenderBox extends RenderNode { } // GENERIC MIXIN FOR RENDER NODES THAT TAKE A LIST OF CHILDREN abstract class ContainerParentDataMixin { ChildType previousSibling; ChildType nextSibling; void detachSiblings() { if (previousSibling != null) { assert(previousSibling.parentData is ContainerParentDataMixin); assert(previousSibling != this); assert(previousSibling.parentData.nextSibling == this); previousSibling.parentData.nextSibling = nextSibling; } if (nextSibling != null) { assert(nextSibling.parentData is ContainerParentDataMixin); assert(nextSibling != this); assert(nextSibling.parentData.previousSibling == this); nextSibling.parentData.previousSibling = previousSibling; } previousSibling = null; nextSibling = null; } } abstract class ContainerRenderNodeMixin> 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); setAsChild(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; } } // CSS SHIMS abstract class RenderCSS extends RenderBox { dynamic debug; sky.Element _skyElement; RenderCSS(this.debug) { _skyElement = createSkyElement(); registerEventTarget(_skyElement, this); } sky.Element createSkyElement(); void updateStyles(List