blob: 5e1f765042b6a98e2efa64450383c6ddfb8430bf [file] [log] [blame]
package aurelienribon.tweenengine;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A Timeline can be used to create complex animations made of sequences and
* parallel sets of Tweens.
* <p/>
*
* The following example will create an animation sequence composed of 5 parts:
* <p/>
*
* 1. First, opacity and scale are set to 0 (with Tween.set() calls).<br/>
* 2. Then, opacity and scale are animated in parallel.<br/>
* 3. Then, the animation is paused for 1s.<br/>
* 4. Then, position is animated to x=100.<br/>
* 5. Then, rotation is animated to 360°.
* <p/>
*
* This animation will be repeated 5 times, with a 500ms delay between each
* iteration:
* <br/><br/>
*
* <pre> {@code
* Timeline.createSequence()
* .push(Tween.set(myObject, OPACITY).target(0))
* .push(Tween.set(myObject, SCALE).target(0, 0))
* .beginParallel()
* .push(Tween.to(myObject, OPACITY, 0.5f).target(1).ease(Quad.INOUT))
* .push(Tween.to(myObject, SCALE, 0.5f).target(1, 1).ease(Quad.INOUT))
* .end()
* .pushPause(1.0f)
* .push(Tween.to(myObject, POSITION_X, 0.5f).target(100).ease(Quad.INOUT))
* .push(Tween.to(myObject, ROTATION, 0.5f).target(360).ease(Quad.INOUT))
* .repeat(5, 0.5f)
* .start(myManager);
* }</pre>
*
* @see Tween
* @see TweenManager
* @see TweenCallback
* @author Aurelien Ribon | http://www.aurelienribon.com/
*/
public final class Timeline extends BaseTween<Timeline> {
// -------------------------------------------------------------------------
// Static -- pool
// -------------------------------------------------------------------------
private static final Pool.Callback<Timeline> poolCallback = new Pool.Callback<Timeline>() {
@Override public void onPool(Timeline obj) {obj.reset();}
@Override public void onUnPool(Timeline obj) {obj.reset();}
};
static final Pool<Timeline> pool = new Pool<Timeline>(10, poolCallback) {
@Override protected Timeline create() {return new Timeline();}
};
/**
* Used for debug purpose. Gets the current number of empty timelines that
* are waiting in the Timeline pool.
*/
public static int getPoolSize() {
return pool.size();
}
/**
* Increases the minimum capacity of the pool. Capacity defaults to 10.
*/
public static void ensurePoolCapacity(int minCapacity) {
pool.ensureCapacity(minCapacity);
}
// -------------------------------------------------------------------------
// Static -- factories
// -------------------------------------------------------------------------
/**
* Creates a new timeline with a 'sequence' behavior. Its children will
* be delayed so that they are triggered one after the other.
*/
public static Timeline createSequence() {
Timeline tl = pool.get();
tl.setup(Modes.SEQUENCE);
return tl;
}
/**
* Creates a new timeline with a 'parallel' behavior. Its children will be
* triggered all at once.
*/
public static Timeline createParallel() {
Timeline tl = pool.get();
tl.setup(Modes.PARALLEL);
return tl;
}
// -------------------------------------------------------------------------
// Attributes
// -------------------------------------------------------------------------
private enum Modes {SEQUENCE, PARALLEL}
private final List<BaseTween<?>> children = new ArrayList<BaseTween<?>>(10);
private Timeline current;
private Timeline parent;
private Modes mode;
private boolean isBuilt;
// -------------------------------------------------------------------------
// Setup
// -------------------------------------------------------------------------
private Timeline() {
reset();
}
@Override
protected void reset() {
super.reset();
children.clear();
current = parent = null;
isBuilt = false;
}
private void setup(Modes mode) {
this.mode = mode;
this.current = this;
}
// -------------------------------------------------------------------------
// Public API
// -------------------------------------------------------------------------
/**
* Adds a Tween to the current timeline.
*
* @return The current timeline, for chaining instructions.
*/
public Timeline push(Tween tween) {
if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
current.children.add(tween);
return this;
}
/**
* Nests a Timeline in the current one.
*
* @return The current timeline, for chaining instructions.
*/
public Timeline push(Timeline timeline) {
if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
if (timeline.current != timeline) throw new RuntimeException("You forgot to call a few 'end()' statements in your pushed timeline");
timeline.parent = current;
current.children.add(timeline);
return this;
}
/**
* Adds a pause to the timeline. The pause may be negative if you want to
* overlap the preceding and following children.
*
* @param time A positive or negative duration.
* @return The current timeline, for chaining instructions.
*/
public Timeline pushPause(float time) {
if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
current.children.add(Tween.mark().delay(time));
return this;
}
/**
* Starts a nested timeline with a 'sequence' behavior. Don't forget to
* call {@link end()} to close this nested timeline.
*
* @return The current timeline, for chaining instructions.
*/
public Timeline beginSequence() {
if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
Timeline tl = pool.get();
tl.parent = current;
tl.mode = Modes.SEQUENCE;
current.children.add(tl);
current = tl;
return this;
}
/**
* Starts a nested timeline with a 'parallel' behavior. Don't forget to
* call {@link end()} to close this nested timeline.
*
* @return The current timeline, for chaining instructions.
*/
public Timeline beginParallel() {
if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
Timeline tl = pool.get();
tl.parent = current;
tl.mode = Modes.PARALLEL;
current.children.add(tl);
current = tl;
return this;
}
/**
* Closes the last nested timeline.
*
* @return The current timeline, for chaining instructions.
*/
public Timeline end() {
if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
if (current == this) throw new RuntimeException("Nothing to end...");
current = current.parent;
return this;
}
/**
* Gets a list of the timeline children. If the timeline is started, the
* list will be immutable.
*/
public List<BaseTween<?>> getChildren() {
if (isBuilt) return Collections.unmodifiableList(current.children);
else return current.children;
}
// -------------------------------------------------------------------------
// Overrides
// -------------------------------------------------------------------------
@Override
public Timeline build() {
if (isBuilt) return this;
duration = 0;
for (int i=0; i<children.size(); i++) {
BaseTween<?> obj = children.get(i);
if (obj.getRepeatCount() < 0) throw new RuntimeException("You can't push an object with infinite repetitions in a timeline");
obj.build();
switch (mode) {
case SEQUENCE:
float tDelay = duration;
duration += obj.getFullDuration();
obj.delay += tDelay;
break;
case PARALLEL:
duration = Math.max(duration, obj.getFullDuration());
break;
}
}
isBuilt = true;
return this;
}
@Override
public Timeline start() {
super.start();
for (int i=0; i<children.size(); i++) {
BaseTween<?> obj = children.get(i);
obj.start();
}
return this;
}
@Override
public void free() {
for (int i=children.size()-1; i>=0; i--) {
BaseTween<?> obj = children.remove(i);
obj.free();
}
pool.free(this);
}
@Override
protected void updateOverride(int step, int lastStep, boolean isIterationStep, float delta) {
if (!isIterationStep && step > lastStep) {
assert delta >= 0;
float dt = isReverse(lastStep) ? -delta-1 : delta+1;
for (int i=0, n=children.size(); i<n; i++) children.get(i).update(dt);
return;
}
if (!isIterationStep && step < lastStep) {
assert delta <= 0;
float dt = isReverse(lastStep) ? -delta-1 : delta+1;
for (int i=children.size()-1; i>=0; i--) children.get(i).update(dt);
return;
}
assert isIterationStep;
if (step > lastStep) {
if (isReverse(step)) {
forceEndValues();
for (int i=0, n=children.size(); i<n; i++) children.get(i).update(delta);
} else {
forceStartValues();
for (int i=0, n=children.size(); i<n; i++) children.get(i).update(delta);
}
} else if (step < lastStep) {
if (isReverse(step)) {
forceStartValues();
for (int i=children.size()-1; i>=0; i--) children.get(i).update(delta);
} else {
forceEndValues();
for (int i=children.size()-1; i>=0; i--) children.get(i).update(delta);
}
} else {
float dt = isReverse(step) ? -delta : delta;
if (delta >= 0) for (int i=0, n=children.size(); i<n; i++) children.get(i).update(dt);
else for (int i=children.size()-1; i>=0; i--) children.get(i).update(dt);
}
}
// -------------------------------------------------------------------------
// BaseTween impl.
// -------------------------------------------------------------------------
@Override
protected void forceStartValues() {
for (int i=children.size()-1; i>=0; i--) {
BaseTween<?> obj = children.get(i);
obj.forceToStart();
}
}
@Override
protected void forceEndValues() {
for (int i=0, n=children.size(); i<n; i++) {
BaseTween<?> obj = children.get(i);
obj.forceToEnd(duration);
}
}
@Override
protected boolean containsTarget(Object target) {
for (int i=0, n=children.size(); i<n; i++) {
BaseTween<?> obj = children.get(i);
if (obj.containsTarget(target)) return true;
}
return false;
}
@Override
protected boolean containsTarget(Object target, int tweenType) {
for (int i=0, n=children.size(); i<n; i++) {
BaseTween<?> obj = children.get(i);
if (obj.containsTarget(target, tweenType)) return true;
}
return false;
}
}