Extract hierarchy and project drag semantics

This commit is contained in:
2026-03-27 00:30:11 +08:00
parent 6ec5f05601
commit c97510ed5b
7 changed files with 119 additions and 72 deletions

View File

@@ -10,6 +10,10 @@ namespace XCEngine {
namespace Editor {
namespace Actions {
inline constexpr const char* HierarchyEntityPayloadType() {
return "ENTITY_PTR";
}
inline void RequestEntityRename(IEditorContext& context, const ::XCEngine::Components::GameObject* gameObject) {
if (!gameObject) {
return;
@@ -18,6 +22,63 @@ inline void RequestEntityRename(IEditorContext& context, const ::XCEngine::Compo
context.GetEventBus().Publish(EntityRenameRequestedEvent{ gameObject->GetID() });
}
inline void HandleHierarchySelectionClick(IEditorContext& context, uint64_t entityId, bool additive) {
auto& selectionManager = context.GetSelectionManager();
if (additive) {
if (!selectionManager.IsSelected(entityId)) {
selectionManager.AddToSelection(entityId);
}
return;
}
selectionManager.SetSelectedEntity(entityId);
}
inline bool BeginHierarchyEntityDrag(::XCEngine::Components::GameObject* gameObject) {
if (!gameObject || !ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
return false;
}
ImGui::SetDragDropPayload(HierarchyEntityPayloadType(), &gameObject, sizeof(::XCEngine::Components::GameObject*));
ImGui::Text("%s", gameObject->GetName().c_str());
ImGui::EndDragDropSource();
return true;
}
inline bool AcceptHierarchyEntityDrop(IEditorContext& context, ::XCEngine::Components::GameObject* target) {
if (!target || !ImGui::BeginDragDropTarget()) {
return false;
}
bool accepted = false;
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(HierarchyEntityPayloadType())) {
::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data;
if (sourceGameObject != target && Commands::CanReparentEntity(sourceGameObject, target)) {
Commands::ReparentEntityPreserveWorldTransform(context, sourceGameObject, target->GetID());
accepted = true;
}
}
ImGui::EndDragDropTarget();
return accepted;
}
inline bool AcceptHierarchyEntityDropToRoot(IEditorContext& context) {
if (!ImGui::BeginDragDropTarget()) {
return false;
}
bool accepted = false;
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(HierarchyEntityPayloadType())) {
::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data;
if (sourceGameObject && sourceGameObject->GetParent() != nullptr) {
Commands::ReparentEntityPreserveWorldTransform(context, sourceGameObject, 0);
accepted = true;
}
}
ImGui::EndDragDropTarget();
return accepted;
}
inline void DrawHierarchyCreateActions(IEditorContext& context, ::XCEngine::Components::GameObject* parent) {
DrawMenuAction(MakeCreateEmptyEntityAction(), [&]() {
Commands::CreateEmptyEntity(context, parent, "Create Entity", "GameObject");

View File

@@ -10,6 +10,10 @@ namespace XCEngine {
namespace Editor {
namespace Actions {
inline constexpr const char* ProjectAssetPayloadType() {
return "ASSET_ITEM";
}
inline int FindProjectItemIndex(IProjectManager& projectManager, const AssetItemPtr& item) {
if (!item) {
return -1;
@@ -29,6 +33,50 @@ inline int FindProjectItemIndex(IProjectManager& projectManager, const AssetItem
return -1;
}
inline const char* GetDraggedProjectAssetPath() {
const ImGuiPayload* payload = ImGui::GetDragDropPayload();
if (!payload || !payload->IsDataType(ProjectAssetPayloadType())) {
return nullptr;
}
return static_cast<const char*>(payload->Data);
}
inline bool IsProjectAssetBeingDragged(const AssetItemPtr& item) {
const char* draggedPath = GetDraggedProjectAssetPath();
return item != nullptr && draggedPath != nullptr && item->fullPath == draggedPath;
}
inline bool AcceptProjectAssetDrop(IProjectManager& projectManager, const AssetItemPtr& targetFolder) {
if (!targetFolder || !targetFolder->isFolder || !ImGui::BeginDragDropTarget()) {
return false;
}
bool accepted = false;
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(ProjectAssetPayloadType())) {
const char* draggedPath = static_cast<const char*>(payload->Data);
accepted = Commands::MoveAssetToFolder(projectManager, draggedPath, targetFolder);
}
ImGui::EndDragDropTarget();
return accepted;
}
inline bool BeginProjectAssetDrag(const AssetItemPtr& item, UI::AssetIconKind iconKind) {
if (!item || item->fullPath.empty() || !ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
return false;
}
ImGui::SetDragDropPayload(ProjectAssetPayloadType(), item->fullPath.c_str(), item->fullPath.length() + 1);
ImVec2 previewMin = ImGui::GetMousePos();
const ImVec2 previewSize = UI::AssetDragPreviewSize();
ImVec2 previewMax = ImVec2(previewMin.x + previewSize.x, previewMin.y + previewSize.y);
UI::DrawAssetIcon(ImGui::GetForegroundDrawList(), previewMin, previewMax, iconKind);
ImGui::EndDragDropSource();
return true;
}
inline void DrawProjectAssetContextActions(IEditorContext& context, const AssetItemPtr& item) {
auto& projectManager = context.GetProjectManager();
const int itemIndex = FindProjectItemIndex(projectManager, item);

View File

@@ -100,15 +100,7 @@ void HierarchyPanel::Render() {
}
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) {
Commands::ReparentEntityPreserveWorldTransform(*m_context, sourceGameObject, 0);
}
}
ImGui::EndDragDropTarget();
}
Actions::AcceptHierarchyEntityDropToRoot(*m_context);
}
@@ -192,21 +184,15 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject
gameObject->GetChildCount() == 0);
if (node.clicked) {
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());
}
Actions::HandleHierarchySelectionClick(*m_context, gameObject->GetID(), ImGui::GetIO().KeyCtrl);
}
if (node.doubleClicked) {
BeginRename(gameObject);
}
HandleDragDrop(gameObject);
Actions::BeginHierarchyEntityDrag(gameObject);
Actions::AcceptHierarchyEntityDrop(*m_context, gameObject);
if (UI::BeginPopupContextItem("EntityContextMenu")) {
Actions::DrawHierarchyContextActions(*m_context, gameObject);
@@ -250,24 +236,6 @@ void HierarchyPanel::CancelRename() {
m_renameState.Cancel();
}
void HierarchyPanel::HandleDragDrop(::XCEngine::Components::GameObject* gameObject) {
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 && Commands::CanReparentEntity(sourceGameObject, gameObject)) {
Commands::ReparentEntityPreserveWorldTransform(*m_context, sourceGameObject, gameObject->GetID());
}
}
ImGui::EndDragDropTarget();
}
}
bool HierarchyPanel::PassesFilter(::XCEngine::Components::GameObject* gameObject, const std::string& filter) {
if (!gameObject) return false;

View File

@@ -26,7 +26,6 @@ private:
void BeginRename(::XCEngine::Components::GameObject* gameObject);
void CommitRename();
void CancelRename();
void HandleDragDrop(::XCEngine::Components::GameObject* gameObject);
bool PassesFilter(::XCEngine::Components::GameObject* gameObject, const std::string& filter);
void SortEntities(std::vector<::XCEngine::Components::GameObject*>& entities);

View File

@@ -11,10 +11,6 @@
namespace XCEngine {
namespace Editor {
namespace {
constexpr const char* kAssetDragDropType = "ASSET_ITEM";
}
ProjectPanel::ProjectPanel() : Panel("Project") {
}
@@ -23,13 +19,6 @@ void ProjectPanel::Initialize(const std::string& projectPath) {
}
void ProjectPanel::Render() {
const ImGuiPayload* payload = ImGui::GetDragDropPayload();
if (payload && payload->IsDataType(kAssetDragDropType)) {
m_draggingPath = (const char*)payload->Data;
} else if (!ImGui::IsMouseDown(0)) {
m_draggingPath.clear();
}
UI::PanelWindowScope panel(m_name.c_str());
if (!panel.IsOpen()) {
return;
@@ -131,7 +120,7 @@ void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
bool isSelected = (manager.GetSelectedIndex() == index);
ImGui::PushID(index);
const bool isDraggingThisItem = !m_draggingPath.empty() && item->fullPath == m_draggingPath;
const bool isDraggingThisItem = Actions::IsProjectAssetBeingDragged(item);
const UI::AssetIconKind iconKind = item->isFolder ? UI::AssetIconKind::Folder : UI::AssetIconKind::File;
const UI::AssetTileResult tile = UI::DrawAssetTile(
@@ -151,28 +140,8 @@ void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
m_itemContextMenu.RequestOpen(item);
}
if (item->isFolder) {
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(kAssetDragDropType)) {
const char* draggedPath = (const char*)payload->Data;
Commands::MoveAssetToFolder(manager, draggedPath, item);
}
ImGui::EndDragDropTarget();
}
}
if (!item->fullPath.empty()) {
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
ImGui::SetDragDropPayload(kAssetDragDropType, item->fullPath.c_str(), item->fullPath.length() + 1);
ImVec2 previewMin = ImGui::GetMousePos();
const ImVec2 previewSize = UI::AssetDragPreviewSize();
ImVec2 previewMax = ImVec2(previewMin.x + previewSize.x, previewMin.y + previewSize.y);
UI::DrawAssetIcon(ImGui::GetForegroundDrawList(), previewMin, previewMax, iconKind);
ImGui::EndDragDropSource();
}
}
Actions::AcceptProjectAssetDrop(manager, item);
Actions::BeginProjectAssetDrag(item, iconKind);
if (tile.openRequested) {
Commands::OpenAsset(*m_context, item);

View File

@@ -19,7 +19,6 @@ private:
char m_searchBuffer[256] = "";
UI::TextInputPopupState<256> m_createFolderDialog;
UI::TargetedPopupState<AssetItemPtr> m_itemContextMenu;
std::string m_draggingPath;
};
}