diff --git a/editor/README.md b/editor/README.md index 3977c85d..e7d4792b 100644 --- a/editor/README.md +++ b/editor/README.md @@ -1,8 +1,10 @@ # XCEditor -`editor/` 是 XCEngine 当前随仓库维护的桌面编辑器模块。它不是一套独立渲染器,而是 `D3D12` 宿主应用,用来承接引擎 `Rendering + RHI + Scene + Scripting` 主链。 +`editor/` 是 XCEngine 当前随仓库维护的桌面编辑器模块。它不是第二套独立渲染器,而是 `D3D12` 宿主应用,用来承接引擎 `Rendering + RHI + Scene + Scripting` 主链。 -当前 editor 已经具备: +如果你想了解整个工作区,先看仓库根目录的 [README.md](../README.md);如果你要直接改 editor 或相关引擎接线,再看 [AGENT.md](../AGENT.md)。 + +## 当前能力 - Scene / Game viewport 离屏渲染接入 - object-id picking 与选中描边 @@ -10,20 +12,22 @@ - 项目根目录解析与 `Project.xcproject` 加载 - `Assets + .meta + Library` 风格项目目录接入 - `ScriptComponent` 的脚本类与字段编辑入口 +- 项目脚本程序集重建与运行时重载入口 ## 当前定位 -如果你想理解当前 editor,先把它当成三层: +理解当前 editor,推荐把它拆成四层: 1. `Win32 + D3D12` 宿主窗口与 ImGui backend -2. `ViewportHostService` 对引擎渲染链路的接线 +2. `ViewportHostService` 以及 Scene View helper 对引擎渲染链路的接线 3. `panels/`、`Managers/`、`ComponentEditors/` 这些编辑器业务层 +4. `Scripting/` 对项目脚本程序集和运行时状态的桥接 -当前不应再把 editor 视为旧式“UI sample”。它已经是引擎工作区的正式入口之一。 +当前不要再把 editor 当成“旧式 UI sample”。它已经是引擎工作区的正式入口之一。 ## 构建 -推荐直接在仓库根目录构建,而不是单独进入 `editor/` 目录。 +推荐始终在仓库根目录构建,而不是单独进入 `editor/` 目录。 ### 前置要求 @@ -32,19 +36,13 @@ - CMake 3.15+ - Vulkan SDK -如果需要启用 Mono 脚本运行时,还需要: +如果要启用 Mono 脚本运行时,还需要: - .NET SDK -- `参考/Fermion/Fermion/external/mono` 下的 Mono 依赖 +- `参考/Fermion/Fermion/external/mono` 下可用的 Mono 依赖 ### 配置 -```bash -cmake -S .. -B ..\build -A x64 -``` - -更常见的做法是直接在仓库根目录运行: - ```bash cmake -S . -B build -A x64 ``` @@ -85,48 +83,99 @@ cmake --build build --config Debug --target XCEditor cmake --build build --config Debug --target xcengine_project_managed_assemblies ``` -该 target 会把程序集放到: +成功重建后,项目脚本程序集目录通常应包含: - `project/Library/ScriptAssemblies/XCEngine.ScriptCore.dll` - `project/Library/ScriptAssemblies/GameScripts.dll` - `project/Library/ScriptAssemblies/mscorlib.dll` +当前工作树如果暂时缺少 `XCEngine.ScriptCore.dll`,通常说明项目脚本程序集还没有按最新状态完成重建,而不是 editor 只需要两个程序集。 + ## 当前目录结构 ```text editor/ -├── CMakeLists.txt -├── README.md -├── resources/ -│ └── Icons/ -├── src/ -│ ├── Actions/ # 编辑器动作路由 -│ ├── Commands/ # 命令与实体操作 -│ ├── ComponentEditors/ # Inspector 组件编辑器 -│ ├── Core/ # 应用生命周期、日志、项目根解析、撤销等 -│ ├── Layers/ # EditorLayer 等高层组装 -│ ├── Layout/ -│ ├── Managers/ # SceneManager / ProjectManager -│ ├── panels/ # Hierarchy / Scene / Game / Inspector / Project / Console -│ ├── Platform/ # Win32 host、D3D12 backend 辅助 -│ ├── UI/ # ImGui bridge 与通用 widget -│ ├── Utils/ -│ ├── Viewport/ -│ │ ├── Passes/ # editor viewport overlay pass -│ │ ├── SceneViewportOverlayBuilder.* -│ │ ├── SceneViewportPicker.* -│ │ ├── SceneViewportMoveGizmo.* -│ │ ├── SceneViewportRotateGizmo.* -│ │ ├── SceneViewportScaleGizmo.* -│ │ ├── ViewportHostRenderFlowUtils.h -│ │ └── ViewportHostService.h -│ ├── Application.cpp -│ ├── Application.h -│ ├── EditorApp.rc -│ ├── Theme.cpp -│ ├── Theme.h -│ └── main.cpp -└── bin/ +|- CMakeLists.txt +|- README.md +|- bin/ # 输出目录,产物名仍为 XCEngine.exe +|- resources/ +| `- Icons/ +`- src/ + |- Actions/ # 编辑器动作路由 + |- Commands/ # 命令与实体操作 + |- ComponentEditors/ # Inspector 组件编辑器 + |- Core/ # 生命周期、日志、项目根解析、撤销、play session + |- Layers/ # EditorLayer 等高层组装 + |- Layout/ + |- Managers/ # SceneManager / ProjectManager / SelectionManager + |- panels/ + | |- ConsolePanel.cpp + | |- ConsolePanel.h + | |- GameViewPanel.cpp + | |- GameViewPanel.h + | |- HierarchyPanel.cpp + | |- HierarchyPanel.h + | |- InspectorPanel.cpp + | |- InspectorPanel.h + | |- MenuBar.cpp + | |- MenuBar.h + | |- Panel.cpp + | |- Panel.h + | |- PanelCollection.h + | |- ProjectPanel.cpp + | |- ProjectPanel.h + | |- SceneViewPanel.cpp + | |- SceneViewPanel.h + | `- ViewportPanelContent.h + |- Platform/ # Win32 host、D3D12 backend 辅助 + |- Scripting/ # 项目脚本程序集构建与运行时状态 + |- UI/ # ImGui bridge 与通用 widget + |- Utils/ + |- Viewport/ + | |- Passes/ + | | |- SceneViewportEditorOverlayPass.* + | | |- SceneViewportGridPass.* + | | `- SceneViewportSelectionOutlinePass.* + | |- IViewportHostService.h + | |- SceneViewportCameraController.h + | |- SceneViewportChrome.* + | |- SceneViewportEditorModes.h + | |- SceneViewportEditorOverlayData.h + | |- SceneViewportHudOverlay.* + | |- SceneViewportInteractionActions.* + | |- SceneViewportInteractionFrame.h + | |- SceneViewportInteractionResolver.* + | |- SceneViewportMath.h + | |- SceneViewportMoveGizmo.* + | |- SceneViewportNavigation.h + | |- SceneViewportOrientationGizmo.* + | |- SceneViewportOverlayBuilder.* + | |- SceneViewportOverlayFrameCache.* + | |- SceneViewportOverlayHandleBuilder.h + | |- SceneViewportOverlayHitTester.h + | |- SceneViewportOverlayProviders.* + | |- SceneViewportOverlaySpriteResources.* + | |- SceneViewportPassSpecs.h + | |- SceneViewportPicker.* + | |- SceneViewportRenderPlan.h + | |- SceneViewportResourcePaths.h + | |- SceneViewportRotateGizmo.* + | |- SceneViewportScaleGizmo.* + | |- SceneViewportShaderPaths.h # 兼容 include,真实 owner 已转到 ResourcePaths + | |- SceneViewportTransformGizmoCoordinator.* + | |- SceneViewportTransformGizmoFrameBuilder.h + | |- ViewportHostRenderFlowUtils.h + | |- ViewportHostRenderTargets.h + | |- ViewportHostService.h + | |- ViewportHostSurfaceUtils.h + | `- ViewportObjectIdPicker.h + |- Application.cpp + |- Application.h + |- EditorApp.rc + |- EditorResources.h + |- Theme.cpp + |- Theme.h + `- main.cpp ``` ## 关键模块 @@ -140,7 +189,7 @@ editor/ - editor 初始化与关闭 - resource root 设置 -- scripting runtime 初始化 +- scripting runtime 初始化与重载 - ImGui backend 初始化 - `ViewportHostService` 接线 @@ -148,25 +197,35 @@ editor/ - `src/Core/ProjectRootResolver.h` - `src/Utils/ProjectFileUtils.h` +- `src/Managers/ProjectManager.*` 负责: - 自动识别仓库内 `project/` - 解析 `--project` - 读写 `Project.xcproject` +- 当前 Project 面板目录与资源操作 ### Viewport - `src/Viewport/ViewportHostService.h` -- `src/Viewport/ViewportHostRenderFlowUtils.h` +- `src/Viewport/SceneViewportChrome.h` +- `src/Viewport/SceneViewportInteractionFrame.h` +- `src/Viewport/SceneViewportNavigation.h` +- `src/Viewport/SceneViewportTransformGizmoCoordinator.h` +- `src/Viewport/SceneViewportRenderPlan.h` - `src/Viewport/SceneViewportOverlayBuilder.*` +- `src/Viewport/SceneViewportOverlaySpriteResources.*` +- `src/Viewport/SceneViewportResourcePaths.h` - `src/Viewport/Passes/SceneViewportEditorOverlayPass.*` 负责: -- 组装 scene/game viewport 渲染请求 +- 组装 Scene / Game viewport 渲染请求 - 把 editor overlay 接入 `CameraRenderRequest::overlayPasses` -- object-id picking、outline、overlay pass 等 editor 视口能力 +- 处理 Scene View 的工具切换、hover / click 解析与导航输入 +- 维护 gizmo overlay state、handle hit-test、HUD overlay、sprite 资源缓存与 presentation 刷新 +- 提供 object-id picking、outline、grid 等 editor 视口能力 ### Panels @@ -181,12 +240,29 @@ editor/ ### Component Editors -`ComponentEditors/` 当前不仅负责基础组件,也已经包含 `ScriptComponent` 的 Inspector 编辑入口。 +`ComponentEditors/` 不只负责基础组件,也已经包含: + +- `MeshFilterComponentEditor` +- `MeshRendererComponentEditor` +- `ScriptComponentEditor` +- `AssetReferenceEditorUtils` + +### Scripting + +- `src/Scripting/EditorScriptAssemblyBuilder.*` +- `src/Scripting/EditorScriptAssemblyBuilderUtils.h` +- `src/Scripting/EditorScriptRuntimeStatus.h` + +负责: + +- 从项目脚本资产构建程序集 +- 汇报脚本运行时状态 +- 为 Inspector / 菜单动作提供“能否重建脚本程序集”的判断依据 ## 开发约束 - editor 是宿主,不是第二套 renderer。 -- 新的世界空间 overlay / gizmo,不应继续堆到 ImGui world draw 路径。 +- 新的世界空间 overlay / gizmo,不应继续堆到旧的 ImGui world draw 路径。 - viewport 相关问题优先检查 `engine/Rendering`、`RenderSurface` 与 `ViewportHostService` 的接线,而不是直接在 panel 里复制渲染逻辑。 - 与项目资源、脚本程序集、`.meta`、`Library` 相关的问题,不要假设 editor 仍处于“无工程状态”的旧结构。 @@ -197,7 +273,7 @@ cmake --build build --config Debug --target editor_tests cmake --build build --config Debug --target rendering_phase_regression ``` -如果改动影响脚本类发现或 Inspector 脚本字段编辑,再补: +如果改动影响脚本类发现、脚本程序集重建或 Inspector 脚本字段编辑,再补: ```bash cmake --build build --config Debug --target xcengine_project_managed_assemblies diff --git a/editor/resources/xcui_demo/xcui_demo_theme.xctheme b/editor/resources/xcui_demo/xcui_demo_theme.xctheme new file mode 100644 index 00000000..c4c1b784 --- /dev/null +++ b/editor/resources/xcui_demo/xcui_demo_theme.xctheme @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/editor/resources/xcui_demo/xcui_demo_view.xcui b/editor/resources/xcui_demo/xcui_demo_view.xcui new file mode 100644 index 00000000..b151dbf9 --- /dev/null +++ b/editor/resources/xcui_demo/xcui_demo_view.xcui @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/editor/src/Actions/EditorActions.h b/editor/src/Actions/EditorActions.h index a3310fd7..01f19f10 100644 --- a/editor/src/Actions/EditorActions.h +++ b/editor/src/Actions/EditorActions.h @@ -32,8 +32,16 @@ inline ActionBinding MakeRebuildScriptsAction(bool enabled = true) { return MakeAction("Rebuild Script Assemblies", nullptr, false, enabled); } -inline ActionBinding MakeMigrateSceneAssetReferencesAction(bool enabled = true) { - return MakeAction("Migrate Scene AssetRefs", nullptr, false, enabled); +inline ActionBinding MakeReimportSelectedAssetAction(bool enabled = true) { + return MakeAction("Reimport Selected Asset", nullptr, false, enabled); +} + +inline ActionBinding MakeReimportAllAssetsAction(bool enabled = true) { + return MakeAction("Reimport All Assets", nullptr, false, enabled); +} + +inline ActionBinding MakeClearLibraryAction(bool enabled = true) { + return MakeAction("Clear Library", nullptr, false, enabled); } inline ActionBinding MakeNewSceneAction(bool enabled = true) { diff --git a/editor/src/Actions/MainMenuActionRouter.h b/editor/src/Actions/MainMenuActionRouter.h index 3070528b..0f013715 100644 --- a/editor/src/Actions/MainMenuActionRouter.h +++ b/editor/src/Actions/MainMenuActionRouter.h @@ -38,8 +38,16 @@ inline void ExecuteRebuildScriptAssemblies(IEditorContext& context) { Commands::RebuildScriptAssemblies(context); } -inline void ExecuteMigrateSceneAssetReferences(IEditorContext& context) { - Commands::MigrateSceneAssetReferences(context); +inline void ExecuteReimportSelectedAsset(IEditorContext& context) { + Commands::ReimportSelectedAsset(context); +} + +inline void ExecuteReimportAllAssets(IEditorContext& context) { + Commands::ReimportAllAssets(context); +} + +inline void ExecuteClearLibrary(IEditorContext& context) { + Commands::ClearLibrary(context); } inline void ExecuteOpenScene(IEditorContext& context) { @@ -151,10 +159,6 @@ inline void DrawFileMenuActions(IEditorContext& context) { DrawMenuAction(MakeSaveSceneAction(canEditDocuments), [&]() { ExecuteSaveScene(context); }); DrawMenuAction(MakeSaveSceneAsAction(canEditDocuments), [&]() { ExecuteSaveSceneAs(context); }); DrawMenuSeparator(); - DrawMenuAction( - MakeMigrateSceneAssetReferencesAction(Commands::CanMigrateSceneAssetReferences(context)), - [&]() { ExecuteMigrateSceneAssetReferences(context); }); - DrawMenuSeparator(); DrawMenuAction(MakeExitAction(), [&]() { RequestEditorExit(context); }); } @@ -178,6 +182,19 @@ inline void DrawScriptsMenuActions(IEditorContext& context) { [&]() { ExecuteRebuildScriptAssemblies(context); }); } +inline void DrawAssetsMenuActions(IEditorContext& context) { + DrawMenuAction( + MakeReimportSelectedAssetAction(Commands::CanReimportSelectedAsset(context)), + [&]() { ExecuteReimportSelectedAsset(context); }); + DrawMenuAction( + MakeReimportAllAssetsAction(Commands::CanReimportAllAssets(context)), + [&]() { ExecuteReimportAllAssets(context); }); + DrawMenuSeparator(); + DrawMenuAction( + MakeClearLibraryAction(Commands::CanClearLibrary(context)), + [&]() { ExecuteClearLibrary(context); }); +} + inline void DrawViewMenuActions(IEditorContext& context) { DrawMenuAction(MakeResetLayoutAction(), [&]() { RequestDockLayoutReset(context); }); } @@ -201,6 +218,9 @@ inline void DrawMainMenuBar(IEditorContext& context, UI::DeferredPopupState& abo UI::DrawMenuScope("Edit", [&]() { DrawEditActions(context); }); + UI::DrawMenuScope("Assets", [&]() { + DrawAssetsMenuActions(context); + }); UI::DrawMenuScope("Run", [&]() { DrawRunMenuActions(context); }); diff --git a/editor/src/Application.cpp b/editor/src/Application.cpp index 5cf8644f..9985c9dc 100644 --- a/editor/src/Application.cpp +++ b/editor/src/Application.cpp @@ -145,6 +145,40 @@ bool Application::RebuildScriptingAssemblies() { #endif } +bool Application::CanReimportProjectAsset(const std::string& assetPath) const { + if (assetPath.empty()) { + return false; + } + + return ::XCEngine::Resources::ResourceManager::Get().CanReimportProjectAsset(assetPath.c_str()); +} + +bool Application::ReimportProjectAsset(const std::string& assetPath) { + if (assetPath.empty()) { + return false; + } + + auto& resourceManager = ::XCEngine::Resources::ResourceManager::Get(); + resourceManager.Initialize(); + return resourceManager.ReimportProjectAsset(assetPath.c_str()); +} + +bool Application::ReimportAllProjectAssets() { + auto& resourceManager = ::XCEngine::Resources::ResourceManager::Get(); + resourceManager.Initialize(); + return resourceManager.RebuildProjectAssetCache(); +} + +bool Application::ClearProjectLibrary() { + auto& resourceManager = ::XCEngine::Resources::ResourceManager::Get(); + resourceManager.Initialize(); + return resourceManager.ClearProjectLibraryCache(); +} + +std::string Application::GetProjectLibraryRoot() const { + return ::XCEngine::Resources::ResourceManager::Get().GetProjectLibraryRoot().CStr(); +} + bool Application::InitializeWindowRenderer(HWND hwnd) { RECT clientRect = {}; if (!GetClientRect(hwnd, &clientRect)) { @@ -227,13 +261,13 @@ void Application::RenderEditorFrame() { m_imguiBackend.BeginFrame(); m_viewportHostService.BeginFrame(); - m_layerStack.onImGuiRender(); + m_layerStack.onUIRender(); UpdateWindowTitle(); ImGui::Render(); m_windowRenderer.Render( m_imguiBackend, kClearColor, - [this](const Rendering::RenderContext& renderContext) { + [this](const Rendering::RenderContext& renderContext, const Rendering::RenderSurface&) { if (m_editorContext) { m_viewportHostService.RenderRequestedViewports(*m_editorContext, renderContext); } diff --git a/editor/src/Application.h b/editor/src/Application.h index 4490ea92..869fb028 100644 --- a/editor/src/Application.h +++ b/editor/src/Application.h @@ -7,6 +7,7 @@ #include "Viewport/ViewportHostService.h" #include +#include #include #include #include @@ -42,8 +43,14 @@ public: bool SwitchProject(const std::string& projectPath); bool ReloadScriptingRuntime(); bool RebuildScriptingAssemblies(); + bool CanReimportProjectAsset(const std::string& assetPath) const; + bool ReimportProjectAsset(const std::string& assetPath); + bool ReimportAllProjectAssets(); + bool ClearProjectLibrary(); + std::string GetProjectLibraryRoot() const; void SaveProjectState(); Rendering::RenderContext GetMainRenderContext() const { return m_windowRenderer.GetRenderContext(); } + const Rendering::RenderSurface* GetMainRenderSurface() const { return m_windowRenderer.GetCurrentRenderSurface(); } RHI::RHIDevice* GetMainRHIDevice() const { return m_windowRenderer.GetRHIDevice(); } RHI::RHISwapChain* GetMainSwapChain() const { return m_windowRenderer.GetSwapChain(); } IViewportHostService& GetViewportHostService() { return m_viewportHostService; } diff --git a/editor/src/Commands/ProjectCommands.h b/editor/src/Commands/ProjectCommands.h index 094f2c1c..29021681 100644 --- a/editor/src/Commands/ProjectCommands.h +++ b/editor/src/Commands/ProjectCommands.h @@ -12,6 +12,8 @@ #include "Utils/FileDialogUtils.h" #include "Utils/ProjectFileUtils.h" +#include + #include #include #include @@ -331,16 +333,79 @@ inline bool RebuildScriptAssemblies(IEditorContext& context) { return rebuilt; } -inline bool CanMigrateSceneAssetReferences(const IEditorContext& context) { +inline bool CanManageProjectAssetCache(const IEditorContext& context) { return IsProjectDocumentEditingAllowed(context) && !context.GetProjectPath().empty(); } -inline IProjectManager::SceneAssetReferenceMigrationReport MigrateSceneAssetReferences(IEditorContext& context) { - if (!CanMigrateSceneAssetReferences(context)) { - return {}; +inline Resources::ResourceManager& GetProjectResourceManager(IEditorContext& context) { + auto& resourceManager = Resources::ResourceManager::Get(); + resourceManager.Initialize(); + + if (!context.GetProjectPath().empty() && + std::string(resourceManager.GetResourceRoot().CStr()) != context.GetProjectPath()) { + resourceManager.SetResourceRoot(context.GetProjectPath().c_str()); } - return context.GetProjectManager().MigrateSceneAssetReferences(); + return resourceManager; +} + +inline bool CanReimportSelectedAsset(IEditorContext& context) { + if (!CanManageProjectAssetCache(context)) { + return false; + } + + const AssetItemPtr selectedItem = context.GetProjectManager().GetSelectedItem(); + return selectedItem != nullptr && + !selectedItem->isFolder && + GetProjectResourceManager(context).CanReimportProjectAsset(selectedItem->fullPath.c_str()); +} + +inline bool ReimportSelectedAsset(IEditorContext& context) { + if (!CanReimportSelectedAsset(context)) { + return false; + } + + const AssetItemPtr selectedItem = context.GetProjectManager().GetSelectedItem(); + const bool reimported = GetProjectResourceManager(context).ReimportProjectAsset(selectedItem->fullPath.c_str()); + if (reimported) { + context.GetProjectManager().RefreshCurrentFolder(); + } + + return reimported; +} + +inline bool CanReimportAllAssets(const IEditorContext& context) { + return CanManageProjectAssetCache(context); +} + +inline bool ReimportAllAssets(IEditorContext& context) { + if (!CanReimportAllAssets(context)) { + return false; + } + + const bool reimported = GetProjectResourceManager(context).RebuildProjectAssetCache(); + if (reimported) { + context.GetProjectManager().RefreshCurrentFolder(); + } + + return reimported; +} + +inline bool CanClearLibrary(const IEditorContext& context) { + return CanManageProjectAssetCache(context); +} + +inline bool ClearLibrary(IEditorContext& context) { + if (!CanClearLibrary(context)) { + return false; + } + + const bool cleared = GetProjectResourceManager(context).ClearProjectLibraryCache(); + if (cleared) { + context.GetProjectManager().RefreshCurrentFolder(); + } + + return cleared; } inline bool SwitchProject(IEditorContext& context, const std::string& projectPath) { diff --git a/editor/src/Core/EditorConsoleSink.cpp b/editor/src/Core/EditorConsoleSink.cpp index 11140be5..41fa9b40 100644 --- a/editor/src/Core/EditorConsoleSink.cpp +++ b/editor/src/Core/EditorConsoleSink.cpp @@ -6,8 +6,7 @@ namespace Debug { EditorConsoleSink* EditorConsoleSink::s_instance = nullptr; EditorConsoleSink* EditorConsoleSink::GetInstance() { - static EditorConsoleSink fallbackInstance; - return s_instance ? s_instance : &fallbackInstance; + return s_instance; } EditorConsoleSink::EditorConsoleSink() { diff --git a/editor/src/Core/IProjectManager.h b/editor/src/Core/IProjectManager.h index b4b63e7c..41ee24e0 100644 --- a/editor/src/Core/IProjectManager.h +++ b/editor/src/Core/IProjectManager.h @@ -11,13 +11,6 @@ namespace Editor { class IProjectManager { public: - struct SceneAssetReferenceMigrationReport { - size_t scannedSceneCount = 0; - size_t migratedSceneCount = 0; - size_t unchangedSceneCount = 0; - size_t failedSceneCount = 0; - }; - virtual ~IProjectManager() = default; virtual const std::vector& GetCurrentItems() const = 0; @@ -47,7 +40,6 @@ public: 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 SceneAssetReferenceMigrationReport MigrateSceneAssetReferences() = 0; virtual const std::string& GetProjectPath() const = 0; }; diff --git a/editor/src/Core/ISceneManager.h b/editor/src/Core/ISceneManager.h index f51cb374..db24bfe3 100644 --- a/editor/src/Core/ISceneManager.h +++ b/editor/src/Core/ISceneManager.h @@ -14,6 +14,30 @@ class Scene; namespace Editor { +struct SceneLoadProgressSnapshot { + std::uint64_t revision = 0; + bool inProgress = false; + bool success = false; + bool firstFramePresented = false; + bool streamingCompleted = false; + bool interactive = false; + bool startupScene = false; + std::string operation; + std::string scenePath; + std::string message; + std::uint64_t startedAtMs = 0; + std::uint64_t structureReadyAtMs = 0; + std::uint64_t firstFrameAtMs = 0; + std::uint64_t streamingCompletedAtMs = 0; + std::uint64_t interactiveAtMs = 0; + std::uint32_t currentPendingAsyncLoads = 0; + std::uint32_t peakPendingAsyncLoads = 0; + + bool HasValue() const { + return revision != 0; + } +}; + class ISceneManager { public: virtual ~ISceneManager() = default; @@ -42,6 +66,8 @@ public: virtual const std::string& GetCurrentSceneName() const = 0; virtual ::XCEngine::Components::Scene* GetScene() = 0; virtual const ::XCEngine::Components::Scene* GetScene() const = 0; + virtual SceneLoadProgressSnapshot GetSceneLoadProgress() const = 0; + virtual void NotifySceneViewportFramePresented(std::uint32_t pendingAsyncLoads) = 0; virtual SceneSnapshot CaptureSceneSnapshot() const = 0; virtual bool RestoreSceneSnapshot(const SceneSnapshot& snapshot) = 0; virtual void CreateDemoScene() = 0; diff --git a/editor/src/Layers/EditorLayer.cpp b/editor/src/Layers/EditorLayer.cpp index fd87563a..3cc17ff3 100644 --- a/editor/src/Layers/EditorLayer.cpp +++ b/editor/src/Layers/EditorLayer.cpp @@ -32,7 +32,7 @@ void EditorLayer::onEvent(void* event) { m_workspace.DispatchEvent(event); } -void EditorLayer::onImGuiRender() { +void EditorLayer::onUIRender() { m_workspace.Render(); } diff --git a/editor/src/Layers/EditorLayer.h b/editor/src/Layers/EditorLayer.h index 89a21376..270715c7 100644 --- a/editor/src/Layers/EditorLayer.h +++ b/editor/src/Layers/EditorLayer.h @@ -20,7 +20,7 @@ public: void onDetach() override; void onUpdate(float dt) override; void onEvent(void* event) override; - void onImGuiRender() override; + void onUIRender() override; void SetContext(std::shared_ptr context); diff --git a/editor/src/Layout/DockLayoutController.h b/editor/src/Layout/DockLayoutController.h index 3ab64eb2..8c32d985 100644 --- a/editor/src/Layout/DockLayoutController.h +++ b/editor/src/Layout/DockLayoutController.h @@ -6,7 +6,6 @@ #include "UI/DockHostStyle.h" #include "UI/DockTabBarChrome.h" -#include #include #include @@ -70,9 +69,7 @@ public: } if (m_layoutDirty) { - if (m_forceRebuild || !HasSavedDockLayout()) { - BuildDefaultLayout(dockspaceId, viewport->WorkSize); - } + BuildDefaultLayout(dockspaceId, viewport->WorkSize); m_layoutDirty = false; m_forceRebuild = false; } @@ -82,31 +79,6 @@ public: } private: - static bool HasSavedDockLayout() { - if (ImGui::GetCurrentContext() == nullptr) { - return false; - } - - const char* iniFilename = ImGui::GetIO().IniFilename; - if (iniFilename == nullptr || iniFilename[0] == '\0') { - return false; - } - - std::ifstream input(iniFilename, std::ios::in); - if (!input.is_open()) { - return false; - } - - std::string line; - while (std::getline(input, line)) { - if (line == "[Docking][Data]") { - return true; - } - } - - return false; - } - void BuildDefaultLayout(ImGuiID dockspaceId, const ImVec2& dockspaceSize) { ImGui::DockBuilderRemoveNode(dockspaceId); ImGui::DockBuilderAddNode(dockspaceId, m_dockspaceFlags | ImGuiDockNodeFlags_DockSpace); diff --git a/editor/src/Managers/ProjectManager.cpp b/editor/src/Managers/ProjectManager.cpp index 22d2da75..208a0188 100644 --- a/editor/src/Managers/ProjectManager.cpp +++ b/editor/src/Managers/ProjectManager.cpp @@ -1,14 +1,9 @@ #include "ProjectManager.h" -#include -#include -#include #include #include #include #include -#include -#include #include #include #include @@ -217,27 +212,6 @@ bool CanPreviewImageAssetExtension(std::wstring_view extension) { }); } -bool IsSceneAssetFile(const fs::path& path) { - if (!fs::is_regular_file(path)) { - return false; - } - - std::wstring extension = path.extension().wstring(); - std::transform(extension.begin(), extension.end(), extension.begin(), ::towlower); - return extension == L".xc"; -} - -std::string ReadFileText(const fs::path& path) { - std::ifstream input(path, std::ios::binary); - if (!input.is_open()) { - return {}; - } - - std::ostringstream stream; - stream << input.rdbuf(); - return stream.str(); -} - } // namespace const std::vector& ProjectManager::GetCurrentItems() const { @@ -522,89 +496,6 @@ bool ProjectManager::RenameItem(const std::string& sourceFullPath, const std::st } } -IProjectManager::SceneAssetReferenceMigrationReport ProjectManager::MigrateSceneAssetReferences() { - IProjectManager::SceneAssetReferenceMigrationReport report; - if (m_projectPath.empty()) { - return report; - } - - const fs::path assetsPath = fs::path(Utf8PathToWstring(m_projectPath)) / L"Assets"; - if (!fs::exists(assetsPath) || !fs::is_directory(assetsPath)) { - return report; - } - - auto& logger = ::XCEngine::Debug::Logger::Get(); - ::XCEngine::Resources::ResourceManager& resourceManager = ::XCEngine::Resources::ResourceManager::Get(); - resourceManager.Initialize(); - - const std::string previousRoot = resourceManager.GetResourceRoot().CStr(); - const bool restoreResourceRoot = previousRoot != m_projectPath; - if (restoreResourceRoot) { - resourceManager.SetResourceRoot(m_projectPath.c_str()); - } - - try { - for (const fs::directory_entry& entry : fs::recursive_directory_iterator(assetsPath)) { - if (!IsSceneAssetFile(entry.path())) { - continue; - } - - ++report.scannedSceneCount; - const fs::path scenePath = entry.path(); - const std::string scenePathUtf8 = WstringPathToUtf8(scenePath.wstring()); - - try { - const std::string before = ReadFileText(scenePath); - ::XCEngine::Components::Scene scene; - { - ::XCEngine::Resources::ResourceManager::ScopedDeferredSceneLoad deferredLoadScope(resourceManager); - scene.Load(scenePathUtf8); - } - - scene.Save(scenePathUtf8); - const std::string after = ReadFileText(scenePath); - if (after != before) { - ++report.migratedSceneCount; - } else { - ++report.unchangedSceneCount; - } - } catch (const std::exception& exception) { - ++report.failedSceneCount; - logger.Error( - ::XCEngine::Debug::LogCategory::FileSystem, - ("Failed to migrate scene asset references: " + scenePathUtf8 + " - " + exception.what()).c_str()); - } catch (...) { - ++report.failedSceneCount; - logger.Error( - ::XCEngine::Debug::LogCategory::FileSystem, - ("Failed to migrate scene asset references: " + scenePathUtf8 + " - unknown error").c_str()); - } - } - } catch (const std::exception& exception) { - logger.Error( - ::XCEngine::Debug::LogCategory::FileSystem, - ("Scene asset reference migration aborted: " + std::string(exception.what())).c_str()); - } catch (...) { - logger.Error( - ::XCEngine::Debug::LogCategory::FileSystem, - "Scene asset reference migration aborted: unknown error"); - } - - if (restoreResourceRoot) { - resourceManager.SetResourceRoot(previousRoot.c_str()); - } - - logger.Info( - ::XCEngine::Debug::LogCategory::FileSystem, - ("Scene asset reference migration finished. scanned=" + std::to_string(report.scannedSceneCount) + - " migrated=" + std::to_string(report.migratedSceneCount) + - " unchanged=" + std::to_string(report.unchangedSceneCount) + - " failed=" + std::to_string(report.failedSceneCount)).c_str()); - - RefreshCurrentFolder(); - return report; -} - AssetItemPtr ProjectManager::FindCurrentItemByPath(const std::string& fullPath) const { const int index = FindCurrentItemIndex(fullPath); if (index < 0) { diff --git a/editor/src/Managers/ProjectManager.h b/editor/src/Managers/ProjectManager.h index 505013e1..1602cd08 100644 --- a/editor/src/Managers/ProjectManager.h +++ b/editor/src/Managers/ProjectManager.h @@ -38,7 +38,6 @@ public: 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; - SceneAssetReferenceMigrationReport MigrateSceneAssetReferences() override; const std::string& GetProjectPath() const override { return m_projectPath; } diff --git a/editor/src/Managers/SceneManager.cpp b/editor/src/Managers/SceneManager.cpp index 0b9d3e84..b7bfb661 100644 --- a/editor/src/Managers/SceneManager.cpp +++ b/editor/src/Managers/SceneManager.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,48 @@ std::pair SerializeComponent(const ::XCEngine::Compone SceneManager::SceneManager(EventBus* eventBus) : m_eventBus(eventBus) {} +std::uint64_t SceneManager::GetCurrentSteadyTimeMs() { + return static_cast( + std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count()); +} + +void SceneManager::BeginSceneLoadProgress( + const std::string& operation, + const std::string& scenePath, + bool startupScene, + const std::string& message) { + m_sceneLoadProgress = {}; + m_sceneLoadProgress.revision = m_nextSceneLoadProgressRevision++; + m_sceneLoadProgress.inProgress = true; + m_sceneLoadProgress.success = false; + m_sceneLoadProgress.startupScene = startupScene; + m_sceneLoadProgress.operation = operation; + m_sceneLoadProgress.scenePath = scenePath; + m_sceneLoadProgress.message = message; + m_sceneLoadProgress.startedAtMs = GetCurrentSteadyTimeMs(); +} + +void SceneManager::MarkSceneStructureReady(const std::string& message) { + if (!m_sceneLoadProgress.HasValue()) { + return; + } + + m_sceneLoadProgress.success = true; + m_sceneLoadProgress.inProgress = true; + m_sceneLoadProgress.message = message; + m_sceneLoadProgress.structureReadyAtMs = GetCurrentSteadyTimeMs(); + m_sceneLoadProgress.currentPendingAsyncLoads = + ::XCEngine::Resources::ResourceManager::Get().GetAsyncPendingCount(); + m_sceneLoadProgress.peakPendingAsyncLoads = (std::max)( + m_sceneLoadProgress.peakPendingAsyncLoads, + m_sceneLoadProgress.currentPendingAsyncLoads); +} + +void SceneManager::ClearSceneLoadProgress() { + m_sceneLoadProgress = {}; +} + void SceneManager::SetSceneDirty(bool dirty) { m_isSceneDirty = dirty; } @@ -188,6 +231,7 @@ bool SceneManager::RestoreSceneSnapshot(const SceneSnapshot& snapshot) { m_currentScenePath.clear(); m_currentSceneName = "Untitled Scene"; SetSceneDirty(snapshot.dirty); + ClearSceneLoadProgress(); OnSceneChanged.Invoke(); if (m_eventBus) { @@ -196,6 +240,11 @@ bool SceneManager::RestoreSceneSnapshot(const SceneSnapshot& snapshot) { return true; } + BeginSceneLoadProgress( + "Restore Scene Snapshot", + snapshot.scenePath, + false, + "Restoring scene snapshot structure..."); auto scene = std::make_unique<::XCEngine::Components::Scene>(); { ::XCEngine::Resources::ResourceManager::ScopedDeferredSceneLoad deferredLoadScope; @@ -208,6 +257,7 @@ bool SceneManager::RestoreSceneSnapshot(const SceneSnapshot& snapshot) { m_currentScenePath = snapshot.scenePath; m_currentSceneName = m_scene ? m_scene->GetName() : "Untitled Scene"; SetSceneDirty(snapshot.dirty); + MarkSceneStructureReady("Scene snapshot restored. Waiting for first viewport frame..."); OnSceneChanged.Invoke(); if (m_eventBus) { @@ -238,6 +288,7 @@ void SceneManager::NewScene(const std::string& name) { m_currentScenePath.clear(); m_currentSceneName = name; SetSceneDirty(true); + ClearSceneLoadProgress(); OnSceneChanged.Invoke(); if (m_eventBus) { @@ -252,6 +303,11 @@ bool SceneManager::LoadScene(const std::string& filePath) { return false; } + BeginSceneLoadProgress( + "Load Scene", + filePath, + false, + "Restoring scene structure..."); auto scene = std::make_unique<::XCEngine::Components::Scene>(); { ::XCEngine::Resources::ResourceManager::ScopedDeferredSceneLoad deferredLoadScope; @@ -264,6 +320,7 @@ bool SceneManager::LoadScene(const std::string& filePath) { m_currentScenePath = filePath; m_currentSceneName = m_scene ? m_scene->GetName() : "Untitled Scene"; SetSceneDirty(false); + MarkSceneStructureReady("Scene structure restored. Waiting for first viewport frame..."); OnSceneChanged.Invoke(); if (m_eventBus) { @@ -310,11 +367,57 @@ bool SceneManager::SaveSceneAs(const std::string& filePath) { bool SceneManager::LoadStartupScene(const std::string& projectPath) { const std::string defaultScenePath = ResolveDefaultScenePath(projectPath); if (IsSceneFileUsable(defaultScenePath) && LoadScene(defaultScenePath)) { + if (m_sceneLoadProgress.HasValue()) { + m_sceneLoadProgress.operation = "Load Startup Scene"; + m_sceneLoadProgress.startupScene = true; + } return true; } CreateDemoScene(); - return SaveSceneAs(defaultScenePath); + const bool saved = SaveSceneAs(defaultScenePath); + if (saved) { + BeginSceneLoadProgress( + "Load Startup Scene", + defaultScenePath, + true, + "Created default startup scene."); + MarkSceneStructureReady("Created default startup scene. Waiting for first viewport frame..."); + } + return saved; +} + +void SceneManager::NotifySceneViewportFramePresented(std::uint32_t pendingAsyncLoads) { + if (!m_sceneLoadProgress.HasValue() || + !m_sceneLoadProgress.success || + m_sceneLoadProgress.streamingCompleted) { + return; + } + + const std::uint64_t nowMs = GetCurrentSteadyTimeMs(); + m_sceneLoadProgress.currentPendingAsyncLoads = pendingAsyncLoads; + m_sceneLoadProgress.peakPendingAsyncLoads = (std::max)( + m_sceneLoadProgress.peakPendingAsyncLoads, + pendingAsyncLoads); + + if (!m_sceneLoadProgress.firstFramePresented) { + m_sceneLoadProgress.firstFramePresented = true; + m_sceneLoadProgress.firstFrameAtMs = nowMs; + m_sceneLoadProgress.interactive = true; + m_sceneLoadProgress.interactiveAtMs = nowMs; + } + + if (pendingAsyncLoads == 0) { + m_sceneLoadProgress.streamingCompleted = true; + m_sceneLoadProgress.streamingCompletedAtMs = nowMs; + m_sceneLoadProgress.inProgress = false; + m_sceneLoadProgress.message = "Scene ready."; + return; + } + + m_sceneLoadProgress.inProgress = true; + m_sceneLoadProgress.message = + "Runtime streaming scene assets... (" + std::to_string(pendingAsyncLoads) + ")"; } void SceneManager::RenameEntity(::XCEngine::Components::GameObject::ID id, const std::string& newName) { @@ -365,6 +468,7 @@ void SceneManager::CreateDemoScene() { m_currentScenePath.clear(); m_currentSceneName = m_scene->GetName(); SetSceneDirty(true); + ClearSceneLoadProgress(); ::XCEngine::Components::GameObject* camera = CreateEntity("Main Camera", nullptr); camera->AddComponent<::XCEngine::Components::CameraComponent>(); diff --git a/editor/src/Managers/SceneManager.h b/editor/src/Managers/SceneManager.h index 4b1fa359..5ceacb11 100644 --- a/editor/src/Managers/SceneManager.h +++ b/editor/src/Managers/SceneManager.h @@ -67,6 +67,8 @@ public: const std::string& GetCurrentSceneName() const override { return m_currentSceneName; } ::XCEngine::Components::Scene* GetScene() override { return m_scene.get(); } const ::XCEngine::Components::Scene* GetScene() const override { return m_scene.get(); } + SceneLoadProgressSnapshot GetSceneLoadProgress() const override { return m_sceneLoadProgress; } + void NotifySceneViewportFramePresented(std::uint32_t pendingAsyncLoads) override; SceneSnapshot CaptureSceneSnapshot() const override; bool RestoreSceneSnapshot(const SceneSnapshot& snapshot) override; void CreateDemoScene() override; @@ -92,6 +94,14 @@ private: ::XCEngine::Components::GameObject::ID PasteEntityRecursive(const ClipboardData& data, ::XCEngine::Components::GameObject::ID parent); void SetSceneDirty(bool dirty); void SyncRootEntities(); + static std::uint64_t GetCurrentSteadyTimeMs(); + void BeginSceneLoadProgress( + const std::string& operation, + const std::string& scenePath, + bool startupScene, + const std::string& message); + void MarkSceneStructureReady(const std::string& message); + void ClearSceneLoadProgress(); static std::string GetScenesDirectory(const std::string& projectPath); static std::string ResolveDefaultScenePath(const std::string& projectPath); static bool IsSceneFileUsable(const std::string& filePath); @@ -104,6 +114,8 @@ private: std::string m_currentSceneName = "Untitled Scene"; bool m_isSceneDirty = false; bool m_sceneDocumentDirtyTrackingEnabled = true; + SceneLoadProgressSnapshot m_sceneLoadProgress; + std::uint64_t m_nextSceneLoadProgressRevision = 1; }; } diff --git a/editor/src/Platform/D3D12WindowRenderer.h b/editor/src/Platform/D3D12WindowRenderer.h index 04176cfa..d6af3e3f 100644 --- a/editor/src/Platform/D3D12WindowRenderer.h +++ b/editor/src/Platform/D3D12WindowRenderer.h @@ -3,6 +3,7 @@ #include "UI/ImGuiBackendBridge.h" #include +#include #include #include #include @@ -28,6 +29,8 @@ class D3D12WindowRenderer { public: static constexpr UINT kSrvDescriptorCount = 64; static constexpr uint32_t kSwapChainBufferCount = 3; + using RenderCallback = + std::function; bool Initialize(HWND hwnd, int width, int height) { Shutdown(); @@ -47,6 +50,10 @@ public: } RHI::RHIDeviceDesc deviceDesc = {}; +#ifdef _DEBUG + deviceDesc.enableDebugLayer = true; + deviceDesc.enableGPUValidation = true; +#endif if (!m_device->Initialize(deviceDesc)) { Shutdown(); return false; @@ -175,7 +182,7 @@ public: void Render( UI::ImGuiBackendBridge& imguiBackend, const float clearColor[4], - const std::function& beforeUiRender = {}) { + const RenderCallback& beforeUiRender = {}) { auto* d3d12Queue = GetD3D12CommandQueue(); auto* d3d12CommandList = GetD3D12CommandList(); if (m_swapChain == nullptr || @@ -185,16 +192,15 @@ public: return; } - if (beforeUiRender) { - beforeUiRender(GetRenderContext()); - } - const uint32_t backBufferIndex = m_swapChain->GetCurrentBackBufferIndex(); - if (backBufferIndex >= m_backBufferViews.size() || m_backBufferViews[backBufferIndex] == nullptr) { + if (backBufferIndex >= m_backBufferViews.size() || + backBufferIndex >= m_backBufferSurfaces.size() || + m_backBufferViews[backBufferIndex] == nullptr) { return; } RHI::RHIResourceView* renderTargetView = m_backBufferViews[backBufferIndex]; + Rendering::RenderSurface& renderSurface = m_backBufferSurfaces[backBufferIndex]; d3d12CommandList->TransitionBarrier( renderTargetView, RHI::ResourceStates::Present, @@ -202,6 +208,16 @@ public: d3d12CommandList->SetRenderTargets(1, &renderTargetView, nullptr); d3d12CommandList->ClearRenderTarget(renderTargetView, clearColor); + // Host-owned swapchain surfaces are handed to the callback already bound and ready for rendering. + // The callback must leave the color attachment in RenderTarget state so ImGui can render after it. + if (beforeUiRender) { + beforeUiRender(GetRenderContext(), renderSurface); + + // Viewport rendering can bind offscreen render targets on the shared command list. + // Rebind the swapchain backbuffer so the subsequent ImGui draw data lands on the main window. + d3d12CommandList->SetRenderTargets(1, &renderTargetView, nullptr); + } + ID3D12DescriptorHeap* descriptorHeaps[] = { m_srvHeap->GetDescriptorHeap() }; d3d12CommandList->SetDescriptorHeaps(1, descriptorHeaps); imguiBackend.RenderDrawData(d3d12CommandList->GetCommandList()); @@ -247,6 +263,19 @@ public: return m_swapChain; } + const Rendering::RenderSurface* GetCurrentRenderSurface() const { + if (m_swapChain == nullptr) { + return nullptr; + } + + const uint32_t backBufferIndex = m_swapChain->GetCurrentBackBufferIndex(); + if (backBufferIndex >= m_backBufferSurfaces.size()) { + return nullptr; + } + + return &m_backBufferSurfaces[backBufferIndex]; + } + Rendering::RenderContext GetRenderContext() const { Rendering::RenderContext context = {}; context.device = m_device; @@ -287,6 +316,7 @@ private: } } m_backBufferViews.clear(); + m_backBufferSurfaces.clear(); } bool RecreateBackBufferViews() { @@ -296,6 +326,7 @@ private: } m_backBufferViews.resize(kSwapChainBufferCount, nullptr); + m_backBufferSurfaces.resize(kSwapChainBufferCount); RHI::ResourceViewDesc viewDesc = {}; viewDesc.format = static_cast(RHI::Format::R8G8B8A8_UNorm); @@ -309,6 +340,15 @@ private: ReleaseBackBufferViews(); return false; } + + Rendering::RenderSurface& surface = m_backBufferSurfaces[backBufferIndex]; + surface = Rendering::RenderSurface( + static_cast(m_width), + static_cast(m_height)); + surface.SetColorAttachment(m_backBufferViews[backBufferIndex]); + surface.SetAutoTransitionEnabled(false); + surface.SetColorStateBefore(RHI::ResourceStates::RenderTarget); + surface.SetColorStateAfter(RHI::ResourceStates::RenderTarget); } return true; @@ -325,6 +365,7 @@ private: RHI::RHIDescriptorPool* m_srvPool = nullptr; RHI::D3D12DescriptorHeap* m_srvHeap = nullptr; std::vector m_backBufferViews; + std::vector m_backBufferSurfaces; UINT m_srvDescriptorSize = 0; }; diff --git a/editor/src/Viewport/ViewportHostService.h b/editor/src/Viewport/ViewportHostService.h index 7878e67b..bb0252d3 100644 --- a/editor/src/Viewport/ViewportHostService.h +++ b/editor/src/Viewport/ViewportHostService.h @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -60,6 +61,52 @@ Math::Vector3 GetSceneViewportOrientationAxisVector(SceneViewportOrientationAxis } } +std::uint64_t GetCurrentSceneLoadStatusTimeMs() { + return static_cast( + std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count()); +} + +std::uint64_t ResolveSceneLoadStatusElapsedMs(const SceneLoadProgressSnapshot& status) { + if (!status.HasValue() || status.startedAtMs == 0) { + return 0; + } + + if (status.inProgress) { + const std::uint64_t nowMs = GetCurrentSceneLoadStatusTimeMs(); + return nowMs >= status.startedAtMs ? nowMs - status.startedAtMs : 0; + } + + if (status.streamingCompletedAtMs >= status.startedAtMs && status.streamingCompletedAtMs != 0) { + return status.streamingCompletedAtMs - status.startedAtMs; + } + + if (status.firstFrameAtMs >= status.startedAtMs && status.firstFrameAtMs != 0) { + return status.firstFrameAtMs - status.startedAtMs; + } + + if (status.structureReadyAtMs >= status.startedAtMs && status.structureReadyAtMs != 0) { + return status.structureReadyAtMs - status.startedAtMs; + } + + return 0; +} + +std::string BuildViewportSceneLoadStatusText(const SceneLoadProgressSnapshot& status) { + if (!status.HasValue() || !status.inProgress || status.message.empty()) { + return {}; + } + + std::string text = status.message; + const std::uint64_t elapsedMs = ResolveSceneLoadStatusElapsedMs(status); + if (elapsedMs > 0) { + text += " ("; + text += std::to_string(elapsedMs); + text += " ms)"; + } + return text; +} + } // namespace class ViewportHostService : public IViewportHostService { @@ -568,8 +615,10 @@ private: MarkSceneViewportRenderSuccess(entry.renderTargets, requests[0]); const Core::uint32 pendingAsyncLoads = Resources::ResourceManager::Get().GetAsyncPendingCount(); - if (pendingAsyncLoads > 0) { - entry.statusText = "Loading scene assets... (" + std::to_string(pendingAsyncLoads) + ")"; + context.GetSceneManager().NotifySceneViewportFramePresented(pendingAsyncLoads); + if (entry.statusText.empty()) { + entry.statusText = BuildViewportSceneLoadStatusText( + context.GetSceneManager().GetSceneLoadProgress()); } return true; } diff --git a/editor/src/XCUIBackend/XCUIDemoRuntime.h b/editor/src/XCUIBackend/XCUIDemoRuntime.h new file mode 100644 index 00000000..fb05ecde --- /dev/null +++ b/editor/src/XCUIBackend/XCUIDemoRuntime.h @@ -0,0 +1,72 @@ +#pragma once + +#include + +#include +#include + +namespace XCEngine { +namespace UI { +struct UIRect; +struct UIPoint; +} // namespace UI +namespace Editor { +namespace XCUIBackend { + +struct XCUIDemoInputState { + UI::UIRect canvasRect = {}; + UI::UIPoint pointerPosition = {}; + bool pointerInside = false; + bool pointerPressed = false; + bool pointerReleased = false; + bool pointerDown = false; + bool windowFocused = false; + bool shortcutPressed = false; +}; + +struct XCUIDemoFrameStats { + bool documentsReady = false; + std::string statusMessage = {}; + std::size_t dependencyCount = 0; + std::size_t elementCount = 0; + std::size_t dirtyRootCount = 0; + std::size_t drawListCount = 0; + std::size_t commandCount = 0; + std::uint64_t treeGeneration = 0; + bool accentEnabled = false; + std::string lastCommandId = {}; + std::string focusedElementId = {}; + std::string hoveredElementId = {}; +}; + +struct XCUIDemoFrameResult { + UI::UIDrawData drawData = {}; + XCUIDemoFrameStats stats = {}; +}; + +class XCUIDemoRuntime { +public: + XCUIDemoRuntime(); + ~XCUIDemoRuntime(); + + XCUIDemoRuntime(XCUIDemoRuntime&& other) noexcept; + XCUIDemoRuntime& operator=(XCUIDemoRuntime&& other) noexcept; + + XCUIDemoRuntime(const XCUIDemoRuntime&) = delete; + XCUIDemoRuntime& operator=(const XCUIDemoRuntime&) = delete; + + bool ReloadDocuments(); + + const XCUIDemoFrameResult& Update(const XCUIDemoInputState& input); + const XCUIDemoFrameResult& GetFrameResult() const; + + bool TryGetElementRect(const std::string& elementId, UI::UIRect& outRect) const; + +private: + struct RuntimeState; + std::unique_ptr m_state; +}; + +} // namespace XCUIBackend +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/panels/InspectorPanel.cpp b/editor/src/panels/InspectorPanel.cpp index 2fcf9a79..bbeb2841 100644 --- a/editor/src/panels/InspectorPanel.cpp +++ b/editor/src/panels/InspectorPanel.cpp @@ -430,16 +430,11 @@ const char* SerializeBlendFactor(::XCEngine::Resources::MaterialBlendFactor fact } } -void ApplyMaterialStateToResource( +void ApplyResolvedMaterialStateToResource( const InspectorPanel::MaterialAssetState& state, + const ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>& shader, ::XCEngine::Resources::Material& material) { - const std::string shaderPath = TrimCopy(BufferToString(state.shaderPath)); - if (shaderPath.empty()) { - material.SetShader(::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>()); - } else { - material.SetShader(::XCEngine::Resources::ResourceManager::Get().Load<::XCEngine::Resources::Shader>(shaderPath.c_str())); - } - + material.SetShader(shader); material.SetShaderPass(TrimCopy(BufferToString(state.shaderPass)).c_str()); material.SetRenderQueue(state.renderQueue); material.SetRenderState(state.renderState); @@ -454,6 +449,50 @@ void ApplyMaterialStateToResource( material.RecalculateMemorySize(); } +bool TryGetLoadedShaderHandle( + const std::string& shaderPath, + ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>& outShader) { + outShader.Reset(); + if (shaderPath.empty()) { + return false; + } + + ::XCEngine::Resources::IResource* cachedResource = + ::XCEngine::Resources::ResourceManager::Get().Find(shaderPath.c_str()); + if (cachedResource == nullptr || + cachedResource->GetType() != ::XCEngine::Resources::ResourceType::Shader || + !cachedResource->IsValid()) { + return false; + } + + outShader = ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>( + static_cast<::XCEngine::Resources::Shader*>(cachedResource)); + return outShader.IsValid(); +} + +std::string BuildMaterialLoadFailureMessage(const ::XCEngine::Resources::LoadResult& result) { + if (!result.errorMessage.Empty()) { + return std::string("Material file is invalid or unavailable: ") + result.errorMessage.CStr(); + } + + return "Material file is empty or invalid. Showing default values until you save it."; +} + +std::string BuildShaderLoadFailureMessage( + const std::string& shaderPath, + const ::XCEngine::Resources::LoadResult& result) { + std::string message = "Failed to load shader for live material update"; + if (!shaderPath.empty()) { + message += ": "; + message += shaderPath; + } + if (!result.errorMessage.Empty()) { + message += " - "; + message += result.errorMessage.CStr(); + } + return message; +} + std::string BuildMaterialAssetFileText(const InspectorPanel::MaterialAssetState& state) { std::vector rootEntries; @@ -596,16 +635,130 @@ void InspectorPanel::SetSubjectMode(SubjectMode mode) { } void InspectorPanel::ClearMaterialAsset() { + InvalidateMaterialAssetAsyncState(); m_selectedMaterial.Reset(); m_materialAssetState.Reset(); } +void InspectorPanel::InvalidateMaterialAssetAsyncState() { + ++m_materialAssetLoadRevision; + ++m_materialShaderLoadRevision; + m_materialShaderLoadInFlight = false; + m_pendingMaterialShaderPath.clear(); +} + +void InspectorPanel::PopulateMaterialAssetStateFromResource(::XCEngine::Resources::Material& material) { + m_materialAssetState.shaderPath.fill('\0'); + m_materialAssetState.shaderPass.fill('\0'); + m_materialAssetState.renderQueue = material.GetRenderQueue(); + m_materialAssetState.renderState = material.GetRenderState(); + m_materialAssetState.tags.clear(); + + if (::XCEngine::Resources::Shader* shader = material.GetShader()) { + CopyToCharBuffer(std::string(shader->GetPath().CStr()), m_materialAssetState.shaderPath); + } + CopyToCharBuffer(std::string(material.GetShaderPass().CStr()), m_materialAssetState.shaderPass); + + m_materialAssetState.tags.reserve(material.GetTagCount()); + for (Core::uint32 tagIndex = 0; tagIndex < material.GetTagCount(); ++tagIndex) { + MaterialTagEditRow row; + CopyToCharBuffer(std::string(material.GetTagName(tagIndex).CStr()), row.name); + CopyToCharBuffer(std::string(material.GetTagValue(tagIndex).CStr()), row.value); + m_materialAssetState.tags.push_back(row); + } + + m_materialAssetState.loaded = true; + m_materialAssetState.dirty = false; +} + +void InspectorPanel::ApplyMaterialAssetStateToSelectedMaterial() { + if (!m_selectedMaterial.IsValid() || m_selectedMaterial.Get() == nullptr) { + return; + } + + ::XCEngine::Resources::Material& material = *m_selectedMaterial.Get(); + const std::string shaderPath = TrimCopy(BufferToString(m_materialAssetState.shaderPath)); + if (shaderPath.empty()) { + ++m_materialShaderLoadRevision; + m_materialShaderLoadInFlight = false; + m_pendingMaterialShaderPath.clear(); + ApplyResolvedMaterialStateToResource( + m_materialAssetState, + ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader>(), + material); + m_materialAssetState.errorMessage.clear(); + return; + } + + ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader> shaderHandle; + if (TryGetLoadedShaderHandle(shaderPath, shaderHandle)) { + ++m_materialShaderLoadRevision; + m_materialShaderLoadInFlight = false; + m_pendingMaterialShaderPath.clear(); + ApplyResolvedMaterialStateToResource(m_materialAssetState, shaderHandle, material); + m_materialAssetState.errorMessage.clear(); + return; + } + + if (m_materialShaderLoadInFlight && m_pendingMaterialShaderPath == shaderPath) { + m_materialAssetState.errorMessage = + std::string("Loading shader for live material update: ") + shaderPath; + return; + } + + const std::uint64_t shaderLoadRevision = ++m_materialShaderLoadRevision; + m_materialShaderLoadInFlight = true; + m_pendingMaterialShaderPath = shaderPath; + m_materialAssetState.errorMessage = + std::string("Loading shader for live material update: ") + shaderPath; + + ::XCEngine::Resources::ResourceManager::Get().LoadAsync( + shaderPath.c_str(), + ::XCEngine::Resources::ResourceType::Shader, + [this, shaderLoadRevision, requestedShaderPath = shaderPath](::XCEngine::Resources::LoadResult result) { + if (shaderLoadRevision != m_materialShaderLoadRevision || + m_subjectMode != SubjectMode::MaterialAsset || + TrimCopy(BufferToString(m_materialAssetState.shaderPath)) != requestedShaderPath) { + return; + } + + m_materialShaderLoadInFlight = false; + m_pendingMaterialShaderPath.clear(); + + if (!m_selectedMaterial.IsValid() || m_selectedMaterial.Get() == nullptr) { + m_materialAssetState.errorMessage.clear(); + return; + } + + if (!result || result.resource == nullptr) { + m_materialAssetState.errorMessage = + BuildShaderLoadFailureMessage(requestedShaderPath, result); + return; + } + + ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Shader> loadedShader( + static_cast<::XCEngine::Resources::Shader*>(result.resource)); + if (!loadedShader.IsValid()) { + m_materialAssetState.errorMessage = + BuildShaderLoadFailureMessage(requestedShaderPath, result); + return; + } + + ApplyResolvedMaterialStateToResource( + m_materialAssetState, + loadedShader, + *m_selectedMaterial.Get()); + m_materialAssetState.errorMessage.clear(); + }); +} + void InspectorPanel::ReloadMaterialAsset() { if (!m_selectedAssetItem || !m_context) { ClearMaterialAsset(); return; } + InvalidateMaterialAssetAsyncState(); m_selectedMaterial.Reset(); m_materialAssetState.Reset(); m_materialAssetState.assetFullPath = m_selectedAssetItem->fullPath; @@ -619,38 +772,41 @@ void InspectorPanel::ReloadMaterialAsset() { return; } - m_selectedMaterial = ::XCEngine::Resources::ResourceManager::Get().Load<::XCEngine::Resources::Material>( - m_materialAssetState.assetPath.c_str()); - if (!m_selectedMaterial.IsValid()) { - m_materialAssetState.errorMessage = "Material file is empty or invalid. Showing default values until you save it."; - m_materialAssetState.loaded = true; - m_materialAssetState.dirty = false; - return; - } + const std::uint64_t loadRevision = m_materialAssetLoadRevision; + const std::string requestedAssetPath = m_materialAssetState.assetPath; + m_materialAssetState.errorMessage = "Loading material asset from Library..."; - ::XCEngine::Resources::Material* material = m_selectedMaterial.Get(); - if (material == nullptr) { - m_materialAssetState.errorMessage = "Material resource is unavailable. Showing default values until you save it."; - m_materialAssetState.loaded = true; - m_materialAssetState.dirty = false; - return; - } + ::XCEngine::Resources::ResourceManager::Get().LoadAsync( + m_materialAssetState.assetPath.c_str(), + ::XCEngine::Resources::ResourceType::Material, + [this, loadRevision, requestedAssetPath](::XCEngine::Resources::LoadResult result) { + if (loadRevision != m_materialAssetLoadRevision || + m_subjectMode != SubjectMode::MaterialAsset || + m_materialAssetState.assetPath != requestedAssetPath) { + return; + } - if (::XCEngine::Resources::Shader* shader = material->GetShader()) { - CopyToCharBuffer(std::string(shader->GetPath().CStr()), m_materialAssetState.shaderPath); - } - CopyToCharBuffer(std::string(material->GetShaderPass().CStr()), m_materialAssetState.shaderPass); - m_materialAssetState.renderQueue = material->GetRenderQueue(); - m_materialAssetState.renderState = material->GetRenderState(); - m_materialAssetState.tags.reserve(material->GetTagCount()); - for (Core::uint32 tagIndex = 0; tagIndex < material->GetTagCount(); ++tagIndex) { - MaterialTagEditRow row; - CopyToCharBuffer(std::string(material->GetTagName(tagIndex).CStr()), row.name); - CopyToCharBuffer(std::string(material->GetTagValue(tagIndex).CStr()), row.value); - m_materialAssetState.tags.push_back(row); - } - m_materialAssetState.loaded = true; - m_materialAssetState.dirty = false; + if (!result || result.resource == nullptr) { + m_selectedMaterial.Reset(); + m_materialAssetState.loaded = true; + m_materialAssetState.dirty = false; + m_materialAssetState.errorMessage = BuildMaterialLoadFailureMessage(result); + return; + } + + m_selectedMaterial = ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Material>( + static_cast<::XCEngine::Resources::Material*>(result.resource)); + if (!m_selectedMaterial.IsValid() || m_selectedMaterial.Get() == nullptr) { + m_selectedMaterial.Reset(); + m_materialAssetState.loaded = true; + m_materialAssetState.dirty = false; + m_materialAssetState.errorMessage = BuildMaterialLoadFailureMessage(result); + return; + } + + m_materialAssetState.errorMessage.clear(); + PopulateMaterialAssetStateFromResource(*m_selectedMaterial.Get()); + }); } void InspectorPanel::InspectMaterialAsset(const AssetItemPtr& item) { @@ -811,16 +967,9 @@ bool InspectorPanel::SaveMaterialAsset() { return false; } - if (!m_selectedMaterial.IsValid()) { - m_selectedMaterial = ::XCEngine::Resources::ResourceManager::Get().Load<::XCEngine::Resources::Material>( - m_materialAssetState.assetPath.c_str()); - } - if (m_selectedMaterial.IsValid()) { - ApplyMaterialStateToResource(m_materialAssetState, *m_selectedMaterial.Get()); - } - m_materialAssetState.errorMessage.clear(); m_materialAssetState.dirty = false; + ApplyMaterialAssetStateToSelectedMaterial(); return true; } catch (...) { m_materialAssetState.errorMessage = "Saving material asset failed."; diff --git a/editor/src/panels/InspectorPanel.h b/editor/src/panels/InspectorPanel.h index 3ba6dc1d..85822d3b 100644 --- a/editor/src/panels/InspectorPanel.h +++ b/editor/src/panels/InspectorPanel.h @@ -76,6 +76,9 @@ private: void SetSubjectMode(SubjectMode mode); void InspectMaterialAsset(const AssetItemPtr& item); void ClearMaterialAsset(); + void InvalidateMaterialAssetAsyncState(); + void PopulateMaterialAssetStateFromResource(::XCEngine::Resources::Material& material); + void ApplyMaterialAssetStateToSelectedMaterial(); void RenderMaterialAsset(); void RenderUnsupportedAsset(); bool SaveMaterialAsset(); @@ -92,6 +95,10 @@ private: AssetItemPtr m_selectedAssetItem; ::XCEngine::Resources::ResourceHandle<::XCEngine::Resources::Material> m_selectedMaterial; MaterialAssetState m_materialAssetState; + std::uint64_t m_materialAssetLoadRevision = 0; + std::uint64_t m_materialShaderLoadRevision = 0; + bool m_materialShaderLoadInFlight = false; + std::string m_pendingMaterialShaderPath; UI::DeferredPopupState m_addComponentPopup; std::function m_deferredContextAction; }; diff --git a/editor/src/panels/ProjectPanel.cpp b/editor/src/panels/ProjectPanel.cpp index 10f4678e..0655f168 100644 --- a/editor/src/panels/ProjectPanel.cpp +++ b/editor/src/panels/ProjectPanel.cpp @@ -4,6 +4,7 @@ #include "ProjectPanel.h" #include "Core/IEditorContext.h" #include "Core/IProjectManager.h" +#include "Core/ISceneManager.h" #include "Core/AssetItem.h" #include "Platform/Win32Utf8.h" #include "Utils/ProjectFileUtils.h" @@ -60,6 +61,51 @@ ImVec4 ResolveImportStatusColor(const XCEngine::Resources::AssetImportService::I : XCEngine::Editor::UI::ConsoleErrorColor(); } +std::uint64_t ResolveSceneLoadElapsedMs(const XCEngine::Editor::SceneLoadProgressSnapshot& status) { + if (!status.HasValue() || status.startedAtMs == 0) { + return 0; + } + + if (status.inProgress) { + const auto nowMs = static_cast( + std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count()); + return nowMs >= status.startedAtMs ? nowMs - status.startedAtMs : 0; + } + + if (status.streamingCompletedAtMs >= status.startedAtMs && status.streamingCompletedAtMs != 0) { + return status.streamingCompletedAtMs - status.startedAtMs; + } + + if (status.firstFrameAtMs >= status.startedAtMs && status.firstFrameAtMs != 0) { + return status.firstFrameAtMs - status.startedAtMs; + } + + if (status.structureReadyAtMs >= status.startedAtMs && status.structureReadyAtMs != 0) { + return status.structureReadyAtMs - status.startedAtMs; + } + + return 0; +} + +std::uint64_t ResolveSceneLoadMarkerDeltaMs(std::uint64_t startMs, std::uint64_t endMs) { + return endMs >= startMs && startMs != 0 ? endMs - startMs : 0; +} + +ImVec4 ResolveSceneLoadStatusColor(const XCEngine::Editor::SceneLoadProgressSnapshot& status) { + if (!status.HasValue()) { + return XCEngine::Editor::UI::ConsoleSecondaryTextColor(); + } + + if (status.inProgress) { + return XCEngine::Editor::UI::ConsoleWarningColor(); + } + + return status.success + ? XCEngine::Editor::UI::ConsoleSecondaryTextColor() + : XCEngine::Editor::UI::ConsoleErrorColor(); +} + std::string BuildImportStatusText(const XCEngine::Resources::AssetImportService::ImportStatusSnapshot& status) { if (!status.HasValue()) { return "Library: ready"; @@ -76,6 +122,32 @@ std::string BuildImportStatusText(const XCEngine::Resources::AssetImportService: return text; } +std::string BuildSceneLoadStatusText(const XCEngine::Editor::SceneLoadProgressSnapshot& status) { + if (!status.HasValue()) { + return "Scene: ready"; + } + + std::string text = "Scene: "; + if (!status.success) { + text += status.message.empty() ? "failed" : status.message; + return text; + } + + if (status.inProgress) { + text += status.message.empty() ? "loading..." : status.message; + } else { + text += "ready"; + } + + const std::uint64_t elapsedMs = ResolveSceneLoadElapsedMs(status); + if (elapsedMs > 0) { + text += " ("; + text += std::to_string(elapsedMs); + text += " ms)"; + } + return text; +} + std::string BuildImportStatusTooltipText(const XCEngine::Resources::AssetImportService::ImportStatusSnapshot& status) { if (!status.HasValue()) { return "No import operations have been recorded in this session."; @@ -107,6 +179,68 @@ std::string BuildImportStatusTooltipText(const XCEngine::Resources::AssetImportS return tooltip; } +std::string BuildSceneLoadStatusTooltipText(const XCEngine::Editor::SceneLoadProgressSnapshot& status) { + if (!status.HasValue()) { + return "No scene load operations have been recorded in this session."; + } + + std::string tooltip; + tooltip += "Operation: "; + tooltip += status.operation.empty() ? "Scene Load" : status.operation; + if (!status.scenePath.empty()) { + tooltip += "\nScene: "; + tooltip += status.scenePath; + } + tooltip += "\nState: "; + tooltip += status.inProgress ? "Running" : (status.success ? "Succeeded" : "Failed"); + + const std::uint64_t structureMs = + ResolveSceneLoadMarkerDeltaMs(status.startedAtMs, status.structureReadyAtMs); + if (structureMs > 0) { + tooltip += "\nStructure restore: "; + tooltip += std::to_string(structureMs); + tooltip += " ms"; + } + + const std::uint64_t firstFrameMs = + ResolveSceneLoadMarkerDeltaMs(status.startedAtMs, status.firstFrameAtMs); + if (firstFrameMs > 0) { + tooltip += "\nFirst viewport frame: "; + tooltip += std::to_string(firstFrameMs); + tooltip += " ms"; + } + + const std::uint64_t interactiveMs = + ResolveSceneLoadMarkerDeltaMs(status.startedAtMs, status.interactiveAtMs); + if (interactiveMs > 0) { + tooltip += "\nFirst interactive frame: "; + tooltip += std::to_string(interactiveMs); + tooltip += " ms"; + } + + const std::uint64_t streamingMs = + ResolveSceneLoadMarkerDeltaMs(status.startedAtMs, status.streamingCompletedAtMs); + if (streamingMs > 0) { + tooltip += "\nRuntime streaming complete: "; + tooltip += std::to_string(streamingMs); + tooltip += " ms"; + } + + if (status.peakPendingAsyncLoads > 0) { + tooltip += "\nPeak async loads: "; + tooltip += std::to_string(status.peakPendingAsyncLoads); + } + if (status.inProgress && status.currentPendingAsyncLoads > 0) { + tooltip += "\nPending async loads: "; + tooltip += std::to_string(status.currentPendingAsyncLoads); + } + if (!status.message.empty()) { + tooltip += "\nStatus: "; + tooltip += status.message; + } + return tooltip; +} + template void QueueDeferredAction(std::function& pendingAction, Fn&& fn) { if (!pendingAction) { @@ -457,8 +591,12 @@ void ProjectPanel::Render() { void ProjectPanel::RenderToolbar() { const auto importStatus = ::XCEngine::Resources::ResourceManager::Get().GetProjectAssetImportStatus(); + const SceneLoadProgressSnapshot sceneLoadStatus = + m_context != nullptr ? m_context->GetSceneManager().GetSceneLoadProgress() : SceneLoadProgressSnapshot{}; const std::string importStatusText = BuildImportStatusText(importStatus); const std::string importStatusTooltip = BuildImportStatusTooltipText(importStatus); + const std::string sceneLoadStatusText = BuildSceneLoadStatusText(sceneLoadStatus); + const std::string sceneLoadStatusTooltip = BuildSceneLoadStatusTooltipText(sceneLoadStatus); UI::PanelToolbarScope toolbar( "ProjectToolbar", @@ -473,8 +611,9 @@ void ProjectPanel::RenderToolbar() { } ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0.0f, 0.0f)); - if (ImGui::BeginTable("##ProjectToolbarLayout", 2, ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_SizingStretchProp)) { + if (ImGui::BeginTable("##ProjectToolbarLayout", 3, ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("##ImportStatus", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("##SceneLoadStatus", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("##Search", ImGuiTableColumnFlags_WidthFixed, 220.0f); ImGui::TableNextRow(); @@ -492,6 +631,19 @@ void ProjectPanel::RenderToolbar() { ImGui::EndTooltip(); } + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::PushStyleColor(ImGuiCol_Text, ResolveSceneLoadStatusColor(sceneLoadStatus)); + ImGui::TextUnformatted(sceneLoadStatusText.c_str()); + ImGui::PopStyleColor(); + if (ImGui::IsItemHovered() && !sceneLoadStatusTooltip.empty()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 36.0f); + ImGui::TextUnformatted(sceneLoadStatusTooltip.c_str()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + ImGui::TableNextColumn(); UI::ToolbarSearchField("##Search", "Search assets", m_searchBuffer, sizeof(m_searchBuffer)); diff --git a/editor/src/panels/ViewportPanelContent.h b/editor/src/panels/ViewportPanelContent.h index a5606c7b..24ea337f 100644 --- a/editor/src/panels/ViewportPanelContent.h +++ b/editor/src/panels/ViewportPanelContent.h @@ -83,7 +83,9 @@ inline void RenderViewportInteractionSurface( result.clickedMiddle = ImGui::IsItemClicked(ImGuiMouseButton_Middle); } -inline ViewportPanelContentResult RenderViewportPanelContent(IEditorContext& context, EditorViewportKind kind) { +inline ViewportPanelContentResult RenderViewportPanelContent( + IEditorContext& context, + EditorViewportKind kind) { ViewportPanelContentResult result = {}; UI::CollapsePanelSectionSpacing(); result.availableSize = ImGui::GetContentRegionAvail(); diff --git a/engine/include/XCEngine/Core/Layer.h b/engine/include/XCEngine/Core/Layer.h index 8473bdde..31ed0746 100644 --- a/engine/include/XCEngine/Core/Layer.h +++ b/engine/include/XCEngine/Core/Layer.h @@ -14,7 +14,7 @@ public: virtual void onDetach() {} virtual void onUpdate(float dt) {} virtual void onEvent(void* event) {} - virtual void onImGuiRender() {} + virtual void onUIRender() {} const std::string& getName() const { return m_name; } diff --git a/engine/include/XCEngine/Core/LayerStack.h b/engine/include/XCEngine/Core/LayerStack.h index f331cfff..535f547e 100644 --- a/engine/include/XCEngine/Core/LayerStack.h +++ b/engine/include/XCEngine/Core/LayerStack.h @@ -20,7 +20,7 @@ public: void onUpdate(float dt); void onEvent(void* event); - void onImGuiRender(); + void onUIRender(); void onAttach(); void onDetach(); @@ -78,9 +78,9 @@ inline void LayerStack::onEvent(void* event) { } } -inline void LayerStack::onImGuiRender() { +inline void LayerStack::onUIRender() { for (auto& layer : m_layers) { - layer->onImGuiRender(); + layer->onUIRender(); } }