#include "Windowing/System/EditorWindowSystem.h" #include "Windowing/System/EditorWindowSynchronizationPlanner.h" #include #include #include namespace XCEngine::UI::Editor::App { EditorWindowSystem::EditorWindowSystem(UIEditorPanelRegistry panelRegistry) : m_workspaceStore(std::move(panelRegistry)) {} const UIEditorPanelRegistry& EditorWindowSystem::GetPanelRegistry() const { return m_workspaceStore.GetPanelRegistry(); } bool EditorWindowSystem::BootstrapPrimaryWindow( std::string_view primaryWindowId, const UIEditorWorkspaceController& workspaceController, std::string& outError) { const std::string resolvedPrimaryWindowId = primaryWindowId.empty() ? std::string("main-window") : std::string(primaryWindowId); UIEditorWindowWorkspaceSet windowSet = {}; windowSet.primaryWindowId = resolvedPrimaryWindowId; windowSet.activeWindowId = resolvedPrimaryWindowId; UIEditorWindowWorkspaceState primaryWindowState = {}; primaryWindowState.windowId = resolvedPrimaryWindowId; primaryWindowState.workspace = workspaceController.GetWorkspace(); primaryWindowState.session = workspaceController.GetSession(); windowSet.windows.push_back(std::move(primaryWindowState)); return m_workspaceStore.TrySetWindowSet(std::move(windowSet), outError); } bool EditorWindowSystem::ValidateWindowSet( const UIEditorWindowWorkspaceSet& windowSet, std::string& outError) const { return m_workspaceStore.ValidateWindowSet(windowSet, outError); } bool EditorWindowSystem::IsPrimaryWindowId(std::string_view windowId) const { return m_workspaceStore.IsPrimaryWindowId(windowId); } const UIEditorWindowWorkspaceSet& EditorWindowSystem::GetWindowSet() const { return m_workspaceStore.GetWindowSet(); } UIEditorWindowWorkspaceController EditorWindowSystem::BuildWorkspaceMutationController() const { return UIEditorWindowWorkspaceController(GetPanelRegistry(), GetWindowSet()); } EditorWindowSynchronizationPlan EditorWindowSystem::BuildPlanForWindowSet( const UIEditorWindowWorkspaceSet& targetWindowSet, const std::vector& hostWindows, std::wstring_view primaryWindowTitle, std::string_view preferredNewWindowId, const EditorWindowSynchronizationPlacement& preferredPlacement, std::string& outError) const { return BuildSynchronizationPlan( EditorWindowSynchronizationPlannerInput{ .targetWindowSet = &targetWindowSet, .primaryWindowTitle = primaryWindowTitle, .preferredNewWindowId = preferredNewWindowId, .preferredPlacement = preferredPlacement, .hostWindows = hostWindows, }, outError); } EditorWindowSynchronizationPlan EditorWindowSystem::BuildPlanForLiveWindowMutation( std::string_view windowId, const UIEditorWorkspaceController& workspaceController, const std::vector& hostWindows, std::wstring_view primaryWindowTitle, std::string& outError) const { EditorWindowWorkspaceMutationRequest request = {}; request.windowState.windowId = std::string(windowId); request.windowState.workspace = workspaceController.GetWorkspace(); request.windowState.session = workspaceController.GetSession(); return BuildPlanForWorkspaceMutationRequest( request, hostWindows, primaryWindowTitle, outError); } EditorWindowSynchronizationPlan EditorWindowSystem::BuildPlanForWorkspaceMutationRequest( const EditorWindowWorkspaceMutationRequest& request, const std::vector& hostWindows, std::wstring_view primaryWindowTitle, std::string& outError) const { if (!request.IsValid()) { outError = "live window mutation request missing window id"; return {}; } UIEditorWindowWorkspaceSet nextWindowSet = GetWindowSet(); UIEditorWindowWorkspaceState* existingState = FindMutableUIEditorWindowWorkspaceState(nextWindowSet, request.windowState.windowId); if (existingState == nullptr) { outError = "live window mutation references unknown window '" + request.windowState.windowId + "'"; return {}; } existingState->workspace = request.windowState.workspace; existingState->session = request.windowState.session; return BuildPlanForWindowSet( nextWindowSet, hostWindows, primaryWindowTitle, {}, {}, outError); } EditorWindowSynchronizationPlan EditorWindowSystem::BuildPlanForDestroyedWindow( std::string_view windowId, const std::vector& hostWindows, std::wstring_view primaryWindowTitle, std::string& outError) const { if (windowId.empty()) { outError = "window destroy event missing window id"; return {}; } UIEditorWindowWorkspaceSet nextWindowSet = GetWindowSet(); const bool destroyedPrimary = nextWindowSet.primaryWindowId == windowId; if (!RemoveWindowStateFromSet(nextWindowSet, windowId)) { outError = "window destroy event references unknown authoritative window '" + std::string(windowId) + "'"; return {}; } if (destroyedPrimary) { EditorWindowSynchronizationPlan plan = {}; plan.valid = true; plan.targetWindowSet = {}; for (const EditorWindowHostSnapshot& snapshot : hostWindows) { if (!snapshot.workspaceWindow || !snapshot.hasNativeWindow || !snapshot.running || snapshot.windowId == windowId) { continue; } EditorWindowSynchronizationAction action = {}; action.kind = EditorWindowSynchronizationActionKind::CloseWorkspaceWindow; action.close.windowId = snapshot.windowId; plan.actions.push_back(std::move(action)); } outError.clear(); return plan; } return BuildPlanForWindowSet( nextWindowSet, hostWindows, primaryWindowTitle, {}, {}, outError); } EditorWindowSynchronizationPlan EditorWindowSystem::BuildSynchronizationPlan( const EditorWindowSynchronizationPlannerInput& input, std::string& outError) const { if (input.targetWindowSet == nullptr) { outError = "window synchronization planner missing target window set"; return {}; } if (!ValidateWindowSet(*input.targetWindowSet, outError)) { return {}; } EditorWindowSynchronizationPlannerInput resolvedInput = input; resolvedInput.panelRegistry = &GetPanelRegistry(); EditorWindowSynchronizationPlan plan = EditorWindowSynchronizationPlanner::Build(resolvedInput); if (!plan.valid) { outError = plan.errorMessage; return {}; } outError.clear(); return plan; } bool EditorWindowSystem::CommitSynchronizationPlan( const EditorWindowSynchronizationPlan& plan, std::string& outError) { if (!plan.valid) { outError = !plan.errorMessage.empty() ? plan.errorMessage : "cannot commit invalid synchronization plan"; return false; } if (plan.targetWindowSet.windows.empty()) { m_workspaceStore.ClearWindowSet(); outError.clear(); return true; } return m_workspaceStore.TrySetWindowSet(plan.targetWindowSet, outError); } UIEditorWindowWorkspaceOperationResult EditorWindowSystem::EvaluateDetachPanelToNewWindow( std::string_view sourceWindowId, std::string_view sourceNodeId, std::string_view panelId, UIEditorWindowWorkspaceController& outController) const { outController = BuildWorkspaceMutationController(); return outController.DetachPanelToNewWindow(sourceWindowId, sourceNodeId, panelId); } bool EditorWindowSystem::RemoveWindowStateFromSet( UIEditorWindowWorkspaceSet& windowSet, std::string_view windowId) { const auto originalSize = windowSet.windows.size(); windowSet.windows.erase( std::remove_if( windowSet.windows.begin(), windowSet.windows.end(), [windowId](const UIEditorWindowWorkspaceState& state) { return state.windowId == windowId; }), windowSet.windows.end()); if (windowSet.windows.size() == originalSize) { return false; } if (windowSet.windows.empty()) { windowSet = {}; return true; } if (windowSet.primaryWindowId == windowId || FindUIEditorWindowWorkspaceState(windowSet, windowSet.primaryWindowId) == nullptr) { windowSet.primaryWindowId = windowSet.windows.front().windowId; } if (windowSet.activeWindowId == windowId || FindUIEditorWindowWorkspaceState(windowSet, windowSet.activeWindowId) == nullptr) { windowSet.activeWindowId = windowSet.primaryWindowId; } return true; } } // namespace XCEngine::UI::Editor::App