#include "Project/EditorProjectRuntime.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_ownedSelectionService = {}; m_selectionService = &m_ownedSelectionService; m_pendingSceneOpenPath.reset(); return true; } void EditorProjectRuntime::BindSelectionService( EditorSelectionService* selectionService) { m_selectionService = selectionService != nullptr ? selectionService : &m_ownedSelectionService; } 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 SelectionService().HasSelectionKind(EditorSelectionKind::ProjectItem); } const EditorSelectionState& EditorProjectRuntime::GetSelection() const { return SelectionService().GetSelection(); } std::uint64_t EditorProjectRuntime::GetSelectionStamp() const { return SelectionService().GetStamp(); } bool EditorProjectRuntime::SetSelection(std::string_view itemId) { const ProjectBrowserModel::AssetEntry* asset = FindAssetEntry(itemId); if (asset == nullptr) { return false; } const EditorSelectionState& selection = SelectionService().GetSelection(); const bool changed = selection.kind != EditorSelectionKind::ProjectItem || selection.itemId != asset->itemId || selection.absolutePath != asset->absolutePath || selection.directory != asset->directory; ApplySelection(*asset); return changed; } void EditorProjectRuntime::ClearSelection() { SelectionService().ClearSelection(); } 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()) { const EditorSelectionState& selection = GetSelection(); if (const ProjectBrowserModel::AssetEntry* selectedAsset = FindAssetEntry(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()) { const EditorSelectionState& selection = GetSelection(); if (const ProjectBrowserModel::AssetEntry* selectedAsset = FindAssetEntry(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; } EditorSelectionService& EditorProjectRuntime::SelectionService() { return *m_selectionService; } const EditorSelectionService& EditorProjectRuntime::SelectionService() const { return *m_selectionService; } void EditorProjectRuntime::ApplySelection( const ProjectBrowserModel::AssetEntry& asset) { SelectionService().SetProjectSelection( asset.itemId, asset.displayName.empty() ? asset.nameWithExtension : asset.displayName, asset.absolutePath, asset.directory); } void EditorProjectRuntime::RevalidateSelection() { if (!HasSelection()) { return; } const ProjectBrowserModel::AssetEntry* asset = FindAssetEntry(GetSelection().itemId); if (asset == nullptr) { ClearSelection(); return; } ApplySelection(*asset); } bool EditorProjectRuntime::SelectionTargetsItem(std::string_view itemId) const { return HasSelection() && IsSameOrDescendantItemId(GetSelection().itemId, itemId); } } // namespace XCEngine::UI::Editor::App