#include #include #include #include #include namespace XCEngine::NewEditor { namespace { UIEditorWorkspaceValidationResult MakeValidationError( UIEditorWorkspaceValidationCode code, std::string message) { UIEditorWorkspaceValidationResult result = {}; result.code = code; result.message = std::move(message); return result; } bool IsValidSplitRatio(float value) { return std::isfinite(value) && value > 0.0f && value < 1.0f; } const UIEditorPanelDescriptor& RequirePanelDescriptor( const UIEditorPanelRegistry& registry, std::string_view panelId) { if (const UIEditorPanelDescriptor* descriptor = FindUIEditorPanelDescriptor(registry, panelId); descriptor != nullptr) { return *descriptor; } static const UIEditorPanelDescriptor fallbackDescriptor = {}; return fallbackDescriptor; } 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; } bool TryActivateRecursive( UIEditorWorkspaceNode& node, std::string_view panelId) { switch (node.kind) { case UIEditorWorkspaceNodeKind::Panel: return node.panel.panelId == panelId; case UIEditorWorkspaceNodeKind::TabStack: for (std::size_t index = 0; index < node.children.size(); ++index) { UIEditorWorkspaceNode& child = node.children[index]; if (child.kind == UIEditorWorkspaceNodeKind::Panel && child.panel.panelId == panelId) { node.selectedTabIndex = index; return true; } } return false; case UIEditorWorkspaceNodeKind::Split: for (UIEditorWorkspaceNode& child : node.children) { if (TryActivateRecursive(child, panelId)) { return true; } } return false; } return false; } void CollectVisiblePanelsRecursive( const UIEditorWorkspaceNode& node, std::string_view activePanelId, std::vector& outPanels) { switch (node.kind) { case UIEditorWorkspaceNodeKind::Panel: { 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: if (node.selectedTabIndex < node.children.size()) { CollectVisiblePanelsRecursive( node.children[node.selectedTabIndex], activePanelId, outPanels); } return; case UIEditorWorkspaceNodeKind::Split: for (const UIEditorWorkspaceNode& child : node.children) { CollectVisiblePanelsRecursive(child, activePanelId, outPanels); } return; } } UIEditorWorkspaceValidationResult ValidateNodeRecursive( const UIEditorWorkspaceNode& node, std::unordered_set& panelIds) { if (node.nodeId.empty()) { return MakeValidationError( UIEditorWorkspaceValidationCode::EmptyNodeId, "Workspace node id must not be empty."); } switch (node.kind) { case UIEditorWorkspaceNodeKind::Panel: if (!node.children.empty()) { return MakeValidationError( UIEditorWorkspaceValidationCode::NonPanelTabChild, "Panel node '" + node.nodeId + "' must not contain child nodes."); } if (node.panel.panelId.empty()) { return MakeValidationError( UIEditorWorkspaceValidationCode::EmptyPanelId, "Panel node '" + node.nodeId + "' must define a panelId."); } if (node.panel.title.empty()) { return MakeValidationError( UIEditorWorkspaceValidationCode::EmptyPanelTitle, "Panel node '" + node.nodeId + "' must define a title."); } if (!panelIds.insert(node.panel.panelId).second) { return MakeValidationError( UIEditorWorkspaceValidationCode::DuplicatePanelId, "Panel id '" + node.panel.panelId + "' is duplicated in the workspace tree."); } return {}; case UIEditorWorkspaceNodeKind::TabStack: if (node.children.empty()) { return MakeValidationError( UIEditorWorkspaceValidationCode::EmptyTabStack, "Tab stack '" + node.nodeId + "' must contain at least one panel."); } if (node.selectedTabIndex >= node.children.size()) { return MakeValidationError( UIEditorWorkspaceValidationCode::InvalidSelectedTabIndex, "Tab stack '" + node.nodeId + "' selectedTabIndex is out of range."); } for (const UIEditorWorkspaceNode& child : node.children) { if (child.kind != UIEditorWorkspaceNodeKind::Panel) { return MakeValidationError( UIEditorWorkspaceValidationCode::NonPanelTabChild, "Tab stack '" + node.nodeId + "' may only contain panel leaf nodes."); } if (UIEditorWorkspaceValidationResult result = ValidateNodeRecursive(child, panelIds); !result.IsValid()) { return result; } } return {}; case UIEditorWorkspaceNodeKind::Split: if (node.children.size() != 2u) { return MakeValidationError( UIEditorWorkspaceValidationCode::InvalidSplitChildCount, "Split node '" + node.nodeId + "' must contain exactly two child nodes."); } if (!IsValidSplitRatio(node.splitRatio)) { return MakeValidationError( UIEditorWorkspaceValidationCode::InvalidSplitRatio, "Split node '" + node.nodeId + "' must define a ratio in the open interval (0, 1)."); } for (const UIEditorWorkspaceNode& child : node.children) { if (UIEditorWorkspaceValidationResult result = ValidateNodeRecursive(child, panelIds); !result.IsValid()) { return result; } } return {}; } return {}; } } // namespace UIEditorWorkspaceModel BuildDefaultEditorShellWorkspaceModel() { const UIEditorPanelRegistry registry = BuildDefaultEditorShellPanelRegistry(); const UIEditorPanelDescriptor& rootPanel = RequirePanelDescriptor(registry, "editor-foundation-root"); UIEditorWorkspaceModel workspace = {}; workspace.root = BuildUIEditorWorkspacePanel( "editor-foundation-root-node", rootPanel.panelId, rootPanel.defaultTitle, rootPanel.placeholder); workspace.activePanelId = rootPanel.panelId; return workspace; } UIEditorWorkspaceNode BuildUIEditorWorkspacePanel( std::string nodeId, std::string panelId, std::string title, bool placeholder) { UIEditorWorkspaceNode node = {}; node.kind = UIEditorWorkspaceNodeKind::Panel; node.nodeId = std::move(nodeId); node.panel.panelId = std::move(panelId); node.panel.title = std::move(title); node.panel.placeholder = placeholder; return node; } UIEditorWorkspaceNode BuildUIEditorWorkspaceTabStack( std::string nodeId, std::vector panels, std::size_t selectedTabIndex) { UIEditorWorkspaceNode node = {}; node.kind = UIEditorWorkspaceNodeKind::TabStack; node.nodeId = std::move(nodeId); node.selectedTabIndex = selectedTabIndex; node.children = std::move(panels); return node; } UIEditorWorkspaceNode BuildUIEditorWorkspaceSplit( std::string nodeId, UIEditorWorkspaceSplitAxis axis, float splitRatio, UIEditorWorkspaceNode primary, UIEditorWorkspaceNode secondary) { UIEditorWorkspaceNode node = {}; node.kind = UIEditorWorkspaceNodeKind::Split; node.nodeId = std::move(nodeId); node.splitAxis = axis; node.splitRatio = splitRatio; node.children.push_back(std::move(primary)); node.children.push_back(std::move(secondary)); return node; } UIEditorWorkspaceValidationResult ValidateUIEditorWorkspace( const UIEditorWorkspaceModel& workspace) { std::unordered_set panelIds = {}; UIEditorWorkspaceValidationResult result = ValidateNodeRecursive(workspace.root, panelIds); if (!result.IsValid()) { return result; } if (!workspace.activePanelId.empty()) { const UIEditorWorkspacePanelState* activePanel = FindUIEditorWorkspaceActivePanel(workspace); if (activePanel == nullptr) { return MakeValidationError( UIEditorWorkspaceValidationCode::InvalidActivePanelId, "Active panel id '" + workspace.activePanelId + "' is missing or hidden by the current tab selection."); } } return {}; } std::vector CollectUIEditorWorkspaceVisiblePanels( const UIEditorWorkspaceModel& workspace) { std::vector visiblePanels = {}; CollectVisiblePanelsRecursive(workspace.root, workspace.activePanelId, visiblePanels); return visiblePanels; } bool ContainsUIEditorWorkspacePanel( const UIEditorWorkspaceModel& workspace, std::string_view panelId) { return FindPanelRecursive(workspace.root, panelId) != nullptr; } const UIEditorWorkspacePanelState* FindUIEditorWorkspaceActivePanel( const UIEditorWorkspaceModel& workspace) { if (workspace.activePanelId.empty()) { return nullptr; } std::vector visiblePanels = CollectUIEditorWorkspaceVisiblePanels(workspace); for (const UIEditorWorkspaceVisiblePanel& panel : visiblePanels) { if (panel.panelId == workspace.activePanelId) { return FindPanelRecursive(workspace.root, workspace.activePanelId); } } return nullptr; } bool TryActivateUIEditorWorkspacePanel( UIEditorWorkspaceModel& workspace, std::string_view panelId) { if (!TryActivateRecursive(workspace.root, panelId)) { return false; } workspace.activePanelId = std::string(panelId); return true; } } // namespace XCEngine::NewEditor