463 lines
14 KiB
C++
463 lines
14 KiB
C++
#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<std::filesystem::path> EditorProjectRuntime::ConsumePendingSceneOpenPath() {
|
|
if (!m_pendingSceneOpenPath.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<std::filesystem::path> 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::EditCommandTarget>
|
|
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<std::string> 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
|