| 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");
|
| + });
|
| + }
|
| +}
|
|
|