diff --git a/packages/flutter/lib/src/fn3/basic.dart b/packages/flutter/lib/src/fn3/basic.dart index 1430f46365..effb5b7581 100644 --- a/packages/flutter/lib/src/fn3/basic.dart +++ b/packages/flutter/lib/src/fn3/basic.dart @@ -467,11 +467,59 @@ class Stack extends MultiChildRenderObjectWidget { void updateRenderObject(RenderStack renderObject, Stack oldWidget) { // Nothing to update } - - // TODO(abarth): Update parent data } -// TODO(abarth): Positioned +class Positioned extends ParentDataWidget { + Positioned({ + Key key, + Widget child, + this.top, + this.right, + this.bottom, + this.left + }) : super(key: key, child: child); + + final double top; + final double right; + final double bottom; + final double left; + + void debugValidateAncestor(Widget ancestor) { + assert(() { + 'Positioned must placed inside a Stack'; + return ancestor is Stack; + }); + } + + void applyParentData(RenderObject renderObject) { + assert(renderObject.parentData is StackParentData); + final StackParentData parentData = renderObject.parentData; + bool needsLayout = false; + + if (parentData.top != top) { + parentData.top = top; + needsLayout = true; + } + + if (parentData.right != right) { + parentData.right = right; + needsLayout = true; + } + + if (parentData.bottom != bottom) { + parentData.bottom = bottom; + needsLayout = true; + } + + if (parentData.left != left) { + parentData.left = left; + needsLayout = true; + } + + if (needsLayout) + renderObject.markNeedsLayout(); + } +} class Grid extends MultiChildRenderObjectWidget { Grid(List children, { Key key, this.maxChildExtent }) @@ -514,8 +562,6 @@ class Flex extends MultiChildRenderObjectWidget { renderObject.alignItems = alignItems; renderObject.textBaseline = textBaseline; } - - // TODO(abarth): Update parent data } class Row extends Flex { @@ -536,7 +582,28 @@ class Column extends Flex { }) : super(children, key: key, direction: FlexDirection.vertical, justifyContent: justifyContent, alignItems: alignItems, textBaseline: textBaseline); } -// TODO(abarth): Flexible +class Flexible extends ParentDataWidget { + Flexible({ Key key, this.flex: 1, Widget child }) + : super(key: key, child: child); + + final int flex; + + void debugValidateAncestor(Widget ancestor) { + assert(() { + 'Flexible must placed inside a Flex'; + return ancestor is Flex; + }); + } + + void applyParentData(RenderObject renderObject) { + assert(renderObject.parentData is FlexParentData); + final FlexParentData parentData = renderObject.parentData; + if (parentData.flex != flex) { + parentData.flex = flex; + renderObject.markNeedsLayout(); + } + } +} class Paragraph extends LeafRenderObjectWidget { Paragraph({ Key key, this.text }) : super(key: key) { @@ -579,7 +646,51 @@ class StyledText extends StatelessComponent { } } -// TODO(abarth): Text +class DefaultTextStyle extends InheritedWidget { + DefaultTextStyle({ + Key key, + this.style, + Widget child + }) : super(key: key, child: child) { + assert(style != null); + assert(child != null); + } + + final TextStyle style; + + static TextStyle of(BuildContext context) { + DefaultTextStyle result = context.inheritedWidgetOfType(DefaultTextStyle); + return result?.style; + } + + bool updateShouldNotify(DefaultTextStyle old) => style != old.style; +} + +class Text extends StatelessComponent { + Text(this.data, { Key key, TextStyle this.style }) : super(key: key) { + assert(data != null); + } + + final String data; + final TextStyle style; + + Widget build(BuildContext context) { + TextSpan text = new PlainTextSpan(data); + TextStyle defaultStyle = DefaultTextStyle.of(context); + TextStyle combinedStyle; + if (defaultStyle != null) { + if (style != null) + combinedStyle = defaultStyle.merge(style); + else + combinedStyle = defaultStyle; + } else { + combinedStyle = style; + } + if (combinedStyle != null) + text = new StyledTextSpan(combinedStyle, [text]); + return new Paragraph(text: text); + } +} class Image extends LeafRenderObjectWidget { Image({ diff --git a/packages/flutter/lib/src/fn3/framework.dart b/packages/flutter/lib/src/fn3/framework.dart index abb9d13dd7..dccb9d4eeb 100644 --- a/packages/flutter/lib/src/fn3/framework.dart +++ b/packages/flutter/lib/src/fn3/framework.dart @@ -105,7 +105,11 @@ abstract class StatelessComponent extends Widget { /// Returns another Widget out of which this StatelessComponent is built. /// Typically that Widget will have been configured with further children, /// such that really this function returns a tree of configuration. - Widget build(); + /// + /// The given build context object contains information about the location in + /// the tree at which this component is being built. For example, the context + /// provides the set of inherited widgets for this location in the tree. + Widget build(BuildContext context); } /// StatefulComponents provide the configuration for @@ -162,7 +166,41 @@ abstract class ComponentState { /// Returns another Widget out of which this StatefulComponent is built. /// Typically that Widget will have been configured with further children, /// such that really this function returns a tree of configuration. - Widget build(); + /// + /// The given build context object contains information about the location in + /// the tree at which this component is being built. For example, the context + /// provides the set of inherited widgets for this location in the tree. + Widget build(BuildContext context); +} + +abstract class ProxyWidget extends StatelessComponent { + const ProxyWidget({ Key key, Widget this.child }) : super(key: key); + + final Widget child; + + Widget build(BuildContext context) => child; +} + +abstract class ParentDataWidget extends ProxyWidget { + ParentDataWidget({ Key key, Widget child }) + : super(key: key, child: child); + + /// Subclasses should override this function to ensure that they are placed + /// inside widgets that expect them. + /// + /// The given ancestor is the first RenderObjectWidget ancestor of this widget. + void debugValidateAncestor(RenderObjectWidget ancestor); + + void applyParentData(RenderObject renderObject); +} + +abstract class InheritedWidget extends ProxyWidget { + const InheritedWidget({ Key key, Widget child }) + : super(key: key, child: child); + + InheritedElement createElement() => new InheritedElement(this); + + bool updateShouldNotify(InheritedWidget oldWidget); } bool _canUpdate(Widget oldWidget, Widget newWidget) { @@ -180,11 +218,15 @@ typedef void ElementVisitor(Element element); const Object _uniqueChild = const Object(); +abstract class BuildContext { + InheritedWidget inheritedWidgetOfType(Type targetType); +} + /// Elements are the instantiations of Widget configurations. /// /// Elements can, in principle, have children. Only subclasses of /// RenderObjectElement are allowed to have more than one child. -abstract class Element { +abstract class Element implements BuildContext { Element(T widget) : _widget = widget { assert(_widget != null); } @@ -355,9 +397,24 @@ abstract class Element { assert(_depth != null); assert(() { _debugLifecycleState = _ElementLifecycle.defunct; return true; }); } + + Set _dependencies; + InheritedWidget inheritedWidgetOfType(Type targetType) { + if (_dependencies == null) + _dependencies = new Set(); + _dependencies.add(targetType); + Element ancestor = _parent; + while (ancestor != null && ancestor._widget.runtimeType != targetType) + ancestor = ancestor._parent; + return ancestor._widget; + } + + void dependenciesChanged() { + assert(false); + } } -typedef Widget WidgetBuilder(); +typedef Widget WidgetBuilder(BuildContext context); typedef void BuildScheduler(BuildableElement element); /// Base class for the instantiation of StatelessComponent and StatefulComponent @@ -393,7 +450,7 @@ abstract class BuildableElement extends Element { _dirty = false; Widget built; try { - built = _builder(); + built = _builder(this); assert(built != null); } catch (e, stack) { _debugReportException('building $this', e, stack); @@ -428,10 +485,14 @@ abstract class BuildableElement extends Element { super.unmount(); _dirty = false; // so that we don't get rebuilt even if we're already marked dirty } + + void dependenciesChanged() { + markNeedsBuild(); + } } /// Instantiation of StatelessComponent widgets. -class StatelessComponentElement extends BuildableElement { +class StatelessComponentElement extends BuildableElement { StatelessComponentElement(StatelessComponent widget) : super(widget) { _builder = _widget.build; } @@ -474,6 +535,52 @@ class StatefulComponentElement extends BuildableElement { } } +class ParentDataElement extends StatelessComponentElement { + ParentDataElement(ParentDataWidget widget) : super(widget); + + void update(ParentDataWidget newWidget) { + ParentDataWidget oldWidget = _widget; + super.update(newWidget); + assert(_widget == newWidget); + if (_widget != oldWidget) + _notifyDescendants(); + } + + void _notifyDescendants() { + void notifyChildren(Element child) { + if (child is RenderObjectElement) + child.updateParentData(_widget); + else if (child is! ParentDataElement) + child.visitChildren(notifyChildren); + } + visitChildren(notifyChildren); + } +} + +class InheritedElement extends StatelessComponentElement { + InheritedElement(InheritedWidget widget) : super(widget); + + void update(StatelessComponent newWidget) { + InheritedWidget oldWidget = _widget; + super.update(newWidget); + assert(_widget == newWidget); + if (_widget.updateShouldNotify(oldWidget)) + _notifyDescendants(); + } + + void _notifyDescendants() { + final Type ourRuntimeType = runtimeType; + void notifyChildren(Element child) { + if (child._dependencies != null && + child._dependencies.contains(ourRuntimeType)) + child.dependenciesChanged(); + if (child.runtimeType != ourRuntimeType) + child.visitChildren(notifyChildren); + } + visitChildren(notifyChildren); + } +} + /// Base class for instantiations of RenderObjectWidget subclasses abstract class RenderObjectElement extends Element { RenderObjectElement(T widget) @@ -490,6 +597,16 @@ abstract class RenderObjectElement extends Element return ancestor; } + ParentDataElement _findAncestorParentDataElement() { + Element ancestor = _parent; + while (ancestor != null && ancestor is! RenderObjectElement) { + if (ancestor is ParentDataElement) + return ancestor; + ancestor = ancestor._parent; + } + return null; + } + static Map _registry = new Map(); static Iterable getElementsForRenderObject(RenderObject renderObject) sync* { Element target = _registry[renderObject]; @@ -507,10 +624,13 @@ abstract class RenderObjectElement extends Element assert(_ancestorRenderObjectElement == null); _ancestorRenderObjectElement = _findAncestorRenderObjectElement(); _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, slot); + ParentDataElement parentDataElement = _findAncestorParentDataElement(); + if (parentDataElement != null) + updateParentData(parentDataElement._widget); } void update(T newWidget) { - Widget oldWidget = _widget; + RenderObjectWidget oldWidget = _widget; super.update(newWidget); assert(_widget == newWidget); _widget.updateRenderObject(renderObject, oldWidget); @@ -522,6 +642,14 @@ abstract class RenderObjectElement extends Element _registry.remove(renderObject); } + void updateParentData(ParentDataWidget parentData) { + assert(() { + parentData.debugValidateAncestor(_ancestorRenderObjectElement._widget); + return true; + }); + parentData.applyParentData(renderObject); + } + void detachRenderObject() { if (_ancestorRenderObjectElement != null) { _ancestorRenderObjectElement.removeChildRenderObject(renderObject); @@ -590,7 +718,15 @@ class OneChildRenderObjectElement extends class MultiChildRenderObjectElement extends RenderObjectElement { MultiChildRenderObjectElement(T widget) : super(widget); - // TODO(ianh): implement + void insertChildRenderObject(RenderObject child, dynamic slot) { + // TODO(ianh): implement + assert(false); + } + + void removeChildRenderObject(RenderObject child) { + // TODO(ianh): implement + assert(false); + } } typedef void WidgetsExceptionHandler(String context, dynamic exception, StackTrace stack); diff --git a/packages/unit/test/fn3/render_object_widget_test.dart b/packages/unit/test/fn3/render_object_widget_test.dart index d1bfd81b2e..77ac14f2be 100644 --- a/packages/unit/test/fn3/render_object_widget_test.dart +++ b/packages/unit/test/fn3/render_object_widget_test.dart @@ -11,7 +11,7 @@ final BoxDecoration kBoxDecorationC = new BoxDecoration(); class TestComponent extends StatelessComponent { const TestComponent({ this.child }); final Widget child; - Widget build() => child; + Widget build(BuildContext context) => child; } void main() { diff --git a/packages/unit/test/fn3/stateful_component_test.dart b/packages/unit/test/fn3/stateful_component_test.dart index 3b3588c5fe..b62e79efb7 100644 --- a/packages/unit/test/fn3/stateful_component_test.dart +++ b/packages/unit/test/fn3/stateful_component_test.dart @@ -23,7 +23,7 @@ class TestComponentState extends ComponentState { }); } - Widget build() { + Widget build(BuildContext context) { return _showLeft ? config.left : config.right; } } @@ -34,7 +34,7 @@ final BoxDecoration kBoxDecorationB = new BoxDecoration(); class TestBuildCounter extends StatelessComponent { static int buildCount = 0; - Widget build() { + Widget build(BuildContext context) { ++buildCount; return new DecoratedBox(decoration: kBoxDecorationA); } diff --git a/packages/unit/test/fn3/widget_tester.dart b/packages/unit/test/fn3/widget_tester.dart index 077026c3fc..becb81bf52 100644 --- a/packages/unit/test/fn3/widget_tester.dart +++ b/packages/unit/test/fn3/widget_tester.dart @@ -15,7 +15,7 @@ class RootComponentState extends ComponentState { }); } } - Widget build() => child; + Widget build(BuildContext context) => child; } const Object _rootSlot = const Object();