PageRenderTime 90ms CodeModel.GetById 70ms app.highlight 16ms RepoModel.GetById 1ms app.codeStats 0ms

/edu/uncc/parsets/util/AnimatableProperty.java

https://code.google.com/p/parsets/
Java | 412 lines | 217 code | 52 blank | 143 comment | 44 complexity | 77a3dc18c55e8cb213fdfb8e62f26111 MD5 | raw file
  1package edu.uncc.parsets.util;
  2
  3import java.awt.event.ActionListener;
  4import java.lang.ref.WeakReference;
  5import java.util.ArrayList;
  6import java.util.List;
  7import java.util.Timer;
  8import java.util.TimerTask;
  9import java.util.concurrent.locks.Lock;
 10import java.util.concurrent.locks.ReentrantLock;
 11
 12/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 13 * Copyright (c) 2009, Robert Kosara, Caroline Ziemkiewicz,
 14 *                     and others (see Authors.txt for full list)
 15 * All rights reserved.
 16 * 
 17 * Redistribution and use in source and binary forms, with or without
 18 * modification, are permitted provided that the following conditions are met:
 19 * 
 20 *    * Redistributions of source code must retain the above copyright
 21 *      notice, this list of conditions and the following disclaimer.
 22 *    * Redistributions in binary form must reproduce the above copyright
 23 *      notice, this list of conditions and the following disclaimer in the
 24 *      documentation and/or other materials provided with the distribution.
 25 *    * Neither the name of UNC Charlotte nor the names of its contributors
 26 *      may be used to endorse or promote products derived from this software
 27 *      without specific prior written permission.
 28 *      
 29 * THIS SOFTWARE IS PROVIDED BY ITS AUTHORS ''AS IS'' AND ANY
 30 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 31 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 32 * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
 33 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 34 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 35 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 36 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 37 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 38 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 39\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
 40
 41/**
 42 * Property class that provides the means for animating (i.e., interpolating)
 43 * the value from the old to the new. Largely modeled around the UIView
 44 * animation capabilities in Cocoa Touch/iPhone.
 45 * 
 46 * A property's value can be read using {@link #getValue()} and changed using
 47 * {@link #setValue(int)}.
 48 * 
 49 * Animations are initiated by calling
 50 * {@link #beginAnimations(float, int, ActionListener)}, making changes to
 51 * property values, and then calling {@link #commitAnimations()}. The values of
 52 * all AnimatableProperty objects will be part of that animation. An animation
 53 * can be canceled using {@link #stopAnimations()}.
 54 * 
 55 * The animation is performed so that the steps are on time and delays do not
 56 * accumulate. That means that steps will be skipped if they are more than 10ms
 57 * late. The application therefore cannot count on the number of steps performed
 58 * being the exact number that was specified.
 59 * 
 60 * Use of this class is thread-safe.
 61 * 
 62 * @author Robert Kosara
 63 * 
 64 */
 65public class AnimatableProperty {
 66
 67	public enum SpeedProfile {
 68		linear, linearInSlowOut, slowInLinearOut, slowInSlowOut
 69	};
 70
 71	// static variables
 72
 73	public static int STEPS_PER_SECOND = 30;
 74	
 75	private static List<WeakReference<AnimatableProperty>> properties = new ArrayList<WeakReference<AnimatableProperty>>();
 76
 77	private static boolean animationSetup = false;
 78
 79	private static AnimationListener listener;
 80
 81	private static Timer timer;
 82
 83	private static float numSteps[];
 84
 85	private static int period;
 86
 87	private static Lock propertiesLock = new ReentrantLock();
 88
 89	private static int currentSegment;
 90	
 91	/**
 92	 * The speed profile to use when interpolating the values.
 93	 */
 94	private static SpeedProfile speedProfile = SpeedProfile.slowInSlowOut;
 95
 96	private static int numSegments;
 97
 98	// instance variables
 99
100	private float currentValue;
101	
102	private float values[];
103	
104	private boolean interpolate = false;
105
106	// used to exclude certain propertis from an animation
107	private boolean dontAnimate = false;
108
109	/**
110	 * Create a property with an initial value of 0.
111	 */
112	public AnimatableProperty() {
113		this(0);
114	}
115
116	/**
117	 * Create a property with an initial value.
118	 * 
119	 * @param initialValue
120	 */
121	public AnimatableProperty(float initialValue) {
122		currentValue = initialValue;
123		propertiesLock.lock();
124		try {
125			properties.add(new WeakReference<AnimatableProperty>(this));
126		} finally {
127			propertiesLock.unlock();
128		}
129	}
130
131	/**
132	 * Remove this object from the list of objects to be updated when it is
133	 * about to be destroyed by the garbage collector.
134	 */
135	protected void finalize() throws Throwable {
136		propertiesLock.lock();
137		try {
138			WeakReference<AnimatableProperty> pref = null;
139			for (WeakReference<AnimatableProperty> ref : properties)
140				if (ref.get() == this)
141					pref = ref;
142			properties.remove(pref);
143		} finally {
144			propertiesLock.unlock();
145		}
146		super.finalize();
147	}
148
149	/**
150	 * Begin setup for animations. All changes to all properties between this
151	 * method and {@link #commitAnimations()} will be part of the animation.
152	 * Ongoing animations will be stopped and will not continue unless the
153	 * respective properties' values are changed.
154	 * 
155	 * @param duration
156	 *            Duration of the animation in seconds
157	 * @param segments
158	 * 			  Number of segments the transition will have. A value over 1 is used for keyframe animation.
159	 * @param animationListener
160	 *            Listener to be called at every step of the animation (may be
161	 *            null)
162	 * 
163	 * @see #setValue(int)
164	 */
165	public static void beginAnimations(float duration, int segments, SpeedProfile profile, AnimationListener animationListener) {
166		stopAnimations();
167		animationSetup = true;
168		listener = animationListener;
169		numSegments = segments;
170		speedProfile = profile;
171		currentSegment = 0;
172		period = 1000/STEPS_PER_SECOND;
173		numSteps = new float[segments];
174		for (int i = 0; i < segments; i++)
175			numSteps[i] = (int)(STEPS_PER_SECOND * duration);
176	}
177
178	/**
179	 * Perform animations based on property changes between
180	 * {@link #beginAnimations(float, int, ActionListener)} and calling this
181	 * method. Animations start immediately.
182	 */
183	public static void commitAnimations() {
184		animationSetup = false;
185		currentSegment = 0;
186		timer = new Timer();
187		timer.scheduleAtFixedRate(new StepTask(period), 0, period);
188	}
189
190	/**
191	 * Internal method, should not be called by other classes. Has to be protected (rather than private) so
192	 * it can be called from {@link StepTask#run()}.
193	 */
194	protected static void takeStep(int stepNum) {
195		if (stepNum > numSteps[currentSegment]) {
196			currentSegment++;
197			if (currentSegment == numSegments) {
198				stopAnimations();
199				return;
200			} else
201				stepNum = 0;
202		}
203		float interpolationFactor = pace(stepNum / numSteps[currentSegment]);
204		propertiesLock.lock();
205		try {
206			for (WeakReference<AnimatableProperty> pref : properties) {
207				AnimatableProperty p = pref.get();
208				if (p != null && p.interpolate)
209					p.step(interpolationFactor);
210			}
211		} finally {
212			propertiesLock.unlock();
213		}
214		if (listener != null)
215			listener.animationStep(currentSegment, interpolationFactor);
216	}
217
218	/**
219	 * Stop the animation. If the animation is stopped before all steps have
220	 * been performed, all properties snap to their designated final values.
221	 */
222	public static void stopAnimations() {
223		if (timer != null) {
224			propertiesLock.lock();
225			try {
226				timer.cancel();
227				timer = null;
228				for (WeakReference<AnimatableProperty> pref : properties) {
229					AnimatableProperty p = pref.get();
230					if (p != null) {
231						p.finishAnimation();
232						p.interpolate = false;
233					}
234				}
235			} finally {
236				propertiesLock.unlock();
237			}
238		}
239	}
240
241	/**
242	 * Take a step in the animation. The parameter defines how far along the animation is.
243	 * The only guarantees here are that the first call will be with a value of exactly 0.0f
244	 * and the last call will be with 1.0f.
245	 * 
246	 * @param ifact interpolation factor
247	 */
248	private void step(float ifact) {
249		currentValue = values[currentSegment] + (values[currentSegment+1] - values[currentSegment]) * ifact;
250		if (ifact == 1.0f)
251			values = null;
252	}
253
254	/** Set the value to the final value of the animation. This is to provide
255	 * a reasonable semantics for the case where a new animation begins before
256	 * the previous one ended. The value snaps to the new value, so it is in
257	 * the expected state after the end of the animation.
258	 * 
259	 */
260	private void finishAnimation() {
261		if (values != null)
262			currentValue = values[numSegments];
263		values = null;
264	}
265	
266	/**
267	 * Get this property's current value.
268	 * 
269	 * @return value of this property
270	 * @see #setValue(int)
271	 */
272	public float getValue() {
273		return currentValue;
274	}
275	
276	public float getFutureValue(){
277		
278		if(values != null){
279			return values[values.length-1];
280		}
281		else
282		{
283			return currentValue;
284		}
285		
286	}
287
288	/**
289	 * Set this property's new value. If called between
290	 * {@link #beginAnimations(float, int, ActionListener)} and
291	 * {@link #commitAnimations()}, the change will not be effective
292	 * immediately, but the value will change once the animation starts. If
293	 * called anywhere else, the value changes immediately.
294	 * 
295	 * @param newValue
296	 *            this property's new value
297	 * @see #getValue()
298	 */
299	public void setValue(float newValue) {
300		if (animationSetup == false || dontAnimate )
301			currentValue = newValue;
302		else {
303			if (values == null) {
304				values = new float[numSegments+1];
305				values[0] = currentValue;
306				for (int i = 1; i <= numSegments; i++)
307					values[i] = Float.NaN;
308			}
309			float lastValue = values[0];
310			for (int i = 1; i <= currentSegment; i++)
311				if (Float.isNaN(values[i]))
312					values[i] = lastValue;
313				else
314					lastValue = values[i];
315
316			values[currentSegment+1] = newValue;
317			interpolate = true;
318		}
319	}
320
321	public static void nextSegment(float duration) {
322		currentSegment++;
323		numSteps[currentSegment] = (int)(STEPS_PER_SECOND * duration);
324	}
325	
326	/**
327	 * Pacing function providing slow-in, slow-out animation. Taken from
328	 * prefuse.
329	 */
330	private static float pace(float f) {
331		if (f == 0f)
332			return 0f;
333		else if (f >= 1f)
334			return 1f;
335		else {
336			switch (speedProfile) {
337			case linear:
338				return f;
339
340			case slowInLinearOut:
341				if (f < 0.5f)
342					return sigmoid(f);
343				else
344					return f;
345
346			case linearInSlowOut:
347				if (f > 0.5f)
348					return sigmoid(f);
349				else
350					return f;
351
352			case slowInSlowOut:
353				return sigmoid(f);
354			}
355		}
356		return 0; // just to make Eclipse happy
357	}
358
359	/**
360	 * Computes a normalized sigmoid
361	 * 
362	 * @param x
363	 *            input value in the interval [0,1]
364	 */
365	private static float sigmoid(float x) {
366		x = 12f * x - 6f;
367		return (1f / (1f + (float) Math.exp(-x)));
368	}
369
370	public static int getNumSteps() {
371		return (int)numSteps[currentSegment];
372	}
373	
374	public void setDontAnimate(boolean b) {
375		dontAnimate = b;
376	}
377}
378
379/**
380 * Auxiliary class that just passes events from the timer to static methods.
381 * 
382 */
383class StepTask extends TimerTask {
384
385	private static final int MAX_TARDINESS = 10;
386
387	private int stepNum = 0;
388
389	private long startTime;
390
391	private long periodLength;
392
393	public StepTask(int period) {
394		startTime = System.currentTimeMillis();
395		periodLength = period;
396	}
397
398	@Override
399	public void run() {
400		long tardiness = System.currentTimeMillis() - scheduledExecutionTime();
401		if ((tardiness < MAX_TARDINESS) || (stepNum == AnimatableProperty.getNumSteps()-1)) {
402			AnimatableProperty.takeStep(stepNum);
403		} else
404			PSLogging.logger.info("Skipping animation step "+stepNum+", tardiness = "+tardiness);
405		stepNum++;
406	}
407
408	@Override
409	public long scheduledExecutionTime() {
410		return startTime + (long) stepNum * periodLength;
411	}
412}