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