diff --git a/sky/engine/core/painting/Color.dart b/sky/engine/core/painting/Color.dart index 266145ec18e7082d971a4d4dc9c508d2ba135c78..d78fc536be02bf4b6467aeea92d0ce051c0e2c54 100644 --- a/sky/engine/core/painting/Color.dart +++ b/sky/engine/core/painting/Color.dart @@ -22,6 +22,22 @@ class Color { bool operator ==(other) => other is Color && _value == other._value; + Color withAlpha(int a) { + return new Color.fromARGB(a, red, green, blue); + } + + Color withRed(int r) { + return new Color.fromARGB(alpha, r, green, blue); + } + + Color withGreen(int g) { + return new Color.fromARGB(alpha, red, g, blue); + } + + Color withBlue(int b) { + return new Color.fromARGB(alpha, red, green, b); + } + int get hashCode => _value.hashCode; String toString() => "Color(0x${_value.toRadixString(16).padLeft(8, '0')})"; } diff --git a/sky/sdk/BUILD.gn b/sky/sdk/BUILD.gn index b78bbc25489f2dea6685400eb8e3f66bf728d69c..e13e38d3fe97a36c893f0dcf455f638f2836ac5a 100644 --- a/sky/sdk/BUILD.gn +++ b/sky/sdk/BUILD.gn @@ -50,6 +50,7 @@ dart_pkg("sky") { "lib/theme/typography.dart", "lib/theme/view_configuration.dart", "lib/widgets/animated_component.dart", + "lib/widgets/animated_container.dart", "lib/widgets/animation_builder.dart", "lib/widgets/basic.dart", "lib/widgets/block_viewport.dart", diff --git a/sky/sdk/lib/base/lerp.dart b/sky/sdk/lib/base/lerp.dart index c40934c024b8acf38d0343f66fa9cce123011326..db4b8d9d78c6b8266ada0fd0a5e29a01d34ae1be 100644 --- a/sky/sdk/lib/base/lerp.dart +++ b/sky/sdk/lib/base/lerp.dart @@ -4,9 +4,27 @@ import 'dart:sky'; -num lerpNum(num a, num b, double t) => a + (b - a) * t; +num lerpNum(num a, num b, double t) { + if (a == null && b == null) + return null; + if (a == null) + a = 0.0; + if (b == null) + b = 0.0; + return a + (b - a) * t; +} + +Color _scaleAlpha(Color a, double factor) { + return a.withAlpha((a.alpha * factor).round()); +} Color lerpColor(Color a, Color b, double t) { + if (a == null && b == null) + return null; + if (a == null) + return _scaleAlpha(b, t); + if (b == null) + return _scaleAlpha(b, 1.0 - t); return new Color.fromARGB( lerpNum(a.alpha, b.alpha, t).toInt(), lerpNum(a.red, b.red, t).toInt(), @@ -15,5 +33,11 @@ Color lerpColor(Color a, Color b, double t) { } Offset lerpOffset(Offset a, Offset b, double t) { + if (a == null && b == null) + return null; + if (a == null) + return b * t; + if (b == null) + return a * (1.0 - t); return new Offset(lerpNum(a.dx, b.dx, t), lerpNum(a.dy, b.dy, t)); } diff --git a/sky/sdk/lib/painting/box_painter.dart b/sky/sdk/lib/painting/box_painter.dart index c7c7e4c66614c3730d2b453c802f8f59220ad939..33039ca75c1f2571b3ebb46c6f30e9fb2630325f 100644 --- a/sky/sdk/lib/painting/box_painter.dart +++ b/sky/sdk/lib/painting/box_painter.dart @@ -70,16 +70,48 @@ class BoxShadow { final Offset offset; final double blur; + BoxShadow scale(double factor) { + return new BoxShadow( + color: color, + offset: offset * factor, + blur: blur * factor + ); + } + String toString() => 'BoxShadow($color, $offset, $blur)'; } BoxShadow lerpBoxShadow(BoxShadow a, BoxShadow b, double t) { + if (a == null && b == null) + return null; + if (a == null) + return b.scale(t); + if (b == null) + return a.scale(1.0 - t); return new BoxShadow( color: lerpColor(a.color, b.color, t), offset: lerpOffset(a.offset, b.offset, t), blur: lerpNum(a.blur, b.blur, t)); } +List lerpListBoxShadow(List a, List b, double t) { + if (a == null && b == null) + return null; + if (a == null) + a = new List(); + if (b == null) + b = new List(); + List result = new List(); + int commonLength = math.min(a.length, b.length); + for (int i = 0; i < commonLength; ++i) + result.add(lerpBoxShadow(a[i], b[i], t)); + for (int i = commonLength; i < a.length; ++i) + result.add(a[i].scale(1.0 - t)); + for (int i = commonLength; i < b.length; ++i) + result.add(b[i].scale(t)); + return result; +} + abstract class Gradient { sky.Shader createShader(); } @@ -198,6 +230,19 @@ class BoxDecoration { final Gradient gradient; final Shape shape; + BoxDecoration scale(double factor) { + // TODO(abarth): Scale ALL the things. + return new BoxDecoration( + backgroundColor: lerpColor(null, backgroundColor, factor), + backgroundImage: backgroundImage, + border: border, + borderRadius: lerpNum(null, borderRadius, factor), + boxShadow: lerpListBoxShadow(null, boxShadow, factor), + gradient: gradient, + shape: shape + ); + } + String toString([String prefix = '']) { List result = []; if (backgroundColor != null) @@ -220,6 +265,25 @@ class BoxDecoration { } } +BoxDecoration lerpBoxDecoration(BoxDecoration a, BoxDecoration b, double t) { + if (a == null && b == null) + return null; + if (a == null) + return b.scale(t); + if (b == null) + return a.scale(1.0 - t); + // TODO(abarth): lerp ALL the fields. + return new BoxDecoration( + backgroundColor: lerpColor(a.backgroundColor, b.backgroundColor, t), + backgroundImage: b.backgroundImage, + border: b.border, + borderRadius: lerpNum(a.borderRadius, b.borderRadius, t), + boxShadow: lerpListBoxShadow(a.boxShadow, b.boxShadow, t), + gradient: b.gradient, + shape: b.shape + ); +} + class BoxPainter { BoxPainter(BoxDecoration decoration) : _decoration = decoration { assert(decoration != null); diff --git a/sky/sdk/lib/widgets/animated_container.dart b/sky/sdk/lib/widgets/animated_container.dart new file mode 100644 index 0000000000000000000000000000000000000000..1898664e4622f7a62d0e1dacbb0884258e2c9345 --- /dev/null +++ b/sky/sdk/lib/widgets/animated_container.dart @@ -0,0 +1,206 @@ +// 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:vector_math/vector_math.dart'; + +import 'package:sky/animation/animation_performance.dart'; +import 'package:sky/animation/curves.dart'; +import 'package:sky/base/lerp.dart'; +import 'package:sky/painting/box_painter.dart'; +import 'package:sky/widgets/basic.dart'; +import 'package:sky/widgets/animated_component.dart'; + +class AnimatedBoxConstraintsValue extends AnimatedType { + AnimatedBoxConstraintsValue(BoxConstraints begin, { BoxConstraints end, Curve curve: linear }) + : super(begin, end: end, curve: curve); + + void setFraction(double t) { + // TODO(abarth): We should lerp the BoxConstraints. + value = end; + } +} + +class AnimatedBoxDecorationValue extends AnimatedType { + AnimatedBoxDecorationValue(BoxDecoration begin, { BoxDecoration end, Curve curve: linear }) + : super(begin, end: end, curve: curve); + + void setFraction(double t) { + if (t == 1.0) { + value = end; + return; + } + value = lerpBoxDecoration(begin, end, t); + } +} + +class AnimatedEdgeDimsValue extends AnimatedType { + AnimatedEdgeDimsValue(EdgeDims begin, { EdgeDims end, Curve curve: linear }) + : super(begin, end: end, curve: curve); + + void setFraction(double t) { + if (t == 1.0) { + value = end; + return; + } + value = new EdgeDims( + lerpNum(begin.top, end.top, t), + lerpNum(begin.right, end.right, t), + lerpNum(begin.bottom, end.bottom, t), + lerpNum(begin.bottom, end.left, t) + ); + } +} + +class ImplicitlyAnimatedValue { + final AnimationPerformance performance = new AnimationPerformance(); + final AnimatedType _variable; + + ImplicitlyAnimatedValue(this._variable, Duration duration) { + performance + ..variable = _variable + ..duration = duration; + } + + T get value => _variable.value; + void set value(T newValue) { + _variable.begin = _variable.value; + _variable.end = newValue; + if (_variable.value != _variable.end) { + performance + ..progress = 0.0 + ..play(); + } + } +} + +class AnimatedContainer extends AnimatedComponent { + AnimatedContainer({ + String key, + this.child, + this.duration, + this.constraints, + this.decoration, + this.width, + this.height, + this.margin, + this.padding, + this.transform + }) : super(key: key); + + Widget child; + Duration duration; // TODO(abarth): Support separate durations for each value. + BoxConstraints constraints; + BoxDecoration decoration; + EdgeDims margin; + EdgeDims padding; + Matrix4 transform; + double width; + double height; + + ImplicitlyAnimatedValue _constraints; + ImplicitlyAnimatedValue _decoration; + ImplicitlyAnimatedValue _margin; + ImplicitlyAnimatedValue _padding; + ImplicitlyAnimatedValue _transform; + ImplicitlyAnimatedValue _width; + ImplicitlyAnimatedValue _height; + + void initState() { + _updateFields(); + } + + void syncFields(AnimatedContainer source) { + child = source.child; + constraints = source.constraints; + decoration = source.decoration; + margin = source.margin; + padding = source.padding; + width = source.width; + height = source.height; + _updateFields(); + } + + void _updateFields() { + _updateConstraints(); + _updateDecoration(); + _updateMargin(); + _updatePadding(); + _updateTransform(); + _updateWidth(); + _updateHeight(); + } + + void _updateField(dynamic value, ImplicitlyAnimatedValue animatedValue, Function initField) { + if (animatedValue != null) + animatedValue.value = value; + else if (value != null) + initField(); + } + + void _updateConstraints() { + _updateField(constraints, _constraints, () { + _constraints = new ImplicitlyAnimatedValue(new AnimatedBoxConstraintsValue(constraints), duration); + watch(_constraints.performance); + }); + } + + void _updateDecoration() { + _updateField(decoration, _decoration, () { + _decoration = new ImplicitlyAnimatedValue(new AnimatedBoxDecorationValue(decoration), duration); + watch(_decoration.performance); + }); + } + + void _updateMargin() { + _updateField(margin, _margin, () { + _margin = new ImplicitlyAnimatedValue(new AnimatedEdgeDimsValue(margin), duration); + watch(_margin.performance); + }); + } + + void _updatePadding() { + _updateField(padding, _padding, () { + _padding = new ImplicitlyAnimatedValue(new AnimatedEdgeDimsValue(padding), duration); + watch(_padding.performance); + }); + } + + void _updateTransform() { + _updateField(transform, _transform, () { + _transform = new ImplicitlyAnimatedValue(new AnimatedType(transform), duration); + watch(_transform.performance); + }); + } + + void _updateWidth() { + _updateField(width, _width, () { + _width = new ImplicitlyAnimatedValue(new AnimatedType(width), duration); + watch(_width.performance); + }); + } + + void _updateHeight() { + _updateField(height, _height, () { + _height = new ImplicitlyAnimatedValue( new AnimatedType(height), duration); + watch(_height.performance); + }); + } + + dynamic _getValue(dynamic value, ImplicitlyAnimatedValue animatedValue) { + return animatedValue == null ? value : animatedValue.value; + } + + Widget build() { + return new Container( + child: child, + constraints: _getValue(constraints, _constraints), + decoration: _getValue(decoration, _decoration), + margin: _getValue(margin, _margin), + padding: _getValue(padding, _padding), + transform: _getValue(transform, _transform), + width: _getValue(width, _width), + height: _getValue(height, _height) + ); + } +} diff --git a/sky/sdk/lib/widgets/material.dart b/sky/sdk/lib/widgets/material.dart index 4efcae3436cfe14332b28eed98900ed089a53c63..aa44a68b7d07143c1c59ae8e655bb56cf1537b5e 100644 --- a/sky/sdk/lib/widgets/material.dart +++ b/sky/sdk/lib/widgets/material.dart @@ -2,10 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:sky/animation/animation_performance.dart'; import 'package:sky/painting/box_painter.dart'; -import 'package:sky/widgets/animated_component.dart'; -import 'package:sky/widgets/animation_builder.dart'; +import 'package:sky/theme/shadows.dart'; +import 'package:sky/widgets/animated_container.dart'; import 'package:sky/widgets/basic.dart'; import 'package:sky/widgets/default_text_style.dart'; import 'package:sky/widgets/theme.dart'; @@ -19,11 +18,7 @@ const Map edges = const { MaterialType.button: 2.0, }; -const Duration _kAnimateShadowDuration = const Duration(milliseconds: 100); -const Duration _kAnimateColorDuration = const Duration(milliseconds: 100); - -class Material extends AnimatedComponent { - +class Material extends Component { Material({ String key, this.child, @@ -34,64 +29,37 @@ class Material extends AnimatedComponent { assert(level != null); } - Widget child; - MaterialType type; - int level; - Color color; - - final AnimationBuilder _builder = new AnimationBuilder(); - - void initState() { - _builder - ..shadow = new AnimatedType(level.toDouble()) - ..backgroundColor = _getBackgroundColor(type, color) - ..borderRadius = edges[type] - ..shape = type == MaterialType.circle ? Shape.circle : Shape.rectangle; - watch(_builder.createPerformance( - [_builder.shadow], - duration: _kAnimateShadowDuration - )); - watch(_builder.createPerformance( - [_builder.backgroundColor], - duration: _kAnimateColorDuration - )); - super.initState(); - } + final Widget child; + final MaterialType type; + final int level; + final Color color; - void syncFields(Material source) { - child = source.child; - type = source.type; - level = source.level; - color = source.color; - _builder.updateFields( - shadow: new AnimatedType(level.toDouble()), - backgroundColor: _getBackgroundColor(type, color), - borderRadius: edges[type], - shape: type == MaterialType.circle ? Shape.circle : Shape.rectangle - ); - super.syncFields(source); - } - - AnimatedColor _getBackgroundColor(MaterialType type, Color color) { - if (color == null) { - switch (type) { - case MaterialType.canvas: - color = Theme.of(this).canvasColor; - break; - case MaterialType.card: - color = Theme.of(this).cardColor; - break; - default: - break; - } + Color get _backgroundColor { + if (color != null) + return color; + switch (type) { + case MaterialType.canvas: + return Theme.of(this).canvasColor; + case MaterialType.card: + return Theme.of(this).cardColor; + default: + return null; } - return color == null ? null : new AnimatedColor(color); } Widget build() { - return _builder.build( - new DefaultTextStyle(style: Theme.of(this).text.body1, child: child) + return new AnimatedContainer( + duration: const Duration(milliseconds: 1000), + decoration: new BoxDecoration( + backgroundColor: _backgroundColor, + borderRadius: edges[type], + boxShadow: level == 0 ? null : shadows[level], + shape: type == MaterialType.circle ? Shape.circle : Shape.rectangle + ), + child: new DefaultTextStyle( + style: Theme.of(this).text.body1, + child: child + ) ); } - } diff --git a/sky/tests/examples/sector-expected.txt b/sky/tests/examples/sector-expected.txt index f7e9dbe231f9bff26d1b1c81d3bfb7c1f2afb760..bd6f0751889bf4e8d76d32f33bba1711313472da 100644 --- a/sky/tests/examples/sector-expected.txt +++ b/sky/tests/examples/sector-expected.txt @@ -189,7 +189,7 @@ PAINT FOR FRAME #3 ---------------------------------------------- 3 | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 3 | | | | | | | | | paintChild RenderDecoratedBox at Point(496.75, 89.0) 3 | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -3 | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffd6d6d6))) +3 | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffd6d6d6), drawLooper:true)) 3 | | | | | | | | | | paintChild RenderInkWell at Point(496.75, 89.0) 3 | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 3 | | | | | | | | | | | paintChild RenderPadding at Point(496.75, 89.0) @@ -318,7 +318,7 @@ PAINT FOR FRAME #4 ---------------------------------------------- 4 | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 4 | | | | | | | | | paintChild RenderDecoratedBox at Point(496.75, 89.0) 4 | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -4 | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffd6d6d6))) +4 | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffd6d6d6), drawLooper:true)) 4 | | | | | | | | | | paintChild RenderInkWell at Point(496.75, 89.0) 4 | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 4 | | | | | | | | | | | paintChild RenderPadding at Point(496.75, 89.0) @@ -450,7 +450,7 @@ PAINT FOR FRAME #5 ---------------------------------------------- 5 | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 5 | | | | | | | | | paintChild RenderDecoratedBox at Point(496.75, 89.0) 5 | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -5 | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffd6d6d6))) +5 | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffd6d6d6), drawLooper:true)) 5 | | | | | | | | | | paintChild RenderInkWell at Point(496.75, 89.0) 5 | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 5 | | | | | | | | | | | paintChild RenderPadding at Point(496.75, 89.0) @@ -585,7 +585,7 @@ PAINT FOR FRAME #6 ---------------------------------------------- 6 | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 6 | | | | | | | | | paintChild RenderDecoratedBox at Point(496.75, 89.0) 6 | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -6 | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffd6d6d6))) +6 | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffd6d6d6), drawLooper:true)) 6 | | | | | | | | | | paintChild RenderInkWell at Point(496.75, 89.0) 6 | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 6 | | | | | | | | | | | paintChild RenderPadding at Point(496.75, 89.0) @@ -717,7 +717,7 @@ PAINT FOR FRAME #7 ---------------------------------------------- 7 | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 7 | | | | | | | | | paintChild RenderDecoratedBox at Point(496.75, 89.0) 7 | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -7 | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffd6d6d6))) +7 | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffd6d6d6), drawLooper:true)) 7 | | | | | | | | | | paintChild RenderInkWell at Point(496.75, 89.0) 7 | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 7 | | | | | | | | | | | paintChild RenderPadding at Point(496.75, 89.0) @@ -846,7 +846,7 @@ PAINT FOR FRAME #8 ---------------------------------------------- 8 | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 8 | | | | | | | | | paintChild RenderDecoratedBox at Point(496.75, 89.0) 8 | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 -8 | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffd6d6d6))) +8 | | | | | | | | | | drawRRect(Instance of 'RRect', Paint(color:Color(0xffd6d6d6), drawLooper:true)) 8 | | | | | | | | | | paintChild RenderInkWell at Point(496.75, 89.0) 8 | | | | | | | | | | | TestPaintingCanvas() constructor: 800.0 x 600.0 8 | | | | | | | | | | | paintChild RenderPadding at Point(496.75, 89.0)