/******************************************************************************* * Copyright (c) 2005, 2010 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.draw2d; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * A utility for coordinating figure animations. During animation, multiple * <i>animators</i> are employed to capture the <em>initial</em> and * <em>final</em> states for one or more figures. The animators then playback * the animation by interpolating the intermediate states for each figure using * the initial and final "keyframes". * <P> * An animator is usually stateless and represents an specific technique. Any * state information is stored by the Animation utility. Therefore, one instance * can be used with multiple figures. Animators hook into the validation * mechanism for figures and connections. These hooks are used to capture the * states, and to intercept the typical layout process to insert the * interpolated state. * <P> * To indicate that animation is desired, clients must call {@link #markBegin()} * prior to invalidating any figures that are to be included in the animation. * After this method is called, changes are made, and {@link #run()} is invoked. * The run method will force a validation pass to capture the final states, and * then commence the animation. The animation is synchronous and the method does * not return until the animation has completed. * * @see LayoutAnimator * @since 3.2 */ public class Animation { static class AnimPair { final Animator animator; final IFigure figure; AnimPair(Animator animator, IFigure figure) { this.animator = animator; this.figure = figure; } public boolean equals(Object obj) { AnimPair pair = (AnimPair) obj; return pair.animator == animator && pair.figure == figure; } public int hashCode() { return animator.hashCode() ^ figure.hashCode(); } } private static final int DEFAULT_DELAY = 250; private static Set figureAnimators; private static Map finalStates; private static Map initialStates; private static final int PLAYBACK = 3; private static float progress; private static final int RECORD_FINAL = 2; private static final int RECORD_INITIAL = 1; private static long startTime; private static int state; private static Set toCapture; private static UpdateManager updateManager; private static void capture() { Iterator keys = figureAnimators.iterator(); while (keys.hasNext()) { AnimPair pair = (AnimPair) keys.next(); if (toCapture.contains(pair)) pair.animator.capture(pair.figure); else keys.remove(); } } static void cleanup() { if (figureAnimators != null) { Iterator keys = figureAnimators.iterator(); while (keys.hasNext()) { AnimPair pair = (AnimPair) keys.next(); pair.animator.tearDown(pair.figure); } } state = 0; step(); // Allow layout to occur normally // updateManager.performUpdate(); initialStates = null; finalStates = null; figureAnimators = null; updateManager = null; toCapture = null; state = 0; } private static void doRun(int duration) { state = RECORD_FINAL; findUpdateManager(); updateManager.performValidation(); capture(); state = PLAYBACK; progress = 0.1f; startTime = System.currentTimeMillis(); notifyPlaybackStarting(); while (progress != 0) { step(); updateManager.performUpdate(); if (progress == 1.0) progress = 0; else { int delta = (int) (System.currentTimeMillis() - startTime); if (delta >= duration) progress = 1f; else progress = 0.1f + 0.9f * delta / duration; } } } private static void findUpdateManager() { AnimPair pair = (AnimPair) figureAnimators.iterator().next(); updateManager = pair.figure.getUpdateManager(); } /** * Returns the final animation state for the given figure. * * @param animator * the animator for the figure * @param figure * the figure being animated * @return the final state * @since 3.2 */ public static Object getFinalState(Animator animator, IFigure figure) { return finalStates.get(new AnimPair(animator, figure)); } /** * Returns the initial animation state for the given animator and figure. If * no state was recorded, <code>null</code> is returned. * * @param animator * the animator for the figure * @param figure * the figure being animated * @return the initial state * @since 3.2 */ public static Object getInitialState(Animator animator, IFigure figure) { return initialStates.get(new AnimPair(animator, figure)); } /** * Returns the animation progress, where 0.0 < progress ≤ 1.0. * * @return the progress of the animation * @since 3.2 */ public static float getProgress() { return progress; } static void hookAnimator(IFigure figure, Animator animator) { AnimPair pair = new AnimPair(animator, figure); if (figureAnimators.add(pair)) animator.init(figure); } static void hookNeedsCapture(IFigure figure, Animator animator) { AnimPair pair = new AnimPair(animator, figure); if (figureAnimators.contains(pair)) toCapture.add(pair); } static boolean hookPlayback(IFigure figure, Animator animator) { if (toCapture.contains(new AnimPair(animator, figure))) return animator.playback(figure); return false; } /** * Returns <code>true</code> if animation is in progress. * * @return <code>true</code> when animating * @since 3.2 */ public static boolean isAnimating() { return state == PLAYBACK; } static boolean isFinalRecording() { return state == RECORD_FINAL; } static boolean isInitialRecording() { return state == RECORD_INITIAL; } /** * Marks the beginning of the animation process. If the beginning has * already been marked, this has no effect. * * @return returns <code>true</code> if beginning was not previously marked * @since 3.2 */ public static boolean markBegin() { if (state == 0) { state = RECORD_INITIAL; initialStates = new HashMap(); finalStates = new HashMap(); figureAnimators = new HashSet(); toCapture = new HashSet(); return true; } return false; } private static void notifyPlaybackStarting() { Iterator keys = figureAnimators.iterator(); while (keys.hasNext()) { AnimPair pair = (AnimPair) keys.next(); pair.animator.playbackStarting(pair.figure); } } static void putFinalState(Animator animator, IFigure key, Object state) { finalStates.put(new AnimPair(animator, key), state); } static void putInitialState(Animator animator, IFigure key, Object state) { initialStates.put(new AnimPair(animator, key), state); } /** * Runs animation using the recommended duration: 250 milliseconds. * * @see #run(int) * @since 3.2 */ public static void run() { run(DEFAULT_DELAY); } /** * Captures the final states for the animation and then plays the animation. * * @param duration * the length of animation in milliseconds * @since 3.2 */ public static void run(int duration) { if (state == 0) return; try { if (!figureAnimators.isEmpty()) doRun(duration); } finally { cleanup(); } } private static void step() { Iterator iter = initialStates.keySet().iterator(); while (iter.hasNext()) ((AnimPair) iter.next()).figure.revalidate(); } }