| /******************************************************************************* |
| * 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; |
| } |
| } |