Unify dock leaves around single-tab stacks
This commit is contained in:
@@ -40,7 +40,7 @@ UIEditorWorkspaceModel BuildWorkspace() {
|
||||
"workspace-top",
|
||||
UIEditorWorkspaceSplitAxis::Horizontal,
|
||||
0.15f,
|
||||
BuildUIEditorWorkspacePanel(
|
||||
BuildUIEditorWorkspaceSingleTabStack(
|
||||
"hierarchy-panel",
|
||||
"hierarchy",
|
||||
"Hierarchy",
|
||||
@@ -64,7 +64,7 @@ UIEditorWorkspaceModel BuildWorkspace() {
|
||||
false)
|
||||
},
|
||||
0u),
|
||||
BuildUIEditorWorkspacePanel(
|
||||
BuildUIEditorWorkspaceSingleTabStack(
|
||||
"inspector-panel",
|
||||
"inspector",
|
||||
"Inspector",
|
||||
|
||||
@@ -96,15 +96,6 @@ struct UIEditorDockHostSplitterLayout {
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
struct UIEditorDockHostPanelLayout {
|
||||
std::string nodeId = {};
|
||||
std::string panelId = {};
|
||||
std::string title = {};
|
||||
bool active = false;
|
||||
UIEditorPanelFrameState frameState = {};
|
||||
UIEditorPanelFrameLayout frameLayout = {};
|
||||
};
|
||||
|
||||
struct UIEditorDockHostTabStackLayout {
|
||||
std::string nodeId = {};
|
||||
std::string selectedPanelId = {};
|
||||
@@ -119,7 +110,6 @@ struct UIEditorDockHostTabStackLayout {
|
||||
struct UIEditorDockHostLayout {
|
||||
::XCEngine::UI::UIRect bounds = {};
|
||||
std::vector<UIEditorDockHostSplitterLayout> splitters = {};
|
||||
std::vector<UIEditorDockHostPanelLayout> panels = {};
|
||||
std::vector<UIEditorDockHostTabStackLayout> tabStacks = {};
|
||||
};
|
||||
|
||||
|
||||
@@ -78,6 +78,12 @@ UIEditorWorkspaceNode BuildUIEditorWorkspacePanel(
|
||||
std::string title,
|
||||
bool placeholder = false);
|
||||
|
||||
UIEditorWorkspaceNode BuildUIEditorWorkspaceSingleTabStack(
|
||||
std::string nodeId,
|
||||
std::string panelId,
|
||||
std::string title,
|
||||
bool placeholder = false);
|
||||
|
||||
UIEditorWorkspaceNode BuildUIEditorWorkspaceTabStack(
|
||||
std::string nodeId,
|
||||
std::vector<UIEditorWorkspaceNode> panels,
|
||||
@@ -93,6 +99,9 @@ UIEditorWorkspaceNode BuildUIEditorWorkspaceSplit(
|
||||
UIEditorWorkspaceValidationResult ValidateUIEditorWorkspace(
|
||||
const UIEditorWorkspaceModel& workspace);
|
||||
|
||||
UIEditorWorkspaceModel CanonicalizeUIEditorWorkspaceModel(
|
||||
UIEditorWorkspaceModel workspace);
|
||||
|
||||
std::vector<UIEditorWorkspaceVisiblePanel> CollectUIEditorWorkspaceVisiblePanels(
|
||||
const UIEditorWorkspaceModel& workspace);
|
||||
|
||||
|
||||
@@ -23,9 +23,6 @@ using ::XCEngine::UI::Layout::UITabStripMeasureItem;
|
||||
using ::XCEngine::UI::Layout::UILayoutAxis;
|
||||
using ::XCEngine::UI::Widgets::ExpandUISplitterHandleHitRect;
|
||||
|
||||
constexpr std::string_view kStandalonePanelActiveFooter = "";
|
||||
constexpr std::string_view kStandalonePanelInactiveFooter = "";
|
||||
|
||||
struct DockMeasureResult {
|
||||
bool visible = false;
|
||||
UISize minimumSize = {};
|
||||
@@ -102,17 +99,6 @@ UIEditorPanelFrameMetrics BuildTabContentFrameMetrics(
|
||||
return frameMetrics;
|
||||
}
|
||||
|
||||
UISize MeasurePanelMinimumSize(
|
||||
const UIEditorDockHostMetrics& metrics) {
|
||||
const UIEditorPanelFrameMetrics& frameMetrics = metrics.panelFrameMetrics;
|
||||
return UISize(
|
||||
metrics.minimumStandalonePanelBodySize.width +
|
||||
ClampNonNegative(frameMetrics.contentPadding) * 2.0f,
|
||||
ClampNonNegative(frameMetrics.headerHeight) +
|
||||
metrics.minimumStandalonePanelBodySize.height +
|
||||
ClampNonNegative(frameMetrics.contentPadding) * 2.0f);
|
||||
}
|
||||
|
||||
UISize MeasureTabContentMinimumSize(
|
||||
const UIEditorDockHostMetrics& metrics) {
|
||||
const UIEditorPanelFrameMetrics frameMetrics = BuildTabContentFrameMetrics(metrics);
|
||||
@@ -205,14 +191,8 @@ DockMeasureResult MeasureNodeRecursive(
|
||||
const UIEditorWorkspaceSession& session,
|
||||
const UIEditorDockHostMetrics& metrics) {
|
||||
switch (node.kind) {
|
||||
case UIEditorWorkspaceNodeKind::Panel: {
|
||||
DockMeasureResult result = {};
|
||||
result.visible = IsPanelOpenAndVisible(session, node.panel.panelId);
|
||||
if (result.visible) {
|
||||
result.minimumSize = MeasurePanelMinimumSize(metrics);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case UIEditorWorkspaceNodeKind::Panel:
|
||||
return {};
|
||||
case UIEditorWorkspaceNodeKind::TabStack:
|
||||
return MeasureTabStackNode(node, panelRegistry, session, metrics);
|
||||
case UIEditorWorkspaceNodeKind::Split:
|
||||
@@ -269,38 +249,6 @@ UIEditorTabStripState BuildTabStripState(
|
||||
return tabState;
|
||||
}
|
||||
|
||||
UIEditorPanelFrameState BuildStandalonePanelFrameState(
|
||||
const UIEditorDockHostState& state,
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
const UIEditorPanelDescriptor* descriptor,
|
||||
const UIEditorWorkspaceNode& node) {
|
||||
UIEditorPanelFrameState frameState = {};
|
||||
frameState.active = workspace.activePanelId == node.panel.panelId;
|
||||
frameState.focused = state.focused && frameState.active;
|
||||
frameState.closable = descriptor != nullptr ? descriptor->canClose : true;
|
||||
frameState.pinnable = false;
|
||||
frameState.showFooter = true;
|
||||
|
||||
if (state.hoveredTarget.panelId != node.panel.panelId) {
|
||||
return frameState;
|
||||
}
|
||||
|
||||
switch (state.hoveredTarget.kind) {
|
||||
case UIEditorDockHostHitTargetKind::PanelHeader:
|
||||
case UIEditorDockHostHitTargetKind::PanelBody:
|
||||
case UIEditorDockHostHitTargetKind::PanelFooter:
|
||||
case UIEditorDockHostHitTargetKind::PanelCloseButton:
|
||||
frameState.hovered = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
frameState.closeHovered =
|
||||
state.hoveredTarget.kind == UIEditorDockHostHitTargetKind::PanelCloseButton;
|
||||
return frameState;
|
||||
}
|
||||
|
||||
UIEditorPanelFrameState BuildTabContentFrameState(
|
||||
const UIEditorDockHostState& state,
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
@@ -338,29 +286,6 @@ void LayoutNodeRecursive(
|
||||
const UIEditorDockHostMetrics& metrics,
|
||||
UIEditorDockHostLayout& layout);
|
||||
|
||||
void LayoutPanelNode(
|
||||
const UIEditorWorkspaceNode& node,
|
||||
const UIRect& bounds,
|
||||
const UIEditorPanelRegistry& panelRegistry,
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
const UIEditorDockHostState& state,
|
||||
const UIEditorDockHostMetrics& metrics,
|
||||
UIEditorDockHostLayout& layout) {
|
||||
const UIEditorPanelDescriptor* descriptor =
|
||||
FindPanelDescriptor(panelRegistry, node.panel.panelId);
|
||||
|
||||
UIEditorDockHostPanelLayout panelLayout = {};
|
||||
panelLayout.nodeId = node.nodeId;
|
||||
panelLayout.panelId = node.panel.panelId;
|
||||
panelLayout.title = node.panel.title;
|
||||
panelLayout.active = workspace.activePanelId == node.panel.panelId;
|
||||
panelLayout.frameState =
|
||||
BuildStandalonePanelFrameState(state, workspace, descriptor, node);
|
||||
panelLayout.frameLayout =
|
||||
BuildUIEditorPanelFrameLayout(bounds, panelLayout.frameState, metrics.panelFrameMetrics);
|
||||
layout.panels.push_back(std::move(panelLayout));
|
||||
}
|
||||
|
||||
void LayoutTabStackNode(
|
||||
const UIEditorWorkspaceNode& node,
|
||||
const UIRect& bounds,
|
||||
@@ -525,9 +450,6 @@ void LayoutNodeRecursive(
|
||||
UIEditorDockHostLayout& layout) {
|
||||
switch (node.kind) {
|
||||
case UIEditorWorkspaceNodeKind::Panel:
|
||||
if (IsPanelOpenAndVisible(session, node.panel.panelId)) {
|
||||
LayoutPanelNode(node, bounds, panelRegistry, workspace, state, metrics, layout);
|
||||
}
|
||||
return;
|
||||
case UIEditorWorkspaceNodeKind::TabStack:
|
||||
LayoutTabStackNode(node, bounds, panelRegistry, workspace, session, state, metrics, layout);
|
||||
@@ -606,11 +528,13 @@ UIEditorDockHostLayout BuildUIEditorDockHostLayout(
|
||||
bounds.y,
|
||||
ClampNonNegative(bounds.width),
|
||||
ClampNonNegative(bounds.height));
|
||||
const UIEditorWorkspaceModel canonicalWorkspace =
|
||||
CanonicalizeUIEditorWorkspaceModel(workspace);
|
||||
LayoutNodeRecursive(
|
||||
workspace.root,
|
||||
canonicalWorkspace.root,
|
||||
layout.bounds,
|
||||
panelRegistry,
|
||||
workspace,
|
||||
canonicalWorkspace,
|
||||
session,
|
||||
state,
|
||||
metrics,
|
||||
@@ -675,17 +599,6 @@ UIEditorDockHostHitTarget HitTestUIEditorDockHost(
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t index = layout.panels.size(); index > 0u; --index) {
|
||||
const UIEditorDockHostPanelLayout& panel = layout.panels[index - 1u];
|
||||
const UIEditorPanelFrameHitTarget hitTarget = HitTestUIEditorPanelFrame(
|
||||
panel.frameLayout,
|
||||
panel.frameState,
|
||||
point);
|
||||
if (hitTarget != UIEditorPanelFrameHitTarget::None) {
|
||||
return MapPanelFrameHitTarget(hitTarget, panel.nodeId, panel.panelId);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -694,15 +607,6 @@ void AppendUIEditorDockHostBackground(
|
||||
const UIEditorDockHostLayout& layout,
|
||||
const UIEditorDockHostPalette& palette,
|
||||
const UIEditorDockHostMetrics& metrics) {
|
||||
for (const UIEditorDockHostPanelLayout& panel : layout.panels) {
|
||||
AppendUIEditorPanelFrameBackground(
|
||||
drawList,
|
||||
panel.frameLayout,
|
||||
panel.frameState,
|
||||
palette.panelFramePalette,
|
||||
metrics.panelFrameMetrics);
|
||||
}
|
||||
|
||||
const UIEditorPanelFrameMetrics tabContentFrameMetrics =
|
||||
BuildTabContentFrameMetrics(metrics);
|
||||
for (const UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) {
|
||||
@@ -744,23 +648,6 @@ void AppendUIEditorDockHostForeground(
|
||||
const UIEditorDockHostForegroundOptions& options,
|
||||
const UIEditorDockHostPalette& palette,
|
||||
const UIEditorDockHostMetrics& metrics) {
|
||||
for (const UIEditorDockHostPanelLayout& panel : layout.panels) {
|
||||
AppendUIEditorPanelFrameForeground(
|
||||
drawList,
|
||||
panel.frameLayout,
|
||||
panel.frameState,
|
||||
UIEditorPanelFrameText{
|
||||
panel.title,
|
||||
{},
|
||||
panel.active ? kStandalonePanelActiveFooter : kStandalonePanelInactiveFooter
|
||||
},
|
||||
palette.panelFramePalette,
|
||||
metrics.panelFrameMetrics);
|
||||
if (UsesExternalBodyPresentation(options, panel.panelId)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const UIEditorPanelFrameMetrics tabContentFrameMetrics =
|
||||
BuildTabContentFrameMetrics(metrics);
|
||||
for (const UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) {
|
||||
|
||||
@@ -12,12 +12,6 @@ namespace {
|
||||
const ::XCEngine::UI::UIRect* FindVisiblePanelBodyRect(
|
||||
const Widgets::UIEditorDockHostLayout& layout,
|
||||
std::string_view panelId) {
|
||||
for (const Widgets::UIEditorDockHostPanelLayout& panel : layout.panels) {
|
||||
if (panel.panelId == panelId) {
|
||||
return &panel.frameLayout.bodyRect;
|
||||
}
|
||||
}
|
||||
|
||||
for (const Widgets::UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) {
|
||||
if (tabStack.selectedPanelId == panelId) {
|
||||
return &tabStack.contentFrameLayout.bodyRect;
|
||||
|
||||
@@ -75,9 +75,9 @@ UIEditorWorkspaceController::UIEditorWorkspaceController(
|
||||
UIEditorWorkspaceModel workspace,
|
||||
UIEditorWorkspaceSession session)
|
||||
: m_panelRegistry(std::move(panelRegistry))
|
||||
, m_baselineWorkspace(workspace)
|
||||
, m_baselineWorkspace(CanonicalizeUIEditorWorkspaceModel(workspace))
|
||||
, m_baselineSession(session)
|
||||
, m_workspace(std::move(workspace))
|
||||
, m_workspace(m_baselineWorkspace)
|
||||
, m_session(std::move(session)) {
|
||||
}
|
||||
|
||||
@@ -178,6 +178,10 @@ const UIEditorPanelDescriptor* UIEditorWorkspaceController::FindPanelDescriptor(
|
||||
|
||||
UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::RestoreLayoutSnapshot(
|
||||
const UIEditorWorkspaceLayoutSnapshot& snapshot) {
|
||||
UIEditorWorkspaceLayoutSnapshot canonicalSnapshot = snapshot;
|
||||
canonicalSnapshot.workspace =
|
||||
CanonicalizeUIEditorWorkspaceModel(std::move(canonicalSnapshot.workspace));
|
||||
|
||||
const UIEditorPanelRegistryValidationResult registryValidation =
|
||||
ValidateUIEditorPanelRegistry(m_panelRegistry);
|
||||
if (!registryValidation.IsValid()) {
|
||||
@@ -187,7 +191,7 @@ UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::RestoreLayou
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceValidationResult workspaceValidation =
|
||||
ValidateUIEditorWorkspace(snapshot.workspace);
|
||||
ValidateUIEditorWorkspace(canonicalSnapshot.workspace);
|
||||
if (!workspaceValidation.IsValid()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
@@ -195,15 +199,18 @@ UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::RestoreLayou
|
||||
}
|
||||
|
||||
const UIEditorWorkspaceSessionValidationResult sessionValidation =
|
||||
ValidateUIEditorWorkspaceSession(m_panelRegistry, snapshot.workspace, snapshot.session);
|
||||
ValidateUIEditorWorkspaceSession(
|
||||
m_panelRegistry,
|
||||
canonicalSnapshot.workspace,
|
||||
canonicalSnapshot.session);
|
||||
if (!sessionValidation.IsValid()) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::Rejected,
|
||||
"Layout session invalid: " + sessionValidation.message);
|
||||
}
|
||||
|
||||
if (AreUIEditorWorkspaceModelsEquivalent(m_workspace, snapshot.workspace) &&
|
||||
AreUIEditorWorkspaceSessionsEquivalent(m_session, snapshot.session)) {
|
||||
if (AreUIEditorWorkspaceModelsEquivalent(m_workspace, canonicalSnapshot.workspace) &&
|
||||
AreUIEditorWorkspaceSessionsEquivalent(m_session, canonicalSnapshot.session)) {
|
||||
return BuildLayoutOperationResult(
|
||||
UIEditorWorkspaceLayoutOperationStatus::NoOp,
|
||||
"Current state already matches the requested layout snapshot.");
|
||||
@@ -211,8 +218,8 @@ UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::RestoreLayou
|
||||
|
||||
const UIEditorWorkspaceModel previousWorkspace = m_workspace;
|
||||
const UIEditorWorkspaceSession previousSession = m_session;
|
||||
m_workspace = snapshot.workspace;
|
||||
m_session = snapshot.session;
|
||||
m_workspace = canonicalSnapshot.workspace;
|
||||
m_session = canonicalSnapshot.session;
|
||||
|
||||
const UIEditorWorkspaceControllerValidationResult validation = ValidateState();
|
||||
if (!validation.IsValid()) {
|
||||
@@ -441,10 +448,12 @@ UIEditorWorkspaceCommandResult UIEditorWorkspaceController::Dispatch(
|
||||
UIEditorWorkspaceController BuildDefaultUIEditorWorkspaceController(
|
||||
const UIEditorPanelRegistry& panelRegistry,
|
||||
const UIEditorWorkspaceModel& workspace) {
|
||||
const UIEditorWorkspaceModel canonicalWorkspace =
|
||||
CanonicalizeUIEditorWorkspaceModel(workspace);
|
||||
return UIEditorWorkspaceController(
|
||||
panelRegistry,
|
||||
workspace,
|
||||
BuildDefaultUIEditorWorkspaceSession(panelRegistry, workspace));
|
||||
canonicalWorkspace,
|
||||
BuildDefaultUIEditorWorkspaceSession(panelRegistry, canonicalWorkspace));
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor
|
||||
|
||||
@@ -391,7 +391,7 @@ UIEditorWorkspaceLayoutSnapshot BuildUIEditorWorkspaceLayoutSnapshot(
|
||||
const UIEditorWorkspaceModel& workspace,
|
||||
const UIEditorWorkspaceSession& session) {
|
||||
UIEditorWorkspaceLayoutSnapshot snapshot = {};
|
||||
snapshot.workspace = workspace;
|
||||
snapshot.workspace = CanonicalizeUIEditorWorkspaceModel(workspace);
|
||||
snapshot.session = session;
|
||||
return snapshot;
|
||||
}
|
||||
@@ -461,6 +461,8 @@ UIEditorWorkspaceLayoutLoadResult DeserializeUIEditorWorkspaceLayoutSnapshot(
|
||||
return rootResult;
|
||||
}
|
||||
|
||||
snapshot.workspace = CanonicalizeUIEditorWorkspaceModel(std::move(snapshot.workspace));
|
||||
|
||||
snapshot.session.panelStates.clear();
|
||||
while (index < lines.size()) {
|
||||
UIEditorPanelSessionState state = {};
|
||||
|
||||
@@ -22,6 +22,55 @@ bool IsValidSplitRatio(float value) {
|
||||
return std::isfinite(value) && value > 0.0f && value < 1.0f;
|
||||
}
|
||||
|
||||
std::string BuildSingleTabPanelNodeId(std::string_view stackNodeId) {
|
||||
if (stackNodeId.empty()) {
|
||||
return "single-tab-panel";
|
||||
}
|
||||
|
||||
return std::string(stackNodeId) + "__panel";
|
||||
}
|
||||
|
||||
UIEditorWorkspaceNode WrapStandalonePanelAsTabStack(UIEditorWorkspaceNode panelNode) {
|
||||
UIEditorWorkspaceNode panelChild = std::move(panelNode);
|
||||
const std::string stackNodeId = panelChild.nodeId;
|
||||
panelChild.nodeId = BuildSingleTabPanelNodeId(stackNodeId);
|
||||
|
||||
UIEditorWorkspaceNode tabStack = {};
|
||||
tabStack.kind = UIEditorWorkspaceNodeKind::TabStack;
|
||||
tabStack.nodeId = stackNodeId;
|
||||
tabStack.selectedTabIndex = 0u;
|
||||
tabStack.children.push_back(std::move(panelChild));
|
||||
return tabStack;
|
||||
}
|
||||
|
||||
void CanonicalizeNodeRecursive(
|
||||
UIEditorWorkspaceNode& node,
|
||||
bool allowStandalonePanelLeaf) {
|
||||
if (node.kind == UIEditorWorkspaceNodeKind::Panel) {
|
||||
if (!allowStandalonePanelLeaf) {
|
||||
node = WrapStandalonePanelAsTabStack(std::move(node));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (UIEditorWorkspaceNode& child : node.children) {
|
||||
CanonicalizeNodeRecursive(
|
||||
child,
|
||||
node.kind == UIEditorWorkspaceNodeKind::TabStack);
|
||||
}
|
||||
|
||||
if (node.kind == UIEditorWorkspaceNodeKind::TabStack &&
|
||||
!node.children.empty() &&
|
||||
node.selectedTabIndex >= node.children.size()) {
|
||||
node.selectedTabIndex = node.children.size() - 1u;
|
||||
}
|
||||
|
||||
if (node.kind == UIEditorWorkspaceNodeKind::Split &&
|
||||
node.children.size() == 1u) {
|
||||
node = std::move(node.children.front());
|
||||
}
|
||||
}
|
||||
|
||||
const UIEditorPanelDescriptor& RequirePanelDescriptor(
|
||||
const UIEditorPanelRegistry& registry,
|
||||
std::string_view panelId) {
|
||||
@@ -274,7 +323,7 @@ UIEditorWorkspaceModel BuildDefaultEditorShellWorkspaceModel() {
|
||||
RequirePanelDescriptor(registry, "editor-foundation-root");
|
||||
|
||||
UIEditorWorkspaceModel workspace = {};
|
||||
workspace.root = BuildUIEditorWorkspacePanel(
|
||||
workspace.root = BuildUIEditorWorkspaceSingleTabStack(
|
||||
"editor-foundation-root-node",
|
||||
rootPanel.panelId,
|
||||
rootPanel.defaultTitle,
|
||||
@@ -297,6 +346,22 @@ UIEditorWorkspaceNode BuildUIEditorWorkspacePanel(
|
||||
return node;
|
||||
}
|
||||
|
||||
UIEditorWorkspaceNode BuildUIEditorWorkspaceSingleTabStack(
|
||||
std::string nodeId,
|
||||
std::string panelId,
|
||||
std::string title,
|
||||
bool placeholder) {
|
||||
UIEditorWorkspaceNode panel = BuildUIEditorWorkspacePanel(
|
||||
BuildSingleTabPanelNodeId(nodeId),
|
||||
std::move(panelId),
|
||||
std::move(title),
|
||||
placeholder);
|
||||
return BuildUIEditorWorkspaceTabStack(
|
||||
std::move(nodeId),
|
||||
{ std::move(panel) },
|
||||
0u);
|
||||
}
|
||||
|
||||
UIEditorWorkspaceNode BuildUIEditorWorkspaceTabStack(
|
||||
std::string nodeId,
|
||||
std::vector<UIEditorWorkspaceNode> panels,
|
||||
@@ -345,6 +410,12 @@ UIEditorWorkspaceValidationResult ValidateUIEditorWorkspace(
|
||||
return {};
|
||||
}
|
||||
|
||||
UIEditorWorkspaceModel CanonicalizeUIEditorWorkspaceModel(
|
||||
UIEditorWorkspaceModel workspace) {
|
||||
CanonicalizeNodeRecursive(workspace.root, false);
|
||||
return workspace;
|
||||
}
|
||||
|
||||
std::vector<UIEditorWorkspaceVisiblePanel> CollectUIEditorWorkspaceVisiblePanels(
|
||||
const UIEditorWorkspaceModel& workspace) {
|
||||
std::vector<UIEditorWorkspaceVisiblePanel> visiblePanels = {};
|
||||
|
||||
@@ -586,7 +586,7 @@ private:
|
||||
|
||||
DrawCard(drawList, m_introRect, "这个测试验证什么功能?", "只验证 DockHost 基础交互 contract,不做 editor 业务面板。");
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 72.0f), "1. 验证 splitter drag 是否只通过 DockHostInteraction + WorkspaceController 完成。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 94.0f), "2. 验证 tab activate / tab close / standalone panel activate / panel close。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 94.0f), "2. 验证 unified dock:tab activate / tab close / single-tab body activate。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 116.0f), "3. 验证 active panel、visible panels、split ratio 是否统一收口到 controller。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 138.0f), "4. 验证 pointer capture / release 请求是否通过 contract 明确返回。", kTextPrimary, 12.0f);
|
||||
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 162.0f), "建议操作:先拖中间 splitter,再点 Document A。", kTextWeak, 11.0f);
|
||||
|
||||
@@ -14,6 +14,7 @@ using XCEngine::UI::UIPoint;
|
||||
using XCEngine::UI::UIRect;
|
||||
using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceSession;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSingleTabStack;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack;
|
||||
using XCEngine::UI::Editor::TryHideUIEditorWorkspacePanel;
|
||||
@@ -62,8 +63,8 @@ UIEditorWorkspaceModel BuildWorkspace() {
|
||||
"right-split",
|
||||
UIEditorWorkspaceSplitAxis::Vertical,
|
||||
0.6f,
|
||||
BuildUIEditorWorkspacePanel("details-node", "details", "Details", true),
|
||||
BuildUIEditorWorkspacePanel("console-node", "console", "Console", true)));
|
||||
BuildUIEditorWorkspaceSingleTabStack("details-node", "details", "Details", true),
|
||||
BuildUIEditorWorkspaceSingleTabStack("console-node", "console", "Console", true)));
|
||||
workspace.activePanelId = "doc-b";
|
||||
return workspace;
|
||||
}
|
||||
@@ -80,7 +81,7 @@ bool ContainsTextCommand(const UIDrawList& drawList, std::string_view text) {
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(UIEditorDockHostTest, LayoutComposesSplitTabStackAndStandalonePanelsFromWorkspaceTree) {
|
||||
TEST(UIEditorDockHostTest, LayoutComposesOnlyUnifiedTabStacksFromWorkspaceTree) {
|
||||
const UIEditorPanelRegistry registry = BuildPanelRegistry();
|
||||
const UIEditorWorkspaceModel workspace = BuildWorkspace();
|
||||
const UIEditorWorkspaceSession session =
|
||||
@@ -93,8 +94,7 @@ TEST(UIEditorDockHostTest, LayoutComposesSplitTabStackAndStandalonePanelsFromWor
|
||||
session);
|
||||
|
||||
ASSERT_EQ(layout.splitters.size(), 2u);
|
||||
ASSERT_EQ(layout.tabStacks.size(), 1u);
|
||||
ASSERT_EQ(layout.panels.size(), 2u);
|
||||
ASSERT_EQ(layout.tabStacks.size(), 3u);
|
||||
|
||||
const auto* rootSplitter = FindUIEditorDockHostSplitterLayout(layout, "root-split");
|
||||
ASSERT_NE(rootSplitter, nullptr);
|
||||
@@ -109,8 +109,10 @@ TEST(UIEditorDockHostTest, LayoutComposesSplitTabStackAndStandalonePanelsFromWor
|
||||
EXPECT_EQ(tabStack.items[1].panelId, "doc-b");
|
||||
EXPECT_EQ(tabStack.tabStripState.selectedIndex, 1u);
|
||||
|
||||
EXPECT_EQ(layout.panels[0].panelId, "details");
|
||||
EXPECT_EQ(layout.panels[1].panelId, "console");
|
||||
EXPECT_EQ(layout.tabStacks[1].nodeId, "details-node");
|
||||
EXPECT_EQ(layout.tabStacks[1].selectedPanelId, "details");
|
||||
EXPECT_EQ(layout.tabStacks[2].nodeId, "console-node");
|
||||
EXPECT_EQ(layout.tabStacks[2].selectedPanelId, "console");
|
||||
}
|
||||
|
||||
TEST(UIEditorDockHostTest, HiddenBranchCollapsesAndVisibleBranchUsesFullBounds) {
|
||||
@@ -130,7 +132,6 @@ TEST(UIEditorDockHostTest, HiddenBranchCollapsesAndVisibleBranchUsesFullBounds)
|
||||
|
||||
EXPECT_TRUE(layout.splitters.empty());
|
||||
ASSERT_EQ(layout.tabStacks.size(), 1u);
|
||||
EXPECT_TRUE(layout.panels.empty());
|
||||
EXPECT_FLOAT_EQ(layout.tabStacks.front().bounds.x, 10.0f);
|
||||
EXPECT_FLOAT_EQ(layout.tabStacks.front().bounds.y, 20.0f);
|
||||
EXPECT_FLOAT_EQ(layout.tabStacks.front().bounds.width, 640.0f);
|
||||
@@ -158,7 +159,7 @@ TEST(UIEditorDockHostTest, HitTestPrioritizesSplitterThenTabCloseThenPanelBody)
|
||||
EXPECT_EQ(splitterHit.kind, UIEditorDockHostHitTargetKind::SplitterHandle);
|
||||
EXPECT_EQ(splitterHit.nodeId, "root-split");
|
||||
|
||||
ASSERT_EQ(layout.tabStacks.size(), 1u);
|
||||
ASSERT_EQ(layout.tabStacks.size(), 3u);
|
||||
const auto& closeRect = layout.tabStacks.front().tabStripLayout.closeButtonRects[1];
|
||||
const auto tabCloseHit = HitTestUIEditorDockHost(
|
||||
layout,
|
||||
@@ -209,7 +210,7 @@ TEST(UIEditorDockHostTest, BackgroundAndForegroundEmitStableCompositeCommands) {
|
||||
EXPECT_GT(foreground.GetCommandCount(), 10u);
|
||||
}
|
||||
|
||||
TEST(UIEditorDockHostTest, ForegroundByDefaultStillDrawsPlaceholderText) {
|
||||
TEST(UIEditorDockHostTest, ForegroundDrawsUnifiedTabTitlesAcrossAllLeafStacks) {
|
||||
const UIEditorPanelRegistry registry = BuildPanelRegistry();
|
||||
const UIEditorWorkspaceModel workspace = BuildWorkspace();
|
||||
const UIEditorWorkspaceSession session =
|
||||
@@ -224,11 +225,13 @@ TEST(UIEditorDockHostTest, ForegroundByDefaultStillDrawsPlaceholderText) {
|
||||
UIDrawList foreground("DockHostForegroundDefault");
|
||||
AppendUIEditorDockHostForeground(foreground, layout);
|
||||
|
||||
EXPECT_TRUE(ContainsTextCommand(foreground, "DockHost tab content placeholder"));
|
||||
EXPECT_TRUE(ContainsTextCommand(foreground, "DockHost standalone panel"));
|
||||
EXPECT_TRUE(ContainsTextCommand(foreground, "Document A"));
|
||||
EXPECT_TRUE(ContainsTextCommand(foreground, "Document B"));
|
||||
EXPECT_TRUE(ContainsTextCommand(foreground, "Details"));
|
||||
EXPECT_TRUE(ContainsTextCommand(foreground, "Console"));
|
||||
}
|
||||
|
||||
TEST(UIEditorDockHostTest, ForegroundSkipsPlaceholderForExternalBodyPanelId) {
|
||||
TEST(UIEditorDockHostTest, ForegroundWithExternalBodyStillDrawsUnifiedTabTitles) {
|
||||
const UIEditorPanelRegistry registry = BuildPanelRegistry();
|
||||
const UIEditorWorkspaceModel workspace = BuildWorkspace();
|
||||
const UIEditorWorkspaceSession session =
|
||||
@@ -245,6 +248,8 @@ TEST(UIEditorDockHostTest, ForegroundSkipsPlaceholderForExternalBodyPanelId) {
|
||||
options.externalBodyPanelIds = { "doc-b" };
|
||||
AppendUIEditorDockHostForeground(foreground, layout, options);
|
||||
|
||||
EXPECT_FALSE(ContainsTextCommand(foreground, "DockHost tab content placeholder"));
|
||||
EXPECT_TRUE(ContainsTextCommand(foreground, "DockHost standalone panel"));
|
||||
EXPECT_TRUE(ContainsTextCommand(foreground, "Document A"));
|
||||
EXPECT_TRUE(ContainsTextCommand(foreground, "Document B"));
|
||||
EXPECT_TRUE(ContainsTextCommand(foreground, "Details"));
|
||||
EXPECT_TRUE(ContainsTextCommand(foreground, "Console"));
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ using XCEngine::UI::UIPointerButton;
|
||||
using XCEngine::Input::KeyCode;
|
||||
using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceController;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSingleTabStack;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack;
|
||||
using XCEngine::UI::Editor::FindUIEditorPanelSessionState;
|
||||
@@ -24,6 +25,8 @@ using XCEngine::UI::Editor::UIEditorWorkspaceModel;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceSplitAxis;
|
||||
using XCEngine::UI::Editor::UpdateUIEditorDockHostInteraction;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorDockHostHitTargetKind;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorDockHostLayout;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorDockHostTabStackLayout;
|
||||
|
||||
UIEditorPanelRegistry BuildPanelRegistry() {
|
||||
UIEditorPanelRegistry registry = {};
|
||||
@@ -53,8 +56,8 @@ UIEditorWorkspaceModel BuildWorkspace() {
|
||||
"right-split",
|
||||
UIEditorWorkspaceSplitAxis::Vertical,
|
||||
0.6f,
|
||||
BuildUIEditorWorkspacePanel("details-node", "details", "Details", true),
|
||||
BuildUIEditorWorkspacePanel("console-node", "console", "Console", true)));
|
||||
BuildUIEditorWorkspaceSingleTabStack("details-node", "details", "Details", true),
|
||||
BuildUIEditorWorkspaceSingleTabStack("console-node", "console", "Console", true)));
|
||||
workspace.activePanelId = "doc-b";
|
||||
return workspace;
|
||||
}
|
||||
@@ -99,6 +102,18 @@ UIPoint RectCenter(const UIRect& rect) {
|
||||
return UIPoint(rect.x + rect.width * 0.5f, rect.y + rect.height * 0.5f);
|
||||
}
|
||||
|
||||
const UIEditorDockHostTabStackLayout* FindTabStackByNodeId(
|
||||
const UIEditorDockHostLayout& layout,
|
||||
std::string_view nodeId) {
|
||||
for (const UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) {
|
||||
if (tabStack.nodeId == nodeId) {
|
||||
return &tabStack;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(UIEditorDockHostInteractionTest, SplitterDragUpdatesWorkspaceSplitRatio) {
|
||||
@@ -182,8 +197,9 @@ TEST(UIEditorDockHostInteractionTest, ClickingTabActivatesTargetPanel) {
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 800.0f, 600.0f),
|
||||
{});
|
||||
ASSERT_EQ(frame.layout.tabStacks.size(), 1u);
|
||||
const UIRect docARect = frame.layout.tabStacks.front().tabStripLayout.tabHeaderRects[0];
|
||||
const auto* documentStack = FindTabStackByNodeId(frame.layout, "document-tabs");
|
||||
ASSERT_NE(documentStack, nullptr);
|
||||
const UIRect docARect = documentStack->tabStripLayout.tabHeaderRects[0];
|
||||
|
||||
frame = UpdateUIEditorDockHostInteraction(
|
||||
state,
|
||||
@@ -209,8 +225,9 @@ TEST(UIEditorDockHostInteractionTest, ClickingTabActivatesTargetPanel) {
|
||||
EXPECT_TRUE(frame.result.consumed);
|
||||
EXPECT_TRUE(frame.result.commandExecuted);
|
||||
EXPECT_EQ(controller.GetWorkspace().activePanelId, "doc-a");
|
||||
ASSERT_EQ(frame.layout.tabStacks.size(), 1u);
|
||||
EXPECT_EQ(frame.layout.tabStacks.front().selectedPanelId, "doc-a");
|
||||
documentStack = FindTabStackByNodeId(frame.layout, "document-tabs");
|
||||
ASSERT_NE(documentStack, nullptr);
|
||||
EXPECT_EQ(documentStack->selectedPanelId, "doc-a");
|
||||
}
|
||||
|
||||
TEST(UIEditorDockHostInteractionTest, ClickingTabCloseClosesPanelThroughController) {
|
||||
@@ -223,8 +240,9 @@ TEST(UIEditorDockHostInteractionTest, ClickingTabCloseClosesPanelThroughControll
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 800.0f, 600.0f),
|
||||
{});
|
||||
ASSERT_EQ(frame.layout.tabStacks.size(), 1u);
|
||||
const UIRect closeRect = frame.layout.tabStacks.front().tabStripLayout.closeButtonRects[1];
|
||||
const auto* documentStack = FindTabStackByNodeId(frame.layout, "document-tabs");
|
||||
ASSERT_NE(documentStack, nullptr);
|
||||
const UIRect closeRect = documentStack->tabStripLayout.closeButtonRects[1];
|
||||
const UIPoint closeCenter = RectCenter(closeRect);
|
||||
|
||||
frame = UpdateUIEditorDockHostInteraction(
|
||||
@@ -243,6 +261,14 @@ TEST(UIEditorDockHostInteractionTest, ClickingTabCloseClosesPanelThroughControll
|
||||
EXPECT_TRUE(frame.result.consumed);
|
||||
EXPECT_FALSE(frame.result.commandExecuted);
|
||||
|
||||
frame = UpdateUIEditorDockHostInteraction(
|
||||
state,
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 800.0f, 600.0f),
|
||||
{ MakePointerDown(closeCenter.x, closeCenter.y) });
|
||||
EXPECT_TRUE(frame.result.consumed);
|
||||
EXPECT_FALSE(frame.result.commandExecuted);
|
||||
|
||||
frame = UpdateUIEditorDockHostInteraction(
|
||||
state,
|
||||
controller,
|
||||
@@ -268,8 +294,9 @@ TEST(UIEditorDockHostInteractionTest, FocusedTabStripHandlesKeyboardNavigationTh
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 800.0f, 600.0f),
|
||||
{});
|
||||
ASSERT_EQ(frame.layout.tabStacks.size(), 1u);
|
||||
const UIRect docARect = frame.layout.tabStacks.front().tabStripLayout.tabHeaderRects[0];
|
||||
const auto* documentStack = FindTabStackByNodeId(frame.layout, "document-tabs");
|
||||
ASSERT_NE(documentStack, nullptr);
|
||||
const UIRect docARect = documentStack->tabStripLayout.tabHeaderRects[0];
|
||||
const UIPoint docACenter = RectCenter(docARect);
|
||||
|
||||
frame = UpdateUIEditorDockHostInteraction(
|
||||
@@ -302,8 +329,9 @@ TEST(UIEditorDockHostInteractionTest, FocusedTabStripHandlesKeyboardNavigationTh
|
||||
EXPECT_TRUE(frame.result.consumed);
|
||||
EXPECT_TRUE(frame.result.commandExecuted);
|
||||
EXPECT_EQ(controller.GetWorkspace().activePanelId, "doc-b");
|
||||
ASSERT_EQ(frame.layout.tabStacks.size(), 1u);
|
||||
EXPECT_EQ(frame.layout.tabStacks.front().selectedPanelId, "doc-b");
|
||||
documentStack = FindTabStackByNodeId(frame.layout, "document-tabs");
|
||||
ASSERT_NE(documentStack, nullptr);
|
||||
EXPECT_EQ(documentStack->selectedPanelId, "doc-b");
|
||||
}
|
||||
|
||||
TEST(UIEditorDockHostInteractionTest, BatchedPointerMoveDownUpActivatesTabInSameUpdateCall) {
|
||||
@@ -316,9 +344,10 @@ TEST(UIEditorDockHostInteractionTest, BatchedPointerMoveDownUpActivatesTabInSame
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 800.0f, 600.0f),
|
||||
{});
|
||||
ASSERT_EQ(frame.layout.tabStacks.size(), 1u);
|
||||
const auto* documentStack = FindTabStackByNodeId(frame.layout, "document-tabs");
|
||||
ASSERT_NE(documentStack, nullptr);
|
||||
const UIPoint docACenter =
|
||||
RectCenter(frame.layout.tabStacks.front().tabStripLayout.tabHeaderRects[0]);
|
||||
RectCenter(documentStack->tabStripLayout.tabHeaderRects[0]);
|
||||
|
||||
frame = UpdateUIEditorDockHostInteraction(
|
||||
state,
|
||||
@@ -332,11 +361,12 @@ TEST(UIEditorDockHostInteractionTest, BatchedPointerMoveDownUpActivatesTabInSame
|
||||
EXPECT_TRUE(frame.result.consumed);
|
||||
EXPECT_TRUE(frame.result.commandExecuted);
|
||||
EXPECT_EQ(controller.GetWorkspace().activePanelId, "doc-a");
|
||||
ASSERT_EQ(frame.layout.tabStacks.size(), 1u);
|
||||
EXPECT_EQ(frame.layout.tabStacks.front().selectedPanelId, "doc-a");
|
||||
documentStack = FindTabStackByNodeId(frame.layout, "document-tabs");
|
||||
ASSERT_NE(documentStack, nullptr);
|
||||
EXPECT_EQ(documentStack->selectedPanelId, "doc-a");
|
||||
}
|
||||
|
||||
TEST(UIEditorDockHostInteractionTest, ClickingStandalonePanelBodyActivatesTargetPanel) {
|
||||
TEST(UIEditorDockHostInteractionTest, ClickingSingleTabStackBodyActivatesTargetPanel) {
|
||||
auto controller =
|
||||
BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace());
|
||||
UIEditorDockHostInteractionState state = {};
|
||||
@@ -346,8 +376,9 @@ TEST(UIEditorDockHostInteractionTest, ClickingStandalonePanelBodyActivatesTarget
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 800.0f, 600.0f),
|
||||
{});
|
||||
ASSERT_EQ(frame.layout.panels.size(), 2u);
|
||||
const UIRect detailsBodyRect = frame.layout.panels[0].frameLayout.bodyRect;
|
||||
const auto* detailsStack = FindTabStackByNodeId(frame.layout, "details-node");
|
||||
ASSERT_NE(detailsStack, nullptr);
|
||||
const UIRect detailsBodyRect = detailsStack->contentFrameLayout.bodyRect;
|
||||
const UIPoint detailsBodyCenter = RectCenter(detailsBodyRect);
|
||||
|
||||
frame = UpdateUIEditorDockHostInteraction(
|
||||
@@ -368,7 +399,7 @@ TEST(UIEditorDockHostInteractionTest, ClickingStandalonePanelBodyActivatesTarget
|
||||
EXPECT_EQ(controller.GetWorkspace().activePanelId, "details");
|
||||
}
|
||||
|
||||
TEST(UIEditorDockHostInteractionTest, ClickingStandalonePanelCloseClosesPanelThroughController) {
|
||||
TEST(UIEditorDockHostInteractionTest, ClickingSingleTabStackTabCloseClosesPanelThroughController) {
|
||||
auto controller =
|
||||
BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace());
|
||||
UIEditorDockHostInteractionState state = {};
|
||||
@@ -378,8 +409,9 @@ TEST(UIEditorDockHostInteractionTest, ClickingStandalonePanelCloseClosesPanelThr
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 800.0f, 600.0f),
|
||||
{});
|
||||
ASSERT_EQ(frame.layout.panels.size(), 2u);
|
||||
const UIRect closeRect = frame.layout.panels[1].frameLayout.closeButtonRect;
|
||||
const auto* consoleStack = FindTabStackByNodeId(frame.layout, "console-node");
|
||||
ASSERT_NE(consoleStack, nullptr);
|
||||
const UIRect closeRect = consoleStack->tabStripLayout.closeButtonRects[0];
|
||||
const UIPoint closeCenter = RectCenter(closeRect);
|
||||
|
||||
frame = UpdateUIEditorDockHostInteraction(
|
||||
@@ -387,7 +419,7 @@ TEST(UIEditorDockHostInteractionTest, ClickingStandalonePanelCloseClosesPanelThr
|
||||
controller,
|
||||
UIRect(0.0f, 0.0f, 800.0f, 600.0f),
|
||||
{ MakePointerMove(closeCenter.x, closeCenter.y) });
|
||||
EXPECT_EQ(frame.result.hitTarget.kind, UIEditorDockHostHitTargetKind::PanelCloseButton);
|
||||
EXPECT_EQ(frame.result.hitTarget.kind, UIEditorDockHostHitTargetKind::TabCloseButton);
|
||||
EXPECT_EQ(frame.result.hitTarget.panelId, "console");
|
||||
|
||||
frame = UpdateUIEditorDockHostInteraction(
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace {
|
||||
using XCEngine::UI::UIRect;
|
||||
using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceSession;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSingleTabStack;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack;
|
||||
using XCEngine::UI::Editor::CollectMountedUIEditorPanelContentHostPanelIds;
|
||||
@@ -50,7 +51,7 @@ UIEditorWorkspaceModel BuildWorkspace() {
|
||||
BuildUIEditorWorkspacePanel("console-node", "console", "Console", true)
|
||||
},
|
||||
1u),
|
||||
BuildUIEditorWorkspacePanel("inspector-node", "inspector", "Inspector"));
|
||||
BuildUIEditorWorkspaceSingleTabStack("inspector-node", "inspector", "Inspector"));
|
||||
workspace.activePanelId = "doc-b";
|
||||
return workspace;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceController;
|
||||
using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceSession;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceLayoutSnapshot;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSingleTabStack;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack;
|
||||
using XCEngine::UI::Editor::DeserializeUIEditorWorkspaceLayoutSnapshot;
|
||||
@@ -28,6 +29,7 @@ using XCEngine::UI::Editor::UIEditorWorkspaceCommandKind;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceCommandStatus;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceModel;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceSession;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceNodeKind;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceSplitAxis;
|
||||
|
||||
UIEditorPanelRegistry BuildPanelRegistry() {
|
||||
@@ -53,7 +55,7 @@ UIEditorWorkspaceModel BuildWorkspace() {
|
||||
BuildUIEditorWorkspacePanel("doc-b-node", "doc-b", "Document B", true)
|
||||
},
|
||||
0u),
|
||||
BuildUIEditorWorkspacePanel("details-node", "details", "Details", true));
|
||||
BuildUIEditorWorkspaceSingleTabStack("details-node", "details", "Details", true));
|
||||
workspace.activePanelId = "doc-a";
|
||||
return workspace;
|
||||
}
|
||||
@@ -128,6 +130,35 @@ TEST(UIEditorWorkspaceLayoutPersistenceTest, DeserializeRejectsMissingSessionRec
|
||||
EXPECT_EQ(loadResult.code, UIEditorWorkspaceLayoutLoadCode::InvalidWorkspaceSession);
|
||||
}
|
||||
|
||||
TEST(UIEditorWorkspaceLayoutPersistenceTest, DeserializeUpgradesLegacyStandalonePanelLeaves) {
|
||||
const UIEditorPanelRegistry registry = BuildPanelRegistry();
|
||||
const std::string legacySerialized =
|
||||
"XCUI_EDITOR_WORKSPACE_LAYOUT 1\n"
|
||||
"active \"doc-a\"\n"
|
||||
"node_split \"root-split\" \"horizontal\" 0.66\n"
|
||||
"node_tabstack \"document-tabs\" 0 2\n"
|
||||
"node_panel \"doc-a-node\" \"doc-a\" \"Document A\" 1\n"
|
||||
"node_panel \"doc-b-node\" \"doc-b\" \"Document B\" 1\n"
|
||||
"node_panel \"details-node\" \"details\" \"Details\" 1\n"
|
||||
"session \"doc-a\" 1 1\n"
|
||||
"session \"doc-b\" 1 1\n"
|
||||
"session \"details\" 1 1\n";
|
||||
|
||||
const auto loadResult =
|
||||
DeserializeUIEditorWorkspaceLayoutSnapshot(registry, legacySerialized);
|
||||
|
||||
ASSERT_TRUE(loadResult.IsValid()) << loadResult.message;
|
||||
ASSERT_EQ(loadResult.snapshot.workspace.root.kind, UIEditorWorkspaceNodeKind::Split);
|
||||
ASSERT_EQ(loadResult.snapshot.workspace.root.children.size(), 2u);
|
||||
EXPECT_EQ(
|
||||
loadResult.snapshot.workspace.root.children[1].kind,
|
||||
UIEditorWorkspaceNodeKind::TabStack);
|
||||
ASSERT_EQ(loadResult.snapshot.workspace.root.children[1].children.size(), 1u);
|
||||
EXPECT_EQ(
|
||||
loadResult.snapshot.workspace.root.children[1].children[0].panel.panelId,
|
||||
"details");
|
||||
}
|
||||
|
||||
TEST(UIEditorWorkspaceLayoutPersistenceTest, RestoreSerializedLayoutRestoresSavedStateAfterFurtherMutations) {
|
||||
const UIEditorPanelRegistry registry = BuildPanelRegistry();
|
||||
UIEditorWorkspaceController controller =
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
namespace {
|
||||
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSingleTabStack;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack;
|
||||
using XCEngine::UI::Editor::CanonicalizeUIEditorWorkspaceModel;
|
||||
using XCEngine::UI::Editor::CollectUIEditorWorkspaceVisiblePanels;
|
||||
using XCEngine::UI::Editor::ContainsUIEditorWorkspacePanel;
|
||||
using XCEngine::UI::Editor::FindUIEditorWorkspaceActivePanel;
|
||||
@@ -73,7 +75,7 @@ TEST(UIEditorWorkspaceModelTest, VisiblePanelsOnlyIncludeSelectedTabsAcrossSplit
|
||||
"root-split",
|
||||
UIEditorWorkspaceSplitAxis::Horizontal,
|
||||
0.68f,
|
||||
BuildUIEditorWorkspacePanel("left-panel-node", "left-panel", "Left Panel", true),
|
||||
BuildUIEditorWorkspaceSingleTabStack("left-panel-node", "left-panel", "Left Panel", true),
|
||||
BuildUIEditorWorkspaceSplit(
|
||||
"right-split",
|
||||
UIEditorWorkspaceSplitAxis::Vertical,
|
||||
@@ -85,7 +87,11 @@ TEST(UIEditorWorkspaceModelTest, VisiblePanelsOnlyIncludeSelectedTabsAcrossSplit
|
||||
BuildUIEditorWorkspacePanel("doc-b-node", "doc-b", "Document B", true)
|
||||
},
|
||||
1u),
|
||||
BuildUIEditorWorkspacePanel("bottom-panel-node", "bottom-panel", "Bottom Panel", true)));
|
||||
BuildUIEditorWorkspaceSingleTabStack(
|
||||
"bottom-panel-node",
|
||||
"bottom-panel",
|
||||
"Bottom Panel",
|
||||
true)));
|
||||
workspace.activePanelId = "doc-b";
|
||||
|
||||
const auto validation = ValidateUIEditorWorkspace(workspace);
|
||||
@@ -116,7 +122,7 @@ TEST(UIEditorWorkspaceModelTest, ActivatingHiddenPanelSelectsContainingTabAndUpd
|
||||
BuildUIEditorWorkspacePanel("doc-c-node", "doc-c", "Document C", true)
|
||||
},
|
||||
0u),
|
||||
BuildUIEditorWorkspacePanel("details-node", "details", "Details", true));
|
||||
BuildUIEditorWorkspaceSingleTabStack("details-node", "details", "Details", true));
|
||||
|
||||
ASSERT_TRUE(ContainsUIEditorWorkspacePanel(workspace, "doc-b"));
|
||||
ASSERT_TRUE(TryActivateUIEditorWorkspacePanel(workspace, "doc-b"));
|
||||
@@ -150,8 +156,8 @@ TEST(UIEditorWorkspaceModelTest, SplitRatioMutationTargetsSplitNodeAndRejectsInv
|
||||
"root-split",
|
||||
UIEditorWorkspaceSplitAxis::Horizontal,
|
||||
0.62f,
|
||||
BuildUIEditorWorkspacePanel("left-node", "left", "Left", true),
|
||||
BuildUIEditorWorkspacePanel("right-node", "right", "Right", true));
|
||||
BuildUIEditorWorkspaceSingleTabStack("left-node", "left", "Left", true),
|
||||
BuildUIEditorWorkspaceSingleTabStack("right-node", "right", "Right", true));
|
||||
|
||||
ASSERT_TRUE(TrySetUIEditorWorkspaceSplitRatio(workspace, "root-split", 0.35f));
|
||||
const auto* splitNode = FindUIEditorWorkspaceNode(workspace, "root-split");
|
||||
@@ -163,3 +169,29 @@ TEST(UIEditorWorkspaceModelTest, SplitRatioMutationTargetsSplitNodeAndRejectsInv
|
||||
EXPECT_FALSE(TrySetUIEditorWorkspaceSplitRatio(workspace, "missing", 0.5f));
|
||||
EXPECT_FALSE(TrySetUIEditorWorkspaceSplitRatio(workspace, "root-split", 1.0f));
|
||||
}
|
||||
|
||||
TEST(UIEditorWorkspaceModelTest, CanonicalizeWrapsStandalonePanelsIntoSingleTabStacks) {
|
||||
UIEditorWorkspaceModel workspace = {};
|
||||
workspace.root = BuildUIEditorWorkspaceSplit(
|
||||
"root-split",
|
||||
UIEditorWorkspaceSplitAxis::Horizontal,
|
||||
0.5f,
|
||||
BuildUIEditorWorkspacePanel("left-node", "left", "Left", true),
|
||||
BuildUIEditorWorkspacePanel("right-node", "right", "Right", true));
|
||||
|
||||
const UIEditorWorkspaceModel canonicalWorkspace =
|
||||
CanonicalizeUIEditorWorkspaceModel(workspace);
|
||||
|
||||
ASSERT_EQ(canonicalWorkspace.root.kind, UIEditorWorkspaceNodeKind::Split);
|
||||
ASSERT_EQ(canonicalWorkspace.root.children.size(), 2u);
|
||||
EXPECT_EQ(canonicalWorkspace.root.children[0].kind, UIEditorWorkspaceNodeKind::TabStack);
|
||||
EXPECT_EQ(canonicalWorkspace.root.children[0].nodeId, "left-node");
|
||||
ASSERT_EQ(canonicalWorkspace.root.children[0].children.size(), 1u);
|
||||
EXPECT_EQ(
|
||||
canonicalWorkspace.root.children[0].children[0].kind,
|
||||
UIEditorWorkspaceNodeKind::Panel);
|
||||
EXPECT_EQ(
|
||||
canonicalWorkspace.root.children[0].children[0].panel.panelId,
|
||||
"left");
|
||||
EXPECT_EQ(canonicalWorkspace.root.children[1].kind, UIEditorWorkspaceNodeKind::TabStack);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user