#include #include namespace XCEngine::NewEditor { namespace { bool AreWorkspaceNodesEquivalent( const UIEditorWorkspaceNode& lhs, const UIEditorWorkspaceNode& rhs) { if (lhs.kind != rhs.kind || lhs.nodeId != rhs.nodeId || lhs.splitAxis != rhs.splitAxis || lhs.splitRatio != rhs.splitRatio || lhs.selectedTabIndex != rhs.selectedTabIndex || lhs.panel.panelId != rhs.panel.panelId || lhs.panel.title != rhs.panel.title || lhs.panel.placeholder != rhs.panel.placeholder || lhs.children.size() != rhs.children.size()) { return false; } for (std::size_t index = 0; index < lhs.children.size(); ++index) { if (!AreWorkspaceNodesEquivalent(lhs.children[index], rhs.children[index])) { return false; } } return true; } bool AreWorkspaceModelsEquivalent( const UIEditorWorkspaceModel& lhs, const UIEditorWorkspaceModel& rhs) { return lhs.activePanelId == rhs.activePanelId && AreWorkspaceNodesEquivalent(lhs.root, rhs.root); } bool AreWorkspaceSessionsEquivalent( const UIEditorWorkspaceSession& lhs, const UIEditorWorkspaceSession& rhs) { if (lhs.panelStates.size() != rhs.panelStates.size()) { return false; } for (std::size_t index = 0; index < lhs.panelStates.size(); ++index) { const UIEditorPanelSessionState& lhsState = lhs.panelStates[index]; const UIEditorPanelSessionState& rhsState = rhs.panelStates[index]; if (lhsState.panelId != rhsState.panelId || lhsState.open != rhsState.open || lhsState.visible != rhsState.visible) { return false; } } return true; } 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; } } // 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"; } UIEditorWorkspaceController::UIEditorWorkspaceController( UIEditorPanelRegistry panelRegistry, UIEditorWorkspaceModel workspace, UIEditorWorkspaceSession session) : m_panelRegistry(std::move(panelRegistry)) , m_baselineWorkspace(workspace) , m_baselineSession(session) , m_workspace(std::move(workspace)) , 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 {}; } 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; } 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); } 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 (AreWorkspaceModelsEquivalent(m_workspace, m_baselineWorkspace) && AreWorkspaceSessionsEquivalent(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) { return UIEditorWorkspaceController( panelRegistry, workspace, BuildDefaultUIEditorWorkspaceSession(panelRegistry, workspace)); } } // namespace XCEngine::NewEditor