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 }