Add editor panel content host contract

This commit is contained in:
2026-04-07 14:03:52 +08:00
parent b2ab516228
commit 1f140c4bab
14 changed files with 1292 additions and 37 deletions

View File

@@ -9,6 +9,7 @@ set(EDITOR_UI_UNIT_TEST_SOURCES
test_ui_editor_menu_session.cpp
test_ui_editor_menu_bar.cpp
test_ui_editor_menu_popup.cpp
test_ui_editor_panel_content_host.cpp
test_ui_editor_panel_host_lifecycle.cpp
test_ui_editor_panel_registry.cpp
test_ui_editor_shell_compose.cpp

View File

@@ -0,0 +1,181 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorPanelContentHost.h>
#include <XCEditor/Core/UIEditorWorkspaceSession.h>
#include <XCEditor/Widgets/UIEditorDockHost.h>
namespace {
using XCEngine::UI::UIRect;
using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceSession;
using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel;
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit;
using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack;
using XCEngine::UI::Editor::CollectMountedUIEditorPanelContentHostPanelIds;
using XCEngine::UI::Editor::FindUIEditorPanelContentHostMountRequest;
using XCEngine::UI::Editor::FindUIEditorPanelContentHostPanelState;
using XCEngine::UI::Editor::GetUIEditorPanelContentHostEventKindName;
using XCEngine::UI::Editor::ResolveUIEditorPanelContentHostRequest;
using XCEngine::UI::Editor::UIEditorPanelContentHostBinding;
using XCEngine::UI::Editor::UIEditorPanelContentHostState;
using XCEngine::UI::Editor::UIEditorPanelPresentationKind;
using XCEngine::UI::Editor::UIEditorPanelRegistry;
using XCEngine::UI::Editor::UIEditorWorkspaceModel;
using XCEngine::UI::Editor::UIEditorWorkspaceSplitAxis;
using XCEngine::UI::Editor::UpdateUIEditorPanelContentHost;
using XCEngine::UI::Editor::Widgets::BuildUIEditorDockHostLayout;
UIEditorPanelRegistry BuildPanelRegistry() {
UIEditorPanelRegistry registry = {};
registry.panels = {
{ "doc-a", "Document A", UIEditorPanelPresentationKind::HostedContent, false, true, true },
{ "doc-b", "Document B", UIEditorPanelPresentationKind::HostedContent, false, true, true },
{ "console", "Console", UIEditorPanelPresentationKind::Placeholder, true, true, true },
{ "inspector", "Inspector", UIEditorPanelPresentationKind::HostedContent, false, true, true }
};
return registry;
}
UIEditorWorkspaceModel BuildWorkspace() {
UIEditorWorkspaceModel workspace = {};
workspace.root = BuildUIEditorWorkspaceSplit(
"root",
UIEditorWorkspaceSplitAxis::Horizontal,
0.68f,
BuildUIEditorWorkspaceTabStack(
"documents",
{
BuildUIEditorWorkspacePanel("doc-a-node", "doc-a", "Document A"),
BuildUIEditorWorkspacePanel("doc-b-node", "doc-b", "Document B"),
BuildUIEditorWorkspacePanel("console-node", "console", "Console", true)
},
1u),
BuildUIEditorWorkspacePanel("inspector-node", "inspector", "Inspector"));
workspace.activePanelId = "doc-b";
return workspace;
}
std::vector<UIEditorPanelContentHostBinding> BuildBindings() {
return {
{ "doc-a", UIEditorPanelPresentationKind::HostedContent },
{ "doc-b", UIEditorPanelPresentationKind::HostedContent },
{ "console", UIEditorPanelPresentationKind::Placeholder },
{ "inspector", UIEditorPanelPresentationKind::HostedContent }
};
}
std::vector<std::string> FormatEvents(
const std::vector<XCEngine::UI::Editor::UIEditorPanelContentHostEvent>& events) {
std::vector<std::string> formatted = {};
formatted.reserve(events.size());
for (const auto& event : events) {
formatted.push_back(
std::string(GetUIEditorPanelContentHostEventKindName(event.kind)) + ":" + event.panelId);
}
return formatted;
}
} // namespace
TEST(UIEditorPanelContentHostTest, ResolveRequestMountsSelectedHostedTabAndStandaloneHostedPanel) {
const UIEditorPanelRegistry registry = BuildPanelRegistry();
const UIEditorWorkspaceModel workspace = BuildWorkspace();
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
const auto layout = BuildUIEditorDockHostLayout(
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
registry,
workspace,
session);
const auto request = ResolveUIEditorPanelContentHostRequest(
layout,
registry,
BuildBindings());
ASSERT_EQ(request.mountRequests.size(), 2u);
EXPECT_NE(FindUIEditorPanelContentHostMountRequest(request, "doc-b"), nullptr);
EXPECT_NE(FindUIEditorPanelContentHostMountRequest(request, "inspector"), nullptr);
EXPECT_EQ(FindUIEditorPanelContentHostMountRequest(request, "doc-a"), nullptr);
EXPECT_EQ(FindUIEditorPanelContentHostMountRequest(request, "console"), nullptr);
}
TEST(UIEditorPanelContentHostTest, UpdateEmitsMountedAndUnmountedWhenHostedTabSelectionChanges) {
const UIEditorPanelRegistry registry = BuildPanelRegistry();
UIEditorWorkspaceModel workspace = BuildWorkspace();
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
UIEditorPanelContentHostState state = {};
auto layout = BuildUIEditorDockHostLayout(
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
registry,
workspace,
session);
auto request = ResolveUIEditorPanelContentHostRequest(layout, registry, BuildBindings());
const auto initialFrame = UpdateUIEditorPanelContentHost(
state,
request,
registry,
BuildBindings());
EXPECT_EQ(
FormatEvents(initialFrame.events),
std::vector<std::string>({ "Mounted:doc-b", "Mounted:inspector" }));
EXPECT_EQ(
CollectMountedUIEditorPanelContentHostPanelIds(initialFrame),
std::vector<std::string>({ "doc-b", "inspector" }));
workspace.root.children[0].selectedTabIndex = 0u;
workspace.activePanelId = "doc-a";
layout = BuildUIEditorDockHostLayout(
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
registry,
workspace,
session);
request = ResolveUIEditorPanelContentHostRequest(layout, registry, BuildBindings());
const auto switchedFrame = UpdateUIEditorPanelContentHost(
state,
request,
registry,
BuildBindings());
EXPECT_EQ(
FormatEvents(switchedFrame.events),
std::vector<std::string>({ "Mounted:doc-a", "Unmounted:doc-b" }));
const auto* docAState = FindUIEditorPanelContentHostPanelState(switchedFrame, "doc-a");
const auto* docBState = FindUIEditorPanelContentHostPanelState(switchedFrame, "doc-b");
ASSERT_NE(docAState, nullptr);
ASSERT_NE(docBState, nullptr);
EXPECT_TRUE(docAState->mounted);
EXPECT_FALSE(docBState->mounted);
}
TEST(UIEditorPanelContentHostTest, UpdateEmitsBoundsChangedForMountedHostedPanelsWhenLayoutChanges) {
const UIEditorPanelRegistry registry = BuildPanelRegistry();
const UIEditorWorkspaceModel workspace = BuildWorkspace();
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
UIEditorPanelContentHostState state = {};
auto layout = BuildUIEditorDockHostLayout(
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
registry,
workspace,
session);
auto request = ResolveUIEditorPanelContentHostRequest(layout, registry, BuildBindings());
UpdateUIEditorPanelContentHost(state, request, registry, BuildBindings());
layout = BuildUIEditorDockHostLayout(
UIRect(0.0f, 0.0f, 1440.0f, 800.0f),
registry,
workspace,
session);
request = ResolveUIEditorPanelContentHostRequest(layout, registry, BuildBindings());
const auto resizedFrame = UpdateUIEditorPanelContentHost(
state,
request,
registry,
BuildBindings());
EXPECT_EQ(
FormatEvents(resizedFrame.events),
std::vector<std::string>({ "BoundsChanged:doc-b", "BoundsChanged:inspector" }));
}

View File

@@ -10,6 +10,7 @@ using XCEngine::UI::UIInputEventType;
using XCEngine::UI::UIPoint;
using XCEngine::UI::UIPointerButton;
using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceSession;
using XCEngine::UI::Editor::CollectUIEditorWorkspaceComposeExternalBodyPanelIds;
using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel;
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit;
using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack;
@@ -88,6 +89,42 @@ XCEngine::UI::Editor::UIEditorViewportShellModel BuildViewportShellModel(std::st
return model;
}
UIEditorPanelRegistry BuildRegistryWithHostedContentPanels() {
UIEditorPanelRegistry registry = {};
registry.panels = {
{ "doc-a", "Document A", UIEditorPanelPresentationKind::HostedContent, false, true, true },
{ "doc-b", "Document B", UIEditorPanelPresentationKind::HostedContent, false, true, true },
{ "inspector", "Inspector", UIEditorPanelPresentationKind::HostedContent, false, true, true }
};
return registry;
}
UIEditorWorkspaceModel BuildHostedContentWorkspace() {
UIEditorWorkspaceModel workspace = {};
workspace.root = BuildUIEditorWorkspaceSplit(
"root",
UIEditorWorkspaceSplitAxis::Horizontal,
0.68f,
BuildUIEditorWorkspaceTabStack(
"documents",
{
BuildUIEditorWorkspacePanel("doc-a-node", "doc-a", "Document A"),
BuildUIEditorWorkspacePanel("doc-b-node", "doc-b", "Document B")
},
1u),
BuildUIEditorWorkspacePanel("inspector-node", "inspector", "Inspector"));
workspace.activePanelId = "doc-b";
return workspace;
}
UIEditorWorkspacePanelPresentationModel BuildHostedContentPresentationModel(
std::string panelId) {
UIEditorWorkspacePanelPresentationModel model = {};
model.panelId = std::move(panelId);
model.kind = UIEditorPanelPresentationKind::HostedContent;
return model;
}
UIEditorWorkspacePanelPresentationModel BuildViewportPresentationModel(
std::string panelId,
std::string title) {
@@ -267,3 +304,29 @@ TEST(UIEditorWorkspaceComposeTest, HiddenViewportTabResetsCapturedAndFocusedStat
EXPECT_FALSE(viewportBStateAfterHide->viewportShellState.inputBridgeState.focused);
EXPECT_FALSE(viewportBStateAfterHide->viewportShellState.inputBridgeState.captured);
}
TEST(UIEditorWorkspaceComposeTest, HostedContentPanelsFlowThroughContentHostAndSuppressDockPlaceholder) {
const auto registry = BuildRegistryWithHostedContentPanels();
const UIEditorWorkspaceModel workspace = BuildHostedContentWorkspace();
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
const std::vector<UIEditorWorkspacePanelPresentationModel> presentationModels = {
BuildHostedContentPresentationModel("doc-a"),
BuildHostedContentPresentationModel("doc-b"),
BuildHostedContentPresentationModel("inspector")
};
UIEditorWorkspaceComposeState state = {};
const UIEditorWorkspaceComposeFrame frame = UpdateUIEditorWorkspaceCompose(
state,
UIRect(0.0f, 0.0f, 1200.0f, 760.0f),
registry,
workspace,
session,
presentationModels,
{});
EXPECT_TRUE(frame.viewportFrames.empty());
EXPECT_EQ(
CollectUIEditorWorkspaceComposeExternalBodyPanelIds(frame),
std::vector<std::string>({ "doc-b", "inspector" }));
}