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