Refactor XCUI editor module layout
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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/`.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
} };
|
||||
|
||||
@@ -16,7 +16,6 @@ struct EditorValidationScenario {
|
||||
std::string categoryId = {};
|
||||
std::string displayName = {};
|
||||
std::filesystem::path documentPath = {};
|
||||
std::filesystem::path themePath = {};
|
||||
std::filesystem::path captureRootPath = {};
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
736
tests/UI/Editor/integration/shell/asset_field_basic/main.cpp
Normal file
736
tests/UI/Editor/integration/shell/asset_field_basic/main.cpp
Normal 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 / result;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,
|
||||
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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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。");
|
||||
"重点看 hover、popup、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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 popup,hover 应直接弹出。";
|
||||
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), "建议操作:点击 File,hover `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), "建议操作:点击 File,hover `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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 只切换 selection;hover、selected、focused 三种状态要能区分。",
|
||||
"2. 点击 row 只切换 selection;hover、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,
|
||||
|
||||
@@ -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 切换选中项后按 F2;F12 或按钮触发截图。",
|
||||
"6. 需要时先按 Esc 退出,再用 Up/Down/Home/End 切换选中项后按 F2;F12 可截图。",
|
||||
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);
|
||||
|
||||
|
||||
@@ -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 多选 contract:Ctrl/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 = {};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 commit,Esc cancel。",
|
||||
"3. 按 Enter 进入编辑态,直接输入字符;Enter commit,Esc 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
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
736
tests/UI/Editor/integration/shell/object_field_basic/main.cpp
Normal file
736
tests/UI/Editor/integration/shell/object_field_basic/main.cpp
Normal 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);
|
||||
}
|
||||
@@ -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 ||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 / End;Reset 恢复;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);
|
||||
"状态摘要",
|
||||
"持续显示 hover、focus、active 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 后,应自动回退到相邻 tab;Document C 无法关闭。",
|
||||
"可点击 Document B 切换,或点击 Document C 验证不可关闭 tab。",
|
||||
kTextWeak,
|
||||
12.0f);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:只切换 selection,hover / selected / focused 必须能明确区分。",
|
||||
@@ -708,7 +708,7 @@ private:
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f),
|
||||
"5. 点击树外空白清除 focus;F12 手动截图;XCUI_AUTO_CAPTURE_ON_STARTUP=1 自动截图。",
|
||||
"5. 点击树外空白清除 focus;F12 手动截图;XCUI_AUTO_CAPTURE_ON_STARTUP=1 可启动自动截图。",
|
||||
kTextPrimary,
|
||||
12.0f);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 commit,Escape 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
|
||||
|
||||
@@ -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 commit,Escape 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
|
||||
|
||||
@@ -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 切换 component;Up/Down/Home/End 检查 step 与边界。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), "3. Enter 开始编辑;直接输入字符也应开始编辑;Enter commit,Escape cancel。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), "4. F6 模拟 FocusLost;F12 截图;R 重置当前测试。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 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 切换 component;Up / Down / Home / End 检查 step 与边界。", shellPalette.textPrimary, shellMetrics.bodyFontSize);
|
||||
drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), "3. 按 Enter 开始编辑;直接输入字符也应开始编辑;Enter 提交,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
|
||||
|
||||
@@ -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 Surface,click 获取 Focus,按住拖出再松开,检查 Capture。",
|
||||
"4. 验证 hover Surface 与 click 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,
|
||||
|
||||
@@ -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),
|
||||
"检查1:TopBar / Surface / 状态条布局。",
|
||||
"1. 验证 TopBar / Surface / BottomBar 的区域划分。",
|
||||
kTextMuted,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 88.0f),
|
||||
"检查2:hover / focus / active / capture。",
|
||||
"2. 验证 hover / focus / active / capture 状态。",
|
||||
kTextMuted,
|
||||
12.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 110.0f),
|
||||
"操作1:hover 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),
|
||||
"操作2:click 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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 基础壳层组合"
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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-b,visible 应变成 `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。");
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 应是 Changed;active 从 doc-a 切到 doc-b,selected tab 也同步到 B。", kTextPrimary, 13.0f);
|
||||
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 70.0f), "1. 点 `1 Hide Active`,Result 应是 Changed,active 从 doc-a 切到 doc-b,selected tab 也同步到 B。", kTextPrimary, 13.0f);
|
||||
drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 92.0f), "2. 再点 `1 Hide Active`,如果当前 panel 已 hidden,Result 应变成 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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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),
|
||||
"检查1:hover / focus / capture / local 坐标。",
|
||||
"检查:hover / focus / capture / local 坐标。",
|
||||
kTextMuted,
|
||||
11.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 86.0f),
|
||||
"检查2:surface 内左键按下后,Focus + Capture 进入。",
|
||||
"检查:surface 内左键按下后,Focus + Capture 进入。",
|
||||
kTextMuted,
|
||||
11.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(m_introRect.x + 16.0f, m_introRect.y + 106.0f),
|
||||
"检查3:drag 出 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),
|
||||
"检查4:release 后,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),
|
||||
"操作:hover,click,drag,wheel,A/W/F/Space,输入字符。",
|
||||
"操作:hover、click、drag、wheel、A/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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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));
|
||||
|
||||
123
tests/UI/Editor/unit/test_ui_editor_asset_field.cpp
Normal file
123
tests/UI/Editor/unit/test_ui_editor_asset_field.cpp
Normal 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
|
||||
189
tests/UI/Editor/unit/test_ui_editor_asset_field_interaction.cpp
Normal file
189
tests/UI/Editor/unit/test_ui_editor_asset_field_interaction.cpp
Normal 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");
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
#include <XCEditor/Widgets/UIEditorBoolField.h>
|
||||
#include <XCEditor/Fields/UIEditorBoolField.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Core/UIEditorBoolFieldInteraction.h>
|
||||
#include <XCEditor/Fields/UIEditorBoolFieldInteraction.h>
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
#include <XCEditor/Widgets/UIEditorColorField.h>
|
||||
#include <XCEditor/Fields/UIEditorColorField.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Core/UIEditorColorFieldInteraction.h>
|
||||
#include <XCEditor/Fields/UIEditorColorFieldInteraction.h>
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Core/UIEditorEnumFieldInteraction.h>
|
||||
#include <XCEditor/Fields/UIEditorEnumFieldInteraction.h>
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
|
||||
|
||||
156
tests/UI/Editor/unit/test_ui_editor_hosted_field_builders.cpp
Normal file
156
tests/UI/Editor/unit/test_ui_editor_hosted_field_builders.cpp
Normal 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
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
#include <XCEditor/Widgets/UIEditorMenuBar.h>
|
||||
#include <XCEditor/Shell/UIEditorMenuBar.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
#include <XCEditor/Widgets/UIEditorMenuPopup.h>
|
||||
#include <XCEditor/Shell/UIEditorMenuPopup.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Core/UIEditorMenuSession.h>
|
||||
#include <XCEditor/Shell/UIEditorMenuSession.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Widgets/UIEditorNumberField.h>
|
||||
#include <XCEditor/Fields/UIEditorNumberField.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Core/UIEditorNumberFieldInteraction.h>
|
||||
#include <XCEditor/Fields/UIEditorNumberFieldInteraction.h>
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
|
||||
|
||||
122
tests/UI/Editor/unit/test_ui_editor_object_field.cpp
Normal file
122
tests/UI/Editor/unit/test_ui_editor_object_field.cpp
Normal 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
|
||||
157
tests/UI/Editor/unit/test_ui_editor_object_field_interaction.cpp
Normal file
157
tests/UI/Editor/unit/test_ui_editor_object_field_interaction.cpp
Normal 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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
#include <XCEditor/Widgets/UIEditorPanelFrame.h>
|
||||
#include <XCEditor/Shell/UIEditorPanelFrame.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Core/UIEditorPanelHostLifecycle.h>
|
||||
#include <XCEditor/Shell/UIEditorPanelHostLifecycle.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Core/UIEditorPanelRegistry.h>
|
||||
#include <XCEditor/Shell/UIEditorPanelRegistry.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
#include <XCEditor/Widgets/UIEditorScrollView.h>
|
||||
#include <XCEditor/Collections/UIEditorScrollView.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Core/UIEditorScrollViewInteraction.h>
|
||||
#include <XCEditor/Collections/UIEditorScrollViewInteraction.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
#include <XCEditor/Widgets/UIEditorStatusBar.h>
|
||||
#include <XCEditor/Shell/UIEditorStatusBar.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Widgets/UIEditorTextField.h>
|
||||
#include <XCEditor/Fields/UIEditorTextField.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Core/UIEditorTextFieldInteraction.h>
|
||||
#include <XCEditor/Fields/UIEditorTextFieldInteraction.h>
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Widgets/UIEditorVector2Field.h>
|
||||
#include <XCEditor/Fields/UIEditorVector2Field.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Core/UIEditorVector2FieldInteraction.h>
|
||||
#include <XCEditor/Fields/UIEditorVector2FieldInteraction.h>
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Widgets/UIEditorVector3Field.h>
|
||||
#include <XCEditor/Fields/UIEditorVector3Field.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Core/UIEditorVector3FieldInteraction.h>
|
||||
#include <XCEditor/Fields/UIEditorVector3FieldInteraction.h>
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Widgets/UIEditorVector4Field.h>
|
||||
#include <XCEditor/Fields/UIEditorVector4Field.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Core/UIEditorVector4FieldInteraction.h>
|
||||
#include <XCEditor/Fields/UIEditorVector4FieldInteraction.h>
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Core/UIEditorViewportInputBridge.h>
|
||||
#include <XCEditor/Shell/UIEditorViewportInputBridge.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Core/UIEditorViewportShell.h>
|
||||
#include <XCEditor/Shell/UIEditorViewportShell.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
#include <XCEditor/Widgets/UIEditorViewportSlot.h>
|
||||
#include <XCEditor/Shell/UIEditorViewportSlot.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Core/UIEditorWorkspaceCompose.h>
|
||||
#include <XCEditor/Shell/UIEditorWorkspaceCompose.h>
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user