Replace new_editor with native XCUI shell sandbox
This commit is contained in:
@@ -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 = {};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
54
tests/NewEditor/test_sandbox_frame_builder.cpp
Normal file
54
tests/NewEditor/test_sandbox_frame_builder.cpp
Normal 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"));
|
||||
}
|
||||
91
tests/NewEditor/test_structured_editor_shell.cpp
Normal file
91
tests/NewEditor/test_structured_editor_shell.cpp
Normal 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"));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user