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 * This module handles the screen which is a container of layers with components. 12 */ 13 module novelate.screens.screen; 14 15 import novelate.state; 16 import novelate.screens.layer; 17 import novelate.external; 18 import novelate.events; 19 import novelate.ui.component; 20 import novelate.ui.imagecomponent; 21 import novelate.ui.animatedimage; 22 import novelate.media; 23 import novelate.core : LayerType; 24 import novelate.config : NovelateImageAnimation; 25 import novelate.buildstate; 26 27 /// A screen. 28 abstract class Screen 29 { 30 private: 31 /// The layers of the screen. 32 Layer[] _layers; 33 /// The width of the screen. Usually the resolution width. 34 size_t _width; 35 /// The height of the screen. Usually the resolution height. 36 size_t _height; 37 /// The current mouse position within the window. 38 FloatVector _mousePosition; 39 /// The screen name. 40 string _screenName; 41 42 protected: 43 /** 44 * Creates a new screen. 45 * Params: 46 * screenName = The name of the screen. 47 */ 48 this(string screenName) 49 { 50 _screenName = screenName; 51 52 foreach (_; 0 .. 10) 53 { 54 _layers ~= new Layer(screenName); 55 } 56 } 57 58 /// Clears the old background. 59 private void clearOldBackground() 60 { 61 { 62 auto oldBackground = cast(ImageComponent)getComponent(LayerType.background, "background"); 63 64 if (oldBackground) 65 { 66 addComponent(LayerType.background, oldBackground, "background_old"); 67 removeComponent(LayerType.background, "background", false); 68 oldBackground.fadeOut(20); 69 70 static if (isManualMemory) 71 { 72 oldBackground.fadedOut = () 73 { 74 oldBackground.clean(); 75 }; 76 } 77 } 78 } 79 80 { 81 auto oldBackground = cast(AnimatedImage)getComponent(LayerType.background, "background"); 82 83 if (oldBackground) 84 { 85 addComponent(LayerType.background, oldBackground, "background_old"); 86 removeComponent(LayerType.background, "background", false); 87 oldBackground.fadeOut(20); 88 89 static if (isManualMemory) 90 { 91 oldBackground.fadedOut = () 92 { 93 oldBackground.clean(); 94 }; 95 } 96 } 97 } 98 } 99 100 /** 101 * Updates the background with a media file. 102 * Params: 103 * mediaFile = The name of the media file. 104 */ 105 void updateBackground(string mediaFile) 106 { 107 clearOldBackground(); 108 109 if (!mediaFile || !mediaFile.length) 110 { 111 return; 112 } 113 114 auto backgroundImage = getMediaFile(mediaFile); 115 116 if (!backgroundImage) 117 { 118 return; 119 } 120 121 auto image = new ImageComponent(backgroundImage.relativePath(_width)); 122 image.fadeIn(20); 123 image.fadedIn = () 124 { 125 removeComponent(LayerType.background, "background_old"); 126 }; 127 image.fullScreen = true; 128 image.refresh(_width, _height); 129 130 addComponent(LayerType.background, image, "background"); 131 } 132 133 /** 134 * Updates the background with an animation. 135 * Params: 136 * backgroundAnimation = The background animation to update with. 137 */ 138 void updateBackground(NovelateImageAnimation backgroundAnimation) 139 { 140 clearOldBackground(); 141 142 if (!backgroundAnimation || !backgroundAnimation.frames || !backgroundAnimation.frames.length) 143 { 144 return; 145 } 146 147 string[] backgroundImages = []; 148 149 auto frameSpeed = backgroundAnimation.frames[0].nextFrameTime; 150 151 foreach (frame; backgroundAnimation.frames) 152 { 153 backgroundImages ~= getMediaFile(frame.image).relativePath(_width); 154 } 155 156 if (!backgroundImages || !backgroundImages.length) 157 { 158 return; 159 } 160 161 auto image = new AnimatedImage(backgroundImages); 162 image.animationSpeed = frameSpeed; 163 image.fadeIn(20); 164 image.fadedIn = () 165 { 166 removeComponent(LayerType.background, "background_old"); 167 }; 168 image.fullScreen = true; 169 image.refresh(_width, _height); 170 171 addComponent(LayerType.background, image, "background"); 172 } 173 174 package(novelate) 175 { 176 static if (isManualMemory) 177 { 178 /// Cleans the screen for its native objects. 179 void clean() 180 { 181 foreach (layer; _layers) 182 { 183 layer.clean(); 184 } 185 } 186 } 187 188 /** 189 * Sets the width and height of the screen. 190 * Params: 191 * width = The width. 192 * height = The height. 193 */ 194 void setWidthAndHeight(size_t width, size_t height) 195 { 196 _width = width; 197 _height = height; 198 } 199 200 /// Clears all layer components except for the background. 201 void clearAllLayersButBackground() 202 { 203 if (!_layers || !_layers.length) 204 { 205 return; 206 } 207 208 foreach (i; 1 .. _layers.length) 209 { 210 _layers[i].clear(); 211 } 212 213 fireEvent!(EventType.onClearingAllLayersButBackground); 214 } 215 } 216 217 public: 218 /** 219 * A function that tells the engine whether the screen should clear layers before updating and becoming active. 220 * Params: 221 * data = The data passed to the screen. Use the data to validate only. 222 * Returns: 223 * True if the screen should clear layers, false otherwise. (Defaults to true when not override.) 224 */ 225 bool shouldClearLayers(string[] data) 226 { 227 return true; 228 } 229 230 final 231 { 232 @property 233 { 234 /// Gets the width of the screen. Usually the resolution width. 235 size_t width() { return _width; } 236 237 /// Gets the height of the screen. Usually the resolution height. 238 size_t height() { return _height; } 239 240 /// Gets the screen name. 241 string name() { return _screenName; } 242 } 243 244 /** 245 * Adds a component to a layer. 246 * Params: 247 * index = The index of the layer to add the component to. 248 * component = The component to add. 249 * name = The name of the component. This must be unique for the layer, otherwise an existing component will be replaced. 250 * cleanOldComponent = Boolean determining whether the old component's memory should be cleaned or not. Only used if "NOVELATE_MANUALMEMORY" is enabled. 251 */ 252 void addComponent(size_t index, Component component, string name, bool cleanOldComponent = true) 253 { 254 if (!_layers || index >= _layers.length) 255 { 256 return; 257 } 258 259 _layers[index].addComponent(component, name, cleanOldComponent); 260 } 261 262 /** 263 * Removes a component from a layer. 264 * Params: 265 * index = The index of the layer to remove the component from. 266 * name = The name of the component to remove. 267 * cleanOldComponent = Boolean determining whether the component's memory should be cleaned or not. Only used if "NOVELATE_MANUALMEMORY" is enabled. 268 */ 269 void removeComponent(size_t index, string name, bool cleanOldComponent = true) 270 { 271 if (!_layers || index >= _layers.length) 272 { 273 return; 274 } 275 276 _layers[index].removeComponent(name, cleanOldComponent); 277 } 278 279 /** 280 * Gets a component from a layer. You msut cast to the original component type for it to be useful. 281 * Params: 282 * name = The name of the component to get. 283 * Returns: 284 * The component if found, null otherwise. 285 */ 286 Component getComponent(size_t index, string name) 287 { 288 if (!_layers || index >= _layers.length) 289 { 290 return null; 291 } 292 293 return _layers[index].getComponent(name); 294 } 295 296 /** 297 * Gets a layer of the screen. 298 * Params: 299 * index = The index of the layer. 300 * Returns: 301 * The layer if present at the index given, null otherwise. 302 */ 303 Layer getLayer(size_t index) 304 { 305 if (!_layers || index >= _layers.length) 306 { 307 return null; 308 } 309 310 return _layers[index]; 311 } 312 313 /** 314 * Clears a layer of its components. 315 * Params: 316 * index = The index of the layer. 317 */ 318 void clear(size_t index) 319 { 320 if (!_layers || index >= _layers.length) 321 { 322 return; 323 } 324 325 _layers[index].clear(); 326 } 327 328 /** 329 * Will render the screen, its layers and their components. 330 * Params: 331 * window = The window to render at. 332 */ 333 void render(ExternalWindow window) 334 { 335 if (!window) 336 { 337 return; 338 } 339 340 if (!_layers) 341 { 342 return; 343 } 344 345 foreach (layer; _layers) 346 { 347 layer.render(_window); 348 } 349 } 350 351 /** 352 * Refreshes the screen and its layers and their components with a given width and height. Usually the resolution width and height. 353 * Params: 354 * width = The width to refresh with. 355 * height = The height to refresh with. 356 */ 357 void refresh(size_t width, size_t height) 358 { 359 _width = width; 360 _height = height; 361 362 if (!_layers) 363 { 364 return; 365 } 366 367 foreach (layer; _layers) 368 { 369 layer.refresh(_width, _height); 370 } 371 } 372 373 /** 374 * Handles key press events and delegates them to its layers. 375 * Params: 376 * key = The key pressed. 377 */ 378 void keyPress(KeyboardKey key) 379 { 380 if (!_layers) 381 { 382 return; 383 } 384 385 foreach_reverse (layer; _layers) 386 { 387 if (_screenName != _activeScreenName) 388 { 389 break; 390 } 391 392 bool stopEvent; 393 layer.keyPress(key, stopEvent); 394 395 if (stopEvent) 396 { 397 return; 398 } 399 } 400 } 401 402 /** 403 * Handles key release events and delegates them to its layers. 404 * Params: 405 * key = The key released. 406 */ 407 void keyRelease(KeyboardKey key) 408 { 409 if (!_layers) 410 { 411 return; 412 } 413 414 foreach_reverse (layer; _layers) 415 { 416 if (_screenName != _activeScreenName) 417 { 418 break; 419 } 420 421 bool stopEvent; 422 layer.keyRelease(key, stopEvent); 423 424 if (stopEvent) 425 { 426 return; 427 } 428 } 429 } 430 431 /** 432 * Handles mouse button press events and delegates them to its layers. 433 * Params: 434 * button = The mouse button pressed. 435 */ 436 void mousePress(MouseButton button) 437 { 438 if (!_layers) 439 { 440 return; 441 } 442 443 foreach_reverse (layer; _layers) 444 { 445 if (_screenName != _activeScreenName) 446 { 447 break; 448 } 449 450 bool stopEvent; 451 layer.mousePress(button, stopEvent); 452 453 if (stopEvent) 454 { 455 return; 456 } 457 } 458 } 459 460 /** 461 * Handles mouse button release events and delegates them to its layers. 462 * Params: 463 * button = The mouse button released. 464 */ 465 void mouseRelease(MouseButton button) 466 { 467 import novelate.state; 468 469 if (!_layers) 470 { 471 return; 472 } 473 474 foreach_reverse (layer; _layers) 475 { 476 if (_screenName != _activeScreenName) 477 { 478 break; 479 } 480 481 bool stopEvent; 482 layer.mouseRelease(button, stopEvent); 483 484 if (stopEvent) 485 { 486 return; 487 } 488 } 489 } 490 491 /** 492 * Handles mouse move events and delegates them to its layers. 493 * Params: 494 * x = The x coordinate of the mouse cursor. 495 * y = The y coordinate of the mouse cursor. 496 */ 497 void mouseMove(ptrdiff_t x, ptrdiff_t y) 498 { 499 _mousePosition = FloatVector(x, y); 500 501 if (!_layers) 502 { 503 return; 504 } 505 506 foreach_reverse (layer; _layers) 507 { 508 if (_screenName != _activeScreenName) 509 { 510 break; 511 } 512 513 bool stopEvent; 514 layer.mouseMove(_mousePosition, stopEvent); 515 516 if (stopEvent) 517 { 518 return; 519 } 520 } 521 } 522 } 523 524 abstract: 525 /** 526 * Updates the screen with data. 527 * Params: 528 * data = The data to update the screen with. 529 */ 530 void update(string[] data); 531 }