Commit 05c9ca91 authored by Adam Barth's avatar Adam Barth

Add dartdoc for the animation library

parent 6fc343a0
...@@ -9,14 +9,22 @@ import 'package:sky/src/animation/scheduler.dart'; ...@@ -9,14 +9,22 @@ import 'package:sky/src/animation/scheduler.dart';
const double _kSecondsPerMillisecond = 1000.0; const double _kSecondsPerMillisecond = 1000.0;
// TODO(abarth): Change from double to Duration.
typedef _TickerCallback(double timeStamp);
/// Calls its callback once per animation frame
class Ticker { class Ticker {
Ticker(Function onTick) : _onTick = onTick; /// Constructs a ticker that will call onTick once per frame while running
Ticker(_TickerCallback onTick) : _onTick = onTick;
final Function _onTick; final _TickerCallback _onTick;
Completer _completer; Completer _completer;
int _animationId; int _animationId;
/// Start calling onTick once per animation frame
///
/// The returned future resolves once the ticker stops ticking.
Future start() { Future start() {
assert(!isTicking); assert(!isTicking);
_completer = new Completer(); _completer = new Completer();
...@@ -24,6 +32,9 @@ class Ticker { ...@@ -24,6 +32,9 @@ class Ticker {
return _completer.future; return _completer.future;
} }
/// Stop calling onTick
///
/// Causes the future returned by [start] to resolve.
void stop() { void stop() {
if (!isTicking) if (!isTicking)
return; return;
...@@ -42,6 +53,7 @@ class Ticker { ...@@ -42,6 +53,7 @@ class Ticker {
localCompleter.complete(); localCompleter.complete();
} }
/// Whether this ticker has scheduled a call to onTick
bool get isTicking => _completer != null; bool get isTicking => _completer != null;
void _tick(double timeStamp) { void _tick(double timeStamp) {
...@@ -63,6 +75,7 @@ class Ticker { ...@@ -63,6 +75,7 @@ class Ticker {
} }
} }
/// Ticks a simulation once per frame
class AnimatedSimulation { class AnimatedSimulation {
AnimatedSimulation(Function onTick) : _onTick = onTick { AnimatedSimulation(Function onTick) : _onTick = onTick {
...@@ -76,6 +89,7 @@ class AnimatedSimulation { ...@@ -76,6 +89,7 @@ class AnimatedSimulation {
double _startTime; double _startTime;
double _value = 0.0; double _value = 0.0;
/// The current value of the simulation
double get value => _value; double get value => _value;
void set value(double newValue) { void set value(double newValue) {
assert(!_ticker.isTicking); assert(!_ticker.isTicking);
...@@ -83,6 +97,9 @@ class AnimatedSimulation { ...@@ -83,6 +97,9 @@ class AnimatedSimulation {
_onTick(_value); _onTick(_value);
} }
/// Start ticking the given simulation once per frame
///
/// Returns a future that resolves when the simulation stops ticking.
Future start(Simulation simulation) { Future start(Simulation simulation) {
assert(simulation != null); assert(simulation != null);
assert(!_ticker.isTicking); assert(!_ticker.isTicking);
...@@ -92,12 +109,14 @@ class AnimatedSimulation { ...@@ -92,12 +109,14 @@ class AnimatedSimulation {
return _ticker.start(); return _ticker.start();
} }
/// Stop ticking the current simulation
void stop() { void stop() {
_simulation = null; _simulation = null;
_startTime = null; _startTime = null;
_ticker.stop(); _ticker.stop();
} }
/// Whether this object is currently ticking a simulation
bool get isAnimating => _ticker.isTicking; bool get isAnimating => _ticker.isTicking;
void _tick(double timeStamp) { void _tick(double timeStamp) {
......
...@@ -6,24 +6,48 @@ import "dart:sky"; ...@@ -6,24 +6,48 @@ import "dart:sky";
import 'package:sky/src/animation/curves.dart'; import 'package:sky/src/animation/curves.dart';
/// The direction in which an animation is running
enum Direction { enum Direction {
/// The animation is running from beginning to end
forward, forward,
/// The animation is running backwards, from end to beginning
reverse reverse
} }
/// A variable that changes as an animation progresses
abstract class AnimatedVariable { abstract class AnimatedVariable {
/// Update the variable to a given time in an animation that is running in the given direction
void setProgress(double t, Direction direction); void setProgress(double t, Direction direction);
String toString(); String toString();
} }
///
class AnimationTiming { class AnimationTiming {
AnimationTiming({this.interval, this.reverseInterval, this.curve, this.reverseCurve}); AnimationTiming({
this.interval,
this.reverseInterval,
this.curve,
this.reverseCurve
});
/// The interval during which this timing is active in the forward direction
Interval interval; Interval interval;
/// The interval during which this timing is active in the reverse direction
///
/// If this field is null, the timing defaules to using [interval] in both directions.
Interval reverseInterval; Interval reverseInterval;
/// The curve that this timing applies to the animation clock in the forward direction
Curve curve; Curve curve;
/// The curve that this timing applies to the animation clock in the reverse direction
///
/// If this field is null, the timing defaules to using [curve] in both directions.
Curve reverseCurve; Curve reverseCurve;
/// Applies this timing to the given animation clock value in the given direction
double transform(double t, Direction direction) { double transform(double t, Direction direction) {
Interval interval = _getInterval(direction); Interval interval = _getInterval(direction);
if (interval != null) if (interval != null)
...@@ -49,18 +73,26 @@ class AnimationTiming { ...@@ -49,18 +73,26 @@ class AnimationTiming {
} }
} }
/// An animated variable with a concrete type
class AnimatedValue<T extends dynamic> extends AnimationTiming implements AnimatedVariable { class AnimatedValue<T extends dynamic> extends AnimationTiming implements AnimatedVariable {
AnimatedValue(this.begin, { this.end, Interval interval, Curve curve, Curve reverseCurve }) AnimatedValue(this.begin, { this.end, Interval interval, Curve curve, Curve reverseCurve })
: super(interval: interval, curve: curve, reverseCurve: reverseCurve) { : super(interval: interval, curve: curve, reverseCurve: reverseCurve) {
value = begin; value = begin;
} }
/// The current value of this variable
T value; T value;
/// The value this variable has at the beginning of the animation
T begin; T begin;
/// The value this variable has at the end of the animation
T end; T end;
/// Returns the value this variable has at the given animation clock value
T lerp(double t) => begin + (end - begin) * t; T lerp(double t) => begin + (end - begin) * t;
/// Updates the value of this variable according to the given animation clock value and direction
void setProgress(double t, Direction direction) { void setProgress(double t, Direction direction) {
if (end != null) { if (end != null) {
t = transform(t, direction); t = transform(t, direction);
...@@ -71,12 +103,15 @@ class AnimatedValue<T extends dynamic> extends AnimationTiming implements Animat ...@@ -71,12 +103,15 @@ class AnimatedValue<T extends dynamic> extends AnimationTiming implements Animat
String toString() => 'AnimatedValue(begin=$begin, end=$end, value=$value)'; String toString() => 'AnimatedValue(begin=$begin, end=$end, value=$value)';
} }
/// A list of animated variables
class AnimatedList extends AnimationTiming implements AnimatedVariable { class AnimatedList extends AnimationTiming implements AnimatedVariable {
/// The list of variables contained in the list
List<AnimatedVariable> variables; List<AnimatedVariable> variables;
AnimatedList(this.variables, { Interval interval, Curve curve, Curve reverseCurve }) AnimatedList(this.variables, { Interval interval, Curve curve, Curve reverseCurve })
: super(interval: interval, curve: curve, reverseCurve: reverseCurve); : super(interval: interval, curve: curve, reverseCurve: reverseCurve);
// Updates the value of all the variables in the list according to the given animation clock value and direction
void setProgress(double t, Direction direction) { void setProgress(double t, Direction direction) {
double adjustedTime = transform(t, direction); double adjustedTime = transform(t, direction);
for (AnimatedVariable variable in variables) for (AnimatedVariable variable in variables)
...@@ -86,6 +121,10 @@ class AnimatedList extends AnimationTiming implements AnimatedVariable { ...@@ -86,6 +121,10 @@ class AnimatedList extends AnimationTiming implements AnimatedVariable {
String toString() => 'AnimatedList([$variables])'; String toString() => 'AnimatedList([$variables])';
} }
/// An animated variable containing a color
///
/// This class specializes the interpolation of AnimatedValue<Color> to be
/// appropriate for colors.
class AnimatedColorValue extends AnimatedValue<Color> { class AnimatedColorValue extends AnimatedValue<Color> {
AnimatedColorValue(Color begin, { Color end, Curve curve }) AnimatedColorValue(Color begin, { Color end, Curve curve })
: super(begin, end: end, curve: curve); : super(begin, end: end, curve: curve);
...@@ -93,6 +132,10 @@ class AnimatedColorValue extends AnimatedValue<Color> { ...@@ -93,6 +132,10 @@ class AnimatedColorValue extends AnimatedValue<Color> {
Color lerp(double t) => Color.lerp(begin, end, t); Color lerp(double t) => Color.lerp(begin, end, t);
} }
/// An animated variable containing a rectangle
///
/// This class specializes the interpolation of AnimatedValue<Rect> to be
/// appropriate for rectangles.
class AnimatedRect extends AnimatedValue<Rect> { class AnimatedRect extends AnimatedValue<Rect> {
AnimatedRect(Rect begin, { Rect end, Curve curve }) AnimatedRect(Rect begin, { Rect end, Curve curve })
: super(begin, end: end, curve: curve); : super(begin, end: end, curve: curve);
......
...@@ -8,50 +8,62 @@ import 'package:sky/src/animation/animated_value.dart'; ...@@ -8,50 +8,62 @@ import 'package:sky/src/animation/animated_value.dart';
import 'package:sky/src/animation/forces.dart'; import 'package:sky/src/animation/forces.dart';
import 'package:sky/src/animation/timeline.dart'; import 'package:sky/src/animation/timeline.dart';
/// The status of an animation
enum AnimationStatus { enum AnimationStatus {
dismissed, // stoped at 0 /// The animation is stopped at the beginning
forward, // animating from 0 => 1 dismissed,
reverse, // animating from 1 => 0
completed, // stopped at 1 /// The animation is running from beginning to end
forward,
/// The animation is running backwards, from end to beginning
reverse,
/// The animation is stopped at the end
completed,
} }
// This class manages a "performance" - a collection of values that change /// A collection of values that animated based on a timeline
// 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) /// For example, a performance may handle an animation of a menu opening by
// over .5 seconds. The performance can move forwards (present) or backwards /// sliding and fading in (changing Y value and opacity) over .5 seconds. The
// (dismiss). A consumer may also take direct control of the timeline by /// performance can move forwards (present) or backwards (dismiss). A consumer
// manipulating |progress|, or |fling| the timeline causing a physics-based /// may also take direct control of the timeline by manipulating [progress], or
// simulation to take over the progression. /// [fling] the timeline causing a physics-based simulation to take over the
/// progression.
class AnimationPerformance { class AnimationPerformance {
AnimationPerformance({AnimatedVariable variable, this.duration}) : AnimationPerformance({AnimatedVariable variable, this.duration}) :
_variable = variable { _variable = variable {
_timeline = new Timeline(_tick); _timeline = new Timeline(_tick);
} }
AnimatedVariable _variable; /// The length of time this performance should last
Duration duration; Duration duration;
/// The variable being updated by this performance
AnimatedVariable get variable => _variable; AnimatedVariable get variable => _variable;
void set variable(AnimatedVariable v) { _variable = v; } void set variable(AnimatedVariable variable) { _variable = variable; }
AnimatedVariable _variable;
// Advances from 0 to 1. On each tick, we'll update our variable's values.
Timeline _timeline; Timeline _timeline;
Timeline get timeline => _timeline;
Direction _direction; Direction _direction;
Direction get direction => _direction;
// This controls which curve we use for variables with different curves in /// The direction used to select the current curve
// the forward/reverse directions. Curve direction is only reset when we hit ///
// 0 or 1, to avoid discontinuities. /// Curve direction is only reset when we hit the beginning or the end of the
/// timeline to avoid discontinuities in the value of the variable.
Direction _curveDirection; Direction _curveDirection;
Direction get curveDirection => _curveDirection;
/// If non-null, animate with this timing instead of a linear timing
AnimationTiming timing; AnimationTiming timing;
// If non-null, animate with this force instead of a tween animation. /// If non-null, animate with this force instead of a zero-to-one timeline.
Force attachedForce; Force attachedForce;
/// Add a variable to this animation
///
/// If there are no attached variables, this variable becomes the value of
/// [variable]. Otherwise, all the variables are stored in an [AnimatedList].
void addVariable(AnimatedVariable newVariable) { void addVariable(AnimatedVariable newVariable) {
if (variable == null) { if (variable == null) {
variable = newVariable; variable = newVariable;
...@@ -62,70 +74,92 @@ class AnimationPerformance { ...@@ -62,70 +74,92 @@ class AnimationPerformance {
} }
} }
double get progress => timeline.value; /// The progress of this performance along the timeline
///
/// Note: Setting this value stops the current animation.
double get progress => _timeline.value;
void set progress(double t) { void set progress(double t) {
// TODO(mpcomplete): should this affect |direction|? // TODO(mpcomplete): should this affect |direction|?
stop(); stop();
timeline.value = t.clamp(0.0, 1.0); _timeline.value = t.clamp(0.0, 1.0);
_checkStatusChanged(); _checkStatusChanged();
} }
double get curvedProgress { double get _curvedProgress {
return timing != null ? timing.transform(progress, curveDirection) : progress; return timing != null ? timing.transform(progress, _curveDirection) : progress;
} }
/// Whether this animation is stopped at the beginning
bool get isDismissed => status == AnimationStatus.dismissed; bool get isDismissed => status == AnimationStatus.dismissed;
/// Whether this animation is stopped at the end
bool get isCompleted => status == AnimationStatus.completed; bool get isCompleted => status == AnimationStatus.completed;
bool get isAnimating => timeline.isAnimating;
/// Whether this animation is currently animating in either the forward or reverse direction
bool get isAnimating => _timeline.isAnimating;
/// The current status of this animation
AnimationStatus get status { AnimationStatus get status {
if (!isAnimating && progress == 1.0) if (!isAnimating && progress == 1.0)
return AnimationStatus.completed; return AnimationStatus.completed;
if (!isAnimating && progress == 0.0) if (!isAnimating && progress == 0.0)
return AnimationStatus.dismissed; return AnimationStatus.dismissed;
return direction == Direction.forward ? return _direction == Direction.forward ?
AnimationStatus.forward : AnimationStatus.forward :
AnimationStatus.reverse; AnimationStatus.reverse;
} }
/// Update the given varaible according to the current progress of this performance
void updateVariable(AnimatedVariable variable) { void updateVariable(AnimatedVariable variable) {
variable.setProgress(curvedProgress, curveDirection); variable.setProgress(_curvedProgress, _curveDirection);
} }
/// Start running this animation in the given direction
Future play([Direction direction = Direction.forward]) { Future play([Direction direction = Direction.forward]) {
_direction = direction; _direction = direction;
return resume(); return resume();
} }
/// Start running this animation forwards (towards the end)
Future forward() => play(Direction.forward); Future forward() => play(Direction.forward);
/// Start running this animation in reverse (towards the beginning)
Future reverse() => play(Direction.reverse); Future reverse() => play(Direction.reverse);
/// Start running this animation in the most recently direction
Future resume() { Future resume() {
if (attachedForce != null) { if (attachedForce != null) {
return fling(velocity: _direction == Direction.forward ? 1.0 : -1.0, return fling(velocity: _direction == Direction.forward ? 1.0 : -1.0,
force: attachedForce); force: attachedForce);
} }
return _animateTo(direction == Direction.forward ? 1.0 : 0.0); return _animateTo(_direction == Direction.forward ? 1.0 : 0.0);
} }
/// Stop running this animation
void stop() { void stop() {
timeline.stop(); _timeline.stop();
} }
// Flings the timeline with an optional force (defaults to a critically /// Start running this animation according to the given physical parameters
// damped spring) and initial velocity. If velocity is positive, the ///
// animation will complete, otherwise it will dismiss. /// 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}) { Future fling({double velocity: 1.0, Force force}) {
if (force == null) if (force == null)
force = kDefaultSpringForce; force = kDefaultSpringForce;
_direction = velocity < 0.0 ? Direction.reverse : Direction.forward; _direction = velocity < 0.0 ? Direction.reverse : Direction.forward;
return timeline.fling(force.release(progress, velocity)); return _timeline.fling(force.release(progress, velocity));
} }
final List<Function> _listeners = new List<Function>(); final List<Function> _listeners = new List<Function>();
/// Calls the listener every time the progress of this performance changes
void addListener(Function listener) { void addListener(Function listener) {
_listeners.add(listener); _listeners.add(listener);
} }
/// Stop calling the listener every time the progress of this performance changes
void removeListener(Function listener) { void removeListener(Function listener) {
_listeners.remove(listener); _listeners.remove(listener);
} }
...@@ -138,10 +172,12 @@ class AnimationPerformance { ...@@ -138,10 +172,12 @@ class AnimationPerformance {
final List<Function> _statusListeners = new List<Function>(); final List<Function> _statusListeners = new List<Function>();
/// Calls listener every time the status of this performance changes
void addStatusListener(Function listener) { void addStatusListener(Function listener) {
_statusListeners.add(listener); _statusListeners.add(listener);
} }
/// Stops calling the listener every time the status of this performance changes
void removeStatusListener(Function listener) { void removeStatusListener(Function listener) {
_statusListeners.remove(listener); _statusListeners.remove(listener);
} }
...@@ -160,28 +196,28 @@ class AnimationPerformance { ...@@ -160,28 +196,28 @@ class AnimationPerformance {
void _updateCurveDirection() { void _updateCurveDirection() {
if (status != _lastStatus) { if (status != _lastStatus) {
if (_lastStatus == AnimationStatus.dismissed || _lastStatus == AnimationStatus.completed) if (_lastStatus == AnimationStatus.dismissed || _lastStatus == AnimationStatus.completed)
_curveDirection = direction; _curveDirection = _direction;
} }
} }
Future _animateTo(double target) { Future _animateTo(double target) {
Duration remainingDuration = duration * (target - timeline.value).abs(); Duration remainingDuration = duration * (target - _timeline.value).abs();
timeline.stop(); _timeline.stop();
if (remainingDuration == Duration.ZERO) if (remainingDuration == Duration.ZERO)
return new Future.value(); return new Future.value();
return timeline.animateTo(target, duration: remainingDuration); return _timeline.animateTo(target, duration: remainingDuration);
} }
void _tick(double t) { void _tick(double t) {
_updateCurveDirection(); _updateCurveDirection();
if (variable != null) if (variable != null)
variable.setProgress(curvedProgress, curveDirection); variable.setProgress(_curvedProgress, _curveDirection);
_notifyListeners(); _notifyListeners();
_checkStatusChanged(); _checkStatusChanged();
} }
} }
// Simple helper class for an animation with a single value. /// An animation performance with an animated variable with a concrete type
class ValueAnimation<T> extends AnimationPerformance { class ValueAnimation<T> extends AnimationPerformance {
ValueAnimation({AnimatedValue<T> variable, Duration duration}) : ValueAnimation({AnimatedValue<T> variable, Duration duration}) :
super(variable: variable, duration: duration); super(variable: variable, duration: duration);
......
...@@ -11,10 +11,17 @@ double _evaluateCubic(double a, double b, double m) { ...@@ -11,10 +11,17 @@ double _evaluateCubic(double a, double b, double m) {
const double _kCubicErrorBound = 0.001; const double _kCubicErrorBound = 0.001;
/// A mapping of the unit interval to the unit interval
///
/// A curve must map 0.0 to 0.0 and 1.0 to 1.0.
abstract class Curve { abstract class Curve {
/// Return the value of the curve at point t
///
/// The value of t must be between 0.0 and 1.0, inclusive.
double transform(double t); double transform(double t);
} }
/// The idenity map over the unit interval
class Linear implements Curve { class Linear implements Curve {
const Linear(); const Linear();
...@@ -23,8 +30,12 @@ class Linear implements Curve { ...@@ -23,8 +30,12 @@ class Linear implements Curve {
} }
} }
/// A curve that is initially 0.0, then linear, then 1.0
class Interval implements Curve { class Interval implements Curve {
/// The smallest value for which this interval is 0.0
final double start; final double start;
/// The smallest value for which this interval is 1.0
final double end; final double end;
Interval(this.start, this.end) { Interval(this.start, this.end) {
...@@ -39,6 +50,7 @@ class Interval implements Curve { ...@@ -39,6 +50,7 @@ class Interval implements Curve {
} }
} }
/// A cubic polynomial mapping of the unit interval
class Cubic implements Curve { class Cubic implements Curve {
final double a; final double a;
final double b; final double b;
...@@ -79,6 +91,7 @@ double _bounce(double t) { ...@@ -79,6 +91,7 @@ double _bounce(double t) {
return 7.5625 * t * t + 0.984375; return 7.5625 * t * t + 0.984375;
} }
/// An oscillating curve that grows in magnitude
class BounceInCurve implements Curve { class BounceInCurve implements Curve {
const BounceInCurve(); const BounceInCurve();
...@@ -87,6 +100,7 @@ class BounceInCurve implements Curve { ...@@ -87,6 +100,7 @@ class BounceInCurve implements Curve {
} }
} }
/// An oscillating curve that shrink in magnitude
class BounceOutCurve implements Curve { class BounceOutCurve implements Curve {
const BounceOutCurve(); const BounceOutCurve();
...@@ -95,6 +109,7 @@ class BounceOutCurve implements Curve { ...@@ -95,6 +109,7 @@ class BounceOutCurve implements Curve {
} }
} }
/// An oscillating curve that first grows and then shrink in magnitude
class BounceInOutCurve implements Curve { class BounceInOutCurve implements Curve {
const BounceInOutCurve(); const BounceInOutCurve();
...@@ -106,6 +121,7 @@ class BounceInOutCurve implements Curve { ...@@ -106,6 +121,7 @@ class BounceInOutCurve implements Curve {
} }
} }
/// An oscillating curve that grows in magnitude while overshootings its bounds
class ElasticInCurve implements Curve { class ElasticInCurve implements Curve {
const ElasticInCurve([this.period = 0.4]); const ElasticInCurve([this.period = 0.4]);
final double period; final double period;
...@@ -117,6 +133,7 @@ class ElasticInCurve implements Curve { ...@@ -117,6 +133,7 @@ class ElasticInCurve implements Curve {
} }
} }
/// An oscillating curve that shrinks in magnitude while overshootings its bounds
class ElasticOutCurve implements Curve { class ElasticOutCurve implements Curve {
const ElasticOutCurve([this.period = 0.4]); const ElasticOutCurve([this.period = 0.4]);
final double period; final double period;
...@@ -127,6 +144,7 @@ class ElasticOutCurve implements Curve { ...@@ -127,6 +144,7 @@ class ElasticOutCurve implements Curve {
} }
} }
/// An oscillating curve that grows and then shrinks in magnitude while overshootings its bounds
class ElasticInOutCurve implements Curve { class ElasticInOutCurve implements Curve {
const ElasticInOutCurve([this.period = 0.4]); const ElasticInOutCurve([this.period = 0.4]);
final double period; final double period;
...@@ -141,14 +159,35 @@ class ElasticInOutCurve implements Curve { ...@@ -141,14 +159,35 @@ class ElasticInOutCurve implements Curve {
} }
} }
/// A linear animation curve
const Linear linear = const Linear(); const Linear linear = const Linear();
/// A cubic animation cuve that speeds up quickly and ends slowly
const Cubic ease = const Cubic(0.25, 0.1, 0.25, 1.0); const Cubic ease = const Cubic(0.25, 0.1, 0.25, 1.0);
/// A cubic animation cuve that starts slowly and ends quickly
const Cubic easeIn = const Cubic(0.42, 0.0, 1.0, 1.0); const Cubic easeIn = const Cubic(0.42, 0.0, 1.0, 1.0);
/// A cubic animation cuve that starts quickly and ends slowly
const Cubic easeOut = const Cubic(0.0, 0.0, 0.58, 1.0); const Cubic easeOut = const Cubic(0.0, 0.0, 0.58, 1.0);
/// A cubic animation cuve that starts slowly, speeds up, and then and ends slowly
const Cubic easeInOut = const Cubic(0.42, 0.0, 0.58, 1.0); const Cubic easeInOut = const Cubic(0.42, 0.0, 0.58, 1.0);
/// An oscillating curve that grows in magnitude
const BounceInCurve bounceIn = const BounceInCurve(); const BounceInCurve bounceIn = const BounceInCurve();
/// An oscillating curve that first grows and then shrink in magnitude
const BounceOutCurve bounceOut = const BounceOutCurve(); const BounceOutCurve bounceOut = const BounceOutCurve();
/// An oscillating curve that first grows and then shrink in magnitude
const BounceInOutCurve bounceInOut = const BounceInOutCurve(); const BounceInOutCurve bounceInOut = const BounceInOutCurve();
/// An oscillating curve that grows in magnitude while overshootings its bounds
const ElasticInCurve elasticIn = const ElasticInCurve(); const ElasticInCurve elasticIn = const ElasticInCurve();
/// An oscillating curve that shrinks in magnitude while overshootings its bounds
const ElasticOutCurve elasticOut = const ElasticOutCurve(); const ElasticOutCurve elasticOut = const ElasticOutCurve();
/// An oscillating curve that grows and then shrinks in magnitude while overshootings its bounds
const ElasticInOutCurve elasticInOut = const ElasticInOutCurve(); const ElasticInOutCurve elasticInOut = const ElasticInOutCurve();
...@@ -4,26 +4,31 @@ ...@@ -4,26 +4,31 @@
import 'package:newton/newton.dart'; import 'package:newton/newton.dart';
// Base class for creating Simulations for the animation Timeline. /// A factory for simulations
abstract class Force { abstract class Force {
const Force(); const Force();
Simulation release(double position, double velocity); Simulation release(double position, double velocity);
} }
/// A factory for spring-based physics simulations
class SpringForce extends Force { class SpringForce extends Force {
const SpringForce(this.spring, { this.left: 0.0, this.right: 1.0 }); const SpringForce(this.spring, { this.left: 0.0, this.right: 1.0 });
/// The description of the spring to be used in the created simulations
final SpringDescription spring; final SpringDescription spring;
// Where to put the spring's resting point when releasing left or right, /// Where to put the spring's resting point when releasing left
// respectively.
final double left; final double left;
/// Where to put the spring's resting point when releasing right
final double right; final double right;
// We overshoot the target by this distance, but stop the simulation when /// How pricely to terminate the simulation
// the spring gets within this distance (regardless of how fast it's moving). ///
// This causes the spring to settle a bit faster than it otherwise would. /// We overshoot the target by this distance, but stop the simulation when
/// the spring gets within this distance (regardless of how fast it's moving).
/// This causes the spring to settle a bit faster than it otherwise would.
static const Tolerance tolerance = const Tolerance( static const Tolerance tolerance = const Tolerance(
velocity: double.INFINITY, velocity: double.INFINITY,
distance: 0.01 distance: 0.01
...@@ -43,4 +48,5 @@ final SpringDescription _kDefaultSpringDesc = new SpringDescription.withDampingR ...@@ -43,4 +48,5 @@ final SpringDescription _kDefaultSpringDesc = new SpringDescription.withDampingR
ratio: 1.0 ratio: 1.0
); );
/// A spring force with reasonable default values
final SpringForce kDefaultSpringForce = new SpringForce(_kDefaultSpringDesc); final SpringForce kDefaultSpringForce = new SpringForce(_kDefaultSpringDesc);
...@@ -9,24 +9,36 @@ import 'package:newton/newton.dart'; ...@@ -9,24 +9,36 @@ import 'package:newton/newton.dart';
const double _kSecondsPerMillisecond = 1000.0; const double _kSecondsPerMillisecond = 1000.0;
const double _kScrollDrag = 0.025; const double _kScrollDrag = 0.025;
/// An interface for controlling the behavior of scrollable widgets
abstract class ScrollBehavior { abstract class ScrollBehavior {
/// A simulation to run to determine the scroll offset
///
/// Called when the user stops scrolling at a given position with a given
/// instantaneous velocity.
Simulation release(double position, double velocity) => null; Simulation release(double position, double velocity) => null;
// Returns the new scroll offset. /// The new scroll offset to use when the user attempts to scroll from the given offset by the given delta
double applyCurve(double scrollOffset, double scrollDelta); double applyCurve(double scrollOffset, double scrollDelta);
} }
/// A scroll behavior for a scrollable widget with linear extent
abstract class ExtentScrollBehavior extends ScrollBehavior { abstract class ExtentScrollBehavior extends ScrollBehavior {
ExtentScrollBehavior({ double contentExtent: 0.0, double containerExtent: 0.0 }) ExtentScrollBehavior({ double contentExtent: 0.0, double containerExtent: 0.0 })
: _contentExtent = contentExtent, _containerExtent = containerExtent; : _contentExtent = contentExtent, _containerExtent = containerExtent;
double _contentExtent; /// The linear extent of the content inside the scrollable widget
double get contentExtent => _contentExtent; double get contentExtent => _contentExtent;
double _contentExtent;
double _containerExtent; /// The linear extent of the exterior of the scrollable widget
double get containerExtent => _containerExtent; double get containerExtent => _containerExtent;
double _containerExtent;
/// Returns the new scrollOffset. /// Update either content or container extent (or both)
///
/// The scrollOffset parameter is the scroll offset of the widget before the
/// change in extent. Returns the new scroll offset of the widget after the
/// change in extent.
double updateExtents({ double updateExtents({
double contentExtent, double contentExtent,
double containerExtent, double containerExtent,
...@@ -39,10 +51,14 @@ abstract class ExtentScrollBehavior extends ScrollBehavior { ...@@ -39,10 +51,14 @@ abstract class ExtentScrollBehavior extends ScrollBehavior {
return scrollOffset.clamp(minScrollOffset, maxScrollOffset); return scrollOffset.clamp(minScrollOffset, maxScrollOffset);
} }
/// The minimum value the scroll offset can obtain
double get minScrollOffset; double get minScrollOffset;
/// The maximum value the scroll offset can obatin
double get maxScrollOffset; double get maxScrollOffset;
} }
/// A scroll behavior that prevents the user from exeeding scroll bounds
class BoundedBehavior extends ExtentScrollBehavior { class BoundedBehavior extends ExtentScrollBehavior {
BoundedBehavior({ double contentExtent: 0.0, double containerExtent: 0.0 }) BoundedBehavior({ double contentExtent: 0.0, double containerExtent: 0.0 })
: super(contentExtent: contentExtent, containerExtent: containerExtent); : super(contentExtent: contentExtent, containerExtent: containerExtent);
...@@ -55,6 +71,7 @@ class BoundedBehavior extends ExtentScrollBehavior { ...@@ -55,6 +71,7 @@ class BoundedBehavior extends ExtentScrollBehavior {
} }
} }
/// A scroll behavior that does not prevent the user from exeeding scroll bounds
class UnboundedBehavior extends ExtentScrollBehavior { class UnboundedBehavior extends ExtentScrollBehavior {
UnboundedBehavior({ double contentExtent: 0.0, double containerExtent: 0.0 }) UnboundedBehavior({ double contentExtent: 0.0, double containerExtent: 0.0 })
: super(contentExtent: contentExtent, containerExtent: containerExtent); : super(contentExtent: contentExtent, containerExtent: containerExtent);
...@@ -74,19 +91,20 @@ class UnboundedBehavior extends ExtentScrollBehavior { ...@@ -74,19 +91,20 @@ class UnboundedBehavior extends ExtentScrollBehavior {
} }
} }
Simulation createDefaultScrollSimulation(double position, double velocity, double minScrollOffset, double maxScrollOffset) { Simulation _createDefaultScrollSimulation(double position, double velocity, double minScrollOffset, double maxScrollOffset) {
double velocityPerSecond = velocity * _kSecondsPerMillisecond; double velocityPerSecond = velocity * _kSecondsPerMillisecond;
SpringDescription spring = new SpringDescription.withDampingRatio( SpringDescription spring = new SpringDescription.withDampingRatio(
mass: 1.0, springConstant: 170.0, ratio: 1.1); mass: 1.0, springConstant: 170.0, ratio: 1.1);
return new ScrollSimulation(position, velocityPerSecond, minScrollOffset, maxScrollOffset, spring, _kScrollDrag); return new ScrollSimulation(position, velocityPerSecond, minScrollOffset, maxScrollOffset, spring, _kScrollDrag);
} }
/// A scroll behavior that lets the user scroll beyond the scroll bounds with some resistance
class OverscrollBehavior extends BoundedBehavior { class OverscrollBehavior extends BoundedBehavior {
OverscrollBehavior({ double contentExtent: 0.0, double containerExtent: 0.0 }) OverscrollBehavior({ double contentExtent: 0.0, double containerExtent: 0.0 })
: super(contentExtent: contentExtent, containerExtent: containerExtent); : super(contentExtent: contentExtent, containerExtent: containerExtent);
Simulation release(double position, double velocity) { Simulation release(double position, double velocity) {
return createDefaultScrollSimulation(position, velocity, minScrollOffset, maxScrollOffset); return _createDefaultScrollSimulation(position, velocity, minScrollOffset, maxScrollOffset);
} }
double applyCurve(double scrollOffset, double scrollDelta) { double applyCurve(double scrollOffset, double scrollDelta) {
...@@ -106,6 +124,7 @@ class OverscrollBehavior extends BoundedBehavior { ...@@ -106,6 +124,7 @@ class OverscrollBehavior extends BoundedBehavior {
} }
} }
/// A scroll behavior that lets the user scroll beyond the scroll bounds only when the bounds are disjoint
class OverscrollWhenScrollableBehavior extends OverscrollBehavior { class OverscrollWhenScrollableBehavior extends OverscrollBehavior {
bool get isScrollable => contentExtent > containerExtent; bool get isScrollable => contentExtent > containerExtent;
......
...@@ -8,10 +8,14 @@ import 'package:newton/newton.dart'; ...@@ -8,10 +8,14 @@ import 'package:newton/newton.dart';
import 'package:sky/src/animation/animated_simulation.dart'; import 'package:sky/src/animation/animated_simulation.dart';
// Simple simulation that linearly varies from |begin| to |end| over |duration|. /// A simulation that linearly varies from [begin] to [end] over [duration]
class TweenSimulation extends Simulation { class TweenSimulation extends Simulation {
final double _durationInSeconds; final double _durationInSeconds;
/// The initial value of the simulation
final double begin; final double begin;
/// The terminal value of the simulation
final double end; final double end;
TweenSimulation(Duration duration, this.begin, this.end) : TweenSimulation(Duration duration, this.begin, this.end) :
...@@ -32,14 +36,15 @@ class TweenSimulation extends Simulation { ...@@ -32,14 +36,15 @@ class TweenSimulation extends Simulation {
bool isDone(double timeInSeconds) => timeInSeconds > _durationInSeconds; bool isDone(double timeInSeconds) => timeInSeconds > _durationInSeconds;
} }
/// A timeline for an animation
class Timeline { class Timeline {
Timeline(Function onTick) : _onTick = onTick { Timeline(Function onTick) {
_animation = new AnimatedSimulation(_tick); _animation = new AnimatedSimulation(onTick);
} }
final Function _onTick;
AnimatedSimulation _animation; AnimatedSimulation _animation;
/// The current value of the timeline
double get value => _animation.value.clamp(0.0, 1.0); double get value => _animation.value.clamp(0.0, 1.0);
void set value(double newValue) { void set value(double newValue) {
assert(newValue != null && newValue >= 0.0 && newValue <= 1.0); assert(newValue != null && newValue >= 0.0 && newValue <= 1.0);
...@@ -47,6 +52,7 @@ class Timeline { ...@@ -47,6 +52,7 @@ class Timeline {
_animation.value = newValue; _animation.value = newValue;
} }
/// Whether the timeline is currently animating
bool get isAnimating => _animation.isAnimating; bool get isAnimating => _animation.isAnimating;
Future _start({ Future _start({
...@@ -59,22 +65,23 @@ class Timeline { ...@@ -59,22 +65,23 @@ class Timeline {
return _animation.start(new TweenSimulation(duration, begin, end)); return _animation.start(new TweenSimulation(duration, begin, end));
} }
/// Animate value of the timeline to the given target over the given duration
///
/// Returns a future that resolves when the timeline stops animating,
/// typically when the timeline arives at the target value.
Future animateTo(double target, { Duration duration }) { Future animateTo(double target, { Duration duration }) {
assert(duration > Duration.ZERO); assert(duration > Duration.ZERO);
return _start(duration: duration, begin: value, end: target); return _start(duration: duration, begin: value, end: target);
} }
/// Stop animating the timeline
void stop() { void stop() {
_animation.stop(); _animation.stop();
} }
// Give |simulation| control over the timeline. // Gives the given simulation control over the timeline
Future fling(Simulation simulation) { Future fling(Simulation simulation) {
stop(); stop();
return _animation.start(simulation); return _animation.start(simulation);
} }
void _tick(double newValue) {
_onTick(value);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment