Refactor XCUI editor module layout

This commit is contained in:
2026-04-10 00:41:28 +08:00
parent 4b47764f26
commit 02a0e626fe
263 changed files with 12396 additions and 7592 deletions

View File

@@ -58,11 +58,26 @@ if(TARGET editor_ui_tree_view_multiselect_validation)
editor_ui_tree_view_multiselect_validation)
endif()
if(TARGET editor_ui_tree_view_inline_rename_validation)
list(APPEND EDITOR_UI_INTEGRATION_TARGETS
editor_ui_tree_view_inline_rename_validation)
endif()
if(TARGET editor_ui_property_grid_basic_validation)
list(APPEND EDITOR_UI_INTEGRATION_TARGETS
editor_ui_property_grid_basic_validation)
endif()
if(TARGET editor_ui_panel_content_host_basic_validation)
list(APPEND EDITOR_UI_INTEGRATION_TARGETS
editor_ui_panel_content_host_basic_validation)
endif()
if(TARGET editor_ui_panel_host_lifecycle_validation)
list(APPEND EDITOR_UI_INTEGRATION_TARGETS
editor_ui_panel_host_lifecycle_validation)
endif()
if(TARGET editor_ui_bool_field_basic_validation)
list(APPEND EDITOR_UI_INTEGRATION_TARGETS
editor_ui_bool_field_basic_validation)
@@ -73,6 +88,16 @@ if(TARGET editor_ui_number_field_basic_validation)
editor_ui_number_field_basic_validation)
endif()
if(TARGET editor_ui_asset_field_basic_validation)
list(APPEND EDITOR_UI_INTEGRATION_TARGETS
editor_ui_asset_field_basic_validation)
endif()
if(TARGET editor_ui_object_field_basic_validation)
list(APPEND EDITOR_UI_INTEGRATION_TARGETS
editor_ui_object_field_basic_validation)
endif()
if(TARGET editor_ui_text_field_basic_validation)
list(APPEND EDITOR_UI_INTEGRATION_TARGETS
editor_ui_text_field_basic_validation)
@@ -88,6 +113,11 @@ if(TARGET editor_ui_vector3_field_basic_validation)
editor_ui_vector3_field_basic_validation)
endif()
if(TARGET editor_ui_vector4_field_basic_validation)
list(APPEND EDITOR_UI_INTEGRATION_TARGETS
editor_ui_vector4_field_basic_validation)
endif()
if(TARGET editor_ui_enum_field_basic_validation)
list(APPEND EDITOR_UI_INTEGRATION_TARGETS
editor_ui_enum_field_basic_validation)
@@ -113,6 +143,11 @@ if(TARGET editor_ui_list_view_inline_rename_validation)
editor_ui_list_view_inline_rename_validation)
endif()
if(TARGET editor_ui_scroll_view_basic_validation)
list(APPEND EDITOR_UI_INTEGRATION_TARGETS
editor_ui_scroll_view_basic_validation)
endif()
add_custom_target(editor_ui_integration_tests
DEPENDS
${EDITOR_UI_INTEGRATION_TARGETS}

View File

@@ -1,293 +1,23 @@
# Editor UI Integration Validation
# Editor UI Integration
This directory contains editor-only XCUI manual validation scenarios.
This directory contains manual Editor UI validation scenarios under `tests/UI/Editor/integration/`.
Rules:
- Shared Core primitives stay in `tests/UI/Core/integration/`.
- Only editor-only shell, host, widget, and domain-integrated validation belongs here.
- Scenarios are organized as `tests/UI/Editor/integration/<category>/<scenario>/`.
- Each scenario owns its own `captures/` directory.
- `tests/UI` remains the active validation entry point. `new_editor` is not a test entry point.
- Shared infrastructure belongs in `shared/`. Do not duplicate host plumbing per scenario unless the scenario is intentionally a custom host.
- One scenario maps to one executable.
Layout:
- Primary categories are `shell/` and `state/`.
- `menu`, `workspace`, and `viewport` in scenario names describe contract families, not extra directory levels.
- `shared/`: shared host wrapper, scenario registry, shared theme
- `shell/workspace_shell_compose/`: split/tab/panel shell compose only
- `shell/editor_shell_compose/`: editor root shell compose only
- `shell/editor_shell_interaction/`: editor root shell interaction only
- `shell/dock_host_basic/`: DockHost interaction contract only
- `shell/panel_content_host_basic/`: external panel body mount/switch/unmount only
- `shell/menu_bar_basic/`: menu bar open/close/hover/dispatch only
- `shell/context_menu_basic/`: context menu root/submenu/dismiss/dispatch only
- `shell/panel_frame_basic/`: panel frame layout/state/hit-test only
- `shell/scroll_view_basic/`: ScrollView viewport, clip, thumb drag, wheel offset only
- `shell/property_grid_basic/`: PropertyGrid section toggle, field selection, value edit, keyboard navigation only
- `shell/bool_field_basic/`: BoolField click toggle, keyboard toggle, hover/focus/value feedback only
- `shell/number_field_basic/`: NumberField step buttons, direct text edit, Enter commit, Esc cancel only
- `shell/enum_field_basic/`: EnumField previous/next switch, keyboard switch, hover/focus/selection feedback only
- `shell/status_bar_basic/`: status bar slot/segment/hit-test only
- `shell/tree_view_basic/`: TreeView row layout, indent, disclosure, selection, focus, hit-test only
- `shell/tree_view_multiselect/`: TreeView multi-selection contract only
- `shell/list_view_basic/`: ListView row layout, selection, focus, keyboard navigation, hit-test only
- `shell/list_view_multiselect/`: ListView multi-selection contract only
- `shell/list_view_inline_rename/`: ListView inline rename/edit session only
- `shell/tab_strip_basic/`: tab strip layout/state/hit-test/close/navigation only
- `shell/viewport_slot_basic/`: viewport slot chrome/surface/status only
- `shell/viewport_shell_basic/`: viewport shell request/state compose only
- `shell/workspace_viewport_compose/`: workspace body external presentation compose only
- `shell/workspace_interaction_basic/`: workspace unified interaction only
- `state/panel_session_flow/`: panel session state flow only
- `state/panel_host_lifecycle/`: panel host attach/detach/show/hide/activate/focus contract only
- `state/layout_persistence/`: layout save/load/reset only
- `state/shortcut_dispatch/`: shortcut match/suppression/dispatch only
- `state/viewport_input_bridge_basic/`: viewport hover/focus/capture, local pointer coordinates, wheel/key/character bridge only
Scenarios:
- `editor.shell.workspace_shell_compose`
Build target: `editor_ui_workspace_shell_compose_validation`
Executable: `XCUIEditorWorkspaceShellComposeValidation.exe`
Scope: DockHost compose, splitter drag, tab host, panel frame placeholders, workspace active-panel sync
- `editor.shell.editor_shell_compose`
Build target: `editor_ui_editor_shell_compose_validation`
Executable: `XCUIEditorShellComposeValidation.exe`
Scope: root shell compose only; MenuBar / WorkspaceCompose / StatusBar three-band layout and workspace body embedding
- `editor.shell.editor_shell_interaction`
Build target: `editor_ui_editor_shell_interaction_validation`
Executable: `XCUIEditorShellInteractionValidation.exe`
Scope: root shell unified interaction only; menu bar root switching, submenu hover chain, outside/Esc dismiss, command hook, menu-modal workspace shielding, and post-dismiss workspace interaction restore
- `editor.shell.dock_host_basic`
Build target: `editor_ui_dock_host_basic_validation`
Executable: `XCUIEditorDockHostBasicValidation.exe`
Scope: `UpdateUIEditorDockHostInteraction(...)` basic contract only; splitter drag, tab activate/close, standalone panel activate/close, pointer capture/release request, workspace active-panel sync
- `editor.shell.panel_content_host_basic`
Build target: `editor_ui_panel_content_host_basic_validation`
Executable: `XCUIEditorPanelContentHostBasicValidation.exe`
Scope: external HostedContent body mount/switch/unmount only; DockHost 只画 frame chrome真实 body 由 content host 接管
- `editor.shell.menu_bar_basic`
Build target: `editor_ui_menu_bar_basic_validation`
Executable: `XCUIEditorMenuBarBasicValidation.exe`
Scope: menu bar open/close, hover, dismiss, menu command dispatch only
- `editor.shell.context_menu_basic`
Build target: `editor_ui_context_menu_basic_validation`
Executable: `XCUIEditorContextMenuBasicValidation.exe`
Scope: context menu root anchor, submenu hover, outside/Esc dismiss, command dispatch only
- `editor.shell.panel_frame_basic`
Build target: `editor_ui_panel_frame_basic_validation`
Executable: `XCUIEditorPanelFrameBasicValidation.exe`
Scope: panel frame header/body/footer layout, focus/active/hover chrome, pin/close hit target only
- `editor.shell.scroll_view_basic`
Build target: `editor_ui_scroll_view_basic_validation`
Executable: `XCUIEditorScrollViewBasicValidation.exe`
Scope: ScrollView viewport clip, wheel scrolling, thumb drag, focus, and hit-test only
- `editor.shell.property_grid_basic`
Build target: `editor_ui_property_grid_basic_validation`
Executable: `XCUIEditorPropertyGridBasicValidation.exe`
Scope: PropertyGrid 基础控件验证;只检查 section toggle、field selection、value edit、Enter/Esc、keyboard navigation不涉及业务 Inspector
- `editor.shell.bool_field_basic`
Build target: `editor_ui_bool_field_basic_validation`
Executable: `XCUIEditorBoolFieldBasicValidation.exe`
Scope: BoolField 基础控件契约只验证点击切换、Space/Enter 切换、hover/focus/value/result 联动
- `editor.shell.number_field_basic`
Build target: `editor_ui_number_field_basic_validation`
Executable: `XCUIEditorNumberFieldBasicValidation.exe`
Scope: NumberField 基础控件契约只验证步进按钮、直接字符编辑、Enter 提交、Esc 取消、hover/focus/result 联动
- `editor.shell.enum_field_basic`
Build target: `editor_ui_enum_field_basic_validation`
Executable: `XCUIEditorEnumFieldBasicValidation.exe`
Scope: EnumField 基础控件契约只验证前后切换按钮、Left/Right/Home/End 键盘切换、hover/focus/result 联动
- `editor.shell.status_bar_basic`
Build target: `editor_ui_status_bar_basic_validation`
Executable: `XCUIEditorStatusBarBasicValidation.exe`
Scope: status bar slot layout, hover/active segment hit target, separator layout only
- `editor.shell.tree_view_basic`
Build target: `editor_ui_tree_view_basic_validation`
Executable: `XCUIEditorTreeViewBasicValidation.exe`
Scope: TreeView 基础控件验证只检查行缩进、disclosure 展开/折叠、selection、hover/focus 和 hit-test不涉及业务面板
- `editor.shell.tree_view_multiselect`
Build target: `editor_ui_tree_view_multiselect_validation`
Executable: `XCUIEditorTreeViewMultiSelectValidation.exe`
Scope: TreeView 多选契约验证;只检查 Ctrl/Shift 多选、锚点、右键命中已选集合、键盘范围扩选、expanded/visible/current 同步,不涉及业务面板
- `editor.shell.tree_view_inline_rename`
Build target: `editor_ui_tree_view_inline_rename_validation`
Executable: `XCUIEditorTreeViewInlineRenameValidation.exe`
Scope: TreeView inline rename 契约验证;只检查 F2 开启、字符编辑、Enter 提交、Esc 取消、点击外部结束编辑,以及标签写回
- `editor.shell.list_view_basic`
Build target: `editor_ui_list_view_basic_validation`
Executable: `XCUIEditorListViewBasicValidation.exe`
Scope: ListView 基础控件验证;只检查 row 排列、selection、hover/focus、Up/Down/Home/End 键盘导航和 hit-test不涉及业务面板
- `editor.shell.list_view_multiselect`
Build target: `editor_ui_list_view_multiselect_validation`
Executable: `XCUIEditorListViewMultiSelectValidation.exe`
Scope: ListView 多选契约验证;只检查 Ctrl/Shift 多选、锚点、右键命中已选集合、键盘范围扩选、current/primary 同步,不涉及业务面板
- `editor.shell.list_view_inline_rename`
Build target: `editor_ui_list_view_inline_rename_validation`
Executable: `XCUIEditorListViewInlineRenameValidation.exe`
Scope: ListView inline rename 契约验证;只检查 F2 开启、字符编辑、Enter 提交、Esc 取消、点击外部结束编辑,以及标签写回
- `editor.shell.tab_strip_basic`
Build target: `editor_ui_tab_strip_basic_validation`
Executable: `XCUIEditorTabStripBasicValidation.exe`
Scope: tab header layout, selected/hover/focus, close hit target, close fallback, Left/Right/Home/End navigation only
- `editor.shell.viewport_slot_basic`
Build target: `editor_ui_viewport_slot_basic_validation`
Executable: `XCUIEditorViewportSlotBasicValidation.exe`
Scope: viewport top bar / surface / status bar layout, hover/focus/active/capture, texture vs fallback only
- `editor.shell.viewport_shell_basic`
Build target: `editor_ui_viewport_shell_basic_validation`
Executable: `XCUIEditorViewportShellBasicValidation.exe`
Scope: `ResolveUIEditorViewportShellRequest(...)` + `UpdateUIEditorViewportShell(...)` basic contract, TopBar / BottomBar request-size sync, input rect + hover/focus/capture state sync only
- `editor.shell.workspace_viewport_compose`
Build target: `editor_ui_workspace_viewport_compose_validation`
Executable: `XCUIEditorWorkspaceViewportComposeValidation.exe`
Scope: `ResolveUIEditorWorkspaceComposeRequest(...)` + `UpdateUIEditorWorkspaceCompose(...)` body presentation contract only; selected Scene tab body is hosted by `ViewportShell`, switching back to Document restores DockHost placeholder
- `editor.shell.workspace_interaction_basic`
Build target: `editor_ui_workspace_interaction_basic_validation`
Executable: `XCUIEditorWorkspaceInteractionBasicValidation.exe`
Scope: `UpdateUIEditorWorkspaceInteraction(...)` unified contract only; DockHost splitter/tab interaction plus ViewportShell body focus/capture routing in the same workspace layer
- `editor.state.panel_session_flow`
Build target: `editor_ui_panel_session_flow_validation`
Executable: `XCUIEditorPanelSessionFlowValidation.exe`
Scope: command dispatch + workspace controller + open/close/show/hide/activate
- `editor.state.panel_host_lifecycle`
Build target: `editor_ui_panel_host_lifecycle_validation`
Executable: `XCUIEditorPanelHostLifecycleValidation.exe`
Scope: panel host attach/detach/show/hide/activate/focus lifecycle only; active/focus decoupling and hidden-tab attached state
- `editor.state.layout_persistence`
Build target: `editor_ui_layout_persistence_validation`
Executable: `XCUIEditorLayoutPersistenceValidation.exe`
Scope: layout snapshot + serialize/deserialize + invalid payload reject
- `editor.state.shortcut_dispatch`
Build target: `editor_ui_shortcut_dispatch_validation`
Executable: `XCUIEditorShortcutDispatchValidation.exe`
Scope: shortcut match + scope + suppression + command dispatch
- `editor.state.viewport_input_bridge_basic`
Build target: `editor_ui_viewport_input_bridge_basic_validation`
Executable: `XCUIEditorViewportInputBridgeBasicValidation.exe`
Scope: viewport hover/focus/capture, local pointer coordinates, wheel/key/character bridge only
Run:
Build:
```bash
cmake --build build --config Debug --target editor_ui_integration_tests
```
Auto capture:
Screenshot output:
- Set `XCUI_AUTO_CAPTURE_ON_STARTUP=1` before launching a validation executable to force a first-frame screenshot into that scenario's `captures/` directory.
- Manual validation still uses `F12`; startup auto capture is only for deterministic self-check / automation.
Selected controls:
- `shell/workspace_shell_compose/`
Drag splitters, switch `Document A/B/C`, close tabs or side panels, press `Reset`, press `F12`.
- `shell/editor_shell_compose/`
Click `切到 Scene / 切到 Document`, toggle `TopBar / BottomBar / Texture`, inspect `MenuBar Rect / Workspace Rect / StatusBar Rect / Selected Presentation / Request Size`, press `Reset`, press `截图` or `F12`.
- `shell/editor_shell_interaction/`
Click `File / Window`, hover `Workspace Tools`, click outside the menu or press `Esc`, then click `Document` or drag a splitter, inspect `Open Root / Popup Chain / Submenu Path / Selected Presentation / Active Panel / Host Capture / Result`, press `Reset`, `Capture`, or `F12`.
- `shell/dock_host_basic/`
Drag `root-split`, click `Document A`, close `Document B`, click `Details`, close `Console`, inspect `Hover / Result / Active Panel / Visible Panels / Capture / split ratio`, press `Reset`, `Capture`, or `F12`.
- `shell/panel_content_host_basic/`
Click `Activate Doc A / Activate Doc B / Activate Console / Close Inspector / Open Inspector`, inspect `Mounted Panels / Events` 和蓝色 external body`Capture``F12`
- `shell/menu_bar_basic/`
Click `File / Window / Layout`, move the mouse across menu items, click outside the menu or press `Esc`, press `F12`.
- `shell/context_menu_basic/`
Right click inside `Context Target`, hover `Workspace Tools`, click actions, click outside the menu or press `Esc`, press `F12`.
- `shell/panel_frame_basic/`
Move the mouse over the preview panel, click `Body / Pin / Close`, toggle `Active / Focus / Closable / Footer`, press `F12`.
- `shell/scroll_view_basic/`
把鼠标移到右侧日志区内滚轮滚动,拖拽 scrollbar thumb检查 `Hover / Focused / Thumb Dragging / Offset / Has Scrollbar / Result`,按 `重置``截图(F12)` 或直接按 `F12`
- `shell/bool_field_basic/`
先看顶部中文说明,再点击 `BoolField` 的 row 或 toggle控件获得 focus 后按 `Space / Enter`,检查 `Hover / Focused / Value / Result` 是否同步更新,按 `重置``截图(F12)` 或直接按 `F12`
- `shell/number_field_basic/`
先看顶部中文说明,再点击 `- / +` 检查步进;点击数值框后直接输入字符并按 `Enter` 提交,或按 `Esc` 取消,检查 `Hover / Focused / Editing / Value / Result`,按 `重置``截图(F12)` 或直接按 `F12`
- `shell/enum_field_basic/`
先看顶部中文说明,再点击 `< / >` 切换选项;控件获得 focus 后按 `Left / Right / Home / End`,检查 `Hover / Focused / Selected / Result`,按 `重置``截图(F12)` 或直接按 `F12`
- `shell/status_bar_basic/`
Move the mouse across leading/trailing segments, click interactive segments, toggle focus/active, press `F12`.
- `shell/tree_view_basic/`
先看顶部中文说明“这个测试在验证什么功能”,再点击 disclosure 和树节点行,检查 `Hover / Focused / Selected / Expanded / Visible / Result`,按 `重置``截图(F12)` 或直接按 `F12`
- `shell/tree_view_multiselect/`
先看顶部中文说明“这个测试在验证什么功能”,再分别执行 `单击``Ctrl+单击``Shift+单击``Shift+Up/Down/Home/End``Right Click`,检查 `Primary / Selected Count / Selected Ids / Anchor / Current / Expanded / Visible / Result`
- `shell/tree_view_inline_rename/`
先看顶部中文说明“这个测试在验证什么功能”,再依次执行 `单击``F2`、输入字符、`Enter / Esc / 点击外部`,检查 `Rename Active / Rename Item / Draft / Committed / Result`
- `shell/list_view_basic/`
先看顶部中文说明“这个测试在验证什么功能”,再点击列表行,并在列表获得 focus 后按 `Up / Down / Home / End`,检查 `Hover / Focused / Selected / Current / Result`,按 `重置``截图(F12)` 或直接按 `F12`
- `shell/list_view_multiselect/`
先看顶部中文说明“这个测试在验证什么功能”,再分别执行 `单击``Ctrl+单击``Shift+单击``Shift+Up/Down/Home/End``Right Click`,检查 `Primary / Selected Count / Selected Ids / Anchor / Current / Result`
- `shell/list_view_inline_rename/`
先看顶部中文说明“这个测试在验证什么功能”,再依次执行 `单击``F2`、输入字符、`Enter / Esc / 点击外部`,检查 `Rename Active / Rename Item / Draft / Committed / Result`
- `shell/tab_strip_basic/`
Click `Document A / B / C`, click `X` on closable tabs, click content to focus, press `Left / Right / Home / End`, press `Reset`, press `F12`.
- `shell/viewport_slot_basic/`
Hover toolbar / surface / status bar, click surface to focus, hold and release left mouse to inspect capture, toggle `TopBar / StatusBar / Texture / Aspect`, press `F12`.
- `shell/viewport_shell_basic/`
Hover / click / drag the viewport shell surface, toggle `TopBar / BottomBar / Texture`, inspect left-side `Request Size / Input Rect / Hover Hit / Hover / Focus / Capture / Result`, press `Reset`, press `截图` or `F12`.
- `shell/workspace_viewport_compose/`
Click `切到 Scene / 切到 Document`, toggle `TopBar / BottomBar / Texture`, hover or click the center viewport only when `Scene` is selected, inspect `Selected Presentation / Request Size / Hover / Focus / Capture / Result`, press `Reset`, press `截图` or `F12`.
- `shell/workspace_interaction_basic/`
Click the center `Viewport` body to inspect unified focus/capture routing, click `Document` to verify fallback to DockHost placeholder, drag `root-split` to verify layout sync, inspect `Selected Presentation / Active Panel / Host Capture / root-split ratio`, press `Reset`, `Capture`, or `F12`.
- `state/panel_session_flow/`
Click `Hide Active / Show Doc A / Close Doc B / Open Doc B / Activate Details / Reset`, press `F12`.
- `state/panel_host_lifecycle/`
Click `Hide Active / Close Doc B / Open Doc B / Activate Details / Focus Active / Clear Focus / Reset`, inspect `Events` and the three host cards, press `Capture` or `F12`.
- `state/layout_persistence/`
Click `Hide Active -> Save Layout -> Close Doc B -> Load Layout`, click `Load Invalid`, press `F12`.
- `state/shortcut_dispatch/`
Press `Ctrl+P / Ctrl+H / Ctrl+W / Ctrl+O / Ctrl+R`, toggle `Text Input`, press `F12`.
- `state/viewport_input_bridge_basic/`
Hover surface, click and drag outside, roll the wheel, press `A / W / F / Space`, type characters, click outside to clear focus, press `F12`.
- Shared Editor integration hosts write screenshots only under the active CMake build directory.
- The output root is the executable directory: `.../Debug/captures/<scenario>/`.
- Example output: `<build>/tests/UI/Editor/integration/shell/object_field_basic/Debug/captures/object_field_basic/latest.png`
- History output: `<build>/tests/UI/Editor/integration/shell/object_field_basic/Debug/captures/object_field_basic/history/<timestamp>_<index>_<reason>.png`
- No new screenshot output is allowed under source-tree `tests/UI/.../captures/`.

View File

@@ -1,4 +1,5 @@
file(TO_CMAKE_PATH "${CMAKE_SOURCE_DIR}" XCENGINE_EDITOR_UI_TESTS_REPO_ROOT_PATH)
file(TO_CMAKE_PATH "${CMAKE_BINARY_DIR}" XCENGINE_EDITOR_UI_TESTS_BUILD_ROOT_PATH)
add_library(editor_ui_validation_registry STATIC
src/EditorValidationScenario.cpp
@@ -38,6 +39,7 @@ target_compile_definitions(editor_ui_integration_host
UNICODE
_UNICODE
XCENGINE_EDITOR_UI_TESTS_REPO_ROOT="${XCENGINE_EDITOR_UI_TESTS_REPO_ROOT_PATH}"
XCENGINE_EDITOR_UI_TESTS_BUILD_ROOT="${XCENGINE_EDITOR_UI_TESTS_BUILD_ROOT_PATH}"
)
if(MSVC)

View File

@@ -11,6 +11,14 @@
#include <unordered_set>
#include <vector>
#ifndef XCENGINE_EDITOR_UI_TESTS_REPO_ROOT
#define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "."
#endif
#ifndef XCENGINE_EDITOR_UI_TESTS_BUILD_ROOT
#define XCENGINE_EDITOR_UI_TESTS_BUILD_ROOT "."
#endif
namespace XCEngine::Tests::EditorUI {
namespace {
@@ -41,6 +49,55 @@ Application* GetApplicationFromWindow(HWND hwnd) {
return reinterpret_cast<Application*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
}
std::filesystem::path GetRepoRootPath() {
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 GetBuildRootPath() {
std::string root = XCENGINE_EDITOR_UI_TESTS_BUILD_ROOT;
if (root.size() >= 2u && root.front() == '"' && root.back() == '"') {
root = root.substr(1u, root.size() - 2u);
}
return std::filesystem::path(root).lexically_normal();
}
bool TryMakeRepoRelativePath(
const std::filesystem::path& absolutePath,
std::filesystem::path& outRelativePath) {
std::error_code errorCode = {};
outRelativePath = std::filesystem::relative(
absolutePath,
GetRepoRootPath(),
errorCode);
if (errorCode || outRelativePath.empty()) {
return false;
}
for (const auto& part : outRelativePath) {
if (part == "..") {
return false;
}
}
return true;
}
std::filesystem::path ResolveCaptureOutputRoot(
const std::filesystem::path& sourceCaptureRoot) {
const std::filesystem::path normalizedSourcePath =
sourceCaptureRoot.lexically_normal();
std::filesystem::path relativePath = {};
if (TryMakeRepoRelativePath(normalizedSourcePath, relativePath)) {
return (GetBuildRootPath() / relativePath).lexically_normal();
}
return (GetBuildRootPath() / "ui_test_captures" / normalizedSourcePath.filename())
.lexically_normal();
}
std::string TruncateText(const std::string& text, std::size_t maxLength) {
if (text.size() <= maxLength) {
return text;
@@ -230,7 +287,8 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) {
initialScenario = &GetDefaultEditorValidationScenario();
}
m_autoScreenshot.Initialize(initialScenario->captureRootPath);
m_autoScreenshot.Initialize(
ResolveCaptureOutputRoot(initialScenario->captureRootPath));
LoadStructuredScreen("startup");
return true;
}
@@ -406,7 +464,6 @@ bool Application::LoadStructuredScreen(const char* triggerReason) {
m_screenAsset = {};
m_screenAsset.screenId = scenario->id;
m_screenAsset.documentPath = scenario->documentPath.string();
m_screenAsset.themePath = scenario->themePath.string();
const bool loaded = m_screenPlayer.Load(m_screenAsset);
m_useStructuredScreen = loaded;
@@ -463,7 +520,6 @@ void Application::RebuildTrackedFileStates() {
};
appendTrackedPath(m_screenAsset.documentPath);
appendTrackedPath(m_screenAsset.themePath);
if (const auto* document = m_screenPlayer.GetDocument(); document != nullptr) {
for (const std::string& dependency : document->dependencies) {

View File

@@ -33,7 +33,6 @@ const std::array<EditorValidationScenario, 1>& GetEditorValidationScenarios() {
"shell",
"Editor 壳层 | 工作区组合",
RepoRelative("tests/UI/Editor/integration/shell/workspace_shell_compose/View.xcui"),
RepoRelative("tests/UI/Editor/integration/shared/themes/editor_validation.xctheme"),
RepoRelative("tests/UI/Editor/integration/shell/workspace_shell_compose/captures")
}
} };

View File

@@ -16,7 +16,6 @@ struct EditorValidationScenario {
std::string categoryId = {};
std::string displayName = {};
std::filesystem::path documentPath = {};
std::filesystem::path themePath = {};
std::filesystem::path captureRootPath = {};
};

View File

@@ -1,25 +1,9 @@
#pragma once
#include <XCEngine/Core/Containers/String.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/Resources/UI/UIDocumentCompiler.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEngine/UI/Style/DocumentStyleCompiler.h>
#include <XCEngine/UI/Style/Theme.h>
#include <filesystem>
#include <initializer_list>
#include <string>
#include <string_view>
namespace XCEngine::Tests::EditorUI {
struct EditorValidationThemeLoadResult {
::XCEngine::UI::Style::UITheme theme = {};
std::string error = {};
bool succeeded = false;
};
struct EditorValidationShellPalette {
::XCEngine::UI::UIColor windowBackground = ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f);
::XCEngine::UI::UIColor cardBackground = ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f);
@@ -41,178 +25,12 @@ struct EditorValidationShellMetrics {
float bodyFontSize = 12.0f;
};
inline ::XCEngine::UI::UIColor ToUIColor(const ::XCEngine::Math::Color& color) {
return ::XCEngine::UI::UIColor(color.r, color.g, color.b, color.a);
inline EditorValidationShellPalette GetEditorValidationShellPalette() {
return {};
}
inline bool TryResolveThemeFloat(
const ::XCEngine::UI::Style::UITheme& theme,
std::string_view tokenName,
float& outValue) {
const auto resolution =
theme.ResolveToken(std::string(tokenName), ::XCEngine::UI::Style::UIStyleValueType::Float);
if (resolution.status != ::XCEngine::UI::Style::UITokenResolveStatus::Resolved) {
return false;
}
const float* value = resolution.value.TryGetFloat();
if (value == nullptr) {
return false;
}
outValue = *value;
return true;
}
inline bool TryResolveThemeColor(
const ::XCEngine::UI::Style::UITheme& theme,
std::string_view tokenName,
::XCEngine::UI::UIColor& outColor) {
const auto resolution =
theme.ResolveToken(std::string(tokenName), ::XCEngine::UI::Style::UIStyleValueType::Color);
if (resolution.status != ::XCEngine::UI::Style::UITokenResolveStatus::Resolved) {
return false;
}
const ::XCEngine::Math::Color* value = resolution.value.TryGetColor();
if (value == nullptr) {
return false;
}
outColor = ToUIColor(*value);
return true;
}
inline float ResolveThemeFloatAliases(
const ::XCEngine::UI::Style::UITheme& theme,
std::initializer_list<std::string_view> tokenNames,
float fallbackValue) {
float resolvedValue = fallbackValue;
for (std::string_view tokenName : tokenNames) {
if (TryResolveThemeFloat(theme, tokenName, resolvedValue)) {
return resolvedValue;
}
}
return fallbackValue;
}
inline ::XCEngine::UI::UIColor ResolveThemeColorAliases(
const ::XCEngine::UI::Style::UITheme& theme,
std::initializer_list<std::string_view> tokenNames,
const ::XCEngine::UI::UIColor& fallbackValue) {
::XCEngine::UI::UIColor resolvedValue = fallbackValue;
for (std::string_view tokenName : tokenNames) {
if (TryResolveThemeColor(theme, tokenName, resolvedValue)) {
return resolvedValue;
}
}
return fallbackValue;
}
inline EditorValidationThemeLoadResult LoadEditorValidationTheme(
const std::filesystem::path& themePath) {
EditorValidationThemeLoadResult result = {};
::XCEngine::Resources::UIDocumentCompileResult compileResult = {};
const ::XCEngine::Containers::String pathString(themePath.generic_string().c_str());
if (!::XCEngine::Resources::CompileUIDocument(
::XCEngine::Resources::UIDocumentCompileRequest {
::XCEngine::Resources::UIDocumentKind::Theme,
pathString,
::XCEngine::Resources::GetUIDocumentDefaultRootTag(
::XCEngine::Resources::UIDocumentKind::Theme)
},
compileResult)) {
result.error = compileResult.errorMessage.Empty()
? std::string("Failed to compile editor validation theme document.")
: std::string(compileResult.errorMessage.CStr());
return result;
}
const auto styleCompileResult =
::XCEngine::UI::Style::CompileDocumentStyle(compileResult.document);
if (!styleCompileResult.succeeded) {
result.error = styleCompileResult.errorMessage;
return result;
}
result.theme = styleCompileResult.theme;
result.succeeded = true;
return result;
}
inline EditorValidationShellPalette ResolveEditorValidationShellPalette(
const ::XCEngine::UI::Style::UITheme& theme) {
EditorValidationShellPalette palette = {};
palette.windowBackground = ResolveThemeColorAliases(
theme,
{ "editor.color.validation.window", "color.bg.workspace" },
palette.windowBackground);
palette.cardBackground = ResolveThemeColorAliases(
theme,
{ "editor.color.validation.card", "color.bg.panel" },
palette.cardBackground);
palette.cardBorder = ResolveThemeColorAliases(
theme,
{ "editor.color.validation.card_border", "editor.color.menu_popup.border" },
palette.cardBorder);
palette.textPrimary = ResolveThemeColorAliases(
theme,
{ "editor.color.validation.text_primary", "color.text.primary" },
palette.textPrimary);
palette.textMuted = ResolveThemeColorAliases(
theme,
{ "editor.color.validation.text_muted", "color.text.muted" },
palette.textMuted);
palette.textWeak = ResolveThemeColorAliases(
theme,
{ "editor.color.validation.text_weak", "color.text.muted" },
palette.textWeak);
palette.textSuccess = ResolveThemeColorAliases(
theme,
{ "editor.color.validation.text_success" },
palette.textSuccess);
palette.buttonBackground = ResolveThemeColorAliases(
theme,
{ "editor.color.validation.button", "color.bg.selection" },
palette.buttonBackground);
palette.buttonHoverBackground = ResolveThemeColorAliases(
theme,
{ "editor.color.validation.button_hover", "editor.color.validation.button", "color.bg.selection" },
palette.buttonHoverBackground);
return palette;
}
inline EditorValidationShellMetrics ResolveEditorValidationShellMetrics(
const ::XCEngine::UI::Style::UITheme& theme) {
EditorValidationShellMetrics metrics = {};
metrics.margin = ResolveThemeFloatAliases(
theme,
{ "editor.space.validation.margin", "space.shell" },
metrics.margin);
metrics.gap = ResolveThemeFloatAliases(
theme,
{ "editor.space.validation.gap" },
metrics.gap);
metrics.cardRadius = ResolveThemeFloatAliases(
theme,
{ "editor.radius.validation.card", "radius.panel" },
metrics.cardRadius);
metrics.buttonRadius = ResolveThemeFloatAliases(
theme,
{ "editor.radius.validation.button", "radius.control" },
metrics.buttonRadius);
metrics.titleFontSize = ResolveThemeFloatAliases(
theme,
{ "editor.font.validation.title" },
metrics.titleFontSize);
metrics.bodyFontSize = ResolveThemeFloatAliases(
theme,
{ "editor.font.validation.body", "editor.font.field.value" },
metrics.bodyFontSize);
return metrics;
inline EditorValidationShellMetrics GetEditorValidationShellMetrics() {
return {};
}
} // namespace XCEngine::Tests::EditorUI

View File

@@ -1,177 +0,0 @@
<Theme name="EditorValidationTheme">
<Tokens>
<Color name="color.bg.workspace" value="#1A1A1A" />
<Color name="color.bg.panel" value="#262626" />
<Color name="color.bg.selection" value="#3A3A3A" />
<Color name="color.text.primary" value="#F2F2F2" />
<Color name="color.text.muted" value="#B4B4B4" />
<Color name="color.text.weak" value="#8F8F8F" />
<Color name="color.text.success" value="#A6C3A6" />
<Color name="color.border.panel" value="#4A4A4A" />
<Color name="color.button.default" value="#343434" />
<Color name="color.button.hover" value="#404040" />
<Spacing name="space.shell" value="18" />
<Spacing name="space.panel" value="12" />
<Radius name="radius.panel" value="10" />
<Radius name="radius.control" value="8" />
<Spacing name="editor.space.validation.margin" value="20" />
<Spacing name="editor.space.validation.gap" value="16" />
<Radius name="editor.radius.validation.card" value="radius.panel" />
<Radius name="editor.radius.validation.button" value="radius.control" />
<Number name="editor.font.validation.title" value="17" />
<Number name="editor.font.validation.body" value="12" />
<Color name="editor.color.validation.window" value="color.bg.workspace" />
<Color name="editor.color.validation.card" value="color.bg.panel" />
<Color name="editor.color.validation.card_border" value="color.border.panel" />
<Color name="editor.color.validation.text_primary" value="color.text.primary" />
<Color name="editor.color.validation.text_muted" value="color.text.muted" />
<Color name="editor.color.validation.text_weak" value="color.text.weak" />
<Color name="editor.color.validation.text_success" value="color.text.success" />
<Color name="editor.color.validation.button" value="color.button.default" />
<Color name="editor.color.validation.button_hover" value="color.button.hover" />
<Number name="editor.size.field.row" value="22" />
<Number name="editor.space.field.padding_x" value="12" />
<Number name="editor.space.field.label_gap" value="20" />
<Number name="editor.layout.field.control_column" value="236" />
<Number name="editor.space.field.control_trailing_inset" value="8" />
<Number name="editor.size.field.checkbox" value="14" />
<Number name="editor.size.field.control_min_width" value="96" />
<Number name="editor.space.field.vector_component_gap" value="6" />
<Number name="editor.size.field.vector_component_min_width" value="72" />
<Number name="editor.size.field.vector_prefix_width" value="9" />
<Number name="editor.space.field.vector_prefix_gap" value="4" />
<Number name="editor.space.field.vector_prefix_inset_x" value="0" />
<Number name="editor.space.field.vector_prefix_inset_y" value="-1" />
<Number name="editor.space.field.control_inset_y" value="1" />
<Number name="editor.space.field.label_inset_y" value="0" />
<Number name="editor.space.field.value_inset_x" value="5" />
<Number name="editor.space.field.value_inset_y" value="0" />
<Number name="editor.space.field.checkbox_glyph_inset_x" value="1" />
<Number name="editor.space.field.checkbox_glyph_inset_y" value="-2" />
<Number name="editor.size.field.dropdown_arrow_width" value="16" />
<Number name="editor.space.field.dropdown_arrow_inset_x" value="4" />
<Number name="editor.space.field.dropdown_arrow_inset_y" value="3" />
<Number name="editor.radius.field.row" value="0" />
<Number name="editor.radius.field.control" value="2" />
<Number name="editor.border.field" value="1" />
<Number name="editor.border.field.focus" value="1" />
<Number name="editor.font.field.label" value="11" />
<Number name="editor.font.field.value" value="12" />
<Number name="editor.font.field.glyph" value="10" />
<Color name="editor.color.field.row" value="#00000000" />
<Color name="editor.color.field.row_hover" value="#2A2A2AFF" />
<Color name="editor.color.field.row_active" value="#313131FF" />
<Color name="editor.color.field.border" value="#00000000" />
<Color name="editor.color.field.border_focus" value="#00000000" />
<Color name="editor.color.field.label" value="#C9C9C9FF" />
<Color name="editor.color.field.value" value="#EEEEEEFF" />
<Color name="editor.color.field.value_readonly" value="#8E8E8EFF" />
<Color name="editor.color.field.control" value="#2E2E2EFF" />
<Color name="editor.color.field.control_hover" value="#353535FF" />
<Color name="editor.color.field.control_editing" value="#3A3A3AFF" />
<Color name="editor.color.field.control_readonly" value="#252525FF" />
<Color name="editor.color.field.control_border" value="#242424FF" />
<Color name="editor.color.field.control_border_focus" value="#2A2A2AFF" />
<Color name="editor.color.field.vector_prefix" value="#222222FF" />
<Color name="editor.color.field.vector_prefix_border" value="#343434FF" />
<Color name="editor.color.field.vector_axis_x" value="#A8A8A8FF" />
<Color name="editor.color.field.vector_axis_y" value="#A8A8A8FF" />
<Color name="editor.color.field.vector_axis_z" value="#A8A8A8FF" />
<Color name="editor.color.field.checkbox" value="#1C1C1CFF" />
<Color name="editor.color.field.checkbox_hover" value="#202020FF" />
<Color name="editor.color.field.checkbox_border" value="#343434FF" />
<Color name="editor.color.field.checkbox_mark" value="#D8D8D8FF" />
<Color name="editor.color.field.dropdown_arrow" value="#D0D0D0FF" />
<Number name="editor.space.property.content_inset" value="6" />
<Number name="editor.space.property.section_gap" value="4" />
<Number name="editor.size.property.section_header" value="24" />
<Number name="editor.size.property.field_row" value="24" />
<Number name="editor.space.property.row_gap" value="1" />
<Number name="editor.size.property.disclosure" value="10" />
<Number name="editor.space.property.disclosure_label_gap" value="6" />
<Number name="editor.space.property.section_inset_y" value="5" />
<Number name="editor.space.property.disclosure_glyph_inset_x" value="1" />
<Number name="editor.space.property.disclosure_glyph_inset_y" value="-1" />
<Number name="editor.space.property.label_inset_y" value="5" />
<Number name="editor.space.property.value_inset_y" value="4" />
<Number name="editor.space.property.value_box_inset_y" value="2" />
<Number name="editor.space.property.value_box_inset_x" value="6" />
<Number name="editor.radius.property.panel" value="0" />
<Number name="editor.radius.property.value" value="2" />
<Number name="editor.border.property" value="1" />
<Number name="editor.border.property.focus" value="1" />
<Number name="editor.border.property.edit" value="1" />
<Number name="editor.font.property.section" value="11" />
<Number name="editor.font.property.disclosure" value="10" />
<Number name="editor.font.property.label" value="11" />
<Number name="editor.font.property.value" value="12" />
<Number name="editor.font.property.tag" value="10" />
<Color name="editor.color.property.surface" value="#232323FF" />
<Color name="editor.color.property.border" value="#171717FF" />
<Color name="editor.color.property.border_focus" value="#717171FF" />
<Color name="editor.color.property.section" value="#2B2B2BFF" />
<Color name="editor.color.property.section_hover" value="#313131FF" />
<Color name="editor.color.property.field_hover" value="#262626FF" />
<Color name="editor.color.property.field_selected" value="#303030FF" />
<Color name="editor.color.property.field_selected_focused" value="#393939FF" />
<Color name="editor.color.property.value" value="#1C1C1CFF" />
<Color name="editor.color.property.value_hover" value="#222222FF" />
<Color name="editor.color.property.value_editing" value="#292929FF" />
<Color name="editor.color.property.value_readonly" value="#171717FF" />
<Color name="editor.color.property.value_border" value="#343434FF" />
<Color name="editor.color.property.value_border_editing" value="#767676FF" />
<Color name="editor.color.property.disclosure" value="#C9C9C9FF" />
<Color name="editor.color.property.section_text" value="#E2E2E2FF" />
<Color name="editor.color.property.label" value="#CDCDCDFF" />
<Color name="editor.color.property.value_text" value="#EFEFEFFF" />
<Color name="editor.color.property.value_text_readonly" value="#8E8E8EFF" />
<Color name="editor.color.property.edit_tag" value="#82A7DAFF" />
<Number name="editor.space.menu_popup.padding_x" value="6" />
<Number name="editor.space.menu_popup.padding_y" value="5" />
<Number name="editor.size.menu_popup.item" value="24" />
<Number name="editor.size.menu_popup.separator" value="8" />
<Number name="editor.size.menu_popup.check_column" value="16" />
<Number name="editor.space.menu_popup.shortcut_gap" value="18" />
<Number name="editor.size.menu_popup.submenu_indicator" value="12" />
<Number name="editor.radius.menu_popup.row" value="3" />
<Number name="editor.radius.menu_popup.surface" value="4" />
<Number name="editor.space.menu_popup.label_inset_x" value="10" />
<Number name="editor.space.menu_popup.label_inset_y" value="-1" />
<Number name="editor.font.menu_popup.label" value="11" />
<Number name="editor.space.menu_popup.shortcut_inset_right" value="18" />
<Number name="editor.size.menu_popup.estimated_glyph_width" value="6" />
<Number name="editor.font.menu_popup.glyph" value="10" />
<Number name="editor.border.menu_popup.separator" value="1" />
<Number name="editor.border.menu_popup.surface" value="1" />
<Color name="editor.color.menu_popup.surface" value="#242424FF" />
<Color name="editor.color.menu_popup.border" value="#343434FF" />
<Color name="editor.color.menu_popup.item_hover" value="#2C2C2CFF" />
<Color name="editor.color.menu_popup.item_open" value="#313131FF" />
<Color name="editor.color.menu_popup.separator" value="#3A3A3AFF" />
<Color name="editor.color.menu_popup.label" value="#E8E8E8FF" />
<Color name="editor.color.menu_popup.text_muted" value="#B9B9B9FF" />
<Color name="editor.color.menu_popup.text_disabled" value="#757575FF" />
<Color name="editor.color.menu_popup.glyph" value="#D0D0D0FF" />
</Tokens>
<Widgets>
<Widget type="View" style="EditorValidationWorkspace">
<Property name="background" value="color.bg.workspace" />
<Property name="padding" value="space.shell" />
</Widget>
<Widget type="Card" style="EditorShellPanel">
<Property name="background" value="color.bg.panel" />
<Property name="radius" value="radius.panel" />
<Property name="padding" value="space.panel" />
</Widget>
<Widget type="Button" style="EditorShellChip">
<Property name="background" value="color.bg.selection" />
<Property name="radius" value="radius.control" />
</Widget>
</Widgets>
</Theme>

View File

@@ -25,6 +25,12 @@ endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/number_field_basic/CMakeLists.txt")
add_subdirectory(number_field_basic)
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/asset_field_basic/CMakeLists.txt")
add_subdirectory(asset_field_basic)
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/object_field_basic/CMakeLists.txt")
add_subdirectory(object_field_basic)
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/text_field_basic/CMakeLists.txt")
add_subdirectory(text_field_basic)
endif()

View File

@@ -0,0 +1,31 @@
add_executable(editor_ui_asset_field_basic_validation WIN32
main.cpp
)
target_include_directories(editor_ui_asset_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_asset_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_asset_field_basic_validation PRIVATE /utf-8 /FS)
set_property(TARGET editor_ui_asset_field_basic_validation PROPERTY
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
endif()
target_link_libraries(editor_ui_asset_field_basic_validation PRIVATE
XCUIEditorLib
XCUIEditorHost
)
set_target_properties(editor_ui_asset_field_basic_validation PROPERTIES
OUTPUT_NAME "XCUIEditorAssetFieldBasicValidation"
)

View File

@@ -0,0 +1,736 @@
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Foundation/UIEditorTheme.h>
#include <XCEditor/Fields/UIEditorAssetField.h>
#include <XCEditor/Fields/UIEditorAssetFieldInteraction.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 <iterator>
#include <string>
#include <string_view>
#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::UIColor;
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::UIEditorAssetFieldInteractionFrame;
using XCEngine::UI::Editor::UIEditorAssetFieldInteractionResult;
using XCEngine::UI::Editor::UIEditorAssetFieldInteractionState;
using XCEngine::UI::Editor::UpdateUIEditorAssetFieldInteraction;
using XCEngine::UI::Editor::Widgets::AppendUIEditorAssetField;
using XCEngine::UI::Editor::Widgets::HitTestUIEditorAssetField;
using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldHitTarget;
using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldHitTargetKind;
using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldPalette;
using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldSpec;
constexpr const wchar_t* kWindowClassName = L"XCUIEditorAssetFieldBasicValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | AssetField Basic";
enum class ActionId : unsigned char {
None = 0,
Reset,
Capture
};
struct ButtonLayout {
ActionId action = ActionId::None;
const char* label = "";
UIRect rect = {};
};
struct ScenarioLayout {
UIRect introRect = {};
UIRect controlRect = {};
UIRect stateRect = {};
UIRect previewRect = {};
UIRect fieldRect = {};
std::vector<ButtonLayout> buttons = {};
};
struct SampleAsset {
const char* assetId = "";
const char* displayName = "";
const char* statusText = "";
UIColor tint = {};
};
constexpr SampleAsset kSampleAssets[] = {
{
"assets/textures/crate_albedo",
"Crate_Albedo",
"Ready",
UIColor(0.30f, 0.55f, 0.84f, 1.0f)
},
{
"assets/materials/sci_fi_panel",
"SciFi_Panel",
"Dirty",
UIColor(0.77f, 0.56f, 0.28f, 1.0f)
}
};
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();
}
bool ContainsPoint(const UIRect& rect, float x, float y) {
return x >= rect.x &&
x <= rect.x + rect.width &&
y >= rect.y &&
y <= rect.y + rect.height;
}
std::int32_t MapAssetFieldKey(UINT keyCode) {
switch (keyCode) {
case VK_SPACE:
return static_cast<std::int32_t>(KeyCode::Space);
case VK_RETURN:
return static_cast<std::int32_t>(KeyCode::Enter);
case VK_DELETE:
return static_cast<std::int32_t>(KeyCode::Delete);
case VK_BACK:
return static_cast<std::int32_t>(KeyCode::Backspace);
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, 260.0f);
layout.controlRect = UIRect(margin, layout.introRect.y + layout.introRect.height + gap, leftWidth, 84.0f);
layout.stateRect = UIRect(
margin,
layout.controlRect.y + layout.controlRect.height + gap,
leftWidth,
(std::max)(220.0f, height - (layout.controlRect.y + layout.controlRect.height + gap) - margin));
layout.previewRect = UIRect(
leftWidth + margin * 2.0f,
margin,
(std::max)(420.0f, width - leftWidth - margin * 3.0f),
height - margin * 2.0f);
layout.fieldRect = UIRect(
layout.previewRect.x + 28.0f,
layout.previewRect.y + 96.0f,
(std::min)(460.0f, layout.previewRect.width - 56.0f),
22.0f);
const float buttonWidth = (layout.controlRect.width - 44.0f) * 0.5f;
const float buttonY = layout.controlRect.y + 32.0f;
layout.buttons = {
{ ActionId::Reset, "重置", UIRect(layout.controlRect.x + 14.0f, buttonY, buttonWidth, 36.0f) },
{ ActionId::Capture, "截图(F12)", UIRect(layout.controlRect.x + 26.0f + buttonWidth, buttonY, buttonWidth, 36.0f) }
};
return layout;
}
void DrawCard(
UIDrawList& drawList,
const UIRect& rect,
const XCEngine::Tests::EditorUI::EditorValidationShellPalette& shellPalette,
const XCEngine::Tests::EditorUI::EditorValidationShellMetrics& shellMetrics,
std::string_view title,
std::string_view subtitle = {}) {
drawList.AddFilledRect(rect, shellPalette.cardBackground, shellMetrics.cardRadius);
drawList.AddRectOutline(rect, shellPalette.cardBorder, 1.0f, shellMetrics.cardRadius);
drawList.AddText(
UIPoint(rect.x + 16.0f, rect.y + 14.0f),
std::string(title),
shellPalette.textPrimary,
shellMetrics.titleFontSize);
if (!subtitle.empty()) {
drawList.AddText(
UIPoint(rect.x + 16.0f, rect.y + 40.0f),
std::string(subtitle),
shellPalette.textMuted,
shellMetrics.bodyFontSize);
}
}
void DrawButton(
UIDrawList& drawList,
const ButtonLayout& button,
const XCEngine::Tests::EditorUI::EditorValidationShellPalette& shellPalette,
const XCEngine::Tests::EditorUI::EditorValidationShellMetrics& shellMetrics,
bool hovered) {
drawList.AddFilledRect(
button.rect,
hovered ? shellPalette.buttonHoverBackground : shellPalette.buttonBackground,
shellMetrics.buttonRadius);
drawList.AddRectOutline(button.rect, shellPalette.cardBorder, 1.0f, shellMetrics.buttonRadius);
drawList.AddText(
UIPoint(button.rect.x + 16.0f, button.rect.y + 10.0f),
button.label,
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
}
std::string DescribeHitTarget(const UIEditorAssetFieldHitTarget& hitTarget) {
switch (hitTarget.kind) {
case UIEditorAssetFieldHitTargetKind::ValueBox:
return "value_box";
case UIEditorAssetFieldHitTargetKind::PickerButton:
return "picker_button";
case UIEditorAssetFieldHitTargetKind::ClearButton:
return "clear_button";
case UIEditorAssetFieldHitTargetKind::Row:
return "row";
case UIEditorAssetFieldHitTargetKind::None:
default:
return "none";
}
}
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) {
UIInputEvent event = {};
event.type = UIInputEventType::KeyDown;
event.keyCode = keyCode;
return event;
}
UIInputEvent MakeFocusEvent(UIInputEventType type) {
UIInputEvent event = {};
event.type = type;
return event;
}
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 = static_cast<ScenarioApp*>(createStruct->lpCreateParams);
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(app));
return TRUE;
}
auto* app = reinterpret_cast<ScenarioApp*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
if (app == nullptr) {
return DefWindowProcW(hwnd, message, wParam, lParam);
}
return app->HandleMessage(hwnd, message, wParam, lParam);
}
bool Initialize(HINSTANCE hInstance, int nCmdShow) {
m_hInstance = hInstance;
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,
1440,
860,
nullptr,
nullptr,
hInstance,
this);
if (m_hwnd == nullptr) {
return false;
}
ShowWindow(m_hwnd, nCmdShow);
UpdateWindow(m_hwnd);
if (!m_renderer.Initialize(m_hwnd)) {
return false;
}
m_shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
m_shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
m_fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorAssetFieldMetrics();
m_fieldPalette = XCEngine::UI::Editor::ResolveUIEditorAssetFieldPalette();
m_captureRoot =
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/asset_field_basic/captures";
m_autoScreenshot.Initialize(m_captureRoot);
ResetScenario();
return true;
}
void Shutdown() {
m_autoScreenshot.Shutdown();
m_renderer.Shutdown();
if (m_hwnd != nullptr && IsWindow(m_hwnd)) {
DestroyWindow(m_hwnd);
}
m_hwnd = nullptr;
if (m_windowClassAtom != 0 && m_hInstance != nullptr) {
UnregisterClassW(kWindowClassName, m_hInstance);
m_windowClassAtom = 0;
}
}
void ResetScenario() {
m_spec = {};
m_spec.fieldId = "renderer.base_map";
m_spec.label = "Base Map";
m_spec.emptyText = "None (Asset)";
ApplySampleAsset(0u);
m_interactionState = {};
m_lastResult = "已重置到默认 AssetField 状态";
m_activateCount = 0u;
m_pressedAction = ActionId::None;
}
void ApplySampleAsset(std::size_t sampleIndex) {
m_currentSampleIndex = sampleIndex % std::size(kSampleAssets);
const SampleAsset& sample = kSampleAssets[m_currentSampleIndex];
m_spec.assetId = sample.assetId;
m_spec.displayName = sample.displayName;
m_spec.statusText = sample.statusText;
m_spec.tint = sample.tint;
}
void ApplyNextSampleAsset() {
ApplySampleAsset(m_currentSampleIndex + 1u);
}
ScenarioLayout GetLayout() const {
RECT clientRect = {};
GetClientRect(m_hwnd, &clientRect);
const float width = static_cast<float>((std::max)(clientRect.right - clientRect.left, 1L));
const float height = static_cast<float>((std::max)(clientRect.bottom - clientRect.top, 1L));
return BuildScenarioLayout(width, height, m_shellMetrics);
}
void RefreshFrame(const UIRect& fieldRect) {
m_frame = UpdateUIEditorAssetFieldInteraction(
m_interactionState,
m_spec,
fieldRect,
{},
m_fieldMetrics);
}
void RenderFrame() {
if (m_hwnd == nullptr) {
return;
}
RECT clientRect = {};
GetClientRect(m_hwnd, &clientRect);
const float width = static_cast<float>((std::max)(clientRect.right - clientRect.left, 1L));
const float height = static_cast<float>((std::max)(clientRect.bottom - clientRect.top, 1L));
const ScenarioLayout layout = BuildScenarioLayout(width, height, m_shellMetrics);
std::vector<UIInputEvent> events = std::move(m_pendingInputEvents);
m_pendingInputEvents.clear();
m_frame = UpdateUIEditorAssetFieldInteraction(
m_interactionState,
m_spec,
layout.fieldRect,
events,
m_fieldMetrics);
ApplyInteractionResult(m_frame.result, layout.fieldRect);
const UIEditorAssetFieldHitTarget currentHit =
HitTestUIEditorAssetField(m_frame.layout, m_mousePosition);
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("EditorAssetFieldBasic");
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), m_shellPalette.windowBackground);
DrawCard(
drawList,
layout.introRect,
m_shellPalette,
m_shellMetrics,
"这个测试在验证什么功能?",
"验证 Editor 基础 AssetField 的固定风格、清晰 hit target以及 activate / picker / clear 三类最小请求。");
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 74.0f),
"1. 字段视觉固定为 Unity 风格对象槽:标签、预览块、值文本、状态标记、选择按钮、清空按钮。",
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 98.0f),
"2. 点击值区域只产生 activateRequested基础层不决定 ping、打开面板或业务跳转。",
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 122.0f),
"3. 点击 o或 focused 后按 Enter / Space只产生 pickerRequested。",
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 146.0f),
"4. 点击 X或 focused 后按 Delete / Backspace清空当前引用。",
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 170.0f),
"5. 本壳程序只用两个固定样本资产模拟 picker 外部响应,不引入 object picker 弹窗系统。",
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 194.0f),
"6. 左侧状态区观察 hover / focus / assetId / displayName / status / resultF12 截图。",
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
DrawCard(drawList, layout.controlRect, m_shellPalette, m_shellMetrics, "操作");
for (const ButtonLayout& button : layout.buttons) {
DrawButton(
drawList,
button,
m_shellPalette,
m_shellMetrics,
ContainsPoint(button.rect, m_mousePosition.x, m_mousePosition.y));
}
DrawCard(
drawList,
layout.stateRect,
m_shellPalette,
m_shellMetrics,
"状态摘要",
"重点看 hit、focus、值是否变化以及请求是否保持薄语义。");
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 72.0f),
"Hover: " + DescribeHitTarget(currentHit),
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 96.0f),
std::string("Focused: ") + (m_interactionState.fieldState.focused ? "yes" : "no"),
m_interactionState.fieldState.focused ? m_shellPalette.textSuccess : m_shellPalette.textMuted,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 120.0f),
"AssetId: " + (m_spec.assetId.empty() ? std::string("(empty)") : m_spec.assetId),
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 144.0f),
"Display: " + (m_spec.displayName.empty() ? std::string("(empty)") : m_spec.displayName),
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 168.0f),
"Status: " + (m_spec.statusText.empty() ? std::string("(empty)") : m_spec.statusText),
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 192.0f),
"Activate Count: " + std::to_string(m_activateCount),
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 216.0f),
"Result: " + m_lastResult,
m_shellPalette.textMuted,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 240.0f),
m_autoScreenshot.HasPendingCapture()
? "截图排队中..."
: (m_autoScreenshot.GetLastCaptureSummary().empty()
? std::string("F12 -> tests/UI/Editor/integration/shell/asset_field_basic/captures/")
: m_autoScreenshot.GetLastCaptureSummary()),
m_shellPalette.textWeak,
m_shellMetrics.bodyFontSize);
DrawCard(
drawList,
layout.previewRect,
m_shellPalette,
m_shellMetrics,
"AssetField 预览",
"这里只放一个基础 AssetField不接业务面板也不接 picker。");
AppendUIEditorAssetField(
drawList,
layout.fieldRect,
m_spec,
m_interactionState.fieldState,
m_fieldPalette,
m_fieldMetrics);
const bool framePresented = m_renderer.Render(drawData);
m_autoScreenshot.CaptureIfRequested(
m_renderer,
drawData,
static_cast<unsigned int>(width),
static_cast<unsigned int>(height),
framePresented);
}
void ApplyInteractionResult(
const UIEditorAssetFieldInteractionResult& result,
const UIRect& fieldRect) {
bool refreshNeeded = false;
if (result.pickerRequested) {
ApplyNextSampleAsset();
m_lastResult = "收到 pickerRequested壳程序已用固定样本资产模拟外部选择结果";
refreshNeeded = true;
} else if (result.clearRequested) {
m_lastResult = "收到 clearRequested当前引用已清空";
refreshNeeded = true;
} else if (result.activateRequested) {
++m_activateCount;
m_lastResult = "收到 activateRequested基础层只发请求不绑定业务动作";
} else if (result.focusChanged) {
m_lastResult = std::string("焦点变化: ") +
(m_interactionState.fieldState.focused ? "focused" : "lost");
} else if (result.consumed) {
m_lastResult = "输入已被当前字段消费";
}
if (refreshNeeded) {
RefreshFrame(fieldRect);
}
}
void ExecuteAction(ActionId action) {
switch (action) {
case ActionId::Reset:
ResetScenario();
break;
case ActionId::Capture:
m_autoScreenshot.RequestCapture("manual_button");
m_lastResult = "已请求截图,输出到 captures/latest.png";
break;
case ActionId::None:
default:
break;
}
}
const ButtonLayout* HitTestAction(const ScenarioLayout& layout, float x, float y) const {
for (const ButtonLayout& button : layout.buttons) {
if (ContainsPoint(button.rect, x, y)) {
return &button;
}
}
return nullptr;
}
LRESULT HandleMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_CLOSE:
DestroyWindow(hwnd);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_SIZE:
if (wParam != SIZE_MINIMIZED) {
m_renderer.Resize(static_cast<UINT>(LOWORD(lParam)), static_cast<UINT>(HIWORD(lParam)));
}
return 0;
case WM_MOUSEMOVE: {
m_mousePosition = UIPoint(
static_cast<float>(GET_X_LPARAM(lParam)),
static_cast<float>(GET_Y_LPARAM(lParam)));
TRACKMOUSEEVENT trackEvent = {};
trackEvent.cbSize = sizeof(trackEvent);
trackEvent.dwFlags = TME_LEAVE;
trackEvent.hwndTrack = hwnd;
TrackMouseEvent(&trackEvent);
m_pendingInputEvents.push_back(MakePointerEvent(UIInputEventType::PointerMove, m_mousePosition));
return 0;
}
case WM_MOUSELEAVE:
m_mousePosition = UIPoint(-1000.0f, -1000.0f);
m_pendingInputEvents.push_back(MakePointerEvent(UIInputEventType::PointerLeave, m_mousePosition));
return 0;
case WM_LBUTTONDOWN: {
SetFocus(hwnd);
m_mousePosition = UIPoint(
static_cast<float>(GET_X_LPARAM(lParam)),
static_cast<float>(GET_Y_LPARAM(lParam)));
const ScenarioLayout layout = GetLayout();
const ButtonLayout* button = HitTestAction(layout, m_mousePosition.x, m_mousePosition.y);
m_pressedAction = button != nullptr ? button->action : ActionId::None;
if (button == nullptr) {
m_pendingInputEvents.push_back(
MakePointerEvent(UIInputEventType::PointerButtonDown, m_mousePosition, UIPointerButton::Left));
}
return 0;
}
case WM_LBUTTONUP: {
m_mousePosition = UIPoint(
static_cast<float>(GET_X_LPARAM(lParam)),
static_cast<float>(GET_Y_LPARAM(lParam)));
const ScenarioLayout layout = GetLayout();
const ButtonLayout* button = HitTestAction(layout, m_mousePosition.x, m_mousePosition.y);
if (m_pressedAction != ActionId::None &&
button != nullptr &&
button->action == m_pressedAction) {
ExecuteAction(button->action);
} else {
m_pendingInputEvents.push_back(
MakePointerEvent(UIInputEventType::PointerButtonUp, m_mousePosition, UIPointerButton::Left));
}
m_pressedAction = ActionId::None;
return 0;
}
case WM_SETFOCUS:
m_pendingInputEvents.push_back(MakeFocusEvent(UIInputEventType::FocusGained));
return 0;
case WM_KILLFOCUS:
m_pendingInputEvents.push_back(MakeFocusEvent(UIInputEventType::FocusLost));
return 0;
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
if (wParam == VK_F12) {
m_autoScreenshot.RequestCapture("manual_f12");
m_lastResult = "已请求截图,输出到 captures/latest.png";
return 0;
}
if (wParam == VK_F6) {
m_pendingInputEvents.push_back(MakeFocusEvent(UIInputEventType::FocusLost));
return 0;
}
if (const std::int32_t keyCode = MapAssetFieldKey(static_cast<UINT>(wParam));
keyCode != static_cast<std::int32_t>(KeyCode::None)) {
m_pendingInputEvents.push_back(MakeKeyEvent(keyCode));
return 0;
}
break;
case WM_PAINT:
RenderFrame();
ValidateRect(hwnd, nullptr);
return 0;
case WM_ERASEBKGND:
return 1;
default:
break;
}
return DefWindowProcW(hwnd, message, wParam, lParam);
}
HWND m_hwnd = nullptr;
HINSTANCE m_hInstance = nullptr;
ATOM m_windowClassAtom = 0;
NativeRenderer m_renderer = {};
AutoScreenshotController m_autoScreenshot = {};
std::filesystem::path m_captureRoot = {};
XCEngine::Tests::EditorUI::EditorValidationShellPalette m_shellPalette = {};
XCEngine::Tests::EditorUI::EditorValidationShellMetrics m_shellMetrics = {};
UIEditorAssetFieldSpec m_spec = {};
UIEditorAssetFieldInteractionState m_interactionState = {};
UIEditorAssetFieldInteractionFrame m_frame = {};
UIEditorAssetFieldPalette m_fieldPalette = {};
XCEngine::UI::Editor::Widgets::UIEditorAssetFieldMetrics m_fieldMetrics = {};
std::vector<UIInputEvent> m_pendingInputEvents = {};
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
ActionId m_pressedAction = ActionId::None;
std::string m_lastResult = {};
std::size_t m_currentSampleIndex = 0u;
std::size_t m_activateCount = 0u;
};
} // namespace
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) {
return ScenarioApp().Run(hInstance, nCmdShow);
}

View File

@@ -1,10 +1,10 @@
#ifndef NOMINMAX
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorBoolFieldInteraction.h>
#include <XCEditor/Core/UIEditorTheme.h>
#include <XCEditor/Widgets/UIEditorBoolField.h>
#include <XCEditor/Fields/UIEditorBoolFieldInteraction.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
#include <XCEditor/Fields/UIEditorBoolField.h>
#include "EditorValidationTheme.h"
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -47,7 +47,6 @@ using XCEngine::UI::Editor::Widgets::HitTestUIEditorBoolField;
using XCEngine::UI::Editor::Widgets::UIEditorBoolFieldHitTarget;
using XCEngine::UI::Editor::Widgets::UIEditorBoolFieldHitTargetKind;
using XCEngine::UI::Editor::Widgets::UIEditorBoolFieldSpec;
namespace Style = XCEngine::UI::Style;
constexpr const wchar_t* kWindowClassName = L"XCUIEditorBoolFieldBasicValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | BoolField Basic";
@@ -80,10 +79,6 @@ std::filesystem::path ResolveRepoRootPath() {
return std::filesystem::path(root).lexically_normal();
}
std::filesystem::path ResolveValidationThemePath() {
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
.lexically_normal();
}
bool ContainsPoint(const UIRect& rect, float x, float y) {
return x >= rect.x &&
@@ -371,14 +366,6 @@ private:
m_captureRoot =
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/bool_field_basic/captures";
m_autoScreenshot.Initialize(m_captureRoot);
const auto themeLoad =
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
if (themeLoad.succeeded) {
m_theme = themeLoad.theme;
m_themeStatus = "loaded";
} else {
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
}
ResetScenario();
return true;
@@ -407,7 +394,7 @@ private:
return BuildScenarioLayout(
width,
height,
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
}
void ResetScenario() {
@@ -430,7 +417,7 @@ private:
}
const ScenarioLayout layout = GetLayout();
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics(m_theme);
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics();
m_frame = UpdateUIEditorBoolFieldInteraction(
m_interactionState,
m_value,
@@ -531,7 +518,7 @@ private:
UIEditorBoolFieldInteractionResult PumpEvents(std::vector<UIInputEvent> events) {
const ScenarioLayout layout = GetLayout();
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics(m_theme);
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics();
m_frame = UpdateUIEditorBoolFieldInteraction(
m_interactionState,
m_value,
@@ -578,15 +565,15 @@ private:
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 shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
RefreshFrame();
const UIEditorBoolFieldHitTarget currentHit =
HitTestUIEditorBoolField(m_frame.layout, m_mousePosition);
const auto boolMetrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics(m_theme);
const auto boolPalette = XCEngine::UI::Editor::ResolveUIEditorBoolFieldPalette(m_theme);
const auto boolMetrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics();
const auto boolPalette = XCEngine::UI::Editor::ResolveUIEditorBoolFieldPalette();
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("EditorBoolFieldBasic");
@@ -597,8 +584,8 @@ private:
layout.introRect,
shellPalette,
shellMetrics,
"这个测试在验证什么功能",
"验证 Editor BoolField 的基础交互契约,不涉及 PropertyGrid 或业务 Inspector");
"这个测试在验证什么功能",
"验证 Editor BoolField 的点击切换、键盘切换和状态同步,样式固定为 Editor 字段风格");
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
"1. 点击 row 或 checkbox检查 true / false 是否稳定切换。",
@@ -606,7 +593,7 @@ private:
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
"2. 控件获得 focus 后按 Space / Enter也必须能切换值。",
"2. 控件获得 focus 后按 Space / Enter也必须能切换值。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
@@ -644,12 +631,12 @@ private:
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 94.0f),
std::string("Focused: ") + (m_interactionState.fieldState.focused ? "" : ""),
std::string("Focused: ") + (m_interactionState.fieldState.focused ? "" : ""),
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 118.0f),
std::string("Active: ") + (m_interactionState.fieldState.active ? "" : ""),
std::string("Active: ") + (m_interactionState.fieldState.active ? "" : ""),
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
@@ -674,11 +661,6 @@ private:
captureSummary,
shellPalette.textWeak,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 214.0f),
"Theme: " + m_themeStatus,
shellPalette.textWeak,
shellMetrics.bodyFontSize);
DrawCard(
drawList,
@@ -686,7 +668,7 @@ private:
shellPalette,
shellMetrics,
"BoolField 预览",
"这里只放一个 Unity 风格 BoolField。");
"这里只放一个固定样式 BoolField,不混入其他业务控件");
UIEditorBoolFieldSpec previewSpec = m_spec;
previewSpec.value = m_value;
AppendUIEditorBoolField(
@@ -715,12 +697,10 @@ private:
bool m_value = false;
UIEditorBoolFieldInteractionState m_interactionState = {};
UIEditorBoolFieldInteractionFrame m_frame = {};
Style::UITheme m_theme = {};
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
ActionId m_hoveredAction = ActionId::Reset;
bool m_hasHoveredAction = false;
std::string m_lastResult = {};
std::string m_themeStatus = "fallback";
};
} // namespace

View File

@@ -1,10 +1,10 @@
#ifndef NOMINMAX
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorColorFieldInteraction.h>
#include <XCEditor/Core/UIEditorTheme.h>
#include <XCEditor/Widgets/UIEditorColorField.h>
#include <XCEditor/Fields/UIEditorColorFieldInteraction.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
#include <XCEditor/Fields/UIEditorColorField.h>
#include "EditorValidationTheme.h"
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -48,8 +48,6 @@ using XCEngine::UI::Editor::Widgets::HitTestUIEditorColorField;
using XCEngine::UI::Editor::Widgets::UIEditorColorFieldHitTarget;
using XCEngine::UI::Editor::Widgets::UIEditorColorFieldHitTargetKind;
using XCEngine::UI::Editor::Widgets::UIEditorColorFieldSpec;
namespace Style = XCEngine::UI::Style;
constexpr const wchar_t* kWindowClassName = L"XCUIEditorColorFieldBasicValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | ColorField Basic";
@@ -81,11 +79,6 @@ std::filesystem::path ResolveRepoRootPath() {
return std::filesystem::path(root).lexically_normal();
}
std::filesystem::path ResolveValidationThemePath() {
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
.lexically_normal();
}
bool IsTruthyEnvironmentFlag(const char* name) {
const char* value = std::getenv(name);
if (value == nullptr || value[0] == '\0') {
@@ -135,7 +128,7 @@ ScenarioLayout BuildScenarioLayout(
layout.previewRect.x + 28.0f,
layout.previewRect.y + 72.0f,
360.0f,
32.0f);
22.0f);
const float buttonWidth = (layout.controlRect.width - 44.0f) * 0.5f;
const float buttonY = layout.controlRect.y + 32.0f;
@@ -379,14 +372,6 @@ private:
m_captureRoot =
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/color_field_basic/captures";
m_autoScreenshot.Initialize(m_captureRoot);
const auto themeLoad =
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
if (themeLoad.succeeded) {
m_theme = themeLoad.theme;
m_themeStatus = "loaded";
} else {
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
}
ResetScenario();
if (IsTruthyEnvironmentFlag("XCUI_COLOR_FIELD_OPEN_POPUP_ON_STARTUP")) {
@@ -405,6 +390,7 @@ private:
});
m_lastResult = "已自动打开 ColorField 弹窗";
}
return true;
}
@@ -431,7 +417,7 @@ private:
return BuildScenarioLayout(
width,
height,
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
}
UIRect GetViewportRect() const {
@@ -465,7 +451,7 @@ private:
}
const ScenarioLayout layout = GetLayout();
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics(m_theme);
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics();
m_frame = UpdateUIEditorColorFieldInteraction(
m_interactionState,
m_spec,
@@ -558,7 +544,7 @@ private:
UIEditorColorFieldInteractionResult PumpEvents(std::vector<UIInputEvent> events) {
const ScenarioLayout layout = GetLayout();
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics(m_theme);
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics();
m_frame = UpdateUIEditorColorFieldInteraction(
m_interactionState,
m_spec,
@@ -594,6 +580,7 @@ private:
case ActionId::Reset:
ResetScenario();
break;
case ActionId::Capture:
m_autoScreenshot.RequestCapture("manual_button");
m_lastResult = "已请求截图,输出到 captures/latest.png";
@@ -602,16 +589,7 @@ private:
}
std::string BuildColorSummary() const {
char buffer[128] = {};
std::snprintf(
buffer,
sizeof(buffer),
"R %.3f G %.3f B %.3f A %.3f",
m_spec.value.r,
m_spec.value.g,
m_spec.value.b,
m_spec.value.a);
return std::string(buffer);
return XCEngine::UI::Editor::Widgets::FormatUIEditorColorFieldRgbaText(m_spec);
}
void RenderFrame() {
@@ -620,19 +598,20 @@ private:
}
const UIRect viewportRect = GetViewportRect();
const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme);
const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme);
const ScenarioLayout layout = BuildScenarioLayout(viewportRect.width, viewportRect.height, shellMetrics);
const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
const ScenarioLayout layout = BuildScenarioLayout(
viewportRect.width,
viewportRect.height,
shellMetrics);
RefreshFrame();
const UIEditorColorFieldHitTarget currentHit =
HitTestUIEditorColorField(
m_frame.layout,
m_interactionState.colorFieldState.popupOpen,
m_mousePosition);
const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics(m_theme);
const auto fieldPalette = XCEngine::UI::Editor::ResolveUIEditorColorFieldPalette(m_theme);
const UIEditorColorFieldHitTarget currentHit = HitTestUIEditorColorField(
m_frame.layout,
m_interactionState.colorFieldState.popupOpen,
m_mousePosition);
const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics();
const auto fieldPalette = XCEngine::UI::Editor::ResolveUIEditorColorFieldPalette();
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("EditorColorFieldBasic");
@@ -643,31 +622,31 @@ private:
layout.introRect,
shellPalette,
shellMetrics,
"这个测试在验证什么功能",
"验证 Editor ColorField 的独立样式、弹窗结构和拾色交互,不混入 PropertyGrid");
"这个测试在验证什么功能",
"验证 Editor ColorField 的 swatch、popup、SV square、hue wheel、RGBA slider 和关闭行为");
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
"1. 点击 swatch检查 popup 是否在控件下方稳定展开",
"1. 点击 swatch打开 popup。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
"2. SV square 内拖拽,检查颜色是否连续变化。",
"2. 拖动 SV square检查颜色实时变化。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
"3. 拖 hue wheel R/G/B/A slider检查颜色和透明度是否同步更新",
"3. 拖 hue wheel R / G / B / A slider检查结果同步",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f),
"4. 点击右上角 close 或点击控件外部,检查 popup 是否关闭",
"4. 点击 close 或外部区域,关闭 popup。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
"5. 检查 Hexadecimal、数值框、handle、checkerboard 与截图路径是否同步",
"5. 左侧重点看 Hex、RGBA、Popup 和 Result",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
@@ -687,7 +666,7 @@ private:
shellPalette,
shellMetrics,
"状态摘要",
"重点检查 hover / popup / result / hex / rgba");
"重点 hoverpopup、hex、rgba 和结果");
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f),
"Hover: " + DescribeHitTarget(currentHit),
@@ -713,6 +692,7 @@ private:
"Result: " + m_lastResult,
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
const std::string captureSummary =
m_autoScreenshot.HasPendingCapture()
? "截图排队中..."
@@ -724,11 +704,6 @@ private:
captureSummary,
shellPalette.textWeak,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 214.0f),
"Theme: " + m_themeStatus,
shellPalette.textWeak,
shellMetrics.bodyFontSize);
DrawCard(
drawList,
@@ -736,7 +711,7 @@ private:
shellPalette,
shellMetrics,
"ColorField 预览",
"这里只放一个独立 ColorField便于单点检查样式与交互。");
"这里只放一个 ColorField用来验证 Editor 基础字段的 popup 交互。");
AppendUIEditorColorField(
drawList,
layout.fieldRect,
@@ -763,12 +738,10 @@ private:
UIEditorColorFieldSpec m_spec = {};
UIEditorColorFieldInteractionState m_interactionState = {};
UIEditorColorFieldInteractionFrame m_frame = {};
Style::UITheme m_theme = {};
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
ActionId m_hoveredAction = ActionId::Reset;
bool m_hasHoveredAction = false;
std::string m_lastResult = {};
std::string m_themeStatus = "fallback";
};
} // namespace

View File

@@ -2,13 +2,13 @@
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorCommandDispatcher.h>
#include <XCEditor/Core/UIEditorMenuModel.h>
#include <XCEditor/Core/UIEditorMenuSession.h>
#include <XCEditor/Core/UIEditorShortcutManager.h>
#include <XCEditor/Core/UIEditorWorkspaceController.h>
#include <XCEditor/Core/UIEditorWorkspaceModel.h>
#include <XCEditor/Widgets/UIEditorMenuPopup.h>
#include <XCEditor/Foundation/UIEditorCommandDispatcher.h>
#include <XCEditor/Shell/UIEditorMenuModel.h>
#include <XCEditor/Shell/UIEditorMenuSession.h>
#include <XCEditor/Foundation/UIEditorShortcutManager.h>
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
#include <XCEditor/Shell/UIEditorWorkspaceModel.h>
#include <XCEditor/Shell/UIEditorMenuPopup.h>
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -767,7 +767,7 @@ void ScenarioApp::ResetScenario() {
SetCustomResult(
"等待操作",
"Ready",
"右键 Context Target 打开菜单hover `Workspace Tools` 展开子菜单Esc / 外部点击关闭。");
"右键 Context Target 打开菜单hover `Workspace Tools` 展开子菜单Esc / 点击外部关闭。");
}
void ScenarioApp::OnResize(UINT width, UINT height) {
@@ -913,7 +913,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) {
SetCustomResult(
"Hover 收起更深层子菜单",
"Dismissed",
"鼠标移到普通菜单项后,更深层 child popup 已收起。closed: " +
"鼠标移到普通菜单项后,更深层 child popup 已收起。Closed: " +
JoinClosedPopupIds(mutation));
dirty = true;
}
@@ -925,7 +925,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) {
SetCustomResult(
"Hover popup 空白区",
"Dismissed",
"鼠标停留在 popup 空白区后,更深层 child popup 已回收。closed: " +
"鼠标停留在 popup 空白区后,更深层 child popup 已回收。Closed: " +
JoinClosedPopupIds(mutation));
dirty = true;
}
@@ -952,7 +952,7 @@ void ScenarioApp::HandleLeftClick(float x, float y) {
mutation.changed
? "已展开 `" + hoveredItem->label +
"` 子菜单。正常行为是 hover 也会直接展开。"
: "子菜单已经处于开状态。");
: "子菜单已经处于开状态。");
InvalidateRect(m_hwnd, nullptr, FALSE);
return;
}
@@ -983,7 +983,7 @@ void ScenarioApp::HandleLeftClick(float x, float y) {
SetCustomResult(
"点击 popup 空白区",
"Dismissed",
"点击当前 popup 空白区后,仅更深层子菜单被关闭。closed: " +
"点击当前 popup 空白区后,仅更深层子菜单被关闭。Closed: " +
JoinClosedPopupIds(mutation));
ClearHoverWhenMenuClosed();
InvalidateRect(m_hwnd, nullptr, FALSE);
@@ -998,7 +998,7 @@ void ScenarioApp::HandleLeftClick(float x, float y) {
"点击菜单外区域",
mutation.changed ? "Dismissed" : "NoOp",
mutation.changed
? "点击外部区域后,整条菜单链已关闭。closed: " +
? "点击外部区域后,整条菜单链已关闭。Closed: " +
JoinClosedPopupIds(mutation)
: "菜单链没有变化。");
ClearHoverWhenMenuClosed();
@@ -1015,7 +1015,7 @@ void ScenarioApp::HandleRightClick(float x, float y) {
"右键外部区域",
mutation.changed ? "Dismissed" : "NoOp",
mutation.changed
? "右键菜单外部区域后,整条菜单链已关闭。closed: " +
? "右键菜单外部区域后,整条菜单链已关闭。Closed: " +
JoinClosedPopupIds(mutation)
: "菜单链没有变化。");
ClearHoverWhenMenuClosed();
@@ -1045,7 +1045,7 @@ void ScenarioApp::HandleKeyDown(UINT keyCode) {
"Escape 关闭菜单",
mutation.changed ? "Dismissed" : "NoOp",
mutation.changed
? "按下 Escape 后topmost popup 已关闭。closed: " +
? "按下 Escape 后topmost popup 已关闭。Closed: " +
JoinClosedPopupIds(mutation)
: "当前没有可关闭的 popup。");
ClearHoverWhenMenuClosed();
@@ -1166,13 +1166,13 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height)
DrawCard(
drawList,
m_headerRect,
"测试功能Editor ContextMenu 基础层",
"这个测试验证什么功能?",
"本场景只验证 root popup 锚点、submenu hover、outside/Esc dismiss、命令派发不验证业务面板。");
drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 70.0f), "1. 在左侧 Context Target 内右键root popup 必须贴近鼠标位置打开。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 92.0f), "2. hover `Workspace Tools`,右侧 child popup 必须立即弹出。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 114.0f), "3. 点击 Show/Activate/Hide/Reset右侧 Details visible / active / activePanel 状态必须同步变化。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 136.0f), "4. 点击 popup 外部区域应整条关闭;如果 child popup 已打开Esc 先关 topmost再关 root。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 158.0f), "5. F12 截图R 直接触发 Reset Workspace便于确认命令派发仍正常。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 158.0f), "5. F12 截图R 直接触发 Reset Workspace便于确认命令派发仍正常。", kTextPrimary, 13.0f);
DrawCard(drawList, m_targetRect, "Context Target", "只在这里接受右键打开 ContextMenu。");
DrawCard(drawList, m_stateRect, "状态摘要", "重点看 popup chain、anchor 和 Details 状态。");
@@ -1190,22 +1190,22 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height)
drawList.AddRectOutline(targetSurface, kCardBorder, 1.0f, 10.0f);
drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 18.0f), "在这块区域右键打开 ContextMenu", kTextPrimary, 16.0f);
drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 46.0f), "当前 anchor: " + FormatAnchorPoint(m_contextAnchorPoint, m_hasContextAnchor), kTextMuted, 12.0f);
drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 68.0f), "Visible Panels: " + JoinVisiblePanelIds(workspace, session), kTextMuted, 12.0f);
drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 90.0f), "Details visible: " + std::string(detailsState != nullptr && detailsState->visible ? "true" : "false"), kTextMuted, 12.0f);
drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 112.0f), "Details active: " + std::string(workspace.activePanelId == "details" ? "true" : "false"), kTextMuted, 12.0f);
drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 68.0f), "可见面板: " + JoinVisiblePanelIds(workspace, session), kTextMuted, 12.0f);
drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 90.0f), "Details 可见: " + std::string(detailsState != nullptr && detailsState->visible ? "true" : "false"), kTextMuted, 12.0f);
drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 112.0f), "Details 激活: " + std::string(workspace.activePanelId == "details" ? "true" : "false"), kTextMuted, 12.0f);
const UIRect stateBox(m_stateRect.x + 18.0f, m_stateRect.y + 74.0f, m_stateRect.width - 36.0f, 220.0f);
drawList.AddFilledRect(stateBox, kIndicatorBg, 10.0f);
drawList.AddRectOutline(stateBox, kCardBorder, 1.0f, 10.0f);
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 14.0f), "Open root menu", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 14.0f), "已打开 root menu", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 34.0f), m_menuSession.HasOpenMenu() ? std::string(m_menuSession.GetOpenRootMenuId()) : "(none)", kTextPrimary, 14.0f);
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 62.0f), "Popup chain", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 62.0f), "Popup ", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 82.0f), JoinPopupChainIds(m_menuSession), kTextPrimary, 14.0f);
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 110.0f), "Submenu path", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 110.0f), "Submenu 路径", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 130.0f), JoinSubmenuPathIds(m_menuSession), kTextPrimary, 14.0f);
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 158.0f), "Active panel", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 158.0f), "当前激活面板", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 178.0f), workspace.activePanelId.empty() ? "(none)" : workspace.activePanelId, kTextPrimary, 14.0f);
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 202.0f), "Menu validation: " + std::string(menuValidation.IsValid() ? "OK" : menuValidation.message), menuValidation.IsValid() ? kSuccess : kDanger, 12.0f);
drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 202.0f), "菜单验证: " + std::string(menuValidation.IsValid() ? "OK" : menuValidation.message), menuValidation.IsValid() ? kSuccess : kDanger, 12.0f);
const std::string captureSummary =
m_autoScreenshot.HasPendingCapture()

View File

@@ -2,10 +2,10 @@
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorDockHostInteraction.h>
#include <XCEditor/Core/UIEditorWorkspaceController.h>
#include <XCEditor/Core/UIEditorWorkspaceModel.h>
#include <XCEditor/Widgets/UIEditorDockHost.h>
#include <XCEditor/Shell/UIEditorDockHostInteraction.h>
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
#include <XCEditor/Shell/UIEditorWorkspaceModel.h>
#include <XCEditor/Shell/UIEditorDockHost.h>
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -584,15 +584,15 @@ private:
UIDrawList& drawList = drawData.EmplaceDrawList("DockHostBasic");
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg);
DrawCard(drawList, m_introRect, "这个测试验证什么功能", "只验证 DockHost 基础交互 contract不做 editor 业务面板。");
DrawCard(drawList, m_introRect, "这个测试验证什么功能", "只验证 DockHost 基础交互 contract不做 editor 业务面板。");
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 72.0f), "1. 验证 splitter drag 是否只通过 DockHostInteraction + WorkspaceController 完成。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 94.0f), "2. 验证 tab activate / tab close / standalone panel activate / panel close。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 116.0f), "3. 验证 active panel、visible panels、split ratio 是否统一收口到 controller。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 138.0f), "4. 验证 pointer capture / release 请求是否 contract 明确返回。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 162.0f), "建议操作: 先拖中间 splitter再点 Document A。", kTextWeak, 11.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 180.0f), "然后关闭 Document B最后点 Details Console 的 X。", kTextWeak, 11.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 138.0f), "4. 验证 pointer capture / release 请求是否通过 contract 明确返回。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 162.0f), "建议操作先拖中间 splitter再点 Document A。", kTextWeak, 11.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 180.0f), "然后关闭 Document B最后点 Details Console 的 X。", kTextWeak, 11.0f);
DrawCard(drawList, m_controlsRect, "操作", "只保留当前场景必要按钮。");
DrawCard(drawList, m_controlsRect, "操作", "这里只保留当前场景必要按钮。");
for (const ButtonState& button : m_buttons) {
DrawButton(drawList, button);
}
@@ -605,25 +605,25 @@ private:
};
addStateLine("Hover: " + DescribeHitTarget(m_interactionState.dockHostState.hoveredTarget), kTextPrimary, 11.0f);
addStateLine("Result: " + m_lastStatus, m_lastColor);
addStateLine("结果: " + m_lastStatus, m_lastColor);
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f), m_lastMessage, kTextMuted, 11.0f);
stateY += 34.0f;
addStateLine("Active Panel: " + (m_controller.GetWorkspace().activePanelId.empty() ? std::string("(none)") : m_controller.GetWorkspace().activePanelId));
addStateLine("Visible Panels: " + JoinVisiblePanelIds(m_controller), kTextWeak, 11.0f);
addStateLine("Focused: " + FormatBool(m_cachedFrame.focused), m_cachedFrame.focused ? kSuccess : kTextMuted);
addStateLine("Capture: " + FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kSuccess : kTextMuted);
addStateLine("当前面板: " + (m_controller.GetWorkspace().activePanelId.empty() ? std::string("(none)") : m_controller.GetWorkspace().activePanelId));
addStateLine("可见面板: " + JoinVisiblePanelIds(m_controller), kTextWeak, 11.0f);
addStateLine("焦点: " + FormatBool(m_cachedFrame.focused), m_cachedFrame.focused ? kSuccess : kTextMuted);
addStateLine("捕获: " + FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kSuccess : kTextMuted);
addStateLine(
"Dragging Splitter: " +
"拖拽 Splitter: " +
(m_interactionState.dockHostState.activeSplitterNodeId.empty()
? std::string("(none)")
: m_interactionState.dockHostState.activeSplitterNodeId),
kTextWeak,
11.0f);
addStateLine("root-split ratio: " + FormatFloat(m_controller.GetWorkspace().root.splitRatio), kTextWeak, 11.0f);
addStateLine("根分割比例: " + FormatFloat(m_controller.GetWorkspace().root.splitRatio), kTextWeak, 11.0f);
if (m_controller.GetWorkspace().root.children.size() > 1u &&
m_controller.GetWorkspace().root.children[1].children.size() > 1u) {
addStateLine(
"right-split ratio: " +
"右侧分割比例: " +
FormatFloat(m_controller.GetWorkspace().root.children[1].splitRatio),
kTextWeak,
11.0f);
@@ -638,7 +638,7 @@ private:
kTextWeak,
11.0f);
DrawCard(drawList, m_previewRect, "Preview", "真实 DockHost 交互预览,只看基础层,不接旧 editor。");
DrawCard(drawList, m_previewRect, "预览", "真实 DockHost 交互预览,只看基础层,不接旧 editor。");
drawList.AddFilledRect(m_dockHostRect, kPreviewBg, 8.0f);
AppendUIEditorDockHostBackground(drawList, m_cachedFrame.layout);
AppendUIEditorDockHostForeground(drawList, m_cachedFrame.layout);

View File

@@ -2,10 +2,12 @@
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorPanelRegistry.h>
#include <XCEditor/Core/UIEditorShellCompose.h>
#include <XCEditor/Core/UIEditorWorkspaceController.h>
#include <XCEditor/Core/UIEditorWorkspaceModel.h>
#include <XCEditor/Shell/UIEditorPanelRegistry.h>
#include <XCEditor/Shell/UIEditorShellCompose.h>
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
#include <XCEditor/Shell/UIEditorWorkspaceModel.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
#include "../../shared/src/EditorValidationTheme.h"
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -41,6 +43,8 @@ using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit;
using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack;
using XCEngine::UI::Editor::FindUIEditorWorkspaceViewportPresentationRequest;
using XCEngine::UI::Editor::GetUIEditorWorkspaceCommandStatusName;
using XCEngine::UI::Editor::ResolveUIEditorShellComposeMetrics;
using XCEngine::UI::Editor::ResolveUIEditorShellComposePalette;
using XCEngine::UI::Editor::ResolveUIEditorShellComposeRequest;
using XCEngine::UI::Editor::UIEditorPanelPresentationKind;
using XCEngine::UI::Editor::UIEditorPanelRegistry;
@@ -61,19 +65,12 @@ using XCEngine::UI::Editor::Host::NativeRenderer;
using XCEngine::UI::Editor::Widgets::UIEditorMenuBarItem;
using XCEngine::UI::Editor::Widgets::UIEditorStatusBarSegment;
using XCEngine::UI::Editor::Widgets::UIEditorStatusBarSlot;
const auto kShellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
const auto kShellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
constexpr const wchar_t* kWindowClassName = L"XCUIEditorShellComposeValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Shell Compose";
constexpr const wchar_t* kWindowTitle = L"XCUI <EFBFBD>༭??| <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>";
constexpr UIColor kWindowBg(0.10f, 0.10f, 0.10f, 1.0f);
constexpr UIColor kCardBg(0.17f, 0.17f, 0.17f, 1.0f);
constexpr UIColor kCardBorder(0.28f, 0.28f, 0.28f, 1.0f);
constexpr UIColor kTextPrimary(0.94f, 0.94f, 0.94f, 1.0f);
constexpr UIColor kTextMuted(0.72f, 0.72f, 0.72f, 1.0f);
constexpr UIColor kTextWeak(0.56f, 0.56f, 0.56f, 1.0f);
constexpr UIColor kButtonBg(0.24f, 0.24f, 0.24f, 1.0f);
constexpr UIColor kButtonOnBg(0.36f, 0.36f, 0.36f, 1.0f);
constexpr UIColor kButtonBorder(0.46f, 0.46f, 0.46f, 1.0f);
constexpr UIColor kSuccess(0.46f, 0.72f, 0.50f, 1.0f);
constexpr UIColor kDanger(0.78f, 0.35f, 0.35f, 1.0f);
@@ -103,6 +100,7 @@ std::filesystem::path ResolveRepoRootPath() {
return std::filesystem::path(root).lexically_normal();
}
bool ContainsPoint(const UIRect& rect, float x, float y) {
return x >= rect.x &&
x <= rect.x + rect.width &&
@@ -134,27 +132,27 @@ void DrawCard(
const UIRect& rect,
std::string_view title,
std::string_view subtitle = {}) {
drawList.AddFilledRect(rect, kCardBg, 10.0f);
drawList.AddRectOutline(rect, kCardBorder, 1.0f, 10.0f);
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 14.0f), std::string(title), kTextPrimary, 17.0f);
drawList.AddFilledRect(rect, kShellPalette.cardBackground, kShellMetrics.cardRadius);
drawList.AddRectOutline(rect, kShellPalette.cardBorder, 1.0f, kShellMetrics.cardRadius);
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 14.0f), std::string(title), kShellPalette.textPrimary, kShellMetrics.titleFontSize);
if (!subtitle.empty()) {
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 38.0f), std::string(subtitle), kTextMuted, 12.0f);
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 38.0f), std::string(subtitle), kShellPalette.textMuted, kShellMetrics.bodyFontSize);
}
}
void DrawButton(UIDrawList& drawList, const ButtonState& button) {
drawList.AddFilledRect(button.rect, button.selected ? kButtonOnBg : kButtonBg, 8.0f);
drawList.AddRectOutline(button.rect, kButtonBorder, 1.0f, 8.0f);
drawList.AddText(UIPoint(button.rect.x + 12.0f, button.rect.y + 10.0f), button.label, kTextPrimary, 12.0f);
drawList.AddFilledRect(button.rect, button.selected ? kShellPalette.buttonHoverBackground : kShellPalette.buttonBackground, kShellMetrics.buttonRadius);
drawList.AddRectOutline(button.rect, kShellPalette.cardBorder, 1.0f, kShellMetrics.buttonRadius);
drawList.AddText(UIPoint(button.rect.x + 12.0f, button.rect.y + 10.0f), button.label, kShellPalette.textPrimary, kShellMetrics.bodyFontSize);
}
UIEditorPanelRegistry BuildPanelRegistry() {
UIEditorPanelRegistry registry = {};
registry.panels = {
{ "hierarchy", "Hierarchy", UIEditorPanelPresentationKind::Placeholder, true, true, false },
{ "scene", "Scene", UIEditorPanelPresentationKind::ViewportShell, false, true, false },
{ "document", "Document", UIEditorPanelPresentationKind::Placeholder, true, true, true },
{ "inspector", "Inspector", UIEditorPanelPresentationKind::Placeholder, true, true, true }
{ "hierarchy", "层级", UIEditorPanelPresentationKind::Placeholder, true, true, false },
{ "scene", "场景", UIEditorPanelPresentationKind::ViewportShell, false, true, false },
{ "document", "文档", UIEditorPanelPresentationKind::Placeholder, true, true, true },
{ "inspector", "检视器", UIEditorPanelPresentationKind::Placeholder, true, true, true }
};
return registry;
}
@@ -165,7 +163,7 @@ UIEditorWorkspaceModel BuildWorkspace() {
"root-left-main",
UIEditorWorkspaceSplitAxis::Horizontal,
0.23f,
BuildUIEditorWorkspacePanel("hierarchy-node", "hierarchy", "Hierarchy", true),
BuildUIEditorWorkspacePanel("hierarchy-node", "hierarchy", "层级", true),
BuildUIEditorWorkspaceSplit(
"main-center-right",
UIEditorWorkspaceSplitAxis::Horizontal,
@@ -173,11 +171,11 @@ UIEditorWorkspaceModel BuildWorkspace() {
BuildUIEditorWorkspaceTabStack(
"center-tabs",
{
BuildUIEditorWorkspacePanel("scene-node", "scene", "Scene"),
BuildUIEditorWorkspacePanel("document-node", "document", "Document", true)
BuildUIEditorWorkspacePanel("scene-node", "scene", "场景"),
BuildUIEditorWorkspacePanel("document-node", "document", "文档", true)
},
0u),
BuildUIEditorWorkspacePanel("inspector-node", "inspector", "Inspector", true)));
BuildUIEditorWorkspacePanel("inspector-node", "inspector", "检视器", true)));
workspace.activePanelId = "scene";
return workspace;
}
@@ -318,7 +316,7 @@ private:
m_showTopBar = true;
m_showBottomBar = true;
m_textureEnabled = true;
m_lastResult = "Ready";
m_lastResult = "就绪";
}
void UpdateLayoutForCurrentWindow() {
@@ -350,13 +348,13 @@ private:
const float widthAvailable = m_controlsRect.width - 32.0f;
const bool sceneSelected = GetSelectedTabId() == "scene";
m_buttons = {
{ ActionId::ActivateScene, "切到 Scene", UIRect(left, top, widthAvailable, buttonHeight), sceneSelected },
{ ActionId::ActivateDocument, "切到 Document", UIRect(left, top + (buttonHeight + gap), widthAvailable, buttonHeight), !sceneSelected },
{ ActionId::ToggleTopBar, std::string("TopBar: ") + (m_showTopBar ? "" : ""), UIRect(left, top + (buttonHeight + gap) * 2.0f, widthAvailable, buttonHeight), m_showTopBar },
{ ActionId::ToggleBottomBar, std::string("BottomBar: ") + (m_showBottomBar ? "" : ""), UIRect(left, top + (buttonHeight + gap) * 3.0f, widthAvailable, buttonHeight), m_showBottomBar },
{ ActionId::ToggleTexture, std::string("Texture: ") + (m_textureEnabled ? "" : ""), UIRect(left, top + (buttonHeight + gap) * 4.0f, widthAvailable, buttonHeight), m_textureEnabled },
{ ActionId::ActivateScene, "Scene", UIRect(left, top, widthAvailable, buttonHeight), sceneSelected },
{ ActionId::ActivateDocument, "Document", UIRect(left, top + (buttonHeight + gap), widthAvailable, buttonHeight), !sceneSelected },
{ ActionId::ToggleTopBar, std::string("Top Bar: ") + (m_showTopBar ? "on" : "off"), UIRect(left, top + (buttonHeight + gap) * 2.0f, widthAvailable, buttonHeight), m_showTopBar },
{ ActionId::ToggleBottomBar, std::string("Bottom Bar: ") + (m_showBottomBar ? "on" : "off"), UIRect(left, top + (buttonHeight + gap) * 3.0f, widthAvailable, buttonHeight), m_showBottomBar },
{ ActionId::ToggleTexture, std::string("Texture: ") + (m_textureEnabled ? "on" : "off"), UIRect(left, top + (buttonHeight + gap) * 4.0f, widthAvailable, buttonHeight), m_textureEnabled },
{ ActionId::Reset, "Reset", UIRect(left, top + (buttonHeight + gap) * 5.0f, widthAvailable, buttonHeight), false },
{ ActionId::Capture, "截图", UIRect(left, top + (buttonHeight + gap) * 6.0f, widthAvailable, buttonHeight), false }
{ ActionId::Capture, "Capture", UIRect(left, top + (buttonHeight + gap) * 6.0f, widthAvailable, buttonHeight), false }
};
}
@@ -369,7 +367,7 @@ private:
};
model.statusSegments = {
UIEditorStatusBarSegment{ "mode", GetSelectedTabId() == "scene" ? "Scene" : "Document", UIEditorStatusBarSlot::Leading, {}, true, true, 86.0f },
UIEditorStatusBarSegment{ "chrome", std::string("Top ") + (m_showTopBar ? "On" : "Off"), UIEditorStatusBarSlot::Leading, {}, true, false, 82.0f },
UIEditorStatusBarSegment{ "chrome", std::string("Top ") + (m_showTopBar ? "on" : "off"), UIEditorStatusBarSlot::Leading, {}, true, false, 82.0f },
UIEditorStatusBarSegment{ "branch", m_textureEnabled ? "Texture" : "Fallback", UIEditorStatusBarSlot::Trailing, {}, true, true, 96.0f }
};
@@ -377,7 +375,7 @@ private:
presentation.panelId = "scene";
presentation.kind = UIEditorPanelPresentationKind::ViewportShell;
presentation.viewportShellModel.spec.chrome.title = "Scene";
presentation.viewportShellModel.spec.chrome.subtitle = "Editor Shell 基础层";
presentation.viewportShellModel.spec.chrome.subtitle = "Shell compose validation";
presentation.viewportShellModel.spec.chrome.showTopBar = m_showTopBar;
presentation.viewportShellModel.spec.chrome.showBottomBar = m_showBottomBar;
presentation.viewportShellModel.spec.chrome.topBarHeight = 40.0f;
@@ -387,16 +385,16 @@ private:
};
presentation.viewportShellModel.spec.statusSegments = {
{ "view", "Shell", UIEditorStatusBarSlot::Leading, {}, true, true, 64.0f },
{ "branch", m_textureEnabled ? "Texture" : "Fallback", UIEditorStatusBarSlot::Trailing, {}, true, false, 96.0f }
{ "branch", m_textureEnabled ? "纹理" : "回退", UIEditorStatusBarSlot::Trailing, {}, true, false, 96.0f }
};
if (m_textureEnabled) {
presentation.viewportShellModel.frame.hasTexture = true;
presentation.viewportShellModel.frame.texture = { 1u, 1280u, 720u };
presentation.viewportShellModel.frame.presentedSize = UISize(1280.0f, 720.0f);
presentation.viewportShellModel.frame.statusText = "Fake viewport frame";
presentation.viewportShellModel.frame.statusText = "Simulated viewport frame";
} else {
presentation.viewportShellModel.frame.hasTexture = false;
presentation.viewportShellModel.frame.statusText = "这里只验证 Editor shell compose不接 Scene/Game 业务。";
presentation.viewportShellModel.frame.statusText = "Simulated viewport frame";
}
model.workspacePresentations = { presentation };
return model;
@@ -411,6 +409,7 @@ private:
void UpdateShellFrame() {
const UIEditorShellComposeModel model = BuildShellModel();
const auto metrics = ResolveUIEditorShellComposeMetrics();
m_shellRequest = ResolveUIEditorShellComposeRequest(
m_shellRect,
m_controller.GetPanelRegistry(),
@@ -418,7 +417,8 @@ private:
m_controller.GetSession(),
model,
{},
m_shellState);
m_shellState,
metrics);
m_shellFrame = UpdateUIEditorShellCompose(
m_shellState,
m_shellRect,
@@ -426,7 +426,9 @@ private:
m_controller.GetWorkspace(),
m_controller.GetSession(),
model,
{});
{},
{},
metrics);
m_cachedModel = model;
}
@@ -445,30 +447,30 @@ private:
void ExecuteAction(ActionId action) {
switch (action) {
case ActionId::ActivateScene:
DispatchWorkspaceCommand(UIEditorWorkspaceCommandKind::ActivatePanel, "scene", "Activate Scene");
DispatchWorkspaceCommand(UIEditorWorkspaceCommandKind::ActivatePanel, "scene", "切到场景");
break;
case ActionId::ActivateDocument:
DispatchWorkspaceCommand(UIEditorWorkspaceCommandKind::ActivatePanel, "document", "Activate Document");
DispatchWorkspaceCommand(UIEditorWorkspaceCommandKind::ActivatePanel, "document", "切到文档");
break;
case ActionId::ToggleTopBar:
m_showTopBar = !m_showTopBar;
m_lastResult = m_showTopBar ? "TopBar 已打开" : "TopBar 已关闭";
m_lastResult = m_showTopBar ? "Top bar enabled" : "Top bar disabled";
break;
case ActionId::ToggleBottomBar:
m_showBottomBar = !m_showBottomBar;
m_lastResult = m_showBottomBar ? "BottomBar 已打开" : "BottomBar 已关闭";
m_lastResult = m_showBottomBar ? "Bottom bar enabled" : "Bottom bar disabled";
break;
case ActionId::ToggleTexture:
m_textureEnabled = !m_textureEnabled;
m_lastResult = m_textureEnabled ? "切到 Texture 分支" : "切到 Fallback 分支";
m_lastResult = m_textureEnabled ? "Texture branch enabled" : "Fallback branch enabled";
break;
case ActionId::Reset:
ResetScenario();
m_lastResult = "状态已重置";
m_lastResult = "Scenario reset";
break;
case ActionId::Capture:
m_autoScreenshot.RequestCapture("manual_button");
m_lastResult = "截图已排队";
m_lastResult = "Capture queued";
InvalidateRect(m_hwnd, nullptr, FALSE);
UpdateWindow(m_hwnd);
break;
@@ -504,53 +506,46 @@ private:
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("EditorShellCompose");
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg);
DrawCard(drawList, m_introRect, "测试功能Editor 根壳层 compose", "只验证 MenuBar / Workspace / StatusBar 三段根壳组合。");
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f), "检查 1顶部 MenuBar、中间 WorkspaceCompose、底部 StatusBar 必须各自占带,不能互相挤压或重叠。", kTextMuted, 11.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 88.0f), "检查 2切换 Scene / Document 时,只影响中间 Workspace body presentation不应破坏顶栏和底栏。", kTextMuted, 11.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 110.0f), "检查 3切换 TopBar / BottomBar / Texture 时Request Size 必须同步,但 MenuBar / StatusBar 位置保持稳定。", kTextMuted, 11.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 132.0f), "这一层暂时不验证 menu popup 或 shortcut只验证 Editor 根框架 compose", kTextWeak, 11.0f);
DrawCard(drawList, m_controlsRect, "操作", "这里只保留当前这个根壳层需要检查的 7 个控件。");
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kShellPalette.windowBackground);
DrawCard(drawList, m_introRect, "Validation Goal", "Verify root shell composition only.");
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f), "1. Menu bar, workspace, and status bar must compose into one stable shell.", kShellPalette.textMuted, 11.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 88.0f), "2. Scene should host the viewport shell, while Document should fall back to a placeholder.", kShellPalette.textMuted, 11.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 110.0f), "3. Top bar, bottom bar, and texture toggles must update layout and branch selection together.", kShellPalette.textMuted, 11.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 132.0f), "4. This scene validates shell composition only. No business panels are involved.", kShellPalette.textWeak, 11.0f);
DrawCard(drawList, m_controlsRect, "Actions", "Only controls required for this validation are exposed.");
for (const ButtonState& button : m_buttons) {
DrawButton(drawList, button);
}
DrawCard(drawList, m_stateRect, "状态", "左侧直接回显根壳层三段布局与中间 workspace request。");
DrawCard(drawList, m_stateRect, "State", "Inspect current shell layout, request size, and validation result.");
float stateY = m_stateRect.y + 66.0f;
auto addStateLine = [&](std::string text, const UIColor& color, float fontSize = 12.0f) {
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY), std::move(text), color, fontSize);
stateY += 18.0f;
};
addStateLine("Active Panel: " + m_controller.GetWorkspace().activePanelId, kTextPrimary);
addStateLine("Selected Tab: " + GetSelectedTabId(), kTextPrimary);
addStateLine("Selected Presentation: " + selectedPresentation, kTextPrimary);
addStateLine("MenuBar Rect: " + FormatRect(m_shellFrame.layout.menuBarRect), kTextMuted, 11.0f);
addStateLine("Workspace Rect: " + FormatRect(m_shellFrame.layout.workspaceRect), kTextMuted, 11.0f);
addStateLine("StatusBar Rect: " + FormatRect(m_shellFrame.layout.statusBarRect), kTextMuted, 11.0f);
addStateLine("Requested viewport size: " + (viewportRequest != nullptr ? FormatSize(viewportRequest->viewportShellRequest.requestedViewportSize) : std::string("n/a")), kShellPalette.textPrimary);
addStateLine("Result: " + m_lastResult, kShellPalette.textMuted);
addStateLine(
"Request Size: " +
(viewportRequest != nullptr
? FormatSize(viewportRequest->viewportShellRequest.requestedViewportSize)
: std::string("n/a")),
kTextPrimary);
addStateLine("Result: " + m_lastResult, kTextMuted);
addStateLine(
validation.IsValid() ? "Workspace Validation: OK" : "Workspace Validation: " + validation.message,
validation.IsValid() ? "工作区校验:正常" : "工作区校验:" + validation.message,
validation.IsValid() ? kSuccess : kDanger,
11.0f);
const std::string captureSummary =
m_autoScreenshot.HasPendingCapture()
? "截图排队中..."
? "Capture queued..."
: (m_autoScreenshot.GetLastCaptureSummary().empty()
? std::string("截图:F12 或按钮 -> editor_shell_compose/captures/")
? std::string("F12 or the Capture button -> editor_shell_compose/captures/")
: m_autoScreenshot.GetLastCaptureSummary());
addStateLine(captureSummary, kTextWeak, 11.0f);
DrawCard(drawList, m_previewRect, "Preview", "这里只有 Editor 根壳层 compose不混入业务面板。");
AppendUIEditorShellCompose(drawList, m_shellFrame, m_cachedModel, m_shellState);
addStateLine(captureSummary, kShellPalette.textWeak, 11.0f);
DrawCard(drawList, m_previewRect, "Preview", "Live UIEditorShellCompose preview.");
const auto palette = ResolveUIEditorShellComposePalette();
const auto metrics = ResolveUIEditorShellComposeMetrics();
AppendUIEditorShellCompose(
drawList,
m_shellFrame,
m_cachedModel,
m_shellState,
palette,
metrics);
const bool framePresented = m_renderer.Render(drawData);
m_autoScreenshot.CaptureIfRequested(

View File

@@ -2,13 +2,15 @@
#define NOMINMAX
#endif
#include "Core/EditorShellAsset.h"
#include "Shell/EditorShellAsset.h"
#include <XCEditor/Core/UIEditorCommandDispatcher.h>
#include <XCEditor/Core/UIEditorMenuModel.h>
#include <XCEditor/Core/UIEditorShellInteraction.h>
#include <XCEditor/Core/UIEditorWorkspaceController.h>
#include <XCEditor/Core/UIEditorWorkspaceModel.h>
#include <XCEditor/Foundation/UIEditorCommandDispatcher.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
#include <XCEditor/Shell/UIEditorMenuModel.h>
#include <XCEditor/Shell/UIEditorShellInteraction.h>
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
#include <XCEditor/Shell/UIEditorWorkspaceModel.h>
#include "../../shared/src/EditorValidationTheme.h"
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -19,6 +21,8 @@
#include <windowsx.h>
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <filesystem>
#include <sstream>
#include <string>
@@ -54,6 +58,8 @@ using XCEngine::UI::Editor::EditorShellAsset;
using XCEngine::UI::Editor::EditorShellAssetValidationResult;
using XCEngine::UI::Editor::FindUIEditorWorkspaceViewportPresentationFrame;
using XCEngine::UI::Editor::GetUIEditorCommandDispatchStatusName;
using XCEngine::UI::Editor::ResolveUIEditorShellInteractionMetrics;
using XCEngine::UI::Editor::ResolveUIEditorShellInteractionPalette;
using XCEngine::UI::Editor::UpdateUIEditorShellInteraction;
using XCEngine::UI::Editor::UIEditorCommandDispatchResult;
using XCEngine::UI::Editor::UIEditorCommandDispatcher;
@@ -85,19 +91,12 @@ using XCEngine::UI::Editor::Widgets::UIEditorStatusBarSlot;
using XCEngine::UI::Widgets::UIPopupDismissReason;
using XCEngine::UI::Editor::Host::AutoScreenshotController;
using XCEngine::UI::Editor::Host::NativeRenderer;
const auto kShellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
const auto kShellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
constexpr const wchar_t* kWindowClassName = L"XCUIEditorShellInteractionValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Shell Interaction";
constexpr UIColor kWindowBg(0.10f, 0.10f, 0.10f, 1.0f);
constexpr UIColor kCardBg(0.17f, 0.17f, 0.17f, 1.0f);
constexpr UIColor kCardBorder(0.28f, 0.28f, 0.28f, 1.0f);
constexpr UIColor kTextPrimary(0.94f, 0.94f, 0.94f, 1.0f);
constexpr UIColor kTextMuted(0.72f, 0.72f, 0.72f, 1.0f);
constexpr UIColor kTextWeak(0.56f, 0.56f, 0.56f, 1.0f);
constexpr UIColor kButtonBg(0.24f, 0.24f, 0.24f, 1.0f);
constexpr UIColor kButtonHover(0.32f, 0.32f, 0.32f, 1.0f);
constexpr UIColor kButtonBorder(0.46f, 0.46f, 0.46f, 1.0f);
constexpr UIColor kSuccess(0.46f, 0.72f, 0.50f, 1.0f);
constexpr UIColor kWarning(0.82f, 0.67f, 0.35f, 1.0f);
constexpr UIColor kDanger(0.78f, 0.35f, 0.35f, 1.0f);
@@ -122,6 +121,24 @@ std::filesystem::path ResolveRepoRootPath() {
return std::filesystem::path(root).lexically_normal();
}
bool IsAutoCaptureOnStartupEnabled() {
const char* value = std::getenv("XCUI_AUTO_CAPTURE_ON_STARTUP");
if (value == nullptr || value[0] == '\0') {
return false;
}
std::string normalized = value;
for (char& character : normalized) {
character = static_cast<char>(std::tolower(static_cast<unsigned char>(character)));
}
return normalized != "0" &&
normalized != "false" &&
normalized != "off" &&
normalized != "no";
}
bool ContainsPoint(const UIRect& rect, float x, float y) {
return x >= rect.x &&
x <= rect.x + rect.width &&
@@ -248,18 +265,18 @@ void DrawCard(
const UIRect& rect,
std::string_view title,
std::string_view subtitle = {}) {
drawList.AddFilledRect(rect, kCardBg, 10.0f);
drawList.AddRectOutline(rect, kCardBorder, 1.0f, 10.0f);
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 14.0f), std::string(title), kTextPrimary, 17.0f);
drawList.AddFilledRect(rect, kShellPalette.cardBackground, kShellMetrics.cardRadius);
drawList.AddRectOutline(rect, kShellPalette.cardBorder, 1.0f, kShellMetrics.cardRadius);
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 14.0f), std::string(title), kShellPalette.textPrimary, kShellMetrics.titleFontSize);
if (!subtitle.empty()) {
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 38.0f), std::string(subtitle), kTextMuted, 12.0f);
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 38.0f), std::string(subtitle), kShellPalette.textMuted, kShellMetrics.bodyFontSize);
}
}
void DrawButton(UIDrawList& drawList, const ButtonState& button) {
drawList.AddFilledRect(button.rect, button.hovered ? kButtonHover : kButtonBg, 8.0f);
drawList.AddRectOutline(button.rect, kButtonBorder, 1.0f, 8.0f);
drawList.AddText(UIPoint(button.rect.x + 14.0f, button.rect.y + 10.0f), button.label, kTextPrimary, 12.0f);
drawList.AddFilledRect(button.rect, button.hovered ? kShellPalette.buttonHoverBackground : kShellPalette.buttonBackground, kShellMetrics.buttonRadius);
drawList.AddRectOutline(button.rect, kShellPalette.cardBorder, 1.0f, kShellMetrics.buttonRadius);
drawList.AddText(UIPoint(button.rect.x + 14.0f, button.rect.y + 10.0f), button.label, kShellPalette.textPrimary, kShellMetrics.bodyFontSize);
}
std::int32_t MapVirtualKeyToUIKeyCode(WPARAM wParam) {
@@ -433,7 +450,7 @@ EditorShellAsset BuildScenarioShellAsset() {
presentation.viewportShellModel.spec.chrome.showBottomBar = true;
presentation.viewportShellModel.frame.hasTexture = false;
presentation.viewportShellModel.frame.statusText =
"这里只验证 Editor 根壳交互,不接 editor 业务面板。";
"该场景只验证 Editor Shell 交互契约,不接 editor 业务面板。";
}
asset.shellDefinition.workspacePresentations.push_back(std::move(presentation));
}
@@ -441,7 +458,6 @@ EditorShellAsset BuildScenarioShellAsset() {
asset.shortcutAsset.commandRegistry = BuildCommandRegistry();
return asset;
}
class ScenarioApp {
public:
int Run(HINSTANCE hInstance, int nCmdShow);
@@ -485,7 +501,7 @@ private:
bool m_trackingMouseLeave = false;
std::string m_lastStatus = {};
std::string m_lastMessage = {};
UIColor m_lastColor = kTextMuted;
UIColor m_lastColor = kShellPalette.textMuted;
};
int ScenarioApp::Run(HINSTANCE hInstance, int nCmdShow) {
@@ -666,6 +682,11 @@ bool ScenarioApp::Initialize(HINSTANCE hInstance, int nCmdShow) {
}
ResetScenario();
if (IsAutoCaptureOnStartupEnabled()) {
m_lastStatus = "Capture";
m_lastMessage = "启动自动截图已排队。先核对首帧,再检查 root switch、child popup、dismiss 与 workspace suppression。";
m_lastColor = kWarning;
}
return true;
}
@@ -708,10 +729,9 @@ void ScenarioApp::ResetScenario() {
return;
}
m_lastStatus = "Ready";
m_lastMessage = "等待交互。这里只验证 Editor 根壳统一交互 contract不接旧 editor 业务";
m_lastMessage = "等待交互。重点检查根菜单切换、hover 子菜单、关闭链路与 workspace 抑制;设 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可抓启动首帧";
m_lastColor = kWarning;
}
void ScenarioApp::UpdateLayout() {
RECT clientRect = {};
GetClientRect(m_hwnd, &clientRect);
@@ -734,7 +754,6 @@ void ScenarioApp::UpdateLayout() {
{ ActionId::Capture, "截图(F12)", UIRect(left + buttonWidth + 12.0f, top, buttonWidth, 36.0f), false }
};
}
void ScenarioApp::HandleMouseMove(float x, float y) {
UpdateLayout();
for (ButtonState& button : m_buttons) {
@@ -775,17 +794,17 @@ void ScenarioApp::ExecuteAction(ActionId action) {
if (action == ActionId::Reset) {
ResetScenario();
m_lastStatus = "Ready";
m_lastMessage = "场景状态已重置。请重新检查 root open / child popup / dismiss 行为。";
m_lastMessage = "已重置场景。请重新检查 root open / child popup / dismiss 行为。";
m_lastColor = kWarning;
return;
}
m_autoScreenshot.RequestCapture("manual_button");
m_lastStatus = "Ready";
m_lastMessage = "截图已排队,输出到 tests/UI/Editor/integration/shell/editor_shell_interaction/captures/。";
m_lastMessage =
"截图请求已排队。检查状态卡片中的 Output/latest.png。";
m_lastColor = kWarning;
}
bool ScenarioApp::HasInteractiveCaptureState() const {
if (m_interactionState.workspaceInteractionState.dockHostInteractionState.splitterDragState.active) {
return true;
@@ -833,7 +852,7 @@ void ScenarioApp::SetInteractionResult(const UIEditorShellInteractionResult& res
if (result.commandTriggered) {
m_lastStatus = "Triggered";
m_lastMessage = "命令已命中,但本帧没有完成 dispatch。请检查 shell services / asset contract";
m_lastMessage = "命令已触发但尚未派发,请检查 dispatch 链路";
m_lastColor = kWarning;
return;
}
@@ -842,19 +861,19 @@ void ScenarioApp::SetInteractionResult(const UIEditorShellInteractionResult& res
if (!result.itemId.empty() && !result.menuMutation.openedPopupId.empty()) {
m_lastStatus = "Changed";
m_lastMessage =
"已展开子菜单 `" + result.itemId + "`。请检查 child popup 是否在 hover 直接弹出。";
"`" + result.itemId + "` 已展开 child popuphover 直接弹出。";
m_lastColor = kSuccess;
} else if (!result.menuId.empty() && !result.menuMutation.openedPopupId.empty()) {
m_lastStatus = "Changed";
m_lastMessage =
"当前根菜单为 `" + result.menuId + "`。请检查 root popup 是否在打开态下可直接切换。";
"当前根菜单切换为 `" + result.menuId + "`root popup 应立即切换。";
m_lastColor = kSuccess;
} else {
m_lastStatus = "Dismissed";
m_lastMessage =
"菜单链已收起。DismissReason = " +
FormatDismissReason(result.menuMutation.dismissReason) +
"请检查 outside pointer down / Esc / focus loss 的收束是否正确";
"请检查 outside pointer down / Esc / focus loss。";
m_lastColor = kWarning;
}
return;
@@ -888,41 +907,41 @@ void ScenarioApp::SetInteractionResult(const UIEditorShellInteractionResult& res
if (result.requestPointerCapture) {
m_lastStatus = "Capture";
m_lastMessage = "宿主已收到 root shell 返回的 pointer capture 请求";
m_lastMessage = "控件请求了 pointer capture。";
m_lastColor = kSuccess;
return;
}
if (result.releasePointerCapture) {
m_lastStatus = "Release";
m_lastMessage = "宿主已执行 root shell 返回的 pointer release。";
m_lastMessage = "控件释放了 pointer capture。";
m_lastColor = kWarning;
return;
}
if (result.consumed) {
m_lastStatus = "NoOp";
m_lastMessage = "这次输入被根壳交互层消费,但没有触发额外状态变化。";
m_lastMessage = "输入已被消费,但没有产生更高层状态变化。";
m_lastColor = kWarning;
}
}
void ScenarioApp::SetDispatchResult(const UIEditorCommandDispatchResult& result) {
m_lastStatus = std::string(GetUIEditorCommandDispatchStatusName(result.status));
m_lastMessage = result.message.empty() ? std::string("命令派发完成") : result.message;
m_lastMessage = result.message.empty() ? std::string("命令派发。") : result.message;
m_lastColor = m_lastStatus == "Dispatched" ? kSuccess : kDanger;
}
void ScenarioApp::RenderFrame() {
UpdateLayout();
const UIEditorShellInteractionDefinition definition = BuildInteractionDefinition();
const auto metrics = ResolveUIEditorShellInteractionMetrics();
m_cachedFrame = UpdateUIEditorShellInteraction(
m_interactionState,
m_controller,
m_shellRect,
definition,
m_pendingInputEvents,
m_shellServices);
m_shellServices,
metrics);
m_pendingInputEvents.clear();
ApplyHostCaptureRequests(m_cachedFrame.result);
SetInteractionResult(m_cachedFrame.result);
@@ -939,71 +958,73 @@ void ScenarioApp::RenderFrame() {
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("EditorShellInteraction");
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg);
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kShellPalette.windowBackground);
DrawCard(drawList, m_introRect, "这个测试验证什么功能", "只验证 Editor 根壳统一交互 contract不做业务面板。");
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 70.0f), "1. 验证 MenuBar 的 root open / root switch 行为是否统一稳定。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 92.0f), "2. 验证 hover 子菜单时child popup 是否直接展开,不需要额外点击。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 114.0f), "3. 验证 outside pointer down / Esc / focus loss 是否能正确收起 popup chain。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 136.0f), "4. 验证菜单打开时会屏蔽 workspace 输入菜单关闭后workspace 交互立即恢复。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 158.0f), "5. 验证菜单命令会在 root shell 内直接 dispatch宿主不再二次派发。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 182.0f), "建议操作:点击 Filehover `Workspace Tools`,点预览外空白处,再点 `Document` 或拖 splitter。", kTextWeak, 11.0f);
DrawCard(
drawList,
m_introRect,
"这个测试验证什么?",
"只验证 Editor Shell 统一交互 contract不接 editor 业务面板。");
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 70.0f), "1. 验证 MenuBar 的 root open / root switch 行为是否统一。", kShellPalette.textPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 92.0f), "2. 验证 hover 子菜单项时child popup 是否直接弹出。", kShellPalette.textPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 114.0f), "3. 验证 outside pointer down / Esc / focus loss 是否正确关闭 popup chain。", kShellPalette.textPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 136.0f), "4. 验证菜单打开时是否抑制 workspace 输入;菜单关闭后是否恢复。", kShellPalette.textPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 158.0f), "5. 验证命令是否由 root shell 直接 dispatch不依赖 editor 业务层。", kShellPalette.textPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 182.0f), "建议操作:点击 Filehover `Workspace Tools`;再切换 `Document` 标签并拖动 splitter。设 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可自动抓首帧。", kShellPalette.textWeak, 11.0f);
DrawCard(drawList, m_controlsRect, "操作", "保留这个场景必要的控制");
DrawCard(drawList, m_controlsRect, "操作", "放当前批次需要检查的交互");
for (const ButtonState& button : m_buttons) {
DrawButton(drawList, button);
}
DrawCard(drawList, m_stateRect, "状态", "重点检查根壳 contract 当前状态。");
DrawCard(drawList, m_stateRect, "状态", "直接观察当前 contract 状态。");
float stateY = m_stateRect.y + 66.0f;
auto addStateLine = [&](std::string label, std::string value, const UIColor& color, float fontSize = 12.0f) {
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY), std::move(label) + ": " + std::move(value), color, fontSize);
stateY += 20.0f;
};
addStateLine("Open Root", m_cachedFrame.openRootMenuId.empty() ? "(none)" : m_cachedFrame.openRootMenuId, kTextPrimary);
addStateLine("Popup Chain", JoinPopupChainIds(m_interactionState), kTextPrimary, 11.0f);
addStateLine("Submenu Path", JoinSubmenuPathIds(m_interactionState), kTextPrimary, 11.0f);
addStateLine("根菜单", m_cachedFrame.openRootMenuId.empty() ? "(none)" : m_cachedFrame.openRootMenuId, kShellPalette.textPrimary);
addStateLine("Popup ", JoinPopupChainIds(m_interactionState), kShellPalette.textPrimary, 11.0f);
addStateLine("子菜单路径", JoinSubmenuPathIds(m_interactionState), kShellPalette.textPrimary, 11.0f);
addStateLine("资源校验", m_assetValidation.IsValid() ? "OK" : m_assetValidation.message, m_assetValidation.IsValid() ? kSuccess : kDanger, 11.0f);
addStateLine("当前呈现", selectedPresentation, kShellPalette.textPrimary, 11.0f);
addStateLine("当前面板", m_controller.GetWorkspace().activePanelId.empty() ? "(none)" : m_controller.GetWorkspace().activePanelId, kShellPalette.textPrimary, 11.0f);
addStateLine("焦点", FormatBool(m_cachedFrame.focused), m_cachedFrame.focused ? kSuccess : kShellPalette.textMuted);
addStateLine("菜单模态", FormatBool(m_cachedFrame.result.menuModal), m_cachedFrame.result.menuModal ? kSuccess : kShellPalette.textMuted, 11.0f);
addStateLine("Workspace 抑制", FormatBool(m_cachedFrame.result.workspaceInputSuppressed), m_cachedFrame.result.workspaceInputSuppressed ? kWarning : kShellPalette.textMuted, 11.0f);
addStateLine(
"Asset Validation",
m_assetValidation.IsValid() ? "OK" : m_assetValidation.message,
m_assetValidation.IsValid() ? kSuccess : kDanger,
11.0f);
addStateLine("Selected Presentation", selectedPresentation, kTextPrimary, 11.0f);
addStateLine("Active Panel", m_controller.GetWorkspace().activePanelId.empty() ? "(none)" : m_controller.GetWorkspace().activePanelId, kTextPrimary, 11.0f);
addStateLine("Focused", FormatBool(m_cachedFrame.focused), m_cachedFrame.focused ? kSuccess : kTextMuted);
addStateLine("Menu Modal", FormatBool(m_cachedFrame.result.menuModal), m_cachedFrame.result.menuModal ? kSuccess : kTextMuted, 11.0f);
addStateLine(
"Workspace Suppressed",
FormatBool(m_cachedFrame.result.workspaceInputSuppressed),
m_cachedFrame.result.workspaceInputSuppressed ? kWarning : kTextMuted,
11.0f);
addStateLine(
"Command Dispatch",
"命令派发",
m_cachedFrame.result.commandDispatched
? std::string(GetUIEditorCommandDispatchStatusName(m_cachedFrame.result.commandDispatchResult.status))
: std::string("(none)"),
m_cachedFrame.result.commandDispatched && m_cachedFrame.result.commandDispatchResult.commandExecuted
? kSuccess
: kTextMuted,
: kShellPalette.textMuted,
11.0f);
addStateLine("Result", m_lastStatus, m_lastColor);
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f), m_lastMessage, kTextMuted, 11.0f);
addStateLine("结果", m_lastStatus, m_lastColor);
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f), m_lastMessage, kShellPalette.textMuted, 11.0f);
stateY += 34.0f;
addStateLine("Visible Panels", JoinVisiblePanelIds(m_controller.GetWorkspace(), m_controller.GetSession()), kTextWeak, 11.0f);
addStateLine("Host Capture", FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kSuccess : kTextMuted, 11.0f);
addStateLine("可见面板", JoinVisiblePanelIds(m_controller.GetWorkspace(), m_controller.GetSession()), kShellPalette.textWeak, 11.0f);
addStateLine("宿主捕获", FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kSuccess : kShellPalette.textMuted, 11.0f);
addStateLine(
"Screenshot",
"截图",
m_autoScreenshot.HasPendingCapture()
? "截图排队中..."
? "截图请求排队中..."
: (m_autoScreenshot.GetLastCaptureSummary().empty()
? std::string("F12 或 按钮 -> captures/")
? std::string("F12 或按钮 -> captures/")
: m_autoScreenshot.GetLastCaptureSummary()),
kTextWeak,
kShellPalette.textWeak,
11.0f);
DrawCard(drawList, m_previewRect, "Preview", "真实 UIEditorShellInteraction 预览,不接 editor 业务。");
AppendUIEditorShellInteraction(drawList, m_cachedFrame, m_interactionState);
DrawCard(drawList, m_previewRect, "预览", "这里是实际 UIEditorShellInteraction 预览,不接 editor 业务");
const auto palette = ResolveUIEditorShellInteractionPalette();
AppendUIEditorShellInteraction(
drawList,
m_cachedFrame,
m_interactionState,
palette,
metrics);
const bool framePresented = m_renderer.Render(drawData);
m_autoScreenshot.CaptureIfRequested(
@@ -1013,9 +1034,9 @@ void ScenarioApp::RenderFrame() {
static_cast<unsigned int>(height),
framePresented);
}
} // namespace
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) {
return ScenarioApp().Run(hInstance, nCmdShow);
}

View File

@@ -1,11 +1,11 @@
#ifndef NOMINMAX
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorEnumFieldInteraction.h>
#include <XCEditor/Core/UIEditorTheme.h>
#include <XCEditor/Widgets/UIEditorEnumField.h>
#include <XCEditor/Widgets/UIEditorMenuPopup.h>
#include <XCEditor/Fields/UIEditorEnumFieldInteraction.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
#include <XCEditor/Fields/UIEditorEnumField.h>
#include <XCEditor/Shell/UIEditorMenuPopup.h>
#include "EditorValidationTheme.h"
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -51,8 +51,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorEnumFieldHitTarget;
using XCEngine::UI::Editor::Widgets::UIEditorEnumFieldHitTargetKind;
using XCEngine::UI::Editor::Widgets::UIEditorEnumFieldSpec;
using XCEngine::UI::Editor::Widgets::UIEditorMenuPopupInvalidIndex;
namespace Style = XCEngine::UI::Style;
constexpr const wchar_t* kWindowClassName = L"XCUIEditorEnumFieldBasicValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | EnumField Basic";
@@ -84,11 +82,6 @@ std::filesystem::path ResolveRepoRootPath() {
return std::filesystem::path(root).lexically_normal();
}
std::filesystem::path ResolveValidationThemePath() {
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
.lexically_normal();
}
bool ContainsPoint(const UIRect& rect, float x, float y) {
return x >= rect.x &&
x <= rect.x + rect.width &&
@@ -142,7 +135,7 @@ ScenarioLayout BuildScenarioLayout(
layout.previewRect.x + 24.0f,
layout.previewRect.y + 82.0f,
340.0f,
32.0f);
22.0f);
const float buttonWidth = (layout.controlRect.width - 44.0f) * 0.5f;
const float buttonY = layout.controlRect.y + 32.0f;
@@ -387,14 +380,6 @@ private:
m_captureRoot =
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/enum_field_basic/captures";
m_autoScreenshot.Initialize(m_captureRoot);
const auto themeLoad =
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
if (themeLoad.succeeded) {
m_theme = themeLoad.theme;
m_themeStatus = "loaded";
} else {
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
}
ResetScenario();
return true;
@@ -423,7 +408,7 @@ private:
return BuildScenarioLayout(
width,
height,
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
}
UIRect GetViewportRect() const {
@@ -458,8 +443,8 @@ private:
}
const ScenarioLayout layout = GetLayout();
const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics(m_theme);
const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics(m_theme);
const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics();
const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics();
m_spec.selectedIndex = m_selectedIndex;
m_frame = UpdateUIEditorEnumFieldInteraction(
m_interactionState,
@@ -563,8 +548,8 @@ private:
UIEditorEnumFieldInteractionResult PumpEvents(std::vector<UIInputEvent> events) {
const ScenarioLayout layout = GetLayout();
const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics(m_theme);
const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics(m_theme);
const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics();
const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics();
m_spec.selectedIndex = m_selectedIndex;
m_frame = UpdateUIEditorEnumFieldInteraction(
m_interactionState,
@@ -581,7 +566,7 @@ private:
void UpdateResultText(const UIEditorEnumFieldInteractionResult& result) {
if (result.selectionChanged && m_selectedIndex < m_spec.options.size()) {
m_lastResult = std::string("已切换: ") + m_spec.options[m_selectedIndex];
m_lastResult = std::string("已切换选项: ") + m_spec.options[m_selectedIndex];
return;
}
if (result.popupOpened) {
@@ -627,17 +612,17 @@ private:
}
const UIRect viewportRect = GetViewportRect();
const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme);
const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme);
const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
const ScenarioLayout layout = BuildScenarioLayout(viewportRect.width, viewportRect.height, shellMetrics);
RefreshFrame();
const UIEditorEnumFieldHitTarget currentHit =
HitTestUIEditorEnumField(m_frame.layout, m_mousePosition);
const auto enumMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics(m_theme);
const auto enumPalette = XCEngine::UI::Editor::ResolveUIEditorEnumFieldPalette(m_theme);
const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics(m_theme);
const auto popupPalette = XCEngine::UI::Editor::ResolveUIEditorMenuPopupPalette(m_theme);
const auto enumMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics();
const auto enumPalette = XCEngine::UI::Editor::ResolveUIEditorEnumFieldPalette();
const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics();
const auto popupPalette = XCEngine::UI::Editor::ResolveUIEditorMenuPopupPalette();
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("EditorEnumFieldBasic");
@@ -648,26 +633,26 @@ private:
layout.introRect,
shellPalette,
shellMetrics,
"这个测试在验证什么功能",
"验证 Editor EnumField 的基础交互契约,不涉及 PropertyGrid 或业务 Inspector。");
"这个测试在验证什么功能",
"验证枚举字段的下拉打开/关闭、键盘切换、高亮同步,以及固定 Inspector 风格承载");
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
"1. 点击 value box 或 dropdown arrow检查下拉菜单是否展开/收起",
"1. 点击 value box 或 dropdown arrow应该打开或关闭下拉菜单",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
"2. 开后 hover 列表项,检查高亮是否稳定跟随",
"2. 开后检查 hover 高亮是否同步更新",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
"3. 获得 focus 后Up / Down / Home / End,再按 Enter / Space 选中,Esc 关闭。",
"3. 获得 focus 后Up / Down / Home / End 切换高亮;Enter / Space 确认;Esc 关闭。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f),
"4. 检查 Hover / Popup / Highlight / Selected / Result 是否同步更新",
"4. 观察 Hover / Popup / Highlight / Selected / Result 是否同步。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
@@ -735,11 +720,6 @@ private:
captureSummary,
shellPalette.textWeak,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 238.0f),
"Theme: " + m_themeStatus,
shellPalette.textWeak,
shellMetrics.bodyFontSize);
DrawCard(
drawList,
@@ -747,7 +727,7 @@ private:
shellPalette,
shellMetrics,
"EnumField 预览",
"这里只放一个 Unity 风格 EnumField");
"这里只放一个固定样式的枚举字段预览");
AppendUIEditorEnumField(
drawList,
layout.fieldRect,
@@ -783,12 +763,10 @@ private:
std::size_t m_selectedIndex = 0u;
UIEditorEnumFieldInteractionState m_interactionState = {};
UIEditorEnumFieldInteractionFrame m_frame = {};
Style::UITheme m_theme = {};
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
ActionId m_hoveredAction = ActionId::Reset;
bool m_hasHoveredAction = false;
std::string m_lastResult = {};
std::string m_themeStatus = "fallback";
};
} // namespace

View File

@@ -643,31 +643,31 @@ private:
DrawCard(
drawList,
layout.introRect,
"这个测试验证什么功能",
"只验证 Editor ListView 基础控件不涉及任何业务面板。");
"这个测试验证什么功能",
"只验证 Editor ListView 基础控件行布局、单选、focus 和键盘导航。不涉及任何业务面板。");
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
"1. 验证列表行垂直排是否稳定:主标题和次标题不能互相挤压。",
"1. 检查列表行垂直排是否稳定:主标题和次标题不能互相挤压。",
kTextPrimary,
12.0f);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
"2. 点击 row 只切换 selectionhover、selected、focused 三种状态要能区分。",
"2. 点击 row 只切换 selectionhover、selected、focused 必须能区分。",
kTextPrimary,
12.0f);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
"3. 列表获得 focus 后,按 Up / Down / Home / End 应稳定移动当前选择。",
"3. 列表获得 focus 后,按 Up / Down / Home / End 应稳定移动当前选择。",
kTextPrimary,
12.0f);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f),
"4. 点击列表外空白后focus 应清除,但 selection 不应丢失",
"4. 点击列表外空白后focus 应清除,但 selection 必须保留",
kTextPrimary,
12.0f);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
"5. 按 F12 手动截图;设置 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可自动截图。",
"5. 按 F12 手动截图;设置 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可启动自动截图。",
kTextPrimary,
12.0f);
@@ -727,7 +727,7 @@ private:
kTextWeak,
12.0f);
DrawCard(drawList, layout.previewRect, "ListView 预览", "这里只放一个 ListView不混入 Project/Console 等业务内容。");
DrawCard(drawList, layout.previewRect, "ListView 预览", "这里只放一个 ListView不混入 Project / Console 等业务内容。");
AppendUIEditorListViewBackground(
drawList,
m_listFrame.layout,

View File

@@ -1,10 +1,11 @@
#ifndef NOMINMAX
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Collections/UIEditorInlineRenameSession.h>
#include <XCEditor/Collections/UIEditorListView.h>
#include <XCEditor/Collections/UIEditorListViewInteraction.h>
#include <XCEditor/Fields/UIEditorFieldStyle.h>
#include <XCEditor/Fields/UIEditorTextField.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
#include "EditorValidationTheme.h"
@@ -14,7 +15,6 @@
#include <XCEngine/Input/InputTypes.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEngine/UI/Style/Theme.h>
#include <XCEngine/UI/Widgets/UISelectionModel.h>
#include <windows.h>
@@ -33,6 +33,8 @@
#define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "."
#endif
// 场景说明:验证固定样式下 ListView 的 inline rename 提交、取消与外部点击提交。
namespace {
using XCEngine::Input::KeyCode;
@@ -47,8 +49,8 @@ using XCEngine::UI::UIPoint;
using XCEngine::UI::UIPointerButton;
using XCEngine::UI::UIRect;
using XCEngine::UI::Widgets::UISelectionModel;
using XCEngine::UI::Editor::BuildUIEditorHostedTextFieldMetrics;
using XCEngine::UI::Editor::BuildUIEditorHostedTextFieldPalette;
using XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldMetrics;
using XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldPalette;
using XCEngine::UI::Editor::BuildUIEditorInlineRenameTextFieldMetrics;
using XCEngine::UI::Editor::Host::AutoScreenshotController;
using XCEngine::UI::Editor::Host::InputModifierTracker;
@@ -77,8 +79,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorListViewInvalidIndex;
using XCEngine::UI::Editor::Widgets::UIEditorListViewItem;
using XCEngine::UI::Editor::Widgets::UIEditorTextFieldMetrics;
using XCEngine::UI::Editor::Widgets::UIEditorTextFieldPalette;
namespace Style = XCEngine::UI::Style;
constexpr const wchar_t* kWindowClassName = L"XCUIEditorListViewInlineRenameValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | ListView Inline Rename";
@@ -111,11 +111,6 @@ std::filesystem::path ResolveRepoRootPath() {
return std::filesystem::path(root).lexically_normal();
}
std::filesystem::path ResolveValidationThemePath() {
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
.lexically_normal();
}
bool ContainsPoint(const UIRect& rect, float x, float y) {
return x >= rect.x &&
x <= rect.x + rect.width &&
@@ -242,12 +237,12 @@ void DrawButton(
std::vector<UIEditorListViewItem> BuildListItems() {
return {
{ "scene", "SampleScene.unity", "Scene | 4 分钟前修改", 0.0f },
{ "lighting", "LightingProfile.asset", "Preset | 3 profile", 0.0f },
{ "scene", "SampleScene.unity", "Scene | modified 4 minutes ago", 0.0f },
{ "lighting", "LightingProfile.asset", "Preset | 3 profiles", 0.0f },
{ "material", "RobotBody.mat", "Material | Metallic Workflow", 0.0f },
{ "script", "PlayerController.cs", "C# Script | 3.4 KB", 0.0f },
{ "texture", "Checker_AO.png", "Texture2D | 2048x2048", 0.0f },
{ "prefab", "Robot.prefab", "Prefab | 9 个组件", 0.0f },
{ "prefab", "Robot.prefab", "Prefab | 9 children", 0.0f },
{ "anim", "Walk.anim", "Animation Clip | 1.2 s", 0.0f },
{ "shader", "Outline.shader", "Shader | URP compatible", 0.0f }
};
@@ -346,8 +341,6 @@ private:
AutoScreenshotController m_autoScreenshot = {};
InputModifierTracker m_modifierTracker = {};
std::filesystem::path m_captureRoot = {};
Style::UITheme m_theme = {};
std::string m_themeStatus = "fallback";
std::vector<UIEditorListViewItem> m_items = {};
UISelectionModel m_selectionModel = {};
UIEditorListViewInteractionState m_listInteractionState = {};
@@ -524,13 +517,6 @@ bool ScenarioApp::Initialize(HINSTANCE hInstance, int nCmdShow) {
m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/list_view_inline_rename/captures";
m_autoScreenshot.Initialize(m_captureRoot);
const auto themeLoad = XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
if (themeLoad.succeeded) {
m_theme = themeLoad.theme;
m_themeStatus = "loaded";
} else {
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
}
m_modifierTracker.SyncFromSystemState();
ResetScenario();
@@ -560,7 +546,7 @@ ScenarioLayout ScenarioApp::GetLayout() const {
return BuildScenarioLayout(
width,
height,
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
}
void ScenarioApp::ResetScenario() {
@@ -769,7 +755,7 @@ void ScenarioApp::HandleWindowFocusLost() {
PumpRenameEvents({ MakeFocusEvent(UIInputEventType::FocusLost) });
} else {
PumpListEvents({ MakeFocusEvent(UIInputEventType::FocusLost) });
m_lastResult = "窗口失去焦点,ListView focus 已清除。";
m_lastResult = "Window focus lost. ListView focus cleared.";
}
InvalidateRect(m_hwnd, nullptr, FALSE);
@@ -867,7 +853,6 @@ void ScenarioApp::ApplyRenameFrame(const UIEditorInlineRenameSessionFrame& frame
m_lastCommittedValue = result.valueAfter;
RefreshListFrame();
}
m_lastResult = "已提交 rename: " + result.itemId + " -> " + result.valueAfter;
return;
}
@@ -918,9 +903,9 @@ UIEditorInlineRenameSessionRequest ScenarioApp::BuildRenameRequest(bool beginSes
}
UIEditorTextFieldMetrics ScenarioApp::ResolveHostedTextFieldMetrics() const {
const auto propertyMetrics = ResolveUIEditorPropertyGridMetrics(m_theme);
const auto textMetrics = ResolveUIEditorTextFieldMetrics(m_theme);
return BuildUIEditorHostedTextFieldMetrics(propertyMetrics, textMetrics);
const auto propertyMetrics = ResolveUIEditorPropertyGridMetrics();
const auto textMetrics = ResolveUIEditorTextFieldMetrics();
return BuildUIEditorPropertyGridTextFieldMetrics(propertyMetrics, textMetrics);
}
UIEditorTextFieldMetrics ScenarioApp::ResolveInlineRenameMetrics(const UIRect& bounds) const {
@@ -928,9 +913,9 @@ UIEditorTextFieldMetrics ScenarioApp::ResolveInlineRenameMetrics(const UIRect& b
}
UIEditorTextFieldPalette ScenarioApp::ResolveInlineRenamePalette() const {
const auto propertyPalette = ResolveUIEditorPropertyGridPalette(m_theme);
const auto textPalette = ResolveUIEditorTextFieldPalette(m_theme);
return BuildUIEditorHostedTextFieldPalette(propertyPalette, textPalette);
const auto propertyPalette = ResolveUIEditorPropertyGridPalette();
const auto textPalette = ResolveUIEditorTextFieldPalette();
return BuildUIEditorPropertyGridTextFieldPalette(propertyPalette, textPalette);
}
void ScenarioApp::UpdateListResultText(const UIEditorListViewInteractionResult& result) {
@@ -978,8 +963,8 @@ void ScenarioApp::RenderFrame() {
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 shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
RefreshListFrame();
@@ -1002,36 +987,36 @@ void ScenarioApp::RenderFrame() {
layout.introRect,
shellPalette,
shellMetrics,
"这个测试验证什么功能?",
"只验证 Editor ListView 的 inline rename 契约:默认直接进入编辑、字符编辑、Enter 提交、Esc 取消、点击外部提交。");
"这个测试验证什么功能?",
"只验证 Editor ListView 的 inline rename默认进入编辑、字符编辑、Enter 提交、Esc 取消、点击外部提交。");
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
"1. 启动后默认直接进入 script 的 rename检查输入框是否只覆盖当前选中行主文本。",
"1. 启动后默认直接进入 script 的 rename输入框只覆盖当前选中行主文本。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
"2. 输入字符后,检查 Draft 是否实时变化,且列表原文本被 overlay 正确遮住,不应双层叠字。",
"2. 输入字符后Draft 必须实时变化,原文本不能和 overlay 叠字。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
"3. 按 Enter,检查名称是否写回到对应 ListViewItem.primaryText关闭 rename。",
"3. 按 Enter:名称写回 ListViewItem.primaryText退出 rename。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f),
"4. 按 Esc,检查是否取消编辑保留原文本。",
"4. 按 Esc取消编辑保留原文本。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
"5. rename 过程中点击输入框外部,检查是否提交当前草稿并退出编辑态。",
"5. rename 过程中点击输入框外部提交当前草稿并退出编辑态。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 182.0f),
"6. 需要时先按 Esc 退出,再用 Up/Down/Home/End 切换选中项后按 F2F12 或按钮触发截图。",
"6. 需要时先按 Esc 退出,再用 Up/Down/Home/End 切换选中项后按 F2F12 截图。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
@@ -1116,7 +1101,7 @@ void ScenarioApp::RenderFrame() {
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 310.0f),
"Theme: " + m_themeStatus,
"Style: fixed",
shellPalette.textWeak,
shellMetrics.bodyFontSize);

View File

@@ -11,7 +11,6 @@
#include <XCEngine/Input/InputTypes.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEngine/UI/Style/Theme.h>
#include <XCEngine/UI/Widgets/UISelectionModel.h>
#include <windows.h>
@@ -56,8 +55,6 @@ using XCEngine::UI::Editor::Widgets::AppendUIEditorListViewForeground;
using XCEngine::UI::Editor::Widgets::HitTestUIEditorListView;
using XCEngine::UI::Editor::Widgets::UIEditorListViewHitTarget;
using XCEngine::UI::Editor::Widgets::UIEditorListViewItem;
namespace Style = XCEngine::UI::Style;
constexpr const wchar_t* kWindowClassName = L"XCUIEditorListViewMultiSelectValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | ListView MultiSelect";
@@ -90,11 +87,6 @@ std::filesystem::path ResolveRepoRootPath() {
return std::filesystem::path(root).lexically_normal();
}
std::filesystem::path ResolveValidationThemePath() {
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
.lexically_normal();
}
bool ContainsPoint(const UIRect& rect, float x, float y) {
return x >= rect.x &&
x <= rect.x + rect.width &&
@@ -196,12 +188,12 @@ void DrawButton(
std::vector<UIEditorListViewItem> BuildListItems() {
return {
{ "scene", "SampleScene.unity", "Scene | 4 分钟前修改", 0.0f },
{ "lighting", "LightingProfile.asset", "Preset | 3 profile", 0.0f },
{ "scene", "SampleScene.unity", "Scene | modified 4 minutes ago", 0.0f },
{ "lighting", "LightingProfile.asset", "Preset | 3 profiles", 0.0f },
{ "material", "RobotBody.mat", "Material | Metallic Workflow", 0.0f },
{ "script", "PlayerController.cs", "C# Script | 3.4 KB", 0.0f },
{ "texture", "Checker_AO.png", "Texture2D | 2048x2048", 0.0f },
{ "prefab", "Robot.prefab", "Prefab | 9 个组件", 0.0f },
{ "prefab", "Robot.prefab", "Prefab | 9 children", 0.0f },
{ "anim", "Walk.anim", "Animation Clip | 1.2 s", 0.0f },
{ "shader", "Outline.shader", "Shader | URP compatible", 0.0f },
{ "mesh", "Robot.fbx", "Model | 38k triangles", 0.0f },
@@ -439,14 +431,6 @@ private:
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/list_view_multiselect/captures";
m_autoScreenshot.Initialize(m_captureRoot);
const auto themeLoad =
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
if (themeLoad.succeeded) {
m_theme = themeLoad.theme;
m_themeStatus = "loaded";
} else {
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
}
m_modifierTracker.SyncFromSystemState();
ResetScenario();
@@ -476,7 +460,7 @@ private:
return BuildScenarioLayout(
width,
height,
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
}
void ResetScenario() {
@@ -489,7 +473,7 @@ private:
m_hasHoveredAction = false;
m_hoveredAction = ActionId::Reset;
m_lastModifiers = {};
m_lastResult = "已重置到默认多选状态";
m_lastResult = "Reset to the default multiselect state.";
RefreshFrame();
}
@@ -605,7 +589,7 @@ private:
if (wParam == VK_F12) {
m_autoScreenshot.RequestCapture("manual_f12");
m_lastResult = "已请求截图,输出到 captures/latest.png";
m_lastResult = "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>??captures/latest.png";
InvalidateRect(m_hwnd, nullptr, FALSE);
return;
}
@@ -675,13 +659,13 @@ private:
}
if (result.keyboardNavigated && !result.selectedItemId.empty()) {
m_lastResult = "键盘导航到: " + result.selectedItemId;
m_lastResult = "<EFBFBD><EFBFBD><EFBFBD>̵<EFBFBD><EFBFBD><EFBFBD>?? " + result.selectedItemId;
return;
}
if (result.selectionChanged) {
m_lastResult =
"选集已更新: primary=" + m_selectionModel.GetSelectedId() +
"ѡ<EFBFBD><EFBFBD><EFBFBD>Ѹ<EFBFBD>?? primary=" + m_selectionModel.GetSelectedId() +
" | ids=" + JoinSelectedIds(m_selectionModel);
return;
}
@@ -692,7 +676,7 @@ private:
}
if (insideList) {
m_lastResult = "点击列表内空白,只更新 hover / focus";
m_lastResult = "<EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><EFBFBD>ڿհף<EFBFBD>ֻ<EFBFBD><EFBFBD>??hover / focus";
return;
}
@@ -706,7 +690,7 @@ private:
break;
case ActionId::Capture:
m_autoScreenshot.RequestCapture("manual_button");
m_lastResult = "已请求截图,输出到 captures/latest.png";
m_lastResult = "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>??captures/latest.png";
break;
}
}
@@ -720,8 +704,8 @@ private:
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 shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
RefreshFrame();
@@ -737,7 +721,7 @@ private:
layout.introRect,
shellPalette,
shellMetrics,
"这个测试验证什么功能?",
"这个测试验证什么功能?",
"只验证 Editor ListView 多选 contractCtrl/Shift 选集、右键 primary 切换、键盘范围扩展,不涉及任何业务面板。");
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
@@ -845,11 +829,6 @@ private:
captureSummary,
shellPalette.textWeak,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 310.0f),
"Theme: " + m_themeStatus,
shellPalette.textWeak,
shellMetrics.bodyFontSize);
DrawCard(
drawList,
@@ -881,8 +860,6 @@ private:
AutoScreenshotController m_autoScreenshot = {};
InputModifierTracker m_modifierTracker = {};
std::filesystem::path m_captureRoot = {};
Style::UITheme m_theme = {};
std::string m_themeStatus = "fallback";
std::vector<UIEditorListViewItem> m_items = {};
UISelectionModel m_selectionModel = {};
UIEditorListViewInteractionState m_interactionState = {};

View File

@@ -2,12 +2,12 @@
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorCommandDispatcher.h>
#include <XCEditor/Core/UIEditorMenuModel.h>
#include <XCEditor/Core/UIEditorMenuSession.h>
#include <XCEditor/Core/UIEditorShortcutManager.h>
#include <XCEditor/Widgets/UIEditorMenuBar.h>
#include <XCEditor/Widgets/UIEditorMenuPopup.h>
#include <XCEditor/Foundation/UIEditorCommandDispatcher.h>
#include <XCEditor/Shell/UIEditorMenuModel.h>
#include <XCEditor/Shell/UIEditorMenuSession.h>
#include <XCEditor/Foundation/UIEditorShortcutManager.h>
#include <XCEditor/Shell/UIEditorMenuBar.h>
#include <XCEditor/Shell/UIEditorMenuPopup.h>
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -866,9 +866,9 @@ void ScenarioApp::ResetScenario() {
m_menuPopups.clear();
m_menuItems.clear();
SetCustomResult(
"等待操作",
"绛夊緟鎿嶄綔",
"Pending",
"先点 File / Window / Layout,确认一次只会打开一个菜单;再点菜单外区域或按 Escape 关闭。");
"鍏堢偣 File / Window / Layout锛岀‘璁や竴娆″彧浼氭墦寮€涓€涓彍鍗曪紱鍐嶇偣鑿滃崟澶栧尯鍩熸垨鎸?Escape 鍏抽棴銆?);
#endif
}
@@ -1004,7 +1004,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) {
SetCustomResult(
"Hover 切换顶层菜单",
"Changed",
"已切换到顶层菜单 `" + hoveredButton->label + "`。重点检查:旧 root popup 被替换,新 root popup 立即出现。");
"已切换到顶层菜单 `" + hoveredButton->label + "`。重点检查:旧 root popup 被替换,新 root popup 是否立即出现。");
dirty = true;
}
} else {
@@ -1014,7 +1014,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) {
SetCustomResult(
"Hover 收起子菜单",
"Dismissed",
"鼠标回到顶层按钮后,旧的 child popup 已收起。closed: " +
"鼠标回到顶层按钮后,旧的 child popup 已收起。Closed: " +
JoinClosedPopupIds(mutation));
dirty = true;
}
@@ -1040,7 +1040,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) {
SetCustomResult(
"Hover 收起子菜单",
"Dismissed",
"鼠标移到普通菜单项后,旧的 child popup 已收起。closed: " +
"鼠标移到普通菜单项后,旧的 child popup 已收起。Closed: " +
JoinClosedPopupIds(mutation));
dirty = true;
}
@@ -1052,7 +1052,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) {
SetCustomResult(
"Hover popup 空白区",
"Dismissed",
"鼠标停留在 popup 空白区后,子菜单链已回收到当前 popup。closed: " +
"鼠标停留在 popup 空白区后,子菜单链已回收到当前 popup。Closed: " +
JoinClosedPopupIds(mutation));
dirty = true;
}
@@ -1075,7 +1075,7 @@ void ScenarioApp::HandleClick(float x, float y) {
"点击关闭顶层菜单",
mutation.changed ? "Dismissed" : "NoOp",
mutation.changed
? "再次点击当前顶层按钮后,整条菜单链已关闭。closed: " +
? "再次点击当前顶层按钮后,整条菜单链已关闭。Closed: " +
JoinClosedPopupIds(mutation)
: "当前顶层菜单没有发生变化。");
} else {
@@ -1108,8 +1108,8 @@ void ScenarioApp::HandleClick(float x, float y) {
mutation.changed ? "Changed" : "NoOp",
mutation.changed
? "已展开 `" + hoveredItem->label +
"` 子菜单。这个场景正常行为是 hover 也会直接展开。"
: "子菜单已经处于开状态。");
"` 子菜单。这个场景正常行为是 hover 也会直接展开。"
: "子菜单已经处于开状态。");
InvalidateRect(m_hwnd, nullptr, FALSE);
return;
}
@@ -1140,7 +1140,7 @@ void ScenarioApp::HandleClick(float x, float y) {
SetCustomResult(
"点击 popup 空白区",
"Dismissed",
"点击当前 popup 空白区后,仅更深层子菜单被关闭。closed: " +
"点击当前 popup 空白区后,仅更深层子菜单被关闭。Closed: " +
JoinClosedPopupIds(mutation));
ClearHoverWhenMenuClosed();
InvalidateRect(m_hwnd, nullptr, FALSE);
@@ -1155,7 +1155,7 @@ void ScenarioApp::HandleClick(float x, float y) {
"点击菜单外区域",
mutation.changed ? "Dismissed" : "NoOp",
mutation.changed
? "点击外部区域后,整条菜单链已关闭。closed: " +
? "点击外部区域后,整条菜单链已关闭。Closed: " +
JoinClosedPopupIds(mutation)
: "菜单链没有变化。");
ClearHoverWhenMenuClosed();
@@ -1172,14 +1172,14 @@ void ScenarioApp::HandleClick(float x, float y) {
if (m_openMenuId == button.menuId) {
m_openMenuId.clear();
m_hoveredItemId.clear();
SetCustomResult("关闭菜单", "NoOp", "再次点击当前菜单按钮,菜单已关闭。");
SetCustomResult("鍏抽棴鑿滃崟", "NoOp", "鍐嶆鐐瑰嚮褰撳墠鑿滃崟鎸夐挳锛岃彍鍗曞凡鍏抽棴銆?);
} else {
m_openMenuId = button.menuId;
m_hoveredItemId.clear();
SetCustomResult(
"打开菜单",
"鎵撳紑鑿滃崟",
"Changed",
"当前激活菜单: " + button.label + "。确认同一时刻只存在一个下拉菜单。");
"褰撳墠婵€娲昏彍鍗? " + button.label + "銆傜‘璁ゅ悓涓€鏃跺埢鍙瓨鍦ㄤ竴涓笅鎷夎彍鍗曘€?);
}
InvalidateRect(m_hwnd, nullptr, FALSE);
return;
@@ -1197,9 +1197,9 @@ void ScenarioApp::HandleClick(float x, float y) {
if (!item.enabled) {
SetCustomResult(
"菜单项不可执行",
"鑿滃崟椤逛笉鍙墽琛?,
"Disabled",
"当前工作区状态下 `" + item.label + "` 不可执行。");
"褰撳墠宸ヤ綔鍖虹姸鎬佷笅 `" + item.label + "` 涓嶅彲鎵ц銆?);
InvalidateRect(m_hwnd, nullptr, FALSE);
return;
}
@@ -1216,7 +1216,7 @@ void ScenarioApp::HandleClick(float x, float y) {
if (!m_openMenuId.empty()) {
m_openMenuId.clear();
m_hoveredItemId.clear();
SetCustomResult("菜单失焦", "Dismissed", "点击菜单外区域,菜单已关闭。");
SetCustomResult("鑿滃崟澶辩劍", "Dismissed", "鐐瑰嚮鑿滃崟澶栧尯鍩燂紝鑿滃崟宸插叧闂€?);
InvalidateRect(m_hwnd, nullptr, FALSE);
}
#endif
@@ -1255,13 +1255,13 @@ void ScenarioApp::HandleKeyDown(UINT keyCode) {
if (!m_openMenuId.empty()) {
m_openMenuId.clear();
m_hoveredItemId.clear();
SetCustomResult("Esc 关闭菜单", "Dismissed", "按下 Escape 后,菜单已关闭。");
SetCustomResult("Esc 鍏抽棴鑿滃崟", "Dismissed", "鎸変笅 Escape 鍚庯紝鑿滃崟宸插叧闂€?);
InvalidateRect(m_hwnd, nullptr, FALSE);
}
break;
case 'R':
SetDispatchResult(
"键盘 Reset Workspace",
"閿洏 Reset Workspace",
m_commandDispatcher.Dispatch("workspace.reset", m_controller));
InvalidateRect(m_hwnd, nullptr, FALSE);
break;
@@ -1446,13 +1446,13 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height)
DrawCard(
drawList,
headerRect,
"测试内容Editor Menu 会话状态层",
"这个测试验证什么功能?",
"本场景只验证顶层菜单切换、child popup hover、Esc / outside dismiss、命令派发不验证业务面板。");
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 70.0f), "1. 点击 File 打开 root popup再 hover `Workspace Tools`,右侧 child popup 应立即弹出。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 92.0f), "2. root popup 打开时,把鼠标移到 Window / Layout 顶层按钮,旧 popup 应直接被新的 root popup 替换。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 114.0f), "3. 点击 Window -> Activate Details再点 Window -> Hide Active检查右侧 details visible / active 是否按状态变化。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 136.0f), "4. 如果 child popup 已打开,按一次 Esc 只关 topmost再按一次 Esc 关闭整条菜单链。点击外部空白区也必须整条关闭。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 158.0f), "5. F12 保存截图R 可直接触发 Reset Workspace方便确认命令派发仍正常。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 158.0f), "5. F12 保存截图R 可直接触发 Reset Workspace方便确认命令派发仍正常。", kTextPrimary, 13.0f);
DrawCard(drawList, shellRect, "操作区", "这里只保留 MenuBar 和当前 popup overlay。");
DrawCard(drawList, stateRect, "状态摘要", "重点看 menu session 当前链路和 Details 状态,不放无关杂项。");
@@ -1464,15 +1464,15 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height)
const UIRect shellInfoRect(shellRect.x + 18.0f, shellRect.y + 144.0f, shellRect.width - 36.0f, 190.0f);
drawList.AddFilledRect(shellInfoRect, kIndicatorBg, 8.0f);
drawList.AddRectOutline(shellInfoRect, kCardBorder, 1.0f, 8.0f);
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 14.0f), "Open root menu", kTextMuted, 12.0f);
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 14.0f), "已打开 root menu", kTextMuted, 12.0f);
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 34.0f), m_menuSession.HasOpenMenu() ? std::string(m_menuSession.GetOpenRootMenuId()) : "(none)", kTextPrimary, 14.0f);
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 62.0f), "Popup chain", kTextMuted, 12.0f);
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 62.0f), "Popup ", kTextMuted, 12.0f);
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 82.0f), JoinPopupChainIds(m_menuSession), kTextPrimary, 14.0f);
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 110.0f), "Submenu path", kTextMuted, 12.0f);
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 110.0f), "Submenu 路径", kTextMuted, 12.0f);
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 130.0f), JoinSubmenuPathIds(m_menuSession), kTextPrimary, 14.0f);
drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 158.0f), "提示popup overlay 必须压在右侧状态面板上方,这一轮会专门检查这个层级关系。", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 72.0f), "Hover summary", kAccent, 15.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 72.0f), "Hover 摘要", kAccent, 15.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 100.0f), "hover menu", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 120.0f), m_hoveredMenuId.empty() ? "(none)" : m_hoveredMenuId, kTextPrimary, 14.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 150.0f), "hover popup", kTextMuted, 12.0f);
@@ -1480,19 +1480,19 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height)
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 200.0f), "hover item", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 220.0f), m_hoveredItemId.empty() ? "(none)" : m_hoveredItemId, kTextPrimary, 14.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 262.0f), "Workspace summary", kAccent, 15.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 290.0f), "active panel", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 262.0f), "Workspace 摘要", kAccent, 15.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 290.0f), "当前激活面板", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 310.0f), workspace.activePanelId.empty() ? "(none)" : workspace.activePanelId, kTextPrimary, 14.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 338.0f), "visible panels", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 338.0f), "可见面板", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 358.0f), JoinVisiblePanelIds(workspace, session), kTextPrimary, 14.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 386.0f), "details visible", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 386.0f), "Details 可见", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 406.0f), detailsState != nullptr && detailsState->visible ? "true" : "false", detailsState != nullptr && detailsState->visible ? kSuccess : kWarning, 14.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 434.0f), "details active", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 434.0f), "Details 激活", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 454.0f), workspace.activePanelId == "details" ? "true" : "false", workspace.activePanelId == "details" ? kSuccess : kTextMuted, 14.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 482.0f), "menu model validation", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 482.0f), "菜单模型验证", kTextMuted, 12.0f);
drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 502.0f), menuValidation.IsValid() ? "OK" : menuValidation.message, menuValidation.IsValid() ? kSuccess : kDanger, 12.0f);
drawList.AddText(UIPoint(footerRect.x + 18.0f, footerRect.y + 56.0f), "Last interaction: " + m_lastActionName + " | Result: " + m_lastStatusLabel, m_lastStatusColor, 13.0f);
drawList.AddText(UIPoint(footerRect.x + 18.0f, footerRect.y + 56.0f), "最近交互: " + m_lastActionName + " | Result: " + m_lastStatusLabel, m_lastStatusColor, 13.0f);
drawList.AddText(UIPoint(footerRect.x + 18.0f, footerRect.y + 74.0f), m_lastMessage, kTextPrimary, 12.0f);
const std::string captureSummary =
@@ -1630,7 +1630,7 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height)
DrawCard(
drawList,
headerRect,
"测试内容Editor Menu 基础壳层验证",
"这个测试验证什么功能?",
"只验证 MenuBar / 下拉展开 / hover / 菜单关闭 / command dispatch不验证业务面板不验证完整编辑器菜单体系。");
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 70.0f), "1. 点击 File / Window / Layout确认同一时刻只会有一个菜单展开。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 92.0f), "2. 展开菜单后移动鼠标,确认 hover 高亮稳定disabled 项不会误显示成可点击。", kTextPrimary, 13.0f);

View File

@@ -1,10 +1,11 @@
#ifndef NOMINMAX
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorNumberFieldInteraction.h>
#include <XCEditor/Core/UIEditorTheme.h>
#include <XCEditor/Widgets/UIEditorNumberField.h>
#include <XCEditor/Fields/UIEditorNumberFieldInteraction.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
#include <XCEditor/Fields/UIEditorFieldStyle.h>
#include <XCEditor/Fields/UIEditorNumberField.h>
#include "EditorValidationTheme.h"
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -48,7 +49,6 @@ using XCEngine::UI::Editor::Widgets::HitTestUIEditorNumberField;
using XCEngine::UI::Editor::Widgets::UIEditorNumberFieldHitTarget;
using XCEngine::UI::Editor::Widgets::UIEditorNumberFieldHitTargetKind;
using XCEngine::UI::Editor::Widgets::UIEditorNumberFieldSpec;
namespace Style = XCEngine::UI::Style;
constexpr const wchar_t* kWindowClassName = L"XCUIEditorNumberFieldBasicValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | NumberField Basic";
@@ -84,10 +84,6 @@ std::filesystem::path ResolveRepoRootPath() {
return std::filesystem::path(root).lexically_normal();
}
std::filesystem::path ResolveValidationThemePath() {
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
.lexically_normal();
}
bool ContainsPoint(const UIRect& rect, float x, float y) {
return x >= rect.x &&
@@ -170,18 +166,16 @@ ScenarioLayout BuildScenarioLayout(
return layout;
}
XCEngine::UI::Editor::Widgets::UIEditorNumberFieldMetrics ResolveHostedNumberFieldMetrics(
const Style::UITheme& theme) {
const auto propertyMetrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(theme);
const auto numberMetrics = XCEngine::UI::Editor::ResolveUIEditorNumberFieldMetrics(theme);
return XCEngine::UI::Editor::BuildUIEditorHostedNumberFieldMetrics(propertyMetrics, numberMetrics);
XCEngine::UI::Editor::Widgets::UIEditorNumberFieldMetrics ResolvePropertyGridNumberFieldMetrics() {
return XCEngine::UI::Editor::BuildUIEditorPropertyGridNumberFieldMetrics(
XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(),
XCEngine::UI::Editor::ResolveUIEditorNumberFieldMetrics());
}
XCEngine::UI::Editor::Widgets::UIEditorNumberFieldPalette ResolveHostedNumberFieldPalette(
const Style::UITheme& theme) {
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(theme);
const auto numberPalette = XCEngine::UI::Editor::ResolveUIEditorNumberFieldPalette(theme);
return XCEngine::UI::Editor::BuildUIEditorHostedNumberFieldPalette(propertyPalette, numberPalette);
XCEngine::UI::Editor::Widgets::UIEditorNumberFieldPalette ResolvePropertyGridNumberFieldPalette() {
return XCEngine::UI::Editor::BuildUIEditorPropertyGridNumberFieldPalette(
XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(),
XCEngine::UI::Editor::ResolveUIEditorNumberFieldPalette());
}
void DrawCard(
@@ -430,14 +424,6 @@ private:
m_captureRoot =
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/number_field_basic/captures";
m_autoScreenshot.Initialize(m_captureRoot);
const auto themeLoad =
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
if (themeLoad.succeeded) {
m_theme = themeLoad.theme;
m_themeStatus = "loaded";
} else {
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
}
ResetScenario();
return true;
@@ -466,7 +452,7 @@ private:
return BuildScenarioLayout(
width,
height,
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
}
void ResetScenario() {
@@ -494,7 +480,7 @@ private:
}
const ScenarioLayout layout = GetLayout();
const auto metrics = ResolveHostedNumberFieldMetrics(m_theme);
const auto metrics = ResolvePropertyGridNumberFieldMetrics();
m_frame = UpdateUIEditorNumberFieldInteraction(
m_interactionState,
m_spec,
@@ -604,7 +590,7 @@ private:
UIEditorNumberFieldInteractionResult PumpEvents(std::vector<UIInputEvent> events) {
const ScenarioLayout layout = GetLayout();
const auto metrics = ResolveHostedNumberFieldMetrics(m_theme);
const auto metrics = ResolvePropertyGridNumberFieldMetrics();
m_frame = UpdateUIEditorNumberFieldInteraction(
m_interactionState,
m_spec,
@@ -616,7 +602,7 @@ private:
void UpdateResultText(const UIEditorNumberFieldInteractionResult& result) {
if (result.editCommitRejected) {
m_lastResult = "提交失败,仍保持在编辑";
m_lastResult = "提交失败,仍保持在编辑状态。";
return;
}
if (result.editCommitted) {
@@ -624,11 +610,11 @@ private:
return;
}
if (result.editCanceled) {
m_lastResult = "已取消编辑";
m_lastResult = "已取消编辑";
return;
}
if (result.editStarted) {
m_lastResult = "已进入编辑";
m_lastResult = "已进入编辑状态。";
return;
}
if (result.valueChanged || result.stepApplied) {
@@ -636,7 +622,7 @@ private:
return;
}
if (result.consumed) {
m_lastResult = "控件已消费输入";
m_lastResult = "控件已消费输入";
return;
}
m_lastResult = "等待交互";
@@ -664,16 +650,16 @@ private:
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 shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
RefreshFrame();
const UIEditorNumberFieldHitTarget currentHit =
HitTestUIEditorNumberField(m_frame.layout, m_mousePosition);
const auto numberMetrics = ResolveHostedNumberFieldMetrics(m_theme);
const auto numberPalette = ResolveHostedNumberFieldPalette(m_theme);
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme);
const auto numberMetrics = ResolvePropertyGridNumberFieldMetrics();
const auto numberPalette = ResolvePropertyGridNumberFieldPalette();
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette();
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("EditorNumberFieldBasic");
@@ -684,7 +670,7 @@ private:
layout.introRect,
shellPalette,
shellMetrics,
"这个测试在验证什么功能",
"这个测试在验证什么功能",
"验证 Inspector 宿主中的 NumberField 交互契约和默认宿主风格。");
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
@@ -698,7 +684,7 @@ private:
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
"3. 按 Enter 进入编辑态,直接输入字符Enter commitEsc cancel。",
"3. 按 Enter 进入编辑态,直接输入字符Enter commitEsc cancel。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
@@ -773,7 +759,7 @@ private:
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 238.0f),
"Theme: " + m_themeStatus,
"Style: fixed",
shellPalette.textWeak,
shellMetrics.bodyFontSize);
@@ -783,7 +769,7 @@ private:
shellPalette,
shellMetrics,
"NumberField 预览",
"这里预览 Inspector 宿主中的 Unity 风格 Number 字段。");
"这里预览 Inspector 宿主中的 Unity 风格 Number 字段。");
drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor);
drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f);
drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground);
@@ -825,12 +811,10 @@ private:
UIEditorNumberFieldSpec m_spec = {};
UIEditorNumberFieldInteractionState m_interactionState = {};
UIEditorNumberFieldInteractionFrame m_frame = {};
Style::UITheme m_theme = {};
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
ActionId m_hoveredAction = ActionId::Reset;
bool m_hasHoveredAction = false;
std::string m_lastResult = {};
std::string m_themeStatus = "fallback";
};
} // namespace

View File

@@ -0,0 +1,31 @@
add_executable(editor_ui_object_field_basic_validation WIN32
main.cpp
)
target_include_directories(editor_ui_object_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_object_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_object_field_basic_validation PRIVATE /utf-8 /FS)
set_property(TARGET editor_ui_object_field_basic_validation PROPERTY
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
endif()
target_link_libraries(editor_ui_object_field_basic_validation PRIVATE
XCUIEditorLib
XCUIEditorHost
)
set_target_properties(editor_ui_object_field_basic_validation PROPERTIES
OUTPUT_NAME "XCUIEditorObjectFieldBasicValidation"
)

View File

@@ -0,0 +1,736 @@
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Foundation/UIEditorTheme.h>
#include <XCEditor/Fields/UIEditorObjectField.h>
#include <XCEditor/Fields/UIEditorObjectFieldInteraction.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 <string_view>
#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::UIEditorObjectFieldInteractionFrame;
using XCEngine::UI::Editor::UIEditorObjectFieldInteractionResult;
using XCEngine::UI::Editor::UIEditorObjectFieldInteractionState;
using XCEngine::UI::Editor::UpdateUIEditorObjectFieldInteraction;
using XCEngine::UI::Editor::Widgets::AppendUIEditorObjectField;
using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldHitTarget;
using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldHitTargetKind;
using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldSpec;
constexpr const wchar_t* kWindowClassName = L"XCUIEditorObjectFieldBasicValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | ObjectField Basic";
enum class ActionId : unsigned char {
Reset = 0,
Capture
};
struct ButtonLayout {
ActionId action = ActionId::Reset;
const char* label = "";
UIRect rect = {};
};
struct ScenarioLayout {
UIRect introRect = {};
UIRect controlRect = {};
UIRect stateRect = {};
UIRect previewRect = {};
UIRect fieldRect = {};
std::vector<ButtonLayout> buttons = {};
};
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();
}
bool ContainsPoint(const UIRect& rect, float x, float y) {
return x >= rect.x &&
x <= rect.x + rect.width &&
y >= rect.y &&
y <= rect.y + rect.height;
}
std::int32_t MapObjectFieldKey(UINT keyCode) {
switch (keyCode) {
case VK_SPACE:
return static_cast<std::int32_t>(KeyCode::Space);
case VK_RETURN:
return static_cast<std::int32_t>(KeyCode::Enter);
case VK_DELETE:
return static_cast<std::int32_t>(KeyCode::Delete);
case VK_BACK:
return static_cast<std::int32_t>(KeyCode::Backspace);
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 = 440.0f;
const float gap = shellMetrics.gap;
ScenarioLayout layout = {};
layout.introRect = UIRect(margin, margin, leftWidth, 236.0f);
layout.controlRect = UIRect(margin, layout.introRect.y + layout.introRect.height + gap, leftWidth, 84.0f);
layout.stateRect = UIRect(
margin,
layout.controlRect.y + layout.controlRect.height + gap,
leftWidth,
(std::max)(232.0f, height - (layout.controlRect.y + layout.controlRect.height + gap) - margin));
layout.previewRect = UIRect(
leftWidth + margin * 2.0f,
margin,
(std::max)(420.0f, width - leftWidth - margin * 3.0f),
height - margin * 2.0f);
layout.fieldRect = UIRect(
layout.previewRect.x + 24.0f,
layout.previewRect.y + 82.0f,
(std::min)(440.0f, layout.previewRect.width - 48.0f),
22.0f);
const float buttonWidth = (layout.controlRect.width - 44.0f) * 0.5f;
const float buttonY = layout.controlRect.y + 32.0f;
layout.buttons = {
{ ActionId::Reset, "重置", UIRect(layout.controlRect.x + 14.0f, buttonY, buttonWidth, 36.0f) },
{ ActionId::Capture, "截图(F12)", UIRect(layout.controlRect.x + 26.0f + buttonWidth, buttonY, buttonWidth, 36.0f) }
};
return layout;
}
void DrawCard(
UIDrawList& drawList,
const UIRect& rect,
const XCEngine::Tests::EditorUI::EditorValidationShellPalette& shellPalette,
const XCEngine::Tests::EditorUI::EditorValidationShellMetrics& shellMetrics,
std::string_view title,
std::string_view subtitle = {}) {
drawList.AddFilledRect(rect, shellPalette.cardBackground, shellMetrics.cardRadius);
drawList.AddRectOutline(rect, shellPalette.cardBorder, 1.0f, shellMetrics.cardRadius);
drawList.AddText(
UIPoint(rect.x + 16.0f, rect.y + 14.0f),
std::string(title),
shellPalette.textPrimary,
shellMetrics.titleFontSize);
if (!subtitle.empty()) {
drawList.AddText(
UIPoint(rect.x + 16.0f, rect.y + 40.0f),
std::string(subtitle),
shellPalette.textMuted,
shellMetrics.bodyFontSize);
}
}
void DrawButton(
UIDrawList& drawList,
const ButtonLayout& button,
const XCEngine::Tests::EditorUI::EditorValidationShellPalette& shellPalette,
const XCEngine::Tests::EditorUI::EditorValidationShellMetrics& shellMetrics,
bool hovered) {
drawList.AddFilledRect(
button.rect,
hovered ? shellPalette.buttonHoverBackground : shellPalette.buttonBackground,
shellMetrics.buttonRadius);
drawList.AddRectOutline(button.rect, shellPalette.cardBorder, 1.0f, shellMetrics.buttonRadius);
drawList.AddText(
UIPoint(button.rect.x + 16.0f, button.rect.y + 10.0f),
button.label,
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
}
std::string DescribeHitTarget(const UIEditorObjectFieldHitTarget& hitTarget) {
switch (hitTarget.kind) {
case UIEditorObjectFieldHitTargetKind::ValueBox:
return "value_box";
case UIEditorObjectFieldHitTargetKind::ClearButton:
return "clear_button";
case UIEditorObjectFieldHitTargetKind::PickerButton:
return "picker_button";
case UIEditorObjectFieldHitTargetKind::Row:
return "row";
case UIEditorObjectFieldHitTargetKind::None:
default:
return "none";
}
}
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 MakeFocusEvent(UIInputEventType type) {
UIInputEvent event = {};
event.type = type;
return event;
}
UIInputEvent MakeKeyEvent(std::int32_t keyCode) {
UIInputEvent event = {};
event.type = UIInputEventType::KeyDown;
event.keyCode = keyCode;
return event;
}
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 = static_cast<ScenarioApp*>(createStruct->lpCreateParams);
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(app));
return TRUE;
}
auto* app = reinterpret_cast<ScenarioApp*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
if (app == nullptr) {
return DefWindowProcW(hwnd, message, wParam, lParam);
}
return app->HandleMessage(hwnd, message, wParam, lParam);
}
bool Initialize(HINSTANCE hInstance, int nCmdShow) {
m_hInstance = hInstance;
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,
1360,
820,
nullptr,
nullptr,
hInstance,
this);
if (m_hwnd == nullptr) {
return false;
}
ShowWindow(m_hwnd, nCmdShow);
UpdateWindow(m_hwnd);
if (!m_renderer.Initialize(m_hwnd)) {
return false;
}
m_shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
m_shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
m_fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorObjectFieldMetrics();
m_fieldPalette = XCEngine::UI::Editor::ResolveUIEditorObjectFieldPalette();
m_captureRoot =
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/object_field_basic/captures";
m_autoScreenshot.Initialize(m_captureRoot);
ResetScenario();
return true;
}
void Shutdown() {
m_autoScreenshot.Shutdown();
m_renderer.Shutdown();
if (m_hwnd != nullptr && IsWindow(m_hwnd)) {
DestroyWindow(m_hwnd);
}
m_hwnd = nullptr;
if (m_windowClassAtom != 0 && m_hInstance != nullptr) {
UnregisterClassW(kWindowClassName, m_hInstance);
m_windowClassAtom = 0;
}
}
void ResetScenario() {
m_spec = {};
m_spec.fieldId = "target";
m_spec.label = "Target";
m_spec.objectName = "Main Camera";
m_spec.objectTypeName = "Camera";
m_spec.emptyText = "None (GameObject)";
m_spec.hasValue = true;
m_spec.showClearButton = true;
m_spec.showPickerButton = true;
m_interactionState = {};
m_activateCount = 0u;
m_lastResult = "已重置到默认 ObjectField 状态";
m_hoveredAction = ActionId::Reset;
m_pressedAction = ActionId::Reset;
m_hasHoveredAction = false;
m_hasPressedAction = false;
}
ScenarioLayout GetLayout() const {
RECT clientRect = {};
GetClientRect(m_hwnd, &clientRect);
const float width = static_cast<float>((std::max)(clientRect.right - clientRect.left, 1L));
const float height = static_cast<float>((std::max)(clientRect.bottom - clientRect.top, 1L));
return BuildScenarioLayout(width, height, m_shellMetrics);
}
void UpdateHoveredAction(const ScenarioLayout& layout, float x, float y) {
for (const ButtonLayout& button : layout.buttons) {
if (ContainsPoint(button.rect, x, y)) {
m_hoveredAction = button.action;
m_hasHoveredAction = true;
return;
}
}
m_hasHoveredAction = false;
}
const ButtonLayout* HitTestAction(const ScenarioLayout& layout, float x, float y) const {
for (const ButtonLayout& button : layout.buttons) {
if (ContainsPoint(button.rect, x, y)) {
return &button;
}
}
return nullptr;
}
void ApplyInteractionResult(const UIEditorObjectFieldInteractionResult& result) {
if (result.activateRequested) {
++m_activateCount;
m_lastResult = "已触发 activateRequested";
return;
}
if (result.clearRequested) {
m_spec.hasValue = false;
m_spec.objectName.clear();
m_spec.objectTypeName.clear();
m_lastResult = "已触发 clearRequested当前引用已清空";
return;
}
if (result.focusChanged) {
m_lastResult = std::string("焦点已切换: ") + (m_interactionState.fieldState.focused ? "focused" : "blurred");
return;
}
if (result.consumed) {
m_lastResult = "输入已消费";
return;
}
}
void QueueInput(UIInputEvent event) {
m_pendingInputEvents.push_back(std::move(event));
}
void ExecuteAction(ActionId action) {
switch (action) {
case ActionId::Reset:
ResetScenario();
break;
case ActionId::Capture:
m_autoScreenshot.RequestCapture("manual_button");
m_lastResult = "截图已排队,输出到 captures/latest.png";
break;
}
}
void HandleMouseMove(float x, float y) {
m_mousePosition = UIPoint(x, y);
const ScenarioLayout layout = GetLayout();
UpdateHoveredAction(layout, x, y);
TRACKMOUSEEVENT trackEvent = {};
trackEvent.cbSize = sizeof(trackEvent);
trackEvent.dwFlags = TME_LEAVE;
trackEvent.hwndTrack = m_hwnd;
TrackMouseEvent(&trackEvent);
QueueInput(MakePointerEvent(UIInputEventType::PointerMove, m_mousePosition));
}
void HandleMouseLeave() {
m_mousePosition = UIPoint(-1000.0f, -1000.0f);
m_hasHoveredAction = false;
QueueInput(MakePointerEvent(UIInputEventType::PointerLeave, m_mousePosition));
}
void HandleLeftButtonDown(float x, float y) {
SetFocus(m_hwnd);
m_mousePosition = UIPoint(x, y);
const ScenarioLayout layout = GetLayout();
UpdateHoveredAction(layout, x, y);
if (const ButtonLayout* button = HitTestAction(layout, x, y)) {
m_pressedAction = button->action;
m_hasPressedAction = true;
return;
}
m_hasPressedAction = false;
QueueInput(MakePointerEvent(
UIInputEventType::PointerButtonDown,
m_mousePosition,
UIPointerButton::Left));
}
void HandleLeftButtonUp(float x, float y) {
m_mousePosition = UIPoint(x, y);
const ScenarioLayout layout = GetLayout();
UpdateHoveredAction(layout, x, y);
if (const ButtonLayout* button = HitTestAction(layout, x, y)) {
if (m_hasPressedAction && m_pressedAction == button->action) {
ExecuteAction(button->action);
}
m_hasPressedAction = false;
return;
}
m_hasPressedAction = false;
QueueInput(MakePointerEvent(
UIInputEventType::PointerButtonUp,
m_mousePosition,
UIPointerButton::Left));
}
void HandleKeyDown(std::int32_t keyCode) {
QueueInput(MakeKeyEvent(keyCode));
}
void OnResize(UINT width, UINT height) {
if (width == 0u || height == 0u) {
return;
}
m_renderer.Resize(width, height);
}
void RenderFrame() {
if (m_hwnd == nullptr) {
return;
}
RECT clientRect = {};
GetClientRect(m_hwnd, &clientRect);
const float width = static_cast<float>((std::max)(clientRect.right - clientRect.left, 1L));
const float height = static_cast<float>((std::max)(clientRect.bottom - clientRect.top, 1L));
const ScenarioLayout layout = BuildScenarioLayout(width, height, m_shellMetrics);
std::vector<UIInputEvent> events = std::move(m_pendingInputEvents);
m_pendingInputEvents.clear();
m_frame = UpdateUIEditorObjectFieldInteraction(
m_interactionState,
m_spec,
layout.fieldRect,
events,
m_fieldMetrics);
ApplyInteractionResult(m_frame.result);
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("EditorObjectFieldBasic");
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), m_shellPalette.windowBackground);
DrawCard(
drawList,
layout.introRect,
m_shellPalette,
m_shellMetrics,
"这个测试在验证什么功能?",
"验证 Unity 风格 ObjectField 的值框、类型标签、clear / picker 按钮,以及 focus、activate、clear 契约。");
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
"1. 点击 value box 或 picker 按钮,应触发 activateRequested。",
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
"2. 点击 clear 按钮,或 focused 时按 Delete / Backspace应清空当前对象。",
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
"3. focused 后按 Enter / Space应继续走 activate 契约。",
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f),
"4. 重点检查 Hover、Focused、Has Value、Activate Count、Result 是否同步。",
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
"5. 按 F12 或点击截图按钮,可导出当前窗口截图。",
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
DrawCard(
drawList,
layout.controlRect,
m_shellPalette,
m_shellMetrics,
"操作",
"这里只保留当前场景需要的最小操作。");
for (const ButtonLayout& button : layout.buttons) {
DrawButton(
drawList,
button,
m_shellPalette,
m_shellMetrics,
m_hasHoveredAction && m_hoveredAction == button.action);
}
DrawCard(
drawList,
layout.stateRect,
m_shellPalette,
m_shellMetrics,
"状态摘要",
"重点检查 hit、focus、value、activate、result。");
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 74.0f),
"Hover: " + DescribeHitTarget(m_frame.result.hitTarget),
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 96.0f),
std::string("Focused: ") + (m_interactionState.fieldState.focused ? "" : ""),
m_interactionState.fieldState.focused ? m_shellPalette.textSuccess : m_shellPalette.textMuted,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 118.0f),
std::string("Has Value: ") + (m_spec.hasValue ? "" : ""),
m_spec.hasValue ? m_shellPalette.textSuccess : m_shellPalette.textMuted,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 140.0f),
"Value: " + (m_spec.hasValue ? m_spec.objectName : m_spec.emptyText),
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 162.0f),
"Type: " + (m_spec.objectTypeName.empty() ? std::string("(none)") : m_spec.objectTypeName),
m_shellPalette.textMuted,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 184.0f),
"Activate Count: " + std::to_string(m_activateCount),
m_shellPalette.textPrimary,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 206.0f),
"Result: " + m_lastResult,
m_shellPalette.textMuted,
m_shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 232.0f),
m_autoScreenshot.HasPendingCapture()
? "截图排队中..."
: (m_autoScreenshot.GetLastCaptureSummary().empty()
? std::string("F12 -> tests/UI/Editor/integration/shell/object_field_basic/captures/")
: m_autoScreenshot.GetLastCaptureSummary()),
m_shellPalette.textWeak,
m_shellMetrics.bodyFontSize);
DrawCard(
drawList,
layout.previewRect,
m_shellPalette,
m_shellMetrics,
"ObjectField 预览",
"这里只放一个 Unity 风格 ObjectField不混入业务 Inspector。");
AppendUIEditorObjectField(
drawList,
layout.fieldRect,
m_spec,
m_interactionState.fieldState,
m_fieldPalette,
m_fieldMetrics);
const bool framePresented = m_renderer.Render(drawData);
m_autoScreenshot.CaptureIfRequested(
m_renderer,
drawData,
static_cast<unsigned int>(width),
static_cast<unsigned int>(height),
framePresented);
}
LRESULT HandleMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_CLOSE:
DestroyWindow(hwnd);
return 0;
case WM_DESTROY:
m_hwnd = nullptr;
PostQuitMessage(0);
return 0;
case WM_SIZE:
if (wParam != SIZE_MINIMIZED) {
OnResize(static_cast<UINT>(LOWORD(lParam)), static_cast<UINT>(HIWORD(lParam)));
}
return 0;
case WM_ERASEBKGND:
return 1;
case WM_MOUSEMOVE:
HandleMouseMove(
static_cast<float>(GET_X_LPARAM(lParam)),
static_cast<float>(GET_Y_LPARAM(lParam)));
return 0;
case WM_MOUSELEAVE:
HandleMouseLeave();
return 0;
case WM_LBUTTONDOWN:
HandleLeftButtonDown(
static_cast<float>(GET_X_LPARAM(lParam)),
static_cast<float>(GET_Y_LPARAM(lParam)));
return 0;
case WM_LBUTTONUP:
HandleLeftButtonUp(
static_cast<float>(GET_X_LPARAM(lParam)),
static_cast<float>(GET_Y_LPARAM(lParam)));
return 0;
case WM_SETFOCUS:
QueueInput(MakeFocusEvent(UIInputEventType::FocusGained));
return 0;
case WM_KILLFOCUS:
QueueInput(MakeFocusEvent(UIInputEventType::FocusLost));
return 0;
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
if (wParam == VK_F12) {
m_autoScreenshot.RequestCapture("manual_f12");
m_lastResult = "截图已排队,输出到 captures/latest.png";
return 0;
}
if (const std::int32_t keyCode = MapObjectFieldKey(static_cast<UINT>(wParam));
keyCode != static_cast<std::int32_t>(KeyCode::None)) {
HandleKeyDown(keyCode);
return 0;
}
break;
default:
break;
}
return DefWindowProcW(hwnd, message, wParam, lParam);
}
HWND m_hwnd = nullptr;
HINSTANCE m_hInstance = nullptr;
ATOM m_windowClassAtom = 0;
NativeRenderer m_renderer = {};
AutoScreenshotController m_autoScreenshot = {};
std::filesystem::path m_captureRoot = {};
XCEngine::Tests::EditorUI::EditorValidationShellPalette m_shellPalette = {};
XCEngine::Tests::EditorUI::EditorValidationShellMetrics m_shellMetrics = {};
XCEngine::UI::Editor::Widgets::UIEditorObjectFieldPalette m_fieldPalette = {};
XCEngine::UI::Editor::Widgets::UIEditorObjectFieldMetrics m_fieldMetrics = {};
UIEditorObjectFieldSpec m_spec = {};
UIEditorObjectFieldInteractionState m_interactionState = {};
UIEditorObjectFieldInteractionFrame m_frame = {};
std::vector<UIInputEvent> m_pendingInputEvents = {};
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
ActionId m_hoveredAction = ActionId::Reset;
ActionId m_pressedAction = ActionId::Reset;
bool m_hasHoveredAction = false;
bool m_hasPressedAction = false;
std::string m_lastResult = {};
std::size_t m_activateCount = 0u;
};
} // namespace
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) {
return ScenarioApp().Run(hInstance, nCmdShow);
}

View File

@@ -2,9 +2,9 @@
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorPanelContentHost.h>
#include <XCEditor/Core/UIEditorWorkspaceCompose.h>
#include <XCEditor/Core/UIEditorWorkspaceController.h>
#include <XCEditor/Shell/UIEditorPanelContentHost.h>
#include <XCEditor/Shell/UIEditorWorkspaceCompose.h>
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -376,7 +376,7 @@ private:
m_composeState = {};
m_lastStatus = "Ready";
m_lastMessage =
"当前先看 doc-a inspector:两者都会 mount切到 Console 后tab body 会回退成 placeholder";
"默认状态下 doc-a inspector 会挂到外部内容宿主Console 仍然走占位内容路径";
UpdateComposeFrame();
}
@@ -406,8 +406,8 @@ private:
{ ActionId::ActivateInspector, "Activate Inspector", UIRect(buttonLeft + buttonWidth + 16.0f, buttonTop + buttonHeight + rowGap, buttonWidth, buttonHeight), false },
{ ActionId::CloseInspector, "Close Inspector", UIRect(buttonLeft, buttonTop + (buttonHeight + rowGap) * 2.0f, buttonWidth, buttonHeight), false },
{ ActionId::OpenInspector, "Open Inspector", UIRect(buttonLeft + buttonWidth + 16.0f, buttonTop + (buttonHeight + rowGap) * 2.0f, buttonWidth, buttonHeight), false },
{ ActionId::Reset, "Reset", UIRect(buttonLeft, buttonTop + (buttonHeight + rowGap) * 3.0f, buttonWidth, buttonHeight), false },
{ ActionId::Capture, "Capture(F12)", UIRect(buttonLeft + buttonWidth + 16.0f, buttonTop + (buttonHeight + rowGap) * 3.0f, buttonWidth, buttonHeight), false }
{ ActionId::Reset, "重置", UIRect(buttonLeft, buttonTop + (buttonHeight + rowGap) * 3.0f, buttonWidth, buttonHeight), false },
{ ActionId::Capture, "截图(F12)", UIRect(buttonLeft + buttonWidth + 16.0f, buttonTop + (buttonHeight + rowGap) * 3.0f, buttonWidth, buttonHeight), false }
};
}
@@ -443,7 +443,7 @@ private:
m_autoScreenshot.RequestCapture("manual_button");
m_lastStatus = "Ready";
m_lastMessage =
"截图已排队,输出到 tests/UI/Editor/integration/shell/panel_content_host_basic/captures/。";
"已请求截图,输出到 tests/UI/Editor/integration/shell/panel_content_host_basic/captures/。";
return;
}
@@ -507,19 +507,19 @@ private:
UIDrawList& drawList = drawData.EmplaceDrawList("PanelContentHostBasic");
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg);
DrawCard(drawList, m_introRect, "这个测试验证什么功能", "验证 Editor panel content host contract,不做业务逻辑。");
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 72.0f), "1. 验证 HostedContent panel 会正式接管 DockHost body而不是继续画 placeholder", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 94.0f), "2. 验证 tab 切换时,旧 body unmount,新 body mount", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 116.0f), "3. 验证切到 Console 这种 placeholder panel 后,external host 会退出", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 138.0f), "4. 验证 standalone HostedContent close/open 时,会发生 unmount / remount", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 164.0f), "建议操作Doc A -> Doc B -> Console -> Open/Close Inspector观察 Mounted Panels 和 Events", kTextWeak, 11.0f);
DrawCard(drawList, m_introRect, "这个测试验证什么功能", "验证面板内容宿主在 DockHost 中的挂载、卸载和占位回退契约,不做业务逻辑。");
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 72.0f), "1. HostedContent panel 应挂到 DockHost 外部 body不再走占位内容", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 94.0f), "2. 切换 tab 时,旧 body 应卸载,新 body 应挂载", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 116.0f), "3. Console 仍然是占位面板,不应该进入 external host。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 138.0f), "4. 关闭再打开 inspector应看到卸载和重新挂载事件", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 164.0f), "建议顺序Doc A -> Doc B -> Console -> Close/Open Inspector", kTextWeak, 11.0f);
DrawCard(drawList, m_controlsRect, "操作", "只保留内容承载 contract 必要按钮。");
DrawCard(drawList, m_controlsRect, "操作", "这里只保留当前契约验证需要的按钮。");
for (const ButtonState& button : m_buttons) {
DrawButton(drawList, button);
}
DrawCard(drawList, m_stateRect, "状态", "重点检查 mounted panel 集合和本帧 mount events");
DrawCard(drawList, m_stateRect, "状态", "重点检查已挂载面板集合和本帧挂载事件");
float stateY = m_stateRect.y + 66.0f;
auto addStateLine = [&](std::string label, std::string value, const UIColor& color, float fontSize = 12.0f) {
drawList.AddText(UIPoint(m_stateRect.x + 18.0f, stateY), std::move(label) + ": " + std::move(value), color, fontSize);
@@ -539,16 +539,16 @@ private:
addStateLine("doc-b", DescribeMountedState(m_composeFrame, "doc-b"), kTextWeak, 11.0f);
addStateLine("inspector", DescribeMountedState(m_composeFrame, "inspector"), kTextWeak, 11.0f);
addStateLine(
"Screenshot",
"截图",
m_autoScreenshot.HasPendingCapture()
? "截图排队中..."
: (m_autoScreenshot.GetLastCaptureSummary().empty()
? std::string("F12 或 Capture -> captures/")
? std::string("F12 或点按钮 -> captures/")
: m_autoScreenshot.GetLastCaptureSummary()),
kTextWeak,
11.0f);
DrawCard(drawList, m_previewRect, "Preview", "DockHost 壳;蓝色内容块是 external content host 实际挂进去的 body。");
DrawCard(drawList, m_previewRect, "预览", "DockHost 只画外壳;蓝色内容块表示外部内容宿主实际挂的 body。");
AppendUIEditorWorkspaceCompose(drawList, m_composeFrame);
for (const UIEditorPanelContentHostPanelState& panelState : m_composeFrame.contentHostFrame.panelStates) {
if (!panelState.mounted ||

View File

@@ -2,7 +2,7 @@
#define NOMINMAX
#endif
#include <XCEditor/Widgets/UIEditorPanelFrame.h>
#include <XCEditor/Shell/UIEditorPanelFrame.h>
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -375,7 +375,7 @@ private:
{ ActionId::ToggleFooter, "Footer", UIRect(left, top, width, buttonHeight), m_state.showFooter },
{ ActionId::TogglePin, "Pin Button", UIRect(left, top + (buttonHeight + gap), width, buttonHeight), m_state.pinnable },
{ ActionId::ToggleClose, "Close Button", UIRect(left, top + (buttonHeight + gap) * 2.0f, width, buttonHeight), m_state.closable },
{ ActionId::Reset, "Reset", UIRect(left, top + (buttonHeight + gap) * 3.0f, width, buttonHeight), false }
{ ActionId::Reset, "重置", UIRect(left, top + (buttonHeight + gap) * 3.0f, width, buttonHeight), false }
};
}
@@ -405,25 +405,30 @@ private:
DrawCard(
drawList,
introRect,
"测试功能PanelFrame 基础壳层",
"重点检查Header / Body / Footer 布局Active / Focus 边框Pin / Close 命中。");
"这个测试验证什么功能?",
"验证 PanelFrame 的 header/body/footer 分区,以及 active、focus、pin、close 命中。");
drawList.AddText(
UIPoint(introRect.x + 16.0f, introRect.y + 66.0f),
"操作:移动鼠标观察 hover点 Header 切 Active点 Body/Footer 取 Focus点 Pin/Close 看 Result按 F12 截图",
"1. hover 各区域,检查命中 part 是否正确",
kTextMuted,
12.0f);
drawList.AddText(
UIPoint(introRect.x + 16.0f, introRect.y + 90.0f),
"这个场景只验证 Editor 基础层 PanelFrame不包含任何业务面板",
"2. 点击 Header、Body、Footer、Pin、Close检查状态变化",
kTextMuted,
12.0f);
drawList.AddText(
UIPoint(introRect.x + 16.0f, introRect.y + 114.0f),
"3. 右侧只放一个 PanelFrame不接业务面板。",
kTextWeak,
12.0f);
DrawCard(drawList, controlsRect, "开关", "这里只保留基础状态开关,避免试验面板过杂");
DrawCard(drawList, controlsRect, "操作", "只保留影响 PanelFrame contract 的开关");
for (const ButtonState& button : m_buttons) {
DrawButton(drawList, button);
}
DrawCard(drawList, stateRect, "状态", "右侧面板的命中结果和状态变化会同步显示在这里");
DrawCard(drawList, stateRect, "状态摘要", "重点看 hover、active、focus、pinned 和结果");
drawList.AddText(
UIPoint(stateRect.x + 16.0f, stateRect.y + 66.0f),
"Hover: " + DescribePart(m_hoveredPart),

View File

@@ -1,10 +1,10 @@
#ifndef NOMINMAX
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorPropertyGridInteraction.h>
#include <XCEditor/Core/UIEditorTheme.h>
#include <XCEditor/Widgets/UIEditorPropertyGrid.h>
#include <XCEditor/Fields/UIEditorPropertyGridInteraction.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
#include <XCEditor/Fields/UIEditorPropertyGrid.h>
#include "EditorValidationTheme.h"
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -57,7 +57,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorPropertyGridFieldKind;
using XCEngine::UI::Editor::Widgets::UIEditorPropertyGridHitTarget;
using XCEngine::UI::Editor::Widgets::UIEditorPropertyGridHitTargetKind;
using XCEngine::UI::Editor::Widgets::UIEditorPropertyGridSection;
namespace Style = XCEngine::UI::Style;
constexpr const wchar_t* kWindowClassName = L"XCUIEditorPropertyGridBasicValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | PropertyGrid Basic";
@@ -91,11 +90,6 @@ std::filesystem::path ResolveRepoRootPath() {
return std::filesystem::path(root).lexically_normal();
}
std::filesystem::path ResolveValidationThemePath() {
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
.lexically_normal();
}
bool ContainsPoint(const UIRect& rect, float x, float y) {
return x >= rect.x &&
x <= rect.x + rect.width &&
@@ -543,15 +537,6 @@ private:
m_captureRoot =
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/property_grid_basic/captures";
m_autoScreenshot.Initialize(m_captureRoot);
const auto themeLoad =
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
if (themeLoad.succeeded) {
m_theme = themeLoad.theme;
m_themeStatus = "loaded";
} else {
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
}
ResetScenario();
return true;
}
@@ -579,7 +564,7 @@ private:
return BuildScenarioLayout(
width,
height,
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
}
void ResetScenario() {
@@ -606,7 +591,7 @@ private:
}
const ScenarioLayout layout = GetLayout();
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(m_theme);
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics();
m_gridFrame =
UpdateUIEditorPropertyGridInteraction(
m_interactionState,
@@ -741,7 +726,7 @@ private:
UIEditorPropertyGridInteractionResult PumpGridEvents(std::vector<UIInputEvent> events) {
const ScenarioLayout layout = GetLayout();
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(m_theme);
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics();
m_gridFrame =
UpdateUIEditorPropertyGridInteraction(
m_interactionState,
@@ -855,17 +840,17 @@ private:
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 shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
RefreshGridFrame();
const UIEditorPropertyGridHitTarget currentHit =
HitTestUIEditorPropertyGrid(m_gridFrame.layout, m_mousePosition);
const auto propertyMetrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(m_theme);
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme);
const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics(m_theme);
const auto popupPalette = XCEngine::UI::Editor::ResolveUIEditorMenuPopupPalette(m_theme);
const auto propertyMetrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics();
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette();
const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics();
const auto popupPalette = XCEngine::UI::Editor::ResolveUIEditorMenuPopupPalette();
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("EditorPropertyGridBasic");
@@ -987,7 +972,7 @@ private:
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 310.0f),
"Theme: " + m_themeStatus,
"Style: fixed",
shellPalette.textWeak,
shellMetrics.bodyFontSize);
@@ -1037,14 +1022,11 @@ private:
UIExpansionModel m_expansionModel = {};
UIPropertyEditModel m_propertyEditModel = {};
UIEditorPropertyGridInteractionState m_interactionState = {};
UIEditorPropertyGridInteractionFrame m_gridFrame = {};
Style::UITheme m_theme = {};
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
UIEditorPropertyGridInteractionFrame m_gridFrame = {}; UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
ActionId m_hoveredAction = ActionId::Reset;
bool m_hasHoveredAction = false;
std::string m_lastResult = {};
std::string m_lastCommit = {};
std::string m_themeStatus = "fallback";
};
} // namespace

View File

@@ -2,8 +2,8 @@
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorScrollViewInteraction.h>
#include <XCEditor/Widgets/UIEditorScrollView.h>
#include <XCEditor/Collections/UIEditorScrollViewInteraction.h>
#include <XCEditor/Collections/UIEditorScrollView.h>
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -194,7 +194,7 @@ std::string DescribeHitTarget(const UIEditorScrollViewHitTarget& hitTarget) {
return "scrollbar-thumb";
case UIEditorScrollViewHitTargetKind::None:
default:
return "";
return "none";
}
}
@@ -423,7 +423,7 @@ private:
m_mousePosition = UIPoint(-1000.0f, -1000.0f);
m_hoveredAction = ActionId::Reset;
m_hasHoveredAction = false;
m_lastResult = "已重置到 " + std::to_string(m_logLines.size()) + " 行默认滚动位置";
m_lastResult = "已重置到 " + std::to_string(m_logLines.size()) + " 行默认滚动内容";
RefreshScrollFrame();
}
@@ -478,7 +478,7 @@ private:
const UIEditorScrollViewInteractionResult result =
PumpScrollEvents({ MakeWheelEvent(m_mousePosition, wheelDelta) });
if (result.offsetChanged) {
m_lastResult = "滚轮滚动: offset 已更新";
m_lastResult = "滚轮滚动offset 已更新";
}
InvalidateRect(m_hwnd, nullptr, FALSE);
}
@@ -568,23 +568,23 @@ private:
}
if (result.offsetChanged) {
m_lastResult = "滚动位置变化";
m_lastResult = "滚动位置变化";
return;
}
if (result.focusChanged) {
m_lastResult = m_interactionState.scrollViewState.focused
? "ScrollView 获得 focus"
? "ScrollView 获得 focus"
: "ScrollView focus 已清除";
return;
}
if (insideScrollView) {
m_lastResult = "点击内容区域: 只验证 focus / hover / scrollbar";
m_lastResult = "点击内容区只验证 focus / hover / scrollbar";
return;
}
m_lastResult = "等待交互";
m_lastResult = "无变化";
}
void ExecuteAction(ActionId action) {
@@ -622,31 +622,31 @@ private:
DrawCard(
drawList,
layout.introRect,
"这个测试验证什么功能",
"验证 Editor ScrollView 基础控件,不涉及任何业务面板");
"这个测试验证什么功能",
"验证滚动视图的滚轮滚动、thumb 拖拽、focus 切换和 offset clamp");
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
"1. 把鼠标放到右侧日志区内滚动滚轮:内容应上下移动,offset 应变化",
"1. 在内容区滚轮滚动,检查 offset 是否连续更新",
kTextPrimary,
12.0f);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
"2. 继续滚到边界后 offset 被 clamp,不能越界",
"2. 滚到底部后继续滚动,offset 必须被 clamp。",
kTextPrimary,
12.0f);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
"3. 拖拽 scrollbar thumb内容位置应同步变化",
"3. 拖拽 scrollbar thumb检查 offset 与 thumb 位置是否同步",
kTextPrimary,
12.0f);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f),
"4. 点击内容区只改变 focus点击外部空白后 focus 应清除",
"4. 点击内容区只更新 focus / hover;点击外部应能清掉 focus。",
kTextPrimary,
12.0f);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
"5. 按 F12 手动截图;设置 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可自动截图。",
"5. 按 F12 设置 XCUI_AUTO_CAPTURE_ON_STARTUP=1 触发截图。",
kTextPrimary,
12.0f);
@@ -666,7 +666,7 @@ private:
12.0f);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 94.0f),
std::string("Focused: ") + (m_interactionState.scrollViewState.focused ? "" : ""),
std::string("Focused: ") + (m_interactionState.scrollViewState.focused ? "" : ""),
kTextPrimary,
12.0f);
drawList.AddText(

View File

@@ -2,7 +2,7 @@
#define NOMINMAX
#endif
#include <XCEditor/Widgets/UIEditorStatusBar.h>
#include <XCEditor/Shell/UIEditorStatusBar.h>
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -313,10 +313,10 @@ private:
if (m_hoverTarget.kind == UIEditorStatusBarHitTargetKind::Segment) {
m_state.activeIndex = m_hoverTarget.index;
m_state.focused = true;
m_lastResult = "激活 segment: " + m_segments[m_hoverTarget.index].segmentId;
m_lastResult = "命中 segment: " + m_segments[m_hoverTarget.index].segmentId;
} else if (m_hoverTarget.kind == UIEditorStatusBarHitTargetKind::Background) {
m_state.activeIndex = UIEditorStatusBarInvalidIndex;
m_lastResult = "点击 status bar background";
m_lastResult = "命中 status bar background";
} else {
m_lastResult = "命中 " + DescribeHitTarget(m_hoverTarget);
}
@@ -330,22 +330,22 @@ private:
m_segments[1].tone == UIEditorStatusBarTextTone::Accent
? UIEditorStatusBarTextTone::Primary
: UIEditorStatusBarTextTone::Accent;
m_lastResult = "切换 Selection 文本强调";
m_lastResult = "Selection 强调已切换";
break;
case ActionId::ToggleSeparator:
m_segments[0].showSeparator = !m_segments[0].showSeparator;
m_lastResult = m_segments[0].showSeparator ? "开启 Leading separator" : "关闭 Leading separator";
m_lastResult = m_segments[0].showSeparator ? "Leading separator 已开启" : "Leading separator 已关闭";
break;
case ActionId::MoveToTrailing:
m_segments[1].slot =
m_segments[1].slot == UIEditorStatusBarSlot::Leading
? UIEditorStatusBarSlot::Trailing
: UIEditorStatusBarSlot::Leading;
m_lastResult = "切换 Selection slot -> " + DescribeSlot(m_segments[1].slot);
m_lastResult = "Selection slot -> " + DescribeSlot(m_segments[1].slot);
break;
case ActionId::Reset:
ResetState();
m_lastResult = "状态重置";
m_lastResult = "状态重置";
break;
case ActionId::Capture:
m_autoScreenshot.RequestCapture("manual_button");
@@ -368,7 +368,7 @@ private:
m_state.focused = true;
m_state.activeIndex = 1u;
m_hoverTarget = {};
m_lastResult = "Ready";
m_lastResult = "就绪";
}
void UpdateHover() {
@@ -383,11 +383,11 @@ private:
const float buttonHeight = 34.0f;
const float gap = 10.0f;
m_buttons = {
{ ActionId::ToggleAccent, "强调文本", UIRect(left, top, width, buttonHeight), m_segments[1].tone == UIEditorStatusBarTextTone::Accent },
{ ActionId::ToggleSeparator, "Leading 分隔线", UIRect(left, top + (buttonHeight + gap), width, buttonHeight), m_segments[0].showSeparator },
{ ActionId::ToggleAccent, "切换强调", UIRect(left, top, width, buttonHeight), m_segments[1].tone == UIEditorStatusBarTextTone::Accent },
{ ActionId::ToggleSeparator, "Leading separator", UIRect(left, top + (buttonHeight + gap), width, buttonHeight), m_segments[0].showSeparator },
{ ActionId::MoveToTrailing, "切换 Selection Slot", UIRect(left, top + (buttonHeight + gap) * 2.0f, width, buttonHeight), m_segments[1].slot == UIEditorStatusBarSlot::Trailing },
{ ActionId::Reset, "Reset", UIRect(left, top + (buttonHeight + gap) * 3.0f, width, buttonHeight), false },
{ ActionId::Capture, "截图", UIRect(left, top + (buttonHeight + gap) * 4.0f, width, buttonHeight), false }
{ ActionId::Reset, "重置", UIRect(left, top + (buttonHeight + gap) * 3.0f, width, buttonHeight), false },
{ ActionId::Capture, "截图(F12)", UIRect(left, top + (buttonHeight + gap) * 4.0f, width, buttonHeight), false }
};
}
@@ -429,25 +429,30 @@ private:
DrawCard(
drawList,
introRect,
"测试功能StatusBar 基础壳层",
"重点检查Leading / Trailing slot 对齐文本强调separator 开关,hover / active 命中。");
"这个测试验证什么功能?",
"验证 StatusBar 的 leading/trailing 布局、separator、强调文本以及 hover/active 命中。");
drawList.AddText(
UIPoint(introRect.x + 16.0f, introRect.y + 66.0f),
"操作:hover 观察 segment 高亮;点击 segment 切 active切换左侧按钮检查 slot / separator / emphasis按 F12 或点“截图”",
"1. hover 不同 segment / separator检查命中是否正确",
kTextMuted,
12.0f);
drawList.AddText(
UIPoint(introRect.x + 16.0f, introRect.y + 90.0f),
"这个场景只验证 Editor 基础层 StatusBar不混入任何业务面板",
"2. 点击 segment检查 active 是否切到对应项",
kTextMuted,
12.0f);
drawList.AddText(
UIPoint(introRect.x + 16.0f, introRect.y + 114.0f),
"3. 切换强调、separator 和 Selection slot检查布局是否稳定。",
kTextWeak,
12.0f);
DrawCard(drawList, controlsRect, "开关", "只保留和当前 StatusBar contract 直接相关的操作");
DrawCard(drawList, controlsRect, "操作", "只保留 StatusBar contract 直接相关的开关");
for (const ButtonState& button : m_buttons) {
DrawButton(drawList, button);
}
DrawCard(drawList, stateRect, "状态", "命中、active、slot 和结果统一回显在这里");
DrawCard(drawList, stateRect, "状态摘要", "重点看 hover、active、Selection slot、separator 和结果");
drawList.AddText(
UIPoint(stateRect.x + 16.0f, stateRect.y + 66.0f),
"Hover: " + DescribeHitTarget(m_hoverTarget),
@@ -489,12 +494,12 @@ private:
drawList.AddRectOutline(viewportRect, kCardBorder, 1.0f, 10.0f);
drawList.AddText(
UIPoint(viewportRect.x + 18.0f, viewportRect.y + 18.0f),
"Preview Host",
"预览宿主",
kTextPrimary,
18.0f);
drawList.AddText(
UIPoint(viewportRect.x + 18.0f, viewportRect.y + 48.0f),
"这里只保留一个空白宿主区域,专门看底部 StatusBar 的对齐和交互",
"这里只放一个宿主区域,用来观察底部 StatusBar 的布局和状态变化",
kTextMuted,
12.0f);

View File

@@ -2,10 +2,10 @@
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorTabStripInteraction.h>
#include <XCEditor/Core/UIEditorPanelRegistry.h>
#include <XCEditor/Core/UIEditorWorkspaceController.h>
#include <XCEditor/Widgets/UIEditorTabStrip.h>
#include <XCEditor/Collections/UIEditorTabStripInteraction.h>
#include <XCEditor/Shell/UIEditorPanelRegistry.h>
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
#include <XCEditor/Collections/UIEditorTabStrip.h>
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -79,7 +79,6 @@ constexpr UIColor kTextPrimary(0.94f, 0.94f, 0.94f, 1.0f);
constexpr UIColor kTextMuted(0.73f, 0.73f, 0.73f, 1.0f);
constexpr UIColor kTextWeak(0.58f, 0.58f, 0.58f, 1.0f);
constexpr UIColor kSuccess(0.62f, 0.74f, 0.62f, 1.0f);
constexpr UIColor kWarning(0.78f, 0.70f, 0.46f, 1.0f);
constexpr UIColor kDanger(0.82f, 0.48f, 0.48f, 1.0f);
constexpr UIColor kButtonBg(0.25f, 0.25f, 0.25f, 1.0f);
constexpr UIColor kButtonHoveredBg(0.31f, 0.31f, 0.31f, 1.0f);
@@ -402,7 +401,7 @@ private:
m_tabStripFrame = {};
m_tabItems.clear();
m_mousePosition = UIPoint(-1000.0f, -1000.0f);
m_lastResult = "等待操作";
m_lastResult = "已重置到默认标签状态";
}
UIRect GetTabStripRect() const {
@@ -524,7 +523,7 @@ private:
if (result.hitTarget.kind == UIEditorTabStripHitTargetKind::HeaderBackground ||
result.hitTarget.kind == UIEditorTabStripHitTargetKind::Content) {
m_lastResult = "TabStrip 获得 focus";
m_lastResult = "命中 TabStrip 背景,保留 focus";
return;
}
@@ -577,7 +576,6 @@ private:
m_tabItems.push_back(std::move(item));
}
}
}
void RenderFrame() {
@@ -610,30 +608,30 @@ private:
DrawCard(
drawList,
introRect,
"测试功能Editor TabStrip 基础层",
"验证 TabStrip header / hit-test / selection / close / keyboard不包含任何业务面板。");
"这个测试验证什么功能?",
"验证 TabStrip header 命中、选中切换、关闭请求和键盘导航,不接业务面板。");
drawList.AddText(
UIPoint(introRect.x + 16.0f, introRect.y + 68.0f),
"重点检查tab 布局是否整齐selected / hover / focus 是否正确Close 后 active panel 是否回退正确",
"1. 点击 tab检查 selected / active panel 是否同步",
kTextPrimary,
12.0f);
drawList.AddText(
UIPoint(introRect.x + 16.0f, introRect.y + 92.0f),
"操作:点击 tab 切换;点击 X 关闭Document C 没有 X点击内容区后按 Left / Right / Home / EndReset 恢复F12 截图",
"2. 点击 X只验证关闭请求Document C 没有关闭按钮",
kTextMuted,
12.0f);
drawList.AddText(
UIPoint(introRect.x + 16.0f, introRect.y + 116.0f),
"预期Close 命中优先于 tab键盘导航只在 focused 时生效Document C 始终保留",
"3. Left / Right / Home / End 验证键盘导航;按重置恢复初始状态",
kTextWeak,
12.0f);
DrawCard(
drawList,
stateRect,
"状态回显",
"这里直接回显 hover / focus / active panel / result方便人工检查");
DrawButton(drawList, m_resetButtonRect, "Reset", m_resetButtonHovered);
"状态摘要",
"持续显示 hoverfocusactive panel、tabs、result 和校验结果");
DrawButton(drawList, m_resetButtonRect, "重置", m_resetButtonHovered);
const std::size_t selectedIndex =
ResolveUIEditorTabStripSelectedIndex(m_tabItems, m_controller.GetWorkspace().activePanelId);
@@ -676,7 +674,7 @@ private:
12.0f);
drawList.AddText(
UIPoint(stateRect.x + 16.0f, stateRect.y + 276.0f),
validation.IsValid() ? "Validation: OK" : "Validation: " + validation.message,
validation.IsValid() ? "校验: OK" : "校验: " + validation.message,
validation.IsValid() ? kSuccess : kDanger,
12.0f);
@@ -696,7 +694,7 @@ private:
drawList,
previewCardRect,
"预览区",
"这里只放一个 TabStrip 和一个 content placeholder避免试验面板过杂");
"这里只放一个 TabStrip 和一个 content placeholder用来观察 header 与 content frame");
AppendUIEditorTabStripBackground(drawList, m_layout, m_tabStripState);
AppendUIEditorTabStripForeground(drawList, m_layout, m_tabItems, m_tabStripState);
@@ -716,12 +714,12 @@ private:
13.0f);
drawList.AddText(
UIPoint(m_layout.contentRect.x + 20.0f, m_layout.contentRect.y + 76.0f),
"个区域只用于验证 TabStrip content frame 与 focus,不承载业务内容。",
"里只验证 TabStrip content frame 与 focus 同步,不接业务内容。",
kTextWeak,
12.0f);
drawList.AddText(
UIPoint(m_layout.contentRect.x + 20.0f, m_layout.contentRect.y + 100.0f),
"检查点:关闭 Document B 后,应自动回退到相邻 tabDocument C 无法关闭",
"可点击 Document B 切换,或点击 Document C 验证不可关闭 tab",
kTextWeak,
12.0f);

View File

@@ -1,10 +1,11 @@
#ifndef NOMINMAX
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorTextFieldInteraction.h>
#include <XCEditor/Core/UIEditorTheme.h>
#include <XCEditor/Widgets/UIEditorTextField.h>
#include <XCEditor/Fields/UIEditorTextFieldInteraction.h>
#include <XCEditor/Fields/UIEditorFieldStyle.h>
#include <XCEditor/Fields/UIEditorTextField.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
#include "EditorValidationTheme.h"
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -25,6 +26,8 @@
#define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "."
#endif
// 场景说明:验证固定样式下 TextField 的基础编辑、提交、取消与焦点切换。
namespace {
using XCEngine::Input::KeyCode;
@@ -46,7 +49,6 @@ using XCEngine::UI::Editor::Widgets::HitTestUIEditorTextField;
using XCEngine::UI::Editor::Widgets::UIEditorTextFieldHitTarget;
using XCEngine::UI::Editor::Widgets::UIEditorTextFieldHitTargetKind;
using XCEngine::UI::Editor::Widgets::UIEditorTextFieldSpec;
namespace Style = XCEngine::UI::Style;
constexpr const wchar_t* kWindowClassName = L"XCUIEditorTextFieldBasicValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | TextField Basic";
@@ -83,10 +85,6 @@ std::filesystem::path ResolveRepoRootPath() {
return std::filesystem::path(root).lexically_normal();
}
std::filesystem::path ResolveValidationThemePath() {
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
.lexically_normal();
}
bool ContainsPoint(const UIRect& rect, float x, float y) {
return x >= rect.x &&
@@ -157,18 +155,16 @@ ScenarioLayout BuildScenarioLayout(
return layout;
}
XCEngine::UI::Editor::Widgets::UIEditorTextFieldMetrics ResolveHostedTextFieldMetrics(
const Style::UITheme& theme) {
const auto propertyMetrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(theme);
const auto textMetrics = XCEngine::UI::Editor::ResolveUIEditorTextFieldMetrics(theme);
return XCEngine::UI::Editor::BuildUIEditorHostedTextFieldMetrics(propertyMetrics, textMetrics);
XCEngine::UI::Editor::Widgets::UIEditorTextFieldMetrics ResolvePropertyGridTextFieldMetrics() {
return XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldMetrics(
XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(),
XCEngine::UI::Editor::ResolveUIEditorTextFieldMetrics());
}
XCEngine::UI::Editor::Widgets::UIEditorTextFieldPalette ResolveHostedTextFieldPalette(
const Style::UITheme& theme) {
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(theme);
const auto textPalette = XCEngine::UI::Editor::ResolveUIEditorTextFieldPalette(theme);
return XCEngine::UI::Editor::BuildUIEditorHostedTextFieldPalette(propertyPalette, textPalette);
XCEngine::UI::Editor::Widgets::UIEditorTextFieldPalette ResolvePropertyGridTextFieldPalette() {
return XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldPalette(
XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(),
XCEngine::UI::Editor::ResolveUIEditorTextFieldPalette());
}
void DrawCard(
@@ -427,14 +423,6 @@ private:
m_captureRoot =
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/text_field_basic/captures";
m_autoScreenshot.Initialize(m_captureRoot);
const auto themeLoad =
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
if (themeLoad.succeeded) {
m_theme = themeLoad.theme;
m_themeStatus = "loaded";
} else {
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
}
ResetScenario();
return true;
@@ -463,7 +451,7 @@ private:
return BuildScenarioLayout(
width,
height,
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
}
void ResetScenario() {
@@ -487,7 +475,7 @@ private:
}
const ScenarioLayout layout = GetLayout();
const auto metrics = ResolveHostedTextFieldMetrics(m_theme);
const auto metrics = ResolvePropertyGridTextFieldMetrics();
m_frame = UpdateUIEditorTextFieldInteraction(
m_interactionState,
m_spec,
@@ -605,7 +593,7 @@ private:
UIEditorTextFieldInteractionResult PumpEvents(std::vector<UIInputEvent> events) {
const ScenarioLayout layout = GetLayout();
const auto metrics = ResolveHostedTextFieldMetrics(m_theme);
const auto metrics = ResolvePropertyGridTextFieldMetrics();
m_frame = UpdateUIEditorTextFieldInteraction(
m_interactionState,
m_spec,
@@ -661,16 +649,16 @@ private:
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 shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
RefreshFrame();
const UIEditorTextFieldHitTarget currentHit =
HitTestUIEditorTextField(m_frame.layout, m_mousePosition);
const auto textMetrics = ResolveHostedTextFieldMetrics(m_theme);
const auto textPalette = ResolveHostedTextFieldPalette(m_theme);
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme);
const auto textMetrics = ResolvePropertyGridTextFieldMetrics();
const auto textPalette = ResolvePropertyGridTextFieldPalette();
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette();
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("EditorTextFieldBasic");
@@ -681,8 +669,8 @@ private:
layout.introRect,
shellPalette,
shellMetrics,
"这个测试验证什么功能",
"验证 UIEditorTextField 的基础编辑交互契约,不涉及 PropertyGrid 或业务 Inspector。");
"这个测试验证什么功能",
"验证 UIEditorTextField 的基础编辑契约,不涉及 PropertyGrid 或任何 Inspector 业务逻辑");
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
"1. 点击 value box检查是否进入编辑态。",
@@ -775,7 +763,7 @@ private:
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 238.0f),
"Theme: " + m_themeStatus,
"Style: fixed",
shellPalette.textWeak,
shellMetrics.bodyFontSize);
@@ -785,7 +773,7 @@ private:
shellPalette,
shellMetrics,
"TextField 预览",
"这里只放一个 Editor TextField用来验证基础字段行为。");
"这里只放一个固定样式 TextField用来验证基础字段行为。");
drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor);
drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f);
drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground);
@@ -827,12 +815,10 @@ private:
UIEditorTextFieldSpec m_spec = {};
UIEditorTextFieldInteractionState m_interactionState = {};
UIEditorTextFieldInteractionFrame m_frame = {};
Style::UITheme m_theme = {};
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
ActionId m_hoveredAction = ActionId::Reset;
bool m_hasHoveredAction = false;
std::string m_lastResult = {};
std::string m_themeStatus = "fallback";
};
} // namespace

View File

@@ -684,8 +684,8 @@ private:
DrawCard(
drawList,
layout.introRect,
"这个测试验证什么功能?",
"只验证 Editor TreeView 的单选、层级展开/折叠和键盘导航契约,不涉及任何业务面板。");
"这个测试验证什么功能?",
"只验证 Editor TreeView 的单选、层级展开/折叠和键盘导航不涉及任何业务面板。");
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
"1. 点击 row只切换 selectionhover / selected / focused 必须能明确区分。",
@@ -708,7 +708,7 @@ private:
12.0f);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
"5. 点击树外空白清除 focusF12 手动截图XCUI_AUTO_CAPTURE_ON_STARTUP=1 自动截图。",
"5. 点击树外空白清除 focusF12 手动截图XCUI_AUTO_CAPTURE_ON_STARTUP=1 可启动自动截图。",
kTextPrimary,
12.0f);

View File

@@ -5,6 +5,7 @@
#include <XCEditor/Collections/UIEditorInlineRenameSession.h>
#include <XCEditor/Collections/UIEditorTreeView.h>
#include <XCEditor/Collections/UIEditorTreeViewInteraction.h>
#include <XCEditor/Fields/UIEditorFieldStyle.h>
#include <XCEditor/Fields/UIEditorTextField.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
#include "EditorValidationTheme.h"
@@ -13,7 +14,6 @@
#include <XCEngine/Input/InputTypes.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEngine/UI/Style/Theme.h>
#include <XCEngine/UI/Widgets/UIExpansionModel.h>
#include <XCEngine/UI/Widgets/UISelectionModel.h>
@@ -32,6 +32,7 @@
#define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "."
#endif
// 场景说明:验证固定样式下 TreeView 的 inline rename 启动、提交、取消与外部点击提交。
namespace {
using XCEngine::Input::KeyCode;
@@ -46,8 +47,8 @@ using XCEngine::UI::UIPointerButton;
using XCEngine::UI::UIRect;
using XCEngine::UI::Widgets::UIExpansionModel;
using XCEngine::UI::Widgets::UISelectionModel;
using XCEngine::UI::Editor::BuildUIEditorHostedTextFieldMetrics;
using XCEngine::UI::Editor::BuildUIEditorHostedTextFieldPalette;
using XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldMetrics;
using XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldPalette;
using XCEngine::UI::Editor::BuildUIEditorInlineRenameTextFieldMetrics;
using XCEngine::UI::Editor::Host::AutoScreenshotController;
using XCEngine::UI::Editor::Host::NativeRenderer;
@@ -75,8 +76,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorTreeViewInvalidIndex;
using XCEngine::UI::Editor::Widgets::UIEditorTreeViewItem;
using XCEngine::UI::Editor::Widgets::UIEditorTextFieldMetrics;
using XCEngine::UI::Editor::Widgets::UIEditorTextFieldPalette;
namespace Style = XCEngine::UI::Style;
constexpr const wchar_t* kWindowClassName = L"XCUIEditorTreeViewInlineRenameValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | TreeView Inline Rename";
@@ -93,7 +92,6 @@ struct ScenarioLayout {
};
std::filesystem::path ResolveRepoRootPath();
std::filesystem::path ResolveValidationThemePath();
bool ContainsPoint(const UIRect& rect, float x, float y);
std::int32_t MapTreeKey(UINT keyCode);
ScenarioLayout BuildScenarioLayout(float width, float height, const EditorValidationShellMetrics& shellMetrics);
@@ -143,8 +141,6 @@ private:
NativeRenderer m_renderer = {};
AutoScreenshotController m_autoScreenshot = {};
std::filesystem::path m_captureRoot = {};
Style::UITheme m_theme = {};
std::string m_themeStatus = "fallback";
std::vector<UIEditorTreeViewItem> m_items = {};
UISelectionModel m_selectionModel = {};
UIExpansionModel m_expansionModel = {};
@@ -168,10 +164,6 @@ std::filesystem::path ResolveRepoRootPath() {
return std::filesystem::path(root).lexically_normal();
}
std::filesystem::path ResolveValidationThemePath() {
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme").lexically_normal();
}
bool ContainsPoint(const UIRect& rect, float x, float y) {
return x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height;
}
@@ -345,13 +337,6 @@ bool ScenarioApp::Initialize(HINSTANCE hInstance, int nCmdShow) {
}
m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/tree_view_inline_rename/captures";
m_autoScreenshot.Initialize(m_captureRoot);
const auto themeLoad = XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
if (themeLoad.succeeded) {
m_theme = themeLoad.theme;
m_themeStatus = "loaded";
} else {
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
}
ResetScenario();
return true;
}
@@ -374,7 +359,7 @@ ScenarioLayout ScenarioApp::GetLayout() const {
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));
return BuildScenarioLayout(width, height, XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
return BuildScenarioLayout(width, height, XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
}
void ScenarioApp::ResetScenario() {
@@ -718,9 +703,9 @@ UIEditorInlineRenameSessionRequest ScenarioApp::BuildRenameRequest(bool beginSes
}
UIEditorTextFieldMetrics ScenarioApp::ResolveHostedTextFieldMetrics() const {
const auto propertyMetrics = ResolveUIEditorPropertyGridMetrics(m_theme);
const auto textMetrics = ResolveUIEditorTextFieldMetrics(m_theme);
return BuildUIEditorHostedTextFieldMetrics(propertyMetrics, textMetrics);
const auto propertyMetrics = ResolveUIEditorPropertyGridMetrics();
const auto textMetrics = ResolveUIEditorTextFieldMetrics();
return BuildUIEditorPropertyGridTextFieldMetrics(propertyMetrics, textMetrics);
}
UIEditorTextFieldMetrics ScenarioApp::ResolveInlineRenameMetrics(const UIRect& bounds) const {
@@ -728,9 +713,9 @@ UIEditorTextFieldMetrics ScenarioApp::ResolveInlineRenameMetrics(const UIRect& b
}
UIEditorTextFieldPalette ScenarioApp::ResolveInlineRenamePalette() const {
const auto propertyPalette = ResolveUIEditorPropertyGridPalette(m_theme);
const auto textPalette = ResolveUIEditorTextFieldPalette(m_theme);
return BuildUIEditorHostedTextFieldPalette(propertyPalette, textPalette);
const auto propertyPalette = ResolveUIEditorPropertyGridPalette();
const auto textPalette = ResolveUIEditorTextFieldPalette();
return BuildUIEditorPropertyGridTextFieldPalette(propertyPalette, textPalette);
}
void ScenarioApp::ExecuteAction(ActionId action) {
@@ -755,9 +740,9 @@ void ScenarioApp::RenderFrame() {
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);
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
const auto shellPalette =
XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme);
XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
RefreshTreeFrame();
@@ -781,8 +766,8 @@ void ScenarioApp::RenderFrame() {
layout.introRect,
shellPalette,
shellMetrics,
"这个测试验证什么功能?",
"只验证 Editor TreeView 的 inline rename 契约默认进入编辑、字符编辑、Enter 提交、Esc 取消、点击外部提交。");
"这个测试验证什么功能?",
"只验证 Editor TreeView 的 inline rename默认进入编辑、字符编辑、Enter 提交、Esc 取消、点击外部提交。");
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
"1. 启动后默认进入 hero-mesh rename输入框只能覆盖 label 区。",
@@ -790,7 +775,7 @@ void ScenarioApp::RenderFrame() {
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
"2. 输入字符后Draft 必须实时变化原标签不能和 overlay 叠字。",
"2. 输入字符后Draft 必须实时变化原标签不能和 overlay 叠字。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
@@ -805,7 +790,7 @@ void ScenarioApp::RenderFrame() {
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
"5. rename 中点击输入框外部:提交当前草稿并退出编辑态。",
"5. rename 中点击输入框外部:提交当前草稿并退出编辑态。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
@@ -903,10 +888,9 @@ void ScenarioApp::RenderFrame() {
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 334.0f),
"Theme: " + m_themeStatus,
"Style: fixed",
shellPalette.textWeak,
shellMetrics.bodyFontSize);
DrawCard(
drawList,
layout.previewRect,

View File

@@ -810,7 +810,7 @@ private:
DrawCard(
drawList,
layout.introRect,
"这个测试验证什么功能?",
"这个测试验证什么功能?",
"只验证 Editor TreeView 的多选契约,不混入 Hierarchy / Inspector 业务面板。");
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
@@ -819,12 +819,12 @@ private:
12.0f);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
"2. Ctrl+单击 / Shift+单击:只验证 visible tree 范围内的多选切换与连续扩选。",
"2. Ctrl+单击 / Shift+单击:只验证 visible tree 范围内的多选切换与连续扩选。",
kTextPrimary,
12.0f);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
"3. 点击 disclosure只切换 expanded不应无故打散 selection。",
"3. 点击 disclosure只切换 expanded不应无故打散 selection。",
kTextPrimary,
12.0f);
drawList.AddText(
@@ -853,7 +853,7 @@ private:
DrawButton(drawList, button, m_hasHoveredAction && m_hoveredAction == button.action);
}
DrawCard(drawList, layout.stateRect, "状态摘要", "重点检查 multi-select 与 expanded / visible contract 是否稳定。");
DrawCard(drawList, layout.stateRect, "状态摘要", "重点检查 multi-select 与 expanded / visible 契约是否稳定。");
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f),
"Hit: " + DescribeHitTarget(currentHit, m_items),

View File

@@ -1,10 +1,10 @@
#ifndef NOMINMAX
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorTheme.h>
#include <XCEditor/Core/UIEditorVector2FieldInteraction.h>
#include <XCEditor/Widgets/UIEditorVector2Field.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
#include <XCEditor/Fields/UIEditorVector2FieldInteraction.h>
#include <XCEditor/Fields/UIEditorVector2Field.h>
#include "EditorValidationTheme.h"
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -48,8 +48,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorVector2FieldHitTarget;
using XCEngine::UI::Editor::Widgets::UIEditorVector2FieldHitTargetKind;
using XCEngine::UI::Editor::Widgets::UIEditorVector2FieldInvalidComponentIndex;
using XCEngine::UI::Editor::Widgets::UIEditorVector2FieldSpec;
namespace Style = XCEngine::UI::Style;
constexpr const wchar_t* kWindowClassName = L"XCUIEditorVector2FieldBasicValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Vector2Field Basic";
@@ -85,11 +83,6 @@ std::filesystem::path ResolveRepoRootPath() {
return std::filesystem::path(root).lexically_normal();
}
std::filesystem::path ResolveValidationThemePath() {
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
.lexically_normal();
}
bool ContainsPoint(const UIRect& rect, float x, float y) {
return x >= rect.x &&
x <= rect.x + rect.width &&
@@ -437,14 +430,6 @@ private:
m_captureRoot =
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/vector2_field_basic/captures";
m_autoScreenshot.Initialize(m_captureRoot);
const auto themeLoad =
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
if (themeLoad.succeeded) {
m_theme = themeLoad.theme;
m_themeStatus = "loaded";
} else {
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
}
ResetScenario();
return true;
@@ -473,7 +458,7 @@ private:
return BuildScenarioLayout(
width,
height,
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
}
void ResetScenario() {
@@ -503,7 +488,7 @@ private:
}
const ScenarioLayout layout = GetLayout();
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics(m_theme);
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics();
m_frame = UpdateUIEditorVector2FieldInteraction(
m_interactionState,
m_spec,
@@ -623,7 +608,7 @@ private:
UIEditorVector2FieldInteractionResult PumpEvents(std::vector<UIInputEvent> events) {
const ScenarioLayout layout = GetLayout();
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics(m_theme);
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics();
m_frame = UpdateUIEditorVector2FieldInteraction(
m_interactionState,
m_spec,
@@ -635,12 +620,12 @@ private:
void UpdateResultText(const UIEditorVector2FieldInteractionResult& result) {
if (result.editCommitRejected) {
m_lastResult = "提交失败,当前文本不是合法数字";
m_lastResult = "提交被拒绝:当前输入不是合法数字";
return;
}
if (result.editCommitted) {
m_lastResult =
std::string("已提交 ") +
std::string("已提交编辑: ") +
DescribeSelectedComponent(result.changedComponentIndex) +
" = " + result.committedText;
return;
@@ -657,7 +642,7 @@ private:
}
if (result.stepApplied || result.valueChanged) {
m_lastResult =
std::string("值已更新,当前 component = ") +
std::string("值已更新,当前 component = ") +
DescribeSelectedComponent(result.changedComponentIndex);
return;
}
@@ -669,15 +654,15 @@ private:
}
if (result.focusChanged) {
m_lastResult =
std::string("焦点变化: ") +
std::string("焦点状态: ") +
(m_interactionState.vector2FieldState.focused ? "focused" : "lost");
return;
}
if (result.consumed) {
m_lastResult = "控件已消费输入";
m_lastResult = "输入已处理,但没有额外状态变化";
return;
}
m_lastResult = "等待交互";
m_lastResult = "无变化";
}
void ExecuteAction(ActionId action) {
@@ -702,16 +687,16 @@ private:
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 shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
RefreshFrame();
const UIEditorVector2FieldHitTarget currentHit =
HitTestUIEditorVector2Field(m_frame.layout, m_mousePosition);
const auto vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics(m_theme);
const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector2FieldPalette(m_theme);
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme);
const auto vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics();
const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector2FieldPalette();
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette();
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("EditorVector2FieldBasic");
@@ -722,36 +707,36 @@ private:
layout.introRect,
shellPalette,
shellMetrics,
"这个测试验证什么功能",
"验证 UIEditorVector2Field 的双通道数值编辑契约,不涉及 PropertyGrid 或业务 Inspector。");
"这个测试验证什么功能",
"验证 UIEditorVector2Field 的选择切换、键盘步进、编辑提交/取消,以及固定 Inspector 风格承载");
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
"1. 点击 X / Y 对应的 value box,检查 selected component 是否切换,并且应进入编辑态",
"1. 点击 X / Y value box 可切换 selected component,并检查 hover 与高亮",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
"2. 获得 focus 后按 Tab,检查 selected component 在 X / Y 之间切换",
"2. 获得 focus 后按 Tab 在 X / Y 之间切换 selected component。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
"3. 非编辑态按 Up / Down / Home / End,检查当前 component step / 边界行为",
"3. 不进入编辑时,Up / Down / Home / End 会对当前 component step 或边界跳转",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f),
"4. 按 Enter 开始编辑直接输入字符也应开始编辑Enter commitEscape cancel",
"4. 按 Enter 开始编辑,输入后再按 Enter 提交,按 Escape 取消",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
"5. 编辑态按 F6 模拟 FocusLost应提交暂存文本并退出编辑态",
"5. 按 F6 模拟 FocusLost检查未提交编辑是否按约定结束",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 182.0f),
"6. 检查 Hover / Selected / Editing / Values / Result 是否同步更新",
"6. 观察 Hover / Selected / Editing / Values / Result 是否同步。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
@@ -826,19 +811,13 @@ private:
captureSummary,
shellPalette.textWeak,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 262.0f),
"Theme: " + m_themeStatus,
shellPalette.textWeak,
shellMetrics.bodyFontSize);
DrawCard(
drawList,
layout.previewRect,
shellPalette,
shellMetrics,
"Vector2Field 预览",
"这里只放一个 Unity 风格的双通道 Vector2 字段");
"这里只放一个固定样式的 Vector2 输入项");
drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor);
drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f);
drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground);
@@ -880,12 +859,10 @@ private:
UIEditorVector2FieldSpec m_spec = {};
UIEditorVector2FieldInteractionState m_interactionState = {};
UIEditorVector2FieldInteractionFrame m_frame = {};
Style::UITheme m_theme = {};
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
ActionId m_hoveredAction = ActionId::Reset;
bool m_hasHoveredAction = false;
std::string m_lastResult = "等待交互";
std::string m_themeStatus = "fallback";
std::string m_lastResult = "无变化";
};
} // namespace

View File

@@ -1,10 +1,10 @@
#ifndef NOMINMAX
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorTheme.h>
#include <XCEditor/Core/UIEditorVector3FieldInteraction.h>
#include <XCEditor/Widgets/UIEditorVector3Field.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
#include <XCEditor/Fields/UIEditorVector3FieldInteraction.h>
#include <XCEditor/Fields/UIEditorVector3Field.h>
#include "EditorValidationTheme.h"
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -48,8 +48,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorVector3FieldHitTarget;
using XCEngine::UI::Editor::Widgets::UIEditorVector3FieldHitTargetKind;
using XCEngine::UI::Editor::Widgets::UIEditorVector3FieldInvalidComponentIndex;
using XCEngine::UI::Editor::Widgets::UIEditorVector3FieldSpec;
namespace Style = XCEngine::UI::Style;
constexpr const wchar_t* kWindowClassName = L"XCUIEditorVector3FieldBasicValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Vector3Field Basic";
@@ -85,11 +83,6 @@ std::filesystem::path ResolveRepoRootPath() {
return std::filesystem::path(root).lexically_normal();
}
std::filesystem::path ResolveValidationThemePath() {
return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme")
.lexically_normal();
}
bool ContainsPoint(const UIRect& rect, float x, float y) {
return x >= rect.x &&
x <= rect.x + rect.width &&
@@ -443,14 +436,6 @@ private:
m_captureRoot =
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/vector3_field_basic/captures";
m_autoScreenshot.Initialize(m_captureRoot);
const auto themeLoad =
XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath());
if (themeLoad.succeeded) {
m_theme = themeLoad.theme;
m_themeStatus = "loaded";
} else {
m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error;
}
ResetScenario();
return true;
@@ -479,7 +464,7 @@ private:
return BuildScenarioLayout(
width,
height,
XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme));
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
}
void ResetScenario() {
@@ -509,7 +494,7 @@ private:
}
const ScenarioLayout layout = GetLayout();
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics(m_theme);
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics();
m_frame = UpdateUIEditorVector3FieldInteraction(
m_interactionState,
m_spec,
@@ -629,7 +614,7 @@ private:
UIEditorVector3FieldInteractionResult PumpEvents(std::vector<UIInputEvent> events) {
const ScenarioLayout layout = GetLayout();
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics(m_theme);
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics();
m_frame = UpdateUIEditorVector3FieldInteraction(
m_interactionState,
m_spec,
@@ -641,12 +626,12 @@ private:
void UpdateResultText(const UIEditorVector3FieldInteractionResult& result) {
if (result.editCommitRejected) {
m_lastResult = "提交失败,当前文本不是合法数字";
m_lastResult = "提交被拒绝:当前输入不是合法数字";
return;
}
if (result.editCommitted) {
m_lastResult =
std::string("已提交 ") +
std::string("已提交编辑: ") +
DescribeSelectedComponent(result.changedComponentIndex) +
" = " + result.committedText;
return;
@@ -663,7 +648,7 @@ private:
}
if (result.stepApplied || result.valueChanged) {
m_lastResult =
std::string("值已更新,当前 component = ") +
std::string("值已更新,当前 component = ") +
DescribeSelectedComponent(result.changedComponentIndex);
return;
}
@@ -675,15 +660,15 @@ private:
}
if (result.focusChanged) {
m_lastResult =
std::string("焦点变化: ") +
std::string("焦点状态: ") +
(m_interactionState.vector3FieldState.focused ? "focused" : "lost");
return;
}
if (result.consumed) {
m_lastResult = "控件已消费输入";
m_lastResult = "输入已处理,但没有额外状态变化";
return;
}
m_lastResult = "等待交互";
m_lastResult = "无变化";
}
void ExecuteAction(ActionId action) {
@@ -708,16 +693,16 @@ private:
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 shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics);
RefreshFrame();
const UIEditorVector3FieldHitTarget currentHit =
HitTestUIEditorVector3Field(m_frame.layout, m_mousePosition);
const auto vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics(m_theme);
const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector3FieldPalette(m_theme);
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme);
const auto vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics();
const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector3FieldPalette();
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette();
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("EditorVector3FieldBasic");
@@ -728,36 +713,36 @@ private:
layout.introRect,
shellPalette,
shellMetrics,
"这个测试验证什么功能",
"验证 UIEditorVector3Field 的三通道数值编辑契约,不涉及 PropertyGrid 或业务 Inspector。");
"这个测试验证什么功能",
"验证 UIEditorVector3Field 的选择切换、键盘步进、编辑提交/取消,以及固定 Inspector 风格承载");
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f),
"1. 点击 X / Y / Z 对应的 value box,检查 selected component 是否切换,并应进入编辑态",
"1. 点击 X / Y / Z value box 可切换 selected component,并检查 hover 与高亮",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f),
"2. 获得 focus 后按 Tab,检查 selected component 在 X / Y / Z 之间切换",
"2. 获得 focus 后按 Tab 在 X / Y / Z 之间切换 selected component。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f),
"3. 非编辑态按 Up / Down / Home / End,检查当前 component step / 边界行为",
"3. 不进入编辑时,Up / Down / Home / End 会对当前 component step 或边界跳转",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f),
"4. 按 Enter 开始编辑直接输入字符也应开始编辑Enter commitEscape cancel",
"4. 按 Enter 开始编辑,输入后再按 Enter 提交,按 Escape 取消",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
"5. 编辑态按 F6 模拟 FocusLost应提交暂存文本并退出编辑态",
"5. 按 F6 模拟 FocusLost检查未提交编辑是否按约定结束",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 182.0f),
"6. 检查 Hover / Selected / Editing / Values / Result 是否同步更新",
"6. 观察 Hover / Selected / Editing / Values / Result 是否同步。",
shellPalette.textPrimary,
shellMetrics.bodyFontSize);
drawList.AddText(
@@ -834,19 +819,13 @@ private:
captureSummary,
shellPalette.textWeak,
shellMetrics.bodyFontSize);
drawList.AddText(
UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 262.0f),
"Theme: " + m_themeStatus,
shellPalette.textWeak,
shellMetrics.bodyFontSize);
DrawCard(
drawList,
layout.previewRect,
shellPalette,
shellMetrics,
"Vector3Field 预览",
"这里只放一个 Unity 风格的三通道 Vector3 字段");
"这里只放一个固定样式的 Vector3 输入项");
drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor);
drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f);
drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground);
@@ -888,12 +867,10 @@ private:
UIEditorVector3FieldSpec m_spec = {};
UIEditorVector3FieldInteractionState m_interactionState = {};
UIEditorVector3FieldInteractionFrame m_frame = {};
Style::UITheme m_theme = {};
UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f);
ActionId m_hoveredAction = ActionId::Reset;
bool m_hasHoveredAction = false;
std::string m_lastResult = "等待交互";
std::string m_themeStatus = "fallback";
std::string m_lastResult = "无变化";
};
} // namespace

View File

@@ -1,10 +1,10 @@
#ifndef NOMINMAX
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorTheme.h>
#include <XCEditor/Core/UIEditorVector4FieldInteraction.h>
#include <XCEditor/Widgets/UIEditorVector4Field.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
#include <XCEditor/Fields/UIEditorVector4FieldInteraction.h>
#include <XCEditor/Fields/UIEditorVector4Field.h>
#include "EditorValidationTheme.h"
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -46,8 +46,6 @@ 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";
@@ -69,11 +67,6 @@ std::filesystem::path ResolveRepoRootPath() {
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);
@@ -304,9 +297,6 @@ private:
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;
}
@@ -344,8 +334,8 @@ private:
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);
XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics());
const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector4FieldMetrics();
m_frame = UpdateUIEditorVector4FieldInteraction(
m_interactionState,
m_spec,
@@ -357,21 +347,21 @@ private:
void UpdateResultText(const UIEditorVector4FieldInteractionResult& result) {
if (result.editCommitRejected) {
m_lastResult = "提交失败,当前文本不是合法数字";
m_lastResult = "提交被拒绝:当前输入不是合法数字";
} else if (result.editCommitted) {
m_lastResult = "已提交 " + DescribeSelectedComponent(result.changedComponentIndex) + " = " + result.committedText;
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);
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");
m_lastResult = std::string("焦点状态: ") + (m_interactionState.vector4FieldState.focused ? "focused" : "lost");
} else if (result.consumed) {
m_lastResult = "控件已消费输入";
m_lastResult = "输入已处理,但没有额外状态变化";
}
}
@@ -380,14 +370,14 @@ private:
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 shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
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 vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector4FieldMetrics();
const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector4FieldPalette();
const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette();
const auto currentHit = HitTestUIEditorVector4Field(m_frame.layout, m_mousePosition);
UIDrawData drawData = {};
@@ -396,12 +386,12 @@ private:
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 切换 componentUp/Down/Home/End 检查 step 与边界。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), "3. Enter 开始编辑直接输入字符也应开始编辑Enter commitEscape cancel", shellPalette.textPrimary, shellMetrics.bodyFontSize);
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), "4. F6 模拟 FocusLostF12 截图R 重置当前测试。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
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 的四分量切换、键盘步进、编辑提交/取消,以及固定 Inspector 风格展示", 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。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), "2. 按 Tab / Shift+Tab 切换 componentUp / Down / Home / End 检查 step 与边界。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), "3. Enter 开始编辑直接输入字符也应开始编辑Enter 提交Escape 取消", 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);
@@ -414,13 +404,12 @@ private:
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.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 214.0f), "截图: " + (m_autoScreenshot.GetLastCaptureSummary().empty() ? std::string("F12 -> captures/") : m_autoScreenshot.GetLastCaptureSummary()), 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.AddText(UIPoint(layout.previewRect.x + 16.0f, layout.previewRect.y + 40.0f), "这里只放一个固定样式的 Vector4 输入项", shellPalette.textMuted, shellMetrics.bodyFontSize);
drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor);
drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f);
drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground);
@@ -443,10 +432,8 @@ private:
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";
std::string m_lastResult = "无变化";
};
} // namespace

View File

@@ -2,8 +2,8 @@
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorViewportShell.h>
#include <XCEditor/Widgets/UIEditorViewportSlot.h>
#include <XCEditor/Shell/UIEditorViewportShell.h>
#include <XCEditor/Shell/UIEditorViewportSlot.h>
#include "Host/AutoScreenshot.h"
#include "Host/InputModifierTracker.h"
#include "Host/NativeRenderer.h"
@@ -506,7 +506,7 @@ private:
UIEditorViewportShellSpec BuildShellSpec() const {
UIEditorViewportShellSpec spec = {};
spec.chrome.title = "Scene View";
spec.chrome.subtitle = "ViewportShell 基础层";
spec.chrome.subtitle = "ViewportShell 基础";
spec.chrome.showTopBar = m_showTopBar;
spec.chrome.showBottomBar = m_showBottomBar;
spec.chrome.topBarHeight = 40.0f;
@@ -565,25 +565,25 @@ private:
m_buttons = {
{
ActionId::ToggleTopBar,
std::string("TopBar: ") + (m_showTopBar ? "" : ""),
std::string("TopBar: ") + (m_showTopBar ? "" : ""),
UIRect(left, top, widthAvailable, buttonHeight),
m_showTopBar
},
{
ActionId::ToggleBottomBar,
std::string("BottomBar: ") + (m_showBottomBar ? "" : ""),
std::string("BottomBar: ") + (m_showBottomBar ? "" : ""),
UIRect(left, top + (buttonHeight + gap), widthAvailable, buttonHeight),
m_showBottomBar
},
{
ActionId::ToggleTexture,
std::string("Texture: ") + (m_textureEnabled ? "" : ""),
std::string("Texture: ") + (m_textureEnabled ? "" : ""),
UIRect(left, top + (buttonHeight + gap) * 2.0f, widthAvailable, buttonHeight),
m_textureEnabled
},
{
ActionId::Reset,
"Reset",
"重置",
UIRect(left, top + (buttonHeight + gap) * 3.0f, widthAvailable, buttonHeight),
false
},
@@ -687,41 +687,41 @@ private:
DrawCard(
drawList,
m_introRect,
"测试功能ViewportShell 基础 contract",
"只验证 Resolve + Update不接 Scene/Game 业务面板");
"这个测试验证什么功能?",
"只验证 Resolve + Update 的 ViewportShell contract,不接 Scene/Game 业务。");
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f),
"重点检查:切换 TopBar / BottomBar 后,Request Size Input Rect 要同步变化",
"1. 验证 TopBar / BottomBar Request Size Input Rect 的影响",
kTextMuted,
11.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 88.0f),
"重点检查:Hover / Focus / Capture 要和右侧 surface 边框状态一致",
"2. 验证 Hover / Focus / Capture 是否与 surface 输入桥同步",
kTextMuted,
11.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 110.0f),
"重点检查:Texture Fallback 只影响 frame 分支,不应破坏 shell 输入边界",
"3. 验证 Texture / Fallback 分支切换后frame 输出仍由 shell 承接",
kTextMuted,
11.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 132.0f),
"操作:hover Surfaceclick 获取 Focus按住拖出再松开检查 Capture。",
"4. 验证 hover Surfaceclick Surface 后的 Focus / Capture 变化",
kTextMuted,
11.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 154.0f),
"操作:点击左侧 TopBar / BottomBar / Texture,观察左侧状态与右侧布局",
"5. 验证 TopBar / BottomBar / Texture 三个开关切换后的整体表现",
kTextMuted,
11.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 176.0f),
"操作:需要保留当前画面时点“截图”,或直接按 F12",
"6. 验证截图链路,支持按钮触发和 F12 手动截图",
kTextMuted,
11.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 204.0f),
"结果判定:左侧 Request / Input / HoverHit / Hover / Focus / Capture 必须一致",
"7. 左侧持续展示 Request / Input / HoverHit / Hover / Focus / Capture 摘要",
kTextWeak,
11.0f);
@@ -745,18 +745,18 @@ private:
addStateLine("Hover Hit: " + DescribeHitTarget(m_hoverHit), kTextPrimary);
addStateLine("Hover: " + BoolText(m_shellFrame.inputFrame.hovered), kTextPrimary);
addStateLine("Focus: " + BoolText(m_shellFrame.inputFrame.focused), kTextPrimary);
addStateLine("Capture: " + BoolText(m_shellFrame.inputFrame.captured), kTextPrimary);
addStateLine("捕获: " + BoolText(m_shellFrame.inputFrame.captured), kTextPrimary);
addStateLine("Result: " + m_lastResult, kTextMuted);
const std::string captureSummary =
m_autoScreenshot.HasPendingCapture()
? "截图排队中..."
: (m_autoScreenshot.GetLastCaptureSummary().empty()
? std::string("截图F12 或按钮 -> viewport_shell_basic/captures/")
? std::string("截图: F12 或按钮 -> viewport_shell_basic/captures/")
: m_autoScreenshot.GetLastCaptureSummary());
addStateLine(captureSummary, kTextWeak, 11.0f);
DrawCard(drawList, m_previewRect, "Preview", "这里只有一个 ViewportShell用来检 Editor 基础层 compose。");
DrawCard(drawList, m_previewRect, "Preview", "这里只有一个 ViewportShell用来检 Editor 基础层 compose。");
drawList.AddFilledRect(
UIRect(
m_previewRect.x + 12.0f,

View File

@@ -2,7 +2,7 @@
#define NOMINMAX
#endif
#include <XCEditor/Widgets/UIEditorViewportSlot.h>
#include <XCEditor/Shell/UIEditorViewportSlot.h>
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -322,7 +322,7 @@ private:
UIEditorViewportSlotChrome BuildChrome() const {
UIEditorViewportSlotChrome chrome = {};
chrome.title = "Scene View";
chrome.subtitle = "ViewportSlot 基础壳层";
chrome.subtitle = "ViewportSlot 基础布局验证";
chrome.showTopBar = m_showTopBar;
chrome.showBottomBar = m_showBottomBar;
chrome.topBarHeight = 40.0f;
@@ -341,7 +341,7 @@ private:
frame.statusText = "Fake viewport frame";
} else {
frame.hasTexture = false;
frame.statusText = "当前无纹理,检查 fallback message 与 input 边界";
frame.statusText = "当前无纹理,检查 fallback message 与 input 区域";
}
return frame;
}
@@ -414,10 +414,10 @@ private:
const float width = m_controlsRect.width - 32.0f;
m_buttons = {
{ ActionId::ToggleTopBar, "TopBar", UIRect(left, top, width, buttonHeight), m_showTopBar },
{ ActionId::ToggleBottomBar, "状态条", UIRect(left, top + (buttonHeight + gap), width, buttonHeight), m_showBottomBar },
{ ActionId::ToggleBottomBar, "BottomBar", UIRect(left, top + (buttonHeight + gap), width, buttonHeight), m_showBottomBar },
{ ActionId::ToggleTexture, "Texture", UIRect(left, top + (buttonHeight + gap) * 2.0f, width, buttonHeight), m_textureEnabled },
{ ActionId::ToggleSquareAspect, "方形比例", UIRect(left, top + (buttonHeight + gap) * 3.0f, width, buttonHeight), m_squareAspect },
{ ActionId::Reset, "Reset", UIRect(left, top + (buttonHeight + gap) * 4.0f, width, buttonHeight), false },
{ ActionId::Reset, "重置", UIRect(left, top + (buttonHeight + gap) * 4.0f, width, buttonHeight), false },
{ ActionId::Capture, "截图", UIRect(left, top + (buttonHeight + gap) * 5.0f, width, buttonHeight), false }
};
}
@@ -480,7 +480,7 @@ private:
m_slotState.focused = true;
m_slotState.surfaceActive = true;
m_slotState.inputCaptured = true;
m_lastResult = "Surface 按下focus + active + capture";
m_lastResult = "Surface 按下,进入 focus + active + capture";
}
InvalidateScenario();
}
@@ -493,7 +493,7 @@ private:
break;
case ActionId::ToggleBottomBar:
m_showBottomBar = !m_showBottomBar;
m_lastResult = m_showBottomBar ? "状态条已打开" : "状态条已关闭";
m_lastResult = m_showBottomBar ? "BottomBar 已打开" : "BottomBar 已关闭";
break;
case ActionId::ToggleTexture:
m_textureEnabled = !m_textureEnabled;
@@ -537,11 +537,11 @@ private:
switch (hit.kind) {
case UIEditorViewportSlotHitTargetKind::ToolItem:
m_slotState.activeToolIndex = hit.index;
m_lastResult = "ToolItem 命中" + std::to_string(hit.index);
m_lastResult = "ToolItem 命中: " + std::to_string(hit.index);
break;
case UIEditorViewportSlotHitTargetKind::StatusSegment:
m_slotState.statusBarState.activeIndex = hit.index;
m_lastResult = "StatusSegment 命中" + std::to_string(hit.index);
m_lastResult = "StatusSegment 命中: " + std::to_string(hit.index);
break;
case UIEditorViewportSlotHitTargetKind::Surface:
m_slotState.focused = true;
@@ -584,40 +584,40 @@ private:
DrawCard(
drawList,
m_introRect,
"测试功能ViewportSlot 基础壳层",
"只验证 Editor 基础层。");
"这个测试验证什么功能?",
"只验证 Editor ViewportSlot 的基础层。");
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f),
"检查1TopBar / Surface / 状态条布局",
"1. 验证 TopBar / Surface / BottomBar 的区域划分",
kTextMuted,
12.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 88.0f),
"检查2hover / focus / active / capture。",
"2. 验证 hover / focus / active / capture 状态",
kTextMuted,
12.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 110.0f),
"操作1hover toolbar / surface / status bar。",
"3. 验证 hover toolbar / surface / status bar。",
kTextMuted,
12.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 132.0f),
"操作2click surface;按住左键观察 capture。",
"4. 验证点击 surface 后进入 capture。",
kTextMuted,
12.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 154.0f),
"操作3切换 TopBar / 状态条 / Texture / 比例F12 或“截图”",
"5. 验证切换 TopBar / BottomBar / Texture / 方形比例四个开关",
kTextMuted,
12.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 180.0f),
"结果chrome 开关后 surface 扩张;Texture/Fallback 正确",
"6. Chrome 固定,surface Texture / Fallback 之间切换",
kTextWeak,
12.0f);
DrawCard(drawList, m_controlsRect, "开关", "只保留和 ViewportSlot contract 直接相关的操作。");
DrawCard(drawList, m_controlsRect, "操作", "只保留和 ViewportSlot contract 直接相关的操作。");
for (const ButtonState& button : m_buttons) {
DrawButton(drawList, button);
}
@@ -640,12 +640,12 @@ private:
13.0f);
drawList.AddText(
UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 144.0f),
"Capture: " + BoolText(m_slotState.inputCaptured),
"捕获: " + BoolText(m_slotState.inputCaptured),
kTextPrimary,
13.0f);
drawList.AddText(
UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 170.0f),
"Chrome: TopBar " + BoolText(m_showTopBar) + " | 状态条 " + BoolText(m_showBottomBar),
"Chrome: TopBar " + BoolText(m_showTopBar) + " | BottomBar " + BoolText(m_showBottomBar),
kTextPrimary,
13.0f);
drawList.AddText(
@@ -662,7 +662,7 @@ private:
m_autoScreenshot.HasPendingCapture()
? "截图排队中..."
: (m_autoScreenshot.GetLastCaptureSummary().empty()
? std::string("截图F12 或按钮 -> viewport_slot_basic/captures/")
? std::string("截图: F12 或按钮 -> viewport_slot_basic/captures/")
: m_autoScreenshot.GetLastCaptureSummary());
drawList.AddText(
UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 248.0f),

View File

@@ -2,7 +2,9 @@
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorWorkspaceInteraction.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
#include <XCEditor/Shell/UIEditorWorkspaceInteraction.h>
#include "../../shared/src/EditorValidationTheme.h"
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -12,6 +14,8 @@
#include <windowsx.h>
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <filesystem>
#include <sstream>
#include <string>
@@ -41,14 +45,20 @@ using XCEngine::UI::Editor::CollectUIEditorWorkspaceVisiblePanels;
using XCEngine::UI::Editor::FindUIEditorWorkspaceViewportPresentationFrame;
using XCEngine::UI::Editor::Host::AutoScreenshotController;
using XCEngine::UI::Editor::Host::NativeRenderer;
using XCEngine::UI::Editor::ResolveUIEditorDockHostMetrics;
using XCEngine::UI::Editor::ResolveUIEditorDockHostPalette;
using XCEngine::UI::Editor::ResolveUIEditorViewportSlotMetrics;
using XCEngine::UI::Editor::ResolveUIEditorViewportSlotPalette;
using XCEngine::UI::Editor::UIEditorPanelPresentationKind;
using XCEngine::UI::Editor::UIEditorPanelRegistry;
using XCEngine::UI::Editor::UIEditorViewportInputBridgeFrame;
using XCEngine::UI::Editor::UIEditorWorkspaceCommandStatus;
using XCEngine::UI::Editor::UIEditorWorkspaceController;
using XCEngine::UI::Editor::UIEditorWorkspaceInteractionFrame;
using XCEngine::UI::Editor::UIEditorWorkspaceInteractionModel;
using XCEngine::UI::Editor::UIEditorWorkspaceInteractionResult;
using XCEngine::UI::Editor::UIEditorWorkspaceInteractionState;
using XCEngine::UI::Editor::UIEditorWorkspaceLayoutOperationStatus;
using XCEngine::UI::Editor::UIEditorWorkspaceModel;
using XCEngine::UI::Editor::UIEditorWorkspacePanelPresentationModel;
using XCEngine::UI::Editor::UIEditorWorkspaceSplitAxis;
@@ -56,18 +66,12 @@ using XCEngine::UI::Editor::UpdateUIEditorWorkspaceInteraction;
using XCEngine::UI::Editor::Widgets::UIEditorDockHostHitTarget;
using XCEngine::UI::Editor::Widgets::UIEditorDockHostHitTargetKind;
constexpr const wchar_t* kWindowClassName = L"XCUIEditorWorkspaceInteractionBasicValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Workspace Interaction";
const auto kShellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette();
const auto kShellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics();
constexpr const wchar_t* kWindowClassName = L"XCUIEditorWorkspaceInteractionBasicValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Workspace Interaction Basic";
constexpr UIColor kWindowBg(0.11f, 0.11f, 0.11f, 1.0f);
constexpr UIColor kCardBg(0.17f, 0.17f, 0.17f, 1.0f);
constexpr UIColor kCardBorder(0.28f, 0.28f, 0.28f, 1.0f);
constexpr UIColor kTextPrimary(0.94f, 0.94f, 0.94f, 1.0f);
constexpr UIColor kTextMuted(0.72f, 0.72f, 0.72f, 1.0f);
constexpr UIColor kTextWeak(0.56f, 0.56f, 0.56f, 1.0f);
constexpr UIColor kButtonBg(0.24f, 0.24f, 0.24f, 1.0f);
constexpr UIColor kButtonHover(0.32f, 0.32f, 0.32f, 1.0f);
constexpr UIColor kButtonBorder(0.47f, 0.47f, 0.47f, 1.0f);
constexpr UIColor kSuccess(0.48f, 0.72f, 0.52f, 1.0f);
constexpr UIColor kWarning(0.82f, 0.67f, 0.35f, 1.0f);
@@ -91,6 +95,23 @@ std::filesystem::path ResolveRepoRootPath() {
return std::filesystem::path(root).lexically_normal();
}
bool IsAutoCaptureOnStartupEnabled() {
const char* value = std::getenv("XCUI_AUTO_CAPTURE_ON_STARTUP");
if (value == nullptr || value[0] == '\0') {
return false;
}
std::string normalized = value;
for (char& character : normalized) {
character = static_cast<char>(std::tolower(static_cast<unsigned char>(character)));
}
return normalized != "0" &&
normalized != "false" &&
normalized != "off" &&
normalized != "no";
}
bool ContainsPoint(const UIRect& rect, float x, float y) {
return x >= rect.x &&
x <= rect.x + rect.width &&
@@ -112,14 +133,22 @@ std::string FormatFloat(float value, int precision = 2) {
std::string DescribeHitTarget(const UIEditorDockHostHitTarget& target) {
switch (target.kind) {
case UIEditorDockHostHitTargetKind::SplitterHandle: return "Splitter: " + target.nodeId;
case UIEditorDockHostHitTargetKind::TabStripBackground: return "TabStripBackground: " + target.nodeId;
case UIEditorDockHostHitTargetKind::Tab: return "Tab: " + target.panelId;
case UIEditorDockHostHitTargetKind::TabCloseButton: return "TabClose: " + target.panelId;
case UIEditorDockHostHitTargetKind::PanelHeader: return "PanelHeader: " + target.panelId;
case UIEditorDockHostHitTargetKind::PanelBody: return "PanelBody: " + target.panelId;
case UIEditorDockHostHitTargetKind::PanelFooter: return "PanelFooter: " + target.panelId;
case UIEditorDockHostHitTargetKind::PanelCloseButton: return "PanelClose: " + target.panelId;
case UIEditorDockHostHitTargetKind::SplitterHandle:
return "分割条: " + target.nodeId;
case UIEditorDockHostHitTargetKind::TabStripBackground:
return "标签栏背景: " + target.nodeId;
case UIEditorDockHostHitTargetKind::Tab:
return "标签: " + target.panelId;
case UIEditorDockHostHitTargetKind::TabCloseButton:
return "标签关闭: " + target.panelId;
case UIEditorDockHostHitTargetKind::PanelHeader:
return "面板标题: " + target.panelId;
case UIEditorDockHostHitTargetKind::PanelBody:
return "面板内容: " + target.panelId;
case UIEditorDockHostHitTargetKind::PanelFooter:
return "面板底栏: " + target.panelId;
case UIEditorDockHostHitTargetKind::PanelCloseButton:
return "面板关闭: " + target.panelId;
case UIEditorDockHostHitTargetKind::None:
default:
return "None";
@@ -146,52 +175,85 @@ std::string JoinVisiblePanelIds(const UIEditorWorkspaceController& controller) {
std::string DescribeViewportEvent(const UIEditorViewportInputBridgeFrame& frame) {
if (frame.captureStarted) {
return "Viewport CaptureStarted";
return "视口开始捕获";
}
if (frame.captureEnded) {
return "Viewport CaptureEnded";
return "视口结束捕获";
}
if (frame.focusGained) {
return "Viewport FocusGained";
return "视口获得焦点";
}
if (frame.focusLost) {
return "Viewport FocusLost";
return "视口失去焦点";
}
if (frame.pointerPressedInside) {
return "Viewport PointerDownInside";
return "视口内按下";
}
if (frame.pointerReleasedInside) {
return "Viewport PointerUpInside";
return "视口内抬起";
}
if (frame.pointerMoved) {
return "Viewport PointerMove";
return "视口指针移动";
}
if (frame.wheelDelta != 0.0f) {
return "Viewport Wheel " + FormatFloat(frame.wheelDelta);
return "视口滚轮 " + FormatFloat(frame.wheelDelta);
}
return "Viewport Input";
return "视口输入";
}
void DrawCard(
UIDrawList& drawList,
const UIRect& rect,
std::string_view title,
std::string_view subtitle = {}) {
drawList.AddFilledRect(rect, kCardBg, 10.0f);
drawList.AddRectOutline(rect, kCardBorder, 1.0f, 10.0f);
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 14.0f), std::string(title), kTextPrimary, 17.0f);
if (!subtitle.empty()) {
drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 38.0f), std::string(subtitle), kTextMuted, 12.0f);
std::string DescribeValidationHitTarget(const UIEditorDockHostHitTarget& target) {
switch (target.kind) {
case UIEditorDockHostHitTargetKind::SplitterHandle:
return "Splitter: " + target.nodeId;
case UIEditorDockHostHitTargetKind::TabStripBackground:
return "Tab strip background: " + target.nodeId;
case UIEditorDockHostHitTargetKind::Tab:
return "Tab: " + target.panelId;
case UIEditorDockHostHitTargetKind::TabCloseButton:
return "Tab close: " + target.panelId;
case UIEditorDockHostHitTargetKind::PanelHeader:
return "Panel header: " + target.panelId;
case UIEditorDockHostHitTargetKind::PanelBody:
return "Panel body: " + target.panelId;
case UIEditorDockHostHitTargetKind::PanelFooter:
return "Panel footer: " + target.panelId;
case UIEditorDockHostHitTargetKind::PanelCloseButton:
return "Panel close: " + target.panelId;
case UIEditorDockHostHitTargetKind::None:
default:
return "None";
}
}
void DrawButton(UIDrawList& drawList, const ButtonState& button) {
drawList.AddFilledRect(button.rect, button.hovered ? kButtonHover : kButtonBg, 8.0f);
drawList.AddRectOutline(button.rect, kButtonBorder, 1.0f, 8.0f);
drawList.AddText(UIPoint(button.rect.x + 14.0f, button.rect.y + 10.0f), button.label, kTextPrimary, 12.0f);
std::string DescribeValidationViewportEvent(const UIEditorViewportInputBridgeFrame& frame) {
if (frame.captureStarted) {
return "Viewport capture started";
}
if (frame.captureEnded) {
return "Viewport capture ended";
}
if (frame.focusGained) {
return "Viewport focus gained";
}
if (frame.focusLost) {
return "Viewport focus lost";
}
if (frame.pointerPressedInside) {
return "Viewport pointer down";
}
if (frame.pointerReleasedInside) {
return "Viewport pointer up";
}
if (frame.pointerMoved) {
return "Viewport pointer move";
}
if (frame.wheelDelta != 0.0f) {
return "Viewport wheel " + FormatFloat(frame.wheelDelta);
}
return "Viewport input";
}
UIEditorPanelRegistry BuildPanelRegistry() {
UIEditorPanelRegistry BuildValidationPanelRegistry() {
UIEditorPanelRegistry registry = {};
registry.panels = {
{ "viewport", "Viewport", UIEditorPanelPresentationKind::ViewportShell, false, true, true },
@@ -201,7 +263,7 @@ UIEditorPanelRegistry BuildPanelRegistry() {
return registry;
}
UIEditorWorkspaceModel BuildWorkspace() {
UIEditorWorkspaceModel BuildValidationWorkspace() {
UIEditorWorkspaceModel workspace = {};
workspace.root = BuildUIEditorWorkspaceSplit(
"root-split",
@@ -219,17 +281,99 @@ UIEditorWorkspaceModel BuildWorkspace() {
return workspace;
}
UIEditorWorkspaceInteractionModel BuildInteractionModel() {
UIEditorWorkspaceInteractionModel BuildValidationInteractionModel() {
UIEditorWorkspaceInteractionModel model = {};
UIEditorWorkspacePanelPresentationModel presentation = {};
presentation.panelId = "viewport";
presentation.kind = UIEditorPanelPresentationKind::ViewportShell;
presentation.viewportShellModel.spec.chrome.title = "Viewport";
presentation.viewportShellModel.spec.chrome.subtitle = "Workspace Interaction";
presentation.viewportShellModel.spec.chrome.subtitle = "Workspace interaction contract";
presentation.viewportShellModel.spec.chrome.showTopBar = true;
presentation.viewportShellModel.spec.chrome.showBottomBar = true;
presentation.viewportShellModel.frame.statusText =
"这里只验证 WorkspaceInteraction contract不接 Scene/Game 业务。";
"Check splitter drag, tab switch, panel activation, and viewport input.";
model.workspacePresentations = { presentation };
return model;
}
void DrawCard(
UIDrawList& drawList,
const UIRect& rect,
std::string_view title,
std::string_view subtitle = {}) {
drawList.AddFilledRect(rect, kShellPalette.cardBackground, kShellMetrics.cardRadius);
drawList.AddRectOutline(rect, kShellPalette.cardBorder, 1.0f, kShellMetrics.cardRadius);
drawList.AddText(
UIPoint(rect.x + 16.0f, rect.y + 14.0f),
std::string(title),
kShellPalette.textPrimary,
kShellMetrics.titleFontSize);
if (!subtitle.empty()) {
drawList.AddText(
UIPoint(rect.x + 16.0f, rect.y + 38.0f),
std::string(subtitle),
kShellPalette.textMuted,
kShellMetrics.bodyFontSize);
}
}
void DrawButton(UIDrawList& drawList, const ButtonState& button) {
drawList.AddFilledRect(
button.rect,
button.hovered ? kShellPalette.buttonHoverBackground : kShellPalette.buttonBackground,
kShellMetrics.buttonRadius);
drawList.AddRectOutline(
button.rect,
kShellPalette.cardBorder,
1.0f,
kShellMetrics.buttonRadius);
drawList.AddText(
UIPoint(button.rect.x + 14.0f, button.rect.y + 10.0f),
button.label,
kShellPalette.textPrimary,
kShellMetrics.bodyFontSize);
}
UIEditorPanelRegistry BuildPanelRegistry() {
UIEditorPanelRegistry registry = {};
registry.panels = {
{ "viewport", "视口", UIEditorPanelPresentationKind::ViewportShell, false, true, true },
{ "doc", "文档", UIEditorPanelPresentationKind::Placeholder, true, true, true },
{ "details", "详情", UIEditorPanelPresentationKind::Placeholder, true, true, true }
};
return registry;
}
UIEditorWorkspaceModel BuildWorkspace() {
UIEditorWorkspaceModel workspace = {};
workspace.root = BuildUIEditorWorkspaceSplit(
"root-split",
UIEditorWorkspaceSplitAxis::Horizontal,
0.7f,
BuildUIEditorWorkspaceTabStack(
"tab-stack",
{
BuildUIEditorWorkspacePanel("viewport-node", "viewport", "视口"),
BuildUIEditorWorkspacePanel("doc-node", "doc", "文档", true)
},
0u),
BuildUIEditorWorkspacePanel("details-node", "details", "详情", true));
workspace.activePanelId = "viewport";
return workspace;
}
UIEditorWorkspaceInteractionModel BuildInteractionModel() {
UIEditorWorkspaceInteractionModel model = {};
UIEditorWorkspacePanelPresentationModel presentation = {};
presentation.panelId = "viewport";
presentation.kind = UIEditorPanelPresentationKind::ViewportShell;
presentation.viewportShellModel.spec.chrome.title = "视口";
presentation.viewportShellModel.spec.chrome.subtitle =
"验证 Workspace 与 Viewport 的统一交互";
presentation.viewportShellModel.spec.chrome.showTopBar = true;
presentation.viewportShellModel.spec.chrome.showBottomBar = true;
presentation.viewportShellModel.frame.statusText =
"验证分割、标签切换、面板激活与视口输入桥接";
model.workspacePresentations = { presentation };
return model;
}
@@ -271,7 +415,9 @@ private:
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)));
app->m_renderer.Resize(
static_cast<UINT>(LOWORD(lParam)),
static_cast<UINT>(HIWORD(lParam)));
}
return 0;
case WM_PAINT:
@@ -373,7 +519,8 @@ private:
bool Initialize(HINSTANCE hInstance, int nCmdShow) {
m_captureRoot =
ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/workspace_interaction_basic/captures";
ResolveRepoRootPath() /
"tests/UI/Editor/integration/shell/workspace_interaction_basic/captures";
m_autoScreenshot.Initialize(m_captureRoot);
WNDCLASSEXW windowClass = {};
@@ -411,6 +558,11 @@ private:
}
ResetScenario();
if (IsAutoCaptureOnStartupEnabled()) {
m_lastStatus = "Capture";
m_lastMessage = "Startup capture queued. Verify the first frame before interacting.";
m_lastColor = kWarning;
}
return true;
}
@@ -432,13 +584,15 @@ private:
if (GetCapture() == m_hwnd) {
ReleaseCapture();
}
m_controller = BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace());
m_controller = BuildDefaultUIEditorWorkspaceController(
BuildValidationPanelRegistry(),
BuildValidationWorkspace());
m_interactionState = {};
m_interactionModel = BuildInteractionModel();
m_interactionModel = BuildValidationInteractionModel();
m_cachedFrame = {};
m_pendingInputEvents.clear();
m_lastStatus = "Ready";
m_lastMessage = "等待交互。这里只验证 WorkspaceInteraction 统一输入路由 contract。";
m_lastMessage = "Use the workspace on the right to exercise splitter drag, tab switch, and viewport input. Set XCUI_AUTO_CAPTURE_ON_STARTUP=1 to capture the startup frame.";
m_lastColor = kWarning;
}
@@ -468,8 +622,8 @@ private:
const float left = m_controlsRect.x + 16.0f;
const float top = m_controlsRect.y + 62.0f;
m_buttons = {
{ ActionId::Reset, "重置", UIRect(left, top, buttonWidth, 36.0f), false },
{ ActionId::Capture, "截图(F12)", UIRect(left + buttonWidth + 12.0f, top, buttonWidth, 36.0f), false }
{ ActionId::Reset, "Reset", UIRect(left, top, buttonWidth, 36.0f), false },
{ ActionId::Capture, "Capture (F12)", UIRect(left + buttonWidth + 12.0f, top, buttonWidth, 36.0f), false }
};
}
@@ -513,14 +667,14 @@ private:
if (action == ActionId::Reset) {
ResetScenario();
m_lastStatus = "Ready";
m_lastMessage = "场景状态已重置。请重新检查 viewport body / tab switch / splitter drag。";
m_lastMessage = "Scenario reset. Re-check splitter drag, tab switch, and viewport input.";
m_lastColor = kWarning;
return;
}
m_autoScreenshot.RequestCapture("manual_button");
m_lastStatus = "Ready";
m_lastMessage = "截图已排队,输出到 tests/UI/Editor/integration/shell/workspace_interaction_basic/captures/。";
m_lastStatus = "Capture";
m_lastMessage = "Capture queued. Check the Output/latest.png path shown below.";
m_lastColor = kWarning;
}
@@ -556,16 +710,16 @@ private:
void SetInteractionResult(const UIEditorWorkspaceInteractionResult& result) {
if (result.dockHostResult.layoutResult.status !=
XCEngine::UI::Editor::UIEditorWorkspaceLayoutOperationStatus::Rejected) {
m_lastStatus = "DockHostLayout";
UIEditorWorkspaceLayoutOperationStatus::Rejected) {
m_lastStatus = "Layout";
m_lastMessage = result.dockHostResult.layoutResult.message;
m_lastColor = kSuccess;
return;
}
if (result.dockHostResult.commandResult.status !=
XCEngine::UI::Editor::UIEditorWorkspaceCommandStatus::Rejected) {
m_lastStatus = "DockHostCommand";
UIEditorWorkspaceCommandStatus::Rejected) {
m_lastStatus = "Command";
m_lastMessage = result.dockHostResult.commandResult.message;
m_lastColor = kSuccess;
return;
@@ -573,7 +727,7 @@ private:
if (!result.viewportPanelId.empty()) {
m_lastStatus = result.viewportPanelId;
m_lastMessage = DescribeViewportEvent(result.viewportInputFrame);
m_lastMessage = DescribeValidationViewportEvent(result.viewportInputFrame);
m_lastColor =
result.viewportInputFrame.captureStarted || result.viewportInputFrame.focusGained
? kSuccess
@@ -583,33 +737,165 @@ private:
if (result.requestPointerCapture) {
m_lastStatus = "Capture";
m_lastMessage = "宿主已收到 WorkspaceInteraction pointer capture 请求。";
m_lastMessage = "Workspace interaction requested host pointer capture.";
m_lastColor = kSuccess;
return;
}
if (result.releasePointerCapture) {
m_lastStatus = "Release";
m_lastMessage = "宿主已执行 WorkspaceInteraction pointer release。";
m_lastMessage = "Workspace interaction released host pointer capture.";
m_lastColor = kWarning;
return;
}
if (result.consumed) {
m_lastStatus = "Consumed";
m_lastMessage = "这次输入被 WorkspaceInteraction 层消费。";
m_lastMessage = "The workspace consumed the input, but no higher-level state changed.";
m_lastColor = kWarning;
}
}
void RenderValidationUI(float width, float height) {
const auto* viewportFrame =
FindUIEditorWorkspaceViewportPresentationFrame(m_cachedFrame.composeFrame, "viewport");
const std::string selectedPresentation =
viewportFrame != nullptr ? "ViewportShell" : "Placeholder";
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("WorkspaceInteractionBasic");
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kShellPalette.windowBackground);
DrawCard(
drawList,
m_introRect,
"What This Validates",
"Verify that workspace interaction and viewport input share one stable contract.");
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 72.0f),
"1. Splitter drag should update the right-side layout ratio immediately.",
kShellPalette.textPrimary,
12.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 94.0f),
"2. Clicking a tab, panel, or viewport should surface the hit target and result on the left.",
kShellPalette.textPrimary,
12.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 116.0f),
"3. Viewport hover, focus, and capture changes should be reflected in the state card.",
kShellPalette.textPrimary,
12.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 138.0f),
"4. This scene validates editor interaction plumbing only, not business-specific panels.",
kShellPalette.textPrimary,
12.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 162.0f),
"Use XCUI_AUTO_CAPTURE_ON_STARTUP=1 for a startup shot; otherwise press F12 after exercising the scene.",
kShellPalette.textWeak,
11.0f);
DrawCard(drawList, m_controlsRect, "Actions", "Only reset and capture are exposed here.");
for (const ButtonState& button : m_buttons) {
DrawButton(drawList, button);
}
DrawCard(drawList, m_stateRect, "State", "Observe workspace interaction and viewport input in real time.");
float stateY = m_stateRect.y + 66.0f;
auto addStateLine = [&](std::string text,
const UIColor& color = kShellPalette.textPrimary,
float fontSize = 12.0f) {
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY), std::move(text), color, fontSize);
stateY += 20.0f;
};
addStateLine(
"Hit target: " +
DescribeValidationHitTarget(m_interactionState.dockHostInteractionState.dockHostState.hoveredTarget),
kShellPalette.textPrimary,
11.0f);
addStateLine("Presentation: " + selectedPresentation, kShellPalette.textPrimary);
addStateLine(
"Active panel: " +
(m_controller.GetWorkspace().activePanelId.empty()
? std::string("(none)")
: m_controller.GetWorkspace().activePanelId));
addStateLine("Selected tab: " + GetSelectedTabId(), kShellPalette.textPrimary);
addStateLine("Visible panels: " + JoinVisiblePanelIds(m_controller), kShellPalette.textWeak, 11.0f);
addStateLine("Result: " + m_lastStatus, m_lastColor);
drawList.AddText(
UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f),
m_lastMessage,
kShellPalette.textMuted,
11.0f);
stateY += 34.0f;
addStateLine(
"Host capture: " + FormatBool(GetCapture() == m_hwnd),
GetCapture() == m_hwnd ? kSuccess : kShellPalette.textMuted);
addStateLine(
"Root split ratio: " + FormatFloat(m_controller.GetWorkspace().root.splitRatio),
kShellPalette.textWeak,
11.0f);
if (viewportFrame != nullptr) {
addStateLine(
"Viewport hover: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.hovered),
kShellPalette.textWeak,
11.0f);
addStateLine(
"Viewport focus: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.focused),
kShellPalette.textWeak,
11.0f);
addStateLine(
"Viewport capture: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.captured),
kShellPalette.textWeak,
11.0f);
}
addStateLine(
"Capture: " +
(m_autoScreenshot.HasPendingCapture()
? std::string("capture queued...")
: (m_autoScreenshot.GetLastCaptureSummary().empty()
? std::string("Set XCUI_AUTO_CAPTURE_ON_STARTUP=1 or press F12.")
: m_autoScreenshot.GetLastCaptureSummary())),
kShellPalette.textWeak,
11.0f);
DrawCard(drawList, m_previewRect, "Preview", "The right side renders the real workspace interaction frame.");
const auto dockPalette = ResolveUIEditorDockHostPalette();
const auto viewportPalette = ResolveUIEditorViewportSlotPalette();
const auto dockMetrics = ResolveUIEditorDockHostMetrics();
const auto viewportMetrics = ResolveUIEditorViewportSlotMetrics();
AppendUIEditorWorkspaceCompose(
drawList,
m_cachedFrame.composeFrame,
dockPalette,
dockMetrics,
viewportPalette,
viewportMetrics);
const bool framePresented = m_renderer.Render(drawData);
m_autoScreenshot.CaptureIfRequested(
m_renderer,
drawData,
static_cast<unsigned int>(width),
static_cast<unsigned int>(height),
framePresented);
}
void RenderFrame() {
UpdateLayout();
const auto dockMetrics = ResolveUIEditorDockHostMetrics();
const auto viewportMetrics = ResolveUIEditorViewportSlotMetrics();
m_cachedFrame = UpdateUIEditorWorkspaceInteraction(
m_interactionState,
m_controller,
m_workspaceRect,
m_interactionModel,
m_pendingInputEvents);
m_pendingInputEvents,
dockMetrics,
viewportMetrics);
m_pendingInputEvents.clear();
ApplyHostCaptureRequests(m_cachedFrame.result);
SetInteractionResult(m_cachedFrame.result);
@@ -618,63 +904,126 @@ private:
FindUIEditorWorkspaceViewportPresentationFrame(m_cachedFrame.composeFrame, "viewport");
const bool viewportVisible = viewportFrame != nullptr;
const std::string selectedPresentation =
viewportVisible ? "ViewportShell" : "DockHost Placeholder";
viewportVisible ? "视口呈现中" : "停用或未生成占位";
RECT clientRect = {};
GetClientRect(m_hwnd, &clientRect);
const float width = static_cast<float>((std::max)(clientRect.right - clientRect.left, 1L));
const float height = static_cast<float>((std::max)(clientRect.bottom - clientRect.top, 1L));
RenderValidationUI(width, height);
return;
UIDrawData drawData = {};
UIDrawList& drawList = drawData.EmplaceDrawList("WorkspaceInteractionBasic");
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg);
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kShellPalette.windowBackground);
DrawCard(drawList, m_introRect, "这个测试验证什么功能", "只验证 WorkspaceInteraction 统一路由 contract不做 editor 业务。");
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 72.0f), "1. 验证 DockHost splitter drag 与 ViewportShell input 可以被同一层统一收口。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 94.0f), "2. 验证点击 viewport body 时focus/capture 请求会直接冒泡给宿主。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 116.0f), "3. 验证点击 Document tab 后body 会立即从 ViewportShell 切回 DockHost placeholder。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 138.0f), "4. 验证 splitter 改尺寸后viewport body bounds 与 workspace layout 同步变化。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 162.0f), "建议操作: 先点中间 viewport再切到 Document最后拖右侧 splitter。", kTextWeak, 11.0f);
DrawCard(
drawList,
m_introRect,
"这个测试验证什么",
"验证 Workspace 基础交互与 Viewport 输入桥接是否统一。");
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 72.0f),
"1. 拖拽分割条后,右侧工作区布局比例应实时变化。",
kShellPalette.textPrimary,
12.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 94.0f),
"2. 点击标签、面板或视口时,左侧状态应显示命中目标与结果。",
kShellPalette.textPrimary,
12.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 116.0f),
"3. 视口 hover、focus、capture 的变化应反馈到状态面板。",
kShellPalette.textPrimary,
12.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 138.0f),
"4. 该场景只验证 Editor 基础交互,不验证具体业务面板。",
kShellPalette.textPrimary,
12.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 162.0f),
"重点观察:命中目标、当前激活面板、宿主 capture 与视口输入状态。",
kShellPalette.textWeak,
11.0f);
DrawCard(drawList, m_controlsRect, "操作", "这里只保留 Reset / Capture");
DrawCard(drawList, m_controlsRect, "操作", "这里只保留重置和截图两个辅助动作");
for (const ButtonState& button : m_buttons) {
DrawButton(drawList, button);
}
DrawCard(drawList, m_stateRect, "状态", "重点检查 WorkspaceInteraction 当前状态。");
DrawCard(drawList, m_stateRect, "状态", "观察当前工作区交互与视口输入状态。");
float stateY = m_stateRect.y + 66.0f;
auto addStateLine = [&](std::string text, const UIColor& color = kTextPrimary, float fontSize = 12.0f) {
auto addStateLine = [&](std::string text,
const UIColor& color = kShellPalette.textPrimary,
float fontSize = 12.0f) {
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY), std::move(text), color, fontSize);
stateY += 20.0f;
};
addStateLine("DockHost Hover: " + DescribeHitTarget(m_interactionState.dockHostInteractionState.dockHostState.hoveredTarget), kTextPrimary, 11.0f);
addStateLine("Selected Presentation: " + selectedPresentation, kTextPrimary);
addStateLine("Active Panel: " + (m_controller.GetWorkspace().activePanelId.empty() ? std::string("(none)") : m_controller.GetWorkspace().activePanelId));
addStateLine("Selected Tab: " + GetSelectedTabId(), kTextPrimary);
addStateLine("Visible Panels: " + JoinVisiblePanelIds(m_controller), kTextWeak, 11.0f);
addStateLine("Result: " + m_lastStatus, m_lastColor);
drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f), m_lastMessage, kTextMuted, 11.0f);
addStateLine(
"命中目标: " +
DescribeHitTarget(m_interactionState.dockHostInteractionState.dockHostState.hoveredTarget),
kShellPalette.textPrimary,
11.0f);
addStateLine("当前呈现: " + selectedPresentation, kShellPalette.textPrimary);
addStateLine(
"当前激活: " +
(m_controller.GetWorkspace().activePanelId.empty()
? std::string("(none)")
: m_controller.GetWorkspace().activePanelId));
addStateLine("当前标签: " + GetSelectedTabId(), kShellPalette.textPrimary);
addStateLine("可见面板: " + JoinVisiblePanelIds(m_controller), kShellPalette.textWeak, 11.0f);
addStateLine("结果: " + m_lastStatus, m_lastColor);
drawList.AddText(
UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f),
m_lastMessage,
kShellPalette.textMuted,
11.0f);
stateY += 34.0f;
addStateLine("Host Capture: " + FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kSuccess : kTextMuted);
addStateLine("root-split ratio: " + FormatFloat(m_controller.GetWorkspace().root.splitRatio), kTextWeak, 11.0f);
addStateLine(
"宿主 capture: " + FormatBool(GetCapture() == m_hwnd),
GetCapture() == m_hwnd ? kSuccess : kShellPalette.textMuted);
addStateLine(
"根分割比例: " + FormatFloat(m_controller.GetWorkspace().root.splitRatio),
kShellPalette.textWeak,
11.0f);
if (viewportFrame != nullptr) {
addStateLine("Viewport Hover: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.hovered), kTextWeak, 11.0f);
addStateLine("Viewport Focus: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.focused), kTextWeak, 11.0f);
addStateLine("Viewport Capture: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.captured), kTextWeak, 11.0f);
addStateLine(
"视口 hover: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.hovered),
kShellPalette.textWeak,
11.0f);
addStateLine(
"视口 focus: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.focused),
kShellPalette.textWeak,
11.0f);
addStateLine(
"视口 capture: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.captured),
kShellPalette.textWeak,
11.0f);
}
addStateLine(
"截图: " +
(m_autoScreenshot.HasPendingCapture()
? std::string("截图排队中...")
: (m_autoScreenshot.GetLastCaptureSummary().empty()
? std::string("F12 或 按钮 -> captures/")
: m_autoScreenshot.GetLastCaptureSummary())),
kTextWeak,
? std::string("截图排队中...")
: (m_autoScreenshot.GetLastCaptureSummary().empty()
? std::string("F12 或按钮截图 -> captures/")
: m_autoScreenshot.GetLastCaptureSummary())),
kShellPalette.textWeak,
11.0f);
DrawCard(drawList, m_previewRect, "Preview", "真实 WorkspaceInteraction 预览,不再在 exe 宿主里手写拼装输入路由");
AppendUIEditorWorkspaceCompose(drawList, m_cachedFrame.composeFrame);
DrawCard(drawList, m_previewRect, "预览", "右侧是真实的工作区交互预览");
const auto dockPalette = ResolveUIEditorDockHostPalette();
const auto viewportPalette = ResolveUIEditorViewportSlotPalette();
AppendUIEditorWorkspaceCompose(
drawList,
m_cachedFrame.composeFrame,
dockPalette,
dockMetrics,
viewportPalette,
viewportMetrics);
const bool framePresented = m_renderer.Render(drawData);
m_autoScreenshot.CaptureIfRequested(
@@ -704,7 +1053,7 @@ private:
bool m_trackingMouseLeave = false;
std::string m_lastStatus = {};
std::string m_lastMessage = {};
UIColor m_lastColor = kTextMuted;
UIColor m_lastColor = kShellPalette.textMuted;
};
} // namespace

View File

@@ -1,6 +1,5 @@
<View
name="EditorWorkspaceShellComposeValidation"
theme="../shared/themes/editor_validation.xctheme">
name="EditorWorkspaceShellComposeValidation">
<Column width="fill" height="fill" padding="20" gap="12">
<Card
title="测试内容Editor Shell 基础壳层组合"

View File

@@ -2,11 +2,11 @@
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorDockHostInteraction.h>
#include <XCEditor/Core/UIEditorPanelRegistry.h>
#include <XCEditor/Core/UIEditorWorkspaceController.h>
#include <XCEditor/Core/UIEditorWorkspaceModel.h>
#include <XCEditor/Widgets/UIEditorDockHost.h>
#include <XCEditor/Shell/UIEditorDockHostInteraction.h>
#include <XCEditor/Shell/UIEditorPanelRegistry.h>
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
#include <XCEditor/Shell/UIEditorWorkspaceModel.h>
#include <XCEditor/Shell/UIEditorDockHost.h>
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -513,7 +513,7 @@ private:
m_resetPressed = false;
if (resetTriggered) {
ResetScenario();
m_lastResult = "Reset";
m_lastResult = "已重置";
InvalidateRect(m_hwnd, nullptr, FALSE);
return;
}
@@ -555,22 +555,22 @@ private:
}
if (result.requestPointerCapture) {
m_lastResult = "Capture: begin splitter drag";
m_lastResult = "捕获:开始拖动 splitter";
return;
}
if (result.releasePointerCapture) {
m_lastResult = "Capture: end splitter drag";
m_lastResult = "捕获:结束拖动 splitter";
return;
}
if (result.hitTarget.kind != UIEditorDockHostHitTargetKind::None) {
m_lastResult = "Hover: " + DescribeHitTarget(result.hitTarget);
m_lastResult = "悬停: " + DescribeHitTarget(result.hitTarget);
return;
}
if (result.consumed) {
m_lastResult = "Consumed: input handled by DockHostInteraction";
m_lastResult = "已消费: 输入由 DockHostInteraction 处理";
}
}
@@ -597,7 +597,7 @@ private:
DrawCard(
drawList,
m_introRect,
"这个测试验证什么功能?",
"这个测试验证什么功能?",
"验证 Workspace + DockHost 的组合场景是否完全收口到统一交互层。");
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 68.0f),
@@ -625,12 +625,12 @@ private:
m_stateRect,
"状态回显",
"这里直接展示 hover / focus / dragging / active panel / split ratio。");
DrawButton(drawList, m_resetButtonRect, "Reset", m_resetHovered);
DrawButton(drawList, m_resetButtonRect, "重置", m_resetHovered);
const auto validation = m_controller.ValidateState();
drawList.AddText(
UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 70.0f),
"Hover: " + DescribeHitTarget(m_interactionState.dockHostState.hoveredTarget),
"悬停: " + DescribeHitTarget(m_interactionState.dockHostState.hoveredTarget),
kTextPrimary,
13.0f);
drawList.AddText(
@@ -640,7 +640,7 @@ private:
13.0f);
drawList.AddText(
UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 122.0f),
"Capture: " + std::string(GetCapture() == m_hwnd ? "On" : "Off"),
"捕获: " + std::string(GetCapture() == m_hwnd ? "开启" : "关闭"),
GetCapture() == m_hwnd ? kSuccess : kTextMuted,
13.0f);
drawList.AddText(
@@ -653,12 +653,12 @@ private:
13.0f);
drawList.AddText(
UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 174.0f),
"Active Panel: " + m_controller.GetWorkspace().activePanelId,
"当前激活面板: " + m_controller.GetWorkspace().activePanelId,
kTextPrimary,
13.0f);
drawList.AddText(
UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 198.0f),
"Visible Panels: " + JoinVisiblePanelIds(m_controller),
"可见面板: " + JoinVisiblePanelIds(m_controller),
kTextMuted,
12.0f);
drawList.AddText(
@@ -683,7 +683,7 @@ private:
12.0f);
drawList.AddText(
UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 382.0f),
validation.IsValid() ? "Validation: OK" : "Validation: " + validation.message,
validation.IsValid() ? "验证: OK" : "验证: " + validation.message,
validation.IsValid() ? kSuccess : kDanger,
12.0f);

View File

@@ -1,9 +1,9 @@
#ifndef NOMINMAX
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorWorkspaceController.h>
#include <XCEditor/Core/UIEditorWorkspaceLayoutPersistence.h>
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
#include <XCEditor/Shell/UIEditorWorkspaceLayoutPersistence.h>
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -732,13 +732,13 @@ private:
const UIRect footerRect(margin, height - 100.0f, width - margin * 2.0f, 80.0f);
DrawCard(drawList, headerRect, "测试功能Editor Layout Persistence", "只验证 Save / Load / Load Invalid / Reset 的布局恢复链路;不验证业务面板。");
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 70.0f), "1. 点 `1 Hide Active`,把 Document A 隐藏active 应切到 doc-bvisible 应变成 `doc-b, details`。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 70.0f), "1. 点 `1 Hide Active`,把 Document A 隐藏active 应切到 `doc-b`visible 应变成 `doc-b, details`。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 92.0f), "2. 点 `2 Save Layout` 保存当前布局;右侧 Saved 摘要必须记录刚才的 active/visible。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 114.0f), "3. 再点 `3 Close Doc B` 或 `5 Activate Details` 改状态,然后点 `4 Load Layout`;当前状态必须恢复到保存时。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 136.0f), "4. 点 `6 Load Invalid`Result 必须是 Rejected且当前 active/visible 不得被污染。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 136.0f), "4. 点 `6 Load Invalid`Result 必须是 Rejected且当前 active/visible 不得被污染。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 158.0f), "5. `R Reset` 必须回到基线 active=doc-a按 `F12` 保存当前窗口截图。", kTextPrimary, 13.0f);
DrawCard(drawList, actionRect, "操作区", "只保留这一步需要检查的布局保存/恢复动作。");
DrawCard(drawList, actionRect, "操作区", "这里只保留这一步需要检查的布局保存/恢复动作。");
DrawCard(drawList, stateRect, "状态摘要", "左侧看 Current右侧看 Saved重点检查 active、visible 和坏数据恢复。");
DrawCard(drawList, footerRect, "最近结果", "显示最近一次操作、状态和当前 validation。");

View File

@@ -1,9 +1,9 @@
#ifndef NOMINMAX
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorPanelHostLifecycle.h>
#include <XCEditor/Core/UIEditorWorkspaceController.h>
#include <XCEditor/Shell/UIEditorPanelHostLifecycle.h>
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -247,7 +247,7 @@ void DrawHostStateCard(
if (!attached) {
footer = "host 已 detached";
} else if (!visible) {
footer = "host 保 attach但当前不在屏幕";
footer = "host 保 attached,但当前不在屏幕";
} else if (focused) {
footer = "当前 host 已 visible + active + focused";
} else if (active) {
@@ -500,7 +500,8 @@ private:
if (action == ActionId::Capture) {
m_autoScreenshot.RequestCapture("manual_button");
m_lastStatus = "Ready";
m_lastMessage = "截图已排队,输出到 tests/UI/Editor/integration/state/panel_host_lifecycle/captures/。";
m_lastMessage =
"截图已排队,输出到 tests/UI/Editor/integration/state/panel_host_lifecycle/captures/。";
return;
}
@@ -560,10 +561,14 @@ private:
UIDrawList& drawList = drawData.EmplaceDrawList("PanelHostLifecycle");
drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg);
DrawCard(drawList, m_introRect, "这个测试验证什么功能", "只验证 Editor panel host lifecycle contract不做业务面板。");
DrawCard(
drawList,
m_introRect,
"这个测试验证什么功能?",
"只验证 Editor panel host lifecycle contract不做业务面板。");
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 72.0f), "1. 验证 attach/detach 由 panel open/close 驱动,而不是由 visible 驱动。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 94.0f), "2. 验证非选中 tab 依然 attached但 visible=false。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 116.0f), "3. 验证 active focused 是两层不同状态focus 由外部 request 决定。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 116.0f), "3. 验证 active focused 是两层不同状态focus 由外部 request 决定。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 138.0f), "4. 验证 hide/close/reopen/activate 会输出稳定的 lifecycle events。", kTextPrimary, 12.0f);
drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 164.0f), "建议操作:先看 doc-b 默认 attached 但 hidden再点 Hide Active / Focus Active / Close Doc B / Open Doc B。", kTextWeak, 11.0f);

View File

@@ -1,8 +1,8 @@
#ifndef NOMINMAX
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorWorkspaceController.h>
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
#include "Host/AutoScreenshot.h"
#include "Host/NativeRenderer.h"
@@ -510,7 +510,7 @@ private:
const UIRect footerRect(margin, height - 96.0f, width - margin * 2.0f, 76.0f);
DrawCard(drawList, headerRect, "测试功能Editor Command Dispatch + Workspace Controller", "只验证命令分发、状态变更结果和 panel session 联动;不验证业务面板。");
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 70.0f), "1. 点 `1 Hide Active`Result 应是 Changedactive 从 doc-a 切到 doc-bselected tab 也同步到 B。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 70.0f), "1. 点 `1 Hide Active`Result 应是 Changedactive 从 doc-a 切到 doc-bselected tab 也同步到 B。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 92.0f), "2. 再点 `1 Hide Active`,如果当前 panel 已 hiddenResult 应变成 NoOp而不是乱改状态。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 114.0f), "3. 点 `3 Close Doc B` 后再点 `5 Activate Details` / `4 Open Doc B`,检查 Changed / Rejected / NoOp 是否符合提示。", kTextPrimary, 13.0f);
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 136.0f), "4. `R Reset` 必须把状态拉回基线;按 `F12` 保存当前窗口截图。", kTextPrimary, 13.0f);

View File

@@ -1,8 +1,8 @@
#ifndef NOMINMAX
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorShortcutManager.h>
#include <XCEditor/Foundation/UIEditorShortcutManager.h>
#include "Host/AutoScreenshot.h"
#include "Host/InputModifierTracker.h"
#include "Host/NativeRenderer.h"

View File

@@ -1,9 +1,9 @@
#ifndef NOMINMAX
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <XCEditor/Core/UIEditorViewportInputBridge.h>
#include <XCEditor/Widgets/UIEditorViewportSlot.h>
#include <XCEditor/Shell/UIEditorViewportInputBridge.h>
#include <XCEditor/Shell/UIEditorViewportSlot.h>
#include "Host/AutoScreenshot.h"
#include "Host/InputModifierTracker.h"
#include "Host/NativeRenderer.h"
@@ -58,7 +58,7 @@ using XCEngine::UI::Editor::Widgets::UIEditorViewportSlotToolItem;
using XCEngine::UI::Editor::Widgets::UIEditorViewportSlotToolSlot;
constexpr const wchar_t* kWindowClassName = L"XCUIEditorViewportInputBridgeBasicValidation";
constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Viewport Input Bridge";
constexpr const wchar_t* kWindowTitle = L"XCUI 编辑器 | 视口输入桥接";
constexpr UIColor kWindowBg(0.12f, 0.12f, 0.12f, 1.0f);
constexpr UIColor kCardBg(0.18f, 0.18f, 0.18f, 1.0f);
@@ -98,7 +98,7 @@ bool ContainsPoint(const UIRect& rect, float x, float y) {
}
std::string BoolText(bool value) {
return value ? "On" : "Off";
return value ? "" : "";
}
std::string FormatFloat(float value) {
@@ -473,7 +473,7 @@ private:
m_bridgeState = {};
m_bridgeFrame = {};
m_pendingEvents.clear();
m_lastResult = "Ready";
m_lastResult = "就绪";
}
void QueuePointerEvent(UIInputEventType type, UIPointerButton button, WPARAM wParam, LPARAM lParam) {
@@ -565,12 +565,12 @@ private:
if (buttonState.action == ActionId::Reset) {
ResetScenario();
m_lastResult = "状态已重置";
m_lastResult = "场景已重置";
} else {
m_autoScreenshot.RequestCapture("manual_button");
InvalidateRect(m_hwnd, nullptr, FALSE);
UpdateWindow(m_hwnd);
m_lastResult = "截图已排队";
m_lastResult = "截图已加入队列";
}
break;
@@ -583,8 +583,8 @@ private:
UIEditorViewportSlotChrome BuildChrome() const {
UIEditorViewportSlotChrome chrome = {};
chrome.title = "Scene View";
chrome.subtitle = "ViewportInputBridge 验证面板";
chrome.title = "场景视图";
chrome.subtitle = "视口输入桥接验证";
chrome.showTopBar = true;
chrome.showBottomBar = true;
chrome.topBarHeight = 40.0f;
@@ -597,23 +597,23 @@ private:
frame.hasTexture = true;
frame.texture = { 1u, 1280u, 720u };
frame.presentedSize = { 1280.0f, 720.0f };
frame.statusText = "Fake viewport frame";
frame.statusText = "模拟视口帧";
return frame;
}
std::vector<UIEditorViewportSlotToolItem> BuildToolItems() const {
return {
{ "mode", "Perspective", UIEditorViewportSlotToolSlot::Leading, true, true, 98.0f },
{ "input", "Input", UIEditorViewportSlotToolSlot::Trailing, true, true, 58.0f }
{ "mode", "透视", UIEditorViewportSlotToolSlot::Leading, true, true, 98.0f },
{ "input", "输入", UIEditorViewportSlotToolSlot::Trailing, true, true, 58.0f }
};
}
std::vector<UIEditorStatusBarSegment> BuildStatusSegments() const {
return {
{ "hover", std::string("Hover ") + BoolText(m_bridgeFrame.hovered), UIEditorStatusBarSlot::Leading, {}, true, true, 96.0f },
{ "focus", std::string("Focus ") + BoolText(m_bridgeFrame.focused), UIEditorStatusBarSlot::Leading, {}, true, false, 92.0f },
{ "capture", std::string("Capture ") + BoolText(m_bridgeFrame.captured), UIEditorStatusBarSlot::Trailing, {}, true, true, 110.0f },
{ "wheel", "Wheel " + FormatFloat(m_bridgeFrame.wheelDelta), UIEditorStatusBarSlot::Trailing, {}, true, false, 86.0f }
{ "hover", std::string("悬停 ") + BoolText(m_bridgeFrame.hovered), UIEditorStatusBarSlot::Leading, {}, true, true, 96.0f },
{ "focus", std::string("焦点 ") + BoolText(m_bridgeFrame.focused), UIEditorStatusBarSlot::Leading, {}, true, false, 92.0f },
{ "capture", std::string("捕获 ") + BoolText(m_bridgeFrame.captured), UIEditorStatusBarSlot::Trailing, {}, true, true, 110.0f },
{ "wheel", "滚轮 " + FormatFloat(m_bridgeFrame.wheelDelta), UIEditorStatusBarSlot::Trailing, {}, true, false, 86.0f }
};
}
@@ -645,7 +645,7 @@ private:
const float buttonHeight = 34.0f;
m_buttons = {
{ ActionId::Reset, "Reset", UIRect(m_controlsRect.x + 16.0f, m_controlsRect.y + 54.0f, m_controlsRect.width - 32.0f, buttonHeight) },
{ ActionId::Reset, "重置", UIRect(m_controlsRect.x + 16.0f, m_controlsRect.y + 54.0f, m_controlsRect.width - 32.0f, buttonHeight) },
{ ActionId::Capture, "截图", UIRect(m_controlsRect.x + 16.0f, m_controlsRect.y + 98.0f, m_controlsRect.width - 32.0f, buttonHeight) }
};
@@ -676,25 +676,25 @@ private:
m_layout.inputRect,
frameEvents);
if (m_bridgeFrame.focusLost) {
m_lastResult = "FocusLost";
m_lastResult = "焦点丢失";
} else if (m_bridgeFrame.focusGained) {
m_lastResult = "FocusGained";
m_lastResult = "获得焦点";
} else if (m_bridgeFrame.captureStarted) {
m_lastResult = "CaptureStarted";
m_lastResult = "开始捕获";
} else if (m_bridgeFrame.captureEnded) {
m_lastResult = "CaptureEnded";
m_lastResult = "结束捕获";
} else if (!m_bridgeFrame.characters.empty()) {
m_lastResult = "Char " + DescribeCharacters(m_bridgeFrame);
m_lastResult = "字符 " + DescribeCharacters(m_bridgeFrame);
} else if (!m_bridgeFrame.pressedKeyCodes.empty()) {
m_lastResult = "KeyDown " + FormatKeyCodes(m_bridgeFrame.pressedKeyCodes);
m_lastResult = "按键按下 " + FormatKeyCodes(m_bridgeFrame.pressedKeyCodes);
} else if (!m_bridgeFrame.releasedKeyCodes.empty()) {
m_lastResult = "KeyUp " + FormatKeyCodes(m_bridgeFrame.releasedKeyCodes);
m_lastResult = "按键抬起 " + FormatKeyCodes(m_bridgeFrame.releasedKeyCodes);
} else if (m_bridgeFrame.wheelDelta != 0.0f) {
m_lastResult = "Wheel " + std::to_string(static_cast<int>(m_bridgeFrame.wheelDelta));
m_lastResult = "滚轮 " + std::to_string(static_cast<int>(m_bridgeFrame.wheelDelta));
} else if (m_bridgeFrame.pointerPressedInside) {
m_lastResult = "PointerDownInside";
m_lastResult = "内部按下";
} else if (m_bridgeFrame.pointerReleasedInside) {
m_lastResult = "PointerUpInside";
m_lastResult = "内部抬起";
}
UIEditorViewportSlotState slotState = {};
@@ -711,35 +711,35 @@ private:
drawList,
m_introRect,
"测试功能ViewportInputBridge",
"只验证 Editor viewport 输入桥,不混入 Scene/Game 业务。");
"只验证 Editor viewport 输入桥,不混入 Scene/Game 业务。");
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f),
"检查1hover / focus / capture / local 坐标。",
"检查hover / focus / capture / local 坐标。",
kTextMuted,
11.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 86.0f),
"检查2surface 内左键按下后Focus + Capture 进入。",
"检查surface 内左键按下后Focus + Capture 进入。",
kTextMuted,
11.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 106.0f),
"检查3drag 出 surface 后Capture 保留Local Pos 继续更新。",
"检查drag 出 surface 后Capture 保留Local Pos 继续更新。",
kTextMuted,
11.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 126.0f),
"检查4release 后Capture 必须释放。",
"检查release 后Capture 必须释放。",
kTextMuted,
11.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 146.0f),
"检查5:滚轮 / 按键 / 字符只在 Focus 下进入 frame。",
"检查:滚轮 / 按键 / 字符只在 Focus 下进入 frame。",
kTextMuted,
11.0f);
drawList.AddText(
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 166.0f),
"操作hoverclickdragwheelA/W/F/Space输入字符。",
"操作hoverclickdragwheelA/W/F/Space输入字符。",
kTextMuted,
11.0f);
drawList.AddText(
@@ -753,7 +753,7 @@ private:
kTextWeak,
11.0f);
DrawCard(drawList, m_controlsRect, "操作", "只保留 Reset / 截图两个辅助操作。");
DrawCard(drawList, m_controlsRect, "操作", "只保留 Reset / 截图 两个辅助操作。");
for (const ButtonState& button : m_buttons) {
DrawButton(drawList, button);
}
@@ -796,7 +796,7 @@ private:
addStateLine("Result: " + m_lastResult, kTextMuted);
addStateLine(captureSummary, kTextWeak);
DrawCard(drawList, m_previewRect, "Preview", "这里只放一个 ViewportSlot用它承载输入边界。");
DrawCard(drawList, m_previewRect, "预览", "这里只放一个 ViewportSlot用它承载输入边界。");
drawList.AddFilledRect(
UIRect(
m_previewRect.x + 12.0f,

View File

@@ -18,21 +18,24 @@ set(EDITOR_UI_UNIT_TEST_SOURCES
test_ui_editor_shell_interaction.cpp
test_ui_editor_collection_primitives.cpp
test_ui_editor_field_row_layout.cpp
test_ui_editor_theme.cpp
test_ui_editor_hosted_field_builders.cpp
test_ui_editor_bool_field.cpp
test_ui_editor_bool_field_interaction.cpp
test_ui_editor_color_field.cpp
test_ui_editor_color_field_interaction.cpp
test_ui_editor_asset_field.cpp
test_ui_editor_asset_field_interaction.cpp
test_ui_editor_dock_host.cpp
test_ui_editor_inline_rename_session.cpp
test_ui_editor_list_view.cpp
test_ui_editor_list_view_interaction.cpp
test_ui_editor_panel_chrome.cpp
test_ui_editor_panel_frame.cpp
test_ui_editor_enum_field.cpp
test_ui_editor_enum_field_interaction.cpp
test_ui_editor_number_field.cpp
test_ui_editor_number_field_interaction.cpp
test_ui_editor_object_field.cpp
test_ui_editor_object_field_interaction.cpp
test_ui_editor_text_field.cpp
test_ui_editor_text_field_interaction.cpp
test_ui_editor_vector2_field.cpp
@@ -78,6 +81,16 @@ target_include_directories(editor_ui_tests
if(MSVC)
target_compile_options(editor_ui_tests PRIVATE /utf-8 /FS)
set_target_properties(editor_ui_tests PROPERTIES
MSVC_DEBUG_INFORMATION_FORMAT "$<$<CONFIG:Debug,RelWithDebInfo>:Embedded>"
COMPILE_PDB_NAME "editor_ui_tests-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"
)
set_property(TARGET editor_ui_tests PROPERTY
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
endif()

View File

@@ -1,8 +1,8 @@
#include <gtest/gtest.h>
#include "Core/EditorShellAsset.h"
#include "Shell/EditorShellAsset.h"
#include <XCEditor/Core/UIEditorPanelRegistry.h>
#include <XCEditor/Shell/UIEditorPanelRegistry.h>
#include <XCEngine/Input/InputTypes.h>
@@ -35,6 +35,7 @@ TEST(EditorShellAssetValidationTest, DefaultShellAssetPassesValidation) {
const auto validation = ValidateEditorShellAsset(shellAsset);
EXPECT_TRUE(validation.IsValid()) << validation.message;
EXPECT_TRUE(shellAsset.documentPath.empty());
ASSERT_EQ(
shellAsset.shellDefinition.workspacePresentations.size(),
shellAsset.panelRegistry.panels.size());

View File

@@ -2,16 +2,13 @@
#include "Application.h"
#include <XCEditor/Core/UIEditorShellInteraction.h>
#include <XCEditor/Core/UIEditorWorkspaceModel.h>
#include <XCEditor/Core/UIEditorWorkspaceSession.h>
#include <XCEditor/Shell/UIEditorShellInteraction.h>
#include <XCEditor/Shell/UIEditorWorkspaceModel.h>
#include <XCEditor/Shell/UIEditorWorkspaceSession.h>
#include <XCEngine/Input/InputTypes.h>
#include <XCEngine/UI/Runtime/UIScreenDocumentHost.h>
#include <XCEngine/UI/Runtime/UIScreenPlayer.h>
#include <filesystem>
#include <string>
#include <vector>
#ifndef XCUIEDITOR_REPO_ROOT
#define XCUIEDITOR_REPO_ROOT "."
@@ -29,16 +26,9 @@ using XCEngine::UI::Editor::AreUIEditorWorkspaceSessionsEquivalent;
using XCEngine::UI::Editor::UIEditorCommandPanelSource;
using XCEngine::UI::Editor::UIEditorMenuItemKind;
using XCEngine::UI::Editor::UIEditorWorkspaceCommandKind;
using XCEngine::UI::UIDrawCommand;
using XCEngine::UI::UIDrawCommandType;
using XCEngine::UI::UIDrawData;
using XCEngine::UI::UIInputEventType;
using XCEngine::UI::UIShortcutBinding;
using XCEngine::UI::UIShortcutScope;
using XCEngine::UI::Runtime::UIScreenAsset;
using XCEngine::UI::Runtime::UIScreenFrameInput;
using XCEngine::UI::Runtime::UIScreenPlayer;
using XCEngine::UI::Runtime::UIDocumentScreenHost;
std::filesystem::path RepoRootPath() {
std::string root = XCUIEDITOR_REPO_ROOT;
@@ -48,30 +38,6 @@ std::filesystem::path RepoRootPath() {
return std::filesystem::path(root).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;
}
bool PanelRegistriesMatch(
const XCEngine::UI::Editor::UIEditorPanelRegistry& lhs,
const XCEngine::UI::Editor::UIEditorPanelRegistry& rhs) {
@@ -157,35 +123,14 @@ UIShortcutBinding MakeBinding(std::string commandId, KeyCode keyCode) {
} // namespace
TEST(EditorUIStructuredShellTest, AuthoredEditorShellLoadsFromRepositoryResources) {
TEST(EditorUIStructuredShellTest, StructuredEditorShellDoesNotRequireRepositoryXCUIDocument) {
const auto shell = BuildDefaultEditorShellAsset(RepoRootPath());
const auto binding = BuildStructuredEditorShellBinding(shell);
ASSERT_TRUE(binding.IsValid()) << binding.assetValidation.message;
ASSERT_TRUE(std::filesystem::exists(std::filesystem::path(binding.screenAsset.documentPath)));
ASSERT_TRUE(std::filesystem::exists(std::filesystem::path(binding.screenAsset.themePath)));
UIDocumentScreenHost host = {};
UIScreenPlayer player(host);
ASSERT_TRUE(player.Load(binding.screenAsset)) << player.GetLastError();
ASSERT_NE(player.GetDocument(), nullptr);
EXPECT_TRUE(player.GetDocument()->hasThemeDocument);
EXPECT_TRUE(ContainsPathWithFilename(player.GetDocument()->dependencies, "editor_shell.xctheme"));
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_GE(frame.stats.nodeCount, 2u);
EXPECT_FALSE(DrawDataContainsText(frame.drawData, "XCUI Editor Layer"));
EXPECT_FALSE(DrawDataContainsText(frame.drawData, "Left Pane Host"));
EXPECT_FALSE(DrawDataContainsText(frame.drawData, "Primary Workspace Host"));
EXPECT_FALSE(DrawDataContainsText(frame.drawData, "Right Pane Host"));
EXPECT_FALSE(DrawDataContainsText(frame.drawData, "Bottom Pane Host"));
EXPECT_TRUE(binding.screenAsset.documentPath.empty());
EXPECT_TRUE(shell.documentPath.empty());
EXPECT_TRUE(binding.screenAsset.themePath.empty());
}
TEST(EditorUIStructuredShellTest, StructuredShellBindingUsesEditorShellAssetAsSingleSource) {
@@ -214,8 +159,9 @@ TEST(EditorUIStructuredShellTest, StructuredShellBindingUsesEditorShellAssetAsSi
ASSERT_TRUE(binding.IsValid()) << binding.assetValidation.message;
EXPECT_EQ(binding.screenAsset.screenId, shell.screenId);
EXPECT_EQ(std::filesystem::path(binding.screenAsset.documentPath), shell.documentPath);
EXPECT_EQ(std::filesystem::path(binding.screenAsset.themePath), shell.themePath);
EXPECT_TRUE(binding.screenAsset.documentPath.empty());
EXPECT_TRUE(shell.documentPath.empty());
EXPECT_TRUE(binding.screenAsset.themePath.empty());
EXPECT_TRUE(PanelRegistriesMatch(binding.workspaceController.GetPanelRegistry(), shell.panelRegistry));
EXPECT_TRUE(AreUIEditorWorkspaceModelsEquivalent(binding.workspaceController.GetWorkspace(), shell.workspace));

View File

@@ -0,0 +1,123 @@
#include <gtest/gtest.h>
#include <XCEditor/Fields/UIEditorAssetField.h>
#include <XCEngine/UI/DrawData.h>
namespace {
using XCEngine::UI::UIDrawCommandType;
using XCEngine::UI::UIDrawData;
using XCEngine::UI::UIPoint;
using XCEngine::UI::UIRect;
using XCEngine::UI::Editor::Widgets::AppendUIEditorAssetFieldBackground;
using XCEngine::UI::Editor::Widgets::AppendUIEditorAssetFieldForeground;
using XCEngine::UI::Editor::Widgets::BuildUIEditorAssetFieldLayout;
using XCEngine::UI::Editor::Widgets::HasUIEditorAssetFieldValue;
using XCEngine::UI::Editor::Widgets::HitTestUIEditorAssetField;
using XCEngine::UI::Editor::Widgets::ResolveUIEditorAssetFieldValueText;
using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldHitTargetKind;
using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldSpec;
using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldState;
TEST(UIEditorAssetFieldTest, ValueTextUsesDisplayNameAndFallsBackToEmptyText) {
UIEditorAssetFieldSpec assignedSpec = {};
assignedSpec.displayName = "Crate_Albedo";
assignedSpec.emptyText = "None (Texture)";
EXPECT_TRUE(HasUIEditorAssetFieldValue(assignedSpec));
EXPECT_EQ(ResolveUIEditorAssetFieldValueText(assignedSpec), "Crate_Albedo");
UIEditorAssetFieldSpec emptySpec = {};
emptySpec.emptyText = "None (Texture)";
EXPECT_FALSE(HasUIEditorAssetFieldValue(emptySpec));
EXPECT_EQ(ResolveUIEditorAssetFieldValueText(emptySpec), "None (Texture)");
}
TEST(UIEditorAssetFieldTest, LayoutReservesPreviewStatusAndActionButtons) {
UIEditorAssetFieldSpec spec = {};
spec.fieldId = "material.base_color";
spec.label = "Base Map";
spec.assetId = "assets/textures/crate_albedo";
spec.displayName = "Crate_Albedo";
spec.statusText = "Ready";
const auto layout = BuildUIEditorAssetFieldLayout(UIRect(0.0f, 0.0f, 360.0f, 22.0f), spec);
EXPECT_FLOAT_EQ(layout.controlRect.x, 236.0f);
EXPECT_FLOAT_EQ(layout.valueRect.y, 1.0f);
EXPECT_GT(layout.previewRect.width, 0.0f);
EXPECT_GT(layout.statusBadgeRect.width, 0.0f);
EXPECT_FLOAT_EQ(layout.pickerRect.width, 20.0f);
EXPECT_FLOAT_EQ(layout.clearRect.width, 20.0f);
EXPECT_GT(layout.textRect.width, 0.0f);
}
TEST(UIEditorAssetFieldTest, HitTestResolvesClearPickerAndValueBox) {
UIEditorAssetFieldSpec spec = {};
spec.assetId = "assets/textures/crate_albedo";
spec.displayName = "Crate_Albedo";
spec.statusText = "Ready";
const auto layout = BuildUIEditorAssetFieldLayout(UIRect(0.0f, 0.0f, 360.0f, 22.0f), spec);
EXPECT_EQ(
HitTestUIEditorAssetField(
layout,
UIPoint(layout.clearRect.x + 2.0f, layout.clearRect.y + 2.0f)).kind,
UIEditorAssetFieldHitTargetKind::ClearButton);
EXPECT_EQ(
HitTestUIEditorAssetField(
layout,
UIPoint(layout.pickerRect.x + 2.0f, layout.pickerRect.y + 2.0f)).kind,
UIEditorAssetFieldHitTargetKind::PickerButton);
EXPECT_EQ(
HitTestUIEditorAssetField(
layout,
UIPoint(layout.textRect.x + 2.0f, layout.textRect.y + 2.0f)).kind,
UIEditorAssetFieldHitTargetKind::ValueBox);
}
TEST(UIEditorAssetFieldTest, DrawCommandsContainPreviewStatusAndActionGlyphs) {
UIEditorAssetFieldSpec spec = {};
spec.label = "Base Map";
spec.assetId = "assets/textures/crate_albedo";
spec.displayName = "Crate_Albedo";
spec.statusText = "Ready";
UIEditorAssetFieldState state = {};
UIDrawData drawData = {};
auto& drawList = drawData.EmplaceDrawList("AssetField");
const auto layout = BuildUIEditorAssetFieldLayout(UIRect(0.0f, 0.0f, 360.0f, 22.0f), spec);
AppendUIEditorAssetFieldBackground(drawList, layout, spec, state);
AppendUIEditorAssetFieldForeground(drawList, layout, spec, state);
bool foundLabel = false;
bool foundValue = false;
bool foundStatus = false;
bool foundPicker = false;
bool foundClear = false;
bool foundPreviewGradient = false;
for (const auto& command : drawList.GetCommands()) {
if (command.type == UIDrawCommandType::FilledRectLinearGradient &&
command.rect.x == layout.previewRect.x) {
foundPreviewGradient = true;
}
if (command.type == UIDrawCommandType::Text) {
foundLabel = foundLabel || command.text == "Base Map";
foundValue = foundValue || command.text == "Crate_Albedo";
foundStatus = foundStatus || command.text == "Ready";
foundPicker = foundPicker || command.text == "o";
foundClear = foundClear || command.text == "X";
}
}
EXPECT_TRUE(foundPreviewGradient);
EXPECT_TRUE(foundLabel);
EXPECT_TRUE(foundValue);
EXPECT_TRUE(foundStatus);
EXPECT_TRUE(foundPicker);
EXPECT_TRUE(foundClear);
}
} // namespace

View File

@@ -0,0 +1,189 @@
#include <gtest/gtest.h>
#include <XCEditor/Fields/UIEditorAssetFieldInteraction.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::UIEditorAssetFieldInteractionState;
using XCEngine::UI::Editor::UpdateUIEditorAssetFieldInteraction;
using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldHitTargetKind;
using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldSpec;
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;
}
UIEditorAssetFieldSpec MakeAssignedSpec() {
UIEditorAssetFieldSpec spec = {};
spec.fieldId = "material.base_color";
spec.label = "Base Map";
spec.assetId = "assets/textures/crate_albedo";
spec.displayName = "Crate_Albedo";
spec.statusText = "Ready";
return spec;
}
} // namespace
TEST(UIEditorAssetFieldInteractionTest, ClickPickerRequestsPickerWithoutMutatingValue) {
UIEditorAssetFieldSpec spec = MakeAssignedSpec();
UIEditorAssetFieldInteractionState state = {};
auto frame = UpdateUIEditorAssetFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
{});
frame = UpdateUIEditorAssetFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
{
MakePointer(
UIInputEventType::PointerButtonDown,
frame.layout.pickerRect.x + 2.0f,
frame.layout.pickerRect.y + 2.0f,
UIPointerButton::Left),
MakePointer(
UIInputEventType::PointerButtonUp,
frame.layout.pickerRect.x + 2.0f,
frame.layout.pickerRect.y + 2.0f,
UIPointerButton::Left)
});
EXPECT_TRUE(frame.result.pickerRequested);
EXPECT_TRUE(frame.result.consumed);
EXPECT_EQ(frame.result.hitTarget.kind, UIEditorAssetFieldHitTargetKind::PickerButton);
EXPECT_EQ(spec.assetId, "assets/textures/crate_albedo");
EXPECT_EQ(spec.displayName, "Crate_Albedo");
EXPECT_TRUE(state.fieldState.focused);
}
TEST(UIEditorAssetFieldInteractionTest, ClickClearClearsAssignedValue) {
UIEditorAssetFieldSpec spec = MakeAssignedSpec();
UIEditorAssetFieldInteractionState state = {};
auto frame = UpdateUIEditorAssetFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
{});
frame = UpdateUIEditorAssetFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
{
MakePointer(
UIInputEventType::PointerButtonDown,
frame.layout.clearRect.x + 2.0f,
frame.layout.clearRect.y + 2.0f,
UIPointerButton::Left),
MakePointer(
UIInputEventType::PointerButtonUp,
frame.layout.clearRect.x + 2.0f,
frame.layout.clearRect.y + 2.0f,
UIPointerButton::Left)
});
EXPECT_TRUE(frame.result.clearRequested);
EXPECT_TRUE(frame.result.valueChanged);
EXPECT_EQ(frame.result.assetIdBefore, "assets/textures/crate_albedo");
EXPECT_EQ(frame.result.displayNameBefore, "Crate_Albedo");
EXPECT_TRUE(spec.assetId.empty());
EXPECT_TRUE(spec.displayName.empty());
EXPECT_TRUE(spec.statusText.empty());
}
TEST(UIEditorAssetFieldInteractionTest, ClickValueBoxRequestsActivate) {
UIEditorAssetFieldSpec spec = MakeAssignedSpec();
UIEditorAssetFieldInteractionState state = {};
auto frame = UpdateUIEditorAssetFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
{});
frame = UpdateUIEditorAssetFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
{
MakePointer(
UIInputEventType::PointerButtonDown,
frame.layout.textRect.x + 2.0f,
frame.layout.textRect.y + 2.0f,
UIPointerButton::Left),
MakePointer(
UIInputEventType::PointerButtonUp,
frame.layout.textRect.x + 2.0f,
frame.layout.textRect.y + 2.0f,
UIPointerButton::Left)
});
EXPECT_TRUE(frame.result.activateRequested);
EXPECT_TRUE(frame.result.consumed);
EXPECT_EQ(frame.result.hitTarget.kind, UIEditorAssetFieldHitTargetKind::ValueBox);
}
TEST(UIEditorAssetFieldInteractionTest, KeyboardEnterRequestsPickerAndDeleteClearsFocusedValue) {
UIEditorAssetFieldSpec spec = MakeAssignedSpec();
UIEditorAssetFieldInteractionState state = {};
state.fieldState.focused = true;
auto frame = UpdateUIEditorAssetFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
{ MakeKey(KeyCode::Enter) });
EXPECT_TRUE(frame.result.pickerRequested);
EXPECT_EQ(frame.result.hitTarget.kind, UIEditorAssetFieldHitTargetKind::PickerButton);
frame = UpdateUIEditorAssetFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
{ MakeKey(KeyCode::Delete) });
EXPECT_TRUE(frame.result.clearRequested);
EXPECT_TRUE(frame.result.valueChanged);
EXPECT_TRUE(spec.assetId.empty());
}
TEST(UIEditorAssetFieldInteractionTest, ReadOnlyFieldIgnoresPickerAndClearRequests) {
UIEditorAssetFieldSpec spec = MakeAssignedSpec();
spec.readOnly = true;
UIEditorAssetFieldInteractionState state = {};
state.fieldState.focused = true;
auto frame = UpdateUIEditorAssetFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
{ MakeKey(KeyCode::Enter), MakeKey(KeyCode::Delete) });
EXPECT_FALSE(frame.result.pickerRequested);
EXPECT_FALSE(frame.result.clearRequested);
EXPECT_FALSE(frame.result.valueChanged);
EXPECT_EQ(spec.assetId, "assets/textures/crate_albedo");
}

View File

@@ -1,7 +1,7 @@
#include <gtest/gtest.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEditor/Widgets/UIEditorBoolField.h>
#include <XCEditor/Fields/UIEditorBoolField.h>
namespace {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorBoolFieldInteraction.h>
#include <XCEditor/Fields/UIEditorBoolFieldInteraction.h>
#include <XCEngine/Input/InputTypes.h>

View File

@@ -1,25 +1,11 @@
#include <gtest/gtest.h>
#include <XCEngine/UI/Style/Theme.h>
#include <XCEngine/UI/Style/StyleTypes.h>
#include <XCEditor/Widgets/UIEditorCollectionPrimitives.h>
namespace {
namespace Style = XCEngine::UI::Style;
namespace UIWidgets = XCEngine::UI::Editor::Widgets;
Style::UITheme BuildEditorPrimitiveTheme() {
Style::UIThemeDefinition definition = {};
definition.SetToken("space.cardInset", Style::UIStyleValue(14.0f));
definition.SetToken("size.treeItemHeight", Style::UIStyleValue(30.0f));
definition.SetToken("size.listItemHeight", Style::UIStyleValue(64.0f));
definition.SetToken("size.fieldRowHeight", Style::UIStyleValue(36.0f));
definition.SetToken("size.propertySectionHeight", Style::UIStyleValue(156.0f));
definition.SetToken("size.treeIndent", Style::UIStyleValue(20.0f));
return Style::BuildTheme(definition);
}
TEST(UIEditorCollectionPrimitivesTest, ClassifyAndFlagsMatchEditorCollectionTags) {
using Kind = UIWidgets::UIEditorCollectionPrimitiveKind;
@@ -55,30 +41,21 @@ TEST(UIEditorCollectionPrimitivesTest, ClassifyAndFlagsMatchEditorCollectionTags
EXPECT_FALSE(UIWidgets::IsUIEditorCollectionPrimitiveHoverable(Kind::TreeView));
}
TEST(UIEditorCollectionPrimitivesTest, ResolveMetricsUseThemeTokensAndFallbacks) {
TEST(UIEditorCollectionPrimitivesTest, ResolveMetricsUseFixedEditorDefaults) {
using Kind = UIWidgets::UIEditorCollectionPrimitiveKind;
const Style::UITheme themed = BuildEditorPrimitiveTheme();
const Style::UITheme fallback = Style::UITheme();
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::TreeView), 12.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::ListView), 12.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::PropertySection), 12.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::ScrollView), 0.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::TreeView, themed), 14.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::ListView, themed), 14.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::PropertySection, themed), 14.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::ScrollView, themed), 0.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::TreeItem), 28.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::ListItem), 60.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::FieldRow), 32.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::PropertySection), 148.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::TreeItem, themed), 30.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::ListItem, themed), 64.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::FieldRow, themed), 36.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::PropertySection, themed), 156.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::TreeItem, fallback), 28.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::ListItem, fallback), 60.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::FieldRow, fallback), 32.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::PropertySection, fallback), 148.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(Kind::TreeItem, themed, 2.0f), 40.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(Kind::TreeItem, fallback, 2.0f), 36.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(Kind::ListItem, themed, 2.0f), 0.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(Kind::TreeItem, 2.0f), 36.0f);
EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(Kind::ListItem, 2.0f), 0.0f);
}
} // namespace

View File

@@ -1,7 +1,7 @@
#include <gtest/gtest.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEditor/Widgets/UIEditorColorField.h>
#include <XCEditor/Fields/UIEditorColorField.h>
namespace {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorColorFieldInteraction.h>
#include <XCEditor/Fields/UIEditorColorFieldInteraction.h>
#include <XCEngine/Input/InputTypes.h>

View File

@@ -1,10 +1,10 @@
#include <gtest/gtest.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEditor/Core/UIEditorPanelRegistry.h>
#include <XCEditor/Core/UIEditorWorkspaceModel.h>
#include <XCEditor/Core/UIEditorWorkspaceSession.h>
#include <XCEditor/Widgets/UIEditorDockHost.h>
#include <XCEditor/Shell/UIEditorPanelRegistry.h>
#include <XCEditor/Shell/UIEditorWorkspaceModel.h>
#include <XCEditor/Shell/UIEditorWorkspaceSession.h>
#include <XCEditor/Shell/UIEditorDockHost.h>
namespace {

View File

@@ -1,9 +1,9 @@
#include <gtest/gtest.h>
#include <XCEngine/Input/InputTypes.h>
#include <XCEditor/Core/UIEditorDockHostInteraction.h>
#include <XCEditor/Core/UIEditorWorkspaceController.h>
#include <XCEditor/Core/UIEditorWorkspaceModel.h>
#include <XCEditor/Shell/UIEditorDockHostInteraction.h>
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
#include <XCEditor/Shell/UIEditorWorkspaceModel.h>
namespace {

View File

@@ -1,7 +1,7 @@
#include <gtest/gtest.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEditor/Widgets/UIEditorEnumField.h>
#include <XCEditor/Fields/UIEditorEnumField.h>
namespace {
@@ -29,7 +29,7 @@ TEST(UIEditorEnumFieldTest, LayoutKeepsInspectorControlColumnAndUnityArrowWidth)
EXPECT_FLOAT_EQ(layout.controlRect.x, 236.0f);
EXPECT_FLOAT_EQ(layout.valueRect.y, 1.0f);
EXPECT_FLOAT_EQ(layout.valueRect.height, 20.0f);
EXPECT_FLOAT_EQ(layout.arrowRect.width, 20.0f);
EXPECT_FLOAT_EQ(layout.arrowRect.width, 14.0f);
}
TEST(UIEditorEnumFieldTest, HitTestResolvesArrowAndValueBox) {
@@ -44,7 +44,7 @@ TEST(UIEditorEnumFieldTest, HitTestResolvesArrowAndValueBox) {
UIEditorEnumFieldHitTargetKind::ValueBox);
}
TEST(UIEditorEnumFieldTest, BackgroundAndForegroundEmitControlOnlyChromeAndCenteredText) {
TEST(UIEditorEnumFieldTest, BackgroundAndForegroundEmitInspectorChromeAndChevron) {
UIEditorEnumFieldSpec spec = { "blend", "Blend", { "Opaque", "Cutout", "Fade" }, 1u, false };
UIEditorEnumFieldState state = {};
state.popupOpen = true;
@@ -57,21 +57,53 @@ TEST(UIEditorEnumFieldTest, BackgroundAndForegroundEmitControlOnlyChromeAndCente
AppendUIEditorEnumFieldForeground(drawList, layout, spec);
const auto& commands = drawList.GetCommands();
ASSERT_EQ(commands.size(), 9u);
EXPECT_EQ(commands[0].type, UIDrawCommandType::FilledRect);
EXPECT_EQ(commands[0].rect.x, layout.valueRect.x);
EXPECT_EQ(commands[1].type, UIDrawCommandType::RectOutline);
EXPECT_EQ(commands[2].type, UIDrawCommandType::PushClipRect);
EXPECT_EQ(commands[3].type, UIDrawCommandType::Text);
EXPECT_FLOAT_EQ(commands[3].position.y, 2.0f);
EXPECT_EQ(commands[4].type, UIDrawCommandType::PopClipRect);
EXPECT_EQ(commands[5].type, UIDrawCommandType::PushClipRect);
EXPECT_EQ(commands[6].type, UIDrawCommandType::Text);
EXPECT_FLOAT_EQ(commands[6].position.y, 1.0f);
EXPECT_EQ(commands[7].type, UIDrawCommandType::PopClipRect);
EXPECT_EQ(commands[8].type, UIDrawCommandType::Text);
EXPECT_EQ(commands[8].text, "V");
EXPECT_FLOAT_EQ(commands[8].position.y, 2.0f);
std::size_t filledRectCount = 0u;
std::size_t rectOutlineCount = 0u;
std::size_t pushClipCount = 0u;
std::size_t popClipCount = 0u;
std::size_t lineCount = 0u;
bool foundLabelText = false;
bool foundValueText = false;
for (const auto& command : commands) {
switch (command.type) {
case UIDrawCommandType::FilledRect:
++filledRectCount;
break;
case UIDrawCommandType::RectOutline:
++rectOutlineCount;
break;
case UIDrawCommandType::PushClipRect:
++pushClipCount;
break;
case UIDrawCommandType::PopClipRect:
++popClipCount;
break;
case UIDrawCommandType::Line:
++lineCount;
break;
case UIDrawCommandType::Text:
if (command.text == "Blend") {
foundLabelText = true;
EXPECT_FLOAT_EQ(command.position.y, 2.0f);
}
if (command.text == "Cutout") {
foundValueText = true;
EXPECT_FLOAT_EQ(command.position.y, 1.0f);
}
break;
default:
break;
}
}
EXPECT_EQ(filledRectCount, 2u);
EXPECT_EQ(rectOutlineCount, 1u);
EXPECT_EQ(pushClipCount, 2u);
EXPECT_EQ(popClipCount, 2u);
EXPECT_EQ(lineCount, 3u);
EXPECT_TRUE(foundLabelText);
EXPECT_TRUE(foundValueText);
}
} // namespace

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorEnumFieldInteraction.h>
#include <XCEditor/Fields/UIEditorEnumFieldInteraction.h>
#include <XCEngine/Input/InputTypes.h>

View File

@@ -0,0 +1,156 @@
#include <gtest/gtest.h>
#include <XCEditor/Fields/UIEditorFieldStyle.h>
namespace {
namespace UI = XCEngine::UI;
namespace Editor = XCEngine::UI::Editor;
TEST(UIEditorHostedFieldBuildersTest, HostedFieldBuildersInheritPropertyGridMetricsAndPalette) {
UI::Editor::Widgets::UIEditorPropertyGridMetrics propertyMetrics = {};
propertyMetrics.fieldRowHeight = 25.0f;
propertyMetrics.horizontalPadding = 7.0f;
propertyMetrics.labelControlGap = 11.0f;
propertyMetrics.controlColumnStart = 210.0f;
propertyMetrics.labelTextInsetY = 4.0f;
propertyMetrics.labelFontSize = 10.0f;
propertyMetrics.valueTextInsetY = 3.0f;
propertyMetrics.valueFontSize = 12.0f;
propertyMetrics.tagFontSize = 9.0f;
propertyMetrics.valueBoxInsetY = 2.0f;
propertyMetrics.valueBoxInsetX = 5.0f;
propertyMetrics.cornerRounding = 1.0f;
propertyMetrics.valueBoxRounding = 2.0f;
propertyMetrics.borderThickness = 1.0f;
propertyMetrics.focusedBorderThickness = 2.0f;
UI::Editor::Widgets::UIEditorPropertyGridPalette propertyPalette = {};
propertyPalette.valueBoxColor = UI::UIColor(0.2f, 0.2f, 0.2f, 1.0f);
propertyPalette.valueBoxHoverColor = UI::UIColor(0.3f, 0.3f, 0.3f, 1.0f);
propertyPalette.valueBoxEditingColor = UI::UIColor(0.4f, 0.4f, 0.4f, 1.0f);
propertyPalette.valueBoxReadOnlyColor = UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f);
propertyPalette.valueBoxBorderColor = UI::UIColor(0.5f, 0.5f, 0.5f, 1.0f);
propertyPalette.valueBoxEditingBorderColor = UI::UIColor(0.55f, 0.55f, 0.55f, 1.0f);
propertyPalette.labelTextColor = UI::UIColor(0.8f, 0.8f, 0.8f, 1.0f);
propertyPalette.valueTextColor = UI::UIColor(0.9f, 0.9f, 0.9f, 1.0f);
propertyPalette.readOnlyValueTextColor = UI::UIColor(0.6f, 0.6f, 0.6f, 1.0f);
propertyPalette.editTagColor = UI::UIColor(0.52f, 0.68f, 0.94f, 1.0f);
const auto boolMetrics = Editor::BuildUIEditorPropertyGridBoolFieldMetrics(propertyMetrics);
const auto boolPalette = Editor::BuildUIEditorPropertyGridBoolFieldPalette(propertyPalette);
EXPECT_FLOAT_EQ(boolMetrics.rowHeight, 25.0f);
EXPECT_FLOAT_EQ(boolMetrics.controlTrailingInset, 5.0f);
EXPECT_FLOAT_EQ(boolMetrics.labelFontSize, 10.0f);
EXPECT_FLOAT_EQ(boolMetrics.checkboxGlyphFontSize, 12.0f);
EXPECT_FLOAT_EQ(boolPalette.checkboxColor.r, 0.2f);
EXPECT_FLOAT_EQ(boolPalette.labelColor.r, 0.8f);
const auto numberMetrics = Editor::BuildUIEditorPropertyGridNumberFieldMetrics(propertyMetrics);
const auto numberPalette = Editor::BuildUIEditorPropertyGridNumberFieldPalette(propertyPalette);
EXPECT_FLOAT_EQ(numberMetrics.controlTrailingInset, 5.0f);
EXPECT_FLOAT_EQ(numberMetrics.controlInsetY, 2.0f);
EXPECT_FLOAT_EQ(numberMetrics.valueTextInsetX, 5.0f);
EXPECT_FLOAT_EQ(numberMetrics.valueFontSize, 12.0f);
EXPECT_FLOAT_EQ(numberPalette.valueBoxEditingColor.r, 0.4f);
EXPECT_FLOAT_EQ(numberPalette.readOnlyValueColor.r, 0.6f);
EXPECT_FLOAT_EQ(numberPalette.controlFocusedBorderColor.r, 0.55f);
const auto textMetrics = Editor::BuildUIEditorPropertyGridTextFieldMetrics(propertyMetrics);
const auto textPalette = Editor::BuildUIEditorPropertyGridTextFieldPalette(propertyPalette);
EXPECT_FLOAT_EQ(textMetrics.controlTrailingInset, 5.0f);
EXPECT_FLOAT_EQ(textMetrics.controlInsetY, 2.0f);
EXPECT_FLOAT_EQ(textMetrics.valueTextInsetX, 5.0f);
EXPECT_FLOAT_EQ(textMetrics.valueFontSize, 12.0f);
EXPECT_FLOAT_EQ(textPalette.valueBoxEditingColor.r, 0.4f);
EXPECT_FLOAT_EQ(textPalette.readOnlyValueColor.r, 0.6f);
EXPECT_FLOAT_EQ(textPalette.controlFocusedBorderColor.r, 0.55f);
const auto objectMetrics = Editor::BuildUIEditorPropertyGridObjectFieldMetrics(propertyMetrics);
const auto objectPalette = Editor::BuildUIEditorPropertyGridObjectFieldPalette(propertyPalette);
EXPECT_FLOAT_EQ(objectMetrics.controlTrailingInset, 5.0f);
EXPECT_FLOAT_EQ(objectMetrics.controlInsetY, 2.0f);
EXPECT_FLOAT_EQ(objectMetrics.valueTextInsetX, 5.0f);
EXPECT_FLOAT_EQ(objectMetrics.typeTextInsetX, 5.0f);
EXPECT_FLOAT_EQ(objectMetrics.typeTextInsetY, 3.0f);
EXPECT_FLOAT_EQ(objectMetrics.typeFontSize, 9.0f);
EXPECT_FLOAT_EQ(objectMetrics.buttonGlyphFontSize, 12.0f);
EXPECT_FLOAT_EQ(objectPalette.valueBoxColor.r, 0.2f);
EXPECT_FLOAT_EQ(objectPalette.buttonColor.r, 0.15f);
EXPECT_FLOAT_EQ(objectPalette.buttonHoverColor.r, 0.3f);
EXPECT_FLOAT_EQ(objectPalette.buttonActiveColor.r, 0.4f);
EXPECT_FLOAT_EQ(objectPalette.buttonGlyphColor.r, 0.9f);
EXPECT_FLOAT_EQ(objectPalette.separatorColor.r, 0.5f);
EXPECT_FLOAT_EQ(objectPalette.typeColor.r, 0.6f);
const auto vector2Metrics = Editor::BuildUIEditorPropertyGridVector2FieldMetrics(propertyMetrics);
const auto vector2Palette = Editor::BuildUIEditorPropertyGridVector2FieldPalette(propertyPalette);
EXPECT_FLOAT_EQ(vector2Metrics.controlInsetY, 2.0f);
EXPECT_FLOAT_EQ(vector2Metrics.controlTrailingInset, 5.0f);
EXPECT_FLOAT_EQ(vector2Metrics.valueTextInsetX, 5.0f);
EXPECT_FLOAT_EQ(vector2Metrics.componentRounding, 2.0f);
EXPECT_FLOAT_EQ(vector2Palette.componentEditingColor.r, 0.4f);
EXPECT_FLOAT_EQ(vector2Palette.componentFocusedBorderColor.r, 0.55f);
const auto vector3Metrics = Editor::BuildUIEditorPropertyGridVector3FieldMetrics(propertyMetrics);
const auto vector3Palette = Editor::BuildUIEditorPropertyGridVector3FieldPalette(propertyPalette);
EXPECT_FLOAT_EQ(vector3Metrics.controlInsetY, 2.0f);
EXPECT_FLOAT_EQ(vector3Metrics.controlTrailingInset, 5.0f);
EXPECT_FLOAT_EQ(vector3Metrics.valueTextInsetX, 5.0f);
EXPECT_FLOAT_EQ(vector3Metrics.componentRounding, 2.0f);
EXPECT_FLOAT_EQ(vector3Palette.componentEditingColor.r, 0.4f);
EXPECT_FLOAT_EQ(vector3Palette.componentFocusedBorderColor.r, 0.55f);
const auto vector4Metrics = Editor::BuildUIEditorPropertyGridVector4FieldMetrics(propertyMetrics);
const auto vector4Palette = Editor::BuildUIEditorPropertyGridVector4FieldPalette(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::BuildUIEditorPropertyGridEnumFieldMetrics(propertyMetrics);
const auto enumPalette = Editor::BuildUIEditorPropertyGridEnumFieldPalette(propertyPalette);
EXPECT_FLOAT_EQ(enumMetrics.controlTrailingInset, 5.0f);
EXPECT_FLOAT_EQ(enumMetrics.controlInsetY, 2.0f);
EXPECT_FLOAT_EQ(enumMetrics.valueTextInsetX, 5.0f);
EXPECT_FLOAT_EQ(enumMetrics.dropdownArrowFontSize, 12.0f);
EXPECT_FLOAT_EQ(enumPalette.arrowColor.r, 0.9f);
const auto colorMetrics = Editor::BuildUIEditorPropertyGridColorFieldMetrics(propertyMetrics);
const auto colorPalette = Editor::BuildUIEditorPropertyGridColorFieldPalette(propertyPalette);
EXPECT_FLOAT_EQ(colorMetrics.controlTrailingInset, 5.0f);
EXPECT_FLOAT_EQ(colorMetrics.swatchInsetY, 2.0f);
EXPECT_FLOAT_EQ(colorMetrics.labelFontSize, 10.0f);
EXPECT_FLOAT_EQ(colorMetrics.valueFontSize, 12.0f);
EXPECT_FLOAT_EQ(colorMetrics.popupHeaderHeight, 30.0f);
EXPECT_FLOAT_EQ(colorPalette.labelColor.r, 0.8f);
EXPECT_FLOAT_EQ(colorPalette.popupBorderColor.r, 0.15f);
EXPECT_FLOAT_EQ(colorPalette.popupHeaderColor.r, 0.43f);
EXPECT_FLOAT_EQ(colorPalette.swatchBorderColor.r, 0.5f);
const auto assetMetrics = Editor::BuildUIEditorPropertyGridAssetFieldMetrics(propertyMetrics);
const auto assetPalette = Editor::BuildUIEditorPropertyGridAssetFieldPalette(propertyPalette);
EXPECT_FLOAT_EQ(assetMetrics.controlTrailingInset, 5.0f);
EXPECT_FLOAT_EQ(assetMetrics.controlInsetY, 2.0f);
EXPECT_FLOAT_EQ(assetMetrics.valueTextInsetX, 5.0f);
EXPECT_FLOAT_EQ(assetMetrics.previewGlyphFontSize, 12.0f);
EXPECT_FLOAT_EQ(assetMetrics.statusBadgeFontSize, 9.0f);
EXPECT_FLOAT_EQ(assetMetrics.actionGlyphFontSize, 12.0f);
EXPECT_FLOAT_EQ(assetMetrics.previewRounding, 2.0f);
EXPECT_FLOAT_EQ(assetMetrics.badgeRounding, 2.0f);
EXPECT_FLOAT_EQ(assetPalette.valueBoxActiveColor.r, 0.4f);
EXPECT_FLOAT_EQ(assetPalette.previewBaseColor.r, 0.2f);
EXPECT_FLOAT_EQ(assetPalette.previewEmptyColor.r, 0.15f);
EXPECT_FLOAT_EQ(assetPalette.previewGlyphColor.r, 0.9f);
EXPECT_FLOAT_EQ(assetPalette.statusBadgeColor.r, 0.52f);
EXPECT_FLOAT_EQ(assetPalette.statusBadgeColor.g, 0.68f);
EXPECT_FLOAT_EQ(assetPalette.statusBadgeBorderColor.r, 0.5f);
EXPECT_FLOAT_EQ(assetPalette.actionButtonColor.r, 0.15f);
EXPECT_FLOAT_EQ(assetPalette.actionButtonHoverColor.r, 0.3f);
EXPECT_FLOAT_EQ(assetPalette.actionButtonActiveColor.r, 0.4f);
EXPECT_FLOAT_EQ(assetPalette.clearGlyphColor.r, 0.6f);
}
} // namespace

View File

@@ -1,7 +1,7 @@
#include <gtest/gtest.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEditor/Widgets/UIEditorMenuBar.h>
#include <XCEditor/Shell/UIEditorMenuBar.h>
namespace {

View File

@@ -1,7 +1,7 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorMenuModel.h>
#include <XCEditor/Core/UIEditorShortcutManager.h>
#include <XCEditor/Shell/UIEditorMenuModel.h>
#include <XCEditor/Foundation/UIEditorShortcutManager.h>
#include <XCEngine/Input/InputTypes.h>

View File

@@ -1,7 +1,7 @@
#include <gtest/gtest.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEditor/Widgets/UIEditorMenuPopup.h>
#include <XCEditor/Shell/UIEditorMenuPopup.h>
namespace {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorMenuSession.h>
#include <XCEditor/Shell/UIEditorMenuSession.h>
#include <vector>

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Widgets/UIEditorNumberField.h>
#include <XCEditor/Fields/UIEditorNumberField.h>
namespace {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorNumberFieldInteraction.h>
#include <XCEditor/Fields/UIEditorNumberFieldInteraction.h>
#include <XCEngine/Input/InputTypes.h>

View File

@@ -0,0 +1,122 @@
#include <gtest/gtest.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEditor/Fields/UIEditorObjectField.h>
namespace {
using XCEngine::UI::UIDrawCommandType;
using XCEngine::UI::UIPoint;
using XCEngine::UI::UIRect;
using XCEngine::UI::Editor::Widgets::AppendUIEditorObjectField;
using XCEngine::UI::Editor::Widgets::BuildUIEditorObjectFieldLayout;
using XCEngine::UI::Editor::Widgets::HitTestUIEditorObjectField;
using XCEngine::UI::Editor::Widgets::ResolveUIEditorObjectFieldDisplayText;
using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldHitTargetKind;
using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldSpec;
using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldState;
TEST(UIEditorObjectFieldTest, DisplayTextFallsBackForEmptyAndUnnamedObjects) {
UIEditorObjectFieldSpec spec = {};
spec.emptyText = "None (Object)";
EXPECT_EQ(ResolveUIEditorObjectFieldDisplayText(spec), "None (Object)");
spec.hasValue = true;
spec.objectName.clear();
EXPECT_EQ(ResolveUIEditorObjectFieldDisplayText(spec), "(unnamed)");
spec.objectName = "Main Camera";
EXPECT_EQ(ResolveUIEditorObjectFieldDisplayText(spec), "Main Camera");
}
TEST(UIEditorObjectFieldTest, LayoutReservesClearAndPickerButtonsWhenValueExists) {
UIEditorObjectFieldSpec spec = {};
spec.fieldId = "target";
spec.label = "Target";
spec.hasValue = true;
spec.objectName = "Main Camera";
spec.objectTypeName = "Camera";
const auto layout = BuildUIEditorObjectFieldLayout(
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
spec);
EXPECT_FLOAT_EQ(layout.valueRect.x, 236.0f);
EXPECT_GT(layout.clearButtonRect.width, 0.0f);
EXPECT_GT(layout.pickerButtonRect.width, 0.0f);
EXPECT_GT(layout.typeRect.width, 0.0f);
EXPECT_EQ(
HitTestUIEditorObjectField(
layout,
UIPoint(layout.clearButtonRect.x + 2.0f, layout.clearButtonRect.y + 2.0f)).kind,
UIEditorObjectFieldHitTargetKind::ClearButton);
EXPECT_EQ(
HitTestUIEditorObjectField(
layout,
UIPoint(layout.pickerButtonRect.x + 2.0f, layout.pickerButtonRect.y + 2.0f)).kind,
UIEditorObjectFieldHitTargetKind::PickerButton);
}
TEST(UIEditorObjectFieldTest, LayoutWithoutValueUsesEmptyDisplayAndOmitsClearButton) {
UIEditorObjectFieldSpec spec = {};
spec.fieldId = "target";
spec.label = "Target";
spec.hasValue = false;
spec.emptyText = "None (GameObject)";
const auto layout = BuildUIEditorObjectFieldLayout(
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
spec);
EXPECT_FLOAT_EQ(layout.clearButtonRect.width, 0.0f);
EXPECT_GT(layout.pickerButtonRect.width, 0.0f);
EXPECT_FLOAT_EQ(layout.typeRect.width, 0.0f);
EXPECT_EQ(
HitTestUIEditorObjectField(
layout,
UIPoint(layout.valueRect.x + 4.0f, layout.valueRect.y + 4.0f)).kind,
UIEditorObjectFieldHitTargetKind::ValueBox);
}
TEST(UIEditorObjectFieldTest, DrawEmitsLabelValueTypeAndButtonGlyphs) {
UIEditorObjectFieldSpec spec = {};
spec.fieldId = "target";
spec.label = "Target";
spec.hasValue = true;
spec.objectName = "Main Camera";
spec.objectTypeName = "Camera";
UIEditorObjectFieldState state = {};
XCEngine::UI::UIDrawData drawData = {};
auto& drawList = drawData.EmplaceDrawList("ObjectField");
AppendUIEditorObjectField(
drawList,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
spec,
state);
bool hasLabel = false;
bool hasValue = false;
bool hasType = false;
bool hasClearGlyph = false;
bool hasPickerGlyph = false;
bool hasOutline = false;
for (const auto& command : drawList.GetCommands()) {
hasLabel = hasLabel || command.text == "Target";
hasValue = hasValue || command.text == "Main Camera";
hasType = hasType || command.text == "Camera";
hasClearGlyph = hasClearGlyph || command.text == "X";
hasPickerGlyph = hasPickerGlyph || command.text == "o";
hasOutline = hasOutline || command.type == UIDrawCommandType::RectOutline;
}
EXPECT_TRUE(hasLabel);
EXPECT_TRUE(hasValue);
EXPECT_TRUE(hasType);
EXPECT_TRUE(hasClearGlyph);
EXPECT_TRUE(hasPickerGlyph);
EXPECT_TRUE(hasOutline);
}
} // namespace

View File

@@ -0,0 +1,157 @@
#include <gtest/gtest.h>
#include <XCEditor/Fields/UIEditorObjectFieldInteraction.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::UIEditorObjectFieldInteractionState;
using XCEngine::UI::Editor::UpdateUIEditorObjectFieldInteraction;
using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldHitTargetKind;
using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldSpec;
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;
}
UIEditorObjectFieldSpec BuildSpec() {
UIEditorObjectFieldSpec spec = {};
spec.fieldId = "target";
spec.label = "Target";
spec.hasValue = true;
spec.objectName = "Main Camera";
spec.objectTypeName = "Camera";
return spec;
}
} // namespace
TEST(UIEditorObjectFieldInteractionTest, ClickValueBoxRequestsActivateAndFocus) {
UIEditorObjectFieldSpec spec = BuildSpec();
UIEditorObjectFieldInteractionState state = {};
auto frame = UpdateUIEditorObjectFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
{});
frame = UpdateUIEditorObjectFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
{
MakePointer(UIInputEventType::PointerButtonDown, frame.layout.valueRect.x + 4.0f, frame.layout.valueRect.y + 4.0f, UIPointerButton::Left),
MakePointer(UIInputEventType::PointerButtonUp, frame.layout.valueRect.x + 4.0f, frame.layout.valueRect.y + 4.0f, UIPointerButton::Left)
});
EXPECT_TRUE(frame.result.consumed);
EXPECT_TRUE(frame.result.activateRequested);
EXPECT_TRUE(state.fieldState.focused);
EXPECT_EQ(frame.result.hitTarget.kind, UIEditorObjectFieldHitTargetKind::ValueBox);
}
TEST(UIEditorObjectFieldInteractionTest, ClickClearButtonRequestsClear) {
UIEditorObjectFieldSpec spec = BuildSpec();
UIEditorObjectFieldInteractionState state = {};
auto frame = UpdateUIEditorObjectFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
{});
frame = UpdateUIEditorObjectFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
{
MakePointer(UIInputEventType::PointerButtonDown, frame.layout.clearButtonRect.x + 2.0f, frame.layout.clearButtonRect.y + 2.0f, UIPointerButton::Left),
MakePointer(UIInputEventType::PointerButtonUp, frame.layout.clearButtonRect.x + 2.0f, frame.layout.clearButtonRect.y + 2.0f, UIPointerButton::Left)
});
EXPECT_TRUE(frame.result.consumed);
EXPECT_TRUE(frame.result.clearRequested);
EXPECT_EQ(frame.result.hitTarget.kind, UIEditorObjectFieldHitTargetKind::ClearButton);
}
TEST(UIEditorObjectFieldInteractionTest, KeyboardActivateAndClearFollowFocus) {
UIEditorObjectFieldSpec spec = BuildSpec();
UIEditorObjectFieldInteractionState state = {};
state.fieldState.focused = true;
auto frame = UpdateUIEditorObjectFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
{ MakeKey(KeyCode::Enter) });
EXPECT_TRUE(frame.result.activateRequested);
EXPECT_TRUE(frame.result.consumed);
frame = UpdateUIEditorObjectFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
{ MakeKey(KeyCode::Delete) });
EXPECT_TRUE(frame.result.clearRequested);
EXPECT_TRUE(frame.result.consumed);
}
TEST(UIEditorObjectFieldInteractionTest, ReadOnlyFieldSuppressesActivateAndClear) {
UIEditorObjectFieldSpec spec = BuildSpec();
spec.readOnly = true;
UIEditorObjectFieldInteractionState state = {};
state.fieldState.focused = true;
auto frame = UpdateUIEditorObjectFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
{ MakeKey(KeyCode::Space), MakeKey(KeyCode::Delete) });
EXPECT_FALSE(frame.result.activateRequested);
EXPECT_FALSE(frame.result.clearRequested);
}
TEST(UIEditorObjectFieldInteractionTest, FocusLostClearsHoverAndActiveState) {
UIEditorObjectFieldSpec spec = BuildSpec();
UIEditorObjectFieldInteractionState state = {};
state.fieldState.focused = true;
state.fieldState.activeTarget = UIEditorObjectFieldHitTargetKind::PickerButton;
state.fieldState.hoveredTarget = UIEditorObjectFieldHitTargetKind::ValueBox;
state.hasPointerPosition = true;
state.pointerPosition = UIPoint(250.0f, 10.0f);
UIInputEvent focusLost = {};
focusLost.type = UIInputEventType::FocusLost;
const auto frame = UpdateUIEditorObjectFieldInteraction(
state,
spec,
UIRect(0.0f, 0.0f, 360.0f, 22.0f),
{ focusLost });
EXPECT_TRUE(frame.result.focusChanged);
EXPECT_FALSE(state.fieldState.focused);
EXPECT_EQ(state.fieldState.activeTarget, UIEditorObjectFieldHitTargetKind::None);
EXPECT_EQ(state.fieldState.hoveredTarget, UIEditorObjectFieldHitTargetKind::None);
EXPECT_FALSE(state.hasPointerPosition);
}

View File

@@ -1,126 +0,0 @@
#include <gtest/gtest.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEditor/Widgets/UIEditorPanelChrome.h>
namespace {
using XCEngine::UI::UIColor;
using XCEngine::UI::UIDrawCommandType;
using XCEngine::UI::UIDrawList;
using XCEngine::UI::UIRect;
using XCEngine::UI::Editor::Widgets::AppendUIEditorPanelChromeBackground;
using XCEngine::UI::Editor::Widgets::AppendUIEditorPanelChromeForeground;
using XCEngine::UI::Editor::Widgets::BuildUIEditorPanelChromeHeaderRect;
using XCEngine::UI::Editor::Widgets::ResolveUIEditorPanelChromeBorderColor;
using XCEngine::UI::Editor::Widgets::ResolveUIEditorPanelChromeBorderThickness;
using XCEngine::UI::Editor::Widgets::UIEditorPanelChromePalette;
using XCEngine::UI::Editor::Widgets::UIEditorPanelChromeState;
using XCEngine::UI::Editor::Widgets::UIEditorPanelChromeText;
void ExpectColorEq(
const UIColor& actual,
const UIColor& expected) {
EXPECT_FLOAT_EQ(actual.r, expected.r);
EXPECT_FLOAT_EQ(actual.g, expected.g);
EXPECT_FLOAT_EQ(actual.b, expected.b);
EXPECT_FLOAT_EQ(actual.a, expected.a);
}
TEST(UIEditorPanelChromeTest, HeaderRectAndBorderPolicyMatchNativeShellCardChrome) {
const UIRect panelRect(100.0f, 200.0f, 320.0f, 180.0f);
const UIEditorPanelChromePalette palette = {};
const auto headerRect = BuildUIEditorPanelChromeHeaderRect(panelRect);
EXPECT_FLOAT_EQ(headerRect.x, 100.0f);
EXPECT_FLOAT_EQ(headerRect.y, 200.0f);
EXPECT_FLOAT_EQ(headerRect.width, 320.0f);
EXPECT_FLOAT_EQ(headerRect.height, 42.0f);
ExpectColorEq(
ResolveUIEditorPanelChromeBorderColor(UIEditorPanelChromeState(), palette),
palette.borderColor);
ExpectColorEq(
ResolveUIEditorPanelChromeBorderColor(UIEditorPanelChromeState{ false, true }, palette),
palette.hoveredAccentColor);
ExpectColorEq(
ResolveUIEditorPanelChromeBorderColor(UIEditorPanelChromeState{ true, true }, palette),
palette.accentColor);
EXPECT_FLOAT_EQ(ResolveUIEditorPanelChromeBorderThickness(UIEditorPanelChromeState()), 1.0f);
EXPECT_FLOAT_EQ(ResolveUIEditorPanelChromeBorderThickness(UIEditorPanelChromeState{ true, false }), 2.0f);
}
TEST(UIEditorPanelChromeTest, BackgroundAppendEmitsSurfaceOutlineAndHeaderFill) {
UIDrawList drawList("PanelChrome");
const UIRect panelRect(40.0f, 60.0f, 400.0f, 280.0f);
const UIEditorPanelChromeState state{ true, false };
const UIEditorPanelChromePalette palette = {};
AppendUIEditorPanelChromeBackground(drawList, panelRect, state, palette);
ASSERT_EQ(drawList.GetCommandCount(), 3u);
const auto& commands = drawList.GetCommands();
EXPECT_EQ(commands[0].type, UIDrawCommandType::FilledRect);
EXPECT_EQ(commands[1].type, UIDrawCommandType::RectOutline);
EXPECT_EQ(commands[2].type, UIDrawCommandType::FilledRect);
EXPECT_FLOAT_EQ(commands[0].rect.x, 40.0f);
EXPECT_FLOAT_EQ(commands[0].rounding, 18.0f);
ExpectColorEq(commands[0].color, palette.surfaceColor);
EXPECT_FLOAT_EQ(commands[1].thickness, 2.0f);
EXPECT_FLOAT_EQ(commands[1].rounding, 18.0f);
ExpectColorEq(commands[1].color, palette.accentColor);
EXPECT_FLOAT_EQ(commands[2].rect.height, 42.0f);
EXPECT_FLOAT_EQ(commands[2].rounding, 18.0f);
ExpectColorEq(commands[2].color, palette.headerColor);
}
TEST(UIEditorPanelChromeTest, ForegroundAppendPlacesTitleSubtitleAndFooterAtCurrentOffsets) {
UIDrawList drawList("PanelChromeText");
const UIRect panelRect(100.0f, 200.0f, 320.0f, 180.0f);
const UIEditorPanelChromePalette palette = {};
const UIEditorPanelChromeText text{
"XCUI Demo",
"native queued offscreen surface",
"Active | 42 elements | 9 cmds"
};
AppendUIEditorPanelChromeForeground(drawList, panelRect, text, palette);
ASSERT_EQ(drawList.GetCommandCount(), 3u);
const auto& commands = drawList.GetCommands();
EXPECT_EQ(commands[0].type, UIDrawCommandType::Text);
EXPECT_EQ(commands[1].type, UIDrawCommandType::Text);
EXPECT_EQ(commands[2].type, UIDrawCommandType::Text);
EXPECT_EQ(commands[0].text, "XCUI Demo");
EXPECT_FLOAT_EQ(commands[0].position.x, 116.0f);
EXPECT_FLOAT_EQ(commands[0].position.y, 212.0f);
ExpectColorEq(commands[0].color, palette.textPrimary);
EXPECT_EQ(commands[1].text, "native queued offscreen surface");
EXPECT_FLOAT_EQ(commands[1].position.x, 116.0f);
EXPECT_FLOAT_EQ(commands[1].position.y, 228.0f);
ExpectColorEq(commands[1].color, palette.textSecondary);
EXPECT_EQ(commands[2].text, "Active | 42 elements | 9 cmds");
EXPECT_FLOAT_EQ(commands[2].position.x, 116.0f);
EXPECT_FLOAT_EQ(commands[2].position.y, 362.0f);
ExpectColorEq(commands[2].color, palette.textMuted);
}
TEST(UIEditorPanelChromeTest, ForegroundAppendSkipsEmptyStrings) {
UIDrawList drawList("PanelChromeEmptyText");
AppendUIEditorPanelChromeForeground(
drawList,
UIRect(0.0f, 0.0f, 320.0f, 180.0f),
UIEditorPanelChromeText{});
EXPECT_EQ(drawList.GetCommandCount(), 0u);
}
} // namespace

View File

@@ -1,8 +1,8 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorPanelContentHost.h>
#include <XCEditor/Core/UIEditorWorkspaceSession.h>
#include <XCEditor/Widgets/UIEditorDockHost.h>
#include <XCEditor/Shell/UIEditorPanelContentHost.h>
#include <XCEditor/Shell/UIEditorWorkspaceSession.h>
#include <XCEditor/Shell/UIEditorDockHost.h>
namespace {

View File

@@ -1,7 +1,7 @@
#include <gtest/gtest.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEditor/Widgets/UIEditorPanelFrame.h>
#include <XCEditor/Shell/UIEditorPanelFrame.h>
namespace {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorPanelHostLifecycle.h>
#include <XCEditor/Shell/UIEditorPanelHostLifecycle.h>
namespace {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorPanelRegistry.h>
#include <XCEditor/Shell/UIEditorPanelRegistry.h>
namespace {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Widgets/UIEditorPropertyGrid.h>
#include <XCEditor/Fields/UIEditorPropertyGrid.h>
namespace {
@@ -372,19 +372,13 @@ TEST(UIEditorPropertyGridTest, ColorFieldUsesHostedLayoutAndPopupAwareForeground
state.focused = true;
state.hoveredFieldId = "tint";
state.hoveredHitTarget = UIEditorPropertyGridHitTargetKind::ValueBox;
state.colorFieldStates = {
{
"tint",
{
XCEngine::UI::Editor::Widgets::UIEditorColorFieldHitTargetKind::Swatch,
XCEngine::UI::Editor::Widgets::UIEditorColorFieldHitTargetKind::None,
true,
true,
0.0f,
false
}
}
};
XCEngine::UI::Editor::Widgets::UIEditorPropertyGridColorFieldVisualState colorState = {};
colorState.fieldId = "tint";
colorState.state.hoveredTarget =
XCEngine::UI::Editor::Widgets::UIEditorColorFieldHitTargetKind::Swatch;
colorState.state.focused = true;
colorState.state.popupOpen = true;
state.colorFieldStates.push_back(colorState);
const auto layout = BuildUIEditorPropertyGridLayout(
UIRect(0.0f, 0.0f, 520.0f, 320.0f),

View File

@@ -1,8 +1,8 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorTheme.h>
#include <XCEditor/Core/UIEditorPropertyGridInteraction.h>
#include <XCEditor/Widgets/UIEditorColorField.h>
#include <XCEditor/Fields/UIEditorFieldStyle.h>
#include <XCEditor/Fields/UIEditorPropertyGridInteraction.h>
#include <XCEditor/Fields/UIEditorColorField.h>
#include <XCEngine/Input/InputTypes.h>
@@ -17,7 +17,7 @@ using XCEngine::UI::UIRect;
using XCEngine::UI::Widgets::UIExpansionModel;
using XCEngine::UI::Widgets::UIPropertyEditModel;
using XCEngine::UI::Widgets::UISelectionModel;
using XCEngine::UI::Editor::BuildUIEditorHostedColorFieldMetrics;
using XCEngine::UI::Editor::BuildUIEditorPropertyGridColorFieldMetrics;
using XCEngine::UI::Editor::UIEditorPropertyGridInteractionState;
using XCEngine::UI::Editor::UpdateUIEditorPropertyGridInteraction;
using XCEngine::UI::Editor::Widgets::BuildUIEditorColorFieldLayout;
@@ -542,7 +542,7 @@ TEST(UIEditorPropertyGridInteractionTest, ColorFieldPopupCanOpenAndDragAlphaThro
const auto initialColorLayout = BuildUIEditorColorFieldLayout(
frame.layout.fieldRowRects[0],
initialColorSpec,
BuildUIEditorHostedColorFieldMetrics({}),
BuildUIEditorPropertyGridColorFieldMetrics({}),
UIRect(-4096.0f, -4096.0f, 8192.0f, 8192.0f));
const UIPoint swatchCenter = RectCenter(initialColorLayout.swatchRect);
frame = UpdateUIEditorPropertyGridInteraction(
@@ -581,7 +581,7 @@ TEST(UIEditorPropertyGridInteractionTest, ColorFieldPopupCanOpenAndDragAlphaThro
const auto colorLayout = BuildUIEditorColorFieldLayout(
popupFrame.layout.fieldRowRects[0],
colorSpec,
BuildUIEditorHostedColorFieldMetrics({}),
BuildUIEditorPropertyGridColorFieldMetrics({}),
UIRect(-4096.0f, -4096.0f, 8192.0f, 8192.0f));
const float alphaX =

View File

@@ -1,7 +1,7 @@
#include <gtest/gtest.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEditor/Widgets/UIEditorScrollView.h>
#include <XCEditor/Collections/UIEditorScrollView.h>
namespace {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorScrollViewInteraction.h>
#include <XCEditor/Collections/UIEditorScrollViewInteraction.h>
namespace {

View File

@@ -1,9 +1,9 @@
#include <gtest/gtest.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEditor/Core/UIEditorShellCompose.h>
#include <XCEditor/Core/UIEditorWorkspaceModel.h>
#include <XCEditor/Core/UIEditorWorkspaceSession.h>
#include <XCEditor/Shell/UIEditorShellCompose.h>
#include <XCEditor/Shell/UIEditorWorkspaceModel.h>
#include <XCEditor/Shell/UIEditorWorkspaceSession.h>
namespace {

View File

@@ -1,8 +1,8 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorShellInteraction.h>
#include <XCEditor/Core/UIEditorWorkspaceModel.h>
#include <XCEditor/Core/UIEditorWorkspaceSession.h>
#include <XCEditor/Shell/UIEditorShellInteraction.h>
#include <XCEditor/Shell/UIEditorWorkspaceModel.h>
#include <XCEditor/Shell/UIEditorWorkspaceSession.h>
#include <XCEngine/Input/InputTypes.h>

View File

@@ -1,7 +1,7 @@
#include <gtest/gtest.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEditor/Widgets/UIEditorStatusBar.h>
#include <XCEditor/Shell/UIEditorStatusBar.h>
namespace {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Widgets/UIEditorTextField.h>
#include <XCEditor/Fields/UIEditorTextField.h>
namespace {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorTextFieldInteraction.h>
#include <XCEditor/Fields/UIEditorTextFieldInteraction.h>
#include <XCEngine/Input/InputTypes.h>

View File

@@ -1,444 +0,0 @@
#include <gtest/gtest.h>
#include <XCEditor/Foundation/UIEditorTheme.h>
#include <XCEngine/Core/Math/Color.h>
#include <XCEngine/UI/Style/StyleTypes.h>
#include <XCEngine/UI/Style/Theme.h>
namespace {
namespace Math = XCEngine::Math;
namespace Style = XCEngine::UI::Style;
namespace UI = XCEngine::UI;
namespace Editor = XCEngine::UI::Editor;
Style::UITheme BuildEditorFieldTheme() {
Style::UIThemeDefinition definition = {};
definition.SetToken("editor.size.field.row", Style::UIStyleValue(26.0f));
definition.SetToken("editor.space.field.padding_x", Style::UIStyleValue(8.0f));
definition.SetToken("editor.space.field.label_gap", Style::UIStyleValue(12.0f));
definition.SetToken("editor.layout.field.control_column", Style::UIStyleValue(220.0f));
definition.SetToken("editor.size.field.checkbox", Style::UIStyleValue(15.0f));
definition.SetToken("editor.space.field.control_inset_y", Style::UIStyleValue(3.0f));
definition.SetToken("editor.space.field.label_inset_y", Style::UIStyleValue(5.0f));
definition.SetToken("editor.space.field.value_inset_x", Style::UIStyleValue(6.0f));
definition.SetToken("editor.space.field.value_inset_y", Style::UIStyleValue(4.0f));
definition.SetToken("editor.space.field.checkbox_glyph_inset_x", Style::UIStyleValue(1.0f));
definition.SetToken("editor.space.field.checkbox_glyph_inset_y", Style::UIStyleValue(-2.0f));
definition.SetToken("editor.size.field.control_min_width", Style::UIStyleValue(88.0f));
definition.SetToken("editor.space.field.vector_component_gap", Style::UIStyleValue(6.0f));
definition.SetToken("editor.size.field.vector_component_min_width", Style::UIStyleValue(74.0f));
definition.SetToken("editor.size.field.vector_prefix_width", Style::UIStyleValue(18.0f));
definition.SetToken("editor.space.field.control_trailing_inset", Style::UIStyleValue(9.0f));
definition.SetToken("editor.space.field.vector_prefix_gap", Style::UIStyleValue(5.0f));
definition.SetToken("editor.space.field.vector_prefix_inset_x", Style::UIStyleValue(4.0f));
definition.SetToken("editor.space.field.vector_prefix_inset_y", Style::UIStyleValue(5.0f));
definition.SetToken("editor.size.field.dropdown_arrow_width", Style::UIStyleValue(14.0f));
definition.SetToken("editor.space.field.dropdown_arrow_inset_x", Style::UIStyleValue(4.0f));
definition.SetToken("editor.space.field.dropdown_arrow_inset_y", Style::UIStyleValue(4.0f));
definition.SetToken("editor.size.field.color_popup_width", Style::UIStyleValue(320.0f));
definition.SetToken("editor.size.field.color_popup_top_row", Style::UIStyleValue(36.0f));
definition.SetToken("editor.size.field.color_preview_width", Style::UIStyleValue(108.0f));
definition.SetToken("editor.size.field.color_preview_height", Style::UIStyleValue(26.0f));
definition.SetToken("editor.size.field.color_wheel_outer_radius", Style::UIStyleValue(112.0f));
definition.SetToken("editor.size.field.color_wheel_ring_thickness", Style::UIStyleValue(22.0f));
definition.SetToken("editor.size.field.color_sv_square", Style::UIStyleValue(118.0f));
definition.SetToken("editor.size.field.color_wheel_region_height", Style::UIStyleValue(224.0f));
definition.SetToken("editor.size.field.color_channel_row_height", Style::UIStyleValue(21.0f));
definition.SetToken("editor.size.field.color_numeric_box_width", Style::UIStyleValue(66.0f));
definition.SetToken("editor.size.field.color_channel_label_width", Style::UIStyleValue(14.0f));
definition.SetToken("editor.size.field.color_hex_label_width", Style::UIStyleValue(90.0f));
definition.SetToken("editor.space.field.color_control_row_spacing", Style::UIStyleValue(7.0f));
definition.SetToken("editor.space.field.color_popup_field_inset", Style::UIStyleValue(5.0f));
definition.SetToken("editor.color.field.color_popup_surface", Style::UIStyleValue(Math::Color(0.22f, 0.22f, 0.22f, 1.0f)));
definition.SetToken("editor.color.field.color_popup_header", Style::UIStyleValue(Math::Color(0.48f, 0.28f, 0.10f, 1.0f)));
definition.SetToken("editor.color.field.color_popup_close", Style::UIStyleValue(Math::Color(0.74f, 0.34f, 0.32f, 1.0f)));
definition.SetToken("editor.color.field.color_popup_close_hover", Style::UIStyleValue(Math::Color(0.80f, 0.38f, 0.36f, 1.0f)));
definition.SetToken("editor.color.field.color_popup_slider_border", Style::UIStyleValue(Math::Color(0.11f, 0.11f, 0.11f, 1.0f)));
definition.SetToken("editor.color.field.color_popup_numeric_box", Style::UIStyleValue(Math::Color(0.17f, 0.17f, 0.17f, 1.0f)));
definition.SetToken("editor.color.field.color_popup_numeric_box_text", Style::UIStyleValue(Math::Color(0.93f, 0.93f, 0.93f, 1.0f)));
definition.SetToken("editor.color.field.color_popup_handle_outline", Style::UIStyleValue(Math::Color(0.10f, 0.10f, 0.10f, 0.5f)));
definition.SetToken("editor.radius.field.row", Style::UIStyleValue(2.0f));
definition.SetToken("editor.radius.field.control", Style::UIStyleValue(2.0f));
definition.SetToken("editor.border.field", Style::UIStyleValue(1.0f));
definition.SetToken("editor.border.field.focus", Style::UIStyleValue(2.0f));
definition.SetToken("editor.font.field.label", Style::UIStyleValue(11.0f));
definition.SetToken("editor.font.field.value", Style::UIStyleValue(12.0f));
definition.SetToken("editor.font.field.glyph", Style::UIStyleValue(10.0f));
definition.SetToken("editor.space.menu_popup.padding_x", Style::UIStyleValue(6.0f));
definition.SetToken("editor.space.menu_popup.padding_y", Style::UIStyleValue(5.0f));
definition.SetToken("editor.size.menu_popup.item", Style::UIStyleValue(24.0f));
definition.SetToken("editor.size.menu_popup.separator", Style::UIStyleValue(8.0f));
definition.SetToken("editor.size.menu_popup.check_column", Style::UIStyleValue(16.0f));
definition.SetToken("editor.space.menu_popup.shortcut_gap", Style::UIStyleValue(18.0f));
definition.SetToken("editor.size.menu_popup.submenu_indicator", Style::UIStyleValue(12.0f));
definition.SetToken("editor.radius.menu_popup.row", Style::UIStyleValue(3.0f));
definition.SetToken("editor.radius.menu_popup.surface", Style::UIStyleValue(4.0f));
definition.SetToken("editor.space.menu_popup.label_inset_x", Style::UIStyleValue(10.0f));
definition.SetToken("editor.space.menu_popup.label_inset_y", Style::UIStyleValue(-1.0f));
definition.SetToken("editor.font.menu_popup.label", Style::UIStyleValue(11.0f));
definition.SetToken("editor.space.menu_popup.shortcut_inset_right", Style::UIStyleValue(18.0f));
definition.SetToken("editor.size.menu_popup.estimated_glyph_width", Style::UIStyleValue(6.0f));
definition.SetToken("editor.font.menu_popup.glyph", Style::UIStyleValue(10.0f));
definition.SetToken("editor.border.menu_popup.separator", Style::UIStyleValue(1.0f));
definition.SetToken("editor.border.menu_popup.surface", Style::UIStyleValue(1.0f));
definition.SetToken("editor.color.field.row", Style::UIStyleValue(Math::Color(0.16f, 0.16f, 0.16f, 1.0f)));
definition.SetToken("editor.color.field.row_hover", Style::UIStyleValue(Math::Color(0.20f, 0.20f, 0.20f, 1.0f)));
definition.SetToken("editor.color.field.row_active", Style::UIStyleValue(Math::Color(0.24f, 0.24f, 0.24f, 1.0f)));
definition.SetToken("editor.color.field.border", Style::UIStyleValue(Math::Color(0.12f, 0.12f, 0.12f, 1.0f)));
definition.SetToken("editor.color.field.border_focus", Style::UIStyleValue(Math::Color(0.78f, 0.78f, 0.78f, 1.0f)));
definition.SetToken("editor.color.field.label", Style::UIStyleValue(Math::Color(0.84f, 0.84f, 0.84f, 1.0f)));
definition.SetToken("editor.color.field.value", Style::UIStyleValue(Math::Color(0.92f, 0.92f, 0.92f, 1.0f)));
definition.SetToken("editor.color.field.value_readonly", Style::UIStyleValue(Math::Color(0.60f, 0.60f, 0.60f, 1.0f)));
definition.SetToken("editor.color.field.control", Style::UIStyleValue(Math::Color(0.18f, 0.18f, 0.18f, 1.0f)));
definition.SetToken("editor.color.field.control_hover", Style::UIStyleValue(Math::Color(0.21f, 0.21f, 0.21f, 1.0f)));
definition.SetToken("editor.color.field.control_editing", Style::UIStyleValue(Math::Color(0.24f, 0.24f, 0.24f, 1.0f)));
definition.SetToken("editor.color.field.control_readonly", Style::UIStyleValue(Math::Color(0.15f, 0.15f, 0.15f, 1.0f)));
definition.SetToken("editor.color.field.control_border", Style::UIStyleValue(Math::Color(0.30f, 0.30f, 0.30f, 1.0f)));
definition.SetToken("editor.color.field.control_border_focus", Style::UIStyleValue(Math::Color(0.64f, 0.64f, 0.64f, 1.0f)));
definition.SetToken("editor.color.field.vector_prefix", Style::UIStyleValue(Math::Color(0.20f, 0.20f, 0.20f, 1.0f)));
definition.SetToken("editor.color.field.vector_prefix_border", Style::UIStyleValue(Math::Color(0.31f, 0.31f, 0.31f, 1.0f)));
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)));
definition.SetToken("editor.color.field.checkbox_mark", Style::UIStyleValue(Math::Color(0.90f, 0.90f, 0.90f, 1.0f)));
definition.SetToken("editor.color.field.dropdown_arrow", Style::UIStyleValue(Math::Color(0.88f, 0.88f, 0.88f, 1.0f)));
definition.SetToken("editor.color.menu_popup.surface", Style::UIStyleValue(Math::Color(0.15f, 0.15f, 0.15f, 1.0f)));
definition.SetToken("editor.color.menu_popup.border", Style::UIStyleValue(Math::Color(0.32f, 0.32f, 0.32f, 1.0f)));
definition.SetToken("editor.color.menu_popup.item_hover", Style::UIStyleValue(Math::Color(0.24f, 0.24f, 0.24f, 1.0f)));
definition.SetToken("editor.color.menu_popup.item_open", Style::UIStyleValue(Math::Color(0.27f, 0.27f, 0.27f, 1.0f)));
definition.SetToken("editor.color.menu_popup.separator", Style::UIStyleValue(Math::Color(0.35f, 0.35f, 0.35f, 1.0f)));
definition.SetToken("editor.color.menu_popup.label", Style::UIStyleValue(Math::Color(0.91f, 0.91f, 0.91f, 1.0f)));
definition.SetToken("editor.color.menu_popup.shortcut", Style::UIStyleValue(Math::Color(0.76f, 0.76f, 0.76f, 1.0f)));
definition.SetToken("editor.color.menu_popup.text_disabled", Style::UIStyleValue(Math::Color(0.48f, 0.48f, 0.48f, 1.0f)));
definition.SetToken("editor.color.menu_popup.glyph", Style::UIStyleValue(Math::Color(0.86f, 0.86f, 0.86f, 1.0f)));
return Style::BuildTheme(definition);
}
Style::UITheme BuildPropertyGridTheme() {
Style::UIThemeDefinition definition = {};
definition.SetToken("editor.space.property.content_inset", Style::UIStyleValue(6.0f));
definition.SetToken("editor.space.property.section_gap", Style::UIStyleValue(4.0f));
definition.SetToken("editor.size.property.section_header", Style::UIStyleValue(24.0f));
definition.SetToken("editor.size.property.field_row", Style::UIStyleValue(26.0f));
definition.SetToken("editor.space.property.row_gap", Style::UIStyleValue(1.0f));
definition.SetToken("editor.size.property.disclosure", Style::UIStyleValue(10.0f));
definition.SetToken("editor.space.property.disclosure_label_gap", Style::UIStyleValue(6.0f));
definition.SetToken("editor.space.property.section_inset_y", Style::UIStyleValue(4.0f));
definition.SetToken("editor.space.property.disclosure_glyph_inset_x", Style::UIStyleValue(1.0f));
definition.SetToken("editor.space.property.disclosure_glyph_inset_y", Style::UIStyleValue(-1.0f));
definition.SetToken("editor.space.property.label_inset_y", Style::UIStyleValue(5.0f));
definition.SetToken("editor.space.property.value_inset_y", Style::UIStyleValue(4.0f));
definition.SetToken("editor.space.property.value_box_inset_y", Style::UIStyleValue(3.0f));
definition.SetToken("editor.space.property.value_box_inset_x", Style::UIStyleValue(6.0f));
definition.SetToken("editor.radius.property.panel", Style::UIStyleValue(0.0f));
definition.SetToken("editor.radius.property.value", Style::UIStyleValue(2.0f));
definition.SetToken("editor.border.property", Style::UIStyleValue(1.0f));
definition.SetToken("editor.border.property.focus", Style::UIStyleValue(1.0f));
definition.SetToken("editor.border.property.edit", Style::UIStyleValue(1.0f));
definition.SetToken("editor.font.property.section", Style::UIStyleValue(11.0f));
definition.SetToken("editor.font.property.disclosure", Style::UIStyleValue(10.0f));
definition.SetToken("editor.font.property.label", Style::UIStyleValue(11.0f));
definition.SetToken("editor.font.property.value", Style::UIStyleValue(12.0f));
definition.SetToken("editor.font.property.tag", Style::UIStyleValue(10.0f));
definition.SetToken("editor.color.property.surface", Style::UIStyleValue(Math::Color(0.17f, 0.17f, 0.17f, 1.0f)));
definition.SetToken("editor.color.property.border", Style::UIStyleValue(Math::Color(0.10f, 0.10f, 0.10f, 1.0f)));
definition.SetToken("editor.color.property.border_focus", Style::UIStyleValue(Math::Color(0.75f, 0.75f, 0.75f, 1.0f)));
definition.SetToken("editor.color.property.section", Style::UIStyleValue(Math::Color(0.21f, 0.21f, 0.21f, 1.0f)));
definition.SetToken("editor.color.property.section_hover", Style::UIStyleValue(Math::Color(0.24f, 0.24f, 0.24f, 1.0f)));
definition.SetToken("editor.color.property.field_hover", Style::UIStyleValue(Math::Color(0.22f, 0.22f, 0.22f, 1.0f)));
definition.SetToken("editor.color.property.field_selected", Style::UIStyleValue(Math::Color(0.26f, 0.26f, 0.26f, 1.0f)));
definition.SetToken("editor.color.property.field_selected_focused", Style::UIStyleValue(Math::Color(0.30f, 0.30f, 0.30f, 1.0f)));
definition.SetToken("editor.color.property.value", Style::UIStyleValue(Math::Color(0.18f, 0.18f, 0.18f, 1.0f)));
definition.SetToken("editor.color.property.value_hover", Style::UIStyleValue(Math::Color(0.21f, 0.21f, 0.21f, 1.0f)));
definition.SetToken("editor.color.property.value_editing", Style::UIStyleValue(Math::Color(0.23f, 0.23f, 0.23f, 1.0f)));
definition.SetToken("editor.color.property.value_readonly", Style::UIStyleValue(Math::Color(0.15f, 0.15f, 0.15f, 1.0f)));
definition.SetToken("editor.color.property.value_border", Style::UIStyleValue(Math::Color(0.28f, 0.28f, 0.28f, 1.0f)));
definition.SetToken("editor.color.property.value_border_editing", Style::UIStyleValue(Math::Color(0.72f, 0.72f, 0.72f, 1.0f)));
definition.SetToken("editor.color.property.disclosure", Style::UIStyleValue(Math::Color(0.84f, 0.84f, 0.84f, 1.0f)));
definition.SetToken("editor.color.property.section_text", Style::UIStyleValue(Math::Color(0.92f, 0.92f, 0.92f, 1.0f)));
definition.SetToken("editor.color.property.label", Style::UIStyleValue(Math::Color(0.80f, 0.80f, 0.80f, 1.0f)));
definition.SetToken("editor.color.property.value_text", Style::UIStyleValue(Math::Color(0.92f, 0.92f, 0.92f, 1.0f)));
definition.SetToken("editor.color.property.value_text_readonly", Style::UIStyleValue(Math::Color(0.60f, 0.60f, 0.60f, 1.0f)));
definition.SetToken("editor.color.property.edit_tag", Style::UIStyleValue(Math::Color(0.55f, 0.70f, 0.96f, 1.0f)));
definition.SetToken("editor.space.menu.padding_x", Style::UIStyleValue(7.0f));
definition.SetToken("editor.space.menu.padding_y", Style::UIStyleValue(5.0f));
definition.SetToken("editor.size.menu.item", Style::UIStyleValue(24.0f));
definition.SetToken("editor.size.menu.separator", Style::UIStyleValue(8.0f));
definition.SetToken("editor.size.menu.check_column", Style::UIStyleValue(16.0f));
definition.SetToken("editor.space.menu.shortcut_gap", Style::UIStyleValue(18.0f));
definition.SetToken("editor.size.menu.submenu_indicator", Style::UIStyleValue(12.0f));
definition.SetToken("editor.radius.menu.row", Style::UIStyleValue(3.0f));
definition.SetToken("editor.radius.menu.surface", Style::UIStyleValue(6.0f));
definition.SetToken("editor.space.menu.label_inset_x", Style::UIStyleValue(11.0f));
definition.SetToken("editor.space.menu.label_inset_y", Style::UIStyleValue(1.0f));
definition.SetToken("editor.font.menu.label", Style::UIStyleValue(11.0f));
definition.SetToken("editor.space.menu.shortcut_inset_right", Style::UIStyleValue(18.0f));
definition.SetToken("editor.size.menu.glyph_width", Style::UIStyleValue(6.0f));
definition.SetToken("editor.font.menu.glyph", Style::UIStyleValue(10.0f));
definition.SetToken("editor.border.menu.separator", Style::UIStyleValue(1.0f));
definition.SetToken("editor.border.menu.surface", Style::UIStyleValue(1.0f));
definition.SetToken("editor.color.menu.surface", Style::UIStyleValue(Math::Color(0.16f, 0.16f, 0.16f, 1.0f)));
definition.SetToken("editor.color.menu.border", Style::UIStyleValue(Math::Color(0.24f, 0.24f, 0.24f, 1.0f)));
definition.SetToken("editor.color.menu.item_hover", Style::UIStyleValue(Math::Color(0.23f, 0.23f, 0.23f, 1.0f)));
definition.SetToken("editor.color.menu.item_open", Style::UIStyleValue(Math::Color(0.28f, 0.28f, 0.28f, 1.0f)));
definition.SetToken("editor.color.menu.separator", Style::UIStyleValue(Math::Color(0.30f, 0.30f, 0.30f, 1.0f)));
definition.SetToken("editor.color.menu.text", Style::UIStyleValue(Math::Color(0.92f, 0.92f, 0.92f, 1.0f)));
definition.SetToken("editor.color.menu.text_muted", Style::UIStyleValue(Math::Color(0.74f, 0.74f, 0.74f, 1.0f)));
definition.SetToken("editor.color.menu.text_disabled", Style::UIStyleValue(Math::Color(0.47f, 0.47f, 0.47f, 1.0f)));
definition.SetToken("editor.color.menu.glyph", Style::UIStyleValue(Math::Color(0.86f, 0.86f, 0.86f, 1.0f)));
return Style::BuildTheme(definition);
}
TEST(UIEditorThemeTest, FieldResolversReadEditorThemeTokens) {
const Style::UITheme theme = BuildEditorFieldTheme();
const auto boolMetrics = Editor::ResolveUIEditorBoolFieldMetrics(theme);
const auto boolPalette = Editor::ResolveUIEditorBoolFieldPalette(theme);
EXPECT_FLOAT_EQ(boolMetrics.rowHeight, 26.0f);
EXPECT_FLOAT_EQ(boolMetrics.horizontalPadding, 8.0f);
EXPECT_FLOAT_EQ(boolMetrics.controlTrailingInset, 9.0f);
EXPECT_FLOAT_EQ(boolMetrics.checkboxGlyphFontSize, 10.0f);
EXPECT_FLOAT_EQ(boolPalette.rowHoverColor.r, 0.20f);
EXPECT_FLOAT_EQ(boolPalette.checkboxBorderColor.r, 0.33f);
const auto numberMetrics = Editor::ResolveUIEditorNumberFieldMetrics(theme);
const auto numberPalette = Editor::ResolveUIEditorNumberFieldPalette(theme);
EXPECT_FLOAT_EQ(numberMetrics.controlTrailingInset, 9.0f);
EXPECT_FLOAT_EQ(numberMetrics.controlInsetY, 3.0f);
EXPECT_FLOAT_EQ(numberMetrics.valueTextInsetX, 6.0f);
EXPECT_FLOAT_EQ(numberMetrics.valueFontSize, 12.0f);
EXPECT_FLOAT_EQ(numberPalette.valueBoxEditingColor.r, 0.24f);
EXPECT_FLOAT_EQ(numberPalette.controlBorderColor.r, 0.30f);
EXPECT_FLOAT_EQ(numberPalette.controlFocusedBorderColor.r, 0.64f);
const auto textMetrics = Editor::ResolveUIEditorTextFieldMetrics(theme);
const auto textPalette = Editor::ResolveUIEditorTextFieldPalette(theme);
EXPECT_FLOAT_EQ(textMetrics.controlTrailingInset, 9.0f);
EXPECT_FLOAT_EQ(textMetrics.valueBoxMinWidth, 88.0f);
EXPECT_FLOAT_EQ(textMetrics.valueTextInsetY, 4.0f);
EXPECT_FLOAT_EQ(textMetrics.valueFontSize, 12.0f);
EXPECT_FLOAT_EQ(textPalette.valueBoxEditingColor.r, 0.24f);
EXPECT_FLOAT_EQ(textPalette.readOnlyValueColor.r, 0.60f);
EXPECT_FLOAT_EQ(textPalette.controlFocusedBorderColor.r, 0.64f);
const auto vector2Metrics = Editor::ResolveUIEditorVector2FieldMetrics(theme);
const auto vector2Palette = Editor::ResolveUIEditorVector2FieldPalette(theme);
EXPECT_FLOAT_EQ(vector2Metrics.componentGap, 6.0f);
EXPECT_FLOAT_EQ(vector2Metrics.componentPrefixWidth, 18.0f);
EXPECT_FLOAT_EQ(vector2Metrics.controlTrailingInset, 9.0f);
EXPECT_FLOAT_EQ(vector2Metrics.componentLabelGap, 5.0f);
EXPECT_FLOAT_EQ(vector2Metrics.prefixFontSize, 10.0f);
EXPECT_FLOAT_EQ(vector2Palette.componentFocusedBorderColor.r, 0.64f);
EXPECT_FLOAT_EQ(vector2Palette.axisXColor.r, 0.78f);
EXPECT_FLOAT_EQ(vector2Palette.axisYColor.g, 0.72f);
const auto vector3Metrics = Editor::ResolveUIEditorVector3FieldMetrics(theme);
const auto vector3Palette = Editor::ResolveUIEditorVector3FieldPalette(theme);
EXPECT_FLOAT_EQ(vector3Metrics.componentGap, 6.0f);
EXPECT_FLOAT_EQ(vector3Metrics.componentPrefixWidth, 18.0f);
EXPECT_FLOAT_EQ(vector3Metrics.controlTrailingInset, 9.0f);
EXPECT_FLOAT_EQ(vector3Metrics.componentLabelGap, 5.0f);
EXPECT_FLOAT_EQ(vector3Metrics.prefixFontSize, 10.0f);
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);
EXPECT_FLOAT_EQ(enumMetrics.valueBoxMinWidth, 88.0f);
EXPECT_FLOAT_EQ(enumMetrics.dropdownArrowWidth, 14.0f);
EXPECT_FLOAT_EQ(enumMetrics.dropdownArrowFontSize, 10.0f);
EXPECT_FLOAT_EQ(enumPalette.arrowColor.r, 0.88f);
const auto colorMetrics = Editor::ResolveUIEditorColorFieldMetrics(theme);
const auto colorPalette = Editor::ResolveUIEditorColorFieldPalette(theme);
EXPECT_FLOAT_EQ(colorMetrics.popupWidth, 320.0f);
EXPECT_FLOAT_EQ(colorMetrics.popupTopRowHeight, 36.0f);
EXPECT_FLOAT_EQ(colorMetrics.popupPreviewWidth, 108.0f);
EXPECT_FLOAT_EQ(colorMetrics.wheelOuterRadius, 112.0f);
EXPECT_FLOAT_EQ(colorMetrics.wheelRingThickness, 22.0f);
EXPECT_FLOAT_EQ(colorMetrics.saturationValueSize, 118.0f);
EXPECT_FLOAT_EQ(colorMetrics.channelRowHeight, 21.0f);
EXPECT_FLOAT_EQ(colorMetrics.numericBoxWidth, 66.0f);
EXPECT_FLOAT_EQ(colorMetrics.hexLabelWidth, 90.0f);
EXPECT_FLOAT_EQ(colorPalette.popupColor.r, 0.22f);
EXPECT_FLOAT_EQ(colorPalette.popupHeaderColor.r, 0.48f);
EXPECT_FLOAT_EQ(colorPalette.closeButtonColor.r, 0.74f);
EXPECT_FLOAT_EQ(colorPalette.closeButtonHoverColor.r, 0.80f);
EXPECT_FLOAT_EQ(colorPalette.sliderBorderColor.r, 0.11f);
EXPECT_FLOAT_EQ(colorPalette.numericBoxTextColor.r, 0.93f);
EXPECT_FLOAT_EQ(colorPalette.handleStrokeColor.r, 0.10f);
const auto popupMetrics = Editor::ResolveUIEditorMenuPopupMetrics(theme);
const auto popupPalette = Editor::ResolveUIEditorMenuPopupPalette(theme);
EXPECT_FLOAT_EQ(popupMetrics.contentPaddingX, 6.0f);
EXPECT_FLOAT_EQ(popupMetrics.itemHeight, 24.0f);
EXPECT_FLOAT_EQ(popupMetrics.glyphFontSize, 10.0f);
EXPECT_FLOAT_EQ(popupPalette.popupColor.r, 0.15f);
EXPECT_FLOAT_EQ(popupPalette.textPrimary.r, 0.91f);
EXPECT_FLOAT_EQ(popupPalette.textMuted.r, 0.76f);
}
TEST(UIEditorThemeTest, PropertyGridResolversSupportOverridesAndFallbacks) {
const Style::UITheme theme = BuildPropertyGridTheme();
const Style::UITheme fallbackTheme = Style::UITheme();
UI::Editor::Widgets::UIEditorPropertyGridMetrics fallbackMetrics = {};
fallbackMetrics.sectionHeaderHeight = 40.0f;
fallbackMetrics.disclosureGlyphFontSize = 15.0f;
fallbackMetrics.tagFontSize = 13.0f;
const auto fallbackResolvedMetrics =
Editor::ResolveUIEditorPropertyGridMetrics(fallbackTheme, fallbackMetrics);
EXPECT_FLOAT_EQ(fallbackResolvedMetrics.sectionHeaderHeight, 40.0f);
EXPECT_FLOAT_EQ(fallbackResolvedMetrics.disclosureGlyphFontSize, 15.0f);
EXPECT_FLOAT_EQ(fallbackResolvedMetrics.tagFontSize, 13.0f);
UI::Editor::Widgets::UIEditorPropertyGridPalette fallbackPalette = {};
fallbackPalette.surfaceColor = UI::UIColor(0.5f, 0.4f, 0.3f, 1.0f);
const auto fallbackResolvedPalette =
Editor::ResolveUIEditorPropertyGridPalette(fallbackTheme, fallbackPalette);
EXPECT_FLOAT_EQ(fallbackResolvedPalette.surfaceColor.r, 0.5f);
const auto themedMetrics = Editor::ResolveUIEditorPropertyGridMetrics(theme);
const auto themedPalette = Editor::ResolveUIEditorPropertyGridPalette(theme);
const auto popupMetrics = Editor::ResolveUIEditorMenuPopupMetrics(theme);
const auto popupPalette = Editor::ResolveUIEditorMenuPopupPalette(theme);
EXPECT_FLOAT_EQ(themedMetrics.contentInset, 6.0f);
EXPECT_FLOAT_EQ(themedMetrics.sectionHeaderHeight, 24.0f);
EXPECT_FLOAT_EQ(themedMetrics.disclosureGlyphFontSize, 10.0f);
EXPECT_FLOAT_EQ(themedMetrics.tagFontSize, 10.0f);
EXPECT_FLOAT_EQ(themedPalette.sectionHeaderColor.r, 0.21f);
EXPECT_FLOAT_EQ(themedPalette.valueBoxEditingBorderColor.r, 0.72f);
EXPECT_FLOAT_EQ(themedPalette.valueTextColor.r, 0.92f);
EXPECT_FLOAT_EQ(themedPalette.readOnlyValueTextColor.r, 0.60f);
EXPECT_FLOAT_EQ(popupMetrics.contentPaddingX, 7.0f);
EXPECT_FLOAT_EQ(popupMetrics.itemHeight, 24.0f);
EXPECT_FLOAT_EQ(popupMetrics.popupCornerRounding, 6.0f);
EXPECT_FLOAT_EQ(popupMetrics.glyphFontSize, 10.0f);
EXPECT_FLOAT_EQ(popupPalette.popupColor.r, 0.16f);
EXPECT_FLOAT_EQ(popupPalette.itemHoverColor.r, 0.23f);
EXPECT_FLOAT_EQ(popupPalette.glyphColor.r, 0.86f);
}
TEST(UIEditorThemeTest, HostedFieldBuildersInheritPropertyGridMetricsAndPalette) {
UI::Editor::Widgets::UIEditorPropertyGridMetrics propertyMetrics = {};
propertyMetrics.fieldRowHeight = 25.0f;
propertyMetrics.horizontalPadding = 7.0f;
propertyMetrics.labelControlGap = 11.0f;
propertyMetrics.controlColumnStart = 210.0f;
propertyMetrics.labelTextInsetY = 4.0f;
propertyMetrics.labelFontSize = 10.0f;
propertyMetrics.valueTextInsetY = 3.0f;
propertyMetrics.valueFontSize = 12.0f;
propertyMetrics.valueBoxInsetY = 2.0f;
propertyMetrics.valueBoxInsetX = 5.0f;
propertyMetrics.cornerRounding = 1.0f;
propertyMetrics.valueBoxRounding = 2.0f;
propertyMetrics.borderThickness = 1.0f;
propertyMetrics.focusedBorderThickness = 2.0f;
UI::Editor::Widgets::UIEditorPropertyGridPalette propertyPalette = {};
propertyPalette.valueBoxColor = UI::UIColor(0.2f, 0.2f, 0.2f, 1.0f);
propertyPalette.valueBoxHoverColor = UI::UIColor(0.3f, 0.3f, 0.3f, 1.0f);
propertyPalette.valueBoxEditingColor = UI::UIColor(0.4f, 0.4f, 0.4f, 1.0f);
propertyPalette.valueBoxReadOnlyColor = UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f);
propertyPalette.valueBoxBorderColor = UI::UIColor(0.5f, 0.5f, 0.5f, 1.0f);
propertyPalette.valueBoxEditingBorderColor = UI::UIColor(0.55f, 0.55f, 0.55f, 1.0f);
propertyPalette.labelTextColor = UI::UIColor(0.8f, 0.8f, 0.8f, 1.0f);
propertyPalette.valueTextColor = UI::UIColor(0.9f, 0.9f, 0.9f, 1.0f);
propertyPalette.readOnlyValueTextColor = UI::UIColor(0.6f, 0.6f, 0.6f, 1.0f);
const auto boolMetrics = Editor::BuildUIEditorHostedBoolFieldMetrics(propertyMetrics);
const auto boolPalette = Editor::BuildUIEditorHostedBoolFieldPalette(propertyPalette);
EXPECT_FLOAT_EQ(boolMetrics.rowHeight, 25.0f);
EXPECT_FLOAT_EQ(boolMetrics.controlTrailingInset, 5.0f);
EXPECT_FLOAT_EQ(boolMetrics.labelFontSize, 10.0f);
EXPECT_FLOAT_EQ(boolMetrics.checkboxGlyphFontSize, 12.0f);
EXPECT_FLOAT_EQ(boolPalette.checkboxColor.r, 0.2f);
EXPECT_FLOAT_EQ(boolPalette.labelColor.r, 0.8f);
const auto numberMetrics = Editor::BuildUIEditorHostedNumberFieldMetrics(propertyMetrics);
const auto numberPalette = Editor::BuildUIEditorHostedNumberFieldPalette(propertyPalette);
EXPECT_FLOAT_EQ(numberMetrics.controlTrailingInset, 5.0f);
EXPECT_FLOAT_EQ(numberMetrics.controlInsetY, 2.0f);
EXPECT_FLOAT_EQ(numberMetrics.valueTextInsetX, 5.0f);
EXPECT_FLOAT_EQ(numberMetrics.valueFontSize, 12.0f);
EXPECT_FLOAT_EQ(numberPalette.valueBoxEditingColor.r, 0.4f);
EXPECT_FLOAT_EQ(numberPalette.readOnlyValueColor.r, 0.6f);
EXPECT_FLOAT_EQ(numberPalette.controlFocusedBorderColor.r, 0.55f);
const auto textMetrics = Editor::BuildUIEditorHostedTextFieldMetrics(propertyMetrics);
const auto textPalette = Editor::BuildUIEditorHostedTextFieldPalette(propertyPalette);
EXPECT_FLOAT_EQ(textMetrics.controlTrailingInset, 5.0f);
EXPECT_FLOAT_EQ(textMetrics.controlInsetY, 2.0f);
EXPECT_FLOAT_EQ(textMetrics.valueTextInsetX, 5.0f);
EXPECT_FLOAT_EQ(textMetrics.valueFontSize, 12.0f);
EXPECT_FLOAT_EQ(textPalette.valueBoxEditingColor.r, 0.4f);
EXPECT_FLOAT_EQ(textPalette.readOnlyValueColor.r, 0.6f);
EXPECT_FLOAT_EQ(textPalette.controlFocusedBorderColor.r, 0.55f);
const auto vector2Metrics = Editor::BuildUIEditorHostedVector2FieldMetrics(propertyMetrics);
const auto vector2Palette = Editor::BuildUIEditorHostedVector2FieldPalette(propertyPalette);
EXPECT_FLOAT_EQ(vector2Metrics.controlInsetY, 2.0f);
EXPECT_FLOAT_EQ(vector2Metrics.controlTrailingInset, 5.0f);
EXPECT_FLOAT_EQ(vector2Metrics.valueTextInsetX, 5.0f);
EXPECT_FLOAT_EQ(vector2Metrics.componentRounding, 2.0f);
EXPECT_FLOAT_EQ(vector2Palette.componentEditingColor.r, 0.4f);
EXPECT_FLOAT_EQ(vector2Palette.componentFocusedBorderColor.r, 0.55f);
const auto vector3Metrics = Editor::BuildUIEditorHostedVector3FieldMetrics(propertyMetrics);
const auto vector3Palette = Editor::BuildUIEditorHostedVector3FieldPalette(propertyPalette);
EXPECT_FLOAT_EQ(vector3Metrics.controlInsetY, 2.0f);
EXPECT_FLOAT_EQ(vector3Metrics.controlTrailingInset, 5.0f);
EXPECT_FLOAT_EQ(vector3Metrics.valueTextInsetX, 5.0f);
EXPECT_FLOAT_EQ(vector3Metrics.componentRounding, 2.0f);
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);
EXPECT_FLOAT_EQ(enumMetrics.controlInsetY, 2.0f);
EXPECT_FLOAT_EQ(enumMetrics.valueTextInsetX, 5.0f);
EXPECT_FLOAT_EQ(enumMetrics.dropdownArrowFontSize, 12.0f);
EXPECT_FLOAT_EQ(enumPalette.arrowColor.r, 0.9f);
const auto colorMetrics = Editor::BuildUIEditorHostedColorFieldMetrics(propertyMetrics);
const auto colorPalette = Editor::BuildUIEditorHostedColorFieldPalette(propertyPalette);
EXPECT_FLOAT_EQ(colorMetrics.controlTrailingInset, 5.0f);
EXPECT_FLOAT_EQ(colorMetrics.swatchInsetY, 2.0f);
EXPECT_FLOAT_EQ(colorMetrics.labelFontSize, 10.0f);
EXPECT_FLOAT_EQ(colorMetrics.valueFontSize, 12.0f);
EXPECT_FLOAT_EQ(colorMetrics.popupHeaderHeight, 30.0f);
EXPECT_FLOAT_EQ(colorPalette.labelColor.r, 0.8f);
EXPECT_FLOAT_EQ(colorPalette.popupBorderColor.r, 0.15f);
EXPECT_FLOAT_EQ(colorPalette.popupHeaderColor.r, 0.43f);
EXPECT_FLOAT_EQ(colorPalette.swatchBorderColor.r, 0.5f);
}
} // namespace

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Widgets/UIEditorVector2Field.h>
#include <XCEditor/Fields/UIEditorVector2Field.h>
namespace {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorVector2FieldInteraction.h>
#include <XCEditor/Fields/UIEditorVector2FieldInteraction.h>
#include <XCEngine/Input/InputTypes.h>

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Widgets/UIEditorVector3Field.h>
#include <XCEditor/Fields/UIEditorVector3Field.h>
namespace {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorVector3FieldInteraction.h>
#include <XCEditor/Fields/UIEditorVector3FieldInteraction.h>
#include <XCEngine/Input/InputTypes.h>

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Widgets/UIEditorVector4Field.h>
#include <XCEditor/Fields/UIEditorVector4Field.h>
namespace {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorVector4FieldInteraction.h>
#include <XCEditor/Fields/UIEditorVector4FieldInteraction.h>
#include <XCEngine/Input/InputTypes.h>

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorViewportInputBridge.h>
#include <XCEditor/Shell/UIEditorViewportInputBridge.h>
namespace {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorViewportShell.h>
#include <XCEditor/Shell/UIEditorViewportShell.h>
namespace {

View File

@@ -1,7 +1,7 @@
#include <gtest/gtest.h>
#include <XCEngine/UI/DrawData.h>
#include <XCEditor/Widgets/UIEditorViewportSlot.h>
#include <XCEditor/Shell/UIEditorViewportSlot.h>
namespace {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorWorkspaceCompose.h>
#include <XCEditor/Shell/UIEditorWorkspaceCompose.h>
namespace {

View File

@@ -1,6 +1,6 @@
#include <gtest/gtest.h>
#include <XCEditor/Core/UIEditorWorkspaceController.h>
#include <XCEditor/Shell/UIEditorWorkspaceController.h>
namespace {

Some files were not shown because too many files have changed in this diff Show More