#include #include #include #include #include namespace XCEngine::UI::Editor { namespace { bool IsPanelOpenAndVisible( const UIEditorWorkspaceSession& session, std::string_view panelId) { const UIEditorPanelSessionState* panelState = FindUIEditorPanelSessionState(session, panelId); return panelState != nullptr && panelState->open && panelState->visible; } std::vector CollectVisiblePanelIds( const UIEditorWorkspaceModel& workspace, const UIEditorWorkspaceSession& session) { const std::vector panels = CollectUIEditorWorkspaceVisiblePanels(workspace, session); std::vector ids = {}; ids.reserve(panels.size()); for (const UIEditorWorkspaceVisiblePanel& panel : panels) { ids.push_back(panel.panelId); } 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, std::string_view panelId) { VisibleTabStackInfo info = {}; for (const UIEditorWorkspaceNode& child : node.children) { if (child.kind != UIEditorWorkspaceNodeKind::Panel) { continue; } const bool visible = IsPanelOpenAndVisible(session, child.panel.panelId); if (child.panel.panelId == panelId) { info.panelExists = true; info.panelVisible = visible; if (visible) { info.currentVisibleIndex = info.visibleTabCount; } } if (visible) { ++info.visibleTabCount; } } return info; } std::size_t CountVisibleTabs( const UIEditorWorkspaceNode& node, const UIEditorWorkspaceSession& session) { if (node.kind != UIEditorWorkspaceNodeKind::TabStack) { return 0u; } std::size_t visibleCount = 0u; for (const UIEditorWorkspaceNode& child : node.children) { if (child.kind == UIEditorWorkspaceNodeKind::Panel && IsPanelOpenAndVisible(session, child.panel.panelId)) { ++visibleCount; } } return visibleCount; } } // namespace std::string_view GetUIEditorWorkspaceCommandKindName(UIEditorWorkspaceCommandKind kind) { switch (kind) { case UIEditorWorkspaceCommandKind::OpenPanel: return "OpenPanel"; case UIEditorWorkspaceCommandKind::ClosePanel: return "ClosePanel"; case UIEditorWorkspaceCommandKind::ShowPanel: return "ShowPanel"; case UIEditorWorkspaceCommandKind::HidePanel: return "HidePanel"; case UIEditorWorkspaceCommandKind::ActivatePanel: return "ActivatePanel"; case UIEditorWorkspaceCommandKind::ResetWorkspace: return "ResetWorkspace"; } return "Unknown"; } std::string_view GetUIEditorWorkspaceCommandStatusName(UIEditorWorkspaceCommandStatus status) { switch (status) { case UIEditorWorkspaceCommandStatus::Changed: return "Changed"; case UIEditorWorkspaceCommandStatus::NoOp: return "NoOp"; case UIEditorWorkspaceCommandStatus::Rejected: return "Rejected"; } return "Unknown"; } std::string_view GetUIEditorWorkspaceLayoutOperationStatusName( UIEditorWorkspaceLayoutOperationStatus status) { switch (status) { case UIEditorWorkspaceLayoutOperationStatus::Changed: return "Changed"; case UIEditorWorkspaceLayoutOperationStatus::NoOp: return "NoOp"; case UIEditorWorkspaceLayoutOperationStatus::Rejected: return "Rejected"; } return "Unknown"; } UIEditorWorkspaceController::UIEditorWorkspaceController( UIEditorPanelRegistry panelRegistry, UIEditorWorkspaceModel workspace, UIEditorWorkspaceSession session) : m_panelRegistry(std::move(panelRegistry)) , m_baselineWorkspace(CanonicalizeUIEditorWorkspaceModel(workspace)) , m_baselineSession(session) , m_workspace(m_baselineWorkspace) , m_session(std::move(session)) { } UIEditorWorkspaceControllerValidationResult UIEditorWorkspaceController::ValidateState() const { const UIEditorPanelRegistryValidationResult registryValidation = ValidateUIEditorPanelRegistry(m_panelRegistry); if (!registryValidation.IsValid()) { UIEditorWorkspaceControllerValidationResult result = {}; result.code = UIEditorWorkspaceControllerValidationCode::InvalidPanelRegistry; result.message = registryValidation.message; return result; } const UIEditorWorkspaceValidationResult workspaceValidation = ValidateUIEditorWorkspace(m_workspace); if (!workspaceValidation.IsValid()) { UIEditorWorkspaceControllerValidationResult result = {}; result.code = UIEditorWorkspaceControllerValidationCode::InvalidWorkspace; result.message = workspaceValidation.message; return result; } const UIEditorWorkspaceSessionValidationResult sessionValidation = ValidateUIEditorWorkspaceSession(m_panelRegistry, m_workspace, m_session); if (!sessionValidation.IsValid()) { UIEditorWorkspaceControllerValidationResult result = {}; result.code = UIEditorWorkspaceControllerValidationCode::InvalidWorkspaceSession; result.message = sessionValidation.message; return result; } return {}; } UIEditorWorkspaceLayoutSnapshot UIEditorWorkspaceController::CaptureLayoutSnapshot() const { return BuildUIEditorWorkspaceLayoutSnapshot(m_workspace, m_session); } UIEditorWorkspaceCommandResult UIEditorWorkspaceController::BuildResult( const UIEditorWorkspaceCommand& command, UIEditorWorkspaceCommandStatus status, std::string message) const { UIEditorWorkspaceCommandResult result = {}; result.kind = command.kind; result.status = status; result.panelId = command.panelId; result.message = std::move(message); result.activePanelId = m_workspace.activePanelId; result.visiblePanelIds = CollectVisiblePanelIds(m_workspace, m_session); return result; } UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::BuildLayoutOperationResult( UIEditorWorkspaceLayoutOperationStatus status, std::string message) const { UIEditorWorkspaceLayoutOperationResult result = {}; result.status = status; result.message = std::move(message); result.activePanelId = m_workspace.activePanelId; result.visiblePanelIds = CollectVisiblePanelIds(m_workspace, m_session); return result; } UIEditorWorkspaceCommandResult UIEditorWorkspaceController::FinalizeMutation( const UIEditorWorkspaceCommand& command, bool changed, std::string changedMessage, std::string unexpectedFailureMessage, const UIEditorWorkspaceModel& previousWorkspace, const UIEditorWorkspaceSession& previousSession) { if (!changed) { return BuildResult( command, UIEditorWorkspaceCommandStatus::Rejected, std::move(unexpectedFailureMessage)); } const UIEditorWorkspaceControllerValidationResult validation = ValidateState(); if (!validation.IsValid()) { m_workspace = previousWorkspace; m_session = previousSession; return BuildResult( command, UIEditorWorkspaceCommandStatus::Rejected, "Command produced invalid workspace state: " + validation.message); } return BuildResult( command, UIEditorWorkspaceCommandStatus::Changed, std::move(changedMessage)); } const UIEditorPanelDescriptor* UIEditorWorkspaceController::FindPanelDescriptor( std::string_view panelId) const { 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) { const UIEditorWorkspaceModel canonicalWorkspace = CanonicalizeUIEditorWorkspaceModel(workspace); return UIEditorWorkspaceController( panelRegistry, canonicalWorkspace, BuildDefaultUIEditorWorkspaceSession(panelRegistry, canonicalWorkspace)); } } // namespace XCEngine::UI::Editor