/******************************************************************************* * Copyright (c) 2004, 2007 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.ui.internal; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.util.Geometry; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IWorkbenchPreferenceConstants; import org.eclipse.ui.internal.util.PrefUtil; /** * This job creates an animated rectangle that moves from a source rectangle to * a target in a fixed amount of time. To begin the animation, instantiate this * object then call schedule(). * * @since 3.0 */ public class RectangleAnimation extends Job { private static class AnimationFeedbackFactory { /** * Determines whether or not the system being used is * sufficiently fast to support image animations. * * Assumes that a pixel is ~3 bytes * * For now we use a base limitation of 50MB/sec as a * 'reverse blt' rate so that a 2MB size shell can be * captured in under 1/25th of a sec. */ private static final int IMAGE_ANIMATION_THRESHOLD = 25; // Frame captures / Sec private static final int IMAGE_ANIMATION_TEST_LOOP_COUNT = 20; // test the copy 'n' times //private static double framesPerSec = 0.0; public static double getCaptureSpeed(Shell wb) { // OK, capture Rectangle bb = wb.getBounds(); Image backingStore = new Image(wb.getDisplay(), bb); GC gc = new GC(wb); // Loop 'n' times to average the result long startTime = System.currentTimeMillis(); for (int i = 0; i < IMAGE_ANIMATION_TEST_LOOP_COUNT; i++) gc.copyArea(backingStore, bb.x, bb.y); gc.dispose(); long endTime = System.currentTimeMillis(); // get Frames / Sec double fps = IMAGE_ANIMATION_TEST_LOOP_COUNT / ((endTime-startTime) / 1000.0); double pps = fps * (bb.width*bb.height*4); // 4 bytes/pixel System.out.println("FPS: " + fps + " Bytes/sec: " + (long)pps); //$NON-NLS-1$ //$NON-NLS-2$ return fps; } public boolean useImageAnimations(Shell wb) { return getCaptureSpeed(wb) >= IMAGE_ANIMATION_THRESHOLD; } public static DefaultAnimationFeedback createAnimationRenderer(Shell parentShell) { // on the first call test the animation threshold to determine // whether to use image animations or not... // if (framesPerSec == 0.0) // framesPerSec = getCaptureSpeed(parentShell); // // IPreferenceStore preferenceStore = PrefUtil.getAPIPreferenceStore(); // boolean useNewMinMax = preferenceStore.getBoolean(IWorkbenchPreferenceConstants.ENABLE_NEW_MIN_MAX); // // if (useNewMinMax && framesPerSec >= IMAGE_ANIMATION_THRESHOLD) { // return new ImageAnimationFeedback(); // } // return new DefaultAnimationFeedback(); } } // Constants public static final int TICK_TIMER = 1; public static final int FRAME_COUNT = 2; // Animation Parameters private Display display; private boolean enableAnimations; private int timingStyle = TICK_TIMER; private int duration; // Control State private DefaultAnimationFeedback feedbackRenderer; private long stepCount; private long frameCount; private long startTime; private long curTime; private long prevTime; // Macros private boolean done() { return amount() >= 1.0; } public static Rectangle interpolate(Rectangle start, Rectangle end, double amount) { double initialWeight = 1.0 - amount; Rectangle result = new Rectangle((int) (start.x * initialWeight + end.x * amount), (int) (start.y * initialWeight + end.y * amount), (int) (start.width * initialWeight + end.width * amount), (int) (start.height * initialWeight + end.height * amount)); return result; } // Animation Step private Runnable animationStep = new Runnable() { public void run() { // Capture time prevTime = curTime; curTime = System.currentTimeMillis(); // Has the system timer 'ticked'? if (curTime != prevTime) { clockTick(); } if (isUpdateStep()) { updateDisplay(); frameCount++; } stepCount++; } }; /** * Creates an animation that will morph the start rectangle to the end rectangle in the * given number of milliseconds. The animation will take the given number of milliseconds to * complete. * * Note that this is a Job, so you must invoke schedule() before the animation will begin * * @param whereToDraw specifies the composite where the animation will be drawn. Note that * although the start and end rectangles can accept any value in display coordinates, the * actual animation will be clipped to the boundaries of this composite. For this reason, * it is good to select a composite that encloses both the start and end rectangles. * @param start initial rectangle (display coordinates) * @param end final rectangle (display coordinates) * @param duration number of milliseconds over which the animation will run */ public RectangleAnimation(Shell parentShell, Rectangle start, Rectangle end, int duration) { super(WorkbenchMessages.RectangleAnimation_Animating_Rectangle); // if animations aren't on this is a NO-OP IPreferenceStore preferenceStore = PrefUtil.getAPIPreferenceStore(); enableAnimations = preferenceStore.getBoolean(IWorkbenchPreferenceConstants.ENABLE_ANIMATIONS); if (!enableAnimations) { return; } // Capture paraeters display = parentShell.getDisplay(); this.duration = duration; // Don't show the job in monitors setSystem(true); // Pick the renderer (could be a preference...) feedbackRenderer = AnimationFeedbackFactory.createAnimationRenderer(parentShell); // Set it up feedbackRenderer.initialize(parentShell, start, end); // Set the animation's initial state stepCount = 0; //long totalFrames = (long) ((duration / 1000.0) * framesPerSec); curTime = startTime = System.currentTimeMillis(); } public RectangleAnimation(Shell parentShell, Rectangle start, Rectangle end) { this(parentShell, start, end, 400); } public void addStartRect(Rectangle rect) { if (feedbackRenderer != null) feedbackRenderer.addStartRect(rect); } public void addEndRect(Rectangle rect) { if (feedbackRenderer != null) feedbackRenderer.addEndRect(rect); } public void addStartRect(Control ctrl) { Rectangle ctrlBounds = ctrl.getBounds(); Rectangle startRect = Geometry.toDisplay(ctrl.getParent(), ctrlBounds); addStartRect(startRect); } public void addEndRect(Control ctrl) { Rectangle ctrlBounds = ctrl.getBounds(); Rectangle endRect = Geometry.toDisplay(ctrl.getParent(), ctrlBounds); addEndRect(endRect); } /** * */ protected void clockTick() { } /** * @return */ protected boolean isUpdateStep() { switch (timingStyle) { case TICK_TIMER: return prevTime != curTime; case FRAME_COUNT: return true; } return false; } private double amount() { double amount = 0.0; switch (timingStyle) { case TICK_TIMER: amount = (double) (curTime - startTime) / (double) duration; break; case FRAME_COUNT: amount = (double)frameCount / (double)duration; } if (amount > 1.0) amount = 1.0; return amount; } /** * */ protected void updateDisplay() { feedbackRenderer.renderStep(amount()); } /* (non-Javadoc) * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) */ protected IStatus run(IProgressMonitor monitor) { // We use preference value to indicate that the animation should be skipped on this platform. if (!enableAnimations || feedbackRenderer == null) { return Status.OK_STATUS; } // Do we have anything to animate ? boolean isEmpty = feedbackRenderer.getStartRects().size() == 0; if (isEmpty) { return Status.OK_STATUS; } // We're starting, initialize display.syncExec(new Runnable() { public void run() { feedbackRenderer.jobInit(); } }); // Only start the animation timer -after- we've initialized curTime = startTime = System.currentTimeMillis(); while (!done()) { display.syncExec(animationStep); // Don't pin the CPU Thread.yield(); } //System.out.println("Done: " + (curTime-startTime) + " steps: " + stepCount + " frames:" + frameCount); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ // We're done, clean up display.syncExec(new Runnable() { public void run() { feedbackRenderer.dispose(); } }); return Status.OK_STATUS; } }