chore: sync workspace state
This commit is contained in:
@@ -200,7 +200,7 @@ inline void DrawHierarchyBackgroundContextPopup(IEditorContext& context, UI::Def
|
||||
}
|
||||
|
||||
inline void DrawHierarchyContextActions(IEditorContext& context, ::XCEngine::Components::GameObject* gameObject) {
|
||||
if (UI::DrawMenuScope("Create", [&]() {
|
||||
if (UI::DrawPopupSubmenuScope("Create", [&]() {
|
||||
DrawHierarchyCreateActions(context, gameObject);
|
||||
})) {
|
||||
}
|
||||
|
||||
@@ -31,34 +31,14 @@ inline bool IsProjectAssetBeingDragged(const AssetItemPtr& item) {
|
||||
return item != nullptr && draggedPath != nullptr && item->fullPath == draggedPath;
|
||||
}
|
||||
|
||||
inline std::string AcceptProjectAssetDropPayload(const AssetItemPtr& targetFolder) {
|
||||
if (!targetFolder || !targetFolder->isFolder || !ImGui::BeginDragDropTarget()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string draggedPath;
|
||||
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(ProjectAssetPayloadType())) {
|
||||
const char* payloadPath = static_cast<const char*>(payload->Data);
|
||||
if (payloadPath) {
|
||||
draggedPath = payloadPath;
|
||||
}
|
||||
}
|
||||
ImGui::EndDragDropTarget();
|
||||
return draggedPath;
|
||||
}
|
||||
|
||||
inline bool BeginProjectAssetDrag(const AssetItemPtr& item, UI::AssetIconKind iconKind) {
|
||||
if (!item || item->fullPath.empty() || !ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
|
||||
if (!item || item->fullPath.empty() || !ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceNoPreviewTooltip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
(void)iconKind;
|
||||
ImGui::SetDragDropPayload(ProjectAssetPayloadType(), 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();
|
||||
return true;
|
||||
}
|
||||
@@ -82,8 +62,11 @@ inline bool DrawProjectNavigateBackAction(IProjectManager& projectManager) {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void HandleProjectBackgroundPrimaryClick(IProjectManager& projectManager) {
|
||||
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered()) {
|
||||
template <size_t BufferCapacity>
|
||||
inline void HandleProjectBackgroundPrimaryClick(
|
||||
IProjectManager& projectManager,
|
||||
const UI::InlineTextEditState<std::string, BufferCapacity>& renameState) {
|
||||
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && !renameState.IsActive()) {
|
||||
projectManager.ClearSelection();
|
||||
}
|
||||
}
|
||||
@@ -133,52 +116,19 @@ inline void DrawProjectAssetContextActions(IEditorContext& context, const AssetI
|
||||
});
|
||||
}
|
||||
|
||||
template <size_t BufferCapacity>
|
||||
inline void DrawProjectEmptyContextActions(UI::TextInputPopupState<BufferCapacity>& createFolderDialog) {
|
||||
DrawMenuAction(MakeCreateFolderAction(), [&]() {
|
||||
createFolderDialog.RequestOpen("NewFolder");
|
||||
});
|
||||
}
|
||||
|
||||
template <size_t BufferCapacity>
|
||||
template <typename CreateFolderFn>
|
||||
inline void DrawProjectEmptyContextPopup(
|
||||
UI::DeferredPopupState& emptyContextMenu,
|
||||
UI::TextInputPopupState<BufferCapacity>& createFolderDialog) {
|
||||
CreateFolderFn&& createFolder) {
|
||||
emptyContextMenu.ConsumeOpenRequest("EmptyContextMenu");
|
||||
|
||||
if (!UI::BeginPopup("EmptyContextMenu")) {
|
||||
return;
|
||||
}
|
||||
|
||||
DrawProjectEmptyContextActions(createFolderDialog);
|
||||
UI::EndPopup();
|
||||
}
|
||||
|
||||
template <size_t BufferCapacity>
|
||||
inline void DrawProjectCreateFolderDialog(IEditorContext& context, UI::TextInputPopupState<BufferCapacity>& createFolderDialog) {
|
||||
createFolderDialog.ConsumeOpenRequest("Create Folder");
|
||||
|
||||
if (!UI::BeginModalPopup("Create Folder")) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::InputText("Name", createFolderDialog.Buffer(), createFolderDialog.BufferSize());
|
||||
ImGui::Separator();
|
||||
|
||||
switch (UI::DrawDialogActionRow("Create", "Cancel", !createFolderDialog.Empty())) {
|
||||
case UI::DialogActionResult::Primary:
|
||||
if (Commands::CreateFolder(context.GetProjectManager(), createFolderDialog.Buffer())) {
|
||||
createFolderDialog.Clear();
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
break;
|
||||
case UI::DialogActionResult::Secondary:
|
||||
createFolderDialog.Clear();
|
||||
ImGui::CloseCurrentPopup();
|
||||
break;
|
||||
case UI::DialogActionResult::None:
|
||||
break;
|
||||
}
|
||||
DrawMenuAction(MakeCreateFolderAction(), [&]() {
|
||||
createFolder();
|
||||
});
|
||||
|
||||
UI::EndPopup();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "Utils/FileDialogUtils.h"
|
||||
#include "Utils/ProjectFileUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cwctype>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
@@ -18,6 +20,36 @@ namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace Commands {
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline std::wstring MakeProjectPathKey(const std::filesystem::path& path) {
|
||||
std::wstring key = path.lexically_normal().native();
|
||||
std::replace(key.begin(), key.end(), L'/', L'\\');
|
||||
std::transform(key.begin(), key.end(), key.begin(), [](wchar_t ch) {
|
||||
return static_cast<wchar_t>(std::towlower(ch));
|
||||
});
|
||||
|
||||
while (key.size() > 1 && (key.back() == L'\\' || key.back() == L'/')) {
|
||||
key.pop_back();
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
inline bool IsSameOrDescendantProjectPath(const std::filesystem::path& path, const std::filesystem::path& root) {
|
||||
const std::wstring pathKey = MakeProjectPathKey(path);
|
||||
std::wstring rootKey = MakeProjectPathKey(root);
|
||||
if (pathKey == rootKey) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!rootKey.empty() && rootKey.back() != L'\\') {
|
||||
rootKey.push_back(L'\\');
|
||||
}
|
||||
return pathKey.rfind(rootKey, 0) == 0;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
inline bool CanOpenAsset(const AssetItemPtr& item) {
|
||||
return item != nullptr && (item->isFolder || item->type == "Scene");
|
||||
}
|
||||
@@ -35,13 +67,12 @@ inline bool OpenAsset(IEditorContext& context, const AssetItemPtr& item) {
|
||||
return LoadScene(context, item->fullPath);
|
||||
}
|
||||
|
||||
inline bool CreateFolder(IProjectManager& projectManager, const std::string& name) {
|
||||
inline AssetItemPtr CreateFolder(IProjectManager& projectManager, const std::string& name) {
|
||||
if (name.empty()) {
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
projectManager.CreateFolder(name);
|
||||
return true;
|
||||
return projectManager.CreateFolder(name);
|
||||
}
|
||||
|
||||
inline bool DeleteAsset(IProjectManager& projectManager, const std::string& fullPath) {
|
||||
@@ -60,7 +91,7 @@ inline bool DeleteAsset(IProjectManager& projectManager, const AssetItemPtr& ite
|
||||
return DeleteAsset(projectManager, item->fullPath);
|
||||
}
|
||||
|
||||
inline bool MoveAssetToFolder(
|
||||
inline bool CanMoveAssetToFolder(
|
||||
IProjectManager& projectManager,
|
||||
const std::string& sourceFullPath,
|
||||
const AssetItemPtr& targetFolder) {
|
||||
@@ -68,13 +99,68 @@ inline bool MoveAssetToFolder(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceFullPath == targetFolder->fullPath) {
|
||||
const AssetItemPtr rootFolder = projectManager.GetRootFolder();
|
||||
if (!rootFolder) {
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
try {
|
||||
const fs::path sourcePath = fs::path(sourceFullPath);
|
||||
const fs::path destFolderPath = fs::path(targetFolder->fullPath);
|
||||
const fs::path rootPath = fs::path(rootFolder->fullPath);
|
||||
|
||||
if (!fs::exists(sourcePath) || !fs::exists(destFolderPath) || !fs::is_directory(destFolderPath)) {
|
||||
return false;
|
||||
}
|
||||
if (!detail::IsSameOrDescendantProjectPath(sourcePath, rootPath) ||
|
||||
!detail::IsSameOrDescendantProjectPath(destFolderPath, rootPath)) {
|
||||
return false;
|
||||
}
|
||||
if (detail::MakeProjectPathKey(sourcePath) == detail::MakeProjectPathKey(rootPath)) {
|
||||
return false;
|
||||
}
|
||||
if (fs::is_directory(sourcePath) && detail::IsSameOrDescendantProjectPath(destFolderPath, sourcePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fs::path destPath = destFolderPath / sourcePath.filename();
|
||||
if (detail::MakeProjectPathKey(destPath) == detail::MakeProjectPathKey(sourcePath) || fs::exists(destPath)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool MoveAssetToFolder(
|
||||
IProjectManager& projectManager,
|
||||
const std::string& sourceFullPath,
|
||||
const AssetItemPtr& targetFolder) {
|
||||
if (!CanMoveAssetToFolder(projectManager, sourceFullPath, targetFolder)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return projectManager.MoveItem(sourceFullPath, targetFolder->fullPath);
|
||||
}
|
||||
|
||||
inline bool RenameAsset(IProjectManager& projectManager, const std::string& sourceFullPath, const std::string& newName) {
|
||||
if (sourceFullPath.empty() || newName.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return projectManager.RenameItem(sourceFullPath, newName);
|
||||
}
|
||||
|
||||
inline bool RenameAsset(IProjectManager& projectManager, const AssetItemPtr& item, const std::string& newName) {
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return RenameAsset(projectManager, item->fullPath, newName);
|
||||
}
|
||||
|
||||
inline std::string BuildProjectFallbackScenePath(const std::string& projectPath) {
|
||||
return (std::filesystem::path(projectPath) / "Assets" / "Scenes" / "Main.xc").string();
|
||||
}
|
||||
|
||||
@@ -35,9 +35,10 @@ public:
|
||||
virtual void Initialize(const std::string& projectPath) = 0;
|
||||
virtual void RefreshCurrentFolder() = 0;
|
||||
|
||||
virtual void CreateFolder(const std::string& name) = 0;
|
||||
virtual AssetItemPtr CreateFolder(const std::string& name) = 0;
|
||||
virtual bool DeleteItem(const std::string& fullPath) = 0;
|
||||
virtual bool MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath) = 0;
|
||||
virtual bool RenameItem(const std::string& sourceFullPath, const std::string& newName) = 0;
|
||||
|
||||
virtual const std::string& GetProjectPath() const = 0;
|
||||
};
|
||||
|
||||
@@ -34,9 +34,10 @@ public:
|
||||
void Initialize(const std::string& projectPath) override;
|
||||
void RefreshCurrentFolder() override;
|
||||
|
||||
void CreateFolder(const std::string& name) override;
|
||||
AssetItemPtr CreateFolder(const std::string& name) override;
|
||||
bool DeleteItem(const std::string& fullPath) override;
|
||||
bool MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath) override;
|
||||
bool RenameItem(const std::string& sourceFullPath, const std::string& newName) override;
|
||||
|
||||
const std::string& GetProjectPath() const override { return m_projectPath; }
|
||||
|
||||
|
||||
@@ -6,9 +6,13 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cwctype>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include <stb_image.h>
|
||||
@@ -36,12 +40,26 @@ struct BuiltInTexture {
|
||||
|
||||
struct BuiltInIconState {
|
||||
ImGuiBackendBridge* backend = nullptr;
|
||||
ID3D12Device* device = nullptr;
|
||||
ID3D12CommandQueue* commandQueue = nullptr;
|
||||
BuiltInTexture folder;
|
||||
BuiltInTexture gameObject;
|
||||
BuiltInTexture scene;
|
||||
struct CachedAssetPreview {
|
||||
BuiltInTexture texture;
|
||||
bool loadAttempted = false;
|
||||
int lastUsedFrame = -1;
|
||||
};
|
||||
std::unordered_map<std::string, CachedAssetPreview> assetPreviews;
|
||||
int lastPreviewBudgetFrame = -1;
|
||||
int previewLoadsThisFrame = 0;
|
||||
};
|
||||
|
||||
BuiltInIconState g_icons;
|
||||
|
||||
constexpr size_t kMaxCachedAssetPreviews = 40;
|
||||
constexpr int kMaxPreviewLoadsPerFrame = 2;
|
||||
|
||||
std::filesystem::path ResolveFolderIconPath() {
|
||||
const std::filesystem::path exeDir(Platform::GetExecutableDirectoryUtf8());
|
||||
return (exeDir / ".." / ".." / "resources" / "Icons" / "folder_icon.png").lexically_normal();
|
||||
@@ -52,6 +70,33 @@ std::filesystem::path ResolveGameObjectIconPath() {
|
||||
return (exeDir / ".." / ".." / "resources" / "Icons" / "gameobject_icon.png").lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveSceneIconPath() {
|
||||
const std::filesystem::path exeDir(Platform::GetExecutableDirectoryUtf8());
|
||||
return (exeDir / ".." / ".." / "resources" / "Icons" / "scene_icon.png").lexically_normal();
|
||||
}
|
||||
|
||||
std::string MakeAssetPreviewKey(const std::string& filePath) {
|
||||
std::wstring key = std::filesystem::path(Platform::Utf8ToWide(filePath)).lexically_normal().generic_wstring();
|
||||
std::transform(key.begin(), key.end(), key.begin(), ::towlower);
|
||||
return Platform::WideToUtf8(key);
|
||||
}
|
||||
|
||||
bool ReadFileBytes(const std::filesystem::path& filePath, std::vector<stbi_uc>& bytes) {
|
||||
std::ifstream stream(filePath, std::ios::binary | std::ios::ate);
|
||||
if (!stream.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::streamsize size = stream.tellg();
|
||||
if (size <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bytes.resize(static_cast<size_t>(size));
|
||||
stream.seekg(0, std::ios::beg);
|
||||
return stream.read(reinterpret_cast<char*>(bytes.data()), size).good();
|
||||
}
|
||||
|
||||
void ResetTexture(BuiltInTexture& texture) {
|
||||
if (g_icons.backend && texture.cpuHandle.ptr != 0) {
|
||||
g_icons.backend->FreeTextureDescriptor(texture.cpuHandle, texture.gpuHandle);
|
||||
@@ -65,6 +110,13 @@ void ResetTexture(BuiltInTexture& texture) {
|
||||
texture.height = 0;
|
||||
}
|
||||
|
||||
void ResetAssetPreviewCache() {
|
||||
for (auto& entry : g_icons.assetPreviews) {
|
||||
ResetTexture(entry.second.texture);
|
||||
}
|
||||
g_icons.assetPreviews.clear();
|
||||
}
|
||||
|
||||
bool WaitForQueueIdle(ID3D12Device* device, ID3D12CommandQueue* commandQueue) {
|
||||
if (!device || !commandQueue) {
|
||||
return false;
|
||||
@@ -109,10 +161,21 @@ bool LoadTextureFromFile(
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<stbi_uc> fileData;
|
||||
if (!ReadFileBytes(filePath, fileData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int channels = 0;
|
||||
stbi_uc* pixels = stbi_load(filePath.string().c_str(), &width, &height, &channels, STBI_rgb_alpha);
|
||||
stbi_uc* pixels = stbi_load_from_memory(
|
||||
fileData.data(),
|
||||
static_cast<int>(fileData.size()),
|
||||
&width,
|
||||
&height,
|
||||
&channels,
|
||||
STBI_rgb_alpha);
|
||||
if (!pixels || width <= 0 || height <= 0) {
|
||||
if (pixels) {
|
||||
stbi_image_free(pixels);
|
||||
@@ -328,6 +391,76 @@ void DrawBuiltInFileIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2&
|
||||
drawList->AddLine(foldA, foldB, lineColor);
|
||||
}
|
||||
|
||||
BuiltInIconState::CachedAssetPreview* GetOrCreateAssetPreview(const std::string& filePath) {
|
||||
if (!g_icons.backend || !g_icons.device || !g_icons.commandQueue || filePath.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::string key = MakeAssetPreviewKey(filePath);
|
||||
auto [it, inserted] = g_icons.assetPreviews.try_emplace(key);
|
||||
BuiltInIconState::CachedAssetPreview& preview = it->second;
|
||||
preview.lastUsedFrame = ImGui::GetFrameCount();
|
||||
|
||||
if (!inserted && !std::filesystem::exists(std::filesystem::path(Platform::Utf8ToWide(filePath)))) {
|
||||
ResetTexture(preview.texture);
|
||||
preview.loadAttempted = true;
|
||||
return &preview;
|
||||
}
|
||||
|
||||
if (preview.texture.IsValid() || preview.loadAttempted) {
|
||||
return &preview;
|
||||
}
|
||||
|
||||
const int frame = ImGui::GetFrameCount();
|
||||
if (g_icons.lastPreviewBudgetFrame != frame) {
|
||||
g_icons.lastPreviewBudgetFrame = frame;
|
||||
g_icons.previewLoadsThisFrame = 0;
|
||||
}
|
||||
if (g_icons.previewLoadsThisFrame >= kMaxPreviewLoadsPerFrame) {
|
||||
return &preview;
|
||||
}
|
||||
|
||||
preview.loadAttempted = true;
|
||||
++g_icons.previewLoadsThisFrame;
|
||||
LoadTextureFromFile(
|
||||
*g_icons.backend,
|
||||
g_icons.device,
|
||||
g_icons.commandQueue,
|
||||
std::filesystem::path(Platform::Utf8ToWide(filePath)),
|
||||
preview.texture);
|
||||
return &preview;
|
||||
}
|
||||
|
||||
void PruneAssetPreviewCache() {
|
||||
if (g_icons.assetPreviews.size() <= kMaxCachedAssetPreviews) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, int>> candidates;
|
||||
candidates.reserve(g_icons.assetPreviews.size());
|
||||
for (const auto& entry : g_icons.assetPreviews) {
|
||||
candidates.emplace_back(entry.first, entry.second.lastUsedFrame);
|
||||
}
|
||||
|
||||
std::sort(
|
||||
candidates.begin(),
|
||||
candidates.end(),
|
||||
[](const auto& lhs, const auto& rhs) {
|
||||
return lhs.second < rhs.second;
|
||||
});
|
||||
|
||||
size_t removeCount = g_icons.assetPreviews.size() - kMaxCachedAssetPreviews;
|
||||
for (size_t i = 0; i < removeCount && i < candidates.size(); ++i) {
|
||||
auto it = g_icons.assetPreviews.find(candidates[i].first);
|
||||
if (it == g_icons.assetPreviews.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ResetTexture(it->second.texture);
|
||||
g_icons.assetPreviews.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void InitializeBuiltInIcons(
|
||||
@@ -336,14 +469,23 @@ void InitializeBuiltInIcons(
|
||||
ID3D12CommandQueue* commandQueue) {
|
||||
ShutdownBuiltInIcons();
|
||||
g_icons.backend = &backend;
|
||||
g_icons.device = device;
|
||||
g_icons.commandQueue = commandQueue;
|
||||
LoadTextureFromFile(backend, device, commandQueue, ResolveFolderIconPath(), g_icons.folder);
|
||||
LoadTextureFromFile(backend, device, commandQueue, ResolveGameObjectIconPath(), g_icons.gameObject);
|
||||
LoadTextureFromFile(backend, device, commandQueue, ResolveSceneIconPath(), g_icons.scene);
|
||||
}
|
||||
|
||||
void ShutdownBuiltInIcons() {
|
||||
ResetAssetPreviewCache();
|
||||
ResetTexture(g_icons.folder);
|
||||
ResetTexture(g_icons.gameObject);
|
||||
ResetTexture(g_icons.scene);
|
||||
g_icons.backend = nullptr;
|
||||
g_icons.device = nullptr;
|
||||
g_icons.commandQueue = nullptr;
|
||||
g_icons.lastPreviewBudgetFrame = -1;
|
||||
g_icons.previewLoadsThisFrame = 0;
|
||||
}
|
||||
|
||||
void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, AssetIconKind kind) {
|
||||
@@ -367,9 +509,34 @@ void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, A
|
||||
return;
|
||||
}
|
||||
|
||||
if (kind == AssetIconKind::Scene) {
|
||||
if (g_icons.scene.IsValid()) {
|
||||
DrawTextureIcon(drawList, g_icons.scene, min, max);
|
||||
return;
|
||||
}
|
||||
|
||||
DrawBuiltInFileIcon(drawList, min, max);
|
||||
return;
|
||||
}
|
||||
|
||||
DrawBuiltInFileIcon(drawList, min, max);
|
||||
}
|
||||
|
||||
bool DrawTextureAssetPreview(
|
||||
ImDrawList* drawList,
|
||||
const ImVec2& min,
|
||||
const ImVec2& max,
|
||||
const std::string& filePath) {
|
||||
BuiltInIconState::CachedAssetPreview* preview = GetOrCreateAssetPreview(filePath);
|
||||
if (!preview || !preview->texture.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DrawTextureIcon(drawList, preview->texture, min, max);
|
||||
PruneAssetPreviewCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
#include <string>
|
||||
|
||||
struct ID3D12Device;
|
||||
struct ID3D12CommandQueue;
|
||||
@@ -14,7 +15,8 @@ class ImGuiBackendBridge;
|
||||
enum class AssetIconKind {
|
||||
Folder,
|
||||
File,
|
||||
GameObject
|
||||
GameObject,
|
||||
Scene
|
||||
};
|
||||
|
||||
void InitializeBuiltInIcons(
|
||||
@@ -25,6 +27,11 @@ void InitializeBuiltInIcons(
|
||||
void ShutdownBuiltInIcons();
|
||||
|
||||
void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, AssetIconKind kind);
|
||||
bool DrawTextureAssetPreview(
|
||||
ImDrawList* drawList,
|
||||
const ImVec2& min,
|
||||
const ImVec2& max,
|
||||
const std::string& filePath);
|
||||
|
||||
} // namespace UI
|
||||
} // namespace Editor
|
||||
|
||||
@@ -89,6 +89,18 @@ inline float ColorPickerInputWidth() {
|
||||
return 62.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerChannelLabelWidth() {
|
||||
return 12.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerHexLabelWidth() {
|
||||
return 84.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerFieldSpacing() {
|
||||
return 8.0f;
|
||||
}
|
||||
|
||||
inline float ColorPickerCloseButtonSize() {
|
||||
return 18.0f;
|
||||
}
|
||||
@@ -137,6 +149,10 @@ inline ImVec4 ColorPickerBorderColor() {
|
||||
return ImVec4(0.15f, 0.15f, 0.15f, 1.0f);
|
||||
}
|
||||
|
||||
inline float ColorPickerPopupRounding() {
|
||||
return 3.0f;
|
||||
}
|
||||
|
||||
inline ImVec4 ColorPickerPreviewBorderColor() {
|
||||
return ImVec4(0.12f, 0.12f, 0.12f, 1.0f);
|
||||
}
|
||||
@@ -550,14 +566,15 @@ inline bool DrawChannelGradientSlider(
|
||||
}
|
||||
|
||||
inline bool DrawChannelRow(const char* label, int componentIndex, ColorPickerState& state, float rowWidth) {
|
||||
constexpr float labelWidth = 14.0f;
|
||||
constexpr float spacing = 8.0f;
|
||||
const float labelWidth = ColorPickerChannelLabelWidth();
|
||||
const float spacing = ColorPickerFieldSpacing();
|
||||
const float inputWidth = ColorPickerInputWidth();
|
||||
const float sliderWidth = ImMax(rowWidth - labelWidth - spacing - inputWidth - spacing, 60.0f);
|
||||
const float rowStartX = ImGui::GetCursorPosX();
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted(label);
|
||||
ImGui::SameLine(0.0f, spacing);
|
||||
ImGui::SameLine(rowStartX + labelWidth + spacing, 0.0f);
|
||||
|
||||
ImGui::PushID(label);
|
||||
float& value = state.color[componentIndex];
|
||||
@@ -577,14 +594,15 @@ inline bool DrawChannelRow(const char* label, int componentIndex, ColorPickerSta
|
||||
}
|
||||
|
||||
inline bool DrawAlphaRow(ColorPickerState& state, float rowWidth) {
|
||||
constexpr float labelWidth = 14.0f;
|
||||
constexpr float spacing = 8.0f;
|
||||
const float labelWidth = ColorPickerChannelLabelWidth();
|
||||
const float spacing = ColorPickerFieldSpacing();
|
||||
const float inputWidth = ColorPickerInputWidth();
|
||||
const float sliderWidth = ImMax(rowWidth - labelWidth - spacing - inputWidth - spacing, 60.0f);
|
||||
const float rowStartX = ImGui::GetCursorPosX();
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("A");
|
||||
ImGui::SameLine(0.0f, spacing);
|
||||
ImGui::SameLine(rowStartX + labelWidth + spacing, 0.0f);
|
||||
|
||||
ImGui::PushID("Alpha");
|
||||
const ImVec4 startColor(state.color[0], state.color[1], state.color[2], 0.0f);
|
||||
@@ -607,15 +625,19 @@ inline bool DrawAlphaRow(ColorPickerState& state, float rowWidth) {
|
||||
}
|
||||
|
||||
inline bool DrawHexRow(ColorPickerState& state, float rowWidth) {
|
||||
const float labelWidth = ColorPickerHexLabelWidth();
|
||||
const float spacing = ColorPickerFieldSpacing();
|
||||
const float rowStartX = ImGui::GetCursorPosX();
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Hexadecimal");
|
||||
ImGui::SameLine(0.0f, 8.0f);
|
||||
ImGui::SameLine(rowStartX + labelWidth + spacing, 0.0f);
|
||||
|
||||
if (!state.hexEditing) {
|
||||
FormatHexBuffer(state);
|
||||
}
|
||||
|
||||
ImGui::SetNextItemWidth(ImMax(rowWidth - ImGui::CalcTextSize("Hexadecimal").x - 8.0f, 40.0f));
|
||||
ImGui::SetNextItemWidth(ImMax(rowWidth - labelWidth - spacing, 40.0f));
|
||||
const bool edited = ImGui::InputText(
|
||||
"##hex",
|
||||
state.hexBuffer,
|
||||
@@ -660,10 +682,11 @@ inline bool DrawCloseButton(const char* id, const ImVec2& size) {
|
||||
inline bool DrawUnityColorPickerPopup(const char* popupId, ColorPickerState& state, bool includeAlpha) {
|
||||
const float headerHeight = ColorPickerHeaderHeight();
|
||||
const float bodyPadding = ColorPickerBodyPadding();
|
||||
const float popupRounding = ColorPickerPopupRounding();
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, 3.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 1.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, popupRounding);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, PopupWindowBorderSize());
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ColorPickerBodyColor());
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, ColorPickerBorderColor());
|
||||
|
||||
@@ -685,15 +708,16 @@ inline bool DrawUnityColorPickerPopup(const char* popupId, ColorPickerState& sta
|
||||
const ImVec2 windowPos = ImGui::GetWindowPos();
|
||||
const ImVec2 windowSize = ImGui::GetWindowSize();
|
||||
const ImVec2 windowMax(windowPos.x + windowSize.x, windowPos.y + windowSize.y);
|
||||
drawList->AddRectFilled(windowPos, windowMax, ImGui::GetColorU32(ColorPickerBodyColor()), 3.0f);
|
||||
drawList->AddRectFilled(windowPos, windowMax, ImGui::GetColorU32(ColorPickerBodyColor()), popupRounding);
|
||||
drawList->AddRectFilled(
|
||||
windowPos,
|
||||
ImVec2(windowMax.x, windowPos.y + headerHeight),
|
||||
ImGui::GetColorU32(ColorPickerHeaderColor()),
|
||||
3.0f,
|
||||
popupRounding,
|
||||
ImDrawFlags_RoundCornersTop);
|
||||
drawList->AddRect(windowPos, windowMax, ImGui::GetColorU32(ColorPickerBorderColor()), 3.0f);
|
||||
drawList->AddText(ImVec2(windowPos.x + 10.0f, windowPos.y + 8.0f), IM_COL32(255, 255, 255, 255), "Color");
|
||||
drawList->AddRect(windowPos, windowMax, ImGui::GetColorU32(ColorPickerBorderColor()), popupRounding);
|
||||
const float titleY = windowPos.y + (headerHeight - ImGui::GetTextLineHeight()) * 0.5f - 1.0f;
|
||||
drawList->AddText(ImVec2(windowPos.x + 10.0f, titleY), IM_COL32(255, 255, 255, 255), "Color");
|
||||
|
||||
const float closeButtonSize = ColorPickerCloseButtonSize();
|
||||
ImGui::SetCursorPos(ImVec2(windowSize.x - closeButtonSize - 4.0f, 5.0f));
|
||||
|
||||
@@ -108,12 +108,11 @@ inline void PopPopupWindowChrome() {
|
||||
}
|
||||
|
||||
inline void PushComboPopupWindowChrome() {
|
||||
const ImVec4 borderColor = ImGui::GetStyleColorVec4(ImGuiCol_Border);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ComboPopupWindowPadding());
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, ComboPopupRounding());
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, ComboPopupBorderSize());
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ComboPopupBackgroundColor());
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, borderColor);
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, PopupBorderColor());
|
||||
}
|
||||
|
||||
inline void PopComboPopupWindowChrome() {
|
||||
|
||||
@@ -15,6 +15,7 @@ struct PropertyLayoutSpec {
|
||||
float controlColumnStart = InspectorPropertyControlColumnStart();
|
||||
float labelControlGap = InspectorPropertyLabelControlGap();
|
||||
float controlTrailingInset = InspectorPropertyControlTrailingInset();
|
||||
float minimumRowHeight = 0.0f;
|
||||
};
|
||||
|
||||
struct PropertyLayoutMetrics {
|
||||
@@ -32,6 +33,15 @@ inline PropertyLayoutSpec MakePropertyLayout() {
|
||||
return PropertyLayoutSpec{};
|
||||
}
|
||||
|
||||
inline float CalcPropertyRowHeightForFramePadding(const ImVec2& framePadding) {
|
||||
return ImGui::GetFontSize() + framePadding.y * 2.0f + ControlRowHeightOffset();
|
||||
}
|
||||
|
||||
inline PropertyLayoutSpec WithMinimumRowHeight(PropertyLayoutSpec spec, float minimumRowHeight) {
|
||||
spec.minimumRowHeight = std::max(spec.minimumRowHeight, minimumRowHeight);
|
||||
return spec;
|
||||
}
|
||||
|
||||
inline void PushPropertyLayoutStyles() {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ControlFramePadding());
|
||||
}
|
||||
@@ -58,6 +68,15 @@ inline void AlignPropertyControlToRight(
|
||||
}
|
||||
}
|
||||
|
||||
inline void AlignPropertyControlVertically(
|
||||
const PropertyLayoutMetrics& layout,
|
||||
float height) {
|
||||
const float offset = std::max((layout.rowHeight - height) * 0.5f, 0.0f);
|
||||
if (offset > 0.0f) {
|
||||
ImGui::SetCursorPosY(layout.cursorPos.y + offset);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename DrawControlFn>
|
||||
inline auto DrawPropertyRow(
|
||||
const char* label,
|
||||
@@ -72,7 +91,9 @@ inline auto DrawPropertyRow(
|
||||
const ImVec2 rowCursorPos = ImGui::GetCursorPos();
|
||||
const ImVec2 rowScreenPos = ImGui::GetCursorScreenPos();
|
||||
const float rowWidth = std::max(ImGui::GetContentRegionAvail().x, 1.0f);
|
||||
const float rowHeight = ImGui::GetFrameHeight() + ControlRowHeightOffset();
|
||||
const float rowHeight = std::max(
|
||||
ImGui::GetFrameHeight() + ControlRowHeightOffset(),
|
||||
spec.minimumRowHeight);
|
||||
const float labelInset = std::max(spec.labelInset, 0.0f);
|
||||
const float controlColumnStart = std::clamp(
|
||||
std::max(spec.controlColumnStart, 0.0f),
|
||||
|
||||
@@ -118,6 +118,14 @@ inline ImVec2 ControlFramePadding() {
|
||||
return ImVec2(3.0f, 0.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 ScalarControlFramePadding() {
|
||||
return ImVec2(3.0f, 2.0f);
|
||||
}
|
||||
|
||||
inline float CompactIndicatorSize() {
|
||||
return 18.0f;
|
||||
}
|
||||
|
||||
inline float ControlRowHeightOffset() {
|
||||
return 1.0f;
|
||||
}
|
||||
@@ -138,12 +146,16 @@ inline float SliderValueFieldWidth() {
|
||||
return 64.0f;
|
||||
}
|
||||
|
||||
inline float ComboPreviewHeightOffset() {
|
||||
return -2.0f;
|
||||
}
|
||||
|
||||
inline float LinearSliderTrackThickness() {
|
||||
return 2.0f;
|
||||
return 3.0f;
|
||||
}
|
||||
|
||||
inline float LinearSliderGrabRadius() {
|
||||
return 5.0f;
|
||||
return 6.0f;
|
||||
}
|
||||
|
||||
inline float LinearSliderHorizontalPadding() {
|
||||
@@ -381,19 +393,43 @@ inline ImVec2 InspectorActionButtonPadding() {
|
||||
}
|
||||
|
||||
inline ImVec2 PopupWindowPadding() {
|
||||
return ImVec2(12.0f, 10.0f);
|
||||
return ImVec2(10.0f, 0.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 ComboPopupWindowPadding() {
|
||||
return ImVec2(6.0f, 1.0f);
|
||||
return ImVec2(5.0f, 0.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 ComboPopupItemSpacing() {
|
||||
return ImVec2(0.0f, 0.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 MenuSurfaceColor() {
|
||||
return ImVec4(0.98f, 0.98f, 0.98f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 MenuSurfaceHoverColor() {
|
||||
return ImVec4(0.97f, 0.97f, 0.97f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 MenuSurfaceActiveColor() {
|
||||
return ImVec4(0.955f, 0.955f, 0.955f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 MenuBorderColor() {
|
||||
return ImVec4(0.84f, 0.84f, 0.84f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 MenuTextColor() {
|
||||
return ImVec4(0.16f, 0.16f, 0.16f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 MenuTextDisabledColor() {
|
||||
return ImVec4(0.47f, 0.47f, 0.47f, 1.0f);
|
||||
}
|
||||
|
||||
inline float ComboPopupRounding() {
|
||||
return 1.0f;
|
||||
return 3.0f;
|
||||
}
|
||||
|
||||
inline float ComboPopupBorderSize() {
|
||||
@@ -401,15 +437,15 @@ inline float ComboPopupBorderSize() {
|
||||
}
|
||||
|
||||
inline ImVec4 ComboPopupBackgroundColor() {
|
||||
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
return MenuSurfaceColor();
|
||||
}
|
||||
|
||||
inline ImVec4 ComboPopupTextColor() {
|
||||
return ImVec4(0.14f, 0.14f, 0.14f, 1.0f);
|
||||
return MenuTextColor();
|
||||
}
|
||||
|
||||
inline ImVec4 ComboPopupTextDisabledColor() {
|
||||
return ImVec4(0.55f, 0.55f, 0.55f, 1.0f);
|
||||
return MenuTextDisabledColor();
|
||||
}
|
||||
|
||||
inline ImVec4 ComboPopupItemColor() {
|
||||
@@ -417,27 +453,39 @@ inline ImVec4 ComboPopupItemColor() {
|
||||
}
|
||||
|
||||
inline ImVec4 ComboPopupItemHoveredColor() {
|
||||
return ImVec4(0.0f, 0.0f, 0.0f, 0.045f);
|
||||
return MenuSurfaceHoverColor();
|
||||
}
|
||||
|
||||
inline ImVec4 ComboPopupItemActiveColor() {
|
||||
return ImVec4(0.0f, 0.0f, 0.0f, 0.08f);
|
||||
return MenuSurfaceActiveColor();
|
||||
}
|
||||
|
||||
inline ImVec4 ComboPopupCheckMarkColor() {
|
||||
return ImVec4(0.20f, 0.20f, 0.20f, 1.0f);
|
||||
return MenuTextColor();
|
||||
}
|
||||
|
||||
inline float PopupWindowRounding() {
|
||||
return 5.0f;
|
||||
return 3.0f;
|
||||
}
|
||||
|
||||
inline float PopupWindowBorderSize() {
|
||||
return 0.0f;
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
inline float PopupFrameRounding() {
|
||||
return 4.0f;
|
||||
return 3.0f;
|
||||
}
|
||||
|
||||
inline float PopupSubmenuArrowExtent() {
|
||||
return 8.0f;
|
||||
}
|
||||
|
||||
inline float PopupSubmenuArrowTrailingInset() {
|
||||
return 8.0f;
|
||||
}
|
||||
|
||||
inline float PopupSubmenuOpenOffsetX() {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline float PopupFrameBorderSize() {
|
||||
@@ -445,19 +493,19 @@ inline float PopupFrameBorderSize() {
|
||||
}
|
||||
|
||||
inline ImVec4 PopupBackgroundColor() {
|
||||
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
return MenuSurfaceColor();
|
||||
}
|
||||
|
||||
inline ImVec4 PopupBorderColor() {
|
||||
return ImVec4(0.0f, 0.0f, 0.0f, 0.10f);
|
||||
return MenuBorderColor();
|
||||
}
|
||||
|
||||
inline ImVec4 PopupTextColor() {
|
||||
return ImVec4(0.14f, 0.14f, 0.14f, 1.0f);
|
||||
return MenuTextColor();
|
||||
}
|
||||
|
||||
inline ImVec4 PopupTextDisabledColor() {
|
||||
return ImVec4(0.55f, 0.55f, 0.55f, 1.0f);
|
||||
return MenuTextDisabledColor();
|
||||
}
|
||||
|
||||
inline ImVec4 PopupItemColor() {
|
||||
@@ -465,39 +513,39 @@ inline ImVec4 PopupItemColor() {
|
||||
}
|
||||
|
||||
inline ImVec4 PopupItemHoveredColor() {
|
||||
return ImVec4(0.0f, 0.0f, 0.0f, 0.06f);
|
||||
return MenuSurfaceHoverColor();
|
||||
}
|
||||
|
||||
inline ImVec4 PopupItemActiveColor() {
|
||||
return ImVec4(0.0f, 0.0f, 0.0f, 0.10f);
|
||||
return MenuSurfaceActiveColor();
|
||||
}
|
||||
|
||||
inline ImVec4 PopupFrameColor() {
|
||||
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
return MenuSurfaceColor();
|
||||
}
|
||||
|
||||
inline ImVec4 PopupFrameHoveredColor() {
|
||||
return ImVec4(0.965f, 0.965f, 0.965f, 1.0f);
|
||||
return MenuSurfaceHoverColor();
|
||||
}
|
||||
|
||||
inline ImVec4 PopupFrameActiveColor() {
|
||||
return ImVec4(0.94f, 0.94f, 0.94f, 1.0f);
|
||||
return MenuSurfaceActiveColor();
|
||||
}
|
||||
|
||||
inline ImVec4 PopupButtonColor() {
|
||||
return ImVec4(0.95f, 0.95f, 0.95f, 1.0f);
|
||||
return MenuSurfaceColor();
|
||||
}
|
||||
|
||||
inline ImVec4 PopupButtonHoveredColor() {
|
||||
return ImVec4(0.90f, 0.90f, 0.90f, 1.0f);
|
||||
return MenuSurfaceHoverColor();
|
||||
}
|
||||
|
||||
inline ImVec4 PopupButtonActiveColor() {
|
||||
return ImVec4(0.86f, 0.86f, 0.86f, 1.0f);
|
||||
return MenuSurfaceActiveColor();
|
||||
}
|
||||
|
||||
inline ImVec4 PopupCheckMarkColor() {
|
||||
return ImVec4(0.20f, 0.20f, 0.20f, 1.0f);
|
||||
return MenuTextColor();
|
||||
}
|
||||
|
||||
inline ImVec2 AssetTileSize() {
|
||||
@@ -556,14 +604,22 @@ inline ImVec2 AssetTileIconSize() {
|
||||
return ImVec2(32.0f, 24.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 FolderAssetTileIconSize() {
|
||||
inline ImVec2 ProjectAssetTileIconSize() {
|
||||
return ImVec2(72.0f, 72.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 FolderAssetTileIconOffset() {
|
||||
inline ImVec2 ProjectAssetTileIconOffset() {
|
||||
return ImVec2(0.0f, 2.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 FolderAssetTileIconSize() {
|
||||
return ProjectAssetTileIconSize();
|
||||
}
|
||||
|
||||
inline ImVec2 FolderAssetTileIconOffset() {
|
||||
return ProjectAssetTileIconOffset();
|
||||
}
|
||||
|
||||
inline float AssetTileIconTextGap() {
|
||||
return 4.0f;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ struct AssetTileResult {
|
||||
bool hovered = false;
|
||||
ImVec2 min = ImVec2(0.0f, 0.0f);
|
||||
ImVec2 max = ImVec2(0.0f, 0.0f);
|
||||
ImVec2 labelMin = ImVec2(0.0f, 0.0f);
|
||||
ImVec2 labelMax = ImVec2(0.0f, 0.0f);
|
||||
};
|
||||
|
||||
struct AssetTileOptions {
|
||||
@@ -30,6 +32,7 @@ struct AssetTileOptions {
|
||||
ImVec2 iconSize = AssetTileIconSize();
|
||||
bool drawIdleFrame = true;
|
||||
bool drawSelectionBorder = true;
|
||||
bool drawLabel = true;
|
||||
};
|
||||
|
||||
enum class DialogActionResult {
|
||||
@@ -79,6 +82,77 @@ inline bool DrawMenuScope(const char* label, DrawContentFn&& drawContent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename DrawContentFn>
|
||||
inline bool DrawPopupSubmenuScope(const char* label, DrawContentFn&& drawContent) {
|
||||
if (!label || label[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
ImGui::PushID(label);
|
||||
const char* popupId = "##PopupSubmenu";
|
||||
const ImVec2 labelSize = ImGui::CalcTextSize(label);
|
||||
const ImVec2 rowPos = ImGui::GetCursorScreenPos();
|
||||
const float rowHeight = labelSize.y;
|
||||
const float rowWidth = ImMax(ImGui::GetContentRegionAvail().x, 1.0f);
|
||||
const bool popupOpen = ImGui::IsPopupOpen(popupId);
|
||||
|
||||
if (ImGui::Selectable(
|
||||
"##PopupSubmenuRow",
|
||||
popupOpen,
|
||||
ImGuiSelectableFlags_NoAutoClosePopups,
|
||||
ImVec2(rowWidth, rowHeight))) {
|
||||
ImGui::OpenPopup(popupId);
|
||||
}
|
||||
|
||||
const bool hovered = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
|
||||
if (hovered && !popupOpen) {
|
||||
ImGui::OpenPopup(popupId);
|
||||
}
|
||||
|
||||
const ImVec2 itemMin = ImGui::GetItemRectMin();
|
||||
const ImVec2 itemMax = ImGui::GetItemRectMax();
|
||||
const ImVec2 parentWindowPos = ImGui::GetWindowPos();
|
||||
const ImVec2 parentWindowSize = ImGui::GetWindowSize();
|
||||
const float parentWindowRight = parentWindowPos.x + parentWindowSize.x;
|
||||
const float itemHeight = itemMax.y - itemMin.y;
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
drawList->AddText(
|
||||
ImVec2(itemMin.x + ImGui::GetStyle().FramePadding.x, itemMin.y + (itemHeight - labelSize.y) * 0.5f),
|
||||
ImGui::GetColorU32(ImGuiCol_Text),
|
||||
label);
|
||||
|
||||
const float arrowExtent = PopupSubmenuArrowExtent();
|
||||
const float arrowCenterX = itemMax.x - PopupSubmenuArrowTrailingInset() - arrowExtent * 0.5f;
|
||||
const float arrowCenterY = (itemMin.y + itemMax.y) * 0.5f;
|
||||
drawList->AddTriangleFilled(
|
||||
ImVec2(arrowCenterX - arrowExtent * 0.30f, arrowCenterY - arrowExtent * 0.50f),
|
||||
ImVec2(arrowCenterX - arrowExtent * 0.30f, arrowCenterY + arrowExtent * 0.50f),
|
||||
ImVec2(arrowCenterX + arrowExtent * 0.50f, arrowCenterY),
|
||||
ImGui::GetColorU32(ImGuiCol_Text));
|
||||
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(parentWindowRight + PopupSubmenuOpenOffsetX(), rowPos.y - PopupWindowPadding().y),
|
||||
ImGuiCond_Always);
|
||||
const bool open = BeginPopup(
|
||||
popupId,
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoSavedSettings);
|
||||
if (!open) {
|
||||
ImGui::PopID();
|
||||
return false;
|
||||
}
|
||||
|
||||
drawContent();
|
||||
const bool popupHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
|
||||
if (!hovered && !popupHovered && !ImGui::IsWindowAppearing()) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
EndPopup();
|
||||
ImGui::PopID();
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename ExecuteFn>
|
||||
inline bool DrawMenuCommand(const MenuCommand& command, ExecuteFn&& execute) {
|
||||
if (command.kind == MenuCommandKind::Separator) {
|
||||
@@ -285,8 +359,8 @@ inline AssetTileResult DrawAssetTile(
|
||||
drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileIdleBorderColor()), AssetTileRounding());
|
||||
}
|
||||
|
||||
if (hovered || selected) {
|
||||
drawList->AddRectFilled(min, max, ImGui::GetColorU32(selected ? AssetTileSelectedFillColor() : AssetTileHoverFillColor()), AssetTileRounding());
|
||||
if (selected) {
|
||||
drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileSelectedFillColor()), AssetTileRounding());
|
||||
}
|
||||
if (selected && options.drawSelectionBorder) {
|
||||
drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileSelectedBorderColor()), AssetTileRounding());
|
||||
@@ -302,14 +376,19 @@ inline AssetTileResult DrawAssetTile(
|
||||
drawIcon(drawList, iconMin, iconMax);
|
||||
|
||||
const ImVec2 textPadding = AssetTileTextPadding();
|
||||
const float textAreaWidth = tileSize.x - textPadding.x * 2.0f;
|
||||
const float centeredTextX = min.x + textPadding.x + std::max(0.0f, (textAreaWidth - textSize.x) * 0.5f);
|
||||
const float textY = max.y - textSize.y - AssetTileTextPadding().y;
|
||||
ImGui::PushClipRect(ImVec2(min.x + textPadding.x, min.y), ImVec2(max.x - textPadding.x, max.y), true);
|
||||
drawList->AddText(ImVec2(centeredTextX, textY), ImGui::GetColorU32(AssetTileTextColor(selected)), label);
|
||||
ImGui::PopClipRect();
|
||||
const float labelHeight = ImGui::GetFrameHeight();
|
||||
const ImVec2 labelMin(min.x + textPadding.x, max.y - labelHeight - textPadding.y * 0.5f);
|
||||
const ImVec2 labelMax(max.x - textPadding.x, labelMin.y + labelHeight);
|
||||
if (options.drawLabel) {
|
||||
const float textAreaWidth = labelMax.x - labelMin.x;
|
||||
const float centeredTextX = labelMin.x + std::max(0.0f, (textAreaWidth - textSize.x) * 0.5f);
|
||||
const float textY = labelMin.y + (labelHeight - textSize.y) * 0.5f;
|
||||
ImGui::PushClipRect(labelMin, labelMax, true);
|
||||
drawList->AddText(ImVec2(centeredTextX, textY), ImGui::GetColorU32(AssetTileTextColor(selected)), label);
|
||||
ImGui::PopClipRect();
|
||||
}
|
||||
|
||||
return AssetTileResult{ clicked, contextRequested, openRequested, hovered, min, max };
|
||||
return AssetTileResult{ clicked, contextRequested, openRequested, hovered, min, max, labelMin, labelMax };
|
||||
}
|
||||
|
||||
template <typename DrawMenuFn>
|
||||
|
||||
@@ -33,6 +33,49 @@ void DrawProjectFolderTreePrefix(const UI::TreeNodePrefixContext& context) {
|
||||
UI::AssetIconKind::Folder);
|
||||
}
|
||||
|
||||
UI::AssetIconKind ResolveProjectAssetIconKind(const AssetItemPtr& item) {
|
||||
if (!item) {
|
||||
return UI::AssetIconKind::File;
|
||||
}
|
||||
|
||||
if (item->isFolder) {
|
||||
return UI::AssetIconKind::Folder;
|
||||
}
|
||||
|
||||
if (item->type == "Scene") {
|
||||
return UI::AssetIconKind::Scene;
|
||||
}
|
||||
|
||||
return UI::AssetIconKind::File;
|
||||
}
|
||||
|
||||
std::string GetProjectAssetDisplayName(const AssetItemPtr& item) {
|
||||
if (!item) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (item->isFolder) {
|
||||
return item->name;
|
||||
}
|
||||
|
||||
const size_t extensionPos = item->name.find_last_of('.');
|
||||
if (extensionPos == std::string::npos || extensionPos == 0) {
|
||||
return item->name;
|
||||
}
|
||||
|
||||
return item->name.substr(0, extensionPos);
|
||||
}
|
||||
|
||||
UI::AssetTileOptions MakeProjectAssetTileOptions() {
|
||||
UI::AssetTileOptions options;
|
||||
options.drawIdleFrame = false;
|
||||
options.drawSelectionBorder = false;
|
||||
options.iconOffset = UI::ProjectAssetTileIconOffset();
|
||||
options.iconSize = UI::ProjectAssetTileIconSize();
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ProjectPanel::ProjectPanel() : Panel("Project") {
|
||||
@@ -42,6 +85,97 @@ void ProjectPanel::Initialize(const std::string& projectPath) {
|
||||
m_context->GetProjectManager().Initialize(projectPath);
|
||||
}
|
||||
|
||||
void ProjectPanel::BeginAssetDragDropFrame() {
|
||||
m_assetDragDropState.Reset();
|
||||
if (const char* draggedPath = Actions::GetDraggedProjectAssetPath()) {
|
||||
m_assetDragDropState.dragging = true;
|
||||
m_assetDragDropState.sourcePath = draggedPath;
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectPanel::RegisterFolderDropTarget(IProjectManager& manager, const AssetItemPtr& folder) {
|
||||
if (!m_assetDragDropState.dragging || !folder || !folder->isFolder || !ImGui::BeginDragDropTarget()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ImGuiPayload* payload = ImGui::GetDragDropPayload();
|
||||
if (!payload || !payload->IsDataType(Actions::ProjectAssetPayloadType())) {
|
||||
ImGui::EndDragDropTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
m_assetDragDropState.hoveredTarget = true;
|
||||
const bool canDrop = Commands::CanMoveAssetToFolder(manager, m_assetDragDropState.sourcePath, folder);
|
||||
if (canDrop) {
|
||||
m_assetDragDropState.hoveredValidTarget = true;
|
||||
if (const ImGuiPayload* accepted = ImGui::AcceptDragDropPayload(
|
||||
Actions::ProjectAssetPayloadType(),
|
||||
ImGuiDragDropFlags_AcceptNoDrawDefaultRect))
|
||||
{
|
||||
if (accepted->Delivery) {
|
||||
m_assetDragDropState.deliveredSourcePath = m_assetDragDropState.sourcePath;
|
||||
m_assetDragDropState.deliveredTarget = folder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
|
||||
void ProjectPanel::FinalizeAssetDragDrop(IProjectManager& manager) {
|
||||
if (m_assetDragDropState.dragging) {
|
||||
ImGui::SetMouseCursor(
|
||||
m_assetDragDropState.hoveredValidTarget ? ImGuiMouseCursor_Arrow : ImGuiMouseCursor_NotAllowed);
|
||||
}
|
||||
|
||||
if (!m_assetDragDropState.deliveredSourcePath.empty() && m_assetDragDropState.deliveredTarget) {
|
||||
Commands::MoveAssetToFolder(
|
||||
manager,
|
||||
m_assetDragDropState.deliveredSourcePath,
|
||||
m_assetDragDropState.deliveredTarget);
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectPanel::BeginRename(const AssetItemPtr& item) {
|
||||
if (!item) {
|
||||
CancelRename();
|
||||
return;
|
||||
}
|
||||
|
||||
m_renameState.Begin(item->fullPath, GetProjectAssetDisplayName(item).c_str());
|
||||
}
|
||||
|
||||
bool ProjectPanel::CommitRename(IProjectManager& manager) {
|
||||
if (!m_renameState.IsActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string sourcePath = m_renameState.Item();
|
||||
std::string currentDisplayName;
|
||||
for (const auto& item : manager.GetCurrentItems()) {
|
||||
if (item && item->fullPath == sourcePath) {
|
||||
currentDisplayName = GetProjectAssetDisplayName(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentDisplayName.empty() && currentDisplayName == m_renameState.Buffer()) {
|
||||
CancelRename();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Commands::RenameAsset(manager, sourcePath, m_renameState.Buffer())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CancelRename();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProjectPanel::CancelRename() {
|
||||
m_renameState.Cancel();
|
||||
}
|
||||
|
||||
void ProjectPanel::Render() {
|
||||
UI::PanelWindowScope panel(m_name.c_str());
|
||||
if (!panel.IsOpen()) {
|
||||
@@ -51,6 +185,7 @@ void ProjectPanel::Render() {
|
||||
Actions::ObserveFocusedActionRoute(*m_context, EditorActionRoute::Project);
|
||||
|
||||
auto& manager = m_context->GetProjectManager();
|
||||
BeginAssetDragDropFrame();
|
||||
RenderToolbar();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ProjectBrowserSurfaceColor());
|
||||
@@ -78,7 +213,7 @@ void ProjectPanel::Render() {
|
||||
ImGui::SameLine(0.0f, 0.0f);
|
||||
RenderBrowserPane(manager);
|
||||
|
||||
Actions::DrawProjectCreateFolderDialog(*m_context, m_createFolderDialog);
|
||||
FinalizeAssetDragDrop(manager);
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
@@ -174,6 +309,8 @@ void ProjectPanel::RenderFolderTreeNode(
|
||||
folder->name.c_str(),
|
||||
nodeDefinition);
|
||||
|
||||
RegisterFolderDropTarget(manager, folder);
|
||||
|
||||
if (node.open) {
|
||||
for (const auto& child : folder->children) {
|
||||
if (!child || !child->isFolder) {
|
||||
@@ -199,9 +336,12 @@ void ProjectPanel::RenderBrowserPane(IProjectManager& manager) {
|
||||
std::vector<AssetItemPtr> visibleItems;
|
||||
const auto& items = manager.GetCurrentItems();
|
||||
const std::string search = m_searchBuffer;
|
||||
if (m_renameState.IsActive() && manager.FindCurrentItemIndex(m_renameState.Item()) < 0) {
|
||||
CancelRename();
|
||||
}
|
||||
visibleItems.reserve(items.size());
|
||||
for (const auto& item : items) {
|
||||
if (MatchesSearch(item, search)) {
|
||||
if ((m_renameState.IsActive() && item && item->fullPath == m_renameState.Item()) || MatchesSearch(item, search)) {
|
||||
visibleItems.push_back(item);
|
||||
}
|
||||
}
|
||||
@@ -231,9 +371,6 @@ void ProjectPanel::RenderBrowserPane(IProjectManager& manager) {
|
||||
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) {
|
||||
@@ -248,11 +385,6 @@ void ProjectPanel::RenderBrowserPane(IProjectManager& manager) {
|
||||
if (interaction.contextRequested) {
|
||||
pendingContextTarget = item;
|
||||
}
|
||||
if (!interaction.droppedSourcePath.empty()) {
|
||||
pendingMoveSourcePath = interaction.droppedSourcePath;
|
||||
pendingMoveTarget = item;
|
||||
break;
|
||||
}
|
||||
if (interaction.openRequested) {
|
||||
pendingOpenTarget = item;
|
||||
break;
|
||||
@@ -265,22 +397,23 @@ void ProjectPanel::RenderBrowserPane(IProjectManager& manager) {
|
||||
"No assets match the current search");
|
||||
}
|
||||
|
||||
Actions::HandleProjectBackgroundPrimaryClick(manager);
|
||||
Actions::HandleProjectBackgroundPrimaryClick(manager, m_renameState);
|
||||
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);
|
||||
}
|
||||
Actions::DrawProjectItemContextPopup(*m_context, m_itemContextMenu);
|
||||
Actions::RequestProjectEmptyContextPopup(m_emptyContextMenu);
|
||||
Actions::DrawProjectEmptyContextPopup(m_emptyContextMenu, m_createFolderDialog);
|
||||
Actions::DrawProjectEmptyContextPopup(m_emptyContextMenu, [&]() {
|
||||
if (AssetItemPtr createdFolder = Commands::CreateFolder(manager, "New Folder")) {
|
||||
BeginRename(createdFolder);
|
||||
}
|
||||
});
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::EndChild();
|
||||
@@ -305,7 +438,7 @@ void ProjectPanel::RenderBrowserHeader(IProjectManager& manager) {
|
||||
const float startY = ImGui::GetCursorPosY();
|
||||
const float availableHeight = ImGui::GetContentRegionAvail().y;
|
||||
if (availableHeight > rowHeight) {
|
||||
ImGui::SetCursorPosY(startY + (availableHeight - rowHeight) * 0.5f);
|
||||
ImGui::SetCursorPosY(startY + (availableHeight - rowHeight) * 0.5f - 1.0f);
|
||||
}
|
||||
|
||||
UI::DrawToolbarBreadcrumbs(
|
||||
@@ -326,40 +459,65 @@ ProjectPanel::AssetItemInteraction ProjectPanel::RenderAssetItem(const AssetItem
|
||||
AssetItemInteraction interaction;
|
||||
|
||||
ImGui::PushID(item ? item->fullPath.c_str() : "ProjectItem");
|
||||
const bool isDraggingThisItem = Actions::IsProjectAssetBeingDragged(item);
|
||||
const UI::AssetIconKind iconKind = item->isFolder ? UI::AssetIconKind::Folder : UI::AssetIconKind::File;
|
||||
UI::AssetTileOptions tileOptions;
|
||||
tileOptions.drawIdleFrame = false;
|
||||
tileOptions.drawSelectionBorder = false;
|
||||
if (item->isFolder) {
|
||||
tileOptions.iconOffset = UI::FolderAssetTileIconOffset();
|
||||
tileOptions.iconSize = UI::FolderAssetTileIconSize();
|
||||
}
|
||||
const bool isRenaming = item && m_renameState.IsEditing(item->fullPath);
|
||||
const bool isDraggingThisItem = !isRenaming && Actions::IsProjectAssetBeingDragged(item);
|
||||
const UI::AssetIconKind iconKind = ResolveProjectAssetIconKind(item);
|
||||
const std::string displayName = GetProjectAssetDisplayName(item);
|
||||
UI::AssetTileOptions tileOptions = MakeProjectAssetTileOptions();
|
||||
tileOptions.drawLabel = !isRenaming;
|
||||
|
||||
const UI::AssetTileResult tile = UI::DrawAssetTile(
|
||||
item->name.c_str(),
|
||||
displayName.c_str(),
|
||||
isSelected,
|
||||
isDraggingThisItem,
|
||||
[&](ImDrawList* drawList, const ImVec2& iconMin, const ImVec2& iconMax) {
|
||||
if (item && item->type == "Texture" &&
|
||||
UI::DrawTextureAssetPreview(drawList, iconMin, iconMax, item->fullPath)) {
|
||||
return;
|
||||
}
|
||||
UI::DrawAssetIcon(drawList, iconMin, iconMax, iconKind);
|
||||
},
|
||||
tileOptions);
|
||||
|
||||
if (tile.clicked) {
|
||||
interaction.clicked = true;
|
||||
if (isRenaming) {
|
||||
const ImVec2 restoreCursor = ImGui::GetCursorPos();
|
||||
ImGui::SetCursorScreenPos(tile.labelMin);
|
||||
ImGui::SetNextItemWidth(tile.labelMax.x - tile.labelMin.x);
|
||||
if (m_renameState.ConsumeFocusRequest()) {
|
||||
ImGui::SetKeyboardFocusHere();
|
||||
}
|
||||
|
||||
const bool submitted = ImGui::InputText(
|
||||
"##Rename",
|
||||
m_renameState.Buffer(),
|
||||
m_renameState.BufferSize(),
|
||||
ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll);
|
||||
const bool cancelRequested = ImGui::IsItemActive() && ImGui::IsKeyPressed(ImGuiKey_Escape);
|
||||
const bool deactivated = ImGui::IsItemDeactivated();
|
||||
ImGui::SetCursorPos(restoreCursor);
|
||||
|
||||
if (cancelRequested) {
|
||||
CancelRename();
|
||||
} else if (submitted || deactivated) {
|
||||
CommitRename(m_context->GetProjectManager());
|
||||
}
|
||||
} else {
|
||||
if (tile.clicked) {
|
||||
interaction.clicked = true;
|
||||
}
|
||||
|
||||
if (tile.contextRequested) {
|
||||
interaction.contextRequested = true;
|
||||
}
|
||||
|
||||
RegisterFolderDropTarget(m_context->GetProjectManager(), item);
|
||||
Actions::BeginProjectAssetDrag(item, iconKind);
|
||||
|
||||
if (tile.openRequested) {
|
||||
interaction.openRequested = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (tile.contextRequested) {
|
||||
interaction.contextRequested = true;
|
||||
}
|
||||
|
||||
interaction.droppedSourcePath = Actions::AcceptProjectAssetDropPayload(item);
|
||||
Actions::BeginProjectAssetDrag(item, iconKind);
|
||||
|
||||
if (tile.openRequested) {
|
||||
interaction.openRequested = true;
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
return interaction;
|
||||
}
|
||||
|
||||
@@ -15,13 +15,36 @@ public:
|
||||
void Initialize(const std::string& projectPath);
|
||||
|
||||
private:
|
||||
struct AssetDragDropState {
|
||||
bool dragging = false;
|
||||
bool hoveredTarget = false;
|
||||
bool hoveredValidTarget = false;
|
||||
std::string sourcePath;
|
||||
std::string deliveredSourcePath;
|
||||
AssetItemPtr deliveredTarget;
|
||||
|
||||
void Reset() {
|
||||
dragging = false;
|
||||
hoveredTarget = false;
|
||||
hoveredValidTarget = false;
|
||||
sourcePath.clear();
|
||||
deliveredSourcePath.clear();
|
||||
deliveredTarget.reset();
|
||||
}
|
||||
};
|
||||
|
||||
struct AssetItemInteraction {
|
||||
bool clicked = false;
|
||||
bool contextRequested = false;
|
||||
bool openRequested = false;
|
||||
std::string droppedSourcePath;
|
||||
};
|
||||
|
||||
void BeginAssetDragDropFrame();
|
||||
void RegisterFolderDropTarget(IProjectManager& manager, const AssetItemPtr& folder);
|
||||
void FinalizeAssetDragDrop(IProjectManager& manager);
|
||||
void BeginRename(const AssetItemPtr& item);
|
||||
bool CommitRename(IProjectManager& manager);
|
||||
void CancelRename();
|
||||
void RenderToolbar();
|
||||
void RenderFolderTreePane(IProjectManager& manager);
|
||||
void RenderFolderTreeNode(IProjectManager& manager, const AssetItemPtr& folder, const std::string& currentFolderPath);
|
||||
@@ -34,9 +57,10 @@ private:
|
||||
char m_searchBuffer[256] = "";
|
||||
float m_navigationWidth = UI::ProjectNavigationDefaultWidth();
|
||||
UI::TreeViewState m_folderTreeState;
|
||||
UI::TextInputPopupState<256> m_createFolderDialog;
|
||||
UI::InlineTextEditState<std::string, 256> m_renameState;
|
||||
UI::DeferredPopupState m_emptyContextMenu;
|
||||
UI::TargetedPopupState<AssetItemPtr> m_itemContextMenu;
|
||||
AssetDragDropState m_assetDragDropState;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user