| OLD | NEW | 
|---|
| (Empty) |  | 
|  | 1 part of fn; | 
|  | 2 | 
|  | 3 void parentInsertBefore(sky.ParentNode parent, | 
|  | 4                         sky.Node node, | 
|  | 5                         sky.Node ref) { | 
|  | 6   if (ref != null) { | 
|  | 7     ref.insertBefore([node]); | 
|  | 8   } else { | 
|  | 9     parent.appendChild(node); | 
|  | 10   } | 
|  | 11 } | 
|  | 12 | 
|  | 13 abstract class Node { | 
|  | 14   String _key = null; | 
|  | 15   sky.Node _root = null; | 
|  | 16 | 
|  | 17   Node({ Object key }) { | 
|  | 18     _key = key == null ? "$runtimeType" : "$runtimeType-$key"; | 
|  | 19   } | 
|  | 20 | 
|  | 21   // Return true IFF the old node has *become* the new node (should be | 
|  | 22   // retained because it is stateful) | 
|  | 23   bool _sync(Node old, sky.ParentNode host, sky.Node insertBefore); | 
|  | 24 | 
|  | 25   void _remove() { | 
|  | 26     assert(_root != null); | 
|  | 27     _root.remove(); | 
|  | 28     _root = null; | 
|  | 29   } | 
|  | 30 } | 
|  | 31 | 
|  | 32 class Text extends Node { | 
|  | 33   String data; | 
|  | 34 | 
|  | 35   // Text nodes are special cases of having non-unique keys (which don't need | 
|  | 36   // to be assigned as part of the API). Since they are unique in not having | 
|  | 37   // children, there's little point to reordering, so we always just re-assign | 
|  | 38   // the data. | 
|  | 39   Text(this.data) : super(key:'*text*'); | 
|  | 40 | 
|  | 41   bool _sync(Node old, sky.ParentNode host, sky.Node insertBefore) { | 
|  | 42     if (old == null) { | 
|  | 43       _root = new sky.Text(data); | 
|  | 44       parentInsertBefore(host, _root, insertBefore); | 
|  | 45       return false; | 
|  | 46     } | 
|  | 47 | 
|  | 48     _root = old._root; | 
|  | 49     (_root as sky.Text).data = data; | 
|  | 50     return false; | 
|  | 51   } | 
|  | 52 } | 
|  | 53 | 
|  | 54 var _emptyList = new List<Node>(); | 
|  | 55 | 
|  | 56 abstract class Element extends Node { | 
|  | 57 | 
|  | 58   String get _tagName; | 
|  | 59 | 
|  | 60   Element get _emptyElement; | 
|  | 61 | 
|  | 62   String inlineStyle; | 
|  | 63 | 
|  | 64   sky.EventListener onClick; | 
|  | 65   sky.EventListener onFlingCancel; | 
|  | 66   sky.EventListener onFlingStart; | 
|  | 67   sky.EventListener onGestureTap; | 
|  | 68   sky.EventListener onPointerCancel; | 
|  | 69   sky.EventListener onPointerDown; | 
|  | 70   sky.EventListener onPointerMove; | 
|  | 71   sky.EventListener onPointerUp; | 
|  | 72   sky.EventListener onScrollEnd; | 
|  | 73   sky.EventListener onScrollStart; | 
|  | 74   sky.EventListener onScrollUpdate; | 
|  | 75   sky.EventListener onWheel; | 
|  | 76 | 
|  | 77   List<Node> _children = null; | 
|  | 78   String _className = ''; | 
|  | 79 | 
|  | 80   Element({ | 
|  | 81     Object key, | 
|  | 82     List<Node> children, | 
|  | 83     Style style, | 
|  | 84 | 
|  | 85     this.inlineStyle, | 
|  | 86 | 
|  | 87     // Events | 
|  | 88     this.onClick, | 
|  | 89     this.onFlingCancel, | 
|  | 90     this.onFlingStart, | 
|  | 91     this.onGestureTap, | 
|  | 92     this.onPointerCancel, | 
|  | 93     this.onPointerDown, | 
|  | 94     this.onPointerMove, | 
|  | 95     this.onPointerUp, | 
|  | 96     this.onScrollEnd, | 
|  | 97     this.onScrollStart, | 
|  | 98     this.onScrollUpdate, | 
|  | 99     this.onWheel | 
|  | 100   }) : super(key:key) { | 
|  | 101 | 
|  | 102     _className = style == null ? '': style._className; | 
|  | 103     _children = children == null ? _emptyList : children; | 
|  | 104 | 
|  | 105     if (debugWarnings()) { | 
|  | 106       _debugReportDuplicateIds(); | 
|  | 107     } | 
|  | 108   } | 
|  | 109 | 
|  | 110   void _remove() { | 
|  | 111     super._remove(); | 
|  | 112     if (_children != null) { | 
|  | 113       for (var child in _children) { | 
|  | 114         child._remove(); | 
|  | 115       } | 
|  | 116     } | 
|  | 117     _children = null; | 
|  | 118   } | 
|  | 119 | 
|  | 120   void _debugReportDuplicateIds() { | 
|  | 121     var idSet = new HashSet<String>(); | 
|  | 122     for (var child in _children) { | 
|  | 123       if (child is Text) { | 
|  | 124         continue; // Text nodes all have the same key and are never reordered. | 
|  | 125       } | 
|  | 126 | 
|  | 127       if (!idSet.add(child._key)) { | 
|  | 128         throw '''If multiple (non-Text) nodes of the same type exist as children | 
|  | 129                  of another node, they must have unique keys.'''; | 
|  | 130       } | 
|  | 131     } | 
|  | 132   } | 
|  | 133 | 
|  | 134   void _syncEvent(String eventName, sky.EventListener listener, | 
|  | 135                   sky.EventListener oldListener) { | 
|  | 136     sky.Element root = _root as sky.Element; | 
|  | 137     if (listener == oldListener) | 
|  | 138       return; | 
|  | 139 | 
|  | 140     if (oldListener != null) { | 
|  | 141       root.removeEventListener(eventName, oldListener); | 
|  | 142     } | 
|  | 143 | 
|  | 144     if (listener != null) { | 
|  | 145       root.addEventListener(eventName, listener); | 
|  | 146     } | 
|  | 147   } | 
|  | 148 | 
|  | 149   void _syncEvents([Element old]) { | 
|  | 150     _syncEvent('click', onClick, old.onClick); | 
|  | 151     _syncEvent('gestureflingcancel', onFlingCancel, old.onFlingCancel); | 
|  | 152     _syncEvent('gestureflingstart', onFlingStart, old.onFlingStart); | 
|  | 153     _syncEvent('gesturescrollend', onScrollEnd, old.onScrollEnd); | 
|  | 154     _syncEvent('gesturescrollstart', onScrollStart, old.onScrollStart); | 
|  | 155     _syncEvent('gesturescrollupdate', onScrollUpdate, old.onScrollUpdate); | 
|  | 156     _syncEvent('gesturetap', onGestureTap, old.onGestureTap); | 
|  | 157     _syncEvent('pointercancel', onPointerCancel, old.onPointerCancel); | 
|  | 158     _syncEvent('pointerdown', onPointerDown, old.onPointerDown); | 
|  | 159     _syncEvent('pointermove', onPointerMove, old.onPointerMove); | 
|  | 160     _syncEvent('pointerup', onPointerUp, old.onPointerUp); | 
|  | 161     _syncEvent('wheel', onWheel, old.onWheel); | 
|  | 162   } | 
|  | 163 | 
|  | 164   void _syncNode([Element old]) { | 
|  | 165     if (old == null) { | 
|  | 166       old = _emptyElement; | 
|  | 167     } | 
|  | 168 | 
|  | 169     _syncEvents(old); | 
|  | 170 | 
|  | 171     sky.Element root = _root as sky.Element; | 
|  | 172     if (_className != old._className) { | 
|  | 173       root.setAttribute('class', _className); | 
|  | 174     } | 
|  | 175 | 
|  | 176     if (inlineStyle != old.inlineStyle) { | 
|  | 177       root.setAttribute('style', inlineStyle); | 
|  | 178     } | 
|  | 179   } | 
|  | 180 | 
|  | 181   bool _sync(Node old, sky.ParentNode host, sky.Node insertBefore) { | 
|  | 182     // print("---Syncing children of $_key"); | 
|  | 183 | 
|  | 184     Element oldElement = old as Element; | 
|  | 185 | 
|  | 186     if (oldElement == null) { | 
|  | 187       // print("...no oldElement, initial render"); | 
|  | 188 | 
|  | 189       _root = sky.document.createElement(_tagName); | 
|  | 190       _syncNode(); | 
|  | 191 | 
|  | 192       for (var child in _children) { | 
|  | 193         child._sync(null, _root, null); | 
|  | 194         assert(child._root is sky.Node); | 
|  | 195       } | 
|  | 196 | 
|  | 197       parentInsertBefore(host, _root, insertBefore); | 
|  | 198       return false; | 
|  | 199     } | 
|  | 200 | 
|  | 201     _root = oldElement._root; | 
|  | 202     oldElement._root = null; | 
|  | 203     sky.Element root = (_root as sky.Element); | 
|  | 204 | 
|  | 205     _syncNode(oldElement); | 
|  | 206 | 
|  | 207     var startIndex = 0; | 
|  | 208     var endIndex = _children.length; | 
|  | 209 | 
|  | 210     var oldChildren = oldElement._children; | 
|  | 211     var oldStartIndex = 0; | 
|  | 212     var oldEndIndex = oldChildren.length; | 
|  | 213 | 
|  | 214     sky.Node nextSibling = null; | 
|  | 215     Node currentNode = null; | 
|  | 216     Node oldNode = null; | 
|  | 217 | 
|  | 218     void sync(int atIndex) { | 
|  | 219       if (currentNode._sync(oldNode, root, nextSibling)) { | 
|  | 220         // oldNode was stateful and must be retained. | 
|  | 221         assert(oldNode != null); | 
|  | 222         currentNode = oldNode; | 
|  | 223         _children[atIndex] = currentNode; | 
|  | 224       } | 
|  | 225       assert(currentNode._root is sky.Node); | 
|  | 226     } | 
|  | 227 | 
|  | 228     // Scan backwards from end of list while nodes can be directly synced | 
|  | 229     // without reordering. | 
|  | 230     // print("...scanning backwards"); | 
|  | 231     while (endIndex > startIndex && oldEndIndex > oldStartIndex) { | 
|  | 232       currentNode = _children[endIndex - 1]; | 
|  | 233       oldNode = oldChildren[oldEndIndex - 1]; | 
|  | 234 | 
|  | 235       if (currentNode._key != oldNode._key) { | 
|  | 236         break; | 
|  | 237       } | 
|  | 238 | 
|  | 239       // print('> syncing matched at: $endIndex : $oldEndIndex'); | 
|  | 240       endIndex--; | 
|  | 241       oldEndIndex--; | 
|  | 242       sync(endIndex); | 
|  | 243       nextSibling = currentNode._root; | 
|  | 244     } | 
|  | 245 | 
|  | 246     HashMap<String, Node> oldNodeIdMap = null; | 
|  | 247 | 
|  | 248     bool oldNodeReordered(String key) { | 
|  | 249       return oldNodeIdMap != null && | 
|  | 250              oldNodeIdMap.containsKey(key) && | 
|  | 251              oldNodeIdMap[key] == null; | 
|  | 252     } | 
|  | 253 | 
|  | 254     void advanceOldStartIndex() { | 
|  | 255       oldStartIndex++; | 
|  | 256       while (oldStartIndex < oldEndIndex && | 
|  | 257              oldNodeReordered(oldChildren[oldStartIndex]._key)) { | 
|  | 258         oldStartIndex++; | 
|  | 259       } | 
|  | 260     } | 
|  | 261 | 
|  | 262     void ensureOldIdMap() { | 
|  | 263       if (oldNodeIdMap != null) | 
|  | 264         return; | 
|  | 265 | 
|  | 266       oldNodeIdMap = new HashMap<String, Node>(); | 
|  | 267       for (int i = oldStartIndex; i < oldEndIndex; i++) { | 
|  | 268         var node = oldChildren[i]; | 
|  | 269         if (node is! Text) { | 
|  | 270           oldNodeIdMap.putIfAbsent(node._key, () => node); | 
|  | 271         } | 
|  | 272       } | 
|  | 273     } | 
|  | 274 | 
|  | 275     bool searchForOldNode() { | 
|  | 276       if (currentNode is Text) | 
|  | 277         return false; // Never re-order Text nodes. | 
|  | 278 | 
|  | 279       ensureOldIdMap(); | 
|  | 280       oldNode = oldNodeIdMap[currentNode._key]; | 
|  | 281       if (oldNode == null) | 
|  | 282         return false; | 
|  | 283 | 
|  | 284       oldNodeIdMap[currentNode._key] = null; // mark it reordered. | 
|  | 285       // print("Reparenting ${currentNode._key}"); | 
|  | 286       parentInsertBefore(root, oldNode._root, nextSibling); | 
|  | 287       return true; | 
|  | 288     } | 
|  | 289 | 
|  | 290     // Scan forwards, this time we may re-order; | 
|  | 291     // print("...scanning forward"); | 
|  | 292     nextSibling = root.firstChild; | 
|  | 293     while (startIndex < endIndex && oldStartIndex < oldEndIndex) { | 
|  | 294       currentNode = _children[startIndex]; | 
|  | 295       oldNode = oldChildren[oldStartIndex]; | 
|  | 296 | 
|  | 297       if (currentNode._key == oldNode._key) { | 
|  | 298         // print('> syncing matched at: $startIndex : $oldStartIndex'); | 
|  | 299         assert(currentNode.runtimeType == oldNode.runtimeType); | 
|  | 300         nextSibling = nextSibling.nextSibling; | 
|  | 301         sync(startIndex); | 
|  | 302         startIndex++; | 
|  | 303         advanceOldStartIndex(); | 
|  | 304         continue; | 
|  | 305       } | 
|  | 306 | 
|  | 307       oldNode = null; | 
|  | 308       if (searchForOldNode()) { | 
|  | 309         // print('> reordered to $startIndex'); | 
|  | 310       } else { | 
|  | 311         // print('> inserting at $startIndex'); | 
|  | 312       } | 
|  | 313 | 
|  | 314       sync(startIndex); | 
|  | 315       startIndex++; | 
|  | 316     } | 
|  | 317 | 
|  | 318     // New insertions | 
|  | 319     oldNode = null; | 
|  | 320     // print('...processing remaining insertions'); | 
|  | 321     while (startIndex < endIndex) { | 
|  | 322       // print('> inserting at $startIndex'); | 
|  | 323       currentNode = _children[startIndex]; | 
|  | 324       sync(startIndex); | 
|  | 325       startIndex++; | 
|  | 326     } | 
|  | 327 | 
|  | 328     // Removals | 
|  | 329     // print('...processing remaining removals'); | 
|  | 330     currentNode = null; | 
|  | 331     while (oldStartIndex < oldEndIndex) { | 
|  | 332       oldNode = oldChildren[oldStartIndex]; | 
|  | 333       // print('> ${oldNode._key} removing from $oldEndIndex'); | 
|  | 334       oldNode._remove(); | 
|  | 335       advanceOldStartIndex(); | 
|  | 336     } | 
|  | 337 | 
|  | 338     oldElement._children = null; | 
|  | 339     return false; | 
|  | 340   } | 
|  | 341 } | 
|  | 342 | 
|  | 343 class Container extends Element { | 
|  | 344 | 
|  | 345   String get _tagName => 'div'; | 
|  | 346 | 
|  | 347   static Container _emptyContainer = new Container(); | 
|  | 348 | 
|  | 349   Element get _emptyElement => _emptyContainer; | 
|  | 350 | 
|  | 351   Container({ | 
|  | 352     Object key, | 
|  | 353     List<Node> children, | 
|  | 354     Style style, | 
|  | 355     String inlineStyle, | 
|  | 356     sky.EventListener onClick, | 
|  | 357     sky.EventListener onFlingCancel, | 
|  | 358     sky.EventListener onFlingStart, | 
|  | 359     sky.EventListener onGestureTap, | 
|  | 360     sky.EventListener onPointerCancel, | 
|  | 361     sky.EventListener onPointerDown, | 
|  | 362     sky.EventListener onPointerMove, | 
|  | 363     sky.EventListener onPointerUp, | 
|  | 364     sky.EventListener onScrollEnd, | 
|  | 365     sky.EventListener onScrollStart, | 
|  | 366     sky.EventListener onScrollUpdate, | 
|  | 367     sky.EventListener onWheel | 
|  | 368   }) : super( | 
|  | 369     key: key, | 
|  | 370     children: children, | 
|  | 371     style: style, | 
|  | 372     inlineStyle: inlineStyle, | 
|  | 373     onClick: onClick, | 
|  | 374     onFlingCancel: onFlingCancel, | 
|  | 375     onFlingStart: onFlingStart, | 
|  | 376     onGestureTap: onGestureTap, | 
|  | 377     onPointerCancel: onPointerCancel, | 
|  | 378     onPointerDown: onPointerDown, | 
|  | 379     onPointerMove: onPointerMove, | 
|  | 380     onPointerUp: onPointerUp, | 
|  | 381     onScrollEnd: onScrollEnd, | 
|  | 382     onScrollStart: onScrollStart, | 
|  | 383     onScrollUpdate: onScrollUpdate, | 
|  | 384     onWheel: onWheel | 
|  | 385   ); | 
|  | 386 } | 
|  | 387 | 
|  | 388 class Image extends Element { | 
|  | 389 | 
|  | 390   String get _tagName => 'img'; | 
|  | 391 | 
|  | 392   static Image _emptyImage = new Image(); | 
|  | 393   Element get _emptyElement => _emptyImage; | 
|  | 394 | 
|  | 395   String src; | 
|  | 396   int width; | 
|  | 397   int height; | 
|  | 398 | 
|  | 399   Image({ | 
|  | 400     Object key, | 
|  | 401     List<Node> children, | 
|  | 402     Style style, | 
|  | 403     String inlineStyle, | 
|  | 404     sky.EventListener onClick, | 
|  | 405     sky.EventListener onFlingCancel, | 
|  | 406     sky.EventListener onFlingStart, | 
|  | 407     sky.EventListener onGestureTap, | 
|  | 408     sky.EventListener onPointerCancel, | 
|  | 409     sky.EventListener onPointerDown, | 
|  | 410     sky.EventListener onPointerMove, | 
|  | 411     sky.EventListener onPointerUp, | 
|  | 412     sky.EventListener onScrollEnd, | 
|  | 413     sky.EventListener onScrollStart, | 
|  | 414     sky.EventListener onScrollUpdate, | 
|  | 415     sky.EventListener onWheel, | 
|  | 416     this.width, | 
|  | 417     this.height, | 
|  | 418     this.src | 
|  | 419   }) : super( | 
|  | 420     key: key, | 
|  | 421     children: children, | 
|  | 422     style: style, | 
|  | 423     inlineStyle: inlineStyle, | 
|  | 424     onClick: onClick, | 
|  | 425     onFlingCancel: onFlingCancel, | 
|  | 426     onFlingStart: onFlingStart, | 
|  | 427     onGestureTap: onGestureTap, | 
|  | 428     onPointerCancel: onPointerCancel, | 
|  | 429     onPointerDown: onPointerDown, | 
|  | 430     onPointerMove: onPointerMove, | 
|  | 431     onPointerUp: onPointerUp, | 
|  | 432     onScrollEnd: onScrollEnd, | 
|  | 433     onScrollStart: onScrollStart, | 
|  | 434     onScrollUpdate: onScrollUpdate, | 
|  | 435     onWheel: onWheel | 
|  | 436   ); | 
|  | 437 | 
|  | 438   void _syncNode([Element old]) { | 
|  | 439     super._syncNode(old); | 
|  | 440 | 
|  | 441     Image oldImage = old != null ? old : _emptyImage; | 
|  | 442     sky.HTMLImageElement skyImage = _root as sky.HTMLImageElement; | 
|  | 443     if (src != oldImage.src) { | 
|  | 444       skyImage.src = src; | 
|  | 445     } | 
|  | 446 | 
|  | 447     if (width != oldImage.width) { | 
|  | 448       skyImage.style['width'] = '${width}px'; | 
|  | 449     } | 
|  | 450     if (height != oldImage.height) { | 
|  | 451       skyImage.style['height'] = '${height}px'; | 
|  | 452     } | 
|  | 453   } | 
|  | 454 } | 
|  | 455 | 
|  | 456 class Anchor extends Element { | 
|  | 457 | 
|  | 458   String get _tagName => 'a'; | 
|  | 459 | 
|  | 460   static Anchor _emptyAnchor = new Anchor(); | 
|  | 461 | 
|  | 462   String href; | 
|  | 463 | 
|  | 464   Anchor({ | 
|  | 465     Object key, | 
|  | 466     List<Node> children, | 
|  | 467     Style style, | 
|  | 468     String inlineStyle, | 
|  | 469     sky.EventListener onClick, | 
|  | 470     sky.EventListener onFlingCancel, | 
|  | 471     sky.EventListener onFlingStart, | 
|  | 472     sky.EventListener onGestureTap, | 
|  | 473     sky.EventListener onPointerCancel, | 
|  | 474     sky.EventListener onPointerDown, | 
|  | 475     sky.EventListener onPointerMove, | 
|  | 476     sky.EventListener onPointerUp, | 
|  | 477     sky.EventListener onScrollEnd, | 
|  | 478     sky.EventListener onScrollStart, | 
|  | 479     sky.EventListener onScrollUpdate, | 
|  | 480     sky.EventListener onWheel, | 
|  | 481     this.width, | 
|  | 482     this.height, | 
|  | 483     this.href | 
|  | 484   }) : super( | 
|  | 485     key: key, | 
|  | 486     children: children, | 
|  | 487     style: style, | 
|  | 488     inlineStyle: inlineStyle, | 
|  | 489     onClick: onClick, | 
|  | 490     onFlingCancel: onFlingCancel, | 
|  | 491     onFlingStart: onFlingStart, | 
|  | 492     onGestureTap: onGestureTap, | 
|  | 493     onPointerCancel: onPointerCancel, | 
|  | 494     onPointerDown: onPointerDown, | 
|  | 495     onPointerMove: onPointerMove, | 
|  | 496     onPointerUp: onPointerUp, | 
|  | 497     onScrollEnd: onScrollEnd, | 
|  | 498     onScrollStart: onScrollStart, | 
|  | 499     onScrollUpdate: onScrollUpdate, | 
|  | 500     onWheel: onWheel | 
|  | 501   ); | 
|  | 502 | 
|  | 503   void _syncNode([Element old]) { | 
|  | 504     Anchor oldAnchor = old != null ? old as Anchor : _emptyAnchor; | 
|  | 505     super._syncNode(oldAnchor); | 
|  | 506 | 
|  | 507     sky.HTMLAnchorElement skyAnchor = _root as sky.HTMLAnchorElement; | 
|  | 508     if (href != oldAnchor.href) { | 
|  | 509       skyAnchor.href = href; | 
|  | 510     } | 
|  | 511   } | 
|  | 512 } | 
| OLD | NEW | 
|---|