Replace new_editor with native XCUI shell sandbox
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
#include <XCEngine/Rendering/ObjectIdEncoding.h>
|
||||
#include <XCEngine/Rendering/RenderPass.h>
|
||||
#include <XCEngine/Rendering/RenderResourceCache.h>
|
||||
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/RHI/RHIPipelineState.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
|
||||
|
||||
@@ -32,7 +32,10 @@ namespace Layout = XCEngine::UI::Layout;
|
||||
|
||||
constexpr float kDefaultFontSize = 16.0f;
|
||||
constexpr float kSmallFontSize = 13.0f;
|
||||
constexpr float kButtonFontSize = 14.0f;
|
||||
constexpr float kApproximateGlyphWidth = 0.56f;
|
||||
constexpr float kHeaderTextInset = 12.0f;
|
||||
constexpr float kHeaderTextGap = 2.0f;
|
||||
|
||||
struct RuntimeLayoutNode {
|
||||
const UIDocumentNode* source = nullptr;
|
||||
@@ -71,6 +74,10 @@ float MeasureTextHeight(float fontSize) {
|
||||
return fontSize + 6.0f;
|
||||
}
|
||||
|
||||
float ComputeCenteredTextTop(const UIRect& rect, float fontSize) {
|
||||
return rect.y + (std::max)(0.0f, std::floor((rect.height - fontSize) * 0.5f));
|
||||
}
|
||||
|
||||
const UIDocumentAttribute* FindAttribute(const UIDocumentNode& node, const char* name) {
|
||||
for (const UIDocumentAttribute& attribute : node.attributes) {
|
||||
if (attribute.name == name) {
|
||||
@@ -89,6 +96,43 @@ std::string GetAttribute(
|
||||
return attribute != nullptr ? ToStdString(attribute->value) : fallback;
|
||||
}
|
||||
|
||||
float MeasureHeaderTextWidth(const UIDocumentNode& node) {
|
||||
float width = 0.0f;
|
||||
|
||||
const std::string title = GetAttribute(node, "title");
|
||||
if (!title.empty()) {
|
||||
width = (std::max)(width, MeasureTextWidth(title, kDefaultFontSize));
|
||||
}
|
||||
|
||||
const std::string subtitle = GetAttribute(node, "subtitle");
|
||||
if (!subtitle.empty()) {
|
||||
width = (std::max)(width, MeasureTextWidth(subtitle, kSmallFontSize));
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
float MeasureHeaderHeight(const UIDocumentNode& node) {
|
||||
const std::string title = GetAttribute(node, "title");
|
||||
const std::string subtitle = GetAttribute(node, "subtitle");
|
||||
if (title.empty() && subtitle.empty()) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float headerHeight = kHeaderTextInset;
|
||||
if (!title.empty()) {
|
||||
headerHeight += MeasureTextHeight(kDefaultFontSize);
|
||||
}
|
||||
if (!subtitle.empty()) {
|
||||
if (!title.empty()) {
|
||||
headerHeight += kHeaderTextGap;
|
||||
}
|
||||
headerHeight += MeasureTextHeight(kSmallFontSize);
|
||||
}
|
||||
|
||||
return headerHeight;
|
||||
}
|
||||
|
||||
bool TryParseFloat(const std::string& text, float& outValue) {
|
||||
if (text.empty()) {
|
||||
return false;
|
||||
@@ -139,6 +183,38 @@ Layout::UILayoutThickness ParsePadding(
|
||||
return Layout::UILayoutThickness::Uniform(ParseFloatAttribute(node, "padding", fallback));
|
||||
}
|
||||
|
||||
Layout::UILayoutItem BuildLayoutItem(
|
||||
const RuntimeLayoutNode& child,
|
||||
Layout::UILayoutAxis parentAxis) {
|
||||
Layout::UILayoutItem item = {};
|
||||
item.desiredContentSize = child.desiredSize;
|
||||
item.width = ParseLengthAttribute(*child.source, "width");
|
||||
item.height = ParseLengthAttribute(*child.source, "height");
|
||||
|
||||
// Pixel-authored lengths act as requested extents, but never below the measured content floor.
|
||||
if (item.width.unit == Layout::UILayoutLengthUnit::Pixels &&
|
||||
item.width.value < child.desiredSize.width) {
|
||||
item.minSize.width = child.desiredSize.width;
|
||||
}
|
||||
|
||||
if (item.height.unit == Layout::UILayoutLengthUnit::Pixels &&
|
||||
item.height.value < child.desiredSize.height) {
|
||||
item.minSize.height = child.desiredSize.height;
|
||||
}
|
||||
|
||||
if (parentAxis == Layout::UILayoutAxis::Vertical &&
|
||||
item.width.unit == Layout::UILayoutLengthUnit::Auto) {
|
||||
item.horizontalAlignment = Layout::UILayoutAlignment::Stretch;
|
||||
}
|
||||
|
||||
if (parentAxis == Layout::UILayoutAxis::Horizontal &&
|
||||
item.height.unit == Layout::UILayoutLengthUnit::Auto) {
|
||||
item.verticalAlignment = Layout::UILayoutAlignment::Stretch;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
std::string ResolveNodeText(const UIDocumentNode& node) {
|
||||
const std::string text = GetAttribute(node, "text");
|
||||
if (!text.empty()) {
|
||||
@@ -175,31 +251,31 @@ Color ResolveBackgroundColor(const UIDocumentNode& node) {
|
||||
const std::string tagName = ToStdString(node.tagName);
|
||||
|
||||
if (tagName == "View") {
|
||||
return Color(0.08f, 0.09f, 0.11f, 1.0f);
|
||||
return Color(0.11f, 0.11f, 0.11f, 1.0f);
|
||||
}
|
||||
if (tone == "accent") {
|
||||
return Color(0.19f, 0.31f, 0.52f, 1.0f);
|
||||
return Color(0.25f, 0.25f, 0.25f, 1.0f);
|
||||
}
|
||||
if (tone == "accent-alt") {
|
||||
return Color(0.24f, 0.26f, 0.39f, 1.0f);
|
||||
return Color(0.22f, 0.22f, 0.22f, 1.0f);
|
||||
}
|
||||
if (tagName == "Button") {
|
||||
return Color(0.20f, 0.23f, 0.29f, 1.0f);
|
||||
return Color(0.24f, 0.24f, 0.24f, 1.0f);
|
||||
}
|
||||
|
||||
return Color(0.13f, 0.15f, 0.18f, 1.0f);
|
||||
return Color(0.16f, 0.16f, 0.16f, 1.0f);
|
||||
}
|
||||
|
||||
Color ResolveBorderColor(const UIDocumentNode& node) {
|
||||
const std::string tone = GetAttribute(node, "tone");
|
||||
if (tone == "accent") {
|
||||
return Color(0.31f, 0.56f, 0.90f, 1.0f);
|
||||
return Color(0.42f, 0.42f, 0.42f, 1.0f);
|
||||
}
|
||||
if (tone == "accent-alt") {
|
||||
return Color(0.47f, 0.51f, 0.76f, 1.0f);
|
||||
return Color(0.34f, 0.34f, 0.34f, 1.0f);
|
||||
}
|
||||
|
||||
return Color(0.25f, 0.28f, 0.33f, 1.0f);
|
||||
return Color(0.30f, 0.30f, 0.30f, 1.0f);
|
||||
}
|
||||
|
||||
RuntimeLayoutNode BuildLayoutTree(const UIDocumentNode& source) {
|
||||
@@ -246,42 +322,32 @@ UISize MeasureNode(RuntimeLayoutNode& node) {
|
||||
std::vector<Layout::UILayoutItem> items = {};
|
||||
items.reserve(node.children.size());
|
||||
for (RuntimeLayoutNode& child : node.children) {
|
||||
Layout::UILayoutItem item = {};
|
||||
item.desiredContentSize = MeasureNode(child);
|
||||
item.width = ParseLengthAttribute(*child.source, "width");
|
||||
item.height = ParseLengthAttribute(*child.source, "height");
|
||||
MeasureNode(child);
|
||||
Layout::UILayoutItem item = BuildLayoutItem(child, options.axis);
|
||||
items.push_back(item);
|
||||
}
|
||||
|
||||
const auto measured = Layout::MeasureStackLayout(options, items);
|
||||
node.desiredSize = measured.desiredSize;
|
||||
|
||||
const std::string title = GetAttribute(source, "title");
|
||||
const std::string subtitle = GetAttribute(source, "subtitle");
|
||||
float headerHeight = 0.0f;
|
||||
if (!title.empty()) {
|
||||
headerHeight += MeasureTextHeight(kDefaultFontSize);
|
||||
}
|
||||
if (!subtitle.empty()) {
|
||||
if (headerHeight > 0.0f) {
|
||||
headerHeight += 4.0f;
|
||||
}
|
||||
headerHeight += MeasureTextHeight(kSmallFontSize);
|
||||
}
|
||||
const float headerHeight = MeasureHeaderHeight(source);
|
||||
const float headerTextWidth = MeasureHeaderTextWidth(source);
|
||||
|
||||
node.desiredSize.width = (std::max)(
|
||||
node.desiredSize.width,
|
||||
MeasureTextWidth(ResolveNodeText(source), kDefaultFontSize) + options.padding.Horizontal());
|
||||
headerTextWidth > 0.0f
|
||||
? headerTextWidth + options.padding.Horizontal()
|
||||
: MeasureTextWidth(ResolveNodeText(source), kDefaultFontSize) + options.padding.Horizontal());
|
||||
node.desiredSize.height += headerHeight;
|
||||
|
||||
float explicitWidth = 0.0f;
|
||||
if (TryParseFloat(GetAttribute(source, "width"), explicitWidth)) {
|
||||
node.desiredSize.width = explicitWidth;
|
||||
node.desiredSize.width = (std::max)(node.desiredSize.width, explicitWidth);
|
||||
}
|
||||
|
||||
float explicitHeight = 0.0f;
|
||||
if (TryParseFloat(GetAttribute(source, "height"), explicitHeight)) {
|
||||
node.desiredSize.height = explicitHeight;
|
||||
node.desiredSize.height = (std::max)(node.desiredSize.height, explicitHeight);
|
||||
}
|
||||
|
||||
return node.desiredSize;
|
||||
@@ -308,16 +374,7 @@ void ArrangeNode(RuntimeLayoutNode& node, const UIRect& rect) {
|
||||
source,
|
||||
tagName == "View" ? 16.0f : 12.0f);
|
||||
|
||||
float headerHeight = 0.0f;
|
||||
if (!GetAttribute(source, "title").empty()) {
|
||||
headerHeight += MeasureTextHeight(kDefaultFontSize);
|
||||
}
|
||||
if (!GetAttribute(source, "subtitle").empty()) {
|
||||
if (headerHeight > 0.0f) {
|
||||
headerHeight += 4.0f;
|
||||
}
|
||||
headerHeight += MeasureTextHeight(kSmallFontSize);
|
||||
}
|
||||
const float headerHeight = MeasureHeaderHeight(source);
|
||||
|
||||
UIRect contentRect = rect;
|
||||
contentRect.y += headerHeight;
|
||||
@@ -326,10 +383,7 @@ void ArrangeNode(RuntimeLayoutNode& node, const UIRect& rect) {
|
||||
std::vector<Layout::UILayoutItem> items = {};
|
||||
items.reserve(node.children.size());
|
||||
for (RuntimeLayoutNode& child : node.children) {
|
||||
Layout::UILayoutItem item = {};
|
||||
item.desiredContentSize = child.desiredSize;
|
||||
item.width = ParseLengthAttribute(*child.source, "width");
|
||||
item.height = ParseLengthAttribute(*child.source, "height");
|
||||
Layout::UILayoutItem item = BuildLayoutItem(child, options.axis);
|
||||
items.push_back(item);
|
||||
}
|
||||
|
||||
@@ -359,7 +413,7 @@ void EmitNode(
|
||||
|
||||
const std::string title = GetAttribute(source, "title");
|
||||
const std::string subtitle = GetAttribute(source, "subtitle");
|
||||
float textY = node.rect.y + 12.0f;
|
||||
float textY = node.rect.y + kHeaderTextInset;
|
||||
if (!title.empty()) {
|
||||
drawList.AddText(
|
||||
UIPoint(node.rect.x + 12.0f, textY),
|
||||
@@ -367,7 +421,7 @@ void EmitNode(
|
||||
ToUIColor(Color(0.94f, 0.95f, 0.97f, 1.0f)),
|
||||
kDefaultFontSize);
|
||||
++stats.textCommandCount;
|
||||
textY += MeasureTextHeight(kDefaultFontSize) + 2.0f;
|
||||
textY += MeasureTextHeight(kDefaultFontSize) + kHeaderTextGap;
|
||||
}
|
||||
|
||||
if (!subtitle.empty()) {
|
||||
@@ -390,10 +444,10 @@ void EmitNode(
|
||||
|
||||
if (tagName == "Button" && title.empty() && subtitle.empty()) {
|
||||
drawList.AddText(
|
||||
UIPoint(node.rect.x + 12.0f, node.rect.y + 12.0f),
|
||||
UIPoint(node.rect.x + 12.0f, ComputeCenteredTextTop(node.rect, kButtonFontSize)),
|
||||
ResolveNodeText(source),
|
||||
ToUIColor(Color(0.95f, 0.97f, 1.0f, 1.0f)),
|
||||
kDefaultFontSize);
|
||||
kButtonFontSize);
|
||||
++stats.textCommandCount;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,171 +1,77 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
|
||||
if(MSVC AND POLICY CMP0141)
|
||||
cmake_policy(SET CMP0141 NEW)
|
||||
endif()
|
||||
|
||||
project(XCNewEditor VERSION 0.1 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_definitions(-DUNICODE -D_UNICODE)
|
||||
file(TO_CMAKE_PATH "${CMAKE_SOURCE_DIR}" XCNEWEDITOR_REPO_ROOT_PATH)
|
||||
|
||||
set(XCENGINE_ROOT_DIR "")
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/engine/CMakeLists.txt")
|
||||
set(XCENGINE_ROOT_DIR "${CMAKE_SOURCE_DIR}")
|
||||
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../engine/CMakeLists.txt")
|
||||
set(XCENGINE_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to locate XCEngine root directory from new_editor.")
|
||||
endif()
|
||||
|
||||
if(NOT TARGET XCEngine)
|
||||
add_subdirectory(${XCENGINE_ROOT_DIR}/engine ${CMAKE_CURRENT_BINARY_DIR}/engine_dependency)
|
||||
endif()
|
||||
|
||||
set(IMGUI_SOURCE_DIR "${CMAKE_BINARY_DIR}/_deps/imgui-src")
|
||||
if(NOT EXISTS "${IMGUI_SOURCE_DIR}/imgui.cpp")
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
imgui_new_editor
|
||||
GIT_REPOSITORY https://gitee.com/mirrors/imgui.git
|
||||
GIT_TAG docking
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
FetchContent_MakeAvailable(imgui_new_editor)
|
||||
set(IMGUI_SOURCE_DIR "${imgui_new_editor_SOURCE_DIR}")
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS "${IMGUI_SOURCE_DIR}/imgui.cpp")
|
||||
message(FATAL_ERROR "ImGui source was not found at ${IMGUI_SOURCE_DIR}.")
|
||||
endif()
|
||||
|
||||
set(NEW_EDITOR_SOURCES
|
||||
src/main.cpp
|
||||
src/Application.cpp
|
||||
src/ApplicationDefaultShell.cpp
|
||||
src/panels/Panel.cpp
|
||||
src/Rendering/MainWindowBackdropPass.cpp
|
||||
src/Rendering/MainWindowNativeBackdropRenderer.cpp
|
||||
src/XCUIBackend/NativeWindowUICompositor.cpp
|
||||
src/XCUIBackend/XCUIAssetDocumentSource.cpp
|
||||
src/XCUIBackend/XCUIEditorCommandRouter.cpp
|
||||
src/XCUIBackend/XCUIInputBridge.cpp
|
||||
src/XCUIBackend/XCUIShellChromeState.cpp
|
||||
src/XCUIBackend/XCUIRHICommandCompiler.cpp
|
||||
src/XCUIBackend/XCUIRHIRenderBackend.cpp
|
||||
src/XCUIBackend/XCUIStandaloneTextAtlasProvider.cpp
|
||||
src/XCUIBackend/XCUIDemoRuntime.cpp
|
||||
src/XCUIBackend/XCUILayoutLabRuntime.cpp
|
||||
set(NEW_EDITOR_RESOURCE_FILES
|
||||
ui/views/editor_shell.xcui
|
||||
ui/themes/editor_shell.xctheme
|
||||
ui/schemas/editor_inspector_shell.xcschema
|
||||
)
|
||||
|
||||
set(NEW_EDITOR_IMGUI_COMPAT_SOURCES
|
||||
src/ApplicationLegacyImGui.cpp
|
||||
src/panels/XCUIDemoPanel.cpp
|
||||
src/panels/XCUILayoutLabPanel.cpp
|
||||
src/XCUIBackend/ImGuiXCUIInputAdapter.cpp
|
||||
src/XCUIBackend/LegacyImGuiHostInterop.cpp
|
||||
src/XCUIBackend/ImGuiHostCompositor.cpp
|
||||
src/XCUIBackend/XCUIEditorFontSetup.cpp
|
||||
${IMGUI_SOURCE_DIR}/imgui.cpp
|
||||
${IMGUI_SOURCE_DIR}/imgui_demo.cpp
|
||||
${IMGUI_SOURCE_DIR}/imgui_draw.cpp
|
||||
${IMGUI_SOURCE_DIR}/imgui_tables.cpp
|
||||
${IMGUI_SOURCE_DIR}/imgui_widgets.cpp
|
||||
${IMGUI_SOURCE_DIR}/backends/imgui_impl_win32.cpp
|
||||
${IMGUI_SOURCE_DIR}/backends/imgui_impl_dx12.cpp
|
||||
add_library(XCNewEditorLib STATIC
|
||||
src/SandboxFrameBuilder.cpp
|
||||
)
|
||||
|
||||
add_library(XCNewEditorImGuiCompat STATIC ${NEW_EDITOR_IMGUI_COMPAT_SOURCES})
|
||||
add_executable(${PROJECT_NAME} WIN32 ${NEW_EDITOR_SOURCES})
|
||||
|
||||
set(NEW_EDITOR_COMMON_INCLUDE_DIRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${XCENGINE_ROOT_DIR}/engine/include
|
||||
target_include_directories(XCNewEditorLib
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
)
|
||||
|
||||
set(NEW_EDITOR_IMGUI_COMPAT_INCLUDE_DIRS
|
||||
${NEW_EDITOR_COMMON_INCLUDE_DIRS}
|
||||
${IMGUI_SOURCE_DIR}
|
||||
${IMGUI_SOURCE_DIR}/backends
|
||||
)
|
||||
|
||||
target_include_directories(XCNewEditorImGuiCompat PRIVATE ${NEW_EDITOR_IMGUI_COMPAT_INCLUDE_DIRS})
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${NEW_EDITOR_COMMON_INCLUDE_DIRS})
|
||||
|
||||
file(TO_CMAKE_PATH "${XCENGINE_ROOT_DIR}" XCENGINE_ROOT_DIR_CMAKE)
|
||||
|
||||
target_compile_definitions(XCNewEditorImGuiCompat PRIVATE
|
||||
target_compile_definitions(XCNewEditorLib PUBLIC
|
||||
UNICODE
|
||||
_UNICODE
|
||||
NOMINMAX
|
||||
XCENGINE_NEW_EDITOR_REPO_ROOT="${XCENGINE_ROOT_DIR_CMAKE}"
|
||||
)
|
||||
target_compile_options(XCNewEditorImGuiCompat PRIVATE /utf-8)
|
||||
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
||||
UNICODE
|
||||
_UNICODE
|
||||
NOMINMAX
|
||||
XCENGINE_NEW_EDITOR_REPO_ROOT="${XCENGINE_ROOT_DIR_CMAKE}"
|
||||
)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE /utf-8)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(XCNewEditorImGuiCompat PRIVATE /FS)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE /FS)
|
||||
set_property(TARGET XCNewEditorImGuiCompat PROPERTY
|
||||
target_compile_options(XCNewEditorLib PRIVATE /utf-8 /FS)
|
||||
set_property(TARGET XCNewEditorLib PROPERTY
|
||||
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||
set_target_properties(XCNewEditorImGuiCompat PROPERTIES
|
||||
MSVC_DEBUG_INFORMATION_FORMAT "$<$<CONFIG:Debug,RelWithDebInfo>:Embedded>"
|
||||
COMPILE_PDB_NAME "XCNewEditorImGuiCompat-compile"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/new_editor/compile-pdb-compat"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/new_editor/compile-pdb-compat/Debug"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/new_editor/compile-pdb-compat/Release"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_BINARY_DIR}/new_editor/compile-pdb-compat/MinSizeRel"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/new_editor/compile-pdb-compat/RelWithDebInfo"
|
||||
PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/new_editor/pdb-compat"
|
||||
PDB_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/new_editor/pdb-compat/Debug"
|
||||
PDB_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/new_editor/pdb-compat/Release"
|
||||
PDB_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_BINARY_DIR}/new_editor/pdb-compat/MinSizeRel"
|
||||
PDB_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/new_editor/pdb-compat/RelWithDebInfo")
|
||||
target_link_options(${PROJECT_NAME} PRIVATE
|
||||
$<$<CONFIG:Debug,RelWithDebInfo>:/INCREMENTAL:NO>)
|
||||
set_property(TARGET ${PROJECT_NAME} PROPERTY
|
||||
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
MSVC_DEBUG_INFORMATION_FORMAT "$<$<CONFIG:Debug,RelWithDebInfo>:Embedded>"
|
||||
COMPILE_PDB_NAME "XCNewEditor-compile"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/new_editor/compile-pdb"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/new_editor/compile-pdb/Debug"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/new_editor/compile-pdb/Release"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_BINARY_DIR}/new_editor/compile-pdb/MinSizeRel"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/new_editor/compile-pdb/RelWithDebInfo"
|
||||
VS_GLOBAL_UseMultiToolTask "false"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(XCNewEditorImGuiCompat PRIVATE
|
||||
target_link_libraries(XCNewEditorLib PUBLIC
|
||||
XCEngine
|
||||
d3d12.lib
|
||||
dxgi.lib
|
||||
user32
|
||||
gdi32
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
XCEngine
|
||||
d3d12.lib
|
||||
dxgi.lib
|
||||
user32
|
||||
gdi32
|
||||
add_executable(XCNewEditorApp WIN32
|
||||
src/main.cpp
|
||||
src/Application.cpp
|
||||
src/AutoScreenshot.cpp
|
||||
src/NativeRenderer.cpp
|
||||
${NEW_EDITOR_RESOURCE_FILES}
|
||||
)
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
target_include_directories(XCNewEditorApp PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
)
|
||||
|
||||
target_compile_definitions(XCNewEditorApp PRIVATE
|
||||
UNICODE
|
||||
_UNICODE
|
||||
XCNEWEDITOR_REPO_ROOT="${XCNEWEDITOR_REPO_ROOT_PATH}"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(XCNewEditorApp PRIVATE /utf-8 /FS)
|
||||
set_property(TARGET XCNewEditorApp PROPERTY
|
||||
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||
endif()
|
||||
|
||||
target_link_libraries(XCNewEditorApp PRIVATE
|
||||
XCNewEditorLib
|
||||
d2d1.lib
|
||||
dwrite.lib
|
||||
windowscodecs.lib
|
||||
)
|
||||
|
||||
set_target_properties(XCNewEditorApp PROPERTIES
|
||||
OUTPUT_NAME "XCNewEditor"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/new_editor/bin"
|
||||
PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/new_editor/bin"
|
||||
VS_DEBUGGER_WORKING_DIRECTORY "${XCENGINE_ROOT_DIR}"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin"
|
||||
)
|
||||
|
||||
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${NEW_EDITOR_RESOURCE_FILES})
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# new_editor XCUI sandbox
|
||||
|
||||
`new_editor` is an **isolated playground** for the XCUI experiment. It is intentionally separated from the main `editor` so you can redesign the UI stack without destabilizing the current ImGui-based editor.
|
||||
|
||||
## Current scope
|
||||
- Provides a minimal Win32 + ImGui host (scaffolded elsewhere) that links the engine and XCUI code.
|
||||
- Hosts a single demo panel that loads XCUI documents and reroutes draw commands through `ImGuiTransitionBackend`.
|
||||
- Bundles themed XCUI documents under `resources/` so the demo can run without touching the primary editor's assets.
|
||||
|
||||
## Resources
|
||||
`resources/xcui_demo_view.xcui` describes the card layout, placeholder texts, and debug markers the demo renders. `resources/xcui_demo_theme.xctheme` defines colors, spacing, and tokenized styles that the runtime resolves.
|
||||
|
||||
## Next steps
|
||||
1. Expand the demo UI to exercise more layout primitives (stack, grid, overlays) and token-based styling.
|
||||
2. Hook the runtime into actual document hot-reload so the panel responds to source edits.
|
||||
3. Use `new_editor` to prototype a native XCUI shell before deciding whether to migrate the real editor.
|
||||
@@ -1,24 +0,0 @@
|
||||
<Theme name="XCUI New Editor Theme">
|
||||
<Token name="color.surface" type="color" value="#0D1218" />
|
||||
<Token name="color.surface.elevated" type="color" value="#141C26" />
|
||||
<Token name="color.surface.card" type="color" value="#1D2733" />
|
||||
<Token name="color.surface.input" type="color" value="#101923" />
|
||||
<Token name="color.text.primary" type="color" value="#F7F9FC" />
|
||||
<Token name="color.text.secondary" type="color" value="#BCC8DB" />
|
||||
<Token name="color.text.placeholder" type="color" value="#7B8A9B" />
|
||||
<Token name="color.accent" type="color" value="#5EE3FF" />
|
||||
<Token name="color.accent.alt" type="color" value="#FFB65F" />
|
||||
<Token name="color.outline" type="color" value="#3A4A5A" />
|
||||
<Token name="color.surface.track" type="color" value="#243241" />
|
||||
<Token name="space.compact" type="float" value="8" />
|
||||
<Token name="space.regular" type="float" value="14" />
|
||||
<Token name="space.loose" type="float" value="20" />
|
||||
<Token name="padding.panel" type="thickness" value="18" />
|
||||
<Token name="padding.card" type="thickness" value="12" />
|
||||
<Token name="radius.card" type="corner-radius" value="10" />
|
||||
<Token name="radius.button" type="corner-radius" value="6" />
|
||||
<Token name="radius.pill" type="corner-radius" value="18" />
|
||||
<Token name="font.body" type="float" value="14" />
|
||||
<Token name="font.title" type="float" value="18" />
|
||||
<Token name="line.default" type="float" value="1" />
|
||||
</Theme>
|
||||
@@ -1,89 +0,0 @@
|
||||
<View name="NewEditor Demo" theme="xcui_demo_theme.xctheme" style="Root">
|
||||
<Column id="rootColumn" gap="10" padding="16">
|
||||
<Card id="hero" style="HeroCard">
|
||||
<Column gap="4">
|
||||
<Text id="title" text="New XCUI Shell" style="HeroTitle" />
|
||||
<Text id="subtitle" text="Markup -> Layout -> Style -> DrawData" style="HeroSubtitle" />
|
||||
</Column>
|
||||
</Card>
|
||||
<Row id="metricRow" gap="10">
|
||||
<Card id="metric1" style="MetricCard" width="stretch">
|
||||
<Column gap="6">
|
||||
<Text text="Tree status" style="MetricLabel" />
|
||||
<Text text="Driven by runtime" style="MetricValue" />
|
||||
<ProgressBar
|
||||
id="densityMeter"
|
||||
text="Density stress"
|
||||
state-key="toggleDensity"
|
||||
value-off="0.44"
|
||||
value-on="0.86"
|
||||
fill="token:color.accent"
|
||||
width="stretch" />
|
||||
</Column>
|
||||
</Card>
|
||||
<Card id="metric2" style="MetricCard" width="stretch">
|
||||
<Column gap="6">
|
||||
<Text text="Input" style="MetricLabel" />
|
||||
<Text text="Hover + shortcuts" style="MetricValue" />
|
||||
<Row gap="6">
|
||||
<Swatch id="swatchAccent" text="Accent" token="color.accent" width="stretch" height="24" />
|
||||
<Swatch id="swatchAlt" text="Alt" token="color.accent.alt" width="stretch" height="24" />
|
||||
</Row>
|
||||
</Column>
|
||||
</Card>
|
||||
</Row>
|
||||
<Row id="toggleRow" gap="8">
|
||||
<Toggle id="toggleDensity" text="Density stress" width="stretch" />
|
||||
<Toggle
|
||||
id="toggleDiagnostics"
|
||||
text="Debug path"
|
||||
width="stretch"
|
||||
fill="token:color.accent.alt" />
|
||||
</Row>
|
||||
<Card id="commandCard" style="MetricCard">
|
||||
<Column gap="6">
|
||||
<Text text="Agent command" style="MetricLabel" />
|
||||
<TextField
|
||||
id="agentPrompt"
|
||||
style="CommandField"
|
||||
width="stretch"
|
||||
min-width="240"
|
||||
placeholder="Type a command or note for XCUI..."
|
||||
value="" />
|
||||
<Text
|
||||
id="promptMeta"
|
||||
text="Single-line input, Enter submits"
|
||||
style="Meta" />
|
||||
</Column>
|
||||
</Card>
|
||||
<Card id="notesCard" style="MetricCard">
|
||||
<Column gap="6">
|
||||
<Text text="Session notes" style="MetricLabel" />
|
||||
<TextArea
|
||||
id="sessionNotes"
|
||||
style="CommandArea"
|
||||
width="stretch"
|
||||
min-width="240"
|
||||
rows="4"
|
||||
show-line-numbers="true"
|
||||
placeholder="Write multiline notes, prompts, or todos for the current screen..."
|
||||
value="" />
|
||||
<Text
|
||||
id="notesMeta"
|
||||
text="Multiline input, click caret, Tab indent"
|
||||
style="Meta" />
|
||||
</Column>
|
||||
</Card>
|
||||
<Button id="toggleAccent" action="demo.toggleAccent" style="AccentButton">
|
||||
<Text text="Toggle Accent" style="ButtonLabel" />
|
||||
</Button>
|
||||
<Card id="debug" style="DebugCard">
|
||||
<Column gap="3">
|
||||
<Text id="statusFocus" text="Focus: waiting" style="Meta" />
|
||||
<Text id="statusLayout" text="Layout: idle" style="Meta" />
|
||||
<Text id="statusCommands" text="Commands: ready" style="Meta" />
|
||||
<Text id="statusWidgets" text="Widgets: ready" style="Meta" />
|
||||
</Column>
|
||||
</Card>
|
||||
</Column>
|
||||
</View>
|
||||
@@ -1,21 +0,0 @@
|
||||
<Theme name="Layout Lab Theme">
|
||||
<Token name="color.panel" type="color" value="#10161D" />
|
||||
<Token name="color.card" type="color" value="#1C2632" />
|
||||
<Token name="color.card.alt" type="color" value="#243243" />
|
||||
<Token name="color.accent" type="color" value="#2E5A73" />
|
||||
<Token name="color.border" type="color" value="#3B4E62" />
|
||||
<Token name="color.text" type="color" value="#F1F6FB" />
|
||||
<Token name="color.text.muted" type="color" value="#B8C6D6" />
|
||||
<Token name="space.outer" type="float" value="18" />
|
||||
<Token name="space.gap" type="float" value="14" />
|
||||
<Token name="space.stack" type="float" value="12" />
|
||||
<Token name="space.cardInset" type="float" value="12" />
|
||||
<Token name="radius.card" type="float" value="10" />
|
||||
<Token name="size.listItemHeight" type="float" value="60" />
|
||||
<Token name="size.treeItemHeight" type="float" value="30" />
|
||||
<Token name="size.treeIndent" type="float" value="18" />
|
||||
<Token name="size.fieldRowHeight" type="float" value="32" />
|
||||
<Token name="size.propertySectionHeight" type="float" value="156" />
|
||||
<Token name="font.title" type="float" value="16" />
|
||||
<Token name="font.body" type="float" value="13" />
|
||||
</Theme>
|
||||
@@ -1,118 +0,0 @@
|
||||
<View name="Layout Lab" theme="xcui_layout_lab_theme.xctheme">
|
||||
<Column id="root" gap="14" padding="18">
|
||||
<Card
|
||||
id="heroCard"
|
||||
height="92"
|
||||
tone="accent"
|
||||
title="XCUI Layout Lab"
|
||||
subtitle="Resource-driven row / column / overlay stress." />
|
||||
<Row id="mainRow" height="stretch" gap="14">
|
||||
<Column id="leftRail" width="272" gap="10">
|
||||
<Card
|
||||
id="toolShelf"
|
||||
height="62"
|
||||
tone="accent-alt"
|
||||
title="Tool Shelf"
|
||||
subtitle="Scene, asset, and play-mode actions." />
|
||||
<ScrollView id="assetList" height="stretch" padding="10" gap="8" scrollY="40">
|
||||
<Card
|
||||
id="assetListHeader"
|
||||
height="54"
|
||||
tone="accent-alt"
|
||||
title="Project Browser"
|
||||
subtitle="Pinned filters and import shortcuts." />
|
||||
<TreeView id="projectTree" height="156" padding="8" gap="6">
|
||||
<TreeItem id="treeAssetsRoot" title="Assets" subtitle="Workspace root" />
|
||||
<TreeItem id="treeScenes" title="Scenes" subtitle="4 authored scenes" indent="1" />
|
||||
<TreeItem id="treeMaterials" title="Materials" subtitle="Shared lookdev library" indent="1" />
|
||||
<TreeItem id="treeCharacters" title="Characters" subtitle="Prefab variants" indent="1" />
|
||||
<TreeItem id="treeUi" title="UI" subtitle="Runtime atlas + themes" indent="1" />
|
||||
</TreeView>
|
||||
<ListView id="recentAssetList" height="360" padding="8" gap="6">
|
||||
<ListItem id="assetLighting" title="Lighting_GlobalRig" subtitle="Prefab -Directional setup" />
|
||||
<ListItem id="assetMaterials" title="Materials_Master" subtitle="14 assets selected" />
|
||||
<ListItem id="assetTerrain" title="Terrain_Cliffs_04" subtitle="Texture2D -4096 x 4096" />
|
||||
<ListItem id="assetCharacter" title="Hero_Character_Controller" subtitle="Prefab -Animation graph bound" />
|
||||
<ListItem id="assetUiAtlas" title="UI_RuntimeAtlas" subtitle="SpriteAtlas -83 packed sprites" />
|
||||
</ListView>
|
||||
</ScrollView>
|
||||
</Column>
|
||||
<Column id="centerColumn" width="stretch" gap="10">
|
||||
<Card
|
||||
id="viewportToolbar"
|
||||
height="62"
|
||||
title="Viewport Toolbar"
|
||||
subtitle="Gizmos, snap presets, camera bookmarks." />
|
||||
<Overlay id="viewportOverlay" height="stretch">
|
||||
<Card
|
||||
id="viewportBase"
|
||||
title="Scene Viewport"
|
||||
subtitle="Primary preview surface with composition overlays." />
|
||||
<Card
|
||||
id="viewportBadge"
|
||||
x="18"
|
||||
y="18"
|
||||
width="224"
|
||||
height="68"
|
||||
tone="accent-alt"
|
||||
title="Selection Overlay"
|
||||
subtitle="Bounds, pivots, nav markers." />
|
||||
<Card
|
||||
id="viewportInspectorBubble"
|
||||
x="0.58"
|
||||
y="0.54"
|
||||
width="0.32"
|
||||
height="88"
|
||||
tone="accent-alt"
|
||||
title="Context Bubble"
|
||||
subtitle="Inline quick edit affordance." />
|
||||
</Overlay>
|
||||
</Column>
|
||||
<Column id="inspectorColumn" width="320" gap="10">
|
||||
<Card
|
||||
id="inspectorSummary"
|
||||
height="88"
|
||||
title="Inspector Summary"
|
||||
subtitle="Transform, renderer, and prefab overrides." />
|
||||
<ScrollView id="inspectorSections" height="stretch" padding="10" gap="8" scrollY="0">
|
||||
<PropertySection id="inspectorTransform" height="156" title="Transform" subtitle="Position / rotation / scale">
|
||||
<FieldRow id="fieldPosition" title="Position" subtitle="0.0, 1.5, 0.0" />
|
||||
<FieldRow id="fieldRotation" title="Rotation" subtitle="0.0, 42.0, 0.0" />
|
||||
<FieldRow id="fieldScale" title="Scale" subtitle="1.0, 1.0, 1.0" />
|
||||
</PropertySection>
|
||||
<PropertySection id="inspectorMesh" height="156" title="Mesh Renderer" subtitle="Materials, shadow flags, probes">
|
||||
<FieldRow id="fieldMaterial" title="Material" subtitle="M_StylizedTerrain" />
|
||||
<FieldRow id="fieldShadows" title="Cast Shadows" subtitle="On" />
|
||||
<FieldRow id="fieldProbe" title="Light Probe" subtitle="Blend Probes" />
|
||||
</PropertySection>
|
||||
<PropertySection id="inspectorMetadata" height="132" title="Metadata" subtitle="Tags, labels, import provenance">
|
||||
<FieldRow id="fieldTags" title="Tags" subtitle="Gameplay, Hero, Traversal" />
|
||||
<FieldRow id="fieldImportedBy" title="Imported By" subtitle="Asset pipeline v2" />
|
||||
</PropertySection>
|
||||
</ScrollView>
|
||||
</Column>
|
||||
</Row>
|
||||
<Overlay id="footerOverlay" height="146">
|
||||
<Card
|
||||
id="footerBase"
|
||||
title="Footer Overlay"
|
||||
subtitle="Second overlay region for quick smoke testing." />
|
||||
<Card
|
||||
id="footerLeft"
|
||||
x="18"
|
||||
y="48"
|
||||
width="180"
|
||||
height="58"
|
||||
tone="accent-alt"
|
||||
title="Footer A" />
|
||||
<Card
|
||||
id="footerRight"
|
||||
x="0.74"
|
||||
y="48"
|
||||
width="180"
|
||||
height="58"
|
||||
tone="accent-alt"
|
||||
title="Footer B" />
|
||||
</Overlay>
|
||||
</Column>
|
||||
</View>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,463 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include "XCUIBackend/XCUIEditorCommandRouter.h"
|
||||
#include "XCUIBackend/NativeXCUIPanelCanvasHost.h"
|
||||
#include "XCUIBackend/XCUIHostedPreviewPresenter.h"
|
||||
#include "XCUIBackend/XCUIInputBridge.h"
|
||||
#include "XCUIBackend/XCUILayoutLabRuntime.h"
|
||||
#include "XCUIBackend/XCUIRHIRenderBackend.h"
|
||||
#include "XCUIBackend/XCUIShellChromeState.h"
|
||||
#include "XCUIBackend/XCUIStandaloneTextAtlasProvider.h"
|
||||
#include "XCUIBackend/UITextureRegistration.h"
|
||||
#include "Platform/D3D12WindowRenderer.h"
|
||||
#include "Rendering/MainWindowNativeBackdropRenderer.h"
|
||||
#include "AutoScreenshot.h"
|
||||
#include "NativeRenderer.h"
|
||||
|
||||
#include <XCEngine/UI/Runtime/UIScreenDocumentHost.h>
|
||||
#include <XCEngine/UI/Runtime/UIScreenPlayer.h>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <windows.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
class IWindowUICompositor;
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
|
||||
namespace NewEditor {
|
||||
|
||||
class XCUIDemoPanel;
|
||||
class XCUILayoutLabPanel;
|
||||
|
||||
class Application {
|
||||
public:
|
||||
Application();
|
||||
~Application();
|
||||
|
||||
using ShellChromeState = ::XCEngine::Editor::XCUIBackend::XCUIShellChromeState;
|
||||
using ShellPanelId = ::XCEngine::Editor::XCUIBackend::XCUIShellPanelId;
|
||||
using ShellViewToggleId = ::XCEngine::Editor::XCUIBackend::XCUIShellViewToggleId;
|
||||
using ShellHostedPreviewMode = ::XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewMode;
|
||||
using ShellPanelChromeState = ::XCEngine::Editor::XCUIBackend::XCUIShellPanelChromeState;
|
||||
using ShellViewToggleState = ::XCEngine::Editor::XCUIBackend::XCUIShellViewToggleState;
|
||||
enum class WindowHostMode : std::uint8_t {
|
||||
NativeXCUI = 0,
|
||||
CompatibilityHost
|
||||
};
|
||||
|
||||
struct ShellCommandIds {
|
||||
static constexpr const char* ToggleXCUIDemoPanel =
|
||||
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleXCUIDemoPanel;
|
||||
static constexpr const char* ToggleXCUILayoutLabPanel =
|
||||
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleXCUILayoutLabPanel;
|
||||
static constexpr const char* ToggleNativeBackdrop =
|
||||
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleNativeBackdrop;
|
||||
static constexpr const char* TogglePulseAccent =
|
||||
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::TogglePulseAccent;
|
||||
static constexpr const char* ToggleNativeXCUIOverlay =
|
||||
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleNativeXCUIOverlay;
|
||||
static constexpr const char* ToggleHostedPreviewHud =
|
||||
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleHostedPreviewHud;
|
||||
static constexpr const char* ToggleNativeDemoPanelPreview =
|
||||
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleNativeDemoPanelPreview;
|
||||
static constexpr const char* ToggleNativeLayoutLabPreview =
|
||||
::XCEngine::Editor::XCUIBackend::XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview;
|
||||
};
|
||||
|
||||
struct ShellCommandBindings {
|
||||
std::function<bool()> getXCUIDemoPanelVisible = {};
|
||||
std::function<void(bool)> setXCUIDemoPanelVisible = {};
|
||||
std::function<bool()> getXCUILayoutLabPanelVisible = {};
|
||||
std::function<void(bool)> setXCUILayoutLabPanelVisible = {};
|
||||
std::function<bool()> getNativeBackdropVisible = {};
|
||||
std::function<void(bool)> setNativeBackdropVisible = {};
|
||||
std::function<bool()> getPulseAccentEnabled = {};
|
||||
std::function<void(bool)> setPulseAccentEnabled = {};
|
||||
std::function<bool()> getNativeXCUIOverlayVisible = {};
|
||||
std::function<void(bool)> setNativeXCUIOverlayVisible = {};
|
||||
std::function<bool()> getHostedPreviewHudVisible = {};
|
||||
std::function<void(bool)> setHostedPreviewHudVisible = {};
|
||||
std::function<bool()> getNativeDemoPanelPreviewEnabled = {};
|
||||
std::function<void(bool)> setNativeDemoPanelPreviewEnabled = {};
|
||||
std::function<bool()> getNativeLayoutLabPreviewEnabled = {};
|
||||
std::function<void(bool)> setNativeLayoutLabPreviewEnabled = {};
|
||||
std::function<void()> onHostedPreviewModeChanged = {};
|
||||
};
|
||||
|
||||
enum class NativeHostedPreviewSurfaceState : std::uint8_t {
|
||||
Disabled = 0,
|
||||
AwaitingSubmit,
|
||||
Warming,
|
||||
Live
|
||||
};
|
||||
|
||||
struct NativeHostedPreviewConsumption {
|
||||
NativeHostedPreviewSurfaceState surfaceState = NativeHostedPreviewSurfaceState::Disabled;
|
||||
bool queueRuntimeFrame = false;
|
||||
bool appendRuntimeDrawDataToShell = true;
|
||||
bool showSurfaceImage = false;
|
||||
bool drawRuntimeDebugRects = true;
|
||||
std::string_view placeholderTitle = {};
|
||||
std::string_view placeholderSubtitle = {};
|
||||
};
|
||||
|
||||
static ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot BuildShellShortcutSnapshot(
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& frameDelta) {
|
||||
::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot snapshot = {};
|
||||
snapshot.modifiers = frameDelta.state.modifiers;
|
||||
snapshot.windowFocused = frameDelta.state.windowFocused;
|
||||
snapshot.wantCaptureKeyboard = frameDelta.state.wantCaptureKeyboard;
|
||||
snapshot.wantTextInput = frameDelta.state.wantTextInput;
|
||||
|
||||
snapshot.keys.reserve(
|
||||
frameDelta.keyboard.pressedKeys.size() +
|
||||
frameDelta.keyboard.repeatedKeys.size());
|
||||
|
||||
const auto appendKeyState =
|
||||
[&snapshot](std::int32_t keyCode, bool repeat) {
|
||||
for (auto& existing : snapshot.keys) {
|
||||
if (existing.keyCode != keyCode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
existing.down = true;
|
||||
existing.repeat = existing.repeat || repeat;
|
||||
return;
|
||||
}
|
||||
|
||||
::XCEngine::Editor::XCUIBackend::XCUIEditorCommandKeyState keyState = {};
|
||||
keyState.keyCode = keyCode;
|
||||
keyState.down = true;
|
||||
keyState.repeat = repeat;
|
||||
snapshot.keys.push_back(keyState);
|
||||
};
|
||||
|
||||
for (std::int32_t keyCode : frameDelta.keyboard.pressedKeys) {
|
||||
appendKeyState(keyCode, false);
|
||||
}
|
||||
for (std::int32_t keyCode : frameDelta.keyboard.repeatedKeys) {
|
||||
appendKeyState(keyCode, true);
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
static void RegisterShellViewCommands(
|
||||
::XCEngine::Editor::XCUIBackend::XCUIEditorCommandRouter& router,
|
||||
const ShellCommandBindings& bindings) {
|
||||
using ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandAccelerator;
|
||||
using ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandDefinition;
|
||||
using ::XCEngine::Input::KeyCode;
|
||||
using ModifierState = ::XCEngine::UI::UIInputModifiers;
|
||||
|
||||
const auto bindToggleCommand =
|
||||
[&router](
|
||||
const char* commandId,
|
||||
const std::function<bool()>& getter,
|
||||
const std::function<void(bool)>& setter,
|
||||
std::initializer_list<XCUIEditorCommandAccelerator> accelerators,
|
||||
const std::function<void()>& afterToggle = {}) {
|
||||
if (!getter || !setter) {
|
||||
return;
|
||||
}
|
||||
|
||||
XCUIEditorCommandDefinition definition = {};
|
||||
definition.commandId = commandId;
|
||||
definition.isEnabled = [getter, setter]() {
|
||||
return static_cast<bool>(getter) && static_cast<bool>(setter);
|
||||
};
|
||||
definition.invoke = [getter, setter, afterToggle]() {
|
||||
const bool nextValue = !getter();
|
||||
setter(nextValue);
|
||||
if (afterToggle) {
|
||||
afterToggle();
|
||||
}
|
||||
};
|
||||
definition.accelerators.assign(accelerators.begin(), accelerators.end());
|
||||
router.RegisterCommand(definition);
|
||||
};
|
||||
|
||||
const ModifierState ctrlOnly = { false, true, false, false };
|
||||
const ModifierState ctrlShift = { true, true, false, false };
|
||||
const ModifierState ctrlAlt = { false, true, true, false };
|
||||
|
||||
bindToggleCommand(
|
||||
ShellCommandIds::ToggleXCUIDemoPanel,
|
||||
bindings.getXCUIDemoPanelVisible,
|
||||
bindings.setXCUIDemoPanelVisible,
|
||||
{ XCUIEditorCommandAccelerator{
|
||||
static_cast<std::int32_t>(KeyCode::One),
|
||||
ctrlOnly,
|
||||
true,
|
||||
false } });
|
||||
bindToggleCommand(
|
||||
ShellCommandIds::ToggleXCUILayoutLabPanel,
|
||||
bindings.getXCUILayoutLabPanelVisible,
|
||||
bindings.setXCUILayoutLabPanelVisible,
|
||||
{ XCUIEditorCommandAccelerator{
|
||||
static_cast<std::int32_t>(KeyCode::Two),
|
||||
ctrlOnly,
|
||||
true,
|
||||
false } });
|
||||
bindToggleCommand(
|
||||
ShellCommandIds::ToggleNativeBackdrop,
|
||||
bindings.getNativeBackdropVisible,
|
||||
bindings.setNativeBackdropVisible,
|
||||
{ XCUIEditorCommandAccelerator{
|
||||
static_cast<std::int32_t>(KeyCode::B),
|
||||
ctrlShift,
|
||||
true,
|
||||
false } });
|
||||
bindToggleCommand(
|
||||
ShellCommandIds::TogglePulseAccent,
|
||||
bindings.getPulseAccentEnabled,
|
||||
bindings.setPulseAccentEnabled,
|
||||
{ XCUIEditorCommandAccelerator{
|
||||
static_cast<std::int32_t>(KeyCode::P),
|
||||
ctrlShift,
|
||||
true,
|
||||
false } });
|
||||
bindToggleCommand(
|
||||
ShellCommandIds::ToggleNativeXCUIOverlay,
|
||||
bindings.getNativeXCUIOverlayVisible,
|
||||
bindings.setNativeXCUIOverlayVisible,
|
||||
{ XCUIEditorCommandAccelerator{
|
||||
static_cast<std::int32_t>(KeyCode::O),
|
||||
ctrlShift,
|
||||
true,
|
||||
false } });
|
||||
bindToggleCommand(
|
||||
ShellCommandIds::ToggleHostedPreviewHud,
|
||||
bindings.getHostedPreviewHudVisible,
|
||||
bindings.setHostedPreviewHudVisible,
|
||||
{ XCUIEditorCommandAccelerator{
|
||||
static_cast<std::int32_t>(KeyCode::H),
|
||||
ctrlShift,
|
||||
true,
|
||||
false } });
|
||||
bindToggleCommand(
|
||||
ShellCommandIds::ToggleNativeDemoPanelPreview,
|
||||
bindings.getNativeDemoPanelPreviewEnabled,
|
||||
bindings.setNativeDemoPanelPreviewEnabled,
|
||||
{ XCUIEditorCommandAccelerator{
|
||||
static_cast<std::int32_t>(KeyCode::One),
|
||||
ctrlAlt,
|
||||
true,
|
||||
false } },
|
||||
bindings.onHostedPreviewModeChanged);
|
||||
bindToggleCommand(
|
||||
ShellCommandIds::ToggleNativeLayoutLabPreview,
|
||||
bindings.getNativeLayoutLabPreviewEnabled,
|
||||
bindings.setNativeLayoutLabPreviewEnabled,
|
||||
{ XCUIEditorCommandAccelerator{
|
||||
static_cast<std::int32_t>(KeyCode::Two),
|
||||
ctrlAlt,
|
||||
true,
|
||||
false } },
|
||||
bindings.onHostedPreviewModeChanged);
|
||||
}
|
||||
|
||||
static void BeginHostedPreviewFrameLifecycle(
|
||||
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewQueue& previewQueue,
|
||||
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceRegistry& surfaceRegistry) {
|
||||
previewQueue.BeginFrame();
|
||||
surfaceRegistry.BeginFrame();
|
||||
}
|
||||
|
||||
static bool HasHostedPreviewTextureRegistration(
|
||||
const ::XCEngine::Editor::XCUIBackend::UITextureRegistration& registration) {
|
||||
return registration.texture.IsValid() ||
|
||||
registration.cpuHandle.ptr != 0u ||
|
||||
registration.gpuHandle.ptr != 0u;
|
||||
}
|
||||
|
||||
static bool HasHostedPreviewPublishedTexture(
|
||||
const ::XCEngine::Editor::XCUIBackend::UITextureRegistration& registration) {
|
||||
return registration.texture.IsValid();
|
||||
}
|
||||
|
||||
static NativeHostedPreviewConsumption ResolveNativeHostedPreviewConsumption(
|
||||
bool nativeHostedPreview,
|
||||
bool hasHostedSurfaceDescriptor,
|
||||
bool showHostedSurfaceImage,
|
||||
std::string_view pendingTitle = {},
|
||||
std::string_view pendingSubtitle = {}) {
|
||||
NativeHostedPreviewConsumption consumption = {};
|
||||
if (!nativeHostedPreview) {
|
||||
return consumption;
|
||||
}
|
||||
|
||||
consumption.queueRuntimeFrame = true;
|
||||
consumption.appendRuntimeDrawDataToShell = false;
|
||||
consumption.showSurfaceImage = showHostedSurfaceImage;
|
||||
consumption.drawRuntimeDebugRects = showHostedSurfaceImage;
|
||||
consumption.surfaceState = showHostedSurfaceImage
|
||||
? NativeHostedPreviewSurfaceState::Live
|
||||
: (hasHostedSurfaceDescriptor
|
||||
? NativeHostedPreviewSurfaceState::Warming
|
||||
: NativeHostedPreviewSurfaceState::AwaitingSubmit);
|
||||
if (!showHostedSurfaceImage) {
|
||||
consumption.placeholderTitle = pendingTitle;
|
||||
consumption.placeholderSubtitle = pendingSubtitle;
|
||||
}
|
||||
|
||||
return consumption;
|
||||
}
|
||||
|
||||
static std::string ComposeNativeHostedPreviewStatusLine(
|
||||
const NativeHostedPreviewConsumption& consumption,
|
||||
std::string_view status) {
|
||||
switch (consumption.surfaceState) {
|
||||
case NativeHostedPreviewSurfaceState::AwaitingSubmit:
|
||||
return std::string("Native surface awaiting submit | ") + std::string(status);
|
||||
case NativeHostedPreviewSurfaceState::Warming:
|
||||
return std::string("Native surface warming | ") + std::string(status);
|
||||
case NativeHostedPreviewSurfaceState::Live:
|
||||
return std::string("Native surface live | ") + std::string(status);
|
||||
case NativeHostedPreviewSurfaceState::Disabled:
|
||||
default:
|
||||
return std::string(status);
|
||||
}
|
||||
}
|
||||
|
||||
int Run(HINSTANCE instance, int nCmdShow);
|
||||
int Run(HINSTANCE hInstance, int nCmdShow);
|
||||
|
||||
private:
|
||||
struct HostedPreviewPanelDiagnostics {
|
||||
std::string debugName = {};
|
||||
std::string debugSource = {};
|
||||
bool visible = false;
|
||||
bool hostedPreviewEnabled = false;
|
||||
bool nativeRequested = false;
|
||||
bool nativePresenterBound = false;
|
||||
bool descriptorAvailable = false;
|
||||
bool surfaceImageAvailable = false;
|
||||
bool surfaceAllocated = false;
|
||||
bool surfaceReady = false;
|
||||
bool presentedThisFrame = false;
|
||||
bool queuedToNativePassThisFrame = false;
|
||||
std::uint32_t surfaceWidth = 0;
|
||||
std::uint32_t surfaceHeight = 0;
|
||||
float logicalWidth = 0.0f;
|
||||
float logicalHeight = 0.0f;
|
||||
std::size_t queuedFrameIndex = 0;
|
||||
std::size_t submittedDrawListCount = 0;
|
||||
std::size_t submittedCommandCount = 0;
|
||||
std::size_t flushedDrawListCount = 0;
|
||||
std::size_t flushedCommandCount = 0;
|
||||
struct TrackedFileState {
|
||||
std::filesystem::path path = {};
|
||||
std::filesystem::file_time_type writeTime = {};
|
||||
bool exists = false;
|
||||
};
|
||||
|
||||
struct HostedPreviewOffscreenSurface {
|
||||
std::string debugName = {};
|
||||
std::uint32_t width = 0;
|
||||
std::uint32_t height = 0;
|
||||
::XCEngine::RHI::RHITexture* colorTexture = nullptr;
|
||||
::XCEngine::RHI::RHIResourceView* colorView = nullptr;
|
||||
::XCEngine::Editor::XCUIBackend::UITextureRegistration textureRegistration = {};
|
||||
::XCEngine::RHI::ResourceStates colorState = ::XCEngine::RHI::ResourceStates::Common;
|
||||
static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
bool IsReady() const {
|
||||
return !debugName.empty() &&
|
||||
colorTexture != nullptr &&
|
||||
colorView != nullptr &&
|
||||
Application::HasHostedPreviewPublishedTexture(textureRegistration) &&
|
||||
width > 0u &&
|
||||
height > 0u;
|
||||
}
|
||||
};
|
||||
|
||||
static LRESULT CALLBACK StaticWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
|
||||
LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
bool CreateMainWindow(HINSTANCE instance, int nCmdShow);
|
||||
bool InitializeRenderer();
|
||||
void InitializeWindowCompositor();
|
||||
void InitializeNativeShell();
|
||||
void ShutdownWindowCompositor();
|
||||
void ShutdownRenderer();
|
||||
void DestroyHostedPreviewSurfaces();
|
||||
void SyncShellChromePanelStateFromPanels();
|
||||
void SyncHostedPreviewSurfaces();
|
||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> CreateHostedPreviewPresenter(
|
||||
bool nativePreview);
|
||||
void ConfigureHostedPreviewPresenters();
|
||||
const ShellPanelChromeState* TryGetShellPanelState(ShellPanelId panelId) const;
|
||||
bool IsShellViewToggleEnabled(ShellViewToggleId toggleId) const;
|
||||
void SetShellViewToggleEnabled(ShellViewToggleId toggleId, bool enabled);
|
||||
bool IsNativeHostedPreviewEnabled(ShellPanelId panelId) const;
|
||||
HostedPreviewPanelDiagnostics BuildHostedPreviewPanelDiagnostics(
|
||||
const char* debugName,
|
||||
const char* fallbackDebugSource,
|
||||
bool visible,
|
||||
bool hostedPreviewEnabled,
|
||||
bool nativeRequested,
|
||||
bool nativePresenterBound,
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& previewStats) const;
|
||||
HostedPreviewOffscreenSurface* FindHostedPreviewSurface(const std::string& debugName);
|
||||
const HostedPreviewOffscreenSurface* FindHostedPreviewSurface(const std::string& debugName) const;
|
||||
HostedPreviewOffscreenSurface& FindOrAddHostedPreviewSurface(const std::string& debugName);
|
||||
bool EnsureHostedPreviewSurface(
|
||||
HostedPreviewOffscreenSurface& previewSurface,
|
||||
std::uint32_t width,
|
||||
std::uint32_t height);
|
||||
bool RenderHostedPreviewOffscreenSurface(
|
||||
HostedPreviewOffscreenSurface& previewSurface,
|
||||
const ::XCEngine::Rendering::RenderContext& renderContext,
|
||||
const ::XCEngine::UI::UIDrawData& drawData);
|
||||
void ResetCompatibilityHostPanels();
|
||||
void ConfigureShellCommandRouter();
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta DispatchShellShortcuts(
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot& snapshot);
|
||||
bool IsNativeWindowHostEnabled() const;
|
||||
void InitializePanelsForActiveWindowHost();
|
||||
void InitializeCompatibilityHostPanels();
|
||||
void RenderCompatibilityHostUiFrame();
|
||||
::XCEngine::UI::UIDrawData BuildNativeShellDrawData(
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot& shellSnapshot,
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& shellFrameDelta);
|
||||
void FrameCompatibilityHost();
|
||||
void FrameNativeXCUIHost();
|
||||
void RenderShellChrome();
|
||||
void RenderHostedPreviewHud();
|
||||
void RenderQueuedHostedPreviews(
|
||||
const ::XCEngine::Rendering::RenderContext& renderContext,
|
||||
const ::XCEngine::Rendering::RenderSurface& surface);
|
||||
void Frame();
|
||||
bool Initialize(HINSTANCE hInstance, int nCmdShow);
|
||||
void Shutdown();
|
||||
void RenderFrame();
|
||||
void OnResize(UINT width, UINT height);
|
||||
bool LoadStructuredScreen(const char* triggerReason);
|
||||
void RefreshStructuredScreen();
|
||||
void RebuildTrackedFileStates();
|
||||
bool DetectTrackedFileChange() const;
|
||||
void AppendRuntimeOverlay(::XCEngine::UI::UIDrawData& drawData, float width, float height) const;
|
||||
static std::filesystem::path ResolveRepoRelativePath(const char* relativePath);
|
||||
|
||||
HWND m_hwnd = nullptr;
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer m_windowRenderer;
|
||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IWindowUICompositor> m_windowCompositor;
|
||||
std::unique_ptr<XCUIDemoPanel> m_demoPanel;
|
||||
std::unique_ptr<XCUILayoutLabPanel> m_layoutLabPanel;
|
||||
::XCEngine::Editor::XCUIBackend::XCUIWin32InputSource m_xcuiInputSource;
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridge m_shellInputBridge;
|
||||
::XCEngine::Editor::XCUIBackend::XCUIEditorCommandRouter m_shellCommandRouter;
|
||||
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewQueue m_hostedPreviewQueue;
|
||||
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceRegistry m_hostedPreviewSurfaceRegistry;
|
||||
::XCEngine::Editor::XCUIBackend::XCUIStandaloneTextAtlasProvider m_hostedPreviewTextAtlasProvider;
|
||||
::XCEngine::Editor::XCUIBackend::XCUIRHIRenderBackend m_hostedPreviewRenderBackend;
|
||||
ShellChromeState m_shellChromeState = {};
|
||||
std::vector<HostedPreviewOffscreenSurface> m_hostedPreviewSurfaces = {};
|
||||
WindowHostMode m_windowHostMode = WindowHostMode::NativeXCUI;
|
||||
::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost m_nativeDemoCanvasHost;
|
||||
::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost m_nativeLayoutCanvasHost;
|
||||
ShellPanelId m_nativeActivePanel = ShellPanelId::XCUIDemo;
|
||||
bool m_legacyHostDemoWindowVisible = false;
|
||||
::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_nativeOverlayRuntime;
|
||||
MainWindowNativeBackdropRenderer m_nativeBackdropRenderer;
|
||||
bool m_running = false;
|
||||
bool m_renderReady = false;
|
||||
HINSTANCE m_hInstance = nullptr;
|
||||
ATOM m_windowClassAtom = 0;
|
||||
NativeRenderer m_renderer;
|
||||
AutoScreenshotController m_autoScreenshot;
|
||||
::XCEngine::UI::Runtime::UIDocumentScreenHost m_documentHost;
|
||||
::XCEngine::UI::Runtime::UIScreenPlayer m_screenPlayer;
|
||||
::XCEngine::UI::Runtime::UIScreenAsset m_screenAsset = {};
|
||||
std::vector<TrackedFileState> m_trackedFiles = {};
|
||||
std::chrono::steady_clock::time_point m_startTime = {};
|
||||
std::chrono::steady_clock::time_point m_lastFrameTime = {};
|
||||
std::chrono::steady_clock::time_point m_lastReloadPollTime = {};
|
||||
std::uint64_t m_frameIndex = 0;
|
||||
bool m_useStructuredScreen = false;
|
||||
std::string m_runtimeStatus = {};
|
||||
std::string m_runtimeError = {};
|
||||
};
|
||||
|
||||
int RunNewEditor(HINSTANCE hInstance, int nCmdShow);
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
#include "Application.h"
|
||||
|
||||
#include "XCUIBackend/NativeWindowUICompositor.h"
|
||||
#include "panels/XCUIDemoPanel.h"
|
||||
#include "panels/XCUILayoutLabPanel.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
Application::Application() = default;
|
||||
Application::~Application() = default;
|
||||
|
||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter>
|
||||
Application::CreateHostedPreviewPresenter(bool nativePreview) {
|
||||
if (nativePreview) {
|
||||
return ::XCEngine::Editor::XCUIBackend::CreateQueuedNativeXCUIHostedPreviewPresenter(
|
||||
m_hostedPreviewQueue,
|
||||
m_hostedPreviewSurfaceRegistry);
|
||||
}
|
||||
|
||||
return ::XCEngine::Editor::XCUIBackend::CreateNullXCUIHostedPreviewPresenter();
|
||||
}
|
||||
|
||||
void Application::InitializePanelsForActiveWindowHost() {
|
||||
InitializeNativeShell();
|
||||
}
|
||||
|
||||
void Application::InitializeCompatibilityHostPanels() {
|
||||
}
|
||||
|
||||
void Application::ConfigureHostedPreviewPresenters() {
|
||||
}
|
||||
|
||||
void Application::ResetCompatibilityHostPanels() {
|
||||
m_demoPanel.reset();
|
||||
m_layoutLabPanel.reset();
|
||||
}
|
||||
|
||||
void Application::SyncShellChromePanelStateFromPanels() {
|
||||
}
|
||||
|
||||
void Application::ConfigureShellCommandRouter() {
|
||||
m_shellCommandRouter.Clear();
|
||||
|
||||
ShellCommandBindings bindings = {};
|
||||
bindings.getXCUIDemoPanelVisible = [this]() {
|
||||
return m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo);
|
||||
};
|
||||
bindings.setXCUIDemoPanelVisible = [this](bool visible) {
|
||||
m_shellChromeState.SetPanelVisible(ShellPanelId::XCUIDemo, visible);
|
||||
};
|
||||
bindings.getXCUILayoutLabPanelVisible = [this]() {
|
||||
return m_shellChromeState.IsPanelVisible(ShellPanelId::XCUILayoutLab);
|
||||
};
|
||||
bindings.setXCUILayoutLabPanelVisible = [this](bool visible) {
|
||||
m_shellChromeState.SetPanelVisible(ShellPanelId::XCUILayoutLab, visible);
|
||||
};
|
||||
bindings.getNativeBackdropVisible = [this]() {
|
||||
return IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop);
|
||||
};
|
||||
bindings.setNativeBackdropVisible = [this](bool visible) {
|
||||
SetShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop, visible);
|
||||
};
|
||||
bindings.getPulseAccentEnabled = [this]() {
|
||||
return IsShellViewToggleEnabled(ShellViewToggleId::PulseAccent);
|
||||
};
|
||||
bindings.setPulseAccentEnabled = [this](bool enabled) {
|
||||
SetShellViewToggleEnabled(ShellViewToggleId::PulseAccent, enabled);
|
||||
};
|
||||
bindings.getNativeXCUIOverlayVisible = [this]() {
|
||||
return IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay);
|
||||
};
|
||||
bindings.setNativeXCUIOverlayVisible = [this](bool visible) {
|
||||
SetShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay, visible);
|
||||
};
|
||||
bindings.getHostedPreviewHudVisible = [this]() {
|
||||
return IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud);
|
||||
};
|
||||
bindings.setHostedPreviewHudVisible = [this](bool visible) {
|
||||
SetShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud, visible);
|
||||
};
|
||||
bindings.getNativeDemoPanelPreviewEnabled = [this]() {
|
||||
return IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo);
|
||||
};
|
||||
bindings.setNativeDemoPanelPreviewEnabled = [this](bool enabled) {
|
||||
m_shellChromeState.SetHostedPreviewMode(
|
||||
ShellPanelId::XCUIDemo,
|
||||
enabled ? ShellHostedPreviewMode::NativeOffscreen : ShellHostedPreviewMode::HostedPresenter);
|
||||
};
|
||||
bindings.getNativeLayoutLabPreviewEnabled = [this]() {
|
||||
return IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab);
|
||||
};
|
||||
bindings.setNativeLayoutLabPreviewEnabled = [this](bool enabled) {
|
||||
m_shellChromeState.SetHostedPreviewMode(
|
||||
ShellPanelId::XCUILayoutLab,
|
||||
enabled ? ShellHostedPreviewMode::NativeOffscreen : ShellHostedPreviewMode::HostedPresenter);
|
||||
};
|
||||
|
||||
Application::RegisterShellViewCommands(m_shellCommandRouter, bindings);
|
||||
}
|
||||
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta
|
||||
Application::DispatchShellShortcuts(
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot& snapshot) {
|
||||
if (!m_shellInputBridge.HasBaseline()) {
|
||||
m_shellInputBridge.Prime(snapshot);
|
||||
}
|
||||
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta frameDelta =
|
||||
m_shellInputBridge.Translate(snapshot);
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot commandSnapshot =
|
||||
Application::BuildShellShortcutSnapshot(frameDelta);
|
||||
m_shellCommandRouter.InvokeMatchingShortcut({ &commandSnapshot });
|
||||
return frameDelta;
|
||||
}
|
||||
|
||||
void Application::InitializeWindowCompositor() {
|
||||
m_windowCompositor = ::XCEngine::Editor::XCUIBackend::CreateNativeWindowUICompositor();
|
||||
if (m_windowCompositor != nullptr) {
|
||||
m_windowCompositor->Initialize(m_hwnd, m_windowRenderer, {});
|
||||
}
|
||||
}
|
||||
|
||||
void Application::FrameCompatibilityHost() {
|
||||
m_xcuiInputSource.ClearFrameTransients();
|
||||
}
|
||||
|
||||
void Application::RenderCompatibilityHostUiFrame() {
|
||||
}
|
||||
|
||||
void Application::RenderShellChrome() {
|
||||
}
|
||||
|
||||
void Application::RenderHostedPreviewHud() {
|
||||
}
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
@@ -1,655 +0,0 @@
|
||||
#include "Application.h"
|
||||
|
||||
#include "XCUIBackend/LegacyImGuiHostInterop.h"
|
||||
#include "panels/XCUIDemoPanel.h"
|
||||
#include "panels/XCUILayoutLabPanel.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kLegacyHostClearColor[4] = { 0.08f, 0.09f, 0.11f, 1.0f };
|
||||
constexpr const char* kLegacyHostDemoWindowCommandId = "new_editor.view.legacy_host_demo";
|
||||
|
||||
std::uint64_t MakeLegacyHostFrameTimestampNanoseconds() {
|
||||
return static_cast<std::uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
.count());
|
||||
}
|
||||
|
||||
const char* GetHostedPreviewPathLabel(bool nativeRequested, bool nativePresenterBound) {
|
||||
if (nativeRequested && nativePresenterBound) {
|
||||
return "native queued offscreen surface";
|
||||
}
|
||||
if (nativeRequested) {
|
||||
return "native requested, hosted presenter bound";
|
||||
}
|
||||
if (nativePresenterBound) {
|
||||
return "hosted presenter requested, native presenter still bound";
|
||||
}
|
||||
return "hosted presenter";
|
||||
}
|
||||
|
||||
const char* GetHostedPreviewStateLabel(
|
||||
bool hostedPreviewEnabled,
|
||||
bool nativePresenterBound,
|
||||
bool presentedThisFrame,
|
||||
bool queuedToNativePassThisFrame,
|
||||
bool surfaceImageAvailable,
|
||||
bool surfaceAllocated,
|
||||
bool surfaceReady,
|
||||
bool descriptorAvailable) {
|
||||
if (!hostedPreviewEnabled) {
|
||||
return "disabled";
|
||||
}
|
||||
|
||||
if (nativePresenterBound) {
|
||||
if (surfaceImageAvailable && surfaceReady) {
|
||||
return "live";
|
||||
}
|
||||
if (queuedToNativePassThisFrame || surfaceAllocated || descriptorAvailable) {
|
||||
return "warming";
|
||||
}
|
||||
return "awaiting submit";
|
||||
}
|
||||
|
||||
if (presentedThisFrame) {
|
||||
return "live";
|
||||
}
|
||||
return "idle";
|
||||
}
|
||||
|
||||
void RegisterLegacyHostDemoWindowCommand(
|
||||
::XCEngine::Editor::XCUIBackend::XCUIEditorCommandRouter& router,
|
||||
const std::function<bool()>& getter,
|
||||
const std::function<void(bool)>& setter) {
|
||||
if (!getter || !setter) {
|
||||
return;
|
||||
}
|
||||
|
||||
using ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandAccelerator;
|
||||
using ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandDefinition;
|
||||
using ::XCEngine::Input::KeyCode;
|
||||
|
||||
XCUIEditorCommandDefinition definition = {};
|
||||
definition.commandId = kLegacyHostDemoWindowCommandId;
|
||||
definition.isEnabled = [getter, setter]() {
|
||||
return static_cast<bool>(getter) && static_cast<bool>(setter);
|
||||
};
|
||||
definition.invoke = [getter, setter]() {
|
||||
setter(!getter());
|
||||
};
|
||||
definition.accelerators = {
|
||||
XCUIEditorCommandAccelerator{
|
||||
static_cast<std::int32_t>(KeyCode::Three),
|
||||
{ false, true, false, false },
|
||||
true,
|
||||
false,
|
||||
},
|
||||
};
|
||||
router.RegisterCommand(definition);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Application::Application() = default;
|
||||
Application::~Application() = default;
|
||||
|
||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter>
|
||||
Application::CreateHostedPreviewPresenter(bool nativePreview) {
|
||||
if (nativePreview) {
|
||||
return ::XCEngine::Editor::XCUIBackend::CreateQueuedNativeXCUIHostedPreviewPresenter(
|
||||
m_hostedPreviewQueue,
|
||||
m_hostedPreviewSurfaceRegistry);
|
||||
}
|
||||
|
||||
return ::XCEngine::Editor::XCUIBackend::CreateLegacyImGuiHostedPreviewPresenter();
|
||||
}
|
||||
|
||||
void Application::InitializePanelsForActiveWindowHost() {
|
||||
if (IsNativeWindowHostEnabled()) {
|
||||
InitializeNativeShell();
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeCompatibilityHostPanels();
|
||||
ConfigureHostedPreviewPresenters();
|
||||
}
|
||||
|
||||
void Application::InitializeCompatibilityHostPanels() {
|
||||
m_demoPanel = std::make_unique<XCUIDemoPanel>(
|
||||
&m_xcuiInputSource,
|
||||
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)),
|
||||
::XCEngine::Editor::XCUIBackend::CreateLegacyImGuiPanelCanvasHost());
|
||||
m_layoutLabPanel = std::make_unique<XCUILayoutLabPanel>(
|
||||
&m_xcuiInputSource,
|
||||
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)),
|
||||
::XCEngine::Editor::XCUIBackend::CreateLegacyImGuiPanelCanvasHost());
|
||||
}
|
||||
|
||||
void Application::ConfigureHostedPreviewPresenters() {
|
||||
const ShellPanelChromeState* demoState = TryGetShellPanelState(ShellPanelId::XCUIDemo);
|
||||
if (m_demoPanel != nullptr) {
|
||||
m_demoPanel->SetVisible(demoState != nullptr && demoState->visible);
|
||||
m_demoPanel->SetHostedPreviewEnabled(demoState == nullptr || demoState->hostedPreviewEnabled);
|
||||
m_demoPanel->SetHostedPreviewPresenter(CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)));
|
||||
}
|
||||
|
||||
const ShellPanelChromeState* layoutLabState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab);
|
||||
if (m_layoutLabPanel != nullptr) {
|
||||
m_layoutLabPanel->SetVisible(layoutLabState != nullptr && layoutLabState->visible);
|
||||
m_layoutLabPanel->SetHostedPreviewEnabled(layoutLabState == nullptr || layoutLabState->hostedPreviewEnabled);
|
||||
m_layoutLabPanel->SetHostedPreviewPresenter(
|
||||
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)));
|
||||
}
|
||||
}
|
||||
|
||||
void Application::ResetCompatibilityHostPanels() {
|
||||
m_demoPanel.reset();
|
||||
m_layoutLabPanel.reset();
|
||||
}
|
||||
|
||||
void Application::SyncShellChromePanelStateFromPanels() {
|
||||
m_shellChromeState.SetPanelVisible(
|
||||
ShellPanelId::XCUIDemo,
|
||||
m_demoPanel != nullptr && m_demoPanel->IsVisible());
|
||||
m_shellChromeState.SetPanelVisible(
|
||||
ShellPanelId::XCUILayoutLab,
|
||||
m_layoutLabPanel != nullptr && m_layoutLabPanel->IsVisible());
|
||||
}
|
||||
|
||||
void Application::ConfigureShellCommandRouter() {
|
||||
m_shellCommandRouter.Clear();
|
||||
|
||||
ShellCommandBindings bindings = {};
|
||||
bindings.getXCUIDemoPanelVisible = [this]() {
|
||||
return m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo);
|
||||
};
|
||||
bindings.setXCUIDemoPanelVisible = [this](bool visible) {
|
||||
m_shellChromeState.SetPanelVisible(ShellPanelId::XCUIDemo, visible);
|
||||
if (m_demoPanel != nullptr) {
|
||||
m_demoPanel->SetVisible(visible);
|
||||
}
|
||||
};
|
||||
bindings.getXCUILayoutLabPanelVisible = [this]() {
|
||||
return m_shellChromeState.IsPanelVisible(ShellPanelId::XCUILayoutLab);
|
||||
};
|
||||
bindings.setXCUILayoutLabPanelVisible = [this](bool visible) {
|
||||
m_shellChromeState.SetPanelVisible(ShellPanelId::XCUILayoutLab, visible);
|
||||
if (m_layoutLabPanel != nullptr) {
|
||||
m_layoutLabPanel->SetVisible(visible);
|
||||
}
|
||||
};
|
||||
bindings.getNativeBackdropVisible = [this]() {
|
||||
return IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop);
|
||||
};
|
||||
bindings.setNativeBackdropVisible = [this](bool visible) {
|
||||
SetShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop, visible);
|
||||
};
|
||||
bindings.getPulseAccentEnabled = [this]() {
|
||||
return IsShellViewToggleEnabled(ShellViewToggleId::PulseAccent);
|
||||
};
|
||||
bindings.setPulseAccentEnabled = [this](bool enabled) {
|
||||
SetShellViewToggleEnabled(ShellViewToggleId::PulseAccent, enabled);
|
||||
};
|
||||
bindings.getNativeXCUIOverlayVisible = [this]() {
|
||||
return IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay);
|
||||
};
|
||||
bindings.setNativeXCUIOverlayVisible = [this](bool visible) {
|
||||
SetShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay, visible);
|
||||
};
|
||||
bindings.getHostedPreviewHudVisible = [this]() {
|
||||
return IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud);
|
||||
};
|
||||
bindings.setHostedPreviewHudVisible = [this](bool visible) {
|
||||
SetShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud, visible);
|
||||
};
|
||||
bindings.getNativeDemoPanelPreviewEnabled = [this]() {
|
||||
return IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo);
|
||||
};
|
||||
bindings.setNativeDemoPanelPreviewEnabled = [this](bool enabled) {
|
||||
m_shellChromeState.SetHostedPreviewMode(
|
||||
ShellPanelId::XCUIDemo,
|
||||
enabled ? ShellHostedPreviewMode::NativeOffscreen : ShellHostedPreviewMode::HostedPresenter);
|
||||
};
|
||||
bindings.getNativeLayoutLabPreviewEnabled = [this]() {
|
||||
return IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab);
|
||||
};
|
||||
bindings.setNativeLayoutLabPreviewEnabled = [this](bool enabled) {
|
||||
m_shellChromeState.SetHostedPreviewMode(
|
||||
ShellPanelId::XCUILayoutLab,
|
||||
enabled ? ShellHostedPreviewMode::NativeOffscreen : ShellHostedPreviewMode::HostedPresenter);
|
||||
};
|
||||
bindings.onHostedPreviewModeChanged = [this]() {
|
||||
if (IsNativeWindowHostEnabled()) {
|
||||
InitializeNativeShell();
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigureHostedPreviewPresenters();
|
||||
};
|
||||
|
||||
Application::RegisterShellViewCommands(m_shellCommandRouter, bindings);
|
||||
RegisterLegacyHostDemoWindowCommand(
|
||||
m_shellCommandRouter,
|
||||
[this]() { return m_legacyHostDemoWindowVisible; },
|
||||
[this](bool visible) { m_legacyHostDemoWindowVisible = visible; });
|
||||
}
|
||||
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta
|
||||
Application::DispatchShellShortcuts(
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot& snapshot) {
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot shellSnapshot = snapshot;
|
||||
if (!IsNativeWindowHostEnabled()) {
|
||||
::XCEngine::Editor::XCUIBackend::ApplyLegacyImGuiHostInputCapture(shellSnapshot);
|
||||
}
|
||||
|
||||
if (!m_shellInputBridge.HasBaseline()) {
|
||||
m_shellInputBridge.Prime(shellSnapshot);
|
||||
}
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta frameDelta =
|
||||
m_shellInputBridge.Translate(shellSnapshot);
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot commandSnapshot =
|
||||
Application::BuildShellShortcutSnapshot(frameDelta);
|
||||
m_shellCommandRouter.InvokeMatchingShortcut({ &commandSnapshot });
|
||||
return frameDelta;
|
||||
}
|
||||
|
||||
void Application::InitializeWindowCompositor() {
|
||||
m_windowCompositor = IsNativeWindowHostEnabled()
|
||||
? ::XCEngine::Editor::XCUIBackend::CreateNativeWindowUICompositor()
|
||||
: ::XCEngine::Editor::XCUIBackend::CreateLegacyImGuiWindowUICompositor();
|
||||
if (m_windowCompositor != nullptr) {
|
||||
m_windowCompositor->Initialize(
|
||||
m_hwnd,
|
||||
m_windowRenderer,
|
||||
[]() { (void)::XCEngine::Editor::XCUIBackend::ConfigureLegacyImGuiHostFonts(); });
|
||||
}
|
||||
}
|
||||
|
||||
void Application::FrameCompatibilityHost() {
|
||||
Application::BeginHostedPreviewFrameLifecycle(
|
||||
m_hostedPreviewQueue,
|
||||
m_hostedPreviewSurfaceRegistry);
|
||||
SyncHostedPreviewSurfaces();
|
||||
if (m_windowCompositor == nullptr) {
|
||||
m_xcuiInputSource.ClearFrameTransients();
|
||||
return;
|
||||
}
|
||||
|
||||
m_windowCompositor->RenderFrame(
|
||||
kLegacyHostClearColor,
|
||||
[this]() { RenderCompatibilityHostUiFrame(); },
|
||||
[this](
|
||||
const ::XCEngine::Rendering::RenderContext& renderContext,
|
||||
const ::XCEngine::Rendering::RenderSurface& surface) {
|
||||
RenderQueuedHostedPreviews(renderContext, surface);
|
||||
|
||||
if (!IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop) &&
|
||||
!IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay)) {
|
||||
return;
|
||||
}
|
||||
|
||||
MainWindowNativeBackdropRenderer::FrameState frameState = {};
|
||||
frameState.elapsedSeconds = static_cast<float>(
|
||||
std::chrono::duration<double>(std::chrono::steady_clock::now() - m_startTime).count());
|
||||
frameState.pulseAccent = IsShellViewToggleEnabled(ShellViewToggleId::PulseAccent);
|
||||
frameState.drawBackdrop = IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop);
|
||||
if (IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay)) {
|
||||
const float width = static_cast<float>(surface.GetWidth());
|
||||
const float height = static_cast<float>(surface.GetHeight());
|
||||
const float horizontalMargin = (std::min)(width * 0.14f, 128.0f);
|
||||
const float topMargin = (std::min)(height * 0.15f, 132.0f);
|
||||
const float bottomMargin = (std::min)(height * 0.12f, 96.0f);
|
||||
|
||||
::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState overlayInput = {};
|
||||
overlayInput.canvasRect = ::XCEngine::UI::UIRect(
|
||||
horizontalMargin,
|
||||
topMargin,
|
||||
(std::max)(0.0f, width - horizontalMargin * 2.0f),
|
||||
(std::max)(0.0f, height - topMargin - bottomMargin));
|
||||
overlayInput.pointerPosition = m_xcuiInputSource.GetPointerPosition();
|
||||
overlayInput.pointerInside =
|
||||
overlayInput.pointerPosition.x >= overlayInput.canvasRect.x &&
|
||||
overlayInput.pointerPosition.y >= overlayInput.canvasRect.y &&
|
||||
overlayInput.pointerPosition.x <= overlayInput.canvasRect.x + overlayInput.canvasRect.width &&
|
||||
overlayInput.pointerPosition.y <= overlayInput.canvasRect.y + overlayInput.canvasRect.height;
|
||||
const auto& overlayFrame = m_nativeOverlayRuntime.Update(overlayInput);
|
||||
frameState.overlayDrawData = &overlayFrame.drawData;
|
||||
}
|
||||
m_nativeBackdropRenderer.Render(renderContext, surface, frameState);
|
||||
});
|
||||
|
||||
m_xcuiInputSource.ClearFrameTransients();
|
||||
}
|
||||
|
||||
void Application::RenderCompatibilityHostUiFrame() {
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions options = {};
|
||||
options.timestampNanoseconds = MakeLegacyHostFrameTimestampNanoseconds();
|
||||
options.windowFocused = m_xcuiInputSource.IsWindowFocused();
|
||||
const auto shellSnapshot = m_xcuiInputSource.CaptureSnapshot(options);
|
||||
DispatchShellShortcuts(shellSnapshot);
|
||||
|
||||
RenderShellChrome();
|
||||
if (m_demoPanel != nullptr) {
|
||||
m_demoPanel->RenderIfVisible();
|
||||
}
|
||||
if (m_layoutLabPanel != nullptr) {
|
||||
m_layoutLabPanel->RenderIfVisible();
|
||||
}
|
||||
|
||||
bool showLegacyHostDemoWindow = m_legacyHostDemoWindowVisible;
|
||||
if (::XCEngine::Editor::XCUIBackend::RenderLegacyImGuiDemoWindow(showLegacyHostDemoWindow)) {
|
||||
m_legacyHostDemoWindowVisible = showLegacyHostDemoWindow;
|
||||
}
|
||||
|
||||
SyncShellChromePanelStateFromPanels();
|
||||
SyncHostedPreviewSurfaces();
|
||||
}
|
||||
|
||||
void Application::RenderShellChrome() {
|
||||
SyncShellChromePanelStateFromPanels();
|
||||
|
||||
ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
if (viewport == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGuiWindowFlags windowFlags =
|
||||
ImGuiWindowFlags_NoDocking |
|
||||
ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
ImGuiWindowFlags_NoNavFocus |
|
||||
ImGuiWindowFlags_MenuBar;
|
||||
|
||||
ImGui::SetNextWindowPos(viewport->WorkPos);
|
||||
ImGui::SetNextWindowSize(viewport->WorkSize);
|
||||
ImGui::SetNextWindowViewport(viewport->ID);
|
||||
ImGui::SetNextWindowBgAlpha(0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
const bool opened = ImGui::Begin("XCNewEditorShell", nullptr, windowFlags);
|
||||
ImGui::PopStyleVar(3);
|
||||
|
||||
if (opened) {
|
||||
if (ImGui::BeginMenuBar()) {
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
const auto drawCommandMenuItem =
|
||||
[this](const char* label, const char* shortcut, bool selected, const char* commandId) {
|
||||
const bool enabled = m_shellCommandRouter.IsCommandEnabled(commandId);
|
||||
if (ImGui::MenuItem(label, shortcut, selected, enabled)) {
|
||||
m_shellCommandRouter.InvokeCommand(commandId);
|
||||
}
|
||||
};
|
||||
|
||||
drawCommandMenuItem(
|
||||
"XCUI Demo",
|
||||
"Ctrl+1",
|
||||
TryGetShellPanelState(ShellPanelId::XCUIDemo) != nullptr &&
|
||||
TryGetShellPanelState(ShellPanelId::XCUIDemo)->visible,
|
||||
ShellCommandIds::ToggleXCUIDemoPanel);
|
||||
drawCommandMenuItem(
|
||||
"XCUI Layout Lab",
|
||||
"Ctrl+2",
|
||||
TryGetShellPanelState(ShellPanelId::XCUILayoutLab) != nullptr &&
|
||||
TryGetShellPanelState(ShellPanelId::XCUILayoutLab)->visible,
|
||||
ShellCommandIds::ToggleXCUILayoutLabPanel);
|
||||
drawCommandMenuItem(
|
||||
"Legacy Host Demo",
|
||||
"Ctrl+3",
|
||||
m_legacyHostDemoWindowVisible,
|
||||
kLegacyHostDemoWindowCommandId);
|
||||
ImGui::Separator();
|
||||
drawCommandMenuItem(
|
||||
"Native Backdrop",
|
||||
"Ctrl+Shift+B",
|
||||
IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop),
|
||||
ShellCommandIds::ToggleNativeBackdrop);
|
||||
drawCommandMenuItem(
|
||||
"Pulse Accent",
|
||||
"Ctrl+Shift+P",
|
||||
IsShellViewToggleEnabled(ShellViewToggleId::PulseAccent),
|
||||
ShellCommandIds::TogglePulseAccent);
|
||||
drawCommandMenuItem(
|
||||
"Native XCUI Overlay",
|
||||
"Ctrl+Shift+O",
|
||||
IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay),
|
||||
ShellCommandIds::ToggleNativeXCUIOverlay);
|
||||
drawCommandMenuItem(
|
||||
"Hosted Preview HUD",
|
||||
"Ctrl+Shift+H",
|
||||
IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud),
|
||||
ShellCommandIds::ToggleHostedPreviewHud);
|
||||
drawCommandMenuItem(
|
||||
"Native Demo Panel Preview",
|
||||
"Ctrl+Alt+1",
|
||||
IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo),
|
||||
ShellCommandIds::ToggleNativeDemoPanelPreview);
|
||||
drawCommandMenuItem(
|
||||
"Native Layout Lab Preview",
|
||||
"Ctrl+Alt+2",
|
||||
IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab),
|
||||
ShellCommandIds::ToggleNativeLayoutLabPreview);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::SeparatorText("XCUI Sandbox");
|
||||
const MainWindowNativeBackdropRenderer::OverlayStats& nativeOverlayStats =
|
||||
m_nativeBackdropRenderer.GetLastOverlayStats();
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameStats& overlayFrameStats =
|
||||
m_nativeOverlayRuntime.GetFrameResult().stats;
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewDrainStats& hostedPreviewStats =
|
||||
m_hostedPreviewQueue.GetLastDrainStats();
|
||||
if (IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay)) {
|
||||
ImGui::TextDisabled(
|
||||
"Native XCUI overlay: %s | runtime %zu cmds (%zu fill, %zu outline, %zu text, %zu image, clips %zu/%zu)",
|
||||
overlayFrameStats.nativeOverlayReady ? "preflight OK" : "preflight issues",
|
||||
overlayFrameStats.commandCount,
|
||||
overlayFrameStats.filledRectCommandCount,
|
||||
overlayFrameStats.rectOutlineCommandCount,
|
||||
overlayFrameStats.textCommandCount,
|
||||
overlayFrameStats.imageCommandCount,
|
||||
overlayFrameStats.clipPushCommandCount,
|
||||
overlayFrameStats.clipPopCommandCount);
|
||||
ImGui::TextDisabled(
|
||||
"%s | supported %zu | unsupported %zu | prev native pass %zu cmds, %zu rendered, %zu skipped",
|
||||
overlayFrameStats.nativeOverlayStatusMessage.empty()
|
||||
? "Overlay diagnostics unavailable"
|
||||
: overlayFrameStats.nativeOverlayStatusMessage.c_str(),
|
||||
overlayFrameStats.nativeSupportedCommandCount,
|
||||
overlayFrameStats.nativeUnsupportedCommandCount,
|
||||
nativeOverlayStats.commandCount,
|
||||
nativeOverlayStats.renderedCommandCount,
|
||||
nativeOverlayStats.skippedCommandCount);
|
||||
} else {
|
||||
ImGui::TextDisabled(
|
||||
IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop)
|
||||
? "Transition backend + runtime diagnostics + native backbuffer pass"
|
||||
: "Transition backend + runtime diagnostics");
|
||||
}
|
||||
ImGui::TextDisabled(
|
||||
"Hosted preview queue: %zu frames | queued %zu cmds | rendered %zu cmds | skipped %zu cmds",
|
||||
hostedPreviewStats.queuedFrameCount,
|
||||
hostedPreviewStats.queuedCommandCount,
|
||||
hostedPreviewStats.renderedCommandCount,
|
||||
hostedPreviewStats.skippedCommandCount);
|
||||
std::size_t allocatedSurfaceCount = 0u;
|
||||
std::size_t readySurfaceCount = 0u;
|
||||
for (const HostedPreviewOffscreenSurface& previewSurface : m_hostedPreviewSurfaces) {
|
||||
if (previewSurface.colorTexture != nullptr || previewSurface.colorView != nullptr) {
|
||||
++allocatedSurfaceCount;
|
||||
}
|
||||
if (previewSurface.IsReady()) {
|
||||
++readySurfaceCount;
|
||||
}
|
||||
}
|
||||
ImGui::TextDisabled(
|
||||
"Hosted surfaces: %zu registry entries | %zu allocated | %zu ready",
|
||||
m_hostedPreviewSurfaceRegistry.GetDescriptors().size(),
|
||||
allocatedSurfaceCount,
|
||||
readySurfaceCount);
|
||||
if (m_demoPanel != nullptr) {
|
||||
ImGui::TextDisabled(
|
||||
"XCUI Demo preview: %s",
|
||||
IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)
|
||||
? "native offscreen preview surface"
|
||||
: "hosted presenter");
|
||||
}
|
||||
if (m_layoutLabPanel != nullptr) {
|
||||
ImGui::TextDisabled(
|
||||
"Layout Lab preview: %s",
|
||||
IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)
|
||||
? "native offscreen preview surface"
|
||||
: "hosted presenter");
|
||||
}
|
||||
ImGui::EndMenuBar();
|
||||
}
|
||||
|
||||
ImGui::DockSpace(
|
||||
ImGui::GetID("XCNewEditorDockSpace"),
|
||||
ImVec2(0.0f, 0.0f),
|
||||
ImGuiDockNodeFlags_PassthruCentralNode);
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
|
||||
if (IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud)) {
|
||||
RenderHostedPreviewHud();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::RenderHostedPreviewHud() {
|
||||
ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
if (viewport == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ShellPanelChromeState* demoState = TryGetShellPanelState(ShellPanelId::XCUIDemo);
|
||||
const ShellPanelChromeState* layoutLabState = TryGetShellPanelState(ShellPanelId::XCUILayoutLab);
|
||||
const HostedPreviewPanelDiagnostics demoDiagnostics = BuildHostedPreviewPanelDiagnostics(
|
||||
demoState != nullptr ? demoState->previewDebugName.data() : "XCUI Demo",
|
||||
demoState != nullptr ? demoState->previewDebugSource.data() : "new_editor.panels.xcui_demo",
|
||||
demoState != nullptr && demoState->visible,
|
||||
demoState != nullptr && demoState->hostedPreviewEnabled,
|
||||
IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo),
|
||||
m_demoPanel != nullptr && m_demoPanel->IsUsingNativeHostedPreview(),
|
||||
m_demoPanel != nullptr
|
||||
? m_demoPanel->GetLastPreviewStats()
|
||||
: ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats{});
|
||||
const HostedPreviewPanelDiagnostics layoutLabDiagnostics = BuildHostedPreviewPanelDiagnostics(
|
||||
layoutLabState != nullptr ? layoutLabState->previewDebugName.data() : "XCUI Layout Lab",
|
||||
layoutLabState != nullptr ? layoutLabState->previewDebugSource.data() : "new_editor.panels.xcui_layout_lab",
|
||||
layoutLabState != nullptr && layoutLabState->visible,
|
||||
layoutLabState != nullptr && layoutLabState->hostedPreviewEnabled,
|
||||
IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab),
|
||||
m_layoutLabPanel != nullptr && m_layoutLabPanel->IsUsingNativeHostedPreview(),
|
||||
m_layoutLabPanel != nullptr
|
||||
? m_layoutLabPanel->GetLastPreviewStats()
|
||||
: ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats{});
|
||||
|
||||
std::size_t allocatedSurfaceCount = 0u;
|
||||
std::size_t readySurfaceCount = 0u;
|
||||
for (const HostedPreviewOffscreenSurface& previewSurface : m_hostedPreviewSurfaces) {
|
||||
if (previewSurface.colorTexture != nullptr || previewSurface.colorView != nullptr) {
|
||||
++allocatedSurfaceCount;
|
||||
}
|
||||
if (previewSurface.IsReady()) {
|
||||
++readySurfaceCount;
|
||||
}
|
||||
}
|
||||
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewDrainStats& drainStats =
|
||||
m_hostedPreviewQueue.GetLastDrainStats();
|
||||
|
||||
ImGuiWindowFlags windowFlags =
|
||||
ImGuiWindowFlags_NoDocking |
|
||||
ImGuiWindowFlags_NoSavedSettings |
|
||||
ImGuiWindowFlags_AlwaysAutoResize |
|
||||
ImGuiWindowFlags_NoFocusOnAppearing |
|
||||
ImGuiWindowFlags_NoNav;
|
||||
|
||||
ImGui::SetNextWindowViewport(viewport->ID);
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(viewport->WorkPos.x + viewport->WorkSize.x - 18.0f, viewport->WorkPos.y + 42.0f),
|
||||
ImGuiCond_Always,
|
||||
ImVec2(1.0f, 0.0f));
|
||||
ImGui::SetNextWindowBgAlpha(0.9f);
|
||||
if (!ImGui::Begin("XCUI Hosted Preview HUD", nullptr, windowFlags)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::TextUnformatted("XCUI Hosted Preview");
|
||||
ImGui::Text(
|
||||
"Registry %zu | surfaces %zu/%zu ready | last native drain %zu rendered, %zu skipped",
|
||||
m_hostedPreviewSurfaceRegistry.GetDescriptors().size(),
|
||||
readySurfaceCount,
|
||||
allocatedSurfaceCount,
|
||||
drainStats.renderedFrameCount,
|
||||
drainStats.skippedFrameCount);
|
||||
ImGui::Separator();
|
||||
|
||||
const auto drawPanelRow = [](const HostedPreviewPanelDiagnostics& diagnostics) {
|
||||
const char* const pathLabel =
|
||||
GetHostedPreviewPathLabel(diagnostics.nativeRequested, diagnostics.nativePresenterBound);
|
||||
const char* const stateLabel = GetHostedPreviewStateLabel(
|
||||
diagnostics.hostedPreviewEnabled,
|
||||
diagnostics.nativePresenterBound,
|
||||
diagnostics.presentedThisFrame,
|
||||
diagnostics.queuedToNativePassThisFrame,
|
||||
diagnostics.surfaceImageAvailable,
|
||||
diagnostics.surfaceAllocated,
|
||||
diagnostics.surfaceReady,
|
||||
diagnostics.descriptorAvailable);
|
||||
|
||||
ImGui::Text(
|
||||
"%s [%s] %s",
|
||||
diagnostics.debugName.c_str(),
|
||||
diagnostics.visible ? "visible" : "hidden",
|
||||
stateLabel);
|
||||
ImGui::TextDisabled("%s", pathLabel);
|
||||
ImGui::Text(
|
||||
"source %s | submit %zu lists / %zu cmds | flush %zu lists / %zu cmds",
|
||||
diagnostics.debugSource.empty() ? "n/a" : diagnostics.debugSource.c_str(),
|
||||
diagnostics.submittedDrawListCount,
|
||||
diagnostics.submittedCommandCount,
|
||||
diagnostics.flushedDrawListCount,
|
||||
diagnostics.flushedCommandCount);
|
||||
if (diagnostics.nativePresenterBound) {
|
||||
ImGui::Text(
|
||||
"surface %ux%u | logical %.0f x %.0f | descriptor %s | image %s | submit->native %s",
|
||||
diagnostics.surfaceWidth,
|
||||
diagnostics.surfaceHeight,
|
||||
diagnostics.logicalWidth,
|
||||
diagnostics.logicalHeight,
|
||||
diagnostics.descriptorAvailable ? "yes" : "no",
|
||||
diagnostics.surfaceImageAvailable ? "yes" : "no",
|
||||
diagnostics.queuedToNativePassThisFrame ? "yes" : "no");
|
||||
} else {
|
||||
ImGui::Text(
|
||||
"legacy present %s | cached native surface %s",
|
||||
diagnostics.presentedThisFrame ? "yes" : "no",
|
||||
(diagnostics.surfaceAllocated || diagnostics.surfaceImageAvailable) ? "retained" : "none");
|
||||
}
|
||||
};
|
||||
|
||||
drawPanelRow(demoDiagnostics);
|
||||
ImGui::Separator();
|
||||
drawPanelRow(layoutLabDiagnostics);
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
148
new_editor/src/AutoScreenshot.cpp
Normal file
148
new_editor/src/AutoScreenshot.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
#include "AutoScreenshot.h"
|
||||
|
||||
#include "NativeRenderer.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <sstream>
|
||||
#include <system_error>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
void AutoScreenshotController::Initialize(const std::filesystem::path& captureRoot) {
|
||||
m_captureRoot = captureRoot.lexically_normal();
|
||||
m_historyRoot = (m_captureRoot / "history").lexically_normal();
|
||||
m_latestCapturePath = (m_captureRoot / "latest.png").lexically_normal();
|
||||
m_captureCount = 0;
|
||||
m_capturePending = false;
|
||||
m_pendingReason.clear();
|
||||
m_lastCaptureSummary.clear();
|
||||
m_lastCaptureError.clear();
|
||||
}
|
||||
|
||||
void AutoScreenshotController::Shutdown() {
|
||||
m_capturePending = false;
|
||||
m_pendingReason.clear();
|
||||
}
|
||||
|
||||
void AutoScreenshotController::RequestCapture(std::string reason) {
|
||||
m_pendingReason = reason.empty() ? "capture" : std::move(reason);
|
||||
m_capturePending = true;
|
||||
}
|
||||
|
||||
void AutoScreenshotController::CaptureIfRequested(
|
||||
NativeRenderer& renderer,
|
||||
const ::XCEngine::UI::UIDrawData& drawData,
|
||||
unsigned int width,
|
||||
unsigned int height,
|
||||
bool framePresented) {
|
||||
if (!m_capturePending || !framePresented || drawData.Empty() || width == 0u || height == 0u) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::error_code errorCode = {};
|
||||
std::filesystem::create_directories(m_historyRoot, errorCode);
|
||||
if (errorCode) {
|
||||
m_lastCaptureError = "Failed to create screenshot directory: " + m_historyRoot.string();
|
||||
m_lastCaptureSummary = "AutoShot failed";
|
||||
m_capturePending = false;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string captureError = {};
|
||||
const std::filesystem::path historyPath = BuildHistoryCapturePath(m_pendingReason);
|
||||
if (!renderer.CaptureToPng(drawData, width, height, m_latestCapturePath, captureError) ||
|
||||
!renderer.CaptureToPng(drawData, width, height, historyPath, captureError)) {
|
||||
m_lastCaptureError = std::move(captureError);
|
||||
m_lastCaptureSummary = "AutoShot failed";
|
||||
m_capturePending = false;
|
||||
return;
|
||||
}
|
||||
|
||||
++m_captureCount;
|
||||
m_lastCaptureError.clear();
|
||||
m_lastCaptureSummary =
|
||||
"Shot: latest.png | " + historyPath.filename().string();
|
||||
m_capturePending = false;
|
||||
m_pendingReason.clear();
|
||||
}
|
||||
|
||||
bool AutoScreenshotController::HasPendingCapture() const {
|
||||
return m_capturePending;
|
||||
}
|
||||
|
||||
const std::filesystem::path& AutoScreenshotController::GetLatestCapturePath() const {
|
||||
return m_latestCapturePath;
|
||||
}
|
||||
|
||||
const std::string& AutoScreenshotController::GetLastCaptureSummary() const {
|
||||
return m_lastCaptureSummary;
|
||||
}
|
||||
|
||||
const std::string& AutoScreenshotController::GetLastCaptureError() const {
|
||||
return m_lastCaptureError;
|
||||
}
|
||||
|
||||
std::filesystem::path AutoScreenshotController::BuildHistoryCapturePath(std::string_view reason) const {
|
||||
std::ostringstream filename;
|
||||
filename << BuildTimestampString()
|
||||
<< '_'
|
||||
<< (m_captureCount + 1u)
|
||||
<< '_'
|
||||
<< SanitizeReason(reason)
|
||||
<< ".png";
|
||||
return (m_historyRoot / filename.str()).lexically_normal();
|
||||
}
|
||||
|
||||
std::string AutoScreenshotController::BuildTimestampString() {
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const std::time_t currentTime = std::chrono::system_clock::to_time_t(now);
|
||||
std::tm localTime = {};
|
||||
localtime_s(&localTime, ¤tTime);
|
||||
|
||||
char buffer[32] = {};
|
||||
std::snprintf(
|
||||
buffer,
|
||||
sizeof(buffer),
|
||||
"%04d%02d%02d_%02d%02d%02d",
|
||||
localTime.tm_year + 1900,
|
||||
localTime.tm_mon + 1,
|
||||
localTime.tm_mday,
|
||||
localTime.tm_hour,
|
||||
localTime.tm_min,
|
||||
localTime.tm_sec);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string AutoScreenshotController::SanitizeReason(std::string_view reason) {
|
||||
std::string sanitized = {};
|
||||
sanitized.reserve(reason.size());
|
||||
|
||||
bool lastWasSeparator = false;
|
||||
for (const unsigned char value : reason) {
|
||||
if (std::isalnum(value)) {
|
||||
sanitized.push_back(static_cast<char>(std::tolower(value)));
|
||||
lastWasSeparator = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!lastWasSeparator) {
|
||||
sanitized.push_back('_');
|
||||
lastWasSeparator = true;
|
||||
}
|
||||
}
|
||||
|
||||
while (!sanitized.empty() && sanitized.front() == '_') {
|
||||
sanitized.erase(sanitized.begin());
|
||||
}
|
||||
while (!sanitized.empty() && sanitized.back() == '_') {
|
||||
sanitized.pop_back();
|
||||
}
|
||||
|
||||
return sanitized.empty() ? "capture" : sanitized;
|
||||
}
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
54
new_editor/src/AutoScreenshot.h
Normal file
54
new_editor/src/AutoScreenshot.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
class NativeRenderer;
|
||||
|
||||
class AutoScreenshotController {
|
||||
public:
|
||||
void Initialize(const std::filesystem::path& captureRoot);
|
||||
void Shutdown();
|
||||
|
||||
void RequestCapture(std::string reason);
|
||||
void CaptureIfRequested(
|
||||
NativeRenderer& renderer,
|
||||
const ::XCEngine::UI::UIDrawData& drawData,
|
||||
unsigned int width,
|
||||
unsigned int height,
|
||||
bool framePresented);
|
||||
|
||||
bool HasPendingCapture() const;
|
||||
const std::filesystem::path& GetLatestCapturePath() const;
|
||||
const std::string& GetLastCaptureSummary() const;
|
||||
const std::string& GetLastCaptureError() const;
|
||||
|
||||
private:
|
||||
std::filesystem::path BuildHistoryCapturePath(std::string_view reason) const;
|
||||
|
||||
static std::string BuildTimestampString();
|
||||
static std::string SanitizeReason(std::string_view reason);
|
||||
|
||||
std::filesystem::path m_captureRoot = {};
|
||||
std::filesystem::path m_historyRoot = {};
|
||||
std::filesystem::path m_latestCapturePath = {};
|
||||
std::string m_pendingReason = {};
|
||||
std::string m_lastCaptureSummary = {};
|
||||
std::string m_lastCaptureError = {};
|
||||
std::uint64_t m_captureCount = 0;
|
||||
bool m_capturePending = false;
|
||||
};
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
487
new_editor/src/NativeRenderer.cpp
Normal file
487
new_editor/src/NativeRenderer.cpp
Normal file
@@ -0,0 +1,487 @@
|
||||
#include "NativeRenderer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <filesystem>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
namespace {
|
||||
|
||||
D2D1_RECT_F ToD2DRect(const ::XCEngine::UI::UIRect& rect) {
|
||||
return D2D1::RectF(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
|
||||
}
|
||||
|
||||
std::string HrToString(const char* operation, HRESULT hr) {
|
||||
char buffer[128] = {};
|
||||
sprintf_s(buffer, "%s failed with hr=0x%08X.", operation, static_cast<unsigned int>(hr));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool NativeRenderer::Initialize(HWND hwnd) {
|
||||
Shutdown();
|
||||
|
||||
if (hwnd == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_hwnd = hwnd;
|
||||
if (FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, m_d2dFactory.ReleaseAndGetAddressOf()))) {
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FAILED(DWriteCreateFactory(
|
||||
DWRITE_FACTORY_TYPE_SHARED,
|
||||
__uuidof(IDWriteFactory),
|
||||
reinterpret_cast<IUnknown**>(m_dwriteFactory.ReleaseAndGetAddressOf())))) {
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
return EnsureRenderTarget();
|
||||
}
|
||||
|
||||
void NativeRenderer::Shutdown() {
|
||||
m_textFormats.clear();
|
||||
m_solidBrush.Reset();
|
||||
m_renderTarget.Reset();
|
||||
m_wicFactory.Reset();
|
||||
m_dwriteFactory.Reset();
|
||||
m_d2dFactory.Reset();
|
||||
if (m_wicComInitialized) {
|
||||
CoUninitialize();
|
||||
m_wicComInitialized = false;
|
||||
}
|
||||
m_hwnd = nullptr;
|
||||
}
|
||||
|
||||
void NativeRenderer::Resize(UINT width, UINT height) {
|
||||
if (!m_renderTarget || width == 0 || height == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const HRESULT hr = m_renderTarget->Resize(D2D1::SizeU(width, height));
|
||||
if (hr == D2DERR_RECREATE_TARGET) {
|
||||
DiscardRenderTarget();
|
||||
}
|
||||
}
|
||||
|
||||
bool NativeRenderer::Render(const ::XCEngine::UI::UIDrawData& drawData) {
|
||||
if (!EnsureRenderTarget()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool rendered = RenderToTarget(*m_renderTarget.Get(), *m_solidBrush.Get(), drawData);
|
||||
const HRESULT hr = m_renderTarget->EndDraw();
|
||||
if (hr == D2DERR_RECREATE_TARGET) {
|
||||
DiscardRenderTarget();
|
||||
return false;
|
||||
}
|
||||
|
||||
return rendered && SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
bool NativeRenderer::CaptureToPng(
|
||||
const ::XCEngine::UI::UIDrawData& drawData,
|
||||
UINT width,
|
||||
UINT height,
|
||||
const std::filesystem::path& outputPath,
|
||||
std::string& outError) {
|
||||
outError.clear();
|
||||
if (width == 0 || height == 0) {
|
||||
outError = "CaptureToPng rejected an empty render size.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_d2dFactory || !m_dwriteFactory) {
|
||||
outError = "CaptureToPng requires an initialized NativeRenderer.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EnsureWicFactory(outError)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::error_code errorCode = {};
|
||||
std::filesystem::create_directories(outputPath.parent_path(), errorCode);
|
||||
if (errorCode) {
|
||||
outError = "Failed to create screenshot directory: " + outputPath.parent_path().string();
|
||||
return false;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IWICBitmap> bitmap;
|
||||
HRESULT hr = m_wicFactory->CreateBitmap(
|
||||
width,
|
||||
height,
|
||||
GUID_WICPixelFormat32bppPBGRA,
|
||||
WICBitmapCacheOnLoad,
|
||||
bitmap.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr)) {
|
||||
outError = HrToString("IWICImagingFactory::CreateBitmap", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
const D2D1_RENDER_TARGET_PROPERTIES renderTargetProperties = D2D1::RenderTargetProperties(
|
||||
D2D1_RENDER_TARGET_TYPE_DEFAULT,
|
||||
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED));
|
||||
|
||||
Microsoft::WRL::ComPtr<ID2D1RenderTarget> offscreenRenderTarget;
|
||||
hr = m_d2dFactory->CreateWicBitmapRenderTarget(
|
||||
bitmap.Get(),
|
||||
renderTargetProperties,
|
||||
offscreenRenderTarget.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr)) {
|
||||
outError = HrToString("ID2D1Factory::CreateWicBitmapRenderTarget", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> offscreenBrush;
|
||||
hr = offscreenRenderTarget->CreateSolidColorBrush(
|
||||
D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f),
|
||||
offscreenBrush.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr)) {
|
||||
outError = HrToString("ID2D1RenderTarget::CreateSolidColorBrush", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool rendered = RenderToTarget(*offscreenRenderTarget.Get(), *offscreenBrush.Get(), drawData);
|
||||
hr = offscreenRenderTarget->EndDraw();
|
||||
if (!rendered || FAILED(hr)) {
|
||||
outError = HrToString("ID2D1RenderTarget::EndDraw", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::wstring wideOutputPath = outputPath.wstring();
|
||||
Microsoft::WRL::ComPtr<IWICStream> stream;
|
||||
hr = m_wicFactory->CreateStream(stream.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr)) {
|
||||
outError = HrToString("IWICImagingFactory::CreateStream", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = stream->InitializeFromFilename(wideOutputPath.c_str(), GENERIC_WRITE);
|
||||
if (FAILED(hr)) {
|
||||
outError = HrToString("IWICStream::InitializeFromFilename", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IWICBitmapEncoder> encoder;
|
||||
hr = m_wicFactory->CreateEncoder(GUID_ContainerFormatPng, nullptr, encoder.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr)) {
|
||||
outError = HrToString("IWICImagingFactory::CreateEncoder", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = encoder->Initialize(stream.Get(), WICBitmapEncoderNoCache);
|
||||
if (FAILED(hr)) {
|
||||
outError = HrToString("IWICBitmapEncoder::Initialize", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IWICBitmapFrameEncode> frame;
|
||||
Microsoft::WRL::ComPtr<IPropertyBag2> propertyBag;
|
||||
hr = encoder->CreateNewFrame(frame.ReleaseAndGetAddressOf(), propertyBag.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr)) {
|
||||
outError = HrToString("IWICBitmapEncoder::CreateNewFrame", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = frame->Initialize(propertyBag.Get());
|
||||
if (FAILED(hr)) {
|
||||
outError = HrToString("IWICBitmapFrameEncode::Initialize", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = frame->SetSize(width, height);
|
||||
if (FAILED(hr)) {
|
||||
outError = HrToString("IWICBitmapFrameEncode::SetSize", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppPBGRA;
|
||||
hr = frame->SetPixelFormat(&pixelFormat);
|
||||
if (FAILED(hr)) {
|
||||
outError = HrToString("IWICBitmapFrameEncode::SetPixelFormat", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = frame->WriteSource(bitmap.Get(), nullptr);
|
||||
if (FAILED(hr)) {
|
||||
outError = HrToString("IWICBitmapFrameEncode::WriteSource", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = frame->Commit();
|
||||
if (FAILED(hr)) {
|
||||
outError = HrToString("IWICBitmapFrameEncode::Commit", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = encoder->Commit();
|
||||
if (FAILED(hr)) {
|
||||
outError = HrToString("IWICBitmapEncoder::Commit", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NativeRenderer::EnsureRenderTarget() {
|
||||
if (!m_hwnd || !m_d2dFactory || !m_dwriteFactory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return CreateDeviceResources();
|
||||
}
|
||||
|
||||
bool NativeRenderer::EnsureWicFactory(std::string& outError) {
|
||||
outError.clear();
|
||||
if (m_wicFactory) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const HRESULT initHr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
if (FAILED(initHr) && initHr != RPC_E_CHANGED_MODE) {
|
||||
outError = HrToString("CoInitializeEx", initHr);
|
||||
return false;
|
||||
}
|
||||
if (SUCCEEDED(initHr)) {
|
||||
m_wicComInitialized = true;
|
||||
}
|
||||
|
||||
const HRESULT factoryHr = CoCreateInstance(
|
||||
CLSID_WICImagingFactory,
|
||||
nullptr,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
IID_PPV_ARGS(m_wicFactory.ReleaseAndGetAddressOf()));
|
||||
if (FAILED(factoryHr)) {
|
||||
outError = HrToString("CoCreateInstance(CLSID_WICImagingFactory)", factoryHr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NativeRenderer::DiscardRenderTarget() {
|
||||
m_solidBrush.Reset();
|
||||
m_renderTarget.Reset();
|
||||
}
|
||||
|
||||
bool NativeRenderer::CreateDeviceResources() {
|
||||
if (m_renderTarget) {
|
||||
return true;
|
||||
}
|
||||
|
||||
RECT clientRect = {};
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const UINT width = static_cast<UINT>((std::max)(clientRect.right - clientRect.left, 1L));
|
||||
const UINT height = static_cast<UINT>((std::max)(clientRect.bottom - clientRect.top, 1L));
|
||||
|
||||
const D2D1_RENDER_TARGET_PROPERTIES renderTargetProps = D2D1::RenderTargetProperties();
|
||||
const D2D1_HWND_RENDER_TARGET_PROPERTIES hwndProps = D2D1::HwndRenderTargetProperties(
|
||||
m_hwnd,
|
||||
D2D1::SizeU(width, height));
|
||||
|
||||
if (FAILED(m_d2dFactory->CreateHwndRenderTarget(
|
||||
renderTargetProps,
|
||||
hwndProps,
|
||||
m_renderTarget.ReleaseAndGetAddressOf()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FAILED(m_renderTarget->CreateSolidColorBrush(
|
||||
D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f),
|
||||
m_solidBrush.ReleaseAndGetAddressOf()))) {
|
||||
DiscardRenderTarget();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_renderTarget->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NativeRenderer::RenderToTarget(
|
||||
ID2D1RenderTarget& renderTarget,
|
||||
ID2D1SolidColorBrush& solidBrush,
|
||||
const ::XCEngine::UI::UIDrawData& drawData) {
|
||||
renderTarget.SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
|
||||
renderTarget.BeginDraw();
|
||||
renderTarget.Clear(D2D1::ColorF(0.04f, 0.05f, 0.06f, 1.0f));
|
||||
|
||||
std::vector<D2D1_RECT_F> clipStack = {};
|
||||
for (const ::XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
|
||||
for (const ::XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) {
|
||||
RenderCommand(renderTarget, solidBrush, command, clipStack);
|
||||
}
|
||||
}
|
||||
|
||||
while (!clipStack.empty()) {
|
||||
renderTarget.PopAxisAlignedClip();
|
||||
clipStack.pop_back();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NativeRenderer::RenderCommand(
|
||||
ID2D1RenderTarget& renderTarget,
|
||||
ID2D1SolidColorBrush& solidBrush,
|
||||
const ::XCEngine::UI::UIDrawCommand& command,
|
||||
std::vector<D2D1_RECT_F>& clipStack) {
|
||||
solidBrush.SetColor(ToD2DColor(command.color));
|
||||
|
||||
switch (command.type) {
|
||||
case ::XCEngine::UI::UIDrawCommandType::FilledRect: {
|
||||
const D2D1_RECT_F rect = ToD2DRect(command.rect);
|
||||
if (command.rounding > 0.0f) {
|
||||
renderTarget.FillRoundedRectangle(
|
||||
D2D1::RoundedRect(rect, command.rounding, command.rounding),
|
||||
&solidBrush);
|
||||
} else {
|
||||
renderTarget.FillRectangle(rect, &solidBrush);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ::XCEngine::UI::UIDrawCommandType::RectOutline: {
|
||||
const D2D1_RECT_F rect = ToD2DRect(command.rect);
|
||||
const float thickness = command.thickness > 0.0f ? command.thickness : 1.0f;
|
||||
if (command.rounding > 0.0f) {
|
||||
renderTarget.DrawRoundedRectangle(
|
||||
D2D1::RoundedRect(rect, command.rounding, command.rounding),
|
||||
&solidBrush,
|
||||
thickness);
|
||||
} else {
|
||||
renderTarget.DrawRectangle(rect, &solidBrush, thickness);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ::XCEngine::UI::UIDrawCommandType::Text: {
|
||||
if (command.text.empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const float fontSize = command.fontSize > 0.0f ? command.fontSize : 16.0f;
|
||||
IDWriteTextFormat* textFormat = GetTextFormat(fontSize);
|
||||
if (textFormat == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
const std::wstring text = Utf8ToWide(command.text);
|
||||
if (text.empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const D2D1_SIZE_F targetSize = renderTarget.GetSize();
|
||||
const D2D1_RECT_F layoutRect = D2D1::RectF(
|
||||
command.position.x,
|
||||
command.position.y,
|
||||
targetSize.width,
|
||||
command.position.y + fontSize * 1.8f);
|
||||
renderTarget.DrawTextW(
|
||||
text.c_str(),
|
||||
static_cast<UINT32>(text.size()),
|
||||
textFormat,
|
||||
layoutRect,
|
||||
&solidBrush,
|
||||
D2D1_DRAW_TEXT_OPTIONS_CLIP,
|
||||
DWRITE_MEASURING_MODE_NATURAL);
|
||||
break;
|
||||
}
|
||||
case ::XCEngine::UI::UIDrawCommandType::Image: {
|
||||
if (!command.texture.IsValid()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const D2D1_RECT_F rect = ToD2DRect(command.rect);
|
||||
renderTarget.DrawRectangle(rect, &solidBrush, 1.0f);
|
||||
break;
|
||||
}
|
||||
case ::XCEngine::UI::UIDrawCommandType::PushClipRect: {
|
||||
const D2D1_RECT_F rect = ToD2DRect(command.rect);
|
||||
renderTarget.PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
|
||||
clipStack.push_back(rect);
|
||||
break;
|
||||
}
|
||||
case ::XCEngine::UI::UIDrawCommandType::PopClipRect: {
|
||||
if (!clipStack.empty()) {
|
||||
renderTarget.PopAxisAlignedClip();
|
||||
clipStack.pop_back();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
IDWriteTextFormat* NativeRenderer::GetTextFormat(float fontSize) {
|
||||
if (!m_dwriteFactory) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const int key = static_cast<int>(std::lround(fontSize * 10.0f));
|
||||
const auto found = m_textFormats.find(key);
|
||||
if (found != m_textFormats.end()) {
|
||||
return found->second.Get();
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IDWriteTextFormat> textFormat;
|
||||
const HRESULT hr = m_dwriteFactory->CreateTextFormat(
|
||||
L"Segoe UI",
|
||||
nullptr,
|
||||
DWRITE_FONT_WEIGHT_REGULAR,
|
||||
DWRITE_FONT_STYLE_NORMAL,
|
||||
DWRITE_FONT_STRETCH_NORMAL,
|
||||
fontSize,
|
||||
L"",
|
||||
textFormat.ReleaseAndGetAddressOf());
|
||||
if (FAILED(hr)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
|
||||
textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
|
||||
textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
|
||||
|
||||
IDWriteTextFormat* result = textFormat.Get();
|
||||
m_textFormats.emplace(key, std::move(textFormat));
|
||||
return result;
|
||||
}
|
||||
|
||||
D2D1_COLOR_F NativeRenderer::ToD2DColor(const ::XCEngine::UI::UIColor& color) {
|
||||
return D2D1::ColorF(color.r, color.g, color.b, color.a);
|
||||
}
|
||||
|
||||
std::wstring NativeRenderer::Utf8ToWide(std::string_view text) {
|
||||
if (text.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const int sizeNeeded = MultiByteToWideChar(
|
||||
CP_UTF8,
|
||||
0,
|
||||
text.data(),
|
||||
static_cast<int>(text.size()),
|
||||
nullptr,
|
||||
0);
|
||||
if (sizeNeeded <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::wstring wideText(static_cast<size_t>(sizeNeeded), L'\0');
|
||||
MultiByteToWideChar(
|
||||
CP_UTF8,
|
||||
0,
|
||||
text.data(),
|
||||
static_cast<int>(text.size()),
|
||||
wideText.data(),
|
||||
sizeNeeded);
|
||||
return wideText;
|
||||
}
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
67
new_editor/src/NativeRenderer.h
Normal file
67
new_editor/src/NativeRenderer.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <d2d1.h>
|
||||
#include <dwrite.h>
|
||||
#include <wincodec.h>
|
||||
#include <windows.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
class NativeRenderer {
|
||||
public:
|
||||
bool Initialize(HWND hwnd);
|
||||
void Shutdown();
|
||||
void Resize(UINT width, UINT height);
|
||||
bool Render(const ::XCEngine::UI::UIDrawData& drawData);
|
||||
bool CaptureToPng(
|
||||
const ::XCEngine::UI::UIDrawData& drawData,
|
||||
UINT width,
|
||||
UINT height,
|
||||
const std::filesystem::path& outputPath,
|
||||
std::string& outError);
|
||||
|
||||
private:
|
||||
bool EnsureRenderTarget();
|
||||
bool EnsureWicFactory(std::string& outError);
|
||||
void DiscardRenderTarget();
|
||||
bool CreateDeviceResources();
|
||||
bool RenderToTarget(
|
||||
ID2D1RenderTarget& renderTarget,
|
||||
ID2D1SolidColorBrush& solidBrush,
|
||||
const ::XCEngine::UI::UIDrawData& drawData);
|
||||
void RenderCommand(
|
||||
ID2D1RenderTarget& renderTarget,
|
||||
ID2D1SolidColorBrush& solidBrush,
|
||||
const ::XCEngine::UI::UIDrawCommand& command,
|
||||
std::vector<D2D1_RECT_F>& clipStack);
|
||||
|
||||
IDWriteTextFormat* GetTextFormat(float fontSize);
|
||||
static D2D1_COLOR_F ToD2DColor(const ::XCEngine::UI::UIColor& color);
|
||||
static std::wstring Utf8ToWide(std::string_view text);
|
||||
|
||||
HWND m_hwnd = nullptr;
|
||||
Microsoft::WRL::ComPtr<ID2D1Factory> m_d2dFactory;
|
||||
Microsoft::WRL::ComPtr<IDWriteFactory> m_dwriteFactory;
|
||||
Microsoft::WRL::ComPtr<IWICImagingFactory> m_wicFactory;
|
||||
Microsoft::WRL::ComPtr<ID2D1HwndRenderTarget> m_renderTarget;
|
||||
Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> m_solidBrush;
|
||||
std::unordered_map<int, Microsoft::WRL::ComPtr<IDWriteTextFormat>> m_textFormats;
|
||||
bool m_wicComInitialized = false;
|
||||
};
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
@@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include "../../../editor/src/Platform/D3D12WindowRenderer.h"
|
||||
@@ -1,83 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include "D3D12WindowRenderer.h"
|
||||
|
||||
#include "../UI/ImGuiBackendBridge.h"
|
||||
|
||||
#include <XCEngine/RHI/D3D12/D3D12CommandList.h>
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace Platform {
|
||||
|
||||
inline void RenderImGuiFrame(
|
||||
D3D12WindowRenderer& windowRenderer,
|
||||
UI::ImGuiBackendBridge& imguiBackend,
|
||||
const float clearColor[4],
|
||||
const D3D12WindowRenderer::RenderCallback& beforeUiRender = {},
|
||||
const D3D12WindowRenderer::RenderCallback& afterUiRender = {}) {
|
||||
const Rendering::RenderSurface* renderSurface = windowRenderer.GetCurrentRenderSurface();
|
||||
Rendering::RenderContext renderContext = windowRenderer.GetRenderContext();
|
||||
auto* d3d12CommandList = dynamic_cast<RHI::D3D12CommandList*>(renderContext.commandList);
|
||||
if (!renderContext.IsValid() ||
|
||||
d3d12CommandList == nullptr ||
|
||||
renderContext.commandQueue == nullptr ||
|
||||
windowRenderer.GetSwapChain() == nullptr ||
|
||||
renderSurface == nullptr ||
|
||||
windowRenderer.GetSrvHeap() == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& colorAttachments = renderSurface->GetColorAttachments();
|
||||
if (colorAttachments.empty() || colorAttachments[0] == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
RHI::RHIResourceView* renderTargetView = colorAttachments[0];
|
||||
renderContext.commandList->TransitionBarrier(
|
||||
renderTargetView,
|
||||
RHI::ResourceStates::Present,
|
||||
RHI::ResourceStates::RenderTarget);
|
||||
renderContext.commandList->SetRenderTargets(1, &renderTargetView, nullptr);
|
||||
renderContext.commandList->ClearRenderTarget(renderTargetView, clearColor);
|
||||
|
||||
if (beforeUiRender) {
|
||||
beforeUiRender(renderContext, *renderSurface);
|
||||
renderContext.commandList->SetRenderTargets(1, &renderTargetView, nullptr);
|
||||
}
|
||||
|
||||
ID3D12DescriptorHeap* descriptorHeaps[] = { windowRenderer.GetSrvHeap() };
|
||||
d3d12CommandList->SetDescriptorHeaps(1, descriptorHeaps);
|
||||
imguiBackend.RenderDrawData(d3d12CommandList->GetCommandList());
|
||||
|
||||
if (afterUiRender) {
|
||||
renderContext.commandList->SetRenderTargets(1, &renderTargetView, nullptr);
|
||||
afterUiRender(renderContext, *renderSurface);
|
||||
}
|
||||
|
||||
renderContext.commandList->TransitionBarrier(
|
||||
renderTargetView,
|
||||
RHI::ResourceStates::RenderTarget,
|
||||
RHI::ResourceStates::Present);
|
||||
renderContext.commandList->Close();
|
||||
|
||||
void* commandLists[] = { renderContext.commandList };
|
||||
renderContext.commandQueue->ExecuteCommandLists(1, commandLists);
|
||||
windowRenderer.GetSwapChain()->Present(1, 0);
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,275 +0,0 @@
|
||||
#include "Rendering/MainWindowBackdropPass.h"
|
||||
|
||||
#include <XCEngine/Core/Math/Vector4.h>
|
||||
#include <XCEngine/RHI/RHICommandList.h>
|
||||
#include <XCEngine/RHI/RHIDevice.h>
|
||||
#include <XCEngine/RHI/RHITypes.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
namespace {
|
||||
|
||||
struct BackdropConstants {
|
||||
Math::Vector4 viewportAndTime = Math::Vector4::Zero();
|
||||
Math::Vector4 primaryAccent = Math::Vector4::Zero();
|
||||
Math::Vector4 secondaryAccent = Math::Vector4::Zero();
|
||||
};
|
||||
|
||||
constexpr char kMainWindowBackdropShader[] = R"(
|
||||
cbuffer BackdropConstants : register(b0) {
|
||||
float4 gViewportAndTime;
|
||||
float4 gPrimaryAccent;
|
||||
float4 gSecondaryAccent;
|
||||
};
|
||||
|
||||
struct VSOutput {
|
||||
float4 position : SV_POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
};
|
||||
|
||||
VSOutput MainVS(uint vertexId : SV_VertexID) {
|
||||
VSOutput output;
|
||||
float2 uv = float2((vertexId << 1) & 2, vertexId & 2);
|
||||
output.position = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||
output.uv = uv;
|
||||
return output;
|
||||
}
|
||||
|
||||
float Hash21(float2 p) {
|
||||
p = frac(p * float2(123.34, 456.21));
|
||||
p += dot(p, p + 45.32);
|
||||
return frac(p.x * p.y);
|
||||
}
|
||||
|
||||
float4 MainPS(VSOutput input) : SV_TARGET0 {
|
||||
const float2 uv = input.uv;
|
||||
const float time = gViewportAndTime.z;
|
||||
const float pulseEnabled = gViewportAndTime.w;
|
||||
|
||||
float3 baseLow = float3(0.055, 0.062, 0.078);
|
||||
float3 baseHigh = float3(0.105, 0.118, 0.142);
|
||||
float3 color = lerp(baseLow, baseHigh, saturate(uv.y * 0.9 + 0.08));
|
||||
|
||||
const float vignetteX = uv.x * (1.0 - uv.x);
|
||||
const float vignetteY = uv.y * (1.0 - uv.y);
|
||||
const float vignette = saturate(pow(vignetteX * vignetteY * 5.5, 0.42));
|
||||
color *= lerp(0.72, 1.0, vignette);
|
||||
|
||||
const float pulse = pulseEnabled > 0.5 ? (0.5 + 0.5 * sin(time * 1.15)) : 0.55;
|
||||
const float diagonalBand = saturate(
|
||||
1.0 - abs(frac(uv.x * 0.95 - uv.y * 0.82 + time * 0.018) - 0.5) * 7.0);
|
||||
color += gPrimaryAccent.rgb * diagonalBand * (0.05 + pulse * 0.04);
|
||||
|
||||
const float stripePhase = uv.y * 26.0 + time * 0.6;
|
||||
const float stripe = pow(saturate(sin(stripePhase) * 0.5 + 0.5), 6.0);
|
||||
color += gSecondaryAccent.rgb * stripe * 0.018;
|
||||
|
||||
const float headerMask = 1.0 - smoothstep(0.085, 0.16, uv.y);
|
||||
color = lerp(color, color + gPrimaryAccent.rgb * 0.12, headerMask);
|
||||
|
||||
const float leftRailMask = 1.0 - smoothstep(0.0, 0.18, uv.x);
|
||||
const float railNoise = Hash21(float2(floor(uv.y * 90.0), floor(time * 8.0)));
|
||||
color += gSecondaryAccent.rgb * leftRailMask * (0.03 + railNoise * 0.015);
|
||||
|
||||
return float4(saturate(color), 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
} // namespace
|
||||
|
||||
void MainWindowBackdropPass::Shutdown() {
|
||||
DestroyResources();
|
||||
}
|
||||
|
||||
bool MainWindowBackdropPass::Render(
|
||||
const ::XCEngine::Rendering::RenderContext& renderContext,
|
||||
const ::XCEngine::Rendering::RenderSurface& surface,
|
||||
const RenderParams& params) {
|
||||
if (!renderContext.IsValid() || renderContext.backendType != RHI::RHIType::D3D12) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& colorAttachments = surface.GetColorAttachments();
|
||||
if (colorAttachments.empty() || colorAttachments[0] == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EnsureInitialized(renderContext)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::RHICommandList* commandList = renderContext.commandList;
|
||||
if (commandList == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::RHIResourceView* renderTarget = colorAttachments[0];
|
||||
|
||||
BackdropConstants constants = {};
|
||||
constants.viewportAndTime = Math::Vector4(
|
||||
static_cast<float>(surface.GetWidth()),
|
||||
static_cast<float>(surface.GetHeight()),
|
||||
params.elapsedSeconds,
|
||||
params.pulseAccent ? 1.0f : 0.0f);
|
||||
constants.primaryAccent = Math::Vector4(0.87f, 0.41f, 0.16f, 1.0f);
|
||||
constants.secondaryAccent = Math::Vector4(0.24f, 0.72f, 0.84f, 1.0f);
|
||||
m_constantSet->WriteConstant(0, &constants, sizeof(constants));
|
||||
|
||||
commandList->SetRenderTargets(1, &renderTarget, nullptr);
|
||||
|
||||
const RHI::Viewport viewport = {
|
||||
0.0f,
|
||||
0.0f,
|
||||
static_cast<float>(surface.GetWidth()),
|
||||
static_cast<float>(surface.GetHeight()),
|
||||
0.0f,
|
||||
1.0f
|
||||
};
|
||||
const RHI::Rect scissorRect = {
|
||||
0,
|
||||
0,
|
||||
static_cast<int32_t>(surface.GetWidth()),
|
||||
static_cast<int32_t>(surface.GetHeight())
|
||||
};
|
||||
|
||||
commandList->SetViewport(viewport);
|
||||
commandList->SetScissorRect(scissorRect);
|
||||
commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
||||
commandList->SetPipelineState(m_pipelineState);
|
||||
|
||||
RHI::RHIDescriptorSet* descriptorSets[] = { m_constantSet };
|
||||
commandList->SetGraphicsDescriptorSets(0, 1, descriptorSets, m_pipelineLayout);
|
||||
commandList->Draw(3, 1, 0, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MainWindowBackdropPass::EnsureInitialized(
|
||||
const ::XCEngine::Rendering::RenderContext& renderContext) {
|
||||
if (m_pipelineState != nullptr &&
|
||||
m_pipelineLayout != nullptr &&
|
||||
m_constantPool != nullptr &&
|
||||
m_constantSet != nullptr &&
|
||||
m_device == renderContext.device &&
|
||||
m_backendType == renderContext.backendType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DestroyResources();
|
||||
return CreateResources(renderContext);
|
||||
}
|
||||
|
||||
bool MainWindowBackdropPass::CreateResources(
|
||||
const ::XCEngine::Rendering::RenderContext& renderContext) {
|
||||
if (!renderContext.IsValid() || renderContext.backendType != RHI::RHIType::D3D12) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_device = renderContext.device;
|
||||
m_backendType = renderContext.backendType;
|
||||
|
||||
RHI::DescriptorSetLayoutBinding constantBinding = {};
|
||||
constantBinding.binding = 0;
|
||||
constantBinding.type = static_cast<uint32_t>(RHI::DescriptorType::CBV);
|
||||
constantBinding.count = 1;
|
||||
|
||||
RHI::DescriptorSetLayoutDesc constantLayout = {};
|
||||
constantLayout.bindings = &constantBinding;
|
||||
constantLayout.bindingCount = 1;
|
||||
|
||||
RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {};
|
||||
pipelineLayoutDesc.setLayouts = &constantLayout;
|
||||
pipelineLayoutDesc.setLayoutCount = 1;
|
||||
m_pipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc);
|
||||
if (m_pipelineLayout == nullptr) {
|
||||
DestroyResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::DescriptorPoolDesc constantPoolDesc = {};
|
||||
constantPoolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV;
|
||||
constantPoolDesc.descriptorCount = 1;
|
||||
constantPoolDesc.shaderVisible = false;
|
||||
m_constantPool = m_device->CreateDescriptorPool(constantPoolDesc);
|
||||
if (m_constantPool == nullptr) {
|
||||
DestroyResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_constantSet = m_constantPool->AllocateSet(constantLayout);
|
||||
if (m_constantSet == nullptr) {
|
||||
DestroyResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::GraphicsPipelineDesc pipelineDesc = {};
|
||||
pipelineDesc.pipelineLayout = m_pipelineLayout;
|
||||
pipelineDesc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
||||
pipelineDesc.renderTargetCount = 1;
|
||||
pipelineDesc.renderTargetFormats[0] = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
||||
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(RHI::Format::Unknown);
|
||||
pipelineDesc.sampleCount = 1;
|
||||
|
||||
pipelineDesc.rasterizerState.fillMode = static_cast<uint32_t>(RHI::FillMode::Solid);
|
||||
pipelineDesc.rasterizerState.cullMode = static_cast<uint32_t>(RHI::CullMode::None);
|
||||
pipelineDesc.rasterizerState.frontFace = static_cast<uint32_t>(RHI::FrontFace::CounterClockwise);
|
||||
pipelineDesc.rasterizerState.depthClipEnable = true;
|
||||
|
||||
pipelineDesc.blendState.blendEnable = false;
|
||||
pipelineDesc.blendState.colorWriteMask = 0xF;
|
||||
|
||||
pipelineDesc.depthStencilState.depthTestEnable = false;
|
||||
pipelineDesc.depthStencilState.depthWriteEnable = false;
|
||||
pipelineDesc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
|
||||
|
||||
pipelineDesc.vertexShader.source.assign(
|
||||
kMainWindowBackdropShader,
|
||||
kMainWindowBackdropShader + sizeof(kMainWindowBackdropShader) - 1);
|
||||
pipelineDesc.vertexShader.sourceLanguage = RHI::ShaderLanguage::HLSL;
|
||||
pipelineDesc.vertexShader.entryPoint = L"MainVS";
|
||||
pipelineDesc.vertexShader.profile = L"vs_5_0";
|
||||
|
||||
pipelineDesc.fragmentShader.source.assign(
|
||||
kMainWindowBackdropShader,
|
||||
kMainWindowBackdropShader + sizeof(kMainWindowBackdropShader) - 1);
|
||||
pipelineDesc.fragmentShader.sourceLanguage = RHI::ShaderLanguage::HLSL;
|
||||
pipelineDesc.fragmentShader.entryPoint = L"MainPS";
|
||||
pipelineDesc.fragmentShader.profile = L"ps_5_0";
|
||||
|
||||
m_pipelineState = m_device->CreatePipelineState(pipelineDesc);
|
||||
if (m_pipelineState == nullptr || !m_pipelineState->IsValid()) {
|
||||
DestroyResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWindowBackdropPass::DestroyResources() {
|
||||
if (m_pipelineState != nullptr) {
|
||||
m_pipelineState->Shutdown();
|
||||
delete m_pipelineState;
|
||||
m_pipelineState = nullptr;
|
||||
}
|
||||
if (m_constantSet != nullptr) {
|
||||
m_constantSet->Shutdown();
|
||||
delete m_constantSet;
|
||||
m_constantSet = nullptr;
|
||||
}
|
||||
if (m_constantPool != nullptr) {
|
||||
m_constantPool->Shutdown();
|
||||
delete m_constantPool;
|
||||
m_constantPool = nullptr;
|
||||
}
|
||||
if (m_pipelineLayout != nullptr) {
|
||||
m_pipelineLayout->Shutdown();
|
||||
delete m_pipelineLayout;
|
||||
m_pipelineLayout = nullptr;
|
||||
}
|
||||
|
||||
m_device = nullptr;
|
||||
m_backendType = RHI::RHIType::D3D12;
|
||||
}
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
@@ -1,41 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/RHI/RHIDescriptorPool.h>
|
||||
#include <XCEngine/RHI/RHIDescriptorSet.h>
|
||||
#include <XCEngine/RHI/RHIEnums.h>
|
||||
#include <XCEngine/RHI/RHIPipelineLayout.h>
|
||||
#include <XCEngine/RHI/RHIPipelineState.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
class MainWindowBackdropPass {
|
||||
public:
|
||||
struct RenderParams {
|
||||
float elapsedSeconds = 0.0f;
|
||||
bool pulseAccent = true;
|
||||
};
|
||||
|
||||
void Shutdown();
|
||||
bool Render(
|
||||
const ::XCEngine::Rendering::RenderContext& renderContext,
|
||||
const ::XCEngine::Rendering::RenderSurface& surface,
|
||||
const RenderParams& params);
|
||||
|
||||
private:
|
||||
bool EnsureInitialized(const ::XCEngine::Rendering::RenderContext& renderContext);
|
||||
bool CreateResources(const ::XCEngine::Rendering::RenderContext& renderContext);
|
||||
void DestroyResources();
|
||||
|
||||
::XCEngine::RHI::RHIDevice* m_device = nullptr;
|
||||
::XCEngine::RHI::RHIType m_backendType = ::XCEngine::RHI::RHIType::D3D12;
|
||||
::XCEngine::RHI::RHIPipelineLayout* m_pipelineLayout = nullptr;
|
||||
::XCEngine::RHI::RHIDescriptorPool* m_constantPool = nullptr;
|
||||
::XCEngine::RHI::RHIDescriptorSet* m_constantSet = nullptr;
|
||||
::XCEngine::RHI::RHIPipelineState* m_pipelineState = nullptr;
|
||||
};
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
@@ -1,44 +0,0 @@
|
||||
#include "Rendering/MainWindowNativeBackdropRenderer.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
void MainWindowNativeBackdropRenderer::Shutdown() {
|
||||
m_overlayBackend.Shutdown();
|
||||
m_backdropPass.Shutdown();
|
||||
}
|
||||
|
||||
bool MainWindowNativeBackdropRenderer::Render(
|
||||
const ::XCEngine::Rendering::RenderContext& renderContext,
|
||||
const ::XCEngine::Rendering::RenderSurface& surface,
|
||||
const FrameState& frameState) {
|
||||
m_overlayBackend.ResetStats();
|
||||
|
||||
if (!renderContext.IsValid() || renderContext.backendType != RHI::RHIType::D3D12) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (frameState.drawBackdrop) {
|
||||
MainWindowBackdropPass::RenderParams backdropParams = {};
|
||||
backdropParams.elapsedSeconds = frameState.elapsedSeconds;
|
||||
backdropParams.pulseAccent = frameState.pulseAccent;
|
||||
if (!m_backdropPass.Render(renderContext, surface, backdropParams)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (frameState.overlayDrawData == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto& colorAttachments = surface.GetColorAttachments();
|
||||
if (colorAttachments.empty() || colorAttachments[0] == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_overlayBackend.SetTextAtlasProvider(&m_textAtlasProvider);
|
||||
return m_overlayBackend.Render(renderContext, surface, *frameState.overlayDrawData);
|
||||
}
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
@@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Rendering/MainWindowBackdropPass.h"
|
||||
#include "XCUIBackend/XCUIRHIRenderBackend.h"
|
||||
#include "XCUIBackend/XCUIStandaloneTextAtlasProvider.h"
|
||||
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
class MainWindowNativeBackdropRenderer {
|
||||
public:
|
||||
using OverlayStats = ::XCEngine::Editor::XCUIBackend::XCUIRHIRenderBackend::OverlayStats;
|
||||
|
||||
struct FrameState {
|
||||
float elapsedSeconds = 0.0f;
|
||||
bool pulseAccent = true;
|
||||
bool drawBackdrop = true;
|
||||
const ::XCEngine::UI::UIDrawData* overlayDrawData = nullptr;
|
||||
};
|
||||
|
||||
void Shutdown();
|
||||
bool Render(
|
||||
const ::XCEngine::Rendering::RenderContext& renderContext,
|
||||
const ::XCEngine::Rendering::RenderSurface& surface,
|
||||
const FrameState& frameState);
|
||||
const OverlayStats& GetLastOverlayStats() const { return m_overlayBackend.GetLastOverlayStats(); }
|
||||
|
||||
private:
|
||||
MainWindowBackdropPass m_backdropPass = {};
|
||||
::XCEngine::Editor::XCUIBackend::XCUIStandaloneTextAtlasProvider m_textAtlasProvider = {};
|
||||
::XCEngine::Editor::XCUIBackend::XCUIRHIRenderBackend m_overlayBackend = {};
|
||||
};
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
334
new_editor/src/SandboxFrameBuilder.cpp
Normal file
334
new_editor/src/SandboxFrameBuilder.cpp
Normal file
@@ -0,0 +1,334 @@
|
||||
#include "SandboxFrameBuilder.h"
|
||||
|
||||
#include <XCEngine/UI/Widgets/UIEditorPanelChrome.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::XCEngine::UI::UIColor;
|
||||
using ::XCEngine::UI::UIDrawData;
|
||||
using ::XCEngine::UI::UIDrawList;
|
||||
using ::XCEngine::UI::UIPoint;
|
||||
using ::XCEngine::UI::UIRect;
|
||||
using ::XCEngine::UI::Widgets::AppendUIEditorPanelChromeBackground;
|
||||
using ::XCEngine::UI::Widgets::AppendUIEditorPanelChromeForeground;
|
||||
using ::XCEngine::UI::Widgets::UIEditorPanelChromeMetrics;
|
||||
using ::XCEngine::UI::Widgets::UIEditorPanelChromeState;
|
||||
using ::XCEngine::UI::Widgets::UIEditorPanelChromeText;
|
||||
|
||||
constexpr UIColor kWorkspaceColor(0.05f, 0.07f, 0.09f, 1.0f);
|
||||
constexpr UIColor kShellColor(0.08f, 0.10f, 0.13f, 1.0f);
|
||||
constexpr UIColor kSubtleLineColor(0.18f, 0.23f, 0.29f, 1.0f);
|
||||
constexpr UIColor kTextPrimary(0.92f, 0.95f, 0.98f, 1.0f);
|
||||
constexpr UIColor kTextSecondary(0.67f, 0.74f, 0.81f, 1.0f);
|
||||
constexpr UIColor kTextMuted(0.49f, 0.56f, 0.64f, 1.0f);
|
||||
constexpr UIColor kSelectionColor(0.16f, 0.37f, 0.64f, 0.90f);
|
||||
constexpr UIColor kAccentColor(0.19f, 0.76f, 0.57f, 1.0f);
|
||||
constexpr UIColor kWarningColor(0.96f, 0.72f, 0.22f, 1.0f);
|
||||
constexpr UIColor kErrorColor(0.88f, 0.29f, 0.30f, 1.0f);
|
||||
|
||||
float ClampPositive(float value, float fallback) {
|
||||
return value > 1.0f ? value : fallback;
|
||||
}
|
||||
|
||||
float Pulse01(double timeSeconds, double speed) {
|
||||
const double phase = std::sin(timeSeconds * speed);
|
||||
return static_cast<float>(0.5 + phase * 0.5);
|
||||
}
|
||||
|
||||
UIRect InsetRect(const UIRect& rect, float insetX, float insetY) {
|
||||
return UIRect(
|
||||
rect.x + insetX,
|
||||
rect.y + insetY,
|
||||
(std::max)(0.0f, rect.width - insetX * 2.0f),
|
||||
(std::max)(0.0f, rect.height - insetY * 2.0f));
|
||||
}
|
||||
|
||||
void AddSectionTitle(
|
||||
UIDrawList& drawList,
|
||||
float x,
|
||||
float y,
|
||||
std::string_view title,
|
||||
std::string_view subtitle = {}) {
|
||||
drawList.AddText(UIPoint(x, y), std::string(title), kTextPrimary, 17.0f);
|
||||
if (!subtitle.empty()) {
|
||||
drawList.AddText(UIPoint(x, y + 20.0f), std::string(subtitle), kTextMuted, 13.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void AddRow(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& rect,
|
||||
std::string_view label,
|
||||
bool selected = false,
|
||||
float indent = 0.0f,
|
||||
const UIColor& dotColor = kAccentColor) {
|
||||
if (selected) {
|
||||
drawList.AddFilledRect(rect, kSelectionColor, 6.0f);
|
||||
}
|
||||
|
||||
drawList.AddFilledRect(
|
||||
UIRect(rect.x + 10.0f + indent, rect.y + 10.0f, 8.0f, 8.0f),
|
||||
dotColor,
|
||||
4.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(rect.x + 26.0f + indent, rect.y + 6.0f),
|
||||
std::string(label),
|
||||
selected ? kTextPrimary : kTextSecondary,
|
||||
14.0f);
|
||||
}
|
||||
|
||||
void AddPropertyRow(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& rect,
|
||||
std::string_view name,
|
||||
std::string_view value) {
|
||||
drawList.AddText(UIPoint(rect.x, rect.y + 6.0f), std::string(name), kTextSecondary, 14.0f);
|
||||
|
||||
const UIRect fieldRect(rect.x + rect.width * 0.42f, rect.y, rect.width * 0.58f, rect.height);
|
||||
drawList.AddFilledRect(fieldRect, UIColor(0.11f, 0.15f, 0.20f, 1.0f), 6.0f);
|
||||
drawList.AddRectOutline(fieldRect, kSubtleLineColor, 1.0f, 6.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(fieldRect.x + 10.0f, fieldRect.y + 6.0f),
|
||||
std::string(value),
|
||||
kTextPrimary,
|
||||
14.0f);
|
||||
}
|
||||
|
||||
void AddLogRow(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& rect,
|
||||
const UIColor& levelColor,
|
||||
std::string_view level,
|
||||
std::string_view message) {
|
||||
drawList.AddFilledRect(
|
||||
UIRect(rect.x, rect.y + 7.0f, 6.0f, rect.height - 14.0f),
|
||||
levelColor,
|
||||
3.0f);
|
||||
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 4.0f), std::string(level), levelColor, 13.0f);
|
||||
drawList.AddText(UIPoint(rect.x + 82.0f, rect.y + 4.0f), std::string(message), kTextSecondary, 13.0f);
|
||||
}
|
||||
|
||||
void AppendPanel(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& panelRect,
|
||||
std::string_view title,
|
||||
std::string_view subtitle,
|
||||
std::string_view footer,
|
||||
bool active) {
|
||||
UIEditorPanelChromeState state = {};
|
||||
state.active = active;
|
||||
state.hovered = active;
|
||||
|
||||
UIEditorPanelChromeText text = {};
|
||||
text.title = title;
|
||||
text.subtitle = subtitle;
|
||||
text.footer = footer;
|
||||
|
||||
UIEditorPanelChromeMetrics metrics = {};
|
||||
metrics.headerHeight = 46.0f;
|
||||
|
||||
AppendUIEditorPanelChromeBackground(drawList, panelRect, state, {}, metrics);
|
||||
AppendUIEditorPanelChromeForeground(drawList, panelRect, text, {}, metrics);
|
||||
}
|
||||
|
||||
void AppendTopBar(UIDrawList& drawList, const UIRect& rect, double timeSeconds) {
|
||||
drawList.AddFilledRect(rect, kShellColor, 0.0f);
|
||||
drawList.AddRectOutline(rect, kSubtleLineColor, 1.0f, 0.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(rect.x + 20.0f, rect.y + 14.0f),
|
||||
"XCUI Native Sandbox",
|
||||
kTextPrimary,
|
||||
20.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(rect.x + 228.0f, rect.y + 17.0f),
|
||||
"editor ui proving ground",
|
||||
kTextMuted,
|
||||
13.0f);
|
||||
|
||||
const float pulse = Pulse01(timeSeconds, 2.2);
|
||||
const UIColor liveColor(
|
||||
0.18f + pulse * 0.16f,
|
||||
0.74f,
|
||||
0.58f,
|
||||
1.0f);
|
||||
drawList.AddFilledRect(
|
||||
UIRect(rect.x + rect.width - 170.0f, rect.y + 12.0f, 108.0f, 24.0f),
|
||||
UIColor(0.10f, 0.16f, 0.12f, 1.0f),
|
||||
12.0f);
|
||||
drawList.AddFilledRect(
|
||||
UIRect(rect.x + rect.width - 160.0f, rect.y + 20.0f, 8.0f, 8.0f),
|
||||
liveColor,
|
||||
4.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(rect.x + rect.width - 146.0f, rect.y + 14.0f),
|
||||
"Native Render",
|
||||
kTextPrimary,
|
||||
13.0f);
|
||||
}
|
||||
|
||||
void AppendStatusBar(UIDrawList& drawList, const UIRect& rect, const SandboxFrameOptions& options) {
|
||||
drawList.AddFilledRect(rect, kShellColor, 0.0f);
|
||||
drawList.AddRectOutline(rect, kSubtleLineColor, 1.0f, 0.0f);
|
||||
|
||||
const std::string leftText =
|
||||
"Direct renderer | size " +
|
||||
std::to_string(static_cast<int>(options.width)) +
|
||||
"x" +
|
||||
std::to_string(static_cast<int>(options.height));
|
||||
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 8.0f), leftText, kTextSecondary, 13.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(rect.x + rect.width - 176.0f, rect.y + 8.0f),
|
||||
"No ImGui Host",
|
||||
kAccentColor,
|
||||
13.0f);
|
||||
}
|
||||
|
||||
void AppendHierarchyPanel(UIDrawList& drawList, const UIRect& panelRect) {
|
||||
AppendPanel(drawList, panelRect, "Hierarchy", "scene graph", "12 items", false);
|
||||
|
||||
const UIRect body = InsetRect(panelRect, 16.0f, 60.0f);
|
||||
AddSectionTitle(drawList, body.x, body.y, "Active Scene", "Sandbox_City.xcscene");
|
||||
|
||||
float rowY = body.y + 42.0f;
|
||||
const float rowHeight = 28.0f;
|
||||
AddRow(drawList, UIRect(body.x, rowY, body.width, rowHeight), "World", false, 0.0f, kWarningColor);
|
||||
rowY += rowHeight + 4.0f;
|
||||
AddRow(drawList, UIRect(body.x, rowY, body.width, rowHeight), "Main Camera", false, 18.0f);
|
||||
rowY += rowHeight + 4.0f;
|
||||
AddRow(drawList, UIRect(body.x, rowY, body.width, rowHeight), "Directional Light", false, 18.0f, kWarningColor);
|
||||
rowY += rowHeight + 4.0f;
|
||||
AddRow(drawList, UIRect(body.x, rowY, body.width, rowHeight), "Player", true, 18.0f);
|
||||
rowY += rowHeight + 4.0f;
|
||||
AddRow(drawList, UIRect(body.x, rowY, body.width, rowHeight), "WeaponSocket", false, 36.0f, UIColor(0.58f, 0.70f, 0.88f, 1.0f));
|
||||
rowY += rowHeight + 4.0f;
|
||||
AddRow(drawList, UIRect(body.x, rowY, body.width, rowHeight), "FX_Trail", false, 36.0f, UIColor(0.76f, 0.43f, 0.93f, 1.0f));
|
||||
}
|
||||
|
||||
void AppendInspectorPanel(UIDrawList& drawList, const UIRect& panelRect) {
|
||||
AppendPanel(drawList, panelRect, "Inspector", "Player", "schema-first draft", false);
|
||||
|
||||
const UIRect body = InsetRect(panelRect, 16.0f, 60.0f);
|
||||
AddSectionTitle(drawList, body.x, body.y, "Transform", "shared widget experiment");
|
||||
|
||||
float rowY = body.y + 42.0f;
|
||||
const float rowHeight = 30.0f;
|
||||
AddPropertyRow(drawList, UIRect(body.x, rowY, body.width, rowHeight), "Position", "12.0, 1.8, -4.0");
|
||||
rowY += rowHeight + 6.0f;
|
||||
AddPropertyRow(drawList, UIRect(body.x, rowY, body.width, rowHeight), "Rotation", "0.0, 36.5, 0.0");
|
||||
rowY += rowHeight + 6.0f;
|
||||
AddPropertyRow(drawList, UIRect(body.x, rowY, body.width, rowHeight), "Scale", "1.0, 1.0, 1.0");
|
||||
rowY += 48.0f;
|
||||
|
||||
AddSectionTitle(drawList, body.x, rowY, "Character", "future AutoForm target");
|
||||
rowY += 42.0f;
|
||||
AddPropertyRow(drawList, UIRect(body.x, rowY, body.width, rowHeight), "State", "Locomotion");
|
||||
rowY += rowHeight + 6.0f;
|
||||
AddPropertyRow(drawList, UIRect(body.x, rowY, body.width, rowHeight), "Move Speed", "6.4");
|
||||
rowY += rowHeight + 6.0f;
|
||||
AddPropertyRow(drawList, UIRect(body.x, rowY, body.width, rowHeight), "Jump Height", "1.3");
|
||||
}
|
||||
|
||||
void AppendScenePanel(UIDrawList& drawList, const UIRect& panelRect, double timeSeconds) {
|
||||
AppendPanel(drawList, panelRect, "Scene", "native shell draft", "ViewportSlot comes later", true);
|
||||
|
||||
const UIRect body = InsetRect(panelRect, 16.0f, 60.0f);
|
||||
drawList.AddFilledRect(body, UIColor(0.07f, 0.09f, 0.11f, 1.0f), 12.0f);
|
||||
drawList.AddRectOutline(body, kSubtleLineColor, 1.0f, 12.0f);
|
||||
|
||||
const float gridStep = 34.0f;
|
||||
for (float x = body.x; x < body.x + body.width; x += gridStep) {
|
||||
drawList.AddRectOutline(UIRect(x, body.y, 1.0f, body.height), UIColor(0.10f, 0.13f, 0.16f, 0.6f));
|
||||
}
|
||||
for (float y = body.y; y < body.y + body.height; y += gridStep) {
|
||||
drawList.AddRectOutline(UIRect(body.x, y, body.width, 1.0f), UIColor(0.10f, 0.13f, 0.16f, 0.6f));
|
||||
}
|
||||
|
||||
const float pulse = Pulse01(timeSeconds, 1.8);
|
||||
const UIRect selectionRect(
|
||||
body.x + body.width * 0.32f,
|
||||
body.y + body.height * 0.24f,
|
||||
body.width * 0.22f,
|
||||
body.height * 0.34f);
|
||||
drawList.AddRectOutline(
|
||||
selectionRect,
|
||||
UIColor(0.24f + pulse * 0.12f, 0.56f, 0.95f, 1.0f),
|
||||
2.0f,
|
||||
8.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(body.x + 18.0f, body.y + 18.0f),
|
||||
"This area is intentionally native-rendered, not hosted by ImGui.",
|
||||
kTextSecondary,
|
||||
15.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(body.x + 18.0f, body.y + 40.0f),
|
||||
"Use this sandbox to iterate shell chrome, panel composition, and draw packets.",
|
||||
kTextMuted,
|
||||
13.0f);
|
||||
}
|
||||
|
||||
void AppendConsolePanel(UIDrawList& drawList, const UIRect& panelRect) {
|
||||
AppendPanel(drawList, panelRect, "Console", "native renderer smoke log", "6 records", false);
|
||||
|
||||
const UIRect body = InsetRect(panelRect, 16.0f, 60.0f);
|
||||
AddLogRow(drawList, UIRect(body.x, body.y, body.width, 22.0f), kAccentColor, "Info", "XCUI native sandbox frame submitted.");
|
||||
AddLogRow(drawList, UIRect(body.x, body.y + 26.0f, body.width, 22.0f), kWarningColor, "Warn", "Viewport host not connected in this phase.");
|
||||
AddLogRow(drawList, UIRect(body.x, body.y + 52.0f, body.width, 22.0f), kTextSecondary, "Info", "Hierarchy / Inspector are draw-packet prototypes.");
|
||||
AddLogRow(drawList, UIRect(body.x, body.y + 78.0f, body.width, 22.0f), kErrorColor, "Todo", "Replace bespoke scene panel with authored shell document.");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UIDrawData BuildSandboxFrame(const SandboxFrameOptions& options) {
|
||||
const float width = ClampPositive(options.width, 1280.0f);
|
||||
const float height = ClampPositive(options.height, 720.0f);
|
||||
|
||||
const float margin = 18.0f;
|
||||
const float gap = 14.0f;
|
||||
const float topBarHeight = 48.0f;
|
||||
const float statusBarHeight = 30.0f;
|
||||
const float leftPanelWidth = 260.0f;
|
||||
const float rightPanelWidth = 320.0f;
|
||||
const float bottomPanelHeight = 210.0f;
|
||||
|
||||
const UIRect fullRect(0.0f, 0.0f, width, height);
|
||||
const UIRect topBarRect(0.0f, 0.0f, width, topBarHeight);
|
||||
const UIRect statusRect(0.0f, height - statusBarHeight, width, statusBarHeight);
|
||||
|
||||
const float workspaceTop = topBarHeight + margin;
|
||||
const float workspaceBottom = height - statusBarHeight - margin;
|
||||
const float workspaceHeight = (std::max)(0.0f, workspaceBottom - workspaceTop);
|
||||
const float centerX = margin + leftPanelWidth + gap;
|
||||
const float centerWidth = (std::max)(240.0f, width - margin * 2.0f - leftPanelWidth - rightPanelWidth - gap * 2.0f);
|
||||
const float sceneHeight = (std::max)(220.0f, workspaceHeight - bottomPanelHeight - gap);
|
||||
|
||||
const UIRect hierarchyRect(margin, workspaceTop, leftPanelWidth, workspaceHeight);
|
||||
const UIRect sceneRect(centerX, workspaceTop, centerWidth, sceneHeight);
|
||||
const UIRect consoleRect(centerX, workspaceTop + sceneHeight + gap, centerWidth, bottomPanelHeight);
|
||||
const UIRect inspectorRect(centerX + centerWidth + gap, workspaceTop, rightPanelWidth, workspaceHeight);
|
||||
|
||||
UIDrawData drawData = {};
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("XCUI Native Editor Sandbox");
|
||||
drawList.PushClipRect(fullRect);
|
||||
drawList.AddFilledRect(fullRect, kWorkspaceColor, 0.0f);
|
||||
|
||||
AppendTopBar(drawList, topBarRect, options.timeSeconds);
|
||||
AppendHierarchyPanel(drawList, hierarchyRect);
|
||||
AppendScenePanel(drawList, sceneRect, options.timeSeconds);
|
||||
AppendConsolePanel(drawList, consoleRect);
|
||||
AppendInspectorPanel(drawList, inspectorRect);
|
||||
AppendStatusBar(drawList, statusRect, options);
|
||||
|
||||
drawList.PopClipRect();
|
||||
return drawData;
|
||||
}
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
17
new_editor/src/SandboxFrameBuilder.h
Normal file
17
new_editor/src/SandboxFrameBuilder.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
struct SandboxFrameOptions {
|
||||
float width = 1280.0f;
|
||||
float height = 720.0f;
|
||||
double timeSeconds = 0.0;
|
||||
};
|
||||
|
||||
::XCEngine::UI::UIDrawData BuildSandboxFrame(const SandboxFrameOptions& options);
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
@@ -1,3 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../../editor/src/UI/ImGuiBackendBridge.h"
|
||||
@@ -1,66 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include "XCUIBackend/UITextureRegistration.h"
|
||||
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/RHI/RHIDevice.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace Platform {
|
||||
|
||||
class D3D12WindowRenderer;
|
||||
|
||||
} // namespace Platform
|
||||
|
||||
namespace XCUIBackend {
|
||||
|
||||
class IEditorHostCompositor {
|
||||
public:
|
||||
using ConfigureFontsCallback = std::function<void()>;
|
||||
using RenderCallback =
|
||||
std::function<void(const ::XCEngine::Rendering::RenderContext&, const ::XCEngine::Rendering::RenderSurface&)>;
|
||||
|
||||
virtual ~IEditorHostCompositor() = default;
|
||||
|
||||
virtual bool Initialize(
|
||||
HWND hwnd,
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer& windowRenderer,
|
||||
const ConfigureFontsCallback& configureFonts) = 0;
|
||||
virtual void Shutdown() = 0;
|
||||
virtual bool HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) = 0;
|
||||
virtual void BeginFrame() = 0;
|
||||
virtual void EndFrameAndPresent(
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer& windowRenderer,
|
||||
const float clearColor[4],
|
||||
const RenderCallback& beforeUiRender = {},
|
||||
const RenderCallback& afterUiRender = {}) = 0;
|
||||
virtual bool CreateTextureDescriptor(
|
||||
::XCEngine::RHI::RHIDevice* device,
|
||||
::XCEngine::RHI::RHITexture* texture,
|
||||
UITextureRegistration& outRegistration) = 0;
|
||||
virtual void FreeTextureDescriptor(const UITextureRegistration& registration) = 0;
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,68 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include "XCUIBackend/UITextureRegistration.h"
|
||||
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/RHI/RHIDevice.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace Platform {
|
||||
|
||||
class D3D12WindowRenderer;
|
||||
|
||||
} // namespace Platform
|
||||
|
||||
namespace XCUIBackend {
|
||||
|
||||
class IWindowUICompositor {
|
||||
public:
|
||||
using ConfigureFontsCallback = std::function<void()>;
|
||||
using UiRenderCallback = std::function<void()>;
|
||||
using RenderCallback =
|
||||
std::function<void(const ::XCEngine::Rendering::RenderContext&, const ::XCEngine::Rendering::RenderSurface&)>;
|
||||
|
||||
virtual ~IWindowUICompositor() = default;
|
||||
|
||||
virtual bool Initialize(
|
||||
HWND hwnd,
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer& windowRenderer,
|
||||
const ConfigureFontsCallback& configureFonts) = 0;
|
||||
virtual void Shutdown() = 0;
|
||||
virtual bool HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) = 0;
|
||||
virtual void RenderFrame(
|
||||
const float clearColor[4],
|
||||
const UiRenderCallback& renderUi,
|
||||
const RenderCallback& beforeUiRender = {},
|
||||
const RenderCallback& afterUiRender = {}) = 0;
|
||||
virtual bool CreateTextureDescriptor(
|
||||
::XCEngine::RHI::RHIDevice* device,
|
||||
::XCEngine::RHI::RHITexture* texture,
|
||||
UITextureRegistration& outRegistration) = 0;
|
||||
virtual void FreeTextureDescriptor(const UITextureRegistration& registration) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IWindowUICompositor> CreateNativeWindowUICompositor();
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,90 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
class IXCUITextAtlasProvider {
|
||||
public:
|
||||
enum class PixelFormat : std::uint8_t {
|
||||
Unknown = 0,
|
||||
Alpha8,
|
||||
RGBA32
|
||||
};
|
||||
|
||||
struct AtlasTextureView {
|
||||
const unsigned char* pixels = nullptr;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int stride = 0;
|
||||
int bytesPerPixel = 0;
|
||||
PixelFormat format = PixelFormat::Unknown;
|
||||
// Stable cache key for the logical atlas storage owned by the provider.
|
||||
std::uintptr_t atlasStorageKey = 0;
|
||||
// Cache key for the currently exposed pixel buffer. This may change independently
|
||||
// from atlasStorageKey when the provider re-packs or re-uploads atlas pixels.
|
||||
std::uintptr_t pixelDataKey = 0;
|
||||
|
||||
bool IsValid() const {
|
||||
return pixels != nullptr && width > 0 && height > 0 && bytesPerPixel > 0 && stride > 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Opaque font handle. Callers must treat this as provider-scoped data and must not
|
||||
// assume it remains valid after the provider swaps to a different atlas generation.
|
||||
struct FontHandle {
|
||||
std::uintptr_t value = 0;
|
||||
|
||||
bool IsValid() const {
|
||||
return value != 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct FontInfo {
|
||||
FontHandle handle = {};
|
||||
float nominalSize = 0.0f;
|
||||
};
|
||||
|
||||
struct BakedFontInfo {
|
||||
float lineHeight = 0.0f;
|
||||
float ascent = 0.0f;
|
||||
float descent = 0.0f;
|
||||
float rasterizerDensity = 0.0f;
|
||||
};
|
||||
|
||||
struct GlyphInfo {
|
||||
std::uint32_t requestedCodepoint = 0;
|
||||
std::uint32_t resolvedCodepoint = 0;
|
||||
bool visible = false;
|
||||
bool colored = false;
|
||||
float advanceX = 0.0f;
|
||||
float x0 = 0.0f;
|
||||
float y0 = 0.0f;
|
||||
float x1 = 0.0f;
|
||||
float y1 = 0.0f;
|
||||
float u0 = 0.0f;
|
||||
float v0 = 0.0f;
|
||||
float u1 = 0.0f;
|
||||
float v1 = 0.0f;
|
||||
};
|
||||
|
||||
virtual ~IXCUITextAtlasProvider() = default;
|
||||
|
||||
// Returns false until atlas pixels and corresponding font/glyph metadata are ready.
|
||||
// Callers may request a preferred pixel format, but providers may return a different
|
||||
// format if only one backing representation is available.
|
||||
virtual bool GetAtlasTextureView(PixelFormat preferredFormat, AtlasTextureView& outView) const = 0;
|
||||
virtual std::size_t GetFontCount() const = 0;
|
||||
virtual FontHandle GetFont(std::size_t index) const = 0;
|
||||
virtual FontHandle GetDefaultFont() const = 0;
|
||||
virtual bool GetFontInfo(FontHandle font, FontInfo& outInfo) const = 0;
|
||||
virtual bool GetBakedFontInfo(FontHandle font, float fontSize, BakedFontInfo& outInfo) const = 0;
|
||||
virtual bool FindGlyph(FontHandle font, float fontSize, std::uint32_t codepoint, GlyphInfo& outInfo) const = 0;
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,111 +0,0 @@
|
||||
#include "XCUIBackend/IEditorHostCompositor.h"
|
||||
|
||||
#include "Platform/D3D12WindowRendererImGuiInterop.h"
|
||||
#include "UI/ImGuiBackendBridge.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
namespace {
|
||||
|
||||
class ImGuiHostCompositor final : public IEditorHostCompositor {
|
||||
public:
|
||||
bool Initialize(
|
||||
HWND hwnd,
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer& windowRenderer,
|
||||
const ConfigureFontsCallback& configureFonts) override {
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
|
||||
if (configureFonts) {
|
||||
configureFonts();
|
||||
}
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
m_backend.Initialize(
|
||||
hwnd,
|
||||
windowRenderer.GetDevice(),
|
||||
windowRenderer.GetCommandQueue(),
|
||||
windowRenderer.GetSrvHeap(),
|
||||
windowRenderer.GetSrvDescriptorSize(),
|
||||
windowRenderer.GetSrvDescriptorCount());
|
||||
return true;
|
||||
}
|
||||
|
||||
void Shutdown() override {
|
||||
m_backend.Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
|
||||
bool HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) override {
|
||||
return ::XCEngine::Editor::UI::ImGuiBackendBridge::HandleWindowMessage(
|
||||
hwnd,
|
||||
message,
|
||||
wParam,
|
||||
lParam);
|
||||
}
|
||||
|
||||
void BeginFrame() override {
|
||||
m_backend.BeginFrame();
|
||||
}
|
||||
|
||||
void EndFrameAndPresent(
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer& windowRenderer,
|
||||
const float clearColor[4],
|
||||
const RenderCallback& beforeUiRender,
|
||||
const RenderCallback& afterUiRender) override {
|
||||
ImGui::Render();
|
||||
::XCEngine::Editor::Platform::RenderImGuiFrame(
|
||||
windowRenderer,
|
||||
m_backend,
|
||||
clearColor,
|
||||
beforeUiRender,
|
||||
afterUiRender);
|
||||
}
|
||||
|
||||
bool CreateTextureDescriptor(
|
||||
::XCEngine::RHI::RHIDevice* device,
|
||||
::XCEngine::RHI::RHITexture* texture,
|
||||
UITextureRegistration& outRegistration) override {
|
||||
ImTextureID textureId = {};
|
||||
if (!m_backend.CreateTextureDescriptor(
|
||||
device,
|
||||
texture,
|
||||
&outRegistration.cpuHandle,
|
||||
&outRegistration.gpuHandle,
|
||||
&textureId)) {
|
||||
outRegistration = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
outRegistration.texture.nativeHandle = (std::uintptr_t)textureId;
|
||||
outRegistration.texture.width = texture != nullptr ? texture->GetWidth() : 0u;
|
||||
outRegistration.texture.height = texture != nullptr ? texture->GetHeight() : 0u;
|
||||
outRegistration.texture.kind = ::XCEngine::UI::UITextureHandleKind::ImGuiDescriptor;
|
||||
return outRegistration.IsValid();
|
||||
}
|
||||
|
||||
void FreeTextureDescriptor(const UITextureRegistration& registration) override {
|
||||
m_backend.FreeTextureDescriptor(registration.cpuHandle, registration.gpuHandle);
|
||||
}
|
||||
|
||||
private:
|
||||
::XCEngine::Editor::UI::ImGuiBackendBridge m_backend;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<IEditorHostCompositor> CreateImGuiHostCompositor() {
|
||||
return std::make_unique<ImGuiHostCompositor>();
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,223 +0,0 @@
|
||||
#include "XCUIBackend/ImGuiTextAtlasProvider.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
namespace {
|
||||
|
||||
IXCUITextAtlasProvider::FontHandle MakeFontHandle(const ImFont* font) {
|
||||
IXCUITextAtlasProvider::FontHandle handle = {};
|
||||
handle.value = reinterpret_cast<std::uintptr_t>(font);
|
||||
return handle;
|
||||
}
|
||||
|
||||
ImFont* ResolveFontHandle(IXCUITextAtlasProvider::FontHandle handle) {
|
||||
return reinterpret_cast<ImFont*>(handle.value);
|
||||
}
|
||||
|
||||
ImFontAtlas* ResolveAtlas(::ImGuiContext* context) {
|
||||
if (context == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return context->IO.Fonts;
|
||||
}
|
||||
|
||||
ImFont* ResolveDefaultFont(::ImGuiContext* context) {
|
||||
ImFontAtlas* atlas = ResolveAtlas(context);
|
||||
if (atlas == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ImFont* font = context->IO.FontDefault;
|
||||
if (font == nullptr && atlas->Fonts.Size > 0) {
|
||||
font = atlas->Fonts[0];
|
||||
}
|
||||
return font;
|
||||
}
|
||||
|
||||
float ResolveNominalFontSize(const ImFont& font) {
|
||||
return font.LegacySize > 0.0f ? font.LegacySize : 16.0f;
|
||||
}
|
||||
|
||||
float ResolveRequestedFontSize(const ImFont& font, float requestedFontSize) {
|
||||
return requestedFontSize > 0.0f ? requestedFontSize : ResolveNominalFontSize(font);
|
||||
}
|
||||
|
||||
bool IsFontOwnedByAtlas(const ImFont* font, const ImFontAtlas* atlas) {
|
||||
return font != nullptr && atlas != nullptr && font->OwnerAtlas == atlas;
|
||||
}
|
||||
|
||||
ImFontBaked* ResolveBakedFont(
|
||||
IXCUITextAtlasProvider::FontHandle fontHandle,
|
||||
ImFontAtlas* atlas,
|
||||
float requestedFontSize) {
|
||||
ImFont* font = ResolveFontHandle(fontHandle);
|
||||
if (!IsFontOwnedByAtlas(font, atlas)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const float resolvedFontSize = ResolveRequestedFontSize(*font, requestedFontSize);
|
||||
if (resolvedFontSize <= 0.0f) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return font->GetFontBaked(resolvedFontSize);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ImGuiTextAtlasProvider::ImGuiTextAtlasProvider(::ImGuiContext* context)
|
||||
: m_context(context) {
|
||||
}
|
||||
|
||||
void ImGuiTextAtlasProvider::SetContext(::ImGuiContext* context) {
|
||||
m_context = context;
|
||||
}
|
||||
|
||||
::ImGuiContext* ImGuiTextAtlasProvider::GetContext() const {
|
||||
return m_context;
|
||||
}
|
||||
|
||||
bool ImGuiTextAtlasProvider::GetAtlasTextureView(
|
||||
PixelFormat preferredFormat,
|
||||
AtlasTextureView& outView) const {
|
||||
outView = {};
|
||||
|
||||
ImFontAtlas* atlas = ResolveAtlas(ResolveContext());
|
||||
if (atlas == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char* pixels = nullptr;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int bytesPerPixel = 0;
|
||||
PixelFormat resolvedFormat = preferredFormat;
|
||||
|
||||
switch (preferredFormat) {
|
||||
case PixelFormat::Alpha8:
|
||||
atlas->GetTexDataAsAlpha8(&pixels, &width, &height, &bytesPerPixel);
|
||||
break;
|
||||
case PixelFormat::RGBA32:
|
||||
case PixelFormat::Unknown:
|
||||
default:
|
||||
atlas->GetTexDataAsRGBA32(&pixels, &width, &height, &bytesPerPixel);
|
||||
resolvedFormat = PixelFormat::RGBA32;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pixels == nullptr || width <= 0 || height <= 0 || bytesPerPixel <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outView.pixels = pixels;
|
||||
outView.width = width;
|
||||
outView.height = height;
|
||||
outView.stride = width * bytesPerPixel;
|
||||
outView.bytesPerPixel = bytesPerPixel;
|
||||
outView.format = resolvedFormat;
|
||||
outView.atlasStorageKey = reinterpret_cast<std::uintptr_t>(atlas);
|
||||
outView.pixelDataKey = reinterpret_cast<std::uintptr_t>(pixels);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t ImGuiTextAtlasProvider::GetFontCount() const {
|
||||
const ImFontAtlas* atlas = ResolveAtlas(ResolveContext());
|
||||
return atlas != nullptr ? static_cast<std::size_t>(atlas->Fonts.Size) : 0u;
|
||||
}
|
||||
|
||||
IXCUITextAtlasProvider::FontHandle ImGuiTextAtlasProvider::GetFont(std::size_t index) const {
|
||||
const ImFontAtlas* atlas = ResolveAtlas(ResolveContext());
|
||||
if (atlas == nullptr || index >= static_cast<std::size_t>(atlas->Fonts.Size)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return MakeFontHandle(atlas->Fonts[static_cast<int>(index)]);
|
||||
}
|
||||
|
||||
IXCUITextAtlasProvider::FontHandle ImGuiTextAtlasProvider::GetDefaultFont() const {
|
||||
return MakeFontHandle(ResolveDefaultFont(ResolveContext()));
|
||||
}
|
||||
|
||||
bool ImGuiTextAtlasProvider::GetFontInfo(FontHandle fontHandle, FontInfo& outInfo) const {
|
||||
outInfo = {};
|
||||
|
||||
ImFontAtlas* atlas = ResolveAtlas(ResolveContext());
|
||||
ImFont* font = ResolveFontHandle(fontHandle);
|
||||
if (!IsFontOwnedByAtlas(font, atlas)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outInfo.handle = fontHandle;
|
||||
outInfo.nominalSize = ResolveNominalFontSize(*font);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ImGuiTextAtlasProvider::GetBakedFontInfo(
|
||||
FontHandle fontHandle,
|
||||
float fontSize,
|
||||
BakedFontInfo& outInfo) const {
|
||||
outInfo = {};
|
||||
|
||||
ImFontAtlas* atlas = ResolveAtlas(ResolveContext());
|
||||
ImFontBaked* bakedFont = ResolveBakedFont(fontHandle, atlas, fontSize);
|
||||
if (bakedFont == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outInfo.lineHeight = bakedFont->Size;
|
||||
outInfo.ascent = bakedFont->Ascent;
|
||||
outInfo.descent = bakedFont->Descent;
|
||||
outInfo.rasterizerDensity = bakedFont->RasterizerDensity;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ImGuiTextAtlasProvider::FindGlyph(
|
||||
FontHandle fontHandle,
|
||||
float fontSize,
|
||||
std::uint32_t codepoint,
|
||||
GlyphInfo& outInfo) const {
|
||||
outInfo = {};
|
||||
|
||||
if (codepoint > IM_UNICODE_CODEPOINT_MAX) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ImFontAtlas* atlas = ResolveAtlas(ResolveContext());
|
||||
ImFontBaked* bakedFont = ResolveBakedFont(fontHandle, atlas, fontSize);
|
||||
if (bakedFont == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ImFontGlyph* glyph = bakedFont->FindGlyph(static_cast<ImWchar>(codepoint));
|
||||
if (glyph == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outInfo.requestedCodepoint = codepoint;
|
||||
outInfo.resolvedCodepoint = glyph->Codepoint;
|
||||
outInfo.visible = glyph->Visible != 0;
|
||||
outInfo.colored = glyph->Colored != 0;
|
||||
outInfo.advanceX = glyph->AdvanceX;
|
||||
outInfo.x0 = glyph->X0;
|
||||
outInfo.y0 = glyph->Y0;
|
||||
outInfo.x1 = glyph->X1;
|
||||
outInfo.y1 = glyph->Y1;
|
||||
outInfo.u0 = glyph->U0;
|
||||
outInfo.v0 = glyph->V0;
|
||||
outInfo.u1 = glyph->U1;
|
||||
outInfo.v1 = glyph->V1;
|
||||
return true;
|
||||
}
|
||||
|
||||
::ImGuiContext* ImGuiTextAtlasProvider::ResolveContext() const {
|
||||
return m_context;
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,35 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "IXCUITextAtlasProvider.h"
|
||||
|
||||
struct ImGuiContext;
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
class ImGuiTextAtlasProvider final : public IXCUITextAtlasProvider {
|
||||
public:
|
||||
ImGuiTextAtlasProvider() = default;
|
||||
explicit ImGuiTextAtlasProvider(::ImGuiContext* context);
|
||||
|
||||
void SetContext(::ImGuiContext* context);
|
||||
::ImGuiContext* GetContext() const;
|
||||
|
||||
bool GetAtlasTextureView(PixelFormat preferredFormat, AtlasTextureView& outView) const override;
|
||||
std::size_t GetFontCount() const override;
|
||||
FontHandle GetFont(std::size_t index) const override;
|
||||
FontHandle GetDefaultFont() const override;
|
||||
bool GetFontInfo(FontHandle font, FontInfo& outInfo) const override;
|
||||
bool GetBakedFontInfo(FontHandle font, float fontSize, BakedFontInfo& outInfo) const override;
|
||||
bool FindGlyph(FontHandle font, float fontSize, std::uint32_t codepoint, GlyphInfo& outInfo) const override;
|
||||
|
||||
private:
|
||||
::ImGuiContext* ResolveContext() const;
|
||||
|
||||
::ImGuiContext* m_context = nullptr;
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,193 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
class ImGuiTransitionBackend {
|
||||
public:
|
||||
void BeginFrame() {
|
||||
m_lastFlushedDrawListCount = 0;
|
||||
m_lastFlushedCommandCount = 0;
|
||||
m_pendingCommandCount = 0;
|
||||
m_pendingDrawLists.clear();
|
||||
}
|
||||
|
||||
void Submit(const ::XCEngine::UI::UIDrawList& drawList) {
|
||||
m_pendingCommandCount += drawList.GetCommandCount();
|
||||
m_pendingDrawLists.push_back(drawList);
|
||||
}
|
||||
|
||||
void Submit(::XCEngine::UI::UIDrawList&& drawList) {
|
||||
m_pendingCommandCount += drawList.GetCommandCount();
|
||||
m_pendingDrawLists.push_back(std::move(drawList));
|
||||
}
|
||||
|
||||
void Submit(const ::XCEngine::UI::UIDrawData& drawData) {
|
||||
for (const ::XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
|
||||
Submit(drawList);
|
||||
}
|
||||
}
|
||||
|
||||
bool HasPendingDrawData() const {
|
||||
return !m_pendingDrawLists.empty();
|
||||
}
|
||||
|
||||
std::size_t GetPendingDrawListCount() const {
|
||||
return m_pendingDrawLists.size();
|
||||
}
|
||||
|
||||
std::size_t GetPendingCommandCount() const {
|
||||
return m_pendingCommandCount;
|
||||
}
|
||||
|
||||
std::size_t GetLastFlushedDrawListCount() const {
|
||||
return m_lastFlushedDrawListCount;
|
||||
}
|
||||
|
||||
std::size_t GetLastFlushedCommandCount() const {
|
||||
return m_lastFlushedCommandCount;
|
||||
}
|
||||
|
||||
bool EndFrame(ImDrawList* targetDrawList = nullptr) {
|
||||
ImDrawList* drawList = targetDrawList != nullptr ? targetDrawList : ImGui::GetWindowDrawList();
|
||||
if (drawList == nullptr) {
|
||||
ClearPendingState();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t clipDepth = 0;
|
||||
for (const ::XCEngine::UI::UIDrawList& pendingDrawList : m_pendingDrawLists) {
|
||||
for (const ::XCEngine::UI::UIDrawCommand& command : pendingDrawList.GetCommands()) {
|
||||
RenderCommand(*drawList, command, clipDepth);
|
||||
}
|
||||
}
|
||||
|
||||
while (clipDepth > 0) {
|
||||
drawList->PopClipRect();
|
||||
--clipDepth;
|
||||
}
|
||||
|
||||
m_lastFlushedDrawListCount = m_pendingDrawLists.size();
|
||||
m_lastFlushedCommandCount = m_pendingCommandCount;
|
||||
ClearPendingState();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
static ImVec2 ToImVec2(const ::XCEngine::UI::UIPoint& point) {
|
||||
return ImVec2(point.x, point.y);
|
||||
}
|
||||
|
||||
static ImVec2 ToImVec2Min(const ::XCEngine::UI::UIRect& rect) {
|
||||
return ImVec2(rect.x, rect.y);
|
||||
}
|
||||
|
||||
static ImVec2 ToImVec2Max(const ::XCEngine::UI::UIRect& rect) {
|
||||
return ImVec2(rect.x + rect.width, rect.y + rect.height);
|
||||
}
|
||||
|
||||
static ImTextureID ToImTextureId(const ::XCEngine::UI::UITextureHandle& texture) {
|
||||
if (texture.kind != ::XCEngine::UI::UITextureHandleKind::ImGuiDescriptor) {
|
||||
return static_cast<ImTextureID>(0);
|
||||
}
|
||||
return static_cast<ImTextureID>(texture.nativeHandle);
|
||||
}
|
||||
|
||||
static ImU32 ToImU32(const ::XCEngine::UI::UIColor& color) {
|
||||
const float r = (std::max)(0.0f, (std::min)(1.0f, color.r));
|
||||
const float g = (std::max)(0.0f, (std::min)(1.0f, color.g));
|
||||
const float b = (std::max)(0.0f, (std::min)(1.0f, color.b));
|
||||
const float a = (std::max)(0.0f, (std::min)(1.0f, color.a));
|
||||
return IM_COL32(
|
||||
static_cast<int>(r * 255.0f),
|
||||
static_cast<int>(g * 255.0f),
|
||||
static_cast<int>(b * 255.0f),
|
||||
static_cast<int>(a * 255.0f));
|
||||
}
|
||||
|
||||
static void RenderCommand(
|
||||
ImDrawList& drawList,
|
||||
const ::XCEngine::UI::UIDrawCommand& command,
|
||||
std::size_t& clipDepth) {
|
||||
switch (command.type) {
|
||||
case ::XCEngine::UI::UIDrawCommandType::FilledRect:
|
||||
drawList.AddRectFilled(
|
||||
ToImVec2Min(command.rect),
|
||||
ToImVec2Max(command.rect),
|
||||
ToImU32(command.color),
|
||||
command.rounding);
|
||||
break;
|
||||
case ::XCEngine::UI::UIDrawCommandType::RectOutline:
|
||||
drawList.AddRect(
|
||||
ToImVec2Min(command.rect),
|
||||
ToImVec2Max(command.rect),
|
||||
ToImU32(command.color),
|
||||
command.rounding,
|
||||
0,
|
||||
command.thickness > 0.0f ? command.thickness : 1.0f);
|
||||
break;
|
||||
case ::XCEngine::UI::UIDrawCommandType::Text:
|
||||
if (!command.text.empty()) {
|
||||
ImFont* font = ImGui::GetFont();
|
||||
drawList.AddText(
|
||||
font,
|
||||
command.fontSize > 0.0f ? command.fontSize : ImGui::GetFontSize(),
|
||||
ToImVec2(command.position),
|
||||
ToImU32(command.color),
|
||||
command.text.c_str());
|
||||
}
|
||||
break;
|
||||
case ::XCEngine::UI::UIDrawCommandType::Image:
|
||||
if (command.texture.IsValid() &&
|
||||
command.texture.kind == ::XCEngine::UI::UITextureHandleKind::ImGuiDescriptor) {
|
||||
drawList.AddImage(
|
||||
ToImTextureId(command.texture),
|
||||
ToImVec2Min(command.rect),
|
||||
ToImVec2Max(command.rect),
|
||||
ImVec2(0.0f, 0.0f),
|
||||
ImVec2(1.0f, 1.0f),
|
||||
ToImU32(command.color));
|
||||
}
|
||||
break;
|
||||
case ::XCEngine::UI::UIDrawCommandType::PushClipRect:
|
||||
drawList.PushClipRect(
|
||||
ToImVec2Min(command.rect),
|
||||
ToImVec2Max(command.rect),
|
||||
command.intersectWithCurrentClip);
|
||||
++clipDepth;
|
||||
break;
|
||||
case ::XCEngine::UI::UIDrawCommandType::PopClipRect:
|
||||
if (clipDepth > 0) {
|
||||
drawList.PopClipRect();
|
||||
--clipDepth;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ClearPendingState() {
|
||||
m_pendingCommandCount = 0;
|
||||
m_pendingDrawLists.clear();
|
||||
}
|
||||
|
||||
std::vector<::XCEngine::UI::UIDrawList> m_pendingDrawLists;
|
||||
std::size_t m_pendingCommandCount = 0;
|
||||
std::size_t m_lastFlushedDrawListCount = 0;
|
||||
std::size_t m_lastFlushedCommandCount = 0;
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,84 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCUIBackend/IEditorHostCompositor.h"
|
||||
#include "XCUIBackend/IWindowUICompositor.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
class ImGuiWindowUICompositor final : public IWindowUICompositor {
|
||||
public:
|
||||
explicit ImGuiWindowUICompositor(
|
||||
std::unique_ptr<IEditorHostCompositor> hostCompositor)
|
||||
: m_hostCompositor(std::move(hostCompositor)) {
|
||||
}
|
||||
|
||||
bool Initialize(
|
||||
HWND hwnd,
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer& windowRenderer,
|
||||
const ConfigureFontsCallback& configureFonts) override {
|
||||
if (m_hostCompositor == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_windowRenderer = &windowRenderer;
|
||||
return m_hostCompositor->Initialize(hwnd, windowRenderer, configureFonts);
|
||||
}
|
||||
|
||||
void Shutdown() override {
|
||||
if (m_hostCompositor != nullptr) {
|
||||
m_hostCompositor->Shutdown();
|
||||
}
|
||||
m_windowRenderer = nullptr;
|
||||
}
|
||||
|
||||
bool HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) override {
|
||||
return m_hostCompositor != nullptr &&
|
||||
m_hostCompositor->HandleWindowMessage(hwnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
void RenderFrame(
|
||||
const float clearColor[4],
|
||||
const UiRenderCallback& renderUi,
|
||||
const RenderCallback& beforeUiRender,
|
||||
const RenderCallback& afterUiRender) override {
|
||||
if (m_hostCompositor == nullptr || m_windowRenderer == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_hostCompositor->BeginFrame();
|
||||
if (renderUi) {
|
||||
renderUi();
|
||||
}
|
||||
m_hostCompositor->EndFrameAndPresent(*m_windowRenderer, clearColor, beforeUiRender, afterUiRender);
|
||||
}
|
||||
|
||||
bool CreateTextureDescriptor(
|
||||
::XCEngine::RHI::RHIDevice* device,
|
||||
::XCEngine::RHI::RHITexture* texture,
|
||||
UITextureRegistration& outRegistration) override {
|
||||
return m_hostCompositor != nullptr &&
|
||||
m_hostCompositor->CreateTextureDescriptor(
|
||||
device,
|
||||
texture,
|
||||
outRegistration);
|
||||
}
|
||||
|
||||
void FreeTextureDescriptor(const UITextureRegistration& registration) override {
|
||||
if (m_hostCompositor != nullptr) {
|
||||
m_hostCompositor->FreeTextureDescriptor(registration);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer* m_windowRenderer = nullptr;
|
||||
std::unique_ptr<IEditorHostCompositor> m_hostCompositor;
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,89 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCUIBackend/ImGuiTransitionBackend.h"
|
||||
#include "XCUIBackend/XCUIHostedPreviewPresenter.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
class IImGuiXCUIHostedPreviewTargetBinding {
|
||||
public:
|
||||
virtual ~IImGuiXCUIHostedPreviewTargetBinding() = default;
|
||||
|
||||
virtual ImDrawList* ResolveTargetDrawList(const XCUIHostedPreviewFrame& frame) const = 0;
|
||||
};
|
||||
|
||||
class ImGuiCurrentWindowXCUIHostedPreviewTargetBinding final
|
||||
: public IImGuiXCUIHostedPreviewTargetBinding {
|
||||
public:
|
||||
ImDrawList* ResolveTargetDrawList(const XCUIHostedPreviewFrame& frame) const override {
|
||||
(void)frame;
|
||||
return ImGui::GetWindowDrawList();
|
||||
}
|
||||
};
|
||||
|
||||
class ImGuiXCUIHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter {
|
||||
public:
|
||||
explicit ImGuiXCUIHostedPreviewPresenter(
|
||||
std::unique_ptr<IImGuiXCUIHostedPreviewTargetBinding> targetBinding = {})
|
||||
: m_targetBinding(std::move(targetBinding)) {
|
||||
if (m_targetBinding == nullptr) {
|
||||
m_targetBinding = std::make_unique<ImGuiCurrentWindowXCUIHostedPreviewTargetBinding>();
|
||||
}
|
||||
}
|
||||
|
||||
bool Present(const XCUIHostedPreviewFrame& frame) override {
|
||||
m_lastStats = {};
|
||||
if (frame.drawData == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_backend.BeginFrame();
|
||||
m_backend.Submit(*frame.drawData);
|
||||
m_lastStats.submittedDrawListCount = m_backend.GetPendingDrawListCount();
|
||||
m_lastStats.submittedCommandCount = m_backend.GetPendingCommandCount();
|
||||
ImDrawList* targetDrawList =
|
||||
m_targetBinding != nullptr ? m_targetBinding->ResolveTargetDrawList(frame) : nullptr;
|
||||
if (targetDrawList == nullptr) {
|
||||
m_backend.BeginFrame();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_lastStats.presented = m_backend.EndFrame(targetDrawList);
|
||||
m_lastStats.flushedDrawListCount = m_backend.GetLastFlushedDrawListCount();
|
||||
m_lastStats.flushedCommandCount = m_backend.GetLastFlushedCommandCount();
|
||||
return m_lastStats.presented;
|
||||
}
|
||||
|
||||
const XCUIHostedPreviewStats& GetLastStats() const override {
|
||||
return m_lastStats;
|
||||
}
|
||||
|
||||
private:
|
||||
ImGuiTransitionBackend m_backend = {};
|
||||
std::unique_ptr<IImGuiXCUIHostedPreviewTargetBinding> m_targetBinding = {};
|
||||
XCUIHostedPreviewStats m_lastStats = {};
|
||||
};
|
||||
|
||||
inline std::unique_ptr<IImGuiXCUIHostedPreviewTargetBinding>
|
||||
CreateImGuiCurrentWindowXCUIHostedPreviewTargetBinding() {
|
||||
return std::make_unique<ImGuiCurrentWindowXCUIHostedPreviewTargetBinding>();
|
||||
}
|
||||
|
||||
inline std::unique_ptr<IXCUIHostedPreviewPresenter> CreateImGuiXCUIHostedPreviewPresenter(
|
||||
std::unique_ptr<IImGuiXCUIHostedPreviewTargetBinding> targetBinding) {
|
||||
return std::make_unique<ImGuiXCUIHostedPreviewPresenter>(std::move(targetBinding));
|
||||
}
|
||||
|
||||
inline std::unique_ptr<IXCUIHostedPreviewPresenter> CreateImGuiXCUIHostedPreviewPresenter() {
|
||||
return CreateImGuiXCUIHostedPreviewPresenter(CreateImGuiCurrentWindowXCUIHostedPreviewTargetBinding());
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,226 +0,0 @@
|
||||
#include "XCUIBackend/ImGuiXCUIInputAdapter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Input::KeyCode;
|
||||
|
||||
struct ImGuiKeyMappingEntry {
|
||||
ImGuiKey imguiKey = ImGuiKey_None;
|
||||
KeyCode keyCode = KeyCode::None;
|
||||
};
|
||||
|
||||
constexpr ImGuiKeyMappingEntry kImGuiKeyMappings[] = {
|
||||
{ ImGuiKey_A, KeyCode::A },
|
||||
{ ImGuiKey_B, KeyCode::B },
|
||||
{ ImGuiKey_C, KeyCode::C },
|
||||
{ ImGuiKey_D, KeyCode::D },
|
||||
{ ImGuiKey_E, KeyCode::E },
|
||||
{ ImGuiKey_F, KeyCode::F },
|
||||
{ ImGuiKey_G, KeyCode::G },
|
||||
{ ImGuiKey_H, KeyCode::H },
|
||||
{ ImGuiKey_I, KeyCode::I },
|
||||
{ ImGuiKey_J, KeyCode::J },
|
||||
{ ImGuiKey_K, KeyCode::K },
|
||||
{ ImGuiKey_L, KeyCode::L },
|
||||
{ ImGuiKey_M, KeyCode::M },
|
||||
{ ImGuiKey_N, KeyCode::N },
|
||||
{ ImGuiKey_O, KeyCode::O },
|
||||
{ ImGuiKey_P, KeyCode::P },
|
||||
{ ImGuiKey_Q, KeyCode::Q },
|
||||
{ ImGuiKey_R, KeyCode::R },
|
||||
{ ImGuiKey_S, KeyCode::S },
|
||||
{ ImGuiKey_T, KeyCode::T },
|
||||
{ ImGuiKey_U, KeyCode::U },
|
||||
{ ImGuiKey_V, KeyCode::V },
|
||||
{ ImGuiKey_W, KeyCode::W },
|
||||
{ ImGuiKey_X, KeyCode::X },
|
||||
{ ImGuiKey_Y, KeyCode::Y },
|
||||
{ ImGuiKey_Z, KeyCode::Z },
|
||||
{ ImGuiKey_0, KeyCode::Zero },
|
||||
{ ImGuiKey_1, KeyCode::One },
|
||||
{ ImGuiKey_2, KeyCode::Two },
|
||||
{ ImGuiKey_3, KeyCode::Three },
|
||||
{ ImGuiKey_4, KeyCode::Four },
|
||||
{ ImGuiKey_5, KeyCode::Five },
|
||||
{ ImGuiKey_6, KeyCode::Six },
|
||||
{ ImGuiKey_7, KeyCode::Seven },
|
||||
{ ImGuiKey_8, KeyCode::Eight },
|
||||
{ ImGuiKey_9, KeyCode::Nine },
|
||||
{ ImGuiKey_Space, KeyCode::Space },
|
||||
{ ImGuiKey_Tab, KeyCode::Tab },
|
||||
{ ImGuiKey_Enter, KeyCode::Enter },
|
||||
{ ImGuiKey_KeypadEnter, KeyCode::Enter },
|
||||
{ ImGuiKey_Escape, KeyCode::Escape },
|
||||
{ ImGuiKey_LeftShift, KeyCode::LeftShift },
|
||||
{ ImGuiKey_RightShift, KeyCode::RightShift },
|
||||
{ ImGuiKey_LeftCtrl, KeyCode::LeftCtrl },
|
||||
{ ImGuiKey_RightCtrl, KeyCode::RightCtrl },
|
||||
{ ImGuiKey_LeftAlt, KeyCode::LeftAlt },
|
||||
{ ImGuiKey_RightAlt, KeyCode::RightAlt },
|
||||
{ ImGuiKey_UpArrow, KeyCode::Up },
|
||||
{ ImGuiKey_DownArrow, KeyCode::Down },
|
||||
{ ImGuiKey_LeftArrow, KeyCode::Left },
|
||||
{ ImGuiKey_RightArrow, KeyCode::Right },
|
||||
{ ImGuiKey_Home, KeyCode::Home },
|
||||
{ ImGuiKey_End, KeyCode::End },
|
||||
{ ImGuiKey_PageUp, KeyCode::PageUp },
|
||||
{ ImGuiKey_PageDown, KeyCode::PageDown },
|
||||
{ ImGuiKey_Delete, KeyCode::Delete },
|
||||
{ ImGuiKey_Backspace, KeyCode::Backspace },
|
||||
{ ImGuiKey_F1, KeyCode::F1 },
|
||||
{ ImGuiKey_F2, KeyCode::F2 },
|
||||
{ ImGuiKey_F3, KeyCode::F3 },
|
||||
{ ImGuiKey_F4, KeyCode::F4 },
|
||||
{ ImGuiKey_F5, KeyCode::F5 },
|
||||
{ ImGuiKey_F6, KeyCode::F6 },
|
||||
{ ImGuiKey_F7, KeyCode::F7 },
|
||||
{ ImGuiKey_F8, KeyCode::F8 },
|
||||
{ ImGuiKey_F9, KeyCode::F9 },
|
||||
{ ImGuiKey_F10, KeyCode::F10 },
|
||||
{ ImGuiKey_F11, KeyCode::F11 },
|
||||
{ ImGuiKey_F12, KeyCode::F12 },
|
||||
{ ImGuiKey_Minus, KeyCode::Minus },
|
||||
{ ImGuiKey_Equal, KeyCode::Equals },
|
||||
{ ImGuiKey_LeftBracket, KeyCode::BracketLeft },
|
||||
{ ImGuiKey_RightBracket, KeyCode::BracketRight },
|
||||
{ ImGuiKey_Semicolon, KeyCode::Semicolon },
|
||||
{ ImGuiKey_Apostrophe, KeyCode::Quote },
|
||||
{ ImGuiKey_Comma, KeyCode::Comma },
|
||||
{ ImGuiKey_Period, KeyCode::Period },
|
||||
{ ImGuiKey_Slash, KeyCode::Slash },
|
||||
{ ImGuiKey_Backslash, KeyCode::Backslash },
|
||||
{ ImGuiKey_GraveAccent, KeyCode::Backtick },
|
||||
};
|
||||
|
||||
bool IsPointerPositionValid(const ImVec2& position) {
|
||||
return std::isfinite(position.x) &&
|
||||
std::isfinite(position.y) &&
|
||||
position.x > -std::numeric_limits<float>::max() * 0.5f &&
|
||||
position.y > -std::numeric_limits<float>::max() * 0.5f;
|
||||
}
|
||||
|
||||
bool SnapshotHasKeyRepeat(const ImGuiIO& io, ImGuiKey key) {
|
||||
if (key < ImGuiKey_NamedKey_BEGIN || key >= ImGuiKey_NamedKey_END) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ImGuiKeyData& data = io.KeysData[key - ImGuiKey_NamedKey_BEGIN];
|
||||
return data.Down &&
|
||||
data.DownDurationPrev >= 0.0f &&
|
||||
ImGui::IsKeyPressed(key, true);
|
||||
}
|
||||
|
||||
void UpsertKeyState(
|
||||
std::vector<XCUIInputBridgeKeyState>& states,
|
||||
std::int32_t keyCode,
|
||||
bool down,
|
||||
bool repeat) {
|
||||
if (keyCode == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (XCUIInputBridgeKeyState& state : states) {
|
||||
if (state.keyCode != keyCode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
state.down = state.down || down;
|
||||
state.repeat = state.repeat || repeat;
|
||||
return;
|
||||
}
|
||||
|
||||
XCUIInputBridgeKeyState state = {};
|
||||
state.keyCode = keyCode;
|
||||
state.down = down;
|
||||
state.repeat = repeat;
|
||||
states.push_back(state);
|
||||
}
|
||||
|
||||
void SortSnapshotKeys(XCUIInputBridgeFrameSnapshot& snapshot) {
|
||||
std::sort(
|
||||
snapshot.keys.begin(),
|
||||
snapshot.keys.end(),
|
||||
[](const XCUIInputBridgeKeyState& lhs, const XCUIInputBridgeKeyState& rhs) {
|
||||
return lhs.keyCode < rhs.keyCode;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
XCUIInputBridgeFrameSnapshot ImGuiXCUIInputAdapter::CaptureSnapshot(
|
||||
const ImGuiIO& io,
|
||||
const XCUIInputBridgeCaptureOptions& options) {
|
||||
XCUIInputBridgeFrameSnapshot snapshot = {};
|
||||
snapshot.pointerPosition = UI::UIPoint(
|
||||
io.MousePos.x - options.pointerOffset.x,
|
||||
io.MousePos.y - options.pointerOffset.y);
|
||||
snapshot.pointerInside = options.hasPointerInsideOverride
|
||||
? options.pointerInsideOverride
|
||||
: IsPointerPositionValid(io.MousePos);
|
||||
snapshot.wheelDelta = UI::UIPoint(io.MouseWheelH, io.MouseWheel);
|
||||
snapshot.modifiers.shift = io.KeyShift;
|
||||
snapshot.modifiers.control = io.KeyCtrl;
|
||||
snapshot.modifiers.alt = io.KeyAlt;
|
||||
snapshot.modifiers.super = io.KeySuper;
|
||||
snapshot.windowFocused = options.windowFocused;
|
||||
snapshot.wantCaptureMouse = io.WantCaptureMouse;
|
||||
snapshot.wantCaptureKeyboard = io.WantCaptureKeyboard;
|
||||
snapshot.wantTextInput = io.WantTextInput;
|
||||
snapshot.timestampNanoseconds = options.timestampNanoseconds;
|
||||
|
||||
for (std::size_t index = 0; index < XCUIInputBridgeFrameSnapshot::PointerButtonCount; ++index) {
|
||||
snapshot.pointerButtonsDown[index] = io.MouseDown[index];
|
||||
}
|
||||
|
||||
snapshot.characters.reserve(static_cast<std::size_t>(io.InputQueueCharacters.Size));
|
||||
for (int index = 0; index < io.InputQueueCharacters.Size; ++index) {
|
||||
const ImWchar character = io.InputQueueCharacters[index];
|
||||
if (character != 0) {
|
||||
snapshot.characters.push_back(static_cast<std::uint32_t>(character));
|
||||
}
|
||||
}
|
||||
|
||||
snapshot.keys.reserve(sizeof(kImGuiKeyMappings) / sizeof(kImGuiKeyMappings[0]));
|
||||
for (const ImGuiKeyMappingEntry& mapping : kImGuiKeyMappings) {
|
||||
if (mapping.keyCode == KeyCode::None) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool isDown = ImGui::IsKeyDown(mapping.imguiKey);
|
||||
const bool isRepeat = SnapshotHasKeyRepeat(io, mapping.imguiKey);
|
||||
if (!isDown && !isRepeat) {
|
||||
continue;
|
||||
}
|
||||
|
||||
UpsertKeyState(
|
||||
snapshot.keys,
|
||||
static_cast<std::int32_t>(mapping.keyCode),
|
||||
isDown,
|
||||
isRepeat);
|
||||
}
|
||||
|
||||
SortSnapshotKeys(snapshot);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::int32_t ImGuiXCUIInputAdapter::MapKeyCode(ImGuiKey key) {
|
||||
for (const ImGuiKeyMappingEntry& mapping : kImGuiKeyMappings) {
|
||||
if (mapping.imguiKey == key) {
|
||||
return static_cast<std::int32_t>(mapping.keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<std::int32_t>(KeyCode::None);
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,22 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCUIBackend/XCUIInputBridge.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
class ImGuiXCUIInputAdapter {
|
||||
public:
|
||||
static XCUIInputBridgeFrameSnapshot CaptureSnapshot(
|
||||
const ImGuiIO& io,
|
||||
const XCUIInputBridgeCaptureOptions& options = XCUIInputBridgeCaptureOptions());
|
||||
|
||||
static std::int32_t MapKeyCode(ImGuiKey key);
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,56 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCUIBackend/ImGuiXCUIInputAdapter.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
class ImGuiXCUIInputSnapshotSource final : public IXCUIInputSnapshotSource {
|
||||
public:
|
||||
explicit ImGuiXCUIInputSnapshotSource(const ImGuiIO* io = nullptr)
|
||||
: m_io(io) {
|
||||
}
|
||||
|
||||
void SetIO(const ImGuiIO* io) {
|
||||
m_io = io;
|
||||
}
|
||||
|
||||
const ImGuiIO* GetIO() const {
|
||||
return ResolveIO();
|
||||
}
|
||||
|
||||
XCUIInputBridgeFrameSnapshot CaptureSnapshot(
|
||||
const XCUIInputBridgeCaptureOptions& options = XCUIInputBridgeCaptureOptions()) const override {
|
||||
const ImGuiIO* io = ResolveIO();
|
||||
return io != nullptr
|
||||
? ImGuiXCUIInputAdapter::CaptureSnapshot(*io, options)
|
||||
: XCUIInputBridgeFrameSnapshot();
|
||||
}
|
||||
|
||||
const ::XCEngine::UI::UIPoint& GetPointerPosition() const override {
|
||||
m_cachedPointerPosition = {};
|
||||
const ImGuiIO* io = ResolveIO();
|
||||
if (io != nullptr) {
|
||||
m_cachedPointerPosition = ::XCEngine::UI::UIPoint(io->MousePos.x, io->MousePos.y);
|
||||
}
|
||||
|
||||
return m_cachedPointerPosition;
|
||||
}
|
||||
|
||||
private:
|
||||
const ImGuiIO* ResolveIO() const {
|
||||
if (m_io != nullptr) {
|
||||
return m_io;
|
||||
}
|
||||
|
||||
return ImGui::GetCurrentContext() != nullptr ? &ImGui::GetIO() : nullptr;
|
||||
}
|
||||
|
||||
const ImGuiIO* m_io = nullptr;
|
||||
mutable ::XCEngine::UI::UIPoint m_cachedPointerPosition = {};
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,248 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCUIBackend/XCUIPanelCanvasHost.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline ImVec2 ToImVec2(const ::XCEngine::UI::UIPoint& point) {
|
||||
return ImVec2(point.x, point.y);
|
||||
}
|
||||
|
||||
inline ImU32 ToImU32(const ::XCEngine::UI::UIColor& color) {
|
||||
return ImGui::ColorConvertFloat4ToU32(ImVec4(color.r, color.g, color.b, color.a));
|
||||
}
|
||||
|
||||
inline ImTextureID ToImTextureId(const ::XCEngine::UI::UITextureHandle& texture) {
|
||||
return texture.IsValid()
|
||||
? static_cast<ImTextureID>(texture.nativeHandle)
|
||||
: ImTextureID{};
|
||||
}
|
||||
|
||||
inline void DrawPlaceholder(
|
||||
ImDrawList* drawList,
|
||||
const ::XCEngine::UI::UIRect& rect,
|
||||
const char* title,
|
||||
const char* subtitle) {
|
||||
if (drawList == nullptr || rect.width <= 1.0f || rect.height <= 1.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ImVec2 minPoint(rect.x, rect.y);
|
||||
const ImVec2 maxPoint(rect.x + rect.width, rect.y + rect.height);
|
||||
drawList->AddRectFilled(minPoint, maxPoint, IM_COL32(18, 24, 32, 255), 8.0f);
|
||||
drawList->AddRect(minPoint, maxPoint, IM_COL32(54, 72, 94, 255), 8.0f, 0, 1.0f);
|
||||
|
||||
if (title != nullptr && title[0] != '\0') {
|
||||
drawList->AddText(ImVec2(rect.x + 14.0f, rect.y + 14.0f), IM_COL32(191, 205, 224, 255), title);
|
||||
}
|
||||
if (subtitle != nullptr && subtitle[0] != '\0') {
|
||||
drawList->AddText(
|
||||
ImVec2(rect.x + 14.0f, rect.y + 36.0f),
|
||||
IM_COL32(132, 147, 170, 255),
|
||||
subtitle);
|
||||
}
|
||||
}
|
||||
|
||||
inline void DrawBadge(
|
||||
ImDrawList* drawList,
|
||||
const ::XCEngine::UI::UIRect& canvasRect,
|
||||
const char* title,
|
||||
const char* subtitle) {
|
||||
if (drawList == nullptr || title == nullptr || title[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
const ::XCEngine::UI::UIRect badgeRect(
|
||||
canvasRect.x + 10.0f,
|
||||
canvasRect.y + 10.0f,
|
||||
290.0f,
|
||||
42.0f);
|
||||
const ImVec2 minPoint(badgeRect.x, badgeRect.y);
|
||||
const ImVec2 maxPoint(badgeRect.x + badgeRect.width, badgeRect.y + badgeRect.height);
|
||||
drawList->AddRectFilled(minPoint, maxPoint, IM_COL32(16, 22, 30, 216), 8.0f);
|
||||
drawList->AddRect(minPoint, maxPoint, IM_COL32(52, 72, 96, 255), 8.0f, 0, 1.0f);
|
||||
drawList->AddText(
|
||||
ImVec2(badgeRect.x + 10.0f, badgeRect.y + 8.0f),
|
||||
IM_COL32(191, 205, 224, 255),
|
||||
title);
|
||||
if (subtitle != nullptr && subtitle[0] != '\0') {
|
||||
drawList->AddText(
|
||||
ImVec2(badgeRect.x + 10.0f, badgeRect.y + 24.0f),
|
||||
IM_COL32(132, 147, 170, 255),
|
||||
subtitle);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
class ImGuiXCUIPanelCanvasHost final : public IXCUIPanelCanvasHost {
|
||||
public:
|
||||
const char* GetDebugName() const override {
|
||||
return "ImGuiXCUIPanelCanvasHost";
|
||||
}
|
||||
|
||||
XCUIPanelCanvasSession BeginCanvas(const XCUIPanelCanvasRequest& request) override {
|
||||
const char* childId =
|
||||
request.childId != nullptr && request.childId[0] != '\0'
|
||||
? request.childId
|
||||
: "XCUIPanelCanvasHost";
|
||||
ImGui::BeginChild(
|
||||
childId,
|
||||
ImVec2(0.0f, request.height),
|
||||
request.bordered,
|
||||
ImGuiWindowFlags_NoScrollWithMouse);
|
||||
|
||||
m_drawList = ImGui::GetWindowDrawList();
|
||||
m_canvasSession = {};
|
||||
|
||||
const ImVec2 canvasMin = ImGui::GetCursorScreenPos();
|
||||
const ImVec2 canvasSize = ImGui::GetContentRegionAvail();
|
||||
m_canvasSession.hostRect = ::XCEngine::UI::UIRect(
|
||||
canvasMin.x,
|
||||
canvasMin.y,
|
||||
canvasSize.x,
|
||||
canvasSize.y);
|
||||
const float topInset = request.topInset > 0.0f ? request.topInset : 0.0f;
|
||||
m_canvasSession.canvasRect = ::XCEngine::UI::UIRect(
|
||||
m_canvasSession.hostRect.x,
|
||||
m_canvasSession.hostRect.y + topInset,
|
||||
m_canvasSession.hostRect.width,
|
||||
(m_canvasSession.hostRect.height > topInset)
|
||||
? (m_canvasSession.hostRect.height - topInset)
|
||||
: 0.0f);
|
||||
m_canvasSession.validCanvas =
|
||||
m_canvasSession.canvasRect.width > 1.0f &&
|
||||
m_canvasSession.canvasRect.height > 1.0f;
|
||||
m_canvasSession.windowFocused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
|
||||
m_canvasSession.pointerPosition =
|
||||
::XCEngine::UI::UIPoint(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y);
|
||||
|
||||
if (m_canvasSession.validCanvas) {
|
||||
ImGui::SetCursorScreenPos(ImVec2(m_canvasSession.canvasRect.x, m_canvasSession.canvasRect.y));
|
||||
if (request.showSurfaceImage && request.surfaceImage.IsValid()) {
|
||||
ImGui::Image(
|
||||
detail::ToImTextureId(request.surfaceImage.texture),
|
||||
ImVec2(m_canvasSession.canvasRect.width, m_canvasSession.canvasRect.height),
|
||||
detail::ToImVec2(request.surfaceImage.uvMin),
|
||||
detail::ToImVec2(request.surfaceImage.uvMax));
|
||||
} else {
|
||||
std::string buttonId = "##";
|
||||
buttonId += childId;
|
||||
buttonId += ".canvas";
|
||||
ImGui::InvisibleButton(
|
||||
buttonId.c_str(),
|
||||
ImVec2(m_canvasSession.canvasRect.width, m_canvasSession.canvasRect.height));
|
||||
detail::DrawPlaceholder(
|
||||
m_drawList,
|
||||
m_canvasSession.canvasRect,
|
||||
request.placeholderTitle,
|
||||
request.placeholderSubtitle);
|
||||
}
|
||||
|
||||
m_canvasSession.hovered = ImGui::IsItemHovered();
|
||||
if (request.showSurfaceImage && request.drawPreviewFrame) {
|
||||
DrawOutlineRect(
|
||||
m_canvasSession.canvasRect,
|
||||
::XCEngine::UI::UIColor(54.0f / 255.0f, 72.0f / 255.0f, 94.0f / 255.0f, 1.0f),
|
||||
1.0f,
|
||||
8.0f);
|
||||
}
|
||||
detail::DrawBadge(
|
||||
m_drawList,
|
||||
m_canvasSession.canvasRect,
|
||||
request.badgeTitle,
|
||||
request.badgeSubtitle);
|
||||
} else {
|
||||
ImGui::Dummy(ImVec2(0.0f, 0.0f));
|
||||
}
|
||||
|
||||
return m_canvasSession;
|
||||
}
|
||||
|
||||
void DrawFilledRect(
|
||||
const ::XCEngine::UI::UIRect& rect,
|
||||
const ::XCEngine::UI::UIColor& color,
|
||||
float rounding = 0.0f) override {
|
||||
if (m_drawList == nullptr || rect.width <= 0.0f || rect.height <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_drawList->AddRectFilled(
|
||||
ImVec2(rect.x, rect.y),
|
||||
ImVec2(rect.x + rect.width, rect.y + rect.height),
|
||||
detail::ToImU32(color),
|
||||
rounding);
|
||||
}
|
||||
|
||||
void DrawOutlineRect(
|
||||
const ::XCEngine::UI::UIRect& rect,
|
||||
const ::XCEngine::UI::UIColor& color,
|
||||
float thickness = 1.0f,
|
||||
float rounding = 0.0f) override {
|
||||
if (m_drawList == nullptr || rect.width <= 0.0f || rect.height <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_drawList->AddRect(
|
||||
ImVec2(rect.x, rect.y),
|
||||
ImVec2(rect.x + rect.width, rect.y + rect.height),
|
||||
detail::ToImU32(color),
|
||||
rounding,
|
||||
0,
|
||||
thickness);
|
||||
}
|
||||
|
||||
void DrawText(
|
||||
const ::XCEngine::UI::UIPoint& position,
|
||||
std::string_view text,
|
||||
const ::XCEngine::UI::UIColor& color,
|
||||
float fontSize = 0.0f) override {
|
||||
if (m_drawList == nullptr || text.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ImVec2 textPosition(position.x, position.y);
|
||||
if (fontSize > 0.0f) {
|
||||
m_drawList->AddText(
|
||||
ImGui::GetFont(),
|
||||
fontSize,
|
||||
textPosition,
|
||||
detail::ToImU32(color),
|
||||
text.data(),
|
||||
text.data() + text.size());
|
||||
return;
|
||||
}
|
||||
|
||||
m_drawList->AddText(
|
||||
textPosition,
|
||||
detail::ToImU32(color),
|
||||
text.data(),
|
||||
text.data() + text.size());
|
||||
}
|
||||
|
||||
void EndCanvas() override {
|
||||
ImGui::EndChild();
|
||||
m_drawList = nullptr;
|
||||
m_canvasSession = {};
|
||||
}
|
||||
|
||||
private:
|
||||
ImDrawList* m_drawList = nullptr;
|
||||
XCUIPanelCanvasSession m_canvasSession = {};
|
||||
};
|
||||
|
||||
inline std::unique_ptr<IXCUIPanelCanvasHost> CreateImGuiXCUIPanelCanvasHost() {
|
||||
return std::make_unique<ImGuiXCUIPanelCanvasHost>();
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,51 +0,0 @@
|
||||
#include "XCUIBackend/LegacyImGuiHostInterop.h"
|
||||
|
||||
#include "XCUIBackend/ImGuiXCUIPanelCanvasHost.h"
|
||||
#include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h"
|
||||
#include "XCUIBackend/ImGuiWindowUICompositor.h"
|
||||
#include "XCUIBackend/XCUIEditorFontSetup.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
bool ConfigureLegacyImGuiHostFonts() {
|
||||
return ConfigureDefaultXCUIEditorFontsForCurrentContext();
|
||||
}
|
||||
|
||||
std::unique_ptr<IWindowUICompositor> CreateImGuiWindowUICompositor() {
|
||||
return std::make_unique<ImGuiWindowUICompositor>(CreateImGuiHostCompositor());
|
||||
}
|
||||
|
||||
std::unique_ptr<IWindowUICompositor> CreateLegacyImGuiWindowUICompositor() {
|
||||
return CreateImGuiWindowUICompositor();
|
||||
}
|
||||
|
||||
std::unique_ptr<IXCUIHostedPreviewPresenter> CreateLegacyImGuiHostedPreviewPresenter() {
|
||||
return CreateImGuiXCUIHostedPreviewPresenter();
|
||||
}
|
||||
|
||||
std::unique_ptr<IXCUIPanelCanvasHost> CreateLegacyImGuiPanelCanvasHost() {
|
||||
return CreateImGuiXCUIPanelCanvasHost();
|
||||
}
|
||||
|
||||
void ApplyLegacyImGuiHostInputCapture(XCUIInputBridgeFrameSnapshot& snapshot) {
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
snapshot.wantCaptureKeyboard = io.WantCaptureKeyboard;
|
||||
snapshot.wantTextInput = io.WantTextInput;
|
||||
}
|
||||
|
||||
bool RenderLegacyImGuiDemoWindow(bool& visible) {
|
||||
if (!visible) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ImGui::ShowDemoWindow(&visible);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCUIBackend/IEditorHostCompositor.h"
|
||||
#include "XCUIBackend/XCUIInputBridge.h"
|
||||
#include "XCUIBackend/IWindowUICompositor.h"
|
||||
#include "XCUIBackend/XCUIHostedPreviewPresenter.h"
|
||||
#include "XCUIBackend/XCUIPanelCanvasHost.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
bool ConfigureLegacyImGuiHostFonts();
|
||||
|
||||
std::unique_ptr<IEditorHostCompositor> CreateImGuiHostCompositor();
|
||||
|
||||
std::unique_ptr<IWindowUICompositor> CreateImGuiWindowUICompositor();
|
||||
|
||||
std::unique_ptr<IWindowUICompositor> CreateLegacyImGuiWindowUICompositor();
|
||||
|
||||
std::unique_ptr<IXCUIHostedPreviewPresenter> CreateLegacyImGuiHostedPreviewPresenter();
|
||||
|
||||
std::unique_ptr<IXCUIPanelCanvasHost> CreateLegacyImGuiPanelCanvasHost();
|
||||
|
||||
void ApplyLegacyImGuiHostInputCapture(XCUIInputBridgeFrameSnapshot& snapshot);
|
||||
|
||||
bool RenderLegacyImGuiDemoWindow(bool& visible);
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,282 +0,0 @@
|
||||
#include "XCUIBackend/NativeWindowUICompositor.h"
|
||||
|
||||
#include "Platform/D3D12WindowRenderer.h"
|
||||
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/RHI/RHICommandList.h>
|
||||
#include <XCEngine/RHI/RHICommandQueue.h>
|
||||
#include <XCEngine/RHI/RHIResourceView.h>
|
||||
#include <XCEngine/RHI/RHISwapChain.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
namespace {
|
||||
|
||||
::XCEngine::RHI::ResourceViewDimension ResolveShaderResourceDimension(
|
||||
::XCEngine::RHI::TextureType textureType) {
|
||||
switch (textureType) {
|
||||
case ::XCEngine::RHI::TextureType::Texture1D:
|
||||
return ::XCEngine::RHI::ResourceViewDimension::Texture1D;
|
||||
case ::XCEngine::RHI::TextureType::Texture2D:
|
||||
return ::XCEngine::RHI::ResourceViewDimension::Texture2D;
|
||||
case ::XCEngine::RHI::TextureType::Texture2DArray:
|
||||
return ::XCEngine::RHI::ResourceViewDimension::Texture2DArray;
|
||||
case ::XCEngine::RHI::TextureType::Texture3D:
|
||||
return ::XCEngine::RHI::ResourceViewDimension::Texture3D;
|
||||
case ::XCEngine::RHI::TextureType::TextureCube:
|
||||
return ::XCEngine::RHI::ResourceViewDimension::TextureCube;
|
||||
case ::XCEngine::RHI::TextureType::TextureCubeArray:
|
||||
return ::XCEngine::RHI::ResourceViewDimension::TextureCubeArray;
|
||||
default:
|
||||
return ::XCEngine::RHI::ResourceViewDimension::Texture2D;
|
||||
}
|
||||
}
|
||||
|
||||
bool PrepareSwapChainRender(
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer& windowRenderer,
|
||||
const float clearColor[4],
|
||||
::XCEngine::Rendering::RenderContext& outRenderContext,
|
||||
const ::XCEngine::Rendering::RenderSurface*& outRenderSurface,
|
||||
::XCEngine::RHI::RHIResourceView*& outRenderTargetView) {
|
||||
outRenderContext = windowRenderer.GetRenderContext();
|
||||
outRenderSurface = windowRenderer.GetCurrentRenderSurface();
|
||||
outRenderTargetView = nullptr;
|
||||
|
||||
if (!outRenderContext.IsValid() ||
|
||||
outRenderSurface == nullptr ||
|
||||
outRenderContext.commandList == nullptr ||
|
||||
windowRenderer.GetSwapChain() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& colorAttachments = outRenderSurface->GetColorAttachments();
|
||||
if (colorAttachments.empty() || colorAttachments[0] == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outRenderTargetView = colorAttachments[0];
|
||||
outRenderContext.commandList->TransitionBarrier(
|
||||
outRenderTargetView,
|
||||
::XCEngine::RHI::ResourceStates::Present,
|
||||
::XCEngine::RHI::ResourceStates::RenderTarget);
|
||||
outRenderContext.commandList->SetRenderTargets(1, &outRenderTargetView, nullptr);
|
||||
outRenderContext.commandList->ClearRenderTarget(outRenderTargetView, clearColor);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RebindSwapChainRenderTarget(
|
||||
::XCEngine::Rendering::RenderContext& renderContext,
|
||||
::XCEngine::RHI::RHIResourceView* renderTargetView) {
|
||||
if (renderContext.commandList == nullptr || renderTargetView == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderContext.commandList->SetRenderTargets(1, &renderTargetView, nullptr);
|
||||
}
|
||||
|
||||
void PresentSwapChainRender(
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer& windowRenderer,
|
||||
::XCEngine::Rendering::RenderContext& renderContext,
|
||||
::XCEngine::RHI::RHIResourceView* renderTargetView) {
|
||||
if (renderContext.commandList == nullptr ||
|
||||
renderContext.commandQueue == nullptr ||
|
||||
renderTargetView == nullptr ||
|
||||
windowRenderer.GetSwapChain() == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderContext.commandList->TransitionBarrier(
|
||||
renderTargetView,
|
||||
::XCEngine::RHI::ResourceStates::RenderTarget,
|
||||
::XCEngine::RHI::ResourceStates::Present);
|
||||
renderContext.commandList->Close();
|
||||
|
||||
void* commandLists[] = { renderContext.commandList };
|
||||
renderContext.commandQueue->ExecuteCommandLists(1, commandLists);
|
||||
windowRenderer.GetSwapChain()->Present(1, 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool NativeWindowUICompositor::Initialize(
|
||||
HWND hwnd,
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer& windowRenderer,
|
||||
const ConfigureFontsCallback& configureFonts) {
|
||||
(void)configureFonts;
|
||||
|
||||
m_hwnd = hwnd;
|
||||
m_windowRenderer = hwnd != nullptr ? &windowRenderer : nullptr;
|
||||
m_renderBackend.Shutdown();
|
||||
m_renderBackend.ResetStats();
|
||||
m_pendingRenderPacket.Clear();
|
||||
m_lastPresentStats = {};
|
||||
return m_windowRenderer != nullptr;
|
||||
}
|
||||
|
||||
void NativeWindowUICompositor::Shutdown() {
|
||||
m_renderBackend.Shutdown();
|
||||
m_pendingRenderPacket.Clear();
|
||||
m_lastPresentStats = {};
|
||||
m_windowRenderer = nullptr;
|
||||
m_hwnd = nullptr;
|
||||
}
|
||||
|
||||
bool NativeWindowUICompositor::HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
(void)hwnd;
|
||||
(void)message;
|
||||
(void)wParam;
|
||||
(void)lParam;
|
||||
return false;
|
||||
}
|
||||
|
||||
void NativeWindowUICompositor::RenderFrame(
|
||||
const float clearColor[4],
|
||||
const UiRenderCallback& renderUi,
|
||||
const RenderCallback& beforeUiRender,
|
||||
const RenderCallback& afterUiRender) {
|
||||
(void)renderUi;
|
||||
|
||||
m_lastPresentStats = {};
|
||||
if (m_windowRenderer == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
::XCEngine::Rendering::RenderContext renderContext = {};
|
||||
const ::XCEngine::Rendering::RenderSurface* renderSurface = nullptr;
|
||||
::XCEngine::RHI::RHIResourceView* renderTargetView = nullptr;
|
||||
if (!PrepareSwapChainRender(
|
||||
*m_windowRenderer,
|
||||
clearColor,
|
||||
renderContext,
|
||||
renderSurface,
|
||||
renderTargetView)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (beforeUiRender) {
|
||||
beforeUiRender(renderContext, *renderSurface);
|
||||
RebindSwapChainRenderTarget(renderContext, renderTargetView);
|
||||
}
|
||||
|
||||
m_lastPresentStats.hadPendingPacket = m_pendingRenderPacket.HasDrawData();
|
||||
m_lastPresentStats.submittedDrawListCount = m_pendingRenderPacket.drawData.GetDrawListCount();
|
||||
m_lastPresentStats.submittedCommandCount = m_pendingRenderPacket.drawData.GetTotalCommandCount();
|
||||
|
||||
if (m_pendingRenderPacket.textAtlasProvider != nullptr) {
|
||||
m_renderBackend.SetTextAtlasProvider(m_pendingRenderPacket.textAtlasProvider);
|
||||
} else {
|
||||
m_renderBackend.SetTextAtlasProvider(nullptr);
|
||||
}
|
||||
|
||||
if (m_lastPresentStats.hadPendingPacket) {
|
||||
m_lastPresentStats.renderedNativeOverlay =
|
||||
m_renderBackend.Render(renderContext, *renderSurface, m_pendingRenderPacket.drawData);
|
||||
m_lastPresentStats.overlayStats = m_renderBackend.GetLastOverlayStats();
|
||||
} else {
|
||||
m_renderBackend.ResetStats();
|
||||
m_lastPresentStats.overlayStats = m_renderBackend.GetLastOverlayStats();
|
||||
}
|
||||
|
||||
m_pendingRenderPacket.Clear();
|
||||
|
||||
if (afterUiRender) {
|
||||
RebindSwapChainRenderTarget(renderContext, renderTargetView);
|
||||
afterUiRender(renderContext, *renderSurface);
|
||||
}
|
||||
|
||||
PresentSwapChainRender(*m_windowRenderer, renderContext, renderTargetView);
|
||||
}
|
||||
|
||||
bool NativeWindowUICompositor::CreateTextureDescriptor(
|
||||
::XCEngine::RHI::RHIDevice* device,
|
||||
::XCEngine::RHI::RHITexture* texture,
|
||||
UITextureRegistration& outRegistration) {
|
||||
outRegistration = {};
|
||||
if (device == nullptr || texture == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
::XCEngine::RHI::ResourceViewDesc viewDesc = {};
|
||||
viewDesc.format = static_cast<std::uint32_t>(texture->GetFormat());
|
||||
viewDesc.dimension = ResolveShaderResourceDimension(texture->GetTextureType());
|
||||
viewDesc.mipLevel = 0u;
|
||||
|
||||
::XCEngine::RHI::RHIResourceView* shaderResourceView = device->CreateShaderResourceView(texture, viewDesc);
|
||||
if (shaderResourceView == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!shaderResourceView->IsValid() ||
|
||||
shaderResourceView->GetViewType() != ::XCEngine::RHI::ResourceViewType::ShaderResource) {
|
||||
shaderResourceView->Shutdown();
|
||||
delete shaderResourceView;
|
||||
return false;
|
||||
}
|
||||
|
||||
outRegistration.cpuHandle.ptr =
|
||||
reinterpret_cast<std::uintptr_t>(shaderResourceView->GetNativeHandle());
|
||||
outRegistration.texture.nativeHandle =
|
||||
reinterpret_cast<std::uintptr_t>(shaderResourceView);
|
||||
outRegistration.texture.width = texture->GetWidth();
|
||||
outRegistration.texture.height = texture->GetHeight();
|
||||
outRegistration.texture.kind = ::XCEngine::UI::UITextureHandleKind::ShaderResourceView;
|
||||
|
||||
if (!outRegistration.IsValid()) {
|
||||
shaderResourceView->Shutdown();
|
||||
delete shaderResourceView;
|
||||
outRegistration = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NativeWindowUICompositor::FreeTextureDescriptor(const UITextureRegistration& registration) {
|
||||
if (registration.texture.kind != ::XCEngine::UI::UITextureHandleKind::ShaderResourceView ||
|
||||
registration.texture.nativeHandle == 0u) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* shaderResourceView =
|
||||
reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(registration.texture.nativeHandle);
|
||||
shaderResourceView->Shutdown();
|
||||
delete shaderResourceView;
|
||||
}
|
||||
|
||||
void NativeWindowUICompositor::SubmitRenderPacket(const XCUINativeWindowRenderPacket& packet) {
|
||||
m_pendingRenderPacket = packet;
|
||||
}
|
||||
|
||||
void NativeWindowUICompositor::SubmitRenderPacket(
|
||||
const ::XCEngine::UI::UIDrawData& drawData,
|
||||
const IXCUITextAtlasProvider* textAtlasProvider) {
|
||||
m_pendingRenderPacket.drawData = drawData;
|
||||
m_pendingRenderPacket.textAtlasProvider = textAtlasProvider;
|
||||
}
|
||||
|
||||
void NativeWindowUICompositor::ClearPendingRenderPacket() {
|
||||
m_pendingRenderPacket.Clear();
|
||||
}
|
||||
|
||||
bool NativeWindowUICompositor::HasPendingRenderPacket() const {
|
||||
return m_pendingRenderPacket.HasDrawData();
|
||||
}
|
||||
|
||||
const XCUINativeWindowRenderPacket& NativeWindowUICompositor::GetPendingRenderPacket() const {
|
||||
return m_pendingRenderPacket;
|
||||
}
|
||||
|
||||
const XCUINativeWindowPresentStats& NativeWindowUICompositor::GetLastPresentStats() const {
|
||||
return m_lastPresentStats;
|
||||
}
|
||||
|
||||
std::unique_ptr<IWindowUICompositor> CreateNativeWindowUICompositor() {
|
||||
return std::make_unique<NativeWindowUICompositor>();
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,76 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCUIBackend/IWindowUICompositor.h"
|
||||
#include "XCUIBackend/IXCUITextAtlasProvider.h"
|
||||
#include "XCUIBackend/XCUIRHIRenderBackend.h"
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
struct XCUINativeWindowRenderPacket {
|
||||
::XCEngine::UI::UIDrawData drawData = {};
|
||||
const IXCUITextAtlasProvider* textAtlasProvider = nullptr;
|
||||
|
||||
void Clear() {
|
||||
drawData.Clear();
|
||||
textAtlasProvider = nullptr;
|
||||
}
|
||||
|
||||
bool HasDrawData() const {
|
||||
return drawData.GetDrawListCount() > 0u && drawData.GetTotalCommandCount() > 0u;
|
||||
}
|
||||
};
|
||||
|
||||
struct XCUINativeWindowPresentStats {
|
||||
bool hadPendingPacket = false;
|
||||
bool renderedNativeOverlay = false;
|
||||
std::size_t submittedDrawListCount = 0;
|
||||
std::size_t submittedCommandCount = 0;
|
||||
XCUIRHIRenderBackend::OverlayStats overlayStats = {};
|
||||
};
|
||||
|
||||
class NativeWindowUICompositor final : public IWindowUICompositor {
|
||||
public:
|
||||
bool Initialize(
|
||||
HWND hwnd,
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer& windowRenderer,
|
||||
const ConfigureFontsCallback& configureFonts) override;
|
||||
void Shutdown() override;
|
||||
bool HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) override;
|
||||
void RenderFrame(
|
||||
const float clearColor[4],
|
||||
const UiRenderCallback& renderUi,
|
||||
const RenderCallback& beforeUiRender = {},
|
||||
const RenderCallback& afterUiRender = {}) override;
|
||||
bool CreateTextureDescriptor(
|
||||
::XCEngine::RHI::RHIDevice* device,
|
||||
::XCEngine::RHI::RHITexture* texture,
|
||||
UITextureRegistration& outRegistration) override;
|
||||
void FreeTextureDescriptor(const UITextureRegistration& registration) override;
|
||||
|
||||
void SubmitRenderPacket(const XCUINativeWindowRenderPacket& packet);
|
||||
void SubmitRenderPacket(
|
||||
const ::XCEngine::UI::UIDrawData& drawData,
|
||||
const IXCUITextAtlasProvider* textAtlasProvider = nullptr);
|
||||
void ClearPendingRenderPacket();
|
||||
bool HasPendingRenderPacket() const;
|
||||
const XCUINativeWindowRenderPacket& GetPendingRenderPacket() const;
|
||||
const XCUINativeWindowPresentStats& GetLastPresentStats() const;
|
||||
|
||||
private:
|
||||
HWND m_hwnd = nullptr;
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer* m_windowRenderer = nullptr;
|
||||
XCUIRHIRenderBackend m_renderBackend = {};
|
||||
XCUINativeWindowRenderPacket m_pendingRenderPacket = {};
|
||||
XCUINativeWindowPresentStats m_lastPresentStats = {};
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,291 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCUIBackend/XCUIPanelCanvasHost.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline std::string CopyCanvasLabel(const char* text) {
|
||||
return text != nullptr ? std::string(text) : std::string();
|
||||
}
|
||||
|
||||
inline ::XCEngine::UI::UIColor NativeCanvasPlaceholderFillColor() {
|
||||
return ::XCEngine::UI::UIColor(18.0f / 255.0f, 24.0f / 255.0f, 32.0f / 255.0f, 1.0f);
|
||||
}
|
||||
|
||||
inline ::XCEngine::UI::UIColor NativeCanvasPlaceholderStrokeColor() {
|
||||
return ::XCEngine::UI::UIColor(54.0f / 255.0f, 72.0f / 255.0f, 94.0f / 255.0f, 1.0f);
|
||||
}
|
||||
|
||||
inline ::XCEngine::UI::UIColor NativeCanvasPrimaryTextColor() {
|
||||
return ::XCEngine::UI::UIColor(191.0f / 255.0f, 205.0f / 255.0f, 224.0f / 255.0f, 1.0f);
|
||||
}
|
||||
|
||||
inline ::XCEngine::UI::UIColor NativeCanvasSecondaryTextColor() {
|
||||
return ::XCEngine::UI::UIColor(132.0f / 255.0f, 147.0f / 255.0f, 170.0f / 255.0f, 1.0f);
|
||||
}
|
||||
|
||||
inline XCUIPanelCanvasSession NormalizeNativeCanvasSession(
|
||||
const XCUIPanelCanvasRequest& request,
|
||||
const XCUIPanelCanvasSession* configuredSession) {
|
||||
if (configuredSession == nullptr) {
|
||||
return BuildPassiveXCUIPanelCanvasSession(request);
|
||||
}
|
||||
|
||||
XCUIPanelCanvasSession session = *configuredSession;
|
||||
const float fallbackHostHeight = request.height > 0.0f ? request.height : 0.0f;
|
||||
if (session.hostRect.height <= 0.0f && fallbackHostHeight > 0.0f) {
|
||||
session.hostRect.height = fallbackHostHeight;
|
||||
}
|
||||
|
||||
if (session.canvasRect.width <= 0.0f || session.canvasRect.height <= 0.0f) {
|
||||
const float topInset = request.topInset > 0.0f ? request.topInset : 0.0f;
|
||||
const float clampedTopInset =
|
||||
session.hostRect.height > 0.0f
|
||||
? (std::min)(topInset, session.hostRect.height)
|
||||
: 0.0f;
|
||||
session.canvasRect = ::XCEngine::UI::UIRect(
|
||||
session.hostRect.x,
|
||||
session.hostRect.y + clampedTopInset,
|
||||
session.hostRect.width,
|
||||
(std::max)(0.0f, session.hostRect.height - clampedTopInset));
|
||||
}
|
||||
|
||||
session.validCanvas =
|
||||
session.canvasRect.width > 1.0f &&
|
||||
session.canvasRect.height > 1.0f;
|
||||
if (!session.validCanvas) {
|
||||
session.hovered = false;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
inline void RecordPlaceholder(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const ::XCEngine::UI::UIRect& rect,
|
||||
const std::string& title,
|
||||
const std::string& subtitle) {
|
||||
if (rect.width <= 1.0f || rect.height <= 1.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
drawList.AddFilledRect(rect, NativeCanvasPlaceholderFillColor(), 8.0f);
|
||||
drawList.AddRectOutline(rect, NativeCanvasPlaceholderStrokeColor(), 1.0f, 8.0f);
|
||||
if (!title.empty()) {
|
||||
drawList.AddText(
|
||||
::XCEngine::UI::UIPoint(rect.x + 14.0f, rect.y + 14.0f),
|
||||
title,
|
||||
NativeCanvasPrimaryTextColor());
|
||||
}
|
||||
if (!subtitle.empty()) {
|
||||
drawList.AddText(
|
||||
::XCEngine::UI::UIPoint(rect.x + 14.0f, rect.y + 36.0f),
|
||||
subtitle,
|
||||
NativeCanvasSecondaryTextColor());
|
||||
}
|
||||
}
|
||||
|
||||
inline void RecordBadge(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const ::XCEngine::UI::UIRect& canvasRect,
|
||||
const std::string& title,
|
||||
const std::string& subtitle) {
|
||||
if (title.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ::XCEngine::UI::UIRect badgeRect(
|
||||
canvasRect.x + 10.0f,
|
||||
canvasRect.y + 10.0f,
|
||||
290.0f,
|
||||
42.0f);
|
||||
drawList.AddFilledRect(
|
||||
badgeRect,
|
||||
::XCEngine::UI::UIColor(16.0f / 255.0f, 22.0f / 255.0f, 30.0f / 255.0f, 216.0f / 255.0f),
|
||||
8.0f);
|
||||
drawList.AddRectOutline(badgeRect, NativeCanvasPlaceholderStrokeColor(), 1.0f, 8.0f);
|
||||
drawList.AddText(
|
||||
::XCEngine::UI::UIPoint(badgeRect.x + 10.0f, badgeRect.y + 8.0f),
|
||||
title,
|
||||
NativeCanvasPrimaryTextColor());
|
||||
if (!subtitle.empty()) {
|
||||
drawList.AddText(
|
||||
::XCEngine::UI::UIPoint(badgeRect.x + 10.0f, badgeRect.y + 24.0f),
|
||||
subtitle,
|
||||
NativeCanvasSecondaryTextColor());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
class NativeXCUIPanelCanvasHost final : public IXCUIPanelCanvasHost {
|
||||
public:
|
||||
const char* GetDebugName() const override {
|
||||
return "NativeXCUIPanelCanvasHost";
|
||||
}
|
||||
|
||||
void SetCanvasSession(const XCUIPanelCanvasSession& session) {
|
||||
m_configuredSession = session;
|
||||
m_hasConfiguredSession = true;
|
||||
}
|
||||
|
||||
void ClearCanvasSession() {
|
||||
m_configuredSession = {};
|
||||
m_hasConfiguredSession = false;
|
||||
}
|
||||
|
||||
bool HasConfiguredSession() const {
|
||||
return m_hasConfiguredSession;
|
||||
}
|
||||
|
||||
const XCUIPanelCanvasSession& GetConfiguredSession() const {
|
||||
return m_configuredSession;
|
||||
}
|
||||
|
||||
XCUIPanelCanvasSession BeginCanvas(const XCUIPanelCanvasRequest& request) override {
|
||||
m_currentFrame = {};
|
||||
m_currentFrame.childId = ResolveXCUIPanelCanvasChildId(request, "NativeXCUIPanelCanvasHost");
|
||||
m_currentFrame.session = detail::NormalizeNativeCanvasSession(
|
||||
request,
|
||||
m_hasConfiguredSession ? &m_configuredSession : nullptr);
|
||||
m_currentFrame.bordered = request.bordered;
|
||||
m_currentFrame.drawPreviewFrame = request.drawPreviewFrame;
|
||||
m_currentFrame.showingSurfaceImage = request.showSurfaceImage && request.surfaceImage.IsValid();
|
||||
m_currentFrame.placeholderTitle = detail::CopyCanvasLabel(request.placeholderTitle);
|
||||
m_currentFrame.placeholderSubtitle = detail::CopyCanvasLabel(request.placeholderSubtitle);
|
||||
m_currentFrame.badgeTitle = detail::CopyCanvasLabel(request.badgeTitle);
|
||||
m_currentFrame.badgeSubtitle = detail::CopyCanvasLabel(request.badgeSubtitle);
|
||||
m_currentFrame.surfaceImage = request.surfaceImage;
|
||||
|
||||
m_overlayDrawList = nullptr;
|
||||
m_clipDepth = 0u;
|
||||
|
||||
if (m_currentFrame.session.validCanvas) {
|
||||
const bool shouldRecordPlaceholder =
|
||||
!m_currentFrame.showingSurfaceImage &&
|
||||
(!m_currentFrame.placeholderTitle.empty() || !m_currentFrame.placeholderSubtitle.empty());
|
||||
if (shouldRecordPlaceholder) {
|
||||
::XCEngine::UI::UIDrawList& drawList = EnsureOverlayDrawList();
|
||||
detail::RecordPlaceholder(
|
||||
drawList,
|
||||
m_currentFrame.session.canvasRect,
|
||||
m_currentFrame.placeholderTitle,
|
||||
m_currentFrame.placeholderSubtitle);
|
||||
}
|
||||
|
||||
if (m_currentFrame.showingSurfaceImage) {
|
||||
EnsureOverlayDrawList().AddImage(
|
||||
m_currentFrame.session.canvasRect,
|
||||
m_currentFrame.surfaceImage.texture,
|
||||
::XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
|
||||
m_currentFrame.surfaceImage.uvMin,
|
||||
m_currentFrame.surfaceImage.uvMax);
|
||||
}
|
||||
|
||||
if (request.drawPreviewFrame) {
|
||||
DrawOutlineRect(
|
||||
m_currentFrame.session.canvasRect,
|
||||
detail::NativeCanvasPlaceholderStrokeColor(),
|
||||
1.0f,
|
||||
8.0f);
|
||||
}
|
||||
|
||||
if (!m_currentFrame.badgeTitle.empty()) {
|
||||
::XCEngine::UI::UIDrawList& drawList = EnsureOverlayDrawList();
|
||||
detail::RecordBadge(
|
||||
drawList,
|
||||
m_currentFrame.session.canvasRect,
|
||||
m_currentFrame.badgeTitle,
|
||||
m_currentFrame.badgeSubtitle);
|
||||
}
|
||||
}
|
||||
|
||||
return m_currentFrame.session;
|
||||
}
|
||||
|
||||
void DrawFilledRect(
|
||||
const ::XCEngine::UI::UIRect& rect,
|
||||
const ::XCEngine::UI::UIColor& color,
|
||||
float rounding = 0.0f) override {
|
||||
if (rect.width <= 0.0f || rect.height <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureOverlayDrawList().AddFilledRect(rect, color, rounding);
|
||||
}
|
||||
|
||||
void DrawOutlineRect(
|
||||
const ::XCEngine::UI::UIRect& rect,
|
||||
const ::XCEngine::UI::UIColor& color,
|
||||
float thickness = 1.0f,
|
||||
float rounding = 0.0f) override {
|
||||
if (rect.width <= 0.0f || rect.height <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureOverlayDrawList().AddRectOutline(rect, color, thickness, rounding);
|
||||
}
|
||||
|
||||
void DrawText(
|
||||
const ::XCEngine::UI::UIPoint& position,
|
||||
std::string_view text,
|
||||
const ::XCEngine::UI::UIColor& color,
|
||||
float fontSize = 0.0f) override {
|
||||
if (text.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureOverlayDrawList().AddText(position, std::string(text), color, fontSize);
|
||||
}
|
||||
|
||||
void EndCanvas() override {
|
||||
if (m_overlayDrawList != nullptr) {
|
||||
while (m_clipDepth > 0u) {
|
||||
m_overlayDrawList->PopClipRect();
|
||||
--m_clipDepth;
|
||||
}
|
||||
}
|
||||
|
||||
m_overlayDrawList = nullptr;
|
||||
}
|
||||
|
||||
bool TryGetLatestFrameSnapshot(XCUIPanelCanvasFrameSnapshot& outSnapshot) const override {
|
||||
outSnapshot = m_currentFrame;
|
||||
return !outSnapshot.childId.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
::XCEngine::UI::UIDrawList& EnsureOverlayDrawList() {
|
||||
if (m_overlayDrawList == nullptr) {
|
||||
::XCEngine::UI::UIDrawList& drawList =
|
||||
m_currentFrame.overlayDrawData.EmplaceDrawList(m_currentFrame.childId + ".overlay");
|
||||
drawList.PushClipRect(m_currentFrame.session.canvasRect, true);
|
||||
m_overlayDrawList = &drawList;
|
||||
m_clipDepth = 1u;
|
||||
}
|
||||
|
||||
return *m_overlayDrawList;
|
||||
}
|
||||
|
||||
bool m_hasConfiguredSession = false;
|
||||
XCUIPanelCanvasSession m_configuredSession = {};
|
||||
XCUIPanelCanvasFrameSnapshot m_currentFrame = {};
|
||||
::XCEngine::UI::UIDrawList* m_overlayDrawList = nullptr;
|
||||
std::size_t m_clipDepth = 0u;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<IXCUIPanelCanvasHost> CreateNativeXCUIPanelCanvasHost() {
|
||||
return std::make_unique<NativeXCUIPanelCanvasHost>();
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,67 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCUIBackend/XCUIPanelCanvasHost.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
class NullXCUIPanelCanvasHost final : public IXCUIPanelCanvasHost {
|
||||
public:
|
||||
const char* GetDebugName() const override {
|
||||
return "NullXCUIPanelCanvasHost";
|
||||
}
|
||||
|
||||
XCUIPanelCanvasSession BeginCanvas(const XCUIPanelCanvasRequest& request) override {
|
||||
m_canvasSession = BuildPassiveXCUIPanelCanvasSession(request);
|
||||
return m_canvasSession;
|
||||
}
|
||||
|
||||
void DrawFilledRect(
|
||||
const ::XCEngine::UI::UIRect& rect,
|
||||
const ::XCEngine::UI::UIColor& color,
|
||||
float rounding = 0.0f) override {
|
||||
(void)rect;
|
||||
(void)color;
|
||||
(void)rounding;
|
||||
}
|
||||
|
||||
void DrawOutlineRect(
|
||||
const ::XCEngine::UI::UIRect& rect,
|
||||
const ::XCEngine::UI::UIColor& color,
|
||||
float thickness = 1.0f,
|
||||
float rounding = 0.0f) override {
|
||||
(void)rect;
|
||||
(void)color;
|
||||
(void)thickness;
|
||||
(void)rounding;
|
||||
}
|
||||
|
||||
void DrawText(
|
||||
const ::XCEngine::UI::UIPoint& position,
|
||||
std::string_view text,
|
||||
const ::XCEngine::UI::UIColor& color,
|
||||
float fontSize = 0.0f) override {
|
||||
(void)position;
|
||||
(void)text;
|
||||
(void)color;
|
||||
(void)fontSize;
|
||||
}
|
||||
|
||||
void EndCanvas() override {
|
||||
m_canvasSession = {};
|
||||
}
|
||||
|
||||
private:
|
||||
XCUIPanelCanvasSession m_canvasSession = {};
|
||||
};
|
||||
|
||||
inline std::unique_ptr<IXCUIPanelCanvasHost> CreateNullXCUIPanelCanvasHost() {
|
||||
return std::make_unique<NullXCUIPanelCanvasHost>();
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <d3d12.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
struct UITextureRegistration {
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = {};
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle = {};
|
||||
::XCEngine::UI::UITextureHandle texture = {};
|
||||
|
||||
bool IsValid() const {
|
||||
if (!texture.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView) {
|
||||
return cpuHandle.ptr != 0u;
|
||||
}
|
||||
|
||||
return cpuHandle.ptr != 0u && gpuHandle.ptr != 0u;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,750 +0,0 @@
|
||||
#include "XCUIAssetDocumentSource.h"
|
||||
|
||||
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Containers/String.h>
|
||||
#include <XCEngine/Resources/UI/UIDocuments.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Containers::String;
|
||||
using XCEngine::Resources::CompileUIDocument;
|
||||
using XCEngine::Resources::ResourceManager;
|
||||
using XCEngine::Resources::UIDocumentCompileRequest;
|
||||
using XCEngine::Resources::UIDocumentCompileResult;
|
||||
using XCEngine::Resources::UIDocumentKind;
|
||||
using XCEngine::Resources::UIDocumentResource;
|
||||
using XCEngine::Resources::UITheme;
|
||||
using XCEngine::Resources::UIView;
|
||||
|
||||
String ToContainersString(const std::string& value) {
|
||||
return String(value.c_str());
|
||||
}
|
||||
|
||||
std::string ToStdString(const String& value) {
|
||||
return std::string(value.CStr());
|
||||
}
|
||||
|
||||
std::string ToGenericString(const fs::path& path) {
|
||||
return path.lexically_normal().generic_string();
|
||||
}
|
||||
|
||||
bool PathExists(const fs::path& path) {
|
||||
std::error_code ec;
|
||||
return !path.empty() && fs::exists(path, ec) && !fs::is_directory(path, ec);
|
||||
}
|
||||
|
||||
bool TryGetWriteTime(
|
||||
const fs::path& path,
|
||||
fs::file_time_type& outWriteTime) {
|
||||
std::error_code ec;
|
||||
if (path.empty() || !fs::exists(path, ec) || fs::is_directory(path, ec)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outWriteTime = fs::last_write_time(path, ec);
|
||||
return !ec;
|
||||
}
|
||||
|
||||
std::string SanitizeSetName(const std::string& setName) {
|
||||
std::string sanitized = {};
|
||||
sanitized.reserve(setName.size());
|
||||
for (unsigned char ch : setName) {
|
||||
if (std::isalnum(ch) != 0 || ch == '_' || ch == '-') {
|
||||
sanitized.push_back(static_cast<char>(ch));
|
||||
}
|
||||
}
|
||||
|
||||
return sanitized.empty() ? std::string("Default") : sanitized;
|
||||
}
|
||||
|
||||
std::string ToSnakeCase(const std::string& value) {
|
||||
std::string snake = {};
|
||||
snake.reserve(value.size() + 8u);
|
||||
|
||||
char previous = '\0';
|
||||
for (unsigned char rawCh : value) {
|
||||
if (!(std::isalnum(rawCh) != 0)) {
|
||||
if (!snake.empty() && snake.back() != '_') {
|
||||
snake.push_back('_');
|
||||
}
|
||||
previous = '_';
|
||||
continue;
|
||||
}
|
||||
|
||||
const char ch = static_cast<char>(rawCh);
|
||||
const bool isUpper = std::isupper(rawCh) != 0;
|
||||
const bool previousIsLowerOrDigit =
|
||||
std::islower(static_cast<unsigned char>(previous)) != 0 ||
|
||||
std::isdigit(static_cast<unsigned char>(previous)) != 0;
|
||||
if (isUpper && !snake.empty() && previousIsLowerOrDigit && snake.back() != '_') {
|
||||
snake.push_back('_');
|
||||
}
|
||||
|
||||
snake.push_back(static_cast<char>(std::tolower(rawCh)));
|
||||
previous = ch;
|
||||
}
|
||||
|
||||
while (!snake.empty() && snake.back() == '_') {
|
||||
snake.pop_back();
|
||||
}
|
||||
|
||||
return snake.empty() ? std::string("default") : snake;
|
||||
}
|
||||
|
||||
std::optional<fs::path> GetCurrentPath() {
|
||||
std::error_code ec;
|
||||
const fs::path currentPath = fs::current_path(ec);
|
||||
if (ec) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return currentPath;
|
||||
}
|
||||
|
||||
fs::path GetConfiguredResourceRoot() {
|
||||
const String resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
if (resourceRoot.Empty()) {
|
||||
return fs::path();
|
||||
}
|
||||
|
||||
return fs::path(resourceRoot.CStr()).lexically_normal();
|
||||
}
|
||||
|
||||
std::optional<std::string> FindKnownDocumentUnderRoot(
|
||||
const fs::path& root,
|
||||
const XCUIAssetDocumentSource::PathSet& paths) {
|
||||
if (PathExists(root / paths.view.primaryRelativePath)) {
|
||||
return paths.view.primaryRelativePath;
|
||||
}
|
||||
if (PathExists(root / paths.theme.primaryRelativePath)) {
|
||||
return paths.theme.primaryRelativePath;
|
||||
}
|
||||
if (PathExists(root / paths.view.legacyRelativePath)) {
|
||||
return paths.view.legacyRelativePath;
|
||||
}
|
||||
if (PathExists(root / paths.theme.legacyRelativePath)) {
|
||||
return paths.theme.legacyRelativePath;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void AppendUniqueSearchRoot(
|
||||
const fs::path& searchRoot,
|
||||
std::vector<fs::path>& outRoots,
|
||||
std::unordered_set<std::string>& seenRoots) {
|
||||
if (searchRoot.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fs::path normalized = searchRoot.lexically_normal();
|
||||
const std::string key = ToGenericString(normalized);
|
||||
if (!seenRoots.insert(key).second) {
|
||||
return;
|
||||
}
|
||||
|
||||
outRoots.push_back(normalized);
|
||||
}
|
||||
|
||||
std::vector<fs::path> BuildRepositoryRootSearchRoots(
|
||||
const std::vector<fs::path>& explicitSearchRoots,
|
||||
bool includeDefaultSearchRoots) {
|
||||
std::vector<fs::path> searchRoots = {};
|
||||
std::unordered_set<std::string> seenRoots = {};
|
||||
|
||||
for (const fs::path& explicitSearchRoot : explicitSearchRoots) {
|
||||
AppendUniqueSearchRoot(explicitSearchRoot, searchRoots, seenRoots);
|
||||
}
|
||||
|
||||
if (!includeDefaultSearchRoots) {
|
||||
return searchRoots;
|
||||
}
|
||||
|
||||
#ifdef XCENGINE_NEW_EDITOR_REPO_ROOT
|
||||
AppendUniqueSearchRoot(
|
||||
fs::path(XCENGINE_NEW_EDITOR_REPO_ROOT),
|
||||
searchRoots,
|
||||
seenRoots);
|
||||
#endif
|
||||
|
||||
const fs::path resourceRoot = GetConfiguredResourceRoot();
|
||||
if (!resourceRoot.empty()) {
|
||||
AppendUniqueSearchRoot(resourceRoot, searchRoots, seenRoots);
|
||||
}
|
||||
|
||||
const std::optional<fs::path> currentPath = GetCurrentPath();
|
||||
if (currentPath.has_value()) {
|
||||
AppendUniqueSearchRoot(*currentPath, searchRoots, seenRoots);
|
||||
}
|
||||
|
||||
return searchRoots;
|
||||
}
|
||||
|
||||
void AddCandidate(
|
||||
std::vector<XCUIAssetDocumentSource::ResolutionCandidate>& candidates,
|
||||
std::unordered_set<std::string>& seenPaths,
|
||||
const std::string& requestPath,
|
||||
const fs::path& resolvedPath,
|
||||
XCUIAssetDocumentSource::PathOrigin origin) {
|
||||
const std::string key = ToGenericString(resolvedPath);
|
||||
if (requestPath.empty() || resolvedPath.empty() || !seenPaths.insert(key).second) {
|
||||
return;
|
||||
}
|
||||
|
||||
XCUIAssetDocumentSource::ResolutionCandidate candidate = {};
|
||||
candidate.requestPath = requestPath;
|
||||
candidate.resolvedPath = resolvedPath.lexically_normal();
|
||||
candidate.origin = origin;
|
||||
candidates.push_back(std::move(candidate));
|
||||
}
|
||||
|
||||
void AddRelativeCandidateIfReachable(
|
||||
const std::string& relativePath,
|
||||
const fs::path& repositoryRoot,
|
||||
const fs::path& resourceRoot,
|
||||
XCUIAssetDocumentSource::PathOrigin origin,
|
||||
std::vector<XCUIAssetDocumentSource::ResolutionCandidate>& candidates,
|
||||
std::unordered_set<std::string>& seenPaths) {
|
||||
if (relativePath.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fs::path relative(relativePath);
|
||||
if (PathExists(relative)) {
|
||||
AddCandidate(candidates, seenPaths, relativePath, relative, origin);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!resourceRoot.empty() && PathExists(resourceRoot / relative)) {
|
||||
AddCandidate(candidates, seenPaths, relativePath, resourceRoot / relative, origin);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!repositoryRoot.empty() && PathExists(repositoryRoot / relative)) {
|
||||
AddCandidate(
|
||||
candidates,
|
||||
seenPaths,
|
||||
(repositoryRoot / relative).generic_string(),
|
||||
repositoryRoot / relative,
|
||||
origin);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TDocumentResource>
|
||||
bool TryLoadDocumentFromResourceManager(
|
||||
const std::string& requestPath,
|
||||
UIDocumentCompileResult& outResult) {
|
||||
static_assert(
|
||||
std::is_base_of_v<UIDocumentResource, TDocumentResource>,
|
||||
"TDocumentResource must derive from UIDocumentResource");
|
||||
|
||||
auto resource = ResourceManager::Get().Load<TDocumentResource>(ToContainersString(requestPath));
|
||||
if (!resource || resource->GetDocument().valid == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outResult = UIDocumentCompileResult();
|
||||
outResult.document = resource->GetDocument();
|
||||
outResult.succeeded = outResult.document.valid;
|
||||
return outResult.succeeded;
|
||||
}
|
||||
|
||||
bool TryLoadDocumentFromResourceManager(
|
||||
UIDocumentKind kind,
|
||||
const std::string& requestPath,
|
||||
UIDocumentCompileResult& outResult) {
|
||||
switch (kind) {
|
||||
case UIDocumentKind::View:
|
||||
return TryLoadDocumentFromResourceManager<UIView>(requestPath, outResult);
|
||||
case UIDocumentKind::Theme:
|
||||
return TryLoadDocumentFromResourceManager<UITheme>(requestPath, outResult);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool TryCompileDocumentDirect(
|
||||
const XCUIAssetDocumentSource::DocumentPathSpec& spec,
|
||||
const std::string& requestPath,
|
||||
UIDocumentCompileResult& outResult) {
|
||||
UIDocumentCompileRequest request = {};
|
||||
request.kind = spec.kind;
|
||||
request.path = ToContainersString(requestPath);
|
||||
request.expectedRootTag = ToContainersString(spec.expectedRootTag);
|
||||
return CompileUIDocument(request, outResult) && outResult.succeeded;
|
||||
}
|
||||
|
||||
void CollectTrackedSourcePaths(
|
||||
const XCUIAssetDocumentSource::DocumentLoadState& documentState,
|
||||
std::vector<std::string>& outPaths,
|
||||
std::unordered_set<std::string>& seenPaths) {
|
||||
if (!documentState.succeeded) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto pushPath = [&](const String& path) {
|
||||
if (path.Empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string text = ToStdString(path);
|
||||
if (seenPaths.insert(text).second) {
|
||||
outPaths.push_back(text);
|
||||
}
|
||||
};
|
||||
|
||||
if (!documentState.sourcePath.empty() &&
|
||||
seenPaths.insert(documentState.sourcePath).second) {
|
||||
outPaths.push_back(documentState.sourcePath);
|
||||
}
|
||||
|
||||
if (!documentState.compileResult.document.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
pushPath(documentState.compileResult.document.sourcePath);
|
||||
for (const String& dependency : documentState.compileResult.document.dependencies) {
|
||||
pushPath(dependency);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateTrackedWriteTimes(
|
||||
const XCUIAssetDocumentSource::LoadState& state,
|
||||
std::vector<XCUIAssetDocumentSource::TrackedWriteTime>& outTrackedWriteTimes,
|
||||
std::vector<std::string>& outMissingTrackedSourcePaths,
|
||||
bool& outChangeTrackingReady,
|
||||
std::string& outTrackingStatusMessage) {
|
||||
outTrackedWriteTimes.clear();
|
||||
outMissingTrackedSourcePaths.clear();
|
||||
outTrackedWriteTimes.reserve(state.trackedSourcePaths.size());
|
||||
outMissingTrackedSourcePaths.reserve(state.trackedSourcePaths.size());
|
||||
|
||||
for (const std::string& pathText : state.trackedSourcePaths) {
|
||||
XCUIAssetDocumentSource::TrackedWriteTime tracked = {};
|
||||
tracked.path = fs::path(pathText).lexically_normal();
|
||||
if (!TryGetWriteTime(tracked.path, tracked.writeTime)) {
|
||||
outMissingTrackedSourcePaths.push_back(ToGenericString(tracked.path));
|
||||
continue;
|
||||
}
|
||||
|
||||
outTrackedWriteTimes.push_back(std::move(tracked));
|
||||
}
|
||||
|
||||
outChangeTrackingReady =
|
||||
!state.trackedSourcePaths.empty() &&
|
||||
outTrackedWriteTimes.size() == state.trackedSourcePaths.size();
|
||||
|
||||
if (state.trackedSourcePaths.empty()) {
|
||||
outTrackingStatusMessage = "No XCUI source files were recorded for hot reload.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (outMissingTrackedSourcePaths.empty()) {
|
||||
outTrackingStatusMessage =
|
||||
"Tracking " +
|
||||
std::to_string(static_cast<unsigned long long>(outTrackedWriteTimes.size())) +
|
||||
" XCUI source file(s) for hot reload.";
|
||||
return;
|
||||
}
|
||||
|
||||
outTrackingStatusMessage =
|
||||
"Tracking " +
|
||||
std::to_string(static_cast<unsigned long long>(outTrackedWriteTimes.size())) +
|
||||
" of " +
|
||||
std::to_string(static_cast<unsigned long long>(state.trackedSourcePaths.size())) +
|
||||
" XCUI source file(s); unable to stat " +
|
||||
std::to_string(static_cast<unsigned long long>(outMissingTrackedSourcePaths.size())) +
|
||||
" path(s).";
|
||||
}
|
||||
|
||||
XCUIAssetDocumentSource::DocumentLoadState LoadDocument(
|
||||
const XCUIAssetDocumentSource::DocumentPathSpec& spec,
|
||||
const fs::path& repositoryRoot,
|
||||
bool preferCompilerFallback) {
|
||||
XCUIAssetDocumentSource::DocumentLoadState state = {};
|
||||
state.kind = spec.kind;
|
||||
state.expectedRootTag = spec.expectedRootTag;
|
||||
state.primaryRelativePath = spec.primaryRelativePath;
|
||||
state.legacyRelativePath = spec.legacyRelativePath;
|
||||
|
||||
state.candidatePaths = XCUIAssetDocumentSource::CollectCandidatePaths(
|
||||
spec,
|
||||
repositoryRoot,
|
||||
GetConfiguredResourceRoot());
|
||||
if (state.candidatePaths.empty()) {
|
||||
state.errorMessage =
|
||||
"Unable to locate XCUI document source. Expected " +
|
||||
spec.primaryRelativePath +
|
||||
" or legacy mirror " +
|
||||
spec.legacyRelativePath + ".";
|
||||
return state;
|
||||
}
|
||||
|
||||
state.attemptMessages.reserve(state.candidatePaths.size());
|
||||
|
||||
for (const XCUIAssetDocumentSource::ResolutionCandidate& candidate : state.candidatePaths) {
|
||||
state.requestedPath = candidate.requestPath;
|
||||
state.resolvedPath = candidate.resolvedPath;
|
||||
state.pathOrigin = candidate.origin;
|
||||
state.usedLegacyFallback =
|
||||
candidate.origin == XCUIAssetDocumentSource::PathOrigin::LegacyMirror;
|
||||
|
||||
UIDocumentCompileResult compileResult = {};
|
||||
if (TryCompileDocumentDirect(spec, candidate.requestPath, compileResult)) {
|
||||
state.backend = XCUIAssetDocumentSource::LoadBackend::CompilerFallback;
|
||||
state.compileResult = std::move(compileResult);
|
||||
state.sourcePath = ToStdString(state.compileResult.document.sourcePath);
|
||||
if (state.sourcePath.empty()) {
|
||||
state.sourcePath = ToGenericString(candidate.resolvedPath);
|
||||
}
|
||||
state.statusMessage =
|
||||
(preferCompilerFallback
|
||||
? std::string("Tracked source changed; direct compile refresh succeeded from ")
|
||||
: std::string("Direct compile load succeeded from ")) +
|
||||
ToGenericString(candidate.resolvedPath) +
|
||||
".";
|
||||
state.succeeded = true;
|
||||
return state;
|
||||
}
|
||||
|
||||
UIDocumentCompileResult resourceResult = {};
|
||||
if (TryLoadDocumentFromResourceManager(spec.kind, candidate.requestPath, resourceResult)) {
|
||||
state.backend = XCUIAssetDocumentSource::LoadBackend::ResourceManager;
|
||||
state.compileResult = std::move(resourceResult);
|
||||
state.sourcePath = ToStdString(state.compileResult.document.sourcePath);
|
||||
if (state.sourcePath.empty()) {
|
||||
state.sourcePath = ToGenericString(candidate.resolvedPath);
|
||||
}
|
||||
state.statusMessage =
|
||||
std::string("Loaded via ResourceManager from ") +
|
||||
ToString(candidate.origin) +
|
||||
" path: " +
|
||||
ToGenericString(candidate.resolvedPath) +
|
||||
".";
|
||||
state.succeeded = true;
|
||||
return state;
|
||||
}
|
||||
|
||||
const std::string compileError = compileResult.errorMessage.Empty()
|
||||
? std::string("ResourceManager load failed and compile fallback returned no diagnostic.")
|
||||
: ToStdString(compileResult.errorMessage);
|
||||
state.attemptMessages.push_back(
|
||||
std::string(ToString(candidate.origin)) +
|
||||
" candidate " +
|
||||
ToGenericString(candidate.resolvedPath) +
|
||||
" -> " +
|
||||
compileError);
|
||||
}
|
||||
|
||||
state.requestedPath.clear();
|
||||
state.resolvedPath.clear();
|
||||
state.sourcePath.clear();
|
||||
state.backend = XCUIAssetDocumentSource::LoadBackend::None;
|
||||
state.pathOrigin = XCUIAssetDocumentSource::PathOrigin::None;
|
||||
state.usedLegacyFallback = false;
|
||||
|
||||
state.errorMessage = state.attemptMessages.empty()
|
||||
? std::string("Failed to load XCUI document.")
|
||||
: state.attemptMessages.front();
|
||||
if (state.attemptMessages.size() > 1u) {
|
||||
state.errorMessage += " (";
|
||||
state.errorMessage += std::to_string(
|
||||
static_cast<unsigned long long>(state.attemptMessages.size()));
|
||||
state.errorMessage += " candidates tried)";
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const char* ToString(XCUIAssetDocumentSource::PathOrigin origin) {
|
||||
switch (origin) {
|
||||
case XCUIAssetDocumentSource::PathOrigin::ProjectAssets:
|
||||
return "project-assets";
|
||||
case XCUIAssetDocumentSource::PathOrigin::LegacyMirror:
|
||||
return "legacy-mirror";
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
const char* ToString(XCUIAssetDocumentSource::LoadBackend backend) {
|
||||
switch (backend) {
|
||||
case XCUIAssetDocumentSource::LoadBackend::ResourceManager:
|
||||
return "resource-manager";
|
||||
case XCUIAssetDocumentSource::LoadBackend::CompilerFallback:
|
||||
return "compiler-fallback";
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
XCUIAssetDocumentSource::XCUIAssetDocumentSource() = default;
|
||||
|
||||
XCUIAssetDocumentSource::XCUIAssetDocumentSource(PathSet paths)
|
||||
: m_paths(std::move(paths)) {
|
||||
}
|
||||
|
||||
void XCUIAssetDocumentSource::SetPathSet(PathSet paths) {
|
||||
m_paths = std::move(paths);
|
||||
}
|
||||
|
||||
const XCUIAssetDocumentSource::PathSet& XCUIAssetDocumentSource::GetPathSet() const {
|
||||
return m_paths;
|
||||
}
|
||||
|
||||
bool XCUIAssetDocumentSource::Reload() {
|
||||
const bool preferCompilerFallback = m_state.succeeded && HasTrackedChanges();
|
||||
m_state = LoadState();
|
||||
m_state.paths = m_paths;
|
||||
|
||||
m_state.repositoryDiscovery = DiagnoseRepositoryRoot(m_paths);
|
||||
m_state.repositoryRoot = m_state.repositoryDiscovery.repositoryRoot;
|
||||
|
||||
m_state.view = LoadDocument(m_paths.view, m_state.repositoryRoot, preferCompilerFallback);
|
||||
if (!m_state.view.succeeded) {
|
||||
m_state.errorMessage = m_state.view.errorMessage;
|
||||
if (!m_state.repositoryDiscovery.statusMessage.empty()) {
|
||||
m_state.errorMessage += " " + m_state.repositoryDiscovery.statusMessage;
|
||||
}
|
||||
m_state.statusMessage =
|
||||
"XCUI view document load failed. " +
|
||||
m_state.repositoryDiscovery.statusMessage;
|
||||
m_trackedWriteTimes.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_state.theme = LoadDocument(m_paths.theme, m_state.repositoryRoot, preferCompilerFallback);
|
||||
if (!m_state.theme.succeeded) {
|
||||
m_state.errorMessage = m_state.theme.errorMessage;
|
||||
if (!m_state.repositoryDiscovery.statusMessage.empty()) {
|
||||
m_state.errorMessage += " " + m_state.repositoryDiscovery.statusMessage;
|
||||
}
|
||||
m_state.statusMessage =
|
||||
"XCUI theme document load failed. " +
|
||||
m_state.repositoryDiscovery.statusMessage;
|
||||
m_trackedWriteTimes.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> seenPaths = {};
|
||||
CollectTrackedSourcePaths(m_state.view, m_state.trackedSourcePaths, seenPaths);
|
||||
CollectTrackedSourcePaths(m_state.theme, m_state.trackedSourcePaths, seenPaths);
|
||||
UpdateTrackedWriteTimes(
|
||||
m_state,
|
||||
m_trackedWriteTimes,
|
||||
m_state.missingTrackedSourcePaths,
|
||||
m_state.changeTrackingReady,
|
||||
m_state.trackingStatusMessage);
|
||||
|
||||
m_state.usedLegacyFallback =
|
||||
m_state.view.usedLegacyFallback || m_state.theme.usedLegacyFallback;
|
||||
m_state.succeeded = true;
|
||||
m_state.statusMessage =
|
||||
(m_state.usedLegacyFallback
|
||||
? std::string("XCUI documents loaded with legacy mirror fallback. ")
|
||||
: std::string("XCUI documents loaded from Assets/XCUI/NewEditor. ")) +
|
||||
m_state.trackingStatusMessage;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XCUIAssetDocumentSource::ReloadIfChanged() {
|
||||
if (!m_state.succeeded) {
|
||||
return Reload();
|
||||
}
|
||||
|
||||
return HasTrackedChanges() ? Reload() : true;
|
||||
}
|
||||
|
||||
bool XCUIAssetDocumentSource::HasTrackedChanges() const {
|
||||
if (!m_state.succeeded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_state.trackedSourcePaths.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_trackedWriteTimes.size() != m_state.trackedSourcePaths.size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const TrackedWriteTime& tracked : m_trackedWriteTimes) {
|
||||
fs::file_time_type currentWriteTime = {};
|
||||
if (!TryGetWriteTime(tracked.path, currentWriteTime) ||
|
||||
currentWriteTime != tracked.writeTime) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool XCUIAssetDocumentSource::IsLoaded() const {
|
||||
return m_state.succeeded;
|
||||
}
|
||||
|
||||
const XCUIAssetDocumentSource::LoadState& XCUIAssetDocumentSource::GetState() const {
|
||||
return m_state;
|
||||
}
|
||||
|
||||
XCUIAssetDocumentSource::PathSet XCUIAssetDocumentSource::MakePathSet(
|
||||
const std::string& setName) {
|
||||
PathSet paths = {};
|
||||
paths.setName = SanitizeSetName(setName);
|
||||
|
||||
paths.view.kind = UIDocumentKind::View;
|
||||
paths.view.expectedRootTag = "View";
|
||||
paths.view.primaryRelativePath = BuildProjectAssetViewPath(paths.setName);
|
||||
paths.view.legacyRelativePath = BuildLegacyViewPath(paths.setName);
|
||||
|
||||
paths.theme.kind = UIDocumentKind::Theme;
|
||||
paths.theme.expectedRootTag = "Theme";
|
||||
paths.theme.primaryRelativePath = BuildProjectAssetThemePath(paths.setName);
|
||||
paths.theme.legacyRelativePath = BuildLegacyThemePath(paths.setName);
|
||||
return paths;
|
||||
}
|
||||
|
||||
XCUIAssetDocumentSource::PathSet XCUIAssetDocumentSource::MakeDemoPathSet() {
|
||||
return MakePathSet("Demo");
|
||||
}
|
||||
|
||||
XCUIAssetDocumentSource::PathSet XCUIAssetDocumentSource::MakeLayoutLabPathSet() {
|
||||
return MakePathSet("LayoutLab");
|
||||
}
|
||||
|
||||
std::vector<XCUIAssetDocumentSource::ResolutionCandidate>
|
||||
XCUIAssetDocumentSource::CollectCandidatePaths(
|
||||
const DocumentPathSpec& spec,
|
||||
const fs::path& repositoryRoot) {
|
||||
return CollectCandidatePaths(spec, repositoryRoot, GetConfiguredResourceRoot());
|
||||
}
|
||||
|
||||
std::vector<XCUIAssetDocumentSource::ResolutionCandidate>
|
||||
XCUIAssetDocumentSource::CollectCandidatePaths(
|
||||
const DocumentPathSpec& spec,
|
||||
const fs::path& repositoryRoot,
|
||||
const fs::path& resourceRoot) {
|
||||
std::vector<ResolutionCandidate> candidates = {};
|
||||
std::unordered_set<std::string> seenPaths = {};
|
||||
|
||||
AddRelativeCandidateIfReachable(
|
||||
spec.primaryRelativePath,
|
||||
repositoryRoot,
|
||||
resourceRoot,
|
||||
PathOrigin::ProjectAssets,
|
||||
candidates,
|
||||
seenPaths);
|
||||
AddRelativeCandidateIfReachable(
|
||||
spec.legacyRelativePath,
|
||||
repositoryRoot,
|
||||
resourceRoot,
|
||||
PathOrigin::LegacyMirror,
|
||||
candidates,
|
||||
seenPaths);
|
||||
return candidates;
|
||||
}
|
||||
|
||||
XCUIAssetDocumentSource::RepositoryDiscovery
|
||||
XCUIAssetDocumentSource::DiagnoseRepositoryRoot(const PathSet& paths) {
|
||||
return DiagnoseRepositoryRoot(paths, {}, true);
|
||||
}
|
||||
|
||||
XCUIAssetDocumentSource::RepositoryDiscovery
|
||||
XCUIAssetDocumentSource::DiagnoseRepositoryRoot(
|
||||
const PathSet& paths,
|
||||
const std::vector<fs::path>& searchRoots,
|
||||
bool includeDefaultSearchRoots) {
|
||||
RepositoryDiscovery discovery = {};
|
||||
discovery.probes.reserve(searchRoots.size() + 3u);
|
||||
|
||||
const std::vector<fs::path> effectiveSearchRoots = BuildRepositoryRootSearchRoots(
|
||||
searchRoots,
|
||||
includeDefaultSearchRoots);
|
||||
discovery.probes.reserve(effectiveSearchRoots.size());
|
||||
|
||||
for (const fs::path& searchRoot : effectiveSearchRoots) {
|
||||
RepositoryProbe probe = {};
|
||||
probe.searchRoot = searchRoot;
|
||||
|
||||
fs::path current = searchRoot;
|
||||
while (!current.empty()) {
|
||||
const std::optional<std::string> matchedRelativePath =
|
||||
FindKnownDocumentUnderRoot(current, paths);
|
||||
if (matchedRelativePath.has_value()) {
|
||||
probe.matched = true;
|
||||
probe.matchedRoot = current.lexically_normal();
|
||||
probe.matchedRelativePath = *matchedRelativePath;
|
||||
discovery.repositoryRoot = probe.matchedRoot;
|
||||
discovery.probes.push_back(std::move(probe));
|
||||
discovery.statusMessage =
|
||||
"Repository root resolved to " +
|
||||
ToGenericString(discovery.repositoryRoot) +
|
||||
" via " +
|
||||
discovery.probes.back().matchedRelativePath +
|
||||
".";
|
||||
return discovery;
|
||||
}
|
||||
|
||||
const fs::path parent = current.parent_path();
|
||||
if (parent == current) {
|
||||
break;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
|
||||
discovery.probes.push_back(std::move(probe));
|
||||
}
|
||||
|
||||
discovery.statusMessage =
|
||||
"Repository root not found for XCUI set '" +
|
||||
paths.setName +
|
||||
"'. Probed " +
|
||||
std::to_string(static_cast<unsigned long long>(discovery.probes.size())) +
|
||||
" search root(s).";
|
||||
return discovery;
|
||||
}
|
||||
|
||||
std::string XCUIAssetDocumentSource::BuildProjectAssetViewPath(
|
||||
const std::string& setName) {
|
||||
const std::string folderName = SanitizeSetName(setName);
|
||||
return std::string(kProjectAssetRoot) + "/" + folderName + "/View.xcui";
|
||||
}
|
||||
|
||||
std::string XCUIAssetDocumentSource::BuildProjectAssetThemePath(
|
||||
const std::string& setName) {
|
||||
const std::string folderName = SanitizeSetName(setName);
|
||||
return std::string(kProjectAssetRoot) + "/" + folderName + "/Theme.xctheme";
|
||||
}
|
||||
|
||||
std::string XCUIAssetDocumentSource::BuildLegacyViewPath(
|
||||
const std::string& setName) {
|
||||
return std::string(kLegacyResourceRoot) +
|
||||
"/xcui_" +
|
||||
ToSnakeCase(SanitizeSetName(setName)) +
|
||||
"_view.xcui";
|
||||
}
|
||||
|
||||
std::string XCUIAssetDocumentSource::BuildLegacyThemePath(
|
||||
const std::string& setName) {
|
||||
return std::string(kLegacyResourceRoot) +
|
||||
"/xcui_" +
|
||||
ToSnakeCase(SanitizeSetName(setName)) +
|
||||
"_theme.xctheme";
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,148 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Resources/UI/UIDocumentCompiler.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
class XCUIAssetDocumentSource {
|
||||
public:
|
||||
static constexpr const char* kProjectAssetRoot = "Assets/XCUI/NewEditor";
|
||||
static constexpr const char* kLegacyResourceRoot = "new_editor/resources";
|
||||
|
||||
enum class PathOrigin {
|
||||
None,
|
||||
ProjectAssets,
|
||||
LegacyMirror
|
||||
};
|
||||
|
||||
enum class LoadBackend {
|
||||
None,
|
||||
ResourceManager,
|
||||
CompilerFallback
|
||||
};
|
||||
|
||||
struct ResolutionCandidate {
|
||||
std::string requestPath = {};
|
||||
std::filesystem::path resolvedPath = {};
|
||||
PathOrigin origin = PathOrigin::None;
|
||||
};
|
||||
|
||||
struct RepositoryProbe {
|
||||
std::filesystem::path searchRoot = {};
|
||||
std::filesystem::path matchedRoot = {};
|
||||
std::string matchedRelativePath = {};
|
||||
bool matched = false;
|
||||
};
|
||||
|
||||
struct RepositoryDiscovery {
|
||||
std::filesystem::path repositoryRoot = {};
|
||||
std::vector<RepositoryProbe> probes = {};
|
||||
std::string statusMessage = {};
|
||||
};
|
||||
|
||||
struct DocumentPathSpec {
|
||||
Resources::UIDocumentKind kind = Resources::UIDocumentKind::View;
|
||||
std::string expectedRootTag = {};
|
||||
std::string primaryRelativePath = {};
|
||||
std::string legacyRelativePath = {};
|
||||
};
|
||||
|
||||
struct PathSet {
|
||||
std::string setName = {};
|
||||
DocumentPathSpec view = {};
|
||||
DocumentPathSpec theme = {};
|
||||
};
|
||||
|
||||
struct DocumentLoadState {
|
||||
Resources::UIDocumentKind kind = Resources::UIDocumentKind::View;
|
||||
std::string expectedRootTag = {};
|
||||
std::string primaryRelativePath = {};
|
||||
std::string legacyRelativePath = {};
|
||||
std::string requestedPath = {};
|
||||
std::filesystem::path resolvedPath = {};
|
||||
std::string sourcePath = {};
|
||||
std::string statusMessage = {};
|
||||
std::string errorMessage = {};
|
||||
std::vector<ResolutionCandidate> candidatePaths = {};
|
||||
std::vector<std::string> attemptMessages = {};
|
||||
LoadBackend backend = LoadBackend::None;
|
||||
PathOrigin pathOrigin = PathOrigin::None;
|
||||
Resources::UIDocumentCompileResult compileResult = {};
|
||||
bool succeeded = false;
|
||||
bool usedLegacyFallback = false;
|
||||
};
|
||||
|
||||
struct LoadState {
|
||||
PathSet paths = {};
|
||||
DocumentLoadState view = {};
|
||||
DocumentLoadState theme = {};
|
||||
std::vector<std::string> trackedSourcePaths = {};
|
||||
std::vector<std::string> missingTrackedSourcePaths = {};
|
||||
std::string statusMessage = {};
|
||||
std::string errorMessage = {};
|
||||
std::string trackingStatusMessage = {};
|
||||
std::filesystem::path repositoryRoot = {};
|
||||
RepositoryDiscovery repositoryDiscovery = {};
|
||||
bool succeeded = false;
|
||||
bool changeTrackingReady = false;
|
||||
bool usedLegacyFallback = false;
|
||||
};
|
||||
|
||||
struct TrackedWriteTime {
|
||||
std::filesystem::path path = {};
|
||||
std::filesystem::file_time_type writeTime = {};
|
||||
};
|
||||
|
||||
XCUIAssetDocumentSource();
|
||||
explicit XCUIAssetDocumentSource(PathSet paths);
|
||||
|
||||
void SetPathSet(PathSet paths);
|
||||
const PathSet& GetPathSet() const;
|
||||
|
||||
bool Reload();
|
||||
bool ReloadIfChanged();
|
||||
bool HasTrackedChanges() const;
|
||||
bool IsLoaded() const;
|
||||
|
||||
const LoadState& GetState() const;
|
||||
|
||||
static PathSet MakePathSet(const std::string& setName);
|
||||
static PathSet MakeDemoPathSet();
|
||||
static PathSet MakeLayoutLabPathSet();
|
||||
|
||||
static std::vector<ResolutionCandidate> CollectCandidatePaths(
|
||||
const DocumentPathSpec& spec,
|
||||
const std::filesystem::path& repositoryRoot);
|
||||
static std::vector<ResolutionCandidate> CollectCandidatePaths(
|
||||
const DocumentPathSpec& spec,
|
||||
const std::filesystem::path& repositoryRoot,
|
||||
const std::filesystem::path& resourceRoot);
|
||||
static RepositoryDiscovery DiagnoseRepositoryRoot(const PathSet& paths);
|
||||
static RepositoryDiscovery DiagnoseRepositoryRoot(
|
||||
const PathSet& paths,
|
||||
const std::vector<std::filesystem::path>& searchRoots,
|
||||
bool includeDefaultSearchRoots);
|
||||
|
||||
static std::string BuildProjectAssetViewPath(const std::string& setName);
|
||||
static std::string BuildProjectAssetThemePath(const std::string& setName);
|
||||
static std::string BuildLegacyViewPath(const std::string& setName);
|
||||
static std::string BuildLegacyThemePath(const std::string& setName);
|
||||
|
||||
private:
|
||||
PathSet m_paths = {};
|
||||
LoadState m_state = {};
|
||||
std::vector<TrackedWriteTime> m_trackedWriteTimes = {};
|
||||
};
|
||||
|
||||
const char* ToString(XCUIAssetDocumentSource::PathOrigin origin);
|
||||
const char* ToString(XCUIAssetDocumentSource::LoadBackend backend);
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,79 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace UI {
|
||||
struct UIRect;
|
||||
struct UIPoint;
|
||||
} // namespace UI
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
struct XCUIDemoInputState {
|
||||
UI::UIRect canvasRect = {};
|
||||
UI::UIPoint pointerPosition = {};
|
||||
bool pointerInside = false;
|
||||
bool pointerPressed = false;
|
||||
bool pointerReleased = false;
|
||||
bool pointerDown = false;
|
||||
bool windowFocused = false;
|
||||
bool shortcutPressed = false;
|
||||
bool wantCaptureMouse = false;
|
||||
bool wantCaptureKeyboard = false;
|
||||
bool wantTextInput = false;
|
||||
std::vector<UI::UIInputEvent> events = {};
|
||||
};
|
||||
|
||||
struct XCUIDemoFrameStats {
|
||||
bool documentsReady = false;
|
||||
std::string statusMessage = {};
|
||||
std::size_t dependencyCount = 0;
|
||||
std::size_t elementCount = 0;
|
||||
std::size_t dirtyRootCount = 0;
|
||||
std::size_t drawListCount = 0;
|
||||
std::size_t commandCount = 0;
|
||||
std::uint64_t treeGeneration = 0;
|
||||
bool accentEnabled = false;
|
||||
std::string lastCommandId = {};
|
||||
std::string focusedElementId = {};
|
||||
std::string hoveredElementId = {};
|
||||
};
|
||||
|
||||
struct XCUIDemoFrameResult {
|
||||
UI::UIDrawData drawData = {};
|
||||
XCUIDemoFrameStats stats = {};
|
||||
};
|
||||
|
||||
class XCUIDemoRuntime {
|
||||
public:
|
||||
XCUIDemoRuntime();
|
||||
~XCUIDemoRuntime();
|
||||
|
||||
XCUIDemoRuntime(XCUIDemoRuntime&& other) noexcept;
|
||||
XCUIDemoRuntime& operator=(XCUIDemoRuntime&& other) noexcept;
|
||||
|
||||
XCUIDemoRuntime(const XCUIDemoRuntime&) = delete;
|
||||
XCUIDemoRuntime& operator=(const XCUIDemoRuntime&) = delete;
|
||||
|
||||
bool ReloadDocuments();
|
||||
|
||||
const XCUIDemoFrameResult& Update(const XCUIDemoInputState& input);
|
||||
const XCUIDemoFrameResult& GetFrameResult() const;
|
||||
std::vector<std::string> DrainPendingCommandIds();
|
||||
|
||||
bool TryGetElementRect(const std::string& elementId, UI::UIRect& outRect) const;
|
||||
|
||||
private:
|
||||
struct RuntimeState;
|
||||
std::unique_ptr<RuntimeState> m_state;
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,210 +0,0 @@
|
||||
#include "XCUIBackend/XCUIEditorCommandRouter.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
namespace {
|
||||
|
||||
bool ModifiersMatch(
|
||||
const UI::UIInputModifiers& required,
|
||||
const UI::UIInputModifiers& actual,
|
||||
bool exact) {
|
||||
if (exact) {
|
||||
return required.shift == actual.shift &&
|
||||
required.control == actual.control &&
|
||||
required.alt == actual.alt &&
|
||||
required.super == actual.super;
|
||||
}
|
||||
|
||||
return (!required.shift || actual.shift) &&
|
||||
(!required.control || actual.control) &&
|
||||
(!required.alt || actual.alt) &&
|
||||
(!required.super || actual.super);
|
||||
}
|
||||
|
||||
bool AcceleratorMatchesSnapshot(
|
||||
const XCUIEditorCommandAccelerator& accelerator,
|
||||
const XCUIEditorCommandInputSnapshot& snapshot) {
|
||||
if (!accelerator.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const XCUIEditorCommandKeyState* keyState = snapshot.FindKeyState(accelerator.keyCode);
|
||||
if (keyState == nullptr || !keyState->down) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keyState->repeat && !accelerator.allowRepeat) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ModifiersMatch(accelerator.modifiers, snapshot.modifiers, accelerator.exactModifiers);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const XCUIEditorCommandKeyState* XCUIEditorCommandInputSnapshot::FindKeyState(std::int32_t keyCode) const {
|
||||
for (const XCUIEditorCommandKeyState& keyState : keys) {
|
||||
if (keyState.keyCode == keyCode) {
|
||||
return &keyState;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool XCUIEditorCommandInputSnapshot::IsKeyDown(std::int32_t keyCode) const {
|
||||
const XCUIEditorCommandKeyState* keyState = FindKeyState(keyCode);
|
||||
return keyState != nullptr && keyState->down;
|
||||
}
|
||||
|
||||
bool XCUIEditorCommandAccelerator::IsValid() const {
|
||||
return keyCode != 0;
|
||||
}
|
||||
|
||||
bool XCUIEditorCommandDefinition::IsValid() const {
|
||||
return !commandId.empty() && static_cast<bool>(invoke);
|
||||
}
|
||||
|
||||
bool XCUIEditorCommandRouter::RegisterCommand(const XCUIEditorCommandDefinition& definition) {
|
||||
if (!definition.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CommandRecord replacement = {};
|
||||
replacement.commandId = definition.commandId;
|
||||
replacement.invoke = definition.invoke;
|
||||
replacement.isEnabled = definition.isEnabled;
|
||||
replacement.accelerators = definition.accelerators;
|
||||
|
||||
if (CommandRecord* existing = FindRecord(definition.commandId)) {
|
||||
*existing = std::move(replacement);
|
||||
return true;
|
||||
}
|
||||
|
||||
m_commands.push_back(std::move(replacement));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XCUIEditorCommandRouter::UnregisterCommand(std::string_view commandId) {
|
||||
const auto it = std::find_if(
|
||||
m_commands.begin(),
|
||||
m_commands.end(),
|
||||
[commandId](const CommandRecord& record) {
|
||||
return record.commandId == commandId;
|
||||
});
|
||||
if (it == m_commands.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_commands.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
void XCUIEditorCommandRouter::Clear() {
|
||||
m_commands.clear();
|
||||
}
|
||||
|
||||
bool XCUIEditorCommandRouter::HasCommand(std::string_view commandId) const {
|
||||
return FindRecord(commandId) != nullptr;
|
||||
}
|
||||
|
||||
std::size_t XCUIEditorCommandRouter::GetCommandCount() const {
|
||||
return m_commands.size();
|
||||
}
|
||||
|
||||
bool XCUIEditorCommandRouter::IsCommandEnabled(std::string_view commandId) const {
|
||||
const CommandRecord* record = FindRecord(commandId);
|
||||
return record != nullptr && EvaluateEnabled(*record);
|
||||
}
|
||||
|
||||
bool XCUIEditorCommandRouter::InvokeCommand(std::string_view commandId) const {
|
||||
const CommandRecord* record = FindRecord(commandId);
|
||||
if (record == nullptr || !EvaluateEnabled(*record) || !record->invoke) {
|
||||
return false;
|
||||
}
|
||||
|
||||
record->invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
XCUIEditorCommandShortcutMatch XCUIEditorCommandRouter::MatchShortcut(
|
||||
const XCUIEditorCommandShortcutQuery& query) const {
|
||||
XCUIEditorCommandShortcutMatch result = {};
|
||||
if (query.snapshot == nullptr) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const XCUIEditorCommandInputSnapshot& snapshot = *query.snapshot;
|
||||
if (query.requireWindowFocused && !snapshot.windowFocused) {
|
||||
return result;
|
||||
}
|
||||
if (!query.allowWhenKeyboardCaptured && snapshot.wantCaptureKeyboard) {
|
||||
return result;
|
||||
}
|
||||
if (!query.allowWhenTextInputActive && snapshot.wantTextInput) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const CommandRecord& record : m_commands) {
|
||||
if (!EvaluateEnabled(record)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const XCUIEditorCommandAccelerator& accelerator : record.accelerators) {
|
||||
if (!AcceleratorMatchesSnapshot(accelerator, snapshot)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.matched = true;
|
||||
result.commandId = record.commandId;
|
||||
result.accelerator = accelerator;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool XCUIEditorCommandRouter::InvokeMatchingShortcut(
|
||||
const XCUIEditorCommandShortcutQuery& query,
|
||||
XCUIEditorCommandShortcutMatch* outMatch) const {
|
||||
const XCUIEditorCommandShortcutMatch match = MatchShortcut(query);
|
||||
if (outMatch != nullptr) {
|
||||
*outMatch = match;
|
||||
}
|
||||
|
||||
return match.matched && InvokeCommand(match.commandId);
|
||||
}
|
||||
|
||||
XCUIEditorCommandRouter::CommandRecord* XCUIEditorCommandRouter::FindRecord(std::string_view commandId) {
|
||||
const auto it = std::find_if(
|
||||
m_commands.begin(),
|
||||
m_commands.end(),
|
||||
[commandId](const CommandRecord& record) {
|
||||
return record.commandId == commandId;
|
||||
});
|
||||
return it != m_commands.end() ? &(*it) : nullptr;
|
||||
}
|
||||
|
||||
const XCUIEditorCommandRouter::CommandRecord* XCUIEditorCommandRouter::FindRecord(
|
||||
std::string_view commandId) const {
|
||||
const auto it = std::find_if(
|
||||
m_commands.begin(),
|
||||
m_commands.end(),
|
||||
[commandId](const CommandRecord& record) {
|
||||
return record.commandId == commandId;
|
||||
});
|
||||
return it != m_commands.end() ? &(*it) : nullptr;
|
||||
}
|
||||
|
||||
bool XCUIEditorCommandRouter::EvaluateEnabled(const CommandRecord& record) {
|
||||
return !record.isEnabled || record.isEnabled();
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,101 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
struct XCUIEditorCommandKeyState {
|
||||
std::int32_t keyCode = 0;
|
||||
bool down = false;
|
||||
bool repeat = false;
|
||||
};
|
||||
|
||||
struct XCUIEditorCommandInputSnapshot {
|
||||
UI::UIInputModifiers modifiers = {};
|
||||
bool windowFocused = false;
|
||||
bool wantCaptureKeyboard = false;
|
||||
bool wantTextInput = false;
|
||||
std::vector<XCUIEditorCommandKeyState> keys = {};
|
||||
|
||||
const XCUIEditorCommandKeyState* FindKeyState(std::int32_t keyCode) const;
|
||||
bool IsKeyDown(std::int32_t keyCode) const;
|
||||
};
|
||||
|
||||
struct XCUIEditorCommandAccelerator {
|
||||
std::int32_t keyCode = 0;
|
||||
UI::UIInputModifiers modifiers = {};
|
||||
bool exactModifiers = true;
|
||||
bool allowRepeat = false;
|
||||
|
||||
bool IsValid() const;
|
||||
};
|
||||
|
||||
struct XCUIEditorCommandDefinition {
|
||||
using InvokeCallback = std::function<void()>;
|
||||
using EnabledPredicate = std::function<bool()>;
|
||||
|
||||
std::string commandId = {};
|
||||
InvokeCallback invoke = {};
|
||||
EnabledPredicate isEnabled = {};
|
||||
std::vector<XCUIEditorCommandAccelerator> accelerators = {};
|
||||
|
||||
bool IsValid() const;
|
||||
};
|
||||
|
||||
struct XCUIEditorCommandShortcutQuery {
|
||||
const XCUIEditorCommandInputSnapshot* snapshot = nullptr;
|
||||
bool requireWindowFocused = true;
|
||||
bool allowWhenKeyboardCaptured = false;
|
||||
bool allowWhenTextInputActive = false;
|
||||
};
|
||||
|
||||
struct XCUIEditorCommandShortcutMatch {
|
||||
bool matched = false;
|
||||
std::string commandId = {};
|
||||
XCUIEditorCommandAccelerator accelerator = {};
|
||||
};
|
||||
|
||||
class XCUIEditorCommandRouter {
|
||||
public:
|
||||
bool RegisterCommand(const XCUIEditorCommandDefinition& definition);
|
||||
bool UnregisterCommand(std::string_view commandId);
|
||||
void Clear();
|
||||
|
||||
bool HasCommand(std::string_view commandId) const;
|
||||
std::size_t GetCommandCount() const;
|
||||
bool IsCommandEnabled(std::string_view commandId) const;
|
||||
|
||||
bool InvokeCommand(std::string_view commandId) const;
|
||||
XCUIEditorCommandShortcutMatch MatchShortcut(
|
||||
const XCUIEditorCommandShortcutQuery& query) const;
|
||||
bool InvokeMatchingShortcut(
|
||||
const XCUIEditorCommandShortcutQuery& query,
|
||||
XCUIEditorCommandShortcutMatch* outMatch = nullptr) const;
|
||||
|
||||
private:
|
||||
struct CommandRecord {
|
||||
std::string commandId = {};
|
||||
XCUIEditorCommandDefinition::InvokeCallback invoke = {};
|
||||
XCUIEditorCommandDefinition::EnabledPredicate isEnabled = {};
|
||||
std::vector<XCUIEditorCommandAccelerator> accelerators = {};
|
||||
};
|
||||
|
||||
CommandRecord* FindRecord(std::string_view commandId);
|
||||
const CommandRecord* FindRecord(std::string_view commandId) const;
|
||||
static bool EvaluateEnabled(const CommandRecord& record);
|
||||
|
||||
std::vector<CommandRecord> m_commands = {};
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,71 +0,0 @@
|
||||
#include "XCUIBackend/XCUIEditorFontSetup.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kUiFontSize = 18.0f;
|
||||
constexpr const char* kPrimaryUiFontPath = "C:/Windows/Fonts/segoeui.ttf";
|
||||
constexpr const char* kChineseFallbackFontPath = "C:/Windows/Fonts/msyh.ttc";
|
||||
|
||||
bool BuildDefaultXCUIEditorFontAtlas(::ImFontAtlas& atlas, ::ImFont*& outDefaultFont) {
|
||||
outDefaultFont = nullptr;
|
||||
atlas.Clear();
|
||||
|
||||
ImFontConfig baseConfig = {};
|
||||
baseConfig.OversampleH = 2;
|
||||
baseConfig.OversampleV = 1;
|
||||
baseConfig.PixelSnapH = true;
|
||||
|
||||
outDefaultFont = atlas.AddFontFromFileTTF(
|
||||
kPrimaryUiFontPath,
|
||||
kUiFontSize,
|
||||
&baseConfig,
|
||||
atlas.GetGlyphRangesDefault());
|
||||
|
||||
if (outDefaultFont != nullptr) {
|
||||
ImFontConfig mergeConfig = baseConfig;
|
||||
mergeConfig.MergeMode = true;
|
||||
mergeConfig.PixelSnapH = true;
|
||||
atlas.AddFontFromFileTTF(
|
||||
kChineseFallbackFontPath,
|
||||
kUiFontSize,
|
||||
&mergeConfig,
|
||||
atlas.GetGlyphRangesChineseSimplifiedCommon());
|
||||
} else {
|
||||
outDefaultFont = atlas.AddFontFromFileTTF(
|
||||
kChineseFallbackFontPath,
|
||||
kUiFontSize,
|
||||
&baseConfig,
|
||||
atlas.GetGlyphRangesChineseSimplifiedCommon());
|
||||
}
|
||||
|
||||
if (outDefaultFont == nullptr) {
|
||||
ImFontConfig fallbackConfig = baseConfig;
|
||||
fallbackConfig.SizePixels = kUiFontSize;
|
||||
outDefaultFont = atlas.AddFontDefault(&fallbackConfig);
|
||||
}
|
||||
|
||||
return outDefaultFont != nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ConfigureDefaultXCUIEditorFontsForCurrentContext() {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImFont* uiFont = nullptr;
|
||||
if (!BuildDefaultXCUIEditorFontAtlas(*io.Fonts, uiFont)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
io.FontDefault = uiFont;
|
||||
return io.FontDefault != nullptr;
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,11 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
bool ConfigureDefaultXCUIEditorFontsForCurrentContext();
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,336 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
struct XCUIHostedPreviewFrame {
|
||||
const ::XCEngine::UI::UIDrawData* drawData = nullptr;
|
||||
::XCEngine::UI::UIRect canvasRect = {};
|
||||
::XCEngine::UI::UISize logicalSize = {};
|
||||
const char* debugName = nullptr;
|
||||
const char* debugSource = nullptr;
|
||||
};
|
||||
|
||||
struct XCUIHostedPreviewStats {
|
||||
bool presented = false;
|
||||
bool queuedToNativePass = false;
|
||||
std::size_t submittedDrawListCount = 0;
|
||||
std::size_t submittedCommandCount = 0;
|
||||
std::size_t flushedDrawListCount = 0;
|
||||
std::size_t flushedCommandCount = 0;
|
||||
};
|
||||
|
||||
struct XCUIHostedPreviewQueuedFrame {
|
||||
std::string debugName = {};
|
||||
std::string debugSource = {};
|
||||
::XCEngine::UI::UIRect canvasRect = {};
|
||||
::XCEngine::UI::UISize logicalSize = {};
|
||||
::XCEngine::UI::UIDrawData drawData = {};
|
||||
};
|
||||
|
||||
struct XCUIHostedPreviewSurfaceImage {
|
||||
::XCEngine::UI::UITextureHandle texture = {};
|
||||
::XCEngine::UI::UIPoint uvMin = {};
|
||||
::XCEngine::UI::UIPoint uvMax = ::XCEngine::UI::UIPoint(1.0f, 1.0f);
|
||||
::XCEngine::UI::UIRect renderedCanvasRect = {};
|
||||
std::uint32_t surfaceWidth = 0;
|
||||
std::uint32_t surfaceHeight = 0;
|
||||
|
||||
bool IsValid() const {
|
||||
return texture.IsValid() && surfaceWidth > 0u && surfaceHeight > 0u;
|
||||
}
|
||||
};
|
||||
|
||||
struct XCUIHostedPreviewDrainStats {
|
||||
std::size_t queuedFrameCount = 0;
|
||||
std::size_t queuedDrawListCount = 0;
|
||||
std::size_t queuedCommandCount = 0;
|
||||
std::size_t renderedFrameCount = 0;
|
||||
std::size_t renderedDrawListCount = 0;
|
||||
std::size_t renderedCommandCount = 0;
|
||||
std::size_t skippedFrameCount = 0;
|
||||
std::size_t skippedCommandCount = 0;
|
||||
};
|
||||
|
||||
struct XCUIHostedPreviewSurfaceDescriptor {
|
||||
std::string debugName = {};
|
||||
std::string debugSource = {};
|
||||
::XCEngine::UI::UIRect canvasRect = {};
|
||||
::XCEngine::UI::UISize logicalSize = {};
|
||||
std::size_t queuedFrameIndex = 0;
|
||||
std::size_t submittedDrawListCount = 0;
|
||||
std::size_t submittedCommandCount = 0;
|
||||
bool queuedThisFrame = false;
|
||||
XCUIHostedPreviewSurfaceImage image = {};
|
||||
};
|
||||
|
||||
class XCUIHostedPreviewSurfaceRegistry {
|
||||
public:
|
||||
void BeginFrame() {
|
||||
for (XCUIHostedPreviewSurfaceDescriptor& descriptor : m_descriptors) {
|
||||
descriptor.queuedThisFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
void RecordQueuedFrame(
|
||||
const XCUIHostedPreviewQueuedFrame& queuedFrame,
|
||||
std::size_t queuedFrameIndex = 0u) {
|
||||
if (queuedFrame.debugName.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
XCUIHostedPreviewSurfaceDescriptor* descriptor = FindMutableDescriptor(queuedFrame.debugName);
|
||||
if (descriptor == nullptr) {
|
||||
XCUIHostedPreviewSurfaceDescriptor newDescriptor = {};
|
||||
newDescriptor.debugName = queuedFrame.debugName;
|
||||
m_descriptors.push_back(std::move(newDescriptor));
|
||||
descriptor = &m_descriptors.back();
|
||||
}
|
||||
|
||||
descriptor->debugSource = queuedFrame.debugSource;
|
||||
descriptor->canvasRect = queuedFrame.canvasRect;
|
||||
descriptor->logicalSize = queuedFrame.logicalSize;
|
||||
descriptor->queuedFrameIndex = queuedFrameIndex;
|
||||
descriptor->submittedDrawListCount = queuedFrame.drawData.GetDrawListCount();
|
||||
descriptor->submittedCommandCount = queuedFrame.drawData.GetTotalCommandCount();
|
||||
descriptor->queuedThisFrame = true;
|
||||
}
|
||||
|
||||
void UpdateSurface(
|
||||
const std::string& debugName,
|
||||
const ::XCEngine::UI::UITextureHandle& texture,
|
||||
const ::XCEngine::UI::UIRect& renderedCanvasRect) {
|
||||
if (debugName.empty() || !texture.IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
XCUIHostedPreviewSurfaceDescriptor* descriptor = FindMutableDescriptor(debugName);
|
||||
if (descriptor == nullptr) {
|
||||
XCUIHostedPreviewSurfaceDescriptor newDescriptor = {};
|
||||
newDescriptor.debugName = debugName;
|
||||
m_descriptors.push_back(std::move(newDescriptor));
|
||||
descriptor = &m_descriptors.back();
|
||||
}
|
||||
|
||||
descriptor->image.texture = texture;
|
||||
descriptor->image.surfaceWidth = texture.width;
|
||||
descriptor->image.surfaceHeight = texture.height;
|
||||
descriptor->image.renderedCanvasRect = renderedCanvasRect;
|
||||
descriptor->image.uvMin = ::XCEngine::UI::UIPoint(
|
||||
renderedCanvasRect.x / static_cast<float>(texture.width),
|
||||
renderedCanvasRect.y / static_cast<float>(texture.height));
|
||||
descriptor->image.uvMax = ::XCEngine::UI::UIPoint(
|
||||
(renderedCanvasRect.x + renderedCanvasRect.width) / static_cast<float>(texture.width),
|
||||
(renderedCanvasRect.y + renderedCanvasRect.height) / static_cast<float>(texture.height));
|
||||
}
|
||||
|
||||
bool TryGetSurfaceDescriptor(
|
||||
const char* debugName,
|
||||
XCUIHostedPreviewSurfaceDescriptor& outDescriptor) const {
|
||||
outDescriptor = {};
|
||||
if (debugName == nullptr || debugName[0] == '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const XCUIHostedPreviewSurfaceDescriptor& descriptor : m_descriptors) {
|
||||
if (descriptor.debugName == debugName) {
|
||||
outDescriptor = descriptor;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryGetSurfaceImage(const char* debugName, XCUIHostedPreviewSurfaceImage& outImage) const {
|
||||
outImage = {};
|
||||
XCUIHostedPreviewSurfaceDescriptor descriptor = {};
|
||||
if (!TryGetSurfaceDescriptor(debugName, descriptor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outImage = descriptor.image;
|
||||
return outImage.IsValid();
|
||||
}
|
||||
|
||||
const std::vector<XCUIHostedPreviewSurfaceDescriptor>& GetDescriptors() const {
|
||||
return m_descriptors;
|
||||
}
|
||||
|
||||
private:
|
||||
XCUIHostedPreviewSurfaceDescriptor* FindMutableDescriptor(const std::string& debugName) {
|
||||
for (XCUIHostedPreviewSurfaceDescriptor& descriptor : m_descriptors) {
|
||||
if (descriptor.debugName == debugName) {
|
||||
return &descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<XCUIHostedPreviewSurfaceDescriptor> m_descriptors = {};
|
||||
};
|
||||
|
||||
class XCUIHostedPreviewQueue {
|
||||
public:
|
||||
void BeginFrame() {
|
||||
m_queuedFrames.clear();
|
||||
}
|
||||
|
||||
bool Submit(const XCUIHostedPreviewFrame& frame, XCUIHostedPreviewStats* outStats = nullptr) {
|
||||
XCUIHostedPreviewStats stats = {};
|
||||
if (frame.drawData == nullptr) {
|
||||
if (outStats != nullptr) {
|
||||
*outStats = stats;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
XCUIHostedPreviewQueuedFrame queuedFrame = {};
|
||||
if (frame.debugName != nullptr) {
|
||||
queuedFrame.debugName = frame.debugName;
|
||||
}
|
||||
if (frame.debugSource != nullptr) {
|
||||
queuedFrame.debugSource = frame.debugSource;
|
||||
}
|
||||
queuedFrame.canvasRect = frame.canvasRect;
|
||||
queuedFrame.logicalSize = frame.logicalSize.width > 0.0f && frame.logicalSize.height > 0.0f
|
||||
? frame.logicalSize
|
||||
: ::XCEngine::UI::UISize(frame.canvasRect.width, frame.canvasRect.height);
|
||||
queuedFrame.drawData = *frame.drawData;
|
||||
|
||||
stats.presented = true;
|
||||
stats.queuedToNativePass = true;
|
||||
stats.submittedDrawListCount = queuedFrame.drawData.GetDrawListCount();
|
||||
stats.submittedCommandCount = queuedFrame.drawData.GetTotalCommandCount();
|
||||
m_queuedFrames.push_back(std::move(queuedFrame));
|
||||
|
||||
if (outStats != nullptr) {
|
||||
*outStats = stats;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<XCUIHostedPreviewQueuedFrame>& GetQueuedFrames() const {
|
||||
return m_queuedFrames;
|
||||
}
|
||||
|
||||
void SetLastDrainStats(const XCUIHostedPreviewDrainStats& stats) {
|
||||
m_lastDrainStats = stats;
|
||||
}
|
||||
|
||||
const XCUIHostedPreviewDrainStats& GetLastDrainStats() const {
|
||||
return m_lastDrainStats;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<XCUIHostedPreviewQueuedFrame> m_queuedFrames = {};
|
||||
XCUIHostedPreviewDrainStats m_lastDrainStats = {};
|
||||
};
|
||||
|
||||
class IXCUIHostedPreviewPresenter {
|
||||
public:
|
||||
virtual ~IXCUIHostedPreviewPresenter() = default;
|
||||
|
||||
virtual bool Present(const XCUIHostedPreviewFrame& frame) = 0;
|
||||
virtual const XCUIHostedPreviewStats& GetLastStats() const = 0;
|
||||
virtual bool IsNativeQueued() const { return false; }
|
||||
virtual bool TryGetSurfaceImage(
|
||||
const char* debugName,
|
||||
XCUIHostedPreviewSurfaceImage& outImage) const {
|
||||
outImage = {};
|
||||
return false;
|
||||
}
|
||||
virtual bool TryGetSurfaceDescriptor(
|
||||
const char* debugName,
|
||||
XCUIHostedPreviewSurfaceDescriptor& outDescriptor) const {
|
||||
outDescriptor = {};
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class NullXCUIHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter {
|
||||
public:
|
||||
bool Present(const XCUIHostedPreviewFrame& frame) override {
|
||||
m_lastStats = {};
|
||||
if (frame.drawData == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_lastStats.submittedDrawListCount = frame.drawData->GetDrawListCount();
|
||||
m_lastStats.submittedCommandCount = frame.drawData->GetTotalCommandCount();
|
||||
return false;
|
||||
}
|
||||
|
||||
const XCUIHostedPreviewStats& GetLastStats() const override {
|
||||
return m_lastStats;
|
||||
}
|
||||
|
||||
private:
|
||||
XCUIHostedPreviewStats m_lastStats = {};
|
||||
};
|
||||
|
||||
class QueuedNativeXCUIHostedPreviewPresenter final : public IXCUIHostedPreviewPresenter {
|
||||
public:
|
||||
QueuedNativeXCUIHostedPreviewPresenter(
|
||||
XCUIHostedPreviewQueue& queue,
|
||||
XCUIHostedPreviewSurfaceRegistry& surfaceRegistry)
|
||||
: m_queue(queue)
|
||||
, m_surfaceRegistry(surfaceRegistry) {
|
||||
}
|
||||
|
||||
bool Present(const XCUIHostedPreviewFrame& frame) override {
|
||||
m_lastStats = {};
|
||||
return m_queue.Submit(frame, &m_lastStats);
|
||||
}
|
||||
|
||||
const XCUIHostedPreviewStats& GetLastStats() const override {
|
||||
return m_lastStats;
|
||||
}
|
||||
|
||||
bool IsNativeQueued() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryGetSurfaceImage(
|
||||
const char* debugName,
|
||||
XCUIHostedPreviewSurfaceImage& outImage) const override {
|
||||
return m_surfaceRegistry.TryGetSurfaceImage(debugName, outImage);
|
||||
}
|
||||
|
||||
bool TryGetSurfaceDescriptor(
|
||||
const char* debugName,
|
||||
XCUIHostedPreviewSurfaceDescriptor& outDescriptor) const override {
|
||||
return m_surfaceRegistry.TryGetSurfaceDescriptor(debugName, outDescriptor);
|
||||
}
|
||||
|
||||
private:
|
||||
XCUIHostedPreviewQueue& m_queue;
|
||||
XCUIHostedPreviewSurfaceRegistry& m_surfaceRegistry;
|
||||
XCUIHostedPreviewStats m_lastStats = {};
|
||||
};
|
||||
|
||||
inline std::unique_ptr<IXCUIHostedPreviewPresenter> CreateQueuedNativeXCUIHostedPreviewPresenter(
|
||||
XCUIHostedPreviewQueue& queue,
|
||||
XCUIHostedPreviewSurfaceRegistry& surfaceRegistry) {
|
||||
return std::make_unique<QueuedNativeXCUIHostedPreviewPresenter>(queue, surfaceRegistry);
|
||||
}
|
||||
|
||||
inline std::unique_ptr<IXCUIHostedPreviewPresenter> CreateNullXCUIHostedPreviewPresenter() {
|
||||
return std::make_unique<NullXCUIHostedPreviewPresenter>();
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,587 +0,0 @@
|
||||
#include "XCUIBackend/XCUIInputBridge.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Input::KeyCode;
|
||||
using XCEngine::UI::UIInputEvent;
|
||||
using XCEngine::UI::UIInputEventType;
|
||||
using XCEngine::UI::UIPointerButton;
|
||||
using XCEngine::UI::UIPoint;
|
||||
|
||||
UIPoint Subtract(const UIPoint& lhs, const UIPoint& rhs) {
|
||||
return UIPoint(lhs.x - rhs.x, lhs.y - rhs.y);
|
||||
}
|
||||
|
||||
UIPointerButton ToPointerButton(std::size_t index) {
|
||||
switch (index) {
|
||||
case 0u:
|
||||
return UIPointerButton::Left;
|
||||
case 1u:
|
||||
return UIPointerButton::Right;
|
||||
case 2u:
|
||||
return UIPointerButton::Middle;
|
||||
case 3u:
|
||||
return UIPointerButton::X1;
|
||||
case 4u:
|
||||
return UIPointerButton::X2;
|
||||
default:
|
||||
return UIPointerButton::None;
|
||||
}
|
||||
}
|
||||
|
||||
void AppendEvent(std::vector<UIInputEvent>& events, const UIInputEvent& event) {
|
||||
events.push_back(event);
|
||||
}
|
||||
|
||||
UIInputEvent MakeBaseEvent(
|
||||
UIInputEventType type,
|
||||
const XCUIInputBridgeFrameSnapshot& snapshot) {
|
||||
UIInputEvent event = {};
|
||||
event.type = type;
|
||||
event.position = snapshot.pointerPosition;
|
||||
event.timestampNanoseconds = snapshot.timestampNanoseconds;
|
||||
event.modifiers = snapshot.modifiers;
|
||||
return event;
|
||||
}
|
||||
|
||||
void AppendUniqueKeyCode(std::vector<std::int32_t>& keyCodes, std::int32_t keyCode) {
|
||||
if (keyCode == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto it = std::find(keyCodes.begin(), keyCodes.end(), keyCode);
|
||||
if (it == keyCodes.end()) {
|
||||
keyCodes.push_back(keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t GetTimestampNanoseconds() {
|
||||
return static_cast<std::uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
.count());
|
||||
}
|
||||
|
||||
UIPoint MakeClientPoint(LPARAM lParam) {
|
||||
return UIPoint(
|
||||
static_cast<float>(static_cast<short>(LOWORD(static_cast<DWORD_PTR>(lParam)))),
|
||||
static_cast<float>(static_cast<short>(HIWORD(static_cast<DWORD_PTR>(lParam)))));
|
||||
}
|
||||
|
||||
std::int32_t TranslateVirtualKeyToXCUIKeyCode(WPARAM wParam, LPARAM lParam) {
|
||||
switch (static_cast<UINT>(wParam)) {
|
||||
case 'A': return static_cast<std::int32_t>(KeyCode::A);
|
||||
case 'B': return static_cast<std::int32_t>(KeyCode::B);
|
||||
case 'C': return static_cast<std::int32_t>(KeyCode::C);
|
||||
case 'D': return static_cast<std::int32_t>(KeyCode::D);
|
||||
case 'E': return static_cast<std::int32_t>(KeyCode::E);
|
||||
case 'F': return static_cast<std::int32_t>(KeyCode::F);
|
||||
case 'G': return static_cast<std::int32_t>(KeyCode::G);
|
||||
case 'H': return static_cast<std::int32_t>(KeyCode::H);
|
||||
case 'I': return static_cast<std::int32_t>(KeyCode::I);
|
||||
case 'J': return static_cast<std::int32_t>(KeyCode::J);
|
||||
case 'K': return static_cast<std::int32_t>(KeyCode::K);
|
||||
case 'L': return static_cast<std::int32_t>(KeyCode::L);
|
||||
case 'M': return static_cast<std::int32_t>(KeyCode::M);
|
||||
case 'N': return static_cast<std::int32_t>(KeyCode::N);
|
||||
case 'O': return static_cast<std::int32_t>(KeyCode::O);
|
||||
case 'P': return static_cast<std::int32_t>(KeyCode::P);
|
||||
case 'Q': return static_cast<std::int32_t>(KeyCode::Q);
|
||||
case 'R': return static_cast<std::int32_t>(KeyCode::R);
|
||||
case 'S': return static_cast<std::int32_t>(KeyCode::S);
|
||||
case 'T': return static_cast<std::int32_t>(KeyCode::T);
|
||||
case 'U': return static_cast<std::int32_t>(KeyCode::U);
|
||||
case 'V': return static_cast<std::int32_t>(KeyCode::V);
|
||||
case 'W': return static_cast<std::int32_t>(KeyCode::W);
|
||||
case 'X': return static_cast<std::int32_t>(KeyCode::X);
|
||||
case 'Y': return static_cast<std::int32_t>(KeyCode::Y);
|
||||
case 'Z': return static_cast<std::int32_t>(KeyCode::Z);
|
||||
case '0': return static_cast<std::int32_t>(KeyCode::Zero);
|
||||
case '1': return static_cast<std::int32_t>(KeyCode::One);
|
||||
case '2': return static_cast<std::int32_t>(KeyCode::Two);
|
||||
case '3': return static_cast<std::int32_t>(KeyCode::Three);
|
||||
case '4': return static_cast<std::int32_t>(KeyCode::Four);
|
||||
case '5': return static_cast<std::int32_t>(KeyCode::Five);
|
||||
case '6': return static_cast<std::int32_t>(KeyCode::Six);
|
||||
case '7': return static_cast<std::int32_t>(KeyCode::Seven);
|
||||
case '8': return static_cast<std::int32_t>(KeyCode::Eight);
|
||||
case '9': return static_cast<std::int32_t>(KeyCode::Nine);
|
||||
case VK_SPACE: return static_cast<std::int32_t>(KeyCode::Space);
|
||||
case VK_TAB: return static_cast<std::int32_t>(KeyCode::Tab);
|
||||
case VK_RETURN: return static_cast<std::int32_t>(KeyCode::Enter);
|
||||
case VK_ESCAPE: return static_cast<std::int32_t>(KeyCode::Escape);
|
||||
case VK_SHIFT: {
|
||||
const UINT scanCode = (static_cast<UINT>(lParam) >> 16) & 0xFFu;
|
||||
const UINT leftShiftScanCode = MapVirtualKeyW(VK_LSHIFT, MAPVK_VK_TO_VSC);
|
||||
return static_cast<std::int32_t>(
|
||||
scanCode == leftShiftScanCode ? KeyCode::LeftShift : KeyCode::RightShift);
|
||||
}
|
||||
case VK_CONTROL:
|
||||
return static_cast<std::int32_t>(
|
||||
(static_cast<UINT>(lParam) & 0x01000000u) != 0u ? KeyCode::RightCtrl : KeyCode::LeftCtrl);
|
||||
case VK_MENU:
|
||||
return static_cast<std::int32_t>(
|
||||
(static_cast<UINT>(lParam) & 0x01000000u) != 0u ? KeyCode::RightAlt : KeyCode::LeftAlt);
|
||||
case VK_UP: return static_cast<std::int32_t>(KeyCode::Up);
|
||||
case VK_DOWN: return static_cast<std::int32_t>(KeyCode::Down);
|
||||
case VK_LEFT: return static_cast<std::int32_t>(KeyCode::Left);
|
||||
case VK_RIGHT: return static_cast<std::int32_t>(KeyCode::Right);
|
||||
case VK_HOME: return static_cast<std::int32_t>(KeyCode::Home);
|
||||
case VK_END: return static_cast<std::int32_t>(KeyCode::End);
|
||||
case VK_PRIOR: return static_cast<std::int32_t>(KeyCode::PageUp);
|
||||
case VK_NEXT: return static_cast<std::int32_t>(KeyCode::PageDown);
|
||||
case VK_DELETE: return static_cast<std::int32_t>(KeyCode::Delete);
|
||||
case VK_BACK: return static_cast<std::int32_t>(KeyCode::Backspace);
|
||||
case VK_F1: return static_cast<std::int32_t>(KeyCode::F1);
|
||||
case VK_F2: return static_cast<std::int32_t>(KeyCode::F2);
|
||||
case VK_F3: return static_cast<std::int32_t>(KeyCode::F3);
|
||||
case VK_F4: return static_cast<std::int32_t>(KeyCode::F4);
|
||||
case VK_F5: return static_cast<std::int32_t>(KeyCode::F5);
|
||||
case VK_F6: return static_cast<std::int32_t>(KeyCode::F6);
|
||||
case VK_F7: return static_cast<std::int32_t>(KeyCode::F7);
|
||||
case VK_F8: return static_cast<std::int32_t>(KeyCode::F8);
|
||||
case VK_F9: return static_cast<std::int32_t>(KeyCode::F9);
|
||||
case VK_F10: return static_cast<std::int32_t>(KeyCode::F10);
|
||||
case VK_F11: return static_cast<std::int32_t>(KeyCode::F11);
|
||||
case VK_F12: return static_cast<std::int32_t>(KeyCode::F12);
|
||||
case VK_OEM_MINUS: return static_cast<std::int32_t>(KeyCode::Minus);
|
||||
case VK_OEM_PLUS: return static_cast<std::int32_t>(KeyCode::Equals);
|
||||
case VK_OEM_4: return static_cast<std::int32_t>(KeyCode::BracketLeft);
|
||||
case VK_OEM_6: return static_cast<std::int32_t>(KeyCode::BracketRight);
|
||||
case VK_OEM_1: return static_cast<std::int32_t>(KeyCode::Semicolon);
|
||||
case VK_OEM_7: return static_cast<std::int32_t>(KeyCode::Quote);
|
||||
case VK_OEM_COMMA: return static_cast<std::int32_t>(KeyCode::Comma);
|
||||
case VK_OEM_PERIOD: return static_cast<std::int32_t>(KeyCode::Period);
|
||||
case VK_OEM_2: return static_cast<std::int32_t>(KeyCode::Slash);
|
||||
case VK_OEM_5: return static_cast<std::int32_t>(KeyCode::Backslash);
|
||||
case VK_OEM_3: return static_cast<std::int32_t>(KeyCode::Backtick);
|
||||
default:
|
||||
return static_cast<std::int32_t>(KeyCode::None);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const XCUIInputBridgeKeyState* XCUIInputBridgeFrameSnapshot::FindKeyState(std::int32_t keyCode) const {
|
||||
for (const XCUIInputBridgeKeyState& keyState : keys) {
|
||||
if (keyState.keyCode == keyCode) {
|
||||
return &keyState;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool XCUIInputBridgeFrameSnapshot::IsKeyDown(std::int32_t keyCode) const {
|
||||
const XCUIInputBridgeKeyState* keyState = FindKeyState(keyCode);
|
||||
return keyState != nullptr && keyState->down;
|
||||
}
|
||||
|
||||
bool XCUIInputBridgeFrameDelta::HasEvents() const {
|
||||
return !events.empty();
|
||||
}
|
||||
|
||||
bool XCUIInputBridgeFrameDelta::HasPointerActivity() const {
|
||||
return pointer.moved ||
|
||||
pointer.entered ||
|
||||
pointer.left ||
|
||||
pointer.wheelDelta.x != 0.0f ||
|
||||
pointer.wheelDelta.y != 0.0f ||
|
||||
std::any_of(pointer.pressed.begin(), pointer.pressed.end(), [](bool value) { return value; }) ||
|
||||
std::any_of(pointer.released.begin(), pointer.released.end(), [](bool value) { return value; });
|
||||
}
|
||||
|
||||
bool XCUIInputBridgeFrameDelta::HasKeyboardActivity() const {
|
||||
return !keyboard.pressedKeys.empty() ||
|
||||
!keyboard.releasedKeys.empty() ||
|
||||
!keyboard.repeatedKeys.empty() ||
|
||||
!keyboard.characters.empty();
|
||||
}
|
||||
|
||||
bool XCUIInputBridgeFrameDelta::HasEventType(UI::UIInputEventType type) const {
|
||||
return std::any_of(
|
||||
events.begin(),
|
||||
events.end(),
|
||||
[type](const UIInputEvent& event) {
|
||||
return event.type == type;
|
||||
});
|
||||
}
|
||||
|
||||
void XCUIInputBridge::Reset() {
|
||||
m_hasBaseline = false;
|
||||
m_baseline = {};
|
||||
}
|
||||
|
||||
void XCUIInputBridge::Prime(const XCUIInputBridgeFrameSnapshot& snapshot) {
|
||||
m_baseline = snapshot;
|
||||
m_hasBaseline = true;
|
||||
}
|
||||
|
||||
XCUIInputBridgeFrameDelta XCUIInputBridge::Translate(const XCUIInputBridgeFrameSnapshot& current) {
|
||||
const XCUIInputBridgeFrameSnapshot previous = m_hasBaseline ? m_baseline : XCUIInputBridgeFrameSnapshot();
|
||||
XCUIInputBridgeFrameDelta delta = Translate(previous, current);
|
||||
m_baseline = current;
|
||||
m_hasBaseline = true;
|
||||
return delta;
|
||||
}
|
||||
|
||||
XCUIInputBridgeFrameDelta XCUIInputBridge::Translate(
|
||||
const XCUIInputBridgeFrameSnapshot& previous,
|
||||
const XCUIInputBridgeFrameSnapshot& current) {
|
||||
XCUIInputBridgeFrameDelta delta = {};
|
||||
delta.state = current;
|
||||
delta.pointer.wheelDelta = current.wheelDelta;
|
||||
delta.keyboard.characters = current.characters;
|
||||
|
||||
delta.focusGained = current.windowFocused && !previous.windowFocused;
|
||||
delta.focusLost = previous.windowFocused && !current.windowFocused;
|
||||
|
||||
if (delta.focusGained) {
|
||||
AppendEvent(delta.events, MakeBaseEvent(UIInputEventType::FocusGained, current));
|
||||
}
|
||||
if (delta.focusLost) {
|
||||
AppendEvent(delta.events, MakeBaseEvent(UIInputEventType::FocusLost, current));
|
||||
}
|
||||
|
||||
delta.pointer.entered = current.pointerInside && !previous.pointerInside;
|
||||
delta.pointer.left = previous.pointerInside && !current.pointerInside;
|
||||
if (previous.pointerInside && current.pointerInside) {
|
||||
delta.pointer.delta = Subtract(current.pointerPosition, previous.pointerPosition);
|
||||
}
|
||||
|
||||
const bool pointerMovedInside =
|
||||
current.pointerInside &&
|
||||
(delta.pointer.entered ||
|
||||
previous.pointerPosition.x != current.pointerPosition.x ||
|
||||
previous.pointerPosition.y != current.pointerPosition.y);
|
||||
delta.pointer.moved = pointerMovedInside;
|
||||
|
||||
if (delta.pointer.entered) {
|
||||
AppendEvent(delta.events, MakeBaseEvent(UIInputEventType::PointerEnter, current));
|
||||
}
|
||||
if (delta.pointer.moved) {
|
||||
UIInputEvent event = MakeBaseEvent(UIInputEventType::PointerMove, current);
|
||||
event.delta = delta.pointer.delta;
|
||||
AppendEvent(delta.events, event);
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < XCUIInputBridgeFrameSnapshot::PointerButtonCount; ++index) {
|
||||
const bool wasDown = previous.pointerButtonsDown[index];
|
||||
const bool isDown = current.pointerButtonsDown[index];
|
||||
if (isDown == wasDown) {
|
||||
continue;
|
||||
}
|
||||
|
||||
UIInputEvent event = MakeBaseEvent(
|
||||
isDown ? UIInputEventType::PointerButtonDown : UIInputEventType::PointerButtonUp,
|
||||
current);
|
||||
event.pointerButton = ToPointerButton(index);
|
||||
|
||||
if (isDown) {
|
||||
delta.pointer.pressed[index] = true;
|
||||
} else {
|
||||
delta.pointer.released[index] = true;
|
||||
}
|
||||
|
||||
AppendEvent(delta.events, event);
|
||||
}
|
||||
|
||||
if (current.wheelDelta.x != 0.0f || current.wheelDelta.y != 0.0f) {
|
||||
UIInputEvent event = MakeBaseEvent(UIInputEventType::PointerWheel, current);
|
||||
event.delta = current.wheelDelta;
|
||||
event.wheelDelta = current.wheelDelta.y != 0.0f ? current.wheelDelta.y : current.wheelDelta.x;
|
||||
AppendEvent(delta.events, event);
|
||||
}
|
||||
|
||||
if (delta.pointer.left) {
|
||||
UIInputEvent event = MakeBaseEvent(UIInputEventType::PointerLeave, current);
|
||||
if (previous.pointerInside) {
|
||||
event.delta = Subtract(current.pointerPosition, previous.pointerPosition);
|
||||
}
|
||||
AppendEvent(delta.events, event);
|
||||
}
|
||||
|
||||
std::vector<std::int32_t> keyCodes = {};
|
||||
keyCodes.reserve(previous.keys.size() + current.keys.size());
|
||||
for (const XCUIInputBridgeKeyState& keyState : previous.keys) {
|
||||
AppendUniqueKeyCode(keyCodes, keyState.keyCode);
|
||||
}
|
||||
for (const XCUIInputBridgeKeyState& keyState : current.keys) {
|
||||
AppendUniqueKeyCode(keyCodes, keyState.keyCode);
|
||||
}
|
||||
std::sort(keyCodes.begin(), keyCodes.end());
|
||||
|
||||
for (std::int32_t keyCode : keyCodes) {
|
||||
const XCUIInputBridgeKeyState* previousKeyState = previous.FindKeyState(keyCode);
|
||||
const XCUIInputBridgeKeyState* currentKeyState = current.FindKeyState(keyCode);
|
||||
const bool wasDown = previousKeyState != nullptr && previousKeyState->down;
|
||||
const bool isDown = currentKeyState != nullptr && currentKeyState->down;
|
||||
|
||||
if (isDown && !wasDown) {
|
||||
UIInputEvent event = MakeBaseEvent(UIInputEventType::KeyDown, current);
|
||||
event.keyCode = keyCode;
|
||||
delta.keyboard.pressedKeys.push_back(keyCode);
|
||||
AppendEvent(delta.events, event);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isDown && wasDown) {
|
||||
UIInputEvent event = MakeBaseEvent(UIInputEventType::KeyUp, current);
|
||||
event.keyCode = keyCode;
|
||||
delta.keyboard.releasedKeys.push_back(keyCode);
|
||||
AppendEvent(delta.events, event);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isDown && wasDown && currentKeyState != nullptr && currentKeyState->repeat) {
|
||||
UIInputEvent event = MakeBaseEvent(UIInputEventType::KeyDown, current);
|
||||
event.keyCode = keyCode;
|
||||
event.repeat = true;
|
||||
delta.keyboard.repeatedKeys.push_back(keyCode);
|
||||
AppendEvent(delta.events, event);
|
||||
}
|
||||
}
|
||||
|
||||
for (std::uint32_t character : current.characters) {
|
||||
if (character == 0u) {
|
||||
continue;
|
||||
}
|
||||
|
||||
UIInputEvent event = MakeBaseEvent(UIInputEventType::Character, current);
|
||||
event.character = character;
|
||||
AppendEvent(delta.events, event);
|
||||
}
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
void XCUIWin32InputSource::Reset() {
|
||||
m_pointerPosition = {};
|
||||
m_pointerInside = false;
|
||||
m_windowFocused = false;
|
||||
m_trackingMouseLeave = false;
|
||||
m_pointerButtonsDown.fill(false);
|
||||
m_wheelDelta = {};
|
||||
m_modifiers = {};
|
||||
m_keyStates.clear();
|
||||
m_characters.clear();
|
||||
}
|
||||
|
||||
void XCUIWin32InputSource::HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
switch (message) {
|
||||
case WM_SETFOCUS:
|
||||
m_windowFocused = true;
|
||||
UpdateModifierState();
|
||||
return;
|
||||
case WM_KILLFOCUS:
|
||||
m_windowFocused = false;
|
||||
m_pointerInside = false;
|
||||
m_trackingMouseLeave = false;
|
||||
m_pointerButtonsDown.fill(false);
|
||||
m_keyStates.clear();
|
||||
UpdateModifierState();
|
||||
return;
|
||||
case WM_MOUSEMOVE: {
|
||||
m_pointerPosition = MakeClientPoint(lParam);
|
||||
UpdatePointerInside(hwnd, m_pointerPosition.x, m_pointerPosition.y, true);
|
||||
UpdateModifierState();
|
||||
if (hwnd != nullptr && !m_trackingMouseLeave) {
|
||||
TRACKMOUSEEVENT trackMouseEvent = {};
|
||||
trackMouseEvent.cbSize = sizeof(trackMouseEvent);
|
||||
trackMouseEvent.dwFlags = TME_LEAVE;
|
||||
trackMouseEvent.hwndTrack = hwnd;
|
||||
m_trackingMouseLeave = TrackMouseEvent(&trackMouseEvent) == TRUE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case WM_MOUSELEAVE:
|
||||
m_trackingMouseLeave = false;
|
||||
m_pointerInside = false;
|
||||
return;
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_LBUTTONUP:
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_RBUTTONUP:
|
||||
case WM_MBUTTONDOWN:
|
||||
case WM_MBUTTONUP:
|
||||
case WM_XBUTTONDOWN:
|
||||
case WM_XBUTTONUP: {
|
||||
m_pointerPosition = MakeClientPoint(lParam);
|
||||
UpdatePointerInside(hwnd, m_pointerPosition.x, m_pointerPosition.y, true);
|
||||
UpdateModifierState();
|
||||
switch (message) {
|
||||
case WM_LBUTTONDOWN: SetPointerButtonDown(0u, true); return;
|
||||
case WM_LBUTTONUP: SetPointerButtonDown(0u, false); return;
|
||||
case WM_RBUTTONDOWN: SetPointerButtonDown(1u, true); return;
|
||||
case WM_RBUTTONUP: SetPointerButtonDown(1u, false); return;
|
||||
case WM_MBUTTONDOWN: SetPointerButtonDown(2u, true); return;
|
||||
case WM_MBUTTONUP: SetPointerButtonDown(2u, false); return;
|
||||
case WM_XBUTTONDOWN:
|
||||
SetPointerButtonDown(HIWORD(wParam) == XBUTTON1 ? 3u : 4u, true);
|
||||
return;
|
||||
case WM_XBUTTONUP:
|
||||
SetPointerButtonDown(HIWORD(wParam) == XBUTTON1 ? 3u : 4u, false);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
case WM_MOUSEWHEEL:
|
||||
case WM_MOUSEHWHEEL: {
|
||||
POINT screenPoint = {
|
||||
static_cast<LONG>(static_cast<short>(LOWORD(static_cast<DWORD_PTR>(lParam)))),
|
||||
static_cast<LONG>(static_cast<short>(HIWORD(static_cast<DWORD_PTR>(lParam))))
|
||||
};
|
||||
if (hwnd != nullptr && ScreenToClient(hwnd, &screenPoint)) {
|
||||
m_pointerPosition = UIPoint(static_cast<float>(screenPoint.x), static_cast<float>(screenPoint.y));
|
||||
UpdatePointerInside(hwnd, m_pointerPosition.x, m_pointerPosition.y, false);
|
||||
}
|
||||
const float wheelStep = static_cast<float>(GET_WHEEL_DELTA_WPARAM(wParam)) / static_cast<float>(WHEEL_DELTA);
|
||||
if (message == WM_MOUSEHWHEEL) {
|
||||
m_wheelDelta.x += wheelStep;
|
||||
} else {
|
||||
m_wheelDelta.y += wheelStep;
|
||||
}
|
||||
UpdateModifierState();
|
||||
return;
|
||||
}
|
||||
case WM_KEYDOWN:
|
||||
case WM_SYSKEYDOWN: {
|
||||
UpdateModifierState();
|
||||
const std::int32_t keyCode = TranslateVirtualKeyToXCUIKeyCode(wParam, lParam);
|
||||
const bool repeat = (static_cast<UINT>(lParam) & 0x40000000u) != 0u;
|
||||
SetKeyDown(keyCode, true, repeat);
|
||||
UpdateModifierState();
|
||||
return;
|
||||
}
|
||||
case WM_KEYUP:
|
||||
case WM_SYSKEYUP:
|
||||
SetKeyDown(TranslateVirtualKeyToXCUIKeyCode(wParam, lParam), false, false);
|
||||
UpdateModifierState();
|
||||
return;
|
||||
case WM_CHAR:
|
||||
case WM_SYSCHAR:
|
||||
if (wParam != 0u) {
|
||||
m_characters.push_back(static_cast<std::uint32_t>(wParam));
|
||||
}
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void XCUIWin32InputSource::ClearFrameTransients() {
|
||||
m_wheelDelta = {};
|
||||
m_characters.clear();
|
||||
for (XCUIInputBridgeKeyState& keyState : m_keyStates) {
|
||||
keyState.repeat = false;
|
||||
}
|
||||
}
|
||||
|
||||
XCUIInputBridgeFrameSnapshot XCUIWin32InputSource::CaptureSnapshot(
|
||||
const XCUIInputBridgeCaptureOptions& options) const {
|
||||
XCUIInputBridgeFrameSnapshot snapshot = {};
|
||||
snapshot.pointerPosition = UIPoint(
|
||||
m_pointerPosition.x - options.pointerOffset.x,
|
||||
m_pointerPosition.y - options.pointerOffset.y);
|
||||
snapshot.pointerInside = options.hasPointerInsideOverride ? options.pointerInsideOverride : m_pointerInside;
|
||||
snapshot.pointerButtonsDown = m_pointerButtonsDown;
|
||||
snapshot.wheelDelta = m_wheelDelta;
|
||||
snapshot.modifiers = m_modifiers;
|
||||
snapshot.windowFocused = options.windowFocused && m_windowFocused;
|
||||
snapshot.wantCaptureMouse = false;
|
||||
snapshot.wantCaptureKeyboard = false;
|
||||
snapshot.wantTextInput = false;
|
||||
snapshot.timestampNanoseconds =
|
||||
options.timestampNanoseconds != 0u ? options.timestampNanoseconds : GetTimestampNanoseconds();
|
||||
snapshot.keys = m_keyStates;
|
||||
snapshot.characters = m_characters;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
void XCUIWin32InputSource::UpdateModifierState() {
|
||||
m_modifiers.shift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
|
||||
m_modifiers.control = (GetKeyState(VK_CONTROL) & 0x8000) != 0;
|
||||
m_modifiers.alt = (GetKeyState(VK_MENU) & 0x8000) != 0;
|
||||
m_modifiers.super =
|
||||
(GetKeyState(VK_LWIN) & 0x8000) != 0 ||
|
||||
(GetKeyState(VK_RWIN) & 0x8000) != 0;
|
||||
}
|
||||
|
||||
void XCUIWin32InputSource::UpdatePointerInside(
|
||||
HWND hwnd,
|
||||
float x,
|
||||
float y,
|
||||
bool assumeInsideIfUnknown) {
|
||||
if (hwnd == nullptr) {
|
||||
m_pointerInside = assumeInsideIfUnknown;
|
||||
return;
|
||||
}
|
||||
|
||||
RECT clientRect = {};
|
||||
if (!GetClientRect(hwnd, &clientRect)) {
|
||||
m_pointerInside = assumeInsideIfUnknown;
|
||||
return;
|
||||
}
|
||||
|
||||
m_pointerInside =
|
||||
x >= static_cast<float>(clientRect.left) &&
|
||||
y >= static_cast<float>(clientRect.top) &&
|
||||
x < static_cast<float>(clientRect.right) &&
|
||||
y < static_cast<float>(clientRect.bottom);
|
||||
}
|
||||
|
||||
void XCUIWin32InputSource::SetPointerButtonDown(std::size_t index, bool down) {
|
||||
if (index < m_pointerButtonsDown.size()) {
|
||||
m_pointerButtonsDown[index] = down;
|
||||
}
|
||||
}
|
||||
|
||||
void XCUIWin32InputSource::SetKeyDown(std::int32_t keyCode, bool down, bool repeat) {
|
||||
if (keyCode == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto it = std::find_if(
|
||||
m_keyStates.begin(),
|
||||
m_keyStates.end(),
|
||||
[keyCode](const XCUIInputBridgeKeyState& state) {
|
||||
return state.keyCode == keyCode;
|
||||
});
|
||||
|
||||
if (!down) {
|
||||
if (it != m_keyStates.end()) {
|
||||
m_keyStates.erase(it);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (it != m_keyStates.end()) {
|
||||
it->down = true;
|
||||
it->repeat = it->repeat || repeat;
|
||||
return;
|
||||
}
|
||||
|
||||
XCUIInputBridgeKeyState state = {};
|
||||
state.keyCode = keyCode;
|
||||
state.down = true;
|
||||
state.repeat = repeat;
|
||||
m_keyStates.push_back(state);
|
||||
std::sort(
|
||||
m_keyStates.begin(),
|
||||
m_keyStates.end(),
|
||||
[](const XCUIInputBridgeKeyState& lhs, const XCUIInputBridgeKeyState& rhs) {
|
||||
return lhs.keyCode < rhs.keyCode;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,159 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
struct XCUIInputBridgeKeyState {
|
||||
std::int32_t keyCode = 0;
|
||||
bool down = false;
|
||||
bool repeat = false;
|
||||
};
|
||||
|
||||
struct XCUIInputBridgeFrameSnapshot {
|
||||
static constexpr std::size_t PointerButtonCount = 5u;
|
||||
|
||||
UI::UIPoint pointerPosition = {};
|
||||
bool pointerInside = false;
|
||||
std::array<bool, PointerButtonCount> pointerButtonsDown = {};
|
||||
UI::UIPoint wheelDelta = {};
|
||||
UI::UIInputModifiers modifiers = {};
|
||||
bool windowFocused = false;
|
||||
bool wantCaptureMouse = false;
|
||||
bool wantCaptureKeyboard = false;
|
||||
bool wantTextInput = false;
|
||||
std::uint64_t timestampNanoseconds = 0;
|
||||
std::vector<XCUIInputBridgeKeyState> keys = {};
|
||||
std::vector<std::uint32_t> characters = {};
|
||||
|
||||
const XCUIInputBridgeKeyState* FindKeyState(std::int32_t keyCode) const;
|
||||
bool IsKeyDown(std::int32_t keyCode) const;
|
||||
};
|
||||
|
||||
struct XCUIInputBridgeCaptureOptions {
|
||||
UI::UIPoint pointerOffset = {};
|
||||
bool hasPointerInsideOverride = false;
|
||||
bool pointerInsideOverride = false;
|
||||
bool windowFocused = true;
|
||||
std::uint64_t timestampNanoseconds = 0;
|
||||
};
|
||||
|
||||
struct XCUIInputBridgePointerDelta {
|
||||
UI::UIPoint delta = {};
|
||||
UI::UIPoint wheelDelta = {};
|
||||
std::array<bool, XCUIInputBridgeFrameSnapshot::PointerButtonCount> pressed = {};
|
||||
std::array<bool, XCUIInputBridgeFrameSnapshot::PointerButtonCount> released = {};
|
||||
bool moved = false;
|
||||
bool entered = false;
|
||||
bool left = false;
|
||||
};
|
||||
|
||||
struct XCUIInputBridgeKeyboardDelta {
|
||||
std::vector<std::int32_t> pressedKeys = {};
|
||||
std::vector<std::int32_t> releasedKeys = {};
|
||||
std::vector<std::int32_t> repeatedKeys = {};
|
||||
std::vector<std::uint32_t> characters = {};
|
||||
};
|
||||
|
||||
struct XCUIInputBridgeFrameDelta {
|
||||
XCUIInputBridgeFrameSnapshot state = {};
|
||||
XCUIInputBridgePointerDelta pointer = {};
|
||||
XCUIInputBridgeKeyboardDelta keyboard = {};
|
||||
std::vector<UI::UIInputEvent> events = {};
|
||||
bool focusGained = false;
|
||||
bool focusLost = false;
|
||||
|
||||
bool HasEvents() const;
|
||||
bool HasPointerActivity() const;
|
||||
bool HasKeyboardActivity() const;
|
||||
bool HasEventType(UI::UIInputEventType type) const;
|
||||
};
|
||||
|
||||
class IXCUIInputSnapshotSource {
|
||||
public:
|
||||
virtual ~IXCUIInputSnapshotSource() = default;
|
||||
|
||||
virtual XCUIInputBridgeFrameSnapshot CaptureSnapshot(
|
||||
const XCUIInputBridgeCaptureOptions& options = XCUIInputBridgeCaptureOptions()) const = 0;
|
||||
virtual const UI::UIPoint& GetPointerPosition() const = 0;
|
||||
};
|
||||
|
||||
class XCUIInputBridge {
|
||||
public:
|
||||
void Reset();
|
||||
void Prime(const XCUIInputBridgeFrameSnapshot& snapshot);
|
||||
|
||||
bool HasBaseline() const {
|
||||
return m_hasBaseline;
|
||||
}
|
||||
|
||||
const XCUIInputBridgeFrameSnapshot& GetBaseline() const {
|
||||
return m_baseline;
|
||||
}
|
||||
|
||||
XCUIInputBridgeFrameDelta Translate(const XCUIInputBridgeFrameSnapshot& current);
|
||||
|
||||
static XCUIInputBridgeFrameDelta Translate(
|
||||
const XCUIInputBridgeFrameSnapshot& previous,
|
||||
const XCUIInputBridgeFrameSnapshot& current);
|
||||
|
||||
private:
|
||||
bool m_hasBaseline = false;
|
||||
XCUIInputBridgeFrameSnapshot m_baseline = {};
|
||||
};
|
||||
|
||||
class XCUIWin32InputSource : public IXCUIInputSnapshotSource {
|
||||
public:
|
||||
void Reset();
|
||||
void HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
|
||||
void ClearFrameTransients();
|
||||
|
||||
XCUIInputBridgeFrameSnapshot CaptureSnapshot(
|
||||
const XCUIInputBridgeCaptureOptions& options = XCUIInputBridgeCaptureOptions()) const override;
|
||||
|
||||
const UI::UIPoint& GetPointerPosition() const override {
|
||||
return m_pointerPosition;
|
||||
}
|
||||
|
||||
bool IsPointerInsideWindow() const {
|
||||
return m_pointerInside;
|
||||
}
|
||||
|
||||
bool IsWindowFocused() const {
|
||||
return m_windowFocused;
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateModifierState();
|
||||
void UpdatePointerInside(HWND hwnd, float x, float y, bool assumeInsideIfUnknown);
|
||||
void SetPointerButtonDown(std::size_t index, bool down);
|
||||
void SetKeyDown(std::int32_t keyCode, bool down, bool repeat);
|
||||
|
||||
UI::UIPoint m_pointerPosition = {};
|
||||
bool m_pointerInside = false;
|
||||
bool m_windowFocused = false;
|
||||
bool m_trackingMouseLeave = false;
|
||||
std::array<bool, XCUIInputBridgeFrameSnapshot::PointerButtonCount> m_pointerButtonsDown = {};
|
||||
UI::UIPoint m_wheelDelta = {};
|
||||
UI::UIInputModifiers m_modifiers = {};
|
||||
std::vector<XCUIInputBridgeKeyState> m_keyStates = {};
|
||||
std::vector<std::uint32_t> m_characters = {};
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,90 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace UI {
|
||||
struct UIRect;
|
||||
struct UIPoint;
|
||||
} // namespace UI
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
struct XCUILayoutLabInputState {
|
||||
UI::UIRect canvasRect = {};
|
||||
UI::UIPoint pointerPosition = {};
|
||||
bool pointerInside = false;
|
||||
bool pointerPressed = false;
|
||||
bool navigatePrevious = false;
|
||||
bool navigateNext = false;
|
||||
bool navigateHome = false;
|
||||
bool navigateEnd = false;
|
||||
bool navigateCollapse = false;
|
||||
bool navigateExpand = false;
|
||||
};
|
||||
|
||||
struct XCUILayoutLabFrameStats {
|
||||
bool documentsReady = false;
|
||||
std::string statusMessage = {};
|
||||
std::size_t drawListCount = 0;
|
||||
std::size_t commandCount = 0;
|
||||
std::size_t filledRectCommandCount = 0;
|
||||
std::size_t rectOutlineCommandCount = 0;
|
||||
std::size_t textCommandCount = 0;
|
||||
std::size_t imageCommandCount = 0;
|
||||
std::size_t clipPushCommandCount = 0;
|
||||
std::size_t clipPopCommandCount = 0;
|
||||
std::size_t nativeSupportedCommandCount = 0;
|
||||
std::size_t nativeUnsupportedCommandCount = 0;
|
||||
std::size_t nativeUnsupportedImageCommandCount = 0;
|
||||
std::size_t nativeUnsupportedUnknownCommandCount = 0;
|
||||
bool nativeOverlayReady = false;
|
||||
std::string nativeOverlayStatusMessage = {};
|
||||
std::size_t rowCount = 0;
|
||||
std::size_t columnCount = 0;
|
||||
std::size_t overlayCount = 0;
|
||||
std::size_t scrollViewCount = 0;
|
||||
std::size_t treeViewCount = 0;
|
||||
std::size_t treeItemCount = 0;
|
||||
std::size_t listViewCount = 0;
|
||||
std::size_t listItemCount = 0;
|
||||
std::size_t propertySectionCount = 0;
|
||||
std::size_t fieldRowCount = 0;
|
||||
std::size_t expandedTreeItemCount = 0;
|
||||
std::size_t expandedPropertySectionCount = 0;
|
||||
std::string hoveredElementId = {};
|
||||
std::string selectedElementId = {};
|
||||
};
|
||||
|
||||
struct XCUILayoutLabFrameResult {
|
||||
UI::UIDrawData drawData = {};
|
||||
XCUILayoutLabFrameStats stats = {};
|
||||
};
|
||||
|
||||
class XCUILayoutLabRuntime {
|
||||
public:
|
||||
XCUILayoutLabRuntime();
|
||||
~XCUILayoutLabRuntime();
|
||||
|
||||
XCUILayoutLabRuntime(XCUILayoutLabRuntime&& other) noexcept;
|
||||
XCUILayoutLabRuntime& operator=(XCUILayoutLabRuntime&& other) noexcept;
|
||||
|
||||
XCUILayoutLabRuntime(const XCUILayoutLabRuntime&) = delete;
|
||||
XCUILayoutLabRuntime& operator=(const XCUILayoutLabRuntime&) = delete;
|
||||
|
||||
bool ReloadDocuments();
|
||||
const XCUILayoutLabFrameResult& Update(const XCUILayoutLabInputState& input);
|
||||
const XCUILayoutLabFrameResult& GetFrameResult() const;
|
||||
bool TryGetElementRect(const std::string& elementId, UI::UIRect& outRect) const;
|
||||
|
||||
private:
|
||||
struct RuntimeState;
|
||||
std::unique_ptr<RuntimeState> m_state;
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,227 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCUIShellChromeState.h"
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <initializer_list>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
struct XCUINativeShellMetrics {
|
||||
float outerMargin = 22.0f;
|
||||
float innerGap = 18.0f;
|
||||
float headerHeight = 58.0f;
|
||||
float footerHeight = 34.0f;
|
||||
float panelHeaderHeight = 42.0f;
|
||||
float panelPadding = 14.0f;
|
||||
float panelMinWidth = 260.0f;
|
||||
float panelMinHeight = 180.0f;
|
||||
float primaryPanelWidthRatio = 0.60f;
|
||||
};
|
||||
|
||||
struct XCUINativeShellPanelSpec {
|
||||
XCUIShellPanelId panelId = XCUIShellPanelId::XCUIDemo;
|
||||
std::string_view title = {};
|
||||
bool visible = true;
|
||||
};
|
||||
|
||||
struct XCUINativeShellPanelLayout {
|
||||
XCUIShellPanelId panelId = XCUIShellPanelId::XCUIDemo;
|
||||
std::string title = {};
|
||||
UI::UIRect panelRect = {};
|
||||
UI::UIRect canvasRect = {};
|
||||
bool visible = false;
|
||||
bool hovered = false;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
struct XCUINativeShellLayoutResult {
|
||||
UI::UIRect shellRect = {};
|
||||
UI::UIRect topBarRect = {};
|
||||
UI::UIRect footerRect = {};
|
||||
UI::UIRect workspaceRect = {};
|
||||
std::vector<XCUINativeShellPanelLayout> panelLayouts = {};
|
||||
XCUIShellPanelId activePanel = XCUIShellPanelId::XCUIDemo;
|
||||
};
|
||||
|
||||
inline bool XCUINativeShellContainsPoint(
|
||||
const UI::UIRect& rect,
|
||||
const UI::UIPoint& point) {
|
||||
return point.x >= rect.x &&
|
||||
point.y >= rect.y &&
|
||||
point.x <= rect.x + rect.width &&
|
||||
point.y <= rect.y + rect.height;
|
||||
}
|
||||
|
||||
inline UI::UIRect BuildXCUINativeShellCanvasRect(
|
||||
const UI::UIRect& panelRect,
|
||||
const XCUINativeShellMetrics& metrics = {}) {
|
||||
return UI::UIRect(
|
||||
panelRect.x + metrics.panelPadding,
|
||||
panelRect.y + metrics.panelHeaderHeight,
|
||||
(std::max)(0.0f, panelRect.width - metrics.panelPadding * 2.0f),
|
||||
(std::max)(0.0f, panelRect.height - metrics.panelHeaderHeight - metrics.panelPadding));
|
||||
}
|
||||
|
||||
inline XCUINativeShellPanelLayout BuildXCUINativeShellPanelLayout(
|
||||
const XCUINativeShellPanelSpec& spec,
|
||||
const UI::UIRect& panelRect,
|
||||
const UI::UIPoint& pointerPosition,
|
||||
bool windowFocused,
|
||||
bool active,
|
||||
const XCUINativeShellMetrics& metrics = {}) {
|
||||
XCUINativeShellPanelLayout layout = {};
|
||||
layout.panelId = spec.panelId;
|
||||
layout.title = std::string(spec.title);
|
||||
layout.panelRect = panelRect;
|
||||
layout.canvasRect = BuildXCUINativeShellCanvasRect(panelRect, metrics);
|
||||
layout.visible =
|
||||
spec.visible &&
|
||||
panelRect.width >= metrics.panelMinWidth &&
|
||||
panelRect.height >= metrics.panelMinHeight;
|
||||
layout.hovered = windowFocused && XCUINativeShellContainsPoint(layout.canvasRect, pointerPosition);
|
||||
layout.active = active;
|
||||
return layout;
|
||||
}
|
||||
|
||||
inline XCUIShellPanelId ResolveXCUINativeShellActivePanel(
|
||||
XCUIShellPanelId currentActivePanel,
|
||||
const std::vector<XCUINativeShellPanelLayout>& panelLayouts,
|
||||
bool pointerPressed) {
|
||||
if (panelLayouts.empty()) {
|
||||
return currentActivePanel;
|
||||
}
|
||||
|
||||
bool activePanelStillPresent = false;
|
||||
for (const XCUINativeShellPanelLayout& panelLayout : panelLayouts) {
|
||||
if (panelLayout.panelId == currentActivePanel) {
|
||||
activePanelStillPresent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
XCUIShellPanelId resolvedActivePanel = activePanelStillPresent
|
||||
? currentActivePanel
|
||||
: panelLayouts.front().panelId;
|
||||
if (!pointerPressed) {
|
||||
return resolvedActivePanel;
|
||||
}
|
||||
|
||||
for (const XCUINativeShellPanelLayout& panelLayout : panelLayouts) {
|
||||
if (panelLayout.hovered) {
|
||||
return panelLayout.panelId;
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedActivePanel;
|
||||
}
|
||||
|
||||
inline XCUINativeShellLayoutResult BuildXCUINativeShellLayout(
|
||||
const UI::UIRect& shellRect,
|
||||
std::initializer_list<XCUINativeShellPanelSpec> panelSpecs,
|
||||
XCUIShellPanelId currentActivePanel,
|
||||
const UI::UIPoint& pointerPosition,
|
||||
bool windowFocused,
|
||||
bool pointerPressed,
|
||||
const XCUINativeShellMetrics& metrics = {}) {
|
||||
XCUINativeShellLayoutResult result = {};
|
||||
result.shellRect = shellRect;
|
||||
result.activePanel = currentActivePanel;
|
||||
|
||||
result.topBarRect = UI::UIRect(
|
||||
shellRect.x + metrics.outerMargin,
|
||||
shellRect.y + metrics.outerMargin,
|
||||
(std::max)(0.0f, shellRect.width - metrics.outerMargin * 2.0f),
|
||||
metrics.headerHeight);
|
||||
|
||||
result.footerRect = UI::UIRect(
|
||||
shellRect.x + metrics.outerMargin,
|
||||
(std::max)(
|
||||
result.topBarRect.y + result.topBarRect.height + metrics.innerGap,
|
||||
shellRect.y + shellRect.height - metrics.outerMargin - metrics.footerHeight),
|
||||
(std::max)(0.0f, shellRect.width - metrics.outerMargin * 2.0f),
|
||||
metrics.footerHeight);
|
||||
|
||||
const float workspaceTop = result.topBarRect.y + result.topBarRect.height + metrics.innerGap;
|
||||
const float workspaceBottom = result.footerRect.y - metrics.innerGap;
|
||||
result.workspaceRect = UI::UIRect(
|
||||
shellRect.x + metrics.outerMargin,
|
||||
workspaceTop,
|
||||
(std::max)(0.0f, shellRect.width - metrics.outerMargin * 2.0f),
|
||||
(std::max)(0.0f, workspaceBottom - workspaceTop));
|
||||
|
||||
std::vector<XCUINativeShellPanelSpec> visiblePanels = {};
|
||||
visiblePanels.reserve(panelSpecs.size());
|
||||
for (const XCUINativeShellPanelSpec& panelSpec : panelSpecs) {
|
||||
if (panelSpec.visible) {
|
||||
visiblePanels.push_back(panelSpec);
|
||||
}
|
||||
}
|
||||
|
||||
if (visiblePanels.empty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result.panelLayouts.reserve((std::min)(std::size_t(2), visiblePanels.size()));
|
||||
if (visiblePanels.size() >= 2u) {
|
||||
const float leftWidth = (std::max)(
|
||||
metrics.panelMinWidth,
|
||||
(std::min)(
|
||||
result.workspaceRect.width * metrics.primaryPanelWidthRatio,
|
||||
result.workspaceRect.width - metrics.panelMinWidth - metrics.innerGap));
|
||||
const float rightWidth = (std::max)(0.0f, result.workspaceRect.width - leftWidth - metrics.innerGap);
|
||||
|
||||
result.panelLayouts.push_back(BuildXCUINativeShellPanelLayout(
|
||||
visiblePanels[0],
|
||||
UI::UIRect(
|
||||
result.workspaceRect.x,
|
||||
result.workspaceRect.y,
|
||||
leftWidth,
|
||||
result.workspaceRect.height),
|
||||
pointerPosition,
|
||||
windowFocused,
|
||||
false,
|
||||
metrics));
|
||||
result.panelLayouts.push_back(BuildXCUINativeShellPanelLayout(
|
||||
visiblePanels[1],
|
||||
UI::UIRect(
|
||||
result.workspaceRect.x + leftWidth + metrics.innerGap,
|
||||
result.workspaceRect.y,
|
||||
rightWidth,
|
||||
result.workspaceRect.height),
|
||||
pointerPosition,
|
||||
windowFocused,
|
||||
false,
|
||||
metrics));
|
||||
} else {
|
||||
result.panelLayouts.push_back(BuildXCUINativeShellPanelLayout(
|
||||
visiblePanels.front(),
|
||||
result.workspaceRect,
|
||||
pointerPosition,
|
||||
windowFocused,
|
||||
false,
|
||||
metrics));
|
||||
}
|
||||
|
||||
result.activePanel = ResolveXCUINativeShellActivePanel(
|
||||
currentActivePanel,
|
||||
result.panelLayouts,
|
||||
pointerPressed);
|
||||
for (XCUINativeShellPanelLayout& panelLayout : result.panelLayouts) {
|
||||
panelLayout.active = panelLayout.panelId == result.activePanel;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,113 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCUIBackend/XCUIHostedPreviewPresenter.h"
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
struct XCUIPanelCanvasRequest {
|
||||
const char* childId = nullptr;
|
||||
float height = 0.0f;
|
||||
float topInset = 0.0f;
|
||||
bool bordered = true;
|
||||
bool showSurfaceImage = false;
|
||||
bool drawPreviewFrame = true;
|
||||
const char* placeholderTitle = nullptr;
|
||||
const char* placeholderSubtitle = nullptr;
|
||||
const char* badgeTitle = nullptr;
|
||||
const char* badgeSubtitle = nullptr;
|
||||
XCUIHostedPreviewSurfaceImage surfaceImage = {};
|
||||
};
|
||||
|
||||
struct XCUIPanelCanvasSession {
|
||||
::XCEngine::UI::UIRect hostRect = {};
|
||||
::XCEngine::UI::UIRect canvasRect = {};
|
||||
::XCEngine::UI::UIPoint pointerPosition = {};
|
||||
bool validCanvas = false;
|
||||
bool hovered = false;
|
||||
bool windowFocused = false;
|
||||
};
|
||||
|
||||
struct XCUIPanelCanvasFrameSnapshot {
|
||||
std::string childId = {};
|
||||
XCUIPanelCanvasSession session = {};
|
||||
bool bordered = true;
|
||||
bool showingSurfaceImage = false;
|
||||
bool drawPreviewFrame = true;
|
||||
std::string placeholderTitle = {};
|
||||
std::string placeholderSubtitle = {};
|
||||
std::string badgeTitle = {};
|
||||
std::string badgeSubtitle = {};
|
||||
XCUIHostedPreviewSurfaceImage surfaceImage = {};
|
||||
::XCEngine::UI::UIDrawData overlayDrawData = {};
|
||||
};
|
||||
|
||||
inline const char* ResolveXCUIPanelCanvasChildId(
|
||||
const XCUIPanelCanvasRequest& request,
|
||||
const char* fallback = "XCUIPanelCanvasHost") {
|
||||
if (request.childId != nullptr && request.childId[0] != '\0') {
|
||||
return request.childId;
|
||||
}
|
||||
|
||||
return fallback != nullptr ? fallback : "XCUIPanelCanvasHost";
|
||||
}
|
||||
|
||||
inline XCUIPanelCanvasSession BuildPassiveXCUIPanelCanvasSession(
|
||||
const XCUIPanelCanvasRequest& request) {
|
||||
const float hostHeight = request.height > 0.0f ? request.height : 0.0f;
|
||||
const float topInset = request.topInset > 0.0f ? request.topInset : 0.0f;
|
||||
const float clampedTopInset = topInset < hostHeight ? topInset : hostHeight;
|
||||
|
||||
XCUIPanelCanvasSession session = {};
|
||||
session.hostRect = ::XCEngine::UI::UIRect(0.0f, 0.0f, 0.0f, hostHeight);
|
||||
session.canvasRect = ::XCEngine::UI::UIRect(
|
||||
0.0f,
|
||||
clampedTopInset,
|
||||
0.0f,
|
||||
hostHeight - clampedTopInset);
|
||||
session.pointerPosition = {};
|
||||
session.validCanvas = false;
|
||||
session.hovered = false;
|
||||
session.windowFocused = false;
|
||||
return session;
|
||||
}
|
||||
|
||||
class IXCUIPanelCanvasHost {
|
||||
public:
|
||||
virtual ~IXCUIPanelCanvasHost() = default;
|
||||
|
||||
virtual const char* GetDebugName() const = 0;
|
||||
virtual XCUIPanelCanvasSession BeginCanvas(const XCUIPanelCanvasRequest& request) = 0;
|
||||
virtual void DrawFilledRect(
|
||||
const ::XCEngine::UI::UIRect& rect,
|
||||
const ::XCEngine::UI::UIColor& color,
|
||||
float rounding = 0.0f) = 0;
|
||||
virtual void DrawOutlineRect(
|
||||
const ::XCEngine::UI::UIRect& rect,
|
||||
const ::XCEngine::UI::UIColor& color,
|
||||
float thickness = 1.0f,
|
||||
float rounding = 0.0f) = 0;
|
||||
virtual void DrawText(
|
||||
const ::XCEngine::UI::UIPoint& position,
|
||||
std::string_view text,
|
||||
const ::XCEngine::UI::UIColor& color,
|
||||
float fontSize = 0.0f) = 0;
|
||||
virtual void EndCanvas() = 0;
|
||||
virtual bool TryGetLatestFrameSnapshot(XCUIPanelCanvasFrameSnapshot& outSnapshot) const {
|
||||
outSnapshot = {};
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<IXCUIPanelCanvasHost> CreateNullXCUIPanelCanvasHost();
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,590 +0,0 @@
|
||||
#include "XCUIBackend/XCUIRHICommandCompiler.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
namespace {
|
||||
|
||||
using Compiler = XCUIRHICommandCompiler;
|
||||
|
||||
UI::UIRect NormalizeRect(const UI::UIRect& rect) {
|
||||
UI::UIRect normalized = rect;
|
||||
if (normalized.width < 0.0f) {
|
||||
normalized.x += normalized.width;
|
||||
normalized.width = -normalized.width;
|
||||
}
|
||||
if (normalized.height < 0.0f) {
|
||||
normalized.y += normalized.height;
|
||||
normalized.height = -normalized.height;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
bool IsRectEmpty(const UI::UIRect& rect) {
|
||||
return rect.width <= 0.0f || rect.height <= 0.0f;
|
||||
}
|
||||
|
||||
bool IntersectRects(
|
||||
const UI::UIRect& lhs,
|
||||
const UI::UIRect& rhs,
|
||||
UI::UIRect& outIntersection) {
|
||||
const UI::UIRect left = NormalizeRect(lhs);
|
||||
const UI::UIRect right = NormalizeRect(rhs);
|
||||
const float minX = (std::max)(left.x, right.x);
|
||||
const float minY = (std::max)(left.y, right.y);
|
||||
const float maxX = (std::min)(left.x + left.width, right.x + right.width);
|
||||
const float maxY = (std::min)(left.y + left.height, right.y + right.height);
|
||||
if (maxX <= minX || maxY <= minY) {
|
||||
outIntersection = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
outIntersection = UI::UIRect(minX, minY, maxX - minX, maxY - minY);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RectEquals(const UI::UIRect& lhs, const UI::UIRect& rhs) {
|
||||
return lhs.x == rhs.x &&
|
||||
lhs.y == rhs.y &&
|
||||
lhs.width == rhs.width &&
|
||||
lhs.height == rhs.height;
|
||||
}
|
||||
|
||||
bool TextureEquals(const UI::UITextureHandle& lhs, const UI::UITextureHandle& rhs) {
|
||||
return lhs.nativeHandle == rhs.nativeHandle &&
|
||||
lhs.width == rhs.width &&
|
||||
lhs.height == rhs.height &&
|
||||
lhs.kind == rhs.kind;
|
||||
}
|
||||
|
||||
void AppendFilledRectVertices(
|
||||
std::vector<Compiler::ColorVertex>& outVertices,
|
||||
const UI::UIRect& rect,
|
||||
const UI::UIColor& color) {
|
||||
const Compiler::ColorVertex vertices[6] = {
|
||||
{ { rect.x, rect.y }, { color.r, color.g, color.b, color.a } },
|
||||
{ { rect.x + rect.width, rect.y }, { color.r, color.g, color.b, color.a } },
|
||||
{ { rect.x + rect.width, rect.y + rect.height }, { color.r, color.g, color.b, color.a } },
|
||||
{ { rect.x, rect.y }, { color.r, color.g, color.b, color.a } },
|
||||
{ { rect.x + rect.width, rect.y + rect.height }, { color.r, color.g, color.b, color.a } },
|
||||
{ { rect.x, rect.y + rect.height }, { color.r, color.g, color.b, color.a } }
|
||||
};
|
||||
outVertices.insert(outVertices.end(), std::begin(vertices), std::end(vertices));
|
||||
}
|
||||
|
||||
bool AppendClippedFilledRect(
|
||||
std::vector<Compiler::ColorVertex>& outVertices,
|
||||
const UI::UIRect& rect,
|
||||
const UI::UIColor& color,
|
||||
const UI::UIRect& clipRect) {
|
||||
UI::UIRect clipped = {};
|
||||
if (!IntersectRects(rect, clipRect, clipped)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AppendFilledRectVertices(outVertices, clipped, color);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AppendClippedRectOutline(
|
||||
std::vector<Compiler::ColorVertex>& outVertices,
|
||||
const UI::UIRect& rect,
|
||||
const UI::UIColor& color,
|
||||
float thickness,
|
||||
const UI::UIRect& clipRect) {
|
||||
const UI::UIRect normalized = NormalizeRect(rect);
|
||||
if (IsRectEmpty(normalized)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const float resolvedThickness = (std::max)(1.0f, thickness);
|
||||
const float halfWidth = normalized.width * 0.5f;
|
||||
const float halfHeight = normalized.height * 0.5f;
|
||||
const float edgeThickness = (std::min)(resolvedThickness, (std::min)(halfWidth, halfHeight));
|
||||
if (edgeThickness <= 0.0f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool rendered = false;
|
||||
rendered |= AppendClippedFilledRect(
|
||||
outVertices,
|
||||
UI::UIRect(normalized.x, normalized.y, normalized.width, edgeThickness),
|
||||
color,
|
||||
clipRect);
|
||||
rendered |= AppendClippedFilledRect(
|
||||
outVertices,
|
||||
UI::UIRect(
|
||||
normalized.x,
|
||||
normalized.y + normalized.height - edgeThickness,
|
||||
normalized.width,
|
||||
edgeThickness),
|
||||
color,
|
||||
clipRect);
|
||||
rendered |= AppendClippedFilledRect(
|
||||
outVertices,
|
||||
UI::UIRect(
|
||||
normalized.x,
|
||||
normalized.y + edgeThickness,
|
||||
edgeThickness,
|
||||
normalized.height - edgeThickness * 2.0f),
|
||||
color,
|
||||
clipRect);
|
||||
rendered |= AppendClippedFilledRect(
|
||||
outVertices,
|
||||
UI::UIRect(
|
||||
normalized.x + normalized.width - edgeThickness,
|
||||
normalized.y + edgeThickness,
|
||||
edgeThickness,
|
||||
normalized.height - edgeThickness * 2.0f),
|
||||
color,
|
||||
clipRect);
|
||||
return rendered;
|
||||
}
|
||||
|
||||
void AppendTexturedRectVertices(
|
||||
std::vector<Compiler::TexturedVertex>& outVertices,
|
||||
const UI::UIRect& rect,
|
||||
float u0,
|
||||
float v0,
|
||||
float u1,
|
||||
float v1,
|
||||
const UI::UIColor& color) {
|
||||
const Compiler::TexturedVertex vertices[6] = {
|
||||
{ { rect.x, rect.y }, { u0, v0 }, { color.r, color.g, color.b, color.a } },
|
||||
{ { rect.x + rect.width, rect.y }, { u1, v0 }, { color.r, color.g, color.b, color.a } },
|
||||
{ { rect.x + rect.width, rect.y + rect.height }, { u1, v1 }, { color.r, color.g, color.b, color.a } },
|
||||
{ { rect.x, rect.y }, { u0, v0 }, { color.r, color.g, color.b, color.a } },
|
||||
{ { rect.x + rect.width, rect.y + rect.height }, { u1, v1 }, { color.r, color.g, color.b, color.a } },
|
||||
{ { rect.x, rect.y + rect.height }, { u0, v1 }, { color.r, color.g, color.b, color.a } }
|
||||
};
|
||||
outVertices.insert(outVertices.end(), std::begin(vertices), std::end(vertices));
|
||||
}
|
||||
|
||||
bool AppendClippedTexturedRect(
|
||||
std::vector<Compiler::TexturedVertex>& outVertices,
|
||||
const UI::UIRect& rect,
|
||||
float u0,
|
||||
float v0,
|
||||
float u1,
|
||||
float v1,
|
||||
const UI::UIColor& color,
|
||||
const UI::UIRect& clipRect) {
|
||||
const bool flipU = rect.width < 0.0f;
|
||||
const bool flipV = rect.height < 0.0f;
|
||||
const UI::UIRect normalized = NormalizeRect(rect);
|
||||
if (IsRectEmpty(normalized)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UI::UIRect clipped = {};
|
||||
if (!IntersectRects(normalized, clipRect, clipped)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const float leftT = (clipped.x - normalized.x) / normalized.width;
|
||||
const float rightT = (clipped.x + clipped.width - normalized.x) / normalized.width;
|
||||
const float topT = (clipped.y - normalized.y) / normalized.height;
|
||||
const float bottomT = (clipped.y + clipped.height - normalized.y) / normalized.height;
|
||||
const float resolvedU0 = flipU ? u1 : u0;
|
||||
const float resolvedU1 = flipU ? u0 : u1;
|
||||
const float resolvedV0 = flipV ? v1 : v0;
|
||||
const float resolvedV1 = flipV ? v0 : v1;
|
||||
const float clippedU0 = resolvedU0 + (resolvedU1 - resolvedU0) * leftT;
|
||||
const float clippedU1 = resolvedU0 + (resolvedU1 - resolvedU0) * rightT;
|
||||
const float clippedV0 = resolvedV0 + (resolvedV1 - resolvedV0) * topT;
|
||||
const float clippedV1 = resolvedV0 + (resolvedV1 - resolvedV0) * bottomT;
|
||||
AppendTexturedRectVertices(
|
||||
outVertices,
|
||||
clipped,
|
||||
clippedU0,
|
||||
clippedV0,
|
||||
clippedU1,
|
||||
clippedV1,
|
||||
color);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodeNextUtf8(const char*& cursor, const char* end, std::uint32_t& outCodepoint) {
|
||||
if (cursor >= end) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const unsigned char lead = static_cast<unsigned char>(*cursor++);
|
||||
if (lead < 0x80u) {
|
||||
outCodepoint = static_cast<std::uint32_t>(lead);
|
||||
return true;
|
||||
}
|
||||
|
||||
const int continuationCount =
|
||||
(lead & 0xE0u) == 0xC0u ? 1 :
|
||||
(lead & 0xF0u) == 0xE0u ? 2 :
|
||||
(lead & 0xF8u) == 0xF0u ? 3 :
|
||||
-1;
|
||||
if (continuationCount < 0 || cursor + continuationCount > end) {
|
||||
outCodepoint = 0xFFFDu;
|
||||
cursor = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint32_t codepoint =
|
||||
continuationCount == 1 ? static_cast<std::uint32_t>(lead & 0x1Fu) :
|
||||
continuationCount == 2 ? static_cast<std::uint32_t>(lead & 0x0Fu) :
|
||||
static_cast<std::uint32_t>(lead & 0x07u);
|
||||
for (int index = 0; index < continuationCount; ++index) {
|
||||
const unsigned char continuation = static_cast<unsigned char>(*cursor);
|
||||
if ((continuation & 0xC0u) != 0x80u) {
|
||||
outCodepoint = 0xFFFDu;
|
||||
return true;
|
||||
}
|
||||
++cursor;
|
||||
codepoint = (codepoint << 6u) | static_cast<std::uint32_t>(continuation & 0x3Fu);
|
||||
}
|
||||
|
||||
outCodepoint = codepoint;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AppendOrMergeBatch(
|
||||
std::vector<Compiler::Batch>& outBatches,
|
||||
Compiler::BatchKind kind,
|
||||
std::size_t drawListIndex,
|
||||
std::size_t commandIndex,
|
||||
std::size_t firstVertex,
|
||||
std::size_t vertexCount,
|
||||
const UI::UIRect& clipRect,
|
||||
const UI::UITextureHandle& texture,
|
||||
bool allowMerge) {
|
||||
if (vertexCount == 0u) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (allowMerge && !outBatches.empty()) {
|
||||
Compiler::Batch& lastBatch = outBatches.back();
|
||||
if (lastBatch.kind == kind &&
|
||||
lastBatch.drawListIndex == drawListIndex &&
|
||||
lastBatch.firstCommandIndex + lastBatch.commandCount == commandIndex &&
|
||||
RectEquals(lastBatch.clipRect, clipRect) &&
|
||||
TextureEquals(lastBatch.texture, texture) &&
|
||||
static_cast<std::size_t>(lastBatch.firstVertex + lastBatch.vertexCount) == firstVertex) {
|
||||
lastBatch.vertexCount += static_cast<std::uint32_t>(vertexCount);
|
||||
lastBatch.commandCount += 1u;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Compiler::Batch batch = {};
|
||||
batch.kind = kind;
|
||||
batch.drawListIndex = static_cast<std::uint32_t>(drawListIndex);
|
||||
batch.firstCommandIndex = static_cast<std::uint32_t>(commandIndex);
|
||||
batch.commandCount = 1u;
|
||||
batch.firstVertex = static_cast<std::uint32_t>(firstVertex);
|
||||
batch.vertexCount = static_cast<std::uint32_t>(vertexCount);
|
||||
batch.clipRect = clipRect;
|
||||
batch.texture = texture;
|
||||
outBatches.push_back(batch);
|
||||
}
|
||||
|
||||
bool CompileTextCommand(
|
||||
const UI::UIDrawCommand& command,
|
||||
const UI::UIRect& clipRect,
|
||||
const Compiler::TextGlyphProvider& glyphProvider,
|
||||
std::vector<Compiler::TexturedVertex>& outVertices,
|
||||
Compiler::TextRunContext& outContext,
|
||||
std::size_t& outAddedVertexCount) {
|
||||
outContext = {};
|
||||
if (!glyphProvider.BeginText(command.fontSize, outContext)) {
|
||||
outAddedVertexCount = 0u;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!outContext.texture.IsValid() || outContext.lineHeight <= 0.0f) {
|
||||
outAddedVertexCount = 0u;
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::size_t startVertex = outVertices.size();
|
||||
const float startX = command.position.x;
|
||||
float cursorX = startX;
|
||||
float cursorY = command.position.y;
|
||||
|
||||
const char* cursor = command.text.c_str();
|
||||
const char* const end = cursor + command.text.size();
|
||||
while (cursor < end) {
|
||||
std::uint32_t codepoint = 0u;
|
||||
if (!DecodeNextUtf8(cursor, end, codepoint)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (codepoint == '\r') {
|
||||
continue;
|
||||
}
|
||||
if (codepoint == '\n') {
|
||||
cursorX = startX;
|
||||
cursorY += outContext.lineHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
Compiler::TextGlyph glyph = {};
|
||||
if (!glyphProvider.ResolveGlyph(outContext, codepoint, glyph)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (glyph.visible) {
|
||||
const UI::UIRect glyphRect(
|
||||
cursorX + glyph.x0,
|
||||
cursorY + glyph.y0,
|
||||
glyph.x1 - glyph.x0,
|
||||
glyph.y1 - glyph.y0);
|
||||
AppendClippedTexturedRect(
|
||||
outVertices,
|
||||
glyphRect,
|
||||
glyph.u0,
|
||||
glyph.v0,
|
||||
glyph.u1,
|
||||
glyph.v1,
|
||||
command.color,
|
||||
clipRect);
|
||||
}
|
||||
|
||||
cursorX += glyph.advanceX;
|
||||
}
|
||||
|
||||
outAddedVertexCount = outVertices.size() - startVertex;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void XCUIRHICommandCompiler::CompiledDrawData::Clear() {
|
||||
colorVertices.clear();
|
||||
texturedVertices.clear();
|
||||
batches.clear();
|
||||
stats = {};
|
||||
}
|
||||
|
||||
bool XCUIRHICommandCompiler::CompiledDrawData::Empty() const {
|
||||
return colorVertices.empty() && texturedVertices.empty() && batches.empty();
|
||||
}
|
||||
|
||||
void XCUIRHICommandCompiler::Compile(
|
||||
const UI::UIDrawData& drawData,
|
||||
const CompileConfig& config,
|
||||
CompiledDrawData& outCompiledDrawData) const {
|
||||
outCompiledDrawData.Clear();
|
||||
outCompiledDrawData.stats.drawListCount = drawData.GetDrawListCount();
|
||||
outCompiledDrawData.stats.commandCount = drawData.GetTotalCommandCount();
|
||||
if (drawData.Empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
UI::UIRect surfaceClipRect = NormalizeRect(config.surfaceClipRect);
|
||||
if (IsRectEmpty(surfaceClipRect)) {
|
||||
surfaceClipRect = {};
|
||||
}
|
||||
|
||||
outCompiledDrawData.colorVertices.reserve(drawData.GetTotalCommandCount() * 24u);
|
||||
outCompiledDrawData.texturedVertices.reserve(drawData.GetTotalCommandCount() * 36u);
|
||||
outCompiledDrawData.batches.reserve(drawData.GetTotalCommandCount());
|
||||
|
||||
std::vector<UI::UIRect> clipStack = {};
|
||||
UI::UIRect currentClipRect = surfaceClipRect;
|
||||
|
||||
std::size_t drawListIndex = 0u;
|
||||
for (const UI::UIDrawList& drawList : drawData.GetDrawLists()) {
|
||||
std::size_t commandIndex = 0u;
|
||||
for (const UI::UIDrawCommand& command : drawList.GetCommands()) {
|
||||
bool compiled = false;
|
||||
bool culled = false;
|
||||
bool unsupported = false;
|
||||
|
||||
switch (command.type) {
|
||||
case UI::UIDrawCommandType::FilledRect: {
|
||||
++outCompiledDrawData.stats.filledRectCommandCount;
|
||||
const std::size_t firstVertex = outCompiledDrawData.colorVertices.size();
|
||||
compiled = AppendClippedFilledRect(
|
||||
outCompiledDrawData.colorVertices,
|
||||
command.rect,
|
||||
command.color,
|
||||
currentClipRect);
|
||||
if (compiled) {
|
||||
AppendOrMergeBatch(
|
||||
outCompiledDrawData.batches,
|
||||
BatchKind::Color,
|
||||
drawListIndex,
|
||||
commandIndex,
|
||||
firstVertex,
|
||||
outCompiledDrawData.colorVertices.size() - firstVertex,
|
||||
currentClipRect,
|
||||
{},
|
||||
config.mergeBatchesWithinDrawList);
|
||||
} else {
|
||||
culled = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UI::UIDrawCommandType::RectOutline: {
|
||||
++outCompiledDrawData.stats.rectOutlineCommandCount;
|
||||
const std::size_t firstVertex = outCompiledDrawData.colorVertices.size();
|
||||
compiled = AppendClippedRectOutline(
|
||||
outCompiledDrawData.colorVertices,
|
||||
command.rect,
|
||||
command.color,
|
||||
command.thickness,
|
||||
currentClipRect);
|
||||
if (compiled) {
|
||||
AppendOrMergeBatch(
|
||||
outCompiledDrawData.batches,
|
||||
BatchKind::Color,
|
||||
drawListIndex,
|
||||
commandIndex,
|
||||
firstVertex,
|
||||
outCompiledDrawData.colorVertices.size() - firstVertex,
|
||||
currentClipRect,
|
||||
{},
|
||||
config.mergeBatchesWithinDrawList);
|
||||
} else {
|
||||
culled = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UI::UIDrawCommandType::Text: {
|
||||
++outCompiledDrawData.stats.textCommandCount;
|
||||
if (command.text.empty()) {
|
||||
culled = true;
|
||||
break;
|
||||
}
|
||||
if (config.textGlyphProvider == nullptr) {
|
||||
unsupported = true;
|
||||
break;
|
||||
}
|
||||
|
||||
const std::size_t firstVertex = outCompiledDrawData.texturedVertices.size();
|
||||
std::size_t addedVertexCount = 0u;
|
||||
TextRunContext textRunContext = {};
|
||||
compiled = CompileTextCommand(
|
||||
command,
|
||||
currentClipRect,
|
||||
*config.textGlyphProvider,
|
||||
outCompiledDrawData.texturedVertices,
|
||||
textRunContext,
|
||||
addedVertexCount);
|
||||
if (compiled && addedVertexCount > 0u) {
|
||||
AppendOrMergeBatch(
|
||||
outCompiledDrawData.batches,
|
||||
BatchKind::Textured,
|
||||
drawListIndex,
|
||||
commandIndex,
|
||||
firstVertex,
|
||||
addedVertexCount,
|
||||
currentClipRect,
|
||||
textRunContext.texture,
|
||||
config.mergeBatchesWithinDrawList);
|
||||
} else if (!compiled) {
|
||||
unsupported = true;
|
||||
} else {
|
||||
compiled = false;
|
||||
culled = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UI::UIDrawCommandType::Image: {
|
||||
++outCompiledDrawData.stats.imageCommandCount;
|
||||
if (!command.texture.IsValid()) {
|
||||
unsupported = true;
|
||||
break;
|
||||
}
|
||||
|
||||
const std::size_t firstVertex = outCompiledDrawData.texturedVertices.size();
|
||||
compiled = AppendClippedTexturedRect(
|
||||
outCompiledDrawData.texturedVertices,
|
||||
command.rect,
|
||||
command.uvMin.x,
|
||||
command.uvMin.y,
|
||||
command.uvMax.x,
|
||||
command.uvMax.y,
|
||||
command.color,
|
||||
currentClipRect);
|
||||
if (compiled) {
|
||||
AppendOrMergeBatch(
|
||||
outCompiledDrawData.batches,
|
||||
BatchKind::Textured,
|
||||
drawListIndex,
|
||||
commandIndex,
|
||||
firstVertex,
|
||||
outCompiledDrawData.texturedVertices.size() - firstVertex,
|
||||
currentClipRect,
|
||||
command.texture,
|
||||
config.mergeBatchesWithinDrawList);
|
||||
} else {
|
||||
culled = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UI::UIDrawCommandType::PushClipRect: {
|
||||
++outCompiledDrawData.stats.clipPushCommandCount;
|
||||
UI::UIRect nextClipRect = NormalizeRect(command.rect);
|
||||
UI::UIRect intersection = {};
|
||||
if (command.intersectWithCurrentClip) {
|
||||
if (!IntersectRects(currentClipRect, nextClipRect, intersection)) {
|
||||
intersection = {};
|
||||
}
|
||||
} else if (!IntersectRects(surfaceClipRect, nextClipRect, intersection)) {
|
||||
intersection = {};
|
||||
}
|
||||
clipStack.push_back(currentClipRect);
|
||||
currentClipRect = intersection;
|
||||
compiled = true;
|
||||
outCompiledDrawData.stats.maxClipDepth =
|
||||
(std::max)(outCompiledDrawData.stats.maxClipDepth, clipStack.size());
|
||||
break;
|
||||
}
|
||||
case UI::UIDrawCommandType::PopClipRect:
|
||||
++outCompiledDrawData.stats.clipPopCommandCount;
|
||||
if (!clipStack.empty()) {
|
||||
currentClipRect = clipStack.back();
|
||||
clipStack.pop_back();
|
||||
} else {
|
||||
currentClipRect = surfaceClipRect;
|
||||
++outCompiledDrawData.stats.clipStackUnderflowCount;
|
||||
}
|
||||
compiled = true;
|
||||
break;
|
||||
default:
|
||||
unsupported = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (compiled) {
|
||||
++outCompiledDrawData.stats.compiledCommandCount;
|
||||
} else {
|
||||
++outCompiledDrawData.stats.skippedCommandCount;
|
||||
if (culled) {
|
||||
++outCompiledDrawData.stats.culledCommandCount;
|
||||
}
|
||||
if (unsupported) {
|
||||
++outCompiledDrawData.stats.unsupportedCommandCount;
|
||||
}
|
||||
}
|
||||
|
||||
++commandIndex;
|
||||
}
|
||||
++drawListIndex;
|
||||
}
|
||||
|
||||
outCompiledDrawData.stats.colorVertexCount = outCompiledDrawData.colorVertices.size();
|
||||
outCompiledDrawData.stats.texturedVertexCount = outCompiledDrawData.texturedVertices.size();
|
||||
outCompiledDrawData.stats.triangleCount =
|
||||
(outCompiledDrawData.stats.colorVertexCount +
|
||||
outCompiledDrawData.stats.texturedVertexCount) /
|
||||
3u;
|
||||
outCompiledDrawData.stats.batchCount = outCompiledDrawData.batches.size();
|
||||
outCompiledDrawData.stats.danglingClipDepth = clipStack.size();
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,119 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
class XCUIRHICommandCompiler {
|
||||
public:
|
||||
struct ColorVertex {
|
||||
float position[2] = { 0.0f, 0.0f };
|
||||
float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
};
|
||||
|
||||
struct TexturedVertex {
|
||||
float position[2] = { 0.0f, 0.0f };
|
||||
float uv[2] = { 0.0f, 0.0f };
|
||||
float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
};
|
||||
|
||||
enum class BatchKind : std::uint8_t {
|
||||
Color = 0,
|
||||
Textured
|
||||
};
|
||||
|
||||
struct Batch {
|
||||
BatchKind kind = BatchKind::Color;
|
||||
std::uint32_t drawListIndex = 0;
|
||||
std::uint32_t firstCommandIndex = 0;
|
||||
std::uint32_t commandCount = 0;
|
||||
std::uint32_t firstVertex = 0;
|
||||
std::uint32_t vertexCount = 0;
|
||||
UI::UIRect clipRect = {};
|
||||
UI::UITextureHandle texture = {};
|
||||
};
|
||||
|
||||
struct Stats {
|
||||
std::size_t drawListCount = 0;
|
||||
std::size_t commandCount = 0;
|
||||
std::size_t compiledCommandCount = 0;
|
||||
std::size_t skippedCommandCount = 0;
|
||||
std::size_t culledCommandCount = 0;
|
||||
std::size_t unsupportedCommandCount = 0;
|
||||
std::size_t filledRectCommandCount = 0;
|
||||
std::size_t rectOutlineCommandCount = 0;
|
||||
std::size_t textCommandCount = 0;
|
||||
std::size_t imageCommandCount = 0;
|
||||
std::size_t clipPushCommandCount = 0;
|
||||
std::size_t clipPopCommandCount = 0;
|
||||
std::size_t colorVertexCount = 0;
|
||||
std::size_t texturedVertexCount = 0;
|
||||
std::size_t triangleCount = 0;
|
||||
std::size_t batchCount = 0;
|
||||
std::size_t maxClipDepth = 0;
|
||||
std::size_t clipStackUnderflowCount = 0;
|
||||
std::size_t danglingClipDepth = 0;
|
||||
};
|
||||
|
||||
struct CompiledDrawData {
|
||||
std::vector<ColorVertex> colorVertices = {};
|
||||
std::vector<TexturedVertex> texturedVertices = {};
|
||||
std::vector<Batch> batches = {};
|
||||
Stats stats = {};
|
||||
|
||||
void Clear();
|
||||
bool Empty() const;
|
||||
};
|
||||
|
||||
struct TextRunContext {
|
||||
float requestedFontSize = 0.0f;
|
||||
float resolvedFontSize = 0.0f;
|
||||
float lineHeight = 0.0f;
|
||||
UI::UITextureHandle texture = {};
|
||||
};
|
||||
|
||||
struct TextGlyph {
|
||||
float x0 = 0.0f;
|
||||
float y0 = 0.0f;
|
||||
float x1 = 0.0f;
|
||||
float y1 = 0.0f;
|
||||
float u0 = 0.0f;
|
||||
float v0 = 0.0f;
|
||||
float u1 = 0.0f;
|
||||
float v1 = 0.0f;
|
||||
float advanceX = 0.0f;
|
||||
bool visible = true;
|
||||
};
|
||||
|
||||
class TextGlyphProvider {
|
||||
public:
|
||||
virtual ~TextGlyphProvider() = default;
|
||||
|
||||
virtual bool BeginText(float requestedFontSize, TextRunContext& outContext) const = 0;
|
||||
virtual bool ResolveGlyph(
|
||||
const TextRunContext& context,
|
||||
std::uint32_t codepoint,
|
||||
TextGlyph& outGlyph) const = 0;
|
||||
};
|
||||
|
||||
struct CompileConfig {
|
||||
UI::UIRect surfaceClipRect = {};
|
||||
const TextGlyphProvider* textGlyphProvider = nullptr;
|
||||
bool mergeBatchesWithinDrawList = true;
|
||||
};
|
||||
|
||||
void Compile(
|
||||
const UI::UIDrawData& drawData,
|
||||
const CompileConfig& config,
|
||||
CompiledDrawData& outCompiledDrawData) const;
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,267 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
enum class XCUIRHICommandCategory : std::uint8_t {
|
||||
FilledRect = 0,
|
||||
RectOutline,
|
||||
Text,
|
||||
Image,
|
||||
PushClipRect,
|
||||
PopClipRect,
|
||||
Unknown
|
||||
};
|
||||
|
||||
enum class XCUIRHICommandSupportReason : std::uint8_t {
|
||||
Supported = 0,
|
||||
UnsupportedImageTexture,
|
||||
UnsupportedUnknownCommand
|
||||
};
|
||||
|
||||
struct XCUIRHICommandClassification {
|
||||
XCUIRHICommandCategory category = XCUIRHICommandCategory::Unknown;
|
||||
XCUIRHICommandSupportReason supportReason =
|
||||
XCUIRHICommandSupportReason::UnsupportedUnknownCommand;
|
||||
|
||||
constexpr bool IsSupported() const {
|
||||
return supportReason == XCUIRHICommandSupportReason::Supported;
|
||||
}
|
||||
};
|
||||
|
||||
struct XCUIRHICommandSupportStats {
|
||||
std::size_t drawListCount = 0;
|
||||
std::size_t commandCount = 0;
|
||||
std::size_t filledRectCommandCount = 0;
|
||||
std::size_t rectOutlineCommandCount = 0;
|
||||
std::size_t textCommandCount = 0;
|
||||
std::size_t imageCommandCount = 0;
|
||||
std::size_t clipPushCommandCount = 0;
|
||||
std::size_t clipPopCommandCount = 0;
|
||||
std::size_t supportedCommandCount = 0;
|
||||
std::size_t unsupportedCommandCount = 0;
|
||||
std::size_t unsupportedImageCommandCount = 0;
|
||||
std::size_t unsupportedUnknownCommandCount = 0;
|
||||
|
||||
constexpr bool HasCommands() const {
|
||||
return commandCount != 0u;
|
||||
}
|
||||
|
||||
constexpr bool SupportsAllCommands() const {
|
||||
return unsupportedCommandCount == 0u;
|
||||
}
|
||||
};
|
||||
|
||||
struct XCUIRHICommandDiagnosticOptions {
|
||||
const char* noCommandsMessage = "Overlay runtime produced no commands.";
|
||||
const char* allSupportedMessage = "All commands preflight for native overlay.";
|
||||
const char* unsupportedPrefix = "command(s) will be skipped by native overlay: ";
|
||||
const char* unsupportedImageDescription =
|
||||
"image command(s) missing valid ShaderResourceView textures";
|
||||
const char* unsupportedUnknownDescription = "unknown command type(s)";
|
||||
};
|
||||
|
||||
struct XCUIRHICommandSupportSummary {
|
||||
XCUIRHICommandSupportStats stats = {};
|
||||
std::string diagnostic = {};
|
||||
};
|
||||
|
||||
inline XCUIRHICommandClassification ClassifyXCUIRHICommandSupport(
|
||||
const UI::UIDrawCommand& command) {
|
||||
XCUIRHICommandClassification classification = {};
|
||||
|
||||
switch (command.type) {
|
||||
case UI::UIDrawCommandType::FilledRect:
|
||||
classification.category = XCUIRHICommandCategory::FilledRect;
|
||||
classification.supportReason = XCUIRHICommandSupportReason::Supported;
|
||||
break;
|
||||
case UI::UIDrawCommandType::RectOutline:
|
||||
classification.category = XCUIRHICommandCategory::RectOutline;
|
||||
classification.supportReason = XCUIRHICommandSupportReason::Supported;
|
||||
break;
|
||||
case UI::UIDrawCommandType::Text:
|
||||
classification.category = XCUIRHICommandCategory::Text;
|
||||
classification.supportReason = XCUIRHICommandSupportReason::Supported;
|
||||
break;
|
||||
case UI::UIDrawCommandType::Image:
|
||||
classification.category = XCUIRHICommandCategory::Image;
|
||||
classification.supportReason =
|
||||
(command.texture.IsValid() &&
|
||||
command.texture.kind == UI::UITextureHandleKind::ShaderResourceView)
|
||||
? XCUIRHICommandSupportReason::Supported
|
||||
: XCUIRHICommandSupportReason::UnsupportedImageTexture;
|
||||
break;
|
||||
case UI::UIDrawCommandType::PushClipRect:
|
||||
classification.category = XCUIRHICommandCategory::PushClipRect;
|
||||
classification.supportReason = XCUIRHICommandSupportReason::Supported;
|
||||
break;
|
||||
case UI::UIDrawCommandType::PopClipRect:
|
||||
classification.category = XCUIRHICommandCategory::PopClipRect;
|
||||
classification.supportReason = XCUIRHICommandSupportReason::Supported;
|
||||
break;
|
||||
default:
|
||||
classification.category = XCUIRHICommandCategory::Unknown;
|
||||
classification.supportReason = XCUIRHICommandSupportReason::UnsupportedUnknownCommand;
|
||||
break;
|
||||
}
|
||||
|
||||
return classification;
|
||||
}
|
||||
|
||||
inline void AccumulateXCUIRHICommandSupport(
|
||||
const XCUIRHICommandClassification& classification,
|
||||
XCUIRHICommandSupportStats& stats) {
|
||||
switch (classification.category) {
|
||||
case XCUIRHICommandCategory::FilledRect:
|
||||
++stats.filledRectCommandCount;
|
||||
break;
|
||||
case XCUIRHICommandCategory::RectOutline:
|
||||
++stats.rectOutlineCommandCount;
|
||||
break;
|
||||
case XCUIRHICommandCategory::Text:
|
||||
++stats.textCommandCount;
|
||||
break;
|
||||
case XCUIRHICommandCategory::Image:
|
||||
++stats.imageCommandCount;
|
||||
break;
|
||||
case XCUIRHICommandCategory::PushClipRect:
|
||||
++stats.clipPushCommandCount;
|
||||
break;
|
||||
case XCUIRHICommandCategory::PopClipRect:
|
||||
++stats.clipPopCommandCount;
|
||||
break;
|
||||
case XCUIRHICommandCategory::Unknown:
|
||||
break;
|
||||
}
|
||||
|
||||
++stats.commandCount;
|
||||
|
||||
if (classification.IsSupported()) {
|
||||
++stats.supportedCommandCount;
|
||||
return;
|
||||
}
|
||||
|
||||
++stats.unsupportedCommandCount;
|
||||
|
||||
switch (classification.supportReason) {
|
||||
case XCUIRHICommandSupportReason::Supported:
|
||||
break;
|
||||
case XCUIRHICommandSupportReason::UnsupportedImageTexture:
|
||||
++stats.unsupportedImageCommandCount;
|
||||
break;
|
||||
case XCUIRHICommandSupportReason::UnsupportedUnknownCommand:
|
||||
++stats.unsupportedUnknownCommandCount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline void AccumulateXCUIRHICommandSupport(
|
||||
const UI::UIDrawCommand& command,
|
||||
XCUIRHICommandSupportStats& stats) {
|
||||
AccumulateXCUIRHICommandSupport(ClassifyXCUIRHICommandSupport(command), stats);
|
||||
}
|
||||
|
||||
inline void AccumulateXCUIRHICommandSupport(
|
||||
const UI::UIDrawList& drawList,
|
||||
XCUIRHICommandSupportStats& stats) {
|
||||
++stats.drawListCount;
|
||||
for (const UI::UIDrawCommand& command : drawList.GetCommands()) {
|
||||
AccumulateXCUIRHICommandSupport(command, stats);
|
||||
}
|
||||
}
|
||||
|
||||
inline XCUIRHICommandSupportStats AnalyzeXCUIRHICommandSupport(
|
||||
const UI::UIDrawList& drawList) {
|
||||
XCUIRHICommandSupportStats stats = {};
|
||||
AccumulateXCUIRHICommandSupport(drawList, stats);
|
||||
return stats;
|
||||
}
|
||||
|
||||
inline XCUIRHICommandSupportStats AnalyzeXCUIRHICommandSupport(
|
||||
const UI::UIDrawData& drawData) {
|
||||
XCUIRHICommandSupportStats stats = {};
|
||||
for (const UI::UIDrawList& drawList : drawData.GetDrawLists()) {
|
||||
AccumulateXCUIRHICommandSupport(drawList, stats);
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
inline void AppendXCUIRHICommandDiagnosticIssue(
|
||||
std::ostringstream& builder,
|
||||
bool& hasPreviousIssue,
|
||||
std::size_t count,
|
||||
const char* description) {
|
||||
if (count == 0u) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasPreviousIssue) {
|
||||
builder << ", ";
|
||||
}
|
||||
|
||||
builder << count << ' ' << description;
|
||||
hasPreviousIssue = true;
|
||||
}
|
||||
|
||||
inline std::string BuildXCUIRHICommandSupportDiagnostic(
|
||||
const XCUIRHICommandSupportStats& stats,
|
||||
const XCUIRHICommandDiagnosticOptions& options = {}) {
|
||||
if (!stats.HasCommands()) {
|
||||
return std::string(options.noCommandsMessage);
|
||||
}
|
||||
|
||||
if (stats.SupportsAllCommands()) {
|
||||
return std::string(options.allSupportedMessage);
|
||||
}
|
||||
|
||||
std::ostringstream builder = {};
|
||||
builder << stats.unsupportedCommandCount << ' ' << options.unsupportedPrefix;
|
||||
|
||||
bool hasPreviousIssue = false;
|
||||
AppendXCUIRHICommandDiagnosticIssue(
|
||||
builder,
|
||||
hasPreviousIssue,
|
||||
stats.unsupportedImageCommandCount,
|
||||
options.unsupportedImageDescription);
|
||||
AppendXCUIRHICommandDiagnosticIssue(
|
||||
builder,
|
||||
hasPreviousIssue,
|
||||
stats.unsupportedUnknownCommandCount,
|
||||
options.unsupportedUnknownDescription);
|
||||
|
||||
if (!hasPreviousIssue) {
|
||||
builder << "unsupported command(s)";
|
||||
}
|
||||
|
||||
return builder.str();
|
||||
}
|
||||
|
||||
inline XCUIRHICommandSupportSummary SummarizeXCUIRHICommandSupport(
|
||||
const UI::UIDrawData& drawData,
|
||||
const XCUIRHICommandDiagnosticOptions& options = {}) {
|
||||
XCUIRHICommandSupportSummary summary = {};
|
||||
summary.stats = AnalyzeXCUIRHICommandSupport(drawData);
|
||||
summary.diagnostic = BuildXCUIRHICommandSupportDiagnostic(summary.stats, options);
|
||||
return summary;
|
||||
}
|
||||
|
||||
inline XCUIRHICommandSupportSummary SummarizeXCUIRHICommandSupport(
|
||||
const UI::UIDrawList& drawList,
|
||||
const XCUIRHICommandDiagnosticOptions& options = {}) {
|
||||
XCUIRHICommandSupportSummary summary = {};
|
||||
summary.stats = AnalyzeXCUIRHICommandSupport(drawList);
|
||||
summary.diagnostic = BuildXCUIRHICommandSupportDiagnostic(summary.stats, options);
|
||||
return summary;
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,582 +0,0 @@
|
||||
#include "XCUIBackend/XCUIRHIRenderBackend.h"
|
||||
|
||||
#include "XCUIBackend/XCUIRHICommandCompiler.h"
|
||||
|
||||
#include <XCEngine/Core/Math/Vector4.h>
|
||||
#include <XCEngine/RHI/RHICommandList.h>
|
||||
#include <XCEngine/RHI/RHIDevice.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <optional>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
namespace {
|
||||
|
||||
using CommandCompiler = XCUIRHICommandCompiler;
|
||||
|
||||
struct OverlayConstants { Math::Vector4 viewportSize = Math::Vector4::Zero(); };
|
||||
|
||||
class TextGlyphProvider final : public CommandCompiler::TextGlyphProvider {
|
||||
public:
|
||||
TextGlyphProvider(
|
||||
const IXCUITextAtlasProvider& atlasProvider,
|
||||
IXCUITextAtlasProvider::FontHandle fontHandle,
|
||||
const UI::UITextureHandle& textureHandle)
|
||||
: m_atlasProvider(atlasProvider), m_fontHandle(fontHandle), m_textureHandle(textureHandle) {}
|
||||
|
||||
bool BeginText(float requestedFontSize, CommandCompiler::TextRunContext& outContext) const override {
|
||||
outContext = {};
|
||||
if (!m_fontHandle.IsValid() || !m_textureHandle.IsValid()) return false;
|
||||
IXCUITextAtlasProvider::FontInfo fontInfo = {};
|
||||
if (!m_atlasProvider.GetFontInfo(m_fontHandle, fontInfo)) return false;
|
||||
const float resolvedFontSize = requestedFontSize > 0.0f
|
||||
? requestedFontSize
|
||||
: (fontInfo.nominalSize > 0.0f ? fontInfo.nominalSize : 16.0f);
|
||||
IXCUITextAtlasProvider::BakedFontInfo baked = {};
|
||||
if (!m_atlasProvider.GetBakedFontInfo(m_fontHandle, resolvedFontSize, baked)) return false;
|
||||
outContext.requestedFontSize = requestedFontSize;
|
||||
outContext.resolvedFontSize = resolvedFontSize;
|
||||
outContext.lineHeight = baked.lineHeight;
|
||||
outContext.texture = m_textureHandle;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResolveGlyph(
|
||||
const CommandCompiler::TextRunContext& context,
|
||||
std::uint32_t codepoint,
|
||||
CommandCompiler::TextGlyph& outGlyph) const override {
|
||||
outGlyph = {};
|
||||
IXCUITextAtlasProvider::GlyphInfo glyph = {};
|
||||
if (!m_atlasProvider.FindGlyph(m_fontHandle, context.resolvedFontSize, codepoint, glyph)) return false;
|
||||
outGlyph.x0 = glyph.x0; outGlyph.y0 = glyph.y0; outGlyph.x1 = glyph.x1; outGlyph.y1 = glyph.y1;
|
||||
outGlyph.u0 = glyph.u0; outGlyph.v0 = glyph.v0; outGlyph.u1 = glyph.u1; outGlyph.v1 = glyph.v1;
|
||||
outGlyph.advanceX = glyph.advanceX; outGlyph.visible = glyph.visible;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
const IXCUITextAtlasProvider& m_atlasProvider;
|
||||
IXCUITextAtlasProvider::FontHandle m_fontHandle = {};
|
||||
UI::UITextureHandle m_textureHandle = {};
|
||||
};
|
||||
|
||||
constexpr char kColorShader[] =
|
||||
"cbuffer OverlayConstants : register(b0) { float4 gViewportSize; };"
|
||||
"struct VSInput { float2 position : POSITION; float4 color : COLOR0; };"
|
||||
"struct VSOutput { float4 position : SV_POSITION; float4 color : COLOR0; };"
|
||||
"VSOutput MainVS(VSInput input) { VSOutput output;"
|
||||
" float2 viewport = max(gViewportSize.xy, float2(1.0, 1.0));"
|
||||
" float2 ndc = float2(input.position.x / viewport.x * 2.0 - 1.0, 1.0 - input.position.y / viewport.y * 2.0);"
|
||||
" output.position = float4(ndc, 0.0, 1.0); output.color = input.color; return output; }"
|
||||
"float4 MainPS(VSOutput input) : SV_TARGET0 { return input.color; }";
|
||||
|
||||
constexpr char kTexturedShader[] =
|
||||
"cbuffer OverlayConstants : register(b0) { float4 gViewportSize; };"
|
||||
"Texture2D gOverlayTexture : register(t0); SamplerState gOverlaySampler : register(s0);"
|
||||
"struct VSInput { float2 position : POSITION; float2 uv : TEXCOORD0; float4 color : COLOR0; };"
|
||||
"struct VSOutput { float4 position : SV_POSITION; float2 uv : TEXCOORD0; float4 color : COLOR0; };"
|
||||
"VSOutput MainVS(VSInput input) { VSOutput output;"
|
||||
" float2 viewport = max(gViewportSize.xy, float2(1.0, 1.0));"
|
||||
" float2 ndc = float2(input.position.x / viewport.x * 2.0 - 1.0, 1.0 - input.position.y / viewport.y * 2.0);"
|
||||
" output.position = float4(ndc, 0.0, 1.0); output.uv = input.uv; output.color = input.color; return output; }"
|
||||
"float4 MainPS(VSOutput input) : SV_TARGET0 {"
|
||||
" const float4 sampled = gOverlayTexture.Sample(gOverlaySampler, input.uv);"
|
||||
" float4 color = sampled * input.color; if (color.a <= 0.001f) { discard; } return color; }";
|
||||
|
||||
RHI::Rect MakeSurfaceScissorRect(const ::XCEngine::Rendering::RenderSurface& surface) {
|
||||
return RHI::Rect{
|
||||
0,
|
||||
0,
|
||||
static_cast<int32_t>(surface.GetWidth()),
|
||||
static_cast<int32_t>(surface.GetHeight())
|
||||
};
|
||||
}
|
||||
|
||||
RHI::Rect ClampBatchClipRect(
|
||||
const ::XCEngine::Rendering::RenderSurface& surface,
|
||||
const UI::UIRect& clipRect) {
|
||||
const float surfaceWidth = static_cast<float>(surface.GetWidth());
|
||||
const float surfaceHeight = static_cast<float>(surface.GetHeight());
|
||||
const float minX = (std::max)(0.0f, (std::min)(clipRect.x, surfaceWidth));
|
||||
const float minY = (std::max)(0.0f, (std::min)(clipRect.y, surfaceHeight));
|
||||
const float maxX = (std::max)(minX, (std::min)(clipRect.x + clipRect.width, surfaceWidth));
|
||||
const float maxY = (std::max)(minY, (std::min)(clipRect.y + clipRect.height, surfaceHeight));
|
||||
return RHI::Rect{
|
||||
static_cast<int32_t>(std::floor(minX)),
|
||||
static_cast<int32_t>(std::floor(minY)),
|
||||
static_cast<int32_t>(std::ceil(maxX)),
|
||||
static_cast<int32_t>(std::ceil(maxY))
|
||||
};
|
||||
}
|
||||
|
||||
bool RectEquals(const RHI::Rect& lhs, const RHI::Rect& rhs) {
|
||||
return lhs.left == rhs.left &&
|
||||
lhs.top == rhs.top &&
|
||||
lhs.right == rhs.right &&
|
||||
lhs.bottom == rhs.bottom;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void XCUIRHIRenderBackend::SetTextAtlasProvider(const IXCUITextAtlasProvider* provider) {
|
||||
if (m_textAtlasProvider == provider) return;
|
||||
m_textAtlasProvider = provider;
|
||||
ResetFontAtlasResources();
|
||||
}
|
||||
|
||||
void XCUIRHIRenderBackend::Shutdown() { DestroyResources(); }
|
||||
void XCUIRHIRenderBackend::ResetStats() { m_lastOverlayStats = {}; }
|
||||
|
||||
bool XCUIRHIRenderBackend::Render(
|
||||
const ::XCEngine::Rendering::RenderContext& renderContext,
|
||||
const ::XCEngine::Rendering::RenderSurface& surface,
|
||||
const ::XCEngine::UI::UIDrawData& drawData) {
|
||||
ResetStats();
|
||||
if (!renderContext.IsValid() || renderContext.backendType != RHI::RHIType::D3D12) return false;
|
||||
const auto& colorAttachments = surface.GetColorAttachments();
|
||||
if (colorAttachments.empty() || colorAttachments[0] == nullptr || renderContext.commandList == nullptr) return false;
|
||||
if (!EnsureInitialized(renderContext)) return false;
|
||||
|
||||
bool fontAtlasReady = false;
|
||||
const IXCUITextAtlasProvider* atlasProvider = ResolveActiveTextAtlasProvider(fontAtlasReady);
|
||||
return RenderCompiledDrawData(
|
||||
*renderContext.commandList,
|
||||
colorAttachments[0],
|
||||
surface,
|
||||
drawData,
|
||||
atlasProvider,
|
||||
fontAtlasReady);
|
||||
}
|
||||
|
||||
const IXCUITextAtlasProvider* XCUIRHIRenderBackend::ResolveActiveTextAtlasProvider(
|
||||
bool& outFontAtlasReady) {
|
||||
outFontAtlasReady = false;
|
||||
if (m_textAtlasProvider == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
outFontAtlasReady = EnsureFontAtlasResources(*m_textAtlasProvider);
|
||||
return m_textAtlasProvider;
|
||||
}
|
||||
|
||||
bool XCUIRHIRenderBackend::EnsureInitialized(const ::XCEngine::Rendering::RenderContext& renderContext) {
|
||||
if (m_overlayPipelineState != nullptr &&
|
||||
m_overlayPipelineLayout != nullptr &&
|
||||
m_overlayConstantPool != nullptr &&
|
||||
m_overlayConstantSet != nullptr &&
|
||||
m_texturedOverlayPipelineState != nullptr &&
|
||||
m_texturedOverlayPipelineLayout != nullptr &&
|
||||
m_overlayTexturePool != nullptr &&
|
||||
m_overlaySamplerPool != nullptr &&
|
||||
m_overlaySamplerSet != nullptr &&
|
||||
m_overlaySampler != nullptr &&
|
||||
m_device == renderContext.device &&
|
||||
m_backendType == renderContext.backendType) {
|
||||
return true;
|
||||
}
|
||||
DestroyResources();
|
||||
return CreateResources(renderContext);
|
||||
}
|
||||
|
||||
bool XCUIRHIRenderBackend::CreateResources(const ::XCEngine::Rendering::RenderContext& renderContext) {
|
||||
if (!renderContext.IsValid() || renderContext.backendType != RHI::RHIType::D3D12) return false;
|
||||
m_device = renderContext.device;
|
||||
m_backendType = renderContext.backendType;
|
||||
return CreateOverlayResources() && CreateTexturedOverlayResources();
|
||||
}
|
||||
|
||||
bool XCUIRHIRenderBackend::CreateOverlayResources() {
|
||||
RHI::DescriptorSetLayoutBinding binding = { 0, static_cast<uint32_t>(RHI::DescriptorType::CBV), 1 };
|
||||
RHI::DescriptorSetLayoutDesc layout = { &binding, 1 };
|
||||
RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {};
|
||||
pipelineLayoutDesc.setLayouts = &layout;
|
||||
pipelineLayoutDesc.setLayoutCount = 1;
|
||||
m_overlayPipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc);
|
||||
if (m_overlayPipelineLayout == nullptr) { DestroyResources(); return false; }
|
||||
|
||||
RHI::DescriptorPoolDesc poolDesc = {};
|
||||
poolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV;
|
||||
poolDesc.descriptorCount = 1;
|
||||
poolDesc.shaderVisible = false;
|
||||
m_overlayConstantPool = m_device->CreateDescriptorPool(poolDesc);
|
||||
if (m_overlayConstantPool == nullptr) { DestroyResources(); return false; }
|
||||
m_overlayConstantSet = m_overlayConstantPool->AllocateSet(layout);
|
||||
if (m_overlayConstantSet == nullptr) { DestroyResources(); return false; }
|
||||
|
||||
RHI::GraphicsPipelineDesc desc = {};
|
||||
desc.pipelineLayout = m_overlayPipelineLayout;
|
||||
desc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
||||
desc.renderTargetCount = 1;
|
||||
desc.renderTargetFormats[0] = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
||||
desc.depthStencilFormat = static_cast<uint32_t>(RHI::Format::Unknown);
|
||||
desc.sampleCount = 1;
|
||||
desc.inputLayout.elements = {
|
||||
{ "POSITION", 0, static_cast<uint32_t>(RHI::Format::R32G32_Float), 0, 0, 0, 0 },
|
||||
{ "COLOR", 0, static_cast<uint32_t>(RHI::Format::R32G32B32A32_Float), 0, 8, 0, 0 }
|
||||
};
|
||||
desc.rasterizerState.fillMode = static_cast<uint32_t>(RHI::FillMode::Solid);
|
||||
desc.rasterizerState.cullMode = static_cast<uint32_t>(RHI::CullMode::None);
|
||||
desc.rasterizerState.frontFace = static_cast<uint32_t>(RHI::FrontFace::CounterClockwise);
|
||||
desc.rasterizerState.depthClipEnable = true;
|
||||
desc.blendState.blendEnable = true;
|
||||
desc.blendState.srcBlend = static_cast<uint32_t>(RHI::BlendFactor::SrcAlpha);
|
||||
desc.blendState.dstBlend = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
|
||||
desc.blendState.srcBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::One);
|
||||
desc.blendState.dstBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
|
||||
desc.blendState.blendOp = static_cast<uint32_t>(RHI::BlendOp::Add);
|
||||
desc.blendState.blendOpAlpha = static_cast<uint32_t>(RHI::BlendOp::Add);
|
||||
desc.blendState.colorWriteMask = 0xF;
|
||||
desc.depthStencilState.depthTestEnable = false;
|
||||
desc.depthStencilState.depthWriteEnable = false;
|
||||
desc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
|
||||
desc.vertexShader.source.assign(kColorShader, kColorShader + sizeof(kColorShader) - 1);
|
||||
desc.vertexShader.sourceLanguage = RHI::ShaderLanguage::HLSL;
|
||||
desc.vertexShader.entryPoint = L"MainVS";
|
||||
desc.vertexShader.profile = L"vs_5_0";
|
||||
desc.fragmentShader.source.assign(kColorShader, kColorShader + sizeof(kColorShader) - 1);
|
||||
desc.fragmentShader.sourceLanguage = RHI::ShaderLanguage::HLSL;
|
||||
desc.fragmentShader.entryPoint = L"MainPS";
|
||||
desc.fragmentShader.profile = L"ps_5_0";
|
||||
m_overlayPipelineState = m_device->CreatePipelineState(desc);
|
||||
if (m_overlayPipelineState == nullptr || !m_overlayPipelineState->IsValid()) { DestroyResources(); return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XCUIRHIRenderBackend::CreateTexturedOverlayResources() {
|
||||
RHI::DescriptorSetLayoutBinding constantBinding = { 0, static_cast<uint32_t>(RHI::DescriptorType::CBV), 1 };
|
||||
RHI::DescriptorSetLayoutBinding textureBinding = { 0, static_cast<uint32_t>(RHI::DescriptorType::SRV), 1 };
|
||||
RHI::DescriptorSetLayoutBinding samplerBinding = { 0, static_cast<uint32_t>(RHI::DescriptorType::Sampler), 1 };
|
||||
RHI::DescriptorSetLayoutDesc setLayouts[3] = { { &constantBinding, 1 }, { &textureBinding, 1 }, { &samplerBinding, 1 } };
|
||||
RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {};
|
||||
pipelineLayoutDesc.setLayouts = setLayouts;
|
||||
pipelineLayoutDesc.setLayoutCount = 3;
|
||||
m_texturedOverlayPipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc);
|
||||
if (m_texturedOverlayPipelineLayout == nullptr) { DestroyResources(); return false; }
|
||||
|
||||
RHI::DescriptorPoolDesc texturePoolDesc = {};
|
||||
texturePoolDesc.type = RHI::DescriptorHeapType::CBV_SRV_UAV;
|
||||
texturePoolDesc.descriptorCount = 64;
|
||||
texturePoolDesc.shaderVisible = true;
|
||||
m_overlayTexturePool = m_device->CreateDescriptorPool(texturePoolDesc);
|
||||
if (m_overlayTexturePool == nullptr) { DestroyResources(); return false; }
|
||||
|
||||
RHI::DescriptorPoolDesc samplerPoolDesc = {};
|
||||
samplerPoolDesc.type = RHI::DescriptorHeapType::Sampler;
|
||||
samplerPoolDesc.descriptorCount = 1;
|
||||
samplerPoolDesc.shaderVisible = true;
|
||||
m_overlaySamplerPool = m_device->CreateDescriptorPool(samplerPoolDesc);
|
||||
if (m_overlaySamplerPool == nullptr) { DestroyResources(); return false; }
|
||||
m_overlaySamplerSet = m_overlaySamplerPool->AllocateSet(setLayouts[2]);
|
||||
if (m_overlaySamplerSet == nullptr) { DestroyResources(); return false; }
|
||||
|
||||
RHI::SamplerDesc samplerDesc = {};
|
||||
samplerDesc.filter = static_cast<uint32_t>(RHI::FilterMode::Linear);
|
||||
samplerDesc.addressU = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
|
||||
samplerDesc.addressV = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
|
||||
samplerDesc.addressW = static_cast<uint32_t>(RHI::TextureAddressMode::Clamp);
|
||||
samplerDesc.comparisonFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
|
||||
samplerDesc.maxLod = 1000.0f;
|
||||
m_overlaySampler = m_device->CreateSampler(samplerDesc);
|
||||
if (m_overlaySampler == nullptr) { DestroyResources(); return false; }
|
||||
m_overlaySamplerSet->UpdateSampler(0, m_overlaySampler);
|
||||
|
||||
RHI::GraphicsPipelineDesc desc = {};
|
||||
desc.pipelineLayout = m_texturedOverlayPipelineLayout;
|
||||
desc.topologyType = static_cast<uint32_t>(RHI::PrimitiveTopologyType::Triangle);
|
||||
desc.renderTargetCount = 1;
|
||||
desc.renderTargetFormats[0] = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
||||
desc.depthStencilFormat = static_cast<uint32_t>(RHI::Format::Unknown);
|
||||
desc.sampleCount = 1;
|
||||
desc.inputLayout.elements = {
|
||||
{ "POSITION", 0, static_cast<uint32_t>(RHI::Format::R32G32_Float), 0, 0, 0, 0 },
|
||||
{ "TEXCOORD", 0, static_cast<uint32_t>(RHI::Format::R32G32_Float), 0, 8, 0, 0 },
|
||||
{ "COLOR", 0, static_cast<uint32_t>(RHI::Format::R32G32B32A32_Float), 0, 16, 0, 0 }
|
||||
};
|
||||
desc.rasterizerState.fillMode = static_cast<uint32_t>(RHI::FillMode::Solid);
|
||||
desc.rasterizerState.cullMode = static_cast<uint32_t>(RHI::CullMode::None);
|
||||
desc.rasterizerState.frontFace = static_cast<uint32_t>(RHI::FrontFace::CounterClockwise);
|
||||
desc.rasterizerState.depthClipEnable = true;
|
||||
desc.blendState.blendEnable = true;
|
||||
desc.blendState.srcBlend = static_cast<uint32_t>(RHI::BlendFactor::SrcAlpha);
|
||||
desc.blendState.dstBlend = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
|
||||
desc.blendState.srcBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::One);
|
||||
desc.blendState.dstBlendAlpha = static_cast<uint32_t>(RHI::BlendFactor::InvSrcAlpha);
|
||||
desc.blendState.blendOp = static_cast<uint32_t>(RHI::BlendOp::Add);
|
||||
desc.blendState.blendOpAlpha = static_cast<uint32_t>(RHI::BlendOp::Add);
|
||||
desc.blendState.colorWriteMask = 0xF;
|
||||
desc.depthStencilState.depthTestEnable = false;
|
||||
desc.depthStencilState.depthWriteEnable = false;
|
||||
desc.depthStencilState.depthFunc = static_cast<uint32_t>(RHI::ComparisonFunc::Always);
|
||||
desc.vertexShader.source.assign(kTexturedShader, kTexturedShader + sizeof(kTexturedShader) - 1);
|
||||
desc.vertexShader.sourceLanguage = RHI::ShaderLanguage::HLSL;
|
||||
desc.vertexShader.entryPoint = L"MainVS";
|
||||
desc.vertexShader.profile = L"vs_5_0";
|
||||
desc.fragmentShader.source.assign(kTexturedShader, kTexturedShader + sizeof(kTexturedShader) - 1);
|
||||
desc.fragmentShader.sourceLanguage = RHI::ShaderLanguage::HLSL;
|
||||
desc.fragmentShader.entryPoint = L"MainPS";
|
||||
desc.fragmentShader.profile = L"ps_5_0";
|
||||
m_texturedOverlayPipelineState = m_device->CreatePipelineState(desc);
|
||||
if (m_texturedOverlayPipelineState == nullptr || !m_texturedOverlayPipelineState->IsValid()) { DestroyResources(); return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XCUIRHIRenderBackend::EnsureOverlayVertexBufferCapacity(std::size_t requiredVertexCount) {
|
||||
const std::uint64_t requiredBytes = static_cast<std::uint64_t>(requiredVertexCount * sizeof(CommandCompiler::ColorVertex));
|
||||
if (requiredBytes == 0u) return true;
|
||||
if (m_overlayVertexBuffer != nullptr && m_overlayVertexBufferCapacity >= requiredBytes) return true;
|
||||
if (m_overlayVertexBufferView != nullptr) { m_overlayVertexBufferView->Shutdown(); delete m_overlayVertexBufferView; m_overlayVertexBufferView = nullptr; }
|
||||
if (m_overlayVertexBuffer != nullptr) { m_overlayVertexBuffer->Shutdown(); delete m_overlayVertexBuffer; m_overlayVertexBuffer = nullptr; }
|
||||
m_overlayVertexBufferCapacity = (std::max<std::uint64_t>)(requiredBytes, 4096u);
|
||||
RHI::BufferDesc bufferDesc = {};
|
||||
bufferDesc.size = m_overlayVertexBufferCapacity;
|
||||
bufferDesc.stride = static_cast<std::uint32_t>(sizeof(CommandCompiler::ColorVertex));
|
||||
bufferDesc.bufferType = static_cast<uint32_t>(RHI::BufferType::Vertex);
|
||||
m_overlayVertexBuffer = m_device->CreateBuffer(bufferDesc);
|
||||
if (m_overlayVertexBuffer == nullptr) { m_overlayVertexBufferCapacity = 0u; return false; }
|
||||
m_overlayVertexBuffer->SetStride(bufferDesc.stride);
|
||||
m_overlayVertexBuffer->SetBufferType(RHI::BufferType::Vertex);
|
||||
RHI::ResourceViewDesc viewDesc = {};
|
||||
viewDesc.dimension = RHI::ResourceViewDimension::Buffer;
|
||||
viewDesc.structureByteStride = bufferDesc.stride;
|
||||
m_overlayVertexBufferView = m_device->CreateVertexBufferView(m_overlayVertexBuffer, viewDesc);
|
||||
return m_overlayVertexBufferView != nullptr;
|
||||
}
|
||||
|
||||
bool XCUIRHIRenderBackend::EnsureTexturedOverlayVertexBufferCapacity(std::size_t requiredVertexCount) {
|
||||
const std::uint64_t requiredBytes = static_cast<std::uint64_t>(requiredVertexCount * sizeof(CommandCompiler::TexturedVertex));
|
||||
if (requiredBytes == 0u) return true;
|
||||
if (m_texturedOverlayVertexBuffer != nullptr && m_texturedOverlayVertexBufferCapacity >= requiredBytes) return true;
|
||||
if (m_texturedOverlayVertexBufferView != nullptr) { m_texturedOverlayVertexBufferView->Shutdown(); delete m_texturedOverlayVertexBufferView; m_texturedOverlayVertexBufferView = nullptr; }
|
||||
if (m_texturedOverlayVertexBuffer != nullptr) { m_texturedOverlayVertexBuffer->Shutdown(); delete m_texturedOverlayVertexBuffer; m_texturedOverlayVertexBuffer = nullptr; }
|
||||
m_texturedOverlayVertexBufferCapacity = (std::max<std::uint64_t>)(requiredBytes, 4096u);
|
||||
RHI::BufferDesc bufferDesc = {};
|
||||
bufferDesc.size = m_texturedOverlayVertexBufferCapacity;
|
||||
bufferDesc.stride = static_cast<std::uint32_t>(sizeof(CommandCompiler::TexturedVertex));
|
||||
bufferDesc.bufferType = static_cast<uint32_t>(RHI::BufferType::Vertex);
|
||||
m_texturedOverlayVertexBuffer = m_device->CreateBuffer(bufferDesc);
|
||||
if (m_texturedOverlayVertexBuffer == nullptr) { m_texturedOverlayVertexBufferCapacity = 0u; return false; }
|
||||
m_texturedOverlayVertexBuffer->SetStride(bufferDesc.stride);
|
||||
m_texturedOverlayVertexBuffer->SetBufferType(RHI::BufferType::Vertex);
|
||||
RHI::ResourceViewDesc viewDesc = {};
|
||||
viewDesc.dimension = RHI::ResourceViewDimension::Buffer;
|
||||
viewDesc.structureByteStride = bufferDesc.stride;
|
||||
m_texturedOverlayVertexBufferView = m_device->CreateVertexBufferView(m_texturedOverlayVertexBuffer, viewDesc);
|
||||
return m_texturedOverlayVertexBufferView != nullptr;
|
||||
}
|
||||
|
||||
bool XCUIRHIRenderBackend::EnsureFontAtlasResources(const IXCUITextAtlasProvider& atlasProvider) {
|
||||
if (m_overlayTexturePool == nullptr) return false;
|
||||
IXCUITextAtlasProvider::AtlasTextureView atlasView = {};
|
||||
if (!atlasProvider.GetAtlasTextureView(IXCUITextAtlasProvider::PixelFormat::RGBA32, atlasView) ||
|
||||
!atlasView.IsValid() || atlasView.bytesPerPixel != 4) {
|
||||
return false;
|
||||
}
|
||||
const TextFontHandle font = atlasProvider.GetDefaultFont();
|
||||
if (!font.IsValid()) return false;
|
||||
if (m_overlayFontTexture != nullptr && m_overlayFontTextureView != nullptr && m_overlayFontTextureSet != nullptr &&
|
||||
m_overlayFont.value == font.value &&
|
||||
m_fontAtlasStorageKey == atlasView.atlasStorageKey &&
|
||||
m_fontAtlasPixelDataKey == atlasView.pixelDataKey) {
|
||||
return true;
|
||||
}
|
||||
ResetFontAtlasResources();
|
||||
RHI::TextureDesc textureDesc = {};
|
||||
textureDesc.width = static_cast<std::uint32_t>(atlasView.width);
|
||||
textureDesc.height = static_cast<std::uint32_t>(atlasView.height);
|
||||
textureDesc.depth = 1; textureDesc.mipLevels = 1; textureDesc.arraySize = 1;
|
||||
textureDesc.format = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
||||
textureDesc.textureType = static_cast<uint32_t>(RHI::TextureType::Texture2D);
|
||||
textureDesc.sampleCount = 1;
|
||||
m_overlayFontTexture = m_device->CreateTexture(
|
||||
textureDesc,
|
||||
atlasView.pixels,
|
||||
static_cast<std::size_t>(atlasView.width) * static_cast<std::size_t>(atlasView.height) * static_cast<std::size_t>(atlasView.bytesPerPixel),
|
||||
static_cast<std::uint32_t>(atlasView.stride));
|
||||
if (m_overlayFontTexture == nullptr) { ResetFontAtlasResources(); return false; }
|
||||
RHI::ResourceViewDesc viewDesc = {};
|
||||
viewDesc.format = static_cast<uint32_t>(RHI::Format::R8G8B8A8_UNorm);
|
||||
viewDesc.dimension = RHI::ResourceViewDimension::Texture2D;
|
||||
viewDesc.mipLevel = 0;
|
||||
m_overlayFontTextureView = m_device->CreateShaderResourceView(m_overlayFontTexture, viewDesc);
|
||||
if (m_overlayFontTextureView == nullptr) { ResetFontAtlasResources(); return false; }
|
||||
RHI::DescriptorSetLayoutBinding textureBinding = { 0, static_cast<uint32_t>(RHI::DescriptorType::SRV), 1 };
|
||||
RHI::DescriptorSetLayoutDesc textureLayout = { &textureBinding, 1 };
|
||||
m_overlayFontTextureSet = m_overlayTexturePool->AllocateSet(textureLayout);
|
||||
if (m_overlayFontTextureSet == nullptr) { ResetFontAtlasResources(); return false; }
|
||||
m_overlayFontTextureSet->Update(0, m_overlayFontTextureView);
|
||||
m_overlayFont = font;
|
||||
m_overlayFontTextureHandle.nativeHandle = reinterpret_cast<std::uintptr_t>(m_overlayFontTextureView);
|
||||
m_overlayFontTextureHandle.width = static_cast<std::uint32_t>(atlasView.width);
|
||||
m_overlayFontTextureHandle.height = static_cast<std::uint32_t>(atlasView.height);
|
||||
m_overlayFontTextureHandle.kind = UI::UITextureHandleKind::ShaderResourceView;
|
||||
m_fontAtlasStorageKey = atlasView.atlasStorageKey;
|
||||
m_fontAtlasPixelDataKey = atlasView.pixelDataKey;
|
||||
return true;
|
||||
}
|
||||
|
||||
::XCEngine::RHI::RHIDescriptorSet* XCUIRHIRenderBackend::ResolveTextureSet(
|
||||
const ::XCEngine::UI::UITextureHandle& texture,
|
||||
bool* outCacheHit) {
|
||||
if (outCacheHit != nullptr) {
|
||||
*outCacheHit = false;
|
||||
}
|
||||
if (!texture.IsValid() || texture.kind != UI::UITextureHandleKind::ShaderResourceView || m_overlayTexturePool == nullptr) return nullptr;
|
||||
if (m_overlayFontTextureSet != nullptr && m_overlayFontTextureHandle.IsValid() && texture.nativeHandle == m_overlayFontTextureHandle.nativeHandle) {
|
||||
if (outCacheHit != nullptr) {
|
||||
*outCacheHit = true;
|
||||
}
|
||||
return m_overlayFontTextureSet;
|
||||
}
|
||||
for (const ExternalTextureBinding& binding : m_externalTextureBindings) {
|
||||
if (binding.handleKey == texture.nativeHandle && binding.textureSet != nullptr && binding.shaderView != nullptr) {
|
||||
if (outCacheHit != nullptr) {
|
||||
*outCacheHit = true;
|
||||
}
|
||||
return binding.textureSet;
|
||||
}
|
||||
}
|
||||
RHI::RHIResourceView* shaderView = reinterpret_cast<RHI::RHIResourceView*>(texture.nativeHandle);
|
||||
if (shaderView == nullptr || !shaderView->IsValid() || shaderView->GetViewType() != RHI::ResourceViewType::ShaderResource) return nullptr;
|
||||
RHI::DescriptorSetLayoutBinding textureBinding = { 0, static_cast<uint32_t>(RHI::DescriptorType::SRV), 1 };
|
||||
RHI::DescriptorSetLayoutDesc textureLayout = { &textureBinding, 1 };
|
||||
RHI::RHIDescriptorSet* textureSet = m_overlayTexturePool->AllocateSet(textureLayout);
|
||||
if (textureSet == nullptr) return nullptr;
|
||||
textureSet->Update(0, shaderView);
|
||||
m_externalTextureBindings.push_back({ texture.nativeHandle, shaderView, textureSet });
|
||||
return textureSet;
|
||||
}
|
||||
|
||||
bool XCUIRHIRenderBackend::RenderCompiledDrawData(
|
||||
::XCEngine::RHI::RHICommandList& commandList,
|
||||
::XCEngine::RHI::RHIResourceView* renderTarget,
|
||||
const ::XCEngine::Rendering::RenderSurface& surface,
|
||||
const ::XCEngine::UI::UIDrawData& drawData,
|
||||
const IXCUITextAtlasProvider* atlasProvider,
|
||||
bool fontAtlasReady) {
|
||||
CommandCompiler compiler = {};
|
||||
CommandCompiler::CompileConfig config = {};
|
||||
config.surfaceClipRect = UI::UIRect(0.0f, 0.0f, static_cast<float>(surface.GetWidth()), static_cast<float>(surface.GetHeight()));
|
||||
std::optional<TextGlyphProvider> glyphProvider = std::nullopt;
|
||||
if (fontAtlasReady && atlasProvider != nullptr) {
|
||||
glyphProvider.emplace(*atlasProvider, m_overlayFont, m_overlayFontTextureHandle);
|
||||
config.textGlyphProvider = &(*glyphProvider);
|
||||
}
|
||||
CommandCompiler::CompiledDrawData compiled = {};
|
||||
compiler.Compile(drawData, config, compiled);
|
||||
m_lastOverlayStats.drawListCount = compiled.stats.drawListCount;
|
||||
m_lastOverlayStats.commandCount = compiled.stats.commandCount;
|
||||
m_lastOverlayStats.batchCount = compiled.stats.batchCount;
|
||||
m_lastOverlayStats.skippedCommandCount = compiled.stats.skippedCommandCount;
|
||||
m_lastOverlayStats.vertexCount = compiled.stats.colorVertexCount + compiled.stats.texturedVertexCount;
|
||||
m_lastOverlayStats.triangleCount = compiled.stats.triangleCount;
|
||||
if (compiled.Empty()) return true;
|
||||
if (!compiled.colorVertices.empty() && !EnsureOverlayVertexBufferCapacity(compiled.colorVertices.size())) return false;
|
||||
if (!compiled.texturedVertices.empty() && !EnsureTexturedOverlayVertexBufferCapacity(compiled.texturedVertices.size())) return false;
|
||||
if (!compiled.colorVertices.empty()) m_overlayVertexBuffer->SetData(compiled.colorVertices.data(), compiled.colorVertices.size() * sizeof(CommandCompiler::ColorVertex));
|
||||
if (!compiled.texturedVertices.empty()) m_texturedOverlayVertexBuffer->SetData(compiled.texturedVertices.data(), compiled.texturedVertices.size() * sizeof(CommandCompiler::TexturedVertex));
|
||||
OverlayConstants constants = {};
|
||||
constants.viewportSize = Math::Vector4(static_cast<float>(surface.GetWidth()), static_cast<float>(surface.GetHeight()), 0.0f, 0.0f);
|
||||
m_overlayConstantSet->WriteConstant(0, &constants, sizeof(constants));
|
||||
commandList.SetRenderTargets(1, &renderTarget, nullptr);
|
||||
const RHI::Viewport viewport = { 0.0f, 0.0f, static_cast<float>(surface.GetWidth()), static_cast<float>(surface.GetHeight()), 0.0f, 1.0f };
|
||||
const RHI::Rect fullSurfaceScissorRect = MakeSurfaceScissorRect(surface);
|
||||
commandList.SetViewport(viewport);
|
||||
commandList.SetScissorRect(fullSurfaceScissorRect);
|
||||
commandList.SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList);
|
||||
RHI::Rect currentScissorRect = fullSurfaceScissorRect;
|
||||
for (const CommandCompiler::Batch& batch : compiled.batches) {
|
||||
if (batch.vertexCount == 0u) continue;
|
||||
const RHI::Rect batchScissorRect = ClampBatchClipRect(surface, batch.clipRect);
|
||||
if (!RectEquals(currentScissorRect, batchScissorRect)) {
|
||||
commandList.SetScissorRect(batchScissorRect);
|
||||
currentScissorRect = batchScissorRect;
|
||||
}
|
||||
if (!RectEquals(batchScissorRect, fullSurfaceScissorRect)) {
|
||||
++m_lastOverlayStats.scissoredBatchCount;
|
||||
}
|
||||
if (batch.kind == CommandCompiler::BatchKind::Color) {
|
||||
++m_lastOverlayStats.colorBatchCount;
|
||||
m_lastOverlayStats.renderedCommandCount += batch.commandCount;
|
||||
commandList.SetPipelineState(m_overlayPipelineState);
|
||||
RHI::RHIResourceView* vertexBuffers[] = { m_overlayVertexBufferView };
|
||||
const std::uint64_t offsets[] = { 0u };
|
||||
const std::uint32_t strides[] = { static_cast<std::uint32_t>(sizeof(CommandCompiler::ColorVertex)) };
|
||||
commandList.SetVertexBuffers(0, 1, vertexBuffers, offsets, strides);
|
||||
RHI::RHIDescriptorSet* descriptorSets[] = { m_overlayConstantSet };
|
||||
commandList.SetGraphicsDescriptorSets(0, 1, descriptorSets, m_overlayPipelineLayout);
|
||||
commandList.Draw(batch.vertexCount, 1, batch.firstVertex, 0);
|
||||
continue;
|
||||
}
|
||||
++m_lastOverlayStats.texturedBatchCount;
|
||||
++m_lastOverlayStats.textureResolveCount;
|
||||
bool textureCacheHit = false;
|
||||
RHI::RHIDescriptorSet* textureSet = ResolveTextureSet(batch.texture, &textureCacheHit);
|
||||
if (textureCacheHit) {
|
||||
++m_lastOverlayStats.textureCacheHitCount;
|
||||
}
|
||||
if (textureSet == nullptr || m_overlaySamplerSet == nullptr) {
|
||||
m_lastOverlayStats.skippedCommandCount += batch.commandCount;
|
||||
continue;
|
||||
}
|
||||
m_lastOverlayStats.renderedCommandCount += batch.commandCount;
|
||||
commandList.SetPipelineState(m_texturedOverlayPipelineState);
|
||||
RHI::RHIResourceView* vertexBuffers[] = { m_texturedOverlayVertexBufferView };
|
||||
const std::uint64_t offsets[] = { 0u };
|
||||
const std::uint32_t strides[] = { static_cast<std::uint32_t>(sizeof(CommandCompiler::TexturedVertex)) };
|
||||
commandList.SetVertexBuffers(0, 1, vertexBuffers, offsets, strides);
|
||||
RHI::RHIDescriptorSet* descriptorSets[] = { m_overlayConstantSet, textureSet, m_overlaySamplerSet };
|
||||
commandList.SetGraphicsDescriptorSets(0, 3, descriptorSets, m_texturedOverlayPipelineLayout);
|
||||
commandList.Draw(batch.vertexCount, 1, batch.firstVertex, 0);
|
||||
}
|
||||
if (!RectEquals(currentScissorRect, fullSurfaceScissorRect)) {
|
||||
commandList.SetScissorRect(fullSurfaceScissorRect);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void XCUIRHIRenderBackend::ResetFontAtlasResources() {
|
||||
if (m_overlayFontTextureSet != nullptr) { m_overlayFontTextureSet->Shutdown(); delete m_overlayFontTextureSet; m_overlayFontTextureSet = nullptr; }
|
||||
if (m_overlayFontTextureView != nullptr) { m_overlayFontTextureView->Shutdown(); delete m_overlayFontTextureView; m_overlayFontTextureView = nullptr; }
|
||||
if (m_overlayFontTexture != nullptr) { m_overlayFontTexture->Shutdown(); delete m_overlayFontTexture; m_overlayFontTexture = nullptr; }
|
||||
m_overlayFont = {};
|
||||
m_overlayFontTextureHandle = {};
|
||||
m_fontAtlasStorageKey = 0;
|
||||
m_fontAtlasPixelDataKey = 0;
|
||||
}
|
||||
|
||||
void XCUIRHIRenderBackend::DestroyResources() {
|
||||
for (ExternalTextureBinding& binding : m_externalTextureBindings) {
|
||||
if (binding.textureSet != nullptr) { binding.textureSet->Shutdown(); delete binding.textureSet; binding.textureSet = nullptr; }
|
||||
binding.shaderView = nullptr;
|
||||
binding.handleKey = 0;
|
||||
}
|
||||
m_externalTextureBindings.clear();
|
||||
if (m_texturedOverlayVertexBufferView != nullptr) { m_texturedOverlayVertexBufferView->Shutdown(); delete m_texturedOverlayVertexBufferView; m_texturedOverlayVertexBufferView = nullptr; }
|
||||
if (m_texturedOverlayVertexBuffer != nullptr) { m_texturedOverlayVertexBuffer->Shutdown(); delete m_texturedOverlayVertexBuffer; m_texturedOverlayVertexBuffer = nullptr; }
|
||||
if (m_overlayVertexBufferView != nullptr) { m_overlayVertexBufferView->Shutdown(); delete m_overlayVertexBufferView; m_overlayVertexBufferView = nullptr; }
|
||||
if (m_overlayVertexBuffer != nullptr) { m_overlayVertexBuffer->Shutdown(); delete m_overlayVertexBuffer; m_overlayVertexBuffer = nullptr; }
|
||||
ResetFontAtlasResources();
|
||||
if (m_texturedOverlayPipelineState != nullptr) { m_texturedOverlayPipelineState->Shutdown(); delete m_texturedOverlayPipelineState; m_texturedOverlayPipelineState = nullptr; }
|
||||
if (m_overlaySamplerSet != nullptr) { m_overlaySamplerSet->Shutdown(); delete m_overlaySamplerSet; m_overlaySamplerSet = nullptr; }
|
||||
if (m_overlaySampler != nullptr) { m_overlaySampler->Shutdown(); delete m_overlaySampler; m_overlaySampler = nullptr; }
|
||||
if (m_overlaySamplerPool != nullptr) { m_overlaySamplerPool->Shutdown(); delete m_overlaySamplerPool; m_overlaySamplerPool = nullptr; }
|
||||
if (m_overlayTexturePool != nullptr) { m_overlayTexturePool->Shutdown(); delete m_overlayTexturePool; m_overlayTexturePool = nullptr; }
|
||||
if (m_texturedOverlayPipelineLayout != nullptr) { m_texturedOverlayPipelineLayout->Shutdown(); delete m_texturedOverlayPipelineLayout; m_texturedOverlayPipelineLayout = nullptr; }
|
||||
if (m_overlayPipelineState != nullptr) { m_overlayPipelineState->Shutdown(); delete m_overlayPipelineState; m_overlayPipelineState = nullptr; }
|
||||
if (m_overlayConstantSet != nullptr) { m_overlayConstantSet->Shutdown(); delete m_overlayConstantSet; m_overlayConstantSet = nullptr; }
|
||||
if (m_overlayConstantPool != nullptr) { m_overlayConstantPool->Shutdown(); delete m_overlayConstantPool; m_overlayConstantPool = nullptr; }
|
||||
if (m_overlayPipelineLayout != nullptr) { m_overlayPipelineLayout->Shutdown(); delete m_overlayPipelineLayout; m_overlayPipelineLayout = nullptr; }
|
||||
m_overlayVertexBufferCapacity = 0u;
|
||||
m_texturedOverlayVertexBufferCapacity = 0u;
|
||||
m_lastOverlayStats = {};
|
||||
m_device = nullptr;
|
||||
m_backendType = RHI::RHIType::D3D12;
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,120 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "IXCUITextAtlasProvider.h"
|
||||
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/RHI/RHIBuffer.h>
|
||||
#include <XCEngine/RHI/RHICommandList.h>
|
||||
#include <XCEngine/RHI/RHIDescriptorPool.h>
|
||||
#include <XCEngine/RHI/RHIDescriptorSet.h>
|
||||
#include <XCEngine/RHI/RHIEnums.h>
|
||||
#include <XCEngine/RHI/RHIPipelineLayout.h>
|
||||
#include <XCEngine/RHI/RHIPipelineState.h>
|
||||
#include <XCEngine/RHI/RHIResourceView.h>
|
||||
#include <XCEngine/RHI/RHISampler.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
class XCUIRHIRenderBackend {
|
||||
public:
|
||||
struct OverlayStats {
|
||||
std::size_t drawListCount = 0;
|
||||
std::size_t commandCount = 0;
|
||||
std::size_t batchCount = 0;
|
||||
std::size_t colorBatchCount = 0;
|
||||
std::size_t texturedBatchCount = 0;
|
||||
std::size_t scissoredBatchCount = 0;
|
||||
std::size_t renderedCommandCount = 0;
|
||||
std::size_t skippedCommandCount = 0;
|
||||
std::size_t textureResolveCount = 0;
|
||||
std::size_t textureCacheHitCount = 0;
|
||||
std::size_t vertexCount = 0;
|
||||
std::size_t triangleCount = 0;
|
||||
};
|
||||
|
||||
void SetTextAtlasProvider(const IXCUITextAtlasProvider* provider);
|
||||
const IXCUITextAtlasProvider* GetTextAtlasProvider() const { return m_textAtlasProvider; }
|
||||
void Shutdown();
|
||||
void ResetStats();
|
||||
bool Render(
|
||||
const ::XCEngine::Rendering::RenderContext& renderContext,
|
||||
const ::XCEngine::Rendering::RenderSurface& surface,
|
||||
const ::XCEngine::UI::UIDrawData& drawData);
|
||||
const OverlayStats& GetLastStats() const { return m_lastOverlayStats; }
|
||||
const OverlayStats& GetLastOverlayStats() const { return m_lastOverlayStats; }
|
||||
|
||||
private:
|
||||
using TextFontHandle = IXCUITextAtlasProvider::FontHandle;
|
||||
|
||||
struct ExternalTextureBinding {
|
||||
std::uintptr_t handleKey = 0;
|
||||
::XCEngine::RHI::RHIResourceView* shaderView = nullptr;
|
||||
::XCEngine::RHI::RHIDescriptorSet* textureSet = nullptr;
|
||||
};
|
||||
|
||||
bool EnsureInitialized(const ::XCEngine::Rendering::RenderContext& renderContext);
|
||||
bool CreateResources(const ::XCEngine::Rendering::RenderContext& renderContext);
|
||||
bool CreateOverlayResources();
|
||||
bool CreateTexturedOverlayResources();
|
||||
const IXCUITextAtlasProvider* ResolveActiveTextAtlasProvider(bool& outFontAtlasReady);
|
||||
bool EnsureOverlayVertexBufferCapacity(std::size_t requiredVertexCount);
|
||||
bool EnsureTexturedOverlayVertexBufferCapacity(std::size_t requiredVertexCount);
|
||||
bool EnsureFontAtlasResources(const IXCUITextAtlasProvider& atlasProvider);
|
||||
void ResetFontAtlasResources();
|
||||
::XCEngine::RHI::RHIDescriptorSet* ResolveTextureSet(
|
||||
const ::XCEngine::UI::UITextureHandle& texture,
|
||||
bool* outCacheHit = nullptr);
|
||||
bool RenderCompiledDrawData(
|
||||
::XCEngine::RHI::RHICommandList& commandList,
|
||||
::XCEngine::RHI::RHIResourceView* renderTarget,
|
||||
const ::XCEngine::Rendering::RenderSurface& surface,
|
||||
const ::XCEngine::UI::UIDrawData& drawData,
|
||||
const IXCUITextAtlasProvider* atlasProvider,
|
||||
bool fontAtlasReady);
|
||||
void DestroyResources();
|
||||
|
||||
const IXCUITextAtlasProvider* m_textAtlasProvider = nullptr;
|
||||
::XCEngine::RHI::RHIDevice* m_device = nullptr;
|
||||
::XCEngine::RHI::RHIType m_backendType = ::XCEngine::RHI::RHIType::D3D12;
|
||||
::XCEngine::RHI::RHIPipelineLayout* m_overlayPipelineLayout = nullptr;
|
||||
::XCEngine::RHI::RHIDescriptorPool* m_overlayConstantPool = nullptr;
|
||||
::XCEngine::RHI::RHIDescriptorSet* m_overlayConstantSet = nullptr;
|
||||
::XCEngine::RHI::RHIPipelineState* m_overlayPipelineState = nullptr;
|
||||
::XCEngine::RHI::RHIPipelineLayout* m_texturedOverlayPipelineLayout = nullptr;
|
||||
::XCEngine::RHI::RHIDescriptorPool* m_overlayTexturePool = nullptr;
|
||||
::XCEngine::RHI::RHIDescriptorSet* m_overlayFontTextureSet = nullptr;
|
||||
::XCEngine::RHI::RHIDescriptorPool* m_overlaySamplerPool = nullptr;
|
||||
::XCEngine::RHI::RHIDescriptorSet* m_overlaySamplerSet = nullptr;
|
||||
::XCEngine::RHI::RHIPipelineState* m_texturedOverlayPipelineState = nullptr;
|
||||
::XCEngine::RHI::RHISampler* m_overlaySampler = nullptr;
|
||||
::XCEngine::RHI::RHITexture* m_overlayFontTexture = nullptr;
|
||||
::XCEngine::RHI::RHIResourceView* m_overlayFontTextureView = nullptr;
|
||||
::XCEngine::UI::UITextureHandle m_overlayFontTextureHandle = {};
|
||||
::XCEngine::RHI::RHIBuffer* m_overlayVertexBuffer = nullptr;
|
||||
::XCEngine::RHI::RHIResourceView* m_overlayVertexBufferView = nullptr;
|
||||
::XCEngine::RHI::RHIBuffer* m_texturedOverlayVertexBuffer = nullptr;
|
||||
::XCEngine::RHI::RHIResourceView* m_texturedOverlayVertexBufferView = nullptr;
|
||||
std::uint64_t m_overlayVertexBufferCapacity = 0;
|
||||
std::uint64_t m_texturedOverlayVertexBufferCapacity = 0;
|
||||
TextFontHandle m_overlayFont = {};
|
||||
std::uintptr_t m_fontAtlasStorageKey = 0;
|
||||
std::uintptr_t m_fontAtlasPixelDataKey = 0;
|
||||
int m_fontAtlasWidth = 0;
|
||||
int m_fontAtlasHeight = 0;
|
||||
int m_fontAtlasStride = 0;
|
||||
std::vector<ExternalTextureBinding> m_externalTextureBindings = {};
|
||||
OverlayStats m_lastOverlayStats = {};
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,414 +0,0 @@
|
||||
#include "XCUIBackend/XCUIShellChromeState.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t ToIndex(XCUIShellPanelId panelId) {
|
||||
return static_cast<std::size_t>(panelId);
|
||||
}
|
||||
|
||||
constexpr std::string_view kViewMenuLabel = "View";
|
||||
constexpr std::string_view kXCUIDemoShortcut = "Ctrl+1";
|
||||
constexpr std::string_view kXCUILayoutLabShortcut = "Ctrl+2";
|
||||
constexpr std::string_view kNativeBackdropShortcut = "Ctrl+Shift+B";
|
||||
constexpr std::string_view kPulseAccentShortcut = "Ctrl+Shift+P";
|
||||
constexpr std::string_view kNativeXCUIOverlayShortcut = "Ctrl+Shift+O";
|
||||
constexpr std::string_view kHostedPreviewHudShortcut = "Ctrl+Shift+H";
|
||||
constexpr std::string_view kNativeDemoPanelPreviewShortcut = "Ctrl+Alt+1";
|
||||
constexpr std::string_view kNativeLayoutLabPreviewShortcut = "Ctrl+Alt+2";
|
||||
|
||||
} // namespace
|
||||
|
||||
XCUIShellChromeState::XCUIShellChromeState() {
|
||||
m_panels[ToIndex(XCUIShellPanelId::XCUIDemo)] = {
|
||||
XCUIShellPanelId::XCUIDemo,
|
||||
"XCUI Demo",
|
||||
"XCUI Demo",
|
||||
"new_editor.panels.xcui_demo",
|
||||
true,
|
||||
true,
|
||||
XCUIShellHostedPreviewMode::NativeOffscreen
|
||||
};
|
||||
m_panels[ToIndex(XCUIShellPanelId::XCUILayoutLab)] = {
|
||||
XCUIShellPanelId::XCUILayoutLab,
|
||||
"XCUI Layout Lab",
|
||||
"XCUI Layout Lab",
|
||||
"new_editor.panels.xcui_layout_lab",
|
||||
true,
|
||||
true,
|
||||
XCUIShellHostedPreviewMode::HostedPresenter
|
||||
};
|
||||
}
|
||||
|
||||
const XCUIShellViewToggleState& XCUIShellChromeState::GetViewToggles() const {
|
||||
return m_viewToggles;
|
||||
}
|
||||
|
||||
const std::array<XCUIShellPanelChromeState, static_cast<std::size_t>(XCUIShellPanelId::Count)>&
|
||||
XCUIShellChromeState::GetPanels() const {
|
||||
return m_panels;
|
||||
}
|
||||
|
||||
const XCUIShellPanelChromeState* XCUIShellChromeState::TryGetPanelState(XCUIShellPanelId panelId) const {
|
||||
const std::size_t index = ToIndex(panelId);
|
||||
if (index >= m_panels.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &m_panels[index];
|
||||
}
|
||||
|
||||
XCUIShellPanelChromeState* XCUIShellChromeState::TryGetPanelStateMutable(XCUIShellPanelId panelId) {
|
||||
const std::size_t index = ToIndex(panelId);
|
||||
if (index >= m_panels.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &m_panels[index];
|
||||
}
|
||||
|
||||
bool XCUIShellChromeState::IsPanelVisible(XCUIShellPanelId panelId) const {
|
||||
const XCUIShellPanelChromeState* panelState = TryGetPanelState(panelId);
|
||||
return panelState != nullptr && panelState->visible;
|
||||
}
|
||||
|
||||
bool XCUIShellChromeState::SetPanelVisible(XCUIShellPanelId panelId, bool visible) {
|
||||
XCUIShellPanelChromeState* panelState = TryGetPanelStateMutable(panelId);
|
||||
if (panelState == nullptr || panelState->visible == visible) {
|
||||
return false;
|
||||
}
|
||||
|
||||
panelState->visible = visible;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XCUIShellChromeState::TogglePanelVisible(XCUIShellPanelId panelId) {
|
||||
XCUIShellPanelChromeState* panelState = TryGetPanelStateMutable(panelId);
|
||||
if (panelState == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
panelState->visible = !panelState->visible;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XCUIShellChromeState::IsHostedPreviewEnabled(XCUIShellPanelId panelId) const {
|
||||
const XCUIShellPanelChromeState* panelState = TryGetPanelState(panelId);
|
||||
return panelState != nullptr && panelState->hostedPreviewEnabled;
|
||||
}
|
||||
|
||||
bool XCUIShellChromeState::SetHostedPreviewEnabled(XCUIShellPanelId panelId, bool enabled) {
|
||||
XCUIShellPanelChromeState* panelState = TryGetPanelStateMutable(panelId);
|
||||
if (panelState == nullptr || panelState->hostedPreviewEnabled == enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
panelState->hostedPreviewEnabled = enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
XCUIShellHostedPreviewMode XCUIShellChromeState::GetHostedPreviewMode(XCUIShellPanelId panelId) const {
|
||||
const XCUIShellPanelChromeState* panelState = TryGetPanelState(panelId);
|
||||
return panelState != nullptr
|
||||
? panelState->previewMode
|
||||
: XCUIShellHostedPreviewMode::HostedPresenter;
|
||||
}
|
||||
|
||||
XCUIShellHostedPreviewState XCUIShellChromeState::GetHostedPreviewState(XCUIShellPanelId panelId) const {
|
||||
const XCUIShellPanelChromeState* panelState = TryGetPanelState(panelId);
|
||||
if (panelState == nullptr || !panelState->hostedPreviewEnabled) {
|
||||
return XCUIShellHostedPreviewState::Disabled;
|
||||
}
|
||||
|
||||
return panelState->previewMode == XCUIShellHostedPreviewMode::NativeOffscreen
|
||||
? XCUIShellHostedPreviewState::NativeOffscreen
|
||||
: XCUIShellHostedPreviewState::HostedPresenter;
|
||||
}
|
||||
|
||||
bool XCUIShellChromeState::IsNativeHostedPreviewActive(XCUIShellPanelId panelId) const {
|
||||
return GetHostedPreviewState(panelId) == XCUIShellHostedPreviewState::NativeOffscreen;
|
||||
}
|
||||
|
||||
bool XCUIShellChromeState::IsHostedPresenterPreviewActive(XCUIShellPanelId panelId) const {
|
||||
return GetHostedPreviewState(panelId) == XCUIShellHostedPreviewState::HostedPresenter;
|
||||
}
|
||||
|
||||
bool XCUIShellChromeState::SetHostedPreviewMode(
|
||||
XCUIShellPanelId panelId,
|
||||
XCUIShellHostedPreviewMode mode) {
|
||||
XCUIShellPanelChromeState* panelState = TryGetPanelStateMutable(panelId);
|
||||
if (panelState == nullptr || panelState->previewMode == mode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
panelState->previewMode = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XCUIShellChromeState::ToggleHostedPreviewMode(XCUIShellPanelId panelId) {
|
||||
XCUIShellPanelChromeState* panelState = TryGetPanelStateMutable(panelId);
|
||||
if (panelState == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
panelState->previewMode =
|
||||
panelState->previewMode == XCUIShellHostedPreviewMode::NativeOffscreen
|
||||
? XCUIShellHostedPreviewMode::HostedPresenter
|
||||
: XCUIShellHostedPreviewMode::NativeOffscreen;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XCUIShellChromeState::GetViewToggle(XCUIShellViewToggleId toggleId) const {
|
||||
switch (toggleId) {
|
||||
case XCUIShellViewToggleId::NativeBackdrop:
|
||||
return m_viewToggles.nativeBackdropVisible;
|
||||
case XCUIShellViewToggleId::PulseAccent:
|
||||
return m_viewToggles.pulseAccentEnabled;
|
||||
case XCUIShellViewToggleId::NativeXCUIOverlay:
|
||||
return m_viewToggles.nativeXCUIOverlayVisible;
|
||||
case XCUIShellViewToggleId::HostedPreviewHud:
|
||||
return m_viewToggles.hostedPreviewHudVisible;
|
||||
case XCUIShellViewToggleId::Count:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool XCUIShellChromeState::SetViewToggle(XCUIShellViewToggleId toggleId, bool enabled) {
|
||||
bool* target = nullptr;
|
||||
switch (toggleId) {
|
||||
case XCUIShellViewToggleId::NativeBackdrop:
|
||||
target = &m_viewToggles.nativeBackdropVisible;
|
||||
break;
|
||||
case XCUIShellViewToggleId::PulseAccent:
|
||||
target = &m_viewToggles.pulseAccentEnabled;
|
||||
break;
|
||||
case XCUIShellViewToggleId::NativeXCUIOverlay:
|
||||
target = &m_viewToggles.nativeXCUIOverlayVisible;
|
||||
break;
|
||||
case XCUIShellViewToggleId::HostedPreviewHud:
|
||||
target = &m_viewToggles.hostedPreviewHudVisible;
|
||||
break;
|
||||
case XCUIShellViewToggleId::Count:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*target == enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*target = enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XCUIShellChromeState::ToggleViewToggle(XCUIShellViewToggleId toggleId) {
|
||||
return SetViewToggle(toggleId, !GetViewToggle(toggleId));
|
||||
}
|
||||
|
||||
bool XCUIShellChromeState::HasCommand(std::string_view commandId) const {
|
||||
XCUIShellCommandDescriptor descriptor = {};
|
||||
return TryGetCommandDescriptor(commandId, descriptor);
|
||||
}
|
||||
|
||||
bool XCUIShellChromeState::InvokeCommand(std::string_view commandId) {
|
||||
if (commandId == XCUIShellChromeCommandIds::ToggleXCUIDemoPanel) {
|
||||
return TogglePanelVisible(XCUIShellPanelId::XCUIDemo);
|
||||
}
|
||||
if (commandId == XCUIShellChromeCommandIds::ToggleXCUILayoutLabPanel) {
|
||||
return TogglePanelVisible(XCUIShellPanelId::XCUILayoutLab);
|
||||
}
|
||||
if (commandId == XCUIShellChromeCommandIds::ToggleNativeBackdrop) {
|
||||
return ToggleViewToggle(XCUIShellViewToggleId::NativeBackdrop);
|
||||
}
|
||||
if (commandId == XCUIShellChromeCommandIds::TogglePulseAccent) {
|
||||
return ToggleViewToggle(XCUIShellViewToggleId::PulseAccent);
|
||||
}
|
||||
if (commandId == XCUIShellChromeCommandIds::ToggleNativeXCUIOverlay) {
|
||||
return ToggleViewToggle(XCUIShellViewToggleId::NativeXCUIOverlay);
|
||||
}
|
||||
if (commandId == XCUIShellChromeCommandIds::ToggleHostedPreviewHud) {
|
||||
return ToggleViewToggle(XCUIShellViewToggleId::HostedPreviewHud);
|
||||
}
|
||||
if (commandId == XCUIShellChromeCommandIds::ToggleNativeDemoPanelPreview) {
|
||||
return ToggleHostedPreviewMode(XCUIShellPanelId::XCUIDemo);
|
||||
}
|
||||
if (commandId == XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview) {
|
||||
return ToggleHostedPreviewMode(XCUIShellPanelId::XCUILayoutLab);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool XCUIShellChromeState::TryGetCommandDescriptor(
|
||||
std::string_view commandId,
|
||||
XCUIShellCommandDescriptor& outDescriptor) const {
|
||||
outDescriptor = {};
|
||||
outDescriptor.checkable = false;
|
||||
outDescriptor.enabled = false;
|
||||
|
||||
const auto tryBuildPanelVisibilityDescriptor =
|
||||
[this, commandId, &outDescriptor](
|
||||
XCUIShellPanelId panelId,
|
||||
std::string_view shortcut) {
|
||||
if (commandId != GetPanelVisibilityCommandId(panelId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const XCUIShellPanelChromeState* panelState = TryGetPanelState(panelId);
|
||||
if (panelState == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outDescriptor.label = panelState->panelTitle;
|
||||
outDescriptor.shortcut = shortcut;
|
||||
outDescriptor.commandId = commandId;
|
||||
outDescriptor.checkable = true;
|
||||
outDescriptor.checked = panelState->visible;
|
||||
outDescriptor.enabled = true;
|
||||
return true;
|
||||
};
|
||||
|
||||
if (tryBuildPanelVisibilityDescriptor(XCUIShellPanelId::XCUIDemo, kXCUIDemoShortcut)) {
|
||||
return true;
|
||||
}
|
||||
if (tryBuildPanelVisibilityDescriptor(XCUIShellPanelId::XCUILayoutLab, kXCUILayoutLabShortcut)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (commandId == GetViewToggleCommandId(XCUIShellViewToggleId::NativeBackdrop)) {
|
||||
outDescriptor.label = "Native Backdrop";
|
||||
outDescriptor.shortcut = kNativeBackdropShortcut;
|
||||
outDescriptor.commandId = commandId;
|
||||
outDescriptor.checkable = true;
|
||||
outDescriptor.checked = GetViewToggle(XCUIShellViewToggleId::NativeBackdrop);
|
||||
outDescriptor.enabled = true;
|
||||
return true;
|
||||
}
|
||||
if (commandId == GetViewToggleCommandId(XCUIShellViewToggleId::PulseAccent)) {
|
||||
outDescriptor.label = "Pulse Accent";
|
||||
outDescriptor.shortcut = kPulseAccentShortcut;
|
||||
outDescriptor.commandId = commandId;
|
||||
outDescriptor.checkable = true;
|
||||
outDescriptor.checked = GetViewToggle(XCUIShellViewToggleId::PulseAccent);
|
||||
outDescriptor.enabled = true;
|
||||
return true;
|
||||
}
|
||||
if (commandId == GetViewToggleCommandId(XCUIShellViewToggleId::NativeXCUIOverlay)) {
|
||||
outDescriptor.label = "Native XCUI Overlay";
|
||||
outDescriptor.shortcut = kNativeXCUIOverlayShortcut;
|
||||
outDescriptor.commandId = commandId;
|
||||
outDescriptor.checkable = true;
|
||||
outDescriptor.checked = GetViewToggle(XCUIShellViewToggleId::NativeXCUIOverlay);
|
||||
outDescriptor.enabled = true;
|
||||
return true;
|
||||
}
|
||||
if (commandId == GetViewToggleCommandId(XCUIShellViewToggleId::HostedPreviewHud)) {
|
||||
outDescriptor.label = "Hosted Preview HUD";
|
||||
outDescriptor.shortcut = kHostedPreviewHudShortcut;
|
||||
outDescriptor.commandId = commandId;
|
||||
outDescriptor.checkable = true;
|
||||
outDescriptor.checked = GetViewToggle(XCUIShellViewToggleId::HostedPreviewHud);
|
||||
outDescriptor.enabled = true;
|
||||
return true;
|
||||
}
|
||||
if (commandId == GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUIDemo)) {
|
||||
outDescriptor.label = "Native Demo Panel Preview";
|
||||
outDescriptor.shortcut = kNativeDemoPanelPreviewShortcut;
|
||||
outDescriptor.commandId = commandId;
|
||||
outDescriptor.checkable = true;
|
||||
outDescriptor.checked = IsNativeHostedPreviewActive(XCUIShellPanelId::XCUIDemo);
|
||||
outDescriptor.enabled = true;
|
||||
return true;
|
||||
}
|
||||
if (commandId == GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUILayoutLab)) {
|
||||
outDescriptor.label = "Native Layout Lab Preview";
|
||||
outDescriptor.shortcut = kNativeLayoutLabPreviewShortcut;
|
||||
outDescriptor.commandId = commandId;
|
||||
outDescriptor.checkable = true;
|
||||
outDescriptor.checked = IsNativeHostedPreviewActive(XCUIShellPanelId::XCUILayoutLab);
|
||||
outDescriptor.enabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
XCUIShellMenuDescriptor XCUIShellChromeState::BuildViewMenuDescriptor() const {
|
||||
XCUIShellMenuDescriptor descriptor = {};
|
||||
descriptor.label = kViewMenuLabel;
|
||||
descriptor.items.reserve(9u);
|
||||
|
||||
const auto appendCommandItem = [this, &descriptor](std::string_view commandId) {
|
||||
XCUIShellCommandDescriptor commandDescriptor = {};
|
||||
if (!TryGetCommandDescriptor(commandId, commandDescriptor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
XCUIShellMenuItemDescriptor item = {};
|
||||
item.kind = XCUIShellMenuItemKind::Command;
|
||||
item.command = commandDescriptor;
|
||||
descriptor.items.push_back(item);
|
||||
};
|
||||
|
||||
appendCommandItem(GetPanelVisibilityCommandId(XCUIShellPanelId::XCUIDemo));
|
||||
appendCommandItem(GetPanelVisibilityCommandId(XCUIShellPanelId::XCUILayoutLab));
|
||||
|
||||
descriptor.items.push_back({ XCUIShellMenuItemKind::Separator, {} });
|
||||
|
||||
appendCommandItem(GetViewToggleCommandId(XCUIShellViewToggleId::NativeBackdrop));
|
||||
appendCommandItem(GetViewToggleCommandId(XCUIShellViewToggleId::PulseAccent));
|
||||
appendCommandItem(GetViewToggleCommandId(XCUIShellViewToggleId::NativeXCUIOverlay));
|
||||
appendCommandItem(GetViewToggleCommandId(XCUIShellViewToggleId::HostedPreviewHud));
|
||||
appendCommandItem(GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUIDemo));
|
||||
appendCommandItem(GetPanelPreviewModeCommandId(XCUIShellPanelId::XCUILayoutLab));
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
std::string_view XCUIShellChromeState::GetPanelVisibilityCommandId(XCUIShellPanelId panelId) {
|
||||
switch (panelId) {
|
||||
case XCUIShellPanelId::XCUIDemo:
|
||||
return XCUIShellChromeCommandIds::ToggleXCUIDemoPanel;
|
||||
case XCUIShellPanelId::XCUILayoutLab:
|
||||
return XCUIShellChromeCommandIds::ToggleXCUILayoutLabPanel;
|
||||
case XCUIShellPanelId::Count:
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view XCUIShellChromeState::GetPanelPreviewModeCommandId(XCUIShellPanelId panelId) {
|
||||
switch (panelId) {
|
||||
case XCUIShellPanelId::XCUIDemo:
|
||||
return XCUIShellChromeCommandIds::ToggleNativeDemoPanelPreview;
|
||||
case XCUIShellPanelId::XCUILayoutLab:
|
||||
return XCUIShellChromeCommandIds::ToggleNativeLayoutLabPreview;
|
||||
case XCUIShellPanelId::Count:
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view XCUIShellChromeState::GetViewToggleCommandId(XCUIShellViewToggleId toggleId) {
|
||||
switch (toggleId) {
|
||||
case XCUIShellViewToggleId::NativeBackdrop:
|
||||
return XCUIShellChromeCommandIds::ToggleNativeBackdrop;
|
||||
case XCUIShellViewToggleId::PulseAccent:
|
||||
return XCUIShellChromeCommandIds::TogglePulseAccent;
|
||||
case XCUIShellViewToggleId::NativeXCUIOverlay:
|
||||
return XCUIShellChromeCommandIds::ToggleNativeXCUIOverlay;
|
||||
case XCUIShellViewToggleId::HostedPreviewHud:
|
||||
return XCUIShellChromeCommandIds::ToggleHostedPreviewHud;
|
||||
case XCUIShellViewToggleId::Count:
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,135 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
enum class XCUIShellPanelId : std::uint8_t {
|
||||
XCUIDemo = 0,
|
||||
XCUILayoutLab,
|
||||
Count
|
||||
};
|
||||
|
||||
enum class XCUIShellViewToggleId : std::uint8_t {
|
||||
NativeBackdrop = 0,
|
||||
PulseAccent,
|
||||
NativeXCUIOverlay,
|
||||
HostedPreviewHud,
|
||||
Count
|
||||
};
|
||||
|
||||
enum class XCUIShellHostedPreviewMode : std::uint8_t {
|
||||
HostedPresenter = 0,
|
||||
NativeOffscreen
|
||||
};
|
||||
|
||||
enum class XCUIShellHostedPreviewState : std::uint8_t {
|
||||
Disabled = 0,
|
||||
HostedPresenter,
|
||||
NativeOffscreen
|
||||
};
|
||||
|
||||
struct XCUIShellPanelChromeState {
|
||||
XCUIShellPanelId panelId = XCUIShellPanelId::XCUIDemo;
|
||||
std::string_view panelTitle = {};
|
||||
std::string_view previewDebugName = {};
|
||||
std::string_view previewDebugSource = {};
|
||||
bool visible = true;
|
||||
bool hostedPreviewEnabled = true;
|
||||
XCUIShellHostedPreviewMode previewMode = XCUIShellHostedPreviewMode::HostedPresenter;
|
||||
};
|
||||
|
||||
struct XCUIShellViewToggleState {
|
||||
bool nativeBackdropVisible = true;
|
||||
bool pulseAccentEnabled = true;
|
||||
bool nativeXCUIOverlayVisible = true;
|
||||
bool hostedPreviewHudVisible = true;
|
||||
};
|
||||
|
||||
struct XCUIShellChromeCommandIds {
|
||||
static constexpr const char* ToggleXCUIDemoPanel = "new_editor.view.xcui_demo";
|
||||
static constexpr const char* ToggleXCUILayoutLabPanel = "new_editor.view.xcui_layout_lab";
|
||||
static constexpr const char* ToggleNativeBackdrop = "new_editor.view.native_backdrop";
|
||||
static constexpr const char* TogglePulseAccent = "new_editor.view.pulse_accent";
|
||||
static constexpr const char* ToggleNativeXCUIOverlay = "new_editor.view.native_xcui_overlay";
|
||||
static constexpr const char* ToggleHostedPreviewHud = "new_editor.view.hosted_preview_hud";
|
||||
static constexpr const char* ToggleNativeDemoPanelPreview = "new_editor.view.native_demo_panel_preview";
|
||||
static constexpr const char* ToggleNativeLayoutLabPreview = "new_editor.view.native_layout_lab_preview";
|
||||
};
|
||||
|
||||
enum class XCUIShellMenuItemKind : std::uint8_t {
|
||||
Command = 0,
|
||||
Separator
|
||||
};
|
||||
|
||||
struct XCUIShellCommandDescriptor {
|
||||
std::string_view label = {};
|
||||
std::string_view shortcut = {};
|
||||
std::string_view commandId = {};
|
||||
bool checkable = true;
|
||||
bool checked = false;
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
struct XCUIShellMenuItemDescriptor {
|
||||
XCUIShellMenuItemKind kind = XCUIShellMenuItemKind::Command;
|
||||
XCUIShellCommandDescriptor command = {};
|
||||
};
|
||||
|
||||
struct XCUIShellMenuDescriptor {
|
||||
std::string_view label = {};
|
||||
std::vector<XCUIShellMenuItemDescriptor> items = {};
|
||||
};
|
||||
|
||||
class XCUIShellChromeState {
|
||||
public:
|
||||
XCUIShellChromeState();
|
||||
|
||||
const XCUIShellViewToggleState& GetViewToggles() const;
|
||||
const std::array<XCUIShellPanelChromeState, static_cast<std::size_t>(XCUIShellPanelId::Count)>& GetPanels() const;
|
||||
const XCUIShellPanelChromeState* TryGetPanelState(XCUIShellPanelId panelId) const;
|
||||
|
||||
bool IsPanelVisible(XCUIShellPanelId panelId) const;
|
||||
bool SetPanelVisible(XCUIShellPanelId panelId, bool visible);
|
||||
bool TogglePanelVisible(XCUIShellPanelId panelId);
|
||||
|
||||
bool IsHostedPreviewEnabled(XCUIShellPanelId panelId) const;
|
||||
bool SetHostedPreviewEnabled(XCUIShellPanelId panelId, bool enabled);
|
||||
|
||||
XCUIShellHostedPreviewMode GetHostedPreviewMode(XCUIShellPanelId panelId) const;
|
||||
XCUIShellHostedPreviewState GetHostedPreviewState(XCUIShellPanelId panelId) const;
|
||||
bool IsNativeHostedPreviewActive(XCUIShellPanelId panelId) const;
|
||||
bool IsHostedPresenterPreviewActive(XCUIShellPanelId panelId) const;
|
||||
bool SetHostedPreviewMode(XCUIShellPanelId panelId, XCUIShellHostedPreviewMode mode);
|
||||
bool ToggleHostedPreviewMode(XCUIShellPanelId panelId);
|
||||
|
||||
bool GetViewToggle(XCUIShellViewToggleId toggleId) const;
|
||||
bool SetViewToggle(XCUIShellViewToggleId toggleId, bool enabled);
|
||||
bool ToggleViewToggle(XCUIShellViewToggleId toggleId);
|
||||
|
||||
bool HasCommand(std::string_view commandId) const;
|
||||
bool InvokeCommand(std::string_view commandId);
|
||||
bool TryGetCommandDescriptor(
|
||||
std::string_view commandId,
|
||||
XCUIShellCommandDescriptor& outDescriptor) const;
|
||||
XCUIShellMenuDescriptor BuildViewMenuDescriptor() const;
|
||||
|
||||
static std::string_view GetPanelVisibilityCommandId(XCUIShellPanelId panelId);
|
||||
static std::string_view GetPanelPreviewModeCommandId(XCUIShellPanelId panelId);
|
||||
static std::string_view GetViewToggleCommandId(XCUIShellViewToggleId toggleId);
|
||||
|
||||
private:
|
||||
XCUIShellPanelChromeState* TryGetPanelStateMutable(XCUIShellPanelId panelId);
|
||||
|
||||
XCUIShellViewToggleState m_viewToggles = {};
|
||||
std::array<XCUIShellPanelChromeState, static_cast<std::size_t>(XCUIShellPanelId::Count)> m_panels = {};
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,842 +0,0 @@
|
||||
#include "XCUIBackend/XCUIStandaloneTextAtlasProvider.h"
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <wingdi.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kDefaultNominalFontSize = 18.0f;
|
||||
constexpr int kAtlasPadding = 1;
|
||||
constexpr int kMaxAtlasDimension = 4096;
|
||||
constexpr std::size_t kInvalidFontIndex = (std::numeric_limits<std::size_t>::max)();
|
||||
constexpr wchar_t kPrimaryFontFace[] = L"Segoe UI";
|
||||
constexpr wchar_t kFallbackFontFace[] = L"Microsoft YaHei";
|
||||
constexpr std::array<int, 5> kSupportedFontSizes = { 13, 14, 16, 18, 20 };
|
||||
constexpr std::array<std::pair<std::uint32_t, std::uint32_t>, 2> kPrebakedCodepointRanges = {{
|
||||
{ 0x0020u, 0x007Eu },
|
||||
{ 0x00A0u, 0x00FFu },
|
||||
}};
|
||||
|
||||
struct CachedPixelBuffer {
|
||||
std::vector<unsigned char> bytes = {};
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int bytesPerPixel = 0;
|
||||
|
||||
void Clear() {
|
||||
bytes.clear();
|
||||
width = 0;
|
||||
height = 0;
|
||||
bytesPerPixel = 0;
|
||||
}
|
||||
|
||||
bool IsValid() const {
|
||||
return !bytes.empty() && width > 0 && height > 0 && bytesPerPixel > 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct GlyphRecord {
|
||||
int sizeKey = 0;
|
||||
std::uint32_t codepoint = 0u;
|
||||
bool visible = false;
|
||||
bool colored = false;
|
||||
float advanceX = 0.0f;
|
||||
float x0 = 0.0f;
|
||||
float y0 = 0.0f;
|
||||
float x1 = 0.0f;
|
||||
float y1 = 0.0f;
|
||||
int bitmapWidth = 0;
|
||||
int bitmapHeight = 0;
|
||||
int atlasX = 0;
|
||||
int atlasY = 0;
|
||||
std::vector<unsigned char> alpha = {};
|
||||
};
|
||||
|
||||
struct BakedFontRecord {
|
||||
int sizeKey = 0;
|
||||
IXCUITextAtlasProvider::BakedFontInfo metrics = {};
|
||||
std::unordered_map<std::uint32_t, IXCUITextAtlasProvider::GlyphInfo> glyphs = {};
|
||||
};
|
||||
|
||||
IXCUITextAtlasProvider::FontHandle MakeDefaultFontHandle() {
|
||||
IXCUITextAtlasProvider::FontHandle handle = {};
|
||||
handle.value = 1u;
|
||||
return handle;
|
||||
}
|
||||
|
||||
bool IsDefaultFontHandle(IXCUITextAtlasProvider::FontHandle handle) {
|
||||
return handle.value == MakeDefaultFontHandle().value;
|
||||
}
|
||||
|
||||
std::size_t ResolveFontIndex(IXCUITextAtlasProvider::FontHandle handle) {
|
||||
return IsDefaultFontHandle(handle) ? 0u : kInvalidFontIndex;
|
||||
}
|
||||
|
||||
std::uint64_t MakeGlyphKey(int sizeKey, std::uint32_t codepoint) {
|
||||
return (static_cast<std::uint64_t>(static_cast<std::uint32_t>(sizeKey)) << 32u) |
|
||||
static_cast<std::uint64_t>(codepoint);
|
||||
}
|
||||
|
||||
void AppendCodepointRange(
|
||||
std::vector<std::uint32_t>& outCodepoints,
|
||||
std::uint32_t first,
|
||||
std::uint32_t last) {
|
||||
if (first > last) {
|
||||
return;
|
||||
}
|
||||
|
||||
outCodepoints.reserve(outCodepoints.size() + static_cast<std::size_t>(last - first + 1u));
|
||||
for (std::uint32_t codepoint = first; codepoint <= last; ++codepoint) {
|
||||
outCodepoints.push_back(codepoint);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> BuildPrebakedCodepointSet() {
|
||||
std::vector<std::uint32_t> codepoints = {};
|
||||
codepoints.reserve(768u);
|
||||
for (const auto& range : kPrebakedCodepointRanges) {
|
||||
AppendCodepointRange(codepoints, range.first, range.second);
|
||||
}
|
||||
codepoints.push_back(0xFFFDu);
|
||||
std::sort(codepoints.begin(), codepoints.end());
|
||||
codepoints.erase(std::unique(codepoints.begin(), codepoints.end()), codepoints.end());
|
||||
return codepoints;
|
||||
}
|
||||
|
||||
HFONT CreateEditorFont(int pixelHeight, const wchar_t* faceName) {
|
||||
return ::CreateFontW(
|
||||
-pixelHeight,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
FW_NORMAL,
|
||||
FALSE,
|
||||
FALSE,
|
||||
FALSE,
|
||||
DEFAULT_CHARSET,
|
||||
OUT_TT_PRECIS,
|
||||
CLIP_DEFAULT_PRECIS,
|
||||
ANTIALIASED_QUALITY,
|
||||
DEFAULT_PITCH | FF_DONTCARE,
|
||||
faceName);
|
||||
}
|
||||
|
||||
bool QueryTextMetrics(HDC dc, HFONT font, TEXTMETRICW& outMetrics) {
|
||||
outMetrics = {};
|
||||
if (dc == nullptr || font == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HGDIOBJ previous = ::SelectObject(dc, font);
|
||||
const BOOL result = ::GetTextMetricsW(dc, &outMetrics);
|
||||
::SelectObject(dc, previous);
|
||||
return result != FALSE;
|
||||
}
|
||||
|
||||
bool FontHasGlyph(HDC dc, HFONT font, std::uint32_t codepoint) {
|
||||
if (dc == nullptr || font == nullptr || codepoint > 0xFFFFu) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WORD glyphIndex = 0xFFFFu;
|
||||
WCHAR wideCodepoint = static_cast<WCHAR>(codepoint);
|
||||
HGDIOBJ previous = ::SelectObject(dc, font);
|
||||
const DWORD result = ::GetGlyphIndicesW(
|
||||
dc,
|
||||
&wideCodepoint,
|
||||
1u,
|
||||
&glyphIndex,
|
||||
GGI_MARK_NONEXISTING_GLYPHS);
|
||||
::SelectObject(dc, previous);
|
||||
return result != GDI_ERROR && glyphIndex != 0xFFFFu;
|
||||
}
|
||||
|
||||
bool IsAdvanceOnlyWhitespace(std::uint32_t codepoint) {
|
||||
return codepoint == 0x0020u ||
|
||||
codepoint == 0x00A0u ||
|
||||
(codepoint >= 0x2000u && codepoint <= 0x200Au) ||
|
||||
codepoint == 0x202Fu ||
|
||||
codepoint == 0x205Fu ||
|
||||
codepoint == 0x3000u;
|
||||
}
|
||||
|
||||
TEXTMETRICW MergeTextMetrics(
|
||||
const TEXTMETRICW& primaryMetrics,
|
||||
bool hasPrimaryMetrics,
|
||||
const TEXTMETRICW& fallbackMetrics,
|
||||
bool hasFallbackMetrics) {
|
||||
TEXTMETRICW merged = {};
|
||||
if (hasPrimaryMetrics) {
|
||||
merged = primaryMetrics;
|
||||
}
|
||||
if (!hasFallbackMetrics) {
|
||||
return merged;
|
||||
}
|
||||
if (!hasPrimaryMetrics) {
|
||||
return fallbackMetrics;
|
||||
}
|
||||
|
||||
merged.tmHeight = (std::max)(primaryMetrics.tmHeight, fallbackMetrics.tmHeight);
|
||||
merged.tmAscent = (std::max)(primaryMetrics.tmAscent, fallbackMetrics.tmAscent);
|
||||
merged.tmDescent = (std::max)(primaryMetrics.tmDescent, fallbackMetrics.tmDescent);
|
||||
merged.tmExternalLeading = (std::max)(primaryMetrics.tmExternalLeading, fallbackMetrics.tmExternalLeading);
|
||||
merged.tmAveCharWidth = (std::max)(primaryMetrics.tmAveCharWidth, fallbackMetrics.tmAveCharWidth);
|
||||
merged.tmMaxCharWidth = (std::max)(primaryMetrics.tmMaxCharWidth, fallbackMetrics.tmMaxCharWidth);
|
||||
return merged;
|
||||
}
|
||||
|
||||
class GdiFontContext {
|
||||
public:
|
||||
explicit GdiFontContext(int sizeKey)
|
||||
: m_sizeKey(sizeKey) {
|
||||
m_dc = ::CreateCompatibleDC(nullptr);
|
||||
if (m_dc == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_primaryFont = CreateEditorFont(sizeKey, kPrimaryFontFace);
|
||||
m_fallbackFont = CreateEditorFont(sizeKey, kFallbackFontFace);
|
||||
m_hasPrimaryMetrics = QueryTextMetrics(m_dc, m_primaryFont, m_primaryMetrics);
|
||||
m_hasFallbackMetrics = QueryTextMetrics(m_dc, m_fallbackFont, m_fallbackMetrics);
|
||||
m_lineMetrics = MergeTextMetrics(
|
||||
m_primaryMetrics,
|
||||
m_hasPrimaryMetrics,
|
||||
m_fallbackMetrics,
|
||||
m_hasFallbackMetrics);
|
||||
}
|
||||
|
||||
~GdiFontContext() {
|
||||
if (m_primaryFont != nullptr) {
|
||||
::DeleteObject(m_primaryFont);
|
||||
m_primaryFont = nullptr;
|
||||
}
|
||||
if (m_fallbackFont != nullptr) {
|
||||
::DeleteObject(m_fallbackFont);
|
||||
m_fallbackFont = nullptr;
|
||||
}
|
||||
if (m_dc != nullptr) {
|
||||
::DeleteDC(m_dc);
|
||||
m_dc = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsValid() const {
|
||||
return m_dc != nullptr && (m_hasPrimaryMetrics || m_hasFallbackMetrics);
|
||||
}
|
||||
|
||||
IXCUITextAtlasProvider::BakedFontInfo BuildBakedFontInfo() const {
|
||||
IXCUITextAtlasProvider::BakedFontInfo info = {};
|
||||
info.lineHeight = static_cast<float>(m_lineMetrics.tmHeight);
|
||||
info.ascent = static_cast<float>(m_lineMetrics.tmAscent);
|
||||
info.descent = static_cast<float>(m_lineMetrics.tmDescent);
|
||||
info.rasterizerDensity = 1.0f;
|
||||
return info;
|
||||
}
|
||||
|
||||
bool RasterizeGlyph(std::uint32_t codepoint, GlyphRecord& outRecord) const {
|
||||
outRecord = {};
|
||||
outRecord.sizeKey = m_sizeKey;
|
||||
outRecord.codepoint = codepoint;
|
||||
outRecord.colored = false;
|
||||
|
||||
if (!IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HFONT resolvedFont = ResolveFontForCodepoint(codepoint);
|
||||
if (resolvedFont == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsAdvanceOnlyWhitespace(codepoint)) {
|
||||
HGDIOBJ previous = ::SelectObject(m_dc, resolvedFont);
|
||||
SIZE textExtent = {};
|
||||
WCHAR wideCodepoint = static_cast<WCHAR>(codepoint);
|
||||
const BOOL extentResult = ::GetTextExtentPoint32W(m_dc, &wideCodepoint, 1, &textExtent);
|
||||
::SelectObject(m_dc, previous);
|
||||
outRecord.visible = false;
|
||||
outRecord.advanceX = extentResult != FALSE
|
||||
? static_cast<float>(textExtent.cx)
|
||||
: static_cast<float>(m_lineMetrics.tmAveCharWidth);
|
||||
return true;
|
||||
}
|
||||
|
||||
HGDIOBJ previous = ::SelectObject(m_dc, resolvedFont);
|
||||
MAT2 transform = {};
|
||||
transform.eM11.value = 1;
|
||||
transform.eM22.value = 1;
|
||||
|
||||
GLYPHMETRICS glyphMetrics = {};
|
||||
const DWORD bufferSize = ::GetGlyphOutlineW(
|
||||
m_dc,
|
||||
static_cast<UINT>(codepoint),
|
||||
GGO_GRAY8_BITMAP,
|
||||
&glyphMetrics,
|
||||
0u,
|
||||
nullptr,
|
||||
&transform);
|
||||
::SelectObject(m_dc, previous);
|
||||
|
||||
if (bufferSize == GDI_ERROR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outRecord.visible = glyphMetrics.gmBlackBoxX > 0u && glyphMetrics.gmBlackBoxY > 0u;
|
||||
outRecord.advanceX = static_cast<float>(glyphMetrics.gmCellIncX);
|
||||
outRecord.bitmapWidth = static_cast<int>(glyphMetrics.gmBlackBoxX);
|
||||
outRecord.bitmapHeight = static_cast<int>(glyphMetrics.gmBlackBoxY);
|
||||
outRecord.x0 = static_cast<float>(glyphMetrics.gmptGlyphOrigin.x);
|
||||
outRecord.y0 = static_cast<float>(m_lineMetrics.tmAscent - glyphMetrics.gmptGlyphOrigin.y);
|
||||
outRecord.x1 = outRecord.x0 + static_cast<float>(glyphMetrics.gmBlackBoxX);
|
||||
outRecord.y1 = outRecord.y0 + static_cast<float>(glyphMetrics.gmBlackBoxY);
|
||||
|
||||
if (!outRecord.visible) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> bitmapBuffer(bufferSize);
|
||||
previous = ::SelectObject(m_dc, resolvedFont);
|
||||
const DWORD writeResult = ::GetGlyphOutlineW(
|
||||
m_dc,
|
||||
static_cast<UINT>(codepoint),
|
||||
GGO_GRAY8_BITMAP,
|
||||
&glyphMetrics,
|
||||
bufferSize,
|
||||
bitmapBuffer.data(),
|
||||
&transform);
|
||||
::SelectObject(m_dc, previous);
|
||||
if (writeResult == GDI_ERROR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int sourceStride = (static_cast<int>(glyphMetrics.gmBlackBoxX) + 3) & ~3;
|
||||
const std::size_t requiredBytes =
|
||||
static_cast<std::size_t>(sourceStride) *
|
||||
static_cast<std::size_t>((std::max)(0, outRecord.bitmapHeight));
|
||||
if (bitmapBuffer.size() < requiredBytes) {
|
||||
return false;
|
||||
}
|
||||
outRecord.alpha.resize(
|
||||
static_cast<std::size_t>(outRecord.bitmapWidth) *
|
||||
static_cast<std::size_t>(outRecord.bitmapHeight));
|
||||
for (int y = 0; y < outRecord.bitmapHeight; ++y) {
|
||||
const unsigned char* sourceRow = bitmapBuffer.data() + static_cast<std::size_t>(y * sourceStride);
|
||||
unsigned char* destRow = outRecord.alpha.data() +
|
||||
static_cast<std::size_t>(y * outRecord.bitmapWidth);
|
||||
for (int x = 0; x < outRecord.bitmapWidth; ++x) {
|
||||
const unsigned int value = static_cast<unsigned int>(sourceRow[x]);
|
||||
destRow[x] = static_cast<unsigned char>((value * 255u + 32u) / 64u);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
HFONT ResolveFontForCodepoint(std::uint32_t codepoint) const {
|
||||
if (FontHasGlyph(m_dc, m_primaryFont, codepoint)) {
|
||||
return m_primaryFont;
|
||||
}
|
||||
if (FontHasGlyph(m_dc, m_fallbackFont, codepoint)) {
|
||||
return m_fallbackFont;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int m_sizeKey = 0;
|
||||
HDC m_dc = nullptr;
|
||||
HFONT m_primaryFont = nullptr;
|
||||
HFONT m_fallbackFont = nullptr;
|
||||
TEXTMETRICW m_primaryMetrics = {};
|
||||
TEXTMETRICW m_fallbackMetrics = {};
|
||||
TEXTMETRICW m_lineMetrics = {};
|
||||
bool m_hasPrimaryMetrics = false;
|
||||
bool m_hasFallbackMetrics = false;
|
||||
};
|
||||
|
||||
std::uintptr_t MakePixelDataKey(const CachedPixelBuffer& pixels, std::uint64_t generation) {
|
||||
return reinterpret_cast<std::uintptr_t>(pixels.bytes.data()) ^
|
||||
static_cast<std::uintptr_t>(generation * 1315423911ull);
|
||||
}
|
||||
|
||||
int ResolveSupportedSizeKey(float requestedSize, float nominalSize) {
|
||||
const float targetSize = requestedSize > 0.0f ? requestedSize : nominalSize;
|
||||
auto bestIt = std::min_element(
|
||||
kSupportedFontSizes.begin(),
|
||||
kSupportedFontSizes.end(),
|
||||
[targetSize](int lhs, int rhs) {
|
||||
const float lhsDistance = std::fabs(targetSize - static_cast<float>(lhs));
|
||||
const float rhsDistance = std::fabs(targetSize - static_cast<float>(rhs));
|
||||
return lhsDistance < rhsDistance;
|
||||
});
|
||||
return bestIt != kSupportedFontSizes.end() ? *bestIt : static_cast<int>(std::lround(targetSize));
|
||||
}
|
||||
|
||||
bool PopulateGlyphInfo(
|
||||
const GlyphRecord& glyph,
|
||||
int atlasWidth,
|
||||
int atlasHeight,
|
||||
IXCUITextAtlasProvider::GlyphInfo& outInfo) {
|
||||
outInfo = {};
|
||||
outInfo.requestedCodepoint = glyph.codepoint;
|
||||
outInfo.resolvedCodepoint = glyph.codepoint;
|
||||
outInfo.visible = glyph.visible;
|
||||
outInfo.colored = glyph.colored;
|
||||
outInfo.advanceX = glyph.advanceX;
|
||||
outInfo.x0 = glyph.x0;
|
||||
outInfo.y0 = glyph.y0;
|
||||
outInfo.x1 = glyph.x1;
|
||||
outInfo.y1 = glyph.y1;
|
||||
if (!glyph.visible) {
|
||||
return true;
|
||||
}
|
||||
if (atlasWidth <= 0 || atlasHeight <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outInfo.u0 = static_cast<float>(glyph.atlasX) / static_cast<float>(atlasWidth);
|
||||
outInfo.v0 = static_cast<float>(glyph.atlasY) / static_cast<float>(atlasHeight);
|
||||
outInfo.u1 = static_cast<float>(glyph.atlasX + glyph.bitmapWidth) / static_cast<float>(atlasWidth);
|
||||
outInfo.v1 = static_cast<float>(glyph.atlasY + glyph.bitmapHeight) / static_cast<float>(atlasHeight);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::size_t> BuildVisiblePackOrder(const std::vector<GlyphRecord>& glyphs) {
|
||||
std::vector<std::size_t> order = {};
|
||||
order.reserve(glyphs.size());
|
||||
for (std::size_t index = 0; index < glyphs.size(); ++index) {
|
||||
if (glyphs[index].visible) {
|
||||
order.push_back(index);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(
|
||||
order.begin(),
|
||||
order.end(),
|
||||
[&glyphs](std::size_t lhs, std::size_t rhs) {
|
||||
const GlyphRecord& left = glyphs[lhs];
|
||||
const GlyphRecord& right = glyphs[rhs];
|
||||
if (left.bitmapHeight != right.bitmapHeight) {
|
||||
return left.bitmapHeight > right.bitmapHeight;
|
||||
}
|
||||
if (left.bitmapWidth != right.bitmapWidth) {
|
||||
return left.bitmapWidth > right.bitmapWidth;
|
||||
}
|
||||
if (left.sizeKey != right.sizeKey) {
|
||||
return left.sizeKey < right.sizeKey;
|
||||
}
|
||||
return left.codepoint < right.codepoint;
|
||||
});
|
||||
return order;
|
||||
}
|
||||
|
||||
bool TryPackVisibleGlyphs(
|
||||
std::vector<GlyphRecord>& glyphs,
|
||||
int atlasWidth,
|
||||
int& outAtlasHeight) {
|
||||
outAtlasHeight = 1;
|
||||
if (atlasWidth <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (GlyphRecord& glyph : glyphs) {
|
||||
glyph.atlasX = 0;
|
||||
glyph.atlasY = 0;
|
||||
}
|
||||
|
||||
const std::vector<std::size_t> packOrder = BuildVisiblePackOrder(glyphs);
|
||||
int cursorX = kAtlasPadding;
|
||||
int cursorY = kAtlasPadding;
|
||||
int rowHeight = 0;
|
||||
|
||||
for (std::size_t index : packOrder) {
|
||||
GlyphRecord& glyph = glyphs[index];
|
||||
const int requiredWidth = glyph.bitmapWidth + kAtlasPadding * 2;
|
||||
const int requiredHeight = glyph.bitmapHeight + kAtlasPadding * 2;
|
||||
if (requiredWidth > atlasWidth) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cursorX + requiredWidth > atlasWidth) {
|
||||
cursorX = kAtlasPadding;
|
||||
cursorY += rowHeight;
|
||||
rowHeight = 0;
|
||||
}
|
||||
|
||||
glyph.atlasX = cursorX + kAtlasPadding;
|
||||
glyph.atlasY = cursorY + kAtlasPadding;
|
||||
cursorX += requiredWidth;
|
||||
rowHeight = (std::max)(rowHeight, requiredHeight);
|
||||
outAtlasHeight = (std::max)(outAtlasHeight, cursorY + rowHeight + kAtlasPadding);
|
||||
if (outAtlasHeight > kMaxAtlasDimension) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct XCUIStandaloneTextAtlasProvider::Impl {
|
||||
bool ready = false;
|
||||
float nominalSize = kDefaultNominalFontSize;
|
||||
CachedPixelBuffer alpha8Pixels = {};
|
||||
CachedPixelBuffer rgba32Pixels = {};
|
||||
std::uint64_t pixelGeneration = 0u;
|
||||
std::unordered_map<int, BakedFontRecord> bakedFonts = {};
|
||||
std::vector<GlyphRecord> glyphRecords = {};
|
||||
std::unordered_map<std::uint64_t, std::size_t> glyphIndices = {};
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
void ResetImpl(XCUIStandaloneTextAtlasProvider::Impl& impl) {
|
||||
impl.ready = false;
|
||||
impl.nominalSize = kDefaultNominalFontSize;
|
||||
impl.alpha8Pixels.Clear();
|
||||
impl.rgba32Pixels.Clear();
|
||||
impl.bakedFonts.clear();
|
||||
impl.glyphRecords.clear();
|
||||
impl.glyphIndices.clear();
|
||||
++impl.pixelGeneration;
|
||||
}
|
||||
|
||||
bool RebuildAtlasPixels(XCUIStandaloneTextAtlasProvider::Impl& impl) {
|
||||
int chosenAtlasWidth = 0;
|
||||
int chosenAtlasHeight = 1;
|
||||
for (const int candidateWidth : { 1024, 2048, 4096 }) {
|
||||
int packedHeight = 1;
|
||||
if (TryPackVisibleGlyphs(impl.glyphRecords, candidateWidth, packedHeight)) {
|
||||
chosenAtlasWidth = candidateWidth;
|
||||
chosenAtlasHeight = packedHeight;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (chosenAtlasWidth == 0 || chosenAtlasHeight <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
impl.alpha8Pixels.width = chosenAtlasWidth;
|
||||
impl.alpha8Pixels.height = chosenAtlasHeight;
|
||||
impl.alpha8Pixels.bytesPerPixel = 1;
|
||||
impl.alpha8Pixels.bytes.assign(
|
||||
static_cast<std::size_t>(chosenAtlasWidth) * static_cast<std::size_t>(chosenAtlasHeight),
|
||||
0u);
|
||||
|
||||
for (const GlyphRecord& glyph : impl.glyphRecords) {
|
||||
if (!glyph.visible || glyph.bitmapWidth <= 0 || glyph.bitmapHeight <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int y = 0; y < glyph.bitmapHeight; ++y) {
|
||||
const unsigned char* sourceRow = glyph.alpha.data() +
|
||||
static_cast<std::size_t>(y * glyph.bitmapWidth);
|
||||
unsigned char* destRow = impl.alpha8Pixels.bytes.data() +
|
||||
static_cast<std::size_t>((glyph.atlasY + y) * impl.alpha8Pixels.width + glyph.atlasX);
|
||||
std::copy(sourceRow, sourceRow + glyph.bitmapWidth, destRow);
|
||||
}
|
||||
}
|
||||
|
||||
impl.rgba32Pixels.width = chosenAtlasWidth;
|
||||
impl.rgba32Pixels.height = chosenAtlasHeight;
|
||||
impl.rgba32Pixels.bytesPerPixel = 4;
|
||||
impl.rgba32Pixels.bytes.assign(
|
||||
static_cast<std::size_t>(chosenAtlasWidth) *
|
||||
static_cast<std::size_t>(chosenAtlasHeight) *
|
||||
4u,
|
||||
0u);
|
||||
for (std::size_t pixelIndex = 0; pixelIndex < impl.alpha8Pixels.bytes.size(); ++pixelIndex) {
|
||||
const unsigned char alpha = impl.alpha8Pixels.bytes[pixelIndex];
|
||||
unsigned char* rgba = impl.rgba32Pixels.bytes.data() + pixelIndex * 4u;
|
||||
rgba[0] = 255u;
|
||||
rgba[1] = 255u;
|
||||
rgba[2] = 255u;
|
||||
rgba[3] = alpha;
|
||||
}
|
||||
|
||||
for (auto& [sizeKey, bakedFont] : impl.bakedFonts) {
|
||||
(void)sizeKey;
|
||||
bakedFont.glyphs.clear();
|
||||
}
|
||||
|
||||
for (const GlyphRecord& glyph : impl.glyphRecords) {
|
||||
IXCUITextAtlasProvider::GlyphInfo glyphInfo = {};
|
||||
if (!PopulateGlyphInfo(
|
||||
glyph,
|
||||
impl.alpha8Pixels.width,
|
||||
impl.alpha8Pixels.height,
|
||||
glyphInfo)) {
|
||||
return false;
|
||||
}
|
||||
impl.bakedFonts[glyph.sizeKey].glyphs.insert_or_assign(glyph.codepoint, glyphInfo);
|
||||
}
|
||||
|
||||
++impl.pixelGeneration;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AddGlyphRecord(
|
||||
XCUIStandaloneTextAtlasProvider::Impl& impl,
|
||||
int sizeKey,
|
||||
std::uint32_t codepoint) {
|
||||
const std::uint64_t glyphKey = MakeGlyphKey(sizeKey, codepoint);
|
||||
if (impl.glyphIndices.find(glyphKey) != impl.glyphIndices.end()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GdiFontContext context(sizeKey);
|
||||
if (!context.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (impl.bakedFonts.find(sizeKey) == impl.bakedFonts.end()) {
|
||||
BakedFontRecord bakedFont = {};
|
||||
bakedFont.sizeKey = sizeKey;
|
||||
bakedFont.metrics = context.BuildBakedFontInfo();
|
||||
impl.bakedFonts.insert_or_assign(sizeKey, std::move(bakedFont));
|
||||
}
|
||||
|
||||
GlyphRecord glyph = {};
|
||||
if (!context.RasterizeGlyph(codepoint, glyph)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::size_t previousGlyphCount = impl.glyphRecords.size();
|
||||
impl.glyphRecords.push_back(std::move(glyph));
|
||||
impl.glyphIndices.insert_or_assign(glyphKey, previousGlyphCount);
|
||||
if (!RebuildAtlasPixels(impl)) {
|
||||
impl.glyphIndices.erase(glyphKey);
|
||||
impl.glyphRecords.pop_back();
|
||||
RebuildAtlasPixels(impl);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const BakedFontRecord* FindBakedFont(
|
||||
const XCUIStandaloneTextAtlasProvider::Impl& impl,
|
||||
int sizeKey) {
|
||||
const auto it = impl.bakedFonts.find(sizeKey);
|
||||
return it != impl.bakedFonts.end() ? &it->second : nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
XCUIStandaloneTextAtlasProvider::XCUIStandaloneTextAtlasProvider()
|
||||
: m_impl(std::make_unique<Impl>()) {
|
||||
RebuildDefaultEditorAtlas();
|
||||
}
|
||||
|
||||
XCUIStandaloneTextAtlasProvider::~XCUIStandaloneTextAtlasProvider() = default;
|
||||
|
||||
void XCUIStandaloneTextAtlasProvider::Reset() {
|
||||
if (m_impl == nullptr) {
|
||||
m_impl = std::make_unique<Impl>();
|
||||
return;
|
||||
}
|
||||
|
||||
ResetImpl(*m_impl);
|
||||
}
|
||||
|
||||
bool XCUIStandaloneTextAtlasProvider::RebuildDefaultEditorAtlas() {
|
||||
Reset();
|
||||
|
||||
if (m_impl == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<std::uint32_t> prebakedCodepoints = BuildPrebakedCodepointSet();
|
||||
for (const int sizeKey : kSupportedFontSizes) {
|
||||
GdiFontContext context(sizeKey);
|
||||
if (!context.IsValid()) {
|
||||
ResetImpl(*m_impl);
|
||||
return false;
|
||||
}
|
||||
|
||||
BakedFontRecord bakedFont = {};
|
||||
bakedFont.sizeKey = sizeKey;
|
||||
bakedFont.metrics = context.BuildBakedFontInfo();
|
||||
m_impl->bakedFonts.insert_or_assign(sizeKey, std::move(bakedFont));
|
||||
|
||||
for (std::uint32_t codepoint : prebakedCodepoints) {
|
||||
GlyphRecord glyph = {};
|
||||
if (!context.RasterizeGlyph(codepoint, glyph)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::uint64_t glyphKey = MakeGlyphKey(sizeKey, codepoint);
|
||||
m_impl->glyphIndices.insert_or_assign(glyphKey, m_impl->glyphRecords.size());
|
||||
m_impl->glyphRecords.push_back(std::move(glyph));
|
||||
}
|
||||
}
|
||||
|
||||
if (m_impl->bakedFonts.empty() || !RebuildAtlasPixels(*m_impl)) {
|
||||
ResetImpl(*m_impl);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_impl->ready = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XCUIStandaloneTextAtlasProvider::IsReady() const {
|
||||
return m_impl != nullptr &&
|
||||
m_impl->ready &&
|
||||
!m_impl->bakedFonts.empty() &&
|
||||
m_impl->rgba32Pixels.IsValid();
|
||||
}
|
||||
|
||||
bool XCUIStandaloneTextAtlasProvider::GetAtlasTextureView(
|
||||
PixelFormat preferredFormat,
|
||||
AtlasTextureView& outView) const {
|
||||
outView = {};
|
||||
|
||||
if (!IsReady() || m_impl == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const CachedPixelBuffer* resolvedPixels = nullptr;
|
||||
PixelFormat resolvedFormat = PixelFormat::Unknown;
|
||||
if (preferredFormat == PixelFormat::Alpha8 && m_impl->alpha8Pixels.IsValid()) {
|
||||
resolvedPixels = &m_impl->alpha8Pixels;
|
||||
resolvedFormat = PixelFormat::Alpha8;
|
||||
} else if (m_impl->rgba32Pixels.IsValid()) {
|
||||
resolvedPixels = &m_impl->rgba32Pixels;
|
||||
resolvedFormat = PixelFormat::RGBA32;
|
||||
} else if (m_impl->alpha8Pixels.IsValid()) {
|
||||
resolvedPixels = &m_impl->alpha8Pixels;
|
||||
resolvedFormat = PixelFormat::Alpha8;
|
||||
}
|
||||
|
||||
if (resolvedPixels == nullptr || !resolvedPixels->IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outView.pixels = resolvedPixels->bytes.data();
|
||||
outView.width = resolvedPixels->width;
|
||||
outView.height = resolvedPixels->height;
|
||||
outView.stride = resolvedPixels->width * resolvedPixels->bytesPerPixel;
|
||||
outView.bytesPerPixel = resolvedPixels->bytesPerPixel;
|
||||
outView.format = resolvedFormat;
|
||||
outView.atlasStorageKey = reinterpret_cast<std::uintptr_t>(m_impl.get());
|
||||
outView.pixelDataKey = MakePixelDataKey(*resolvedPixels, m_impl->pixelGeneration);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t XCUIStandaloneTextAtlasProvider::GetFontCount() const {
|
||||
return IsReady() ? 1u : 0u;
|
||||
}
|
||||
|
||||
IXCUITextAtlasProvider::FontHandle XCUIStandaloneTextAtlasProvider::GetFont(std::size_t index) const {
|
||||
return IsReady() && index == 0u ? MakeDefaultFontHandle() : FontHandle{};
|
||||
}
|
||||
|
||||
IXCUITextAtlasProvider::FontHandle XCUIStandaloneTextAtlasProvider::GetDefaultFont() const {
|
||||
return IsReady() ? MakeDefaultFontHandle() : FontHandle{};
|
||||
}
|
||||
|
||||
bool XCUIStandaloneTextAtlasProvider::GetFontInfo(FontHandle fontHandle, FontInfo& outInfo) const {
|
||||
outInfo = {};
|
||||
|
||||
if (!IsReady() || ResolveFontIndex(fontHandle) == kInvalidFontIndex || m_impl == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outInfo.handle = fontHandle;
|
||||
outInfo.nominalSize = m_impl->nominalSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XCUIStandaloneTextAtlasProvider::GetBakedFontInfo(
|
||||
FontHandle fontHandle,
|
||||
float fontSize,
|
||||
BakedFontInfo& outInfo) const {
|
||||
outInfo = {};
|
||||
|
||||
if (!IsReady() || ResolveFontIndex(fontHandle) == kInvalidFontIndex || m_impl == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int sizeKey = ResolveSupportedSizeKey(fontSize, m_impl->nominalSize);
|
||||
const BakedFontRecord* bakedFont = FindBakedFont(*m_impl, sizeKey);
|
||||
if (bakedFont == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outInfo = bakedFont->metrics;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XCUIStandaloneTextAtlasProvider::FindGlyph(
|
||||
FontHandle fontHandle,
|
||||
float fontSize,
|
||||
std::uint32_t codepoint,
|
||||
GlyphInfo& outInfo) const {
|
||||
outInfo = {};
|
||||
|
||||
if (!IsReady() || ResolveFontIndex(fontHandle) == kInvalidFontIndex || m_impl == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int sizeKey = ResolveSupportedSizeKey(fontSize, m_impl->nominalSize);
|
||||
const BakedFontRecord* bakedFont = FindBakedFont(*m_impl, sizeKey);
|
||||
if (bakedFont == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto glyphIt = bakedFont->glyphs.find(codepoint);
|
||||
if (glyphIt != bakedFont->glyphs.end()) {
|
||||
outInfo = glyphIt->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (codepoint <= 0xFFFFu && AddGlyphRecord(*m_impl, sizeKey, codepoint)) {
|
||||
const BakedFontRecord* refreshedBakedFont = FindBakedFont(*m_impl, sizeKey);
|
||||
if (refreshedBakedFont != nullptr) {
|
||||
const auto refreshedGlyphIt = refreshedBakedFont->glyphs.find(codepoint);
|
||||
if (refreshedGlyphIt != refreshedBakedFont->glyphs.end()) {
|
||||
outInfo = refreshedGlyphIt->second;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const BakedFontRecord* fallbackBakedFont = FindBakedFont(*m_impl, sizeKey);
|
||||
if (fallbackBakedFont == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto fallbackGlyphIt = fallbackBakedFont->glyphs.find(static_cast<std::uint32_t>('?'));
|
||||
if (fallbackGlyphIt == fallbackBakedFont->glyphs.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outInfo = fallbackGlyphIt->second;
|
||||
outInfo.requestedCodepoint = codepoint;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "IXCUITextAtlasProvider.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
class XCUIStandaloneTextAtlasProvider final : public IXCUITextAtlasProvider {
|
||||
public:
|
||||
XCUIStandaloneTextAtlasProvider();
|
||||
~XCUIStandaloneTextAtlasProvider();
|
||||
|
||||
XCUIStandaloneTextAtlasProvider(const XCUIStandaloneTextAtlasProvider&) = delete;
|
||||
XCUIStandaloneTextAtlasProvider& operator=(const XCUIStandaloneTextAtlasProvider&) = delete;
|
||||
|
||||
void Reset();
|
||||
bool RebuildDefaultEditorAtlas();
|
||||
bool IsReady() const;
|
||||
|
||||
bool GetAtlasTextureView(PixelFormat preferredFormat, AtlasTextureView& outView) const override;
|
||||
std::size_t GetFontCount() const override;
|
||||
FontHandle GetFont(std::size_t index) const override;
|
||||
FontHandle GetDefaultFont() const override;
|
||||
bool GetFontInfo(FontHandle font, FontInfo& outInfo) const override;
|
||||
bool GetBakedFontInfo(FontHandle font, float fontSize, BakedFontInfo& outInfo) const override;
|
||||
bool FindGlyph(FontHandle font, float fontSize, std::uint32_t codepoint, GlyphInfo& outInfo) const override;
|
||||
|
||||
struct Impl;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "Application.h"
|
||||
|
||||
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) {
|
||||
XCEngine::NewEditor::Application app;
|
||||
return app.Run(hInstance, nCmdShow);
|
||||
return XCEngine::NewEditor::RunNewEditor(hInstance, nCmdShow);
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
#include "Panel.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
Panel::Panel(std::string name, bool visible)
|
||||
: m_name(std::move(name))
|
||||
, m_visible(visible) {
|
||||
}
|
||||
|
||||
Panel::~Panel() = default;
|
||||
|
||||
const std::string& Panel::GetName() const {
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void Panel::SetName(const std::string& name) {
|
||||
m_name = name;
|
||||
}
|
||||
|
||||
bool Panel::IsVisible() const {
|
||||
return m_visible;
|
||||
}
|
||||
|
||||
void Panel::SetVisible(bool visible) {
|
||||
m_visible = visible;
|
||||
}
|
||||
|
||||
void Panel::ToggleVisible() {
|
||||
m_visible = !m_visible;
|
||||
}
|
||||
|
||||
void Panel::RenderIfVisible() {
|
||||
if (!m_visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
Render();
|
||||
}
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
@@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
class Panel {
|
||||
public:
|
||||
explicit Panel(std::string name, bool visible = true);
|
||||
virtual ~Panel();
|
||||
|
||||
const std::string& GetName() const;
|
||||
void SetName(const std::string& name);
|
||||
|
||||
bool IsVisible() const;
|
||||
void SetVisible(bool visible);
|
||||
void ToggleVisible();
|
||||
|
||||
void RenderIfVisible();
|
||||
virtual void Render() = 0;
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
bool m_visible = true;
|
||||
};
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
@@ -1,154 +0,0 @@
|
||||
#include "XCUIDemoPanel.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kCanvasHudHeight = 82.0f;
|
||||
constexpr float kCanvasHudPadding = 10.0f;
|
||||
constexpr char kPreviewDebugSource[] = "new_editor.panels.xcui_demo";
|
||||
|
||||
} // namespace
|
||||
|
||||
void XCUIDemoPanel::Render() {
|
||||
ImGui::SetNextWindowSize(ImVec2(1040.0f, 720.0f), ImGuiCond_Appearing);
|
||||
ImGui::SetNextWindowDockID(ImGui::GetID("XCNewEditorDockSpace"), ImGuiCond_Appearing);
|
||||
|
||||
bool open = true;
|
||||
if (!ImGui::Begin(GetName().c_str(), &open)) {
|
||||
ImGui::End();
|
||||
if (!open) {
|
||||
SetVisible(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::Button("Reload Documents")) {
|
||||
m_lastReloadSucceeded = m_runtime.ReloadDocuments();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Canvas HUD", &m_showCanvasHud);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Debug Rects", &m_showDebugRects);
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(m_lastReloadSucceeded ? "Reload: OK" : "Reload: Failed");
|
||||
ImGui::Separator();
|
||||
|
||||
const float diagnosticsHeight = 232.0f;
|
||||
const ImVec2 hostRegion = ImGui::GetContentRegionAvail();
|
||||
const float canvasHeight = (std::max)(140.0f, hostRegion.y - diagnosticsHeight);
|
||||
const float topInset = m_showCanvasHud ? (kCanvasHudHeight + kCanvasHudPadding) : 0.0f;
|
||||
const XCUIDemoPanelFrameComposition& composition = ComposeFrame(
|
||||
XCUIDemoPanelFrameComposeOptions{
|
||||
canvasHeight,
|
||||
topInset,
|
||||
m_hostedPreviewEnabled,
|
||||
m_showCanvasHud,
|
||||
m_showDebugRects,
|
||||
0u,
|
||||
"XCUIDemoCanvasHost"});
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameStats& stats = composition.frame->stats;
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::BeginChild("XCUIDemoDiagnostics", ImVec2(0.0f, 0.0f), false, ImGuiWindowFlags_NoScrollbar);
|
||||
ImGui::SeparatorText("Preview");
|
||||
ImGui::Text(
|
||||
"Path: %s | state: %s",
|
||||
composition.previewPathLabel.c_str(),
|
||||
composition.previewStateLabel.c_str());
|
||||
ImGui::Text(
|
||||
"Presenter: presented %s | submit->native %s",
|
||||
composition.previewStats.presented ? "yes" : "no",
|
||||
composition.previewStats.queuedToNativePass ? "yes" : "no");
|
||||
ImGui::Text(
|
||||
"Submitted: %zu lists / %zu cmds | Flushed: %zu lists / %zu cmds",
|
||||
composition.previewStats.submittedDrawListCount,
|
||||
composition.previewStats.submittedCommandCount,
|
||||
composition.previewStats.flushedDrawListCount,
|
||||
composition.previewStats.flushedCommandCount);
|
||||
ImGui::TextWrapped(
|
||||
"Source: %s",
|
||||
composition.hasHostedSurfaceDescriptor && !composition.hostedSurfaceDescriptor.debugSource.empty()
|
||||
? composition.hostedSurfaceDescriptor.debugSource.c_str()
|
||||
: kPreviewDebugSource);
|
||||
if (composition.nativeHostedPreview) {
|
||||
ImGui::Text(
|
||||
"Surface descriptor: %s | image published: %s | queued frame index: %zu",
|
||||
composition.hasHostedSurfaceDescriptor ? "yes" : "no",
|
||||
composition.showHostedSurfaceImage ? "yes" : "no",
|
||||
composition.hasHostedSurfaceDescriptor ? composition.hostedSurfaceDescriptor.queuedFrameIndex : 0u);
|
||||
if (composition.hasHostedSurfaceDescriptor) {
|
||||
ImGui::Text(
|
||||
"Surface: %ux%u | logical: %.0f x %.0f | rendered rect: %.0f, %.0f %.0f x %.0f",
|
||||
composition.hostedSurfaceDescriptor.image.surfaceWidth,
|
||||
composition.hostedSurfaceDescriptor.image.surfaceHeight,
|
||||
composition.hostedSurfaceDescriptor.logicalSize.width,
|
||||
composition.hostedSurfaceDescriptor.logicalSize.height,
|
||||
composition.hostedSurfaceDescriptor.image.renderedCanvasRect.x,
|
||||
composition.hostedSurfaceDescriptor.image.renderedCanvasRect.y,
|
||||
composition.hostedSurfaceDescriptor.image.renderedCanvasRect.width,
|
||||
composition.hostedSurfaceDescriptor.image.renderedCanvasRect.height);
|
||||
} else {
|
||||
ImGui::TextDisabled("No native surface descriptor has been published back yet.");
|
||||
}
|
||||
} else {
|
||||
ImGui::TextDisabled("Legacy path renders directly into the panel draw list. No native surface descriptor exists.");
|
||||
}
|
||||
|
||||
ImGui::SeparatorText("Runtime");
|
||||
ImGui::Text("Status: %s", stats.statusMessage.c_str());
|
||||
ImGui::Text(
|
||||
"Tree gen: %llu | elements: %zu | dirty roots: %zu",
|
||||
static_cast<unsigned long long>(stats.treeGeneration),
|
||||
stats.elementCount,
|
||||
stats.dirtyRootCount);
|
||||
ImGui::Text(
|
||||
"Draw lists: %zu | draw cmds: %zu | dependencies: %zu",
|
||||
stats.drawListCount,
|
||||
stats.commandCount,
|
||||
stats.dependencyCount);
|
||||
ImGui::Text(
|
||||
"Hovered: %s | Focused: %s",
|
||||
stats.hoveredElementId.empty() ? "none" : stats.hoveredElementId.c_str(),
|
||||
stats.focusedElementId.empty() ? "none" : stats.focusedElementId.c_str());
|
||||
ImGui::Text(
|
||||
"Last command: %s | Accent: %s",
|
||||
stats.lastCommandId.empty() ? "none" : stats.lastCommandId.c_str(),
|
||||
stats.accentEnabled ? "on" : "off");
|
||||
ImGui::Text(
|
||||
"Canvas: %.0f x %.0f",
|
||||
composition.input.canvasRect.width,
|
||||
composition.input.canvasRect.height);
|
||||
|
||||
ImGui::SeparatorText("Input");
|
||||
ImGui::Text(
|
||||
"Pointer: %.0f, %.0f | inside %s | down %s | pressed %s | released %s",
|
||||
composition.input.pointerPosition.x,
|
||||
composition.input.pointerPosition.y,
|
||||
composition.input.pointerInside ? "yes" : "no",
|
||||
composition.input.pointerDown ? "yes" : "no",
|
||||
composition.input.pointerPressed ? "yes" : "no",
|
||||
composition.input.pointerReleased ? "yes" : "no");
|
||||
ImGui::Text(
|
||||
"Focus %s | capture mouse %s | capture keyboard %s | text input %s | events %zu",
|
||||
composition.input.windowFocused ? "yes" : "no",
|
||||
composition.input.wantCaptureMouse ? "yes" : "no",
|
||||
composition.input.wantCaptureKeyboard ? "yes" : "no",
|
||||
composition.input.wantTextInput ? "yes" : "no",
|
||||
composition.input.events.size());
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::End();
|
||||
|
||||
if (!open) {
|
||||
SetVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
@@ -1,91 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Panel.h"
|
||||
|
||||
#include "XCUIBackend/XCUIHostedPreviewPresenter.h"
|
||||
#include "XCUIBackend/XCUIInputBridge.h"
|
||||
#include "XCUIBackend/XCUIPanelCanvasHost.h"
|
||||
#include "XCUIBackend/XCUIDemoRuntime.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
struct XCUIDemoPanelFrameComposeOptions {
|
||||
float canvasHeight = 0.0f;
|
||||
float canvasTopInset = 0.0f;
|
||||
bool hostedPreviewEnabled = true;
|
||||
bool showCanvasHud = true;
|
||||
bool showDebugRects = true;
|
||||
std::uint64_t timestampNanoseconds = 0;
|
||||
const char* canvasChildId = "XCUIDemoCanvasHost";
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot* inputSnapshot = nullptr;
|
||||
bool useDefaultPlaceholder = true;
|
||||
const char* placeholderTitle = nullptr;
|
||||
const char* placeholderSubtitle = nullptr;
|
||||
bool drawPreviewFrame = true;
|
||||
const char* badgeTitle = nullptr;
|
||||
const char* badgeSubtitle = nullptr;
|
||||
};
|
||||
|
||||
struct XCUIDemoPanelFrameComposition {
|
||||
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {};
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot inputSnapshot = {};
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta inputDelta = {};
|
||||
::XCEngine::Editor::XCUIBackend::XCUIDemoInputState input = {};
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameResult* frame = nullptr;
|
||||
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats previewStats = {};
|
||||
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {};
|
||||
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {};
|
||||
bool hostedPreviewEnabled = false;
|
||||
bool nativeHostedPreview = false;
|
||||
bool hasHostedSurfaceDescriptor = false;
|
||||
bool showHostedSurfaceImage = false;
|
||||
std::string previewPathLabel = {};
|
||||
std::string previewStateLabel = {};
|
||||
};
|
||||
|
||||
class XCUIDemoPanel : public Panel {
|
||||
public:
|
||||
explicit XCUIDemoPanel(
|
||||
::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource = nullptr);
|
||||
XCUIDemoPanel(
|
||||
::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource,
|
||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter,
|
||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost = nullptr);
|
||||
~XCUIDemoPanel() override = default;
|
||||
|
||||
void Render() override;
|
||||
const XCUIDemoPanelFrameComposition& ComposeFrame(
|
||||
const XCUIDemoPanelFrameComposeOptions& options = XCUIDemoPanelFrameComposeOptions());
|
||||
void SetHostedPreviewEnabled(bool enabled);
|
||||
void SetHostedPreviewPresenter(
|
||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter);
|
||||
void SetCanvasHost(
|
||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost);
|
||||
bool IsHostedPreviewEnabled() const { return m_hostedPreviewEnabled; }
|
||||
bool IsUsingNativeHostedPreview() const;
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIDemoFrameResult& GetFrameResult() const;
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& GetLastPreviewStats() const;
|
||||
const XCUIDemoPanelFrameComposition& GetLastFrameComposition() const;
|
||||
bool GetLastReloadSucceeded() const { return m_lastReloadSucceeded; }
|
||||
|
||||
private:
|
||||
bool m_lastReloadSucceeded = false;
|
||||
bool m_hostedPreviewEnabled = true;
|
||||
bool m_showCanvasHud = true;
|
||||
bool m_showDebugRects = true;
|
||||
::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* m_inputSource = nullptr;
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridge m_inputBridge;
|
||||
::XCEngine::Editor::XCUIBackend::XCUIDemoRuntime m_runtime;
|
||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> m_previewPresenter;
|
||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> m_canvasHost;
|
||||
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats m_lastPreviewStats = {};
|
||||
XCUIDemoPanelFrameComposition m_lastFrameComposition = {};
|
||||
};
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
@@ -1,240 +0,0 @@
|
||||
#include "XCUILayoutLabPanel.h"
|
||||
|
||||
#include "XCUIBackend/NullXCUIPanelCanvasHost.h"
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kPreviewDebugName[] = "XCUI Layout Lab";
|
||||
|
||||
bool ContainsPoint(const UI::UIRect& rect, const UI::UIPoint& point) {
|
||||
return point.x >= rect.x &&
|
||||
point.y >= rect.y &&
|
||||
point.x <= rect.x + rect.width &&
|
||||
point.y <= rect.y + rect.height;
|
||||
}
|
||||
|
||||
const char* GetPreviewPathLabel(
|
||||
const ::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter* previewPresenter) {
|
||||
if (previewPresenter == nullptr) {
|
||||
return "not injected";
|
||||
}
|
||||
|
||||
return previewPresenter->IsNativeQueued()
|
||||
? "native queued offscreen surface"
|
||||
: "hosted presenter";
|
||||
}
|
||||
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot BuildPassiveSnapshot(
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession& canvasSession,
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions& options) {
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot snapshot = {};
|
||||
snapshot.pointerPosition = canvasSession.pointerPosition;
|
||||
snapshot.pointerInside = options.hasPointerInsideOverride
|
||||
? options.pointerInsideOverride
|
||||
: (canvasSession.validCanvas && canvasSession.hovered);
|
||||
snapshot.windowFocused = options.windowFocused;
|
||||
snapshot.timestampNanoseconds = options.timestampNanoseconds;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot CapturePanelSnapshot(
|
||||
::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource,
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession& canvasSession) {
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions bridgeOptions = {};
|
||||
bridgeOptions.timestampNanoseconds = static_cast<std::uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
.count());
|
||||
|
||||
if (inputSource != nullptr) {
|
||||
bridgeOptions.hasPointerInsideOverride = true;
|
||||
bridgeOptions.windowFocused = canvasSession.windowFocused;
|
||||
const UI::UIPoint pointerPosition = inputSource->GetPointerPosition();
|
||||
bridgeOptions.pointerInsideOverride =
|
||||
canvasSession.validCanvas && ContainsPoint(canvasSession.canvasRect, pointerPosition);
|
||||
return inputSource->CaptureSnapshot(bridgeOptions);
|
||||
}
|
||||
|
||||
bridgeOptions.hasPointerInsideOverride = true;
|
||||
bridgeOptions.pointerInsideOverride = canvasSession.validCanvas && canvasSession.hovered;
|
||||
bridgeOptions.windowFocused = canvasSession.windowFocused;
|
||||
return BuildPassiveSnapshot(canvasSession, bridgeOptions);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void XCUILayoutLabPanel::Render() {
|
||||
ImGui::SetNextWindowSize(ImVec2(960.0f, 720.0f), ImGuiCond_Appearing);
|
||||
ImGui::SetNextWindowDockID(ImGui::GetID("XCNewEditorDockSpace"), ImGuiCond_Appearing);
|
||||
|
||||
bool open = true;
|
||||
if (!ImGui::Begin(GetName().c_str(), &open)) {
|
||||
ImGui::End();
|
||||
if (!open) {
|
||||
SetVisible(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::Button("Reload Layout Lab")) {
|
||||
m_lastReloadSucceeded = m_runtime.ReloadDocuments();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(m_lastReloadSucceeded ? "Reload: OK" : "Reload: Failed");
|
||||
ImGui::Separator();
|
||||
|
||||
const float diagnosticsHeight = 240.0f;
|
||||
const ImVec2 hostRegion = ImGui::GetContentRegionAvail();
|
||||
const float canvasHeight = (std::max)(140.0f, hostRegion.y - diagnosticsHeight);
|
||||
|
||||
if (m_canvasHost == nullptr) {
|
||||
m_canvasHost = ::XCEngine::Editor::XCUIBackend::CreateNullXCUIPanelCanvasHost();
|
||||
}
|
||||
|
||||
const bool nativeHostedPreview = IsUsingNativeHostedPreview();
|
||||
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {};
|
||||
const bool showHostedSurfaceImage =
|
||||
nativeHostedPreview &&
|
||||
m_previewPresenter != nullptr &&
|
||||
m_previewPresenter->TryGetSurfaceImage(kPreviewDebugName, hostedSurfaceImage);
|
||||
const char* const previewPathLabel = GetPreviewPathLabel(m_previewPresenter.get());
|
||||
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest canvasRequest = {};
|
||||
canvasRequest.childId = "XCUILayoutLabCanvasHost";
|
||||
canvasRequest.height = canvasHeight;
|
||||
canvasRequest.showSurfaceImage = showHostedSurfaceImage;
|
||||
canvasRequest.surfaceImage = hostedSurfaceImage;
|
||||
canvasRequest.placeholderTitle =
|
||||
nativeHostedPreview ? "Native layout preview pending" : "Injected layout canvas host";
|
||||
canvasRequest.placeholderSubtitle =
|
||||
nativeHostedPreview
|
||||
? "Waiting for native queued render output to publish back into the layout sandbox."
|
||||
: "Inject a concrete canvas host to render the layout sandbox inside this panel.";
|
||||
canvasRequest.badgeTitle = "Layout Lab";
|
||||
canvasRequest.badgeSubtitle = previewPathLabel;
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession =
|
||||
m_canvasHost->BeginCanvas(canvasRequest);
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot snapshot =
|
||||
CapturePanelSnapshot(m_inputSource, canvasSession);
|
||||
const XCUILayoutLabFrameCompositionRequest compositionRequest = {
|
||||
canvasSession,
|
||||
snapshot
|
||||
};
|
||||
const XCUILayoutLabFrameComposition& composition = ComposeFrame(compositionRequest);
|
||||
m_canvasHost->EndCanvas();
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::BeginChild("XCUILayoutLabDiagnostics", ImVec2(0.0f, 0.0f), false, ImGuiWindowFlags_NoScrollbar);
|
||||
ImGui::SeparatorText("Preview");
|
||||
ImGui::Text(
|
||||
"Path: %s | state: %s",
|
||||
composition.previewPathLabel.c_str(),
|
||||
composition.previewStateLabel.c_str());
|
||||
ImGui::Text(
|
||||
"Presenter: presented %s | submit->native %s",
|
||||
composition.previewStats.presented ? "yes" : "no",
|
||||
composition.previewStats.queuedToNativePass ? "yes" : "no");
|
||||
ImGui::Text(
|
||||
"Submitted: %zu lists / %zu cmds | Flushed: %zu lists / %zu cmds",
|
||||
composition.previewStats.submittedDrawListCount,
|
||||
composition.previewStats.submittedCommandCount,
|
||||
composition.previewStats.flushedDrawListCount,
|
||||
composition.previewStats.flushedCommandCount);
|
||||
ImGui::TextWrapped("Source: %s", composition.previewSourceLabel.c_str());
|
||||
if (nativeHostedPreview) {
|
||||
ImGui::Text(
|
||||
"Surface descriptor: %s | image published: %s | queued frame index: %zu",
|
||||
composition.hasHostedSurfaceDescriptor ? "yes" : "no",
|
||||
composition.showHostedSurfaceImage ? "yes" : "no",
|
||||
composition.hasHostedSurfaceDescriptor ? composition.hostedSurfaceDescriptor.queuedFrameIndex : 0u);
|
||||
if (composition.hasHostedSurfaceDescriptor) {
|
||||
ImGui::Text(
|
||||
"Surface: %ux%u | logical: %.0f x %.0f | rendered rect: %.0f, %.0f %.0f x %.0f",
|
||||
composition.hostedSurfaceDescriptor.image.surfaceWidth,
|
||||
composition.hostedSurfaceDescriptor.image.surfaceHeight,
|
||||
composition.hostedSurfaceDescriptor.logicalSize.width,
|
||||
composition.hostedSurfaceDescriptor.logicalSize.height,
|
||||
composition.hostedSurfaceDescriptor.image.renderedCanvasRect.x,
|
||||
composition.hostedSurfaceDescriptor.image.renderedCanvasRect.y,
|
||||
composition.hostedSurfaceDescriptor.image.renderedCanvasRect.width,
|
||||
composition.hostedSurfaceDescriptor.image.renderedCanvasRect.height);
|
||||
} else {
|
||||
ImGui::TextDisabled("No native surface descriptor has been published back yet.");
|
||||
}
|
||||
} else {
|
||||
ImGui::TextDisabled("No native surface descriptor is available without a native queued presenter.");
|
||||
}
|
||||
|
||||
ImGui::SeparatorText("Runtime");
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameStats& stats =
|
||||
composition.frameResult != nullptr
|
||||
? composition.frameResult->stats
|
||||
: m_runtime.GetFrameResult().stats;
|
||||
ImGui::Text("Status: %s", stats.statusMessage.c_str());
|
||||
ImGui::Text(
|
||||
"Rows: %zu | Columns: %zu | Overlays: %zu | Scroll views: %zu",
|
||||
stats.rowCount,
|
||||
stats.columnCount,
|
||||
stats.overlayCount,
|
||||
stats.scrollViewCount);
|
||||
ImGui::Text(
|
||||
"Tree items: %zu (%zu expanded) | Property sections: %zu (%zu expanded)",
|
||||
stats.treeItemCount,
|
||||
stats.expandedTreeItemCount,
|
||||
stats.propertySectionCount,
|
||||
stats.expandedPropertySectionCount);
|
||||
ImGui::Text(
|
||||
"Draw lists: %zu | Draw commands: %zu",
|
||||
stats.drawListCount,
|
||||
stats.commandCount);
|
||||
ImGui::Text(
|
||||
"Command types: fill %zu | outline %zu | text %zu | image %zu | clip %zu/%zu",
|
||||
stats.filledRectCommandCount,
|
||||
stats.rectOutlineCommandCount,
|
||||
stats.textCommandCount,
|
||||
stats.imageCommandCount,
|
||||
stats.clipPushCommandCount,
|
||||
stats.clipPopCommandCount);
|
||||
ImGui::Text(
|
||||
"Native overlay: %s | supported %zu | unsupported %zu",
|
||||
stats.nativeOverlayReady ? "preflight OK" : "preflight issues",
|
||||
stats.nativeSupportedCommandCount,
|
||||
stats.nativeUnsupportedCommandCount);
|
||||
ImGui::TextWrapped(
|
||||
"Native note: %s",
|
||||
stats.nativeOverlayStatusMessage.empty() ? "none" : stats.nativeOverlayStatusMessage.c_str());
|
||||
ImGui::Text(
|
||||
"Hovered: %s | Selected: %s | canvas: %.0f x %.0f",
|
||||
stats.hoveredElementId.empty() ? "none" : stats.hoveredElementId.c_str(),
|
||||
stats.selectedElementId.empty() ? "none" : stats.selectedElementId.c_str(),
|
||||
composition.inputState.canvasRect.width,
|
||||
composition.inputState.canvasRect.height);
|
||||
|
||||
ImGui::SeparatorText("Input");
|
||||
ImGui::Text(
|
||||
"Pointer: %.0f, %.0f | inside %s | pressed %s",
|
||||
composition.inputState.pointerPosition.x,
|
||||
composition.inputState.pointerPosition.y,
|
||||
composition.inputState.pointerInside ? "yes" : "no",
|
||||
composition.inputState.pointerPressed ? "yes" : "no");
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::End();
|
||||
|
||||
if (!open) {
|
||||
SetVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
@@ -1,78 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Panel.h"
|
||||
|
||||
#include "XCUIBackend/XCUIHostedPreviewPresenter.h"
|
||||
#include "XCUIBackend/XCUIInputBridge.h"
|
||||
#include "XCUIBackend/XCUIPanelCanvasHost.h"
|
||||
#include "XCUIBackend/XCUILayoutLabRuntime.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
|
||||
struct XCUILayoutLabFrameCompositionRequest {
|
||||
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {};
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot inputSnapshot = {};
|
||||
};
|
||||
|
||||
struct XCUILayoutLabFrameComposition {
|
||||
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {};
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot inputSnapshot = {};
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta inputDelta = {};
|
||||
::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState inputState = {};
|
||||
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats previewStats = {};
|
||||
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceDescriptor hostedSurfaceDescriptor = {};
|
||||
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewSurfaceImage hostedSurfaceImage = {};
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult* frameResult = nullptr;
|
||||
std::string previewPathLabel = {};
|
||||
std::string previewStateLabel = {};
|
||||
std::string previewSourceLabel = {};
|
||||
bool hostedPreviewEnabled = true;
|
||||
bool nativeHostedPreview = false;
|
||||
bool hasHostedSurfaceDescriptor = false;
|
||||
bool showHostedSurfaceImage = false;
|
||||
};
|
||||
|
||||
class XCUILayoutLabPanel : public Panel {
|
||||
public:
|
||||
explicit XCUILayoutLabPanel(
|
||||
::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource = nullptr);
|
||||
XCUILayoutLabPanel(
|
||||
::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* inputSource,
|
||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter,
|
||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost = nullptr);
|
||||
~XCUILayoutLabPanel() override = default;
|
||||
|
||||
void Render() override;
|
||||
const XCUILayoutLabFrameComposition& ComposeFrame(
|
||||
const XCUILayoutLabFrameCompositionRequest& request);
|
||||
void SetHostedPreviewEnabled(bool enabled);
|
||||
void SetHostedPreviewPresenter(
|
||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> previewPresenter);
|
||||
void SetCanvasHost(
|
||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> canvasHost);
|
||||
bool IsHostedPreviewEnabled() const { return m_hostedPreviewEnabled; }
|
||||
bool IsUsingNativeHostedPreview() const;
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult& GetFrameResult() const;
|
||||
const XCUILayoutLabFrameComposition& GetLastFrameComposition() const;
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats& GetLastPreviewStats() const;
|
||||
bool GetLastReloadSucceeded() const { return m_lastReloadSucceeded; }
|
||||
bool TryGetElementRect(const std::string& elementId, ::XCEngine::UI::UIRect& outRect) const;
|
||||
|
||||
private:
|
||||
bool m_lastReloadSucceeded = false;
|
||||
bool m_hostedPreviewEnabled = true;
|
||||
::XCEngine::Editor::XCUIBackend::IXCUIInputSnapshotSource* m_inputSource = nullptr;
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridge m_inputBridge;
|
||||
::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_runtime;
|
||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter> m_previewPresenter;
|
||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost> m_canvasHost;
|
||||
::XCEngine::Editor::XCUIBackend::XCUIHostedPreviewStats m_lastPreviewStats = {};
|
||||
XCUILayoutLabFrameComposition m_lastFrameComposition = {};
|
||||
};
|
||||
|
||||
} // namespace NewEditor
|
||||
} // namespace XCEngine
|
||||
21
new_editor/ui/schemas/editor_inspector_shell.xcschema
Normal file
21
new_editor/ui/schemas/editor_inspector_shell.xcschema
Normal file
@@ -0,0 +1,21 @@
|
||||
<Schema name="EditorInspectorShell">
|
||||
<Element tag="InspectorPanel" allowUnknownAttributes="true">
|
||||
<Attribute name="vm" type="string" />
|
||||
<Attribute name="schema" type="document" documentKind="schema" />
|
||||
|
||||
<Element tag="Section" allowUnknownAttributes="true">
|
||||
<Attribute name="title" type="string" />
|
||||
<Attribute name="subtitle" type="string" />
|
||||
|
||||
<Element tag="Field" allowUnknownAttributes="true">
|
||||
<Attribute name="label" type="string" />
|
||||
<Attribute name="binding" type="string" />
|
||||
<Attribute
|
||||
name="widget"
|
||||
type="enum"
|
||||
values="Vector3Field,FloatField,EnumField,AssetField" />
|
||||
<Attribute name="step" type="number" />
|
||||
</Element>
|
||||
</Element>
|
||||
</Element>
|
||||
</Schema>
|
||||
32
new_editor/ui/themes/editor_shell.xctheme
Normal file
32
new_editor/ui/themes/editor_shell.xctheme
Normal file
@@ -0,0 +1,32 @@
|
||||
<Theme name="EditorShellTheme">
|
||||
<Tokens>
|
||||
<Color name="color.bg.workspace" value="#1C1C1C" />
|
||||
<Color name="color.bg.panel" value="#292929" />
|
||||
<Color name="color.bg.accent" value="#3A3A3A" />
|
||||
<Color name="color.bg.selection" value="#4A4A4A" />
|
||||
<Color name="color.text.primary" value="#EEEEEE" />
|
||||
<Color name="color.text.muted" value="#B0B0B0" />
|
||||
<Spacing name="space.panel" value="12" />
|
||||
<Spacing name="space.shell" value="18" />
|
||||
<Radius name="radius.panel" value="10" />
|
||||
<Radius name="radius.control" value="8" />
|
||||
</Tokens>
|
||||
|
||||
<Widgets>
|
||||
<Widget type="View" style="EditorWorkspace">
|
||||
<Property name="background" value="color.bg.workspace" />
|
||||
<Property name="padding" value="space.shell" />
|
||||
</Widget>
|
||||
|
||||
<Widget type="Card" style="EditorPanel">
|
||||
<Property name="background" value="color.bg.panel" />
|
||||
<Property name="radius" value="radius.panel" />
|
||||
<Property name="padding" value="space.panel" />
|
||||
</Widget>
|
||||
|
||||
<Widget type="Button" style="EditorChip">
|
||||
<Property name="background" value="color.bg.selection" />
|
||||
<Property name="radius" value="radius.control" />
|
||||
</Widget>
|
||||
</Widgets>
|
||||
</Theme>
|
||||
82
new_editor/ui/views/editor_shell.xcui
Normal file
82
new_editor/ui/views/editor_shell.xcui
Normal file
@@ -0,0 +1,82 @@
|
||||
<View
|
||||
name="NewEditorShell"
|
||||
theme="../themes/editor_shell.xctheme">
|
||||
<Column padding="18" gap="14">
|
||||
<Card
|
||||
title="XCUI Editor Sandbox"
|
||||
subtitle="Editor-layer proving ground | direct native renderer | hot reload"
|
||||
tone="accent"
|
||||
height="86">
|
||||
<Row gap="10">
|
||||
<Button text="Shell" />
|
||||
<Button text="Panels" />
|
||||
<Button text="Schema First" />
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
<Row gap="14" height="fill">
|
||||
<Card title="Hierarchy" subtitle="scene graph" width="280">
|
||||
<Column gap="8">
|
||||
<Text text="Sandbox_City.xcscene" />
|
||||
<Button text="World" />
|
||||
<Button text="Main Camera" />
|
||||
<Button text="Directional Light" />
|
||||
<Button text="Player" tone="accent" />
|
||||
<Button text="WeaponSocket" />
|
||||
<Button text="FX_Trail" />
|
||||
</Column>
|
||||
</Card>
|
||||
|
||||
<Column width="fill" gap="14">
|
||||
<Card
|
||||
title="Scene"
|
||||
subtitle="ViewportSlot comes later | shell composition first"
|
||||
tone="accent-alt"
|
||||
height="fill">
|
||||
<Column gap="10">
|
||||
<Row gap="10">
|
||||
<Button text="Translate" />
|
||||
<Button text="Rotate" />
|
||||
<Button text="Scale" />
|
||||
</Row>
|
||||
<Text text="This sandbox stays native-rendered and does not host ImGui." />
|
||||
<Text text="Use it to iterate shell chrome, panel composition, and authored XCUI." />
|
||||
<Button text="Selection: Player" tone="accent" />
|
||||
<Button text="Camera: Perspective" />
|
||||
</Column>
|
||||
</Card>
|
||||
|
||||
<Card title="Console" subtitle="runtime smoke log" height="220">
|
||||
<Column gap="8">
|
||||
<Text text="Info XCUI authored screen loaded." />
|
||||
<Text text="Info Theme + schema resources are tracked for reload." />
|
||||
<Text text="Warn Viewport host stays out of scope in this phase." />
|
||||
<Text text="Todo Replace shell placeholders with Editor widgets." />
|
||||
</Column>
|
||||
</Card>
|
||||
</Column>
|
||||
|
||||
<Card
|
||||
title="Inspector"
|
||||
subtitle="schema-first scaffold"
|
||||
width="336"
|
||||
schema="../schemas/editor_inspector_shell.xcschema">
|
||||
<Column gap="8">
|
||||
<Text text="Transform" />
|
||||
<Button text="Position 12.0, 1.8, -4.0" />
|
||||
<Button text="Rotation 0.0, 36.5, 0.0" />
|
||||
<Button text="Scale 1.0, 1.0, 1.0" />
|
||||
<Text text="Character" />
|
||||
<Button text="State Locomotion" />
|
||||
<Button text="MoveSpeed 6.4" />
|
||||
<Button text="JumpHeight 1.3" />
|
||||
</Column>
|
||||
</Card>
|
||||
</Row>
|
||||
|
||||
<Card
|
||||
title="Status"
|
||||
subtitle="No ImGui host | authored XCUI drives the shell | fallback stays for resilience"
|
||||
height="58" />
|
||||
</Column>
|
||||
</View>
|
||||
@@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user