448 lines
16 KiB
C++
448 lines
16 KiB
C++
#include <XCEditor/Workspace/UIEditorWorkspaceSession.h>
|
|
#include <XCEditor/Workspace/UIEditorWorkspaceMutation.h>
|
|
#include <XCEditor/Workspace/UIEditorWorkspaceQueries.h>
|
|
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
|
|
namespace XCEngine::UI::Editor {
|
|
|
|
namespace {
|
|
|
|
UIEditorWorkspaceSessionValidationResult MakeValidationError(
|
|
UIEditorWorkspaceSessionValidationCode code,
|
|
std::string message) {
|
|
UIEditorWorkspaceSessionValidationResult result = {};
|
|
result.code = code;
|
|
result.message = std::move(message);
|
|
return result;
|
|
}
|
|
|
|
UIEditorPanelSessionState* FindMutablePanelSessionState(
|
|
UIEditorWorkspaceSession& session,
|
|
std::string_view panelId) {
|
|
for (UIEditorPanelSessionState& state : session.panelStates) {
|
|
if (state.panelId == panelId) {
|
|
return &state;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const UIEditorPanelDescriptor* FindPanelDescriptor(
|
|
const UIEditorPanelRegistry& panelRegistry,
|
|
std::string_view panelId) {
|
|
return FindUIEditorPanelDescriptor(panelRegistry, panelId);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void CollectWorkspacePanelIdsRecursive(
|
|
const UIEditorWorkspaceNode& node,
|
|
std::vector<std::string>& outPanelIds) {
|
|
if (node.kind == UIEditorWorkspaceNodeKind::Panel) {
|
|
outPanelIds.push_back(node.panel.panelId);
|
|
return;
|
|
}
|
|
|
|
for (const UIEditorWorkspaceNode& child : node.children) {
|
|
CollectWorkspacePanelIdsRecursive(child, outPanelIds);
|
|
}
|
|
}
|
|
|
|
bool IsPanelOpenAndVisible(
|
|
const UIEditorWorkspaceSession& session,
|
|
std::string_view panelId) {
|
|
const UIEditorPanelSessionState* state = FindUIEditorPanelSessionState(session, panelId);
|
|
return state != nullptr && state->open && state->visible;
|
|
}
|
|
|
|
bool IsPanelSelectable(
|
|
const UIEditorWorkspaceModel& workspace,
|
|
const UIEditorWorkspaceSession& session,
|
|
std::string_view panelId) {
|
|
return !panelId.empty() &&
|
|
IsPanelOpenAndVisible(session, panelId) &&
|
|
ContainsUIEditorWorkspacePanel(workspace, panelId);
|
|
}
|
|
|
|
std::size_t ResolveVisibleTabIndex(
|
|
const UIEditorWorkspaceNode& node,
|
|
const UIEditorWorkspaceSession& session) {
|
|
if (node.kind != UIEditorWorkspaceNodeKind::TabStack || node.children.empty()) {
|
|
return node.selectedTabIndex;
|
|
}
|
|
|
|
if (node.selectedTabIndex < node.children.size()) {
|
|
const UIEditorWorkspaceNode& selectedChild = node.children[node.selectedTabIndex];
|
|
if (selectedChild.kind == UIEditorWorkspaceNodeKind::Panel &&
|
|
IsPanelOpenAndVisible(session, selectedChild.panel.panelId)) {
|
|
return node.selectedTabIndex;
|
|
}
|
|
}
|
|
|
|
for (std::size_t index = 0; index < node.children.size(); ++index) {
|
|
const UIEditorWorkspaceNode& child = node.children[index];
|
|
if (child.kind == UIEditorWorkspaceNodeKind::Panel &&
|
|
IsPanelOpenAndVisible(session, child.panel.panelId)) {
|
|
return index;
|
|
}
|
|
}
|
|
|
|
return node.children.size();
|
|
}
|
|
|
|
void CollectVisiblePanelsRecursive(
|
|
const UIEditorWorkspaceNode& node,
|
|
const UIEditorWorkspaceSession& session,
|
|
std::string_view activePanelId,
|
|
std::vector<UIEditorWorkspaceVisiblePanel>& outPanels) {
|
|
switch (node.kind) {
|
|
case UIEditorWorkspaceNodeKind::Panel: {
|
|
if (!IsPanelOpenAndVisible(session, node.panel.panelId)) {
|
|
return;
|
|
}
|
|
|
|
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: {
|
|
const std::size_t resolvedIndex = ResolveVisibleTabIndex(node, session);
|
|
if (resolvedIndex < node.children.size()) {
|
|
CollectVisiblePanelsRecursive(
|
|
node.children[resolvedIndex],
|
|
session,
|
|
activePanelId,
|
|
outPanels);
|
|
}
|
|
return;
|
|
}
|
|
|
|
case UIEditorWorkspaceNodeKind::Split:
|
|
for (const UIEditorWorkspaceNode& child : node.children) {
|
|
CollectVisiblePanelsRecursive(child, session, activePanelId, outPanels);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
void NormalizeSessionStatesAgainstRegistry(
|
|
const UIEditorPanelRegistry& panelRegistry,
|
|
UIEditorWorkspaceSession& session) {
|
|
for (UIEditorPanelSessionState& state : session.panelStates) {
|
|
if (!state.open) {
|
|
state.visible = false;
|
|
}
|
|
|
|
const UIEditorPanelDescriptor* descriptor = FindPanelDescriptor(panelRegistry, state.panelId);
|
|
if (descriptor == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
if (!descriptor->canClose) {
|
|
state.open = true;
|
|
state.visible = true;
|
|
continue;
|
|
}
|
|
|
|
if (!descriptor->canHide && state.open) {
|
|
state.visible = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void NormalizeWorkspaceSession(
|
|
const UIEditorPanelRegistry& panelRegistry,
|
|
UIEditorWorkspaceModel& workspace,
|
|
UIEditorWorkspaceSession& session,
|
|
std::string_view preferredActivePanelId) {
|
|
NormalizeSessionStatesAgainstRegistry(panelRegistry, session);
|
|
|
|
std::string targetActivePanelId = {};
|
|
if (IsPanelSelectable(workspace, session, preferredActivePanelId)) {
|
|
targetActivePanelId = std::string(preferredActivePanelId);
|
|
} else if (IsPanelSelectable(workspace, session, workspace.activePanelId)) {
|
|
targetActivePanelId = workspace.activePanelId;
|
|
} else {
|
|
const std::vector<UIEditorWorkspaceVisiblePanel> visiblePanels =
|
|
CollectUIEditorWorkspaceVisiblePanels(workspace, session);
|
|
if (!visiblePanels.empty()) {
|
|
targetActivePanelId = visiblePanels.front().panelId;
|
|
}
|
|
}
|
|
|
|
if (targetActivePanelId.empty()) {
|
|
workspace.activePanelId.clear();
|
|
return;
|
|
}
|
|
|
|
TryActivateUIEditorWorkspacePanel(workspace, targetActivePanelId);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
UIEditorWorkspaceSession BuildDefaultUIEditorWorkspaceSession(
|
|
const UIEditorPanelRegistry& panelRegistry,
|
|
const UIEditorWorkspaceModel& workspace) {
|
|
UIEditorWorkspaceSession session = {};
|
|
std::vector<std::string> panelIds = {};
|
|
CollectWorkspacePanelIdsRecursive(workspace.root, panelIds);
|
|
session.panelStates.reserve(panelIds.size());
|
|
for (std::string& panelId : panelIds) {
|
|
UIEditorPanelSessionState state = {};
|
|
state.panelId = std::move(panelId);
|
|
session.panelStates.push_back(std::move(state));
|
|
}
|
|
|
|
NormalizeSessionStatesAgainstRegistry(panelRegistry, session);
|
|
return session;
|
|
}
|
|
|
|
const UIEditorPanelSessionState* FindUIEditorPanelSessionState(
|
|
const UIEditorWorkspaceSession& session,
|
|
std::string_view panelId) {
|
|
for (const UIEditorPanelSessionState& state : session.panelStates) {
|
|
if (state.panelId == panelId) {
|
|
return &state;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
UIEditorWorkspaceSessionValidationResult ValidateUIEditorWorkspaceSession(
|
|
const UIEditorPanelRegistry& panelRegistry,
|
|
const UIEditorWorkspaceModel& workspace,
|
|
const UIEditorWorkspaceSession& session) {
|
|
std::vector<std::string> workspacePanelIds = {};
|
|
CollectWorkspacePanelIdsRecursive(workspace.root, workspacePanelIds);
|
|
|
|
std::unordered_set<std::string> expectedPanelIds = {};
|
|
expectedPanelIds.insert(workspacePanelIds.begin(), workspacePanelIds.end());
|
|
|
|
std::unordered_set<std::string> seenPanelIds = {};
|
|
for (const UIEditorPanelSessionState& state : session.panelStates) {
|
|
if (!seenPanelIds.insert(state.panelId).second) {
|
|
return MakeValidationError(
|
|
UIEditorWorkspaceSessionValidationCode::DuplicatePanelId,
|
|
"Workspace session contains duplicated panel state '" + state.panelId + "'.");
|
|
}
|
|
|
|
if (!expectedPanelIds.contains(state.panelId)) {
|
|
return MakeValidationError(
|
|
UIEditorWorkspaceSessionValidationCode::UnknownPanelId,
|
|
"Workspace session state '" + state.panelId + "' is not present in the workspace tree.");
|
|
}
|
|
|
|
if (!state.open && state.visible) {
|
|
return MakeValidationError(
|
|
UIEditorWorkspaceSessionValidationCode::ClosedPanelVisible,
|
|
"Workspace session state '" + state.panelId + "' cannot be visible while closed.");
|
|
}
|
|
|
|
const UIEditorPanelDescriptor* descriptor = FindPanelDescriptor(panelRegistry, state.panelId);
|
|
if (descriptor == nullptr) {
|
|
return MakeValidationError(
|
|
UIEditorWorkspaceSessionValidationCode::UnknownPanelId,
|
|
"Workspace session state '" + state.panelId + "' is missing from the panel registry.");
|
|
}
|
|
|
|
if (!descriptor->canClose && !state.open) {
|
|
return MakeValidationError(
|
|
UIEditorWorkspaceSessionValidationCode::NonCloseablePanelClosed,
|
|
"Workspace session state '" + state.panelId + "' cannot be closed.");
|
|
}
|
|
|
|
if (!descriptor->canHide && state.open && !state.visible) {
|
|
return MakeValidationError(
|
|
UIEditorWorkspaceSessionValidationCode::NonHideablePanelHidden,
|
|
"Workspace session state '" + state.panelId + "' cannot be hidden.");
|
|
}
|
|
}
|
|
|
|
for (const std::string& panelId : workspacePanelIds) {
|
|
if (!seenPanelIds.contains(panelId)) {
|
|
return MakeValidationError(
|
|
UIEditorWorkspaceSessionValidationCode::MissingPanelState,
|
|
"Workspace panel '" + panelId + "' is missing from the workspace session.");
|
|
}
|
|
}
|
|
|
|
if (!workspace.activePanelId.empty() &&
|
|
FindUIEditorWorkspaceActivePanel(workspace, session) == nullptr) {
|
|
return MakeValidationError(
|
|
UIEditorWorkspaceSessionValidationCode::InvalidActivePanelId,
|
|
"Active panel id '" + workspace.activePanelId + "' is missing, closed, or hidden.");
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
bool AreUIEditorWorkspaceSessionsEquivalent(
|
|
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<UIEditorWorkspaceVisiblePanel> CollectUIEditorWorkspaceVisiblePanels(
|
|
const UIEditorWorkspaceModel& workspace,
|
|
const UIEditorWorkspaceSession& session) {
|
|
std::vector<UIEditorWorkspaceVisiblePanel> visiblePanels = {};
|
|
CollectVisiblePanelsRecursive(workspace.root, session, workspace.activePanelId, visiblePanels);
|
|
return visiblePanels;
|
|
}
|
|
|
|
const UIEditorWorkspacePanelState* FindUIEditorWorkspaceActivePanel(
|
|
const UIEditorWorkspaceModel& workspace,
|
|
const UIEditorWorkspaceSession& session) {
|
|
if (workspace.activePanelId.empty() ||
|
|
!IsPanelOpenAndVisible(session, workspace.activePanelId)) {
|
|
return nullptr;
|
|
}
|
|
|
|
const std::vector<UIEditorWorkspaceVisiblePanel> visiblePanels =
|
|
CollectUIEditorWorkspaceVisiblePanels(workspace, session);
|
|
for (const UIEditorWorkspaceVisiblePanel& panel : visiblePanels) {
|
|
if (panel.panelId == workspace.activePanelId) {
|
|
return FindPanelRecursive(workspace.root, workspace.activePanelId);
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool TryOpenUIEditorWorkspacePanel(
|
|
const UIEditorPanelRegistry& panelRegistry,
|
|
UIEditorWorkspaceModel& workspace,
|
|
UIEditorWorkspaceSession& session,
|
|
std::string_view panelId) {
|
|
const UIEditorWorkspaceModel workspaceBefore = workspace;
|
|
const UIEditorWorkspaceSession sessionBefore = session;
|
|
|
|
UIEditorPanelSessionState* state = FindMutablePanelSessionState(session, panelId);
|
|
if (state == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
state->open = true;
|
|
state->visible = true;
|
|
NormalizeWorkspaceSession(panelRegistry, workspace, session, panelId);
|
|
return !AreUIEditorWorkspaceModelsEquivalent(workspaceBefore, workspace) ||
|
|
!AreUIEditorWorkspaceSessionsEquivalent(sessionBefore, session);
|
|
}
|
|
|
|
bool TryCloseUIEditorWorkspacePanel(
|
|
const UIEditorPanelRegistry& panelRegistry,
|
|
UIEditorWorkspaceModel& workspace,
|
|
UIEditorWorkspaceSession& session,
|
|
std::string_view panelId) {
|
|
const UIEditorWorkspaceModel workspaceBefore = workspace;
|
|
const UIEditorWorkspaceSession sessionBefore = session;
|
|
|
|
UIEditorPanelSessionState* state = FindMutablePanelSessionState(session, panelId);
|
|
const UIEditorPanelDescriptor* descriptor = FindPanelDescriptor(panelRegistry, panelId);
|
|
if (state == nullptr || descriptor == nullptr || !descriptor->canClose) {
|
|
return false;
|
|
}
|
|
|
|
state->open = false;
|
|
state->visible = false;
|
|
NormalizeWorkspaceSession(panelRegistry, workspace, session, {});
|
|
return !AreUIEditorWorkspaceModelsEquivalent(workspaceBefore, workspace) ||
|
|
!AreUIEditorWorkspaceSessionsEquivalent(sessionBefore, session);
|
|
}
|
|
|
|
bool TryShowUIEditorWorkspacePanel(
|
|
const UIEditorPanelRegistry& panelRegistry,
|
|
UIEditorWorkspaceModel& workspace,
|
|
UIEditorWorkspaceSession& session,
|
|
std::string_view panelId) {
|
|
const UIEditorWorkspaceModel workspaceBefore = workspace;
|
|
const UIEditorWorkspaceSession sessionBefore = session;
|
|
|
|
UIEditorPanelSessionState* state = FindMutablePanelSessionState(session, panelId);
|
|
const UIEditorPanelDescriptor* descriptor = FindPanelDescriptor(panelRegistry, panelId);
|
|
if (state == nullptr || descriptor == nullptr || !state->open || !descriptor->canHide) {
|
|
return false;
|
|
}
|
|
|
|
state->visible = true;
|
|
NormalizeWorkspaceSession(panelRegistry, workspace, session, panelId);
|
|
return !AreUIEditorWorkspaceModelsEquivalent(workspaceBefore, workspace) ||
|
|
!AreUIEditorWorkspaceSessionsEquivalent(sessionBefore, session);
|
|
}
|
|
|
|
bool TryHideUIEditorWorkspacePanel(
|
|
const UIEditorPanelRegistry& panelRegistry,
|
|
UIEditorWorkspaceModel& workspace,
|
|
UIEditorWorkspaceSession& session,
|
|
std::string_view panelId) {
|
|
const UIEditorWorkspaceModel workspaceBefore = workspace;
|
|
const UIEditorWorkspaceSession sessionBefore = session;
|
|
|
|
UIEditorPanelSessionState* state = FindMutablePanelSessionState(session, panelId);
|
|
const UIEditorPanelDescriptor* descriptor = FindPanelDescriptor(panelRegistry, panelId);
|
|
if (state == nullptr || descriptor == nullptr || !state->open || !descriptor->canHide) {
|
|
return false;
|
|
}
|
|
|
|
state->visible = false;
|
|
NormalizeWorkspaceSession(panelRegistry, workspace, session, {});
|
|
return !AreUIEditorWorkspaceModelsEquivalent(workspaceBefore, workspace) ||
|
|
!AreUIEditorWorkspaceSessionsEquivalent(sessionBefore, session);
|
|
}
|
|
|
|
bool TryActivateUIEditorWorkspacePanel(
|
|
const UIEditorPanelRegistry& panelRegistry,
|
|
UIEditorWorkspaceModel& workspace,
|
|
UIEditorWorkspaceSession& session,
|
|
std::string_view panelId) {
|
|
const UIEditorWorkspaceModel workspaceBefore = workspace;
|
|
const UIEditorWorkspaceSession sessionBefore = session;
|
|
|
|
if (!IsPanelSelectable(workspace, session, panelId)) {
|
|
return false;
|
|
}
|
|
|
|
NormalizeWorkspaceSession(panelRegistry, workspace, session, panelId);
|
|
return !AreUIEditorWorkspaceModelsEquivalent(workspaceBefore, workspace) ||
|
|
!AreUIEditorWorkspaceSessionsEquivalent(sessionBefore, session);
|
|
}
|
|
|
|
} // namespace XCEngine::UI::Editor
|