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); |