1 module evael.graphics.gui.controls.Container;
2 
3 import std.math;
4 
5 public import evael.graphics.gui.controls.Control;
6 import evael.graphics.GUI;
7 
8 import evael.utils.Math;
9 import evael.utils.Size;
10 import evael.utils.Rectangle;
11 import evael.utils.Color;
12 
13 abstract class Container : Control
14 {
15     /// Children list
16     protected Control[] m_controls;
17 
18     /// Current focused child
19     protected Control m_focusedControl;
20     protected Control m_controlUnderMouse;
21 
22     /// Current selected control tooltip
23     protected Tooltip m_tooltip;
24 
25     /// Container should be resized if a child control is bigger ?
26     protected bool m_autoResize;
27 
28     protected ScrollBar m_verticalScrollBar, m_horizontalScrollBar;
29 
30     mixin(ControlGetter!("Button"));
31     mixin(ControlGetter!("ListBox"));
32     mixin(ControlGetter!("TextBox"));
33     mixin(ControlGetter!("Window"));
34     mixin(ControlGetter!("PictureBox"));
35     mixin(ControlGetter!("TextArea"));
36     mixin(ControlGetter!("TextBlock"));
37     mixin(ControlGetter!("ProgressBar"));
38 
39     public this()(in auto ref vec2 position, in auto ref Size!int size)
40     {
41         super(position, size);
42 
43         this.m_autoResize = true;
44 
45         this.m_tooltip = new Tooltip();
46         this.m_tooltip.id = int.max;
47         this.m_tooltip.hide();
48 
49         this.addChild(this.m_tooltip);
50     }
51 
52     public override void update(in float deltaTime)
53     {
54         super.update(deltaTime);
55 
56         foreach(control; this.m_controls)
57         {
58             control.update(deltaTime);
59         }
60     }
61 
62     /**
63      * Draws the container with children
64      */
65     public override void draw(in float deltaTime)
66     {
67         if(!this.m_isVisible)
68         {
69             return;
70         }
71 
72         super.draw(deltaTime);
73 
74         foreach(i; 1..this.m_controls.length)
75         {
76             this.m_controls[i].draw(deltaTime);
77         }
78 
79         this.m_tooltip.draw(deltaTime);
80     }
81 
82     /**
83      * Event called on mouse button click
84      * Params:
85      *		mouseButton : mouse button
86      *		mousePosition : mouse position
87      */
88     public override void onMouseClick(in MouseButton mouseButton, in ref vec2 mousePosition)
89     {
90         super.onMouseClick(mouseButton, mousePosition);
91 
92         if(this.m_controlUnderMouse !is null && this.m_controlUnderMouse.isEnabled)
93         {
94             this.m_controlUnderMouse.onMouseClick(mouseButton, mousePosition);
95         }
96     }
97 
98     /**
99      * Event called on mouse button release
100      * Params:
101      *		mouseButton : mouse button
102      */
103     public override void onMouseUp(in MouseButton mouseButton)
104     {
105         super.onMouseUp(mouseButton);
106 
107         if(this.m_controlUnderMouse !is null && this.m_controlUnderMouse.isEnabled)
108         {
109             this.m_controlUnderMouse.onMouseUp(mouseButton);
110 
111             if(this.m_controlUnderMouse.isFocusable)
112             {
113                 this.focusControl(this.m_controlUnderMouse);
114             }
115         }
116     }
117 
118     /**
119      * Event called when mouse enters in control's rect
120      * Params:
121      * 		 mousePosition : mouse position
122      */
123     public override void onMouseMove(in ref vec2 position)
124     {
125         super.onMouseMove(position);
126 
127         if(!this.m_isVisible)
128             return;
129 
130         auto lastControlUnderMouse = this.m_controlUnderMouse;
131 
132         this.m_controlUnderMouse = this.getControlUnderMouse(position);
133 
134         if(this.m_controlUnderMouse !is null)
135         {
136 			this.m_controlUnderMouse.onMouseMove(position);
137 		}
138 
139         // We unfocus last focused item
140         if(lastControlUnderMouse !is null && lastControlUnderMouse != this.m_controlUnderMouse)
141         {
142             lastControlUnderMouse.onMouseLeave();
143         }
144     }
145 
146     /**
147      * Event called when mouse leaves control's rect
148      */
149     public override void onMouseLeave()
150     {
151         super.onMouseLeave();
152 
153         // Mouse is leaving container, we hide the tooltip
154         this.m_tooltip.hide();
155 
156         if(this.m_controlUnderMouse !is null && this.m_controlUnderMouse.isEnabled)
157         {
158             this.m_controlUnderMouse.onMouseLeave();
159         }
160     }
161 
162     /**
163      * Event called on character input
164      * Params:
165      *		text :
166      */
167     public override void onText(in int key)
168     {
169         super.onText(key);
170 
171         if(this.m_focusedControl !is null && this.m_focusedControl.isEnabled)
172         {
173             this.m_focusedControl.onText(key);
174         }
175     }
176 
177 
178     /**
179      * Event called on key input
180      * Params:
181      *		key : pressed key
182      */
183     public override void onKey(in int key)
184     {
185         super.onKey(key);
186 
187         if(this.m_focusedControl !is null && this.m_focusedControl.isEnabled)
188         {
189             this.m_focusedControl.onKey(key);
190         }
191     }
192 
193     /**
194      * Adds child control
195      * Params:
196      *		 control : control to add
197      */
198     public void addChild(Control control)
199     {
200         control.parent = this;
201 
202         // We check if item is added at runtime
203         if(this.m_initialized)
204         {
205             control.nvg = this.m_nvg;
206 
207             if(control.name in this.m_theme.subThemes)
208             {
209                 // This control have a custom theme
210                 control.theme = this.m_theme.subThemes[control.name].copy();
211             }
212             else
213             {
214                 control.theme = this.m_theme.copy();
215             }
216 
217             control.initialize();
218             this.updateChildPosition(control);
219 
220         }
221 
222         this.m_controls ~= control;
223 
224         import std.algorithm : sort;
225 
226         this.m_controls.sort!((a, b) => a.zIndex < b.zIndex);
227     }
228 
229     /**
230      * Returns child control by id
231      * Params:
232      *		 id : id
233      */
234     public T getControl(T : Control)(in uint id) nothrow @nogc
235     {
236         foreach(control; this.m_controls)
237         {
238             if(control.id == id)
239             {
240                 return cast(T)control;
241             }
242         }
243 
244         return null;
245     }
246 
247     /**
248      * Returns the control under the mouse
249      */
250     private Control getControlUnderMouse(in ref vec2 position) nothrow
251     {
252         foreach_reverse(control; this.m_controls)
253         {
254             if(!control.isVisible)
255                 continue;
256 
257             immutable rect = Rectangle!float(control.realPosition.x, control.realPosition.y, control.size);
258 
259             if(rect.isIn(position))
260             {
261                 return control;
262             }
263         }
264 
265         return null;
266     }
267 
268     /**
269      * Initializes control
270      */
271     public override void initialize()
272     {
273         super.initialize();
274 
275         // If we add this container directly in GuiManager, we need to do this
276         if(this.m_name in this.m_theme.subThemes)
277         {
278             this.m_theme =	this.m_theme.subThemes[this.m_name].copy();
279         }
280 
281         foreach(childControl; this.m_controls)
282         {
283             childControl.nvg = this.m_nvg;
284 
285             // write("Searching theme for ", childControl.name);
286 
287             if(childControl.name in this.m_theme.subThemes)
288             {
289                 // writeln("... found! ", this.m_theme.subThemes[childControl.name].name);
290 
291                 // This control have a custom theme
292                 childControl.theme = this.m_theme.subThemes[childControl.name].copy();
293             }
294             else
295             {
296                 // writeln("... not found!");
297 
298                 auto themePtr = this.m_theme.parent;
299 
300                 while(themePtr)
301                 {
302                     if(childControl.name in themePtr.subThemes)
303                     {
304                         childControl.theme = themePtr.subThemes[childControl.name].copy();
305                         break;
306                     }
307 
308                     themePtr = themePtr.parent;
309                 }
310 
311                 if(!themePtr)
312                 {
313                     childControl.theme = this.m_theme.copy();
314                 }
315 
316                 // writeln("Child theme is ", childControl.theme.name);
317             }
318 
319             childControl.initialize();
320 
321             if(this.m_autoResize)
322             {
323                 if(childControl.size.width > this.m_size.width)
324                 {
325                     this.m_size.width = childControl.size.width;
326                 }
327 
328                 if(childControl.size.height > this.m_size.height)
329                 {
330                     this.m_size.height = childControl.size.height;
331                 }
332             }
333 
334             this.updateChildPosition(childControl);
335         }
336     }
337 
338     /**
339      * Resize the container if a child control is bigger
340      */
341     public void reSize()
342     {
343         foreach(childControl; this.m_controls)
344         {
345             if(childControl.size.width > this.m_size.width)
346             {
347                 this.m_size.width = childControl.size.width;
348             }
349 
350             if(childControl.size.height > this.m_size.height)
351             {
352                 this.m_size.height = childControl.size.height;
353             }
354         }
355     }
356 
357 
358     /**
359      * Updates child control position using dock property
360      * Params:
361      *		 childControl : child control
362      */
363     protected void updateChildPosition(Control childControl)
364     {
365         switch(childControl.dock)
366         {
367             case Dock.Fill:
368             {
369                 childControl.position = vec2(0, 0);
370                 childControl.size = this.m_size;
371                 break;
372             }
373             case Dock.Right:
374             {
375                 childControl.position = vec2(this.m_size.width - childControl.size.width, childControl.position.y);
376                 childControl.realPosition = this.m_realPosition + childControl.position;
377 
378                 childControl.size = Size!int(childControl.size.width, childControl.size.height);
379                 break;
380             }
381             case Dock.Top:
382             {
383                 childControl.position = vec2(0, 0);
384                 childControl.realPosition = this.m_realPosition + childControl.position;
385 
386                 childControl.size = Size!int(childControl.size.width, childControl.size.height);
387                 break;
388             }
389             case Dock.Bottom:
390             {
391                 childControl.position = vec2(0, this.m_size.height - childControl.size.height);
392                 childControl.realPosition = this.m_realPosition + childControl.position;
393 
394                 childControl.size = Size!int(this.m_size.width, childControl.size.height);
395                 break;
396             }
397             default:
398                 break;
399         }
400     }
401 
402 
403     /**
404      * Removes a control from control list
405      * Params:
406      *		 control : control to remove
407      */
408     public void remove(Control toRemove)
409     {
410         import std.algorithm : remove;
411 
412         foreach(i, control; this.m_controls)
413         {
414             if(control == toRemove)
415             {
416                 this.m_controls = this.m_controls.remove(i);
417                 return;
418             }
419         }
420     }
421 
422     /**
423      * Gives focus to a control
424      */
425     public void focusControl(Control control)
426     {
427         control.focus();
428 
429         this.m_focusedControl = control;
430     }
431 
432     /**
433      * Removes focus from a control
434      */
435     public void unfocusControl(Control control)
436     {
437         control.unfocus();
438 
439         this.m_focusedControl = null;
440     }
441 
442     @property
443     public override void opacity(in ubyte value)   @nogc
444     {
445         super.opacity = value;
446 
447         foreach(control; this.m_controls)
448         {
449             control.opacity = value;
450         }
451     }
452 
453     @property
454     {
455         public override ref const(vec2) realPosition() const nothrow @nogc
456         {
457             return this.m_realPosition;
458         }
459 
460         public override void realPosition(in vec2 value) nothrow
461         {
462             // We do this because we need to calculate viewport's rect in Control class
463             super.realPosition = value;
464 
465             foreach(childControl; this.m_controls)
466             {
467                 childControl.realPosition = this.m_realPosition + childControl.position;
468             }
469         }
470 
471         public Control[] controls() nothrow @nogc
472         {
473             return this.m_controls;
474         }
475 
476         public Control focusedControl() nothrow @nogc
477         {
478             return this.m_focusedControl;
479         }
480 
481         public Control controlUnderMouse() nothrow @nogc
482         {
483             return this.m_controlUnderMouse;
484         }
485 
486         public Tooltip tooltip() nothrow @nogc
487         {
488             return this.m_tooltip;
489         }
490     }
491 
492     template ControlGetter(string controlClass)
493     {
494         enum ControlGetter =
495             "public " ~ controlClass ~ " get" ~ controlClass ~ "(in uint id) nothrow @nogc
496              {
497                 return this.getControl!(" ~ controlClass ~ ")(id);
498              }";
499     }
500 }