1 /**
2 * Copyright © Novelate 2020
3 * License: MIT (https://github.com/Novelate/NovelateEngine/blob/master/LICENSE)
4 * Author: Jacob Jensen (bausshf)
5 * Website: https://novelate.com/
6 * ------
7 * Novelate is a free and open-source visual novel engine and framework written in the D programming language.
8 * It can be used freely for both personal and commercial projects.
9 * ------
10 * Module Description:
11 * An animated image component is basically a wrapper around multiple images that are displayed in order to create an animation.
12 */
13 module novelate.ui.animatedimage;
14 
15 import std.conv : to;
16 
17 import novelate.ui.component;
18 import novelate.buildstate;
19 
20 /// An animated image component.
21 final class AnimatedImage : Component
22 {
23   private:
24   /// The images for each frame.
25   ExternalImage[] _images;
26   /// The current frame.
27   size_t _currentFrame;
28   /// Boolean determining whether the animation is full-screen or not.
29   bool _fullScreen;
30   /// The alpha channel of the animation.
31   ubyte _alpha;
32   /// The current fade-in amount.
33   ubyte _fadingIn;
34   /// The current fade-out amount.
35   ubyte _fadingOut;
36   /// The animation count.
37   ptrdiff_t _animatorCount;
38   /// The animation speed.
39   ptrdiff_t _animationSpeed;
40 
41   public:
42   final:
43   /**
44   * Creates a new animated image component.
45   * Params:
46   *   paths = The paths of the animation frame images.
47   */
48   this(string[] paths)
49   {
50     foreach (path; paths)
51     {
52       auto externalImage = new ExternalImage;
53       externalImage.loadFromFile(path);
54 
55       _images ~= externalImage;
56     }
57 
58     _alpha = 255;
59     _animatorCount = 1000;
60     _currentFrame = 0;
61     _animationSpeed = 100;
62 
63     auto firstImage = _images[0];
64     auto firstBounds = firstImage.bounds;
65 
66     super(firstBounds.x, firstBounds.y);
67   }
68 
69   @property
70   {
71     /// Gets the animation speed.
72     ptrdiff_t animationSpeed() { return _animationSpeed; }
73 
74     /// Sets the animation speed.
75     void animationSpeed(ptrdiff_t newAnimationSpeed)
76     {
77       _animationSpeed = newAnimationSpeed;
78     }
79 
80     /// Gets a boolean determining whether the animation is full-screen or not.
81     bool fullScreen() { return _fullScreen; }
82 
83     /// Sets a boolean determining whether the animation is full-screen or not. If not initialized with width/height from a layer then refresh() must manually be set.
84     void fullScreen(bool isFullScreen)
85     {
86       _fullScreen = isFullScreen;
87     }
88 
89     /// Gets the alpha channel of the animation.
90     ubyte alpha() { return _alpha; }
91 
92     /// Sets the alpha channel of the animation.
93     void alpha(ubyte newAlpha)
94     {
95       _alpha = newAlpha;
96 
97       foreach (image; _images)
98       {
99         image.color = Paint(255, 255, 255, _alpha);
100       }
101     }
102   }
103 
104   /// A handler for when the animation has faded out.
105   void delegate() fadedOut;
106   /// A handler for when the animation has faded in.
107   void delegate() fadedIn;
108   /// A handler for when the animation is fading out.
109   void delegate(ubyte) fadingOut;
110   /// A handler for when the animation is fading in.
111   void delegate(ubyte) fadingIn;
112 
113   /**
114   * Fades out the animation.
115   * Params:
116   *   speed = The speed at which the animation will fade.
117   */
118   void fadeOut(ubyte speed)
119   {
120     if (_fadingOut)
121     {
122       return;
123     }
124 
125     _fadingIn = 0;
126     _fadingOut = speed;
127     alpha = 255;
128   }
129 
130   /**
131   * Fades in the animation.
132   * Params:
133   *   speed = The speed at which the animation will fade.
134   */
135   void fadeIn(ubyte speed)
136   {
137     if (_fadingIn)
138     {
139       return;
140     }
141 
142     _fadingOut = 0;
143     _fadingIn = speed;
144     alpha = 0;
145   }
146 
147   /// See: Component.render()
148   override void render(ExternalWindow window)
149   {
150     if (_images && _images.length)
151     {
152       if (_currentFrame >= _images.length)
153       {
154         _currentFrame = 0;
155       }
156     }
157 
158     auto sprite = _images && _images.length ? _images[_currentFrame] : null;
159 
160     if (sprite)
161     {
162       _animatorCount -= _animationSpeed;
163 
164       if (_animatorCount <= 0)
165       {
166         _currentFrame++;
167         _animatorCount = 1000;
168       }
169 
170       ptrdiff_t oldAlpha = cast(ptrdiff_t)_alpha;
171 
172       if (_fadingIn && oldAlpha < 255)
173       {
174         oldAlpha += _fadingIn;
175 
176         if (fadingIn)
177         {
178           fadingIn(cast(ubyte)oldAlpha);
179         }
180 
181         if (oldAlpha >= 255)
182         {
183           oldAlpha = 255;
184 
185           if (fadedIn)
186           {
187             fadedIn();
188           }
189         }
190 
191         alpha = cast(ubyte)oldAlpha;
192       }
193       else if (_fadingOut && _alpha > 0)
194       {
195         oldAlpha -= _fadingOut;
196 
197         if (fadingOut)
198         {
199           fadingOut(cast(ubyte)oldAlpha);
200         }
201 
202         if (oldAlpha <= 0)
203         {
204           oldAlpha = 0;
205 
206           if (fadedOut)
207           {
208             fadedOut();
209           }
210         }
211 
212         alpha = cast(ubyte)oldAlpha;
213       }
214 
215       sprite.draw(window);
216     }
217   }
218 
219   /// See: Component.refresh()
220   override void refresh(size_t width, size_t height)
221   {
222     if (!_fullScreen)
223     {
224       return;
225     }
226 
227     foreach (sprite; _images)
228     {
229       auto bounds = sprite.bounds;
230 
231       sprite.scale =
232       FloatVector
233       (
234         cast(int)width / bounds.x,
235         cast(int)height / bounds.y
236       );
237     }
238   }
239 
240   /// See: Component.updateSize()
241   override void updateSize()
242   {
243     if (_fullScreen)
244     {
245       return;
246     }
247 
248     foreach (sprite; _images)
249     {
250       auto bounds = sprite.bounds;
251 
252       sprite.scale =
253       FloatVector
254       (
255         cast(int)super.width / bounds.x,
256         cast(int)super.height / bounds.y
257       );
258     }
259   }
260 
261   /**
262   * Scales and fits the image into a specific size.
263   * Params:
264   *   maxWidth = The maximum width at which the image can fit into.
265   *   maxHeight = The maximum height at which the image can fit into.
266   *   enlarge = A boolean determining whether scaling up is allowed or not.
267   */
268   void fitToSize(double maxWidth, double maxHeight, bool enlarge = false)
269   {
270     import std.math : fmin, round;
271 
272     auto src = super.size;
273 
274     maxWidth = enlarge ? cast(double)maxWidth : fmin(maxWidth, cast(double)src.x);
275     maxHeight = enlarge ? cast(double)maxHeight : fmin(maxHeight, cast(double)src.y);
276 
277     double rnd = fmin(maxWidth / cast(double)src.x, maxHeight / cast(double)src.y);
278 
279     auto newWidth = round(src.x * rnd);
280     auto newHeight = round(src.y * rnd);
281 
282     super.size = FloatVector(newWidth, newHeight);
283   }
284 
285   /// See: Component.updatePosition()
286   override void updatePosition()
287   {
288     foreach (sprite; _images)
289     {
290       sprite.position = super.position;
291     }
292   }
293 
294   static if (isManualMemory)
295   {
296     /// See: Component.clean()
297     override void clean()
298     {
299       if (_images)
300       {
301         foreach (image; _images)
302         {
303           if (image)
304           {
305             image.clean();
306           }
307         }
308       }
309     }
310   }
311 }