Integrate XCUI shell state and runtime frame seams

This commit is contained in:
2026-04-05 12:50:55 +08:00
parent ec97445071
commit e5e9f348a3
29 changed files with 3183 additions and 102 deletions

View File

@@ -40,6 +40,15 @@ set(NEW_EDITOR_LAYOUT_LAB_RUNTIME_HEADER
set(NEW_EDITOR_LAYOUT_LAB_RUNTIME_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUILayoutLabRuntime.cpp
)
set(NEW_EDITOR_LAYOUT_LAB_PANEL_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/panels/XCUILayoutLabPanel.h
)
set(NEW_EDITOR_LAYOUT_LAB_PANEL_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/panels/XCUILayoutLabPanel.cpp
)
set(NEW_EDITOR_BASE_PANEL_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/panels/Panel.cpp
)
set(NEW_EDITOR_ASSET_DOCUMENT_SOURCE_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIAssetDocumentSource.h
)
@@ -64,6 +73,15 @@ set(NEW_EDITOR_COMMAND_ROUTER_HEADER
set(NEW_EDITOR_COMMAND_ROUTER_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIEditorCommandRouter.cpp
)
set(NEW_EDITOR_SHELL_CHROME_STATE_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIShellChromeState.h
)
set(NEW_EDITOR_SHELL_CHROME_STATE_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIShellChromeState.cpp
)
set(NEW_EDITOR_APPLICATION_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/Application.h
)
set(NEW_EDITOR_IMGUI_INPUT_ADAPTER_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/ImGuiXCUIInputAdapter.h
)
@@ -213,6 +231,52 @@ else()
message(STATUS "Skipping new_editor_xcui_layout_lab_runtime_tests because XCUILayoutLabRuntime files are missing.")
endif()
if(EXISTS "${NEW_EDITOR_LAYOUT_LAB_PANEL_HEADER}" AND
EXISTS "${NEW_EDITOR_LAYOUT_LAB_PANEL_SOURCE}" AND
EXISTS "${NEW_EDITOR_LAYOUT_LAB_RUNTIME_HEADER}" AND
EXISTS "${NEW_EDITOR_LAYOUT_LAB_RUNTIME_SOURCE}" AND
EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE_HEADER}" AND
EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_xcui_layout_lab_panel.cpp" AND
EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp")
add_executable(new_editor_xcui_layout_lab_panel_tests
test_xcui_layout_lab_panel.cpp
${NEW_EDITOR_LAYOUT_LAB_PANEL_SOURCE}
${NEW_EDITOR_BASE_PANEL_SOURCE}
${NEW_EDITOR_LAYOUT_LAB_RUNTIME_SOURCE}
${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_draw.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_tables.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_widgets.cpp
)
xcengine_configure_new_editor_test_target(new_editor_xcui_layout_lab_panel_tests)
target_link_libraries(new_editor_xcui_layout_lab_panel_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
user32
comdlg32
)
target_include_directories(new_editor_xcui_layout_lab_panel_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
${CMAKE_BINARY_DIR}/_deps/imgui-src
${CMAKE_BINARY_DIR}/_deps/imgui-src/backends
)
target_compile_definitions(new_editor_xcui_layout_lab_panel_tests PRIVATE
XCENGINE_NEW_EDITOR_REPO_ROOT="${XCENGINE_TEST_REPO_ROOT_CMAKE}"
)
xcengine_discover_new_editor_gtests(new_editor_xcui_layout_lab_panel_tests)
else()
message(STATUS "Skipping new_editor_xcui_layout_lab_panel_tests because panel, runtime, test, or ImGui sources are missing.")
endif()
if(EXISTS "${NEW_EDITOR_BACKEND_HEADER}" AND EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp")
add_executable(new_editor_imgui_transition_backend_tests
test_new_editor_imgui_transition_backend.cpp
@@ -388,6 +452,63 @@ else()
message(STATUS "Skipping new_editor_xcui_editor_command_router_tests because command router files or the test source are missing.")
endif()
if(EXISTS "${NEW_EDITOR_SHELL_CHROME_STATE_HEADER}" AND
EXISTS "${NEW_EDITOR_SHELL_CHROME_STATE_SOURCE}" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_xcui_shell_chrome_state.cpp")
add_executable(new_editor_xcui_shell_chrome_state_tests
test_xcui_shell_chrome_state.cpp
${NEW_EDITOR_SHELL_CHROME_STATE_SOURCE}
)
xcengine_configure_new_editor_test_target(new_editor_xcui_shell_chrome_state_tests)
target_link_libraries(new_editor_xcui_shell_chrome_state_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
)
target_include_directories(new_editor_xcui_shell_chrome_state_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
)
xcengine_discover_new_editor_gtests(new_editor_xcui_shell_chrome_state_tests)
else()
message(STATUS "Skipping new_editor_xcui_shell_chrome_state_tests because shell chrome state files or the test source are missing.")
endif()
if(EXISTS "${NEW_EDITOR_APPLICATION_HEADER}" AND
EXISTS "${NEW_EDITOR_COMMAND_ROUTER_SOURCE}" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_application_shell_command_bindings.cpp")
add_executable(new_editor_application_shell_command_bindings_tests
test_application_shell_command_bindings.cpp
${NEW_EDITOR_COMMAND_ROUTER_SOURCE}
)
xcengine_configure_new_editor_test_target(new_editor_application_shell_command_bindings_tests)
target_link_libraries(new_editor_application_shell_command_bindings_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
)
target_include_directories(new_editor_application_shell_command_bindings_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
${CMAKE_SOURCE_DIR}/editor/src
${CMAKE_BINARY_DIR}/_deps/imgui-src
${CMAKE_BINARY_DIR}/_deps/imgui-src/backends
)
xcengine_discover_new_editor_gtests(new_editor_application_shell_command_bindings_tests)
else()
message(STATUS "Skipping new_editor_application_shell_command_bindings_tests because Application header, command router source, or the test source are missing.")
endif()
if(EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_HEADER}" AND
EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE}" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_imgui_xcui_input_adapter.cpp" AND
@@ -576,3 +697,63 @@ if(EXISTS "${NEW_EDITOR_RHI_COMMAND_COMPILER_HEADER}" AND
else()
message(STATUS "Skipping new_editor_xcui_rhi_command_compiler_tests because compiler files are missing.")
endif()
if(EXISTS "${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIPanelCanvasHost.h" AND
EXISTS "${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/NullXCUIPanelCanvasHost.h" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_xcui_panel_canvas_host.cpp")
add_executable(new_editor_xcui_panel_canvas_host_tests
test_xcui_panel_canvas_host.cpp
)
xcengine_configure_new_editor_test_target(new_editor_xcui_panel_canvas_host_tests)
target_link_libraries(new_editor_xcui_panel_canvas_host_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
)
target_include_directories(new_editor_xcui_panel_canvas_host_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
)
xcengine_discover_new_editor_gtests(new_editor_xcui_panel_canvas_host_tests)
else()
message(STATUS "Skipping new_editor_xcui_panel_canvas_host_tests because panel canvas host headers or the test source are missing.")
endif()
if(EXISTS "${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/ImGuiXCUIPanelCanvasHost.h" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_imgui_xcui_panel_canvas_host.cpp" AND
EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp")
add_executable(new_editor_imgui_xcui_panel_canvas_host_tests
test_imgui_xcui_panel_canvas_host.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_draw.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_tables.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_widgets.cpp
)
xcengine_configure_new_editor_test_target(new_editor_imgui_xcui_panel_canvas_host_tests)
target_link_libraries(new_editor_imgui_xcui_panel_canvas_host_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
user32
comdlg32
)
target_include_directories(new_editor_imgui_xcui_panel_canvas_host_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
${CMAKE_BINARY_DIR}/_deps/imgui-src
${CMAKE_BINARY_DIR}/_deps/imgui-src/backends
)
xcengine_discover_new_editor_gtests(new_editor_imgui_xcui_panel_canvas_host_tests)
else()
message(STATUS "Skipping new_editor_imgui_xcui_panel_canvas_host_tests because the ImGui host header, test source, or ImGui sources are missing.")
endif()

View File

@@ -0,0 +1,250 @@
#include "Application.h"
#include <XCEngine/Input/InputTypes.h>
#include <gtest/gtest.h>
namespace {
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandRouter;
using XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta;
using XCEngine::Input::KeyCode;
using XCEngine::NewEditor::Application;
constexpr std::size_t ToPanelIndex(Application::ShellPanelId panelId) {
return static_cast<std::size_t>(panelId);
}
struct ShellCommandHarness {
Application::ShellViewToggleState viewToggles = {};
std::array<Application::ShellPanelChromeState, static_cast<std::size_t>(Application::ShellPanelId::Count)>
panels = {};
int hostedPreviewReconfigureCount = 0;
ShellCommandHarness() {
panels[ToPanelIndex(Application::ShellPanelId::XCUIDemo)] = {
Application::ShellPanelId::XCUIDemo,
"XCUI Demo",
"XCUI Demo",
"new_editor.panels.xcui_demo",
true,
true,
Application::ShellHostedPreviewMode::NativeOffscreen
};
panels[ToPanelIndex(Application::ShellPanelId::XCUILayoutLab)] = {
Application::ShellPanelId::XCUILayoutLab,
"XCUI Layout Lab",
"XCUI Layout Lab",
"new_editor.panels.xcui_layout_lab",
true,
true,
Application::ShellHostedPreviewMode::LegacyImGui
};
}
Application::ShellPanelChromeState& Panel(Application::ShellPanelId panelId) {
return panels[ToPanelIndex(panelId)];
}
const Application::ShellPanelChromeState& Panel(Application::ShellPanelId panelId) const {
return panels[ToPanelIndex(panelId)];
}
Application::ShellCommandBindings BuildBindings() {
Application::ShellCommandBindings bindings = {};
bindings.getXCUIDemoPanelVisible = [this]() { return Panel(Application::ShellPanelId::XCUIDemo).visible; };
bindings.setXCUIDemoPanelVisible = [this](bool visible) {
Panel(Application::ShellPanelId::XCUIDemo).visible = visible;
};
bindings.getXCUILayoutLabPanelVisible = [this]() {
return Panel(Application::ShellPanelId::XCUILayoutLab).visible;
};
bindings.setXCUILayoutLabPanelVisible = [this](bool visible) {
Panel(Application::ShellPanelId::XCUILayoutLab).visible = visible;
};
bindings.getImGuiDemoWindowVisible = [this]() { return viewToggles.imguiDemoWindowVisible; };
bindings.setImGuiDemoWindowVisible = [this](bool visible) { viewToggles.imguiDemoWindowVisible = visible; };
bindings.getNativeBackdropVisible = [this]() { return viewToggles.nativeBackdropVisible; };
bindings.setNativeBackdropVisible = [this](bool visible) { viewToggles.nativeBackdropVisible = visible; };
bindings.getPulseAccentEnabled = [this]() { return viewToggles.pulseAccentEnabled; };
bindings.setPulseAccentEnabled = [this](bool enabled) { viewToggles.pulseAccentEnabled = enabled; };
bindings.getNativeXCUIOverlayVisible = [this]() { return viewToggles.nativeXCUIOverlayVisible; };
bindings.setNativeXCUIOverlayVisible = [this](bool visible) { viewToggles.nativeXCUIOverlayVisible = visible; };
bindings.getHostedPreviewHudVisible = [this]() { return viewToggles.hostedPreviewHudVisible; };
bindings.setHostedPreviewHudVisible = [this](bool visible) { viewToggles.hostedPreviewHudVisible = visible; };
bindings.getNativeDemoPanelPreviewEnabled = [this]() {
return Panel(Application::ShellPanelId::XCUIDemo).previewMode ==
Application::ShellHostedPreviewMode::NativeOffscreen;
};
bindings.setNativeDemoPanelPreviewEnabled = [this](bool enabled) {
Panel(Application::ShellPanelId::XCUIDemo).previewMode =
enabled
? Application::ShellHostedPreviewMode::NativeOffscreen
: Application::ShellHostedPreviewMode::LegacyImGui;
};
bindings.getNativeLayoutLabPreviewEnabled = [this]() {
return Panel(Application::ShellPanelId::XCUILayoutLab).previewMode ==
Application::ShellHostedPreviewMode::NativeOffscreen;
};
bindings.setNativeLayoutLabPreviewEnabled = [this](bool enabled) {
Panel(Application::ShellPanelId::XCUILayoutLab).previewMode =
enabled
? Application::ShellHostedPreviewMode::NativeOffscreen
: Application::ShellHostedPreviewMode::LegacyImGui;
};
bindings.onHostedPreviewModeChanged = [this]() { ++hostedPreviewReconfigureCount; };
return bindings;
}
};
TEST(ApplicationShellCommandBindingsTest, RegisterShellViewCommandsInvokesBoundToggleHandlers) {
ShellCommandHarness harness = {};
XCUIEditorCommandRouter router = {};
Application::RegisterShellViewCommands(router, harness.BuildBindings());
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleXCUIDemoPanel));
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleXCUILayoutLabPanel));
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleImGuiDemoWindow));
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleNativeBackdrop));
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::TogglePulseAccent));
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleNativeXCUIOverlay));
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleHostedPreviewHud));
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleNativeDemoPanelPreview));
EXPECT_TRUE(router.HasCommand(Application::ShellCommandIds::ToggleNativeLayoutLabPreview));
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleXCUIDemoPanel));
EXPECT_FALSE(harness.Panel(Application::ShellPanelId::XCUIDemo).visible);
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleImGuiDemoWindow));
EXPECT_TRUE(harness.viewToggles.imguiDemoWindowVisible);
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeBackdrop));
EXPECT_FALSE(harness.viewToggles.nativeBackdropVisible);
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeXCUIOverlay));
EXPECT_FALSE(harness.viewToggles.nativeXCUIOverlayVisible);
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleHostedPreviewHud));
EXPECT_FALSE(harness.viewToggles.hostedPreviewHudVisible);
}
TEST(ApplicationShellCommandBindingsTest, PreviewModeCommandsTriggerHostedPreviewReconfigureCallback) {
ShellCommandHarness harness = {};
XCUIEditorCommandRouter router = {};
Application::RegisterShellViewCommands(router, harness.BuildBindings());
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeDemoPanelPreview));
EXPECT_EQ(
harness.Panel(Application::ShellPanelId::XCUIDemo).previewMode,
Application::ShellHostedPreviewMode::LegacyImGui);
EXPECT_EQ(harness.hostedPreviewReconfigureCount, 1);
EXPECT_TRUE(router.InvokeCommand(Application::ShellCommandIds::ToggleNativeLayoutLabPreview));
EXPECT_EQ(
harness.Panel(Application::ShellPanelId::XCUILayoutLab).previewMode,
Application::ShellHostedPreviewMode::NativeOffscreen);
EXPECT_EQ(harness.hostedPreviewReconfigureCount, 2);
}
TEST(ApplicationShellCommandBindingsTest, BuildShellShortcutSnapshotCarriesBridgeStateAndKeyboardEdges) {
XCUIInputBridgeFrameDelta frameDelta = {};
frameDelta.state.windowFocused = true;
frameDelta.state.wantCaptureKeyboard = true;
frameDelta.state.wantTextInput = true;
frameDelta.state.modifiers.control = true;
frameDelta.state.modifiers.shift = true;
frameDelta.keyboard.pressedKeys.push_back(static_cast<std::int32_t>(KeyCode::One));
frameDelta.keyboard.repeatedKeys.push_back(static_cast<std::int32_t>(KeyCode::Two));
const auto snapshot = Application::BuildShellShortcutSnapshot(frameDelta);
EXPECT_TRUE(snapshot.windowFocused);
EXPECT_TRUE(snapshot.wantCaptureKeyboard);
EXPECT_TRUE(snapshot.wantTextInput);
EXPECT_TRUE(snapshot.modifiers.control);
EXPECT_TRUE(snapshot.modifiers.shift);
EXPECT_TRUE(snapshot.IsKeyDown(static_cast<std::int32_t>(KeyCode::One)));
EXPECT_TRUE(snapshot.IsKeyDown(static_cast<std::int32_t>(KeyCode::Two)));
const auto* repeatedKey = snapshot.FindKeyState(static_cast<std::int32_t>(KeyCode::Two));
ASSERT_NE(repeatedKey, nullptr);
EXPECT_TRUE(repeatedKey->repeat);
}
TEST(ApplicationShellCommandBindingsTest, RegisteredShellShortcutsMatchExpectedViewCommands) {
ShellCommandHarness harness = {};
XCUIEditorCommandRouter router = {};
Application::RegisterShellViewCommands(router, harness.BuildBindings());
XCUIInputBridgeFrameDelta frameDelta = {};
frameDelta.state.windowFocused = true;
frameDelta.state.modifiers.control = true;
frameDelta.keyboard.pressedKeys.push_back(static_cast<std::int32_t>(KeyCode::One));
const auto demoSnapshot = Application::BuildShellShortcutSnapshot(frameDelta);
const auto demoMatch = router.MatchShortcut({ &demoSnapshot });
ASSERT_TRUE(demoMatch.matched);
EXPECT_EQ(demoMatch.commandId, Application::ShellCommandIds::ToggleXCUIDemoPanel);
XCUIInputBridgeFrameDelta previewDelta = {};
previewDelta.state.windowFocused = true;
previewDelta.state.modifiers.control = true;
previewDelta.state.modifiers.alt = true;
previewDelta.keyboard.pressedKeys.push_back(static_cast<std::int32_t>(KeyCode::Two));
const auto previewSnapshot = Application::BuildShellShortcutSnapshot(previewDelta);
const auto previewMatch = router.MatchShortcut({ &previewSnapshot });
ASSERT_TRUE(previewMatch.matched);
EXPECT_EQ(previewMatch.commandId, Application::ShellCommandIds::ToggleNativeLayoutLabPreview);
}
TEST(ApplicationShellCommandBindingsTest, RegisteredShellShortcutsRespectCaptureAndRepeatGuards) {
ShellCommandHarness harness = {};
XCUIEditorCommandRouter router = {};
Application::RegisterShellViewCommands(router, harness.BuildBindings());
XCUIInputBridgeFrameDelta capturedDelta = {};
capturedDelta.state.windowFocused = true;
capturedDelta.state.wantCaptureKeyboard = true;
capturedDelta.state.modifiers.control = true;
capturedDelta.keyboard.pressedKeys.push_back(static_cast<std::int32_t>(KeyCode::One));
const auto capturedSnapshot = Application::BuildShellShortcutSnapshot(capturedDelta);
EXPECT_FALSE(router.MatchShortcut({ &capturedSnapshot }).matched);
XCUIInputBridgeFrameDelta textInputDelta = {};
textInputDelta.state.windowFocused = true;
textInputDelta.state.wantTextInput = true;
textInputDelta.state.modifiers.control = true;
textInputDelta.keyboard.pressedKeys.push_back(static_cast<std::int32_t>(KeyCode::One));
const auto textInputSnapshot = Application::BuildShellShortcutSnapshot(textInputDelta);
EXPECT_FALSE(router.MatchShortcut({ &textInputSnapshot }).matched);
XCUIInputBridgeFrameDelta repeatedDelta = {};
repeatedDelta.state.windowFocused = true;
repeatedDelta.state.modifiers.control = true;
repeatedDelta.keyboard.repeatedKeys.push_back(static_cast<std::int32_t>(KeyCode::One));
const auto repeatedSnapshot = Application::BuildShellShortcutSnapshot(repeatedDelta);
EXPECT_FALSE(router.MatchShortcut({ &repeatedSnapshot }).matched);
}
TEST(ApplicationShellCommandBindingsTest, PreviewShortcutInvokesCommandHandlerAndReconfigureCallback) {
ShellCommandHarness harness = {};
XCUIEditorCommandRouter router = {};
Application::RegisterShellViewCommands(router, harness.BuildBindings());
XCUIInputBridgeFrameDelta previewDelta = {};
previewDelta.state.windowFocused = true;
previewDelta.state.modifiers.control = true;
previewDelta.state.modifiers.alt = true;
previewDelta.keyboard.pressedKeys.push_back(static_cast<std::int32_t>(KeyCode::One));
const auto previewSnapshot = Application::BuildShellShortcutSnapshot(previewDelta);
EXPECT_TRUE(router.InvokeMatchingShortcut({ &previewSnapshot }));
EXPECT_EQ(
harness.Panel(Application::ShellPanelId::XCUIDemo).previewMode,
Application::ShellHostedPreviewMode::LegacyImGui);
EXPECT_EQ(harness.hostedPreviewReconfigureCount, 1);
}
} // namespace

