feat: expand editor scripting asset and viewport flow
This commit is contained in:
@@ -5,11 +5,16 @@
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "Core/IProjectManager.h"
|
||||
#include "Core/AssetItem.h"
|
||||
#include "Platform/Win32Utf8.h"
|
||||
#include "Utils/ProjectFileUtils.h"
|
||||
#include "UI/UI.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
#include <imgui.h>
|
||||
#include <shellapi.h>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
@@ -86,6 +91,52 @@ UI::AssetTileOptions MakeProjectAssetTileOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
std::string BuildProjectRelativeAssetPath(const std::string& projectPath, const std::string& fullPath) {
|
||||
if (projectPath.empty() || fullPath.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return ProjectFileUtils::MakeProjectRelativePath(projectPath, fullPath);
|
||||
}
|
||||
|
||||
bool ShowPathInExplorer(const std::string& fullPath, bool selectTarget) {
|
||||
if (fullPath.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
std::error_code ec;
|
||||
const fs::path targetPath = fs::path(Platform::Utf8ToWide(fullPath)).lexically_normal();
|
||||
if (targetPath.empty() || !fs::exists(targetPath, ec)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HINSTANCE result = nullptr;
|
||||
if (selectTarget) {
|
||||
const std::wstring parameters = L"/select,\"" + targetPath.native() + L"\"";
|
||||
const std::wstring workingDirectory = targetPath.parent_path().native();
|
||||
result = ShellExecuteW(
|
||||
nullptr,
|
||||
L"open",
|
||||
L"explorer.exe",
|
||||
parameters.c_str(),
|
||||
workingDirectory.empty() ? nullptr : workingDirectory.c_str(),
|
||||
SW_SHOWNORMAL);
|
||||
} else {
|
||||
const std::wstring workingDirectory = targetPath.parent_path().native();
|
||||
result = ShellExecuteW(
|
||||
nullptr,
|
||||
L"open",
|
||||
targetPath.c_str(),
|
||||
nullptr,
|
||||
workingDirectory.empty() ? nullptr : workingDirectory.c_str(),
|
||||
SW_SHOWNORMAL);
|
||||
}
|
||||
|
||||
return reinterpret_cast<INT_PTR>(result) > 32;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ProjectPanel::ProjectPanel() : Panel("Project") {
|
||||
@@ -186,6 +237,93 @@ void ProjectPanel::CancelRename() {
|
||||
m_renameState.Cancel();
|
||||
}
|
||||
|
||||
ProjectPanel::ContextMenuTarget ProjectPanel::BuildContextMenuTarget(
|
||||
IProjectManager& manager,
|
||||
const AssetItemPtr& item) const {
|
||||
ContextMenuTarget target;
|
||||
target.item = item;
|
||||
if (item) {
|
||||
target.subjectPath = item->fullPath;
|
||||
target.createFolderPath = item->isFolder ? item->fullPath : std::string();
|
||||
target.showInExplorerSelect = true;
|
||||
return target;
|
||||
}
|
||||
|
||||
if (const AssetItemPtr currentFolder = manager.GetCurrentFolder()) {
|
||||
target.subjectPath = currentFolder->fullPath;
|
||||
target.createFolderPath = currentFolder->fullPath;
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
void ProjectPanel::DrawProjectContextMenu(IProjectManager& manager, const ContextMenuTarget& target) {
|
||||
auto* managerPtr = &manager;
|
||||
const bool canCreate = !target.createFolderPath.empty();
|
||||
const bool canShowInExplorer = !target.subjectPath.empty();
|
||||
const bool canOpen = target.item != nullptr && Commands::CanOpenAsset(target.item);
|
||||
const bool canDelete = target.item != nullptr;
|
||||
const bool canRename = target.item != nullptr;
|
||||
const std::string copyPath = BuildProjectRelativeAssetPath(
|
||||
m_context ? m_context->GetProjectPath() : std::string(),
|
||||
target.subjectPath);
|
||||
const bool canCopyPath = !copyPath.empty();
|
||||
|
||||
const auto queueCreateAsset = [this, managerPtr, target](auto createFn) {
|
||||
QueueDeferredAction(m_deferredContextAction, [this, managerPtr, target, createFn]() {
|
||||
if (!target.createFolderPath.empty() && target.item && target.item->isFolder) {
|
||||
managerPtr->NavigateToFolder(target.item);
|
||||
}
|
||||
|
||||
if (AssetItemPtr createdItem = createFn(*managerPtr)) {
|
||||
BeginRename(createdItem);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
UI::DrawContextSubmenu("Create", [&]() {
|
||||
Actions::DrawMenuAction(Actions::MakeAction("Folder", nullptr, false, canCreate), [&]() {
|
||||
queueCreateAsset([](IProjectManager& createManager) {
|
||||
return Commands::CreateFolder(createManager, "New Folder");
|
||||
});
|
||||
});
|
||||
|
||||
Actions::DrawMenuAction(Actions::MakeAction("Material", nullptr, false, canCreate), [&]() {
|
||||
queueCreateAsset([](IProjectManager& createManager) {
|
||||
return Commands::CreateMaterial(createManager, "New Material");
|
||||
});
|
||||
});
|
||||
}, canCreate);
|
||||
|
||||
Actions::DrawMenuAction(Actions::MakeAction("Show in Explore", nullptr, false, canShowInExplorer), [&]() {
|
||||
QueueDeferredAction(m_deferredContextAction, [target]() {
|
||||
ShowPathInExplorer(target.subjectPath, target.showInExplorerSelect);
|
||||
});
|
||||
});
|
||||
|
||||
Actions::DrawMenuAction(Actions::MakeOpenAssetAction(canOpen), [&]() {
|
||||
QueueDeferredAction(m_deferredContextAction, [this, target]() {
|
||||
Actions::OpenProjectAsset(*m_context, target.item);
|
||||
});
|
||||
});
|
||||
|
||||
Actions::DrawMenuAction(Actions::MakeDeleteAssetAction(canDelete), [&]() {
|
||||
QueueDeferredAction(m_deferredContextAction, [this, target]() {
|
||||
Commands::DeleteAsset(m_context->GetProjectManager(), target.item);
|
||||
});
|
||||
});
|
||||
|
||||
Actions::DrawMenuAction(Actions::MakeAction("Rename", nullptr, false, canRename), [&]() {
|
||||
QueueDeferredAction(m_deferredContextAction, [this, target]() {
|
||||
BeginRename(target.item);
|
||||
});
|
||||
});
|
||||
|
||||
Actions::DrawMenuAction(Actions::MakeAction("Copy Path", nullptr, false, canCopyPath), [copyPath]() {
|
||||
ImGui::SetClipboardText(copyPath.c_str());
|
||||
});
|
||||
}
|
||||
|
||||
void ProjectPanel::Render() {
|
||||
UI::PanelWindowScope panel(m_name.c_str());
|
||||
if (!panel.IsOpen()) {
|
||||
@@ -265,7 +403,6 @@ void ProjectPanel::RenderToolbar() {
|
||||
}
|
||||
|
||||
void ProjectPanel::RenderFolderTreePane(IProjectManager& manager) {
|
||||
auto* managerPtr = &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);
|
||||
@@ -287,13 +424,7 @@ void ProjectPanel::RenderFolderTreePane(IProjectManager& manager) {
|
||||
}
|
||||
|
||||
if (UI::BeginContextMenuForWindow("##ProjectFolderTreeContext")) {
|
||||
Actions::DrawMenuAction(Actions::MakeCreateFolderAction(), [&]() {
|
||||
QueueDeferredAction(m_deferredContextAction, [this, managerPtr]() {
|
||||
if (AssetItemPtr createdFolder = Commands::CreateFolder(*managerPtr, "New Folder")) {
|
||||
BeginRename(createdFolder);
|
||||
}
|
||||
});
|
||||
});
|
||||
DrawProjectContextMenu(manager, BuildContextMenuTarget(manager, nullptr));
|
||||
UI::EndContextMenu();
|
||||
}
|
||||
|
||||
@@ -351,7 +482,6 @@ void ProjectPanel::RenderFolderTreeNode(
|
||||
}
|
||||
|
||||
void ProjectPanel::RenderBrowserPane(IProjectManager& manager) {
|
||||
auto* managerPtr = &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);
|
||||
@@ -393,7 +523,6 @@ void ProjectPanel::RenderBrowserPane(IProjectManager& manager) {
|
||||
}
|
||||
|
||||
const float tileWidth = UI::AssetTileSize().x;
|
||||
const float tileHeight = UI::AssetTileSize().y;
|
||||
const float spacing = UI::AssetGridSpacing().x;
|
||||
const float rowSpacing = UI::AssetGridSpacing().y;
|
||||
const float panelWidth = ImGui::GetContentRegionAvail().x;
|
||||
@@ -402,19 +531,41 @@ void ProjectPanel::RenderBrowserPane(IProjectManager& manager) {
|
||||
columns = 1;
|
||||
}
|
||||
|
||||
const int rowCount = visibleItems.empty() ? 0 : (static_cast<int>(visibleItems.size()) + columns - 1) / columns;
|
||||
std::vector<float> rowHeights(static_cast<size_t>(rowCount), UI::AssetTileSize().y);
|
||||
|
||||
AssetItemPtr pendingSelection;
|
||||
AssetItemPtr pendingOpenTarget;
|
||||
const std::string selectedItemPath = manager.GetSelectedItemPath();
|
||||
const ImVec2 gridOrigin = ImGui::GetCursorPos();
|
||||
for (int visibleIndex = 0; visibleIndex < static_cast<int>(visibleItems.size()); ++visibleIndex) {
|
||||
const AssetItemPtr& item = visibleItems[visibleIndex];
|
||||
const bool isRenaming = item && m_renameState.IsEditing(item->fullPath);
|
||||
UI::AssetTileOptions tileOptions = MakeProjectAssetTileOptions();
|
||||
tileOptions.drawLabel = !isRenaming;
|
||||
const ImVec2 tileSize = UI::ComputeAssetTileSize(GetProjectAssetDisplayName(item).c_str(), tileOptions);
|
||||
const int row = visibleIndex / columns;
|
||||
rowHeights[static_cast<size_t>(row)] = (std::max)(rowHeights[static_cast<size_t>(row)], tileSize.y);
|
||||
}
|
||||
|
||||
std::vector<float> rowOffsets(static_cast<size_t>(rowCount), gridOrigin.y);
|
||||
float nextRowY = gridOrigin.y;
|
||||
for (int row = 0; row < rowCount; ++row) {
|
||||
rowOffsets[static_cast<size_t>(row)] = nextRowY;
|
||||
nextRowY += rowHeights[static_cast<size_t>(row)] + rowSpacing;
|
||||
}
|
||||
|
||||
int renderedItemCount = 0;
|
||||
for (int visibleIndex = 0; visibleIndex < static_cast<int>(visibleItems.size()); ++visibleIndex) {
|
||||
const int column = visibleIndex % columns;
|
||||
const int row = visibleIndex / columns;
|
||||
ImGui::SetCursorPos(ImVec2(
|
||||
gridOrigin.x + column * (tileWidth + spacing),
|
||||
gridOrigin.y + row * (tileHeight + rowSpacing)));
|
||||
rowOffsets[static_cast<size_t>(row)]));
|
||||
|
||||
const AssetItemPtr& item = visibleItems[visibleIndex];
|
||||
const AssetItemInteraction interaction = RenderAssetItem(item, selectedItemPath == item->fullPath);
|
||||
++renderedItemCount;
|
||||
if (interaction.clicked) {
|
||||
pendingSelection = item;
|
||||
}
|
||||
@@ -427,9 +578,14 @@ void ProjectPanel::RenderBrowserPane(IProjectManager& manager) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!visibleItems.empty()) {
|
||||
const int rowCount = (static_cast<int>(visibleItems.size()) + columns - 1) / columns;
|
||||
ImGui::SetCursorPosY(gridOrigin.y + rowCount * tileHeight + (rowCount - 1) * rowSpacing);
|
||||
if (renderedItemCount > 0) {
|
||||
const int renderedRowCount = (renderedItemCount + columns - 1) / columns;
|
||||
float contentBottom = gridOrigin.y;
|
||||
for (int row = 0; row < renderedRowCount; ++row) {
|
||||
contentBottom = rowOffsets[static_cast<size_t>(row)] + rowHeights[static_cast<size_t>(row)];
|
||||
}
|
||||
ImGui::SetCursorPos(ImVec2(gridOrigin.x, contentBottom));
|
||||
ImGui::Dummy(ImVec2(0.0f, 0.0f));
|
||||
}
|
||||
|
||||
if (visibleItems.empty() && !searchQuery.Empty()) {
|
||||
@@ -449,21 +605,7 @@ void ProjectPanel::RenderBrowserPane(IProjectManager& manager) {
|
||||
}
|
||||
|
||||
if (UI::BeginContextMenuForWindow("##ProjectBrowserContext")) {
|
||||
Actions::DrawMenuAction(Actions::MakeCreateFolderAction(), [&]() {
|
||||
QueueDeferredAction(m_deferredContextAction, [this, managerPtr]() {
|
||||
if (AssetItemPtr createdFolder = Commands::CreateFolder(*managerPtr, "New Folder")) {
|
||||
BeginRename(createdFolder);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (manager.CanNavigateBack()) {
|
||||
Actions::DrawMenuSeparator();
|
||||
Actions::DrawMenuAction(Actions::MakeNavigateBackAction(true), [&]() {
|
||||
QueueDeferredAction(m_deferredContextAction, [managerPtr]() {
|
||||
managerPtr->NavigateBack();
|
||||
});
|
||||
});
|
||||
}
|
||||
DrawProjectContextMenu(manager, BuildContextMenuTarget(manager, nullptr));
|
||||
UI::EndContextMenu();
|
||||
}
|
||||
|
||||
@@ -570,21 +712,7 @@ ProjectPanel::AssetItemInteraction ProjectPanel::RenderAssetItem(const AssetItem
|
||||
Actions::BeginProjectAssetDrag(item, iconKind);
|
||||
|
||||
if (UI::BeginContextMenuForLastItem("##ProjectItemContext")) {
|
||||
Actions::DrawMenuAction(Actions::MakeOpenAssetAction(Commands::CanOpenAsset(item)), [&]() {
|
||||
QueueDeferredAction(m_deferredContextAction, [this, item]() {
|
||||
Actions::OpenProjectAsset(*m_context, item);
|
||||
});
|
||||
});
|
||||
Actions::DrawMenuAction(Actions::MakeAction("Rename", nullptr, false, item != nullptr), [&]() {
|
||||
QueueDeferredAction(m_deferredContextAction, [this, item]() {
|
||||
BeginRename(item);
|
||||
});
|
||||
});
|
||||
Actions::DrawMenuAction(Actions::MakeDeleteAssetAction(item != nullptr), [&]() {
|
||||
QueueDeferredAction(m_deferredContextAction, [this, item]() {
|
||||
Commands::DeleteAsset(m_context->GetProjectManager(), item);
|
||||
});
|
||||
});
|
||||
DrawProjectContextMenu(m_context->GetProjectManager(), BuildContextMenuTarget(m_context->GetProjectManager(), item));
|
||||
UI::EndContextMenu();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user