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 }