blob: d1a556fbc861919a5c6fc5ee5d0007ec7cfac1e8 [file] [log] [blame]
// 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();
}
}