Home | History | Annotate | Download | only in tweenengine
      1 package aurelienribon.tweenengine;
      2 
      3 import java.util.ArrayList;
      4 import java.util.Collections;
      5 import java.util.List;
      6 
      7 /**
      8  * A Timeline can be used to create complex animations made of sequences and
      9  * parallel sets of Tweens.
     10  * <p/>
     11  *
     12  * The following example will create an animation sequence composed of 5 parts:
     13  * <p/>
     14  *
     15  * 1. First, opacity and scale are set to 0 (with Tween.set() calls).<br/>
     16  * 2. Then, opacity and scale are animated in parallel.<br/>
     17  * 3. Then, the animation is paused for 1s.<br/>
     18  * 4. Then, position is animated to x=100.<br/>
     19  * 5. Then, rotation is animated to 360.
     20  * <p/>
     21  *
     22  * This animation will be repeated 5 times, with a 500ms delay between each
     23  * iteration:
     24  * <br/><br/>
     25  *
     26  * <pre> {@code
     27  * Timeline.createSequence()
     28  *     .push(Tween.set(myObject, OPACITY).target(0))
     29  *     .push(Tween.set(myObject, SCALE).target(0, 0))
     30  *     .beginParallel()
     31  *          .push(Tween.to(myObject, OPACITY, 0.5f).target(1).ease(Quad.INOUT))
     32  *          .push(Tween.to(myObject, SCALE, 0.5f).target(1, 1).ease(Quad.INOUT))
     33  *     .end()
     34  *     .pushPause(1.0f)
     35  *     .push(Tween.to(myObject, POSITION_X, 0.5f).target(100).ease(Quad.INOUT))
     36  *     .push(Tween.to(myObject, ROTATION, 0.5f).target(360).ease(Quad.INOUT))
     37  *     .repeat(5, 0.5f)
     38  *     .start(myManager);
     39  * }</pre>
     40  *
     41  * @see Tween
     42  * @see TweenManager
     43  * @see TweenCallback
     44  * @author Aurelien Ribon | http://www.aurelienribon.com/
     45  */
     46 public final class Timeline extends BaseTween<Timeline> {
     47 	// -------------------------------------------------------------------------
     48 	// Static -- pool
     49 	// -------------------------------------------------------------------------
     50 
     51 	private static final Pool.Callback<Timeline> poolCallback = new Pool.Callback<Timeline>() {
     52 		@Override public void onPool(Timeline obj) {obj.reset();}
     53 		@Override public void onUnPool(Timeline obj) {obj.reset();}
     54 	};
     55 
     56 	static final Pool<Timeline> pool = new Pool<Timeline>(10, poolCallback) {
     57 		@Override protected Timeline create() {return new Timeline();}
     58 	};
     59 
     60 	/**
     61 	 * Used for debug purpose. Gets the current number of empty timelines that
     62 	 * are waiting in the Timeline pool.
     63 	 */
     64 	public static int getPoolSize() {
     65 		return pool.size();
     66 	}
     67 
     68 	/**
     69 	 * Increases the minimum capacity of the pool. Capacity defaults to 10.
     70 	 */
     71 	public static void ensurePoolCapacity(int minCapacity) {
     72 		pool.ensureCapacity(minCapacity);
     73 	}
     74 
     75 	// -------------------------------------------------------------------------
     76 	// Static -- factories
     77 	// -------------------------------------------------------------------------
     78 
     79 	/**
     80 	 * Creates a new timeline with a 'sequence' behavior. Its children will
     81 	 * be delayed so that they are triggered one after the other.
     82 	 */
     83 	public static Timeline createSequence() {
     84 		Timeline tl = pool.get();
     85 		tl.setup(Modes.SEQUENCE);
     86 		return tl;
     87 	}
     88 
     89 	/**
     90 	 * Creates a new timeline with a 'parallel' behavior. Its children will be
     91 	 * triggered all at once.
     92 	 */
     93 	public static Timeline createParallel() {
     94 		Timeline tl = pool.get();
     95 		tl.setup(Modes.PARALLEL);
     96 		return tl;
     97 	}
     98 
     99 	// -------------------------------------------------------------------------
    100 	// Attributes
    101 	// -------------------------------------------------------------------------
    102 
    103 	private enum Modes {SEQUENCE, PARALLEL}
    104 
    105 	private final List<BaseTween<?>> children = new ArrayList<BaseTween<?>>(10);
    106 	private Timeline current;
    107 	private Timeline parent;
    108 	private Modes mode;
    109 	private boolean isBuilt;
    110 
    111 	// -------------------------------------------------------------------------
    112 	// Setup
    113 	// -------------------------------------------------------------------------
    114 
    115 	private Timeline() {
    116 		reset();
    117 	}
    118 
    119 	@Override
    120 	protected void reset() {
    121 		super.reset();
    122 
    123 		children.clear();
    124 		current = parent = null;
    125 
    126 		isBuilt = false;
    127 	}
    128 
    129 	private void setup(Modes mode) {
    130 		this.mode = mode;
    131 		this.current = this;
    132 	}
    133 
    134 	// -------------------------------------------------------------------------
    135 	// Public API
    136 	// -------------------------------------------------------------------------
    137 
    138 	/**
    139 	 * Adds a Tween to the current timeline.
    140 	 *
    141 	 * @return The current timeline, for chaining instructions.
    142 	 */
    143 	public Timeline push(Tween tween) {
    144 		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
    145 		current.children.add(tween);
    146 		return this;
    147 	}
    148 
    149 	/**
    150 	 * Nests a Timeline in the current one.
    151 	 *
    152 	 * @return The current timeline, for chaining instructions.
    153 	 */
    154 	public Timeline push(Timeline timeline) {
    155 		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
    156 		if (timeline.current != timeline) throw new RuntimeException("You forgot to call a few 'end()' statements in your pushed timeline");
    157 		timeline.parent = current;
    158 		current.children.add(timeline);
    159 		return this;
    160 	}
    161 
    162 	/**
    163 	 * Adds a pause to the timeline. The pause may be negative if you want to
    164 	 * overlap the preceding and following children.
    165 	 *
    166 	 * @param time A positive or negative duration.
    167 	 * @return The current timeline, for chaining instructions.
    168 	 */
    169 	public Timeline pushPause(float time) {
    170 		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
    171 		current.children.add(Tween.mark().delay(time));
    172 		return this;
    173 	}
    174 
    175 	/**
    176 	 * Starts a nested timeline with a 'sequence' behavior. Don't forget to
    177 	 * call {@link end()} to close this nested timeline.
    178 	 *
    179 	 * @return The current timeline, for chaining instructions.
    180 	 */
    181 	public Timeline beginSequence() {
    182 		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
    183 		Timeline tl = pool.get();
    184 		tl.parent = current;
    185 		tl.mode = Modes.SEQUENCE;
    186 		current.children.add(tl);
    187 		current = tl;
    188 		return this;
    189 	}
    190 
    191 	/**
    192 	 * Starts a nested timeline with a 'parallel' behavior. Don't forget to
    193 	 * call {@link end()} to close this nested timeline.
    194 	 *
    195 	 * @return The current timeline, for chaining instructions.
    196 	 */
    197 	public Timeline beginParallel() {
    198 		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
    199 		Timeline tl = pool.get();
    200 		tl.parent = current;
    201 		tl.mode = Modes.PARALLEL;
    202 		current.children.add(tl);
    203 		current = tl;
    204 		return this;
    205 	}
    206 
    207 	/**
    208 	 * Closes the last nested timeline.
    209 	 *
    210 	 * @return The current timeline, for chaining instructions.
    211 	 */
    212 	public Timeline end() {
    213 		if (isBuilt) throw new RuntimeException("You can't push anything to a timeline once it is started");
    214 		if (current == this) throw new RuntimeException("Nothing to end...");
    215 		current = current.parent;
    216 		return this;
    217 	}
    218 
    219 	/**
    220 	 * Gets a list of the timeline children. If the timeline is started, the
    221 	 * list will be immutable.
    222 	 */
    223 	public List<BaseTween<?>> getChildren() {
    224 		if (isBuilt) return Collections.unmodifiableList(current.children);
    225 		else return current.children;
    226 	}
    227 
    228 	// -------------------------------------------------------------------------
    229 	// Overrides
    230 	// -------------------------------------------------------------------------
    231 
    232 	@Override
    233 	public Timeline build() {
    234 		if (isBuilt) return this;
    235 
    236 		duration = 0;
    237 
    238 		for (int i=0; i<children.size(); i++) {
    239 			BaseTween<?> obj = children.get(i);
    240 
    241 			if (obj.getRepeatCount() < 0) throw new RuntimeException("You can't push an object with infinite repetitions in a timeline");
    242 			obj.build();
    243 
    244 			switch (mode) {
    245 				case SEQUENCE:
    246 					float tDelay = duration;
    247 					duration += obj.getFullDuration();
    248 					obj.delay += tDelay;
    249 					break;
    250 
    251 				case PARALLEL:
    252 					duration = Math.max(duration, obj.getFullDuration());
    253 					break;
    254 			}
    255 		}
    256 
    257 		isBuilt = true;
    258 		return this;
    259 	}
    260 
    261 	@Override
    262 	public Timeline start() {
    263 		super.start();
    264 
    265 		for (int i=0; i<children.size(); i++) {
    266 			BaseTween<?> obj = children.get(i);
    267 			obj.start();
    268 		}
    269 
    270 		return this;
    271 	}
    272 
    273 	@Override
    274 	public void free() {
    275 		for (int i=children.size()-1; i>=0; i--) {
    276 			BaseTween<?> obj = children.remove(i);
    277 			obj.free();
    278 		}
    279 
    280 		pool.free(this);
    281 	}
    282 
    283 	@Override
    284 	protected void updateOverride(int step, int lastStep, boolean isIterationStep, float delta) {
    285 		if (!isIterationStep && step > lastStep) {
    286 			assert delta >= 0;
    287 			float dt = isReverse(lastStep) ? -delta-1 : delta+1;
    288 			for (int i=0, n=children.size(); i<n; i++) children.get(i).update(dt);
    289 			return;
    290 		}
    291 
    292 		if (!isIterationStep && step < lastStep) {
    293 			assert delta <= 0;
    294 			float dt = isReverse(lastStep) ? -delta-1 : delta+1;
    295 			for (int i=children.size()-1; i>=0; i--) children.get(i).update(dt);
    296 			return;
    297 		}
    298 
    299 		assert isIterationStep;
    300 
    301 		if (step > lastStep) {
    302 			if (isReverse(step)) {
    303 				forceEndValues();
    304 				for (int i=0, n=children.size(); i<n; i++) children.get(i).update(delta);
    305 			} else {
    306 				forceStartValues();
    307 				for (int i=0, n=children.size(); i<n; i++) children.get(i).update(delta);
    308 			}
    309 
    310 		} else if (step < lastStep) {
    311 			if (isReverse(step)) {
    312 				forceStartValues();
    313 				for (int i=children.size()-1; i>=0; i--) children.get(i).update(delta);
    314 			} else {
    315 				forceEndValues();
    316 				for (int i=children.size()-1; i>=0; i--) children.get(i).update(delta);
    317 			}
    318 
    319 		} else {
    320 			float dt = isReverse(step) ? -delta : delta;
    321 			if (delta >= 0) for (int i=0, n=children.size(); i<n; i++) children.get(i).update(dt);
    322 			else for (int i=children.size()-1; i>=0; i--) children.get(i).update(dt);
    323 		}
    324 	}
    325 
    326 	// -------------------------------------------------------------------------
    327 	// BaseTween impl.
    328 	// -------------------------------------------------------------------------
    329 
    330 	@Override
    331 	protected void forceStartValues() {
    332 		for (int i=children.size()-1; i>=0; i--) {
    333 			BaseTween<?> obj = children.get(i);
    334 			obj.forceToStart();
    335 		}
    336 	}
    337 
    338 	@Override
    339 	protected void forceEndValues() {
    340 		for (int i=0, n=children.size(); i<n; i++) {
    341 			BaseTween<?> obj = children.get(i);
    342 			obj.forceToEnd(duration);
    343 		}
    344 	}
    345 
    346 	@Override
    347 	protected boolean containsTarget(Object target) {
    348 		for (int i=0, n=children.size(); i<n; i++) {
    349 			BaseTween<?> obj = children.get(i);
    350 			if (obj.containsTarget(target)) return true;
    351 		}
    352 		return false;
    353 	}
    354 
    355 	@Override
    356 	protected boolean containsTarget(Object target, int tweenType) {
    357 		for (int i=0, n=children.size(); i<n; i++) {
    358 			BaseTween<?> obj = children.get(i);
    359 			if (obj.containsTarget(target, tweenType)) return true;
    360 		}
    361 		return false;
    362 	}
    363 }
    364