Files
XCEngine/editor/src/Workspace/UIEditorWorkspaceSession.cpp
2026-04-25 16:46:01 +08:00

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