#include "Actions/EditorActions.h" #include "Commands/ProjectCommands.h" #include "ProjectPanel.h" #include "Core/IEditorContext.h" #include "Core/IProjectManager.h" #include "Core/AssetItem.h" #include "UI/UI.h" #include #include namespace XCEngine { namespace Editor { const char* DRAG_DROP_TYPE = "ASSET_ITEM"; 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", UI::ProjectPanelToolbarHeight()); if (toolbar.IsOpen()) { bool canGoBack = manager.CanNavigateBack(); if (Actions::DrawToolbarAction(Actions::MakeNavigateBackAction(canGoBack), UI::ProjectBackButtonSize())) { manager.NavigateBack(); } ImGui::SameLine(); size_t pathDepth = manager.GetPathDepth(); UI::DrawToolbarBreadcrumbs( "Assets", pathDepth, [&](size_t index) { return manager.GetPathName(index); }, [&](size_t index) { manager.NavigateToIndex(index); }); UI::DrawToolbarRowGap(); UI::ToolbarSearchField("##Search", "Search assets", m_searchBuffer, sizeof(m_searchBuffer)); } UI::PanelContentScope content( "ProjectContent", UI::AssetPanelContentPadding(), ImGuiWindowFlags_None, true, UI::AssetGridSpacing()); if (!content.IsOpen()) { return; } const float tileWidth = UI::AssetTileSize().x; const float spacing = UI::AssetGridSpacing().x; const float panelWidth = ImGui::GetContentRegionAvail().x; int columns = static_cast((panelWidth + spacing) / (tileWidth + spacing)); 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 (displayedCount == 0) { UI::DrawEmptyState( searchStr.empty() ? "No Assets" : "No Search Results", searchStr.empty() ? "Current folder is empty" : "No assets match the current search"); } if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered()) { manager.SetSelectedIndex(-1); } if (UI::BeginPopup("ItemContextMenu")) { if (m_contextMenuIndex >= 0 && m_contextMenuIndex < (int)items.size()) { auto& item = items[m_contextMenuIndex]; const bool canOpen = item->isFolder || item->type == "Scene"; Actions::DrawMenuAction(Actions::MakeOpenAssetAction(canOpen), [&]() { Commands::OpenAsset(*m_context, item); }); Actions::DrawMenuSeparator(); Actions::DrawMenuAction(Actions::MakeDeleteAssetAction(), [&]() { Commands::DeleteAsset(manager, m_contextMenuIndex); m_contextMenuIndex = -1; }); } UI::EndPopup(); } if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(1) && !ImGui::IsAnyItemHovered()) { ImGui::OpenPopup("EmptyContextMenu"); } if (UI::BeginPopup("EmptyContextMenu")) { Actions::DrawMenuAction(Actions::MakeCreateFolderAction(), [&]() { m_createFolderDialog.RequestOpen("NewFolder"); }); UI::EndPopup(); } m_createFolderDialog.ConsumeOpenRequest("Create Folder"); if (UI::BeginModalPopup("Create Folder")) { ImGui::InputText("Name", m_createFolderDialog.Buffer(), m_createFolderDialog.BufferSize()); ImGui::Separator(); const Actions::ActionBinding createAction = Actions::MakeConfirmCreateAction(!m_createFolderDialog.Empty()); const Actions::ActionBinding cancelAction = Actions::MakeCancelAction(); if (Actions::DrawButtonAction(createAction, UI::DialogActionButtonSize())) { if (Commands::CreateFolder(manager, m_createFolderDialog.Buffer())) { ImGui::CloseCurrentPopup(); } } ImGui::SameLine(); if (Actions::DrawButtonAction(cancelAction, UI::DialogActionButtonSize())) { ImGui::CloseCurrentPopup(); } UI::EndPopup(); } } void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) { auto& manager = m_context->GetProjectManager(); bool isSelected = (manager.GetSelectedIndex() == index); ImGui::PushID(index); const bool isDraggingThisItem = !m_draggingPath.empty() && item->fullPath == m_draggingPath; const UI::AssetIconKind iconKind = item->isFolder ? UI::AssetIconKind::Folder : UI::AssetIconKind::File; const UI::AssetTileResult tile = UI::DrawAssetTile( item->name.c_str(), isSelected, isDraggingThisItem, [&](ImDrawList* drawList, const ImVec2& iconMin, const ImVec2& iconMax) { UI::DrawAssetIcon(drawList, iconMin, iconMax, iconKind); }); if (tile.clicked) { manager.SetSelectedIndex(index); } bool openContextMenu = false; if (tile.contextRequested) { manager.SetSelectedIndex(index); m_contextMenuIndex = index; openContextMenu = true; } if (item->isFolder) { if (ImGui::BeginDragDropTarget()) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(DRAG_DROP_TYPE)) { const char* draggedPath = (const char*)payload->Data; Commands::MoveAssetToFolder(manager, draggedPath, item); } 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(); const ImVec2 previewSize = UI::AssetDragPreviewSize(); ImVec2 previewMax = ImVec2(previewMin.x + previewSize.x, previewMin.y + previewSize.y); UI::DrawAssetIcon(ImGui::GetForegroundDrawList(), previewMin, previewMax, iconKind); ImGui::EndDragDropSource(); } } if (tile.openRequested) { Commands::OpenAsset(*m_context, item); } ImGui::PopID(); if (openContextMenu) { ImGui::OpenPopup("ItemContextMenu"); } } } }