#include #include #include #include #include namespace XCEngine::UI::Editor { namespace { UIEditorWorkspaceSessionValidationResult MakeValidationError( UIEditorWorkspaceSessionValidationCode code, std::string message) { UIEditorWorkspaceSessionValidationResult result = {}; result.code = code; result.message = std::move(message); return result; } UIEditorPanelSessionState* FindMutablePanelSessionState( UIEditorWorkspaceSession& session, std::string_view panelId) { for (UIEditorPanelSessionState& state : session.panelStates) { if (state.panelId == panelId) { return &state; } } return nullptr; } const UIEditorPanelDescriptor* FindPanelDescriptor( const UIEditorPanelRegistry& panelRegistry, std::string_view panelId) { return FindUIEditorPanelDescriptor(panelRegistry, panelId); } const UIEditorWorkspacePanelState* FindPanelRecursive( const UIEditorWorkspaceNode& node, std::string_view panelId) { if (node.kind == UIEditorWorkspaceNodeKind::Panel) { return node.panel.panelId == panelId ? &node.panel : nullptr; } for (const UIEditorWorkspaceNode& child : node.children) { if (const UIEditorWorkspacePanelState* found = FindPanelRecursive(child, panelId)) { return found; } } return nullptr; } void CollectWorkspacePanelIdsRecursive( const UIEditorWorkspaceNode& node, std::vector& outPanelIds) { if (node.kind == UIEditorWorkspaceNodeKind::Panel) { outPanelIds.push_back(node.panel.panelId); return; } for (const UIEditorWorkspaceNode& child : node.children) { CollectWorkspacePanelIdsRecursive(child, outPanelIds); } } bool IsPanelOpenAndVisible( const UIEditorWorkspaceSession& session, std::string_view panelId) { const UIEditorPanelSessionState* state = FindUIEditorPanelSessionState(session, panelId); return state != nullptr && state->open && state->visible; } bool IsPanelSelectable( const UIEditorWorkspaceModel& workspace, const UIEditorWorkspaceSession& session, std::string_view panelId) { return !panelId.empty() && IsPanelOpenAndVisible(session, panelId) && ContainsUIEditorWorkspacePanel(workspace, panelId); } std::size_t ResolveVisibleTabIndex( const UIEditorWorkspaceNode& node, const UIEditorWorkspaceSession& session) { if (node.kind != UIEditorWorkspaceNodeKind::TabStack || node.children.empty()) { return node.selectedTabIndex; } if (node.selectedTabIndex < node.children.size()) { const UIEditorWorkspaceNode& selectedChild = node.children[node.selectedTabIndex]; if (selectedChild.kind == UIEditorWorkspaceNodeKind::Panel && IsPanelOpenAndVisible(session, selectedChild.panel.panelId)) { return node.selectedTabIndex; } } for (std::size_t index = 0; index < node.children.size(); ++index) { const UIEditorWorkspaceNode& child = node.children[index]; if (child.kind == UIEditorWorkspaceNodeKind::Panel && IsPanelOpenAndVisible(session, child.panel.panelId)) { return index; } } return node.children.size(); } void CollectVisiblePanelsRecursive( const UIEditorWorkspaceNode& node, const UIEditorWorkspaceSession& session, std::string_view activePanelId, std::vector& outPanels) { switch (node.kind) { case UIEditorWorkspaceNodeKind::Panel: { if (!IsPanelOpenAndVisible(session, node.panel.panelId)) { return; } UIEditorWorkspaceVisiblePanel panel = {}; panel.panelId = node.panel.panelId; panel.title = node.panel.title; panel.active = node.panel.panelId == activePanelId; panel.placeholder = node.panel.placeholder; outPanels.push_back(std::move(panel)); return; } case UIEditorWorkspaceNodeKind::TabStack: { const std::size_t resolvedIndex = ResolveVisibleTabIndex(node, session); if (resolvedIndex < node.children.size()) { CollectVisiblePanelsRecursive( node.children[resolvedIndex], session, activePanelId, outPanels); } return; } case UIEditorWorkspaceNodeKind::Split: for (const UIEditorWorkspaceNode& child : node.children) { CollectVisiblePanelsRecursive(child, session, activePanelId, outPanels); } return; } } void NormalizeSessionStatesAgainstRegistry( const UIEditorPanelRegistry& panelRegistry, UIEditorWorkspaceSession& session) { for (UIEditorPanelSessionState& state : session.panelStates) { if (!state.open) { state.visible = false; } const UIEditorPanelDescriptor* descriptor = FindPanelDescriptor(panelRegistry, state.panelId); if (descriptor == nullptr) { continue; } if (!descriptor->canClose) { state.open = true; state.visible = true; continue; } if (!descriptor->canHide && state.open) { state.visible = true; } } } void NormalizeWorkspaceSession( const UIEditorPanelRegistry& panelRegistry, UIEditorWorkspaceModel& workspace, UIEditorWorkspaceSession& session, std::string_view preferredActivePanelId) { NormalizeSessionStatesAgainstRegistry(panelRegistry, session); std::string targetActivePanelId = {}; if (IsPanelSelectable(workspace, session, preferredActivePanelId)) { targetActivePanelId = std::string(preferredActivePanelId); } else if (IsPanelSelectable(workspace, session, workspace.activePanelId)) { targetActivePanelId = workspace.activePanelId; } else { const std::vector visiblePanels = CollectUIEditorWorkspaceVisiblePanels(workspace, session); if (!visiblePanels.empty()) { targetActivePanelId = visiblePanels.front().panelId; } } if (targetActivePanelId.empty()) { workspace.activePanelId.clear(); return; } TryActivateUIEditorWorkspacePanel(workspace, targetActivePanelId); } } // namespace UIEditorWorkspaceSession BuildDefaultUIEditorWorkspaceSession( const UIEditorPanelRegistry& panelRegistry, const UIEditorWorkspaceModel& workspace) { UIEditorWorkspaceSession session = {}; std::vector panelIds = {}; CollectWorkspacePanelIdsRecursive(workspace.root, panelIds); session.panelStates.reserve(panelIds.size()); for (std::string& panelId : panelIds) { UIEditorPanelSessionState state = {}; state.panelId = std::move(panelId); session.panelStates.push_back(std::move(state)); } NormalizeSessionStatesAgainstRegistry(panelRegistry, session); return session; } const UIEditorPanelSessionState* FindUIEditorPanelSessionState( const UIEditorWorkspaceSession& session, std::string_view panelId) { for (const UIEditorPanelSessionState& state : session.panelStates) { if (state.panelId == panelId) { return &state; } } return nullptr; } UIEditorWorkspaceSessionValidationResult ValidateUIEditorWorkspaceSession( const UIEditorPanelRegistry& panelRegistry, const UIEditorWorkspaceModel& workspace, const UIEditorWorkspaceSession& session) { std::vector workspacePanelIds = {}; CollectWorkspacePanelIdsRecursive(workspace.root, workspacePanelIds); std::unordered_set expectedPanelIds = {}; expectedPanelIds.insert(workspacePanelIds.begin(), workspacePanelIds.end()); std::unordered_set seenPanelIds = {}; for (const UIEditorPanelSessionState& state : session.panelStates) { if (!seenPanelIds.insert(state.panelId).second) { return MakeValidationError( UIEditorWorkspaceSessionValidationCode::DuplicatePanelId, "Workspace session contains duplicated panel state '" + state.panelId + "'."); } if (!expectedPanelIds.contains(state.panelId)) { return MakeValidationError( UIEditorWorkspaceSessionValidationCode::UnknownPanelId, "Workspace session state '" + state.panelId + "' is not present in the workspace tree."); } if (!state.open && state.visible) { return MakeValidationError( UIEditorWorkspaceSessionValidationCode::ClosedPanelVisible, "Workspace session state '" + state.panelId + "' cannot be visible while closed."); } const UIEditorPanelDescriptor* descriptor = FindPanelDescriptor(panelRegistry, state.panelId); if (descriptor == nullptr) { return MakeValidationError( UIEditorWorkspaceSessionValidationCode::UnknownPanelId, "Workspace session state '" + state.panelId + "' is missing from the panel registry."); } if (!descriptor->canClose && !state.open) { return MakeValidationError( UIEditorWorkspaceSessionValidationCode::NonCloseablePanelClosed, "Workspace session state '" + state.panelId + "' cannot be closed."); } if (!descriptor->canHide && state.open && !state.visible) { return MakeValidationError( UIEditorWorkspaceSessionValidationCode::NonHideablePanelHidden, "Workspace session state '" + state.panelId + "' cannot be hidden."); } } for (const std::string& panelId : workspacePanelIds) { if (!seenPanelIds.contains(panelId)) { return MakeValidationError( UIEditorWorkspaceSessionValidationCode::MissingPanelState, "Workspace panel '" + panelId + "' is missing from the workspace session."); } } if (!workspace.activePanelId.empty() && FindUIEditorWorkspaceActivePanel(workspace, session) == nullptr) { return MakeValidationError( UIEditorWorkspaceSessionValidationCode::InvalidActivePanelId, "Active panel id '" + workspace.activePanelId + "' is missing, closed, or hidden."); } return {}; } bool AreUIEditorWorkspaceSessionsEquivalent( 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 CollectUIEditorWorkspaceVisiblePanels( const UIEditorWorkspaceModel& workspace, const UIEditorWorkspaceSession& session) { std::vector visiblePanels = {}; CollectVisiblePanelsRecursive(workspace.root, session, workspace.activePanelId, visiblePanels); return visiblePanels; } const UIEditorWorkspacePanelState* FindUIEditorWorkspaceActivePanel( const UIEditorWorkspaceModel& workspace, const UIEditorWorkspaceSession& session) { if (workspace.activePanelId.empty() || !IsPanelOpenAndVisible(session, workspace.activePanelId)) { return nullptr; } const std::vector visiblePanels = CollectUIEditorWorkspaceVisiblePanels(workspace, session); for (const UIEditorWorkspaceVisiblePanel& panel : visiblePanels) { if (panel.panelId == workspace.activePanelId) { return FindPanelRecursive(workspace.root, workspace.activePanelId); } } return nullptr; } bool TryOpenUIEditorWorkspacePanel( const UIEditorPanelRegistry& panelRegistry, UIEditorWorkspaceModel& workspace, UIEditorWorkspaceSession& session, std::string_view panelId) { const UIEditorWorkspaceModel workspaceBefore = workspace; const UIEditorWorkspaceSession sessionBefore = session; UIEditorPanelSessionState* state = FindMutablePanelSessionState(session, panelId); if (state == nullptr) { return false; } state->open = true; state->visible = true; NormalizeWorkspaceSession(panelRegistry, workspace, session, panelId); return !AreUIEditorWorkspaceModelsEquivalent(workspaceBefore, workspace) || !AreUIEditorWorkspaceSessionsEquivalent(sessionBefore, session); } bool TryCloseUIEditorWorkspacePanel( const UIEditorPanelRegistry& panelRegistry, UIEditorWorkspaceModel& workspace, UIEditorWorkspaceSession& session, std::string_view panelId) { const UIEditorWorkspaceModel workspaceBefore = workspace; const UIEditorWorkspaceSession sessionBefore = session; UIEditorPanelSessionState* state = FindMutablePanelSessionState(session, panelId); const UIEditorPanelDescriptor* descriptor = FindPanelDescriptor(panelRegistry, panelId); if (state == nullptr || descriptor == nullptr || !descriptor->canClose) { return false; } state->open = false; state->visible = false; NormalizeWorkspaceSession(panelRegistry, workspace, session, {}); return !AreUIEditorWorkspaceModelsEquivalent(workspaceBefore, workspace) || !AreUIEditorWorkspaceSessionsEquivalent(sessionBefore, session); } bool TryShowUIEditorWorkspacePanel( const UIEditorPanelRegistry& panelRegistry, UIEditorWorkspaceModel& workspace, UIEditorWorkspaceSession& session, std::string_view panelId) { const UIEditorWorkspaceModel workspaceBefore = workspace; const UIEditorWorkspaceSession sessionBefore = session; UIEditorPanelSessionState* state = FindMutablePanelSessionState(session, panelId); const UIEditorPanelDescriptor* descriptor = FindPanelDescriptor(panelRegistry, panelId); if (state == nullptr || descriptor == nullptr || !state->open || !descriptor->canHide) { return false; } state->visible = true; NormalizeWorkspaceSession(panelRegistry, workspace, session, panelId); return !AreUIEditorWorkspaceModelsEquivalent(workspaceBefore, workspace) || !AreUIEditorWorkspaceSessionsEquivalent(sessionBefore, session); } bool TryHideUIEditorWorkspacePanel( const UIEditorPanelRegistry& panelRegistry, UIEditorWorkspaceModel& workspace, UIEditorWorkspaceSession& session, std::string_view panelId) { const UIEditorWorkspaceModel workspaceBefore = workspace; const UIEditorWorkspaceSession sessionBefore = session; UIEditorPanelSessionState* state = FindMutablePanelSessionState(session, panelId); const UIEditorPanelDescriptor* descriptor = FindPanelDescriptor(panelRegistry, panelId); if (state == nullptr || descriptor == nullptr || !state->open || !descriptor->canHide) { return false; } state->visible = false; NormalizeWorkspaceSession(panelRegistry, workspace, session, {}); return !AreUIEditorWorkspaceModelsEquivalent(workspaceBefore, workspace) || !AreUIEditorWorkspaceSessionsEquivalent(sessionBefore, session); } bool TryActivateUIEditorWorkspacePanel( const UIEditorPanelRegistry& panelRegistry, UIEditorWorkspaceModel& workspace, UIEditorWorkspaceSession& session, std::string_view panelId) { const UIEditorWorkspaceModel workspaceBefore = workspace; const UIEditorWorkspaceSession sessionBefore = session; if (!IsPanelSelectable(workspace, session, panelId)) { return false; } NormalizeWorkspaceSession(panelRegistry, workspace, session, panelId); return !AreUIEditorWorkspaceModelsEquivalent(workspaceBefore, workspace) || !AreUIEditorWorkspaceSessionsEquivalent(sessionBefore, session); } } // namespace XCEngine::UI::Editor