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 |