| // 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/forces.dart'; |
| import 'package:sky/animation/timeline.dart'; |
| |
| export 'package:sky/animation/forces.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({this.variable, this.duration}) { |
| _timeline = new Timeline(_tick); |
| } |
| |
| AnimatedVariable variable; |
| Duration duration; |
| |
| // 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; |
| |
| // 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(); |
| } |
| |
| 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; |
| } |
| |
| 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(_direction, force: attachedForce); |
| return _animateTo(direction == Direction.forward ? 1.0 : 0.0); |
| } |
| |
| void stop() { |
| timeline.stop(); |
| } |
| |
| // Flings the timeline in the given direction with an optional force |
| // (defaults to a critically damped spring) and initial velocity. |
| Future fling(Direction direction, {double velocity: 0.0, Force force}) { |
| if (force == null) |
| force = kDefaultSpringForce; |
| _direction = direction; |
| return timeline.fling(force.release(progress, velocity, _direction)); |
| } |
| |
| 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; |
| } |
| |
| 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) { |
| if (variable != null) |
| variable.setProgress(t); |
| _notifyListeners(); |
| _checkStatusChanged(); |
| } |
| } |