diff --git a/docs/plan/Editor重构3.26.md b/docs/plan/Editor重构3.26.md index e7c64ee9..1005983b 100644 --- a/docs/plan/Editor重构3.26.md +++ b/docs/plan/Editor重构3.26.md @@ -108,6 +108,7 @@ - `Inspector / Console` 的局部 action 组装也开始继续下沉到 shared router - `Inspector` 的 component section header 菜单已开始改成 callback/router 驱动,而不是在 widget 层硬编码动作 - `MenuBar` 的 File / View / Help / global shortcut 也开始继续下沉到 shared main-menu router +- `Hierarchy / Project` 的 drag-drop payload、拖拽接收与部分选择语义也开始继续下沉到 shared action router ### 5. Dock / Layout 层 @@ -190,6 +191,7 @@ - 重命名状态已收成 `Begin / Commit / Cancel` - 重命名交互已从 panel 局部字段收口到 shared inline edit state - `Rename` 请求已能从 `MenuBar -> EventBus -> Hierarchy inline edit` 触发 +- entity drag payload / 目标接收 / root drop / selection click 语义已开始继续从 panel 下沉到 shared hierarchy router 仍待完成: @@ -205,6 +207,7 @@ - 资源图标绘制与图标配色已下沉到 shared UI token / widget - 创建文件夹弹窗已改成 shared popup state 驱动 - `Back / Open / Delete` 已接 panel-focused keyboard action +- asset drag payload / folder drop / 拖拽预览高亮已开始继续从 panel 下沉到 shared project router 仍待完成: diff --git a/editor/src/Actions/HierarchyActionRouter.h b/editor/src/Actions/HierarchyActionRouter.h index 131a97be..55a3740a 100644 --- a/editor/src/Actions/HierarchyActionRouter.h +++ b/editor/src/Actions/HierarchyActionRouter.h @@ -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"); diff --git a/editor/src/Actions/ProjectActionRouter.h b/editor/src/Actions/ProjectActionRouter.h index af3ee0b7..f855ea8b 100644 --- a/editor/src/Actions/ProjectActionRouter.h +++ b/editor/src/Actions/ProjectActionRouter.h @@ -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(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(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); diff --git a/editor/src/panels/HierarchyPanel.cpp b/editor/src/panels/HierarchyPanel.cpp index 4137173f..613e8881 100644 --- a/editor/src/panels/HierarchyPanel.cpp +++ b/editor/src/panels/HierarchyPanel.cpp @@ -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; diff --git a/editor/src/panels/HierarchyPanel.h b/editor/src/panels/HierarchyPanel.h index e0144e7e..3aa1553b 100644 --- a/editor/src/panels/HierarchyPanel.h +++ b/editor/src/panels/HierarchyPanel.h @@ -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); diff --git a/editor/src/panels/ProjectPanel.cpp b/editor/src/panels/ProjectPanel.cpp index cdee4e1d..9d2d925a 100644 --- a/editor/src/panels/ProjectPanel.cpp +++ b/editor/src/panels/ProjectPanel.cpp @@ -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); diff --git a/editor/src/panels/ProjectPanel.h b/editor/src/panels/ProjectPanel.h index 6954bcb7..e63845c5 100644 --- a/editor/src/panels/ProjectPanel.h +++ b/editor/src/panels/ProjectPanel.h @@ -19,7 +19,6 @@ private: char m_searchBuffer[256] = ""; UI::TextInputPopupState<256> m_createFolderDialog; UI::TargetedPopupState m_itemContextMenu; - std::string m_draggingPath; }; }