Files
XCEngine/editor/src/Managers/ProjectManager.cpp

413 lines
12 KiB
C++
Raw Normal View History

#include "ProjectManager.h"
#include <filesystem>
#include <algorithm>
#include <cwctype>
#include <fstream>
#include <windows.h>
namespace fs = std::filesystem;
namespace XCEngine {
namespace Editor {
namespace {
std::wstring MakePathKey(const fs::path& path) {
std::wstring key = path.lexically_normal().generic_wstring();
std::transform(key.begin(), key.end(), key.begin(), ::towlower);
return key;
}
bool IsSameOrDescendantPath(const fs::path& path, const fs::path& ancestor) {
const std::wstring pathKey = MakePathKey(path);
std::wstring ancestorKey = MakePathKey(ancestor);
if (pathKey.empty() || ancestorKey.empty()) {
return false;
}
if (pathKey == ancestorKey) {
return true;
}
if (ancestorKey.back() != L'/') {
ancestorKey += L'/';
}
return pathKey.rfind(ancestorKey, 0) == 0;
}
} // namespace
const std::vector<AssetItemPtr>& ProjectManager::GetCurrentItems() const {
if (m_path.empty()) {
static const std::vector<AssetItemPtr> empty;
return empty;
}
return m_path.back()->children;
}
AssetItemPtr ProjectManager::GetSelectedItem() const {
return FindCurrentItemByPath(m_selectedItemPath);
}
int ProjectManager::GetSelectedIndex() const {
return FindCurrentItemIndex(m_selectedItemPath);
}
void ProjectManager::SetSelectedIndex(int index) {
const auto& items = GetCurrentItems();
if (index < 0 || index >= static_cast<int>(items.size())) {
ClearSelection();
return;
}
SetSelectedItem(items[index]);
}
void ProjectManager::SetSelectedItem(const AssetItemPtr& item) {
if (!item) {
ClearSelection();
return;
}
m_selectedItemPath = item->fullPath;
}
void ProjectManager::ClearSelection() {
m_selectedItemPath.clear();
}
int ProjectManager::FindCurrentItemIndex(const std::string& fullPath) const {
if (fullPath.empty()) {
return -1;
}
const auto& items = GetCurrentItems();
for (int i = 0; i < static_cast<int>(items.size()); ++i) {
if (items[i] && items[i]->fullPath == fullPath) {
return i;
}
}
return -1;
}
void ProjectManager::NavigateToFolder(const AssetItemPtr& folder) {
if (!folder || !folder->isFolder || !m_rootFolder) {
return;
}
std::vector<AssetItemPtr> resolvedPath;
if (!BuildPathToFolder(m_rootFolder, folder->fullPath, resolvedPath)) {
return;
}
m_path = std::move(resolvedPath);
ClearSelection();
}
void ProjectManager::NavigateBack() {
if (m_path.size() > 1) {
m_path.pop_back();
ClearSelection();
}
}
void ProjectManager::NavigateToIndex(size_t index) {
if (index >= m_path.size()) return;
while (m_path.size() > index + 1) {
m_path.pop_back();
}
ClearSelection();
}
std::string ProjectManager::GetCurrentPath() const {
if (m_path.empty()) return "Assets";
std::string result = "Assets";
for (size_t i = 1; i < m_path.size(); i++) {
result += "/";
result += m_path[i]->name;
}
return result;
}
std::string ProjectManager::GetPathName(size_t index) const {
if (index >= m_path.size()) return "";
return m_path[index]->name;
}
static std::wstring Utf8ToWstring(const std::string& str) {
if (str.empty()) return L"";
int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
if (len <= 0) return L"";
std::wstring result(len - 1, 0);
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &result[0], len);
return result;
}
static std::string WstringToUtf8(const std::wstring& wstr) {
if (wstr.empty()) return "";
int len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
if (len <= 0) return "";
std::string result(len - 1, 0);
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &result[0], len, nullptr, nullptr);
return result;
}
void ProjectManager::Initialize(const std::string& projectPath) {
m_projectPath = projectPath;
std::wstring projectPathW = Utf8ToWstring(projectPath);
fs::path assetsPath = fs::path(projectPathW) / L"Assets";
try {
if (!fs::exists(assetsPath)) {
fs::create_directories(assetsPath);
fs::create_directories(assetsPath / L"Scenes");
}
m_rootFolder = ScanDirectory(assetsPath.wstring());
m_rootFolder->name = "Assets";
m_rootFolder->fullPath = WstringToUtf8(assetsPath.wstring());
m_path.clear();
m_path.push_back(m_rootFolder);
ClearSelection();
} catch (...) {
m_rootFolder = std::make_shared<AssetItem>();
m_rootFolder->name = "Assets";
m_rootFolder->isFolder = true;
m_rootFolder->type = "Folder";
m_rootFolder->fullPath = WstringToUtf8(assetsPath.wstring());
m_path.clear();
m_path.push_back(m_rootFolder);
ClearSelection();
}
}
std::wstring ProjectManager::GetCurrentFullPathW() const {
if (AssetItemPtr currentFolder = GetCurrentFolder()) {
return Utf8ToWstring(currentFolder->fullPath);
}
return Utf8ToWstring(m_projectPath);
}
void ProjectManager::RefreshCurrentFolder() {
if (m_path.empty()) return;
try {
RebuildTreePreservingPath();
} catch (...) {
}
}
void ProjectManager::CreateFolder(const std::string& name) {
try {
std::wstring fullPath = GetCurrentFullPathW();
fs::path newFolderPath = fs::path(fullPath) / Utf8ToWstring(name);
fs::create_directory(newFolderPath);
RefreshCurrentFolder();
} catch (...) {
}
}
bool ProjectManager::DeleteItem(const std::string& fullPath) {
if (fullPath.empty() || !m_rootFolder) {
return false;
}
try {
const fs::path itemPath = Utf8ToWstring(fullPath);
const fs::path rootPath = Utf8ToWstring(m_rootFolder->fullPath);
if (!fs::exists(itemPath) || !IsSameOrDescendantPath(itemPath, rootPath)) {
return false;
}
if (MakePathKey(itemPath) == MakePathKey(rootPath)) {
return false;
}
fs::remove_all(itemPath);
if (m_selectedItemPath == fullPath) {
ClearSelection();
}
RefreshCurrentFolder();
return true;
} catch (...) {
return false;
}
}
bool ProjectManager::MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath) {
if (sourceFullPath.empty() || destFolderFullPath.empty() || !m_rootFolder) {
return false;
}
try {
const fs::path sourcePath = Utf8ToWstring(sourceFullPath);
const fs::path destFolderPath = Utf8ToWstring(destFolderFullPath);
const fs::path rootPath = Utf8ToWstring(m_rootFolder->fullPath);
if (!fs::exists(sourcePath) || !fs::exists(destFolderPath) || !fs::is_directory(destFolderPath)) {
return false;
}
if (!IsSameOrDescendantPath(sourcePath, rootPath) || !IsSameOrDescendantPath(destFolderPath, rootPath)) {
return false;
}
if (MakePathKey(sourcePath) == MakePathKey(rootPath)) {
return false;
}
if (fs::is_directory(sourcePath) && IsSameOrDescendantPath(destFolderPath, sourcePath)) {
return false;
}
const fs::path destPath = destFolderPath / sourcePath.filename();
if (MakePathKey(destPath) == MakePathKey(sourcePath) || fs::exists(destPath)) {
return false;
}
fs::rename(sourcePath, destPath);
RefreshCurrentFolder();
return true;
} catch (...) {
return false;
}
}
AssetItemPtr ProjectManager::FindCurrentItemByPath(const std::string& fullPath) const {
const int index = FindCurrentItemIndex(fullPath);
if (index < 0) {
return nullptr;
}
return GetCurrentItems()[index];
}
void ProjectManager::SyncSelection() {
if (!m_selectedItemPath.empty() && !FindCurrentItemByPath(m_selectedItemPath)) {
ClearSelection();
}
}
AssetItemPtr ProjectManager::ScanDirectory(const std::wstring& path) {
auto folder = std::make_shared<AssetItem>();
folder->name = WstringToUtf8(fs::path(path).filename().wstring());
folder->isFolder = true;
folder->type = "Folder";
folder->fullPath = WstringToUtf8(path);
if (!fs::exists(path)) return folder;
std::vector<AssetItemPtr> items;
try {
for (const auto& entry : fs::directory_iterator(path)) {
std::wstring nameW = entry.path().filename().wstring();
bool isFolder = entry.is_directory();
items.push_back(CreateAssetItem(entry.path().wstring(), nameW, isFolder));
}
} catch (...) {
}
std::sort(items.begin(), items.end(), [](const AssetItemPtr& a, const AssetItemPtr& b) {
if (a->isFolder != b->isFolder) return a->isFolder;
return a->name < b->name;
});
folder->children = items;
return folder;
}
bool ProjectManager::BuildPathToFolder(
const AssetItemPtr& current,
const std::string& fullPath,
std::vector<AssetItemPtr>& outPath) const {
if (!current || !current->isFolder) {
return false;
}
outPath.push_back(current);
if (current->fullPath == fullPath) {
return true;
}
for (const auto& child : current->children) {
if (!child || !child->isFolder) {
continue;
}
if (BuildPathToFolder(child, fullPath, outPath)) {
return true;
}
}
outPath.pop_back();
return false;
}
void ProjectManager::RebuildTreePreservingPath() {
std::vector<std::string> preservedPaths;
preservedPaths.reserve(m_path.size());
for (const auto& folder : m_path) {
if (folder && folder->isFolder) {
preservedPaths.push_back(folder->fullPath);
}
}
const fs::path assetsPath = fs::path(Utf8ToWstring(m_projectPath)) / L"Assets";
m_rootFolder = ScanDirectory(assetsPath.wstring());
m_rootFolder->name = "Assets";
m_rootFolder->fullPath = WstringToUtf8(assetsPath.wstring());
m_path.clear();
m_path.push_back(m_rootFolder);
for (size_t i = 1; i < preservedPaths.size(); ++i) {
std::vector<AssetItemPtr> resolvedPath;
if (!BuildPathToFolder(m_rootFolder, preservedPaths[i], resolvedPath)) {
break;
}
m_path = std::move(resolvedPath);
}
SyncSelection();
}
AssetItemPtr ProjectManager::CreateAssetItem(const std::wstring& path, const std::wstring& nameW, bool isFolder) {
auto item = std::make_shared<AssetItem>();
item->name = WstringToUtf8(nameW);
item->isFolder = isFolder;
item->fullPath = WstringToUtf8(path);
if (isFolder) {
item->type = "Folder";
try {
auto subFolder = ScanDirectory(path);
item->children = subFolder->children;
} catch (...) {
}
} else {
std::wstring ext = fs::path(path).extension().wstring();
std::transform(ext.begin(), ext.end(), ext.begin(), ::towlower);
if (ext == L".png" || ext == L".jpg" || ext == L".tga" || ext == L".bmp") {
item->type = "Texture";
} else if (ext == L".fbx" || ext == L".obj" || ext == L".gltf" || ext == L".glb") {
item->type = "Model";
} else if (ext == L".cs" || ext == L".cpp" || ext == L".h") {
item->type = "Script";
} else if (ext == L".mat") {
item->type = "Material";
} else if (ext == L".xc" || ext == L".unity" || ext == L".scene") {
item->type = "Scene";
} else if (ext == L".prefab") {
item->type = "Prefab";
} else {
item->type = "File";
}
}
return item;
}
}
}