1 module animate.animate.animation; 2 3 import std.stdio; 4 import std.traits; 5 import core.time; 6 7 import animate.animate.interpolator; 8 9 immutable int INFINITE = -1; 10 11 enum RepeatMode 12 { 13 REPEAT, 14 REVERSE 15 } 16 17 enum AnimationSetMode 18 { 19 PARALLEL, 20 SEQUENTIAL 21 } 22 23 interface Updatable 24 { 25 protected void updateProgress(double progress); 26 public void update(Duration deltaTime); 27 public bool isRunning(); 28 } 29 30 class Animation : Updatable 31 { 32 private 33 { 34 alias AnimListenerFunc = void delegate(); 35 36 Duration m_duration; 37 Duration m_progress; 38 bool m_isRunning; 39 40 Interpolator m_interpolator; 41 42 RepeatMode m_repeatMode; 43 int m_repeatCount; 44 int m_currentRunCount; 45 46 bool m_isReverse; 47 48 AnimListenerFunc[] m_animEndListeners; 49 AnimListenerFunc[] m_animRepeatListeners; 50 } 51 52 this(Duration duration) 53 { 54 m_duration = duration; 55 m_interpolator = new LinearInterpolator(); 56 m_isRunning = true; 57 58 m_repeatMode = RepeatMode.REPEAT; 59 m_isReverse = false; 60 } 61 62 /// This is called with the value (0.0 -> 1.0) of the 63 /// amount that this animation has completed by. 64 protected abstract void updateProgress(double progress); 65 66 /// This takes the delta time since the last update call as the input. 67 final void update(Duration deltaTime) 68 { 69 if(m_isRunning) 70 { 71 m_progress += deltaTime; 72 double progress = cast(double)(m_progress.total!"usecs") / m_duration.total!"usecs"; 73 74 if(progress >= 1.0) 75 { 76 if(m_repeatCount != 0) 77 { 78 while(progress >= 1.0) 79 { 80 progress -= 1.0; 81 ++m_currentRunCount; 82 } 83 84 //Check that we are still in a valid animation frame... 85 if(m_repeatCount > 0 && m_repeatCount < m_currentRunCount) 86 { 87 //We have run out of animation frames, so just leave this at the end animation... 88 final switch(m_repeatMode) 89 { 90 case RepeatMode.REPEAT: 91 progress = 1.0; 92 break; 93 case RepeatMode.REVERSE: 94 m_isReverse = m_repeatCount % 2 == 0; 95 m_progress = usecs(m_duration.total!"usecs"); 96 break; 97 } 98 m_isRunning = false; 99 sendOnAnimationEnd(); 100 } 101 else 102 { 103 //We ARE in a valid animation frame, so update the status accordingly 104 final switch(m_repeatMode) 105 { 106 case RepeatMode.REPEAT: 107 m_progress = usecs(m_progress.total!"usecs" % m_duration.total!"usecs"); 108 break; 109 case RepeatMode.REVERSE: 110 m_isReverse = m_currentRunCount % 2 == 1; 111 m_progress = msecs(m_progress.total!"usecs" % m_duration.total!"usecs"); 112 progress = cast(double)(m_progress.total!"usecs") / m_duration.total!"usecs"; 113 break; 114 } 115 116 sendOnAnimationRepeat(); 117 } 118 } 119 else 120 { 121 progress = 1.0; 122 m_isRunning = false; 123 sendOnAnimationEnd(); 124 } 125 } 126 127 progress = m_isReverse ? 1.0 - progress : progress; 128 129 // interpolate the current progress value 130 progress = m_interpolator.interpolate(progress); 131 132 // send the progress update call to this animation 133 updateProgress(progress); 134 } 135 } 136 137 void addOnAnimationEndListener(AnimListenerFunc listener) 138 { 139 m_animEndListeners ~= listener; 140 } 141 142 private void sendOnAnimationEnd() 143 { 144 foreach(listener; m_animEndListeners) 145 { 146 listener(); 147 } 148 } 149 150 void addOnAnimationRepeatListener(AnimListenerFunc listener) 151 { 152 m_animRepeatListeners ~= listener; 153 } 154 155 private void sendOnAnimationRepeat() 156 { 157 foreach(listener; m_animRepeatListeners) 158 { 159 listener(); 160 } 161 } 162 163 void setInterpolator(Interpolator interpolator) 164 { 165 if(interpolator) 166 { 167 m_interpolator = interpolator; 168 } 169 else if(!m_interpolator) 170 { 171 m_interpolator = new LinearInterpolator(); 172 } 173 } 174 175 final bool isRunning() 176 { 177 return m_isRunning; 178 } 179 180 /// This determines the style of our animation repeat 181 @property 182 { 183 RepeatMode repeatMode(RepeatMode mode) 184 { 185 m_repeatMode = mode; 186 return m_repeatMode; 187 } 188 189 RepeatMode repeatMode() 190 { 191 return m_repeatMode; 192 } 193 } 194 195 /// If the repeat count is negative, then we repeat infinitely. 196 /// Otherwise, we run the animation repeatCount number of times. 197 @property 198 { 199 int repeatCount(int count) 200 { 201 m_repeatCount = count; 202 return m_repeatCount; 203 } 204 205 int repeatCount() 206 { 207 return m_repeatCount; 208 } 209 } 210 } 211 212 /+ 213 + This class animates between two values of type T, 214 + placing the result in the pointer argument. 215 +/ 216 class ValueAnimation(T) : Animation 217 if(isNumeric!T) 218 { 219 private 220 { 221 T m_startVal; 222 T m_difference; 223 T* m_target; 224 } 225 226 this(Duration duration, T* target, T start, T end) 227 { 228 super(duration); 229 assert(target); 230 m_target = target; 231 m_startVal = start; 232 m_difference = end - start; 233 } 234 235 override protected void updateProgress(double progress) 236 { 237 *m_target = cast(T) (m_startVal + (m_difference * progress)); 238 } 239 240 } 241 242 /+ 243 + This class progresses like a normal Animation, but leaves 244 + the details of utilizing the progress updates up to the caller. 245 +/ 246 class DelegateAnimation : Animation 247 { 248 private 249 { 250 void delegate(double) m_update; 251 } 252 253 this(Duration duration, void delegate(double) update) 254 { 255 super(duration); 256 m_update = update; 257 } 258 259 override protected void updateProgress(double progress) 260 { 261 m_update(progress); 262 } 263 264 } 265 266 /+ 267 + This class acts the same as an Animation, but runs an arbitrary 268 + number of Animations either simultaneously or in parallel. 269 +/ 270 class AnimationSet : Updatable 271 { 272 private 273 { 274 Animation[] m_anims; 275 int m_currentAnim; 276 277 AnimationSetMode m_mode; 278 bool m_isRunning; 279 } 280 281 this(Animation[] anims...) 282 { 283 m_anims = anims.dup; 284 m_mode = AnimationSetMode.PARALLEL; 285 m_isRunning = true; 286 } 287 288 void setMode(AnimationSetMode mode) 289 { 290 m_mode = mode; 291 } 292 293 //Does nothing because this doesn't need it... 294 final void updateProgress(double progress) {} 295 296 final void update(Duration deltaT) 297 { 298 if(m_isRunning) 299 { 300 final switch(m_mode) 301 { 302 case AnimationSetMode.PARALLEL: 303 m_isRunning = false; 304 foreach(anim; m_anims) 305 { 306 anim.update(deltaT); 307 m_isRunning = anim.isRunning() || m_isRunning; 308 } 309 break; 310 case AnimationSetMode.SEQUENTIAL: 311 m_anims[m_currentAnim].update(deltaT); 312 if(!m_anims[m_currentAnim].isRunning()) 313 { 314 m_currentAnim++; 315 m_isRunning = m_currentAnim < m_anims.length; 316 } 317 break; 318 } 319 } 320 } 321 322 final bool isRunning() 323 { 324 return m_isRunning; 325 } 326 }