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 }