关键节点

This commit is contained in:
2026-04-25 16:46:01 +08:00
parent 6002d86a7e
commit ef41c44464
516 changed files with 6175 additions and 12401 deletions

View File

@@ -0,0 +1,463 @@
#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
void EditorProjectRuntime::Reset() {
m_browserModel.Reset();
m_ownedSelectionService.ClearSelection();
m_selectionService = &m_ownedSelectionService;
m_pendingSceneOpenPath.reset();
}
bool EditorProjectRuntime::Initialize(const std::filesystem::path& repoRoot) {
Reset();
m_browserModel.Initialize(repoRoot);
return true;
}
void EditorProjectRuntime::BindSelectionService(
EditorSelectionService* selectionService) {
m_selectionService =
selectionService != nullptr ? selectionService : &m_ownedSelectionService;
}
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

View File

@@ -0,0 +1,121 @@
#pragma once
#include "Features/Project/ProjectBrowserModel.h"
#include "State/EditorSelectionService.h"
#include "State/EditorSession.h"
#include <XCEngine/UI/Types.h>
#include <filesystem>
#include <optional>
#include <string>
#include <string_view>
namespace XCEngine::UI::Editor::App {
class EditorProjectRuntime {
public:
EditorProjectRuntime() = default;
EditorProjectRuntime(const EditorProjectRuntime&) = delete;
EditorProjectRuntime& operator=(const EditorProjectRuntime&) = delete;
EditorProjectRuntime(EditorProjectRuntime&&) = delete;
EditorProjectRuntime& operator=(EditorProjectRuntime&&) = delete;
struct EditCommandTarget {
std::string itemId = {};
std::filesystem::path absolutePath = {};
std::string displayName = {};
ProjectBrowserModel::ItemKind itemKind =
ProjectBrowserModel::ItemKind::File;
bool directory = false;
bool assetsRoot = false;
};
struct AssetCommandTarget {
const ProjectBrowserModel::FolderEntry* currentFolder = nullptr;
const ProjectBrowserModel::FolderEntry* containerFolder = nullptr;
const ProjectBrowserModel::FolderEntry* subjectFolder = nullptr;
const ProjectBrowserModel::AssetEntry* subjectAsset = nullptr;
std::string subjectItemId = {};
std::filesystem::path subjectPath = {};
std::string subjectDisplayName = {};
std::string subjectRelativePath = {};
bool showInExplorerSelectTarget = false;
};
void Reset();
bool Initialize(const std::filesystem::path& repoRoot);
void BindSelectionService(EditorSelectionService* selectionService);
void Refresh();
const ProjectBrowserModel& GetBrowserModel() const;
ProjectBrowserModel& GetBrowserModel();
bool HasSelection() const;
const EditorSelectionState& GetSelection() const;
std::uint64_t GetSelectionStamp() const;
bool SetSelection(std::string_view itemId);
void ClearSelection();
bool NavigateToFolder(std::string_view itemId);
bool OpenItem(std::string_view itemId);
std::optional<std::filesystem::path> ConsumePendingSceneOpenPath();
const ProjectBrowserModel::FolderEntry* FindFolderEntry(
std::string_view itemId) const;
const ProjectBrowserModel::AssetEntry* FindAssetEntry(
std::string_view itemId) const;
AssetCommandTarget ResolveAssetCommandTarget(
std::string_view explicitItemId = {},
bool forceCurrentFolder = false) const;
std::optional<EditCommandTarget> ResolveEditCommandTarget(
std::string_view explicitItemId = {},
bool forceCurrentFolder = false) const;
bool CreateFolder(
std::string_view parentFolderId,
std::string_view requestedName,
std::string* createdFolderId = nullptr);
bool CreateMaterial(
std::string_view parentFolderId,
std::string_view requestedName,
std::string* createdItemId = nullptr);
bool RenameItem(
std::string_view itemId,
std::string_view newName,
std::string* renamedItemId = nullptr);
bool DeleteItem(std::string_view itemId);
bool CanMoveItemToFolder(
std::string_view itemId,
std::string_view targetFolderId) const;
bool MoveItemToFolder(
std::string_view itemId,
std::string_view targetFolderId,
std::string* movedItemId = nullptr);
std::optional<std::string> GetParentFolderId(std::string_view itemId) const;
bool CanReparentFolder(
std::string_view sourceFolderId,
std::string_view targetParentId) const;
bool ReparentFolder(
std::string_view sourceFolderId,
std::string_view targetParentId,
std::string* movedFolderId = nullptr);
bool MoveFolderToRoot(
std::string_view sourceFolderId,
std::string* movedFolderId = nullptr);
private:
EditorSelectionService& SelectionService();
const EditorSelectionService& SelectionService() const;
void ApplySelection(const ProjectBrowserModel::AssetEntry& asset);
void RevalidateSelection();
bool SelectionTargetsItem(std::string_view itemId) const;
ProjectBrowserModel m_browserModel = {};
EditorSelectionService m_ownedSelectionService = {};
EditorSelectionService* m_selectionService = &m_ownedSelectionService;
std::optional<std::filesystem::path> m_pendingSceneOpenPath = std::nullopt;
};
} // namespace XCEngine::UI::Editor::App