OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 library fn; | |
6 | |
7 import 'app.dart'; | |
8 import 'dart:async'; | |
9 import 'dart:collection'; | |
10 import 'dart:mirrors'; | |
11 import 'dart:sky' as sky; | |
12 import 'package:vector_math/vector_math.dart'; | |
13 import 'rendering/block.dart'; | |
14 import 'rendering/box.dart'; | |
15 import 'rendering/flex.dart'; | |
16 import 'rendering/object.dart'; | |
17 import 'rendering/paragraph.dart'; | |
18 import 'rendering/stack.dart'; | |
19 export 'rendering/object.dart' show Point, Size, Rect, Color, Paint, Path; | |
20 export 'rendering/box.dart' show BoxConstraints, BoxDecoration, Border, BorderSi
de, EdgeDims; | |
21 export 'rendering/flex.dart' show FlexDirection; | |
22 | |
23 // final sky.Tracing _tracing = sky.window.tracing; | |
24 | |
25 final bool _shouldLogRenderDuration = false; | |
26 | |
27 /* | |
28 * All Effen nodes derive from UINode. All nodes have a _parent, a _key and | |
29 * can be sync'd. | |
30 */ | |
31 abstract class UINode { | |
32 | |
33 UINode({ Object key }) { | |
34 _key = key == null ? "$runtimeType" : "$runtimeType-$key"; | |
35 assert(this is App || _inRenderDirtyComponents); // you should not build the
UI tree ahead of time, build it only during build() | |
36 } | |
37 | |
38 String _key; | |
39 String get key => _key; | |
40 | |
41 UINode _parent; | |
42 UINode get parent => _parent; | |
43 | |
44 bool _mounted = false; | |
45 bool _wasMounted = false; | |
46 bool get mounted => _mounted; | |
47 static bool _notifyingMountStatus = false; | |
48 static Set<UINode> _mountedChanged = new HashSet<UINode>(); | |
49 | |
50 void setParent(UINode newParent) { | |
51 assert(!_notifyingMountStatus); | |
52 _parent = newParent; | |
53 if (newParent == null) { | |
54 if (_mounted) { | |
55 _mounted = false; | |
56 _mountedChanged.add(this); | |
57 } | |
58 } else { | |
59 assert(newParent._mounted); | |
60 if (_parent._mounted != _mounted) { | |
61 _mounted = _parent._mounted; | |
62 _mountedChanged.add(this); | |
63 } | |
64 } | |
65 } | |
66 | |
67 static void _notifyMountStatusChanged() { | |
68 try { | |
69 _notifyingMountStatus = true; | |
70 for (UINode node in _mountedChanged) { | |
71 if (node._wasMounted != node._mounted) { | |
72 if (node._mounted) | |
73 node.didMount(); | |
74 else | |
75 node.didUnmount(); | |
76 node._wasMounted = node._mounted; | |
77 } | |
78 } | |
79 _mountedChanged.clear(); | |
80 } finally { | |
81 _notifyingMountStatus = false; | |
82 } | |
83 } | |
84 void didMount() { } | |
85 void didUnmount() { } | |
86 | |
87 RenderObject _root; | |
88 RenderObject get root => _root; | |
89 | |
90 // Subclasses which implements Nodes that become stateful may return true | |
91 // if the |old| node has become stateful and should be retained. | |
92 // This is called immediately before _sync(). | |
93 // Component._retainStatefulNodeIfPossible() calls syncFields(). | |
94 bool _retainStatefulNodeIfPossible(UINode old) => false; | |
95 | |
96 bool get interchangeable => false; // if true, then keys can be duplicated | |
97 | |
98 void _sync(UINode old, dynamic slot); | |
99 // 'slot' is the identifier that the parent RenderObjectWrapper uses to know | |
100 // where to put this descendant | |
101 | |
102 void remove() { | |
103 _root = null; | |
104 setParent(null); | |
105 } | |
106 | |
107 UINode findAncestor(Type targetType) { | |
108 var ancestor = _parent; | |
109 while (ancestor != null && !reflectClass(ancestor.runtimeType).isSubtypeOf(r
eflectClass(targetType))) | |
110 ancestor = ancestor._parent; | |
111 return ancestor; | |
112 } | |
113 | |
114 void removeChild(UINode node) { | |
115 node.remove(); | |
116 } | |
117 | |
118 // Returns the child which should be retained as the child of this node. | |
119 UINode syncChild(UINode node, UINode oldNode, dynamic slot) { | |
120 | |
121 assert(oldNode is! Component || !oldNode._disqualifiedFromEverAppearingAgain
); | |
122 | |
123 if (node == oldNode) { | |
124 assert(node == null || node.mounted); | |
125 return node; // Nothing to do. Subtrees must be identical. | |
126 } | |
127 | |
128 if (node == null) { | |
129 // the child in this slot has gone away | |
130 assert(oldNode.mounted); | |
131 removeChild(oldNode); | |
132 assert(!oldNode.mounted); | |
133 return null; | |
134 } | |
135 | |
136 if (oldNode != null && node._key == oldNode._key && node._retainStatefulNode
IfPossible(oldNode)) { | |
137 assert(oldNode.mounted); | |
138 assert(!node.mounted); | |
139 oldNode._sync(node, slot); | |
140 assert(oldNode.root is RenderObject); | |
141 return oldNode; | |
142 } | |
143 | |
144 if (oldNode != null && node._key != oldNode._key) { | |
145 assert(oldNode.mounted); | |
146 removeChild(oldNode); | |
147 oldNode = null; | |
148 } | |
149 | |
150 assert(!node.mounted); | |
151 node.setParent(this); | |
152 node._sync(oldNode, slot); | |
153 assert(node.root is RenderObject); | |
154 return node; | |
155 } | |
156 } | |
157 | |
158 // Descendants of TagNode provide a way to tag RenderObjectWrapper and | |
159 // Component nodes with annotations, such as event listeners, | |
160 // stylistic information, etc. | |
161 abstract class TagNode extends UINode { | |
162 | |
163 TagNode(UINode content, { Object key }) : this.content = content, super(key: k
ey); | |
164 | |
165 UINode content; | |
166 | |
167 void _sync(UINode old, dynamic slot) { | |
168 UINode oldContent = old == null ? null : (old as TagNode).content; | |
169 content = syncChild(content, oldContent, slot); | |
170 assert(content.root != null); | |
171 _root = content.root; | |
172 assert(_root == root); // in case a subclass reintroduces it | |
173 } | |
174 | |
175 void remove() { | |
176 if (content != null) | |
177 removeChild(content); | |
178 super.remove(); | |
179 } | |
180 | |
181 } | |
182 | |
183 class ParentDataNode extends TagNode { | |
184 ParentDataNode(UINode content, this.parentData, { Object key }): super(content
, key: key); | |
185 final ParentData parentData; | |
186 } | |
187 | |
188 typedef void GestureEventListener(sky.GestureEvent e); | |
189 typedef void PointerEventListener(sky.PointerEvent e); | |
190 typedef void EventListener(sky.Event e); | |
191 | |
192 class EventListenerNode extends TagNode { | |
193 | |
194 EventListenerNode(UINode content, { | |
195 EventListener onWheel, | |
196 GestureEventListener onGestureFlingCancel, | |
197 GestureEventListener onGestureFlingStart, | |
198 GestureEventListener onGestureScrollStart, | |
199 GestureEventListener onGestureScrollUpdate, | |
200 GestureEventListener onGestureTap, | |
201 GestureEventListener onGestureTapDown, | |
202 PointerEventListener onPointerCancel, | |
203 PointerEventListener onPointerDown, | |
204 PointerEventListener onPointerMove, | |
205 PointerEventListener onPointerUp, | |
206 Map<String, sky.EventListener> custom | |
207 }) : listeners = _createListeners( | |
208 onWheel: onWheel, | |
209 onGestureFlingCancel: onGestureFlingCancel, | |
210 onGestureFlingStart: onGestureFlingStart, | |
211 onGestureScrollUpdate: onGestureScrollUpdate, | |
212 onGestureScrollStart: onGestureScrollStart, | |
213 onGestureTap: onGestureTap, | |
214 onGestureTapDown: onGestureTapDown, | |
215 onPointerCancel: onPointerCancel, | |
216 onPointerDown: onPointerDown, | |
217 onPointerMove: onPointerMove, | |
218 onPointerUp: onPointerUp, | |
219 custom: custom | |
220 ), | |
221 super(content); | |
222 | |
223 final Map<String, sky.EventListener> listeners; | |
224 | |
225 static Map<String, sky.EventListener> _createListeners({ | |
226 EventListener onWheel, | |
227 GestureEventListener onGestureFlingCancel, | |
228 GestureEventListener onGestureFlingStart, | |
229 GestureEventListener onGestureScrollStart, | |
230 GestureEventListener onGestureScrollUpdate, | |
231 GestureEventListener onGestureTap, | |
232 GestureEventListener onGestureTapDown, | |
233 PointerEventListener onPointerCancel, | |
234 PointerEventListener onPointerDown, | |
235 PointerEventListener onPointerMove, | |
236 PointerEventListener onPointerUp, | |
237 Map<String, sky.EventListener> custom | |
238 }) { | |
239 var listeners = custom != null ? | |
240 new HashMap<String, sky.EventListener>.from(custom) : | |
241 new HashMap<String, sky.EventListener>(); | |
242 | |
243 if (onWheel != null) | |
244 listeners['wheel'] = onWheel; | |
245 if (onGestureFlingCancel != null) | |
246 listeners['gestureflingcancel'] = onGestureFlingCancel; | |
247 if (onGestureFlingStart != null) | |
248 listeners['gestureflingstart'] = onGestureFlingStart; | |
249 if (onGestureScrollStart != null) | |
250 listeners['gesturescrollstart'] = onGestureScrollStart; | |
251 if (onGestureScrollUpdate != null) | |
252 listeners['gesturescrollupdate'] = onGestureScrollUpdate; | |
253 if (onGestureTap != null) | |
254 listeners['gesturetap'] = onGestureTap; | |
255 if (onGestureTapDown != null) | |
256 listeners['gesturetapdown'] = onGestureTapDown; | |
257 if (onPointerCancel != null) | |
258 listeners['pointercancel'] = onPointerCancel; | |
259 if (onPointerDown != null) | |
260 listeners['pointerdown'] = onPointerDown; | |
261 if (onPointerMove != null) | |
262 listeners['pointermove'] = onPointerMove; | |
263 if (onPointerUp != null) | |
264 listeners['pointerup'] = onPointerUp; | |
265 | |
266 return listeners; | |
267 } | |
268 | |
269 void _handleEvent(sky.Event e) { | |
270 sky.EventListener listener = listeners[e.type]; | |
271 if (listener != null) { | |
272 listener(e); | |
273 } | |
274 } | |
275 | |
276 } | |
277 | |
278 /* | |
279 * RenderObjectWrappers correspond to a desired state of a RenderObject. | |
280 * They are fully immutable, with one exception: A UINode which is a | |
281 * Component which lives within an MultiChildRenderObjectWrapper's | |
282 * children list, may be replaced with the "old" instance if it has | |
283 * become stateful. | |
284 */ | |
285 abstract class RenderObjectWrapper extends UINode { | |
286 | |
287 RenderObjectWrapper({ | |
288 Object key | |
289 }) : super(key: key); | |
290 | |
291 RenderObject createNode(); | |
292 | |
293 void insert(RenderObjectWrapper child, dynamic slot); | |
294 | |
295 static final Map<RenderObject, RenderObjectWrapper> _nodeMap = | |
296 new HashMap<RenderObject, RenderObjectWrapper>(); | |
297 | |
298 static RenderObjectWrapper _getMounted(RenderObject node) => _nodeMap[node]; | |
299 | |
300 void _sync(UINode old, dynamic slot) { | |
301 assert(parent != null); | |
302 if (old == null) { | |
303 _root = createNode(); | |
304 var ancestor = findAncestor(RenderObjectWrapper); | |
305 if (ancestor is RenderObjectWrapper) | |
306 ancestor.insert(this, slot); | |
307 } else { | |
308 _root = old.root; | |
309 } | |
310 assert(_root == root); // in case a subclass reintroduces it | |
311 assert(root != null); | |
312 assert(mounted); | |
313 _nodeMap[root] = this; | |
314 syncRenderObject(old); | |
315 } | |
316 | |
317 void syncRenderObject(RenderObjectWrapper old) { | |
318 ParentData parentData = null; | |
319 UINode ancestor = parent; | |
320 while (ancestor != null && ancestor is! RenderObjectWrapper) { | |
321 if (ancestor is ParentDataNode && ancestor.parentData != null) { | |
322 if (parentData != null) | |
323 parentData.merge(ancestor.parentData); // this will throw if the types
aren't the same | |
324 else | |
325 parentData = ancestor.parentData; | |
326 } | |
327 ancestor = ancestor.parent; | |
328 } | |
329 if (parentData != null) { | |
330 assert(root.parentData != null); | |
331 root.parentData.merge(parentData); // this will throw if the types aren't
appropriate | |
332 if (parent.root != null) | |
333 parent.root.markNeedsLayout(); | |
334 } | |
335 } | |
336 | |
337 void remove() { | |
338 assert(root != null); | |
339 _nodeMap.remove(root); | |
340 super.remove(); | |
341 } | |
342 } | |
343 | |
344 abstract class OneChildRenderObjectWrapper extends RenderObjectWrapper { | |
345 | |
346 OneChildRenderObjectWrapper({ UINode child, Object key }) : _child = child, su
per(key: key); | |
347 | |
348 UINode _child; | |
349 UINode get child => _child; | |
350 | |
351 void syncRenderObject(RenderObjectWrapper old) { | |
352 super.syncRenderObject(old); | |
353 UINode oldChild = old == null ? null : (old as OneChildRenderObjectWrapper).
child; | |
354 _child = syncChild(child, oldChild, null); | |
355 } | |
356 | |
357 void insert(RenderObjectWrapper child, dynamic slot) { | |
358 final root = this.root; // TODO(ianh): Remove this once the analyzer is clev
erer | |
359 assert(slot == null); | |
360 assert(root is RenderObjectWithChildMixin); | |
361 root.child = child.root; | |
362 assert(root == this.root); // TODO(ianh): Remove this once the analyzer is c
leverer | |
363 } | |
364 | |
365 void removeChild(UINode node) { | |
366 final root = this.root; // TODO(ianh): Remove this once the analyzer is clev
erer | |
367 assert(root is RenderObjectWithChildMixin); | |
368 root.child = null; | |
369 super.removeChild(node); | |
370 assert(root == this.root); // TODO(ianh): Remove this once the analyzer is c
leverer | |
371 } | |
372 | |
373 void remove() { | |
374 if (child != null) | |
375 removeChild(child); | |
376 super.remove(); | |
377 } | |
378 | |
379 } | |
380 | |
381 class Opacity extends OneChildRenderObjectWrapper { | |
382 Opacity({ this.opacity, UINode child, Object key }) | |
383 : super(child: child, key: key); | |
384 | |
385 RenderOpacity get root { RenderOpacity result = super.root; return result; } | |
386 final double opacity; | |
387 | |
388 RenderOpacity createNode() => new RenderOpacity(opacity: opacity); | |
389 | |
390 void syncRenderObject(Opacity old) { | |
391 super.syncRenderObject(old); | |
392 root.opacity = opacity; | |
393 } | |
394 } | |
395 | |
396 class ClipRect extends OneChildRenderObjectWrapper { | |
397 | |
398 ClipRect({ UINode child, Object key }) | |
399 : super(child: child, key: key); | |
400 | |
401 RenderClipRect get root { RenderClipRect result = super.root; return result; } | |
402 RenderClipRect createNode() => new RenderClipRect(); | |
403 } | |
404 | |
405 class ClipOval extends OneChildRenderObjectWrapper { | |
406 | |
407 ClipOval({ UINode child, Object key }) | |
408 : super(child: child, key: key); | |
409 | |
410 RenderClipOval get root { RenderClipOval result = super.root; return result; } | |
411 RenderClipOval createNode() => new RenderClipOval(); | |
412 } | |
413 | |
414 class Padding extends OneChildRenderObjectWrapper { | |
415 | |
416 Padding({ this.padding, UINode child, Object key }) | |
417 : super(child: child, key: key); | |
418 | |
419 RenderPadding get root { RenderPadding result = super.root; return result; } | |
420 final EdgeDims padding; | |
421 | |
422 RenderPadding createNode() => new RenderPadding(padding: padding); | |
423 | |
424 void syncRenderObject(Padding old) { | |
425 super.syncRenderObject(old); | |
426 root.padding = padding; | |
427 } | |
428 | |
429 } | |
430 | |
431 class DecoratedBox extends OneChildRenderObjectWrapper { | |
432 | |
433 DecoratedBox({ this.decoration, UINode child, Object key }) | |
434 : super(child: child, key: key); | |
435 | |
436 RenderDecoratedBox get root { RenderDecoratedBox result = super.root; return r
esult; } | |
437 final BoxDecoration decoration; | |
438 | |
439 RenderDecoratedBox createNode() => new RenderDecoratedBox(decoration: decorati
on); | |
440 | |
441 void syncRenderObject(DecoratedBox old) { | |
442 super.syncRenderObject(old); | |
443 root.decoration = decoration; | |
444 } | |
445 | |
446 } | |
447 | |
448 class SizedBox extends OneChildRenderObjectWrapper { | |
449 | |
450 SizedBox({ | |
451 double width: double.INFINITY, | |
452 double height: double.INFINITY, | |
453 UINode child, | |
454 Object key | |
455 }) : desiredSize = new Size(width, height), super(child: child, key: key); | |
456 | |
457 RenderSizedBox get root { RenderSizedBox result = super.root; return result; } | |
458 final Size desiredSize; | |
459 | |
460 RenderSizedBox createNode() => new RenderSizedBox(desiredSize: desiredSize); | |
461 | |
462 void syncRenderObject(SizedBox old) { | |
463 super.syncRenderObject(old); | |
464 root.desiredSize = desiredSize; | |
465 } | |
466 | |
467 } | |
468 | |
469 class ConstrainedBox extends OneChildRenderObjectWrapper { | |
470 | |
471 ConstrainedBox({ this.constraints, UINode child, Object key }) | |
472 : super(child: child, key: key); | |
473 | |
474 RenderConstrainedBox get root { RenderConstrainedBox result = super.root; retu
rn result; } | |
475 final BoxConstraints constraints; | |
476 | |
477 RenderConstrainedBox createNode() => new RenderConstrainedBox(additionalConstr
aints: constraints); | |
478 | |
479 void syncRenderObject(ConstrainedBox old) { | |
480 super.syncRenderObject(old); | |
481 root.additionalConstraints = constraints; | |
482 } | |
483 | |
484 } | |
485 | |
486 class ShrinkWrapWidth extends OneChildRenderObjectWrapper { | |
487 | |
488 ShrinkWrapWidth({ UINode child, Object key }) : super(child: child, key: key); | |
489 | |
490 RenderShrinkWrapWidth get root { RenderShrinkWrapWidth result = super.root; re
turn result; } | |
491 | |
492 RenderShrinkWrapWidth createNode() => new RenderShrinkWrapWidth(); | |
493 | |
494 } | |
495 | |
496 class Transform extends OneChildRenderObjectWrapper { | |
497 | |
498 Transform({ this.transform, UINode child, Object key }) | |
499 : super(child: child, key: key); | |
500 | |
501 RenderTransform get root { RenderTransform result = super.root; return result;
} | |
502 final Matrix4 transform; | |
503 | |
504 RenderTransform createNode() => new RenderTransform(transform: transform); | |
505 | |
506 void syncRenderObject(Transform old) { | |
507 super.syncRenderObject(old); | |
508 root.transform = transform; | |
509 } | |
510 | |
511 } | |
512 | |
513 class SizeObserver extends OneChildRenderObjectWrapper { | |
514 | |
515 SizeObserver({ this.callback, UINode child, Object key }) | |
516 : super(child: child, key: key); | |
517 | |
518 RenderSizeObserver get root { RenderSizeObserver result = super.root; return r
esult; } | |
519 final SizeChangedCallback callback; | |
520 | |
521 RenderSizeObserver createNode() => new RenderSizeObserver(callback: callback); | |
522 | |
523 void syncRenderObject(SizeObserver old) { | |
524 super.syncRenderObject(old); | |
525 root.callback = callback; | |
526 } | |
527 | |
528 void remove() { | |
529 root.callback = null; | |
530 super.remove(); | |
531 } | |
532 | |
533 } | |
534 | |
535 // TODO(jackson) need a mechanism for marking the RenderCustomPaint as needing p
aint | |
536 class CustomPaint extends OneChildRenderObjectWrapper { | |
537 | |
538 CustomPaint({ this.callback, UINode child, Object key }) | |
539 : super(child: child, key: key); | |
540 | |
541 RenderCustomPaint get root { RenderCustomPaint result = super.root; return res
ult; } | |
542 final CustomPaintCallback callback; | |
543 | |
544 RenderCustomPaint createNode() => new RenderCustomPaint(callback: callback); | |
545 | |
546 void syncRenderObject(CustomPaint old) { | |
547 super.syncRenderObject(old); | |
548 root.callback = callback; | |
549 } | |
550 | |
551 void remove() { | |
552 root.callback = null; | |
553 super.remove(); | |
554 } | |
555 | |
556 } | |
557 | |
558 abstract class MultiChildRenderObjectWrapper extends RenderObjectWrapper { | |
559 | |
560 // In MultiChildRenderObjectWrapper subclasses, slots are RenderObject nodes | |
561 // to use as the "insert before" sibling in ContainerRenderObjectMixin.add() c
alls | |
562 | |
563 MultiChildRenderObjectWrapper({ | |
564 Object key, | |
565 List<UINode> children | |
566 }) : this.children = children == null ? const [] : children, | |
567 super(key: key) { | |
568 assert(!_debugHasDuplicateIds()); | |
569 } | |
570 | |
571 final List<UINode> children; | |
572 | |
573 void insert(RenderObjectWrapper child, dynamic slot) { | |
574 final root = this.root; // TODO(ianh): Remove this once the analyzer is clev
erer | |
575 assert(slot == null || slot is RenderObject); | |
576 assert(root is ContainerRenderObjectMixin); | |
577 root.add(child.root, before: slot); | |
578 assert(root == this.root); // TODO(ianh): Remove this once the analyzer is c
leverer | |
579 } | |
580 | |
581 void removeChild(UINode node) { | |
582 final root = this.root; // TODO(ianh): Remove this once the analyzer is clev
erer | |
583 assert(root is ContainerRenderObjectMixin); | |
584 assert(node.root.parent == root); | |
585 root.remove(node.root); | |
586 super.removeChild(node); | |
587 assert(root == this.root); // TODO(ianh): Remove this once the analyzer is c
leverer | |
588 } | |
589 | |
590 void remove() { | |
591 assert(children != null); | |
592 for (var child in children) { | |
593 assert(child != null); | |
594 removeChild(child); | |
595 } | |
596 super.remove(); | |
597 } | |
598 | |
599 bool _debugHasDuplicateIds() { | |
600 var idSet = new HashSet<String>(); | |
601 for (var child in children) { | |
602 assert(child != null); | |
603 if (child.interchangeable) | |
604 continue; // when these nodes are reordered, we just reassign the data | |
605 | |
606 if (!idSet.add(child._key)) { | |
607 throw '''If multiple non-interchangeable nodes of the same type exist as
children | |
608 of another node, they must have unique keys. | |
609 Duplicate: "${child._key}"'''; | |
610 } | |
611 } | |
612 return false; | |
613 } | |
614 | |
615 void syncRenderObject(MultiChildRenderObjectWrapper old) { | |
616 super.syncRenderObject(old); | |
617 | |
618 final root = this.root; // TODO(ianh): Remove this once the analyzer is clev
erer | |
619 if (root is! ContainerRenderObjectMixin) | |
620 return; | |
621 | |
622 var startIndex = 0; | |
623 var endIndex = children.length; | |
624 | |
625 var oldChildren = old == null ? [] : old.children; | |
626 var oldStartIndex = 0; | |
627 var oldEndIndex = oldChildren.length; | |
628 | |
629 RenderObject nextSibling = null; | |
630 UINode currentNode = null; | |
631 UINode oldNode = null; | |
632 | |
633 void sync(int atIndex) { | |
634 children[atIndex] = syncChild(currentNode, oldNode, nextSibling); | |
635 assert(children[atIndex] != null); | |
636 } | |
637 | |
638 // Scan backwards from end of list while nodes can be directly synced | |
639 // without reordering. | |
640 while (endIndex > startIndex && oldEndIndex > oldStartIndex) { | |
641 currentNode = children[endIndex - 1]; | |
642 oldNode = oldChildren[oldEndIndex - 1]; | |
643 | |
644 if (currentNode._key != oldNode._key) { | |
645 break; | |
646 } | |
647 | |
648 endIndex--; | |
649 oldEndIndex--; | |
650 sync(endIndex); | |
651 } | |
652 | |
653 HashMap<String, UINode> oldNodeIdMap = null; | |
654 | |
655 bool oldNodeReordered(String key) { | |
656 return oldNodeIdMap != null && | |
657 oldNodeIdMap.containsKey(key) && | |
658 oldNodeIdMap[key] == null; | |
659 } | |
660 | |
661 void advanceOldStartIndex() { | |
662 oldStartIndex++; | |
663 while (oldStartIndex < oldEndIndex && | |
664 oldNodeReordered(oldChildren[oldStartIndex]._key)) { | |
665 oldStartIndex++; | |
666 } | |
667 } | |
668 | |
669 void ensureOldIdMap() { | |
670 if (oldNodeIdMap != null) | |
671 return; | |
672 | |
673 oldNodeIdMap = new HashMap<String, UINode>(); | |
674 for (int i = oldStartIndex; i < oldEndIndex; i++) { | |
675 var node = oldChildren[i]; | |
676 if (!node.interchangeable) | |
677 oldNodeIdMap.putIfAbsent(node._key, () => node); | |
678 } | |
679 } | |
680 | |
681 bool searchForOldNode() { | |
682 if (currentNode.interchangeable) | |
683 return false; // never re-order these nodes | |
684 | |
685 ensureOldIdMap(); | |
686 oldNode = oldNodeIdMap[currentNode._key]; | |
687 if (oldNode == null) | |
688 return false; | |
689 | |
690 oldNodeIdMap[currentNode._key] = null; // mark it reordered | |
691 assert(root is ContainerRenderObjectMixin); | |
692 assert(old.root is ContainerRenderObjectMixin); | |
693 assert(oldNode.root != null); | |
694 | |
695 (old.root as ContainerRenderObjectMixin).remove(oldNode.root); // TODO(ian
h): Remove cast once the analyzer is cleverer | |
696 root.add(oldNode.root, before: nextSibling); | |
697 | |
698 return true; | |
699 } | |
700 | |
701 // Scan forwards, this time we may re-order; | |
702 nextSibling = root.firstChild; | |
703 while (startIndex < endIndex && oldStartIndex < oldEndIndex) { | |
704 currentNode = children[startIndex]; | |
705 oldNode = oldChildren[oldStartIndex]; | |
706 | |
707 if (currentNode._key == oldNode._key) { | |
708 assert(currentNode.runtimeType == oldNode.runtimeType); | |
709 nextSibling = root.childAfter(nextSibling); | |
710 sync(startIndex); | |
711 startIndex++; | |
712 advanceOldStartIndex(); | |
713 continue; | |
714 } | |
715 | |
716 oldNode = null; | |
717 searchForOldNode(); | |
718 sync(startIndex); | |
719 startIndex++; | |
720 } | |
721 | |
722 // New insertions | |
723 oldNode = null; | |
724 while (startIndex < endIndex) { | |
725 currentNode = children[startIndex]; | |
726 sync(startIndex); | |
727 startIndex++; | |
728 } | |
729 | |
730 // Removals | |
731 currentNode = null; | |
732 while (oldStartIndex < oldEndIndex) { | |
733 oldNode = oldChildren[oldStartIndex]; | |
734 removeChild(oldNode); | |
735 advanceOldStartIndex(); | |
736 } | |
737 | |
738 assert(root == this.root); // TODO(ianh): Remove this once the analyzer is c
leverer | |
739 } | |
740 | |
741 } | |
742 | |
743 class Block extends MultiChildRenderObjectWrapper { | |
744 | |
745 Block(List<UINode> children, { Object key }) | |
746 : super(key: key, children: children); | |
747 | |
748 RenderBlock get root { RenderBlock result = super.root; return result; } | |
749 RenderBlock createNode() => new RenderBlock(); | |
750 | |
751 } | |
752 | |
753 class Stack extends MultiChildRenderObjectWrapper { | |
754 | |
755 Stack(List<UINode> children, { Object key }) | |
756 : super(key: key, children: children); | |
757 | |
758 RenderStack get root { RenderStack result = super.root; return result; } | |
759 RenderStack createNode() => new RenderStack(); | |
760 | |
761 } | |
762 | |
763 class StackPositionedChild extends ParentDataNode { | |
764 StackPositionedChild(UINode content, { | |
765 double top, double right, double bottom, double left | |
766 }) : super(content, new StackParentData()..top = top | |
767 ..right = right | |
768 ..bottom = bottom | |
769 ..left = left); | |
770 } | |
771 | |
772 class Paragraph extends RenderObjectWrapper { | |
773 | |
774 Paragraph({ Object key, this.text }) : super(key: key); | |
775 | |
776 RenderParagraph get root { RenderParagraph result = super.root; return result;
} | |
777 RenderParagraph createNode() => new RenderParagraph(text: text); | |
778 | |
779 final String text; | |
780 | |
781 void syncRenderObject(UINode old) { | |
782 super.syncRenderObject(old); | |
783 root.text = text; | |
784 } | |
785 | |
786 void insert(RenderObjectWrapper child, dynamic slot) { | |
787 assert(false); | |
788 // Paragraph does not support having children currently | |
789 } | |
790 | |
791 } | |
792 | |
793 class Text extends Component { | |
794 Text(this.data) : super(key: '*text*'); | |
795 final String data; | |
796 bool get interchangeable => true; | |
797 UINode build() => new Paragraph(text: data); | |
798 } | |
799 | |
800 class Flex extends MultiChildRenderObjectWrapper { | |
801 | |
802 Flex(List<UINode> children, { | |
803 Object key, | |
804 this.direction: FlexDirection.horizontal, | |
805 this.justifyContent: FlexJustifyContent.flexStart, | |
806 this.alignItems: FlexAlignItems.center | |
807 }) : super(key: key, children: children); | |
808 | |
809 RenderFlex get root { RenderFlex result = super.root; return result; } | |
810 RenderFlex createNode() => new RenderFlex(direction: this.direction); | |
811 | |
812 final FlexDirection direction; | |
813 final FlexJustifyContent justifyContent; | |
814 final FlexAlignItems alignItems; | |
815 | |
816 void syncRenderObject(UINode old) { | |
817 super.syncRenderObject(old); | |
818 root.direction = direction; | |
819 root.justifyContent = justifyContent; | |
820 root.alignItems = alignItems; | |
821 } | |
822 | |
823 } | |
824 | |
825 class FlexExpandingChild extends ParentDataNode { | |
826 FlexExpandingChild(UINode content, { int flex: 1, Object key }) | |
827 : super(content, new FlexBoxParentData()..flex = flex, key: key); | |
828 } | |
829 | |
830 class Image extends RenderObjectWrapper { | |
831 | |
832 Image({ | |
833 Object key, | |
834 this.src, | |
835 this.size | |
836 }) : super(key: key); | |
837 | |
838 RenderImage get root { RenderImage result = super.root; return result; } | |
839 RenderImage createNode() => new RenderImage(this.src, this.size); | |
840 | |
841 final String src; | |
842 final Size size; | |
843 | |
844 void syncRenderObject(UINode old) { | |
845 super.syncRenderObject(old); | |
846 root.src = src; | |
847 root.requestedSize = size; | |
848 } | |
849 | |
850 void insert(RenderObjectWrapper child, dynamic slot) { | |
851 assert(false); | |
852 // Image does not support having children currently | |
853 } | |
854 | |
855 } | |
856 | |
857 Set<Component> _dirtyComponents = new Set<Component>(); | |
858 bool _buildScheduled = false; | |
859 bool _inRenderDirtyComponents = false; | |
860 | |
861 void _buildDirtyComponents() { | |
862 //_tracing.begin('fn::_buildDirtyComponents'); | |
863 | |
864 Stopwatch sw; | |
865 if (_shouldLogRenderDuration) | |
866 sw = new Stopwatch()..start(); | |
867 | |
868 try { | |
869 _inRenderDirtyComponents = true; | |
870 | |
871 List<Component> sortedDirtyComponents = _dirtyComponents.toList(); | |
872 sortedDirtyComponents.sort((Component a, Component b) => a._order - b._order
); | |
873 for (var comp in sortedDirtyComponents) { | |
874 comp._buildIfDirty(); | |
875 } | |
876 | |
877 _dirtyComponents.clear(); | |
878 _buildScheduled = false; | |
879 } finally { | |
880 _inRenderDirtyComponents = false; | |
881 } | |
882 | |
883 UINode._notifyMountStatusChanged(); | |
884 | |
885 if (_shouldLogRenderDuration) { | |
886 sw.stop(); | |
887 print('Render took ${sw.elapsedMicroseconds} microseconds'); | |
888 } | |
889 | |
890 //_tracing.end('fn::_buildDirtyComponents'); | |
891 } | |
892 | |
893 void _scheduleComponentForRender(Component c) { | |
894 assert(!_inRenderDirtyComponents); | |
895 _dirtyComponents.add(c); | |
896 | |
897 if (!_buildScheduled) { | |
898 _buildScheduled = true; | |
899 new Future.microtask(_buildDirtyComponents); | |
900 } | |
901 } | |
902 | |
903 abstract class Component extends UINode { | |
904 | |
905 Component({ Object key, bool stateful }) | |
906 : _stateful = stateful != null ? stateful : false, | |
907 _order = _currentOrder + 1, | |
908 super(key: key); | |
909 | |
910 Component.fromArgs(Object key, bool stateful) | |
911 : this(key: key, stateful: stateful); | |
912 | |
913 static Component _currentlyBuilding; | |
914 bool get _isBuilding => _currentlyBuilding == this; | |
915 | |
916 bool _stateful; | |
917 bool _dirty = true; | |
918 bool _disqualifiedFromEverAppearingAgain = false; | |
919 | |
920 UINode _built; | |
921 dynamic _slot; // cached slot from the last time we were synced | |
922 | |
923 void didMount() { | |
924 assert(!_disqualifiedFromEverAppearingAgain); | |
925 super.didMount(); | |
926 } | |
927 | |
928 void remove() { | |
929 assert(_built != null); | |
930 assert(root != null); | |
931 removeChild(_built); | |
932 _built = null; | |
933 super.remove(); | |
934 } | |
935 | |
936 bool _retainStatefulNodeIfPossible(UINode old) { | |
937 assert(!_disqualifiedFromEverAppearingAgain); | |
938 | |
939 Component oldComponent = old as Component; | |
940 if (oldComponent == null || !oldComponent._stateful) | |
941 return false; | |
942 | |
943 assert(key == oldComponent.key); | |
944 | |
945 // Make |this|, the newly-created object, into the "old" Component, and kill
it | |
946 _stateful = false; | |
947 _built = oldComponent._built; | |
948 assert(_built != null); | |
949 _disqualifiedFromEverAppearingAgain = true; | |
950 | |
951 // Make |oldComponent| the "new" component | |
952 oldComponent._built = null; | |
953 oldComponent._dirty = true; | |
954 oldComponent.syncFields(this); | |
955 return true; | |
956 } | |
957 | |
958 // This is called by _retainStatefulNodeIfPossible(), during | |
959 // syncChild(), just before _sync() is called. | |
960 // This must be implemented on any subclass that can become stateful | |
961 // (but don't call super.syncFields() if you inherit directly from | |
962 // Component, since that'll fire an assert). | |
963 // If you don't ever become stateful, then don't override this. | |
964 void syncFields(Component source) { | |
965 assert(false); | |
966 } | |
967 | |
968 final int _order; | |
969 static int _currentOrder = 0; | |
970 | |
971 /* There are three cases here: | |
972 * 1) Building for the first time: | |
973 * assert(_built == null && old == null) | |
974 * 2) Re-building (because a dirty flag got set): | |
975 * assert(_built != null && old == null) | |
976 * 3) Syncing against an old version | |
977 * assert(_built == null && old != null) | |
978 */ | |
979 void _sync(UINode old, dynamic slot) { | |
980 assert(_built == null || old == null); | |
981 assert(!_disqualifiedFromEverAppearingAgain); | |
982 | |
983 Component oldComponent = old as Component; | |
984 | |
985 _slot = slot; | |
986 | |
987 var oldBuilt; | |
988 if (oldComponent == null) { | |
989 oldBuilt = _built; | |
990 } else { | |
991 assert(_built == null); | |
992 oldBuilt = oldComponent._built; | |
993 } | |
994 | |
995 int lastOrder = _currentOrder; | |
996 _currentOrder = _order; | |
997 _currentlyBuilding = this; | |
998 _built = build(); | |
999 assert(_built != null); | |
1000 _currentlyBuilding = null; | |
1001 _currentOrder = lastOrder; | |
1002 | |
1003 _built = syncChild(_built, oldBuilt, slot); | |
1004 assert(_built != null); | |
1005 _dirty = false; | |
1006 _root = _built.root; | |
1007 assert(_root == root); // in case a subclass reintroduces it | |
1008 assert(root != null); | |
1009 } | |
1010 | |
1011 void _buildIfDirty() { | |
1012 assert(!_disqualifiedFromEverAppearingAgain); | |
1013 if (!_dirty || !_mounted) | |
1014 return; | |
1015 | |
1016 assert(root != null); | |
1017 _sync(null, _slot); | |
1018 } | |
1019 | |
1020 void scheduleBuild() { | |
1021 setState(() {}); | |
1022 } | |
1023 | |
1024 void setState(Function fn()) { | |
1025 assert(!_disqualifiedFromEverAppearingAgain); | |
1026 _stateful = true; | |
1027 fn(); | |
1028 if (_isBuilding || _dirty || !_mounted) | |
1029 return; | |
1030 | |
1031 _dirty = true; | |
1032 _scheduleComponentForRender(this); | |
1033 } | |
1034 | |
1035 UINode build(); | |
1036 | |
1037 } | |
1038 | |
1039 class Container extends Component { | |
1040 | |
1041 Container({ | |
1042 Object key, | |
1043 this.child, | |
1044 this.constraints, | |
1045 this.decoration, | |
1046 this.width, | |
1047 this.height, | |
1048 this.margin, | |
1049 this.padding, | |
1050 this.transform | |
1051 }) : super(key: key); | |
1052 | |
1053 final UINode child; | |
1054 final BoxConstraints constraints; | |
1055 final BoxDecoration decoration; | |
1056 final EdgeDims margin; | |
1057 final EdgeDims padding; | |
1058 final Matrix4 transform; | |
1059 final double width; | |
1060 final double height; | |
1061 | |
1062 UINode build() { | |
1063 UINode current = child; | |
1064 | |
1065 if (child == null && width == null && height == null) | |
1066 current = new SizedBox(); | |
1067 | |
1068 if (padding != null) | |
1069 current = new Padding(padding: padding, child: current); | |
1070 | |
1071 if (decoration != null) | |
1072 current = new DecoratedBox(decoration: decoration, child: current); | |
1073 | |
1074 if (width != null || height != null) | |
1075 current = new SizedBox( | |
1076 width: width == null ? double.INFINITY : width, | |
1077 height: height == null ? double.INFINITY : height, | |
1078 child: current | |
1079 ); | |
1080 | |
1081 if (constraints != null) | |
1082 current = new ConstrainedBox(constraints: constraints, child: current); | |
1083 | |
1084 if (margin != null) | |
1085 current = new Padding(padding: margin, child: current); | |
1086 | |
1087 if (transform != null) | |
1088 current = new Transform(transform: transform, child: current); | |
1089 | |
1090 return current; | |
1091 } | |
1092 | |
1093 } | |
1094 | |
1095 class UINodeAppView extends AppView { | |
1096 | |
1097 UINodeAppView() { | |
1098 assert(_appView == null); | |
1099 } | |
1100 | |
1101 static UINodeAppView _appView; | |
1102 static void initUINodeAppView() { | |
1103 if (_appView == null) | |
1104 _appView = new UINodeAppView(); | |
1105 } | |
1106 | |
1107 void dispatchEvent(sky.Event event, HitTestResult result) { | |
1108 assert(_appView == this); | |
1109 super.dispatchEvent(event, result); | |
1110 for (HitTestEntry entry in result.path.reversed) { | |
1111 UINode target = RenderObjectWrapper._getMounted(entry.target); | |
1112 if (target == null) | |
1113 continue; | |
1114 RenderObject targetRoot = target.root; | |
1115 while (target != null && target.root == targetRoot) { | |
1116 if (target is EventListenerNode) | |
1117 target._handleEvent(event); | |
1118 target = target._parent; | |
1119 } | |
1120 } | |
1121 } | |
1122 | |
1123 } | |
1124 | |
1125 abstract class AbstractUINodeRoot extends Component { | |
1126 | |
1127 AbstractUINodeRoot() : super(stateful: true) { | |
1128 UINodeAppView.initUINodeAppView(); | |
1129 _mounted = true; | |
1130 _scheduleComponentForRender(this); | |
1131 } | |
1132 | |
1133 void syncFields(AbstractUINodeRoot source) { | |
1134 assert(false); | |
1135 // if we get here, it implies that we have a parent | |
1136 } | |
1137 | |
1138 void _buildIfDirty() { | |
1139 assert(_dirty); | |
1140 assert(_mounted); | |
1141 assert(parent == null); | |
1142 _sync(null, null); | |
1143 } | |
1144 | |
1145 } | |
1146 | |
1147 abstract class App extends AbstractUINodeRoot { | |
1148 | |
1149 App(); | |
1150 | |
1151 AppView get appView => UINodeAppView._appView; | |
1152 | |
1153 void _buildIfDirty() { | |
1154 super._buildIfDirty(); | |
1155 | |
1156 if (root.parent == null) { | |
1157 // we haven't attached it yet | |
1158 UINodeAppView._appView.root = root; | |
1159 } | |
1160 assert(root.parent is RenderView); | |
1161 } | |
1162 | |
1163 } | |
1164 | |
1165 typedef UINode Builder(); | |
1166 | |
1167 class RenderNodeToUINodeAdapter extends AbstractUINodeRoot { | |
1168 | |
1169 RenderNodeToUINodeAdapter( | |
1170 RenderObjectWithChildMixin<RenderBox> container, | |
1171 this.builder | |
1172 ) : _container = container { | |
1173 assert(builder != null); | |
1174 } | |
1175 | |
1176 RenderObjectWithChildMixin<RenderBox> _container; | |
1177 RenderObjectWithChildMixin<RenderBox> get container => _container; | |
1178 void set container(RenderObjectWithChildMixin<RenderBox> value) { | |
1179 if (_container != value) { | |
1180 assert(value.child == null); | |
1181 if (root != null) { | |
1182 assert(_container.child == root); | |
1183 _container.child = null; | |
1184 } | |
1185 _container = value; | |
1186 if (root != null) { | |
1187 _container.child = root; | |
1188 assert(_container.child == root); | |
1189 } | |
1190 } | |
1191 } | |
1192 | |
1193 final Builder builder; | |
1194 | |
1195 void _buildIfDirty() { | |
1196 super._buildIfDirty(); | |
1197 if (root.parent == null) { | |
1198 // we haven't attached it yet | |
1199 assert(_container.child == null); | |
1200 _container.child = root; | |
1201 } | |
1202 assert(root.parent == _container); | |
1203 } | |
1204 | |
1205 UINode build() => builder(); | |
1206 | |
1207 } | |
OLD | NEW |