1 module gui.manager;
2 
3 import app;
4 
5 import cimgui.cimgui;
6 
7 import bubel.ecs.entity;
8 import bubel.ecs.manager;
9 import bubel.ecs.std;
10 import bubel.ecs.system;
11 import bubel.ecs.vector;
12 
13 import ecs_utils.math.vector;
14 
15 import gui.attributes;
16 import gui.component;
17 import gui.system;
18 import gui.template_;
19 
20 import std.traits;
21 
22 extern(C):
23 
24 struct GUIManager
25 {
26     Vector!SystemGUI systems;
27     Vector!ComponentGUI components;
28     Vector!TemplateGUI templates;
29     Vector!ComponentEditGUI edit_components;
30     Vector!bool filter;
31     Vector!ushort filter_list;
32 
33     int selected_template = 0;
34     int selected_component = 0;
35 
36     ~this()
37     {
38         clear();
39     }
40 
41     void selectTemplate(int id)
42     {
43         if(templates.length == 0)return;
44         selected_template = id;
45         while(selected_template < 0)selected_template += cast(int)templates.length;
46         while(selected_template >= templates.length)selected_template -= cast(int)templates.length;
47     }
48 
49     void selectComponent(int id)
50     {
51         if(components.length == 0)return;
52         selected_component = id;
53         while(selected_component < 0)selected_component += cast(int)components.length;
54         while(selected_component >= components.length)selected_component -= cast(int)components.length;
55     }
56     
57     void clear()
58     {
59         foreach(tmpl; templates)
60         {
61             gEntityManager.freeTemplate(tmpl.tmpl);
62         }
63         foreach(comp; components)
64         {
65             free(comp.data);
66         }
67         foreach(ref comp; edit_components)
68         {
69             if(comp.variables)Mallocator.dispose(comp.variables);
70             comp.variables = null;
71             comp.used = 0;
72             comp.name = null;
73         }
74         foreach(ref comp; filter)
75         {
76             comp = false;
77         }
78 
79         filter_list.clear();
80         systems.clear();
81         templates.clear();
82         components.clear();
83         selected_template = 0;
84         selected_component = 0;
85     }
86 
87     EntityTemplate* getSelectedTemplate()
88     {
89         if(templates.length > selected_template)return templates[selected_template].tmpl;
90         else return null;
91     }
92 
93     ComponentRef getSelectedComponent()
94     {
95         if(components.length > selected_component)return ComponentRef(components[selected_component].data, components[selected_component].component_id);
96         else return ComponentRef(null, ushort.max);
97     }
98 
99     void addSystem(ushort id, const (char)* name, bool enabled = true)
100     {
101         foreach(ref sys; systems)
102         {
103             if(sys.id == id)return;
104         }
105         System* system = gEntityManager.getSystem(id);
106         //const (char)* name = 
107         systems.add(SystemGUI(name,id,enabled));
108     }
109 
110     void addTemplate(ushort[] components, const (char)* name)
111     {
112         templates.add(TemplateGUI(name, gEntityManager.allocateTemplate(components)));
113     }
114 
115     void addTemplate(EntityTemplate* tmpl, const (char)* name)
116     {
117         templates.add(TemplateGUI(name, tmpl));
118     }
119 
120     // void addComponent(ComponentRef comp, const (char)* name)
121     // {
122     //     uint size = gEntityManager.components[becsID(comp)].size;
123     //     void* data = malloc(size);
124     //     memcpy(data, comp.ptr, size);
125     //     components.add(ComponentGUI(name, data, becsID(comp)));
126     // }
127 
128     void addComponent(T)(T comp, const (char)* name)
129     {
130         uint size = gEntityManager.components[becsID(comp)].size;
131         void* data = malloc(size);
132         memcpy(data, &comp, size);
133         components.add(ComponentGUI(name, data, becsID(comp)));
134 
135         if(edit_components.length <= becsID(comp))
136         {
137             edit_components.length = becsID(comp)+1;//.extend(becsID(comp) + 1);
138         }
139         //edit_components[becsID(comp)] = ComponentEditGUI(name);
140         if(edit_components[becsID(comp)].variables)return;
141         ComponentEditGUI comp_edit;
142         comp_edit.name = T.stringof;
143         //enum fields = __traits(allMembers, T);
144         alias fields = FieldNameTuple!T;
145         //pragma(msg,fields);
146         comp_edit.variables = Mallocator.makeArray!VariableGUI(fields.length);
147         foreach(member_str; fields)
148         {
149             alias member = __traits(getMember, T, member_str);
150             alias member_type = typeof(member);
151             //pragma(msg,member);
152             //pragma(msg,member_type);
153             //pragma(msg,__traits(getMember, T, member).offsetof);
154             ushort offset = member.offsetof;//cast(ushort)__traits(getMember, T, member).offsetof;
155             
156             static if(is(member_type == vec2))
157             {
158                 comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.vec2,member_str,offset);
159             }
160             else static if(is(member_type == ivec2))
161             {
162                 comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.ivec2,member_str,offset);
163             }
164             else static if(is(member_type == vec4))
165             {
166                 comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.vec4,member_str,offset);
167             }
168             else static if(is(member_type == ivec4))
169             {
170                 comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.ivec4,member_str,offset);
171             }
172             else static if(__traits(isIntegral,member_type))
173             {
174                 static if(__traits(isUnsigned, member_type))
175                 {
176                     static if(hasUDA!(member,GUIColor))
177                     {
178                         comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.color,member_str,offset);
179                     }
180                     else switch(member_type.sizeof)
181                     {
182                         case 1: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.ubyte_,member_str,offset);break;
183                         case 2: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.ushort_,member_str,offset);break;
184                         case 4: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.uint_,member_str,offset);break;
185                         default:break;
186                     }
187                     static if(hasUDA!(member,GUIRange))
188                     {
189                         comp_edit.variables[comp_edit.used-1].int_.min = getUDAs!(member,GUIRange)[0].min;
190                         comp_edit.variables[comp_edit.used-1].int_.max = getUDAs!(member,GUIRange)[0].max;
191                     }
192                     else
193                     {
194                         comp_edit.variables[comp_edit.used-1].int_.min = 0;
195                         comp_edit.variables[comp_edit.used-1].int_.max = int.max;
196                     }
197                 }
198                 else
199                 {
200                     switch(member_type.sizeof)
201                     {
202                         case 1: 
203                             comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.byte_,member_str,offset);
204                             comp_edit.variables[comp_edit.used-1].int_.min = byte.min;
205                             comp_edit.variables[comp_edit.used-1].int_.max = byte.max;
206                             break;
207                         case 2: 
208                             comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.short_,member_str,offset);
209                             comp_edit.variables[comp_edit.used-1].int_.min = short.min;
210                             comp_edit.variables[comp_edit.used-1].int_.max = short.max;
211                             break;
212                         case 4: 
213                             comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.int_,member_str,offset);
214                             comp_edit.variables[comp_edit.used-1].int_.min = int.min;
215                             comp_edit.variables[comp_edit.used-1].int_.max = int.max;
216                             break;
217                         default:break;
218                     }
219                     static if(hasUDA!(member,GUIRange))
220                     {
221                         comp_edit.variables[comp_edit.used-1].int_.min = getUDAs!(member,GUIRange)[0].min;
222                         comp_edit.variables[comp_edit.used-1].int_.max = getUDAs!(member,GUIRange)[0].max;
223                     }
224                     /*{
225                         comp_edit.variables[comp_edit.used-1].int_.min = int.min;
226                         comp_edit.variables[comp_edit.used-1].int_.max = int.max;
227                     }*/
228                 }
229             }
230             else static if(__traits(isScalar,member_type))
231             {
232                 switch(member_type.sizeof)
233                 {
234                     case 4:comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.float_,member_str,offset);break;
235                     case 8:
236                     default:break;
237                 }
238                 static if(hasUDA!(member,GUIRange))
239                 {
240                     comp_edit.variables[comp_edit.used-1].float_.min = getUDAs!(member,GUIRange)[0].min;
241                     comp_edit.variables[comp_edit.used-1].float_.max = getUDAs!(member,GUIRange)[0].max;
242                 }
243                 else static if(hasUDA!(member,GUIRangeF))
244                 {
245                     comp_edit.variables[comp_edit.used-1].float_.min = getUDAs!(member,GUIRangeF)[0].minf;
246                     comp_edit.variables[comp_edit.used-1].float_.max = getUDAs!(member,GUIRangeF)[0].maxf;
247                 }
248                 else {
249                     comp_edit.variables[comp_edit.used-1].float_.min = -float.max;
250                     comp_edit.variables[comp_edit.used-1].float_.max = float.max;
251                 }
252             }
253             static if(hasUDA!(member,GUIDisabled))comp_edit.variables[comp_edit.used - 1].disabled = true;
254         }
255         edit_components[becsID(comp)] = comp_edit;
256     }
257 
258     void gui()
259     {
260         if(igCollapsingHeader("Systems", ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_DefaultOpen))
261         {
262             //bool true_ = true;
263             igIndent(8);
264             foreach(ref SystemGUI system;systems)
265             {
266                 igPushIDPtr(&system);
267                 if(igCheckbox(system.name,&system.enabled))
268                 {
269                     System* sys = gEntityManager.getSystem(system.id);
270                     if(system.enabled)sys.enable();
271                     else sys.disable();
272                 }
273                 igPopID();
274             }
275             igUnindent(8);
276         }
277     }
278 
279     static vec4 colorUintToVec4(uint color)
280     {
281        // color = *cast(uint*)(comp_ptr+var.offset);
282         return vec4(cast(float)(color & 0xFF) / 255,
283             cast(float)(color >> 8 & 0xFF) / 255,
284             cast(float)(color >> 16 & 0xFF) / 255,
285             cast(float)(color >> 24 & 0xFF) / 255);
286     }
287 
288     static uint colorVec4ToUint(vec4 color)
289     {
290         return cast(uint)(color.x * 255) |
291             cast(uint)(color.y * 255) << 8 |
292             cast(uint)(color.z * 255) << 16 |
293             cast(uint)(color.w * 255) << 24;
294     }
295 
296     static bool igDragScalarClamp(const(char)* label, ImGuiDataType data_type, void* v, float v_speed, const(void)* v_min, const(void)* v_max, const(char)* format, float power)
297     {
298         ubyte[8] v_backup;// = *v;
299         final switch(data_type)
300         {
301             case ImGuiDataType_S8:memcpy(v_backup.ptr, v, 1);break;
302             case ImGuiDataType_S16:memcpy(v_backup.ptr, v, 2);break;
303             case ImGuiDataType_S32:memcpy(v_backup.ptr, v, 4);break;
304             case ImGuiDataType_U8:memcpy(v_backup.ptr, v, 1);break;
305             case ImGuiDataType_U16:memcpy(v_backup.ptr, v, 2);break;
306             case ImGuiDataType_U32:memcpy(v_backup.ptr, v, 4);break;
307             case ImGuiDataType_Float:memcpy(v_backup.ptr, v, 4);break;
308         }
309         if (!igDragScalar(label, data_type, v, v_speed, v_min, v_max, format, power))
310             return false;
311 
312         final switch(data_type)
313         {
314             case ImGuiDataType_S8:
315                 if(*cast(byte*)v < *cast(byte*)v_min)*cast(byte*)v = *cast(byte*)v_min;
316                 else if(*cast(byte*)v > *cast(byte*)v_max)*cast(byte*)v = *cast(byte*)v_max;
317                 return *cast(byte*)v != *cast(byte*)v_backup.ptr;
318             case ImGuiDataType_S16:
319                 if(*cast(short*)v < *cast(short*)v_min)*cast(short*)v = *cast(short*)v_min;
320                 else if(*cast(short*)v > *cast(short*)v_max)*cast(short*)v = *cast(short*)v_max;
321                 return *cast(short*)v != *cast(short*)v_backup.ptr;
322             case ImGuiDataType_S32:
323                 if(*cast(int*)v < *cast(int*)v_min)*cast(int*)v = *cast(int*)v_min;
324                 else if(*cast(int*)v > *cast(int*)v_max)*cast(int*)v = *cast(int*)v_max;
325                 return *cast(int*)v != *cast(int*)v_backup.ptr;
326             case ImGuiDataType_U8:
327                 if(*cast(ubyte*)v < *cast(ubyte*)v_min)*cast(ubyte*)v = *cast(ubyte*)v_min;
328                 else if(*cast(ubyte*)v > *cast(ubyte*)v_max)*cast(ubyte*)v = *cast(ubyte*)v_max;
329                 return *cast(ubyte*)v != *cast(ubyte*)v_backup.ptr;
330             case ImGuiDataType_U16:
331                 if(*cast(ushort*)v < *cast(ushort*)v_min)*cast(ushort*)v = *cast(ushort*)v_min;
332                 else if(*cast(ushort*)v > *cast(ushort*)v_max)*cast(ushort*)v = *cast(ushort*)v_max;
333                 return *cast(ushort*)v != *cast(ushort*)v_backup.ptr;
334             case ImGuiDataType_U32:
335                 if(*cast(uint*)v < *cast(uint*)v_min)*cast(uint*)v = *cast(uint*)v_min;
336                 else if(*cast(uint*)v > *cast(uint*)v_max)*cast(uint*)v = *cast(uint*)v_max;
337                 return *cast(uint*)v != *cast(uint*)v_backup.ptr;
338             case ImGuiDataType_Float:
339                 if(*cast(float*)v < *cast(float*)v_min)*cast(float*)v = *cast(float*)v_min;
340                 else if(*cast(float*)v > *cast(float*)v_max)*cast(float*)v = *cast(float*)v_max;
341                 return *cast(float*)v != *cast(float*)v_backup.ptr;
342         }
343     }
344 
345     void componentGUI(ushort comp_id, void* data_ptr)
346     {
347         vec4 color;
348         if(comp_id >= edit_components.length)return;
349         //if(edit_components[comp_id].used)
350         if(edit_components[comp_id].name)
351         {
352             if(igCollapsingHeader(edit_components[comp_id].name, ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_DefaultOpen))
353             {
354                 igIndent(8);
355                 foreach(ref VariableGUI var;edit_components[comp_id].variables[0 .. edit_components[comp_id].used])
356                 {
357                     
358                     igPushIDPtr(&var);
359                     switch(var.type)
360                     {
361                         case VariableGUI.Type.byte_:
362                             igDragScalarClamp(var.name, ImGuiDataType_S8, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1);
363                             break;
364                         case VariableGUI.Type.ubyte_:
365                             if(var.disabled)
366                             {
367                                 ubyte v = *cast(ubyte*)(data_ptr+var.offset);
368                                 igDragScalarClamp(var.name, ImGuiDataType_U8, &v, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1);
369                             }
370                             else igDragScalarClamp(var.name, ImGuiDataType_U8, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1);
371                             break;
372                         case VariableGUI.Type.short_:
373                             igDragScalarClamp(var.name, ImGuiDataType_S16, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1);
374                             break;
375                         case VariableGUI.Type.ushort_:
376                             igDragScalarClamp(var.name, ImGuiDataType_U16, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1);
377                             break;
378                         case VariableGUI.Type.int_:
379                             igDragScalarClamp(var.name, ImGuiDataType_S32, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1);
380                             break;
381                         case VariableGUI.Type.uint_:
382                             if(var.disabled)
383                             {
384                                 uint v = *cast(uint*)(data_ptr+var.offset);
385                                 igDragScalarClamp(var.name, ImGuiDataType_U32, &v, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1);
386                             }
387                             else igDragScalarClamp(var.name, ImGuiDataType_U32, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1);
388                             break;
389                         case VariableGUI.Type.float_:
390                             igDragScalarClamp(var.name, ImGuiDataType_Float, data_ptr+var.offset, 0.1, cast(void*)&var.float_.min, cast(void*)&var.float_.max, "%2.2f", 1);
391                             break;
392                         case VariableGUI.Type.color:
393                             color = colorUintToVec4(*cast(uint*)(data_ptr+var.offset));
394                             if(igColorEdit4(var.name, color.data, ImGuiColorEditFlags_None))
395                                 *cast(uint*)(data_ptr+var.offset) = colorVec4ToUint(color);
396                             break;
397                         case VariableGUI.Type.vec2:
398                             igDragFloat2(var.name, (cast(float*)(data_ptr+var.offset))[0..2], 0.1, -float.max, float.max, null, 1);
399                             break;
400                         case VariableGUI.Type.ivec2:
401                             igDragInt2(var.name, (cast(int*)(data_ptr+var.offset))[0..2], 0.1, int.min, int.max, null);
402                             break;
403                         case VariableGUI.Type.vec4:
404                             igDragFloat4(var.name, (cast(float*)(data_ptr+var.offset))[0..4], 0.1, -float.max, float.max, null, 1);
405                             break;
406                         case VariableGUI.Type.ivec4:
407                             igDragInt4(var.name, (cast(int*)(data_ptr+var.offset))[0..4], 0.1, int.min, int.max, null);
408                             break;
409                         default:break;
410                     }
411                     igPopID();
412                 }
413                 //igPopID();
414                 igUnindent(8);
415             }
416         }
417     }
418 
419     void templateComponentsGUI()
420     {
421         if(selected_template >= templates.length)return;
422         EntityTemplate* tmpl = templates[selected_template].tmpl;
423         EntityManager.EntityInfo* info = tmpl.info;
424         foreach(comp_id; info.components)
425         {
426             void* data_ptr = tmpl.entity_data.ptr;
427             void* comp_ptr = data_ptr + info.tmpl_deltas[comp_id];
428             componentGUI(comp_id, comp_ptr);
429         }
430     }
431 
432     void entityComponentsGUI(Entity* entity)
433     {
434         if(!entity)return;
435         EntityMeta meta = entity.getMeta();
436         EntityManager.EntityInfo* info = meta.block.type_info;
437         foreach(comp_id; info.components)
438         {
439             // void* data_ptr = tmpl.entity_data.ptr;
440             void* comp_ptr = meta.getComponent(comp_id);//data_ptr + info.tmpl_deltas[comp_id];
441             componentGUI(comp_id, comp_ptr);
442         }
443     }
444 
445     void toolGui()
446     {
447         ImGuiStyle * style = igGetStyle();
448         ImVec4 col = style.Colors[ImGuiCol_Header];
449         style.Colors[ImGuiCol_Header] = style.Colors[ImGuiCol_TextSelectedBg];
450         //style.
451         //ImDrawList* draw_list = igGetWindowDrawList();
452         final switch(launcher.used_tool)
453         {
454             case Tool.entity_spawner:
455                 if(templates.length)
456                 {
457                     {
458                         if(igListBoxHeaderInt("Template",cast(int)templates.length,cast(int)templates.length))
459                         {
460                             foreach(i, tmpl; templates)
461                             {
462                                 if(igSelectable(tmpl.name,selected_template == i,ImGuiSelectableFlags_AllowDoubleClick,ImVec2(0,0)))
463                                 {
464                                     selected_template = cast(uint)i;
465                                 }
466                             }
467                             igListBoxFooter();
468                         }
469                     }
470                     if(igIsItemHovered(0))igSetTooltip("Select entity to spawn (SHIFT + Scroll)");
471                 }
472                 style.Colors[ImGuiCol_Header] = col;
473                 templateComponentsGUI();
474                 break;
475             case Tool.component_manipulator:
476                 if(components.length)
477                 {
478                     if(igListBoxHeaderInt("Components",cast(int)components.length,cast(int)components.length))
479                     {
480                         foreach(i, comp; components)
481                         {
482                             if(igSelectable(comp.name,selected_component == i,0,ImVec2(0,0)))
483                             {
484                                 selected_component = cast(uint)i;
485                             }
486                         }
487                         igListBoxFooter();
488                     }
489                     if(igIsItemHovered(0))igSetTooltip("Select component to add/remove (SHIFT + Scroll)");
490                 }
491                 style.Colors[ImGuiCol_Header] = col;
492                 if(selected_component < components.length)componentGUI(components[selected_component].component_id, components[selected_component].data);
493                 break;
494             case Tool.selector:
495                 {
496                     Entity* entity = gEntityManager.getEntity(launcher.selected_entity);
497                     style.Colors[ImGuiCol_Header] = col;
498                     entityComponentsGUI(entity);
499                 }
500                 break;
501         }
502         
503         style.Colors[ImGuiCol_Header] = col;
504     }
505 
506     private void genFilterList()
507     {
508         filter_list.clear();
509         foreach(i, comp; filter)
510         {
511             if(comp)
512             {
513                 filter_list.add(cast(ushort)i);
514             }
515         }
516     }
517 
518     void filterGUI()
519     {
520         ImGuiStyle * style = igGetStyle();
521         ImVec4 col = style.Colors[ImGuiCol_Header];
522         style.Colors[ImGuiCol_Header] = style.Colors[ImGuiCol_TextSelectedBg];
523 
524         if(filter.length < edit_components.length)filter.length(edit_components.length);
525 
526         int length = 0;
527         foreach(comp; edit_components)
528         {
529             if(comp.name !is null)length++;
530         }
531 
532         if(length && igListBoxHeaderInt("Components##FilterComponents",cast(int)length,cast(int)length))
533         {
534             foreach(i, ref comp; edit_components)
535             {
536                 if(comp.name is null)continue;
537                 if(igSelectableBoolPtr(comp.name,&filter[i],0,ImVec2(0,0)))
538                 {
539                     genFilterList();
540                 }
541             }
542             igListBoxFooter();
543         }
544         if(igIsItemHovered(0))igSetTooltip("Select components to filter while tool work.\nComponents are only changed for filtered entities.");
545         
546         style.Colors[ImGuiCol_Header] = col;
547     }
548 }