/*******************************************************************************
* Copyright (c) 2010-2015 Henshin developers. 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:
* TU Berlin, University of Luxembourg, SES S.A.
*******************************************************************************/
package de.tub.tfs.muvitor.animation;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.ScalableFreeformLayeredPane;
import org.eclipse.draw2d.UpdateManager;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.internal.Workbench;
/**
* This class is a modified version of org.eclipse.draw2d.Animation to work with
* multiple {@link UpdateManager}s. <br>
* Additionally there are slight modifications, especially in
* {@link #doRun(int)} so that performValidation() is not been called on the
* update managers. Instead {@link #hookNeedsCapture(IFigure)} has to be called
* manually which is done in {@link AnimatedElement}. <br>
* This class is optimized for use with {@link AnimatedElement} and
* {@link AnimatedElement} in an {@link AnimatingCommand}!
*
* <p>
* This class is not intended for direct usage. It will be used properly by
* {@link AnimatingCommand}s.
* </p>
*
* @author Tony Modica
*/
@SuppressWarnings("restriction")
final class MultipleAnimation {
final private static Set<ScalableFreeformLayeredPane> animatedPanes = new HashSet<ScalableFreeformLayeredPane>();
private static final int IDLE = 0;
private static final int PLAYBACK = 3;
private static final int RECORD_FINAL = 2;
private static final int RECORD_INITIAL = 1;
private static int state;
final static Map<ScalableFreeformLayeredPane, Map<IFigure, Rectangle>> finalStates = new HashMap<ScalableFreeformLayeredPane, Map<IFigure, Rectangle>>();
final static Map<ScalableFreeformLayeredPane, Map<IFigure, Rectangle>> initialStates = new HashMap<ScalableFreeformLayeredPane, Map<IFigure, Rectangle>>();
static float progress;
final static Set<IFigure> toCapture = new HashSet<IFigure>();
/**
* @param duration
*/
private static synchronized void doRun(final int duration) {
state = RECORD_FINAL;
// collect update managers
final Set<UpdateManager> updateManagers = new HashSet<UpdateManager>();
for (final ScalableFreeformLayeredPane pane : animatedPanes) {
updateManagers.add(pane.getUpdateManager());
}
/**
* No validation is done here, instead
* MultipleAnimation.hookNeedsCapture(parent, Animator.getDefault())
* should have been called in AnimatedElement.prepareStep(int)
*/
// for (UpdateManager updateManager : updateManagers) {
// updateManager.performValidation(); }
// capture
for (final Iterator<ScalableFreeformLayeredPane> it = animatedPanes.iterator(); it
.hasNext();) {
final ScalableFreeformLayeredPane pane = it.next();
if (toCapture.contains(pane)) {
// capture figure, record final state of figure, put final state
finalStates.put(pane, getCurrentState(pane));
} else {
it.remove();
}
}
state = PLAYBACK;
progress = 0;
final long startTime = System.currentTimeMillis();
/*
* Fixed this: For smoother animation, progress starts now with 0 and
* not with 0.1f!
*/
final Display display = Workbench.getInstance().getDisplay();
while (progress != -1) {
// step
for (final ScalableFreeformLayeredPane pane : initialStates.keySet()) {
pane.revalidate();
}
for (final UpdateManager updateManager : updateManagers) {
updateManager.performUpdate();
}
// FIXED: this prevents lagging animation and increases GUI's
// responsiveness while animation
while (display.readAndDispatch()) {
;
}
if (progress == 1.0) {
progress = -1;
} else {
final int delta = (int) (System.currentTimeMillis() - startTime);
if (delta >= duration) {
progress = 1f;
} else {
progress = 1f * delta / duration;
}
}
}
}
/**
* Returns an object encapsulating the placement of children in a container.
* This method is called to capture both the initial and final states.
*
* @param container
* the container figure
* @return the current state
* @since 3.2
*/
@SuppressWarnings("unchecked")
final static private Map<IFigure, Rectangle> getCurrentState(final IFigure container) {
final Map<IFigure, Rectangle> locations = new HashMap<IFigure, Rectangle>();
for (final IFigure child : (List<IFigure>) container.getChildren()) {
locations.put(child, child.getBounds().getCopy());
}
return locations;
}
/**
* Called when being in state {@link #RECORD_FINAL} and container is being
* layouted.
*
* @param pane
*/
final static void hookNeedsCapture(final IFigure pane) {
if (animatedPanes.contains(pane)) {
toCapture.add(pane);
}
}
/**
* Called when being in state {@link #RECORD_INITIAL} and container is being
* invalidated.
*
* @param pane
*/
final static void hookPane(final ScalableFreeformLayeredPane pane) {
if (animatedPanes.add(pane)) {
// init figure, record initial state of figure, put initial state
initialStates.put(pane, getCurrentState(pane));
}
}
/**
* Returns <code>true</code> if muvitorkit.animation is in progress.
*
* @return <code>true</code> when animating
* @since 3.2
*/
final static boolean isAnimating() {
return state == PLAYBACK;
}
// final static boolean isFinalRecording() {
// return state == RECORD_FINAL;
// }
final static boolean isInitialRecording() {
return state == RECORD_INITIAL;
}
/**
* Marks the beginning of the muvitorkit.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
*/
final static boolean markBegin() {
if (state == IDLE) {
state = RECORD_INITIAL;
return true;
}
return false;
}
/**
* Captures the final states for the animation and then plays the animation.
*
* @param duration
* the length of animation in milliseconds
*/
final static void run(final int duration) {
if (state == IDLE) {
return;
}
try {
if (!animatedPanes.isEmpty()) {
doRun(duration);
}
} finally {
// cleanup
state = IDLE;
// step
for (final ScalableFreeformLayeredPane pane : initialStates.keySet()) {
pane.revalidate();
}
/*
* Allow layout to occur normally (?) updateManager.performUpdate();
*/
initialStates.clear();
finalStates.clear();
animatedPanes.clear();
toCapture.clear();
}
}
private MultipleAnimation() {
}
}