/* ******************************************************************************
* Copyright (c) 2006-2012 XMind Ltd. and others.
*
* This file is a part of XMind 3. XMind releases 3 and
* above are dual-licensed under the Eclipse Public License (EPL),
* which is available at http://www.eclipse.org/legal/epl-v10.html
* and the GNU Lesser General Public License (LGPL),
* which is available at http://www.gnu.org/licenses/lgpl.html
* See http://www.xmind.net/license.html for details.
*
* Contributors:
* XMind Ltd. - initial API and implementation
*******************************************************************************/
package org.xmind.ui.animation;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.LayoutListener;
import org.eclipse.draw2d.UpdateManager;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.swt.widgets.Control;
import org.xmind.gef.IGraphicalViewer;
import org.xmind.gef.part.IGraphicalPart;
import org.xmind.gef.service.GraphicalViewerService;
import org.xmind.gef.service.IAnimationService;
import org.xmind.gef.service.IPlaybackProvider;
import org.xmind.ui.util.UITimer;
public class AnimationService extends GraphicalViewerService implements
IAnimationService {
private static boolean DEBUG = false;
private class LayoutCapturer implements LayoutListener {
public void invalidate(IFigure container) {
if (isRecordingInitial()) {
hookPlayback(container);
}
}
public boolean layout(IFigure container) {
if (isAnimating()) {
return playback(container);
}
return false;
}
public void postLayout(IFigure container) {
if (isRecordingFinal()) {
hookCapture(container);
}
}
public void remove(IFigure child) {
}
public void setConstraint(IFigure child, Object constraint) {
}
}
private class PlaybackTask extends SafeRunnable {
public void run() throws Exception {
if (getViewer().getControl().isDisposed())
return;
if (initialStates != null) {
for (IFigure figure : initialStates.keySet()) {
figure.revalidate();
}
}
if (timer == null || timer.isCanceled())
return;
progress = timer.getCurrentLoop() * 1.0f / (timer.getLoops() + 1);
}
}
private class PlaybackTimer extends UITimer {
private Runnable afterEffect;
public PlaybackTimer(Runnable after) {
super(0, DEBUG ? 300 : 10, DEBUG ? 10 : 3, task);
this.afterEffect = after;
}
@Override
protected void onFinished() {
super.onFinished();
timer = null;
stop();
runAfterEffect();
}
private void runAfterEffect() {
if (afterEffect != null && !getViewer().getControl().isDisposed()) {
SafeRunner.run(new SafeRunnable(
"Error running effect after animation.") { //$NON-NLS-1$
public void run() throws Exception {
afterEffect.run();
}
});
}
}
@Override
protected void onCanceled() {
super.onCanceled();
runAfterEffect();
timer = null;
stop();
}
}
private static final int IDLE = 0;
private static final int RECORDING_INITIAL = 1;
private static final int RECORDING_FINAL = 2;
private static final int PLAYBACK = 3;
private static final IPlaybackProvider DEFAULT_PLAYBACK_PROVIDER = new LayoutPlaybackProvider();
private UpdateManager updateManager;
private int state = IDLE;
private final LayoutCapturer layoutCapturer = new LayoutCapturer();
private Map<IFigure, IGraphicalPart> registry = null;
private IPlaybackProvider playbackProvider = DEFAULT_PLAYBACK_PROVIDER;
private Set<IFigure> keyFigures = null;
private Map<IFigure, Object> initialStates = null;
private Map<IFigure, Object> finalStates = null;
private Set<IFigure> toCapture = null;
private float progress = 0;
private ISafeRunnable task = new PlaybackTask();
private UITimer timer = null;
public AnimationService(IGraphicalViewer viewer) {
super(viewer);
}
protected void hookControl(Control control) {
super.hookControl(control);
if (control instanceof FigureCanvas) {
updateManager = ((FigureCanvas) control).getLightweightSystem()
.getUpdateManager();
} else {
updateManager = null;
}
}
protected void activate() {
}
protected void deactivate() {
stop();
}
public void registerFigure(IFigure figure, IGraphicalPart part) {
figure.addLayoutListener(layoutCapturer);
if (registry == null)
registry = new HashMap<IFigure, IGraphicalPart>();
registry.put(figure, part);
}
public void unregisterFigure(IFigure figure) {
figure.removeLayoutListener(layoutCapturer);
if (registry != null) {
registry.remove(figure);
}
}
public IGraphicalPart getRegisteredPart(IFigure figure) {
return registry == null ? null : registry.get(figure);
}
public boolean isIdle() {
return state == IDLE;
}
public boolean isAnimating() {
return state == PLAYBACK;
}
protected boolean isRecordingInitial() {
return state == RECORDING_INITIAL;
}
protected boolean isRecordingFinal() {
return state == RECORDING_FINAL;
}
public IPlaybackProvider getPlaybackProvider() {
return playbackProvider;
}
public void setPlaybackProvider(IPlaybackProvider playbackProvider) {
if (playbackProvider == null)
playbackProvider = DEFAULT_PLAYBACK_PROVIDER;
this.playbackProvider = playbackProvider;
}
public void start(Runnable keyframeMaker, Runnable beforeEffect,
Runnable afterEffect) {
if (keyframeMaker == null || !isActive() || updateManager == null
|| getViewer().getControl().isDisposed())
return;
stop();
if (!markBegin())
return;
try {
keyframeMaker.run();
} catch (Exception e) {
stop();
return;
}
if (state == IDLE || keyFigures == null || keyFigures.isEmpty()
|| getViewer().getControl().isDisposed()) {
stop();
return;
}
markEnd();
runPlayback(beforeEffect, afterEffect);
}
public void stop() {
if (timer != null) {
timer.cancel();
timer = null;
}
state = IDLE;
SafeRunner.run(task);
initialStates = null;
finalStates = null;
keyFigures = null;
toCapture = null;
state = IDLE;
}
protected boolean markBegin() {
if (state == IDLE) {
state = RECORDING_INITIAL;
initialStates = new HashMap<IFigure, Object>();
finalStates = new HashMap<IFigure, Object>();
keyFigures = new HashSet<IFigure>();
toCapture = new HashSet<IFigure>();
return true;
}
return false;
}
protected void recordInitialState(IFigure figure) {
if (initialStates != null) {
initialStates.put(figure, getCurrentState(figure));
}
}
protected void markEnd() {
state = RECORDING_FINAL;
if (updateManager != null) {
updateManager.performValidation();
}
recordFinalStates();
}
protected Object getCurrentState(IFigure figure) {
return playbackProvider.getState(figure, getRegisteredPart(figure));
}
private void recordFinalStates() {
Iterator<IFigure> figureIterator = keyFigures.iterator();
while (figureIterator.hasNext()) {
IFigure fig = figureIterator.next();
if (toCapture.contains(fig)) {
recordFinalState(fig);
} else {
figureIterator.remove();
}
}
}
protected void recordFinalState(IFigure figure) {
if (finalStates != null) {
finalStates.put(figure, getCurrentState(figure));
}
}
private void runPlayback(final Runnable beforeEffect, Runnable afterEffect) {
if (beforeEffect != null) {
SafeRunner.run(new SafeRunnable(
"Error running effect before animation.") { //$NON-NLS-1$
public void run() throws Exception {
beforeEffect.run();
}
});
}
if (state == IDLE || getViewer().getControl().isDisposed())
return;
state = PLAYBACK;
progress = 0;
SafeRunner.run(task);
timer = new PlaybackTimer(afterEffect);
timer.run();
}
protected boolean playback(IFigure figure) {
if (toCapture != null && toCapture.contains(figure)) {
return doPlayback(figure);
}
return false;
}
protected boolean doPlayback(IFigure figure) {
Map initial = (Map) getInitialState(figure);
Map ending = (Map) getFinalState(figure);
if (initial == null || ending == null)
return false;
return playbackProvider.doPlayback(figure, getRegisteredPart(figure),
initial, ending, progress);
}
protected Object getInitialState(IFigure figure) {
return initialStates == null ? null : initialStates.get(figure);
}
protected Object getFinalState(IFigure figure) {
return finalStates == null ? null : finalStates.get(figure);
}
protected void hookPlayback(IFigure figure) {
if (keyFigures != null) {
if (keyFigures.add(figure)) {
recordInitialState(figure);
}
}
}
protected void hookCapture(IFigure container) {
if (keyFigures != null && keyFigures.contains(container)
&& toCapture != null) {
toCapture.add(container);
}
}
// public void addFigureInitial(IFigure figure) {
// hookPlayback(figure);
// }
//
// public void addFigureFinal(IFigure figure) {
// hookCapture(figure);
// if (toCapture != null && toCapture.contains(figure))
// recordFinalState(figure);
// }
public boolean isAnimating(IFigure figure) {
if (isAnimating())
return getInitialState(figure) != null
&& getFinalState(figure) != null;
if (isRecordingFinal())
return getInitialState(figure) != null;
return false;
}
}