Files
XCEngine/new_editor/src/Shell/UIEditorWorkspaceController.cpp

451 lines
18 KiB
C++
Raw Normal View History

2026-04-10 00:41:28 +08:00
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
#include <cmath>
#include <utility>
namespace XCEngine::UI::Editor {
namespace {
std::vector<std::string> CollectVisiblePanelIds(
const UIEditorWorkspaceModel& workspace,
const UIEditorWorkspaceSession& session) {
const std::vector<UIEditorWorkspaceVisiblePanel> panels =
CollectUIEditorWorkspaceVisiblePanels(workspace, session);
std::vector<std::string> 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