Build XCEditor workspace viewport compose foundation
This commit is contained in:
@@ -18,6 +18,7 @@ set(EDITOR_UI_UNIT_TEST_SOURCES
|
||||
test_ui_editor_viewport_input_bridge.cpp
|
||||
test_ui_editor_viewport_shell.cpp
|
||||
test_ui_editor_viewport_slot.cpp
|
||||
test_ui_editor_workspace_compose.cpp
|
||||
test_ui_editor_shortcut_manager.cpp
|
||||
test_ui_editor_workspace_controller.cpp
|
||||
test_ui_editor_workspace_layout_persistence.cpp
|
||||
|
||||
@@ -30,6 +30,7 @@ using XCEngine::UI::Editor::Widgets::UIEditorDockHostHitTarget;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorDockHostHitTargetKind;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorDockHostLayout;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorDockHostMetrics;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorDockHostForegroundOptions;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorDockHostState;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorTabStripInvalidIndex;
|
||||
|
||||
@@ -67,6 +68,16 @@ UIEditorWorkspaceModel BuildWorkspace() {
|
||||
return workspace;
|
||||
}
|
||||
|
||||
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(UIEditorDockHostTest, LayoutComposesSplitTabStackAndStandalonePanelsFromWorkspaceTree) {
|
||||
@@ -196,14 +207,44 @@ TEST(UIEditorDockHostTest, BackgroundAndForegroundEmitStableCompositeCommands) {
|
||||
UIDrawList foreground("DockHostForeground");
|
||||
AppendUIEditorDockHostForeground(foreground, layout);
|
||||
EXPECT_GT(foreground.GetCommandCount(), 10u);
|
||||
|
||||
bool foundPlaceholderText = false;
|
||||
for (const auto& command : foreground.GetCommands()) {
|
||||
if (command.type == UIDrawCommandType::Text &&
|
||||
command.text == "DockHost tab content placeholder") {
|
||||
foundPlaceholderText = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(foundPlaceholderText);
|
||||
}
|
||||
|
||||
TEST(UIEditorDockHostTest, ForegroundByDefaultStillDrawsPlaceholderText) {
|
||||
const UIEditorPanelRegistry registry = BuildPanelRegistry();
|
||||
const UIEditorWorkspaceModel workspace = BuildWorkspace();
|
||||
const UIEditorWorkspaceSession session =
|
||||
BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
|
||||
const UIEditorDockHostLayout layout = BuildUIEditorDockHostLayout(
|
||||
UIRect(0.0f, 0.0f, 800.0f, 600.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session);
|
||||
|
||||
UIDrawList foreground("DockHostForegroundDefault");
|
||||
AppendUIEditorDockHostForeground(foreground, layout);
|
||||
|
||||
EXPECT_TRUE(ContainsTextCommand(foreground, "DockHost tab content placeholder"));
|
||||
EXPECT_TRUE(ContainsTextCommand(foreground, "DockHost standalone panel"));
|
||||
}
|
||||
|
||||
TEST(UIEditorDockHostTest, ForegroundSkipsPlaceholderForExternalBodyPanelId) {
|
||||
const UIEditorPanelRegistry registry = BuildPanelRegistry();
|
||||
const UIEditorWorkspaceModel workspace = BuildWorkspace();
|
||||
const UIEditorWorkspaceSession session =
|
||||
BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
|
||||
const UIEditorDockHostLayout layout = BuildUIEditorDockHostLayout(
|
||||
UIRect(0.0f, 0.0f, 800.0f, 600.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session);
|
||||
|
||||
UIDrawList foreground("DockHostForegroundExternalBody");
|
||||
UIEditorDockHostForegroundOptions options = {};
|
||||
options.externalBodyPanelIds = { "doc-b" };
|
||||
AppendUIEditorDockHostForeground(foreground, layout, options);
|
||||
|
||||
EXPECT_FALSE(ContainsTextCommand(foreground, "DockHost tab content placeholder"));
|
||||
EXPECT_TRUE(ContainsTextCommand(foreground, "DockHost standalone panel"));
|
||||
}
|
||||
|
||||
269
tests/UI/Editor/unit/test_ui_editor_workspace_compose.cpp
Normal file
269
tests/UI/Editor/unit/test_ui_editor_workspace_compose.cpp
Normal file
@@ -0,0 +1,269 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Core/UIEditorWorkspaceCompose.h>
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::UI::UIRect;
|
||||
using XCEngine::UI::UIInputEvent;
|
||||
using XCEngine::UI::UIInputEventType;
|
||||
using XCEngine::UI::UIPoint;
|
||||
using XCEngine::UI::UIPointerButton;
|
||||
using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceSession;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit;
|
||||
using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack;
|
||||
using XCEngine::UI::Editor::FindUIEditorWorkspacePanelPresentationState;
|
||||
using XCEngine::UI::Editor::FindUIEditorWorkspaceViewportPresentationFrame;
|
||||
using XCEngine::UI::Editor::FindUIEditorWorkspaceViewportPresentationRequest;
|
||||
using XCEngine::UI::Editor::ResolveUIEditorViewportShellRequest;
|
||||
using XCEngine::UI::Editor::ResolveUIEditorWorkspaceComposeRequest;
|
||||
using XCEngine::UI::Editor::UIEditorPanelPresentationKind;
|
||||
using XCEngine::UI::Editor::UIEditorPanelRegistry;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceComposeFrame;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceComposeState;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceModel;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspacePanelPresentationModel;
|
||||
using XCEngine::UI::Editor::UIEditorWorkspaceSplitAxis;
|
||||
using XCEngine::UI::Editor::UpdateUIEditorWorkspaceCompose;
|
||||
|
||||
UIEditorPanelRegistry BuildRegistryWithViewportPanels() {
|
||||
UIEditorPanelRegistry registry = {};
|
||||
registry.panels = {
|
||||
{ "viewport", "Viewport", UIEditorPanelPresentationKind::ViewportShell, false, true, true },
|
||||
{ "doc", "Document", UIEditorPanelPresentationKind::Placeholder, true, true, true },
|
||||
{ "details", "Details", UIEditorPanelPresentationKind::Placeholder, true, true, true }
|
||||
};
|
||||
return registry;
|
||||
}
|
||||
|
||||
UIEditorPanelRegistry BuildRegistryWithTwoViewportTabs() {
|
||||
UIEditorPanelRegistry registry = {};
|
||||
registry.panels = {
|
||||
{ "viewport-a", "Viewport A", UIEditorPanelPresentationKind::ViewportShell, false, true, true },
|
||||
{ "viewport-b", "Viewport B", UIEditorPanelPresentationKind::ViewportShell, false, true, true }
|
||||
};
|
||||
return registry;
|
||||
}
|
||||
|
||||
UIEditorWorkspaceModel BuildViewportWorkspace() {
|
||||
UIEditorWorkspaceModel workspace = {};
|
||||
workspace.root = BuildUIEditorWorkspaceSplit(
|
||||
"root",
|
||||
UIEditorWorkspaceSplitAxis::Horizontal,
|
||||
0.7f,
|
||||
BuildUIEditorWorkspaceTabStack(
|
||||
"tab-stack",
|
||||
{
|
||||
BuildUIEditorWorkspacePanel("viewport-node", "viewport", "Viewport"),
|
||||
BuildUIEditorWorkspacePanel("doc-node", "doc", "Document", true)
|
||||
},
|
||||
0u),
|
||||
BuildUIEditorWorkspacePanel("details-node", "details", "Details", true));
|
||||
workspace.activePanelId = "viewport";
|
||||
return workspace;
|
||||
}
|
||||
|
||||
UIEditorWorkspaceModel BuildTwoViewportTabWorkspace() {
|
||||
UIEditorWorkspaceModel workspace = {};
|
||||
workspace.root = BuildUIEditorWorkspaceTabStack(
|
||||
"tab-stack",
|
||||
{
|
||||
BuildUIEditorWorkspacePanel("viewport-a-node", "viewport-a", "Viewport A"),
|
||||
BuildUIEditorWorkspacePanel("viewport-b-node", "viewport-b", "Viewport B")
|
||||
},
|
||||
1u);
|
||||
workspace.activePanelId = "viewport-b";
|
||||
return workspace;
|
||||
}
|
||||
|
||||
XCEngine::UI::Editor::UIEditorViewportShellModel BuildViewportShellModel(std::string title) {
|
||||
XCEngine::UI::Editor::UIEditorViewportShellModel model = {};
|
||||
model.spec.chrome.title = std::move(title);
|
||||
model.spec.chrome.subtitle = "Compose";
|
||||
model.spec.chrome.showTopBar = true;
|
||||
model.spec.chrome.showBottomBar = true;
|
||||
model.frame.hasTexture = false;
|
||||
model.frame.statusText = "Viewport shell";
|
||||
return model;
|
||||
}
|
||||
|
||||
UIEditorWorkspacePanelPresentationModel BuildViewportPresentationModel(
|
||||
std::string panelId,
|
||||
std::string title) {
|
||||
UIEditorWorkspacePanelPresentationModel model = {};
|
||||
model.panelId = std::move(panelId);
|
||||
model.kind = UIEditorPanelPresentationKind::ViewportShell;
|
||||
model.viewportShellModel = BuildViewportShellModel(std::move(title));
|
||||
return model;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(UIEditorWorkspaceComposeTest, ResolveRequestMapsViewportPresentationToVisiblePanelBodyRect) {
|
||||
const auto registry = BuildRegistryWithViewportPanels();
|
||||
const UIEditorWorkspaceModel workspace = BuildViewportWorkspace();
|
||||
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
const std::vector<UIEditorWorkspacePanelPresentationModel> presentationModels = {
|
||||
BuildViewportPresentationModel("viewport", "Viewport")
|
||||
};
|
||||
|
||||
const auto request = ResolveUIEditorWorkspaceComposeRequest(
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
presentationModels);
|
||||
|
||||
ASSERT_EQ(request.viewportRequests.size(), 1u);
|
||||
const auto* viewportRequest =
|
||||
FindUIEditorWorkspaceViewportPresentationRequest(request, "viewport");
|
||||
ASSERT_NE(viewportRequest, nullptr);
|
||||
|
||||
ASSERT_EQ(request.dockHostLayout.tabStacks.size(), 1u);
|
||||
const UIRect expectedBodyRect =
|
||||
request.dockHostLayout.tabStacks.front().contentFrameLayout.bodyRect;
|
||||
EXPECT_FLOAT_EQ(viewportRequest->bounds.x, expectedBodyRect.x);
|
||||
EXPECT_FLOAT_EQ(viewportRequest->bounds.y, expectedBodyRect.y);
|
||||
EXPECT_FLOAT_EQ(viewportRequest->bounds.width, expectedBodyRect.width);
|
||||
EXPECT_FLOAT_EQ(viewportRequest->bounds.height, expectedBodyRect.height);
|
||||
|
||||
const auto expectedShellRequest = ResolveUIEditorViewportShellRequest(
|
||||
expectedBodyRect,
|
||||
presentationModels.front().viewportShellModel.spec);
|
||||
EXPECT_FLOAT_EQ(
|
||||
viewportRequest->viewportShellRequest.requestedViewportSize.width,
|
||||
expectedShellRequest.requestedViewportSize.width);
|
||||
EXPECT_FLOAT_EQ(
|
||||
viewportRequest->viewportShellRequest.requestedViewportSize.height,
|
||||
expectedShellRequest.requestedViewportSize.height);
|
||||
}
|
||||
|
||||
TEST(UIEditorWorkspaceComposeTest, UpdateComposeOnlyBuildsFrameForSelectedViewportTab) {
|
||||
const auto registry = BuildRegistryWithTwoViewportTabs();
|
||||
const UIEditorWorkspaceModel workspace = BuildTwoViewportTabWorkspace();
|
||||
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
const std::vector<UIEditorWorkspacePanelPresentationModel> presentationModels = {
|
||||
BuildViewportPresentationModel("viewport-a", "Viewport A"),
|
||||
BuildViewportPresentationModel("viewport-b", "Viewport B")
|
||||
};
|
||||
|
||||
UIEditorWorkspaceComposeState state = {};
|
||||
const UIEditorWorkspaceComposeFrame frame = UpdateUIEditorWorkspaceCompose(
|
||||
state,
|
||||
UIRect(0.0f, 0.0f, 960.0f, 640.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
presentationModels,
|
||||
{});
|
||||
|
||||
ASSERT_EQ(frame.viewportFrames.size(), 1u);
|
||||
EXPECT_EQ(frame.viewportFrames.front().panelId, "viewport-b");
|
||||
EXPECT_EQ(
|
||||
FindUIEditorWorkspaceViewportPresentationFrame(frame, "viewport-a"),
|
||||
nullptr);
|
||||
EXPECT_NE(
|
||||
FindUIEditorWorkspaceViewportPresentationFrame(frame, "viewport-b"),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
TEST(UIEditorWorkspaceComposeTest, PlaceholderPanelsDoNotGenerateExternalViewportFrames) {
|
||||
const auto registry = BuildRegistryWithViewportPanels();
|
||||
const UIEditorWorkspaceModel workspace = BuildViewportWorkspace();
|
||||
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
std::vector<UIEditorWorkspacePanelPresentationModel> presentationModels = {
|
||||
BuildViewportPresentationModel("details", "Details")
|
||||
};
|
||||
|
||||
UIEditorWorkspaceComposeState state = {};
|
||||
const auto request = ResolveUIEditorWorkspaceComposeRequest(
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
presentationModels);
|
||||
EXPECT_TRUE(request.viewportRequests.empty());
|
||||
|
||||
const UIEditorWorkspaceComposeFrame frame = UpdateUIEditorWorkspaceCompose(
|
||||
state,
|
||||
UIRect(0.0f, 0.0f, 1280.0f, 720.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
presentationModels,
|
||||
{});
|
||||
EXPECT_TRUE(frame.viewportFrames.empty());
|
||||
}
|
||||
|
||||
TEST(UIEditorWorkspaceComposeTest, HiddenViewportTabResetsCapturedAndFocusedState) {
|
||||
const auto registry = BuildRegistryWithTwoViewportTabs();
|
||||
UIEditorWorkspaceModel workspace = BuildTwoViewportTabWorkspace();
|
||||
const auto session = BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
const std::vector<UIEditorWorkspacePanelPresentationModel> presentationModels = {
|
||||
BuildViewportPresentationModel("viewport-a", "Viewport A"),
|
||||
BuildViewportPresentationModel("viewport-b", "Viewport B")
|
||||
};
|
||||
|
||||
const auto initialRequest = ResolveUIEditorWorkspaceComposeRequest(
|
||||
UIRect(0.0f, 0.0f, 960.0f, 640.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
presentationModels);
|
||||
ASSERT_EQ(initialRequest.viewportRequests.size(), 1u);
|
||||
const auto* selectedViewportRequest =
|
||||
FindUIEditorWorkspaceViewportPresentationRequest(initialRequest, "viewport-b");
|
||||
ASSERT_NE(selectedViewportRequest, nullptr);
|
||||
|
||||
const UIRect bounds = selectedViewportRequest->bounds;
|
||||
const UIPoint center(
|
||||
bounds.x + bounds.width * 0.5f,
|
||||
bounds.y + bounds.height * 0.5f);
|
||||
const std::vector<UIInputEvent> inputEvents = {
|
||||
[] (const UIPoint& point) {
|
||||
UIInputEvent event = {};
|
||||
event.type = UIInputEventType::PointerButtonDown;
|
||||
event.pointerButton = UIPointerButton::Left;
|
||||
event.position = point;
|
||||
return event;
|
||||
}(center)
|
||||
};
|
||||
|
||||
UIEditorWorkspaceComposeState state = {};
|
||||
UpdateUIEditorWorkspaceCompose(
|
||||
state,
|
||||
UIRect(0.0f, 0.0f, 960.0f, 640.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
presentationModels,
|
||||
inputEvents);
|
||||
|
||||
const auto* viewportBStateBeforeHide =
|
||||
FindUIEditorWorkspacePanelPresentationState(state, "viewport-b");
|
||||
ASSERT_NE(viewportBStateBeforeHide, nullptr);
|
||||
EXPECT_TRUE(viewportBStateBeforeHide->viewportShellState.inputBridgeState.focused);
|
||||
EXPECT_TRUE(viewportBStateBeforeHide->viewportShellState.inputBridgeState.captured);
|
||||
|
||||
workspace.root.selectedTabIndex = 0u;
|
||||
workspace.activePanelId = "viewport-a";
|
||||
|
||||
const UIEditorWorkspaceComposeFrame frame = UpdateUIEditorWorkspaceCompose(
|
||||
state,
|
||||
UIRect(0.0f, 0.0f, 960.0f, 640.0f),
|
||||
registry,
|
||||
workspace,
|
||||
session,
|
||||
presentationModels,
|
||||
{});
|
||||
|
||||
ASSERT_EQ(frame.viewportFrames.size(), 1u);
|
||||
EXPECT_EQ(frame.viewportFrames.front().panelId, "viewport-a");
|
||||
|
||||
const auto* viewportBStateAfterHide =
|
||||
FindUIEditorWorkspacePanelPresentationState(state, "viewport-b");
|
||||
ASSERT_NE(viewportBStateAfterHide, nullptr);
|
||||
EXPECT_FALSE(viewportBStateAfterHide->viewportShellState.inputBridgeState.focused);
|
||||
EXPECT_FALSE(viewportBStateAfterHide->viewportShellState.inputBridgeState.captured);
|
||||
}
|
||||
Reference in New Issue
Block a user