| 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; |
| 11 import 'layout.dart'; |
| 12 |
| 13 export 'layout.dart' show Style; |
| 11 | 14 |
| 12 final sky.Tracing _tracing = sky.window.tracing; | 15 final sky.Tracing _tracing = sky.window.tracing; |
| 13 | 16 |
| 14 final bool _shouldLogRenderDuration = false; | 17 final bool _shouldLogRenderDuration = false; |
| 15 final bool _shouldTrace = false; | 18 final bool _shouldTrace = false; |
| 16 | 19 |
| 17 class Style { | |
| 18 final String _className; | |
| 19 static final Map<String, Style> _cache = new HashMap<String, Style>(); | |
| 20 | |
| 21 static int _nextStyleId = 1; | |
| 22 | |
| 23 static String _getNextClassName() { return "style${_nextStyleId++}"; } | |
| 24 | |
| 25 Style extend(Style other) { | |
| 26 var className = "$_className ${other._className}"; | |
| 27 | |
| 28 return _cache.putIfAbsent(className, () { | |
| 29 return new Style._internal(className); | |
| 30 }); | |
| 31 } | |
| 32 | |
| 33 factory Style(String styles) { | |
| 34 return _cache.putIfAbsent(styles, () { | |
| 35 var className = _getNextClassName(); | |
| 36 sky.Element styleNode = sky.document.createElement('style'); | |
| 37 styleNode.setChild(new sky.Text(".$className { $styles }")); | |
| 38 sky.document.appendChild(styleNode); | |
| 39 return new Style._internal(className); | |
| 40 }); | |
| 41 } | |
| 42 | |
| 43 Style._internal(this._className); | |
| 44 } | |
| 45 | |
| 46 void _parentInsertBefore(sky.ParentNode parent, | |
| 47 sky.Node node, | |
| 48 sky.Node ref) { | |
| 49 if (ref != null) { | |
| 50 ref.insertBefore([node]); | |
| 51 } else { | |
| 52 parent.appendChild(node); | |
| 53 } | |
| 54 } | |
| 55 | |
| 56 enum _SyncOperation { IDENTICAL, INSERTION, STATEFUL, STATELESS, REMOVAL } | 20 enum _SyncOperation { IDENTICAL, INSERTION, STATEFUL, STATELESS, REMOVAL } |
| 57 | 21 |
| 58 /* | 22 /* |
| 59 * All Effen nodes derive from UINode. All nodes have a _parent, a _key and | 23 * All Effen nodes derive from UINode. All nodes have a _parent, a _key and |
| 60 * can be sync'd. | 24 * can be sync'd. |
| 61 */ | 25 */ |
| 62 abstract class UINode { | 26 abstract class UINode { |
| 63 String _key; | 27 String _key; |
| 64 UINode _parent; | 28 UINode _parent; |
| 65 UINode get parent => _parent; | 29 UINode get parent => _parent; |
| 66 sky.Node _root; | 30 RenderCSS _root; |
| 67 bool _defunct = false; | 31 bool _defunct = false; |
| 68 | 32 |
| 69 UINode({ Object key }) { | 33 UINode({ Object key }) { |
| 70 _key = key == null ? "$runtimeType" : "$runtimeType-$key"; | 34 _key = key == null ? "$runtimeType" : "$runtimeType-$key"; |
| 71 } | 35 } |
| 72 | 36 |
| 73 // Subclasses which implements Nodes that become stateful may return true | 37 // Subclasses which implements Nodes that become stateful may return true |
| 74 // if the |old| node has become stateful and should be retained. | 38 // if the |old| node has become stateful and should be retained. |
| 75 bool _willSync(UINode old) => false; | 39 bool _willSync(UINode old) => false; |
| 76 | 40 |
| 77 void _sync(UINode old, sky.ParentNode host, sky.Node insertBefore); | 41 void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore); |
| 78 | 42 |
| 79 void _remove() { | 43 void _remove() { |
| 80 _defunct = true; | 44 _defunct = true; |
| 81 _root = null; | 45 _root = null; |
| 82 handleRemoved(); | 46 handleRemoved(); |
| 83 } | 47 } |
| 84 void handleRemoved() { } | 48 void handleRemoved() { } |
| 85 | 49 |
| 86 int _nodeDepth; | 50 int _nodeDepth; |
| 87 void _ensureDepth() { | 51 void _ensureDepth() { |
| (...skipping 23 matching lines...) Expand all Loading... |
| 111 String outString = opString.substring(opString.indexOf('.') + 1); | 75 String outString = opString.substring(opString.indexOf('.') + 1); |
| 112 _trace('_sync($outString) $key'); | 76 _trace('_sync($outString) $key'); |
| 113 } | 77 } |
| 114 | 78 |
| 115 void _removeChild(UINode node) { | 79 void _removeChild(UINode node) { |
| 116 _traceSync(_SyncOperation.REMOVAL, node._key); | 80 _traceSync(_SyncOperation.REMOVAL, node._key); |
| 117 node._remove(); | 81 node._remove(); |
| 118 } | 82 } |
| 119 | 83 |
| 120 // Returns the child which should be retained as the child of this node. | 84 // Returns the child which should be retained as the child of this node. |
| 121 UINode _syncChild(UINode node, UINode oldNode, sky.ParentNode host, | 85 UINode _syncChild(UINode node, UINode oldNode, RenderCSSContainer host, |
| 122 sky.Node insertBefore) { | 86 RenderCSS insertBefore) { |
| 123 | 87 |
| 124 assert(oldNode == null || node._key == oldNode._key); | 88 assert(oldNode == null || node._key == oldNode._key); |
| 125 | 89 |
| 126 if (node == oldNode) { | 90 if (node == oldNode) { |
| 127 _traceSync(_SyncOperation.IDENTICAL, node._key); | 91 _traceSync(_SyncOperation.IDENTICAL, node._key); |
| 128 return node; // Nothing to do. Subtrees must be identical. | 92 return node; // Nothing to do. Subtrees must be identical. |
| 129 } | 93 } |
| 130 | 94 |
| 131 // TODO(rafaelw): This eagerly removes the old DOM. It may be that a | 95 // TODO(rafaelw): This eagerly removes the old DOM. It may be that a |
| 132 // new component was built that could re-use some of it. Consider | 96 // new component was built that could re-use some of it. Consider |
| 133 // syncing the new VDOM against the old one. | 97 // syncing the new VDOM against the old one. |
| 134 if (oldNode != null && node._key != oldNode._key) { | 98 if (oldNode != null && node._key != oldNode._key) { |
| 135 _removeChild(oldNode); | 99 _removeChild(oldNode); |
| 136 } | 100 } |
| 137 | 101 |
| 138 if (node._willSync(oldNode)) { | 102 if (node._willSync(oldNode)) { |
| 139 _traceSync(_SyncOperation.STATEFUL, node._key); | 103 _traceSync(_SyncOperation.STATEFUL, node._key); |
| 140 oldNode._sync(node, host, insertBefore); | 104 oldNode._sync(node, host, insertBefore); |
| 141 node._defunct = true; | 105 node._defunct = true; |
| 142 assert(oldNode._root is sky.Node); | 106 assert(oldNode._root is RenderCSS); |
| 143 return oldNode; | 107 return oldNode; |
| 144 } | 108 } |
| 145 | 109 |
| 146 node._parent = this; | 110 node._parent = this; |
| 147 | 111 |
| 148 if (oldNode == null) { | 112 if (oldNode == null) { |
| 149 _traceSync(_SyncOperation.INSERTION, node._key); | 113 _traceSync(_SyncOperation.INSERTION, node._key); |
| 150 } else { | 114 } else { |
| 151 _traceSync(_SyncOperation.STATELESS, node._key); | 115 _traceSync(_SyncOperation.STATELESS, node._key); |
| 152 } | 116 } |
| 153 node._sync(oldNode, host, insertBefore); | 117 node._sync(oldNode, host, insertBefore); |
| 154 if (oldNode != null) | 118 if (oldNode != null) |
| 155 oldNode._defunct = true; | 119 oldNode._defunct = true; |
| 156 | 120 |
| 157 assert(node._root is sky.Node); | 121 assert(node._root is RenderCSS); |
| 158 return node; | 122 return node; |
| 159 } | 123 } |
| 160 } | 124 } |
| 161 | 125 |
| 162 abstract class ContentNode extends UINode { | 126 abstract class ContentNode extends UINode { |
| 163 UINode content; | 127 UINode content; |
| 164 | 128 |
| 165 ContentNode(UINode content) : this.content = content, super(key: content._key)
; | 129 ContentNode(UINode content) : this.content = content, super(key: content._key)
; |
| 166 | 130 |
| 167 void _sync(UINode old, sky.ParentNode host, sky.Node insertBefore) { | 131 void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore) { |
| 168 UINode oldContent = old == null ? null : (old as ContentNode).content; | 132 UINode oldContent = old == null ? null : (old as ContentNode).content; |
| 169 content = _syncChild(content, oldContent, host, insertBefore); | 133 content = _syncChild(content, oldContent, host, insertBefore); |
| 134 assert(content._root != null); |
| 170 _root = content._root; | 135 _root = content._root; |
| 171 } | 136 } |
| 172 | 137 |
| 173 void _remove() { | 138 void _remove() { |
| 174 _removeChild(content); | 139 if (content != null) |
| 140 _removeChild(content); |
| 175 super._remove(); | 141 super._remove(); |
| 176 } | 142 } |
| 177 } | 143 } |
| 178 | 144 |
| 179 class StyleNode extends ContentNode { | 145 class StyleNode extends ContentNode { |
| 180 final Style style; | 146 final Style style; |
| 181 | 147 |
| 182 StyleNode(UINode content, this.style): super(content); | 148 StyleNode(UINode content, this.style): super(content); |
| 183 } | 149 } |
| 184 | 150 |
| 185 /* | 151 /* |
| 186 * SkyNodeWrappers correspond to a desired state of a sky.Node. They are fully | 152 * SkyNodeWrappers correspond to a desired state of a RenderCSS. They are fully |
| 187 * immutable, with one exception: A UINode which is a Component which lives with
in | 153 * immutable, with one exception: A UINode which is a Component which lives with
in |
| 188 * an SkyElementWrapper's children list, may be replaced with the "old" instance
if it | 154 * an SkyElementWrapper's children list, may be replaced with the "old" instance
if it |
| 189 * has become stateful. | 155 * has become stateful. |
| 190 */ | 156 */ |
| 191 abstract class SkyNodeWrapper extends UINode { | 157 abstract class SkyNodeWrapper extends UINode { |
| 192 | 158 |
| 193 static final Map<sky.Node, SkyNodeWrapper> _nodeMap = | 159 static final Map<RenderCSS, SkyNodeWrapper> _nodeMap = |
| 194 new HashMap<sky.Node, SkyNodeWrapper>(); | 160 new HashMap<RenderCSS, SkyNodeWrapper>(); |
| 195 | 161 |
| 196 static SkyNodeWrapper _getMounted(sky.Node node) => _nodeMap[node]; | 162 static SkyNodeWrapper _getMounted(RenderCSS node) => _nodeMap[node]; |
| 197 | 163 |
| 198 SkyNodeWrapper({ Object key }) : super(key: key); | 164 SkyNodeWrapper({ Object key }) : super(key: key); |
| 199 | 165 |
| 200 SkyNodeWrapper get _emptyNode; | 166 SkyNodeWrapper get _emptyNode; |
| 201 | 167 |
| 202 sky.Node _createNode(); | 168 RenderCSS _createNode(); |
| 203 | 169 |
| 204 void _sync(UINode old, sky.ParentNode host, sky.Node insertBefore) { | 170 void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore) { |
| 205 if (old == null) { | 171 if (old == null) { |
| 206 _root = _createNode(); | 172 _root = _createNode(); |
| 207 _parentInsertBefore(host, _root, insertBefore); | 173 assert(_root != null); |
| 174 host.add(_root, before: insertBefore); |
| 208 old = _emptyNode; | 175 old = _emptyNode; |
| 209 } else { | 176 } else { |
| 210 _root = old._root; | 177 _root = old._root; |
| 178 assert(_root != null); |
| 211 } | 179 } |
| 212 | 180 |
| 213 _nodeMap[_root] = this; | 181 _nodeMap[_root] = this; |
| 214 _syncNode(old); | 182 _syncNode(old); |
| 215 } | 183 } |
| 216 | 184 |
| 217 void _syncNode(SkyNodeWrapper old); | 185 void _syncNode(SkyNodeWrapper old); |
| 218 | 186 |
| 187 void _removeChild(UINode node) { |
| 188 assert(_root is RenderCSSContainer); |
| 189 _root.remove(node._root); |
| 190 super._removeChild(node); |
| 191 } |
| 192 |
| 219 void _remove() { | 193 void _remove() { |
| 220 assert(_root != null); | 194 assert(_root != null); |
| 221 _root.remove(); | |
| 222 _nodeMap.remove(_root); | 195 _nodeMap.remove(_root); |
| 223 super._remove(); | 196 super._remove(); |
| 224 } | 197 } |
| 225 } | 198 } |
| 226 | 199 |
| 227 typedef GestureEventListener(sky.GestureEvent e); | 200 typedef GestureEventListener(sky.GestureEvent e); |
| 228 typedef PointerEventListener(sky.PointerEvent e); | 201 typedef PointerEventListener(sky.PointerEvent e); |
| 229 typedef EventListener(sky.Event e); | 202 typedef EventListener(sky.Event e); |
| 230 | 203 |
| 231 class EventListenerNode extends ContentNode { | 204 class EventListenerNode extends ContentNode { |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 307 super(content); | 280 super(content); |
| 308 | 281 |
| 309 void _handleEvent(sky.Event e) { | 282 void _handleEvent(sky.Event e) { |
| 310 sky.EventListener listener = listeners[e.type]; | 283 sky.EventListener listener = listeners[e.type]; |
| 311 if (listener != null) { | 284 if (listener != null) { |
| 312 listener(e); | 285 listener(e); |
| 313 } | 286 } |
| 314 } | 287 } |
| 315 | 288 |
| 316 static void _dispatchEvent(sky.Event e) { | 289 static void _dispatchEvent(sky.Event e) { |
| 317 UINode target = SkyNodeWrapper._getMounted(e.target); | 290 UINode target = SkyNodeWrapper._getMounted(bridgeEventTargetToRenderNode(e.t
arget)); |
| 318 | 291 |
| 319 // TODO(rafaelw): StopPropagation? | 292 // TODO(rafaelw): StopPropagation? |
| 320 while (target != null) { | 293 while (target != null) { |
| 321 if (target is EventListenerNode) { | 294 if (target is EventListenerNode) { |
| 322 (target as EventListenerNode)._handleEvent(e); | 295 target._handleEvent(e); |
| 323 } | 296 } |
| 324 | 297 |
| 325 target = target._parent; | 298 target = target._parent; |
| 326 } | 299 } |
| 327 } | 300 } |
| 328 | 301 |
| 329 static void _ensureDocumentListener(String eventType) { | 302 static void _ensureDocumentListener(String eventType) { |
| 330 if (_registeredEvents.add(eventType)) { | 303 if (_registeredEvents.add(eventType)) { |
| 331 sky.document.addEventListener(eventType, _dispatchEvent); | 304 sky.document.addEventListener(eventType, _dispatchEvent); |
| 332 } | 305 } |
| 333 } | 306 } |
| 334 | 307 |
| 335 void _sync(UINode old, sky.ParentNode host, sky.Node insertBefore) { | 308 void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore) { |
| 336 for (var type in listeners.keys) { | 309 for (var type in listeners.keys) { |
| 337 _ensureDocumentListener(type); | 310 _ensureDocumentListener(type); |
| 338 } | 311 } |
| 339 | 312 |
| 340 super._sync(old, host, insertBefore); | 313 super._sync(old, host, insertBefore); |
| 341 } | 314 } |
| 342 } | 315 } |
| 343 | 316 |
| 344 class Text extends SkyNodeWrapper { | 317 class Text extends SkyNodeWrapper { |
| 345 final String data; | 318 final String data; |
| 346 | 319 |
| 347 // Text nodes are special cases of having non-unique keys (which don't need | 320 // Text nodes are special cases of having non-unique keys (which don't need |
| 348 // to be assigned as part of the API). Since they are unique in not having | 321 // to be assigned as part of the API). Since they are unique in not having |
| 349 // children, there's little point to reordering, so we always just re-assign | 322 // children, there's little point to reordering, so we always just re-assign |
| 350 // the data. | 323 // the data. |
| 351 Text(this.data) : super(key:'*text*'); | 324 Text(this.data) : super(key:'*text*'); |
| 352 | 325 |
| 353 static final Text _emptyText = new Text(null); | 326 static final Text _emptyText = new Text(null); |
| 354 | 327 |
| 355 SkyNodeWrapper get _emptyNode => _emptyText; | 328 SkyNodeWrapper get _emptyNode => _emptyText; |
| 356 | 329 |
| 357 static final Style _displayParagraph = new Style('display:paragraph'); | 330 RenderCSSText _root; |
| 358 | 331 RenderCSS _createNode() { |
| 359 sky.Node _createNode() { | 332 return new RenderCSSText(this, this.data); |
| 360 return sky.document.createElement('div') | |
| 361 ..setChild(new sky.Text(this.data)) | |
| 362 ..setAttribute('class', _displayParagraph._className)
; | |
| 363 } | 333 } |
| 364 | 334 |
| 365 void _syncNode(SkyNodeWrapper old) { | 335 void _syncNode(SkyNodeWrapper old) { |
| 366 if (old == _emptyText) | 336 if (old == _emptyText) |
| 367 return; // we set inside _createNode(); | 337 return; // we set inside _createNode(); |
| 368 | 338 _root.data = data; |
| 369 (_root.firstChild as sky.Text).data = data; | |
| 370 } | 339 } |
| 371 } | 340 } |
| 372 | 341 |
| 373 final List<UINode> _emptyList = new List<UINode>(); | 342 final List<UINode> _emptyList = new List<UINode>(); |
| 374 | 343 |
| 375 abstract class SkyElementWrapper extends SkyNodeWrapper { | 344 abstract class SkyElementWrapper extends SkyNodeWrapper { |
| 376 | 345 |
| 377 String get _tagName; | |
| 378 | |
| 379 sky.Node _createNode() => sky.document.createElement(_tagName); | |
| 380 | |
| 381 final List<UINode> children; | 346 final List<UINode> children; |
| 382 final Style style; | 347 final Style style; |
| 383 final String inlineStyle; | 348 final String inlineStyle; |
| 384 | 349 |
| 385 String _class; | |
| 386 | |
| 387 SkyElementWrapper({ | 350 SkyElementWrapper({ |
| 388 Object key, | 351 Object key, |
| 389 List<UINode> children, | 352 List<UINode> children, |
| 390 this.style, | 353 this.style, |
| 391 this.inlineStyle | 354 this.inlineStyle |
| 392 }) : this.children = children == null ? _emptyList : children, | 355 }) : this.children = children == null ? _emptyList : children, |
| 393 super(key:key) { | 356 super(key:key) { |
| 394 | 357 |
| 395 assert(!_debugHasDuplicateIds()); | 358 assert(!_debugHasDuplicateIds()); |
| 396 } | 359 } |
| 397 | 360 |
| 398 void _remove() { | 361 void _remove() { |
| 362 assert(children != null); |
| 363 for (var child in children) { |
| 364 assert(child != null); |
| 365 _removeChild(child); |
| 366 } |
| 399 super._remove(); | 367 super._remove(); |
| 400 if (children != null) { | |
| 401 for (var child in children) { | |
| 402 _removeChild(child); | |
| 403 } | |
| 404 } | |
| 405 } | 368 } |
| 406 | 369 |
| 407 bool _debugHasDuplicateIds() { | 370 bool _debugHasDuplicateIds() { |
| 408 var idSet = new HashSet<String>(); | 371 var idSet = new HashSet<String>(); |
| 409 for (var child in children) { | 372 for (var child in children) { |
| 373 assert(child != null); |
| 410 if (child is Text) { | 374 if (child is Text) { |
| 411 continue; // Text nodes all have the same key and are never reordered. | 375 continue; // Text nodes all have the same key and are never reordered. |
| 412 } | 376 } |
| 413 | 377 |
| 414 if (!idSet.add(child._key)) { | 378 if (!idSet.add(child._key)) { |
| 415 throw '''If multiple (non-Text) nodes of the same type exist as children | 379 throw '''If multiple (non-Text) nodes of the same type exist as children |
| 416 of another node, they must have unique keys.'''; | 380 of another node, they must have unique keys.'''; |
| 417 } | 381 } |
| 418 } | 382 } |
| 419 return false; | 383 return false; |
| 420 } | 384 } |
| 421 | 385 |
| 422 void _ensureClass() { | |
| 423 if (_class == null) { | |
| 424 List<Style> styles = new List<Style>(); | |
| 425 if (style != null) { | |
| 426 styles.add(style); | |
| 427 } | |
| 428 | |
| 429 UINode parent = _parent; | |
| 430 while (parent != null && parent is! SkyNodeWrapper) { | |
| 431 if (parent is StyleNode && (parent as StyleNode).style != null) | |
| 432 styles.add((parent as StyleNode).style); | |
| 433 | |
| 434 parent = parent._parent; | |
| 435 } | |
| 436 | |
| 437 _class = styles.map((s) => s._className).join(' '); | |
| 438 } | |
| 439 } | |
| 440 | |
| 441 void _syncNode(SkyNodeWrapper old) { | 386 void _syncNode(SkyNodeWrapper old) { |
| 442 SkyElementWrapper oldSkyElementWrapper = old as SkyElementWrapper; | 387 SkyElementWrapper oldSkyElementWrapper = old as SkyElementWrapper; |
| 443 sky.Element root = _root as sky.Element; | |
| 444 | 388 |
| 445 _ensureClass(); | 389 List<Style> styles = new List<Style>(); |
| 446 if (_class != oldSkyElementWrapper._class && _class != '') | 390 if (style != null) |
| 447 root.setAttribute('class', _class); | 391 styles.add(style); |
| 392 UINode parent = _parent; |
| 393 while (parent != null && parent is! SkyNodeWrapper) { |
| 394 if (parent is StyleNode && parent.style != null) |
| 395 styles.add(parent.style); |
| 396 parent = parent._parent; |
| 397 } |
| 398 _root.updateStyles(styles); |
| 448 | 399 |
| 449 if (inlineStyle != oldSkyElementWrapper.inlineStyle) | 400 _root.updateInlineStyle(inlineStyle); |
| 450 root.setAttribute('style', inlineStyle); | |
| 451 | 401 |
| 452 _syncChildren(oldSkyElementWrapper); | 402 _syncChildren(oldSkyElementWrapper); |
| 453 } | 403 } |
| 454 | 404 |
| 455 void _syncChildren(SkyElementWrapper oldSkyElementWrapper) { | 405 void _syncChildren(SkyElementWrapper oldSkyElementWrapper) { |
| 456 sky.Element root = _root as sky.Element; | 406 if (_root is! RenderCSSContainer) |
| 457 assert(root != null); | 407 return; |
| 458 | 408 |
| 459 var startIndex = 0; | 409 var startIndex = 0; |
| 460 var endIndex = children.length; | 410 var endIndex = children.length; |
| 461 | 411 |
| 462 var oldChildren = oldSkyElementWrapper.children; | 412 var oldChildren = oldSkyElementWrapper.children; |
| 463 var oldStartIndex = 0; | 413 var oldStartIndex = 0; |
| 464 var oldEndIndex = oldChildren.length; | 414 var oldEndIndex = oldChildren.length; |
| 465 | 415 |
| 466 sky.Node nextSibling = null; | 416 RenderCSS nextSibling = null; |
| 467 UINode currentNode = null; | 417 UINode currentNode = null; |
| 468 UINode oldNode = null; | 418 UINode oldNode = null; |
| 469 | 419 |
| 470 void sync(int atIndex) { | 420 void sync(int atIndex) { |
| 471 children[atIndex] = _syncChild(currentNode, oldNode, _root, nextSibling); | 421 children[atIndex] = _syncChild(currentNode, oldNode, _root, nextSibling); |
| 422 assert(children[atIndex] != null); |
| 472 } | 423 } |
| 473 | 424 |
| 474 // Scan backwards from end of list while nodes can be directly synced | 425 // Scan backwards from end of list while nodes can be directly synced |
| 475 // without reordering. | 426 // without reordering. |
| 476 while (endIndex > startIndex && oldEndIndex > oldStartIndex) { | 427 while (endIndex > startIndex && oldEndIndex > oldStartIndex) { |
| 477 currentNode = children[endIndex - 1]; | 428 currentNode = children[endIndex - 1]; |
| 478 oldNode = oldChildren[oldEndIndex - 1]; | 429 oldNode = oldChildren[oldEndIndex - 1]; |
| 479 | 430 |
| 480 if (currentNode._key != oldNode._key) { | 431 if (currentNode._key != oldNode._key) { |
| 481 break; | 432 break; |
| 482 } | 433 } |
| 483 | 434 |
| 484 endIndex--; | 435 endIndex--; |
| 485 oldEndIndex--; | 436 oldEndIndex--; |
| 486 sync(endIndex); | 437 sync(endIndex); |
| 487 nextSibling = currentNode._root; | |
| 488 } | 438 } |
| 489 | 439 |
| 490 HashMap<String, UINode> oldNodeIdMap = null; | 440 HashMap<String, UINode> oldNodeIdMap = null; |
| 491 | 441 |
| 492 bool oldNodeReordered(String key) { | 442 bool oldNodeReordered(String key) { |
| 493 return oldNodeIdMap != null && | 443 return oldNodeIdMap != null && |
| 494 oldNodeIdMap.containsKey(key) && | 444 oldNodeIdMap.containsKey(key) && |
| 495 oldNodeIdMap[key] == null; | 445 oldNodeIdMap[key] == null; |
| 496 } | 446 } |
| 497 | 447 |
| (...skipping 21 matching lines...) Expand all Loading... |
| 519 bool searchForOldNode() { | 469 bool searchForOldNode() { |
| 520 if (currentNode is Text) | 470 if (currentNode is Text) |
| 521 return false; // Never re-order Text nodes. | 471 return false; // Never re-order Text nodes. |
| 522 | 472 |
| 523 ensureOldIdMap(); | 473 ensureOldIdMap(); |
| 524 oldNode = oldNodeIdMap[currentNode._key]; | 474 oldNode = oldNodeIdMap[currentNode._key]; |
| 525 if (oldNode == null) | 475 if (oldNode == null) |
| 526 return false; | 476 return false; |
| 527 | 477 |
| 528 oldNodeIdMap[currentNode._key] = null; // mark it reordered. | 478 oldNodeIdMap[currentNode._key] = null; // mark it reordered. |
| 529 _parentInsertBefore(root, oldNode._root, nextSibling); | 479 assert(_root is RenderCSSContainer); |
| 480 assert(oldNode._root is RenderCSSContainer); |
| 481 oldSkyElementWrapper._root.remove(oldNode._root); |
| 482 _root.add(oldNode._root, before: nextSibling); |
| 530 return true; | 483 return true; |
| 531 } | 484 } |
| 532 | 485 |
| 533 // Scan forwards, this time we may re-order; | 486 // Scan forwards, this time we may re-order; |
| 534 nextSibling = root.firstChild; | 487 nextSibling = _root.firstChild; |
| 535 while (startIndex < endIndex && oldStartIndex < oldEndIndex) { | 488 while (startIndex < endIndex && oldStartIndex < oldEndIndex) { |
| 536 currentNode = children[startIndex]; | 489 currentNode = children[startIndex]; |
| 537 oldNode = oldChildren[oldStartIndex]; | 490 oldNode = oldChildren[oldStartIndex]; |
| 538 | 491 |
| 539 if (currentNode._key == oldNode._key) { | 492 if (currentNode._key == oldNode._key) { |
| 540 assert(currentNode.runtimeType == oldNode.runtimeType); | 493 assert(currentNode.runtimeType == oldNode.runtimeType); |
| 541 nextSibling = nextSibling.nextSibling; | 494 nextSibling = _root.childAfter(nextSibling); |
| 542 sync(startIndex); | 495 sync(startIndex); |
| 543 startIndex++; | 496 startIndex++; |
| 544 advanceOldStartIndex(); | 497 advanceOldStartIndex(); |
| 545 continue; | 498 continue; |
| 546 } | 499 } |
| 547 | 500 |
| 548 oldNode = null; | 501 oldNode = null; |
| 549 searchForOldNode(); | 502 searchForOldNode(); |
| 550 sync(startIndex); | 503 sync(startIndex); |
| 551 startIndex++; | 504 startIndex++; |
| (...skipping 12 matching lines...) Expand all Loading... |
| 564 while (oldStartIndex < oldEndIndex) { | 517 while (oldStartIndex < oldEndIndex) { |
| 565 oldNode = oldChildren[oldStartIndex]; | 518 oldNode = oldChildren[oldStartIndex]; |
| 566 _removeChild(oldNode); | 519 _removeChild(oldNode); |
| 567 advanceOldStartIndex(); | 520 advanceOldStartIndex(); |
| 568 } | 521 } |
| 569 } | 522 } |
| 570 } | 523 } |
| 571 | 524 |
| 572 class Container extends SkyElementWrapper { | 525 class Container extends SkyElementWrapper { |
| 573 | 526 |
| 574 String get _tagName => 'div'; | 527 RenderCSS _createNode() => new RenderCSSContainer(this); |
| 575 | 528 |
| 576 static final Container _emptyContainer = new Container(); | 529 static final Container _emptyContainer = new Container(); |
| 577 | 530 |
| 578 SkyNodeWrapper get _emptyNode => _emptyContainer; | 531 SkyNodeWrapper get _emptyNode => _emptyContainer; |
| 579 | 532 |
| 580 Container({ | 533 Container({ |
| 581 Object key, | 534 Object key, |
| 582 List<UINode> children, | 535 List<UINode> children, |
| 583 Style style, | 536 Style style, |
| 584 String inlineStyle | 537 String inlineStyle |
| 585 }) : super( | 538 }) : super( |
| 586 key: key, | 539 key: key, |
| 587 children: children, | 540 children: children, |
| 588 style: style, | 541 style: style, |
| 589 inlineStyle: inlineStyle | 542 inlineStyle: inlineStyle |
| 590 ); | 543 ); |
| 591 } | 544 } |
| 592 | 545 |
| 593 abstract class LayoutContainer extends Container { | |
| 594 | |
| 595 LayoutContainer({ | |
| 596 Object key, | |
| 597 List<UINode> children, | |
| 598 Style style, | |
| 599 String inlineStyle | |
| 600 }) : super( | |
| 601 key: key, | |
| 602 children: children, | |
| 603 style: style, | |
| 604 inlineStyle: inlineStyle | |
| 605 ); | |
| 606 | |
| 607 sky.Node _createNode() { | |
| 608 var result = super._createNode(); | |
| 609 result.setLayoutManager(() => layout(_root)); | |
| 610 return result; | |
| 611 } | |
| 612 | |
| 613 // If we ever reuse sky nodes for different classes, then we should | |
| 614 // call _root.setLayoutManager(null) during _remove() here. | |
| 615 | |
| 616 void _syncNode(SkyNodeWrapper old) { | |
| 617 super._syncNode(old); | |
| 618 _root.setLayoutManager(() => layout(_root)); | |
| 619 _root.setNeedsLayout(); | |
| 620 } | |
| 621 | |
| 622 void layout(sky.Element skyNode); | |
| 623 // set skyNode.width (e.g., set it to skyNode.parentNode.width) | |
| 624 // for each skyNode.getChildNodes()[i]: | |
| 625 // call .layout() | |
| 626 // set .x, .y | |
| 627 // set .width if you want to force a width | |
| 628 // set .height if you want to force a height | |
| 629 // set skyNode.height | |
| 630 | |
| 631 } | |
| 632 | |
| 633 class Image extends SkyElementWrapper { | 546 class Image extends SkyElementWrapper { |
| 634 | 547 |
| 635 String get _tagName => 'img'; | 548 RenderCSSImage _root; |
| 549 RenderCSSImage _createNode() => new RenderCSSImage(this, this.src, this.width,
this.height); |
| 636 | 550 |
| 637 static final Image _emptyImage = new Image(); | 551 static final Image _emptyImage = new Image(); |
| 638 | 552 |
| 639 SkyNodeWrapper get _emptyNode => _emptyImage; | 553 SkyNodeWrapper get _emptyNode => _emptyImage; |
| 640 | 554 |
| 641 final String src; | 555 final String src; |
| 642 final int width; | 556 final int width; |
| 643 final int height; | 557 final int height; |
| 644 | 558 |
| 645 Image({ | 559 Image({ |
| 646 Object key, | 560 Object key, |
| 647 List<UINode> children, | 561 List<UINode> children, |
| 648 Style style, | 562 Style style, |
| 649 String inlineStyle, | 563 String inlineStyle, |
| 650 this.width, | 564 this.width, |
| 651 this.height, | 565 this.height, |
| 652 this.src | 566 this.src |
| 653 }) : super( | 567 }) : super( |
| 654 key: key, | 568 key: key, |
| 655 children: children, | 569 children: children, |
| 656 style: style, | 570 style: style, |
| 657 inlineStyle: inlineStyle | 571 inlineStyle: inlineStyle |
| 658 ); | 572 ); |
| 659 | 573 |
| 660 void _syncNode(UINode old) { | 574 void _syncNode(UINode old) { |
| 661 super._syncNode(old); | 575 super._syncNode(old); |
| 662 | 576 _root.configure(this.src, this.width, this.height); |
| 663 Image oldImage = old as Image; | |
| 664 sky.HTMLImageElement skyImage = _root as sky.HTMLImageElement; | |
| 665 | |
| 666 if (src != oldImage.src) | |
| 667 skyImage.src = src; | |
| 668 | |
| 669 if (width != oldImage.width) | |
| 670 skyImage.style['width'] = '${width}px'; | |
| 671 | |
| 672 if (height != oldImage.height) | |
| 673 skyImage.style['height'] = '${height}px'; | |
| 674 } | 577 } |
| 675 } | 578 } |
| 676 | 579 |
| 677 class Anchor extends SkyElementWrapper { | |
| 678 | |
| 679 String get _tagName => 'a'; | |
| 680 | |
| 681 static final Anchor _emptyAnchor = new Anchor(); | |
| 682 | |
| 683 UINode get _emptyNode => _emptyAnchor; | |
| 684 | |
| 685 final String href; | |
| 686 final int width; | |
| 687 final int height; | |
| 688 | |
| 689 Anchor({ | |
| 690 Object key, | |
| 691 List<UINode> children, | |
| 692 Style style, | |
| 693 String inlineStyle, | |
| 694 this.width, | |
| 695 this.height, | |
| 696 this.href | |
| 697 }) : super( | |
| 698 key: key, | |
| 699 children: children, | |
| 700 style: style, | |
| 701 inlineStyle: inlineStyle | |
| 702 ); | |
| 703 | |
| 704 void _syncNode(UINode old) { | |
| 705 super._syncNode(old); | |
| 706 | |
| 707 Anchor oldAnchor = old as Anchor; | |
| 708 sky.HTMLAnchorElement skyAnchor = _root as sky.HTMLAnchorElement; | |
| 709 | |
| 710 if (href != oldAnchor.href) | |
| 711 skyAnchor.href = href; | |
| 712 } | |
| 713 } | |
| 714 | |
| 715 | 580 |
| 716 Set<Component> _mountedComponents = new HashSet<Component>(); | 581 Set<Component> _mountedComponents = new HashSet<Component>(); |
| 717 Set<Component> _unmountedComponents = new HashSet<Component>(); | 582 Set<Component> _unmountedComponents = new HashSet<Component>(); |
| 718 | 583 |
| 719 void _enqueueDidMount(Component c) { | 584 void _enqueueDidMount(Component c) { |
| 720 assert(!_notifingMountStatus); | 585 assert(!_notifingMountStatus); |
| 721 _mountedComponents.add(c); | 586 _mountedComponents.add(c); |
| 722 } | 587 } |
| 723 | 588 |
| 724 void _enqueueDidUnmount(Component c) { | 589 void _enqueueDidUnmount(Component c) { |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 782 if (!_buildScheduled) { | 647 if (!_buildScheduled) { |
| 783 _buildScheduled = true; | 648 _buildScheduled = true; |
| 784 new Future.microtask(_buildDirtyComponents); | 649 new Future.microtask(_buildDirtyComponents); |
| 785 } | 650 } |
| 786 } | 651 } |
| 787 | 652 |
| 788 abstract class Component extends UINode { | 653 abstract class Component extends UINode { |
| 789 bool get _isBuilding => _currentlyBuilding == this; | 654 bool get _isBuilding => _currentlyBuilding == this; |
| 790 bool _dirty = true; | 655 bool _dirty = true; |
| 791 | 656 |
| 792 sky.Node get _host => _root.parentNode; | |
| 793 sky.Node get _insertionPoint => _root == null ? _root : _root.nextSibling; | |
| 794 | |
| 795 UINode _built; | 657 UINode _built; |
| 796 final int _order; | 658 final int _order; |
| 797 static int _currentOrder = 0; | 659 static int _currentOrder = 0; |
| 798 bool _stateful; | 660 bool _stateful; |
| 799 static Component _currentlyBuilding; | 661 static Component _currentlyBuilding; |
| 800 List<Function> _mountCallbacks; | 662 List<Function> _mountCallbacks; |
| 801 List<Function> _unmountCallbacks; | 663 List<Function> _unmountCallbacks; |
| 802 | 664 |
| 803 void onDidMount(Function fn) { | 665 void onDidMount(Function fn) { |
| 804 if (_mountCallbacks == null) | 666 if (_mountCallbacks == null) |
| (...skipping 23 matching lines...) Expand all Loading... |
| 828 _mountCallbacks.forEach((fn) => fn()); | 690 _mountCallbacks.forEach((fn) => fn()); |
| 829 } | 691 } |
| 830 | 692 |
| 831 void _didUnmount() { | 693 void _didUnmount() { |
| 832 if (_unmountCallbacks != null) | 694 if (_unmountCallbacks != null) |
| 833 _unmountCallbacks.forEach((fn) => fn()); | 695 _unmountCallbacks.forEach((fn) => fn()); |
| 834 } | 696 } |
| 835 | 697 |
| 836 // TODO(rafaelw): It seems wrong to expose DOM at all. This is presently | 698 // TODO(rafaelw): It seems wrong to expose DOM at all. This is presently |
| 837 // needed to get sizing info. | 699 // needed to get sizing info. |
| 838 sky.Node getRoot() => _root; | 700 RenderCSS getRoot() => _root; |
| 839 | 701 |
| 840 void _remove() { | 702 void _remove() { |
| 841 assert(_built != null); | 703 assert(_built != null); |
| 842 assert(_root != null); | 704 assert(_root != null); |
| 843 _removeChild(_built); | 705 _removeChild(_built); |
| 844 _built = null; | 706 _built = null; |
| 845 _enqueueDidUnmount(this); | 707 _enqueueDidUnmount(this); |
| 846 super._remove(); | 708 super._remove(); |
| 847 } | 709 } |
| 848 | 710 |
| (...skipping 15 matching lines...) Expand all Loading... |
| 864 } | 726 } |
| 865 | 727 |
| 866 /* There are three cases here: | 728 /* There are three cases here: |
| 867 * 1) Building for the first time: | 729 * 1) Building for the first time: |
| 868 * assert(_built == null && old == null) | 730 * assert(_built == null && old == null) |
| 869 * 2) Re-building (because a dirty flag got set): | 731 * 2) Re-building (because a dirty flag got set): |
| 870 * assert(_built != null && old == null) | 732 * assert(_built != null && old == null) |
| 871 * 3) Syncing against an old version | 733 * 3) Syncing against an old version |
| 872 * assert(_built == null && old != null) | 734 * assert(_built == null && old != null) |
| 873 */ | 735 */ |
| 874 void _sync(UINode old, sky.ParentNode host, sky.Node insertBefore) { | 736 void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore) { |
| 875 assert(!_defunct); | 737 assert(!_defunct); |
| 876 assert(_built == null || old == null); | 738 assert(_built == null || old == null); |
| 877 | 739 |
| 878 Component oldComponent = old as Component; | 740 Component oldComponent = old as Component; |
| 879 | 741 |
| 880 var oldBuilt; | 742 var oldBuilt; |
| 881 if (oldComponent == null) { | 743 if (oldComponent == null) { |
| 882 oldBuilt = _built; | 744 oldBuilt = _built; |
| 883 } else { | 745 } else { |
| 884 assert(_built == null); | 746 assert(_built == null); |
| 885 oldBuilt = oldComponent._built; | 747 oldBuilt = oldComponent._built; |
| 886 } | 748 } |
| 887 | 749 |
| 888 if (oldBuilt == null) | 750 if (oldBuilt == null) |
| 889 _enqueueDidMount(this); | 751 _enqueueDidMount(this); |
| 890 | 752 |
| 891 int lastOrder = _currentOrder; | 753 int lastOrder = _currentOrder; |
| 892 _currentOrder = _order; | 754 _currentOrder = _order; |
| 893 _currentlyBuilding = this; | 755 _currentlyBuilding = this; |
| 894 _built = build(); | 756 _built = build(); |
| 895 _currentlyBuilding = null; | 757 _currentlyBuilding = null; |
| 896 _currentOrder = lastOrder; | 758 _currentOrder = lastOrder; |
| 897 | 759 |
| 898 _built = _syncChild(_built, oldBuilt, host, insertBefore); | 760 _built = _syncChild(_built, oldBuilt, host, insertBefore); |
| 899 _dirty = false; | 761 _dirty = false; |
| 900 _root = _built._root; | 762 _root = _built._root; |
| 763 assert(_root != null); |
| 901 } | 764 } |
| 902 | 765 |
| 903 void _buildIfDirty() { | 766 void _buildIfDirty() { |
| 904 if (!_dirty || _defunct) | 767 if (!_dirty || _defunct) |
| 905 return; | 768 return; |
| 906 | 769 |
| 907 assert(_host != null); | |
| 908 _trace('$_key rebuilding...'); | 770 _trace('$_key rebuilding...'); |
| 909 _sync(null, _host, _insertionPoint); | 771 _sync(null, null, null); // TODO(ianh): figure out how passing "null, null,
null" here is ok |
| 910 } | 772 } |
| 911 | 773 |
| 912 void scheduleBuild() { | 774 void scheduleBuild() { |
| 913 setState(() {}); | 775 setState(() {}); |
| 914 } | 776 } |
| 915 | 777 |
| 916 void setState(Function fn()) { | 778 void setState(Function fn()) { |
| 917 _stateful = true; | 779 _stateful = true; |
| 918 fn(); | 780 fn(); |
| 919 if (_isBuilding || _dirty || _defunct) | 781 if (_isBuilding || _dirty || _defunct) |
| 920 return; | 782 return; |
| 921 | 783 |
| 922 _dirty = true; | 784 _dirty = true; |
| 923 _scheduleComponentForRender(this); | 785 _scheduleComponentForRender(this); |
| 924 } | 786 } |
| 925 | 787 |
| 926 UINode build(); | 788 UINode build(); |
| 927 } | 789 } |
| 928 | 790 |
| 929 abstract class App extends Component { | 791 abstract class App extends Component { |
| 930 sky.Node _host; | 792 RenderCSS _host; |
| 931 | 793 |
| 932 App() : super(stateful: true) { | 794 App() : super(stateful: true) { |
| 933 _host = sky.document.createElement('div'); | 795 _host = new RenderCSSRoot(this); |
| 934 sky.document.appendChild(_host); | |
| 935 _scheduleComponentForRender(this); | 796 _scheduleComponentForRender(this); |
| 936 } | 797 } |
| 798 |
| 799 void _buildIfDirty() { |
| 800 if (!_dirty || _defunct) |
| 801 return; |
| 802 |
| 803 _trace('$_key rebuilding...'); |
| 804 _sync(null, _host, _root); |
| 805 } |
| 937 } | 806 } |
| OLD | NEW |