module gui.manager; import app; import cimgui.cimgui; import bubel.ecs.entity; import bubel.ecs.manager; import bubel.ecs.std; import bubel.ecs.system; import bubel.ecs.vector; import ecs_utils.math.vector; import gui.attributes; import gui.component; import gui.system; import gui.template_; import std.traits; extern(C): struct GUIManager { Vector!SystemGUI systems; Vector!ComponentGUI components; Vector!TemplateGUI templates; Vector!ComponentEditGUI edit_components; Vector!bool filter; Vector!ushort filter_list; int selected_template = 0; int selected_component = 0; void selectTemplate(int id) { if(templates.length == 0)return; selected_template = id; while(selected_template < 0)selected_template += cast(int)templates.length; while(selected_template >= templates.length)selected_template -= cast(int)templates.length; } void selectComponent(int id) { if(components.length == 0)return; selected_component = id; while(selected_component < 0)selected_component += cast(int)components.length; while(selected_component >= components.length)selected_component -= cast(int)components.length; } void clear() { foreach(tmpl; templates) { launcher.manager.freeTemplate(tmpl.tmpl); } foreach(comp; components) { free(comp.data); } foreach(ref comp; edit_components) { if(comp.variables)Mallocator.dispose(comp.variables); comp.variables = null; comp.used = 0; comp.name = null; } foreach(ref comp; filter) { comp = false; } filter_list.clear(); systems.clear(); templates.clear(); components.clear(); selected_template = 0; selected_component = 0; } EntityTemplate* getSelectedTemplate() { if(templates.length > selected_template)return templates[selected_template].tmpl; else return null; } ComponentRef getSelectedComponent() { if(components.length > selected_component)return ComponentRef(components[selected_component].data, components[selected_component].component_id); else return ComponentRef(null, ushort.max); } void addSystem(ushort id, const (char)* name, bool enabled = true) { System* system = launcher.manager.getSystem(id); //const (char)* name = systems.add(SystemGUI(name,system,enabled)); } void addTemplate(ushort[] components, const (char)* name) { templates.add(TemplateGUI(name, launcher.manager.allocateTemplate(components))); } void addTemplate(EntityTemplate* tmpl, const (char)* name) { templates.add(TemplateGUI(name, tmpl)); } // void addComponent(ComponentRef comp, const (char)* name) // { // uint size = EntityManager.instance.components[comp.component_id].size; // void* data = malloc(size); // memcpy(data, comp.ptr, size); // components.add(ComponentGUI(name, data, comp.component_id)); // } void addComponent(T)(T comp, const (char)* name) { static assert(hasStaticMember!(T,"component_id")); uint size = EntityManager.instance.components[comp.component_id].size; void* data = malloc(size); memcpy(data, &comp, size); components.add(ComponentGUI(name, data, comp.component_id)); if(edit_components.length <= comp.component_id) { edit_components.length = comp.component_id+1;//.extend(comp.component_id + 1); } //edit_components[comp.component_id] = ComponentEditGUI(name); ComponentEditGUI comp_edit; comp_edit.name = T.stringof; //enum fields = __traits(allMembers, T); alias fields = FieldNameTuple!T; //pragma(msg,fields); comp_edit.variables = Mallocator.makeArray!VariableGUI(fields.length); foreach(member_str; fields) { alias member = __traits(getMember, T, member_str); alias member_type = typeof(member); //pragma(msg,member); //pragma(msg,member_type); //pragma(msg,__traits(getMember, T, member).offsetof); ushort offset = member.offsetof;//cast(ushort)__traits(getMember, T, member).offsetof; static if(__traits(isIntegral,member_type)) { static if(__traits(isUnsigned, member_type)) { static if(hasUDA!(member,GUIColor)) { comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.color,member_str,offset); } else switch(member_type.sizeof) { case 1: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.ubyte_,member_str,offset);break; case 2: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.ushort_,member_str,offset);break; case 4: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.uint_,member_str,offset);break; default:break; } static if(hasUDA!(member,GUIRange)) { comp_edit.variables[comp_edit.used-1].int_.min = getUDAs!(member,GUIRange)[0].min; comp_edit.variables[comp_edit.used-1].int_.max = getUDAs!(member,GUIRange)[0].max; } else { comp_edit.variables[comp_edit.used-1].int_.min = 0; comp_edit.variables[comp_edit.used-1].int_.max = int.max; } } else { switch(member_type.sizeof) { case 1: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.byte_,member_str,offset);break; case 2: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.short_,member_str,offset);break; case 4: comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.int_,member_str,offset);break; default:break; } static if(hasUDA!(member,GUIRange)) { comp_edit.variables[comp_edit.used-1].int_.min = getUDAs!(member,GUIRange)[0].min; comp_edit.variables[comp_edit.used-1].int_.max = getUDAs!(member,GUIRange)[1].max; } { comp_edit.variables[comp_edit.used-1].int_.min = int.min; comp_edit.variables[comp_edit.used-1].int_.max = int.max; } } } else static if(__traits(isScalar,member_type)) { switch(member_type.sizeof) { case 4:comp_edit.variables[comp_edit.used++] = VariableGUI(VariableGUI.Type.float_,member_str,offset);break; case 8: default:break; } static if(hasUDA!(member,GUIRange)) { comp_edit.variables[comp_edit.used-1].float_.min = getUDAs!(member,GUIRange)[0].minf; comp_edit.variables[comp_edit.used-1].float_.max = getUDAs!(member,GUIRange)[1].maxf; } { comp_edit.variables[comp_edit.used-1].float_.min = -float.max; comp_edit.variables[comp_edit.used-1].float_.max = float.max; } } } edit_components[comp.component_id] = comp_edit; } void gui() { if(igCollapsingHeader("Systems", ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_DefaultOpen)) { bool true_ = true; igIndent(8); foreach(ref SystemGUI system;systems) { if(igCheckbox(system.name,&system.enabled)) { if(system.enabled)system.system.enable(); else system.system.disable(); } } igUnindent(8); } } static vec4 colorUintToVec4(uint color) { // color = *cast(uint*)(comp_ptr+var.offset); return vec4(cast(float)(color & 0xFF) / 255, cast(float)(color >> 8 & 0xFF) / 255, cast(float)(color >> 16 & 0xFF) / 255, cast(float)(color >> 24 & 0xFF) / 255); } static uint colorVec4ToUint(vec4 color) { return cast(uint)(color.x * 255) | cast(uint)(color.y * 255) << 8 | cast(uint)(color.z * 255) << 16 | cast(uint)(color.w * 255) << 24; } 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) { ubyte[8] v_backup;// = *v; final switch(data_type) { case ImGuiDataType_S8:memcpy(v_backup.ptr, v, 1);break; case ImGuiDataType_S16:memcpy(v_backup.ptr, v, 2);break; case ImGuiDataType_S32:memcpy(v_backup.ptr, v, 4);break; case ImGuiDataType_U8:memcpy(v_backup.ptr, v, 1);break; case ImGuiDataType_U16:memcpy(v_backup.ptr, v, 2);break; case ImGuiDataType_U32:memcpy(v_backup.ptr, v, 4);break; case ImGuiDataType_Float:memcpy(v_backup.ptr, v, 4);break; } if (!igDragScalar(label, data_type, v, v_speed, v_min, v_max, format, power)) return false; final switch(data_type) { case ImGuiDataType_S8: if(*cast(byte*)v < *cast(byte*)v_min)*cast(byte*)v = *cast(byte*)v_min; else if(*cast(byte*)v > *cast(byte*)v_max)*cast(byte*)v = *cast(byte*)v_max; return *cast(byte*)v != *cast(byte*)v_backup.ptr; case ImGuiDataType_S16: if(*cast(short*)v < *cast(short*)v_min)*cast(short*)v = *cast(short*)v_min; else if(*cast(short*)v > *cast(short*)v_max)*cast(short*)v = *cast(short*)v_max; return *cast(short*)v != *cast(short*)v_backup.ptr; case ImGuiDataType_S32: if(*cast(int*)v < *cast(int*)v_min)*cast(int*)v = *cast(int*)v_min; else if(*cast(int*)v > *cast(int*)v_max)*cast(int*)v = *cast(int*)v_max; return *cast(int*)v != *cast(int*)v_backup.ptr; case ImGuiDataType_U8: if(*cast(ubyte*)v < *cast(ubyte*)v_min)*cast(ubyte*)v = *cast(ubyte*)v_min; else if(*cast(ubyte*)v > *cast(ubyte*)v_max)*cast(ubyte*)v = *cast(ubyte*)v_max; return *cast(ubyte*)v != *cast(ubyte*)v_backup.ptr; case ImGuiDataType_U16: if(*cast(ushort*)v < *cast(ushort*)v_min)*cast(ushort*)v = *cast(ushort*)v_min; else if(*cast(ushort*)v > *cast(ushort*)v_max)*cast(ushort*)v = *cast(ushort*)v_max; return *cast(ushort*)v != *cast(ushort*)v_backup.ptr; case ImGuiDataType_U32: if(*cast(uint*)v < *cast(uint*)v_min)*cast(uint*)v = *cast(uint*)v_min; else if(*cast(uint*)v > *cast(uint*)v_max)*cast(uint*)v = *cast(uint*)v_max; return *cast(uint*)v != *cast(uint*)v_backup.ptr; case ImGuiDataType_Float: if(*cast(float*)v < *cast(float*)v_min)*cast(float*)v = *cast(float*)v_min; else if(*cast(float*)v > *cast(float*)v_max)*cast(float*)v = *cast(float*)v_max; return *cast(float*)v != *cast(float*)v_backup.ptr; } } void componentGUI(ushort comp_id, void* data_ptr) { vec4 color; if(comp_id >= edit_components.length)return; if(edit_components[comp_id].used) { if(igCollapsingHeader(edit_components[comp_id].name, ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_DefaultOpen)) { igIndent(8); foreach(ref VariableGUI var;edit_components[comp_id].variables[0 .. edit_components[comp_id].used]) { igPushIDPtr(&var); switch(var.type) { case VariableGUI.Type.byte_: igDragScalarClamp(var.name, ImGuiDataType_S8, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1); break; case VariableGUI.Type.ubyte_: igDragScalarClamp(var.name, ImGuiDataType_U8, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1); break; case VariableGUI.Type.short_: igDragScalarClamp(var.name, ImGuiDataType_S16, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1); break; case VariableGUI.Type.ushort_: igDragScalarClamp(var.name, ImGuiDataType_U16, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1); break; case VariableGUI.Type.int_: igDragScalarClamp(var.name, ImGuiDataType_S32, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1); break; case VariableGUI.Type.uint_: igDragScalarClamp(var.name, ImGuiDataType_U32, data_ptr+var.offset, 0.1, cast(void*)&var.int_.min, cast(void*)&var.int_.max, null, 1); break; case VariableGUI.Type.float_: igDragScalarClamp(var.name, ImGuiDataType_Float, data_ptr+var.offset, 0.1, cast(void*)&var.float_.min, cast(void*)&var.float_.max, "%2.2f", 1); break; case VariableGUI.Type.color: color = colorUintToVec4(*cast(uint*)(data_ptr+var.offset)); if(igColorEdit4(var.name, color.data, ImGuiColorEditFlags_None)) *cast(uint*)(data_ptr+var.offset) = colorVec4ToUint(color); break; default:break; } igPopID(); } //igPopID(); igUnindent(8); } } } void entityComponentsGUI() { EntityTemplate* tmpl = templates[selected_template].tmpl; EntityManager.EntityInfo* info = tmpl.info; foreach(comp_id; info.components) { void* data_ptr = tmpl.entity_data.ptr; void* comp_ptr = data_ptr + info.tmpl_deltas[comp_id]; componentGUI(comp_id, comp_ptr); } } void toolGui() { ImGuiStyle * style = igGetStyle(); ImVec4 col = style.Colors[ImGuiCol_Header]; style.Colors[ImGuiCol_Header] = style.Colors[ImGuiCol_TextSelectedBg]; //style. //ImDrawList* draw_list = igGetWindowDrawList(); final switch(launcher.used_tool) { case Tool.entity_spawner: if(templates.length) { { if(igListBoxHeaderInt("Template",cast(int)templates.length,cast(int)templates.length)) { foreach(i, tmpl; templates) { if(igSelectable(tmpl.name,selected_template == i,ImGuiSelectableFlags_AllowDoubleClick,ImVec2(0,0))) { selected_template = cast(uint)i; } } igListBoxFooter(); } } if(igIsItemHovered(0))igSetTooltip("Select entity to spawn (SHIFT + Scroll)"); } style.Colors[ImGuiCol_Header] = col; entityComponentsGUI(); break; case Tool.component_manipulator: if(components.length) { if(igListBoxHeaderInt("Components",cast(int)components.length,cast(int)components.length)) { foreach(i, comp; components) { if(igSelectable(comp.name,selected_component == i,0,ImVec2(0,0))) { selected_component = cast(uint)i; } } igListBoxFooter(); } if(igIsItemHovered(0))igSetTooltip("Select component to add/remove (SHIFT + Scroll)"); } style.Colors[ImGuiCol_Header] = col; componentGUI(components[selected_component].component_id, components[selected_component].data); break; case Tool.selector: break; } style.Colors[ImGuiCol_Header] = col; } private void genFilterList() { filter_list.clear(); foreach(i, comp; filter) { if(comp) { filter_list.add(cast(ushort)i); } } } void filterGUI() { ImGuiStyle * style = igGetStyle(); ImVec4 col = style.Colors[ImGuiCol_Header]; style.Colors[ImGuiCol_Header] = style.Colors[ImGuiCol_TextSelectedBg]; if(filter.length < edit_components.length)filter.length(edit_components.length); int length = 0; foreach(comp; edit_components) { if(comp.name !is null)length++; } if(length && igListBoxHeaderInt("Components##FilterComponents",cast(int)length,cast(int)length)) { foreach(i, ref comp; edit_components) { if(comp.name is null)continue; if(igSelectableBoolPtr(comp.name,&filter[i],0,ImVec2(0,0))) { genFilterList(); } } igListBoxFooter(); } if(igIsItemHovered(0))igSetTooltip("Select components to filter while tool work.\nComponents are only changed for filtered entities."); style.Colors[ImGuiCol_Header] = col; } }