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; } }