diff --git a/editor/AGENTS.md b/editor/AGENTS.md index a68cce0b..b73c39ed 100644 --- a/editor/AGENTS.md +++ b/editor/AGENTS.md @@ -12,18 +12,19 @@ - UI widget / shell / workspace 代码优先保持 model/state/request/frame/result 风格。新增行为要能被 `tests/UI/Editor/unit` 以纯状态方式测试。 - scene/project 的用户操作应通过 runtime 或 command route 进入,不要在 draw/append 阶段直接改 scene 或文件系统。 - scene 渲染私有逻辑不要塞进 panel 或 shell。新增渲染能力时先判断它属于 engine `Rendering/RHI`、editor viewport pass bundle,还是 UI overlay。 +- 运行时路径以 `app/Core/Environment/EditorRuntimePaths.h` 为显式契约。workspace、executable、resource、project、capture 根路径由 bootstrap 一次性解析并向下传递;不要在下游重新从 repo root、当前工作目录或文档文件推导运行环境。 - 资源路径、图标、shader、截图输出都应走明确服务或 host 接口。不要硬编码从当前工作目录猜路径;手动验证截图不得写回 source tree。 ## 当前情况 -- `editor/AGENTS.md` 本身被 `app/Bootstrap/Application.cpp` 的 `HasEditorRepoMarkers()` 用作仓库根定位标记之一。不要重命名、删除或移动它;如果根定位规则变了,同时更新本文。 +- `editor/AGENTS.md` 只是协作说明,不是运行时定位标记。`Application::ResolveRuntimePaths()` 当前用稳定工程标记(根 `CMakeLists.txt`、`editor/resources`、`project`)定位开发 workspace,并填充 `EditorRuntimePaths`。 - 顶层 `CMakeLists.txt` 默认启用 `XCENGINE_BUILD_XCUI_EDITOR_CORE` 和 `XCENGINE_BUILD_XCUI_EDITOR_APP`。构建 core/app 时必须启用 `XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT`。 - `XCEditor` 目标输出名是 `XCEngine.exe`,Debug 产物位于 `build/editor/Debug/XCEngine.exe`。 -- 当前应用没有命令行项目选择流程;`EditorContext` 将项目根固定为 `/project`,不要沿用旧文档里的 `--project ` 说法。 +- 当前应用没有命令行项目选择流程;默认项目根由 `EditorRuntimePaths.projectRoot` 提供,开发 workspace 下通常是 `/project`。不要沿用旧文档里的 `--project ` 说法。 - 当前面板 ID 为 `hierarchy`、`scene`、`game`、`inspector`、`console`、`project`,由 `EditorProductManifest.*` 声明并驱动注册。 - 当前 `game` panel 是 viewport shell,但 renderer owner 是 placeholder,不是正式 Game runtime。它会显示 `Game view runtime is not implemented`,不要假设 Game view 已完整实现。 - 当前 command 事实:`file.exit` 已绑定退出;`edit.*` 通过 active route 分发到 hierarchy/project/scene/inspector;`assets.*` 通过 project route 分发;多数 `file.*`、`run.*`、`scripts.*`、`help.about` 仍只是菜单/命令 surface,未拥有完整 host owner。 -- `EditorContext` 是产品级状态聚合点:`EditorSession`、project runtime、scene runtime、selection、command focus、utility window request、shortcut manager 和 host command bridge 都在这里串接。 +- `EditorContext` 是产品级状态聚合点:`EditorSession`、project runtime、scene runtime、selection、command focus、utility window request、shortcut manager 和 host command bridge 都在这里串接;初始化只接收 `EditorRuntimePaths`,不再自行拼接 repo/project/resource 路径。 - `UIEditorWorkspaceController` 管单窗口 workspace model/session;`EditorWindowSystem` 管多窗口 workspace set。跨窗口 detach、close、update 必须经过 synchronization plan。 - `EditorWorkspacePanelRuntimeSet` 托管产品面板生命周期。新增 panel 时先改 `EditorProductManifest.*`,再按需要调整 `BuildEditorWorkspaceModel()` 的默认布局和测试;不要再手工在 registry / menu / runtime set / viewport host 多处分别补定义。 - `EditorSelectionService` 是 hierarchy/project/inspector/scene viewport 之间的选择同步核心。不要在单个 panel 内维护另一套长期选择真相。 @@ -41,6 +42,7 @@ - `include/XCEditor/Windowing`:多窗口 workspace 状态、同步计划和 presentation policy。 - `src/**`:对应公共头的实现。保持偏纯函数/状态机风格。 - `app/Core`:产品级 contracts、session、command focus、selection、panel services、scene/project/viewport/windowing 接口。 +- `app/Core/Environment`:运行时路径契约。bootstrap 负责解析 `EditorRuntimePaths`,core/composition/features/rendering 只消费显式路径。 - `app/Core/Product`:产品 manifest。这里定义正式 panel 集、route 归属、runtime owner 和 viewport renderer owner。 - `app/Composition`:装配编辑器 shell。`EditorContext` 拥有 session、project runtime、scene runtime、selection、command bridge;`EditorShellRuntime` 驱动 shell interaction、hosted panels 和 viewport runtime。 - `app/Features`:产品面板与场景视图工具。 @@ -55,6 +57,7 @@ wWinMain -> RunXCEditor -> Application::Initialize / Run +-> Application::ResolveRuntimePaths -> EditorContext::Initialize -> BuildEditorApplicationShellAsset -> EditorWindowSystem::BootstrapPrimaryWindow diff --git a/editor/app/Bootstrap/Application.cpp b/editor/app/Bootstrap/Application.cpp index 455d121e..56224c72 100644 --- a/editor/app/Bootstrap/Application.cpp +++ b/editor/app/Bootstrap/Application.cpp @@ -35,11 +35,48 @@ constexpr const wchar_t* kWindowTitle = L"Main Scene * - Main.xx - XCEngine Edit constexpr DWORD kBorderlessWindowStyle = WS_POPUP | WS_THICKFRAME; constexpr int kDefaultSmokeTestDurationSeconds = 12; -bool HasEditorRepoMarkers(const std::filesystem::path& root) { - return std::filesystem::exists(root / "editor" / "AGENTS.md") && +bool HasEditorWorkspaceMarkers(const std::filesystem::path& root) { + return std::filesystem::exists(root / "CMakeLists.txt") && + std::filesystem::exists(root / "editor" / "resources") && std::filesystem::exists(root / "project"); } +std::filesystem::path NormalizePath(const std::filesystem::path& path) { + std::error_code errorCode = {}; + std::filesystem::path normalized = + std::filesystem::weakly_canonical(path, errorCode); + return errorCode + ? path.lexically_normal() + : normalized.lexically_normal(); +} + +App::EditorRuntimePaths BuildEditorRuntimePaths( + const std::filesystem::path& workspaceRoot, + const std::filesystem::path& executableDirectory) { + App::EditorRuntimePaths paths = {}; + paths.workspaceRoot = NormalizePath(workspaceRoot); + paths.executableRoot = NormalizePath(executableDirectory); + + const std::filesystem::path sourceTreeResourceRoot = + paths.workspaceRoot / "editor" / "resources"; + const std::filesystem::path packagedResourceRoot = + paths.executableRoot / "resources"; + paths.resourceRoot = std::filesystem::exists(sourceTreeResourceRoot) + ? NormalizePath(sourceTreeResourceRoot) + : NormalizePath(packagedResourceRoot); + + const std::filesystem::path sourceTreeProjectRoot = + paths.workspaceRoot / "project"; + const std::filesystem::path packagedProjectRoot = + paths.executableRoot / "project"; + paths.projectRoot = std::filesystem::exists(sourceTreeProjectRoot) + ? NormalizePath(sourceTreeProjectRoot) + : NormalizePath(packagedProjectRoot); + + paths.captureRoot = NormalizePath(paths.executableRoot / "captures"); + return paths; +} + void EnableDpiAwareness() { const HMODULE user32 = GetModuleHandleW(L"user32.dll"); if (user32 != nullptr) { @@ -118,7 +155,7 @@ namespace XCEngine::UI::Editor { bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { m_hInstance = hInstance; m_resourceService = std::make_unique(m_hInstance); - m_repoRoot = ResolveRepoRootPath(m_resourceService->GetExecutableDirectory()); + m_runtimePaths = ResolveRuntimePaths(m_resourceService->GetExecutableDirectory()); EnableDpiAwareness(); const std::filesystem::path logRoot = m_resourceService->GetExecutableDirectory() / "logs"; @@ -126,7 +163,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { SetUnhandledExceptionFilter(&Application::HandleUnhandledException); AppendUIEditorRuntimeTrace("app", "initialize begin"); - if (!m_editorContext->Initialize(m_repoRoot)) { + if (!m_editorContext->Initialize(m_runtimePaths)) { AppendUIEditorRuntimeTrace( "app", "shell asset validation failed: " + m_editorContext->GetValidationMessage()); @@ -149,8 +186,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { hostConfig.windowUserData = this; m_windowHostRuntime = std::make_unique( hostConfig, - m_repoRoot, - m_editorContext->GetShellAsset().captureRootPath); + m_runtimePaths); m_renderRuntimeFactory = std::make_unique(); App::EditorWorkspaceShellRuntimeFactory workspaceShellRuntimeFactory = []() { @@ -270,18 +306,13 @@ void Application::Shutdown() { ShutdownUIEditorRuntimeTrace(); } -std::filesystem::path Application::ResolveRepoRootPath( +App::EditorRuntimePaths Application::ResolveRuntimePaths( const std::filesystem::path& executableDirectory) { - std::error_code errorCode = {}; - std::filesystem::path current = - std::filesystem::weakly_canonical(executableDirectory, errorCode); - if (errorCode) { - current = executableDirectory.lexically_normal(); - } + std::filesystem::path current = NormalizePath(executableDirectory); while (!current.empty()) { - if (HasEditorRepoMarkers(current)) { - return current.lexically_normal(); + if (HasEditorWorkspaceMarkers(current)) { + return BuildEditorRuntimePaths(current, executableDirectory); } const std::filesystem::path parent = current.parent_path(); @@ -291,7 +322,7 @@ std::filesystem::path Application::ResolveRepoRootPath( current = parent; } - return executableDirectory.lexically_normal(); + return BuildEditorRuntimePaths(executableDirectory, executableDirectory); } LONG WINAPI Application::HandleUnhandledException(EXCEPTION_POINTERS* exceptionInfo) { diff --git a/editor/app/Bootstrap/Application.h b/editor/app/Bootstrap/Application.h index 0ff5a025..fee9bad1 100644 --- a/editor/app/Bootstrap/Application.h +++ b/editor/app/Bootstrap/Application.h @@ -6,6 +6,8 @@ #include +#include "Environment/EditorRuntimePaths.h" + #include #include #include @@ -49,13 +51,13 @@ private: bool Initialize(HINSTANCE hInstance, int nCmdShow); void Shutdown(); bool RegisterWindowClass(); - static std::filesystem::path ResolveRepoRootPath( + static App::EditorRuntimePaths ResolveRuntimePaths( const std::filesystem::path& executableDirectory); static LONG WINAPI HandleUnhandledException(EXCEPTION_POINTERS* exceptionInfo); HINSTANCE m_hInstance = nullptr; ATOM m_windowClassAtom = 0; - std::filesystem::path m_repoRoot = {}; + App::EditorRuntimePaths m_runtimePaths = {}; std::chrono::steady_clock::time_point m_smokeTestStartTime = {}; std::chrono::milliseconds m_smokeTestDuration = std::chrono::milliseconds::zero(); std::unique_ptr m_editorContext = {}; diff --git a/editor/app/Composition/EditorContext.cpp b/editor/app/Composition/EditorContext.cpp index 17badee7..1f266a92 100644 --- a/editor/app/Composition/EditorContext.cpp +++ b/editor/app/Composition/EditorContext.cpp @@ -52,9 +52,9 @@ UIEditorWorkspacePanelPresentationModel* FindMutablePresentation( } // namespace -bool EditorContext::Initialize(const std::filesystem::path& repoRoot) { +bool EditorContext::Initialize(const EditorRuntimePaths& runtimePaths) { AppendUIEditorRuntimeTrace("startup", "EditorContext::Initialize begin"); - m_shellAsset = BuildEditorApplicationShellAsset(repoRoot); + m_shellAsset = BuildEditorApplicationShellAsset(runtimePaths); AppendUIEditorRuntimeTrace("startup", "BuildEditorApplicationShellAsset complete"); m_shellValidation = ValidateEditorShellAsset(m_shellAsset); AppendUIEditorRuntimeTrace( @@ -66,13 +66,13 @@ bool EditorContext::Initialize(const std::filesystem::path& repoRoot) { } m_session = {}; - m_session.repoRoot = repoRoot; - m_session.projectRoot = (repoRoot / "project").lexically_normal(); + m_session.workspaceRoot = runtimePaths.workspaceRoot; + m_session.projectRoot = runtimePaths.projectRoot; m_commandFocusService = {}; m_selectionService = {}; m_projectRuntime.Reset(); AppendUIEditorRuntimeTrace("startup", "EditorProjectRuntime::Initialize begin"); - m_projectRuntime.Initialize(repoRoot); + m_projectRuntime.Initialize(runtimePaths.projectRoot); AppendUIEditorRuntimeTrace("startup", "EditorProjectRuntime::Initialize end"); m_projectRuntime.BindSelectionService(&m_selectionService); m_sceneRuntime.Reset(); diff --git a/editor/app/Composition/EditorContext.h b/editor/app/Composition/EditorContext.h index 822ef82b..7fa2900e 100644 --- a/editor/app/Composition/EditorContext.h +++ b/editor/app/Composition/EditorContext.h @@ -1,6 +1,7 @@ #pragma once #include "Panels/EditorPanelServices.h" +#include "Environment/EditorRuntimePaths.h" #include "Windowing/EditorFrameServices.h" #include "Scene/EditorSceneRuntime.h" #include "Project/EditorProjectRuntime.h" @@ -33,7 +34,7 @@ class EditorEditCommandRoute; class EditorContext : public EditorFrameServices { public: - bool Initialize(const std::filesystem::path& repoRoot); + bool Initialize(const EditorRuntimePaths& runtimePaths); void AttachTextMeasurer(const UIEditorTextMeasurer& textMeasurer) override; void SetSystemInteractionHost(System::SystemInteractionService* systemInteractionHost); void BindEditCommandRoutes( diff --git a/editor/app/Composition/EditorShellAssetBuilder.cpp b/editor/app/Composition/EditorShellAssetBuilder.cpp index d017a796..17e58042 100644 --- a/editor/app/Composition/EditorShellAssetBuilder.cpp +++ b/editor/app/Composition/EditorShellAssetBuilder.cpp @@ -510,10 +510,10 @@ UIEditorMenuModel BuildEditorMenuModel() { namespace XCEngine::UI::Editor::App { -EditorShellAsset BuildEditorApplicationShellAsset(const std::filesystem::path& repoRoot) { +EditorShellAsset BuildEditorApplicationShellAsset(const EditorRuntimePaths& runtimePaths) { EditorShellAsset asset = {}; asset.screenId = "editor.shell"; - asset.captureRootPath = (repoRoot / "editor/captures").lexically_normal(); + asset.captureRootPath = runtimePaths.captureRoot.lexically_normal(); asset.panelRegistry = BuildEditorPanelRegistry(); asset.workspace = BuildEditorWorkspaceModel(asset.panelRegistry); asset.workspaceSession = diff --git a/editor/app/Composition/EditorShellAssetBuilder.h b/editor/app/Composition/EditorShellAssetBuilder.h index 0a9bddb4..83b52436 100644 --- a/editor/app/Composition/EditorShellAssetBuilder.h +++ b/editor/app/Composition/EditorShellAssetBuilder.h @@ -1,16 +1,16 @@ #pragma once +#include "Environment/EditorRuntimePaths.h" #include "Windowing/EditorShellVariant.h" #include #include -#include #include namespace XCEngine::UI::Editor::App { -EditorShellAsset BuildEditorApplicationShellAsset(const std::filesystem::path& repoRoot); +EditorShellAsset BuildEditorApplicationShellAsset(const EditorRuntimePaths& runtimePaths); UIEditorShellInteractionDefinition BuildEditorApplicationShellInteractionDefinition( const EditorShellAsset& asset, diff --git a/editor/app/Composition/EditorShellRuntime.cpp b/editor/app/Composition/EditorShellRuntime.cpp index e6e9c597..4fa91d01 100644 --- a/editor/app/Composition/EditorShellRuntime.cpp +++ b/editor/app/Composition/EditorShellRuntime.cpp @@ -18,7 +18,7 @@ EditorShellRuntime::EditorShellRuntime( , m_workspacePanels(std::move(workspacePanels)) {} void EditorShellRuntime::Initialize( - const std::filesystem::path& repoRoot, + const EditorRuntimePaths& runtimePaths, Rendering::Host::UiTextureHost& textureHost, Host::EditorHostResourceService& resourceService, UIEditorTextMeasurer& textMeasurer) { @@ -29,10 +29,10 @@ void EditorShellRuntime::Initialize( } m_iconService->Initialize(textureHost, resourceService); - m_viewportRuntimeServices->Initialize(repoRoot); + m_viewportRuntimeServices->Initialize(runtimePaths); m_workspacePanels.Initialize( EditorWorkspacePanelInitializationContext{ - .repoRoot = repoRoot, + .runtimePaths = runtimePaths, .textMeasurer = textMeasurer, .iconService = *m_iconService, .sceneViewportRuntime = &m_viewportRuntimeServices->GetSceneViewportRuntime(), diff --git a/editor/app/Composition/EditorShellRuntime.h b/editor/app/Composition/EditorShellRuntime.h index 48d8c67a..6649e46e 100644 --- a/editor/app/Composition/EditorShellRuntime.h +++ b/editor/app/Composition/EditorShellRuntime.h @@ -51,7 +51,7 @@ public: std::unique_ptr viewportRuntimeServices); void Initialize( - const std::filesystem::path& repoRoot, + const EditorRuntimePaths& runtimePaths, Rendering::Host::UiTextureHost& textureHost, Host::EditorHostResourceService& resourceService, UIEditorTextMeasurer& textMeasurer) override; diff --git a/editor/app/Core/Environment/EditorRuntimePaths.h b/editor/app/Core/Environment/EditorRuntimePaths.h new file mode 100644 index 00000000..5e5cf875 --- /dev/null +++ b/editor/app/Core/Environment/EditorRuntimePaths.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace XCEngine::UI::Editor::App { + +struct EditorRuntimePaths { + std::filesystem::path workspaceRoot = {}; + std::filesystem::path executableRoot = {}; + std::filesystem::path resourceRoot = {}; + std::filesystem::path projectRoot = {}; + std::filesystem::path captureRoot = {}; + + [[nodiscard]] bool IsComplete() const { + return !workspaceRoot.empty() && + !executableRoot.empty() && + !resourceRoot.empty() && + !projectRoot.empty() && + !captureRoot.empty(); + } +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/editor/app/Core/State/EditorSession.h b/editor/app/Core/State/EditorSession.h index 75a11b24..e1ca2003 100644 --- a/editor/app/Core/State/EditorSession.h +++ b/editor/app/Core/State/EditorSession.h @@ -51,7 +51,7 @@ struct EditorConsoleEntry { }; struct EditorSession { - std::filesystem::path repoRoot = {}; + std::filesystem::path workspaceRoot = {}; std::filesystem::path projectRoot = {}; std::string activePanelId = {}; EditorRuntimeMode runtimeMode = EditorRuntimeMode::Edit; diff --git a/editor/app/Core/Viewport/EditorViewportRuntimeServices.h b/editor/app/Core/Viewport/EditorViewportRuntimeServices.h index b1f1e579..34ffa21e 100644 --- a/editor/app/Core/Viewport/EditorViewportRuntimeServices.h +++ b/editor/app/Core/Viewport/EditorViewportRuntimeServices.h @@ -1,10 +1,9 @@ #pragma once #include "Scene/SceneViewportRenderRequest.h" +#include "Environment/EditorRuntimePaths.h" #include "Viewport/EditorViewportPicking.h" #include "Viewport/EditorViewportTypes.h" - -#include #include namespace XCEngine::Rendering { @@ -33,7 +32,7 @@ class EditorViewportRuntimeServices { public: virtual ~EditorViewportRuntimeServices() = default; - virtual void Initialize(const std::filesystem::path& repoRoot) = 0; + virtual void Initialize(const EditorRuntimePaths& runtimePaths) = 0; virtual void Shutdown() = 0; virtual void AttachWindowRenderer(Rendering::Host::ViewportRenderHost& windowRenderer) = 0; virtual void DetachWindowRenderer() = 0; diff --git a/editor/app/Core/Windowing/EditorWorkspaceShellRuntime.h b/editor/app/Core/Windowing/EditorWorkspaceShellRuntime.h index 4cf6a923..2e08aa1a 100644 --- a/editor/app/Core/Windowing/EditorWorkspaceShellRuntime.h +++ b/editor/app/Core/Windowing/EditorWorkspaceShellRuntime.h @@ -1,5 +1,6 @@ #pragma once +#include "Environment/EditorRuntimePaths.h" #include "Windowing/EditorFrameServices.h" #include @@ -7,7 +8,6 @@ #include #include -#include #include #include #include @@ -52,7 +52,7 @@ public: virtual ~EditorWorkspaceShellRuntime() = default; virtual void Initialize( - const std::filesystem::path& repoRoot, + const EditorRuntimePaths& runtimePaths, Rendering::Host::UiTextureHost& textureHost, Host::EditorHostResourceService& resourceService, UIEditorTextMeasurer& textMeasurer) = 0; diff --git a/editor/app/Core/WorkspacePanels/EditorWorkspacePanelRuntime.h b/editor/app/Core/WorkspacePanels/EditorWorkspacePanelRuntime.h index 0c8c4acc..e77bdeb6 100644 --- a/editor/app/Core/WorkspacePanels/EditorWorkspacePanelRuntime.h +++ b/editor/app/Core/WorkspacePanels/EditorWorkspacePanelRuntime.h @@ -2,6 +2,7 @@ #include "Assets/EditorIconService.h" #include "Commands/EditorEditCommandRoute.h" +#include "Environment/EditorRuntimePaths.h" #include "State/EditorSession.h" #include "Panels/EditorPanelServices.h" #include "Viewport/EditorViewportRuntimeServices.h" @@ -14,7 +15,6 @@ #include #include -#include #include #include #include @@ -46,7 +46,7 @@ struct EditorWorkspacePanelFrameEvent { }; struct EditorWorkspacePanelInitializationContext { - const std::filesystem::path& repoRoot; + const EditorRuntimePaths& runtimePaths; UIEditorTextMeasurer& textMeasurer; EditorIconService& iconService; EditorSceneViewportRuntime* sceneViewportRuntime = nullptr; diff --git a/editor/app/Features/Project/ProjectPanel.cpp b/editor/app/Features/Project/ProjectPanel.cpp index 0c621b1d..7ecb302b 100644 --- a/editor/app/Features/Project/ProjectPanel.cpp +++ b/editor/app/Features/Project/ProjectPanel.cpp @@ -400,9 +400,9 @@ ProjectPanel::GetPresentedWindowTreeExpansionModel() const { return ResolveUIEditorFilterableTreeHostExpansionModel(m_treeFilterHostFrame); } -void ProjectPanel::Initialize(const std::filesystem::path& repoRoot) { +void ProjectPanel::Initialize(const std::filesystem::path& projectRoot) { m_ownedProjectRuntime = std::make_unique(); - m_ownedProjectRuntime->Initialize(repoRoot); + m_ownedProjectRuntime->Initialize(projectRoot); SyncSelectionsFromRuntime(); } diff --git a/editor/app/Features/Project/ProjectPanel.h b/editor/app/Features/Project/ProjectPanel.h index 3014b6a5..1b47ae8f 100644 --- a/editor/app/Features/Project/ProjectPanel.h +++ b/editor/app/Features/Project/ProjectPanel.h @@ -82,7 +82,7 @@ public: bool directory = false; }; - void Initialize(const std::filesystem::path& repoRoot); + void Initialize(const std::filesystem::path& projectRoot); void SetProjectRuntime(EditorProjectRuntime* projectRuntime); void SetCommandFocusService(EditorCommandFocusService* commandFocusService); void SetSystemInteractionHost(System::SystemInteractionService* systemInteractionHost); diff --git a/editor/app/Host/Interfaces/EditorWindowHostInterfaces.h b/editor/app/Host/Interfaces/EditorWindowHostInterfaces.h index 6892e552..7198c39f 100644 --- a/editor/app/Host/Interfaces/EditorWindowHostInterfaces.h +++ b/editor/app/Host/Interfaces/EditorWindowHostInterfaces.h @@ -3,6 +3,7 @@ #include "EditorWindowHostTypes.h" #include "EditorWindowPointerCapture.h" #include "EditorWindowRenderRuntime.h" +#include "Environment/EditorRuntimePaths.h" #include "Windowing/EditorWindowGeometry.h" #include "Windowing/EditorWindowTransferRequests.h" #include "Windowing/EditorWindowTypes.h" @@ -12,7 +13,6 @@ #include #include -#include #include #include #include @@ -40,8 +40,7 @@ class EditorWindowInputFeedbackBinding; class EditorWindowTitleBarBinding; struct EditorHostWindowRuntimeInitializationParams { - std::filesystem::path repoRoot = {}; - std::filesystem::path captureRoot = {}; + EditorRuntimePaths runtimePaths = {}; bool autoCaptureOnStartup = false; }; diff --git a/editor/app/Host/Win32/Windowing/EditorWindowHostRuntime.cpp b/editor/app/Host/Win32/Windowing/EditorWindowHostRuntime.cpp index 032ebb73..55a4bc38 100644 --- a/editor/app/Host/Win32/Windowing/EditorWindowHostRuntime.cpp +++ b/editor/app/Host/Win32/Windowing/EditorWindowHostRuntime.cpp @@ -63,11 +63,9 @@ DWORD ResolveExtendedWindowStyle(const EditorWindowNativeHostPolicy& policy) { EditorWindowHostRuntime::EditorWindowHostRuntime( EditorWindowHostConfig hostConfig, - std::filesystem::path repoRoot, - std::filesystem::path captureRoot) + EditorRuntimePaths runtimePaths) : m_hostConfig(hostConfig), - m_repoRoot(std::move(repoRoot)), - m_captureRoot(std::move(captureRoot)) {} + m_runtimePaths(std::move(runtimePaths)) {} EditorWindowHostRuntime::~EditorWindowHostRuntime() = default; @@ -176,8 +174,7 @@ bool EditorWindowHostRuntime::CreateHostWindow( if (!m_hostCoordinator->InitializeHostWindow( window, EditorHostWindowRuntimeInitializationParams{ - .repoRoot = m_repoRoot, - .captureRoot = m_captureRoot, + .runtimePaths = m_runtimePaths, .autoCaptureOnStartup = params.autoCaptureOnStartup, })) { return failWindowInitialization("managed window initialization failed"); diff --git a/editor/app/Host/Win32/Windowing/EditorWindowHostRuntime.h b/editor/app/Host/Win32/Windowing/EditorWindowHostRuntime.h index e00c3756..13b0c4d8 100644 --- a/editor/app/Host/Win32/Windowing/EditorWindowHostRuntime.h +++ b/editor/app/Host/Win32/Windowing/EditorWindowHostRuntime.h @@ -17,8 +17,7 @@ class EditorWindowHostRuntime final : public EditorWindowHostRuntimeServices { public: EditorWindowHostRuntime( EditorWindowHostConfig hostConfig, - std::filesystem::path repoRoot, - std::filesystem::path captureRoot); + EditorRuntimePaths runtimePaths); ~EditorWindowHostRuntime(); bool CreateHostWindow( @@ -54,8 +53,8 @@ public: return m_hostConfig; } - const std::filesystem::path& GetRepoRoot() const { - return m_repoRoot; + const EditorRuntimePaths& GetRuntimePaths() const { + return m_runtimePaths; } std::vector>& GetWindowStorage() { @@ -73,8 +72,7 @@ private: const EditorWindow* FindWindowByIdImpl(std::string_view windowId) const; EditorWindowHostConfig m_hostConfig = {}; - std::filesystem::path m_repoRoot = {}; - std::filesystem::path m_captureRoot = {}; + EditorRuntimePaths m_runtimePaths = {}; std::vector> m_windows = {}; EditorWindow* m_pendingCreateWindow = nullptr; EditorWindowHostCoordinator* m_hostCoordinator = nullptr; diff --git a/editor/app/Rendering/Viewport/SceneViewportResourcePaths.h b/editor/app/Rendering/Viewport/SceneViewportResourcePaths.h index 5c82bec9..f9579d45 100644 --- a/editor/app/Rendering/Viewport/SceneViewportResourcePaths.h +++ b/editor/app/Rendering/Viewport/SceneViewportResourcePaths.h @@ -1,5 +1,7 @@ #pragma once +#include "Environment/EditorRuntimePaths.h" + #include #include @@ -25,8 +27,8 @@ inline ::XCEngine::Containers::String NormalizeSceneViewportResourcePath( } inline std::filesystem::path GetSceneViewportResourceRootPath( - const std::filesystem::path& repoRoot) { - return (repoRoot / "editor" / "resources").lexically_normal(); + const EditorRuntimePaths& runtimePaths) { + return runtimePaths.resourceRoot.lexically_normal(); } inline ::XCEngine::Containers::String BuildSceneViewportResourcePath( @@ -37,9 +39,9 @@ inline ::XCEngine::Containers::String BuildSceneViewportResourcePath( } inline SceneViewportShaderPaths BuildSceneViewportShaderPaths( - const std::filesystem::path& repoRoot) { + const EditorRuntimePaths& runtimePaths) { const std::filesystem::path resourceRoot = - GetSceneViewportResourceRootPath(repoRoot); + GetSceneViewportResourceRootPath(runtimePaths); SceneViewportShaderPaths paths = {}; paths.infiniteGridShaderPath = BuildSceneViewportResourcePath( diff --git a/editor/app/Rendering/Viewport/ViewportHostService.cpp b/editor/app/Rendering/Viewport/ViewportHostService.cpp index 07ecbd4b..a9a69ffd 100644 --- a/editor/app/Rendering/Viewport/ViewportHostService.cpp +++ b/editor/app/Rendering/Viewport/ViewportHostService.cpp @@ -45,8 +45,8 @@ ViewportHostService::ViewportHostService() = default; ViewportHostService::~ViewportHostService() = default; -void ViewportHostService::Initialize(const std::filesystem::path& repoRoot) { - m_sceneViewportRuntime.Initialize(BuildSceneViewportShaderPaths(repoRoot)); +void ViewportHostService::Initialize(const EditorRuntimePaths& runtimePaths) { + m_sceneViewportRuntime.Initialize(BuildSceneViewportShaderPaths(runtimePaths)); m_placeholderRenderers.clear(); for (const EditorProductPanelDescriptor& panel : GetEditorProductPanels()) { if (panel.presentationKind != UIEditorPanelPresentationKind::ViewportShell) { diff --git a/editor/app/Rendering/Viewport/ViewportHostService.h b/editor/app/Rendering/Viewport/ViewportHostService.h index 6197dd72..305e0e40 100644 --- a/editor/app/Rendering/Viewport/ViewportHostService.h +++ b/editor/app/Rendering/Viewport/ViewportHostService.h @@ -23,7 +23,7 @@ public: ViewportHostService(); ~ViewportHostService(); - void Initialize(const std::filesystem::path& repoRoot) override; + void Initialize(const EditorRuntimePaths& runtimePaths) override; void Shutdown() override; void AttachWindowRenderer(Rendering::Host::ViewportRenderHost& windowRenderer) override; void DetachWindowRenderer() override; diff --git a/editor/app/Services/Project/EditorProjectRuntime.cpp b/editor/app/Services/Project/EditorProjectRuntime.cpp index 9b324647..a37336b4 100644 --- a/editor/app/Services/Project/EditorProjectRuntime.cpp +++ b/editor/app/Services/Project/EditorProjectRuntime.cpp @@ -84,9 +84,9 @@ void EditorProjectRuntime::Reset() { m_pendingSceneOpenPath.reset(); } -bool EditorProjectRuntime::Initialize(const std::filesystem::path& repoRoot) { +bool EditorProjectRuntime::Initialize(const std::filesystem::path& projectRoot) { Reset(); - m_browserModel.Initialize(repoRoot); + m_browserModel.Initialize(projectRoot); return true; } diff --git a/editor/app/Services/Project/EditorProjectRuntime.h b/editor/app/Services/Project/EditorProjectRuntime.h index 46358e1d..ed2cc192 100644 --- a/editor/app/Services/Project/EditorProjectRuntime.h +++ b/editor/app/Services/Project/EditorProjectRuntime.h @@ -45,7 +45,7 @@ public: }; void Reset(); - bool Initialize(const std::filesystem::path& repoRoot); + bool Initialize(const std::filesystem::path& projectRoot); void BindSelectionService(EditorSelectionService* selectionService); void Refresh(); diff --git a/editor/app/Services/Project/ProjectBrowserModel.cpp b/editor/app/Services/Project/ProjectBrowserModel.cpp index 526fe2ef..4eed9bf3 100644 --- a/editor/app/Services/Project/ProjectBrowserModel.cpp +++ b/editor/app/Services/Project/ProjectBrowserModel.cpp @@ -545,9 +545,9 @@ void ProjectBrowserModel::Reset() { m_currentFolderId.clear(); } -void ProjectBrowserModel::Initialize(const std::filesystem::path& repoRoot) { +void ProjectBrowserModel::Initialize(const std::filesystem::path& projectRoot) { Reset(); - m_assetsRootPath = (repoRoot / "project/Assets").lexically_normal(); + m_assetsRootPath = (projectRoot / "Assets").lexically_normal(); TraceProjectBrowser("ProjectBrowserModel::Initialize assetsRoot=" + PathToUtf8String(m_assetsRootPath)); std::error_code errorCode = {}; if (!std::filesystem::exists(m_assetsRootPath, errorCode)) { diff --git a/editor/app/Services/Project/ProjectBrowserModel.h b/editor/app/Services/Project/ProjectBrowserModel.h index 696a6a45..3f4699bd 100644 --- a/editor/app/Services/Project/ProjectBrowserModel.h +++ b/editor/app/Services/Project/ProjectBrowserModel.h @@ -54,7 +54,7 @@ public: ProjectBrowserModel& operator=(ProjectBrowserModel&&) noexcept = default; void Reset(); - void Initialize(const std::filesystem::path& repoRoot); + void Initialize(const std::filesystem::path& projectRoot); void Refresh(); bool Empty() const; diff --git a/editor/app/Windowing/Content/EditorWindowContentController.h b/editor/app/Windowing/Content/EditorWindowContentController.h index 7d77f889..df3254fd 100644 --- a/editor/app/Windowing/Content/EditorWindowContentController.h +++ b/editor/app/Windowing/Content/EditorWindowContentController.h @@ -1,12 +1,12 @@ #pragma once +#include "Environment/EditorRuntimePaths.h" #include "Windowing/EditorWindowTransferRequests.h" #include "Windowing/EditorFrameServices.h" #include "EditorWindowContentBindings.h" #include -#include #include #include #include @@ -61,7 +61,7 @@ struct EditorWindowContentCapabilities { }; struct EditorWindowContentInitializationContext { - const std::filesystem::path& repoRoot; + const EditorRuntimePaths& runtimePaths; EditorFrameServices& frameServices; Rendering::Host::UiTextureHost& textureHost; Host::EditorHostResourceService& resourceService; diff --git a/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp index 55b85260..e4fdfab5 100644 --- a/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp +++ b/editor/app/Windowing/Content/EditorWorkspaceWindowContentController.cpp @@ -115,7 +115,7 @@ void EditorWorkspaceWindowContentController::Initialize( return; } m_shellRuntime->Initialize( - context.repoRoot, + context.runtimePaths, context.textureHost, context.resourceService, context.textMeasurer); diff --git a/editor/app/Windowing/EditorWindowInstance.cpp b/editor/app/Windowing/EditorWindowInstance.cpp index e99e071c..00103ae8 100644 --- a/editor/app/Windowing/EditorWindowInstance.cpp +++ b/editor/app/Windowing/EditorWindowInstance.cpp @@ -313,8 +313,7 @@ bool EditorWindowInstance::InitializeRuntime( .widthPixels = runtimeSurface.widthPixels, .heightPixels = runtimeSurface.heightPixels, }, - params.repoRoot, - params.captureRoot, + params.runtimePaths, params.autoCaptureOnStartup); if (initialized) { MarkRunning(); diff --git a/editor/app/Windowing/Runtime/EditorWindowRuntimeController.cpp b/editor/app/Windowing/Runtime/EditorWindowRuntimeController.cpp index e6a0d4d6..31256187 100644 --- a/editor/app/Windowing/Runtime/EditorWindowRuntimeController.cpp +++ b/editor/app/Windowing/Runtime/EditorWindowRuntimeController.cpp @@ -142,8 +142,7 @@ const ::XCEngine::UI::UITextureHandle& EditorWindowRuntimeController::GetTitleBa bool EditorWindowRuntimeController::Initialize( const Rendering::Host::EditorWindowRenderRuntimeInitializeParams& renderParams, - const std::filesystem::path& repoRoot, - const std::filesystem::path& captureRoot, + const EditorRuntimePaths& runtimePaths, bool autoCaptureOnStartup) { if (m_renderRuntime == nullptr) { LogRuntimeTrace("app", "window initialize failed: render runtime is null"); @@ -167,7 +166,7 @@ bool EditorWindowRuntimeController::Initialize( m_frameServices, m_renderRuntime->GetTextMeasurer()); m_contentController->Initialize(EditorWindowContentInitializationContext{ - .repoRoot = repoRoot, + .runtimePaths = runtimePaths, .frameServices = m_frameServices, .textureHost = m_renderRuntime->GetTextureHost(), .resourceService = m_resourceService, @@ -200,7 +199,7 @@ bool EditorWindowRuntimeController::Initialize( m_ready = true; m_screenshotController.Initialize( - captureRoot, + runtimePaths.captureRoot, m_resourceService.GetExecutableDirectory()); if (autoCaptureOnStartup) { m_screenshotController.RequestCapture("startup"); diff --git a/editor/app/Windowing/Runtime/EditorWindowRuntimeController.h b/editor/app/Windowing/Runtime/EditorWindowRuntimeController.h index 74e87082..0395ed8e 100644 --- a/editor/app/Windowing/Runtime/EditorWindowRuntimeController.h +++ b/editor/app/Windowing/Runtime/EditorWindowRuntimeController.h @@ -5,6 +5,7 @@ #endif #include "Content/EditorWindowContentController.h" +#include "Environment/EditorRuntimePaths.h" #include "Runtime/EditorWindowScreenshotController.h" #include "Windowing/EditorFrameServices.h" @@ -14,7 +15,6 @@ #include #include -#include #include #include #include @@ -69,8 +69,7 @@ public: bool Initialize( const Rendering::Host::EditorWindowRenderRuntimeInitializeParams& renderParams, - const std::filesystem::path& repoRoot, - const std::filesystem::path& captureRoot, + const EditorRuntimePaths& runtimePaths, bool autoCaptureOnStartup); void Shutdown(); void ResetInteractionState(); diff --git a/editor/app/Windowing/Runtime/EditorWindowScreenshotController.cpp b/editor/app/Windowing/Runtime/EditorWindowScreenshotController.cpp index ae96c896..0cddf9c3 100644 --- a/editor/app/Windowing/Runtime/EditorWindowScreenshotController.cpp +++ b/editor/app/Windowing/Runtime/EditorWindowScreenshotController.cpp @@ -12,13 +12,11 @@ namespace { std::filesystem::path ResolveBuildCaptureRoot( const std::filesystem::path& requestedCaptureRoot, const std::filesystem::path& executableDirectory) { - std::filesystem::path captureRoot = executableDirectory / "captures"; - const std::filesystem::path scenarioPath = requestedCaptureRoot.parent_path().filename(); - if (!scenarioPath.empty() && scenarioPath != "captures") { - captureRoot /= scenarioPath; + if (!requestedCaptureRoot.empty()) { + return requestedCaptureRoot.lexically_normal(); } - return captureRoot.lexically_normal(); + return (executableDirectory / "captures").lexically_normal(); } } // namespace diff --git a/tests/UI/Editor/unit/test_editor_project_runtime.cpp b/tests/UI/Editor/unit/test_editor_project_runtime.cpp index fd01840d..77a59b26 100644 --- a/tests/UI/Editor/unit/test_editor_project_runtime.cpp +++ b/tests/UI/Editor/unit/test_editor_project_runtime.cpp @@ -65,7 +65,7 @@ TEST(EditorProjectRuntimeTests, NavigateToFolderClearsCurrentProjectSelection) { ASSERT_TRUE(repo.WriteFile("project/Assets/Scenes/Main.xc")); EditorProjectRuntime runtime = {}; - ASSERT_TRUE(runtime.Initialize(repo.Root())); + ASSERT_TRUE(runtime.Initialize(repo.Root() / "project")); ASSERT_TRUE(runtime.NavigateToFolder("Assets/Scenes")); ASSERT_TRUE(runtime.SetSelection("Assets/Scenes/Main.xc")); @@ -80,7 +80,7 @@ TEST(EditorProjectRuntimeTests, OpenSceneItemQueuesSceneOpenRequest) { ASSERT_TRUE(repo.WriteFile("project/Assets/Scenes/Main.xc")); EditorProjectRuntime runtime = {}; - ASSERT_TRUE(runtime.Initialize(repo.Root())); + ASSERT_TRUE(runtime.Initialize(repo.Root() / "project")); ASSERT_TRUE(runtime.NavigateToFolder("Assets/Scenes")); ASSERT_TRUE(runtime.OpenItem("Assets/Scenes/Main.xc")); @@ -97,7 +97,7 @@ TEST(EditorProjectRuntimeTests, ResolveCommandTargetsFollowRuntimeSelectionAndCu ASSERT_TRUE(repo.WriteFile("project/Assets/Scenes/Main.xc")); EditorProjectRuntime runtime = {}; - ASSERT_TRUE(runtime.Initialize(repo.Root())); + ASSERT_TRUE(runtime.Initialize(repo.Root() / "project")); ASSERT_TRUE(runtime.NavigateToFolder("Assets/Scenes")); ASSERT_TRUE(runtime.SetSelection("Assets/Scenes/Main.xc")); @@ -134,7 +134,7 @@ TEST(EditorProjectRuntimeTests, ResolveEditCommandTargetMarksAssetsRootAndIgnore TemporaryRepo repo = {}; EditorProjectRuntime runtime = {}; - ASSERT_TRUE(runtime.Initialize(repo.Root())); + ASSERT_TRUE(runtime.Initialize(repo.Root() / "project")); const std::optional rootTarget = runtime.ResolveEditCommandTarget(); @@ -153,7 +153,7 @@ TEST(EditorProjectRuntimeTests, RenameSelectedItemRemapsSelectionAndDeleteClears ASSERT_TRUE(repo.WriteFile("project/Assets/Scripts/Player.cs.meta")); EditorProjectRuntime runtime = {}; - ASSERT_TRUE(runtime.Initialize(repo.Root())); + ASSERT_TRUE(runtime.Initialize(repo.Root() / "project")); ASSERT_TRUE(runtime.NavigateToFolder("Assets/Scripts")); ASSERT_TRUE(runtime.SetSelection("Assets/Scripts/Player.cs")); @@ -176,7 +176,7 @@ TEST(EditorProjectRuntimeTests, BoundSelectionServiceBecomesTheSingleProjectSele EditorSelectionService selectionService = {}; EditorProjectRuntime runtime = {}; - ASSERT_TRUE(runtime.Initialize(repo.Root())); + ASSERT_TRUE(runtime.Initialize(repo.Root() / "project")); runtime.BindSelectionService(&selectionService); ASSERT_TRUE(runtime.NavigateToFolder("Assets/Scripts")); diff --git a/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp b/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp index 9852f9eb..41c343f9 100644 --- a/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp +++ b/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp @@ -15,6 +15,7 @@ using XCEngine::UI::Editor::App::BuildEditorApplicationShellAsset; using XCEngine::UI::Editor::App::EditorProductManifestValidationCode; using XCEngine::UI::Editor::App::EditorProductPanelRuntimeKind; using XCEngine::UI::Editor::App::EditorProductViewportRendererKind; +using XCEngine::UI::Editor::App::EditorRuntimePaths; using XCEngine::UI::Editor::App::FindEditorProductPanel; using XCEngine::UI::Editor::App::GetEditorProductPanels; using XCEngine::UI::Editor::App::kGamePanelId; @@ -56,8 +57,18 @@ UIShortcutBinding MakeBinding(std::string commandId, KeyCode keyCode) { return binding; } +EditorRuntimePaths TestRuntimePaths() { + return EditorRuntimePaths{ + .workspaceRoot = ".", + .executableRoot = ".", + .resourceRoot = "editor/resources", + .projectRoot = "project", + .captureRoot = "captures", + }; +} + TEST(EditorShellAssetValidationTest, DefaultShellAssetPassesValidation) { - const auto shellAsset = BuildEditorApplicationShellAsset("."); + const auto shellAsset = BuildEditorApplicationShellAsset(TestRuntimePaths()); const auto validation = ValidateEditorShellAsset(shellAsset); EXPECT_TRUE(validation.IsValid()) << validation.message; @@ -79,7 +90,7 @@ TEST(EditorShellAssetValidationTest, ProductManifestDeclaresPanelRuntimeAndViewp << validation.message; ASSERT_TRUE(validation.IsValid()); - const auto shellAsset = BuildEditorApplicationShellAsset("."); + const auto shellAsset = BuildEditorApplicationShellAsset(TestRuntimePaths()); ASSERT_EQ(shellAsset.panelRegistry.panels.size(), GetEditorProductPanels().size()); const auto* gamePanel = FindEditorProductPanel(kGamePanelId); @@ -92,7 +103,7 @@ TEST(EditorShellAssetValidationTest, ProductManifestDeclaresPanelRuntimeAndViewp } TEST(EditorShellAssetValidationTest, ValidationRejectsWorkspacePanelMissingFromRegistry) { - auto shellAsset = BuildEditorApplicationShellAsset("."); + auto shellAsset = BuildEditorApplicationShellAsset(TestRuntimePaths()); auto* documentPanel = const_cast( @@ -108,7 +119,7 @@ TEST(EditorShellAssetValidationTest, ValidationRejectsWorkspacePanelMissingFromR } TEST(EditorShellAssetValidationTest, ValidationRejectsWorkspaceTitleDriftFromRegistry) { - auto shellAsset = BuildEditorApplicationShellAsset("."); + auto shellAsset = BuildEditorApplicationShellAsset(TestRuntimePaths()); auto* scenePanel = FindWorkspacePanelNode(shellAsset.workspace.root, "scene"); ASSERT_NE(scenePanel, nullptr); shellAsset.workspace.activePanelId = scenePanel->panel.panelId; @@ -119,7 +130,7 @@ TEST(EditorShellAssetValidationTest, ValidationRejectsWorkspaceTitleDriftFromReg } TEST(EditorShellAssetValidationTest, ValidationRejectsInvalidWorkspaceSessionState) { - auto shellAsset = BuildEditorApplicationShellAsset("."); + auto shellAsset = BuildEditorApplicationShellAsset(TestRuntimePaths()); ASSERT_FALSE(shellAsset.workspaceSession.panelStates.empty()); shellAsset.workspaceSession.panelStates.front().open = false; @@ -128,7 +139,7 @@ TEST(EditorShellAssetValidationTest, ValidationRejectsInvalidWorkspaceSessionSta } TEST(EditorShellAssetValidationTest, ValidationRejectsShellPresentationMissingFromRegistry) { - auto shellAsset = BuildEditorApplicationShellAsset("."); + auto shellAsset = BuildEditorApplicationShellAsset(TestRuntimePaths()); ASSERT_EQ( shellAsset.shellDefinition.workspacePresentations.size(), shellAsset.panelRegistry.panels.size()); @@ -142,7 +153,7 @@ TEST(EditorShellAssetValidationTest, ValidationRejectsShellPresentationMissingFr } TEST(EditorShellAssetValidationTest, ValidationRejectsDuplicateShellPresentationPanelId) { - auto shellAsset = BuildEditorApplicationShellAsset("."); + auto shellAsset = BuildEditorApplicationShellAsset(TestRuntimePaths()); ASSERT_EQ( shellAsset.shellDefinition.workspacePresentations.size(), shellAsset.panelRegistry.panels.size()); @@ -156,7 +167,7 @@ TEST(EditorShellAssetValidationTest, ValidationRejectsDuplicateShellPresentation } TEST(EditorShellAssetValidationTest, ValidationRejectsMissingRequiredShellPresentation) { - auto shellAsset = BuildEditorApplicationShellAsset("."); + auto shellAsset = BuildEditorApplicationShellAsset(TestRuntimePaths()); shellAsset.shellDefinition.workspacePresentations.clear(); const auto validation = ValidateEditorShellAsset(shellAsset); @@ -166,7 +177,7 @@ TEST(EditorShellAssetValidationTest, ValidationRejectsMissingRequiredShellPresen } TEST(EditorShellAssetValidationTest, ValidationRejectsInvalidShellMenuModel) { - auto shellAsset = BuildEditorApplicationShellAsset("."); + auto shellAsset = BuildEditorApplicationShellAsset(TestRuntimePaths()); shellAsset.shellDefinition.menuModel.menus = { { "window", @@ -191,7 +202,7 @@ TEST(EditorShellAssetValidationTest, ValidationRejectsInvalidShellMenuModel) { } TEST(EditorShellAssetValidationTest, ValidationRejectsInvalidShortcutConfiguration) { - auto shellAsset = BuildEditorApplicationShellAsset("."); + auto shellAsset = BuildEditorApplicationShellAsset(TestRuntimePaths()); shellAsset.shortcutAsset.bindings.push_back( MakeBinding("missing.command", KeyCode::R)); @@ -202,7 +213,7 @@ TEST(EditorShellAssetValidationTest, ValidationRejectsInvalidShortcutConfigurati } TEST(EditorShellAssetValidationTest, ValidationRejectsShellPresentationKindMismatch) { - auto shellAsset = BuildEditorApplicationShellAsset("."); + auto shellAsset = BuildEditorApplicationShellAsset(TestRuntimePaths()); ASSERT_EQ( shellAsset.shellDefinition.workspacePresentations.size(), shellAsset.panelRegistry.panels.size()); diff --git a/tests/UI/Editor/unit/test_project_browser_model.cpp b/tests/UI/Editor/unit/test_project_browser_model.cpp index f0b20122..12e137b5 100644 --- a/tests/UI/Editor/unit/test_project_browser_model.cpp +++ b/tests/UI/Editor/unit/test_project_browser_model.cpp @@ -67,7 +67,7 @@ TEST(ProjectBrowserModelTests, ReparentFolderMovesFolderMetaAndRemapsCurrentFold ASSERT_TRUE(repo.WriteFile("project/Assets/B.meta")); ProjectBrowserModel model = {}; - model.Initialize(repo.Root()); + model.Initialize(repo.Root() / "project"); ASSERT_TRUE(model.NavigateToFolder("Assets/A/Child")); @@ -89,7 +89,7 @@ TEST(ProjectBrowserModelTests, MoveFolderToRootMovesFolderMetaAndRemapsCurrentFo ASSERT_TRUE(repo.WriteFile("project/Assets/Parent/Nested.meta")); ProjectBrowserModel model = {}; - model.Initialize(repo.Root()); + model.Initialize(repo.Root() / "project"); ASSERT_TRUE(model.NavigateToFolder("Assets/Parent/Nested")); @@ -110,7 +110,7 @@ TEST(ProjectBrowserModelTests, CreateFolderCreatesUniqueDirectoryUnderTargetFold ASSERT_TRUE(repo.CreateDirectory("project/Assets/Scenes/New Folder")); ProjectBrowserModel model = {}; - model.Initialize(repo.Root()); + model.Initialize(repo.Root() / "project"); std::string createdFolderId = {}; ASSERT_TRUE(model.CreateFolder("Assets/Scenes", "New Folder", &createdFolderId)); @@ -125,7 +125,7 @@ TEST(ProjectBrowserModelTests, CreateMaterialCreatesUniqueMaterialFileAndExposes ASSERT_TRUE(repo.WriteFile("project/Assets/Materials/New Material.mat")); ProjectBrowserModel model = {}; - model.Initialize(repo.Root()); + model.Initialize(repo.Root() / "project"); std::string createdItemId = {}; ASSERT_TRUE(model.CreateMaterial("Assets/Materials", "New Material", &createdItemId)); @@ -151,7 +151,7 @@ TEST(ProjectBrowserModelTests, CanMoveItemToFolderRejectsDescendantFolderTargets ASSERT_TRUE(repo.CreateDirectory("project/Assets/FolderB")); ProjectBrowserModel model = {}; - model.Initialize(repo.Root()); + model.Initialize(repo.Root() / "project"); EXPECT_FALSE(model.CanMoveItemToFolder("Assets/FolderA", "Assets/FolderA/Nested")); EXPECT_TRUE(model.CanMoveItemToFolder("Assets/FolderA", "Assets/FolderB")); @@ -165,7 +165,7 @@ TEST(ProjectBrowserModelTests, MoveItemToFolderMovesFileMetaAndRefreshesCurrentL ASSERT_TRUE(repo.WriteFile("project/Assets/Scripts/Player.cs.meta")); ProjectBrowserModel model = {}; - model.Initialize(repo.Root()); + model.Initialize(repo.Root() / "project"); ASSERT_TRUE(model.NavigateToFolder("Assets/Scripts")); std::string movedItemId = {}; @@ -192,7 +192,7 @@ TEST(ProjectBrowserModelTests, RenameFilePreservesExtensionAndUpdatesCurrentList ASSERT_TRUE(repo.WriteFile("project/Assets/Scenes/Main.xc.meta")); ProjectBrowserModel model = {}; - model.Initialize(repo.Root()); + model.Initialize(repo.Root() / "project"); ASSERT_TRUE(model.NavigateToFolder("Assets/Scenes")); std::string renamedItemId = {}; @@ -218,7 +218,7 @@ TEST(ProjectBrowserModelTests, DeleteFolderRemovesMetaAndFallsBackCurrentFolder) ASSERT_TRUE(repo.WriteFile("project/Assets/Parent/Nested.meta")); ProjectBrowserModel model = {}; - model.Initialize(repo.Root()); + model.Initialize(repo.Root() / "project"); ASSERT_TRUE(model.NavigateToFolder("Assets/Parent/Nested")); ASSERT_TRUE(model.DeleteItem("Assets/Parent")); @@ -236,7 +236,7 @@ TEST(ProjectBrowserModelTests, AssetEntriesExposeKindAndOpenCapability) { ASSERT_TRUE(repo.WriteFile("project/Assets/readme.txt")); ProjectBrowserModel model = {}; - model.Initialize(repo.Root()); + model.Initialize(repo.Root() / "project"); const ProjectBrowserModel::AssetEntry* sceneEntry = model.FindAssetEntry("Assets/mesh.fbx"); diff --git a/tests/UI/Editor/unit/test_project_panel.cpp b/tests/UI/Editor/unit/test_project_panel.cpp index 0ea4951b..4bfe2593 100644 --- a/tests/UI/Editor/unit/test_project_panel.cpp +++ b/tests/UI/Editor/unit/test_project_panel.cpp @@ -144,7 +144,7 @@ TEST(ProjectPanelTests, CreateFolderCommandCreatesDirectoryAndQueuesRename) { TemporaryRepo repo = {}; ProjectPanel panel = {}; - panel.Initialize(repo.Root()); + panel.Initialize(repo.Root() / "project"); const UIEditorHostCommandEvaluationResult evaluation = panel.EvaluateAssetCommand("assets.create_folder"); @@ -169,7 +169,7 @@ TEST(ProjectPanelTests, CreateMaterialCommandCreatesFileAndQueuesRename) { TemporaryRepo repo = {}; ProjectPanel panel = {}; - panel.Initialize(repo.Root()); + panel.Initialize(repo.Root() / "project"); const UIEditorHostCommandEvaluationResult evaluation = panel.EvaluateAssetCommand("assets.create_material"); @@ -192,7 +192,7 @@ TEST(ProjectPanelTests, BackgroundContextMenuCreateFolderUsesCurrentFolder) { TemporaryRepo repo = {}; ProjectPanel panel = {}; - panel.Initialize(repo.Root()); + panel.Initialize(repo.Root() / "project"); const UIEditorHostedPanelDispatchEntry dispatchEntry = MakeProjectDispatchEntry(); panel.Update( @@ -219,7 +219,7 @@ TEST(ProjectPanelTests, FolderContextMenuCreateFolderUsesFolderTarget) { ASSERT_TRUE(std::filesystem::create_directories(repo.Root() / "project/Assets/FolderA")); ProjectPanel panel = {}; - panel.Initialize(repo.Root()); + panel.Initialize(repo.Root() / "project"); const UIEditorHostedPanelDispatchEntry dispatchEntry = MakeProjectDispatchEntry(); panel.Update( @@ -239,7 +239,7 @@ TEST(ProjectPanelTests, InjectedRuntimeSelectionDrivesRenameWithoutPanelSelectio ASSERT_TRUE(repo.WriteFile("project/Assets/Scripts/Player.cs.meta")); EditorProjectRuntime runtime = {}; - ASSERT_TRUE(runtime.Initialize(repo.Root())); + ASSERT_TRUE(runtime.Initialize(repo.Root() / "project")); ProjectPanel panel = {}; panel.SetProjectRuntime(&runtime); @@ -263,7 +263,7 @@ TEST(ProjectPanelTests, InjectedRuntimeCurrentFolderDrivesRenameFallbackWithoutT ASSERT_TRUE(repo.CreateDirectory("project/Assets/FolderA")); EditorProjectRuntime runtime = {}; - ASSERT_TRUE(runtime.Initialize(repo.Root())); + ASSERT_TRUE(runtime.Initialize(repo.Root() / "project")); ProjectPanel panel = {}; panel.SetProjectRuntime(&runtime); @@ -283,7 +283,7 @@ TEST(ProjectPanelTests, IconServiceCanBeConfiguredBeforeRuntimeInitialization) { FakeIconService icons = {}; panel.SetIconService(&icons); - panel.Initialize(repo.Root()); + panel.Initialize(repo.Root() / "project"); const UIEditorHostCommandEvaluationResult evaluation = panel.EvaluateAssetCommand("assets.create_folder"); @@ -296,7 +296,7 @@ TEST(ProjectPanelTests, CopyPathCommandUsesInjectedSystemHost) { ASSERT_TRUE(repo.WriteFile("project/Assets/Scripts/Player.cs")); EditorProjectRuntime runtime = {}; - ASSERT_TRUE(runtime.Initialize(repo.Root())); + ASSERT_TRUE(runtime.Initialize(repo.Root() / "project")); ASSERT_TRUE(runtime.NavigateToFolder("Assets/Scripts")); ASSERT_TRUE(runtime.SetSelection("Assets/Scripts/Player.cs")); @@ -322,7 +322,7 @@ TEST(ProjectPanelTests, ShowInExplorerCommandUsesInjectedSystemHost) { ASSERT_TRUE(repo.WriteFile("project/Assets/Scripts/Player.cs")); EditorProjectRuntime runtime = {}; - ASSERT_TRUE(runtime.Initialize(repo.Root())); + ASSERT_TRUE(runtime.Initialize(repo.Root() / "project")); ASSERT_TRUE(runtime.NavigateToFolder("Assets/Scripts")); ASSERT_TRUE(runtime.SetSelection("Assets/Scripts/Player.cs")); diff --git a/tests/UI/Editor/unit/test_structured_editor_shell.cpp b/tests/UI/Editor/unit/test_structured_editor_shell.cpp index 875e57fd..d11476ac 100644 --- a/tests/UI/Editor/unit/test_structured_editor_shell.cpp +++ b/tests/UI/Editor/unit/test_structured_editor_shell.cpp @@ -20,6 +20,7 @@ namespace { using XCEngine::Input::KeyCode; using XCEngine::UI::Editor::App::BuildEditorApplicationShellAsset; +using XCEngine::UI::Editor::App::EditorRuntimePaths; using XCEngine::UI::Editor::BuildStructuredEditorShellBinding; using XCEngine::UI::Editor::BuildStructuredEditorShellServices; using XCEngine::UI::Editor::ResolveUIEditorShellInteractionModel; @@ -40,6 +41,17 @@ std::filesystem::path RepoRootPath() { return std::filesystem::path(root).lexically_normal(); } +EditorRuntimePaths TestRuntimePaths() { + const std::filesystem::path root = RepoRootPath(); + return EditorRuntimePaths{ + .workspaceRoot = root, + .executableRoot = root, + .resourceRoot = root / "editor" / "resources", + .projectRoot = root / "project", + .captureRoot = root / "build" / "editor" / "captures", + }; +} + bool PanelRegistriesMatch( const XCEngine::UI::Editor::UIEditorPanelRegistry& lhs, const XCEngine::UI::Editor::UIEditorPanelRegistry& rhs) { @@ -126,7 +138,7 @@ UIShortcutBinding MakeBinding(std::string commandId, KeyCode keyCode) { } // namespace TEST(EditorUIStructuredShellTest, StructuredEditorShellDoesNotRequireRepositoryXCUIDocument) { - const auto shell = BuildEditorApplicationShellAsset(RepoRootPath()); + const auto shell = BuildEditorApplicationShellAsset(TestRuntimePaths()); const auto binding = BuildStructuredEditorShellBinding(shell); ASSERT_TRUE(binding.IsValid()) << binding.assetValidation.message; @@ -136,7 +148,7 @@ TEST(EditorUIStructuredShellTest, StructuredEditorShellDoesNotRequireRepositoryX } TEST(EditorUIStructuredShellTest, StructuredShellBindingUsesEditorShellAssetAsSingleSource) { - auto shell = BuildEditorApplicationShellAsset(RepoRootPath()); + auto shell = BuildEditorApplicationShellAsset(TestRuntimePaths()); shell.shortcutAsset.commandRegistry.commands = { { "workspace.reset_layout", diff --git a/tests/UI/Editor/unit/test_ui_editor_panel_registry.cpp b/tests/UI/Editor/unit/test_ui_editor_panel_registry.cpp index 0d9ecc1a..adb5be53 100644 --- a/tests/UI/Editor/unit/test_ui_editor_panel_registry.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_panel_registry.cpp @@ -7,6 +7,7 @@ namespace { using XCEngine::UI::Editor::App::BuildEditorApplicationShellAsset; +using XCEngine::UI::Editor::App::EditorRuntimePaths; using XCEngine::UI::Editor::FindUIEditorPanelDescriptor; using XCEngine::UI::Editor::UIEditorPanelDescriptor; using XCEngine::UI::Editor::UIEditorPanelRegistry; @@ -14,8 +15,15 @@ using XCEngine::UI::Editor::UIEditorPanelRegistryValidationCode; using XCEngine::UI::Editor::ValidateUIEditorPanelRegistry; TEST(UIEditorPanelRegistryTest, DefaultRegistryContainsShellDescriptors) { + const EditorRuntimePaths paths{ + .workspaceRoot = ".", + .executableRoot = ".", + .resourceRoot = "editor/resources", + .projectRoot = "project", + .captureRoot = "captures", + }; const UIEditorPanelRegistry registry = - BuildEditorApplicationShellAsset(".").panelRegistry; + BuildEditorApplicationShellAsset(paths).panelRegistry; ASSERT_EQ(registry.panels.size(), 6u); const UIEditorPanelDescriptor* descriptor =