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 	 */
getPoolSize()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 	 */
ensurePoolCapacity(int minCapacity)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 	 */
createSequence()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 	 */
createParallel()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 
Timeline()115 	private Timeline() {
116 		reset();
117 	}
118 
119 	@Override
reset()120 	protected void reset() {
121 		super.reset();
122 
123 		children.clear();
124 		current = parent = null;
125 
126 		isBuilt = false;
127 	}
128 
setup(Modes mode)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 	 */
push(Tween tween)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 	 */
push(Timeline timeline)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 	 */
pushPause(float time)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 	 */
beginSequence()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 	 */
beginParallel()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 	 */
end()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 	 */
getChildren()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
build()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
start()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
free()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
updateOverride(int step, int lastStep, boolean isIterationStep, float delta)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
forceStartValues()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
forceEndValues()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
containsTarget(Object target)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
containsTarget(Object target, int tweenType)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