// for the license, see the end of the file #pragma once #include "entt/entity/fwd.hpp" #include #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(static_cast(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([[maybe_unused]] entt::basic_registry& registry, [[maybe_unused]] EntityType entity) {} template void ComponentAddAction(entt::basic_registry& registry, EntityType entity) { registry.template emplace(entity); } template void ComponentRemoveAction(entt::basic_registry& registry, EntityType entity) { registry.template remove(entity); } template class EntityEditor { public: using Registry = entt::basic_registry; using ComponentTypeID = entt::id_type; struct ComponentInfo { using Callback = std::function; std::string name; Callback widget, create, destroy; }; bool show_window = true; private: std::map component_infos; bool entityHasComponent(Registry& registry, EntityType& entity, ComponentTypeID type_id) { const auto* storage_ptr = registry.storage(type_id); return storage_ptr != nullptr && storage_ptr->contains(entity); } public: template ComponentInfo& registerComponent(const ComponentInfo& component_info) { auto index = entt::type_hash::value(); auto insert_info = component_infos.insert_or_assign(index, component_info); MM_IEEE_ASSERT(insert_info.second); return std::get(*insert_info.first); } template ComponentInfo& registerComponent(const std::string& name, typename ComponentInfo::Callback widget) { return registerComponent(ComponentInfo{ name, widget, ComponentAddAction, ComponentRemoveAction, }); } template ComponentInfo& registerComponent(const std::string& name) { return registerComponent(name, ComponentEditorWidget); } void renderEditor(Registry& registry, EntityType& e) { 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(); if (ImGui::Button("Clone")) { auto old_e = e; e = registry.create(); // create a copy of an entity component by component for (auto &&curr: registry.storage()) { if (auto &storage = curr.second; storage.contains(old_e)) { // TODO: do something with the return value. returns false on failure. storage.push(e, storage.value(old_e)); } } } ImGui::SameLine(); ImGui::Dummy({10, 0}); // space destroy a bit, to not accidentally click it ImGui::SameLine(); // red button ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.65f, 0.15f, 0.15f, 1.f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.8f, 0.3f, 0.3f, 1.f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.f, 0.2f, 0.2f, 1.f)); if (ImGui::Button("Destroy")) { registry.destroy(e); e = entt::null; } ImGui::PopStyleColor(3); } ImGui::Separator(); if (registry.valid(e)) { ImGui::PushID(static_cast(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 (!has_not.empty()) { if (ImGui::Button("+ Add Component")) { ImGui::OpenPopup("Add Component"); } 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(); } } void renderEntityList(Registry& registry, std::set& comp_list) { ImGui::Text("Components Filter:"); ImGui::SameLine(); if (ImGui::SmallButton("clear")) { comp_list.clear(); } 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:"); for (EntityType e : registry.template storage()) { if (registry.orphan(e)) { MM_IEEE_ENTITY_WIDGET(e, registry, false); } } } else { entt::basic_runtime_view> view{}; for (const auto type : comp_list) { auto* storage_ptr = registry.storage(type); if (storage_ptr != nullptr) { view.iterate(*storage_ptr); } } // TODO: add support for exclude ImGui::Text("%lu Entities Matching:", view.size_hint()); if (ImGui::BeginChild("entity list")) { for (auto e : view) { MM_IEEE_ENTITY_WIDGET(e, registry, false); } } ImGui::EndChild(); } } // 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 // MIT License // Copyright (c) 2019-2022 Erik Scholz // Copyright (c) 2020 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 // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE.