diff --git a/packages/flutter/lib/src/fn3/basic.dart b/packages/flutter/lib/src/fn3/basic.dart index 3d3dbd54a1..bd738dfd76 100644 --- a/packages/flutter/lib/src/fn3/basic.dart +++ b/packages/flutter/lib/src/fn3/basic.dart @@ -2,7 +2,10 @@ // 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/rendering.dart'; +import 'package:sky/services.dart'; import 'package:sky/src/fn3/framework.dart'; export 'package:sky/rendering.dart' show @@ -30,6 +33,42 @@ export 'package:sky/rendering.dart' show Size, ValueChanged; + +// PAINTING NODES + +class Opacity extends OneChildRenderObjectWidget { + Opacity({ Key key, this.opacity, Widget child }) + : super(key: key, child: child) { + assert(opacity >= 0.0 && opacity <= 1.0); + } + + final double opacity; + + RenderOpacity createRenderObject() => new RenderOpacity(opacity: opacity); + + void updateRenderObject(RenderOpacity renderObject) { + renderObject.opacity = opacity; + } +} + +class ColorFilter extends OneChildRenderObjectWidget { + ColorFilter({ Key key, this.color, this.transferMode, Widget child }) + : super(key: key, child: child) { + assert(color != null); + assert(transferMode != null); + } + + final Color color; + final sky.TransferMode transferMode; + + RenderColorFilter createRenderObject() => new RenderColorFilter(color: color, transferMode: transferMode); + + void updateRenderObject(RenderColorFilter renderObject) { + renderObject.color = color; + renderObject.transferMode = transferMode; + } +} + class DecoratedBox extends OneChildRenderObjectWidget { DecoratedBox({ Key key, @@ -44,13 +83,653 @@ class DecoratedBox extends OneChildRenderObjectWidget { final BoxDecoration decoration; final BoxDecorationPosition position; - RenderObject createRenderObject() => new RenderDecoratedBox( - decoration: decoration, - position: position - ); + RenderObject createRenderObject() => new RenderDecoratedBox(decoration: decoration, position: position); void updateRenderObject(RenderDecoratedBox renderObject) { renderObject.decoration = decoration; renderObject.position = position; } } + +// TODO(abarth): CustomPaint + +class ClipRect extends OneChildRenderObjectWidget { + ClipRect({ Key key, Widget child }) + : super(key: key, child: child); + + RenderClipRect createRenderObject() => new RenderClipRect(); + + void updateRenderObject(ClipRect renderObject) { + // Nothing to update + } +} + +class ClipRRect extends OneChildRenderObjectWidget { + ClipRRect({ Key key, this.xRadius, this.yRadius, Widget child }) + : super(key: key, child: child); + + final double xRadius; + final double yRadius; + + RenderClipRRect createRenderObject() => new RenderClipRRect(xRadius: xRadius, yRadius: yRadius); + + void updateRenderObject(RenderClipRRect renderObject) { + renderObject.xRadius = xRadius; + renderObject.yRadius = yRadius; + } +} + +class ClipOval extends OneChildRenderObjectWidget { + ClipOval({ Key key, Widget child }) + : super(key: key, child: child); + + RenderClipOval createRenderObject() => new RenderClipOval(); + + void updateRenderObject(ClipOval renderObject) { + // Nothing to update + } +} + + +// POSITIONING AND SIZING NODES + +class Transform extends OneChildRenderObjectWidget { + Transform({ Key key, this.transform, this.origin, Widget child }) + : super(key: key, child: child) { + assert(transform != null); + } + + final Matrix4 transform; + final Offset origin; + + RenderTransform createRenderObject() => new RenderTransform(transform: transform, origin: origin); + + void updateRenderObject(RenderTransform renderObject) { + renderObject.transform = transform; + renderObject.origin = origin; + } +} + +class Padding extends OneChildRenderObjectWidget { + Padding({ Key key, this.padding, Widget child }) + : super(key: key, child: child) { + assert(padding != null); + } + + final EdgeDims padding; + + RenderPadding createRenderObject() => new RenderPadding(padding: padding); + + void updateRenderObject(RenderPadding renderObject) { + renderObject.padding = padding; + } +} + +class Align extends OneChildRenderObjectWidget { + Align({ + Key key, + this.horizontal: 0.5, + this.vertical: 0.5, + this.shrinkWrap: ShrinkWrap.none, + Widget child + }) : super(key: key, child: child) { + assert(horizontal != null); + assert(vertical != null); + assert(shrinkWrap != null); + } + + final double horizontal; + final double vertical; + final ShrinkWrap shrinkWrap; + + RenderPositionedBox createRenderObject() => new RenderPositionedBox(horizontal: horizontal, vertical: vertical, shrinkWrap: shrinkWrap); + + void updateRenderObject(RenderPositionedBox renderObject) { + renderObject.horizontal = horizontal; + renderObject.vertical = vertical; + renderObject.shrinkWrap = shrinkWrap; + } +} + +class Center extends Align { + Center({ Key key, ShrinkWrap shrinkWrap: ShrinkWrap.none, Widget child }) + : super(key: key, shrinkWrap: shrinkWrap, child: child); +} + +class SizedBox extends OneChildRenderObjectWidget { + SizedBox({ Key key, this.width, this.height, Widget child }) + : super(key: key, child: child); + + final double width; + final double height; + + RenderConstrainedBox createRenderObject() => new RenderConstrainedBox(additionalConstraints: _additionalConstraints); + + BoxConstraints get _additionalConstraints { + BoxConstraints result = const BoxConstraints(); + if (width != null) + result = result.tightenWidth(width); + if (height != null) + result = result.tightenHeight(height); + return result; + } + + void updateRenderObject(RenderConstrainedBox renderObject) { + renderObject.additionalConstraints = _additionalConstraints; + } +} + +class ConstrainedBox extends OneChildRenderObjectWidget { + ConstrainedBox({ Key key, this.constraints, Widget child }) + : super(key: key, child: child) { + assert(constraints != null); + } + + final BoxConstraints constraints; + + RenderConstrainedBox createRenderObject() => new RenderConstrainedBox(additionalConstraints: constraints); + + void updateRenderObject(RenderConstrainedBox renderObject) { + renderObject.additionalConstraints = constraints; + } +} + +class AspectRatio extends OneChildRenderObjectWidget { + AspectRatio({ Key key, this.aspectRatio, Widget child }) + : super(key: key, child: child) { + assert(aspectRatio != null); + } + + final double aspectRatio; + + RenderAspectRatio createRenderObject() => new RenderAspectRatio(aspectRatio: aspectRatio); + + void updateRenderObject(RenderAspectRatio renderObject) { + renderObject.aspectRatio = aspectRatio; + } +} + +class IntrinsicWidth extends OneChildRenderObjectWidget { + IntrinsicWidth({ Key key, this.stepWidth, this.stepHeight, Widget child }) + : super(key: key, child: child); + + final double stepWidth; + final double stepHeight; + + RenderIntrinsicWidth createRenderObject() => new RenderIntrinsicWidth(stepWidth: stepWidth, stepHeight: stepHeight); + + void updateRenderObject(RenderIntrinsicWidth renderObject) { + renderObject.stepWidth = stepWidth; + renderObject.stepHeight = stepHeight; + } +} + +class IntrinsicHeight extends OneChildRenderObjectWidget { + IntrinsicHeight({ Key key, Widget child }) + : super(key: key, child: child); + + RenderIntrinsicHeight createRenderObject() => new RenderIntrinsicHeight(); + + void updateRenderObject(RenderIntrinsicHeight renderObject) { + // Nothing to update + } +} + +class Baseline extends OneChildRenderObjectWidget { + Baseline({ Key key, this.baseline, this.baselineType: TextBaseline.alphabetic, Widget child }) + : super(key: key, child: child) { + assert(baseline != null); + assert(baselineType != null); + } + + final double baseline; // in pixels + final TextBaseline baselineType; + + RenderBaseline createRenderObject() => new RenderBaseline(baseline: baseline, baselineType: baselineType); + + void updateRenderObject(RenderBaseline renderObject) { + renderObject.baseline = baseline; + renderObject.baselineType = baselineType; + } +} + +class Viewport extends OneChildRenderObjectWidget { + Viewport({ + Key key, + this.scrollDirection: ScrollDirection.vertical, + this.scrollOffset: Offset.zero, + Widget child + }) : super(key: key, child: child) { + assert(scrollDirection != null); + assert(scrollOffset != null); + } + + final ScrollDirection scrollDirection; + final Offset scrollOffset; + + RenderViewport createRenderObject() => new RenderViewport(scrollDirection: scrollDirection, scrollOffset: scrollOffset); + + void updateRenderObject(RenderViewport renderObject) { + // Order dependency: RenderViewport validates scrollOffset based on scrollDirection. + renderObject.scrollDirection = scrollDirection; + renderObject.scrollOffset = scrollOffset; + } +} + +class SizeObserver extends OneChildRenderObjectWidget { + SizeObserver({ Key key, this.callback, Widget child }) + : super(key: key, child: child) { + assert(callback != null); + } + + final SizeChangedCallback callback; + + RenderSizeObserver createRenderObject() => new RenderSizeObserver(callback: callback); + + void updateRenderObject(RenderSizeObserver renderObject) { + renderObject.callback = callback; + } + + // TODO(abarth): Null out renderObject.callback during unmount +} + + +// CONVENIENCE CLASS TO COMBINE COMMON PAINTING, POSITIONING, AND SIZING NODES + +class Container extends StatelessComponent { + + Container({ + Key key, + this.child, + this.constraints, + this.decoration, + this.foregroundDecoration, + this.width, + this.height, + this.margin, + this.padding, + this.transform + }) : super(key: key) { + assert(margin == null || margin.isNonNegative); + assert(padding == null || padding.isNonNegative); + } + + final Widget child; + final BoxConstraints constraints; + final BoxDecoration decoration; + final BoxDecoration foregroundDecoration; + final EdgeDims margin; + final EdgeDims padding; + final Matrix4 transform; + final double width; + final double height; + + EdgeDims get _paddingIncludingBorder { + if (decoration == null || decoration.border == null) + return padding; + EdgeDims borderPadding = decoration.border.dimensions; + if (padding == null) + return borderPadding; + return padding + borderPadding; + } + + Widget build() { + Widget current = child; + + if (child == null && (width == null || height == null)) + current = new ConstrainedBox(constraints: const BoxConstraints.expand()); + + EdgeDims effectivePadding = _paddingIncludingBorder; + if (effectivePadding != null) + current = new Padding(padding: effectivePadding, child: current); + + if (decoration != null) + current = new DecoratedBox(decoration: decoration, child: current); + + if (foregroundDecoration != null) { + current = new DecoratedBox( + decoration: foregroundDecoration, + position: BoxDecorationPosition.foreground, + child: current + ); + } + + if (width != null || height != null) { + current = new SizedBox( + width: width, + height: height, + child: current + ); + } + + if (constraints != null) + current = new ConstrainedBox(constraints: constraints, child: current); + + if (margin != null) + current = new Padding(padding: margin, child: current); + + if (transform != null) + current = new Transform(transform: transform, child: current); + + return current; + } + +} + + +// LAYOUT NODES + +class BlockBody extends MultiChildRenderObjectWidget { + BlockBody(List children, { + Key key, + this.direction: BlockDirection.vertical + }) : super(key: key, children: children) { + assert(direction != null); + } + + final BlockDirection direction; + + RenderBlock createRenderObject() => new RenderBlock(direction: direction); + + void updateRenderObject(RenderBlock renderObject) { + renderObject.direction = direction; + } +} + +class Stack extends MultiChildRenderObjectWidget { + Stack(List children, { Key key }) + : super(key: key, children: children); + + RenderStack createRenderObject() => new RenderStack(); + + void updateRenderObject(ClipRect renderObject) { + // Nothing to update + } + + // TODO(abarth): Update parent data +} + +// TODO(abarth): Positioned + +class Grid extends MultiChildRenderObjectWidget { + Grid(List children, { Key key, this.maxChildExtent }) + : super(key: key, children: children) { + assert(maxChildExtent != null); + } + + final double maxChildExtent; + + RenderGrid createRenderObject() => new RenderGrid(maxChildExtent: maxChildExtent); + + void updateRenderObject(RenderGrid renderObject) { + renderObject.maxChildExtent = maxChildExtent; + } +} + +class Flex extends MultiChildRenderObjectWidget { + Flex(List children, { + Key key, + this.direction: FlexDirection.horizontal, + this.justifyContent: FlexJustifyContent.start, + this.alignItems: FlexAlignItems.center, + this.textBaseline + }) : super(key: key, children: children) { + assert(direction != null); + assert(justifyContent != null); + assert(alignItems != null); + } + + final FlexDirection direction; + final FlexJustifyContent justifyContent; + final FlexAlignItems alignItems; + final TextBaseline textBaseline; + + RenderFlex createRenderObject() => new RenderFlex(direction: direction); + + void updateRenderObject(RenderFlex renderObject) { + renderObject.direction = direction; + renderObject.justifyContent = justifyContent; + renderObject.alignItems = alignItems; + renderObject.textBaseline = textBaseline; + } + + // TODO(abarth): Update parent data +} + +class Row extends Flex { + Row(List children, { + Key key, + justifyContent: FlexJustifyContent.start, + alignItems: FlexAlignItems.center, + textBaseline + }) : super(children, key: key, direction: FlexDirection.horizontal, justifyContent: justifyContent, alignItems: alignItems, textBaseline: textBaseline); +} + +class Column extends Flex { + Column(List children, { + Key key, + justifyContent: FlexJustifyContent.start, + alignItems: FlexAlignItems.center, + textBaseline + }) : super(children, key: key, direction: FlexDirection.vertical, justifyContent: justifyContent, alignItems: alignItems, textBaseline: textBaseline); +} + +// TODO(abarth): Flexible + +class Paragraph extends LeafRenderObjectWidget { + Paragraph({ Key key, this.text }) : super(key: key) { + assert(text != null); + } + + final TextSpan text; + + RenderParagraph createRenderObject() => new RenderParagraph(text); + + void updateRenderObject(RenderParagraph renderObject) { + renderObject.text = text; + } +} + +class StyledText extends StatelessComponent { + // elements ::= "string" | [ *] + // Where "string" is text to display and text-style is an instance of + // TextStyle. The text-style applies to all of the elements that follow. + StyledText({ this.elements, Key key }) : super(key: key) { + assert(_toSpan(elements) != null); + } + + final dynamic elements; + + TextSpan _toSpan(dynamic element) { + if (element is String) + return new PlainTextSpan(element); + if (element is Iterable) { + dynamic first = element.first; + if (first is! TextStyle) + throw new ArgumentError("First element of Iterable is a ${first.runtimeType} not a TextStyle"); + return new StyledTextSpan(first, element.skip(1).map(_toSpan).toList()); + } + throw new ArgumentError("Element is ${element.runtimeType} not a String or an Iterable"); + } + + Widget build() { + return new Paragraph(text: _toSpan(elements)); + } +} + +// TODO(abarth): Text + +class Image extends LeafRenderObjectWidget { + Image({ + Key key, + this.image, + this.width, + this.height, + this.colorFilter, + this.fit: ImageFit.scaleDown, + this.repeat: ImageRepeat.noRepeat + }) : super(key: key); + + final sky.Image image; + final double width; + final double height; + final sky.ColorFilter colorFilter; + final ImageFit fit; + final ImageRepeat repeat; + + RenderImage createRenderObject() => new RenderImage( + image: image, + width: width, + height: height, + colorFilter: colorFilter, + fit: fit, + repeat: repeat); + + void updateRenderObject(RenderImage renderObject) { + renderObject.image = image; + renderObject.width = width; + renderObject.height = height; + renderObject.colorFilter = colorFilter; + renderObject.fit = fit; + renderObject.repeat = repeat; + } +} + +class ImageListener extends StatefulComponent { + ImageListener({ + Key key, + this.image, + this.width, + this.height, + this.colorFilter, + this.fit: ImageFit.scaleDown, + this.repeat: ImageRepeat.noRepeat + }) : super(key: key) { + assert(image != null); + } + + final ImageResource image; + final double width; + final double height; + final sky.ColorFilter colorFilter; + final ImageFit fit; + final ImageRepeat repeat; + + ImageListenerState createState() => new ImageListenerState(this); +} + +class ImageListenerState extends ComponentState { + ImageListenerState(ImageListener config) : super(config) { + config.image.addListener(_handleImageChanged); + } + + sky.Image _resolvedImage; + + void _handleImageChanged(sky.Image resolvedImage) { + setState(() { + _resolvedImage = resolvedImage; + }); + } + + void dispose() { + config.image.removeListener(_handleImageChanged); + } + + void didUpdateConfig(ImageListener oldConfig) { + if (config.image != oldConfig.image) { + oldConfig.image.removeListener(_handleImageChanged); + config.image.addListener(_handleImageChanged); + } + } + + Widget build() { + return new Image( + image: _resolvedImage, + width: config.width, + height: config.height, + colorFilter: config.colorFilter, + fit: config.fit, + repeat: config.repeat + ); + } +} + +class NetworkImage extends StatelessComponent { + NetworkImage({ + Key key, + this.src, + this.width, + this.height, + this.colorFilter, + this.fit: ImageFit.scaleDown, + this.repeat: ImageRepeat.noRepeat + }) : super(key: key); + + final String src; + final double width; + final double height; + final sky.ColorFilter colorFilter; + final ImageFit fit; + final ImageRepeat repeat; + + Widget build() { + return new ImageListener( + image: imageCache.load(src), + width: width, + height: height, + colorFilter: colorFilter, + fit: fit, + repeat: repeat + ); + } +} + +class AssetImage extends StatelessComponent { + AssetImage({ + Key key, + this.name, + this.bundle, + this.width, + this.height, + this.colorFilter, + this.fit: ImageFit.scaleDown, + this.repeat: ImageRepeat.noRepeat + }) : super(key: key); + + final String name; + final AssetBundle bundle; + final double width; + final double height; + final sky.ColorFilter colorFilter; + final ImageFit fit; + final ImageRepeat repeat; + + Widget build() { + return new ImageListener( + image: bundle.loadImage(name), + width: width, + height: height, + colorFilter: colorFilter, + fit: fit, + repeat: repeat + ); + } +} + +// TODO(abarth): WidgetToRenderBoxAdapter + + +// EVENT HANDLING + +class IgnorePointer extends OneChildRenderObjectWidget { + IgnorePointer({ Key key, Widget child, this.ignoring: true }) + : super(key: key, child: child); + + final bool ignoring; + + RenderIgnorePointer createElement() => new RenderIgnorePointer(ignoring: ignoring); + + void updateRenderObject(RenderIgnorePointer renderObject) { + renderObject.ignoring = ignoring; + } +}