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 }