ui: add vector4 editor field validation
This commit is contained in:
@@ -34,6 +34,9 @@ endif()
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/vector3_field_basic/CMakeLists.txt")
|
||||
add_subdirectory(vector3_field_basic)
|
||||
endif()
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/vector4_field_basic/CMakeLists.txt")
|
||||
add_subdirectory(vector4_field_basic)
|
||||
endif()
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/enum_field_basic/CMakeLists.txt")
|
||||
add_subdirectory(enum_field_basic)
|
||||
endif()
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <windowsx.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -262,6 +263,18 @@ UIEditorPropertyGridField MakeEnumField(
|
||||
return field;
|
||||
}
|
||||
|
||||
UIEditorPropertyGridField MakeVector4Field(
|
||||
std::string id,
|
||||
std::string label,
|
||||
std::array<double, 4u> values) {
|
||||
UIEditorPropertyGridField field = {};
|
||||
field.fieldId = std::move(id);
|
||||
field.label = std::move(label);
|
||||
field.kind = UIEditorPropertyGridFieldKind::Vector4;
|
||||
field.vector4Value.values = values;
|
||||
return field;
|
||||
}
|
||||
|
||||
std::vector<UIEditorPropertyGridSection> BuildSections() {
|
||||
return {
|
||||
{
|
||||
@@ -269,6 +282,7 @@ std::vector<UIEditorPropertyGridSection> BuildSections() {
|
||||
"Inspector",
|
||||
{
|
||||
MakeBoolField("enabled", "Enabled", true),
|
||||
MakeVector4Field("rotation", "Rotation", { 0.0, 15.0, 0.0, 1.0 }),
|
||||
MakeNumberField("render_queue", "Render Queue", 2000.0),
|
||||
MakeEnumField("render_mode", "Render Mode", { "Opaque", "Cutout", "Fade" }, 0u),
|
||||
MakeTextField("tag", "Tag", "Player")
|
||||
@@ -871,7 +885,7 @@ private:
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
|
||||
"2. 点击 value host:Bool toggle、Number/Text edit、Enum popup。",
|
||||
"2. 点击 value host:Bool toggle、Vector4 宿主排版、Number/Text edit、Enum popup。",
|
||||
shellPalette.textPrimary,
|
||||
shellMetrics.bodyFontSize);
|
||||
drawList.AddText(
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
add_executable(editor_ui_vector4_field_basic_validation WIN32
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_include_directories(editor_ui_vector4_field_basic_validation PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/tests/UI/Editor/integration/shared/src
|
||||
${CMAKE_SOURCE_DIR}/new_editor/include
|
||||
${CMAKE_SOURCE_DIR}/new_editor/app
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
)
|
||||
|
||||
target_compile_definitions(editor_ui_vector4_field_basic_validation PRIVATE
|
||||
UNICODE
|
||||
_UNICODE
|
||||
XCENGINE_EDITOR_UI_TESTS_REPO_ROOT="${XCENGINE_EDITOR_UI_TESTS_REPO_ROOT_PATH}"
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(editor_ui_vector4_field_basic_validation PRIVATE /utf-8 /FS)
|
||||
set_property(TARGET editor_ui_vector4_field_basic_validation PROPERTY
|
||||
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||
endif()
|
||||
|
||||
target_link_libraries(editor_ui_vector4_field_basic_validation PRIVATE
|
||||
XCUIEditorLib
|
||||
XCUIEditorHost
|
||||
)
|
||||
|
||||
set_target_properties(editor_ui_vector4_field_basic_validation PROPERTIES
|
||||
OUTPUT_NAME "XCUIEditorVector4FieldBasicValidation"
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
456
tests/UI/Editor/integration/shell/vector4_field_basic/main.cpp
Normal file
456
tests/UI/Editor/integration/shell/vector4_field_basic/main.cpp
Normal file
@@ -0,0 +1,456 @@
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEditor/Core/UIEditorTheme.h>
|
||||
#include <XCEditor/Core/UIEditorVector4FieldInteraction.h>
|
||||
#include <XCEditor/Widgets/UIEditorVector4Field.h>
|
||||
#include "EditorValidationTheme.h"
|
||||
#include "Host/AutoScreenshot.h"
|
||||
#include "Host/NativeRenderer.h"
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifndef XCENGINE_EDITOR_UI_TESTS_REPO_ROOT
|
||||
#define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "."
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Input::KeyCode;
|
||||
using XCEngine::UI::UIDrawData;
|
||||
using XCEngine::UI::UIDrawList;
|
||||
using XCEngine::UI::UIInputEvent;
|
||||
using XCEngine::UI::UIInputEventType;
|
||||
using XCEngine::UI::UIPoint;
|
||||
using XCEngine::UI::UIPointerButton;
|
||||
using XCEngine::UI::UIRect;
|
||||
using XCEngine::UI::Editor::Host::AutoScreenshotController;
|
||||
using XCEngine::UI::Editor::Host::NativeRenderer;
|
||||
using XCEngine::UI::Editor::UIEditorVector4FieldInteractionFrame;
|
||||
using XCEngine::UI::Editor::UIEditorVector4FieldInteractionResult;
|
||||
using XCEngine::UI::Editor::UIEditorVector4FieldInteractionState;
|
||||
using XCEngine::UI::Editor::UpdateUIEditorVector4FieldInteraction;
|
||||
using XCEngine::UI::Editor::Widgets::AppendUIEditorVector4Field;
|
||||
using XCEngine::UI::Editor::Widgets::FormatUIEditorVector4FieldComponentValue;
|
||||
using XCEngine::UI::Editor::Widgets::HitTestUIEditorVector4Field;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorVector4FieldHitTarget;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorVector4FieldHitTargetKind;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorVector4FieldSpec;
|
||||
namespace Style = XCEngine::UI::Style;
|
||||
|
||||
constexpr const wchar_t* kWindowClassName = L"XCUIEditorVector4FieldBasicValidation";
|
||||
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Vector4Field Basic";
|
||||
|
||||
struct ScenarioLayout {
|
||||
UIRect introRect = {};
|
||||
UIRect stateRect = {};
|
||||
UIRect previewRect = {};
|
||||
UIRect inspectorRect = {};
|
||||
UIRect inspectorHeaderRect = {};
|
||||
UIRect sectionRect = {};
|
||||
UIRect fieldRect = {};
|
||||
};
|
||||
|
||||
std::filesystem::path ResolveRepoRootPath() {
|
||||
std::string root = XCENGINE_EDITOR_UI_TESTS_REPO_ROOT;
|
||||
if (root.size() >= 2u && root.front() == '"' && root.back() == '"') {
|
||||
root = root.substr(1u, root.size() - 2u);
|
||||
}
|
||||
return std::filesystem::path(root).lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveValidationThemePath() {
|
||||
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
|
||||
.lexically_normal();
|
||||
}
|
||||
|
||||
std::int32_t MapVectorKey(UINT keyCode) {
|
||||
switch (keyCode) {
|
||||
case VK_LEFT: return static_cast<std::int32_t>(KeyCode::Left);
|
||||
case VK_RIGHT: return static_cast<std::int32_t>(KeyCode::Right);
|
||||
case VK_UP: return static_cast<std::int32_t>(KeyCode::Up);
|
||||
case VK_DOWN: return static_cast<std::int32_t>(KeyCode::Down);
|
||||
case VK_HOME: return static_cast<std::int32_t>(KeyCode::Home);
|
||||
case VK_END: return static_cast<std::int32_t>(KeyCode::End);
|
||||
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);
|
||||
default: return static_cast<std::int32_t>(KeyCode::None);
|
||||
}
|
||||
}
|
||||
|
||||
ScenarioLayout BuildScenarioLayout(
|
||||
float width,
|
||||
float height,
|
||||
const XCEngine::Tests::EditorUI::EditorValidationShellMetrics& shellMetrics) {
|
||||
const float margin = shellMetrics.margin;
|
||||
constexpr float leftWidth = 470.0f;
|
||||
const float gap = shellMetrics.gap;
|
||||
|
||||
ScenarioLayout layout = {};
|
||||
layout.introRect = UIRect(margin, margin, leftWidth, 230.0f);
|
||||
layout.stateRect = UIRect(
|
||||
margin,
|
||||
layout.introRect.y + layout.introRect.height + gap,
|
||||
leftWidth,
|
||||
(std::max)(280.0f, height - (layout.introRect.y + layout.introRect.height + gap) - margin));
|
||||
layout.previewRect = UIRect(
|
||||
leftWidth + margin * 2.0f,
|
||||
margin,
|
||||
(std::max)(480.0f, width - leftWidth - margin * 3.0f),
|
||||
height - margin * 2.0f);
|
||||
layout.inspectorRect = UIRect(layout.previewRect.x + 18.0f, layout.previewRect.y + 54.0f, 460.0f, 174.0f);
|
||||
layout.inspectorHeaderRect = UIRect(layout.inspectorRect.x, layout.inspectorRect.y, layout.inspectorRect.width, 24.0f);
|
||||
layout.sectionRect = UIRect(layout.inspectorRect.x, layout.inspectorRect.y + 24.0f, layout.inspectorRect.width, 24.0f);
|
||||
layout.fieldRect = UIRect(layout.inspectorRect.x, layout.sectionRect.y + 26.0f, layout.inspectorRect.width, 22.0f);
|
||||
return layout;
|
||||
}
|
||||
|
||||
UIInputEvent MakePointerEvent(
|
||||
UIInputEventType type,
|
||||
const UIPoint& position,
|
||||
UIPointerButton button = UIPointerButton::None) {
|
||||
UIInputEvent event = {};
|
||||
event.type = type;
|
||||
event.position = position;
|
||||
event.pointerButton = button;
|
||||
return event;
|
||||
}
|
||||
|
||||
UIInputEvent MakeKeyEvent(std::int32_t keyCode, bool shift = false) {
|
||||
UIInputEvent event = {};
|
||||
event.type = UIInputEventType::KeyDown;
|
||||
event.keyCode = keyCode;
|
||||
event.modifiers.shift = shift;
|
||||
return event;
|
||||
}
|
||||
|
||||
UIInputEvent MakeCharacterEvent(wchar_t character) {
|
||||
UIInputEvent event = {};
|
||||
event.type = UIInputEventType::Character;
|
||||
event.character = static_cast<std::uint32_t>(character);
|
||||
return event;
|
||||
}
|
||||
|
||||
std::string DescribeHitTarget(const UIEditorVector4FieldHitTarget& hitTarget) {
|
||||
if (hitTarget.kind == UIEditorVector4FieldHitTargetKind::Component) {
|
||||
return std::string("component_") + std::to_string(hitTarget.componentIndex);
|
||||
}
|
||||
if (hitTarget.kind == UIEditorVector4FieldHitTargetKind::Row) {
|
||||
return "row";
|
||||
}
|
||||
return "none";
|
||||
}
|
||||
|
||||
std::string DescribeSelectedComponent(std::size_t componentIndex) {
|
||||
switch (componentIndex) {
|
||||
case 0u: return "X";
|
||||
case 1u: return "Y";
|
||||
case 2u: return "Z";
|
||||
case 3u: return "W";
|
||||
default: return "none";
|
||||
}
|
||||
}
|
||||
|
||||
class ScenarioApp {
|
||||
public:
|
||||
int Run(HINSTANCE hInstance, int nCmdShow) {
|
||||
if (!Initialize(hInstance, nCmdShow)) {
|
||||
Shutdown();
|
||||
return 1;
|
||||
}
|
||||
MSG message = {};
|
||||
while (message.message != WM_QUIT) {
|
||||
if (PeekMessageW(&message, nullptr, 0U, 0U, PM_REMOVE)) {
|
||||
TranslateMessage(&message);
|
||||
DispatchMessageW(&message);
|
||||
continue;
|
||||
}
|
||||
RenderFrame();
|
||||
Sleep(8);
|
||||
}
|
||||
Shutdown();
|
||||
return static_cast<int>(message.wParam);
|
||||
}
|
||||
|
||||
private:
|
||||
static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
if (message == WM_NCCREATE) {
|
||||
const auto* createStruct = reinterpret_cast<CREATESTRUCTW*>(lParam);
|
||||
auto* app = reinterpret_cast<ScenarioApp*>(createStruct->lpCreateParams);
|
||||
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(app));
|
||||
return TRUE;
|
||||
}
|
||||
auto* app = reinterpret_cast<ScenarioApp*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
||||
switch (message) {
|
||||
case WM_SIZE:
|
||||
if (app != nullptr && wParam != SIZE_MINIMIZED) {
|
||||
app->m_renderer.Resize(static_cast<UINT>(LOWORD(lParam)), static_cast<UINT>(HIWORD(lParam)));
|
||||
}
|
||||
return 0;
|
||||
case WM_MOUSEMOVE:
|
||||
if (app != nullptr) {
|
||||
app->m_mousePosition = UIPoint(static_cast<float>(GET_X_LPARAM(lParam)), static_cast<float>(GET_Y_LPARAM(lParam)));
|
||||
TRACKMOUSEEVENT trackEvent = { sizeof(trackEvent), TME_LEAVE, hwnd, 0 };
|
||||
TrackMouseEvent(&trackEvent);
|
||||
app->PumpEvents({ MakePointerEvent(UIInputEventType::PointerMove, app->m_mousePosition) });
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case WM_MOUSELEAVE:
|
||||
if (app != nullptr) {
|
||||
app->m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
app->PumpEvents({ MakePointerEvent(UIInputEventType::PointerLeave, app->m_mousePosition) });
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_LBUTTONUP:
|
||||
if (app != nullptr) {
|
||||
app->m_mousePosition = UIPoint(static_cast<float>(GET_X_LPARAM(lParam)), static_cast<float>(GET_Y_LPARAM(lParam)));
|
||||
const UIInputEventType type =
|
||||
message == WM_LBUTTONDOWN ? UIInputEventType::PointerButtonDown : UIInputEventType::PointerButtonUp;
|
||||
app->UpdateResultText(app->PumpEvents({ MakePointerEvent(type, app->m_mousePosition, UIPointerButton::Left) }));
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case WM_KEYDOWN:
|
||||
case WM_SYSKEYDOWN:
|
||||
if (app != nullptr) {
|
||||
if (wParam == VK_F12) {
|
||||
app->m_autoScreenshot.RequestCapture("manual_f12");
|
||||
app->m_lastResult = "已请求截图,输出到 captures/latest.png";
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
return 0;
|
||||
}
|
||||
if (wParam == 'R') {
|
||||
app->ResetScenario();
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
return 0;
|
||||
}
|
||||
if (wParam == VK_F6) {
|
||||
app->UpdateResultText(app->PumpEvents({ MakePointerEvent(UIInputEventType::FocusLost, app->m_mousePosition) }));
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
return 0;
|
||||
}
|
||||
const std::int32_t keyCode = MapVectorKey(static_cast<UINT>(wParam));
|
||||
if (keyCode != static_cast<std::int32_t>(KeyCode::None)) {
|
||||
app->UpdateResultText(app->PumpEvents({ MakeKeyEvent(keyCode, (GetKeyState(VK_SHIFT) & 0x8000) != 0) }));
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WM_CHAR:
|
||||
if (app != nullptr && wParam >= 32) {
|
||||
app->UpdateResultText(app->PumpEvents({ MakeCharacterEvent(static_cast<wchar_t>(wParam)) }));
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case WM_PAINT:
|
||||
if (app != nullptr) {
|
||||
PAINTSTRUCT paintStruct = {};
|
||||
BeginPaint(hwnd, &paintStruct);
|
||||
app->RenderFrame();
|
||||
EndPaint(hwnd, &paintStruct);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case WM_ERASEBKGND:
|
||||
return 1;
|
||||
case WM_DESTROY:
|
||||
if (app != nullptr) {
|
||||
app->m_hwnd = nullptr;
|
||||
}
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return DefWindowProcW(hwnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
bool Initialize(HINSTANCE hInstance, int nCmdShow) {
|
||||
WNDCLASSEXW windowClass = {};
|
||||
windowClass.cbSize = sizeof(windowClass);
|
||||
windowClass.style = CS_HREDRAW | CS_VREDRAW;
|
||||
windowClass.lpfnWndProc = &ScenarioApp::WndProc;
|
||||
windowClass.hInstance = hInstance;
|
||||
windowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW);
|
||||
windowClass.lpszClassName = kWindowClassName;
|
||||
m_windowClassAtom = RegisterClassExW(&windowClass);
|
||||
if (m_windowClassAtom == 0) {
|
||||
return false;
|
||||
}
|
||||
m_hwnd = CreateWindowExW(0, kWindowClassName, kWindowTitle, WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 1560, 920, nullptr, nullptr, hInstance, this);
|
||||
if (m_hwnd == nullptr || !m_renderer.Initialize(m_hwnd)) {
|
||||
return false;
|
||||
}
|
||||
ShowWindow(m_hwnd, nCmdShow);
|
||||
UpdateWindow(m_hwnd);
|
||||
m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/vector4_field_basic/captures";
|
||||
m_autoScreenshot.Initialize(m_captureRoot);
|
||||
const auto themeLoad = XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
|
||||
m_theme = themeLoad.theme;
|
||||
m_themeStatus = themeLoad.succeeded ? "loaded" : (themeLoad.error.empty() ? "fallback" : themeLoad.error);
|
||||
ResetScenario();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
m_autoScreenshot.Shutdown();
|
||||
m_renderer.Shutdown();
|
||||
if (m_hwnd != nullptr && IsWindow(m_hwnd)) {
|
||||
DestroyWindow(m_hwnd);
|
||||
}
|
||||
if (m_windowClassAtom != 0) {
|
||||
UnregisterClassW(kWindowClassName, GetModuleHandleW(nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
void ResetScenario() {
|
||||
m_spec = {};
|
||||
m_spec.fieldId = "rotation";
|
||||
m_spec.label = "Rotation";
|
||||
m_spec.values = { 1.25, -2.5, 4.75, 0.5 };
|
||||
m_spec.step = 0.25;
|
||||
m_spec.minValue = -10.0;
|
||||
m_spec.maxValue = 10.0;
|
||||
m_interactionState = {};
|
||||
m_interactionState.vector4FieldState.focused = true;
|
||||
m_interactionState.vector4FieldState.selectedComponentIndex = 0u;
|
||||
m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
m_lastResult = "已重置到默认 Vector4Field 状态";
|
||||
PumpEvents({});
|
||||
}
|
||||
|
||||
UIEditorVector4FieldInteractionResult PumpEvents(std::vector<UIInputEvent> events) {
|
||||
RECT clientRect = {};
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const auto layout = BuildScenarioLayout(
|
||||
static_cast<float>((std::max)(1L, clientRect.right - clientRect.left)),
|
||||
static_cast<float>((std::max)(1L, clientRect.bottom - clientRect.top)),
|
||||
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
|
||||
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector4FieldMetrics(m_theme);
|
||||
m_frame = UpdateUIEditorVector4FieldInteraction(
|
||||
m_interactionState,
|
||||
m_spec,
|
||||
layout.fieldRect,
|
||||
std::move(events),
|
||||
metrics);
|
||||
return m_frame.result;
|
||||
}
|
||||
|
||||
void UpdateResultText(const UIEditorVector4FieldInteractionResult& result) {
|
||||
if (result.editCommitRejected) {
|
||||
m_lastResult = "提交失败,当前文本不是合法数字";
|
||||
} else if (result.editCommitted) {
|
||||
m_lastResult = "已提交 " + DescribeSelectedComponent(result.changedComponentIndex) + " = " + result.committedText;
|
||||
} else if (result.editCanceled) {
|
||||
m_lastResult = "已取消编辑";
|
||||
} else if (result.editStarted) {
|
||||
m_lastResult = "开始编辑 component " + DescribeSelectedComponent(result.selectedComponentIndex);
|
||||
} else if (result.stepApplied || result.valueChanged) {
|
||||
m_lastResult = "数值已更新,当前 component = " + DescribeSelectedComponent(result.changedComponentIndex);
|
||||
} else if (result.selectionChanged) {
|
||||
m_lastResult = "已切换选中 component: " + DescribeSelectedComponent(result.selectedComponentIndex);
|
||||
} else if (result.focusChanged) {
|
||||
m_lastResult = std::string("焦点变化: ") + (m_interactionState.vector4FieldState.focused ? "focused" : "lost");
|
||||
} else if (result.consumed) {
|
||||
m_lastResult = "控件已消费输入";
|
||||
}
|
||||
}
|
||||
|
||||
void RenderFrame() {
|
||||
RECT clientRect = {};
|
||||
GetClientRect(m_hwnd, &clientRect);
|
||||
const float width = static_cast<float>((std::max)(1L, clientRect.right - clientRect.left));
|
||||
const float height = static_cast<float>((std::max)(1L, clientRect.bottom - clientRect.top));
|
||||
const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme);
|
||||
const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme);
|
||||
const auto layout = BuildScenarioLayout(width, height, shellMetrics);
|
||||
PumpEvents({});
|
||||
|
||||
const auto vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector4FieldMetrics(m_theme);
|
||||
const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector4FieldPalette(m_theme);
|
||||
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme);
|
||||
const auto currentHit = HitTestUIEditorVector4Field(m_frame.layout, m_mousePosition);
|
||||
|
||||
UIDrawData drawData = {};
|
||||
UIDrawList& drawList = drawData.EmplaceDrawList("EditorVector4FieldBasic");
|
||||
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), shellPalette.windowBackground);
|
||||
|
||||
drawList.AddFilledRect(layout.introRect, shellPalette.cardBackground, shellMetrics.cardRadius);
|
||||
drawList.AddRectOutline(layout.introRect, shellPalette.cardBorder, 1.0f, shellMetrics.cardRadius);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 14.0f), "这个测试验证什么功能?", shellPalette.textPrimary, shellMetrics.titleFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 40.0f), "只验证 UIEditorVector4Field 的四通道编辑契约。", shellPalette.textMuted, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), "1. 点击 X/Y/Z/W 的 value box,检查 selected component 和 editing。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), "2. 按 Tab/Shift+Tab 切换 component;Up/Down/Home/End 检查 step 与边界。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), "3. Enter 开始编辑;直接输入字符也应开始编辑;Enter commit,Escape cancel。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), "4. F6 模拟 FocusLost;F12 截图;R 重置当前测试。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f), "5. 重点检查 Hover / Selected / Editing / Values / Result 是否同步。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
|
||||
drawList.AddFilledRect(layout.stateRect, shellPalette.cardBackground, shellMetrics.cardRadius);
|
||||
drawList.AddRectOutline(layout.stateRect, shellPalette.cardBorder, 1.0f, shellMetrics.cardRadius);
|
||||
drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 14.0f), "状态摘要", shellPalette.textPrimary, shellMetrics.titleFontSize);
|
||||
drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 46.0f), "Hover: " + DescribeHitTarget(currentHit), shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Selected: " + DescribeSelectedComponent(m_interactionState.vector4FieldState.selectedComponentIndex), shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 94.0f), std::string("Focused: ") + (m_interactionState.vector4FieldState.focused ? "是" : "否"), shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 118.0f), std::string("Editing: ") + (m_interactionState.vector4FieldState.editing ? "是" : "否"), shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 142.0f), "Values: X=" + FormatUIEditorVector4FieldComponentValue(m_spec, 0u) + " Y=" + FormatUIEditorVector4FieldComponentValue(m_spec, 1u) + " Z=" + FormatUIEditorVector4FieldComponentValue(m_spec, 2u) + " W=" + FormatUIEditorVector4FieldComponentValue(m_spec, 3u), shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 166.0f), "Display: X=" + m_interactionState.vector4FieldState.displayTexts[0] + " Y=" + m_interactionState.vector4FieldState.displayTexts[1] + " Z=" + m_interactionState.vector4FieldState.displayTexts[2] + " W=" + m_interactionState.vector4FieldState.displayTexts[3], shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 190.0f), "Result: " + m_lastResult, shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 214.0f), "Capture: " + (m_autoScreenshot.GetLastCaptureSummary().empty() ? std::string("F12 -> captures/") : m_autoScreenshot.GetLastCaptureSummary()), shellPalette.textWeak, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 238.0f), "Theme: " + m_themeStatus, shellPalette.textWeak, shellMetrics.bodyFontSize);
|
||||
|
||||
drawList.AddFilledRect(layout.previewRect, shellPalette.cardBackground, shellMetrics.cardRadius);
|
||||
drawList.AddRectOutline(layout.previewRect, shellPalette.cardBorder, 1.0f, shellMetrics.cardRadius);
|
||||
drawList.AddText(UIPoint(layout.previewRect.x + 16.0f, layout.previewRect.y + 14.0f), "Vector4Field 预览", shellPalette.textPrimary, shellMetrics.titleFontSize);
|
||||
drawList.AddText(UIPoint(layout.previewRect.x + 16.0f, layout.previewRect.y + 40.0f), "这里只放一个 Unity 风格的四通道字段。", shellPalette.textMuted, shellMetrics.bodyFontSize);
|
||||
drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor);
|
||||
drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f);
|
||||
drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground);
|
||||
drawList.AddRectOutline(layout.inspectorHeaderRect, propertyPalette.borderColor, 1.0f);
|
||||
drawList.AddText(UIPoint(layout.inspectorHeaderRect.x + 10.0f, layout.inspectorHeaderRect.y + 5.0f), "Inspector", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddFilledRect(layout.sectionRect, propertyPalette.sectionHeaderColor);
|
||||
drawList.AddRectOutline(layout.sectionRect, propertyPalette.borderColor, 1.0f);
|
||||
drawList.AddText(UIPoint(layout.sectionRect.x + 10.0f, layout.sectionRect.y + 5.0f), "v Transform", propertyPalette.sectionTextColor, shellMetrics.bodyFontSize);
|
||||
AppendUIEditorVector4Field(drawList, layout.fieldRect, m_spec, m_interactionState.vector4FieldState, vectorPalette, vectorMetrics);
|
||||
|
||||
const bool framePresented = m_renderer.Render(drawData);
|
||||
m_autoScreenshot.CaptureIfRequested(m_renderer, drawData, static_cast<unsigned int>(width), static_cast<unsigned int>(height), framePresented);
|
||||
}
|
||||
|
||||
HWND m_hwnd = nullptr;
|
||||
ATOM m_windowClassAtom = 0;
|
||||
NativeRenderer m_renderer = {};
|
||||
AutoScreenshotController m_autoScreenshot = {};
|
||||
std::filesystem::path m_captureRoot = {};
|
||||
UIEditorVector4FieldSpec m_spec = {};
|
||||
UIEditorVector4FieldInteractionState m_interactionState = {};
|
||||
UIEditorVector4FieldInteractionFrame m_frame = {};
|
||||
Style::UITheme m_theme = {};
|
||||
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
|
||||
std::string m_lastResult = "等待交互";
|
||||
std::string m_themeStatus = "fallback";
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int nCmdShow) {
|
||||
return ScenarioApp().Run(hInstance, nCmdShow);
|
||||
}
|
||||
@@ -36,6 +36,8 @@ set(EDITOR_UI_UNIT_TEST_SOURCES
|
||||
test_ui_editor_vector2_field_interaction.cpp
|
||||
test_ui_editor_vector3_field.cpp
|
||||
test_ui_editor_vector3_field_interaction.cpp
|
||||
test_ui_editor_vector4_field.cpp
|
||||
test_ui_editor_vector4_field_interaction.cpp
|
||||
test_ui_editor_scroll_view.cpp
|
||||
test_ui_editor_scroll_view_interaction.cpp
|
||||
test_ui_editor_status_bar.cpp
|
||||
|
||||
@@ -94,6 +94,18 @@ UIEditorPropertyGridField MakeEnumField(
|
||||
return field;
|
||||
}
|
||||
|
||||
UIEditorPropertyGridField MakeVector4Field(
|
||||
std::string id,
|
||||
std::string label,
|
||||
std::array<double, 4u> values) {
|
||||
UIEditorPropertyGridField field = {};
|
||||
field.fieldId = std::move(id);
|
||||
field.label = std::move(label);
|
||||
field.kind = UIEditorPropertyGridFieldKind::Vector4;
|
||||
field.vector4Value.values = values;
|
||||
return field;
|
||||
}
|
||||
|
||||
std::vector<UIEditorPropertyGridSection> BuildSections() {
|
||||
return {
|
||||
{
|
||||
@@ -274,3 +286,54 @@ TEST(UIEditorPropertyGridTest, BackgroundAndForegroundEmitTypedCommandsAndPopupO
|
||||
EXPECT_TRUE(ContainsTextCommand(drawData, "Cutout"));
|
||||
EXPECT_EQ(ResolveUIEditorPropertyGridFieldValueText(sections[0].fields[1]), "2000");
|
||||
}
|
||||
|
||||
TEST(UIEditorPropertyGridTest, Vector4FieldUsesHostedLayoutAndForegroundText) {
|
||||
std::vector<UIEditorPropertyGridSection> sections = {
|
||||
{
|
||||
"transform",
|
||||
"Transform",
|
||||
{
|
||||
MakeVector4Field("rotation", "Rotation", { 1.0, 2.0, 3.0, 4.0 })
|
||||
},
|
||||
0.0f
|
||||
}
|
||||
};
|
||||
UISelectionModel selectionModel = {};
|
||||
UIExpansionModel expansionModel = {};
|
||||
expansionModel.Expand("transform");
|
||||
UIPropertyEditModel propertyEditModel = {};
|
||||
UIEditorPropertyGridState state = {};
|
||||
|
||||
const auto layout = BuildUIEditorPropertyGridLayout(
|
||||
UIRect(0.0f, 0.0f, 520.0f, 220.0f),
|
||||
sections,
|
||||
expansionModel);
|
||||
|
||||
ASSERT_EQ(layout.visibleFieldIndices.size(), 1u);
|
||||
EXPECT_GT(layout.fieldValueRects[0].x, layout.fieldLabelRects[0].x + layout.fieldLabelRects[0].width);
|
||||
EXPECT_GT(layout.fieldValueRects[0].width, 200.0f);
|
||||
|
||||
XCEngine::UI::UIDrawData drawData = {};
|
||||
auto& drawList = drawData.EmplaceDrawList("PropertyGridVector4");
|
||||
AppendUIEditorPropertyGridBackground(
|
||||
drawList,
|
||||
layout,
|
||||
sections,
|
||||
selectionModel,
|
||||
propertyEditModel,
|
||||
state);
|
||||
AppendUIEditorPropertyGridForeground(
|
||||
drawList,
|
||||
layout,
|
||||
sections,
|
||||
state,
|
||||
propertyEditModel);
|
||||
|
||||
EXPECT_TRUE(ContainsTextCommand(drawData, "Rotation"));
|
||||
EXPECT_TRUE(ContainsTextCommand(drawData, "X"));
|
||||
EXPECT_TRUE(ContainsTextCommand(drawData, "Y"));
|
||||
EXPECT_TRUE(ContainsTextCommand(drawData, "Z"));
|
||||
EXPECT_TRUE(ContainsTextCommand(drawData, "W"));
|
||||
EXPECT_TRUE(ContainsTextCommand(drawData, "4"));
|
||||
EXPECT_EQ(ResolveUIEditorPropertyGridFieldValueText(sections[0].fields[0]), "1, 2, 3, 4");
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ Style::UITheme BuildEditorFieldTheme() {
|
||||
definition.SetToken("editor.color.field.vector_axis_x", Style::UIStyleValue(Math::Color(0.78f, 0.42f, 0.42f, 1.0f)));
|
||||
definition.SetToken("editor.color.field.vector_axis_y", Style::UIStyleValue(Math::Color(0.56f, 0.72f, 0.46f, 1.0f)));
|
||||
definition.SetToken("editor.color.field.vector_axis_z", Style::UIStyleValue(Math::Color(0.45f, 0.62f, 0.82f, 1.0f)));
|
||||
definition.SetToken("editor.color.field.vector_axis_w", Style::UIStyleValue(Math::Color(0.76f, 0.66f, 0.42f, 1.0f)));
|
||||
definition.SetToken("editor.color.field.checkbox", Style::UIStyleValue(Math::Color(0.19f, 0.19f, 0.19f, 1.0f)));
|
||||
definition.SetToken("editor.color.field.checkbox_hover", Style::UIStyleValue(Math::Color(0.22f, 0.22f, 0.22f, 1.0f)));
|
||||
definition.SetToken("editor.color.field.checkbox_border", Style::UIStyleValue(Math::Color(0.33f, 0.33f, 0.33f, 1.0f)));
|
||||
@@ -225,6 +226,16 @@ TEST(UIEditorThemeTest, FieldResolversReadEditorThemeTokens) {
|
||||
EXPECT_FLOAT_EQ(vector3Palette.componentFocusedBorderColor.r, 0.64f);
|
||||
EXPECT_FLOAT_EQ(vector3Palette.axisZColor.b, 0.82f);
|
||||
|
||||
const auto vector4Metrics = Editor::ResolveUIEditorVector4FieldMetrics(theme);
|
||||
const auto vector4Palette = Editor::ResolveUIEditorVector4FieldPalette(theme);
|
||||
EXPECT_FLOAT_EQ(vector4Metrics.componentGap, 6.0f);
|
||||
EXPECT_FLOAT_EQ(vector4Metrics.componentPrefixWidth, 18.0f);
|
||||
EXPECT_FLOAT_EQ(vector4Metrics.controlTrailingInset, 9.0f);
|
||||
EXPECT_FLOAT_EQ(vector4Metrics.componentLabelGap, 5.0f);
|
||||
EXPECT_FLOAT_EQ(vector4Metrics.prefixFontSize, 10.0f);
|
||||
EXPECT_FLOAT_EQ(vector4Palette.componentFocusedBorderColor.r, 0.64f);
|
||||
EXPECT_FLOAT_EQ(vector4Palette.axisWColor.r, 0.76f);
|
||||
|
||||
const auto enumMetrics = Editor::ResolveUIEditorEnumFieldMetrics(theme);
|
||||
const auto enumPalette = Editor::ResolveUIEditorEnumFieldPalette(theme);
|
||||
EXPECT_FLOAT_EQ(enumMetrics.controlTrailingInset, 9.0f);
|
||||
@@ -359,6 +370,15 @@ TEST(UIEditorThemeTest, HostedFieldBuildersInheritPropertyGridMetricsAndPalette)
|
||||
EXPECT_FLOAT_EQ(vector3Palette.componentEditingColor.r, 0.4f);
|
||||
EXPECT_FLOAT_EQ(vector3Palette.componentFocusedBorderColor.r, 0.55f);
|
||||
|
||||
const auto vector4Metrics = Editor::BuildUIEditorHostedVector4FieldMetrics(propertyMetrics);
|
||||
const auto vector4Palette = Editor::BuildUIEditorHostedVector4FieldPalette(propertyPalette);
|
||||
EXPECT_FLOAT_EQ(vector4Metrics.controlInsetY, 2.0f);
|
||||
EXPECT_FLOAT_EQ(vector4Metrics.controlTrailingInset, 5.0f);
|
||||
EXPECT_FLOAT_EQ(vector4Metrics.valueTextInsetX, 5.0f);
|
||||
EXPECT_FLOAT_EQ(vector4Metrics.componentRounding, 2.0f);
|
||||
EXPECT_FLOAT_EQ(vector4Palette.componentEditingColor.r, 0.4f);
|
||||
EXPECT_FLOAT_EQ(vector4Palette.componentFocusedBorderColor.r, 0.55f);
|
||||
|
||||
const auto enumMetrics = Editor::BuildUIEditorHostedEnumFieldMetrics(propertyMetrics);
|
||||
const auto enumPalette = Editor::BuildUIEditorHostedEnumFieldPalette(propertyPalette);
|
||||
EXPECT_FLOAT_EQ(enumMetrics.controlTrailingInset, 5.0f);
|
||||
|
||||
61
tests/UI/Editor/unit/test_ui_editor_vector4_field.cpp
Normal file
61
tests/UI/Editor/unit/test_ui_editor_vector4_field.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Widgets/UIEditorVector4Field.h>
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::UI::UIPoint;
|
||||
using XCEngine::UI::UIRect;
|
||||
using XCEngine::UI::Editor::Widgets::BuildUIEditorVector4FieldLayout;
|
||||
using XCEngine::UI::Editor::Widgets::FormatUIEditorVector4FieldComponentValue;
|
||||
using XCEngine::UI::Editor::Widgets::HitTestUIEditorVector4Field;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorVector4FieldHitTargetKind;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorVector4FieldSpec;
|
||||
|
||||
TEST(UIEditorVector4FieldTest, FormatSupportsPerComponentDisplay) {
|
||||
UIEditorVector4FieldSpec spec = {};
|
||||
spec.fieldId = "rotation";
|
||||
spec.label = "Rotation";
|
||||
spec.values = { 1.25, -3.5, 8.0, 0.125 };
|
||||
|
||||
EXPECT_EQ(FormatUIEditorVector4FieldComponentValue(spec, 0u), "1.25");
|
||||
EXPECT_EQ(FormatUIEditorVector4FieldComponentValue(spec, 1u), "-3.5");
|
||||
EXPECT_EQ(FormatUIEditorVector4FieldComponentValue(spec, 2u), "8");
|
||||
EXPECT_EQ(FormatUIEditorVector4FieldComponentValue(spec, 3u), "0.125");
|
||||
}
|
||||
|
||||
TEST(UIEditorVector4FieldTest, LayoutBuildsFourComponentRects) {
|
||||
UIEditorVector4FieldSpec spec = {};
|
||||
spec.fieldId = "rotation";
|
||||
spec.label = "Rotation";
|
||||
const auto layout = BuildUIEditorVector4FieldLayout(UIRect(0.0f, 0.0f, 620.0f, 32.0f), spec);
|
||||
|
||||
EXPECT_GT(layout.labelRect.width, 0.0f);
|
||||
EXPECT_GT(layout.componentRects[0].width, 0.0f);
|
||||
EXPECT_GT(layout.componentRects[1].width, 0.0f);
|
||||
EXPECT_GT(layout.componentRects[2].width, 0.0f);
|
||||
EXPECT_GT(layout.componentRects[3].width, 0.0f);
|
||||
EXPECT_LT(layout.componentRects[0].x + layout.componentRects[0].width, layout.componentRects[1].x);
|
||||
EXPECT_LT(layout.componentRects[1].x + layout.componentRects[1].width, layout.componentRects[2].x);
|
||||
EXPECT_LT(layout.componentRects[2].x + layout.componentRects[2].width, layout.componentRects[3].x);
|
||||
}
|
||||
|
||||
TEST(UIEditorVector4FieldTest, HitTestResolvesComponentAndRow) {
|
||||
UIEditorVector4FieldSpec spec = {};
|
||||
spec.fieldId = "rotation";
|
||||
spec.label = "Rotation";
|
||||
const auto layout = BuildUIEditorVector4FieldLayout(UIRect(0.0f, 0.0f, 620.0f, 32.0f), spec);
|
||||
|
||||
const auto fourthHit = HitTestUIEditorVector4Field(
|
||||
layout,
|
||||
UIPoint(layout.componentRects[3].x + 4.0f, layout.componentRects[3].y + 4.0f));
|
||||
EXPECT_EQ(fourthHit.kind, UIEditorVector4FieldHitTargetKind::Component);
|
||||
EXPECT_EQ(fourthHit.componentIndex, 3u);
|
||||
|
||||
const auto rowHit = HitTestUIEditorVector4Field(
|
||||
layout,
|
||||
UIPoint(layout.labelRect.x + 4.0f, layout.labelRect.y + 4.0f));
|
||||
EXPECT_EQ(rowHit.kind, UIEditorVector4FieldHitTargetKind::Row);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,165 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Core/UIEditorVector4FieldInteraction.h>
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Input::KeyCode;
|
||||
using XCEngine::UI::UIInputEvent;
|
||||
using XCEngine::UI::UIInputEventType;
|
||||
using XCEngine::UI::UIPoint;
|
||||
using XCEngine::UI::UIPointerButton;
|
||||
using XCEngine::UI::UIRect;
|
||||
using XCEngine::UI::Editor::UIEditorVector4FieldInteractionState;
|
||||
using XCEngine::UI::Editor::UpdateUIEditorVector4FieldInteraction;
|
||||
using XCEngine::UI::Editor::Widgets::UIEditorVector4FieldSpec;
|
||||
|
||||
UIInputEvent MakePointer(UIInputEventType type, float x, float y, UIPointerButton button = UIPointerButton::None) {
|
||||
UIInputEvent event = {};
|
||||
event.type = type;
|
||||
event.position = UIPoint(x, y);
|
||||
event.pointerButton = button;
|
||||
return event;
|
||||
}
|
||||
|
||||
UIInputEvent MakeKey(KeyCode keyCode) {
|
||||
UIInputEvent event = {};
|
||||
event.type = UIInputEventType::KeyDown;
|
||||
event.keyCode = static_cast<std::int32_t>(keyCode);
|
||||
return event;
|
||||
}
|
||||
|
||||
UIInputEvent MakeCharacter(char character) {
|
||||
UIInputEvent event = {};
|
||||
event.type = UIInputEventType::Character;
|
||||
event.character = static_cast<std::uint32_t>(character);
|
||||
return event;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(UIEditorVector4FieldInteractionTest, ClickFourthComponentStartsEditing) {
|
||||
UIEditorVector4FieldSpec spec = {};
|
||||
spec.fieldId = "rotation";
|
||||
spec.label = "Rotation";
|
||||
spec.values = { 1.0, 2.0, 3.0, 4.0 };
|
||||
UIEditorVector4FieldInteractionState state = {};
|
||||
|
||||
auto frame = UpdateUIEditorVector4FieldInteraction(
|
||||
state,
|
||||
spec,
|
||||
UIRect(0.0f, 0.0f, 620.0f, 32.0f),
|
||||
{});
|
||||
|
||||
frame = UpdateUIEditorVector4FieldInteraction(
|
||||
state,
|
||||
spec,
|
||||
UIRect(0.0f, 0.0f, 620.0f, 32.0f),
|
||||
{
|
||||
MakePointer(
|
||||
UIInputEventType::PointerButtonDown,
|
||||
frame.layout.componentRects[3].x + 4.0f,
|
||||
frame.layout.componentRects[3].y + 4.0f,
|
||||
UIPointerButton::Left),
|
||||
MakePointer(
|
||||
UIInputEventType::PointerButtonUp,
|
||||
frame.layout.componentRects[3].x + 4.0f,
|
||||
frame.layout.componentRects[3].y + 4.0f,
|
||||
UIPointerButton::Left)
|
||||
});
|
||||
|
||||
EXPECT_TRUE(frame.result.editStarted);
|
||||
EXPECT_TRUE(state.vector4FieldState.editing);
|
||||
EXPECT_EQ(state.vector4FieldState.selectedComponentIndex, 3u);
|
||||
}
|
||||
|
||||
TEST(UIEditorVector4FieldInteractionTest, TabSelectsNextComponentAndArrowAppliesStep) {
|
||||
UIEditorVector4FieldSpec spec = {};
|
||||
spec.fieldId = "rotation";
|
||||
spec.label = "Rotation";
|
||||
spec.values = { 1.0, 2.0, 3.0, 4.0 };
|
||||
spec.step = 0.5;
|
||||
UIEditorVector4FieldInteractionState state = {};
|
||||
state.vector4FieldState.focused = true;
|
||||
state.vector4FieldState.selectedComponentIndex = 2u;
|
||||
|
||||
auto frame = UpdateUIEditorVector4FieldInteraction(
|
||||
state,
|
||||
spec,
|
||||
UIRect(0.0f, 0.0f, 620.0f, 32.0f),
|
||||
{ MakeKey(KeyCode::Tab) });
|
||||
EXPECT_TRUE(frame.result.selectionChanged);
|
||||
EXPECT_EQ(state.vector4FieldState.selectedComponentIndex, 3u);
|
||||
|
||||
frame = UpdateUIEditorVector4FieldInteraction(
|
||||
state,
|
||||
spec,
|
||||
UIRect(0.0f, 0.0f, 620.0f, 32.0f),
|
||||
{ MakeKey(KeyCode::Up) });
|
||||
EXPECT_TRUE(frame.result.stepApplied);
|
||||
EXPECT_EQ(frame.result.changedComponentIndex, 3u);
|
||||
EXPECT_DOUBLE_EQ(spec.values[3], 4.5);
|
||||
}
|
||||
|
||||
TEST(UIEditorVector4FieldInteractionTest, EnterStartsEditingAndCommitUpdatesSelectedComponent) {
|
||||
UIEditorVector4FieldSpec spec = {};
|
||||
spec.fieldId = "rotation";
|
||||
spec.label = "Rotation";
|
||||
spec.values = { 1.0, 2.0, 3.0, 4.0 };
|
||||
spec.integerMode = true;
|
||||
UIEditorVector4FieldInteractionState state = {};
|
||||
state.vector4FieldState.focused = true;
|
||||
state.vector4FieldState.selectedComponentIndex = 2u;
|
||||
|
||||
auto frame = UpdateUIEditorVector4FieldInteraction(
|
||||
state,
|
||||
spec,
|
||||
UIRect(0.0f, 0.0f, 620.0f, 32.0f),
|
||||
{ MakeKey(KeyCode::Enter) });
|
||||
EXPECT_TRUE(frame.result.editStarted);
|
||||
EXPECT_TRUE(state.vector4FieldState.editing);
|
||||
|
||||
frame = UpdateUIEditorVector4FieldInteraction(
|
||||
state,
|
||||
spec,
|
||||
UIRect(0.0f, 0.0f, 620.0f, 32.0f),
|
||||
{ MakeCharacter('7'), MakeKey(KeyCode::Enter) });
|
||||
|
||||
EXPECT_TRUE(frame.result.editCommitted);
|
||||
EXPECT_TRUE(frame.result.valueChanged);
|
||||
EXPECT_EQ(frame.result.changedComponentIndex, 2u);
|
||||
EXPECT_DOUBLE_EQ(spec.values[0], 1.0);
|
||||
EXPECT_DOUBLE_EQ(spec.values[1], 2.0);
|
||||
EXPECT_DOUBLE_EQ(spec.values[2], 37.0);
|
||||
EXPECT_DOUBLE_EQ(spec.values[3], 4.0);
|
||||
}
|
||||
|
||||
TEST(UIEditorVector4FieldInteractionTest, CharacterInputCanStartEditingAndEscapeCancels) {
|
||||
UIEditorVector4FieldSpec spec = {};
|
||||
spec.fieldId = "rotation";
|
||||
spec.label = "Rotation";
|
||||
spec.values = { 1.0, 2.0, 3.0, 4.0 };
|
||||
UIEditorVector4FieldInteractionState state = {};
|
||||
state.vector4FieldState.focused = true;
|
||||
state.vector4FieldState.selectedComponentIndex = 0u;
|
||||
|
||||
auto frame = UpdateUIEditorVector4FieldInteraction(
|
||||
state,
|
||||
spec,
|
||||
UIRect(0.0f, 0.0f, 620.0f, 32.0f),
|
||||
{ MakeCharacter('9') });
|
||||
EXPECT_TRUE(frame.result.editStarted);
|
||||
EXPECT_TRUE(state.vector4FieldState.editing);
|
||||
EXPECT_EQ(state.vector4FieldState.displayTexts[0], "9");
|
||||
|
||||
frame = UpdateUIEditorVector4FieldInteraction(
|
||||
state,
|
||||
spec,
|
||||
UIRect(0.0f, 0.0f, 620.0f, 32.0f),
|
||||
{ MakeKey(KeyCode::Escape) });
|
||||
EXPECT_TRUE(frame.result.editCanceled);
|
||||
EXPECT_FALSE(state.vector4FieldState.editing);
|
||||
EXPECT_DOUBLE_EQ(spec.values[0], 1.0);
|
||||
}
|
||||
Reference in New Issue
Block a user