diff --git a/editor/src/Actions/HierarchyActionRouter.h b/editor/src/Actions/HierarchyActionRouter.h index 00035daa..ea99105b 100644 --- a/editor/src/Actions/HierarchyActionRouter.h +++ b/editor/src/Actions/HierarchyActionRouter.h @@ -231,6 +231,46 @@ inline void HandleHierarchyItemContextRequest( itemContextMenu.RequestOpen(gameObject); } +inline void DrawHierarchyContextActions( + IEditorContext& context, + ::XCEngine::Components::GameObject* gameObject, + bool disableTopSection) { + ::XCEngine::Components::GameObject* target = disableTopSection ? nullptr : gameObject; + + DrawMenuAction(MakeDetachEntityAction(target), [&]() { + Commands::DetachEntity(context, gameObject); + }); + DrawMenuAction(MakeRenameEntityAction(target), [&]() { + RequestEntityRename(context, gameObject); + }); + DrawMenuAction(MakeDeleteEntityAction(target), [&]() { + Commands::DeleteEntity(context, gameObject->GetID()); + }); + DrawMenuSeparator(); + DrawMenuAction(MakeCopyEntityAction(target), [&]() { + Commands::CopyEntity(context, gameObject->GetID()); + }); + + ActionBinding pasteAction = MakePasteEntityAction(context); + if (disableTopSection) { + pasteAction.enabled = false; + } + DrawMenuAction(pasteAction, [&]() { + Commands::PasteEntity(context, gameObject->GetID()); + }); + DrawMenuAction(MakeDuplicateEntityAction(target), [&]() { + Commands::DuplicateEntity(context, gameObject->GetID()); + }); + DrawMenuSeparator(); + DrawHierarchyCreateActions(context, gameObject); +} + +inline void DrawHierarchyContextActions( + IEditorContext& context, + ::XCEngine::Components::GameObject* gameObject) { + DrawHierarchyContextActions(context, gameObject, false); +} + inline void DrawHierarchyBackgroundContextPopup(IEditorContext& context, UI::DeferredPopupState& backgroundContextMenu) { backgroundContextMenu.ConsumeOpenRequest("HierarchyContextMenu"); static bool s_lastBackgroundPopupOpen = false; @@ -248,34 +288,10 @@ inline void DrawHierarchyBackgroundContextPopup(IEditorContext& context, UI::Def s_lastBackgroundPopupOpen = true; } - DrawHierarchyCreateActions(context, nullptr); + DrawHierarchyContextActions(context, nullptr, true); UI::EndPopup(); } -inline void DrawHierarchyContextActions(IEditorContext& context, ::XCEngine::Components::GameObject* gameObject) { - DrawMenuAction(MakeDetachEntityAction(gameObject), [&]() { - Commands::DetachEntity(context, gameObject); - }); - DrawMenuAction(MakeRenameEntityAction(gameObject), [&]() { - RequestEntityRename(context, gameObject); - }); - DrawMenuAction(MakeDeleteEntityAction(gameObject), [&]() { - Commands::DeleteEntity(context, gameObject->GetID()); - }); - DrawMenuSeparator(); - DrawMenuAction(MakeCopyEntityAction(gameObject), [&]() { - Commands::CopyEntity(context, gameObject->GetID()); - }); - DrawMenuAction(MakePasteEntityAction(context), [&]() { - Commands::PasteEntity(context, gameObject->GetID()); - }); - DrawMenuAction(MakeDuplicateEntityAction(gameObject), [&]() { - Commands::DuplicateEntity(context, gameObject->GetID()); - }); - DrawMenuSeparator(); - DrawHierarchyCreateActions(context, gameObject); -} - inline void DrawHierarchyEntityContextPopup( IEditorContext& context, UI::TargetedPopupState<::XCEngine::Components::GameObject*>& itemContextMenu) { diff --git a/editor/src/UI/BuiltInIcons.cpp b/editor/src/UI/BuiltInIcons.cpp index 4eedd10d..71f0d058 100644 --- a/editor/src/UI/BuiltInIcons.cpp +++ b/editor/src/UI/BuiltInIcons.cpp @@ -5,6 +5,7 @@ #include "StyleTokens.h" #include +#include #include #include #include @@ -12,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -58,14 +60,56 @@ struct PreviewGpuUpload { struct PreviewDecodeJob { std::string key; std::filesystem::path filePath; + std::filesystem::path cacheFilePath; + std::string relativePathKey; + std::uint64_t fileSize = 0; + std::int64_t lastWriteTimeTicks = 0; + bool useDiskCache = false; }; struct PreviewDecodeResult { std::string key; LoadedTexturePixels pixels; + std::uint64_t fileSize = 0; + std::int64_t lastWriteTimeTicks = 0; bool success = false; }; +struct SourceFileFingerprint { + std::uint64_t fileSize = 0; + std::int64_t lastWriteTimeTicks = 0; + + bool IsValid() const { + return fileSize > 0 && lastWriteTimeTicks != 0; + } +}; + +bool operator==(const SourceFileFingerprint& lhs, const SourceFileFingerprint& rhs) { + return lhs.fileSize == rhs.fileSize && lhs.lastWriteTimeTicks == rhs.lastWriteTimeTicks; +} + +bool operator!=(const SourceFileFingerprint& lhs, const SourceFileFingerprint& rhs) { + return !(lhs == rhs); +} + +struct PreviewDiskCacheEntry { + std::filesystem::path filePath; + std::string relativePathKey; +}; + +#pragma pack(push, 1) +struct ThumbnailCacheFileHeader { + char magic[8]; + std::uint32_t version = 1; + std::uint64_t fileSize = 0; + std::int64_t lastWriteTimeTicks = 0; + std::uint32_t width = 0; + std::uint32_t height = 0; + std::uint32_t relativePathSize = 0; + std::uint32_t pixelDataSize = 0; +}; +#pragma pack(pop) + struct BuiltInIconState { ImGuiBackendBridge* backend = nullptr; ID3D12Device* device = nullptr; @@ -77,9 +121,12 @@ struct BuiltInIconState { BuiltInTexture texture; std::unique_ptr decodedPixels; std::unique_ptr pendingUpload; + SourceFileFingerprint sourceFingerprint; bool decodeQueued = false; + bool hasStaleTexture = false; bool loadFailed = false; int lastUsedFrame = -1; + int lastSourceValidationFrame = -1; }; std::unordered_map assetPreviews; std::vector> pendingIconUploads; @@ -104,6 +151,10 @@ constexpr int kMaxPreviewLoadsPerFrame = 64; constexpr size_t kMaxQueuedPreviewDecodeJobs = 64; constexpr int kMaxPreviewThumbnailExtent = 192; constexpr size_t kPreviewWorkerCount = 2; +constexpr int kPreviewSourceValidationIntervalFrames = 30; +constexpr std::uint32_t kThumbnailCacheVersion = 1; +constexpr std::array kThumbnailCacheMagic = { 'X', 'C', 'T', 'H', 'M', 'B', '1', '\0' }; +constexpr std::uint32_t kMaxThumbnailCacheRelativePathBytes = 16u * 1024u; std::filesystem::path ResolveFolderIconPath() { const std::filesystem::path exeDir(Platform::GetExecutableDirectoryUtf8()); @@ -120,12 +171,261 @@ std::filesystem::path ResolveSceneIconPath() { 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::string NormalizePathKey(const std::filesystem::path& path) { + std::wstring key = path.lexically_normal().generic_wstring(); std::transform(key.begin(), key.end(), key.begin(), ::towlower); return Platform::WideToUtf8(key); } +std::string MakeAssetPreviewKey(const std::string& filePath) { + return NormalizePathKey(std::filesystem::path(Platform::Utf8ToWide(filePath))); +} + +SourceFileFingerprint MakeSourceFingerprint( + std::uint64_t fileSize, + std::int64_t lastWriteTimeTicks) { + SourceFileFingerprint fingerprint; + fingerprint.fileSize = fileSize; + fingerprint.lastWriteTimeTicks = lastWriteTimeTicks; + return fingerprint; +} + +bool QuerySourceFileFingerprint( + const std::filesystem::path& filePath, + SourceFileFingerprint& outFingerprint) { + std::error_code ec; + if (!std::filesystem::exists(filePath, ec) || ec) { + return false; + } + + ec.clear(); + if (!std::filesystem::is_regular_file(filePath, ec) || ec) { + return false; + } + + ec.clear(); + const auto fileSize = std::filesystem::file_size(filePath, ec); + if (ec || fileSize == static_cast(-1)) { + return false; + } + + ec.clear(); + const auto lastWriteTime = std::filesystem::last_write_time(filePath, ec); + if (ec) { + return false; + } + + outFingerprint.fileSize = static_cast(fileSize); + outFingerprint.lastWriteTimeTicks = static_cast(lastWriteTime.time_since_epoch().count()); + return outFingerprint.IsValid(); +} + +std::uint64_t ComputeFnv1a64(const std::string& value) { + constexpr std::uint64_t kOffsetBasis = 14695981039346656037ull; + constexpr std::uint64_t kPrime = 1099511628211ull; + + std::uint64_t hash = kOffsetBasis; + for (unsigned char ch : value) { + hash ^= static_cast(ch); + hash *= kPrime; + } + return hash; +} + +std::string FormatHashHex(std::uint64_t value) { + static constexpr char kHexDigits[] = "0123456789abcdef"; + + std::string result(16, '0'); + for (int index = 15; index >= 0; --index) { + result[static_cast(index)] = kHexDigits[value & 0xFu]; + value >>= 4u; + } + return result; +} + +bool IsProjectRelativePath(const std::filesystem::path& relativePath) { + if (relativePath.empty()) { + return false; + } + + const std::wstring generic = relativePath.generic_wstring(); + return generic != L"." && generic != L".." && generic.rfind(L"../", 0) != 0; +} + +bool ResolvePreviewDiskCacheEntry( + const std::string& projectPath, + const std::string& filePath, + PreviewDiskCacheEntry& outEntry) { + if (projectPath.empty() || filePath.empty()) { + return false; + } + + std::error_code ec; + const std::filesystem::path rootPath = std::filesystem::weakly_canonical( + std::filesystem::path(Platform::Utf8ToWide(projectPath)), + ec); + if (ec || rootPath.empty()) { + return false; + } + + ec.clear(); + const std::filesystem::path assetPath = std::filesystem::weakly_canonical( + std::filesystem::path(Platform::Utf8ToWide(filePath)), + ec); + if (ec || assetPath.empty()) { + return false; + } + + ec.clear(); + std::filesystem::path relativePath = std::filesystem::relative(assetPath, rootPath, ec); + if (ec) { + return false; + } + + relativePath = relativePath.lexically_normal(); + if (!IsProjectRelativePath(relativePath)) { + return false; + } + + outEntry.relativePathKey = NormalizePathKey(relativePath); + const std::string hashFileName = FormatHashHex(ComputeFnv1a64(outEntry.relativePathKey)) + ".thumb"; + outEntry.filePath = + (rootPath / L".xceditor" / L"thumbs" / std::filesystem::path(Platform::Utf8ToWide(hashFileName))) + .lexically_normal(); + return true; +} + +bool ReadThumbnailCacheFile( + const std::filesystem::path& cacheFilePath, + const std::string& relativePathKey, + const SourceFileFingerprint& sourceFingerprint, + LoadedTexturePixels& outTexturePixels) { + std::ifstream stream(cacheFilePath, std::ios::binary | std::ios::in); + if (!stream.is_open()) { + return false; + } + + ThumbnailCacheFileHeader header = {}; + if (!stream.read(reinterpret_cast(&header), sizeof(header)).good()) { + return false; + } + + if (std::memcmp(header.magic, kThumbnailCacheMagic.data(), kThumbnailCacheMagic.size()) != 0 || + header.version != kThumbnailCacheVersion || + header.fileSize != sourceFingerprint.fileSize || + header.lastWriteTimeTicks != sourceFingerprint.lastWriteTimeTicks || + header.relativePathSize == 0 || + header.relativePathSize > kMaxThumbnailCacheRelativePathBytes || + header.width == 0 || + header.height == 0 || + header.width > static_cast(kMaxPreviewThumbnailExtent) || + header.height > static_cast(kMaxPreviewThumbnailExtent)) { + return false; + } + + const std::uint64_t expectedPixelBytes = + static_cast(header.width) * static_cast(header.height) * 4ull; + if (expectedPixelBytes == 0 || + expectedPixelBytes > static_cast((std::numeric_limits::max)()) || + header.pixelDataSize != expectedPixelBytes) { + return false; + } + + std::string cachedRelativePath(header.relativePathSize, '\0'); + if (!stream.read(cachedRelativePath.data(), static_cast(cachedRelativePath.size())).good()) { + return false; + } + if (cachedRelativePath != relativePathKey) { + return false; + } + + outTexturePixels.width = static_cast(header.width); + outTexturePixels.height = static_cast(header.height); + outTexturePixels.rgbaPixels.resize(static_cast(header.pixelDataSize)); + if (!stream.read( + reinterpret_cast(outTexturePixels.rgbaPixels.data()), + static_cast(outTexturePixels.rgbaPixels.size())).good()) { + outTexturePixels.rgbaPixels.clear(); + outTexturePixels.width = 0; + outTexturePixels.height = 0; + return false; + } + + return true; +} + +void RemoveDiskCacheFile(const std::filesystem::path& cacheFilePath) { + std::error_code ec; + std::filesystem::remove(cacheFilePath, ec); +} + +bool WriteThumbnailCacheFile( + const std::filesystem::path& cacheFilePath, + const std::string& relativePathKey, + const SourceFileFingerprint& sourceFingerprint, + const LoadedTexturePixels& texturePixels) { + if (cacheFilePath.empty() || + relativePathKey.empty() || + relativePathKey.size() > kMaxThumbnailCacheRelativePathBytes || + !sourceFingerprint.IsValid() || + texturePixels.width <= 0 || + texturePixels.height <= 0 || + texturePixels.rgbaPixels.empty()) { + return false; + } + + const std::uint64_t pixelDataSize = static_cast(texturePixels.rgbaPixels.size()); + if (pixelDataSize != + static_cast(texturePixels.width) * static_cast(texturePixels.height) * 4ull || + pixelDataSize > static_cast((std::numeric_limits::max)())) { + return false; + } + + std::error_code ec; + std::filesystem::create_directories(cacheFilePath.parent_path(), ec); + if (ec) { + return false; + } + + const std::filesystem::path tempPath = cacheFilePath.parent_path() / (cacheFilePath.filename().wstring() + L".tmp"); + std::ofstream stream(tempPath, std::ios::binary | std::ios::out | std::ios::trunc); + if (!stream.is_open()) { + return false; + } + + ThumbnailCacheFileHeader header = {}; + std::memcpy(header.magic, kThumbnailCacheMagic.data(), kThumbnailCacheMagic.size()); + header.version = kThumbnailCacheVersion; + header.fileSize = sourceFingerprint.fileSize; + header.lastWriteTimeTicks = sourceFingerprint.lastWriteTimeTicks; + header.width = static_cast(texturePixels.width); + header.height = static_cast(texturePixels.height); + header.relativePathSize = static_cast(relativePathKey.size()); + header.pixelDataSize = static_cast(pixelDataSize); + + const bool writeSucceeded = + stream.write(reinterpret_cast(&header), sizeof(header)).good() && + stream.write(relativePathKey.data(), static_cast(relativePathKey.size())).good() && + stream.write( + reinterpret_cast(texturePixels.rgbaPixels.data()), + static_cast(texturePixels.rgbaPixels.size())).good(); + stream.close(); + if (!writeSucceeded) { + RemoveDiskCacheFile(tempPath); + return false; + } + + ec.clear(); + std::filesystem::remove(cacheFilePath, ec); + ec.clear(); + std::filesystem::rename(tempPath, cacheFilePath, ec); + if (ec) { + RemoveDiskCacheFile(tempPath); + return false; + } + return true; +} + bool ReadFileBytes(const std::filesystem::path& filePath, std::vector& bytes) { std::ifstream stream(filePath, std::ios::binary | std::ios::ate); if (!stream.is_open()) { @@ -226,6 +526,39 @@ bool DownscalePreviewTextureIfNeeded(LoadedTexturePixels& texturePixels) { return true; } +void ResetTexture(BuiltInTexture& texture); + +void InvalidateAssetPreview(BuiltInIconState::CachedAssetPreview& preview) { + if (preview.pendingUpload) { + preview.hasStaleTexture = true; + } else { + ResetTexture(preview.texture); + preview.hasStaleTexture = false; + } + + preview.decodedPixels.reset(); + preview.decodeQueued = false; + preview.loadFailed = false; +} + +bool RefreshAssetPreviewSourceFingerprint( + const std::filesystem::path& filePath, + BuiltInIconState::CachedAssetPreview& preview) { + SourceFileFingerprint currentFingerprint; + if (!QuerySourceFileFingerprint(filePath, currentFingerprint)) { + InvalidateAssetPreview(preview); + preview.sourceFingerprint = {}; + preview.loadFailed = true; + return false; + } + + if (preview.sourceFingerprint != currentFingerprint) { + InvalidateAssetPreview(preview); + preview.sourceFingerprint = currentFingerprint; + } + return true; +} + void ResetTexture(BuiltInTexture& texture) { if (g_icons.backend && texture.cpuHandle.ptr != 0) { g_icons.backend->FreeTextureDescriptor(texture.cpuHandle, texture.gpuHandle); @@ -524,7 +857,30 @@ void PreviewWorkerMain() { PreviewDecodeResult result; result.key = std::move(job.key); - result.success = DecodeTextureFromFile(job.filePath, result.pixels); + result.fileSize = job.fileSize; + result.lastWriteTimeTicks = job.lastWriteTimeTicks; + + if (job.useDiskCache) { + result.success = ReadThumbnailCacheFile( + job.cacheFilePath, + job.relativePathKey, + MakeSourceFingerprint(job.fileSize, job.lastWriteTimeTicks), + result.pixels); + if (!result.success) { + RemoveDiskCacheFile(job.cacheFilePath); + } + } + + if (!result.success) { + result.success = DecodeTextureFromFile(job.filePath, result.pixels); + if (result.success && job.useDiskCache) { + WriteThumbnailCacheFile( + job.cacheFilePath, + job.relativePathKey, + MakeSourceFingerprint(job.fileSize, job.lastWriteTimeTicks), + result.pixels); + } + } { std::lock_guard lock(g_icons.previewQueueMutex); @@ -563,13 +919,32 @@ void StopPreviewWorkers() { g_icons.previewWorkers.clear(); } -bool QueuePreviewDecode(const std::string& key, const std::filesystem::path& filePath) { +bool QueuePreviewDecode( + const std::string& key, + const std::filesystem::path& filePath, + const SourceFileFingerprint& sourceFingerprint, + const std::string& projectPath, + const std::string& filePathUtf8) { + PreviewDiskCacheEntry diskCacheEntry; + const bool useDiskCache = ResolvePreviewDiskCacheEntry(projectPath, filePathUtf8, diskCacheEntry); + std::lock_guard lock(g_icons.previewQueueMutex); if (!g_icons.previewWorkersRunning || g_icons.pendingPreviewDecodeJobs >= kMaxQueuedPreviewDecodeJobs) { return false; } - g_icons.previewDecodeQueue.push_back(PreviewDecodeJob{ key, filePath }); + PreviewDecodeJob job; + job.key = key; + job.filePath = filePath; + job.fileSize = sourceFingerprint.fileSize; + job.lastWriteTimeTicks = sourceFingerprint.lastWriteTimeTicks; + if (useDiskCache) { + job.cacheFilePath = std::move(diskCacheEntry.filePath); + job.relativePathKey = std::move(diskCacheEntry.relativePathKey); + job.useDiskCache = true; + } + + g_icons.previewDecodeQueue.push_back(std::move(job)); ++g_icons.pendingPreviewDecodeJobs; g_icons.previewQueueEvent.notify_one(); return true; @@ -594,6 +969,10 @@ void DrainPreviewDecodeResults() { BuiltInIconState::CachedAssetPreview& preview = it->second; preview.decodeQueued = false; + if (preview.sourceFingerprint != MakeSourceFingerprint(result.fileSize, result.lastWriteTimeTicks)) { + continue; + } + if (!result.success) { preview.loadFailed = true; preview.decodedPixels.reset(); @@ -616,6 +995,10 @@ void PollCompletedUploads() { auto& preview = entry.second; if (preview.pendingUpload && completedFenceValue >= preview.pendingUpload->fenceValue) { preview.pendingUpload.reset(); + if (preview.hasStaleTexture) { + ResetTexture(preview.texture); + preview.hasStaleTexture = false; + } } } @@ -713,36 +1096,43 @@ void DrawBuiltInFileIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& drawList->AddLine(foldA, foldB, lineColor); } -BuiltInIconState::CachedAssetPreview* GetOrCreateAssetPreview(const std::string& filePath) { +BuiltInIconState::CachedAssetPreview* GetOrCreateAssetPreview( + const std::string& filePath, + const std::string& projectPath) { if (!g_icons.backend || !g_icons.device || !g_icons.commandQueue || filePath.empty()) { return nullptr; } MaintainIconRuntimeState(); + const std::filesystem::path filePathW(Platform::Utf8ToWide(filePath)); 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)))) { - if (!preview.pendingUpload) { - ResetTexture(preview.texture); - preview.decodedPixels.reset(); + const int frame = ImGui::GetFrameCount(); + if (inserted || + preview.lastSourceValidationFrame < 0 || + frame - preview.lastSourceValidationFrame >= kPreviewSourceValidationIntervalFrames) { + preview.lastSourceValidationFrame = frame; + if (!RefreshAssetPreviewSourceFingerprint(filePathW, preview)) { + return &preview; } - preview.loadFailed = true; - preview.decodeQueued = false; + } + + if (!preview.sourceFingerprint.IsValid()) { return &preview; } - if (preview.texture.IsValid()) { + if (preview.texture.IsValid() && !preview.hasStaleTexture) { if (preview.pendingUpload && preview.pendingUpload->fenceValue <= g_icons.uploadFence->GetCompletedValue()) { preview.pendingUpload.reset(); } return &preview; } - if (preview.decodedPixels) { + if (preview.decodedPixels && !preview.pendingUpload) { std::unique_ptr pendingUpload; if (!UploadTexturePixels( *g_icons.backend, @@ -766,7 +1156,6 @@ BuiltInIconState::CachedAssetPreview* GetOrCreateAssetPreview(const std::string& return &preview; } - const int frame = ImGui::GetFrameCount(); if (g_icons.lastPreviewBudgetFrame != frame) { g_icons.lastPreviewBudgetFrame = frame; g_icons.previewLoadsThisFrame = 0; @@ -775,7 +1164,7 @@ BuiltInIconState::CachedAssetPreview* GetOrCreateAssetPreview(const std::string& return &preview; } - if (QueuePreviewDecode(key, std::filesystem::path(Platform::Utf8ToWide(filePath)))) { + if (QueuePreviewDecode(key, filePathW, preview.sourceFingerprint, projectPath, filePath)) { preview.decodeQueued = true; ++g_icons.previewLoadsThisFrame; } @@ -903,9 +1292,10 @@ bool DrawTextureAssetPreview( ImDrawList* drawList, const ImVec2& min, const ImVec2& max, - const std::string& filePath) { - BuiltInIconState::CachedAssetPreview* preview = GetOrCreateAssetPreview(filePath); - if (!preview || !preview->texture.IsValid()) { + const std::string& filePath, + const std::string& projectPath) { + BuiltInIconState::CachedAssetPreview* preview = GetOrCreateAssetPreview(filePath, projectPath); + if (!preview || !preview->texture.IsValid() || preview->hasStaleTexture) { return false; } diff --git a/editor/src/UI/BuiltInIcons.h b/editor/src/UI/BuiltInIcons.h index e99e58f4..bf1766f6 100644 --- a/editor/src/UI/BuiltInIcons.h +++ b/editor/src/UI/BuiltInIcons.h @@ -31,7 +31,8 @@ bool DrawTextureAssetPreview( ImDrawList* drawList, const ImVec2& min, const ImVec2& max, - const std::string& filePath); + const std::string& filePath, + const std::string& projectPath = {}); } // namespace UI } // namespace Editor diff --git a/editor/src/UI/TreeView.h b/editor/src/UI/TreeView.h index 0aaa957a..6ac949e3 100644 --- a/editor/src/UI/TreeView.h +++ b/editor/src/UI/TreeView.h @@ -182,7 +182,7 @@ inline TreeNodeResult DrawTreeNode( } if (!options.leaf) { - DrawDisclosureArrow(drawList, arrowRect.Min, arrowRect.Max, open, ImGui::GetColorU32(ImGuiCol_Text)); + DrawDisclosureArrow(drawList, arrowRect.Min, arrowRect.Max, open, ImGui::GetColorU32(DisclosureArrowColor())); } const float baseTextX = itemMin.x + arrowSlotWidth; diff --git a/editor/src/UI/VectorControls.h b/editor/src/UI/VectorControls.h index 7e9917e9..86e5dc3d 100644 --- a/editor/src/UI/VectorControls.h +++ b/editor/src/UI/VectorControls.h @@ -77,7 +77,7 @@ inline bool DrawAxisFloatControls( for (int i = 0; i < axisCount; ++i) { ImGui::AlignTextToFramePadding(); - ImGui::TextDisabled("%s", axes[i].label); + ImGui::TextUnformatted(axes[i].label); ImGui::SameLine(); ImGui::SetNextItemWidth(itemWidth); if (ImGui::DragFloat((std::string("##") + axes[i].label).c_str(), axes[i].value, dragSpeed, 0.0f, 0.0f, "%.2f")) {