From 64dd8339dd71d467217a9b8b1396517b8f390447 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Thu, 12 Mar 2026 17:07:37 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84Project=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=9A=E6=90=9C=E7=B4=A2=E6=A1=86=E3=80=81?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E6=96=87=E4=BB=B6=E5=A4=B9=E3=80=81=E5=8F=B3?= =?UTF-8?q?=E9=94=AE=E8=8F=9C=E5=8D=95=E3=80=81=E4=B8=AD=E6=96=87=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/Application.cpp | 20 +++ ui/src/Core/AssetItem.h | 5 + ui/src/Managers/ProjectManager.cpp | 207 +++++++++++++++++++++++++++-- ui/src/Managers/ProjectManager.h | 25 +++- ui/src/panels/ProjectPanel.cpp | 152 ++++++++++++++++++--- ui/src/panels/ProjectPanel.h | 11 +- 6 files changed, 388 insertions(+), 32 deletions(-) diff --git a/ui/src/Application.cpp b/ui/src/Application.cpp index 98a1c9d8..274a343b 100644 --- a/ui/src/Application.cpp +++ b/ui/src/Application.cpp @@ -30,6 +30,11 @@ bool Application::Initialize(HWND hwnd) { ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + ImFont* font = io.Fonts->AddFontFromFileTTF("C:/Windows/Fonts/msyh.ttc", 16.0f); + ImFont* defaultFont = io.Fonts->AddFontDefault(); + (void)font; + (void)defaultFont; + unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); @@ -48,6 +53,21 @@ bool Application::Initialize(HWND hwnd) { m_consolePanel = std::make_unique(); m_projectPanel = std::make_unique(); + wchar_t exePath[MAX_PATH]; + GetModuleFileNameW(nullptr, exePath, MAX_PATH); + std::wstring exeDirW(exePath); + size_t pos = exeDirW.find_last_of(L"\\/"); + if (pos != std::wstring::npos) { + exeDirW = exeDirW.substr(0, pos); + } + std::string exeDir; + int len = WideCharToMultiByte(CP_UTF8, 0, exeDirW.c_str(), -1, nullptr, 0, nullptr, nullptr); + if (len > 0) { + exeDir.resize(len - 1); + WideCharToMultiByte(CP_UTF8, 0, exeDirW.c_str(), -1, &exeDir[0], len, nullptr, nullptr); + } + m_projectPanel->Initialize(exeDir); + return true; } diff --git a/ui/src/Core/AssetItem.h b/ui/src/Core/AssetItem.h index bbcf9271..e6ca839f 100644 --- a/ui/src/Core/AssetItem.h +++ b/ui/src/Core/AssetItem.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include namespace UI { @@ -8,6 +10,9 @@ struct AssetItem { std::string name; std::string type; bool isFolder; + std::vector> children; }; +using AssetItemPtr = std::shared_ptr; + } \ No newline at end of file diff --git a/ui/src/Managers/ProjectManager.cpp b/ui/src/Managers/ProjectManager.cpp index f2affc1d..7451bab7 100644 --- a/ui/src/Managers/ProjectManager.cpp +++ b/ui/src/Managers/ProjectManager.cpp @@ -1,4 +1,10 @@ #include "ProjectManager.h" +#include +#include +#include +#include + +namespace fs = std::filesystem; namespace UI { @@ -7,17 +13,196 @@ ProjectManager& ProjectManager::Get() { return instance; } -void ProjectManager::CreateDemoAssets() { - m_items = { - {"Cube", "Prefab", false}, - {"Sphere", "Prefab", false}, - {"Player", "Prefab", false}, - {"MainScript", "Script", false}, - {"DefaultMat", "Material", false}, - {"Scene1", "Scene", false}, - {"Textures", "Folder", true}, - {"Models", "Folder", true}, - }; +std::vector& ProjectManager::GetCurrentItems() { + if (m_path.empty()) { + static std::vector empty; + return empty; + } + return m_path.back()->children; +} + +void ProjectManager::NavigateToFolder(const AssetItemPtr& folder) { + m_path.push_back(folder); + m_selectedIndex = -1; +} + +void ProjectManager::NavigateBack() { + if (m_path.size() > 1) { + m_path.pop_back(); + m_selectedIndex = -1; + } +} + +std::string ProjectManager::GetCurrentPath() const { + if (m_path.empty()) return ""; + std::string result; + for (size_t i = 0; i < m_path.size(); i++) { + if (i > 0) result += "/"; + result += m_path[i]->name; + } + return result; +} + +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"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()); + std::ofstream((assetsPath / L"Scenes" / L"Main.unity").wstring()); + } + + m_rootFolder = ScanDirectory(assetsPath.wstring()); + m_rootFolder->name = "Assets"; + + m_path.clear(); + m_path.push_back(m_rootFolder); + m_selectedIndex = -1; + } catch (const std::exception& e) { + m_rootFolder = std::make_shared(); + m_rootFolder->name = "Assets"; + m_rootFolder->isFolder = true; + m_rootFolder->type = "Folder"; + m_path.push_back(m_rootFolder); + } +} + +std::wstring ProjectManager::GetCurrentFullPathW() const { + std::wstring fullPath = Utf8ToWstring(m_projectPath); + for (size_t i = 0; i < m_path.size(); i++) { + fullPath += L"/" + Utf8ToWstring(m_path[i]->name); + } + return fullPath; +} + +void ProjectManager::RefreshCurrentFolder() { + if (m_path.empty()) return; + + try { + auto newFolder = ScanDirectory(GetCurrentFullPathW()); + m_path.back()->children = newFolder->children; + } 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 (...) { + } +} + +void ProjectManager::DeleteItem(int index) { + if (m_path.empty()) return; + auto& items = m_path.back()->children; + if (index < 0 || index >= (int)items.size()) return; + + try { + std::wstring fullPath = GetCurrentFullPathW(); + fs::path itemPath = fs::path(fullPath) / Utf8ToWstring(items[index]->name); + fs::remove_all(itemPath); + m_selectedIndex = -1; + RefreshCurrentFolder(); + } catch (...) { + } +} + +AssetItemPtr ProjectManager::ScanDirectory(const std::wstring& path) { + auto folder = std::make_shared(); + folder->name = WstringToUtf8(fs::path(path).filename().wstring()); + folder->isFolder = true; + folder->type = "Folder"; + + if (!fs::exists(path)) return folder; + + std::vector 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; +} + +AssetItemPtr ProjectManager::CreateAssetItem(const std::wstring& path, const std::wstring& nameW, bool isFolder) { + auto item = std::make_shared(); + item->name = WstringToUtf8(nameW); + item->isFolder = isFolder; + + 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".unity" || ext == L".scene") { + item->type = "Scene"; + } else if (ext == L".prefab") { + item->type = "Prefab"; + } else { + item->type = "File"; + } + } + + return item; } } \ No newline at end of file diff --git a/ui/src/Managers/ProjectManager.h b/ui/src/Managers/ProjectManager.h index b7e664f2..f2b84bcb 100644 --- a/ui/src/Managers/ProjectManager.h +++ b/ui/src/Managers/ProjectManager.h @@ -3,6 +3,7 @@ #include "Core/AssetItem.h" #include #include +#include namespace UI { @@ -10,17 +11,35 @@ class ProjectManager { public: static ProjectManager& Get(); - std::vector& GetItems() { return m_items; } + std::vector& GetCurrentItems(); int GetSelectedIndex() const { return m_selectedIndex; } void SetSelectedIndex(int index) { m_selectedIndex = index; } - void CreateDemoAssets(); + void NavigateToFolder(const AssetItemPtr& folder); + void NavigateBack(); + bool CanNavigateBack() const { return m_path.size() > 1; } + + std::string GetCurrentPath() const; + + void Initialize(const std::string& projectPath); + void RefreshCurrentFolder(); + + void CreateFolder(const std::string& name); + void DeleteItem(int index); + + const std::string& GetProjectPath() const { return m_projectPath; } private: ProjectManager() = default; - std::vector m_items; + 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 m_path; int m_selectedIndex = -1; + std::string m_projectPath; }; } \ No newline at end of file diff --git a/ui/src/panels/ProjectPanel.cpp b/ui/src/panels/ProjectPanel.cpp index fd65c791..67a0b40f 100644 --- a/ui/src/panels/ProjectPanel.cpp +++ b/ui/src/panels/ProjectPanel.cpp @@ -7,50 +7,143 @@ namespace UI { ProjectPanel::ProjectPanel() : Panel("Project") { - ProjectManager::Get().CreateDemoAssets(); +} + +void ProjectPanel::Initialize(const std::string& projectPath) { + ProjectManager::Get().Initialize(projectPath); } void ProjectPanel::Render() { ImGui::Begin(m_name.c_str(), &m_isOpen, ImGuiWindowFlags_None); - ImGui::Text("Assets/"); + auto& manager = ProjectManager::Get(); + + if (manager.CanNavigateBack()) { + if (ImGui::Button("<")) { + manager.NavigateBack(); + } + ImGui::SameLine(); + } + + ImGui::Text("%s", manager.GetCurrentPath().c_str()); + ImGui::SameLine(); - ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "> Project"); + float refreshBtnWidth = 60.0f; + ImGui::SetCursorPosX(ImGui::GetWindowWidth() - refreshBtnWidth - 80.0f); + if (ImGui::Button("Refresh")) { + manager.RefreshCurrentFolder(); + } + + ImGui::SameLine(); + if (ImGui::Button("+")) { + m_showCreateFolderPopup = true; + strcpy_s(m_newFolderName, "NewFolder"); + } + + ImGui::Separator(); + + ImGui::PushItemWidth(-1); + ImGui::InputTextWithHint("##Search", "Search...", m_searchBuffer, sizeof(m_searchBuffer)); + ImGui::PopItemWidth(); ImGui::Separator(); ImGui::BeginChild("AssetList", ImVec2(0, 0), false); float buttonWidth = 80.0f; - float buttonHeight = 90.0f; float padding = 10.0f; float panelWidth = ImGui::GetContentRegionAvail().x; int columns = (int)(panelWidth / (buttonWidth + padding)); if (columns < 1) columns = 1; - auto& items = ProjectManager::Get().GetItems(); + auto& items = manager.GetCurrentItems(); + std::string searchStr = m_searchBuffer; for (int i = 0; i < items.size(); i++) { + if (!searchStr.empty()) { + if (items[i]->name.find(searchStr) == std::string::npos) { + continue; + } + } + if (i > 0 && i % columns != 0) { ImGui::SameLine(); } RenderAssetItem(items[i], i); } + if (ImGui::BeginPopupContextWindow("ProjectContextMenu")) { + if (ImGui::MenuItem("Create Folder")) { + m_showCreateFolderPopup = true; + strcpy_s(m_newFolderName, "NewFolder"); + } + ImGui::Separator(); + if (ImGui::MenuItem("Refresh")) { + manager.RefreshCurrentFolder(); + } + ImGui::EndPopup(); + } + ImGui::EndChild(); + + if (m_showCreateFolderPopup) { + ImGui::OpenPopup("Create Folder"); + } + + if (ImGui::BeginPopupModal("Create Folder", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::InputText("Name", m_newFolderName, sizeof(m_newFolderName)); + ImGui::Separator(); + if (ImGui::Button("Create", ImVec2(80, 0))) { + CreateNewFolder(m_newFolderName); + m_showCreateFolderPopup = false; + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(80, 0))) { + m_showCreateFolderPopup = false; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + ImGui::End(); } -void ProjectPanel::RenderAssetItem(const AssetItem& item, int index) { - ImGui::PushID(index); +void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) { + auto& manager = ProjectManager::Get(); + bool isSelected = (manager.GetSelectedIndex() == index); + + std::string popupId = "ItemMenu" + std::to_string(index); - bool isSelected = (ProjectManager::Get().GetSelectedIndex() == index); if (isSelected) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.40f, 0.40f, 0.40f, 0.50f)); } ImVec2 buttonSize(80.0f, 90.0f); - if (ImGui::Button("##Asset", buttonSize)) { - ProjectManager::Get().SetSelectedIndex(index); + if (ImGui::Button(("##Asset" + std::to_string(index)).c_str(), buttonSize)) { + manager.SetSelectedIndex(index); + } + + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + manager.SetSelectedIndex(index); + ImGui::OpenPopup(popupId.c_str()); + } + + if (item->isFolder && ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { + manager.NavigateToFolder(item); + } + + if (ImGui::BeginPopup(popupId.c_str())) { + if (item->isFolder) { + if (ImGui::MenuItem("Open")) { + manager.NavigateToFolder(item); + } + } + ImGui::Separator(); + if (ImGui::MenuItem("Delete")) { + manager.SetSelectedIndex(index); + DeleteSelectedItem(); + } + ImGui::EndPopup(); } if (isSelected) { @@ -58,23 +151,48 @@ void ProjectPanel::RenderAssetItem(const AssetItem& item, int index) { } ImVec2 min = ImGui::GetItemRectMin(); - ImVec2 max = ImGui::GetItemRectMax(); ImDrawList* drawList = ImGui::GetWindowDrawList(); - ImU32 iconColor = item.isFolder ? IM_COL32(200, 180, 100, 255) : IM_COL32(100, 150, 200, 255); + ImU32 iconColor; + if (item->isFolder) { + iconColor = IM_COL32(200, 180, 100, 255); + } else if (item->type == "Texture") { + iconColor = IM_COL32(150, 200, 150, 255); + } else if (item->type == "Model") { + iconColor = IM_COL32(150, 150, 200, 255); + } else if (item->type == "Script") { + iconColor = IM_COL32(200, 150, 150, 255); + } else if (item->type == "Scene") { + iconColor = IM_COL32(200, 200, 150, 255); + } else { + iconColor = IM_COL32(100, 150, 200, 255); + } float iconSize = 40.0f; ImVec2 iconMin(min.x + (80.0f - iconSize) * 0.5f, min.y + 10.0f); ImVec2 iconMax(iconMin.x + iconSize, iconMin.y + iconSize); drawList->AddRectFilled(iconMin, iconMax, iconColor, 4.0f); - ImVec2 textPos(min.x + 5.0f, min.y + 60.0f); ImVec4 textColor = isSelected ? ImVec4(1.0f, 1.0f, 1.0f, 1.0f) : ImVec4(0.8f, 0.8f, 0.8f, 1.0f); - ImVec2 textSize = ImGui::CalcTextSize(item.name.c_str()); - float textOffset = (80.0f - textSize.x) * 0.5f; - drawList->AddText(ImVec2(min.x + textOffset, textPos.y), ImGui::GetColorU32(textColor), item.name.c_str()); + ImVec2 textSize = ImGui::CalcTextSize(item->name.c_str()); + float textOffset = std::max(0.0f, (80.0f - textSize.x) * 0.5f); - ImGui::PopID(); + ImGui::PushClipRect(min, ImVec2(min.x + 80.0f, min.y + 90.0f), true); + drawList->AddText(ImVec2(min.x + textOffset, min.y + 60.0f), ImGui::GetColorU32(textColor), item->name.c_str()); + ImGui::PopClipRect(); +} + +void ProjectPanel::CreateNewFolder(const std::string& name) { + auto& manager = ProjectManager::Get(); + manager.CreateFolder(name); +} + +void ProjectPanel::DeleteSelectedItem() { + auto& manager = ProjectManager::Get(); + int selectedIndex = manager.GetSelectedIndex(); + if (selectedIndex >= 0) { + manager.DeleteItem(selectedIndex); + } } } \ No newline at end of file diff --git a/ui/src/panels/ProjectPanel.h b/ui/src/panels/ProjectPanel.h index cddc1607..f13501d0 100644 --- a/ui/src/panels/ProjectPanel.h +++ b/ui/src/panels/ProjectPanel.h @@ -1,6 +1,7 @@ #pragma once #include "Panel.h" +#include "Core/AssetItem.h" namespace UI { @@ -8,9 +9,17 @@ class ProjectPanel : public Panel { public: ProjectPanel(); void Render() override; + void Initialize(const std::string& projectPath); private: - void RenderAssetItem(const struct AssetItem& item, int index); + void RenderAssetItem(const AssetItemPtr& item, int index); + void RenderContextMenu(); + void CreateNewFolder(const std::string& name); + void DeleteSelectedItem(); + + char m_searchBuffer[256] = ""; + bool m_showCreateFolderPopup = false; + char m_newFolderName[256] = "NewFolder"; }; } \ No newline at end of file