Refactor XCUI editor module layout
This commit is contained in:
416
new_editor/src/Shell/UIEditorWorkspaceModel.cpp
Normal file
416
new_editor/src/Shell/UIEditorWorkspaceModel.cpp
Normal file
@@ -0,0 +1,416 @@
|
||||
#include <XCEditor/Shell/UIEditorPanelRegistry.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceModel.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine::UI::Editor {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceNode* FindNodeRecursive(
|
||||
const UIEditorWorkspaceNode& node,
|
||||
std::string_view nodeId) {
|
||||
if (node.nodeId == nodeId) {
|
||||
return &node;
|
||||
}
|
||||
|
||||
for (const UIEditorWorkspaceNode& child : node.children) {
|
||||
if (const UIEditorWorkspaceNode* found = FindNodeRecursive(child, nodeId)) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UIEditorWorkspaceNode* FindMutableNodeRecursive(
|
||||
UIEditorWorkspaceNode& node,
|
||||
std::string_view nodeId) {
|
||||
if (node.nodeId == nodeId) {
|
||||
return &node;
|
||||
}
|
||||
|
||||
for (UIEditorWorkspaceNode& child : node.children) {
|
||||
if (UIEditorWorkspaceNode* found = FindMutableNodeRecursive(child, nodeId)) {
|
||||
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<UIEditorWorkspaceVisiblePanel>& 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<std::string>& 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
|
||||
|
||||
bool AreUIEditorWorkspaceNodesEquivalent(
|
||||
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 (!AreUIEditorWorkspaceNodesEquivalent(lhs.children[index], rhs.children[index])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AreUIEditorWorkspaceModelsEquivalent(
|
||||
const UIEditorWorkspaceModel& lhs,
|
||||
const UIEditorWorkspaceModel& rhs) {
|
||||
return lhs.activePanelId == rhs.activePanelId &&
|
||||
AreUIEditorWorkspaceNodesEquivalent(lhs.root, rhs.root);
|
||||
}
|
||||
|
||||
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<UIEditorWorkspaceNode> 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<std::string> 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<UIEditorWorkspaceVisiblePanel> CollectUIEditorWorkspaceVisiblePanels(
|
||||
const UIEditorWorkspaceModel& workspace) {
|
||||
std::vector<UIEditorWorkspaceVisiblePanel> 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 UIEditorWorkspaceNode* FindUIEditorWorkspaceNode(
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
std::string_view nodeId) {
|
||||
return FindNodeRecursive(workspace.root, nodeId);
|
||||
}
|
||||
|
||||
const UIEditorWorkspacePanelState* FindUIEditorWorkspaceActivePanel(
|
||||
const UIEditorWorkspaceModel& workspace) {
|
||||
if (workspace.activePanelId.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<UIEditorWorkspaceVisiblePanel> 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;
|
||||
}
|
||||
|
||||
bool TrySetUIEditorWorkspaceSplitRatio(
|
||||
UIEditorWorkspaceModel& workspace,
|
||||
std::string_view nodeId,
|
||||
float splitRatio) {
|
||||
if (!IsValidSplitRatio(splitRatio)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UIEditorWorkspaceNode* node = FindMutableNodeRecursive(workspace.root, nodeId);
|
||||
if (node == nullptr || node->kind != UIEditorWorkspaceNodeKind::Split) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (std::fabs(node->splitRatio - splitRatio) <= 0.0001f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
node->splitRatio = splitRatio;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor
|
||||
Reference in New Issue
Block a user