| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 library fn; | 5 library fn; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 import 'dart:sky' as sky; | 9 import 'dart:sky' as sky; |
| 10 import 'reflect.dart' as reflect; | 10 import 'reflect.dart' as reflect; |
| (...skipping 20 matching lines...) Expand all Loading... |
| 31 bool _defunct = false; | 31 bool _defunct = false; |
| 32 | 32 |
| 33 UINode({ Object key }) { | 33 UINode({ Object key }) { |
| 34 _key = key == null ? "$runtimeType" : "$runtimeType-$key"; | 34 _key = key == null ? "$runtimeType" : "$runtimeType-$key"; |
| 35 } | 35 } |
| 36 | 36 |
| 37 // Subclasses which implements Nodes that become stateful may return true | 37 // Subclasses which implements Nodes that become stateful may return true |
| 38 // if the |old| node has become stateful and should be retained. | 38 // if the |old| node has become stateful and should be retained. |
| 39 bool _willSync(UINode old) => false; | 39 bool _willSync(UINode old) => false; |
| 40 | 40 |
| 41 bool get interchangeable => false; // if true, then keys can be duplicated |
| 42 |
| 41 void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore); | 43 void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore); |
| 42 | 44 |
| 43 void _remove() { | 45 void _remove() { |
| 44 _defunct = true; | 46 _defunct = true; |
| 45 _root = null; | 47 _root = null; |
| 46 handleRemoved(); | 48 handleRemoved(); |
| 47 } | 49 } |
| 48 void handleRemoved() { } | 50 void handleRemoved() { } |
| 49 | 51 |
| 50 int _nodeDepth; | 52 int _nodeDepth; |
| (...skipping 276 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 327 final List<UINode> children; | 329 final List<UINode> children; |
| 328 final Style style; | 330 final Style style; |
| 329 final String inlineStyle; | 331 final String inlineStyle; |
| 330 | 332 |
| 331 SkyElementWrapper({ | 333 SkyElementWrapper({ |
| 332 Object key, | 334 Object key, |
| 333 List<UINode> children, | 335 List<UINode> children, |
| 334 this.style, | 336 this.style, |
| 335 this.inlineStyle | 337 this.inlineStyle |
| 336 }) : this.children = children == null ? _emptyList : children, | 338 }) : this.children = children == null ? _emptyList : children, |
| 337 super(key:key) { | 339 super(key: key) { |
| 338 | 340 |
| 339 assert(!_debugHasDuplicateIds()); | 341 assert(!_debugHasDuplicateIds()); |
| 340 } | 342 } |
| 341 | 343 |
| 342 void _remove() { | 344 void _remove() { |
| 343 assert(children != null); | 345 assert(children != null); |
| 344 for (var child in children) { | 346 for (var child in children) { |
| 345 assert(child != null); | 347 assert(child != null); |
| 346 _removeChild(child); | 348 _removeChild(child); |
| 347 } | 349 } |
| 348 super._remove(); | 350 super._remove(); |
| 349 } | 351 } |
| 350 | 352 |
| 351 bool _debugHasDuplicateIds() { | 353 bool _debugHasDuplicateIds() { |
| 352 var idSet = new HashSet<String>(); | 354 var idSet = new HashSet<String>(); |
| 353 for (var child in children) { | 355 for (var child in children) { |
| 354 assert(child != null); | 356 assert(child != null); |
| 355 if (child is Text) { | 357 if (child.interchangeable) |
| 356 continue; // Text nodes all have the same key and are never reordered. | 358 continue; // when these nodes are reordered, we just reassign the data |
| 357 } | |
| 358 | 359 |
| 359 if (!idSet.add(child._key)) { | 360 if (!idSet.add(child._key)) { |
| 360 throw '''If multiple (non-Text) nodes of the same type exist as children | 361 throw '''If multiple non-interchangeable nodes of the same type exist as
children |
| 361 of another node, they must have unique keys. | 362 of another node, they must have unique keys. |
| 362 Duplicate: "${child._key}"'''; | 363 Duplicate: "${child._key}"'''; |
| 363 } | 364 } |
| 364 } | 365 } |
| 365 return false; | 366 return false; |
| 366 } | 367 } |
| 367 | 368 |
| 368 void _syncNode(SkyNodeWrapper old) { | 369 void _syncNode(SkyNodeWrapper old) { |
| 369 SkyElementWrapper oldSkyElementWrapper = old as SkyElementWrapper; | 370 SkyElementWrapper oldSkyElementWrapper = old as SkyElementWrapper; |
| 370 | 371 |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 449 } | 450 } |
| 450 } | 451 } |
| 451 | 452 |
| 452 void ensureOldIdMap() { | 453 void ensureOldIdMap() { |
| 453 if (oldNodeIdMap != null) | 454 if (oldNodeIdMap != null) |
| 454 return; | 455 return; |
| 455 | 456 |
| 456 oldNodeIdMap = new HashMap<String, UINode>(); | 457 oldNodeIdMap = new HashMap<String, UINode>(); |
| 457 for (int i = oldStartIndex; i < oldEndIndex; i++) { | 458 for (int i = oldStartIndex; i < oldEndIndex; i++) { |
| 458 var node = oldChildren[i]; | 459 var node = oldChildren[i]; |
| 459 if (node is! Text) { | 460 if (!node.interchangeable) |
| 460 oldNodeIdMap.putIfAbsent(node._key, () => node); | 461 oldNodeIdMap.putIfAbsent(node._key, () => node); |
| 461 } | |
| 462 } | 462 } |
| 463 } | 463 } |
| 464 | 464 |
| 465 bool searchForOldNode() { | 465 bool searchForOldNode() { |
| 466 if (currentNode is Text) | 466 if (currentNode.interchangeable) |
| 467 return false; // Never re-order Text nodes. | 467 return false; // never re-order these nodes |
| 468 | 468 |
| 469 ensureOldIdMap(); | 469 ensureOldIdMap(); |
| 470 oldNode = oldNodeIdMap[currentNode._key]; | 470 oldNode = oldNodeIdMap[currentNode._key]; |
| 471 if (oldNode == null) | 471 if (oldNode == null) |
| 472 return false; | 472 return false; |
| 473 | 473 |
| 474 oldNodeIdMap[currentNode._key] = null; // mark it reordered. | 474 oldNodeIdMap[currentNode._key] = null; // mark it reordered |
| 475 assert(_root is RenderCSSContainer); | 475 assert(_root is RenderCSSContainer); |
| 476 assert(oldNode._root is RenderCSSContainer); | 476 assert(oldNode._root is RenderCSSContainer); |
| 477 oldSkyElementWrapper._root.remove(oldNode._root); | 477 oldSkyElementWrapper._root.remove(oldNode._root); |
| 478 _root.add(oldNode._root, before: nextSibling); | 478 _root.add(oldNode._root, before: nextSibling); |
| 479 return true; | 479 return true; |
| 480 } | 480 } |
| 481 | 481 |
| 482 // Scan forwards, this time we may re-order; | 482 // Scan forwards, this time we may re-order; |
| 483 nextSibling = _root.firstChild; | 483 nextSibling = _root.firstChild; |
| 484 while (startIndex < endIndex && oldStartIndex < oldEndIndex) { | 484 while (startIndex < endIndex && oldStartIndex < oldEndIndex) { |
| (...skipping 28 matching lines...) Expand all Loading... |
| 513 while (oldStartIndex < oldEndIndex) { | 513 while (oldStartIndex < oldEndIndex) { |
| 514 oldNode = oldChildren[oldStartIndex]; | 514 oldNode = oldChildren[oldStartIndex]; |
| 515 _removeChild(oldNode); | 515 _removeChild(oldNode); |
| 516 advanceOldStartIndex(); | 516 advanceOldStartIndex(); |
| 517 } | 517 } |
| 518 } | 518 } |
| 519 } | 519 } |
| 520 | 520 |
| 521 class Container extends SkyElementWrapper { | 521 class Container extends SkyElementWrapper { |
| 522 | 522 |
| 523 RenderCSS _createNode() => new RenderCSSContainer(this); | 523 RenderCSSContainer _root; |
| 524 RenderCSSContainer _createNode() => new RenderCSSContainer(this); |
| 524 | 525 |
| 525 static final Container _emptyContainer = new Container(); | 526 static final Container _emptyContainer = new Container(); |
| 526 | 527 |
| 527 SkyNodeWrapper get _emptyNode => _emptyContainer; | 528 SkyNodeWrapper get _emptyNode => _emptyContainer; |
| 528 | 529 |
| 529 Container({ | 530 Container({ |
| 530 Object key, | 531 Object key, |
| 531 List<UINode> children, | 532 List<UINode> children, |
| 532 Style style, | 533 Style style, |
| 533 String inlineStyle | 534 String inlineStyle |
| 534 }) : super( | 535 }) : super( |
| 535 key: key, | 536 key: key, |
| 536 children: children, | 537 children: children, |
| 537 style: style, | 538 style: style, |
| 538 inlineStyle: inlineStyle | 539 inlineStyle: inlineStyle |
| 539 ); | 540 ); |
| 540 } | 541 } |
| 541 | 542 |
| 543 class Paragraph extends SkyElementWrapper { |
| 544 |
| 545 RenderCSSParagraph _root; |
| 546 RenderCSSParagraph _createNode() => new RenderCSSParagraph(this); |
| 547 |
| 548 static final Paragraph _emptyContainer = new Paragraph(); |
| 549 |
| 550 SkyNodeWrapper get _emptyNode => _emptyContainer; |
| 551 |
| 552 Paragraph({ |
| 553 Object key, |
| 554 List<UINode> children, |
| 555 Style style, |
| 556 String inlineStyle |
| 557 }) : super( |
| 558 key: key, |
| 559 children: children, |
| 560 style: style, |
| 561 inlineStyle: inlineStyle |
| 562 ); |
| 563 } |
| 564 |
| 542 class FlexContainer extends SkyElementWrapper { | 565 class FlexContainer extends SkyElementWrapper { |
| 543 | 566 |
| 544 RenderCSSFlex _root; | 567 RenderCSSFlex _root; |
| 545 RenderCSSFlex _createNode() => new RenderCSSFlex(this, this.direction); | 568 RenderCSSFlex _createNode() => new RenderCSSFlex(this, this.direction); |
| 546 | 569 |
| 547 static final FlexContainer _emptyContainer = new FlexContainer(); | 570 static final FlexContainer _emptyContainer = new FlexContainer(); |
| 548 // direction doesn't matter if it's empty | 571 // direction doesn't matter if it's empty |
| 549 | 572 |
| 550 SkyNodeWrapper get _emptyNode => _emptyContainer; | 573 SkyNodeWrapper get _emptyNode => _emptyContainer; |
| 551 | 574 |
| (...skipping 11 matching lines...) Expand all Loading... |
| 563 style: style, | 586 style: style, |
| 564 inlineStyle: inlineStyle | 587 inlineStyle: inlineStyle |
| 565 ); | 588 ); |
| 566 | 589 |
| 567 void _syncNode(UINode old) { | 590 void _syncNode(UINode old) { |
| 568 super._syncNode(old); | 591 super._syncNode(old); |
| 569 _root.direction = direction; | 592 _root.direction = direction; |
| 570 } | 593 } |
| 571 } | 594 } |
| 572 | 595 |
| 573 class Text extends SkyElementWrapper { | 596 class TextFragment extends SkyElementWrapper { |
| 574 | 597 |
| 575 RenderCSSText _root; | 598 RenderCSSInline _root; |
| 576 RenderCSSText _createNode() => new RenderCSSText(this, this.data); | 599 RenderCSSInline _createNode() => new RenderCSSInline(this, this.data); |
| 577 | 600 |
| 578 static final Text _emptyText = new Text(''); | 601 static final TextFragment _emptyText = new TextFragment(''); |
| 579 | 602 |
| 580 SkyNodeWrapper get _emptyNode => _emptyText; | 603 SkyNodeWrapper get _emptyNode => _emptyText; |
| 581 | 604 |
| 582 final String data; | 605 final String data; |
| 583 | 606 |
| 584 // Text nodes are special cases of having non-unique keys (which don't need | 607 TextFragment(this.data, { |
| 585 // to be assigned as part of the API). Since they are unique in not having | 608 Object key, |
| 586 // children, there's little point to reordering, so we always just re-assign | |
| 587 // the data. | |
| 588 Text(this.data, { | |
| 589 Style style, | 609 Style style, |
| 590 String inlineStyle | 610 String inlineStyle |
| 591 }) : super( | 611 }) : super( |
| 592 key: '*text*', | 612 key: key, |
| 593 style: style, | 613 style: style, |
| 594 inlineStyle: inlineStyle | 614 inlineStyle: inlineStyle |
| 595 ); | 615 ); |
| 596 | 616 |
| 597 void _syncNode(UINode old) { | 617 void _syncNode(UINode old) { |
| 598 super._syncNode(old); | 618 super._syncNode(old); |
| 599 _root.data = data; | 619 _root.data = data; |
| 600 } | 620 } |
| 601 } | 621 } |
| 602 | 622 |
| 603 class Image extends SkyElementWrapper { | 623 class Image extends SkyElementWrapper { |
| 604 | 624 |
| 605 RenderCSSImage _root; | 625 RenderCSSImage _root; |
| 606 RenderCSSImage _createNode() => new RenderCSSImage(this, this.src, this.width,
this.height); | 626 RenderCSSImage _createNode() => new RenderCSSImage(this, this.src, this.width,
this.height); |
| 607 | 627 |
| 608 static final Image _emptyImage = new Image(); | 628 static final Image _emptyImage = new Image(); |
| 609 | 629 |
| 610 SkyNodeWrapper get _emptyNode => _emptyImage; | 630 SkyNodeWrapper get _emptyNode => _emptyImage; |
| 611 | 631 |
| 612 final String src; | 632 final String src; |
| 613 final int width; | 633 final int width; |
| 614 final int height; | 634 final int height; |
| 615 | 635 |
| 616 Image({ | 636 Image({ |
| 617 Object key, | 637 Object key, |
| 618 List<UINode> children, | |
| 619 Style style, | 638 Style style, |
| 620 String inlineStyle, | 639 String inlineStyle, |
| 621 this.width, | 640 this.width, |
| 622 this.height, | 641 this.height, |
| 623 this.src | 642 this.src |
| 624 }) : super( | 643 }) : super( |
| 625 key: key, | 644 key: key, |
| 626 children: children, | |
| 627 style: style, | 645 style: style, |
| 628 inlineStyle: inlineStyle | 646 inlineStyle: inlineStyle |
| 629 ); | 647 ); |
| 630 | 648 |
| 631 void _syncNode(UINode old) { | 649 void _syncNode(UINode old) { |
| 632 super._syncNode(old); | 650 super._syncNode(old); |
| 633 _root.configure(this.src, this.width, this.height); | 651 _root.configure(this.src, this.width, this.height); |
| 634 } | 652 } |
| 635 } | 653 } |
| 636 | 654 |
| (...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 730 if (_unmountCallbacks == null) | 748 if (_unmountCallbacks == null) |
| 731 _unmountCallbacks = new List<Function>(); | 749 _unmountCallbacks = new List<Function>(); |
| 732 | 750 |
| 733 _unmountCallbacks.add(fn); | 751 _unmountCallbacks.add(fn); |
| 734 } | 752 } |
| 735 | 753 |
| 736 | 754 |
| 737 Component({ Object key, bool stateful }) | 755 Component({ Object key, bool stateful }) |
| 738 : _stateful = stateful != null ? stateful : false, | 756 : _stateful = stateful != null ? stateful : false, |
| 739 _order = _currentOrder + 1, | 757 _order = _currentOrder + 1, |
| 740 super(key:key); | 758 super(key: key); |
| 741 | 759 |
| 742 Component.fromArgs(Object key, bool stateful) | 760 Component.fromArgs(Object key, bool stateful) |
| 743 : this(key: key, stateful: stateful); | 761 : this(key: key, stateful: stateful); |
| 744 | 762 |
| 745 void _didMount() { | 763 void _didMount() { |
| 746 if (_mountCallbacks != null) | 764 if (_mountCallbacks != null) |
| 747 _mountCallbacks.forEach((fn) => fn()); | 765 _mountCallbacks.forEach((fn) => fn()); |
| 748 } | 766 } |
| 749 | 767 |
| 750 void _didUnmount() { | 768 void _didUnmount() { |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 854 } | 872 } |
| 855 | 873 |
| 856 void _buildIfDirty() { | 874 void _buildIfDirty() { |
| 857 if (!_dirty || _defunct) | 875 if (!_dirty || _defunct) |
| 858 return; | 876 return; |
| 859 | 877 |
| 860 _trace('$_key rebuilding...'); | 878 _trace('$_key rebuilding...'); |
| 861 _sync(null, _host, _root); | 879 _sync(null, _host, _root); |
| 862 } | 880 } |
| 863 } | 881 } |
| 882 |
| 883 class Text extends Component { |
| 884 Text(this.data) : super(key: '*text*'); |
| 885 final String data; |
| 886 bool get interchangeable => true; |
| 887 UINode build() => new Paragraph(children: [new TextFragment(data)]); |
| 888 } |
| OLD | NEW |