feat(new_editor): wire project, inspector, and viewport runtime
This commit is contained in:
454
new_editor/app/Project/EditorProjectRuntime.cpp
Normal file
454
new_editor/app/Project/EditorProjectRuntime.cpp
Normal file
@@ -0,0 +1,454 @@
|
||||
#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<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()) {
|
||||
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::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()) {
|
||||
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<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;
|
||||
}
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user