2026-03-26 21:18:33 +08:00
|
|
|
#include "Actions/EditorActions.h"
|
|
|
|
|
#include "Commands/ProjectCommands.h"
|
2026-03-20 17:08:06 +08:00
|
|
|
#include "ProjectPanel.h"
|
2026-03-25 16:25:55 +08:00
|
|
|
#include "Core/IEditorContext.h"
|
|
|
|
|
#include "Core/IProjectManager.h"
|
2026-03-20 17:08:06 +08:00
|
|
|
#include "Core/AssetItem.h"
|
2026-03-26 21:18:33 +08:00
|
|
|
#include "UI/UI.h"
|
2026-03-20 17:08:06 +08:00
|
|
|
#include <imgui.h>
|
|
|
|
|
#include <imgui_internal.h>
|
|
|
|
|
|
2026-03-24 20:02:38 +08:00
|
|
|
namespace XCEngine {
|
|
|
|
|
namespace Editor {
|
2026-03-20 17:08:06 +08:00
|
|
|
|
|
|
|
|
const char* DRAG_DROP_TYPE = "ASSET_ITEM";
|
|
|
|
|
|
|
|
|
|
ProjectPanel::ProjectPanel() : Panel("Project") {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProjectPanel::Initialize(const std::string& projectPath) {
|
2026-03-25 16:25:55 +08:00
|
|
|
m_context->GetProjectManager().Initialize(projectPath);
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:43:06 +08:00
|
|
|
UI::PanelWindowScope panel(m_name.c_str());
|
|
|
|
|
if (!panel.IsOpen()) {
|
|
|
|
|
return;
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-26 16:43:06 +08:00
|
|
|
auto& manager = m_context->GetProjectManager();
|
|
|
|
|
|
2026-03-26 21:18:33 +08:00
|
|
|
UI::PanelToolbarScope toolbar("ProjectToolbar", UI::ProjectPanelToolbarHeight());
|
2026-03-26 16:43:06 +08:00
|
|
|
if (toolbar.IsOpen()) {
|
|
|
|
|
|
|
|
|
|
bool canGoBack = manager.CanNavigateBack();
|
2026-03-26 21:18:33 +08:00
|
|
|
if (Actions::DrawToolbarAction(Actions::MakeNavigateBackAction(canGoBack), UI::ProjectBackButtonSize())) {
|
|
|
|
|
manager.NavigateBack();
|
2026-03-26 16:43:06 +08:00
|
|
|
}
|
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
|
|
|
|
|
size_t pathDepth = manager.GetPathDepth();
|
2026-03-26 21:18:33 +08:00
|
|
|
UI::DrawToolbarBreadcrumbs(
|
|
|
|
|
"Assets",
|
|
|
|
|
pathDepth,
|
|
|
|
|
[&](size_t index) { return manager.GetPathName(index); },
|
|
|
|
|
[&](size_t index) { manager.NavigateToIndex(index); });
|
2026-03-26 16:43:06 +08:00
|
|
|
|
2026-03-26 21:18:33 +08:00
|
|
|
UI::DrawToolbarRowGap();
|
|
|
|
|
UI::ToolbarSearchField("##Search", "Search assets", m_searchBuffer, sizeof(m_searchBuffer));
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
2026-03-26 16:43:06 +08:00
|
|
|
|
|
|
|
|
UI::PanelContentScope content(
|
|
|
|
|
"ProjectContent",
|
|
|
|
|
UI::AssetPanelContentPadding(),
|
|
|
|
|
ImGuiWindowFlags_None,
|
|
|
|
|
true,
|
2026-03-26 21:18:33 +08:00
|
|
|
UI::AssetGridSpacing());
|
2026-03-26 16:43:06 +08:00
|
|
|
if (!content.IsOpen()) {
|
|
|
|
|
return;
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
2026-03-26 16:43:06 +08:00
|
|
|
|
2026-03-26 21:18:33 +08:00
|
|
|
const float tileWidth = UI::AssetTileSize().x;
|
|
|
|
|
const float spacing = UI::AssetGridSpacing().x;
|
|
|
|
|
const float panelWidth = ImGui::GetContentRegionAvail().x;
|
|
|
|
|
int columns = static_cast<int>((panelWidth + spacing) / (tileWidth + spacing));
|
2026-03-20 17:08:06 +08:00
|
|
|
if (columns < 1) columns = 1;
|
|
|
|
|
|
|
|
|
|
auto& items = manager.GetCurrentItems();
|
|
|
|
|
std::string searchStr = m_searchBuffer;
|
2026-03-26 01:26:26 +08:00
|
|
|
int displayedCount = 0;
|
2026-03-20 17:08:06 +08:00
|
|
|
|
|
|
|
|
for (int i = 0; i < (int)items.size(); i++) {
|
|
|
|
|
if (!searchStr.empty()) {
|
|
|
|
|
if (items[i]->name.find(searchStr) == std::string::npos) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 01:26:26 +08:00
|
|
|
if (displayedCount > 0 && displayedCount % columns != 0) {
|
2026-03-20 17:08:06 +08:00
|
|
|
ImGui::SameLine();
|
|
|
|
|
}
|
2026-03-26 01:26:26 +08:00
|
|
|
RenderAssetItem(items[i], i);
|
|
|
|
|
displayedCount++;
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
2026-03-26 16:43:06 +08:00
|
|
|
|
2026-03-26 21:18:33 +08:00
|
|
|
if (displayedCount == 0) {
|
|
|
|
|
UI::DrawEmptyState(
|
|
|
|
|
searchStr.empty() ? "No Assets" : "No Search Results",
|
|
|
|
|
searchStr.empty() ? "Current folder is empty" : "No assets match the current search");
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 17:08:06 +08:00
|
|
|
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered()) {
|
|
|
|
|
manager.SetSelectedIndex(-1);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 21:18:33 +08:00
|
|
|
if (UI::BeginPopup("ItemContextMenu")) {
|
2026-03-20 17:08:06 +08:00
|
|
|
if (m_contextMenuIndex >= 0 && m_contextMenuIndex < (int)items.size()) {
|
|
|
|
|
auto& item = items[m_contextMenuIndex];
|
2026-03-26 21:18:33 +08:00
|
|
|
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);
|
2026-03-20 17:08:06 +08:00
|
|
|
m_contextMenuIndex = -1;
|
2026-03-26 21:18:33 +08:00
|
|
|
});
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
2026-03-26 21:18:33 +08:00
|
|
|
UI::EndPopup();
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(1) && !ImGui::IsAnyItemHovered()) {
|
|
|
|
|
ImGui::OpenPopup("EmptyContextMenu");
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 21:18:33 +08:00
|
|
|
if (UI::BeginPopup("EmptyContextMenu")) {
|
|
|
|
|
Actions::DrawMenuAction(Actions::MakeCreateFolderAction(), [&]() {
|
|
|
|
|
m_createFolderDialog.RequestOpen("NewFolder");
|
|
|
|
|
});
|
|
|
|
|
UI::EndPopup();
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-26 21:18:33 +08:00
|
|
|
m_createFolderDialog.ConsumeOpenRequest("Create Folder");
|
2026-03-20 17:08:06 +08:00
|
|
|
|
2026-03-26 21:18:33 +08:00
|
|
|
if (UI::BeginModalPopup("Create Folder")) {
|
|
|
|
|
ImGui::InputText("Name", m_createFolderDialog.Buffer(), m_createFolderDialog.BufferSize());
|
2026-03-20 17:08:06 +08:00
|
|
|
ImGui::Separator();
|
2026-03-26 21:18:33 +08:00
|
|
|
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();
|
|
|
|
|
}
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
|
|
|
|
ImGui::SameLine();
|
2026-03-26 21:18:33 +08:00
|
|
|
if (Actions::DrawButtonAction(cancelAction, UI::DialogActionButtonSize())) {
|
2026-03-20 17:08:06 +08:00
|
|
|
ImGui::CloseCurrentPopup();
|
|
|
|
|
}
|
2026-03-26 21:18:33 +08:00
|
|
|
UI::EndPopup();
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
|
2026-03-25 16:25:55 +08:00
|
|
|
auto& manager = m_context->GetProjectManager();
|
2026-03-20 17:08:06 +08:00
|
|
|
bool isSelected = (manager.GetSelectedIndex() == index);
|
|
|
|
|
|
|
|
|
|
ImGui::PushID(index);
|
2026-03-26 21:18:33 +08:00
|
|
|
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) {
|
2026-03-20 17:08:06 +08:00
|
|
|
manager.SetSelectedIndex(index);
|
|
|
|
|
}
|
2026-03-26 21:18:33 +08:00
|
|
|
|
2026-03-20 17:08:06 +08:00
|
|
|
bool openContextMenu = false;
|
2026-03-26 21:18:33 +08:00
|
|
|
if (tile.contextRequested) {
|
2026-03-20 17:08:06 +08:00
|
|
|
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;
|
2026-03-26 21:18:33 +08:00
|
|
|
Commands::MoveAssetToFolder(manager, draggedPath, item);
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
|
|
|
|
ImGui::EndDragDropTarget();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!item->fullPath.empty()) {
|
|
|
|
|
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
|
|
|
|
|
ImGui::SetDragDropPayload(DRAG_DROP_TYPE, item->fullPath.c_str(), item->fullPath.length() + 1);
|
|
|
|
|
|
2026-03-26 16:43:06 +08:00
|
|
|
ImVec2 previewMin = ImGui::GetMousePos();
|
2026-03-26 21:18:33 +08:00
|
|
|
const ImVec2 previewSize = UI::AssetDragPreviewSize();
|
|
|
|
|
ImVec2 previewMax = ImVec2(previewMin.x + previewSize.x, previewMin.y + previewSize.y);
|
|
|
|
|
UI::DrawAssetIcon(ImGui::GetForegroundDrawList(), previewMin, previewMax, iconKind);
|
2026-03-20 17:08:06 +08:00
|
|
|
|
|
|
|
|
ImGui::EndDragDropSource();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 21:18:33 +08:00
|
|
|
if (tile.openRequested) {
|
|
|
|
|
Commands::OpenAsset(*m_context, item);
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui::PopID();
|
|
|
|
|
|
|
|
|
|
if (openContextMenu) {
|
|
|
|
|
ImGui::OpenPopup("ItemContextMenu");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-24 20:02:38 +08:00
|
|
|
}
|
2026-03-26 01:26:26 +08:00
|
|
|
}
|