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;
export 'fn3/basic.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 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:sky/animation.dart';
import 'package:sky/rendering.dart';
abstract class Key {
......@@ -144,7 +143,7 @@ abstract class ComponentState<T extends StatefulComponent> {
/// rendering will not be updated.
void setState(void fn()) {
fn();
_element.scheduleBuild();
_element.markNeedsBuild();
}
/// The current configuration (an instance of the corresponding
......@@ -201,6 +200,7 @@ abstract class Element<T extends Widget> {
/// 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.
int get depth => _depth;
int _depth;
/// The configuration for this element.
......@@ -270,6 +270,7 @@ abstract class Element<T extends Widget> {
/// Called when an Element is given a new parent shortly after having been
/// created.
// TODO(ianh): rename to didMount
void mount(Element parent, dynamic slot) {
assert(_debugLifecycleState == _ElementLifecycle.initial);
assert(_widget != null);
......@@ -347,20 +348,17 @@ abstract class Element<T extends Widget> {
/// Called when an Element is removed from the tree.
/// Currently, an Element removed from the tree never returns.
// TODO(ianh): rename to didUnmount
void unmount() {
assert(_debugLifecycleState == _ElementLifecycle.mounted);
assert(_widget != null);
assert(_depth != null);
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 void BuildScheduler(BuildableElement element);
/// Base class for the instantiation of StatelessComponent and StatefulComponent
/// widgets.
......@@ -369,18 +367,29 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
WidgetBuilder _builder;
Element _child;
/// Returns true if the element has been marked as needing rebuilding.
bool get dirty => _dirty;
bool _dirty = true;
void mount(Element parent, dynamic slot) {
super.mount(parent, slot);
assert(_child == null);
_rebuild();
rebuild();
assert(_child != null);
}
// This is also called for the first build
void _rebuild() {
/// Reinvokes the build() method of the StatelessComponent object (for
/// 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);
if (!_dirty)
return;
_dirty = false;
Widget built;
try {
......@@ -392,11 +401,7 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
_child = _updateChild(_child, built, _slot);
}
/// Called by the binding when scheduleBuild() has been called to mark this element dirty.
void _rebuildIfNeeded() {
if (_dirty)
_rebuild();
}
static BuildScheduler scheduleBuildFor;
/// Marks the element as dirty and adds it to the global list of widgets to
/// rebuild in the next frame.
......@@ -405,12 +410,13 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
/// applications and components should be structured so as to only mark
/// components dirty during event handlers before the frame begins, not during
/// the build itself.
void scheduleBuild() {
void markNeedsBuild() {
assert(_debugLifecycleState == _ElementLifecycle.mounted);
if (_dirty)
return;
_dirty = true;
_buildScheduler.schedule(this);
assert(scheduleBuildFor != null);
scheduleBuildFor(this);
}
void visitChildren(ElementVisitor visitor) {
......@@ -434,7 +440,8 @@ class StatelessComponentElement extends BuildableElement<StatelessComponent> {
super.update(newWidget);
assert(_widget == newWidget);
_builder = _widget.build;
_rebuild();
_dirty = true;
rebuild();
}
}
......@@ -456,7 +463,8 @@ class StatefulComponentElement extends BuildableElement<StatefulComponent> {
StatefulComponent oldConfig = _state._config;
_state._config = _widget;
_state.didUpdateConfig(oldConfig);
_rebuild();
_dirty = true;
rebuild();
}
void unmount() {
......@@ -482,8 +490,20 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
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) {
super.mount(parent, slot);
_registry[renderObject] = this;
assert(_ancestorRenderObjectElement == null);
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
_ancestorRenderObjectElement?.insertChildRenderObject(renderObject, slot);
......@@ -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 removeChildRenderObject(RenderObject child);
......@@ -572,56 +597,6 @@ class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> exte
// 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);
/// This callback is invoked whenever an exception is caught by the widget
/// system. The 'context' argument is a description of what was happening when
......
......@@ -8,6 +8,12 @@ final BoxDecoration kBoxDecorationA = new BoxDecoration();
final BoxDecoration kBoxDecorationB = new BoxDecoration();
final BoxDecoration kBoxDecorationC = new BoxDecoration();
class TestComponent extends StatelessComponent {
const TestComponent({ this.child });
final Widget child;
Widget build() => child;
}
void main() {
test('RenderObjectWidget smoke test', () {
WidgetTester tester = new WidgetTester();
......
......@@ -78,7 +78,8 @@ void main() {
checkTree(kBoxDecorationB);
flipStatefulComponent(tester);
Element.flushBuild();
tester.pumpFrameWithoutChange();
checkTree(kBoxDecorationA);
......@@ -105,7 +106,8 @@ void main() {
expect(TestBuildCounter.buildCount, equals(1));
flipStatefulComponent(tester);
Element.flushBuild();
tester.pumpFrameWithoutChange();
expect(TestBuildCounter.buildCount, equals(1));
});
......
import 'package:sky/src/fn3/framework.dart';
import 'package:sky/src/fn3.dart';
class TestComponent extends StatelessComponent {
TestComponent({ this.child });
final Widget child;
class RootComponent extends StatefulComponent {
RootComponentState createState() => new RootComponentState(this);
}
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;
}
final Object _rootSlot = new Object();
const Object _rootSlot = const Object();
class WidgetTester {
StatelessComponentElement _rootElement;
WidgetTester() {
WidgetSkyBinding.initWidgetSkyBinding();
_rootElement = new StatefulComponentElement(new RootComponent());
_rootElement.mount(null, _rootSlot);
}
StatefulComponentElement _rootElement;
void walkElements(ElementVisitor visitor) {
void walk(Element element) {
......@@ -35,12 +54,12 @@ class WidgetTester {
}
void pumpFrame(Widget widget) {
if (_rootElement == null) {
_rootElement = new StatelessComponentElement(new TestComponent(child: widget));
_rootElement.mount(null, _rootSlot);
} else {
_rootElement.update(new TestComponent(child: widget));
}
(_rootElement.state as RootComponentState).child = widget;
WidgetSkyBinding.instance.beginFrame(0.0); // TODO(ianh): https://github.com/flutter/engine/issues/1084
}
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