Files
XCEngine/editor/src/panels/HierarchyPanel.cpp

500 lines
18 KiB
C++

#include "HierarchyPanel.h"
#include "Core/IEditorContext.h"
#include "Core/ISceneManager.h"
#include "Core/ISelectionManager.h"
#include "Core/IUndoManager.h"
#include "Core/EditorEvents.h"
#include "Core/EventBus.h"
#include "UI/Core.h"
#include "UI/PanelChrome.h"
#include "Utils/UndoUtils.h"
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Core/Math/Quaternion.h>
#include <XCEngine/Components/CameraComponent.h>
#include <XCEngine/Components/LightComponent.h>
#include <imgui.h>
#include <cstring>
namespace XCEngine {
namespace Editor {
namespace {
constexpr float kHierarchyToolbarHeight = 34.0f;
} // namespace
HierarchyPanel::HierarchyPanel() : Panel("Hierarchy") {
}
HierarchyPanel::~HierarchyPanel() {
if (m_context) {
m_context->GetEventBus().Unsubscribe<SelectionChangedEvent>(m_selectionHandlerId);
}
}
void HierarchyPanel::OnAttach() {
m_selectionHandlerId = m_context->GetEventBus().Subscribe<SelectionChangedEvent>(
[this](const SelectionChangedEvent& event) {
OnSelectionChanged(event);
}
);
}
void HierarchyPanel::OnSelectionChanged(const SelectionChangedEvent& event) {
m_needsRefresh = true;
}
void HierarchyPanel::Render() {
UI::PanelWindowScope panel(m_name.c_str());
if (!panel.IsOpen()) {
return;
}
RenderSearchBar();
HandleKeyboardShortcuts();
std::string filter = m_searchBuffer;
UI::PanelContentScope content("EntityList");
if (!content.IsOpen()) {
return;
}
auto& sceneManager = m_context->GetSceneManager();
auto rootEntities = sceneManager.GetRootEntities();
SortEntities(const_cast<std::vector<::XCEngine::Components::GameObject*>&>(rootEntities));
for (auto* gameObject : rootEntities) {
RenderEntity(gameObject, filter);
}
if (ImGui::IsWindowHovered() && ImGui::IsMouseDown(0) && !ImGui::IsAnyItemHovered()) {
if (!m_renaming) {
m_context->GetSelectionManager().ClearSelection();
}
}
if (ImGui::BeginPopupContextWindow("HierarchyContextMenu", ImGuiPopupFlags_MouseButtonRight)) {
RenderCreateMenu(nullptr);
ImGui::EndPopup();
}
ImGui::InvisibleButton("##DragTarget", ImVec2(-1, -1));
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_PTR")) {
::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data;
if (sourceGameObject && sourceGameObject->GetParent() != nullptr) {
auto& sceneManager = m_context->GetSceneManager();
UndoUtils::ExecuteSceneCommand(*m_context, "Reparent Entity", [&]() {
auto* srcTransform = sourceGameObject->GetTransform();
Math::Vector3 worldPos = srcTransform->GetPosition();
Math::Quaternion worldRot = srcTransform->GetRotation();
Math::Vector3 worldScale = srcTransform->GetScale();
sceneManager.MoveEntity(sourceGameObject->GetID(), 0);
srcTransform->SetPosition(worldPos);
srcTransform->SetRotation(worldRot);
srcTransform->SetScale(worldScale);
sceneManager.MarkSceneDirty();
});
}
}
ImGui::EndDragDropTarget();
}
}
void HierarchyPanel::RenderSearchBar() {
UI::PanelToolbarScope toolbar("HierarchyToolbar", kHierarchyToolbarHeight);
if (!toolbar.IsOpen()) {
return;
}
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(7.0f, 4.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 2.0f);
const float buttonWidth = 26.0f;
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - buttonWidth - 4.0f);
ImGui::InputTextWithHint("##Search", "Search hierarchy", m_searchBuffer, sizeof(m_searchBuffer));
ImGui::SameLine();
if (UI::ToolbarButton("...", false, ImVec2(buttonWidth, 0.0f))) {
ImGui::OpenPopup("HierarchyOptions");
}
if (ImGui::BeginPopup("HierarchyOptions")) {
if (ImGui::MenuItem("Sort By Name", nullptr, m_sortMode == SortMode::Name)) {
m_sortMode = SortMode::Name;
}
if (ImGui::MenuItem("Sort By Component Count", nullptr, m_sortMode == SortMode::ComponentCount)) {
m_sortMode = SortMode::ComponentCount;
}
if (ImGui::MenuItem("Transform First", nullptr, m_sortMode == SortMode::TransformFirst)) {
m_sortMode = SortMode::TransformFirst;
}
ImGui::EndPopup();
}
ImGui::PopStyleVar(2);
}
void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject, const std::string& filter) {
if (!gameObject) return;
if (!filter.empty() && !PassesFilter(gameObject, filter)) {
return;
}
ImGui::PushID(static_cast<int>(gameObject->GetID()));
ImGuiTreeNodeFlags flags =
ImGuiTreeNodeFlags_OpenOnArrow |
ImGuiTreeNodeFlags_SpanAvailWidth |
ImGuiTreeNodeFlags_FramePadding;
if (gameObject->GetChildCount() == 0) {
flags |= ImGuiTreeNodeFlags_Leaf;
}
if (m_context->GetSelectionManager().IsSelected(gameObject->GetID())) {
flags |= ImGuiTreeNodeFlags_Selected;
}
if (m_renaming && m_renamingEntity == gameObject) {
if (m_renameJustStarted) {
ImGui::SetKeyboardFocusHere();
m_renameJustStarted = false;
}
ImGui::SetNextItemWidth(-1);
if (ImGui::InputText("##Rename", m_renameBuffer, sizeof(m_renameBuffer), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) {
if (strlen(m_renameBuffer) > 0) {
UndoUtils::ExecuteSceneCommand(*m_context, "Rename Entity", [&]() {
m_context->GetSceneManager().RenameEntity(gameObject->GetID(), m_renameBuffer);
});
}
m_renaming = false;
m_renamingEntity = nullptr;
}
if (!ImGui::IsItemActive() && ImGui::IsMouseClicked(0)) {
if (strlen(m_renameBuffer) > 0) {
UndoUtils::ExecuteSceneCommand(*m_context, "Rename Entity", [&]() {
m_context->GetSceneManager().RenameEntity(gameObject->GetID(), m_renameBuffer);
});
}
m_renaming = false;
m_renamingEntity = nullptr;
}
} else {
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f));
bool isOpen = ImGui::TreeNodeEx((void*)gameObject->GetUUID(), flags, "%s", gameObject->GetName().c_str());
ImGui::PopStyleVar();
if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) {
ImGuiIO& io = ImGui::GetIO();
if (io.KeyCtrl) {
if (!m_context->GetSelectionManager().IsSelected(gameObject->GetID())) {
m_context->GetSelectionManager().AddToSelection(gameObject->GetID());
}
} else {
m_context->GetSelectionManager().SetSelectedEntity(gameObject->GetID());
}
}
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
m_renaming = true;
m_renamingEntity = gameObject;
strcpy_s(m_renameBuffer, gameObject->GetName().c_str());
m_renameJustStarted = true;
}
HandleDragDrop(gameObject);
if (ImGui::BeginPopupContextItem("EntityContextMenu")) {
RenderContextMenu(gameObject);
ImGui::EndPopup();
}
if (isOpen) {
for (size_t i = 0; i < gameObject->GetChildCount(); i++) {
RenderEntity(gameObject->GetChild(i), filter);
}
ImGui::TreePop();
}
}
ImGui::PopID();
}
void HierarchyPanel::RenderContextMenu(::XCEngine::Components::GameObject* gameObject) {
auto& sceneManager = m_context->GetSceneManager();
auto& selectionManager = m_context->GetSelectionManager();
if (ImGui::BeginMenu("Create")) {
RenderCreateMenu(gameObject);
ImGui::EndMenu();
}
if (gameObject != nullptr && ImGui::MenuItem("Create Child")) {
UndoUtils::ExecuteSceneCommand(*m_context, "Create Child", [&]() {
auto* child = sceneManager.CreateEntity("GameObject", gameObject);
selectionManager.SetSelectedEntity(child->GetID());
});
}
ImGui::Separator();
if (gameObject != nullptr && gameObject->GetParent() != nullptr) {
if (ImGui::MenuItem("Detach from Parent")) {
UndoUtils::ExecuteSceneCommand(*m_context, "Reparent Entity", [&]() {
sceneManager.MoveEntity(gameObject->GetID(), 0);
});
}
}
if (ImGui::MenuItem("Rename", "F2")) {
if (gameObject) {
m_renaming = true;
m_renamingEntity = gameObject;
strcpy_s(m_renameBuffer, gameObject->GetName().c_str());
m_renameJustStarted = true;
}
}
if (ImGui::MenuItem("Delete", "Delete")) {
UndoUtils::ExecuteSceneCommand(*m_context, "Delete Entity", [&]() {
sceneManager.DeleteEntity(gameObject->GetID());
});
}
ImGui::Separator();
if (ImGui::MenuItem("Copy", "Ctrl+C")) {
sceneManager.CopyEntity(gameObject->GetID());
}
if (ImGui::MenuItem("Paste", "Ctrl+V", false, sceneManager.HasClipboardData())) {
UndoUtils::ExecuteSceneCommand(*m_context, "Paste Entity", [&]() {
const uint64_t newId = sceneManager.PasteEntity(gameObject->GetID());
if (newId != 0) {
selectionManager.SetSelectedEntity(newId);
}
});
}
if (ImGui::MenuItem("Duplicate", "Ctrl+D")) {
uint64_t newId = 0;
UndoUtils::ExecuteSceneCommand(*m_context, "Duplicate Entity", [&]() {
newId = sceneManager.DuplicateEntity(gameObject->GetID());
if (newId != 0) {
selectionManager.SetSelectedEntity(newId);
}
});
if (newId != 0) {
}
}
}
void HierarchyPanel::RenderCreateMenu(::XCEngine::Components::GameObject* parent) {
auto& sceneManager = m_context->GetSceneManager();
auto& selectionManager = m_context->GetSelectionManager();
if (ImGui::MenuItem("Empty Object")) {
UndoUtils::ExecuteSceneCommand(*m_context, "Create Entity", [&]() {
auto* newEntity = sceneManager.CreateEntity("GameObject", parent);
selectionManager.SetSelectedEntity(newEntity->GetID());
});
}
ImGui::Separator();
if (ImGui::MenuItem("Camera")) {
UndoUtils::ExecuteSceneCommand(*m_context, "Create Camera", [&]() {
auto* newEntity = sceneManager.CreateEntity("Camera", parent);
newEntity->AddComponent<::XCEngine::Components::CameraComponent>();
sceneManager.MarkSceneDirty();
selectionManager.SetSelectedEntity(newEntity->GetID());
});
}
if (ImGui::MenuItem("Light")) {
UndoUtils::ExecuteSceneCommand(*m_context, "Create Light", [&]() {
auto* newEntity = sceneManager.CreateEntity("Light", parent);
newEntity->AddComponent<::XCEngine::Components::LightComponent>();
sceneManager.MarkSceneDirty();
selectionManager.SetSelectedEntity(newEntity->GetID());
});
}
ImGui::Separator();
if (ImGui::MenuItem("Cube")) {
UndoUtils::ExecuteSceneCommand(*m_context, "Create Cube", [&]() {
auto* newEntity = sceneManager.CreateEntity("Cube", parent);
selectionManager.SetSelectedEntity(newEntity->GetID());
});
}
if (ImGui::MenuItem("Sphere")) {
UndoUtils::ExecuteSceneCommand(*m_context, "Create Sphere", [&]() {
auto* newEntity = sceneManager.CreateEntity("Sphere", parent);
selectionManager.SetSelectedEntity(newEntity->GetID());
});
}
if (ImGui::MenuItem("Plane")) {
UndoUtils::ExecuteSceneCommand(*m_context, "Create Plane", [&]() {
auto* newEntity = sceneManager.CreateEntity("Plane", parent);
selectionManager.SetSelectedEntity(newEntity->GetID());
});
}
}
void HierarchyPanel::HandleDragDrop(::XCEngine::Components::GameObject* gameObject) {
auto& sceneManager = m_context->GetSceneManager();
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
ImGui::SetDragDropPayload("ENTITY_PTR", &gameObject, sizeof(::XCEngine::Components::GameObject*));
ImGui::Text("%s", gameObject->GetName().c_str());
ImGui::EndDragDropSource();
}
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_PTR")) {
::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data;
if (sourceGameObject != gameObject && sourceGameObject != nullptr) {
bool isValidMove = true;
::XCEngine::Components::GameObject* checkParent = gameObject;
while (checkParent != nullptr) {
if (checkParent == sourceGameObject) {
isValidMove = false;
break;
}
checkParent = checkParent->GetParent();
}
if (isValidMove) {
UndoUtils::ExecuteSceneCommand(*m_context, "Reparent Entity", [&]() {
auto* srcTransform = sourceGameObject->GetTransform();
Math::Vector3 worldPos = srcTransform->GetPosition();
Math::Quaternion worldRot = srcTransform->GetRotation();
Math::Vector3 worldScale = srcTransform->GetScale();
sceneManager.MoveEntity(sourceGameObject->GetID(), gameObject->GetID());
srcTransform->SetPosition(worldPos);
srcTransform->SetRotation(worldRot);
srcTransform->SetScale(worldScale);
sceneManager.MarkSceneDirty();
});
}
}
}
ImGui::EndDragDropTarget();
}
}
void HierarchyPanel::HandleKeyboardShortcuts() {
auto& sceneManager = m_context->GetSceneManager();
auto& selectionManager = m_context->GetSelectionManager();
::XCEngine::Components::GameObject* selectedGameObject = sceneManager.GetEntity(selectionManager.GetSelectedEntity());
if (ImGui::IsWindowFocused()) {
if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
if (selectedGameObject != nullptr) {
UndoUtils::ExecuteSceneCommand(*m_context, "Delete Entity", [&]() {
sceneManager.DeleteEntity(selectedGameObject->GetID());
});
}
}
if (ImGui::IsKeyPressed(ImGuiKey_F2)) {
if (selectedGameObject != nullptr) {
m_renaming = true;
m_renamingEntity = selectedGameObject;
strcpy_s(m_renameBuffer, selectedGameObject->GetName().c_str());
m_renameJustStarted = true;
}
}
ImGuiIO& io = ImGui::GetIO();
if (io.KeyCtrl) {
if (ImGui::IsKeyPressed(ImGuiKey_C)) {
if (selectedGameObject != nullptr) {
sceneManager.CopyEntity(selectedGameObject->GetID());
}
}
if (ImGui::IsKeyPressed(ImGuiKey_V)) {
if (sceneManager.HasClipboardData()) {
UndoUtils::ExecuteSceneCommand(*m_context, "Paste Entity", [&]() {
const uint64_t newId = sceneManager.PasteEntity(selectedGameObject ? selectedGameObject->GetID() : 0);
if (newId != 0) {
selectionManager.SetSelectedEntity(newId);
}
});
}
}
if (ImGui::IsKeyPressed(ImGuiKey_D)) {
if (selectedGameObject != nullptr) {
UndoUtils::ExecuteSceneCommand(*m_context, "Duplicate Entity", [&]() {
const uint64_t newId = sceneManager.DuplicateEntity(selectedGameObject->GetID());
if (newId != 0) {
selectionManager.SetSelectedEntity(newId);
}
});
}
}
}
}
}
bool HierarchyPanel::PassesFilter(::XCEngine::Components::GameObject* gameObject, const std::string& filter) {
if (!gameObject) return false;
if (gameObject->GetName().find(filter) != std::string::npos) {
return true;
}
for (size_t i = 0; i < gameObject->GetChildCount(); i++) {
if (PassesFilter(gameObject->GetChild(i), filter)) {
return true;
}
}
return false;
}
void HierarchyPanel::SortEntities(std::vector<::XCEngine::Components::GameObject*>& entities) {
switch (m_sortMode) {
case SortMode::Name:
std::sort(entities.begin(), entities.end(), [](::XCEngine::Components::GameObject* a, ::XCEngine::Components::GameObject* b) {
return a->GetName() < b->GetName();
});
break;
case SortMode::ComponentCount:
std::sort(entities.begin(), entities.end(), [](::XCEngine::Components::GameObject* a, ::XCEngine::Components::GameObject* b) {
return a->GetComponents<::XCEngine::Components::Component>().size() > b->GetComponents<::XCEngine::Components::Component>().size();
});
break;
case SortMode::TransformFirst:
std::sort(entities.begin(), entities.end(), [](::XCEngine::Components::GameObject* a, ::XCEngine::Components::GameObject* b) {
bool aHasTransform = a->GetComponent<::XCEngine::Components::TransformComponent>() != nullptr;
bool bHasTransform = b->GetComponent<::XCEngine::Components::TransformComponent>() != nullptr;
if (aHasTransform != bHasTransform) {
return aHasTransform;
}
return a->GetName() < b->GetName();
});
break;
}
}
}
}