Commit b73b06e4 authored by Hixie's avatar Hixie

fn3: Add a binding for fn3 and sky.

- I extracted the BuildScheduler into a separate binding.dart file.
- Various changes to expose private members that are needed by
  binding.dart.
- Registering the render objects for event dispatch.
- Convert the tests to use the new binding mechanism.

This doesn't yet have a RenderView or event handling.
parent 8eb8c47a
...@@ -6,3 +6,4 @@ library fn3; ...@@ -6,3 +6,4 @@ library fn3;
export 'fn3/basic.dart'; export 'fn3/basic.dart';
export 'fn3/framework.dart'; export 'fn3/framework.dart';
export 'fn3/binding.dart';
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:sky' as sky;
import 'package:sky/animation.dart';
import 'package:sky/rendering.dart';
import 'package:sky/src/fn3/framework.dart';
class WidgetSkyBinding extends SkyBinding {
WidgetSkyBinding({ RenderView renderViewOverride: null })
: super(renderViewOverride: renderViewOverride) {
BuildableElement.scheduleBuildFor = this.scheduleBuildFor;
}
/// Ensures that there is a SkyBinding object instantiated.
static void initWidgetSkyBinding({ RenderView renderViewOverride: null }) {
if (SkyBinding.instance == null)
new WidgetSkyBinding(renderViewOverride: renderViewOverride);
assert(SkyBinding.instance is WidgetSkyBinding);
}
static WidgetSkyBinding get instance => SkyBinding.instance;
void handleEvent(sky.Event event, BindingHitTestEntry entry) {
for (HitTestEntry entry in entry.result.path) {
if (entry.target is! RenderObject)
continue;
for (Widget target in RenderObjectElement.getElementsForRenderObject(entry.target)) {
// TODO(ianh): implement event handling
// if (target is ListenerElement)
// target.handleEvent(event);
}
}
super.handleEvent(event, entry);
}
void beginFrame(double timeStamp) {
buildDirtyElements();
super.beginFrame(timeStamp);
}
final List<BuildableElement> _dirtyElements = new List<BuildableElement>();
int _debugBuildingAtDepth;
/// Adds an element to the dirty elements list so that it will be rebuilt
/// when buildDirtyElements is called.
void scheduleBuildFor(BuildableElement element) {
assert(_debugBuildingAtDepth == null || element.depth > _debugBuildingAtDepth);
assert(!_dirtyElements.contains(element));
assert(element.dirty);
if (_dirtyElements.isEmpty)
scheduler.ensureVisualUpdate();
_dirtyElements.add(element);
}
void _absorbDirtyElements(List<BuildableElement> list) {
assert(_debugBuildingAtDepth != null);
assert(!_dirtyElements.any((element) => element.depth <= _debugBuildingAtDepth));
_dirtyElements.sort((BuildableElement a, BuildableElement b) => a.depth - b.depth);
list.addAll(_dirtyElements);
_dirtyElements.clear();
}
/// Builds all the elements that were marked as dirty using schedule(), in depth order.
/// If elements are marked as dirty while this runs, they must be deeper than the algorithm
/// has yet reached.
/// This is called by beginFrame().
void buildDirtyElements() {
assert(_debugBuildingAtDepth == null);
if (_dirtyElements.isEmpty)
return;
assert(() { _debugBuildingAtDepth = 0; return true; });
List<BuildableElement> sortedDirtyElements = new List<BuildableElement>();
int index = 0;
do {
_absorbDirtyElements(sortedDirtyElements);
for (; index < sortedDirtyElements.length; index += 1) {
BuildableElement element = sortedDirtyElements[index];
assert(() {
if (element.depth > _debugBuildingAtDepth)
_debugBuildingAtDepth = element.depth;
return element.depth == _debugBuildingAtDepth;
});
element.rebuild();
}
} while (_dirtyElements.isNotEmpty);
assert(() { _debugBuildingAtDepth = null; return true; });
}
}
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:sky/animation.dart';
import 'package:sky/rendering.dart'; import 'package:sky/rendering.dart';
abstract class Key { abstract class Key {
...@@ -144,7 +143,7 @@ abstract class ComponentState<T extends StatefulComponent> { ...@@ -144,7 +143,7 @@ abstract class ComponentState<T extends StatefulComponent> {
/// rendering will not be updated. /// rendering will not be updated.
void setState(void fn()) { void setState(void fn()) {
fn(); fn();
_element.scheduleBuild(); _element.markNeedsBuild();
} }
/// The current configuration (an instance of the corresponding /// The current configuration (an instance of the corresponding
...@@ -201,6 +200,7 @@ abstract class Element<T extends Widget> { ...@@ -201,6 +200,7 @@ abstract class Element<T extends Widget> {
/// An integer that is guaranteed to be greater than the parent's, if any. /// An integer that is guaranteed to be greater than the parent's, if any.
/// The element at the root of the tree must have a depth greater than 0. /// The element at the root of the tree must have a depth greater than 0.
int get depth => _depth;
int _depth; int _depth;
/// The configuration for this element. /// The configuration for this element.
...@@ -270,6 +270,7 @@ abstract class Element<T extends Widget> { ...@@ -270,6 +270,7 @@ abstract class Element<T extends Widget> {
/// Called when an Element is given a new parent shortly after having been /// Called when an Element is given a new parent shortly after having been
/// created. /// created.
// TODO(ianh): rename to didMount
void mount(Element parent, dynamic slot) { void mount(Element parent, dynamic slot) {
assert(_debugLifecycleState == _ElementLifecycle.initial); assert(_debugLifecycleState == _ElementLifecycle.initial);
assert(_widget != null); assert(_widget != null);
...@@ -347,20 +348,17 @@ abstract class Element<T extends Widget> { ...@@ -347,20 +348,17 @@ abstract class Element<T extends Widget> {
/// Called when an Element is removed from the tree. /// Called when an Element is removed from the tree.
/// Currently, an Element removed from the tree never returns. /// Currently, an Element removed from the tree never returns.
// TODO(ianh): rename to didUnmount
void unmount() { void unmount() {
assert(_debugLifecycleState == _ElementLifecycle.mounted); assert(_debugLifecycleState == _ElementLifecycle.mounted);
assert(_widget != null); assert(_widget != null);
assert(_depth != null); assert(_depth != null);
assert(() { _debugLifecycleState = _ElementLifecycle.defunct; return true; }); assert(() { _debugLifecycleState = _ElementLifecycle.defunct; return true; });
} }
// TODO(ianh): Merge this into the binding, expose for tests
static void flushBuild() {
_buildScheduler.buildDirtyElements();
}
} }
typedef Widget WidgetBuilder(); typedef Widget WidgetBuilder();
typedef void BuildScheduler(BuildableElement element);
/// Base class for the instantiation of StatelessComponent and StatefulComponent /// Base class for the instantiation of StatelessComponent and StatefulComponent
/// widgets. /// widgets.
...@@ -369,18 +367,29 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -369,18 +367,29 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
WidgetBuilder _builder; WidgetBuilder _builder;
Element _child; Element _child;
/// Returns true if the element has been marked as needing rebuilding.
bool get dirty => _dirty;
bool _dirty = true; bool _dirty = true;
void mount(Element parent, dynamic slot) { void mount(Element parent, dynamic slot) {
super.mount(parent, slot); super.mount(parent, slot);
assert(_child == null); assert(_child == null);
_rebuild(); rebuild();
assert(_child != null); assert(_child != null);
} }
// This is also called for the first build /// Reinvokes the build() method of the StatelessComponent object (for
void _rebuild() { /// stateless components) or the ComponentState object (for stateful
/// components) and then updates the widget tree.
///
/// Called automatically during didMount() to generate the first build, by the
/// binding when scheduleBuild() has been called to mark this element dirty,
/// and by update() when the Widget has changed.
void rebuild() {
assert(_debugLifecycleState == _ElementLifecycle.mounted); assert(_debugLifecycleState == _ElementLifecycle.mounted);
if (!_dirty)
return;
_dirty = false; _dirty = false;
Widget built; Widget built;
try { try {
...@@ -392,11 +401,7 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -392,11 +401,7 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
_child = _updateChild(_child, built, _slot); _child = _updateChild(_child, built, _slot);
} }
/// Called by the binding when scheduleBuild() has been called to mark this element dirty. static BuildScheduler scheduleBuildFor;
void _rebuildIfNeeded() {
if (_dirty)
_rebuild();
}
/// Marks the element as dirty and adds it to the global list of widgets to /// Marks the element as dirty and adds it to the global list of widgets to
/// rebuild in the next frame. /// rebuild in the next frame.
...@@ -405,12 +410,13 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -405,12 +410,13 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
/// applications and components should be structured so as to only mark /// applications and components should be structured so as to only mark
/// components dirty during event handlers before the frame begins, not during /// components dirty during event handlers before the frame begins, not during
/// the build itself. /// the build itself.
void scheduleBuild() { void markNeedsBuild() {
assert(_debugLifecycleState == _ElementLifecycle.mounted); assert(_debugLifecycleState == _ElementLifecycle.mounted);
if (_dirty) if (_dirty)
return; return;
_dirty = true; _dirty = true;
_buildScheduler.schedule(this); assert(scheduleBuildFor != null);
scheduleBuildFor(this);
} }
void visitChildren(ElementVisitor visitor) { void visitChildren(ElementVisitor visitor) {
...@@ -434,7 +440,8 @@ class StatelessComponentElement extends BuildableElement<StatelessComponent> { ...@@ -434,7 +440,8 @@ class StatelessComponentElement extends BuildableElement<StatelessComponent> {
super.update(newWidget); super.update(newWidget);
assert(_widget == newWidget); assert(_widget == newWidget);
_builder = _widget.build; _builder = _widget.build;
_rebuild(); _dirty = true;
rebuild();
} }
} }
...@@ -456,7 +463,8 @@ class StatefulComponentElement extends BuildableElement<StatefulComponent> { ...@@ -456,7 +463,8 @@ class StatefulComponentElement extends BuildableElement<StatefulComponent> {
StatefulComponent oldConfig = _state._config; StatefulComponent oldConfig = _state._config;
_state._config = _widget; _state._config = _widget;
_state.didUpdateConfig(oldConfig); _state.didUpdateConfig(oldConfig);
_rebuild(); _dirty = true;
rebuild();
} }
void unmount() { void unmount() {
...@@ -482,8 +490,20 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element ...@@ -482,8 +490,20 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
return ancestor; return ancestor;
} }
static Map<RenderObject, RenderObjectElement> _registry = new Map<RenderObject, RenderObjectElement>();
static Iterable<RenderObjectElement> getElementsForRenderObject(RenderObject renderObject) sync* {
Element target = _registry[renderObject];
while (target != null) {
yield target;
target = target._parent;
if (target is RenderObjectElement)
break;
}
}
void mount(Element parent, dynamic slot) { void mount(Element parent, dynamic slot) {
super.mount(parent, slot); super.mount(parent, slot);
_registry[renderObject] = this;
assert(_ancestorRenderObjectElement == null); assert(_ancestorRenderObjectElement == null);
_ancestorRenderObjectElement = _findAncestorRenderObjectElement(); _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
_ancestorRenderObjectElement?.insertChildRenderObject(renderObject, slot); _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, slot);
...@@ -508,6 +528,11 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element ...@@ -508,6 +528,11 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
} }
} }
void unmount() {
super.unmount();
_registry.remove(renderObject);
}
void insertChildRenderObject(RenderObject child, dynamic slot); void insertChildRenderObject(RenderObject child, dynamic slot);
void removeChildRenderObject(RenderObject child); void removeChildRenderObject(RenderObject child);
...@@ -572,56 +597,6 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte ...@@ -572,56 +597,6 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
// TODO(ianh): implement // TODO(ianh): implement
} }
class _BuildScheduler {
final List<BuildableElement> _dirtyElements = new List<BuildableElement>();
int _debugBuildingAtDepth;
void schedule(BuildableElement element) {
assert(_debugBuildingAtDepth == null || element._depth > _debugBuildingAtDepth);
assert(!_dirtyElements.contains(element));
if (_dirtyElements.isEmpty)
scheduler.ensureVisualUpdate();
_dirtyElements.add(element);
}
void _absorbDirtyElements(List<BuildableElement> list) {
assert(_debugBuildingAtDepth != null);
assert(!_dirtyElements.any((element) => element._depth <= _debugBuildingAtDepth));
_dirtyElements.sort((BuildableElement a, BuildableElement b) => a._depth - b._depth);
list.addAll(_dirtyElements);
_dirtyElements.clear();
}
/// Builds all the elements that were marked as dirty using schedule(), in depth order.
/// If elements are marked as dirty while this runs, they must be deeper than the algorithm
/// has yet reached.
void buildDirtyElements() {
assert(_debugBuildingAtDepth == null);
if (_dirtyElements.isEmpty)
return;
assert(() { _debugBuildingAtDepth = 0; return true; });
List<BuildableElement> sortedDirtyElements = new List<BuildableElement>();
int index = 0;
do {
_absorbDirtyElements(sortedDirtyElements);
for (; index < sortedDirtyElements.length; index += 1) {
BuildableElement element = sortedDirtyElements[index];
assert(() {
if (element._depth > _debugBuildingAtDepth)
_debugBuildingAtDepth = element._depth;
return element._depth == _debugBuildingAtDepth;
});
element._rebuildIfNeeded();
}
} while (_dirtyElements.isNotEmpty);
assert(() { _debugBuildingAtDepth = null; return true; });
}
}
final _BuildScheduler _buildScheduler = new _BuildScheduler();
typedef void WidgetsExceptionHandler(String context, dynamic exception, StackTrace stack); typedef void WidgetsExceptionHandler(String context, dynamic exception, StackTrace stack);
/// This callback is invoked whenever an exception is caught by the widget /// This callback is invoked whenever an exception is caught by the widget
/// system. The 'context' argument is a description of what was happening when /// system. The 'context' argument is a description of what was happening when
......
...@@ -8,6 +8,12 @@ final BoxDecoration kBoxDecorationA = new BoxDecoration(); ...@@ -8,6 +8,12 @@ final BoxDecoration kBoxDecorationA = new BoxDecoration();
final BoxDecoration kBoxDecorationB = new BoxDecoration(); final BoxDecoration kBoxDecorationB = new BoxDecoration();
final BoxDecoration kBoxDecorationC = new BoxDecoration(); final BoxDecoration kBoxDecorationC = new BoxDecoration();
class TestComponent extends StatelessComponent {
const TestComponent({ this.child });
final Widget child;
Widget build() => child;
}
void main() { void main() {
test('RenderObjectWidget smoke test', () { test('RenderObjectWidget smoke test', () {
WidgetTester tester = new WidgetTester(); WidgetTester tester = new WidgetTester();
......
...@@ -78,7 +78,8 @@ void main() { ...@@ -78,7 +78,8 @@ void main() {
checkTree(kBoxDecorationB); checkTree(kBoxDecorationB);
flipStatefulComponent(tester); flipStatefulComponent(tester);
Element.flushBuild();
tester.pumpFrameWithoutChange();
checkTree(kBoxDecorationA); checkTree(kBoxDecorationA);
...@@ -105,7 +106,8 @@ void main() { ...@@ -105,7 +106,8 @@ void main() {
expect(TestBuildCounter.buildCount, equals(1)); expect(TestBuildCounter.buildCount, equals(1));
flipStatefulComponent(tester); flipStatefulComponent(tester);
Element.flushBuild();
tester.pumpFrameWithoutChange();
expect(TestBuildCounter.buildCount, equals(1)); expect(TestBuildCounter.buildCount, equals(1));
}); });
......
import 'package:sky/src/fn3/framework.dart'; import 'package:sky/src/fn3.dart';
class TestComponent extends StatelessComponent { class RootComponent extends StatefulComponent {
TestComponent({ this.child }); RootComponentState createState() => new RootComponentState(this);
final Widget child; }
class RootComponentState extends ComponentState<RootComponent> {
RootComponentState(RootComponent widget) : super(widget);
Widget _child = new DecoratedBox(decoration: new BoxDecoration());
Widget get child => _child;
void set child(Widget value) {
if (value != _child) {
setState(() {
_child = value;
});
}
}
Widget build() => child; Widget build() => child;
} }
final Object _rootSlot = new Object(); const Object _rootSlot = const Object();
class WidgetTester { class WidgetTester {
StatelessComponentElement _rootElement;
WidgetTester() {
WidgetSkyBinding.initWidgetSkyBinding();
_rootElement = new StatefulComponentElement(new RootComponent());
_rootElement.mount(null, _rootSlot);
}
StatefulComponentElement _rootElement;
void walkElements(ElementVisitor visitor) { void walkElements(ElementVisitor visitor) {
void walk(Element element) { void walk(Element element) {
...@@ -35,12 +54,12 @@ class WidgetTester { ...@@ -35,12 +54,12 @@ class WidgetTester {
} }
void pumpFrame(Widget widget) { void pumpFrame(Widget widget) {
if (_rootElement == null) { (_rootElement.state as RootComponentState).child = widget;
_rootElement = new StatelessComponentElement(new TestComponent(child: widget)); WidgetSkyBinding.instance.beginFrame(0.0); // TODO(ianh): https://github.com/flutter/engine/issues/1084
_rootElement.mount(null, _rootSlot); }
} else {
_rootElement.update(new TestComponent(child: widget)); void pumpFrameWithoutChange() {
} WidgetSkyBinding.instance.beginFrame(0.0); // TODO(ianh): https://github.com/flutter/engine/issues/1084
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment