2026-03-26 22:10:43 +08:00
|
|
|
#include "Actions/ActionRouting.h"
|
2026-03-26 23:52:05 +08:00
|
|
|
#include "Actions/ProjectActionRouter.h"
|
2026-03-26 21:18:33 +08:00
|
|
|
#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-27 21:55:14 +08:00
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cctype>
|
2026-03-20 17:08:06 +08:00
|
|
|
#include <imgui.h>
|
|
|
|
|
|
2026-03-24 20:02:38 +08:00
|
|
|
namespace XCEngine {
|
|
|
|
|
namespace Editor {
|
2026-03-20 17:08:06 +08:00
|
|
|
|
2026-03-27 23:21:43 +08:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
void DrawProjectFolderTreePrefix(const UI::TreeNodePrefixContext& context) {
|
|
|
|
|
if (!context.drawList) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 15:07:19 +08:00
|
|
|
const float width = context.max.x - context.min.x;
|
|
|
|
|
const float height = context.max.y - context.min.y;
|
|
|
|
|
const float iconExtent = UI::NavigationTreeIconSize();
|
|
|
|
|
const float minX = context.min.x + (width - iconExtent) * 0.5f;
|
|
|
|
|
const float minY = context.min.y + (height - iconExtent) * 0.5f;
|
2026-03-27 23:21:43 +08:00
|
|
|
UI::DrawAssetIcon(
|
|
|
|
|
context.drawList,
|
|
|
|
|
ImVec2(minX, minY),
|
2026-03-28 15:07:19 +08:00
|
|
|
ImVec2(minX + iconExtent, minY + iconExtent),
|
2026-03-27 23:21:43 +08:00
|
|
|
UI::AssetIconKind::Folder);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
2026-03-20 17:08:06 +08:00
|
|
|
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() {
|
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 22:10:43 +08:00
|
|
|
|
|
|
|
|
Actions::ObserveFocusedActionRoute(*m_context, EditorActionRoute::Project);
|
2026-03-27 21:55:14 +08:00
|
|
|
|
2026-03-26 16:43:06 +08:00
|
|
|
auto& manager = m_context->GetProjectManager();
|
2026-03-27 21:55:14 +08:00
|
|
|
RenderToolbar();
|
|
|
|
|
|
2026-03-28 15:07:19 +08:00
|
|
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ProjectBrowserSurfaceColor());
|
2026-03-27 21:55:14 +08:00
|
|
|
UI::PanelContentScope content("ProjectContent", ImVec2(0.0f, 0.0f));
|
|
|
|
|
if (!content.IsOpen()) {
|
2026-03-28 15:07:19 +08:00
|
|
|
ImGui::PopStyleColor();
|
2026-03-27 21:55:14 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float totalHeight = ImGui::GetContentRegionAvail().y;
|
|
|
|
|
const float splitterWidth = UI::PanelSplitterHitThickness();
|
|
|
|
|
const float availableWidth = ImGui::GetContentRegionAvail().x;
|
|
|
|
|
const float clampedNavigationWidth = std::clamp(
|
|
|
|
|
m_navigationWidth,
|
|
|
|
|
UI::ProjectNavigationMinWidth(),
|
2026-03-28 15:07:19 +08:00
|
|
|
(std::max)(UI::ProjectNavigationMinWidth(), availableWidth - UI::ProjectBrowserMinWidth() - splitterWidth));
|
2026-03-27 21:55:14 +08:00
|
|
|
m_navigationWidth = clampedNavigationWidth;
|
|
|
|
|
|
|
|
|
|
RenderFolderTreePane(manager);
|
|
|
|
|
ImGui::SameLine(0.0f, 0.0f);
|
|
|
|
|
const UI::SplitterResult splitter = UI::DrawSplitter("##ProjectPaneSplitter", UI::SplitterAxis::Vertical, totalHeight);
|
|
|
|
|
if (splitter.active) {
|
|
|
|
|
m_navigationWidth += splitter.delta;
|
|
|
|
|
}
|
|
|
|
|
ImGui::SameLine(0.0f, 0.0f);
|
|
|
|
|
RenderBrowserPane(manager);
|
|
|
|
|
|
|
|
|
|
Actions::DrawProjectCreateFolderDialog(*m_context, m_createFolderDialog);
|
2026-03-28 15:07:19 +08:00
|
|
|
ImGui::PopStyleColor();
|
2026-03-27 21:55:14 +08:00
|
|
|
}
|
2026-03-26 16:43:06 +08:00
|
|
|
|
2026-03-27 21:55:14 +08:00
|
|
|
void ProjectPanel::RenderToolbar() {
|
2026-03-28 15:07:19 +08:00
|
|
|
UI::PanelToolbarScope toolbar(
|
|
|
|
|
"ProjectToolbar",
|
|
|
|
|
UI::ProjectPanelToolbarHeight(),
|
|
|
|
|
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse,
|
|
|
|
|
true,
|
|
|
|
|
UI::ToolbarPadding(),
|
|
|
|
|
UI::ToolbarItemSpacing(),
|
|
|
|
|
UI::ProjectPanelToolbarBackgroundColor());
|
2026-03-27 21:55:14 +08:00
|
|
|
if (!toolbar.IsOpen()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-03-26 16:43:06 +08:00
|
|
|
|
2026-03-27 21:55:14 +08:00
|
|
|
if (ImGui::BeginTable("##ProjectToolbarLayout", 2, ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_SizingStretchProp)) {
|
|
|
|
|
ImGui::TableSetupColumn("##Spacer", ImGuiTableColumnFlags_WidthStretch);
|
|
|
|
|
ImGui::TableSetupColumn("##Search", ImGuiTableColumnFlags_WidthFixed, 220.0f);
|
|
|
|
|
|
|
|
|
|
ImGui::TableNextRow();
|
2026-03-26 16:43:06 +08:00
|
|
|
|
2026-03-27 21:55:14 +08:00
|
|
|
ImGui::TableNextColumn();
|
|
|
|
|
|
|
|
|
|
ImGui::TableNextColumn();
|
2026-03-26 21:18:33 +08:00
|
|
|
UI::ToolbarSearchField("##Search", "Search assets", m_searchBuffer, sizeof(m_searchBuffer));
|
2026-03-27 21:55:14 +08:00
|
|
|
|
|
|
|
|
ImGui::EndTable();
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
2026-03-27 21:55:14 +08:00
|
|
|
}
|
2026-03-26 16:43:06 +08:00
|
|
|
|
2026-03-27 21:55:14 +08:00
|
|
|
void ProjectPanel::RenderFolderTreePane(IProjectManager& manager) {
|
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ProjectNavigationPaneBackgroundColor());
|
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, UI::ProjectNavigationPanePadding());
|
|
|
|
|
const bool open = ImGui::BeginChild("ProjectFolderTree", ImVec2(m_navigationWidth, 0.0f), false);
|
|
|
|
|
ImGui::PopStyleVar();
|
|
|
|
|
ImGui::PopStyleColor();
|
|
|
|
|
if (!open) {
|
|
|
|
|
ImGui::EndChild();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const AssetItemPtr rootFolder = manager.GetRootFolder();
|
|
|
|
|
const AssetItemPtr currentFolder = manager.GetCurrentFolder();
|
|
|
|
|
const std::string currentFolderPath = currentFolder ? currentFolder->fullPath : std::string();
|
2026-03-28 15:07:19 +08:00
|
|
|
UI::ResetTreeLayout();
|
2026-03-27 21:55:14 +08:00
|
|
|
if (rootFolder) {
|
|
|
|
|
RenderFolderTreeNode(manager, rootFolder, currentFolderPath);
|
|
|
|
|
} else {
|
|
|
|
|
UI::DrawEmptyState("No Assets Folder");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui::EndChild();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProjectPanel::RenderFolderTreeNode(
|
|
|
|
|
IProjectManager& manager,
|
|
|
|
|
const AssetItemPtr& folder,
|
|
|
|
|
const std::string& currentFolderPath) {
|
|
|
|
|
if (!folder || !folder->isFolder) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool hasChildFolders = false;
|
|
|
|
|
for (const auto& child : folder->children) {
|
|
|
|
|
if (child && child->isFolder) {
|
|
|
|
|
hasChildFolders = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-27 23:21:43 +08:00
|
|
|
UI::TreeNodeDefinition nodeDefinition;
|
|
|
|
|
nodeDefinition.options.selected = folder->fullPath == currentFolderPath;
|
|
|
|
|
nodeDefinition.options.leaf = !hasChildFolders;
|
|
|
|
|
nodeDefinition.options.defaultOpen = IsCurrentTreeBranch(currentFolderPath, folder->fullPath);
|
|
|
|
|
nodeDefinition.persistenceKey = folder->fullPath;
|
2026-03-28 15:07:19 +08:00
|
|
|
nodeDefinition.style = UI::ProjectFolderTreeStyle();
|
2026-03-27 23:21:43 +08:00
|
|
|
nodeDefinition.prefix.width = UI::NavigationTreePrefixWidth();
|
|
|
|
|
nodeDefinition.prefix.draw = DrawProjectFolderTreePrefix;
|
2026-03-28 00:03:20 +08:00
|
|
|
nodeDefinition.callbacks.onInteraction = [this, &manager, folder](const UI::TreeNodeResult& node) {
|
2026-03-27 23:21:43 +08:00
|
|
|
if (node.clicked) {
|
|
|
|
|
manager.NavigateToFolder(folder);
|
|
|
|
|
}
|
2026-03-28 00:03:20 +08:00
|
|
|
|
|
|
|
|
if (node.secondaryClicked) {
|
|
|
|
|
Actions::HandleProjectItemContextRequest(manager, folder, m_itemContextMenu);
|
|
|
|
|
}
|
2026-03-27 23:21:43 +08:00
|
|
|
};
|
2026-03-27 21:55:14 +08:00
|
|
|
|
2026-03-27 22:05:05 +08:00
|
|
|
const UI::TreeNodeResult node = UI::DrawTreeNode(
|
2026-03-27 23:21:43 +08:00
|
|
|
&m_folderTreeState,
|
2026-03-27 22:05:05 +08:00
|
|
|
(void*)folder.get(),
|
|
|
|
|
folder->name.c_str(),
|
2026-03-27 23:21:43 +08:00
|
|
|
nodeDefinition);
|
2026-03-27 21:55:14 +08:00
|
|
|
|
2026-03-27 22:05:05 +08:00
|
|
|
if (node.open) {
|
2026-03-27 21:55:14 +08:00
|
|
|
for (const auto& child : folder->children) {
|
|
|
|
|
if (!child || !child->isFolder) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
RenderFolderTreeNode(manager, child, currentFolderPath);
|
|
|
|
|
}
|
2026-03-27 22:05:05 +08:00
|
|
|
UI::EndTreeNode();
|
2026-03-27 21:55:14 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProjectPanel::RenderBrowserPane(IProjectManager& manager) {
|
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ProjectBrowserPaneBackgroundColor());
|
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
|
|
|
|
const bool open = ImGui::BeginChild("ProjectBrowser", ImVec2(0.0f, 0.0f), false);
|
|
|
|
|
ImGui::PopStyleVar();
|
|
|
|
|
ImGui::PopStyleColor();
|
|
|
|
|
if (!open) {
|
|
|
|
|
ImGui::EndChild();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<AssetItemPtr> visibleItems;
|
|
|
|
|
const auto& items = manager.GetCurrentItems();
|
|
|
|
|
const std::string search = m_searchBuffer;
|
|
|
|
|
visibleItems.reserve(items.size());
|
|
|
|
|
for (const auto& item : items) {
|
|
|
|
|
if (MatchesSearch(item, search)) {
|
|
|
|
|
visibleItems.push_back(item);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RenderBrowserHeader(manager);
|
|
|
|
|
|
2026-03-28 15:07:19 +08:00
|
|
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ProjectBrowserPaneBackgroundColor());
|
2026-03-27 21:55:14 +08:00
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, UI::ProjectBrowserPanePadding());
|
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, UI::AssetGridSpacing());
|
|
|
|
|
const bool bodyOpen = ImGui::BeginChild("ProjectBrowserBody", ImVec2(0.0f, 0.0f), false);
|
|
|
|
|
ImGui::PopStyleVar(2);
|
2026-03-28 15:07:19 +08:00
|
|
|
ImGui::PopStyleColor();
|
2026-03-27 21:55:14 +08:00
|
|
|
if (!bodyOpen) {
|
|
|
|
|
ImGui::EndChild();
|
|
|
|
|
ImGui::EndChild();
|
2026-03-26 16:43:06 +08:00
|
|
|
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-27 21:55:14 +08:00
|
|
|
if (columns < 1) {
|
|
|
|
|
columns = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AssetItemPtr pendingSelection;
|
|
|
|
|
AssetItemPtr pendingContextTarget;
|
|
|
|
|
AssetItemPtr pendingOpenTarget;
|
|
|
|
|
AssetItemPtr pendingMoveTarget;
|
|
|
|
|
std::string pendingMoveSourcePath;
|
|
|
|
|
|
|
|
|
|
const std::string selectedItemPath = manager.GetSelectedItemPath();
|
|
|
|
|
for (int visibleIndex = 0; visibleIndex < static_cast<int>(visibleItems.size()); ++visibleIndex) {
|
|
|
|
|
if (visibleIndex > 0 && visibleIndex % columns != 0) {
|
2026-03-20 17:08:06 +08:00
|
|
|
ImGui::SameLine();
|
|
|
|
|
}
|
2026-03-27 21:55:14 +08:00
|
|
|
|
|
|
|
|
const AssetItemPtr& item = visibleItems[visibleIndex];
|
|
|
|
|
const AssetItemInteraction interaction = RenderAssetItem(item, selectedItemPath == item->fullPath);
|
|
|
|
|
if (interaction.clicked) {
|
|
|
|
|
pendingSelection = item;
|
|
|
|
|
}
|
|
|
|
|
if (interaction.contextRequested) {
|
|
|
|
|
pendingContextTarget = item;
|
|
|
|
|
}
|
|
|
|
|
if (!interaction.droppedSourcePath.empty()) {
|
|
|
|
|
pendingMoveSourcePath = interaction.droppedSourcePath;
|
|
|
|
|
pendingMoveTarget = item;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (interaction.openRequested) {
|
|
|
|
|
pendingOpenTarget = item;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
2026-03-26 16:43:06 +08:00
|
|
|
|
2026-03-28 15:07:19 +08:00
|
|
|
if (visibleItems.empty() && !search.empty()) {
|
2026-03-26 21:18:33 +08:00
|
|
|
UI::DrawEmptyState(
|
2026-03-28 15:07:19 +08:00
|
|
|
"No Search Results",
|
|
|
|
|
"No assets match the current search");
|
2026-03-26 21:18:33 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-27 12:06:24 +08:00
|
|
|
Actions::HandleProjectBackgroundPrimaryClick(manager);
|
2026-03-27 21:55:14 +08:00
|
|
|
if (pendingSelection) {
|
|
|
|
|
manager.SetSelectedItem(pendingSelection);
|
|
|
|
|
}
|
|
|
|
|
if (pendingContextTarget) {
|
|
|
|
|
Actions::HandleProjectItemContextRequest(manager, pendingContextTarget, m_itemContextMenu);
|
|
|
|
|
}
|
|
|
|
|
if (!pendingMoveSourcePath.empty() && pendingMoveTarget) {
|
|
|
|
|
Commands::MoveAssetToFolder(manager, pendingMoveSourcePath, pendingMoveTarget);
|
|
|
|
|
}
|
|
|
|
|
if (pendingOpenTarget) {
|
|
|
|
|
Actions::OpenProjectAsset(*m_context, pendingOpenTarget);
|
|
|
|
|
}
|
2026-03-27 12:06:24 +08:00
|
|
|
Actions::DrawProjectItemContextPopup(*m_context, m_itemContextMenu);
|
|
|
|
|
Actions::RequestProjectEmptyContextPopup(m_emptyContextMenu);
|
|
|
|
|
Actions::DrawProjectEmptyContextPopup(m_emptyContextMenu, m_createFolderDialog);
|
2026-03-26 23:52:05 +08:00
|
|
|
|
2026-03-27 21:55:14 +08:00
|
|
|
ImGui::EndChild();
|
|
|
|
|
ImGui::EndChild();
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-27 21:55:14 +08:00
|
|
|
void ProjectPanel::RenderBrowserHeader(IProjectManager& manager) {
|
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ProjectBrowserHeaderBackgroundColor());
|
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10.0f, 5.0f));
|
|
|
|
|
const bool open = ImGui::BeginChild(
|
|
|
|
|
"ProjectBrowserHeader",
|
|
|
|
|
ImVec2(0.0f, UI::ProjectBrowserHeaderHeight()),
|
|
|
|
|
false,
|
|
|
|
|
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
|
|
|
|
|
ImGui::PopStyleVar();
|
|
|
|
|
ImGui::PopStyleColor();
|
|
|
|
|
if (!open) {
|
|
|
|
|
ImGui::EndChild();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 15:07:19 +08:00
|
|
|
const float rowHeight = UI::BreadcrumbItemHeight();
|
|
|
|
|
const float startY = ImGui::GetCursorPosY();
|
|
|
|
|
const float availableHeight = ImGui::GetContentRegionAvail().y;
|
|
|
|
|
if (availableHeight > rowHeight) {
|
|
|
|
|
ImGui::SetCursorPosY(startY + (availableHeight - rowHeight) * 0.5f);
|
2026-03-27 21:55:14 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-28 15:07:19 +08:00
|
|
|
UI::DrawToolbarBreadcrumbs(
|
|
|
|
|
"Assets",
|
|
|
|
|
manager.GetPathDepth(),
|
|
|
|
|
[&](size_t index) { return manager.GetPathName(index); },
|
|
|
|
|
[&](size_t index) { manager.NavigateToIndex(index); });
|
|
|
|
|
|
2026-03-27 21:55:14 +08:00
|
|
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
2026-03-28 15:07:19 +08:00
|
|
|
const ImVec2 windowMin = ImGui::GetWindowPos();
|
|
|
|
|
const ImVec2 windowMax(windowMin.x + ImGui::GetWindowSize().x, windowMin.y + ImGui::GetWindowSize().y);
|
|
|
|
|
UI::DrawHorizontalDivider(drawList, windowMin.x, windowMax.x, windowMax.y - 0.5f);
|
2026-03-27 21:55:14 +08:00
|
|
|
|
|
|
|
|
ImGui::EndChild();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ProjectPanel::AssetItemInteraction ProjectPanel::RenderAssetItem(const AssetItemPtr& item, bool isSelected) {
|
|
|
|
|
AssetItemInteraction interaction;
|
|
|
|
|
|
|
|
|
|
ImGui::PushID(item ? item->fullPath.c_str() : "ProjectItem");
|
2026-03-27 00:30:11 +08:00
|
|
|
const bool isDraggingThisItem = Actions::IsProjectAssetBeingDragged(item);
|
2026-03-26 21:18:33 +08:00
|
|
|
const UI::AssetIconKind iconKind = item->isFolder ? UI::AssetIconKind::Folder : UI::AssetIconKind::File;
|
2026-03-28 15:07:19 +08:00
|
|
|
UI::AssetTileOptions tileOptions;
|
|
|
|
|
tileOptions.drawIdleFrame = false;
|
|
|
|
|
tileOptions.drawSelectionBorder = false;
|
|
|
|
|
if (item->isFolder) {
|
|
|
|
|
tileOptions.iconOffset = UI::FolderAssetTileIconOffset();
|
|
|
|
|
tileOptions.iconSize = UI::FolderAssetTileIconSize();
|
|
|
|
|
}
|
2026-03-26 21:18:33 +08:00
|
|
|
|
|
|
|
|
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);
|
2026-03-28 15:07:19 +08:00
|
|
|
},
|
|
|
|
|
tileOptions);
|
2026-03-26 21:18:33 +08:00
|
|
|
|
|
|
|
|
if (tile.clicked) {
|
2026-03-27 21:55:14 +08:00
|
|
|
interaction.clicked = true;
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
2026-03-26 21:18:33 +08:00
|
|
|
|
|
|
|
|
if (tile.contextRequested) {
|
2026-03-27 21:55:14 +08:00
|
|
|
interaction.contextRequested = true;
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-27 21:55:14 +08:00
|
|
|
interaction.droppedSourcePath = Actions::AcceptProjectAssetDropPayload(item);
|
2026-03-27 00:30:11 +08:00
|
|
|
Actions::BeginProjectAssetDrag(item, iconKind);
|
2026-03-20 17:08:06 +08:00
|
|
|
|
2026-03-26 21:18:33 +08:00
|
|
|
if (tile.openRequested) {
|
2026-03-27 21:55:14 +08:00
|
|
|
interaction.openRequested = true;
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImGui::PopID();
|
2026-03-27 21:55:14 +08:00
|
|
|
return interaction;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ProjectPanel::MatchesSearch(const AssetItemPtr& item, const std::string& search) {
|
|
|
|
|
if (!item) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (search.empty()) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto toLower = [](unsigned char c) {
|
|
|
|
|
return static_cast<char>(std::tolower(c));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::string itemName = item->name;
|
|
|
|
|
std::string searchText = search;
|
|
|
|
|
std::transform(itemName.begin(), itemName.end(), itemName.begin(), toLower);
|
|
|
|
|
std::transform(searchText.begin(), searchText.end(), searchText.begin(), toLower);
|
|
|
|
|
return itemName.find(searchText) != std::string::npos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ProjectPanel::IsCurrentTreeBranch(const std::string& currentFolderPath, const std::string& folderPath) {
|
|
|
|
|
if (currentFolderPath.empty() || folderPath.empty()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (currentFolderPath == folderPath) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::string withForwardSlash = folderPath + "/";
|
|
|
|
|
const std::string withBackwardSlash = folderPath + "\\";
|
|
|
|
|
return currentFolderPath.rfind(withForwardSlash, 0) == 0 || currentFolderPath.rfind(withBackwardSlash, 0) == 0;
|
2026-03-20 17:08:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-03-24 20:02:38 +08:00
|
|
|
}
|
2026-03-26 01:26:26 +08:00
|
|
|
}
|