Index: sky/framework/fn.dart |
diff --git a/sky/examples/fn/lib/node.dart b/sky/framework/fn.dart |
similarity index 66% |
rename from sky/examples/fn/lib/node.dart |
rename to sky/framework/fn.dart |
index 54853b3e83278efc6a1a4aac8e60968ba2ce74e9..cfd25775265a673f5aa94577ae6212a1c2c8ca6b 100644 |
--- a/sky/examples/fn/lib/node.dart |
+++ b/sky/framework/fn.dart |
@@ -2,7 +2,76 @@ |
// Use of this source code is governed by a BSD-style license that can be |
// found in the LICENSE file. |
-part of fn; |
+library fn; |
+ |
+import 'dart:async'; |
+import 'dart:collection'; |
+import 'dart:sky' as sky; |
+import 'reflect.dart' as reflect; |
+ |
+bool _checkedMode; |
+ |
+bool _debugWarnings() { |
+ void testFn(double i) {} |
+ |
+ if (_checkedMode == null) { |
+ _checkedMode = false; |
+ try { |
+ testFn('not a double'); |
+ } catch (ex) { |
+ _checkedMode = true; |
+ } |
+ } |
+ |
+ return _checkedMode; |
+} |
+ |
+class EventHandler { |
+ final String type; |
+ final sky.EventListener listener; |
+ |
+ EventHandler(this.type, this.listener); |
+} |
+ |
+class EventMap { |
+ final List<EventHandler> _handlers = new List<EventHandler>(); |
+ |
+ void listen(String type, sky.EventListener listener) { |
+ assert(listener != null); |
+ _handlers.add(new EventHandler(type, listener)); |
+ } |
+ |
+ void addAll(EventMap events) { |
+ _handlers.addAll(events._handlers); |
+ } |
+} |
+ |
+class Style { |
+ final String _className; |
+ static final Map<String, Style> _cache = new HashMap<String, Style>(); |
+ |
+ static int nextStyleId = 1; |
+ |
+ static String nextClassName(String styles) { |
+ assert(sky.document != null); |
+ String className = "style$nextStyleId"; |
+ nextStyleId++; |
+ |
+ sky.Element styleNode = sky.document.createElement('style'); |
+ styleNode.setChild(new sky.Text(".$className { $styles }")); |
+ sky.document.appendChild(styleNode); |
+ |
+ return className; |
+ } |
+ |
+ factory Style(String styles) { |
+ return _cache.putIfAbsent(styles, () { |
+ return new Style._internal(nextClassName(styles)); |
+ }); |
+ } |
+ |
+ Style._internal(this._className); |
+} |
void _parentInsertBefore(sky.ParentNode parent, |
sky.Node node, |
@@ -433,3 +502,162 @@ class Anchor extends Element { |
} |
} |
} |
+ |
+List<Component> _dirtyComponents = new List<Component>(); |
+bool _renderScheduled = false; |
+ |
+void _renderDirtyComponents() { |
+ Stopwatch sw = new Stopwatch()..start(); |
+ |
+ _dirtyComponents.sort((a, b) => a._order - b._order); |
+ for (var comp in _dirtyComponents) { |
+ comp._renderIfDirty(); |
+ } |
+ |
+ _dirtyComponents.clear(); |
+ _renderScheduled = false; |
+ sw.stop(); |
+ print("Render took ${sw.elapsedMicroseconds} microseconds"); |
+} |
+ |
+void _scheduleComponentForRender(Component c) { |
+ _dirtyComponents.add(c); |
+ |
+ if (!_renderScheduled) { |
+ _renderScheduled = true; |
+ new Future.microtask(_renderDirtyComponents); |
+ } |
+} |
+ |
+abstract class Component extends Node { |
+ bool _dirty = true; // components begin dirty because they haven't rendered. |
+ Node _rendered = null; |
+ bool _removed = false; |
+ final int _order; |
+ static int _currentOrder = 0; |
+ bool _stateful; |
+ static Component _currentlyRendering; |
+ |
+ Component({ Object key, bool stateful }) |
+ : _stateful = stateful != null ? stateful : false, |
+ _order = _currentOrder + 1, |
+ super(key:key); |
+ |
+ void willUnmount() {} |
+ |
+ void _remove() { |
+ assert(_rendered != null); |
+ assert(_root != null); |
+ willUnmount(); |
+ _rendered._remove(); |
+ _rendered = null; |
+ _root = null; |
+ _removed = true; |
+ } |
+ |
+ // TODO(rafaelw): It seems wrong to expose DOM at all. This is presently |
+ // needed to get sizing info. |
+ sky.Node getRoot() => _root; |
+ |
+ bool _sync(Node old, sky.Node host, sky.Node insertBefore) { |
+ Component oldComponent = old as Component; |
+ |
+ if (oldComponent == null || oldComponent == this) { |
+ _renderInternal(host, insertBefore); |
+ return false; |
+ } |
+ |
+ assert(oldComponent != null); |
+ assert(_dirty); |
+ assert(_rendered == null); |
+ |
+ if (oldComponent._stateful) { |
+ _stateful = false; // prevent iloop from _renderInternal below. |
+ |
+ reflect.copyPublicFields(this, oldComponent); |
+ |
+ oldComponent._dirty = true; |
+ _dirty = false; |
+ |
+ oldComponent._renderInternal(host, insertBefore); |
+ return true; // Must retain old component |
+ } |
+ |
+ _rendered = oldComponent._rendered; |
+ _renderInternal(host, insertBefore); |
+ return false; |
+ } |
+ |
+ void _renderInternal(sky.Node host, sky.Node insertBefore) { |
+ if (!_dirty) { |
+ assert(_rendered != null); |
+ return; |
+ } |
+ |
+ var oldRendered = _rendered; |
+ int lastOrder = _currentOrder; |
+ _currentOrder = _order; |
+ _currentlyRendering = this; |
+ _rendered = render(); |
+ _currentlyRendering = null; |
+ _currentOrder = lastOrder; |
+ |
+ _rendered.events.addAll(events); |
+ |
+ _dirty = false; |
+ |
+ // TODO(rafaelw): This prevents components from returning different node |
+ // types as their root node at different times. Consider relaxing. |
+ assert(oldRendered == null || |
+ _rendered.runtimeType == oldRendered.runtimeType); |
+ |
+ if (_rendered._sync(oldRendered, host, insertBefore)) { |
+ _rendered = oldRendered; // retain stateful component |
+ } |
+ _root = _rendered._root; |
+ assert(_rendered._root is sky.Node); |
+ } |
+ |
+ void _renderIfDirty() { |
+ assert(_rendered != null); |
+ assert(!_removed); |
+ |
+ var rendered = _rendered; |
+ while (rendered is Component) { |
+ rendered = rendered._rendered; |
+ } |
+ sky.Node root = rendered._root; |
+ |
+ _renderInternal(root.parentNode, root.nextSibling); |
+ } |
+ |
+ void setState(Function fn()) { |
+ assert(_rendered != null); // cannot setState before mounting. |
+ _stateful = true; |
+ fn(); |
+ if (_currentlyRendering != this) { |
+ _dirty = true; |
+ _scheduleComponentForRender(this); |
+ } |
+ } |
+ |
+ Node render(); |
+} |
+ |
+abstract class App extends Component { |
+ sky.Node _host = null; |
+ App() |
+ : super(stateful: true) { |
+ |
+ _host = sky.document.createElement('div'); |
+ sky.document.appendChild(_host); |
+ |
+ new Future.microtask(() { |
+ Stopwatch sw = new Stopwatch()..start(); |
+ _sync(null, _host, null); |
+ assert(_root is sky.Node); |
+ sw.stop(); |
+ print("Initial render: ${sw.elapsedMicroseconds} microseconds"); |
+ }); |
+ } |
+} |