358 lines
14 KiB
C++
358 lines
14 KiB
C++
|
|
#include <XCNewEditor/Editor/UIEditorWorkspaceController.h>
|
||
|
|
|
||
|
|
#include <utility>
|
||
|
|
|
||
|
|
namespace XCEngine::NewEditor {
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
bool AreWorkspaceNodesEquivalent(
|
||
|
|
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 (!AreWorkspaceNodesEquivalent(lhs.children[index], rhs.children[index])) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool AreWorkspaceModelsEquivalent(
|
||
|
|
const UIEditorWorkspaceModel& lhs,
|
||
|
|
const UIEditorWorkspaceModel& rhs) {
|
||
|
|
return lhs.activePanelId == rhs.activePanelId &&
|
||
|
|
AreWorkspaceNodesEquivalent(lhs.root, rhs.root);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool AreWorkspaceSessionsEquivalent(
|
||
|
|
const UIEditorWorkspaceSession& lhs,
|
||
|
|
const UIEditorWorkspaceSession& rhs) {
|
||
|
|
if (lhs.panelStates.size() != rhs.panelStates.size()) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (std::size_t index = 0; index < lhs.panelStates.size(); ++index) {
|
||
|
|
const UIEditorPanelSessionState& lhsState = lhs.panelStates[index];
|
||
|
|
const UIEditorPanelSessionState& rhsState = rhs.panelStates[index];
|
||
|
|
if (lhsState.panelId != rhsState.panelId ||
|
||
|
|
lhsState.open != rhsState.open ||
|
||
|
|
lhsState.visible != rhsState.visible) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<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";
|
||
|
|
}
|
||
|
|
|
||
|
|
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 {};
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
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 (AreWorkspaceModelsEquivalent(m_workspace, m_baselineWorkspace) &&
|
||
|
|
AreWorkspaceSessionsEquivalent(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::NewEditor
|