Replace new_editor with native XCUI shell sandbox

This commit is contained in:
2026-04-05 20:46:24 +08:00
parent 18f3f9f227
commit ba6c8eaae5
108 changed files with 2041 additions and 24403 deletions

View File

@@ -9,6 +9,7 @@
#include <chrono>
#include <filesystem>
#include <fstream>
#include <limits>
#include <string>
#include <vector>
@@ -88,6 +89,70 @@ bool DrawDataContainsText(
return false;
}
std::vector<XCEngine::UI::UIRect> CollectFilledRects(
const XCEngine::UI::UIDrawData& drawData) {
std::vector<XCEngine::UI::UIRect> rects = {};
for (const XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
for (const XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) {
if (command.type == XCEngine::UI::UIDrawCommandType::FilledRect) {
rects.push_back(command.rect);
}
}
}
return rects;
}
const XCEngine::UI::UIDrawCommand* FindTextCommand(
const XCEngine::UI::UIDrawData& drawData,
const std::string& text) {
for (const XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
for (const XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) {
if (command.type == XCEngine::UI::UIDrawCommandType::Text &&
command.text == text) {
return &command;
}
}
}
return nullptr;
}
bool RectContainsPoint(
const XCEngine::UI::UIRect& rect,
const XCEngine::UI::UIPoint& point) {
return point.x >= rect.x &&
point.x <= rect.x + rect.width &&
point.y >= rect.y &&
point.y <= rect.y + rect.height;
}
bool TryFindSmallestFilledRectContainingPoint(
const XCEngine::UI::UIDrawData& drawData,
const XCEngine::UI::UIPoint& point,
XCEngine::UI::UIRect& outRect) {
bool found = false;
float bestArea = (std::numeric_limits<float>::max)();
for (const XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
for (const XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) {
if (command.type != XCEngine::UI::UIDrawCommandType::FilledRect ||
!RectContainsPoint(command.rect, point)) {
continue;
}
const float area = command.rect.width * command.rect.height;
if (!found || area < bestArea) {
outRect = command.rect;
bestArea = area;
found = true;
}
}
}
return found;
}
UIScreenFrameInput BuildInputState(std::uint64_t frameIndex = 1u) {
UIScreenFrameInput input = {};
input.viewportRect = XCEngine::UI::UIRect(0.0f, 0.0f, 800.0f, 480.0f);
@@ -181,6 +246,68 @@ TEST(UIRuntimeTest, ScreenPlayerBuildsDrawDataFromDocumentTree) {
EXPECT_EQ(player.GetPresentedFrameCount(), 1u);
}
TEST(UIRuntimeTest, DocumentHostStretchesColumnChildrenAcrossCrossAxis) {
TempFileScope viewFile(
"xcui_runtime_stretch_column",
".xcui",
"<View name=\"Stretch Column\">\n"
" <Column padding=\"18\" gap=\"10\">\n"
" <Button text=\"Wide Button\" />\n"
" </Column>\n"
"</View>\n");
UIDocumentScreenHost host = {};
UIScreenPlayer player(host);
ASSERT_TRUE(player.Load(BuildScreenAsset(viewFile.Path(), "runtime.stretch.column")));
UIScreenFrameInput input = BuildInputState();
input.viewportRect = XCEngine::UI::UIRect(0.0f, 0.0f, 400.0f, 220.0f);
const auto& frame = player.Update(input);
const auto* buttonText = FindTextCommand(frame.drawData, "Wide Button");
ASSERT_NE(buttonText, nullptr);
XCEngine::UI::UIRect buttonRect = {};
ASSERT_TRUE(TryFindSmallestFilledRectContainingPoint(frame.drawData, buttonText->position, buttonRect));
EXPECT_FLOAT_EQ(buttonRect.x, 34.0f);
EXPECT_FLOAT_EQ(buttonRect.width, 332.0f);
}
TEST(UIRuntimeTest, DocumentHostDoesNotLetExplicitHeightCrushCardContent) {
TempFileScope viewFile(
"xcui_runtime_card_height_floor",
".xcui",
"<View name=\"Card Height Floor\">\n"
" <Column padding=\"18\" gap=\"10\">\n"
" <Card title=\"Hero\" subtitle=\"Subtitle\" height=\"32\">\n"
" <Row gap=\"10\">\n"
" <Button text=\"Action\" />\n"
" </Row>\n"
" </Card>\n"
" </Column>\n"
"</View>\n");
UIDocumentScreenHost host = {};
UIScreenPlayer player(host);
ASSERT_TRUE(player.Load(BuildScreenAsset(viewFile.Path(), "runtime.card.height.floor")));
UIScreenFrameInput input = BuildInputState();
input.viewportRect = XCEngine::UI::UIRect(0.0f, 0.0f, 500.0f, 300.0f);
const auto& frame = player.Update(input);
const auto* heroTitle = FindTextCommand(frame.drawData, "Hero");
const auto* actionText = FindTextCommand(frame.drawData, "Action");
ASSERT_NE(heroTitle, nullptr);
ASSERT_NE(actionText, nullptr);
XCEngine::UI::UIRect cardRect = {};
XCEngine::UI::UIRect buttonRect = {};
ASSERT_TRUE(TryFindSmallestFilledRectContainingPoint(frame.drawData, heroTitle->position, cardRect));
ASSERT_TRUE(TryFindSmallestFilledRectContainingPoint(frame.drawData, actionText->position, buttonRect));
EXPECT_GT(cardRect.height, 32.0f);
EXPECT_GE(buttonRect.y, cardRect.y + 50.0f);
EXPECT_LE(buttonRect.y + buttonRect.height, cardRect.y + cardRect.height - 8.0f);
}
TEST(UIRuntimeTest, ScreenPlayerConsumeLastFrameReturnsDetachedPacketAndClearsBorrowedState) {
TempFileScope viewFile("xcui_runtime_consume_player", ".xcui", BuildViewMarkup("Runtime Consume"));
UIDocumentScreenHost host = {};

View File

@@ -1,961 +1,30 @@
cmake_minimum_required(VERSION 3.15)
project(XCEngine_NewEditorTests)
file(TO_CMAKE_PATH "${CMAKE_SOURCE_DIR}" XCENGINE_TEST_REPO_ROOT_CMAKE)
function(xcengine_configure_new_editor_test_target target_name)
if(MSVC)
set_target_properties(${target_name} PROPERTIES
LINK_FLAGS "/NODEFAULTLIB:libcpmt.lib /NODEFAULTLIB:libcmt.lib"
COMPILE_PDB_NAME "${target_name}-compile"
COMPILE_PDB_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb"
COMPILE_PDB_OUTPUT_DIRECTORY_DEBUG "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/Debug"
COMPILE_PDB_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/Release"
COMPILE_PDB_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/MinSizeRel"
COMPILE_PDB_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/RelWithDebInfo"
VS_GLOBAL_UseMultiToolTask "false"
)
target_compile_options(${target_name} PRIVATE /FS /utf-8)
target_link_options(${target_name} PRIVATE
$<$<CONFIG:Debug,RelWithDebInfo>:/INCREMENTAL:NO>)
endif()
endfunction()
function(xcengine_discover_new_editor_gtests target_name)
include(GoogleTest)
gtest_discover_tests(${target_name}
DISCOVERY_MODE PRE_TEST)
endfunction()
set(NEW_EDITOR_RUNTIME_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIDemoRuntime.h
)
set(NEW_EDITOR_RUNTIME_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIDemoRuntime.cpp
)
set(NEW_EDITOR_LAYOUT_LAB_RUNTIME_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUILayoutLabRuntime.h
)
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_DEMO_PANEL_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/panels/XCUIDemoPanel.h
)
set(NEW_EDITOR_DEMO_PANEL_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/panels/XCUIDemoPanel.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
)
set(NEW_EDITOR_ASSET_DOCUMENT_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIAssetDocumentSource.cpp
)
set(NEW_EDITOR_UI_DOCUMENT_COMPILER_SOURCE
${CMAKE_SOURCE_DIR}/engine/src/Resources/UI/UIDocumentCompiler.cpp
)
set(NEW_EDITOR_BACKEND_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/ImGuiTransitionBackend.h
)
set(NEW_EDITOR_INPUT_BRIDGE_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIInputBridge.h
)
set(NEW_EDITOR_INPUT_BRIDGE_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIInputBridge.cpp
)
set(NEW_EDITOR_COMMAND_ROUTER_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIEditorCommandRouter.h
)
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_NATIVE_SHELL_LAYOUT_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUINativeShellLayout.h
)
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
)
set(NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/ImGuiXCUIInputAdapter.cpp
)
set(NEW_EDITOR_RHI_COMMAND_SUPPORT_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIRHICommandSupport.h
)
set(NEW_EDITOR_RHI_COMMAND_COMPILER_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIRHICommandCompiler.h
)
set(NEW_EDITOR_RHI_COMMAND_COMPILER_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIRHICommandCompiler.cpp
)
set(NEW_EDITOR_RHI_RENDER_BACKEND_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIRHIRenderBackend.h
)
set(NEW_EDITOR_RHI_RENDER_BACKEND_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIRHIRenderBackend.cpp
)
set(NEW_EDITOR_IMGUI_TEXT_ATLAS_PROVIDER_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/ImGuiTextAtlasProvider.h
)
set(NEW_EDITOR_IMGUI_TEXT_ATLAS_PROVIDER_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/ImGuiTextAtlasProvider.cpp
)
set(NEW_EDITOR_IMGUI_HOST_COMPOSITOR_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/ImGuiHostCompositor.cpp
)
set(NEW_EDITOR_FONT_SETUP_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIEditorFontSetup.h
)
set(NEW_EDITOR_FONT_SETUP_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIEditorFontSetup.cpp
)
set(NEW_EDITOR_STANDALONE_TEXT_ATLAS_PROVIDER_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIStandaloneTextAtlasProvider.h
)
set(NEW_EDITOR_STANDALONE_TEXT_ATLAS_PROVIDER_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIStandaloneTextAtlasProvider.cpp
)
set(NEW_EDITOR_HOSTED_PREVIEW_PRESENTER_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/XCUIHostedPreviewPresenter.h
)
set(NEW_EDITOR_LEGACY_IMGUI_HOST_INTEROP_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/LegacyImGuiHostInterop.h
)
set(NEW_EDITOR_LEGACY_IMGUI_HOST_INTEROP_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/LegacyImGuiHostInterop.cpp
)
set(NEW_EDITOR_WINDOW_UI_COMPOSITOR_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/IWindowUICompositor.h
)
set(NEW_EDITOR_IMGUI_WINDOW_UI_COMPOSITOR_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/ImGuiWindowUICompositor.h
)
set(NEW_EDITOR_NATIVE_WINDOW_UI_COMPOSITOR_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/NativeWindowUICompositor.h
)
set(NEW_EDITOR_NATIVE_WINDOW_UI_COMPOSITOR_SOURCE
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/NativeWindowUICompositor.cpp
)
set(NEW_EDITOR_NATIVE_BACKDROP_RENDERER_HEADER
${CMAKE_SOURCE_DIR}/new_editor/src/Rendering/MainWindowNativeBackdropRenderer.h
set(NEW_EDITOR_TEST_SOURCES
test_sandbox_frame_builder.cpp
test_structured_editor_shell.cpp
)
if(EXISTS "${NEW_EDITOR_RUNTIME_HEADER}" AND
EXISTS "${NEW_EDITOR_RUNTIME_SOURCE}" AND
EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE_HEADER}" AND
EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}")
add_executable(new_editor_xcui_demo_runtime_tests
test_xcui_demo_runtime.cpp
${NEW_EDITOR_RUNTIME_SOURCE}
${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}
)
add_executable(new_editor_tests ${NEW_EDITOR_TEST_SOURCES})
xcengine_configure_new_editor_test_target(new_editor_xcui_demo_runtime_tests)
target_link_libraries(new_editor_tests PRIVATE
XCNewEditorLib
GTest::gtest_main
)
target_link_libraries(new_editor_xcui_demo_runtime_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
)
target_include_directories(new_editor_tests PRIVATE
${CMAKE_SOURCE_DIR}/new_editor/src
${CMAKE_SOURCE_DIR}/engine/include
)
target_include_directories(new_editor_xcui_demo_runtime_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
)
target_compile_definitions(new_editor_xcui_demo_runtime_tests PRIVATE
XCENGINE_NEW_EDITOR_REPO_ROOT="${XCENGINE_TEST_REPO_ROOT_CMAKE}"
)
file(TO_CMAKE_PATH "${CMAKE_SOURCE_DIR}" XCNEWEDITOR_REPO_ROOT_PATH)
xcengine_discover_new_editor_gtests(new_editor_xcui_demo_runtime_tests)
else()
message(STATUS "Skipping new_editor_xcui_demo_runtime_tests because XCUIDemoRuntime files are missing.")
target_compile_definitions(new_editor_tests PRIVATE
XCNEWEDITOR_REPO_ROOT="${XCNEWEDITOR_REPO_ROOT_PATH}"
)
if(MSVC)
target_compile_options(new_editor_tests PRIVATE /utf-8 /FS)
set_property(TARGET new_editor_tests PROPERTY
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
endif()
if(EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE_HEADER}" AND
EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}" AND
EXISTS "${NEW_EDITOR_UI_DOCUMENT_COMPILER_SOURCE}" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_xcui_asset_document_source.cpp")
add_executable(new_editor_xcui_asset_document_source_tests
test_xcui_asset_document_source.cpp
${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}
${NEW_EDITOR_UI_DOCUMENT_COMPILER_SOURCE}
)
xcengine_configure_new_editor_test_target(new_editor_xcui_asset_document_source_tests)
target_link_libraries(new_editor_xcui_asset_document_source_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
)
target_include_directories(new_editor_xcui_asset_document_source_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
)
target_compile_definitions(new_editor_xcui_asset_document_source_tests PRIVATE
XCENGINE_NEW_EDITOR_REPO_ROOT="${XCENGINE_TEST_REPO_ROOT_CMAKE}"
)
include(GoogleTest)
gtest_discover_tests(new_editor_xcui_asset_document_source_tests)
else()
message(STATUS "Skipping new_editor_xcui_asset_document_source_tests because asset document source files or the test source are missing.")
endif()
if(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}")
add_executable(new_editor_xcui_layout_lab_runtime_tests
test_xcui_layout_lab_runtime.cpp
${NEW_EDITOR_LAYOUT_LAB_RUNTIME_SOURCE}
${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}
)
xcengine_configure_new_editor_test_target(new_editor_xcui_layout_lab_runtime_tests)
target_link_libraries(new_editor_xcui_layout_lab_runtime_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
)
target_include_directories(new_editor_xcui_layout_lab_runtime_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
)
target_compile_definitions(new_editor_xcui_layout_lab_runtime_tests PRIVATE
XCENGINE_NEW_EDITOR_REPO_ROOT="${XCENGINE_TEST_REPO_ROOT_CMAKE}"
)
xcengine_discover_new_editor_gtests(new_editor_xcui_layout_lab_runtime_tests)
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 "${NEW_EDITOR_INPUT_BRIDGE_HEADER}" AND
EXISTS "${NEW_EDITOR_INPUT_BRIDGE_SOURCE}" AND
EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_HEADER}" AND
EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_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}
${NEW_EDITOR_INPUT_BRIDGE_SOURCE}
${NEW_EDITOR_IMGUI_INPUT_ADAPTER_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, input bridge, or ImGui sources are missing.")
endif()
if(EXISTS "${NEW_EDITOR_DEMO_PANEL_HEADER}" AND
EXISTS "${NEW_EDITOR_DEMO_PANEL_SOURCE}" AND
EXISTS "${NEW_EDITOR_RUNTIME_HEADER}" AND
EXISTS "${NEW_EDITOR_RUNTIME_SOURCE}" AND
EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE_HEADER}" AND
EXISTS "${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}" AND
EXISTS "${NEW_EDITOR_INPUT_BRIDGE_HEADER}" AND
EXISTS "${NEW_EDITOR_INPUT_BRIDGE_SOURCE}" AND
EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_HEADER}" AND
EXISTS "${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE}" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_xcui_demo_panel.cpp" AND
EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp")
add_executable(new_editor_xcui_demo_panel_tests
test_xcui_demo_panel.cpp
${NEW_EDITOR_DEMO_PANEL_SOURCE}
${NEW_EDITOR_BASE_PANEL_SOURCE}
${NEW_EDITOR_RUNTIME_SOURCE}
${NEW_EDITOR_ASSET_DOCUMENT_SOURCE}
${NEW_EDITOR_INPUT_BRIDGE_SOURCE}
${NEW_EDITOR_IMGUI_INPUT_ADAPTER_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_demo_panel_tests)
target_link_libraries(new_editor_xcui_demo_panel_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
user32
comdlg32
)
target_include_directories(new_editor_xcui_demo_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_demo_panel_tests PRIVATE
XCENGINE_NEW_EDITOR_REPO_ROOT="${XCENGINE_TEST_REPO_ROOT_CMAKE}"
)
xcengine_discover_new_editor_gtests(new_editor_xcui_demo_panel_tests)
else()
message(STATUS "Skipping new_editor_xcui_demo_panel_tests because panel, runtime, test, input bridge, 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
${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_transition_backend_tests)
target_link_libraries(new_editor_imgui_transition_backend_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
user32
comdlg32
)
target_include_directories(new_editor_imgui_transition_backend_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_transition_backend_tests)
else()
message(STATUS "Skipping new_editor_imgui_transition_backend_tests because backend or ImGui sources are missing.")
endif()
if(EXISTS "${NEW_EDITOR_HOSTED_PREVIEW_PRESENTER_HEADER}" AND EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp")
add_executable(new_editor_xcui_hosted_preview_presenter_tests
test_xcui_hosted_preview_presenter.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_xcui_hosted_preview_presenter_tests)
target_link_libraries(new_editor_xcui_hosted_preview_presenter_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
user32
comdlg32
)
target_include_directories(new_editor_xcui_hosted_preview_presenter_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_xcui_hosted_preview_presenter_tests)
else()
message(STATUS "Skipping new_editor_xcui_hosted_preview_presenter_tests because presenter header or ImGui sources are missing.")
endif()
if(EXISTS "${NEW_EDITOR_LEGACY_IMGUI_HOST_INTEROP_HEADER}" AND
EXISTS "${NEW_EDITOR_LEGACY_IMGUI_HOST_INTEROP_SOURCE}" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_legacy_imgui_host_interop.cpp" AND
EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp")
add_executable(new_editor_legacy_imgui_host_interop_tests
test_legacy_imgui_host_interop.cpp
${NEW_EDITOR_LEGACY_IMGUI_HOST_INTEROP_SOURCE}
${NEW_EDITOR_IMGUI_HOST_COMPOSITOR_SOURCE}
${NEW_EDITOR_FONT_SETUP_SOURCE}
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui_demo.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
${CMAKE_BINARY_DIR}/_deps/imgui-src/backends/imgui_impl_win32.cpp
${CMAKE_BINARY_DIR}/_deps/imgui-src/backends/imgui_impl_dx12.cpp
)
xcengine_configure_new_editor_test_target(new_editor_legacy_imgui_host_interop_tests)
target_link_libraries(new_editor_legacy_imgui_host_interop_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
d3d12
dxgi
user32
gdi32
comdlg32
)
target_include_directories(new_editor_legacy_imgui_host_interop_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_legacy_imgui_host_interop_tests)
else()
message(STATUS "Skipping new_editor_legacy_imgui_host_interop_tests because helper, test source, or ImGui sources are missing.")
endif()
if(EXISTS "${NEW_EDITOR_WINDOW_UI_COMPOSITOR_HEADER}" AND
EXISTS "${NEW_EDITOR_IMGUI_WINDOW_UI_COMPOSITOR_HEADER}" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_imgui_window_ui_compositor.cpp")
add_executable(new_editor_imgui_window_ui_compositor_tests
test_imgui_window_ui_compositor.cpp
)
xcengine_configure_new_editor_test_target(new_editor_imgui_window_ui_compositor_tests)
target_link_libraries(new_editor_imgui_window_ui_compositor_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
user32
comdlg32
)
target_include_directories(new_editor_imgui_window_ui_compositor_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_imgui_window_ui_compositor_tests)
else()
message(STATUS "Skipping new_editor_imgui_window_ui_compositor_tests because compositor headers or the test source are missing.")
endif()
if(EXISTS "${NEW_EDITOR_WINDOW_UI_COMPOSITOR_HEADER}" AND
EXISTS "${NEW_EDITOR_NATIVE_WINDOW_UI_COMPOSITOR_HEADER}" AND
EXISTS "${NEW_EDITOR_NATIVE_WINDOW_UI_COMPOSITOR_SOURCE}" AND
EXISTS "${NEW_EDITOR_RHI_RENDER_BACKEND_SOURCE}" AND
EXISTS "${NEW_EDITOR_RHI_COMMAND_COMPILER_SOURCE}" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_native_window_ui_compositor.cpp")
add_executable(new_editor_native_window_ui_compositor_tests
test_native_window_ui_compositor.cpp
${NEW_EDITOR_NATIVE_WINDOW_UI_COMPOSITOR_SOURCE}
${NEW_EDITOR_RHI_RENDER_BACKEND_SOURCE}
${NEW_EDITOR_RHI_COMMAND_COMPILER_SOURCE}
)
xcengine_configure_new_editor_test_target(new_editor_native_window_ui_compositor_tests)
target_link_libraries(new_editor_native_window_ui_compositor_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
user32
comdlg32
)
target_include_directories(new_editor_native_window_ui_compositor_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
)
xcengine_discover_new_editor_gtests(new_editor_native_window_ui_compositor_tests)
else()
message(STATUS "Skipping new_editor_native_window_ui_compositor_tests because native compositor/backend files or the test source are missing.")
endif()
if(EXISTS "${NEW_EDITOR_NATIVE_BACKDROP_RENDERER_HEADER}")
add_executable(new_editor_native_backdrop_renderer_api_tests
test_main_window_native_backdrop_renderer_api.cpp
)
xcengine_configure_new_editor_test_target(new_editor_native_backdrop_renderer_api_tests)
target_link_libraries(new_editor_native_backdrop_renderer_api_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
)
target_include_directories(new_editor_native_backdrop_renderer_api_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
${CMAKE_BINARY_DIR}/_deps/imgui-src
)
xcengine_discover_new_editor_gtests(new_editor_native_backdrop_renderer_api_tests)
else()
message(STATUS "Skipping new_editor_native_backdrop_renderer_api_tests because renderer header is missing.")
endif()
if(EXISTS "${NEW_EDITOR_INPUT_BRIDGE_HEADER}" AND
EXISTS "${NEW_EDITOR_INPUT_BRIDGE_SOURCE}")
add_executable(new_editor_xcui_input_bridge_tests
test_xcui_input_bridge.cpp
${NEW_EDITOR_INPUT_BRIDGE_SOURCE}
)
xcengine_configure_new_editor_test_target(new_editor_xcui_input_bridge_tests)
target_link_libraries(new_editor_xcui_input_bridge_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
)
target_include_directories(new_editor_xcui_input_bridge_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_xcui_input_bridge_tests)
else()
message(STATUS "Skipping new_editor_xcui_input_bridge_tests because input bridge files are missing.")
endif()
if(EXISTS "${NEW_EDITOR_COMMAND_ROUTER_HEADER}" AND
EXISTS "${NEW_EDITOR_COMMAND_ROUTER_SOURCE}" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_xcui_editor_command_router.cpp")
add_executable(new_editor_xcui_editor_command_router_tests
test_xcui_editor_command_router.cpp
${NEW_EDITOR_COMMAND_ROUTER_SOURCE}
)
xcengine_configure_new_editor_test_target(new_editor_xcui_editor_command_router_tests)
target_link_libraries(new_editor_xcui_editor_command_router_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
)
target_include_directories(new_editor_xcui_editor_command_router_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
)
xcengine_discover_new_editor_gtests(new_editor_xcui_editor_command_router_tests)
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_NATIVE_SHELL_LAYOUT_HEADER}" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_xcui_native_shell_layout.cpp")
add_executable(new_editor_xcui_native_shell_layout_tests
test_xcui_native_shell_layout.cpp
)
xcengine_configure_new_editor_test_target(new_editor_xcui_native_shell_layout_tests)
target_link_libraries(new_editor_xcui_native_shell_layout_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
)
target_include_directories(new_editor_xcui_native_shell_layout_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
)
xcengine_discover_new_editor_gtests(new_editor_xcui_native_shell_layout_tests)
else()
message(STATUS "Skipping new_editor_xcui_native_shell_layout_tests because the layout helper header or the test source is 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
)
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 "${NEW_EDITOR_INPUT_BRIDGE_HEADER}" AND
EXISTS "${NEW_EDITOR_INPUT_BRIDGE_SOURCE}" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_imgui_xcui_input_adapter.cpp" AND
EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp")
add_executable(new_editor_imgui_xcui_input_adapter_tests
test_imgui_xcui_input_adapter.cpp
${NEW_EDITOR_IMGUI_INPUT_ADAPTER_SOURCE}
${NEW_EDITOR_INPUT_BRIDGE_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_imgui_xcui_input_adapter_tests)
target_link_libraries(new_editor_imgui_xcui_input_adapter_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
user32
comdlg32
)
target_include_directories(new_editor_imgui_xcui_input_adapter_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_input_adapter_tests)
else()
message(STATUS "Skipping new_editor_imgui_xcui_input_adapter_tests because ImGui adapter files, input bridge files, test source, or ImGui sources are missing.")
endif()
if(EXISTS "${NEW_EDITOR_RHI_COMMAND_SUPPORT_HEADER}")
add_executable(new_editor_xcui_rhi_command_support_tests
test_xcui_rhi_command_support.cpp
)
xcengine_configure_new_editor_test_target(new_editor_xcui_rhi_command_support_tests)
target_link_libraries(new_editor_xcui_rhi_command_support_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
)
target_include_directories(new_editor_xcui_rhi_command_support_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
)
xcengine_discover_new_editor_gtests(new_editor_xcui_rhi_command_support_tests)
else()
message(STATUS "Skipping new_editor_xcui_rhi_command_support_tests because support helper header is missing.")
endif()
if(EXISTS "${NEW_EDITOR_RHI_RENDER_BACKEND_HEADER}" AND
EXISTS "${NEW_EDITOR_RHI_RENDER_BACKEND_SOURCE}" AND
EXISTS "${NEW_EDITOR_RHI_COMMAND_COMPILER_HEADER}" AND
EXISTS "${NEW_EDITOR_RHI_COMMAND_COMPILER_SOURCE}" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_xcui_rhi_render_backend.cpp")
add_executable(new_editor_xcui_rhi_render_backend_tests
test_xcui_rhi_render_backend.cpp
${NEW_EDITOR_RHI_RENDER_BACKEND_SOURCE}
${NEW_EDITOR_RHI_COMMAND_COMPILER_SOURCE}
)
xcengine_configure_new_editor_test_target(new_editor_xcui_rhi_render_backend_tests)
target_link_libraries(new_editor_xcui_rhi_render_backend_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
)
target_include_directories(new_editor_xcui_rhi_render_backend_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
)
xcengine_discover_new_editor_gtests(new_editor_xcui_rhi_render_backend_tests)
else()
message(STATUS "Skipping new_editor_xcui_rhi_render_backend_tests because backend/compiler sources or the test source are missing.")
endif()
if(EXISTS "${NEW_EDITOR_IMGUI_TEXT_ATLAS_PROVIDER_HEADER}" AND
EXISTS "${NEW_EDITOR_IMGUI_TEXT_ATLAS_PROVIDER_SOURCE}" AND
EXISTS "${CMAKE_BINARY_DIR}/_deps/imgui-src/imgui.cpp")
add_executable(new_editor_imgui_text_atlas_provider_tests
test_imgui_text_atlas_provider.cpp
${NEW_EDITOR_IMGUI_TEXT_ATLAS_PROVIDER_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_imgui_text_atlas_provider_tests)
target_link_libraries(new_editor_imgui_text_atlas_provider_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
user32
comdlg32
)
target_include_directories(new_editor_imgui_text_atlas_provider_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_text_atlas_provider_tests)
else()
message(STATUS "Skipping new_editor_imgui_text_atlas_provider_tests because atlas provider or ImGui sources are missing.")
endif()
if(EXISTS "${NEW_EDITOR_STANDALONE_TEXT_ATLAS_PROVIDER_HEADER}" AND
EXISTS "${NEW_EDITOR_STANDALONE_TEXT_ATLAS_PROVIDER_SOURCE}")
add_executable(new_editor_xcui_standalone_text_atlas_provider_tests
test_xcui_standalone_text_atlas_provider.cpp
${NEW_EDITOR_STANDALONE_TEXT_ATLAS_PROVIDER_SOURCE}
)
xcengine_configure_new_editor_test_target(new_editor_xcui_standalone_text_atlas_provider_tests)
target_link_libraries(new_editor_xcui_standalone_text_atlas_provider_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
user32
gdi32
comdlg32
)
target_include_directories(new_editor_xcui_standalone_text_atlas_provider_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
)
xcengine_discover_new_editor_gtests(new_editor_xcui_standalone_text_atlas_provider_tests)
else()
message(STATUS "Skipping new_editor_xcui_standalone_text_atlas_provider_tests because standalone atlas provider files are missing.")
endif()
if(EXISTS "${NEW_EDITOR_RHI_COMMAND_COMPILER_HEADER}" AND
EXISTS "${NEW_EDITOR_RHI_COMMAND_COMPILER_SOURCE}")
add_executable(new_editor_xcui_rhi_command_compiler_tests
test_xcui_rhi_command_compiler.cpp
${NEW_EDITOR_RHI_COMMAND_COMPILER_SOURCE}
)
xcengine_configure_new_editor_test_target(new_editor_xcui_rhi_command_compiler_tests)
target_link_libraries(new_editor_xcui_rhi_command_compiler_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
)
target_include_directories(new_editor_xcui_rhi_command_compiler_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
)
xcengine_discover_new_editor_gtests(new_editor_xcui_rhi_command_compiler_tests)
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()
if(EXISTS "${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/NativeXCUIPanelCanvasHost.h" AND
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_native_xcui_panel_canvas_host.cpp")
add_executable(new_editor_native_xcui_panel_canvas_host_tests
test_native_xcui_panel_canvas_host.cpp
)
xcengine_configure_new_editor_test_target(new_editor_native_xcui_panel_canvas_host_tests)
target_link_libraries(new_editor_native_xcui_panel_canvas_host_tests
PRIVATE
XCEngine
GTest::gtest
GTest::gtest_main
)
target_include_directories(new_editor_native_xcui_panel_canvas_host_tests PRIVATE
${CMAKE_SOURCE_DIR}/engine/include
${CMAKE_SOURCE_DIR}/new_editor/src
)
xcengine_discover_new_editor_gtests(new_editor_native_xcui_panel_canvas_host_tests)
else()
message(STATUS "Skipping new_editor_native_xcui_panel_canvas_host_tests because the native host header or test source is missing.")
endif()
add_test(NAME new_editor_tests COMMAND new_editor_tests)

View File

@@ -1,399 +0,0 @@
#include "Application.h"
#include <XCEngine/Input/InputTypes.h>
#include <gtest/gtest.h>
namespace {
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandRouter;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewQueue;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceRegistry;
using XCEngine::Editor::XCUIBackend::UITextureRegistration;
using XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta;
using XCEngine::Input::KeyCode;
using XCEngine::NewEditor::Application;
using XCEngine::UI::UIColor;
using XCEngine::UI::UIDrawData;
using XCEngine::UI::UIDrawList;
using XCEngine::UI::UIRect;
using XCEngine::UI::UISize;
using XCEngine::UI::UITextureHandle;
using XCEngine::UI::UITextureHandleKind;
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::HostedPresenter
};
}
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) {
panels[ToPanelIndex(Application::ShellPanelId::XCUIDemo)].visible = visible;
};
bindings.getXCUILayoutLabPanelVisible = [this]() {
return Panel(Application::ShellPanelId::XCUILayoutLab).visible;
};
bindings.setXCUILayoutLabPanelVisible = [this](bool visible) {
panels[ToPanelIndex(Application::ShellPanelId::XCUILayoutLab)].visible = 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) {
panels[ToPanelIndex(Application::ShellPanelId::XCUIDemo)].previewMode =
enabled
? Application::ShellHostedPreviewMode::NativeOffscreen
: Application::ShellHostedPreviewMode::HostedPresenter;
};
bindings.getNativeLayoutLabPreviewEnabled = [this]() {
return Panel(Application::ShellPanelId::XCUILayoutLab).previewMode ==
Application::ShellHostedPreviewMode::NativeOffscreen;
};
bindings.setNativeLayoutLabPreviewEnabled = [this](bool enabled) {
panels[ToPanelIndex(Application::ShellPanelId::XCUILayoutLab)].previewMode =
enabled
? Application::ShellHostedPreviewMode::NativeOffscreen
: Application::ShellHostedPreviewMode::HostedPresenter;
};
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::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::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::HostedPresenter);
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::HostedPresenter);
EXPECT_EQ(harness.hostedPreviewReconfigureCount, 1);
}
TEST(ApplicationShellCommandBindingsTest, BeginHostedPreviewFrameLifecycleClearsQueueAndResetsRegistryFlags) {
XCUIHostedPreviewQueue queue = {};
XCUIHostedPreviewSurfaceRegistry registry = {};
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("HostedPreview");
drawList.AddFilledRect(UIRect(4.0f, 6.0f, 24.0f, 16.0f), UIColor(1.0f, 1.0f, 1.0f, 1.0f));
XCUIHostedPreviewFrame frame = {};
frame.drawData = &drawData;
frame.canvasRect = UIRect(0.0f, 0.0f, 160.0f, 90.0f);
frame.logicalSize = UISize(160.0f, 90.0f);
frame.debugName = "HostedPreview";
frame.debugSource = "tests.application";
ASSERT_TRUE(queue.Submit(frame));
ASSERT_EQ(queue.GetQueuedFrames().size(), 1u);
registry.RecordQueuedFrame(queue.GetQueuedFrames().front(), 0u);
ASSERT_EQ(registry.GetDescriptors().size(), 1u);
EXPECT_TRUE(registry.GetDescriptors().front().queuedThisFrame);
Application::BeginHostedPreviewFrameLifecycle(queue, registry);
EXPECT_TRUE(queue.GetQueuedFrames().empty());
ASSERT_EQ(registry.GetDescriptors().size(), 1u);
EXPECT_FALSE(registry.GetDescriptors().front().queuedThisFrame);
}
TEST(ApplicationShellCommandBindingsTest, HostedPreviewRegistrationGuardsAllowTextureOnlyNativePublication) {
UITextureRegistration registration = {};
registration.texture = UITextureHandle{ 99u, 256u, 128u, UITextureHandleKind::ShaderResourceView };
EXPECT_TRUE(Application::HasHostedPreviewTextureRegistration(registration));
EXPECT_TRUE(Application::HasHostedPreviewPublishedTexture(registration));
registration = {};
registration.cpuHandle.ptr = 11u;
registration.gpuHandle.ptr = 29u;
EXPECT_TRUE(Application::HasHostedPreviewTextureRegistration(registration));
EXPECT_FALSE(Application::HasHostedPreviewPublishedTexture(registration));
}
TEST(ApplicationShellCommandBindingsTest, NativeHostedPreviewConsumptionLeavesHostedPathInDirectAppendMode) {
const auto consumption = Application::ResolveNativeHostedPreviewConsumption(
false,
false,
false,
"Pending",
"Waiting");
EXPECT_EQ(
consumption.surfaceState,
Application::NativeHostedPreviewSurfaceState::Disabled);
EXPECT_FALSE(consumption.queueRuntimeFrame);
EXPECT_TRUE(consumption.appendRuntimeDrawDataToShell);
EXPECT_FALSE(consumption.showSurfaceImage);
EXPECT_TRUE(consumption.drawRuntimeDebugRects);
EXPECT_TRUE(consumption.placeholderTitle.empty());
EXPECT_TRUE(consumption.placeholderSubtitle.empty());
EXPECT_EQ(
Application::ComposeNativeHostedPreviewStatusLine(consumption, "Runtime status"),
"Runtime status");
}
TEST(ApplicationShellCommandBindingsTest, NativeHostedPreviewConsumptionQueuesFrameWhileAwaitingFirstSurfacePublish) {
const auto consumption = Application::ResolveNativeHostedPreviewConsumption(
true,
false,
false,
"Native XCUI preview pending",
"Waiting for queued native preview output to publish into the shell card.");
EXPECT_EQ(
consumption.surfaceState,
Application::NativeHostedPreviewSurfaceState::AwaitingSubmit);
EXPECT_TRUE(consumption.queueRuntimeFrame);
EXPECT_FALSE(consumption.appendRuntimeDrawDataToShell);
EXPECT_FALSE(consumption.showSurfaceImage);
EXPECT_FALSE(consumption.drawRuntimeDebugRects);
EXPECT_EQ(consumption.placeholderTitle, "Native XCUI preview pending");
EXPECT_EQ(
consumption.placeholderSubtitle,
"Waiting for queued native preview output to publish into the shell card.");
EXPECT_EQ(
Application::ComposeNativeHostedPreviewStatusLine(consumption, "Runtime status"),
"Native surface awaiting submit | Runtime status");
}
TEST(ApplicationShellCommandBindingsTest, NativeHostedPreviewConsumptionDistinguishesWarmingFromLiveSurfaceStates) {
const auto warming = Application::ResolveNativeHostedPreviewConsumption(
true,
true,
false,
"Native layout preview pending",
"Waiting for queued native preview output to publish into the layout card.");
const auto live = Application::ResolveNativeHostedPreviewConsumption(
true,
true,
true,
"Native layout preview pending",
"Waiting for queued native preview output to publish into the layout card.");
EXPECT_EQ(
warming.surfaceState,
Application::NativeHostedPreviewSurfaceState::Warming);
EXPECT_TRUE(warming.queueRuntimeFrame);
EXPECT_FALSE(warming.appendRuntimeDrawDataToShell);
EXPECT_FALSE(warming.showSurfaceImage);
EXPECT_FALSE(warming.drawRuntimeDebugRects);
EXPECT_EQ(warming.placeholderTitle, "Native layout preview pending");
EXPECT_EQ(
Application::ComposeNativeHostedPreviewStatusLine(warming, "Layout status"),
"Native surface warming | Layout status");
EXPECT_EQ(
live.surfaceState,
Application::NativeHostedPreviewSurfaceState::Live);
EXPECT_TRUE(live.queueRuntimeFrame);
EXPECT_FALSE(live.appendRuntimeDrawDataToShell);
EXPECT_TRUE(live.showSurfaceImage);
EXPECT_TRUE(live.drawRuntimeDebugRects);
EXPECT_TRUE(live.placeholderTitle.empty());
EXPECT_TRUE(live.placeholderSubtitle.empty());
EXPECT_EQ(
Application::ComposeNativeHostedPreviewStatusLine(live, "Layout status"),
"Native surface live | Layout status");
}
} // namespace

View File

@@ -1,90 +0,0 @@
#include <gtest/gtest.h>
#include "XCUIBackend/ImGuiTextAtlasProvider.h"
#include <imgui.h>
#include <cstdint>
namespace {
using XCEngine::Editor::XCUIBackend::IXCUITextAtlasProvider;
using XCEngine::Editor::XCUIBackend::ImGuiTextAtlasProvider;
class ImGuiContextScope {
public:
ImGuiContextScope() {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui::StyleColorsDark();
}
~ImGuiContextScope() {
ImGui::DestroyContext();
}
};
void BuildDefaultFontAtlas() {
ImGuiIO& io = ImGui::GetIO();
if (io.Fonts->Fonts.empty()) {
io.Fonts->AddFontDefault();
}
unsigned char* pixels = nullptr;
int width = 0;
int height = 0;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
}
TEST(ImGuiTextAtlasProviderTest, ReturnsEmptyResultsWhenNoContextIsAvailable) {
ImGuiTextAtlasProvider provider = {};
IXCUITextAtlasProvider::AtlasTextureView atlasView = {};
IXCUITextAtlasProvider::FontInfo fontInfo = {};
IXCUITextAtlasProvider::BakedFontInfo bakedFontInfo = {};
IXCUITextAtlasProvider::GlyphInfo glyphInfo = {};
EXPECT_EQ(provider.GetContext(), nullptr);
EXPECT_FALSE(provider.GetAtlasTextureView(IXCUITextAtlasProvider::PixelFormat::RGBA32, atlasView));
EXPECT_EQ(provider.GetFontCount(), 0u);
EXPECT_FALSE(provider.GetDefaultFont().IsValid());
EXPECT_FALSE(provider.GetFontInfo({}, fontInfo));
EXPECT_FALSE(provider.GetBakedFontInfo({}, 14.0f, bakedFontInfo));
EXPECT_FALSE(provider.FindGlyph({}, 14.0f, 'A', glyphInfo));
}
TEST(ImGuiTextAtlasProviderTest, ExposesAtlasAndGlyphDataFromExplicitContext) {
ImGuiContextScope contextScope;
BuildDefaultFontAtlas();
ImGuiTextAtlasProvider provider(ImGui::GetCurrentContext());
IXCUITextAtlasProvider::AtlasTextureView atlasView = {};
ASSERT_TRUE(provider.GetAtlasTextureView(IXCUITextAtlasProvider::PixelFormat::RGBA32, atlasView));
EXPECT_TRUE(atlasView.IsValid());
EXPECT_EQ(atlasView.format, IXCUITextAtlasProvider::PixelFormat::RGBA32);
EXPECT_GT(atlasView.atlasStorageKey, 0u);
EXPECT_GT(atlasView.pixelDataKey, 0u);
const IXCUITextAtlasProvider::FontHandle defaultFont = provider.GetDefaultFont();
ASSERT_TRUE(defaultFont.IsValid());
ASSERT_GE(provider.GetFontCount(), 1u);
IXCUITextAtlasProvider::FontInfo fontInfo = {};
ASSERT_TRUE(provider.GetFontInfo(defaultFont, fontInfo));
EXPECT_TRUE(fontInfo.handle.IsValid());
EXPECT_GT(fontInfo.nominalSize, 0.0f);
IXCUITextAtlasProvider::BakedFontInfo bakedFontInfo = {};
ASSERT_TRUE(provider.GetBakedFontInfo(defaultFont, 0.0f, bakedFontInfo));
EXPECT_GT(bakedFontInfo.lineHeight, 0.0f);
EXPECT_GT(bakedFontInfo.rasterizerDensity, 0.0f);
IXCUITextAtlasProvider::GlyphInfo glyphInfo = {};
ASSERT_TRUE(provider.FindGlyph(defaultFont, 0.0f, 'A', glyphInfo));
EXPECT_EQ(glyphInfo.requestedCodepoint, static_cast<std::uint32_t>('A'));
EXPECT_GT(glyphInfo.advanceX, 0.0f);
EXPECT_GE(glyphInfo.u1, glyphInfo.u0);
EXPECT_GE(glyphInfo.v1, glyphInfo.v0);
}
} // namespace

View File

@@ -1,334 +0,0 @@
#include <gtest/gtest.h>
#include "Platform/D3D12WindowRenderer.h"
#include "XCUIBackend/IEditorHostCompositor.h"
#include "XCUIBackend/ImGuiWindowUICompositor.h"
#include "XCUIBackend/UITextureRegistration.h"
#include <array>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
namespace {
using XCEngine::Editor::Platform::D3D12WindowRenderer;
using XCEngine::Editor::XCUIBackend::IEditorHostCompositor;
using XCEngine::Editor::XCUIBackend::ImGuiWindowUICompositor;
using XCEngine::Editor::XCUIBackend::UITextureRegistration;
class RecordingHostCompositor final : public IEditorHostCompositor {
public:
bool initializeResult = true;
bool handleWindowMessageResult = false;
bool createTextureDescriptorResult = false;
bool invokeConfigureFonts = false;
int initializeCount = 0;
int shutdownCount = 0;
int beginFrameCount = 0;
int endFrameCount = 0;
int handleWindowMessageCount = 0;
int createTextureDescriptorCount = 0;
int freeTextureDescriptorCount = 0;
HWND lastHwnd = nullptr;
UINT lastMessage = 0u;
WPARAM lastWParam = 0u;
LPARAM lastLParam = 0u;
D3D12WindowRenderer* initializedRenderer = nullptr;
D3D12WindowRenderer* presentedRenderer = nullptr;
::XCEngine::RHI::RHIDevice* lastDevice = nullptr;
::XCEngine::RHI::RHITexture* lastTexture = nullptr;
bool beforeUiRenderProvided = false;
bool afterUiRenderProvided = false;
std::array<float, 4> lastClearColor = { 0.0f, 0.0f, 0.0f, 0.0f };
UITextureRegistration nextRegistration = {};
UITextureRegistration freedRegistration = {};
std::vector<std::string> callOrder = {};
bool Initialize(
HWND hwnd,
D3D12WindowRenderer& windowRenderer,
const ConfigureFontsCallback& configureFonts) override {
++initializeCount;
lastHwnd = hwnd;
initializedRenderer = &windowRenderer;
callOrder.push_back("initialize");
if (invokeConfigureFonts && configureFonts) {
configureFonts();
}
return initializeResult;
}
void Shutdown() override {
++shutdownCount;
callOrder.push_back("shutdown");
}
bool HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) override {
++handleWindowMessageCount;
lastHwnd = hwnd;
lastMessage = message;
lastWParam = wParam;
lastLParam = lParam;
callOrder.push_back("message");
return handleWindowMessageResult;
}
void BeginFrame() override {
++beginFrameCount;
callOrder.push_back("begin");
}
void EndFrameAndPresent(
D3D12WindowRenderer& windowRenderer,
const float clearColor[4],
const RenderCallback& beforeUiRender,
const RenderCallback& afterUiRender) override {
++endFrameCount;
presentedRenderer = &windowRenderer;
beforeUiRenderProvided = static_cast<bool>(beforeUiRender);
afterUiRenderProvided = static_cast<bool>(afterUiRender);
for (std::size_t index = 0; index < lastClearColor.size(); ++index) {
lastClearColor[index] = clearColor[index];
}
callOrder.push_back("present");
}
bool CreateTextureDescriptor(
::XCEngine::RHI::RHIDevice* device,
::XCEngine::RHI::RHITexture* texture,
UITextureRegistration& outRegistration) override {
++createTextureDescriptorCount;
lastDevice = device;
lastTexture = texture;
if (createTextureDescriptorResult) {
outRegistration = nextRegistration;
}
return createTextureDescriptorResult;
}
void FreeTextureDescriptor(const UITextureRegistration& registration) override {
++freeTextureDescriptorCount;
freedRegistration = registration;
}
};
HWND MakeFakeHwnd() {
return reinterpret_cast<HWND>(static_cast<std::uintptr_t>(0x1234u));
}
UITextureRegistration MakeRegistration() {
UITextureRegistration registration = {};
registration.cpuHandle.ptr = 11u;
registration.gpuHandle.ptr = 29u;
registration.texture.nativeHandle = 43u;
registration.texture.width = 256u;
registration.texture.height = 128u;
registration.texture.kind = ::XCEngine::UI::UITextureHandleKind::ShaderResourceView;
return registration;
}
TEST(ImGuiWindowUICompositorTest, InitializeForwardsToHostAndConfigureFontsCallback) {
auto host = std::make_unique<RecordingHostCompositor>();
RecordingHostCompositor* hostPtr = host.get();
hostPtr->invokeConfigureFonts = true;
ImGuiWindowUICompositor compositor(std::move(host));
D3D12WindowRenderer renderer = {};
bool configureFontsCalled = false;
EXPECT_TRUE(compositor.Initialize(
MakeFakeHwnd(),
renderer,
[&configureFontsCalled]() { configureFontsCalled = true; }));
EXPECT_TRUE(configureFontsCalled);
ASSERT_NE(hostPtr, nullptr);
EXPECT_EQ(hostPtr->initializeCount, 1);
EXPECT_EQ(hostPtr->lastHwnd, MakeFakeHwnd());
EXPECT_EQ(hostPtr->initializedRenderer, &renderer);
}
TEST(ImGuiWindowUICompositorTest, RenderFrameCallsHostBeginUiAndPresentInOrder) {
auto host = std::make_unique<RecordingHostCompositor>();
RecordingHostCompositor* hostPtr = host.get();
ImGuiWindowUICompositor compositor(std::move(host));
D3D12WindowRenderer renderer = {};
ASSERT_TRUE(compositor.Initialize(MakeFakeHwnd(), renderer, {}));
hostPtr->callOrder.clear();
bool uiRendered = false;
constexpr float clearColor[4] = { 0.1f, 0.2f, 0.3f, 0.4f };
compositor.RenderFrame(
clearColor,
[&]() {
uiRendered = true;
hostPtr->callOrder.push_back("ui");
},
[](const ::XCEngine::Rendering::RenderContext&, const ::XCEngine::Rendering::RenderSurface&) {},
[](const ::XCEngine::Rendering::RenderContext&, const ::XCEngine::Rendering::RenderSurface&) {});
EXPECT_TRUE(uiRendered);
EXPECT_EQ(hostPtr->beginFrameCount, 1);
EXPECT_EQ(hostPtr->endFrameCount, 1);
EXPECT_EQ(hostPtr->presentedRenderer, &renderer);
EXPECT_TRUE(hostPtr->beforeUiRenderProvided);
EXPECT_TRUE(hostPtr->afterUiRenderProvided);
EXPECT_EQ(hostPtr->lastClearColor[0], clearColor[0]);
EXPECT_EQ(hostPtr->lastClearColor[1], clearColor[1]);
EXPECT_EQ(hostPtr->lastClearColor[2], clearColor[2]);
EXPECT_EQ(hostPtr->lastClearColor[3], clearColor[3]);
EXPECT_EQ(
hostPtr->callOrder,
(std::vector<std::string>{ "begin", "ui", "present" }));
}
TEST(ImGuiWindowUICompositorTest, RenderFrameWithoutUiCallbackStillBeginsAndPresents) {
auto host = std::make_unique<RecordingHostCompositor>();
RecordingHostCompositor* hostPtr = host.get();
ImGuiWindowUICompositor compositor(std::move(host));
D3D12WindowRenderer renderer = {};
ASSERT_TRUE(compositor.Initialize(MakeFakeHwnd(), renderer, {}));
hostPtr->callOrder.clear();
constexpr float clearColor[4] = { 0.7f, 0.6f, 0.5f, 0.4f };
compositor.RenderFrame(clearColor, {}, {}, {});
EXPECT_EQ(hostPtr->beginFrameCount, 1);
EXPECT_EQ(hostPtr->endFrameCount, 1);
EXPECT_EQ(hostPtr->presentedRenderer, &renderer);
EXPECT_FALSE(hostPtr->beforeUiRenderProvided);
EXPECT_FALSE(hostPtr->afterUiRenderProvided);
EXPECT_EQ(hostPtr->lastClearColor[0], clearColor[0]);
EXPECT_EQ(hostPtr->lastClearColor[1], clearColor[1]);
EXPECT_EQ(hostPtr->lastClearColor[2], clearColor[2]);
EXPECT_EQ(hostPtr->lastClearColor[3], clearColor[3]);
EXPECT_EQ(
hostPtr->callOrder,
(std::vector<std::string>{ "begin", "present" }));
}
TEST(ImGuiWindowUICompositorTest, HandleWindowMessageAndTextureRegistrationForwardToHost) {
auto host = std::make_unique<RecordingHostCompositor>();
RecordingHostCompositor* hostPtr = host.get();
hostPtr->handleWindowMessageResult = true;
hostPtr->createTextureDescriptorResult = true;
hostPtr->nextRegistration = MakeRegistration();
ImGuiWindowUICompositor compositor(std::move(host));
EXPECT_TRUE(compositor.HandleWindowMessage(MakeFakeHwnd(), WM_SIZE, 7u, 19u));
EXPECT_EQ(hostPtr->handleWindowMessageCount, 1);
EXPECT_EQ(hostPtr->lastMessage, static_cast<UINT>(WM_SIZE));
EXPECT_EQ(hostPtr->lastWParam, static_cast<WPARAM>(7u));
EXPECT_EQ(hostPtr->lastLParam, static_cast<LPARAM>(19u));
UITextureRegistration registration = {};
auto* fakeDevice = reinterpret_cast<::XCEngine::RHI::RHIDevice*>(static_cast<std::uintptr_t>(0x41u));
auto* fakeTexture = reinterpret_cast<::XCEngine::RHI::RHITexture*>(static_cast<std::uintptr_t>(0x59u));
EXPECT_TRUE(compositor.CreateTextureDescriptor(fakeDevice, fakeTexture, registration));
EXPECT_EQ(hostPtr->createTextureDescriptorCount, 1);
EXPECT_EQ(hostPtr->lastDevice, fakeDevice);
EXPECT_EQ(hostPtr->lastTexture, fakeTexture);
EXPECT_EQ(registration.cpuHandle.ptr, hostPtr->nextRegistration.cpuHandle.ptr);
EXPECT_EQ(registration.gpuHandle.ptr, hostPtr->nextRegistration.gpuHandle.ptr);
EXPECT_EQ(registration.texture.nativeHandle, hostPtr->nextRegistration.texture.nativeHandle);
compositor.FreeTextureDescriptor(registration);
EXPECT_EQ(hostPtr->freeTextureDescriptorCount, 1);
EXPECT_EQ(hostPtr->freedRegistration.cpuHandle.ptr, registration.cpuHandle.ptr);
EXPECT_EQ(hostPtr->freedRegistration.gpuHandle.ptr, registration.gpuHandle.ptr);
EXPECT_EQ(hostPtr->freedRegistration.texture.nativeHandle, registration.texture.nativeHandle);
}
TEST(ImGuiWindowUICompositorTest, SecondInitializeRebindsRendererForSubsequentFrames) {
auto host = std::make_unique<RecordingHostCompositor>();
RecordingHostCompositor* hostPtr = host.get();
ImGuiWindowUICompositor compositor(std::move(host));
D3D12WindowRenderer firstRenderer = {};
D3D12WindowRenderer secondRenderer = {};
ASSERT_TRUE(compositor.Initialize(MakeFakeHwnd(), firstRenderer, {}));
ASSERT_TRUE(compositor.Initialize(MakeFakeHwnd(), secondRenderer, {}));
EXPECT_EQ(hostPtr->initializeCount, 2);
EXPECT_EQ(hostPtr->initializedRenderer, &secondRenderer);
compositor.RenderFrame(
std::array<float, 4>{ 0.2f, 0.3f, 0.4f, 1.0f }.data(),
[]() {},
{},
{});
EXPECT_EQ(hostPtr->beginFrameCount, 1);
EXPECT_EQ(hostPtr->endFrameCount, 1);
EXPECT_EQ(hostPtr->presentedRenderer, &secondRenderer);
}
TEST(ImGuiWindowUICompositorTest, ShutdownClearsRendererBindingAndPreventsFurtherRender) {
auto host = std::make_unique<RecordingHostCompositor>();
RecordingHostCompositor* hostPtr = host.get();
ImGuiWindowUICompositor compositor(std::move(host));
D3D12WindowRenderer renderer = {};
ASSERT_TRUE(compositor.Initialize(MakeFakeHwnd(), renderer, {}));
bool firstUiRendered = false;
compositor.RenderFrame(
std::array<float, 4>{ 0.0f, 0.0f, 0.0f, 1.0f }.data(),
[&]() { firstUiRendered = true; },
{},
{});
EXPECT_TRUE(firstUiRendered);
EXPECT_EQ(hostPtr->beginFrameCount, 1);
EXPECT_EQ(hostPtr->endFrameCount, 1);
compositor.Shutdown();
EXPECT_EQ(hostPtr->shutdownCount, 1);
bool secondUiRendered = false;
compositor.RenderFrame(
std::array<float, 4>{ 1.0f, 0.0f, 0.0f, 1.0f }.data(),
[&]() { secondUiRendered = true; },
{},
{});
EXPECT_FALSE(secondUiRendered);
EXPECT_EQ(hostPtr->beginFrameCount, 1);
EXPECT_EQ(hostPtr->endFrameCount, 1);
}
TEST(ImGuiWindowUICompositorTest, NullHostCompositorReturnsSafeDefaults) {
ImGuiWindowUICompositor compositor(std::unique_ptr<IEditorHostCompositor>{});
D3D12WindowRenderer renderer = {};
bool configureFontsCalled = false;
EXPECT_FALSE(compositor.Initialize(
MakeFakeHwnd(),
renderer,
[&configureFontsCalled]() { configureFontsCalled = true; }));
EXPECT_FALSE(configureFontsCalled);
EXPECT_FALSE(compositor.HandleWindowMessage(MakeFakeHwnd(), WM_CLOSE, 0u, 0u));
UITextureRegistration registration = {};
EXPECT_FALSE(compositor.CreateTextureDescriptor(nullptr, nullptr, registration));
bool uiRendered = false;
compositor.RenderFrame(
std::array<float, 4>{ 0.0f, 0.0f, 0.0f, 1.0f }.data(),
[&]() { uiRendered = true; },
{},
{});
EXPECT_FALSE(uiRendered);
compositor.Shutdown();
}
} // namespace

View File

@@ -1,91 +0,0 @@
#include <gtest/gtest.h>
#include "XCUIBackend/ImGuiXCUIInputAdapter.h"
#include <XCEngine/Input/InputTypes.h>
namespace {
using XCEngine::Editor::XCUIBackend::ImGuiXCUIInputAdapter;
using XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions;
using XCEngine::Input::KeyCode;
class ImGuiContextScope {
public:
ImGuiContextScope() {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui::StyleColorsDark();
}
~ImGuiContextScope() {
ImGui::DestroyContext();
}
};
void PrepareImGui(float width = 1024.0f, float height = 768.0f) {
ImGuiIO& io = ImGui::GetIO();
io.DisplaySize = ImVec2(width, height);
io.DeltaTime = 1.0f / 60.0f;
unsigned char* fontPixels = nullptr;
int fontWidth = 0;
int fontHeight = 0;
io.Fonts->GetTexDataAsRGBA32(&fontPixels, &fontWidth, &fontHeight);
io.Fonts->SetTexID(static_cast<ImTextureID>(1));
}
} // namespace
TEST(ImGuiXCUIInputAdapterTest, CaptureSnapshotMapsImGuiStateIntoXCUIFrameSnapshot) {
ImGuiContextScope contextScope;
PrepareImGui(800.0f, 600.0f);
ImGuiIO& io = ImGui::GetIO();
io.MousePos = ImVec2(120.0f, 72.0f);
io.MouseDown[0] = true;
io.MouseWheel = 1.0f;
io.WantCaptureMouse = true;
io.WantCaptureKeyboard = true;
io.WantTextInput = true;
io.KeyCtrl = true;
io.KeysData[ImGuiKey_LeftCtrl - ImGuiKey_NamedKey_BEGIN].Down = true;
io.KeysData[ImGuiKey_P - ImGuiKey_NamedKey_BEGIN].Down = true;
io.InputQueueCharacters.resize(0);
io.InputQueueCharacters.push_back(static_cast<ImWchar>('p'));
XCUIInputBridgeCaptureOptions options = {};
options.pointerOffset = XCEngine::UI::UIPoint(20.0f, 12.0f);
options.windowFocused = false;
options.timestampNanoseconds = 99u;
const auto snapshot = ImGuiXCUIInputAdapter::CaptureSnapshot(io, options);
EXPECT_FLOAT_EQ(snapshot.pointerPosition.x, 100.0f);
EXPECT_FLOAT_EQ(snapshot.pointerPosition.y, 60.0f);
EXPECT_TRUE(snapshot.pointerInside);
EXPECT_TRUE(snapshot.pointerButtonsDown[0]);
EXPECT_FLOAT_EQ(snapshot.wheelDelta.x, 0.0f);
EXPECT_FLOAT_EQ(snapshot.wheelDelta.y, 1.0f);
EXPECT_TRUE(snapshot.modifiers.control);
EXPECT_FALSE(snapshot.windowFocused);
EXPECT_TRUE(snapshot.wantCaptureMouse);
EXPECT_TRUE(snapshot.wantCaptureKeyboard);
EXPECT_TRUE(snapshot.wantTextInput);
EXPECT_EQ(snapshot.timestampNanoseconds, 99u);
EXPECT_TRUE(snapshot.IsKeyDown(static_cast<std::int32_t>(KeyCode::LeftCtrl)));
EXPECT_TRUE(snapshot.IsKeyDown(static_cast<std::int32_t>(KeyCode::P)));
ASSERT_EQ(snapshot.characters.size(), 1u);
EXPECT_EQ(snapshot.characters[0], static_cast<std::uint32_t>('p'));
}
TEST(ImGuiXCUIInputAdapterTest, MapKeyCodeReturnsXCUIKeyCodesForNamedKeys) {
EXPECT_EQ(
ImGuiXCUIInputAdapter::MapKeyCode(ImGuiKey_A),
static_cast<std::int32_t>(KeyCode::A));
EXPECT_EQ(
ImGuiXCUIInputAdapter::MapKeyCode(ImGuiKey_F12),
static_cast<std::int32_t>(KeyCode::F12));
EXPECT_EQ(
ImGuiXCUIInputAdapter::MapKeyCode(ImGuiKey_None),
static_cast<std::int32_t>(KeyCode::None));
}

View File

@@ -1,17 +0,0 @@
#include <gtest/gtest.h>
#include "XCUIBackend/ImGuiXCUIPanelCanvasHost.h"
namespace {
using XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost;
TEST(NewEditorImGuiXCUIPanelCanvasHostTest, FactoryCreatesCompatCanvasHostWithStableDebugName) {
std::unique_ptr<IXCUIPanelCanvasHost> host = CreateImGuiXCUIPanelCanvasHost();
ASSERT_NE(host, nullptr);
EXPECT_STREQ(host->GetDebugName(), "ImGuiXCUIPanelCanvasHost");
}
} // namespace

View File

@@ -1,72 +0,0 @@
#include <gtest/gtest.h>
#include "XCUIBackend/LegacyImGuiHostInterop.h"
#include <imgui.h>
namespace {
using XCEngine::Editor::XCUIBackend::ApplyLegacyImGuiHostInputCapture;
using XCEngine::Editor::XCUIBackend::ConfigureLegacyImGuiHostFonts;
using XCEngine::Editor::XCUIBackend::CreateLegacyImGuiHostedPreviewPresenter;
using XCEngine::Editor::XCUIBackend::CreateLegacyImGuiPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::CreateLegacyImGuiWindowUICompositor;
using XCEngine::Editor::XCUIBackend::RenderLegacyImGuiDemoWindow;
using XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot;
class ScopedImGuiContext final {
public:
ScopedImGuiContext() {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
}
~ScopedImGuiContext() {
ImGui::DestroyContext();
}
ScopedImGuiContext(const ScopedImGuiContext&) = delete;
ScopedImGuiContext& operator=(const ScopedImGuiContext&) = delete;
};
TEST(LegacyImGuiHostInteropTest, CreatesLegacyHostAdaptersWithExpectedDebugNames) {
auto compositor = CreateLegacyImGuiWindowUICompositor();
auto presenter = CreateLegacyImGuiHostedPreviewPresenter();
auto canvasHost = CreateLegacyImGuiPanelCanvasHost();
ASSERT_NE(compositor, nullptr);
ASSERT_NE(presenter, nullptr);
ASSERT_NE(canvasHost, nullptr);
EXPECT_STREQ(canvasHost->GetDebugName(), "ImGuiXCUIPanelCanvasHost");
}
TEST(LegacyImGuiHostInteropTest, ApplyInputCaptureCopiesImGuiIoFlagsIntoSnapshot) {
ScopedImGuiContext context = {};
ImGuiIO& io = ImGui::GetIO();
io.WantCaptureKeyboard = true;
io.WantTextInput = true;
XCUIInputBridgeFrameSnapshot snapshot = {};
ApplyLegacyImGuiHostInputCapture(snapshot);
EXPECT_TRUE(snapshot.wantCaptureKeyboard);
EXPECT_TRUE(snapshot.wantTextInput);
}
TEST(LegacyImGuiHostInteropTest, ConfigureFontsPopulatesDefaultFont) {
ScopedImGuiContext context = {};
ASSERT_TRUE(ConfigureLegacyImGuiHostFonts());
EXPECT_NE(ImGui::GetIO().FontDefault, nullptr);
EXPECT_GT(ImGui::GetIO().Fonts->Fonts.Size, 0);
}
TEST(LegacyImGuiHostInteropTest, RenderDemoWindowSkipsWorkWhenVisibilityIsDisabled) {
ScopedImGuiContext context = {};
bool visible = false;
EXPECT_FALSE(RenderLegacyImGuiDemoWindow(visible));
EXPECT_FALSE(visible);
}
} // namespace

View File

@@ -1,32 +0,0 @@
#include <gtest/gtest.h>
#include "Rendering/MainWindowNativeBackdropRenderer.h"
#include <type_traits>
namespace {
using XCEngine::NewEditor::MainWindowNativeBackdropRenderer;
using XCEngine::Rendering::RenderContext;
using XCEngine::Rendering::RenderSurface;
TEST(NewEditorMainWindowNativeBackdropRendererApiTest, ExposesFrameStateAndSurfaceRenderEntry) {
using FrameState = MainWindowNativeBackdropRenderer::FrameState;
static_assert(std::is_same_v<
decltype(std::declval<FrameState>().elapsedSeconds),
float>);
static_assert(std::is_same_v<
decltype(std::declval<FrameState>().pulseAccent),
bool>);
static_assert(std::is_same_v<
decltype(std::declval<MainWindowNativeBackdropRenderer&>().Render(
std::declval<const RenderContext&>(),
std::declval<const RenderSurface&>(),
std::declval<const FrameState&>())),
bool>);
SUCCEED();
}
} // namespace

View File

@@ -1,446 +0,0 @@
#include <gtest/gtest.h>
#include "Platform/D3D12WindowRenderer.h"
#include "XCUIBackend/IWindowUICompositor.h"
#include "XCUIBackend/NativeWindowUICompositor.h"
#include "XCUIBackend/UITextureRegistration.h"
#include <XCEngine/RHI/RHICapabilities.h>
#include <XCEngine/RHI/RHIDevice.h>
#include <XCEngine/RHI/RHIResourceView.h>
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/UI/DrawData.h>
#include <array>
#include <cstdint>
#include <memory>
#include <string>
namespace {
using XCEngine::Editor::Platform::D3D12WindowRenderer;
using XCEngine::Editor::XCUIBackend::CreateNativeWindowUICompositor;
using XCEngine::Editor::XCUIBackend::IXCUITextAtlasProvider;
using XCEngine::Editor::XCUIBackend::IWindowUICompositor;
using XCEngine::Editor::XCUIBackend::NativeWindowUICompositor;
using XCEngine::Editor::XCUIBackend::UITextureRegistration;
using XCEngine::Editor::XCUIBackend::XCUINativeWindowPresentStats;
using XCEngine::Editor::XCUIBackend::XCUINativeWindowRenderPacket;
using XCEngine::RHI::Format;
using XCEngine::RHI::ResourceStates;
using XCEngine::RHI::ResourceViewDesc;
using XCEngine::RHI::ResourceViewDimension;
using XCEngine::RHI::ResourceViewType;
using XCEngine::RHI::RHIDevice;
using XCEngine::RHI::RHIResourceView;
using XCEngine::RHI::RHITexture;
using XCEngine::RHI::TextureType;
using XCEngine::UI::UITextureHandleKind;
HWND MakeFakeHwnd() {
return reinterpret_cast<HWND>(static_cast<std::uintptr_t>(0x2345u));
}
class StubTextAtlasProvider final : public IXCUITextAtlasProvider {
public:
bool GetAtlasTextureView(PixelFormat preferredFormat, AtlasTextureView& outView) const override {
(void)preferredFormat;
outView = {};
return false;
}
std::size_t GetFontCount() const override {
return 0u;
}
FontHandle GetFont(std::size_t index) const override {
(void)index;
return {};
}
FontHandle GetDefaultFont() const override {
return {};
}
bool GetFontInfo(FontHandle font, FontInfo& outInfo) const override {
(void)font;
outInfo = {};
return false;
}
bool GetBakedFontInfo(FontHandle font, float fontSize, BakedFontInfo& outInfo) const override {
(void)font;
(void)fontSize;
outInfo = {};
return false;
}
bool FindGlyph(FontHandle font, float fontSize, std::uint32_t codepoint, GlyphInfo& outInfo) const override {
(void)font;
(void)fontSize;
(void)codepoint;
outInfo = {};
return false;
}
};
XCEngine::UI::UIDrawData MakeDrawData() {
XCEngine::UI::UIDrawData drawData = {};
drawData.EmplaceDrawList("NativeOverlay").AddFilledRect(
XCEngine::UI::UIRect(10.0f, 12.0f, 48.0f, 24.0f),
XCEngine::UI::UIColor(0.2f, 0.4f, 0.8f, 1.0f));
return drawData;
}
struct TrackingShaderViewState {
int shutdownCount = 0;
int destructorCount = 0;
};
class TrackingShaderResourceView final : public XCEngine::RHI::RHIShaderResourceView {
public:
TrackingShaderResourceView(
TrackingShaderViewState& state,
bool valid,
ResourceViewDimension dimension = ResourceViewDimension::Texture2D,
Format format = Format::R8G8B8A8_UNorm)
: m_state(state)
, m_valid(valid)
, m_dimension(dimension)
, m_format(format) {
}
~TrackingShaderResourceView() override {
++m_state.destructorCount;
}
void Shutdown() override {
++m_state.shutdownCount;
m_valid = false;
}
void* GetNativeHandle() override {
return this;
}
bool IsValid() const override {
return m_valid;
}
ResourceViewType GetViewType() const override {
return ResourceViewType::ShaderResource;
}
ResourceViewDimension GetDimension() const override {
return m_dimension;
}
Format GetFormat() const override {
return m_format;
}
private:
TrackingShaderViewState& m_state;
bool m_valid = true;
ResourceViewDimension m_dimension = ResourceViewDimension::Texture2D;
Format m_format = Format::R8G8B8A8_UNorm;
};
class FakeTexture final : public RHITexture {
public:
FakeTexture(
std::uint32_t width,
std::uint32_t height,
Format format = Format::R8G8B8A8_UNorm,
TextureType textureType = TextureType::Texture2D)
: m_width(width)
, m_height(height)
, m_format(format)
, m_textureType(textureType) {
}
std::uint32_t GetWidth() const override { return m_width; }
std::uint32_t GetHeight() const override { return m_height; }
std::uint32_t GetDepth() const override { return 1u; }
std::uint32_t GetMipLevels() const override { return 1u; }
Format GetFormat() const override { return m_format; }
TextureType GetTextureType() const override { return m_textureType; }
ResourceStates GetState() const override { return m_state; }
void SetState(ResourceStates state) override { m_state = state; }
void* GetNativeHandle() override { return this; }
const std::string& GetName() const override { return m_name; }
void SetName(const std::string& name) override { m_name = name; }
void Shutdown() override { m_shutdownCalled = true; }
bool shutdownCalled() const { return m_shutdownCalled; }
private:
std::uint32_t m_width = 0u;
std::uint32_t m_height = 0u;
Format m_format = Format::Unknown;
TextureType m_textureType = TextureType::Texture2D;
ResourceStates m_state = ResourceStates::Common;
std::string m_name = {};
bool m_shutdownCalled = false;
};
class RecordingDevice final : public RHIDevice {
public:
TrackingShaderViewState* nextShaderViewState = nullptr;
bool createShaderViewValid = true;
int createShaderViewCount = 0;
RHITexture* lastShaderViewTexture = nullptr;
ResourceViewDesc lastShaderViewDesc = {};
bool Initialize(const XCEngine::RHI::RHIDeviceDesc&) override { return true; }
void Shutdown() override {}
XCEngine::RHI::RHIBuffer* CreateBuffer(const XCEngine::RHI::BufferDesc&) override { return nullptr; }
XCEngine::RHI::RHITexture* CreateTexture(const XCEngine::RHI::TextureDesc&) override { return nullptr; }
XCEngine::RHI::RHITexture* CreateTexture(const XCEngine::RHI::TextureDesc&, const void*, size_t, std::uint32_t) override { return nullptr; }
XCEngine::RHI::RHISwapChain* CreateSwapChain(const XCEngine::RHI::SwapChainDesc&, XCEngine::RHI::RHICommandQueue*) override { return nullptr; }
XCEngine::RHI::RHICommandList* CreateCommandList(const XCEngine::RHI::CommandListDesc&) override { return nullptr; }
XCEngine::RHI::RHICommandQueue* CreateCommandQueue(const XCEngine::RHI::CommandQueueDesc&) override { return nullptr; }
XCEngine::RHI::RHIShader* CreateShader(const XCEngine::RHI::ShaderCompileDesc&) override { return nullptr; }
XCEngine::RHI::RHIPipelineState* CreatePipelineState(const XCEngine::RHI::GraphicsPipelineDesc&) override { return nullptr; }
XCEngine::RHI::RHIPipelineLayout* CreatePipelineLayout(const XCEngine::RHI::RHIPipelineLayoutDesc&) override { return nullptr; }
XCEngine::RHI::RHIFence* CreateFence(const XCEngine::RHI::FenceDesc&) override { return nullptr; }
XCEngine::RHI::RHISampler* CreateSampler(const XCEngine::RHI::SamplerDesc&) override { return nullptr; }
XCEngine::RHI::RHIRenderPass* CreateRenderPass(
std::uint32_t,
const XCEngine::RHI::AttachmentDesc*,
const XCEngine::RHI::AttachmentDesc*) override { return nullptr; }
XCEngine::RHI::RHIFramebuffer* CreateFramebuffer(
XCEngine::RHI::RHIRenderPass*,
std::uint32_t,
std::uint32_t,
std::uint32_t,
RHIResourceView**,
RHIResourceView*) override { return nullptr; }
XCEngine::RHI::RHIDescriptorPool* CreateDescriptorPool(const XCEngine::RHI::DescriptorPoolDesc&) override { return nullptr; }
XCEngine::RHI::RHIDescriptorSet* CreateDescriptorSet(
XCEngine::RHI::RHIDescriptorPool*,
const XCEngine::RHI::DescriptorSetLayoutDesc&) override { return nullptr; }
RHIResourceView* CreateVertexBufferView(XCEngine::RHI::RHIBuffer*, const ResourceViewDesc&) override { return nullptr; }
RHIResourceView* CreateIndexBufferView(XCEngine::RHI::RHIBuffer*, const ResourceViewDesc&) override { return nullptr; }
RHIResourceView* CreateRenderTargetView(XCEngine::RHI::RHITexture*, const ResourceViewDesc&) override { return nullptr; }
RHIResourceView* CreateDepthStencilView(XCEngine::RHI::RHITexture*, const ResourceViewDesc&) override { return nullptr; }
RHIResourceView* CreateShaderResourceView(XCEngine::RHI::RHITexture* texture, const ResourceViewDesc& desc) override {
++createShaderViewCount;
lastShaderViewTexture = texture;
lastShaderViewDesc = desc;
if (nextShaderViewState == nullptr) {
return nullptr;
}
return new TrackingShaderResourceView(
*nextShaderViewState,
createShaderViewValid,
desc.dimension != ResourceViewDimension::Unknown ? desc.dimension : ResourceViewDimension::Texture2D,
desc.format != 0u ? static_cast<Format>(desc.format) : Format::R8G8B8A8_UNorm);
}
RHIResourceView* CreateUnorderedAccessView(XCEngine::RHI::RHITexture*, const ResourceViewDesc&) override { return nullptr; }
const XCEngine::RHI::RHICapabilities& GetCapabilities() const override { return m_capabilities; }
const XCEngine::RHI::RHIDeviceInfo& GetDeviceInfo() const override { return m_deviceInfo; }
void* GetNativeDevice() override { return this; }
private:
XCEngine::RHI::RHICapabilities m_capabilities = {};
XCEngine::RHI::RHIDeviceInfo m_deviceInfo = {};
};
TEST(NativeWindowUICompositorTest, RenderPacketReportsDrawDataPresenceAndClearResetsPayload) {
XCUINativeWindowRenderPacket packet = {};
EXPECT_FALSE(packet.HasDrawData());
EXPECT_EQ(packet.textAtlasProvider, nullptr);
StubTextAtlasProvider atlasProvider = {};
packet.drawData = MakeDrawData();
packet.textAtlasProvider = &atlasProvider;
EXPECT_TRUE(packet.HasDrawData());
EXPECT_EQ(packet.drawData.GetDrawListCount(), 1u);
EXPECT_EQ(packet.drawData.GetTotalCommandCount(), 1u);
EXPECT_EQ(packet.textAtlasProvider, &atlasProvider);
packet.Clear();
EXPECT_FALSE(packet.HasDrawData());
EXPECT_EQ(packet.drawData.GetDrawListCount(), 0u);
EXPECT_EQ(packet.drawData.GetTotalCommandCount(), 0u);
EXPECT_EQ(packet.textAtlasProvider, nullptr);
}
TEST(NativeWindowUICompositorTest, SubmitAndClearPendingPacketTracksCopiedDrawDataAndAtlasProvider) {
NativeWindowUICompositor compositor = {};
StubTextAtlasProvider atlasProvider = {};
const XCEngine::UI::UIDrawData drawData = MakeDrawData();
compositor.SubmitRenderPacket(drawData, &atlasProvider);
ASSERT_TRUE(compositor.HasPendingRenderPacket());
const XCUINativeWindowRenderPacket& packet = compositor.GetPendingRenderPacket();
EXPECT_TRUE(packet.HasDrawData());
EXPECT_EQ(packet.drawData.GetDrawListCount(), 1u);
EXPECT_EQ(packet.drawData.GetTotalCommandCount(), 1u);
EXPECT_EQ(packet.textAtlasProvider, &atlasProvider);
compositor.ClearPendingRenderPacket();
EXPECT_FALSE(compositor.HasPendingRenderPacket());
EXPECT_FALSE(compositor.GetPendingRenderPacket().HasDrawData());
EXPECT_EQ(compositor.GetPendingRenderPacket().textAtlasProvider, nullptr);
}
TEST(NativeWindowUICompositorTest, InitializeAndShutdownResetStateAlongSafePaths) {
NativeWindowUICompositor compositor = {};
D3D12WindowRenderer renderer = {};
compositor.SubmitRenderPacket(MakeDrawData(), nullptr);
ASSERT_TRUE(compositor.HasPendingRenderPacket());
bool configureFontsCalled = false;
EXPECT_FALSE(compositor.Initialize(
nullptr,
renderer,
[&configureFontsCalled]() { configureFontsCalled = true; }));
EXPECT_FALSE(configureFontsCalled);
EXPECT_FALSE(compositor.HasPendingRenderPacket());
compositor.SubmitRenderPacket(MakeDrawData(), nullptr);
EXPECT_TRUE(compositor.Initialize(
MakeFakeHwnd(),
renderer,
[&configureFontsCalled]() { configureFontsCalled = true; }));
EXPECT_FALSE(configureFontsCalled);
EXPECT_FALSE(compositor.HasPendingRenderPacket());
compositor.Shutdown();
EXPECT_FALSE(compositor.HasPendingRenderPacket());
const XCUINativeWindowPresentStats& stats = compositor.GetLastPresentStats();
EXPECT_FALSE(stats.hadPendingPacket);
EXPECT_FALSE(stats.renderedNativeOverlay);
EXPECT_EQ(stats.submittedDrawListCount, 0u);
EXPECT_EQ(stats.submittedCommandCount, 0u);
}
TEST(NativeWindowUICompositorTest, RenderFrameWithUnpreparedRendererSkipsCallbacksAndKeepsPendingPacket) {
NativeWindowUICompositor compositor = {};
D3D12WindowRenderer renderer = {};
ASSERT_TRUE(compositor.Initialize(MakeFakeHwnd(), renderer, {}));
compositor.SubmitRenderPacket(MakeDrawData(), nullptr);
ASSERT_TRUE(compositor.HasPendingRenderPacket());
bool uiRendered = false;
bool beforeUiRendered = false;
bool afterUiRendered = false;
compositor.RenderFrame(
std::array<float, 4>{ 0.1f, 0.2f, 0.3f, 1.0f }.data(),
[&uiRendered]() { uiRendered = true; },
[&beforeUiRendered](const ::XCEngine::Rendering::RenderContext&, const ::XCEngine::Rendering::RenderSurface&) {
beforeUiRendered = true;
},
[&afterUiRendered](const ::XCEngine::Rendering::RenderContext&, const ::XCEngine::Rendering::RenderSurface&) {
afterUiRendered = true;
});
EXPECT_FALSE(uiRendered);
EXPECT_FALSE(beforeUiRendered);
EXPECT_FALSE(afterUiRendered);
EXPECT_TRUE(compositor.HasPendingRenderPacket());
const XCUINativeWindowPresentStats& stats = compositor.GetLastPresentStats();
EXPECT_FALSE(stats.hadPendingPacket);
EXPECT_FALSE(stats.renderedNativeOverlay);
EXPECT_EQ(stats.submittedDrawListCount, 0u);
EXPECT_EQ(stats.submittedCommandCount, 0u);
}
TEST(NativeWindowUICompositorTest, InterfaceFactoryReturnsSafeNativeCompositorDefaults) {
std::unique_ptr<IWindowUICompositor> compositor = CreateNativeWindowUICompositor();
ASSERT_NE(compositor, nullptr);
D3D12WindowRenderer renderer = {};
bool configureFontsCalled = false;
EXPECT_FALSE(compositor->Initialize(
nullptr,
renderer,
[&configureFontsCalled]() { configureFontsCalled = true; }));
EXPECT_FALSE(configureFontsCalled);
EXPECT_FALSE(compositor->HandleWindowMessage(MakeFakeHwnd(), WM_CLOSE, 0u, 0u));
UITextureRegistration registration = {};
EXPECT_FALSE(compositor->CreateTextureDescriptor(nullptr, nullptr, registration));
EXPECT_EQ(registration.texture.nativeHandle, 0u);
bool uiRendered = false;
compositor->RenderFrame(
std::array<float, 4>{ 0.0f, 0.0f, 0.0f, 1.0f }.data(),
[&uiRendered]() { uiRendered = true; },
{},
{});
EXPECT_FALSE(uiRendered);
compositor->Shutdown();
}
TEST(NativeWindowUICompositorTest, ShaderResourceViewRegistrationsStayValidWithoutGpuDescriptorHandle) {
UITextureRegistration registration = {};
registration.cpuHandle.ptr = 17u;
registration.texture.nativeHandle = 33u;
registration.texture.width = 64u;
registration.texture.height = 32u;
registration.texture.kind = UITextureHandleKind::ShaderResourceView;
EXPECT_TRUE(registration.IsValid());
registration.texture.kind = UITextureHandleKind::ImGuiDescriptor;
EXPECT_FALSE(registration.IsValid());
registration.gpuHandle.ptr = 19u;
EXPECT_TRUE(registration.IsValid());
}
TEST(NativeWindowUICompositorTest, CreateTextureDescriptorPublishesShaderResourceViewAndFreeReleasesIt) {
NativeWindowUICompositor compositor = {};
RecordingDevice device = {};
TrackingShaderViewState viewState = {};
device.nextShaderViewState = &viewState;
FakeTexture texture(256u, 128u, Format::R8G8B8A8_UNorm, TextureType::Texture2DArray);
UITextureRegistration registration = {};
ASSERT_TRUE(compositor.CreateTextureDescriptor(&device, &texture, registration));
EXPECT_EQ(device.createShaderViewCount, 1);
EXPECT_EQ(device.lastShaderViewTexture, &texture);
EXPECT_EQ(device.lastShaderViewDesc.format, static_cast<std::uint32_t>(Format::R8G8B8A8_UNorm));
EXPECT_EQ(device.lastShaderViewDesc.dimension, ResourceViewDimension::Texture2DArray);
EXPECT_TRUE(registration.IsValid());
EXPECT_NE(registration.cpuHandle.ptr, 0u);
EXPECT_EQ(registration.gpuHandle.ptr, 0u);
EXPECT_EQ(registration.texture.width, 256u);
EXPECT_EQ(registration.texture.height, 128u);
EXPECT_EQ(registration.texture.kind, UITextureHandleKind::ShaderResourceView);
EXPECT_EQ(registration.cpuHandle.ptr, registration.texture.nativeHandle);
compositor.FreeTextureDescriptor(registration);
EXPECT_EQ(viewState.shutdownCount, 1);
EXPECT_EQ(viewState.destructorCount, 1);
}
TEST(NativeWindowUICompositorTest, CreateTextureDescriptorRejectsInvalidShaderResourceViewAndCleansItUp) {
NativeWindowUICompositor compositor = {};
RecordingDevice device = {};
TrackingShaderViewState viewState = {};
device.nextShaderViewState = &viewState;
device.createShaderViewValid = false;
FakeTexture texture(96u, 64u);
UITextureRegistration registration = {};
EXPECT_FALSE(compositor.CreateTextureDescriptor(&device, &texture, registration));
EXPECT_EQ(device.createShaderViewCount, 1);
EXPECT_FALSE(registration.IsValid());
EXPECT_EQ(registration.texture.nativeHandle, 0u);
EXPECT_EQ(viewState.shutdownCount, 1);
EXPECT_EQ(viewState.destructorCount, 1);
}
} // namespace

View File

@@ -1,172 +0,0 @@
#include <gtest/gtest.h>
#include "XCUIBackend/NativeXCUIPanelCanvasHost.h"
namespace {
using XCEngine::Editor::XCUIBackend::CreateNativeXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasFrameSnapshot;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession;
XCEngine::UI::UITextureHandle MakeSurfaceTextureHandle(std::uintptr_t nativeHandle, std::uint32_t width, std::uint32_t height) {
XCEngine::UI::UITextureHandle texture = {};
texture.nativeHandle = nativeHandle;
texture.width = width;
texture.height = height;
texture.kind = XCEngine::UI::UITextureHandleKind::ShaderResourceView;
return texture;
}
TEST(NativeXCUIPanelCanvasHostTest, FactoryCreatesHostWithStableDebugNameAndNoSnapshotBeforeBegin) {
std::unique_ptr<IXCUIPanelCanvasHost> host = CreateNativeXCUIPanelCanvasHost();
ASSERT_NE(host, nullptr);
EXPECT_STREQ(host->GetDebugName(), "NativeXCUIPanelCanvasHost");
XCUIPanelCanvasFrameSnapshot snapshot = {};
EXPECT_FALSE(host->TryGetLatestFrameSnapshot(snapshot));
EXPECT_TRUE(snapshot.childId.empty());
EXPECT_EQ(snapshot.overlayDrawData.GetDrawListCount(), 0u);
}
TEST(NativeXCUIPanelCanvasHostTest, BeginCanvasWithConfiguredSessionCapturesSnapshotAndOverlayCommands) {
NativeXCUIPanelCanvasHost host = {};
XCUIPanelCanvasSession configuredSession = {};
configuredSession.hostRect = XCEngine::UI::UIRect(20.0f, 30.0f, 640.0f, 360.0f);
configuredSession.canvasRect = XCEngine::UI::UIRect(20.0f, 72.0f, 640.0f, 318.0f);
configuredSession.pointerPosition = XCEngine::UI::UIPoint(128.0f, 144.0f);
configuredSession.validCanvas = true;
configuredSession.hovered = true;
configuredSession.windowFocused = true;
host.SetCanvasSession(configuredSession);
XCUIPanelCanvasRequest request = {};
request.childId = "NativeDemoCanvas";
request.height = 360.0f;
request.topInset = 42.0f;
request.placeholderTitle = "Placeholder";
request.placeholderSubtitle = "Native host placeholder";
request.badgeTitle = "XCUI Demo";
request.badgeSubtitle = "native path";
const XCUIPanelCanvasSession session = host.BeginCanvas(request);
EXPECT_FLOAT_EQ(session.hostRect.x, configuredSession.hostRect.x);
EXPECT_FLOAT_EQ(session.hostRect.y, configuredSession.hostRect.y);
EXPECT_FLOAT_EQ(session.canvasRect.x, configuredSession.canvasRect.x);
EXPECT_FLOAT_EQ(session.canvasRect.y, configuredSession.canvasRect.y);
EXPECT_TRUE(session.validCanvas);
EXPECT_TRUE(session.hovered);
EXPECT_TRUE(session.windowFocused);
host.DrawFilledRect(
XCEngine::UI::UIRect(40.0f, 90.0f, 120.0f, 60.0f),
XCEngine::UI::UIColor(0.8f, 0.2f, 0.1f, 1.0f),
6.0f);
host.DrawOutlineRect(
XCEngine::UI::UIRect(42.0f, 92.0f, 118.0f, 58.0f),
XCEngine::UI::UIColor(0.2f, 0.8f, 0.3f, 1.0f),
2.0f,
4.0f);
host.DrawText(
XCEngine::UI::UIPoint(48.0f, 104.0f),
"hello native",
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
16.0f);
host.EndCanvas();
XCUIPanelCanvasFrameSnapshot snapshot = {};
ASSERT_TRUE(host.TryGetLatestFrameSnapshot(snapshot));
EXPECT_EQ(snapshot.childId, "NativeDemoCanvas");
EXPECT_FALSE(snapshot.showingSurfaceImage);
EXPECT_TRUE(snapshot.drawPreviewFrame);
EXPECT_EQ(snapshot.placeholderTitle, "Placeholder");
EXPECT_EQ(snapshot.placeholderSubtitle, "Native host placeholder");
EXPECT_EQ(snapshot.badgeTitle, "XCUI Demo");
EXPECT_EQ(snapshot.badgeSubtitle, "native path");
EXPECT_TRUE(snapshot.session.validCanvas);
EXPECT_EQ(snapshot.overlayDrawData.GetDrawListCount(), 1u);
EXPECT_EQ(snapshot.overlayDrawData.GetDrawLists().front().GetDebugName(), "NativeDemoCanvas.overlay");
EXPECT_EQ(snapshot.overlayDrawData.GetTotalCommandCount(), 14u);
EXPECT_EQ(
snapshot.overlayDrawData.GetDrawLists().front().GetCommands().front().type,
XCEngine::UI::UIDrawCommandType::PushClipRect);
EXPECT_EQ(
snapshot.overlayDrawData.GetDrawLists().front().GetCommands().back().type,
XCEngine::UI::UIDrawCommandType::PopClipRect);
}
TEST(NativeXCUIPanelCanvasHostTest, SurfaceImagePathCapturesSurfaceAndPreviewFrameWithoutPlaceholder) {
NativeXCUIPanelCanvasHost host = {};
XCUIPanelCanvasSession configuredSession = {};
configuredSession.hostRect = XCEngine::UI::UIRect(0.0f, 0.0f, 800.0f, 480.0f);
configuredSession.canvasRect = XCEngine::UI::UIRect(0.0f, 0.0f, 800.0f, 480.0f);
configuredSession.validCanvas = true;
host.SetCanvasSession(configuredSession);
XCUIPanelCanvasRequest request = {};
request.childId = "NativeSurfaceCanvas";
request.showSurfaceImage = true;
request.drawPreviewFrame = true;
request.surfaceImage.texture = MakeSurfaceTextureHandle(17u, 1024u, 512u);
request.surfaceImage.surfaceWidth = 1024u;
request.surfaceImage.surfaceHeight = 512u;
request.surfaceImage.uvMin = XCEngine::UI::UIPoint(0.0f, 0.0f);
request.surfaceImage.uvMax = XCEngine::UI::UIPoint(1.0f, 1.0f);
const XCUIPanelCanvasSession session = host.BeginCanvas(request);
EXPECT_TRUE(session.validCanvas);
host.EndCanvas();
XCUIPanelCanvasFrameSnapshot snapshot = {};
ASSERT_TRUE(host.TryGetLatestFrameSnapshot(snapshot));
EXPECT_TRUE(snapshot.showingSurfaceImage);
EXPECT_TRUE(snapshot.surfaceImage.IsValid());
EXPECT_EQ(snapshot.surfaceImage.texture.nativeHandle, 17u);
EXPECT_EQ(snapshot.overlayDrawData.GetDrawListCount(), 1u);
EXPECT_EQ(snapshot.overlayDrawData.GetTotalCommandCount(), 4u);
const auto& commands = snapshot.overlayDrawData.GetDrawLists().front().GetCommands();
ASSERT_EQ(commands.size(), 4u);
EXPECT_EQ(commands[0].type, XCEngine::UI::UIDrawCommandType::PushClipRect);
EXPECT_EQ(commands[1].type, XCEngine::UI::UIDrawCommandType::Image);
EXPECT_FLOAT_EQ(commands[1].uvMin.x, 0.0f);
EXPECT_FLOAT_EQ(commands[1].uvMin.y, 0.0f);
EXPECT_FLOAT_EQ(commands[1].uvMax.x, 1.0f);
EXPECT_FLOAT_EQ(commands[1].uvMax.y, 1.0f);
EXPECT_EQ(commands[2].type, XCEngine::UI::UIDrawCommandType::RectOutline);
EXPECT_EQ(commands[3].type, XCEngine::UI::UIDrawCommandType::PopClipRect);
}
TEST(NativeXCUIPanelCanvasHostTest, ClearingConfiguredSessionFallsBackToPassiveSnapshot) {
NativeXCUIPanelCanvasHost host = {};
XCUIPanelCanvasSession configuredSession = {};
configuredSession.hostRect = XCEngine::UI::UIRect(4.0f, 5.0f, 320.0f, 240.0f);
configuredSession.canvasRect = XCEngine::UI::UIRect(4.0f, 25.0f, 320.0f, 220.0f);
configuredSession.validCanvas = true;
host.SetCanvasSession(configuredSession);
ASSERT_TRUE(host.HasConfiguredSession());
host.ClearCanvasSession();
EXPECT_FALSE(host.HasConfiguredSession());
XCUIPanelCanvasRequest request = {};
request.height = 180.0f;
request.topInset = 24.0f;
const XCUIPanelCanvasSession session = host.BeginCanvas(request);
host.EndCanvas();
EXPECT_FALSE(session.validCanvas);
EXPECT_FLOAT_EQ(session.hostRect.height, 180.0f);
EXPECT_FLOAT_EQ(session.canvasRect.y, 24.0f);
EXPECT_FLOAT_EQ(session.canvasRect.height, 156.0f);
XCUIPanelCanvasFrameSnapshot snapshot = {};
ASSERT_TRUE(host.TryGetLatestFrameSnapshot(snapshot));
EXPECT_FALSE(snapshot.session.validCanvas);
EXPECT_EQ(snapshot.overlayDrawData.GetDrawListCount(), 0u);
}
} // namespace

View File

@@ -1,306 +0,0 @@
#include <gtest/gtest.h>
#include "XCUIBackend/ImGuiTransitionBackend.h"
#include <XCEngine/UI/DrawData.h>
#include <imgui.h>
namespace {
class ImGuiContextScope {
public:
ImGuiContextScope() {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui::StyleColorsDark();
}
~ImGuiContextScope() {
ImGui::DestroyContext();
}
};
void PrepareImGui(float width = 1024.0f, float height = 768.0f) {
ImGuiIO& io = ImGui::GetIO();
io.DisplaySize = ImVec2(width, height);
io.DeltaTime = 1.0f / 60.0f;
unsigned char* fontPixels = nullptr;
int fontWidth = 0;
int fontHeight = 0;
io.Fonts->GetTexDataAsRGBA32(&fontPixels, &fontWidth, &fontHeight);
io.Fonts->SetTexID(static_cast<ImTextureID>(1));
}
} // namespace
TEST(NewEditorImGuiTransitionBackendTest, BeginFrameClearsPendingQueue) {
ImGuiContextScope contextScope;
XCEngine::Editor::XCUIBackend::ImGuiTransitionBackend backend = {};
XCEngine::UI::UIDrawList drawList("Pending");
drawList.AddFilledRect(
XCEngine::UI::UIRect(0.0f, 0.0f, 20.0f, 20.0f),
XCEngine::UI::UIColor(1.0f, 0.0f, 0.0f, 1.0f));
backend.BeginFrame();
backend.Submit(drawList);
ASSERT_EQ(backend.GetPendingDrawListCount(), 1u);
ASSERT_EQ(backend.GetPendingCommandCount(), 1u);
backend.BeginFrame();
EXPECT_EQ(backend.GetPendingDrawListCount(), 0u);
EXPECT_EQ(backend.GetPendingCommandCount(), 0u);
}
TEST(NewEditorImGuiTransitionBackendTest, EndFrameFlushesSubmittedCommands) {
ImGuiContextScope contextScope;
PrepareImGui();
XCEngine::Editor::XCUIBackend::ImGuiTransitionBackend backend = {};
XCEngine::UI::UIDrawList drawList("Flush");
drawList.PushClipRect(XCEngine::UI::UIRect(0.0f, 0.0f, 240.0f, 160.0f));
drawList.AddFilledRect(
XCEngine::UI::UIRect(10.0f, 10.0f, 70.0f, 45.0f),
XCEngine::UI::UIColor(0.2f, 0.4f, 0.6f, 1.0f),
6.0f);
drawList.AddRectOutline(
XCEngine::UI::UIRect(10.0f, 10.0f, 70.0f, 45.0f),
XCEngine::UI::UIColor(0.9f, 0.9f, 0.9f, 1.0f),
2.0f,
6.0f);
drawList.PopClipRect();
backend.BeginFrame();
backend.Submit(drawList);
ImGui::NewFrame();
ASSERT_TRUE(ImGui::Begin("NewEditorXCUIBackendTest"));
ImDrawList* targetDrawList = ImGui::GetWindowDrawList();
ASSERT_NE(targetDrawList, nullptr);
const bool flushed = backend.EndFrame(targetDrawList);
ImGui::End();
ImGui::EndFrame();
EXPECT_TRUE(flushed);
EXPECT_EQ(backend.GetLastFlushedDrawListCount(), 1u);
EXPECT_EQ(backend.GetLastFlushedCommandCount(), 4u);
EXPECT_EQ(backend.GetPendingCommandCount(), 0u);
EXPECT_GT(targetDrawList->VtxBuffer.Size, 0);
EXPECT_GT(targetDrawList->CmdBuffer.Size, 0);
}
TEST(NewEditorImGuiTransitionBackendTest, EndFrameFlushesMultipleDrawLists) {
ImGuiContextScope contextScope;
PrepareImGui(800.0f, 600.0f);
XCEngine::Editor::XCUIBackend::ImGuiTransitionBackend backend = {};
XCEngine::UI::UIDrawList firstDrawList("First");
firstDrawList.AddFilledRect(
XCEngine::UI::UIRect(8.0f, 8.0f, 40.0f, 30.0f),
XCEngine::UI::UIColor(0.7f, 0.1f, 0.1f, 1.0f));
XCEngine::UI::UIDrawList secondDrawList("Second");
secondDrawList.PushClipRect(XCEngine::UI::UIRect(0.0f, 0.0f, 300.0f, 200.0f));
secondDrawList.AddRectOutline(
XCEngine::UI::UIRect(24.0f, 26.0f, 52.0f, 28.0f),
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
1.5f,
5.0f);
secondDrawList.PopClipRect();
backend.BeginFrame();
backend.Submit(firstDrawList);
backend.Submit(std::move(secondDrawList));
ASSERT_EQ(backend.GetPendingDrawListCount(), 2u);
ASSERT_EQ(backend.GetPendingCommandCount(), 4u);
ImGui::NewFrame();
ASSERT_TRUE(ImGui::Begin("NewEditorXCUIBackendMultiDrawList"));
ImDrawList* targetDrawList = ImGui::GetWindowDrawList();
ASSERT_NE(targetDrawList, nullptr);
const bool flushed = backend.EndFrame(targetDrawList);
ImGui::End();
ImGui::EndFrame();
EXPECT_TRUE(flushed);
EXPECT_EQ(backend.GetLastFlushedDrawListCount(), 2u);
EXPECT_EQ(backend.GetLastFlushedCommandCount(), 4u);
EXPECT_EQ(backend.GetPendingDrawListCount(), 0u);
EXPECT_EQ(backend.GetPendingCommandCount(), 0u);
EXPECT_GT(targetDrawList->CmdBuffer.Size, 0);
}
TEST(NewEditorImGuiTransitionBackendTest, EndFrameWithNoPendingDataLeavesFlushCountsAtZero) {
ImGuiContextScope contextScope;
PrepareImGui(640.0f, 480.0f);
XCEngine::Editor::XCUIBackend::ImGuiTransitionBackend backend = {};
backend.BeginFrame();
ImGui::NewFrame();
ASSERT_TRUE(ImGui::Begin("NewEditorXCUIBackendEmptyFrame"));
ImDrawList* targetDrawList = ImGui::GetWindowDrawList();
ASSERT_NE(targetDrawList, nullptr);
const bool flushed = backend.EndFrame(targetDrawList);
ImGui::End();
ImGui::EndFrame();
EXPECT_TRUE(flushed);
EXPECT_EQ(backend.GetLastFlushedDrawListCount(), 0u);
EXPECT_EQ(backend.GetLastFlushedCommandCount(), 0u);
EXPECT_EQ(backend.GetPendingDrawListCount(), 0u);
EXPECT_EQ(backend.GetPendingCommandCount(), 0u);
}
TEST(NewEditorImGuiTransitionBackendTest, SubmitDrawDataAggregatesMultipleListsBeforeFlush) {
ImGuiContextScope contextScope;
PrepareImGui(720.0f, 512.0f);
XCEngine::Editor::XCUIBackend::ImGuiTransitionBackend backend = {};
XCEngine::UI::UIDrawData drawData = {};
XCEngine::UI::UIDrawList& firstDrawList = drawData.EmplaceDrawList("First");
firstDrawList.AddFilledRect(
XCEngine::UI::UIRect(12.0f, 14.0f, 48.0f, 24.0f),
XCEngine::UI::UIColor(0.4f, 0.5f, 0.9f, 1.0f));
XCEngine::UI::UIDrawList& secondDrawList = drawData.EmplaceDrawList("Second");
secondDrawList.AddRectOutline(
XCEngine::UI::UIRect(36.0f, 42.0f, 64.0f, 28.0f),
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
2.0f,
3.0f);
secondDrawList.AddText(
XCEngine::UI::UIPoint(40.0f, 48.0f),
"xcui",
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
14.0f);
backend.BeginFrame();
backend.Submit(drawData);
ASSERT_EQ(backend.GetPendingDrawListCount(), 2u);
ASSERT_EQ(backend.GetPendingCommandCount(), 3u);
ImGui::NewFrame();
ASSERT_TRUE(ImGui::Begin("NewEditorXCUIBackendDrawData"));
ImDrawList* targetDrawList = ImGui::GetWindowDrawList();
ASSERT_NE(targetDrawList, nullptr);
const bool flushed = backend.EndFrame(targetDrawList);
ImGui::End();
ImGui::EndFrame();
EXPECT_TRUE(flushed);
EXPECT_EQ(backend.GetLastFlushedDrawListCount(), 2u);
EXPECT_EQ(backend.GetLastFlushedCommandCount(), 3u);
EXPECT_EQ(backend.GetPendingDrawListCount(), 0u);
EXPECT_EQ(backend.GetPendingCommandCount(), 0u);
}
TEST(NewEditorImGuiTransitionBackendTest, EndFrameAcceptsTextCommandsThatUseDefaultFontSize) {
ImGuiContextScope contextScope;
PrepareImGui(720.0f, 512.0f);
XCEngine::Editor::XCUIBackend::ImGuiTransitionBackend backend = {};
XCEngine::UI::UIDrawList drawList("DefaultFontText");
drawList.AddText(
XCEngine::UI::UIPoint(24.0f, 30.0f),
"fallback font text",
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
0.0f);
backend.BeginFrame();
backend.Submit(drawList);
ImGui::NewFrame();
ASSERT_TRUE(ImGui::Begin("NewEditorXCUIBackendDefaultFontText"));
ImDrawList* targetDrawList = ImGui::GetWindowDrawList();
ASSERT_NE(targetDrawList, nullptr);
const bool flushed = backend.EndFrame(targetDrawList);
ImGui::End();
ImGui::EndFrame();
EXPECT_TRUE(flushed);
EXPECT_EQ(backend.GetLastFlushedDrawListCount(), 1u);
EXPECT_EQ(backend.GetLastFlushedCommandCount(), 1u);
EXPECT_EQ(backend.GetPendingDrawListCount(), 0u);
EXPECT_EQ(backend.GetPendingCommandCount(), 0u);
}
TEST(NewEditorImGuiTransitionBackendTest, EndFrameSkipsEmptyTextGeometryButClearsPendingState) {
ImGuiContextScope contextScope;
PrepareImGui(720.0f, 512.0f);
XCEngine::Editor::XCUIBackend::ImGuiTransitionBackend backend = {};
XCEngine::UI::UIDrawList drawList("EmptyText");
drawList.AddText(
XCEngine::UI::UIPoint(24.0f, 30.0f),
"",
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
14.0f);
backend.BeginFrame();
backend.Submit(drawList);
ImGui::NewFrame();
ASSERT_TRUE(ImGui::Begin("NewEditorXCUIBackendEmptyText"));
ImGui::TextUnformatted("anchor");
ImDrawList* targetDrawList = ImGui::GetWindowDrawList();
ASSERT_NE(targetDrawList, nullptr);
const int initialVertexCount = targetDrawList->VtxBuffer.Size;
const bool flushed = backend.EndFrame(targetDrawList);
ImGui::End();
ImGui::EndFrame();
EXPECT_TRUE(flushed);
EXPECT_EQ(backend.GetLastFlushedDrawListCount(), 1u);
EXPECT_EQ(backend.GetLastFlushedCommandCount(), 1u);
EXPECT_EQ(targetDrawList->VtxBuffer.Size, initialVertexCount);
EXPECT_EQ(backend.GetPendingDrawListCount(), 0u);
EXPECT_EQ(backend.GetPendingCommandCount(), 0u);
}
TEST(NewEditorImGuiTransitionBackendTest, EndFrameRestoresClipRectStackAfterUnbalancedPush) {
ImGuiContextScope contextScope;
PrepareImGui(800.0f, 600.0f);
XCEngine::Editor::XCUIBackend::ImGuiTransitionBackend backend = {};
XCEngine::UI::UIDrawList drawList("ClipRecovery");
drawList.PushClipRect(XCEngine::UI::UIRect(0.0f, 0.0f, 180.0f, 120.0f));
drawList.AddFilledRect(
XCEngine::UI::UIRect(16.0f, 18.0f, 52.0f, 30.0f),
XCEngine::UI::UIColor(0.9f, 0.3f, 0.2f, 1.0f));
backend.BeginFrame();
backend.Submit(drawList);
ImGui::NewFrame();
ASSERT_TRUE(ImGui::Begin("NewEditorXCUIBackendClipRecovery"));
ImDrawList* targetDrawList = ImGui::GetWindowDrawList();
ASSERT_NE(targetDrawList, nullptr);
const int initialClipDepth = targetDrawList->_ClipRectStack.Size;
const bool flushed = backend.EndFrame(targetDrawList);
EXPECT_TRUE(flushed);
EXPECT_EQ(targetDrawList->_ClipRectStack.Size, initialClipDepth);
EXPECT_EQ(backend.GetLastFlushedDrawListCount(), 1u);
EXPECT_EQ(backend.GetLastFlushedCommandCount(), 2u);
EXPECT_EQ(backend.GetPendingCommandCount(), 0u);
ImGui::End();
ImGui::EndFrame();
}

View File

@@ -0,0 +1,54 @@
#include <gtest/gtest.h>
#include "SandboxFrameBuilder.h"
#include <string>
namespace {
bool DrawDataContainsText(
const XCEngine::UI::UIDrawData& drawData,
const std::string& text) {
for (const XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
for (const XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) {
if (command.type == XCEngine::UI::UIDrawCommandType::Text &&
command.text == text) {
return true;
}
}
}
return false;
}
} // namespace
TEST(NewEditorSandboxFrameTest, BuildsNativeEditorSandboxPacket) {
XCEngine::NewEditor::SandboxFrameOptions options = {};
options.width = 1440.0f;
options.height = 900.0f;
options.timeSeconds = 1.0;
const XCEngine::UI::UIDrawData drawData =
XCEngine::NewEditor::BuildSandboxFrame(options);
EXPECT_FALSE(drawData.Empty());
EXPECT_GT(drawData.GetTotalCommandCount(), 40u);
EXPECT_TRUE(DrawDataContainsText(drawData, "XCUI Native Sandbox"));
EXPECT_TRUE(DrawDataContainsText(drawData, "Hierarchy"));
EXPECT_TRUE(DrawDataContainsText(drawData, "Scene"));
EXPECT_TRUE(DrawDataContainsText(drawData, "Inspector"));
EXPECT_TRUE(DrawDataContainsText(drawData, "No ImGui Host"));
}
TEST(NewEditorSandboxFrameTest, ClampsInvalidSizeToSafeFallback) {
XCEngine::NewEditor::SandboxFrameOptions options = {};
options.width = 0.0f;
options.height = -10.0f;
const XCEngine::UI::UIDrawData drawData =
XCEngine::NewEditor::BuildSandboxFrame(options);
EXPECT_FALSE(drawData.Empty());
EXPECT_TRUE(DrawDataContainsText(drawData, "XCUI Native Sandbox"));
}

View File

@@ -0,0 +1,91 @@
#include <gtest/gtest.h>
#include <XCEngine/UI/Runtime/UIScreenDocumentHost.h>
#include <XCEngine/UI/Runtime/UIScreenPlayer.h>
#include <filesystem>
#include <string>
#include <vector>
#ifndef XCNEWEDITOR_REPO_ROOT
#define XCNEWEDITOR_REPO_ROOT "."
#endif
namespace {
using XCEngine::UI::UIDrawCommand;
using XCEngine::UI::UIDrawCommandType;
using XCEngine::UI::UIDrawData;
using XCEngine::UI::Runtime::UIScreenAsset;
using XCEngine::UI::Runtime::UIScreenFrameInput;
using XCEngine::UI::Runtime::UIScreenPlayer;
using XCEngine::UI::Runtime::UIDocumentScreenHost;
std::filesystem::path RepoRelative(const char* relativePath) {
return (std::filesystem::path(XCNEWEDITOR_REPO_ROOT) / relativePath).lexically_normal();
}
bool DrawDataContainsText(const UIDrawData& drawData, const std::string& text) {
for (const auto& drawList : drawData.GetDrawLists()) {
for (const UIDrawCommand& command : drawList.GetCommands()) {
if (command.type == UIDrawCommandType::Text && command.text == text) {
return true;
}
}
}
return false;
}
bool ContainsPathWithFilename(
const std::vector<std::string>& paths,
const char* expectedFileName) {
for (const std::string& path : paths) {
if (std::filesystem::path(path).filename() == expectedFileName) {
return true;
}
}
return false;
}
} // namespace
TEST(NewEditorStructuredShellTest, AuthoredEditorShellLoadsFromRepositoryResources) {
const std::filesystem::path viewPath = RepoRelative("new_editor/ui/views/editor_shell.xcui");
const std::filesystem::path themePath = RepoRelative("new_editor/ui/themes/editor_shell.xctheme");
const std::filesystem::path schemaPath = RepoRelative("new_editor/ui/schemas/editor_inspector_shell.xcschema");
ASSERT_TRUE(std::filesystem::exists(viewPath));
ASSERT_TRUE(std::filesystem::exists(themePath));
ASSERT_TRUE(std::filesystem::exists(schemaPath));
UIScreenAsset asset = {};
asset.screenId = "new_editor.editor_shell";
asset.documentPath = viewPath.string();
asset.themePath = themePath.string();
UIDocumentScreenHost host = {};
UIScreenPlayer player(host);
ASSERT_TRUE(player.Load(asset)) << player.GetLastError();
ASSERT_NE(player.GetDocument(), nullptr);
EXPECT_TRUE(player.GetDocument()->hasThemeDocument);
EXPECT_TRUE(ContainsPathWithFilename(player.GetDocument()->dependencies, "editor_shell.xctheme"));
EXPECT_TRUE(ContainsPathWithFilename(player.GetDocument()->dependencies, "editor_inspector_shell.xcschema"));
UIScreenFrameInput input = {};
input.viewportRect = XCEngine::UI::UIRect(0.0f, 0.0f, 1440.0f, 900.0f);
input.frameIndex = 1u;
input.focused = true;
const auto& frame = player.Update(input);
EXPECT_TRUE(frame.stats.documentLoaded);
EXPECT_GT(frame.stats.nodeCount, 12u);
EXPECT_GT(frame.stats.commandCount, 20u);
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "XCUI Editor Sandbox"));
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Hierarchy"));
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Scene"));
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Inspector"));
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Status"));
}

View File

@@ -1,257 +0,0 @@
#include <gtest/gtest.h>
#include "XCUIBackend/XCUIAssetDocumentSource.h"
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <XCEngine/Core/Containers/String.h>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <string>
namespace {
namespace fs = std::filesystem;
using XCEngine::Containers::String;
using XCEngine::Editor::XCUIBackend::XCUIAssetDocumentSource;
using XCEngine::Resources::ResourceManager;
String ToContainersString(const std::string& value) {
return String(value.c_str());
}
void WriteTextFile(const fs::path& path, const std::string& contents) {
fs::create_directories(path.parent_path());
std::ofstream output(path, std::ios::binary | std::ios::trunc);
output << contents;
}
std::string BuildMinimalViewDocument(const std::string& themeReference) {
return
"<View name=\"Test\" theme=\"" + themeReference + "\">\n"
" <Column id=\"root\">\n"
" <Text text=\"hello\" />\n"
" </Column>\n"
"</View>\n";
}
std::string BuildMinimalThemeDocument() {
return
"<Theme name=\"TestTheme\">\n"
" <Token name=\"color.text.primary\" type=\"color\" value=\"#FFFFFF\" />\n"
"</Theme>\n";
}
class ScopedCurrentPath {
public:
explicit ScopedCurrentPath(const fs::path& newPath) {
m_originalPath = fs::current_path();
fs::create_directories(newPath);
fs::current_path(newPath);
}
~ScopedCurrentPath() {
if (!m_originalPath.empty()) {
fs::current_path(m_originalPath);
}
}
private:
fs::path m_originalPath = {};
};
class XCUIAssetDocumentSourceTest : public ::testing::Test {
protected:
void SetUp() override {
m_originalResourceRoot = ResourceManager::Get().GetResourceRoot();
ResourceManager::Get().SetResourceRoot(String());
m_tempRoot = fs::temp_directory_path() /
fs::path("xcui_asset_document_source_tests");
m_tempRoot /= fs::path(::testing::UnitTest::GetInstance()->current_test_info()->name());
fs::remove_all(m_tempRoot);
fs::create_directories(m_tempRoot);
}
void TearDown() override {
ResourceManager::Get().UnloadAll();
ResourceManager::Get().SetResourceRoot(m_originalResourceRoot);
std::error_code ec;
fs::remove_all(m_tempRoot, ec);
}
fs::path CreateRepositorySubdir(const std::string& relativePath) const {
const fs::path path = m_tempRoot / fs::path(relativePath);
fs::create_directories(path);
return path;
}
void WriteProjectDocuments(const XCUIAssetDocumentSource::PathSet& paths) const {
WriteTextFile(
m_tempRoot / fs::path(paths.view.primaryRelativePath),
BuildMinimalViewDocument("Theme.xctheme"));
WriteTextFile(
m_tempRoot / fs::path(paths.theme.primaryRelativePath),
BuildMinimalThemeDocument());
}
void WriteLegacyDocuments(const XCUIAssetDocumentSource::PathSet& paths) const {
WriteTextFile(
m_tempRoot / fs::path(paths.view.legacyRelativePath),
BuildMinimalViewDocument(
fs::path(paths.theme.legacyRelativePath).filename().generic_string()));
WriteTextFile(
m_tempRoot / fs::path(paths.theme.legacyRelativePath),
BuildMinimalThemeDocument());
}
fs::path m_tempRoot = {};
private:
String m_originalResourceRoot = {};
};
TEST_F(XCUIAssetDocumentSourceTest, DemoAndLayoutLabPathSetsUseExpectedPaths) {
const XCUIAssetDocumentSource::PathSet demoPaths =
XCUIAssetDocumentSource::MakeDemoPathSet();
EXPECT_EQ(demoPaths.setName, "Demo");
EXPECT_EQ(demoPaths.view.primaryRelativePath, "Assets/XCUI/NewEditor/Demo/View.xcui");
EXPECT_EQ(demoPaths.theme.primaryRelativePath, "Assets/XCUI/NewEditor/Demo/Theme.xctheme");
EXPECT_EQ(demoPaths.view.legacyRelativePath, "new_editor/resources/xcui_demo_view.xcui");
EXPECT_EQ(demoPaths.theme.legacyRelativePath, "new_editor/resources/xcui_demo_theme.xctheme");
const XCUIAssetDocumentSource::PathSet layoutLabPaths =
XCUIAssetDocumentSource::MakeLayoutLabPathSet();
EXPECT_EQ(layoutLabPaths.setName, "LayoutLab");
EXPECT_EQ(layoutLabPaths.view.primaryRelativePath, "Assets/XCUI/NewEditor/LayoutLab/View.xcui");
EXPECT_EQ(layoutLabPaths.theme.primaryRelativePath, "Assets/XCUI/NewEditor/LayoutLab/Theme.xctheme");
EXPECT_EQ(layoutLabPaths.view.legacyRelativePath, "new_editor/resources/xcui_layout_lab_view.xcui");
EXPECT_EQ(layoutLabPaths.theme.legacyRelativePath, "new_editor/resources/xcui_layout_lab_theme.xctheme");
}
TEST_F(XCUIAssetDocumentSourceTest, MakePathSetSanitizesNamesAndBuildsLegacySnakeCase) {
const XCUIAssetDocumentSource::PathSet paths =
XCUIAssetDocumentSource::MakePathSet(" Layout Lab! Debug-42 ");
EXPECT_EQ(paths.setName, "LayoutLabDebug-42");
EXPECT_EQ(
paths.view.primaryRelativePath,
"Assets/XCUI/NewEditor/LayoutLabDebug-42/View.xcui");
EXPECT_EQ(
paths.theme.legacyRelativePath,
"new_editor/resources/xcui_layout_lab_debug_42_theme.xctheme");
}
TEST_F(XCUIAssetDocumentSourceTest, CollectCandidatePathsPrefersProjectAssetsBeforeLegacyMirror) {
const XCUIAssetDocumentSource::PathSet paths =
XCUIAssetDocumentSource::MakePathSet("Worker1CandidateOrder");
WriteProjectDocuments(paths);
WriteLegacyDocuments(paths);
const std::vector<XCUIAssetDocumentSource::ResolutionCandidate> candidates =
XCUIAssetDocumentSource::CollectCandidatePaths(paths.view, m_tempRoot, fs::path());
ASSERT_EQ(candidates.size(), 2u);
EXPECT_EQ(candidates[0].origin, XCUIAssetDocumentSource::PathOrigin::ProjectAssets);
EXPECT_EQ(candidates[0].resolvedPath, fs::path(m_tempRoot / paths.view.primaryRelativePath).lexically_normal());
EXPECT_EQ(candidates[1].origin, XCUIAssetDocumentSource::PathOrigin::LegacyMirror);
EXPECT_EQ(candidates[1].resolvedPath, fs::path(m_tempRoot / paths.view.legacyRelativePath).lexically_normal());
}
TEST_F(XCUIAssetDocumentSourceTest, DiagnoseRepositoryRootReportsProjectAssetAncestor) {
const XCUIAssetDocumentSource::PathSet paths =
XCUIAssetDocumentSource::MakeDemoPathSet();
WriteProjectDocuments(paths);
const fs::path searchRoot = CreateRepositorySubdir("tools/worker1/deep");
const XCUIAssetDocumentSource::RepositoryDiscovery discovery =
XCUIAssetDocumentSource::DiagnoseRepositoryRoot(paths, { searchRoot }, false);
EXPECT_EQ(discovery.repositoryRoot, m_tempRoot.lexically_normal());
ASSERT_EQ(discovery.probes.size(), 1u);
EXPECT_TRUE(discovery.probes[0].matched);
EXPECT_EQ(discovery.probes[0].searchRoot, searchRoot.lexically_normal());
EXPECT_EQ(discovery.probes[0].matchedRelativePath, paths.view.primaryRelativePath);
EXPECT_NE(discovery.statusMessage.find(paths.view.primaryRelativePath), std::string::npos);
}
TEST_F(XCUIAssetDocumentSourceTest, DiagnoseRepositoryRootReportsLegacyMirrorAncestor) {
const XCUIAssetDocumentSource::PathSet paths =
XCUIAssetDocumentSource::MakeLayoutLabPathSet();
WriteLegacyDocuments(paths);
const fs::path searchRoot = CreateRepositorySubdir("sandbox/runtime/session");
const XCUIAssetDocumentSource::RepositoryDiscovery discovery =
XCUIAssetDocumentSource::DiagnoseRepositoryRoot(paths, { searchRoot }, false);
EXPECT_EQ(discovery.repositoryRoot, m_tempRoot.lexically_normal());
ASSERT_EQ(discovery.probes.size(), 1u);
EXPECT_TRUE(discovery.probes[0].matched);
EXPECT_EQ(discovery.probes[0].matchedRelativePath, paths.view.legacyRelativePath);
EXPECT_NE(discovery.statusMessage.find(paths.view.legacyRelativePath), std::string::npos);
}
TEST_F(XCUIAssetDocumentSourceTest, ReloadUsesLegacyFallbackAndTracksSourceChanges) {
const XCUIAssetDocumentSource::PathSet paths =
XCUIAssetDocumentSource::MakePathSet("Worker1HotReloadRegression");
WriteLegacyDocuments(paths);
const fs::path sandboxPath = CreateRepositorySubdir("runtime/worker1");
ScopedCurrentPath scopedCurrentPath(sandboxPath);
XCUIAssetDocumentSource source(paths);
ASSERT_TRUE(source.Reload());
const XCUIAssetDocumentSource::LoadState& initialState = source.GetState();
EXPECT_TRUE(initialState.succeeded);
EXPECT_TRUE(initialState.usedLegacyFallback);
EXPECT_TRUE(initialState.changeTrackingReady);
EXPECT_TRUE(initialState.missingTrackedSourcePaths.empty());
EXPECT_EQ(initialState.repositoryRoot, m_tempRoot.lexically_normal());
EXPECT_EQ(initialState.view.pathOrigin, XCUIAssetDocumentSource::PathOrigin::LegacyMirror);
EXPECT_EQ(initialState.theme.pathOrigin, XCUIAssetDocumentSource::PathOrigin::LegacyMirror);
EXPECT_FALSE(initialState.trackedSourcePaths.empty());
EXPECT_NE(initialState.trackingStatusMessage.find("Tracking "), std::string::npos);
EXPECT_FALSE(source.HasTrackedChanges());
const fs::path themePath = m_tempRoot / fs::path(paths.theme.legacyRelativePath);
fs::last_write_time(
themePath,
fs::last_write_time(themePath) + std::chrono::seconds(2));
EXPECT_TRUE(source.HasTrackedChanges());
ASSERT_TRUE(source.ReloadIfChanged());
const XCUIAssetDocumentSource::LoadState& reloadedState = source.GetState();
EXPECT_EQ(
reloadedState.view.backend,
XCUIAssetDocumentSource::LoadBackend::CompilerFallback);
EXPECT_EQ(
reloadedState.theme.backend,
XCUIAssetDocumentSource::LoadBackend::CompilerFallback);
EXPECT_TRUE(reloadedState.changeTrackingReady);
}
TEST_F(XCUIAssetDocumentSourceTest, ReloadFailureIncludesRepositoryDiscoveryDiagnostic) {
const XCUIAssetDocumentSource::PathSet paths =
XCUIAssetDocumentSource::MakePathSet("Worker1MissingDocuments");
const fs::path sandboxPath = CreateRepositorySubdir("runtime/missing");
ScopedCurrentPath scopedCurrentPath(sandboxPath);
XCUIAssetDocumentSource source(paths);
EXPECT_FALSE(source.Reload());
const XCUIAssetDocumentSource::LoadState& state = source.GetState();
EXPECT_FALSE(state.succeeded);
EXPECT_TRUE(state.repositoryRoot.empty());
EXPECT_NE(state.repositoryDiscovery.statusMessage.find("Repository root not found"), std::string::npos);
EXPECT_NE(state.errorMessage.find(paths.view.primaryRelativePath), std::string::npos);
EXPECT_NE(state.errorMessage.find("Repository root not found"), std::string::npos);
EXPECT_TRUE(state.view.candidatePaths.empty());
EXPECT_TRUE(state.view.attemptMessages.empty());
}
} // namespace

View File

@@ -1,248 +0,0 @@
#include <gtest/gtest.h>
#include "panels/XCUIDemoPanel.h"
#include "XCUIBackend/XCUIHostedPreviewPresenter.h"
#include "XCUIBackend/XCUIPanelCanvasHost.h"
#include <imgui.h>
#include <memory>
#include <string>
#include <string_view>
namespace {
using XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter;
using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession;
using XCEngine::NewEditor::XCUIDemoPanel;
class ImGuiContextScope {
public:
ImGuiContextScope() {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui::StyleColorsDark();
}
~ImGuiContextScope() {
ImGui::DestroyContext();
}
};
void PrepareImGui(float width = 1280.0f, float height = 900.0f) {
ImGuiIO& io = ImGui::GetIO();
io.DisplaySize = ImVec2(width, height);
io.DeltaTime = 1.0f / 60.0f;
unsigned char* fontPixels = nullptr;
int fontWidth = 0;
int fontHeight = 0;
io.Fonts->GetTexDataAsRGBA32(&fontPixels, &fontWidth, &fontHeight);
io.Fonts->SetTexID(static_cast<ImTextureID>(1));
}
class RecordingHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter {
public:
bool Present(const XCUIHostedPreviewFrame& frame) override {
++presentCallCount;
lastCanvasRect = frame.canvasRect;
lastLogicalSize = frame.logicalSize;
lastDebugName = frame.debugName != nullptr ? frame.debugName : "";
m_lastStats = {};
if (frame.drawData == nullptr) {
return false;
}
lastDrawListCount = frame.drawData->GetDrawListCount();
lastCommandCount = frame.drawData->GetTotalCommandCount();
m_lastStats.presented = true;
m_lastStats.submittedDrawListCount = lastDrawListCount;
m_lastStats.submittedCommandCount = lastCommandCount;
return true;
}
const XCUIHostedPreviewStats& GetLastStats() const override {
return m_lastStats;
}
std::size_t presentCallCount = 0u;
XCEngine::UI::UIRect lastCanvasRect = {};
XCEngine::UI::UISize lastLogicalSize = {};
std::size_t lastDrawListCount = 0u;
std::size_t lastCommandCount = 0u;
std::string lastDebugName = {};
private:
XCUIHostedPreviewStats m_lastStats = {};
};
class StubCanvasHost final : public IXCUIPanelCanvasHost {
public:
const char* GetDebugName() const override {
return "StubCanvasHost";
}
XCUIPanelCanvasSession BeginCanvas(const XCUIPanelCanvasRequest& request) override {
lastRequest = request;
++beginCanvasCallCount;
return session;
}
void DrawFilledRect(
const XCEngine::UI::UIRect&,
const XCEngine::UI::UIColor&,
float) override {
}
void DrawOutlineRect(
const XCEngine::UI::UIRect&,
const XCEngine::UI::UIColor&,
float,
float) override {
}
void DrawText(
const XCEngine::UI::UIPoint&,
std::string_view,
const XCEngine::UI::UIColor&,
float) override {
}
void EndCanvas() override {
++endCanvasCallCount;
}
std::size_t beginCanvasCallCount = 0u;
std::size_t endCanvasCallCount = 0u;
XCUIPanelCanvasRequest lastRequest = {};
XCUIPanelCanvasSession session = {
XCEngine::UI::UIRect(0.0f, 0.0f, 960.0f, 640.0f),
XCEngine::UI::UIRect(12.0f, 18.0f, 936.0f, 512.0f),
XCEngine::UI::UIPoint(120.0f, 140.0f),
true,
true,
true
};
};
void RenderPanelFrame(XCUIDemoPanel& panel, ImGuiContextScope&) {
PrepareImGui();
ImGui::NewFrame();
panel.Render();
ImGui::Render();
}
TEST(NewEditorXCUIDemoPanelTest, DefaultConstructionDoesNotAutoCreateHostedPreviewPresenter) {
ImGuiContextScope contextScope;
auto canvasHost = std::make_unique<StubCanvasHost>();
XCUIDemoPanel panel;
panel.SetCanvasHost(std::move(canvasHost));
RenderPanelFrame(panel, contextScope);
const XCUIHostedPreviewStats& stats = panel.GetLastPreviewStats();
EXPECT_FALSE(stats.presented);
EXPECT_FALSE(stats.queuedToNativePass);
EXPECT_EQ(stats.submittedDrawListCount, 0u);
EXPECT_EQ(stats.submittedCommandCount, 0u);
EXPECT_EQ(stats.flushedDrawListCount, 0u);
EXPECT_EQ(stats.flushedCommandCount, 0u);
}
TEST(NewEditorXCUIDemoPanelTest, ConstructorUsesGenericNullCanvasHostUntilOuterLayerInjectsOne) {
ImGuiContextScope contextScope;
auto previewPresenter = std::make_unique<RecordingHostedPreviewPresenter>();
RecordingHostedPreviewPresenter* previewPresenterPtr = previewPresenter.get();
XCUIDemoPanel panel(nullptr, std::move(previewPresenter));
RenderPanelFrame(panel, contextScope);
ASSERT_EQ(previewPresenterPtr->presentCallCount, 1u);
EXPECT_EQ(previewPresenterPtr->lastDebugName, "XCUI Demo");
EXPECT_FLOAT_EQ(previewPresenterPtr->lastCanvasRect.x, 0.0f);
EXPECT_GT(previewPresenterPtr->lastCanvasRect.y, 0.0f);
EXPECT_FLOAT_EQ(previewPresenterPtr->lastCanvasRect.width, 0.0f);
EXPECT_GT(previewPresenterPtr->lastCanvasRect.height, 0.0f);
EXPECT_FLOAT_EQ(previewPresenterPtr->lastLogicalSize.width, 0.0f);
EXPECT_FLOAT_EQ(
previewPresenterPtr->lastLogicalSize.height,
previewPresenterPtr->lastCanvasRect.height);
}
TEST(NewEditorXCUIDemoPanelTest, ClearingHostedPreviewPresenterDoesNotRestoreImGuiFallback) {
ImGuiContextScope contextScope;
auto previewPresenter = std::make_unique<RecordingHostedPreviewPresenter>();
RecordingHostedPreviewPresenter* previewPresenterPtr = previewPresenter.get();
auto canvasHost = std::make_unique<StubCanvasHost>();
XCUIDemoPanel panel(nullptr, std::move(previewPresenter));
panel.SetCanvasHost(std::move(canvasHost));
RenderPanelFrame(panel, contextScope);
ASSERT_EQ(previewPresenterPtr->presentCallCount, 1u);
EXPECT_TRUE(panel.GetLastPreviewStats().presented);
panel.SetHostedPreviewPresenter(nullptr);
RenderPanelFrame(panel, contextScope);
const XCUIHostedPreviewStats& stats = panel.GetLastPreviewStats();
EXPECT_FALSE(stats.presented);
EXPECT_FALSE(stats.queuedToNativePass);
EXPECT_EQ(stats.submittedDrawListCount, 0u);
EXPECT_EQ(stats.submittedCommandCount, 0u);
EXPECT_EQ(stats.flushedDrawListCount, 0u);
EXPECT_EQ(stats.flushedCommandCount, 0u);
}
TEST(NewEditorXCUIDemoPanelTest, ComposeFrameBuildsShellAgnosticFrameState) {
auto previewPresenter = std::make_unique<RecordingHostedPreviewPresenter>();
RecordingHostedPreviewPresenter* previewPresenterPtr = previewPresenter.get();
auto canvasHost = std::make_unique<StubCanvasHost>();
StubCanvasHost* canvasHostPtr = canvasHost.get();
XCUIDemoPanel panel(nullptr, std::move(previewPresenter));
panel.SetCanvasHost(std::move(canvasHost));
const XCEngine::NewEditor::XCUIDemoPanelFrameComposition& composition =
panel.ComposeFrame(XCEngine::NewEditor::XCUIDemoPanelFrameComposeOptions{
512.0f,
24.0f,
true,
false,
false,
123456u,
"XCUIDemoTestCanvas"});
EXPECT_EQ(&composition, &panel.GetLastFrameComposition());
ASSERT_NE(composition.frame, nullptr);
EXPECT_TRUE(composition.hostedPreviewEnabled);
EXPECT_FALSE(composition.nativeHostedPreview);
EXPECT_FALSE(composition.hasHostedSurfaceDescriptor);
EXPECT_FALSE(composition.showHostedSurfaceImage);
EXPECT_EQ(composition.previewPathLabel, "hosted presenter");
EXPECT_EQ(composition.previewStateLabel, "live");
EXPECT_EQ(composition.inputSnapshot.timestampNanoseconds, 123456u);
EXPECT_EQ(composition.canvasSession.canvasRect.width, canvasHostPtr->session.canvasRect.width);
EXPECT_EQ(composition.canvasSession.canvasRect.height, canvasHostPtr->session.canvasRect.height);
EXPECT_EQ(composition.input.canvasRect.width, canvasHostPtr->session.canvasRect.width);
EXPECT_EQ(composition.input.canvasRect.height, canvasHostPtr->session.canvasRect.height);
EXPECT_EQ(canvasHostPtr->beginCanvasCallCount, 1u);
EXPECT_EQ(canvasHostPtr->endCanvasCallCount, 1u);
EXPECT_FLOAT_EQ(canvasHostPtr->lastRequest.height, 512.0f);
EXPECT_FLOAT_EQ(canvasHostPtr->lastRequest.topInset, 24.0f);
EXPECT_STREQ(canvasHostPtr->lastRequest.childId, "XCUIDemoTestCanvas");
EXPECT_EQ(previewPresenterPtr->presentCallCount, 1u);
EXPECT_EQ(previewPresenterPtr->lastDebugName, "XCUI Demo");
EXPECT_EQ(composition.previewStats.submittedDrawListCount, panel.GetLastPreviewStats().submittedDrawListCount);
EXPECT_EQ(composition.previewStats.submittedCommandCount, panel.GetLastPreviewStats().submittedCommandCount);
}
} // namespace

View File

@@ -1,617 +0,0 @@
#include <gtest/gtest.h>
#include "XCUIBackend/XCUIDemoRuntime.h"
#include <XCEngine/Input/InputTypes.h>
#include <XCEngine/UI/Types.h>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
namespace {
namespace fs = std::filesystem;
using XCEngine::UI::UIDrawCommand;
using XCEngine::UI::UIDrawCommandType;
using XCEngine::UI::UIInputEvent;
using XCEngine::UI::UIInputEventType;
XCEngine::Editor::XCUIBackend::XCUIDemoInputState BuildInputState(
float width = 720.0f,
float height = 420.0f) {
XCEngine::Editor::XCUIBackend::XCUIDemoInputState input = {};
input.canvasRect = XCEngine::UI::UIRect(0.0f, 0.0f, width, height);
input.pointerPosition = XCEngine::UI::UIPoint(width * 0.5f, height * 0.5f);
input.pointerInside = true;
return input;
}
UIInputEvent MakeCharacterEvent(std::uint32_t character) {
UIInputEvent event = {};
event.type = UIInputEventType::Character;
event.character = character;
return event;
}
UIInputEvent MakeKeyDownEvent(
XCEngine::Input::KeyCode keyCode,
bool repeat = false,
bool shift = false,
bool control = false,
bool alt = false,
bool super = false) {
UIInputEvent event = {};
event.type = UIInputEventType::KeyDown;
event.keyCode = static_cast<std::int32_t>(keyCode);
event.repeat = repeat;
event.modifiers.shift = shift;
event.modifiers.control = control;
event.modifiers.alt = alt;
event.modifiers.super = super;
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) {
const fs::path canonicalCandidate = probe / "Assets/XCUI/NewEditor/Demo/View.xcui";
if (fs::exists(canonicalCandidate)) {
return canonicalCandidate;
}
const fs::path legacyCandidate = probe / "new_editor/resources/xcui_demo_view.xcui";
if (fs::exists(legacyCandidate)) {
return legacyCandidate;
}
if (!probe.has_parent_path()) {
break;
}
probe = probe.parent_path();
}
return {};
}
std::vector<const UIDrawCommand*> CollectTextCommands(const XCEngine::UI::UIDrawData& drawData) {
std::vector<const UIDrawCommand*> textCommands = {};
for (const XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
for (const UIDrawCommand& command : drawList.GetCommands()) {
if (command.type == UIDrawCommandType::Text) {
textCommands.push_back(&command);
}
}
}
return textCommands;
}
const UIDrawCommand* FindTextCommand(
const XCEngine::UI::UIDrawData& drawData,
const std::string& text) {
for (const XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
for (const UIDrawCommand& command : drawList.GetCommands()) {
if (command.type == UIDrawCommandType::Text && command.text == text) {
return &command;
}
}
}
return nullptr;
}
class FileTimestampRestoreScope {
public:
explicit FileTimestampRestoreScope(fs::path path)
: m_path(std::move(path)) {
if (!m_path.empty() && fs::exists(m_path)) {
m_originalWriteTime = fs::last_write_time(m_path);
std::ifstream input(m_path, std::ios::binary);
std::ostringstream stream;
stream << input.rdbuf();
m_originalContents = stream.str();
m_valid = true;
}
}
~FileTimestampRestoreScope() {
if (m_valid) {
std::ofstream output(m_path, std::ios::binary | std::ios::trunc);
output << m_originalContents;
output.close();
fs::last_write_time(m_path, m_originalWriteTime);
}
}
private:
fs::path m_path;
fs::file_time_type m_originalWriteTime = {};
std::string m_originalContents = {};
bool m_valid = false;
};
} // namespace
TEST(NewEditorXCUIDemoRuntimeTest, UpdateProvidesDeterministicFrameContainer) {
XCEngine::Editor::XCUIBackend::XCUIDemoRuntime runtime;
const bool reloadSucceeded = runtime.ReloadDocuments();
const auto& firstFrame = runtime.Update(BuildInputState());
EXPECT_EQ(firstFrame.stats.documentsReady, reloadSucceeded);
EXPECT_EQ(firstFrame.stats.drawListCount, firstFrame.drawData.GetDrawListCount());
EXPECT_EQ(firstFrame.stats.commandCount, firstFrame.drawData.GetTotalCommandCount());
const auto& secondFrame = runtime.Update(BuildInputState());
EXPECT_GE(secondFrame.stats.treeGeneration, firstFrame.stats.treeGeneration);
EXPECT_EQ(secondFrame.stats.drawListCount, secondFrame.drawData.GetDrawListCount());
EXPECT_EQ(secondFrame.stats.commandCount, secondFrame.drawData.GetTotalCommandCount());
if (secondFrame.stats.documentsReady) {
EXPECT_GT(secondFrame.stats.elementCount, 0u);
EXPECT_GT(secondFrame.stats.drawListCount, 0u);
EXPECT_GT(secondFrame.stats.commandCount, 0u);
} else {
EXPECT_FALSE(secondFrame.stats.statusMessage.empty());
}
}
TEST(NewEditorXCUIDemoRuntimeTest, RuntimeFrameEmitsTextCommandsWithResolvedFontSizes) {
XCEngine::Editor::XCUIBackend::XCUIDemoRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& frame = runtime.Update(BuildInputState());
ASSERT_TRUE(frame.stats.documentsReady);
const std::vector<const UIDrawCommand*> textCommands = CollectTextCommands(frame.drawData);
ASSERT_FALSE(textCommands.empty());
for (const UIDrawCommand* command : textCommands) {
ASSERT_NE(command, nullptr);
EXPECT_FALSE(command->text.empty());
EXPECT_GT(command->fontSize, 0.0f);
}
const UIDrawCommand* titleCommand = FindTextCommand(frame.drawData, "New XCUI Shell");
ASSERT_NE(titleCommand, nullptr);
EXPECT_FLOAT_EQ(titleCommand->fontSize, 18.0f);
const UIDrawCommand* metricValueCommand = FindTextCommand(frame.drawData, "Driven by runtime");
ASSERT_NE(metricValueCommand, nullptr);
EXPECT_FLOAT_EQ(metricValueCommand->fontSize, 18.0f);
const UIDrawCommand* buttonLabelCommand = FindTextCommand(frame.drawData, "Toggle Accent");
ASSERT_NE(buttonLabelCommand, nullptr);
EXPECT_FLOAT_EQ(buttonLabelCommand->fontSize, 14.0f);
EXPECT_NE(
FindTextCommand(frame.drawData, "Single-line input, Enter submits, 0 chars"),
nullptr);
EXPECT_NE(
FindTextCommand(frame.drawData, "Multiline input, click caret, Tab indent, 1 lines"),
nullptr);
}
TEST(NewEditorXCUIDemoRuntimeTest, InputStateTransitionsAreAcceptedAndFrameStillBuilds) {
XCEngine::Editor::XCUIBackend::XCUIDemoRuntime runtime;
runtime.ReloadDocuments();
XCEngine::Editor::XCUIBackend::XCUIDemoInputState frameInput = BuildInputState();
frameInput.pointerPressed = true;
frameInput.pointerDown = true;
const auto& pressedFrame = runtime.Update(frameInput);
frameInput.pointerPressed = false;
frameInput.pointerReleased = true;
frameInput.pointerDown = false;
frameInput.shortcutPressed = true;
const auto& releasedFrame = runtime.Update(frameInput);
EXPECT_GE(releasedFrame.stats.treeGeneration, pressedFrame.stats.treeGeneration);
EXPECT_EQ(releasedFrame.stats.drawListCount, releasedFrame.drawData.GetDrawListCount());
EXPECT_EQ(releasedFrame.stats.commandCount, releasedFrame.drawData.GetTotalCommandCount());
if (releasedFrame.stats.documentsReady) {
EXPECT_GT(releasedFrame.stats.elementCount, 0u);
EXPECT_GE(releasedFrame.stats.dirtyRootCount, 0u);
}
}
TEST(NewEditorXCUIDemoRuntimeTest, DrainPendingCommandIdsReturnsEmptyForFramesWithoutCommands) {
XCEngine::Editor::XCUIBackend::XCUIDemoRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& frame = runtime.Update(BuildInputState());
ASSERT_TRUE(frame.stats.documentsReady);
const std::vector<std::string> drainedCommandIds = runtime.DrainPendingCommandIds();
EXPECT_TRUE(drainedCommandIds.empty());
EXPECT_TRUE(runtime.DrainPendingCommandIds().empty());
}
TEST(NewEditorXCUIDemoRuntimeTest, DrainPendingCommandIdsCapturesPointerActivationCommands) {
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 buttonRect = {};
ASSERT_TRUE(runtime.TryGetElementRect("toggleAccent", buttonRect));
const XCEngine::UI::UIPoint buttonCenter(
buttonRect.x + buttonRect.width * 0.5f,
buttonRect.y + buttonRect.height * 0.5f);
XCEngine::Editor::XCUIBackend::XCUIDemoInputState pressedInput = BuildInputState();
pressedInput.pointerPosition = buttonCenter;
pressedInput.pointerPressed = true;
pressedInput.pointerDown = true;
runtime.Update(pressedInput);
EXPECT_TRUE(runtime.DrainPendingCommandIds().empty());
XCEngine::Editor::XCUIBackend::XCUIDemoInputState releasedInput = BuildInputState();
releasedInput.pointerPosition = buttonCenter;
releasedInput.pointerReleased = true;
const auto& toggledFrame = runtime.Update(releasedInput);
ASSERT_TRUE(toggledFrame.stats.documentsReady);
EXPECT_EQ(runtime.DrainPendingCommandIds(), std::vector<std::string>({ "demo.toggleAccent" }));
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());
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 pressedInput = BuildInputState();
pressedInput.pointerPosition = promptCenter;
pressedInput.pointerPressed = true;
pressedInput.pointerDown = true;
runtime.Update(pressedInput);
EXPECT_TRUE(runtime.DrainPendingCommandIds().empty());
XCEngine::Editor::XCUIBackend::XCUIDemoInputState releasedInput = BuildInputState();
releasedInput.pointerPosition = promptCenter;
releasedInput.pointerReleased = true;
const auto& focusedFrame = runtime.Update(releasedInput);
ASSERT_TRUE(focusedFrame.stats.documentsReady);
EXPECT_EQ(focusedFrame.stats.focusedElementId, "agentPrompt");
EXPECT_EQ(
runtime.DrainPendingCommandIds(),
std::vector<std::string>({ "demo.activate.agentPrompt" }));
XCEngine::Editor::XCUIBackend::XCUIDemoInputState textInput = BuildInputState();
textInput.events.push_back(MakeCharacterEvent('A'));
textInput.events.push_back(MakeCharacterEvent('I'));
textInput.events.push_back(MakeKeyDownEvent(XCEngine::Input::KeyCode::Backspace));
const auto& typedFrame = runtime.Update(textInput);
ASSERT_TRUE(typedFrame.stats.documentsReady);
EXPECT_EQ(typedFrame.stats.focusedElementId, "agentPrompt");
EXPECT_EQ(
runtime.DrainPendingCommandIds(),
std::vector<std::string>({
"demo.text.edit.agentPrompt",
"demo.text.edit.agentPrompt",
"demo.text.edit.agentPrompt" }));
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());
const auto& baselineFrame = runtime.Update(BuildInputState());
ASSERT_TRUE(baselineFrame.stats.documentsReady);
EXPECT_FALSE(baselineFrame.stats.accentEnabled);
EXPECT_NE(
FindTextCommand(baselineFrame.drawData, "Markup -> Layout -> Style -> DrawData"),
nullptr);
XCEngine::UI::UIRect buttonRect = {};
ASSERT_TRUE(runtime.TryGetElementRect("toggleAccent", buttonRect));
const XCEngine::UI::UIPoint buttonCenter(
buttonRect.x + buttonRect.width * 0.5f,
buttonRect.y + buttonRect.height * 0.5f);
XCEngine::Editor::XCUIBackend::XCUIDemoInputState pressedInput = BuildInputState();
pressedInput.pointerPosition = buttonCenter;
pressedInput.pointerPressed = true;
pressedInput.pointerDown = true;
const auto& pressedFrame = runtime.Update(pressedInput);
ASSERT_TRUE(pressedFrame.stats.documentsReady);
XCEngine::Editor::XCUIBackend::XCUIDemoInputState releasedInput = BuildInputState();
releasedInput.pointerPosition = buttonCenter;
releasedInput.pointerReleased = true;
const auto& toggledFrame = runtime.Update(releasedInput);
ASSERT_TRUE(toggledFrame.stats.documentsReady);
EXPECT_TRUE(toggledFrame.stats.accentEnabled);
EXPECT_EQ(toggledFrame.stats.lastCommandId, "demo.toggleAccent");
EXPECT_EQ(toggledFrame.stats.focusedElementId, "toggleAccent");
const UIDrawCommand* focusStatusCommand = FindTextCommand(
toggledFrame.drawData,
"Focus: toggleAccent");
ASSERT_NE(focusStatusCommand, nullptr);
EXPECT_FLOAT_EQ(focusStatusCommand->fontSize, 14.0f);
}
TEST(NewEditorXCUIDemoRuntimeTest, UpdateAutoReloadsWhenSourceTimestampChanges) {
const fs::path viewPath = FindDemoResourcePath();
ASSERT_FALSE(viewPath.empty());
ASSERT_TRUE(fs::exists(viewPath));
FileTimestampRestoreScope restoreScope(viewPath);
XCEngine::Editor::XCUIBackend::XCUIDemoRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& baselineFrame = runtime.Update(BuildInputState());
ASSERT_TRUE(baselineFrame.stats.documentsReady);
XCEngine::UI::UIRect probeRect = {};
EXPECT_FALSE(runtime.TryGetElementRect("autoReloadProbe", probeRect));
std::ifstream input(viewPath, std::ios::binary);
std::ostringstream stream;
stream << input.rdbuf();
const std::string originalContents = stream.str();
input.close();
const std::string marker = "</Column>\n</View>";
const std::size_t insertPosition = originalContents.rfind(marker);
ASSERT_NE(insertPosition, std::string::npos);
const std::string injectedNode =
" <Text id=\"autoReloadProbe\" text=\"Auto Reload Probe\" style=\"Meta\" />\n";
std::string modifiedContents = originalContents;
modifiedContents.insert(insertPosition, injectedNode);
std::ofstream output(viewPath, std::ios::binary | std::ios::trunc);
output << modifiedContents;
output.close();
const fs::file_time_type originalWriteTime = fs::last_write_time(viewPath);
fs::last_write_time(viewPath, originalWriteTime + std::chrono::seconds(2));
const auto& reloadedFrame = runtime.Update(BuildInputState());
EXPECT_TRUE(reloadedFrame.stats.documentsReady);
EXPECT_GT(reloadedFrame.stats.elementCount, 0u);
EXPECT_GT(reloadedFrame.stats.commandCount, 0u);
EXPECT_TRUE(runtime.TryGetElementRect("autoReloadProbe", probeRect));
}
TEST(NewEditorXCUIDemoRuntimeTest, TextFieldAcceptsUtf8CharactersAndBackspace) {
XCEngine::Editor::XCUIBackend::XCUIDemoRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& baselineFrame = runtime.Update(BuildInputState());
ASSERT_TRUE(baselineFrame.stats.documentsReady);
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 pressedInput = BuildInputState();
pressedInput.pointerPosition = promptCenter;
pressedInput.pointerPressed = true;
pressedInput.pointerDown = true;
runtime.Update(pressedInput);
XCEngine::Editor::XCUIBackend::XCUIDemoInputState releasedInput = BuildInputState();
releasedInput.pointerPosition = promptCenter;
releasedInput.pointerReleased = true;
const auto& focusedFrame = runtime.Update(releasedInput);
ASSERT_TRUE(focusedFrame.stats.documentsReady);
EXPECT_EQ(focusedFrame.stats.focusedElementId, "agentPrompt");
XCEngine::Editor::XCUIBackend::XCUIDemoInputState textInput = BuildInputState();
textInput.events.push_back(MakeCharacterEvent('A'));
textInput.events.push_back(MakeCharacterEvent('I'));
textInput.events.push_back(MakeCharacterEvent(0x4F60u));
const auto& typedFrame = runtime.Update(textInput);
ASSERT_TRUE(typedFrame.stats.documentsReady);
EXPECT_EQ(typedFrame.stats.focusedElementId, "agentPrompt");
EXPECT_NE(FindTextCommand(typedFrame.drawData, "AI你"), nullptr);
EXPECT_EQ(typedFrame.stats.lastCommandId, "demo.text.edit.agentPrompt");
XCEngine::Editor::XCUIBackend::XCUIDemoInputState backspaceInput = BuildInputState();
backspaceInput.events.push_back(MakeKeyDownEvent(XCEngine::Input::KeyCode::Backspace));
const auto& backspacedFrame = runtime.Update(backspaceInput);
ASSERT_TRUE(backspacedFrame.stats.documentsReady);
EXPECT_NE(FindTextCommand(backspacedFrame.drawData, "AI"), nullptr);
EXPECT_EQ(backspacedFrame.stats.focusedElementId, "agentPrompt");
}
TEST(NewEditorXCUIDemoRuntimeTest, TextAreaAcceptsMultilineInputAndCaretMovement) {
XCEngine::Editor::XCUIBackend::XCUIDemoRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& baselineFrame = runtime.Update(BuildInputState());
ASSERT_TRUE(baselineFrame.stats.documentsReady);
XCEngine::UI::UIRect notesRect = {};
ASSERT_TRUE(runtime.TryGetElementRect("sessionNotes", notesRect));
const XCEngine::UI::UIPoint notesCenter(
notesRect.x + notesRect.width * 0.5f,
notesRect.y + notesRect.height * 0.5f);
XCEngine::Editor::XCUIBackend::XCUIDemoInputState pressedInput = BuildInputState();
pressedInput.pointerPosition = notesCenter;
pressedInput.pointerPressed = true;
pressedInput.pointerDown = true;
runtime.Update(pressedInput);
XCEngine::Editor::XCUIBackend::XCUIDemoInputState releasedInput = BuildInputState();
releasedInput.pointerPosition = notesCenter;
releasedInput.pointerReleased = true;
const auto& focusedFrame = runtime.Update(releasedInput);
ASSERT_TRUE(focusedFrame.stats.documentsReady);
EXPECT_EQ(focusedFrame.stats.focusedElementId, "sessionNotes");
XCEngine::Editor::XCUIBackend::XCUIDemoInputState textInput = BuildInputState();
textInput.events.push_back(MakeCharacterEvent('O'));
textInput.events.push_back(MakeCharacterEvent('K'));
textInput.events.push_back(MakeKeyDownEvent(XCEngine::Input::KeyCode::Enter));
textInput.events.push_back(MakeCharacterEvent('X'));
const auto& typedFrame = runtime.Update(textInput);
ASSERT_TRUE(typedFrame.stats.documentsReady);
EXPECT_EQ(typedFrame.stats.focusedElementId, "sessionNotes");
EXPECT_EQ(typedFrame.stats.lastCommandId, "demo.text.edit.sessionNotes");
EXPECT_NE(FindTextCommand(typedFrame.drawData, "1"), nullptr);
EXPECT_NE(FindTextCommand(typedFrame.drawData, "2"), nullptr);
EXPECT_NE(FindTextCommand(typedFrame.drawData, "OK"), nullptr);
EXPECT_NE(FindTextCommand(typedFrame.drawData, "X"), nullptr);
EXPECT_NE(
FindTextCommand(typedFrame.drawData, "Multiline input, click caret, Tab indent, 2 lines"),
nullptr);
const UIDrawCommand* secondLineText = FindTextCommand(typedFrame.drawData, "X");
ASSERT_NE(secondLineText, nullptr);
XCEngine::Editor::XCUIBackend::XCUIDemoInputState secondLinePressed = BuildInputState();
secondLinePressed.pointerPosition = XCEngine::UI::UIPoint(
secondLineText->position.x + 1.0f,
secondLineText->position.y + secondLineText->fontSize * 0.5f);
secondLinePressed.pointerPressed = true;
secondLinePressed.pointerDown = true;
runtime.Update(secondLinePressed);
XCEngine::Editor::XCUIBackend::XCUIDemoInputState secondLineReleased = BuildInputState();
secondLineReleased.pointerPosition = secondLinePressed.pointerPosition;
secondLineReleased.pointerReleased = true;
const auto& secondLineCaretFrame = runtime.Update(secondLineReleased);
ASSERT_TRUE(secondLineCaretFrame.stats.documentsReady);
EXPECT_EQ(secondLineCaretFrame.stats.focusedElementId, "sessionNotes");
XCEngine::Editor::XCUIBackend::XCUIDemoInputState tabInput = BuildInputState();
tabInput.events.push_back(MakeKeyDownEvent(XCEngine::Input::KeyCode::Tab));
const auto& indentedFrame = runtime.Update(tabInput);
ASSERT_TRUE(indentedFrame.stats.documentsReady);
EXPECT_EQ(indentedFrame.stats.focusedElementId, "sessionNotes");
EXPECT_EQ(indentedFrame.stats.lastCommandId, "demo.text.edit.sessionNotes");
EXPECT_NE(FindTextCommand(indentedFrame.drawData, "OK"), nullptr);
EXPECT_NE(FindTextCommand(indentedFrame.drawData, " X"), nullptr);
const UIDrawCommand* firstLineText = FindTextCommand(indentedFrame.drawData, "OK");
ASSERT_NE(firstLineText, nullptr);
XCEngine::Editor::XCUIBackend::XCUIDemoInputState firstLinePressed = BuildInputState();
firstLinePressed.pointerPosition = XCEngine::UI::UIPoint(
firstLineText->position.x + 1.0f,
firstLineText->position.y + firstLineText->fontSize * 0.5f);
firstLinePressed.pointerPressed = true;
firstLinePressed.pointerDown = true;
runtime.Update(firstLinePressed);
XCEngine::Editor::XCUIBackend::XCUIDemoInputState firstLineReleased = BuildInputState();
firstLineReleased.pointerPosition = firstLinePressed.pointerPosition;
firstLineReleased.pointerReleased = true;
const auto& caretFrame = runtime.Update(firstLineReleased);
ASSERT_TRUE(caretFrame.stats.documentsReady);
EXPECT_EQ(caretFrame.stats.focusedElementId, "sessionNotes");
XCEngine::Editor::XCUIBackend::XCUIDemoInputState editInput = BuildInputState();
editInput.events.push_back(MakeCharacterEvent('!'));
const auto& editedFrame = runtime.Update(editInput);
ASSERT_TRUE(editedFrame.stats.documentsReady);
EXPECT_NE(FindTextCommand(editedFrame.drawData, "!OK"), nullptr);
EXPECT_NE(FindTextCommand(editedFrame.drawData, " X"), nullptr);
EXPECT_EQ(editedFrame.stats.focusedElementId, "sessionNotes");
}

View File

@@ -1,205 +0,0 @@
#include "XCUIBackend/XCUIEditorCommandRouter.h"
#include <XCEngine/Input/InputTypes.h>
#include <gtest/gtest.h>
namespace {
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandAccelerator;
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandDefinition;
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot;
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandKeyState;
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandRouter;
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandShortcutMatch;
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandShortcutQuery;
using XCEngine::Input::KeyCode;
XCUIEditorCommandKeyState MakeKeyState(std::int32_t keyCode, bool down, bool repeat = false) {
XCUIEditorCommandKeyState state = {};
state.keyCode = keyCode;
state.down = down;
state.repeat = repeat;
return state;
}
XCUIEditorCommandInputSnapshot MakeSnapshot(
std::initializer_list<XCUIEditorCommandKeyState> keys,
const XCEngine::UI::UIInputModifiers& modifiers = {},
bool windowFocused = true) {
XCUIEditorCommandInputSnapshot snapshot = {};
snapshot.keys.assign(keys.begin(), keys.end());
snapshot.modifiers = modifiers;
snapshot.windowFocused = windowFocused;
return snapshot;
}
TEST(XCUIEditorCommandRouterTest, RegisterInvokeAndUnregisterTrackCommandsById) {
XCUIEditorCommandRouter router = {};
int invokeCount = 0;
XCUIEditorCommandDefinition definition = {};
definition.commandId = "xcui.file.save";
definition.invoke = [&invokeCount]() { ++invokeCount; };
EXPECT_TRUE(router.RegisterCommand(definition));
EXPECT_EQ(router.GetCommandCount(), 1u);
EXPECT_TRUE(router.HasCommand("xcui.file.save"));
EXPECT_TRUE(router.IsCommandEnabled("xcui.file.save"));
EXPECT_TRUE(router.InvokeCommand("xcui.file.save"));
EXPECT_EQ(invokeCount, 1);
EXPECT_TRUE(router.UnregisterCommand("xcui.file.save"));
EXPECT_FALSE(router.HasCommand("xcui.file.save"));
EXPECT_FALSE(router.InvokeCommand("xcui.file.save"));
EXPECT_EQ(router.GetCommandCount(), 0u);
}
TEST(XCUIEditorCommandRouterTest, RegisterRejectsMissingIdOrHandlerAndDuplicateIdReplacesEntry) {
XCUIEditorCommandRouter router = {};
int invokeCount = 0;
XCUIEditorCommandDefinition invalid = {};
invalid.commandId = "xcui.invalid";
EXPECT_FALSE(router.RegisterCommand(invalid));
invalid = {};
invalid.invoke = []() {};
EXPECT_FALSE(router.RegisterCommand(invalid));
XCUIEditorCommandDefinition first = {};
first.commandId = "xcui.edit.rename";
first.invoke = [&invokeCount]() { invokeCount += 1; };
first.accelerators.push_back({ static_cast<std::int32_t>(KeyCode::F2), {}, true, false });
ASSERT_TRUE(router.RegisterCommand(first));
XCUIEditorCommandDefinition replacement = {};
replacement.commandId = "xcui.edit.rename";
replacement.invoke = [&invokeCount]() { invokeCount += 10; };
replacement.accelerators.push_back({ static_cast<std::int32_t>(KeyCode::Enter), {}, true, false });
ASSERT_TRUE(router.RegisterCommand(replacement));
EXPECT_EQ(router.GetCommandCount(), 1u);
EXPECT_TRUE(router.InvokeCommand("xcui.edit.rename"));
EXPECT_EQ(invokeCount, 10);
const XCUIEditorCommandInputSnapshot f2Snapshot =
MakeSnapshot({ MakeKeyState(static_cast<std::int32_t>(KeyCode::F2), true) });
const auto f2Match = router.MatchShortcut({ &f2Snapshot });
EXPECT_FALSE(f2Match.matched);
const XCUIEditorCommandInputSnapshot enterSnapshot =
MakeSnapshot({ MakeKeyState(static_cast<std::int32_t>(KeyCode::Enter), true) });
const auto enterMatch = router.MatchShortcut({ &enterSnapshot });
EXPECT_TRUE(enterMatch.matched);
EXPECT_EQ(enterMatch.commandId, "xcui.edit.rename");
}
TEST(XCUIEditorCommandRouterTest, DisabledPredicateBlocksDirectAndShortcutInvocation) {
XCUIEditorCommandRouter router = {};
bool enabled = false;
int invokeCount = 0;
XCUIEditorCommandDefinition definition = {};
definition.commandId = "xcui.edit.delete";
definition.invoke = [&invokeCount]() { ++invokeCount; };
definition.isEnabled = [&enabled]() { return enabled; };
definition.accelerators.push_back({ static_cast<std::int32_t>(KeyCode::Delete), {}, true, false });
ASSERT_TRUE(router.RegisterCommand(definition));
const XCUIEditorCommandInputSnapshot snapshot =
MakeSnapshot({ MakeKeyState(static_cast<std::int32_t>(KeyCode::Delete), true) });
EXPECT_FALSE(router.IsCommandEnabled("xcui.edit.delete"));
EXPECT_FALSE(router.InvokeCommand("xcui.edit.delete"));
EXPECT_FALSE(router.MatchShortcut({ &snapshot }).matched);
EXPECT_FALSE(router.InvokeMatchingShortcut({ &snapshot }));
EXPECT_EQ(invokeCount, 0);
enabled = true;
EXPECT_TRUE(router.IsCommandEnabled("xcui.edit.delete"));
EXPECT_TRUE(router.InvokeMatchingShortcut({ &snapshot }));
EXPECT_EQ(invokeCount, 1);
}
TEST(XCUIEditorCommandRouterTest, ShortcutMatchingRespectsModifiersRepeatAndPolicyFlags) {
XCUIEditorCommandRouter router = {};
int invokeCount = 0;
XCEngine::UI::UIInputModifiers modifiers = {};
modifiers.control = true;
modifiers.shift = true;
XCUIEditorCommandDefinition definition = {};
definition.commandId = "xcui.search.command_palette";
definition.invoke = [&invokeCount]() { ++invokeCount; };
definition.accelerators.push_back({
static_cast<std::int32_t>(KeyCode::P),
modifiers,
true,
false });
ASSERT_TRUE(router.RegisterCommand(definition));
const XCUIEditorCommandInputSnapshot exactSnapshot = MakeSnapshot(
{ MakeKeyState(static_cast<std::int32_t>(KeyCode::P), true) },
modifiers);
auto match = router.MatchShortcut({ &exactSnapshot });
ASSERT_TRUE(match.matched);
EXPECT_EQ(match.commandId, "xcui.search.command_palette");
const XCUIEditorCommandInputSnapshot repeatedSnapshot = MakeSnapshot(
{ MakeKeyState(static_cast<std::int32_t>(KeyCode::P), true, true) },
modifiers);
EXPECT_FALSE(router.MatchShortcut({ &repeatedSnapshot }).matched);
XCUIEditorCommandInputSnapshot captureSnapshot = exactSnapshot;
captureSnapshot.wantCaptureKeyboard = true;
EXPECT_FALSE(router.MatchShortcut({ &captureSnapshot }).matched);
EXPECT_TRUE(router.MatchShortcut({ &captureSnapshot, true, true, false }).matched);
XCUIEditorCommandInputSnapshot textInputSnapshot = exactSnapshot;
textInputSnapshot.wantTextInput = true;
EXPECT_FALSE(router.MatchShortcut({ &textInputSnapshot }).matched);
EXPECT_TRUE(router.MatchShortcut({ &textInputSnapshot, true, false, true }).matched);
XCUIEditorCommandInputSnapshot unfocusedSnapshot = exactSnapshot;
unfocusedSnapshot.windowFocused = false;
EXPECT_FALSE(router.MatchShortcut({ &unfocusedSnapshot }).matched);
EXPECT_TRUE(router.MatchShortcut({ &unfocusedSnapshot, false, false, false }).matched);
XCUIEditorCommandShortcutMatch invokedMatch = {};
EXPECT_TRUE(router.InvokeMatchingShortcut({ &exactSnapshot }, &invokedMatch));
EXPECT_TRUE(invokedMatch.matched);
EXPECT_EQ(invokedMatch.commandId, "xcui.search.command_palette");
EXPECT_EQ(invokeCount, 1);
}
TEST(XCUIEditorCommandRouterTest, NonExactModifierAcceleratorsAllowAdditionalPressedModifiers) {
XCUIEditorCommandRouter router = {};
XCEngine::UI::UIInputModifiers requiredModifiers = {};
requiredModifiers.control = true;
XCUIEditorCommandDefinition definition = {};
definition.commandId = "xcui.edit.duplicate";
definition.invoke = []() {};
definition.accelerators.push_back({
static_cast<std::int32_t>(KeyCode::D),
requiredModifiers,
false,
false });
ASSERT_TRUE(router.RegisterCommand(definition));
XCEngine::UI::UIInputModifiers actualModifiers = requiredModifiers;
actualModifiers.shift = true;
const XCUIEditorCommandInputSnapshot permissiveSnapshot = MakeSnapshot(
{ MakeKeyState(static_cast<std::int32_t>(KeyCode::D), true) },
actualModifiers);
EXPECT_TRUE(router.MatchShortcut({ &permissiveSnapshot }).matched);
XCUIEditorCommandRouter exactRouter = {};
definition.accelerators.front().exactModifiers = true;
ASSERT_TRUE(exactRouter.RegisterCommand(definition));
EXPECT_FALSE(exactRouter.MatchShortcut({ &permissiveSnapshot }).matched);
}
} // namespace

View File

@@ -1,827 +0,0 @@
#include <gtest/gtest.h>
#include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h"
#include "XCUIBackend/XCUIHostedPreviewPresenter.h"
#include <XCEngine/UI/DrawData.h>
#include <imgui.h>
#include <cstdint>
namespace {
using XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter;
using XCEngine::Editor::XCUIBackend::CreateNullXCUIHostedPreviewPresenter;
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;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewQueue;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewQueuedFrame;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceRegistry;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats;
class ImGuiContextScope {
public:
ImGuiContextScope() {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui::StyleColorsDark();
}
~ImGuiContextScope() {
ImGui::DestroyContext();
}
};
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);
io.DeltaTime = 1.0f / 60.0f;
unsigned char* fontPixels = nullptr;
int fontWidth = 0;
int fontHeight = 0;
io.Fonts->GetTexDataAsRGBA32(&fontPixels, &fontWidth, &fontHeight);
io.Fonts->SetTexID(static_cast<ImTextureID>(1));
}
XCEngine::UI::UITextureHandle MakeHostedPreviewTextureHandle(
std::uintptr_t nativeHandle,
std::uint32_t width,
std::uint32_t height) {
XCEngine::UI::UITextureHandle texture = {};
texture.nativeHandle = nativeHandle;
texture.width = width;
texture.height = height;
texture.kind = XCEngine::UI::UITextureHandleKind::ImGuiDescriptor;
return texture;
}
TEST(XCUIHostedPreviewPresenterTest, PresentReturnsFalseAndClearsStatsWhenFrameHasNoDrawData) {
ImGuiContextScope contextScope;
PrepareImGui();
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter = CreateImGuiXCUIHostedPreviewPresenter();
ASSERT_NE(presenter, nullptr);
XCUIHostedPreviewFrame frame = {};
const bool presented = presenter->Present(frame);
const XCUIHostedPreviewStats& stats = presenter->GetLastStats();
EXPECT_FALSE(presented);
EXPECT_FALSE(stats.presented);
EXPECT_EQ(stats.submittedDrawListCount, 0u);
EXPECT_EQ(stats.submittedCommandCount, 0u);
EXPECT_EQ(stats.flushedDrawListCount, 0u);
EXPECT_EQ(stats.flushedCommandCount, 0u);
}
TEST(XCUIHostedPreviewPresenterTest, NullPresenterReturnsFalseAndClearsStatsWhenFrameHasNoDrawData) {
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter = CreateNullXCUIHostedPreviewPresenter();
ASSERT_NE(presenter, nullptr);
XCUIHostedPreviewFrame frame = {};
const bool presented = presenter->Present(frame);
const XCUIHostedPreviewStats& stats = presenter->GetLastStats();
EXPECT_FALSE(presented);
EXPECT_FALSE(stats.presented);
EXPECT_FALSE(stats.queuedToNativePass);
EXPECT_EQ(stats.submittedDrawListCount, 0u);
EXPECT_EQ(stats.submittedCommandCount, 0u);
EXPECT_EQ(stats.flushedDrawListCount, 0u);
EXPECT_EQ(stats.flushedCommandCount, 0u);
}
TEST(XCUIHostedPreviewPresenterTest, NullPresenterAcceptsDrawDataWithoutPresentingOrQueueing) {
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter = CreateNullXCUIHostedPreviewPresenter();
ASSERT_NE(presenter, nullptr);
XCEngine::UI::UIDrawData drawData = {};
XCEngine::UI::UIDrawList& drawList = drawData.EmplaceDrawList("HostedPreviewNull");
drawList.AddFilledRect(
XCEngine::UI::UIRect(12.0f, 14.0f, 48.0f, 30.0f),
XCEngine::UI::UIColor(0.25f, 0.5f, 0.8f, 1.0f));
drawList.AddText(
XCEngine::UI::UIPoint(18.0f, 24.0f),
"null",
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
14.0f);
XCUIHostedPreviewFrame frame = {};
frame.drawData = &drawData;
frame.debugName = "XCUI Null Preview";
const bool presented = presenter->Present(frame);
const XCUIHostedPreviewStats& stats = presenter->GetLastStats();
EXPECT_FALSE(presented);
EXPECT_FALSE(stats.presented);
EXPECT_FALSE(stats.queuedToNativePass);
EXPECT_EQ(stats.submittedDrawListCount, 1u);
EXPECT_EQ(stats.submittedCommandCount, 2u);
EXPECT_EQ(stats.flushedDrawListCount, 0u);
EXPECT_EQ(stats.flushedCommandCount, 0u);
}
TEST(XCUIHostedPreviewPresenterTest, NullPresenterSurfaceQueriesReturnFalseAndClearOutputs) {
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter = CreateNullXCUIHostedPreviewPresenter();
ASSERT_NE(presenter, nullptr);
XCUIHostedPreviewSurfaceDescriptor descriptor = {};
descriptor.debugName = "stale";
descriptor.debugSource = "stale";
descriptor.queuedThisFrame = true;
XCUIHostedPreviewSurfaceImage image = {};
image.texture = MakeHostedPreviewTextureHandle(29u, 256u, 128u);
image.surfaceWidth = 256u;
image.surfaceHeight = 128u;
EXPECT_FALSE(presenter->TryGetSurfaceDescriptor("XCUI Demo", descriptor));
EXPECT_TRUE(descriptor.debugName.empty());
EXPECT_TRUE(descriptor.debugSource.empty());
EXPECT_FALSE(descriptor.queuedThisFrame);
EXPECT_FALSE(descriptor.image.IsValid());
EXPECT_FALSE(presenter->TryGetSurfaceImage("XCUI Demo", image));
EXPECT_FALSE(image.IsValid());
EXPECT_EQ(image.surfaceWidth, 0u);
EXPECT_EQ(image.surfaceHeight, 0u);
}
TEST(XCUIHostedPreviewPresenterTest, PresentFlushesDrawDataIntoExplicitBindingResolvedImGuiDrawList) {
ImGuiContextScope contextScope;
PrepareImGui(800.0f, 600.0f);
XCEngine::UI::UIDrawData drawData = {};
XCEngine::UI::UIDrawList& drawList = drawData.EmplaceDrawList("HostedPreview");
drawList.AddFilledRect(
XCEngine::UI::UIRect(12.0f, 14.0f, 48.0f, 30.0f),
XCEngine::UI::UIColor(0.25f, 0.5f, 0.8f, 1.0f));
drawList.AddText(
XCEngine::UI::UIPoint(18.0f, 24.0f),
"xcui",
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
14.0f);
ImGui::NewFrame();
ASSERT_TRUE(ImGui::Begin("HostedPreviewPresenterWindow"));
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;
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.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, 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, DefaultFactoryStillUsesCurrentWindowBindingForImGuiPresenterPath) {
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);
}
TEST(XCUIHostedPreviewPresenterTest, QueuedNativePresenterCopiesFrameIntoQueue) {
XCUIHostedPreviewQueue queue = {};
XCUIHostedPreviewSurfaceRegistry surfaceRegistry = {};
queue.BeginFrame();
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter =
CreateQueuedNativeXCUIHostedPreviewPresenter(queue, surfaceRegistry);
ASSERT_NE(presenter, nullptr);
EXPECT_TRUE(presenter->IsNativeQueued());
XCEngine::UI::UIDrawData drawData = {};
XCEngine::UI::UIDrawList& drawList = drawData.EmplaceDrawList("HostedPreviewNative");
drawList.AddFilledRect(
XCEngine::UI::UIRect(20.0f, 24.0f, 56.0f, 32.0f),
XCEngine::UI::UIColor(0.2f, 0.35f, 0.9f, 1.0f));
drawList.AddText(
XCEngine::UI::UIPoint(28.0f, 36.0f),
"native",
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
16.0f);
XCUIHostedPreviewFrame frame = {};
frame.drawData = &drawData;
frame.debugName = "Hosted Preview Native Queue";
frame.debugSource = "tests.hosted_preview.native_queue";
frame.canvasRect = XCEngine::UI::UIRect(32.0f, 48.0f, 320.0f, 180.0f);
frame.logicalSize = XCEngine::UI::UISize(640.0f, 360.0f);
EXPECT_TRUE(presenter->Present(frame));
const XCUIHostedPreviewStats& stats = presenter->GetLastStats();
EXPECT_TRUE(stats.presented);
EXPECT_TRUE(stats.queuedToNativePass);
EXPECT_EQ(stats.submittedDrawListCount, 1u);
EXPECT_EQ(stats.submittedCommandCount, 2u);
EXPECT_EQ(stats.flushedDrawListCount, 0u);
EXPECT_EQ(stats.flushedCommandCount, 0u);
drawData.Clear();
const auto& queuedFrames = queue.GetQueuedFrames();
ASSERT_EQ(queuedFrames.size(), 1u);
EXPECT_EQ(queuedFrames[0].debugName, "Hosted Preview Native Queue");
EXPECT_EQ(queuedFrames[0].debugSource, "tests.hosted_preview.native_queue");
EXPECT_FLOAT_EQ(queuedFrames[0].canvasRect.x, 32.0f);
EXPECT_FLOAT_EQ(queuedFrames[0].canvasRect.y, 48.0f);
EXPECT_FLOAT_EQ(queuedFrames[0].canvasRect.width, 320.0f);
EXPECT_FLOAT_EQ(queuedFrames[0].canvasRect.height, 180.0f);
EXPECT_FLOAT_EQ(queuedFrames[0].logicalSize.width, 640.0f);
EXPECT_FLOAT_EQ(queuedFrames[0].logicalSize.height, 360.0f);
EXPECT_EQ(queuedFrames[0].drawData.GetDrawListCount(), 1u);
EXPECT_EQ(queuedFrames[0].drawData.GetTotalCommandCount(), 2u);
}
TEST(XCUIHostedPreviewPresenterTest, QueuedNativePresenterFallsBackLogicalSizeToCanvasRectAndDelegatesSurfaceQueries) {
XCUIHostedPreviewQueue queue = {};
XCUIHostedPreviewSurfaceRegistry surfaceRegistry = {};
queue.BeginFrame();
surfaceRegistry.BeginFrame();
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter =
CreateQueuedNativeXCUIHostedPreviewPresenter(queue, surfaceRegistry);
ASSERT_NE(presenter, nullptr);
XCEngine::UI::UIDrawData drawData = {};
XCEngine::UI::UIDrawList& drawList = drawData.EmplaceDrawList("HostedPreviewFallback");
drawList.AddFilledRect(
XCEngine::UI::UIRect(6.0f, 10.0f, 40.0f, 24.0f),
XCEngine::UI::UIColor(0.85f, 0.35f, 0.2f, 1.0f));
XCUIHostedPreviewFrame frame = {};
frame.drawData = &drawData;
frame.debugName = "XCUI Demo";
frame.debugSource = "tests.hosted_preview.logical_size_fallback";
frame.canvasRect = XCEngine::UI::UIRect(18.0f, 22.0f, 320.0f, 180.0f);
ASSERT_TRUE(presenter->Present(frame));
ASSERT_EQ(queue.GetQueuedFrames().size(), 1u);
const XCUIHostedPreviewQueuedFrame& queuedFrame = queue.GetQueuedFrames().front();
EXPECT_FLOAT_EQ(queuedFrame.logicalSize.width, 320.0f);
EXPECT_FLOAT_EQ(queuedFrame.logicalSize.height, 180.0f);
XCUIHostedPreviewSurfaceDescriptor descriptor = {};
EXPECT_FALSE(presenter->TryGetSurfaceDescriptor("XCUI Demo", descriptor));
EXPECT_TRUE(descriptor.debugName.empty());
surfaceRegistry.RecordQueuedFrame(queuedFrame, 0u);
ASSERT_TRUE(presenter->TryGetSurfaceDescriptor("XCUI Demo", descriptor));
EXPECT_EQ(descriptor.debugName, "XCUI Demo");
EXPECT_EQ(descriptor.debugSource, "tests.hosted_preview.logical_size_fallback");
EXPECT_FLOAT_EQ(descriptor.canvasRect.x, 18.0f);
EXPECT_FLOAT_EQ(descriptor.canvasRect.y, 22.0f);
EXPECT_FLOAT_EQ(descriptor.canvasRect.width, 320.0f);
EXPECT_FLOAT_EQ(descriptor.canvasRect.height, 180.0f);
EXPECT_FLOAT_EQ(descriptor.logicalSize.width, 320.0f);
EXPECT_FLOAT_EQ(descriptor.logicalSize.height, 180.0f);
EXPECT_EQ(descriptor.submittedDrawListCount, 1u);
EXPECT_EQ(descriptor.submittedCommandCount, 1u);
EXPECT_TRUE(descriptor.queuedThisFrame);
XCUIHostedPreviewSurfaceImage image = {};
EXPECT_FALSE(presenter->TryGetSurfaceImage("XCUI Demo", image));
EXPECT_FALSE(image.IsValid());
surfaceRegistry.UpdateSurface(
"XCUI Demo",
MakeHostedPreviewTextureHandle(11u, 640u, 360u),
XCEngine::UI::UIRect(0.0f, 0.0f, 320.0f, 180.0f));
ASSERT_TRUE(presenter->TryGetSurfaceImage("XCUI Demo", image));
EXPECT_TRUE(image.IsValid());
EXPECT_EQ(image.texture.nativeHandle, 11u);
EXPECT_EQ(image.texture.width, 640u);
EXPECT_EQ(image.texture.height, 360u);
EXPECT_FLOAT_EQ(image.uvMin.x, 0.0f);
EXPECT_FLOAT_EQ(image.uvMin.y, 0.0f);
EXPECT_FLOAT_EQ(image.uvMax.x, 0.5f);
EXPECT_FLOAT_EQ(image.uvMax.y, 0.5f);
}
TEST(XCUIHostedPreviewPresenterTest, QueuedNativePresenterFallsBackWhenLogicalSizeIsPartiallySpecified) {
XCUIHostedPreviewQueue queue = {};
XCUIHostedPreviewSurfaceRegistry surfaceRegistry = {};
queue.BeginFrame();
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter =
CreateQueuedNativeXCUIHostedPreviewPresenter(queue, surfaceRegistry);
ASSERT_NE(presenter, nullptr);
XCEngine::UI::UIDrawData drawData = {};
drawData.EmplaceDrawList("HostedPreviewPartialLogicalSize").AddFilledRect(
XCEngine::UI::UIRect(2.0f, 3.0f, 18.0f, 9.0f),
XCEngine::UI::UIColor(0.1f, 0.2f, 0.3f, 1.0f));
XCUIHostedPreviewFrame frame = {};
frame.drawData = &drawData;
frame.debugName = "XCUI Partial Logical Size";
frame.canvasRect = XCEngine::UI::UIRect(10.0f, 20.0f, 300.0f, 140.0f);
frame.logicalSize = XCEngine::UI::UISize(512.0f, 0.0f);
ASSERT_TRUE(presenter->Present(frame));
ASSERT_EQ(queue.GetQueuedFrames().size(), 1u);
const XCUIHostedPreviewQueuedFrame& queuedFrame = queue.GetQueuedFrames().front();
EXPECT_FLOAT_EQ(queuedFrame.logicalSize.width, 300.0f);
EXPECT_FLOAT_EQ(queuedFrame.logicalSize.height, 140.0f);
}
TEST(XCUIHostedPreviewPresenterTest, QueuedNativePresenterRejectsMissingDrawDataAndLeavesQueueUntouched) {
XCUIHostedPreviewQueue queue = {};
XCUIHostedPreviewSurfaceRegistry surfaceRegistry = {};
queue.BeginFrame();
std::unique_ptr<IXCUIHostedPreviewPresenter> presenter =
CreateQueuedNativeXCUIHostedPreviewPresenter(queue, surfaceRegistry);
ASSERT_NE(presenter, nullptr);
XCUIHostedPreviewFrame frame = {};
frame.debugName = "Missing DrawData";
EXPECT_FALSE(presenter->Present(frame));
const XCUIHostedPreviewStats& stats = presenter->GetLastStats();
EXPECT_FALSE(stats.presented);
EXPECT_FALSE(stats.queuedToNativePass);
EXPECT_EQ(stats.submittedDrawListCount, 0u);
EXPECT_EQ(stats.submittedCommandCount, 0u);
EXPECT_EQ(stats.flushedDrawListCount, 0u);
EXPECT_EQ(stats.flushedCommandCount, 0u);
EXPECT_TRUE(queue.GetQueuedFrames().empty());
}
TEST(XCUIHostedPreviewPresenterTest, HostedPreviewQueuePreservesSubmissionOrderAndPayloadMetadata) {
XCUIHostedPreviewQueue queue = {};
queue.BeginFrame();
XCEngine::UI::UIDrawData firstDrawData = {};
XCEngine::UI::UIDrawList& firstDrawList = firstDrawData.EmplaceDrawList("FirstPreview");
firstDrawList.AddFilledRect(
XCEngine::UI::UIRect(0.0f, 0.0f, 20.0f, 10.0f),
XCEngine::UI::UIColor(1.0f, 0.2f, 0.2f, 1.0f));
XCEngine::UI::UIDrawData secondDrawData = {};
XCEngine::UI::UIDrawList& secondDrawList = secondDrawData.EmplaceDrawList("SecondPreview");
secondDrawList.AddFilledRect(
XCEngine::UI::UIRect(10.0f, 8.0f, 24.0f, 14.0f),
XCEngine::UI::UIColor(0.2f, 0.7f, 1.0f, 1.0f));
secondDrawList.AddText(
XCEngine::UI::UIPoint(12.0f, 12.0f),
"queued",
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
13.0f);
XCUIHostedPreviewStats firstStats = {};
XCUIHostedPreviewFrame firstFrame = {};
firstFrame.drawData = &firstDrawData;
firstFrame.debugName = "First Native Preview";
ASSERT_TRUE(queue.Submit(firstFrame, &firstStats));
XCUIHostedPreviewStats secondStats = {};
XCUIHostedPreviewFrame secondFrame = {};
secondFrame.drawData = &secondDrawData;
ASSERT_TRUE(queue.Submit(secondFrame, &secondStats));
firstDrawData.Clear();
secondDrawData.Clear();
const auto& queuedFrames = queue.GetQueuedFrames();
ASSERT_EQ(queuedFrames.size(), 2u);
EXPECT_EQ(firstStats.submittedDrawListCount, 1u);
EXPECT_EQ(firstStats.submittedCommandCount, 1u);
EXPECT_TRUE(firstStats.queuedToNativePass);
EXPECT_EQ(secondStats.submittedDrawListCount, 1u);
EXPECT_EQ(secondStats.submittedCommandCount, 2u);
EXPECT_TRUE(secondStats.queuedToNativePass);
EXPECT_EQ(queuedFrames[0].debugName, "First Native Preview");
EXPECT_EQ(queuedFrames[0].drawData.GetDrawListCount(), 1u);
EXPECT_EQ(queuedFrames[0].drawData.GetTotalCommandCount(), 1u);
EXPECT_EQ(queuedFrames[1].debugName, "");
EXPECT_EQ(queuedFrames[1].drawData.GetDrawListCount(), 1u);
EXPECT_EQ(queuedFrames[1].drawData.GetTotalCommandCount(), 2u);
}
TEST(XCUIHostedPreviewPresenterTest, SurfaceRegistryIgnoresUnnamedQueuedFramesAndKeepsDescriptorListStable) {
XCUIHostedPreviewSurfaceRegistry surfaceRegistry = {};
XCUIHostedPreviewQueuedFrame unnamedFrame = {};
unnamedFrame.debugSource = "tests.hosted_preview.unnamed";
unnamedFrame.canvasRect = XCEngine::UI::UIRect(10.0f, 12.0f, 120.0f, 80.0f);
unnamedFrame.logicalSize = XCEngine::UI::UISize(120.0f, 80.0f);
unnamedFrame.drawData.EmplaceDrawList("Unnamed").AddFilledRect(
XCEngine::UI::UIRect(0.0f, 0.0f, 16.0f, 16.0f),
XCEngine::UI::UIColor(0.4f, 0.7f, 0.9f, 1.0f));
surfaceRegistry.RecordQueuedFrame(unnamedFrame, 5u);
EXPECT_TRUE(surfaceRegistry.GetDescriptors().empty());
surfaceRegistry.UpdateSurface(
"XCUI Demo",
MakeHostedPreviewTextureHandle(13u, 800u, 600u),
XCEngine::UI::UIRect(0.0f, 0.0f, 400.0f, 300.0f));
ASSERT_EQ(surfaceRegistry.GetDescriptors().size(), 1u);
surfaceRegistry.RecordQueuedFrame(unnamedFrame, 9u);
ASSERT_EQ(surfaceRegistry.GetDescriptors().size(), 1u);
EXPECT_EQ(surfaceRegistry.GetDescriptors().front().debugName, "XCUI Demo");
EXPECT_TRUE(surfaceRegistry.GetDescriptors().front().image.IsValid());
}
TEST(XCUIHostedPreviewPresenterTest, SurfaceRegistryExposesImageUvForRenderedCanvasRect) {
XCUIHostedPreviewSurfaceRegistry surfaceRegistry = {};
XCUIHostedPreviewSurfaceImage image = {};
surfaceRegistry.UpdateSurface(
"XCUI Demo",
MakeHostedPreviewTextureHandle(7u, 1024u, 768u),
XCEngine::UI::UIRect(128.0f, 96.0f, 512.0f, 384.0f));
ASSERT_TRUE(surfaceRegistry.TryGetSurfaceImage("XCUI Demo", image));
EXPECT_TRUE(image.IsValid());
EXPECT_EQ(image.texture.nativeHandle, 7u);
EXPECT_EQ(image.texture.width, 1024u);
EXPECT_EQ(image.texture.height, 768u);
EXPECT_EQ(image.surfaceWidth, 1024u);
EXPECT_EQ(image.surfaceHeight, 768u);
EXPECT_FLOAT_EQ(image.uvMin.x, 0.125f);
EXPECT_FLOAT_EQ(image.uvMin.y, 0.125f);
EXPECT_FLOAT_EQ(image.uvMax.x, 0.625f);
EXPECT_FLOAT_EQ(image.uvMax.y, 0.625f);
}
TEST(XCUIHostedPreviewPresenterTest, SurfaceRegistryTracksQueuedFrameMetadataAlongsideLatestSurfaceImage) {
XCUIHostedPreviewSurfaceRegistry surfaceRegistry = {};
surfaceRegistry.BeginFrame();
XCEngine::UI::UIDrawData drawData = {};
XCEngine::UI::UIDrawList& drawList = drawData.EmplaceDrawList("RegistryPreview");
drawList.AddFilledRect(
XCEngine::UI::UIRect(8.0f, 6.0f, 30.0f, 18.0f),
XCEngine::UI::UIColor(0.3f, 0.7f, 0.9f, 1.0f));
drawList.AddText(
XCEngine::UI::UIPoint(16.0f, 12.0f),
"meta",
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
12.0f);
XCUIHostedPreviewQueuedFrame queuedFrame = {};
queuedFrame.debugName = "XCUI Demo";
queuedFrame.debugSource = "tests.hosted_preview.registry";
queuedFrame.canvasRect = XCEngine::UI::UIRect(24.0f, 32.0f, 300.0f, 160.0f);
queuedFrame.logicalSize = XCEngine::UI::UISize(600.0f, 320.0f);
queuedFrame.drawData = drawData;
surfaceRegistry.RecordQueuedFrame(queuedFrame, 2u);
XCUIHostedPreviewSurfaceDescriptor descriptor = {};
ASSERT_TRUE(surfaceRegistry.TryGetSurfaceDescriptor("XCUI Demo", descriptor));
EXPECT_EQ(descriptor.debugName, "XCUI Demo");
EXPECT_EQ(descriptor.debugSource, "tests.hosted_preview.registry");
EXPECT_FLOAT_EQ(descriptor.canvasRect.x, 24.0f);
EXPECT_FLOAT_EQ(descriptor.canvasRect.y, 32.0f);
EXPECT_FLOAT_EQ(descriptor.canvasRect.width, 300.0f);
EXPECT_FLOAT_EQ(descriptor.canvasRect.height, 160.0f);
EXPECT_FLOAT_EQ(descriptor.logicalSize.width, 600.0f);
EXPECT_FLOAT_EQ(descriptor.logicalSize.height, 320.0f);
EXPECT_EQ(descriptor.queuedFrameIndex, 2u);
EXPECT_EQ(descriptor.submittedDrawListCount, 1u);
EXPECT_EQ(descriptor.submittedCommandCount, 2u);
EXPECT_TRUE(descriptor.queuedThisFrame);
EXPECT_FALSE(descriptor.image.IsValid());
surfaceRegistry.UpdateSurface(
"XCUI Demo",
MakeHostedPreviewTextureHandle(9u, 1024u, 512u),
XCEngine::UI::UIRect(128.0f, 64.0f, 320.0f, 160.0f));
ASSERT_TRUE(surfaceRegistry.TryGetSurfaceDescriptor("XCUI Demo", descriptor));
EXPECT_TRUE(descriptor.queuedThisFrame);
EXPECT_TRUE(descriptor.image.IsValid());
EXPECT_EQ(descriptor.image.texture.nativeHandle, 9u);
EXPECT_EQ(descriptor.image.texture.width, 1024u);
EXPECT_EQ(descriptor.image.texture.height, 512u);
EXPECT_FLOAT_EQ(descriptor.image.uvMin.x, 0.125f);
EXPECT_FLOAT_EQ(descriptor.image.uvMin.y, 0.125f);
EXPECT_FLOAT_EQ(descriptor.image.uvMax.x, 0.4375f);
EXPECT_FLOAT_EQ(descriptor.image.uvMax.y, 0.4375f);
surfaceRegistry.BeginFrame();
ASSERT_TRUE(surfaceRegistry.TryGetSurfaceDescriptor("XCUI Demo", descriptor));
EXPECT_FALSE(descriptor.queuedThisFrame);
EXPECT_TRUE(descriptor.image.IsValid());
}
TEST(XCUIHostedPreviewPresenterTest, SurfaceRegistryReusesDescriptorForRepeatedQueuedFrameKeys) {
XCUIHostedPreviewSurfaceRegistry surfaceRegistry = {};
surfaceRegistry.BeginFrame();
surfaceRegistry.UpdateSurface(
"XCUI Demo",
MakeHostedPreviewTextureHandle(31u, 400u, 200u),
XCEngine::UI::UIRect(0.0f, 0.0f, 200.0f, 100.0f));
XCUIHostedPreviewQueuedFrame firstQueuedFrame = {};
firstQueuedFrame.debugName = "XCUI Demo";
firstQueuedFrame.debugSource = "tests.hosted_preview.first";
firstQueuedFrame.canvasRect = XCEngine::UI::UIRect(8.0f, 12.0f, 320.0f, 180.0f);
firstQueuedFrame.logicalSize = XCEngine::UI::UISize(640.0f, 360.0f);
firstQueuedFrame.drawData.EmplaceDrawList("First").AddFilledRect(
XCEngine::UI::UIRect(0.0f, 0.0f, 16.0f, 16.0f),
XCEngine::UI::UIColor(1.0f, 0.0f, 0.0f, 1.0f));
XCUIHostedPreviewQueuedFrame secondQueuedFrame = {};
secondQueuedFrame.debugName = "XCUI Demo";
secondQueuedFrame.debugSource = "tests.hosted_preview.second";
secondQueuedFrame.canvasRect = XCEngine::UI::UIRect(20.0f, 24.0f, 256.0f, 144.0f);
secondQueuedFrame.logicalSize = XCEngine::UI::UISize(512.0f, 288.0f);
XCEngine::UI::UIDrawList& secondDrawList = secondQueuedFrame.drawData.EmplaceDrawList("Second");
secondDrawList.AddFilledRect(
XCEngine::UI::UIRect(1.0f, 2.0f, 20.0f, 10.0f),
XCEngine::UI::UIColor(0.0f, 1.0f, 0.0f, 1.0f));
secondDrawList.AddText(
XCEngine::UI::UIPoint(4.0f, 6.0f),
"second",
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
12.0f);
surfaceRegistry.RecordQueuedFrame(firstQueuedFrame, 1u);
surfaceRegistry.RecordQueuedFrame(secondQueuedFrame, 7u);
ASSERT_EQ(surfaceRegistry.GetDescriptors().size(), 1u);
XCUIHostedPreviewSurfaceDescriptor descriptor = {};
ASSERT_TRUE(surfaceRegistry.TryGetSurfaceDescriptor("XCUI Demo", descriptor));
EXPECT_EQ(descriptor.debugSource, "tests.hosted_preview.second");
EXPECT_FLOAT_EQ(descriptor.canvasRect.x, 20.0f);
EXPECT_FLOAT_EQ(descriptor.canvasRect.y, 24.0f);
EXPECT_FLOAT_EQ(descriptor.canvasRect.width, 256.0f);
EXPECT_FLOAT_EQ(descriptor.canvasRect.height, 144.0f);
EXPECT_FLOAT_EQ(descriptor.logicalSize.width, 512.0f);
EXPECT_FLOAT_EQ(descriptor.logicalSize.height, 288.0f);
EXPECT_EQ(descriptor.queuedFrameIndex, 7u);
EXPECT_EQ(descriptor.submittedDrawListCount, 1u);
EXPECT_EQ(descriptor.submittedCommandCount, 2u);
EXPECT_TRUE(descriptor.queuedThisFrame);
EXPECT_TRUE(descriptor.image.IsValid());
EXPECT_EQ(descriptor.image.texture.nativeHandle, 31u);
}
TEST(XCUIHostedPreviewPresenterTest, SurfaceRegistryRejectsInvalidSurfaceUpdatesWithoutClobberingExistingImage) {
XCUIHostedPreviewSurfaceRegistry surfaceRegistry = {};
XCUIHostedPreviewSurfaceDescriptor descriptor = {};
surfaceRegistry.UpdateSurface(
"XCUI Demo",
MakeHostedPreviewTextureHandle(17u, 512u, 256u),
XCEngine::UI::UIRect(64.0f, 32.0f, 256.0f, 128.0f));
ASSERT_TRUE(surfaceRegistry.TryGetSurfaceDescriptor("XCUI Demo", descriptor));
ASSERT_TRUE(descriptor.image.IsValid());
const XCUIHostedPreviewSurfaceImage originalImage = descriptor.image;
surfaceRegistry.UpdateSurface(
"XCUI Demo",
XCEngine::UI::UITextureHandle{},
XCEngine::UI::UIRect(0.0f, 0.0f, 128.0f, 64.0f));
surfaceRegistry.UpdateSurface(
"",
MakeHostedPreviewTextureHandle(19u, 512u, 256u),
XCEngine::UI::UIRect(0.0f, 0.0f, 128.0f, 64.0f));
surfaceRegistry.UpdateSurface(
"XCUI Demo",
MakeHostedPreviewTextureHandle(21u, 0u, 256u),
XCEngine::UI::UIRect(0.0f, 0.0f, 128.0f, 64.0f));
ASSERT_TRUE(surfaceRegistry.TryGetSurfaceDescriptor("XCUI Demo", descriptor));
EXPECT_EQ(descriptor.image.texture.nativeHandle, originalImage.texture.nativeHandle);
EXPECT_EQ(descriptor.image.texture.width, originalImage.texture.width);
EXPECT_EQ(descriptor.image.texture.height, originalImage.texture.height);
EXPECT_EQ(descriptor.image.surfaceWidth, originalImage.surfaceWidth);
EXPECT_EQ(descriptor.image.surfaceHeight, originalImage.surfaceHeight);
EXPECT_FLOAT_EQ(descriptor.image.uvMin.x, originalImage.uvMin.x);
EXPECT_FLOAT_EQ(descriptor.image.uvMin.y, originalImage.uvMin.y);
EXPECT_FLOAT_EQ(descriptor.image.uvMax.x, originalImage.uvMax.x);
EXPECT_FLOAT_EQ(descriptor.image.uvMax.y, originalImage.uvMax.y);
}
TEST(XCUIHostedPreviewPresenterTest, BeginFrameClearsQueuedFramesButKeepsLastDrainStatsUntilReplaced) {
XCUIHostedPreviewQueue queue = {};
XCEngine::UI::UIDrawData drawData = {};
XCEngine::UI::UIDrawList& drawList = drawData.EmplaceDrawList("Preview");
drawList.AddFilledRect(
XCEngine::UI::UIRect(4.0f, 4.0f, 18.0f, 12.0f),
XCEngine::UI::UIColor(0.4f, 0.6f, 0.9f, 1.0f));
XCUIHostedPreviewFrame frame = {};
frame.drawData = &drawData;
ASSERT_TRUE(queue.Submit(frame));
ASSERT_EQ(queue.GetQueuedFrames().size(), 1u);
XCUIHostedPreviewDrainStats initialDrainStats = {};
initialDrainStats.queuedFrameCount = 3u;
initialDrainStats.queuedCommandCount = 7u;
initialDrainStats.renderedFrameCount = 2u;
initialDrainStats.skippedFrameCount = 1u;
queue.SetLastDrainStats(initialDrainStats);
queue.BeginFrame();
EXPECT_TRUE(queue.GetQueuedFrames().empty());
const XCUIHostedPreviewDrainStats& drainStats = queue.GetLastDrainStats();
EXPECT_EQ(drainStats.queuedFrameCount, 3u);
EXPECT_EQ(drainStats.queuedCommandCount, 7u);
EXPECT_EQ(drainStats.renderedFrameCount, 2u);
EXPECT_EQ(drainStats.skippedFrameCount, 1u);
}
TEST(XCUIHostedPreviewPresenterTest, SurfaceRegistryQueriesClearOutputForMissingOrInvalidNames) {
XCUIHostedPreviewSurfaceRegistry surfaceRegistry = {};
XCUIHostedPreviewSurfaceDescriptor descriptor = {};
descriptor.debugName = "stale";
descriptor.debugSource = "stale";
descriptor.queuedThisFrame = true;
XCUIHostedPreviewSurfaceImage image = {};
image.texture = MakeHostedPreviewTextureHandle(23u, 64u, 64u);
image.surfaceWidth = 64u;
image.surfaceHeight = 64u;
EXPECT_FALSE(surfaceRegistry.TryGetSurfaceDescriptor(nullptr, descriptor));
EXPECT_TRUE(descriptor.debugName.empty());
EXPECT_TRUE(descriptor.debugSource.empty());
EXPECT_FALSE(descriptor.queuedThisFrame);
EXPECT_FALSE(descriptor.image.IsValid());
EXPECT_FALSE(surfaceRegistry.TryGetSurfaceImage("", image));
EXPECT_FALSE(image.IsValid());
EXPECT_EQ(image.surfaceWidth, 0u);
EXPECT_EQ(image.surfaceHeight, 0u);
}
} // namespace

View File

@@ -1,146 +0,0 @@
#include <gtest/gtest.h>
#include "XCUIBackend/XCUIInputBridge.h"
#include <XCEngine/Input/InputTypes.h>
namespace {
using XCEngine::Editor::XCUIBackend::XCUIInputBridge;
using XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions;
using XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta;
using XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot;
using XCEngine::Editor::XCUIBackend::XCUIInputBridgeKeyState;
using XCEngine::Editor::XCUIBackend::XCUIWin32InputSource;
using XCEngine::Input::KeyCode;
using XCEngine::UI::UIInputEventType;
using XCEngine::UI::UIPoint;
XCUIInputBridgeKeyState MakeKeyState(KeyCode keyCode, bool down, bool repeat = false) {
XCUIInputBridgeKeyState state = {};
state.keyCode = static_cast<std::int32_t>(keyCode);
state.down = down;
state.repeat = repeat;
return state;
}
} // namespace
TEST(XCUIInputBridgeTest, TranslateBuildsPointerKeyboardAndCharacterEventsFromSnapshots) {
XCUIInputBridgeFrameSnapshot previous = {};
XCUIInputBridgeFrameSnapshot current = {};
current.pointerInside = true;
current.pointerPosition = UIPoint(32.0f, 48.0f);
current.pointerButtonsDown[0] = true;
current.windowFocused = true;
current.keys.push_back(MakeKeyState(KeyCode::P, true));
current.characters.push_back(static_cast<std::uint32_t>('p'));
const XCUIInputBridgeFrameDelta delta = XCUIInputBridge::Translate(previous, current);
EXPECT_TRUE(delta.focusGained);
EXPECT_TRUE(delta.pointer.entered);
EXPECT_TRUE(delta.pointer.moved);
EXPECT_TRUE(delta.pointer.pressed[0]);
ASSERT_EQ(delta.keyboard.pressedKeys.size(), 1u);
EXPECT_EQ(delta.keyboard.pressedKeys[0], static_cast<std::int32_t>(KeyCode::P));
ASSERT_EQ(delta.keyboard.characters.size(), 1u);
EXPECT_EQ(delta.keyboard.characters[0], static_cast<std::uint32_t>('p'));
EXPECT_TRUE(delta.HasEventType(UIInputEventType::FocusGained));
EXPECT_TRUE(delta.HasEventType(UIInputEventType::PointerEnter));
EXPECT_TRUE(delta.HasEventType(UIInputEventType::PointerMove));
EXPECT_TRUE(delta.HasEventType(UIInputEventType::PointerButtonDown));
EXPECT_TRUE(delta.HasEventType(UIInputEventType::KeyDown));
EXPECT_TRUE(delta.HasEventType(UIInputEventType::Character));
}
TEST(XCUIInputBridgeTest, PrimeSuppressesSyntheticFirstFrameTransitions) {
XCUIInputBridge bridge = {};
XCUIInputBridgeFrameSnapshot baseline = {};
baseline.pointerInside = true;
baseline.pointerPosition = UIPoint(24.0f, 12.0f);
baseline.pointerButtonsDown[0] = true;
baseline.windowFocused = true;
baseline.keys.push_back(MakeKeyState(KeyCode::P, true));
bridge.Prime(baseline);
const XCUIInputBridgeFrameDelta firstDelta = bridge.Translate(baseline);
EXPECT_FALSE(firstDelta.HasEvents());
XCUIInputBridgeFrameSnapshot released = baseline;
released.pointerButtonsDown[0] = false;
released.keys.clear();
const XCUIInputBridgeFrameDelta secondDelta = bridge.Translate(released);
EXPECT_TRUE(secondDelta.pointer.released[0]);
ASSERT_EQ(secondDelta.keyboard.releasedKeys.size(), 1u);
EXPECT_EQ(secondDelta.keyboard.releasedKeys[0], static_cast<std::int32_t>(KeyCode::P));
EXPECT_TRUE(secondDelta.HasEventType(UIInputEventType::PointerButtonUp));
EXPECT_TRUE(secondDelta.HasEventType(UIInputEventType::KeyUp));
}
TEST(XCUIInputBridgeTest, Win32InputSourceCapturesPointerWheelKeyRepeatAndCharacters) {
XCUIWin32InputSource inputSource = {};
inputSource.HandleWindowMessage(nullptr, WM_SETFOCUS, 0, 0);
inputSource.HandleWindowMessage(nullptr, WM_MOUSEMOVE, 0, MAKELPARAM(64, 96));
inputSource.HandleWindowMessage(nullptr, WM_LBUTTONDOWN, MK_LBUTTON, MAKELPARAM(64, 96));
inputSource.HandleWindowMessage(nullptr, WM_MOUSEWHEEL, MAKEWPARAM(0, WHEEL_DELTA), 0);
inputSource.HandleWindowMessage(nullptr, WM_KEYDOWN, 'P', 0);
inputSource.HandleWindowMessage(nullptr, WM_KEYDOWN, 'P', 1u << 30u);
inputSource.HandleWindowMessage(nullptr, WM_CHAR, 'p', 0);
XCUIInputBridgeCaptureOptions options = {};
options.windowFocused = true;
const XCUIInputBridgeFrameSnapshot snapshot = inputSource.CaptureSnapshot(options);
EXPECT_TRUE(snapshot.windowFocused);
EXPECT_TRUE(snapshot.pointerInside);
EXPECT_EQ(snapshot.pointerPosition.x, 64.0f);
EXPECT_EQ(snapshot.pointerPosition.y, 96.0f);
EXPECT_TRUE(snapshot.pointerButtonsDown[0]);
EXPECT_FLOAT_EQ(snapshot.wheelDelta.y, 1.0f);
EXPECT_TRUE(snapshot.IsKeyDown(static_cast<std::int32_t>(KeyCode::P)));
ASSERT_EQ(snapshot.characters.size(), 1u);
EXPECT_EQ(snapshot.characters[0], static_cast<std::uint32_t>('p'));
const XCUIInputBridgeFrameDelta delta = XCUIInputBridge::Translate(XCUIInputBridgeFrameSnapshot(), snapshot);
EXPECT_TRUE(delta.pointer.pressed[0]);
ASSERT_EQ(delta.keyboard.pressedKeys.size(), 1u);
EXPECT_EQ(delta.keyboard.pressedKeys[0], static_cast<std::int32_t>(KeyCode::P));
EXPECT_TRUE(delta.HasEventType(UIInputEventType::Character));
inputSource.ClearFrameTransients();
const XCUIInputBridgeFrameSnapshot afterClear = inputSource.CaptureSnapshot(options);
EXPECT_FLOAT_EQ(afterClear.wheelDelta.x, 0.0f);
EXPECT_FLOAT_EQ(afterClear.wheelDelta.y, 0.0f);
EXPECT_TRUE(afterClear.characters.empty());
const XCUIInputBridgeKeyState* repeatedKey =
afterClear.FindKeyState(static_cast<std::int32_t>(KeyCode::P));
ASSERT_NE(repeatedKey, nullptr);
EXPECT_FALSE(repeatedKey->repeat);
}
TEST(XCUIInputBridgeTest, Win32InputSourceClearsPressedStateOnFocusLoss) {
XCUIWin32InputSource inputSource = {};
inputSource.HandleWindowMessage(nullptr, WM_SETFOCUS, 0, 0);
inputSource.HandleWindowMessage(nullptr, WM_LBUTTONDOWN, MK_LBUTTON, MAKELPARAM(20, 24));
inputSource.HandleWindowMessage(nullptr, WM_KEYDOWN, 'P', 0);
XCUIInputBridgeCaptureOptions options = {};
options.windowFocused = true;
XCUIInputBridgeFrameSnapshot focused = inputSource.CaptureSnapshot(options);
EXPECT_TRUE(focused.pointerButtonsDown[0]);
EXPECT_TRUE(focused.IsKeyDown(static_cast<std::int32_t>(KeyCode::P)));
inputSource.HandleWindowMessage(nullptr, WM_KILLFOCUS, 0, 0);
XCUIInputBridgeFrameSnapshot blurred = inputSource.CaptureSnapshot(options);
EXPECT_FALSE(blurred.windowFocused);
EXPECT_FALSE(blurred.pointerButtonsDown[0]);
EXPECT_FALSE(blurred.IsKeyDown(static_cast<std::int32_t>(KeyCode::P)));
}

View File

@@ -1,526 +0,0 @@
#include <gtest/gtest.h>
#include "panels/XCUILayoutLabPanel.h"
#include "XCUIBackend/ImGuiXCUIInputSource.h"
#include "XCUIBackend/XCUIHostedPreviewPresenter.h"
#include "XCUIBackend/XCUIPanelCanvasHost.h"
#include <imgui.h>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
namespace {
using XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter;
using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::ImGuiXCUIInputSnapshotSource;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewFrame;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage;
using XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats;
using XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot;
using XCEngine::Editor::XCUIBackend::XCUIInputBridgeKeyState;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession;
using XCEngine::Input::KeyCode;
using XCEngine::NewEditor::XCUILayoutLabFrameComposition;
using XCEngine::NewEditor::XCUILayoutLabFrameCompositionRequest;
using XCEngine::NewEditor::XCUILayoutLabPanel;
class ImGuiContextScope {
public:
ImGuiContextScope() {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui::StyleColorsDark();
}
~ImGuiContextScope() {
ImGui::DestroyContext();
}
};
void PrepareImGui(float width = 1280.0f, float height = 900.0f) {
ImGuiIO& io = ImGui::GetIO();
io.DisplaySize = ImVec2(width, height);
io.DeltaTime = 1.0f / 60.0f;
unsigned char* fontPixels = nullptr;
int fontWidth = 0;
int fontHeight = 0;
io.Fonts->GetTexDataAsRGBA32(&fontPixels, &fontWidth, &fontHeight);
io.Fonts->SetTexID(static_cast<ImTextureID>(1));
}
class StubHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter {
public:
bool Present(const XCUIHostedPreviewFrame& frame) override {
++m_presentCallCount;
m_lastStats = {};
m_lastStats.presented = frame.drawData != nullptr;
return m_lastStats.presented;
}
const XCUIHostedPreviewStats& GetLastStats() const override {
return m_lastStats;
}
std::size_t GetPresentCallCount() const {
return m_presentCallCount;
}
private:
std::size_t m_presentCallCount = 0u;
XCUIHostedPreviewStats m_lastStats = {};
};
class StubNativeHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter {
public:
bool Present(const XCUIHostedPreviewFrame& frame) override {
++m_presentCallCount;
m_lastFrame = frame;
m_lastStats = {};
m_lastStats.presented = frame.drawData != nullptr;
m_lastStats.queuedToNativePass = frame.drawData != nullptr;
m_lastStats.submittedDrawListCount = frame.drawData != nullptr ? frame.drawData->GetDrawListCount() : 0u;
m_lastStats.submittedCommandCount = frame.drawData != nullptr ? frame.drawData->GetTotalCommandCount() : 0u;
return m_lastStats.presented;
}
const XCUIHostedPreviewStats& GetLastStats() const override {
return m_lastStats;
}
bool IsNativeQueued() const override {
return true;
}
bool TryGetSurfaceImage(
const char* debugName,
XCUIHostedPreviewSurfaceImage& outImage) const override {
outImage = {};
if (debugName == nullptr || m_descriptor.debugName != debugName) {
return false;
}
outImage = m_descriptor.image;
return outImage.IsValid();
}
bool TryGetSurfaceDescriptor(
const char* debugName,
XCUIHostedPreviewSurfaceDescriptor& outDescriptor) const override {
outDescriptor = {};
if (debugName == nullptr || m_descriptor.debugName != debugName) {
return false;
}
outDescriptor = m_descriptor;
return true;
}
void SetDescriptor(XCUIHostedPreviewSurfaceDescriptor descriptor) {
m_descriptor = std::move(descriptor);
}
std::size_t GetPresentCallCount() const {
return m_presentCallCount;
}
const XCUIHostedPreviewFrame& GetLastFrame() const {
return m_lastFrame;
}
private:
std::size_t m_presentCallCount = 0u;
XCUIHostedPreviewFrame m_lastFrame = {};
XCUIHostedPreviewStats m_lastStats = {};
XCUIHostedPreviewSurfaceDescriptor m_descriptor = {};
};
class StubCanvasHost final : public IXCUIPanelCanvasHost {
public:
const char* GetDebugName() const override {
return "StubCanvasHost";
}
XCUIPanelCanvasSession BeginCanvas(const XCUIPanelCanvasRequest&) override {
return m_session;
}
void DrawFilledRect(
const XCEngine::UI::UIRect&,
const XCEngine::UI::UIColor&,
float) override {
}
void DrawOutlineRect(
const XCEngine::UI::UIRect&,
const XCEngine::UI::UIColor&,
float,
float) override {
}
void DrawText(
const XCEngine::UI::UIPoint&,
std::string_view,
const XCEngine::UI::UIColor&,
float) override {
}
void EndCanvas() override {
}
void SetPointerPosition(const XCEngine::UI::UIPoint& position) {
m_session.pointerPosition = position;
}
void SetHovered(bool hovered) {
m_session.hovered = hovered;
}
void SetWindowFocused(bool focused) {
m_session.windowFocused = focused;
}
private:
XCUIPanelCanvasSession m_session = {
XCEngine::UI::UIRect(0.0f, 0.0f, 960.0f, 640.0f),
XCEngine::UI::UIRect(0.0f, 0.0f, 960.0f, 640.0f),
XCEngine::UI::UIPoint(120.0f, 120.0f),
true,
true,
true
};
};
std::uint64_t NextTimestampNanoseconds() {
static std::uint64_t timestampNanoseconds = 1'000'000u;
timestampNanoseconds += 16'666'667u;
return timestampNanoseconds;
}
XCUIPanelCanvasSession MakeCanvasSession() {
XCUIPanelCanvasSession session = {};
session.hostRect = XCEngine::UI::UIRect(0.0f, 0.0f, 960.0f, 640.0f);
session.canvasRect = XCEngine::UI::UIRect(0.0f, 0.0f, 960.0f, 640.0f);
session.pointerPosition = XCEngine::UI::UIPoint(120.0f, 120.0f);
session.validCanvas = true;
session.hovered = true;
session.windowFocused = true;
return session;
}
XCEngine::UI::UITextureHandle MakeSurfaceTextureHandle(
std::uintptr_t nativeHandle,
std::uint32_t width,
std::uint32_t height) {
XCEngine::UI::UITextureHandle texture = {};
texture.nativeHandle = nativeHandle;
texture.width = width;
texture.height = height;
texture.kind = XCEngine::UI::UITextureHandleKind::ShaderResourceView;
return texture;
}
XCUIInputBridgeFrameSnapshot MakePointerSnapshot(
const XCEngine::UI::UIPoint& pointerPosition,
bool pointerInside,
bool pointerDown,
bool windowFocused) {
XCUIInputBridgeFrameSnapshot snapshot = {};
snapshot.pointerPosition = pointerPosition;
snapshot.pointerInside = pointerInside;
snapshot.pointerButtonsDown[0] = pointerDown;
snapshot.windowFocused = windowFocused;
snapshot.timestampNanoseconds = NextTimestampNanoseconds();
return snapshot;
}
XCUIInputBridgeFrameSnapshot MakeKeyboardSnapshot(
const XCEngine::UI::UIPoint& pointerPosition,
bool pointerInside,
bool windowFocused,
KeyCode keyCode) {
XCUIInputBridgeFrameSnapshot snapshot =
MakePointerSnapshot(pointerPosition, pointerInside, false, windowFocused);
snapshot.keys.push_back(XCUIInputBridgeKeyState {
static_cast<std::int32_t>(keyCode),
true,
false
});
return snapshot;
}
const XCUILayoutLabFrameComposition& ComposePanelFrame(
XCUILayoutLabPanel& panel,
const XCUIPanelCanvasSession& canvasSession,
const XCUIInputBridgeFrameSnapshot& snapshot) {
XCUILayoutLabFrameCompositionRequest request = {};
request.canvasSession = canvasSession;
request.inputSnapshot = snapshot;
return panel.ComposeFrame(request);
}
void ClickElementWithComposition(
XCUILayoutLabPanel& panel,
const XCUIPanelCanvasSession& baseSession,
const std::string& elementId) {
XCEngine::UI::UIRect elementRect = {};
ASSERT_TRUE(panel.TryGetElementRect(elementId, elementRect));
const XCEngine::UI::UIPoint clickPoint(
elementRect.x + elementRect.width * 0.5f,
elementRect.y + elementRect.height * 0.5f);
XCUIPanelCanvasSession clickSession = baseSession;
clickSession.pointerPosition = clickPoint;
clickSession.hovered = true;
ComposePanelFrame(panel, clickSession, MakePointerSnapshot(clickPoint, true, true, true));
ComposePanelFrame(panel, clickSession, MakePointerSnapshot(clickPoint, true, false, true));
}
void RenderPanelFrame(
XCUILayoutLabPanel& panel,
ImGuiContextScope&,
const std::function<void(ImGuiIO&)>& configureIo = nullptr) {
PrepareImGui();
if (configureIo) {
configureIo(ImGui::GetIO());
}
ImGui::NewFrame();
panel.Render();
ImGui::Render();
}
void ClickElement(
XCUILayoutLabPanel& panel,
StubCanvasHost& canvasHost,
const std::string& elementId,
ImGuiContextScope& contextScope) {
XCEngine::UI::UIRect elementRect = {};
ASSERT_TRUE(panel.TryGetElementRect(elementId, elementRect));
const XCEngine::UI::UIPoint clickPoint(
elementRect.x + elementRect.width * 0.5f,
elementRect.y + elementRect.height * 0.5f);
canvasHost.SetPointerPosition(clickPoint);
canvasHost.SetHovered(true);
RenderPanelFrame(
panel,
contextScope,
[clickPoint](ImGuiIO& io) {
io.AddMousePosEvent(clickPoint.x, clickPoint.y);
io.AddMouseButtonEvent(ImGuiMouseButton_Left, true);
});
RenderPanelFrame(
panel,
contextScope,
[clickPoint](ImGuiIO& io) {
io.AddMousePosEvent(clickPoint.x, clickPoint.y);
io.AddMouseButtonEvent(ImGuiMouseButton_Left, false);
});
}
void PressKeyOnce(
XCUILayoutLabPanel& panel,
ImGuiContextScope& contextScope,
ImGuiKey key) {
RenderPanelFrame(
panel,
contextScope,
[key](ImGuiIO& io) {
io.AddKeyEvent(key, true);
});
RenderPanelFrame(
panel,
contextScope,
[key](ImGuiIO& io) {
io.AddKeyEvent(key, false);
});
}
TEST(NewEditorXCUILayoutLabPanelTest, MapsPreviousNextHomeAndEndIntoRuntimeNavigation) {
auto previewPresenter = std::make_unique<StubHostedPreviewPresenter>();
auto canvasHost = std::make_unique<StubCanvasHost>();
XCUILayoutLabPanel panel(nullptr, std::move(previewPresenter), std::move(canvasHost));
panel.SetHostedPreviewEnabled(false);
XCUIPanelCanvasSession session = MakeCanvasSession();
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, true, false, true));
ClickElementWithComposition(panel, session, "assetLighting");
ASSERT_EQ(panel.GetFrameResult().stats.selectedElementId, "assetLighting");
session.hovered = false;
session.windowFocused = true;
const XCUILayoutLabFrameComposition& downComposition = ComposePanelFrame(
panel,
session,
MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Down));
EXPECT_TRUE(downComposition.inputState.navigateNext);
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true));
EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "assetMaterials");
const XCUILayoutLabFrameComposition& upComposition = ComposePanelFrame(
panel,
session,
MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Up));
EXPECT_TRUE(upComposition.inputState.navigatePrevious);
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true));
EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "assetLighting");
const XCUILayoutLabFrameComposition& endComposition = ComposePanelFrame(
panel,
session,
MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::End));
EXPECT_TRUE(endComposition.inputState.navigateEnd);
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true));
const std::string endSelection = panel.GetFrameResult().stats.selectedElementId;
ASSERT_FALSE(endSelection.empty());
EXPECT_NE(endSelection, "assetLighting");
const XCUILayoutLabFrameComposition& homeComposition = ComposePanelFrame(
panel,
session,
MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Home));
EXPECT_TRUE(homeComposition.inputState.navigateHome);
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true));
EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "assetLighting");
}
TEST(NewEditorXCUILayoutLabPanelTest, MapsCollapseAndExpandIntoRuntimeNavigation) {
auto previewPresenter = std::make_unique<StubHostedPreviewPresenter>();
auto canvasHost = std::make_unique<StubCanvasHost>();
XCUILayoutLabPanel panel(nullptr, std::move(previewPresenter), std::move(canvasHost));
panel.SetHostedPreviewEnabled(false);
XCUIPanelCanvasSession session = MakeCanvasSession();
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, true, false, true));
ClickElementWithComposition(panel, session, "treeScenes");
ASSERT_EQ(panel.GetFrameResult().stats.selectedElementId, "treeScenes");
session.hovered = false;
session.windowFocused = true;
const XCUILayoutLabFrameComposition& collapseSelection = ComposePanelFrame(
panel,
session,
MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Left));
EXPECT_TRUE(collapseSelection.inputState.navigateCollapse);
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true));
EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "treeAssetsRoot");
ComposePanelFrame(panel, session, MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Left));
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true));
EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "treeAssetsRoot");
EXPECT_EQ(panel.GetFrameResult().stats.expandedTreeItemCount, 0u);
const XCUILayoutLabFrameComposition& expandRoot = ComposePanelFrame(
panel,
session,
MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Right));
EXPECT_TRUE(expandRoot.inputState.navigateExpand);
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true));
EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "treeAssetsRoot");
EXPECT_EQ(panel.GetFrameResult().stats.expandedTreeItemCount, 1u);
ComposePanelFrame(panel, session, MakeKeyboardSnapshot(session.pointerPosition, false, true, KeyCode::Right));
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, false, false, true));
EXPECT_EQ(panel.GetFrameResult().stats.selectedElementId, "treeScenes");
}
TEST(NewEditorXCUILayoutLabPanelTest, DefaultFallbackDoesNotCreateImplicitHostedPreviewPresenter) {
ImGuiContextScope contextScope;
XCUILayoutLabPanel panel(nullptr);
RenderPanelFrame(panel, contextScope);
EXPECT_FALSE(panel.IsUsingNativeHostedPreview());
EXPECT_FALSE(panel.GetLastPreviewStats().presented);
EXPECT_EQ(panel.GetLastPreviewStats().submittedDrawListCount, 0u);
EXPECT_EQ(panel.GetLastPreviewStats().submittedCommandCount, 0u);
}
TEST(NewEditorXCUILayoutLabPanelTest, ComposeFramePublishesShellAgnosticLastCompositionState) {
auto previewPresenter = std::make_unique<StubHostedPreviewPresenter>();
auto canvasHost = std::make_unique<StubCanvasHost>();
XCUILayoutLabPanel panel(nullptr, std::move(previewPresenter), std::move(canvasHost));
XCUIPanelCanvasSession session = MakeCanvasSession();
const XCUILayoutLabFrameComposition& composition =
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, true, false, true));
EXPECT_EQ(&composition, &panel.GetLastFrameComposition());
ASSERT_NE(composition.frameResult, nullptr);
EXPECT_TRUE(composition.previewStats.presented);
EXPECT_EQ(composition.previewPathLabel, "hosted presenter");
EXPECT_EQ(composition.previewStateLabel, "live");
EXPECT_EQ(composition.previewSourceLabel, "new_editor.panels.xcui_layout_lab");
EXPECT_TRUE(composition.hostedPreviewEnabled);
EXPECT_EQ(composition.inputState.canvasRect.width, session.canvasRect.width);
EXPECT_EQ(composition.inputState.canvasRect.height, session.canvasRect.height);
EXPECT_TRUE(composition.inputState.pointerInside);
}
TEST(NewEditorXCUILayoutLabPanelTest, ComposeFrameCarriesNativeQueuedPreviewMetadataIntoComposition) {
auto previewPresenter = std::make_unique<StubNativeHostedPreviewPresenter>();
XCUIHostedPreviewSurfaceDescriptor descriptor = {};
descriptor.debugName = "XCUI Layout Lab";
descriptor.debugSource = "tests.native_layout_preview";
descriptor.logicalSize = XCEngine::UI::UISize(512.0f, 320.0f);
descriptor.queuedFrameIndex = 7u;
descriptor.image.texture = MakeSurfaceTextureHandle(42u, 512u, 320u);
descriptor.image.surfaceWidth = 512u;
descriptor.image.surfaceHeight = 320u;
descriptor.image.renderedCanvasRect = XCEngine::UI::UIRect(0.0f, 0.0f, 512.0f, 320.0f);
previewPresenter->SetDescriptor(std::move(descriptor));
StubNativeHostedPreviewPresenter* previewPresenterRaw = previewPresenter.get();
XCUILayoutLabPanel panel(nullptr, std::move(previewPresenter), std::make_unique<StubCanvasHost>());
XCUIPanelCanvasSession session = MakeCanvasSession();
const XCUILayoutLabFrameComposition& composition =
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, true, false, true));
EXPECT_TRUE(composition.hostedPreviewEnabled);
EXPECT_TRUE(composition.nativeHostedPreview);
EXPECT_TRUE(composition.hasHostedSurfaceDescriptor);
EXPECT_TRUE(composition.showHostedSurfaceImage);
EXPECT_EQ(composition.previewPathLabel, "native queued offscreen surface");
EXPECT_EQ(composition.previewStateLabel, "live");
EXPECT_EQ(composition.previewSourceLabel, "tests.native_layout_preview");
EXPECT_EQ(composition.hostedSurfaceDescriptor.queuedFrameIndex, 7u);
EXPECT_EQ(composition.hostedSurfaceImage.texture.nativeHandle, 42u);
EXPECT_TRUE(composition.previewStats.presented);
EXPECT_TRUE(composition.previewStats.queuedToNativePass);
EXPECT_EQ(previewPresenterRaw->GetPresentCallCount(), 1u);
EXPECT_EQ(previewPresenterRaw->GetLastFrame().debugName, std::string("XCUI Layout Lab"));
EXPECT_EQ(previewPresenterRaw->GetLastFrame().debugSource, std::string("new_editor.panels.xcui_layout_lab"));
EXPECT_FLOAT_EQ(previewPresenterRaw->GetLastFrame().logicalSize.width, session.canvasRect.width);
EXPECT_FLOAT_EQ(previewPresenterRaw->GetLastFrame().logicalSize.height, session.canvasRect.height);
}
TEST(NewEditorXCUILayoutLabPanelTest, ComposeFrameMarksPreviewDisabledWhenPresenterIsInjectedButDisabled) {
auto previewPresenter = std::make_unique<StubHostedPreviewPresenter>();
StubHostedPreviewPresenter* previewPresenterRaw = previewPresenter.get();
XCUILayoutLabPanel panel(nullptr, std::move(previewPresenter), std::make_unique<StubCanvasHost>());
panel.SetHostedPreviewEnabled(false);
XCUIPanelCanvasSession session = MakeCanvasSession();
const XCUILayoutLabFrameComposition& composition =
ComposePanelFrame(panel, session, MakePointerSnapshot(session.pointerPosition, true, false, true));
EXPECT_FALSE(composition.hostedPreviewEnabled);
EXPECT_EQ(composition.previewStateLabel, "disabled");
EXPECT_FALSE(composition.previewStats.presented);
EXPECT_EQ(previewPresenterRaw->GetPresentCallCount(), 0u);
EXPECT_FALSE(panel.GetLastPreviewStats().presented);
}
} // namespace

View File

@@ -1,532 +0,0 @@
#include <gtest/gtest.h>
#include "XCUIBackend/XCUILayoutLabRuntime.h"
#include <XCEngine/UI/Types.h>
#include <string>
#include <vector>
namespace {
using XCEngine::UI::UIDrawCommand;
using XCEngine::UI::UIDrawCommandType;
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState BuildInputState(
float width = 960.0f,
float height = 640.0f) {
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState input = {};
input.canvasRect = XCEngine::UI::UIRect(0.0f, 0.0f, width, height);
input.pointerPosition = XCEngine::UI::UIPoint(width * 0.5f, height * 0.5f);
input.pointerInside = true;
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()) {
for (const UIDrawCommand& command : drawList.GetCommands()) {
if (command.type == UIDrawCommandType::Text) {
textCommands.push_back(&command);
}
}
}
return textCommands;
}
const UIDrawCommand* FindTextCommand(
const XCEngine::UI::UIDrawData& drawData,
const std::string& text) {
for (const XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
for (const UIDrawCommand& command : drawList.GetCommands()) {
if (command.type == UIDrawCommandType::Text && command.text == text) {
return &command;
}
}
}
return nullptr;
}
std::size_t CountCommandsOfType(
const XCEngine::UI::UIDrawData& drawData,
UIDrawCommandType type) {
std::size_t count = 0;
for (const XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
for (const UIDrawCommand& command : drawList.GetCommands()) {
if (command.type == type) {
++count;
}
}
}
return count;
}
} // namespace
TEST(NewEditorXCUILayoutLabRuntimeTest, UpdateBuildsLayoutSmokeFrame) {
XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime runtime;
const bool reloadSucceeded = runtime.ReloadDocuments();
const auto& frame = runtime.Update(BuildInputState());
EXPECT_EQ(frame.stats.documentsReady, reloadSucceeded);
EXPECT_EQ(frame.stats.drawListCount, frame.drawData.GetDrawListCount());
EXPECT_EQ(frame.stats.commandCount, frame.drawData.GetTotalCommandCount());
if (frame.stats.documentsReady) {
EXPECT_GT(frame.stats.drawListCount, 0u);
EXPECT_GT(frame.stats.commandCount, 0u);
EXPECT_GE(frame.stats.rowCount, 1u);
EXPECT_GE(frame.stats.columnCount, 1u);
EXPECT_GE(frame.stats.overlayCount, 1u);
EXPECT_GE(frame.stats.scrollViewCount, 2u);
EXPECT_GE(frame.stats.treeViewCount, 1u);
EXPECT_GE(frame.stats.treeItemCount, 3u);
EXPECT_GE(frame.stats.listViewCount, 1u);
EXPECT_GE(frame.stats.listItemCount, 3u);
EXPECT_GE(frame.stats.propertySectionCount, 2u);
EXPECT_GE(frame.stats.fieldRowCount, 4u);
XCEngine::UI::UIRect heroRect = {};
EXPECT_TRUE(runtime.TryGetElementRect("heroCard", heroRect));
EXPECT_GT(heroRect.width, 0.0f);
EXPECT_GT(heroRect.height, 0.0f);
} else {
EXPECT_FALSE(frame.stats.statusMessage.empty());
}
}
TEST(NewEditorXCUILayoutLabRuntimeTest, FrameIncludesTextCommandsWithThemeFontSizes) {
XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& frame = runtime.Update(BuildInputState());
ASSERT_TRUE(frame.stats.documentsReady);
const std::vector<const UIDrawCommand*> textCommands = CollectTextCommands(frame.drawData);
ASSERT_FALSE(textCommands.empty());
for (const UIDrawCommand* command : textCommands) {
ASSERT_NE(command, nullptr);
EXPECT_FALSE(command->text.empty());
EXPECT_GT(command->fontSize, 0.0f);
}
const UIDrawCommand* titleCommand = FindTextCommand(frame.drawData, "XCUI Layout Lab");
ASSERT_NE(titleCommand, nullptr);
EXPECT_FLOAT_EQ(titleCommand->fontSize, 16.0f);
const UIDrawCommand* subtitleCommand = FindTextCommand(
frame.drawData,
"Editor-style panels with overlay and scroll semantics.");
ASSERT_NE(subtitleCommand, nullptr);
EXPECT_FLOAT_EQ(subtitleCommand->fontSize, 13.0f);
}
TEST(NewEditorXCUILayoutLabRuntimeTest, HoverProbeResolvesTrackedElementRect) {
XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& baseline = runtime.Update(BuildInputState());
ASSERT_TRUE(baseline.stats.documentsReady);
XCEngine::UI::UIRect probeRect = {};
ASSERT_TRUE(runtime.TryGetElementRect("assetLighting", probeRect));
ASSERT_GT(probeRect.width, 0.0f);
ASSERT_GT(probeRect.height, 0.0f);
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState input = BuildInputState();
input.pointerPosition = XCEngine::UI::UIPoint(
probeRect.x + probeRect.width * 0.5f,
probeRect.y + probeRect.height * 0.5f);
const auto& frame = runtime.Update(input);
ASSERT_TRUE(frame.stats.documentsReady);
EXPECT_FALSE(frame.stats.hoveredElementId.empty());
XCEngine::UI::UIRect hoveredRect = {};
EXPECT_TRUE(runtime.TryGetElementRect(frame.stats.hoveredElementId, hoveredRect));
EXPECT_GT(hoveredRect.width, 0.0f);
EXPECT_GT(hoveredRect.height, 0.0f);
}
TEST(NewEditorXCUILayoutLabRuntimeTest, EditorPrototypeWidgetsExposeRectsAndLabels) {
XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& frame = runtime.Update(BuildInputState());
ASSERT_TRUE(frame.stats.documentsReady);
XCEngine::UI::UIRect projectTreeRect = {};
XCEngine::UI::UIRect fieldPositionRect = {};
ASSERT_TRUE(runtime.TryGetElementRect("projectTree", projectTreeRect));
ASSERT_TRUE(runtime.TryGetElementRect("fieldPosition", fieldPositionRect));
EXPECT_GT(projectTreeRect.height, 0.0f);
EXPECT_GT(fieldPositionRect.width, 0.0f);
EXPECT_NE(FindTextCommand(frame.drawData, "Assets"), nullptr);
EXPECT_NE(FindTextCommand(frame.drawData, "Lighting_GlobalRig"), nullptr);
EXPECT_NE(FindTextCommand(frame.drawData, "Position"), nullptr);
EXPECT_NE(FindTextCommand(frame.drawData, "0.0, 1.5, 0.0"), nullptr);
}
TEST(NewEditorXCUILayoutLabRuntimeTest, ScrollViewOffsetsContentAndAddsNestedClips) {
XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& frame = runtime.Update(BuildInputState());
ASSERT_TRUE(frame.stats.documentsReady);
XCEngine::UI::UIRect assetListRect = {};
XCEngine::UI::UIRect headerRect = {};
XCEngine::UI::UIRect visibleItemRect = {};
ASSERT_TRUE(runtime.TryGetElementRect("assetList", assetListRect));
ASSERT_TRUE(runtime.TryGetElementRect("assetListHeader", headerRect));
ASSERT_TRUE(runtime.TryGetElementRect("assetLighting", visibleItemRect));
EXPECT_LT(headerRect.y, assetListRect.y);
EXPECT_GT(visibleItemRect.y, assetListRect.y);
EXPECT_LT(visibleItemRect.y, assetListRect.y + assetListRect.height);
EXPECT_EQ(
CountCommandsOfType(frame.drawData, UIDrawCommandType::PushClipRect),
frame.stats.clipPushCommandCount);
EXPECT_EQ(
CountCommandsOfType(frame.drawData, UIDrawCommandType::PopClipRect),
frame.stats.clipPopCommandCount);
EXPECT_GE(frame.stats.clipPushCommandCount, 3u);
EXPECT_GE(frame.stats.clipPopCommandCount, 3u);
}
TEST(NewEditorXCUILayoutLabRuntimeTest, HoverIgnoresClippedScrollViewContent) {
XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& baseline = runtime.Update(BuildInputState());
ASSERT_TRUE(baseline.stats.documentsReady);
XCEngine::UI::UIRect assetListRect = {};
ASSERT_TRUE(runtime.TryGetElementRect("assetList", assetListRect));
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState input = BuildInputState();
input.pointerPosition = XCEngine::UI::UIPoint(
assetListRect.x + assetListRect.width * 0.5f,
assetListRect.y + assetListRect.height + 6.0f);
const auto& frame = runtime.Update(input);
ASSERT_TRUE(frame.stats.documentsReady);
EXPECT_TRUE(frame.stats.hoveredElementId.empty());
}
TEST(NewEditorXCUILayoutLabRuntimeTest, ClickSelectionPersistsOnSharedCollectionItems) {
XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& baseline = runtime.Update(BuildInputState());
ASSERT_TRUE(baseline.stats.documentsReady);
XCEngine::UI::UIRect targetRect = {};
ASSERT_TRUE(runtime.TryGetElementRect("fieldPosition", targetRect));
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState clickInput = BuildInputState();
clickInput.pointerPosition = XCEngine::UI::UIPoint(
targetRect.x + targetRect.width * 0.5f,
targetRect.y + targetRect.height * 0.5f);
clickInput.pointerPressed = true;
const auto& selectedFrame = runtime.Update(clickInput);
ASSERT_TRUE(selectedFrame.stats.documentsReady);
ASSERT_FALSE(selectedFrame.stats.hoveredElementId.empty());
EXPECT_EQ(
selectedFrame.stats.selectedElementId,
selectedFrame.stats.hoveredElementId);
const std::string selectedElementId = selectedFrame.stats.selectedElementId;
XCEngine::UI::UIRect selectedRect = {};
EXPECT_TRUE(runtime.TryGetElementRect(selectedElementId, selectedRect));
EXPECT_GT(selectedRect.width, 0.0f);
EXPECT_GT(selectedRect.height, 0.0f);
const auto& persistedFrame = runtime.Update(BuildInputState());
ASSERT_TRUE(persistedFrame.stats.documentsReady);
EXPECT_EQ(persistedFrame.stats.selectedElementId, selectedElementId);
}
TEST(NewEditorXCUILayoutLabRuntimeTest, ClickingTreeRootTogglesIndentedChildrenVisibility) {
XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& baseline = runtime.Update(BuildInputState());
ASSERT_TRUE(baseline.stats.documentsReady);
EXPECT_EQ(baseline.stats.expandedTreeItemCount, 1u);
XCEngine::UI::UIRect treeRootRect = {};
XCEngine::UI::UIRect treeChildRect = {};
ASSERT_TRUE(runtime.TryGetElementRect("treeAssetsRoot", treeRootRect));
ASSERT_TRUE(runtime.TryGetElementRect("treeScenes", treeChildRect));
ASSERT_GT(treeRootRect.width, 0.0f);
ASSERT_GT(treeRootRect.height, 0.0f);
const XCEngine::UI::UIPoint rootClickPoint(
treeRootRect.x + 18.0f,
treeRootRect.y + treeRootRect.height * 0.5f);
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState hoverInput = BuildInputState();
hoverInput.pointerPosition = rootClickPoint;
const auto& hoveredFrame = runtime.Update(hoverInput);
ASSERT_TRUE(hoveredFrame.stats.documentsReady);
ASSERT_EQ(hoveredFrame.stats.hoveredElementId, "treeAssetsRoot")
<< "treeRootRect=("
<< treeRootRect.x << ", "
<< treeRootRect.y << ", "
<< treeRootRect.width << ", "
<< treeRootRect.height << ")";
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState collapseInput = BuildInputState();
collapseInput.pointerPosition = rootClickPoint;
collapseInput.pointerPressed = true;
const auto& collapsedFrame = runtime.Update(collapseInput);
ASSERT_TRUE(collapsedFrame.stats.documentsReady);
EXPECT_EQ(collapsedFrame.stats.selectedElementId, "treeAssetsRoot");
const auto& collapsedPersistedFrame = runtime.Update(BuildInputState());
ASSERT_TRUE(collapsedPersistedFrame.stats.documentsReady);
EXPECT_EQ(collapsedPersistedFrame.stats.selectedElementId, "treeAssetsRoot");
EXPECT_EQ(collapsedPersistedFrame.stats.expandedTreeItemCount, 0u);
EXPECT_FALSE(runtime.TryGetElementRect("treeScenes", treeChildRect));
ASSERT_TRUE(runtime.TryGetElementRect("treeAssetsRoot", treeRootRect));
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState expandInput = BuildInputState();
expandInput.pointerPosition = XCEngine::UI::UIPoint(
treeRootRect.x + 18.0f,
treeRootRect.y + treeRootRect.height * 0.5f);
expandInput.pointerPressed = true;
const auto& expandedClickFrame = runtime.Update(expandInput);
ASSERT_TRUE(expandedClickFrame.stats.documentsReady);
const auto& expandedFrame = runtime.Update(BuildInputState());
ASSERT_TRUE(expandedFrame.stats.documentsReady);
EXPECT_EQ(expandedFrame.stats.expandedTreeItemCount, 1u);
EXPECT_TRUE(runtime.TryGetElementRect("treeScenes", treeChildRect));
EXPECT_GT(treeChildRect.height, 0.0f);
}
TEST(NewEditorXCUILayoutLabRuntimeTest, ClickingPropertySectionHeaderTogglesFieldVisibility) {
XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime runtime;
ASSERT_TRUE(runtime.ReloadDocuments());
const auto& baseline = runtime.Update(BuildInputState());
ASSERT_TRUE(baseline.stats.documentsReady);
XCEngine::UI::UIRect sectionRect = {};
XCEngine::UI::UIRect fieldRect = {};
ASSERT_TRUE(runtime.TryGetElementRect("inspectorTransform", sectionRect));
ASSERT_TRUE(runtime.TryGetElementRect("fieldPosition", fieldRect));
const float expandedHeight = sectionRect.height;
const XCEngine::UI::UIPoint sectionHeaderPoint(
sectionRect.x + 18.0f,
sectionRect.y + 10.0f);
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState hoverInput = BuildInputState();
hoverInput.pointerPosition = sectionHeaderPoint;
const auto& hoveredFrame = runtime.Update(hoverInput);
ASSERT_TRUE(hoveredFrame.stats.documentsReady);
ASSERT_EQ(hoveredFrame.stats.hoveredElementId, "inspectorTransform")
<< "sectionRect=("
<< sectionRect.x << ", "
<< sectionRect.y << ", "
<< sectionRect.width << ", "
<< sectionRect.height << "), expandedPropertySectionCount="
<< baseline.stats.expandedPropertySectionCount;
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState collapseInput = BuildInputState();
collapseInput.pointerPosition = sectionHeaderPoint;
collapseInput.pointerPressed = true;
const auto& collapsedFrame = runtime.Update(collapseInput);
ASSERT_TRUE(collapsedFrame.stats.documentsReady);
const auto& collapsedPersistedFrame = runtime.Update(BuildInputState());
ASSERT_TRUE(collapsedPersistedFrame.stats.documentsReady);
EXPECT_EQ(collapsedPersistedFrame.stats.selectedElementId, "inspectorTransform");
ASSERT_TRUE(runtime.TryGetElementRect("inspectorTransform", sectionRect));
EXPECT_LT(sectionRect.height, expandedHeight);
EXPECT_FALSE(runtime.TryGetElementRect("fieldPosition", fieldRect));
XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState expandInput = BuildInputState();
expandInput.pointerPosition = XCEngine::UI::UIPoint(
sectionRect.x + 18.0f,
sectionRect.y + 10.0f);
expandInput.pointerPressed = true;
const auto& expandedClickFrame = runtime.Update(expandInput);
ASSERT_TRUE(expandedClickFrame.stats.documentsReady);
const auto& expandedFrame = runtime.Update(BuildInputState());
ASSERT_TRUE(expandedFrame.stats.documentsReady);
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

@@ -1,133 +0,0 @@
#include "XCUIBackend/XCUINativeShellLayout.h"
#include <gtest/gtest.h>
namespace {
using XCEngine::Editor::XCUIBackend::BuildXCUINativeShellCanvasRect;
using XCEngine::Editor::XCUIBackend::BuildXCUINativeShellLayout;
using XCEngine::Editor::XCUIBackend::XCUINativeShellMetrics;
using XCEngine::Editor::XCUIBackend::XCUINativeShellPanelLayout;
using XCEngine::Editor::XCUIBackend::XCUINativeShellPanelSpec;
using XCEngine::Editor::XCUIBackend::XCUIShellPanelId;
using XCEngine::UI::UIPoint;
using XCEngine::UI::UIRect;
void ExpectRectEq(
const UIRect& actual,
const UIRect& expected) {
EXPECT_FLOAT_EQ(actual.x, expected.x);
EXPECT_FLOAT_EQ(actual.y, expected.y);
EXPECT_FLOAT_EQ(actual.width, expected.width);
EXPECT_FLOAT_EQ(actual.height, expected.height);
}
TEST(XCUINativeShellLayoutTest, CanvasRectUsesPanelHeaderAndPaddingInsets) {
const XCUINativeShellMetrics metrics = {};
const UIRect panelRect(10.0f, 20.0f, 400.0f, 300.0f);
const UIRect canvasRect = BuildXCUINativeShellCanvasRect(panelRect, metrics);
ExpectRectEq(canvasRect, UIRect(24.0f, 62.0f, 372.0f, 244.0f));
}
TEST(XCUINativeShellLayoutTest, ActivePanelFallsBackToVisiblePanelWhenRequestedPanelIsHidden) {
const auto layout = BuildXCUINativeShellLayout(
UIRect(0.0f, 0.0f, 1200.0f, 800.0f),
{
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUIDemo, "XCUI Demo", false },
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUILayoutLab, "XCUI Layout Lab", true },
},
XCUIShellPanelId::XCUIDemo,
UIPoint(),
false,
false);
ASSERT_EQ(layout.panelLayouts.size(), 1u);
EXPECT_EQ(layout.activePanel, XCUIShellPanelId::XCUILayoutLab);
EXPECT_TRUE(layout.panelLayouts.front().active);
}
TEST(XCUINativeShellLayoutTest, TwoPanelSplitMatchesCurrentSandboxPolicy) {
const XCUINativeShellMetrics metrics = {};
const auto layout = BuildXCUINativeShellLayout(
UIRect(0.0f, 0.0f, 1200.0f, 800.0f),
{
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUIDemo, "XCUI Demo", true },
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUILayoutLab, "XCUI Layout Lab", true },
},
XCUIShellPanelId::XCUIDemo,
UIPoint(),
false,
false,
metrics);
ExpectRectEq(layout.topBarRect, UIRect(22.0f, 22.0f, 1156.0f, 58.0f));
ExpectRectEq(layout.footerRect, UIRect(22.0f, 744.0f, 1156.0f, 34.0f));
ExpectRectEq(layout.workspaceRect, UIRect(22.0f, 98.0f, 1156.0f, 628.0f));
ASSERT_EQ(layout.panelLayouts.size(), 2u);
const XCUINativeShellPanelLayout& leftPanel = layout.panelLayouts[0];
const XCUINativeShellPanelLayout& rightPanel = layout.panelLayouts[1];
EXPECT_EQ(leftPanel.panelId, XCUIShellPanelId::XCUIDemo);
EXPECT_EQ(rightPanel.panelId, XCUIShellPanelId::XCUILayoutLab);
EXPECT_NEAR(leftPanel.panelRect.width, 693.6f, 0.001f);
EXPECT_NEAR(rightPanel.panelRect.width, 444.4f, 0.001f);
EXPECT_NEAR(rightPanel.panelRect.x, 733.6f, 0.001f);
EXPECT_FLOAT_EQ(leftPanel.panelRect.height, 628.0f);
EXPECT_FLOAT_EQ(rightPanel.panelRect.height, 628.0f);
}
TEST(XCUINativeShellLayoutTest, PointerPressTransfersActivePanelToHoveredCanvas) {
const auto layout = BuildXCUINativeShellLayout(
UIRect(0.0f, 0.0f, 1200.0f, 800.0f),
{
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUIDemo, "XCUI Demo", true },
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUILayoutLab, "XCUI Layout Lab", true },
},
XCUIShellPanelId::XCUIDemo,
UIPoint(900.0f, 200.0f),
true,
true);
ASSERT_EQ(layout.panelLayouts.size(), 2u);
EXPECT_EQ(layout.activePanel, XCUIShellPanelId::XCUILayoutLab);
EXPECT_FALSE(layout.panelLayouts[0].active);
EXPECT_TRUE(layout.panelLayouts[1].active);
EXPECT_TRUE(layout.panelLayouts[1].hovered);
}
TEST(XCUINativeShellLayoutTest, SingleVisiblePanelFillsWorkspaceAndBecomesActive) {
const auto layout = BuildXCUINativeShellLayout(
UIRect(0.0f, 0.0f, 1000.0f, 720.0f),
{
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUIDemo, "XCUI Demo", true },
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUILayoutLab, "XCUI Layout Lab", false },
},
XCUIShellPanelId::XCUILayoutLab,
UIPoint(),
false,
false);
ASSERT_EQ(layout.panelLayouts.size(), 1u);
EXPECT_EQ(layout.activePanel, XCUIShellPanelId::XCUIDemo);
ExpectRectEq(layout.panelLayouts[0].panelRect, layout.workspaceRect);
}
TEST(XCUINativeShellLayoutTest, UndersizedPanelStillParticipatesButReportsNotVisible) {
const auto layout = BuildXCUINativeShellLayout(
UIRect(0.0f, 0.0f, 240.0f, 400.0f),
{
XCUINativeShellPanelSpec{ XCUIShellPanelId::XCUIDemo, "XCUI Demo", true },
},
XCUIShellPanelId::XCUIDemo,
UIPoint(),
false,
false);
ASSERT_EQ(layout.panelLayouts.size(), 1u);
EXPECT_FALSE(layout.panelLayouts[0].visible);
ExpectRectEq(layout.panelLayouts[0].panelRect, layout.workspaceRect);
}
} // namespace

View File

@@ -1,111 +0,0 @@
#include <gtest/gtest.h>
#include "XCUIBackend/NullXCUIPanelCanvasHost.h"
namespace {
using XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::BuildPassiveXCUIPanelCanvasSession;
using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::ResolveXCUIPanelCanvasChildId;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession;
TEST(NewEditorXCUIPanelCanvasHostTest, NullHostProvidesStableDebugNameAndSafePassiveSessions) {
std::unique_ptr<IXCUIPanelCanvasHost> host = CreateNullXCUIPanelCanvasHost();
ASSERT_NE(host, nullptr);
EXPECT_STREQ(host->GetDebugName(), "NullXCUIPanelCanvasHost");
const XCUIPanelCanvasSession session = host->BeginCanvas({});
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);
host->EndCanvas();
}
TEST(NewEditorXCUIPanelCanvasHostTest, ResolveChildIdFallsBackToStableDefaultForMissingNames) {
XCUIPanelCanvasRequest request = {};
EXPECT_STREQ(ResolveXCUIPanelCanvasChildId(request), "XCUIPanelCanvasHost");
EXPECT_STREQ(ResolveXCUIPanelCanvasChildId(request, "FallbackCanvas"), "FallbackCanvas");
request.childId = "";
EXPECT_STREQ(ResolveXCUIPanelCanvasChildId(request), "XCUIPanelCanvasHost");
request.childId = "CanvasHost";
EXPECT_STREQ(ResolveXCUIPanelCanvasChildId(request, "FallbackCanvas"), "CanvasHost");
}
TEST(NewEditorXCUIPanelCanvasHostTest, PassiveSessionClampsRequestGeometryIntoSafeDefaultState) {
XCUIPanelCanvasRequest request = {};
request.height = -18.0f;
request.topInset = 42.0f;
XCUIPanelCanvasSession session = BuildPassiveXCUIPanelCanvasSession(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.y, 0.0f);
EXPECT_FLOAT_EQ(session.canvasRect.height, 0.0f);
request.height = 120.0f;
request.topInset = 180.0f;
session = BuildPassiveXCUIPanelCanvasSession(request);
EXPECT_FLOAT_EQ(session.hostRect.height, 120.0f);
EXPECT_FLOAT_EQ(session.canvasRect.y, 120.0f);
EXPECT_FLOAT_EQ(session.canvasRect.height, 0.0f);
}
TEST(NewEditorXCUIPanelCanvasHostTest, NullHostBeginCanvasReturnsSafePassiveSessionAndDrawCallsAreNoops) {
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, 280.0f);
EXPECT_FLOAT_EQ(session.canvasRect.width, 0.0f);
EXPECT_FLOAT_EQ(session.canvasRect.y, 24.0f);
EXPECT_FLOAT_EQ(session.canvasRect.height, 256.0f);
EXPECT_FLOAT_EQ(session.pointerPosition.x, 0.0f);
EXPECT_FLOAT_EQ(session.pointerPosition.y, 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();
const XCUIPanelCanvasSession secondSession = host->BeginCanvas({});
EXPECT_FLOAT_EQ(secondSession.hostRect.height, 0.0f);
EXPECT_FLOAT_EQ(secondSession.canvasRect.height, 0.0f);
host->EndCanvas();
}
} // namespace

View File

@@ -1,271 +0,0 @@
#include <gtest/gtest.h>
#include "XCUIBackend/XCUIRHICommandCompiler.h"
namespace {
using XCEngine::Editor::XCUIBackend::XCUIRHICommandCompiler;
using XCEngine::UI::UIColor;
using XCEngine::UI::UIDrawData;
using XCEngine::UI::UIDrawList;
using XCEngine::UI::UIPoint;
using XCEngine::UI::UIRect;
using XCEngine::UI::UITextureHandle;
using XCEngine::UI::UITextureHandleKind;
class StubTextGlyphProvider final : public XCUIRHICommandCompiler::TextGlyphProvider {
public:
bool BeginText(
float requestedFontSize,
XCUIRHICommandCompiler::TextRunContext& outContext) const override {
outContext.requestedFontSize = requestedFontSize;
outContext.resolvedFontSize = requestedFontSize > 0.0f ? requestedFontSize : 14.0f;
outContext.lineHeight = 12.0f;
outContext.texture = UITextureHandle{ 99u, 256u, 256u, UITextureHandleKind::ShaderResourceView };
return true;
}
bool ResolveGlyph(
const XCUIRHICommandCompiler::TextRunContext&,
std::uint32_t codepoint,
XCUIRHICommandCompiler::TextGlyph& outGlyph) const override {
switch (codepoint) {
case 'A':
outGlyph.x0 = 0.0f;
outGlyph.y0 = 0.0f;
outGlyph.x1 = 8.0f;
outGlyph.y1 = 10.0f;
outGlyph.u0 = 0.0f;
outGlyph.v0 = 0.0f;
outGlyph.u1 = 0.25f;
outGlyph.v1 = 0.5f;
outGlyph.advanceX = 8.0f;
outGlyph.visible = true;
return true;
case 'B':
outGlyph.x0 = 0.0f;
outGlyph.y0 = 0.0f;
outGlyph.x1 = 7.0f;
outGlyph.y1 = 10.0f;
outGlyph.u0 = 0.25f;
outGlyph.v0 = 0.0f;
outGlyph.u1 = 0.5f;
outGlyph.v1 = 0.5f;
outGlyph.advanceX = 7.0f;
outGlyph.visible = true;
return true;
default:
return false;
}
}
};
TEST(XCUIRHICommandCompilerTest, CompileMergesAdjacentColorAndTexturedCommandsPerDrawList) {
XCUIRHICommandCompiler compiler = {};
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("Batches");
drawList.AddFilledRect(UIRect(0.0f, 0.0f, 10.0f, 10.0f), UIColor(1.0f, 0.0f, 0.0f, 1.0f));
drawList.AddFilledRect(UIRect(10.0f, 0.0f, 10.0f, 10.0f), UIColor(0.0f, 1.0f, 0.0f, 1.0f));
const UITextureHandle texture{ 7u, 64u, 64u, UITextureHandleKind::ShaderResourceView };
drawList.AddImage(UIRect(0.0f, 20.0f, 10.0f, 10.0f), texture, UIColor(1.0f, 1.0f, 1.0f, 1.0f));
drawList.AddImage(UIRect(10.0f, 20.0f, 10.0f, 10.0f), texture, UIColor(0.5f, 0.5f, 0.5f, 1.0f));
XCUIRHICommandCompiler::CompileConfig config = {};
config.surfaceClipRect = UIRect(0.0f, 0.0f, 100.0f, 100.0f);
XCUIRHICommandCompiler::CompiledDrawData compiled = {};
compiler.Compile(drawData, config, compiled);
ASSERT_EQ(compiled.batches.size(), 2u);
EXPECT_EQ(compiled.batches[0].kind, XCUIRHICommandCompiler::BatchKind::Color);
EXPECT_EQ(compiled.batches[0].commandCount, 2u);
EXPECT_EQ(compiled.batches[0].vertexCount, 12u);
EXPECT_EQ(compiled.batches[1].kind, XCUIRHICommandCompiler::BatchKind::Textured);
EXPECT_EQ(compiled.batches[1].commandCount, 2u);
EXPECT_EQ(compiled.batches[1].vertexCount, 12u);
EXPECT_EQ(compiled.batches[1].texture.nativeHandle, texture.nativeHandle);
EXPECT_EQ(compiled.stats.compiledCommandCount, 4u);
EXPECT_EQ(compiled.stats.batchCount, 2u);
EXPECT_EQ(compiled.stats.colorVertexCount, 12u);
EXPECT_EQ(compiled.stats.texturedVertexCount, 12u);
}
TEST(XCUIRHICommandCompilerTest, CompileTracksClipStackTransitionsAndUnderflow) {
XCUIRHICommandCompiler compiler = {};
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("ClipStack");
drawList.PushClipRect(UIRect(0.0f, 0.0f, 50.0f, 50.0f));
drawList.PushClipRect(UIRect(10.0f, 10.0f, 40.0f, 40.0f));
drawList.AddFilledRect(UIRect(0.0f, 0.0f, 30.0f, 30.0f), UIColor(1.0f, 0.0f, 0.0f, 1.0f));
drawList.PopClipRect();
drawList.AddFilledRect(UIRect(0.0f, 0.0f, 20.0f, 20.0f), UIColor(0.0f, 1.0f, 0.0f, 1.0f));
drawList.PopClipRect();
drawList.PopClipRect();
XCUIRHICommandCompiler::CompileConfig config = {};
config.surfaceClipRect = UIRect(0.0f, 0.0f, 100.0f, 100.0f);
XCUIRHICommandCompiler::CompiledDrawData compiled = {};
compiler.Compile(drawData, config, compiled);
ASSERT_EQ(compiled.batches.size(), 2u);
EXPECT_EQ(compiled.stats.compiledCommandCount, 7u);
EXPECT_EQ(compiled.stats.clipPushCommandCount, 2u);
EXPECT_EQ(compiled.stats.clipPopCommandCount, 3u);
EXPECT_EQ(compiled.stats.clipStackUnderflowCount, 1u);
EXPECT_EQ(compiled.stats.maxClipDepth, 2u);
EXPECT_EQ(compiled.stats.danglingClipDepth, 0u);
EXPECT_FLOAT_EQ(compiled.batches[0].clipRect.x, 10.0f);
EXPECT_FLOAT_EQ(compiled.batches[0].clipRect.y, 10.0f);
EXPECT_FLOAT_EQ(compiled.batches[0].clipRect.width, 40.0f);
EXPECT_FLOAT_EQ(compiled.batches[0].clipRect.height, 40.0f);
ASSERT_GE(compiled.colorVertices.size(), 12u);
EXPECT_FLOAT_EQ(compiled.colorVertices[0].position[0], 10.0f);
EXPECT_FLOAT_EQ(compiled.colorVertices[0].position[1], 10.0f);
EXPECT_FLOAT_EQ(compiled.colorVertices[6].position[0], 0.0f);
EXPECT_FLOAT_EQ(compiled.colorVertices[6].position[1], 0.0f);
}
TEST(XCUIRHICommandCompilerTest, CompileUsesTextGlyphProviderOutputForTextBatches) {
XCUIRHICommandCompiler compiler = {};
StubTextGlyphProvider glyphProvider = {};
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("Text");
drawList.AddText(UIPoint(4.0f, 6.0f), "A\nB", UIColor(1.0f, 1.0f, 1.0f, 1.0f), 16.0f);
XCUIRHICommandCompiler::CompileConfig config = {};
config.surfaceClipRect = UIRect(0.0f, 0.0f, 100.0f, 100.0f);
config.textGlyphProvider = &glyphProvider;
XCUIRHICommandCompiler::CompiledDrawData compiled = {};
compiler.Compile(drawData, config, compiled);
ASSERT_EQ(compiled.batches.size(), 1u);
EXPECT_EQ(compiled.batches[0].kind, XCUIRHICommandCompiler::BatchKind::Textured);
EXPECT_EQ(compiled.batches[0].vertexCount, 12u);
EXPECT_EQ(compiled.batches[0].texture.nativeHandle, 99u);
EXPECT_EQ(compiled.stats.textCommandCount, 1u);
EXPECT_EQ(compiled.stats.compiledCommandCount, 1u);
ASSERT_EQ(compiled.texturedVertices.size(), 12u);
EXPECT_FLOAT_EQ(compiled.texturedVertices[0].position[0], 4.0f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[0].position[1], 6.0f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[6].position[0], 4.0f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[6].position[1], 18.0f);
}
TEST(XCUIRHICommandCompilerTest, CompileReportsUnsupportedTextWhenNoGlyphProviderIsAvailable) {
XCUIRHICommandCompiler compiler = {};
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("UnsupportedText");
drawList.AddText(UIPoint(0.0f, 0.0f), "xcui", UIColor(1.0f, 1.0f, 1.0f, 1.0f), 12.0f);
XCUIRHICommandCompiler::CompileConfig config = {};
config.surfaceClipRect = UIRect(0.0f, 0.0f, 32.0f, 32.0f);
XCUIRHICommandCompiler::CompiledDrawData compiled = {};
compiler.Compile(drawData, config, compiled);
EXPECT_TRUE(compiled.Empty());
EXPECT_EQ(compiled.stats.textCommandCount, 1u);
EXPECT_EQ(compiled.stats.compiledCommandCount, 0u);
EXPECT_EQ(compiled.stats.skippedCommandCount, 1u);
EXPECT_EQ(compiled.stats.unsupportedCommandCount, 1u);
}
TEST(XCUIRHICommandCompilerTest, CompileClipsImageCommandsAndAdjustsUvCoordinates) {
XCUIRHICommandCompiler compiler = {};
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("ClippedImage");
drawList.PushClipRect(UIRect(10.0f, 8.0f, 12.0f, 10.0f));
drawList.AddImage(
UIRect(4.0f, 4.0f, 20.0f, 20.0f),
UITextureHandle{ 77u, 32u, 32u, UITextureHandleKind::ShaderResourceView },
UIColor(0.7f, 0.8f, 0.9f, 1.0f));
XCUIRHICommandCompiler::CompileConfig config = {};
config.surfaceClipRect = UIRect(0.0f, 0.0f, 64.0f, 64.0f);
XCUIRHICommandCompiler::CompiledDrawData compiled = {};
compiler.Compile(drawData, config, compiled);
ASSERT_EQ(compiled.batches.size(), 1u);
ASSERT_EQ(compiled.texturedVertices.size(), 6u);
EXPECT_EQ(compiled.batches[0].kind, XCUIRHICommandCompiler::BatchKind::Textured);
EXPECT_FLOAT_EQ(compiled.batches[0].clipRect.x, 10.0f);
EXPECT_FLOAT_EQ(compiled.batches[0].clipRect.y, 8.0f);
EXPECT_FLOAT_EQ(compiled.batches[0].clipRect.width, 12.0f);
EXPECT_FLOAT_EQ(compiled.batches[0].clipRect.height, 10.0f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[0].position[0], 10.0f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[0].position[1], 8.0f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[0].uv[0], 0.3f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[0].uv[1], 0.2f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[1].position[0], 22.0f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[1].uv[0], 0.9f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[2].position[1], 18.0f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[2].uv[1], 0.7f);
EXPECT_EQ(compiled.stats.imageCommandCount, 1u);
EXPECT_EQ(compiled.stats.compiledCommandCount, 2u);
}
TEST(XCUIRHICommandCompilerTest, CompilePreservesMirroredImageUvForNegativeRectExtents) {
XCUIRHICommandCompiler compiler = {};
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("MirroredImage");
drawList.AddImage(
UIRect(24.0f, 18.0f, -16.0f, -12.0f),
UITextureHandle{ 91u, 64u, 64u, UITextureHandleKind::ShaderResourceView },
UIColor(1.0f, 1.0f, 1.0f, 1.0f));
XCUIRHICommandCompiler::CompileConfig config = {};
config.surfaceClipRect = UIRect(0.0f, 0.0f, 64.0f, 64.0f);
XCUIRHICommandCompiler::CompiledDrawData compiled = {};
compiler.Compile(drawData, config, compiled);
ASSERT_EQ(compiled.batches.size(), 1u);
ASSERT_EQ(compiled.texturedVertices.size(), 6u);
EXPECT_FLOAT_EQ(compiled.texturedVertices[0].position[0], 8.0f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[0].position[1], 6.0f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[0].uv[0], 1.0f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[0].uv[1], 1.0f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[1].position[0], 24.0f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[1].uv[0], 0.0f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[2].position[1], 18.0f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[2].uv[1], 0.0f);
EXPECT_EQ(compiled.stats.imageCommandCount, 1u);
EXPECT_EQ(compiled.stats.compiledCommandCount, 1u);
}
TEST(XCUIRHICommandCompilerTest, CompileUsesExplicitImageUvRectWhenProvided) {
XCUIRHICommandCompiler compiler = {};
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("ExplicitUvImage");
drawList.AddImage(
UIRect(8.0f, 6.0f, 16.0f, 12.0f),
UITextureHandle{ 123u, 64u, 64u, UITextureHandleKind::ShaderResourceView },
UIColor(0.9f, 0.8f, 0.7f, 1.0f),
UIPoint(0.25f, 0.40f),
UIPoint(0.75f, 0.90f));
XCUIRHICommandCompiler::CompileConfig config = {};
config.surfaceClipRect = UIRect(0.0f, 0.0f, 64.0f, 64.0f);
XCUIRHICommandCompiler::CompiledDrawData compiled = {};
compiler.Compile(drawData, config, compiled);
ASSERT_EQ(compiled.batches.size(), 1u);
ASSERT_EQ(compiled.texturedVertices.size(), 6u);
EXPECT_FLOAT_EQ(compiled.texturedVertices[0].uv[0], 0.25f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[0].uv[1], 0.40f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[1].uv[0], 0.75f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[1].uv[1], 0.40f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[2].uv[0], 0.75f);
EXPECT_FLOAT_EQ(compiled.texturedVertices[2].uv[1], 0.90f);
EXPECT_EQ(compiled.stats.imageCommandCount, 1u);
EXPECT_EQ(compiled.stats.compiledCommandCount, 1u);
}
} // namespace

View File

@@ -1,202 +0,0 @@
#include <gtest/gtest.h>
#include "XCUIBackend/XCUIRHICommandSupport.h"
#include <string>
namespace {
using XCEngine::Editor::XCUIBackend::AccumulateXCUIRHICommandSupport;
using XCEngine::Editor::XCUIBackend::AnalyzeXCUIRHICommandSupport;
using XCEngine::Editor::XCUIBackend::BuildXCUIRHICommandSupportDiagnostic;
using XCEngine::Editor::XCUIBackend::ClassifyXCUIRHICommandSupport;
using XCEngine::Editor::XCUIBackend::SummarizeXCUIRHICommandSupport;
using XCEngine::Editor::XCUIBackend::XCUIRHICommandCategory;
using XCEngine::Editor::XCUIBackend::XCUIRHICommandDiagnosticOptions;
using XCEngine::Editor::XCUIBackend::XCUIRHICommandSupportReason;
using XCEngine::Editor::XCUIBackend::XCUIRHICommandSupportStats;
using XCEngine::UI::UIColor;
using XCEngine::UI::UIDrawCommand;
using XCEngine::UI::UIDrawCommandType;
using XCEngine::UI::UIDrawData;
using XCEngine::UI::UIDrawList;
using XCEngine::UI::UIPoint;
using XCEngine::UI::UIRect;
using XCEngine::UI::UITextureHandle;
using XCEngine::UI::UITextureHandleKind;
UITextureHandle MakeShaderResourceTexture() {
UITextureHandle texture = {};
texture.nativeHandle = 42u;
texture.width = 64u;
texture.height = 64u;
texture.kind = UITextureHandleKind::ShaderResourceView;
return texture;
}
UIDrawCommand MakeUnknownCommand() {
UIDrawCommand command = {};
command.type = static_cast<UIDrawCommandType>(255);
return command;
}
} // namespace
TEST(XCUIRHICommandSupportTest, ClassifySupportedCommandTypes) {
UIDrawList drawList("Supported");
drawList.AddFilledRect(UIRect(0.0f, 0.0f, 12.0f, 10.0f), UIColor(1.0f, 0.0f, 0.0f, 1.0f));
drawList.AddRectOutline(
UIRect(1.0f, 2.0f, 8.0f, 5.0f),
UIColor(0.0f, 1.0f, 0.0f, 1.0f),
2.0f,
1.0f);
drawList.AddText(UIPoint(4.0f, 5.0f), "label", UIColor(1.0f, 1.0f, 1.0f, 1.0f), 14.0f);
drawList.AddImage(
UIRect(2.0f, 3.0f, 18.0f, 14.0f),
MakeShaderResourceTexture(),
UIColor(1.0f, 1.0f, 1.0f, 1.0f));
drawList.PushClipRect(UIRect(0.0f, 0.0f, 24.0f, 20.0f));
drawList.PopClipRect();
const auto& commands = drawList.GetCommands();
EXPECT_EQ(
ClassifyXCUIRHICommandSupport(commands[0]).category,
XCUIRHICommandCategory::FilledRect);
EXPECT_EQ(
ClassifyXCUIRHICommandSupport(commands[1]).category,
XCUIRHICommandCategory::RectOutline);
EXPECT_EQ(
ClassifyXCUIRHICommandSupport(commands[2]).category,
XCUIRHICommandCategory::Text);
EXPECT_EQ(
ClassifyXCUIRHICommandSupport(commands[3]).category,
XCUIRHICommandCategory::Image);
EXPECT_EQ(
ClassifyXCUIRHICommandSupport(commands[4]).category,
XCUIRHICommandCategory::PushClipRect);
EXPECT_EQ(
ClassifyXCUIRHICommandSupport(commands[5]).category,
XCUIRHICommandCategory::PopClipRect);
for (const UIDrawCommand& command : commands) {
EXPECT_TRUE(ClassifyXCUIRHICommandSupport(command).IsSupported());
}
}
TEST(XCUIRHICommandSupportTest, InvalidImageCommandIsClassifiedAsUnsupportedTexture) {
UIDrawCommand command = {};
command.type = UIDrawCommandType::Image;
command.rect = UIRect(0.0f, 0.0f, 12.0f, 12.0f);
command.texture.nativeHandle = 7u;
command.texture.width = 32u;
command.texture.height = 32u;
command.texture.kind = UITextureHandleKind::ImGuiDescriptor;
const auto classification = ClassifyXCUIRHICommandSupport(command);
EXPECT_EQ(classification.category, XCUIRHICommandCategory::Image);
EXPECT_EQ(
classification.supportReason,
XCUIRHICommandSupportReason::UnsupportedImageTexture);
EXPECT_FALSE(classification.IsSupported());
}
TEST(XCUIRHICommandSupportTest, UnknownCommandTypeIsClassifiedSeparately) {
const auto classification = ClassifyXCUIRHICommandSupport(MakeUnknownCommand());
EXPECT_EQ(classification.category, XCUIRHICommandCategory::Unknown);
EXPECT_EQ(
classification.supportReason,
XCUIRHICommandSupportReason::UnsupportedUnknownCommand);
EXPECT_FALSE(classification.IsSupported());
}
TEST(XCUIRHICommandSupportTest, AnalyzeDrawDataAggregatesSupportedAndUnsupportedCounts) {
UIDrawData drawData = {};
UIDrawList& firstDrawList = drawData.EmplaceDrawList("First");
firstDrawList.AddFilledRect(
UIRect(0.0f, 0.0f, 8.0f, 8.0f),
UIColor(1.0f, 0.0f, 0.0f, 1.0f));
firstDrawList.AddImage(
UIRect(2.0f, 2.0f, 12.0f, 10.0f),
MakeShaderResourceTexture(),
UIColor(1.0f, 1.0f, 1.0f, 1.0f));
UIDrawList& secondDrawList = drawData.EmplaceDrawList("Second");
secondDrawList.PushClipRect(UIRect(0.0f, 0.0f, 100.0f, 60.0f));
secondDrawList.AddText(
UIPoint(5.0f, 6.0f),
"status",
UIColor(1.0f, 1.0f, 1.0f, 1.0f),
12.0f);
UITextureHandle invalidTexture = {};
invalidTexture.nativeHandle = 9u;
invalidTexture.width = 32u;
invalidTexture.height = 32u;
invalidTexture.kind = UITextureHandleKind::ImGuiDescriptor;
secondDrawList.AddImage(
UIRect(1.0f, 1.0f, 6.0f, 6.0f),
invalidTexture,
UIColor(1.0f, 1.0f, 1.0f, 1.0f));
const auto stats = AnalyzeXCUIRHICommandSupport(drawData);
EXPECT_EQ(stats.drawListCount, 2u);
EXPECT_EQ(stats.commandCount, 5u);
EXPECT_EQ(stats.filledRectCommandCount, 1u);
EXPECT_EQ(stats.textCommandCount, 1u);
EXPECT_EQ(stats.imageCommandCount, 2u);
EXPECT_EQ(stats.clipPushCommandCount, 1u);
EXPECT_EQ(stats.clipPopCommandCount, 0u);
EXPECT_EQ(stats.supportedCommandCount, 4u);
EXPECT_EQ(stats.unsupportedCommandCount, 1u);
EXPECT_EQ(stats.unsupportedImageCommandCount, 1u);
EXPECT_EQ(stats.unsupportedUnknownCommandCount, 0u);
EXPECT_FALSE(stats.SupportsAllCommands());
}
TEST(XCUIRHICommandSupportTest, DiagnosticIncludesUnsupportedReasonsAndCanBeCustomized) {
XCUIRHICommandSupportStats stats = {};
AccumulateXCUIRHICommandSupport(MakeUnknownCommand(), stats);
UIDrawCommand invalidImage = {};
invalidImage.type = UIDrawCommandType::Image;
invalidImage.texture.nativeHandle = 11u;
invalidImage.texture.width = 16u;
invalidImage.texture.height = 16u;
invalidImage.texture.kind = UITextureHandleKind::ImGuiDescriptor;
AccumulateXCUIRHICommandSupport(invalidImage, stats);
const std::string defaultDiagnostic = BuildXCUIRHICommandSupportDiagnostic(stats);
EXPECT_NE(defaultDiagnostic.find("2 command(s) will be skipped by native overlay: "), std::string::npos);
EXPECT_NE(defaultDiagnostic.find("1 image command(s) missing valid ShaderResourceView textures"), std::string::npos);
EXPECT_NE(defaultDiagnostic.find("1 unknown command type(s)"), std::string::npos);
XCUIRHICommandDiagnosticOptions options = {};
options.noCommandsMessage = "No overlay commands.";
options.allSupportedMessage = "Everything supported.";
options.unsupportedPrefix = "command(s) rejected:";
const std::string customDiagnostic = BuildXCUIRHICommandSupportDiagnostic(stats, options);
EXPECT_NE(customDiagnostic.find("2 command(s) rejected:"), std::string::npos);
}
TEST(XCUIRHICommandSupportTest, SummaryReturnsNoCommandAndAllSupportedMessages) {
const auto emptySummary = SummarizeXCUIRHICommandSupport(UIDrawData());
EXPECT_EQ(emptySummary.stats.commandCount, 0u);
EXPECT_EQ(emptySummary.diagnostic, "Overlay runtime produced no commands.");
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("Supported");
drawList.AddFilledRect(
UIRect(0.0f, 0.0f, 4.0f, 4.0f),
UIColor(0.2f, 0.4f, 0.8f, 1.0f));
const auto supportedSummary = SummarizeXCUIRHICommandSupport(drawData);
EXPECT_EQ(supportedSummary.stats.supportedCommandCount, 1u);
EXPECT_TRUE(supportedSummary.stats.SupportsAllCommands());
EXPECT_EQ(supportedSummary.diagnostic, "All commands preflight for native overlay.");
}

View File

@@ -1,710 +0,0 @@
#include <gtest/gtest.h>
#include "XCUIBackend/XCUIRHIRenderBackend.h"
#include <XCEngine/Rendering/RenderContext.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/RHI/RHIBuffer.h>
#include <XCEngine/RHI/RHICapabilities.h>
#include <XCEngine/RHI/RHICommandList.h>
#include <XCEngine/RHI/RHICommandQueue.h>
#include <XCEngine/RHI/RHIDevice.h>
#include <XCEngine/RHI/RHIDescriptorPool.h>
#include <XCEngine/RHI/RHIDescriptorSet.h>
#include <XCEngine/RHI/RHIPipelineLayout.h>
#include <XCEngine/RHI/RHIPipelineState.h>
#include <XCEngine/RHI/RHIResourceView.h>
#include <XCEngine/RHI/RHISampler.h>
#include <XCEngine/UI/DrawData.h>
#include <array>
#include <cstdint>
#include <cstring>
#include <type_traits>
#include <vector>
namespace {
using XCEngine::Editor::XCUIBackend::IXCUITextAtlasProvider;
using XCEngine::Editor::XCUIBackend::XCUIRHIRenderBackend;
using XCEngine::Rendering::RenderContext;
using XCEngine::Rendering::RenderSurface;
using XCEngine::RHI::BlendDesc;
using XCEngine::RHI::BufferDesc;
using XCEngine::RHI::BufferType;
using XCEngine::RHI::CommandQueueType;
using XCEngine::RHI::ComparisonFunc;
using XCEngine::RHI::DepthStencilStateDesc;
using XCEngine::RHI::DescriptorHeapType;
using XCEngine::RHI::DescriptorPoolDesc;
using XCEngine::RHI::DescriptorSetLayoutBinding;
using XCEngine::RHI::DescriptorSetLayoutDesc;
using XCEngine::RHI::Format;
using XCEngine::RHI::GraphicsPipelineDesc;
using XCEngine::RHI::InputLayoutDesc;
using XCEngine::RHI::PipelineStateHash;
using XCEngine::RHI::PipelineType;
using XCEngine::RHI::PrimitiveTopology;
using XCEngine::RHI::RasterizerDesc;
using XCEngine::RHI::Rect;
using XCEngine::RHI::ResourceStates;
using XCEngine::RHI::ResourceViewDesc;
using XCEngine::RHI::ResourceViewDimension;
using XCEngine::RHI::ResourceViewType;
using XCEngine::RHI::RHICommandList;
using XCEngine::RHI::RHICommandQueue;
using XCEngine::RHI::RHIDescriptorPool;
using XCEngine::RHI::RHIDescriptorSet;
using XCEngine::RHI::RHIDevice;
using XCEngine::RHI::RHIPipelineLayout;
using XCEngine::RHI::RHIPipelineState;
using XCEngine::RHI::RHIResourceView;
using XCEngine::RHI::RHISampler;
using XCEngine::RHI::RHIBuffer;
using XCEngine::RHI::RHIShader;
using XCEngine::RHI::RHIType;
using XCEngine::RHI::SamplerDesc;
using XCEngine::RHI::Viewport;
using XCEngine::UI::UIColor;
using XCEngine::UI::UIDrawData;
using XCEngine::UI::UIDrawList;
using XCEngine::UI::UIRect;
using XCEngine::UI::UITextureHandle;
using XCEngine::UI::UITextureHandleKind;
class FakeResourceViewBase {
public:
explicit FakeResourceViewBase(
ResourceViewType viewType,
ResourceViewDimension dimension = ResourceViewDimension::Texture2D,
Format format = Format::R8G8B8A8_UNorm)
: m_viewType(viewType)
, m_dimension(dimension)
, m_format(format) {
}
void ShutdownBase() {
m_valid = false;
}
void* GetNativeHandleBase() {
return this;
}
bool IsValidBase() const {
return m_valid;
}
ResourceViewType GetViewTypeBase() const {
return m_viewType;
}
ResourceViewDimension GetDimensionBase() const {
return m_dimension;
}
Format GetFormatBase() const {
return m_format;
}
void SetValid(bool valid) {
m_valid = valid;
}
private:
ResourceViewType m_viewType = ResourceViewType::ShaderResource;
ResourceViewDimension m_dimension = ResourceViewDimension::Texture2D;
Format m_format = Format::R8G8B8A8_UNorm;
bool m_valid = true;
};
class FakeShaderResourceView final : public XCEngine::RHI::RHIShaderResourceView {
public:
explicit FakeShaderResourceView(bool valid = true)
: m_base(ResourceViewType::ShaderResource) {
m_base.SetValid(valid);
}
void Shutdown() override { m_base.ShutdownBase(); }
void* GetNativeHandle() override { return m_base.GetNativeHandleBase(); }
bool IsValid() const override { return m_base.IsValidBase(); }
ResourceViewType GetViewType() const override { return m_base.GetViewTypeBase(); }
ResourceViewDimension GetDimension() const override { return m_base.GetDimensionBase(); }
Format GetFormat() const override { return m_base.GetFormatBase(); }
private:
FakeResourceViewBase m_base;
};
class FakeRenderTargetView final : public XCEngine::RHI::RHIRenderTargetView {
public:
FakeRenderTargetView()
: m_base(ResourceViewType::RenderTarget) {
}
void Shutdown() override { m_base.ShutdownBase(); }
void* GetNativeHandle() override { return m_base.GetNativeHandleBase(); }
bool IsValid() const override { return m_base.IsValidBase(); }
ResourceViewType GetViewType() const override { return m_base.GetViewTypeBase(); }
ResourceViewDimension GetDimension() const override { return m_base.GetDimensionBase(); }
Format GetFormat() const override { return m_base.GetFormatBase(); }
private:
FakeResourceViewBase m_base;
};
class FakeVertexBufferView final : public XCEngine::RHI::RHIVertexBufferView {
public:
explicit FakeVertexBufferView(std::uint32_t size, std::uint32_t stride)
: m_size(size)
, m_stride(stride)
, m_base(ResourceViewType::VertexBuffer, ResourceViewDimension::Buffer, Format::Unknown) {
}
void Shutdown() override { m_base.ShutdownBase(); }
void* GetNativeHandle() override { return m_base.GetNativeHandleBase(); }
bool IsValid() const override { return m_base.IsValidBase(); }
ResourceViewType GetViewType() const override { return m_base.GetViewTypeBase(); }
ResourceViewDimension GetDimension() const override { return m_base.GetDimensionBase(); }
Format GetFormat() const override { return m_base.GetFormatBase(); }
std::uint64_t GetBufferAddress() const override { return 0u; }
std::uint32_t GetSize() const override { return m_size; }
std::uint32_t GetStride() const override { return m_stride; }
private:
std::uint32_t m_size = 0;
std::uint32_t m_stride = 0;
FakeResourceViewBase m_base;
};
class FakeBuffer final : public RHIBuffer {
public:
explicit FakeBuffer(const BufferDesc& desc)
: m_size(desc.size)
, m_stride(desc.stride)
, m_type(static_cast<BufferType>(desc.bufferType))
, m_storage(static_cast<std::size_t>(desc.size), 0u) {
}
void* Map() override { return m_storage.data(); }
void Unmap() override {}
void SetData(const void* data, size_t size, size_t offset = 0) override {
if (data == nullptr || offset + size > m_storage.size()) {
return;
}
std::memcpy(m_storage.data() + offset, data, size);
}
std::uint64_t GetSize() const override { return m_size; }
BufferType GetBufferType() const override { return m_type; }
void SetBufferType(BufferType type) override { m_type = type; }
std::uint32_t GetStride() const override { return m_stride; }
void SetStride(std::uint32_t stride) override { m_stride = stride; }
void* GetNativeHandle() override { return this; }
ResourceStates GetState() const override { return m_state; }
void SetState(ResourceStates state) override { m_state = state; }
const std::string& GetName() const override { return m_name; }
void SetName(const std::string& name) override { m_name = name; }
void Shutdown() override { m_storage.clear(); }
private:
std::uint64_t m_size = 0;
std::uint32_t m_stride = 0;
BufferType m_type = BufferType::Vertex;
ResourceStates m_state = ResourceStates::Common;
std::string m_name = {};
std::vector<std::uint8_t> m_storage = {};
};
class FakeDescriptorSet final : public RHIDescriptorSet {
public:
explicit FakeDescriptorSet(const DescriptorSetLayoutDesc& layout) {
if (layout.bindings != nullptr && layout.bindingCount > 0u) {
m_bindings.assign(layout.bindings, layout.bindings + layout.bindingCount);
m_views.resize(layout.bindingCount, nullptr);
m_samplers.resize(layout.bindingCount, nullptr);
}
}
void Shutdown() override {
m_views.clear();
m_samplers.clear();
m_constantBuffer.clear();
m_constantDirty = false;
}
void Bind() override {}
void Unbind() override {}
void Update(std::uint32_t offset, RHIResourceView* view) override {
if (offset >= m_views.size()) {
m_views.resize(offset + 1u, nullptr);
}
m_views[offset] = view;
}
void UpdateSampler(std::uint32_t offset, RHISampler* sampler) override {
if (offset >= m_samplers.size()) {
m_samplers.resize(offset + 1u, nullptr);
}
m_samplers[offset] = sampler;
}
void WriteConstant(std::uint32_t, const void* data, size_t size, size_t offset = 0) override {
if (data == nullptr) {
return;
}
if (m_constantBuffer.size() < offset + size) {
m_constantBuffer.resize(offset + size, 0u);
}
std::memcpy(m_constantBuffer.data() + offset, data, size);
m_constantDirty = true;
}
std::uint32_t GetBindingCount() const override {
return static_cast<std::uint32_t>(m_bindings.size());
}
const DescriptorSetLayoutBinding* GetBindings() const override {
return m_bindings.empty() ? nullptr : m_bindings.data();
}
void* GetConstantBufferData() override {
return m_constantBuffer.empty() ? nullptr : m_constantBuffer.data();
}
size_t GetConstantBufferSize() const override {
return m_constantBuffer.size();
}
bool IsConstantDirty() const override {
return m_constantDirty;
}
void MarkConstantClean() override {
m_constantDirty = false;
}
private:
std::vector<DescriptorSetLayoutBinding> m_bindings = {};
std::vector<RHIResourceView*> m_views = {};
std::vector<RHISampler*> m_samplers = {};
std::vector<std::uint8_t> m_constantBuffer = {};
bool m_constantDirty = false;
};
class FakeDescriptorPool final : public RHIDescriptorPool {
public:
explicit FakeDescriptorPool(const DescriptorPoolDesc& desc)
: m_desc(desc) {
}
bool Initialize(const DescriptorPoolDesc& desc) override {
m_desc = desc;
return true;
}
void Shutdown() override {}
void* GetNativeHandle() override { return this; }
std::uint32_t GetDescriptorCount() const override { return m_desc.descriptorCount; }
DescriptorHeapType GetType() const override { return m_desc.type; }
RHIDescriptorSet* AllocateSet(const DescriptorSetLayoutDesc& layout) override {
++m_allocationCount;
return new FakeDescriptorSet(layout);
}
void FreeSet(RHIDescriptorSet* set) override {
delete set;
}
std::uint32_t GetAllocationCount() const {
return m_allocationCount;
}
private:
DescriptorPoolDesc m_desc = {};
std::uint32_t m_allocationCount = 0;
};
class FakePipelineLayout final : public RHIPipelineLayout {
public:
bool Initialize(const XCEngine::RHI::RHIPipelineLayoutDesc& desc) override {
m_desc = desc;
return true;
}
void Shutdown() override {}
void* GetNativeHandle() override { return this; }
private:
XCEngine::RHI::RHIPipelineLayoutDesc m_desc = {};
};
class FakePipelineState final : public RHIPipelineState {
public:
explicit FakePipelineState(const GraphicsPipelineDesc& desc)
: m_inputLayout(desc.inputLayout)
, m_rasterizer(desc.rasterizerState)
, m_blend(desc.blendState)
, m_depthStencil(desc.depthStencilState) {
}
void SetInputLayout(const InputLayoutDesc& layout) override { m_inputLayout = layout; }
void SetRasterizerState(const RasterizerDesc& state) override { m_rasterizer = state; }
void SetBlendState(const BlendDesc& state) override { m_blend = state; }
void SetDepthStencilState(const DepthStencilStateDesc& state) override { m_depthStencil = state; }
void SetTopology(std::uint32_t topologyType) override { m_topologyType = topologyType; }
void SetRenderTargetFormats(std::uint32_t, const std::uint32_t*, std::uint32_t) override {}
void SetSampleCount(std::uint32_t count) override { m_sampleCount = count; }
void SetComputeShader(RHIShader* shader) override { m_computeShader = shader; }
const RasterizerDesc& GetRasterizerState() const override { return m_rasterizer; }
const BlendDesc& GetBlendState() const override { return m_blend; }
const DepthStencilStateDesc& GetDepthStencilState() const override { return m_depthStencil; }
const InputLayoutDesc& GetInputLayout() const override { return m_inputLayout; }
PipelineStateHash GetHash() const override { return {}; }
RHIShader* GetComputeShader() const override { return m_computeShader; }
bool HasComputeShader() const override { return m_computeShader != nullptr; }
bool IsValid() const override { return true; }
void EnsureValid() override {}
void Shutdown() override {}
void Bind() override {}
void Unbind() override {}
void* GetNativeHandle() override { return this; }
PipelineType GetType() const override { return PipelineType::Graphics; }
private:
InputLayoutDesc m_inputLayout = {};
RasterizerDesc m_rasterizer = {};
BlendDesc m_blend = {};
DepthStencilStateDesc m_depthStencil = {};
std::uint32_t m_topologyType = 0u;
std::uint32_t m_sampleCount = 1u;
RHIShader* m_computeShader = nullptr;
};
class FakeSampler final : public RHISampler {
public:
void Shutdown() override {}
void Bind(unsigned int) override {}
void Unbind(unsigned int) override {}
void* GetNativeHandle() override { return this; }
unsigned int GetID() override { return 1u; }
};
class FakeCommandQueue final : public RHICommandQueue {
public:
void Shutdown() override {}
void ExecuteCommandLists(std::uint32_t, void**) override {}
void Signal(XCEngine::RHI::RHIFence*, std::uint64_t) override {}
void Wait(XCEngine::RHI::RHIFence*, std::uint64_t) override {}
std::uint64_t GetCompletedValue() override { return 0u; }
void WaitForIdle() override {}
CommandQueueType GetType() const override { return CommandQueueType::Direct; }
std::uint64_t GetTimestampFrequency() const override { return 0u; }
void* GetNativeHandle() override { return this; }
void WaitForPreviousFrame() override {}
std::uint64_t GetCurrentFrame() const override { return 0u; }
};
class FakeCommandList final : public RHICommandList {
public:
void Shutdown() override {}
void Reset() override {}
void Close() override {}
void TransitionBarrier(RHIResourceView*, ResourceStates, ResourceStates) override {}
void BeginRenderPass(XCEngine::RHI::RHIRenderPass*, XCEngine::RHI::RHIFramebuffer*, const Rect&, std::uint32_t, const XCEngine::RHI::ClearValue*) override {}
void EndRenderPass() override {}
void SetShader(RHIShader*) override {}
void SetPipelineState(RHIPipelineState* pso) override {
pipelineStateHistory.push_back(pso);
}
void SetGraphicsDescriptorSets(
std::uint32_t,
std::uint32_t count,
RHIDescriptorSet** descriptorSets,
RHIPipelineLayout*) override {
if (count >= 2u && descriptorSets != nullptr) {
textureDescriptorSetHistory.push_back(descriptorSets[1]);
}
}
void SetComputeDescriptorSets(std::uint32_t, std::uint32_t, RHIDescriptorSet**, RHIPipelineLayout*) override {}
void SetPrimitiveTopology(PrimitiveTopology topology) override { primitiveTopologyHistory.push_back(topology); }
void SetViewport(const Viewport& viewport) override { viewportHistory.push_back(viewport); }
void SetViewports(std::uint32_t count, const Viewport* viewports) override {
for (std::uint32_t index = 0; index < count; ++index) {
viewportHistory.push_back(viewports[index]);
}
}
void SetScissorRect(const Rect& rect) override { scissorHistory.push_back(rect); }
void SetScissorRects(std::uint32_t count, const Rect* rects) override {
for (std::uint32_t index = 0; index < count; ++index) {
scissorHistory.push_back(rects[index]);
}
}
void SetRenderTargets(std::uint32_t count, RHIResourceView**, RHIResourceView* = nullptr) override {
renderTargetBindHistory.push_back(count);
}
void SetStencilRef(std::uint8_t) override {}
void SetBlendFactor(const float[4]) override {}
void SetVertexBuffers(std::uint32_t, std::uint32_t, RHIResourceView**, const std::uint64_t*, const std::uint32_t*) override {}
void SetIndexBuffer(RHIResourceView*, std::uint64_t) override {}
void Draw(std::uint32_t vertexCount, std::uint32_t, std::uint32_t startVertex, std::uint32_t) override {
drawVertexCounts.push_back(vertexCount);
drawStartVertices.push_back(startVertex);
}
void DrawIndexed(std::uint32_t, std::uint32_t, std::uint32_t, std::int32_t, std::uint32_t) override {}
void Clear(float, float, float, float, std::uint32_t) override {}
void ClearRenderTarget(RHIResourceView*, const float[4], std::uint32_t = 0, const Rect* = nullptr) override {}
void ClearDepthStencil(RHIResourceView*, float, std::uint8_t, std::uint32_t = 0, const Rect* = nullptr) override {}
void CopyResource(RHIResourceView*, RHIResourceView*) override {}
void Dispatch(std::uint32_t, std::uint32_t, std::uint32_t) override {}
void* GetNativeHandle() override { return this; }
std::vector<RHIPipelineState*> pipelineStateHistory = {};
std::vector<PrimitiveTopology> primitiveTopologyHistory = {};
std::vector<Viewport> viewportHistory = {};
std::vector<Rect> scissorHistory = {};
std::vector<std::uint32_t> renderTargetBindHistory = {};
std::vector<RHIDescriptorSet*> textureDescriptorSetHistory = {};
std::vector<std::uint32_t> drawVertexCounts = {};
std::vector<std::uint32_t> drawStartVertices = {};
};
class FakeDevice final : public RHIDevice {
public:
bool Initialize(const XCEngine::RHI::RHIDeviceDesc&) override { return true; }
void Shutdown() override {}
RHIBuffer* CreateBuffer(const BufferDesc& desc) override { return new FakeBuffer(desc); }
XCEngine::RHI::RHITexture* CreateTexture(const XCEngine::RHI::TextureDesc&) override { return nullptr; }
XCEngine::RHI::RHITexture* CreateTexture(const XCEngine::RHI::TextureDesc&, const void*, size_t, std::uint32_t = 0) override { return nullptr; }
XCEngine::RHI::RHISwapChain* CreateSwapChain(const XCEngine::RHI::SwapChainDesc&, RHICommandQueue*) override { return nullptr; }
RHICommandList* CreateCommandList(const XCEngine::RHI::CommandListDesc&) override { return nullptr; }
RHICommandQueue* CreateCommandQueue(const XCEngine::RHI::CommandQueueDesc&) override { return nullptr; }
RHIShader* CreateShader(const XCEngine::RHI::ShaderCompileDesc&) override { return nullptr; }
RHIPipelineState* CreatePipelineState(const GraphicsPipelineDesc& desc) override { return new FakePipelineState(desc); }
RHIPipelineLayout* CreatePipelineLayout(const XCEngine::RHI::RHIPipelineLayoutDesc&) override { return new FakePipelineLayout(); }
XCEngine::RHI::RHIFence* CreateFence(const XCEngine::RHI::FenceDesc&) override { return nullptr; }
RHISampler* CreateSampler(const SamplerDesc&) override { return new FakeSampler(); }
XCEngine::RHI::RHIRenderPass* CreateRenderPass(std::uint32_t, const XCEngine::RHI::AttachmentDesc*, const XCEngine::RHI::AttachmentDesc*) override { return nullptr; }
XCEngine::RHI::RHIFramebuffer* CreateFramebuffer(XCEngine::RHI::RHIRenderPass*, std::uint32_t, std::uint32_t, std::uint32_t, RHIResourceView**, RHIResourceView*) override { return nullptr; }
RHIDescriptorPool* CreateDescriptorPool(const DescriptorPoolDesc& desc) override { return new FakeDescriptorPool(desc); }
RHIDescriptorSet* CreateDescriptorSet(RHIDescriptorPool*, const DescriptorSetLayoutDesc&) override { return nullptr; }
RHIResourceView* CreateVertexBufferView(RHIBuffer* buffer, const ResourceViewDesc& desc) override {
return new FakeVertexBufferView(static_cast<std::uint32_t>(buffer != nullptr ? buffer->GetSize() : 0u), desc.structureByteStride);
}
RHIResourceView* CreateIndexBufferView(RHIBuffer*, const ResourceViewDesc&) override { return nullptr; }
RHIResourceView* CreateRenderTargetView(XCEngine::RHI::RHITexture*, const ResourceViewDesc&) override { return nullptr; }
RHIResourceView* CreateDepthStencilView(XCEngine::RHI::RHITexture*, const ResourceViewDesc&) override { return nullptr; }
RHIResourceView* CreateShaderResourceView(XCEngine::RHI::RHITexture*, const ResourceViewDesc&) override { return nullptr; }
RHIResourceView* CreateUnorderedAccessView(XCEngine::RHI::RHITexture*, const ResourceViewDesc&) override { return nullptr; }
const XCEngine::RHI::RHICapabilities& GetCapabilities() const override { return m_capabilities; }
const XCEngine::RHI::RHIDeviceInfo& GetDeviceInfo() const override { return m_deviceInfo; }
void* GetNativeDevice() override { return this; }
private:
XCEngine::RHI::RHICapabilities m_capabilities = {};
XCEngine::RHI::RHIDeviceInfo m_deviceInfo = {};
};
UITextureHandle MakeExternalTextureHandle(FakeShaderResourceView& shaderView, std::uint32_t width = 32u, std::uint32_t height = 32u) {
UITextureHandle texture = {};
texture.nativeHandle = reinterpret_cast<std::uintptr_t>(&shaderView);
texture.width = width;
texture.height = height;
texture.kind = UITextureHandleKind::ShaderResourceView;
return texture;
}
} // namespace
TEST(XCUIRHIRenderBackendTest, ExposesNativeOverlayEntryAndAtlasInjection) {
static_assert(std::is_same_v<
decltype(std::declval<XCUIRHIRenderBackend&>().Render(
std::declval<const RenderContext&>(),
std::declval<const RenderSurface&>(),
std::declval<const UIDrawData&>())),
bool>);
static_assert(std::is_same_v<
decltype(std::declval<XCUIRHIRenderBackend&>().SetTextAtlasProvider(
std::declval<const IXCUITextAtlasProvider*>())) ,
void>);
static_assert(std::is_same_v<
decltype(std::declval<const XCUIRHIRenderBackend&>().GetLastOverlayStats().drawListCount),
std::size_t>);
SUCCEED();
}
TEST(XCUIRHIRenderBackendTest, RenderRejectsInvalidContextAndLeavesStatsClear) {
XCUIRHIRenderBackend backend = {};
RenderSurface surface(128, 72);
UIDrawData drawData = {};
EXPECT_FALSE(backend.Render(RenderContext(), surface, drawData));
EXPECT_EQ(backend.GetLastOverlayStats().commandCount, 0u);
EXPECT_EQ(backend.GetLastOverlayStats().renderedCommandCount, 0u);
}
TEST(XCUIRHIRenderBackendTest, SetTextAtlasProviderStoresPointerAndResetStatsClearsCounters) {
XCUIRHIRenderBackend backend = {};
class StubAtlasProvider final : public IXCUITextAtlasProvider {
public:
bool GetAtlasTextureView(PixelFormat, AtlasTextureView& outView) const override {
outView = {};
return false;
}
std::size_t GetFontCount() const override { return 0u; }
FontHandle GetFont(std::size_t) const override { return {}; }
FontHandle GetDefaultFont() const override { return {}; }
bool GetFontInfo(FontHandle, FontInfo& outInfo) const override {
outInfo = {};
return false;
}
bool GetBakedFontInfo(FontHandle, float, BakedFontInfo& outInfo) const override {
outInfo = {};
return false;
}
bool FindGlyph(FontHandle, float, std::uint32_t, GlyphInfo& outInfo) const override {
outInfo = {};
return false;
}
} atlasProvider;
backend.SetTextAtlasProvider(&atlasProvider);
EXPECT_EQ(backend.GetTextAtlasProvider(), &atlasProvider);
backend.SetTextAtlasProvider(nullptr);
EXPECT_EQ(backend.GetTextAtlasProvider(), nullptr);
backend.ResetStats();
EXPECT_EQ(backend.GetLastStats().drawListCount, 0u);
EXPECT_EQ(backend.GetLastStats().commandCount, 0u);
EXPECT_EQ(backend.GetLastStats().batchCount, 0u);
EXPECT_EQ(backend.GetLastStats().renderedCommandCount, 0u);
EXPECT_EQ(backend.GetLastStats().skippedCommandCount, 0u);
EXPECT_EQ(backend.GetLastStats().textureResolveCount, 0u);
EXPECT_EQ(backend.GetLastStats().textureCacheHitCount, 0u);
}
TEST(XCUIRHIRenderBackendTest, RenderReusesExternalTextureBindingsAcrossTexturedBatches) {
FakeDevice device = {};
FakeCommandList commandList = {};
FakeCommandQueue commandQueue = {};
FakeRenderTargetView renderTarget = {};
FakeShaderResourceView externalTextureView(true);
RenderContext context = {};
context.device = &device;
context.commandList = &commandList;
context.commandQueue = &commandQueue;
context.backendType = RHIType::D3D12;
RenderSurface surface(64, 48);
surface.SetColorAttachment(&renderTarget);
UIDrawData drawData = {};
UIDrawList& firstDrawList = drawData.EmplaceDrawList("FirstTexturedBatch");
firstDrawList.PushClipRect(UIRect(4.0f, 6.0f, 12.0f, 10.0f));
firstDrawList.AddImage(
UIRect(0.0f, 0.0f, 18.0f, 18.0f),
MakeExternalTextureHandle(externalTextureView),
UIColor(1.0f, 0.8f, 0.6f, 1.0f));
firstDrawList.PopClipRect();
UIDrawList& secondDrawList = drawData.EmplaceDrawList("SecondTexturedBatch");
secondDrawList.PushClipRect(UIRect(20.0f, 8.0f, 10.0f, 12.0f));
secondDrawList.AddImage(
UIRect(18.0f, 4.0f, 16.0f, 16.0f),
MakeExternalTextureHandle(externalTextureView),
UIColor(0.7f, 0.9f, 1.0f, 1.0f));
secondDrawList.PopClipRect();
XCUIRHIRenderBackend backend = {};
ASSERT_TRUE(backend.Render(context, surface, drawData));
const XCUIRHIRenderBackend::OverlayStats& stats = backend.GetLastOverlayStats();
EXPECT_EQ(stats.drawListCount, 2u);
EXPECT_EQ(stats.commandCount, 6u);
EXPECT_EQ(stats.batchCount, 2u);
EXPECT_EQ(stats.colorBatchCount, 0u);
EXPECT_EQ(stats.texturedBatchCount, 2u);
EXPECT_EQ(stats.scissoredBatchCount, 2u);
EXPECT_EQ(stats.renderedCommandCount, 2u);
EXPECT_EQ(stats.skippedCommandCount, 0u);
EXPECT_EQ(stats.textureResolveCount, 2u);
EXPECT_EQ(stats.textureCacheHitCount, 1u);
EXPECT_EQ(stats.vertexCount, 12u);
EXPECT_EQ(stats.triangleCount, 4u);
ASSERT_EQ(commandList.renderTargetBindHistory.size(), 1u);
ASSERT_EQ(commandList.viewportHistory.size(), 1u);
ASSERT_EQ(commandList.drawVertexCounts.size(), 2u);
EXPECT_EQ(commandList.drawVertexCounts[0], 6u);
EXPECT_EQ(commandList.drawVertexCounts[1], 6u);
ASSERT_EQ(commandList.textureDescriptorSetHistory.size(), 2u);
EXPECT_EQ(commandList.textureDescriptorSetHistory[0], commandList.textureDescriptorSetHistory[1]);
ASSERT_EQ(commandList.scissorHistory.size(), 4u);
EXPECT_EQ(commandList.scissorHistory[0].left, 0);
EXPECT_EQ(commandList.scissorHistory[0].top, 0);
EXPECT_EQ(commandList.scissorHistory[0].right, 64);
EXPECT_EQ(commandList.scissorHistory[0].bottom, 48);
EXPECT_EQ(commandList.scissorHistory[1].left, 4);
EXPECT_EQ(commandList.scissorHistory[1].top, 6);
EXPECT_EQ(commandList.scissorHistory[1].right, 16);
EXPECT_EQ(commandList.scissorHistory[1].bottom, 16);
EXPECT_EQ(commandList.scissorHistory[2].left, 20);
EXPECT_EQ(commandList.scissorHistory[2].top, 8);
EXPECT_EQ(commandList.scissorHistory[2].right, 30);
EXPECT_EQ(commandList.scissorHistory[2].bottom, 20);
EXPECT_EQ(commandList.scissorHistory[3].left, 0);
EXPECT_EQ(commandList.scissorHistory[3].top, 0);
EXPECT_EQ(commandList.scissorHistory[3].right, 64);
EXPECT_EQ(commandList.scissorHistory[3].bottom, 48);
}
TEST(XCUIRHIRenderBackendTest, RenderSkipsTexturedBatchWhenExternalTextureViewIsInvalid) {
FakeDevice device = {};
FakeCommandList commandList = {};
FakeCommandQueue commandQueue = {};
FakeRenderTargetView renderTarget = {};
FakeShaderResourceView invalidTextureView(false);
RenderContext context = {};
context.device = &device;
context.commandList = &commandList;
context.commandQueue = &commandQueue;
context.backendType = RHIType::D3D12;
RenderSurface surface(32, 32);
surface.SetColorAttachment(&renderTarget);
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("InvalidExternalTexture");
drawList.AddImage(
UIRect(2.0f, 4.0f, 12.0f, 8.0f),
MakeExternalTextureHandle(invalidTextureView),
UIColor(1.0f, 1.0f, 1.0f, 1.0f));
XCUIRHIRenderBackend backend = {};
ASSERT_TRUE(backend.Render(context, surface, drawData));
const XCUIRHIRenderBackend::OverlayStats& stats = backend.GetLastOverlayStats();
EXPECT_EQ(stats.commandCount, 1u);
EXPECT_EQ(stats.batchCount, 1u);
EXPECT_EQ(stats.texturedBatchCount, 1u);
EXPECT_EQ(stats.renderedCommandCount, 0u);
EXPECT_EQ(stats.skippedCommandCount, 1u);
EXPECT_EQ(stats.textureResolveCount, 1u);
EXPECT_EQ(stats.textureCacheHitCount, 0u);
EXPECT_EQ(commandList.drawVertexCounts.size(), 0u);
EXPECT_EQ(commandList.textureDescriptorSetHistory.size(), 0u);
}

View File

@@ -1,314 +0,0 @@
#include "XCUIBackend/XCUIShellChromeState.h"
#include <gtest/gtest.h>
namespace {
using XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds;
using XCEngine::Editor::XCUIBackend::XCUIShellCommandDescriptor;
using XCEngine::Editor::XCUIBackend::XCUIShellChromeState;
using XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewMode;
using XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewState;
using XCEngine::Editor::XCUIBackend::XCUIShellMenuItemKind;
using XCEngine::Editor::XCUIBackend::XCUIShellPanelId;
using XCEngine::Editor::XCUIBackend::XCUIShellViewToggleId;
TEST(XCUIShellChromeStateTest, DefaultsMatchCurrentShellChromeConfiguration) {
XCUIShellChromeState state = {};
const auto& viewToggles = state.GetViewToggles();
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.IsHostedPresenterPreviewActive(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::HostedPresenter);
EXPECT_EQ(
state.GetHostedPreviewState(XCUIShellPanelId::XCUILayoutLab),
XCUIShellHostedPreviewState::HostedPresenter);
EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUILayoutLab));
EXPECT_TRUE(state.IsHostedPresenterPreviewActive(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::HostedPresenter);
EXPECT_TRUE(state.ToggleHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab));
EXPECT_EQ(
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
XCUIShellHostedPreviewMode::NativeOffscreen);
EXPECT_TRUE(state.SetHostedPreviewMode(
XCUIShellPanelId::XCUILayoutLab,
XCUIShellHostedPreviewMode::HostedPresenter));
EXPECT_EQ(
state.GetHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab),
XCUIShellHostedPreviewMode::HostedPresenter);
EXPECT_FALSE(state.SetHostedPreviewMode(
XCUIShellPanelId::XCUILayoutLab,
XCUIShellHostedPreviewMode::HostedPresenter));
}
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.IsHostedPresenterPreviewActive(XCUIShellPanelId::XCUIDemo));
EXPECT_TRUE(state.SetHostedPreviewMode(
XCUIShellPanelId::XCUIDemo,
XCUIShellHostedPreviewMode::HostedPresenter));
EXPECT_EQ(
state.GetHostedPreviewMode(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewMode::HostedPresenter);
EXPECT_EQ(
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewState::Disabled);
EXPECT_TRUE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUIDemo, true));
EXPECT_EQ(
state.GetHostedPreviewState(XCUIShellPanelId::XCUIDemo),
XCUIShellHostedPreviewState::HostedPresenter);
EXPECT_FALSE(state.IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo));
EXPECT_TRUE(state.IsHostedPresenterPreviewActive(XCUIShellPanelId::XCUIDemo));
}
TEST(XCUIShellChromeStateTest, ViewToggleMutatorsOnlyFlipRequestedFlags) {
XCUIShellChromeState state = {};
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::HostedPresenter);
EXPECT_FALSE(state.InvokeCommand("new_editor.view.unknown"));
}
TEST(XCUIShellChromeStateTest, CommandDescriptorsMatchCurrentShellMenuLabelsShortcutsAndCheckedState) {
XCUIShellChromeState state = {};
XCUIShellCommandDescriptor descriptor = {};
ASSERT_TRUE(state.TryGetCommandDescriptor(XCUIShellChromeCommandIds::ToggleXCUIDemoPanel, descriptor));
EXPECT_EQ(descriptor.label, "XCUI Demo");
EXPECT_EQ(descriptor.shortcut, "Ctrl+1");
EXPECT_EQ(descriptor.commandId, XCUIShellChromeCommandIds::ToggleXCUIDemoPanel);
EXPECT_TRUE(descriptor.checkable);
EXPECT_TRUE(descriptor.checked);
EXPECT_TRUE(descriptor.enabled);
ASSERT_TRUE(state.TryGetCommandDescriptor(XCUIShellChromeCommandIds::ToggleHostedPreviewHud, descriptor));
EXPECT_EQ(descriptor.label, "Hosted Preview HUD");
EXPECT_EQ(descriptor.shortcut, "Ctrl+Shift+H");
EXPECT_TRUE(descriptor.checked);
ASSERT_TRUE(state.TryGetCommandDescriptor(XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview, descriptor));
EXPECT_EQ(descriptor.label, "Native Layout Lab Preview");
EXPECT_EQ(descriptor.shortcut, "Ctrl+Alt+2");
EXPECT_FALSE(descriptor.checked);
EXPECT_FALSE(state.TryGetCommandDescriptor("new_editor.view.unknown", descriptor));
EXPECT_TRUE(descriptor.label.empty());
EXPECT_TRUE(descriptor.shortcut.empty());
EXPECT_TRUE(descriptor.commandId.empty());
EXPECT_FALSE(descriptor.checked);
EXPECT_FALSE(descriptor.enabled);
}
TEST(XCUIShellChromeStateTest, ViewMenuDescriptorMatchesCurrentApplicationOrderingAndSeparators) {
XCUIShellChromeState state = {};
const auto menu = state.BuildViewMenuDescriptor();
ASSERT_EQ(menu.label, "View");
ASSERT_EQ(menu.items.size(), 9u);
ASSERT_EQ(menu.items[0].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[0].command.label, "XCUI Demo");
EXPECT_EQ(menu.items[0].command.shortcut, "Ctrl+1");
EXPECT_EQ(menu.items[0].command.commandId, XCUIShellChromeCommandIds::ToggleXCUIDemoPanel);
EXPECT_TRUE(menu.items[0].command.checked);
ASSERT_EQ(menu.items[1].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[1].command.label, "XCUI Layout Lab");
EXPECT_EQ(menu.items[1].command.shortcut, "Ctrl+2");
EXPECT_EQ(menu.items[1].command.commandId, XCUIShellChromeCommandIds::ToggleXCUILayoutLabPanel);
EXPECT_EQ(menu.items[2].kind, XCUIShellMenuItemKind::Separator);
ASSERT_EQ(menu.items[3].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[3].command.label, "Native Backdrop");
EXPECT_EQ(menu.items[3].command.shortcut, "Ctrl+Shift+B");
EXPECT_EQ(menu.items[3].command.commandId, XCUIShellChromeCommandIds::ToggleNativeBackdrop);
ASSERT_EQ(menu.items[4].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[4].command.label, "Pulse Accent");
EXPECT_EQ(menu.items[4].command.shortcut, "Ctrl+Shift+P");
EXPECT_EQ(menu.items[4].command.commandId, XCUIShellChromeCommandIds::TogglePulseAccent);
ASSERT_EQ(menu.items[5].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[5].command.label, "Native XCUI Overlay");
EXPECT_EQ(menu.items[5].command.shortcut, "Ctrl+Shift+O");
EXPECT_EQ(menu.items[5].command.commandId, XCUIShellChromeCommandIds::ToggleNativeXCUIOverlay);
ASSERT_EQ(menu.items[6].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[6].command.label, "Hosted Preview HUD");
EXPECT_EQ(menu.items[6].command.shortcut, "Ctrl+Shift+H");
EXPECT_EQ(menu.items[6].command.commandId, XCUIShellChromeCommandIds::ToggleHostedPreviewHud);
ASSERT_EQ(menu.items[7].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[7].command.label, "Native Demo Panel Preview");
EXPECT_EQ(menu.items[7].command.shortcut, "Ctrl+Alt+1");
EXPECT_EQ(menu.items[7].command.commandId, XCUIShellChromeCommandIds::ToggleNativeDemoPanelPreview);
ASSERT_EQ(menu.items[8].kind, XCUIShellMenuItemKind::Command);
EXPECT_EQ(menu.items[8].command.label, "Native Layout Lab Preview");
EXPECT_EQ(menu.items[8].command.shortcut, "Ctrl+Alt+2");
EXPECT_EQ(menu.items[8].command.commandId, XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview);
}
TEST(XCUIShellChromeStateTest, ViewMenuDescriptorCheckedStateTracksShellStateChanges) {
XCUIShellChromeState state = {};
EXPECT_TRUE(state.SetPanelVisible(XCUIShellPanelId::XCUIDemo, false));
EXPECT_TRUE(state.SetViewToggle(XCUIShellViewToggleId::HostedPreviewHud, false));
EXPECT_TRUE(state.SetHostedPreviewMode(
XCUIShellPanelId::XCUILayoutLab,
XCUIShellHostedPreviewMode::NativeOffscreen));
EXPECT_TRUE(state.SetHostedPreviewEnabled(XCUIShellPanelId::XCUIDemo, false));
const auto menu = state.BuildViewMenuDescriptor();
EXPECT_FALSE(menu.items[0].command.checked);
EXPECT_FALSE(menu.items[6].command.checked);
EXPECT_FALSE(menu.items[7].command.checked);
EXPECT_TRUE(menu.items[8].command.checked);
}
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::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::HostedPresenter);
EXPECT_EQ(
state.GetHostedPreviewState(invalidPanelId),
XCUIShellHostedPreviewState::Disabled);
EXPECT_FALSE(state.IsNativeHostedPreviewActive(invalidPanelId));
EXPECT_FALSE(state.IsHostedPresenterPreviewActive(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

View File

@@ -1,157 +0,0 @@
#include <gtest/gtest.h>
#include "XCUIBackend/XCUIStandaloneTextAtlasProvider.h"
#include <cstdint>
namespace {
using XCEngine::Editor::XCUIBackend::IXCUITextAtlasProvider;
using XCEngine::Editor::XCUIBackend::XCUIStandaloneTextAtlasProvider;
TEST(XCUIStandaloneTextAtlasProviderTest, BuildsDefaultEditorAtlasWithoutImGuiContext) {
XCUIStandaloneTextAtlasProvider provider = {};
IXCUITextAtlasProvider::AtlasTextureView atlasView = {};
IXCUITextAtlasProvider::AtlasTextureView alphaView = {};
ASSERT_TRUE(provider.IsReady());
ASSERT_TRUE(provider.GetAtlasTextureView(IXCUITextAtlasProvider::PixelFormat::RGBA32, atlasView));
ASSERT_TRUE(provider.GetAtlasTextureView(IXCUITextAtlasProvider::PixelFormat::Alpha8, alphaView));
EXPECT_TRUE(atlasView.IsValid());
EXPECT_TRUE(alphaView.IsValid());
EXPECT_EQ(atlasView.format, IXCUITextAtlasProvider::PixelFormat::RGBA32);
EXPECT_EQ(alphaView.format, IXCUITextAtlasProvider::PixelFormat::Alpha8);
EXPECT_GT(provider.GetFontCount(), 0u);
}
TEST(XCUIStandaloneTextAtlasProviderTest, ExposesDefaultFontMetricsAndGlyphs) {
XCUIStandaloneTextAtlasProvider provider = {};
const IXCUITextAtlasProvider::FontHandle defaultFont = provider.GetDefaultFont();
ASSERT_TRUE(defaultFont.IsValid());
IXCUITextAtlasProvider::FontInfo fontInfo = {};
ASSERT_TRUE(provider.GetFontInfo(defaultFont, fontInfo));
EXPECT_GT(fontInfo.nominalSize, 0.0f);
IXCUITextAtlasProvider::BakedFontInfo bakedFontInfo = {};
ASSERT_TRUE(provider.GetBakedFontInfo(defaultFont, 0.0f, bakedFontInfo));
EXPECT_GT(bakedFontInfo.lineHeight, 0.0f);
EXPECT_GT(bakedFontInfo.rasterizerDensity, 0.0f);
IXCUITextAtlasProvider::GlyphInfo glyphInfo = {};
ASSERT_TRUE(provider.FindGlyph(defaultFont, 0.0f, static_cast<std::uint32_t>('A'), glyphInfo));
EXPECT_EQ(glyphInfo.requestedCodepoint, static_cast<std::uint32_t>('A'));
EXPECT_GT(glyphInfo.advanceX, 0.0f);
EXPECT_GE(glyphInfo.u1, glyphInfo.u0);
EXPECT_GE(glyphInfo.v1, glyphInfo.v0);
}
TEST(XCUIStandaloneTextAtlasProviderTest, RebuildsAfterResetAndRejectsQueriesWhileUnready) {
XCUIStandaloneTextAtlasProvider provider = {};
const IXCUITextAtlasProvider::FontHandle defaultFont = provider.GetDefaultFont();
ASSERT_TRUE(defaultFont.IsValid());
provider.Reset();
IXCUITextAtlasProvider::AtlasTextureView atlasView = {};
IXCUITextAtlasProvider::FontInfo fontInfo = {};
IXCUITextAtlasProvider::BakedFontInfo bakedFontInfo = {};
IXCUITextAtlasProvider::GlyphInfo glyphInfo = {};
EXPECT_FALSE(provider.IsReady());
EXPECT_FALSE(provider.GetAtlasTextureView(IXCUITextAtlasProvider::PixelFormat::RGBA32, atlasView));
EXPECT_EQ(provider.GetFontCount(), 0u);
EXPECT_FALSE(provider.GetDefaultFont().IsValid());
EXPECT_FALSE(provider.GetFontInfo(defaultFont, fontInfo));
EXPECT_FALSE(provider.GetBakedFontInfo(defaultFont, 18.0f, bakedFontInfo));
EXPECT_FALSE(provider.FindGlyph(defaultFont, 18.0f, static_cast<std::uint32_t>('A'), glyphInfo));
ASSERT_TRUE(provider.RebuildDefaultEditorAtlas());
ASSERT_TRUE(provider.IsReady());
ASSERT_TRUE(provider.GetAtlasTextureView(IXCUITextAtlasProvider::PixelFormat::RGBA32, atlasView));
EXPECT_TRUE(atlasView.IsValid());
}
TEST(XCUIStandaloneTextAtlasProviderTest, ResolvesGlyphsForNonNominalRequestedFontSizes) {
XCUIStandaloneTextAtlasProvider provider = {};
const IXCUITextAtlasProvider::FontHandle defaultFont = provider.GetDefaultFont();
ASSERT_TRUE(defaultFont.IsValid());
IXCUITextAtlasProvider::BakedFontInfo bakedFontInfo = {};
ASSERT_TRUE(provider.GetBakedFontInfo(defaultFont, 13.0f, bakedFontInfo));
EXPECT_GT(bakedFontInfo.lineHeight, 0.0f);
EXPECT_GT(bakedFontInfo.rasterizerDensity, 0.0f);
IXCUITextAtlasProvider::GlyphInfo glyphInfo = {};
ASSERT_TRUE(provider.FindGlyph(defaultFont, 13.0f, static_cast<std::uint32_t>('A'), glyphInfo));
EXPECT_EQ(glyphInfo.requestedCodepoint, static_cast<std::uint32_t>('A'));
EXPECT_GT(glyphInfo.advanceX, 0.0f);
EXPECT_GE(glyphInfo.u1, glyphInfo.u0);
EXPECT_GE(glyphInfo.v1, glyphInfo.v0);
}
TEST(XCUIStandaloneTextAtlasProviderTest, LazilyAddsGlyphsOutsideThePrebakedAsciiRanges) {
XCUIStandaloneTextAtlasProvider provider = {};
const IXCUITextAtlasProvider::FontHandle defaultFont = provider.GetDefaultFont();
ASSERT_TRUE(defaultFont.IsValid());
IXCUITextAtlasProvider::AtlasTextureView beforeView = {};
ASSERT_TRUE(provider.GetAtlasTextureView(IXCUITextAtlasProvider::PixelFormat::RGBA32, beforeView));
constexpr std::uint32_t dynamicCodepoint = 0x4E2Du;
IXCUITextAtlasProvider::GlyphInfo glyphInfo = {};
ASSERT_TRUE(provider.FindGlyph(defaultFont, 16.0f, dynamicCodepoint, glyphInfo));
IXCUITextAtlasProvider::AtlasTextureView refreshedView = {};
ASSERT_TRUE(provider.GetAtlasTextureView(IXCUITextAtlasProvider::PixelFormat::RGBA32, refreshedView));
EXPECT_EQ(refreshedView.atlasStorageKey, beforeView.atlasStorageKey);
EXPECT_EQ(glyphInfo.requestedCodepoint, dynamicCodepoint);
if (refreshedView.pixelDataKey != beforeView.pixelDataKey) {
EXPECT_EQ(glyphInfo.resolvedCodepoint, dynamicCodepoint);
EXPECT_GT(glyphInfo.advanceX, 0.0f);
EXPECT_TRUE(glyphInfo.visible);
IXCUITextAtlasProvider::GlyphInfo secondLookup = {};
ASSERT_TRUE(provider.FindGlyph(defaultFont, 16.0f, dynamicCodepoint, secondLookup));
EXPECT_EQ(secondLookup.requestedCodepoint, dynamicCodepoint);
EXPECT_EQ(secondLookup.resolvedCodepoint, dynamicCodepoint);
IXCUITextAtlasProvider::AtlasTextureView secondView = {};
ASSERT_TRUE(provider.GetAtlasTextureView(IXCUITextAtlasProvider::PixelFormat::RGBA32, secondView));
EXPECT_EQ(secondView.pixelDataKey, refreshedView.pixelDataKey);
return;
}
if (glyphInfo.resolvedCodepoint != dynamicCodepoint) {
EXPECT_EQ(glyphInfo.resolvedCodepoint, static_cast<std::uint32_t>('?'));
EXPECT_GT(glyphInfo.advanceX, 0.0f);
EXPECT_TRUE(glyphInfo.visible);
return;
}
if (!glyphInfo.visible) {
EXPECT_GE(glyphInfo.advanceX, 0.0f);
return;
}
if (glyphInfo.resolvedCodepoint == dynamicCodepoint) {
EXPECT_NE(refreshedView.pixelDataKey, beforeView.pixelDataKey);
}
EXPECT_GT(glyphInfo.advanceX, 0.0f);
EXPECT_TRUE(glyphInfo.visible);
}
TEST(XCUIStandaloneTextAtlasProviderTest, MissingGlyphFallsBackToQuestionMarkWhenFontCannotRasterizeIt) {
XCUIStandaloneTextAtlasProvider provider = {};
const IXCUITextAtlasProvider::FontHandle defaultFont = provider.GetDefaultFont();
ASSERT_TRUE(defaultFont.IsValid());
IXCUITextAtlasProvider::GlyphInfo glyphInfo = {};
ASSERT_TRUE(provider.FindGlyph(defaultFont, 16.0f, 0x10FFFFu, glyphInfo));
EXPECT_EQ(glyphInfo.requestedCodepoint, 0x10FFFFu);
EXPECT_EQ(glyphInfo.resolvedCodepoint, static_cast<std::uint32_t>('?'));
EXPECT_GT(glyphInfo.advanceX, 0.0f);
EXPECT_TRUE(glyphInfo.visible);
}
} // namespace