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 * A layer consists of multiple components that have equal importance in terms of rendering and event delegation.
12 */
13 module novelate.screens.layer;
14 
15 import novelate.ui.component;
16 import novelate.buildstate;
17 
18 /// A layer of components.
19 final class Layer
20 {
21   private:
22   /// The screen name.
23   string _screenName;
24   /// The components.
25   Component[string] _components;
26   /// The current mouse position.
27   FloatVector _mousePosition;
28 
29   public:
30   final:
31   package (novelate)
32   {
33     /**
34     * Creates a new layer.
35     * Params:
36     *   screenName = The name of the screen.
37     */
38     this(string screenName)
39     {
40       _screenName = screenName;
41     }
42   }
43 
44   @property
45   {
46     /// Gets the amount of components within the layer.
47     size_t length() { return _components ? _components.length : 0; }
48 
49     /// Gets the screen name.
50     string screenName() { return _screenName; }
51   }
52 
53   static if (isManualMemory)
54   {
55     /// Cleans the layer and all its components.
56     void clean()
57     {
58       foreach (component; _components)
59       {
60         component.clean();
61       }
62     }
63   }
64 
65 
66   /// Clears the layer for its components.
67   void clear()
68   {
69     if (!_components)
70     {
71       return;
72     }
73 
74     static if (isManualMemory)
75     {
76       clean();
77     }
78 
79     _components.clear();
80   }
81 
82   /**
83   * Adds a component to the layer.
84   * Params:
85   *   component = The component to add.
86   *   name = The name of the component. This must be unique for the layer, otherwise an existing component will be replaced.
87   *   cleanOldComponent = Boolean determining whether the old component's memory should be cleaned or not. Only used if "NOVELATE_MANUALMEMORY" is enabled.
88   */
89   void addComponent(Component component, string name, bool cleanOldComponent = true)
90   {
91     static if (isManualMemory)
92     {
93       if (cleanOldComponent && _components && name in _components)
94       {
95         _components[name].clean();
96       }
97     }
98 
99     _components[name] = component;
100   }
101 
102   /**
103   * Removes a component from the layer.
104   * Params:
105   *   name = The name of the component to remove.
106   *   cleanOldComponent = Boolean determining whether the component's memory should be cleaned or not. Only used if "NOVELATE_MANUALMEMORY" is enabled.
107   */
108   void removeComponent(string name, bool cleanOldComponent = true)
109   {
110     if (!_components)
111     {
112       return;
113     }
114 
115     static if (isManualMemory)
116     {
117       if (cleanOldComponent && _components && name in _components)
118       {
119         _components[name].clean();
120       }
121     }
122 
123     _components.remove(name);
124   }
125 
126   /**
127   * Gets a component from the layer. You msut cast to the original component type for it to be useful.
128   * Params:
129   *   name = The name of the component to get.
130   * Returns:
131   *   The component if found, null otherwise.
132   */
133   Component getComponent(string name)
134   {
135     if (!_components)
136     {
137       return null;
138     }
139 
140     return _components.get(name, null);
141   }
142 
143   /**
144   * Will render the components of the layer to the given window.
145   * Params:
146   *   window = The window to render the components at.
147   */
148   void render(ExternalWindow window)
149   {
150     if (!window)
151     {
152       return;
153     }
154 
155     if (!_components)
156     {
157       return;
158     }
159 
160     foreach (k,v; _components)
161     {
162       v.render(window);
163     }
164   }
165 
166   /**
167   * Refreshes the layer and its components with a given width and height. Usually the resolution width and height.
168   * Params:
169   *   width = The width to refresh with.
170   *   height = The height to refresh with.
171   */
172   void refresh(size_t width, size_t height)
173   {
174     if (!_components)
175     {
176       return;
177     }
178 
179     foreach (k,v; _components)
180     {
181       v.refresh(width, height);
182     }
183   }
184 
185   /**
186   * Handles key press events and delegates them to its components.
187   * Params:
188   *   key = The key pressed.
189   *   stopEvent = (ref) Boolean determining whether the event handling should no longer be delegated.
190   */
191   void keyPress(KeyboardKey key, ref bool stopEvent)
192   {
193     if (!_components)
194     {
195       return;
196     }
197 
198     foreach (k,v; _components)
199     {
200       import novelate.state : _activeScreenName;
201 
202       if (_screenName != _activeScreenName)
203       {
204         break;
205       }
206 
207       if (v.globalKeyPress)
208       {
209         v.globalKeyPress(key, stopEvent);
210 
211         if (stopEvent)
212         {
213           break;
214         }
215       }
216 
217       if (v.keyPress && v.intersect(_mousePosition))
218       {
219         v.keyPress(key, stopEvent);
220 
221         if (stopEvent)
222         {
223           break;
224         }
225       }
226     }
227   }
228 
229   /**
230   * Handles key release events and delegates them to its components.
231   * Params:
232   *   key = The key released.
233   *   stopEvent = (ref) Boolean determining whether the event handling should no longer be delegated.
234   */
235   void keyRelease(KeyboardKey key, ref bool stopEvent)
236   {
237     if (!_components)
238     {
239       return;
240     }
241 
242     foreach (k,v; _components)
243     {
244       import novelate.state : _activeScreenName;
245 
246       if (_screenName != _activeScreenName)
247       {
248         break;
249       }
250 
251       if (v.globalKeyRelease)
252       {
253         v.globalKeyRelease(key, stopEvent);
254 
255         if (stopEvent)
256         {
257           break;
258         }
259       }
260 
261       if (v.keyRelease && v.intersect(_mousePosition))
262       {
263         v.keyRelease(key, stopEvent);
264 
265         if (stopEvent)
266         {
267           break;
268         }
269       }
270     }
271   }
272 
273   /**
274   * Handles mouse button press events and delegates them to its components.
275   * Params:
276   *   button = The mouse button pressed.
277   *   stopEvent = (ref) Boolean determining whether the event handling should no longer be delegated.
278   */
279   void mousePress(MouseButton button, ref bool stopEvent)
280   {
281     if (!_components)
282     {
283       return;
284     }
285 
286     foreach (k,v; _components)
287     {
288       import novelate.state : _activeScreenName;
289 
290       if (_screenName != _activeScreenName)
291       {
292         break;
293       }
294 
295       if (v.globalMousePress)
296       {
297         v.globalMousePress(button, stopEvent);
298 
299         if (stopEvent)
300         {
301           break;
302         }
303       }
304 
305       auto mousePosX = ExternalEventState.mouseMoveEvent.x;
306       auto mousePosY = ExternalEventState.mouseMoveEvent.y;
307 
308       if (v.mousePress && v.intersect(FloatVector(mousePosX, mousePosY)))
309       {
310         v.mousePress(button, stopEvent);
311 
312         if (stopEvent)
313         {
314           break;
315         }
316       }
317     }
318   }
319 
320   /**
321   * Handles mouse button release events and delegates them to its components.
322   * Params:
323   *   button = The mouse button released.
324   *   stopEvent = (ref) Boolean determining whether the event handling should no longer be delegated.
325   */
326   void mouseRelease(MouseButton button, ref bool stopEvent)
327   {
328     import novelate.state;
329 
330     if (!_components)
331     {
332       return;
333     }
334 
335     foreach (k,v; _components)
336     {
337       import novelate.state : _activeScreenName;
338 
339       if (_screenName != _activeScreenName)
340       {
341         break;
342       }
343 
344       if (v.globalMouseRelease)
345       {
346         v.globalMouseRelease(button, stopEvent);
347 
348         if (stopEvent)
349         {
350           break;
351         }
352       }
353 
354       auto mousePosX = ExternalEventState.mouseMoveEvent.x;
355       auto mousePosY = ExternalEventState.mouseMoveEvent.y;
356 
357       if (v.mouseRelease && v.intersect(FloatVector(mousePosX, mousePosY)))
358       {
359         v.mouseRelease(button, stopEvent);
360 
361         if (stopEvent)
362         {
363           break;
364         }
365       }
366     }
367   }
368 
369   /**
370   * Handles mouse move events and delegates them to its components.
371   * Params:
372   *   mousePosition = The mouse position.
373   *   stopEvent = (ref) Boolean determining whether the event handling should no longer be delegated.
374   */
375   void mouseMove(FloatVector mousePosition, ref bool stopEvent)
376   {
377     _mousePosition = mousePosition;
378 
379     if (!_components)
380     {
381       return;
382     }
383 
384     foreach (k,v; _components)
385     {
386       import novelate.state : _activeScreenName;
387 
388       if (_screenName != _activeScreenName)
389       {
390         break;
391       }
392 
393       if (v.globalMouseMove)
394       {
395         v.globalMouseMove(_mousePosition, stopEvent);
396 
397         if (stopEvent)
398         {
399           break;
400         }
401       }
402 
403       if (v.mouseMove && v.intersect(_mousePosition))
404       {
405         v.mouseMove(_mousePosition, stopEvent);
406 
407         if (stopEvent)
408         {
409           break;
410         }
411       }
412     }
413   }
414 }