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 }