From 93f06e84ed3f56ad215024a79fd2cd008f54adaa Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sun, 19 Apr 2026 15:52:28 +0800 Subject: [PATCH] Refactor new editor boundaries and test ownership --- ...spector面板与组件系统收口计划_阶段归档_2026-04-19.md} | 0 ...具域与TransformGizmo计划_阶段归档_2026-04-19.md} | 0 ...r_UI模块三层与编辑器实现审核方向_阶段归档_2026-04-19.md} | 0 ...向平台宿主隔离子计划_阶段归档_2026-04-19.md | 383 ++++++++ ...九方向测试体系子计划_阶段归档_2026-04-19.md | 64 ++ ...itor_UI第二阶段依赖方向子计划_阶段归档_2026-04-19.md} | 0 ...输入与交互一致性子计划_阶段归档_2026-04-19.md | 113 +++ ...文件夹结构与命名子计划_阶段归档_2026-04-19.md | 92 ++ engine/include/XCEngine/Input/InputAxis.h | 3 +- engine/include/XCEngine/Input/InputEvent.h | 3 +- engine/include/XCEngine/Input/InputManager.h | 10 +- engine/include/XCEngine/Input/InputTypes.h | 3 +- new_editor/CMakeLists.txt | 42 +- new_editor/app/Bootstrap/Application.h | 28 +- .../app/Bootstrap/ApplicationBootstrap.cpp | 38 +- .../app/Bootstrap/ApplicationLifecycle.cpp | 53 +- .../app/Bootstrap/ApplicationRunLoop.cpp | 23 - .../app/Bootstrap/ApplicationWindowClass.cpp | 13 +- .../Commands}/EditorHostCommandBridge.cpp | 0 .../{State => Composition}/EditorContext.cpp | 24 +- .../{State => Composition}/EditorContext.h | 9 +- .../EditorContextStatus.cpp | 0 .../app/Composition/EditorShellRuntime.cpp | 16 +- .../app/Composition/EditorShellRuntime.h | 18 +- .../EditorShellRuntimeRendering.cpp | 2 +- .../Composition/EditorShellRuntimeUpdate.cpp | 54 +- .../EditorShellRuntimeViewport.cpp | 8 +- .../EditorWindowWorkspaceStore.cpp | 2 +- .../EditorWindowWorkspaceStore.h | 0 .../app/Composition/WorkspaceEventSync.cpp | 2 +- .../app/Features/Hierarchy/HierarchyPanel.cpp | 31 +- .../app/Features/Hierarchy/HierarchyPanel.h | 7 +- .../app/Features/Inspector/InspectorPanel.cpp | 23 +- .../app/Features/Inspector/InspectorPanel.h | 4 +- new_editor/app/Features/PanelInputContext.h | 12 + .../Features/Project/ProjectBrowserModel.cpp | 8 +- .../Project/ProjectBrowserModelInternal.cpp | 36 +- .../Project/ProjectBrowserModelInternal.h | 1 + .../app/Features/Project/ProjectPanel.cpp | 183 ++-- .../app/Features/Project/ProjectPanel.h | 23 +- .../Scene/SceneViewportController.cpp | 66 +- .../Features/Scene/SceneViewportController.h | 18 +- .../Features/Scene/SceneViewportFeature.cpp | 60 ++ .../app/Features/Scene/SceneViewportFeature.h | 52 ++ .../Scene/SceneViewportToolOverlay.cpp | 6 +- .../Features/Scene/SceneViewportToolOverlay.h | 6 +- ...mo.cpp => SceneViewportTransformGizmo.cpp} | 128 +-- ...tGizmo.h => SceneViewportTransformGizmo.h} | 26 +- .../SceneViewportTransformGizmoSupport.cpp} | 6 +- .../SceneViewportTransformGizmoSupport.h} | 4 +- new_editor/app/Host/HostFwd.h | 12 - new_editor/app/Host/ViewportRenderHost.h | 14 - new_editor/app/Internal/EmbeddedPngLoader.cpp | 4 +- new_editor/app/Internal/EmbeddedPngLoader.h | 4 +- new_editor/app/Platform/Win32/EditorWindow.h | 34 +- .../Win32/EditorWindowBorderlessPlacement.cpp | 119 +-- .../Win32/EditorWindowBorderlessResize.cpp | 135 +-- .../Win32/EditorWindowChromeController.cpp | 861 ++++++++++++++++++ .../Win32/EditorWindowChromeController.h | 147 +++ .../app/Platform/Win32/EditorWindowFrame.cpp | 244 +---- .../Win32/EditorWindowFrameOrchestrator.cpp | 208 +++++ .../Win32/EditorWindowFrameOrchestrator.h | 79 ++ .../app/Platform/Win32/EditorWindowInput.cpp | 196 +--- .../Win32/EditorWindowInputController.cpp | 279 ++++++ .../Win32/EditorWindowInputController.h | 78 ++ .../Win32/EditorWindowInternalState.h | 47 - .../Platform/Win32/EditorWindowLifecycle.cpp | 188 ++-- .../app/Platform/Win32/EditorWindowManager.h | 16 +- .../Win32/EditorWindowRuntimeController.cpp | 226 +++++ .../Win32/EditorWindowRuntimeController.h | 105 +++ .../Win32/EditorWindowTitleBarInteraction.cpp | 222 +---- .../Win32/EditorWindowTitleBarRendering.cpp | 192 +--- .../Win32/Win32SystemInteractionHost.cpp | 104 +++ .../Win32/Win32SystemInteractionHost.h | 15 + .../EditorWindowMessageDispatcher.cpp} | 122 +-- .../EditorWindowMessageDispatcher.h} | 18 +- .../Platform/Win32/WindowManager/Internal.h | 2 +- .../Win32/WindowManager/Lifecycle.cpp | 64 +- .../Platform/Win32/WindowManager/TabDrag.cpp | 122 +-- .../Win32/WindowManager/TabDragDropTarget.cpp | 116 +++ .../Win32/WindowManager/TabDragDropTarget.h | 23 + .../Win32/WindowManager/WindowSync.cpp | 2 +- .../app/Platform/Win32/WindowMessageHost.h | 37 - new_editor/app/Ports/PortFwd.h | 10 + .../ShaderResourceDescriptorAllocatorPort.h} | 8 +- new_editor/app/Ports/SystemInteractionPort.h | 18 + .../TextureHost.h => Ports/TexturePort.h} | 8 +- new_editor/app/Ports/ViewportRenderPort.h | 23 + .../app/Rendering/Assets/BuiltInIcons.cpp | 6 +- .../app/Rendering/Assets/BuiltInIcons.h | 6 +- .../D3D12ShaderResourceDescriptorAllocator.h | 5 +- .../Rendering/D3D12/D3D12WindowRenderer.cpp | 60 ++ .../app/Rendering/D3D12/D3D12WindowRenderer.h | 15 +- .../app/Rendering/Native/NativeRenderer.h | 4 +- .../Viewport/SceneViewportRenderPlan.h | 2 +- .../Viewport/SceneViewportRenderRequest.h | 27 + .../Viewport/SceneViewportRenderService.cpp | 178 ++++ .../Viewport/SceneViewportRenderService.h | 59 ++ .../Viewport/ViewportContentRenderer.h | 32 + .../Viewport/ViewportHostService.cpp | 274 ++---- .../Rendering/Viewport/ViewportHostService.h | 61 +- .../Viewport/ViewportObjectPickerService.h | 16 + .../Viewport/ViewportRenderTargetInternal.cpp | 35 +- .../Viewport/ViewportRenderTargetInternal.h | 3 +- .../Viewport/ViewportRenderTargets.cpp | 64 +- .../Viewport/ViewportRenderTargets.h | 18 +- .../app/Rendering/Viewport/ViewportTypes.h | 8 +- new_editor/app/Scene/EditorSceneRuntime.h | 13 +- .../{src/App => app/State}/EditorSession.cpp | 0 .../Collections/UIEditorTreePanelBehavior.h | 8 +- .../Foundation/UIEditorPanelInputFilter.h | 39 + .../XCEditor/Shell/UIEditorStructuredShell.h | 22 +- .../Viewport/UIEditorViewportInputBridge.h | 17 +- .../XCEditor/Viewport/UIEditorViewportShell.h | 3 +- .../Workspace/UIEditorWorkspaceCompose.h | 4 +- .../Workspace/UIEditorWorkspaceInputOwner.h | 54 ++ .../Workspace/UIEditorWorkspaceInteraction.h | 5 + .../Collections/UIEditorTreePanelBehavior.cpp | 20 +- .../src/Shell/UIEditorStructuredShell.cpp | 25 + .../Viewport/UIEditorViewportInputBridge.cpp | 39 +- .../src/Viewport/UIEditorViewportShell.cpp | 6 +- .../Workspace/UIEditorWorkspaceCompose.cpp | 12 +- .../Workspace/UIEditorWorkspaceInputOwner.cpp | 143 +++ .../UIEditorWorkspaceInteraction.cpp | 70 +- tests/UI/Editor/CMakeLists.txt | 18 +- .../CMakeLists.txt | 70 +- .../README.md | 12 +- .../shared/CMakeLists.txt | 1 + .../shared/src/Application.cpp | 0 .../shared/src/Application.h | 0 .../shared/src/EditorValidationScenario.cpp | 6 +- .../shared/src/EditorValidationScenario.h | 0 .../shared/src/EditorValidationTheme.h | 0 .../shell/CMakeLists.txt | 0 .../shell/asset_field_basic/CMakeLists.txt | 0 .../shell/asset_field_basic/captures/.gitkeep | 0 .../shell/asset_field_basic/main.cpp | 34 +- .../shell/bool_field_basic/CMakeLists.txt | 0 .../shell/bool_field_basic/captures/.gitkeep | 0 .../shell/bool_field_basic/main.cpp | 36 +- .../shell/color_field_basic/CMakeLists.txt | 0 .../shell/color_field_basic/captures/.gitkeep | 0 .../history/20260408_160549_1_startup.png | Bin .../color_field_basic/captures/latest.png | Bin .../shell/color_field_basic/main.cpp | 34 +- .../shell/context_menu_basic/CMakeLists.txt | 0 .../shell/context_menu_basic/main.cpp | 66 +- .../shell/dock_host_basic/CMakeLists.txt | 0 .../shell/dock_host_basic/captures/.gitkeep | 0 .../history/20260408_190049_1_startup.png | Bin .../shell/dock_host_basic/captures/latest.png | Bin .../shell/dock_host_basic/main.cpp | 44 +- .../shell/editor_shell_compose/CMakeLists.txt | 0 .../editor_shell_compose/captures/.gitkeep | 0 .../shell/editor_shell_compose/main.cpp | 4 +- .../editor_shell_interaction/CMakeLists.txt | 0 .../captures/.gitkeep | 0 .../shell/editor_shell_interaction/main.cpp | 60 +- .../shell/enum_field_basic/CMakeLists.txt | 0 .../shell/enum_field_basic/captures/.gitkeep | 0 .../shell/enum_field_basic/main.cpp | 36 +- .../shell/list_view_basic/CMakeLists.txt | 0 .../shell/list_view_basic/captures/.gitkeep | 0 .../shell/list_view_basic/main.cpp | 46 +- .../list_view_inline_rename/CMakeLists.txt | 0 .../history/20260409_011602_1_startup.png | Bin .../history/20260409_011741_1_startup.png | Bin .../history/20260409_011820_1_startup.png | Bin .../history/20260409_012145_1_startup.png | Bin .../history/20260409_012151_1_startup.png | Bin .../captures/latest.png | Bin .../shell/list_view_inline_rename/main.cpp | 46 +- .../list_view_multiselect/CMakeLists.txt | 0 .../history/20260409_001451_1_startup.png | Bin .../list_view_multiselect/captures/latest.png | Bin .../shell/list_view_multiselect/main.cpp | 26 +- .../shell/menu_bar_basic/CMakeLists.txt | 0 .../shell/menu_bar_basic/captures/.gitkeep | 0 .../shell/menu_bar_basic/main.cpp | 108 +-- .../shell/number_field_basic/CMakeLists.txt | 0 .../number_field_basic/captures/.gitkeep | 0 .../shell/number_field_basic/main.cpp | 44 +- .../shell/object_field_basic/CMakeLists.txt | 0 .../object_field_basic/captures/.gitkeep | 0 .../shell/object_field_basic/main.cpp | 44 +- .../panel_content_host_basic/CMakeLists.txt | 0 .../captures/.gitkeep | 0 .../shell/panel_content_host_basic/main.cpp | 26 +- .../shell/panel_frame_basic/CMakeLists.txt | 0 .../shell/panel_frame_basic/captures/.gitkeep | 0 .../shell/panel_frame_basic/main.cpp | 14 +- .../shell/property_grid_basic/CMakeLists.txt | 0 .../property_grid_basic/captures/.gitkeep | 0 .../shell/property_grid_basic/main.cpp | 46 +- .../shell/scroll_view_basic/CMakeLists.txt | 0 .../shell/scroll_view_basic/captures/.gitkeep | 0 .../shell/scroll_view_basic/main.cpp | 48 +- .../shell/status_bar_basic/CMakeLists.txt | 0 .../shell/status_bar_basic/main.cpp | 26 +- .../shell/tab_strip_basic/CMakeLists.txt | 0 .../history/20260408_182141_1_startup.png | Bin .../history/20260408_190049_1_startup.png | Bin .../shell/tab_strip_basic/captures/latest.png | Bin .../shell/tab_strip_basic/main.cpp | 30 +- .../shell/text_field_basic/CMakeLists.txt | 0 .../shell/text_field_basic/captures/.gitkeep | 0 .../shell/text_field_basic/main.cpp | 47 +- .../shell/tree_view_basic/CMakeLists.txt | 0 .../shell/tree_view_basic/captures/.gitkeep | 0 .../history/20260408_174622_1_startup.png | Bin .../shell/tree_view_basic/captures/latest.png | Bin .../shell/tree_view_basic/main.cpp | 34 +- .../tree_view_inline_rename/CMakeLists.txt | 0 .../history/20260409_023941_1_startup.png | Bin .../captures/latest.png | Bin .../shell/tree_view_inline_rename/main.cpp | 48 +- .../tree_view_multiselect/CMakeLists.txt | 0 .../history/20260409_001604_1_startup.png | Bin .../history/20260409_001800_1_startup.png | Bin .../tree_view_multiselect/captures/latest.png | Bin .../shell/tree_view_multiselect/main.cpp | 42 +- .../shell/vector2_field_basic/CMakeLists.txt | 0 .../vector2_field_basic/captures/.gitkeep | 0 .../shell/vector2_field_basic/main.cpp | 54 +- .../shell/vector3_field_basic/CMakeLists.txt | 0 .../vector3_field_basic/captures/.gitkeep | 0 .../shell/vector3_field_basic/main.cpp | 54 +- .../shell/vector4_field_basic/CMakeLists.txt | 0 .../vector4_field_basic/captures/.gitkeep | 0 .../shell/vector4_field_basic/main.cpp | 40 +- .../shell/viewport_shell_basic/CMakeLists.txt | 0 .../viewport_shell_basic/captures/.gitkeep | 0 .../shell/viewport_shell_basic/main.cpp | 42 +- .../shell/viewport_slot_basic/CMakeLists.txt | 0 .../viewport_slot_basic/captures/.gitkeep | 0 .../shell/viewport_slot_basic/main.cpp | 40 +- .../CMakeLists.txt | 0 .../captures/.gitkeep | 0 .../history/20260408_190643_1_startup.png | Bin .../captures/latest.png | Bin .../workspace_interaction_basic/main.cpp | 44 +- .../workspace_shell_compose/CMakeLists.txt | 0 .../shell/workspace_shell_compose/View.xcui | 0 .../workspace_shell_compose/captures/.gitkeep | 0 .../history/20260408_222440_1_startup.png | Bin .../captures/latest.png | Bin .../shell/workspace_shell_compose/main.cpp | 36 +- .../workspace_viewport_compose/CMakeLists.txt | 0 .../captures/.gitkeep | 0 .../shell/workspace_viewport_compose/main.cpp | 26 +- .../state/CMakeLists.txt | 0 .../state/layout_persistence/CMakeLists.txt | 0 .../layout_persistence/captures/.gitkeep | 0 .../state/layout_persistence/main.cpp | 42 +- .../state/panel_host_lifecycle/CMakeLists.txt | 0 .../panel_host_lifecycle/captures/.gitkeep | 0 .../state/panel_host_lifecycle/main.cpp | 44 +- .../state/panel_session_flow/CMakeLists.txt | 0 .../panel_session_flow/captures/.gitkeep | 0 .../state/panel_session_flow/main.cpp | 24 +- .../state/shortcut_dispatch/CMakeLists.txt | 0 .../state/shortcut_dispatch/captures/.gitkeep | 0 .../state/shortcut_dispatch/main.cpp | 34 +- .../CMakeLists.txt | 0 .../captures/.gitkeep | 0 .../viewport_input_bridge_basic/main.cpp | 42 +- tests/UI/Editor/smoke/CMakeLists.txt | 37 + .../smoke/xcui_editor_app_smoke_runner.cpp | 324 +++++++ tests/UI/Editor/unit/CMakeLists.txt | 3 +- ...est_editor_window_tab_drag_drop_target.cpp | 83 ++ .../test_editor_window_workspace_store.cpp | 2 +- .../unit/test_input_modifier_tracker.cpp | 2 +- tests/UI/Editor/unit/test_project_panel.cpp | 97 +- .../unit/test_scene_viewport_runtime.cpp | 119 ++- .../test_ui_editor_panel_input_filter.cpp | 26 + .../test_ui_editor_tree_panel_behavior.cpp | 8 +- .../test_ui_editor_viewport_input_bridge.cpp | 38 + .../test_ui_editor_workspace_interaction.cpp | 66 +- tests/UI/TEST_SPEC.md | 6 +- 279 files changed, 6349 insertions(+), 3238 deletions(-) rename docs/{plan/NewEditor_Inspector面板与组件系统收口计划_2026-04-18.md => used/NewEditor_Inspector面板与组件系统收口计划_阶段归档_2026-04-19.md} (100%) rename docs/{plan/NewEditor_Scene工具域与TransformGizmo计划_2026-04-18.md => used/NewEditor_Scene工具域与TransformGizmo计划_阶段归档_2026-04-19.md} (100%) rename docs/{plan/NewEditor_UI模块三层与编辑器实现审核方向_2026-04-18.md => used/NewEditor_UI模块三层与编辑器实现审核方向_阶段归档_2026-04-19.md} (100%) create mode 100644 docs/used/NewEditor_UI第七方向平台宿主隔离子计划_阶段归档_2026-04-19.md create mode 100644 docs/used/NewEditor_UI第九方向测试体系子计划_阶段归档_2026-04-19.md rename docs/{plan/NewEditor_UI第二阶段依赖方向子计划_2026-04-19.md => used/NewEditor_UI第二阶段依赖方向子计划_阶段归档_2026-04-19.md} (100%) create mode 100644 docs/used/NewEditor_UI第五方向输入与交互一致性子计划_阶段归档_2026-04-19.md create mode 100644 docs/used/NewEditor_UI第八方向文件夹结构与命名子计划_阶段归档_2026-04-19.md rename new_editor/{src/App => app/Commands}/EditorHostCommandBridge.cpp (100%) rename new_editor/app/{State => Composition}/EditorContext.cpp (92%) rename new_editor/app/{State => Composition}/EditorContext.h (90%) rename new_editor/app/{State => Composition}/EditorContextStatus.cpp (100%) rename new_editor/app/{Platform/Win32/WindowManager => Composition}/EditorWindowWorkspaceStore.cpp (98%) rename new_editor/app/{Platform/Win32/WindowManager => Composition}/EditorWindowWorkspaceStore.h (100%) create mode 100644 new_editor/app/Features/PanelInputContext.h create mode 100644 new_editor/app/Features/Scene/SceneViewportFeature.cpp create mode 100644 new_editor/app/Features/Scene/SceneViewportFeature.h rename new_editor/app/Features/Scene/{LegacySceneViewportGizmo.cpp => SceneViewportTransformGizmo.cpp} (83%) rename new_editor/app/Features/Scene/{LegacySceneViewportGizmo.h => SceneViewportTransformGizmo.h} (59%) rename new_editor/app/{Legacy/Viewport/LegacySceneViewportGizmoSupport.cpp => Features/Scene/SceneViewportTransformGizmoSupport.cpp} (99%) rename new_editor/app/{Legacy/Viewport/LegacySceneViewportGizmoSupport.h => Features/Scene/SceneViewportTransformGizmoSupport.h} (99%) delete mode 100644 new_editor/app/Host/HostFwd.h delete mode 100644 new_editor/app/Host/ViewportRenderHost.h create mode 100644 new_editor/app/Platform/Win32/EditorWindowChromeController.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowChromeController.h create mode 100644 new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.h create mode 100644 new_editor/app/Platform/Win32/EditorWindowInputController.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowInputController.h create mode 100644 new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp create mode 100644 new_editor/app/Platform/Win32/EditorWindowRuntimeController.h create mode 100644 new_editor/app/Platform/Win32/Win32SystemInteractionHost.cpp create mode 100644 new_editor/app/Platform/Win32/Win32SystemInteractionHost.h rename new_editor/app/Platform/Win32/{WindowMessageDispatcher.cpp => WindowManager/EditorWindowMessageDispatcher.cpp} (76%) rename new_editor/app/Platform/Win32/{WindowMessageDispatcher.h => WindowManager/EditorWindowMessageDispatcher.h} (77%) create mode 100644 new_editor/app/Platform/Win32/WindowManager/TabDragDropTarget.cpp create mode 100644 new_editor/app/Platform/Win32/WindowManager/TabDragDropTarget.h delete mode 100644 new_editor/app/Platform/Win32/WindowMessageHost.h create mode 100644 new_editor/app/Ports/PortFwd.h rename new_editor/app/{Host/ShaderResourceDescriptorAllocator.h => Ports/ShaderResourceDescriptorAllocatorPort.h} (77%) create mode 100644 new_editor/app/Ports/SystemInteractionPort.h rename new_editor/app/{Host/TextureHost.h => Ports/TexturePort.h} (80%) create mode 100644 new_editor/app/Ports/ViewportRenderPort.h create mode 100644 new_editor/app/Rendering/Viewport/SceneViewportRenderRequest.h create mode 100644 new_editor/app/Rendering/Viewport/SceneViewportRenderService.cpp create mode 100644 new_editor/app/Rendering/Viewport/SceneViewportRenderService.h create mode 100644 new_editor/app/Rendering/Viewport/ViewportContentRenderer.h create mode 100644 new_editor/app/Rendering/Viewport/ViewportObjectPickerService.h rename new_editor/{src/App => app/State}/EditorSession.cpp (100%) create mode 100644 new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInputOwner.h create mode 100644 new_editor/src/Shell/UIEditorStructuredShell.cpp create mode 100644 new_editor/src/Workspace/UIEditorWorkspaceInputOwner.cpp rename tests/UI/Editor/{integration => manual_validation}/CMakeLists.txt (66%) rename tests/UI/Editor/{integration => manual_validation}/README.md (50%) rename tests/UI/Editor/{integration => manual_validation}/shared/CMakeLists.txt (97%) rename tests/UI/Editor/{integration => manual_validation}/shared/src/Application.cpp (100%) rename tests/UI/Editor/{integration => manual_validation}/shared/src/Application.h (100%) rename tests/UI/Editor/{integration => manual_validation}/shared/src/EditorValidationScenario.cpp (84%) rename tests/UI/Editor/{integration => manual_validation}/shared/src/EditorValidationScenario.h (100%) rename tests/UI/Editor/{integration => manual_validation}/shared/src/EditorValidationTheme.h (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/asset_field_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/asset_field_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/asset_field_basic/main.cpp (95%) rename tests/UI/Editor/{integration => manual_validation}/shell/bool_field_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/bool_field_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/bool_field_basic/main.cpp (95%) rename tests/UI/Editor/{integration => manual_validation}/shell/color_field_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/color_field_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/color_field_basic/captures/history/20260408_160549_1_startup.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/color_field_basic/captures/latest.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/color_field_basic/main.cpp (95%) rename tests/UI/Editor/{integration => manual_validation}/shell/context_menu_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/context_menu_basic/main.cpp (95%) rename tests/UI/Editor/{integration => manual_validation}/shell/dock_host_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/dock_host_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/dock_host_basic/captures/history/20260408_190049_1_startup.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/dock_host_basic/captures/latest.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/dock_host_basic/main.cpp (94%) rename tests/UI/Editor/{integration => manual_validation}/shell/editor_shell_compose/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/editor_shell_compose/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/editor_shell_compose/main.cpp (99%) rename tests/UI/Editor/{integration => manual_validation}/shell/editor_shell_interaction/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/editor_shell_interaction/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/editor_shell_interaction/main.cpp (93%) rename tests/UI/Editor/{integration => manual_validation}/shell/enum_field_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/enum_field_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/enum_field_basic/main.cpp (96%) rename tests/UI/Editor/{integration => manual_validation}/shell/list_view_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/list_view_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/list_view_basic/main.cpp (93%) rename tests/UI/Editor/{integration => manual_validation}/shell/list_view_inline_rename/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/list_view_inline_rename/captures/history/20260409_011602_1_startup.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/list_view_inline_rename/captures/history/20260409_011741_1_startup.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/list_view_inline_rename/captures/history/20260409_011820_1_startup.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/list_view_inline_rename/captures/history/20260409_012145_1_startup.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/list_view_inline_rename/captures/history/20260409_012151_1_startup.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/list_view_inline_rename/captures/latest.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/list_view_inline_rename/main.cpp (95%) rename tests/UI/Editor/{integration => manual_validation}/shell/list_view_multiselect/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/list_view_multiselect/captures/history/20260409_001451_1_startup.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/list_view_multiselect/captures/latest.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/list_view_multiselect/main.cpp (96%) rename tests/UI/Editor/{integration => manual_validation}/shell/menu_bar_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/menu_bar_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/menu_bar_basic/main.cpp (93%) rename tests/UI/Editor/{integration => manual_validation}/shell/number_field_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/number_field_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/number_field_basic/main.cpp (95%) rename tests/UI/Editor/{integration => manual_validation}/shell/object_field_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/object_field_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/object_field_basic/main.cpp (93%) rename tests/UI/Editor/{integration => manual_validation}/shell/panel_content_host_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/panel_content_host_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/panel_content_host_basic/main.cpp (95%) rename tests/UI/Editor/{integration => manual_validation}/shell/panel_frame_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/panel_frame_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/panel_frame_basic/main.cpp (97%) rename tests/UI/Editor/{integration => manual_validation}/shell/property_grid_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/property_grid_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/property_grid_basic/main.cpp (95%) rename tests/UI/Editor/{integration => manual_validation}/shell/scroll_view_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/scroll_view_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/scroll_view_basic/main.cpp (94%) rename tests/UI/Editor/{integration => manual_validation}/shell/status_bar_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/status_bar_basic/main.cpp (95%) rename tests/UI/Editor/{integration => manual_validation}/shell/tab_strip_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/tab_strip_basic/captures/history/20260408_182141_1_startup.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/tab_strip_basic/captures/history/20260408_190049_1_startup.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/tab_strip_basic/captures/latest.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/tab_strip_basic/main.cpp (95%) rename tests/UI/Editor/{integration => manual_validation}/shell/text_field_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/text_field_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/text_field_basic/main.cpp (95%) rename tests/UI/Editor/{integration => manual_validation}/shell/tree_view_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/tree_view_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/tree_view_basic/captures/history/20260408_174622_1_startup.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/tree_view_basic/captures/latest.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/tree_view_basic/main.cpp (95%) rename tests/UI/Editor/{integration => manual_validation}/shell/tree_view_inline_rename/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/tree_view_inline_rename/captures/history/20260409_023941_1_startup.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/tree_view_inline_rename/captures/latest.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/tree_view_inline_rename/main.cpp (95%) rename tests/UI/Editor/{integration => manual_validation}/shell/tree_view_multiselect/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/tree_view_multiselect/captures/history/20260409_001604_1_startup.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/tree_view_multiselect/captures/history/20260409_001800_1_startup.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/tree_view_multiselect/captures/latest.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/tree_view_multiselect/main.cpp (96%) rename tests/UI/Editor/{integration => manual_validation}/shell/vector2_field_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/vector2_field_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/vector2_field_basic/main.cpp (95%) rename tests/UI/Editor/{integration => manual_validation}/shell/vector3_field_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/vector3_field_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/vector3_field_basic/main.cpp (95%) rename tests/UI/Editor/{integration => manual_validation}/shell/vector4_field_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/vector4_field_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/vector4_field_basic/main.cpp (90%) rename tests/UI/Editor/{integration => manual_validation}/shell/viewport_shell_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/viewport_shell_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/viewport_shell_basic/main.cpp (95%) rename tests/UI/Editor/{integration => manual_validation}/shell/viewport_slot_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/viewport_slot_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/viewport_slot_basic/main.cpp (95%) rename tests/UI/Editor/{integration => manual_validation}/shell/workspace_interaction_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/workspace_interaction_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/workspace_interaction_basic/captures/history/20260408_190643_1_startup.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/workspace_interaction_basic/captures/latest.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/workspace_interaction_basic/main.cpp (97%) rename tests/UI/Editor/{integration => manual_validation}/shell/workspace_shell_compose/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/workspace_shell_compose/View.xcui (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/workspace_shell_compose/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/workspace_shell_compose/captures/history/20260408_222440_1_startup.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/workspace_shell_compose/captures/latest.png (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/workspace_shell_compose/main.cpp (96%) rename tests/UI/Editor/{integration => manual_validation}/shell/workspace_viewport_compose/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/workspace_viewport_compose/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/shell/workspace_viewport_compose/main.cpp (97%) rename tests/UI/Editor/{integration => manual_validation}/state/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/state/layout_persistence/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/state/layout_persistence/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/state/layout_persistence/main.cpp (93%) rename tests/UI/Editor/{integration => manual_validation}/state/panel_host_lifecycle/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/state/panel_host_lifecycle/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/state/panel_host_lifecycle/main.cpp (93%) rename tests/UI/Editor/{integration => manual_validation}/state/panel_session_flow/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/state/panel_session_flow/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/state/panel_session_flow/main.cpp (94%) rename tests/UI/Editor/{integration => manual_validation}/state/shortcut_dispatch/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/state/shortcut_dispatch/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/state/shortcut_dispatch/main.cpp (93%) rename tests/UI/Editor/{integration => manual_validation}/state/viewport_input_bridge_basic/CMakeLists.txt (100%) rename tests/UI/Editor/{integration => manual_validation}/state/viewport_input_bridge_basic/captures/.gitkeep (100%) rename tests/UI/Editor/{integration => manual_validation}/state/viewport_input_bridge_basic/main.cpp (96%) create mode 100644 tests/UI/Editor/smoke/CMakeLists.txt create mode 100644 tests/UI/Editor/smoke/xcui_editor_app_smoke_runner.cpp create mode 100644 tests/UI/Editor/unit/test_editor_window_tab_drag_drop_target.cpp diff --git a/docs/plan/NewEditor_Inspector面板与组件系统收口计划_2026-04-18.md b/docs/used/NewEditor_Inspector面板与组件系统收口计划_阶段归档_2026-04-19.md similarity index 100% rename from docs/plan/NewEditor_Inspector面板与组件系统收口计划_2026-04-18.md rename to docs/used/NewEditor_Inspector面板与组件系统收口计划_阶段归档_2026-04-19.md diff --git a/docs/plan/NewEditor_Scene工具域与TransformGizmo计划_2026-04-18.md b/docs/used/NewEditor_Scene工具域与TransformGizmo计划_阶段归档_2026-04-19.md similarity index 100% rename from docs/plan/NewEditor_Scene工具域与TransformGizmo计划_2026-04-18.md rename to docs/used/NewEditor_Scene工具域与TransformGizmo计划_阶段归档_2026-04-19.md diff --git a/docs/plan/NewEditor_UI模块三层与编辑器实现审核方向_2026-04-18.md b/docs/used/NewEditor_UI模块三层与编辑器实现审核方向_阶段归档_2026-04-19.md similarity index 100% rename from docs/plan/NewEditor_UI模块三层与编辑器实现审核方向_2026-04-18.md rename to docs/used/NewEditor_UI模块三层与编辑器实现审核方向_阶段归档_2026-04-19.md diff --git a/docs/used/NewEditor_UI第七方向平台宿主隔离子计划_阶段归档_2026-04-19.md b/docs/used/NewEditor_UI第七方向平台宿主隔离子计划_阶段归档_2026-04-19.md new file mode 100644 index 00000000..05e18ed1 --- /dev/null +++ b/docs/used/NewEditor_UI第七方向平台宿主隔离子计划_阶段归档_2026-04-19.md @@ -0,0 +1,383 @@ +# NewEditor UI第七方向平台宿主隔离子计划 2026-04-19 + +## 1. 目标 + +本阶段聚焦 `NewEditor_UI模块三层与编辑器实现审核方向` 中的第七方向: + +**把 Win32 / D3D12 / 原生系统交互严格关回宿主层,避免平台细节继续向 editor feature、runtime 组合层和通用状态层渗透。** + +这次不做表面补丁,不做“只是把 include 挪一下”的伪重构。 +目标是直接处理目前已经确认的根问题。 + +## 2. 已确认的根问题 + +### 2.1 通用多窗口状态被错误放进 `Platform/Win32` + +已确认: + +- `EditorWindowWorkspaceStore` 本质上只是多窗口 workspace/session 的权威状态存储与校验器。 +- 它不依赖 `HWND`、Win32 message、DPI、窗口句柄,也不依赖 D3D12。 +- 但它当前位于 `app/Platform/Win32/WindowManager/`,命名和目录都把它伪装成了平台层。 + +这会造成两个后果: + +- 平台层目录被产品逻辑污染。 +- 后续继续做平台替换、宿主重构、多后端支持时,这部分通用逻辑会被错误地认为只能留在 Win32 层。 + +### 2.2 Project feature 直接调用系统 API + +已确认: + +- `ProjectPanel.cpp` 直接包含 `windows.h` / `shellapi.h`。 +- 它直接调用剪贴板、Explorer 打开、系统双击时间、系统 tick 等 API。 + +这说明: + +- feature 层没有通过宿主抽象访问系统能力。 +- 产品逻辑和平台行为被写死在一起。 +- 后续测试替身、跨平台迁移、甚至简单的宿主替换都会被卡住。 + +### 2.3 路径与字符串编码工具把 Win32 细节扩散到 runtime / feature + +已确认: + +- `app/Internal/StringEncoding.h` 基于 Win32 `MultiByteToWideChar/WideCharToMultiByte`。 +- `ProjectBrowserModel` / `ProjectBrowserModelInternal` / `ProjectPanel` 等产品逻辑直接依赖这些工具。 + +根问题不是“用了宽字符”。 +根问题是: + +- 产品层在直接理解 Windows 的编码转换路径。 +- 这类问题本应收敛在平台适配或标准库层,不应扩散到 feature/runtime。 + +### 2.4 Window message host/dispatcher 承载了过多产品语义 + +已确认: + +- `WindowMessageHost` / `WindowMessageDispatcher` 虽然名义上是宿主消息分发层, + 但接口里直接包含 `EditorContext`、`EditorWindowFrameTransferRequests`、global tab drag 等产品语义。 +- `WindowMessageDispatcher` 直接依赖 `EditorWindow`、shell capture 状态和编辑器窗口交互策略。 + +这说明: + +- 当前宿主消息层不是窄接口。 +- 平台消息调度和编辑器产品交互策略被混在一起。 + +这一项本阶段先做审查定型和边界收紧,不强行一次性全拆完; +但必须把前面三项先处理掉,否则后续消息层根本没法继续收口。 + +## 3. 本阶段收口目标 + +本子计划完成后,应达到以下结果: + +1. 通用多窗口 workspace store 不再位于 `Platform/Win32` 目录。 +2. `ProjectPanel` 不再直接包含或调用 Win32 / Shell API。 +3. `ProjectBrowserModel` 链路不再依赖 Win32 字符串编码工具。 +4. 平台层继续保留 Win32 窗口创建、消息分发、原生调用实现; + 但产品 feature 只通过抽象宿主服务消费系统能力。 +5. 相关测试仍可回归通过。 + +## 4. 执行步骤 + +### Phase A: 通用状态归位 + +1. 把 `EditorWindowWorkspaceStore` 从 `Platform/Win32/WindowManager/` 移出。 +2. 放到更接近 composition / window workspace orchestration 的目录。 +3. 更新所有 include、测试和 CMake 归属。 +4. 保持行为不变,只修正边界与归位。 + +### Phase B: Project feature 脱离系统 API + +1. 新增窄宿主接口,承载: + - 复制文本到剪贴板 + - 在系统文件管理器中显示路径 + - 需要的时间查询能力 + - 需要的双击阈值查询能力 +2. 由 Win32 宿主提供具体实现。 +3. `ProjectPanel` 改为依赖该接口,不再包含 `windows.h` / `shellapi.h`。 + +### Phase C: 编码与路径工具去 Win32 化 + +1. 引入基于标准库的 UTF-8 / `std::filesystem::path` 转换工具。 +2. 把 `ProjectBrowserModel` 链路改为只依赖标准库工具。 +3. 删除 project/runtime 对 `app/Internal/StringEncoding.h` 的依赖。 + +### Phase D: 验证 + +1. 构建 `XCUIEditorAppCore`。 +2. 构建并运行 `editor_app_feature_tests`。 +3. 如有必要,补充受影响单测。 + +## 5. 本阶段不做的事 + +以下内容本轮不一次性展开: + +1. 不强行重写整个 `EditorWindow`。 +2. 不一次性拆穿所有 `WindowMessageDispatcher` 产品语义。 +3. 不扩散到新的功能开发。 +4. 不修改 Win32 宿主行为表现,只做边界收口。 + +## 6. 完成判定 + +满足以下条件,才算本子计划完成: + +1. `EditorWindowWorkspaceStore` 已脱离 `Platform/Win32`。 +2. `ProjectPanel.cpp` 不再包含 `windows.h` / `shellapi.h`。 +3. `ProjectBrowserModel*` 不再依赖 `Internal/StringEncoding.h`。 +4. 构建与回归测试通过。 +## 7. Execution Status 2026-04-19 + +Completed in this round: + +1. Moved `EditorWindowWorkspaceStore` out of `app/Platform/Win32/WindowManager/` + into `app/Composition/` and updated includes, tests, and CMake ownership. +2. Introduced `Host/SystemInteractionHost.h` plus + `Platform/Win32/Win32SystemInteractionHost.*`, and routed `ProjectPanel` + clipboard / file-browser actions through the host abstraction instead of + direct Win32 or Shell calls. +3. Removed `ProjectPanel` dependence on Win32 timing and key constants: + double-click detection now uses `std::chrono::steady_clock`, and escape + handling uses the editor input enum instead of `VK_ESCAPE`. +4. Removed `ProjectBrowserModel` and `ProjectBrowserModelInternal` + dependence on `app/Internal/StringEncoding.h` by switching the project-path + conversion path to `std::filesystem` + UTF-8 helpers. +5. Verified with: + - build target `XCUIEditorAppCore` + - build target `editor_app_feature_tests` + - test filter + `EditorWindowWorkspaceStoreTest.*:ProjectPanelTests.*:ProjectBrowserModelTests.*` + and all 22 tests passed. + +Deferred to the next sub-phase: + +1. `WindowMessageHost` / `WindowMessageDispatcher` still carry editor product + semantics inside the Win32 host message layer. This remains the next root + isolation problem under direction 7, but it is intentionally left out of + this phase to keep the current refactor bounded and behavior-safe. + +## 8. Execution Status 2026-04-19 Phase 2 + +Completed in this round: + +1. Removed the fake `WindowMessageHost` abstraction entirely. `Application` + no longer acts as a product-level message host facade. +2. Moved Win32 window message dispatch ownership into + `EditorWindowManager::TryDispatchWindowMessage(...)`, making the manager the + single editor-window orchestration entry point for native messages. +3. Replaced the old host-layer dispatcher with + `app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.*`, so the + message dispatcher is now explicitly app-specific and no longer shipped from + `XCUIEditorHost`. +4. Removed the old `app/Platform/Win32/WindowMessageDispatcher.*` files from + the host library source set and updated CMake ownership accordingly. +5. Verified with: + - build target `XCUIEditorAppCore` + - build target `editor_app_feature_tests` + - build target `XCUIEditorApp` + - test filter + `EditorWindowInputRoutingTests.*:EditorWindowWorkspaceStoreTest.*:ProjectPanelTests.*:ProjectBrowserModelTests.*` + and all 28 tests passed + - smoke run of `XCUIEditor.exe` with + `XCUIEDITOR_SMOKE_TEST=1`, `XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT=4`, + `XCUI_AUTO_CAPTURE_ON_STARTUP=0` + and the process exited successfully + +Current direction-7 remaining root issue: + +1. The Win32 message dispatcher is now correctly owned by the editor app + platform layer, but `EditorWindow` itself still directly mixes Win32 window + behavior, composition runtime orchestration, and render-loop control inside + one class. If direction 7 continues, the next real收口 point is + `EditorWindow` responsibility decomposition rather than host/message facade + cleanup. + +## 9. Execution Status 2026-04-19 Phase 3 + +Completed in this round: + +1. Introduced `app/Platform/Win32/EditorWindowRuntimeController.*` as the + dedicated owner of editor-window runtime state, including: + `NativeRenderer`, `D3D12WindowRenderer`, `D3D12WindowRenderLoop`, + `AutoScreenshotController`, title-bar icon texture, `UIEditorWorkspaceController`, + and `EditorShellRuntime`. +2. Removed render/composition ownership from + `app/Platform/Win32/EditorWindowInternalState.h`. `EditorWindowState` now + only keeps platform-window state, input state, and borderless chrome runtime + state. +3. Rewired `EditorWindowLifecycle.cpp`, `EditorWindowFrame.cpp`, + `EditorWindowInput.cpp`, and `EditorWindowTitleBarRendering.cpp` so + `EditorWindow` delegates runtime/render/composition work through + `EditorWindowRuntimeController` instead of directly mutating a mixed state + blob. +4. Verified the boundary cleanup by checking that there are no remaining + `m_state->render` / `m_state->composition` accesses under + `app/Platform/Win32/`. +5. Verified with: + - build target `XCUIEditorAppCore` + - build target `editor_app_feature_tests` + - build target `XCUIEditorApp` + - test filter + `EditorWindowInputRoutingTests.*:EditorWindowWorkspaceStoreTest.*:ProjectPanelTests.*:ProjectBrowserModelTests.*` + and all 28 tests passed + - smoke run of `XCUIEditor.exe` with + `XCUIEDITOR_SMOKE_TEST=1`, `XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT=4`, + `XCUI_AUTO_CAPTURE_ON_STARTUP=0` + and the process exited successfully + +Current direction-7 remaining root issue: + +1. `EditorWindow` no longer owns runtime state, but it still contains both + platform window behavior and part of the frame orchestration policy + (`RenderFrame`, shell-pointer-capture reconciliation, transfer-request + building, chrome/layout interaction decisions). The next root cleanup point + is to separate Win32 host-window mechanics from window-level frame/runtime + orchestration policy. + +## 10. Execution Status 2026-04-19 Phase 4 + +Completed in this round: + +1. Introduced `app/Platform/Win32/EditorWindowFrameOrchestrator.*` to own + shell-frame update/render orchestration policy instead of keeping that logic + on `EditorWindow`. +2. Moved invalid-frame rendering, verbose input tracing, shell-frame result + tracing, and cross-window transfer-request construction out of + `EditorWindow` and into the new orchestrator. +3. Simplified `EditorWindowFrame.cpp` so the window shell now mainly handles + window metrics, draw-data presentation, pointer-capture application, and + Win32-facing frame entry points, while delegating shell-frame policy to the + orchestrator and runtime state to `EditorWindowRuntimeController`. +4. Verified with: + - build target `XCUIEditorAppCore` + - build target `editor_app_feature_tests` + - build target `XCUIEditorApp` + - test filter + `EditorWindowInputRoutingTests.*:EditorWindowWorkspaceStoreTest.*:ProjectPanelTests.*:ProjectBrowserModelTests.*` + and all 28 tests passed + - smoke run of `XCUIEditor.exe` with + `XCUIEDITOR_SMOKE_TEST=1`, `XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT=4`, + `XCUI_AUTO_CAPTURE_ON_STARTUP=0` + and the process exited successfully + +Current direction-7 remaining root issue: + +1. `EditorWindow` is now mostly a Win32 shell, but input-event queueing, + pointer-capture reconciliation, and borderless chrome interaction are still + stored and coordinated from the same class. The next root cleanup point is + to split input/capture routing from the remaining host-window chrome + mechanics. + +## 11. Execution Status 2026-04-19 Phase 5 + +Completed in this round: + +1. Introduced `app/Platform/Win32/EditorWindowInputController.*` as the + dedicated owner of editor-window input state, including modifier tracking, + pending input events, mouse-leave tracking, and pointer-capture ownership. +2. Removed input ownership from + `app/Platform/Win32/EditorWindowInternalState.h`. `EditorWindowState` now + only keeps platform-window state and borderless chrome runtime state. +3. Rewired `EditorWindowLifecycle.cpp`, `EditorWindowInput.cpp`, and + `EditorWindowFrame.cpp` so `EditorWindow` delegates input queueing, + capture-owner mutation, modifier synchronization, and pending-event transfer + through `EditorWindowInputController`. +4. Verified the boundary cleanup by checking that there are no remaining + `m_state->input` accesses under `app/Platform/Win32/`. +5. Verified with: + - build target `XCUIEditorAppCore` + - build target `editor_app_feature_tests` + - build target `XCUIEditorApp` + - test filter + `EditorWindowInputRoutingTests.*:EditorWindowWorkspaceStoreTest.*:ProjectPanelTests.*:ProjectBrowserModelTests.*` + and all 28 tests passed + - smoke run of `XCUIEditor.exe` with + `XCUIEDITOR_SMOKE_TEST=1`, `XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT=4`, + `XCUI_AUTO_CAPTURE_ON_STARTUP=0` + and the process exited successfully + +Current direction-7 remaining root issue: + +1. `EditorWindow` no longer owns runtime state, frame orchestration state, or + input state, but borderless chrome behavior is still spread across + `EditorWindowBorderlessResize.cpp`, `EditorWindowTitleBarInteraction.cpp`, + `EditorWindowTitleBarRendering.cpp`, and window-level methods on + `EditorWindow`. The next root cleanup point is to collapse the remaining + borderless chrome policy into a dedicated controller so `EditorWindow` + becomes a thin Win32 composition shell. + +## 12. Execution Status 2026-04-19 Phase 6 + +Completed in this round: + +1. Introduced `app/Platform/Win32/EditorWindowChromeController.*` as the + dedicated owner of borderless chrome state and host runtime state, + including chrome hover/press state, DPI state, resize state, predicted + client size, maximize/restore placement state, and drag-restore state. +2. Removed chrome ownership from + `app/Platform/Win32/EditorWindowInternalState.h`. `EditorWindowState` now + only keeps platform-window metadata. +3. Rewired lifecycle, input, borderless placement, borderless resize, title + bar interaction, and title bar rendering paths so `EditorWindow` delegates + chrome-state access through `EditorWindowChromeController`. +4. Verified the boundary cleanup by checking that there are no remaining + `m_state->chrome` / `m_state->input` accesses under + `app/Platform/Win32/`. +5. Verified with: + - build target `XCUIEditorAppCore` + - build target `editor_app_feature_tests` + - build target `XCUIEditorApp` + - test filter + `EditorWindowInputRoutingTests.*:EditorWindowWorkspaceStoreTest.*:ProjectPanelTests.*:ProjectBrowserModelTests.*` + and all 28 tests passed + - smoke run of `XCUIEditor.exe` with + `XCUIEDITOR_SMOKE_TEST=1`, `XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT=4`, + `XCUI_AUTO_CAPTURE_ON_STARTUP=0` + and the process exited successfully + +Current direction-7 remaining root issue: + +1. `EditorWindow` now delegates all major state ownership, but borderless + chrome behavior itself is still implemented as distributed window methods. + The next root cleanup point is behavioral, not state ownership: move the + remaining borderless chrome interaction/render policy out of `EditorWindow` + so the class becomes a pure Win32 shell facade. + +## 13. Execution Status 2026-04-19 Phase 7 + +Completed in this round: + +1. Upgraded `app/Platform/Win32/EditorWindowChromeController.*` from a pure + state holder into the unified borderless chrome behavior controller. It now + owns resize behavior, title-bar interaction behavior, placement/maximize + behavior, layout/hit-test behavior, and title-bar chrome rendering behavior. +2. Rewired: + - `EditorWindowBorderlessPlacement.cpp` + - `EditorWindowBorderlessResize.cpp` + - `EditorWindowTitleBarInteraction.cpp` + - `EditorWindowTitleBarRendering.cpp` + so these files now mainly act as thin delegation layers from `EditorWindow` + into `EditorWindowChromeController`. +3. Preserved `EditorWindow` as the Win32-facing facade for message dispatch, + but removed the last major in-class borderless behavior implementation from + it. At this point `EditorWindowState` only stores window metadata, while + runtime/frame/input/chrome state and chrome behavior all live outside the + window shell. +4. Verified with: + - build target `XCUIEditorAppCore` + - build target `editor_app_feature_tests` + - build target `XCUIEditorApp` + - test filter + `EditorWindowInputRoutingTests.*:EditorWindowWorkspaceStoreTest.*:ProjectPanelTests.*:ProjectBrowserModelTests.*` + and all 28 tests passed + - smoke run of `XCUIEditor.exe` with + `XCUIEDITOR_SMOKE_TEST=1`, `XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT=4`, + `XCUI_AUTO_CAPTURE_ON_STARTUP=0` + and the process exited successfully + +Current direction-7 assessment: + +1. No further root state-ownership or behavior-ownership violation remains in + the `EditorWindow` host boundary at the same severity as earlier phases. + The remaining work under this direction is mostly API-surface slimming + around `EditorWindow`'s many delegation methods, which is lower-risk cleanup + rather than a blocking architectural fault. diff --git a/docs/used/NewEditor_UI第九方向测试体系子计划_阶段归档_2026-04-19.md b/docs/used/NewEditor_UI第九方向测试体系子计划_阶段归档_2026-04-19.md new file mode 100644 index 00000000..39b59e86 --- /dev/null +++ b/docs/used/NewEditor_UI第九方向测试体系子计划_阶段归档_2026-04-19.md @@ -0,0 +1,64 @@ +# NewEditor UI第九方向测试体系子计划 + +日期:2026-04-19 + +## 一、问题定义 + +当前 `UI` 三层与 `new_editor` 的测试体系已经积累出较多用例,但第九方向的根问题不是“缺少若干测试 case”,而是测试类型、测试边界与架构边界没有对齐: + +1. `tests/UI/Editor/integration` 实际承载的是人工验证场景与截图宿主,不是自动回归测试。 +2. `tests/UI/Editor` 顶层构建对 `new_editor/app` 目录结构存在全局 include 级别的硬耦合,导致纯 `XCUIEditorLib` 测试边界被宿主实现污染。 +3. 多窗口、全局 tab drag、宿主捕获与跨窗口 drop 这条链路只在模型层和局部判定层有覆盖,缺少针对宿主编排关键契约的自动回归保护。 +4. `xcui_editor_app_smoke` 目前只验证进程可启动并渲染少量帧后退出,没有验证主窗口与核心 shell 是否真正进入稳定可用状态。 + +## 二、重构目标 + +本阶段只解决第九方向,不扩散到其他审核方向。目标如下: + +1. 明确拆分“自动回归测试”和“人工验证场景”,从命名、目录、CMake 目标三个层面统一语义。 +2. 消除 `tests/UI/Editor` 顶层对 `new_editor/app` 的全局 include 污染,把 app/host 依赖收束到确实需要它们的目标上。 +3. 为宿主层全局 tab drag / cross-window drop 的关键判定链路建立可自动执行的回归保护。 +4. 将 app smoke 从“进程能起来”提升为“主窗口与 shell 已完成基本装配”。 + +## 三、执行原则 + +1. 不做补丁式修修补补,直接调整测试体系边界。 +2. 不把人工验证 exe 继续伪装成自动测试。 +3. 不让纯 UI 库测试通过顶层 include 偷拿 app 内部头文件。 +4. 新增自动回归时,优先抽出可复用、可单测的宿主判定逻辑,而不是继续把逻辑埋在 Win32 消息处理里。 + +## 四、执行步骤 + +### 阶段 1:测试体系正名与目录重构 + +1. 将 `tests/UI/Editor/integration` 重构为 `tests/UI/Editor/manual_validation`。 +2. 同步更新 Editor 侧 README、场景路径、截图输出路径与 CMake 入口命名。 +3. 将顶层聚合目标改为“自动测试目标”和“人工验证目标”分离,避免继续使用误导性的 `integration_tests` 语义。 + +### 阶段 2:清理测试构建边界 + +1. 移除 `tests/UI/Editor/CMakeLists.txt` 中对 `new_editor/app` 的全局 include。 +2. 将带有 app/host 依赖的测试归并到 app feature test 目标。 +3. 只在 manual validation host 目标上声明 `app` 级 include,避免污染纯 `XCUIEditorLib` 测试。 + +### 阶段 3:补宿主级自动回归保护 + +1. 抽离全局 tab drag / cross-window drop 的纯判定逻辑。 +2. 为 drop target 解析、插入索引、边缘 docking 方向建立 gtest 回归。 +3. 保持 Win32 消息编排只负责调度,不再把关键判定藏在匿名静态函数里。 + +### 阶段 4:增强 app smoke + +1. 在 smoke 模式下显式验证主窗口、workspace shell 与基本 compose 结果已建立。 +2. 对“到帧预算仍未 ready”的情况返回失败退出码。 +3. 保留现有快速退出特性,但把 smoke 结果从“能跑”升级为“已装配完成”。 + +## 五、完成标准 + +满足以下条件,才算第九方向完成: + +1. `tests/UI/Editor` 目录语义与测试职责一致。 +2. `XCUIEditorLib` 纯测试目标不再被 `new_editor/app` 全局 include 污染。 +3. 宿主层全局 tab drag / cross-window drop 至少有一组自动回归保护关键判定。 +4. smoke 在核心 shell 未 ready 时会失败,而不是静默通过。 +5. 相关目标编译通过,新增或调整后的自动测试通过。 diff --git a/docs/plan/NewEditor_UI第二阶段依赖方向子计划_2026-04-19.md b/docs/used/NewEditor_UI第二阶段依赖方向子计划_阶段归档_2026-04-19.md similarity index 100% rename from docs/plan/NewEditor_UI第二阶段依赖方向子计划_2026-04-19.md rename to docs/used/NewEditor_UI第二阶段依赖方向子计划_阶段归档_2026-04-19.md diff --git a/docs/used/NewEditor_UI第五方向输入与交互一致性子计划_阶段归档_2026-04-19.md b/docs/used/NewEditor_UI第五方向输入与交互一致性子计划_阶段归档_2026-04-19.md new file mode 100644 index 00000000..9cd34317 --- /dev/null +++ b/docs/used/NewEditor_UI第五方向输入与交互一致性子计划_阶段归档_2026-04-19.md @@ -0,0 +1,113 @@ +# NewEditor UI 第五方向输入与交互一致性子计划 + +## 背景 + +当前 `new_editor` 在单窗口内同时存在多套输入焦点判断: + +- workspace 仍把 `activePanelId` 同时当成“活动 tab”和“输入归属” +- viewport bridge 自己维护 `focused/captured` +- hosted panel 通过 `panelActive` 近似判断是否允许键盘输入 +- dock host 还保留独立的 `focused` 视觉状态 + +这导致输入所有权不是单一真相源,窗口内不同子系统对“谁拥有键盘/焦点”会得出不同结论。 + +## 根因 + +根因不是 Win32 消息入口,而是 `new_editor` 内部缺少一个窗口级 / workspace 级的统一输入所有者模型。 + +结果是: + +- tab 选中状态与输入焦点耦合 +- viewport 与 hosted panel 之间只能靠局部布尔值抢焦点 +- runtime 路由只能依赖 `activePanelId` 做近似分发 +- 交互问题一旦出现,只能继续在各处补特判 + +## 本阶段目标 + +建立统一的 workspace 输入所有者模型,并让以下路径全部基于该模型工作: + +- workspace interaction +- viewport shell / viewport input bridge +- hosted panel runtime update +- dock host 焦点可视状态 + +`activePanelId` 继续只表示 workspace 的活动面板 / 活动 tab,不再承担输入焦点语义。 + +## 重构范围 + +### 1. 建立统一输入所有者模型 + +新增统一输入所有者抽象,至少覆盖: + +- `None` +- `DockHost` +- `HostedPanel(panelId)` +- `Viewport(panelId)` + +该状态只存在于交互态,不进入持久化 workspace model。 + +### 2. 由 workspace 统一解析输入归属 + +在 workspace 交互层统一根据以下信息解析 owner: + +- dock host hit target +- externally hosted panel bounds +- viewport bounds +- focus lost / panel unmount / 隐藏后的归一化 + +不再让 runtime 或各 panel 自己推断当前输入归属。 + +### 3. viewport bridge 改为可被外部 owner 驱动 + +viewport bridge 保留指针捕获、按键跟踪、delta 计算职责,但焦点语义改为: + +- 支持由外部 owner 驱动 focus +- workspace 场景下不再自己发明长期 focus 真相 + +### 4. hosted panel 去掉对 `activePanelId` 的输入依赖 + +Hierarchy / Project / Inspector 的键盘与 focus 过滤改为基于统一 owner,而不是 `panelActive`。 + +相关命名同步收口,避免继续使用会误导含义的参数名。 + +### 5. dock host 焦点可视状态与统一 owner 对齐 + +dock host 的视觉 focus 状态不再作为另一套事实来源存在,而是与统一 owner 对齐。 + +## 执行步骤 + +1. 新增 workspace 输入 owner 抽象与解析逻辑 +2. 将 workspace interaction 输出扩展为可暴露当前 owner +3. 改造 viewport bridge,使 workspace 路径由外部 owner 驱动 focus +4. 改造 runtime update 与 hosted panel 更新签名,移除 `activePanelId == panelId` 输入门控 +5. 补齐单元测试,覆盖 owner 切换、viewport focus、hosted panel 键盘路由 +6. 编译并跑相关 editor 测试,确认没有交互回退 + +## 完成标准 + +- `activePanelId` 不再作为 hosted panel / viewport 的输入焦点真相 +- 同一窗口内存在唯一输入 owner +- viewport 与 hosted panel 的键盘路由都来自统一 owner +- dock host、viewport、hosted panel 的焦点语义一致 +- 相关测试覆盖 owner 切换与交互回归 + +## 当前状态 + +已完成。 + +## 完成说明 + +- 已建立 `UIEditorWorkspaceInputOwner`,由 workspace interaction 统一解析并维护当前输入 owner +- `activePanelId` 已退回为 tab / 活动面板状态,不再承担输入焦点语义 +- viewport bridge 已支持外部 focus owner 驱动,workspace 路径下不再自持长期 focus 真相 +- Hierarchy / Project / Inspector 已改为基于 `PanelInputContext` 与统一 owner 路由输入 +- hosted panel 焦点切换已补齐 synthetic `FocusGained` / `FocusLost` 事件 +- dock host 的可视 focus 已对齐到统一 owner + +## 验证结果 + +- `cmake -S . -B build_codex_verify` +- `cmake --build . --config Debug --target editor_ui_tests` +- `cmake --build . --config Debug --target editor_app_feature_tests` +- `build_codex_verify/tests/UI/Editor/unit/Debug/editor_ui_tests.exe` 通过,`345 / 345` 通过 +- `build_codex_verify/tests/UI/Editor/unit/Debug/editor_app_feature_tests.exe` 通过,`89 / 89` 通过 diff --git a/docs/used/NewEditor_UI第八方向文件夹结构与命名子计划_阶段归档_2026-04-19.md b/docs/used/NewEditor_UI第八方向文件夹结构与命名子计划_阶段归档_2026-04-19.md new file mode 100644 index 00000000..472d5256 --- /dev/null +++ b/docs/used/NewEditor_UI第八方向文件夹结构与命名子计划_阶段归档_2026-04-19.md @@ -0,0 +1,92 @@ +# NewEditor UI第八方向文件夹结构与命名子计划 + +日期:2026-04-19 + +## 目标 + +针对 `new_editor` 在文件夹结构、目标归属、公共头暴露面与命名语义上的根问题做正式收口,确保目录结构能够真实表达架构,而不是继续承载历史迁移痕迹与职责漂移。 + +本计划只覆盖第八方向,不扩散到状态模型、输入模型或具体 feature 行为修复。 + +## 根问题 + +### 1. app 与 lib 的源码归属错位 + +- `new_editor/src/App` 中存在明显的 app-only 实现,但 `src` 应只承载 `XCUIEditorLib` 的内部实现。 +- 这会让 `src` 和 `app` 的边界失真,后续继续放置文件时没有稳定规则。 + +### 2. 公共头中混入装配逻辑 + +- `include/XCEditor` 应暴露稳定契约,不应继续承载产品装配细节或内联构建逻辑。 +- 这类逻辑进入公共头后,会放大 include 面并削弱层次边界。 + +### 3. app/State 目录职责漂移 + +- `app/State` 目前不只承载 session、selection、focus 这类状态对象,还承载了带强装配语义的 `EditorContext`。 +- 目录名与真实职责不一致,继续演进会让状态层再次膨胀成“什么都能放”的目录。 + +### 4. Legacy 命名仍在主路径中 + +- 现役主链中仍保留 `Legacy` 命名,会持续模糊“正式实现”和“迁移桥接”的边界。 +- 如果该实现已是正式方案,就必须去 `Legacy`;如果只是临时桥,则必须显式标注退出路径。 + +### 5. 词汇表不稳定 + +- `Host`、`Runtime`、`Bridge`、`Shell` 等词在不同目录中承担了不同强度的语义。 +- 若不先收拢命名语法,目录收口后仍会反复退化。 + +## 执行原则 + +- 不靠补丁式兼容层维持现状。 +- 不引入 `new_editor -> editor` 反向依赖。 +- 优先修正目录与 target ownership,再收公共 API 暴露面。 +- 每一步都必须让终态更清晰,而不是增加新的过渡名词。 + +## 分阶段计划 + +### 阶段 1:源码归属归位 + +- 将 `src/App` 中的 app-only 实现迁回 `app` 下的真实职责目录。 +- 同步修正 `CMakeLists.txt` 中对应 source ownership。 +- 验证 `XCUIEditorAppCore` 与相关测试目标仍然通过。 + +### 阶段 2:公共头装配逻辑下沉 + +- 收缩 `include/XCEditor/Shell/UIEditorStructuredShell.h`。 +- 将内联装配实现下沉到 `src`,只保留稳定声明。 +- 保持测试语义不变,但让公共头不再承载具体 build 过程。 + +### 阶段 3:app 状态目录归位 + +- 重新定义 `app/State` 的合法内容,只保留状态对象与轻状态服务。 +- 将 `EditorContext` 迁移到更准确的 application/composition 归属位置。 +- 修正其引用路径,避免继续把编排器挂在 `State` 名下。 + +### 阶段 4:Legacy 主链命名正式化 + +- 审查 `Scene` 主链中仍保留的 `Legacy` 命名。 +- 若实现已成为现役主路径,则做正式命名替换。 +- 若仍为临时桥,则补齐显式退出路径并限制传播范围。 + +### 阶段 5:目录词汇表统一 + +- 为 `Host`、`Runtime`、`Bridge`、`Shell`、`Controller`、`Service` 建立稳定语义。 +- 只在语义已经明确的前提下做命名替换,避免“只改名不改边界”。 + +## 本轮执行范围 + +本轮先执行阶段 1 与阶段 2,并在编译验证后评估是否直接进入阶段 3。 + +## 当前进展 + +- 已完成阶段 1:`src/App` 中的 app-only 实现已迁回 `app/Commands` 与 `app/State`,并同步修正 CMake source ownership。 +- 已完成阶段 2:`UIEditorStructuredShell` 的装配实现已从公共头下沉到 `src/Shell`。 +- 已完成阶段 3:`EditorContext` 已从 `app/State` 迁移到 `app/Composition`,`app/State` 的目录语义已恢复为状态与轻状态服务。 +- 已完成阶段 4:Scene 现役 gizmo 主链已从 `Legacy*` 命名和 `app/Legacy` 目录中迁出,正式落到 `Features/Scene/SceneViewportTransformGizmo*`。 +- 已完成阶段 5:原 `app/Host` 接口层已重构为 `app/Ports`,抽象端口与 concrete host 实现完成词汇分离,`Host` 不再同时表示接口与实现。 + +## 当前结论 + +- 本子计划当前范围已完成。 +- `new_editor` 的目录归属、公共头装配边界、`app/State` 语义、Scene gizmo 正式命名与接口端口词汇边界均已收口。 +- 后续如果继续沿第八方向深入,应只做增量命名统一,不再需要重复处理同级别结构根问题。 diff --git a/engine/include/XCEngine/Input/InputAxis.h b/engine/include/XCEngine/Input/InputAxis.h index 39e85bda..14351165 100644 --- a/engine/include/XCEngine/Input/InputAxis.h +++ b/engine/include/XCEngine/Input/InputAxis.h @@ -1,5 +1,6 @@ #pragma once -#include "InputTypes.h" + +#include #include namespace XCEngine { diff --git a/engine/include/XCEngine/Input/InputEvent.h b/engine/include/XCEngine/Input/InputEvent.h index b9ad4792..412d7a62 100644 --- a/engine/include/XCEngine/Input/InputEvent.h +++ b/engine/include/XCEngine/Input/InputEvent.h @@ -1,5 +1,6 @@ #pragma once -#include "InputTypes.h" + +#include #include #include diff --git a/engine/include/XCEngine/Input/InputManager.h b/engine/include/XCEngine/Input/InputManager.h index 624600dc..95608360 100644 --- a/engine/include/XCEngine/Input/InputManager.h +++ b/engine/include/XCEngine/Input/InputManager.h @@ -1,9 +1,11 @@ #pragma once -#include "Core/Event.h" -#include "InputTypes.h" -#include "InputEvent.h" -#include "InputAxis.h" + +#include +#include +#include +#include #include + #include #include diff --git a/engine/include/XCEngine/Input/InputTypes.h b/engine/include/XCEngine/Input/InputTypes.h index d2d929fa..dc62049e 100644 --- a/engine/include/XCEngine/Input/InputTypes.h +++ b/engine/include/XCEngine/Input/InputTypes.h @@ -1,5 +1,6 @@ #pragma once -#include "Core/Types.h" + +#include namespace XCEngine { namespace Input { diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index d42a6c49..15e300a8 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -102,6 +102,7 @@ set(XCUI_EDITOR_SHELL_SOURCES src/Shell/UIEditorShellCapturePolicy.cpp src/Shell/UIEditorShellCompose.cpp src/Shell/UIEditorShellInteraction.cpp + src/Shell/UIEditorStructuredShell.cpp src/Shell/ShellInteractionRequest.cpp src/Shell/ShellInteractionRendering.cpp src/Shell/UIEditorStatusBar.cpp @@ -119,6 +120,7 @@ set(XCUI_EDITOR_WORKSPACE_SOURCES src/Workspace/WorkspaceControllerDispatch.cpp src/Workspace/WorkspaceControllerLayoutOps.cpp src/Workspace/UIEditorWorkspaceInteraction.cpp + src/Workspace/UIEditorWorkspaceInputOwner.cpp src/Workspace/UIEditorWorkspaceLayoutPersistence.cpp src/Workspace/UIEditorWorkspaceModel.cpp src/Workspace/WorkspaceModelMutation.cpp @@ -174,7 +176,6 @@ set(XCUI_EDITOR_HOST_PLATFORM_SOURCES app/Platform/Win32/BorderlessWindowChromeRendering.cpp app/Platform/Win32/BorderlessWindowChromeDwm.cpp app/Platform/Win32/BorderlessWindowFrame.cpp - app/Platform/Win32/WindowMessageDispatcher.cpp ) set(XCUI_EDITOR_HOST_RENDERING_SOURCES @@ -243,8 +244,11 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) ) set(XCUI_EDITOR_APP_STATE_SOURCES - app/State/EditorContext.cpp - app/State/EditorContextStatus.cpp + app/State/EditorSession.cpp + ) + + set(XCUI_EDITOR_APP_COMMAND_SOURCES + app/Commands/EditorHostCommandBridge.cpp ) set(XCUI_EDITOR_APP_COMPOSITION_SOURCES @@ -253,10 +257,13 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Composition/EditorShellAssetDefinition.cpp app/Composition/EditorShellAssetLayout.cpp app/Composition/EditorShellAssetMenu.cpp + app/Composition/EditorContext.cpp + app/Composition/EditorContextStatus.cpp app/Composition/EditorShellRuntime.cpp app/Composition/EditorShellRuntimeRendering.cpp app/Composition/EditorShellRuntimeUpdate.cpp app/Composition/EditorShellRuntimeViewport.cpp + app/Composition/EditorWindowWorkspaceStore.cpp app/Composition/WorkspaceEventSync.cpp ) @@ -276,9 +283,10 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Features/Project/ProjectBrowserModelAssets.cpp app/Features/Project/ProjectBrowserModelFolders.cpp app/Features/Project/ProjectBrowserModelInternal.cpp - app/Features/Scene/LegacySceneViewportGizmo.cpp - app/Legacy/Viewport/LegacySceneViewportGizmoSupport.cpp + app/Features/Scene/SceneViewportTransformGizmo.cpp + app/Features/Scene/SceneViewportTransformGizmoSupport.cpp app/Features/Scene/SceneEditCommandRoute.cpp + app/Features/Scene/SceneViewportFeature.cpp app/Features/Scene/SceneViewportToolOverlay.cpp app/Features/Scene/SceneViewportController.cpp ) @@ -288,6 +296,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Rendering/Viewport/Passes/SceneViewportGridPass.cpp app/Rendering/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp app/Rendering/Viewport/SceneViewportRenderPassBundle.cpp + app/Rendering/Viewport/SceneViewportRenderService.cpp app/Rendering/Viewport/ViewportHostService.cpp app/Rendering/Viewport/ViewportRenderTargets.cpp app/Rendering/Viewport/ViewportRenderTargetInternal.cpp @@ -298,26 +307,31 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Scene/EditorSceneRuntime.cpp app/Internal/EmbeddedPngLoader.cpp app/Scene/EditorSceneBridge.cpp - src/App/EditorHostCommandBridge.cpp - src/App/EditorSession.cpp ) set(XCUI_EDITOR_APP_PLATFORM_SOURCES - app/Platform/Win32/WindowManager/EditorWindowWorkspaceStore.cpp app/Platform/Win32/EditorWindowBorderlessPlacement.cpp app/Platform/Win32/EditorWindowBorderlessResize.cpp + app/Platform/Win32/EditorWindowChromeController.cpp app/Platform/Win32/EditorWindowFrame.cpp + app/Platform/Win32/EditorWindowFrameOrchestrator.cpp + app/Platform/Win32/EditorWindowInputController.cpp app/Platform/Win32/EditorWindowLifecycle.cpp app/Platform/Win32/EditorWindowInput.cpp + app/Platform/Win32/EditorWindowRuntimeController.cpp app/Platform/Win32/EditorWindowTitleBarInteraction.cpp app/Platform/Win32/EditorWindowTitleBarRendering.cpp + app/Platform/Win32/Win32SystemInteractionHost.cpp + app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp app/Platform/Win32/WindowManager/Lifecycle.cpp + app/Platform/Win32/WindowManager/TabDragDropTarget.cpp app/Platform/Win32/WindowManager/WindowSync.cpp app/Platform/Win32/WindowManager/TabDrag.cpp ) set(XCUI_EDITOR_APP_CORE_SOURCES ${XCUI_EDITOR_APP_STATE_SOURCES} + ${XCUI_EDITOR_APP_COMMAND_SOURCES} ${XCUI_EDITOR_APP_COMPOSITION_SOURCES} ${XCUI_EDITOR_APP_FEATURE_SOURCES} ${XCUI_EDITOR_APP_RENDERING_SOURCES} @@ -342,7 +356,6 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) target_link_libraries(XCUIEditorAppCore PRIVATE XCUIEditorLib - XCUIEditorHost ) add_library(XCUIEditorAppLib STATIC @@ -411,17 +424,6 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) xcengine_copy_physx_runtime_dlls(XCUIEditorApp) endif() - add_test( - NAME xcui_editor_app_smoke - COMMAND $ - ) - set_tests_properties(xcui_editor_app_smoke PROPERTIES - TIMEOUT 20 - RUN_SERIAL TRUE - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - ENVIRONMENT - "XCUIEDITOR_SMOKE_TEST=1;XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT=4;XCUI_AUTO_CAPTURE_ON_STARTUP=0" - ) endif() diff --git a/new_editor/app/Bootstrap/Application.h b/new_editor/app/Bootstrap/Application.h index 05b63146..ceae9d71 100644 --- a/new_editor/app/Bootstrap/Application.h +++ b/new_editor/app/Bootstrap/Application.h @@ -4,25 +4,25 @@ #define NOMINMAX #endif -#include "Platform/Win32/WindowMessageHost.h" - #include #include #include -#include -#include namespace XCEngine::UI::Editor { namespace App { class EditorContext; class EditorWindowManager; } + +namespace Ports { +class SystemInteractionPort; +} } namespace XCEngine::UI::Editor { -class Application final : public Host::WindowMessageHost { +class Application final { public: Application(); ~Application(); @@ -33,17 +33,6 @@ public: Application& operator=(Application&&) = delete; int Run(HINSTANCE hInstance, int nCmdShow); - App::EditorContext& GetEditorContext() override; - const App::EditorContext& GetEditorContext() const override; - bool IsGlobalTabDragActive() const override; - bool OwnsActiveGlobalTabDrag(std::string_view windowId) const override; - void EndGlobalTabDragSession() override; - void HandleDestroyedWindow(HWND hwnd) override; - void HandleWindowFrameTransferRequests( - App::EditorWindow& window, - App::EditorWindowFrameTransferRequests&& transferRequests) override; - bool HandleGlobalTabDragPointerMove(HWND hwnd) override; - bool HandleGlobalTabDragPointerButtonUp(HWND hwnd) override; private: static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); @@ -53,18 +42,13 @@ private: bool RegisterWindowClass(); static std::filesystem::path ResolveRepoRootPath(); static LONG WINAPI HandleUnhandledException(EXCEPTION_POINTERS* exceptionInfo); - void InitializeSmokeTestConfig(); - void TickSmokeTest(); HINSTANCE m_hInstance = nullptr; ATOM m_windowClassAtom = 0; std::filesystem::path m_repoRoot = {}; std::unique_ptr m_editorContext = {}; std::unique_ptr m_windowManager = {}; - bool m_smokeTestEnabled = false; - int m_smokeTestFrameLimit = 0; - int m_smokeTestRenderedFrames = 0; - bool m_smokeTestCloseRequested = false; + std::unique_ptr m_systemInteractionHost = {}; }; int RunXCUIEditorApp(HINSTANCE hInstance, int nCmdShow); diff --git a/new_editor/app/Bootstrap/ApplicationBootstrap.cpp b/new_editor/app/Bootstrap/ApplicationBootstrap.cpp index af7412af..e549b69f 100644 --- a/new_editor/app/Bootstrap/ApplicationBootstrap.cpp +++ b/new_editor/app/Bootstrap/ApplicationBootstrap.cpp @@ -5,10 +5,11 @@ #include -#include "State/EditorContext.h" +#include "Ports/SystemInteractionPort.h" +#include "Composition/EditorContext.h" #include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowManager.h" -#include "Internal/EnvironmentFlags.h" +#include "Platform/Win32/Win32SystemInteractionHost.h" #include "Internal/ExecutablePath.h" #ifndef XCUIEDITOR_REPO_ROOT @@ -24,7 +25,6 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { m_hInstance = hInstance; m_repoRoot = ResolveRepoRootPath(); EnableDpiAwareness(); - InitializeSmokeTestConfig(); const std::filesystem::path logRoot = GetExecutableDirectory() / "logs"; InitializeUIEditorRuntimeTrace(logRoot); @@ -41,6 +41,9 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { return false; } + m_systemInteractionHost = std::make_unique(); + m_editorContext->SetSystemInteractionHost(m_systemInteractionHost.get()); + App::EditorWindowHostConfig hostConfig = {}; hostConfig.hInstance = m_hInstance; hostConfig.windowClassName = kWindowClassName; @@ -70,7 +73,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { createParams.title = kWindowTitle; createParams.showCommand = nCmdShow; createParams.primary = true; - createParams.autoCaptureOnStartup = !m_smokeTestEnabled; + createParams.autoCaptureOnStartup = true; if (m_windowManager->CreateEditorWindow( m_editorContext->BuildWorkspaceController(), createParams) == nullptr) { @@ -82,28 +85,6 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { return true; } -void Application::InitializeSmokeTestConfig() { - m_smokeTestEnabled = App::Internal::IsEnvironmentFlagEnabled("XCUIEDITOR_SMOKE_TEST"); - m_smokeTestFrameLimit = 0; - m_smokeTestRenderedFrames = 0; - m_smokeTestCloseRequested = false; - if (!m_smokeTestEnabled) { - return; - } - - constexpr int kDefaultSmokeFrameLimit = 4; - const std::optional frameLimit = - App::Internal::TryGetEnvironmentInt("XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT"); - m_smokeTestFrameLimit = - frameLimit.has_value() && frameLimit.value() > 0 - ? frameLimit.value() - : kDefaultSmokeFrameLimit; - - AppendUIEditorRuntimeTrace( - "smoke", - "enabled with frame limit=" + std::to_string(m_smokeTestFrameLimit)); -} - void Application::Shutdown() { AppendUIEditorRuntimeTrace("app", "shutdown begin"); @@ -112,6 +93,11 @@ void Application::Shutdown() { m_windowManager.reset(); } + if (m_editorContext != nullptr) { + m_editorContext->SetSystemInteractionHost(nullptr); + } + m_systemInteractionHost.reset(); + ::XCEngine::Resources::ResourceManager::Get().Shutdown(); if (m_windowClassAtom != 0 && m_hInstance != nullptr) { diff --git a/new_editor/app/Bootstrap/ApplicationLifecycle.cpp b/new_editor/app/Bootstrap/ApplicationLifecycle.cpp index 183c6432..34844bff 100644 --- a/new_editor/app/Bootstrap/ApplicationLifecycle.cpp +++ b/new_editor/app/Bootstrap/ApplicationLifecycle.cpp @@ -1,7 +1,7 @@ #include "Bootstrap/Application.h" -#include "State/EditorContext.h" -#include "Platform/Win32/EditorWindow.h" +#include "Ports/SystemInteractionPort.h" +#include "Composition/EditorContext.h" #include "Platform/Win32/EditorWindowManager.h" #include @@ -13,55 +13,6 @@ Application::Application() Application::~Application() = default; -App::EditorContext& Application::GetEditorContext() { - return *m_editorContext; -} - -const App::EditorContext& Application::GetEditorContext() const { - return *m_editorContext; -} - -bool Application::IsGlobalTabDragActive() const { - return m_windowManager != nullptr && m_windowManager->IsGlobalTabDragActive(); -} - -bool Application::OwnsActiveGlobalTabDrag(std::string_view windowId) const { - return m_windowManager != nullptr && - m_windowManager->OwnsActiveGlobalTabDrag(windowId); -} - -void Application::EndGlobalTabDragSession() { - if (m_windowManager != nullptr) { - m_windowManager->EndGlobalTabDragSession(); - } -} - -void Application::HandleDestroyedWindow(HWND hwnd) { - if (m_windowManager != nullptr) { - m_windowManager->HandleDestroyedWindow(hwnd); - } -} - -void Application::HandleWindowFrameTransferRequests( - App::EditorWindow& window, - App::EditorWindowFrameTransferRequests&& transferRequests) { - if (m_windowManager != nullptr) { - m_windowManager->HandleWindowFrameTransferRequests( - window, - std::move(transferRequests)); - } -} - -bool Application::HandleGlobalTabDragPointerMove(HWND hwnd) { - return m_windowManager != nullptr && - m_windowManager->HandleGlobalTabDragPointerMove(hwnd); -} - -bool Application::HandleGlobalTabDragPointerButtonUp(HWND hwnd) { - return m_windowManager != nullptr && - m_windowManager->HandleGlobalTabDragPointerButtonUp(hwnd); -} - int RunXCUIEditorApp(HINSTANCE hInstance, int nCmdShow) { Application application; return application.Run(hInstance, nCmdShow); diff --git a/new_editor/app/Bootstrap/ApplicationRunLoop.cpp b/new_editor/app/Bootstrap/ApplicationRunLoop.cpp index 87a1162e..77cd0a86 100644 --- a/new_editor/app/Bootstrap/ApplicationRunLoop.cpp +++ b/new_editor/app/Bootstrap/ApplicationRunLoop.cpp @@ -1,31 +1,9 @@ #include "Bootstrap/Application.h" -#include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowManager.h" -#include - namespace XCEngine::UI::Editor { -void Application::TickSmokeTest() { - if (!m_smokeTestEnabled || m_smokeTestCloseRequested || m_windowManager == nullptr) { - return; - } - - ++m_smokeTestRenderedFrames; - if (m_smokeTestRenderedFrames < m_smokeTestFrameLimit) { - return; - } - - if (App::EditorWindow* primaryWindow = m_windowManager->FindPrimaryWindow(); - primaryWindow != nullptr && - primaryWindow->GetHwnd() != nullptr) { - PostMessageW(primaryWindow->GetHwnd(), WM_CLOSE, 0, 0); - m_smokeTestCloseRequested = true; - AppendUIEditorRuntimeTrace("smoke", "frame budget reached, requested WM_CLOSE"); - } -} - int Application::Run(HINSTANCE hInstance, int nCmdShow) { if (!Initialize(hInstance, nCmdShow)) { Shutdown(); @@ -55,7 +33,6 @@ int Application::Run(HINSTANCE hInstance, int nCmdShow) { } m_windowManager->RenderAllWindows(); - TickSmokeTest(); } else { break; } diff --git a/new_editor/app/Bootstrap/ApplicationWindowClass.cpp b/new_editor/app/Bootstrap/ApplicationWindowClass.cpp index d2a90211..8448ae6e 100644 --- a/new_editor/app/Bootstrap/ApplicationWindowClass.cpp +++ b/new_editor/app/Bootstrap/ApplicationWindowClass.cpp @@ -3,11 +3,8 @@ #include -#include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowManager.h" -#include - namespace XCEngine::UI::Editor { using namespace BootstrapInternal; @@ -92,17 +89,11 @@ LRESULT CALLBACK Application::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP } Application* application = GetApplicationFromWindowUserData(hwnd); - App::EditorWindow* window = - application != nullptr && application->m_windowManager != nullptr - ? application->m_windowManager->FindWindow(hwnd) - : nullptr; LRESULT dispatcherResult = 0; if (application != nullptr && - window != nullptr && - Host::WindowMessageDispatcher::TryDispatch( + application->m_windowManager != nullptr && + application->m_windowManager->TryDispatchWindowMessage( hwnd, - *application, - *window, message, wParam, lParam, diff --git a/new_editor/src/App/EditorHostCommandBridge.cpp b/new_editor/app/Commands/EditorHostCommandBridge.cpp similarity index 100% rename from new_editor/src/App/EditorHostCommandBridge.cpp rename to new_editor/app/Commands/EditorHostCommandBridge.cpp diff --git a/new_editor/app/State/EditorContext.cpp b/new_editor/app/Composition/EditorContext.cpp similarity index 92% rename from new_editor/app/State/EditorContext.cpp rename to new_editor/app/Composition/EditorContext.cpp index 593c43ea..f409fd2b 100644 --- a/new_editor/app/State/EditorContext.cpp +++ b/new_editor/app/Composition/EditorContext.cpp @@ -14,6 +14,7 @@ namespace { using ::XCEngine::UI::Editor::BuildEditorShellShortcutManager; using ::XCEngine::UI::Editor::UIEditorWorkspacePanelPresentationModel; + std::string ComposeStatusText( std::string_view status, std::string_view message) { @@ -78,6 +79,10 @@ void EditorContext::AttachTextMeasurer( m_shellServices.textMeasurer = &textMeasurer; } +void EditorContext::SetSystemInteractionHost(Ports::SystemInteractionPort* systemInteractionHost) { + m_systemInteractionHost = systemInteractionHost; +} + void EditorContext::BindEditCommandRoutes( EditorEditCommandRoute* hierarchyRoute, EditorEditCommandRoute* projectRoute, @@ -159,6 +164,14 @@ void EditorContext::SyncSessionFromCommandFocusService() { ResolveEditorActionRoute(m_session.activePanelId)); } +Ports::SystemInteractionPort* EditorContext::GetSystemInteractionHost() { + return m_systemInteractionHost; +} + +const Ports::SystemInteractionPort* EditorContext::GetSystemInteractionHost() const { + return m_systemInteractionHost; +} + UIEditorWorkspaceController EditorContext::BuildWorkspaceController() const { return UIEditorWorkspaceController( m_shellAsset.panelRegistry, @@ -176,11 +189,11 @@ UIEditorShellInteractionDefinition EditorContext::BuildShellDefinition( EditorShellVariant variant) const { UIEditorShellInteractionDefinition definition = BuildEditorApplicationShellInteractionDefinition( - m_shellAsset, - workspaceController, - ComposeStatusText(m_lastStatus, m_lastMessage), - captureText, - variant); + m_shellAsset, + workspaceController, + ComposeStatusText(m_lastStatus, m_lastMessage), + captureText, + variant); if (UIEditorWorkspacePanelPresentationModel* scenePresentation = FindMutablePresentation(definition.workspacePresentations, kScenePanelId); @@ -227,4 +240,3 @@ std::string EditorContext::DescribeWorkspaceState( } } // namespace XCEngine::UI::Editor::App - diff --git a/new_editor/app/State/EditorContext.h b/new_editor/app/Composition/EditorContext.h similarity index 90% rename from new_editor/app/State/EditorContext.h rename to new_editor/app/Composition/EditorContext.h index d6aa9b1c..3193c152 100644 --- a/new_editor/app/State/EditorContext.h +++ b/new_editor/app/Composition/EditorContext.h @@ -18,6 +18,10 @@ #include #include +namespace XCEngine::UI::Editor::Ports { +class SystemInteractionPort; +} + namespace XCEngine::UI::Editor::App { class EditorEditCommandRoute; @@ -26,6 +30,7 @@ class EditorContext { public: bool Initialize(const std::filesystem::path& repoRoot); void AttachTextMeasurer(const UIEditorTextMeasurer& textMeasurer); + void SetSystemInteractionHost(Ports::SystemInteractionPort* systemInteractionHost); void BindEditCommandRoutes( EditorEditCommandRoute* hierarchyRoute, EditorEditCommandRoute* projectRoute, @@ -48,6 +53,8 @@ public: void ClearSelection(); void SyncSessionFromSelectionService(); void SyncSessionFromCommandFocusService(); + Ports::SystemInteractionPort* GetSystemInteractionHost(); + const Ports::SystemInteractionPort* GetSystemInteractionHost() const; UIEditorWorkspaceController BuildWorkspaceController() const; const UIEditorShellInteractionServices& GetShellServices() const; @@ -79,9 +86,9 @@ private: EditorProjectRuntime m_projectRuntime = {}; EditorSceneRuntime m_sceneRuntime = {}; EditorHostCommandBridge m_hostCommandBridge = {}; + Ports::SystemInteractionPort* m_systemInteractionHost = nullptr; std::string m_lastStatus = {}; std::string m_lastMessage = {}; }; } // namespace XCEngine::UI::Editor::App - diff --git a/new_editor/app/State/EditorContextStatus.cpp b/new_editor/app/Composition/EditorContextStatus.cpp similarity index 100% rename from new_editor/app/State/EditorContextStatus.cpp rename to new_editor/app/Composition/EditorContextStatus.cpp diff --git a/new_editor/app/Composition/EditorShellRuntime.cpp b/new_editor/app/Composition/EditorShellRuntime.cpp index 81d21e53..682da3f9 100644 --- a/new_editor/app/Composition/EditorShellRuntime.cpp +++ b/new_editor/app/Composition/EditorShellRuntime.cpp @@ -1,8 +1,8 @@ #include "Composition/EditorShellRuntime.h" -#include "Host/TextureHost.h" -#include "Host/ViewportRenderHost.h" -#include "State/EditorContext.h" +#include "Ports/TexturePort.h" +#include "Ports/ViewportRenderPort.h" +#include "Composition/EditorContext.h" #include "Composition/EditorPanelIds.h" @@ -12,11 +12,11 @@ namespace XCEngine::UI::Editor::App { void EditorShellRuntime::Initialize( const std::filesystem::path& repoRoot, - Host::TextureHost& textureHost, + Ports::TexturePort& textureHost, UIEditorTextMeasurer& textMeasurer) { m_textureHost = &textureHost; m_builtInIcons.Initialize(textureHost); - m_sceneViewportController.Initialize(repoRoot, textureHost); + m_sceneViewportFeature.Initialize(repoRoot, textureHost, m_viewportHostService); m_hierarchyPanel.SetBuiltInIcons(&m_builtInIcons); m_projectPanel.SetBuiltInIcons(&m_builtInIcons); m_projectPanel.SetTextMeasurer(&textMeasurer); @@ -24,7 +24,7 @@ void EditorShellRuntime::Initialize( m_projectPanel.Initialize(repoRoot); } -void EditorShellRuntime::AttachViewportWindowRenderer(Host::ViewportRenderHost& renderer) { +void EditorShellRuntime::AttachViewportWindowRenderer(Ports::ViewportRenderPort& renderer) { m_viewportHostService.AttachWindowRenderer(renderer); } @@ -43,7 +43,7 @@ void EditorShellRuntime::Shutdown() { m_traceEntries.clear(); m_sceneEditCommandRoute = {}; if (m_textureHost != nullptr) { - m_sceneViewportController.Shutdown(*m_textureHost); + m_sceneViewportFeature.Shutdown(*m_textureHost, m_viewportHostService); m_builtInIcons.Shutdown(); m_textureHost = nullptr; } else { @@ -57,7 +57,7 @@ void EditorShellRuntime::ResetInteractionState() { m_shellInteractionState = {}; m_splitterDragCorrectionState = {}; m_traceEntries.clear(); - m_sceneViewportController.ResetInteractionState(); + m_sceneViewportFeature.ResetInteractionState(); m_hierarchyPanel.ResetInteractionState(); m_projectPanel.ResetInteractionState(); } diff --git a/new_editor/app/Composition/EditorShellRuntime.h b/new_editor/app/Composition/EditorShellRuntime.h index ca432ce8..c08e359e 100644 --- a/new_editor/app/Composition/EditorShellRuntime.h +++ b/new_editor/app/Composition/EditorShellRuntime.h @@ -7,7 +7,7 @@ #include "Features/Inspector/InspectorPanel.h" #include "Features/Project/ProjectPanel.h" #include "Features/Scene/SceneEditCommandRoute.h" -#include "Features/Scene/SceneViewportController.h" +#include "Features/Scene/SceneViewportFeature.h" #include "Rendering/Assets/BuiltInIcons.h" #include "Rendering/Viewport/ViewportHostService.h" #include "Composition/WorkspaceEventSync.h" @@ -29,12 +29,12 @@ class EditorContext; } // namespace XCEngine::UI::Editor::App -namespace XCEngine::UI::Editor::Host { +namespace XCEngine::UI::Editor::Ports { -class TextureHost; -class ViewportRenderHost; +class TexturePort; +class ViewportRenderPort; -} // namespace XCEngine::UI::Editor::Host +} // namespace XCEngine::UI::Editor::Ports namespace XCEngine::Rendering { @@ -48,11 +48,11 @@ class EditorShellRuntime { public: void Initialize( const std::filesystem::path& repoRoot, - Host::TextureHost& textureHost, + Ports::TexturePort& textureHost, UIEditorTextMeasurer& textMeasurer); void Shutdown(); void ResetInteractionState(); - void AttachViewportWindowRenderer(Host::ViewportRenderHost& renderer); + void AttachViewportWindowRenderer(Ports::ViewportRenderPort& renderer); void DetachViewportWindowRenderer(); void SetViewportSurfacePresentationEnabled(bool enabled); @@ -90,14 +90,14 @@ public: private: ViewportHostService m_viewportHostService = {}; + SceneViewportFeature m_sceneViewportFeature = {}; BuiltInIcons m_builtInIcons = {}; - Host::TextureHost* m_textureHost = nullptr; + Ports::TexturePort* m_textureHost = nullptr; ConsolePanel m_consolePanel = {}; HierarchyPanel m_hierarchyPanel = {}; InspectorPanel m_inspectorPanel = {}; ProjectPanel m_projectPanel = {}; SceneEditCommandRoute m_sceneEditCommandRoute = {}; - SceneViewportController m_sceneViewportController = {}; UIEditorShellInteractionState m_shellInteractionState = {}; UIEditorShellInteractionFrame m_shellFrame = {}; std::vector m_traceEntries = {}; diff --git a/new_editor/app/Composition/EditorShellRuntimeRendering.cpp b/new_editor/app/Composition/EditorShellRuntimeRendering.cpp index 5804578b..f6c263cf 100644 --- a/new_editor/app/Composition/EditorShellRuntimeRendering.cpp +++ b/new_editor/app/Composition/EditorShellRuntimeRendering.cpp @@ -75,7 +75,7 @@ void EditorShellRuntime::Append(UIDrawList& drawList) const { m_hierarchyPanel.Append(drawList); m_inspectorPanel.Append(drawList); m_projectPanel.Append(drawList); - m_sceneViewportController.Append(drawList); + m_sceneViewportFeature.Append(drawList); AppendUIEditorShellComposeOverlay( drawList, m_shellFrame.shellFrame, diff --git a/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp b/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp index 67e8a9c8..b0af3905 100644 --- a/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp +++ b/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp @@ -1,6 +1,7 @@ #include "Composition/EditorShellRuntimeInternal.h" -#include "State/EditorContext.h" +#include "Features/PanelInputContext.h" +#include "Composition/EditorContext.h" #include "Composition/EditorPanelIds.h" #include @@ -27,6 +28,23 @@ bool IsPointerInputEventType(UIInputEventType type) { } } +PanelInputContext BuildHostedPanelInputContext( + const UIEditorWorkspaceInteractionFrame& workspaceFrame, + bool allowInteraction, + std::string_view panelId) { + PanelInputContext inputContext = {}; + inputContext.allowInteraction = allowInteraction; + inputContext.hasInputFocus = + IsUIEditorWorkspaceHostedPanelInputOwner(workspaceFrame.inputOwner, panelId); + inputContext.focusGained = + !IsUIEditorWorkspaceHostedPanelInputOwner(workspaceFrame.previousInputOwner, panelId) && + inputContext.hasInputFocus; + inputContext.focusLost = + IsUIEditorWorkspaceHostedPanelInputOwner(workspaceFrame.previousInputOwner, panelId) && + !inputContext.hasInputFocus; + return inputContext; +} + } // namespace std::vector FilterShellInputEventsForHostedContentCapture( @@ -125,11 +143,10 @@ void EditorShellRuntime::Update( m_hierarchyPanel.SetSceneRuntime(&context.GetSceneRuntime()); m_hierarchyPanel.SetCommandFocusService(&context.GetCommandFocusService()); m_sceneEditCommandRoute.BindSceneRuntime(&context.GetSceneRuntime()); - m_sceneViewportController.SetCommandFocusService(&context.GetCommandFocusService()); + m_sceneViewportFeature.SetCommandFocusService(&context.GetCommandFocusService()); // Keep the previous render request available for readback-based picking during // this update, then refresh it again after camera/navigation state changes. - m_viewportHostService.SetSceneViewportRenderRequest( - context.GetSceneRuntime().BuildSceneViewportRenderRequest()); + m_sceneViewportFeature.SyncRenderRequest(context.GetSceneRuntime()); context.BindEditCommandRoutes( &m_hierarchyPanel, &m_projectPanel, @@ -179,39 +196,46 @@ void EditorShellRuntime::Update( FilterHostedContentInputEventsForShellOwnership( inputEvents, shellOwnsHostedContentPointerStream); - m_sceneViewportController.Update( + m_sceneViewportFeature.Update( context.GetSceneRuntime(), - m_viewportHostService, m_shellInteractionState.workspaceInteractionState.composeState, m_shellFrame.workspaceInteractionFrame.composeFrame); - m_viewportHostService.SetSceneViewportRenderRequest( - context.GetSceneRuntime().BuildSceneViewportRenderRequest()); ApplyViewportFramesToShellFrame(m_shellFrame, m_viewportHostService); context.SyncSessionFromWorkspace(workspaceController); context.UpdateStatusFromShellResult(workspaceController, m_shellFrame.result); - const std::string& activePanelId = workspaceController.GetWorkspace().activePanelId; + const bool allowHostedInteraction = !m_shellFrame.result.workspaceInputSuppressed; + const PanelInputContext hierarchyInputContext = BuildHostedPanelInputContext( + m_shellFrame.workspaceInteractionFrame, + allowHostedInteraction, + kHierarchyPanelId); + const PanelInputContext projectInputContext = BuildHostedPanelInputContext( + m_shellFrame.workspaceInteractionFrame, + allowHostedInteraction, + kProjectPanelId); + const PanelInputContext inspectorInputContext = BuildHostedPanelInputContext( + m_shellFrame.workspaceInteractionFrame, + allowHostedInteraction, + kInspectorPanelId); m_projectPanel.SetProjectRuntime(&context.GetProjectRuntime()); m_projectPanel.SetCommandFocusService(&context.GetCommandFocusService()); + m_projectPanel.SetSystemInteractionHost(context.GetSystemInteractionHost()); m_inspectorPanel.SetCommandFocusService(&context.GetCommandFocusService()); m_hierarchyPanel.Update( m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, hostedContentEvents, - !m_shellFrame.result.workspaceInputSuppressed, - activePanelId == kHierarchyPanelId); + hierarchyInputContext); m_projectPanel.Update( m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, hostedContentEvents, - !m_shellFrame.result.workspaceInputSuppressed, - activePanelId == kProjectPanelId); + projectInputContext); m_traceEntries = SyncWorkspaceEvents(context, *this); m_inspectorPanel.Update( context.GetSession(), context.GetSceneRuntime(), m_shellFrame.workspaceInteractionFrame.composeFrame.contentHostFrame, hostedContentEvents, - !m_shellFrame.result.workspaceInputSuppressed, - activePanelId == kInspectorPanelId); + inspectorInputContext); context.SyncSessionFromCommandFocusService(); m_consolePanel.Update( context.GetSession(), diff --git a/new_editor/app/Composition/EditorShellRuntimeViewport.cpp b/new_editor/app/Composition/EditorShellRuntimeViewport.cpp index 9f31a2a9..61487146 100644 --- a/new_editor/app/Composition/EditorShellRuntimeViewport.cpp +++ b/new_editor/app/Composition/EditorShellRuntimeViewport.cpp @@ -10,12 +10,6 @@ bool IsViewportPanel(std::string_view panelId) { return IsEditorViewportPanelId(panelId); } -ViewportKind ResolveViewportKind(std::string_view panelId) { - return panelId == kGamePanelId - ? ViewportKind::Game - : ViewportKind::Scene; -} - void ApplyViewportFrameToPresentation( const ViewportFrame& viewportFrame, UIEditorWorkspacePanelPresentationModel& presentation) { @@ -62,7 +56,7 @@ void ApplyViewportFramesToShellFrame( const ViewportFrame viewportFrame = viewportHostService.RequestViewport( - ResolveViewportKind(viewportComposeFrame.panelId), + viewportComposeFrame.panelId, viewportComposeFrame.viewportShellFrame.requestedViewportSize); ApplyViewportFrameToShellModel( viewportFrame, diff --git a/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceStore.cpp b/new_editor/app/Composition/EditorWindowWorkspaceStore.cpp similarity index 98% rename from new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceStore.cpp rename to new_editor/app/Composition/EditorWindowWorkspaceStore.cpp index 26af2f79..d2a0ec83 100644 --- a/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceStore.cpp +++ b/new_editor/app/Composition/EditorWindowWorkspaceStore.cpp @@ -1,4 +1,4 @@ -#include "Platform/Win32/WindowManager/EditorWindowWorkspaceStore.h" +#include "Composition/EditorWindowWorkspaceStore.h" #include #include diff --git a/new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceStore.h b/new_editor/app/Composition/EditorWindowWorkspaceStore.h similarity index 100% rename from new_editor/app/Platform/Win32/WindowManager/EditorWindowWorkspaceStore.h rename to new_editor/app/Composition/EditorWindowWorkspaceStore.h diff --git a/new_editor/app/Composition/WorkspaceEventSync.cpp b/new_editor/app/Composition/WorkspaceEventSync.cpp index 6f38bb94..edf942ba 100644 --- a/new_editor/app/Composition/WorkspaceEventSync.cpp +++ b/new_editor/app/Composition/WorkspaceEventSync.cpp @@ -1,6 +1,6 @@ #include "Composition/WorkspaceEventSync.h" -#include "State/EditorContext.h" +#include "Composition/EditorContext.h" #include "Features/Hierarchy/HierarchyPanel.h" #include "Features/Project/ProjectPanel.h" #include "Composition/EditorShellRuntime.h" diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp index 7a9353b4..6f84ad73 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp +++ b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp @@ -488,16 +488,17 @@ UIEditorHostCommandDispatchResult HierarchyPanel::DispatchEditCommand( void HierarchyPanel::ProcessDragAndFrameEvents( const std::vector& inputEvents, const UIRect& bounds, - bool allowInteraction, - bool panelActive) { - const std::vector filteredEvents = FilterUIEditorTreePanelInputEvents( + const PanelInputContext& inputContext) { + const std::vector filteredEvents = BuildUIEditorTreePanelInputEvents( bounds, inputEvents, UIEditorTreePanelInputFilterOptions{ - .allowInteraction = allowInteraction, - .panelActive = panelActive, + .allowInteraction = inputContext.allowInteraction, + .hasInputFocus = inputContext.hasInputFocus, .captureActive = HasActivePointerCapture() - }); + }, + inputContext.focusGained, + inputContext.focusLost); if (m_treeFrame.result.selectionChanged) { SyncSceneRuntimeSelectionFromTree(); @@ -570,8 +571,7 @@ void HierarchyPanel::ProcessDragAndFrameEvents( void HierarchyPanel::Update( const UIEditorPanelContentHostFrame& contentHostFrame, const std::vector& inputEvents, - bool allowInteraction, - bool panelActive) { + const PanelInputContext& inputContext) { ResetTransientState(); const UIEditorPanelContentHostPanelState* panelState = @@ -593,16 +593,18 @@ void HierarchyPanel::Update( } m_visible = true; - const std::vector filteredEvents = FilterUIEditorTreePanelInputEvents( + const std::vector filteredEvents = BuildUIEditorTreePanelInputEvents( panelState->bounds, inputEvents, UIEditorTreePanelInputFilterOptions{ - .allowInteraction = allowInteraction, - .panelActive = panelActive, + .allowInteraction = inputContext.allowInteraction, + .hasInputFocus = inputContext.hasInputFocus, .captureActive = HasActivePointerCapture() - }); + }, + inputContext.focusGained, + inputContext.focusLost); SyncTreeFocusState(filteredEvents); - ClaimCommandFocus(filteredEvents, panelState->bounds, allowInteraction); + ClaimCommandFocus(filteredEvents, panelState->bounds, inputContext.allowInteraction); const Widgets::UIEditorTreeViewMetrics treeMetrics = ResolveUIEditorTreeViewMetrics(); @@ -648,8 +650,7 @@ void HierarchyPanel::Update( ProcessDragAndFrameEvents( inputEvents, panelState->bounds, - allowInteraction, - panelActive); + inputContext); } void HierarchyPanel::Append(UIDrawList& drawList) const { diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanel.h b/new_editor/app/Features/Hierarchy/HierarchyPanel.h index 1e125ffe..1ec4c3b3 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanel.h +++ b/new_editor/app/Features/Hierarchy/HierarchyPanel.h @@ -3,6 +3,7 @@ #include "HierarchyModel.h" #include "Commands/EditorEditCommandRoute.h" +#include "Features/PanelInputContext.h" #include #include #include @@ -47,8 +48,7 @@ public: void Update( const UIEditorPanelContentHostFrame& contentHostFrame, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - bool allowInteraction, - bool panelActive); + const PanelInputContext& inputContext); void Append(::XCEngine::UI::UIDrawList& drawList) const; bool WantsHostPointerCapture() const; bool WantsHostPointerRelease() const; @@ -68,8 +68,7 @@ private: void ProcessDragAndFrameEvents( const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, const ::XCEngine::UI::UIRect& bounds, - bool allowInteraction, - bool panelActive); + const PanelInputContext& inputContext); const HierarchyNode* GetSelectedNode() const; void SyncTreeSelectionFromSceneRuntime(); void SyncSceneRuntimeSelectionFromTree(); diff --git a/new_editor/app/Features/Inspector/InspectorPanel.cpp b/new_editor/app/Features/Inspector/InspectorPanel.cpp index cc7f98a1..c05455ee 100644 --- a/new_editor/app/Features/Inspector/InspectorPanel.cpp +++ b/new_editor/app/Features/Inspector/InspectorPanel.cpp @@ -268,8 +268,7 @@ void InspectorPanel::Update( EditorSceneRuntime& sceneRuntime, const UIEditorPanelContentHostFrame& contentHostFrame, const std::vector& inputEvents, - bool allowInteraction, - bool panelActive) { + const PanelInputContext& inputContext) { const UIEditorPanelContentHostPanelState* panelState = FindMountedInspectorPanel(contentHostFrame); if (panelState == nullptr) { @@ -302,17 +301,23 @@ void InspectorPanel::Update( } const std::vector filteredEvents = - FilterUIEditorPanelInputEvents( + BuildUIEditorPanelInputEvents( m_bounds, inputEvents, UIEditorPanelInputFilterOptions{ - .allowPointerInBounds = allowInteraction, + .allowPointerInBounds = inputContext.allowInteraction, .allowPointerWhileCaptured = false, - .allowKeyboardInput = panelActive, - .allowFocusEvents = panelActive, - .includePointerLeave = allowInteraction || panelActive - }); - ClaimCommandFocus(filteredEvents, allowInteraction); + .allowKeyboardInput = inputContext.hasInputFocus, + .allowFocusEvents = + inputContext.hasInputFocus || + inputContext.focusGained || + inputContext.focusLost, + .includePointerLeave = + inputContext.allowInteraction || inputContext.hasInputFocus + }, + inputContext.focusGained, + inputContext.focusLost); + ClaimCommandFocus(filteredEvents, inputContext.allowInteraction); m_gridFrame = UpdateUIEditorPropertyGridInteraction( m_interactionState, m_fieldSelection, diff --git a/new_editor/app/Features/Inspector/InspectorPanel.h b/new_editor/app/Features/Inspector/InspectorPanel.h index 7f055d9c..3e57f774 100644 --- a/new_editor/app/Features/Inspector/InspectorPanel.h +++ b/new_editor/app/Features/Inspector/InspectorPanel.h @@ -4,6 +4,7 @@ #include "Features/Inspector/InspectorSubject.h" #include "Commands/EditorEditCommandRoute.h" +#include "Features/PanelInputContext.h" #include #include @@ -29,8 +30,7 @@ public: EditorSceneRuntime& sceneRuntime, const UIEditorPanelContentHostFrame& contentHostFrame, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - bool allowInteraction, - bool panelActive); + const PanelInputContext& inputContext); void Append(::XCEngine::UI::UIDrawList& drawList) const; UIEditorHostCommandEvaluationResult EvaluateEditCommand( diff --git a/new_editor/app/Features/PanelInputContext.h b/new_editor/app/Features/PanelInputContext.h new file mode 100644 index 00000000..f080b2c9 --- /dev/null +++ b/new_editor/app/Features/PanelInputContext.h @@ -0,0 +1,12 @@ +#pragma once + +namespace XCEngine::UI::Editor::App { + +struct PanelInputContext { + bool allowInteraction = false; + bool hasInputFocus = false; + bool focusGained = false; + bool focusLost = false; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Project/ProjectBrowserModel.cpp b/new_editor/app/Features/Project/ProjectBrowserModel.cpp index 585f270a..e48f01f5 100644 --- a/new_editor/app/Features/Project/ProjectBrowserModel.cpp +++ b/new_editor/app/Features/Project/ProjectBrowserModel.cpp @@ -1,8 +1,6 @@ #include "ProjectBrowserModel.h" #include "ProjectBrowserModelInternal.h" -#include "Internal/StringEncoding.h" - #include namespace XCEngine::UI::Editor::App { @@ -238,9 +236,9 @@ bool ProjectBrowserModel::CreateMaterial( } std::filesystem::path requestedPath = - App::Internal::Utf8ToWide(trimmedName); + BuildPathFromUtf8(trimmedName); if (!requestedPath.has_extension()) { - requestedPath += L".mat"; + requestedPath.replace_extension(".mat"); } const std::filesystem::path materialPath = @@ -317,7 +315,7 @@ bool ProjectBrowserModel::RenameItem( } const std::filesystem::path destinationPath = - (sourcePath->parent_path() / App::Internal::Utf8ToWide(targetName)) + (sourcePath->parent_path() / BuildPathFromUtf8(targetName)) .lexically_normal(); const std::string destinationItemId = BuildRelativeItemId(destinationPath, m_assetsRootPath); diff --git a/new_editor/app/Features/Project/ProjectBrowserModelInternal.cpp b/new_editor/app/Features/Project/ProjectBrowserModelInternal.cpp index 655a4b0b..fcc4dabf 100644 --- a/new_editor/app/Features/Project/ProjectBrowserModelInternal.cpp +++ b/new_editor/app/Features/Project/ProjectBrowserModelInternal.cpp @@ -1,7 +1,5 @@ #include "ProjectBrowserModelInternal.h" -#include "Internal/StringEncoding.h" - #include #include #include @@ -10,6 +8,28 @@ namespace XCEngine::UI::Editor::App::ProjectBrowserModelInternal { +namespace { + +std::string BuildUtf8String(std::u8string_view value) { + std::string result = {}; + result.reserve(value.size()); + for (const char8_t character : value) { + result.push_back(static_cast(character)); + } + return result; +} + +std::u8string BuildU8String(std::string_view value) { + std::u8string result = {}; + result.reserve(value.size()); + for (const char character : value) { + result.push_back(static_cast(character)); + } + return result; +} + +} // namespace + std::string ToLowerCopy(std::string value) { std::transform( value.begin(), @@ -22,7 +42,11 @@ std::string ToLowerCopy(std::string value) { } std::string PathToUtf8String(const std::filesystem::path& path) { - return App::Internal::WideToUtf8(path.native()); + return BuildUtf8String(path.u8string()); +} + +std::filesystem::path BuildPathFromUtf8(std::string_view value) { + return std::filesystem::path(BuildU8String(value)); } std::string NormalizePathSeparators(std::string value) { @@ -168,14 +192,14 @@ std::filesystem::path MakeUniqueFolderPath( const std::filesystem::path& parentPath, std::string_view preferredName) { std::filesystem::path candidatePath = - parentPath / App::Internal::Utf8ToWide(std::string(preferredName)); + parentPath / BuildPathFromUtf8(preferredName); if (!std::filesystem::exists(candidatePath)) { return candidatePath; } for (std::size_t suffix = 1u;; ++suffix) { candidatePath = - parentPath / App::Internal::Utf8ToWide( + parentPath / BuildPathFromUtf8( std::string(preferredName) + " " + std::to_string(suffix)); if (!std::filesystem::exists(candidatePath)) { return candidatePath; @@ -212,7 +236,7 @@ std::string BuildRenamedEntryName( } const std::filesystem::path requestedPath = - App::Internal::Utf8ToWide(std::string(requestedName)); + BuildPathFromUtf8(requestedName); if (requestedPath.has_extension()) { return PathToUtf8String(requestedPath.filename()); } diff --git a/new_editor/app/Features/Project/ProjectBrowserModelInternal.h b/new_editor/app/Features/Project/ProjectBrowserModelInternal.h index 31e462db..5607dc64 100644 --- a/new_editor/app/Features/Project/ProjectBrowserModelInternal.h +++ b/new_editor/app/Features/Project/ProjectBrowserModelInternal.h @@ -13,6 +13,7 @@ inline constexpr std::string_view kAssetsRootId = "Assets"; std::string ToLowerCopy(std::string value); std::string PathToUtf8String(const std::filesystem::path& path); +std::filesystem::path BuildPathFromUtf8(std::string_view value); std::string NormalizePathSeparators(std::string value); std::string BuildRelativeItemId( const std::filesystem::path& path, diff --git a/new_editor/app/Features/Project/ProjectPanel.cpp b/new_editor/app/Features/Project/ProjectPanel.cpp index 1a3d9714..b2e1cad9 100644 --- a/new_editor/app/Features/Project/ProjectPanel.cpp +++ b/new_editor/app/Features/Project/ProjectPanel.cpp @@ -1,5 +1,6 @@ #include "ProjectPanelInternal.h" +#include "Ports/SystemInteractionPort.h" #include "Project/EditorProjectRuntime.h" #include "State/EditorCommandFocusService.h" @@ -8,14 +9,9 @@ #include #include -#include "Internal/StringEncoding.h" - +#include #include -#include -#include - -#include #include #include #include @@ -23,6 +19,7 @@ namespace XCEngine::UI::Editor::App { using namespace ProjectPanelInternal; +using ::XCEngine::Input::KeyCode; namespace GridDrag = XCEngine::UI::Editor::Collections::GridDragDrop; namespace TreeDrag = XCEngine::UI::Editor::Collections::TreeDragDrop; @@ -50,93 +47,7 @@ bool HasValidBounds(const UIRect& bounds) { return bounds.width > 0.0f && bounds.height > 0.0f; } -bool CopyTextToClipboard(std::string_view text) { - if (text.empty()) { - return false; - } - - const std::wstring wideText = - App::Internal::Utf8ToWide(std::string(text)); - const std::size_t byteCount = - (wideText.size() + 1u) * sizeof(wchar_t); - if (!OpenClipboard(nullptr)) { - return false; - } - - struct ClipboardCloser final { - ~ClipboardCloser() { - CloseClipboard(); - } - } clipboardCloser = {}; - - if (!EmptyClipboard()) { - return false; - } - - HGLOBAL handle = GlobalAlloc(GMEM_MOVEABLE, byteCount); - if (handle == nullptr) { - return false; - } - - void* locked = GlobalLock(handle); - if (locked == nullptr) { - GlobalFree(handle); - return false; - } - - std::memcpy(locked, wideText.c_str(), byteCount); - GlobalUnlock(handle); - - if (SetClipboardData(CF_UNICODETEXT, handle) == nullptr) { - GlobalFree(handle); - return false; - } - - return true; -} - -bool ShowPathInExplorer( - const std::filesystem::path& path, - bool selectTarget) { - if (path.empty()) { - return false; - } - - namespace fs = std::filesystem; - - std::error_code errorCode = {}; - const fs::path targetPath = path.lexically_normal(); - if (!fs::exists(targetPath, errorCode) || errorCode) { - return false; - } - - HINSTANCE result = nullptr; - if (selectTarget) { - const std::wstring parameters = - L"/select,\"" + targetPath.native() + L"\""; - const std::wstring workingDirectory = - targetPath.parent_path().native(); - result = ShellExecuteW( - nullptr, - L"open", - L"explorer.exe", - parameters.c_str(), - workingDirectory.empty() ? nullptr : workingDirectory.c_str(), - SW_SHOWNORMAL); - } else { - const std::wstring workingDirectory = - targetPath.parent_path().native(); - result = ShellExecuteW( - nullptr, - L"open", - targetPath.c_str(), - nullptr, - workingDirectory.empty() ? nullptr : workingDirectory.c_str(), - SW_SHOWNORMAL); - } - - return reinterpret_cast(result) > 32; -} +constexpr auto kGridDoubleClickInterval = std::chrono::milliseconds(400); Widgets::UIEditorMenuPopupItem BuildContextMenuCommandItem( std::string itemId, @@ -218,6 +129,11 @@ void ProjectPanel::SetCommandFocusService( m_commandFocusService = commandFocusService; } +void ProjectPanel::SetSystemInteractionHost( + Ports::SystemInteractionPort* systemInteractionHost) { + m_systemInteractionHost = systemInteractionHost; +} + void ProjectPanel::SetBuiltInIcons(const BuiltInIcons* icons) { m_icons = icons; if (EditorProjectRuntime* runtime = ResolveProjectRuntime(); @@ -241,6 +157,7 @@ void ProjectPanel::ResetInteractionState() { m_layout = {}; m_hoveredAssetItemId.clear(); m_lastPrimaryClickedAssetId.clear(); + m_lastPrimaryClickTime = {}; m_hoveredBreadcrumbIndex = kInvalidLayoutIndex; m_pressedBreadcrumbIndex = kInvalidLayoutIndex; m_assetDropTargetSurface = DropTargetSurface::None; @@ -844,7 +761,7 @@ bool ProjectPanel::HandleContextMenuEvent(const UIInputEvent& event) { return false; case UIInputEventType::KeyDown: - if (event.keyCode == VK_ESCAPE) { + if (event.keyCode == static_cast(KeyCode::Escape)) { CloseContextMenu(); return true; } @@ -956,19 +873,25 @@ void ProjectPanel::EmitSelectionClearedEvent(EventSource source) { std::vector ProjectPanel::BuildTreeInteractionInputEvents( const std::vector& inputEvents, const UIRect& bounds, - bool allowInteraction, - bool panelActive) const { + const PanelInputContext& inputContext) const { const std::vector rawEvents = - FilterUIEditorPanelInputEvents( + BuildUIEditorPanelInputEvents( bounds, inputEvents, UIEditorPanelInputFilterOptions{ - .allowPointerInBounds = allowInteraction, + .allowPointerInBounds = inputContext.allowInteraction, .allowPointerWhileCaptured = HasActivePointerCapture(), - .allowKeyboardInput = panelActive, - .allowFocusEvents = panelActive || HasActivePointerCapture(), - .includePointerLeave = allowInteraction || HasActivePointerCapture() - }); + .allowKeyboardInput = inputContext.hasInputFocus, + .allowFocusEvents = + inputContext.hasInputFocus || + HasActivePointerCapture() || + inputContext.focusGained || + inputContext.focusLost, + .includePointerLeave = + inputContext.allowInteraction || HasActivePointerCapture() + }, + inputContext.focusGained, + inputContext.focusLost); const Widgets::UIEditorTreeViewLayout layout = m_treeFrame.layout.bounds.width > 0.0f @@ -1022,6 +945,9 @@ UIEditorHostCommandEvaluationResult ProjectPanel::EvaluateAssetCommand( if (target.subjectRelativePath.empty()) { return BuildEvaluationResult(false, "Project has no selected item or current folder path."); } + if (m_systemInteractionHost == nullptr) { + return BuildEvaluationResult(false, "Project system host is unavailable."); + } return BuildEvaluationResult( true, @@ -1032,6 +958,9 @@ UIEditorHostCommandEvaluationResult ProjectPanel::EvaluateAssetCommand( if (target.subjectItemId.empty() || target.subjectDisplayName.empty()) { return BuildEvaluationResult(false, "Project has no selected item or current folder."); } + if (m_systemInteractionHost == nullptr) { + return BuildEvaluationResult(false, "Project system host is unavailable."); + } return BuildEvaluationResult( true, @@ -1065,7 +994,7 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchAssetCommand( SyncCurrentFolderSelection(); m_hoveredAssetItemId.clear(); m_lastPrimaryClickedAssetId = std::string(createdItemId); - m_lastPrimaryClickTimeMs = 0u; + m_lastPrimaryClickTime = {}; ResolveProjectRuntime()->SetSelection(createdItemId); SyncAssetSelectionFromRuntime(); @@ -1152,7 +1081,8 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchAssetCommand( return BuildDispatchResult(false, "Project has no selected item or current folder path."); } - if (!CopyTextToClipboard(target.subjectRelativePath)) { + if (m_systemInteractionHost == nullptr || + !m_systemInteractionHost->CopyTextToClipboard(target.subjectRelativePath)) { return BuildDispatchResult(false, "Failed to copy the project path to the clipboard."); } @@ -1166,7 +1096,10 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchAssetCommand( return BuildDispatchResult(false, "Project has no selected item or current folder."); } - if (!ShowPathInExplorer(target.subjectPath, target.showInExplorerSelectTarget)) { + if (m_systemInteractionHost == nullptr || + !m_systemInteractionHost->RevealPathInFileBrowser( + target.subjectPath, + target.showInExplorerSelectTarget)) { return BuildDispatchResult(false, "Failed to reveal the target path in Explorer."); } @@ -1285,6 +1218,7 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchEditCommand( SyncAssetSelectionFromRuntime(); m_hoveredAssetItemId.clear(); m_lastPrimaryClickedAssetId.clear(); + m_lastPrimaryClickTime = {}; if (hadAssetSelection && !ResolveProjectRuntime()->HasSelection()) { EmitSelectionClearedEvent(EventSource::GridPrimary); } @@ -1342,8 +1276,7 @@ void ProjectPanel::ClaimCommandFocus( void ProjectPanel::Update( const UIEditorPanelContentHostFrame& contentHostFrame, const std::vector& inputEvents, - bool allowInteraction, - bool panelActive) { + const PanelInputContext& inputContext) { m_requestPointerCapture = false; m_requestPointerRelease = false; m_frameEvents.clear(); @@ -1385,17 +1318,24 @@ void ProjectPanel::Update( m_visible = true; SyncAssetSelectionFromRuntime(); const std::vector filteredEvents = - FilterUIEditorPanelInputEvents( + BuildUIEditorPanelInputEvents( panelState->bounds, inputEvents, UIEditorPanelInputFilterOptions{ - .allowPointerInBounds = allowInteraction, + .allowPointerInBounds = inputContext.allowInteraction, .allowPointerWhileCaptured = HasActivePointerCapture(), - .allowKeyboardInput = panelActive, - .allowFocusEvents = panelActive || HasActivePointerCapture(), - .includePointerLeave = allowInteraction || HasActivePointerCapture() - }); - ClaimCommandFocus(filteredEvents, panelState->bounds, allowInteraction); + .allowKeyboardInput = inputContext.hasInputFocus, + .allowFocusEvents = + inputContext.hasInputFocus || + HasActivePointerCapture() || + inputContext.focusGained || + inputContext.focusLost, + .includePointerLeave = + inputContext.allowInteraction || HasActivePointerCapture() + }, + inputContext.focusGained, + inputContext.focusLost); + ClaimCommandFocus(filteredEvents, panelState->bounds, inputContext.allowInteraction); m_navigationWidth = ClampNavigationWidth(m_navigationWidth, panelState->bounds.width); m_layout = BuildLayout(panelState->bounds); @@ -1426,8 +1366,7 @@ void ProjectPanel::Update( BuildTreeInteractionInputEvents( inputEvents, panelState->bounds, - allowInteraction, - panelActive); + inputContext); m_treeFrame = UpdateUIEditorTreeViewInteraction( m_treeInteractionState, m_folderSelection, @@ -1631,7 +1570,7 @@ void ProjectPanel::Update( ClearRenameState(); m_hoveredAssetItemId.clear(); m_lastPrimaryClickedAssetId.clear(); - m_lastPrimaryClickTimeMs = 0u; + m_lastPrimaryClickTime = {}; SyncCurrentFolderSelection(); const std::string movedItemId = assetDragCallbacks.movedItemId.empty() @@ -1759,17 +1698,15 @@ void ProjectPanel::Update( EmitEvent(EventKind::AssetSelected, EventSource::GridPrimary, &assetEntry); } - const std::uint64_t nowMs = GetTickCount64(); - const std::uint64_t doubleClickThresholdMs = - static_cast(GetDoubleClickTime()); + const auto now = std::chrono::steady_clock::now(); const bool doubleClicked = alreadySelected && m_lastPrimaryClickedAssetId == assetEntry.itemId && - nowMs >= m_lastPrimaryClickTimeMs && - nowMs - m_lastPrimaryClickTimeMs <= doubleClickThresholdMs; + m_lastPrimaryClickTime != std::chrono::steady_clock::time_point{} && + now - m_lastPrimaryClickTime <= kGridDoubleClickInterval; m_lastPrimaryClickedAssetId = assetEntry.itemId; - m_lastPrimaryClickTimeMs = nowMs; + m_lastPrimaryClickTime = now; if (!doubleClicked) { break; } diff --git a/new_editor/app/Features/Project/ProjectPanel.h b/new_editor/app/Features/Project/ProjectPanel.h index 1fd7c376..3893169a 100644 --- a/new_editor/app/Features/Project/ProjectPanel.h +++ b/new_editor/app/Features/Project/ProjectPanel.h @@ -2,6 +2,7 @@ #include "Project/EditorProjectRuntime.h" #include "ProjectBrowserModel.h" +#include "Features/PanelInputContext.h" #include "Commands/EditorEditCommandRoute.h" #include @@ -16,6 +17,7 @@ #include #include +#include #include #include #include @@ -28,6 +30,17 @@ namespace XCEngine::UI::Editor::App { class BuiltInIcons; class EditorCommandFocusService; + +} // namespace XCEngine::UI::Editor::App + +namespace XCEngine::UI::Editor::Ports { + +class SystemInteractionPort; + +} // namespace XCEngine::UI::Editor::Ports + +namespace XCEngine::UI::Editor::App { + class ProjectPanel final : public EditorEditCommandRoute { public: enum class CursorKind : std::uint8_t { @@ -71,14 +84,14 @@ public: void Initialize(const std::filesystem::path& repoRoot); void SetProjectRuntime(EditorProjectRuntime* projectRuntime); void SetCommandFocusService(EditorCommandFocusService* commandFocusService); + void SetSystemInteractionHost(Ports::SystemInteractionPort* systemInteractionHost); void SetBuiltInIcons(const BuiltInIcons* icons); void SetTextMeasurer(const ::XCEngine::UI::Editor::UIEditorTextMeasurer* textMeasurer); void ResetInteractionState(); void Update( const UIEditorPanelContentHostFrame& contentHostFrame, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - bool allowInteraction, - bool panelActive); + const PanelInputContext& inputContext); void Append(::XCEngine::UI::UIDrawList& drawList) const; CursorKind GetCursorKind() const; @@ -205,8 +218,7 @@ private: std::vector<::XCEngine::UI::UIInputEvent> BuildTreeInteractionInputEvents( const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, const ::XCEngine::UI::UIRect& bounds, - bool allowInteraction, - bool panelActive) const; + const PanelInputContext& inputContext) const; void ClaimCommandFocus( const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, const ::XCEngine::UI::UIRect& bounds, @@ -231,6 +243,7 @@ private: std::unique_ptr m_ownedProjectRuntime = {}; EditorProjectRuntime* m_projectRuntime = nullptr; EditorCommandFocusService* m_commandFocusService = nullptr; + Ports::SystemInteractionPort* m_systemInteractionHost = nullptr; const BuiltInIcons* m_icons = nullptr; const ::XCEngine::UI::Editor::UIEditorTextMeasurer* m_textMeasurer = nullptr; ::XCEngine::UI::Widgets::UISelectionModel m_folderSelection = {}; @@ -252,7 +265,7 @@ private: std::string m_hoveredAssetItemId = {}; std::string m_lastPrimaryClickedAssetId = {}; float m_navigationWidth = 248.0f; - std::uint64_t m_lastPrimaryClickTimeMs = 0u; + std::chrono::steady_clock::time_point m_lastPrimaryClickTime = {}; std::size_t m_hoveredBreadcrumbIndex = static_cast(-1); std::size_t m_pressedBreadcrumbIndex = static_cast(-1); bool m_visible = false; diff --git a/new_editor/app/Features/Scene/SceneViewportController.cpp b/new_editor/app/Features/Scene/SceneViewportController.cpp index dd557edb..956289ee 100644 --- a/new_editor/app/Features/Scene/SceneViewportController.cpp +++ b/new_editor/app/Features/Scene/SceneViewportController.cpp @@ -1,6 +1,6 @@ #include "Features/Scene/SceneViewportController.h" -#include "Rendering/Viewport/ViewportHostService.h" +#include "Rendering/Viewport/ViewportObjectPickerService.h" #include "Scene/EditorSceneRuntime.h" #include "State/EditorCommandFocusService.h" @@ -106,12 +106,12 @@ bool TryResolveToolModeShortcut( void ApplySceneToolMode( SceneToolMode mode, EditorSceneRuntime& sceneRuntime, - LegacySceneViewportGizmo& legacyGizmo) { + SceneViewportTransformGizmo& transformGizmo) { if (sceneRuntime.GetToolMode() == mode) { return; } - legacyGizmo.CancelDrag(sceneRuntime); + transformGizmo.CancelDrag(sceneRuntime); sceneRuntime.SetToolMode(mode); } @@ -119,12 +119,12 @@ void ApplySceneToolMode( void SceneViewportController::Initialize( const std::filesystem::path& repoRoot, - Host::TextureHost& renderer) { + Ports::TexturePort& renderer) { m_toolOverlay.Initialize(repoRoot, renderer); ResetInteractionState(); } -void SceneViewportController::Shutdown(Host::TextureHost& renderer) { +void SceneViewportController::Shutdown(Ports::TexturePort& renderer) { m_toolOverlay.Shutdown(renderer); ResetInteractionState(); } @@ -132,7 +132,7 @@ void SceneViewportController::Shutdown(Host::TextureHost& renderer) { void SceneViewportController::ResetInteractionState() { ResetFrameState(); m_toolOverlay.ResetFrame(); - m_legacyGizmo.ResetVisualState(); + m_transformGizmo.ResetVisualState(); } void SceneViewportController::SetCommandFocusService( @@ -142,7 +142,7 @@ void SceneViewportController::SetCommandFocusService( void SceneViewportController::Update( EditorSceneRuntime& sceneRuntime, - ViewportHostService& viewportHostService, + const IViewportObjectPickerService& viewportObjectPicker, const UIEditorWorkspaceComposeState& composeState, const UIEditorWorkspaceComposeFrame& composeFrame) { const UIEditorWorkspaceViewportComposeFrame* viewportFrame = @@ -150,8 +150,8 @@ void SceneViewportController::Update( const UIEditorWorkspacePanelPresentationState* panelState = FindUIEditorWorkspacePanelPresentationState(composeState, kScenePanelId); if (viewportFrame == nullptr || panelState == nullptr) { - if (m_legacyGizmo.IsActive()) { - m_legacyGizmo.CancelDrag(sceneRuntime); + if (m_transformGizmo.IsActive()) { + m_transformGizmo.CancelDrag(sceneRuntime); } sceneRuntime.SetHoveredToolHandle(SceneToolHandle::None); ResetInteractionState(); @@ -190,8 +190,8 @@ void SceneViewportController::Update( m_navigationState = {}; m_hoveredToolOverlayIndex = kSceneViewportToolOverlayInvalidIndex; m_activeToolOverlayIndex = kSceneViewportToolOverlayInvalidIndex; - if (m_legacyGizmo.IsActive()) { - m_legacyGizmo.CancelDrag(sceneRuntime); + if (m_transformGizmo.IsActive()) { + m_transformGizmo.CancelDrag(sceneRuntime); } } @@ -218,13 +218,13 @@ void SceneViewportController::Update( if (transition.pressed && transitionHoveredToolOverlayIndex != kSceneViewportToolOverlayInvalidIndex && - !m_legacyGizmo.IsActive()) { + !m_transformGizmo.IsActive()) { m_activeToolOverlayIndex = transitionHoveredToolOverlayIndex; if (m_activeToolOverlayIndex < m_toolOverlay.GetFrame().buttons.size()) { ApplySceneToolMode( m_toolOverlay.GetFrame().buttons[m_activeToolOverlayIndex].mode, sceneRuntime, - m_legacyGizmo); + m_transformGizmo); } continue; } @@ -247,7 +247,7 @@ void SceneViewportController::Update( const bool usingViewMoveTool = IsViewMoveTool(sceneRuntime.GetToolMode()); - if (!m_legacyGizmo.IsActive()) { + if (!m_transformGizmo.IsActive()) { for (const auto& transition : inputFrame.pointerButtonTransitions) { if (!transition.pressed || !ContainsPoint(slotLayout.inputRect, transition.screenPosition) || @@ -289,34 +289,34 @@ void SceneViewportController::Update( !m_navigationState.lookDragging && !m_navigationState.panDragging; - if (!m_legacyGizmo.IsActive() && + if (!m_transformGizmo.IsActive() && !toolOverlayInteractionActive && inputFrame.focused && !m_navigationState.lookDragging && !m_navigationState.panDragging) { SceneToolMode shortcutMode = SceneToolMode::View; if (TryResolveToolModeShortcut(inputFrame, shortcutMode)) { - ApplySceneToolMode(shortcutMode, sceneRuntime, m_legacyGizmo); + ApplySceneToolMode(shortcutMode, sceneRuntime, m_transformGizmo); } } if (inputFrame.focused && - !m_legacyGizmo.IsActive() && + !m_transformGizmo.IsActive() && WasKeyPressed(inputFrame, KeyCode::F)) { sceneRuntime.FocusSceneSelection(); } - m_legacyGizmo.Refresh( + m_transformGizmo.Refresh( sceneRuntime, slotLayout.inputRect, pointerScreen, viewportHoverEligible); sceneRuntime.SetHoveredToolHandle(SceneToolHandle::None); - if (m_legacyGizmo.IsActive()) { + if (m_transformGizmo.IsActive()) { if (WasKeyPressed(inputFrame, KeyCode::Escape)) { - m_legacyGizmo.CancelDrag(sceneRuntime); - m_legacyGizmo.Refresh( + m_transformGizmo.CancelDrag(sceneRuntime); + m_transformGizmo.Refresh( sceneRuntime, slotLayout.inputRect, pointerScreen, @@ -325,8 +325,8 @@ void SceneViewportController::Update( } if (!leftMouseDown) { - m_legacyGizmo.EndDrag(sceneRuntime); - m_legacyGizmo.Refresh( + m_transformGizmo.EndDrag(sceneRuntime); + m_transformGizmo.Refresh( sceneRuntime, slotLayout.inputRect, pointerScreen, @@ -334,8 +334,8 @@ void SceneViewportController::Update( return; } - m_legacyGizmo.UpdateDrag(sceneRuntime); - m_legacyGizmo.Refresh( + m_transformGizmo.UpdateDrag(sceneRuntime); + m_transformGizmo.Refresh( sceneRuntime, slotLayout.inputRect, pointerScreen, @@ -352,15 +352,15 @@ void SceneViewportController::Update( continue; } - m_legacyGizmo.Refresh( + m_transformGizmo.Refresh( sceneRuntime, slotLayout.inputRect, transition.screenPosition, true); - if (m_legacyGizmo.IsHoveringHandle() && - m_legacyGizmo.TryBeginDrag(sceneRuntime)) { + if (m_transformGizmo.IsHoveringHandle() && + m_transformGizmo.TryBeginDrag(sceneRuntime)) { m_navigationState = {}; - m_legacyGizmo.Refresh( + m_transformGizmo.Refresh( sceneRuntime, slotLayout.inputRect, pointerScreen, @@ -369,7 +369,7 @@ void SceneViewportController::Update( } const ViewportObjectIdPickResult pickResult = - viewportHostService.PickSceneViewportObject( + viewportObjectPicker.PickObject( viewportFrame->viewportShellFrame.requestedViewportSize, transition.localPointerPosition); if (pickResult.status == ViewportObjectIdPickStatus::Success) { @@ -380,7 +380,7 @@ void SceneViewportController::Update( } } - m_legacyGizmo.Refresh( + m_transformGizmo.Refresh( sceneRuntime, slotLayout.inputRect, pointerScreen, @@ -420,7 +420,7 @@ void SceneViewportController::Update( if (HasCameraInput(input)) { sceneRuntime.ApplySceneViewportCameraInput(input); - m_legacyGizmo.Refresh( + m_transformGizmo.Refresh( sceneRuntime, slotLayout.inputRect, pointerScreen, @@ -429,7 +429,7 @@ void SceneViewportController::Update( } void SceneViewportController::Append(::XCEngine::UI::UIDrawList& drawList) const { - AppendLegacySceneViewportGizmo(drawList, m_legacyGizmo.GetFrame()); + AppendSceneViewportTransformGizmo(drawList, m_transformGizmo.GetFrame()); AppendSceneViewportToolOverlay(drawList, m_toolOverlay.GetFrame()); } diff --git a/new_editor/app/Features/Scene/SceneViewportController.h b/new_editor/app/Features/Scene/SceneViewportController.h index 4bc0cb52..dac89d7b 100644 --- a/new_editor/app/Features/Scene/SceneViewportController.h +++ b/new_editor/app/Features/Scene/SceneViewportController.h @@ -1,6 +1,6 @@ #pragma once -#include "Features/Scene/LegacySceneViewportGizmo.h" +#include "Features/Scene/SceneViewportTransformGizmo.h" #include "Features/Scene/SceneViewportToolOverlay.h" #include @@ -15,15 +15,15 @@ namespace XCEngine::UI::Editor::App { class EditorCommandFocusService; class EditorSceneRuntime; -class ViewportHostService; +class IViewportObjectPickerService; } // namespace XCEngine::UI::Editor::App -namespace XCEngine::UI::Editor::Host { +namespace XCEngine::UI::Editor::Ports { -class TextureHost; +class TexturePort; -} // namespace XCEngine::UI::Editor::Host +} // namespace XCEngine::UI::Editor::Ports namespace XCEngine::UI::Editor::App { @@ -31,14 +31,14 @@ class SceneViewportController { public: void Initialize( const std::filesystem::path& repoRoot, - Host::TextureHost& renderer); - void Shutdown(Host::TextureHost& renderer); + Ports::TexturePort& renderer); + void Shutdown(Ports::TexturePort& renderer); void ResetInteractionState(); void SetCommandFocusService(EditorCommandFocusService* commandFocusService); void Update( EditorSceneRuntime& sceneRuntime, - ViewportHostService& viewportHostService, + const IViewportObjectPickerService& viewportObjectPicker, const UIEditorWorkspaceComposeState& composeState, const UIEditorWorkspaceComposeFrame& composeFrame); void Append(::XCEngine::UI::UIDrawList& drawList) const; @@ -56,7 +56,7 @@ private: NavigationState m_navigationState = {}; EditorCommandFocusService* m_commandFocusService = nullptr; - LegacySceneViewportGizmo m_legacyGizmo = {}; + SceneViewportTransformGizmo m_transformGizmo = {}; SceneViewportToolOverlay m_toolOverlay = {}; std::size_t m_hoveredToolOverlayIndex = kSceneViewportToolOverlayInvalidIndex; std::size_t m_activeToolOverlayIndex = kSceneViewportToolOverlayInvalidIndex; diff --git a/new_editor/app/Features/Scene/SceneViewportFeature.cpp b/new_editor/app/Features/Scene/SceneViewportFeature.cpp new file mode 100644 index 00000000..6616f348 --- /dev/null +++ b/new_editor/app/Features/Scene/SceneViewportFeature.cpp @@ -0,0 +1,60 @@ +#include "Features/Scene/SceneViewportFeature.h" + +#include "Composition/EditorPanelIds.h" +#include "Ports/TexturePort.h" +#include "Rendering/Viewport/ViewportHostService.h" +#include "Scene/EditorSceneRuntime.h" +#include "State/EditorCommandFocusService.h" + +namespace XCEngine::UI::Editor::App { + +void SceneViewportFeature::Initialize( + const std::filesystem::path& repoRoot, + Ports::TexturePort& textureHost, + ViewportHostService& viewportHostService) { + viewportHostService.SetContentRenderer( + kScenePanelId, + &m_renderService, + SceneViewportRenderService::GetViewportResourceRequirements()); + m_controller.Initialize(repoRoot, textureHost); +} + +void SceneViewportFeature::Shutdown( + Ports::TexturePort& textureHost, + ViewportHostService& viewportHostService) { + viewportHostService.SetContentRenderer(kScenePanelId, nullptr, {}); + m_controller.Shutdown(textureHost); + m_renderService.Shutdown(); +} + +void SceneViewportFeature::ResetInteractionState() { + m_controller.ResetInteractionState(); +} + +void SceneViewportFeature::SetCommandFocusService( + EditorCommandFocusService* commandFocusService) { + m_controller.SetCommandFocusService(commandFocusService); +} + +void SceneViewportFeature::SyncRenderRequest(EditorSceneRuntime& sceneRuntime) { + m_renderService.SetRenderRequest( + sceneRuntime.BuildSceneViewportRenderRequest()); +} + +void SceneViewportFeature::Update( + EditorSceneRuntime& sceneRuntime, + const UIEditorWorkspaceComposeState& composeState, + const UIEditorWorkspaceComposeFrame& composeFrame) { + m_controller.Update( + sceneRuntime, + m_renderService, + composeState, + composeFrame); + SyncRenderRequest(sceneRuntime); +} + +void SceneViewportFeature::Append(::XCEngine::UI::UIDrawList& drawList) const { + m_controller.Append(drawList); +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Scene/SceneViewportFeature.h b/new_editor/app/Features/Scene/SceneViewportFeature.h new file mode 100644 index 00000000..6a9db88a --- /dev/null +++ b/new_editor/app/Features/Scene/SceneViewportFeature.h @@ -0,0 +1,52 @@ +#pragma once + +#include "Features/Scene/SceneViewportController.h" +#include "Rendering/Viewport/SceneViewportRenderService.h" + +#include + +#include + +#include + +namespace XCEngine::UI::Editor::App { + +class EditorCommandFocusService; +class EditorSceneRuntime; + +} // namespace XCEngine::UI::Editor::App + +namespace XCEngine::UI::Editor::Ports { + +class TexturePort; + +} // namespace XCEngine::UI::Editor::Ports + +namespace XCEngine::UI::Editor::App { + +class ViewportHostService; + +class SceneViewportFeature { +public: + void Initialize( + const std::filesystem::path& repoRoot, + Ports::TexturePort& textureHost, + ViewportHostService& viewportHostService); + void Shutdown( + Ports::TexturePort& textureHost, + ViewportHostService& viewportHostService); + void ResetInteractionState(); + void SetCommandFocusService(EditorCommandFocusService* commandFocusService); + void SyncRenderRequest(EditorSceneRuntime& sceneRuntime); + void Update( + EditorSceneRuntime& sceneRuntime, + const UIEditorWorkspaceComposeState& composeState, + const UIEditorWorkspaceComposeFrame& composeFrame); + void Append(::XCEngine::UI::UIDrawList& drawList) const; + +private: + SceneViewportRenderService m_renderService = {}; + SceneViewportController m_controller = {}; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Scene/SceneViewportToolOverlay.cpp b/new_editor/app/Features/Scene/SceneViewportToolOverlay.cpp index 60a7885d..6df050fa 100644 --- a/new_editor/app/Features/Scene/SceneViewportToolOverlay.cpp +++ b/new_editor/app/Features/Scene/SceneViewportToolOverlay.cpp @@ -1,6 +1,6 @@ #include "Features/Scene/SceneViewportToolOverlay.h" -#include "Host/TextureHost.h" +#include "Ports/TexturePort.h" #include #include @@ -57,7 +57,7 @@ UIRect BuildButtonRect(const UIRect& panelRect, std::size_t index) { bool SceneViewportToolOverlay::Initialize( const std::filesystem::path& repoRoot, - Host::TextureHost& renderer) { + Ports::TexturePort& renderer) { Shutdown(renderer); const std::filesystem::path iconRoot = @@ -87,7 +87,7 @@ bool SceneViewportToolOverlay::Initialize( return loadedAnyTexture; } -void SceneViewportToolOverlay::Shutdown(Host::TextureHost& renderer) { +void SceneViewportToolOverlay::Shutdown(Ports::TexturePort& renderer) { for (ToolTextureSet& textureSet : m_toolTextures) { renderer.ReleaseTexture(textureSet.inactiveTexture); renderer.ReleaseTexture(textureSet.activeTexture); diff --git a/new_editor/app/Features/Scene/SceneViewportToolOverlay.h b/new_editor/app/Features/Scene/SceneViewportToolOverlay.h index fc076097..74339761 100644 --- a/new_editor/app/Features/Scene/SceneViewportToolOverlay.h +++ b/new_editor/app/Features/Scene/SceneViewportToolOverlay.h @@ -1,7 +1,7 @@ #pragma once #include "Scene/SceneToolState.h" -#include "Host/HostFwd.h" +#include "Ports/PortFwd.h" #include #include @@ -36,8 +36,8 @@ class SceneViewportToolOverlay { public: bool Initialize( const std::filesystem::path& repoRoot, - Host::TextureHost& renderer); - void Shutdown(Host::TextureHost& renderer); + Ports::TexturePort& renderer); + void Shutdown(Ports::TexturePort& renderer); void ResetFrame(); void BuildFrame( diff --git a/new_editor/app/Features/Scene/LegacySceneViewportGizmo.cpp b/new_editor/app/Features/Scene/SceneViewportTransformGizmo.cpp similarity index 83% rename from new_editor/app/Features/Scene/LegacySceneViewportGizmo.cpp rename to new_editor/app/Features/Scene/SceneViewportTransformGizmo.cpp index 0865724b..4ec4fd28 100644 --- a/new_editor/app/Features/Scene/LegacySceneViewportGizmo.cpp +++ b/new_editor/app/Features/Scene/SceneViewportTransformGizmo.cpp @@ -1,6 +1,6 @@ -#include "Features/Scene/LegacySceneViewportGizmo.h" +#include "Features/Scene/SceneViewportTransformGizmo.h" -#include "Legacy/Viewport/LegacySceneViewportGizmoSupport.h" +#include "Features/Scene/SceneViewportTransformGizmoSupport.h" #include "Scene/EditorSceneRuntime.h" #include "Scene/SceneToolState.h" @@ -28,27 +28,27 @@ using ::XCEngine::Math::Vector3; using ::XCEngine::UI::UIColor; using ::XCEngine::UI::UIPoint; using ::XCEngine::UI::UIRect; -namespace LegacyViewport = ::XCEngine::UI::Editor::App::Legacy; -using LegacyViewport::BuildSceneViewportTransformGizmoOverlayFrameData; -using LegacyViewport::IUndoManager; -using LegacyViewport::SceneViewportMoveGizmo; -using LegacyViewport::SceneViewportMoveGizmoContext; -using LegacyViewport::SceneViewportOverlayData; -using LegacyViewport::SceneViewportOverlayFrameData; -using LegacyViewport::SceneViewportRotateGizmo; -using LegacyViewport::SceneViewportRotateGizmoContext; -using LegacyViewport::SceneViewportScaleGizmo; -using LegacyViewport::SceneViewportScaleGizmoContext; -using LegacyViewport::SceneViewportTransformGizmoHandleBuildInputs; +namespace SceneViewportGizmoInternal = ::XCEngine::UI::Editor::App::SceneViewportGizmoInternal; +using SceneViewportGizmoInternal::BuildSceneViewportTransformGizmoOverlayFrameData; +using SceneViewportGizmoInternal::IUndoManager; +using SceneViewportGizmoInternal::SceneViewportMoveGizmo; +using SceneViewportGizmoInternal::SceneViewportMoveGizmoContext; +using SceneViewportGizmoInternal::SceneViewportOverlayData; +using SceneViewportGizmoInternal::SceneViewportOverlayFrameData; +using SceneViewportGizmoInternal::SceneViewportRotateGizmo; +using SceneViewportGizmoInternal::SceneViewportRotateGizmoContext; +using SceneViewportGizmoInternal::SceneViewportScaleGizmo; +using SceneViewportGizmoInternal::SceneViewportScaleGizmoContext; +using SceneViewportGizmoInternal::SceneViewportTransformGizmoHandleBuildInputs; -enum class ActiveLegacyGizmoKind : std::uint8_t { +enum class ActiveTransformGizmoKind : std::uint8_t { None = 0, Move, Rotate, Scale }; -struct LegacySelectionState { +struct TransformGizmoSelectionState { GameObject* primaryObject = nullptr; std::vector selectedObjects = {}; Vector3 pivotWorldPosition = Vector3::Zero(); @@ -100,10 +100,10 @@ Vector3 ResolveCenterWorldPosition(const GameObject* gameObject) { return gameObject->GetTransform()->GetPosition(); } -LegacySelectionState BuildSelectionState( +TransformGizmoSelectionState BuildSelectionState( EditorSceneRuntime& sceneRuntime, bool useCenterPivot) { - LegacySelectionState state = {}; + TransformGizmoSelectionState state = {}; const auto selectedId = sceneRuntime.GetSelectedGameObjectId(); if (!selectedId.has_value()) { return state; @@ -151,7 +151,7 @@ SceneViewportOverlayData BuildOverlayData(const EditorSceneRuntime& sceneRuntime } SceneViewportMoveGizmoContext BuildMoveContext( - const LegacySelectionState& selection, + const TransformGizmoSelectionState& selection, const SceneViewportOverlayData& overlay, const UIRect& viewportRect, const Vector2& localPointer, @@ -170,7 +170,7 @@ SceneViewportMoveGizmoContext BuildMoveContext( } SceneViewportRotateGizmoContext BuildRotateContext( - const LegacySelectionState& selection, + const TransformGizmoSelectionState& selection, const SceneViewportOverlayData& overlay, const UIRect& viewportRect, const Vector2& localPointer, @@ -192,7 +192,7 @@ SceneViewportRotateGizmoContext BuildRotateContext( } SceneViewportScaleGizmoContext BuildScaleContext( - const LegacySelectionState& selection, + const TransformGizmoSelectionState& selection, const SceneViewportOverlayData& overlay, const UIRect& viewportRect, const Vector2& localPointer, @@ -209,21 +209,21 @@ SceneViewportScaleGizmoContext BuildScaleContext( return context; } -ActiveLegacyGizmoKind ResolveActiveGizmoKind( +ActiveTransformGizmoKind ResolveActiveGizmoKind( const SceneViewportMoveGizmo& moveGizmo, const SceneViewportRotateGizmo& rotateGizmo, const SceneViewportScaleGizmo& scaleGizmo) { if (moveGizmo.IsActive()) { - return ActiveLegacyGizmoKind::Move; + return ActiveTransformGizmoKind::Move; } if (rotateGizmo.IsActive()) { - return ActiveLegacyGizmoKind::Rotate; + return ActiveTransformGizmoKind::Rotate; } if (scaleGizmo.IsActive()) { - return ActiveLegacyGizmoKind::Scale; + return ActiveTransformGizmoKind::Scale; } - return ActiveLegacyGizmoKind::None; + return ActiveTransformGizmoKind::None; } bool IsTransformToolMode(SceneToolMode mode) { @@ -245,13 +245,13 @@ bool ShouldShowScaleGizmo(SceneToolMode mode) { Vector2 ResolveUpdatePointerPosition( const Vector2& pointerPosition, bool hoverEnabled, - ActiveLegacyGizmoKind activeKind, - ActiveLegacyGizmoKind gizmoKind) { - if (!hoverEnabled && activeKind == ActiveLegacyGizmoKind::None) { + ActiveTransformGizmoKind activeKind, + ActiveTransformGizmoKind gizmoKind) { + if (!hoverEnabled && activeKind == ActiveTransformGizmoKind::None) { return Vector2(-1.0f, -1.0f); } - if (activeKind != ActiveLegacyGizmoKind::None && activeKind != gizmoKind) { + if (activeKind != ActiveTransformGizmoKind::None && activeKind != gizmoKind) { return Vector2(-1.0f, -1.0f); } @@ -312,7 +312,7 @@ private: } // namespace -struct LegacySceneViewportGizmo::State { +struct SceneViewportTransformGizmo::State { SceneGizmoUndoBridge undoBridge = {}; SceneViewportMoveGizmo moveGizmo = {}; SceneViewportRotateGizmo rotateGizmo = {}; @@ -320,22 +320,22 @@ struct LegacySceneViewportGizmo::State { SceneViewportMoveGizmoContext moveContext = {}; SceneViewportRotateGizmoContext rotateContext = {}; SceneViewportScaleGizmoContext scaleContext = {}; - LegacySceneViewportGizmoFrame frame = {}; + SceneViewportTransformGizmoFrame frame = {}; }; -LegacySceneViewportGizmo::LegacySceneViewportGizmo() +SceneViewportTransformGizmo::SceneViewportTransformGizmo() : m_state(std::make_unique()) { } -LegacySceneViewportGizmo::~LegacySceneViewportGizmo() = default; +SceneViewportTransformGizmo::~SceneViewportTransformGizmo() = default; -LegacySceneViewportGizmo::LegacySceneViewportGizmo(LegacySceneViewportGizmo&&) noexcept = +SceneViewportTransformGizmo::SceneViewportTransformGizmo(SceneViewportTransformGizmo&&) noexcept = default; -LegacySceneViewportGizmo& LegacySceneViewportGizmo::operator=( - LegacySceneViewportGizmo&&) noexcept = default; +SceneViewportTransformGizmo& SceneViewportTransformGizmo::operator=( + SceneViewportTransformGizmo&&) noexcept = default; -void LegacySceneViewportGizmo::Refresh( +void SceneViewportTransformGizmo::Refresh( EditorSceneRuntime& sceneRuntime, const UIRect& viewportRect, const UIPoint& pointerScreen, @@ -359,7 +359,7 @@ void LegacySceneViewportGizmo::Refresh( sceneRuntime.GetToolPivotMode() == SceneToolPivotMode::Center; const bool localSpace = sceneRuntime.GetToolSpaceMode() == SceneToolSpaceMode::Local; - const LegacySelectionState selection = + const TransformGizmoSelectionState selection = BuildSelectionState(sceneRuntime, useCenterPivot); if (selection.primaryObject == nullptr) { CancelDrag(sceneRuntime); @@ -431,7 +431,7 @@ void LegacySceneViewportGizmo::Refresh( state.scaleGizmo.CancelDrag(&state.undoBridge); } - const ActiveLegacyGizmoKind activeKind = ResolveActiveGizmoKind( + const ActiveTransformGizmoKind activeKind = ResolveActiveGizmoKind( state.moveGizmo, state.rotateGizmo, state.scaleGizmo); @@ -442,7 +442,7 @@ void LegacySceneViewportGizmo::Refresh( state.moveContext.mousePosition, hoverEnabled, activeKind, - ActiveLegacyGizmoKind::Move); + ActiveTransformGizmoKind::Move); state.moveGizmo.Update(updateContext); inputs.moveGizmo = &state.moveGizmo.GetDrawData(); inputs.moveEntityId = selection.primaryObject->GetID(); @@ -454,7 +454,7 @@ void LegacySceneViewportGizmo::Refresh( state.rotateContext.mousePosition, hoverEnabled, activeKind, - ActiveLegacyGizmoKind::Rotate); + ActiveTransformGizmoKind::Rotate); state.rotateGizmo.Update(updateContext); inputs.rotateGizmo = &state.rotateGizmo.GetDrawData(); inputs.rotateEntityId = selection.primaryObject->GetID(); @@ -466,7 +466,7 @@ void LegacySceneViewportGizmo::Refresh( state.scaleContext.mousePosition, hoverEnabled, activeKind, - ActiveLegacyGizmoKind::Scale); + ActiveTransformGizmoKind::Scale); state.scaleGizmo.Update(updateContext); inputs.scaleGizmo = &state.scaleGizmo.GetDrawData(); inputs.scaleEntityId = selection.primaryObject->GetID(); @@ -481,7 +481,7 @@ void LegacySceneViewportGizmo::Refresh( state.frame.visible = true; state.frame.triangles.reserve(overlayFrame.screenTriangles.size()); for (const auto& triangle : overlayFrame.screenTriangles) { - LegacySceneViewportTriangle triangleFrame = {}; + SceneViewportTransformGizmoTriangle triangleFrame = {}; triangleFrame.a = ToScreenPoint(triangle.vertices[0].screenPosition, viewportRect); triangleFrame.b = @@ -493,7 +493,7 @@ void LegacySceneViewportGizmo::Refresh( } } -bool LegacySceneViewportGizmo::TryBeginDrag(EditorSceneRuntime& sceneRuntime) { +bool SceneViewportTransformGizmo::TryBeginDrag(EditorSceneRuntime& sceneRuntime) { State& state = *m_state; state.undoBridge.Bind(sceneRuntime); @@ -527,7 +527,7 @@ bool LegacySceneViewportGizmo::TryBeginDrag(EditorSceneRuntime& sceneRuntime) { } } -bool LegacySceneViewportGizmo::UpdateDrag(EditorSceneRuntime& sceneRuntime) { +bool SceneViewportTransformGizmo::UpdateDrag(EditorSceneRuntime& sceneRuntime) { State& state = *m_state; state.undoBridge.Bind(sceneRuntime); @@ -535,22 +535,22 @@ bool LegacySceneViewportGizmo::UpdateDrag(EditorSceneRuntime& sceneRuntime) { state.moveGizmo, state.rotateGizmo, state.scaleGizmo)) { - case ActiveLegacyGizmoKind::Move: + case ActiveTransformGizmoKind::Move: state.moveGizmo.UpdateDrag(state.moveContext); return true; - case ActiveLegacyGizmoKind::Rotate: + case ActiveTransformGizmoKind::Rotate: state.rotateGizmo.UpdateDrag(state.rotateContext); return true; - case ActiveLegacyGizmoKind::Scale: + case ActiveTransformGizmoKind::Scale: state.scaleGizmo.UpdateDrag(state.scaleContext); return true; - case ActiveLegacyGizmoKind::None: + case ActiveTransformGizmoKind::None: default: return false; } } -bool LegacySceneViewportGizmo::EndDrag(EditorSceneRuntime& sceneRuntime) { +bool SceneViewportTransformGizmo::EndDrag(EditorSceneRuntime& sceneRuntime) { State& state = *m_state; state.undoBridge.Bind(sceneRuntime); @@ -558,22 +558,22 @@ bool LegacySceneViewportGizmo::EndDrag(EditorSceneRuntime& sceneRuntime) { state.moveGizmo, state.rotateGizmo, state.scaleGizmo)) { - case ActiveLegacyGizmoKind::Move: + case ActiveTransformGizmoKind::Move: state.moveGizmo.EndDrag(state.undoBridge); return true; - case ActiveLegacyGizmoKind::Rotate: + case ActiveTransformGizmoKind::Rotate: state.rotateGizmo.EndDrag(state.undoBridge); return true; - case ActiveLegacyGizmoKind::Scale: + case ActiveTransformGizmoKind::Scale: state.scaleGizmo.EndDrag(state.undoBridge); return true; - case ActiveLegacyGizmoKind::None: + case ActiveTransformGizmoKind::None: default: return false; } } -void LegacySceneViewportGizmo::CancelDrag(EditorSceneRuntime& sceneRuntime) { +void SceneViewportTransformGizmo::CancelDrag(EditorSceneRuntime& sceneRuntime) { State& state = *m_state; state.undoBridge.Bind(sceneRuntime); @@ -588,7 +588,7 @@ void LegacySceneViewportGizmo::CancelDrag(EditorSceneRuntime& sceneRuntime) { } } -void LegacySceneViewportGizmo::ResetVisualState() { +void SceneViewportTransformGizmo::ResetVisualState() { if (m_state == nullptr) { return; } @@ -596,7 +596,7 @@ void LegacySceneViewportGizmo::ResetVisualState() { m_state->frame = {}; } -bool LegacySceneViewportGizmo::IsActive() const { +bool SceneViewportTransformGizmo::IsActive() const { if (m_state == nullptr) { return false; } @@ -604,10 +604,10 @@ bool LegacySceneViewportGizmo::IsActive() const { return ResolveActiveGizmoKind( m_state->moveGizmo, m_state->rotateGizmo, - m_state->scaleGizmo) != ActiveLegacyGizmoKind::None; + m_state->scaleGizmo) != ActiveTransformGizmoKind::None; } -bool LegacySceneViewportGizmo::IsHoveringHandle() const { +bool SceneViewportTransformGizmo::IsHoveringHandle() const { if (m_state == nullptr) { return false; } @@ -617,19 +617,19 @@ bool LegacySceneViewportGizmo::IsHoveringHandle() const { m_state->scaleGizmo.IsHoveringHandle(); } -const LegacySceneViewportGizmoFrame& LegacySceneViewportGizmo::GetFrame() const { +const SceneViewportTransformGizmoFrame& SceneViewportTransformGizmo::GetFrame() const { return m_state->frame; } -void AppendLegacySceneViewportGizmo( +void AppendSceneViewportTransformGizmo( ::XCEngine::UI::UIDrawList& drawList, - const LegacySceneViewportGizmoFrame& frame) { + const SceneViewportTransformGizmoFrame& frame) { if (!frame.visible || frame.triangles.empty()) { return; } drawList.PushClipRect(frame.clipRect); - for (const LegacySceneViewportTriangle& triangle : frame.triangles) { + for (const SceneViewportTransformGizmoTriangle& triangle : frame.triangles) { drawList.AddFilledTriangle( triangle.a, triangle.b, diff --git a/new_editor/app/Features/Scene/LegacySceneViewportGizmo.h b/new_editor/app/Features/Scene/SceneViewportTransformGizmo.h similarity index 59% rename from new_editor/app/Features/Scene/LegacySceneViewportGizmo.h rename to new_editor/app/Features/Scene/SceneViewportTransformGizmo.h index e63040f3..a2cf85c5 100644 --- a/new_editor/app/Features/Scene/LegacySceneViewportGizmo.h +++ b/new_editor/app/Features/Scene/SceneViewportTransformGizmo.h @@ -10,28 +10,28 @@ namespace XCEngine::UI::Editor::App { class EditorSceneRuntime; -struct LegacySceneViewportTriangle { +struct SceneViewportTransformGizmoTriangle { ::XCEngine::UI::UIPoint a = {}; ::XCEngine::UI::UIPoint b = {}; ::XCEngine::UI::UIPoint c = {}; ::XCEngine::UI::UIColor color = {}; }; -struct LegacySceneViewportGizmoFrame { +struct SceneViewportTransformGizmoFrame { bool visible = false; ::XCEngine::UI::UIRect clipRect = {}; - std::vector triangles = {}; + std::vector triangles = {}; }; -class LegacySceneViewportGizmo { +class SceneViewportTransformGizmo { public: - LegacySceneViewportGizmo(); - ~LegacySceneViewportGizmo(); + SceneViewportTransformGizmo(); + ~SceneViewportTransformGizmo(); - LegacySceneViewportGizmo(const LegacySceneViewportGizmo&) = delete; - LegacySceneViewportGizmo& operator=(const LegacySceneViewportGizmo&) = delete; - LegacySceneViewportGizmo(LegacySceneViewportGizmo&&) noexcept; - LegacySceneViewportGizmo& operator=(LegacySceneViewportGizmo&&) noexcept; + SceneViewportTransformGizmo(const SceneViewportTransformGizmo&) = delete; + SceneViewportTransformGizmo& operator=(const SceneViewportTransformGizmo&) = delete; + SceneViewportTransformGizmo(SceneViewportTransformGizmo&&) noexcept; + SceneViewportTransformGizmo& operator=(SceneViewportTransformGizmo&&) noexcept; void Refresh( EditorSceneRuntime& sceneRuntime, @@ -46,7 +46,7 @@ public: bool IsActive() const; bool IsHoveringHandle() const; - const LegacySceneViewportGizmoFrame& GetFrame() const; + const SceneViewportTransformGizmoFrame& GetFrame() const; private: struct State; @@ -54,8 +54,8 @@ private: std::unique_ptr m_state = {}; }; -void AppendLegacySceneViewportGizmo( +void AppendSceneViewportTransformGizmo( ::XCEngine::UI::UIDrawList& drawList, - const LegacySceneViewportGizmoFrame& frame); + const SceneViewportTransformGizmoFrame& frame); } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Legacy/Viewport/LegacySceneViewportGizmoSupport.cpp b/new_editor/app/Features/Scene/SceneViewportTransformGizmoSupport.cpp similarity index 99% rename from new_editor/app/Legacy/Viewport/LegacySceneViewportGizmoSupport.cpp rename to new_editor/app/Features/Scene/SceneViewportTransformGizmoSupport.cpp index 348dc84f..92146453 100644 --- a/new_editor/app/Legacy/Viewport/LegacySceneViewportGizmoSupport.cpp +++ b/new_editor/app/Features/Scene/SceneViewportTransformGizmoSupport.cpp @@ -1,4 +1,4 @@ -#include "LegacySceneViewportGizmoSupport.h" +#include "Features/Scene/SceneViewportTransformGizmoSupport.h" #include #include @@ -6,7 +6,7 @@ #include #include -namespace XCEngine::UI::Editor::App::Legacy { +namespace XCEngine::UI::Editor::App::SceneViewportGizmoInternal { namespace { @@ -2459,4 +2459,4 @@ SceneViewportOverlayFrameData BuildSceneViewportTransformGizmoOverlayFrameData( return frameData; } -} // namespace XCEngine::UI::Editor::App::Legacy +} // namespace XCEngine::UI::Editor::App::SceneViewportGizmoInternal diff --git a/new_editor/app/Legacy/Viewport/LegacySceneViewportGizmoSupport.h b/new_editor/app/Features/Scene/SceneViewportTransformGizmoSupport.h similarity index 99% rename from new_editor/app/Legacy/Viewport/LegacySceneViewportGizmoSupport.h rename to new_editor/app/Features/Scene/SceneViewportTransformGizmoSupport.h index 819bdd92..e58fbf1a 100644 --- a/new_editor/app/Legacy/Viewport/LegacySceneViewportGizmoSupport.h +++ b/new_editor/app/Features/Scene/SceneViewportTransformGizmoSupport.h @@ -20,7 +20,7 @@ class GameObject; } // namespace XCEngine::Components -namespace XCEngine::UI::Editor::App::Legacy { +namespace XCEngine::UI::Editor::App::SceneViewportGizmoInternal { class IUndoManager { public: @@ -428,4 +428,4 @@ SceneViewportOverlayFrameData BuildSceneViewportTransformGizmoOverlayFrameData( const SceneViewportOverlayData& overlay, const SceneViewportTransformGizmoHandleBuildInputs& inputs); -} // namespace XCEngine::UI::Editor::App::Legacy +} // namespace XCEngine::UI::Editor::App::SceneViewportGizmoInternal diff --git a/new_editor/app/Host/HostFwd.h b/new_editor/app/Host/HostFwd.h deleted file mode 100644 index 75975fce..00000000 --- a/new_editor/app/Host/HostFwd.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -namespace XCEngine::UI::Editor::Host { - -class ShaderResourceDescriptorAllocator; -class D3D12ShaderResourceDescriptorAllocator; -class D3D12WindowRenderer; -class NativeRenderer; -class TextureHost; -class ViewportRenderHost; - -} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/ViewportRenderHost.h b/new_editor/app/Host/ViewportRenderHost.h deleted file mode 100644 index 3c9854d1..00000000 --- a/new_editor/app/Host/ViewportRenderHost.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -namespace XCEngine::UI::Editor::Host { - -class ViewportRenderHost { -public: - virtual ~ViewportRenderHost() = default; - - [[nodiscard]] virtual ::XCEngine::RHI::RHIDevice* GetRHIDevice() const = 0; -}; - -} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Internal/EmbeddedPngLoader.cpp b/new_editor/app/Internal/EmbeddedPngLoader.cpp index 712ed7a4..e11596f8 100644 --- a/new_editor/app/Internal/EmbeddedPngLoader.cpp +++ b/new_editor/app/Internal/EmbeddedPngLoader.cpp @@ -1,6 +1,6 @@ #include "Internal/EmbeddedPngLoader.h" -#include "Host/TextureHost.h" +#include "Ports/TexturePort.h" namespace XCEngine::UI::Editor::App::Internal { @@ -49,7 +49,7 @@ bool LoadEmbeddedPngBytes( } bool LoadEmbeddedPngTexture( - Host::TextureHost& renderer, + Ports::TexturePort& renderer, UINT resourceId, ::XCEngine::UI::UITextureHandle& outTexture, std::string& outError) { diff --git a/new_editor/app/Internal/EmbeddedPngLoader.h b/new_editor/app/Internal/EmbeddedPngLoader.h index e6bb5549..5dc374c7 100644 --- a/new_editor/app/Internal/EmbeddedPngLoader.h +++ b/new_editor/app/Internal/EmbeddedPngLoader.h @@ -1,6 +1,6 @@ #pragma once -#include "Host/HostFwd.h" +#include "Ports/PortFwd.h" #include @@ -23,7 +23,7 @@ bool LoadEmbeddedPngBytes( std::string& outError); bool LoadEmbeddedPngTexture( - Host::TextureHost& renderer, + Ports::TexturePort& renderer, UINT resourceId, ::XCEngine::UI::UITextureHandle& outTexture, std::string& outError); diff --git a/new_editor/app/Platform/Win32/EditorWindow.h b/new_editor/app/Platform/Win32/EditorWindow.h index 032e1e1f..aa06ca95 100644 --- a/new_editor/app/Platform/Win32/EditorWindow.h +++ b/new_editor/app/Platform/Win32/EditorWindow.h @@ -61,18 +61,15 @@ class EditorShellRuntime; struct EditorWindowState; namespace Internal { +class EditorWindowChromeController; +class EditorWindowFrameOrchestrator; class EditorWindowHostRuntime; +class EditorWindowInputController; +class EditorWindowMessageDispatcher; +class EditorWindowRuntimeController; class EditorWindowWorkspaceCoordinator; } -} // namespace XCEngine::UI::Editor::App - -namespace XCEngine::UI::Editor::Host { -class WindowMessageDispatcher; -} - -namespace XCEngine::UI::Editor::App { - class EditorWindow { public: EditorWindow( @@ -98,8 +95,9 @@ public: ::XCEngine::UI::UIPoint ConvertScreenPixelsToClientDips(const POINT& screenPoint) const; private: - friend class Host::WindowMessageDispatcher; + friend class Internal::EditorWindowChromeController; friend class Internal::EditorWindowHostRuntime; + friend class Internal::EditorWindowMessageDispatcher; friend class Internal::EditorWindowWorkspaceCoordinator; bool IsRenderReady() const; @@ -211,26 +209,12 @@ private: bool globalTabDragActive, const ::XCEngine::UI::UIRect& workspaceBounds, ::XCEngine::UI::UIDrawList& drawList); - void RenderInvalidFrame( - EditorContext& editorContext, - ::XCEngine::UI::UIDrawList& drawList) const; - void LogFrameInteractionTrace( - EditorContext& editorContext, - const std::vector<::XCEngine::UI::UIInputEvent>& frameEvents, - const UIEditorShellInteractionFrame& shellFrame) const; - EditorWindowFrameTransferRequests BuildShellTransferRequests( - bool globalTabDragActive, - const UIEditorDockHostInteractionState& dockHostInteractionState, - const UIEditorShellInteractionFrame& shellFrame) const; bool IsPointerInsideClientArea() const; LPCWSTR ResolveCurrentCursorResource() const; float GetDpiScale() const; float PixelsToDips(float pixels) const; ::XCEngine::UI::UIPoint ConvertClientPixelsToDips(LONG x, LONG y) const; - std::string BuildCaptureStatusText() const; void ApplyShellRuntimePointerCapture(); - std::string DescribeInputEvents( - const std::vector<::XCEngine::UI::UIInputEvent>& events) const; Host::BorderlessWindowResizeEdge HitTestBorderlessWindowResizeEdge(LPARAM lParam) const; void ApplyBorderlessWindowResizeCursorHoverPriority(); Host::BorderlessWindowChromeLayout ResolveBorderlessWindowChromeLayout( @@ -256,6 +240,10 @@ private: static bool IsVerboseRuntimeTraceEnabled(); std::unique_ptr m_state = {}; + std::unique_ptr m_chromeController = {}; + std::unique_ptr m_frameOrchestrator = {}; + std::unique_ptr m_inputController = {}; + std::unique_ptr m_runtime = {}; }; } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp b/new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp index 5d4896cb..6da5f3aa 100644 --- a/new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp @@ -1,4 +1,5 @@ #include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowChromeController.h" #include "Platform/Win32/EditorWindowInternalState.h" namespace XCEngine::UI::Editor::App { @@ -7,137 +8,51 @@ bool EditorWindow::IsBorderlessWindowEnabled() const { } bool EditorWindow::IsBorderlessWindowMaximized() const { - return m_state->chrome.runtime.IsBorderlessWindowMaximized(); + return m_chromeController->IsBorderlessWindowMaximized(); } bool EditorWindow::HandleBorderlessWindowSystemCommand( EditorContext& editorContext, bool globalTabDragActive, WPARAM wParam) { - if (!IsBorderlessWindowEnabled()) { - return false; - } - - switch (wParam & 0xFFF0u) { - case SC_MAXIMIZE: - ToggleBorderlessWindowMaximizeRestore(editorContext, globalTabDragActive); - return true; - case SC_RESTORE: - if (!IsIconic(m_state->window.hwnd)) { - ToggleBorderlessWindowMaximizeRestore(editorContext, globalTabDragActive); - return true; - } - return false; - default: - return false; - } + return m_chromeController->HandleSystemCommand( + *this, + editorContext, + globalTabDragActive, + wParam); } bool EditorWindow::HandleBorderlessWindowGetMinMaxInfo(LPARAM lParam) const { - return Host::HandleBorderlessWindowGetMinMaxInfo(m_state->window.hwnd, lParam); + return m_chromeController->HandleGetMinMaxInfo(*this, lParam); } LRESULT EditorWindow::HandleBorderlessWindowNcCalcSize(WPARAM wParam, LPARAM lParam) const { - return Host::HandleBorderlessWindowNcCalcSize( - m_state->window.hwnd, - wParam, - lParam, - m_state->chrome.runtime.GetWindowDpi()); + return m_chromeController->HandleNcCalcSize(*this, wParam, lParam); } bool EditorWindow::QueryCurrentWindowRect(RECT& outRect) const { - outRect = {}; - return m_state->window.hwnd != nullptr && - GetWindowRect(m_state->window.hwnd, &outRect) != FALSE; + return m_chromeController->QueryCurrentWindowRect(*this, outRect); } bool EditorWindow::QueryBorderlessWindowWorkAreaRect(RECT& outRect) const { - outRect = {}; - if (m_state->window.hwnd == nullptr) { - return false; - } - - const HMONITOR monitor = - MonitorFromWindow(m_state->window.hwnd, MONITOR_DEFAULTTONEAREST); - if (monitor == nullptr) { - return false; - } - - MONITORINFO monitorInfo = {}; - monitorInfo.cbSize = sizeof(monitorInfo); - if (!GetMonitorInfoW(monitor, &monitorInfo)) { - return false; - } - - outRect = monitorInfo.rcWork; - return true; + return m_chromeController->QueryBorderlessWindowWorkAreaRect(*this, outRect); } bool EditorWindow::ApplyPredictedWindowRectTransition( EditorContext& editorContext, bool globalTabDragActive, const RECT& targetRect) { - if (m_state->window.hwnd == nullptr) { - return false; - } - - const int width = targetRect.right - targetRect.left; - const int height = targetRect.bottom - targetRect.top; - if (width <= 0 || height <= 0) { - return false; - } - - m_state->chrome.runtime.SetPredictedClientPixelSize( - static_cast(width), - static_cast(height)); - ApplyWindowResize(static_cast(width), static_cast(height)); - (void)RenderFrame(editorContext, globalTabDragActive); - SetWindowPos( - m_state->window.hwnd, - nullptr, - targetRect.left, - targetRect.top, - width, - height, - SWP_NOZORDER | SWP_NOACTIVATE); - InvalidateHostWindow(); - return true; + return m_chromeController->ApplyPredictedWindowRectTransition( + *this, + editorContext, + globalTabDragActive, + targetRect); } void EditorWindow::ToggleBorderlessWindowMaximizeRestore( EditorContext& editorContext, bool globalTabDragActive) { - if (m_state->window.hwnd == nullptr) { - return; - } - - if (!IsBorderlessWindowMaximized()) { - RECT currentRect = {}; - RECT workAreaRect = {}; - if (!QueryCurrentWindowRect(currentRect) || - !QueryBorderlessWindowWorkAreaRect(workAreaRect)) { - return; - } - - m_state->chrome.runtime.SetBorderlessWindowRestoreRect(currentRect); - m_state->chrome.runtime.SetBorderlessWindowMaximized(true); - ApplyPredictedWindowRectTransition( - editorContext, - globalTabDragActive, - workAreaRect); - return; - } - - RECT restoreRect = {}; - if (!m_state->chrome.runtime.TryGetBorderlessWindowRestoreRect(restoreRect)) { - return; - } - - m_state->chrome.runtime.SetBorderlessWindowMaximized(false); - ApplyPredictedWindowRectTransition( - editorContext, - globalTabDragActive, - restoreRect); + m_chromeController->ToggleMaximizeRestore(*this, editorContext, globalTabDragActive); } } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp b/new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp index 169f4a32..e1a128a5 100644 --- a/new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp @@ -1,4 +1,5 @@ #include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowChromeController.h" #include "Platform/Win32/EditorWindowInternalState.h" #include @@ -12,153 +13,41 @@ namespace XCEngine::UI::Editor::App { using ::XCEngine::UI::UIRect; bool EditorWindow::UpdateBorderlessWindowResizeHover(LPARAM lParam) { - const Host::BorderlessWindowResizeEdge hoveredEdge = - HitTestBorderlessWindowResizeEdge(lParam); - if (m_state->chrome.runtime.GetHoveredBorderlessResizeEdge() == hoveredEdge) { - return false; - } - - m_state->chrome.runtime.SetHoveredBorderlessResizeEdge(hoveredEdge); - ApplyBorderlessWindowResizeCursorHoverPriority(); - return true; + return m_chromeController->UpdateResizeHover(*this, lParam); } bool EditorWindow::HandleBorderlessWindowResizeButtonDown(LPARAM lParam) { - const Host::BorderlessWindowResizeEdge edge = - HitTestBorderlessWindowResizeEdge(lParam); - if (edge == Host::BorderlessWindowResizeEdge::None || - m_state->window.hwnd == nullptr) { - return false; - } - - POINT screenPoint = {}; - if (!GetCursorPos(&screenPoint)) { - return false; - } - - RECT windowRect = {}; - if (!GetWindowRect(m_state->window.hwnd, &windowRect)) { - return false; - } - - m_state->chrome.runtime.BeginBorderlessResize(edge, screenPoint, windowRect); - AcquirePointerCapture(EditorWindowPointerCaptureOwner::BorderlessResize); - InvalidateHostWindow(); - return true; + return m_chromeController->HandleResizeButtonDown(*this, lParam); } bool EditorWindow::HandleBorderlessWindowResizeButtonUp() { - if (!m_state->chrome.runtime.IsBorderlessResizeActive()) { - return false; - } - - m_state->chrome.runtime.EndBorderlessResize(); - ReleasePointerCapture(EditorWindowPointerCaptureOwner::BorderlessResize); - InvalidateHostWindow(); - return true; + return m_chromeController->HandleResizeButtonUp(*this); } bool EditorWindow::HandleBorderlessWindowResizePointerMove( EditorContext& editorContext, bool globalTabDragActive) { - if (!m_state->chrome.runtime.IsBorderlessResizeActive() || - m_state->window.hwnd == nullptr) { - return false; - } - - POINT currentScreenPoint = {}; - if (!GetCursorPos(¤tScreenPoint)) { - return false; - } - - RECT targetRect = Host::ComputeBorderlessWindowResizeRect( - m_state->chrome.runtime.GetBorderlessResizeInitialWindowRect(), - m_state->chrome.runtime.GetBorderlessResizeInitialScreenPoint(), - currentScreenPoint, - m_state->chrome.runtime.GetBorderlessResizeEdge(), - 640, - 360); - const int width = targetRect.right - targetRect.left; - const int height = targetRect.bottom - targetRect.top; - if (width <= 0 || height <= 0) { - return true; - } - - m_state->chrome.runtime.SetPredictedClientPixelSize( - static_cast(width), - static_cast(height)); - ApplyWindowResize(static_cast(width), static_cast(height)); - (void)RenderFrame(editorContext, globalTabDragActive); - - SetWindowPos( - m_state->window.hwnd, - nullptr, - targetRect.left, - targetRect.top, - width, - height, - SWP_NOZORDER | SWP_NOACTIVATE); - return true; + return m_chromeController->HandleResizePointerMove( + *this, + editorContext, + globalTabDragActive); } void EditorWindow::ClearBorderlessWindowResizeState() { - if (m_state->chrome.runtime.IsBorderlessResizeActive()) { - return; - } - - if (m_state->chrome.runtime.GetHoveredBorderlessResizeEdge() == - Host::BorderlessWindowResizeEdge::None) { - return; - } - - m_state->chrome.runtime.SetHoveredBorderlessResizeEdge( - Host::BorderlessWindowResizeEdge::None); - InvalidateHostWindow(); + m_chromeController->ClearResizeState(*this); } void EditorWindow::ForceClearBorderlessWindowResizeState() { - if (m_state->chrome.runtime.GetHoveredBorderlessResizeEdge() == - Host::BorderlessWindowResizeEdge::None && - !m_state->chrome.runtime.IsBorderlessResizeActive()) { - return; - } - - m_state->chrome.runtime.SetHoveredBorderlessResizeEdge( - Host::BorderlessWindowResizeEdge::None); - m_state->chrome.runtime.EndBorderlessResize(); - ReleasePointerCapture(EditorWindowPointerCaptureOwner::BorderlessResize); - InvalidateHostWindow(); + m_chromeController->ForceClearResizeState(*this); } Host::BorderlessWindowResizeEdge EditorWindow::HitTestBorderlessWindowResizeEdge( LPARAM lParam) const { - if (!IsBorderlessWindowEnabled() || - m_state->window.hwnd == nullptr || - IsBorderlessWindowMaximized()) { - return Host::BorderlessWindowResizeEdge::None; - } - - RECT clientRect = {}; - if (!GetClientRect(m_state->window.hwnd, &clientRect)) { - return Host::BorderlessWindowResizeEdge::None; - } - - const float clientWidthDips = - PixelsToDips(static_cast((std::max)(clientRect.right - clientRect.left, 1L))); - const float clientHeightDips = - PixelsToDips(static_cast((std::max)(clientRect.bottom - clientRect.top, 1L))); - return Host::HitTestBorderlessWindowResizeEdge( - UIRect(0.0f, 0.0f, clientWidthDips, clientHeightDips), - ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); + return m_chromeController->HitTestResizeEdge(*this, lParam); } void EditorWindow::ApplyBorderlessWindowResizeCursorHoverPriority() { - if (m_state->chrome.runtime.GetHoveredBorderlessResizeEdge() != - Host::BorderlessWindowResizeEdge::None || - m_state->chrome.runtime.IsBorderlessResizeActive()) { - m_state->chrome.chromeState.hoveredTarget = - Host::BorderlessWindowChromeHitTarget::None; - } + m_chromeController->ApplyResizeCursorHoverPriority(); } } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowChromeController.cpp b/new_editor/app/Platform/Win32/EditorWindowChromeController.cpp new file mode 100644 index 00000000..bb75129c --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowChromeController.cpp @@ -0,0 +1,861 @@ +#include "Platform/Win32/EditorWindowChromeController.h" + +#include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowConstants.h" +#include "Platform/Win32/EditorWindowInternalState.h" +#include "Platform/Win32/EditorWindowRuntimeController.h" +#include "Platform/Win32/EditorWindowStyle.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace XCEngine::UI::Editor::App::Internal { + +using namespace EditorWindowInternal; +using ::XCEngine::UI::Layout::MeasureUITabStripHeaderWidth; +using ::XCEngine::UI::UIColor; +using ::XCEngine::UI::UIDrawList; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; + +namespace { + +constexpr float kTitleBarLogoExtent = 16.0f; +constexpr float kTitleBarLogoInsetLeft = 8.0f; +constexpr float kTitleBarLogoTextGap = 8.0f; + +std::string ResolveDetachedTitleTabText(const EditorWindow& window) { + const auto& workspaceController = window.GetWorkspaceController(); + const std::string_view activePanelId = workspaceController.GetWorkspace().activePanelId; + if (!activePanelId.empty()) { + if (const UIEditorPanelDescriptor* descriptor = + FindUIEditorPanelDescriptor( + workspaceController.GetPanelRegistry(), + activePanelId); + descriptor != nullptr && + !descriptor->defaultTitle.empty()) { + return descriptor->defaultTitle; + } + } + + return std::string("Panel"); +} + +float ResolveDetachedTabWidth(std::string_view text) { + const Widgets::UIEditorTabStripMetrics& metrics = ResolveUIEditorTabStripMetrics(); + Widgets::UIEditorTabStripItem item = {}; + item.title = std::string(text); + const float desiredLabelWidth = + Widgets::ResolveUIEditorTabStripDesiredHeaderLabelWidth(item, metrics); + return MeasureUITabStripHeaderWidth(desiredLabelWidth, metrics.layoutMetrics); +} + +bool IsRootPanelVisible( + const UIEditorWorkspaceController& controller, + std::string_view panelId) { + const UIEditorPanelSessionState* panelState = + FindUIEditorPanelSessionState(controller.GetSession(), panelId); + return panelState != nullptr && panelState->open && panelState->visible; +} + +bool HasSingleVisibleRootTab(const UIEditorWorkspaceController& controller) { + const UIEditorWorkspaceNode& root = controller.GetWorkspace().root; + if (root.kind != UIEditorWorkspaceNodeKind::TabStack) { + return false; + } + + std::size_t visiblePanelCount = 0u; + for (const UIEditorWorkspaceNode& child : root.children) { + if (child.kind != UIEditorWorkspaceNodeKind::Panel || + !IsRootPanelVisible(controller, child.panel.panelId)) { + continue; + } + + ++visiblePanelCount; + if (visiblePanelCount > 1u) { + return false; + } + } + + return visiblePanelCount == 1u; +} + +UIRect BuildDetachedTitleLogoRect(const Host::BorderlessWindowChromeLayout& layout) { + const float availableLeft = layout.titleBarRect.x; + const float availableRight = layout.minimizeButtonRect.x; + const float centeredX = std::floor( + layout.titleBarRect.x + layout.titleBarRect.width * 0.5f - kTitleBarLogoExtent * 0.5f); + const float clampedX = (std::max)( + availableLeft, + (std::min)(centeredX, availableRight - kTitleBarLogoExtent)); + const float logoY = + layout.titleBarRect.y + + (std::max)(0.0f, (layout.titleBarRect.height - kTitleBarLogoExtent) * 0.5f); + return UIRect(clampedX, logoY, kTitleBarLogoExtent, kTitleBarLogoExtent); +} + +} // namespace + +void EditorWindowChromeController::Reset() { + m_chromeState = {}; + m_runtimeState.Reset(); +} + +void EditorWindowChromeController::SetWindowDpi(UINT dpi) { + m_runtimeState.SetWindowDpi(dpi); +} + +UINT EditorWindowChromeController::GetWindowDpi() const { + return m_runtimeState.GetWindowDpi(); +} + +float EditorWindowChromeController::GetDpiScale(float baseDpiScale) const { + return m_runtimeState.GetDpiScale(baseDpiScale); +} + +void EditorWindowChromeController::BeginInteractiveResize() { + m_runtimeState.BeginInteractiveResize(); +} + +void EditorWindowChromeController::EndInteractiveResize() { + m_runtimeState.EndInteractiveResize(); +} + +void EditorWindowChromeController::BeginBorderlessResize( + Host::BorderlessWindowResizeEdge edge, + const POINT& initialScreenPoint, + const RECT& initialWindowRect) { + m_runtimeState.BeginBorderlessResize(edge, initialScreenPoint, initialWindowRect); +} + +void EditorWindowChromeController::EndBorderlessResize() { + m_runtimeState.EndBorderlessResize(); +} + +bool EditorWindowChromeController::IsBorderlessResizeActive() const { + return m_runtimeState.IsBorderlessResizeActive(); +} + +Host::BorderlessWindowResizeEdge EditorWindowChromeController::GetBorderlessResizeEdge() const { + return m_runtimeState.GetBorderlessResizeEdge(); +} + +const POINT& EditorWindowChromeController::GetBorderlessResizeInitialScreenPoint() const { + return m_runtimeState.GetBorderlessResizeInitialScreenPoint(); +} + +const RECT& EditorWindowChromeController::GetBorderlessResizeInitialWindowRect() const { + return m_runtimeState.GetBorderlessResizeInitialWindowRect(); +} + +void EditorWindowChromeController::SetHoveredBorderlessResizeEdge( + Host::BorderlessWindowResizeEdge edge) { + m_runtimeState.SetHoveredBorderlessResizeEdge(edge); +} + +Host::BorderlessWindowResizeEdge EditorWindowChromeController::GetHoveredBorderlessResizeEdge() + const { + return m_runtimeState.GetHoveredBorderlessResizeEdge(); +} + +void EditorWindowChromeController::SetPredictedClientPixelSize(UINT width, UINT height) { + m_runtimeState.SetPredictedClientPixelSize(width, height); +} + +void EditorWindowChromeController::ClearPredictedClientPixelSize() { + m_runtimeState.ClearPredictedClientPixelSize(); +} + +bool EditorWindowChromeController::TryGetPredictedClientPixelSize( + UINT& outWidth, + UINT& outHeight) const { + return m_runtimeState.TryGetPredictedClientPixelSize(outWidth, outHeight); +} + +void EditorWindowChromeController::SetBorderlessWindowMaximized(bool maximized) { + m_runtimeState.SetBorderlessWindowMaximized(maximized); +} + +bool EditorWindowChromeController::IsBorderlessWindowMaximized() const { + return m_runtimeState.IsBorderlessWindowMaximized(); +} + +void EditorWindowChromeController::SetBorderlessWindowRestoreRect(const RECT& rect) { + m_runtimeState.SetBorderlessWindowRestoreRect(rect); +} + +bool EditorWindowChromeController::TryGetBorderlessWindowRestoreRect(RECT& outRect) const { + return m_runtimeState.TryGetBorderlessWindowRestoreRect(outRect); +} + +void EditorWindowChromeController::BeginBorderlessWindowDragRestore( + const POINT& initialScreenPoint) { + m_runtimeState.BeginBorderlessWindowDragRestore(initialScreenPoint); +} + +void EditorWindowChromeController::EndBorderlessWindowDragRestore() { + m_runtimeState.EndBorderlessWindowDragRestore(); +} + +bool EditorWindowChromeController::IsBorderlessWindowDragRestoreArmed() const { + return m_runtimeState.IsBorderlessWindowDragRestoreArmed(); +} + +const POINT& EditorWindowChromeController::GetBorderlessWindowDragRestoreInitialScreenPoint() + const { + return m_runtimeState.GetBorderlessWindowDragRestoreInitialScreenPoint(); +} + +Host::BorderlessWindowChromeHitTarget EditorWindowChromeController::GetHoveredChromeTarget() const { + return m_chromeState.hoveredTarget; +} + +void EditorWindowChromeController::SetHoveredChromeTarget( + Host::BorderlessWindowChromeHitTarget target) { + m_chromeState.hoveredTarget = target; +} + +Host::BorderlessWindowChromeHitTarget EditorWindowChromeController::GetPressedChromeTarget() const { + return m_chromeState.pressedTarget; +} + +void EditorWindowChromeController::SetPressedChromeTarget( + Host::BorderlessWindowChromeHitTarget target) { + m_chromeState.pressedTarget = target; +} + +void EditorWindowChromeController::ResetChromeState() { + m_chromeState = {}; +} + +bool EditorWindowChromeController::IsChromeStateClear() const { + return m_chromeState.hoveredTarget == Host::BorderlessWindowChromeHitTarget::None && + m_chromeState.pressedTarget == Host::BorderlessWindowChromeHitTarget::None; +} + +const Host::BorderlessWindowChromeState& EditorWindowChromeController::GetChromeState() const { + return m_chromeState; +} + +bool EditorWindowChromeController::HandleSystemCommand( + EditorWindow& window, + EditorContext& editorContext, + bool globalTabDragActive, + WPARAM wParam) { + if (!window.IsBorderlessWindowEnabled()) { + return false; + } + + switch (wParam & 0xFFF0u) { + case SC_MAXIMIZE: + ToggleMaximizeRestore(window, editorContext, globalTabDragActive); + return true; + case SC_RESTORE: + if (!IsIconic(window.m_state->window.hwnd)) { + ToggleMaximizeRestore(window, editorContext, globalTabDragActive); + return true; + } + return false; + default: + return false; + } +} + +bool EditorWindowChromeController::HandleGetMinMaxInfo( + const EditorWindow& window, + LPARAM lParam) const { + return Host::HandleBorderlessWindowGetMinMaxInfo(window.m_state->window.hwnd, lParam); +} + +LRESULT EditorWindowChromeController::HandleNcCalcSize( + const EditorWindow& window, + WPARAM wParam, + LPARAM lParam) const { + return Host::HandleBorderlessWindowNcCalcSize( + window.m_state->window.hwnd, + wParam, + lParam, + GetWindowDpi()); +} + +bool EditorWindowChromeController::UpdateResizeHover(EditorWindow& window, LPARAM lParam) { + const Host::BorderlessWindowResizeEdge hoveredEdge = HitTestResizeEdge(window, lParam); + if (GetHoveredBorderlessResizeEdge() == hoveredEdge) { + return false; + } + + SetHoveredBorderlessResizeEdge(hoveredEdge); + ApplyResizeCursorHoverPriority(); + return true; +} + +bool EditorWindowChromeController::HandleResizeButtonDown(EditorWindow& window, LPARAM lParam) { + const Host::BorderlessWindowResizeEdge edge = HitTestResizeEdge(window, lParam); + if (edge == Host::BorderlessWindowResizeEdge::None || + window.m_state->window.hwnd == nullptr) { + return false; + } + + POINT screenPoint = {}; + if (!GetCursorPos(&screenPoint)) { + return false; + } + + RECT windowRect = {}; + if (!GetWindowRect(window.m_state->window.hwnd, &windowRect)) { + return false; + } + + BeginBorderlessResize(edge, screenPoint, windowRect); + window.AcquirePointerCapture(EditorWindowPointerCaptureOwner::BorderlessResize); + window.InvalidateHostWindow(); + return true; +} + +bool EditorWindowChromeController::HandleResizeButtonUp(EditorWindow& window) { + if (!IsBorderlessResizeActive()) { + return false; + } + + EndBorderlessResize(); + window.ReleasePointerCapture(EditorWindowPointerCaptureOwner::BorderlessResize); + window.InvalidateHostWindow(); + return true; +} + +bool EditorWindowChromeController::HandleResizePointerMove( + EditorWindow& window, + EditorContext& editorContext, + bool globalTabDragActive) { + if (!IsBorderlessResizeActive() || + window.m_state->window.hwnd == nullptr) { + return false; + } + + POINT currentScreenPoint = {}; + if (!GetCursorPos(¤tScreenPoint)) { + return false; + } + + RECT targetRect = Host::ComputeBorderlessWindowResizeRect( + GetBorderlessResizeInitialWindowRect(), + GetBorderlessResizeInitialScreenPoint(), + currentScreenPoint, + GetBorderlessResizeEdge(), + 640, + 360); + const int width = targetRect.right - targetRect.left; + const int height = targetRect.bottom - targetRect.top; + if (width <= 0 || height <= 0) { + return true; + } + + SetPredictedClientPixelSize( + static_cast(width), + static_cast(height)); + window.ApplyWindowResize(static_cast(width), static_cast(height)); + (void)window.RenderFrame(editorContext, globalTabDragActive); + + SetWindowPos( + window.m_state->window.hwnd, + nullptr, + targetRect.left, + targetRect.top, + width, + height, + SWP_NOZORDER | SWP_NOACTIVATE); + return true; +} + +void EditorWindowChromeController::ClearResizeState(EditorWindow& window) { + if (IsBorderlessResizeActive()) { + return; + } + + if (GetHoveredBorderlessResizeEdge() == Host::BorderlessWindowResizeEdge::None) { + return; + } + + SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None); + window.InvalidateHostWindow(); +} + +void EditorWindowChromeController::ForceClearResizeState(EditorWindow& window) { + if (GetHoveredBorderlessResizeEdge() == Host::BorderlessWindowResizeEdge::None && + !IsBorderlessResizeActive()) { + return; + } + + SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None); + EndBorderlessResize(); + window.ReleasePointerCapture(EditorWindowPointerCaptureOwner::BorderlessResize); + window.InvalidateHostWindow(); +} + +Host::BorderlessWindowResizeEdge EditorWindowChromeController::HitTestResizeEdge( + const EditorWindow& window, + LPARAM lParam) const { + if (!window.IsBorderlessWindowEnabled() || + window.m_state->window.hwnd == nullptr || + window.IsBorderlessWindowMaximized()) { + return Host::BorderlessWindowResizeEdge::None; + } + + RECT clientRect = {}; + if (!GetClientRect(window.m_state->window.hwnd, &clientRect)) { + return Host::BorderlessWindowResizeEdge::None; + } + + const float clientWidthDips = + window.PixelsToDips(static_cast((std::max)(clientRect.right - clientRect.left, 1L))); + const float clientHeightDips = + window.PixelsToDips(static_cast((std::max)(clientRect.bottom - clientRect.top, 1L))); + return Host::HitTestBorderlessWindowResizeEdge( + UIRect(0.0f, 0.0f, clientWidthDips, clientHeightDips), + window.ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); +} + +bool EditorWindowChromeController::UpdateChromeHover(EditorWindow& window, LPARAM lParam) { + if (GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None || + IsBorderlessResizeActive()) { + const bool changed = + GetHoveredChromeTarget() != Host::BorderlessWindowChromeHitTarget::None; + SetHoveredChromeTarget(Host::BorderlessWindowChromeHitTarget::None); + return changed; + } + + const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestChrome(window, lParam); + const Host::BorderlessWindowChromeHitTarget buttonTarget = + hitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton || + hitTarget == Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton || + hitTarget == Host::BorderlessWindowChromeHitTarget::CloseButton + ? hitTarget + : Host::BorderlessWindowChromeHitTarget::None; + if (GetHoveredChromeTarget() == buttonTarget) { + return false; + } + + SetHoveredChromeTarget(buttonTarget); + return true; +} + +bool EditorWindowChromeController::HandleChromeButtonDown(EditorWindow& window, LPARAM lParam) { + if (GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None || + IsBorderlessResizeActive()) { + return false; + } + + const Host::BorderlessWindowChromeHitTarget hitTarget = HitTestChrome(window, lParam); + switch (hitTarget) { + case Host::BorderlessWindowChromeHitTarget::MinimizeButton: + case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton: + case Host::BorderlessWindowChromeHitTarget::CloseButton: + SetPressedChromeTarget(hitTarget); + window.AcquirePointerCapture(EditorWindowPointerCaptureOwner::BorderlessChrome); + window.InvalidateHostWindow(); + return true; + case Host::BorderlessWindowChromeHitTarget::DragRegion: + if (window.m_state->window.hwnd != nullptr) { + if (window.IsBorderlessWindowMaximized()) { + POINT screenPoint = {}; + if (GetCursorPos(&screenPoint)) { + BeginBorderlessWindowDragRestore(screenPoint); + window.AcquirePointerCapture(EditorWindowPointerCaptureOwner::BorderlessChrome); + return true; + } + } + + window.ForceReleasePointerCapture(); + SendMessageW(window.m_state->window.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); + } + return true; + case Host::BorderlessWindowChromeHitTarget::None: + default: + return false; + } +} + +bool EditorWindowChromeController::HandleChromeButtonUp( + EditorWindow& window, + EditorContext& editorContext, + bool globalTabDragActive, + LPARAM lParam) { + if (IsBorderlessWindowDragRestoreArmed()) { + ClearChromeDragRestoreState(window); + return true; + } + + const Host::BorderlessWindowChromeHitTarget pressedTarget = GetPressedChromeTarget(); + if (pressedTarget != Host::BorderlessWindowChromeHitTarget::MinimizeButton && + pressedTarget != Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton && + pressedTarget != Host::BorderlessWindowChromeHitTarget::CloseButton) { + return false; + } + + const Host::BorderlessWindowChromeHitTarget releasedTarget = HitTestChrome(window, lParam); + SetPressedChromeTarget(Host::BorderlessWindowChromeHitTarget::None); + window.ReleasePointerCapture(EditorWindowPointerCaptureOwner::BorderlessChrome); + window.InvalidateHostWindow(); + + if (pressedTarget == releasedTarget) { + ExecuteChromeAction(window, editorContext, globalTabDragActive, pressedTarget); + } + return true; +} + +bool EditorWindowChromeController::HandleChromeDoubleClick( + EditorWindow& window, + EditorContext& editorContext, + bool globalTabDragActive, + LPARAM lParam) { + if (IsBorderlessWindowDragRestoreArmed()) { + ClearChromeDragRestoreState(window); + } + + if (HitTestChrome(window, lParam) != Host::BorderlessWindowChromeHitTarget::DragRegion) { + return false; + } + + ExecuteChromeAction( + window, + editorContext, + globalTabDragActive, + Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton); + return true; +} + +bool EditorWindowChromeController::HandleChromeDragRestorePointerMove( + EditorWindow& window, + EditorContext& editorContext, + bool globalTabDragActive) { + if (!IsBorderlessWindowDragRestoreArmed() || + window.m_state->window.hwnd == nullptr) { + return false; + } + + POINT currentScreenPoint = {}; + if (!GetCursorPos(¤tScreenPoint)) { + return true; + } + + const POINT initialScreenPoint = GetBorderlessWindowDragRestoreInitialScreenPoint(); + const int dragThresholdX = (std::max)(GetSystemMetrics(SM_CXDRAG), 1); + const int dragThresholdY = (std::max)(GetSystemMetrics(SM_CYDRAG), 1); + const LONG deltaX = currentScreenPoint.x - initialScreenPoint.x; + const LONG deltaY = currentScreenPoint.y - initialScreenPoint.y; + if (std::abs(deltaX) < dragThresholdX && + std::abs(deltaY) < dragThresholdY) { + return true; + } + + RECT restoreRect = {}; + RECT currentRect = {}; + RECT workAreaRect = {}; + if (!TryGetBorderlessWindowRestoreRect(restoreRect) || + !QueryCurrentWindowRect(window, currentRect) || + !QueryBorderlessWindowWorkAreaRect(window, workAreaRect)) { + ClearChromeDragRestoreState(window); + return true; + } + + const int restoreWidth = restoreRect.right - restoreRect.left; + const int restoreHeight = restoreRect.bottom - restoreRect.top; + const int currentWidth = currentRect.right - currentRect.left; + if (restoreWidth <= 0 || restoreHeight <= 0 || currentWidth <= 0) { + ClearChromeDragRestoreState(window); + return true; + } + + const float pointerRatio = + static_cast(currentScreenPoint.x - currentRect.left) / + static_cast(currentWidth); + const float clampedPointerRatio = (std::clamp)(pointerRatio, 0.0f, 1.0f); + const int newLeft = + (std::clamp)( + currentScreenPoint.x - + static_cast(clampedPointerRatio * static_cast(restoreWidth)), + workAreaRect.left, + workAreaRect.right - restoreWidth); + const int titleBarHeightPixels = + static_cast(kBorderlessTitleBarHeightDips * window.GetDpiScale()); + const int newTop = + (std::clamp)( + currentScreenPoint.y - (std::max)(titleBarHeightPixels / 2, 1), + workAreaRect.top, + workAreaRect.bottom - restoreHeight); + const RECT targetRect = { + newLeft, + newTop, + newLeft + restoreWidth, + newTop + restoreHeight + }; + + SetBorderlessWindowMaximized(false); + ApplyPredictedWindowRectTransition(window, editorContext, globalTabDragActive, targetRect); + ClearChromeDragRestoreState(window); + SendMessageW(window.m_state->window.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); + return true; +} + +void EditorWindowChromeController::ClearChromeDragRestoreState(EditorWindow& window) { + if (!IsBorderlessWindowDragRestoreArmed()) { + return; + } + + EndBorderlessWindowDragRestore(); + window.ReleasePointerCapture(EditorWindowPointerCaptureOwner::BorderlessChrome); +} + +void EditorWindowChromeController::ClearChromeState(EditorWindow& window) { + if (IsChromeStateClear()) { + return; + } + + ResetChromeState(); + window.InvalidateHostWindow(); +} + +Host::BorderlessWindowChromeHitTarget EditorWindowChromeController::HitTestChrome( + const EditorWindow& window, + LPARAM lParam) const { + if (!window.IsBorderlessWindowEnabled() || window.m_state->window.hwnd == nullptr) { + return Host::BorderlessWindowChromeHitTarget::None; + } + + RECT clientRect = {}; + if (!GetClientRect(window.m_state->window.hwnd, &clientRect)) { + return Host::BorderlessWindowChromeHitTarget::None; + } + + const float clientWidthDips = + window.PixelsToDips(static_cast((std::max)(clientRect.right - clientRect.left, 1L))); + const Host::BorderlessWindowChromeLayout layout = + ResolveChromeLayout(window, clientWidthDips); + return Host::HitTestBorderlessWindowChrome( + layout, + window.ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); +} + +Host::BorderlessWindowChromeLayout EditorWindowChromeController::ResolveChromeLayout( + const EditorWindow& window, + float clientWidthDips) const { + float leadingOccupiedRight = 0.0f; + if (ShouldUseDetachedTitleBarTabStrip(window)) { + leadingOccupiedRight = ResolveDetachedTabWidth(ResolveDetachedTitleTabText(window)); + } + + return Host::BuildBorderlessWindowChromeLayout( + UIRect(0.0f, 0.0f, clientWidthDips, kBorderlessTitleBarHeightDips), + leadingOccupiedRight); +} + +bool EditorWindowChromeController::ShouldUseDetachedTitleBarTabStrip( + const EditorWindow& window) const { + return !window.m_state->window.primary && + HasSingleVisibleRootTab(window.m_runtime->GetWorkspaceController()); +} + +void EditorWindowChromeController::AppendChrome( + const EditorWindow& window, + UIDrawList& drawList, + float clientWidthDips) const { + if (!window.IsBorderlessWindowEnabled()) { + return; + } + + const Host::BorderlessWindowChromeLayout layout = + ResolveChromeLayout(window, clientWidthDips); + const bool useDetachedTitleBarTabStrip = ShouldUseDetachedTitleBarTabStrip(window); + if (!useDetachedTitleBarTabStrip) { + drawList.AddFilledRect(layout.titleBarRect, kShellSurfaceColor); + drawList.AddLine( + UIPoint(layout.titleBarRect.x, layout.titleBarRect.y + layout.titleBarRect.height), + UIPoint( + layout.titleBarRect.x + layout.titleBarRect.width, + layout.titleBarRect.y + layout.titleBarRect.height), + kShellBorderColor, + 1.0f); + } + + if (!window.m_state->window.primary) { + if (window.m_runtime->GetTitleBarLogoIcon().IsValid()) { + drawList.AddImage( + BuildDetachedTitleLogoRect(layout), + window.m_runtime->GetTitleBarLogoIcon(), + UIColor(1.0f, 1.0f, 1.0f, 1.0f)); + } + } else { + const float iconX = layout.titleBarRect.x + kTitleBarLogoInsetLeft; + const float iconY = + layout.titleBarRect.y + + (std::max)(0.0f, (layout.titleBarRect.height - kTitleBarLogoExtent) * 0.5f); + if (window.m_runtime->GetTitleBarLogoIcon().IsValid()) { + drawList.AddImage( + UIRect(iconX, iconY, kTitleBarLogoExtent, kTitleBarLogoExtent), + window.m_runtime->GetTitleBarLogoIcon(), + UIColor(1.0f, 1.0f, 1.0f, 1.0f)); + } + + const std::string titleText = + window.m_state->window.titleText.empty() + ? std::string("XCEngine Editor") + : window.m_state->window.titleText; + drawList.AddText( + UIPoint( + iconX + + (window.m_runtime->GetTitleBarLogoIcon().IsValid() + ? (kTitleBarLogoExtent + kTitleBarLogoTextGap) + : 4.0f), + layout.titleBarRect.y + + (std::max)( + 0.0f, + (layout.titleBarRect.height - kBorderlessTitleBarFontSize) * 0.5f - 1.0f)), + titleText, + kShellTextColor, + kBorderlessTitleBarFontSize); + } + + Host::AppendBorderlessWindowChrome( + drawList, + layout, + GetChromeState(), + window.IsBorderlessWindowMaximized()); +} + +void EditorWindowChromeController::ApplyResizeCursorHoverPriority() { + if (GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None || + IsBorderlessResizeActive()) { + SetHoveredChromeTarget(Host::BorderlessWindowChromeHitTarget::None); + } +} + +bool EditorWindowChromeController::QueryCurrentWindowRect( + const EditorWindow& window, + RECT& outRect) const { + outRect = {}; + return window.m_state->window.hwnd != nullptr && + GetWindowRect(window.m_state->window.hwnd, &outRect) != FALSE; +} + +bool EditorWindowChromeController::QueryBorderlessWindowWorkAreaRect( + const EditorWindow& window, + RECT& outRect) const { + outRect = {}; + if (window.m_state->window.hwnd == nullptr) { + return false; + } + + const HMONITOR monitor = + MonitorFromWindow(window.m_state->window.hwnd, MONITOR_DEFAULTTONEAREST); + if (monitor == nullptr) { + return false; + } + + MONITORINFO monitorInfo = {}; + monitorInfo.cbSize = sizeof(monitorInfo); + if (!GetMonitorInfoW(monitor, &monitorInfo)) { + return false; + } + + outRect = monitorInfo.rcWork; + return true; +} + +bool EditorWindowChromeController::ApplyPredictedWindowRectTransition( + EditorWindow& window, + EditorContext& editorContext, + bool globalTabDragActive, + const RECT& targetRect) { + if (window.m_state->window.hwnd == nullptr) { + return false; + } + + const int width = targetRect.right - targetRect.left; + const int height = targetRect.bottom - targetRect.top; + if (width <= 0 || height <= 0) { + return false; + } + + SetPredictedClientPixelSize(static_cast(width), static_cast(height)); + window.ApplyWindowResize(static_cast(width), static_cast(height)); + (void)window.RenderFrame(editorContext, globalTabDragActive); + SetWindowPos( + window.m_state->window.hwnd, + nullptr, + targetRect.left, + targetRect.top, + width, + height, + SWP_NOZORDER | SWP_NOACTIVATE); + window.InvalidateHostWindow(); + return true; +} + +void EditorWindowChromeController::ToggleMaximizeRestore( + EditorWindow& window, + EditorContext& editorContext, + bool globalTabDragActive) { + if (window.m_state->window.hwnd == nullptr) { + return; + } + + if (!window.IsBorderlessWindowMaximized()) { + RECT currentRect = {}; + RECT workAreaRect = {}; + if (!QueryCurrentWindowRect(window, currentRect) || + !QueryBorderlessWindowWorkAreaRect(window, workAreaRect)) { + return; + } + + SetBorderlessWindowRestoreRect(currentRect); + SetBorderlessWindowMaximized(true); + ApplyPredictedWindowRectTransition(window, editorContext, globalTabDragActive, workAreaRect); + return; + } + + RECT restoreRect = {}; + if (!TryGetBorderlessWindowRestoreRect(restoreRect)) { + return; + } + + SetBorderlessWindowMaximized(false); + ApplyPredictedWindowRectTransition(window, editorContext, globalTabDragActive, restoreRect); +} + +void EditorWindowChromeController::ExecuteChromeAction( + EditorWindow& window, + EditorContext& editorContext, + bool globalTabDragActive, + Host::BorderlessWindowChromeHitTarget target) { + if (window.m_state->window.hwnd == nullptr) { + return; + } + + switch (target) { + case Host::BorderlessWindowChromeHitTarget::MinimizeButton: + ShowWindow(window.m_state->window.hwnd, SW_MINIMIZE); + break; + case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton: + ToggleMaximizeRestore(window, editorContext, globalTabDragActive); + break; + case Host::BorderlessWindowChromeHitTarget::CloseButton: + PostMessageW(window.m_state->window.hwnd, WM_CLOSE, 0, 0); + break; + case Host::BorderlessWindowChromeHitTarget::DragRegion: + case Host::BorderlessWindowChromeHitTarget::None: + default: + break; + } + + window.InvalidateHostWindow(); +} + +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/EditorWindowChromeController.h b/new_editor/app/Platform/Win32/EditorWindowChromeController.h new file mode 100644 index 00000000..7015a800 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowChromeController.h @@ -0,0 +1,147 @@ +#pragma once + +#include +#include + +namespace XCEngine::UI { + +class UIDrawList; + +} // namespace XCEngine::UI + +namespace XCEngine::UI::Editor::App { + +class EditorContext; +class EditorWindow; + +} // namespace XCEngine::UI::Editor::App + +namespace XCEngine::UI::Editor::App::Internal { + +class EditorWindowChromeController final { +public: + EditorWindowChromeController() = default; + ~EditorWindowChromeController() = default; + + EditorWindowChromeController(const EditorWindowChromeController&) = delete; + EditorWindowChromeController& operator=(const EditorWindowChromeController&) = delete; + EditorWindowChromeController(EditorWindowChromeController&&) = delete; + EditorWindowChromeController& operator=(EditorWindowChromeController&&) = delete; + + void Reset(); + + void SetWindowDpi(UINT dpi); + UINT GetWindowDpi() const; + float GetDpiScale(float baseDpiScale) const; + + void BeginInteractiveResize(); + void EndInteractiveResize(); + + void BeginBorderlessResize( + Host::BorderlessWindowResizeEdge edge, + const POINT& initialScreenPoint, + const RECT& initialWindowRect); + void EndBorderlessResize(); + bool IsBorderlessResizeActive() const; + Host::BorderlessWindowResizeEdge GetBorderlessResizeEdge() const; + const POINT& GetBorderlessResizeInitialScreenPoint() const; + const RECT& GetBorderlessResizeInitialWindowRect() const; + void SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge edge); + Host::BorderlessWindowResizeEdge GetHoveredBorderlessResizeEdge() const; + + void SetPredictedClientPixelSize(UINT width, UINT height); + void ClearPredictedClientPixelSize(); + bool TryGetPredictedClientPixelSize(UINT& outWidth, UINT& outHeight) const; + + void SetBorderlessWindowMaximized(bool maximized); + bool IsBorderlessWindowMaximized() const; + void SetBorderlessWindowRestoreRect(const RECT& rect); + bool TryGetBorderlessWindowRestoreRect(RECT& outRect) const; + + void BeginBorderlessWindowDragRestore(const POINT& initialScreenPoint); + void EndBorderlessWindowDragRestore(); + bool IsBorderlessWindowDragRestoreArmed() const; + const POINT& GetBorderlessWindowDragRestoreInitialScreenPoint() const; + + Host::BorderlessWindowChromeHitTarget GetHoveredChromeTarget() const; + void SetHoveredChromeTarget(Host::BorderlessWindowChromeHitTarget target); + Host::BorderlessWindowChromeHitTarget GetPressedChromeTarget() const; + void SetPressedChromeTarget(Host::BorderlessWindowChromeHitTarget target); + void ResetChromeState(); + bool IsChromeStateClear() const; + const Host::BorderlessWindowChromeState& GetChromeState() const; + + bool HandleSystemCommand( + EditorWindow& window, + EditorContext& editorContext, + bool globalTabDragActive, + WPARAM wParam); + bool HandleGetMinMaxInfo(const EditorWindow& window, LPARAM lParam) const; + LRESULT HandleNcCalcSize(const EditorWindow& window, WPARAM wParam, LPARAM lParam) const; + + bool UpdateResizeHover(EditorWindow& window, LPARAM lParam); + bool HandleResizeButtonDown(EditorWindow& window, LPARAM lParam); + bool HandleResizeButtonUp(EditorWindow& window); + bool HandleResizePointerMove( + EditorWindow& window, + EditorContext& editorContext, + bool globalTabDragActive); + void ClearResizeState(EditorWindow& window); + void ForceClearResizeState(EditorWindow& window); + Host::BorderlessWindowResizeEdge HitTestResizeEdge( + const EditorWindow& window, + LPARAM lParam) const; + + bool UpdateChromeHover(EditorWindow& window, LPARAM lParam); + bool HandleChromeButtonDown(EditorWindow& window, LPARAM lParam); + bool HandleChromeButtonUp( + EditorWindow& window, + EditorContext& editorContext, + bool globalTabDragActive, + LPARAM lParam); + bool HandleChromeDoubleClick( + EditorWindow& window, + EditorContext& editorContext, + bool globalTabDragActive, + LPARAM lParam); + bool HandleChromeDragRestorePointerMove( + EditorWindow& window, + EditorContext& editorContext, + bool globalTabDragActive); + void ClearChromeDragRestoreState(EditorWindow& window); + void ClearChromeState(EditorWindow& window); + Host::BorderlessWindowChromeHitTarget HitTestChrome( + const EditorWindow& window, + LPARAM lParam) const; + Host::BorderlessWindowChromeLayout ResolveChromeLayout( + const EditorWindow& window, + float clientWidthDips) const; + bool ShouldUseDetachedTitleBarTabStrip(const EditorWindow& window) const; + void AppendChrome( + const EditorWindow& window, + ::XCEngine::UI::UIDrawList& drawList, + float clientWidthDips) const; + void ApplyResizeCursorHoverPriority(); + bool QueryCurrentWindowRect(const EditorWindow& window, RECT& outRect) const; + bool QueryBorderlessWindowWorkAreaRect(const EditorWindow& window, RECT& outRect) const; + bool ApplyPredictedWindowRectTransition( + EditorWindow& window, + EditorContext& editorContext, + bool globalTabDragActive, + const RECT& targetRect); + void ToggleMaximizeRestore( + EditorWindow& window, + EditorContext& editorContext, + bool globalTabDragActive); + void ExecuteChromeAction( + EditorWindow& window, + EditorContext& editorContext, + bool globalTabDragActive, + Host::BorderlessWindowChromeHitTarget target); + +private: + Host::BorderlessWindowChromeState m_chromeState = {}; + Host::HostRuntimeState m_runtimeState = {}; +}; + +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/EditorWindowFrame.cpp b/new_editor/app/Platform/Win32/EditorWindowFrame.cpp index 6fa393c4..ca9f0e7f 100644 --- a/new_editor/app/Platform/Win32/EditorWindowFrame.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowFrame.cpp @@ -1,16 +1,18 @@ #include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowConstants.h" +#include "Platform/Win32/EditorWindowFrameOrchestrator.h" +#include "Platform/Win32/EditorWindowInputController.h" #include "Platform/Win32/EditorWindowInternalState.h" +#include "Platform/Win32/EditorWindowRuntimeController.h" #include "Platform/Win32/EditorWindowRuntimeInternal.h" #include "Platform/Win32/EditorWindowStyle.h" #include "Composition/EditorShellPointerInteraction.h" -#include "State/EditorContext.h" - -#include +#include "Composition/EditorContext.h" #include +#include + #include -#include #include namespace XCEngine::UI::Editor::App { @@ -19,56 +21,12 @@ using namespace EditorWindowInternal; using ::XCEngine::UI::UIDrawData; using ::XCEngine::UI::UIDrawList; using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; using ::XCEngine::UI::UIInputModifiers; -using ::XCEngine::UI::UIPoint; using ::XCEngine::UI::UIPointerButton; using ::XCEngine::UI::UIRect; namespace { -std::string DescribeInputEventType(const UIInputEvent& event) { - switch (event.type) { - case UIInputEventType::PointerMove: return "PointerMove"; - case UIInputEventType::PointerEnter: return "PointerEnter"; - case UIInputEventType::PointerLeave: return "PointerLeave"; - case UIInputEventType::PointerButtonDown: return "PointerDown"; - case UIInputEventType::PointerButtonUp: return "PointerUp"; - case UIInputEventType::PointerWheel: return "PointerWheel"; - case UIInputEventType::KeyDown: return "KeyDown"; - case UIInputEventType::KeyUp: return "KeyUp"; - case UIInputEventType::Character: return "Character"; - case UIInputEventType::FocusGained: return "FocusGained"; - case UIInputEventType::FocusLost: return "FocusLost"; - default: return "Unknown"; - } -} - -bool HasPendingPointerStateReconciliationEvent( - const std::vector& events) { - for (const UIInputEvent& event : events) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - case UIInputEventType::PointerWheel: - case UIInputEventType::FocusLost: - return true; - case UIInputEventType::PointerLeave: - case UIInputEventType::KeyDown: - case UIInputEventType::KeyUp: - case UIInputEventType::Character: - case UIInputEventType::FocusGained: - case UIInputEventType::None: - default: - break; - } - } - - return false; -} - std::uint8_t ButtonMask(UIPointerButton button) { switch (button) { case UIPointerButton::Left: return 1u << 0u; @@ -129,7 +87,7 @@ std::uint8_t ResolveExpectedShellCaptureButtons( EditorWindowFrameTransferRequests EditorWindow::RenderFrame( EditorContext& editorContext, bool globalTabDragActive) { - if (!m_state->render.ready || m_state->window.hwnd == nullptr) { + if (!m_runtime->IsReady() || m_state->window.hwnd == nullptr) { return {}; } @@ -154,19 +112,17 @@ EditorWindowFrameTransferRequests EditorWindow::RenderFrame( transferRequests = RenderRuntimeFrame(editorContext, globalTabDragActive, workspaceBounds, drawList); } else { - RenderInvalidFrame(editorContext, drawList); + m_frameOrchestrator->AppendInvalidFrame(editorContext, drawList); } AppendBorderlessWindowChrome(drawList, width); - const Host::D3D12WindowRenderLoopPresentResult presentResult = - m_state->render.windowRenderLoop.Present(drawData); + const Host::D3D12WindowRenderLoopPresentResult presentResult = m_runtime->Present(drawData); if (!presentResult.warning.empty()) { LogRuntimeTrace("present", presentResult.warning); } - m_state->render.autoScreenshot.CaptureIfRequested( - m_state->render.renderer, + m_runtime->CaptureIfRequested( drawData, pixelWidth, pixelHeight, @@ -177,7 +133,7 @@ EditorWindowFrameTransferRequests EditorWindow::RenderFrame( EditorWindowFrameTransferRequests EditorWindow::OnPaintMessage( EditorContext& editorContext, bool globalTabDragActive) { - if (!m_state->render.ready || m_state->window.hwnd == nullptr) { + if (!m_runtime->IsReady() || m_state->window.hwnd == nullptr) { return {}; } @@ -212,163 +168,36 @@ EditorWindowFrameTransferRequests EditorWindow::RenderRuntimeFrame( const UIRect& workspaceBounds, UIDrawList& drawList) { SyncShellCapturedPointerButtonsFromSystemState(); - std::vector frameEvents = std::move(m_state->input.pendingEvents); - m_state->input.pendingEvents.clear(); - if (!frameEvents.empty() && IsVerboseRuntimeTraceEnabled()) { - LogRuntimeTrace( - "input", - DescribeInputEvents(frameEvents) + " | " + - editorContext.DescribeWorkspaceState( - GetWorkspaceController(), - m_state->composition.shellRuntime.GetShellInteractionState())); - } - - const Host::D3D12WindowRenderLoopFrameContext frameContext = - m_state->render.windowRenderLoop.BeginFrame(); - if (!frameContext.warning.empty()) { - LogRuntimeTrace("viewport", frameContext.warning); - } - - editorContext.AttachTextMeasurer(m_state->render.renderer); + std::vector frameEvents = m_inputController->TakePendingEvents(); const bool useDetachedTitleBarTabStrip = ShouldUseDetachedTitleBarTabStrip(); - UIEditorWorkspaceController& workspaceController = GetMutableWorkspaceController(); - m_state->composition.shellRuntime.Update( - editorContext, - workspaceController, + const EditorWindowFrameTransferRequests transferRequests = + m_frameOrchestrator->UpdateAndAppend( + editorContext, + *m_runtime, workspaceBounds, frameEvents, - BuildCaptureStatusText(), - m_state->window.primary - ? EditorShellVariant::Primary - : EditorShellVariant::DetachedWindow, + m_runtime->BuildCaptureStatusText(), + m_state->window.primary, + globalTabDragActive, useDetachedTitleBarTabStrip, - useDetachedTitleBarTabStrip ? kBorderlessTitleBarHeightDips : 0.0f); - const UIEditorShellInteractionFrame& shellFrame = - m_state->composition.shellRuntime.GetShellFrame(); - const UIEditorDockHostInteractionState& dockHostInteractionState = - m_state->composition.shellRuntime - .GetShellInteractionState() - .workspaceInteractionState - .dockHostInteractionState; - - LogFrameInteractionTrace(editorContext, frameEvents, shellFrame); - const EditorWindowFrameTransferRequests transferRequests = - BuildShellTransferRequests(globalTabDragActive, dockHostInteractionState, shellFrame); + drawList); ApplyShellRuntimePointerCapture(); - for (const WorkspaceTraceEntry& entry : - m_state->composition.shellRuntime.GetTraceEntries()) { - LogRuntimeTrace(entry.channel, entry.message); - } ApplyCurrentCursor(); - m_state->composition.shellRuntime.Append(drawList); - if (frameContext.canRenderViewports) { - m_state->composition.shellRuntime.RenderRequestedViewports(frameContext.renderContext); - } return transferRequests; } -void EditorWindow::RenderInvalidFrame( - EditorContext& editorContext, - UIDrawList& drawList) const { - drawList.AddText( - UIPoint(28.0f, 28.0f), - "Editor shell asset invalid.", - EditorWindowInternal::kShellTextColor, - 16.0f); - drawList.AddText( - UIPoint(28.0f, 54.0f), - editorContext.GetValidationMessage().empty() - ? std::string("Unknown validation error.") - : editorContext.GetValidationMessage(), - EditorWindowInternal::kShellMutedTextColor, - 12.0f); -} - -void EditorWindow::LogFrameInteractionTrace( - EditorContext& editorContext, - const std::vector& frameEvents, - const UIEditorShellInteractionFrame& shellFrame) const { - if (!IsVerboseRuntimeTraceEnabled() || - (frameEvents.empty() && - !shellFrame.result.workspaceResult.dockHostResult.layoutChanged && - !shellFrame.result.workspaceResult.dockHostResult.commandExecuted)) { - return; - } - - std::ostringstream frameTrace = {}; - frameTrace << "result consumed=" - << (shellFrame.result.consumed ? "true" : "false") - << " layoutChanged=" - << (shellFrame.result.workspaceResult.dockHostResult.layoutChanged ? "true" : "false") - << " commandExecuted=" - << (shellFrame.result.workspaceResult.dockHostResult.commandExecuted ? "true" : "false") - << " active=" - << GetWorkspaceController().GetWorkspace().activePanelId - << " message=" - << shellFrame.result.workspaceResult.dockHostResult.layoutResult.message; - LogRuntimeTrace("frame", frameTrace.str()); -} - -EditorWindowFrameTransferRequests EditorWindow::BuildShellTransferRequests( - bool globalTabDragActive, - const UIEditorDockHostInteractionState& dockHostInteractionState, - const UIEditorShellInteractionFrame& shellFrame) const { - EditorWindowFrameTransferRequests transferRequests = {}; - POINT screenPoint = {}; - const bool hasScreenPoint = GetCursorPos(&screenPoint) != FALSE; - - if (!globalTabDragActive && - !dockHostInteractionState.activeTabDragNodeId.empty() && - !dockHostInteractionState.activeTabDragPanelId.empty() && - hasScreenPoint) { - transferRequests.beginGlobalTabDrag = EditorWindowPanelTransferRequest{ - dockHostInteractionState.activeTabDragNodeId, - dockHostInteractionState.activeTabDragPanelId, - screenPoint, - }; - } - - if (shellFrame.result.workspaceResult.dockHostResult.detachRequested && - hasScreenPoint) { - transferRequests.detachPanel = EditorWindowPanelTransferRequest{ - shellFrame.result.workspaceResult.dockHostResult.detachedNodeId, - shellFrame.result.workspaceResult.dockHostResult.detachedPanelId, - screenPoint, - }; - } - - return transferRequests; -} - -std::string EditorWindow::BuildCaptureStatusText() const { - if (m_state->render.autoScreenshot.HasPendingCapture()) { - return "Shot pending..."; - } - - if (!m_state->render.autoScreenshot.GetLastCaptureError().empty()) { - return TruncateText(m_state->render.autoScreenshot.GetLastCaptureError(), 38u); - } - - if (!m_state->render.autoScreenshot.GetLastCaptureSummary().empty()) { - return TruncateText(m_state->render.autoScreenshot.GetLastCaptureSummary(), 38u); - } - - return {}; -} - void EditorWindow::SyncShellCapturedPointerButtonsFromSystemState() { - m_state->input.modifierTracker.SyncFromSystemState(); + m_inputController->SyncInputModifiersFromSystemState(); - const std::uint8_t expectedButtons = - ResolveExpectedShellCaptureButtons(m_state->composition.shellRuntime); + const std::uint8_t expectedButtons = ResolveExpectedShellCaptureButtons( + m_runtime->GetShellRuntime()); if (expectedButtons == 0u || - HasPendingPointerStateReconciliationEvent(m_state->input.pendingEvents)) { + m_inputController->HasPendingPointerStateReconciliationEvent()) { return; } - const UIInputModifiers modifiers = - m_state->input.modifierTracker.GetCurrentModifiers(); + const UIInputModifiers modifiers = m_inputController->GetCurrentModifiers(); if ((ButtonMaskFromModifiers(modifiers) & expectedButtons) == expectedButtons) { return; } @@ -377,8 +206,7 @@ void EditorWindow::SyncShellCapturedPointerButtonsFromSystemState() { } void EditorWindow::ApplyShellRuntimePointerCapture() { - const EditorShellPointerOwner owner = - m_state->composition.shellRuntime.GetPointerOwner(); + const EditorShellPointerOwner owner = m_runtime->GetShellRuntime().GetPointerOwner(); if (IsShellPointerOwner(owner)) { AcquirePointerCapture(EditorWindowPointerCaptureOwner::Shell); return; @@ -398,24 +226,4 @@ void EditorWindow::ApplyShellRuntimePointerCapture() { } } -std::string EditorWindow::DescribeInputEvents( - const std::vector& events) const { - std::ostringstream stream = {}; - stream << "events=["; - for (std::size_t index = 0; index < events.size(); ++index) { - if (index > 0u) { - stream << " | "; - } - - const UIInputEvent& event = events[index]; - stream << DescribeInputEventType(event) - << '@' - << static_cast(event.position.x) - << ',' - << static_cast(event.position.y); - } - stream << ']'; - return stream.str(); -} - } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp b/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp new file mode 100644 index 00000000..b6996b28 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp @@ -0,0 +1,208 @@ +#include "Platform/Win32/EditorWindowFrameOrchestrator.h" + +#include "Composition/EditorShellVariant.h" +#include "Platform/Win32/EditorWindowConstants.h" +#include "Platform/Win32/EditorWindowRuntimeController.h" +#include "Platform/Win32/EditorWindowRuntimeInternal.h" +#include "Platform/Win32/EditorWindowStyle.h" +#include "Composition/EditorContext.h" + +#include + +#include + +namespace XCEngine::UI::Editor::App::Internal { + +using namespace EditorWindowInternal; +using ::XCEngine::UI::UIDrawList; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPoint; + +namespace { + +bool IsVerboseRuntimeTraceEnabled() { + static const bool s_enabled = ResolveVerboseRuntimeTraceEnabled(); + return s_enabled; +} + +std::string DescribeInputEventType(const UIInputEvent& event) { + switch (event.type) { + case UIInputEventType::PointerMove: return "PointerMove"; + case UIInputEventType::PointerEnter: return "PointerEnter"; + case UIInputEventType::PointerLeave: return "PointerLeave"; + case UIInputEventType::PointerButtonDown: return "PointerDown"; + case UIInputEventType::PointerButtonUp: return "PointerUp"; + case UIInputEventType::PointerWheel: return "PointerWheel"; + case UIInputEventType::KeyDown: return "KeyDown"; + case UIInputEventType::KeyUp: return "KeyUp"; + case UIInputEventType::Character: return "Character"; + case UIInputEventType::FocusGained: return "FocusGained"; + case UIInputEventType::FocusLost: return "FocusLost"; + default: return "Unknown"; + } +} + +} // namespace + +EditorWindowFrameTransferRequests EditorWindowFrameOrchestrator::UpdateAndAppend( + EditorContext& editorContext, + EditorWindowRuntimeController& runtimeController, + const ::XCEngine::UI::UIRect& workspaceBounds, + const std::vector& frameEvents, + std::string_view captureStatusText, + bool primary, + bool globalTabDragActive, + bool useDetachedTitleBarTabStrip, + UIDrawList& drawList) const { + LogInputTrace(editorContext, runtimeController, frameEvents); + + const Host::D3D12WindowRenderLoopFrameContext frameContext = runtimeController.BeginFrame(); + if (!frameContext.warning.empty()) { + LogRuntimeTrace("viewport", frameContext.warning); + } + + editorContext.AttachTextMeasurer(runtimeController.GetRenderer()); + UIEditorWorkspaceController& workspaceController = + runtimeController.GetMutableWorkspaceController(); + EditorShellRuntime& shellRuntime = runtimeController.GetShellRuntime(); + shellRuntime.Update( + editorContext, + workspaceController, + workspaceBounds, + frameEvents, + captureStatusText, + primary ? EditorShellVariant::Primary : EditorShellVariant::DetachedWindow, + useDetachedTitleBarTabStrip, + useDetachedTitleBarTabStrip ? kBorderlessTitleBarHeightDips : 0.0f); + const UIEditorShellInteractionFrame& shellFrame = shellRuntime.GetShellFrame(); + const UIEditorDockHostInteractionState& dockHostInteractionState = + shellRuntime.GetShellInteractionState().workspaceInteractionState.dockHostInteractionState; + + LogFrameInteractionTrace(workspaceController, frameEvents, shellFrame); + const EditorWindowFrameTransferRequests transferRequests = + BuildShellTransferRequests(globalTabDragActive, dockHostInteractionState, shellFrame); + + for (const WorkspaceTraceEntry& entry : shellRuntime.GetTraceEntries()) { + LogRuntimeTrace(entry.channel, entry.message); + } + + shellRuntime.Append(drawList); + if (frameContext.canRenderViewports) { + shellRuntime.RenderRequestedViewports(frameContext.renderContext); + } + + return transferRequests; +} + +void EditorWindowFrameOrchestrator::AppendInvalidFrame( + EditorContext& editorContext, + UIDrawList& drawList) const { + drawList.AddText( + UIPoint(28.0f, 28.0f), + "Editor shell asset invalid.", + kShellTextColor, + 16.0f); + drawList.AddText( + UIPoint(28.0f, 54.0f), + editorContext.GetValidationMessage().empty() + ? std::string("Unknown validation error.") + : editorContext.GetValidationMessage(), + kShellMutedTextColor, + 12.0f); +} + +std::string EditorWindowFrameOrchestrator::DescribeInputEvents( + const std::vector& events) const { + std::ostringstream stream = {}; + stream << "events=["; + for (std::size_t index = 0; index < events.size(); ++index) { + if (index > 0u) { + stream << " | "; + } + + const UIInputEvent& event = events[index]; + stream << DescribeInputEventType(event) + << '@' + << static_cast(event.position.x) + << ',' + << static_cast(event.position.y); + } + stream << ']'; + return stream.str(); +} + +void EditorWindowFrameOrchestrator::LogInputTrace( + EditorContext& editorContext, + const EditorWindowRuntimeController& runtimeController, + const std::vector& frameEvents) const { + if (frameEvents.empty() || !IsVerboseRuntimeTraceEnabled()) { + return; + } + + LogRuntimeTrace( + "input", + DescribeInputEvents(frameEvents) + " | " + + editorContext.DescribeWorkspaceState( + runtimeController.GetWorkspaceController(), + runtimeController.GetShellInteractionState())); +} + +void EditorWindowFrameOrchestrator::LogFrameInteractionTrace( + const UIEditorWorkspaceController& workspaceController, + const std::vector& frameEvents, + const UIEditorShellInteractionFrame& shellFrame) const { + if (!IsVerboseRuntimeTraceEnabled() || + (frameEvents.empty() && + !shellFrame.result.workspaceResult.dockHostResult.layoutChanged && + !shellFrame.result.workspaceResult.dockHostResult.commandExecuted)) { + return; + } + + std::ostringstream frameTrace = {}; + frameTrace << "result consumed=" + << (shellFrame.result.consumed ? "true" : "false") + << " layoutChanged=" + << (shellFrame.result.workspaceResult.dockHostResult.layoutChanged ? "true" + : "false") + << " commandExecuted=" + << (shellFrame.result.workspaceResult.dockHostResult.commandExecuted ? "true" + : "false") + << " active=" + << workspaceController.GetWorkspace().activePanelId + << " message=" + << shellFrame.result.workspaceResult.dockHostResult.layoutResult.message; + LogRuntimeTrace("frame", frameTrace.str()); +} + +EditorWindowFrameTransferRequests EditorWindowFrameOrchestrator::BuildShellTransferRequests( + bool globalTabDragActive, + const UIEditorDockHostInteractionState& dockHostInteractionState, + const UIEditorShellInteractionFrame& shellFrame) const { + EditorWindowFrameTransferRequests transferRequests = {}; + POINT screenPoint = {}; + const bool hasScreenPoint = GetCursorPos(&screenPoint) != FALSE; + + if (!globalTabDragActive && + !dockHostInteractionState.activeTabDragNodeId.empty() && + !dockHostInteractionState.activeTabDragPanelId.empty() && + hasScreenPoint) { + transferRequests.beginGlobalTabDrag = EditorWindowPanelTransferRequest{ + dockHostInteractionState.activeTabDragNodeId, + dockHostInteractionState.activeTabDragPanelId, + screenPoint, + }; + } + + if (shellFrame.result.workspaceResult.dockHostResult.detachRequested && hasScreenPoint) { + transferRequests.detachPanel = EditorWindowPanelTransferRequest{ + shellFrame.result.workspaceResult.dockHostResult.detachedNodeId, + shellFrame.result.workspaceResult.dockHostResult.detachedPanelId, + screenPoint, + }; + } + + return transferRequests; +} + +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.h b/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.h new file mode 100644 index 00000000..a6740f41 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.h @@ -0,0 +1,79 @@ +#pragma once + +#include "Platform/Win32/EditorWindowTransferRequests.h" + +#include + +#include +#include +#include + +namespace XCEngine::UI { + +class UIDrawList; + +struct UIRect; +struct UIInputEvent; + +} // namespace XCEngine::UI + +namespace XCEngine::UI::Editor { + +class UIEditorWorkspaceController; + +struct UIEditorDockHostInteractionState; +struct UIEditorShellInteractionFrame; + +} // namespace XCEngine::UI::Editor + +namespace XCEngine::UI::Editor::App { + +class EditorContext; + +namespace Internal { + +class EditorWindowRuntimeController; + +class EditorWindowFrameOrchestrator final { +public: + EditorWindowFrameOrchestrator() = default; + ~EditorWindowFrameOrchestrator() = default; + + EditorWindowFrameOrchestrator(const EditorWindowFrameOrchestrator&) = delete; + EditorWindowFrameOrchestrator& operator=(const EditorWindowFrameOrchestrator&) = delete; + EditorWindowFrameOrchestrator(EditorWindowFrameOrchestrator&&) = delete; + EditorWindowFrameOrchestrator& operator=(EditorWindowFrameOrchestrator&&) = delete; + + EditorWindowFrameTransferRequests UpdateAndAppend( + EditorContext& editorContext, + EditorWindowRuntimeController& runtimeController, + const ::XCEngine::UI::UIRect& workspaceBounds, + const std::vector<::XCEngine::UI::UIInputEvent>& frameEvents, + std::string_view captureStatusText, + bool primary, + bool globalTabDragActive, + bool useDetachedTitleBarTabStrip, + ::XCEngine::UI::UIDrawList& drawList) const; + void AppendInvalidFrame( + EditorContext& editorContext, + ::XCEngine::UI::UIDrawList& drawList) const; + std::string DescribeInputEvents( + const std::vector<::XCEngine::UI::UIInputEvent>& events) const; + +private: + void LogInputTrace( + EditorContext& editorContext, + const EditorWindowRuntimeController& runtimeController, + const std::vector<::XCEngine::UI::UIInputEvent>& frameEvents) const; + void LogFrameInteractionTrace( + const UIEditorWorkspaceController& workspaceController, + const std::vector<::XCEngine::UI::UIInputEvent>& frameEvents, + const UIEditorShellInteractionFrame& shellFrame) const; + EditorWindowFrameTransferRequests BuildShellTransferRequests( + bool globalTabDragActive, + const UIEditorDockHostInteractionState& dockHostInteractionState, + const UIEditorShellInteractionFrame& shellFrame) const; +}; + +} // namespace Internal +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowInput.cpp b/new_editor/app/Platform/Win32/EditorWindowInput.cpp index 3072f6d3..2bf7dbd7 100644 --- a/new_editor/app/Platform/Win32/EditorWindowInput.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowInput.cpp @@ -1,7 +1,9 @@ #include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowChromeController.h" +#include "Platform/Win32/EditorWindowInputController.h" #include "Platform/Win32/EditorWindowInternalState.h" +#include "Platform/Win32/EditorWindowRuntimeController.h" -#include #include #include @@ -33,84 +35,6 @@ bool IsScreenPointOverWindow(HWND hwnd, const POINT& screenPoint) { return screenPoint.x >= windowRect.left && screenPoint.x < windowRect.right && screenPoint.y >= windowRect.top && screenPoint.y < windowRect.bottom; } - -std::int32_t MapVirtualKeyToUIKeyCode(WPARAM wParam) { - using ::XCEngine::Input::KeyCode; - - switch (wParam) { - case 'A': return static_cast(KeyCode::A); - case 'B': return static_cast(KeyCode::B); - case 'C': return static_cast(KeyCode::C); - case 'D': return static_cast(KeyCode::D); - case 'E': return static_cast(KeyCode::E); - case 'F': return static_cast(KeyCode::F); - case 'G': return static_cast(KeyCode::G); - case 'H': return static_cast(KeyCode::H); - case 'I': return static_cast(KeyCode::I); - case 'J': return static_cast(KeyCode::J); - case 'K': return static_cast(KeyCode::K); - case 'L': return static_cast(KeyCode::L); - case 'M': return static_cast(KeyCode::M); - case 'N': return static_cast(KeyCode::N); - case 'O': return static_cast(KeyCode::O); - case 'P': return static_cast(KeyCode::P); - case 'Q': return static_cast(KeyCode::Q); - case 'R': return static_cast(KeyCode::R); - case 'S': return static_cast(KeyCode::S); - case 'T': return static_cast(KeyCode::T); - case 'U': return static_cast(KeyCode::U); - case 'V': return static_cast(KeyCode::V); - case 'W': return static_cast(KeyCode::W); - case 'X': return static_cast(KeyCode::X); - case 'Y': return static_cast(KeyCode::Y); - case 'Z': return static_cast(KeyCode::Z); - case '0': return static_cast(KeyCode::Zero); - case '1': return static_cast(KeyCode::One); - case '2': return static_cast(KeyCode::Two); - case '3': return static_cast(KeyCode::Three); - case '4': return static_cast(KeyCode::Four); - case '5': return static_cast(KeyCode::Five); - case '6': return static_cast(KeyCode::Six); - case '7': return static_cast(KeyCode::Seven); - case '8': return static_cast(KeyCode::Eight); - case '9': return static_cast(KeyCode::Nine); - case VK_SPACE: return static_cast(KeyCode::Space); - case VK_TAB: return static_cast(KeyCode::Tab); - case VK_RETURN: return static_cast(KeyCode::Enter); - case VK_ESCAPE: return static_cast(KeyCode::Escape); - case VK_SHIFT: return static_cast(KeyCode::LeftShift); - case VK_CONTROL: return static_cast(KeyCode::LeftCtrl); - case VK_MENU: return static_cast(KeyCode::LeftAlt); - case VK_UP: return static_cast(KeyCode::Up); - case VK_DOWN: return static_cast(KeyCode::Down); - case VK_LEFT: return static_cast(KeyCode::Left); - case VK_RIGHT: return static_cast(KeyCode::Right); - case VK_HOME: return static_cast(KeyCode::Home); - case VK_END: return static_cast(KeyCode::End); - case VK_PRIOR: return static_cast(KeyCode::PageUp); - case VK_NEXT: return static_cast(KeyCode::PageDown); - case VK_DELETE: return static_cast(KeyCode::Delete); - case VK_BACK: return static_cast(KeyCode::Backspace); - case VK_F1: return static_cast(KeyCode::F1); - case VK_F2: return static_cast(KeyCode::F2); - case VK_F3: return static_cast(KeyCode::F3); - case VK_F4: return static_cast(KeyCode::F4); - case VK_F5: return static_cast(KeyCode::F5); - case VK_F6: return static_cast(KeyCode::F6); - case VK_F7: return static_cast(KeyCode::F7); - case VK_F8: return static_cast(KeyCode::F8); - case VK_F9: return static_cast(KeyCode::F9); - case VK_F10: return static_cast(KeyCode::F10); - case VK_F11: return static_cast(KeyCode::F11); - case VK_F12: return static_cast(KeyCode::F12); - default: return static_cast(KeyCode::None); - } -} - -bool IsRepeatKeyMessage(LPARAM lParam) { - return (static_cast(lParam) & (1ul << 30)) != 0ul; -} - } // namespace bool EditorWindow::ApplyCurrentCursor() const { @@ -129,53 +53,34 @@ bool EditorWindow::ApplyCurrentCursor() const { bool EditorWindow::HasInteractiveCaptureState() const { - return m_state->composition.shellRuntime.HasInteractiveCapture() || - m_state->chrome.runtime.IsBorderlessWindowDragRestoreArmed() || - m_state->chrome.runtime.IsBorderlessResizeActive() || - m_state->input.pointerCaptureOwner != EditorWindowPointerCaptureOwner::None; + return m_runtime->GetShellRuntime().HasInteractiveCapture() || + m_chromeController->IsBorderlessWindowDragRestoreArmed() || + m_chromeController->IsBorderlessResizeActive() || + m_inputController->HasPointerCaptureOwner(); } EditorWindowPointerCaptureOwner EditorWindow::GetPointerCaptureOwner() const { - return m_state->input.pointerCaptureOwner; + return m_inputController->GetPointerCaptureOwner(); } bool EditorWindow::OwnsPointerCapture(EditorWindowPointerCaptureOwner owner) const { - return m_state->input.pointerCaptureOwner == owner; + return m_inputController->OwnsPointerCapture(owner); } void EditorWindow::AcquirePointerCapture(EditorWindowPointerCaptureOwner owner) { - if (owner == EditorWindowPointerCaptureOwner::None || - m_state->window.hwnd == nullptr || - !IsWindow(m_state->window.hwnd)) { - return; - } - - m_state->input.pointerCaptureOwner = owner; - if (GetCapture() != m_state->window.hwnd) { - SetCapture(m_state->window.hwnd); - } + m_inputController->AcquirePointerCapture(m_state->window.hwnd, owner); } void EditorWindow::ReleasePointerCapture(EditorWindowPointerCaptureOwner owner) { - if (m_state->input.pointerCaptureOwner != owner) { - return; - } - - m_state->input.pointerCaptureOwner = EditorWindowPointerCaptureOwner::None; - if (m_state->window.hwnd != nullptr && GetCapture() == m_state->window.hwnd) { - ReleaseCapture(); - } + m_inputController->ReleasePointerCapture(m_state->window.hwnd, owner); } void EditorWindow::ForceReleasePointerCapture() { - m_state->input.pointerCaptureOwner = EditorWindowPointerCaptureOwner::None; - if (m_state->window.hwnd != nullptr && GetCapture() == m_state->window.hwnd) { - ReleaseCapture(); - } + m_inputController->ForceReleasePointerCapture(m_state->window.hwnd); } void EditorWindow::ClearPointerCaptureOwner() { - m_state->input.pointerCaptureOwner = EditorWindowPointerCaptureOwner::None; + m_inputController->ClearPointerCaptureOwner(); } void EditorWindow::TryStartImmediateShellPointerCapture(LPARAM lParam) { @@ -189,7 +94,7 @@ void EditorWindow::TryStartImmediateShellPointerCapture(LPARAM lParam) { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); if (!ShouldStartImmediateUIEditorShellPointerCapture( - m_state->composition.shellRuntime.GetShellFrame(), + m_runtime->GetShellFrame(), clientPoint)) { return; } @@ -203,16 +108,11 @@ void EditorWindow::QueuePointerEvent( WPARAM wParam, LPARAM lParam) { UIInputEvent event = {}; - event.type = type; - event.pointerButton = button; - event.position = ConvertClientPixelsToDips( - GET_X_LPARAM(lParam), - GET_Y_LPARAM(lParam)); - event.modifiers = m_state->input.modifierTracker.ApplyPointerMessage( + m_inputController->QueuePointerEvent( type, button, - static_cast(wParam)); - m_state->input.pendingEvents.push_back(event); + ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)), + wParam); } void EditorWindow::QueueSyntheticPointerStateSyncEvent( @@ -229,24 +129,20 @@ void EditorWindow::QueueSyntheticPointerStateSyncEvent( return; } - UIInputEvent event = {}; - event.type = UIInputEventType::PointerMove; - event.position = ConvertClientPixelsToDips(screenPoint.x, screenPoint.y); - event.modifiers = modifiers; - m_state->input.pendingEvents.push_back(event); + m_inputController->QueueSyntheticPointerStateSyncEvent( + ConvertClientPixelsToDips(screenPoint.x, screenPoint.y), + modifiers); } void EditorWindow::QueuePointerLeaveEvent() { - UIInputEvent event = {}; - event.type = UIInputEventType::PointerLeave; + ::XCEngine::UI::UIPoint position = {}; if (m_state->window.hwnd != nullptr) { POINT clientPoint = {}; GetCursorPos(&clientPoint); ScreenToClient(m_state->window.hwnd, &clientPoint); - event.position = ConvertClientPixelsToDips(clientPoint.x, clientPoint.y); + position = ConvertClientPixelsToDips(clientPoint.x, clientPoint.y); } - event.modifiers = m_state->input.modifierTracker.GetCurrentModifiers(); - m_state->input.pendingEvents.push_back(event); + m_inputController->QueuePointerLeaveEvent(position); } void EditorWindow::QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam) { @@ -260,50 +156,34 @@ void EditorWindow::QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARA }; ScreenToClient(m_state->window.hwnd, &screenPoint); - UIInputEvent event = {}; - event.type = UIInputEventType::PointerWheel; - event.position = ConvertClientPixelsToDips(screenPoint.x, screenPoint.y); - event.wheelDelta = static_cast(wheelDelta); - event.modifiers = m_state->input.modifierTracker.ApplyPointerMessage( - UIInputEventType::PointerWheel, - UIPointerButton::None, - static_cast(wParam)); - m_state->input.pendingEvents.push_back(event); + m_inputController->QueuePointerWheelEvent( + ConvertClientPixelsToDips(screenPoint.x, screenPoint.y), + wheelDelta, + wParam); } void EditorWindow::QueueKeyEvent(UIInputEventType type, WPARAM wParam, LPARAM lParam) { - UIInputEvent event = {}; - event.type = type; - event.keyCode = MapVirtualKeyToUIKeyCode(wParam); - event.modifiers = m_state->input.modifierTracker.ApplyKeyMessage(type, wParam, lParam); - event.repeat = IsRepeatKeyMessage(lParam); - m_state->input.pendingEvents.push_back(event); + m_inputController->QueueKeyEvent(type, wParam, lParam); } void EditorWindow::QueueCharacterEvent(WPARAM wParam, LPARAM) { - UIInputEvent event = {}; - event.type = UIInputEventType::Character; - event.character = static_cast(wParam); - event.modifiers = m_state->input.modifierTracker.GetCurrentModifiers(); - m_state->input.pendingEvents.push_back(event); + m_inputController->QueueCharacterEvent(wParam); } void EditorWindow::QueueWindowFocusEvent(UIInputEventType type) { - UIInputEvent event = {}; - event.type = type; - m_state->input.pendingEvents.push_back(event); + m_inputController->QueueWindowFocusEvent(type); } void EditorWindow::SyncInputModifiersFromSystemState() { - m_state->input.modifierTracker.SyncFromSystemState(); + m_inputController->SyncInputModifiersFromSystemState(); } void EditorWindow::ResetInputModifiers() { - m_state->input.modifierTracker.Reset(); + m_inputController->ResetInputModifiers(); } void EditorWindow::RequestManualScreenshot() { - m_state->render.autoScreenshot.RequestCapture("manual_f12"); + m_runtime->RequestManualScreenshot("manual_f12"); } bool EditorWindow::IsPointerInsideClientArea() const { @@ -328,14 +208,14 @@ bool EditorWindow::IsPointerInsideClientArea() const { LPCWSTR EditorWindow::ResolveCurrentCursorResource() const { const Host::BorderlessWindowResizeEdge borderlessResizeEdge = - m_state->chrome.runtime.IsBorderlessResizeActive() - ? m_state->chrome.runtime.GetBorderlessResizeEdge() - : m_state->chrome.runtime.GetHoveredBorderlessResizeEdge(); + m_chromeController->IsBorderlessResizeActive() + ? m_chromeController->GetBorderlessResizeEdge() + : m_chromeController->GetHoveredBorderlessResizeEdge(); if (borderlessResizeEdge != Host::BorderlessWindowResizeEdge::None) { return Host::ResolveBorderlessWindowResizeCursor(borderlessResizeEdge); } - switch (m_state->composition.shellRuntime.GetHostedContentCursorKind()) { + switch (m_runtime->GetShellRuntime().GetHostedContentCursorKind()) { case ProjectPanel::CursorKind::ResizeEW: return IDC_SIZEWE; case ProjectPanel::CursorKind::Arrow: @@ -343,7 +223,7 @@ LPCWSTR EditorWindow::ResolveCurrentCursorResource() const { break; } - switch (m_state->composition.shellRuntime.GetDockCursorKind()) { + switch (m_runtime->GetShellRuntime().GetDockCursorKind()) { case Widgets::UIEditorDockHostCursorKind::ResizeEW: return IDC_SIZEWE; case Widgets::UIEditorDockHostCursorKind::ResizeNS: diff --git a/new_editor/app/Platform/Win32/EditorWindowInputController.cpp b/new_editor/app/Platform/Win32/EditorWindowInputController.cpp new file mode 100644 index 00000000..7fd024b5 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowInputController.cpp @@ -0,0 +1,279 @@ +#include "Platform/Win32/EditorWindowInputController.h" + +#include + +namespace XCEngine::UI::Editor::App::Internal { + +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIInputModifiers; +using ::XCEngine::UI::UIPointerButton; + +bool EditorWindowInputController::IsTrackingMouseLeave() const { + return m_trackingMouseLeave; +} + +void EditorWindowInputController::SetTrackingMouseLeave(bool trackingMouseLeave) { + m_trackingMouseLeave = trackingMouseLeave; +} + +EditorWindowPointerCaptureOwner EditorWindowInputController::GetPointerCaptureOwner() const { + return m_pointerCaptureOwner; +} + +bool EditorWindowInputController::OwnsPointerCapture(EditorWindowPointerCaptureOwner owner) const { + return m_pointerCaptureOwner == owner; +} + +bool EditorWindowInputController::HasPointerCaptureOwner() const { + return m_pointerCaptureOwner != EditorWindowPointerCaptureOwner::None; +} + +void EditorWindowInputController::AcquirePointerCapture( + HWND hwnd, + EditorWindowPointerCaptureOwner owner) { + if (owner == EditorWindowPointerCaptureOwner::None || + hwnd == nullptr || + !IsWindow(hwnd)) { + return; + } + + m_pointerCaptureOwner = owner; + if (GetCapture() != hwnd) { + SetCapture(hwnd); + } +} + +void EditorWindowInputController::ReleasePointerCapture( + HWND hwnd, + EditorWindowPointerCaptureOwner owner) { + if (m_pointerCaptureOwner != owner) { + return; + } + + m_pointerCaptureOwner = EditorWindowPointerCaptureOwner::None; + if (hwnd != nullptr && GetCapture() == hwnd) { + ReleaseCapture(); + } +} + +void EditorWindowInputController::ForceReleasePointerCapture(HWND hwnd) { + m_pointerCaptureOwner = EditorWindowPointerCaptureOwner::None; + if (hwnd != nullptr && GetCapture() == hwnd) { + ReleaseCapture(); + } +} + +void EditorWindowInputController::ClearPointerCaptureOwner() { + m_pointerCaptureOwner = EditorWindowPointerCaptureOwner::None; +} + +void EditorWindowInputController::QueuePointerEvent( + UIInputEventType type, + UIPointerButton button, + const ::XCEngine::UI::UIPoint& position, + WPARAM wParam) { + UIInputEvent event = {}; + event.type = type; + event.pointerButton = button; + event.position = position; + event.modifiers = m_modifierTracker.ApplyPointerMessage( + type, + button, + static_cast(wParam)); + m_pendingEvents.push_back(event); +} + +void EditorWindowInputController::QueueSyntheticPointerStateSyncEvent( + const ::XCEngine::UI::UIPoint& position, + const UIInputModifiers& modifiers) { + UIInputEvent event = {}; + event.type = UIInputEventType::PointerMove; + event.position = position; + event.modifiers = modifiers; + m_pendingEvents.push_back(event); +} + +void EditorWindowInputController::QueuePointerLeaveEvent( + const ::XCEngine::UI::UIPoint& position) { + UIInputEvent event = {}; + event.type = UIInputEventType::PointerLeave; + event.position = position; + event.modifiers = m_modifierTracker.GetCurrentModifiers(); + m_pendingEvents.push_back(event); +} + +void EditorWindowInputController::QueuePointerWheelEvent( + const ::XCEngine::UI::UIPoint& position, + short wheelDelta, + WPARAM wParam) { + UIInputEvent event = {}; + event.type = UIInputEventType::PointerWheel; + event.position = position; + event.wheelDelta = static_cast(wheelDelta); + event.modifiers = m_modifierTracker.ApplyPointerMessage( + UIInputEventType::PointerWheel, + UIPointerButton::None, + static_cast(wParam)); + m_pendingEvents.push_back(event); +} + +void EditorWindowInputController::QueueKeyEvent(UIInputEventType type, WPARAM wParam, LPARAM lParam) { + UIInputEvent event = {}; + event.type = type; + event.keyCode = MapVirtualKeyToUIKeyCode(wParam); + event.modifiers = m_modifierTracker.ApplyKeyMessage(type, wParam, lParam); + event.repeat = IsRepeatKeyMessage(lParam); + m_pendingEvents.push_back(event); +} + +void EditorWindowInputController::QueueCharacterEvent(WPARAM wParam) { + UIInputEvent event = {}; + event.type = UIInputEventType::Character; + event.character = static_cast(wParam); + event.modifiers = m_modifierTracker.GetCurrentModifiers(); + m_pendingEvents.push_back(event); +} + +void EditorWindowInputController::QueueWindowFocusEvent(UIInputEventType type) { + UIInputEvent event = {}; + event.type = type; + m_pendingEvents.push_back(event); +} + +void EditorWindowInputController::SyncInputModifiersFromSystemState() { + m_modifierTracker.SyncFromSystemState(); +} + +void EditorWindowInputController::ResetInputModifiers() { + m_modifierTracker.Reset(); +} + +UIInputModifiers EditorWindowInputController::GetCurrentModifiers() const { + return m_modifierTracker.GetCurrentModifiers(); +} + +bool EditorWindowInputController::HasPendingPointerStateReconciliationEvent() const { + for (const UIInputEvent& event : m_pendingEvents) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + case UIInputEventType::PointerWheel: + case UIInputEventType::FocusLost: + return true; + case UIInputEventType::PointerLeave: + case UIInputEventType::KeyDown: + case UIInputEventType::KeyUp: + case UIInputEventType::Character: + case UIInputEventType::FocusGained: + case UIInputEventType::None: + default: + break; + } + } + + return false; +} + +std::vector EditorWindowInputController::TakePendingEvents() { + std::vector events = std::move(m_pendingEvents); + m_pendingEvents.clear(); + return events; +} + +void EditorWindowInputController::ClearPendingEvents() { + m_pendingEvents.clear(); +} + +void EditorWindowInputController::ResetInteractionState() { + ForceReleasePointerCapture(nullptr); + ClearPendingEvents(); + m_trackingMouseLeave = false; + m_modifierTracker.Reset(); +} + +void EditorWindowInputController::ResetWindowState() { + m_trackingMouseLeave = false; + m_pointerCaptureOwner = EditorWindowPointerCaptureOwner::None; +} + +std::int32_t EditorWindowInputController::MapVirtualKeyToUIKeyCode(WPARAM wParam) { + using ::XCEngine::Input::KeyCode; + + switch (wParam) { + case 'A': return static_cast(KeyCode::A); + case 'B': return static_cast(KeyCode::B); + case 'C': return static_cast(KeyCode::C); + case 'D': return static_cast(KeyCode::D); + case 'E': return static_cast(KeyCode::E); + case 'F': return static_cast(KeyCode::F); + case 'G': return static_cast(KeyCode::G); + case 'H': return static_cast(KeyCode::H); + case 'I': return static_cast(KeyCode::I); + case 'J': return static_cast(KeyCode::J); + case 'K': return static_cast(KeyCode::K); + case 'L': return static_cast(KeyCode::L); + case 'M': return static_cast(KeyCode::M); + case 'N': return static_cast(KeyCode::N); + case 'O': return static_cast(KeyCode::O); + case 'P': return static_cast(KeyCode::P); + case 'Q': return static_cast(KeyCode::Q); + case 'R': return static_cast(KeyCode::R); + case 'S': return static_cast(KeyCode::S); + case 'T': return static_cast(KeyCode::T); + case 'U': return static_cast(KeyCode::U); + case 'V': return static_cast(KeyCode::V); + case 'W': return static_cast(KeyCode::W); + case 'X': return static_cast(KeyCode::X); + case 'Y': return static_cast(KeyCode::Y); + case 'Z': return static_cast(KeyCode::Z); + case '0': return static_cast(KeyCode::Zero); + case '1': return static_cast(KeyCode::One); + case '2': return static_cast(KeyCode::Two); + case '3': return static_cast(KeyCode::Three); + case '4': return static_cast(KeyCode::Four); + case '5': return static_cast(KeyCode::Five); + case '6': return static_cast(KeyCode::Six); + case '7': return static_cast(KeyCode::Seven); + case '8': return static_cast(KeyCode::Eight); + case '9': return static_cast(KeyCode::Nine); + case VK_SPACE: return static_cast(KeyCode::Space); + case VK_TAB: return static_cast(KeyCode::Tab); + case VK_RETURN: return static_cast(KeyCode::Enter); + case VK_ESCAPE: return static_cast(KeyCode::Escape); + case VK_SHIFT: return static_cast(KeyCode::LeftShift); + case VK_CONTROL: return static_cast(KeyCode::LeftCtrl); + case VK_MENU: return static_cast(KeyCode::LeftAlt); + case VK_UP: return static_cast(KeyCode::Up); + case VK_DOWN: return static_cast(KeyCode::Down); + case VK_LEFT: return static_cast(KeyCode::Left); + case VK_RIGHT: return static_cast(KeyCode::Right); + case VK_HOME: return static_cast(KeyCode::Home); + case VK_END: return static_cast(KeyCode::End); + case VK_PRIOR: return static_cast(KeyCode::PageUp); + case VK_NEXT: return static_cast(KeyCode::PageDown); + case VK_DELETE: return static_cast(KeyCode::Delete); + case VK_BACK: return static_cast(KeyCode::Backspace); + case VK_F1: return static_cast(KeyCode::F1); + case VK_F2: return static_cast(KeyCode::F2); + case VK_F3: return static_cast(KeyCode::F3); + case VK_F4: return static_cast(KeyCode::F4); + case VK_F5: return static_cast(KeyCode::F5); + case VK_F6: return static_cast(KeyCode::F6); + case VK_F7: return static_cast(KeyCode::F7); + case VK_F8: return static_cast(KeyCode::F8); + case VK_F9: return static_cast(KeyCode::F9); + case VK_F10: return static_cast(KeyCode::F10); + case VK_F11: return static_cast(KeyCode::F11); + case VK_F12: return static_cast(KeyCode::F12); + default: return static_cast(KeyCode::None); + } +} + +bool EditorWindowInputController::IsRepeatKeyMessage(LPARAM lParam) { + return (static_cast(lParam) & (1ul << 30)) != 0ul; +} + +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/EditorWindowInputController.h b/new_editor/app/Platform/Win32/EditorWindowInputController.h new file mode 100644 index 00000000..c6a82485 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowInputController.h @@ -0,0 +1,78 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "Platform/Win32/EditorWindowPointerCapture.h" + +#include + +#include + +#include + +#include + +namespace XCEngine::UI::Editor::App::Internal { + +class EditorWindowInputController final { +public: + EditorWindowInputController() = default; + ~EditorWindowInputController() = default; + + EditorWindowInputController(const EditorWindowInputController&) = delete; + EditorWindowInputController& operator=(const EditorWindowInputController&) = delete; + EditorWindowInputController(EditorWindowInputController&&) = delete; + EditorWindowInputController& operator=(EditorWindowInputController&&) = delete; + + bool IsTrackingMouseLeave() const; + void SetTrackingMouseLeave(bool trackingMouseLeave); + + EditorWindowPointerCaptureOwner GetPointerCaptureOwner() const; + bool OwnsPointerCapture(EditorWindowPointerCaptureOwner owner) const; + bool HasPointerCaptureOwner() const; + void AcquirePointerCapture(HWND hwnd, EditorWindowPointerCaptureOwner owner); + void ReleasePointerCapture(HWND hwnd, EditorWindowPointerCaptureOwner owner); + void ForceReleasePointerCapture(HWND hwnd); + void ClearPointerCaptureOwner(); + + void QueuePointerEvent( + ::XCEngine::UI::UIInputEventType type, + ::XCEngine::UI::UIPointerButton button, + const ::XCEngine::UI::UIPoint& position, + WPARAM wParam); + void QueueSyntheticPointerStateSyncEvent( + const ::XCEngine::UI::UIPoint& position, + const ::XCEngine::UI::UIInputModifiers& modifiers); + void QueuePointerLeaveEvent(const ::XCEngine::UI::UIPoint& position); + void QueuePointerWheelEvent( + const ::XCEngine::UI::UIPoint& position, + short wheelDelta, + WPARAM wParam); + void QueueKeyEvent(::XCEngine::UI::UIInputEventType type, WPARAM wParam, LPARAM lParam); + void QueueCharacterEvent(WPARAM wParam); + void QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType type); + + void SyncInputModifiersFromSystemState(); + void ResetInputModifiers(); + ::XCEngine::UI::UIInputModifiers GetCurrentModifiers() const; + + bool HasPendingPointerStateReconciliationEvent() const; + std::vector<::XCEngine::UI::UIInputEvent> TakePendingEvents(); + void ClearPendingEvents(); + void ResetInteractionState(); + void ResetWindowState(); + +private: + static std::int32_t MapVirtualKeyToUIKeyCode(WPARAM wParam); + static bool IsRepeatKeyMessage(LPARAM lParam); + + Host::InputModifierTracker m_modifierTracker = {}; + std::vector<::XCEngine::UI::UIInputEvent> m_pendingEvents = {}; + bool m_trackingMouseLeave = false; + EditorWindowPointerCaptureOwner m_pointerCaptureOwner = + EditorWindowPointerCaptureOwner::None; +}; + +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/EditorWindowInternalState.h b/new_editor/app/Platform/Win32/EditorWindowInternalState.h index a85cbacf..25260936 100644 --- a/new_editor/app/Platform/Win32/EditorWindowInternalState.h +++ b/new_editor/app/Platform/Win32/EditorWindowInternalState.h @@ -4,25 +4,9 @@ #define NOMINMAX #endif -#include "Composition/EditorShellRuntime.h" -#include "Platform/Win32/EditorWindowPointerCapture.h" - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - #include #include -#include namespace XCEngine::UI::Editor::App { @@ -35,39 +19,8 @@ struct EditorWindowWindowState { bool closing = false; }; -struct EditorWindowRenderState { - Host::NativeRenderer renderer = {}; - Host::D3D12WindowRenderer windowRenderer = {}; - Host::D3D12WindowRenderLoop windowRenderLoop = {}; - Host::AutoScreenshotController autoScreenshot = {}; - ::XCEngine::UI::UITextureHandle titleBarLogoIcon = {}; - bool ready = false; -}; - -struct EditorWindowInputState { - Host::InputModifierTracker modifierTracker = {}; - std::vector<::XCEngine::UI::UIInputEvent> pendingEvents = {}; - bool trackingMouseLeave = false; - EditorWindowPointerCaptureOwner pointerCaptureOwner = - EditorWindowPointerCaptureOwner::None; -}; - -struct EditorWindowCompositionState { - UIEditorWorkspaceController workspaceController = {}; - EditorShellRuntime shellRuntime = {}; -}; - -struct EditorWindowChromeRuntimeState { - Host::BorderlessWindowChromeState chromeState = {}; - Host::HostRuntimeState runtime = {}; -}; - struct EditorWindowState { EditorWindowWindowState window = {}; - EditorWindowRenderState render = {}; - EditorWindowInputState input = {}; - EditorWindowCompositionState composition = {}; - EditorWindowChromeRuntimeState chrome = {}; }; } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp b/new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp index a43da9d4..167c12bf 100644 --- a/new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp @@ -1,11 +1,15 @@ #include "Platform/Win32/EditorWindow.h" #include "Bootstrap/EditorResources.h" +#include "Platform/Win32/EditorWindowChromeController.h" #include "Platform/Win32/EditorWindowConstants.h" +#include "Platform/Win32/EditorWindowFrameOrchestrator.h" +#include "Platform/Win32/EditorWindowInputController.h" #include "Platform/Win32/EditorWindowInternalState.h" #include "Platform/Win32/EditorWindowPlatformInternal.h" +#include "Platform/Win32/EditorWindowRuntimeController.h" #include "Platform/Win32/EditorWindowRuntimeInternal.h" -#include "State/EditorContext.h" +#include "Composition/EditorContext.h" #include #include @@ -74,11 +78,15 @@ EditorWindow::EditorWindow( std::wstring title, bool primary, UIEditorWorkspaceController workspaceController) - : m_state(std::make_unique()) { + : m_state(std::make_unique()) + , m_chromeController(std::make_unique()) + , m_frameOrchestrator(std::make_unique()) + , m_inputController(std::make_unique()) + , m_runtime(std::make_unique( + std::move(workspaceController))) { m_state->window.windowId = std::move(windowId); m_state->window.title = std::move(title); m_state->window.primary = primary; - m_state->composition.workspaceController = std::move(workspaceController); UpdateCachedTitleText(); } @@ -105,15 +113,15 @@ bool EditorWindow::IsClosing() const { } bool EditorWindow::IsRenderReady() const { - return m_state->render.ready; + return m_runtime->IsReady(); } bool EditorWindow::IsTrackingMouseLeave() const { - return m_state->input.trackingMouseLeave; + return m_inputController->IsTrackingMouseLeave(); } bool EditorWindow::HasHoveredBorderlessResizeEdge() const { - return m_state->chrome.runtime.GetHoveredBorderlessResizeEdge() != + return m_chromeController->GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None; } @@ -122,36 +130,36 @@ const std::wstring& EditorWindow::GetTitle() const { } const UIEditorWorkspaceController& EditorWindow::GetWorkspaceController() const { - return m_state->composition.workspaceController; + return m_runtime->GetWorkspaceController(); } UIEditorWorkspaceController& EditorWindow::GetMutableWorkspaceController() { - return m_state->composition.workspaceController; + return m_runtime->GetMutableWorkspaceController(); } const EditorShellRuntime& EditorWindow::GetShellRuntime() const { - return m_state->composition.shellRuntime; + return m_runtime->GetShellRuntime(); } EditorShellRuntime& EditorWindow::GetShellRuntime() { - return m_state->composition.shellRuntime; + return m_runtime->GetShellRuntime(); } const UIEditorShellInteractionFrame& EditorWindow::GetShellFrame() const { - return m_state->composition.shellRuntime.GetShellFrame(); + return m_runtime->GetShellFrame(); } const UIEditorShellInteractionState& EditorWindow::GetShellInteractionState() const { - return m_state->composition.shellRuntime.GetShellInteractionState(); + return m_runtime->GetShellInteractionState(); } void EditorWindow::SetExternalDockHostDropPreview( const Widgets::UIEditorDockHostDropPreviewState& preview) { - m_state->composition.shellRuntime.SetExternalDockHostDropPreview(preview); + m_runtime->SetExternalDockHostDropPreview(preview); } void EditorWindow::ClearExternalDockHostDropPreview() { - m_state->composition.shellRuntime.ClearExternalDockHostDropPreview(); + m_runtime->ClearExternalDockHostDropPreview(); } void EditorWindow::AttachHwnd(HWND hwnd) { @@ -162,7 +170,7 @@ void EditorWindow::AttachHwnd(HWND hwnd) { void EditorWindow::MarkDestroyed() { m_state->window.hwnd = nullptr; m_state->window.closing = false; - m_state->input.trackingMouseLeave = false; + m_inputController->ResetWindowState(); } void EditorWindow::MarkClosing() { @@ -174,7 +182,7 @@ void EditorWindow::ClearClosing() { } void EditorWindow::SetTrackingMouseLeave(bool trackingMouseLeave) { - m_state->input.trackingMouseLeave = trackingMouseLeave; + m_inputController->SetTrackingMouseLeave(trackingMouseLeave); } void EditorWindow::SetTitle(std::wstring title) { @@ -183,7 +191,7 @@ void EditorWindow::SetTitle(std::wstring title) { } void EditorWindow::ReplaceWorkspaceController(UIEditorWorkspaceController workspaceController) { - m_state->composition.workspaceController = std::move(workspaceController); + m_runtime->ReplaceWorkspaceController(std::move(workspaceController)); } void EditorWindow::InvalidateHostWindow() const { @@ -203,130 +211,50 @@ bool EditorWindow::Initialize( } Host::RefreshBorderlessWindowDwmDecorations(m_state->window.hwnd); - m_state->chrome.runtime.Reset(); - m_state->chrome.runtime.SetWindowDpi(QueryWindowDpi(m_state->window.hwnd)); - m_state->render.renderer.SetDpiScale(GetDpiScale()); + m_chromeController->Reset(); + m_chromeController->SetWindowDpi(QueryWindowDpi(m_state->window.hwnd)); + m_runtime->SetDpiScale(GetDpiScale()); std::ostringstream dpiTrace = {}; - dpiTrace << "initial dpi=" << m_state->chrome.runtime.GetWindowDpi() + dpiTrace << "initial dpi=" << m_chromeController->GetWindowDpi() << " scale=" << GetDpiScale(); LogRuntimeTrace("window", dpiTrace.str()); - if (!m_state->render.renderer.Initialize(m_state->window.hwnd)) { - LogRuntimeTrace("app", "renderer initialization failed"); - return false; - } - - RECT clientRect = {}; - GetClientRect(m_state->window.hwnd, &clientRect); - const int clientWidth = (std::max)(clientRect.right - clientRect.left, 1L); - const int clientHeight = (std::max)(clientRect.bottom - clientRect.top, 1L); - if (!m_state->render.windowRenderer.Initialize( - m_state->window.hwnd, - clientWidth, - clientHeight)) { - LogRuntimeTrace("app", "d3d12 window renderer initialization failed"); - m_state->render.renderer.Shutdown(); - return false; - } - - const Host::D3D12WindowRenderLoopAttachResult attachResult = - m_state->render.windowRenderLoop.Attach( - m_state->render.renderer, - m_state->render.windowRenderer); - if (!attachResult.interopWarning.empty()) { - LogRuntimeTrace("app", attachResult.interopWarning); - } - - editorContext.AttachTextMeasurer(m_state->render.renderer); - m_state->composition.shellRuntime.Initialize( + return m_runtime->Initialize( + m_state->window.hwnd, repoRoot, - m_state->render.renderer, - m_state->render.renderer); - m_state->composition.shellRuntime.AttachViewportWindowRenderer( - m_state->render.windowRenderer); - m_state->composition.shellRuntime.SetViewportSurfacePresentationEnabled( - attachResult.hasViewportSurfacePresentation); - - std::string titleBarLogoError = {}; - if (!LoadEmbeddedPngTexture( - m_state->render.renderer, - IDR_PNG_LOGO_ICON, - m_state->render.titleBarLogoIcon, - titleBarLogoError)) { - LogRuntimeTrace("icons", "titlebar logo_icon.png: " + titleBarLogoError); - } - if (!m_state->composition.shellRuntime.GetBuiltInIconError().empty()) { - LogRuntimeTrace("icons", m_state->composition.shellRuntime.GetBuiltInIconError()); - } - - LogRuntimeTrace( - "app", - "shell runtime initialized: " + - editorContext.DescribeWorkspaceState( - m_state->composition.workspaceController, - m_state->composition.shellRuntime.GetShellInteractionState())); - m_state->render.ready = true; - - m_state->render.autoScreenshot.Initialize(captureRoot); - if (autoCaptureOnStartup && IsAutoCaptureOnStartupEnabled()) { - m_state->render.autoScreenshot.RequestCapture("startup"); - editorContext.SetStatus("Capture", "Startup capture requested."); - } - - return true; + editorContext, + captureRoot, + autoCaptureOnStartup); } void EditorWindow::Shutdown() { ForceReleasePointerCapture(); - m_state->render.ready = false; - m_state->render.autoScreenshot.Shutdown(); - m_state->composition.shellRuntime.Shutdown(); - m_state->render.renderer.ReleaseTexture(m_state->render.titleBarLogoIcon); - m_state->render.windowRenderLoop.Detach(); - m_state->render.windowRenderer.Shutdown(); - m_state->render.renderer.Shutdown(); - m_state->input.pendingEvents.clear(); - m_state->chrome.chromeState = {}; - m_state->chrome.runtime.Reset(); + m_runtime->Shutdown(); + m_inputController->ClearPendingEvents(); + m_chromeController->Reset(); } void EditorWindow::ResetInteractionState() { ForceReleasePointerCapture(); - m_state->input.pendingEvents.clear(); - m_state->input.trackingMouseLeave = false; - m_state->input.modifierTracker.Reset(); - m_state->composition.shellRuntime.ResetInteractionState(); - m_state->chrome.chromeState = {}; - m_state->chrome.runtime.EndBorderlessResize(); - m_state->chrome.runtime.EndBorderlessWindowDragRestore(); - m_state->chrome.runtime.EndInteractiveResize(); - m_state->chrome.runtime.SetHoveredBorderlessResizeEdge( + m_inputController->ResetInteractionState(); + m_runtime->ResetInteractionState(); + m_chromeController->ResetChromeState(); + m_chromeController->EndBorderlessResize(); + m_chromeController->EndBorderlessWindowDragRestore(); + m_chromeController->EndInteractiveResize(); + m_chromeController->SetHoveredBorderlessResizeEdge( Host::BorderlessWindowResizeEdge::None); - m_state->chrome.runtime.ClearPredictedClientPixelSize(); + m_chromeController->ClearPredictedClientPixelSize(); } bool EditorWindow::ApplyWindowResize(UINT width, UINT height) { - if (!m_state->render.ready || width == 0u || height == 0u) { + if (!m_runtime->IsReady() || width == 0u || height == 0u) { return false; } - - const Host::D3D12WindowRenderLoopResizeResult resizeResult = - m_state->render.windowRenderLoop.ApplyResize(width, height); - m_state->composition.shellRuntime.SetViewportSurfacePresentationEnabled( - resizeResult.hasViewportSurfacePresentation); - - if (!resizeResult.windowRendererWarning.empty()) { - LogRuntimeTrace("present", resizeResult.windowRendererWarning); - } - - if (!resizeResult.interopWarning.empty()) { - LogRuntimeTrace("present", resizeResult.interopWarning); - } - - return resizeResult.hasViewportSurfacePresentation; + return m_runtime->ApplyResize(width, height); } bool EditorWindow::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const { @@ -353,7 +281,7 @@ bool EditorWindow::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) } bool EditorWindow::ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const { - if (m_state->chrome.runtime.TryGetPredictedClientPixelSize(outWidth, outHeight)) { + if (m_chromeController->TryGetPredictedClientPixelSize(outWidth, outHeight)) { return true; } @@ -361,7 +289,7 @@ bool EditorWindow::ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) } float EditorWindow::GetDpiScale() const { - return m_state->chrome.runtime.GetDpiScale(kBaseDpiScale); + return m_chromeController->GetDpiScale(kBaseDpiScale); } float EditorWindow::PixelsToDips(float pixels) const { @@ -381,7 +309,7 @@ UIPoint EditorWindow::ConvertScreenPixelsToClientDips(const POINT& screenPoint) ScreenToClient(m_state->window.hwnd, &clientPoint); } - const float dpiScale = m_state->chrome.runtime.GetDpiScale(kBaseDpiScale); + const float dpiScale = m_chromeController->GetDpiScale(kBaseDpiScale); return UIPoint( dpiScale > 0.0f ? static_cast(clientPoint.x) / dpiScale @@ -395,7 +323,7 @@ void EditorWindow::OnResize(UINT width, UINT height) { bool matchesPredictedClientSize = false; UINT predictedWidth = 0u; UINT predictedHeight = 0u; - if (m_state->chrome.runtime.TryGetPredictedClientPixelSize( + if (m_chromeController->TryGetPredictedClientPixelSize( predictedWidth, predictedHeight)) { matchesPredictedClientSize = @@ -403,7 +331,7 @@ void EditorWindow::OnResize(UINT width, UINT height) { predictedHeight == height; } - m_state->chrome.runtime.ClearPredictedClientPixelSize(); + m_chromeController->ClearPredictedClientPixelSize(); if (IsBorderlessWindowEnabled() && m_state->window.hwnd != nullptr) { Host::RefreshBorderlessWindowDwmDecorations(m_state->window.hwnd); } @@ -414,12 +342,12 @@ void EditorWindow::OnResize(UINT width, UINT height) { } void EditorWindow::OnEnterSizeMove() { - m_state->chrome.runtime.BeginInteractiveResize(); + m_chromeController->BeginInteractiveResize(); } void EditorWindow::OnExitSizeMove() { - m_state->chrome.runtime.EndInteractiveResize(); - m_state->chrome.runtime.ClearPredictedClientPixelSize(); + m_chromeController->EndInteractiveResize(); + m_chromeController->ClearPredictedClientPixelSize(); UINT width = 0u; UINT height = 0u; if (QueryCurrentClientPixelSize(width, height)) { @@ -428,8 +356,8 @@ void EditorWindow::OnExitSizeMove() { } void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { - m_state->chrome.runtime.SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi); - m_state->render.renderer.SetDpiScale(GetDpiScale()); + m_chromeController->SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi); + m_runtime->SetDpiScale(GetDpiScale()); if (m_state->window.hwnd != nullptr) { const LONG windowWidth = suggestedRect.right - suggestedRect.left; const LONG windowHeight = suggestedRect.bottom - suggestedRect.top; @@ -450,7 +378,7 @@ void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { } std::ostringstream trace = {}; - trace << "dpi changed to " << m_state->chrome.runtime.GetWindowDpi() + trace << "dpi changed to " << m_chromeController->GetWindowDpi() << " scale=" << GetDpiScale(); LogRuntimeTrace("window", trace.str()); } diff --git a/new_editor/app/Platform/Win32/EditorWindowManager.h b/new_editor/app/Platform/Win32/EditorWindowManager.h index 11aa0bbc..4c85d758 100644 --- a/new_editor/app/Platform/Win32/EditorWindowManager.h +++ b/new_editor/app/Platform/Win32/EditorWindowManager.h @@ -72,6 +72,12 @@ public: const CreateParams& params); void HandlePendingNativeWindowCreated(HWND hwnd); void Shutdown(); + bool TryDispatchWindowMessage( + HWND hwnd, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult); EditorWindow* FindWindow(HWND hwnd); const EditorWindow* FindWindow(HWND hwnd) const; @@ -84,16 +90,6 @@ public: void DestroyClosedWindows(); void RenderAllWindows(); - bool IsGlobalTabDragActive() const; - bool OwnsActiveGlobalTabDrag(std::string_view windowId) const; - void EndGlobalTabDragSession(); - void HandleDestroyedWindow(HWND hwnd); - void HandleWindowFrameTransferRequests( - EditorWindow& sourceWindow, - EditorWindowFrameTransferRequests&& transferRequests); - bool HandleGlobalTabDragPointerMove(HWND hwnd); - bool HandleGlobalTabDragPointerButtonUp(HWND hwnd); - private: std::unique_ptr m_hostRuntime = {}; std::unique_ptr m_workspaceCoordinator = {}; diff --git a/new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp b/new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp new file mode 100644 index 00000000..f2bff7f5 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp @@ -0,0 +1,226 @@ +#include "Platform/Win32/EditorWindowRuntimeController.h" + +#include "Bootstrap/EditorResources.h" +#include "Internal/EmbeddedPngLoader.h" +#include "Platform/Win32/EditorWindowRuntimeInternal.h" +#include "Composition/EditorContext.h" + +#include +#include + +namespace XCEngine::UI::Editor::App::Internal { + +using App::Internal::LoadEmbeddedPngTexture; +using namespace EditorWindowInternal; + +EditorWindowRuntimeController::EditorWindowRuntimeController( + UIEditorWorkspaceController workspaceController) + : m_workspaceController(std::move(workspaceController)) { +} + +EditorWindowRuntimeController::~EditorWindowRuntimeController() = default; + +bool EditorWindowRuntimeController::IsReady() const { + return m_ready; +} + +const UIEditorWorkspaceController& EditorWindowRuntimeController::GetWorkspaceController() const { + return m_workspaceController; +} + +UIEditorWorkspaceController& EditorWindowRuntimeController::GetMutableWorkspaceController() { + return m_workspaceController; +} + +void EditorWindowRuntimeController::ReplaceWorkspaceController( + UIEditorWorkspaceController workspaceController) { + m_workspaceController = std::move(workspaceController); +} + +const EditorShellRuntime& EditorWindowRuntimeController::GetShellRuntime() const { + return m_shellRuntime; +} + +EditorShellRuntime& EditorWindowRuntimeController::GetShellRuntime() { + return m_shellRuntime; +} + +const UIEditorShellInteractionFrame& EditorWindowRuntimeController::GetShellFrame() const { + return m_shellRuntime.GetShellFrame(); +} + +const UIEditorShellInteractionState& EditorWindowRuntimeController::GetShellInteractionState() + const { + return m_shellRuntime.GetShellInteractionState(); +} + +void EditorWindowRuntimeController::SetExternalDockHostDropPreview( + const Widgets::UIEditorDockHostDropPreviewState& preview) { + m_shellRuntime.SetExternalDockHostDropPreview(preview); +} + +void EditorWindowRuntimeController::ClearExternalDockHostDropPreview() { + m_shellRuntime.ClearExternalDockHostDropPreview(); +} + +void EditorWindowRuntimeController::SetDpiScale(float dpiScale) { + m_renderer.SetDpiScale(dpiScale); +} + +Host::NativeRenderer& EditorWindowRuntimeController::GetRenderer() { + return m_renderer; +} + +const Host::NativeRenderer& EditorWindowRuntimeController::GetRenderer() const { + return m_renderer; +} + +const ::XCEngine::UI::UITextureHandle& EditorWindowRuntimeController::GetTitleBarLogoIcon() const { + return m_titleBarLogoIcon; +} + +bool EditorWindowRuntimeController::Initialize( + HWND hwnd, + const std::filesystem::path& repoRoot, + EditorContext& editorContext, + const std::filesystem::path& captureRoot, + bool autoCaptureOnStartup) { + if (hwnd == nullptr) { + LogRuntimeTrace("app", "window initialize skipped: hwnd is null"); + return false; + } + + if (!m_renderer.Initialize(hwnd)) { + LogRuntimeTrace("app", "renderer initialization failed"); + return false; + } + + RECT clientRect = {}; + GetClientRect(hwnd, &clientRect); + const int clientWidth = (std::max)(clientRect.right - clientRect.left, 1L); + const int clientHeight = (std::max)(clientRect.bottom - clientRect.top, 1L); + if (!m_windowRenderer.Initialize(hwnd, clientWidth, clientHeight)) { + LogRuntimeTrace("app", "d3d12 window renderer initialization failed"); + m_renderer.Shutdown(); + return false; + } + + const Host::D3D12WindowRenderLoopAttachResult attachResult = + m_windowRenderLoop.Attach(m_renderer, m_windowRenderer); + if (!attachResult.interopWarning.empty()) { + LogRuntimeTrace("app", attachResult.interopWarning); + } + + editorContext.AttachTextMeasurer(m_renderer); + m_shellRuntime.Initialize(repoRoot, m_renderer, m_renderer); + m_shellRuntime.AttachViewportWindowRenderer(m_windowRenderer); + m_shellRuntime.SetViewportSurfacePresentationEnabled( + attachResult.hasViewportSurfacePresentation); + + std::string titleBarLogoError = {}; + if (!LoadEmbeddedPngTexture( + m_renderer, + IDR_PNG_LOGO_ICON, + m_titleBarLogoIcon, + titleBarLogoError)) { + LogRuntimeTrace("icons", "titlebar logo_icon.png: " + titleBarLogoError); + } + if (!m_shellRuntime.GetBuiltInIconError().empty()) { + LogRuntimeTrace("icons", m_shellRuntime.GetBuiltInIconError()); + } + + LogRuntimeTrace( + "app", + "shell runtime initialized: " + + editorContext.DescribeWorkspaceState( + m_workspaceController, + m_shellRuntime.GetShellInteractionState())); + m_ready = true; + + m_autoScreenshot.Initialize(captureRoot); + if (autoCaptureOnStartup && IsAutoCaptureOnStartupEnabled()) { + m_autoScreenshot.RequestCapture("startup"); + editorContext.SetStatus("Capture", "Startup capture requested."); + } + + return true; +} + +void EditorWindowRuntimeController::Shutdown() { + m_ready = false; + m_autoScreenshot.Shutdown(); + m_shellRuntime.Shutdown(); + m_renderer.ReleaseTexture(m_titleBarLogoIcon); + m_windowRenderLoop.Detach(); + m_windowRenderer.Shutdown(); + m_renderer.Shutdown(); +} + +void EditorWindowRuntimeController::ResetInteractionState() { + m_shellRuntime.ResetInteractionState(); +} + +bool EditorWindowRuntimeController::ApplyResize(UINT width, UINT height) { + if (!m_ready || width == 0u || height == 0u) { + return false; + } + + const Host::D3D12WindowRenderLoopResizeResult resizeResult = + m_windowRenderLoop.ApplyResize(width, height); + m_shellRuntime.SetViewportSurfacePresentationEnabled( + resizeResult.hasViewportSurfacePresentation); + + if (!resizeResult.windowRendererWarning.empty()) { + LogRuntimeTrace("present", resizeResult.windowRendererWarning); + } + + if (!resizeResult.interopWarning.empty()) { + LogRuntimeTrace("present", resizeResult.interopWarning); + } + + return resizeResult.hasViewportSurfacePresentation; +} + +Host::D3D12WindowRenderLoopFrameContext EditorWindowRuntimeController::BeginFrame() const { + return m_windowRenderLoop.BeginFrame(); +} + +Host::D3D12WindowRenderLoopPresentResult EditorWindowRuntimeController::Present( + const ::XCEngine::UI::UIDrawData& drawData) const { + return m_windowRenderLoop.Present(drawData); +} + +void EditorWindowRuntimeController::CaptureIfRequested( + const ::XCEngine::UI::UIDrawData& drawData, + UINT pixelWidth, + UINT pixelHeight, + bool framePresented) { + m_autoScreenshot.CaptureIfRequested( + m_renderer, + drawData, + pixelWidth, + pixelHeight, + framePresented); +} + +void EditorWindowRuntimeController::RequestManualScreenshot(std::string reason) { + m_autoScreenshot.RequestCapture(std::move(reason)); +} + +std::string EditorWindowRuntimeController::BuildCaptureStatusText() const { + if (m_autoScreenshot.HasPendingCapture()) { + return "Shot pending..."; + } + + if (!m_autoScreenshot.GetLastCaptureError().empty()) { + return TruncateText(m_autoScreenshot.GetLastCaptureError(), 38u); + } + + if (!m_autoScreenshot.GetLastCaptureSummary().empty()) { + return TruncateText(m_autoScreenshot.GetLastCaptureSummary(), 38u); + } + + return {}; +} + +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/EditorWindowRuntimeController.h b/new_editor/app/Platform/Win32/EditorWindowRuntimeController.h new file mode 100644 index 00000000..1b3507f1 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowRuntimeController.h @@ -0,0 +1,105 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "Composition/EditorShellRuntime.h" + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include + +namespace XCEngine::UI::Editor { + +struct UIEditorShellInteractionFrame; +struct UIEditorShellInteractionState; + +namespace Widgets { +struct UIEditorDockHostDropPreviewState; +} + +} // namespace XCEngine::UI::Editor + +namespace XCEngine::UI::Editor::App { + +class EditorContext; + +namespace Internal { + +class EditorWindowRuntimeController final { +public: + explicit EditorWindowRuntimeController(UIEditorWorkspaceController workspaceController); + ~EditorWindowRuntimeController(); + + EditorWindowRuntimeController(const EditorWindowRuntimeController&) = delete; + EditorWindowRuntimeController& operator=(const EditorWindowRuntimeController&) = delete; + EditorWindowRuntimeController(EditorWindowRuntimeController&&) = delete; + EditorWindowRuntimeController& operator=(EditorWindowRuntimeController&&) = delete; + + bool IsReady() const; + + const UIEditorWorkspaceController& GetWorkspaceController() const; + UIEditorWorkspaceController& GetMutableWorkspaceController(); + void ReplaceWorkspaceController(UIEditorWorkspaceController workspaceController); + + const EditorShellRuntime& GetShellRuntime() const; + EditorShellRuntime& GetShellRuntime(); + const UIEditorShellInteractionFrame& GetShellFrame() const; + const UIEditorShellInteractionState& GetShellInteractionState() const; + + void SetExternalDockHostDropPreview( + const Widgets::UIEditorDockHostDropPreviewState& preview); + void ClearExternalDockHostDropPreview(); + + void SetDpiScale(float dpiScale); + Host::NativeRenderer& GetRenderer(); + const Host::NativeRenderer& GetRenderer() const; + const ::XCEngine::UI::UITextureHandle& GetTitleBarLogoIcon() const; + + bool Initialize( + HWND hwnd, + const std::filesystem::path& repoRoot, + EditorContext& editorContext, + const std::filesystem::path& captureRoot, + bool autoCaptureOnStartup); + void Shutdown(); + void ResetInteractionState(); + bool ApplyResize(UINT width, UINT height); + + Host::D3D12WindowRenderLoopFrameContext BeginFrame() const; + Host::D3D12WindowRenderLoopPresentResult Present( + const ::XCEngine::UI::UIDrawData& drawData) const; + void CaptureIfRequested( + const ::XCEngine::UI::UIDrawData& drawData, + UINT pixelWidth, + UINT pixelHeight, + bool framePresented); + + void RequestManualScreenshot(std::string reason); + std::string BuildCaptureStatusText() const; + +private: + Host::NativeRenderer m_renderer = {}; + Host::D3D12WindowRenderer m_windowRenderer = {}; + Host::D3D12WindowRenderLoop m_windowRenderLoop = {}; + Host::AutoScreenshotController m_autoScreenshot = {}; + ::XCEngine::UI::UITextureHandle m_titleBarLogoIcon = {}; + UIEditorWorkspaceController m_workspaceController = {}; + EditorShellRuntime m_shellRuntime = {}; + bool m_ready = false; +}; + +} // namespace Internal +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp b/new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp index d36a7e69..4ae3ba32 100644 --- a/new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp @@ -1,4 +1,5 @@ #include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowChromeController.h" #include "Platform/Win32/EditorWindowConstants.h" #include "Platform/Win32/EditorWindowInternalState.h" @@ -11,246 +12,57 @@ namespace XCEngine::UI::Editor::App { using namespace EditorWindowInternal; bool EditorWindow::UpdateBorderlessWindowChromeHover(LPARAM lParam) { - if (m_state->chrome.runtime.GetHoveredBorderlessResizeEdge() != - Host::BorderlessWindowResizeEdge::None || - m_state->chrome.runtime.IsBorderlessResizeActive()) { - const bool changed = - m_state->chrome.chromeState.hoveredTarget != - Host::BorderlessWindowChromeHitTarget::None; - m_state->chrome.chromeState.hoveredTarget = - Host::BorderlessWindowChromeHitTarget::None; - return changed; - } - - const Host::BorderlessWindowChromeHitTarget hitTarget = - HitTestBorderlessWindowChrome(lParam); - const Host::BorderlessWindowChromeHitTarget buttonTarget = - hitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton || - hitTarget == Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton || - hitTarget == Host::BorderlessWindowChromeHitTarget::CloseButton - ? hitTarget - : Host::BorderlessWindowChromeHitTarget::None; - if (m_state->chrome.chromeState.hoveredTarget == buttonTarget) { - return false; - } - - m_state->chrome.chromeState.hoveredTarget = buttonTarget; - return true; + return m_chromeController->UpdateChromeHover(*this, lParam); } bool EditorWindow::HandleBorderlessWindowChromeButtonDown(LPARAM lParam) { - if (m_state->chrome.runtime.GetHoveredBorderlessResizeEdge() != - Host::BorderlessWindowResizeEdge::None || - m_state->chrome.runtime.IsBorderlessResizeActive()) { - return false; - } - - const Host::BorderlessWindowChromeHitTarget hitTarget = - HitTestBorderlessWindowChrome(lParam); - switch (hitTarget) { - case Host::BorderlessWindowChromeHitTarget::MinimizeButton: - case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton: - case Host::BorderlessWindowChromeHitTarget::CloseButton: - m_state->chrome.chromeState.pressedTarget = hitTarget; - AcquirePointerCapture(EditorWindowPointerCaptureOwner::BorderlessChrome); - InvalidateHostWindow(); - return true; - case Host::BorderlessWindowChromeHitTarget::DragRegion: - if (m_state->window.hwnd != nullptr) { - if (IsBorderlessWindowMaximized()) { - POINT screenPoint = {}; - if (GetCursorPos(&screenPoint)) { - m_state->chrome.runtime.BeginBorderlessWindowDragRestore(screenPoint); - AcquirePointerCapture(EditorWindowPointerCaptureOwner::BorderlessChrome); - return true; - } - } - - ForceReleasePointerCapture(); - SendMessageW(m_state->window.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); - } - return true; - case Host::BorderlessWindowChromeHitTarget::None: - default: - return false; - } + return m_chromeController->HandleChromeButtonDown(*this, lParam); } bool EditorWindow::HandleBorderlessWindowChromeButtonUp( EditorContext& editorContext, bool globalTabDragActive, LPARAM lParam) { - if (m_state->chrome.runtime.IsBorderlessWindowDragRestoreArmed()) { - ClearBorderlessWindowChromeDragRestoreState(); - return true; - } - - const Host::BorderlessWindowChromeHitTarget pressedTarget = - m_state->chrome.chromeState.pressedTarget; - if (pressedTarget != Host::BorderlessWindowChromeHitTarget::MinimizeButton && - pressedTarget != Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton && - pressedTarget != Host::BorderlessWindowChromeHitTarget::CloseButton) { - return false; - } - - const Host::BorderlessWindowChromeHitTarget releasedTarget = - HitTestBorderlessWindowChrome(lParam); - m_state->chrome.chromeState.pressedTarget = - Host::BorderlessWindowChromeHitTarget::None; - ReleasePointerCapture(EditorWindowPointerCaptureOwner::BorderlessChrome); - InvalidateHostWindow(); - - if (pressedTarget == releasedTarget) { - ExecuteBorderlessWindowChromeAction( - editorContext, - globalTabDragActive, - pressedTarget); - } - return true; + return m_chromeController->HandleChromeButtonUp( + *this, + editorContext, + globalTabDragActive, + lParam); } bool EditorWindow::HandleBorderlessWindowChromeDoubleClick( EditorContext& editorContext, bool globalTabDragActive, LPARAM lParam) { - if (m_state->chrome.runtime.IsBorderlessWindowDragRestoreArmed()) { - ClearBorderlessWindowChromeDragRestoreState(); - } - - if (HitTestBorderlessWindowChrome(lParam) != - Host::BorderlessWindowChromeHitTarget::DragRegion) { - return false; - } - - ExecuteBorderlessWindowChromeAction( + return m_chromeController->HandleChromeDoubleClick( + *this, editorContext, globalTabDragActive, - Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton); - return true; + lParam); } bool EditorWindow::HandleBorderlessWindowChromeDragRestorePointerMove( EditorContext& editorContext, bool globalTabDragActive) { - if (!m_state->chrome.runtime.IsBorderlessWindowDragRestoreArmed() || - m_state->window.hwnd == nullptr) { - return false; - } - - POINT currentScreenPoint = {}; - if (!GetCursorPos(¤tScreenPoint)) { - return true; - } - - const POINT initialScreenPoint = - m_state->chrome.runtime.GetBorderlessWindowDragRestoreInitialScreenPoint(); - const int dragThresholdX = (std::max)(GetSystemMetrics(SM_CXDRAG), 1); - const int dragThresholdY = (std::max)(GetSystemMetrics(SM_CYDRAG), 1); - const LONG deltaX = currentScreenPoint.x - initialScreenPoint.x; - const LONG deltaY = currentScreenPoint.y - initialScreenPoint.y; - if (std::abs(deltaX) < dragThresholdX && - std::abs(deltaY) < dragThresholdY) { - return true; - } - - RECT restoreRect = {}; - RECT currentRect = {}; - RECT workAreaRect = {}; - if (!m_state->chrome.runtime.TryGetBorderlessWindowRestoreRect(restoreRect) || - !QueryCurrentWindowRect(currentRect) || - !QueryBorderlessWindowWorkAreaRect(workAreaRect)) { - ClearBorderlessWindowChromeDragRestoreState(); - return true; - } - - const int restoreWidth = restoreRect.right - restoreRect.left; - const int restoreHeight = restoreRect.bottom - restoreRect.top; - const int currentWidth = currentRect.right - currentRect.left; - if (restoreWidth <= 0 || restoreHeight <= 0 || currentWidth <= 0) { - ClearBorderlessWindowChromeDragRestoreState(); - return true; - } - - const float pointerRatio = - static_cast(currentScreenPoint.x - currentRect.left) / - static_cast(currentWidth); - const float clampedPointerRatio = (std::clamp)(pointerRatio, 0.0f, 1.0f); - const int newLeft = - (std::clamp)( - currentScreenPoint.x - - static_cast(clampedPointerRatio * static_cast(restoreWidth)), - workAreaRect.left, - workAreaRect.right - restoreWidth); - const int titleBarHeightPixels = - static_cast(kBorderlessTitleBarHeightDips * GetDpiScale()); - const int newTop = - (std::clamp)( - currentScreenPoint.y - (std::max)(titleBarHeightPixels / 2, 1), - workAreaRect.top, - workAreaRect.bottom - restoreHeight); - const RECT targetRect = { - newLeft, - newTop, - newLeft + restoreWidth, - newTop + restoreHeight - }; - - m_state->chrome.runtime.SetBorderlessWindowMaximized(false); - ApplyPredictedWindowRectTransition( + return m_chromeController->HandleChromeDragRestorePointerMove( + *this, editorContext, - globalTabDragActive, - targetRect); - ClearBorderlessWindowChromeDragRestoreState(); - SendMessageW(m_state->window.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); - return true; + globalTabDragActive); } void EditorWindow::ClearBorderlessWindowChromeDragRestoreState() { - if (!m_state->chrome.runtime.IsBorderlessWindowDragRestoreArmed()) { - return; - } - - m_state->chrome.runtime.EndBorderlessWindowDragRestore(); - ReleasePointerCapture(EditorWindowPointerCaptureOwner::BorderlessChrome); + m_chromeController->ClearChromeDragRestoreState(*this); } void EditorWindow::ClearBorderlessWindowChromeState() { - if (m_state->chrome.chromeState.hoveredTarget == - Host::BorderlessWindowChromeHitTarget::None && - m_state->chrome.chromeState.pressedTarget == - Host::BorderlessWindowChromeHitTarget::None) { - return; - } - - m_state->chrome.chromeState = {}; - InvalidateHostWindow(); + m_chromeController->ClearChromeState(*this); } void EditorWindow::ExecuteBorderlessWindowChromeAction( EditorContext& editorContext, bool globalTabDragActive, Host::BorderlessWindowChromeHitTarget target) { - if (m_state->window.hwnd == nullptr) { - return; - } - - switch (target) { - case Host::BorderlessWindowChromeHitTarget::MinimizeButton: - ShowWindow(m_state->window.hwnd, SW_MINIMIZE); - break; - case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton: - ToggleBorderlessWindowMaximizeRestore(editorContext, globalTabDragActive); - break; - case Host::BorderlessWindowChromeHitTarget::CloseButton: - PostMessageW(m_state->window.hwnd, WM_CLOSE, 0, 0); - break; - case Host::BorderlessWindowChromeHitTarget::DragRegion: - case Host::BorderlessWindowChromeHitTarget::None: - default: - break; - } - - InvalidateHostWindow(); + m_chromeController->ExecuteChromeAction(*this, editorContext, globalTabDragActive, target); } } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp b/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp index 4eba303e..5e571787 100644 --- a/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp @@ -1,206 +1,26 @@ #include "Platform/Win32/EditorWindow.h" -#include "Platform/Win32/EditorWindowConstants.h" -#include "Platform/Win32/EditorWindowInternalState.h" -#include "Platform/Win32/EditorWindowStyle.h" - -#include -#include -#include -#include - -#include -#include -#include - -#include +#include "Platform/Win32/EditorWindowChromeController.h" namespace XCEngine::UI::Editor::App { -using namespace EditorWindowInternal; -using ::XCEngine::UI::Layout::MeasureUITabStripHeaderWidth; -using ::XCEngine::UI::UIColor; -using ::XCEngine::UI::UIDrawList; -using ::XCEngine::UI::UIPoint; -using ::XCEngine::UI::UIRect; - -namespace { - -constexpr float kTitleBarLogoExtent = 16.0f; -constexpr float kTitleBarLogoInsetLeft = 8.0f; -constexpr float kTitleBarLogoTextGap = 8.0f; -std::string ResolveDetachedTitleTabText(const EditorWindow& window) { - const auto& workspaceController = window.GetWorkspaceController(); - const std::string_view activePanelId = workspaceController.GetWorkspace().activePanelId; - if (!activePanelId.empty()) { - if (const UIEditorPanelDescriptor* descriptor = - FindUIEditorPanelDescriptor( - workspaceController.GetPanelRegistry(), - activePanelId); - descriptor != nullptr && - !descriptor->defaultTitle.empty()) { - return descriptor->defaultTitle; - } - } - - return std::string("Panel"); -} - -float ResolveDetachedTabWidth(std::string_view text) { - const Widgets::UIEditorTabStripMetrics& metrics = ResolveUIEditorTabStripMetrics(); - Widgets::UIEditorTabStripItem item = {}; - item.title = std::string(text); - const float desiredLabelWidth = - Widgets::ResolveUIEditorTabStripDesiredHeaderLabelWidth(item, metrics); - return MeasureUITabStripHeaderWidth(desiredLabelWidth, metrics.layoutMetrics); -} - -bool IsRootPanelVisible( - const UIEditorWorkspaceController& controller, - std::string_view panelId) { - const UIEditorPanelSessionState* panelState = - FindUIEditorPanelSessionState(controller.GetSession(), panelId); - return panelState != nullptr && panelState->open && panelState->visible; -} - -bool HasSingleVisibleRootTab(const UIEditorWorkspaceController& controller) { - const UIEditorWorkspaceNode& root = controller.GetWorkspace().root; - if (root.kind != UIEditorWorkspaceNodeKind::TabStack) { - return false; - } - - std::size_t visiblePanelCount = 0u; - for (const UIEditorWorkspaceNode& child : root.children) { - if (child.kind != UIEditorWorkspaceNodeKind::Panel || - !IsRootPanelVisible(controller, child.panel.panelId)) { - continue; - } - - ++visiblePanelCount; - if (visiblePanelCount > 1u) { - return false; - } - } - - return visiblePanelCount == 1u; -} - -UIRect BuildDetachedTitleLogoRect(const Host::BorderlessWindowChromeLayout& layout) { - const float availableLeft = layout.titleBarRect.x; - const float availableRight = layout.minimizeButtonRect.x; - const float centeredX = std::floor( - layout.titleBarRect.x + layout.titleBarRect.width * 0.5f - kTitleBarLogoExtent * 0.5f); - const float clampedX = (std::max)( - availableLeft, - (std::min)(centeredX, availableRight - kTitleBarLogoExtent)); - const float logoY = - layout.titleBarRect.y + - (std::max)(0.0f, (layout.titleBarRect.height - kTitleBarLogoExtent) * 0.5f); - return UIRect(clampedX, logoY, kTitleBarLogoExtent, kTitleBarLogoExtent); -} - -} // namespace - bool EditorWindow::ShouldUseDetachedTitleBarTabStrip() const { - return !m_state->window.primary && - HasSingleVisibleRootTab(m_state->composition.workspaceController); + return m_chromeController->ShouldUseDetachedTitleBarTabStrip(*this); } Host::BorderlessWindowChromeHitTarget EditorWindow::HitTestBorderlessWindowChrome( LPARAM lParam) const { - if (!IsBorderlessWindowEnabled() || m_state->window.hwnd == nullptr) { - return Host::BorderlessWindowChromeHitTarget::None; - } - - RECT clientRect = {}; - if (!GetClientRect(m_state->window.hwnd, &clientRect)) { - return Host::BorderlessWindowChromeHitTarget::None; - } - - const float clientWidthDips = - PixelsToDips(static_cast((std::max)(clientRect.right - clientRect.left, 1L))); - const Host::BorderlessWindowChromeLayout layout = - ResolveBorderlessWindowChromeLayout(clientWidthDips); - return Host::HitTestBorderlessWindowChrome( - layout, - ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))); + return m_chromeController->HitTestChrome(*this, lParam); } Host::BorderlessWindowChromeLayout EditorWindow::ResolveBorderlessWindowChromeLayout( float clientWidthDips) const { - float leadingOccupiedRight = 0.0f; - if (ShouldUseDetachedTitleBarTabStrip()) { - leadingOccupiedRight = ResolveDetachedTabWidth(ResolveDetachedTitleTabText(*this)); - } - - return Host::BuildBorderlessWindowChromeLayout( - UIRect(0.0f, 0.0f, clientWidthDips, kBorderlessTitleBarHeightDips), - leadingOccupiedRight); + return m_chromeController->ResolveChromeLayout(*this, clientWidthDips); } void EditorWindow::AppendBorderlessWindowChrome( - UIDrawList& drawList, + ::XCEngine::UI::UIDrawList& drawList, float clientWidthDips) const { - if (!IsBorderlessWindowEnabled()) { - return; - } - - const Host::BorderlessWindowChromeLayout layout = - ResolveBorderlessWindowChromeLayout(clientWidthDips); - const bool useDetachedTitleBarTabStrip = ShouldUseDetachedTitleBarTabStrip(); - if (!useDetachedTitleBarTabStrip) { - drawList.AddFilledRect(layout.titleBarRect, kShellSurfaceColor); - drawList.AddLine( - UIPoint(layout.titleBarRect.x, layout.titleBarRect.y + layout.titleBarRect.height), - UIPoint( - layout.titleBarRect.x + layout.titleBarRect.width, - layout.titleBarRect.y + layout.titleBarRect.height), - kShellBorderColor, - 1.0f); - } - - if (!m_state->window.primary) { - if (m_state->render.titleBarLogoIcon.IsValid()) { - drawList.AddImage( - BuildDetachedTitleLogoRect(layout), - m_state->render.titleBarLogoIcon, - UIColor(1.0f, 1.0f, 1.0f, 1.0f)); - } - } else { - const float iconX = layout.titleBarRect.x + kTitleBarLogoInsetLeft; - const float iconY = - layout.titleBarRect.y + - (std::max)(0.0f, (layout.titleBarRect.height - kTitleBarLogoExtent) * 0.5f); - if (m_state->render.titleBarLogoIcon.IsValid()) { - drawList.AddImage( - UIRect(iconX, iconY, kTitleBarLogoExtent, kTitleBarLogoExtent), - m_state->render.titleBarLogoIcon, - UIColor(1.0f, 1.0f, 1.0f, 1.0f)); - } - - const std::string& titleText = - m_state->window.titleText.empty() - ? std::string("XCEngine Editor") - : m_state->window.titleText; - drawList.AddText( - UIPoint( - iconX + - (m_state->render.titleBarLogoIcon.IsValid() - ? (kTitleBarLogoExtent + kTitleBarLogoTextGap) - : 4.0f), - layout.titleBarRect.y + - (std::max)( - 0.0f, - (layout.titleBarRect.height - kBorderlessTitleBarFontSize) * 0.5f - 1.0f)), - titleText, - kShellTextColor, - kBorderlessTitleBarFontSize); - } - - Host::AppendBorderlessWindowChrome( - drawList, - layout, - m_state->chrome.chromeState, - IsBorderlessWindowMaximized()); + m_chromeController->AppendChrome(*this, drawList, clientWidthDips); } } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/Win32SystemInteractionHost.cpp b/new_editor/app/Platform/Win32/Win32SystemInteractionHost.cpp new file mode 100644 index 00000000..1d083b2f --- /dev/null +++ b/new_editor/app/Platform/Win32/Win32SystemInteractionHost.cpp @@ -0,0 +1,104 @@ +#include "Platform/Win32/Win32SystemInteractionHost.h" + +#include "Internal/StringEncoding.h" + +#include +#include + +#include + +namespace XCEngine::UI::Editor::Host { + +namespace { + +bool IsPathAvailable(const std::filesystem::path& path) { + std::error_code errorCode = {}; + return !path.empty() && + std::filesystem::exists(path, errorCode) && + !errorCode; +} + +} // namespace + +bool Win32SystemInteractionHost::CopyTextToClipboard(std::string_view text) { + if (text.empty()) { + return false; + } + + const std::wstring wideText = + App::Internal::Utf8ToWide(std::string(text)); + const std::size_t byteCount = + (wideText.size() + 1u) * sizeof(wchar_t); + if (!OpenClipboard(nullptr)) { + return false; + } + + struct ClipboardCloser final { + ~ClipboardCloser() { + CloseClipboard(); + } + } clipboardCloser = {}; + + if (!EmptyClipboard()) { + return false; + } + + HGLOBAL handle = GlobalAlloc(GMEM_MOVEABLE, byteCount); + if (handle == nullptr) { + return false; + } + + void* locked = GlobalLock(handle); + if (locked == nullptr) { + GlobalFree(handle); + return false; + } + + std::memcpy(locked, wideText.c_str(), byteCount); + GlobalUnlock(handle); + + if (SetClipboardData(CF_UNICODETEXT, handle) == nullptr) { + GlobalFree(handle); + return false; + } + + return true; +} + +bool Win32SystemInteractionHost::RevealPathInFileBrowser( + const std::filesystem::path& path, + bool selectTarget) { + if (!IsPathAvailable(path)) { + return false; + } + + const std::filesystem::path targetPath = path.lexically_normal(); + HINSTANCE result = nullptr; + if (selectTarget) { + const std::wstring parameters = + L"/select,\"" + targetPath.native() + L"\""; + const std::wstring workingDirectory = + targetPath.parent_path().native(); + result = ShellExecuteW( + nullptr, + L"open", + L"explorer.exe", + parameters.c_str(), + workingDirectory.empty() ? nullptr : workingDirectory.c_str(), + SW_SHOWNORMAL); + } else { + const std::wstring workingDirectory = + targetPath.parent_path().native(); + result = ShellExecuteW( + nullptr, + L"open", + targetPath.c_str(), + nullptr, + workingDirectory.empty() ? nullptr : workingDirectory.c_str(), + SW_SHOWNORMAL); + } + + return reinterpret_cast(result) > 32; +} + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Platform/Win32/Win32SystemInteractionHost.h b/new_editor/app/Platform/Win32/Win32SystemInteractionHost.h new file mode 100644 index 00000000..263e5d69 --- /dev/null +++ b/new_editor/app/Platform/Win32/Win32SystemInteractionHost.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Ports/SystemInteractionPort.h" + +namespace XCEngine::UI::Editor::Host { + +class Win32SystemInteractionHost final : public Ports::SystemInteractionPort { +public: + bool CopyTextToClipboard(std::string_view text) override; + bool RevealPathInFileBrowser( + const std::filesystem::path& path, + bool selectTarget) override; +}; + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Platform/Win32/WindowMessageDispatcher.cpp b/new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp similarity index 76% rename from new_editor/app/Platform/Win32/WindowMessageDispatcher.cpp rename to new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp index 15827d39..f450f6c3 100644 --- a/new_editor/app/Platform/Win32/WindowMessageDispatcher.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp @@ -1,14 +1,14 @@ -#include "WindowMessageDispatcher.h" +#include "Platform/Win32/WindowManager/EditorWindowMessageDispatcher.h" #include "Composition/EditorShellRuntime.h" #include "Platform/Win32/BorderlessWindowChrome.h" #include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowPointerCapture.h" -#include "WindowMessageHost.h" +#include "Platform/Win32/WindowManager/Internal.h" #include -namespace XCEngine::UI::Editor::Host { +namespace XCEngine::UI::Editor::App::Internal { namespace { @@ -17,22 +17,23 @@ constexpr UINT kMessageNcUaDrawFrame = 0x00AFu; } // namespace -struct WindowMessageDispatcher::DispatchContext { +struct EditorWindowMessageDispatcher::DispatchContext { HWND hwnd = nullptr; - WindowMessageHost& windowHost; - App::EditorWindow& window; + EditorWindowHostRuntime& hostRuntime; + EditorWindowWorkspaceCoordinator& workspaceCoordinator; + EditorWindow& window; }; -void WindowMessageDispatcher::RenderAndValidateWindow(const DispatchContext& context) { +void EditorWindowMessageDispatcher::RenderAndValidateWindow(const DispatchContext& context) { if (!context.window.IsRenderReady()) { return; } - App::EditorWindowFrameTransferRequests transferRequests = context.window.RenderFrame( - context.windowHost.GetEditorContext(), - context.windowHost.IsGlobalTabDragActive()); + EditorWindowFrameTransferRequests transferRequests = context.window.RenderFrame( + context.hostRuntime.GetEditorContext(), + context.workspaceCoordinator.IsGlobalTabDragActive()); if (transferRequests.HasPendingRequests()) { - context.windowHost.HandleWindowFrameTransferRequests( + context.workspaceCoordinator.HandleWindowFrameTransferRequests( context.window, std::move(transferRequests)); } @@ -41,7 +42,7 @@ void WindowMessageDispatcher::RenderAndValidateWindow(const DispatchContext& con } } -bool WindowMessageDispatcher::EnsureTrackingMouseLeave(const DispatchContext& context) { +bool EditorWindowMessageDispatcher::EnsureTrackingMouseLeave(const DispatchContext& context) { if (context.window.IsTrackingMouseLeave()) { return true; } @@ -58,11 +59,11 @@ bool WindowMessageDispatcher::EnsureTrackingMouseLeave(const DispatchContext& co return true; } -bool WindowMessageDispatcher::TryHandleChromeHoverConsumption( +bool EditorWindowMessageDispatcher::TryHandleChromeHoverConsumption( const DispatchContext& context, LPARAM lParam, LRESULT& outResult) { - if (!App::CanConsumeEditorWindowChromeHover( + if (!CanConsumeEditorWindowChromeHover( context.window.GetPointerCaptureOwner(), context.window.GetShellRuntime().HasShellInteractiveCapture(), context.window.GetShellRuntime().HasHostedContentCapture())) { @@ -83,11 +84,11 @@ bool WindowMessageDispatcher::TryHandleChromeHoverConsumption( return true; } - const BorderlessWindowChromeHitTarget chromeHitTarget = + const Host::BorderlessWindowChromeHitTarget chromeHitTarget = context.window.HitTestBorderlessWindowChrome(lParam); - if (chromeHitTarget == BorderlessWindowChromeHitTarget::MinimizeButton || - chromeHitTarget == BorderlessWindowChromeHitTarget::MaximizeRestoreButton || - chromeHitTarget == BorderlessWindowChromeHitTarget::CloseButton) { + if (chromeHitTarget == Host::BorderlessWindowChromeHitTarget::MinimizeButton || + chromeHitTarget == Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton || + chromeHitTarget == Host::BorderlessWindowChromeHitTarget::CloseButton) { outResult = 0; return true; } @@ -95,7 +96,7 @@ bool WindowMessageDispatcher::TryHandleChromeHoverConsumption( return false; } -bool WindowMessageDispatcher::TryDispatchWindowPointerMessage( +bool EditorWindowMessageDispatcher::TryDispatchWindowPointerMessage( const DispatchContext& context, UINT message, WPARAM wParam, @@ -103,26 +104,26 @@ bool WindowMessageDispatcher::TryDispatchWindowPointerMessage( LRESULT& outResult) { switch (message) { case WM_MOUSEMOVE: - if (App::CanRouteEditorWindowGlobalTabDragPointerMessages( + if (CanRouteEditorWindowGlobalTabDragPointerMessages( context.window.GetPointerCaptureOwner(), - context.windowHost.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) && - context.windowHost.HandleGlobalTabDragPointerMove(context.hwnd)) { + context.workspaceCoordinator.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) && + context.workspaceCoordinator.HandleGlobalTabDragPointerMove(context.hwnd)) { outResult = 0; return true; } - if (App::CanRouteEditorWindowBorderlessResizePointerMessages( + if (CanRouteEditorWindowBorderlessResizePointerMessages( context.window.GetPointerCaptureOwner()) && context.window.HandleBorderlessWindowResizePointerMove( - context.windowHost.GetEditorContext(), - context.windowHost.IsGlobalTabDragActive())) { + context.hostRuntime.GetEditorContext(), + context.workspaceCoordinator.IsGlobalTabDragActive())) { outResult = 0; return true; } - if (App::CanRouteEditorWindowBorderlessChromePointerMessages( + if (CanRouteEditorWindowBorderlessChromePointerMessages( context.window.GetPointerCaptureOwner()) && context.window.HandleBorderlessWindowChromeDragRestorePointerMove( - context.windowHost.GetEditorContext(), - context.windowHost.IsGlobalTabDragActive())) { + context.hostRuntime.GetEditorContext(), + context.workspaceCoordinator.IsGlobalTabDragActive())) { outResult = 0; return true; } @@ -146,8 +147,8 @@ bool WindowMessageDispatcher::TryDispatchWindowPointerMessage( outResult = 0; return true; case WM_LBUTTONDOWN: - if (context.windowHost.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) { - context.windowHost.EndGlobalTabDragSession(); + if (context.workspaceCoordinator.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) { + context.workspaceCoordinator.EndGlobalTabDragSession(); } if (context.window.HandleBorderlessWindowResizeButtonDown(lParam)) { outResult = 0; @@ -187,24 +188,24 @@ bool WindowMessageDispatcher::TryDispatchWindowPointerMessage( outResult = 0; return true; case WM_LBUTTONUP: - if (App::CanRouteEditorWindowGlobalTabDragPointerMessages( + if (CanRouteEditorWindowGlobalTabDragPointerMessages( context.window.GetPointerCaptureOwner(), - context.windowHost.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) && - context.windowHost.HandleGlobalTabDragPointerButtonUp(context.hwnd)) { + context.workspaceCoordinator.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) && + context.workspaceCoordinator.HandleGlobalTabDragPointerButtonUp(context.hwnd)) { outResult = 0; return true; } - if (App::CanRouteEditorWindowBorderlessResizePointerMessages( + if (CanRouteEditorWindowBorderlessResizePointerMessages( context.window.GetPointerCaptureOwner()) && context.window.HandleBorderlessWindowResizeButtonUp()) { outResult = 0; return true; } - if (App::CanRouteEditorWindowBorderlessChromePointerMessages( + if (CanRouteEditorWindowBorderlessChromePointerMessages( context.window.GetPointerCaptureOwner()) && context.window.HandleBorderlessWindowChromeButtonUp( - context.windowHost.GetEditorContext(), - context.windowHost.IsGlobalTabDragActive(), + context.hostRuntime.GetEditorContext(), + context.workspaceCoordinator.IsGlobalTabDragActive(), lParam)) { outResult = 0; return true; @@ -234,8 +235,8 @@ bool WindowMessageDispatcher::TryDispatchWindowPointerMessage( return true; case WM_LBUTTONDBLCLK: if (context.window.HandleBorderlessWindowChromeDoubleClick( - context.windowHost.GetEditorContext(), - context.windowHost.IsGlobalTabDragActive(), + context.hostRuntime.GetEditorContext(), + context.workspaceCoordinator.IsGlobalTabDragActive(), lParam)) { outResult = 0; return true; @@ -278,7 +279,7 @@ bool WindowMessageDispatcher::TryDispatchWindowPointerMessage( } } -bool WindowMessageDispatcher::TryDispatchWindowInputMessage( +bool EditorWindowMessageDispatcher::TryDispatchWindowInputMessage( const DispatchContext& context, UINT message, WPARAM wParam, @@ -303,9 +304,9 @@ bool WindowMessageDispatcher::TryDispatchWindowInputMessage( if (reinterpret_cast(lParam) != context.hwnd) { context.window.ClearPointerCaptureOwner(); } - if (context.windowHost.OwnsActiveGlobalTabDrag(context.window.GetWindowId()) && + if (context.workspaceCoordinator.OwnsActiveGlobalTabDrag(context.window.GetWindowId()) && reinterpret_cast(lParam) != context.hwnd) { - context.windowHost.EndGlobalTabDragSession(); + context.workspaceCoordinator.EndGlobalTabDragSession(); outResult = 0; return true; } @@ -346,7 +347,7 @@ bool WindowMessageDispatcher::TryDispatchWindowInputMessage( } } -bool WindowMessageDispatcher::TryDispatchWindowLifecycleMessage( +bool EditorWindowMessageDispatcher::TryDispatchWindowLifecycleMessage( const DispatchContext& context, UINT message, WPARAM wParam, @@ -382,11 +383,11 @@ bool WindowMessageDispatcher::TryDispatchWindowLifecycleMessage( outResult = 0; return true; case WM_PAINT: - if (App::EditorWindowFrameTransferRequests transferRequests = context.window.OnPaintMessage( - context.windowHost.GetEditorContext(), - context.windowHost.IsGlobalTabDragActive()); + if (EditorWindowFrameTransferRequests transferRequests = context.window.OnPaintMessage( + context.hostRuntime.GetEditorContext(), + context.workspaceCoordinator.IsGlobalTabDragActive()); transferRequests.HasPendingRequests()) { - context.windowHost.HandleWindowFrameTransferRequests( + context.workspaceCoordinator.HandleWindowFrameTransferRequests( context.window, std::move(transferRequests)); } @@ -396,10 +397,11 @@ bool WindowMessageDispatcher::TryDispatchWindowLifecycleMessage( outResult = 1; return true; case WM_DESTROY: - if (context.windowHost.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) { - context.windowHost.EndGlobalTabDragSession(); + if (context.workspaceCoordinator.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) { + context.workspaceCoordinator.EndGlobalTabDragSession(); } - context.windowHost.HandleDestroyedWindow(context.hwnd); + context.workspaceCoordinator.HandleWindowDestroyed(context.window); + context.hostRuntime.HandleDestroyedWindow(context.hwnd); outResult = 0; return true; default: @@ -407,7 +409,7 @@ bool WindowMessageDispatcher::TryDispatchWindowLifecycleMessage( } } -bool WindowMessageDispatcher::TryDispatchWindowChromeMessage( +bool EditorWindowMessageDispatcher::TryDispatchWindowChromeMessage( const DispatchContext& context, UINT message, WPARAM wParam, @@ -449,8 +451,8 @@ bool WindowMessageDispatcher::TryDispatchWindowChromeMessage( return false; case WM_SYSCOMMAND: if (context.window.HandleBorderlessWindowSystemCommand( - context.windowHost.GetEditorContext(), - context.windowHost.IsGlobalTabDragActive(), + context.hostRuntime.GetEditorContext(), + context.workspaceCoordinator.IsGlobalTabDragActive(), wParam)) { outResult = 0; return true; @@ -467,17 +469,19 @@ bool WindowMessageDispatcher::TryDispatchWindowChromeMessage( } } -bool WindowMessageDispatcher::TryDispatch( +bool EditorWindowMessageDispatcher::TryDispatch( HWND hwnd, - WindowMessageHost& windowHost, - App::EditorWindow& window, + EditorWindowHostRuntime& hostRuntime, + EditorWindowWorkspaceCoordinator& workspaceCoordinator, + EditorWindow& window, UINT message, WPARAM wParam, LPARAM lParam, - LRESULT& outResult) { + LRESULT& outResult) { const DispatchContext context = { .hwnd = hwnd, - .windowHost = windowHost, + .hostRuntime = hostRuntime, + .workspaceCoordinator = workspaceCoordinator, .window = window, }; @@ -486,4 +490,4 @@ bool WindowMessageDispatcher::TryDispatch( TryDispatchWindowInputMessage(context, message, wParam, lParam, outResult); } -} // namespace XCEngine::UI::Editor::Host +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/WindowMessageDispatcher.h b/new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.h similarity index 77% rename from new_editor/app/Platform/Win32/WindowMessageDispatcher.h rename to new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.h index af8372dd..341ddce1 100644 --- a/new_editor/app/Platform/Win32/WindowMessageDispatcher.h +++ b/new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.h @@ -6,22 +6,22 @@ #include -namespace XCEngine::UI::Editor { -namespace App { +namespace XCEngine::UI::Editor::App { class EditorWindow; } -} -namespace XCEngine::UI::Editor::Host { +namespace XCEngine::UI::Editor::App::Internal { -class WindowMessageHost; +class EditorWindowHostRuntime; +class EditorWindowWorkspaceCoordinator; -class WindowMessageDispatcher { +class EditorWindowMessageDispatcher final { public: static bool TryDispatch( HWND hwnd, - WindowMessageHost& windowHost, - App::EditorWindow& window, + EditorWindowHostRuntime& hostRuntime, + EditorWindowWorkspaceCoordinator& workspaceCoordinator, + EditorWindow& window, UINT message, WPARAM wParam, LPARAM lParam, @@ -62,4 +62,4 @@ private: LRESULT& outResult); }; -} // namespace XCEngine::UI::Editor::Host +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/WindowManager/Internal.h b/new_editor/app/Platform/Win32/WindowManager/Internal.h index db595d6c..c30727d1 100644 --- a/new_editor/app/Platform/Win32/WindowManager/Internal.h +++ b/new_editor/app/Platform/Win32/WindowManager/Internal.h @@ -1,7 +1,7 @@ #pragma once +#include "Composition/EditorWindowWorkspaceStore.h" #include "Platform/Win32/EditorWindowManager.h" -#include "Platform/Win32/WindowManager/EditorWindowWorkspaceStore.h" namespace XCEngine::UI::Editor::App::Internal { diff --git a/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp b/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp index 420a58b3..d0971cc0 100644 --- a/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp @@ -1,7 +1,8 @@ #include "Platform/Win32/WindowManager/Internal.h" +#include "Platform/Win32/WindowManager/EditorWindowMessageDispatcher.h" #include "Bootstrap/EditorResources.h" -#include "State/EditorContext.h" +#include "Composition/EditorContext.h" #include "Platform/Win32/EditorWindow.h" #include @@ -47,6 +48,32 @@ void EditorWindowManager::Shutdown() { m_hostRuntime->Shutdown(); } +bool EditorWindowManager::TryDispatchWindowMessage( + HWND hwnd, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult) { + if (m_hostRuntime == nullptr || m_workspaceCoordinator == nullptr) { + return false; + } + + EditorWindow* const window = m_hostRuntime->FindWindow(hwnd); + if (window == nullptr) { + return false; + } + + return Internal::EditorWindowMessageDispatcher::TryDispatch( + hwnd, + *m_hostRuntime, + *m_workspaceCoordinator, + *window, + message, + wParam, + lParam, + outResult); +} + EditorWindow* EditorWindowManager::FindWindow(HWND hwnd) { return m_hostRuntime->FindWindow(hwnd); } @@ -85,41 +112,6 @@ void EditorWindowManager::RenderAllWindows() { *m_workspaceCoordinator); } -bool EditorWindowManager::IsGlobalTabDragActive() const { - return m_workspaceCoordinator->IsGlobalTabDragActive(); -} - -bool EditorWindowManager::OwnsActiveGlobalTabDrag(std::string_view windowId) const { - return m_workspaceCoordinator->OwnsActiveGlobalTabDrag(windowId); -} - -void EditorWindowManager::EndGlobalTabDragSession() { - m_workspaceCoordinator->EndGlobalTabDragSession(); -} - -void EditorWindowManager::HandleDestroyedWindow(HWND hwnd) { - if (EditorWindow* window = m_hostRuntime->FindWindow(hwnd); window != nullptr) { - m_workspaceCoordinator->HandleWindowDestroyed(*window); - } - m_hostRuntime->HandleDestroyedWindow(hwnd); -} - -void EditorWindowManager::HandleWindowFrameTransferRequests( - EditorWindow& sourceWindow, - EditorWindowFrameTransferRequests&& transferRequests) { - m_workspaceCoordinator->HandleWindowFrameTransferRequests( - sourceWindow, - std::move(transferRequests)); -} - -bool EditorWindowManager::HandleGlobalTabDragPointerMove(HWND hwnd) { - return m_workspaceCoordinator->HandleGlobalTabDragPointerMove(hwnd); -} - -bool EditorWindowManager::HandleGlobalTabDragPointerButtonUp(HWND hwnd) { - return m_workspaceCoordinator->HandleGlobalTabDragPointerButtonUp(hwnd); -} - } // namespace XCEngine::UI::Editor::App namespace XCEngine::UI::Editor::App::Internal { diff --git a/new_editor/app/Platform/Win32/WindowManager/TabDrag.cpp b/new_editor/app/Platform/Win32/WindowManager/TabDrag.cpp index 91798bef..c91f90e2 100644 --- a/new_editor/app/Platform/Win32/WindowManager/TabDrag.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/TabDrag.cpp @@ -1,6 +1,7 @@ #include "Platform/Win32/WindowManager/Internal.h" +#include "Platform/Win32/WindowManager/TabDragDropTarget.h" -#include "State/EditorContext.h" +#include "Composition/EditorContext.h" #include "Platform/Win32/EditorWindow.h" #include @@ -21,13 +22,6 @@ using ::XCEngine::UI::UIRect; constexpr LONG kFallbackDragHotspotX = 40; constexpr LONG kFallbackDragHotspotY = 12; -struct CrossWindowDockDropTarget { - bool valid = false; - std::string nodeId = {}; - UIEditorWorkspaceDockPlacement placement = UIEditorWorkspaceDockPlacement::Center; - std::size_t insertionIndex = Widgets::UIEditorTabStripInvalidIndex; -}; - POINT BuildFallbackGlobalTabDragHotspot() { POINT hotspot = {}; hotspot.x = kFallbackDragHotspotX; @@ -44,13 +38,6 @@ float ResolveWindowDpiScale(HWND hwnd) { return dpi == 0u ? 1.0f : static_cast(dpi) / 96.0f; } -bool IsPointInsideRect(const UIRect& rect, const UIPoint& point) { - return point.x >= rect.x && - point.x <= rect.x + rect.width && - point.y >= rect.y && - point.y <= rect.y + rect.height; -} - bool CanStartGlobalTabDragFromWindow( const EditorWindow& sourceWindow, std::string_view sourceNodeId, @@ -74,100 +61,6 @@ bool IsLiveInteractiveWindow(const EditorWindow* window) { !window->IsClosing(); } -std::size_t ResolveDropInsertionIndex( - const Widgets::UIEditorDockHostTabStackLayout& tabStack, - const UIPoint& point) { - if (!IsPointInsideRect(tabStack.tabStripLayout.headerRect, point)) { - return Widgets::UIEditorTabStripInvalidIndex; - } - - std::size_t insertionIndex = 0u; - for (const UIRect& rect : tabStack.tabStripLayout.tabHeaderRects) { - const float midpoint = rect.x + rect.width * 0.5f; - if (point.x > midpoint) { - ++insertionIndex; - } - } - return insertionIndex; -} - -UIEditorWorkspaceDockPlacement ResolveDropPlacement( - const Widgets::UIEditorDockHostTabStackLayout& tabStack, - const UIPoint& point) { - if (IsPointInsideRect(tabStack.tabStripLayout.headerRect, point)) { - return UIEditorWorkspaceDockPlacement::Center; - } - - const float leftDistance = point.x - tabStack.bounds.x; - const float rightDistance = tabStack.bounds.x + tabStack.bounds.width - point.x; - const float topDistance = point.y - tabStack.bounds.y; - const float bottomDistance = tabStack.bounds.y + tabStack.bounds.height - point.y; - const float minHorizontalThreshold = tabStack.bounds.width * 0.25f; - const float minVerticalThreshold = tabStack.bounds.height * 0.25f; - const float nearestEdge = (std::min)( - (std::min)(leftDistance, rightDistance), - (std::min)(topDistance, bottomDistance)); - - if (nearestEdge == leftDistance && leftDistance <= minHorizontalThreshold) { - return UIEditorWorkspaceDockPlacement::Left; - } - if (nearestEdge == rightDistance && rightDistance <= minHorizontalThreshold) { - return UIEditorWorkspaceDockPlacement::Right; - } - if (nearestEdge == topDistance && topDistance <= minVerticalThreshold) { - return UIEditorWorkspaceDockPlacement::Top; - } - if (nearestEdge == bottomDistance && bottomDistance <= minVerticalThreshold) { - return UIEditorWorkspaceDockPlacement::Bottom; - } - - return UIEditorWorkspaceDockPlacement::Center; -} - -bool TryResolveCrossWindowDockDropTarget( - const Widgets::UIEditorDockHostLayout& layout, - const UIPoint& point, - CrossWindowDockDropTarget& outTarget) { - outTarget = {}; - if (!IsPointInsideRect(layout.bounds, point)) { - return false; - } - - const Widgets::UIEditorDockHostHitTarget hitTarget = - Widgets::HitTestUIEditorDockHost(layout, point); - for (const Widgets::UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { - if ((!hitTarget.nodeId.empty() && tabStack.nodeId != hitTarget.nodeId) || - !IsPointInsideRect(tabStack.bounds, point)) { - continue; - } - - outTarget.valid = true; - outTarget.nodeId = tabStack.nodeId; - outTarget.placement = ResolveDropPlacement(tabStack, point); - if (outTarget.placement == UIEditorWorkspaceDockPlacement::Center) { - outTarget.insertionIndex = ResolveDropInsertionIndex(tabStack, point); - if (outTarget.insertionIndex == Widgets::UIEditorTabStripInvalidIndex) { - outTarget.insertionIndex = tabStack.items.size(); - } - } - return true; - } - - for (const Widgets::UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { - if (IsPointInsideRect(tabStack.bounds, point)) { - outTarget.valid = true; - outTarget.nodeId = tabStack.nodeId; - outTarget.placement = ResolveDropPlacement(tabStack, point); - if (outTarget.placement == UIEditorWorkspaceDockPlacement::Center) { - outTarget.insertionIndex = tabStack.items.size(); - } - return true; - } - } - - return false; -} - } // namespace bool EditorWindowWorkspaceCoordinator::IsGlobalTabDragActive() const { @@ -303,9 +196,9 @@ void EditorWindowWorkspaceCoordinator::UpdateGlobalTabDragDropPreview() { targetWindow->ConvertScreenPixelsToClientDips(m_globalTabDragSession.screenPoint); const Widgets::UIEditorDockHostLayout& targetLayout = targetWindow->GetShellFrame().workspaceInteractionFrame.dockHostFrame.layout; - CrossWindowDockDropTarget dropTarget = {}; - if (!TryResolveCrossWindowDockDropTarget(targetLayout, targetPoint, dropTarget) || - !dropTarget.valid) { + const EditorWindowTabDragDropTarget dropTarget = + ResolveEditorWindowTabDragDropTarget(targetLayout, targetPoint); + if (!dropTarget.valid) { ClearGlobalTabDragDropPreview(); return; } @@ -399,8 +292,9 @@ bool EditorWindowWorkspaceCoordinator::HandleGlobalTabDragPointerButtonUp(HWND h targetWindow->ConvertScreenPixelsToClientDips(screenPoint); const Widgets::UIEditorDockHostLayout& targetLayout = targetWindow->GetShellFrame().workspaceInteractionFrame.dockHostFrame.layout; - CrossWindowDockDropTarget dropTarget = {}; - if (!TryResolveCrossWindowDockDropTarget(targetLayout, targetPoint, dropTarget)) { + const EditorWindowTabDragDropTarget dropTarget = + ResolveEditorWindowTabDragDropTarget(targetLayout, targetPoint); + if (!dropTarget.valid) { return true; } diff --git a/new_editor/app/Platform/Win32/WindowManager/TabDragDropTarget.cpp b/new_editor/app/Platform/Win32/WindowManager/TabDragDropTarget.cpp new file mode 100644 index 00000000..79bf4d1a --- /dev/null +++ b/new_editor/app/Platform/Win32/WindowManager/TabDragDropTarget.cpp @@ -0,0 +1,116 @@ +#include "Platform/Win32/WindowManager/TabDragDropTarget.h" + +#include + +namespace XCEngine::UI::Editor::App::Internal { + +namespace { + +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; + +bool IsPointInsideRect(const UIRect& rect, const UIPoint& point) { + return point.x >= rect.x && + point.x <= rect.x + rect.width && + point.y >= rect.y && + point.y <= rect.y + rect.height; +} + +std::size_t ResolveDropInsertionIndex( + const Widgets::UIEditorDockHostTabStackLayout& tabStack, + const UIPoint& point) { + if (!IsPointInsideRect(tabStack.tabStripLayout.headerRect, point)) { + return Widgets::UIEditorTabStripInvalidIndex; + } + + std::size_t insertionIndex = 0u; + for (const UIRect& rect : tabStack.tabStripLayout.tabHeaderRects) { + const float midpoint = rect.x + rect.width * 0.5f; + if (point.x > midpoint) { + ++insertionIndex; + } + } + return insertionIndex; +} + +UIEditorWorkspaceDockPlacement ResolveDropPlacement( + const Widgets::UIEditorDockHostTabStackLayout& tabStack, + const UIPoint& point) { + if (IsPointInsideRect(tabStack.tabStripLayout.headerRect, point)) { + return UIEditorWorkspaceDockPlacement::Center; + } + + const float leftDistance = point.x - tabStack.bounds.x; + const float rightDistance = tabStack.bounds.x + tabStack.bounds.width - point.x; + const float topDistance = point.y - tabStack.bounds.y; + const float bottomDistance = tabStack.bounds.y + tabStack.bounds.height - point.y; + const float minHorizontalThreshold = tabStack.bounds.width * 0.25f; + const float minVerticalThreshold = tabStack.bounds.height * 0.25f; + const float nearestEdge = (std::min)( + (std::min)(leftDistance, rightDistance), + (std::min)(topDistance, bottomDistance)); + + if (nearestEdge == leftDistance && leftDistance <= minHorizontalThreshold) { + return UIEditorWorkspaceDockPlacement::Left; + } + if (nearestEdge == rightDistance && rightDistance <= minHorizontalThreshold) { + return UIEditorWorkspaceDockPlacement::Right; + } + if (nearestEdge == topDistance && topDistance <= minVerticalThreshold) { + return UIEditorWorkspaceDockPlacement::Top; + } + if (nearestEdge == bottomDistance && bottomDistance <= minVerticalThreshold) { + return UIEditorWorkspaceDockPlacement::Bottom; + } + + return UIEditorWorkspaceDockPlacement::Center; +} + +} // namespace + +EditorWindowTabDragDropTarget ResolveEditorWindowTabDragDropTarget( + const Widgets::UIEditorDockHostLayout& layout, + const UIPoint& point) { + EditorWindowTabDragDropTarget result = {}; + if (!IsPointInsideRect(layout.bounds, point)) { + return result; + } + + const Widgets::UIEditorDockHostHitTarget hitTarget = + Widgets::HitTestUIEditorDockHost(layout, point); + for (const Widgets::UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { + if ((!hitTarget.nodeId.empty() && tabStack.nodeId != hitTarget.nodeId) || + !IsPointInsideRect(tabStack.bounds, point)) { + continue; + } + + result.valid = true; + result.nodeId = tabStack.nodeId; + result.placement = ResolveDropPlacement(tabStack, point); + if (result.placement == UIEditorWorkspaceDockPlacement::Center) { + result.insertionIndex = ResolveDropInsertionIndex(tabStack, point); + if (result.insertionIndex == Widgets::UIEditorTabStripInvalidIndex) { + result.insertionIndex = tabStack.items.size(); + } + } + return result; + } + + for (const Widgets::UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { + if (!IsPointInsideRect(tabStack.bounds, point)) { + continue; + } + + result.valid = true; + result.nodeId = tabStack.nodeId; + result.placement = ResolveDropPlacement(tabStack, point); + if (result.placement == UIEditorWorkspaceDockPlacement::Center) { + result.insertionIndex = tabStack.items.size(); + } + return result; + } + + return result; +} + +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/WindowManager/TabDragDropTarget.h b/new_editor/app/Platform/Win32/WindowManager/TabDragDropTarget.h new file mode 100644 index 00000000..bc4f2aee --- /dev/null +++ b/new_editor/app/Platform/Win32/WindowManager/TabDragDropTarget.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include + +#include +#include + +namespace XCEngine::UI::Editor::App::Internal { + +struct EditorWindowTabDragDropTarget { + bool valid = false; + std::string nodeId = {}; + UIEditorWorkspaceDockPlacement placement = UIEditorWorkspaceDockPlacement::Center; + std::size_t insertionIndex = Widgets::UIEditorTabStripInvalidIndex; +}; + +EditorWindowTabDragDropTarget ResolveEditorWindowTabDragDropTarget( + const Widgets::UIEditorDockHostLayout& layout, + const ::XCEngine::UI::UIPoint& point); + +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/WindowManager/WindowSync.cpp b/new_editor/app/Platform/Win32/WindowManager/WindowSync.cpp index fdfec0da..c0f49281 100644 --- a/new_editor/app/Platform/Win32/WindowManager/WindowSync.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/WindowSync.cpp @@ -1,6 +1,6 @@ #include "Platform/Win32/WindowManager/Internal.h" -#include "State/EditorContext.h" +#include "Composition/EditorContext.h" #include "Platform/Win32/EditorWindow.h" #include diff --git a/new_editor/app/Platform/Win32/WindowMessageHost.h b/new_editor/app/Platform/Win32/WindowMessageHost.h deleted file mode 100644 index 7b2e432d..00000000 --- a/new_editor/app/Platform/Win32/WindowMessageHost.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include - -#include - -namespace XCEngine::UI::Editor::App { -class EditorContext; -class EditorWindow; -struct EditorWindowFrameTransferRequests; -} - -namespace XCEngine::UI::Editor::Host { - -class WindowMessageHost { -public: - virtual ~WindowMessageHost() = default; - - virtual App::EditorContext& GetEditorContext() = 0; - virtual const App::EditorContext& GetEditorContext() const = 0; - virtual bool IsGlobalTabDragActive() const = 0; - virtual bool OwnsActiveGlobalTabDrag(std::string_view windowId) const = 0; - virtual void EndGlobalTabDragSession() = 0; - virtual void HandleDestroyedWindow(HWND hwnd) = 0; - virtual void HandleWindowFrameTransferRequests( - App::EditorWindow& window, - App::EditorWindowFrameTransferRequests&& transferRequests) = 0; - virtual bool HandleGlobalTabDragPointerMove(HWND hwnd) = 0; - virtual bool HandleGlobalTabDragPointerButtonUp(HWND hwnd) = 0; -}; - -} // namespace XCEngine::UI::Editor::Host - diff --git a/new_editor/app/Ports/PortFwd.h b/new_editor/app/Ports/PortFwd.h new file mode 100644 index 00000000..f659d2c4 --- /dev/null +++ b/new_editor/app/Ports/PortFwd.h @@ -0,0 +1,10 @@ +#pragma once + +namespace XCEngine::UI::Editor::Ports { + +class ShaderResourceDescriptorAllocatorPort; +class SystemInteractionPort; +class TexturePort; +class ViewportRenderPort; + +} // namespace XCEngine::UI::Editor::Ports diff --git a/new_editor/app/Host/ShaderResourceDescriptorAllocator.h b/new_editor/app/Ports/ShaderResourceDescriptorAllocatorPort.h similarity index 77% rename from new_editor/app/Host/ShaderResourceDescriptorAllocator.h rename to new_editor/app/Ports/ShaderResourceDescriptorAllocatorPort.h index 035520c1..caaf8310 100644 --- a/new_editor/app/Host/ShaderResourceDescriptorAllocator.h +++ b/new_editor/app/Ports/ShaderResourceDescriptorAllocatorPort.h @@ -9,11 +9,11 @@ #include -namespace XCEngine::UI::Editor::Host { +namespace XCEngine::UI::Editor::Ports { -class ShaderResourceDescriptorAllocator { +class ShaderResourceDescriptorAllocatorPort { public: - virtual ~ShaderResourceDescriptorAllocator() = default; + virtual ~ShaderResourceDescriptorAllocatorPort() = default; virtual bool Initialize(::XCEngine::RHI::RHIDevice& device, UINT descriptorCount = 64u) = 0; virtual void Shutdown() = 0; @@ -28,4 +28,4 @@ public: D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle) = 0; }; -} // namespace XCEngine::UI::Editor::Host +} // namespace XCEngine::UI::Editor::Ports diff --git a/new_editor/app/Ports/SystemInteractionPort.h b/new_editor/app/Ports/SystemInteractionPort.h new file mode 100644 index 00000000..8806d840 --- /dev/null +++ b/new_editor/app/Ports/SystemInteractionPort.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +namespace XCEngine::UI::Editor::Ports { + +class SystemInteractionPort { +public: + virtual ~SystemInteractionPort() = default; + + virtual bool CopyTextToClipboard(std::string_view text) = 0; + virtual bool RevealPathInFileBrowser( + const std::filesystem::path& path, + bool selectTarget) = 0; +}; + +} // namespace XCEngine::UI::Editor::Ports diff --git a/new_editor/app/Host/TextureHost.h b/new_editor/app/Ports/TexturePort.h similarity index 80% rename from new_editor/app/Host/TextureHost.h rename to new_editor/app/Ports/TexturePort.h index 344c96d7..c12deca5 100644 --- a/new_editor/app/Host/TextureHost.h +++ b/new_editor/app/Ports/TexturePort.h @@ -7,11 +7,11 @@ #include #include -namespace XCEngine::UI::Editor::Host { +namespace XCEngine::UI::Editor::Ports { -class TextureHost { +class TexturePort { public: - virtual ~TextureHost() = default; + virtual ~TexturePort() = default; virtual bool LoadTextureFromFile( const std::filesystem::path& path, @@ -25,4 +25,4 @@ public: virtual void ReleaseTexture(::XCEngine::UI::UITextureHandle& texture) = 0; }; -} // namespace XCEngine::UI::Editor::Host +} // namespace XCEngine::UI::Editor::Ports diff --git a/new_editor/app/Ports/ViewportRenderPort.h b/new_editor/app/Ports/ViewportRenderPort.h new file mode 100644 index 00000000..30a5680b --- /dev/null +++ b/new_editor/app/Ports/ViewportRenderPort.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +namespace XCEngine::UI::Editor::Ports { + +class ViewportRenderPort { +public: + virtual ~ViewportRenderPort() = default; + + [[nodiscard]] virtual ::XCEngine::RHI::RHIDevice* GetRHIDevice() const = 0; + virtual bool CreateViewportTextureHandle( + ::XCEngine::RHI::RHITexture& texture, + std::uint32_t width, + std::uint32_t height, + ::XCEngine::UI::UITextureHandle& outTexture) = 0; + virtual void ReleaseViewportTextureHandle( + ::XCEngine::UI::UITextureHandle& texture) = 0; +}; + +} // namespace XCEngine::UI::Editor::Ports diff --git a/new_editor/app/Rendering/Assets/BuiltInIcons.cpp b/new_editor/app/Rendering/Assets/BuiltInIcons.cpp index f11ea178..da2f6607 100644 --- a/new_editor/app/Rendering/Assets/BuiltInIcons.cpp +++ b/new_editor/app/Rendering/Assets/BuiltInIcons.cpp @@ -1,6 +1,6 @@ #include "BuiltInIcons.h" #include "Bootstrap/EditorResources.h" -#include "Host/TextureHost.h" +#include "Ports/TexturePort.h" #include "Internal/EmbeddedPngLoader.h" #include @@ -24,7 +24,7 @@ void AppendLoadError( } void LoadEmbeddedIconTexture( - Host::TextureHost& renderer, + Ports::TexturePort& renderer, UINT resourceId, std::string_view label, ::XCEngine::UI::UITextureHandle& outTexture, @@ -37,7 +37,7 @@ void LoadEmbeddedIconTexture( } // namespace -void BuiltInIcons::Initialize(Host::TextureHost& renderer) { +void BuiltInIcons::Initialize(Ports::TexturePort& renderer) { Shutdown(); m_renderer = &renderer; diff --git a/new_editor/app/Rendering/Assets/BuiltInIcons.h b/new_editor/app/Rendering/Assets/BuiltInIcons.h index c67a1f59..12a26404 100644 --- a/new_editor/app/Rendering/Assets/BuiltInIcons.h +++ b/new_editor/app/Rendering/Assets/BuiltInIcons.h @@ -1,6 +1,6 @@ #pragma once -#include "Host/HostFwd.h" +#include "Ports/PortFwd.h" #include @@ -17,14 +17,14 @@ enum class BuiltInIconKind : std::uint8_t { class BuiltInIcons { public: - void Initialize(Host::TextureHost& renderer); + void Initialize(Ports::TexturePort& renderer); void Shutdown(); const ::XCEngine::UI::UITextureHandle& Resolve(BuiltInIconKind kind) const; const std::string& GetLastError() const; private: - Host::TextureHost* m_renderer = nullptr; + Ports::TexturePort* m_renderer = nullptr; ::XCEngine::UI::UITextureHandle m_folderIcon = {}; ::XCEngine::UI::UITextureHandle m_gameObjectIcon = {}; ::XCEngine::UI::UITextureHandle m_sceneIcon = {}; diff --git a/new_editor/app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.h b/new_editor/app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.h index 6ddeed82..253bc616 100644 --- a/new_editor/app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.h +++ b/new_editor/app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.h @@ -4,7 +4,7 @@ #define NOMINMAX #endif -#include "Host/ShaderResourceDescriptorAllocator.h" +#include "Ports/ShaderResourceDescriptorAllocatorPort.h" #include #include @@ -20,7 +20,8 @@ namespace XCEngine::UI::Editor::Host { -class D3D12ShaderResourceDescriptorAllocator : public ShaderResourceDescriptorAllocator { +class D3D12ShaderResourceDescriptorAllocator + : public Ports::ShaderResourceDescriptorAllocatorPort { public: bool Initialize(::XCEngine::RHI::RHIDevice& device, UINT descriptorCount = 64u) override; void Shutdown() override; diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp index 24874056..6ee11af7 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp +++ b/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp @@ -25,11 +25,21 @@ bool D3D12WindowRenderer::Initialize(HWND hwnd, int width, int height) { return false; } + auto* device = m_hostDevice.GetRHIDevice(); + if (device == nullptr || + !m_viewportTextureAllocator.Initialize(*device)) { + m_lastError = "Failed to initialize the viewport texture allocator."; + Shutdown(); + return false; + } + m_lastError.clear(); return true; } void D3D12WindowRenderer::Shutdown() { + m_viewportTextureCpuHandles.clear(); + m_viewportTextureAllocator.Shutdown(); m_presenter.Shutdown(); m_hostDevice.Shutdown(); m_activeBackBufferIndex = 0u; @@ -132,6 +142,56 @@ RHIDevice* D3D12WindowRenderer::GetRHIDevice() const { return m_hostDevice.GetRHIDevice(); } +bool D3D12WindowRenderer::CreateViewportTextureHandle( + ::XCEngine::RHI::RHITexture& texture, + std::uint32_t width, + std::uint32_t height, + ::XCEngine::UI::UITextureHandle& outTexture) { + outTexture = {}; + if (width == 0u || + height == 0u || + !m_viewportTextureAllocator.IsInitialized()) { + return false; + } + + D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = {}; + D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle = {}; + if (!m_viewportTextureAllocator.CreateTextureDescriptor( + &texture, + &cpuHandle, + &gpuHandle) || + cpuHandle.ptr == 0u || + gpuHandle.ptr == 0u) { + return false; + } + + outTexture.nativeHandle = static_cast(gpuHandle.ptr); + outTexture.width = width; + outTexture.height = height; + outTexture.kind = ::XCEngine::UI::UITextureHandleKind::ShaderResourceView; + outTexture.resourceHandle = reinterpret_cast(&texture); + m_viewportTextureCpuHandles[outTexture.nativeHandle] = cpuHandle; + return true; +} + +void D3D12WindowRenderer::ReleaseViewportTextureHandle( + ::XCEngine::UI::UITextureHandle& texture) { + if (!texture.IsValid()) { + texture = {}; + return; + } + + const auto found = m_viewportTextureCpuHandles.find(texture.nativeHandle); + if (found != m_viewportTextureCpuHandles.end()) { + D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle = {}; + gpuHandle.ptr = static_cast(texture.nativeHandle); + m_viewportTextureAllocator.Free(found->second, gpuHandle); + m_viewportTextureCpuHandles.erase(found); + } + + texture = {}; +} + RHISwapChain* D3D12WindowRenderer::GetSwapChain() const { return m_presenter.GetSwapChain(); } diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h b/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h index 853e2b14..ad9f8ab2 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h +++ b/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h @@ -4,8 +4,9 @@ #define NOMINMAX #endif -#include "Host/ViewportRenderHost.h" +#include "Ports/ViewportRenderPort.h" #include "D3D12HostDevice.h" +#include "D3D12ShaderResourceDescriptorAllocator.h" #include "D3D12WindowSwapChainPresenter.h" #include @@ -16,10 +17,11 @@ #include #include +#include namespace XCEngine::UI::Editor::Host { -class D3D12WindowRenderer : public ViewportRenderHost { +class D3D12WindowRenderer : public Ports::ViewportRenderPort { public: static constexpr std::uint32_t kSwapChainBufferCount = D3D12WindowSwapChainPresenter::kSwapChainBufferCount; @@ -37,6 +39,13 @@ public: ID3D12CommandQueue* GetCommandQueue() const; const std::string& GetLastError() const; ::XCEngine::RHI::RHIDevice* GetRHIDevice() const override; + bool CreateViewportTextureHandle( + ::XCEngine::RHI::RHITexture& texture, + std::uint32_t width, + std::uint32_t height, + ::XCEngine::UI::UITextureHandle& outTexture) override; + void ReleaseViewportTextureHandle( + ::XCEngine::UI::UITextureHandle& texture) override; ::XCEngine::RHI::RHISwapChain* GetSwapChain() const; const ::XCEngine::Rendering::RenderSurface* GetCurrentRenderSurface() const; const ::XCEngine::RHI::D3D12Texture* GetCurrentBackBufferTexture() const; @@ -47,7 +56,9 @@ public: private: D3D12HostDevice m_hostDevice = {}; D3D12WindowSwapChainPresenter m_presenter = {}; + D3D12ShaderResourceDescriptorAllocator m_viewportTextureAllocator = {}; std::uint32_t m_activeBackBufferIndex = 0u; + std::unordered_map m_viewportTextureCpuHandles = {}; std::string m_lastError = {}; }; diff --git a/new_editor/app/Rendering/Native/NativeRenderer.h b/new_editor/app/Rendering/Native/NativeRenderer.h index 5ff11732..9ddaae02 100644 --- a/new_editor/app/Rendering/Native/NativeRenderer.h +++ b/new_editor/app/Rendering/Native/NativeRenderer.h @@ -4,7 +4,7 @@ #define NOMINMAX #endif -#include "Host/TextureHost.h" +#include "Ports/TexturePort.h" #include #include @@ -32,7 +32,7 @@ namespace XCEngine::UI::Editor::Host { class NativeRenderer - : public TextureHost + : public Ports::TexturePort , public ::XCEngine::UI::Editor::UIEditorTextMeasurer { public: bool Initialize(HWND hwnd); diff --git a/new_editor/app/Rendering/Viewport/SceneViewportRenderPlan.h b/new_editor/app/Rendering/Viewport/SceneViewportRenderPlan.h index fb6ada57..6bfd9d7f 100644 --- a/new_editor/app/Rendering/Viewport/SceneViewportRenderPlan.h +++ b/new_editor/app/Rendering/Viewport/SceneViewportRenderPlan.h @@ -1,8 +1,8 @@ #pragma once +#include "Rendering/Viewport/SceneViewportRenderRequest.h" #include "Rendering/Viewport/SceneViewportPassSpecs.h" #include "Rendering/Viewport/ViewportRenderTargets.h" -#include "Scene/EditorSceneRuntime.h" #include #include diff --git a/new_editor/app/Rendering/Viewport/SceneViewportRenderRequest.h b/new_editor/app/Rendering/Viewport/SceneViewportRenderRequest.h new file mode 100644 index 00000000..c5b8ab87 --- /dev/null +++ b/new_editor/app/Rendering/Viewport/SceneViewportRenderRequest.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace XCEngine::Components { + +class CameraComponent; +class Scene; + +} // namespace XCEngine::Components + +namespace XCEngine::UI::Editor::App { + +struct SceneViewportRenderRequest { + ::XCEngine::Components::Scene* scene = nullptr; + ::XCEngine::Components::CameraComponent* camera = nullptr; + std::vector selectedObjectIds = {}; + float orbitDistance = 0.0f; + bool debugSelectionMask = false; + + bool IsValid() const { + return scene != nullptr && camera != nullptr; + } +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Rendering/Viewport/SceneViewportRenderService.cpp b/new_editor/app/Rendering/Viewport/SceneViewportRenderService.cpp new file mode 100644 index 00000000..68f28552 --- /dev/null +++ b/new_editor/app/Rendering/Viewport/SceneViewportRenderService.cpp @@ -0,0 +1,178 @@ +#include "Rendering/Viewport/SceneViewportRenderService.h" + +#include + +#include + +namespace XCEngine::UI::Editor::App { + +namespace { + +ViewportRenderResult BuildFallbackResult( + std::string statusText, + float r, + float g, + float b, + float a) { + ViewportRenderResult result = {}; + result.rendered = false; + result.requiresFallbackClear = true; + result.statusText = std::move(statusText); + result.fallbackClearR = r; + result.fallbackClearG = g; + result.fallbackClearB = b; + result.fallbackClearA = a; + return result; +} + +void SetStatusIfEmpty( + std::string& statusText, + std::string_view message) { + if (statusText.empty()) { + statusText = std::string(message); + } +} + +} // namespace + +SceneViewportRenderService::SceneViewportRenderService() = default; + +SceneViewportRenderService::~SceneViewportRenderService() = default; + +ViewportResourceRequirements +SceneViewportRenderService::GetViewportResourceRequirements() { + ViewportResourceRequirements requirements = {}; + requirements.requiresDepthSampling = true; + requirements.requiresObjectIdSurface = true; + requirements.requiresSelectionMaskSurface = true; + return requirements; +} + +void SceneViewportRenderService::Shutdown() { + m_renderRequest = {}; + m_renderPassBundle.Shutdown(); + m_sceneRenderer.reset(); + m_device = nullptr; + m_lastTargets = nullptr; + m_lastRenderContext = {}; +} + +void SceneViewportRenderService::SetRenderRequest( + SceneViewportRenderRequest request) { + m_renderRequest = std::move(request); +} + +ViewportRenderResult SceneViewportRenderService::Render( + ViewportRenderTargets& targets, + ::XCEngine::RHI::RHIDevice& device, + const ::XCEngine::Rendering::RenderContext& renderContext) { + m_device = &device; + m_lastTargets = &targets; + m_lastRenderContext = renderContext; + + if (m_renderRequest.camera == nullptr) { + return BuildFallbackResult( + "Scene view camera is unavailable", + 0.18f, + 0.07f, + 0.07f, + 1.0f); + } + + if (m_renderRequest.scene == nullptr) { + return BuildFallbackResult( + "No active scene", + 0.07f, + 0.08f, + 0.10f, + 1.0f); + } + + EnsureSceneRenderer(); + + ::XCEngine::Rendering::RenderSurface surface = + BuildViewportColorSurface(targets); + std::vector<::XCEngine::Rendering::CameraFramePlan> plans = + m_sceneRenderer->BuildFramePlans( + *m_renderRequest.scene, + m_renderRequest.camera, + renderContext, + surface); + if (plans.empty()) { + return BuildFallbackResult( + "Scene renderer failed", + 0.18f, + 0.07f, + 0.07f, + 1.0f); + } + + ViewportRenderResult result = {}; + SceneViewportRenderPlanBuildResult renderPlan = + m_renderPassBundle.BuildRenderPlan(targets, m_renderRequest); + ApplySceneViewportRenderPlan( + targets, + renderPlan.plan, + plans.front()); + if (renderPlan.warningStatusText != nullptr) { + SetStatusIfEmpty(result.statusText, renderPlan.warningStatusText); + } + + if (!m_sceneRenderer->Render(plans)) { + return BuildFallbackResult( + "Scene renderer failed", + 0.18f, + 0.07f, + 0.07f, + 1.0f); + } + + MarkSceneViewportRenderSuccess( + targets, + renderPlan.plan, + plans.front()); + result.rendered = true; + return result; +} + +ViewportObjectIdPickResult SceneViewportRenderService::PickObject( + const ::XCEngine::UI::UISize& viewportSize, + const ::XCEngine::UI::UIPoint& viewportMousePosition) const { + if (!m_renderRequest.IsValid() || + m_device == nullptr || + m_lastTargets == nullptr) { + return {}; + } + + ViewportObjectIdPickContext pickContext = {}; + pickContext.commandQueue = m_lastRenderContext.commandQueue; + pickContext.texture = m_lastTargets->objectIdTexture; + pickContext.textureState = m_lastTargets->objectIdState; + pickContext.textureWidth = m_lastTargets->width; + pickContext.textureHeight = m_lastTargets->height; + pickContext.hasValidFrame = m_lastTargets->hasValidObjectIdFrame; + pickContext.viewportSize = viewportSize; + pickContext.viewportMousePosition = viewportMousePosition; + + return PickViewportObjectIdEntity( + pickContext, + [this]( + const ViewportObjectIdReadbackRequest& request, + std::array& outRgba) { + return m_device->ReadTexturePixelRGBA8( + request.commandQueue, + request.texture, + request.textureState, + request.pixelX, + request.pixelY, + outRgba); + }); +} + +void SceneViewportRenderService::EnsureSceneRenderer() { + if (!m_sceneRenderer) { + m_sceneRenderer = std::make_unique<::XCEngine::Rendering::SceneRenderer>(); + } +} + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Rendering/Viewport/SceneViewportRenderService.h b/new_editor/app/Rendering/Viewport/SceneViewportRenderService.h new file mode 100644 index 00000000..9326548b --- /dev/null +++ b/new_editor/app/Rendering/Viewport/SceneViewportRenderService.h @@ -0,0 +1,59 @@ +#pragma once + +#include "Rendering/Viewport/ViewportContentRenderer.h" +#include "Rendering/Viewport/ViewportObjectPickerService.h" +#include "Rendering/Viewport/SceneViewportRenderPassBundle.h" +#include "Rendering/Viewport/SceneViewportRenderRequest.h" + +#include +#include + +#include +#include + +namespace XCEngine::Rendering { + +class SceneRenderer; + +} // namespace XCEngine::Rendering + +namespace XCEngine::RHI { + +class RHIDevice; + +} // namespace XCEngine::RHI + +namespace XCEngine::UI::Editor::App { + +class SceneViewportRenderService + : public IViewportContentRenderer + , public IViewportObjectPickerService { +public: + SceneViewportRenderService(); + ~SceneViewportRenderService(); + + static ViewportResourceRequirements GetViewportResourceRequirements(); + + void Shutdown(); + void SetRenderRequest(SceneViewportRenderRequest request); + + ViewportRenderResult Render( + ViewportRenderTargets& targets, + ::XCEngine::RHI::RHIDevice& device, + const ::XCEngine::Rendering::RenderContext& renderContext) override; + ViewportObjectIdPickResult PickObject( + const ::XCEngine::UI::UISize& viewportSize, + const ::XCEngine::UI::UIPoint& viewportMousePosition) const override; + +private: + void EnsureSceneRenderer(); + + SceneViewportRenderRequest m_renderRequest = {}; + std::unique_ptr<::XCEngine::Rendering::SceneRenderer> m_sceneRenderer = {}; + SceneViewportRenderPassBundle m_renderPassBundle = {}; + ::XCEngine::RHI::RHIDevice* m_device = nullptr; + const ViewportRenderTargets* m_lastTargets = nullptr; + ::XCEngine::Rendering::RenderContext m_lastRenderContext = {}; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Rendering/Viewport/ViewportContentRenderer.h b/new_editor/app/Rendering/Viewport/ViewportContentRenderer.h new file mode 100644 index 00000000..d5803b5c --- /dev/null +++ b/new_editor/app/Rendering/Viewport/ViewportContentRenderer.h @@ -0,0 +1,32 @@ +#pragma once + +#include "Rendering/Viewport/ViewportRenderTargets.h" + +#include +#include + +#include + +namespace XCEngine::UI::Editor::App { + +struct ViewportRenderResult { + bool rendered = false; + bool requiresFallbackClear = false; + std::string statusText = {}; + float fallbackClearR = 0.0f; + float fallbackClearG = 0.0f; + float fallbackClearB = 0.0f; + float fallbackClearA = 1.0f; +}; + +class IViewportContentRenderer { +public: + virtual ~IViewportContentRenderer() = default; + + virtual ViewportRenderResult Render( + ViewportRenderTargets& targets, + ::XCEngine::RHI::RHIDevice& device, + const ::XCEngine::Rendering::RenderContext& renderContext) = 0; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Rendering/Viewport/ViewportHostService.cpp b/new_editor/app/Rendering/Viewport/ViewportHostService.cpp index 67a90dc6..f6ac36f8 100644 --- a/new_editor/app/Rendering/Viewport/ViewportHostService.cpp +++ b/new_editor/app/Rendering/Viewport/ViewportHostService.cpp @@ -1,11 +1,8 @@ #include "ViewportHostService.h" -#include "Host/ViewportRenderHost.h" -#include +#include "Ports/ViewportRenderPort.h" -#include #include -#include #include @@ -15,111 +12,94 @@ namespace { using ::XCEngine::RHI::ResourceStates; -void SetViewportStatusIfEmpty( - std::string& statusText, - std::string_view message) { - if (statusText.empty()) { - statusText = std::string(message); - } -} - } // namespace -ViewportHostService::ViewportHostService() - : m_textureDescriptorAllocator( - std::make_unique()) { -} +ViewportHostService::ViewportHostService() = default; ViewportHostService::~ViewportHostService() = default; void ViewportHostService::AttachWindowRenderer( - Host::ViewportRenderHost& windowRenderer) { + Ports::ViewportRenderPort& windowRenderer) { if (m_windowRenderer == &windowRenderer) { m_device = windowRenderer.GetRHIDevice(); - if (m_device != nullptr && - !m_textureDescriptorAllocator->IsInitialized()) { - m_textureDescriptorAllocator->Initialize(*m_device); - } return; } - Shutdown(); + ReleaseWindowResources(); m_windowRenderer = &windowRenderer; m_device = windowRenderer.GetRHIDevice(); - if (m_device != nullptr) { - m_textureDescriptorAllocator->Initialize(*m_device); - } } void ViewportHostService::DetachWindowRenderer() { - Shutdown(); + ReleaseWindowResources(); } void ViewportHostService::SetSurfacePresentationEnabled(bool enabled) { m_surfacePresentationEnabled = enabled; } -void ViewportHostService::SetSceneViewportRenderRequest( - SceneViewportRenderRequest request) { - m_sceneViewportRenderRequest = request; +void ViewportHostService::SetContentRenderer( + std::string_view viewportId, + IViewportContentRenderer* renderer, + const ViewportResourceRequirements& requirements) { + ViewportEntry& entry = GetOrCreateEntry(viewportId); + entry.renderer = renderer; + entry.requirements = requirements; } void ViewportHostService::Shutdown() { - for (ViewportEntry& entry : m_entries) { + for (auto& [viewportId, entry] : m_entries) { DestroyViewportEntry(entry); } - m_sceneViewportRenderPassBundle.Shutdown(); - m_textureDescriptorAllocator->Shutdown(); m_windowRenderer = nullptr; m_device = nullptr; m_surfacePresentationEnabled = false; - m_sceneViewportRenderRequest = {}; - m_sceneRenderer.reset(); - m_sceneViewportLastRenderContext = {}; + m_entries.clear(); } void ViewportHostService::BeginFrame() { - for (ViewportEntry& entry : m_entries) { + for (auto& [viewportId, entry] : m_entries) { entry.requestedWidth = 0; entry.requestedHeight = 0; entry.requestedThisFrame = false; entry.renderedThisFrame = false; - entry.kind = (&entry == &m_entries[0]) ? ViewportKind::Scene : ViewportKind::Game; } } -ViewportHostService::ViewportEntry& ViewportHostService::GetEntry( - ViewportKind kind) { - const std::size_t index = kind == ViewportKind::Scene ? 0u : 1u; - ViewportEntry& entry = m_entries[index]; - entry.kind = kind; - return entry; +void ViewportHostService::ReleaseWindowResources() { + for (auto& [viewportId, entry] : m_entries) { + DestroyViewportEntry(entry); + } + + m_windowRenderer = nullptr; + m_device = nullptr; + m_surfacePresentationEnabled = false; } -const ViewportHostService::ViewportEntry& ViewportHostService::GetEntry( - ViewportKind kind) const { - const std::size_t index = kind == ViewportKind::Scene ? 0u : 1u; - return m_entries[index]; +ViewportHostService::ViewportEntry& ViewportHostService::GetOrCreateEntry( + std::string_view viewportId) { + const auto [it, inserted] = + m_entries.try_emplace(std::string(viewportId)); + return it->second; } void ViewportHostService::DestroyViewportEntry(ViewportEntry& entry) { m_renderTargetManager.DestroyTargets( - m_textureDescriptorAllocator.get(), + m_windowRenderer, entry.renderTargets); - entry = {}; -} - -void ViewportHostService::EnsureSceneRenderer() { - if (!m_sceneRenderer) { - m_sceneRenderer = std::make_unique<::XCEngine::Rendering::SceneRenderer>(); - } + entry.requestedWidth = 0; + entry.requestedHeight = 0; + entry.requestedThisFrame = false; + entry.renderedThisFrame = false; + entry.renderTargets = {}; + entry.statusText.clear(); } ViewportFrame ViewportHostService::RequestViewport( - ViewportKind kind, + std::string_view viewportId, const ::XCEngine::UI::UISize& requestedSize) { - ViewportEntry& entry = GetEntry(kind); + ViewportEntry& entry = GetOrCreateEntry(viewportId); entry.requestedThisFrame = requestedSize.width > 1.0f && requestedSize.height > 1.0f; entry.requestedWidth = entry.requestedThisFrame ? static_cast(requestedSize.width) @@ -143,42 +123,31 @@ ViewportFrame ViewportHostService::RequestViewport( return BuildFrame(entry, requestedSize); } -ViewportObjectIdPickResult ViewportHostService::PickSceneViewportObject( - const ::XCEngine::UI::UISize& viewportSize, - const ::XCEngine::UI::UIPoint& viewportMousePosition) { - if (!m_sceneViewportRenderRequest.IsValid()) { - return {}; - } - - ViewportEntry& entry = GetEntry(ViewportKind::Scene); - const ViewportObjectIdPickResult objectIdPick = - PickSceneViewportObjectWithObjectId( - entry, - viewportSize, - viewportMousePosition); - if (objectIdPick.status == ViewportObjectIdPickStatus::ReadbackFailed) { - SetViewportStatusIfEmpty( - entry.statusText, - "Scene object id readback failed"); - } - return objectIdPick; -} - void ViewportHostService::RenderRequestedViewports( const ::XCEngine::Rendering::RenderContext& renderContext) { if (m_windowRenderer == nullptr || m_device == nullptr || !renderContext.IsValid()) { return; } - m_sceneViewportLastRenderContext = renderContext; - - for (ViewportEntry& entry : m_entries) { + for (auto& [viewportId, entry] : m_entries) { if (!entry.requestedThisFrame || !EnsureViewportResources(entry)) { continue; } - if (entry.kind == ViewportKind::Scene) { - RenderSceneViewport(entry, renderContext); + IViewportContentRenderer* renderer = entry.renderer; + if (renderer != nullptr) { + const ViewportRenderResult renderResult = + renderer->Render( + entry.renderTargets, + *m_device, + renderContext); + entry.statusText = renderResult.statusText; + if (!renderResult.rendered && renderResult.requiresFallbackClear) { + ApplyViewportFallback( + entry, + renderContext, + renderResult); + } } else { entry.statusText.clear(); ClearViewport(entry, renderContext, 0.09f, 0.09f, 0.09f, 1.0f); @@ -191,7 +160,7 @@ void ViewportHostService::RenderRequestedViewports( bool ViewportHostService::EnsureViewportResources(ViewportEntry& entry) { const ViewportResourceReuseQuery reuseQuery = BuildViewportRenderTargetsReuseQuery( - entry.kind, + entry.requirements, entry.renderTargets, entry.requestedWidth, entry.requestedHeight); @@ -207,140 +176,27 @@ bool ViewportHostService::EnsureViewportResources(ViewportEntry& entry) { } return m_renderTargetManager.EnsureTargets( - entry.kind, + entry.requirements, entry.requestedWidth, entry.requestedHeight, *m_device, - *m_textureDescriptorAllocator, + *m_windowRenderer, entry.renderTargets); } -bool ViewportHostService::RenderSceneViewport( - ViewportEntry& entry, - const ::XCEngine::Rendering::RenderContext& renderContext) { - if (m_sceneViewportRenderRequest.camera == nullptr) { - ApplySceneViewportFallback( - entry, - renderContext, - "Scene view camera is unavailable", - 0.18f, - 0.07f, - 0.07f, - 1.0f); - return false; - } - - if (m_sceneViewportRenderRequest.scene == nullptr) { - ApplySceneViewportFallback( - entry, - renderContext, - "No active scene", - 0.07f, - 0.08f, - 0.10f, - 1.0f); - return false; - } - - EnsureSceneRenderer(); - entry.statusText.clear(); - - ::XCEngine::Rendering::RenderSurface surface = - BuildViewportColorSurface(entry.renderTargets); - std::vector<::XCEngine::Rendering::CameraFramePlan> plans = - m_sceneRenderer->BuildFramePlans( - *m_sceneViewportRenderRequest.scene, - m_sceneViewportRenderRequest.camera, - renderContext, - surface); - if (plans.empty()) { - ApplySceneViewportFallback( - entry, - renderContext, - "Scene renderer failed", - 0.18f, - 0.07f, - 0.07f, - 1.0f); - return false; - } - - SceneViewportRenderPlanBuildResult renderPlan = - m_sceneViewportRenderPassBundle.BuildRenderPlan( - entry.renderTargets, - m_sceneViewportRenderRequest); - ApplySceneViewportRenderPlan( - entry.renderTargets, - renderPlan.plan, - plans.front()); - if (renderPlan.warningStatusText != nullptr) { - SetViewportStatusIfEmpty(entry.statusText, renderPlan.warningStatusText); - } - - if (!m_sceneRenderer->Render(plans)) { - ApplySceneViewportFallback( - entry, - renderContext, - "Scene renderer failed", - 0.18f, - 0.07f, - 0.07f, - 1.0f); - return false; - } - - MarkSceneViewportRenderSuccess( - entry.renderTargets, - renderPlan.plan, - plans.front()); - return true; -} - -ViewportObjectIdPickResult ViewportHostService::PickSceneViewportObjectWithObjectId( - ViewportEntry& entry, - const ::XCEngine::UI::UISize& viewportSize, - const ::XCEngine::UI::UIPoint& viewportMousePosition) { - if (m_device == nullptr) { - return {}; - } - - ViewportObjectIdPickContext pickContext = {}; - pickContext.commandQueue = m_sceneViewportLastRenderContext.commandQueue; - pickContext.texture = entry.renderTargets.objectIdTexture; - pickContext.textureState = entry.renderTargets.objectIdState; - pickContext.textureWidth = entry.renderTargets.width; - pickContext.textureHeight = entry.renderTargets.height; - pickContext.hasValidFrame = entry.renderTargets.hasValidObjectIdFrame; - pickContext.viewportSize = viewportSize; - pickContext.viewportMousePosition = viewportMousePosition; - - return PickViewportObjectIdEntity( - pickContext, - [this]( - const ViewportObjectIdReadbackRequest& request, - std::array& outRgba) { - return m_device != nullptr && - m_device->ReadTexturePixelRGBA8( - request.commandQueue, - request.texture, - request.textureState, - request.pixelX, - request.pixelY, - outRgba); - }); -} - -void ViewportHostService::ApplySceneViewportFallback( +void ViewportHostService::ApplyViewportFallback( ViewportEntry& entry, const ::XCEngine::Rendering::RenderContext& renderContext, - std::string statusText, - float r, - float g, - float b, - float a) { - entry.statusText = std::move(statusText); + const ViewportRenderResult& renderResult) { + entry.statusText = renderResult.statusText; entry.renderTargets.hasValidObjectIdFrame = false; - ClearViewport(entry, renderContext, r, g, b, a); + ClearViewport( + entry, + renderContext, + renderResult.fallbackClearR, + renderResult.fallbackClearG, + renderResult.fallbackClearB, + renderResult.fallbackClearA); } void ViewportHostService::ClearViewport( diff --git a/new_editor/app/Rendering/Viewport/ViewportHostService.h b/new_editor/app/Rendering/Viewport/ViewportHostService.h index 2a600a34..49c06fac 100644 --- a/new_editor/app/Rendering/Viewport/ViewportHostService.h +++ b/new_editor/app/Rendering/Viewport/ViewportHostService.h @@ -1,25 +1,16 @@ #pragma once -#include "Host/HostFwd.h" -#include "SceneViewportRenderPassBundle.h" -#include "ViewportObjectIdPicker.h" +#include "Ports/PortFwd.h" +#include "Rendering/Viewport/ViewportContentRenderer.h" #include "ViewportRenderTargets.h" -#include - #include #include -#include #include -#include #include - -namespace XCEngine::Rendering { - -class SceneRenderer; - -} // namespace XCEngine::Rendering +#include +#include namespace XCEngine::UI::Editor::App { @@ -28,27 +19,28 @@ public: ViewportHostService(); ~ViewportHostService(); - void AttachWindowRenderer(Host::ViewportRenderHost& windowRenderer); + void AttachWindowRenderer(Ports::ViewportRenderPort& windowRenderer); void DetachWindowRenderer(); void SetSurfacePresentationEnabled(bool enabled); - void SetSceneViewportRenderRequest(SceneViewportRenderRequest request); + void SetContentRenderer( + std::string_view viewportId, + IViewportContentRenderer* renderer, + const ViewportResourceRequirements& requirements = {}); void Shutdown(); void BeginFrame(); ViewportFrame RequestViewport( - ViewportKind kind, + std::string_view viewportId, const ::XCEngine::UI::UISize& requestedSize); - ViewportObjectIdPickResult PickSceneViewportObject( - const ::XCEngine::UI::UISize& viewportSize, - const ::XCEngine::UI::UIPoint& viewportMousePosition); void RenderRequestedViewports( const ::XCEngine::Rendering::RenderContext& renderContext); private: struct ViewportEntry { - ViewportKind kind = ViewportKind::Scene; + IViewportContentRenderer* renderer = nullptr; + ViewportResourceRequirements requirements = {}; std::uint32_t requestedWidth = 0; std::uint32_t requestedHeight = 0; bool requestedThisFrame = false; @@ -57,26 +49,14 @@ private: std::string statusText = {}; }; - ViewportEntry& GetEntry(ViewportKind kind); - const ViewportEntry& GetEntry(ViewportKind kind) const; + void ReleaseWindowResources(); + ViewportEntry& GetOrCreateEntry(std::string_view viewportId); void DestroyViewportEntry(ViewportEntry& entry); - void EnsureSceneRenderer(); bool EnsureViewportResources(ViewportEntry& entry); - bool RenderSceneViewport( - ViewportEntry& entry, - const ::XCEngine::Rendering::RenderContext& renderContext); - ViewportObjectIdPickResult PickSceneViewportObjectWithObjectId( - ViewportEntry& entry, - const ::XCEngine::UI::UISize& viewportSize, - const ::XCEngine::UI::UIPoint& viewportMousePosition); - void ApplySceneViewportFallback( + void ApplyViewportFallback( ViewportEntry& entry, const ::XCEngine::Rendering::RenderContext& renderContext, - std::string statusText, - float r, - float g, - float b, - float a); + const ViewportRenderResult& renderResult); void ClearViewport( ViewportEntry& entry, const ::XCEngine::Rendering::RenderContext& renderContext, @@ -88,16 +68,11 @@ private: const ViewportEntry& entry, const ::XCEngine::UI::UISize& requestedSize) const; - Host::ViewportRenderHost* m_windowRenderer = nullptr; + Ports::ViewportRenderPort* m_windowRenderer = nullptr; ::XCEngine::RHI::RHIDevice* m_device = nullptr; - std::unique_ptr m_textureDescriptorAllocator = {}; ViewportRenderTargetManager m_renderTargetManager = {}; bool m_surfacePresentationEnabled = false; - SceneViewportRenderRequest m_sceneViewportRenderRequest = {}; - std::unique_ptr<::XCEngine::Rendering::SceneRenderer> m_sceneRenderer = {}; - SceneViewportRenderPassBundle m_sceneViewportRenderPassBundle = {}; - ::XCEngine::Rendering::RenderContext m_sceneViewportLastRenderContext = {}; - std::array m_entries = {}; + std::unordered_map m_entries = {}; }; } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Rendering/Viewport/ViewportObjectPickerService.h b/new_editor/app/Rendering/Viewport/ViewportObjectPickerService.h new file mode 100644 index 00000000..6cab19ce --- /dev/null +++ b/new_editor/app/Rendering/Viewport/ViewportObjectPickerService.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Rendering/Viewport/ViewportObjectIdPicker.h" + +namespace XCEngine::UI::Editor::App { + +class IViewportObjectPickerService { +public: + virtual ~IViewportObjectPickerService() = default; + + virtual ViewportObjectIdPickResult PickObject( + const ::XCEngine::UI::UISize& viewportSize, + const ::XCEngine::UI::UIPoint& viewportMousePosition) const = 0; +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Rendering/Viewport/ViewportRenderTargetInternal.cpp b/new_editor/app/Rendering/Viewport/ViewportRenderTargetInternal.cpp index 9f344c9f..31811a69 100644 --- a/new_editor/app/Rendering/Viewport/ViewportRenderTargetInternal.cpp +++ b/new_editor/app/Rendering/Viewport/ViewportRenderTargetInternal.cpp @@ -5,10 +5,6 @@ namespace XCEngine::UI::Editor::App { -bool ViewportRequiresObjectIdResources(ViewportKind kind) { - return kind == ViewportKind::Scene; -} - std::uint32_t ClampViewportPixelCoordinate(float value, std::uint32_t extent) { if (extent == 0u) { return 0u; @@ -36,19 +32,28 @@ bool CanReuseViewportResources(const ViewportResourceReuseQuery& query) { return false; } - if (!ViewportRequiresObjectIdResources(query.kind)) { - return true; + if (query.requirements.requiresDepthSampling && + !query.resources.hasDepthShaderView) { + return false; } - return query.resources.hasObjectIdTexture && - query.resources.hasObjectIdDepthTexture && - query.resources.hasObjectIdDepthView && - query.resources.hasObjectIdView && - query.resources.hasObjectIdShaderView && - query.resources.hasDepthShaderView && - query.resources.hasSelectionMaskTexture && - query.resources.hasSelectionMaskView && - query.resources.hasSelectionMaskShaderView; + if (query.requirements.requiresObjectIdSurface && + (!query.resources.hasObjectIdTexture || + !query.resources.hasObjectIdDepthTexture || + !query.resources.hasObjectIdDepthView || + !query.resources.hasObjectIdView || + !query.resources.hasObjectIdShaderView)) { + return false; + } + + if (query.requirements.requiresSelectionMaskSurface && + (!query.resources.hasSelectionMaskTexture || + !query.resources.hasSelectionMaskView || + !query.resources.hasSelectionMaskShaderView)) { + return false; + } + + return true; } ::XCEngine::RHI::TextureDesc BuildViewportTextureDesc( diff --git a/new_editor/app/Rendering/Viewport/ViewportRenderTargetInternal.h b/new_editor/app/Rendering/Viewport/ViewportRenderTargetInternal.h index a938055d..b614beee 100644 --- a/new_editor/app/Rendering/Viewport/ViewportRenderTargetInternal.h +++ b/new_editor/app/Rendering/Viewport/ViewportRenderTargetInternal.h @@ -29,7 +29,7 @@ struct ViewportResourcePresence { }; struct ViewportResourceReuseQuery { - ViewportKind kind = ViewportKind::Scene; + ViewportResourceRequirements requirements = {}; std::uint32_t width = 0; std::uint32_t height = 0; std::uint32_t requestedWidth = 0; @@ -37,7 +37,6 @@ struct ViewportResourceReuseQuery { ViewportResourcePresence resources = {}; }; -bool ViewportRequiresObjectIdResources(ViewportKind kind); std::uint32_t ClampViewportPixelCoordinate(float value, std::uint32_t extent); bool CanReuseViewportResources(const ViewportResourceReuseQuery& query); ::XCEngine::RHI::TextureDesc BuildViewportTextureDesc( diff --git a/new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp b/new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp index 104550bd..b2b1cbe9 100644 --- a/new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp +++ b/new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp @@ -1,6 +1,6 @@ #include "Rendering/Viewport/ViewportRenderTargets.h" -#include "Host/ShaderResourceDescriptorAllocator.h" +#include "Ports/ViewportRenderPort.h" namespace XCEngine::UI::Editor::App { @@ -24,7 +24,8 @@ bool CreateViewportColorResources( bool CreateViewportDepthResources( ::XCEngine::RHI::RHIDevice& device, - ViewportRenderTargets& targets) { + ViewportRenderTargets& targets, + bool createShaderView) { const auto depthDesc = BuildViewportTextureDesc(targets.width, targets.height, ::XCEngine::RHI::Format::D24_UNorm_S8_UInt); targets.depthTexture = device.CreateTexture(depthDesc); @@ -39,6 +40,10 @@ bool CreateViewportDepthResources( return false; } + if (!createShaderView) { + return true; + } + ::XCEngine::RHI::ResourceViewDesc depthShaderViewDesc = {}; depthShaderViewDesc.dimension = ::XCEngine::RHI::ResourceViewDimension::Texture2D; depthShaderViewDesc.mipLevel = 0; @@ -112,23 +117,18 @@ bool CreateViewportSelectionMaskResources( return targets.selectionMaskShaderView != nullptr; } -bool CreateViewportTextureDescriptor( - Host::ShaderResourceDescriptorAllocator& textureDescriptorAllocator, +bool CreateViewportTextureHandle( + Ports::ViewportRenderPort& renderHost, ViewportRenderTargets& targets) { - if (!textureDescriptorAllocator.CreateTextureDescriptor( - targets.colorTexture, - &targets.srvCpuHandle, - &targets.srvGpuHandle)) { + if (targets.colorTexture == nullptr) { return false; } - targets.textureHandle.nativeHandle = static_cast(targets.srvGpuHandle.ptr); - targets.textureHandle.width = targets.width; - targets.textureHandle.height = targets.height; - targets.textureHandle.kind = ::XCEngine::UI::UITextureHandleKind::ShaderResourceView; - targets.textureHandle.resourceHandle = - reinterpret_cast(targets.colorTexture); - return true; + return renderHost.CreateViewportTextureHandle( + *targets.colorTexture, + targets.width, + targets.height, + targets.textureHandle); } template @@ -145,8 +145,6 @@ void ShutdownAndDeleteViewportResource(ResourceType*& resource) { void ResetViewportTargetMetadata(ViewportRenderTargets& targets) { targets.width = 0; targets.height = 0; - targets.srvCpuHandle = {}; - targets.srvGpuHandle = {}; targets.textureHandle = {}; targets.colorState = ::XCEngine::RHI::ResourceStates::Common; targets.objectIdState = ::XCEngine::RHI::ResourceStates::Common; @@ -157,12 +155,12 @@ void ResetViewportTargetMetadata(ViewportRenderTargets& targets) { } // namespace ViewportResourceReuseQuery BuildViewportRenderTargetsReuseQuery( - ViewportKind kind, + const ViewportResourceRequirements& requirements, const ViewportRenderTargets& targets, std::uint32_t requestedWidth, std::uint32_t requestedHeight) { ViewportResourceReuseQuery query = {}; - query.kind = kind; + query.requirements = requirements; query.width = targets.width; query.height = targets.height; query.requestedWidth = requestedWidth; @@ -215,27 +213,31 @@ ViewportResourceReuseQuery BuildViewportRenderTargetsReuseQuery( } bool ViewportRenderTargetManager::EnsureTargets( - ViewportKind kind, + const ViewportResourceRequirements& requirements, std::uint32_t width, std::uint32_t height, ::XCEngine::RHI::RHIDevice& device, - Host::ShaderResourceDescriptorAllocator& textureDescriptorAllocator, + Ports::ViewportRenderPort& renderHost, ViewportRenderTargets& targets) const { if (width == 0u || height == 0u) { return false; } - DestroyTargets(&textureDescriptorAllocator, targets); + DestroyTargets(&renderHost, targets); targets.width = width; targets.height = height; if (!CreateViewportColorResources(device, targets) || - !CreateViewportDepthResources(device, targets) || - (ViewportRequiresObjectIdResources(kind) && - (!CreateViewportObjectIdResources(device, targets) || - !CreateViewportSelectionMaskResources(device, targets))) || - !CreateViewportTextureDescriptor(textureDescriptorAllocator, targets)) { - DestroyTargets(&textureDescriptorAllocator, targets); + !CreateViewportDepthResources( + device, + targets, + requirements.requiresDepthSampling) || + (requirements.requiresObjectIdSurface && + !CreateViewportObjectIdResources(device, targets)) || + (requirements.requiresSelectionMaskSurface && + !CreateViewportSelectionMaskResources(device, targets)) || + !CreateViewportTextureHandle(renderHost, targets)) { + DestroyTargets(&renderHost, targets); return false; } @@ -247,10 +249,10 @@ bool ViewportRenderTargetManager::EnsureTargets( } void ViewportRenderTargetManager::DestroyTargets( - Host::ShaderResourceDescriptorAllocator* textureDescriptorAllocator, + Ports::ViewportRenderPort* renderHost, ViewportRenderTargets& targets) const { - if (textureDescriptorAllocator != nullptr && targets.srvCpuHandle.ptr != 0) { - textureDescriptorAllocator->Free(targets.srvCpuHandle, targets.srvGpuHandle); + if (renderHost != nullptr && targets.textureHandle.IsValid()) { + renderHost->ReleaseViewportTextureHandle(targets.textureHandle); } ShutdownAndDeleteViewportResource(targets.objectIdView); diff --git a/new_editor/app/Rendering/Viewport/ViewportRenderTargets.h b/new_editor/app/Rendering/Viewport/ViewportRenderTargets.h index c0944604..43bd3308 100644 --- a/new_editor/app/Rendering/Viewport/ViewportRenderTargets.h +++ b/new_editor/app/Rendering/Viewport/ViewportRenderTargets.h @@ -1,6 +1,6 @@ #pragma once -#include "Host/HostFwd.h" +#include "Ports/PortFwd.h" #include "ViewportRenderTargetInternal.h" #include @@ -8,12 +8,6 @@ #include #include -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include - namespace XCEngine::UI::Editor::App { struct ViewportRenderTargets { @@ -32,8 +26,6 @@ struct ViewportRenderTargets { ::XCEngine::RHI::RHITexture* selectionMaskTexture = nullptr; ::XCEngine::RHI::RHIResourceView* selectionMaskView = nullptr; ::XCEngine::RHI::RHIResourceView* selectionMaskShaderView = nullptr; - D3D12_CPU_DESCRIPTOR_HANDLE srvCpuHandle = {}; - D3D12_GPU_DESCRIPTOR_HANDLE srvGpuHandle = {}; ::XCEngine::UI::UITextureHandle textureHandle = {}; ::XCEngine::RHI::ResourceStates colorState = ::XCEngine::RHI::ResourceStates::Common; ::XCEngine::RHI::ResourceStates objectIdState = ::XCEngine::RHI::ResourceStates::Common; @@ -42,7 +34,7 @@ struct ViewportRenderTargets { }; ViewportResourceReuseQuery BuildViewportRenderTargetsReuseQuery( - ViewportKind kind, + const ViewportResourceRequirements& requirements, const ViewportRenderTargets& targets, std::uint32_t requestedWidth, std::uint32_t requestedHeight); @@ -57,14 +49,14 @@ ViewportResourceReuseQuery BuildViewportRenderTargetsReuseQuery( class ViewportRenderTargetManager { public: bool EnsureTargets( - ViewportKind kind, + const ViewportResourceRequirements& requirements, std::uint32_t width, std::uint32_t height, ::XCEngine::RHI::RHIDevice& device, - Host::ShaderResourceDescriptorAllocator& textureDescriptorAllocator, + Ports::ViewportRenderPort& renderHost, ViewportRenderTargets& targets) const; void DestroyTargets( - Host::ShaderResourceDescriptorAllocator* textureDescriptorAllocator, + Ports::ViewportRenderPort* renderHost, ViewportRenderTargets& targets) const; }; diff --git a/new_editor/app/Rendering/Viewport/ViewportTypes.h b/new_editor/app/Rendering/Viewport/ViewportTypes.h index 8c7dcce9..a47dc0ff 100644 --- a/new_editor/app/Rendering/Viewport/ViewportTypes.h +++ b/new_editor/app/Rendering/Viewport/ViewportTypes.h @@ -2,14 +2,14 @@ #include -#include #include namespace XCEngine::UI::Editor::App { -enum class ViewportKind : std::uint8_t { - Scene = 0, - Game +struct ViewportResourceRequirements { + bool requiresDepthSampling = false; + bool requiresObjectIdSurface = false; + bool requiresSelectionMaskSurface = false; }; struct ViewportFrame { diff --git a/new_editor/app/Scene/EditorSceneRuntime.h b/new_editor/app/Scene/EditorSceneRuntime.h index 34ea1cf1..bde537da 100644 --- a/new_editor/app/Scene/EditorSceneRuntime.h +++ b/new_editor/app/Scene/EditorSceneRuntime.h @@ -4,6 +4,7 @@ #include "Scene/SceneViewportCameraController.h" #include "Scene/SceneToolState.h" #include "State/EditorSelectionService.h" +#include "Rendering/Viewport/SceneViewportRenderRequest.h" #include @@ -32,18 +33,6 @@ struct Vector3; namespace XCEngine::UI::Editor::App { -struct SceneViewportRenderRequest { - ::XCEngine::Components::Scene* scene = nullptr; - ::XCEngine::Components::CameraComponent* camera = nullptr; - std::vector selectedObjectIds = {}; - float orbitDistance = 0.0f; - bool debugSelectionMask = false; - - bool IsValid() const { - return scene != nullptr && camera != nullptr; - } -}; - struct EditorSceneComponentDescriptor { std::string componentId = {}; std::string typeName = {}; diff --git a/new_editor/src/App/EditorSession.cpp b/new_editor/app/State/EditorSession.cpp similarity index 100% rename from new_editor/src/App/EditorSession.cpp rename to new_editor/app/State/EditorSession.cpp diff --git a/new_editor/include/XCEditor/Collections/UIEditorTreePanelBehavior.h b/new_editor/include/XCEditor/Collections/UIEditorTreePanelBehavior.h index b99d5c19..117bed7d 100644 --- a/new_editor/include/XCEditor/Collections/UIEditorTreePanelBehavior.h +++ b/new_editor/include/XCEditor/Collections/UIEditorTreePanelBehavior.h @@ -13,14 +13,16 @@ namespace XCEngine::UI::Editor { struct UIEditorTreePanelInputFilterOptions { bool allowInteraction = false; - bool panelActive = false; + bool hasInputFocus = false; bool captureActive = false; }; -std::vector<::XCEngine::UI::UIInputEvent> FilterUIEditorTreePanelInputEvents( +std::vector<::XCEngine::UI::UIInputEvent> BuildUIEditorTreePanelInputEvents( const ::XCEngine::UI::UIRect& bounds, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - const UIEditorTreePanelInputFilterOptions& options); + const UIEditorTreePanelInputFilterOptions& options, + bool synthesizeFocusGained = false, + bool synthesizeFocusLost = false); std::vector<::XCEngine::UI::UIInputEvent> FilterUIEditorTreePanelPointerInputEvents( const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, diff --git a/new_editor/include/XCEditor/Foundation/UIEditorPanelInputFilter.h b/new_editor/include/XCEditor/Foundation/UIEditorPanelInputFilter.h index cd109369..43fd36f5 100644 --- a/new_editor/include/XCEditor/Foundation/UIEditorPanelInputFilter.h +++ b/new_editor/include/XCEditor/Foundation/UIEditorPanelInputFilter.h @@ -14,6 +14,13 @@ struct UIEditorPanelInputFilterOptions { bool includePointerLeave = false; }; +inline ::XCEngine::UI::UIInputEvent BuildUIEditorPanelFocusEvent( + ::XCEngine::UI::UIInputEventType type) { + ::XCEngine::UI::UIInputEvent event = {}; + event.type = type; + return event; +} + inline std::vector<::XCEngine::UI::UIInputEvent> FilterUIEditorPanelInputEvents( const ::XCEngine::UI::UIRect& bounds, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, @@ -79,4 +86,36 @@ inline std::vector<::XCEngine::UI::UIInputEvent> FilterUIEditorPanelInputEvents( return filteredEvents; } +inline std::vector<::XCEngine::UI::UIInputEvent> BuildUIEditorPanelInputEvents( + const ::XCEngine::UI::UIRect& bounds, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + const UIEditorPanelInputFilterOptions& options, + bool synthesizeFocusGained = false, + bool synthesizeFocusLost = false) { + std::vector<::XCEngine::UI::UIInputEvent> filteredEvents = + FilterUIEditorPanelInputEvents(bounds, inputEvents, options); + if (!synthesizeFocusGained && !synthesizeFocusLost) { + return filteredEvents; + } + + std::vector<::XCEngine::UI::UIInputEvent> routedEvents = {}; + routedEvents.reserve( + filteredEvents.size() + + static_cast(synthesizeFocusGained) + + static_cast(synthesizeFocusLost)); + if (synthesizeFocusLost) { + routedEvents.push_back( + BuildUIEditorPanelFocusEvent(::XCEngine::UI::UIInputEventType::FocusLost)); + } + if (synthesizeFocusGained) { + routedEvents.push_back( + BuildUIEditorPanelFocusEvent(::XCEngine::UI::UIInputEventType::FocusGained)); + } + routedEvents.insert( + routedEvents.end(), + filteredEvents.begin(), + filteredEvents.end()); + return routedEvents; +} + } // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Shell/UIEditorStructuredShell.h b/new_editor/include/XCEditor/Shell/UIEditorStructuredShell.h index d932d33a..605c059b 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorStructuredShell.h +++ b/new_editor/include/XCEditor/Shell/UIEditorStructuredShell.h @@ -21,25 +21,9 @@ struct StructuredEditorShellBinding { } }; -inline StructuredEditorShellBinding BuildStructuredEditorShellBinding( - const EditorShellAsset& asset) { - StructuredEditorShellBinding binding = {}; - binding.screenAsset.screenId = asset.screenId; - binding.screenAsset.documentPath = asset.documentPath.string(); - binding.workspaceController = - UIEditorWorkspaceController(asset.panelRegistry, asset.workspace, asset.workspaceSession); - binding.shellDefinition = asset.shellDefinition; - binding.shortcutManager = BuildEditorShellShortcutManager(asset); - binding.assetValidation = ValidateEditorShellAsset(asset); - return binding; -} +StructuredEditorShellBinding BuildStructuredEditorShellBinding(const EditorShellAsset& asset); -inline UIEditorShellInteractionServices BuildStructuredEditorShellServices( - const StructuredEditorShellBinding& binding) { - UIEditorShellInteractionServices services = {}; - services.commandDispatcher = &binding.shortcutManager.GetCommandDispatcher(); - services.shortcutManager = &binding.shortcutManager; - return services; -} +UIEditorShellInteractionServices BuildStructuredEditorShellServices( + const StructuredEditorShellBinding& binding); } // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Viewport/UIEditorViewportInputBridge.h b/new_editor/include/XCEditor/Viewport/UIEditorViewportInputBridge.h index 0734def7..0c3cbbf2 100644 --- a/new_editor/include/XCEditor/Viewport/UIEditorViewportInputBridge.h +++ b/new_editor/include/XCEditor/Viewport/UIEditorViewportInputBridge.h @@ -14,6 +14,17 @@ struct UIEditorViewportInputBridgeConfig { bool capturePointerOnPointerDownInside = true; }; +enum class UIEditorViewportInputBridgeFocusMode : std::uint8_t { + SelfManaged = 0, + External +}; + +struct UIEditorViewportInputBridgeRequest { + UIEditorViewportInputBridgeFocusMode focusMode = + UIEditorViewportInputBridgeFocusMode::SelfManaged; + bool focused = false; +}; + struct UIEditorViewportInputBridgeState { bool hovered = false; bool focused = false; @@ -74,13 +85,15 @@ UIEditorViewportInputBridgeFrame UpdateUIEditorViewportInputBridge( UIEditorViewportInputBridgeState& state, const ::XCEngine::UI::UIRect& inputRect, const std::vector<::XCEngine::UI::UIInputEvent>& events, - const UIEditorViewportInputBridgeConfig& config = {}); + const UIEditorViewportInputBridgeConfig& config = {}, + const UIEditorViewportInputBridgeRequest& request = {}); UIEditorViewportInputBridgeFrame UpdateUIEditorViewportInputBridge( UIEditorViewportInputBridgeState& state, const ::XCEngine::UI::UIRect& interactionRect, const ::XCEngine::UI::UIRect& localRect, const std::vector<::XCEngine::UI::UIInputEvent>& events, - const UIEditorViewportInputBridgeConfig& config = {}); + const UIEditorViewportInputBridgeConfig& config = {}, + const UIEditorViewportInputBridgeRequest& request = {}); } // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Viewport/UIEditorViewportShell.h b/new_editor/include/XCEditor/Viewport/UIEditorViewportShell.h index 37c3008d..8a4fcfd9 100644 --- a/new_editor/include/XCEditor/Viewport/UIEditorViewportShell.h +++ b/new_editor/include/XCEditor/Viewport/UIEditorViewportShell.h @@ -52,6 +52,7 @@ UIEditorViewportShellFrame UpdateUIEditorViewportShell( const ::XCEngine::UI::UIRect& bounds, const UIEditorViewportShellModel& model, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - const Widgets::UIEditorViewportSlotMetrics& metrics = {}); + const Widgets::UIEditorViewportSlotMetrics& metrics = {}, + const UIEditorViewportInputBridgeRequest& inputRequest = {}); } // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceCompose.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceCompose.h index 54f680ee..1efeaffa 100644 --- a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceCompose.h +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceCompose.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -95,7 +96,8 @@ UIEditorWorkspaceComposeFrame UpdateUIEditorWorkspaceCompose( const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, const Widgets::UIEditorDockHostState& dockHostState = {}, const Widgets::UIEditorDockHostMetrics& dockHostMetrics = {}, - const Widgets::UIEditorViewportSlotMetrics& viewportMetrics = {}); + const Widgets::UIEditorViewportSlotMetrics& viewportMetrics = {}, + const UIEditorWorkspaceInputOwner* inputOwner = nullptr); std::vector CollectUIEditorWorkspaceComposeExternalBodyPanelIds( const UIEditorWorkspaceComposeFrame& frame); diff --git a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInputOwner.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInputOwner.h new file mode 100644 index 00000000..0ec3be03 --- /dev/null +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInputOwner.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor { + +enum class UIEditorWorkspaceInputOwnerKind : std::uint8_t { + None = 0, + DockHost, + HostedPanel, + Viewport +}; + +struct UIEditorWorkspaceInputOwner { + UIEditorWorkspaceInputOwnerKind kind = UIEditorWorkspaceInputOwnerKind::None; + std::string panelId = {}; +}; + +std::string_view GetUIEditorWorkspaceInputOwnerKindName( + UIEditorWorkspaceInputOwnerKind kind); + +bool AreUIEditorWorkspaceInputOwnersEquivalent( + const UIEditorWorkspaceInputOwner& lhs, + const UIEditorWorkspaceInputOwner& rhs); + +bool IsUIEditorWorkspaceDockHostInputOwner( + const UIEditorWorkspaceInputOwner& owner); + +bool IsUIEditorWorkspaceHostedPanelInputOwner( + const UIEditorWorkspaceInputOwner& owner, + std::string_view panelId = {}); + +bool IsUIEditorWorkspaceViewportInputOwner( + const UIEditorWorkspaceInputOwner& owner, + std::string_view panelId = {}); + +UIEditorWorkspaceInputOwner ResolveUIEditorWorkspacePointerInputOwner( + const Widgets::UIEditorDockHostLayout& dockHostLayout, + const UIEditorPanelContentHostFrame& contentHostFrame, + const ::XCEngine::UI::UIPoint& pointerPosition); + +UIEditorWorkspaceInputOwner NormalizeUIEditorWorkspaceInputOwner( + UIEditorWorkspaceInputOwner owner, + const Widgets::UIEditorDockHostLayout& dockHostLayout, + const UIEditorPanelContentHostFrame& contentHostFrame); + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h index 6e5cdf8f..e494636d 100644 --- a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h @@ -2,6 +2,7 @@ #include #include +#include #include @@ -17,6 +18,7 @@ struct UIEditorWorkspaceInteractionModel { struct UIEditorWorkspaceInteractionState { UIEditorDockHostInteractionState dockHostInteractionState = {}; UIEditorWorkspaceComposeState composeState = {}; + UIEditorWorkspaceInputOwner inputOwner = {}; }; struct UIEditorWorkspaceInteractionResult { @@ -33,6 +35,9 @@ struct UIEditorWorkspaceInteractionFrame { UIEditorDockHostInteractionFrame dockHostFrame = {}; UIEditorWorkspaceComposeFrame composeFrame = {}; UIEditorWorkspaceInteractionResult result = {}; + UIEditorWorkspaceInputOwner previousInputOwner = {}; + UIEditorWorkspaceInputOwner inputOwner = {}; + bool inputOwnerChanged = false; }; UIEditorWorkspaceInteractionFrame UpdateUIEditorWorkspaceInteraction( diff --git a/new_editor/src/Collections/UIEditorTreePanelBehavior.cpp b/new_editor/src/Collections/UIEditorTreePanelBehavior.cpp index b0878292..92202cda 100644 --- a/new_editor/src/Collections/UIEditorTreePanelBehavior.cpp +++ b/new_editor/src/Collections/UIEditorTreePanelBehavior.cpp @@ -23,20 +23,28 @@ bool ContainsPoint(const UIRect& rect, const UIPoint& point) { } // namespace -std::vector FilterUIEditorTreePanelInputEvents( +std::vector BuildUIEditorTreePanelInputEvents( const UIRect& bounds, const std::vector& inputEvents, - const UIEditorTreePanelInputFilterOptions& options) { - return FilterUIEditorPanelInputEvents( + const UIEditorTreePanelInputFilterOptions& options, + bool synthesizeFocusGained, + bool synthesizeFocusLost) { + return BuildUIEditorPanelInputEvents( bounds, inputEvents, UIEditorPanelInputFilterOptions{ .allowPointerInBounds = options.allowInteraction, .allowPointerWhileCaptured = options.captureActive, - .allowKeyboardInput = options.panelActive, - .allowFocusEvents = options.panelActive || options.captureActive, + .allowKeyboardInput = options.hasInputFocus, + .allowFocusEvents = + options.hasInputFocus || + options.captureActive || + synthesizeFocusGained || + synthesizeFocusLost, .includePointerLeave = options.allowInteraction || options.captureActive - }); + }, + synthesizeFocusGained, + synthesizeFocusLost); } std::vector FilterUIEditorTreePanelPointerInputEvents( diff --git a/new_editor/src/Shell/UIEditorStructuredShell.cpp b/new_editor/src/Shell/UIEditorStructuredShell.cpp new file mode 100644 index 00000000..bd3f5884 --- /dev/null +++ b/new_editor/src/Shell/UIEditorStructuredShell.cpp @@ -0,0 +1,25 @@ +#include + +namespace XCEngine::UI::Editor { + +StructuredEditorShellBinding BuildStructuredEditorShellBinding(const EditorShellAsset& asset) { + StructuredEditorShellBinding binding = {}; + binding.screenAsset.screenId = asset.screenId; + binding.screenAsset.documentPath = asset.documentPath.string(); + binding.workspaceController = + UIEditorWorkspaceController(asset.panelRegistry, asset.workspace, asset.workspaceSession); + binding.shellDefinition = asset.shellDefinition; + binding.shortcutManager = BuildEditorShellShortcutManager(asset); + binding.assetValidation = ValidateEditorShellAsset(asset); + return binding; +} + +UIEditorShellInteractionServices BuildStructuredEditorShellServices( + const StructuredEditorShellBinding& binding) { + UIEditorShellInteractionServices services = {}; + services.commandDispatcher = &binding.shortcutManager.GetCommandDispatcher(); + services.shortcutManager = &binding.shortcutManager; + return services; +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Viewport/UIEditorViewportInputBridge.cpp b/new_editor/src/Viewport/UIEditorViewportInputBridge.cpp index e7cdf114..dae8d6cd 100644 --- a/new_editor/src/Viewport/UIEditorViewportInputBridge.cpp +++ b/new_editor/src/Viewport/UIEditorViewportInputBridge.cpp @@ -73,6 +73,27 @@ void ClearInputState(UIEditorViewportInputBridgeState& state) { state.hovered = false; } +void ApplyFocusRequest( + UIEditorViewportInputBridgeState& state, + UIEditorViewportInputBridgeFrame& frame, + const UIEditorViewportInputBridgeRequest& request) { + if (request.focusMode != UIEditorViewportInputBridgeFocusMode::External || + state.focused == request.focused) { + return; + } + + if (!request.focused) { + frame.focusLost = state.focused; + frame.captureEnded = frame.captureEnded || state.captured; + state.focused = false; + ClearInputState(state); + return; + } + + state.focused = true; + frame.focusGained = true; +} + void UpdatePointerPosition( UIEditorViewportInputBridgeState& state, UIEditorViewportInputBridgeFrame& frame, @@ -150,13 +171,15 @@ UIEditorViewportInputBridgeFrame UpdateUIEditorViewportInputBridge( UIEditorViewportInputBridgeState& state, const UIRect& inputRect, const std::vector& events, - const UIEditorViewportInputBridgeConfig& config) { + const UIEditorViewportInputBridgeConfig& config, + const UIEditorViewportInputBridgeRequest& request) { return UpdateUIEditorViewportInputBridge( state, inputRect, inputRect, events, - config); + config, + request); } UIEditorViewportInputBridgeFrame UpdateUIEditorViewportInputBridge( @@ -164,12 +187,14 @@ UIEditorViewportInputBridgeFrame UpdateUIEditorViewportInputBridge( const UIRect& interactionRect, const UIRect& localRect, const std::vector& events, - const UIEditorViewportInputBridgeConfig& config) { + const UIEditorViewportInputBridgeConfig& config, + const UIEditorViewportInputBridgeRequest& request) { UIEditorViewportInputBridgeFrame frame = {}; frame.hasPointerPosition = state.hasPointerPosition; frame.screenPointerPosition = state.lastScreenPointerPosition; frame.localPointerPosition = state.lastLocalPointerPosition; frame.modifiers = state.modifiers; + ApplyFocusRequest(state, frame, request); for (const UIInputEvent& event : events) { const bool inside = ContainsPoint(interactionRect, event.position); @@ -199,7 +224,9 @@ UIEditorViewportInputBridgeFrame UpdateUIEditorViewportInputBridge( frame.changedPointerButton = event.pointerButton; if (inside) { frame.pointerPressedInside = true; - if (config.focusOnPointerDownInside && !state.focused) { + if (request.focusMode == UIEditorViewportInputBridgeFocusMode::SelfManaged && + config.focusOnPointerDownInside && + !state.focused) { state.focused = true; frame.focusGained = true; } @@ -208,7 +235,9 @@ UIEditorViewportInputBridgeFrame UpdateUIEditorViewportInputBridge( state.captureButton = event.pointerButton; frame.captureStarted = true; } - } else if (config.clearFocusOnPointerDownOutside) { + } else if ( + request.focusMode == UIEditorViewportInputBridgeFocusMode::SelfManaged && + config.clearFocusOnPointerDownOutside) { if (state.focused) { state.focused = false; frame.focusLost = true; diff --git a/new_editor/src/Viewport/UIEditorViewportShell.cpp b/new_editor/src/Viewport/UIEditorViewportShell.cpp index 1078b6db..41fcf793 100644 --- a/new_editor/src/Viewport/UIEditorViewportShell.cpp +++ b/new_editor/src/Viewport/UIEditorViewportShell.cpp @@ -68,7 +68,8 @@ UIEditorViewportShellFrame UpdateUIEditorViewportShell( const ::XCEngine::UI::UIRect& bounds, const UIEditorViewportShellModel& model, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - const Widgets::UIEditorViewportSlotMetrics& metrics) { + const Widgets::UIEditorViewportSlotMetrics& metrics, + const UIEditorViewportInputBridgeRequest& inputRequest) { UIEditorViewportShellFrame frame = {}; frame.slotLayout = BuildViewportShellLayout(bounds, model.spec, model.frame, metrics); frame.requestedViewportSize = frame.slotLayout.requestedSurfaceSize; @@ -77,7 +78,8 @@ UIEditorViewportShellFrame UpdateUIEditorViewportShell( frame.slotLayout.bounds, frame.slotLayout.inputRect, inputEvents, - model.spec.inputBridgeConfig); + model.spec.inputBridgeConfig, + inputRequest); frame.slotState = BuildViewportShellSlotState( model.spec.visualState, diff --git a/new_editor/src/Workspace/UIEditorWorkspaceCompose.cpp b/new_editor/src/Workspace/UIEditorWorkspaceCompose.cpp index ddafc2e1..bcb1402a 100644 --- a/new_editor/src/Workspace/UIEditorWorkspaceCompose.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceCompose.cpp @@ -200,7 +200,8 @@ UIEditorWorkspaceComposeFrame UpdateUIEditorWorkspaceCompose( const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, const Widgets::UIEditorDockHostState& dockHostState, const Widgets::UIEditorDockHostMetrics& dockHostMetrics, - const Widgets::UIEditorViewportSlotMetrics& viewportMetrics) { + const Widgets::UIEditorViewportSlotMetrics& viewportMetrics, + const UIEditorWorkspaceInputOwner* inputOwner) { UIEditorWorkspaceComposeFrame frame = {}; frame.dockHostLayout = BuildUIEditorDockHostLayout( bounds, @@ -238,12 +239,19 @@ UIEditorWorkspaceComposeFrame UpdateUIEditorWorkspaceCompose( viewportFrame.panelId = presentation.panelId; viewportFrame.bounds = contentHostPanelState->bounds; viewportFrame.viewportShellModel = presentation.viewportShellModel; + UIEditorViewportInputBridgeRequest inputRequest = {}; + if (inputOwner != nullptr) { + inputRequest.focusMode = UIEditorViewportInputBridgeFocusMode::External; + inputRequest.focused = + IsUIEditorWorkspaceViewportInputOwner(*inputOwner, presentation.panelId); + } viewportFrame.viewportShellFrame = UpdateUIEditorViewportShell( panelState.viewportShellState, contentHostPanelState->bounds, presentation.viewportShellModel, inputEvents, - viewportMetrics); + viewportMetrics, + inputRequest); frame.viewportFrames.push_back(std::move(viewportFrame)); } diff --git a/new_editor/src/Workspace/UIEditorWorkspaceInputOwner.cpp b/new_editor/src/Workspace/UIEditorWorkspaceInputOwner.cpp new file mode 100644 index 00000000..5b61ea24 --- /dev/null +++ b/new_editor/src/Workspace/UIEditorWorkspaceInputOwner.cpp @@ -0,0 +1,143 @@ +#include + +namespace XCEngine::UI::Editor { + +namespace { + +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; +using Widgets::HitTestUIEditorDockHost; +using Widgets::UIEditorDockHostHitTargetKind; + +bool ContainsPoint(const UIRect& rect, const UIPoint& point) { + return point.x >= rect.x && + point.x <= rect.x + rect.width && + point.y >= rect.y && + point.y <= rect.y + rect.height; +} + +bool HasUsableBounds(const UIRect& rect) { + return rect.width > 0.0f && rect.height > 0.0f; +} + +bool IsMountedContentPanelValid( + const UIEditorPanelContentHostPanelState& panelState) { + return panelState.mounted && HasUsableBounds(panelState.bounds); +} + +} // namespace + +std::string_view GetUIEditorWorkspaceInputOwnerKindName( + UIEditorWorkspaceInputOwnerKind kind) { + switch (kind) { + case UIEditorWorkspaceInputOwnerKind::None: + return "None"; + case UIEditorWorkspaceInputOwnerKind::DockHost: + return "DockHost"; + case UIEditorWorkspaceInputOwnerKind::HostedPanel: + return "HostedPanel"; + case UIEditorWorkspaceInputOwnerKind::Viewport: + return "Viewport"; + } + + return "Unknown"; +} + +bool AreUIEditorWorkspaceInputOwnersEquivalent( + const UIEditorWorkspaceInputOwner& lhs, + const UIEditorWorkspaceInputOwner& rhs) { + return lhs.kind == rhs.kind && lhs.panelId == rhs.panelId; +} + +bool IsUIEditorWorkspaceDockHostInputOwner( + const UIEditorWorkspaceInputOwner& owner) { + return owner.kind == UIEditorWorkspaceInputOwnerKind::DockHost; +} + +bool IsUIEditorWorkspaceHostedPanelInputOwner( + const UIEditorWorkspaceInputOwner& owner, + std::string_view panelId) { + return owner.kind == UIEditorWorkspaceInputOwnerKind::HostedPanel && + (panelId.empty() || owner.panelId == panelId); +} + +bool IsUIEditorWorkspaceViewportInputOwner( + const UIEditorWorkspaceInputOwner& owner, + std::string_view panelId) { + return owner.kind == UIEditorWorkspaceInputOwnerKind::Viewport && + (panelId.empty() || owner.panelId == panelId); +} + +UIEditorWorkspaceInputOwner ResolveUIEditorWorkspacePointerInputOwner( + const Widgets::UIEditorDockHostLayout& dockHostLayout, + const UIEditorPanelContentHostFrame& contentHostFrame, + const UIPoint& pointerPosition) { + for (const UIEditorPanelContentHostPanelState& panelState : + contentHostFrame.panelStates) { + if (!IsMountedContentPanelValid(panelState) || + !ContainsPoint(panelState.bounds, pointerPosition)) { + continue; + } + + UIEditorWorkspaceInputOwner owner = {}; + owner.panelId = panelState.panelId; + owner.kind = panelState.kind == UIEditorPanelPresentationKind::ViewportShell + ? UIEditorWorkspaceInputOwnerKind::Viewport + : UIEditorWorkspaceInputOwnerKind::HostedPanel; + return owner; + } + + const Widgets::UIEditorDockHostHitTarget hitTarget = + HitTestUIEditorDockHost(dockHostLayout, pointerPosition); + switch (hitTarget.kind) { + case UIEditorDockHostHitTargetKind::SplitterHandle: + case UIEditorDockHostHitTargetKind::TabStripBackground: + case UIEditorDockHostHitTargetKind::Tab: + case UIEditorDockHostHitTargetKind::PanelHeader: + case UIEditorDockHostHitTargetKind::PanelBody: + case UIEditorDockHostHitTargetKind::PanelFooter: + return { UIEditorWorkspaceInputOwnerKind::DockHost, {} }; + + case UIEditorDockHostHitTargetKind::None: + default: + return {}; + } +} + +UIEditorWorkspaceInputOwner NormalizeUIEditorWorkspaceInputOwner( + UIEditorWorkspaceInputOwner owner, + const Widgets::UIEditorDockHostLayout& dockHostLayout, + const UIEditorPanelContentHostFrame& contentHostFrame) { + switch (owner.kind) { + case UIEditorWorkspaceInputOwnerKind::None: + return {}; + + case UIEditorWorkspaceInputOwnerKind::DockHost: + return HasUsableBounds(dockHostLayout.bounds) + ? owner + : UIEditorWorkspaceInputOwner {}; + + case UIEditorWorkspaceInputOwnerKind::HostedPanel: + case UIEditorWorkspaceInputOwnerKind::Viewport: + for (const UIEditorPanelContentHostPanelState& panelState : + contentHostFrame.panelStates) { + if (!IsMountedContentPanelValid(panelState) || + panelState.panelId != owner.panelId) { + continue; + } + + const UIEditorWorkspaceInputOwnerKind expectedKind = + panelState.kind == UIEditorPanelPresentationKind::ViewportShell + ? UIEditorWorkspaceInputOwnerKind::Viewport + : UIEditorWorkspaceInputOwnerKind::HostedPanel; + return owner.kind == expectedKind + ? owner + : UIEditorWorkspaceInputOwner {}; + } + return {}; + } + + return {}; +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp b/new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp index d30d7782..44f5dc2b 100644 --- a/new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp @@ -20,6 +20,41 @@ bool HasMeaningfulViewportInputFrame(const UIEditorViewportInputBridgeFrame& fra !frame.characters.empty(); } +UIEditorWorkspaceInputOwner ResolveNextInputOwner( + UIEditorWorkspaceInputOwner currentOwner, + const Widgets::UIEditorDockHostLayout& dockHostLayout, + const UIEditorPanelContentHostFrame& contentHostFrame, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents) { + using ::XCEngine::UI::UIInputEventType; + + currentOwner = NormalizeUIEditorWorkspaceInputOwner( + std::move(currentOwner), + dockHostLayout, + contentHostFrame); + for (const ::XCEngine::UI::UIInputEvent& event : inputEvents) { + switch (event.type) { + case UIInputEventType::FocusLost: + currentOwner = {}; + break; + + case UIInputEventType::PointerButtonDown: + currentOwner = ResolveUIEditorWorkspacePointerInputOwner( + dockHostLayout, + contentHostFrame, + event.position); + break; + + default: + break; + } + } + + return NormalizeUIEditorWorkspaceInputOwner( + std::move(currentOwner), + dockHostLayout, + contentHostFrame); +} + } // namespace UIEditorWorkspaceInteractionFrame UpdateUIEditorWorkspaceInteraction( @@ -31,12 +66,44 @@ UIEditorWorkspaceInteractionFrame UpdateUIEditorWorkspaceInteraction( const Widgets::UIEditorDockHostMetrics& dockHostMetrics, const Widgets::UIEditorViewportSlotMetrics& viewportMetrics) { UIEditorWorkspaceInteractionFrame frame = {}; + frame.previousInputOwner = state.inputOwner; frame.dockHostFrame = UpdateUIEditorDockHostInteraction( state.dockHostInteractionState, controller, bounds, inputEvents, dockHostMetrics); + + const UIEditorWorkspaceComposeFrame previewComposeFrame = UpdateUIEditorWorkspaceCompose( + state.composeState, + bounds, + controller.GetPanelRegistry(), + controller.GetWorkspace(), + controller.GetSession(), + model.workspacePresentations, + {}, + state.dockHostInteractionState.dockHostState, + dockHostMetrics, + viewportMetrics); + state.inputOwner = ResolveNextInputOwner( + state.inputOwner, + previewComposeFrame.dockHostLayout, + previewComposeFrame.contentHostFrame, + inputEvents); + frame.inputOwner = state.inputOwner; + frame.inputOwnerChanged = !AreUIEditorWorkspaceInputOwnersEquivalent( + frame.previousInputOwner, + frame.inputOwner); + state.dockHostInteractionState.dockHostState.focused = + IsUIEditorWorkspaceDockHostInputOwner(frame.inputOwner); + frame.dockHostFrame.layout = Widgets::BuildUIEditorDockHostLayout( + bounds, + controller.GetPanelRegistry(), + controller.GetWorkspace(), + controller.GetSession(), + state.dockHostInteractionState.dockHostState, + dockHostMetrics); + frame.dockHostFrame.focused = state.dockHostInteractionState.dockHostState.focused; frame.composeFrame = UpdateUIEditorWorkspaceCompose( state.composeState, bounds, @@ -47,7 +114,8 @@ UIEditorWorkspaceInteractionFrame UpdateUIEditorWorkspaceInteraction( inputEvents, state.dockHostInteractionState.dockHostState, dockHostMetrics, - viewportMetrics); + viewportMetrics, + &frame.inputOwner); frame.result.dockHostResult = frame.dockHostFrame.result; frame.result.consumed = frame.dockHostFrame.result.consumed; diff --git a/tests/UI/Editor/CMakeLists.txt b/tests/UI/Editor/CMakeLists.txt index 3183222f..cc1a7976 100644 --- a/tests/UI/Editor/CMakeLists.txt +++ b/tests/UI/Editor/CMakeLists.txt @@ -5,18 +5,9 @@ project(XCEngine_EditorUITests) set(XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT "${CMAKE_SOURCE_DIR}/new_editor") -if(NOT EXISTS "${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app/Rendering/Native/AutoScreenshot.h" - OR NOT EXISTS "${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app/Platform/Win32/InputModifierTracker.h" - OR NOT EXISTS "${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app/Rendering/Native/NativeRenderer.h") - message(FATAL_ERROR - "Editor UI tests expect rendering/platform headers under " - "${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app.") -endif() - -include_directories("${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app") - add_subdirectory(unit) -add_subdirectory(integration) +add_subdirectory(smoke) +add_subdirectory(manual_validation) set(EDITOR_UI_UNIT_TEST_TARGETS editor_ui_tests @@ -32,8 +23,9 @@ add_custom_target(editor_ui_unit_tests ${EDITOR_UI_UNIT_TEST_TARGETS} ) -add_custom_target(editor_ui_all_tests +add_custom_target(editor_ui_validation_targets DEPENDS editor_ui_unit_tests - editor_ui_integration_tests + editor_ui_smoke_targets + editor_ui_manual_validation_scenarios ) diff --git a/tests/UI/Editor/integration/CMakeLists.txt b/tests/UI/Editor/manual_validation/CMakeLists.txt similarity index 66% rename from tests/UI/Editor/integration/CMakeLists.txt rename to tests/UI/Editor/manual_validation/CMakeLists.txt index 3faf9398..3e93f189 100644 --- a/tests/UI/Editor/integration/CMakeLists.txt +++ b/tests/UI/Editor/manual_validation/CMakeLists.txt @@ -1,16 +1,24 @@ file(TO_CMAKE_PATH "${CMAKE_SOURCE_DIR}" XCENGINE_EDITOR_UI_TESTS_REPO_ROOT_PATH) +if(NOT EXISTS "${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app/Rendering/Native/AutoScreenshot.h" + OR NOT EXISTS "${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app/Platform/Win32/InputModifierTracker.h" + OR NOT EXISTS "${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app/Rendering/Native/NativeRenderer.h") + message(FATAL_ERROR + "Editor UI manual validation expects rendering/platform headers under " + "${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app.") +endif() + function(xcengine_configure_editor_ui_integration_validation_target target) set(options) set(oneValueArgs OUTPUT_NAME) cmake_parse_arguments(XCEDITOR_VALIDATION "${options}" "${oneValueArgs}" "" ${ARGN}) if(NOT TARGET ${target}) - message(FATAL_ERROR "Integration validation target '${target}' does not exist.") + message(FATAL_ERROR "Manual validation target '${target}' does not exist.") endif() if(NOT XCEDITOR_VALIDATION_OUTPUT_NAME) - message(FATAL_ERROR "Integration validation target '${target}' requires OUTPUT_NAME.") + message(FATAL_ERROR "Manual validation target '${target}' requires OUTPUT_NAME.") endif() target_link_libraries(${target} PRIVATE @@ -33,7 +41,7 @@ add_subdirectory(shared) add_subdirectory(shell) add_subdirectory(state) -set(EDITOR_UI_INTEGRATION_TARGETS +set(EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_workspace_shell_compose_validation editor_ui_editor_shell_compose_validation editor_ui_editor_shell_interaction_validation @@ -48,136 +56,136 @@ set(EDITOR_UI_INTEGRATION_TARGETS ) if(TARGET editor_ui_status_bar_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_status_bar_basic_validation) endif() if(TARGET editor_ui_context_menu_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_context_menu_basic_validation) endif() if(TARGET editor_ui_viewport_slot_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_viewport_slot_basic_validation) endif() if(TARGET editor_ui_viewport_shell_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_viewport_shell_basic_validation) endif() if(TARGET editor_ui_workspace_viewport_compose_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_workspace_viewport_compose_validation) endif() if(TARGET editor_ui_viewport_input_bridge_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_viewport_input_bridge_basic_validation) endif() if(TARGET editor_ui_tree_view_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_tree_view_basic_validation) endif() if(TARGET editor_ui_tree_view_multiselect_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_tree_view_multiselect_validation) endif() if(TARGET editor_ui_tree_view_inline_rename_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_tree_view_inline_rename_validation) endif() if(TARGET editor_ui_property_grid_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_property_grid_basic_validation) endif() if(TARGET editor_ui_panel_content_host_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_panel_content_host_basic_validation) endif() if(TARGET editor_ui_panel_host_lifecycle_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_panel_host_lifecycle_validation) endif() if(TARGET editor_ui_bool_field_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_bool_field_basic_validation) endif() if(TARGET editor_ui_number_field_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_number_field_basic_validation) endif() if(TARGET editor_ui_asset_field_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_asset_field_basic_validation) endif() if(TARGET editor_ui_object_field_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_object_field_basic_validation) endif() if(TARGET editor_ui_text_field_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_text_field_basic_validation) endif() if(TARGET editor_ui_vector2_field_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_vector2_field_basic_validation) endif() if(TARGET editor_ui_vector3_field_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_vector3_field_basic_validation) endif() if(TARGET editor_ui_vector4_field_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_vector4_field_basic_validation) endif() if(TARGET editor_ui_enum_field_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_enum_field_basic_validation) endif() if(TARGET editor_ui_color_field_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_color_field_basic_validation) endif() if(TARGET editor_ui_list_view_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_list_view_basic_validation) endif() if(TARGET editor_ui_list_view_multiselect_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_list_view_multiselect_validation) endif() if(TARGET editor_ui_list_view_inline_rename_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_list_view_inline_rename_validation) endif() if(TARGET editor_ui_scroll_view_basic_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS + list(APPEND EDITOR_UI_MANUAL_VALIDATION_TARGETS editor_ui_scroll_view_basic_validation) endif() -add_custom_target(editor_ui_integration_tests +add_custom_target(editor_ui_manual_validation_scenarios DEPENDS - ${EDITOR_UI_INTEGRATION_TARGETS} + ${EDITOR_UI_MANUAL_VALIDATION_TARGETS} ) diff --git a/tests/UI/Editor/integration/README.md b/tests/UI/Editor/manual_validation/README.md similarity index 50% rename from tests/UI/Editor/integration/README.md rename to tests/UI/Editor/manual_validation/README.md index 0c9c7af2..c3e9b5fb 100644 --- a/tests/UI/Editor/integration/README.md +++ b/tests/UI/Editor/manual_validation/README.md @@ -1,6 +1,6 @@ -# Editor UI Integration +# Editor UI Manual Validation -This directory contains manual Editor UI validation scenarios under `tests/UI/Editor/integration/`. +This directory contains manual Editor UI validation scenarios under `tests/UI/Editor/manual_validation/`. Rules: @@ -11,13 +11,13 @@ Rules: Build: ```bash -cmake --build build --config Debug --target editor_ui_integration_tests +cmake --build build --config Debug --target editor_ui_manual_validation_scenarios ``` Screenshot output: -- Shared Editor integration hosts write screenshots only under the active CMake build directory. +- Shared Editor manual-validation hosts write screenshots only under the active CMake build directory. - The output root is the executable directory: `.../Debug/captures//`. -- Example output: `/tests/UI/Editor/integration/shell/object_field_basic/Debug/captures/object_field_basic/latest.png` -- History output: `/tests/UI/Editor/integration/shell/object_field_basic/Debug/captures/object_field_basic/history/__.png` +- Example output: `/tests/UI/Editor/manual_validation/shell/object_field_basic/Debug/captures/object_field_basic/latest.png` +- History output: `/tests/UI/Editor/manual_validation/shell/object_field_basic/Debug/captures/object_field_basic/history/__.png` - No new screenshot output is allowed under source-tree `tests/UI/.../captures/`. diff --git a/tests/UI/Editor/integration/shared/CMakeLists.txt b/tests/UI/Editor/manual_validation/shared/CMakeLists.txt similarity index 97% rename from tests/UI/Editor/integration/shared/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shared/CMakeLists.txt index 1aa43387..e0e82e90 100644 --- a/tests/UI/Editor/integration/shared/CMakeLists.txt +++ b/tests/UI/Editor/manual_validation/shared/CMakeLists.txt @@ -31,6 +31,7 @@ target_include_directories(editor_ui_integration_host ${CMAKE_SOURCE_DIR}/engine/include ${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/include ${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT} + ${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app ) target_compile_definitions(editor_ui_integration_host diff --git a/tests/UI/Editor/integration/shared/src/Application.cpp b/tests/UI/Editor/manual_validation/shared/src/Application.cpp similarity index 100% rename from tests/UI/Editor/integration/shared/src/Application.cpp rename to tests/UI/Editor/manual_validation/shared/src/Application.cpp diff --git a/tests/UI/Editor/integration/shared/src/Application.h b/tests/UI/Editor/manual_validation/shared/src/Application.h similarity index 100% rename from tests/UI/Editor/integration/shared/src/Application.h rename to tests/UI/Editor/manual_validation/shared/src/Application.h diff --git a/tests/UI/Editor/integration/shared/src/EditorValidationScenario.cpp b/tests/UI/Editor/manual_validation/shared/src/EditorValidationScenario.cpp similarity index 84% rename from tests/UI/Editor/integration/shared/src/EditorValidationScenario.cpp rename to tests/UI/Editor/manual_validation/shared/src/EditorValidationScenario.cpp index f3b78612..16962d16 100644 --- a/tests/UI/Editor/integration/shared/src/EditorValidationScenario.cpp +++ b/tests/UI/Editor/manual_validation/shared/src/EditorValidationScenario.cpp @@ -31,9 +31,9 @@ const std::array& GetEditorValidationScenarios() { "editor.shell.workspace_shell_compose", UIValidationDomain::Editor, "shell", - "Editor 壳层 | 工作区组合", - RepoRelative("tests/UI/Editor/integration/shell/workspace_shell_compose/View.xcui"), - RepoRelative("tests/UI/Editor/integration/shell/workspace_shell_compose/captures") + "Editor 壳层 | 工作区组?, + RepoRelative("tests/UI/Editor/manual_validation/shell/workspace_shell_compose/View.xcui"), + RepoRelative("tests/UI/Editor/manual_validation/shell/workspace_shell_compose/captures") } } }; diff --git a/tests/UI/Editor/integration/shared/src/EditorValidationScenario.h b/tests/UI/Editor/manual_validation/shared/src/EditorValidationScenario.h similarity index 100% rename from tests/UI/Editor/integration/shared/src/EditorValidationScenario.h rename to tests/UI/Editor/manual_validation/shared/src/EditorValidationScenario.h diff --git a/tests/UI/Editor/integration/shared/src/EditorValidationTheme.h b/tests/UI/Editor/manual_validation/shared/src/EditorValidationTheme.h similarity index 100% rename from tests/UI/Editor/integration/shared/src/EditorValidationTheme.h rename to tests/UI/Editor/manual_validation/shared/src/EditorValidationTheme.h diff --git a/tests/UI/Editor/integration/shell/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/asset_field_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/asset_field_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/asset_field_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/asset_field_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/asset_field_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/asset_field_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/asset_field_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/asset_field_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/asset_field_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/asset_field_basic/main.cpp similarity index 95% rename from tests/UI/Editor/integration/shell/asset_field_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/asset_field_basic/main.cpp index 7c7f9e13..1b93986f 100644 --- a/tests/UI/Editor/integration/shell/asset_field_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/asset_field_basic/main.cpp @@ -330,7 +330,7 @@ private: m_fieldPalette = XCEngine::UI::Editor::ResolveUIEditorAssetFieldPalette(); m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/asset_field_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/asset_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); ResetScenario(); return true; @@ -358,7 +358,7 @@ private: m_spec.emptyText = "None (Asset)"; ApplySampleAsset(0u); m_interactionState = {}; - m_lastResult = "已重置到默认 AssetField 状态"; + m_lastResult = "已重置到默认 AssetField 状?; m_activateCount = 0u; m_pressedAction = ActionId::None; } @@ -428,35 +428,35 @@ private: m_shellPalette, m_shellMetrics, "这个测试在验证什么功能?", - "验证 Editor 基础 AssetField 的固定风格、清晰 hit target,以及 activate / picker / clear 三类最小请求。"); + "验证 Editor 基础 AssetField 的固定风格、清?hit target,以?activate / picker / clear 三类最小请求?); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 74.0f), - "1. 字段视觉固定为 Unity 风格对象槽:标签、预览块、值文本、状态标记、选择按钮、清空按钮。", + "1. 字段视觉固定?Unity 风格对象槽:标签、预览块、值文本、状态标记、选择按钮、清空按钮?, m_shellPalette.textPrimary, m_shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 98.0f), - "2. 点击值区域只产生 activateRequested;基础层不决定 ping、打开面板或业务跳转。", + "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。", + "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,清空当前引用。", + "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 弹窗系统。", + "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 截图。", + "6. 左侧状态区观察 hover / focus / assetId / displayName / status / result;F12 截图?, m_shellPalette.textPrimary, m_shellMetrics.bodyFontSize); @@ -475,8 +475,8 @@ private: layout.stateRect, m_shellPalette, m_shellMetrics, - "状态摘要", - "重点看 hit、focus、值是否变化,以及请求是否保持薄语义。"); + "状态摘?, + "重点?hit、focus、值是否变化,以及请求是否保持薄语义?); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 72.0f), "Hover: " + DescribeHitTarget(currentHit), @@ -515,9 +515,9 @@ private: 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/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/asset_field_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()), m_shellPalette.textWeak, m_shellMetrics.bodyFontSize); @@ -528,7 +528,7 @@ private: m_shellPalette, m_shellMetrics, "AssetField 预览", - "这里只放一个基础 AssetField,不接业务面板,也不接 picker。"); + "这里只放一个基础 AssetField,不接业务面板,也不?picker?); AppendUIEditorAssetField( drawList, layout.fieldRect, @@ -560,7 +560,7 @@ private: refreshNeeded = true; } else if (result.activateRequested) { ++m_activateCount; - m_lastResult = "收到 activateRequested;基础层只发请求,不绑定业务动作"; + m_lastResult = "收到 activateRequested;基础层只发请求,不绑定业务动?; } else if (result.focusChanged) { m_lastResult = std::string("焦点变化: ") + (m_interactionState.fieldState.focused ? "focused" : "lost"); @@ -581,7 +581,7 @@ private: case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; break; case ActionId::None: @@ -679,7 +679,7 @@ private: case WM_SYSKEYDOWN: if (wParam == VK_F12) { m_autoScreenshot.RequestCapture("manual_f12"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; return 0; } if (wParam == VK_F6) { diff --git a/tests/UI/Editor/integration/shell/bool_field_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/bool_field_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/bool_field_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/bool_field_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/bool_field_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/bool_field_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/bool_field_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/bool_field_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/bool_field_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/bool_field_basic/main.cpp similarity index 95% rename from tests/UI/Editor/integration/shell/bool_field_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/bool_field_basic/main.cpp index 88a394e1..8d006f58 100644 --- a/tests/UI/Editor/integration/shell/bool_field_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/bool_field_basic/main.cpp @@ -285,7 +285,7 @@ private: if (app != nullptr) { if (wParam == VK_F12) { app->m_autoScreenshot.RequestCapture("manual_f12"); - app->m_lastResult = "已请求截图,输出到 captures/latest.png"; + app->m_lastResult = "已请求截图,输出?captures/latest.png"; InvalidateRect(hwnd, nullptr, FALSE); return 0; } @@ -364,7 +364,7 @@ private: } m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/bool_field_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/bool_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); ResetScenario(); @@ -407,7 +407,7 @@ private: m_mousePosition = UIPoint(-1000.0f, -1000.0f); m_hoveredAction = ActionId::Reset; m_hasHoveredAction = false; - m_lastResult = "已重置到默认 BoolField 状态"; + m_lastResult = "已重置到默认 BoolField 状?; RefreshFrame(); } @@ -531,12 +531,12 @@ private: void UpdateResultText(const UIEditorBoolFieldInteractionResult& result) { if (result.valueChanged) { - m_lastResult = std::string("值已切换到: ") + (m_value ? "true" : "false"); + m_lastResult = std::string("值已切换? ") + (m_value ? "true" : "false"); return; } if (result.consumed) { - m_lastResult = "控件已消费输入"; + m_lastResult = "控件已消费输?; return; } @@ -551,7 +551,7 @@ private: case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; break; } } @@ -585,25 +585,25 @@ private: shellPalette, shellMetrics, "这个测试在验证什么功能?", - "验证 Editor BoolField 的点击切换、键盘切换和状态同步,样式固定为 Editor 字段风格。"); + "验证 Editor BoolField 的点击切换、键盘切换和状态同步,样式固定?Editor 字段风格?); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), - "1. 点击 row 或 checkbox,检查 true / false 是否稳定切换。", + "1. 点击 row ?checkbox,检?true / false 是否稳定切换?, shellPalette.textPrimary, 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( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), - "3. 检查 Hover / Focus / Value / Result 是否同步更新。", + "3. 检?Hover / Focus / Value / Result 是否同步更新?, shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), - "4. 按 F12 或点击截图按钮,确认自动截图路径正确。", + "4. ?F12 或点击截图按钮,确认自动截图路径正确?, shellPalette.textPrimary, shellMetrics.bodyFontSize); @@ -622,8 +622,8 @@ private: layout.stateRect, shellPalette, shellMetrics, - "状态摘要", - "重点检查 hit / focus / value / result。"); + "状态摘?, + "重点检?hit / focus / value / result?); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Hover: " + DescribeHitTarget(currentHit), @@ -631,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( @@ -652,9 +652,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/bool_field_basic/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/bool_field_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 190.0f), @@ -668,7 +668,7 @@ private: shellPalette, shellMetrics, "BoolField 预览", - "这里只放一个固定样式 BoolField,不混入其他业务控件。"); + "这里只放一个固定样?BoolField,不混入其他业务控件?); UIEditorBoolFieldSpec previewSpec = m_spec; previewSpec.value = m_value; AppendUIEditorBoolField( diff --git a/tests/UI/Editor/integration/shell/color_field_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/color_field_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/color_field_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/color_field_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/color_field_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/color_field_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/color_field_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/color_field_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/color_field_basic/captures/history/20260408_160549_1_startup.png b/tests/UI/Editor/manual_validation/shell/color_field_basic/captures/history/20260408_160549_1_startup.png similarity index 100% rename from tests/UI/Editor/integration/shell/color_field_basic/captures/history/20260408_160549_1_startup.png rename to tests/UI/Editor/manual_validation/shell/color_field_basic/captures/history/20260408_160549_1_startup.png diff --git a/tests/UI/Editor/integration/shell/color_field_basic/captures/latest.png b/tests/UI/Editor/manual_validation/shell/color_field_basic/captures/latest.png similarity index 100% rename from tests/UI/Editor/integration/shell/color_field_basic/captures/latest.png rename to tests/UI/Editor/manual_validation/shell/color_field_basic/captures/latest.png diff --git a/tests/UI/Editor/integration/shell/color_field_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/color_field_basic/main.cpp similarity index 95% rename from tests/UI/Editor/integration/shell/color_field_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/color_field_basic/main.cpp index def04ca4..7f92752d 100644 --- a/tests/UI/Editor/integration/shell/color_field_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/color_field_basic/main.cpp @@ -298,7 +298,7 @@ private: case WM_SYSKEYDOWN: if (app != nullptr && wParam == VK_F12) { app->m_autoScreenshot.RequestCapture("manual_f12"); - app->m_lastResult = "已请求截图,输出到 captures/latest.png"; + app->m_lastResult = "已请求截图,输出?captures/latest.png"; InvalidateRect(hwnd, nullptr, FALSE); return 0; } @@ -370,7 +370,7 @@ private: } m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/color_field_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/color_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); ResetScenario(); @@ -441,7 +441,7 @@ private: m_mousePosition = UIPoint(-1000.0f, -1000.0f); m_hoveredAction = ActionId::Reset; m_hasHoveredAction = false; - m_lastResult = "已重置到默认 ColorField 状态"; + m_lastResult = "已重置到默认 ColorField 状?; RefreshFrame(); } @@ -565,11 +565,11 @@ private: return; } if (result.popupClosed) { - m_lastResult = "已关闭拾色弹窗"; + m_lastResult = "已关闭拾色弹?; return; } if (result.consumed) { - m_lastResult = "控件已消费输入"; + m_lastResult = "控件已消费输?; return; } m_lastResult = "等待交互"; @@ -583,7 +583,7 @@ private: case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; break; } } @@ -623,30 +623,30 @@ private: shellPalette, shellMetrics, "这个测试在验证什么功能?", - "验证 Editor ColorField 的 swatch、popup、SV square、hue wheel、RGBA slider 和关闭行为。"); + "验证 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. 左侧重点看 Hex、RGBA、Popup 和 Result。", + "5. 左侧重点?Hex、RGBA、Popup ?Result?, shellPalette.textPrimary, shellMetrics.bodyFontSize); @@ -665,8 +665,8 @@ private: layout.stateRect, shellPalette, shellMetrics, - "状态摘要", - "重点看 hover、popup、hex、rgba 和结果。"); + "状态摘?, + "重点?hover、popup、hex、rgba 和结果?); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Hover: " + DescribeHitTarget(currentHit), @@ -695,9 +695,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/color_field_basic/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/color_field_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 190.0f), @@ -711,7 +711,7 @@ private: shellPalette, shellMetrics, "ColorField 预览", - "这里只放一个 ColorField,用来验证 Editor 基础字段的 popup 交互。"); + "这里只放一?ColorField,用来验?Editor 基础字段?popup 交互?); AppendUIEditorColorField( drawList, layout.fieldRect, diff --git a/tests/UI/Editor/integration/shell/context_menu_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/context_menu_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/context_menu_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/context_menu_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/context_menu_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/context_menu_basic/main.cpp similarity index 95% rename from tests/UI/Editor/integration/shell/context_menu_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/context_menu_basic/main.cpp index ad73be84..d5568ec3 100644 --- a/tests/UI/Editor/integration/shell/context_menu_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/context_menu_basic/main.cpp @@ -729,7 +729,7 @@ bool ScenarioApp::Initialize(HINSTANCE hInstance, int nCmdShow) { } m_autoScreenshot.Initialize( - (ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/context_menu_basic/captures") + (ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/context_menu_basic/captures") .lexically_normal()); UpdateRects(); return true; @@ -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) { @@ -901,9 +901,9 @@ void ScenarioApp::HandleMouseMove(float x, float y) { BuildSubmenuPopupEntry(*hoveredItem)); if (mutation.changed) { SetCustomResult( - "Hover 打开子菜单", + "Hover 打开子菜?, "Changed", - "已展开 `" + hoveredItem->label + "` 的 child popup。重点检查:右侧子菜单是否立即出现。"); + "已展开 `" + hoveredItem->label + "` ?child popup。重点检查:右侧子菜单是否立即出现?); dirty = true; } } else { @@ -913,7 +913,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) { SetCustomResult( "Hover 收起更深层子菜单", "Dismissed", - "鼠标移到普通菜单项后,更深层 child popup 已收起。Closed: " + + "鼠标移到普通菜单项后,更深?child popup 已收起。Closed: " + JoinClosedPopupIds(mutation)); dirty = true; } @@ -923,9 +923,9 @@ void ScenarioApp::HandleMouseMove(float x, float y) { m_menuSession.DismissFromFocusLoss(hoveredPopup->surfacePath); if (mutation.changed) { SetCustomResult( - "Hover popup 空白区", + "Hover popup 空白?, "Dismissed", - "鼠标停留在 popup 空白区后,更深层 child popup 已回收。Closed: " + + "鼠标停留?popup 空白区后,更深层 child popup 已回收。Closed: " + JoinClosedPopupIds(mutation)); dirty = true; } @@ -947,21 +947,21 @@ void ScenarioApp::HandleLeftClick(float x, float y) { hoveredItem->itemId, BuildSubmenuPopupEntry(*hoveredItem)); SetCustomResult( - "点击展开子菜单", + "点击展开子菜?, mutation.changed ? "Changed" : "NoOp", mutation.changed ? "已展开 `" + hoveredItem->label + - "` 子菜单。正常行为是 hover 也会直接展开。" - : "子菜单已经处于展开状态。"); + "` 子菜单。正常行为是 hover 也会直接展开? + : "子菜单已经处于展开状态?); InvalidateRect(m_hwnd, nullptr, FALSE); return; } if (!hoveredItem->enabled) { SetCustomResult( - "菜单项不可执行", + "菜单项不可执?, "Disabled", - "当前工作区状态下 `" + hoveredItem->label + "` 不可执行。"); + "当前工作区状态下 `" + hoveredItem->label + "` 不可执行?); InvalidateRect(m_hwnd, nullptr, FALSE); return; } @@ -981,7 +981,7 @@ void ScenarioApp::HandleLeftClick(float x, float y) { m_menuSession.DismissFromPointerDown(hoveredPopup->surfacePath); if (mutation.changed) { SetCustomResult( - "点击 popup 空白区", + "点击 popup 空白?, "Dismissed", "点击当前 popup 空白区后,仅更深层子菜单被关闭。Closed: " + JoinClosedPopupIds(mutation)); @@ -995,12 +995,12 @@ void ScenarioApp::HandleLeftClick(float x, float y) { const auto mutation = m_menuSession.DismissFromPointerDown(UIInputPath{ 999999u }); SetCustomResult( - "点击菜单外区域", + "点击菜单外区?, mutation.changed ? "Dismissed" : "NoOp", mutation.changed ? "点击外部区域后,整条菜单链已关闭。Closed: " + JoinClosedPopupIds(mutation) - : "菜单链没有变化。"); + : "菜单链没有变化?); ClearHoverWhenMenuClosed(); InvalidateRect(m_hwnd, nullptr, FALSE); } @@ -1017,7 +1017,7 @@ void ScenarioApp::HandleRightClick(float x, float y) { mutation.changed ? "右键菜单外部区域后,整条菜单链已关闭。Closed: " + JoinClosedPopupIds(mutation) - : "菜单链没有变化。"); + : "菜单链没有变化?); ClearHoverWhenMenuClosed(); InvalidateRect(m_hwnd, nullptr, FALSE); } @@ -1032,8 +1032,8 @@ void ScenarioApp::HandleRightClick(float x, float y) { "右键打开 ContextMenu", mutation.changed ? "Changed" : "NoOp", mutation.changed - ? "已在右键位置打开 root popup。重点检查:菜单应贴近鼠标位置出现。" - : "ContextMenu 状态没有变化。"); + ? "已在右键位置打开 root popup。重点检查:菜单应贴近鼠标位置出现? + : "ContextMenu 状态没有变化?); InvalidateRect(m_hwnd, nullptr, FALSE); } @@ -1047,7 +1047,7 @@ void ScenarioApp::HandleKeyDown(UINT keyCode) { mutation.changed ? "按下 Escape 后,topmost popup 已关闭。Closed: " + JoinClosedPopupIds(mutation) - : "当前没有可关闭的 popup。"); + : "当前没有可关闭的 popup?); ClearHoverWhenMenuClosed(); InvalidateRect(m_hwnd, nullptr, FALSE); break; @@ -1167,16 +1167,16 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height) drawList, m_headerRect, "这个测试验证什么功能?", - "本场景只验证 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); + "本场景只验证 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); - DrawCard(drawList, m_targetRect, "Context Target", "只在这里接受右键打开 ContextMenu。"); - DrawCard(drawList, m_stateRect, "状态摘要", "重点看 popup chain、anchor 和 Details 状态。"); - DrawCard(drawList, m_footerRect, "最近结果", "显示最近一次交互、命令状态和截图输出。"); + DrawCard(drawList, m_targetRect, "Context Target", "只在这里接受右键打开 ContextMenu?); + DrawCard(drawList, m_stateRect, "状态摘?, "重点?popup chain、anchor ?Details 状态?); + DrawCard(drawList, m_footerRect, "最近结?, "显示最近一次交互、命令状态和截图输出?); const UIRect targetSurface( m_targetRect.x + 18.0f, @@ -1192,26 +1192,26 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height) 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), "可见面板: " + 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); + 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), "已打开 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 链", 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 路径", 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), "当前激活面板", 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), "菜单验证: " + std::string(menuValidation.IsValid() ? "OK" : menuValidation.message), menuValidation.IsValid() ? kSuccess : kDanger, 12.0f); const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/context_menu_basic/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/context_menu_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText(UIPoint(m_footerRect.x + 18.0f, m_footerRect.y + 56.0f), "Action: " + m_lastActionName, kTextPrimary, 13.0f); drawList.AddText(UIPoint(m_footerRect.x + 18.0f, m_footerRect.y + 74.0f), "Result: " + m_lastStatusLabel + " | " + m_lastMessage, m_lastStatusColor, 12.0f); diff --git a/tests/UI/Editor/integration/shell/dock_host_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/dock_host_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/dock_host_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/dock_host_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/dock_host_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/dock_host_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/dock_host_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/dock_host_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/dock_host_basic/captures/history/20260408_190049_1_startup.png b/tests/UI/Editor/manual_validation/shell/dock_host_basic/captures/history/20260408_190049_1_startup.png similarity index 100% rename from tests/UI/Editor/integration/shell/dock_host_basic/captures/history/20260408_190049_1_startup.png rename to tests/UI/Editor/manual_validation/shell/dock_host_basic/captures/history/20260408_190049_1_startup.png diff --git a/tests/UI/Editor/integration/shell/dock_host_basic/captures/latest.png b/tests/UI/Editor/manual_validation/shell/dock_host_basic/captures/latest.png similarity index 100% rename from tests/UI/Editor/integration/shell/dock_host_basic/captures/latest.png rename to tests/UI/Editor/manual_validation/shell/dock_host_basic/captures/latest.png diff --git a/tests/UI/Editor/integration/shell/dock_host_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/dock_host_basic/main.cpp similarity index 94% rename from tests/UI/Editor/integration/shell/dock_host_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/dock_host_basic/main.cpp index 8545f99c..750428ab 100644 --- a/tests/UI/Editor/integration/shell/dock_host_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/dock_host_basic/main.cpp @@ -348,7 +348,7 @@ private: bool Initialize(HINSTANCE hInstance, int nCmdShow) { m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/dock_host_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/dock_host_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); WNDCLASSEXW windowClass = {}; @@ -412,7 +412,7 @@ private: m_cachedFrame = {}; m_pendingInputEvents.clear(); m_lastStatus = "Ready"; - m_lastMessage = "等待交互。这里只验证 DockHost 基础交互 contract,不接旧 editor 业务。"; + m_lastMessage = "等待交互。这里只验证 DockHost 基础交互 contract,不接旧 editor 业务?; m_lastColor = kWarning; } @@ -487,14 +487,14 @@ private: if (action == ActionId::Reset) { ResetScenario(); m_lastStatus = "Ready"; - m_lastMessage = "场景状态已重置。请重新检查 splitter drag / tab activate / panel close / active panel sync。"; + m_lastMessage = "场景状态已重置。请重新检?splitter drag / tab activate / panel close / active panel sync?; m_lastColor = kWarning; return; } m_autoScreenshot.RequestCapture("manual_button"); m_lastStatus = "Ready"; - m_lastMessage = "截图已排队,输出到 tests/UI/Editor/integration/shell/dock_host_basic/captures/。"; + m_lastMessage = "截图已排队,输出?tests/UI/Editor/manual_validation/shell/dock_host_basic/captures/?; m_lastColor = kWarning; } @@ -511,7 +511,7 @@ private: if (result.layoutResult.status != UIEditorWorkspaceLayoutOperationStatus::Rejected) { m_lastStatus = std::string(GetUIEditorWorkspaceLayoutOperationStatusName(result.layoutResult.status)); m_lastMessage = result.layoutResult.message.empty() - ? std::string("Layout 操作已完成。") + ? std::string("Layout 操作已完成?) : result.layoutResult.message; m_lastColor = result.layoutResult.status == UIEditorWorkspaceLayoutOperationStatus::Changed @@ -523,7 +523,7 @@ private: if (result.commandResult.status != UIEditorWorkspaceCommandStatus::Rejected) { m_lastStatus = std::string(GetUIEditorWorkspaceCommandStatusName(result.commandResult.status)); m_lastMessage = result.commandResult.message.empty() - ? std::string("Workspace 命令已完成。") + ? std::string("Workspace 命令已完成?) : result.commandResult.message; m_lastColor = result.commandResult.status == UIEditorWorkspaceCommandStatus::Changed @@ -534,14 +534,14 @@ private: if (result.requestPointerCapture) { m_lastStatus = "Capture"; - m_lastMessage = "Splitter drag 已开始,宿主已收到 pointer capture 请求。"; + m_lastMessage = "Splitter drag 已开始,宿主已收?pointer capture 请求?; m_lastColor = kSuccess; return; } if (result.releasePointerCapture) { m_lastStatus = "Release"; - m_lastMessage = "Splitter drag 已结束,宿主已执行 pointer release。"; + m_lastMessage = "Splitter drag 已结束,宿主已执?pointer release?; m_lastColor = kWarning; return; } @@ -555,7 +555,7 @@ private: if (result.consumed) { m_lastStatus = "Consumed"; - m_lastMessage = "这次输入被 DockHost 基础交互层消费。"; + m_lastMessage = "这次输入?DockHost 基础交互层消费?; m_lastColor = kWarning; } } @@ -580,20 +580,20 @@ private: UIDrawList& drawList = drawData.EmplaceDrawList("DockHostBasic"); drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg); - 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. 验证 unified dock:tab activate / single-tab body 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 A / B,最后点 Details 或 Console 的 X。", kTextWeak, 11.0f); + 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. 验证 unified dock:tab activate / single-tab body 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 A / B,最后点 Details ?Console ?X?, kTextWeak, 11.0f); - DrawCard(drawList, m_controlsRect, "操作", "这里只保留当前场景必要按钮。"); + DrawCard(drawList, m_controlsRect, "操作", "这里只保留当前场景必要按钮?); for (const ButtonState& button : m_buttons) { DrawButton(drawList, button); } - DrawCard(drawList, m_stateRect, "状态", "重点检查 DockHost 基础交互当前状态。"); + DrawCard(drawList, m_stateRect, "状?, "重点检?DockHost 基础交互当前状态?); float stateY = m_stateRect.y + 66.0f; auto addStateLine = [&](std::string text, const UIColor& color = kTextPrimary, float fontSize = 12.0f) { drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY), std::move(text), color, fontSize); @@ -615,7 +615,7 @@ private: : m_interactionState.dockHostState.activeSplitterNodeId), kTextWeak, 11.0f); - addStateLine("根分割比例: " + 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( @@ -627,14 +627,14 @@ private: addStateLine( "截图: " + (m_autoScreenshot.HasPendingCapture() - ? std::string("截图排队中...") + ? std::string("截图排队?..") : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 或 按钮 -> captures/") + ? std::string("F12 ?按钮 -> captures/") : m_autoScreenshot.GetLastCaptureSummary())), kTextWeak, 11.0f); - DrawCard(drawList, m_previewRect, "预览", "真实 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); diff --git a/tests/UI/Editor/integration/shell/editor_shell_compose/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/editor_shell_compose/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/editor_shell_compose/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/editor_shell_compose/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/editor_shell_compose/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/editor_shell_compose/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/editor_shell_compose/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/editor_shell_compose/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/editor_shell_compose/main.cpp b/tests/UI/Editor/manual_validation/shell/editor_shell_compose/main.cpp similarity index 99% rename from tests/UI/Editor/integration/shell/editor_shell_compose/main.cpp rename to tests/UI/Editor/manual_validation/shell/editor_shell_compose/main.cpp index 9b5ccf0a..77a8b267 100644 --- a/tests/UI/Editor/integration/shell/editor_shell_compose/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/editor_shell_compose/main.cpp @@ -69,7 +69,7 @@ const auto kShellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPa const auto kShellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); constexpr const wchar_t* kWindowClassName = L"XCUIEditorShellComposeValidation"; -constexpr const wchar_t* kWindowTitle = L"XCUI �༭??| �������"; +constexpr const wchar_t* kWindowTitle = L"XCUI �༭??| ������?; constexpr UIColor kSuccess(0.46f, 0.72f, 0.50f, 1.0f); constexpr UIColor kDanger(0.78f, 0.35f, 0.35f, 1.0f); @@ -258,7 +258,7 @@ private: bool Initialize(HINSTANCE hInstance, int nCmdShow) { m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/editor_shell_compose/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/editor_shell_compose/captures"; m_autoScreenshot.Initialize(m_captureRoot); WNDCLASSEXW windowClass = {}; diff --git a/tests/UI/Editor/integration/shell/editor_shell_interaction/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/editor_shell_interaction/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/editor_shell_interaction/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/editor_shell_interaction/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/editor_shell_interaction/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/editor_shell_interaction/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/editor_shell_interaction/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/editor_shell_interaction/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp b/tests/UI/Editor/manual_validation/shell/editor_shell_interaction/main.cpp similarity index 93% rename from tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp rename to tests/UI/Editor/manual_validation/shell/editor_shell_interaction/main.cpp index b36ed4ba..93b28cba 100644 --- a/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/editor_shell_interaction/main.cpp @@ -451,7 +451,7 @@ EditorShellAsset BuildScenarioShellAsset() { presentation.viewportShellModel.spec.chrome.showBottomBar = true; presentation.viewportShellModel.frame.hasTexture = false; presentation.viewportShellModel.frame.statusText = - "该场景只验证 Editor Shell 交互契约,不接 editor 业务面板。"; + "该场景只验证 Editor Shell 交互契约,不?editor 业务面板?; } asset.shellDefinition.workspacePresentations.push_back(std::move(presentation)); } @@ -645,7 +645,7 @@ LRESULT CALLBACK ScenarioApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP bool ScenarioApp::Initialize(HINSTANCE hInstance, int nCmdShow) { m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/editor_shell_interaction/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/editor_shell_interaction/captures"; m_autoScreenshot.Initialize(m_captureRoot); WNDCLASSEXW windowClass = {}; @@ -685,7 +685,7 @@ bool ScenarioApp::Initialize(HINSTANCE hInstance, int nCmdShow) { ResetScenario(); if (IsAutoCaptureOnStartupEnabled()) { m_lastStatus = "Capture"; - m_lastMessage = "启动自动截图已排队。先核对首帧,再检查 root switch、child popup、dismiss 与 workspace suppression。"; + m_lastMessage = "启动自动截图已排队。先核对首帧,再检?root switch、child popup、dismiss ?workspace suppression?; m_lastColor = kWarning; } return true; @@ -730,7 +730,7 @@ void ScenarioApp::ResetScenario() { return; } m_lastStatus = "Ready"; - m_lastMessage = "等待交互。重点检查根菜单切换、hover 子菜单、关闭链路与 workspace 抑制;设 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可抓启动首帧。"; + m_lastMessage = "等待交互。重点检查根菜单切换、hover 子菜单、关闭链路与 workspace 抑制;设 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可抓启动首帧?; m_lastColor = kWarning; } void ScenarioApp::UpdateLayout() { @@ -795,7 +795,7 @@ 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; } @@ -803,7 +803,7 @@ void ScenarioApp::ExecuteAction(ActionId action) { m_autoScreenshot.RequestCapture("manual_button"); m_lastStatus = "Ready"; m_lastMessage = - "截图请求已排队。检查状态卡片中的 Output/latest.png。"; + "截图请求已排队。检查状态卡片中?Output/latest.png?; m_lastColor = kWarning; } bool ScenarioApp::HasInteractiveCaptureState() const { @@ -853,7 +853,7 @@ void ScenarioApp::SetInteractionResult(const UIEditorShellInteractionResult& res if (result.commandTriggered) { m_lastStatus = "Triggered"; - m_lastMessage = "命令已触发但尚未派发,请检查 dispatch 链路。"; + m_lastMessage = "命令已触发但尚未派发,请检?dispatch 链路?; m_lastColor = kWarning; return; } @@ -862,19 +862,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; @@ -908,27 +908,27 @@ void ScenarioApp::SetInteractionResult(const UIEditorShellInteractionResult& res if (result.requestPointerCapture) { m_lastStatus = "Capture"; - m_lastMessage = "控件请求了 pointer capture。"; + m_lastMessage = "控件请求?pointer capture?; m_lastColor = kSuccess; return; } if (result.releasePointerCapture) { m_lastStatus = "Release"; - m_lastMessage = "控件释放了 pointer capture。"; + 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() { @@ -965,34 +965,34 @@ void ScenarioApp::RenderFrame() { 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); + "只验?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("根菜单", 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_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("菜单模?, 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( "命令派发", @@ -1011,14 +1011,14 @@ void ScenarioApp::RenderFrame() { addStateLine( "截图", m_autoScreenshot.HasPendingCapture() - ? "截图请求排队中..." + ? "截图请求排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 或按钮 -> captures/") + ? std::string("F12 或按?-> captures/") : m_autoScreenshot.GetLastCaptureSummary()), kShellPalette.textWeak, 11.0f); - DrawCard(drawList, m_previewRect, "预览", "这里是实际 UIEditorShellInteraction 预览,不接 editor 业务层。"); + DrawCard(drawList, m_previewRect, "预览", "这里是实?UIEditorShellInteraction 预览,不?editor 业务层?); const auto palette = ResolveUIEditorShellInteractionPalette(); AppendUIEditorShellInteraction( drawList, diff --git a/tests/UI/Editor/integration/shell/enum_field_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/enum_field_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/enum_field_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/enum_field_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/enum_field_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/enum_field_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/enum_field_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/enum_field_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/enum_field_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/enum_field_basic/main.cpp similarity index 96% rename from tests/UI/Editor/integration/shell/enum_field_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/enum_field_basic/main.cpp index 252e9498..e6ebe915 100644 --- a/tests/UI/Editor/integration/shell/enum_field_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/enum_field_basic/main.cpp @@ -299,7 +299,7 @@ private: if (app != nullptr) { if (wParam == VK_F12) { app->m_autoScreenshot.RequestCapture("manual_f12"); - app->m_lastResult = "已请求截图,输出到 captures/latest.png"; + app->m_lastResult = "已请求截图,输出?captures/latest.png"; InvalidateRect(hwnd, nullptr, FALSE); return 0; } @@ -378,7 +378,7 @@ private: } m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/enum_field_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/enum_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); ResetScenario(); @@ -433,7 +433,7 @@ private: m_mousePosition = UIPoint(-1000.0f, -1000.0f); m_hoveredAction = ActionId::Reset; m_hasHoveredAction = false; - m_lastResult = "已重置到默认 EnumField 状态"; + m_lastResult = "已重置到默认 EnumField 状?; RefreshFrame(); } @@ -574,11 +574,11 @@ private: return; } if (result.popupClosed) { - m_lastResult = "下拉菜单已关闭"; + m_lastResult = "下拉菜单已关?; return; } if (result.consumed) { - m_lastResult = "控件已消费输入"; + m_lastResult = "控件已消费输?; return; } m_lastResult = "等待交互"; @@ -601,7 +601,7 @@ private: break; case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; break; } } @@ -634,30 +634,30 @@ private: shellPalette, shellMetrics, "这个测试在验证什么功能?", - "验证枚举字段的下拉打开/关闭、键盘切换、高亮同步,以及固定 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( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f), - "5. 按 F12 或点击截图按钮,确认自动截图路径正确。", + "5. ?F12 或点击截图按钮,确认自动截图路径正确?, shellPalette.textPrimary, shellMetrics.bodyFontSize); @@ -676,8 +676,8 @@ private: layout.stateRect, shellPalette, shellMetrics, - "状态摘要", - "重点检查 hit / popup / highlight / selected / result。"); + "状态摘?, + "重点检?hit / popup / highlight / selected / result?); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Hover: " + DescribeHitTarget(currentHit), @@ -685,7 +685,7 @@ 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( @@ -711,9 +711,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/enum_field_basic/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/enum_field_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 214.0f), @@ -727,7 +727,7 @@ private: shellPalette, shellMetrics, "EnumField 预览", - "这里只放一个固定样式的枚举字段预览。"); + "这里只放一个固定样式的枚举字段预览?); AppendUIEditorEnumField( drawList, layout.fieldRect, diff --git a/tests/UI/Editor/integration/shell/list_view_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/list_view_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/list_view_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/list_view_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/list_view_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/list_view_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/list_view_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/list_view_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/list_view_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/list_view_basic/main.cpp similarity index 93% rename from tests/UI/Editor/integration/shell/list_view_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/list_view_basic/main.cpp index ec5401f1..032d7e6f 100644 --- a/tests/UI/Editor/integration/shell/list_view_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/list_view_basic/main.cpp @@ -177,11 +177,11 @@ void DrawButton( std::vector BuildListItems() { return { - { "scene", "Scene.unity", "Scene | 最近修改: 1 分钟前", 0.0f }, - { "material", "Metal.mat", "Material | 4 个属性", 0.0f }, + { "scene", "Scene.unity", "Scene | 最近修? 1 分钟?, 0.0f }, + { "material", "Metal.mat", "Material | 4 个属?, 0.0f }, { "script", "PlayerController.cs", "C# Script | 1.8 KB", 0.0f }, { "texture", "Checker.png", "Texture2D | 1024x1024", 0.0f }, - { "prefab", "Robot.prefab", "Prefab | 7 个组件", 0.0f } + { "prefab", "Robot.prefab", "Prefab | 7 个组?, 0.0f } }; } @@ -200,7 +200,7 @@ std::string DescribeHitTarget( const UIEditorListViewHitTarget& hitTarget, const std::vector& items) { if (hitTarget.itemIndex >= items.size()) { - return "无"; + return "?; } return "row: " + items[hitTarget.itemIndex].primaryText; @@ -322,7 +322,7 @@ private: if (app != nullptr) { if (wParam == VK_F12) { app->m_autoScreenshot.RequestCapture("manual_f12"); - app->m_lastResult = "已请求截图,输出到 captures/latest.png"; + app->m_lastResult = "已请求截图,输出?captures/latest.png"; InvalidateRect(hwnd, nullptr, FALSE); return 0; } @@ -401,7 +401,7 @@ private: } m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/list_view_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/list_view_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); ResetScenario(); @@ -440,7 +440,7 @@ private: m_mousePosition = UIPoint(-1000.0f, -1000.0f); m_hoveredAction = ActionId::Reset; m_hasHoveredAction = false; - m_lastResult = "已重置到默认列表状态"; + m_lastResult = "已重置到默认列表状?; RefreshListFrame(); } @@ -586,22 +586,22 @@ private: } if (result.secondaryClicked && !result.selectedItemId.empty()) { - m_lastResult = "右键命中行: " + result.selectedItemId; + m_lastResult = "右键命中? " + result.selectedItemId; return; } if (result.selectionChanged && !result.selectedItemId.empty()) { - m_lastResult = "选中行: " + result.selectedItemId; + m_lastResult = "选中? " + result.selectedItemId; return; } if (!insideList && wasFocused && !m_interactionState.listViewState.focused) { - m_lastResult = "点击列表外空白: focus 已清除,selection 保留"; + m_lastResult = "点击列表外空? focus 已清除,selection 保留"; return; } if (insideList) { - m_lastResult = "点击列表内空白: 只更新 focus / hover"; + m_lastResult = "点击列表内空? 只更?focus / hover"; return; } @@ -616,7 +616,7 @@ private: case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; break; } } @@ -644,30 +644,30 @@ private: drawList, layout.introRect, "这个测试验证什么功能?", - "只验证 Editor ListView 基础控件:行布局、单选、focus 和键盘导航。不涉及任何业务面板。"); + "只验?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); @@ -679,7 +679,7 @@ private: m_hasHoveredAction && m_hoveredAction == button.action); } - DrawCard(drawList, layout.stateRect, "状态摘要", "重点检查 hit / focus / selection / current / items。"); + DrawCard(drawList, layout.stateRect, "状态摘?, "重点检?hit / focus / selection / current / items?); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Hover: " + DescribeHitTarget(currentHit, m_items), @@ -687,7 +687,7 @@ private: 12.0f); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 94.0f), - std::string("Focused: ") + (m_interactionState.listViewState.focused ? "开" : "关"), + std::string("Focused: ") + (m_interactionState.listViewState.focused ? "开" : "?), kTextPrimary, 12.0f); drawList.AddText( @@ -717,9 +717,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/list_view_basic/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/list_view_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 216.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, diff --git a/tests/UI/Editor/integration/shell/list_view_inline_rename/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/list_view_inline_rename/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/list_view_inline_rename/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/list_view_inline_rename/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/list_view_inline_rename/captures/history/20260409_011602_1_startup.png b/tests/UI/Editor/manual_validation/shell/list_view_inline_rename/captures/history/20260409_011602_1_startup.png similarity index 100% rename from tests/UI/Editor/integration/shell/list_view_inline_rename/captures/history/20260409_011602_1_startup.png rename to tests/UI/Editor/manual_validation/shell/list_view_inline_rename/captures/history/20260409_011602_1_startup.png diff --git a/tests/UI/Editor/integration/shell/list_view_inline_rename/captures/history/20260409_011741_1_startup.png b/tests/UI/Editor/manual_validation/shell/list_view_inline_rename/captures/history/20260409_011741_1_startup.png similarity index 100% rename from tests/UI/Editor/integration/shell/list_view_inline_rename/captures/history/20260409_011741_1_startup.png rename to tests/UI/Editor/manual_validation/shell/list_view_inline_rename/captures/history/20260409_011741_1_startup.png diff --git a/tests/UI/Editor/integration/shell/list_view_inline_rename/captures/history/20260409_011820_1_startup.png b/tests/UI/Editor/manual_validation/shell/list_view_inline_rename/captures/history/20260409_011820_1_startup.png similarity index 100% rename from tests/UI/Editor/integration/shell/list_view_inline_rename/captures/history/20260409_011820_1_startup.png rename to tests/UI/Editor/manual_validation/shell/list_view_inline_rename/captures/history/20260409_011820_1_startup.png diff --git a/tests/UI/Editor/integration/shell/list_view_inline_rename/captures/history/20260409_012145_1_startup.png b/tests/UI/Editor/manual_validation/shell/list_view_inline_rename/captures/history/20260409_012145_1_startup.png similarity index 100% rename from tests/UI/Editor/integration/shell/list_view_inline_rename/captures/history/20260409_012145_1_startup.png rename to tests/UI/Editor/manual_validation/shell/list_view_inline_rename/captures/history/20260409_012145_1_startup.png diff --git a/tests/UI/Editor/integration/shell/list_view_inline_rename/captures/history/20260409_012151_1_startup.png b/tests/UI/Editor/manual_validation/shell/list_view_inline_rename/captures/history/20260409_012151_1_startup.png similarity index 100% rename from tests/UI/Editor/integration/shell/list_view_inline_rename/captures/history/20260409_012151_1_startup.png rename to tests/UI/Editor/manual_validation/shell/list_view_inline_rename/captures/history/20260409_012151_1_startup.png diff --git a/tests/UI/Editor/integration/shell/list_view_inline_rename/captures/latest.png b/tests/UI/Editor/manual_validation/shell/list_view_inline_rename/captures/latest.png similarity index 100% rename from tests/UI/Editor/integration/shell/list_view_inline_rename/captures/latest.png rename to tests/UI/Editor/manual_validation/shell/list_view_inline_rename/captures/latest.png diff --git a/tests/UI/Editor/integration/shell/list_view_inline_rename/main.cpp b/tests/UI/Editor/manual_validation/shell/list_view_inline_rename/main.cpp similarity index 95% rename from tests/UI/Editor/integration/shell/list_view_inline_rename/main.cpp rename to tests/UI/Editor/manual_validation/shell/list_view_inline_rename/main.cpp index dad17914..f2ec4a46 100644 --- a/tests/UI/Editor/integration/shell/list_view_inline_rename/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/list_view_inline_rename/main.cpp @@ -33,7 +33,7 @@ #define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "." #endif -// 场景说明:验证固定样式下 ListView 的 inline rename 提交、取消与外部点击提交。 +// 场景说明:验证固定样式下 ListView ?inline rename 提交、取消与外部点击提交? namespace { @@ -514,7 +514,7 @@ bool ScenarioApp::Initialize(HINSTANCE hInstance, int nCmdShow) { return false; } - m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/list_view_inline_rename/captures"; + m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/list_view_inline_rename/captures"; m_autoScreenshot.Initialize(m_captureRoot); @@ -563,7 +563,7 @@ void ScenarioApp::ResetScenario() { m_lastModifiers = {}; m_lastCommittedItemId.clear(); m_lastCommittedValue.clear(); - m_lastResult = "已重置到默认状态,准备进入 script 的 inline rename。"; + m_lastResult = "已重置到默认状态,准备进入 script ?inline rename?; RefreshListFrame(); BeginRename("script"); } @@ -716,7 +716,7 @@ void ScenarioApp::HandleKeyDown(WPARAM wParam, LPARAM lParam) { if (wParam == VK_F12) { m_autoScreenshot.RequestCapture("manual_f12"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; InvalidateRect(m_hwnd, nullptr, FALSE); return; } @@ -841,7 +841,7 @@ void ScenarioApp::PumpRenameEvents(std::vector events) { void ScenarioApp::ApplyRenameFrame(const UIEditorInlineRenameSessionFrame& frame) { const auto& result = frame.result; if (result.sessionStarted) { - m_lastResult = "已进入 inline rename: " + result.itemId; + m_lastResult = "已进?inline rename: " + result.itemId; return; } @@ -853,18 +853,18 @@ void ScenarioApp::ApplyRenameFrame(const UIEditorInlineRenameSessionFrame& frame m_lastCommittedValue = result.valueAfter; RefreshListFrame(); } - m_lastResult = "已提交 rename: " + result.itemId + " -> " + result.valueAfter; + m_lastResult = "已提?rename: " + result.itemId + " -> " + result.valueAfter; return; } if (result.sessionCanceled) { - m_lastResult = "已取消 rename: " + result.itemId; + m_lastResult = "已取?rename: " + result.itemId; return; } if (m_renameState.active && result.textFieldResult.consumed) { m_lastResult = - "编辑中: " + m_renameState.itemId + " -> " + + "编辑? " + m_renameState.itemId + " -> " + m_renameState.textFieldInteraction.textFieldState.displayText; } } @@ -925,7 +925,7 @@ void ScenarioApp::UpdateListResultText(const UIEditorListViewInteractionResult& } if (result.keyboardNavigated && !result.selectedItemId.empty()) { - m_lastResult = "键盘导航到: " + result.selectedItemId; + m_lastResult = "键盘导航? " + result.selectedItemId; return; } @@ -935,7 +935,7 @@ void ScenarioApp::UpdateListResultText(const UIEditorListViewInteractionResult& } if (result.consumed && result.hitTarget.kind == UIEditorListViewHitTargetKind::Row) { - m_lastResult = "点击行: " + DescribeHitTarget(result.hitTarget, m_items); + m_lastResult = "点击? " + DescribeHitTarget(result.hitTarget, m_items); return; } @@ -949,7 +949,7 @@ void ScenarioApp::ExecuteAction(ActionId action) { break; case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; break; } } @@ -988,35 +988,35 @@ void ScenarioApp::RenderFrame() { 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); @@ -1035,8 +1035,8 @@ void ScenarioApp::RenderFrame() { layout.stateRect, shellPalette, shellMetrics, - "状态摘要", - "重点检查 ListView 选择状态和 rename 生命周期是否同步。"); + "状态摘?, + "重点检?ListView 选择状态和 rename 生命周期是否同步?); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Hover: " + DescribeHitTarget(currentHit, m_items), @@ -1090,9 +1090,9 @@ void ScenarioApp::RenderFrame() { const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/list_view_inline_rename/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/list_view_inline_rename/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 286.0f), @@ -1111,7 +1111,7 @@ void ScenarioApp::RenderFrame() { shellPalette, shellMetrics, "ListView 预览", - "这里只放一个 ListView,启动时默认直接进入 script 的 rename,便于截图和自检。" + "这里只放一?ListView,启动时默认直接进入 script ?rename,便于截图和自检? ); AppendUIEditorListViewBackground( drawList, diff --git a/tests/UI/Editor/integration/shell/list_view_multiselect/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/list_view_multiselect/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/list_view_multiselect/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/list_view_multiselect/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/list_view_multiselect/captures/history/20260409_001451_1_startup.png b/tests/UI/Editor/manual_validation/shell/list_view_multiselect/captures/history/20260409_001451_1_startup.png similarity index 100% rename from tests/UI/Editor/integration/shell/list_view_multiselect/captures/history/20260409_001451_1_startup.png rename to tests/UI/Editor/manual_validation/shell/list_view_multiselect/captures/history/20260409_001451_1_startup.png diff --git a/tests/UI/Editor/integration/shell/list_view_multiselect/captures/latest.png b/tests/UI/Editor/manual_validation/shell/list_view_multiselect/captures/latest.png similarity index 100% rename from tests/UI/Editor/integration/shell/list_view_multiselect/captures/latest.png rename to tests/UI/Editor/manual_validation/shell/list_view_multiselect/captures/latest.png diff --git a/tests/UI/Editor/integration/shell/list_view_multiselect/main.cpp b/tests/UI/Editor/manual_validation/shell/list_view_multiselect/main.cpp similarity index 96% rename from tests/UI/Editor/integration/shell/list_view_multiselect/main.cpp rename to tests/UI/Editor/manual_validation/shell/list_view_multiselect/main.cpp index b458a7ea..d55aa911 100644 --- a/tests/UI/Editor/integration/shell/list_view_multiselect/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/list_view_multiselect/main.cpp @@ -428,7 +428,7 @@ private: } m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/list_view_multiselect/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/list_view_multiselect/captures"; m_autoScreenshot.Initialize(m_captureRoot); @@ -676,7 +676,7 @@ private: } if (insideList) { - m_lastResult = "����б��ڿհף�ֻ��??hover / focus"; + m_lastResult = "����б��ڿհף�ֻ�??hover / focus"; return; } @@ -722,30 +722,30 @@ private: shellPalette, shellMetrics, "这个测试验证什么功能?", - "只验证 Editor ListView 多选 contract:Ctrl/Shift 选集、右键 primary 切换、键盘范围扩展,不涉及任何业务面板。"); + "只验?Editor ListView 多?contract:Ctrl/Shift 选集、右?primary 切换、键盘范围扩展,不涉及任何业务面板?); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), - "1. Ctrl+左键:对单行做 add/remove,多选集合必须稳定保留。", + "1. Ctrl+左键:对单行?add/remove,多选集合必须稳定保留?, shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), - "2. Shift+左键:以 anchor 为起点扩展范围,primary 应切到当前点击行。", + "2. Shift+左键:以 anchor 为起点扩展范围,primary 应切到当前点击行?, shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), - "3. 右键命中已选集合:只切换 primary/context target,不应破坏当前多选集合。", + "3. 右键命中已选集合:只切?primary/context target,不应破坏当前多选集合?, shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), - "4. 列表获得 focus 后按 Up/Down/Home/End;按住 Shift 时应扩展范围,不按 Shift 时应回到单选。", + "4. 列表获得 focus 后按 Up/Down/Home/End;按?Shift 时应扩展范围,不?Shift 时应回到单选?, shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f), - "5. 点击列表外空白只清除 focus;点击列表内空白只更新 hover/focus;F12 或按钮触发截图。", + "5. 点击列表外空白只清除 focus;点击列表内空白只更?hover/focus;F12 或按钮触发截图?, shellPalette.textPrimary, shellMetrics.bodyFontSize); @@ -764,8 +764,8 @@ private: layout.stateRect, shellPalette, shellMetrics, - "状态摘要", - "重点检查 primary / count / ids / anchor / current / hit / result。"); + "状态摘?, + "重点检?primary / count / ids / anchor / current / hit / result?); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Hover: " + DescribeHitTarget(currentHit, m_items), @@ -820,9 +820,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/list_view_multiselect/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/list_view_multiselect/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 286.0f), @@ -836,7 +836,7 @@ private: shellPalette, shellMetrics, "ListView 预览", - "这里只放一个 ListView。默认状态下已选中 material + script,方便直接检查多选 contract。"); + "这里只放一?ListView。默认状态下已选中 material + script,方便直接检查多?contract?); AppendUIEditorListViewBackground( drawList, m_frame.layout, diff --git a/tests/UI/Editor/integration/shell/menu_bar_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/menu_bar_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/menu_bar_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/menu_bar_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/menu_bar_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/menu_bar_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/menu_bar_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/menu_bar_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/menu_bar_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/menu_bar_basic/main.cpp similarity index 93% rename from tests/UI/Editor/integration/shell/menu_bar_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/menu_bar_basic/main.cpp index bdb4a076..37dc86c0 100644 --- a/tests/UI/Editor/integration/shell/menu_bar_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/menu_bar_basic/main.cpp @@ -812,7 +812,7 @@ bool ScenarioApp::Initialize(HINSTANCE hInstance, int nCmdShow) { } m_autoScreenshot.Initialize( - (ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/menu_bar_basic/captures") + (ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/menu_bar_basic/captures") .lexically_normal()); return true; } @@ -849,7 +849,7 @@ void ScenarioApp::ResetScenario() { SetCustomResult( "等待操作", "Pending", - "这个测试验证 Editor 菜单会话状态层:顶层菜单切换、child popup hover、Esc / outside click dismiss、命令派发。"); + "这个测试验证 Editor 菜单会话状态层:顶层菜单切换、child popup hover、Esc / outside click dismiss、命令派发?); return; #endif #if 0 @@ -868,7 +868,7 @@ void ScenarioApp::ResetScenario() { 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 { @@ -1012,7 +1012,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) { m_menuSession.DismissFromFocusLoss(hoveredButton->path); if (mutation.changed) { SetCustomResult( - "Hover 收起子菜单", + "Hover 收起子菜?, "Dismissed", "鼠标回到顶层按钮后,旧的 child popup 已收起。Closed: " + JoinClosedPopupIds(mutation)); @@ -1027,10 +1027,10 @@ void ScenarioApp::HandleMouseMove(float x, float y) { BuildSubmenuPopupEntry(*hoveredItem)); if (mutation.changed) { SetCustomResult( - "Hover 打开子菜单", + "Hover 打开子菜?, "Changed", "已展开 `" + hoveredItem->label + - "` 的 child popup。重点检查:右侧子菜单是否立即出现。"); + "` ?child popup。重点检查:右侧子菜单是否立即出现?); dirty = true; } } else { @@ -1038,7 +1038,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) { m_menuSession.DismissFromFocusLoss(hoveredItem->path); if (mutation.changed) { SetCustomResult( - "Hover 收起子菜单", + "Hover 收起子菜?, "Dismissed", "鼠标移到普通菜单项后,旧的 child popup 已收起。Closed: " + JoinClosedPopupIds(mutation)); @@ -1050,9 +1050,9 @@ void ScenarioApp::HandleMouseMove(float x, float y) { m_menuSession.DismissFromFocusLoss(hoveredPopup->surfacePath); if (mutation.changed) { SetCustomResult( - "Hover popup 空白区", + "Hover popup 空白?, "Dismissed", - "鼠标停留在 popup 空白区后,子菜单链已回收到当前 popup。Closed: " + + "鼠标停留?popup 空白区后,子菜单链已回收到当?popup。Closed: " + JoinClosedPopupIds(mutation)); dirty = true; } @@ -1077,7 +1077,7 @@ void ScenarioApp::HandleClick(float x, float y) { mutation.changed ? "再次点击当前顶层按钮后,整条菜单链已关闭。Closed: " + JoinClosedPopupIds(mutation) - : "当前顶层菜单没有发生变化。"); + : "当前顶层菜单没有发生变化?); } else { const auto mutation = m_menuSession.OpenMenuBarRoot( @@ -1088,8 +1088,8 @@ void ScenarioApp::HandleClick(float x, float y) { mutation.changed ? "Changed" : "NoOp", mutation.changed ? "已打开 `" + hoveredButton->label + - "`。重点检查:同一时刻只保留一个 root popup。" - : "顶层菜单状态没有发生变化。"); + "`。重点检查:同一时刻只保留一?root popup? + : "顶层菜单状态没有发生变化?); } ClearHoverWhenMenuClosed(); InvalidateRect(m_hwnd, nullptr, FALSE); @@ -1104,21 +1104,21 @@ void ScenarioApp::HandleClick(float x, float y) { hoveredItem->itemId, BuildSubmenuPopupEntry(*hoveredItem)); SetCustomResult( - "点击展开子菜单", + "点击展开子菜?, mutation.changed ? "Changed" : "NoOp", mutation.changed ? "已展开 `" + hoveredItem->label + - "` 子菜单。这个场景里正常行为是 hover 也会直接展开。" - : "子菜单已经处于展开状态。"); + "` 子菜单。这个场景里正常行为?hover 也会直接展开? + : "子菜单已经处于展开状态?); InvalidateRect(m_hwnd, nullptr, FALSE); return; } if (!hoveredItem->enabled) { SetCustomResult( - "菜单项不可执行", + "菜单项不可执?, "Disabled", - "当前工作区状态下 `" + hoveredItem->label + "` 不可执行。"); + "当前工作区状态下 `" + hoveredItem->label + "` 不可执行?); InvalidateRect(m_hwnd, nullptr, FALSE); return; } @@ -1138,7 +1138,7 @@ void ScenarioApp::HandleClick(float x, float y) { m_menuSession.DismissFromPointerDown(hoveredPopup->surfacePath); if (mutation.changed) { SetCustomResult( - "点击 popup 空白区", + "点击 popup 空白?, "Dismissed", "点击当前 popup 空白区后,仅更深层子菜单被关闭。Closed: " + JoinClosedPopupIds(mutation)); @@ -1152,12 +1152,12 @@ void ScenarioApp::HandleClick(float x, float y) { const auto mutation = m_menuSession.DismissFromPointerDown(UIInputPath { 999999u }); SetCustomResult( - "点击菜单外区域", + "点击菜单外区?, mutation.changed ? "Dismissed" : "NoOp", mutation.changed ? "点击外部区域后,整条菜单链已关闭。Closed: " + JoinClosedPopupIds(mutation) - : "菜单链没有变化。"); + : "菜单链没有变化?); ClearHoverWhenMenuClosed(); InvalidateRect(m_hwnd, nullptr, FALSE); } @@ -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; @@ -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; @@ -1447,16 +1447,16 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height) drawList, headerRect, "这个测试验证什么功能?", - "本场景只验证顶层菜单切换、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); + "本场景只验证顶层菜单切换、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); - DrawCard(drawList, shellRect, "操作区", "这里只保留 MenuBar 和当前 popup overlay。"); - DrawCard(drawList, stateRect, "状态摘要", "重点看 menu session 当前链路和 Details 状态,不放无关杂项。"); - DrawCard(drawList, footerRect, "最近结果", "显示最近一次交互、命令状态和截图输出。"); + DrawCard(drawList, shellRect, "操作?, "这里只保?MenuBar 和当?popup overlay?); + DrawCard(drawList, stateRect, "状态摘?, "重点?menu session 当前链路?Details 状态,不放无关杂项?); + DrawCard(drawList, footerRect, "最近结?, "显示最近一次交互、命令状态和截图输出?); const UIRect menuBarRect(shellRect.x + 18.0f, shellRect.y + 74.0f, shellRect.width - 36.0f, 46.0f); DrawMenuBar(drawList, menuBarRect, resolvedModel); @@ -1466,11 +1466,11 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height) drawList.AddRectOutline(shellInfoRect, kCardBorder, 1.0f, 8.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 链", 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 路径", 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(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 摘要", kAccent, 15.0f); drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 100.0f), "hover menu", kTextMuted, 12.0f); @@ -1481,25 +1481,25 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height) 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 摘要", 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 + 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), "可见面板", 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 可见", 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 激活", 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), "菜单模型验证", 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), "最近交互: " + 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 = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/menu_bar_basic/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/menu_bar_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText(UIPoint(footerRect.x + 18.0f, footerRect.y + 92.0f), captureSummary, kTextMuted, 12.0f); @@ -1631,16 +1631,16 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height) drawList, headerRect, "这个测试验证什么功能?", - "只验证 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); - drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 114.0f), "3. 点击菜单外区域或按 Escape,菜单必须立即关闭;Footer 会显示 Dismissed。", kTextPrimary, 13.0f); - drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 136.0f), "4. 点 Window -> Activate Details,再点 Window -> Hide Active,可检查 Details 的 checked 状态是否随 visible/active 正确变化。", kTextPrimary, 13.0f); - drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 158.0f), "5. 菜单右侧 shortcut 文案只做显示验证;F12 保存截图。", kTextPrimary, 13.0f); + "只验?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); + drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 114.0f), "3. 点击菜单外区域或?Escape,菜单必须立即关闭;Footer 会显?Dismissed?, kTextPrimary, 13.0f); + drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 136.0f), "4. ?Window -> Activate Details,再?Window -> Hide Active,可检?Details ?checked 状态是否随 visible/active 正确变化?, kTextPrimary, 13.0f); + drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 158.0f), "5. 菜单右侧 shortcut 文案只做显示验证;F12 保存截图?, kTextPrimary, 13.0f); - DrawCard(drawList, shellRect, "操作区", "这里只放菜单栏和当前打开的下拉菜单。"); - DrawCard(drawList, stateRect, "状态摘要", "看 open/hover/current workspace,确认菜单交互边界和命令派发结果。"); - DrawCard(drawList, footerRect, "最近结果", "显示最近一次菜单交互、命令状态和截图输出。"); + DrawCard(drawList, shellRect, "操作?, "这里只放菜单栏和当前打开的下拉菜单?); + DrawCard(drawList, stateRect, "状态摘?, "?open/hover/current workspace,确认菜单交互边界和命令派发结果?); + DrawCard(drawList, footerRect, "最近结?, "显示最近一次菜单交互、命令状态和截图输出?); const UIRect menuBarRect(shellRect.x + 18.0f, shellRect.y + 74.0f, shellRect.width - 36.0f, 46.0f); DrawMenuBar(drawList, menuBarRect, resolvedModel); @@ -1654,7 +1654,7 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height) drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 82.0f), m_hoveredMenuId.empty() ? "(none)" : m_hoveredMenuId, kTextPrimary, 14.0f); drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 110.0f), "Hover item", kTextMuted, 12.0f); drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 130.0f), m_hoveredItemId.empty() ? "(none)" : m_hoveredItemId, kTextPrimary, 14.0f); - drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 158.0f), "提示:按 R 可直接触发 Reset Workspace。", kTextMuted, 12.0f); + drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 158.0f), "提示:按 R 可直接触?Reset Workspace?, kTextMuted, 12.0f); if (!m_openMenuId.empty()) { const UIEditorResolvedMenuDescriptor* openMenu = nullptr; @@ -1724,9 +1724,9 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height) const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/menu_bar_basic/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/menu_bar_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(footerRect.x + 18.0f, footerRect.y + 66.0f), diff --git a/tests/UI/Editor/integration/shell/number_field_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/number_field_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/number_field_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/number_field_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/number_field_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/number_field_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/number_field_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/number_field_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/number_field_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/number_field_basic/main.cpp similarity index 95% rename from tests/UI/Editor/integration/shell/number_field_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/number_field_basic/main.cpp index f595353c..6e438578 100644 --- a/tests/UI/Editor/integration/shell/number_field_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/number_field_basic/main.cpp @@ -336,7 +336,7 @@ private: if (app != nullptr) { if (wParam == VK_F12) { app->m_autoScreenshot.RequestCapture("manual_f12"); - app->m_lastResult = "已请求截图,输出到 captures/latest.png"; + app->m_lastResult = "已请求截图,输出?captures/latest.png"; InvalidateRect(hwnd, nullptr, FALSE); return 0; } @@ -422,7 +422,7 @@ private: } m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/number_field_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/number_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); ResetScenario(); @@ -470,7 +470,7 @@ private: m_mousePosition = UIPoint(-1000.0f, -1000.0f); m_hoveredAction = ActionId::Reset; m_hasHoveredAction = false; - m_lastResult = "已重置到默认 NumberField 状态"; + m_lastResult = "已重置到默认 NumberField 状?; RefreshFrame(); } @@ -602,19 +602,19 @@ private: void UpdateResultText(const UIEditorNumberFieldInteractionResult& result) { if (result.editCommitRejected) { - m_lastResult = "提交失败,仍保持在编辑状态。"; + m_lastResult = "提交失败,仍保持在编辑状态?; return; } if (result.editCommitted) { - m_lastResult = std::string("已提交数值: ") + result.committedText; + m_lastResult = std::string("已提交数? ") + result.committedText; return; } if (result.editCanceled) { - m_lastResult = "已取消编辑。"; + m_lastResult = "已取消编辑?; return; } if (result.editStarted) { - m_lastResult = "已进入编辑状态。"; + m_lastResult = "已进入编辑状态?; return; } if (result.valueChanged || result.stepApplied) { @@ -622,7 +622,7 @@ private: return; } if (result.consumed) { - m_lastResult = "控件已消费输入。"; + m_lastResult = "控件已消费输入?; return; } m_lastResult = "等待交互"; @@ -636,7 +636,7 @@ private: case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; break; } } @@ -671,30 +671,30 @@ private: shellPalette, shellMetrics, "这个测试在验证什么功能?", - "验证 Inspector 宿主中的 NumberField 交互契约和默认宿主风格。"); + "验证 Inspector 宿主中的 NumberField 交互契约和默认宿主风格?); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), - "1. 点击 value box,检查是否进入编辑态,外观应是 Unity 风格单输入框。", + "1. 点击 value box,检查是否进入编辑态,外观应是 Unity 风格单输入框?, shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), - "2. 获得 focus 后按 Left / Right / Up / Down / Home / End,检查键盘步进。", + "2. 获得 focus 后按 Left / Right / Up / Down / Home / End,检查键盘步进?, shellPalette.textPrimary, 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( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), - "4. 检查 Hover / Focus / Editing / Value / Result 是否同步更新。", + "4. 检?Hover / Focus / Editing / Value / Result 是否同步更新?, shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f), - "5. 按 F12 或点击截图按钮,确认自动截图路径正确。", + "5. ?F12 或点击截图按钮,确认自动截图路径正确?, shellPalette.textPrimary, shellMetrics.bodyFontSize); @@ -713,8 +713,8 @@ private: layout.stateRect, shellPalette, shellMetrics, - "状态摘要", - "重点检查 hit / focus / editing / value / result。"); + "状态摘?, + "重点检?hit / focus / editing / value / result?); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Hover: " + DescribeHitTarget(currentHit), @@ -722,12 +722,12 @@ private: shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 94.0f), - std::string("Focused: ") + (m_interactionState.numberFieldState.focused ? "是" : "否"), + std::string("Focused: ") + (m_interactionState.numberFieldState.focused ? "? : "?), shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 118.0f), - std::string("Editing: ") + (m_interactionState.numberFieldState.editing ? "是" : "否"), + std::string("Editing: ") + (m_interactionState.numberFieldState.editing ? "? : "?), shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( @@ -748,9 +748,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/number_field_basic/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/number_field_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 214.0f), @@ -769,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); diff --git a/tests/UI/Editor/integration/shell/object_field_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/object_field_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/object_field_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/object_field_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/object_field_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/object_field_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/object_field_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/object_field_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/object_field_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/object_field_basic/main.cpp similarity index 93% rename from tests/UI/Editor/integration/shell/object_field_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/object_field_basic/main.cpp index b5d2dba0..214a9e4f 100644 --- a/tests/UI/Editor/integration/shell/object_field_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/object_field_basic/main.cpp @@ -302,7 +302,7 @@ private: m_fieldPalette = XCEngine::UI::Editor::ResolveUIEditorObjectFieldPalette(); m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/object_field_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/object_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); ResetScenario(); return true; @@ -336,7 +336,7 @@ private: m_interactionState = {}; m_activateCount = 0u; - m_lastResult = "已重置到默认 ObjectField 状态"; + m_lastResult = "已重置到默认 ObjectField 状?; m_hoveredAction = ActionId::Reset; m_pressedAction = ActionId::Reset; m_hasHoveredAction = false; @@ -375,7 +375,7 @@ private: void ApplyInteractionResult(const UIEditorObjectFieldInteractionResult& result) { if (result.activateRequested) { ++m_activateCount; - m_lastResult = "已触发 activateRequested"; + m_lastResult = "已触?activateRequested"; return; } @@ -383,17 +383,17 @@ private: m_spec.hasValue = false; m_spec.objectName.clear(); m_spec.objectTypeName.clear(); - m_lastResult = "已触发 clearRequested,当前引用已清空"; + m_lastResult = "已触?clearRequested,当前引用已清空"; return; } if (result.focusChanged) { - m_lastResult = std::string("焦点已切换: ") + (m_interactionState.fieldState.focused ? "focused" : "blurred"); + m_lastResult = std::string("焦点已切? ") + (m_interactionState.fieldState.focused ? "focused" : "blurred"); return; } if (result.consumed) { - m_lastResult = "输入已消费"; + m_lastResult = "输入已消?; return; } } @@ -409,7 +409,7 @@ private: break; case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "截图已排队,输出到 captures/latest.png"; + m_lastResult = "截图已排队,输出?captures/latest.png"; break; } } @@ -516,30 +516,30 @@ private: m_shellPalette, m_shellMetrics, "这个测试在验证什么功能?", - "验证 Unity 风格 ObjectField 的值框、类型标签、clear / picker 按钮,以及 focus、activate、clear 契约。"); + "验证 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。", + "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,应清空当前对象。", + "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 契约。", + "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 是否同步。", + "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 或点击截图按钮,可导出当前窗口截图。", + "5. ?F12 或点击截图按钮,可导出当前窗口截图?, m_shellPalette.textPrimary, m_shellMetrics.bodyFontSize); @@ -549,7 +549,7 @@ private: m_shellPalette, m_shellMetrics, "操作", - "这里只保留当前场景需要的最小操作。"); + "这里只保留当前场景需要的最小操作?); for (const ButtonLayout& button : layout.buttons) { DrawButton( drawList, @@ -564,8 +564,8 @@ private: layout.stateRect, m_shellPalette, m_shellMetrics, - "状态摘要", - "重点检查 hit、focus、value、activate、result。"); + "状态摘?, + "重点检?hit、focus、value、activate、result?); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 74.0f), "Hover: " + DescribeHitTarget(m_frame.result.hitTarget), @@ -573,12 +573,12 @@ private: m_shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 96.0f), - std::string("Focused: ") + (m_interactionState.fieldState.focused ? "是" : "否"), + 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 ? "是" : "否"), + std::string("Has Value: ") + (m_spec.hasValue ? "? : "?), m_spec.hasValue ? m_shellPalette.textSuccess : m_shellPalette.textMuted, m_shellMetrics.bodyFontSize); drawList.AddText( @@ -604,9 +604,9 @@ private: 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/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/object_field_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()), m_shellPalette.textWeak, m_shellMetrics.bodyFontSize); @@ -617,7 +617,7 @@ private: m_shellPalette, m_shellMetrics, "ObjectField 预览", - "这里只放一个 Unity 风格 ObjectField,不混入业务 Inspector。"); + "这里只放一?Unity 风格 ObjectField,不混入业务 Inspector?); AppendUIEditorObjectField( drawList, layout.fieldRect, @@ -689,7 +689,7 @@ private: case WM_SYSKEYDOWN: if (wParam == VK_F12) { m_autoScreenshot.RequestCapture("manual_f12"); - m_lastResult = "截图已排队,输出到 captures/latest.png"; + m_lastResult = "截图已排队,输出?captures/latest.png"; return 0; } if (const std::int32_t keyCode = MapObjectFieldKey(static_cast(wParam)); diff --git a/tests/UI/Editor/integration/shell/panel_content_host_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/panel_content_host_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/panel_content_host_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/panel_content_host_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/panel_content_host_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/panel_content_host_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/panel_content_host_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/panel_content_host_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/panel_content_host_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/panel_content_host_basic/main.cpp similarity index 95% rename from tests/UI/Editor/integration/shell/panel_content_host_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/panel_content_host_basic/main.cpp index fd1a17c9..bd8db593 100644 --- a/tests/UI/Editor/integration/shell/panel_content_host_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/panel_content_host_basic/main.cpp @@ -349,7 +349,7 @@ private: m_captureRoot = ResolveRepoRootPath() / - "tests/UI/Editor/integration/shell/panel_content_host_basic/captures"; + "tests/UI/Editor/manual_validation/shell/panel_content_host_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); ResetScenario(); return true; @@ -376,7 +376,7 @@ private: m_composeState = {}; m_lastStatus = "Ready"; m_lastMessage = - "默认状态下 doc-a 和 inspector 会挂到外部内容宿主,Console 仍然走占位内容路径。"; + "默认状态下 doc-a ?inspector 会挂到外部内容宿主,Console 仍然走占位内容路径?; UpdateComposeFrame(); } @@ -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/manual_validation/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, "这个测试验证什么功能?", "验证面板内容宿主在 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_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, "操作", "这里只保留当前契约验证需要的按钮。"); + DrawCard(drawList, m_controlsRect, "操作", "这里只保留当前契约验证需要的按钮?); for (const ButtonState& button : m_buttons) { DrawButton(drawList, button); } - DrawCard(drawList, m_stateRect, "状态", "重点检查已挂载面板集合和本帧挂载事件。"); + 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); @@ -541,14 +541,14 @@ private: addStateLine( "截图", m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() ? std::string("F12 或点按钮 -> captures/") : m_autoScreenshot.GetLastCaptureSummary()), kTextWeak, 11.0f); - DrawCard(drawList, m_previewRect, "预览", "DockHost 只画外壳;蓝色内容块表示外部内容宿主实际挂入的 body。"); + DrawCard(drawList, m_previewRect, "预览", "DockHost 只画外壳;蓝色内容块表示外部内容宿主实际挂入?body?); AppendUIEditorWorkspaceCompose(drawList, m_composeFrame); for (const UIEditorPanelContentHostPanelState& panelState : m_composeFrame.contentHostFrame.panelStates) { if (!panelState.mounted || diff --git a/tests/UI/Editor/integration/shell/panel_frame_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/panel_frame_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/panel_frame_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/panel_frame_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/panel_frame_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/panel_frame_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/panel_frame_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/panel_frame_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/panel_frame_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/panel_frame_basic/main.cpp similarity index 97% rename from tests/UI/Editor/integration/shell/panel_frame_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/panel_frame_basic/main.cpp index 027775a6..300575ed 100644 --- a/tests/UI/Editor/integration/shell/panel_frame_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/panel_frame_basic/main.cpp @@ -209,7 +209,7 @@ private: bool Initialize(HINSTANCE hInstance, int nCmdShow) { m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/panel_frame_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/panel_frame_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); WNDCLASSEXW windowClass = {}; @@ -406,29 +406,29 @@ private: 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), - "1. hover 各区域,检查命中 part 是否正确。", + "1. hover 各区域,检查命?part 是否正确?, kTextMuted, 12.0f); drawList.AddText( UIPoint(introRect.x + 16.0f, introRect.y + 90.0f), - "2. 点击 Header、Body、Footer、Pin、Close,检查状态变化。", + "2. 点击 Header、Body、Footer、Pin、Close,检查状态变化?, kTextMuted, 12.0f); drawList.AddText( UIPoint(introRect.x + 16.0f, introRect.y + 114.0f), - "3. 右侧只放一个 PanelFrame,不接业务面板。", + "3. 右侧只放一?PanelFrame,不接业务面板?, kTextWeak, 12.0f); - DrawCard(drawList, controlsRect, "操作", "只保留影响 PanelFrame contract 的开关。"); + DrawCard(drawList, controlsRect, "操作", "只保留影?PanelFrame contract 的开关?); for (const ButtonState& button : m_buttons) { DrawButton(drawList, button); } - DrawCard(drawList, stateRect, "状态摘要", "重点看 hover、active、focus、pinned 和结果。"); + DrawCard(drawList, stateRect, "状态摘?, "重点?hover、active、focus、pinned 和结果?); drawList.AddText( UIPoint(stateRect.x + 16.0f, stateRect.y + 66.0f), "Hover: " + DescribePart(m_hoveredPart), diff --git a/tests/UI/Editor/integration/shell/property_grid_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/property_grid_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/property_grid_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/property_grid_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/property_grid_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/property_grid_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/property_grid_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/property_grid_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/property_grid_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/property_grid_basic/main.cpp similarity index 95% rename from tests/UI/Editor/integration/shell/property_grid_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/property_grid_basic/main.cpp index 23f4360e..346e9f71 100644 --- a/tests/UI/Editor/integration/shell/property_grid_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/property_grid_basic/main.cpp @@ -445,7 +445,7 @@ private: if (app != nullptr) { if (wParam == VK_F12) { app->m_autoScreenshot.RequestCapture("manual_f12"); - app->m_lastResult = "已请求截图,输出到 captures/latest.png"; + app->m_lastResult = "已请求截图,输出?captures/latest.png"; InvalidateRect(hwnd, nullptr, FALSE); return 0; } @@ -535,7 +535,7 @@ private: } m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/property_grid_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/property_grid_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); ResetScenario(); return true; @@ -580,7 +580,7 @@ private: m_mousePosition = UIPoint(-1000.0f, -1000.0f); m_hoveredAction = ActionId::Reset; m_hasHoveredAction = false; - m_lastResult = "已重置到默认 PropertyGrid 状态"; + m_lastResult = "已重置到默认 PropertyGrid 状?; m_lastCommit = "(none)"; RefreshGridFrame(); } @@ -756,12 +756,12 @@ private: } if (result.fieldValueChanged) { - m_lastResult = "字段已更新: " + result.changedFieldId + " = " + result.changedValue; + m_lastResult = "字段已更? " + result.changedFieldId + " = " + result.changedValue; return; } if (result.editCommitRejected && !result.activeFieldId.empty()) { - m_lastResult = "提交被拒绝: " + result.activeFieldId; + m_lastResult = "提交被拒? " + result.activeFieldId; return; } @@ -776,12 +776,12 @@ private: } if (result.editValueChanged) { - m_lastResult = "编辑中: " + result.activeFieldId; + m_lastResult = "编辑? " + result.activeFieldId; return; } if (result.editStarted) { - m_lastResult = "开始编辑: " + result.activeFieldId; + m_lastResult = "开始编? " + result.activeFieldId; return; } @@ -806,12 +806,12 @@ private: } if (!insideGrid && wasFocused && !m_interactionState.propertyGridState.focused) { - m_lastResult = "点击外部后 focus 已清除"; + m_lastResult = "点击外部?focus 已清?; return; } if (insideGrid) { - m_lastResult = "Grid focus / hover 已更新"; + m_lastResult = "Grid focus / hover 已更?; return; } @@ -826,7 +826,7 @@ private: case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; break; } } @@ -861,31 +861,31 @@ private: layout.introRect, shellPalette, shellMetrics, - "这个测试在验证什么功能", - "只验证 Editor PropertyGrid 作为 typed 属性宿主的基础契约,不涉及任何 Inspector 业务逻辑。"); + "这个测试在验证什么功?, + "只验?Editor PropertyGrid 作为 typed 属性宿主的基础契约,不涉及任何 Inspector 业务逻辑?); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), - "1. 点击 section header,检查展开/折叠是否稳定,字段布局不能歪。", + "1. 点击 section header,检查展开/折叠是否稳定,字段布局不能歪?, shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), - "2. 点击 value host:Bool toggle、Vector4 宿主排版、Number/Text edit、Enum popup。", + "2. 点击 value host:Bool toggle、Vector4 宿主排版、Number/Text edit、Enum popup?, shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), - "3. Number / Text 编辑后按 Enter commit、Esc cancel;GUID 只读。", + "3. Number / Text 编辑后按 Enter commit、Esc cancel;GUID 只读?, shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), - "4. Grid 获得 focus 后按 Up / Down / Home / End,检查字段导航和 selection 同步。", + "4. Grid 获得 focus 后按 Up / Down / Home / End,检查字段导航和 selection 同步?, shellPalette.textPrimary, shellMetrics.bodyFontSize); 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 可自动截图?, shellPalette.textPrimary, shellMetrics.bodyFontSize); @@ -904,8 +904,8 @@ private: layout.stateRect, shellPalette, shellMetrics, - "状态摘要", - "重点检查 hit / focus / selection / edit / popup / commit。"); + "状态摘?, + "重点检?hit / focus / selection / edit / popup / commit?); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Hover: " + DescribeHitTarget(currentHit, m_sections), @@ -913,7 +913,7 @@ private: shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 94.0f), - std::string("Focused: ") + (m_interactionState.propertyGridState.focused ? "开" : "关"), + std::string("Focused: ") + (m_interactionState.propertyGridState.focused ? "开" : "?), shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( @@ -961,9 +961,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/property_grid_basic/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/property_grid_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 286.0f), @@ -982,7 +982,7 @@ private: shellPalette, shellMetrics, "PropertyGrid 预览", - "这里只放一个 PropertyGrid,用来验证 typed 属性宿主。"); + "这里只放一?PropertyGrid,用来验?typed 属性宿主?); AppendUIEditorPropertyGridBackground( drawList, m_gridFrame.layout, diff --git a/tests/UI/Editor/integration/shell/scroll_view_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/scroll_view_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/scroll_view_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/scroll_view_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/scroll_view_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/scroll_view_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/scroll_view_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/scroll_view_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/scroll_view_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/scroll_view_basic/main.cpp similarity index 94% rename from tests/UI/Editor/integration/shell/scroll_view_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/scroll_view_basic/main.cpp index ba1ff37f..b7d1c681 100644 --- a/tests/UI/Editor/integration/shell/scroll_view_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/scroll_view_basic/main.cpp @@ -308,7 +308,7 @@ private: case WM_SYSKEYDOWN: if (app != nullptr && wParam == VK_F12) { app->m_autoScreenshot.RequestCapture("manual_f12"); - app->m_lastResult = "已请求截图,输出到 captures/latest.png"; + app->m_lastResult = "已请求截图,输出?captures/latest.png"; InvalidateRect(hwnd, nullptr, FALSE); return 0; } @@ -380,7 +380,7 @@ private: } m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/scroll_view_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/scroll_view_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); ResetScenario(); @@ -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); } @@ -558,7 +558,7 @@ private: const UIEditorScrollViewInteractionResult& result, bool insideScrollView) { if (result.startedThumbDrag) { - m_lastResult = "开始拖拽 scrollbar thumb"; + m_lastResult = "开始拖?scrollbar thumb"; return; } @@ -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" + : "ScrollView focus 已清?; return; } if (insideScrollView) { - m_lastResult = "点击内容区:只验证 focus / hover / scrollbar"; + m_lastResult = "点击内容区:只验?focus / hover / scrollbar"; return; } - m_lastResult = "无变化"; + m_lastResult = "无变?; } void ExecuteAction(ActionId action) { @@ -595,7 +595,7 @@ private: case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; break; } } @@ -623,30 +623,30 @@ private: drawList, layout.introRect, "这个测试验证什么功能?", - "验证滚动视图的滚轮滚动、thumb 拖拽、focus 切换和 offset clamp。"); + "验证滚动视图的滚轮滚动、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,检查 offset 与 thumb 位置是否同步。", + "3. 拖拽 scrollbar thumb,检?offset ?thumb 位置是否同步?, kTextPrimary, 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), - "4. 点击内容区只更新 focus / hover;点击外部应能清掉 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); @@ -658,7 +658,7 @@ private: m_hasHoveredAction && m_hoveredAction == button.action); } - DrawCard(drawList, layout.stateRect, "状态摘要", "重点检查 hit / focus / thumb-drag / offset / overflow。"); + DrawCard(drawList, layout.stateRect, "状态摘?, "重点检?hit / focus / thumb-drag / offset / overflow?); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Hover: " + DescribeHitTarget(currentHit), @@ -666,13 +666,13 @@ 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( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 118.0f), std::string("Thumb Dragging: ") + - (m_interactionState.scrollViewState.draggingScrollbarThumb ? "是" : "否"), + (m_interactionState.scrollViewState.draggingScrollbarThumb ? "? : "?), kTextSuccess, 12.0f); drawList.AddText( @@ -683,7 +683,7 @@ private: 12.0f); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 166.0f), - std::string("Has Scrollbar: ") + (m_scrollFrame.layout.hasScrollbar ? "是" : "否"), + std::string("Has Scrollbar: ") + (m_scrollFrame.layout.hasScrollbar ? "? : "?), kTextMuted, 12.0f); drawList.AddText( @@ -699,9 +699,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/scroll_view_basic/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/scroll_view_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 238.0f), @@ -709,7 +709,7 @@ private: kTextWeak, 12.0f); - DrawCard(drawList, layout.previewRect, "ScrollView 预览", "这里只放一个 ScrollView,不混入任何上层业务内容。"); + DrawCard(drawList, layout.previewRect, "ScrollView 预览", "这里只放一?ScrollView,不混入任何上层业务内容?); AppendUIEditorScrollViewBackground( drawList, m_scrollFrame.layout, diff --git a/tests/UI/Editor/integration/shell/status_bar_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/status_bar_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/status_bar_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/status_bar_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/status_bar_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/status_bar_basic/main.cpp similarity index 95% rename from tests/UI/Editor/integration/shell/status_bar_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/status_bar_basic/main.cpp index 00e7ee45..7f64f13c 100644 --- a/tests/UI/Editor/integration/shell/status_bar_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/status_bar_basic/main.cpp @@ -223,7 +223,7 @@ private: bool Initialize(HINSTANCE hInstance, int nCmdShow) { m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/status_bar_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/status_bar_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); WNDCLASSEXW windowClass = {}; @@ -330,11 +330,11 @@ 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 = @@ -351,7 +351,7 @@ private: m_autoScreenshot.RequestCapture("manual_button"); InvalidateRect(m_hwnd, nullptr, FALSE); UpdateWindow(m_hwnd); - m_lastResult = "截图已排队"; + m_lastResult = "截图已排?; break; } UpdateHover(); @@ -430,29 +430,29 @@ private: drawList, introRect, "这个测试验证什么功能?", - "验证 StatusBar 的 leading/trailing 布局、separator、强调文本,以及 hover/active 命中。"); + "验证 StatusBar ?leading/trailing 布局、separator、强调文本,以及 hover/active 命中?); drawList.AddText( UIPoint(introRect.x + 16.0f, introRect.y + 66.0f), - "1. hover 不同 segment / separator,检查命中是否正确。", + "1. hover 不同 segment / separator,检查命中是否正确?, kTextMuted, 12.0f); drawList.AddText( UIPoint(introRect.x + 16.0f, introRect.y + 90.0f), - "2. 点击 segment,检查 active 是否切到对应项。", + "2. 点击 segment,检?active 是否切到对应项?, kTextMuted, 12.0f); drawList.AddText( UIPoint(introRect.x + 16.0f, introRect.y + 114.0f), - "3. 切换强调、separator 和 Selection slot,检查布局是否稳定。", + "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, "状态摘要", "重点看 hover、active、Selection slot、separator 和结果。"); + DrawCard(drawList, stateRect, "状态摘?, "重点?hover、active、Selection slot、separator 和结果?); drawList.AddText( UIPoint(stateRect.x + 16.0f, stateRect.y + 66.0f), "Hover: " + DescribeHitTarget(m_hoverTarget), @@ -480,9 +480,9 @@ private: 12.0f); const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("截图: F12 或按钮 -> status_bar_basic/captures/") + ? std::string("截图: F12 或按?-> status_bar_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(stateRect.x + 16.0f, stateRect.y + 196.0f), @@ -499,7 +499,7 @@ private: 18.0f); drawList.AddText( UIPoint(viewportRect.x + 18.0f, viewportRect.y + 48.0f), - "这里只放一个宿主区域,用来观察底部 StatusBar 的布局和状态变化。", + "这里只放一个宿主区域,用来观察底部 StatusBar 的布局和状态变化?, kTextMuted, 12.0f); diff --git a/tests/UI/Editor/integration/shell/tab_strip_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/tab_strip_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/tab_strip_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/tab_strip_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/tab_strip_basic/captures/history/20260408_182141_1_startup.png b/tests/UI/Editor/manual_validation/shell/tab_strip_basic/captures/history/20260408_182141_1_startup.png similarity index 100% rename from tests/UI/Editor/integration/shell/tab_strip_basic/captures/history/20260408_182141_1_startup.png rename to tests/UI/Editor/manual_validation/shell/tab_strip_basic/captures/history/20260408_182141_1_startup.png diff --git a/tests/UI/Editor/integration/shell/tab_strip_basic/captures/history/20260408_190049_1_startup.png b/tests/UI/Editor/manual_validation/shell/tab_strip_basic/captures/history/20260408_190049_1_startup.png similarity index 100% rename from tests/UI/Editor/integration/shell/tab_strip_basic/captures/history/20260408_190049_1_startup.png rename to tests/UI/Editor/manual_validation/shell/tab_strip_basic/captures/history/20260408_190049_1_startup.png diff --git a/tests/UI/Editor/integration/shell/tab_strip_basic/captures/latest.png b/tests/UI/Editor/manual_validation/shell/tab_strip_basic/captures/latest.png similarity index 100% rename from tests/UI/Editor/integration/shell/tab_strip_basic/captures/latest.png rename to tests/UI/Editor/manual_validation/shell/tab_strip_basic/captures/latest.png diff --git a/tests/UI/Editor/integration/shell/tab_strip_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/tab_strip_basic/main.cpp similarity index 95% rename from tests/UI/Editor/integration/shell/tab_strip_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/tab_strip_basic/main.cpp index c8ffe109..23f8ed4d 100644 --- a/tests/UI/Editor/integration/shell/tab_strip_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/tab_strip_basic/main.cpp @@ -325,7 +325,7 @@ private: bool Initialize(HINSTANCE hInstance, int nCmdShow) { m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/tab_strip_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/tab_strip_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); WNDCLASSEXW windowClass = {}; @@ -391,7 +391,7 @@ private: m_tabStripFrame = {}; m_tabItems.clear(); m_mousePosition = UIPoint(-1000.0f, -1000.0f); - m_lastResult = "已重置到默认标签状态"; + m_lastResult = "已重置到默认标签状?; } UIRect GetTabStripRect() const { @@ -504,7 +504,7 @@ private: if (result.hitTarget.kind == UIEditorTabStripHitTargetKind::HeaderBackground || result.hitTarget.kind == UIEditorTabStripHitTargetKind::Content) { - m_lastResult = "命中 TabStrip 背景,保留 focus"; + m_lastResult = "命中 TabStrip 背景,保?focus"; return; } @@ -586,28 +586,28 @@ private: drawList, introRect, "这个测试验证什么功能?", - "验证 TabStrip 的 header 命中、选中切换和键盘导航,不接业务面板。"); + "验证 TabStrip ?header 命中、选中切换和键盘导航,不接业务面板?); drawList.AddText( UIPoint(introRect.x + 16.0f, introRect.y + 68.0f), - "1. 点击 tab,检查 selected / active panel 是否同步。", + "1. 点击 tab,检?selected / active panel 是否同步?, kTextPrimary, 12.0f); drawList.AddText( UIPoint(introRect.x + 16.0f, introRect.y + 92.0f), - "2. 所有 tab 都没有关闭按钮;这里只验证命中、focus 和选中同步。", + "2. 所?tab 都没有关闭按钮;这里只验证命中、focus 和选中同步?, kTextMuted, 12.0f); drawList.AddText( UIPoint(introRect.x + 16.0f, introRect.y + 116.0f), - "3. Left / Right / Home / End 验证键盘导航;按重置恢复初始状态。", + "3. Left / Right / Home / End 验证键盘导航;按重置恢复初始状态?, kTextWeak, 12.0f); DrawCard( drawList, stateRect, - "状态摘要", - "持续显示 hover、focus、active panel、tabs、result 和校验结果。"); + "状态摘?, + "持续显示 hover、focus、active panel、tabs、result 和校验结果?); DrawButton(drawList, m_resetButtonRect, "重置", m_resetButtonHovered); const std::size_t selectedIndex = @@ -657,9 +657,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/tab_strip_basic/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/tab_strip_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(stateRect.x + 16.0f, stateRect.y + 302.0f), @@ -670,8 +670,8 @@ private: DrawCard( drawList, previewCardRect, - "预览区", - "这里只放一个 TabStrip 和一个 content placeholder,用来观察 header 与 content frame。"); + "预览?, + "这里只放一?TabStrip 和一?content placeholder,用来观?header ?content frame?); AppendUIEditorTabStripBackground(drawList, m_layout, m_tabStripState); AppendUIEditorTabStripForeground(drawList, m_layout, m_tabItems, m_tabStripState); @@ -691,12 +691,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 切换,再用 Left / Right / Home / End 验证键盘导航。", + "可点?Document B 切换,再?Left / Right / Home / End 验证键盘导航?, kTextWeak, 12.0f); diff --git a/tests/UI/Editor/integration/shell/text_field_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/text_field_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/text_field_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/text_field_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/text_field_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/text_field_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/text_field_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/text_field_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/text_field_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/text_field_basic/main.cpp similarity index 95% rename from tests/UI/Editor/integration/shell/text_field_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/text_field_basic/main.cpp index 108df075..1300707c 100644 --- a/tests/UI/Editor/integration/shell/text_field_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/text_field_basic/main.cpp @@ -26,8 +26,7 @@ #define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "." #endif -// 场景说明:验证固定样式下 TextField 的基础编辑、提交、取消与焦点切换。 - +// 场景说明:验证固定样式下 TextField 的基础编辑、提交、取消与焦点切换? namespace { using XCEngine::Input::KeyCode; @@ -331,7 +330,7 @@ private: if (app != nullptr) { if (wParam == VK_F12) { app->m_autoScreenshot.RequestCapture("manual_f12"); - app->m_lastResult = "已请求截图,输出到 captures/latest.png"; + app->m_lastResult = "已请求截图,输出?captures/latest.png"; InvalidateRect(hwnd, nullptr, FALSE); return 0; } @@ -421,7 +420,7 @@ private: } m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/text_field_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/text_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); ResetScenario(); @@ -465,7 +464,7 @@ private: m_mousePosition = UIPoint(-1000.0f, -1000.0f); m_hoveredAction = ActionId::Reset; m_hasHoveredAction = false; - m_lastResult = "已重置到默认 TextField 状态"; + m_lastResult = "已重置到默认 TextField 状?; RefreshFrame(); } @@ -605,15 +604,15 @@ private: void UpdateResultText(const UIEditorTextFieldInteractionResult& result) { if (result.editCommitted) { - m_lastResult = std::string("已提交文本: ") + result.committedText; + m_lastResult = std::string("已提交文? ") + result.committedText; return; } if (result.editCanceled) { - m_lastResult = "已取消编辑"; + m_lastResult = "已取消编?; return; } if (result.editStarted) { - m_lastResult = "已进入编辑态"; + m_lastResult = "已进入编辑?; return; } if (result.focusChanged) { @@ -621,7 +620,7 @@ private: return; } if (result.consumed) { - m_lastResult = "控件已消费输入"; + m_lastResult = "控件已消费输?; return; } m_lastResult = "等待交互"; @@ -635,7 +634,7 @@ private: case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; break; } } @@ -670,35 +669,35 @@ private: shellPalette, shellMetrics, "这个测试在验证什么功能?", - "验证 UIEditorTextField 的基础编辑契约,不涉及 PropertyGrid 或任何 Inspector 业务逻辑。"); + "验证 UIEditorTextField 的基础编辑契约,不涉及 PropertyGrid 或任?Inspector 业务逻辑?); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), - "1. 点击 value box,检查是否进入编辑态。", + "1. 点击 value box,检查是否进入编辑态?, shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), - "2. 获得 focus 后按 Enter 开始编辑;直接输入字符也应开始编辑。", + "2. 获得 focus 后按 Enter 开始编辑;直接输入字符也应开始编辑?, shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), - "3. 编辑态下按 Enter commit,按 Escape cancel。", + "3. 编辑态下?Enter commit,按 Escape cancel?, shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), - "4. 编辑态下按 F6 模拟 FocusLost,应提交暂存文本并退出编辑态。", + "4. 编辑态下?F6 模拟 FocusLost,应提交暂存文本并退出编辑态?, shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f), - "5. 检查 Hover / Focus / Editing / Value / Result 是否同步更新。", + "5. 检?Hover / Focus / Editing / Value / Result 是否同步更新?, shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 182.0f), - "6. 按 F12 或点击截图按钮,确认自动截图路径正确。", + "6. ?F12 或点击截图按钮,确认自动截图路径正确?, shellPalette.textPrimary, shellMetrics.bodyFontSize); @@ -717,8 +716,8 @@ private: layout.stateRect, shellPalette, shellMetrics, - "状态摘要", - "重点检查 hit / focus / editing / value / display / result。"); + "状态摘?, + "重点检?hit / focus / editing / value / display / result?); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Hover: " + DescribeHitTarget(currentHit), @@ -726,12 +725,12 @@ private: shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 94.0f), - std::string("Focused: ") + (m_interactionState.textFieldState.focused ? "是" : "否"), + std::string("Focused: ") + (m_interactionState.textFieldState.focused ? "? : "?), shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 118.0f), - std::string("Editing: ") + (m_interactionState.textFieldState.editing ? "是" : "否"), + std::string("Editing: ") + (m_interactionState.textFieldState.editing ? "? : "?), shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( @@ -752,9 +751,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/text_field_basic/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/text_field_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 214.0f), @@ -773,7 +772,7 @@ private: shellPalette, shellMetrics, "TextField 预览", - "这里只放一个固定样式 TextField,用来验证基础字段行为。"); + "这里只放一个固定样?TextField,用来验证基础字段行为?); drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor); drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f); drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground); diff --git a/tests/UI/Editor/integration/shell/tree_view_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/tree_view_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/tree_view_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/tree_view_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/tree_view_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/tree_view_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/tree_view_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/tree_view_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/tree_view_basic/captures/history/20260408_174622_1_startup.png b/tests/UI/Editor/manual_validation/shell/tree_view_basic/captures/history/20260408_174622_1_startup.png similarity index 100% rename from tests/UI/Editor/integration/shell/tree_view_basic/captures/history/20260408_174622_1_startup.png rename to tests/UI/Editor/manual_validation/shell/tree_view_basic/captures/history/20260408_174622_1_startup.png diff --git a/tests/UI/Editor/integration/shell/tree_view_basic/captures/latest.png b/tests/UI/Editor/manual_validation/shell/tree_view_basic/captures/latest.png similarity index 100% rename from tests/UI/Editor/integration/shell/tree_view_basic/captures/latest.png rename to tests/UI/Editor/manual_validation/shell/tree_view_basic/captures/latest.png diff --git a/tests/UI/Editor/integration/shell/tree_view_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/tree_view_basic/main.cpp similarity index 95% rename from tests/UI/Editor/integration/shell/tree_view_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/tree_view_basic/main.cpp index 0fd3cbea..6df2450b 100644 --- a/tests/UI/Editor/integration/shell/tree_view_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/tree_view_basic/main.cpp @@ -360,7 +360,7 @@ private: if (app != nullptr) { if (wParam == VK_F12) { app->m_autoScreenshot.RequestCapture("manual_f12"); - app->m_lastResult = "已请求截图,输出到 captures/latest.png"; + app->m_lastResult = "已请求截图,输出?captures/latest.png"; InvalidateRect(hwnd, nullptr, FALSE); return 0; } @@ -439,7 +439,7 @@ private: } m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/tree_view_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/tree_view_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); ResetScenario(); @@ -482,7 +482,7 @@ private: m_mousePosition = UIPoint(-1000.0f, -1000.0f); m_hoveredAction = ActionId::Reset; m_hasHoveredAction = false; - m_lastResult = "已重置到默认树结构"; + m_lastResult = "已重置到默认树结?; RefreshTreeFrame(); } @@ -622,7 +622,7 @@ private: } if (result.expansionChanged && result.selectionChanged && !result.selectedItemId.empty()) { - m_lastResult = "折叠后回收 selection: " + result.selectedItemId; + m_lastResult = "折叠后回?selection: " + result.selectedItemId; return; } @@ -632,7 +632,7 @@ private: } if (result.selectionChanged && !result.selectedItemId.empty()) { - m_lastResult = "选中行: " + result.selectedItemId; + m_lastResult = "选中? " + result.selectedItemId; return; } @@ -642,7 +642,7 @@ private: } if (insideTree) { - m_lastResult = "点击树内空白: 仅更新 focus / hover"; + m_lastResult = "点击树内空白: 仅更?focus / hover"; return; } @@ -657,7 +657,7 @@ private: case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; break; } } @@ -685,30 +685,30 @@ private: 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 必须能明确区分。", + "1. 点击 row:只切换 selection,hover / selected / focused 必须能明确区分?, kTextPrimary, 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), - "2. 点击 disclosure:只切换展开/折叠,不应误改 selection。", + "2. 点击 disclosure:只切换展开/折叠,不应误?selection?, kTextPrimary, 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), - "3. 按 Left / Right:验证折叠、展开,以及父子层级跳转。", + "3. ?Left / Right:验证折叠、展开,以及父子层级跳转?, kTextPrimary, 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), - "4. 按 Up / Down / Home / End:验证可见行导航,以及 current 与 selection 同步。", + "4. ?Up / Down / Home / End:验证可见行导航,以?current ?selection 同步?, kTextPrimary, 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); @@ -720,7 +720,7 @@ private: m_hasHoveredAction && m_hoveredAction == button.action); } - DrawCard(drawList, layout.stateRect, "状态摘要", "重点检查 hit / focus / selection / current / expanded / visible。"); + DrawCard(drawList, layout.stateRect, "状态摘?, "重点检?hit / focus / selection / current / expanded / visible?); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Hover: " + DescribeHitTarget(currentHit, m_items), @@ -764,9 +764,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/tree_view_basic/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/tree_view_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 238.0f), @@ -774,7 +774,7 @@ private: kTextWeak, 12.0f); - DrawCard(drawList, layout.previewRect, "TreeView 预览", "这里只放一个 TreeView,不混入 Hierarchy / Inspector 等业务内容。"); + DrawCard(drawList, layout.previewRect, "TreeView 预览", "这里只放一?TreeView,不混入 Hierarchy / Inspector 等业务内容?); AppendUIEditorTreeViewBackground( drawList, m_treeFrame.layout, diff --git a/tests/UI/Editor/integration/shell/tree_view_inline_rename/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/tree_view_inline_rename/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/tree_view_inline_rename/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/tree_view_inline_rename/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/tree_view_inline_rename/captures/history/20260409_023941_1_startup.png b/tests/UI/Editor/manual_validation/shell/tree_view_inline_rename/captures/history/20260409_023941_1_startup.png similarity index 100% rename from tests/UI/Editor/integration/shell/tree_view_inline_rename/captures/history/20260409_023941_1_startup.png rename to tests/UI/Editor/manual_validation/shell/tree_view_inline_rename/captures/history/20260409_023941_1_startup.png diff --git a/tests/UI/Editor/integration/shell/tree_view_inline_rename/captures/latest.png b/tests/UI/Editor/manual_validation/shell/tree_view_inline_rename/captures/latest.png similarity index 100% rename from tests/UI/Editor/integration/shell/tree_view_inline_rename/captures/latest.png rename to tests/UI/Editor/manual_validation/shell/tree_view_inline_rename/captures/latest.png diff --git a/tests/UI/Editor/integration/shell/tree_view_inline_rename/main.cpp b/tests/UI/Editor/manual_validation/shell/tree_view_inline_rename/main.cpp similarity index 95% rename from tests/UI/Editor/integration/shell/tree_view_inline_rename/main.cpp rename to tests/UI/Editor/manual_validation/shell/tree_view_inline_rename/main.cpp index 934299cd..50f7384e 100644 --- a/tests/UI/Editor/integration/shell/tree_view_inline_rename/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/tree_view_inline_rename/main.cpp @@ -32,7 +32,7 @@ #define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "." #endif -// 场景说明:验证固定样式下 TreeView 的 inline rename 启动、提交、取消与外部点击提交。 +// 场景说明:验证固定样式下 TreeView ?inline rename 启动、提交、取消与外部点击提交? namespace { using XCEngine::Input::KeyCode; @@ -335,7 +335,7 @@ bool ScenarioApp::Initialize(HINSTANCE hInstance, int nCmdShow) { if (!m_renderer.Initialize(m_hwnd)) { return false; } - m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/tree_view_inline_rename/captures"; + m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/tree_view_inline_rename/captures"; m_autoScreenshot.Initialize(m_captureRoot); ResetScenario(); return true; @@ -380,7 +380,7 @@ void ScenarioApp::ResetScenario() { m_hasHoveredAction = false; m_lastCommittedItemId.clear(); m_lastCommittedValue.clear(); - m_lastResult = "已重置到默认状态,准备进入 hero-mesh 的 inline rename。"; + m_lastResult = "已重置到默认状态,准备进入 hero-mesh ?inline rename?; RefreshTreeFrame(); BeginRename("hero-mesh"); } @@ -496,7 +496,7 @@ void ScenarioApp::HandleLeftButtonUp(float x, float y) { void ScenarioApp::HandleKeyDown(UINT virtualKey) { if (virtualKey == VK_F12) { m_autoScreenshot.RequestCapture("manual_f12"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; InvalidateRect(m_hwnd, nullptr, FALSE); return; } @@ -599,7 +599,7 @@ void ScenarioApp::PumpRenameEvents(std::vector events) { void ScenarioApp::ApplyRenameFrame(const UIEditorInlineRenameSessionFrame& frame) { const auto& result = frame.result; if (result.sessionStarted) { - m_lastResult = "已进入 inline rename: " + result.itemId; + m_lastResult = "已进?inline rename: " + result.itemId; return; } @@ -611,18 +611,18 @@ void ScenarioApp::ApplyRenameFrame(const UIEditorInlineRenameSessionFrame& frame m_lastCommittedValue = result.valueAfter; RefreshTreeFrame(); } - m_lastResult = "已提交 rename: " + result.itemId + " -> " + result.valueAfter; + m_lastResult = "已提?rename: " + result.itemId + " -> " + result.valueAfter; return; } if (result.sessionCanceled) { - m_lastResult = "已取消 rename: " + result.itemId; + m_lastResult = "已取?rename: " + result.itemId; return; } if (m_renameState.active && result.textFieldResult.consumed) { m_lastResult = - "编辑中: " + m_renameState.itemId + " -> " + + "编辑? " + m_renameState.itemId + " -> " + m_renameState.textFieldInteraction.textFieldState.displayText; } } @@ -633,7 +633,7 @@ void ScenarioApp::UpdateTreeResultText(const UIEditorTreeViewInteractionResult& return; } if (result.keyboardNavigated && !result.selectedItemId.empty()) { - m_lastResult = "键盘导航到: " + result.selectedItemId; + m_lastResult = "键盘导航? " + result.selectedItemId; return; } if (result.expansionChanged && !result.toggledItemId.empty()) { @@ -641,11 +641,11 @@ void ScenarioApp::UpdateTreeResultText(const UIEditorTreeViewInteractionResult& return; } if (result.selectionChanged && !result.selectedItemId.empty()) { - m_lastResult = "选中行: " + result.selectedItemId; + m_lastResult = "选中? " + result.selectedItemId; return; } if (result.consumed && result.hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { - m_lastResult = "点击行: " + DescribeHitTarget(result.hitTarget, m_items); + m_lastResult = "点击? " + DescribeHitTarget(result.hitTarget, m_items); return; } if (result.consumed && result.hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure) { @@ -725,7 +725,7 @@ void ScenarioApp::ExecuteAction(ActionId action) { break; case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; break; } } @@ -767,35 +767,35 @@ void ScenarioApp::RenderFrame() { 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 区。", + "1. 启动后默认进?hero-mesh rename;输入框只能覆盖 label 区?, 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:名称写回 TreeViewItem.label,并退出 rename。", + "3. ?Enter:名称写?TreeViewItem.label,并退?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 退出,再单击树节点后按 F2;F12 可截图。", + "6. 需要时先按 Esc 退出,再单击树节点后按 F2;F12 可截图?, shellPalette.textPrimary, shellMetrics.bodyFontSize); @@ -814,8 +814,8 @@ void ScenarioApp::RenderFrame() { layout.stateRect, shellPalette, shellMetrics, - "状态摘要", - "重点检查 TreeView 选择状态、可见索引和 rename 生命周期是否同步。"); + "状态摘?, + "重点检?TreeView 选择状态、可见索引和 rename 生命周期是否同步?); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Hover: " + DescribeHitTarget(currentHit, m_items), @@ -877,9 +877,9 @@ void ScenarioApp::RenderFrame() { const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/tree_view_inline_rename/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/tree_view_inline_rename/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 310.0f), @@ -897,7 +897,7 @@ void ScenarioApp::RenderFrame() { shellPalette, shellMetrics, "TreeView 预览", - "这里只放一个 TreeView,启动时默认直接进入 hero-mesh 的 rename,便于截图和自检。"); + "这里只放一?TreeView,启动时默认直接进入 hero-mesh ?rename,便于截图和自检?); AppendUIEditorTreeViewBackground( drawList, m_treeFrame.layout, diff --git a/tests/UI/Editor/integration/shell/tree_view_multiselect/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/tree_view_multiselect/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/tree_view_multiselect/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/tree_view_multiselect/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/tree_view_multiselect/captures/history/20260409_001604_1_startup.png b/tests/UI/Editor/manual_validation/shell/tree_view_multiselect/captures/history/20260409_001604_1_startup.png similarity index 100% rename from tests/UI/Editor/integration/shell/tree_view_multiselect/captures/history/20260409_001604_1_startup.png rename to tests/UI/Editor/manual_validation/shell/tree_view_multiselect/captures/history/20260409_001604_1_startup.png diff --git a/tests/UI/Editor/integration/shell/tree_view_multiselect/captures/history/20260409_001800_1_startup.png b/tests/UI/Editor/manual_validation/shell/tree_view_multiselect/captures/history/20260409_001800_1_startup.png similarity index 100% rename from tests/UI/Editor/integration/shell/tree_view_multiselect/captures/history/20260409_001800_1_startup.png rename to tests/UI/Editor/manual_validation/shell/tree_view_multiselect/captures/history/20260409_001800_1_startup.png diff --git a/tests/UI/Editor/integration/shell/tree_view_multiselect/captures/latest.png b/tests/UI/Editor/manual_validation/shell/tree_view_multiselect/captures/latest.png similarity index 100% rename from tests/UI/Editor/integration/shell/tree_view_multiselect/captures/latest.png rename to tests/UI/Editor/manual_validation/shell/tree_view_multiselect/captures/latest.png diff --git a/tests/UI/Editor/integration/shell/tree_view_multiselect/main.cpp b/tests/UI/Editor/manual_validation/shell/tree_view_multiselect/main.cpp similarity index 96% rename from tests/UI/Editor/integration/shell/tree_view_multiselect/main.cpp rename to tests/UI/Editor/manual_validation/shell/tree_view_multiselect/main.cpp index 396ce762..6c229014 100644 --- a/tests/UI/Editor/integration/shell/tree_view_multiselect/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/tree_view_multiselect/main.cpp @@ -414,7 +414,7 @@ private: if (app != nullptr) { if (wParam == VK_F12) { app->m_autoScreenshot.RequestCapture("manual_f12"); - app->m_lastResult = "已请求截图,输出到 captures/latest.png"; + app->m_lastResult = "已请求截图,输出?captures/latest.png"; app->m_resultFlags = "selectionChanged=false, expansionChanged=false, keyboardNavigated=false, secondaryClicked=false, consumed=true"; InvalidateRect(hwnd, nullptr, FALSE); @@ -495,7 +495,7 @@ private: } m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/tree_view_multiselect/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/tree_view_multiselect/captures"; m_autoScreenshot.Initialize(m_captureRoot); ResetScenario(); @@ -637,14 +637,14 @@ private: std::string actionLabel = "点击树外空白,focus 清除"; if (result.hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure) { - actionLabel = "点击 disclosure 切换展开,不改 selection"; + actionLabel = "点击 disclosure 切换展开,不?selection"; } else if (result.hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { if (modifiers.shift) { - actionLabel = "Shift+单击范围多选"; + actionLabel = "Shift+单击范围多?; } else if (modifiers.control) { - actionLabel = "Ctrl+单击切换多选"; + actionLabel = "Ctrl+单击切换多?; } else { - actionLabel = "单击单选"; + actionLabel = "单击单?; } } else if (insideTree) { actionLabel = "点击树内空白,只更新 focus / hover"; @@ -688,7 +688,7 @@ private: if (result.hitTarget.kind == UIEditorTreeViewHitTargetKind::Row || result.hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure) { if (targetAlreadySelected && selectedCountBefore > 1u) { - actionLabel = "右键命中已选集合,不打散 selection,仅切换 primary"; + actionLabel = "右键命中已选集合,不打?selection,仅切换 primary"; } else if (targetAlreadySelected) { actionLabel = "右键命中已选项,仅切换 primary"; } else { @@ -705,7 +705,7 @@ private: PumpTreeEvents({ MakeKeyEvent(keyCode, modifiers) }); std::string actionLabel = modifiers.shift - ? "Shift+键盘范围扩选" + ? "Shift+键盘范围扩? : "键盘导航"; if (keyCode == static_cast(KeyCode::Left) || keyCode == static_cast(KeyCode::Right)) { @@ -782,7 +782,7 @@ private: case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; m_resultFlags = "selectionChanged=false, expansionChanged=false, keyboardNavigated=false, secondaryClicked=false, consumed=true"; break; @@ -811,40 +811,40 @@ private: drawList, layout.introRect, "这个测试验证什么功能?", - "只验证 Editor TreeView 的多选契约,不混入 Hierarchy / Inspector 业务面板。"); + "只验?Editor TreeView 的多选契约,不混?Hierarchy / Inspector 业务面板?); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), - "1. 单击 row:应切回单选,primary / anchor / current 同步到该行。", + "1. 单击 row:应切回单选,primary / anchor / current 同步到该行?, kTextPrimary, 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( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), - "4. Left / Right:应验证展开、折叠,以及父子层级之间的 current / selection 跳转。", + "4. Left / Right:应验证展开、折叠,以及父子层级之间?current / selection 跳转?, kTextPrimary, 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f), - "5. 右键命中已选集合中的一项:不得打散 selection,只允许切换 primary。", + "5. 右键命中已选集合中的一项:不得打散 selection,只允许切换 primary?, kTextPrimary, 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 182.0f), - "6. 重点检查左侧状态:Primary / Count / Ids / Anchor / Current / Expanded / Visible / Result。", + "6. 重点检查左侧状态:Primary / Count / Ids / Anchor / Current / Expanded / Visible / Result?, kTextPrimary, 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 204.0f), - "7. 按 F12 手动截图;设置 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可做启动自动截图。", + "7. ?F12 手动截图;设?XCUI_AUTO_CAPTURE_ON_STARTUP=1 可做启动自动截图?, kTextPrimary, 12.0f); @@ -853,7 +853,7 @@ private: DrawButton(drawList, button, m_hasHoveredAction && m_hoveredAction == button.action); } - DrawCard(drawList, layout.stateRect, "状态摘要", "重点检查 multi-select 与 expanded / visible 契约是否稳定。"); + 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), @@ -919,9 +919,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/tree_view_multiselect/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/tree_view_multiselect/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 334.0f), @@ -929,7 +929,7 @@ private: kTextWeak, 12.0f); - DrawCard(drawList, layout.previewRect, "TreeView 多选预览", "这里只放一个 TreeView,用来验证多选与层级展开状态机。"); + DrawCard(drawList, layout.previewRect, "TreeView 多选预?, "这里只放一?TreeView,用来验证多选与层级展开状态机?); AppendUIEditorTreeViewBackground( drawList, m_treeFrame.layout, diff --git a/tests/UI/Editor/integration/shell/vector2_field_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/vector2_field_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/vector2_field_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/vector2_field_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/vector2_field_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/vector2_field_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/vector2_field_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/vector2_field_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/vector2_field_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/vector2_field_basic/main.cpp similarity index 95% rename from tests/UI/Editor/integration/shell/vector2_field_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/vector2_field_basic/main.cpp index f5586b06..a0fa79d4 100644 --- a/tests/UI/Editor/integration/shell/vector2_field_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/vector2_field_basic/main.cpp @@ -338,7 +338,7 @@ private: if (app != nullptr) { if (wParam == VK_F12) { app->m_autoScreenshot.RequestCapture("manual_f12"); - app->m_lastResult = "已请求截图,输出到 captures/latest.png"; + app->m_lastResult = "已请求截图,输出?captures/latest.png"; InvalidateRect(hwnd, nullptr, FALSE); return 0; } @@ -428,7 +428,7 @@ private: } m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/vector2_field_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/vector2_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); ResetScenario(); @@ -478,7 +478,7 @@ private: m_mousePosition = UIPoint(-1000.0f, -1000.0f); m_hoveredAction = ActionId::Reset; m_hasHoveredAction = false; - m_lastResult = "已重置到默认 Vector2Field 状态"; + m_lastResult = "已重置到默认 Vector2Field 状?; RefreshFrame(); } @@ -625,24 +625,24 @@ private: } if (result.editCommitted) { m_lastResult = - std::string("已提交编辑: ") + + std::string("已提交编? ") + DescribeSelectedComponent(result.changedComponentIndex) + " = " + result.committedText; return; } if (result.editCanceled) { - m_lastResult = "已取消编辑"; + m_lastResult = "已取消编?; return; } if (result.editStarted) { m_lastResult = - std::string("开始编辑 component ") + + std::string("开始编?component ") + DescribeSelectedComponent(result.selectedComponentIndex); return; } if (result.stepApplied || result.valueChanged) { m_lastResult = - std::string("值已更新,当前 component = ") + + std::string("值已更新,当?component = ") + DescribeSelectedComponent(result.changedComponentIndex); return; } @@ -654,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) { @@ -673,7 +673,7 @@ private: case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; break; } } @@ -708,40 +708,40 @@ private: shellPalette, shellMetrics, "这个测试在验证什么功能?", - "验证 UIEditorVector2Field 的选择切换、键盘步进、编辑提交/取消,以及固定 Inspector 风格承载。"); + "验证 UIEditorVector2Field 的选择切换、键盘步进、编辑提?取消,以及固?Inspector 风格承载?); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), - "1. 点击 X / Y value box 可切换 selected component,并检查 hover 与高亮。", + "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 在 X / Y 之间切换 selected component。", + "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 提交,按 Escape 取消。", + "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( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 204.0f), - "7. 按 F12 或点击截图按钮,确认自动截图路径正确。", + "7. ?F12 或点击截图按钮,确认自动截图路径正确?, shellPalette.textPrimary, shellMetrics.bodyFontSize); @@ -760,8 +760,8 @@ private: layout.stateRect, shellPalette, shellMetrics, - "状态摘要", - "重点检查 hit / selected / editing / values / display / result。"); + "状态摘?, + "重点检?hit / selected / editing / values / display / result?); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Hover: " + DescribeHitTarget(currentHit), @@ -774,12 +774,12 @@ private: shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 118.0f), - std::string("Focused: ") + (m_interactionState.vector2FieldState.focused ? "是" : "否"), + std::string("Focused: ") + (m_interactionState.vector2FieldState.focused ? "? : "?), shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 142.0f), - std::string("Editing: ") + (m_interactionState.vector2FieldState.editing ? "是" : "否"), + std::string("Editing: ") + (m_interactionState.vector2FieldState.editing ? "? : "?), shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( @@ -802,9 +802,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/vector2_field_basic/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/vector2_field_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 238.0f), @@ -817,7 +817,7 @@ private: shellPalette, shellMetrics, "Vector2Field 预览", - "这里只放一个固定样式的 Vector2 输入项。"); + "这里只放一个固定样式的 Vector2 输入项?); drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor); drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f); drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground); @@ -862,7 +862,7 @@ private: UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f); ActionId m_hoveredAction = ActionId::Reset; bool m_hasHoveredAction = false; - std::string m_lastResult = "无变化"; + std::string m_lastResult = "无变?; }; } // namespace diff --git a/tests/UI/Editor/integration/shell/vector3_field_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/vector3_field_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/vector3_field_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/vector3_field_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/vector3_field_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/vector3_field_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/vector3_field_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/vector3_field_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/vector3_field_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/vector3_field_basic/main.cpp similarity index 95% rename from tests/UI/Editor/integration/shell/vector3_field_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/vector3_field_basic/main.cpp index 6175f1d9..f76fb8a1 100644 --- a/tests/UI/Editor/integration/shell/vector3_field_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/vector3_field_basic/main.cpp @@ -344,7 +344,7 @@ private: if (app != nullptr) { if (wParam == VK_F12) { app->m_autoScreenshot.RequestCapture("manual_f12"); - app->m_lastResult = "已请求截图,输出到 captures/latest.png"; + app->m_lastResult = "已请求截图,输出?captures/latest.png"; InvalidateRect(hwnd, nullptr, FALSE); return 0; } @@ -434,7 +434,7 @@ private: } m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/vector3_field_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/vector3_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); ResetScenario(); @@ -484,7 +484,7 @@ private: m_mousePosition = UIPoint(-1000.0f, -1000.0f); m_hoveredAction = ActionId::Reset; m_hasHoveredAction = false; - m_lastResult = "已重置到默认 Vector3Field 状态"; + m_lastResult = "已重置到默认 Vector3Field 状?; RefreshFrame(); } @@ -631,24 +631,24 @@ private: } if (result.editCommitted) { m_lastResult = - std::string("已提交编辑: ") + + std::string("已提交编? ") + DescribeSelectedComponent(result.changedComponentIndex) + " = " + result.committedText; return; } if (result.editCanceled) { - m_lastResult = "已取消编辑"; + m_lastResult = "已取消编?; return; } if (result.editStarted) { m_lastResult = - std::string("开始编辑 component ") + + std::string("开始编?component ") + DescribeSelectedComponent(result.selectedComponentIndex); return; } if (result.stepApplied || result.valueChanged) { m_lastResult = - std::string("值已更新,当前 component = ") + + std::string("值已更新,当?component = ") + DescribeSelectedComponent(result.changedComponentIndex); return; } @@ -660,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) { @@ -679,7 +679,7 @@ private: case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "已请求截图,输出?captures/latest.png"; break; } } @@ -714,40 +714,40 @@ private: shellPalette, shellMetrics, "这个测试在验证什么功能?", - "验证 UIEditorVector3Field 的选择切换、键盘步进、编辑提交/取消,以及固定 Inspector 风格承载。"); + "验证 UIEditorVector3Field 的选择切换、键盘步进、编辑提?取消,以及固?Inspector 风格承载?); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), - "1. 点击 X / Y / Z value box 可切换 selected component,并检查 hover 与高亮。", + "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 在 X / Y / Z 之间切换 selected component。", + "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 提交,按 Escape 取消。", + "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( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 204.0f), - "7. 按 F12 或点击截图按钮,确认自动截图路径正确。", + "7. ?F12 或点击截图按钮,确认自动截图路径正确?, shellPalette.textPrimary, shellMetrics.bodyFontSize); @@ -766,8 +766,8 @@ private: layout.stateRect, shellPalette, shellMetrics, - "状态摘要", - "重点检查 hit / selected / editing / values / display / result。"); + "状态摘?, + "重点检?hit / selected / editing / values / display / result?); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Hover: " + DescribeHitTarget(currentHit), @@ -780,12 +780,12 @@ private: shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 118.0f), - std::string("Focused: ") + (m_interactionState.vector3FieldState.focused ? "是" : "否"), + std::string("Focused: ") + (m_interactionState.vector3FieldState.focused ? "? : "?), shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 142.0f), - std::string("Editing: ") + (m_interactionState.vector3FieldState.editing ? "是" : "否"), + std::string("Editing: ") + (m_interactionState.vector3FieldState.editing ? "? : "?), shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( @@ -810,9 +810,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/vector3_field_basic/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/vector3_field_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 238.0f), @@ -825,7 +825,7 @@ private: shellPalette, shellMetrics, "Vector3Field 预览", - "这里只放一个固定样式的 Vector3 输入项。"); + "这里只放一个固定样式的 Vector3 输入项?); drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor); drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f); drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground); @@ -870,7 +870,7 @@ private: UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f); ActionId m_hoveredAction = ActionId::Reset; bool m_hasHoveredAction = false; - std::string m_lastResult = "无变化"; + std::string m_lastResult = "无变?; }; } // namespace diff --git a/tests/UI/Editor/integration/shell/vector4_field_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/vector4_field_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/vector4_field_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/vector4_field_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/vector4_field_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/vector4_field_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/vector4_field_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/vector4_field_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/vector4_field_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/vector4_field_basic/main.cpp similarity index 90% rename from tests/UI/Editor/integration/shell/vector4_field_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/vector4_field_basic/main.cpp index 7e404d81..372e2e7a 100644 --- a/tests/UI/Editor/integration/shell/vector4_field_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/vector4_field_basic/main.cpp @@ -225,7 +225,7 @@ private: if (app != nullptr) { if (wParam == VK_F12) { app->m_autoScreenshot.RequestCapture("manual_f12"); - app->m_lastResult = "已请求截图,输出到 captures/latest.png"; + app->m_lastResult = "已请求截图,输出?captures/latest.png"; InvalidateRect(hwnd, nullptr, FALSE); return 0; } @@ -295,7 +295,7 @@ private: } ShowWindow(m_hwnd, nCmdShow); UpdateWindow(m_hwnd); - m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/vector4_field_basic/captures"; + m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/vector4_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); ResetScenario(); return true; @@ -324,7 +324,7 @@ private: m_interactionState.vector4FieldState.focused = true; m_interactionState.vector4FieldState.selectedComponentIndex = 0u; m_mousePosition = UIPoint(-1000.0f, -1000.0f); - m_lastResult = "已重置到默认 Vector4Field 状态"; + m_lastResult = "已重置到默认 Vector4Field 状?; PumpEvents({}); } @@ -349,19 +349,19 @@ private: if (result.editCommitRejected) { 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 = "已取消编辑"; + m_lastResult = "已取消编?; } else if (result.editStarted) { - m_lastResult = "开始编辑 component " + DescribeSelectedComponent(result.selectedComponentIndex); + 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 = "输入已处理,但没有额外状态变?; } } @@ -387,20 +387,20 @@ 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 的四分量切换、键盘步进、编辑提交/取消,以及固定 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.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); drawList.AddRectOutline(layout.stateRect, shellPalette.cardBorder, 1.0f, shellMetrics.cardRadius); - drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 14.0f), "状态摘要", shellPalette.textPrimary, shellMetrics.titleFontSize); + drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 14.0f), "状态摘?, shellPalette.textPrimary, shellMetrics.titleFontSize); drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 46.0f), "Hover: " + DescribeHitTarget(currentHit), shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Selected: " + DescribeSelectedComponent(m_interactionState.vector4FieldState.selectedComponentIndex), shellPalette.textPrimary, shellMetrics.bodyFontSize); - drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 94.0f), std::string("Focused: ") + (m_interactionState.vector4FieldState.focused ? "是" : "否"), shellPalette.textPrimary, shellMetrics.bodyFontSize); - drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 118.0f), std::string("Editing: ") + (m_interactionState.vector4FieldState.editing ? "是" : "否"), shellPalette.textPrimary, shellMetrics.bodyFontSize); + drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 94.0f), std::string("Focused: ") + (m_interactionState.vector4FieldState.focused ? "? : "?), shellPalette.textPrimary, shellMetrics.bodyFontSize); + drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 118.0f), std::string("Editing: ") + (m_interactionState.vector4FieldState.editing ? "? : "?), shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 142.0f), "Values: X=" + FormatUIEditorVector4FieldComponentValue(m_spec, 0u) + " Y=" + FormatUIEditorVector4FieldComponentValue(m_spec, 1u) + " Z=" + FormatUIEditorVector4FieldComponentValue(m_spec, 2u) + " W=" + FormatUIEditorVector4FieldComponentValue(m_spec, 3u), shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 166.0f), "Display: X=" + m_interactionState.vector4FieldState.displayTexts[0] + " Y=" + m_interactionState.vector4FieldState.displayTexts[1] + " Z=" + m_interactionState.vector4FieldState.displayTexts[2] + " W=" + m_interactionState.vector4FieldState.displayTexts[3], shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 190.0f), "Result: " + m_lastResult, shellPalette.textPrimary, shellMetrics.bodyFontSize); @@ -409,7 +409,7 @@ private: 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), "这里只放一个固定样式的 Vector4 输入项。", 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); @@ -433,7 +433,7 @@ private: UIEditorVector4FieldInteractionState m_interactionState = {}; UIEditorVector4FieldInteractionFrame m_frame = {}; UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f); - std::string m_lastResult = "无变化"; + std::string m_lastResult = "无变?; }; } // namespace diff --git a/tests/UI/Editor/integration/shell/viewport_shell_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/viewport_shell_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/viewport_shell_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/viewport_shell_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/viewport_shell_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/viewport_shell_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/viewport_shell_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/viewport_shell_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/viewport_shell_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/viewport_shell_basic/main.cpp similarity index 95% rename from tests/UI/Editor/integration/shell/viewport_shell_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/viewport_shell_basic/main.cpp index 64a5781c..9f42d87d 100644 --- a/tests/UI/Editor/integration/shell/viewport_shell_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/viewport_shell_basic/main.cpp @@ -306,7 +306,7 @@ private: bool Initialize(HINSTANCE hInstance, int nCmdShow) { m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/viewport_shell_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/viewport_shell_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); WNDCLASSEXW windowClass = {}; @@ -526,7 +526,7 @@ private: frame.statusText = "Fake viewport frame"; } else { frame.hasTexture = false; - frame.statusText = "这里只验证 ViewportShell contract,不接 Scene/Game 业务。"; + frame.statusText = "这里只验?ViewportShell contract,不?Scene/Game 业务?; } return frame; } @@ -565,19 +565,19 @@ 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 }, @@ -600,11 +600,11 @@ private: switch (action) { case ActionId::ToggleTopBar: m_showTopBar = !m_showTopBar; - m_lastResult = m_showTopBar ? "TopBar 已打开" : "TopBar 已关闭"; + m_lastResult = m_showTopBar ? "TopBar 已打开" : "TopBar 已关?; break; case ActionId::ToggleBottomBar: m_showBottomBar = !m_showBottomBar; - m_lastResult = m_showBottomBar ? "BottomBar 已打开" : "BottomBar 已关闭"; + m_lastResult = m_showBottomBar ? "BottomBar 已打开" : "BottomBar 已关?; break; case ActionId::ToggleTexture: m_textureEnabled = !m_textureEnabled; @@ -618,7 +618,7 @@ private: m_autoScreenshot.RequestCapture("manual_button"); InvalidateRect(m_hwnd, nullptr, FALSE); UpdateWindow(m_hwnd); - m_lastResult = "截图已排队"; + m_lastResult = "截图已排?; break; } } @@ -688,49 +688,49 @@ private: drawList, m_introRect, "这个测试验证什么功能?", - "只验证 Resolve + Update 的 ViewportShell contract,不接 Scene/Game 业务。"); + "只验?Resolve + Update ?ViewportShell contract,不?Scene/Game 业务?); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f), - "1. 验证 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), - "2. 验证 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), - "3. 验证 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), - "4. 验证 hover Surface 与 click Surface 后的 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), - "5. 验证 TopBar / BottomBar / Texture 三个开关切换后的整体表现。", + "5. 验证 TopBar / BottomBar / Texture 三个开关切换后的整体表现?, kTextMuted, 11.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 176.0f), - "6. 验证截图链路,支持按钮触发和 F12 手动截图。", + "6. 验证截图链路,支持按钮触发和 F12 手动截图?, kTextMuted, 11.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 204.0f), - "7. 左侧持续展示 Request / Input / HoverHit / Hover / Focus / Capture 摘要。", + "7. 左侧持续展示 Request / Input / HoverHit / Hover / Focus / Capture 摘要?, kTextWeak, 11.0f); - DrawCard(drawList, m_controlsRect, "操作", "只保留当前测试真正需要检查的 5 个控件。"); + DrawCard(drawList, m_controlsRect, "操作", "只保留当前测试真正需要检查的 5 个控件?); for (const ButtonState& button : m_buttons) { DrawButton(drawList, button); } - DrawCard(drawList, m_stateRect, "状态", "重点看 request、input rect、hover 命中与输入桥同步。"); + DrawCard(drawList, m_stateRect, "状?, "重点?request、input rect、hover 命中与输入桥同步?); 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); @@ -750,13 +750,13 @@ private: 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, diff --git a/tests/UI/Editor/integration/shell/viewport_slot_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/viewport_slot_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/viewport_slot_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/viewport_slot_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/viewport_slot_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/viewport_slot_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/viewport_slot_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/viewport_slot_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/viewport_slot_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/viewport_slot_basic/main.cpp similarity index 95% rename from tests/UI/Editor/integration/shell/viewport_slot_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/viewport_slot_basic/main.cpp index a964e2be..272e8a84 100644 --- a/tests/UI/Editor/integration/shell/viewport_slot_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/viewport_slot_basic/main.cpp @@ -250,7 +250,7 @@ private: bool Initialize(HINSTANCE hInstance, int nCmdShow) { m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/viewport_slot_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/viewport_slot_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); WNDCLASSEXW windowClass = {}; @@ -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; } @@ -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(); } @@ -489,11 +489,11 @@ private: switch (action) { case ActionId::ToggleTopBar: m_showTopBar = !m_showTopBar; - m_lastResult = m_showTopBar ? "TopBar 已打开" : "TopBar 已关闭"; + m_lastResult = m_showTopBar ? "TopBar 已打开" : "TopBar 已关?; break; case ActionId::ToggleBottomBar: m_showBottomBar = !m_showBottomBar; - m_lastResult = m_showBottomBar ? "BottomBar 已打开" : "BottomBar 已关闭"; + m_lastResult = m_showBottomBar ? "BottomBar 已打开" : "BottomBar 已关?; break; case ActionId::ToggleTexture: m_textureEnabled = !m_textureEnabled; @@ -511,7 +511,7 @@ private: m_autoScreenshot.RequestCapture("manual_button"); InvalidateRect(m_hwnd, nullptr, FALSE); UpdateWindow(m_hwnd); - m_lastResult = "截图已排队"; + m_lastResult = "截图已排?; break; } @@ -546,14 +546,14 @@ private: case UIEditorViewportSlotHitTargetKind::Surface: m_slotState.focused = true; m_slotState.surfaceActive = true; - m_lastResult = "Surface 激活,capture 已释放"; + m_lastResult = "Surface 激活,capture 已释?; break; default: m_slotState.focused = false; m_slotState.surfaceActive = false; m_slotState.activeToolIndex = UIEditorViewportSlotInvalidIndex; m_slotState.statusBarState.activeIndex = UIEditorViewportSlotInvalidIndex; - m_lastResult = "点击外部,focus / active 已清除"; + m_lastResult = "点击外部,focus / active 已清?; break; } @@ -585,44 +585,44 @@ private: drawList, m_introRect, "这个测试验证什么功能?", - "只验证 Editor ViewportSlot 的基础壳层。"); + "只验?Editor ViewportSlot 的基础壳层?); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f), - "1. 验证 TopBar / Surface / BottomBar 的区域划分。", + "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), - "3. 验证 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), - "4. 验证点击 surface 后进入 capture。", + "4. 验证点击 surface 后进?capture?, kTextMuted, 12.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 154.0f), - "5. 验证切换 TopBar / BottomBar / Texture / 方形比例四个开关。", + "5. 验证切换 TopBar / BottomBar / Texture / 方形比例四个开关?, kTextMuted, 12.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 180.0f), - "6. 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); } - DrawCard(drawList, m_stateRect, "状态", "实时显示 hover / focus / active / capture 与截图状态。"); + DrawCard(drawList, m_stateRect, "状?, "实时显示 hover / focus / active / capture 与截图状态?); drawList.AddText( UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 66.0f), "Hover: " + DescribeHitTarget(m_lastHover), @@ -660,9 +660,9 @@ private: 12.0f); const std::string captureSummary = 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), @@ -670,7 +670,7 @@ private: kTextWeak, 12.0f); - DrawCard(drawList, m_previewRect, "Preview", "这里只放一个 ViewportSlot,专门检查基础壳层。"); + DrawCard(drawList, m_previewRect, "Preview", "这里只放一?ViewportSlot,专门检查基础壳层?); drawList.AddFilledRect( UIRect( m_previewRect.x + 12.0f, diff --git a/tests/UI/Editor/integration/shell/workspace_interaction_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/workspace_interaction_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/workspace_interaction_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/workspace_interaction_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/workspace_interaction_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/workspace_interaction_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/workspace_interaction_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/workspace_interaction_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/workspace_interaction_basic/captures/history/20260408_190643_1_startup.png b/tests/UI/Editor/manual_validation/shell/workspace_interaction_basic/captures/history/20260408_190643_1_startup.png similarity index 100% rename from tests/UI/Editor/integration/shell/workspace_interaction_basic/captures/history/20260408_190643_1_startup.png rename to tests/UI/Editor/manual_validation/shell/workspace_interaction_basic/captures/history/20260408_190643_1_startup.png diff --git a/tests/UI/Editor/integration/shell/workspace_interaction_basic/captures/latest.png b/tests/UI/Editor/manual_validation/shell/workspace_interaction_basic/captures/latest.png similarity index 100% rename from tests/UI/Editor/integration/shell/workspace_interaction_basic/captures/latest.png rename to tests/UI/Editor/manual_validation/shell/workspace_interaction_basic/captures/latest.png diff --git a/tests/UI/Editor/integration/shell/workspace_interaction_basic/main.cpp b/tests/UI/Editor/manual_validation/shell/workspace_interaction_basic/main.cpp similarity index 97% rename from tests/UI/Editor/integration/shell/workspace_interaction_basic/main.cpp rename to tests/UI/Editor/manual_validation/shell/workspace_interaction_basic/main.cpp index f2917a92..69d59d44 100644 --- a/tests/UI/Editor/integration/shell/workspace_interaction_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/workspace_interaction_basic/main.cpp @@ -134,9 +134,9 @@ std::string FormatFloat(float value, int precision = 2) { std::string DescribeHitTarget(const UIEditorDockHostHitTarget& target) { switch (target.kind) { case UIEditorDockHostHitTargetKind::SplitterHandle: - return "分割条: " + target.nodeId; + return "分割? " + target.nodeId; case UIEditorDockHostHitTargetKind::TabStripBackground: - return "标签栏背景: " + target.nodeId; + return "标签栏背? " + target.nodeId; case UIEditorDockHostHitTargetKind::Tab: return "标签: " + target.panelId; case UIEditorDockHostHitTargetKind::PanelHeader: @@ -171,7 +171,7 @@ std::string JoinVisiblePanelIds(const UIEditorWorkspaceController& controller) { std::string DescribeViewportEvent(const UIEditorViewportInputBridgeFrame& frame) { if (frame.captureStarted) { - return "视口开始捕获"; + return "视口开始捕?; } if (frame.captureEnded) { return "视口结束捕获"; @@ -183,10 +183,10 @@ std::string DescribeViewportEvent(const UIEditorViewportInputBridgeFrame& frame) return "视口失去焦点"; } if (frame.pointerPressedInside) { - return "视口内按下"; + return "视口内按?; } if (frame.pointerReleasedInside) { - return "视口内抬起"; + return "视口内抬?; } if (frame.pointerMoved) { return "视口指针移动"; @@ -361,7 +361,7 @@ UIEditorWorkspaceInteractionModel BuildInteractionModel() { presentation.kind = UIEditorPanelPresentationKind::ViewportShell; presentation.viewportShellModel.spec.chrome.title = "视口"; presentation.viewportShellModel.spec.chrome.subtitle = - "验证 Workspace 与 Viewport 的统一交互"; + "验证 Workspace ?Viewport 的统一交互"; presentation.viewportShellModel.spec.chrome.showTopBar = true; presentation.viewportShellModel.spec.chrome.showBottomBar = true; presentation.viewportShellModel.frame.statusText = @@ -512,7 +512,7 @@ private: bool Initialize(HINSTANCE hInstance, int nCmdShow) { m_captureRoot = ResolveRepoRootPath() / - "tests/UI/Editor/integration/shell/workspace_interaction_basic/captures"; + "tests/UI/Editor/manual_validation/shell/workspace_interaction_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); WNDCLASSEXW windowClass = {}; @@ -896,7 +896,7 @@ private: FindUIEditorWorkspaceViewportPresentationFrame(m_cachedFrame.composeFrame, "viewport"); const bool viewportVisible = viewportFrame != nullptr; const std::string selectedPresentation = - viewportVisible ? "视口呈现中" : "停用或未生成占位"; + viewportVisible ? "视口呈现? : "停用或未生成占位"; RECT clientRect = {}; GetClientRect(m_hwnd, &clientRect); @@ -913,40 +913,40 @@ private: DrawCard( drawList, m_introRect, - "这个测试验证什么", - "验证 Workspace 基础交互与 Viewport 输入桥接是否统一。"); + "这个测试验证什?, + "验证 Workspace 基础交互?Viewport 输入桥接是否统一?); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 72.0f), - "1. 拖拽分割条后,右侧工作区布局比例应实时变化。", + "1. 拖拽分割条后,右侧工作区布局比例应实时变化?, kShellPalette.textPrimary, 12.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 94.0f), - "2. 点击标签、面板或视口时,左侧状态应显示命中目标与结果。", + "2. 点击标签、面板或视口时,左侧状态应显示命中目标与结果?, kShellPalette.textPrimary, 12.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 116.0f), - "3. 视口 hover、focus、capture 的变化应反馈到状态面板。", + "3. 视口 hover、focus、capture 的变化应反馈到状态面板?, kShellPalette.textPrimary, 12.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 138.0f), - "4. 该场景只验证 Editor 基础交互,不验证具体业务面板。", + "4. 该场景只验证 Editor 基础交互,不验证具体业务面板?, kShellPalette.textPrimary, 12.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 162.0f), - "重点观察:命中目标、当前激活面板、宿主 capture 与视口输入状态。", + "重点观察:命中目标、当前激活面板、宿?capture 与视口输入状态?, 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, "状态", "观察当前工作区交互与视口输入状态。"); + DrawCard(drawList, m_stateRect, "状?, "观察当前工作区交互与视口输入状态?); float stateY = m_stateRect.y + 66.0f; auto addStateLine = [&](std::string text, const UIColor& color = kShellPalette.textPrimary, @@ -962,7 +962,7 @@ private: 11.0f); addStateLine("当前呈现: " + selectedPresentation, kShellPalette.textPrimary); addStateLine( - "当前激活: " + + "当前激? " + (m_controller.GetWorkspace().activePanelId.empty() ? std::string("(none)") : m_controller.GetWorkspace().activePanelId)); @@ -979,7 +979,7 @@ private: "宿主 capture: " + FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kSuccess : kShellPalette.textMuted); addStateLine( - "根分割比例: " + FormatFloat(m_controller.GetWorkspace().root.splitRatio), + "根分割比? " + FormatFloat(m_controller.GetWorkspace().root.splitRatio), kShellPalette.textWeak, 11.0f); if (viewportFrame != nullptr) { @@ -999,14 +999,14 @@ private: addStateLine( "截图: " + (m_autoScreenshot.HasPendingCapture() - ? std::string("截图排队中...") + ? std::string("截图排队?..") : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("按 F12 或按钮截图 -> captures/") + ? std::string("?F12 或按钮截?-> captures/") : m_autoScreenshot.GetLastCaptureSummary())), kShellPalette.textWeak, 11.0f); - DrawCard(drawList, m_previewRect, "预览", "右侧是真实的工作区交互预览。"); + DrawCard(drawList, m_previewRect, "预览", "右侧是真实的工作区交互预览?); const auto dockPalette = ResolveUIEditorDockHostPalette(); const auto viewportPalette = ResolveUIEditorViewportSlotPalette(); AppendUIEditorWorkspaceCompose( diff --git a/tests/UI/Editor/integration/shell/workspace_shell_compose/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/workspace_shell_compose/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/workspace_shell_compose/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/workspace_shell_compose/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/workspace_shell_compose/View.xcui b/tests/UI/Editor/manual_validation/shell/workspace_shell_compose/View.xcui similarity index 100% rename from tests/UI/Editor/integration/shell/workspace_shell_compose/View.xcui rename to tests/UI/Editor/manual_validation/shell/workspace_shell_compose/View.xcui diff --git a/tests/UI/Editor/integration/shell/workspace_shell_compose/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/workspace_shell_compose/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/workspace_shell_compose/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/workspace_shell_compose/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/workspace_shell_compose/captures/history/20260408_222440_1_startup.png b/tests/UI/Editor/manual_validation/shell/workspace_shell_compose/captures/history/20260408_222440_1_startup.png similarity index 100% rename from tests/UI/Editor/integration/shell/workspace_shell_compose/captures/history/20260408_222440_1_startup.png rename to tests/UI/Editor/manual_validation/shell/workspace_shell_compose/captures/history/20260408_222440_1_startup.png diff --git a/tests/UI/Editor/integration/shell/workspace_shell_compose/captures/latest.png b/tests/UI/Editor/manual_validation/shell/workspace_shell_compose/captures/latest.png similarity index 100% rename from tests/UI/Editor/integration/shell/workspace_shell_compose/captures/latest.png rename to tests/UI/Editor/manual_validation/shell/workspace_shell_compose/captures/latest.png diff --git a/tests/UI/Editor/integration/shell/workspace_shell_compose/main.cpp b/tests/UI/Editor/manual_validation/shell/workspace_shell_compose/main.cpp similarity index 96% rename from tests/UI/Editor/integration/shell/workspace_shell_compose/main.cpp rename to tests/UI/Editor/manual_validation/shell/workspace_shell_compose/main.cpp index 155bf6ca..d05b04ae 100644 --- a/tests/UI/Editor/integration/shell/workspace_shell_compose/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/workspace_shell_compose/main.cpp @@ -343,7 +343,7 @@ private: bool Initialize(HINSTANCE hInstance, int nCmdShow) { m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/workspace_shell_compose/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/workspace_shell_compose/captures"; m_autoScreenshot.Initialize(m_captureRoot); WNDCLASSEXW windowClass = {}; @@ -510,7 +510,7 @@ private: m_resetPressed = false; if (resetTriggered) { ResetScenario(); - m_lastResult = "已重置"; + m_lastResult = "已重?; InvalidateRect(m_hwnd, nullptr, FALSE); return; } @@ -552,12 +552,12 @@ private: } if (result.requestPointerCapture) { - m_lastResult = "捕获:开始拖动 splitter"; + m_lastResult = "捕获:开始拖?splitter"; return; } if (result.releasePointerCapture) { - m_lastResult = "捕获:结束拖动 splitter"; + m_lastResult = "捕获:结束拖?splitter"; return; } @@ -567,7 +567,7 @@ private: } if (result.consumed) { - m_lastResult = "已消费: 输入由 DockHostInteraction 处理"; + m_lastResult = "已消? 输入?DockHostInteraction 处理"; } } @@ -595,33 +595,33 @@ private: drawList, m_introRect, "这个测试验证什么功能?", - "验证 Workspace + DockHost 的组合场景是否完全收口到统一交互层。"); + "验证 Workspace + DockHost 的组合场景是否完全收口到统一交互层?); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 68.0f), - "1. splitter drag 只能通过 UIEditorDockHostInteraction + WorkspaceController 生效。", + "1. splitter drag 只能通过 UIEditorDockHostInteraction + WorkspaceController 生效?, kTextPrimary, 12.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 92.0f), - "2. tab 激活、panel 激活/关闭都必须统一回到 controller。", + "2. tab 激活、panel 激?关闭都必须统一回到 controller?, kTextPrimary, 12.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 116.0f), - "3. active panel、visible panels、split ratio 在组合场景里必须同步更新。", + "3. active panel、visible panels、split ratio 在组合场景里必须同步更新?, kTextPrimary, 12.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 140.0f), - "4. 操作建议:拖动三个 splitter,点击 Document A/B/C,关闭 Inspector 或 Console,按 F12 截图。", + "4. 操作建议:拖动三?splitter,点?Document A/B/C,关?Inspector ?Console,按 F12 截图?, kTextWeak, 11.0f); DrawCard( drawList, m_stateRect, - "状态回显", - "这里直接展示 hover / focus / dragging / active panel / split ratio。"); + "状态回?, + "这里直接展示 hover / focus / dragging / active panel / split ratio?); DrawButton(drawList, m_resetButtonRect, "重置", m_resetHovered); const auto validation = m_controller.ValidateState(); @@ -637,7 +637,7 @@ private: 13.0f); drawList.AddText( UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 122.0f), - "捕获: " + std::string(GetCapture() == m_hwnd ? "开启" : "关闭"), + "捕获: " + std::string(GetCapture() == m_hwnd ? "开? : "关闭"), GetCapture() == m_hwnd ? kSuccess : kTextMuted, 13.0f); drawList.AddText( @@ -650,7 +650,7 @@ private: 13.0f); drawList.AddText( UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 174.0f), - "当前激活面板: " + m_controller.GetWorkspace().activePanelId, + "当前激活面? " + m_controller.GetWorkspace().activePanelId, kTextPrimary, 13.0f); drawList.AddText( @@ -686,9 +686,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/shell/workspace_shell_compose/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/shell/workspace_shell_compose/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 408.0f), @@ -699,8 +699,8 @@ private: DrawCard( drawList, m_previewCardRect, - "预览区", - "这里只保留一个 DockHost 组合试验场,不混入业务 UI。"); + "预览?, + "这里只保留一?DockHost 组合试验场,不混入业?UI?); AppendUIEditorDockHostBackground(drawList, m_cachedFrame.layout); AppendUIEditorDockHostForeground(drawList, m_cachedFrame.layout); diff --git a/tests/UI/Editor/integration/shell/workspace_viewport_compose/CMakeLists.txt b/tests/UI/Editor/manual_validation/shell/workspace_viewport_compose/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/shell/workspace_viewport_compose/CMakeLists.txt rename to tests/UI/Editor/manual_validation/shell/workspace_viewport_compose/CMakeLists.txt diff --git a/tests/UI/Editor/integration/shell/workspace_viewport_compose/captures/.gitkeep b/tests/UI/Editor/manual_validation/shell/workspace_viewport_compose/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/shell/workspace_viewport_compose/captures/.gitkeep rename to tests/UI/Editor/manual_validation/shell/workspace_viewport_compose/captures/.gitkeep diff --git a/tests/UI/Editor/integration/shell/workspace_viewport_compose/main.cpp b/tests/UI/Editor/manual_validation/shell/workspace_viewport_compose/main.cpp similarity index 97% rename from tests/UI/Editor/integration/shell/workspace_viewport_compose/main.cpp rename to tests/UI/Editor/manual_validation/shell/workspace_viewport_compose/main.cpp index 1699f8f8..8f7d9780 100644 --- a/tests/UI/Editor/integration/shell/workspace_viewport_compose/main.cpp +++ b/tests/UI/Editor/manual_validation/shell/workspace_viewport_compose/main.cpp @@ -125,7 +125,7 @@ bool ContainsPoint(const UIRect& rect, float x, float y) { } std::string BoolText(bool value) { - return value ? "是" : "否"; + return value ? "? : "?; } std::string FormatFloat(float value) { @@ -146,15 +146,15 @@ std::string FormatSize(const UISize& size) { std::string DescribeHitTarget(const UIEditorViewportSlotHitTarget& hit) { switch (hit.kind) { - case UIEditorViewportSlotHitTargetKind::TopBar: return "顶部栏"; + case UIEditorViewportSlotHitTargetKind::TopBar: return "顶部?; case UIEditorViewportSlotHitTargetKind::Title: return "标题"; case UIEditorViewportSlotHitTargetKind::ToolItem: return "工具项[" + std::to_string(hit.index) + "]"; case UIEditorViewportSlotHitTargetKind::Surface: return "视口表面"; - case UIEditorViewportSlotHitTargetKind::BottomBar: return "底部栏"; + case UIEditorViewportSlotHitTargetKind::BottomBar: return "底部?; case UIEditorViewportSlotHitTargetKind::StatusSegment: return "状态段[" + std::to_string(hit.index) + "]"; case UIEditorViewportSlotHitTargetKind::StatusSeparator: return "状态分隔线[" + std::to_string(hit.index) + "]"; case UIEditorViewportSlotHitTargetKind::None: - default: return "无"; + default: return "?; } } @@ -427,7 +427,7 @@ LRESULT CALLBACK ScenarioApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP } bool ScenarioApp::Initialize(HINSTANCE hInstance, int nCmdShow) { - m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/workspace_viewport_compose/captures"; + m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/shell/workspace_viewport_compose/captures"; m_autoScreenshot.Initialize(m_captureRoot); WNDCLASSEXW wc = {}; @@ -627,17 +627,17 @@ void ScenarioApp::RenderFrame() { UIDrawList& drawList = drawData.EmplaceDrawList("WorkspaceViewportCompose"); drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kShellPalette.windowBackground); - DrawCard(drawList, m_introRect, "这个测试验证什么功能?", "验证 WorkspaceCompose 是否为活动页正确组合出 ViewportShell。"); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f), "1. Scene 页应呈现 ViewportShell,Document 页应回退为普通占位。", kShellPalette.textMuted, 11.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 86.0f), "2. 顶部栏、底部栏、纹理分支切换要同步影响 compose 请求和最终帧。", kShellPalette.textMuted, 11.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 106.0f), "3. 只有视口存在时,悬停、焦点、捕获和滚轮输入才应生效。", kShellPalette.textMuted, 11.0f); + DrawCard(drawList, m_introRect, "这个测试验证什么功能?", "验证 WorkspaceCompose 是否为活动页正确组合?ViewportShell?); + drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f), "1. Scene 页应呈现 ViewportShell,Document 页应回退为普通占位?, kShellPalette.textMuted, 11.0f); + drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 86.0f), "2. 顶部栏、底部栏、纹理分支切换要同步影响 compose 请求和最终帧?, kShellPalette.textMuted, 11.0f); + drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 106.0f), "3. 只有视口存在时,悬停、焦点、捕获和滚轮输入才应生效?, kShellPalette.textMuted, 11.0f); - DrawCard(drawList, m_controlsRect, "操作区", "只保留当前需要人工检查的操作。"); + DrawCard(drawList, m_controlsRect, "操作?, "只保留当前需要人工检查的操作?); for (const ButtonState& button : m_buttons) { DrawButton(drawList, button); } - DrawCard(drawList, m_stateRect, "状态", "观察 compose 帧、命中结果与校验状态。"); + DrawCard(drawList, m_stateRect, "状?, "观察 compose 帧、命中结果与校验状态?); float stateY = m_stateRect.y + 66.0f; auto addStateLine = [&](std::string text, const UIColor& color = kShellPalette.textPrimary, float size = 12.0f) { drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY), std::move(text), color, size); @@ -650,9 +650,9 @@ void ScenarioApp::RenderFrame() { addStateLine("输入区域: " + (viewportFrame != nullptr ? FormatRect(viewportFrame->viewportShellFrame.slotLayout.inputRect) : std::string("n/a")), kShellPalette.textMuted, 11.0f); addStateLine("结果: " + m_lastResult, kShellPalette.textMuted); addStateLine(validation.IsValid() ? "工作区校验:通过" : "工作区校验:" + validation.message, validation.IsValid() ? kSuccess : kDanger, 11.0f); - addStateLine(m_autoScreenshot.HasPendingCapture() ? "截图排队中..." : (m_autoScreenshot.GetLastCaptureSummary().empty() ? std::string("F12 或点击截图 -> captures/") : m_autoScreenshot.GetLastCaptureSummary()), kShellPalette.textWeak, 11.0f); + addStateLine(m_autoScreenshot.HasPendingCapture() ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() ? std::string("F12 或点击截?-> captures/") : m_autoScreenshot.GetLastCaptureSummary()), kShellPalette.textWeak, 11.0f); - DrawCard(drawList, m_previewRect, "预览", "实时绘制 UIEditorWorkspaceCompose 结果。"); + DrawCard(drawList, m_previewRect, "预览", "实时绘制 UIEditorWorkspaceCompose 结果?); const auto dockPalette = ResolveUIEditorDockHostPalette(); const auto viewportPalette = ResolveUIEditorViewportSlotPalette(); const auto dockMetrics = ResolveUIEditorDockHostMetrics(); diff --git a/tests/UI/Editor/integration/state/CMakeLists.txt b/tests/UI/Editor/manual_validation/state/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/state/CMakeLists.txt rename to tests/UI/Editor/manual_validation/state/CMakeLists.txt diff --git a/tests/UI/Editor/integration/state/layout_persistence/CMakeLists.txt b/tests/UI/Editor/manual_validation/state/layout_persistence/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/state/layout_persistence/CMakeLists.txt rename to tests/UI/Editor/manual_validation/state/layout_persistence/CMakeLists.txt diff --git a/tests/UI/Editor/integration/state/layout_persistence/captures/.gitkeep b/tests/UI/Editor/manual_validation/state/layout_persistence/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/state/layout_persistence/captures/.gitkeep rename to tests/UI/Editor/manual_validation/state/layout_persistence/captures/.gitkeep diff --git a/tests/UI/Editor/integration/state/layout_persistence/main.cpp b/tests/UI/Editor/manual_validation/state/layout_persistence/main.cpp similarity index 93% rename from tests/UI/Editor/integration/state/layout_persistence/main.cpp rename to tests/UI/Editor/manual_validation/state/layout_persistence/main.cpp index 0e39ab76..53004f74 100644 --- a/tests/UI/Editor/integration/state/layout_persistence/main.cpp +++ b/tests/UI/Editor/manual_validation/state/layout_persistence/main.cpp @@ -401,7 +401,7 @@ private: } m_autoScreenshot.Initialize( - (ResolveRepoRootPath() / "tests/UI/Editor/integration/state/layout_persistence/captures") + (ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/state/layout_persistence/captures") .lexically_normal()); return true; } @@ -430,7 +430,7 @@ private: SetCustomResult( "等待操作", "Pending", - "先点 `1 Hide Active`,再点 `2 Save Layout`;保存后继续改状态,再用 `4 Load Layout` 检查恢复。"); + "先点 `1 Hide Active`,再?`2 Save Layout`;保存后继续改状态,再用 `4 Load Layout` 检查恢复?); } void OnResize(UINT width, UINT height) { @@ -551,7 +551,7 @@ private: SetCustomResult( "Save Layout", "Rejected", - "当前 controller 状态非法,不能保存布局:" + validation.message); + "当前 controller 状态非法,不能保存布局? + validation.message); return; } @@ -566,13 +566,13 @@ private: "Save Layout", changed ? "Saved" : "NoOp", changed - ? "已更新保存的布局快照。接下来继续改状态,再点 `Load Layout` 检查恢复。" - : "保存内容与当前已保存布局一致。"); + ? "已更新保存的布局快照。接下来继续改状态,再点 `Load Layout` 检查恢复? + : "保存内容与当前已保存布局一致?); } void LoadLayout() { if (!m_hasSavedLayout) { - SetCustomResult("Load Layout", "Rejected", "当前还没有保存过布局。"); + SetCustomResult("Load Layout", "Rejected", "当前还没有保存过布局?); return; } @@ -585,7 +585,7 @@ private: SetCustomResult( "Load Invalid", "Rejected", - "当前 controller 状态非法,无法构造 invalid payload:" + validation.message); + "当前 controller 状态非法,无法构?invalid payload? + validation.message); return; } @@ -595,7 +595,7 @@ private: : SerializeUIEditorWorkspaceLayoutSnapshot(m_controller.CaptureLayoutSnapshot()); const std::string invalidSerialized = ReplaceActiveRecord(source, "missing-panel"); if (invalidSerialized.empty()) { - SetCustomResult("Load Invalid", "Rejected", "构造 invalid payload 失败。"); + SetCustomResult("Load Invalid", "Rejected", "构?invalid payload 失败?); return; } @@ -731,16 +731,16 @@ private: const UIRect stateRect(actionRect.x + actionRect.width + 16.0f, actionRect.y, width - actionRect.width - margin * 2.0f - 16.0f, height - 254.0f); 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 + 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 + 158.0f), "5. `R Reset` 必须回到基线 active=doc-a;按 `F12` 保存当前窗口截图。", kTextPrimary, 13.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 + 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 + 158.0f), "5. `R Reset` 必须回到基线 active=doc-a;按 `F12` 保存当前窗口截图?, kTextPrimary, 13.0f); - DrawCard(drawList, actionRect, "操作区", "这里只保留这一步需要检查的布局保存/恢复动作。"); - DrawCard(drawList, stateRect, "状态摘要", "左侧看 Current,右侧看 Saved;重点检查 active、visible 和坏数据恢复。"); - DrawCard(drawList, footerRect, "最近结果", "显示最近一次操作、状态和当前 validation。"); + DrawCard(drawList, actionRect, "操作?, "这里只保留这一步需要检查的布局保存/恢复动作?); + DrawCard(drawList, stateRect, "状态摘?, "左侧?Current,右侧看 Saved;重点检?active、visible 和坏数据恢复?); + DrawCard(drawList, footerRect, "最近结?, "显示最近一次操作、状态和当前 validation?); m_buttons.clear(); const std::vector> buttonDefs = { @@ -794,8 +794,8 @@ private: drawList.AddText(UIPoint(rightColumnX, stateRect.y + 70.0f), "Saved", kAccent, 15.0f); if (!m_hasSavedLayout) { - drawList.AddText(UIPoint(rightColumnX, stateRect.y + 104.0f), "尚未执行 Save Layout。", kTextMuted, 13.0f); - drawList.AddText(UIPoint(rightColumnX, stateRect.y + 128.0f), "先把状态改掉,再保存,这样 Load 才有恢复意义。", kTextMuted, 12.0f); + drawList.AddText(UIPoint(rightColumnX, stateRect.y + 104.0f), "尚未执行 Save Layout?, kTextMuted, 13.0f); + drawList.AddText(UIPoint(rightColumnX, stateRect.y + 128.0f), "先把状态改掉,再保存,这样 Load 才有恢复意义?, kTextMuted, 12.0f); } else { drawList.AddText(UIPoint(rightColumnX, stateRect.y + 96.0f), "active panel", kTextMuted, 12.0f); drawList.AddText(UIPoint(rightColumnX, stateRect.y + 116.0f), m_savedSnapshot.workspace.activePanelId.empty() ? "(none)" : m_savedSnapshot.workspace.activePanelId, kTextPrimary, 14.0f); @@ -827,9 +827,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/state/layout_persistence/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/state/layout_persistence/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText(UIPoint(footerRect.x + 760.0f, footerRect.y + 66.0f), captureSummary, kTextMuted, 12.0f); } diff --git a/tests/UI/Editor/integration/state/panel_host_lifecycle/CMakeLists.txt b/tests/UI/Editor/manual_validation/state/panel_host_lifecycle/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/state/panel_host_lifecycle/CMakeLists.txt rename to tests/UI/Editor/manual_validation/state/panel_host_lifecycle/CMakeLists.txt diff --git a/tests/UI/Editor/integration/state/panel_host_lifecycle/captures/.gitkeep b/tests/UI/Editor/manual_validation/state/panel_host_lifecycle/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/state/panel_host_lifecycle/captures/.gitkeep rename to tests/UI/Editor/manual_validation/state/panel_host_lifecycle/captures/.gitkeep diff --git a/tests/UI/Editor/integration/state/panel_host_lifecycle/main.cpp b/tests/UI/Editor/manual_validation/state/panel_host_lifecycle/main.cpp similarity index 93% rename from tests/UI/Editor/integration/state/panel_host_lifecycle/main.cpp rename to tests/UI/Editor/manual_validation/state/panel_host_lifecycle/main.cpp index 3af7f792..3799a349 100644 --- a/tests/UI/Editor/integration/state/panel_host_lifecycle/main.cpp +++ b/tests/UI/Editor/manual_validation/state/panel_host_lifecycle/main.cpp @@ -245,15 +245,15 @@ void DrawHostStateCard( std::string footer = {}; if (!attached) { - footer = "host 已 detached"; + footer = "host ?detached"; } else if (!visible) { - footer = "host 保持 attached,但当前不在屏幕中"; + footer = "host 保持 attached,但当前不在屏幕?; } else if (focused) { - footer = "当前 host 已 visible + active + focused"; + footer = "当前 host ?visible + active + focused"; } else if (active) { - footer = "当前 host 已 active,但还没 focus"; + footer = "当前 host ?active,但还没 focus"; } else { - footer = "当前 host 只处于 attached/visible"; + footer = "当前 host 只处?attached/visible"; } drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + rect.height - 26.0f), footer, kTextMuted, 11.0f); } @@ -388,7 +388,7 @@ private: m_captureRoot = ResolveRepoRootPath() / - "tests/UI/Editor/integration/state/panel_host_lifecycle/captures"; + "tests/UI/Editor/manual_validation/state/panel_host_lifecycle/captures"; m_autoScreenshot.Initialize(m_captureRoot); return true; } @@ -415,7 +415,7 @@ private: m_focusRequestPanelId.clear(); RefreshLifecycle( "Ready", - "已重置。先看 doc-b:它默认是 attached,但因为不是选中 tab,所以 visible=false。"); + "已重置。先?doc-b:它默认?attached,但因为不是选中 tab,所?visible=false?); } void RefreshLifecycle(std::string status, std::string message) { @@ -501,7 +501,7 @@ private: m_autoScreenshot.RequestCapture("manual_button"); m_lastStatus = "Ready"; m_lastMessage = - "截图已排队,输出到 tests/UI/Editor/integration/state/panel_host_lifecycle/captures/。"; + "截图已排队,输出?tests/UI/Editor/manual_validation/state/panel_host_lifecycle/captures/?; return; } @@ -510,14 +510,14 @@ private: RefreshLifecycle( "Focus", m_focusRequestPanelId.empty() - ? "当前没有 active panel 可 focus。" - : "已把 focus request 指向当前 active panel。"); + ? "当前没有 active panel ?focus? + : "已把 focus request 指向当前 active panel?); return; } if (action == ActionId::ClearFocus) { m_focusRequestPanelId.clear(); - RefreshLifecycle("Focus", "已清空 focus request。"); + RefreshLifecycle("Focus", "已清?focus request?); return; } @@ -565,19 +565,19 @@ private: 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 + 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); + "只验?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 + 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); - DrawCard(drawList, m_controlsRect, "操作", "只保留这组 contract 必要的按钮。"); + DrawCard(drawList, m_controlsRect, "操作", "只保留这?contract 必要的按钮?); for (const ButtonState& button : m_buttons) { DrawButton(drawList, button); } - DrawCard(drawList, m_stateRect, "状态", "重点检查 host flags 和本帧 lifecycle events。"); + DrawCard(drawList, m_stateRect, "状?, "重点检?host flags 和本?lifecycle events?); 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); @@ -614,14 +614,14 @@ private: addStateLine( "Screenshot", m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 或 Capture -> captures/") + ? std::string("F12 ?Capture -> captures/") : m_autoScreenshot.GetLastCaptureSummary()), kTextWeak, 11.0f); - DrawCard(drawList, m_previewRect, "Preview", "这里不是业务面板,只把 host state 直接可视化。"); + DrawCard(drawList, m_previewRect, "Preview", "这里不是业务面板,只?host state 直接可视化?); DrawHostStateCard(drawList, m_docARect, "Document A", docA); DrawHostStateCard(drawList, m_docBRect, "Document B", docB); DrawHostStateCard(drawList, m_detailsRect, "Details", details); diff --git a/tests/UI/Editor/integration/state/panel_session_flow/CMakeLists.txt b/tests/UI/Editor/manual_validation/state/panel_session_flow/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/state/panel_session_flow/CMakeLists.txt rename to tests/UI/Editor/manual_validation/state/panel_session_flow/CMakeLists.txt diff --git a/tests/UI/Editor/integration/state/panel_session_flow/captures/.gitkeep b/tests/UI/Editor/manual_validation/state/panel_session_flow/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/state/panel_session_flow/captures/.gitkeep rename to tests/UI/Editor/manual_validation/state/panel_session_flow/captures/.gitkeep diff --git a/tests/UI/Editor/integration/state/panel_session_flow/main.cpp b/tests/UI/Editor/manual_validation/state/panel_session_flow/main.cpp similarity index 94% rename from tests/UI/Editor/integration/state/panel_session_flow/main.cpp rename to tests/UI/Editor/manual_validation/state/panel_session_flow/main.cpp index d6de8390..7d3b650e 100644 --- a/tests/UI/Editor/integration/state/panel_session_flow/main.cpp +++ b/tests/UI/Editor/manual_validation/state/panel_session_flow/main.cpp @@ -321,7 +321,7 @@ private: } m_autoScreenshot.Initialize( - (ResolveRepoRootPath() / "tests/UI/Editor/integration/state/panel_session_flow/captures") + (ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/state/panel_session_flow/captures") .lexically_normal()); return true; } @@ -345,7 +345,7 @@ private: m_controller = BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); m_hasLastCommandResult = false; - m_lastActionSummary = "等待操作:先点 Hide Active,确认命令结果为 Changed,并检查 active/visible 状态联动。"; + m_lastActionSummary = "等待操作:先?Hide Active,确认命令结果为 Changed,并检?active/visible 状态联动?; } void OnResize(UINT width, UINT height) { @@ -509,15 +509,15 @@ private: const UIRect stateRect(actionRect.x + actionRect.width + 16.0f, actionRect.y, width - actionRect.width - margin * 2.0f - 16.0f, height - 246.0f); 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 + 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); + 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 + 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); - DrawCard(drawList, actionRect, "操作区", "每个按钮都会先生成 command,再交给 Workspace Controller 分发。"); - DrawCard(drawList, stateRect, "状态摘要", "重点检查 active panel、visible panels、selected tab index 和每个 panel session 状态。"); - DrawCard(drawList, footerRect, "最近结果", "这块显示 last command、status、message 与当前 validation。"); + DrawCard(drawList, actionRect, "操作?, "每个按钮都会先生?command,再交给 Workspace Controller 分发?); + DrawCard(drawList, stateRect, "状态摘?, "重点检?active panel、visible panels、selected tab index 和每?panel session 状态?); + DrawCard(drawList, footerRect, "最近结?, "这块显示 last command、status、message 与当?validation?); m_buttons.clear(); const std::vector> buttonDefs = { @@ -621,9 +621,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/state/panel_session_flow/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/state/panel_session_flow/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText(UIPoint(footerRect.x + 530.0f, footerRect.y + 66.0f), captureSummary, kTextMuted, 12.0f); } diff --git a/tests/UI/Editor/integration/state/shortcut_dispatch/CMakeLists.txt b/tests/UI/Editor/manual_validation/state/shortcut_dispatch/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/state/shortcut_dispatch/CMakeLists.txt rename to tests/UI/Editor/manual_validation/state/shortcut_dispatch/CMakeLists.txt diff --git a/tests/UI/Editor/integration/state/shortcut_dispatch/captures/.gitkeep b/tests/UI/Editor/manual_validation/state/shortcut_dispatch/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/state/shortcut_dispatch/captures/.gitkeep rename to tests/UI/Editor/manual_validation/state/shortcut_dispatch/captures/.gitkeep diff --git a/tests/UI/Editor/integration/state/shortcut_dispatch/main.cpp b/tests/UI/Editor/manual_validation/state/shortcut_dispatch/main.cpp similarity index 93% rename from tests/UI/Editor/integration/state/shortcut_dispatch/main.cpp rename to tests/UI/Editor/manual_validation/state/shortcut_dispatch/main.cpp index aadf5c8a..6ebc8222 100644 --- a/tests/UI/Editor/integration/state/shortcut_dispatch/main.cpp +++ b/tests/UI/Editor/manual_validation/state/shortcut_dispatch/main.cpp @@ -392,7 +392,7 @@ private: } m_autoScreenshot.Initialize( - (ResolveRepoRootPath() / "tests/UI/Editor/integration/state/shortcut_dispatch/captures").lexically_normal()); + (ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/state/shortcut_dispatch/captures").lexically_normal()); return true; } @@ -415,7 +415,7 @@ private: m_lastAction = "等待操作"; m_lastShortcutStatus = "Pending"; m_lastCommandStatus = "(none)"; - m_lastMessage = "先按 Ctrl+P,再看 Shortcut result 和 Command result 是不是分别变化。"; + m_lastMessage = "先按 Ctrl+P,再?Shortcut result ?Command result 是不是分别变化?; m_lastShortcutColor = kTextMuted; m_lastCommandColor = kTextMuted; } @@ -442,8 +442,8 @@ private: m_lastShortcutStatus = "Changed"; m_lastCommandStatus = "(none)"; m_lastMessage = m_textInputActive - ? "text input active = true。现在按 Ctrl+H / Ctrl+P,Shortcut result 应为 Suppressed。" - : "text input active = false。快捷键恢复正常 dispatch。"; + ? "text input active = true。现在按 Ctrl+H / Ctrl+P,Shortcut result 应为 Suppressed? + : "text input active = false。快捷键恢复正常 dispatch?; m_lastShortcutColor = kSuccess; m_lastCommandColor = kTextMuted; } else { @@ -475,8 +475,8 @@ private: m_lastShortcutStatus = "Changed"; m_lastCommandStatus = "(none)"; m_lastMessage = m_textInputActive - ? "text input active = true。下一次 Ctrl 快捷键应被 Suppressed。" - : "text input active = false。下一次 Ctrl 快捷键应正常 dispatch。"; + ? "text input active = true。下一?Ctrl 快捷键应?Suppressed? + : "text input active = false。下一?Ctrl 快捷键应正常 dispatch?; m_lastShortcutColor = kSuccess; m_lastCommandColor = kTextMuted; InvalidateRect(m_hwnd, nullptr, FALSE); @@ -534,16 +534,16 @@ private: const UIRect rightRect(leftRect.x + leftRect.width + 16.0f, leftRect.y, width - leftRect.width - margin * 2.0f - 16.0f, height - 254.0f); const UIRect footerRect(margin, height - 100.0f, width - margin * 2.0f, 80.0f); - DrawCard(drawList, headerRect, "测试功能:Editor Shortcut Dispatch", "只验证 shortcut match -> editor command -> workspace command dispatch。"); - drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 70.0f), "1. 初始 active=doc-a,按 Ctrl+P,当前 documents scope 应命中 Activate Details。", kTextPrimary, 13.0f); - drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 92.0f), "2. active=details 后再按 Ctrl+P,同一个 chord 应命中另一条 panel binding,并执行 Reset。", kTextPrimary, 13.0f); - drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 114.0f), "3. 按 Ctrl+H,验证 ActivePanel 参数源;按 Ctrl+W / Ctrl+O,验证固定 panel 命令。", kTextPrimary, 13.0f); - drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 136.0f), "4. 点 Toggle Text Input 或按 T,再按 Ctrl+H / Ctrl+P,Shortcut result 必须变成 Suppressed。", kTextPrimary, 13.0f); - drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 158.0f), "5. F12 保存截图。注意看 Footer 里 Shortcut result 和 Command result 是两层状态。", kTextPrimary, 13.0f); + DrawCard(drawList, headerRect, "测试功能:Editor Shortcut Dispatch", "只验?shortcut match -> editor command -> workspace command dispatch?); + drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 70.0f), "1. 初始 active=doc-a,按 Ctrl+P,当?documents scope 应命?Activate Details?, kTextPrimary, 13.0f); + drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 92.0f), "2. active=details 后再?Ctrl+P,同一?chord 应命中另一?panel binding,并执行 Reset?, kTextPrimary, 13.0f); + drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 114.0f), "3. ?Ctrl+H,验?ActivePanel 参数源;?Ctrl+W / Ctrl+O,验证固?panel 命令?, kTextPrimary, 13.0f); + drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 136.0f), "4. ?Toggle Text Input 或按 T,再?Ctrl+H / Ctrl+P,Shortcut result 必须变成 Suppressed?, kTextPrimary, 13.0f); + drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 158.0f), "5. F12 保存截图。注意看 Footer ?Shortcut result ?Command result 是两层状态?, kTextPrimary, 13.0f); - DrawCard(drawList, leftRect, "操作区", "这里只放辅助按钮;真正的验证入口是键盘快捷键。"); - DrawCard(drawList, rightRect, "状态摘要", "看当前 scope、text input、workspace 状态和 bindings。"); - DrawCard(drawList, footerRect, "最近结果", "Shortcut result 不等于 Command result。"); + DrawCard(drawList, leftRect, "操作?, "这里只放辅助按钮;真正的验证入口是键盘快捷键?); + DrawCard(drawList, rightRect, "状态摘?, "看当?scope、text input、workspace 状态和 bindings?); + DrawCard(drawList, footerRect, "最近结?, "Shortcut result 不等?Command result?); m_buttons = { { ActionId::ToggleTextInput, m_textInputActive ? "Toggle Text Input: On" : "Toggle Text Input: Off", UIRect(leftRect.x + 18.0f, leftRect.y + 74.0f, leftRect.width - 36.0f, 46.0f) }, @@ -600,9 +600,9 @@ private: const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 -> tests/UI/Editor/integration/state/shortcut_dispatch/captures/") + ? std::string("F12 -> tests/UI/Editor/manual_validation/state/shortcut_dispatch/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText(UIPoint(footerRect.x + 870.0f, footerRect.y + 60.0f), captureSummary, kTextMuted, 12.0f); diff --git a/tests/UI/Editor/integration/state/viewport_input_bridge_basic/CMakeLists.txt b/tests/UI/Editor/manual_validation/state/viewport_input_bridge_basic/CMakeLists.txt similarity index 100% rename from tests/UI/Editor/integration/state/viewport_input_bridge_basic/CMakeLists.txt rename to tests/UI/Editor/manual_validation/state/viewport_input_bridge_basic/CMakeLists.txt diff --git a/tests/UI/Editor/integration/state/viewport_input_bridge_basic/captures/.gitkeep b/tests/UI/Editor/manual_validation/state/viewport_input_bridge_basic/captures/.gitkeep similarity index 100% rename from tests/UI/Editor/integration/state/viewport_input_bridge_basic/captures/.gitkeep rename to tests/UI/Editor/manual_validation/state/viewport_input_bridge_basic/captures/.gitkeep diff --git a/tests/UI/Editor/integration/state/viewport_input_bridge_basic/main.cpp b/tests/UI/Editor/manual_validation/state/viewport_input_bridge_basic/main.cpp similarity index 96% rename from tests/UI/Editor/integration/state/viewport_input_bridge_basic/main.cpp rename to tests/UI/Editor/manual_validation/state/viewport_input_bridge_basic/main.cpp index 1185338f..f1858b05 100644 --- a/tests/UI/Editor/integration/state/viewport_input_bridge_basic/main.cpp +++ b/tests/UI/Editor/manual_validation/state/viewport_input_bridge_basic/main.cpp @@ -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 编辑器 | 视口输入桥接"; +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 ? "开" : "关"; + return value ? "开" : "?; } std::string FormatFloat(float value) { @@ -411,7 +411,7 @@ private: bool Initialize(HINSTANCE hInstance, int nCmdShow) { m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/state/viewport_input_bridge_basic/captures"; + ResolveRepoRootPath() / "tests/UI/Editor/manual_validation/state/viewport_input_bridge_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); WNDCLASSEXW windowClass = {}; @@ -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; @@ -597,7 +597,7 @@ private: frame.hasTexture = true; frame.texture = { 1u, 1280u, 720u }; frame.presentedSize = { 1280.0f, 720.0f }; - frame.statusText = "模拟视口帧"; + frame.statusText = "模拟视口?; return frame; } @@ -680,7 +680,7 @@ private: } else if (m_bridgeFrame.focusGained) { m_lastResult = "获得焦点"; } else if (m_bridgeFrame.captureStarted) { - m_lastResult = "开始捕获"; + m_lastResult = "开始捕?; } else if (m_bridgeFrame.captureEnded) { m_lastResult = "结束捕获"; } else if (!m_bridgeFrame.characters.empty()) { @@ -711,54 +711,54 @@ 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), - "检查:hover / focus / capture / local 坐标。", + "检查:hover / focus / capture / local 坐标?, kTextMuted, 11.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 86.0f), - "检查:surface 内左键按下后,Focus + Capture 进入。", + "检查:surface 内左键按下后,Focus + Capture 进入?, kTextMuted, 11.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 106.0f), - "检查: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), - "检查:release 后,Capture 必须释放。", + "检查:release 后,Capture 必须释放?, kTextMuted, 11.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 146.0f), - "检查:滚轮 / 按键 / 字符只在 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( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 186.0f), - "操作:点击 surface 外部,验证 clear focus。", + "操作:点?surface 外部,验?clear focus?, kTextMuted, 11.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 210.0f), - "结果:左侧状态、底部条、surface 边框必须同步。", + "结果:左侧状态、底部条、surface 边框必须同步?, kTextWeak, 11.0f); - DrawCard(drawList, m_controlsRect, "操作", "只保留 Reset / 截图 两个辅助操作。"); + DrawCard(drawList, m_controlsRect, "操作", "只保?Reset / 截图 两个辅助操作?); for (const ButtonState& button : m_buttons) { DrawButton(drawList, button); } - DrawCard(drawList, m_stateRect, "状态", "重点看桥接 frame、按键边沿、字符与 modifiers。"); + DrawCard(drawList, m_stateRect, "状?, "重点看桥?frame、按键边沿、字符与 modifiers?); float stateY = m_stateRect.y + 66.0f; auto addStateLine = [&](std::string text, const UIColor& color = kTextPrimary) { drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY), std::move(text), color, 12.0f); @@ -789,14 +789,14 @@ private: addStateLine("Characters: " + DescribeCharacters(m_bridgeFrame), kTextMuted); const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图排队?.." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("截图:F12 或按钮 -> viewport_input_bridge_basic/captures/") + ? std::string("截图:F12 或按?-> viewport_input_bridge_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); addStateLine("Result: " + m_lastResult, kTextMuted); addStateLine(captureSummary, kTextWeak); - DrawCard(drawList, m_previewRect, "预览", "这里只放一个 ViewportSlot,用它承载输入边界。"); + DrawCard(drawList, m_previewRect, "预览", "这里只放一?ViewportSlot,用它承载输入边界?); drawList.AddFilledRect( UIRect( m_previewRect.x + 12.0f, diff --git a/tests/UI/Editor/smoke/CMakeLists.txt b/tests/UI/Editor/smoke/CMakeLists.txt new file mode 100644 index 00000000..6c280214 --- /dev/null +++ b/tests/UI/Editor/smoke/CMakeLists.txt @@ -0,0 +1,37 @@ +if(NOT TARGET XCUIEditorApp) + add_custom_target(editor_ui_smoke_targets) + return() +endif() + +add_executable(editor_ui_smoke_runner + xcui_editor_app_smoke_runner.cpp +) + +target_link_libraries(editor_ui_smoke_runner + PRIVATE + user32.lib +) + +if(MSVC) + target_compile_options(editor_ui_smoke_runner PRIVATE /utf-8 /FS) + set_property(TARGET editor_ui_smoke_runner PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") +endif() + +add_test( + NAME xcui_editor_app_smoke + COMMAND + $ + $ +) +set_tests_properties(xcui_editor_app_smoke PROPERTIES + TIMEOUT 45 + RUN_SERIAL TRUE + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} +) + +add_custom_target(editor_ui_smoke_targets + DEPENDS + editor_ui_smoke_runner + XCUIEditorApp +) diff --git a/tests/UI/Editor/smoke/xcui_editor_app_smoke_runner.cpp b/tests/UI/Editor/smoke/xcui_editor_app_smoke_runner.cpp new file mode 100644 index 00000000..d538e18e --- /dev/null +++ b/tests/UI/Editor/smoke/xcui_editor_app_smoke_runner.cpp @@ -0,0 +1,324 @@ +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr auto kLaunchTimeout = std::chrono::seconds(20); +constexpr auto kShutdownTimeout = std::chrono::seconds(10); +constexpr auto kPollInterval = std::chrono::milliseconds(100); +constexpr const char* kReadyTrace = "[app] shell runtime initialized:"; + +struct ProcessWindowSearch { + DWORD processId = 0; + HWND hwnd = nullptr; + bool requireVisible = false; +}; + +std::wstring QuoteCommandLine(const std::filesystem::path& executablePath) { + return L"\"" + executablePath.wstring() + L"\""; +} + +void PrintFailure(std::string_view message) { + std::cerr << "[xcui_editor_app_smoke] " << message << '\n'; +} + +std::string ReadTextFile(const std::filesystem::path& path) { + std::ifstream stream(path, std::ios::in | std::ios::binary); + if (!stream.is_open()) { + return {}; + } + + std::ostringstream buffer = {}; + buffer << stream.rdbuf(); + return buffer.str(); +} + +bool FileContains(const std::filesystem::path& path, std::string_view needle) { + const std::string content = ReadTextFile(path); + return !content.empty() && content.find(needle) != std::string::npos; +} + +BOOL CALLBACK FindProcessWindowProc(HWND hwnd, LPARAM lParam) { + auto& search = *reinterpret_cast(lParam); + + DWORD processId = 0; + GetWindowThreadProcessId(hwnd, &processId); + if (processId != search.processId) { + return TRUE; + } + + if (search.requireVisible && !IsWindowVisible(hwnd)) { + return TRUE; + } + + RECT clientRect = {}; + if (!GetClientRect(hwnd, &clientRect)) { + return TRUE; + } + + const LONG width = clientRect.right - clientRect.left; + const LONG height = clientRect.bottom - clientRect.top; + if (width <= 0 || height <= 0) { + return TRUE; + } + + search.hwnd = hwnd; + return FALSE; +} + +HWND FindProcessWindow(DWORD processId, bool requireVisible) { + ProcessWindowSearch search = {}; + search.processId = processId; + search.requireVisible = requireVisible; + EnumWindows(&FindProcessWindowProc, reinterpret_cast(&search)); + return search.hwnd; +} + +bool RemoveFileIfPresent(const std::filesystem::path& path) { + std::error_code errorCode = {}; + if (!std::filesystem::exists(path, errorCode)) { + return true; + } + + std::filesystem::remove(path, errorCode); + return !errorCode; +} + +bool PrepareLogDirectory( + const std::filesystem::path& logDirectory, + const std::filesystem::path& runtimeLogPath, + const std::filesystem::path& crashLogPath) { + std::error_code errorCode = {}; + std::filesystem::create_directories(logDirectory, errorCode); + if (errorCode) { + PrintFailure("failed to create log directory: " + logDirectory.string()); + return false; + } + + if (!RemoveFileIfPresent(runtimeLogPath)) { + PrintFailure("failed to clear runtime log: " + runtimeLogPath.string()); + return false; + } + + if (!RemoveFileIfPresent(crashLogPath)) { + PrintFailure("failed to clear crash log: " + crashLogPath.string()); + return false; + } + + return true; +} + +bool LaunchEditorProcess( + const std::filesystem::path& executablePath, + PROCESS_INFORMATION& outProcessInfo) { + SetEnvironmentVariableW(L"XCUI_AUTO_CAPTURE_ON_STARTUP", L"0"); + SetEnvironmentVariableW(L"XCUIEDITOR_SMOKE_TEST", nullptr); + SetEnvironmentVariableW(L"XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT", nullptr); + + STARTUPINFOW startupInfo = {}; + startupInfo.cb = sizeof(startupInfo); + + std::wstring commandLine = QuoteCommandLine(executablePath); + std::vector mutableCommandLine(commandLine.begin(), commandLine.end()); + mutableCommandLine.push_back(L'\0'); + + ZeroMemory(&outProcessInfo, sizeof(outProcessInfo)); + return CreateProcessW( + executablePath.c_str(), + mutableCommandLine.data(), + nullptr, + nullptr, + FALSE, + 0, + nullptr, + nullptr, + &startupInfo, + &outProcessInfo) == TRUE; +} + +bool WaitForEditorReady( + HANDLE processHandle, + DWORD processId, + const std::filesystem::path& runtimeLogPath, + HWND& outWindow) { + const auto deadline = std::chrono::steady_clock::now() + kLaunchTimeout; + outWindow = nullptr; + + while (std::chrono::steady_clock::now() < deadline) { + if (WaitForSingleObject(processHandle, 0) == WAIT_OBJECT_0) { + return false; + } + + const bool shellReady = FileContains(runtimeLogPath, kReadyTrace); + const HWND visibleWindow = FindProcessWindow(processId, true); + const HWND processWindow = + visibleWindow != nullptr ? visibleWindow : FindProcessWindow(processId, false); + if (shellReady && processWindow != nullptr) { + outWindow = processWindow; + return true; + } + + Sleep(static_cast(kPollInterval.count())); + } + + return false; +} + +bool WaitForEditorExit(HANDLE processHandle, DWORD timeoutMilliseconds, DWORD& outExitCode) { + if (WaitForSingleObject(processHandle, timeoutMilliseconds) != WAIT_OBJECT_0) { + return false; + } + + outExitCode = 0; + return GetExitCodeProcess(processHandle, &outExitCode) == TRUE; +} + +std::string BuildEarlyExitReason( + HANDLE processHandle, + const std::filesystem::path& runtimeLogPath, + const std::filesystem::path& crashLogPath) { + DWORD exitCode = 0; + GetExitCodeProcess(processHandle, &exitCode); + + std::ostringstream message = {}; + message << "editor exited before ready, code=" << exitCode; + + const std::string runtimeLog = ReadTextFile(runtimeLogPath); + if (!runtimeLog.empty()) { + message << "\nruntime.log:\n" << runtimeLog; + } + + const std::string crashLog = ReadTextFile(crashLogPath); + if (!crashLog.empty()) { + message << "\ncrash.log:\n" << crashLog; + } + + return message.str(); +} + +std::string BuildTimeoutReason( + const std::filesystem::path& runtimeLogPath, + const std::filesystem::path& crashLogPath) { + std::ostringstream message = {}; + message << "editor did not reach ready state within " + << std::chrono::duration_cast(kLaunchTimeout).count() + << "ms"; + + const std::string runtimeLog = ReadTextFile(runtimeLogPath); + if (!runtimeLog.empty()) { + message << "\nruntime.log:\n" << runtimeLog; + } + + const std::string crashLog = ReadTextFile(crashLogPath); + if (!crashLog.empty()) { + message << "\ncrash.log:\n" << crashLog; + } + + return message.str(); +} + +void CloseProcessHandles(PROCESS_INFORMATION& processInfo) { + if (processInfo.hThread != nullptr) { + CloseHandle(processInfo.hThread); + processInfo.hThread = nullptr; + } + if (processInfo.hProcess != nullptr) { + CloseHandle(processInfo.hProcess); + processInfo.hProcess = nullptr; + } +} + +} // namespace + +int wmain(int argc, wchar_t* argv[]) { + if (argc < 2 || argv[1] == nullptr || argv[1][0] == L'\0') { + PrintFailure("missing XCUIEditorApp path"); + return 1; + } + + const std::filesystem::path executablePath = std::filesystem::path(argv[1]).lexically_normal(); + if (!std::filesystem::exists(executablePath)) { + PrintFailure("XCUIEditorApp not found: " + executablePath.string()); + return 1; + } + + const std::filesystem::path logDirectory = + (executablePath.parent_path() / "logs").lexically_normal(); + const std::filesystem::path runtimeLogPath = + (logDirectory / "runtime.log").lexically_normal(); + const std::filesystem::path crashLogPath = + (logDirectory / "crash.log").lexically_normal(); + if (!PrepareLogDirectory(logDirectory, runtimeLogPath, crashLogPath)) { + return 1; + } + + PROCESS_INFORMATION processInfo = {}; + if (!LaunchEditorProcess(executablePath, processInfo)) { + PrintFailure("failed to launch XCUIEditorApp"); + return 1; + } + + HWND mainWindow = nullptr; + if (!WaitForEditorReady( + processInfo.hProcess, + processInfo.dwProcessId, + runtimeLogPath, + mainWindow)) { + const DWORD waitState = WaitForSingleObject(processInfo.hProcess, 0); + const std::string message = + waitState == WAIT_OBJECT_0 + ? BuildEarlyExitReason(processInfo.hProcess, runtimeLogPath, crashLogPath) + : BuildTimeoutReason(runtimeLogPath, crashLogPath); + PrintFailure(message); + TerminateProcess(processInfo.hProcess, 2); + CloseProcessHandles(processInfo); + return 1; + } + + if (!PostMessageW(mainWindow, WM_CLOSE, 0, 0)) { + PrintFailure("failed to post WM_CLOSE to XCUIEditorApp"); + TerminateProcess(processInfo.hProcess, 3); + CloseProcessHandles(processInfo); + return 1; + } + + DWORD exitCode = 0; + if (!WaitForEditorExit( + processInfo.hProcess, + static_cast( + std::chrono::duration_cast(kShutdownTimeout).count()), + exitCode)) { + PrintFailure("editor did not exit after WM_CLOSE"); + TerminateProcess(processInfo.hProcess, 4); + CloseProcessHandles(processInfo); + return 1; + } + + CloseProcessHandles(processInfo); + + if (exitCode != 0) { + PrintFailure("editor exited with non-zero code: " + std::to_string(exitCode)); + return 1; + } + + const std::string crashLog = ReadTextFile(crashLogPath); + if (!crashLog.empty()) { + PrintFailure("crash log was generated:\n" + crashLog); + return 1; + } + + return 0; +} diff --git a/tests/UI/Editor/unit/CMakeLists.txt b/tests/UI/Editor/unit/CMakeLists.txt index c54abfbd..0ea3c31e 100644 --- a/tests/UI/Editor/unit/CMakeLists.txt +++ b/tests/UI/Editor/unit/CMakeLists.txt @@ -1,5 +1,4 @@ set(EDITOR_UI_UNIT_TEST_SOURCES - test_input_modifier_tracker.cpp test_ui_editor_command_dispatcher.cpp test_ui_editor_command_registry.cpp test_ui_editor_dock_host_interaction.cpp @@ -116,8 +115,10 @@ if(TARGET XCUIEditorAppLib) endif() list(APPEND EDITOR_APP_FEATURE_TEST_SOURCES + test_input_modifier_tracker.cpp test_editor_host_command_bridge.cpp test_editor_shell_asset_validation.cpp + test_editor_window_tab_drag_drop_target.cpp test_editor_window_workspace_store.cpp test_structured_editor_shell.cpp test_editor_window_input_routing.cpp diff --git a/tests/UI/Editor/unit/test_editor_window_tab_drag_drop_target.cpp b/tests/UI/Editor/unit/test_editor_window_tab_drag_drop_target.cpp new file mode 100644 index 00000000..32cb2ab6 --- /dev/null +++ b/tests/UI/Editor/unit/test_editor_window_tab_drag_drop_target.cpp @@ -0,0 +1,83 @@ +#include + +#include "app/Platform/Win32/WindowManager/TabDragDropTarget.h" + +#include + +namespace { + +using XCEngine::UI::UIPoint; +using XCEngine::UI::UIRect; +using XCEngine::UI::Editor::App::Internal::EditorWindowTabDragDropTarget; +using XCEngine::UI::Editor::App::Internal::ResolveEditorWindowTabDragDropTarget; +using XCEngine::UI::Editor::UIEditorWorkspaceDockPlacement; +using XCEngine::UI::Editor::Widgets::UIEditorDockHostLayout; +using XCEngine::UI::Editor::Widgets::UIEditorDockHostTabItemLayout; +using XCEngine::UI::Editor::Widgets::UIEditorDockHostTabStackLayout; +using XCEngine::UI::Editor::Widgets::UIEditorTabStripInvalidIndex; + +UIEditorDockHostLayout BuildDockLayout() { + UIEditorDockHostLayout layout = {}; + layout.bounds = UIRect(0.0f, 0.0f, 420.0f, 280.0f); + + UIEditorDockHostTabStackLayout tabStack = {}; + tabStack.nodeId = "stack-a"; + tabStack.bounds = UIRect(20.0f, 20.0f, 300.0f, 200.0f); + tabStack.items = { + UIEditorDockHostTabItemLayout{ "doc-a", "Document A", false }, + UIEditorDockHostTabItemLayout{ "doc-b", "Document B", true }, + UIEditorDockHostTabItemLayout{ "doc-c", "Document C", false }, + }; + tabStack.tabStripLayout.headerRect = UIRect(20.0f, 20.0f, 180.0f, 24.0f); + tabStack.tabStripLayout.tabHeaderRects = { + UIRect(20.0f, 20.0f, 40.0f, 24.0f), + UIRect(64.0f, 20.0f, 44.0f, 24.0f), + UIRect(112.0f, 20.0f, 48.0f, 24.0f), + }; + layout.tabStacks.push_back(std::move(tabStack)); + + return layout; +} + +} // namespace + +TEST(EditorWindowTabDragDropTargetTests, ReturnsInvalidTargetOutsideDockHostBounds) { + const UIEditorDockHostLayout layout = BuildDockLayout(); + + const EditorWindowTabDragDropTarget target = + ResolveEditorWindowTabDragDropTarget(layout, UIPoint(460.0f, 40.0f)); + + EXPECT_FALSE(target.valid); + EXPECT_TRUE(target.nodeId.empty()); + EXPECT_EQ(target.placement, UIEditorWorkspaceDockPlacement::Center); + EXPECT_EQ(target.insertionIndex, UIEditorTabStripInvalidIndex); +} + +TEST(EditorWindowTabDragDropTargetTests, HeaderGapResolvesCenterPlacementAndAppendInsertionIndex) { + const UIEditorDockHostLayout layout = BuildDockLayout(); + + const EditorWindowTabDragDropTarget target = + ResolveEditorWindowTabDragDropTarget(layout, UIPoint(190.0f, 32.0f)); + + ASSERT_TRUE(target.valid); + EXPECT_EQ(target.nodeId, "stack-a"); + EXPECT_EQ(target.placement, UIEditorWorkspaceDockPlacement::Center); + EXPECT_EQ(target.insertionIndex, 3u); +} + +TEST(EditorWindowTabDragDropTargetTests, BodyEdgesResolveDirectionalDockPlacement) { + const UIEditorDockHostLayout layout = BuildDockLayout(); + + EXPECT_EQ( + ResolveEditorWindowTabDragDropTarget(layout, UIPoint(24.0f, 120.0f)).placement, + UIEditorWorkspaceDockPlacement::Left); + EXPECT_EQ( + ResolveEditorWindowTabDragDropTarget(layout, UIPoint(314.0f, 120.0f)).placement, + UIEditorWorkspaceDockPlacement::Right); + EXPECT_EQ( + ResolveEditorWindowTabDragDropTarget(layout, UIPoint(120.0f, 48.0f)).placement, + UIEditorWorkspaceDockPlacement::Top); + EXPECT_EQ( + ResolveEditorWindowTabDragDropTarget(layout, UIPoint(120.0f, 214.0f)).placement, + UIEditorWorkspaceDockPlacement::Bottom); +} diff --git a/tests/UI/Editor/unit/test_editor_window_workspace_store.cpp b/tests/UI/Editor/unit/test_editor_window_workspace_store.cpp index 2622c51f..7d590c7b 100644 --- a/tests/UI/Editor/unit/test_editor_window_workspace_store.cpp +++ b/tests/UI/Editor/unit/test_editor_window_workspace_store.cpp @@ -1,6 +1,6 @@ #include -#include "Platform/Win32/WindowManager/EditorWindowWorkspaceStore.h" +#include "Composition/EditorWindowWorkspaceStore.h" #include #include diff --git a/tests/UI/Editor/unit/test_input_modifier_tracker.cpp b/tests/UI/Editor/unit/test_input_modifier_tracker.cpp index 5dbe5650..90e2072c 100644 --- a/tests/UI/Editor/unit/test_input_modifier_tracker.cpp +++ b/tests/UI/Editor/unit/test_input_modifier_tracker.cpp @@ -4,7 +4,7 @@ #include -#include "Platform/Win32/InputModifierTracker.h" +#include "app/Platform/Win32/InputModifierTracker.h" #include diff --git a/tests/UI/Editor/unit/test_project_panel.cpp b/tests/UI/Editor/unit/test_project_panel.cpp index cfb0b14e..e73a61a8 100644 --- a/tests/UI/Editor/unit/test_project_panel.cpp +++ b/tests/UI/Editor/unit/test_project_panel.cpp @@ -1,4 +1,5 @@ #include "Features/Project/ProjectPanel.h" +#include "Ports/SystemInteractionPort.h" #include "Rendering/Assets/BuiltInIcons.h" #include "Composition/EditorPanelIds.h" @@ -13,6 +14,32 @@ namespace XCEngine::UI::Editor::App { namespace { +class FakeSystemInteractionHost final : public Ports::SystemInteractionPort { +public: + bool CopyTextToClipboard(std::string_view text) override { + lastClipboardText = std::string(text); + ++clipboardCallCount; + return clipboardResult; + } + + bool RevealPathInFileBrowser( + const std::filesystem::path& path, + bool selectTarget) override { + lastRevealPath = path; + lastRevealSelectTarget = selectTarget; + ++revealCallCount; + return revealResult; + } + + bool clipboardResult = true; + bool revealResult = true; + int clipboardCallCount = 0; + int revealCallCount = 0; + bool lastRevealSelectTarget = false; + std::string lastClipboardText = {}; + std::filesystem::path lastRevealPath = {}; +}; + class TemporaryRepo final { public: TemporaryRepo() { @@ -83,6 +110,13 @@ UIEditorPanelContentHostFrame MakeProjectHostFrame() { return event; } +PanelInputContext MakeFocusedPanelInputContext() { + PanelInputContext inputContext = {}; + inputContext.allowInteraction = true; + inputContext.hasInputFocus = true; + return inputContext; +} + TEST(ProjectPanelTests, CreateFolderCommandCreatesDirectoryAndQueuesRename) { TemporaryRepo repo = {}; @@ -141,13 +175,11 @@ TEST(ProjectPanelTests, BackgroundContextMenuCreateFolderUsesCurrentFolder) { panel.Update( hostFrame, { MakePointerButtonDown(520.0f, 180.0f, ::XCEngine::UI::UIPointerButton::Right) }, - true, - true); + MakeFocusedPanelInputContext()); panel.Update( hostFrame, { MakePointerButtonDown(520.0f, 194.0f, ::XCEngine::UI::UIPointerButton::Left) }, - true, - true); + MakeFocusedPanelInputContext()); EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/New Folder")); const UIEditorHostCommandEvaluationResult renameEvaluation = @@ -172,13 +204,11 @@ TEST(ProjectPanelTests, FolderContextMenuCreateFolderUsesFolderTarget) { panel.Update( hostFrame, { MakePointerButtonDown(300.0f, 80.0f, ::XCEngine::UI::UIPointerButton::Right) }, - true, - true); + MakeFocusedPanelInputContext()); panel.Update( hostFrame, { MakePointerButtonDown(320.0f, 120.0f, ::XCEngine::UI::UIPointerButton::Left) }, - true, - true); + MakeFocusedPanelInputContext()); EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/FolderA/New Folder")); } @@ -241,5 +271,56 @@ TEST(ProjectPanelTests, BuiltInIconsCanBeConfiguredBeforeRuntimeInitialization) EXPECT_TRUE(evaluation.executable); } +TEST(ProjectPanelTests, CopyPathCommandUsesInjectedSystemHost) { + TemporaryRepo repo = {}; + ASSERT_TRUE(repo.CreateDirectory("project/Assets/Scripts")); + ASSERT_TRUE(repo.WriteFile("project/Assets/Scripts/Player.cs")); + + EditorProjectRuntime runtime = {}; + ASSERT_TRUE(runtime.Initialize(repo.Root())); + ASSERT_TRUE(runtime.NavigateToFolder("Assets/Scripts")); + ASSERT_TRUE(runtime.SetSelection("Assets/Scripts/Player.cs")); + + ProjectPanel panel = {}; + FakeSystemInteractionHost systemHost = {}; + panel.SetProjectRuntime(&runtime); + panel.SetSystemInteractionHost(&systemHost); + + const UIEditorHostCommandEvaluationResult evaluation = + panel.EvaluateAssetCommand("assets.copy_path"); + EXPECT_TRUE(evaluation.executable); + + const UIEditorHostCommandDispatchResult dispatch = + panel.DispatchAssetCommand("assets.copy_path"); + EXPECT_TRUE(dispatch.commandExecuted); + EXPECT_EQ(systemHost.clipboardCallCount, 1); + EXPECT_EQ(systemHost.lastClipboardText, "Assets/Scripts/Player.cs"); +} + +TEST(ProjectPanelTests, ShowInExplorerCommandUsesInjectedSystemHost) { + TemporaryRepo repo = {}; + ASSERT_TRUE(repo.CreateDirectory("project/Assets/Scripts")); + ASSERT_TRUE(repo.WriteFile("project/Assets/Scripts/Player.cs")); + + EditorProjectRuntime runtime = {}; + ASSERT_TRUE(runtime.Initialize(repo.Root())); + ASSERT_TRUE(runtime.NavigateToFolder("Assets/Scripts")); + ASSERT_TRUE(runtime.SetSelection("Assets/Scripts/Player.cs")); + + ProjectPanel panel = {}; + FakeSystemInteractionHost systemHost = {}; + panel.SetProjectRuntime(&runtime); + panel.SetSystemInteractionHost(&systemHost); + + const UIEditorHostCommandDispatchResult dispatch = + panel.DispatchAssetCommand("assets.show_in_explorer"); + EXPECT_TRUE(dispatch.commandExecuted); + EXPECT_EQ(systemHost.revealCallCount, 1); + EXPECT_EQ( + systemHost.lastRevealPath.lexically_normal(), + (repo.Root() / "project/Assets/Scripts/Player.cs").lexically_normal()); + EXPECT_TRUE(systemHost.lastRevealSelectTarget); +} + } // namespace } // namespace XCEngine::UI::Editor::App diff --git a/tests/UI/Editor/unit/test_scene_viewport_runtime.cpp b/tests/UI/Editor/unit/test_scene_viewport_runtime.cpp index 88fda852..29f819bc 100644 --- a/tests/UI/Editor/unit/test_scene_viewport_runtime.cpp +++ b/tests/UI/Editor/unit/test_scene_viewport_runtime.cpp @@ -1,7 +1,9 @@ #include "Scene/EditorSceneRuntime.h" #include "Features/Scene/SceneViewportController.h" #include "Features/Inspector/InspectorSubject.h" +#include "Rendering/Viewport/SceneViewportRenderService.h" #include "Rendering/Viewport/ViewportHostService.h" +#include "Rendering/Viewport/ViewportRenderTargetInternal.h" #include "State/EditorSelectionService.h" #include "Composition/EditorPanelIds.h" @@ -482,7 +484,7 @@ TEST(SceneViewportRuntimeTests, RightMouseDragRotatesSceneCameraThroughViewportC const Math::Vector3 beforeForward = transform->GetForward(); SceneViewportController controller = {}; - ViewportHostService viewportHostService = {}; + SceneViewportRenderService sceneViewportRenderService = {}; UIEditorViewportInputBridgeState inputBridgeState = {}; const UIRect inputRect(100.0f, 80.0f, 640.0f, 360.0f); const UISize viewportSize(640.0f, 360.0f); @@ -501,7 +503,7 @@ TEST(SceneViewportRuntimeTests, RightMouseDragRotatesSceneCameraThroughViewportC }); controller.Update( runtime, - viewportHostService, + sceneViewportRenderService, BuildSceneComposeState(inputBridgeState), BuildSceneComposeFrame(pressFrame, inputRect, viewportSize)); @@ -517,7 +519,7 @@ TEST(SceneViewportRuntimeTests, RightMouseDragRotatesSceneCameraThroughViewportC }); controller.Update( runtime, - viewportHostService, + sceneViewportRenderService, BuildSceneComposeState(inputBridgeState), BuildSceneComposeFrame(dragFrame, inputRect, viewportSize)); @@ -566,7 +568,7 @@ TEST(SceneViewportRuntimeTests, MiddleMouseDragPansSceneCameraWithGrabSemantics) const Math::Vector3 before = transform->GetPosition(); SceneViewportController controller = {}; - ViewportHostService viewportHostService = {}; + SceneViewportRenderService sceneViewportRenderService = {}; UIEditorViewportInputBridgeState inputBridgeState = {}; const UIRect inputRect(100.0f, 80.0f, 640.0f, 360.0f); const UISize viewportSize(640.0f, 360.0f); @@ -585,7 +587,7 @@ TEST(SceneViewportRuntimeTests, MiddleMouseDragPansSceneCameraWithGrabSemantics) }); controller.Update( runtime, - viewportHostService, + sceneViewportRenderService, BuildSceneComposeState(inputBridgeState), BuildSceneComposeFrame(pressFrame, inputRect, viewportSize)); @@ -601,7 +603,7 @@ TEST(SceneViewportRuntimeTests, MiddleMouseDragPansSceneCameraWithGrabSemantics) }); controller.Update( runtime, - viewportHostService, + sceneViewportRenderService, BuildSceneComposeState(inputBridgeState), BuildSceneComposeFrame(dragFrame, inputRect, viewportSize)); @@ -625,7 +627,7 @@ TEST(SceneViewportRuntimeTests, ViewToolLeftMouseDragPansSceneCameraWithGrabSema const Math::Vector3 before = transform->GetPosition(); SceneViewportController controller = {}; - ViewportHostService viewportHostService = {}; + SceneViewportRenderService sceneViewportRenderService = {}; UIEditorViewportInputBridgeState inputBridgeState = {}; const UIRect inputRect(100.0f, 80.0f, 640.0f, 360.0f); const UISize viewportSize(640.0f, 360.0f); @@ -644,7 +646,7 @@ TEST(SceneViewportRuntimeTests, ViewToolLeftMouseDragPansSceneCameraWithGrabSema }); controller.Update( runtime, - viewportHostService, + sceneViewportRenderService, BuildSceneComposeState(inputBridgeState), BuildSceneComposeFrame(pressFrame, inputRect, viewportSize)); @@ -660,7 +662,7 @@ TEST(SceneViewportRuntimeTests, ViewToolLeftMouseDragPansSceneCameraWithGrabSema }); controller.Update( runtime, - viewportHostService, + sceneViewportRenderService, BuildSceneComposeState(inputBridgeState), BuildSceneComposeFrame(dragFrame, inputRect, viewportSize)); @@ -679,7 +681,7 @@ TEST(SceneViewportRuntimeTests, MouseWheelUsesSingleNotchNormalizationForSceneZo runtime.BuildSceneViewportRenderRequest().orbitDistance; SceneViewportController controller = {}; - ViewportHostService viewportHostService = {}; + SceneViewportRenderService sceneViewportRenderService = {}; UIEditorViewportInputBridgeState inputBridgeState = {}; const UIRect inputRect(100.0f, 80.0f, 640.0f, 360.0f); const UISize viewportSize(640.0f, 360.0f); @@ -692,7 +694,7 @@ TEST(SceneViewportRuntimeTests, MouseWheelUsesSingleNotchNormalizationForSceneZo }); controller.Update( runtime, - viewportHostService, + sceneViewportRenderService, BuildSceneComposeState(inputBridgeState), BuildSceneComposeFrame(hoverFrame, inputRect, viewportSize)); @@ -704,7 +706,7 @@ TEST(SceneViewportRuntimeTests, MouseWheelUsesSingleNotchNormalizationForSceneZo }); controller.Update( runtime, - viewportHostService, + sceneViewportRenderService, BuildSceneComposeState(inputBridgeState), BuildSceneComposeFrame(wheelFrame, inputRect, viewportSize)); @@ -725,7 +727,7 @@ TEST(SceneViewportRuntimeTests, ToolShortcutSwitchesFocusedSceneViewportIntoTran EXPECT_EQ(runtime.GetToolMode(), SceneToolMode::View); SceneViewportController controller = {}; - ViewportHostService viewportHostService = {}; + SceneViewportRenderService sceneViewportRenderService = {}; UIEditorViewportInputBridgeState inputBridgeState = {}; const UIRect inputRect(100.0f, 80.0f, 640.0f, 360.0f); const UISize viewportSize(640.0f, 360.0f); @@ -749,7 +751,7 @@ TEST(SceneViewportRuntimeTests, ToolShortcutSwitchesFocusedSceneViewportIntoTran }); controller.Update( runtime, - viewportHostService, + sceneViewportRenderService, BuildSceneComposeState(inputBridgeState), BuildSceneComposeFrame(focusFrame, inputRect, viewportSize)); @@ -765,7 +767,7 @@ TEST(SceneViewportRuntimeTests, ToolShortcutSwitchesFocusedSceneViewportIntoTran }); controller.Update( runtime, - viewportHostService, + sceneViewportRenderService, BuildSceneComposeState(inputBridgeState), BuildSceneComposeFrame(shortcutFrame, inputRect, viewportSize)); @@ -782,7 +784,7 @@ TEST(SceneViewportRuntimeTests, SceneToolOverlayClickSwitchesModeOnPointerDown) runtime.SetToolMode(SceneToolMode::Translate); SceneViewportController controller = {}; - ViewportHostService viewportHostService = {}; + SceneViewportRenderService sceneViewportRenderService = {}; UIEditorViewportInputBridgeState inputBridgeState = {}; const UIRect inputRect(100.0f, 104.0f, 640.0f, 360.0f); const UISize viewportSize(640.0f, 360.0f); @@ -803,7 +805,7 @@ TEST(SceneViewportRuntimeTests, SceneToolOverlayClickSwitchesModeOnPointerDown) }); controller.Update( runtime, - viewportHostService, + sceneViewportRenderService, BuildSceneComposeState(inputBridgeState), BuildSceneComposeFrame(frame, inputRect, viewportSize)); @@ -820,7 +822,7 @@ TEST(SceneViewportRuntimeTests, SceneToolOverlayIncludesTransformButtonAndSwitch runtime.SetToolMode(SceneToolMode::Translate); SceneViewportController controller = {}; - ViewportHostService viewportHostService = {}; + SceneViewportRenderService sceneViewportRenderService = {}; UIEditorViewportInputBridgeState inputBridgeState = {}; const UIRect inputRect(100.0f, 104.0f, 640.0f, 360.0f); const UISize viewportSize(640.0f, 360.0f); @@ -844,7 +846,7 @@ TEST(SceneViewportRuntimeTests, SceneToolOverlayIncludesTransformButtonAndSwitch }); controller.Update( runtime, - viewportHostService, + sceneViewportRenderService, BuildSceneComposeState(inputBridgeState), BuildSceneComposeFrame(frame, inputRect, viewportSize)); @@ -861,7 +863,7 @@ TEST(SceneViewportRuntimeTests, SceneToolOverlayHandlesCoalescedClickInSingleFra runtime.SetToolMode(SceneToolMode::Translate); SceneViewportController controller = {}; - ViewportHostService viewportHostService = {}; + SceneViewportRenderService sceneViewportRenderService = {}; UIEditorViewportInputBridgeState inputBridgeState = {}; const UIRect inputRect(100.0f, 104.0f, 640.0f, 360.0f); const UISize viewportSize(640.0f, 360.0f); @@ -890,12 +892,87 @@ TEST(SceneViewportRuntimeTests, SceneToolOverlayHandlesCoalescedClickInSingleFra }); controller.Update( runtime, - viewportHostService, + sceneViewportRenderService, BuildSceneComposeState(inputBridgeState), BuildSceneComposeFrame(frame, inputRect, viewportSize)); EXPECT_EQ(runtime.GetToolMode(), SceneToolMode::Rotate); } +TEST(SceneViewportRuntimeTests, SceneViewportRendererDeclaresExplicitAuxiliaryResourceRequirements) { + const ViewportResourceRequirements requirements = + SceneViewportRenderService::GetViewportResourceRequirements(); + + EXPECT_TRUE(requirements.requiresDepthSampling); + EXPECT_TRUE(requirements.requiresObjectIdSurface); + EXPECT_TRUE(requirements.requiresSelectionMaskSurface); +} + +TEST(SceneViewportRuntimeTests, ViewportResourceReuseQueryHonorsExplicitRequirementsInsteadOfViewportKind) { + ViewportRenderTargets targets = {}; + targets.width = 1280u; + targets.height = 720u; + targets.colorTexture = reinterpret_cast<::XCEngine::RHI::RHITexture*>(1); + targets.colorView = reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(2); + targets.depthTexture = reinterpret_cast<::XCEngine::RHI::RHITexture*>(3); + targets.depthView = reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(4); + targets.textureHandle = { + 5u, + 1280u, + 720u, + ::XCEngine::UI::UITextureHandleKind::ShaderResourceView, + 6u + }; + + ViewportResourceRequirements requirements = {}; + EXPECT_TRUE(CanReuseViewportResources( + BuildViewportRenderTargetsReuseQuery(requirements, targets, 1280u, 720u))); + + requirements.requiresDepthSampling = true; + EXPECT_FALSE(CanReuseViewportResources( + BuildViewportRenderTargetsReuseQuery(requirements, targets, 1280u, 720u))); + + targets.depthShaderView = reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(7); + EXPECT_TRUE(CanReuseViewportResources( + BuildViewportRenderTargetsReuseQuery(requirements, targets, 1280u, 720u))); + + requirements.requiresObjectIdSurface = true; + EXPECT_FALSE(CanReuseViewportResources( + BuildViewportRenderTargetsReuseQuery(requirements, targets, 1280u, 720u))); + + targets.objectIdTexture = reinterpret_cast<::XCEngine::RHI::RHITexture*>(8); + targets.objectIdDepthTexture = reinterpret_cast<::XCEngine::RHI::RHITexture*>(9); + targets.objectIdDepthView = reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(10); + targets.objectIdView = reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(11); + targets.objectIdShaderView = reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(12); + EXPECT_TRUE(CanReuseViewportResources( + BuildViewportRenderTargetsReuseQuery(requirements, targets, 1280u, 720u))); + + requirements.requiresSelectionMaskSurface = true; + EXPECT_FALSE(CanReuseViewportResources( + BuildViewportRenderTargetsReuseQuery(requirements, targets, 1280u, 720u))); + + targets.selectionMaskTexture = reinterpret_cast<::XCEngine::RHI::RHITexture*>(13); + targets.selectionMaskView = reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(14); + targets.selectionMaskShaderView = reinterpret_cast<::XCEngine::RHI::RHIResourceView*>(15); + EXPECT_TRUE(CanReuseViewportResources( + BuildViewportRenderTargetsReuseQuery(requirements, targets, 1280u, 720u))); +} + +TEST(SceneViewportRuntimeTests, ViewportHostServiceAcceptsArbitraryViewportIds) { + ViewportHostService hostService = {}; + hostService.BeginFrame(); + + const ViewportFrame frame = + hostService.RequestViewport("preview_inspector", UISize(320.0f, 200.0f)); + + EXPECT_TRUE(frame.wasRequested); + EXPECT_FALSE(frame.hasTexture); + EXPECT_FLOAT_EQ(frame.requestedSize.width, 320.0f); + EXPECT_FLOAT_EQ(frame.requestedSize.height, 200.0f); + EXPECT_FLOAT_EQ(frame.renderSize.width, 0.0f); + EXPECT_FLOAT_EQ(frame.renderSize.height, 0.0f); +} + } // namespace } // namespace XCEngine::UI::Editor::App diff --git a/tests/UI/Editor/unit/test_ui_editor_panel_input_filter.cpp b/tests/UI/Editor/unit/test_ui_editor_panel_input_filter.cpp index a47c2205..f1aad2c0 100644 --- a/tests/UI/Editor/unit/test_ui_editor_panel_input_filter.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_panel_input_filter.cpp @@ -5,6 +5,7 @@ namespace { using XCEngine::UI::Editor::FilterUIEditorPanelInputEvents; +using XCEngine::UI::Editor::BuildUIEditorPanelInputEvents; using XCEngine::UI::Editor::UIEditorPanelInputFilterOptions; using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; @@ -93,3 +94,28 @@ TEST(UIEditorPanelInputFilterTests, CapturedPointerEventsBypassBoundsWhenEnabled EXPECT_EQ(filtered[0].type, UIInputEventType::PointerMove); EXPECT_EQ(filtered[1].type, UIInputEventType::PointerButtonDown); } + +TEST(UIEditorPanelInputFilterTests, SyntheticFocusTransitionsArePrependedToFilteredEvents) { + const std::vector filtered = + BuildUIEditorPanelInputEvents( + UIRect(0.0f, 0.0f, 100.0f, 100.0f), + { + MakePointerMove(20.0f, 20.0f), + MakeKeyDown() + }, + UIEditorPanelInputFilterOptions{ + .allowPointerInBounds = true, + .allowPointerWhileCaptured = false, + .allowKeyboardInput = true, + .allowFocusEvents = false, + .includePointerLeave = false + }, + true, + true); + + ASSERT_EQ(filtered.size(), 4u); + EXPECT_EQ(filtered[0].type, UIInputEventType::FocusLost); + EXPECT_EQ(filtered[1].type, UIInputEventType::FocusGained); + EXPECT_EQ(filtered[2].type, UIInputEventType::PointerMove); + EXPECT_EQ(filtered[3].type, UIInputEventType::KeyDown); +} diff --git a/tests/UI/Editor/unit/test_ui_editor_tree_panel_behavior.cpp b/tests/UI/Editor/unit/test_ui_editor_tree_panel_behavior.cpp index c9dcd709..1cec3d1b 100644 --- a/tests/UI/Editor/unit/test_ui_editor_tree_panel_behavior.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_tree_panel_behavior.cpp @@ -12,8 +12,8 @@ namespace TreeDrag = XCEngine::UI::Editor::Collections::TreeDragDrop; namespace Widgets = XCEngine::UI::Editor::Widgets; using XCEngine::UI::Editor::BuildUIEditorTreePanelInlineRenameBounds; +using XCEngine::UI::Editor::BuildUIEditorTreePanelInputEvents; using XCEngine::UI::Editor::BuildUIEditorTreePanelInteractionInputEvents; -using XCEngine::UI::Editor::FilterUIEditorTreePanelInputEvents; using XCEngine::UI::Editor::FilterUIEditorTreePanelPointerInputEvents; using XCEngine::UI::Editor::FindUIEditorTreePanelVisibleItemIndex; using XCEngine::UI::Editor::UIEditorTreePanelInputFilterOptions; @@ -80,9 +80,9 @@ TreeFixture BuildTreeFixture() { } // namespace -TEST(UIEditorTreePanelBehaviorTests, FilterPanelInputHonorsBoundsAndPanelActivity) { +TEST(UIEditorTreePanelBehaviorTests, FilterPanelInputHonorsBoundsAndInputFocus) { const std::vector filtered = - FilterUIEditorTreePanelInputEvents( + BuildUIEditorTreePanelInputEvents( UIRect(0.0f, 0.0f, 100.0f, 100.0f), { MakePointerMove(40.0f, 40.0f), @@ -92,7 +92,7 @@ TEST(UIEditorTreePanelBehaviorTests, FilterPanelInputHonorsBoundsAndPanelActivit }, UIEditorTreePanelInputFilterOptions{ .allowInteraction = true, - .panelActive = true, + .hasInputFocus = true, .captureActive = false }); diff --git a/tests/UI/Editor/unit/test_ui_editor_viewport_input_bridge.cpp b/tests/UI/Editor/unit/test_ui_editor_viewport_input_bridge.cpp index 3c519a2b..0f750d3f 100644 --- a/tests/UI/Editor/unit/test_ui_editor_viewport_input_bridge.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_viewport_input_bridge.cpp @@ -12,6 +12,8 @@ using XCEngine::UI::UIPointerButton; using XCEngine::UI::UIRect; using XCEngine::UI::Editor::IsUIEditorViewportInputBridgeKeyDown; using XCEngine::UI::Editor::IsUIEditorViewportInputBridgePointerButtonDown; +using XCEngine::UI::Editor::UIEditorViewportInputBridgeFocusMode; +using XCEngine::UI::Editor::UIEditorViewportInputBridgeRequest; using XCEngine::UI::Editor::UIEditorViewportInputBridgeState; using XCEngine::UI::Editor::UpdateUIEditorViewportInputBridge; @@ -385,4 +387,40 @@ TEST(UIEditorViewportInputBridgeTest, FocusLostClearsCapturedStateAndHeldKeys) { EXPECT_FALSE(IsUIEditorViewportInputBridgePointerButtonDown(state, UIPointerButton::Left)); } +TEST(UIEditorViewportInputBridgeTest, ExternalFocusModeTransfersFocusAndReleasesCaptureFromOwnerState) { + UIEditorViewportInputBridgeState state = {}; + UIEditorViewportInputBridgeRequest focusedRequest = {}; + focusedRequest.focusMode = UIEditorViewportInputBridgeFocusMode::External; + focusedRequest.focused = true; + + auto frame = UpdateUIEditorViewportInputBridge( + state, + UIRect(100.0f, 200.0f, 640.0f, 360.0f), + { + MakePointerEvent(UIInputEventType::PointerButtonDown, 220.0f, 280.0f, UIPointerButton::Left) + }, + {}, + focusedRequest); + + EXPECT_TRUE(frame.focusGained); + EXPECT_TRUE(frame.focused); + EXPECT_TRUE(frame.captured); + + UIEditorViewportInputBridgeRequest unfocusedRequest = focusedRequest; + unfocusedRequest.focused = false; + frame = UpdateUIEditorViewportInputBridge( + state, + UIRect(100.0f, 200.0f, 640.0f, 360.0f), + {}, + {}, + unfocusedRequest); + + EXPECT_TRUE(frame.focusLost); + EXPECT_TRUE(frame.captureEnded); + EXPECT_FALSE(frame.focused); + EXPECT_FALSE(frame.captured); + EXPECT_FALSE(state.focused); + EXPECT_FALSE(state.captured); +} + } // namespace diff --git a/tests/UI/Editor/unit/test_ui_editor_workspace_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_workspace_interaction.cpp index 80cf3e3f..5f44b462 100644 --- a/tests/UI/Editor/unit/test_ui_editor_workspace_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_workspace_interaction.cpp @@ -1,6 +1,7 @@ #include #include +#include #include namespace { @@ -14,7 +15,10 @@ using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceController; using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel; using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit; using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack; +using XCEngine::UI::Editor::FindUIEditorPanelContentHostPanelState; using XCEngine::UI::Editor::FindUIEditorWorkspaceViewportPresentationFrame; +using XCEngine::UI::Editor::IsUIEditorWorkspaceHostedPanelInputOwner; +using XCEngine::UI::Editor::IsUIEditorWorkspaceViewportInputOwner; using XCEngine::UI::Editor::UIEditorPanelPresentationKind; using XCEngine::UI::Editor::UIEditorPanelRegistry; using XCEngine::UI::Editor::UIEditorViewportShellModel; @@ -33,7 +37,7 @@ UIEditorPanelRegistry BuildPanelRegistry() { registry.panels = { { "viewport", "Viewport", UIEditorPanelPresentationKind::ViewportShell, false, true, true }, { "doc", "Document", UIEditorPanelPresentationKind::Placeholder, true, true, true }, - { "details", "Details", UIEditorPanelPresentationKind::Placeholder, true, true, true } + { "details", "Details", UIEditorPanelPresentationKind::HostedContent, true, true, true } }; return registry; } @@ -138,12 +142,72 @@ TEST(UIEditorWorkspaceInteractionTest, PointerDownInsideViewportBubblesPointerCa EXPECT_EQ(frame.result.viewportPanelId, "viewport"); EXPECT_TRUE(frame.result.viewportInputFrame.captureStarted); EXPECT_TRUE(frame.result.viewportInputFrame.focused); + EXPECT_TRUE(frame.inputOwnerChanged); + EXPECT_TRUE(IsUIEditorWorkspaceViewportInputOwner(frame.inputOwner, "viewport")); viewportFrame = FindUIEditorWorkspaceViewportPresentationFrame(frame.composeFrame, "viewport"); ASSERT_NE(viewportFrame, nullptr); EXPECT_TRUE(viewportFrame->viewportShellFrame.inputFrame.captured); } +TEST(UIEditorWorkspaceInteractionTest, HostedPanelPointerDownTransfersOwnerAndClearsViewportFocus) { + auto controller = + BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); + UIEditorWorkspaceInteractionState state = {}; + const UIEditorWorkspaceInteractionModel model = BuildInteractionModel(); + + auto frame = UpdateUIEditorWorkspaceInteraction( + state, + controller, + UIRect(0.0f, 0.0f, 1280.0f, 720.0f), + model, + {}); + const auto* viewportFrame = + FindUIEditorWorkspaceViewportPresentationFrame(frame.composeFrame, "viewport"); + ASSERT_NE(viewportFrame, nullptr); + const UIPoint viewportCenter = RectCenter(viewportFrame->viewportShellFrame.slotLayout.inputRect); + + frame = UpdateUIEditorWorkspaceInteraction( + state, + controller, + UIRect(0.0f, 0.0f, 1280.0f, 720.0f), + model, + { + MakePointerEvent(UIInputEventType::PointerMove, viewportCenter.x, viewportCenter.y), + MakePointerEvent( + UIInputEventType::PointerButtonDown, + viewportCenter.x, + viewportCenter.y, + UIPointerButton::Left) + }); + ASSERT_TRUE(IsUIEditorWorkspaceViewportInputOwner(frame.inputOwner, "viewport")); + + const auto* detailsPanel = FindUIEditorPanelContentHostPanelState( + frame.composeFrame.contentHostFrame, + "details"); + ASSERT_NE(detailsPanel, nullptr); + const UIPoint detailsCenter = RectCenter(detailsPanel->bounds); + + frame = UpdateUIEditorWorkspaceInteraction( + state, + controller, + UIRect(0.0f, 0.0f, 1280.0f, 720.0f), + model, + { + MakePointerEvent( + UIInputEventType::PointerButtonDown, + detailsCenter.x, + detailsCenter.y, + UIPointerButton::Left) + }); + + EXPECT_TRUE(frame.inputOwnerChanged); + EXPECT_TRUE(IsUIEditorWorkspaceHostedPanelInputOwner(frame.inputOwner, "details")); + EXPECT_EQ(frame.result.viewportPanelId, "viewport"); + EXPECT_TRUE(frame.result.viewportInputFrame.focusLost); + EXPECT_TRUE(frame.result.viewportInputFrame.captureEnded); +} + TEST(UIEditorWorkspaceInteractionTest, PointerUpInsideViewportBubblesPointerReleaseRequest) { auto controller = BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); diff --git a/tests/UI/TEST_SPEC.md b/tests/UI/TEST_SPEC.md index dee0aa56..58305939 100644 --- a/tests/UI/TEST_SPEC.md +++ b/tests/UI/TEST_SPEC.md @@ -24,7 +24,7 @@ tests/UI/ TEST_SPEC.md Core/ unit/ - integration/ + manual_validation/ Runtime/ unit/ integration/ @@ -127,7 +127,7 @@ Runtime 的集成测试结构与 Core 保持同一规范,但宿主职责必须 ### 5.3 Editor ```text -tests/UI/Editor/integration/ +tests/UI/Editor/manual_validation/ / / CMakeLists.txt @@ -207,4 +207,4 @@ XCUI 必须坚持自底向上的建设顺序: 当前 XCUI 的正式验证入口是 `tests/UI`,其下的 `Core / Runtime / Editor` 三层测试树是唯一有效的当前验证体系。 `new_editor` 只作为未来重建 editor 的产品宿主,不是当前测试入口,也不继续承载新的测试场景扩展。 `tests/UI/TEST_SPEC.md` 负责顶层测试分层、职责边界和执行规则。 -`tests/UI/Editor/integration/README.md` 负责 Editor 集成验证的当前入口说明、场景清单、构建运行和截图约定。 +`tests/UI/Editor/manual_validation/README.md` 负责 Editor 人工验证场景的当前入口说明、场景清单、构建运行和截图约定。