| Index: sky/examples/fn/widgets/animationgenerator.dart
|
| diff --git a/sky/examples/fn/widgets/animationgenerator.dart b/sky/examples/fn/widgets/animationgenerator.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a7409345f2ef4067a7c99e7e3b919d92addb7908
|
| --- /dev/null
|
| +++ b/sky/examples/fn/widgets/animationgenerator.dart
|
| @@ -0,0 +1,139 @@
|
| +part of widgets;
|
| +
|
| +class FrameGenerator {
|
| +
|
| + Function onDone;
|
| + StreamController _controller;
|
| +
|
| + Stream<double> get onTick => _controller.stream;
|
| +
|
| + int _animationId = 0;
|
| + bool _cancelled = false;
|
| +
|
| + FrameGenerator({this.onDone}) {
|
| + _controller = new StreamController(
|
| + sync: true,
|
| + onListen: _scheduleTick,
|
| + onCancel: cancel);
|
| + }
|
| +
|
| + void cancel() {
|
| + if (_cancelled) {
|
| + return;
|
| + }
|
| + if (_animationId != 0) {
|
| + sky.window.cancelAnimationFrame(_animationId);
|
| + }
|
| + _animationId = 0;
|
| + _cancelled = true;
|
| + if (onDone != null) {
|
| + onDone();
|
| + }
|
| + }
|
| +
|
| + void _scheduleTick() {
|
| + assert(_animationId == 0);
|
| + _animationId = sky.window.requestAnimationFrame(_tick);
|
| + }
|
| +
|
| + void _tick(double timeStamp) {
|
| + _animationId = 0;
|
| + _controller.add(timeStamp);
|
| + if (!_cancelled) {
|
| + _scheduleTick();
|
| + }
|
| + }
|
| +}
|
| +
|
| +const double _kFrameTime = 1000 / 60;
|
| +
|
| +class AnimationGenerator extends FrameGenerator {
|
| +
|
| + Stream<double> get onTick => _stream;
|
| + final double duration;
|
| + final double begin;
|
| + final double end;
|
| + final Curve curve;
|
| + Stream<double> _stream;
|
| +
|
| + AnimationGenerator(this.duration, {
|
| + this.begin: 0.0,
|
| + this.end: 1.0,
|
| + this.curve: linear,
|
| + Function onDone
|
| + }):super(onDone: onDone) {
|
| + double startTime = 0.0;
|
| + double targetTime = 0.0;
|
| + bool done = false;
|
| + _stream = super.onTick.map((timeStamp) {
|
| + if (startTime == 0.0) {
|
| + startTime = timeStamp;
|
| + targetTime = startTime + duration;
|
| + }
|
| +
|
| + // Clamp the final frame to target time so we terminate the series with
|
| + // 1.0 exactly.
|
| + if ((timeStamp - targetTime).abs() <= _kFrameTime) {
|
| + return 1.0;
|
| + }
|
| +
|
| + return (timeStamp - startTime) / duration;
|
| + })
|
| + .takeWhile((t) => t <= 1.0)
|
| + .map((t) => begin + (end - begin) * curve.transform(t));
|
| + }
|
| +}
|
| +
|
| +double _evaluateCubic(double a, double b, double m) {
|
| + // TODO(abarth): Would Math.pow be faster?
|
| + return 3 * a * (1 - m) * (1 - m) * m + 3 * b * (1 - m) * m * m + m * m * m;
|
| +}
|
| +
|
| +const double _kCubicErrorBound = 0.001;
|
| +
|
| +abstract class Curve {
|
| + double transform(double t);
|
| +}
|
| +
|
| +class Linear implements Curve {
|
| + const Linear();
|
| +
|
| + double transform(double t) {
|
| + return t;
|
| + }
|
| +}
|
| +
|
| +class Cubic implements Curve {
|
| + final double a;
|
| + final double b;
|
| + final double c;
|
| + final double d;
|
| +
|
| + const Cubic(this.a, this.b, this.c, this.d);
|
| +
|
| + double transform(double t) {
|
| + if (t == 0.0 || t == 1.0)
|
| + return t;
|
| +
|
| + double start = 0.0;
|
| + double end = 1.0;
|
| + while (true) {
|
| + double midpoint = (start + end) / 2;
|
| + double estimate = _evaluateCubic(a, c, midpoint);
|
| +
|
| + if ((t - estimate).abs() < _kCubicErrorBound)
|
| + return _evaluateCubic(b, d, midpoint);
|
| +
|
| + if (estimate < t)
|
| + start = midpoint;
|
| + else
|
| + end = midpoint;
|
| + }
|
| + }
|
| +}
|
| +
|
| +const Linear linear = const Linear();
|
| +const Cubic ease = const Cubic(0.25, 0.1, 0.25, 1.0);
|
| +const Cubic easeIn = const Cubic(0.42, 0.0, 1.0, 1.0);
|
| +const Cubic easeOut = const Cubic(0.0, 0.0, 0.58, 1.0);
|
| +const Cubic easeInOut = const Cubic(0.42, 0.0, 0.58, 1.0);
|
|
|