diff --git a/packages/flutter/lib/src/fn3.dart b/packages/flutter/lib/src/fn3.dart index 5d6fe52f513..1b615952468 100644 --- a/packages/flutter/lib/src/fn3.dart +++ b/packages/flutter/lib/src/fn3.dart @@ -6,3 +6,4 @@ library fn3; export 'fn3/basic.dart'; export 'fn3/framework.dart'; +export 'fn3/binding.dart'; diff --git a/packages/flutter/lib/src/fn3/binding.dart b/packages/flutter/lib/src/fn3/binding.dart new file mode 100644 index 00000000000..2d1333f0d45 --- /dev/null +++ b/packages/flutter/lib/src/fn3/binding.dart @@ -0,0 +1,92 @@ +// 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 _dirtyElements = new List(); + + 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 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 sortedDirtyElements = new List(); + 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; }); + } +} diff --git a/packages/flutter/lib/src/fn3/framework.dart b/packages/flutter/lib/src/fn3/framework.dart index e6afbb5190d..1e83ecd24f5 100644 --- a/packages/flutter/lib/src/fn3/framework.dart +++ b/packages/flutter/lib/src/fn3/framework.dart @@ -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 { /// 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 { /// 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 { /// 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 { /// 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 extends Element { 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 extends Element { _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 extends Element { /// 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 { super.update(newWidget); assert(_widget == newWidget); _builder = _widget.build; - _rebuild(); + _dirty = true; + rebuild(); } } @@ -456,7 +463,8 @@ class StatefulComponentElement extends BuildableElement { StatefulComponent oldConfig = _state._config; _state._config = _widget; _state.didUpdateConfig(oldConfig); - _rebuild(); + _dirty = true; + rebuild(); } void unmount() { @@ -482,8 +490,20 @@ abstract class RenderObjectElement extends Element return ancestor; } + static Map _registry = new Map(); + static Iterable 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 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 exte // TODO(ianh): implement } -class _BuildScheduler { - final List _dirtyElements = new List(); - - 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 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 sortedDirtyElements = new List(); - 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 diff --git a/packages/unit/test/fn3/render_object_widget_test.dart b/packages/unit/test/fn3/render_object_widget_test.dart index c4436a4fc46..d1bfd81b2e5 100644 --- a/packages/unit/test/fn3/render_object_widget_test.dart +++ b/packages/unit/test/fn3/render_object_widget_test.dart @@ -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(); diff --git a/packages/unit/test/fn3/stateful_component_test.dart b/packages/unit/test/fn3/stateful_component_test.dart index f0ab1243bba..3b3588c5fe1 100644 --- a/packages/unit/test/fn3/stateful_component_test.dart +++ b/packages/unit/test/fn3/stateful_component_test.dart @@ -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)); }); diff --git a/packages/unit/test/fn3/widget_tester.dart b/packages/unit/test/fn3/widget_tester.dart index a7513c2af6f..077026c3fc8 100644 --- a/packages/unit/test/fn3/widget_tester.dart +++ b/packages/unit/test/fn3/widget_tester.dart @@ -1,15 +1,34 @@ -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 { + 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 } }