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();}
|
};
|
|
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;
|
for (int i=0, n=children.size(); i<n; i++) children.get(i).update(delta + 1);
|
return;
|
}
|
|
if (!isIterationStep && step < lastStep) {
|
assert delta <= 0;
|
for (int i=children.size()-1; i>=0; i--) children.get(i).update(delta - 1);
|
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;
|
}
|
}
|