| /* |
| * Copyright 2008 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.animation.client; |
| |
| import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback; |
| import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle; |
| import com.google.gwt.core.client.Duration; |
| import com.google.gwt.dom.client.Element; |
| |
| /** |
| * An {@link Animation} is a continuous event that updates progressively over |
| * time at a non-fixed frame rate. |
| */ |
| public abstract class Animation { |
| |
| private final AnimationCallback callback = new AnimationCallback() { |
| @Override |
| public void execute(double timestamp) { |
| if (update(timestamp)) { |
| // Schedule the next animation frame. |
| requestHandle = scheduler.requestAnimationFrame(callback, element); |
| } else { |
| requestHandle = null; |
| } |
| } |
| }; |
| |
| /** |
| * The duration of the {@link Animation} in milliseconds. |
| */ |
| private int duration = -1; |
| |
| /** |
| * The element being animated. |
| */ |
| private Element element; |
| |
| /** |
| * Is the animation running, even if it hasn't started yet. |
| */ |
| private boolean isRunning = false; |
| |
| /** |
| * Has the {@link Animation} actually started. |
| */ |
| private boolean isStarted = false; |
| |
| /** |
| * The ID of the pending animation request. |
| */ |
| private AnimationHandle requestHandle; |
| |
| /** |
| * The unique ID of the current run. Used to handle cases where an animation |
| * is restarted within an execution block. |
| */ |
| private int runId = -1; |
| |
| private final AnimationScheduler scheduler; |
| |
| /** |
| * The start time of the {@link Animation}. |
| */ |
| private double startTime = -1; |
| |
| /** |
| * Did the animation start before {@link #cancel()} was called. |
| */ |
| private boolean wasStarted = false; |
| |
| /** |
| * Construct a new {@link Animation}. |
| */ |
| public Animation() { |
| this(AnimationScheduler.get()); |
| } |
| |
| /** |
| * Construct a new {@link AnimationScheduler} using the specified scheduler to |
| * sheduler request frames. |
| * |
| * @param scheduler an {@link AnimationScheduler} instance |
| */ |
| protected Animation(AnimationScheduler scheduler) { |
| this.scheduler = scheduler; |
| } |
| |
| /** |
| * Immediately cancel this animation. If the animation is running or is |
| * scheduled to run, {@link #onCancel()} will be called. |
| */ |
| public void cancel() { |
| // Ignore if the animation is not currently running. |
| if (!isRunning) { |
| return; |
| } |
| |
| // Reset the state. |
| wasStarted = isStarted; // Used by onCancel. |
| element = null; |
| isRunning = false; |
| isStarted = false; |
| |
| // Cancel the animation request. |
| if (requestHandle != null) { |
| requestHandle.cancel(); |
| requestHandle = null; |
| } |
| |
| onCancel(); |
| } |
| |
| /** |
| * Immediately run this animation. If the animation is already running, it |
| * will be canceled first. |
| * <p> |
| * This is equivalent to <code>run(duration, null)</code>. |
| * |
| * @param duration the duration of the animation in milliseconds |
| * @see #run(int, Element) |
| */ |
| public void run(int duration) { |
| run(duration, null); |
| } |
| |
| /** |
| * Immediately run this animation. If the animation is already running, it |
| * will be canceled first. |
| * <p> |
| * If the element is not <code>null</code>, the {@link #onUpdate(double)} |
| * method might be called only if the element may be visible (generally left |
| * at the appreciation of the browser). Otherwise, it will be called |
| * unconditionally. |
| * |
| * @param duration the duration of the animation in milliseconds |
| * @param element the element that visually bounds the entire animation |
| */ |
| public void run(int duration, Element element) { |
| run(duration, Duration.currentTimeMillis(), element); |
| } |
| |
| /** |
| * Run this animation at the given startTime. If the startTime has already |
| * passed, the animation will run synchronously as if it started at the |
| * specified start time. If the animation is already running, it will be |
| * canceled first. |
| * <p> |
| * This is equivalent to <code>run(duration, startTime, null)</code>. |
| * |
| * @param duration the duration of the animation in milliseconds |
| * @param startTime the synchronized start time in milliseconds |
| * @see #run(int, double, Element) |
| */ |
| public void run(int duration, double startTime) { |
| run(duration, startTime, null); |
| } |
| |
| /** |
| * Run this animation at the given startTime. If the startTime has already |
| * passed, the animation will run synchronously as if it started at the |
| * specified start time. If the animation is already running, it will be |
| * canceled first. |
| * <p> |
| * If the element is not <code>null</code>, the {@link #onUpdate(double)} |
| * method might be called only if the element may be visible (generally left |
| * at the appreciation of the browser). Otherwise, it will be called |
| * unconditionally. |
| * |
| * @param duration the duration of the animation in milliseconds |
| * @param startTime the synchronized start time in milliseconds |
| * @param element the element that visually bounds the entire animation |
| */ |
| public void run(int duration, double startTime, Element element) { |
| // Cancel the animation if it is running |
| cancel(); |
| |
| // Save the duration and startTime |
| isRunning = true; |
| isStarted = false; |
| this.duration = duration; |
| this.startTime = startTime; |
| this.element = element; |
| ++runId; |
| |
| // Execute the first callback. |
| callback.execute(Duration.currentTimeMillis()); |
| } |
| |
| /** |
| * Returns true if the animation is running. |
| * Note that animation may be 'running' but no callbacks is executed yet. |
| */ |
| public boolean isRunning() { |
| return isRunning; |
| } |
| |
| /** |
| * Interpolate the linear progress into a more natural easing function. |
| * |
| * Depending on the {@link Animation}, the return value of this method can be |
| * less than 0.0 or greater than 1.0. |
| * |
| * @param progress the linear progress, between 0.0 and 1.0 |
| * @return the interpolated progress |
| */ |
| protected double interpolate(double progress) { |
| return (1 + Math.cos(Math.PI + progress * Math.PI)) / 2; |
| } |
| |
| /** |
| * Called immediately after the animation is canceled. The default |
| * implementation of this method calls {@link #onComplete()} only if the |
| * animation has actually started running. |
| */ |
| protected void onCancel() { |
| if (wasStarted) { |
| onComplete(); |
| } |
| } |
| |
| /** |
| * Called immediately after the animation completes. |
| */ |
| protected void onComplete() { |
| onUpdate(interpolate(1.0)); |
| } |
| |
| /** |
| * Called immediately before the animation starts. |
| */ |
| protected void onStart() { |
| onUpdate(interpolate(0.0)); |
| } |
| |
| /** |
| * Called when the animation should be updated. |
| * |
| * The value of progress is between 0.0 and 1.0 (inclusive) (unless you |
| * override the {@link #interpolate(double)} method to provide a wider range |
| * of values). There is no guarantee that {@link #onUpdate(double)} is called |
| * with 0.0 or 1.0. |
| * If you need to perform setup or tear down procedures, you can override |
| * {@link #onStart()} and {@link #onComplete()}. |
| * |
| * @param progress a double, normally between 0.0 and 1.0 (inclusive) |
| */ |
| protected abstract void onUpdate(double progress); |
| |
| /** |
| * Check if the specified run ID is still being run. |
| * |
| * @param curRunId the current run ID to check |
| * @return true if running, false if canceled or restarted |
| */ |
| private boolean isRunning(int curRunId) { |
| return isRunning && (runId == curRunId); |
| } |
| |
| /** |
| * Update the {@link Animation}. |
| * |
| * @param curTime the current time |
| * @return true if the animation should run again, false if it is complete |
| */ |
| private boolean update(double curTime) { |
| /* |
| * Save the run id. If the runId is incremented during this execution block, |
| * we know that this run has been canceled. |
| */ |
| final int curRunId = runId; |
| |
| boolean finished = curTime >= startTime + duration; |
| if (isStarted && !finished) { |
| // Animation is in progress. |
| double progress = (curTime - startTime) / duration; |
| onUpdate(interpolate(progress)); |
| return isRunning(curRunId); // Check if this run was canceled. |
| } |
| if (!isStarted && curTime >= startTime) { |
| /* |
| * Start the animation. We do not call onUpdate() because onStart() calls |
| * onUpdate() by default. |
| */ |
| isStarted = true; |
| onStart(); |
| if (!isRunning(curRunId)) { |
| // This run was canceled. |
| return false; |
| } |
| // Intentional fall through to possibly end the animation. |
| } |
| if (finished) { |
| // Animation is complete. |
| isRunning = false; |
| isStarted = false; |
| onComplete(); |
| return false; |
| } |
| return true; |
| } |
| } |