1 /* 2 * Copyright 2008 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 //-------------------------------------- 17 // utgb-core Project 18 // 19 // Animation.java 20 // Since: Jun 13, 2010 21 // 22 //-------------------------------------- 23 package org.utgenome.gwt.utgb.client.ui; 24 25 import java.util.ArrayList; 26 import java.util.List; 27 28 import com.google.gwt.core.client.Duration; 29 import com.google.gwt.user.client.Timer; 30 31 /** 32 * A modified version of the animation support class included in GWT. An {@link Animation} is a continuous event that 33 * updates progressively over time at a non-fixed frame rate. 34 */ 35 public abstract class Animation { 36 37 protected Animation() { 38 this(25); 39 } 40 41 protected Animation(int default_frame_delay) { 42 DEFAULT_FRAME_DELAY = default_frame_delay; 43 } 44 45 /** 46 * The default time in milliseconds between frames. 47 */ 48 private final int DEFAULT_FRAME_DELAY; 49 50 /** 51 * The {@link Animation Animations} that are currently in progress. 52 */ 53 private static List<Animation> animations = null; 54 55 /** 56 * The {@link Timer} that applies the animations. 57 */ 58 private static Timer animationTimer = null; 59 60 /** 61 * Update all {@link Animation Animations}. 62 */ 63 private void updateAnimations() { 64 // Duplicate the animations list in case it changes as we iterate over it 65 Animation[] curAnimations = new Animation[animations.size()]; 66 curAnimations = animations.toArray(curAnimations); 67 68 // Iterator through the animations 69 double curTime = Duration.currentTimeMillis(); 70 for (Animation animation : curAnimations) { 71 if (animation.running && animation.update(curTime)) { 72 // We can't just remove the animation at the index, because calling 73 // animation.update may have the side effect of canceling this 74 // animation, running new animations, or canceling other animations. 75 animations.remove(animation); 76 } 77 } 78 79 // Reschedule the timer 80 if (animations.size() > 0) { 81 animationTimer.schedule(DEFAULT_FRAME_DELAY); 82 } 83 } 84 85 /** 86 * The duration of the {@link Animation} in milliseconds. 87 */ 88 private int duration = -1; 89 90 /** 91 * Is the {@link Animation} running, even if {@link #onStart()} has not yet been called. 92 */ 93 private boolean running = false; 94 95 /** 96 * Has the {@link Animation} actually started. 97 */ 98 private boolean started = false; 99 100 /** 101 * The start time of the {@link Animation}. 102 */ 103 private double startTime = -1; 104 105 /** 106 * Immediately cancel this animation. If the animation is running or is scheduled to run, {@link #onCancel()} will 107 * be called. 108 */ 109 public void cancel() { 110 // Ignore if the animation is not currently running 111 if (!running) { 112 return; 113 } 114 115 animations.remove(this); 116 onCancel(); 117 started = false; 118 running = false; 119 } 120 121 /** 122 * Immediately run this animation. If the animation is already running, it will be canceled first. 123 * 124 * @param duration 125 * the duration of the animation in milliseconds 126 */ 127 public void run(int duration) { 128 run(duration, Duration.currentTimeMillis()); 129 } 130 131 /** 132 * Run this animation at the given startTime. If the startTime has already passed, the animation will be synchronize 133 * as if it started at the specified start time. If the animation is already running, it will be canceled first. 134 * 135 * @param duration 136 * the duration of the animation in milliseconds 137 * @param startTime 138 * the synchronized start time in milliseconds 139 */ 140 public void run(int duration, double startTime) { 141 // Cancel the animation if it is running 142 cancel(); 143 144 // Save the duration and startTime 145 this.running = true; 146 this.duration = duration; 147 this.startTime = startTime; 148 149 // Start synchronously if start time has passed 150 if (update(Duration.currentTimeMillis())) { 151 return; 152 } 153 154 // Add to the list of animations 155 if (animations == null) { 156 animations = new ArrayList<Animation>(); 157 animationTimer = new Timer() { 158 @Override 159 public void run() { 160 updateAnimations(); 161 } 162 }; 163 } 164 animations.add(this); 165 166 // Restart the timer if there is the only animation 167 if (animations.size() == 1) { 168 animationTimer.schedule(DEFAULT_FRAME_DELAY); 169 } 170 } 171 172 /** 173 * Interpolate the linear progress into a more natural easing function. 174 * 175 * Depending on the {@link Animation}, the return value of this method can be less than 0.0 or greater than 1.0. 176 * 177 * @param progress 178 * the linear progress, between 0.0 and 1.0 179 * @return the interpolated progress 180 */ 181 protected double interpolate(double progress) { 182 return (1 + Math.cos(Math.PI + progress * Math.PI)) / 2; 183 } 184 185 /** 186 * Called immediately after the animation is canceled. The default implementation of this method calls 187 * {@link #onComplete()} only if the animation has actually started running. 188 */ 189 protected void onCancel() { 190 if (started) { 191 onComplete(); 192 } 193 } 194 195 /** 196 * Called immediately after the animation completes. 197 */ 198 protected void onComplete() { 199 onUpdate(interpolate(1.0)); 200 } 201 202 /** 203 * Called immediately before the animation starts. 204 */ 205 protected void onStart() { 206 onUpdate(interpolate(0.0)); 207 } 208 209 /** 210 * Called when the animation should be updated. 211 * 212 * The value of progress is between 0.0 and 1.0 inclusively (unless you override the {@link #interpolate(double)} 213 * method to provide a wider range of values). You can override {@link #onStart()} and {@link #onComplete()} to 214 * perform setup and tear down procedures. 215 */ 216 protected abstract void onUpdate(double progress); 217 218 /** 219 * Update the {@link Animation}. 220 * 221 * @param curTime 222 * the current time 223 * @return true if the animation is complete, false if still running 224 */ 225 private boolean update(double curTime) { 226 boolean finished = curTime >= startTime + duration; 227 if (started && !finished) { 228 // Animation is in progress. 229 double progress = (curTime - startTime) / duration; 230 onUpdate(interpolate(progress)); 231 return false; 232 } 233 if (!started && curTime >= startTime) { 234 // Start the animation. 235 started = true; 236 onStart(); 237 // Intentional fall through to possibly end the animation. 238 } 239 if (finished) { 240 // Animation is complete. 241 onComplete(); 242 started = false; 243 running = false; 244 return true; 245 } 246 return false; 247 } 248 }