关键节点
This commit is contained in:
447
editor/src/Workspace/UIEditorWorkspaceSession.cpp
Normal file
447
editor/src/Workspace/UIEditorWorkspaceSession.cpp
Normal file
@@ -0,0 +1,447 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user