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 | 11 |
12 bool _initIsInCheckedMode() { | 12 bool _initIsInCheckedMode() { |
13 String testFn(i) { double d = i; return d.toString(); } | 13 String testFn(i) { double d = i; return d.toString(); } |
14 try { | 14 try { |
15 testFn('not a double'); | 15 testFn('not a double'); |
16 } catch (ex) { | 16 } catch (ex) { |
17 return true; | 17 return true; |
18 } | 18 } |
19 return false; | 19 return false; |
20 } | 20 } |
21 | 21 |
22 final bool _isInCheckedMode = _initIsInCheckedMode(); | 22 final bool _isInCheckedMode = _initIsInCheckedMode(); |
23 final bool _shouldLogRenderDuration = false; | 23 final bool _shouldLogRenderDuration = false; |
24 | 24 |
25 class EventHandler { | |
26 final String type; | |
27 final sky.EventListener listener; | |
28 | |
29 EventHandler(this.type, this.listener); | |
30 } | |
31 | |
32 class EventMap { | |
33 final List<EventHandler> _handlers = new List<EventHandler>(); | |
34 | |
35 void listen(String type, sky.EventListener listener) { | |
36 assert(listener != null); | |
37 _handlers.add(new EventHandler(type, listener)); | |
38 } | |
39 | |
40 void addAll(EventMap events) { | |
41 _handlers.addAll(events._handlers); | |
42 } | |
43 } | |
44 | |
45 class Style { | 25 class Style { |
46 final String _className; | 26 final String _className; |
47 static final Map<String, Style> _cache = new HashMap<String, Style>(); | 27 static final Map<String, Style> _cache = new HashMap<String, Style>(); |
48 | 28 |
49 static int _nextStyleId = 1; | 29 static int _nextStyleId = 1; |
50 | 30 |
51 static String _getNextClassName() { return "style${_nextStyleId++}"; } | 31 static String _getNextClassName() { return "style${_nextStyleId++}"; } |
52 | 32 |
53 Style extend(Style other) { | 33 Style extend(Style other) { |
54 var className = "$_className ${other._className}"; | 34 var className = "$_className ${other._className}"; |
(...skipping 29 matching lines...) Expand all Loading... | |
84 /* | 64 /* |
85 * All Effen nodes derive from Node. All nodes have a _parent, a _key and | 65 * All Effen nodes derive from Node. All nodes have a _parent, a _key and |
86 * can be sync'd. | 66 * can be sync'd. |
87 */ | 67 */ |
88 abstract class Node { | 68 abstract class Node { |
89 String _key; | 69 String _key; |
90 Node _parent; | 70 Node _parent; |
91 sky.Node _root; | 71 sky.Node _root; |
92 bool _defunct = false; | 72 bool _defunct = false; |
93 | 73 |
94 // TODO(abarth): Both Elements and Components have |events| but |Text| | |
95 // doesn't. Should we add a common base class to contain |events|? | |
96 final EventMap events = new EventMap(); | |
97 | |
98 Node({ Object key }) { | 74 Node({ Object key }) { |
99 _key = key == null ? "$runtimeType" : "$runtimeType-$key"; | 75 _key = key == null ? "$runtimeType" : "$runtimeType-$key"; |
100 } | 76 } |
101 | 77 |
102 // Subclasses which implements Nodes that become stateful may return true | 78 // Subclasses which implements Nodes that become stateful may return true |
103 // if the |old| node has become stateful and should be retained. | 79 // if the |old| node has become stateful and should be retained. |
104 bool _willSync(Node old) => false; | 80 bool _willSync(Node old) => false; |
105 | 81 |
106 void _sync(Node old, sky.ParentNode host, sky.Node insertBefore); | 82 void _sync(Node old, sky.ParentNode host, sky.Node insertBefore); |
107 | 83 |
(...skipping 23 matching lines...) Expand all Loading... | |
131 } | 107 } |
132 | 108 |
133 node._parent = this; | 109 node._parent = this; |
134 node._sync(oldNode, host, insertBefore); | 110 node._sync(oldNode, host, insertBefore); |
135 if (oldNode != null) | 111 if (oldNode != null) |
136 oldNode._defunct = true; | 112 oldNode._defunct = true; |
137 | 113 |
138 assert(node._root is sky.Node); | 114 assert(node._root is sky.Node); |
139 return node; | 115 return node; |
140 } | 116 } |
141 | |
142 void _syncEvents(EventMap oldEventMap) { | |
143 List<EventHandler> newHandlers = events._handlers; | |
144 int newStartIndex = 0; | |
145 int newEndIndex = newHandlers.length; | |
146 | |
147 List<EventHandler> oldHandlers = oldEventMap._handlers; | |
148 int oldStartIndex = 0; | |
149 int oldEndIndex = oldHandlers.length; | |
150 | |
151 // Skip over leading handlers that match. | |
152 while (newStartIndex < newEndIndex && oldStartIndex < oldEndIndex) { | |
153 EventHandler newHandler = newHandlers[newStartIndex]; | |
154 EventHandler oldHandler = oldHandlers[oldStartIndex]; | |
155 if (newHandler.type != oldHandler.type | |
156 || newHandler.listener != oldHandler.listener) | |
157 break; | |
158 ++newStartIndex; | |
159 ++oldStartIndex; | |
160 } | |
161 | |
162 // Skip over trailing handlers that match. | |
163 while (newStartIndex < newEndIndex && oldStartIndex < oldEndIndex) { | |
164 EventHandler newHandler = newHandlers[newEndIndex - 1]; | |
165 EventHandler oldHandler = oldHandlers[oldEndIndex - 1]; | |
166 if (newHandler.type != oldHandler.type | |
167 || newHandler.listener != oldHandler.listener) | |
168 break; | |
169 --newEndIndex; | |
170 --oldEndIndex; | |
171 } | |
172 | |
173 sky.Element root = _root as sky.Element; | |
174 | |
175 for (int i = oldStartIndex; i < oldEndIndex; ++i) { | |
176 EventHandler oldHandler = oldHandlers[i]; | |
177 root.removeEventListener(oldHandler.type, oldHandler.listener); | |
178 } | |
179 | |
180 for (int i = newStartIndex; i < newEndIndex; ++i) { | |
181 EventHandler newHandler = newHandlers[i]; | |
182 root.addEventListener(newHandler.type, newHandler.listener); | |
183 } | |
184 } | |
185 | |
186 } | 117 } |
187 | 118 |
188 /* | 119 /* |
189 * RenderNodes correspond to a desired state of a sky.Node. They are fully | 120 * RenderNodes correspond to a desired state of a sky.Node. They are fully |
190 * immutable, with one exception: A Node which is a Component which lives within | 121 * immutable, with one exception: A Node which is a Component which lives within |
191 * an Element's children list, may be replaced with the "old" instance if it | 122 * an Element's children list, may be replaced with the "old" instance if it |
192 * has become stateful. | 123 * has become stateful. |
193 */ | 124 */ |
194 abstract class RenderNode extends Node { | 125 abstract class RenderNode extends Node { |
195 | 126 |
127 static Map<sky.Node, RenderNode> _nodeMap = | |
abarth-chromium
2015/03/19 14:19:59
final
rafaelw
2015/03/19 15:03:50
Done.
| |
128 new HashMap<sky.Node, RenderNode>(); | |
129 | |
130 static RenderNode _getMounted(sky.Node node) => _nodeMap[node]; | |
131 | |
196 RenderNode({ Object key }) : super(key: key); | 132 RenderNode({ Object key }) : super(key: key); |
197 | 133 |
198 RenderNode get _emptyNode; | 134 RenderNode get _emptyNode; |
199 | 135 |
200 sky.Node _createNode(); | 136 sky.Node _createNode(); |
201 | 137 |
202 void _sync(Node old, sky.ParentNode host, sky.Node insertBefore) { | 138 void _sync(Node old, sky.ParentNode host, sky.Node insertBefore) { |
203 if (old == null) { | 139 if (old == null) { |
204 _root = _createNode(); | 140 _root = _createNode(); |
205 _parentInsertBefore(host, _root, insertBefore); | 141 _parentInsertBefore(host, _root, insertBefore); |
206 old = _emptyNode; | 142 old = _emptyNode; |
207 } else { | 143 } else { |
208 _root = old._root; | 144 _root = old._root; |
209 } | 145 } |
210 | 146 |
147 _nodeMap[_root] = this; | |
211 _syncNode(old); | 148 _syncNode(old); |
212 } | 149 } |
213 | 150 |
214 void _syncNode(RenderNode old); | 151 void _syncNode(RenderNode old); |
215 | 152 |
216 void _remove() { | 153 void _remove() { |
217 assert(_root != null); | 154 assert(_root != null); |
218 _root.remove(); | 155 _root.remove(); |
156 _nodeMap.remove(_root); | |
219 super._remove(); | 157 super._remove(); |
220 } | 158 } |
221 } | 159 } |
160 | |
161 typedef GestureEventListener(sky.GestureEvent e); | |
162 typedef PointerEventListener(sky.PointerEvent e); | |
163 typedef EventListener(sky.Event e); | |
164 | |
165 class EventTarget extends Node { | |
166 Node content; | |
167 final Map<String, sky.EventListener> listeners; | |
168 bool _docListenersRegistered = false; | |
abarth-chromium
2015/03/19 14:19:59
style nit: _docListenersRegistered -> _haveRegiste
rafaelw
2015/03/19 15:03:50
Removed this.
| |
169 | |
170 static final Set<String> _registeredEvents = new HashSet<String>(); | |
171 | |
172 static Map<String, sky.EventListener> _createListeners({ | |
173 EventListener onWheel, | |
174 GestureEventListener onGestureFlingCancel, | |
175 GestureEventListener onGestureFlingStart, | |
176 GestureEventListener onGestureScrollStart, | |
177 GestureEventListener onGestureScrollUpdate, | |
178 GestureEventListener onGestureTap, | |
179 PointerEventListener onPointerCancel, | |
180 PointerEventListener onPointerDown, | |
181 PointerEventListener onPointerMove, | |
182 PointerEventListener onPointerUp, | |
183 Map<String, sky.EventListener> custom | |
184 }) { | |
185 var listeners = custom != null ? custom | |
186 : new HashMap<String, sky.EventListener>(); | |
187 | |
188 if (onWheel != null) | |
189 listeners['wheel'] = onWheel; | |
abarth-chromium
2015/03/19 14:19:59
This if-cascade is kind of ugly, but I think you'r
rafaelw
2015/03/19 15:03:50
Yeah. We could also do this with reflection (i.e.
| |
190 if (onGestureFlingCancel != null) | |
191 listeners['gestureflingcancel'] = onGestureFlingCancel; | |
192 if (onGestureFlingStart != null) | |
193 listeners['gestureflingstart'] = onGestureFlingStart; | |
194 if (onGestureScrollStart != null) | |
195 listeners['gesturescrollstart'] = onGestureScrollStart; | |
196 if (onGestureScrollUpdate != null) | |
197 listeners['gesturescrollupdate'] = onGestureScrollUpdate; | |
198 if (onGestureTap != null) | |
199 listeners['gesturetap'] = onGestureTap; | |
200 if (onPointerCancel != null) | |
201 listeners['pointercancel'] = onPointerCancel; | |
202 if (onPointerDown != null) | |
203 listeners['pointerdown'] = onPointerDown; | |
204 if (onPointerMove != null) | |
205 listeners['pointermove'] = onPointerMove; | |
206 if (onPointerUp != null) | |
207 listeners['pointerup'] = onPointerUp; | |
abarth-chromium
2015/03/19 14:19:59
Do you actually want to mutate the |custom| argume
rafaelw
2015/03/19 15:03:50
Done.
| |
208 | |
209 return listeners; | |
210 } | |
211 | |
212 EventTarget(Node content, { | |
213 EventListener onWheel, | |
214 GestureEventListener onGestureFlingCancel, | |
215 GestureEventListener onGestureFlingStart, | |
216 GestureEventListener onGestureScrollStart, | |
217 GestureEventListener onGestureScrollUpdate, | |
218 GestureEventListener onGestureTap, | |
219 PointerEventListener onPointerCancel, | |
220 PointerEventListener onPointerDown, | |
221 PointerEventListener onPointerMove, | |
222 PointerEventListener onPointerUp, | |
223 Map<String, sky.EventListener> custom | |
224 }) : this.content = content, | |
abarth-chromium
2015/03/19 14:19:59
You can just write |this.content| in the argument
rafaelw
2015/03/19 15:03:50
Not if you want to use content in the initializer
| |
225 listeners = _createListeners( | |
226 onWheel: onWheel, | |
227 onGestureFlingCancel: onGestureFlingCancel, | |
228 onGestureFlingStart: onGestureFlingStart, | |
229 onGestureScrollUpdate: onGestureScrollUpdate, | |
230 onGestureScrollStart: onGestureScrollStart, | |
231 onGestureTap: onGestureTap, | |
232 onPointerCancel: onPointerCancel, | |
233 onPointerDown: onPointerDown, | |
234 onPointerMove: onPointerMove, | |
235 onPointerUp: onPointerUp, | |
236 custom: custom | |
237 ), | |
238 super(key: content._key); | |
239 | |
240 void _handleEvent(sky.Event e) { | |
241 sky.EventListener listener = listeners[e.type]; | |
242 if (listener != null) { | |
243 listener(e); | |
244 } | |
245 } | |
246 | |
247 static void _dispatchEvent(sky.Event e) { | |
248 Node target = RenderNode._getMounted(e.target); | |
249 | |
250 // TODO(rafaelw): StopPropagation? | |
251 while (target != null) { | |
252 if (target is EventTarget) { | |
253 (target as EventTarget)._handleEvent(e); | |
254 } | |
255 | |
256 target = target._parent; | |
257 } | |
258 } | |
259 | |
260 static void _ensureDocListener(String eventType) { | |
abarth-chromium
2015/03/19 14:19:59
style nit: s/Doc/Document/
rafaelw
2015/03/19 15:03:50
Done.
| |
261 if (_registeredEvents.add(eventType)) { | |
262 sky.document.addEventListener(eventType, _dispatchEvent); | |
263 } | |
264 } | |
abarth-chromium
2015/03/19 14:19:59
Something is off with your indentation here.
rafaelw
2015/03/19 15:03:50
Done.
| |
265 | |
266 void _sync(Node old, sky.ParentNode host, sky.Node insertBefore) { | |
267 if (!_docListenersRegistered) { | |
268 for (var type in listeners.keys) { | |
269 _ensureDocListener(type); | |
270 } | |
271 | |
272 _docListenersRegistered = true; | |
abarth-chromium
2015/03/19 14:19:59
Do we ever sync the same Node twice? It seems lik
rafaelw
2015/03/19 15:03:50
No, I think you're right. This buys us very little
| |
273 } | |
274 | |
275 Node oldContent = old == null ? null : (old as EventTarget).content; | |
276 content = _syncChild(content, oldContent , host, insertBefore); | |
abarth-chromium
2015/03/19 14:19:59
s/ ,/,/
rafaelw
2015/03/19 15:03:51
Done.
| |
277 _root = content._root; | |
278 } | |
279 | |
280 void _remove() { | |
281 content._remove(); | |
282 super._remove(); | |
283 } | |
284 } | |
222 | 285 |
223 class Text extends RenderNode { | 286 class Text extends RenderNode { |
224 final String data; | 287 final String data; |
225 | 288 |
226 // Text nodes are special cases of having non-unique keys (which don't need | 289 // Text nodes are special cases of having non-unique keys (which don't need |
227 // to be assigned as part of the API). Since they are unique in not having | 290 // to be assigned as part of the API). Since they are unique in not having |
228 // children, there's little point to reordering, so we always just re-assign | 291 // children, there's little point to reordering, so we always just re-assign |
229 // the data. | 292 // the data. |
230 Text(this.data) : super(key:'*text*'); | 293 Text(this.data) : super(key:'*text*'); |
231 | 294 |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
292 throw '''If multiple (non-Text) nodes of the same type exist as children | 355 throw '''If multiple (non-Text) nodes of the same type exist as children |
293 of another node, they must have unique keys.'''; | 356 of another node, they must have unique keys.'''; |
294 } | 357 } |
295 } | 358 } |
296 } | 359 } |
297 | 360 |
298 void _syncNode(RenderNode old) { | 361 void _syncNode(RenderNode old) { |
299 Element oldElement = old as Element; | 362 Element oldElement = old as Element; |
300 sky.Element root = _root as sky.Element; | 363 sky.Element root = _root as sky.Element; |
301 | 364 |
302 _syncEvents(oldElement.events); | |
303 | |
304 if (_class != oldElement._class) | 365 if (_class != oldElement._class) |
305 root.setAttribute('class', _class); | 366 root.setAttribute('class', _class); |
306 | 367 |
307 if (inlineStyle != oldElement.inlineStyle) | 368 if (inlineStyle != oldElement.inlineStyle) |
308 root.setAttribute('style', inlineStyle); | 369 root.setAttribute('style', inlineStyle); |
309 | 370 |
310 _syncChildren(oldElement); | 371 _syncChildren(oldElement); |
311 } | 372 } |
312 | 373 |
313 void _syncChildren(Element oldElement) { | 374 void _syncChildren(Element oldElement) { |
(...skipping 260 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
574 void _scheduleComponentForRender(Component c) { | 635 void _scheduleComponentForRender(Component c) { |
575 assert(!_inRenderDirtyComponents); | 636 assert(!_inRenderDirtyComponents); |
576 _dirtyComponents.add(c); | 637 _dirtyComponents.add(c); |
577 | 638 |
578 if (!_buildScheduled) { | 639 if (!_buildScheduled) { |
579 _buildScheduled = true; | 640 _buildScheduled = true; |
580 new Future.microtask(_buildDirtyComponents); | 641 new Future.microtask(_buildDirtyComponents); |
581 } | 642 } |
582 } | 643 } |
583 | 644 |
584 EventMap _emptyEventMap = new EventMap(); | |
585 | |
586 abstract class Component extends Node { | 645 abstract class Component extends Node { |
587 bool get _isBuilding => _currentlyBuilding == this; | 646 bool get _isBuilding => _currentlyBuilding == this; |
588 bool _dirty = true; | 647 bool _dirty = true; |
589 | 648 |
590 Node _built; | 649 Node _built; |
591 final int _order; | 650 final int _order; |
592 static int _currentOrder = 0; | 651 static int _currentOrder = 0; |
593 bool _stateful; | 652 bool _stateful; |
594 static Component _currentlyBuilding; | 653 static Component _currentlyBuilding; |
595 | 654 |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
659 int lastOrder = _currentOrder; | 718 int lastOrder = _currentOrder; |
660 _currentOrder = _order; | 719 _currentOrder = _order; |
661 _currentlyBuilding = this; | 720 _currentlyBuilding = this; |
662 _built = build(); | 721 _built = build(); |
663 _currentlyBuilding = null; | 722 _currentlyBuilding = null; |
664 _currentOrder = lastOrder; | 723 _currentOrder = lastOrder; |
665 | 724 |
666 _built = _syncChild(_built, oldBuilt, host, insertBefore); | 725 _built = _syncChild(_built, oldBuilt, host, insertBefore); |
667 _dirty = false; | 726 _dirty = false; |
668 _root = _built._root; | 727 _root = _built._root; |
669 | |
670 _built.events.addAll(events); | |
671 _syncEvents(oldComponent != null ? oldComponent.events : _emptyEventMap); | |
672 } | 728 } |
673 | 729 |
674 void _buildIfDirty() { | 730 void _buildIfDirty() { |
675 if (!_dirty || _defunct) | 731 if (!_dirty || _defunct) |
676 return; | 732 return; |
677 | 733 |
678 assert(_root != null); | 734 assert(_root != null); |
679 _sync(null, _root.parentNode, _root.nextSibling); | 735 _sync(null, _root.parentNode, _root.nextSibling); |
680 } | 736 } |
681 | 737 |
(...skipping 25 matching lines...) Expand all Loading... | |
707 | 763 |
708 _sync(null, _host, null); | 764 _sync(null, _host, null); |
709 assert(_root is sky.Node); | 765 assert(_root is sky.Node); |
710 | 766 |
711 sw.stop(); | 767 sw.stop(); |
712 if (_shouldLogRenderDuration) | 768 if (_shouldLogRenderDuration) |
713 print("Initial build: ${sw.elapsedMicroseconds} microseconds"); | 769 print("Initial build: ${sw.elapsedMicroseconds} microseconds"); |
714 }); | 770 }); |
715 } | 771 } |
716 } | 772 } |
OLD | NEW |