Build XCEditor editor shell compose foundation

This commit is contained in:
2026-04-07 06:30:34 +08:00
parent 3c0dedcc5f
commit 945420f3bd
11 changed files with 1109 additions and 0 deletions

View File

@@ -9,6 +9,7 @@ set(EDITOR_UI_UNIT_TEST_SOURCES
test_ui_editor_menu_bar.cpp
test_ui_editor_menu_popup.cpp
test_ui_editor_panel_registry.cpp
test_ui_editor_shell_compose.cpp
test_ui_editor_collection_primitives.cpp
test_ui_editor_dock_host.cpp
test_ui_editor_panel_chrome.cpp

View File

@@ -0,0 +1,164 @@
#include <gtest/gtest.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEditor/Core/UIEditorShellCompose.h>
#include <XCEditor/Core/UIEditorWorkspaceModel.h>
#include <XCEditor/Core/UIEditorWorkspaceSession.h>
namespace {
using XCEngine::UI::UIDrawCommandType;
using XCEngine::UI::UIDrawList;
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::BuildUIEditorShellComposeLayout;
using XCEngine::UI::Editor::ResolveUIEditorShellComposeRequest;
using XCEngine::UI::Editor::UIEditorPanelPresentationKind;
using XCEngine::UI::Editor::UIEditorPanelRegistry;
using XCEngine::UI::Editor::UIEditorShellComposeFrame;
using XCEngine::UI::Editor::UIEditorShellComposeModel;
using XCEngine::UI::Editor::UIEditorShellComposeState;
using XCEngine::UI::Editor::UIEditorWorkspaceModel;
using XCEngine::UI::Editor::UIEditorWorkspacePanelPresentationModel;
using XCEngine::UI::Editor::UIEditorWorkspaceSplitAxis;
using XCEngine::UI::Editor::UpdateUIEditorShellCompose;
using XCEngine::UI::Editor::AppendUIEditorShellCompose;
using XCEngine::UI::Editor::Widgets::UIEditorMenuBarItem;
using XCEngine::UI::Editor::Widgets::UIEditorStatusBarSegment;
using XCEngine::UI::Editor::Widgets::UIEditorStatusBarSlot;
UIEditorPanelRegistry BuildPanelRegistry() {
UIEditorPanelRegistry registry = {};
registry.panels = {
{ "hierarchy", "Hierarchy", UIEditorPanelPresentationKind::Placeholder, true, true, false },
{ "scene", "Scene", UIEditorPanelPresentationKind::ViewportShell, false, true, false },
{ "document", "Document", UIEditorPanelPresentationKind::Placeholder, true, true, true },
{ "inspector", "Inspector", UIEditorPanelPresentationKind::Placeholder, true, true, true }
};
return registry;
}
UIEditorWorkspaceModel BuildWorkspace() {
UIEditorWorkspaceModel workspace = {};
workspace.root = BuildUIEditorWorkspaceSplit(
"root",
UIEditorWorkspaceSplitAxis::Horizontal,
0.24f,
BuildUIEditorWorkspacePanel("hierarchy-node", "hierarchy", "Hierarchy", true),
BuildUIEditorWorkspaceSplit(
"main",
UIEditorWorkspaceSplitAxis::Horizontal,
0.72f,
BuildUIEditorWorkspaceTabStack(
"center-tabs",
{
BuildUIEditorWorkspacePanel("scene-node", "scene", "Scene"),
BuildUIEditorWorkspacePanel("document-node", "document", "Document", true)
},
0u),
BuildUIEditorWorkspacePanel("inspector-node", "inspector", "Inspector", true)));
workspace.activePanelId = "scene";
return workspace;
}
UIEditorShellComposeModel BuildShellModel() {
UIEditorShellComposeModel model = {};
model.menuBarItems = {
UIEditorMenuBarItem{ "file", "File", true, 0.0f },
UIEditorMenuBarItem{ "window", "Window", true, 0.0f },
UIEditorMenuBarItem{ "layout", "Layout", true, 0.0f }
};
model.statusSegments = {
UIEditorStatusBarSegment{ "scene", "Scene", UIEditorStatusBarSlot::Leading, {}, true, true, 72.0f },
UIEditorStatusBarSegment{ "frame", "16.7 ms", UIEditorStatusBarSlot::Trailing, {}, true, false, 72.0f }
};
UIEditorWorkspacePanelPresentationModel presentation = {};
presentation.panelId = "scene";
presentation.kind = UIEditorPanelPresentationKind::ViewportShell;
presentation.viewportShellModel.spec.chrome.title = "Scene";
presentation.viewportShellModel.spec.chrome.showTopBar = true;
presentation.viewportShellModel.spec.chrome.showBottomBar = true;
presentation.viewportShellModel.frame.hasTexture = false;
presentation.viewportShellModel.frame.statusText = "Viewport frame";
model.workspacePresentations = { presentation };
return model;
}
bool ContainsTextCommand(const UIDrawList& drawList, std::string_view text) {
for (const auto& command : drawList.GetCommands()) {
if (command.type == UIDrawCommandType::Text && command.text == text) {
return true;
}
}
return false;
}
} // namespace
TEST(UIEditorShellComposeTest, LayoutAllocatesMenuWorkspaceAndStatusBandsWithoutOverlap) {
const UIEditorShellComposeModel model = BuildShellModel();
const auto layout = BuildUIEditorShellComposeLayout(
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
model.menuBarItems,
model.statusSegments);
EXPECT_GT(layout.menuBarRect.height, 0.0f);
EXPECT_GT(layout.workspaceRect.height, 0.0f);
EXPECT_GT(layout.statusBarRect.height, 0.0f);
EXPECT_LE(layout.menuBarRect.y + layout.menuBarRect.height, layout.workspaceRect.y);
EXPECT_LE(layout.workspaceRect.y + layout.workspaceRect.height, layout.statusBarRect.y);
EXPECT_FLOAT_EQ(layout.contentRect.x, 12.0f);
EXPECT_FLOAT_EQ(layout.contentRect.y, 12.0f);
}
TEST(UIEditorShellComposeTest, ResolveRequestRoutesWorkspaceComposeThroughWorkspaceBand) {
const auto registry = BuildPanelRegistry();
const auto workspace = BuildWorkspace();
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
const UIEditorShellComposeModel model = BuildShellModel();
const auto request = ResolveUIEditorShellComposeRequest(
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
registry,
workspace,
session,
model);
EXPECT_FLOAT_EQ(request.workspaceRequest.dockHostLayout.bounds.x, request.layout.workspaceRect.x);
EXPECT_FLOAT_EQ(request.workspaceRequest.dockHostLayout.bounds.y, request.layout.workspaceRect.y);
EXPECT_FLOAT_EQ(request.workspaceRequest.dockHostLayout.bounds.width, request.layout.workspaceRect.width);
EXPECT_FLOAT_EQ(request.workspaceRequest.dockHostLayout.bounds.height, request.layout.workspaceRect.height);
ASSERT_EQ(request.workspaceRequest.viewportRequests.size(), 1u);
EXPECT_EQ(request.workspaceRequest.viewportRequests.front().panelId, "scene");
}
TEST(UIEditorShellComposeTest, AppendComposeEmitsMenuStatusAndWorkspaceCommandsTogether) {
const auto registry = BuildPanelRegistry();
const auto workspace = BuildWorkspace();
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
const UIEditorShellComposeModel model = BuildShellModel();
UIEditorShellComposeState state = {};
const UIEditorShellComposeFrame frame = UpdateUIEditorShellCompose(
state,
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
registry,
workspace,
session,
model,
{});
UIDrawList drawList("EditorShellCompose");
AppendUIEditorShellCompose(drawList, frame, model, state);
EXPECT_GT(drawList.GetCommandCount(), 20u);
EXPECT_TRUE(ContainsTextCommand(drawList, "File"));
EXPECT_TRUE(ContainsTextCommand(drawList, "Scene"));
EXPECT_TRUE(ContainsTextCommand(drawList, "16.7 ms"));
}