547 lines
20 KiB
C++
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;
|
|
}
|
|
}
|