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 void merge(ParentData other) { | |
57 // override this in subclasses to merge in data from other into this | |
58 assert(other.runtimeType == this.runtimeType); | |
59 } | |
60 } | |
61 | |
62 abstract class RenderNode extends Node { | |
63 | |
64 // LAYOUT | |
65 | |
66 // parentData is only for use by the RenderNode that actually lays this | |
67 // node out, and any other nodes who happen to know exactly what | |
68 // kind of node that is. | |
69 ParentData parentData; | |
70 void setupPos(RenderNode child) { | |
71 // override this to setup .parentData correctly for your class | |
72 if (child.parentData is! ParentData) | |
73 child.parentData = new ParentData(); | |
74 } | |
75 | |
76 void setAsChild(RenderNode child) { // only for use by subclasses | |
77 // call this whenever you decide a node is a child | |
78 assert(child != null); | |
79 setupPos(child); | |
80 super.setAsChild(child); | |
81 } | |
82 void dropChild(RenderNode child) { // only for use by subclasses | |
83 assert(child != null); | |
84 assert(child.parentData != null); | |
85 child.parentData.detach(); | |
86 super.dropChild(child); | |
87 } | |
88 | |
89 } | |
90 | |
91 abstract class RenderBox extends RenderNode { } | |
92 | |
93 | |
94 // GENERIC MIXIN FOR RENDER NODES THAT TAKE A LIST OF CHILDREN | |
95 | |
96 abstract class ContainerParentDataMixin<ChildType extends RenderNode> { | |
97 ChildType previousSibling; | |
98 ChildType nextSibling; | |
99 void detachSiblings() { | |
100 if (previousSibling != null) { | |
101 assert(previousSibling.parentData is ContainerParentDataMixin<ChildType>); | |
102 assert(previousSibling != this); | |
103 assert(previousSibling.parentData.nextSibling == this); | |
104 previousSibling.parentData.nextSibling = nextSibling; | |
105 } | |
106 if (nextSibling != null) { | |
107 assert(nextSibling.parentData is ContainerParentDataMixin<ChildType>); | |
108 assert(nextSibling != this); | |
109 assert(nextSibling.parentData.previousSibling == this); | |
110 nextSibling.parentData.previousSibling = previousSibling; | |
111 } | |
112 previousSibling = null; | |
113 nextSibling = null; | |
114 } | |
115 } | |
116 | |
117 abstract class ContainerRenderNodeMixin<ChildType extends RenderNode, ParentData
Type extends ContainerParentDataMixin<ChildType>> implements RenderNode { | |
118 // abstract class that has only InlineNode children | |
119 | |
120 bool _debugUltimatePreviousSiblingOf(ChildType child, { ChildType equals }) { | |
121 assert(child.parentData is ParentDataType); | |
122 while (child.parentData.previousSibling != null) { | |
123 assert(child.parentData.previousSibling != child); | |
124 child = child.parentData.previousSibling; | |
125 assert(child.parentData is ParentDataType); | |
126 } | |
127 return child == equals; | |
128 } | |
129 bool _debugUltimateNextSiblingOf(ChildType child, { ChildType equals }) { | |
130 assert(child.parentData is ParentDataType); | |
131 while (child.parentData.nextSibling != null) { | |
132 assert(child.parentData.nextSibling != child); | |
133 child = child.parentData.nextSibling; | |
134 assert(child.parentData is ParentDataType); | |
135 } | |
136 return child == equals; | |
137 } | |
138 | |
139 ChildType _firstChild; | |
140 ChildType _lastChild; | |
141 void add(ChildType child, { ChildType before }) { | |
142 assert(child != this); | |
143 assert(before != this); | |
144 assert(child != before); | |
145 assert(child != _firstChild); | |
146 assert(child != _lastChild); | |
147 setAsChild(child); | |
148 assert(child.parentData is ParentDataType); | |
149 assert(child.parentData.nextSibling == null); | |
150 assert(child.parentData.previousSibling == null); | |
151 if (before == null) { | |
152 // append at the end (_lastChild) | |
153 child.parentData.previousSibling = _lastChild; | |
154 if (_lastChild != null) { | |
155 assert(_lastChild.parentData is ParentDataType); | |
156 _lastChild.parentData.nextSibling = child; | |
157 } | |
158 _lastChild = child; | |
159 if (_firstChild == null) | |
160 _firstChild = child; | |
161 } else { | |
162 assert(_firstChild != null); | |
163 assert(_lastChild != null); | |
164 assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild)); | |
165 assert(_debugUltimateNextSiblingOf(before, equals: _lastChild)); | |
166 assert(before.parentData is ParentDataType); | |
167 if (before.parentData.previousSibling == null) { | |
168 // insert at the start (_firstChild); we'll end up with two or more chil
dren | |
169 assert(before == _firstChild); | |
170 child.parentData.nextSibling = before; | |
171 before.parentData.previousSibling = child; | |
172 _firstChild = child; | |
173 } else { | |
174 // insert in the middle; we'll end up with three or more children | |
175 // set up links from child to siblings | |
176 child.parentData.previousSibling = before.parentData.previousSibling; | |
177 child.parentData.nextSibling = before; | |
178 // set up links from siblings to child | |
179 assert(child.parentData.previousSibling.parentData is ParentDataType); | |
180 assert(child.parentData.nextSibling.parentData is ParentDataType); | |
181 child.parentData.previousSibling.parentData.nextSibling = child; | |
182 child.parentData.nextSibling.parentData.previousSibling = child; | |
183 assert(before.parentData.previousSibling == child); | |
184 } | |
185 } | |
186 markNeedsLayout(); | |
187 } | |
188 void remove(ChildType child) { | |
189 assert(child.parentData is ParentDataType); | |
190 assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); | |
191 assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); | |
192 if (child.parentData.previousSibling == null) { | |
193 assert(_firstChild == child); | |
194 _firstChild = child.parentData.nextSibling; | |
195 } else { | |
196 assert(child.parentData.previousSibling.parentData is ParentDataType); | |
197 child.parentData.previousSibling.parentData.nextSibling = child.parentData
.nextSibling; | |
198 } | |
199 if (child.parentData.nextSibling == null) { | |
200 assert(_lastChild == child); | |
201 _lastChild = child.parentData.previousSibling; | |
202 } else { | |
203 assert(child.parentData.nextSibling.parentData is ParentDataType); | |
204 child.parentData.nextSibling.parentData.previousSibling = child.parentData
.previousSibling; | |
205 } | |
206 child.parentData.previousSibling = null; | |
207 child.parentData.nextSibling = null; | |
208 dropChild(child); | |
209 markNeedsLayout(); | |
210 } | |
211 void redepthChildren() { | |
212 ChildType child = _firstChild; | |
213 while (child != null) { | |
214 redepthChild(child); | |
215 assert(child.parentData is ParentDataType); | |
216 child = child.parentData.nextSibling; | |
217 } | |
218 } | |
219 void attachChildren() { | |
220 ChildType child = _firstChild; | |
221 while (child != null) { | |
222 child.attach(); | |
223 assert(child.parentData is ParentDataType); | |
224 child = child.parentData.nextSibling; | |
225 } | |
226 } | |
227 void detachChildren() { | |
228 ChildType child = _firstChild; | |
229 while (child != null) { | |
230 child.detach(); | |
231 assert(child.parentData is ParentDataType); | |
232 child = child.parentData.nextSibling; | |
233 } | |
234 } | |
235 | |
236 ChildType get firstChild => _firstChild; | |
237 ChildType get lastChild => _lastChild; | |
238 ChildType childAfter(ChildType child) { | |
239 assert(child.parentData is ParentDataType); | |
240 return child.parentData.nextSibling; | |
241 } | |
242 | |
243 } | |
244 | |
245 | |
246 // CSS SHIMS | |
247 | |
248 abstract class RenderCSS extends RenderBox { | |
249 | |
250 dynamic debug; | |
251 sky.Element _skyElement; | |
252 | |
253 RenderCSS(this.debug) { | |
254 _skyElement = createSkyElement(); | |
255 registerEventTarget(_skyElement, this); | |
256 } | |
257 | |
258 sky.Element createSkyElement(); | |
259 | |
260 void updateStyles(List<Style> styles) { | |
261 _skyElement.setAttribute('class', stylesToClasses(styles)); | |
262 } | |
263 | |
264 String stylesToClasses(List<Style> styles) { | |
265 return styles.map((s) => s._className).join(' '); | |
266 } | |
267 | |
268 String _inlineStyles = ''; | |
269 String _additionalStylesFromParent = ''; // used internally to propagate paren
tData settings to the child | |
270 | |
271 void updateInlineStyle(String newStyle) { | |
272 _inlineStyles = newStyle != null ? newStyle : ''; | |
273 _updateInlineStyleAttribute(); | |
274 } | |
275 | |
276 void _updateInlineStyleAttribute() { | |
277 if ((_inlineStyles != '') && (_additionalStylesFromParent != '')) | |
278 _skyElement.setAttribute('style', "$_inlineStyles;$_additionalStylesFromPa
rent"); | |
279 else | |
280 _skyElement.setAttribute('style', "$_inlineStyles$_additionalStylesFromPar
ent"); | |
281 } | |
282 | |
283 double get width { | |
284 sky.ClientRect rect = _skyElement.getBoundingClientRect(); | |
285 return rect.width; | |
286 } | |
287 | |
288 double get height { | |
289 sky.ClientRect rect = _skyElement.getBoundingClientRect(); | |
290 return rect.height; | |
291 } | |
292 | |
293 Rect get rect { | |
294 sky.ClientRect rect = _skyElement.getBoundingClientRect(); | |
295 return new Rect(rect.left, rect.top, rect.width, rect.height); | |
296 } | |
297 | |
298 } | |
299 | |
300 class CSSParentData extends ParentData with ContainerParentDataMixin<RenderCSS>
{ } | |
301 | |
302 class RenderCSSContainer extends RenderCSS with ContainerRenderNodeMixin<RenderC
SS, CSSParentData> { | |
303 | |
304 RenderCSSContainer(debug) : super(debug); | |
305 | |
306 void setupPos(RenderNode child) { | |
307 if (child.parentData is! CSSParentData) | |
308 child.parentData = new CSSParentData(); | |
309 } | |
310 | |
311 sky.Element createSkyElement() => sky.document.createElement('div') | |
312 ..setAttribute('debug', debug.toS
tring()); | |
313 | |
314 void markNeedsLayout() { } | |
315 | |
316 void add(RenderCSS child, { RenderCSS before }) { | |
317 if (before != null) { | |
318 assert(before._skyElement.parentNode != null); | |
319 assert(before._skyElement.parentNode == _skyElement); | |
320 } | |
321 super.add(child, before: before); | |
322 if (before != null) { | |
323 before._skyElement.insertBefore([child._skyElement]); | |
324 assert(child._skyElement.parentNode != null); | |
325 assert(child._skyElement.parentNode == _skyElement); | |
326 assert(child._skyElement.parentNode == before._skyElement.parentNode); | |
327 } else { | |
328 _skyElement.appendChild(child._skyElement); | |
329 } | |
330 } | |
331 void remove(RenderCSS child) { | |
332 child._skyElement.remove(); | |
333 super.remove(child); | |
334 } | |
335 | |
336 } | |
337 | |
338 class FlexBoxParentData extends CSSParentData { | |
339 int flex; | |
340 void merge(FlexBoxParentData other) { | |
341 if (other.flex != null) | |
342 flex = other.flex; | |
343 super.merge(other); | |
344 } | |
345 } | |
346 | |
347 enum FlexDirection { Row, Column } | |
348 | |
349 class RenderCSSFlex extends RenderCSSContainer { | |
350 | |
351 RenderCSSFlex(debug, FlexDirection direction) : _direction = direction, super(
debug); | |
352 | |
353 FlexDirection _direction; | |
354 FlexDirection get direction => _direction; | |
355 void set direction (FlexDirection value) { | |
356 _direction = value; | |
357 markNeedsLayout(); | |
358 } | |
359 | |
360 void setupPos(RenderNode child) { | |
361 if (child.parentData is! FlexBoxParentData) | |
362 child.parentData = new FlexBoxParentData(); | |
363 } | |
364 | |
365 static final Style _displayFlex = new Style('display:flex'); | |
366 static final Style _displayFlexRow = new Style('flex-direction:row'); | |
367 static final Style _displayFlexColumn = new Style('flex-direction:column'); | |
368 | |
369 String stylesToClasses(List<Style> styles) { | |
370 var settings = _displayFlex._className; | |
371 switch (_direction) { | |
372 case FlexDirection.Row: settings += ' ' + _displayFlexRow._className; brea
k; | |
373 case FlexDirection.Column: settings += ' ' + _displayFlexColumn._className
; break; | |
374 } | |
375 return super.stylesToClasses(styles) + ' ' + settings; | |
376 } | |
377 | |
378 void markNeedsLayout() { | |
379 super.markNeedsLayout(); | |
380 | |
381 // pretend we did the layout: | |
382 RenderCSS child = _firstChild; | |
383 while (child != null) { | |
384 assert(child.parentData is FlexBoxParentData); | |
385 if (child.parentData.flex != null) { | |
386 child._additionalStylesFromParent = 'flex:${child.parentData.flex}'; | |
387 child._updateInlineStyleAttribute(); | |
388 } | |
389 child = child.parentData.nextSibling; | |
390 } | |
391 } | |
392 | |
393 } | |
394 | |
395 class RenderCSSParagraph extends RenderCSSContainer { | |
396 | |
397 RenderCSSParagraph(debug) : super(debug); | |
398 | |
399 static final Style _displayParagraph = new Style('display:paragraph'); | |
400 | |
401 String stylesToClasses(List<Style> styles) { | |
402 return super.stylesToClasses(styles) + ' ' + _displayParagraph._className; | |
403 } | |
404 | |
405 } | |
406 | |
407 class RenderCSSInline extends RenderCSS { | |
408 | |
409 RenderCSSInline(debug, String newData) : super(debug) { | |
410 data = newData; | |
411 } | |
412 | |
413 static final Style _displayInline = new Style('display:inline'); | |
414 | |
415 String stylesToClasses(List<Style> styles) { | |
416 return super.stylesToClasses(styles) + ' ' + _displayInline._className; | |
417 } | |
418 | |
419 sky.Element createSkyElement() { | |
420 return sky.document.createElement('div') | |
421 ..setChild(new sky.Text()) | |
422 ..setAttribute('debug', debug.toString()); | |
423 } | |
424 | |
425 void set data (String value) { | |
426 (_skyElement.firstChild as sky.Text).data = value; | |
427 } | |
428 | |
429 } | |
430 | |
431 class RenderCSSImage extends RenderCSS { | |
432 | |
433 RenderCSSImage(debug, String src, num width, num height) : super(debug) { | |
434 configure(src, width, height); | |
435 } | |
436 | |
437 sky.Element createSkyElement() { | |
438 return sky.document.createElement('img') | |
439 ..setAttribute('debug', debug.toString()); | |
440 } | |
441 | |
442 void configure(String src, num width, num height) { | |
443 if (_skyElement.getAttribute('src') != src) | |
444 _skyElement.setAttribute('src', src); | |
445 _skyElement.style['width'] = '${width}px'; | |
446 _skyElement.style['height'] = '${height}px'; | |
447 } | |
448 | |
449 } | |
450 | |
451 class RenderCSSRoot extends RenderCSSContainer { | |
452 RenderCSSRoot(debug) : super(debug); | |
453 sky.Element createSkyElement() { | |
454 var result = super.createSkyElement(); | |
455 assert(result != null); | |
456 sky.document.appendChild(result); | |
457 return result; | |
458 } | |
459 } | |
460 | |
461 | |
462 // legacy tools | |
463 Map<sky.EventTarget, RenderNode> _eventTargetRegistry = {}; | |
464 void registerEventTarget(sky.EventTarget e, RenderNode n) { | |
465 _eventTargetRegistry[e] = n; | |
466 } | |
467 RenderNode bridgeEventTargetToRenderNode(sky.EventTarget e) { | |
468 return _eventTargetRegistry[e]; | |
469 } | |
470 | |
471 | |
472 | |
473 | |
474 String _attributes(node) { | |
475 if (node is! sky.Element) return ''; | |
476 var result = ''; | |
477 var attrs = node.getAttributes(); | |
478 for (var attr in attrs) | |
479 result += ' ${attr.name}="${attr.value}"'; | |
480 return result; | |
481 } | |
482 | |
483 void _serialiseDOM(node, [String prefix = '']) { | |
484 if (node is sky.Text) { | |
485 print(prefix + 'text: "' + node.data.replaceAll('\n', '\\n') + '"'); | |
486 return; | |
487 } | |
488 print(prefix + node.toString() + _attributes(node)); | |
489 var children = node.getChildNodes(); | |
490 prefix = prefix + ' '; | |
491 for (var child in children) | |
492 _serialiseDOM(child, prefix); | |
493 } | |
494 | |
495 void dumpState() { | |
496 _serialiseDOM(sky.document); | |
497 } | |
OLD | NEW |