1 /**
2  * Copyright:       Copyright (C) 2018 Gabriel Gheorghe, All Rights Reserved
3  * Authors:         $(Gabriel Gheorghe)
4  * License:         $(LINK2 https://www.gnu.org/licenses/gpl-3.0.txt, GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007)
5  * Source:          $(LINK2 https://github.com/GabyForceQ/LibertyEngine/blob/master/source/liberty/framework/gui/impl.d)
6  * Documentation:
7  * Coverage:
8  * TODO:
9  *    - Change action delegate and objEvList dinamically.
10 **/
11 module liberty.framework.gui.impl;
12 
13 import liberty.framework.gui.data;
14 import liberty.graphics.shader.impl;
15 import liberty.math.matrix;
16 import liberty.scene.constants;
17 import liberty.scene.entity;
18 
19 import std.typecons : Tuple;
20 
21 import liberty.logger;
22 import liberty.math.matrix;
23 import liberty.math.util;
24 import liberty.scene.entity;
25 import liberty.core.platform;
26 import liberty.scene.impl;
27 import liberty.framework.gui.event;
28 import liberty.framework.gui.constants;
29 import liberty.framework.gui.widget;
30 import liberty.scene.services;
31 import liberty.framework.gui.controls;
32 import liberty.scene.action;
33 import liberty.graphics.shader.program;
34 
35 import liberty.math.transform;
36 
37 
38 /**
39  * A gui represents a 2-dimensional view containting graphical user interface elements.
40  * Inheriths $(D Entity) class and implements $(D IUpdateable) service.
41 **/
42 abstract class Gui : Entity {
43   private {
44     Shader shader;
45     // getProjection, setProjection
46     GuiProjection projection;
47     // isFixedProjectionEnabled, setFixedProjectionEnabled
48     bool fixedProjectionEnabled;
49     // getProjectionMatrix
50     Matrix4F projectionMatrix = Matrix4F.identity;
51     
52     Canvas rootCanvas;
53     Action!Widget[string] actionMap;
54   }
55   
56   /**
57    * Create a new gui using an id and a parent.
58   **/
59   this(string id) {
60     super(id);
61 
62     shader = Shader
63       .getOrCreate("Gui", (shader) {
64         shader
65           .setViewMatrixEnabled(false)
66           .addCustomRenderMethod((program, self) {
67             program.bind;
68 
69             foreach (node; self.getMap)
70               if (node.visibility == Visibility.Visible) {
71                 (cast(Gui)node).updateProjection(program);
72                 foreach (widget; (cast(Gui)node).getRootCanvas.getWidgets) {
73                   if (widget.getZIndex == 0) {
74                     if (widget.visibility == Visibility.Visible) {
75                       if (widget.model !is null)
76                         program
77                           .loadUniform("uZIndex", 0)
78                           .loadUniform("uModelMatrix", widget.component!Transform.getModelMatrix)
79                           .render(widget.model);
80                     }
81                   }
82                 }
83                 // FILTER Z INDEX FOR NOW WITH ONLY 0 AND 1 --> BUG
84                 foreach (widget; (cast(Gui)node).getRootCanvas.getWidgets) {
85                   if (widget.getZIndex == 1) {
86                     if (widget.visibility == Visibility.Visible) {
87                       if (widget.model !is null)
88                         program
89                           .loadUniform("uZIndex", 1)
90                           .loadUniform("uModelMatrix", widget.component!Transform.getModelMatrix)
91                           .render(widget.model);
92                     }
93                   }
94                 }
95               }
96 
97             program.unbind;
98           });
99       });
100 
101     shader.registerEntity(this);
102     scene.shaderMap[shader.id] = shader;
103     
104     rootCanvas = new Canvas("RootCanvas" ~ id, this);
105     updateProjection(shader.getProgram);
106   }
107 
108   private void updateProjection(ShaderProgram program) {
109     if (!fixedProjectionEnabled) {
110       const tempWidth = Platform.getWindow.getWidth;
111       const tempHeight = Platform.getWindow.getHeight;
112       
113       if (projection.width != tempWidth || projection.height != tempHeight) {
114         projection.width = tempWidth;
115         projection.height = tempHeight;
116       
117         projectionMatrix = MathUtils.getOrthographicMatrixFrom(
118           cast(float)projection.xStart, cast(float)projection.width,
119           cast(float)projection.height, cast(float)projection.yStart,
120           cast(float)projection.zNear, cast(float)projection.zFar
121         );
122         
123         program
124           .bind
125           .loadUniform("uProjectionMatrix", projectionMatrix)
126           .unbind;
127       }
128     }
129   }
130 
131   /**
132    * Returns the projection matrix.
133   **/
134   Matrix4F getProjectionMatrix()   const {
135     return projectionMatrix;
136   }
137 
138   /**
139    *
140   **/
141   override void update() {
142     rootCanvas.update();
143   }
144 
145   /**
146    *
147   **/
148   final Canvas getRootCanvas()   {
149     return rootCanvas;
150   }
151 
152   /**
153    * Add a new action for the current gui using an id, an event,
154    * an array of tuple containing the wanted widget and its event and a priority.
155    * The priority param is optional, its default value is 0.
156    * Returns reference to this so it can be used in a stream.
157   **/
158   Gui addAction(T)(string id, void delegate(Widget, Event) action,
159     Tuple!(T, Event)[] objEvList = null, ubyte priority = 0)
160   do {
161     import std.array : split;
162     import std.traits : EnumMembers;
163 
164     bool possible = false;
165     
166     if (objEvList !is null)
167       static foreach (s; EnumMembers!WidgetType)
168         static if (s == T.stringof.split("!")[0]) {
169           foreach (e; objEvList) {
170             switch (e[1]) with (Event) {
171               static foreach (member; mixin(T.stringof ~ ".getEventArrayString"))
172                 mixin("case " ~ member ~ ": (cast(" ~ s ~ ")e[0]).setOn" ~ member ~
173                   "(action); possible = true; goto END_SWITCH;");
174               default: break;
175             }
176             END_SWITCH:
177           }
178         }
179 
180     possible
181       ? actionMap[id] = new Action!Widget(id, action, priority)
182       : Logger.error("Action with id: " ~ id ~ " can't be created.", typeof(this).stringof);
183     
184     return this;
185   }
186 
187   /**
188    * Simulate an action by starting it right now.
189    * Returns reference to this so it can be used in a stream.
190   **/
191   final Gui simulateAction(string id, Widget sender, Event e) {
192     actionMap[id].callEvent(sender, e);
193     return this;
194   }
195 
196   /**
197    * Remove an user interface action from the memory.
198    * Returns reference to this so it can be used in a stream.
199   **/
200   final Gui removeAction(string id) {
201     actionMap[id].destroy();
202     actionMap[id] = null;
203     actionMap.remove(id);
204     return this;
205   }
206 
207   /**
208    * Returns the action map for user interface elements.
209   **/
210   final Action!Widget[string] getActionMap()   {
211     return actionMap;
212   }
213 
214   /**
215    * Returns an action by given id for user interface elements.
216   **/
217   final Action!Widget getAction(string name)   {
218     return actionMap[name];
219   }
220 
221   /**
222    * Keep window aspect ratio the same.
223    * Returns reference to this so it can be used in a stream.
224   **/
225   final typeof(this) setFixedProjectionEnabled(bool enabled = true)   {
226     fixedProjectionEnabled = enabled;
227     return this;
228   }
229 
230   /**
231    * Returns true if fixed projection is enabled.
232   **/
233   final bool isFixedProjectionEnabled()   const {
234     return fixedProjectionEnabled;
235   }
236 }