editor: explicit runtime path contract

This commit is contained in:
2026-04-28 14:49:41 +08:00
parent 0e506f21ec
commit cd166037bf
38 changed files with 210 additions and 129 deletions

View File

@@ -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` 将项目根固定为 `<repoRoot>/project`不要沿用旧文档里的 `--project <path>` 说法。
- 当前应用没有命令行项目选择流程;默认项目根由 `EditorRuntimePaths.projectRoot` 提供,开发 workspace 下通常是 `<workspaceRoot>/project`不要沿用旧文档里的 `--project <path>` 说法。
- 当前面板 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

View File

@@ -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<Host::Win32EditorResourceService>(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<App::EditorWindowHostRuntime>(
hostConfig,
m_repoRoot,
m_editorContext->GetShellAsset().captureRootPath);
m_runtimePaths);
m_renderRuntimeFactory =
std::make_unique<Host::D3D12EditorWindowRenderRuntimeFactory>();
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) {

View File

@@ -6,6 +6,8 @@
#include <windows.h>
#include "Environment/EditorRuntimePaths.h"
#include <chrono>
#include <filesystem>
#include <memory>
@@ -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<App::EditorContext> m_editorContext = {};

View File

@@ -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();

View File

@@ -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(

View File

@@ -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 =

View File

@@ -1,16 +1,16 @@
#pragma once
#include "Environment/EditorRuntimePaths.h"
#include "Windowing/EditorShellVariant.h"
#include <XCEditor/Shell/UIEditorShellAsset.h>
#include <XCEditor/Workspace/UIEditorWorkspaceController.h>
#include <filesystem>
#include <string_view>
namespace XCEngine::UI::Editor::App {
EditorShellAsset BuildEditorApplicationShellAsset(const std::filesystem::path& repoRoot);
EditorShellAsset BuildEditorApplicationShellAsset(const EditorRuntimePaths& runtimePaths);
UIEditorShellInteractionDefinition BuildEditorApplicationShellInteractionDefinition(
const EditorShellAsset& asset,

View File

@@ -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(),

View File

@@ -51,7 +51,7 @@ public:
std::unique_ptr<EditorViewportRuntimeServices> viewportRuntimeServices);
void Initialize(
const std::filesystem::path& repoRoot,
const EditorRuntimePaths& runtimePaths,
Rendering::Host::UiTextureHost& textureHost,
Host::EditorHostResourceService& resourceService,
UIEditorTextMeasurer& textMeasurer) override;

View File

@@ -0,0 +1,23 @@
#pragma once
#include <filesystem>
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

View File

@@ -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;

View File

@@ -1,10 +1,9 @@
#pragma once
#include "Scene/SceneViewportRenderRequest.h"
#include "Environment/EditorRuntimePaths.h"
#include "Viewport/EditorViewportPicking.h"
#include "Viewport/EditorViewportTypes.h"
#include <filesystem>
#include <string_view>
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;

View File

@@ -1,5 +1,6 @@
#pragma once
#include "Environment/EditorRuntimePaths.h"
#include "Windowing/EditorFrameServices.h"
#include <XCEditor/Docking/UIEditorDockHostTransfer.h>
@@ -7,7 +8,6 @@
#include <XCEngine/UI/Types.h>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <memory>
#include <string>
@@ -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;

View File

@@ -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 <XCEngine/UI/DrawData.h>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <memory>
#include <string>
@@ -46,7 +46,7 @@ struct EditorWorkspacePanelFrameEvent {
};
struct EditorWorkspacePanelInitializationContext {
const std::filesystem::path& repoRoot;
const EditorRuntimePaths& runtimePaths;
UIEditorTextMeasurer& textMeasurer;
EditorIconService& iconService;
EditorSceneViewportRuntime* sceneViewportRuntime = nullptr;

View File

@@ -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<EditorProjectRuntime>();
m_ownedProjectRuntime->Initialize(repoRoot);
m_ownedProjectRuntime->Initialize(projectRoot);
SyncSelectionsFromRuntime();
}

View File

@@ -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);

View File

@@ -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 <XCEditor/Windowing/Presentation/EditorWorkspaceWindowProjection.h>
#include <XCEngine/UI/Types.h>
#include <filesystem>
#include <cstdint>
#include <memory>
#include <optional>
@@ -40,8 +40,7 @@ class EditorWindowInputFeedbackBinding;
class EditorWindowTitleBarBinding;
struct EditorHostWindowRuntimeInitializationParams {
std::filesystem::path repoRoot = {};
std::filesystem::path captureRoot = {};
EditorRuntimePaths runtimePaths = {};
bool autoCaptureOnStartup = false;
};

View File

@@ -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");

View File

@@ -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<std::unique_ptr<EditorWindow>>& 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<std::unique_ptr<EditorWindow>> m_windows = {};
EditorWindow* m_pendingCreateWindow = nullptr;
EditorWindowHostCoordinator* m_hostCoordinator = nullptr;

View File

@@ -1,5 +1,7 @@
#pragma once
#include "Environment/EditorRuntimePaths.h"
#include <XCEngine/Core/Containers/String.h>
#include <filesystem>
@@ -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(

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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)) {

View File

@@ -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;

View File

@@ -1,12 +1,12 @@
#pragma once
#include "Environment/EditorRuntimePaths.h"
#include "Windowing/EditorWindowTransferRequests.h"
#include "Windowing/EditorFrameServices.h"
#include "EditorWindowContentBindings.h"
#include <XCEditor/Foundation/UIEditorTextMeasurement.h>
#include <filesystem>
#include <optional>
#include <string>
#include <string_view>
@@ -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;

View File

@@ -115,7 +115,7 @@ void EditorWorkspaceWindowContentController::Initialize(
return;
}
m_shellRuntime->Initialize(
context.repoRoot,
context.runtimePaths,
context.textureHost,
context.resourceService,
context.textMeasurer);

View File

@@ -313,8 +313,7 @@ bool EditorWindowInstance::InitializeRuntime(
.widthPixels = runtimeSurface.widthPixels,
.heightPixels = runtimeSurface.heightPixels,
},
params.repoRoot,
params.captureRoot,
params.runtimePaths,
params.autoCaptureOnStartup);
if (initialized) {
MarkRunning();

View File

@@ -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");

View File

@@ -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 <chrono>
#include <cstdint>
#include <filesystem>
#include <memory>
#include <optional>
#include <string>
@@ -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();

View File

@@ -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

View File

@@ -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<EditorProjectRuntime::EditCommandTarget> 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"));

View File

@@ -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<XCEngine::UI::Editor::UIEditorPanelDescriptor*>(
@@ -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());

View File

@@ -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");

View File

@@ -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"));

View File

@@ -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",

View File

@@ -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 =