package aurelienribon.tweenengine;
/**
* BaseTween is the base class of Tween and Timeline. It defines the
* iteration engine used to play animations for any number of times, and in
* any direction, at any speed.
* <p/>
*
* It is responsible for calling the different callbacks at the right moments,
* and for making sure that every callbacks are triggered, even if the update
* engine gets a big delta time at once.
*
* @see Tween
* @see Timeline
* @author Aurelien Ribon | http://www.aurelienribon.com/
*/
public abstract class BaseTween<T> {
// General
private int step;
private int repeatCnt;
private boolean isIterationStep;
private boolean isYoyo;
// Timings
protected float delay;
protected float duration;
private float repeatDelay;
private float currentTime;
private float deltaTime;
private boolean isStarted; // true when the object is started
private boolean isInitialized; // true after the delay
private boolean isFinished; // true when all repetitions are done
private boolean isKilled; // true if kill() was called
private boolean isPaused; // true if pause() was called
// Misc
private TweenCallback callback;
private int callbackTriggers;
private Object userData;
// Package access
boolean isAutoRemoveEnabled;
boolean isAutoStartEnabled;
// -------------------------------------------------------------------------
protected void reset() {
step = -2;
repeatCnt = 0;
isIterationStep = isYoyo = false;
delay = duration = repeatDelay = currentTime = deltaTime = 0;
isStarted = isInitialized = isFinished = isKilled = isPaused = false;
callback = null;
callbackTriggers = TweenCallback.COMPLETE;
userData = null;
isAutoRemoveEnabled = isAutoStartEnabled = true;
}
// -------------------------------------------------------------------------
// Public API
// -------------------------------------------------------------------------
/**
* Builds and validates the object. Only needed if you want to finalize a
* tween or timeline without starting it, since a call to ".start()" also
* calls this method.
*
* @return The current object, for chaining instructions.
*/
public T build() {
return (T) this;
}
/**
* Starts or restarts the object unmanaged. You will need to take care of
* its life-cycle. If you want the tween to be managed for you, use a
* {@link TweenManager}.
*
* @return The current object, for chaining instructions.
*/
public T start() {
build();
currentTime = 0;
isStarted = true;
return (T) this;
}
/**
* Convenience method to add an object to a manager. Its life-cycle will be
* handled for you. Relax and enjoy the animation.
*
* @return The current object, for chaining instructions.
*/
public T start(TweenManager manager) {
manager.add(this);
return (T) this;
}
/**
* Adds a delay to the tween or timeline.
*
* @param delay A duration.
* @return The current object, for chaining instructions.
*/
public T delay(float delay) {
this.delay += delay;
return (T) this;
}
/**
* Kills the tween or timeline. If you are using a TweenManager, this object
* will be removed automatically.
*/
public void kill() {
isKilled = true;
}
/**
* Stops and resets the tween or timeline, and sends it to its pool, for
+ * later reuse. Note that if you use a {@link TweenManager}, this method
+ * is automatically called once the animation is finished.
*/
public void free() {
}
/**
* Pauses the tween or timeline. Further update calls won't have any effect.
*/
public void pause() {
isPaused = true;
}
/**
* Resumes the tween or timeline. Has no effect is it was no already paused.
*/
public void resume() {
isPaused = false;
}
/**
* Repeats the tween or timeline for a given number of times.
* @param count The number of repetitions. For infinite repetition,
* use Tween.INFINITY, or a negative number.
*
* @param delay A delay between each iteration.
* @return The current tween or timeline, for chaining instructions.
*/
public T repeat(int count, float delay) {
if (isStarted) throw new RuntimeException("You can't change the repetitions of a tween or timeline once it is started");
repeatCnt = count;
repeatDelay = delay >= 0 ? delay : 0;
isYoyo = false;
return (T) this;
}
/**
* Repeats the tween or timeline for a given number of times.
* Every two iterations, it will be played backwards.
*
* @param count The number of repetitions. For infinite repetition,
* use Tween.INFINITY, or '-1'.
* @param delay A delay before each repetition.
* @return The current tween or timeline, for chaining instructions.
*/
public T repeatYoyo(int count, float delay) {
if (isStarted) throw new RuntimeException("You can't change the repetitions of a tween or timeline once it is started");
repeatCnt = count;
repeatDelay = delay >= 0 ? delay : 0;
isYoyo = true;
return (T) this;
}
/**
* Sets the callback. By default, it will be fired at the completion of the
* tween or timeline (event COMPLETE). If you want to change this behavior
* and add more triggers, use the {@link setCallbackTriggers()} method.
*
* @see TweenCallback
*/
public T setCallback(TweenCallback callback) {
this.callback = callback;
return (T) this;
}
/**
* Changes the triggers of the callback. The available triggers, listed as
* members of the {@link TweenCallback} interface, are:
* <p/>
*
* <b>BEGIN</b>: right after the delay (if any)<br/>
* <b>START</b>: at each iteration beginning<br/>
* <b>END</b>: at each iteration ending, before the repeat delay<br/>
* <b>COMPLETE</b>: at last END event<br/>
* <b>BACK_BEGIN</b>: at the beginning of the first backward iteration<br/>
* <b>BACK_START</b>: at each backward iteration beginning, after the repeat delay<br/>
* <b>BACK_END</b>: at each backward iteration ending<br/>
* <b>BACK_COMPLETE</b>: at last BACK_END event
* <p/>
*
* <pre> {@code
* forward : BEGIN COMPLETE
* forward : START END START END START END
* |--------------[XXXXXXXXXX]------[XXXXXXXXXX]------[XXXXXXXXXX]
* backward: bEND bSTART bEND bSTART bEND bSTART
* backward: bCOMPLETE bBEGIN
* }</pre>
*
* @param flags one or more triggers, separated by the '|' operator.
* @see TweenCallback
*/
public T setCallbackTriggers(int flags) {
this.callbackTriggers = flags;
return (T) this;
}
/**
* Attaches an object to this tween or timeline. It can be useful in order
* to retrieve some data from a TweenCallback.
*
* @param data Any kind of object.
* @return The current tween or timeline, for chaining instructions.
*/
public T setUserData(Object data) {
userData = data;
return (T) this;
}
// -------------------------------------------------------------------------
// Getters
// -------------------------------------------------------------------------
/**
* Gets the delay of the tween or timeline. Nothing will happen before
* this delay.
*/
public float getDelay() {
return delay;
}
/**
* Gets the duration of a single iteration.
*/
public float getDuration() {
return duration;
}
/**
* Gets the number of iterations that will be played.
*/
public int getRepeatCount() {
return repeatCnt;
}
/**
* Gets the delay occuring between two iterations.
*/
public float getRepeatDelay() {
return repeatDelay;
}
/**
* Returns the complete duration, including initial delay and repetitions.
* The formula is as follows:
* <pre>
* fullDuration = delay + duration + (repeatDelay + duration) * repeatCnt
* </pre>
*/
public float getFullDuration() {
if (repeatCnt < 0) return -1;
return delay + duration + (repeatDelay + duration) * repeatCnt;
}
/**
* Gets the attached data, or null if none.
*/
public Object getUserData() {
return userData;
}
/**
* Gets the id of the current step. Values are as follows:<br/>
* <ul>
* <li>even numbers mean that an iteration is playing,<br/>
* <li>odd numbers mean that we are between two iterations,<br/>
* <li>-2 means that the initial delay has not ended,<br/>
* <li>-1 means that we are before the first iteration,<br/>
* <li>repeatCount*2 + 1 means that we are after the last iteration
*/
public int getStep() {
return step;
}
/**
* Gets the local time.
*/
public float getCurrentTime() {
return currentTime;
}
/**
* Returns true if the tween or timeline has been started.
*/
public boolean isStarted() {
return isStarted;
}
/**
* Returns true if the tween or timeline has been initialized. Starting
* values for tweens are stored at initialization time. This initialization
* takes place right after the initial delay, if any.
*/
public boolean isInitialized() {
return isInitialized;
}
/**
* Returns true if the tween is finished (i.e. if the tween has reached
* its end or has been killed). If you don't use a TweenManager, you may
* want to call {@link free()} to reuse the object later.
*/
public boolean isFinished() {
return isFinished || isKilled;
}
/**
* Returns true if the iterations are played as yoyo. Yoyo means that
* every two iterations, the animation will be played backwards.
*/
public boolean isYoyo() {
return isYoyo;
}
/**
* Returns true if the tween or timeline is currently paused.
*/
public boolean isPaused() {
return isPaused;
}
// -------------------------------------------------------------------------
// Abstract API
// -------------------------------------------------------------------------
protected abstract void forceStartValues();
protected abstract void forceEndValues();
protected abstract boolean containsTarget(Object target);
protected abstract boolean containsTarget(Object target, int tweenType);
// -------------------------------------------------------------------------
// Protected API
// -------------------------------------------------------------------------
protected void initializeOverride() {
}
protected void updateOverride(int step, int lastStep, boolean isIterationStep, float delta) {
}
protected void forceToStart() {
currentTime = -delay;
step = -1;
isIterationStep = false;
if (isReverse(0)) forceEndValues();
else forceStartValues();
}
protected void forceToEnd(float time) {
currentTime = time - getFullDuration();
step = repeatCnt*2 + 1;
isIterationStep = false;
if (isReverse(repeatCnt*2)) forceStartValues();
else forceEndValues();
}
protected void callCallback(int type) {
if (callback != null && (callbackTriggers & type) > 0) callback.onEvent(type, this);
}
protected boolean isReverse(int step) {
return isYoyo && Math.abs(step%4) == 2;
}
protected boolean isValid(int step) {
return (step >= 0 && step <= repeatCnt*2) || repeatCnt < 0;
}
protected void killTarget(Object target) {
if (containsTarget(target)) kill();
}
protected void killTarget(Object target, int tweenType) {
if (containsTarget(target, tweenType)) kill();
}
// -------------------------------------------------------------------------
// Update engine
// -------------------------------------------------------------------------
/**
* Updates the tween or timeline state. <b>You may want to use a
* TweenManager to update objects for you.</b>
*
* Slow motion, fast motion and backward play can be easily achieved by
* tweaking this delta time. Multiply it by -1 to play the animation
* backward, or by 0.5 to play it twice slower than its normal speed.
*
* @param delta A delta time between now and the last call.
*/
public void update(float delta) {
if (!isStarted || isPaused || isKilled) return;
deltaTime = delta;
if (!isInitialized) {
initialize();
}
if (isInitialized) {
testRelaunch();
updateStep();
testCompletion();
}
currentTime += deltaTime;
deltaTime = 0;
}
private void initialize() {
if (currentTime+deltaTime >= delay) {
initializeOverride();
isInitialized = true;
isIterationStep = true;
step = 0;
deltaTime -= delay-currentTime;
currentTime = 0;
callCallback(TweenCallback.BEGIN);
callCallback(TweenCallback.START);
}
}
private void testRelaunch() {
if (!isIterationStep && repeatCnt >= 0 && step < 0 && currentTime+deltaTime >= 0) {
assert step == -1;
isIterationStep = true;
step = 0;
float delta = 0-currentTime;
deltaTime -= delta;
currentTime = 0;
callCallback(TweenCallback.BEGIN);
callCallback(TweenCallback.START);
updateOverride(step, step-1, isIterationStep, delta);
} else if (!isIterationStep && repeatCnt >= 0 && step > repeatCnt*2 && currentTime+deltaTime < 0) {
assert step == repeatCnt*2 + 1;
isIterationStep = true;
step = repeatCnt*2;
float delta = 0-currentTime;
deltaTime -= delta;
currentTime = duration;
callCallback(TweenCallback.BACK_BEGIN);
callCallback(TweenCallback.BACK_START);
updateOverride(step, step+1, isIterationStep, delta);
}
}
private void updateStep() {
while (isValid(step)) {
if (!isIterationStep && currentTime+deltaTime <= 0) {
isIterationStep = true;
step -= 1;
float delta = 0-currentTime;
deltaTime -= delta;
currentTime = duration;
if (isReverse(step)) forceStartValues(); else forceEndValues();
callCallback(TweenCallback.BACK_START);
updateOverride(step, step+1, isIterationStep, delta);
} else if (!isIterationStep && currentTime+deltaTime >= repeatDelay) {
isIterationStep = true;
step += 1;
float delta = repeatDelay-currentTime;
deltaTime -= delta;
currentTime = 0;
if (isReverse(step)) forceEndValues(); else forceStartValues();
callCallback(TweenCallback.START);
updateOverride(step, step-1, isIterationStep, delta);
} else if (isIterationStep && currentTime+deltaTime < 0) {
isIterationStep = false;
step -= 1;
float delta = 0-currentTime;
deltaTime -= delta;
currentTime = 0;
updateOverride(step, step+1, isIterationStep, delta);
callCallback(TweenCallback.BACK_END);
if (step < 0 && repeatCnt >= 0) callCallback(TweenCallback.BACK_COMPLETE);
else currentTime = repeatDelay;
} else if (isIterationStep && currentTime+deltaTime > duration) {
isIterationStep = false;
step += 1;
float delta = duration-currentTime;
deltaTime -= delta;
currentTime = duration;
updateOverride(step, step-1, isIterationStep, delta);
callCallback(TweenCallback.END);
if (step > repeatCnt*2 && repeatCnt >= 0) callCallback(TweenCallback.COMPLETE);
currentTime = 0;
} else if (isIterationStep) {
float delta = deltaTime;
deltaTime -= delta;
currentTime += delta;
updateOverride(step, step, isIterationStep, delta);
break;
} else {
float delta = deltaTime;
deltaTime -= delta;
currentTime += delta;
break;
}
}
}
private void testCompletion() {
isFinished = repeatCnt >= 0 && (step > repeatCnt*2 || step < 0);
}
}