diff --git a/LICENSE b/LICENSE index 766a3eb..0e099be 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Erik Scholz +Copyright (c) 2020 Erik Scholz, Gnik Droy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 9678585..4b18a53 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,14 @@ A drop-in, single-file entity editor for EnTT, with ImGui as graphical backend. [demo-code](https://github.com/Green-Sky/imgui_entt_entity_editor_demo) [(live)](http://scam.rocks/imgui_entt_entity_editor_demo/) -![screenshot](https://github.com/Green-Sky/imgui_entt_entity_editor_demo/blob/master/imgui_entt_entity_editor_screenshot0.png) +Editor +![screenshot0](https://github.com/Green-Sky/imgui_entt_entity_editor_demo/blob/master/imgui_entt_entity_editor_screenshot0.png) + +Editor with Entiy-List +![screenshot1](https://github.com/Green-Sky/imgui_entt_entity_editor_demo/blob/master/imgui_entt_entity_editor_screenshot1.png) + +With Drag and Drop +![vid](https://github.com/Green-Sky/imgui_entt_entity_editor_demo/blob/master/imgui_entt_entity_editor_dnd0.gif) # example usage ```c++ @@ -44,8 +51,8 @@ editor.registerComponent("Velocity"); ``` # dependencies -The editor uses (the latest) EnTTv3.4.0 interface and ImGui 1.72b but should work with prior versions. (tested with ImGui 1.68) +The editor uses EnTTv3.4.0 interface and ImGui. (tested with ImGui 1.68, 1.72b, 1.75, 1.78) To use it with EnTTv3.0.0, use the dedicated branch. For specific EnTT version check the tags. -Tested against EnTT 3.1.0, 3.1.1, 3.2.0, 3.2.1, 3.2.2, 3.3.x, 3.4.0 . +Releases available for EnTT 3.1.0, 3.1.1, 3.2.0, 3.2.1, 3.2.2, 3.3.x, 3.4.0 . diff --git a/imgui_entt_entity_editor.hpp b/imgui_entt_entity_editor.hpp index a68490e..1555585 100644 --- a/imgui_entt_entity_editor.hpp +++ b/imgui_entt_entity_editor.hpp @@ -3,14 +3,55 @@ #include #include +#include #include #include +#ifndef MM_IEEE_ASSERT + #define MM_IEEE_ASSERT(x) assert(x) +#endif + +#define MM_IEEE_IMGUI_PAYLOAD_TYPE_ENTITY "MM_IEEE_ENTITY" + +#ifndef MM_IEEE_ENTITY_WIDGET + #define MM_IEEE_ENTITY_WIDGET ::MM::EntityWidget +#endif + namespace MM { +template +inline void EntityWidget(EntityType& e, entt::basic_registry& reg, bool dropTarget = false) +{ + ImGui::PushID(entt::to_integral(e)); + + if (reg.valid(e)) { + ImGui::Text("ID: %d", entt::to_integral(e)); + } else { + ImGui::Text("Invalid Entity"); + } + + if (reg.valid(e)) { + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) { + ImGui::SetDragDropPayload(MM_IEEE_IMGUI_PAYLOAD_TYPE_ENTITY, &e, sizeof(e)); + ImGui::Text("ID: %d", entt::to_integral(e)); + ImGui::EndDragDropSource(); + } + } + + if (dropTarget && ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(MM_IEEE_IMGUI_PAYLOAD_TYPE_ENTITY)) { + e = *(EntityType*)payload->Data; + } + + ImGui::EndDragDropTarget(); + } + + ImGui::PopID(); +} + template -void ComponentEditorWidget(entt::basic_registry& registry, EntityType entity) {} +void ComponentEditorWidget([[maybe_unused]] entt::basic_registry& registry, EntityType entity) {} template void ComponentAddAction(entt::basic_registry& registry, EntityType entity) @@ -53,8 +94,8 @@ class EntityEditor { ComponentInfo& registerComponent(const ComponentInfo& component_info) { auto index = entt::type_info::id(); - auto [it, insert_result] = component_infos.insert_or_assign(index, component_info); - assert(insert_result); + [[maybe_unused]] auto [it, insert_result] = component_infos.insert_or_assign(index, component_info); + MM_IEEE_ASSERT(insert_result); return std::get(*it); } @@ -75,77 +116,171 @@ class EntityEditor { return registerComponent(name, ComponentEditorWidget); } - void render(Registry& registry, EntityType& e) + void renderEditor(Registry& registry, EntityType& e) { - if (show_window) { - if (ImGui::Begin("Entity Editor", &show_window)) { - ImGui::TextUnformatted("Editing:"); - ImGui::SameLine(); + ImGui::TextUnformatted("Editing:"); + ImGui::SameLine(); + + MM_IEEE_ENTITY_WIDGET(e, registry, true); + + if (ImGui::Button("New")) { + e = registry.create(); + } + if (registry.valid(e)) { + ImGui::SameLine(); + + // clone would go here + //if (ImGui::Button("Clone")) { + //auto old_e = e; + //e = registry.create(); + //} + + ImGui::Dummy({10, 0}); // space destroy a bit, to not accidentally click it + ImGui::SameLine(); + + // red button + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.65, 0.15, 0.15, 1)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.8, 0.3, 0.3, 1)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1, 0.2, 0.2, 1)); + if (ImGui::Button("Destroy")) { + registry.destroy(e); + e = entt::null; + } + ImGui::PopStyleColor(3); + } + + ImGui::Separator(); - if (registry.valid(e)) { - ImGui::Text("ID: %d", entt::to_integral(e)); + if (registry.valid(e)) { + ImGui::PushID(entt::to_integral(e)); + std::map has_not; + for (auto& [component_type_id, ci] : component_infos) { + if (entityHasComponent(registry, e, component_type_id)) { + ImGui::PushID(component_type_id); + if (ImGui::Button("-")) { + ci.destroy(registry, e); + ImGui::PopID(); + continue; // early out to prevent access to deleted data + } else { + ImGui::SameLine(); + } + + if (ImGui::CollapsingHeader(ci.name.c_str())) { + ImGui::Indent(30.f); + ImGui::PushID("Widget"); + ci.widget(registry, e); + ImGui::PopID(); + ImGui::Unindent(30.f); + } + ImGui::PopID(); } else { - ImGui::Text("Invalid Entity"); + has_not[component_type_id] = ci; } + } - if (ImGui::Button("New Entity")) { - e = registry.create(); + if (!has_not.empty()) { + if (ImGui::Button("+ Add Component")) { + ImGui::OpenPopup("Add Component"); } - ImGui::Separator(); - - if (registry.valid(e)) { - ImGui::PushID(entt::to_integral(e)); - std::map has_not; - for (auto& [component_type_id, ci] : component_infos) { - if (entityHasComponent(registry, e, component_type_id)) { - ImGui::PushID(component_type_id); - if (ImGui::Button("-")) { - ci.destroy(registry, e); - ImGui::PopID(); - continue; // early out to prevent access to deleted data - } else { - ImGui::SameLine(); - } - - if (ImGui::CollapsingHeader(ci.name.c_str())) { - ImGui::Indent(30.f); - ImGui::PushID("Widget"); - ci.widget(registry, e); - ImGui::PopID(); - ImGui::Unindent(30.f); - } - ImGui::PopID(); - } else { - has_not[component_type_id] = ci; + if (ImGui::BeginPopup("Add Component")) { + ImGui::TextUnformatted("Available:"); + ImGui::Separator(); + + for (auto& [component_type_id, ci] : has_not) { + ImGui::PushID(component_type_id); + if (ImGui::Selectable(ci.name.c_str())) { + ci.create(registry, e); } + ImGui::PopID(); } + ImGui::EndPopup(); + } + } + ImGui::PopID(); + } + } - if (!has_not.empty()) { - if (ImGui::Button("+ Add Component")) { - ImGui::OpenPopup("Add Component"); - } + void renderEntityList(Registry& registry, std::set& comp_list) + { + ImGui::Text("Components Filter:"); + ImGui::SameLine(); + if (ImGui::SmallButton("clear")) { + comp_list.clear(); + } - if (ImGui::BeginPopup("Add Component")) { - ImGui::TextUnformatted("Available:"); - ImGui::Separator(); - - for (auto& [component_type_id, ci] : has_not) { - ImGui::PushID(component_type_id); - if (ImGui::Selectable(ci.name.c_str())) { - ci.create(registry, e); - } - ImGui::PopID(); - } - ImGui::EndPopup(); - } - } - ImGui::PopID(); + ImGui::Indent(); + + for (const auto& [component_type_id, ci] : component_infos) { + bool is_in_list = comp_list.count(component_type_id); + bool active = is_in_list; + + ImGui::Checkbox(ci.name.c_str(), &active); + + if (is_in_list && !active) { // remove + comp_list.erase(component_type_id); + } else if (!is_in_list && active) { // add + comp_list.emplace(component_type_id); + } + } + + ImGui::Unindent(); + ImGui::Separator(); + + if (comp_list.empty()) { + ImGui::Text("Orphans:"); + registry.orphans([®istry](auto e){ + MM_IEEE_ENTITY_WIDGET(e, registry, false); + }); + } else { + auto view = registry.runtime_view(comp_list.begin(), comp_list.end()); + ImGui::Text("%lu Entities Matching:", view.size()); + + if (ImGui::BeginChild("entity list")) { + for (auto e : view) { + MM_IEEE_ENTITY_WIDGET(e, registry, false); + } + } + ImGui::EndChild(); + } + } + + [[deprecated("Use renderEditor() instead. And manage the window yourself.")]] + void render(Registry& registry, EntityType& e) + { + if (show_window) { + if (ImGui::Begin("Entity Editor", &show_window)) { + renderEditor(registry, e); + } + ImGui::End(); + } + } + + // displays both, editor and list + // uses static internally, use only as a quick way to get going! + void renderSimpleCombo(Registry& registry, EntityType& e) + { + if (show_window) { + ImGui::SetNextWindowSize(ImVec2(550, 400), ImGuiCond_FirstUseEver); + if (ImGui::Begin("Entity Editor", &show_window)) { + if (ImGui::BeginChild("list", {200, 0}, true)) { + static std::set comp_list; + renderEntityList(registry, comp_list); } + ImGui::EndChild(); + + ImGui::SameLine(); + + if (ImGui::BeginChild("editor")) { + renderEditor(registry, e); + } + ImGui::EndChild(); + } ImGui::End(); } } + }; } // MM