feat: update editor ui framework and assets
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#include "ProjectManager.h"
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <cwctype>
|
||||
#include <fstream>
|
||||
#include <windows.h>
|
||||
|
||||
@@ -9,23 +10,104 @@ namespace fs = std::filesystem;
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
std::vector<AssetItemPtr>& ProjectManager::GetCurrentItems() {
|
||||
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 std::vector<AssetItemPtr> 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) {
|
||||
m_path.push_back(folder);
|
||||
m_selectedIndex = -1;
|
||||
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();
|
||||
m_selectedIndex = -1;
|
||||
ClearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +116,7 @@ void ProjectManager::NavigateToIndex(size_t index) {
|
||||
while (m_path.size() > index + 1) {
|
||||
m_path.pop_back();
|
||||
}
|
||||
m_selectedIndex = -1;
|
||||
ClearSelection();
|
||||
}
|
||||
|
||||
std::string ProjectManager::GetCurrentPath() const {
|
||||
@@ -79,16 +161,7 @@ void ProjectManager::Initialize(const std::string& projectPath) {
|
||||
try {
|
||||
if (!fs::exists(assetsPath)) {
|
||||
fs::create_directories(assetsPath);
|
||||
fs::create_directories(assetsPath / L"Textures");
|
||||
fs::create_directories(assetsPath / L"Models");
|
||||
fs::create_directories(assetsPath / L"Scripts");
|
||||
fs::create_directories(assetsPath / L"Materials");
|
||||
fs::create_directories(assetsPath / L"Scenes");
|
||||
|
||||
std::ofstream((assetsPath / L"Textures" / L"Grass.png").wstring());
|
||||
std::ofstream((assetsPath / L"Textures" / L"Stone.png").wstring());
|
||||
std::ofstream((assetsPath / L"Models" / L"Character.fbx").wstring());
|
||||
std::ofstream((assetsPath / L"Scripts" / L"PlayerController.cs").wstring());
|
||||
}
|
||||
|
||||
m_rootFolder = ScanDirectory(assetsPath.wstring());
|
||||
@@ -97,32 +170,32 @@ void ProjectManager::Initialize(const std::string& projectPath) {
|
||||
|
||||
m_path.clear();
|
||||
m_path.push_back(m_rootFolder);
|
||||
m_selectedIndex = -1;
|
||||
} catch (const std::exception& e) {
|
||||
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 (m_path.empty()) return Utf8ToWstring(m_projectPath);
|
||||
|
||||
std::wstring fullPath = Utf8ToWstring(m_projectPath);
|
||||
for (size_t i = 0; i < m_path.size(); i++) {
|
||||
fullPath += L"/" + Utf8ToWstring(m_path[i]->name);
|
||||
if (AssetItemPtr currentFolder = GetCurrentFolder()) {
|
||||
return Utf8ToWstring(currentFolder->fullPath);
|
||||
}
|
||||
return fullPath;
|
||||
|
||||
return Utf8ToWstring(m_projectPath);
|
||||
}
|
||||
|
||||
void ProjectManager::RefreshCurrentFolder() {
|
||||
if (m_path.empty()) return;
|
||||
|
||||
try {
|
||||
auto newFolder = ScanDirectory(GetCurrentFullPathW());
|
||||
m_path.back()->children = newFolder->children;
|
||||
RebuildTreePreservingPath();
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
@@ -137,34 +210,60 @@ void ProjectManager::CreateFolder(const std::string& name) {
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectManager::DeleteItem(int index) {
|
||||
if (m_path.empty()) return;
|
||||
auto& items = m_path.back()->children;
|
||||
if (index < 0 || index >= (int)items.size()) return;
|
||||
|
||||
bool ProjectManager::DeleteItem(const std::string& fullPath) {
|
||||
if (fullPath.empty() || !m_rootFolder) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
std::wstring fullPath = GetCurrentFullPathW();
|
||||
fs::path itemPath = fs::path(fullPath) / Utf8ToWstring(items[index]->name);
|
||||
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);
|
||||
m_selectedIndex = -1;
|
||||
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 {
|
||||
fs::path sourcePath = Utf8ToWstring(sourceFullPath);
|
||||
fs::path destPath = fs::path(Utf8ToWstring(destFolderFullPath)) / sourcePath.filename();
|
||||
|
||||
if (!fs::exists(sourcePath)) {
|
||||
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 (fs::exists(destPath)) {
|
||||
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;
|
||||
@@ -173,11 +272,27 @@ bool ProjectManager::MoveItem(const std::string& sourceFullPath, const std::stri
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -201,6 +316,61 @@ AssetItemPtr ProjectManager::ScanDirectory(const std::wstring& path) {
|
||||
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);
|
||||
|
||||
@@ -11,9 +11,16 @@ namespace Editor {
|
||||
|
||||
class ProjectManager : public IProjectManager {
|
||||
public:
|
||||
std::vector<AssetItemPtr>& GetCurrentItems() override;
|
||||
int GetSelectedIndex() const override { return m_selectedIndex; }
|
||||
void SetSelectedIndex(int index) override { m_selectedIndex = index; }
|
||||
const std::vector<AssetItemPtr>& GetCurrentItems() const override;
|
||||
AssetItemPtr GetRootFolder() const override { return m_rootFolder; }
|
||||
AssetItemPtr GetCurrentFolder() const override { return m_path.empty() ? nullptr : m_path.back(); }
|
||||
AssetItemPtr GetSelectedItem() const override;
|
||||
const std::string& GetSelectedItemPath() const override { return m_selectedItemPath; }
|
||||
int GetSelectedIndex() const override;
|
||||
void SetSelectedIndex(int index) override;
|
||||
void SetSelectedItem(const AssetItemPtr& item) override;
|
||||
void ClearSelection() override;
|
||||
int FindCurrentItemIndex(const std::string& fullPath) const override;
|
||||
|
||||
void NavigateToFolder(const AssetItemPtr& folder) override;
|
||||
void NavigateBack() override;
|
||||
@@ -28,21 +35,25 @@ public:
|
||||
void RefreshCurrentFolder() override;
|
||||
|
||||
void CreateFolder(const std::string& name) override;
|
||||
void DeleteItem(int index) override;
|
||||
bool DeleteItem(const std::string& fullPath) override;
|
||||
bool MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath) override;
|
||||
|
||||
const std::string& GetProjectPath() const override { return m_projectPath; }
|
||||
|
||||
private:
|
||||
bool BuildPathToFolder(const AssetItemPtr& current, const std::string& fullPath, std::vector<AssetItemPtr>& outPath) const;
|
||||
void RebuildTreePreservingPath();
|
||||
AssetItemPtr FindCurrentItemByPath(const std::string& fullPath) const;
|
||||
void SyncSelection();
|
||||
AssetItemPtr ScanDirectory(const std::wstring& path);
|
||||
AssetItemPtr CreateAssetItem(const std::wstring& path, const std::wstring& nameW, bool isFolder);
|
||||
std::wstring GetCurrentFullPathW() const;
|
||||
|
||||
AssetItemPtr m_rootFolder;
|
||||
std::vector<AssetItemPtr> m_path;
|
||||
int m_selectedIndex = -1;
|
||||
std::string m_selectedItemPath;
|
||||
std::string m_projectPath;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user