Files
Hazel/Editor/src/Editor/Panels/SceneHierachyPanel.cpp
2025-10-31 00:53:18 +08:00

547 lines
20 KiB
C++

//
// Created by sfd on 25-5-29.
//
#include "SceneHierachyPanel.h"
#include <filesystem>
#include <imgui.h>
#include <imgui_internal.h>
#include <glm/gtc/type_ptr.hpp>
#include <Hazel/Scene/Components.h>
#include <misc/cpp/imgui_stdlib.h>
#include "Hazel/Scripting/ScriptEngine.h"
#include "Hazel/UI/UI.h"
namespace Hazel
{
SceneHierachyPanel::SceneHierachyPanel(const Ref<Scene>& context)
{
SetContext(context);
}
void SceneHierachyPanel::SetContext(const Ref<Scene>& context)
{
m_Context = context;
m_SelectionContext = {};
m_LastSelectionContext = {};
}
void SceneHierachyPanel::OnImGuiRender()
{
if (m_Context)
{
ImGui::Begin("Scene Hierachy");
m_Context->m_Registry.view<entt::entity>().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");
m_LastSelectionContext = m_SelectionContext;
}
else if (ImGui::MenuItem("Create Camera"))
{
m_SelectionContext = m_Context->CreateEntity("Camera");
m_SelectionContext.AddComponent<CameraComponent>();
}
ImGui::EndPopup();
}
ImGui::End();
}
ImGui::Begin("Properties");
if (m_LastSelectionContext)
{
DrawComponents(m_LastSelectionContext);
}
ImGui::End();
}
void SceneHierachyPanel::DrawEntityNode(Entity entity)
{
auto& tag = entity.GetComponent<TagComponent>().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;
m_LastSelectionContext = m_SelectionContext;
}
if (ImGui::BeginPopupContextItem())
{
if (ImGui::MenuItem("Delete Entity"))
entityDeleted = true;
ImGui::EndPopup();
}
if (isopened)
{
ImGui::TreePop();
}
if (entityDeleted)
{
m_Context->DestroyEntity(entity);
if (m_SelectionContext == entity)
{
m_SelectionContext = {};
m_LastSelectionContext = 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<typename T, typename UIfunction>
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<T>())
{
ImGui::PushID(&entity);
auto& component = entity.GetComponent<T>();
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<T>();
ImGui::PopID();
}
}
template<typename T>
void SceneHierachyPanel::DisplayAddComponentEntry(const char* label)
{
if (!m_SelectionContext.HasComponent<T>())
{
if (ImGui::MenuItem(label))
{
m_SelectionContext.AddComponent<T>();
ImGui::CloseCurrentPopup();
}
}
}
void SceneHierachyPanel::DrawComponents(Entity entity)
{
if (entity.HasComponent<TagComponent>())
{
auto& tag = entity.GetComponent<TagComponent>().Tag;
char buffer[256] = {};
strncpy_s(buffer,sizeof(buffer), tag.c_str(), sizeof(buffer));
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"))
{
DisplayAddComponentEntry<CameraComponent>("Camera");
DisplayAddComponentEntry<ScriptComponent>("Script");
DisplayAddComponentEntry<SpriteRendererComponent>("Sprite Renderer");
DisplayAddComponentEntry<CircleRendererComponent>("Circle Renderer");
DisplayAddComponentEntry<RigidBody2DComponent>("RigidBody 2D");
DisplayAddComponentEntry<BoxCollider2DComponent>("Box Collider 2D");
DisplayAddComponentEntry<CircleCollider2DComponent>("Circle Collider 2D");
DisplayAddComponentEntry<TextComponent>("Text Component");
ImGui::EndPopup();
}
ImGui::PopItemWidth();
DrawComponent<TransformComponent>("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<CameraComponent>("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<ScenceCamera::ProjectionType>(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<ScriptComponent>("Script", entity, [entity, &scene = m_Context](auto& component) mutable
{
bool scriptClassExists = ScriptEngine::ClassExists(component.ClassName);
static char buffer[64] = {};
strcpy_s(buffer, sizeof(buffer), component.ClassName.c_str());
UI::ScopedStyleColor textColor(ImGuiCol_Text, ImVec4(0.9f, 0.2f, 0.3f, 1.0f), !scriptClassExists);
if (ImGui::InputText("Class", buffer, sizeof(buffer)))
{
component.ClassName = buffer;
return;
}
// Fields
const Ref<ScriptInstance> scriptInstance = ScriptEngine::GetEntityScriptInstance(entity.GetUUID());
const bool sceneRunning = scene->IsRunning();
if (sceneRunning)
{
if (scriptInstance)
{
const auto& fields = scriptInstance->GetScriptClass()->GetFields();
for (const auto& [name, field] : fields)
{
if (field.Type == ScriptFieldType::Float)
{
float data = scriptInstance->GetFieldValue<float>(name);
if (ImGui::DragFloat(name.c_str(), &data))
{
scriptInstance->SetFieldValue(name, data);
}
}
}
}
}
else
{
if (scriptClassExists)
{
Ref<ScriptClass> entityClass = ScriptEngine::GetEntityClass(component.ClassName);
const auto& fields = entityClass->GetFields();
auto& entityFields = ScriptEngine::GetScriptFieldMap(entity);
for (const auto& [name, field] : fields)
{
// field has been set in Editor
if (entityFields.contains(name))
{
ScriptFieldInstance& scriptField = entityFields.at(name);
// display control to set
if (field.Type == ScriptFieldType::Float)
{
float data = scriptField.GetValue<float>();
if (ImGui::DragFloat(name.c_str(), &data))
scriptField.SetValue(data);
}
}
else
{
// display control to set
if (field.Type == ScriptFieldType::Float)
{
float data = 0.0f;
if (ImGui::DragFloat(name.c_str(), &data))
{
ScriptFieldInstance& fieldInstance = entityFields[name];
fieldInstance.Field = field;
fieldInstance.SetValue(data);
}
}
}
}
}
}
});
DrawComponent<SpriteRendererComponent>("Sprite Renderer", entity, [](auto& component)
{
ImGui::ColorEdit4("Color", glm::value_ptr(component.Color));
// Texture
if (!component.Texture)
ImGui::Button("Texture", ImVec2(100.f, 0.0f));
else
ImGui::ImageButton("Texture", component.Texture->GetRendererID(), ImVec2{100.f, 100.f}, ImVec2{0, 0}, ImVec2{1, 1});
if (ImGui::BeginDragDropTarget())
{
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(),
IM_COL32(0, 120, 215, 255), 0.0f, 0, 3.0f);
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("CONTENT_BROSWER_ITEM"))
{
const wchar_t* path = (const wchar_t*)payload->Data;
const std::filesystem::path texturePath(path);
component.Texture = Texture2D::Create(texturePath.string());
}
ImGui::EndDragDropTarget();
}
ImGui::DragFloat("Tiling Color", &component.TilingFactor, 0.1f, 0.0f, 100.f);
});
DrawComponent<CircleRendererComponent>("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<RigidBody2DComponent>("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<BoxCollider2DComponent>("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<CircleCollider2DComponent>("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);
});
DrawComponent<TextComponent>("Text Renderer", entity, [](auto& component)
{
ImGui::InputTextMultiline("Text string", &component.TextString);
ImGui::ColorEdit4("Color", glm::value_ptr(component.Color));
ImGui::DragFloat("Kerning", &component.Kerning, 0.01f);
ImGui::DragFloat("LineSpacing", &component.LineSpacing, 0.01f);
});
}
void SceneHierachyPanel::SetSelectedEntity(const Entity entity)
{
m_SelectionContext = entity;
m_LastSelectionContext = m_SelectionContext;
}
}