| // 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:async'; |
| |
| import 'package:sky/animation/animated_value.dart'; |
| import 'package:sky/animation/direction.dart'; |
| import 'package:sky/animation/forces.dart'; |
| import 'package:sky/animation/timeline.dart'; |
| |
| export 'package:sky/animation/direction.dart' show Direction; |
| |
| enum AnimationStatus { |
| dismissed, // stoped at 0 |
| forward, // animating from 0 => 1 |
| reverse, // animating from 1 => 0 |
| completed, // stopped at 1 |
| } |
| |
| // This class manages a "performance" - a collection of values that change |
| // based on a timeline. For example, a performance may handle an animation |
| // of a menu opening by sliding and fading in (changing Y value and opacity) |
| // over .5 seconds. The performance can move forwards (present) or backwards |
| // (dismiss). A consumer may also take direct control of the timeline by |
| // manipulating |progress|, or |fling| the timeline causing a physics-based |
| // simulation to take over the progression. |
| class AnimationPerformance { |
| AnimationPerformance({AnimatedVariable variable, this.duration}) : |
| _variable = variable { |
| _timeline = new Timeline(_tick); |
| } |
| |
| AnimatedVariable _variable; |
| Duration duration; |
| |
| AnimatedVariable get variable => _variable; |
| void set variable(AnimatedVariable v) { _variable = v; } |
| |
| // Advances from 0 to 1. On each tick, we'll update our variable's values. |
| Timeline _timeline; |
| Timeline get timeline => _timeline; |
| |
| Direction _direction; |
| Direction get direction => _direction; |
| |
| // This controls which curve we use for variables with different curves in |
| // the forward/reverse directions. Curve direction is only reset when we hit |
| // 0 or 1, to avoid discontinuities. |
| Direction _curveDirection; |
| Direction get curveDirection => _curveDirection; |
| |
| AnimationTiming timing; |
| |
| // If non-null, animate with this force instead of a tween animation. |
| Force attachedForce; |
| |
| void addVariable(AnimatedVariable newVariable) { |
| if (variable == null) { |
| variable = newVariable; |
| } else if (variable is AnimatedList) { |
| (variable as AnimatedList).variables.add(newVariable); |
| } else { |
| variable = new AnimatedList([variable, newVariable]); |
| } |
| } |
| |
| double get progress => timeline.value; |
| void set progress(double t) { |
| // TODO(mpcomplete): should this affect |direction|? |
| stop(); |
| timeline.value = t.clamp(0.0, 1.0); |
| _checkStatusChanged(); |
| } |
| |
| double get curvedProgress { |
| return timing != null ? timing.transform(progress, curveDirection) : progress; |
| } |
| |
| bool get isDismissed => status == AnimationStatus.dismissed; |
| bool get isCompleted => status == AnimationStatus.completed; |
| bool get isAnimating => timeline.isAnimating; |
| |
| AnimationStatus get status { |
| if (!isAnimating && progress == 1.0) |
| return AnimationStatus.completed; |
| if (!isAnimating && progress == 0.0) |
| return AnimationStatus.dismissed; |
| return direction == Direction.forward ? |
| AnimationStatus.forward : |
| AnimationStatus.reverse; |
| } |
| |
| void updateVariable(AnimatedVariable variable) { |
| variable.setProgress(curvedProgress, curveDirection); |
| } |
| |
| Future play([Direction direction = Direction.forward]) { |
| _direction = direction; |
| return resume(); |
| } |
| Future forward() => play(Direction.forward); |
| Future reverse() => play(Direction.reverse); |
| Future resume() { |
| if (attachedForce != null) { |
| return fling(velocity: _direction == Direction.forward ? 1.0 : -1.0, |
| force: attachedForce); |
| } |
| return _animateTo(direction == Direction.forward ? 1.0 : 0.0); |
| } |
| |
| void stop() { |
| timeline.stop(); |
| } |
| |
| // Flings the timeline with an optional force (defaults to a critically |
| // damped spring) and initial velocity. If velocity is positive, the |
| // animation will complete, otherwise it will dismiss. |
| Future fling({double velocity: 1.0, Force force}) { |
| if (force == null) |
| force = kDefaultSpringForce; |
| _direction = velocity < 0.0 ? Direction.reverse : Direction.forward; |
| return timeline.fling(force.release(progress, velocity)); |
| } |
| |
| final List<Function> _listeners = new List<Function>(); |
| |
| void addListener(Function listener) { |
| _listeners.add(listener); |
| } |
| |
| void removeListener(Function listener) { |
| _listeners.remove(listener); |
| } |
| |
| void _notifyListeners() { |
| List<Function> localListeners = new List<Function>.from(_listeners); |
| for (Function listener in localListeners) |
| listener(); |
| } |
| |
| final List<Function> _statusListeners = new List<Function>(); |
| |
| void addStatusListener(Function listener) { |
| _statusListeners.add(listener); |
| } |
| |
| void removeStatusListener(Function listener) { |
| _statusListeners.remove(listener); |
| } |
| |
| AnimationStatus _lastStatus = AnimationStatus.dismissed; |
| void _checkStatusChanged() { |
| AnimationStatus currentStatus = status; |
| if (currentStatus != _lastStatus) { |
| List<Function> localListeners = new List<Function>.from(_statusListeners); |
| for (Function listener in localListeners) |
| listener(currentStatus); |
| } |
| _lastStatus = currentStatus; |
| } |
| |
| void _updateCurveDirection() { |
| if (status != _lastStatus) { |
| if (_lastStatus == AnimationStatus.dismissed || _lastStatus == AnimationStatus.completed) |
| _curveDirection = direction; |
| } |
| } |
| |
| Future _animateTo(double target) { |
| Duration remainingDuration = duration * (target - timeline.value).abs(); |
| timeline.stop(); |
| if (remainingDuration == Duration.ZERO) |
| return new Future.value(); |
| return timeline.animateTo(target, duration: remainingDuration); |
| } |
| |
| void _tick(double t) { |
| _updateCurveDirection(); |
| if (variable != null) |
| variable.setProgress(curvedProgress, curveDirection); |
| _notifyListeners(); |
| _checkStatusChanged(); |
| } |
| } |
| |
| // Simple helper class for an animation with a single value. |
| class ValueAnimation<T> extends AnimationPerformance { |
| ValueAnimation({AnimatedValue<T> variable, Duration duration}) : |
| super(variable: variable, duration: duration); |
| |
| AnimatedValue<T> get variable => _variable as AnimatedValue<T>; |
| void set variable(AnimatedValue<T> v) { _variable = v; } |
| |
| T get value => variable.value; |
| } |