blob: 81347d808464728c020a5f322b2250f2dad8c4fb [file] [log] [blame]
/*******************************************************************************
* 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 &#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();
}
}