Add backpack editor startup scene
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#include "ProjectManager.h"
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cwctype>
|
||||
#include <fstream>
|
||||
#include <windows.h>
|
||||
@@ -12,6 +13,24 @@ namespace Editor {
|
||||
|
||||
namespace {
|
||||
|
||||
std::wstring Utf8PathToWstring(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;
|
||||
}
|
||||
|
||||
std::string WstringPathToUtf8(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;
|
||||
}
|
||||
|
||||
std::wstring MakePathKey(const fs::path& path) {
|
||||
std::wstring key = path.lexically_normal().generic_wstring();
|
||||
std::transform(key.begin(), key.end(), key.begin(), ::towlower);
|
||||
@@ -34,6 +53,92 @@ bool IsSameOrDescendantPath(const fs::path& path, const fs::path& ancestor) {
|
||||
return pathKey.rfind(ancestorKey, 0) == 0;
|
||||
}
|
||||
|
||||
std::string TrimAssetName(const std::string& name) {
|
||||
const auto first = std::find_if_not(name.begin(), name.end(), [](unsigned char ch) {
|
||||
return std::isspace(ch) != 0;
|
||||
});
|
||||
if (first == name.end()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto last = std::find_if_not(name.rbegin(), name.rend(), [](unsigned char ch) {
|
||||
return std::isspace(ch) != 0;
|
||||
}).base();
|
||||
return std::string(first, last);
|
||||
}
|
||||
|
||||
bool HasInvalidAssetName(const std::string& name) {
|
||||
if (name.empty() || name == "." || name == "..") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return name.find_first_of("\\/:*?\"<>|") != std::string::npos;
|
||||
}
|
||||
|
||||
fs::path MakeUniqueFolderPath(const fs::path& parentPath, const std::string& preferredName) {
|
||||
fs::path candidatePath = parentPath / Utf8PathToWstring(preferredName);
|
||||
if (!fs::exists(candidatePath)) {
|
||||
return candidatePath;
|
||||
}
|
||||
|
||||
for (size_t suffix = 1;; ++suffix) {
|
||||
candidatePath = parentPath / Utf8PathToWstring(preferredName + " " + std::to_string(suffix));
|
||||
if (!fs::exists(candidatePath)) {
|
||||
return candidatePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string BuildRenamedEntryName(const fs::path& sourcePath, const std::string& requestedName) {
|
||||
if (fs::is_directory(sourcePath)) {
|
||||
return requestedName;
|
||||
}
|
||||
|
||||
const fs::path requestedPath = Utf8PathToWstring(requestedName);
|
||||
if (requestedPath.has_extension()) {
|
||||
return WstringPathToUtf8(requestedPath.filename().wstring());
|
||||
}
|
||||
|
||||
return requestedName + WstringPathToUtf8(sourcePath.extension().wstring());
|
||||
}
|
||||
|
||||
fs::path MakeCaseOnlyRenameTempPath(const fs::path& sourcePath) {
|
||||
const fs::path parentPath = sourcePath.parent_path();
|
||||
const std::wstring sourceStem = sourcePath.filename().wstring();
|
||||
|
||||
for (size_t suffix = 0;; ++suffix) {
|
||||
std::wstring tempName = sourceStem + L".xc_tmp_rename";
|
||||
if (suffix > 0) {
|
||||
tempName += std::to_wstring(suffix);
|
||||
}
|
||||
|
||||
const fs::path tempPath = parentPath / tempName;
|
||||
if (!fs::exists(tempPath)) {
|
||||
return tempPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RenamePathCaseAware(const fs::path& sourcePath, const fs::path& destPath) {
|
||||
if (MakePathKey(sourcePath) != MakePathKey(destPath)) {
|
||||
if (fs::exists(destPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fs::rename(sourcePath, destPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sourcePath.filename() == destPath.filename()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const fs::path tempPath = MakeCaseOnlyRenameTempPath(sourcePath);
|
||||
fs::rename(sourcePath, tempPath);
|
||||
fs::rename(tempPath, destPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const std::vector<AssetItemPtr>& ProjectManager::GetCurrentItems() const {
|
||||
@@ -134,28 +239,10 @@ std::string ProjectManager::GetPathName(size_t index) const {
|
||||
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);
|
||||
std::wstring projectPathW = Utf8PathToWstring(projectPath);
|
||||
fs::path assetsPath = fs::path(projectPathW) / L"Assets";
|
||||
|
||||
try {
|
||||
@@ -166,7 +253,7 @@ void ProjectManager::Initialize(const std::string& projectPath) {
|
||||
|
||||
m_rootFolder = ScanDirectory(assetsPath.wstring());
|
||||
m_rootFolder->name = "Assets";
|
||||
m_rootFolder->fullPath = WstringToUtf8(assetsPath.wstring());
|
||||
m_rootFolder->fullPath = WstringPathToUtf8(assetsPath.wstring());
|
||||
|
||||
m_path.clear();
|
||||
m_path.push_back(m_rootFolder);
|
||||
@@ -176,7 +263,7 @@ void ProjectManager::Initialize(const std::string& projectPath) {
|
||||
m_rootFolder->name = "Assets";
|
||||
m_rootFolder->isFolder = true;
|
||||
m_rootFolder->type = "Folder";
|
||||
m_rootFolder->fullPath = WstringToUtf8(assetsPath.wstring());
|
||||
m_rootFolder->fullPath = WstringPathToUtf8(assetsPath.wstring());
|
||||
m_path.clear();
|
||||
m_path.push_back(m_rootFolder);
|
||||
ClearSelection();
|
||||
@@ -185,10 +272,10 @@ void ProjectManager::Initialize(const std::string& projectPath) {
|
||||
|
||||
std::wstring ProjectManager::GetCurrentFullPathW() const {
|
||||
if (AssetItemPtr currentFolder = GetCurrentFolder()) {
|
||||
return Utf8ToWstring(currentFolder->fullPath);
|
||||
return Utf8PathToWstring(currentFolder->fullPath);
|
||||
}
|
||||
|
||||
return Utf8ToWstring(m_projectPath);
|
||||
return Utf8PathToWstring(m_projectPath);
|
||||
}
|
||||
|
||||
void ProjectManager::RefreshCurrentFolder() {
|
||||
@@ -200,13 +287,31 @@ void ProjectManager::RefreshCurrentFolder() {
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectManager::CreateFolder(const std::string& name) {
|
||||
AssetItemPtr ProjectManager::CreateFolder(const std::string& name) {
|
||||
if (!m_rootFolder) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
try {
|
||||
std::wstring fullPath = GetCurrentFullPathW();
|
||||
fs::path newFolderPath = fs::path(fullPath) / Utf8ToWstring(name);
|
||||
fs::create_directory(newFolderPath);
|
||||
const std::string trimmedName = TrimAssetName(name);
|
||||
if (HasInvalidAssetName(trimmedName)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const fs::path currentFolderPath = GetCurrentFullPathW();
|
||||
const fs::path newFolderPath = MakeUniqueFolderPath(currentFolderPath, trimmedName);
|
||||
if (!fs::create_directory(newFolderPath)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefreshCurrentFolder();
|
||||
const AssetItemPtr createdFolder = FindCurrentItemByPath(WstringPathToUtf8(newFolderPath.wstring()));
|
||||
if (createdFolder) {
|
||||
SetSelectedItem(createdFolder);
|
||||
}
|
||||
return createdFolder;
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,8 +321,8 @@ bool ProjectManager::DeleteItem(const std::string& fullPath) {
|
||||
}
|
||||
|
||||
try {
|
||||
const fs::path itemPath = Utf8ToWstring(fullPath);
|
||||
const fs::path rootPath = Utf8ToWstring(m_rootFolder->fullPath);
|
||||
const fs::path itemPath = Utf8PathToWstring(fullPath);
|
||||
const fs::path rootPath = Utf8PathToWstring(m_rootFolder->fullPath);
|
||||
if (!fs::exists(itemPath) || !IsSameOrDescendantPath(itemPath, rootPath)) {
|
||||
return false;
|
||||
}
|
||||
@@ -242,9 +347,9 @@ bool ProjectManager::MoveItem(const std::string& sourceFullPath, const std::stri
|
||||
}
|
||||
|
||||
try {
|
||||
const fs::path sourcePath = Utf8ToWstring(sourceFullPath);
|
||||
const fs::path destFolderPath = Utf8ToWstring(destFolderFullPath);
|
||||
const fs::path rootPath = Utf8ToWstring(m_rootFolder->fullPath);
|
||||
const fs::path sourcePath = Utf8PathToWstring(sourceFullPath);
|
||||
const fs::path destFolderPath = Utf8PathToWstring(destFolderFullPath);
|
||||
const fs::path rootPath = Utf8PathToWstring(m_rootFolder->fullPath);
|
||||
|
||||
if (!fs::exists(sourcePath) || !fs::exists(destFolderPath) || !fs::is_directory(destFolderPath)) {
|
||||
return false;
|
||||
@@ -272,6 +377,49 @@ bool ProjectManager::MoveItem(const std::string& sourceFullPath, const std::stri
|
||||
}
|
||||
}
|
||||
|
||||
bool ProjectManager::RenameItem(const std::string& sourceFullPath, const std::string& newName) {
|
||||
if (sourceFullPath.empty() || !m_rootFolder) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const std::string trimmedName = TrimAssetName(newName);
|
||||
if (HasInvalidAssetName(trimmedName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fs::path sourcePath = Utf8PathToWstring(sourceFullPath);
|
||||
const fs::path rootPath = Utf8PathToWstring(m_rootFolder->fullPath);
|
||||
if (!fs::exists(sourcePath) || !IsSameOrDescendantPath(sourcePath, rootPath)) {
|
||||
return false;
|
||||
}
|
||||
if (MakePathKey(sourcePath) == MakePathKey(rootPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string targetName = BuildRenamedEntryName(sourcePath, trimmedName);
|
||||
if (HasInvalidAssetName(targetName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fs::path destPath = sourcePath.parent_path() / Utf8PathToWstring(targetName);
|
||||
if (!RenamePathCaseAware(sourcePath, destPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_selectedItemPath.empty() &&
|
||||
MakePathKey(Utf8PathToWstring(m_selectedItemPath)) == MakePathKey(sourcePath))
|
||||
{
|
||||
m_selectedItemPath = WstringPathToUtf8(destPath.wstring());
|
||||
}
|
||||
|
||||
RefreshCurrentFolder();
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AssetItemPtr ProjectManager::FindCurrentItemByPath(const std::string& fullPath) const {
|
||||
const int index = FindCurrentItemIndex(fullPath);
|
||||
if (index < 0) {
|
||||
@@ -289,10 +437,10 @@ void ProjectManager::SyncSelection() {
|
||||
|
||||
AssetItemPtr ProjectManager::ScanDirectory(const std::wstring& path) {
|
||||
auto folder = std::make_shared<AssetItem>();
|
||||
folder->name = WstringToUtf8(fs::path(path).filename().wstring());
|
||||
folder->name = WstringPathToUtf8(fs::path(path).filename().wstring());
|
||||
folder->isFolder = true;
|
||||
folder->type = "Folder";
|
||||
folder->fullPath = WstringToUtf8(path);
|
||||
folder->fullPath = WstringPathToUtf8(path);
|
||||
|
||||
if (!fs::exists(path)) return folder;
|
||||
|
||||
@@ -352,10 +500,10 @@ void ProjectManager::RebuildTreePreservingPath() {
|
||||
}
|
||||
}
|
||||
|
||||
const fs::path assetsPath = fs::path(Utf8ToWstring(m_projectPath)) / L"Assets";
|
||||
const fs::path assetsPath = fs::path(Utf8PathToWstring(m_projectPath)) / L"Assets";
|
||||
m_rootFolder = ScanDirectory(assetsPath.wstring());
|
||||
m_rootFolder->name = "Assets";
|
||||
m_rootFolder->fullPath = WstringToUtf8(assetsPath.wstring());
|
||||
m_rootFolder->fullPath = WstringPathToUtf8(assetsPath.wstring());
|
||||
|
||||
m_path.clear();
|
||||
m_path.push_back(m_rootFolder);
|
||||
@@ -373,9 +521,9 @@ void ProjectManager::RebuildTreePreservingPath() {
|
||||
|
||||
AssetItemPtr ProjectManager::CreateAssetItem(const std::wstring& path, const std::wstring& nameW, bool isFolder) {
|
||||
auto item = std::make_shared<AssetItem>();
|
||||
item->name = WstringToUtf8(nameW);
|
||||
item->name = WstringPathToUtf8(nameW);
|
||||
item->isFolder = isFolder;
|
||||
item->fullPath = WstringToUtf8(path);
|
||||
item->fullPath = WstringPathToUtf8(path);
|
||||
|
||||
if (isFolder) {
|
||||
item->type = "Folder";
|
||||
@@ -388,7 +536,7 @@ AssetItemPtr ProjectManager::CreateAssetItem(const std::wstring& path, const std
|
||||
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") {
|
||||
if (ext == L".png" || ext == L".jpg" || ext == L".jpeg" || 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";
|
||||
|
||||
Reference in New Issue
Block a user