#include "Project/EditorProjectRuntime.h" #include "State/EditorSelectionStamp.h" namespace XCEngine::UI::Editor::App { namespace { using AssetCommandTarget = EditorProjectRuntime::AssetCommandTarget; using EditCommandTarget = EditorProjectRuntime::EditCommandTarget; bool IsSameOrDescendantItemId( std::string_view candidateId, std::string_view ancestorId) { if (candidateId == ancestorId) { return true; } if (candidateId.size() <= ancestorId.size() || candidateId.substr(0u, ancestorId.size()) != ancestorId) { return false; } return candidateId[ancestorId.size()] == '/'; } void PopulateAssetCommandTargetFromAsset( AssetCommandTarget& target, const ProjectBrowserModel& browserModel, const ProjectBrowserModel::AssetEntry& asset, const ProjectBrowserModel::FolderEntry* containerFolder) { target.subjectAsset = &asset; target.subjectItemId = asset.itemId; target.subjectPath = asset.absolutePath; target.subjectDisplayName = asset.displayName; target.subjectRelativePath = browserModel.BuildProjectRelativePath(asset.itemId); target.showInExplorerSelectTarget = true; target.containerFolder = containerFolder; } void PopulateAssetCommandTargetFromFolder( AssetCommandTarget& target, const ProjectBrowserModel& browserModel, const ProjectBrowserModel::FolderEntry& folder) { target.subjectFolder = &folder; target.subjectItemId = folder.itemId; target.subjectPath = folder.absolutePath; target.subjectDisplayName = folder.label; target.subjectRelativePath = browserModel.BuildProjectRelativePath(folder.itemId); target.showInExplorerSelectTarget = false; target.containerFolder = &folder; } EditCommandTarget BuildEditCommandTarget( const ProjectBrowserModel::AssetEntry& asset) { EditCommandTarget target = {}; target.itemId = asset.itemId; target.absolutePath = asset.absolutePath; target.displayName = asset.displayName; target.itemKind = asset.kind; target.directory = asset.directory; return target; } EditCommandTarget BuildEditCommandTarget( const ProjectBrowserModel& browserModel, const ProjectBrowserModel::FolderEntry& folder) { EditCommandTarget target = {}; target.itemId = folder.itemId; target.absolutePath = folder.absolutePath; target.displayName = folder.label; target.itemKind = ProjectBrowserModel::ItemKind::Folder; target.directory = true; target.assetsRoot = browserModel.IsAssetsRoot(folder.itemId); return target; } } // namespace bool EditorProjectRuntime::Initialize(const std::filesystem::path& repoRoot) { m_browserModel.Initialize(repoRoot); m_selection = {}; m_pendingSceneOpenPath.reset(); return true; } void EditorProjectRuntime::SetFolderIcon(const ::XCEngine::UI::UITextureHandle& icon) { m_browserModel.SetFolderIcon(icon); } void EditorProjectRuntime::Refresh() { m_browserModel.Refresh(); RevalidateSelection(); } const ProjectBrowserModel& EditorProjectRuntime::GetBrowserModel() const { return m_browserModel; } ProjectBrowserModel& EditorProjectRuntime::GetBrowserModel() { return m_browserModel; } bool EditorProjectRuntime::HasSelection() const { return m_selection.kind == EditorSelectionKind::ProjectItem && !m_selection.itemId.empty(); } const EditorSelectionState& EditorProjectRuntime::GetSelection() const { return m_selection; } std::uint64_t EditorProjectRuntime::GetSelectionStamp() const { return m_selection.stamp; } bool EditorProjectRuntime::SetSelection(std::string_view itemId) { const ProjectBrowserModel::AssetEntry* asset = FindAssetEntry(itemId); if (asset == nullptr) { return false; } const bool changed = m_selection.kind != EditorSelectionKind::ProjectItem || m_selection.itemId != asset->itemId || m_selection.absolutePath != asset->absolutePath || m_selection.directory != asset->directory; ApplySelection(*asset, !changed); return changed; } void EditorProjectRuntime::ClearSelection() { m_selection = {}; m_selection.stamp = GenerateEditorSelectionStamp(); } bool EditorProjectRuntime::NavigateToFolder(std::string_view itemId) { if (!m_browserModel.NavigateToFolder(itemId)) { return false; } ClearSelection(); return true; } bool EditorProjectRuntime::OpenItem(std::string_view itemId) { const ProjectBrowserModel::AssetEntry* asset = FindAssetEntry(itemId); if (asset == nullptr) { return false; } if (asset->directory) { return NavigateToFolder(asset->itemId); } if (!asset->canOpen) { return false; } if (asset->kind == ProjectBrowserModel::ItemKind::Scene && !asset->absolutePath.empty()) { m_pendingSceneOpenPath = asset->absolutePath; return true; } return false; } std::optional EditorProjectRuntime::ConsumePendingSceneOpenPath() { if (!m_pendingSceneOpenPath.has_value()) { return std::nullopt; } std::optional result = m_pendingSceneOpenPath; m_pendingSceneOpenPath.reset(); return result; } const ProjectBrowserModel::FolderEntry* EditorProjectRuntime::FindFolderEntry( std::string_view itemId) const { return m_browserModel.FindFolderEntry(itemId); } const ProjectBrowserModel::AssetEntry* EditorProjectRuntime::FindAssetEntry( std::string_view itemId) const { return m_browserModel.FindAssetEntry(itemId); } EditorProjectRuntime::AssetCommandTarget EditorProjectRuntime::ResolveAssetCommandTarget( std::string_view explicitItemId, bool forceCurrentFolder) const { AssetCommandTarget target = {}; target.currentFolder = FindFolderEntry(m_browserModel.GetCurrentFolderId()); const ProjectBrowserModel::AssetEntry* explicitAsset = explicitItemId.empty() ? nullptr : FindAssetEntry(explicitItemId); const ProjectBrowserModel::FolderEntry* explicitFolder = explicitItemId.empty() ? nullptr : FindFolderEntry(explicitItemId); if (!forceCurrentFolder && explicitAsset != nullptr) { PopulateAssetCommandTargetFromAsset( target, m_browserModel, *explicitAsset, explicitAsset->directory ? FindFolderEntry(explicitAsset->itemId) : target.currentFolder); return target; } if (!forceCurrentFolder && explicitFolder != nullptr) { PopulateAssetCommandTargetFromFolder( target, m_browserModel, *explicitFolder); return target; } if (forceCurrentFolder) { if (target.currentFolder != nullptr) { PopulateAssetCommandTargetFromFolder( target, m_browserModel, *target.currentFolder); } return target; } if (HasSelection()) { if (const ProjectBrowserModel::AssetEntry* selectedAsset = FindAssetEntry(m_selection.itemId); selectedAsset != nullptr) { PopulateAssetCommandTargetFromAsset( target, m_browserModel, *selectedAsset, selectedAsset->directory ? FindFolderEntry(selectedAsset->itemId) : target.currentFolder); return target; } } if (target.currentFolder != nullptr) { PopulateAssetCommandTargetFromFolder( target, m_browserModel, *target.currentFolder); } return target; } std::optional EditorProjectRuntime::ResolveEditCommandTarget( std::string_view explicitItemId, bool forceCurrentFolder) const { if (forceCurrentFolder) { return std::nullopt; } if (!explicitItemId.empty()) { if (const ProjectBrowserModel::AssetEntry* asset = FindAssetEntry(explicitItemId); asset != nullptr) { return BuildEditCommandTarget(*asset); } if (const ProjectBrowserModel::FolderEntry* folder = FindFolderEntry(explicitItemId); folder != nullptr) { return BuildEditCommandTarget(m_browserModel, *folder); } return std::nullopt; } if (HasSelection()) { if (const ProjectBrowserModel::AssetEntry* selectedAsset = FindAssetEntry(m_selection.itemId); selectedAsset != nullptr) { return BuildEditCommandTarget(*selectedAsset); } } if (const ProjectBrowserModel::FolderEntry* currentFolder = FindFolderEntry(m_browserModel.GetCurrentFolderId()); currentFolder != nullptr) { return BuildEditCommandTarget(m_browserModel, *currentFolder); } return std::nullopt; } bool EditorProjectRuntime::CreateFolder( std::string_view parentFolderId, std::string_view requestedName, std::string* createdFolderId) { const bool created = m_browserModel.CreateFolder(parentFolderId, requestedName, createdFolderId); RevalidateSelection(); return created; } bool EditorProjectRuntime::CreateMaterial( std::string_view parentFolderId, std::string_view requestedName, std::string* createdItemId) { const bool created = m_browserModel.CreateMaterial(parentFolderId, requestedName, createdItemId); RevalidateSelection(); return created; } bool EditorProjectRuntime::RenameItem( std::string_view itemId, std::string_view newName, std::string* renamedItemId) { std::string resolvedRenamedItemId = {}; std::string* renameOutput = renamedItemId != nullptr ? renamedItemId : &resolvedRenamedItemId; if (!m_browserModel.RenameItem(itemId, newName, renameOutput)) { return false; } if (SelectionTargetsItem(itemId)) { if (!renameOutput->empty() && !SetSelection(*renameOutput)) { ClearSelection(); } } else { RevalidateSelection(); } return true; } bool EditorProjectRuntime::DeleteItem(std::string_view itemId) { const bool selectionAffected = SelectionTargetsItem(itemId); if (!m_browserModel.DeleteItem(itemId)) { return false; } if (selectionAffected) { ClearSelection(); } else { RevalidateSelection(); } return true; } bool EditorProjectRuntime::CanMoveItemToFolder( std::string_view itemId, std::string_view targetFolderId) const { return m_browserModel.CanMoveItemToFolder(itemId, targetFolderId); } bool EditorProjectRuntime::MoveItemToFolder( std::string_view itemId, std::string_view targetFolderId, std::string* movedItemId) { const bool selectionAffected = SelectionTargetsItem(itemId); if (!m_browserModel.MoveItemToFolder(itemId, targetFolderId, movedItemId)) { return false; } if (selectionAffected) { ClearSelection(); } else { RevalidateSelection(); } return true; } std::optional EditorProjectRuntime::GetParentFolderId( std::string_view itemId) const { return m_browserModel.GetParentFolderId(itemId); } bool EditorProjectRuntime::CanReparentFolder( std::string_view sourceFolderId, std::string_view targetParentId) const { return m_browserModel.CanReparentFolder(sourceFolderId, targetParentId); } bool EditorProjectRuntime::ReparentFolder( std::string_view sourceFolderId, std::string_view targetParentId, std::string* movedFolderId) { const bool selectionAffected = SelectionTargetsItem(sourceFolderId); if (!m_browserModel.ReparentFolder(sourceFolderId, targetParentId, movedFolderId)) { return false; } if (selectionAffected) { ClearSelection(); } else { RevalidateSelection(); } return true; } bool EditorProjectRuntime::MoveFolderToRoot( std::string_view sourceFolderId, std::string* movedFolderId) { const bool selectionAffected = SelectionTargetsItem(sourceFolderId); if (!m_browserModel.MoveFolderToRoot(sourceFolderId, movedFolderId)) { return false; } if (selectionAffected) { ClearSelection(); } else { RevalidateSelection(); } return true; } void EditorProjectRuntime::ApplySelection( const ProjectBrowserModel::AssetEntry& asset, bool preserveStamp) { const std::uint64_t previousStamp = m_selection.stamp; m_selection = {}; m_selection.kind = EditorSelectionKind::ProjectItem; m_selection.itemId = asset.itemId; m_selection.displayName = asset.displayName.empty() ? asset.nameWithExtension : asset.displayName; m_selection.absolutePath = asset.absolutePath; m_selection.directory = asset.directory; m_selection.stamp = preserveStamp && previousStamp != 0u ? previousStamp : GenerateEditorSelectionStamp(); } void EditorProjectRuntime::RevalidateSelection() { if (!HasSelection()) { return; } const ProjectBrowserModel::AssetEntry* asset = FindAssetEntry(m_selection.itemId); if (asset == nullptr) { ClearSelection(); return; } ApplySelection(*asset, true); } bool EditorProjectRuntime::SelectionTargetsItem(std::string_view itemId) const { return HasSelection() && IsSameOrDescendantItemId(m_selection.itemId, itemId); } } // namespace XCEngine::UI::Editor::App