Unify dock leaves around single-tab stacks

This commit is contained in:
2026-04-10 21:50:31 +08:00
parent b187c8970b
commit 977a4cf2a4
14 changed files with 258 additions and 195 deletions

View File

@@ -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 docktab 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);

View File

@@ -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"));
}

View File

@@ -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(

View File

@@ -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;
}

View File

@@ -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 =

View File

@@ -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);
}