// 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:vector_math/vector_math.dart'; import 'package:sky/base/image_resource.dart'; import 'package:sky/mojo/asset_bundle.dart'; import 'package:sky/mojo/net/image_cache.dart' as image_cache; import 'package:sky/painting/text_style.dart'; import 'package:sky/painting/paragraph_painter.dart'; import 'package:sky/rendering/block.dart'; import 'package:sky/rendering/box.dart'; import 'package:sky/rendering/flex.dart'; import 'package:sky/rendering/image.dart'; import 'package:sky/rendering/object.dart'; import 'package:sky/rendering/paragraph.dart'; import 'package:sky/rendering/proxy_box.dart'; import 'package:sky/rendering/shifted_box.dart'; import 'package:sky/rendering/stack.dart'; import 'package:sky/rendering/viewport.dart'; import 'package:sky/widgets/default_text_style.dart'; import 'package:sky/widgets/framework.dart'; export 'package:sky/base/hit_test.dart' show EventDisposition, combineEventDispositions; export 'package:sky/painting/text_style.dart'; export 'package:sky/rendering/block.dart' show BlockDirection; export 'package:sky/rendering/box.dart' show BoxConstraints; export 'package:sky/rendering/flex.dart' show FlexDirection, FlexJustifyContent, FlexAlignItems; export 'package:sky/rendering/object.dart' show Point, Offset, Size, Rect, Color, Paint, Path; export 'package:sky/rendering/proxy_box.dart' show BackgroundImage, BoxDecoration, BoxDecorationPosition, BoxShadow, Border, BorderSide, EdgeDims, Shape; export 'package:sky/rendering/toggleable.dart' show ValueChanged; export 'package:sky/rendering/viewport.dart' show ScrollDirection; export 'package:sky/widgets/framework.dart' show Key, GlobalKey, Widget, Component, StatefulComponent, App, runApp, Listener, ParentDataNode; // PAINTING NODES class Opacity extends OneChildRenderObjectWrapper { Opacity({ Key key, this.opacity, Widget child }) : super(key: key, child: child); final double opacity; RenderOpacity createNode() => new RenderOpacity(opacity: opacity); RenderOpacity get renderObject => super.renderObject; void syncRenderObject(Opacity old) { super.syncRenderObject(old); renderObject.opacity = opacity; } } class ColorFilter extends OneChildRenderObjectWrapper { ColorFilter({ Key key, this.color, this.transferMode, Widget child }) : super(key: key, child: child); final Color color; final sky.TransferMode transferMode; RenderColorFilter createNode() => new RenderColorFilter(color: color, transferMode: transferMode); RenderColorFilter get renderObject => super.renderObject; void syncRenderObject(ColorFilter old) { super.syncRenderObject(old); renderObject.color = color; renderObject.transferMode = transferMode; } } class DecoratedBox extends OneChildRenderObjectWrapper { DecoratedBox({ Key key, this.decoration, this.position: BoxDecorationPosition.background, Widget child }) : super(key: key, child: child); final BoxDecoration decoration; final BoxDecorationPosition position; RenderDecoratedBox createNode() => new RenderDecoratedBox(decoration: decoration, position: position); RenderDecoratedBox get renderObject => super.renderObject; void syncRenderObject(DecoratedBox old) { super.syncRenderObject(old); renderObject.decoration = decoration; renderObject.position = position; } } class CustomPaint extends OneChildRenderObjectWrapper { CustomPaint({ Key key, this.callback, this.token, Widget child }) : super(key: key, child: child); final CustomPaintCallback callback; final dynamic token; // set this to be repainted automatically when the token changes RenderCustomPaint createNode() => new RenderCustomPaint(callback: callback); RenderCustomPaint get renderObject => super.renderObject; void syncRenderObject(CustomPaint old) { super.syncRenderObject(old); if (old != null && old.token != token) renderObject.markNeedsPaint(); renderObject.callback = callback; } void remove() { renderObject.callback = null; super.remove(); } } class ClipRect extends OneChildRenderObjectWrapper { ClipRect({ Key key, Widget child }) : super(key: key, child: child); RenderClipRect createNode() => new RenderClipRect(); RenderClipRect get renderObject => super.renderObject; // Nothing to sync, so we don't implement syncRenderObject() } class ClipRRect extends OneChildRenderObjectWrapper { ClipRRect({ Key key, this.xRadius, this.yRadius, Widget child }) : super(key: key, child: child); final double xRadius; final double yRadius; RenderClipRRect createNode() => new RenderClipRRect(xRadius: xRadius, yRadius: yRadius); RenderClipRRect get renderObject => super.renderObject; void syncRenderObject(ClipRRect old) { super.syncRenderObject(old); renderObject.xRadius = xRadius; renderObject.yRadius = yRadius; } } class ClipOval extends OneChildRenderObjectWrapper { ClipOval({ Key key, Widget child }) : super(key: key, child: child); RenderClipOval createNode() => new RenderClipOval(); RenderClipOval get renderObject => super.renderObject; // Nothing to sync, so we don't implement syncRenderObject() } // POSITIONING AND SIZING NODES class Transform extends OneChildRenderObjectWrapper { Transform({ Key key, this.transform, Widget child }) : super(key: key, child: child); final Matrix4 transform; RenderTransform createNode() => new RenderTransform(transform: transform); RenderTransform get renderObject => super.renderObject; void syncRenderObject(Transform old) { super.syncRenderObject(old); renderObject.transform = transform; } } class Padding extends OneChildRenderObjectWrapper { Padding({ Key key, this.padding, Widget child }) : super(key: key, child: child); final EdgeDims padding; RenderPadding createNode() => new RenderPadding(padding: padding); RenderPadding get renderObject => super.renderObject; void syncRenderObject(Padding old) { super.syncRenderObject(old); renderObject.padding = padding; } } class Center extends OneChildRenderObjectWrapper { Center({ Key key, Widget child }) : super(key: key, child: child); RenderPositionedBox createNode() => new RenderPositionedBox(); RenderPositionedBox get renderObject => super.renderObject; // Nothing to sync, so we don't implement syncRenderObject() } class SizedBox extends OneChildRenderObjectWrapper { SizedBox({ Key key, this.width, this.height, Widget child }) : super(key: key, child: child); final double width; final double height; RenderConstrainedBox createNode() => new RenderConstrainedBox(additionalConstraints: _additionalConstraints()); RenderConstrainedBox get renderObject => super.renderObject; BoxConstraints _additionalConstraints() { BoxConstraints result = const BoxConstraints(); if (width != null) result = result.applyWidth(width); if (height != null) result = result.applyHeight(height); return result; } void syncRenderObject(SizedBox old) { super.syncRenderObject(old); renderObject.additionalConstraints = _additionalConstraints(); } } class ConstrainedBox extends OneChildRenderObjectWrapper { ConstrainedBox({ Key key, this.constraints, Widget child }) : super(key: key, child: child); final BoxConstraints constraints; RenderConstrainedBox createNode() => new RenderConstrainedBox(additionalConstraints: constraints); RenderConstrainedBox get renderObject => super.renderObject; void syncRenderObject(ConstrainedBox old) { super.syncRenderObject(old); renderObject.additionalConstraints = constraints; } } class AspectRatio extends OneChildRenderObjectWrapper { AspectRatio({ Key key, this.aspectRatio, Widget child }) : super(key: key, child: child); final double aspectRatio; RenderAspectRatio createNode() => new RenderAspectRatio(aspectRatio: aspectRatio); RenderAspectRatio get renderObject => super.renderObject; void syncRenderObject(AspectRatio old) { super.syncRenderObject(old); renderObject.aspectRatio = aspectRatio; } } class ShrinkWrapWidth extends OneChildRenderObjectWrapper { ShrinkWrapWidth({ Key key, this.stepWidth, this.stepHeight, Widget child }) : super(key: key, child: child); final double stepWidth; final double stepHeight; RenderShrinkWrapWidth createNode() => new RenderShrinkWrapWidth(); RenderShrinkWrapWidth get renderObject => super.renderObject; void syncRenderObject(ShrinkWrapWidth old) { super.syncRenderObject(old); renderObject.stepWidth = stepWidth; renderObject.stepHeight = stepHeight; } } class Baseline extends OneChildRenderObjectWrapper { Baseline({ Key key, this.baseline, this.baselineType: TextBaseline.alphabetic, Widget child }) : super(key: key, child: child); final double baseline; // in pixels final TextBaseline baselineType; RenderBaseline createNode() => new RenderBaseline(baseline: baseline, baselineType: baselineType); RenderBaseline get renderObject => super.renderObject; void syncRenderObject(Baseline old) { super.syncRenderObject(old); renderObject.baseline = baseline; renderObject.baselineType = baselineType; } } class Viewport extends OneChildRenderObjectWrapper { Viewport({ Key key, this.scrollDirection: ScrollDirection.vertical, this.scrollOffset: Offset.zero, Widget child }) : super(key: key, child: child); final ScrollDirection scrollDirection; final Offset scrollOffset; RenderViewport createNode() => new RenderViewport(scrollDirection: scrollDirection, scrollOffset: scrollOffset); RenderViewport get renderObject => super.renderObject; void syncRenderObject(Viewport old) { super.syncRenderObject(old); // Order dependency: RenderViewport validates scrollOffset based on scrollDirection. renderObject.scrollDirection = scrollDirection; renderObject.scrollOffset = scrollOffset; } } class SizeObserver extends OneChildRenderObjectWrapper { SizeObserver({ Key key, this.callback, Widget child }) : super(key: key, child: child); final SizeChangedCallback callback; RenderSizeObserver createNode() => new RenderSizeObserver(callback: callback); RenderSizeObserver get renderObject => super.renderObject; void syncRenderObject(SizeObserver old) { super.syncRenderObject(old); renderObject.callback = callback; } void remove() { renderObject.callback = null; super.remove(); } } // CONVENIENCE CLASS TO COMBINE COMMON PAINTING, POSITIONING, AND SIZING NODES class Container extends Component { Container({ Key key, this.child, this.constraints, this.decoration, this.foregroundDecoration, this.width, this.height, this.margin, this.padding, this.transform }) : super(key: key); 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: 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 Block extends MultiChildRenderObjectWrapper { Block(List children, { Key key, this.direction: BlockDirection.vertical }) : super(key: key, children: children); final BlockDirection direction; RenderBlock createNode() => new RenderBlock(direction: direction); RenderBlock get renderObject => super.renderObject; void syncRenderObject(Widget old) { super.syncRenderObject(old); renderObject.direction = direction; } } class Stack extends MultiChildRenderObjectWrapper { Stack(List children, { Key key }) : super(key: key, children: children); RenderStack createNode() => new RenderStack(); RenderStack get renderObject => super.renderObject; } class Positioned extends ParentDataNode { Positioned({ Key key, Widget child, double top, double right, double bottom, double left }) : super(child, new StackParentData()..top = top ..right = right ..bottom = bottom ..left = left, key: key); } class Flex extends MultiChildRenderObjectWrapper { Flex(List children, { Key key, this.direction: FlexDirection.horizontal, this.justifyContent: FlexJustifyContent.start, this.alignItems: FlexAlignItems.center, this.textBaseline }) : super(key: key, children: children); final FlexDirection direction; final FlexJustifyContent justifyContent; final FlexAlignItems alignItems; final TextBaseline textBaseline; RenderFlex createNode() => new RenderFlex(direction: direction); RenderFlex get renderObject => super.renderObject; void syncRenderObject(Widget old) { super.syncRenderObject(old); renderObject.direction = direction; renderObject.justifyContent = justifyContent; renderObject.alignItems = alignItems; renderObject.textBaseline = textBaseline; } } class Flexible extends ParentDataNode { Flexible({ Key key, int flex: 1, Widget child }) : super(child, new FlexBoxParentData()..flex = flex, key: key); } class Paragraph extends LeafRenderObjectWrapper { Paragraph({ Key key, this.text }) : super(key: key); final TextSpan text; RenderParagraph createNode() => new RenderParagraph(text); RenderParagraph get renderObject => super.renderObject; void syncRenderObject(Widget old) { super.syncRenderObject(old); renderObject.text = text; } } class StyledText extends Component { // 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)); } } class Text extends Component { Text(this.data, { Key key, TextStyle this.style }) : super(key: key); final String data; final TextStyle style; Widget build() { TextSpan text = new PlainTextSpan(data); TextStyle defaultStyle = DefaultTextStyle.of(this); 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 LeafRenderObjectWrapper { 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 createNode() => new RenderImage( image: image, width: width, height: height, colorFilter: colorFilter, fit: fit, repeat: repeat); RenderImage get renderObject => super.renderObject; void syncRenderObject(Widget old) { super.syncRenderObject(old); 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); ImageResource image; double width; double height; sky.ColorFilter colorFilter; ImageFit fit; ImageRepeat repeat; sky.Image _resolvedImage; void _handleImageChanged(sky.Image resolvedImage) { if (!mounted) return; setState(() { _resolvedImage = resolvedImage; }); } void didMount() { super.didMount(); image.addListener(_handleImageChanged); } void didUnmount() { super.didUnmount(); image.removeListener(_handleImageChanged); } void syncFields(ImageListener source) { final bool needToUpdateListeners = (image != source.image) && mounted; if (needToUpdateListeners) image.removeListener(_handleImageChanged); image = source.image; width = source.width; height = source.height; colorFilter = source.colorFilter; fit = source.fit; repeat = source.repeat; if (needToUpdateListeners) image.addListener(_handleImageChanged); } Widget build() { return new Image( image: _resolvedImage, width: width, height: height, colorFilter: colorFilter, fit: fit, repeat: repeat ); } } class NetworkImage extends Component { 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: image_cache.load(src), width: width, height: height, colorFilter: colorFilter, fit: fit, repeat: repeat ); } } class AssetImage extends Component { 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 ); } } class WidgetToRenderBoxAdapter extends LeafRenderObjectWrapper { WidgetToRenderBoxAdapter(RenderBox renderBox) : renderBox = renderBox, super(key: new Key.fromObjectIdentity(renderBox)); final RenderBox renderBox; RenderBox createNode() => this.renderBox; RenderBox get renderObject => super.renderObject; void syncRenderObject(Widget old) { super.syncRenderObject(old); if (old != null) { assert(old is WidgetToRenderBoxAdapter); assert(renderObject == old.renderObject); } } void remove() { RenderObjectWrapper ancestor = findAncestorRenderObjectWrapper(); assert(ancestor is RenderObjectWrapper); assert(ancestor.renderObject == renderObject.parent); ancestor.detachChildRoot(this); super.remove(); } } // EVENT HANDLING class IgnorePointer extends OneChildRenderObjectWrapper { IgnorePointer({ Key key, Widget child }) : super(key: key, child: child); RenderIgnorePointer createNode() => new RenderIgnorePointer(); RenderIgnorePointer get renderObject => super.renderObject; }