Chromium Code Reviews| Index: sky/framework/layout.dart |
| diff --git a/sky/framework/layout.dart b/sky/framework/layout.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..f7c175ab299e1a04aa82121bb21e580dc34cbfe2 |
| --- /dev/null |
| +++ b/sky/framework/layout.dart |
| @@ -0,0 +1,406 @@ |
| +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<String, Style> _cache = new HashMap<String, Style>(); |
| + |
| + 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._internal(className); |
| + }); |
| + } |
| + |
| + factory Style(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._internal(className); |
| + }); |
| + } |
| + |
| + Style._internal(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 |
| +} |
| + |
| +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; |
|
eseidel
2015/05/08 19:15:53
parentData or something more readable than "pos"?
Hixie
2015/05/08 19:39:17
I don't know that it'd be any more readable, but h
|
| + void setupPos(RenderNode child) { |
| + // override this to setup .pos correctly for your class |
| + if (child.pos is! ParentData) |
|
eseidel
2015/05/08 19:15:54
is!?
Hixie
2015/05/08 19:39:17
Dart for "is not".
|
| + 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); |
| + } |
| + |
| +} |
| + |
| +abstract class RenderBox extends RenderNode { } |
| + |
| + |
| +// 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>); |
| + assert(previousSibling != this); |
| + assert(previousSibling.pos.nextSibling == this); |
| + previousSibling.pos.nextSibling = nextSibling; |
| + } |
| + if (nextSibling != null) { |
| + assert(nextSibling.pos is ContainerParentDataMixin<ChildType>); |
| + assert(nextSibling != this); |
| + assert(nextSibling.pos.previousSibling == this); |
| + nextSibling.pos.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.pos is ParentDataType); |
| + while (child.pos.previousSibling != null) { |
| + assert(child.pos.previousSibling != child); |
| + 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) { |
| + assert(child.pos.nextSibling != child); |
| + child = child.pos.nextSibling; |
| + assert(child.pos 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.pos is ParentDataType); |
| + assert(child.pos.nextSibling == null); |
| + assert(child.pos.previousSibling == null); |
| + 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; |
| + assert(before.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) { |
| + assert(_firstChild == child); |
| + _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) { |
| + assert(_lastChild == child); |
| + _lastChild = child.pos.previousSibling; |
| + } else { |
| + assert(child.pos.nextSibling.pos is ParentDataType); |
| + child.pos.nextSibling.pos.previousSibling = child.pos.previousSibling; |
| + } |
| + child.pos.previousSibling = null; |
| + child.pos.nextSibling = null; |
| + dropChild(child); |
| + markNeedsLayout(); |
| + } |
| + void redepthChildren() { |
| + ChildType child = _firstChild; |
| + while (child != null) { |
| + redepthChild(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; |
| + } |
| + } |
| + |
| + ChildType get firstChild => _firstChild; |
| + ChildType get lastChild => _lastChild; |
| + ChildType childAfter(ChildType child) { |
| + assert(child.pos is ParentDataType); |
| + return child.pos.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<Style> styles) { |
| + _skyElement.setAttribute('class', styles.map((s) => s._className).join(' ')); |
| + } |
| + |
| + void updateInlineStyle(String newStyle) { |
| + _skyElement.setAttribute('style', newStyle); |
| + } |
| + |
| + double get width { |
| + sky.ClientRect rect = _skyElement.getBoundingClientRect(); |
| + return rect.width; |
| + } |
| + |
| + double get height { |
| + sky.ClientRect rect = _skyElement.getBoundingClientRect(); |
| + return rect.height; |
| + } |
| + |
| + Rect get rect { |
| + sky.ClientRect rect = _skyElement.getBoundingClientRect(); |
| + return new Rect(rect.left, rect.top, rect.width, rect.height); |
| + } |
| + |
| +} |
| + |
| +class CSSParentData extends ParentData with ContainerParentDataMixin<RenderCSS> { } |
| + |
| +class RenderCSSContainer extends RenderCSS with ContainerRenderNodeMixin<RenderCSS, CSSParentData> { |
| + |
| + RenderCSSContainer(debug) : super(debug); |
| + |
| + void setupPos(RenderNode child) { |
| + if (child.pos is! CSSParentData) |
| + child.pos = new CSSParentData(); |
| + } |
| + |
| + sky.Element createSkyElement() => sky.document.createElement('div') |
| + ..setAttribute('debug', debug.toString()); |
| + |
| + void markNeedsLayout() { } |
| + |
| + void add(RenderCSS child, { RenderCSS before }) { |
| + if (before != null) { |
| + assert(before._skyElement.parentNode != null); |
| + assert(before._skyElement.parentNode == _skyElement); |
| + } |
| + super.add(child, before: before); |
| + if (before != null) { |
| + before._skyElement.insertBefore([child._skyElement]); |
| + assert(child._skyElement.parentNode != null); |
| + assert(child._skyElement.parentNode == _skyElement); |
| + assert(child._skyElement.parentNode == before._skyElement.parentNode); |
| + } else { |
| + _skyElement.appendChild(child._skyElement); |
| + } |
| + } |
| + void remove(RenderCSS child) { |
| + child._skyElement.remove(); |
| + super.remove(child); |
| + } |
| + |
| +} |
| + |
| +class RenderCSSText extends RenderCSS { |
| + |
| + RenderCSSText(debug, String newData) : super(debug) { |
| + data = newData; |
| + } |
| + |
| + static final Style _displayParagraph = new Style('display:paragraph'); |
| + |
| + sky.Element createSkyElement() { |
| + return sky.document.createElement('div') |
| + ..setChild(new sky.Text()) |
| + ..setAttribute('class', _displayParagraph._className) |
| + ..setAttribute('debug', debug.toString()); |
| + } |
| + |
| + void set data (String value) { |
| + (_skyElement.firstChild as sky.Text).data = value; |
| + } |
| + |
| +} |
| + |
| +class RenderCSSImage extends RenderCSS { |
| + |
| + RenderCSSImage(debug, String src, num width, num height) : super(debug) { |
| + configure(src, width, height); |
| + } |
| + |
| + sky.Element createSkyElement() { |
| + return sky.document.createElement('img') |
| + ..setAttribute('debug', debug.toString()); |
| + } |
| + |
| + void configure(String src, num width, num height) { |
| + if (_skyElement.getAttribute('src') != src) |
| + _skyElement.setAttribute('src', src); |
| + _skyElement.style['width'] = '${width}px'; |
| + _skyElement.style['height'] = '${height}px'; |
| + } |
| + |
| +} |
| + |
| +class RenderCSSRoot extends RenderCSSContainer { |
| + RenderCSSRoot(debug) : super(debug); |
| + sky.Element createSkyElement() { |
| + var result = super.createSkyElement(); |
| + assert(result != null); |
| + sky.document.appendChild(result); |
| + return result; |
| + } |
| +} |
| + |
| + |
| +// legacy tools |
| +Map<sky.EventTarget, RenderNode> _eventTargetRegistry = {}; |
| +void registerEventTarget(sky.EventTarget e, RenderNode n) { |
| + _eventTargetRegistry[e] = n; |
| +} |
| +RenderNode bridgeEventTargetToRenderNode(sky.EventTarget e) { |
| + return _eventTargetRegistry[e]; |
| +} |
| + |
| + |
| + |
| + |
| +String _attributes(node) { |
| + if (node is! sky.Element) return ''; |
| + var r = ''; |
|
eseidel
2015/05/08 19:15:54
result?
Hixie
2015/05/08 19:39:16
Changed.
|
| + var attrs = node.getAttributes(); |
| + for (var attr in attrs) |
| + r += ' ${attr.name}="${attr.value}"'; |
| + return r; |
| +} |
| + |
| +void _serialiseDOM(node, [String prefix = '']) { |
| + if (node is sky.Text) { |
| + print(prefix + 'text: "' + node.data.replaceAll('\n', '\\n') + '"'); |
| + return; |
| + } |
| + print(prefix + node.toString() + _attributes(node)); |
| + var children = node.getChildNodes(); |
| + prefix = prefix + ' '; |
| + for (var child in children) |
|
eseidel
2015/05/08 19:15:53
is var needed? Should this be a type?
Hixie
2015/05/08 19:39:17
Either var or a type is needed. The way this funct
|
| + _serialiseDOM(child, prefix); |
| +} |
| + |
| +void dumpState() { |
| + _serialiseDOM(sky.document); |
| +} |