/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

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