Commit 6a88ec6d authored by Ian Hickson's avatar Ian Hickson

Merge pull request #1248 from abarth/fn3

Prototype of fn3
parents 130a5cb6 b4ff5ca6
// 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.
library fn3;
export 'fn3/basic.dart';
export 'fn3/framework.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 'package:sky/rendering.dart';
import 'package:sky/src/fn3/framework.dart';
export 'package:sky/rendering.dart' show
BackgroundImage,
BlockDirection,
Border,
BorderSide,
BoxConstraints,
BoxDecoration,
BoxDecorationPosition,
BoxShadow,
Color,
EdgeDims,
EventDisposition,
FlexAlignItems,
FlexDirection,
FlexJustifyContent,
Offset,
Paint,
Path,
Point,
Rect,
ScrollDirection,
Shape,
ShrinkWrap,
Size,
ValueChanged;
class DecoratedBox extends OneChildRenderObjectWidget {
DecoratedBox({
Key key,
this.decoration,
this.position: BoxDecorationPosition.background,
Widget child
}) : super(key: key, child: child) {
assert(decoration != null);
assert(position != null);
}
final BoxDecoration decoration;
final BoxDecorationPosition position;
RenderObject createRenderObject() => new RenderDecoratedBox(
decoration: decoration,
position: position
);
void updateRenderObject(RenderDecoratedBox renderObject) {
renderObject.decoration = decoration;
renderObject.position = position;
}
}
// 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 'package:sky/rendering.dart';
abstract class Key {
}
abstract class Widget {
Widget(this.key);
final Key key;
Element createElement();
}
typedef Widget WidgetBuilder();
abstract class RenderObjectWidget extends Widget {
RenderObjectWidget({ Key key }) : super(key);
Element createElement() => new RenderObjectElement(this);
RenderObject createRenderObject();
void updateRenderObject(RenderObject renderObject);
}
abstract class OneChildRenderObjectWidget extends RenderObjectWidget {
OneChildRenderObjectWidget({ Key key, Widget this.child }) : super(key: key);
final Widget child;
Element createElement() => new OneChildRenderObjectElement(this);
}
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
MultiChildRenderObjectWidget({ Key key, List<Widget> this.children })
: super(key: key);
final List<Widget> children;
Element createElement() => new MultiChildRenderObjectElement(this);
}
abstract class Component extends Widget {
Component({ Key key }) : super(key);
Element createElement() => new ComponentElement(this);
Widget build();
}
abstract class ComponentState<T extends ComponentConfiguration> {
ComponentStateElement _holder;
void setState(void fn()) {
fn();
_holder.scheduleBuild();
}
T get config => _config;
T _config;
/// Override this setter to update additional state when the config changes.
void set config(T config) {
_config = config;
}
/// Called when this object is removed from the tree
void didUnmount() { }
Widget build();
}
abstract class ComponentConfiguration extends Widget {
ComponentConfiguration({ Key key }) : super(key);
ComponentStateElement createElement() => new ComponentStateElement(this);
ComponentState createState();
}
bool _canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
void _debugReportException(String context, dynamic exception, StackTrace stack) {
print('------------------------------------------------------------------------');
'Exception caught while $context'.split('\n').forEach(print);
print('$exception');
print('Stack trace:');
'$stack'.split('\n').forEach(print);
print('------------------------------------------------------------------------');
}
enum _ElementLifecycle {
initial,
mounted,
defunct,
}
typedef void ElementVisitor(Element element);
abstract class Element<T extends Widget> {
Element(T widget) : _widget = widget {
assert(_widget != null);
}
Element _parent;
T _widget;
_ElementLifecycle _lifecycleState = _ElementLifecycle.initial;
void visitChildren(ElementVisitor visitor) { }
void visitDescendants(ElementVisitor visitor) {
void walk(Element element) {
visitor(element);
element.visitChildren(walk);
}
visitChildren(walk);
}
void mount(dynamic slot) {
assert(_lifecycleState == _ElementLifecycle.initial);
assert(_widget != null);
_lifecycleState = _ElementLifecycle.mounted;
assert(_parent == null || _parent._lifecycleState == _ElementLifecycle.mounted);
}
void update(T updated, dynamic slot) {
assert(_lifecycleState == _ElementLifecycle.mounted);
assert(_widget != null);
assert(updated != null);
assert(_canUpdate(_widget, updated));
_widget = updated;
}
void unmount() {
assert(_lifecycleState == _ElementLifecycle.mounted);
assert(_widget != null);
_lifecycleState = _ElementLifecycle.defunct;
}
void _detachChild(Element child) {
if (child == null)
return;
child._parent = null;
bool haveDetachedRenderObject = false;
void detach(Element descendant) {
if (!haveDetachedRenderObject && descendant is RenderObjectElement) {
descendant.detachRenderObject();
haveDetachedRenderObject = true;
}
descendant.unmount();
}
detach(child);
child.visitDescendants(detach);
}
Element _updateChild(Element child, Widget updated, dynamic slot) {
if (updated == null) {
_detachChild(child);
return null;
}
if (child != null) {
if (_canUpdate(child._widget, updated)) {
child.update(updated, slot);
return child;
}
_detachChild(child);
assert(child._parent == null);
}
Element newChild = updated.createElement();
newChild._parent = this;
newChild.mount(slot);
return newChild;
}
}
abstract class BuildableElement<T extends Widget> extends Element<T> {
BuildableElement(T widget) : super(widget);
WidgetBuilder _builder;
Element _child;
void _rebuild(dynamic slot) {
Widget built;
try {
built = _builder();
assert(built != null);
} catch (e, stack) {
_debugReportException('building $this', e, stack);
}
_child = _updateChild(_child, built, slot);
}
void visitChildren(ElementVisitor visitor) {
if (_child != null)
visitor(_child);
}
void mount(dynamic slot) {
super.mount(slot);
assert(_child == null);
_rebuild(slot);
assert(_child != null);
}
}
class ComponentElement extends BuildableElement<Component> {
ComponentElement(Component component) : super(component) {
_builder = component.build;
}
void update(Component updated, dynamic slot) {
super.update(updated, slot);
assert(_widget == updated);
_builder = _widget.build;
_rebuild(slot);
}
}
class ComponentStateElement extends BuildableElement<ComponentConfiguration> {
ComponentStateElement(ComponentConfiguration configuration)
: _state = configuration.createState(), super(configuration) {
_builder = _state.build;
_state._holder = this;
_state.config = configuration;
}
ComponentState _state;
void update(ComponentConfiguration updated, dynamic slot) {
super.update(updated, slot);
assert(_widget == updated);
_state.config = _widget;
_rebuild(slot);
}
void unmount() {
super.unmount();
_state.didUnmount();
_state = null;
}
void scheduleBuild() {
// TODO(abarth): Implement rebuilding.
}
}
RenderObjectElement _findAncestorRenderObjectElement(Element ancestor) {
while (ancestor != null && ancestor is! RenderObjectElement)
ancestor = ancestor._parent;
return ancestor;
}
class RenderObjectElement<T extends RenderObjectWidget> extends Element<T> {
RenderObjectElement(T widget)
: renderObject = widget.createRenderObject(), super(widget);
final RenderObject renderObject;
RenderObjectElement _ancestorRenderObjectElement;
void mount(dynamic slot) {
super.mount(slot);
assert(_ancestorRenderObjectElement == null);
_ancestorRenderObjectElement = _findAncestorRenderObjectElement(_parent);
if (_ancestorRenderObjectElement != null)
_ancestorRenderObjectElement.insertChildRenderObject(renderObject, slot);
}
void update(T updated, dynamic slot) {
super.update(updated, slot);
assert(_widget == updated);
_widget.updateRenderObject(renderObject);
}
void detachRenderObject() {
if (_ancestorRenderObjectElement != null) {
_ancestorRenderObjectElement.removeChildRenderObject(renderObject);
_ancestorRenderObjectElement = null;
}
}
void insertChildRenderObject(RenderObject child, dynamic slot) {
assert(false);
}
void removeChildRenderObject(RenderObject child) {
assert(false);
}
}
class OneChildRenderObjectElement<T extends OneChildRenderObjectWidget> extends RenderObjectElement<T> {
OneChildRenderObjectElement(T widget) : super(widget);
Element _child;
void visitChildren(ElementVisitor visitor) {
if (_child != null)
visitor(_child);
}
void mount(dynamic slot) {
super.mount(slot);
_child = _updateChild(_child, _widget.child, null);
}
void update(T updated, dynamic slot) {
super.update(updated, slot);
assert(_widget == updated);
_child = _updateChild(_child, _widget.child, null);
}
void insertChildRenderObject(RenderObject child, dynamic slot) {
final renderObject = this.renderObject; // TODO(ianh): Remove this once the analyzer is cleverer
assert(renderObject is RenderObjectWithChildMixin);
assert(slot == null);
renderObject.child = child;
assert(renderObject == this.renderObject); // TODO(ianh): Remove this once the analyzer is cleverer
}
void removeChildRenderObject(RenderObject child) {
final renderObject = this.renderObject; // TODO(ianh): Remove this once the analyzer is cleverer
assert(renderObject is RenderObjectWithChildMixin);
assert(renderObject.child == child);
renderObject.child = null;
assert(renderObject == this.renderObject); // TODO(ianh): Remove this once the analyzer is cleverer
}
}
class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> extends RenderObjectElement<T> {
MultiChildRenderObjectElement(T widget) : super(widget);
}
......@@ -22,7 +22,7 @@ export 'package:sky/src/rendering/object.dart' show Point, Offset, Size, Rect, C
final bool _shouldLogRenderDuration = false; // see also 'enableProfilingLoop' argument to runApp()
typedef Widget Builder();
typedef void WidgetTreeWalker(Widget);
typedef void WidgetTreeWalker(Widget widget);
abstract class Key {
const Key.constructor(); // so that subclasses can call us, since the Key() factory constructor shadows the implicit constructor
......
import 'package:sky/rendering.dart';
import 'package:sky/src/fn3.dart';
import 'package:test/test.dart';
import 'widget_tester.dart';
final BoxDecoration kBoxDecorationA = new BoxDecoration();
final BoxDecoration kBoxDecorationB = new BoxDecoration();
final BoxDecoration kBoxDecorationC = new BoxDecoration();
void main() {
test('RenderObjectWidget smoke test', () {
WidgetTester tester = new WidgetTester();
tester.pumpFrame(new DecoratedBox(decoration: kBoxDecorationA));
OneChildRenderObjectElement element =
tester.findElement((element) => element is OneChildRenderObjectElement);
expect(element, isNotNull);
expect(element.renderObject is RenderDecoratedBox, isTrue);
RenderDecoratedBox renderObject = element.renderObject;
expect(renderObject.decoration, equals(kBoxDecorationA));
expect(renderObject.position, equals(BoxDecorationPosition.background));
tester.pumpFrame(new DecoratedBox(decoration: kBoxDecorationB));
element = tester.findElement((element) => element is OneChildRenderObjectElement);
expect(element, isNotNull);
expect(element.renderObject is RenderDecoratedBox, isTrue);
renderObject = element.renderObject;
expect(renderObject.decoration, equals(kBoxDecorationB));
expect(renderObject.position, equals(BoxDecorationPosition.background));
});
test('RenderObjectWidget can add and remove children', () {
WidgetTester tester = new WidgetTester();
void checkFullTree() {
OneChildRenderObjectElement element =
tester.findElement((element) => element is OneChildRenderObjectElement);
expect(element, isNotNull);
expect(element.renderObject is RenderDecoratedBox, isTrue);
RenderDecoratedBox renderObject = element.renderObject;
expect(renderObject.decoration, equals(kBoxDecorationA));
expect(renderObject.position, equals(BoxDecorationPosition.background));
expect(renderObject.child, isNotNull);
expect(renderObject.child is RenderDecoratedBox, isTrue);
RenderDecoratedBox child = renderObject.child;
expect(child.decoration, equals(kBoxDecorationB));
expect(child.position, equals(BoxDecorationPosition.background));
expect(child.child, isNull);
}
void childBareTree() {
OneChildRenderObjectElement element =
tester.findElement((element) => element is OneChildRenderObjectElement);
expect(element, isNotNull);
expect(element.renderObject is RenderDecoratedBox, isTrue);
RenderDecoratedBox renderObject = element.renderObject;
expect(renderObject.decoration, equals(kBoxDecorationA));
expect(renderObject.position, equals(BoxDecorationPosition.background));
expect(renderObject.child, isNull);
}
tester.pumpFrame(new DecoratedBox(
decoration: kBoxDecorationA,
child: new DecoratedBox(
decoration: kBoxDecorationB
)
));
checkFullTree();
tester.pumpFrame(new DecoratedBox(
decoration: kBoxDecorationA,
child: new TestComponent(
child: new DecoratedBox(
decoration: kBoxDecorationB
)
)
));
checkFullTree();
tester.pumpFrame(new DecoratedBox(
decoration: kBoxDecorationA,
child: new DecoratedBox(
decoration: kBoxDecorationB
)
));
checkFullTree();
tester.pumpFrame(new DecoratedBox(
decoration: kBoxDecorationA
));
childBareTree();
tester.pumpFrame(new DecoratedBox(
decoration: kBoxDecorationA,
child: new TestComponent(
child: new TestComponent(
child: new DecoratedBox(
decoration: kBoxDecorationB
)
)
)
));
checkFullTree();
tester.pumpFrame(new DecoratedBox(
decoration: kBoxDecorationA
));
childBareTree();
});
test('Detached render tree is intact', () {
WidgetTester tester = new WidgetTester();
tester.pumpFrame(new DecoratedBox(
decoration: kBoxDecorationA,
child: new DecoratedBox(
decoration: kBoxDecorationB,
child: new DecoratedBox(
decoration: kBoxDecorationC
)
)
));
OneChildRenderObjectElement element =
tester.findElement((element) => element is OneChildRenderObjectElement);
expect(element.renderObject is RenderDecoratedBox, isTrue);
RenderDecoratedBox parent = element.renderObject;
expect(parent.child is RenderDecoratedBox, isTrue);
RenderDecoratedBox child = parent.child;
expect(child.decoration, equals(kBoxDecorationB));
expect(child.child is RenderDecoratedBox, isTrue);
RenderDecoratedBox grandChild = child.child;
expect(grandChild.decoration, equals(kBoxDecorationC));
expect(grandChild.child, isNull);
tester.pumpFrame(new DecoratedBox(
decoration: kBoxDecorationA
));
element =
tester.findElement((element) => element is OneChildRenderObjectElement);
expect(element.renderObject is RenderDecoratedBox, isTrue);
expect(element.renderObject, equals(parent));
expect(parent.child, isNull);
expect(child.parent, isNull);
expect(child.decoration, equals(kBoxDecorationB));
expect(child.child, equals(grandChild));
expect(grandChild.parent, equals(child));
expect(grandChild.decoration, equals(kBoxDecorationC));
expect(grandChild.child, isNull);
});
}
import 'package:sky/src/fn3/framework.dart';
class TestComponent extends Component {
TestComponent({ this.child });
final Widget child;
Widget build() => child;
}
class WidgetTester {
ComponentElement _rootElement;
void walkElements(ElementVisitor visitor) {
void walk(Element element) {
visitor(element);
element.visitChildren(walk);
}
_rootElement.visitChildren(walk);
}
Element findElement(bool predicate(Element widget)) {
try {
walkElements((Element widget) {
if (predicate(widget))
throw widget;
});
} catch (e) {
if (e is Element)
return e;
rethrow;
}
return null;
}
void pumpFrame(Widget widget) {
if (_rootElement == null) {
_rootElement = new ComponentElement(new TestComponent(child: widget));
_rootElement.mount(null);
} else {
_rootElement.update(new TestComponent(child: widget), null);
}
}
}
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