View File

@@ -0,0 +1,25 @@
#include <gtest/gtest.h>
#include "XCUIBackend/ImGuiXCUIPanelCanvasHost.h"
namespace {
using XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostBackend;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostCapabilities;
TEST(NewEditorImGuiXCUIPanelCanvasHostTest, ReportsExplicitBackendAndCapabilities) {
std::unique_ptr<IXCUIPanelCanvasHost> host = CreateImGuiXCUIPanelCanvasHost();
ASSERT_NE(host, nullptr);
EXPECT_STREQ(host->GetDebugName(), "ImGuiXCUIPanelCanvasHost");
EXPECT_EQ(host->GetBackend(), XCUIPanelCanvasHostBackend::ImGui);
const XCUIPanelCanvasHostCapabilities capabilities = host->GetCapabilities();
EXPECT_TRUE(capabilities.supportsPointerHitTesting);
EXPECT_TRUE(capabilities.supportsHostedSurfaceImages);
EXPECT_TRUE(capabilities.supportsPrimitiveOverlays);
}
} // namespace

View File

@@ -56,6 +56,17 @@ UIInputEvent MakeKeyDownEvent(
return event;
}
UIInputEvent MakePointerButtonEvent(
UIInputEventType type,
const XCEngine::UI::UIPoint& position,
XCEngine::UI::UIPointerButton button = XCEngine::UI::UIPointerButton::Left) {
UIInputEvent event = {};
event.type = type;
event.pointerButton = button;
event.position = position;
return event;
}
fs::path FindDemoResourcePath() {
fs::path probe = fs::current_path();
for (int i = 0; i < 8; ++i) {
@@ -263,6 +274,25 @@ TEST(NewEditorXCUIDemoRuntimeTest, DrainPendingCommandIdsCapturesPointerActivati
EXPECT_TRUE(runtime.DrainPendingCommandIds().empty());
}
TEST(NewEditorXCUIDemoRuntimeTest, DrainPendingCommandIdsCapturesShortcutCommands) {
XCEngine::Editor::XCUIBackend::XCUIDemoRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& baselineFrame = runtime.Update(BuildInputState());
ASSERT_TRUE(baselineFrame.stats.documentsReady);
EXPECT_TRUE(runtime.DrainPendingCommandIds().empty());
XCEngine::Editor::XCUIBackend::XCUIDemoInputState shortcutInput = BuildInputState();
shortcutInput.events.push_back(MakeKeyDownEvent(XCEngine::Input::KeyCode::P, false, false, true));
const auto& shortcutFrame = runtime.Update(shortcutInput);
ASSERT_TRUE(shortcutFrame.stats.documentsReady);
EXPECT_TRUE(shortcutFrame.stats.accentEnabled);
EXPECT_EQ(shortcutFrame.stats.lastCommandId, "demo.toggleAccent");
EXPECT_EQ(runtime.DrainPendingCommandIds(), std::vector<std::string>({ "demo.toggleAccent" }));
EXPECT_TRUE(runtime.DrainPendingCommandIds().empty());
}
TEST(NewEditorXCUIDemoRuntimeTest, DrainPendingCommandIdsPreservesMultipleTextEditCommandsPerFrame) {
XCEngine::Editor::XCUIBackend::XCUIDemoRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
@@ -313,6 +343,43 @@ TEST(NewEditorXCUIDemoRuntimeTest, DrainPendingCommandIdsPreservesMultipleTextEd
EXPECT_TRUE(runtime.DrainPendingCommandIds().empty());
}
TEST(NewEditorXCUIDemoRuntimeTest, DrainPendingCommandIdsPreserveMixedPointerTextAndShortcutOrder) {
XCEngine::Editor::XCUIBackend::XCUIDemoRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& baselineFrame = runtime.Update(BuildInputState());
ASSERT_TRUE(baselineFrame.stats.documentsReady);
EXPECT_TRUE(runtime.DrainPendingCommandIds().empty());
XCEngine::UI::UIRect promptRect = {};
ASSERT_TRUE(runtime.TryGetElementRect("agentPrompt", promptRect));
const XCEngine::UI::UIPoint promptCenter(
promptRect.x + promptRect.width * 0.5f,
promptRect.y + promptRect.height * 0.5f);
XCEngine::Editor::XCUIBackend::XCUIDemoInputState mixedInput = BuildInputState();
mixedInput.pointerPosition = promptCenter;
mixedInput.events.push_back(MakePointerButtonEvent(UIInputEventType::PointerButtonDown, promptCenter));
mixedInput.events.push_back(MakePointerButtonEvent(UIInputEventType::PointerButtonUp, promptCenter));
mixedInput.events.push_back(MakeCharacterEvent('A'));
mixedInput.events.push_back(MakeKeyDownEvent(XCEngine::Input::KeyCode::P, false, false, true));
const auto& mixedFrame = runtime.Update(mixedInput);
ASSERT_TRUE(mixedFrame.stats.documentsReady);
EXPECT_EQ(mixedFrame.stats.focusedElementId, "agentPrompt");
EXPECT_TRUE(mixedFrame.stats.accentEnabled);
EXPECT_EQ(mixedFrame.stats.lastCommandId, "demo.toggleAccent");
EXPECT_NE(FindTextCommand(mixedFrame.drawData, "A"), nullptr);
EXPECT_EQ(
runtime.DrainPendingCommandIds(),
std::vector<std::string>({
"demo.activate.agentPrompt",
"demo.text.edit.agentPrompt",
"demo.toggleAccent" }));
EXPECT_TRUE(runtime.DrainPendingCommandIds().empty());
}
TEST(NewEditorXCUIDemoRuntimeTest, PointerToggleUpdatesFocusStatusTextAndAccentState) {
XCEngine::Editor::XCUIBackend::XCUIDemoRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());

View File

@@ -13,6 +13,7 @@ namespace {
using XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter;
using XCEngine::Editor::XCUIBackend::CreateQueuedNativeXCUIHostedPreviewPresenter;
using XCEngine::Editor::XCUIBackend::IImGuiXCUIHostedPreviewTargetBinding;
using XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewDrainStats;
@@ -36,6 +37,19 @@ public:
}
};
class RecordingImGuiHostedPreviewTargetBinding final : public IImGuiXCUIHostedPreviewTargetBinding {
public:
ImDrawList* ResolveTargetDrawList(const XCUIHostedPreviewFrame& frame) const override {
++resolveCallCount;
lastFrame = &frame;
return resolvedDrawList;
}
mutable std::size_t resolveCallCount = 0u;
mutable const XCUIHostedPreviewFrame* lastFrame = nullptr;
ImDrawList* resolvedDrawList = nullptr;
};
void PrepareImGui(float width = 1024.0f, float height = 768.0f) {
ImGuiIO& io = ImGui::GetIO();
io.DisplaySize = ImVec2(width, height);
@@ -78,13 +92,10 @@ TEST(XCUIHostedPreviewPresenterTest, PresentReturnsFalseAndClearsStatsWhenFrameH
EXPECT_EQ(stats.flushedCommandCount, 0u);
}
TEST(XCUIHostedPreviewPresenterTest, PresentFlushesDrawDataIntoProvidedImGuiDrawList) {
TEST(XCUIHostedPreviewPresenterTest, PresentFlushesDrawDataIntoExplicitBindingResolvedImGuiDrawList) {
ImGuiContextScope contextScope;
PrepareImGui(800.0f, 600.0f);
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter = CreateImGuiXCUIHostedPreviewPresenter();
ASSERT_NE(presenter, nullptr);
XCEngine::UI::UIDrawData drawData = {};
XCEngine::UI::UIDrawList& drawList = drawData.EmplaceDrawList("HostedPreview");
drawList.AddFilledRect(
@@ -101,6 +112,15 @@ TEST(XCUIHostedPreviewPresenterTest, PresentFlushesDrawDataIntoProvidedImGuiDraw
ImDrawList* targetDrawList = ImGui::GetWindowDrawList();
ASSERT_NE(targetDrawList, nullptr);
std::unique_ptr<RecordingImGuiHostedPreviewTargetBinding> targetBinding =
std::make_unique<RecordingImGuiHostedPreviewTargetBinding>();
RecordingImGuiHostedPreviewTargetBinding* targetBindingPtr = targetBinding.get();
targetBindingPtr->resolvedDrawList = targetDrawList;
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter =
CreateImGuiXCUIHostedPreviewPresenter(std::move(targetBinding));
ASSERT_NE(presenter, nullptr);
XCUIHostedPreviewFrame frame = {};
frame.drawData = &drawData;
@@ -116,6 +136,129 @@ TEST(XCUIHostedPreviewPresenterTest, PresentFlushesDrawDataIntoProvidedImGuiDraw
EXPECT_EQ(stats.submittedCommandCount, 2u);
EXPECT_EQ(stats.flushedDrawListCount, 1u);
EXPECT_EQ(stats.flushedCommandCount, 2u);
EXPECT_EQ(targetBindingPtr->resolveCallCount, 1u);
EXPECT_EQ(targetBindingPtr->lastFrame, &frame);
EXPECT_GT(targetDrawList->VtxBuffer.Size, 0);
EXPECT_GT(targetDrawList->CmdBuffer.Size, 0);
}
TEST(XCUIHostedPreviewPresenterTest, PresentReturnsFalseWhenExplicitBindingDoesNotResolveTargetDrawList) {
ImGuiContextScope contextScope;
PrepareImGui(800.0f, 600.0f);
XCEngine::UI::UIDrawData drawData = {};
drawData.EmplaceDrawList("HostedPreviewMissingTarget").AddFilledRect(
XCEngine::UI::UIRect(10.0f, 12.0f, 44.0f, 28.0f),
XCEngine::UI::UIColor(0.9f, 0.3f, 0.2f, 1.0f));
std::unique_ptr<RecordingImGuiHostedPreviewTargetBinding> targetBinding =
std::make_unique<RecordingImGuiHostedPreviewTargetBinding>();
RecordingImGuiHostedPreviewTargetBinding* targetBindingPtr = targetBinding.get();
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter =
CreateImGuiXCUIHostedPreviewPresenter(std::move(targetBinding));
ASSERT_NE(presenter, nullptr);
XCUIHostedPreviewFrame frame = {};
frame.drawData = &drawData;
frame.debugName = "HostedPreviewMissingTarget";
const bool presented = presenter->Present(frame);
const XCUIHostedPreviewStats& stats = presenter->GetLastStats();
EXPECT_FALSE(presented);
EXPECT_FALSE(stats.presented);
EXPECT_EQ(stats.submittedDrawListCount, 1u);
EXPECT_EQ(stats.submittedCommandCount, 1u);
EXPECT_EQ(stats.flushedDrawListCount, 0u);
EXPECT_EQ(stats.flushedCommandCount, 0u);
EXPECT_EQ(targetBindingPtr->resolveCallCount, 1u);
EXPECT_EQ(targetBindingPtr->lastFrame, &frame);
}
TEST(XCUIHostedPreviewPresenterTest, PresentFlushesDrawDataIntoExplicitBindingResolvedForegroundDrawListWithoutWindow) {
ImGuiContextScope contextScope;
PrepareImGui(800.0f, 600.0f);
XCEngine::UI::UIDrawData drawData = {};
XCEngine::UI::UIDrawList& drawList = drawData.EmplaceDrawList("HostedPreviewForegroundTarget");
drawList.AddFilledRect(
XCEngine::UI::UIRect(16.0f, 18.0f, 52.0f, 34.0f),
XCEngine::UI::UIColor(0.15f, 0.75f, 0.45f, 1.0f));
drawList.AddText(
XCEngine::UI::UIPoint(24.0f, 28.0f),
"foreground",
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
15.0f);
ImGui::NewFrame();
ImDrawList* targetDrawList = ImGui::GetForegroundDrawList();
ASSERT_NE(targetDrawList, nullptr);
const int baselineVertexCount = targetDrawList->VtxBuffer.Size;
const int baselineIndexCount = targetDrawList->IdxBuffer.Size;
const int baselineCommandCount = targetDrawList->CmdBuffer.Size;
std::unique_ptr<RecordingImGuiHostedPreviewTargetBinding> targetBinding =
std::make_unique<RecordingImGuiHostedPreviewTargetBinding>();
RecordingImGuiHostedPreviewTargetBinding* targetBindingPtr = targetBinding.get();
targetBindingPtr->resolvedDrawList = targetDrawList;
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter =
CreateImGuiXCUIHostedPreviewPresenter(std::move(targetBinding));
ASSERT_NE(presenter, nullptr);
XCUIHostedPreviewFrame frame = {};
frame.drawData = &drawData;
frame.debugName = "HostedPreviewForegroundTarget";
const bool presented = presenter->Present(frame);
const XCUIHostedPreviewStats& stats = presenter->GetLastStats();
ImGui::EndFrame();
EXPECT_TRUE(presented);
EXPECT_TRUE(stats.presented);
EXPECT_EQ(stats.submittedDrawListCount, 1u);
EXPECT_EQ(stats.submittedCommandCount, 2u);
EXPECT_EQ(stats.flushedDrawListCount, 1u);
EXPECT_EQ(stats.flushedCommandCount, 2u);
EXPECT_EQ(targetBindingPtr->resolveCallCount, 1u);
EXPECT_EQ(targetBindingPtr->lastFrame, &frame);
EXPECT_GT(targetDrawList->VtxBuffer.Size, baselineVertexCount);
EXPECT_GT(targetDrawList->IdxBuffer.Size, baselineIndexCount);
EXPECT_GE(targetDrawList->CmdBuffer.Size, baselineCommandCount);
}
TEST(XCUIHostedPreviewPresenterTest, DefaultFactoryStillUsesCurrentWindowBindingForLegacyImGuiPath) {
ImGuiContextScope contextScope;
PrepareImGui(800.0f, 600.0f);
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter = CreateImGuiXCUIHostedPreviewPresenter();
ASSERT_NE(presenter, nullptr);
XCEngine::UI::UIDrawData drawData = {};
drawData.EmplaceDrawList("HostedPreviewDefaultBinding").AddFilledRect(
XCEngine::UI::UIRect(8.0f, 10.0f, 36.0f, 22.0f),
XCEngine::UI::UIColor(0.2f, 0.6f, 0.9f, 1.0f));
ImGui::NewFrame();
ASSERT_TRUE(ImGui::Begin("HostedPreviewPresenterDefaultBindingWindow"));
ImDrawList* targetDrawList = ImGui::GetWindowDrawList();
ASSERT_NE(targetDrawList, nullptr);
XCUIHostedPreviewFrame frame = {};
frame.drawData = &drawData;
const bool presented = presenter->Present(frame);
const XCUIHostedPreviewStats& stats = presenter->GetLastStats();
ImGui::End();
ImGui::EndFrame();
EXPECT_TRUE(presented);
EXPECT_TRUE(stats.presented);
EXPECT_EQ(stats.flushedDrawListCount, 1u);
EXPECT_EQ(stats.flushedCommandCount, 1u);
EXPECT_GT(targetDrawList->VtxBuffer.Size, 0);
EXPECT_GT(targetDrawList->CmdBuffer.Size, 0);
}

View File

@@ -22,6 +22,20 @@ XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState BuildInputState(
return input;
}
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState BuildKeyboardInputState(
float width = 960.0f,
float height = 640.0f) {
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState input = BuildInputState(width, height);
input.pointerInside = false;
return input;
}
XCEngine::UI::UIPoint RectCenter(const XCEngine::UI::UIRect& rect) {
return XCEngine::UI::UIPoint(
rect.x + rect.width * 0.5f,
rect.y + rect.height * 0.5f);
}
std::vector<const UIDrawCommand*> CollectTextCommands(const XCEngine::UI::UIDrawData& drawData) {
std::vector<const UIDrawCommand*> textCommands = {};
for (const XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
@@ -363,3 +377,156 @@ TEST(NewEditorXCUILayoutLabRuntimeTest, ClickingPropertySectionHeaderTogglesFiel
EXPECT_TRUE(runtime.TryGetElementRect("fieldPosition", fieldRect));
EXPECT_GT(fieldRect.height, 0.0f);
}
TEST(NewEditorXCUILayoutLabRuntimeTest, KeyboardNavigationMovesSelectionAcrossListItems) {
XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& baseline = runtime.Update(BuildInputState());
ASSERT_TRUE(baseline.stats.documentsReady);
XCEngine::UI::UIRect listItemRect = {};
ASSERT_TRUE(runtime.TryGetElementRect("assetLighting", listItemRect));
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState selectInput = BuildInputState();
selectInput.pointerPosition = RectCenter(listItemRect);
selectInput.pointerPressed = true;
const auto& selectedFrame = runtime.Update(selectInput);
ASSERT_TRUE(selectedFrame.stats.documentsReady);
EXPECT_EQ(selectedFrame.stats.selectedElementId, "assetLighting");
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState nextInput = BuildKeyboardInputState();
nextInput.navigateNext = true;
const auto& nextFrame = runtime.Update(nextInput);
ASSERT_TRUE(nextFrame.stats.documentsReady);
EXPECT_EQ(nextFrame.stats.selectedElementId, "assetMaterials");
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState endInput = BuildKeyboardInputState();
endInput.navigateEnd = true;
const auto& endFrame = runtime.Update(endInput);
ASSERT_TRUE(endFrame.stats.documentsReady);
ASSERT_FALSE(endFrame.stats.selectedElementId.empty());
EXPECT_NE(endFrame.stats.selectedElementId, "assetLighting");
const std::string lastListSelection = endFrame.stats.selectedElementId;
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState nextAtEndInput = BuildKeyboardInputState();
nextAtEndInput.navigateNext = true;
const auto& nextAtEndFrame = runtime.Update(nextAtEndInput);
ASSERT_TRUE(nextAtEndFrame.stats.documentsReady);
EXPECT_EQ(nextAtEndFrame.stats.selectedElementId, lastListSelection);
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState homeInput = BuildKeyboardInputState();
homeInput.navigateHome = true;
const auto& homeFrame = runtime.Update(homeInput);
ASSERT_TRUE(homeFrame.stats.documentsReady);
EXPECT_EQ(homeFrame.stats.selectedElementId, "assetLighting");
}
TEST(NewEditorXCUILayoutLabRuntimeTest, KeyboardCollapseAndExpandFollowTreeHierarchy) {
XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& baseline = runtime.Update(BuildInputState());
ASSERT_TRUE(baseline.stats.documentsReady);
XCEngine::UI::UIRect treeChildRect = {};
ASSERT_TRUE(runtime.TryGetElementRect("treeScenes", treeChildRect));
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState selectInput = BuildInputState();
selectInput.pointerPosition = RectCenter(treeChildRect);
selectInput.pointerPressed = true;
const auto& selectedFrame = runtime.Update(selectInput);
ASSERT_TRUE(selectedFrame.stats.documentsReady);
EXPECT_EQ(selectedFrame.stats.selectedElementId, "treeScenes");
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState collapseToParent = BuildKeyboardInputState();
collapseToParent.navigateCollapse = true;
const auto& parentFrame = runtime.Update(collapseToParent);
ASSERT_TRUE(parentFrame.stats.documentsReady);
EXPECT_EQ(parentFrame.stats.selectedElementId, "treeAssetsRoot");
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState collapseRoot = BuildKeyboardInputState();
collapseRoot.navigateCollapse = true;
const auto& collapsedFrame = runtime.Update(collapseRoot);
ASSERT_TRUE(collapsedFrame.stats.documentsReady);
EXPECT_EQ(collapsedFrame.stats.selectedElementId, "treeAssetsRoot");
const auto& collapsedPersistedFrame = runtime.Update(BuildKeyboardInputState());
ASSERT_TRUE(collapsedPersistedFrame.stats.documentsReady);
EXPECT_EQ(collapsedPersistedFrame.stats.expandedTreeItemCount, 0u);
EXPECT_FALSE(runtime.TryGetElementRect("treeScenes", treeChildRect));
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState expandRoot = BuildKeyboardInputState();
expandRoot.navigateExpand = true;
const auto& expandedRootFrame = runtime.Update(expandRoot);
ASSERT_TRUE(expandedRootFrame.stats.documentsReady);
EXPECT_EQ(expandedRootFrame.stats.selectedElementId, "treeAssetsRoot");
const auto& expandedPersistedFrame = runtime.Update(BuildKeyboardInputState());
ASSERT_TRUE(expandedPersistedFrame.stats.documentsReady);
EXPECT_EQ(expandedPersistedFrame.stats.expandedTreeItemCount, 1u);
ASSERT_TRUE(runtime.TryGetElementRect("treeScenes", treeChildRect));
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState moveIntoChild = BuildKeyboardInputState();
moveIntoChild.navigateExpand = true;
const auto& childFrame = runtime.Update(moveIntoChild);
ASSERT_TRUE(childFrame.stats.documentsReady);
EXPECT_EQ(childFrame.stats.selectedElementId, "treeScenes");
}
TEST(NewEditorXCUILayoutLabRuntimeTest, KeyboardNavigationTraversesPropertySectionsAndFields) {
XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& baseline = runtime.Update(BuildInputState());
ASSERT_TRUE(baseline.stats.documentsReady);
XCEngine::UI::UIRect sectionRect = {};
ASSERT_TRUE(runtime.TryGetElementRect("inspectorTransform", sectionRect));
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState selectInput = BuildInputState();
selectInput.pointerPosition = XCEngine::UI::UIPoint(sectionRect.x + 18.0f, sectionRect.y + 10.0f);
selectInput.pointerPressed = true;
const auto& selectedFrame = runtime.Update(selectInput);
ASSERT_TRUE(selectedFrame.stats.documentsReady);
EXPECT_EQ(selectedFrame.stats.selectedElementId, "inspectorTransform");
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState nextSectionInput = BuildKeyboardInputState();
nextSectionInput.navigateNext = true;
const auto& nextSectionFrame = runtime.Update(nextSectionInput);
ASSERT_TRUE(nextSectionFrame.stats.documentsReady);
EXPECT_EQ(nextSectionFrame.stats.selectedElementId, "inspectorMesh");
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState previousSectionInput = BuildKeyboardInputState();
previousSectionInput.navigatePrevious = true;
const auto& previousSectionFrame = runtime.Update(previousSectionInput);
ASSERT_TRUE(previousSectionFrame.stats.documentsReady);
EXPECT_EQ(previousSectionFrame.stats.selectedElementId, "inspectorTransform");
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState expandIntoFieldsInput = BuildKeyboardInputState();
expandIntoFieldsInput.navigateExpand = true;
const auto& expandedSectionFrame = runtime.Update(expandIntoFieldsInput);
ASSERT_TRUE(expandedSectionFrame.stats.documentsReady);
EXPECT_EQ(expandedSectionFrame.stats.selectedElementId, "inspectorTransform");
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState enterFieldsInput = BuildKeyboardInputState();
enterFieldsInput.navigateExpand = true;
const auto& firstFieldFrame = runtime.Update(enterFieldsInput);
ASSERT_TRUE(firstFieldFrame.stats.documentsReady);
EXPECT_EQ(firstFieldFrame.stats.selectedElementId, "fieldPosition");
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState nextFieldInput = BuildKeyboardInputState();
nextFieldInput.navigateNext = true;
const auto& nextFieldFrame = runtime.Update(nextFieldInput);
ASSERT_TRUE(nextFieldFrame.stats.documentsReady);
EXPECT_EQ(nextFieldFrame.stats.selectedElementId, "fieldRotation");
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState collapseToSectionInput = BuildKeyboardInputState();
collapseToSectionInput.navigateCollapse = true;
const auto& collapseToSectionFrame = runtime.Update(collapseToSectionInput);
ASSERT_TRUE(collapseToSectionFrame.stats.documentsReady);
EXPECT_EQ(collapseToSectionFrame.stats.selectedElementId, "inspectorTransform");
}

View File

@@ -0,0 +1,67 @@
#include <gtest/gtest.h>
#include "XCUIBackend/NullXCUIPanelCanvasHost.h"
namespace {
using XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostBackend;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostCapabilities;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession;
TEST(NewEditorXCUIPanelCanvasHostTest, NullHostReportsExplicitBackendAndCapabilities) {
std::unique_ptr<IXCUIPanelCanvasHost> host = CreateNullXCUIPanelCanvasHost();
ASSERT_NE(host, nullptr);
EXPECT_STREQ(host->GetDebugName(), "NullXCUIPanelCanvasHost");
EXPECT_EQ(host->GetBackend(), XCUIPanelCanvasHostBackend::Null);
const XCUIPanelCanvasHostCapabilities capabilities = host->GetCapabilities();
EXPECT_FALSE(capabilities.supportsPointerHitTesting);
EXPECT_FALSE(capabilities.supportsHostedSurfaceImages);
EXPECT_FALSE(capabilities.supportsPrimitiveOverlays);
}
TEST(NewEditorXCUIPanelCanvasHostTest, NullHostBeginCanvasReturnsEmptySessionAndDrawCallsAreNoops) {
std::unique_ptr<IXCUIPanelCanvasHost> host = CreateNullXCUIPanelCanvasHost();
ASSERT_NE(host, nullptr);
XCUIPanelCanvasRequest request = {};
request.childId = "NullCanvas";
request.height = 280.0f;
request.topInset = 24.0f;
request.bordered = true;
request.showSurfaceImage = true;
request.drawPreviewFrame = true;
request.placeholderTitle = "Placeholder";
request.badgeTitle = "Badge";
const XCUIPanelCanvasSession session = host->BeginCanvas(request);
EXPECT_FALSE(session.validCanvas);
EXPECT_FALSE(session.hovered);
EXPECT_FALSE(session.windowFocused);
EXPECT_FLOAT_EQ(session.hostRect.width, 0.0f);
EXPECT_FLOAT_EQ(session.hostRect.height, 0.0f);
EXPECT_FLOAT_EQ(session.canvasRect.width, 0.0f);
EXPECT_FLOAT_EQ(session.canvasRect.height, 0.0f);
host->DrawFilledRect(
XCEngine::UI::UIRect(10.0f, 12.0f, 48.0f, 64.0f),
XCEngine::UI::UIColor(1.0f, 0.0f, 0.0f, 1.0f),
6.0f);
host->DrawOutlineRect(
XCEngine::UI::UIRect(5.0f, 6.0f, 100.0f, 40.0f),
XCEngine::UI::UIColor(0.0f, 1.0f, 0.0f, 1.0f),
2.0f,
8.0f);
host->DrawText(
XCEngine::UI::UIPoint(8.0f, 14.0f),
"Null host should ignore text draws",
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
16.0f);
host->EndCanvas();
}
} // namespace

View File

@@ -0,0 +1,221 @@
#include "XCUIBackend/XCUIShellChromeState.h"
#include <gtest/gtest.h>
namespace {
using XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds;
using XCEngine::Editor::XCUIBackend::XCUIShellChromeState;
using XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewMode;
using XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewState;
using XCEngine::Editor::XCUIBackend::XCUIShellPanelId;
using XCEngine::Editor::XCUIBackend::XCUIShellViewToggleId;
TEST(XCUIShellChromeStateTest, DefaultsMatchCurrentShellChromeConfiguration) {
XCUIShellChromeState state = {};
const auto& viewToggles = state.GetViewToggles();
EXPECT_FALSE(viewToggles.imguiDemoWindowVisible);
EXPECT_TRUE(viewToggles.nativeBackdropVisible);
EXPECT_TRUE(viewToggles.pulseAccentEnabled);
EXPECT_TRUE(viewToggles.nativeXCUIOverlayVisible);
EXPECT_TRUE(viewToggles.hostedPreviewHudVisible);
const auto* demoPanel = state.TryGetPanelState(XCUIShellPanelId::XCUIDemo);
ASSERT_NE(demoPanel, nullptr);
EXPECT_EQ(demoPanel->panelTitle, "XCUI Demo");
EXPECT_EQ(demoPanel->previewDebugName, "XCUI Demo");
EXPECT_EQ(demoPanel->previewDebugSource, "new_editor.panels.xcui_demo");
EXPECT_TRUE(demoPanel->visible);
EXPECT_TRUE(demoPanel->hostedPreviewEnabled);
EXPECT_EQ(demoPanel->previewMode, XCUIShellHostedPreviewMode::NativeOffscreen);
EXPECT_EQ(
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewState::NativeOffscreen);
EXPECT_TRUE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
EXPECT_FALSE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
const auto* layoutLabPanel = state.TryGetPanelState(XCUIShellPanelId::XCUILayoutLab);
ASSERT_NE(layoutLabPanel, nullptr);
EXPECT_EQ(layoutLabPanel->panelTitle, "XCUI Layout Lab");
EXPECT_EQ(layoutLabPanel->previewDebugName, "XCUI Layout Lab");
EXPECT_EQ(layoutLabPanel->previewDebugSource, "new_editor.panels.xcui_layout_lab");
EXPECT_TRUE(layoutLabPanel->visible);
EXPECT_TRUE(layoutLabPanel->hostedPreviewEnabled);
EXPECT_EQ(layoutLabPanel->previewMode, XCUIShellHostedPreviewMode::LegacyImGui);
EXPECT_EQ(
state.GetHostedPreviewState(XCUIShellPanelId::XCUILayoutLab),
XCUIShellHostedPreviewState::LegacyImGui);
EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUILayoutLab));
EXPECT_TRUE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUILayoutLab));
}
TEST(XCUIShellChromeStateTest, PanelVisibilityAndPreviewModeMutatorsTrackStateChanges) {
XCUIShellChromeState state = {};
EXPECT_TRUE(state.SetPanelVisible(XCUIShellPanelId::XCUIDemo, false));
EXPECT_FALSE(state.IsPanelVisible(XCUIShellPanelId::XCUIDemo));
EXPECT_FALSE(state.SetPanelVisible(XCUIShellPanelId::XCUIDemo, false));
EXPECT_TRUE(state.TogglePanelVisible(XCUIShellPanelId::XCUIDemo));
EXPECT_TRUE(state.IsPanelVisible(XCUIShellPanelId::XCUIDemo));
EXPECT_TRUE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUILayoutLab, false));
EXPECT_FALSE(state.IsHostedPreviewEnabled(XCUIShellPanelId::XCUILayoutLab));
EXPECT_FALSE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUILayoutLab, false));
EXPECT_EQ(
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
XCUIShellHostedPreviewMode::LegacyImGui);
EXPECT_TRUE(state.ToggleHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab));
EXPECT_EQ(
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
XCUIShellHostedPreviewMode::NativeOffscreen);
EXPECT_TRUE(state.SetHostedPreviewMode(
XCUIShellPanelId::XCUILayoutLab,
XCUIShellHostedPreviewMode::LegacyImGui));
EXPECT_EQ(
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
XCUIShellHostedPreviewMode::LegacyImGui);
EXPECT_FALSE(state.SetHostedPreviewMode(
XCUIShellPanelId::XCUILayoutLab,
XCUIShellHostedPreviewMode::LegacyImGui));
}
TEST(XCUIShellChromeStateTest, HostedPreviewStateSeparatesEnablementFromRequestedMode) {
XCUIShellChromeState state = {};
EXPECT_EQ(
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewState::NativeOffscreen);
EXPECT_TRUE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUIDemo, false));
EXPECT_EQ(
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewState::Disabled);
EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
EXPECT_FALSE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
EXPECT_TRUE(state.SetHostedPreviewMode(
XCUIShellPanelId::XCUIDemo,
XCUIShellHostedPreviewMode::LegacyImGui));
EXPECT_EQ(
state.GetHostedPreviewMode(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewMode::LegacyImGui);
EXPECT_EQ(
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewState::Disabled);
EXPECT_TRUE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUIDemo, true));
EXPECT_EQ(
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewState::LegacyImGui);
EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
EXPECT_TRUE(state.IsLegacyHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
}
TEST(XCUIShellChromeStateTest, ViewToggleMutatorsOnlyFlipRequestedFlags) {
XCUIShellChromeState state = {};
EXPECT_TRUE(state.SetViewToggle(XCUIShellViewToggleId::ImGuiDemoWindow, true));
EXPECT_TRUE(state.GetViewToggle(XCUIShellViewToggleId::ImGuiDemoWindow));
EXPECT_FALSE(state.SetViewToggle(XCUIShellViewToggleId::ImGuiDemoWindow, true));
EXPECT_TRUE(state.ToggleViewToggle(XCUIShellViewToggleId::HostedPreviewHud));
EXPECT_FALSE(state.GetViewToggle(XCUIShellViewToggleId::HostedPreviewHud));
EXPECT_TRUE(state.GetViewToggle(XCUIShellViewToggleId::NativeBackdrop));
EXPECT_TRUE(state.GetViewToggle(XCUIShellViewToggleId::PulseAccent));
EXPECT_TRUE(state.GetViewToggle(XCUIShellViewToggleId::NativeXCUIOverlay));
}
TEST(XCUIShellChromeStateTest, CommandInterfaceTogglesShellViewAndPreviewStates) {
XCUIShellChromeState state = {};
EXPECT_TRUE(state.HasCommand(XCUIShellChromeCommandIds::ToggleXCUIDemoPanel));
EXPECT_TRUE(state.HasCommand(XCUIShellChromeCommandIds::ToggleHostedPreviewHud));
EXPECT_TRUE(state.HasCommand(XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview));
EXPECT_FALSE(state.HasCommand("new_editor.view.unknown"));
EXPECT_TRUE(state.InvokeCommand(XCUIShellChromeCommandIds::ToggleXCUIDemoPanel));
EXPECT_FALSE(state.IsPanelVisible(XCUIShellPanelId::XCUIDemo));
EXPECT_TRUE(state.InvokeCommand(XCUIShellChromeCommandIds::ToggleHostedPreviewHud));
EXPECT_FALSE(state.GetViewToggle(XCUIShellViewToggleId::HostedPreviewHud));
EXPECT_TRUE(state.InvokeCommand(XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview));
EXPECT_EQ(
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
XCUIShellHostedPreviewMode::NativeOffscreen);
EXPECT_TRUE(state.InvokeCommand(XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview));
EXPECT_EQ(
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
XCUIShellHostedPreviewMode::LegacyImGui);
EXPECT_FALSE(state.InvokeCommand("new_editor.view.unknown"));
}
TEST(XCUIShellChromeStateTest, PanelCommandIdHelpersMatchCurrentShellCommands) {
EXPECT_EQ(
XCUIShellChromeState::GetPanelVisibilityCommandId(XCUIShellPanelId::XCUIDemo),
XCUIShellChromeCommandIds::ToggleXCUIDemoPanel);
EXPECT_EQ(
XCUIShellChromeState::GetPanelVisibilityCommandId(XCUIShellPanelId::XCUILayoutLab),
XCUIShellChromeCommandIds::ToggleXCUILayoutLabPanel);
EXPECT_EQ(
XCUIShellChromeState::GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUIDemo),
XCUIShellChromeCommandIds::ToggleNativeDemoPanelPreview);
EXPECT_EQ(
XCUIShellChromeState::GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUILayoutLab),
XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview);
}
TEST(XCUIShellChromeStateTest, ViewToggleCommandIdHelpersMatchCurrentShellCommands) {
EXPECT_EQ(
XCUIShellChromeState::GetViewToggleCommandId(XCUIShellViewToggleId::ImGuiDemoWindow),
XCUIShellChromeCommandIds::ToggleImGuiDemoWindow);
EXPECT_EQ(
XCUIShellChromeState::GetViewToggleCommandId(XCUIShellViewToggleId::NativeBackdrop),
XCUIShellChromeCommandIds::ToggleNativeBackdrop);
EXPECT_EQ(
XCUIShellChromeState::GetViewToggleCommandId(XCUIShellViewToggleId::PulseAccent),
XCUIShellChromeCommandIds::TogglePulseAccent);
EXPECT_EQ(
XCUIShellChromeState::GetViewToggleCommandId(XCUIShellViewToggleId::NativeXCUIOverlay),
XCUIShellChromeCommandIds::ToggleNativeXCUIOverlay);
EXPECT_EQ(
XCUIShellChromeState::GetViewToggleCommandId(XCUIShellViewToggleId::HostedPreviewHud),
XCUIShellChromeCommandIds::ToggleHostedPreviewHud);
}
TEST(XCUIShellChromeStateTest, InvalidPanelAndToggleIdsFailGracefully) {
XCUIShellChromeState state = {};
const XCUIShellPanelId invalidPanelId = static_cast<XCUIShellPanelId>(255);
const XCUIShellViewToggleId invalidToggleId = static_cast<XCUIShellViewToggleId>(255);
EXPECT_EQ(state.TryGetPanelState(invalidPanelId), nullptr);
EXPECT_FALSE(state.IsPanelVisible(invalidPanelId));
EXPECT_FALSE(state.SetPanelVisible(invalidPanelId, true));
EXPECT_FALSE(state.TogglePanelVisible(invalidPanelId));
EXPECT_FALSE(state.IsHostedPreviewEnabled(invalidPanelId));
EXPECT_FALSE(state.SetHostedPreviewEnabled(invalidPanelId, false));
EXPECT_EQ(
state.GetHostedPreviewMode(invalidPanelId),
XCUIShellHostedPreviewMode::LegacyImGui);
EXPECT_EQ(
state.GetHostedPreviewState(invalidPanelId),
XCUIShellHostedPreviewState::Disabled);
EXPECT_FALSE(state.IsNativeHostedPreviewActive(invalidPanelId));
EXPECT_FALSE(state.IsLegacyHostedPreviewActive(invalidPanelId));
EXPECT_FALSE(state.SetHostedPreviewMode(invalidPanelId, XCUIShellHostedPreviewMode::NativeOffscreen));
EXPECT_FALSE(state.ToggleHostedPreviewMode(invalidPanelId));
EXPECT_FALSE(state.GetViewToggle(invalidToggleId));
EXPECT_FALSE(state.SetViewToggle(invalidToggleId, true));
EXPECT_FALSE(state.ToggleViewToggle(invalidToggleId));
EXPECT_TRUE(XCUIShellChromeState::GetPanelVisibilityCommandId(invalidPanelId).empty());
EXPECT_TRUE(XCUIShellChromeState::GetPanelPreviewModeCommandId(invalidPanelId).empty());
EXPECT_TRUE(XCUIShellChromeState::GetViewToggleCommandId(invalidToggleId).empty());
}
} // namespace