// // Created by sfd on 25-5-29. // #include "SceneHierachyPanel.h" #include #include #include #include #include namespace Hazel { extern const std::filesystem::path g_AssetPath; SceneHierachyPanel::SceneHierachyPanel(const Ref& context) { SetContext(context); } void SceneHierachyPanel::SetContext(const Ref& context) { m_Context = context; m_SelectionContext = {}; } void SceneHierachyPanel::OnImGuiRender() { if (m_Context) { ImGui::Begin("Scene Hierachy"); m_Context->m_Registry.view().each([&](auto entityID) { DrawEntityNode({entityID, m_Context.get()}); }); if (ImGui::IsMouseDown(0) && ImGui::IsWindowHovered()) { m_SelectionContext = {}; } if (ImGui::BeginPopupContextWindow(0, ImGuiPopupFlags_MouseButtonRight | ImGuiPopupFlags_NoOpenOverItems)) { if (ImGui::MenuItem("Create Empty Entity")) { m_SelectionContext = m_Context->CreateEntity("Empty Entity"); } else if (ImGui::MenuItem("Create Camera")) { m_SelectionContext = m_Context->CreateEntity("Camera"); m_SelectionContext.AddComponent(); } ImGui::EndPopup(); } ImGui::End(); } ImGui::Begin("Properties"); if (m_SelectionContext) { DrawComponents(m_SelectionContext); } ImGui::End(); } void SceneHierachyPanel::DrawEntityNode(Entity entity) { auto& tag = entity.GetComponent().Tag; bool entityDeleted = false; ImGuiTreeNodeFlags flags = ((m_SelectionContext == entity) ? ImGuiTreeNodeFlags_Selected : 0) | ImGuiTreeNodeFlags_OpenOnArrow; flags |= ImGuiTreeNodeFlags_SpanAvailWidth; const bool isopened = ImGui::TreeNodeEx((void*)(uint64_t)(uint32_t)entity, flags, tag.c_str()); if (ImGui::IsItemClicked()) { m_SelectionContext = entity; } if (ImGui::BeginPopupContextItem()) { if (ImGui::MenuItem("Delete Entity")) entityDeleted = true; ImGui::EndPopup(); } if (isopened) { ImGui::TreePop(); } if (entityDeleted) { m_Context->DestoryEntity(entity); if (m_SelectionContext == entity) { m_SelectionContext = {}; } } } static bool DrawVec3Control(const std::string& label, glm::vec3& values, float resetValue = 0.0f, float columnWidth = 100.0f) { ImGuiIO& io = ImGui::GetIO(); auto boldFont = io.Fonts->Fonts[0]; bool isChanged = false; ImGui::PushID(label.c_str()); const ImGuiContext* context = ImGui::GetCurrentContext(); ImGui::Columns(2); ImGui::SetColumnWidth(0, columnWidth); ImGui::Text(label.c_str()); ImGui::NextColumn(); ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {0.0f, 0.0f}); float lineHeight = context->Font->FontSize + context->Style.FramePadding.y * 2.0f; // float lineHeight = GImGui->Font->FontSize + GImGui->Style.FramePadding.y * 2.0f; ImVec2 buttonSize = {lineHeight + 3.0f, lineHeight}; ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{0.8f, 0.1f, 0.15f, 1.0f}); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4{0.9f, 0.2f, 0.2f, 1.0f}); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{0.8f, 0.1f, 0.15f, 1.0f}); ImGui::PushFont(boldFont); if (ImGui::Button("X", buttonSize)) { values.x = resetValue; isChanged = true; } ImGui::PopFont(); ImGui::PopStyleColor(3); ImGui::SameLine(); if (ImGui::DragFloat("##X", &values.x, 0.1f)) { isChanged = true; } ImGui::PopItemWidth(); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{0.2f, 0.7f, 0.2f, 1.0f}); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4{0.3f, 0.8f, 0.3f, 1.0f}); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{0.2f, 0.7f, 0.2f, 1.0f}); ImGui::PushFont(boldFont); if (ImGui::Button("Y", buttonSize)) { values.y = resetValue; isChanged = true; } ImGui::PopFont(); ImGui::PopStyleColor(3); ImGui::SameLine(); if (ImGui::DragFloat("##Y", &values.y, 0.1f)) { isChanged = true; } ImGui::PopItemWidth(); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{0.1f, 0.25f, 0.8f, 1.0f}); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4{0.2f, 0.35f, 0.9f, 1.0f}); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{0.1f, 0.25f, 0.8f, 1.0f}); ImGui::PushFont(boldFont); if (ImGui::Button("Z", buttonSize)) { values.z = resetValue; isChanged = true; } ImGui::PopFont(); ImGui::PopStyleColor(3); ImGui::SameLine(); if (ImGui::DragFloat("##Z", &values.z, 0.1f)) { isChanged = true; } ImGui::PopItemWidth(); ImGui::PopStyleVar(); ImGui::Columns(1); ImGui::PopID(); return isChanged; } template void DrawComponent(const std::string& name, Entity entity, UIfunction uiFunction) { constexpr ImGuiTreeNodeFlags treeNodeFlags = ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_AllowItemOverlap | ImGuiTreeNodeFlags_FramePadding;; if (entity.HasComponent()) { ImGui::PushID(&entity); auto& component = entity.GetComponent(); ImVec2 contextReginAvail = ImGui::GetContentRegionAvail(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {4.0f, 4.0f}); auto context = ImGui::GetCurrentContext(); float lineHeight = context->Font->FontSize + context->Style.FramePadding.y * 2.0f; ImGui::Separator(); bool open = ImGui::TreeNodeEx((void*)(uint64_t)typeid(T).hash_code(), treeNodeFlags, name.c_str()); ImGui::PopStyleVar(); ImGui::SameLine(contextReginAvail.x - lineHeight * 0.5f); if (ImGui::Button("+", ImVec2(lineHeight, lineHeight))) { ImGui::OpenPopup("ComponentSettings"); } bool removeComponent = false; if (ImGui::BeginPopup("ComponentSettings")) { if (ImGui::MenuItem("Remove Component")) removeComponent = true; ImGui::EndPopup(); } if (open) { uiFunction(component); ImGui::TreePop(); } if (removeComponent) entity.RemoveComponent(); ImGui::PopID(); } } void SceneHierachyPanel::DrawComponents(Entity entity) { if (entity.HasComponent()) { auto& tag = entity.GetComponent().Tag; char buffer[256] = {}; strcpy_s(buffer,sizeof(buffer), tag.c_str()); if (ImGui::InputText("Tag", buffer, sizeof(buffer))) { tag = std::string(buffer); } } ImGui::SameLine(); ImGui::PushItemWidth(-1); if (ImGui::Button("Add")) ImGui::OpenPopup("Add"); if (ImGui::BeginPopup("Add")) { if (!m_SelectionContext.HasComponent()) { if (ImGui::MenuItem("Camera")) { m_SelectionContext.AddComponent(); ImGui::CloseCurrentPopup(); } } if (!m_SelectionContext.HasComponent()) { if (ImGui::MenuItem("Sprite Renderer")) { m_SelectionContext.AddComponent(); ImGui::CloseCurrentPopup(); } } if (!m_SelectionContext.HasComponent()) { if (ImGui::MenuItem("Circle Renderer")) { m_SelectionContext.AddComponent(); ImGui::CloseCurrentPopup(); } } if (!m_SelectionContext.HasComponent()) { if (ImGui::MenuItem("RigidBody 2D")) { m_SelectionContext.AddComponent(); ImGui::CloseCurrentPopup(); } } if (!m_SelectionContext.HasComponent()) { if (ImGui::MenuItem("Box Collider 2D")) { m_SelectionContext.AddComponent(); ImGui::CloseCurrentPopup(); } } if (!m_SelectionContext.HasComponent()) { if (ImGui::MenuItem("Circle Collider 2D")) { m_SelectionContext.AddComponent(); ImGui::CloseCurrentPopup(); } } ImGui::EndPopup(); } ImGui::PopItemWidth(); DrawComponent("Transform", entity, [](auto& component) { DrawVec3Control("Translation", component.Translation); glm::vec3 rotation = glm::degrees(component.Rotation); if (DrawVec3Control("Rotation", rotation)) { component.Rotation = glm::radians(rotation); } DrawVec3Control("Scale", component.Scale, 1.0f); }); DrawComponent("Camera", entity, [](auto& component) { auto& camera = component.Camera; ImGui::Checkbox("isPrimary", &component.Primary); static const char* projectionTypeStrings[] = { "Perspective", "Orthographic" }; const char* currentProjectionTypeString = projectionTypeStrings[(int)camera.GetProjectionType()]; if (ImGui::BeginCombo("Projection", currentProjectionTypeString)) { for (int i = 0; i < 2; i++) { const bool isSelected = currentProjectionTypeString == projectionTypeStrings[i]; if (ImGui::Selectable(projectionTypeStrings[i], isSelected)) { currentProjectionTypeString = projectionTypeStrings[i]; camera.SetProjectionType(static_cast(i)); } if (isSelected) { ImGui::SetItemDefaultFocus(); } } ImGui::EndCombo(); } if (camera.GetProjectionType() == ScenceCamera::ProjectionType::Perspective) { float fov = glm::degrees(camera.GetPerspectiveVerticalFOV()); if (ImGui::DragFloat("FOV", &fov, 0.1f)) camera.SetPerspectiveVerticalFOV(glm::radians(fov)); float near = camera.GetPerspectiveNearCLip(); if (ImGui::DragFloat("Near", &near)) camera.SetPerspectiveNearClip(near); float far = camera.GetPerspectiveFarCLip(); if (ImGui::DragFloat("Far", &far)) camera.SetPerspectiveFarClip(far); } if (camera.GetProjectionType() == ScenceCamera::ProjectionType::Orthographic) { float size = camera.GetOrthographicSize(); if (ImGui::DragFloat("Size", &size)) camera.SetOrthographicSize(size); float near = camera.GetOrthographicNearCLip(); if (ImGui::DragFloat("Near", &near)) camera.SetOrthographicNearClip(near); float far = camera.GetOrthographicFarCLip(); if (ImGui::DragFloat("Far", &far)) camera.SetOrthographicFarClip(far); ImGui::Checkbox("Fixed Aspect Ratio", &component.FixedAspectRatio); } }); DrawComponent("Sprite Renderer", entity, [](auto& component) { ImGui::ColorEdit4("Color", glm::value_ptr(component.Color)); // Texture ImGui::Button("Texture", ImVec2(100.f, 0.0f)); if (ImGui::BeginDragDropTarget()) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("CONTENT_BROSWER_ITEM")) { const wchar_t* path = (const wchar_t*)payload->Data; std::filesystem::path texturePath = std::filesystem::path(g_AssetPath) / path; component.Texture = Texture2D::Create(texturePath.string()); } ImGui::EndDragDropTarget(); } ImGui::DragFloat("Tiling Color", &component.TilingFactor, 0.1f, 0.0f, 100.f); }); DrawComponent("Circle Renderer", entity, [](auto& component) { ImGui::ColorEdit4("Color", glm::value_ptr(component.Color)); // ImGui::DragFloat("Radius", &component.Radius, 0.01f, 0.0f, 1.f); ImGui::DragFloat("Thickness", &component.Thickness, 0.01f, 0.0f, 1.f); ImGui::DragFloat("Fade", &component.Fade, 0.00001f, 0.0f, 1.f); }); DrawComponent("Rigidbody 2D", entity, [](auto& component) { static const char* bodyTypeStrings[] = {"Static", "Dynamic", "Kinematic"}; const char* currentBodyTypeString = bodyTypeStrings[(int)component.Type]; if (ImGui::BeginCombo("Body Type", currentBodyTypeString)) { for (int i = 0; i < 3; i++) { const bool isSelected = currentBodyTypeString == bodyTypeStrings[i]; if (ImGui::Selectable(bodyTypeStrings[i], isSelected)) { currentBodyTypeString = bodyTypeStrings[i]; component.Type = (RigidBody2DComponent::BodyType)i; } if (isSelected) { ImGui::SetItemDefaultFocus(); } } ImGui::EndCombo(); } ImGui::Checkbox("Fixed Rotation", &component.FixedRotation); }); DrawComponent("Box Collider 2D", entity, [](auto& component) { ImGui::DragFloat2("Offset", glm::value_ptr(component.Offset), 0.001f); ImGui::DragFloat2("Size", glm::value_ptr(component.Size), 0.001f); ImGui::DragFloat("Density", &component.Density, 0.01f, 0.0f); ImGui::DragFloat("Friction", &component.Friction, 0.01f, 0.0f, 1.0f); ImGui::DragFloat("Restitution", &component.Restitution, 0.01f, 0.0f, 1.0f); ImGui::DragFloat("Restitution Threshold", &component.RestitutionThreshold, 0.01f, 0.0f); }); DrawComponent("Circle Collider 2D", entity, [](auto& component) { ImGui::DragFloat2("Offset", glm::value_ptr(component.Offset), 0.001f); ImGui::DragFloat("Radius", &component.Radius, 0.001f); ImGui::DragFloat("Density", &component.Density, 0.01f, 0.0f); ImGui::DragFloat("Friction", &component.Friction, 0.01f, 0.0f, 1.0f); ImGui::DragFloat("Restitution", &component.Restitution, 0.01f, 0.0f, 1.0f); ImGui::DragFloat("Restitution Threshold", &component.RestitutionThreshold, 0.01f, 0.0f); }); } void SceneHierachyPanel::SetSelectedEntity(const Entity entity) { m_SelectionContext = entity; } }