#include "ProjectPanel.h" #include "Core/IEditorContext.h" #include "Core/IProjectManager.h" #include "Core/ISceneManager.h" #include "Core/IUndoManager.h" #include "Core/ISelectionManager.h" #include "Core/AssetItem.h" #include "UI/Core.h" #include "UI/PanelChrome.h" #include "Utils/SceneEditorUtils.h" #include #include namespace XCEngine { namespace Editor { const char* DRAG_DROP_TYPE = "ASSET_ITEM"; namespace { constexpr float kProjectToolbarHeight = 60.0f; void DrawFolderIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, ImU32 fillColor, ImU32 lineColor) { const float width = max.x - min.x; const float height = max.y - min.y; const ImVec2 tabMax(min.x + width * 0.45f, min.y + height * 0.35f); drawList->AddRectFilled(ImVec2(min.x, min.y + height * 0.14f), tabMax, fillColor, 2.0f); drawList->AddRectFilled(ImVec2(min.x, min.y + height * 0.28f), max, fillColor, 2.0f); drawList->AddRect(ImVec2(min.x, min.y + height * 0.14f), tabMax, lineColor, 2.0f); drawList->AddRect(ImVec2(min.x, min.y + height * 0.28f), max, lineColor, 2.0f); } void DrawFileIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, ImU32 fillColor, ImU32 lineColor) { const ImVec2 foldA(max.x - 8.0f, min.y); const ImVec2 foldB(max.x, min.y + 8.0f); drawList->AddRectFilled(min, max, fillColor, 2.0f); drawList->AddRect(min, max, lineColor, 2.0f); drawList->AddTriangleFilled(foldA, ImVec2(max.x, min.y), foldB, IM_COL32(235, 235, 235, 40)); drawList->AddLine(foldA, foldB, lineColor); } } // namespace ProjectPanel::ProjectPanel() : Panel("Project") { } void ProjectPanel::Initialize(const std::string& projectPath) { m_context->GetProjectManager().Initialize(projectPath); } void ProjectPanel::Render() { const ImGuiPayload* payload = ImGui::GetDragDropPayload(); if (payload && payload->IsDataType(DRAG_DROP_TYPE)) { m_draggingPath = (const char*)payload->Data; } else if (!ImGui::IsMouseDown(0)) { m_draggingPath.clear(); } UI::PanelWindowScope panel(m_name.c_str()); if (!panel.IsOpen()) { return; } auto& manager = m_context->GetProjectManager(); UI::PanelToolbarScope toolbar("ProjectToolbar", kProjectToolbarHeight); if (toolbar.IsOpen()) { bool canGoBack = manager.CanNavigateBack(); ImGui::BeginDisabled(!canGoBack); if (UI::ToolbarButton("<", false, ImVec2(28.0f, 0.0f))) { if (canGoBack) { manager.NavigateBack(); } } ImGui::EndDisabled(); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); size_t pathDepth = manager.GetPathDepth(); if (pathDepth == 0) { ImGui::TextUnformatted("Assets"); } else { for (size_t i = 0; i < pathDepth; i++) { if (i > 0) { ImGui::SameLine(); ImGui::TextDisabled("/"); ImGui::SameLine(); } std::string name = manager.GetPathName(i); if (i < pathDepth - 1) { if (ImGui::Button(name.c_str())) { manager.NavigateToIndex(i); } } else { ImGui::Text("%s", name.c_str()); } } } ImGui::PopStyleColor(2); ImGui::Dummy(ImVec2(0.0f, 2.0f)); ImGui::SetNextItemWidth(-1.0f); ImGui::InputTextWithHint("##Search", "Search assets", m_searchBuffer, sizeof(m_searchBuffer)); } UI::PanelContentScope content( "ProjectContent", UI::AssetPanelContentPadding(), ImGuiWindowFlags_None, true, ImVec2(8.0f, 10.0f)); if (!content.IsOpen()) { return; } float buttonWidth = 104.0f; float padding = 8.0f; float panelWidth = ImGui::GetContentRegionAvail().x; int columns = (int)(panelWidth / (buttonWidth + padding)); if (columns < 1) columns = 1; auto& items = manager.GetCurrentItems(); std::string searchStr = m_searchBuffer; int displayedCount = 0; for (int i = 0; i < (int)items.size(); i++) { if (!searchStr.empty()) { if (items[i]->name.find(searchStr) == std::string::npos) { continue; } } if (displayedCount > 0 && displayedCount % columns != 0) { ImGui::SameLine(); } RenderAssetItem(items[i], i); displayedCount++; } if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered()) { manager.SetSelectedIndex(-1); } if (ImGui::BeginPopup("ItemContextMenu")) { if (m_contextMenuIndex >= 0 && m_contextMenuIndex < (int)items.size()) { auto& item = items[m_contextMenuIndex]; if (item->isFolder || item->type == "Scene") { if (ImGui::MenuItem("Open")) { if (item->isFolder) { manager.NavigateToFolder(item); } else if (SceneEditorUtils::ConfirmSceneSwitch(*m_context)) { if (m_context->GetSceneManager().LoadScene(item->fullPath)) { m_context->GetSelectionManager().ClearSelection(); m_context->GetUndoManager().ClearHistory(); } } } ImGui::Separator(); } if (ImGui::MenuItem("Delete")) { manager.DeleteItem(m_contextMenuIndex); m_contextMenuIndex = -1; } } ImGui::EndPopup(); } if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(1) && !ImGui::IsAnyItemHovered()) { ImGui::OpenPopup("EmptyContextMenu"); } if (ImGui::BeginPopup("EmptyContextMenu")) { if (ImGui::MenuItem("Create Folder")) { m_showCreateFolderPopup = true; strcpy_s(m_newFolderName, "NewFolder"); } ImGui::EndPopup(); } if (m_showCreateFolderPopup) { ImGui::OpenPopup("Create Folder"); m_showCreateFolderPopup = false; } 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); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("Cancel", ImVec2(80, 0))) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } } void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) { auto& manager = m_context->GetProjectManager(); bool isSelected = (manager.GetSelectedIndex() == index); ImGui::PushID(index); ImVec2 buttonSize(104.0f, 82.0f); ImGui::InvisibleButton("##AssetBtn", buttonSize); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { manager.SetSelectedIndex(index); } bool doubleClicked = false; if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { doubleClicked = true; } bool openContextMenu = false; if (ImGui::IsItemClicked(1)) { manager.SetSelectedIndex(index); m_contextMenuIndex = index; openContextMenu = true; } ImVec2 min = ImGui::GetItemRectMin(); ImVec2 max = ImVec2(min.x + buttonSize.x, min.y + buttonSize.y); ImDrawList* drawList = ImGui::GetWindowDrawList(); const bool hovered = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); if (hovered || isSelected) { drawList->AddRectFilled(min, max, isSelected ? IM_COL32(156, 156, 156, 30) : IM_COL32(255, 255, 255, 10), 2.0f); } if (isSelected) { drawList->AddRect(min, max, IM_COL32(188, 188, 188, 110), 2.0f); } if (!m_draggingPath.empty() && item->fullPath == m_draggingPath) { drawList->AddRectFilled(min, max, IM_COL32(0, 0, 0, 60), 0.0f); } ImU32 iconFillColor = item->isFolder ? IM_COL32(118, 118, 118, 255) : IM_COL32(104, 104, 104, 255); ImU32 iconLineColor = item->isFolder ? IM_COL32(184, 184, 184, 220) : IM_COL32(166, 166, 166, 220); const ImVec2 iconMin(min.x + 14.0f, min.y + 12.0f); const ImVec2 iconMax(iconMin.x + 28.0f, iconMin.y + 22.0f); if (item->isFolder) { DrawFolderIcon(drawList, iconMin, iconMax, iconFillColor, iconLineColor); } else { DrawFileIcon(drawList, iconMin, iconMax, iconFillColor, iconLineColor); } ImVec4 textColor = isSelected ? ImVec4(0.93f, 0.93f, 0.93f, 1.0f) : ImVec4(0.76f, 0.76f, 0.76f, 1.0f); ImVec2 textSize = ImGui::CalcTextSize(item->name.c_str()); float textY = max.y - textSize.y - 10.0f; ImGui::PushClipRect(ImVec2(min.x + 6.0f, min.y), ImVec2(max.x - 6.0f, max.y), true); drawList->AddText(ImVec2(min.x + 6.0f, textY), ImGui::GetColorU32(textColor), item->name.c_str()); ImGui::PopClipRect(); if (item->isFolder) { if (ImGui::BeginDragDropTarget()) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(DRAG_DROP_TYPE)) { const char* draggedPath = (const char*)payload->Data; std::string sourcePath(draggedPath); manager.MoveItem(sourcePath, item->fullPath); } ImGui::EndDragDropTarget(); } } if (!item->fullPath.empty()) { if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { ImGui::SetDragDropPayload(DRAG_DROP_TYPE, item->fullPath.c_str(), item->fullPath.length() + 1); ImVec2 previewMin = ImGui::GetMousePos(); ImVec2 previewMax = ImVec2(previewMin.x + 24.0f, previewMin.y + 20.0f); if (item->isFolder) { DrawFolderIcon(ImGui::GetForegroundDrawList(), previewMin, previewMax, iconFillColor, iconLineColor); } else { DrawFileIcon(ImGui::GetForegroundDrawList(), previewMin, previewMax, iconFillColor, iconLineColor); } ImGui::EndDragDropSource(); } } if (doubleClicked) { if (item->isFolder) { manager.NavigateToFolder(item); } else if (item->type == "Scene") { if (SceneEditorUtils::ConfirmSceneSwitch(*m_context)) { if (m_context->GetSceneManager().LoadScene(item->fullPath)) { m_context->GetSelectionManager().ClearSelection(); m_context->GetUndoManager().ClearHistory(); } } } } ImGui::PopID(); if (openContextMenu) { ImGui::OpenPopup("ItemContextMenu"); } } void ProjectPanel::CreateNewFolder(const std::string& name) { auto& manager = m_context->GetProjectManager(); manager.CreateFolder(name); } bool ProjectPanel::HandleDrop(const AssetItemPtr& targetFolder) { return false; } } }