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 }