refactor(new_editor/app): reorganize host structure and add smoke test

This commit is contained in:
2026-04-15 08:24:06 +08:00
parent 3617b4840b
commit 9e5954cf0a
235 changed files with 11157 additions and 10028 deletions

View File

@@ -1,179 +1,52 @@
#include "Project/ProductProjectBrowserModel.h"
#include "ProjectBrowserModelSupport.h"
#include <algorithm>
#include <cctype>
#include <string_view>
#include <system_error>
namespace XCEngine::UI::Editor::App {
#include <windows.h>
using namespace ProjectBrowserModelSupport;
namespace XCEngine::UI::Editor::App::Project {
namespace {
constexpr std::string_view kAssetsRootId = "Assets";
std::string ToLowerCopy(std::string value) {
std::transform(
value.begin(),
value.end(),
value.begin(),
[](unsigned char character) {
return static_cast<char>(std::tolower(character));
});
return value;
}
std::string WideToUtf8(std::wstring_view value) {
if (value.empty()) {
return {};
}
const int requiredSize = WideCharToMultiByte(
CP_UTF8,
0,
value.data(),
static_cast<int>(value.size()),
nullptr,
0,
nullptr,
nullptr);
if (requiredSize <= 0) {
return {};
}
std::string result(static_cast<std::size_t>(requiredSize), '\0');
WideCharToMultiByte(
CP_UTF8,
0,
value.data(),
static_cast<int>(value.size()),
result.data(),
requiredSize,
nullptr,
nullptr);
return result;
}
std::string PathToUtf8String(const std::filesystem::path& path) {
return WideToUtf8(path.native());
}
std::string NormalizePathSeparators(std::string value) {
std::replace(value.begin(), value.end(), '\\', '/');
return value;
}
std::string BuildRelativeItemId(
const std::filesystem::path& path,
const std::filesystem::path& assetsRoot) {
const std::filesystem::path relative =
std::filesystem::relative(path, assetsRoot.parent_path());
const std::string normalized =
NormalizePathSeparators(PathToUtf8String(relative.lexically_normal()));
return normalized.empty() ? std::string(kAssetsRootId) : normalized;
}
std::string BuildAssetDisplayName(const std::filesystem::path& path, bool directory) {
if (directory) {
return PathToUtf8String(path.filename());
}
const std::string filename = PathToUtf8String(path.filename());
const std::size_t extensionOffset = filename.find_last_of('.');
if (extensionOffset == std::string::npos || extensionOffset == 0u) {
return filename;
}
return filename.substr(0u, extensionOffset);
}
bool IsMetaFile(const std::filesystem::path& path) {
return ToLowerCopy(path.extension().string()) == ".meta";
}
bool HasChildDirectories(const std::filesystem::path& folderPath) {
std::error_code errorCode = {};
const std::filesystem::directory_iterator end = {};
for (std::filesystem::directory_iterator iterator(folderPath, errorCode);
!errorCode && iterator != end;
iterator.increment(errorCode)) {
if (iterator->is_directory(errorCode)) {
return true;
}
}
return false;
}
std::vector<std::filesystem::path> CollectSortedChildDirectories(
const std::filesystem::path& folderPath) {
std::vector<std::filesystem::path> paths = {};
std::error_code errorCode = {};
const std::filesystem::directory_iterator end = {};
for (std::filesystem::directory_iterator iterator(folderPath, errorCode);
!errorCode && iterator != end;
iterator.increment(errorCode)) {
if (iterator->is_directory(errorCode)) {
paths.push_back(iterator->path());
}
}
std::sort(
paths.begin(),
paths.end(),
[](const std::filesystem::path& lhs, const std::filesystem::path& rhs) {
return ToLowerCopy(PathToUtf8String(lhs.filename())) <
ToLowerCopy(PathToUtf8String(rhs.filename()));
});
return paths;
}
} // namespace
void ProductProjectBrowserModel::Initialize(const std::filesystem::path& repoRoot) {
void ProjectBrowserModel::Initialize(const std::filesystem::path& repoRoot) {
m_assetsRootPath = (repoRoot / "project/Assets").lexically_normal();
Refresh();
}
void ProductProjectBrowserModel::SetFolderIcon(const ::XCEngine::UI::UITextureHandle& icon) {
void ProjectBrowserModel::SetFolderIcon(const ::XCEngine::UI::UITextureHandle& icon) {
m_folderIcon = icon;
if (!m_assetsRootPath.empty()) {
RefreshFolderTree();
}
}
void ProductProjectBrowserModel::Refresh() {
void ProjectBrowserModel::Refresh() {
RefreshFolderTree();
EnsureValidCurrentFolder();
RefreshAssetList();
}
bool ProductProjectBrowserModel::Empty() const {
bool ProjectBrowserModel::Empty() const {
return m_treeItems.empty();
}
const std::filesystem::path& ProductProjectBrowserModel::GetAssetsRootPath() const {
const std::filesystem::path& ProjectBrowserModel::GetAssetsRootPath() const {
return m_assetsRootPath;
}
const std::vector<ProductProjectBrowserModel::FolderEntry>& ProductProjectBrowserModel::GetFolderEntries() const {
const std::vector<ProjectBrowserModel::FolderEntry>& ProjectBrowserModel::GetFolderEntries() const {
return m_folderEntries;
}
const std::vector<Widgets::UIEditorTreeViewItem>& ProductProjectBrowserModel::GetTreeItems() const {
const std::vector<Widgets::UIEditorTreeViewItem>& ProjectBrowserModel::GetTreeItems() const {
return m_treeItems;
}
const std::vector<ProductProjectBrowserModel::AssetEntry>& ProductProjectBrowserModel::GetAssetEntries() const {
const std::vector<ProjectBrowserModel::AssetEntry>& ProjectBrowserModel::GetAssetEntries() const {
return m_assetEntries;
}
const std::string& ProductProjectBrowserModel::GetCurrentFolderId() const {
const std::string& ProjectBrowserModel::GetCurrentFolderId() const {
return m_currentFolderId;
}
const ProductProjectBrowserModel::FolderEntry* ProductProjectBrowserModel::FindFolderEntry(
const ProjectBrowserModel::FolderEntry* ProjectBrowserModel::FindFolderEntry(
std::string_view itemId) const {
for (const FolderEntry& entry : m_folderEntries) {
if (entry.itemId == itemId) {
@@ -184,7 +57,7 @@ const ProductProjectBrowserModel::FolderEntry* ProductProjectBrowserModel::FindF
return nullptr;
}
const ProductProjectBrowserModel::AssetEntry* ProductProjectBrowserModel::FindAssetEntry(
const ProjectBrowserModel::AssetEntry* ProjectBrowserModel::FindAssetEntry(
std::string_view itemId) const {
for (const AssetEntry& entry : m_assetEntries) {
if (entry.itemId == itemId) {
@@ -195,7 +68,7 @@ const ProductProjectBrowserModel::AssetEntry* ProductProjectBrowserModel::FindAs
return nullptr;
}
bool ProductProjectBrowserModel::NavigateToFolder(std::string_view itemId) {
bool ProjectBrowserModel::NavigateToFolder(std::string_view itemId) {
if (itemId.empty() || FindFolderEntry(itemId) == nullptr || itemId == m_currentFolderId) {
return false;
}
@@ -206,7 +79,7 @@ bool ProductProjectBrowserModel::NavigateToFolder(std::string_view itemId) {
return true;
}
std::vector<ProductProjectBrowserModel::BreadcrumbSegment> ProductProjectBrowserModel::BuildBreadcrumbSegments() const {
std::vector<ProjectBrowserModel::BreadcrumbSegment> ProjectBrowserModel::BuildBreadcrumbSegments() const {
std::vector<BreadcrumbSegment> segments = {};
if (m_currentFolderId.empty()) {
segments.push_back(BreadcrumbSegment{ std::string(kAssetsRootId), std::string(kAssetsRootId), true });
@@ -251,117 +124,7 @@ std::vector<ProductProjectBrowserModel::BreadcrumbSegment> ProductProjectBrowser
return segments;
}
std::vector<std::string> ProductProjectBrowserModel::CollectCurrentFolderAncestorIds() const {
return BuildAncestorFolderIds(m_currentFolderId);
}
std::vector<std::string> ProductProjectBrowserModel::BuildAncestorFolderIds(
std::string_view itemId) const {
std::vector<std::string> ancestors = {};
const FolderEntry* folderEntry = FindFolderEntry(itemId);
if (folderEntry == nullptr) {
return ancestors;
}
std::filesystem::path path = folderEntry->absolutePath;
while (true) {
ancestors.push_back(BuildRelativeItemId(path, m_assetsRootPath));
if (path == m_assetsRootPath) {
break;
}
path = path.parent_path();
}
std::reverse(ancestors.begin(), ancestors.end());
return ancestors;
}
void ProductProjectBrowserModel::RefreshFolderTree() {
m_folderEntries.clear();
m_treeItems.clear();
if (m_assetsRootPath.empty() || !std::filesystem::exists(m_assetsRootPath)) {
return;
}
const auto appendFolderRecursive =
[&](auto&& self, const std::filesystem::path& folderPath, std::uint32_t depth) -> void {
const std::string itemId = BuildRelativeItemId(folderPath, m_assetsRootPath);
FolderEntry folderEntry = {};
folderEntry.itemId = itemId;
folderEntry.absolutePath = folderPath;
folderEntry.label = PathToUtf8String(folderPath.filename());
m_folderEntries.push_back(folderEntry);
Widgets::UIEditorTreeViewItem item = {};
item.itemId = itemId;
item.label = folderEntry.label;
item.depth = depth;
item.forceLeaf = !HasChildDirectories(folderPath);
item.leadingIcon = m_folderIcon;
m_treeItems.push_back(std::move(item));
const std::vector<std::filesystem::path> childFolders =
CollectSortedChildDirectories(folderPath);
for (const std::filesystem::path& childPath : childFolders) {
self(self, childPath, depth + 1u);
}
};
appendFolderRecursive(appendFolderRecursive, m_assetsRootPath, 0u);
}
void ProductProjectBrowserModel::RefreshAssetList() {
EnsureValidCurrentFolder();
m_assetEntries.clear();
const FolderEntry* currentFolder = FindFolderEntry(m_currentFolderId);
if (currentFolder == nullptr) {
return;
}
std::vector<std::filesystem::directory_entry> entries = {};
std::error_code errorCode = {};
const std::filesystem::directory_iterator end = {};
for (std::filesystem::directory_iterator iterator(currentFolder->absolutePath, errorCode);
!errorCode && iterator != end;
iterator.increment(errorCode)) {
if (!iterator->exists(errorCode) || IsMetaFile(iterator->path())) {
continue;
}
if (!iterator->is_directory(errorCode) && !iterator->is_regular_file(errorCode)) {
continue;
}
entries.push_back(*iterator);
}
std::sort(
entries.begin(),
entries.end(),
[](const std::filesystem::directory_entry& lhs, const std::filesystem::directory_entry& rhs) {
const bool lhsDirectory = lhs.is_directory();
const bool rhsDirectory = rhs.is_directory();
if (lhsDirectory != rhsDirectory) {
return lhsDirectory && !rhsDirectory;
}
return ToLowerCopy(PathToUtf8String(lhs.path().filename())) <
ToLowerCopy(PathToUtf8String(rhs.path().filename()));
});
for (const std::filesystem::directory_entry& entry : entries) {
AssetEntry assetEntry = {};
assetEntry.itemId = BuildRelativeItemId(entry.path(), m_assetsRootPath);
assetEntry.absolutePath = entry.path();
assetEntry.displayName = BuildAssetDisplayName(entry.path(), entry.is_directory());
assetEntry.directory = entry.is_directory();
m_assetEntries.push_back(std::move(assetEntry));
}
}
void ProductProjectBrowserModel::EnsureValidCurrentFolder() {
void ProjectBrowserModel::EnsureValidCurrentFolder() {
if (m_currentFolderId.empty()) {
m_currentFolderId = std::string(kAssetsRootId);
}
@@ -373,4 +136,4 @@ void ProductProjectBrowserModel::EnsureValidCurrentFolder() {
}
}
} // namespace XCEngine::UI::Editor::App::Project
} // namespace XCEngine::UI::Editor::App