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