From 701ddd9e9d0453345fb2bd47c07de92c6ed42501 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Wed, 15 Apr 2026 11:13:16 +0800 Subject: [PATCH] refactor(new_editor/workspace): split workspace controller responsibilities --- new_editor/CMakeLists.txt | 2 + .../Workspace/UIEditorWorkspaceController.cpp | 580 +----------------- .../UIEditorWorkspaceControllerDispatch.cpp | 239 ++++++++ .../UIEditorWorkspaceControllerInternal.h | 35 ++ .../UIEditorWorkspaceControllerLayoutOps.cpp | 419 +++++++++++++ 5 files changed, 704 insertions(+), 571 deletions(-) create mode 100644 new_editor/src/Workspace/UIEditorWorkspaceControllerDispatch.cpp create mode 100644 new_editor/src/Workspace/UIEditorWorkspaceControllerInternal.h create mode 100644 new_editor/src/Workspace/UIEditorWorkspaceControllerLayoutOps.cpp diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index bcbc28f6..596ffdab 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -100,6 +100,8 @@ set(XCUI_EDITOR_VIEWPORT_SOURCES set(XCUI_EDITOR_WORKSPACE_SOURCES src/Workspace/UIEditorWorkspaceCompose.cpp src/Workspace/UIEditorWorkspaceController.cpp + src/Workspace/UIEditorWorkspaceControllerDispatch.cpp + src/Workspace/UIEditorWorkspaceControllerLayoutOps.cpp src/Workspace/UIEditorWorkspaceInteraction.cpp src/Workspace/UIEditorWorkspaceLayoutPersistence.cpp src/Workspace/UIEditorWorkspaceModel.cpp diff --git a/new_editor/src/Workspace/UIEditorWorkspaceController.cpp b/new_editor/src/Workspace/UIEditorWorkspaceController.cpp index cd0cf30e..3742ec50 100644 --- a/new_editor/src/Workspace/UIEditorWorkspaceController.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceController.cpp @@ -1,13 +1,8 @@ -#include -#include +#include "Workspace/UIEditorWorkspaceControllerInternal.h" -#include -#include #include -namespace XCEngine::UI::Editor { - -namespace { +namespace XCEngine::UI::Editor::Detail { bool IsPanelOpenAndVisible( const UIEditorWorkspaceSession& session, @@ -32,13 +27,6 @@ std::vector CollectVisiblePanelIds( return ids; } -struct VisibleTabStackInfo { - bool panelExists = false; - bool panelVisible = false; - std::size_t currentVisibleIndex = 0u; - std::size_t visibleTabCount = 0u; -}; - VisibleTabStackInfo ResolveVisibleTabStackInfo( const UIEditorWorkspaceNode& node, const UIEditorWorkspaceSession& session, @@ -84,7 +72,9 @@ std::size_t CountVisibleTabs( return visibleCount; } -} // namespace +} // namespace XCEngine::UI::Editor::Detail + +namespace XCEngine::UI::Editor { std::string_view GetUIEditorWorkspaceCommandKindName(UIEditorWorkspaceCommandKind kind) { switch (kind) { @@ -105,7 +95,8 @@ std::string_view GetUIEditorWorkspaceCommandKindName(UIEditorWorkspaceCommandKin return "Unknown"; } -std::string_view GetUIEditorWorkspaceCommandStatusName(UIEditorWorkspaceCommandStatus status) { +std::string_view GetUIEditorWorkspaceCommandStatusName( + UIEditorWorkspaceCommandStatus status) { switch (status) { case UIEditorWorkspaceCommandStatus::Changed: return "Changed"; @@ -188,7 +179,7 @@ UIEditorWorkspaceCommandResult UIEditorWorkspaceController::BuildResult( result.panelId = command.panelId; result.message = std::move(message); result.activePanelId = m_workspace.activePanelId; - result.visiblePanelIds = CollectVisiblePanelIds(m_workspace, m_session); + result.visiblePanelIds = Detail::CollectVisiblePanelIds(m_workspace, m_session); return result; } @@ -199,7 +190,7 @@ UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::BuildLayoutO result.status = status; result.message = std::move(message); result.activePanelId = m_workspace.activePanelId; - result.visiblePanelIds = CollectVisiblePanelIds(m_workspace, m_session); + result.visiblePanelIds = Detail::CollectVisiblePanelIds(m_workspace, m_session); return result; } @@ -238,559 +229,6 @@ const UIEditorPanelDescriptor* UIEditorWorkspaceController::FindPanelDescriptor( return FindUIEditorPanelDescriptor(m_panelRegistry, panelId); } -UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::RestoreLayoutSnapshot( - const UIEditorWorkspaceLayoutSnapshot& snapshot) { - UIEditorWorkspaceLayoutSnapshot canonicalSnapshot = snapshot; - canonicalSnapshot.workspace = - CanonicalizeUIEditorWorkspaceModel(std::move(canonicalSnapshot.workspace)); - - const UIEditorPanelRegistryValidationResult registryValidation = - ValidateUIEditorPanelRegistry(m_panelRegistry); - if (!registryValidation.IsValid()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "Panel registry invalid: " + registryValidation.message); - } - - const UIEditorWorkspaceValidationResult workspaceValidation = - ValidateUIEditorWorkspace(canonicalSnapshot.workspace); - if (!workspaceValidation.IsValid()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "Layout workspace invalid: " + workspaceValidation.message); - } - - const UIEditorWorkspaceSessionValidationResult sessionValidation = - ValidateUIEditorWorkspaceSession( - m_panelRegistry, - canonicalSnapshot.workspace, - canonicalSnapshot.session); - if (!sessionValidation.IsValid()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "Layout session invalid: " + sessionValidation.message); - } - - if (AreUIEditorWorkspaceModelsEquivalent(m_workspace, canonicalSnapshot.workspace) && - AreUIEditorWorkspaceSessionsEquivalent(m_session, canonicalSnapshot.session)) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::NoOp, - "Current state already matches the requested layout snapshot."); - } - - const UIEditorWorkspaceModel previousWorkspace = m_workspace; - const UIEditorWorkspaceSession previousSession = m_session; - m_workspace = canonicalSnapshot.workspace; - m_session = canonicalSnapshot.session; - - const UIEditorWorkspaceControllerValidationResult validation = ValidateState(); - if (!validation.IsValid()) { - m_workspace = previousWorkspace; - m_session = previousSession; - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "Restored layout produced invalid controller state: " + validation.message); - } - - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Changed, - "Layout restored."); -} - -UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::RestoreSerializedLayout( - std::string_view serializedLayout) { - const UIEditorWorkspaceLayoutLoadResult loadResult = - DeserializeUIEditorWorkspaceLayoutSnapshot(m_panelRegistry, serializedLayout); - if (!loadResult.IsValid()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "Serialized layout rejected: " + loadResult.message); - } - - return RestoreLayoutSnapshot(loadResult.snapshot); -} - -UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::SetSplitRatio( - std::string_view nodeId, - float splitRatio) { - const UIEditorWorkspaceControllerValidationResult validation = ValidateState(); - if (!validation.IsValid()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "Controller state invalid: " + validation.message); - } - - if (nodeId.empty()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "SetSplitRatio requires a split node id."); - } - - const UIEditorWorkspaceNode* splitNode = FindUIEditorWorkspaceNode(m_workspace, nodeId); - if (splitNode == nullptr || splitNode->kind != UIEditorWorkspaceNodeKind::Split) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "SetSplitRatio target split node is missing."); - } - - if (std::fabs(splitNode->splitRatio - splitRatio) <= 0.0001f) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::NoOp, - "Split ratio already matches the requested value."); - } - - const UIEditorWorkspaceModel previousWorkspace = m_workspace; - if (!TrySetUIEditorWorkspaceSplitRatio(m_workspace, nodeId, splitRatio)) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "Split ratio update rejected."); - } - - const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState(); - if (!postValidation.IsValid()) { - m_workspace = previousWorkspace; - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "Split ratio update produced invalid controller state: " + postValidation.message); - } - - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Changed, - "Split ratio updated."); -} - -UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::ReorderTab( - std::string_view nodeId, - std::string_view panelId, - std::size_t targetVisibleInsertionIndex) { - const UIEditorWorkspaceControllerValidationResult validation = ValidateState(); - if (!validation.IsValid()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "Controller state invalid: " + validation.message); - } - - if (nodeId.empty()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "ReorderTab requires a tab stack node id."); - } - - if (panelId.empty()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "ReorderTab requires a panel id."); - } - - const UIEditorWorkspaceNode* tabStack = FindUIEditorWorkspaceNode(m_workspace, nodeId); - if (tabStack == nullptr || tabStack->kind != UIEditorWorkspaceNodeKind::TabStack) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "ReorderTab target tab stack is missing."); - } - - const VisibleTabStackInfo tabInfo = - ResolveVisibleTabStackInfo(*tabStack, m_session, panelId); - if (!tabInfo.panelExists) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "ReorderTab target panel is missing from the specified tab stack."); - } - - if (!tabInfo.panelVisible) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "ReorderTab only supports open and visible tabs."); - } - - if (targetVisibleInsertionIndex > tabInfo.visibleTabCount) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "ReorderTab target visible insertion index is out of range."); - } - - if (targetVisibleInsertionIndex == tabInfo.currentVisibleIndex || - targetVisibleInsertionIndex == tabInfo.currentVisibleIndex + 1u) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::NoOp, - "Visible tab order already matches the requested insertion."); - } - - const UIEditorWorkspaceModel previousWorkspace = m_workspace; - if (!TryReorderUIEditorWorkspaceTab( - m_workspace, - m_session, - nodeId, - panelId, - targetVisibleInsertionIndex)) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "Tab reorder rejected."); - } - - if (AreUIEditorWorkspaceModelsEquivalent(previousWorkspace, m_workspace)) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::NoOp, - "Visible tab order already matches the requested insertion."); - } - - const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState(); - if (!postValidation.IsValid()) { - m_workspace = previousWorkspace; - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "Tab reorder produced invalid controller state: " + postValidation.message); - } - - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Changed, - "Tab reordered."); -} - -UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::MoveTabToStack( - std::string_view sourceNodeId, - std::string_view panelId, - std::string_view targetNodeId, - std::size_t targetVisibleInsertionIndex) { - { - std::ostringstream trace = {}; - trace << "MoveTabToStack begin sourceNode=" << sourceNodeId - << " panel=" << panelId - << " targetNode=" << targetNodeId - << " insertion=" << targetVisibleInsertionIndex; - AppendUIEditorRuntimeTrace("workspace", trace.str()); - } - const UIEditorWorkspaceControllerValidationResult validation = ValidateState(); - if (!validation.IsValid()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "Controller state invalid: " + validation.message); - } - - if (sourceNodeId.empty() || targetNodeId.empty()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "MoveTabToStack requires both source and target tab stack ids."); - } - - if (panelId.empty()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "MoveTabToStack requires a panel id."); - } - - if (sourceNodeId == targetNodeId) { - return ReorderTab(sourceNodeId, panelId, targetVisibleInsertionIndex); - } - - const UIEditorWorkspaceNode* sourceTabStack = - FindUIEditorWorkspaceNode(m_workspace, sourceNodeId); - const UIEditorWorkspaceNode* targetTabStack = - FindUIEditorWorkspaceNode(m_workspace, targetNodeId); - if (sourceTabStack == nullptr || - targetTabStack == nullptr || - sourceTabStack->kind != UIEditorWorkspaceNodeKind::TabStack || - targetTabStack->kind != UIEditorWorkspaceNodeKind::TabStack) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "MoveTabToStack source or target tab stack is missing."); - } - - const VisibleTabStackInfo sourceInfo = - ResolveVisibleTabStackInfo(*sourceTabStack, m_session, panelId); - if (!sourceInfo.panelExists) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "MoveTabToStack target panel is missing from the source tab stack."); - } - - if (!sourceInfo.panelVisible) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "MoveTabToStack only supports open and visible tabs."); - } - - const std::size_t visibleTargetCount = CountVisibleTabs(*targetTabStack, m_session); - if (targetVisibleInsertionIndex > visibleTargetCount) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "MoveTabToStack target visible insertion index is out of range."); - } - - const UIEditorWorkspaceModel previousWorkspace = m_workspace; - if (!TryMoveUIEditorWorkspaceTabToStack( - m_workspace, - m_session, - sourceNodeId, - panelId, - targetNodeId, - targetVisibleInsertionIndex)) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "MoveTabToStack rejected."); - } - - if (AreUIEditorWorkspaceModelsEquivalent(previousWorkspace, m_workspace)) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::NoOp, - "Tab already matches the requested target stack insertion."); - } - - const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState(); - if (!postValidation.IsValid()) { - m_workspace = previousWorkspace; - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "MoveTabToStack produced invalid controller state: " + postValidation.message); - } - - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Changed, - "Tab moved to target stack."); -} - -UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::DockTabRelative( - std::string_view sourceNodeId, - std::string_view panelId, - std::string_view targetNodeId, - UIEditorWorkspaceDockPlacement placement, - float splitRatio) { - { - std::ostringstream trace = {}; - trace << "DockTabRelative begin sourceNode=" << sourceNodeId - << " panel=" << panelId - << " targetNode=" << targetNodeId - << " placement=" << static_cast(placement) - << " splitRatio=" << splitRatio; - AppendUIEditorRuntimeTrace("workspace", trace.str()); - } - const UIEditorWorkspaceControllerValidationResult validation = ValidateState(); - if (!validation.IsValid()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "Controller state invalid: " + validation.message); - } - - if (sourceNodeId.empty() || targetNodeId.empty()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "DockTabRelative requires both source and target tab stack ids."); - } - - if (panelId.empty()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "DockTabRelative requires a panel id."); - } - - const UIEditorWorkspaceNode* sourceTabStack = - FindUIEditorWorkspaceNode(m_workspace, sourceNodeId); - const UIEditorWorkspaceNode* targetTabStack = - FindUIEditorWorkspaceNode(m_workspace, targetNodeId); - if (sourceTabStack == nullptr || - targetTabStack == nullptr || - sourceTabStack->kind != UIEditorWorkspaceNodeKind::TabStack || - targetTabStack->kind != UIEditorWorkspaceNodeKind::TabStack) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "DockTabRelative source or target tab stack is missing."); - } - - const VisibleTabStackInfo sourceInfo = - ResolveVisibleTabStackInfo(*sourceTabStack, m_session, panelId); - if (!sourceInfo.panelExists) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "DockTabRelative target panel is missing from the source tab stack."); - } - - if (!sourceInfo.panelVisible) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "DockTabRelative only supports open and visible tabs."); - } - - const UIEditorWorkspaceModel previousWorkspace = m_workspace; - if (!TryDockUIEditorWorkspaceTabRelative( - m_workspace, - m_session, - sourceNodeId, - panelId, - targetNodeId, - placement, - splitRatio)) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "DockTabRelative rejected."); - } - - if (AreUIEditorWorkspaceModelsEquivalent(previousWorkspace, m_workspace)) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::NoOp, - "Dock layout already matches the requested placement."); - } - - const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState(); - if (!postValidation.IsValid()) { - m_workspace = previousWorkspace; - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "DockTabRelative produced invalid controller state: " + postValidation.message); - } - - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Changed, - "Tab docked relative to target stack."); -} - -UIEditorWorkspaceCommandResult UIEditorWorkspaceController::Dispatch( - const UIEditorWorkspaceCommand& command) { - const UIEditorWorkspaceControllerValidationResult validation = ValidateState(); - if (command.kind != UIEditorWorkspaceCommandKind::ResetWorkspace && - !validation.IsValid()) { - return BuildResult( - command, - UIEditorWorkspaceCommandStatus::Rejected, - "Controller state invalid: " + validation.message); - } - - const UIEditorWorkspaceModel previousWorkspace = m_workspace; - const UIEditorWorkspaceSession previousSession = m_session; - const UIEditorPanelSessionState* panelState = - command.kind == UIEditorWorkspaceCommandKind::ResetWorkspace - ? nullptr - : FindUIEditorPanelSessionState(m_session, command.panelId); - const UIEditorPanelDescriptor* panelDescriptor = - command.kind == UIEditorWorkspaceCommandKind::ResetWorkspace - ? nullptr - : FindPanelDescriptor(command.panelId); - - switch (command.kind) { - case UIEditorWorkspaceCommandKind::OpenPanel: - if (command.panelId.empty()) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "OpenPanel requires a panelId."); - } - if (panelDescriptor == nullptr || panelState == nullptr) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "OpenPanel target panel is missing."); - } - if (panelState->open && panelState->visible) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::NoOp, "Panel is already open and visible."); - } - return FinalizeMutation( - command, - TryOpenUIEditorWorkspacePanel(m_panelRegistry, m_workspace, m_session, command.panelId), - "Panel opened and activated.", - "OpenPanel failed unexpectedly.", - previousWorkspace, - previousSession); - - case UIEditorWorkspaceCommandKind::ClosePanel: - if (command.panelId.empty()) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "ClosePanel requires a panelId."); - } - if (panelDescriptor == nullptr || panelState == nullptr) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "ClosePanel target panel is missing."); - } - if (!panelDescriptor->canClose) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "Panel cannot be closed."); - } - if (!panelState->open) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::NoOp, "Panel is already closed."); - } - return FinalizeMutation( - command, - TryCloseUIEditorWorkspacePanel(m_panelRegistry, m_workspace, m_session, command.panelId), - "Panel closed.", - "ClosePanel failed unexpectedly.", - previousWorkspace, - previousSession); - - case UIEditorWorkspaceCommandKind::ShowPanel: - if (command.panelId.empty()) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "ShowPanel requires a panelId."); - } - if (panelDescriptor == nullptr || panelState == nullptr) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "ShowPanel target panel is missing."); - } - if (!panelState->open) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "Closed panel must be opened before it can be shown."); - } - if (panelState->visible) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::NoOp, "Panel is already visible."); - } - return FinalizeMutation( - command, - TryShowUIEditorWorkspacePanel(m_panelRegistry, m_workspace, m_session, command.panelId), - "Panel shown and activated.", - "ShowPanel failed unexpectedly.", - previousWorkspace, - previousSession); - - case UIEditorWorkspaceCommandKind::HidePanel: - if (command.panelId.empty()) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "HidePanel requires a panelId."); - } - if (panelDescriptor == nullptr || panelState == nullptr) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "HidePanel target panel is missing."); - } - if (!panelDescriptor->canHide) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "Panel cannot be hidden."); - } - if (!panelState->open) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "Closed panel cannot be hidden."); - } - if (!panelState->visible) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::NoOp, "Panel is already hidden."); - } - return FinalizeMutation( - command, - TryHideUIEditorWorkspacePanel(m_panelRegistry, m_workspace, m_session, command.panelId), - "Panel hidden and active panel re-resolved.", - "HidePanel failed unexpectedly.", - previousWorkspace, - previousSession); - - case UIEditorWorkspaceCommandKind::ActivatePanel: - if (command.panelId.empty()) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "ActivatePanel requires a panelId."); - } - if (panelDescriptor == nullptr || panelState == nullptr) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "ActivatePanel target panel is missing."); - } - if (!panelState->open || !panelState->visible) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "Only open and visible panels can be activated."); - } - if (m_workspace.activePanelId == command.panelId) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::NoOp, "Panel is already active."); - } - return FinalizeMutation( - command, - TryActivateUIEditorWorkspacePanel(m_panelRegistry, m_workspace, m_session, command.panelId), - "Panel activated.", - "ActivatePanel failed unexpectedly.", - previousWorkspace, - previousSession); - - case UIEditorWorkspaceCommandKind::ResetWorkspace: - if (AreUIEditorWorkspaceModelsEquivalent(m_workspace, m_baselineWorkspace) && - AreUIEditorWorkspaceSessionsEquivalent(m_session, m_baselineSession)) { - return BuildResult(command, UIEditorWorkspaceCommandStatus::NoOp, "Workspace already matches the baseline state."); - } - - m_workspace = m_baselineWorkspace; - m_session = m_baselineSession; - return FinalizeMutation( - command, - true, - "Workspace reset to baseline.", - "ResetWorkspace failed unexpectedly.", - previousWorkspace, - previousSession); - } - - return BuildResult(command, UIEditorWorkspaceCommandStatus::Rejected, "Unknown command kind."); -} - UIEditorWorkspaceController BuildDefaultUIEditorWorkspaceController( const UIEditorPanelRegistry& panelRegistry, const UIEditorWorkspaceModel& workspace) { diff --git a/new_editor/src/Workspace/UIEditorWorkspaceControllerDispatch.cpp b/new_editor/src/Workspace/UIEditorWorkspaceControllerDispatch.cpp new file mode 100644 index 00000000..ed2a5baa --- /dev/null +++ b/new_editor/src/Workspace/UIEditorWorkspaceControllerDispatch.cpp @@ -0,0 +1,239 @@ +#include + +namespace XCEngine::UI::Editor { + +UIEditorWorkspaceCommandResult UIEditorWorkspaceController::Dispatch( + const UIEditorWorkspaceCommand& command) { + const UIEditorWorkspaceControllerValidationResult validation = ValidateState(); + if (command.kind != UIEditorWorkspaceCommandKind::ResetWorkspace && + !validation.IsValid()) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::Rejected, + "Controller state invalid: " + validation.message); + } + + const UIEditorWorkspaceModel previousWorkspace = m_workspace; + const UIEditorWorkspaceSession previousSession = m_session; + const UIEditorPanelSessionState* panelState = + command.kind == UIEditorWorkspaceCommandKind::ResetWorkspace + ? nullptr + : FindUIEditorPanelSessionState(m_session, command.panelId); + const UIEditorPanelDescriptor* panelDescriptor = + command.kind == UIEditorWorkspaceCommandKind::ResetWorkspace + ? nullptr + : FindPanelDescriptor(command.panelId); + + switch (command.kind) { + case UIEditorWorkspaceCommandKind::OpenPanel: + if (command.panelId.empty()) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::Rejected, + "OpenPanel requires a panelId."); + } + if (panelDescriptor == nullptr || panelState == nullptr) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::Rejected, + "OpenPanel target panel is missing."); + } + if (panelState->open && panelState->visible) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::NoOp, + "Panel is already open and visible."); + } + return FinalizeMutation( + command, + TryOpenUIEditorWorkspacePanel( + m_panelRegistry, + m_workspace, + m_session, + command.panelId), + "Panel opened and activated.", + "OpenPanel failed unexpectedly.", + previousWorkspace, + previousSession); + + case UIEditorWorkspaceCommandKind::ClosePanel: + if (command.panelId.empty()) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::Rejected, + "ClosePanel requires a panelId."); + } + if (panelDescriptor == nullptr || panelState == nullptr) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::Rejected, + "ClosePanel target panel is missing."); + } + if (!panelDescriptor->canClose) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::Rejected, + "Panel cannot be closed."); + } + if (!panelState->open) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::NoOp, + "Panel is already closed."); + } + return FinalizeMutation( + command, + TryCloseUIEditorWorkspacePanel( + m_panelRegistry, + m_workspace, + m_session, + command.panelId), + "Panel closed.", + "ClosePanel failed unexpectedly.", + previousWorkspace, + previousSession); + + case UIEditorWorkspaceCommandKind::ShowPanel: + if (command.panelId.empty()) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::Rejected, + "ShowPanel requires a panelId."); + } + if (panelDescriptor == nullptr || panelState == nullptr) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::Rejected, + "ShowPanel target panel is missing."); + } + if (!panelState->open) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::Rejected, + "Closed panel must be opened before it can be shown."); + } + if (panelState->visible) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::NoOp, + "Panel is already visible."); + } + return FinalizeMutation( + command, + TryShowUIEditorWorkspacePanel( + m_panelRegistry, + m_workspace, + m_session, + command.panelId), + "Panel shown and activated.", + "ShowPanel failed unexpectedly.", + previousWorkspace, + previousSession); + + case UIEditorWorkspaceCommandKind::HidePanel: + if (command.panelId.empty()) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::Rejected, + "HidePanel requires a panelId."); + } + if (panelDescriptor == nullptr || panelState == nullptr) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::Rejected, + "HidePanel target panel is missing."); + } + if (!panelDescriptor->canHide) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::Rejected, + "Panel cannot be hidden."); + } + if (!panelState->open) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::Rejected, + "Closed panel cannot be hidden."); + } + if (!panelState->visible) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::NoOp, + "Panel is already hidden."); + } + return FinalizeMutation( + command, + TryHideUIEditorWorkspacePanel( + m_panelRegistry, + m_workspace, + m_session, + command.panelId), + "Panel hidden and active panel re-resolved.", + "HidePanel failed unexpectedly.", + previousWorkspace, + previousSession); + + case UIEditorWorkspaceCommandKind::ActivatePanel: + if (command.panelId.empty()) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::Rejected, + "ActivatePanel requires a panelId."); + } + if (panelDescriptor == nullptr || panelState == nullptr) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::Rejected, + "ActivatePanel target panel is missing."); + } + if (!panelState->open || !panelState->visible) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::Rejected, + "Only open and visible panels can be activated."); + } + if (m_workspace.activePanelId == command.panelId) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::NoOp, + "Panel is already active."); + } + return FinalizeMutation( + command, + TryActivateUIEditorWorkspacePanel( + m_panelRegistry, + m_workspace, + m_session, + command.panelId), + "Panel activated.", + "ActivatePanel failed unexpectedly.", + previousWorkspace, + previousSession); + + case UIEditorWorkspaceCommandKind::ResetWorkspace: + if (AreUIEditorWorkspaceModelsEquivalent(m_workspace, m_baselineWorkspace) && + AreUIEditorWorkspaceSessionsEquivalent(m_session, m_baselineSession)) { + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::NoOp, + "Workspace already matches the baseline state."); + } + + m_workspace = m_baselineWorkspace; + m_session = m_baselineSession; + return FinalizeMutation( + command, + true, + "Workspace reset to baseline.", + "ResetWorkspace failed unexpectedly.", + previousWorkspace, + previousSession); + } + + return BuildResult( + command, + UIEditorWorkspaceCommandStatus::Rejected, + "Unknown command kind."); +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Workspace/UIEditorWorkspaceControllerInternal.h b/new_editor/src/Workspace/UIEditorWorkspaceControllerInternal.h new file mode 100644 index 00000000..37e3d87b --- /dev/null +++ b/new_editor/src/Workspace/UIEditorWorkspaceControllerInternal.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor::Detail { + +bool IsPanelOpenAndVisible( + const UIEditorWorkspaceSession& session, + std::string_view panelId); + +std::vector CollectVisiblePanelIds( + const UIEditorWorkspaceModel& workspace, + const UIEditorWorkspaceSession& session); + +struct VisibleTabStackInfo { + bool panelExists = false; + bool panelVisible = false; + std::size_t currentVisibleIndex = 0u; + std::size_t visibleTabCount = 0u; +}; + +VisibleTabStackInfo ResolveVisibleTabStackInfo( + const UIEditorWorkspaceNode& node, + const UIEditorWorkspaceSession& session, + std::string_view panelId); + +std::size_t CountVisibleTabs( + const UIEditorWorkspaceNode& node, + const UIEditorWorkspaceSession& session); + +} // namespace XCEngine::UI::Editor::Detail diff --git a/new_editor/src/Workspace/UIEditorWorkspaceControllerLayoutOps.cpp b/new_editor/src/Workspace/UIEditorWorkspaceControllerLayoutOps.cpp new file mode 100644 index 00000000..3ee8ad6d --- /dev/null +++ b/new_editor/src/Workspace/UIEditorWorkspaceControllerLayoutOps.cpp @@ -0,0 +1,419 @@ +#include "Workspace/UIEditorWorkspaceControllerInternal.h" + +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor { + +UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::RestoreLayoutSnapshot( + const UIEditorWorkspaceLayoutSnapshot& snapshot) { + UIEditorWorkspaceLayoutSnapshot canonicalSnapshot = snapshot; + canonicalSnapshot.workspace = + CanonicalizeUIEditorWorkspaceModel(std::move(canonicalSnapshot.workspace)); + + const UIEditorPanelRegistryValidationResult registryValidation = + ValidateUIEditorPanelRegistry(m_panelRegistry); + if (!registryValidation.IsValid()) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "Panel registry invalid: " + registryValidation.message); + } + + const UIEditorWorkspaceValidationResult workspaceValidation = + ValidateUIEditorWorkspace(canonicalSnapshot.workspace); + if (!workspaceValidation.IsValid()) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "Layout workspace invalid: " + workspaceValidation.message); + } + + const UIEditorWorkspaceSessionValidationResult sessionValidation = + ValidateUIEditorWorkspaceSession( + m_panelRegistry, + canonicalSnapshot.workspace, + canonicalSnapshot.session); + if (!sessionValidation.IsValid()) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "Layout session invalid: " + sessionValidation.message); + } + + if (AreUIEditorWorkspaceModelsEquivalent(m_workspace, canonicalSnapshot.workspace) && + AreUIEditorWorkspaceSessionsEquivalent(m_session, canonicalSnapshot.session)) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::NoOp, + "Current state already matches the requested layout snapshot."); + } + + const UIEditorWorkspaceModel previousWorkspace = m_workspace; + const UIEditorWorkspaceSession previousSession = m_session; + m_workspace = canonicalSnapshot.workspace; + m_session = canonicalSnapshot.session; + + const UIEditorWorkspaceControllerValidationResult validation = ValidateState(); + if (!validation.IsValid()) { + m_workspace = previousWorkspace; + m_session = previousSession; + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "Restored layout produced invalid controller state: " + validation.message); + } + + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Changed, + "Layout restored."); +} + +UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::RestoreSerializedLayout( + std::string_view serializedLayout) { + const UIEditorWorkspaceLayoutLoadResult loadResult = + DeserializeUIEditorWorkspaceLayoutSnapshot(m_panelRegistry, serializedLayout); + if (!loadResult.IsValid()) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "Serialized layout rejected: " + loadResult.message); + } + + return RestoreLayoutSnapshot(loadResult.snapshot); +} + +UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::SetSplitRatio( + std::string_view nodeId, + float splitRatio) { + const UIEditorWorkspaceControllerValidationResult validation = ValidateState(); + if (!validation.IsValid()) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "Controller state invalid: " + validation.message); + } + + if (nodeId.empty()) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "SetSplitRatio requires a split node id."); + } + + const UIEditorWorkspaceNode* splitNode = FindUIEditorWorkspaceNode(m_workspace, nodeId); + if (splitNode == nullptr || splitNode->kind != UIEditorWorkspaceNodeKind::Split) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "SetSplitRatio target split node is missing."); + } + + if (std::fabs(splitNode->splitRatio - splitRatio) <= 0.0001f) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::NoOp, + "Split ratio already matches the requested value."); + } + + const UIEditorWorkspaceModel previousWorkspace = m_workspace; + if (!TrySetUIEditorWorkspaceSplitRatio(m_workspace, nodeId, splitRatio)) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "Split ratio update rejected."); + } + + const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState(); + if (!postValidation.IsValid()) { + m_workspace = previousWorkspace; + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "Split ratio update produced invalid controller state: " + postValidation.message); + } + + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Changed, + "Split ratio updated."); +} + +UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::ReorderTab( + std::string_view nodeId, + std::string_view panelId, + std::size_t targetVisibleInsertionIndex) { + const UIEditorWorkspaceControllerValidationResult validation = ValidateState(); + if (!validation.IsValid()) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "Controller state invalid: " + validation.message); + } + + if (nodeId.empty()) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "ReorderTab requires a tab stack node id."); + } + + if (panelId.empty()) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "ReorderTab requires a panel id."); + } + + const UIEditorWorkspaceNode* tabStack = FindUIEditorWorkspaceNode(m_workspace, nodeId); + if (tabStack == nullptr || tabStack->kind != UIEditorWorkspaceNodeKind::TabStack) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "ReorderTab target tab stack is missing."); + } + + const Detail::VisibleTabStackInfo tabInfo = + Detail::ResolveVisibleTabStackInfo(*tabStack, m_session, panelId); + if (!tabInfo.panelExists) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "ReorderTab target panel is missing from the specified tab stack."); + } + + if (!tabInfo.panelVisible) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "ReorderTab only supports open and visible tabs."); + } + + if (targetVisibleInsertionIndex > tabInfo.visibleTabCount) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "ReorderTab target visible insertion index is out of range."); + } + + if (targetVisibleInsertionIndex == tabInfo.currentVisibleIndex || + targetVisibleInsertionIndex == tabInfo.currentVisibleIndex + 1u) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::NoOp, + "Visible tab order already matches the requested insertion."); + } + + const UIEditorWorkspaceModel previousWorkspace = m_workspace; + if (!TryReorderUIEditorWorkspaceTab( + m_workspace, + m_session, + nodeId, + panelId, + targetVisibleInsertionIndex)) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "Tab reorder rejected."); + } + + if (AreUIEditorWorkspaceModelsEquivalent(previousWorkspace, m_workspace)) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::NoOp, + "Visible tab order already matches the requested insertion."); + } + + const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState(); + if (!postValidation.IsValid()) { + m_workspace = previousWorkspace; + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "Tab reorder produced invalid controller state: " + postValidation.message); + } + + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Changed, + "Tab reordered."); +} + +UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::MoveTabToStack( + std::string_view sourceNodeId, + std::string_view panelId, + std::string_view targetNodeId, + std::size_t targetVisibleInsertionIndex) { + { + std::ostringstream trace = {}; + trace << "MoveTabToStack begin sourceNode=" << sourceNodeId + << " panel=" << panelId + << " targetNode=" << targetNodeId + << " insertion=" << targetVisibleInsertionIndex; + AppendUIEditorRuntimeTrace("workspace", trace.str()); + } + + const UIEditorWorkspaceControllerValidationResult validation = ValidateState(); + if (!validation.IsValid()) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "Controller state invalid: " + validation.message); + } + + if (sourceNodeId.empty() || targetNodeId.empty()) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "MoveTabToStack requires both source and target tab stack ids."); + } + + if (panelId.empty()) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "MoveTabToStack requires a panel id."); + } + + if (sourceNodeId == targetNodeId) { + return ReorderTab(sourceNodeId, panelId, targetVisibleInsertionIndex); + } + + const UIEditorWorkspaceNode* sourceTabStack = + FindUIEditorWorkspaceNode(m_workspace, sourceNodeId); + const UIEditorWorkspaceNode* targetTabStack = + FindUIEditorWorkspaceNode(m_workspace, targetNodeId); + if (sourceTabStack == nullptr || + targetTabStack == nullptr || + sourceTabStack->kind != UIEditorWorkspaceNodeKind::TabStack || + targetTabStack->kind != UIEditorWorkspaceNodeKind::TabStack) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "MoveTabToStack source or target tab stack is missing."); + } + + const Detail::VisibleTabStackInfo sourceInfo = + Detail::ResolveVisibleTabStackInfo(*sourceTabStack, m_session, panelId); + if (!sourceInfo.panelExists) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "MoveTabToStack target panel is missing from the source tab stack."); + } + + if (!sourceInfo.panelVisible) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "MoveTabToStack only supports open and visible tabs."); + } + + const std::size_t visibleTargetCount = + Detail::CountVisibleTabs(*targetTabStack, m_session); + if (targetVisibleInsertionIndex > visibleTargetCount) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "MoveTabToStack target visible insertion index is out of range."); + } + + const UIEditorWorkspaceModel previousWorkspace = m_workspace; + if (!TryMoveUIEditorWorkspaceTabToStack( + m_workspace, + m_session, + sourceNodeId, + panelId, + targetNodeId, + targetVisibleInsertionIndex)) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "MoveTabToStack rejected."); + } + + if (AreUIEditorWorkspaceModelsEquivalent(previousWorkspace, m_workspace)) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::NoOp, + "Tab already matches the requested target stack insertion."); + } + + const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState(); + if (!postValidation.IsValid()) { + m_workspace = previousWorkspace; + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "MoveTabToStack produced invalid controller state: " + postValidation.message); + } + + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Changed, + "Tab moved to target stack."); +} + +UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::DockTabRelative( + std::string_view sourceNodeId, + std::string_view panelId, + std::string_view targetNodeId, + UIEditorWorkspaceDockPlacement placement, + float splitRatio) { + { + std::ostringstream trace = {}; + trace << "DockTabRelative begin sourceNode=" << sourceNodeId + << " panel=" << panelId + << " targetNode=" << targetNodeId + << " placement=" << static_cast(placement) + << " splitRatio=" << splitRatio; + AppendUIEditorRuntimeTrace("workspace", trace.str()); + } + + const UIEditorWorkspaceControllerValidationResult validation = ValidateState(); + if (!validation.IsValid()) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "Controller state invalid: " + validation.message); + } + + if (sourceNodeId.empty() || targetNodeId.empty()) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "DockTabRelative requires both source and target tab stack ids."); + } + + if (panelId.empty()) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "DockTabRelative requires a panel id."); + } + + const UIEditorWorkspaceNode* sourceTabStack = + FindUIEditorWorkspaceNode(m_workspace, sourceNodeId); + const UIEditorWorkspaceNode* targetTabStack = + FindUIEditorWorkspaceNode(m_workspace, targetNodeId); + if (sourceTabStack == nullptr || + targetTabStack == nullptr || + sourceTabStack->kind != UIEditorWorkspaceNodeKind::TabStack || + targetTabStack->kind != UIEditorWorkspaceNodeKind::TabStack) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "DockTabRelative source or target tab stack is missing."); + } + + const Detail::VisibleTabStackInfo sourceInfo = + Detail::ResolveVisibleTabStackInfo(*sourceTabStack, m_session, panelId); + if (!sourceInfo.panelExists) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "DockTabRelative target panel is missing from the source tab stack."); + } + + if (!sourceInfo.panelVisible) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "DockTabRelative only supports open and visible tabs."); + } + + const UIEditorWorkspaceModel previousWorkspace = m_workspace; + if (!TryDockUIEditorWorkspaceTabRelative( + m_workspace, + m_session, + sourceNodeId, + panelId, + targetNodeId, + placement, + splitRatio)) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "DockTabRelative rejected."); + } + + if (AreUIEditorWorkspaceModelsEquivalent(previousWorkspace, m_workspace)) { + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::NoOp, + "Dock layout already matches the requested placement."); + } + + const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState(); + if (!postValidation.IsValid()) { + m_workspace = previousWorkspace; + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "DockTabRelative produced invalid controller state: " + postValidation.message); + } + + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Changed, + "Tab docked relative to target stack."); +} + +} // namespace XCEngine::UI::Editor