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