完善Project面板功能:搜索框、创建文件夹、右键菜单、中文路径支持

This commit is contained in:
2026-03-12 17:07:37 +08:00
parent a2f3db8718
commit 64dd8339dd
6 changed files with 388 additions and 32 deletions

View File

@@ -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<ConsolePanel>();
m_projectPanel = std::make_unique<ProjectPanel>();
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;
}

View File

@@ -1,6 +1,8 @@
#pragma once
#include <string>
#include <vector>
#include <memory>
namespace UI {
@@ -8,6 +10,9 @@ struct AssetItem {
std::string name;
std::string type;
bool isFolder;
std::vector<std::shared_ptr<AssetItem>> children;
};
using AssetItemPtr = std::shared_ptr<AssetItem>;
}

View File

@@ -1,4 +1,10 @@
#include "ProjectManager.h"
#include <filesystem>
#include <algorithm>
#include <fstream>
#include <windows.h>
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<AssetItemPtr>& ProjectManager::GetCurrentItems() {
if (m_path.empty()) {
static std::vector<AssetItemPtr> 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<AssetItem>();
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<AssetItem>();
folder->name = WstringToUtf8(fs::path(path).filename().wstring());
folder->isFolder = true;
folder->type = "Folder";
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;
}
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;
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;
}
}

View File

@@ -3,6 +3,7 @@
#include "Core/AssetItem.h"
#include <vector>
#include <string>
#include <memory>
namespace UI {
@@ -10,17 +11,35 @@ class ProjectManager {
public:
static ProjectManager& Get();
std::vector<AssetItem>& GetItems() { return m_items; }
std::vector<AssetItemPtr>& 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<AssetItem> 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<AssetItemPtr> m_path;
int m_selectedIndex = -1;
std::string m_projectPath;
};
}

View File

@@ -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);
}
}
}

View File

@@ -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";
};
}