| OLD | NEW |
| (Empty) |
| 1 library layout; | |
| 2 | |
| 3 // This version of layout.dart is a shim version of layout2.dart that is backed
using CSS and <div>s. | |
| 4 | |
| 5 import 'node.dart'; | |
| 6 import 'dart:sky' as sky; | |
| 7 import 'dart:collection'; | |
| 8 | |
| 9 // UTILS | |
| 10 | |
| 11 // Bridge to legacy CSS-like style specification | |
| 12 // Eventually we'll replace this with something else | |
| 13 class Style { | |
| 14 final String _className; | |
| 15 static final Map<String, Style> _cache = new HashMap<String, Style>(); | |
| 16 | |
| 17 static int _nextStyleId = 1; | |
| 18 | |
| 19 static String _getNextClassName() { return "style${_nextStyleId++}"; } | |
| 20 | |
| 21 Style extend(Style other) { | |
| 22 var className = "$_className ${other._className}"; | |
| 23 | |
| 24 return _cache.putIfAbsent(className, () { | |
| 25 return new Style._construct(className); | |
| 26 }); | |
| 27 } | |
| 28 | |
| 29 factory Style(String styles) { | |
| 30 assert(!styles.contains(new RegExp('\\b(display|flex|flex-direction)\\b'))); | |
| 31 return new Style._addToCache(styles); | |
| 32 } | |
| 33 | |
| 34 factory Style._addToCache(String styles) { | |
| 35 return _cache.putIfAbsent(styles, () { | |
| 36 var className = _getNextClassName(); | |
| 37 sky.Element styleNode = sky.document.createElement('style'); | |
| 38 styleNode.setChild(new sky.Text(".$className { $styles }")); | |
| 39 sky.document.appendChild(styleNode); | |
| 40 return new Style._construct(className); | |
| 41 }); | |
| 42 } | |
| 43 | |
| 44 Style._construct(this._className); | |
| 45 } | |
| 46 | |
| 47 class Rect { | |
| 48 const Rect(this.x, this.y, this.width, this.height); | |
| 49 final double x; | |
| 50 final double y; | |
| 51 final double width; | |
| 52 final double height; | |
| 53 } | |
| 54 | |
| 55 | |
| 56 // ABSTRACT LAYOUT | |
| 57 | |
| 58 class ParentData { | |
| 59 void detach() { | |
| 60 detachSiblings(); | |
| 61 } | |
| 62 void detachSiblings() { } // workaround for lack of inter-class mixins in Dart | |
| 63 void merge(ParentData other) { | |
| 64 // override this in subclasses to merge in data from other into this | |
| 65 assert(other.runtimeType == this.runtimeType); | |
| 66 } | |
| 67 } | |
| 68 | |
| 69 abstract class RenderNode extends AbstractNode { | |
| 70 | |
| 71 // LAYOUT | |
| 72 | |
| 73 // parentData is only for use by the RenderNode that actually lays this | |
| 74 // node out, and any other nodes who happen to know exactly what | |
| 75 // kind of node that is. | |
| 76 ParentData parentData; | |
| 77 void setParentData(RenderNode child) { | |
| 78 // override this to setup .parentData correctly for your class | |
| 79 if (child.parentData is! ParentData) | |
| 80 child.parentData = new ParentData(); | |
| 81 } | |
| 82 | |
| 83 void adoptChild(RenderNode child) { // only for use by subclasses | |
| 84 // call this whenever you decide a node is a child | |
| 85 assert(child != null); | |
| 86 setParentData(child); | |
| 87 super.adoptChild(child); | |
| 88 } | |
| 89 void dropChild(RenderNode child) { // only for use by subclasses | |
| 90 assert(child != null); | |
| 91 assert(child.parentData != null); | |
| 92 child.parentData.detach(); | |
| 93 super.dropChild(child); | |
| 94 } | |
| 95 | |
| 96 } | |
| 97 | |
| 98 abstract class RenderBox extends RenderNode { } | |
| 99 | |
| 100 | |
| 101 // GENERIC MIXIN FOR RENDER NODES THAT TAKE A LIST OF CHILDREN | |
| 102 | |
| 103 abstract class ContainerParentDataMixin<ChildType extends RenderNode> { | |
| 104 ChildType previousSibling; | |
| 105 ChildType nextSibling; | |
| 106 void detachSiblings() { | |
| 107 if (previousSibling != null) { | |
| 108 assert(previousSibling.parentData is ContainerParentDataMixin<ChildType>); | |
| 109 assert(previousSibling != this); | |
| 110 assert(previousSibling.parentData.nextSibling == this); | |
| 111 previousSibling.parentData.nextSibling = nextSibling; | |
| 112 } | |
| 113 if (nextSibling != null) { | |
| 114 assert(nextSibling.parentData is ContainerParentDataMixin<ChildType>); | |
| 115 assert(nextSibling != this); | |
| 116 assert(nextSibling.parentData.previousSibling == this); | |
| 117 nextSibling.parentData.previousSibling = previousSibling; | |
| 118 } | |
| 119 previousSibling = null; | |
| 120 nextSibling = null; | |
| 121 } | |
| 122 } | |
| 123 | |
| 124 abstract class ContainerRenderNodeMixin<ChildType extends RenderNode, ParentData
Type extends ContainerParentDataMixin<ChildType>> implements RenderNode { | |
| 125 // abstract class that has only InlineNode children | |
| 126 | |
| 127 bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) { | |
| 128 assert(child.parentData is ParentDataType); | |
| 129 while (child.parentData.previousSibling != null) { | |
| 130 assert(child.parentData.previousSibling != child); | |
| 131 child = child.parentData.previousSibling; | |
| 132 assert(child.parentData is ParentDataType); | |
| 133 } | |
| 134 return child == equals; | |
| 135 } | |
| 136 bool _debugUltimateNextSiblingOf(ChildType child, { ChildType equals }) { | |
| 137 assert(child.parentData is ParentDataType); | |
| 138 while (child.parentData.nextSibling != null) { | |
| 139 assert(child.parentData.nextSibling != child); | |
| 140 child = child.parentData.nextSibling; | |
| 141 assert(child.parentData is ParentDataType); | |
| 142 } | |
| 143 return child == equals; | |
| 144 } | |
| 145 | |
| 146 ChildType _firstChild; | |
| 147 ChildType _lastChild; | |
| 148 void add(ChildType child, { ChildType before }) { | |
| 149 assert(child != this); | |
| 150 assert(before != this); | |
| 151 assert(child != before); | |
| 152 assert(child != _firstChild); | |
| 153 assert(child != _lastChild); | |
| 154 adoptChild(child); | |
| 155 assert(child.parentData is ParentDataType); | |
| 156 assert(child.parentData.nextSibling == null); | |
| 157 assert(child.parentData.previousSibling == null); | |
| 158 if (before == null) { | |
| 159 // append at the end (_lastChild) | |
| 160 child.parentData.previousSibling = _lastChild; | |
| 161 if (_lastChild != null) { | |
| 162 assert(_lastChild.parentData is ParentDataType); | |
| 163 _lastChild.parentData.nextSibling = child; | |
| 164 } | |
| 165 _lastChild = child; | |
| 166 if (_firstChild == null) | |
| 167 _firstChild = child; | |
| 168 } else { | |
| 169 assert(_firstChild != null); | |
| 170 assert(_lastChild != null); | |
| 171 assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild)); | |
| 172 assert(_debugUltimateNextSiblingOf(before, equals: _lastChild)); | |
| 173 assert(before.parentData is ParentDataType); | |
| 174 if (before.parentData.previousSibling == null) { | |
| 175 // insert at the start (_firstChild); we'll end up with two or more chil
dren | |
| 176 assert(before == _firstChild); | |
| 177 child.parentData.nextSibling = before; | |
| 178 before.parentData.previousSibling = child; | |
| 179 _firstChild = child; | |
| 180 } else { | |
| 181 // insert in the middle; we'll end up with three or more children | |
| 182 // set up links from child to siblings | |
| 183 child.parentData.previousSibling = before.parentData.previousSibling; | |
| 184 child.parentData.nextSibling = before; | |
| 185 // set up links from siblings to child | |
| 186 assert(child.parentData.previousSibling.parentData is ParentDataType); | |
| 187 assert(child.parentData.nextSibling.parentData is ParentDataType); | |
| 188 child.parentData.previousSibling.parentData.nextSibling = child; | |
| 189 child.parentData.nextSibling.parentData.previousSibling = child; | |
| 190 assert(before.parentData.previousSibling == child); | |
| 191 } | |
| 192 } | |
| 193 markNeedsLayout(); | |
| 194 } | |
| 195 void remove(ChildType child) { | |
| 196 assert(child.parentData is ParentDataType); | |
| 197 assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); | |
| 198 assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); | |
| 199 if (child.parentData.previousSibling == null) { | |
| 200 assert(_firstChild == child); | |
| 201 _firstChild = child.parentData.nextSibling; | |
| 202 } else { | |
| 203 assert(child.parentData.previousSibling.parentData is ParentDataType); | |
| 204 child.parentData.previousSibling.parentData.nextSibling = child.parentData
.nextSibling; | |
| 205 } | |
| 206 if (child.parentData.nextSibling == null) { | |
| 207 assert(_lastChild == child); | |
| 208 _lastChild = child.parentData.previousSibling; | |
| 209 } else { | |
| 210 assert(child.parentData.nextSibling.parentData is ParentDataType); | |
| 211 child.parentData.nextSibling.parentData.previousSibling = child.parentData
.previousSibling; | |
| 212 } | |
| 213 child.parentData.previousSibling = null; | |
| 214 child.parentData.nextSibling = null; | |
| 215 dropChild(child); | |
| 216 markNeedsLayout(); | |
| 217 } | |
| 218 void redepthChildren() { | |
| 219 ChildType child = _firstChild; | |
| 220 while (child != null) { | |
| 221 redepthChild(child); | |
| 222 assert(child.parentData is ParentDataType); | |
| 223 child = child.parentData.nextSibling; | |
| 224 } | |
| 225 } | |
| 226 void attachChildren() { | |
| 227 ChildType child = _firstChild; | |
| 228 while (child != null) { | |
| 229 child.attach(); | |
| 230 assert(child.parentData is ParentDataType); | |
| 231 child = child.parentData.nextSibling; | |
| 232 } | |
| 233 } | |
| 234 void detachChildren() { | |
| 235 ChildType child = _firstChild; | |
| 236 while (child != null) { | |
| 237 child.detach(); | |
| 238 assert(child.parentData is ParentDataType); | |
| 239 child = child.parentData.nextSibling; | |
| 240 } | |
| 241 } | |
| 242 | |
| 243 ChildType get firstChild => _firstChild; | |
| 244 ChildType get lastChild => _lastChild; | |
| 245 ChildType childAfter(ChildType child) { | |
| 246 assert(child.parentData is ParentDataType); | |
| 247 return child.parentData.nextSibling; | |
| 248 } | |
| 249 | |
| 250 } | |
| 251 | |
| 252 | |
| 253 // CSS SHIMS | |
| 254 | |
| 255 abstract class RenderCSS extends RenderBox { | |
| 256 | |
| 257 dynamic debug; | |
| 258 sky.Element _skyElement; | |
| 259 | |
| 260 RenderCSS(this.debug) { | |
| 261 _skyElement = createSkyElement(); | |
| 262 registerEventTarget(_skyElement, this); | |
| 263 } | |
| 264 | |
| 265 sky.Element createSkyElement(); | |
| 266 | |
| 267 void updateStyles(List<Style> styles) { | |
| 268 _skyElement.setAttribute('class', stylesToClasses(styles)); | |
| 269 } | |
| 270 | |
| 271 String stylesToClasses(List<Style> styles) { | |
| 272 return styles.map((s) => s._className).join(' '); | |
| 273 } | |
| 274 | |
| 275 String _inlineStyles = ''; | |
| 276 String _additionalStylesFromParent = ''; // used internally to propagate paren
tData settings to the child | |
| 277 | |
| 278 void updateInlineStyle(String newStyle) { | |
| 279 assert(newStyle == null || !newStyle.contains(new RegExp('\\b(display|flex|f
lex-direction)\\b'))); | |
| 280 _inlineStyles = newStyle != null ? newStyle : ''; | |
| 281 _updateInlineStyleAttribute(); | |
| 282 } | |
| 283 | |
| 284 void _updateInlineStyleAttribute() { | |
| 285 if ((_inlineStyles != '') && (_additionalStylesFromParent != '')) | |
| 286 _skyElement.setAttribute('style', "$_inlineStyles;$_additionalStylesFromPa
rent"); | |
| 287 else | |
| 288 _skyElement.setAttribute('style', "$_inlineStyles$_additionalStylesFromPar
ent"); | |
| 289 } | |
| 290 | |
| 291 double get width { | |
| 292 sky.ClientRect rect = _skyElement.getBoundingClientRect(); | |
| 293 return rect.width; | |
| 294 } | |
| 295 | |
| 296 double get height { | |
| 297 sky.ClientRect rect = _skyElement.getBoundingClientRect(); | |
| 298 return rect.height; | |
| 299 } | |
| 300 | |
| 301 Rect get rect { | |
| 302 sky.ClientRect rect = _skyElement.getBoundingClientRect(); | |
| 303 return new Rect(rect.left, rect.top, rect.width, rect.height); | |
| 304 } | |
| 305 | |
| 306 } | |
| 307 | |
| 308 class CSSParentData extends ParentData with ContainerParentDataMixin<RenderCSS>
{ } | |
| 309 | |
| 310 class RenderCSSContainer extends RenderCSS with ContainerRenderNodeMixin<RenderC
SS, CSSParentData> { | |
| 311 | |
| 312 RenderCSSContainer(debug) : super(debug); | |
| 313 | |
| 314 void setParentData(RenderNode child) { | |
| 315 if (child.parentData is! CSSParentData) | |
| 316 child.parentData = new CSSParentData(); | |
| 317 } | |
| 318 | |
| 319 sky.Element createSkyElement() => sky.document.createElement('div') | |
| 320 ..setAttribute('debug', debug.toS
tring()); | |
| 321 | |
| 322 void markNeedsLayout() { } | |
| 323 | |
| 324 void add(RenderCSS child, { RenderCSS before }) { | |
| 325 if (before != null) { | |
| 326 assert(before._skyElement.parentNode != null); | |
| 327 assert(before._skyElement.parentNode == _skyElement); | |
| 328 } | |
| 329 super.add(child, before: before); | |
| 330 if (before != null) { | |
| 331 before._skyElement.insertBefore([child._skyElement]); | |
| 332 assert(child._skyElement.parentNode != null); | |
| 333 assert(child._skyElement.parentNode == _skyElement); | |
| 334 assert(child._skyElement.parentNode == before._skyElement.parentNode); | |
| 335 } else { | |
| 336 _skyElement.appendChild(child._skyElement); | |
| 337 } | |
| 338 } | |
| 339 void remove(RenderCSS child) { | |
| 340 child._skyElement.remove(); | |
| 341 super.remove(child); | |
| 342 } | |
| 343 | |
| 344 } | |
| 345 | |
| 346 class FlexBoxParentData extends CSSParentData { | |
| 347 int flex; | |
| 348 void merge(FlexBoxParentData other) { | |
| 349 if (other.flex != null) | |
| 350 flex = other.flex; | |
| 351 super.merge(other); | |
| 352 } | |
| 353 } | |
| 354 | |
| 355 enum FlexDirection { Row, Column } | |
| 356 | |
| 357 class RenderCSSFlex extends RenderCSSContainer { | |
| 358 | |
| 359 RenderCSSFlex(debug, FlexDirection direction) : _direction = direction, super(
debug); | |
| 360 | |
| 361 FlexDirection _direction; | |
| 362 FlexDirection get direction => _direction; | |
| 363 void set direction (FlexDirection value) { | |
| 364 _direction = value; | |
| 365 markNeedsLayout(); | |
| 366 } | |
| 367 | |
| 368 void setParentData(RenderNode child) { | |
| 369 if (child.parentData is! FlexBoxParentData) | |
| 370 child.parentData = new FlexBoxParentData(); | |
| 371 } | |
| 372 | |
| 373 static final Style _displayFlex = new Style._addToCache('display:flex'); | |
| 374 static final Style _displayFlexRow = new Style._addToCache('flex-direction:row
'); | |
| 375 static final Style _displayFlexColumn = new Style._addToCache('flex-direction:
column'); | |
| 376 | |
| 377 String stylesToClasses(List<Style> styles) { | |
| 378 var settings = _displayFlex._className; | |
| 379 switch (_direction) { | |
| 380 case FlexDirection.Row: settings += ' ' + _displayFlexRow._className; brea
k; | |
| 381 case FlexDirection.Column: settings += ' ' + _displayFlexColumn._className
; break; | |
| 382 } | |
| 383 return super.stylesToClasses(styles) + ' ' + settings; | |
| 384 } | |
| 385 | |
| 386 void markNeedsLayout() { | |
| 387 super.markNeedsLayout(); | |
| 388 | |
| 389 // pretend we did the layout: | |
| 390 RenderCSS child = _firstChild; | |
| 391 while (child != null) { | |
| 392 assert(child.parentData is FlexBoxParentData); | |
| 393 if (child.parentData.flex != null) { | |
| 394 child._additionalStylesFromParent = 'flex:${child.parentData.flex}'; | |
| 395 child._updateInlineStyleAttribute(); | |
| 396 } | |
| 397 child = child.parentData.nextSibling; | |
| 398 } | |
| 399 } | |
| 400 | |
| 401 } | |
| 402 | |
| 403 class StackParentData extends CSSParentData { | |
| 404 double top; | |
| 405 double left; | |
| 406 double right; | |
| 407 double bottom; | |
| 408 void merge(StackParentData other) { | |
| 409 if (other.top != null) | |
| 410 top = other.top; | |
| 411 if (other.left != null) | |
| 412 left = other.left; | |
| 413 if (other.right != null) | |
| 414 right = other.right; | |
| 415 if (other.bottom != null) | |
| 416 bottom = other.bottom; | |
| 417 super.merge(other); | |
| 418 } | |
| 419 } | |
| 420 | |
| 421 class RenderCSSStack extends RenderCSSContainer { | |
| 422 | |
| 423 RenderCSSStack(debug) : super(debug); | |
| 424 | |
| 425 void setParentData(RenderNode child) { | |
| 426 if (child.parentData is! StackParentData) | |
| 427 child.parentData = new StackParentData(); | |
| 428 } | |
| 429 | |
| 430 static final Style _displayPosition = new Style._addToCache('transform:transla
teX(0);position:relative'); | |
| 431 | |
| 432 String stylesToClasses(List<Style> styles) { | |
| 433 return super.stylesToClasses(styles) + ' ' + _displayPosition._className; | |
| 434 } | |
| 435 | |
| 436 void markNeedsLayout() { | |
| 437 super.markNeedsLayout(); | |
| 438 | |
| 439 // pretend we did the layout: | |
| 440 RenderCSS child = _firstChild; | |
| 441 while (child != null) { | |
| 442 assert(child.parentData is StackParentData); | |
| 443 var style = 'position:absolute;'; | |
| 444 if (child.parentData.top != null) | |
| 445 style += 'top:${child.parentData.top};'; | |
| 446 if (child.parentData.left != null) | |
| 447 style += 'left:${child.parentData.left};'; | |
| 448 if (child.parentData.right != null) | |
| 449 style += 'right:${child.parentData.right};'; | |
| 450 if (child.parentData.bottom != null) | |
| 451 style += 'bottom:${child.parentData.bottom};'; | |
| 452 child._additionalStylesFromParent = style; | |
| 453 child._updateInlineStyleAttribute(); | |
| 454 child = child.parentData.nextSibling; | |
| 455 } | |
| 456 } | |
| 457 | |
| 458 } | |
| 459 | |
| 460 class RenderCSSParagraph extends RenderCSSContainer { | |
| 461 | |
| 462 RenderCSSParagraph(debug) : super(debug); | |
| 463 | |
| 464 static final Style _displayParagraph = new Style._addToCache('display:paragrap
h'); | |
| 465 | |
| 466 String stylesToClasses(List<Style> styles) { | |
| 467 return super.stylesToClasses(styles) + ' ' + _displayParagraph._className; | |
| 468 } | |
| 469 | |
| 470 } | |
| 471 | |
| 472 class RenderCSSInline extends RenderCSS { | |
| 473 | |
| 474 RenderCSSInline(debug, String newData) : super(debug) { | |
| 475 data = newData; | |
| 476 } | |
| 477 | |
| 478 static final Style _displayInline = new Style._addToCache('display:inline'); | |
| 479 | |
| 480 String stylesToClasses(List<Style> styles) { | |
| 481 return super.stylesToClasses(styles) + ' ' + _displayInline._className; | |
| 482 } | |
| 483 | |
| 484 sky.Element createSkyElement() { | |
| 485 return sky.document.createElement('div') | |
| 486 ..setChild(new sky.Text()) | |
| 487 ..setAttribute('debug', debug.toString()); | |
| 488 } | |
| 489 | |
| 490 void set data (String value) { | |
| 491 (_skyElement.firstChild as sky.Text).data = value; | |
| 492 } | |
| 493 | |
| 494 } | |
| 495 | |
| 496 class RenderCSSImage extends RenderCSS { | |
| 497 | |
| 498 RenderCSSImage(debug, String src, num width, num height) : super(debug) { | |
| 499 configure(src, width, height); | |
| 500 } | |
| 501 | |
| 502 sky.Element createSkyElement() { | |
| 503 return sky.document.createElement('img') | |
| 504 ..setAttribute('debug', debug.toString()); | |
| 505 } | |
| 506 | |
| 507 void configure(String src, num width, num height) { | |
| 508 if (_skyElement.getAttribute('src') != src) | |
| 509 _skyElement.setAttribute('src', src); | |
| 510 _skyElement.style['width'] = '${width}px'; | |
| 511 _skyElement.style['height'] = '${height}px'; | |
| 512 } | |
| 513 | |
| 514 } | |
| 515 | |
| 516 class RenderCSSRoot extends RenderCSSContainer { | |
| 517 RenderCSSRoot(debug) : super(debug); | |
| 518 sky.Element createSkyElement() { | |
| 519 var result = super.createSkyElement(); | |
| 520 assert(result != null); | |
| 521 sky.document.appendChild(result); | |
| 522 return result; | |
| 523 } | |
| 524 } | |
| 525 | |
| 526 | |
| 527 // legacy tools | |
| 528 Map<sky.EventTarget, RenderNode> _eventTargetRegistry = {}; | |
| 529 void registerEventTarget(sky.EventTarget e, RenderNode n) { | |
| 530 _eventTargetRegistry[e] = n; | |
| 531 } | |
| 532 RenderNode bridgeEventTargetToRenderNode(sky.EventTarget e) { | |
| 533 return _eventTargetRegistry[e]; | |
| 534 } | |
| 535 | |
| 536 | |
| 537 | |
| 538 | |
| 539 String _attributes(node) { | |
| 540 if (node is! sky.Element) return ''; | |
| 541 var result = ''; | |
| 542 var attrs = node.getAttributes(); | |
| 543 for (var attr in attrs) | |
| 544 result += ' ${attr.name}="${attr.value}"'; | |
| 545 return result; | |
| 546 } | |
| 547 | |
| 548 void _serialiseDOM(node, [String prefix = '']) { | |
| 549 if (node is sky.Text) { | |
| 550 print(prefix + 'text: "' + node.data.replaceAll('\n', '\\n') + '"'); | |
| 551 return; | |
| 552 } | |
| 553 print(prefix + node.toString() + _attributes(node)); | |
| 554 var children = node.getChildNodes(); | |
| 555 prefix = prefix + ' '; | |
| 556 for (var child in children) | |
| 557 _serialiseDOM(child, prefix); | |
| 558 } | |
| 559 | |
| 560 void dumpState() { | |
| 561 _serialiseDOM(sky.document); | |
| 562 } | |
| OLD | NEW |