View Javadoc

1   /*
2    * Copyright 2008 Google Inc.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * the License at
7    * 
8    * http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  //--------------------------------------
17  // utgb-core Project
18  //
19  // Animation.java
20  // Since: Jun 13, 2010
21  //
22  //--------------------------------------
23  package org.utgenome.gwt.utgb.client.ui;
24  
25  import java.util.ArrayList;
26  import java.util.List;
27  
28  import com.google.gwt.core.client.Duration;
29  import com.google.gwt.user.client.Timer;
30  
31  /**
32   * A modified version of the animation support class included in GWT. An {@link Animation} is a continuous event that
33   * updates progressively over time at a non-fixed frame rate.
34   */
35  public abstract class Animation {
36  
37  	protected Animation() {
38  		this(25);
39  	}
40  
41  	protected Animation(int default_frame_delay) {
42  		DEFAULT_FRAME_DELAY = default_frame_delay;
43  	}
44  
45  	/**
46  	 * The default time in milliseconds between frames.
47  	 */
48  	private final int DEFAULT_FRAME_DELAY;
49  
50  	/**
51  	 * The {@link Animation Animations} that are currently in progress.
52  	 */
53  	private static List<Animation> animations = null;
54  
55  	/**
56  	 * The {@link Timer} that applies the animations.
57  	 */
58  	private static Timer animationTimer = null;
59  
60  	/**
61  	 * Update all {@link Animation Animations}.
62  	 */
63  	private void updateAnimations() {
64  		// Duplicate the animations list in case it changes as we iterate over it
65  		Animation[] curAnimations = new Animation[animations.size()];
66  		curAnimations = animations.toArray(curAnimations);
67  
68  		// Iterator through the animations
69  		double curTime = Duration.currentTimeMillis();
70  		for (Animation animation : curAnimations) {
71  			if (animation.running && animation.update(curTime)) {
72  				// We can't just remove the animation at the index, because calling
73  				// animation.update may have the side effect of canceling this
74  				// animation, running new animations, or canceling other animations.
75  				animations.remove(animation);
76  			}
77  		}
78  
79  		// Reschedule the timer
80  		if (animations.size() > 0) {
81  			animationTimer.schedule(DEFAULT_FRAME_DELAY);
82  		}
83  	}
84  
85  	/**
86  	 * The duration of the {@link Animation} in milliseconds.
87  	 */
88  	private int duration = -1;
89  
90  	/**
91  	 * Is the {@link Animation} running, even if {@link #onStart()} has not yet been called.
92  	 */
93  	private boolean running = false;
94  
95  	/**
96  	 * Has the {@link Animation} actually started.
97  	 */
98  	private boolean started = false;
99  
100 	/**
101 	 * The start time of the {@link Animation}.
102 	 */
103 	private double startTime = -1;
104 
105 	/**
106 	 * Immediately cancel this animation. If the animation is running or is scheduled to run, {@link #onCancel()} will
107 	 * be called.
108 	 */
109 	public void cancel() {
110 		// Ignore if the animation is not currently running
111 		if (!running) {
112 			return;
113 		}
114 
115 		animations.remove(this);
116 		onCancel();
117 		started = false;
118 		running = false;
119 	}
120 
121 	/**
122 	 * Immediately run this animation. If the animation is already running, it will be canceled first.
123 	 * 
124 	 * @param duration
125 	 *            the duration of the animation in milliseconds
126 	 */
127 	public void run(int duration) {
128 		run(duration, Duration.currentTimeMillis());
129 	}
130 
131 	/**
132 	 * Run this animation at the given startTime. If the startTime has already passed, the animation will be synchronize
133 	 * as if it started at the specified start time. If the animation is already running, it will be canceled first.
134 	 * 
135 	 * @param duration
136 	 *            the duration of the animation in milliseconds
137 	 * @param startTime
138 	 *            the synchronized start time in milliseconds
139 	 */
140 	public void run(int duration, double startTime) {
141 		// Cancel the animation if it is running
142 		cancel();
143 
144 		// Save the duration and startTime
145 		this.running = true;
146 		this.duration = duration;
147 		this.startTime = startTime;
148 
149 		// Start synchronously if start time has passed
150 		if (update(Duration.currentTimeMillis())) {
151 			return;
152 		}
153 
154 		// Add to the list of animations
155 		if (animations == null) {
156 			animations = new ArrayList<Animation>();
157 			animationTimer = new Timer() {
158 				@Override
159 				public void run() {
160 					updateAnimations();
161 				}
162 			};
163 		}
164 		animations.add(this);
165 
166 		// Restart the timer if there is the only animation
167 		if (animations.size() == 1) {
168 			animationTimer.schedule(DEFAULT_FRAME_DELAY);
169 		}
170 	}
171 
172 	/**
173 	 * Interpolate the linear progress into a more natural easing function.
174 	 * 
175 	 * Depending on the {@link Animation}, the return value of this method can be less than 0.0 or greater than 1.0.
176 	 * 
177 	 * @param progress
178 	 *            the linear progress, between 0.0 and 1.0
179 	 * @return the interpolated progress
180 	 */
181 	protected double interpolate(double progress) {
182 		return (1 + Math.cos(Math.PI + progress * Math.PI)) / 2;
183 	}
184 
185 	/**
186 	 * Called immediately after the animation is canceled. The default implementation of this method calls
187 	 * {@link #onComplete()} only if the animation has actually started running.
188 	 */
189 	protected void onCancel() {
190 		if (started) {
191 			onComplete();
192 		}
193 	}
194 
195 	/**
196 	 * Called immediately after the animation completes.
197 	 */
198 	protected void onComplete() {
199 		onUpdate(interpolate(1.0));
200 	}
201 
202 	/**
203 	 * Called immediately before the animation starts.
204 	 */
205 	protected void onStart() {
206 		onUpdate(interpolate(0.0));
207 	}
208 
209 	/**
210 	 * Called when the animation should be updated.
211 	 * 
212 	 * The value of progress is between 0.0 and 1.0 inclusively (unless you override the {@link #interpolate(double)}
213 	 * method to provide a wider range of values). You can override {@link #onStart()} and {@link #onComplete()} to
214 	 * perform setup and tear down procedures.
215 	 */
216 	protected abstract void onUpdate(double progress);
217 
218 	/**
219 	 * Update the {@link Animation}.
220 	 * 
221 	 * @param curTime
222 	 *            the current time
223 	 * @return true if the animation is complete, false if still running
224 	 */
225 	private boolean update(double curTime) {
226 		boolean finished = curTime >= startTime + duration;
227 		if (started && !finished) {
228 			// Animation is in progress.
229 			double progress = (curTime - startTime) / duration;
230 			onUpdate(interpolate(progress));
231 			return false;
232 		}
233 		if (!started && curTime >= startTime) {
234 			// Start the animation.
235 			started = true;
236 			onStart();
237 			// Intentional fall through to possibly end the animation.
238 		}
239 		if (finished) {
240 			// Animation is complete.
241 			onComplete();
242 			started = false;
243 			running = false;
244 			return true;
245 		}
246 		return false;
247 	}
248 }