blob: 0c32617c8ae9083a5f05536d7243118aa25bc89b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2006 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 &#8804; 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();
}
}