diff --git a/docs/plan/XCUI_NewEditor收口重构计划_2026-04-17.md b/docs/plan/XCUI_NewEditor收口重构计划_2026-04-17.md index 1db5b393..ca43d8b3 100644 --- a/docs/plan/XCUI_NewEditor收口重构计划_2026-04-17.md +++ b/docs/plan/XCUI_NewEditor收口重构计划_2026-04-17.md @@ -49,11 +49,12 @@ - 当前 consumer 改为通过显式公开的 engine include root 获取编译所需头文件,而不再继承整个 engine link interface 8. `window-workspace` 全局约束已再补一刀: - - 已补跨窗口 `panelId` 唯一性校验 - - 已补对应窗口工作区单元测试 - - 已补 detached single-panel transfer 的 source root / open-visible 约束,避免脏 source 请求绕过可见性校验 - - 已补同窗口 `move/dock` 的 controller 级结果校验与回滚,避免 mutation 回归直接污染 window-set 状态 - - 已补 detached window 起始 global-tab-drag 的源请求校验,避免 stale `nodeId/panelId` 直接进入跨窗口拖拽状态机 + - 已补跨窗口 `panelId` 唯一性校验 + - 已补对应窗口工作区单元测试 + - 已补 detached single-panel transfer 的 source root / open-visible 约束,避免脏 source 请求绕过可见性校验 + - 已补同窗口 `move/dock` 的 controller 级结果校验与回滚,避免 mutation 回归直接污染 window-set 状态 + - 已补 detached window 起始 global-tab-drag 的源请求校验,避免 stale `nodeId/panelId` 直接进入跨窗口拖拽状态机 + - 已补 live window-set 构造时对 destroyed/no-`HWND` 窗口的过滤,避免 `primary/activeWindowId` 指向不存在的运行时窗口 9. integration 测试构建模板已继续收口: - 已把 `tests/UI/Editor/integration` 叶子目标收敛到 shared helper @@ -551,6 +552,7 @@ - 已完成 detached single-panel transfer 对 `sourceNodeId` 与 `open/visible` 的约束补齐,并补单元测试 - 已完成同窗口 `move/dock` 的结果校验与回滚防线补齐 - 已完成 detached window 起始 global-tab-drag 的源请求校验 +- 已完成 live window-set 对 destroyed/no-`HWND` 窗口的 primary/active 过滤 - 尚未完成 transfer 前后 session/state 完整性约束 - 尚未完成 cross-window drag/drop 状态机约束收口 diff --git a/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp b/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp index f2a95b5b..5f7a36a6 100644 --- a/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp @@ -1,10 +1,12 @@ #include "Platform/Win32/WindowManager/Internal.h" +#include "Bootstrap/EditorResources.h" #include "State/EditorContext.h" #include "Platform/Win32/EditorWindow.h" #include +#include #include namespace XCEngine::UI::Editor::App { @@ -122,6 +124,102 @@ EditorWindowHostRuntime::EditorWindowHostRuntime( EditorWindowHostRuntime::~EditorWindowHostRuntime() = default; +EditorWindow* EditorWindowHostRuntime::CreateEditorWindow( + UIEditorWorkspaceController workspaceController, + const CreateParams& params) { + auto windowPtr = std::make_unique( + params.windowId, + params.title.empty() ? std::wstring(L"XCEngine Editor") : params.title, + params.primary, + std::move(workspaceController)); + EditorWindow* const rawWindow = windowPtr.get(); + m_windows.push_back(std::move(windowPtr)); + + const auto eraseRawWindow = [this, rawWindow]() { + const auto it = std::find_if( + m_windows.begin(), + m_windows.end(), + [rawWindow](const std::unique_ptr& candidate) { + return candidate.get() == rawWindow; + }); + if (it != m_windows.end()) { + m_windows.erase(it); + } + }; + + m_pendingCreateWindow = rawWindow; + const HWND hwnd = CreateWindowExW( + WS_EX_APPWINDOW, + m_hostConfig.windowClassName, + rawWindow->GetTitle().c_str(), + m_hostConfig.windowStyle, + params.initialX, + params.initialY, + params.initialWidth, + params.initialHeight, + nullptr, + nullptr, + m_hostConfig.hInstance, + m_hostConfig.windowUserData); + m_pendingCreateWindow = nullptr; + if (hwnd == nullptr) { + eraseRawWindow(); + return nullptr; + } + + if (!rawWindow->HasHwnd()) { + rawWindow->AttachHwnd(hwnd); + } + + auto failWindowInitialization = [&](std::string_view message) { + LogRuntimeTrace("window", std::string(message)); + DestroyEditorWindow(*rawWindow); + eraseRawWindow(); + return static_cast(nullptr); + }; + + const HICON bigIcon = static_cast( + LoadImageW( + m_hostConfig.hInstance, + MAKEINTRESOURCEW(IDI_APP_ICON), + IMAGE_ICON, + 0, + 0, + LR_DEFAULTSIZE)); + const HICON smallIcon = static_cast( + LoadImageW( + m_hostConfig.hInstance, + MAKEINTRESOURCEW(IDI_APP_ICON_SMALL), + IMAGE_ICON, + GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON), + LR_DEFAULTCOLOR)); + if (bigIcon != nullptr) { + SendMessageW(hwnd, WM_SETICON, ICON_BIG, reinterpret_cast(bigIcon)); + } + if (smallIcon != nullptr) { + SendMessageW(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast(smallIcon)); + } + + if (!rawWindow->Initialize( + m_repoRoot, + m_editorContext, + m_editorContext.GetShellAsset().captureRootPath, + params.autoCaptureOnStartup)) { + return failWindowInitialization("managed window initialization failed"); + } + + ShowWindow(hwnd, params.showCommand); + UpdateWindow(hwnd); + return rawWindow; +} + +void EditorWindowHostRuntime::HandlePendingNativeWindowCreated(HWND hwnd) { + if (m_pendingCreateWindow != nullptr && !m_pendingCreateWindow->HasHwnd()) { + m_pendingCreateWindow->AttachHwnd(hwnd); + } +} + void EditorWindowHostRuntime::Shutdown() { for (const std::unique_ptr& window : m_windows) { if (window != nullptr) { @@ -220,6 +318,56 @@ void EditorWindowHostRuntime::HandleDestroyedWindow(HWND hwnd) { } } +EditorWindow* EditorWindowHostRuntime::FindWindow(HWND hwnd) { + if (hwnd == nullptr) { + return nullptr; + } + + for (const std::unique_ptr& window : m_windows) { + if (window != nullptr && window->GetHwnd() == hwnd) { + return window.get(); + } + } + + return nullptr; +} + +const EditorWindow* EditorWindowHostRuntime::FindWindow(HWND hwnd) const { + return const_cast(this)->FindWindow(hwnd); +} + +EditorWindow* EditorWindowHostRuntime::FindWindow(std::string_view windowId) { + if (windowId.empty()) { + return nullptr; + } + + for (const std::unique_ptr& window : m_windows) { + if (window != nullptr && window->GetWindowId() == windowId) { + return window.get(); + } + } + + return nullptr; +} + +const EditorWindow* EditorWindowHostRuntime::FindWindow(std::string_view windowId) const { + return const_cast(this)->FindWindow(windowId); +} + +EditorWindow* EditorWindowHostRuntime::FindPrimaryWindow() { + for (const std::unique_ptr& window : m_windows) { + if (window != nullptr && window->IsPrimary()) { + return window.get(); + } + } + + return nullptr; +} + +const EditorWindow* EditorWindowHostRuntime::FindPrimaryWindow() const { + return const_cast(this)->FindPrimaryWindow(); +} + void EditorWindowHostRuntime::LogRuntimeTrace( std::string_view channel, std::string_view message) const { @@ -232,6 +380,55 @@ EditorWindowWorkspaceCoordinator::EditorWindowWorkspaceCoordinator( EditorWindowWorkspaceCoordinator::~EditorWindowWorkspaceCoordinator() = default; +UIEditorWindowWorkspaceSet EditorWindowWorkspaceCoordinator::BuildWindowWorkspaceSet( + std::string_view activeWindowId) const { + UIEditorWindowWorkspaceSet windowSet = {}; + if (const EditorWindow* primaryWindow = m_hostRuntime.FindPrimaryWindow(); + primaryWindow != nullptr && + primaryWindow->GetHwnd() != nullptr) { + windowSet.primaryWindowId = std::string(primaryWindow->GetWindowId()); + } + + for (const std::unique_ptr& window : m_hostRuntime.GetWindows()) { + if (window == nullptr || window->GetHwnd() == nullptr) { + continue; + } + + UIEditorWindowWorkspaceState entry = {}; + entry.windowId = std::string(window->GetWindowId()); + entry.workspace = window->GetWorkspaceController().GetWorkspace(); + entry.session = window->GetWorkspaceController().GetSession(); + windowSet.windows.push_back(std::move(entry)); + } + + windowSet.activeWindowId = + !activeWindowId.empty() && + ([this, activeWindowId]() { + const EditorWindow* activeWindow = m_hostRuntime.FindWindow(activeWindowId); + return activeWindow != nullptr && activeWindow->GetHwnd() != nullptr; + })() + ? std::string(activeWindowId) + : windowSet.primaryWindowId; + + return windowSet; +} + +UIEditorWindowWorkspaceController +EditorWindowWorkspaceCoordinator::BuildLiveWindowWorkspaceController( + std::string_view activeWindowId) const { + return UIEditorWindowWorkspaceController( + m_hostRuntime.GetEditorContext().GetShellAsset().panelRegistry, + BuildWindowWorkspaceSet(activeWindowId)); +} + +UIEditorWorkspaceController EditorWindowWorkspaceCoordinator::BuildWorkspaceControllerForWindow( + const UIEditorWindowWorkspaceState& windowState) const { + return UIEditorWorkspaceController( + m_hostRuntime.GetEditorContext().GetShellAsset().panelRegistry, + windowState.workspace, + windowState.session); +} + void EditorWindowWorkspaceCoordinator::HandleWindowFrameTransferRequests( EditorWindow& sourceWindow, EditorWindowFrameTransferRequests&& transferRequests) {