1package aurelienribon.tweenengine;
2
3import java.util.ArrayList;
4import java.util.Collections;
5import 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 */
46public 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