refactor(new_editor/workspace): split workspace controller responsibilities
This commit is contained in:
@@ -100,6 +100,8 @@ set(XCUI_EDITOR_VIEWPORT_SOURCES
|
||||
set(XCUI_EDITOR_WORKSPACE_SOURCES
|
||||
src/Workspace/UIEditorWorkspaceCompose.cpp
|
||||
src/Workspace/UIEditorWorkspaceController.cpp
|
||||
src/Workspace/UIEditorWorkspaceControllerDispatch.cpp
|
||||
src/Workspace/UIEditorWorkspaceControllerLayoutOps.cpp
|
||||
src/Workspace/UIEditorWorkspaceInteraction.cpp
|
||||
src/Workspace/UIEditorWorkspaceLayoutPersistence.cpp
|
||||
src/Workspace/UIEditorWorkspaceModel.cpp
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
#include <XCEditor/Workspace/UIEditorWorkspaceController.h>
|
||||
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
|
||||
#include "Workspace/UIEditorWorkspaceControllerInternal.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine::UI::Editor {
|
||||
|
||||
namespace {
|
||||
namespace XCEngine::UI::Editor::Detail {
|
||||
|
||||
bool IsPanelOpenAndVisible(
|
||||
const UIEditorWorkspaceSession& session,
|
||||
@@ -32,13 +27,6 @@ std::vector<std::string> CollectVisiblePanelIds(
|
||||
return ids;
|
||||
}
|
||||
|
||||
struct VisibleTabStackInfo {
|
||||
bool panelExists = false;
|
||||
bool panelVisible = false;
|
||||
std::size_t currentVisibleIndex = 0u;
|
||||
std::size_t visibleTabCount = 0u;
|
||||
};
|
||||
|
||||
VisibleTabStackInfo ResolveVisibleTabStackInfo(
|
||||
const UIEditorWorkspaceNode& node,
|
||||
const UIEditorWorkspaceSession& session,
|
||||
@@ -84,7 +72,9 @@ std::size_t CountVisibleTabs(
|
||||
return visibleCount;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace XCEngine::UI::Editor::Detail
|
||||
|
||||
namespace XCEngine::UI::Editor {
|
||||
|
||||
std::string_view GetUIEditorWorkspaceCommandKindName(UIEditorWorkspaceCommandKind kind) {
|
||||
switch (kind) {
|
||||
@@ -105,7 +95,8 @@ std::string_view GetUIEditorWorkspaceCommandKindName(UIEditorWorkspaceCommandKin
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
std::string_view GetUIEditorWorkspaceCommandStatusName(UIEditorWorkspaceCommandStatus status) {
|
||||
std::string_view GetUIEditorWorkspaceCommandStatusName(
|
||||
UIEditorWorkspaceCommandStatus status) {
|
||||
switch (status) {
|
||||
case UIEditorWorkspaceCommandStatus::Changed:
|
||||
return "Changed";
|
||||
@@ -188,7 +179,7 @@ UIEditorWorkspaceCommandResult UIEditorWorkspaceController::BuildResult(
|
||||
result.panelId = command.panelId;
|
||||
result.message = std::move(message);
|
||||
result.activePanelId = m_workspace.activePanelId;
|
||||
result.visiblePanelIds = CollectVisiblePanelIds(m_workspace, m_session);
|
||||
result.visiblePanelIds = Detail::CollectVisiblePanelIds(m_workspace, m_session);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -199,7 +190,7 @@ UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::BuildLayoutO
|
||||
result.status = status;
|
||||
result.message = std::move(message);
|
||||
result.activePanelId = m_workspace.activePanelId;
|
||||
result.visiblePanelIds = CollectVisiblePanelIds(m_workspace, m_session);
|
||||
result.visiblePanelIds = Detail::CollectVisiblePanelIds(m_workspace, m_session);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -238,559 +229,6 @@ const UIEditorPanelDescriptor* UIEditorWorkspaceController::FindPanelDescriptor(
|
||||
return FindUIEditorPanelDescriptor(m_panelRegistry, panelId);
|
||||
}
|
||||
|
||||
UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::RestoreLayoutSnapshot(
|
||||
const UIEditorWorkspaceLayoutSnapshot& snapshot) {
|
||||
UIEditorWorkspaceLayoutSnapshot canonicalSnapshot = snapshot;
|
||||
canonicalSnapshot.workspace =
|
||||
CanonicalizeUIEditorWorkspaceModel(std::move(canonicalSnapshot.workspace));
|
||||
|
||||
const UIEditorPanelRegistryValidationResult registryValidation =
|
||||
ValidateUIEditorPanelRegistry(m_panelRegistry);
|
||||
if (!registryValidation.IsValid()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Panel registry invalid: " + registryValidation.message);
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceValidationResult workspaceValidation =
|
||||
ValidateUIEditorWorkspace(canonicalSnapshot.workspace);
|
||||
if (!workspaceValidation.IsValid()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Layout workspace invalid: " + workspaceValidation.message);
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceSessionValidationResult sessionValidation =
|
||||
ValidateUIEditorWorkspaceSession(
|
||||
m_panelRegistry,
|
||||
canonicalSnapshot.workspace,
|
||||
canonicalSnapshot.session);
|
||||
if (!sessionValidation.IsValid()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Layout session invalid: " + sessionValidation.message);
|
||||
}
|
||||
|
||||
if (AreUIEditorWorkspaceModelsEquivalent(m_workspace, canonicalSnapshot.workspace) &&
|
||||
AreUIEditorWorkspaceSessionsEquivalent(m_session, canonicalSnapshot.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 = canonicalSnapshot.workspace;
|
||||
m_session = canonicalSnapshot.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.");
|
||||
}
|
||||
|
||||
UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::ReorderTab(
|
||||
std::string_view nodeId,
|
||||
std::string_view panelId,
|
||||
std::size_t targetVisibleInsertionIndex) {
|
||||
const UIEditorWorkspaceControllerValidationResult validation = ValidateState();
|
||||
if (!validation.IsValid()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Controller state invalid: " + validation.message);
|
||||
}
|
||||
|
||||
if (nodeId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab requires a tab stack node id.");
|
||||
}
|
||||
|
||||
if (panelId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab requires a panel id.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceNode* tabStack = FindUIEditorWorkspaceNode(m_workspace, nodeId);
|
||||
if (tabStack == nullptr || tabStack->kind != UIEditorWorkspaceNodeKind::TabStack) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab target tab stack is missing.");
|
||||
}
|
||||
|
||||
const VisibleTabStackInfo tabInfo =
|
||||
ResolveVisibleTabStackInfo(*tabStack, m_session, panelId);
|
||||
if (!tabInfo.panelExists) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab target panel is missing from the specified tab stack.");
|
||||
}
|
||||
|
||||
if (!tabInfo.panelVisible) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab only supports open and visible tabs.");
|
||||
}
|
||||
|
||||
if (targetVisibleInsertionIndex > tabInfo.visibleTabCount) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab target visible insertion index is out of range.");
|
||||
}
|
||||
|
||||
if (targetVisibleInsertionIndex == tabInfo.currentVisibleIndex ||
|
||||
targetVisibleInsertionIndex == tabInfo.currentVisibleIndex + 1u) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::NoOp,
|
||||
"Visible tab order already matches the requested insertion.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceModel previousWorkspace = m_workspace;
|
||||
if (!TryReorderUIEditorWorkspaceTab(
|
||||
m_workspace,
|
||||
m_session,
|
||||
nodeId,
|
||||
panelId,
|
||||
targetVisibleInsertionIndex)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Tab reorder rejected.");
|
||||
}
|
||||
|
||||
if (AreUIEditorWorkspaceModelsEquivalent(previousWorkspace, m_workspace)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::NoOp,
|
||||
"Visible tab order already matches the requested insertion.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState();
|
||||
if (!postValidation.IsValid()) {
|
||||
m_workspace = previousWorkspace;
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Tab reorder produced invalid controller state: " + postValidation.message);
|
||||
}
|
||||
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Changed,
|
||||
"Tab reordered.");
|
||||
}
|
||||
|
||||
UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::MoveTabToStack(
|
||||
std::string_view sourceNodeId,
|
||||
std::string_view panelId,
|
||||
std::string_view targetNodeId,
|
||||
std::size_t targetVisibleInsertionIndex) {
|
||||
{
|
||||
std::ostringstream trace = {};
|
||||
trace << "MoveTabToStack begin sourceNode=" << sourceNodeId
|
||||
<< " panel=" << panelId
|
||||
<< " targetNode=" << targetNodeId
|
||||
<< " insertion=" << targetVisibleInsertionIndex;
|
||||
AppendUIEditorRuntimeTrace("workspace", trace.str());
|
||||
}
|
||||
const UIEditorWorkspaceControllerValidationResult validation = ValidateState();
|
||||
if (!validation.IsValid()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Controller state invalid: " + validation.message);
|
||||
}
|
||||
|
||||
if (sourceNodeId.empty() || targetNodeId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack requires both source and target tab stack ids.");
|
||||
}
|
||||
|
||||
if (panelId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack requires a panel id.");
|
||||
}
|
||||
|
||||
if (sourceNodeId == targetNodeId) {
|
||||
return ReorderTab(sourceNodeId, panelId, targetVisibleInsertionIndex);
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceNode* sourceTabStack =
|
||||
FindUIEditorWorkspaceNode(m_workspace, sourceNodeId);
|
||||
const UIEditorWorkspaceNode* targetTabStack =
|
||||
FindUIEditorWorkspaceNode(m_workspace, targetNodeId);
|
||||
if (sourceTabStack == nullptr ||
|
||||
targetTabStack == nullptr ||
|
||||
sourceTabStack->kind != UIEditorWorkspaceNodeKind::TabStack ||
|
||||
targetTabStack->kind != UIEditorWorkspaceNodeKind::TabStack) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack source or target tab stack is missing.");
|
||||
}
|
||||
|
||||
const VisibleTabStackInfo sourceInfo =
|
||||
ResolveVisibleTabStackInfo(*sourceTabStack, m_session, panelId);
|
||||
if (!sourceInfo.panelExists) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack target panel is missing from the source tab stack.");
|
||||
}
|
||||
|
||||
if (!sourceInfo.panelVisible) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack only supports open and visible tabs.");
|
||||
}
|
||||
|
||||
const std::size_t visibleTargetCount = CountVisibleTabs(*targetTabStack, m_session);
|
||||
if (targetVisibleInsertionIndex > visibleTargetCount) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack target visible insertion index is out of range.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceModel previousWorkspace = m_workspace;
|
||||
if (!TryMoveUIEditorWorkspaceTabToStack(
|
||||
m_workspace,
|
||||
m_session,
|
||||
sourceNodeId,
|
||||
panelId,
|
||||
targetNodeId,
|
||||
targetVisibleInsertionIndex)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack rejected.");
|
||||
}
|
||||
|
||||
if (AreUIEditorWorkspaceModelsEquivalent(previousWorkspace, m_workspace)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::NoOp,
|
||||
"Tab already matches the requested target stack insertion.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState();
|
||||
if (!postValidation.IsValid()) {
|
||||
m_workspace = previousWorkspace;
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack produced invalid controller state: " + postValidation.message);
|
||||
}
|
||||
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Changed,
|
||||
"Tab moved to target stack.");
|
||||
}
|
||||
|
||||
UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::DockTabRelative(
|
||||
std::string_view sourceNodeId,
|
||||
std::string_view panelId,
|
||||
std::string_view targetNodeId,
|
||||
UIEditorWorkspaceDockPlacement placement,
|
||||
float splitRatio) {
|
||||
{
|
||||
std::ostringstream trace = {};
|
||||
trace << "DockTabRelative begin sourceNode=" << sourceNodeId
|
||||
<< " panel=" << panelId
|
||||
<< " targetNode=" << targetNodeId
|
||||
<< " placement=" << static_cast<int>(placement)
|
||||
<< " splitRatio=" << splitRatio;
|
||||
AppendUIEditorRuntimeTrace("workspace", trace.str());
|
||||
}
|
||||
const UIEditorWorkspaceControllerValidationResult validation = ValidateState();
|
||||
if (!validation.IsValid()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Controller state invalid: " + validation.message);
|
||||
}
|
||||
|
||||
if (sourceNodeId.empty() || targetNodeId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative requires both source and target tab stack ids.");
|
||||
}
|
||||
|
||||
if (panelId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative requires a panel id.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceNode* sourceTabStack =
|
||||
FindUIEditorWorkspaceNode(m_workspace, sourceNodeId);
|
||||
const UIEditorWorkspaceNode* targetTabStack =
|
||||
FindUIEditorWorkspaceNode(m_workspace, targetNodeId);
|
||||
if (sourceTabStack == nullptr ||
|
||||
targetTabStack == nullptr ||
|
||||
sourceTabStack->kind != UIEditorWorkspaceNodeKind::TabStack ||
|
||||
targetTabStack->kind != UIEditorWorkspaceNodeKind::TabStack) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative source or target tab stack is missing.");
|
||||
}
|
||||
|
||||
const VisibleTabStackInfo sourceInfo =
|
||||
ResolveVisibleTabStackInfo(*sourceTabStack, m_session, panelId);
|
||||
if (!sourceInfo.panelExists) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative target panel is missing from the source tab stack.");
|
||||
}
|
||||
|
||||
if (!sourceInfo.panelVisible) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative only supports open and visible tabs.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceModel previousWorkspace = m_workspace;
|
||||
if (!TryDockUIEditorWorkspaceTabRelative(
|
||||
m_workspace,
|
||||
m_session,
|
||||
sourceNodeId,
|
||||
panelId,
|
||||
targetNodeId,
|
||||
placement,
|
||||
splitRatio)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative rejected.");
|
||||
}
|
||||
|
||||
if (AreUIEditorWorkspaceModelsEquivalent(previousWorkspace, m_workspace)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::NoOp,
|
||||
"Dock layout already matches the requested placement.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState();
|
||||
if (!postValidation.IsValid()) {
|
||||
m_workspace = previousWorkspace;
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative produced invalid controller state: " + postValidation.message);
|
||||
}
|
||||
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Changed,
|
||||
"Tab docked relative to target stack.");
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
239
new_editor/src/Workspace/UIEditorWorkspaceControllerDispatch.cpp
Normal file
239
new_editor/src/Workspace/UIEditorWorkspaceControllerDispatch.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
#include <XCEditor/Workspace/UIEditorWorkspaceController.h>
|
||||
|
||||
namespace XCEngine::UI::Editor {
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEditor/Workspace/UIEditorWorkspaceController.h>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine::UI::Editor::Detail {
|
||||
|
||||
bool IsPanelOpenAndVisible(
|
||||
const UIEditorWorkspaceSession& session,
|
||||
std::string_view panelId);
|
||||
|
||||
std::vector<std::string> CollectVisiblePanelIds(
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
const UIEditorWorkspaceSession& session);
|
||||
|
||||
struct VisibleTabStackInfo {
|
||||
bool panelExists = false;
|
||||
bool panelVisible = false;
|
||||
std::size_t currentVisibleIndex = 0u;
|
||||
std::size_t visibleTabCount = 0u;
|
||||
};
|
||||
|
||||
VisibleTabStackInfo ResolveVisibleTabStackInfo(
|
||||
const UIEditorWorkspaceNode& node,
|
||||
const UIEditorWorkspaceSession& session,
|
||||
std::string_view panelId);
|
||||
|
||||
std::size_t CountVisibleTabs(
|
||||
const UIEditorWorkspaceNode& node,
|
||||
const UIEditorWorkspaceSession& session);
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Detail
|
||||
@@ -0,0 +1,419 @@
|
||||
#include "Workspace/UIEditorWorkspaceControllerInternal.h"
|
||||
|
||||
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine::UI::Editor {
|
||||
|
||||
UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::RestoreLayoutSnapshot(
|
||||
const UIEditorWorkspaceLayoutSnapshot& snapshot) {
|
||||
UIEditorWorkspaceLayoutSnapshot canonicalSnapshot = snapshot;
|
||||
canonicalSnapshot.workspace =
|
||||
CanonicalizeUIEditorWorkspaceModel(std::move(canonicalSnapshot.workspace));
|
||||
|
||||
const UIEditorPanelRegistryValidationResult registryValidation =
|
||||
ValidateUIEditorPanelRegistry(m_panelRegistry);
|
||||
if (!registryValidation.IsValid()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Panel registry invalid: " + registryValidation.message);
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceValidationResult workspaceValidation =
|
||||
ValidateUIEditorWorkspace(canonicalSnapshot.workspace);
|
||||
if (!workspaceValidation.IsValid()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Layout workspace invalid: " + workspaceValidation.message);
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceSessionValidationResult sessionValidation =
|
||||
ValidateUIEditorWorkspaceSession(
|
||||
m_panelRegistry,
|
||||
canonicalSnapshot.workspace,
|
||||
canonicalSnapshot.session);
|
||||
if (!sessionValidation.IsValid()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Layout session invalid: " + sessionValidation.message);
|
||||
}
|
||||
|
||||
if (AreUIEditorWorkspaceModelsEquivalent(m_workspace, canonicalSnapshot.workspace) &&
|
||||
AreUIEditorWorkspaceSessionsEquivalent(m_session, canonicalSnapshot.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 = canonicalSnapshot.workspace;
|
||||
m_session = canonicalSnapshot.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.");
|
||||
}
|
||||
|
||||
UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::ReorderTab(
|
||||
std::string_view nodeId,
|
||||
std::string_view panelId,
|
||||
std::size_t targetVisibleInsertionIndex) {
|
||||
const UIEditorWorkspaceControllerValidationResult validation = ValidateState();
|
||||
if (!validation.IsValid()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Controller state invalid: " + validation.message);
|
||||
}
|
||||
|
||||
if (nodeId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab requires a tab stack node id.");
|
||||
}
|
||||
|
||||
if (panelId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab requires a panel id.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceNode* tabStack = FindUIEditorWorkspaceNode(m_workspace, nodeId);
|
||||
if (tabStack == nullptr || tabStack->kind != UIEditorWorkspaceNodeKind::TabStack) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab target tab stack is missing.");
|
||||
}
|
||||
|
||||
const Detail::VisibleTabStackInfo tabInfo =
|
||||
Detail::ResolveVisibleTabStackInfo(*tabStack, m_session, panelId);
|
||||
if (!tabInfo.panelExists) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab target panel is missing from the specified tab stack.");
|
||||
}
|
||||
|
||||
if (!tabInfo.panelVisible) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab only supports open and visible tabs.");
|
||||
}
|
||||
|
||||
if (targetVisibleInsertionIndex > tabInfo.visibleTabCount) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"ReorderTab target visible insertion index is out of range.");
|
||||
}
|
||||
|
||||
if (targetVisibleInsertionIndex == tabInfo.currentVisibleIndex ||
|
||||
targetVisibleInsertionIndex == tabInfo.currentVisibleIndex + 1u) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::NoOp,
|
||||
"Visible tab order already matches the requested insertion.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceModel previousWorkspace = m_workspace;
|
||||
if (!TryReorderUIEditorWorkspaceTab(
|
||||
m_workspace,
|
||||
m_session,
|
||||
nodeId,
|
||||
panelId,
|
||||
targetVisibleInsertionIndex)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Tab reorder rejected.");
|
||||
}
|
||||
|
||||
if (AreUIEditorWorkspaceModelsEquivalent(previousWorkspace, m_workspace)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::NoOp,
|
||||
"Visible tab order already matches the requested insertion.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState();
|
||||
if (!postValidation.IsValid()) {
|
||||
m_workspace = previousWorkspace;
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Tab reorder produced invalid controller state: " + postValidation.message);
|
||||
}
|
||||
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Changed,
|
||||
"Tab reordered.");
|
||||
}
|
||||
|
||||
UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::MoveTabToStack(
|
||||
std::string_view sourceNodeId,
|
||||
std::string_view panelId,
|
||||
std::string_view targetNodeId,
|
||||
std::size_t targetVisibleInsertionIndex) {
|
||||
{
|
||||
std::ostringstream trace = {};
|
||||
trace << "MoveTabToStack begin sourceNode=" << sourceNodeId
|
||||
<< " panel=" << panelId
|
||||
<< " targetNode=" << targetNodeId
|
||||
<< " insertion=" << targetVisibleInsertionIndex;
|
||||
AppendUIEditorRuntimeTrace("workspace", trace.str());
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceControllerValidationResult validation = ValidateState();
|
||||
if (!validation.IsValid()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Controller state invalid: " + validation.message);
|
||||
}
|
||||
|
||||
if (sourceNodeId.empty() || targetNodeId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack requires both source and target tab stack ids.");
|
||||
}
|
||||
|
||||
if (panelId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack requires a panel id.");
|
||||
}
|
||||
|
||||
if (sourceNodeId == targetNodeId) {
|
||||
return ReorderTab(sourceNodeId, panelId, targetVisibleInsertionIndex);
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceNode* sourceTabStack =
|
||||
FindUIEditorWorkspaceNode(m_workspace, sourceNodeId);
|
||||
const UIEditorWorkspaceNode* targetTabStack =
|
||||
FindUIEditorWorkspaceNode(m_workspace, targetNodeId);
|
||||
if (sourceTabStack == nullptr ||
|
||||
targetTabStack == nullptr ||
|
||||
sourceTabStack->kind != UIEditorWorkspaceNodeKind::TabStack ||
|
||||
targetTabStack->kind != UIEditorWorkspaceNodeKind::TabStack) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack source or target tab stack is missing.");
|
||||
}
|
||||
|
||||
const Detail::VisibleTabStackInfo sourceInfo =
|
||||
Detail::ResolveVisibleTabStackInfo(*sourceTabStack, m_session, panelId);
|
||||
if (!sourceInfo.panelExists) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack target panel is missing from the source tab stack.");
|
||||
}
|
||||
|
||||
if (!sourceInfo.panelVisible) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack only supports open and visible tabs.");
|
||||
}
|
||||
|
||||
const std::size_t visibleTargetCount =
|
||||
Detail::CountVisibleTabs(*targetTabStack, m_session);
|
||||
if (targetVisibleInsertionIndex > visibleTargetCount) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack target visible insertion index is out of range.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceModel previousWorkspace = m_workspace;
|
||||
if (!TryMoveUIEditorWorkspaceTabToStack(
|
||||
m_workspace,
|
||||
m_session,
|
||||
sourceNodeId,
|
||||
panelId,
|
||||
targetNodeId,
|
||||
targetVisibleInsertionIndex)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack rejected.");
|
||||
}
|
||||
|
||||
if (AreUIEditorWorkspaceModelsEquivalent(previousWorkspace, m_workspace)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::NoOp,
|
||||
"Tab already matches the requested target stack insertion.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState();
|
||||
if (!postValidation.IsValid()) {
|
||||
m_workspace = previousWorkspace;
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"MoveTabToStack produced invalid controller state: " + postValidation.message);
|
||||
}
|
||||
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Changed,
|
||||
"Tab moved to target stack.");
|
||||
}
|
||||
|
||||
UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::DockTabRelative(
|
||||
std::string_view sourceNodeId,
|
||||
std::string_view panelId,
|
||||
std::string_view targetNodeId,
|
||||
UIEditorWorkspaceDockPlacement placement,
|
||||
float splitRatio) {
|
||||
{
|
||||
std::ostringstream trace = {};
|
||||
trace << "DockTabRelative begin sourceNode=" << sourceNodeId
|
||||
<< " panel=" << panelId
|
||||
<< " targetNode=" << targetNodeId
|
||||
<< " placement=" << static_cast<int>(placement)
|
||||
<< " splitRatio=" << splitRatio;
|
||||
AppendUIEditorRuntimeTrace("workspace", trace.str());
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceControllerValidationResult validation = ValidateState();
|
||||
if (!validation.IsValid()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Controller state invalid: " + validation.message);
|
||||
}
|
||||
|
||||
if (sourceNodeId.empty() || targetNodeId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative requires both source and target tab stack ids.");
|
||||
}
|
||||
|
||||
if (panelId.empty()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative requires a panel id.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceNode* sourceTabStack =
|
||||
FindUIEditorWorkspaceNode(m_workspace, sourceNodeId);
|
||||
const UIEditorWorkspaceNode* targetTabStack =
|
||||
FindUIEditorWorkspaceNode(m_workspace, targetNodeId);
|
||||
if (sourceTabStack == nullptr ||
|
||||
targetTabStack == nullptr ||
|
||||
sourceTabStack->kind != UIEditorWorkspaceNodeKind::TabStack ||
|
||||
targetTabStack->kind != UIEditorWorkspaceNodeKind::TabStack) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative source or target tab stack is missing.");
|
||||
}
|
||||
|
||||
const Detail::VisibleTabStackInfo sourceInfo =
|
||||
Detail::ResolveVisibleTabStackInfo(*sourceTabStack, m_session, panelId);
|
||||
if (!sourceInfo.panelExists) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative target panel is missing from the source tab stack.");
|
||||
}
|
||||
|
||||
if (!sourceInfo.panelVisible) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative only supports open and visible tabs.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceModel previousWorkspace = m_workspace;
|
||||
if (!TryDockUIEditorWorkspaceTabRelative(
|
||||
m_workspace,
|
||||
m_session,
|
||||
sourceNodeId,
|
||||
panelId,
|
||||
targetNodeId,
|
||||
placement,
|
||||
splitRatio)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative rejected.");
|
||||
}
|
||||
|
||||
if (AreUIEditorWorkspaceModelsEquivalent(previousWorkspace, m_workspace)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::NoOp,
|
||||
"Dock layout already matches the requested placement.");
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState();
|
||||
if (!postValidation.IsValid()) {
|
||||
m_workspace = previousWorkspace;
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"DockTabRelative produced invalid controller state: " + postValidation.message);
|
||||
}
|
||||
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Changed,
|
||||
"Tab docked relative to target stack.");
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor
|
||||
Reference in New Issue
Block a user