Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 library layout; | |
| 2 | |
| 3 import 'node.dart'; | |
| 4 import 'dart:sky' as sky; | |
| 5 import 'dart:collection'; | |
| 6 | |
| 7 // UTILS | |
| 8 | |
| 9 // Bridge to legacy CSS-like style specification | |
| 10 // Eventually we'll replace this with something else | |
| 11 class Style { | |
| 12 final String _className; | |
| 13 static final Map<String, Style> _cache = new HashMap<String, Style>(); | |
| 14 | |
| 15 static int _nextStyleId = 1; | |
| 16 | |
| 17 static String _getNextClassName() { return "style${_nextStyleId++}"; } | |
| 18 | |
| 19 Style extend(Style other) { | |
| 20 var className = "$_className ${other._className}"; | |
| 21 | |
| 22 return _cache.putIfAbsent(className, () { | |
| 23 return new Style._internal(className); | |
| 24 }); | |
| 25 } | |
| 26 | |
| 27 factory Style(String styles) { | |
| 28 return _cache.putIfAbsent(styles, () { | |
| 29 var className = _getNextClassName(); | |
| 30 sky.Element styleNode = sky.document.createElement('style'); | |
| 31 styleNode.setChild(new sky.Text(".$className { $styles }")); | |
| 32 sky.document.appendChild(styleNode); | |
| 33 return new Style._internal(className); | |
| 34 }); | |
| 35 } | |
| 36 | |
| 37 Style._internal(this._className); | |
| 38 } | |
| 39 | |
| 40 class Rect { | |
| 41 const Rect(this.x, this.y, this.width, this.height); | |
| 42 final double x; | |
| 43 final double y; | |
| 44 final double width; | |
| 45 final double height; | |
| 46 } | |
| 47 | |
| 48 | |
| 49 // ABSTRACT LAYOUT | |
| 50 | |
| 51 class ParentData { | |
| 52 void detach() { | |
| 53 detachSiblings(); | |
| 54 } | |
| 55 void detachSiblings() { } // workaround for lack of inter-class mixins in Dart | |
| 56 } | |
| 57 | |
| 58 abstract class RenderNode extends Node { | |
| 59 | |
| 60 // LAYOUT | |
| 61 | |
| 62 // pos is only for use by the RenderNode that actually lays this | |
| 63 // node out, and any other nodes who happen to know exactly what | |
| 64 // kind of node that is. | |
| 65 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
| |
| 66 void setupPos(RenderNode child) { | |
| 67 // override this to setup .pos correctly for your class | |
| 68 if (child.pos is! ParentData) | |
|
eseidel
2015/05/08 19:15:54
is!?
Hixie
2015/05/08 19:39:17
Dart for "is not".
| |
| 69 child.pos = new ParentData(); | |
| 70 } | |
| 71 | |
| 72 void setAsChild(RenderNode child) { // only for use by subclasses | |
| 73 // call this whenever you decide a node is a child | |
| 74 assert(child != null); | |
| 75 setupPos(child); | |
| 76 super.setAsChild(child); | |
| 77 } | |
| 78 void dropChild(RenderNode child) { // only for use by subclasses | |
| 79 assert(child != null); | |
| 80 assert(child.pos != null); | |
| 81 child.pos.detach(); | |
| 82 super.dropChild(child); | |
| 83 } | |
| 84 | |
| 85 } | |
| 86 | |
| 87 abstract class RenderBox extends RenderNode { } | |
| 88 | |
| 89 | |
| 90 // GENERIC MIXIN FOR RENDER NODES THAT TAKE A LIST OF CHILDREN | |
| 91 | |
| 92 abstract class ContainerParentDataMixin<ChildType extends RenderNode> { | |
| 93 ChildType previousSibling; | |
| 94 ChildType nextSibling; | |
| 95 void detachSiblings() { | |
| 96 if (previousSibling != null) { | |
| 97 assert(previousSibling.pos is ContainerParentDataMixin<ChildType>); | |
| 98 assert(previousSibling != this); | |
| 99 assert(previousSibling.pos.nextSibling == this); | |
| 100 previousSibling.pos.nextSibling = nextSibling; | |
| 101 } | |
| 102 if (nextSibling != null) { | |
| 103 assert(nextSibling.pos is ContainerParentDataMixin<ChildType>); | |
| 104 assert(nextSibling != this); | |
| 105 assert(nextSibling.pos.previousSibling == this); | |
| 106 nextSibling.pos.previousSibling = previousSibling; | |
| 107 } | |
| 108 previousSibling = null; | |
| 109 nextSibling = null; | |
| 110 } | |
| 111 } | |
| 112 | |
| 113 abstract class ContainerRenderNodeMixin<ChildType extends RenderNode, ParentData Type extends ContainerParentDataMixin<ChildType>> implements RenderNode { | |
| 114 // abstract class that has only InlineNode children | |
| 115 | |
| 116 bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) { | |
| 117 assert(child.pos is ParentDataType); | |
| 118 while (child.pos.previousSibling != null) { | |
| 119 assert(child.pos.previousSibling != child); | |
| 120 child = child.pos.previousSibling; | |
| 121 assert(child.pos is ParentDataType); | |
| 122 } | |
| 123 return child == equals; | |
| 124 } | |
| 125 bool _debugUltimateNextSiblingOf(ChildType child, { ChildType equals }) { | |
| 126 assert(child.pos is ParentDataType); | |
| 127 while (child.pos.nextSibling != null) { | |
| 128 assert(child.pos.nextSibling != child); | |
| 129 child = child.pos.nextSibling; | |
| 130 assert(child.pos is ParentDataType); | |
| 131 } | |
| 132 return child == equals; | |
| 133 } | |
| 134 | |
| 135 ChildType _firstChild; | |
| 136 ChildType _lastChild; | |
| 137 void add(ChildType child, { ChildType before }) { | |
| 138 assert(child != this); | |
| 139 assert(before != this); | |
| 140 assert(child != before); | |
| 141 assert(child != _firstChild); | |
| 142 assert(child != _lastChild); | |
| 143 setAsChild(child); | |
| 144 assert(child.pos is ParentDataType); | |
| 145 assert(child.pos.nextSibling == null); | |
| 146 assert(child.pos.previousSibling == null); | |
| 147 if (before == null) { | |
| 148 // append at the end (_lastChild) | |
| 149 child.pos.previousSibling = _lastChild; | |
| 150 if (_lastChild != null) { | |
| 151 assert(_lastChild.pos is ParentDataType); | |
| 152 _lastChild.pos.nextSibling = child; | |
| 153 } | |
| 154 _lastChild = child; | |
| 155 if (_firstChild == null) | |
| 156 _firstChild = child; | |
| 157 } else { | |
| 158 assert(_firstChild != null); | |
| 159 assert(_lastChild != null); | |
| 160 assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild)); | |
| 161 assert(_debugUltimateNextSiblingOf(before, equals: _lastChild)); | |
| 162 assert(before.pos is ParentDataType); | |
| 163 if (before.pos.previousSibling == null) { | |
| 164 // insert at the start (_firstChild); we'll end up with two or more chil dren | |
| 165 assert(before == _firstChild); | |
| 166 child.pos.nextSibling = before; | |
| 167 before.pos.previousSibling = child; | |
| 168 _firstChild = child; | |
| 169 } else { | |
| 170 // insert in the middle; we'll end up with three or more children | |
| 171 // set up links from child to siblings | |
| 172 child.pos.previousSibling = before.pos.previousSibling; | |
| 173 child.pos.nextSibling = before; | |
| 174 // set up links from siblings to child | |
| 175 assert(child.pos.previousSibling.pos is ParentDataType); | |
| 176 assert(child.pos.nextSibling.pos is ParentDataType); | |
| 177 child.pos.previousSibling.pos.nextSibling = child; | |
| 178 child.pos.nextSibling.pos.previousSibling = child; | |
| 179 assert(before.pos.previousSibling == child); | |
| 180 } | |
| 181 } | |
| 182 markNeedsLayout(); | |
| 183 } | |
| 184 void remove(ChildType child) { | |
| 185 assert(child.pos is ParentDataType); | |
| 186 assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); | |
| 187 assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); | |
| 188 if (child.pos.previousSibling == null) { | |
| 189 assert(_firstChild == child); | |
| 190 _firstChild = child.pos.nextSibling; | |
| 191 } else { | |
| 192 assert(child.pos.previousSibling.pos is ParentDataType); | |
| 193 child.pos.previousSibling.pos.nextSibling = child.pos.nextSibling; | |
| 194 } | |
| 195 if (child.pos.nextSibling == null) { | |
| 196 assert(_lastChild == child); | |
| 197 _lastChild = child.pos.previousSibling; | |
| 198 } else { | |
| 199 assert(child.pos.nextSibling.pos is ParentDataType); | |
| 200 child.pos.nextSibling.pos.previousSibling = child.pos.previousSibling; | |
| 201 } | |
| 202 child.pos.previousSibling = null; | |
| 203 child.pos.nextSibling = null; | |
| 204 dropChild(child); | |
| 205 markNeedsLayout(); | |
| 206 } | |
| 207 void redepthChildren() { | |
| 208 ChildType child = _firstChild; | |
| 209 while (child != null) { | |
| 210 redepthChild(child); | |
| 211 assert(child.pos is ParentDataType); | |
| 212 child = child.pos.nextSibling; | |
| 213 } | |
| 214 } | |
| 215 void attachChildren() { | |
| 216 ChildType child = _firstChild; | |
| 217 while (child != null) { | |
| 218 child.attach(); | |
| 219 assert(child.pos is ParentDataType); | |
| 220 child = child.pos.nextSibling; | |
| 221 } | |
| 222 } | |
| 223 void detachChildren() { | |
| 224 ChildType child = _firstChild; | |
| 225 while (child != null) { | |
| 226 child.detach(); | |
| 227 assert(child.pos is ParentDataType); | |
| 228 child = child.pos.nextSibling; | |
| 229 } | |
| 230 } | |
| 231 | |
| 232 ChildType get firstChild => _firstChild; | |
| 233 ChildType get lastChild => _lastChild; | |
| 234 ChildType childAfter(ChildType child) { | |
| 235 assert(child.pos is ParentDataType); | |
| 236 return child.pos.nextSibling; | |
| 237 } | |
| 238 | |
| 239 } | |
| 240 | |
| 241 | |
| 242 // CSS SHIMS | |
| 243 | |
| 244 abstract class RenderCSS extends RenderBox { | |
| 245 | |
| 246 dynamic debug; | |
| 247 sky.Element _skyElement; | |
| 248 | |
| 249 RenderCSS(this.debug) { | |
| 250 _skyElement = createSkyElement(); | |
| 251 registerEventTarget(_skyElement, this); | |
| 252 } | |
| 253 | |
| 254 sky.Element createSkyElement(); | |
| 255 | |
| 256 void updateStyles(List<Style> styles) { | |
| 257 _skyElement.setAttribute('class', styles.map((s) => s._className).join(' ')) ; | |
| 258 } | |
| 259 | |
| 260 void updateInlineStyle(String newStyle) { | |
| 261 _skyElement.setAttribute('style', newStyle); | |
| 262 } | |
| 263 | |
| 264 double get width { | |
| 265 sky.ClientRect rect = _skyElement.getBoundingClientRect(); | |
| 266 return rect.width; | |
| 267 } | |
| 268 | |
| 269 double get height { | |
| 270 sky.ClientRect rect = _skyElement.getBoundingClientRect(); | |
| 271 return rect.height; | |
| 272 } | |
| 273 | |
| 274 Rect get rect { | |
| 275 sky.ClientRect rect = _skyElement.getBoundingClientRect(); | |
| 276 return new Rect(rect.left, rect.top, rect.width, rect.height); | |
| 277 } | |
| 278 | |
| 279 } | |
| 280 | |
| 281 class CSSParentData extends ParentData with ContainerParentDataMixin<RenderCSS> { } | |
| 282 | |
| 283 class RenderCSSContainer extends RenderCSS with ContainerRenderNodeMixin<RenderC SS, CSSParentData> { | |
| 284 | |
| 285 RenderCSSContainer(debug) : super(debug); | |
| 286 | |
| 287 void setupPos(RenderNode child) { | |
| 288 if (child.pos is! CSSParentData) | |
| 289 child.pos = new CSSParentData(); | |
| 290 } | |
| 291 | |
| 292 sky.Element createSkyElement() => sky.document.createElement('div') | |
| 293 ..setAttribute('debug', debug.toS tring()); | |
| 294 | |
| 295 void markNeedsLayout() { } | |
| 296 | |
| 297 void add(RenderCSS child, { RenderCSS before }) { | |
| 298 if (before != null) { | |
| 299 assert(before._skyElement.parentNode != null); | |
| 300 assert(before._skyElement.parentNode == _skyElement); | |
| 301 } | |
| 302 super.add(child, before: before); | |
| 303 if (before != null) { | |
| 304 before._skyElement.insertBefore([child._skyElement]); | |
| 305 assert(child._skyElement.parentNode != null); | |
| 306 assert(child._skyElement.parentNode == _skyElement); | |
| 307 assert(child._skyElement.parentNode == before._skyElement.parentNode); | |
| 308 } else { | |
| 309 _skyElement.appendChild(child._skyElement); | |
| 310 } | |
| 311 } | |
| 312 void remove(RenderCSS child) { | |
| 313 child._skyElement.remove(); | |
| 314 super.remove(child); | |
| 315 } | |
| 316 | |
| 317 } | |
| 318 | |
| 319 class RenderCSSText extends RenderCSS { | |
| 320 | |
| 321 RenderCSSText(debug, String newData) : super(debug) { | |
| 322 data = newData; | |
| 323 } | |
| 324 | |
| 325 static final Style _displayParagraph = new Style('display:paragraph'); | |
| 326 | |
| 327 sky.Element createSkyElement() { | |
| 328 return sky.document.createElement('div') | |
| 329 ..setChild(new sky.Text()) | |
| 330 ..setAttribute('class', _displayParagraph._className) | |
| 331 ..setAttribute('debug', debug.toString()); | |
| 332 } | |
| 333 | |
| 334 void set data (String value) { | |
| 335 (_skyElement.firstChild as sky.Text).data = value; | |
| 336 } | |
| 337 | |
| 338 } | |
| 339 | |
| 340 class RenderCSSImage extends RenderCSS { | |
| 341 | |
| 342 RenderCSSImage(debug, String src, num width, num height) : super(debug) { | |
| 343 configure(src, width, height); | |
| 344 } | |
| 345 | |
| 346 sky.Element createSkyElement() { | |
| 347 return sky.document.createElement('img') | |
| 348 ..setAttribute('debug', debug.toString()); | |
| 349 } | |
| 350 | |
| 351 void configure(String src, num width, num height) { | |
| 352 if (_skyElement.getAttribute('src') != src) | |
| 353 _skyElement.setAttribute('src', src); | |
| 354 _skyElement.style['width'] = '${width}px'; | |
| 355 _skyElement.style['height'] = '${height}px'; | |
| 356 } | |
| 357 | |
| 358 } | |
| 359 | |
| 360 class RenderCSSRoot extends RenderCSSContainer { | |
| 361 RenderCSSRoot(debug) : super(debug); | |
| 362 sky.Element createSkyElement() { | |
| 363 var result = super.createSkyElement(); | |
| 364 assert(result != null); | |
| 365 sky.document.appendChild(result); | |
| 366 return result; | |
| 367 } | |
| 368 } | |
| 369 | |
| 370 | |
| 371 // legacy tools | |
| 372 Map<sky.EventTarget, RenderNode> _eventTargetRegistry = {}; | |
| 373 void registerEventTarget(sky.EventTarget e, RenderNode n) { | |
| 374 _eventTargetRegistry[e] = n; | |
| 375 } | |
| 376 RenderNode bridgeEventTargetToRenderNode(sky.EventTarget e) { | |
| 377 return _eventTargetRegistry[e]; | |
| 378 } | |
| 379 | |
| 380 | |
| 381 | |
| 382 | |
| 383 String _attributes(node) { | |
| 384 if (node is! sky.Element) return ''; | |
| 385 var r = ''; | |
|
eseidel
2015/05/08 19:15:54
result?
Hixie
2015/05/08 19:39:16
Changed.
| |
| 386 var attrs = node.getAttributes(); | |
| 387 for (var attr in attrs) | |
| 388 r += ' ${attr.name}="${attr.value}"'; | |
| 389 return r; | |
| 390 } | |
| 391 | |
| 392 void _serialiseDOM(node, [String prefix = '']) { | |
| 393 if (node is sky.Text) { | |
| 394 print(prefix + 'text: "' + node.data.replaceAll('\n', '\\n') + '"'); | |
| 395 return; | |
| 396 } | |
| 397 print(prefix + node.toString() + _attributes(node)); | |
| 398 var children = node.getChildNodes(); | |
| 399 prefix = prefix + ' '; | |
| 400 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
| |
| 401 _serialiseDOM(child, prefix); | |
| 402 } | |
| 403 | |
| 404 void dumpState() { | |
| 405 _serialiseDOM(sky.document); | |
| 406 } | |
| OLD | NEW |