Extract hierarchy and project drag semantics
This commit is contained in:
@@ -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
|
||||
|
||||
仍待完成:
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -19,7 +19,6 @@ private:
|
||||
char m_searchBuffer[256] = "";
|
||||
UI::TextInputPopupState<256> m_createFolderDialog;
|
||||
UI::TargetedPopupState<AssetItemPtr> m_itemContextMenu;
|
||||
std::string m_draggingPath;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user