#include #include #include namespace XCEngine::UI::Editor { namespace { 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"; } 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(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 {}; } 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) { const UIEditorPanelRegistryValidationResult registryValidation = ValidateUIEditorPanelRegistry(m_panelRegistry); if (!registryValidation.IsValid()) { return BuildLayoutOperationResult( UIEditorWorkspaceLayoutOperationStatus::Rejected, "Panel registry invalid: " + registryValidation.message); } const UIEditorWorkspaceValidationResult workspaceValidation = ValidateUIEditorWorkspace(snapshot.workspace); if (!workspaceValidation.IsValid()) { return BuildLayoutOperationResult( UIEditorWorkspaceLayoutOperationStatus::Rejected, "Layout workspace invalid: " + workspaceValidation.message); } const UIEditorWorkspaceSessionValidationResult sessionValidation = ValidateUIEditorWorkspaceSession(m_panelRegistry, snapshot.workspace, snapshot.session); if (!sessionValidation.IsValid()) { return BuildLayoutOperationResult( UIEditorWorkspaceLayoutOperationStatus::Rejected, "Layout session invalid: " + sessionValidation.message); } if (AreUIEditorWorkspaceModelsEquivalent(m_workspace, snapshot.workspace) && AreUIEditorWorkspaceSessionsEquivalent(m_session, snapshot.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 = snapshot.workspace; m_session = snapshot.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."); } 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) { return UIEditorWorkspaceController( panelRegistry, workspace, BuildDefaultUIEditorWorkspaceSession(panelRegistry, workspace)); } } // namespace XCEngine::UI::Editor