From 02a0e626fe9ddc1f31d389af578ffb9457f6e793 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Fri, 10 Apr 2026 00:41:28 +0800 Subject: [PATCH] Refactor XCUI editor module layout --- .../CameraRenderer/CameraRenderer.md | 109 -- .../Rendering/CameraRenderer/Constructor.md | 73 - .../Rendering/CameraRenderer/Destructor.md | 47 - .../CameraRenderer/GetDepthOnlyPass.md | 33 - .../CameraRenderer/GetObjectIdPass.md | 32 - .../Rendering/CameraRenderer/GetPipeline.md | 38 - .../CameraRenderer/GetPipelineAsset.md | 33 - .../CameraRenderer/GetShadowCasterPass.md | 33 - .../Rendering/CameraRenderer/Render.md | 83 - .../CameraRenderer/SetDepthOnlyPass.md | 36 - .../CameraRenderer/SetObjectIdPass.md | 35 - .../Rendering/CameraRenderer/SetPipeline.md | 44 - .../CameraRenderer/SetPipelineAsset.md | 36 - .../CameraRenderer/SetShadowCasterPass.md | 36 - .../SceneRenderer/BuildRenderRequests.md | 45 - .../Rendering/SceneRenderer/Constructor.md | 33 - .../Rendering/SceneRenderer/Destructor.md | 30 - .../Rendering/SceneRenderer/GetPipeline.md | 35 - .../SceneRenderer/GetPipelineAsset.md | 33 - .../Rendering/SceneRenderer/Render.md | 63 - .../Rendering/SceneRenderer/SceneRenderer.md | 119 -- .../Rendering/SceneRenderer/SetPipeline.md | 32 - .../SceneRenderer/SetPipelineAsset.md | 34 - new_editor/CMakeLists.txt | 182 +- new_editor/app/Application.cpp | 35 +- new_editor/app/Application.h | 9 +- new_editor/app/Host/AutoScreenshot.cpp | 38 +- .../Collections/UIEditorInlineRenameSession.h | 54 + .../UIEditorListView.h | 0 .../UIEditorListViewInteraction.h | 5 +- .../UIEditorScrollView.h | 16 +- .../UIEditorScrollViewInteraction.h | 2 +- .../UIEditorTabStrip.h | 36 +- .../Collections/UIEditorTabStripInteraction.h | 47 + .../UIEditorTreeView.h | 0 .../UIEditorTreeViewInteraction.h | 9 +- .../include/XCEditor/Core/UIEditorTheme.h | 157 -- .../XCEditor/Fields/UIEditorAssetField.h | 188 ++ .../Fields/UIEditorAssetFieldInteraction.h | 44 + .../{Widgets => Fields}/UIEditorBoolField.h | 0 .../UIEditorBoolFieldInteraction.h | 2 +- .../XCEditor/Fields/UIEditorColorField.h | 220 +++ .../Fields/UIEditorColorFieldInteraction.h | 41 + .../{Widgets => Fields}/UIEditorEnumField.h | 0 .../UIEditorEnumFieldInteraction.h | 4 +- .../XCEditor/Fields/UIEditorFieldStyle.h | 101 ++ .../{Widgets => Fields}/UIEditorNumberField.h | 0 .../UIEditorNumberFieldInteraction.h | 2 +- .../XCEditor/Fields/UIEditorObjectField.h | 153 ++ .../Fields/UIEditorObjectFieldInteraction.h | 37 + .../UIEditorPropertyGrid.h | 16 +- .../UIEditorPropertyGridInteraction.h | 2 +- .../{Widgets => Fields}/UIEditorTextField.h | 0 .../UIEditorTextFieldInteraction.h | 2 +- .../UIEditorVector2Field.h | 0 .../UIEditorVector2FieldInteraction.h | 2 +- .../UIEditorVector3Field.h | 0 .../UIEditorVector3FieldInteraction.h | 2 +- .../UIEditorVector4Field.h | 0 .../UIEditorVector4FieldInteraction.h | 2 +- .../UIEditorCommandDispatcher.h | 2 +- .../UIEditorCommandRegistry.h | 2 +- .../UIEditorShortcutManager.h | 2 +- .../XCEditor/Foundation/UIEditorTheme.h | 76 + .../{Widgets => Shell}/UIEditorDockHost.h | 28 +- .../UIEditorDockHostInteraction.h | 11 +- .../{Widgets => Shell}/UIEditorMenuBar.h | 22 +- .../{Core => Shell}/UIEditorMenuModel.h | 2 +- .../{Widgets => Shell}/UIEditorMenuPopup.h | 2 +- .../{Core => Shell}/UIEditorMenuSession.h | 22 +- .../UIEditorPanelContentHost.h | 4 +- .../{Widgets => Shell}/UIEditorPanelFrame.h | 32 +- .../UIEditorPanelHostLifecycle.h | 4 +- .../{Core => Shell}/UIEditorPanelRegistry.h | 0 .../{Core => Shell}/UIEditorShellCompose.h | 8 +- .../UIEditorShellInteraction.h | 12 +- .../{Widgets => Shell}/UIEditorStatusBar.h | 24 +- .../UIEditorViewportInputBridge.h | 0 .../{Core => Shell}/UIEditorViewportShell.h | 4 +- .../{Widgets => Shell}/UIEditorViewportSlot.h | 2 +- .../UIEditorWorkspaceCompose.h | 20 +- .../UIEditorWorkspaceController.h | 4 +- .../UIEditorWorkspaceInteraction.h | 7 +- .../UIEditorWorkspaceLayoutPersistence.h | 2 +- .../{Core => Shell}/UIEditorWorkspaceModel.h | 0 .../UIEditorWorkspaceSession.h | 4 +- .../Widgets/UIEditorCollectionPrimitives.h | 9 +- .../XCEditor/Widgets/UIEditorColorUtils.h | 178 ++ .../XCEditor/Widgets/UIEditorFieldRowLayout.h | 97 + .../XCEditor/Widgets/UIEditorPanelChrome.h | 132 -- .../UIEditorInlineRenameSession.cpp | 114 ++ .../UIEditorListView.cpp | 2 +- .../UIEditorListViewInteraction.cpp | 242 ++- .../UIEditorScrollView.cpp | 47 +- .../UIEditorScrollViewInteraction.cpp | 2 +- .../UIEditorTabStrip.cpp | 35 +- .../UIEditorTabStripInteraction.cpp | 329 ++++ .../UIEditorTreeView.cpp | 2 +- .../UIEditorTreeViewInteraction.cpp | 677 +++++++ .../src/Core/UIEditorDockHostInteraction.cpp | 304 ---- new_editor/src/Core/UIEditorTheme.cpp | 1615 ----------------- .../src/Core/UIEditorTreeViewInteraction.cpp | 192 -- new_editor/src/Fields/UIEditorAssetField.cpp | 394 ++++ .../Fields/UIEditorAssetFieldInteraction.cpp | 260 +++ .../{Widgets => Fields}/UIEditorBoolField.cpp | 2 +- .../UIEditorBoolFieldInteraction.cpp | 2 +- new_editor/src/Fields/UIEditorColorField.cpp | 873 +++++++++ .../Fields/UIEditorColorFieldInteraction.cpp | 453 +++++ new_editor/src/Fields/UIEditorEnumField.cpp | 250 +++ .../UIEditorEnumFieldInteraction.cpp | 2 +- new_editor/src/Fields/UIEditorFieldStyle.cpp | 477 +++++ .../UIEditorNumberField.cpp | 138 +- .../UIEditorNumberFieldInteraction.cpp | 2 +- new_editor/src/Fields/UIEditorObjectField.cpp | 319 ++++ .../Fields/UIEditorObjectFieldInteraction.cpp | 174 ++ .../UIEditorPropertyGrid.cpp | 139 +- .../UIEditorPropertyGridInteraction.cpp | 291 ++- new_editor/src/Fields/UIEditorTextField.cpp | 227 +++ .../UIEditorTextFieldInteraction.cpp | 2 +- .../UIEditorVector2Field.cpp | 198 +- .../UIEditorVector2FieldInteraction.cpp | 2 +- .../UIEditorVector3Field.cpp | 201 +- .../UIEditorVector3FieldInteraction.cpp | 2 +- .../UIEditorVector4Field.cpp | 204 ++- .../UIEditorVector4FieldInteraction.cpp | 2 +- .../UIEditorCommandDispatcher.cpp | 2 +- .../UIEditorCommandRegistry.cpp | 2 +- .../UIEditorShortcutManager.cpp | 2 +- new_editor/src/Foundation/UIEditorTheme.cpp | 69 + .../src/{Core => Shell}/EditorShellAsset.cpp | 2 - .../src/{Core => Shell}/EditorShellAsset.h | 11 +- .../{Widgets => Shell}/UIEditorDockHost.cpp | 50 +- .../src/Shell/UIEditorDockHostInteraction.cpp | 620 +++++++ .../{Widgets => Shell}/UIEditorMenuBar.cpp | 17 +- .../src/{Core => Shell}/UIEditorMenuModel.cpp | 4 +- .../{Widgets => Shell}/UIEditorMenuPopup.cpp | 2 +- .../{Core => Shell}/UIEditorMenuSession.cpp | 35 +- .../UIEditorPanelContentHost.cpp | 2 +- .../{Widgets => Shell}/UIEditorPanelFrame.cpp | 27 +- .../UIEditorPanelHostLifecycle.cpp | 2 +- .../{Core => Shell}/UIEditorPanelRegistry.cpp | 2 +- .../{Core => Shell}/UIEditorShellCompose.cpp | 12 +- .../UIEditorShellInteraction.cpp | 2 +- .../{Widgets => Shell}/UIEditorStatusBar.cpp | 12 +- .../UIEditorViewportInputBridge.cpp | 2 +- .../{Core => Shell}/UIEditorViewportShell.cpp | 2 +- .../UIEditorViewportSlot.cpp | 2 +- .../UIEditorWorkspaceCompose.cpp | 26 +- .../UIEditorWorkspaceController.cpp | 2 +- .../UIEditorWorkspaceInteraction.cpp | 8 +- .../UIEditorWorkspaceLayoutPersistence.cpp | 2 +- .../UIEditorWorkspaceModel.cpp | 4 +- .../UIEditorWorkspaceSession.cpp | 2 +- .../Widgets/UIEditorCollectionPrimitives.cpp | 37 +- new_editor/src/Widgets/UIEditorEnumField.cpp | 166 -- .../src/Widgets/UIEditorFieldRowLayout.cpp | 23 + new_editor/src/Widgets/UIEditorTextField.cpp | 165 -- new_editor/ui/themes/editor_shell.xctheme | 13 - new_editor/ui/views/editor_shell.xcui | 4 +- tests/UI/Editor/integration/CMakeLists.txt | 35 + tests/UI/Editor/integration/README.md | 294 +-- .../Editor/integration/shared/CMakeLists.txt | 2 + .../integration/shared/src/Application.cpp | 62 +- .../shared/src/EditorValidationScenario.cpp | 1 - .../shared/src/EditorValidationScenario.h | 1 - .../shared/src/EditorValidationTheme.h | 190 +- .../shared/themes/editor_validation.xctheme | 177 -- .../Editor/integration/shell/CMakeLists.txt | 6 + .../shell/asset_field_basic/CMakeLists.txt | 31 + .../shell/asset_field_basic/captures/.gitkeep | 1 + .../shell/asset_field_basic/main.cpp | 736 ++++++++ .../shell/bool_field_basic/main.cpp | 54 +- .../shell/color_field_basic/main.cpp | 93 +- .../shell/context_menu_basic/main.cpp | 50 +- .../shell/dock_host_basic/main.cpp | 36 +- .../shell/editor_shell_compose/main.cpp | 157 +- .../shell/editor_shell_interaction/main.cpp | 185 +- .../shell/enum_field_basic/main.cpp | 72 +- .../shell/list_view_basic/main.cpp | 16 +- .../shell/list_view_inline_rename/main.cpp | 71 +- .../shell/list_view_multiselect/main.cpp | 49 +- .../integration/shell/menu_bar_basic/main.cpp | 78 +- .../shell/number_field_basic/main.cpp | 74 +- .../shell/object_field_basic/CMakeLists.txt | 31 + .../object_field_basic/captures/.gitkeep | 1 + .../shell/object_field_basic/main.cpp | 736 ++++++++ .../shell/panel_content_host_basic/main.cpp | 36 +- .../shell/panel_frame_basic/main.cpp | 21 +- .../shell/property_grid_basic/main.cpp | 48 +- .../shell/scroll_view_basic/main.cpp | 34 +- .../shell/status_bar_basic/main.cpp | 45 +- .../shell/tab_strip_basic/main.cpp | 38 +- .../shell/text_field_basic/main.cpp | 68 +- .../shell/tree_view_basic/main.cpp | 6 +- .../shell/tree_view_inline_rename/main.cpp | 52 +- .../shell/tree_view_multiselect/main.cpp | 8 +- .../shell/vector2_field_basic/main.cpp | 79 +- .../shell/vector3_field_basic/main.cpp | 79 +- .../shell/vector4_field_basic/main.cpp | 63 +- .../shell/viewport_shell_basic/main.cpp | 38 +- .../shell/viewport_slot_basic/main.cpp | 42 +- .../workspace_interaction_basic/main.cpp | 545 +++++- .../shell/workspace_shell_compose/View.xcui | 3 +- .../shell/workspace_shell_compose/main.cpp | 34 +- .../shell/workspace_viewport_compose/main.cpp | 1007 ++++------ .../state/layout_persistence/main.cpp | 12 +- .../state/panel_host_lifecycle/main.cpp | 19 +- .../state/panel_session_flow/main.cpp | 6 +- .../state/shortcut_dispatch/main.cpp | 4 +- .../viewport_input_bridge_basic/main.cpp | 74 +- tests/UI/Editor/unit/CMakeLists.txt | 17 +- .../test_editor_shell_asset_validation.cpp | 5 +- .../unit/test_structured_editor_shell.cpp | 74 +- .../unit/test_ui_editor_asset_field.cpp | 123 ++ ...test_ui_editor_asset_field_interaction.cpp | 189 ++ .../Editor/unit/test_ui_editor_bool_field.cpp | 2 +- .../test_ui_editor_bool_field_interaction.cpp | 2 +- .../test_ui_editor_collection_primitives.cpp | 45 +- .../unit/test_ui_editor_color_field.cpp | 2 +- ...test_ui_editor_color_field_interaction.cpp | 2 +- .../Editor/unit/test_ui_editor_dock_host.cpp | 8 +- .../test_ui_editor_dock_host_interaction.cpp | 6 +- .../Editor/unit/test_ui_editor_enum_field.cpp | 68 +- .../test_ui_editor_enum_field_interaction.cpp | 2 +- .../test_ui_editor_hosted_field_builders.cpp | 156 ++ .../Editor/unit/test_ui_editor_menu_bar.cpp | 2 +- .../Editor/unit/test_ui_editor_menu_model.cpp | 4 +- .../Editor/unit/test_ui_editor_menu_popup.cpp | 2 +- .../unit/test_ui_editor_menu_session.cpp | 2 +- .../unit/test_ui_editor_number_field.cpp | 2 +- ...est_ui_editor_number_field_interaction.cpp | 2 +- .../unit/test_ui_editor_object_field.cpp | 122 ++ ...est_ui_editor_object_field_interaction.cpp | 157 ++ .../unit/test_ui_editor_panel_chrome.cpp | 126 -- .../test_ui_editor_panel_content_host.cpp | 6 +- .../unit/test_ui_editor_panel_frame.cpp | 2 +- .../test_ui_editor_panel_host_lifecycle.cpp | 2 +- .../unit/test_ui_editor_panel_registry.cpp | 2 +- .../unit/test_ui_editor_property_grid.cpp | 22 +- ...st_ui_editor_property_grid_interaction.cpp | 12 +- .../unit/test_ui_editor_scroll_view.cpp | 2 +- ...test_ui_editor_scroll_view_interaction.cpp | 2 +- .../unit/test_ui_editor_shell_compose.cpp | 6 +- .../unit/test_ui_editor_shell_interaction.cpp | 6 +- .../Editor/unit/test_ui_editor_status_bar.cpp | 2 +- .../Editor/unit/test_ui_editor_text_field.cpp | 2 +- .../test_ui_editor_text_field_interaction.cpp | 2 +- tests/UI/Editor/unit/test_ui_editor_theme.cpp | 444 ----- .../unit/test_ui_editor_vector2_field.cpp | 2 +- ...st_ui_editor_vector2_field_interaction.cpp | 2 +- .../unit/test_ui_editor_vector3_field.cpp | 2 +- ...st_ui_editor_vector3_field_interaction.cpp | 2 +- .../unit/test_ui_editor_vector4_field.cpp | 2 +- ...st_ui_editor_vector4_field_interaction.cpp | 2 +- .../test_ui_editor_viewport_input_bridge.cpp | 2 +- .../unit/test_ui_editor_viewport_shell.cpp | 2 +- .../unit/test_ui_editor_viewport_slot.cpp | 2 +- .../unit/test_ui_editor_workspace_compose.cpp | 2 +- .../test_ui_editor_workspace_controller.cpp | 2 +- .../test_ui_editor_workspace_interaction.cpp | 2 +- ...ui_editor_workspace_layout_persistence.cpp | 4 +- .../unit/test_ui_editor_workspace_model.cpp | 2 +- .../unit/test_ui_editor_workspace_session.cpp | 6 +- 263 files changed, 12396 insertions(+), 7592 deletions(-) delete mode 100644 docs/api/XCEngine/Rendering/CameraRenderer/CameraRenderer.md delete mode 100644 docs/api/XCEngine/Rendering/CameraRenderer/Constructor.md delete mode 100644 docs/api/XCEngine/Rendering/CameraRenderer/Destructor.md delete mode 100644 docs/api/XCEngine/Rendering/CameraRenderer/GetDepthOnlyPass.md delete mode 100644 docs/api/XCEngine/Rendering/CameraRenderer/GetObjectIdPass.md delete mode 100644 docs/api/XCEngine/Rendering/CameraRenderer/GetPipeline.md delete mode 100644 docs/api/XCEngine/Rendering/CameraRenderer/GetPipelineAsset.md delete mode 100644 docs/api/XCEngine/Rendering/CameraRenderer/GetShadowCasterPass.md delete mode 100644 docs/api/XCEngine/Rendering/CameraRenderer/Render.md delete mode 100644 docs/api/XCEngine/Rendering/CameraRenderer/SetDepthOnlyPass.md delete mode 100644 docs/api/XCEngine/Rendering/CameraRenderer/SetObjectIdPass.md delete mode 100644 docs/api/XCEngine/Rendering/CameraRenderer/SetPipeline.md delete mode 100644 docs/api/XCEngine/Rendering/CameraRenderer/SetPipelineAsset.md delete mode 100644 docs/api/XCEngine/Rendering/CameraRenderer/SetShadowCasterPass.md delete mode 100644 docs/api/XCEngine/Rendering/SceneRenderer/BuildRenderRequests.md delete mode 100644 docs/api/XCEngine/Rendering/SceneRenderer/Constructor.md delete mode 100644 docs/api/XCEngine/Rendering/SceneRenderer/Destructor.md delete mode 100644 docs/api/XCEngine/Rendering/SceneRenderer/GetPipeline.md delete mode 100644 docs/api/XCEngine/Rendering/SceneRenderer/GetPipelineAsset.md delete mode 100644 docs/api/XCEngine/Rendering/SceneRenderer/Render.md delete mode 100644 docs/api/XCEngine/Rendering/SceneRenderer/SceneRenderer.md delete mode 100644 docs/api/XCEngine/Rendering/SceneRenderer/SetPipeline.md delete mode 100644 docs/api/XCEngine/Rendering/SceneRenderer/SetPipelineAsset.md create mode 100644 new_editor/include/XCEditor/Collections/UIEditorInlineRenameSession.h rename new_editor/include/XCEditor/{Widgets => Collections}/UIEditorListView.h (100%) rename new_editor/include/XCEditor/{Core => Collections}/UIEditorListViewInteraction.h (89%) rename new_editor/include/XCEditor/{Widgets => Collections}/UIEditorScrollView.h (85%) rename new_editor/include/XCEditor/{Core => Collections}/UIEditorScrollViewInteraction.h (95%) rename new_editor/include/XCEditor/{Widgets => Collections}/UIEditorTabStrip.h (81%) create mode 100644 new_editor/include/XCEditor/Collections/UIEditorTabStripInteraction.h rename new_editor/include/XCEditor/{Widgets => Collections}/UIEditorTreeView.h (100%) rename new_editor/include/XCEditor/{Core => Collections}/UIEditorTreeViewInteraction.h (76%) delete mode 100644 new_editor/include/XCEditor/Core/UIEditorTheme.h create mode 100644 new_editor/include/XCEditor/Fields/UIEditorAssetField.h create mode 100644 new_editor/include/XCEditor/Fields/UIEditorAssetFieldInteraction.h rename new_editor/include/XCEditor/{Widgets => Fields}/UIEditorBoolField.h (100%) rename new_editor/include/XCEditor/{Core => Fields}/UIEditorBoolFieldInteraction.h (95%) create mode 100644 new_editor/include/XCEditor/Fields/UIEditorColorField.h create mode 100644 new_editor/include/XCEditor/Fields/UIEditorColorFieldInteraction.h rename new_editor/include/XCEditor/{Widgets => Fields}/UIEditorEnumField.h (100%) rename new_editor/include/XCEditor/{Core => Fields}/UIEditorEnumFieldInteraction.h (94%) create mode 100644 new_editor/include/XCEditor/Fields/UIEditorFieldStyle.h rename new_editor/include/XCEditor/{Widgets => Fields}/UIEditorNumberField.h (100%) rename new_editor/include/XCEditor/{Core => Fields}/UIEditorNumberFieldInteraction.h (96%) create mode 100644 new_editor/include/XCEditor/Fields/UIEditorObjectField.h create mode 100644 new_editor/include/XCEditor/Fields/UIEditorObjectFieldInteraction.h rename new_editor/include/XCEditor/{Widgets => Fields}/UIEditorPropertyGrid.h (95%) rename new_editor/include/XCEditor/{Core => Fields}/UIEditorPropertyGridInteraction.h (97%) rename new_editor/include/XCEditor/{Widgets => Fields}/UIEditorTextField.h (100%) rename new_editor/include/XCEditor/{Core => Fields}/UIEditorTextFieldInteraction.h (96%) rename new_editor/include/XCEditor/{Widgets => Fields}/UIEditorVector2Field.h (100%) rename new_editor/include/XCEditor/{Core => Fields}/UIEditorVector2FieldInteraction.h (97%) rename new_editor/include/XCEditor/{Widgets => Fields}/UIEditorVector3Field.h (100%) rename new_editor/include/XCEditor/{Core => Fields}/UIEditorVector3FieldInteraction.h (97%) rename new_editor/include/XCEditor/{Widgets => Fields}/UIEditorVector4Field.h (100%) rename new_editor/include/XCEditor/{Core => Fields}/UIEditorVector4FieldInteraction.h (97%) rename new_editor/include/XCEditor/{Core => Foundation}/UIEditorCommandDispatcher.h (97%) rename new_editor/include/XCEditor/{Core => Foundation}/UIEditorCommandRegistry.h (96%) rename new_editor/include/XCEditor/{Core => Foundation}/UIEditorShortcutManager.h (98%) create mode 100644 new_editor/include/XCEditor/Foundation/UIEditorTheme.h rename new_editor/include/XCEditor/{Widgets => Shell}/UIEditorDockHost.h (87%) rename new_editor/include/XCEditor/{Core => Shell}/UIEditorDockHostInteraction.h (78%) rename new_editor/include/XCEditor/{Widgets => Shell}/UIEditorMenuBar.h (83%) rename new_editor/include/XCEditor/{Core => Shell}/UIEditorMenuModel.h (98%) rename new_editor/include/XCEditor/{Widgets => Shell}/UIEditorMenuPopup.h (99%) rename new_editor/include/XCEditor/{Core => Shell}/UIEditorMenuSession.h (76%) rename new_editor/include/XCEditor/{Core => Shell}/UIEditorPanelContentHost.h (96%) rename new_editor/include/XCEditor/{Widgets => Shell}/UIEditorPanelFrame.h (83%) rename new_editor/include/XCEditor/{Core => Shell}/UIEditorPanelHostLifecycle.h (94%) rename new_editor/include/XCEditor/{Core => Shell}/UIEditorPanelRegistry.h (100%) rename new_editor/include/XCEditor/{Core => Shell}/UIEditorShellCompose.h (92%) rename new_editor/include/XCEditor/{Core => Shell}/UIEditorShellInteraction.h (95%) rename new_editor/include/XCEditor/{Widgets => Shell}/UIEditorStatusBar.h (85%) rename new_editor/include/XCEditor/{Core => Shell}/UIEditorViewportInputBridge.h (100%) rename new_editor/include/XCEditor/{Core => Shell}/UIEditorViewportShell.h (94%) rename new_editor/include/XCEditor/{Widgets => Shell}/UIEditorViewportSlot.h (99%) rename new_editor/include/XCEditor/{Core => Shell}/UIEditorWorkspaceCompose.h (87%) rename new_editor/include/XCEditor/{Core => Shell}/UIEditorWorkspaceController.h (97%) rename new_editor/include/XCEditor/{Core => Shell}/UIEditorWorkspaceInteraction.h (88%) rename new_editor/include/XCEditor/{Core => Shell}/UIEditorWorkspaceLayoutPersistence.h (96%) rename new_editor/include/XCEditor/{Core => Shell}/UIEditorWorkspaceModel.h (100%) rename new_editor/include/XCEditor/{Core => Shell}/UIEditorWorkspaceSession.h (96%) create mode 100644 new_editor/include/XCEditor/Widgets/UIEditorColorUtils.h delete mode 100644 new_editor/include/XCEditor/Widgets/UIEditorPanelChrome.h create mode 100644 new_editor/src/Collections/UIEditorInlineRenameSession.cpp rename new_editor/src/{Widgets => Collections}/UIEditorListView.cpp (99%) rename new_editor/src/{Core => Collections}/UIEditorListViewInteraction.cpp (50%) rename new_editor/src/{Widgets => Collections}/UIEditorScrollView.cpp (79%) rename new_editor/src/{Core => Collections}/UIEditorScrollViewInteraction.cpp (99%) rename new_editor/src/{Widgets => Collections}/UIEditorTabStrip.cpp (93%) create mode 100644 new_editor/src/Collections/UIEditorTabStripInteraction.cpp rename new_editor/src/{Widgets => Collections}/UIEditorTreeView.cpp (99%) create mode 100644 new_editor/src/Collections/UIEditorTreeViewInteraction.cpp delete mode 100644 new_editor/src/Core/UIEditorDockHostInteraction.cpp delete mode 100644 new_editor/src/Core/UIEditorTheme.cpp delete mode 100644 new_editor/src/Core/UIEditorTreeViewInteraction.cpp create mode 100644 new_editor/src/Fields/UIEditorAssetField.cpp create mode 100644 new_editor/src/Fields/UIEditorAssetFieldInteraction.cpp rename new_editor/src/{Widgets => Fields}/UIEditorBoolField.cpp (98%) rename new_editor/src/{Core => Fields}/UIEditorBoolFieldInteraction.cpp (99%) create mode 100644 new_editor/src/Fields/UIEditorColorField.cpp create mode 100644 new_editor/src/Fields/UIEditorColorFieldInteraction.cpp create mode 100644 new_editor/src/Fields/UIEditorEnumField.cpp rename new_editor/src/{Core => Fields}/UIEditorEnumFieldInteraction.cpp (99%) create mode 100644 new_editor/src/Fields/UIEditorFieldStyle.cpp rename new_editor/src/{Widgets => Fields}/UIEditorNumberField.cpp (54%) rename new_editor/src/{Core => Fields}/UIEditorNumberFieldInteraction.cpp (99%) create mode 100644 new_editor/src/Fields/UIEditorObjectField.cpp create mode 100644 new_editor/src/Fields/UIEditorObjectFieldInteraction.cpp rename new_editor/src/{Widgets => Fields}/UIEditorPropertyGrid.cpp (86%) rename new_editor/src/{Core => Fields}/UIEditorPropertyGridInteraction.cpp (74%) create mode 100644 new_editor/src/Fields/UIEditorTextField.cpp rename new_editor/src/{Core => Fields}/UIEditorTextFieldInteraction.cpp (99%) rename new_editor/src/{Widgets => Fields}/UIEditorVector2Field.cpp (51%) rename new_editor/src/{Core => Fields}/UIEditorVector2FieldInteraction.cpp (99%) rename new_editor/src/{Widgets => Fields}/UIEditorVector3Field.cpp (51%) rename new_editor/src/{Core => Fields}/UIEditorVector3FieldInteraction.cpp (99%) rename new_editor/src/{Widgets => Fields}/UIEditorVector4Field.cpp (50%) rename new_editor/src/{Core => Fields}/UIEditorVector4FieldInteraction.cpp (99%) rename new_editor/src/{Core => Foundation}/UIEditorCommandDispatcher.cpp (98%) rename new_editor/src/{Core => Foundation}/UIEditorCommandRegistry.cpp (98%) rename new_editor/src/{Core => Foundation}/UIEditorShortcutManager.cpp (99%) create mode 100644 new_editor/src/Foundation/UIEditorTheme.cpp rename new_editor/src/{Core => Shell}/EditorShellAsset.cpp (97%) rename new_editor/src/{Core => Shell}/EditorShellAsset.h (85%) rename new_editor/src/{Widgets => Shell}/UIEditorDockHost.cpp (94%) create mode 100644 new_editor/src/Shell/UIEditorDockHostInteraction.cpp rename new_editor/src/{Widgets => Shell}/UIEditorMenuBar.cpp (93%) rename new_editor/src/{Core => Shell}/UIEditorMenuModel.cpp (99%) rename new_editor/src/{Widgets => Shell}/UIEditorMenuPopup.cpp (99%) rename new_editor/src/{Core => Shell}/UIEditorMenuSession.cpp (83%) rename new_editor/src/{Core => Shell}/UIEditorPanelContentHost.cpp (99%) rename new_editor/src/{Widgets => Shell}/UIEditorPanelFrame.cpp (92%) rename new_editor/src/{Core => Shell}/UIEditorPanelHostLifecycle.cpp (99%) rename new_editor/src/{Core => Shell}/UIEditorPanelRegistry.cpp (97%) rename new_editor/src/{Core => Shell}/UIEditorShellCompose.cpp (96%) rename new_editor/src/{Core => Shell}/UIEditorShellInteraction.cpp (99%) rename new_editor/src/{Widgets => Shell}/UIEditorStatusBar.cpp (96%) rename new_editor/src/{Core => Shell}/UIEditorViewportInputBridge.cpp (99%) rename new_editor/src/{Core => Shell}/UIEditorViewportShell.cpp (98%) rename new_editor/src/{Widgets => Shell}/UIEditorViewportSlot.cpp (99%) rename new_editor/src/{Core => Shell}/UIEditorWorkspaceCompose.cpp (93%) rename new_editor/src/{Core => Shell}/UIEditorWorkspaceController.cpp (99%) rename new_editor/src/{Core => Shell}/UIEditorWorkspaceInteraction.cpp (92%) rename new_editor/src/{Core => Shell}/UIEditorWorkspaceLayoutPersistence.cpp (99%) rename new_editor/src/{Core => Shell}/UIEditorWorkspaceModel.cpp (99%) rename new_editor/src/{Core => Shell}/UIEditorWorkspaceSession.cpp (99%) delete mode 100644 new_editor/src/Widgets/UIEditorEnumField.cpp delete mode 100644 new_editor/src/Widgets/UIEditorTextField.cpp delete mode 100644 new_editor/ui/themes/editor_shell.xctheme delete mode 100644 tests/UI/Editor/integration/shared/themes/editor_validation.xctheme create mode 100644 tests/UI/Editor/integration/shell/asset_field_basic/CMakeLists.txt create mode 100644 tests/UI/Editor/integration/shell/asset_field_basic/captures/.gitkeep create mode 100644 tests/UI/Editor/integration/shell/asset_field_basic/main.cpp create mode 100644 tests/UI/Editor/integration/shell/object_field_basic/CMakeLists.txt create mode 100644 tests/UI/Editor/integration/shell/object_field_basic/captures/.gitkeep create mode 100644 tests/UI/Editor/integration/shell/object_field_basic/main.cpp create mode 100644 tests/UI/Editor/unit/test_ui_editor_asset_field.cpp create mode 100644 tests/UI/Editor/unit/test_ui_editor_asset_field_interaction.cpp create mode 100644 tests/UI/Editor/unit/test_ui_editor_hosted_field_builders.cpp create mode 100644 tests/UI/Editor/unit/test_ui_editor_object_field.cpp create mode 100644 tests/UI/Editor/unit/test_ui_editor_object_field_interaction.cpp delete mode 100644 tests/UI/Editor/unit/test_ui_editor_panel_chrome.cpp delete mode 100644 tests/UI/Editor/unit/test_ui_editor_theme.cpp diff --git a/docs/api/XCEngine/Rendering/CameraRenderer/CameraRenderer.md b/docs/api/XCEngine/Rendering/CameraRenderer/CameraRenderer.md deleted file mode 100644 index 7cd48430..00000000 --- a/docs/api/XCEngine/Rendering/CameraRenderer/CameraRenderer.md +++ /dev/null @@ -1,109 +0,0 @@ -# CameraRenderer - -**命名空间**: `XCEngine::Rendering` - -**类型**: `class` - -**头文件**: `XCEngine/Rendering/Execution/CameraRenderer.h` - -**描述**: 单个 `CameraRenderRequest` 的执行器,负责在一次提交里串起 scene extraction、shadow/depth 预 pass、主主管线、object-id,以及注入式 pre/post/overlay pass。 - -## 概述 - -`CameraRenderer` 是当前 Rendering 模块里的“单 request 执行层”。它不负责决定应该渲染哪些相机,也不负责 request 排序;这些职责在 [SceneRenderer](../SceneRenderer/SceneRenderer.md) 与 [SceneRenderRequestPlanner](../SceneRenderRequestPlanner/SceneRenderRequestPlanner.md)。 - -它关心的是另一件事:一份已经组装好的 `CameraRenderRequest`,到底按什么顺序跑完。 - -## 当前持有的运行时对象 - -- `m_sceneExtractor` - 使用 request surface 的 render-area 尺寸提取当前相机对应的 `RenderSceneData`。 -- `m_pipelineAsset` - 当前主管线的创建来源工厂;可能为空。 -- `m_pipeline` - 当前实际执行主场景绘制的 runtime [RenderPipeline](../RenderPipeline/RenderPipeline.md)。 -- `m_objectIdPass` - 按需消费 `request.objectId` 的 object-id pass;默认是 [Passes::BuiltinObjectIdPass](../Passes/BuiltinObjectIdPass/BuiltinObjectIdPass.md)。 -- `m_depthOnlyPass` - 按需消费 `request.depthOnly` 的独立 scene pass;默认是 [Passes::BuiltinDepthOnlyPass](../Passes/BuiltinDepthOnlyPass/BuiltinDepthOnlyPass.md)。 -- `m_shadowCasterPass` - 按需消费 `request.shadowCaster` 的独立 scene pass;默认是 [Passes::BuiltinShadowCasterPass](../Passes/BuiltinShadowCasterPass/BuiltinShadowCasterPass.md)。 - -这意味着 `CameraRenderer` 不替调用方生成 Scene View 的特殊效果,但会直接消费 request 上已经明确给出的 depth-only / shadow-caster / object-id / post / overlay 槽位。 - -## 当前执行顺序 - -对一份有效 request,当前 `Render()` 的主顺序是: - -1. 校验 request、自身主管线、render-area,以及 `shadowCaster / depthOnly / objectId` 请求前置条件。 -2. 用 `m_sceneExtractor.ExtractForCamera(...)` 生成 `RenderSceneData`。 -3. 用 request 覆盖 `sceneData.cameraData.clearFlags`,必要时再覆盖 `clearColor`。 -4. 执行 `preScenePasses`。 -5. 如请求了 shadow-caster,执行 `m_shadowCasterPass`。 -6. 如请求了 depth-only,执行 `m_depthOnlyPass`。 -7. 执行 `m_pipeline->Render(...)`。 -8. 如请求了 object-id,执行 `m_objectIdPass->Render(...)`。 -9. 执行 `postScenePasses`。 -10. 执行 `overlayPasses`。 -11. 按已初始化的 sequence 逆向 `Shutdown()`。 - -`tests/Rendering/unit/test_camera_scene_renderer.cpp` 已覆盖这条顺序,包括 shadow-caster / depth-only 位于主主管线之前这一点。 - -## 生命周期与回退策略 - -- 默认构造会使用静态共享的 [BuiltinForwardPipelineAsset](../Pipelines/BuiltinForwardPipelineAsset/BuiltinForwardPipelineAsset.md),并同时创建 builtin object-id / depth-only / shadow-caster pass。 -- `SetPipeline()` 走“手动注入 runtime pipeline”路径,并清空当前 `m_pipelineAsset`。 -- `SetPipelineAsset()` 走“由 asset 创建 runtime pipeline”路径;传空时会回退到默认 asset。 -- `ResetPipeline()` 会先 `Shutdown()` 旧主管线,再接管新实例;若新实例为空,则再次回退到默认 asset 并创建 builtin forward。 -- [SetObjectIdPass](SetObjectIdPass.md) 会先关闭旧 pass;传空时回退到 builtin object-id pass。 -- [SetDepthOnlyPass](SetDepthOnlyPass.md) 会先关闭旧 pass;传空时回退到 builtin depth-only pass。 -- [SetShadowCasterPass](SetShadowCasterPass.md) 会先关闭旧 pass;传空时回退到 builtin shadow-caster pass。 - -## 当前验证规则 - -`Render()` 当前会显式拒绝: - -- `request.IsValid()` 为假 -- 当前 `m_pipeline == nullptr` -- `request.surface` 的 render-area 宽或高为 `0` -- 请求了 shadow-caster,但 `request.shadowCaster.IsValid()` 为假 -- 请求了 depth-only,但 `request.depthOnly.IsValid()` 为假 -- 请求了 object-id,但 `request.objectId.IsValid()` 为假 - -## 当前实现边界 - -- 这里只执行单个 request;多相机排序、stack 顺序和 clear 规则在 `SceneRenderer` / `SceneRenderRequestPlanner` 层完成。 -- 这里只消费 request 上已经存在的 scene-pass 与 pass-sequence 槽位,不主动构建 editor 专用 overlay。 -- post / overlay 阶段仍然只是顺序执行的 `RenderPassSequence`,不是 render graph。 - -## 公开方法 - -| 方法 | 说明 | -|------|------| -| [Constructor](Constructor.md) | 构造 `CameraRenderer`,并决定主主管线与 builtin 独立 pass 的初始来源。 | -| [Destructor](Destructor.md) | 关闭当前主管线、object-id pass、depth-only pass 与 shadow-caster pass。 | -| [SetPipeline](SetPipeline.md) | 手动替换当前 runtime pipeline。 | -| [SetPipelineAsset](SetPipelineAsset.md) | 通过 `RenderPipelineAsset` 重建当前 runtime pipeline。 | -| [SetObjectIdPass](SetObjectIdPass.md) | 替换当前 object-id pass。 | -| [SetDepthOnlyPass](SetDepthOnlyPass.md) | 替换当前 depth-only scene pass。 | -| [SetShadowCasterPass](SetShadowCasterPass.md) | 替换当前 shadow-caster scene pass。 | -| [GetPipeline](GetPipeline.md) | 读取当前主管线的非拥有指针。 | -| [GetPipelineAsset](GetPipelineAsset.md) | 读取当前 pipeline asset 的非拥有指针。 | -| [GetObjectIdPass](GetObjectIdPass.md) | 读取当前 object-id pass 的非拥有指针。 | -| [GetDepthOnlyPass](GetDepthOnlyPass.md) | 读取当前 depth-only pass 的非拥有指针。 | -| [GetShadowCasterPass](GetShadowCasterPass.md) | 读取当前 shadow-caster pass 的非拥有指针。 | -| [Render](Render.md) | 执行一次完整的单相机提交。 | - -## 相关文档 - -- [Rendering](../Rendering.md) -- [SceneRenderer](../SceneRenderer/SceneRenderer.md) -- [CameraRenderRequest](../CameraRenderRequest/CameraRenderRequest.md) -- [RenderSceneExtractor](../RenderSceneExtractor/RenderSceneExtractor.md) -- [RenderPipeline](../RenderPipeline/RenderPipeline.md) -- [ObjectIdPass](../ObjectIdPass/ObjectIdPass.md) -- [BuiltinDepthOnlyPass](../Passes/BuiltinDepthOnlyPass/BuiltinDepthOnlyPass.md) -- [BuiltinShadowCasterPass](../Passes/BuiltinShadowCasterPass/BuiltinShadowCasterPass.md) -- [Camera Request Planning And Clear Rules](../../../_guides/Rendering/Camera-Request-Planning-And-Clear-Rules.md) -- [Scene Extraction And Builtin Forward Pipeline](../../../_guides/Rendering/Scene-Extraction-And-Builtin-Forward-Pipeline.md) - diff --git a/docs/api/XCEngine/Rendering/CameraRenderer/Constructor.md b/docs/api/XCEngine/Rendering/CameraRenderer/Constructor.md deleted file mode 100644 index d1ac0e5f..00000000 --- a/docs/api/XCEngine/Rendering/CameraRenderer/Constructor.md +++ /dev/null @@ -1,73 +0,0 @@ -# CameraRenderer::Constructor - -**命名空间**: `XCEngine::Rendering` - -**类型**: `constructor-overload group` - -**头文件**: `XCEngine/Rendering/Execution/CameraRenderer.h` - -## 签名 - -```cpp -CameraRenderer(); -explicit CameraRenderer(std::unique_ptr pipeline); -explicit CameraRenderer(std::shared_ptr pipelineAsset); -CameraRenderer( - std::unique_ptr pipeline, - std::unique_ptr objectIdPass, - std::unique_ptr depthOnlyPass = nullptr, - std::unique_ptr shadowCasterPass = nullptr); -``` - -## 行为说明 - -构造函数的核心职责是决定三件事: - -- 当前主管线实例从哪里来 -- 当前 object-id pass 是否使用默认 builtin 实现 -- 当前 depth-only / shadow-caster pass 是否使用默认 builtin 实现 - -## 当前构造路径 - -### 默认构造 - -- 通过内部静态 `CreateDefaultPipelineAsset()` 取得共享的默认 asset。 -- 默认 asset 是 [BuiltinForwardPipelineAsset](../Pipelines/BuiltinForwardPipelineAsset/BuiltinForwardPipelineAsset.md)。 -- 随后走 asset 构造路径生成 runtime pipeline。 - -### `std::unique_ptr` 重载 - -- 交由四参重载处理。 -- 如果未显式提供 object-id / depth-only / shadow-caster pass,会分别补: - - [Passes::BuiltinObjectIdPass](../Passes/BuiltinObjectIdPass/BuiltinObjectIdPass.md) - - [Passes::BuiltinDepthOnlyPass](../Passes/BuiltinDepthOnlyPass/BuiltinDepthOnlyPass.md) - - [Passes::BuiltinShadowCasterPass](../Passes/BuiltinShadowCasterPass/BuiltinShadowCasterPass.md) - -### `std::shared_ptr` 重载 - -- 先保存 asset。 -- 再调用 [SetPipelineAsset](SetPipelineAsset.md),立即由 asset 创建 runtime pipeline。 -- 默认同时创建 builtin object-id / depth-only / shadow-caster pass。 - -### 四参重载 - -- 若 `objectIdPass` 为空,自动回退到 builtin object-id pass。 -- 若 `depthOnlyPass` 为空,自动回退到 builtin depth-only pass。 -- 若 `shadowCasterPass` 为空,自动回退到 builtin shadow-caster pass。 -- 调用内部 `ResetPipeline(std::move(pipeline))`。 -- 如果传入 `pipeline` 为空,`ResetPipeline()` 会继续回退到默认 pipeline asset,并创建 builtin forward 主管线。 - -## 所有权 - -- `RenderPipeline`、`ObjectIdPass`、`depthOnlyPass` 和 `shadowCasterPass` 都由 `CameraRenderer` 以 `std::unique_ptr` 独占持有。 -- `RenderPipelineAsset` 以 `std::shared_ptr` 共享持有,只作为 runtime pipeline 的创建来源。 - -## 相关文档 - -- [CameraRenderer](CameraRenderer.md) -- [SetPipeline](SetPipeline.md) -- [SetPipelineAsset](SetPipelineAsset.md) -- [SetObjectIdPass](SetObjectIdPass.md) -- [SetDepthOnlyPass](SetDepthOnlyPass.md) -- [SetShadowCasterPass](SetShadowCasterPass.md) - diff --git a/docs/api/XCEngine/Rendering/CameraRenderer/Destructor.md b/docs/api/XCEngine/Rendering/CameraRenderer/Destructor.md deleted file mode 100644 index c572bcba..00000000 --- a/docs/api/XCEngine/Rendering/CameraRenderer/Destructor.md +++ /dev/null @@ -1,47 +0,0 @@ -# CameraRenderer::~CameraRenderer - -**命名空间**: `XCEngine::Rendering` - -**类型**: `destructor` - -**头文件**: `XCEngine/Rendering/Execution/CameraRenderer.h` - -## 签名 - -```cpp -~CameraRenderer(); -``` - -## 作用 - -在对象销毁前关闭当前持有的主管线、object-id pass、depth-only pass 和 shadow-caster pass。 - -## 当前实现行为 - -析构函数当前按以下顺序执行清理: - -1. 如果 `m_pipeline` 非空,调用 `m_pipeline->Shutdown()`。 -2. 如果 `m_objectIdPass` 非空,调用 `m_objectIdPass->Shutdown()`。 -3. 如果 `m_depthOnlyPass` 非空,调用 `m_depthOnlyPass->Shutdown()`。 -4. 如果 `m_shadowCasterPass` 非空,调用 `m_shadowCasterPass->Shutdown()`。 - -随后由 `unique_ptr` 和成员对象自身析构负责释放内存。 - -## 设计含义 - -- 主管线、object-id pass、depth-only pass 和 shadow-caster pass 的 GPU 资源回收路径都是显式 `Shutdown()`,而不是只依赖 C++ 析构。 -- 调用方注入到 `CameraRenderRequest` 里的 `RenderPassSequence` 并不由这里持有,因此也不在这里做统一回收。 - -## 注意事项 - -- 当前没有内部同步;不应在其它线程仍可能访问这些对象时销毁 `CameraRenderer`。 -- 如果你只是想切换主管线或这些独立 pass,不要等到析构,应该优先使用 [SetPipeline](SetPipeline.md)、[SetPipelineAsset](SetPipelineAsset.md)、[SetObjectIdPass](SetObjectIdPass.md)、[SetDepthOnlyPass](SetDepthOnlyPass.md) 或 [SetShadowCasterPass](SetShadowCasterPass.md)。 - -## 相关文档 - -- [CameraRenderer](CameraRenderer.md) -- [SetPipeline](SetPipeline.md) -- [SetObjectIdPass](SetObjectIdPass.md) -- [SetDepthOnlyPass](SetDepthOnlyPass.md) -- [SetShadowCasterPass](SetShadowCasterPass.md) - diff --git a/docs/api/XCEngine/Rendering/CameraRenderer/GetDepthOnlyPass.md b/docs/api/XCEngine/Rendering/CameraRenderer/GetDepthOnlyPass.md deleted file mode 100644 index e8daf36c..00000000 --- a/docs/api/XCEngine/Rendering/CameraRenderer/GetDepthOnlyPass.md +++ /dev/null @@ -1,33 +0,0 @@ -# CameraRenderer::GetDepthOnlyPass - -**命名空间**: `XCEngine::Rendering` - -**类型**: `method` - -**头文件**: `XCEngine/Rendering/Execution/CameraRenderer.h` - -## 签名 - -```cpp -RenderPass* GetDepthOnlyPass() const; -``` - -## 作用 - -返回当前 depth-only scene pass 的裸指针。 - -## 当前实现行为 - -- 这是头文件内联访问器,直接返回 `m_depthOnlyPass.get()`。 -- 不转移所有权。 - -## 注意事项 - -- 调用 [SetDepthOnlyPass](SetDepthOnlyPass.md) 之后,之前读取到的裸指针可能立刻失效。 -- 当前默认返回的通常是 builtin depth-only pass,但调用方不应把这个 API 当成“只会返回某个具体类型”的承诺。 - -## 相关文档 - -- [SetDepthOnlyPass](SetDepthOnlyPass.md) -- [Render](Render.md) - diff --git a/docs/api/XCEngine/Rendering/CameraRenderer/GetObjectIdPass.md b/docs/api/XCEngine/Rendering/CameraRenderer/GetObjectIdPass.md deleted file mode 100644 index cfe93c41..00000000 --- a/docs/api/XCEngine/Rendering/CameraRenderer/GetObjectIdPass.md +++ /dev/null @@ -1,32 +0,0 @@ -# CameraRenderer::GetObjectIdPass - -**命名空间**: `XCEngine::Rendering` - -**类型**: `method` - -**头文件**: `XCEngine/Rendering/Execution/CameraRenderer.h` - -## 签名 - -```cpp -ObjectIdPass* GetObjectIdPass() const; -``` - -## 作用 - -返回当前 object-id pass 的裸指针。 - -## 当前实现行为 - -- 这是头文件内联访问器,直接返回 `m_objectIdPass.get()`。 -- 不转移所有权。 - -## 注意事项 - -- 调用 [SetObjectIdPass](SetObjectIdPass.md) 后,之前读取到的裸指针可能立即失效。 - -## 相关文档 - -- [SetObjectIdPass](SetObjectIdPass.md) -- [Render](Render.md) - diff --git a/docs/api/XCEngine/Rendering/CameraRenderer/GetPipeline.md b/docs/api/XCEngine/Rendering/CameraRenderer/GetPipeline.md deleted file mode 100644 index 5628bad1..00000000 --- a/docs/api/XCEngine/Rendering/CameraRenderer/GetPipeline.md +++ /dev/null @@ -1,38 +0,0 @@ -# CameraRenderer::GetPipeline - -**命名空间**: `XCEngine::Rendering` - -**类型**: `method` - -**头文件**: `XCEngine/Rendering/Execution/CameraRenderer.h` - -## 签名 - -```cpp -RenderPipeline* GetPipeline() const; -``` - -## 作用 - -返回当前主管线实例的裸指针。 - -## 当前实现行为 - -- 这是头文件内联访问器,直接返回 `m_pipeline.get()`。 -- 不转移所有权,也不提供额外生命周期保证。 - -## 返回值 - -- 正常路径下通常会返回一个有效指针,因为构造和 `SetPipeline()` / `SetPipelineAsset()` 都会回退到默认管线。 -- 理论上如果对象正处于构造中的异常路径或未来实现改变,也不能把“永不为空”当成稳定契约。 - -## 注意事项 - -- 不要长期缓存这个裸指针;调用 [SetPipeline](SetPipeline.md) 或 [SetPipelineAsset](SetPipelineAsset.md) 后它可能立即失效。 - -## 相关文档 - -- [SetPipeline](SetPipeline.md) -- [SetPipelineAsset](SetPipelineAsset.md) -- [GetPipelineAsset](GetPipelineAsset.md) - diff --git a/docs/api/XCEngine/Rendering/CameraRenderer/GetPipelineAsset.md b/docs/api/XCEngine/Rendering/CameraRenderer/GetPipelineAsset.md deleted file mode 100644 index bcd9e910..00000000 --- a/docs/api/XCEngine/Rendering/CameraRenderer/GetPipelineAsset.md +++ /dev/null @@ -1,33 +0,0 @@ -# CameraRenderer::GetPipelineAsset - -**命名空间**: `XCEngine::Rendering` - -**类型**: `method` - -**头文件**: `XCEngine/Rendering/Execution/CameraRenderer.h` - -## 签名 - -```cpp -const RenderPipelineAsset* GetPipelineAsset() const; -``` - -## 作用 - -返回当前绑定的 pipeline asset 裸指针。 - -## 当前实现行为 - -- 这是头文件内联访问器,直接返回 `m_pipelineAsset.get()`。 -- 如果最近一次切换是通过 [SetPipeline](SetPipeline.md) 手动注入实例完成的,这里可能返回 `nullptr`。 - -## 返回值 - -- 返回当前 asset 裸指针,或空指针。 - -## 相关文档 - -- [SetPipelineAsset](SetPipelineAsset.md) -- [SetPipeline](SetPipeline.md) -- [GetPipeline](GetPipeline.md) - diff --git a/docs/api/XCEngine/Rendering/CameraRenderer/GetShadowCasterPass.md b/docs/api/XCEngine/Rendering/CameraRenderer/GetShadowCasterPass.md deleted file mode 100644 index 5ed946e9..00000000 --- a/docs/api/XCEngine/Rendering/CameraRenderer/GetShadowCasterPass.md +++ /dev/null @@ -1,33 +0,0 @@ -# CameraRenderer::GetShadowCasterPass - -**命名空间**: `XCEngine::Rendering` - -**类型**: `method` - -**头文件**: `XCEngine/Rendering/Execution/CameraRenderer.h` - -## 签名 - -```cpp -RenderPass* GetShadowCasterPass() const; -``` - -## 作用 - -返回当前 shadow-caster scene pass 的裸指针。 - -## 当前实现行为 - -- 这是头文件内联访问器,直接返回 `m_shadowCasterPass.get()`。 -- 不转移所有权。 - -## 注意事项 - -- 调用 [SetShadowCasterPass](SetShadowCasterPass.md) 之后,之前读取到的裸指针可能立刻失效。 -- 当前默认返回的通常是 builtin shadow-caster pass,但调用方不应假定返回值一定来自某个具体实现类。 - -## 相关文档 - -- [SetShadowCasterPass](SetShadowCasterPass.md) -- [Render](Render.md) - diff --git a/docs/api/XCEngine/Rendering/CameraRenderer/Render.md b/docs/api/XCEngine/Rendering/CameraRenderer/Render.md deleted file mode 100644 index 39119d12..00000000 --- a/docs/api/XCEngine/Rendering/CameraRenderer/Render.md +++ /dev/null @@ -1,83 +0,0 @@ -# CameraRenderer::Render - -**命名空间**: `XCEngine::Rendering` - -**类型**: `method` - -**头文件**: `XCEngine/Rendering/Execution/CameraRenderer.h` - -## 签名 - -```cpp -bool Render(const CameraRenderRequest& request); -``` - -## 行为说明 - -执行一次完整的单相机提交。当前实现会把 shadow-caster、depth-only、主场景、object-id,以及调用方注入的 pre / post / overlay pass 串成一条固定顺序的执行链。 - -## 当前流程 - -1. 校验 `request.IsValid()`,并确认 `m_pipeline` 非空。 -2. 拒绝 render-area 宽或高为 `0` 的 `surface`。 -3. 如果请求了 shadow-caster,要让 `request.shadowCaster.IsValid()` 为真。 -4. 如果请求了 depth-only,要让 `request.depthOnly.IsValid()` 为真。 -5. 如果请求了 object-id,要让 `request.objectId.IsValid()` 为真。 -6. 用 `request.surface.GetRenderAreaWidth()` / `GetRenderAreaHeight()` 调用 `m_sceneExtractor.ExtractForCamera(...)`。 -7. 若 `sceneData.HasCamera()` 为假,则返回 `false`。 -8. 用 request 覆盖 `sceneData.cameraData.clearFlags`;如果 request 提供了 clear-color override,则继续覆盖 `sceneData.cameraData.clearColor`。 -9. 依次初始化并执行: - - `preScenePasses` - - `m_shadowCasterPass->Execute(...)` - - `m_depthOnlyPass->Execute(...)` - - `m_pipeline->Render(...)` - - `m_objectIdPass->Render(...)` - - `postScenePasses` - - `overlayPasses` -10. 按已经成功初始化的 sequence 做逆向 `Shutdown()`。 - -## `postScenePasses` / `overlayPasses` 接入点 - -`CameraRenderer` 只消费调用方传入的 pass 序列,不额外构建内部后处理层。 - -- `postScenePasses` 直接来自调用方提供的 `RenderPassSequence` -- `overlayPasses` 也是同样的注入点 -- 编辑器 Scene View 当前会先通过 [SceneViewportRenderPlan](../../Editor/Viewport/SceneViewportRenderPlan/SceneViewportRenderPlan.md) 组装这些序列,再调用 `ApplySceneViewportRenderPlan(...)` 挂到 request - -这意味着 Scene View 的无限网格、选中轮廓和编辑器 overlay,已经被上移到 request 组装层,不再由 `CameraRenderer` 内部再做一次翻译。与之相对,`shadowCaster` 和 `depthOnly` 当前仍属于 `CameraRenderRequest` 上的专用 scene-pass 槽位,由 `CameraRenderer` 直接在主主管线之前消费。 - -## 失败与清理语义 - -- 任一验证失败会直接返回 `false`。 -- `shadowCaster` 或 `depthOnly` 请求校验失败时,不会进入主主管线。 -- 任一 sequence 初始化失败会立刻执行对应 `Shutdown()` 回滚。 -- `shadowCaster`、`depthOnly`、主管线、object-id pass、post 或 overlay 任一阶段失败,都会按已经初始化过的 sequence 做清理再返回 `false`。 - -## 参数 - -- `request` - 本次单相机渲染的完整输入。 - -## 返回值 - -- 整条提交流成功执行时返回 `true`。 -- request 校验失败、场景提取失败、主管线失败,或任一附加阶段失败时返回 `false`。 - -## 测试覆盖 - -`tests/Rendering/unit/test_camera_scene_renderer.cpp` 当前验证了: - -- render-area 会影响提取到的相机 viewport 尺寸 -- clear-flags 和 clear-color override 会写回 `sceneData.cameraData` -- `shadowCaster / depthOnly / pipeline / object-id / post / overlay` 的执行顺序 -- shadow-caster 请求无效、object-id 失败和 post-pass 初始化失败时的回滚路径 - -## 相关文档 - -- [CameraRenderer](CameraRenderer.md) -- [CameraRenderRequest](../CameraRenderRequest/CameraRenderRequest.md) -- [RenderSceneExtractor](../RenderSceneExtractor/RenderSceneExtractor.md) -- [RenderPipeline::Render](../RenderPipeline/Render.md) -- [SetDepthOnlyPass](SetDepthOnlyPass.md) -- [SetShadowCasterPass](SetShadowCasterPass.md) -- [Passes](../Passes/Passes.md) - diff --git a/docs/api/XCEngine/Rendering/CameraRenderer/SetDepthOnlyPass.md b/docs/api/XCEngine/Rendering/CameraRenderer/SetDepthOnlyPass.md deleted file mode 100644 index 5f22f2b1..00000000 --- a/docs/api/XCEngine/Rendering/CameraRenderer/SetDepthOnlyPass.md +++ /dev/null @@ -1,36 +0,0 @@ -# CameraRenderer::SetDepthOnlyPass - -**命名空间**: `XCEngine::Rendering` - -**类型**: `method` - -**头文件**: `XCEngine/Rendering/Execution/CameraRenderer.h` - -## 签名 - -```cpp -void SetDepthOnlyPass(std::unique_ptr depthOnlyPass); -``` - -## 作用 - -替换当前用于消费 `request.depthOnly` 的独立 scene pass。 - -## 当前实现行为 - -1. 如果旧 `m_depthOnlyPass` 非空,先调用它的 `Shutdown()`。 -2. 接管新的 `depthOnlyPass`。 -3. 如果新指针为空,则自动回退到 [Passes::BuiltinDepthOnlyPass](../Passes/BuiltinDepthOnlyPass/BuiltinDepthOnlyPass.md)。 - -## 关键语义 - -- 这里替换的是一个普通 `RenderPass`,并不要求具体类型必须来自 builtin pass 层。 -- `CameraRenderer::Render()` 只在 `request.depthOnly.IsRequested()` 时才会真正执行这里持有的 pass。 -- `tests/Rendering/unit/test_camera_scene_renderer.cpp` 通过 mock pass 验证了这条注入链会在主主管线之前执行,并正确消费 `ScenePassRenderRequest` 的 surface / clear-color override。 - -## 相关文档 - -- [GetDepthOnlyPass](GetDepthOnlyPass.md) -- [Render](Render.md) -- [BuiltinDepthOnlyPass](../Passes/BuiltinDepthOnlyPass/BuiltinDepthOnlyPass.md) - diff --git a/docs/api/XCEngine/Rendering/CameraRenderer/SetObjectIdPass.md b/docs/api/XCEngine/Rendering/CameraRenderer/SetObjectIdPass.md deleted file mode 100644 index 1ca5ddb7..00000000 --- a/docs/api/XCEngine/Rendering/CameraRenderer/SetObjectIdPass.md +++ /dev/null @@ -1,35 +0,0 @@ -# CameraRenderer::SetObjectIdPass - -**命名空间**: `XCEngine::Rendering` - -**类型**: `method` - -**头文件**: `XCEngine/Rendering/Execution/CameraRenderer.h` - -## 签名 - -```cpp -void SetObjectIdPass(std::unique_ptr objectIdPass); -``` - -## 作用 - -替换主场景之后使用的 object-id 输出 pass。 - -## 当前实现行为 - -1. 如果旧 `m_objectIdPass` 非空,先调用它的 `Shutdown()`。 -2. 接管新的 `objectIdPass`。 -3. 如果新指针为空,则自动回退到 `Passes::BuiltinObjectIdPass`。 - -## 关键语义 - -- 这是 object-id 渲染链路的主要注入点,测试里可以通过它替换 mock pass。 -- `CameraRenderer::Render()` 只在 `request.objectId.IsRequested()` 时才会真正调用这里持有的 pass。 - -## 相关文档 - -- [GetObjectIdPass](GetObjectIdPass.md) -- [Render](Render.md) -- [Passes](../Passes/Passes.md) - diff --git a/docs/api/XCEngine/Rendering/CameraRenderer/SetPipeline.md b/docs/api/XCEngine/Rendering/CameraRenderer/SetPipeline.md deleted file mode 100644 index a58da904..00000000 --- a/docs/api/XCEngine/Rendering/CameraRenderer/SetPipeline.md +++ /dev/null @@ -1,44 +0,0 @@ -# CameraRenderer::SetPipeline - -**命名空间**: `XCEngine::Rendering` - -**类型**: `method` - -**头文件**: `XCEngine/Rendering/Execution/CameraRenderer.h` - -## 签名 - -```cpp -void SetPipeline(std::unique_ptr pipeline); -``` - -## 作用 - -手动替换当前主管线实例,并切断与 `RenderPipelineAsset` 的绑定关系。 - -## 当前实现行为 - -1. 先把 `m_pipelineAsset.reset()`,表示后续不再认为当前管线来自某个 asset。 -2. 调用内部 `ResetPipeline(std::move(pipeline))`。 -3. `ResetPipeline()` 会: - - 对旧 `m_pipeline` 调用 `Shutdown()` - - 接管新的 `pipeline` - - 如果新指针为空,则回退到默认 pipeline asset 创建的内建前向管线 - -## 参数 - -| 参数 | 说明 | -|------|------| -| `pipeline` | 新的主管线实例所有权。可以为空;为空时会自动回退到默认内建前向管线。 | - -## 关键语义 - -- 这是“直接注入实例”的入口,不会保留原来的 pipeline asset。 -- 替换时走的是显式关停旧管线的路径,而不是简单换指针。 - -## 相关文档 - -- [SetPipelineAsset](SetPipelineAsset.md) -- [GetPipeline](GetPipeline.md) -- [GetPipelineAsset](GetPipelineAsset.md) - diff --git a/docs/api/XCEngine/Rendering/CameraRenderer/SetPipelineAsset.md b/docs/api/XCEngine/Rendering/CameraRenderer/SetPipelineAsset.md deleted file mode 100644 index 36e0297f..00000000 --- a/docs/api/XCEngine/Rendering/CameraRenderer/SetPipelineAsset.md +++ /dev/null @@ -1,36 +0,0 @@ -# CameraRenderer::SetPipelineAsset - -**命名空间**: `XCEngine::Rendering` - -**类型**: `method` - -**头文件**: `XCEngine/Rendering/Execution/CameraRenderer.h` - -## 签名 - -```cpp -void SetPipelineAsset(std::shared_ptr pipelineAsset); -``` - -## 作用 - -通过一份 `RenderPipelineAsset` 重建当前主管线实例。 - -## 当前实现行为 - -1. 如果传入的 `pipelineAsset` 非空,则保存它。 -2. 如果传入为空,则回退到内部静态默认 asset。 -3. 调用 `CreatePipelineFromAsset(m_pipelineAsset)` 创建新的管线实例。 -4. 再通过 `ResetPipeline(...)` 关停旧管线并接管新实例。 - -## 关键语义 - -- 这是“从 asset 创建实例”的入口,和 [SetPipeline](SetPipeline.md) 的手动注入路径不同。 -- 即使 asset 无法返回有效实例,`CreatePipelineFromAsset()` 也会继续回退到内建前向管线。 - -## 相关文档 - -- [SetPipeline](SetPipeline.md) -- [GetPipelineAsset](GetPipelineAsset.md) -- [Constructor](Constructor.md) - diff --git a/docs/api/XCEngine/Rendering/CameraRenderer/SetShadowCasterPass.md b/docs/api/XCEngine/Rendering/CameraRenderer/SetShadowCasterPass.md deleted file mode 100644 index 234c74a4..00000000 --- a/docs/api/XCEngine/Rendering/CameraRenderer/SetShadowCasterPass.md +++ /dev/null @@ -1,36 +0,0 @@ -# CameraRenderer::SetShadowCasterPass - -**命名空间**: `XCEngine::Rendering` - -**类型**: `method` - -**头文件**: `XCEngine/Rendering/Execution/CameraRenderer.h` - -## 签名 - -```cpp -void SetShadowCasterPass(std::unique_ptr shadowCasterPass); -``` - -## 作用 - -替换当前用于消费 `request.shadowCaster` 的独立 scene pass。 - -## 当前实现行为 - -1. 如果旧 `m_shadowCasterPass` 非空,先调用它的 `Shutdown()`。 -2. 接管新的 `shadowCasterPass`。 -3. 如果新指针为空,则自动回退到 [Passes::BuiltinShadowCasterPass](../Passes/BuiltinShadowCasterPass/BuiltinShadowCasterPass.md)。 - -## 关键语义 - -- 这里替换的是一个普通 `RenderPass`,并不要求必须是 builtin shadow-caster 实现。 -- `CameraRenderer::Render()` 只在 `request.shadowCaster.IsRequested()` 时才会执行这里持有的 pass。 -- 当前执行顺序里,shadow-caster pass 位于 depth-only 与主主管线之前。 - -## 相关文档 - -- [GetShadowCasterPass](GetShadowCasterPass.md) -- [Render](Render.md) -- [BuiltinShadowCasterPass](../Passes/BuiltinShadowCasterPass/BuiltinShadowCasterPass.md) - diff --git a/docs/api/XCEngine/Rendering/SceneRenderer/BuildRenderRequests.md b/docs/api/XCEngine/Rendering/SceneRenderer/BuildRenderRequests.md deleted file mode 100644 index 823b3fde..00000000 --- a/docs/api/XCEngine/Rendering/SceneRenderer/BuildRenderRequests.md +++ /dev/null @@ -1,45 +0,0 @@ -# SceneRenderer::BuildRenderRequests - -```cpp -std::vector BuildRenderRequests( - const Components::Scene& scene, - Components::CameraComponent* overrideCamera, - const RenderContext& context, - const RenderSurface& surface) const; -``` - -## 行为说明 - -当前实现直接委托给内部 `SceneRenderRequestPlanner`: - -```cpp -return m_requestPlanner.BuildRequests(scene, overrideCamera, context, surface); -``` - -## 当前用途 - -这个方法只负责“规划请求”,不负责真正渲染。生成的每个 `CameraRenderRequest` 会携带: - -- 选中的相机 -- 相机深度和 stack 顺序 -- 针对该相机解析后的 clear-flags -- 继承并裁剪后的 `RenderSurface` -- 随后交给 `CameraRenderer` 执行所需的上下文 - -## 参数 - -- `scene` - 要规划的场景。 -- `overrideCamera` - 可选的 override 相机;可用时只为它构建请求。 -- `context` - 当前渲染上下文。 -- `surface` - 当前输出目标模板,planner 会基于它推导每个相机的 render-area 和 clear 行为。 - -## 返回值 - -- 按 `SceneRenderRequestPlanner` 规则生成的请求数组。 - -## 相关文档 - -- [SceneRenderer](SceneRenderer.md) -- [Render](Render.md) -- [SceneRenderRequestPlanner](../SceneRenderRequestPlanner/SceneRenderRequestPlanner.md) -- [CameraRenderRequest](../CameraRenderRequest/CameraRenderRequest.md) diff --git a/docs/api/XCEngine/Rendering/SceneRenderer/Constructor.md b/docs/api/XCEngine/Rendering/SceneRenderer/Constructor.md deleted file mode 100644 index 10538ae2..00000000 --- a/docs/api/XCEngine/Rendering/SceneRenderer/Constructor.md +++ /dev/null @@ -1,33 +0,0 @@ -# SceneRenderer::SceneRenderer - -构造一个场景级渲染器。 - -```cpp -SceneRenderer(); -explicit SceneRenderer(std::unique_ptr pipeline); -explicit SceneRenderer(std::shared_ptr pipelineAsset); -``` - -## 当前语义 - -- 三个构造路径的差别只在于内部 `m_cameraRenderer` 如何初始化。 -- 默认构造:使用 `CameraRenderer` 的默认构造路径,最终回退到默认 builtin forward pipeline asset。 -- `std::unique_ptr` 构造:把调用方提供的 runtime pipeline 直接交给内部 `CameraRenderer` 持有。 -- `std::shared_ptr` 构造:让内部 `CameraRenderer` 通过 asset 创建 runtime pipeline。 - -## 当前实现细节 - -- `SceneRenderer` 自己不在构造阶段创建额外的 GPU 资源;真正的 runtime pipeline 创建和回退逻辑都委托给 `CameraRenderer`。 -- 如果传入的 `pipeline` 是空指针,`CameraRenderer` 会回退到默认 pipeline asset,而不是保留一个永久空 pipeline。 -- 如果传入的 `pipelineAsset` 是空指针,也会回退到默认 builtin forward asset。 - -## 测试覆盖 - -- `tests/Rendering/unit/test_camera_scene_renderer.cpp` 验证了默认构造的 `SceneRenderer` 能创建默认 pipeline 并完成渲染。 -- 同一测试文件也覆盖了注入 runtime pipeline 和注入 pipeline asset 两条构造路径。 - -## 相关文档 - -- [SceneRenderer](SceneRenderer.md) -- [SetPipeline](SetPipeline.md) -- [SetPipelineAsset](SetPipelineAsset.md) diff --git a/docs/api/XCEngine/Rendering/SceneRenderer/Destructor.md b/docs/api/XCEngine/Rendering/SceneRenderer/Destructor.md deleted file mode 100644 index b811dd84..00000000 --- a/docs/api/XCEngine/Rendering/SceneRenderer/Destructor.md +++ /dev/null @@ -1,30 +0,0 @@ -# SceneRenderer::~SceneRenderer - -销毁场景级渲染器对象。 - -```cpp -~SceneRenderer() = default; -``` - -## 当前语义 - -- `SceneRenderer` 自身没有自定义析构逻辑。 -- 真正的 teardown 发生在成员对象析构阶段: - - `m_cameraRenderer` 会关闭当前 runtime pipeline 和 object-id pass。 - - `m_requestPlanner` 作为普通值成员按常规方式销毁。 -- `SceneRenderer` 不拥有外部传入请求上的 `RenderPassSequence`,因此析构时不会回收这些外部序列对象。 - -## 调用方影响 - -- 即使 `SceneRenderer` 的析构函数是 `= default`,离开作用域时仍会触发内部 `CameraRenderer` 的资源释放路径。 -- 任何此前通过 [GetPipeline](GetPipeline.md) 或 [GetPipelineAsset](GetPipelineAsset.md) 观察到的非拥有指针,都会在 `SceneRenderer` 销毁后失效。 - -## 测试覆盖 - -- `tests/Rendering/unit/test_camera_scene_renderer.cpp` 使用作用域退出断言了 `SceneRenderer` 内部持有的 pipeline 会在对象销毁时执行 `Shutdown()`。 - -## 相关文档 - -- [SceneRenderer](SceneRenderer.md) -- [GetPipeline](GetPipeline.md) -- [Render](Render.md) diff --git a/docs/api/XCEngine/Rendering/SceneRenderer/GetPipeline.md b/docs/api/XCEngine/Rendering/SceneRenderer/GetPipeline.md deleted file mode 100644 index 0d0fd8c8..00000000 --- a/docs/api/XCEngine/Rendering/SceneRenderer/GetPipeline.md +++ /dev/null @@ -1,35 +0,0 @@ -# SceneRenderer::GetPipeline - -返回当前场景渲染器正在使用的 runtime pipeline。 - -```cpp -RenderPipeline* GetPipeline() const; -``` - -## 返回值 - -- 返回内部 `m_cameraRenderer.GetPipeline()` 的结果。 - -## 当前语义 - -- 这是一个非拥有裸指针,只用于观察当前绑定的是哪一个 `RenderPipeline` 实例。 -- 该指针可能指向: - - 手动注入的 runtime pipeline。 - - 由当前 pipeline asset 创建出的 runtime pipeline。 - - 在空指针回退路径中自动补出的默认 builtin forward pipeline。 -- 调用 [SetPipeline](SetPipeline.md) 或 [SetPipelineAsset](SetPipelineAsset.md) 后,这个指针都可能变化。 - -## 调用方影响 - -- 不应跨生命周期长期缓存这个指针;`SceneRenderer` 销毁、替换 pipeline 或回退默认 pipeline 后,它都可能失效。 -- 它也不是“可由外部 delete 的所有权句柄”;真正的持有者仍是内部 `CameraRenderer`。 - -## 测试覆盖 - -- `tests/Rendering/unit/test_camera_scene_renderer.cpp` 覆盖了替换 pipeline 前后 `GetPipeline()` 返回值的变化,以及旧 pipeline 被 `Shutdown()` 的行为。 - -## 相关文档 - -- [SceneRenderer](SceneRenderer.md) -- [SetPipeline](SetPipeline.md) -- [GetPipelineAsset](GetPipelineAsset.md) diff --git a/docs/api/XCEngine/Rendering/SceneRenderer/GetPipelineAsset.md b/docs/api/XCEngine/Rendering/SceneRenderer/GetPipelineAsset.md deleted file mode 100644 index 140ab654..00000000 --- a/docs/api/XCEngine/Rendering/SceneRenderer/GetPipelineAsset.md +++ /dev/null @@ -1,33 +0,0 @@ -# SceneRenderer::GetPipelineAsset - -返回当前场景渲染器绑定的 pipeline asset。 - -```cpp -const RenderPipelineAsset* GetPipelineAsset() const; -``` - -## 返回值 - -- 返回内部 `m_cameraRenderer.GetPipelineAsset()` 的结果。 - -## 当前语义 - -- 这是一个非拥有观察指针,用来回答“当前 runtime pipeline 是由哪个 asset 工厂派生出来的”。 -- 默认构造的 `SceneRenderer` 通常会返回默认 builtin forward pipeline asset。 -- 如果最近一次切换是 [SetPipeline](SetPipeline.md) 且传入了非空 runtime pipeline,这里会变成 `nullptr`,因为那条路径会清掉当前 asset 绑定。 -- 如果 [SetPipelineAsset](SetPipelineAsset.md) 传入空指针,内部不会保留 `nullptr`,而是回退到默认 builtin forward asset。 - -## 调用方影响 - -- 这不是 `shared_ptr` 的外露别名,不提供所有权,也不保证在 `SceneRenderer` 生命周期之外继续有效。 -- 对外部来说,它更适合做调试、状态展示或测试断言,而不适合做长期缓存键。 - -## 测试覆盖 - -- `tests/Rendering/unit/test_camera_scene_renderer.cpp` 覆盖了默认 asset、替换 asset,以及替换 runtime pipeline 后 asset 观察值的变化。 - -## 相关文档 - -- [SceneRenderer](SceneRenderer.md) -- [SetPipelineAsset](SetPipelineAsset.md) -- [GetPipeline](GetPipeline.md) diff --git a/docs/api/XCEngine/Rendering/SceneRenderer/Render.md b/docs/api/XCEngine/Rendering/SceneRenderer/Render.md deleted file mode 100644 index 4ad1f50c..00000000 --- a/docs/api/XCEngine/Rendering/SceneRenderer/Render.md +++ /dev/null @@ -1,63 +0,0 @@ -# SceneRenderer::Render - -```cpp -bool Render(const CameraRenderRequest& request); -bool Render(const std::vector& requests); -bool Render( - const Components::Scene& scene, - Components::CameraComponent* overrideCamera, - const RenderContext& context, - const RenderSurface& surface); -``` - -## 行为说明 - -`SceneRenderer` 当前提供三种提交入口。 - -### `Render(const CameraRenderRequest&)` - -这是最薄的转发层,直接调用: - -```cpp -m_cameraRenderer.Render(request) -``` - -### `Render(const std::vector&)` - -当前实现会: - -1. 拒绝空数组。 -2. 检查每个请求都必须 `IsValid()`。 -3. 复制一份数组并调用 `SceneRenderRequestUtils::SortCameraRenderRequests(...)` 做稳定排序。 -4. 逐个转发给 `m_cameraRenderer.Render(request)`。 -5. 任一请求失败即返回 `false`。 - -### `Render(const Components::Scene&, ...)` - -这是便捷入口,等价于: - -```cpp -return Render(BuildRenderRequests(scene, overrideCamera, context, surface)); -``` - -因此它本身不直接做 scene extraction;真正的 `RenderSceneExtractor` 调用发生在 `CameraRenderer::Render(...)` 内部。 - -## 返回值 - -- 所有需要执行的请求都成功完成时返回 `true`。 -- 请求数组为空、任一请求无效,或任一请求执行失败时返回 `false`。 - -## 测试覆盖 - -`tests/Rendering/unit/test_camera_scene_renderer.cpp` 当前覆盖了: - -- 多相机请求的排序与稳定性。 -- `Render(scene, ...)` 的请求构建与提交链路。 -- 手工提交请求数组时的排序规则。 - -## 相关文档 - -- [SceneRenderer](SceneRenderer.md) -- [BuildRenderRequests](BuildRenderRequests.md) -- [CameraRenderer](../CameraRenderer/CameraRenderer.md) -- [SceneRenderRequestUtils](../SceneRenderRequestUtils/SceneRenderRequestUtils.md) diff --git a/docs/api/XCEngine/Rendering/SceneRenderer/SceneRenderer.md b/docs/api/XCEngine/Rendering/SceneRenderer/SceneRenderer.md deleted file mode 100644 index 0c5c5255..00000000 --- a/docs/api/XCEngine/Rendering/SceneRenderer/SceneRenderer.md +++ /dev/null @@ -1,119 +0,0 @@ -# SceneRenderer - -**命名空间**: `XCEngine::Rendering` - -**类型**: `class` - -**头文件**: `XCEngine/Rendering/Execution/SceneRenderer.h` - -**描述**: 场景级渲染编排入口。它负责构建和排序 `CameraRenderRequest`,再把每个请求转交给 [CameraRenderer](../CameraRenderer/CameraRenderer.md) 执行。 - -## 概览 - -`SceneRenderer` 当前不再自己持有 `RenderSceneExtractor` 或直接驱动主管线。它只维护两块运行时对象: - -- `m_requestPlanner`:负责从 `Scene` 和可选 override camera 生成 `CameraRenderRequest` 列表。 -- `m_cameraRenderer`:负责真正执行单个相机请求,包括场景提取、主管线提交、object-id pass,以及 request 上挂接的 pre/post/overlay pass sequence。 - -这意味着当前的主链路已经拆成两层: - -1. `SceneRenderer` 负责“这次要渲染哪些相机、按什么顺序、每个请求的 surface/render-area/clear-flags 是什么”。 -2. `CameraRenderer` 负责“单个请求如何真正跑完”。 - -这条边界是当前 Rendering 模块里最值得保留的设计决定之一。 -如果把相机规划和 request 执行重新揉回一个类里,Editor 的 Scene View、object-id picking、overlay 注入和离屏渲染很快就会和主管线细节耦合到一起。 - -现在的拆法则让 `SceneRenderer` 更像一个稳定的编排入口: - -- 从 `Scene` 直接提交时,它负责生成 request -- 从外部手工提交 request 时,它负责复用同一排序规则 -- 不管 request 来源是什么,真正执行都统一落到 `CameraRenderer` - -## 当前执行路径 - -### `BuildRenderRequests(...)` - -直接委托给 [SceneRenderRequestPlanner](../SceneRenderRequestPlanner/SceneRenderRequestPlanner.md): - -```cpp -return m_requestPlanner.BuildRequests(scene, overrideCamera, context, surface); -``` - -### `Render(const Components::Scene&, ...)` - -这是“从场景直接提交”的便捷入口,本质上等价于: - -```cpp -return Render(BuildRenderRequests(scene, overrideCamera, context, surface)); -``` - -### `Render(const std::vector&)` - -当前会: - -1. 拒绝空请求数组。 -2. 拒绝任何 `IsValid()` 为假的请求。 -3. 复制一份请求数组,并通过 `SceneRenderRequestUtils::SortCameraRenderRequests(...)` 做稳定排序。 -4. 依次调用 `m_cameraRenderer.Render(request)`。 -5. 任一请求失败就立即返回 `false`。 - -这里不会额外改写 request 里的 `objectId` 或各类 pass sequence;如果上层已经补好了这些字段,`SceneRenderer` 只负责排序后转交执行。 - -这也是为什么手工 request 提交路径仍然会再次排序。当前实现明确认为“stack/depth 规则”属于 `SceneRenderer` 的稳定契约,而不是调用方各自维护的一套隐式约定。 - -### `Render(const CameraRenderRequest&)` - -这是最薄的一层,直接转发到 `m_cameraRenderer.Render(request)`。 - -## 构造与默认行为 - -- 默认构造通过默认构造的 `CameraRenderer` 获得默认主管线与默认 object-id pass。 -- 传入 `std::unique_ptr` 时,走“手动注入 runtime pipeline”路径。 -- 传入 `std::shared_ptr` 时,走“由 asset 创建 runtime pipeline”路径。 - -默认主管线的具体创建不在 `SceneRenderer` 内部硬编码,而是由 `CameraRenderer` 通过默认 [BuiltinForwardPipelineAsset](../Pipelines/BuiltinForwardPipelineAsset/BuiltinForwardPipelineAsset.md) 负责。 - -## 当前实现边界 - -- 不直接做 scene extraction;真正的 `RenderSceneExtractor` 调用发生在 `CameraRenderer` 里。 -- 不直接操作 `RenderPipeline` 的初始化或渲染细节;它只负责请求规划和排序。 -- 不负责替 request 自动补 `objectId` 或各类 `RenderPassSequence`;这类附加阶段通常由 Editor 或其他上层调用方在提交前写入。 -- 对手工提交的请求数组,会再次按相机优先级稳定排序,而不是按调用方原始顺序盲目执行。 - -## 测试覆盖 - -`tests/Rendering/unit/test_camera_scene_renderer.cpp` 当前验证了: - -- `BuildRenderRequests()` 的多相机排序、override camera、clear-flags 和 viewport/render-area 解析。 -- 手工提交请求数组时的排序和稳定性。 -- `SetPipeline()` / `SetPipelineAsset()` 的转发、替换与 shutdown 行为。 - -这些测试锚点说明,`SceneRenderer` 当前不是一个“薄到没有语义”的转发器。它真正承担了 request planning 契约本身,包括: - -- override camera 的回退语义 -- base / overlay 排序 -- `Auto` clear 的默认策略 -- 手工 request 重新排序的稳定性 - -## 公开方法 - -| 方法 | 说明 | -|------|------| -| [Constructor](Constructor.md) | 构造 `SceneRenderer`,并决定 `CameraRenderer` 的主管线路径。 | -| [Destructor](Destructor.md) | 默认析构;真正的运行时清理由成员对象完成。 | -| [SetPipeline](SetPipeline.md) | 手动替换当前 runtime `RenderPipeline`。 | -| [SetPipelineAsset](SetPipelineAsset.md) | 通过 `RenderPipelineAsset` 重建当前 runtime `RenderPipeline`。 | -| [GetPipeline](GetPipeline.md) | 返回当前主管线的非拥有指针。 | -| [GetPipelineAsset](GetPipelineAsset.md) | 返回当前 pipeline asset 的非拥有指针。 | -| [BuildRenderRequests](BuildRenderRequests.md) | 生成当前场景对应的相机请求列表。 | -| [Render](Render.md) | 执行单个请求、请求数组,或从场景直接生成请求并执行。 | - -## 相关文档 - -- [Rendering](../Rendering.md) -- [CameraRenderer](../CameraRenderer/CameraRenderer.md) -- [SceneRenderRequestPlanner](../SceneRenderRequestPlanner/SceneRenderRequestPlanner.md) -- [SceneRenderRequestUtils](../SceneRenderRequestUtils/SceneRenderRequestUtils.md) -- [RenderSceneExtractor](../RenderSceneExtractor/RenderSceneExtractor.md) -- [Camera Request Planning And Clear Rules](../../../_guides/Rendering/Camera-Request-Planning-And-Clear-Rules.md) - diff --git a/docs/api/XCEngine/Rendering/SceneRenderer/SetPipeline.md b/docs/api/XCEngine/Rendering/SceneRenderer/SetPipeline.md deleted file mode 100644 index 205da9db..00000000 --- a/docs/api/XCEngine/Rendering/SceneRenderer/SetPipeline.md +++ /dev/null @@ -1,32 +0,0 @@ -# SceneRenderer::SetPipeline - -```cpp -void SetPipeline(std::unique_ptr pipeline); -``` - -## 行为说明 - -当前实现只是把调用转发给内部 `m_cameraRenderer`: - -```cpp -m_cameraRenderer.SetPipeline(std::move(pipeline)); -``` - -## 当前语义 - -沿用 `CameraRenderer::SetPipeline()` 的规则: - -- 当前绑定的 `RenderPipelineAsset` 会被清空。 -- 被替换掉的旧 runtime pipeline 会先执行 `Shutdown()`。 -- 如果新传入的 `pipeline` 为空,内部会回退到默认 pipeline asset,并重新创建默认主管线。 - -## 参数 - -- `pipeline` - 新的 runtime pipeline 所有权,可为空。 - -## 相关文档 - -- [SceneRenderer](SceneRenderer.md) -- [SetPipelineAsset](SetPipelineAsset.md) -- [GetPipeline](GetPipeline.md) -- [CameraRenderer::SetPipeline](../CameraRenderer/SetPipeline.md) diff --git a/docs/api/XCEngine/Rendering/SceneRenderer/SetPipelineAsset.md b/docs/api/XCEngine/Rendering/SceneRenderer/SetPipelineAsset.md deleted file mode 100644 index c6b4f531..00000000 --- a/docs/api/XCEngine/Rendering/SceneRenderer/SetPipelineAsset.md +++ /dev/null @@ -1,34 +0,0 @@ -# SceneRenderer::SetPipelineAsset - -切换当前场景渲染器使用的 pipeline asset。 - -```cpp -void SetPipelineAsset(std::shared_ptr pipelineAsset); -``` - -## 参数 - -- `pipelineAsset` - 目标 pipeline asset;允许传入空指针。 - -## 当前语义 - -- 当前实现直接转发到内部 `m_cameraRenderer.SetPipelineAsset(...)`。 -- 如果传入 asset 非空,后续 runtime pipeline 会由该 asset 创建。 -- 如果传入 asset 为空,内部会回退到默认 builtin forward pipeline asset,而不是保留空绑定。 -- 被替换掉的旧 runtime pipeline 会先执行 `Shutdown()`,再由新 asset 创建新实例。 - -## 调用方影响 - -- 调用之后,[GetPipelineAsset](GetPipelineAsset.md) 和 [GetPipeline](GetPipeline.md) 的返回值都可能变化。 -- 任何此前缓存的旧 pipeline 指针都应视为潜在失效。 -- 如果调用方只是想直接接管 runtime pipeline,而不是保留 asset 工厂关系,应改用 [SetPipeline](SetPipeline.md)。 - -## 测试覆盖 - -- `tests/Rendering/unit/test_camera_scene_renderer.cpp` 验证了替换 asset 时旧 pipeline 会被 `Shutdown()`,并且新 asset 会创建新的 runtime pipeline 实例。 - -## 相关文档 - -- [SceneRenderer](SceneRenderer.md) -- [SetPipeline](SetPipeline.md) -- [GetPipelineAsset](GetPipelineAsset.md) diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index b4c6e8bb..6711c9b8 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -6,64 +6,101 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) file(TO_CMAKE_PATH "${CMAKE_SOURCE_DIR}" XCUIEDITOR_REPO_ROOT_PATH) -set(XCUI_EDITOR_RESOURCE_FILES - ui/views/editor_shell.xcui - ui/themes/editor_shell.xctheme +function(xcui_editor_apply_common_target_settings target visibility) + target_compile_definitions(${target} ${visibility} + UNICODE + _UNICODE + ) + + if(MSVC) + target_compile_options(${target} PRIVATE /utf-8 /FS) + set_property(TARGET ${target} PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") + endif() +endfunction() + +set(XCUI_EDITOR_FOUNDATION_SOURCES + src/Foundation/UIEditorCommandDispatcher.cpp + src/Foundation/UIEditorCommandRegistry.cpp + src/Foundation/UIEditorShortcutManager.cpp + src/Foundation/UIEditorTheme.cpp +) + +set(XCUI_EDITOR_FIELD_SOURCES + src/Fields/UIEditorAssetField.cpp + src/Fields/UIEditorAssetFieldInteraction.cpp + src/Fields/UIEditorBoolField.cpp + src/Fields/UIEditorBoolFieldInteraction.cpp + src/Fields/UIEditorColorField.cpp + src/Fields/UIEditorColorFieldInteraction.cpp + src/Fields/UIEditorEnumField.cpp + src/Fields/UIEditorEnumFieldInteraction.cpp + src/Fields/UIEditorFieldStyle.cpp + src/Fields/UIEditorNumberField.cpp + src/Fields/UIEditorNumberFieldInteraction.cpp + src/Fields/UIEditorObjectField.cpp + src/Fields/UIEditorObjectFieldInteraction.cpp + src/Fields/UIEditorPropertyGrid.cpp + src/Fields/UIEditorPropertyGridInteraction.cpp + src/Fields/UIEditorTextField.cpp + src/Fields/UIEditorTextFieldInteraction.cpp + src/Fields/UIEditorVector2Field.cpp + src/Fields/UIEditorVector2FieldInteraction.cpp + src/Fields/UIEditorVector3Field.cpp + src/Fields/UIEditorVector3FieldInteraction.cpp + src/Fields/UIEditorVector4Field.cpp + src/Fields/UIEditorVector4FieldInteraction.cpp +) + +set(XCUI_EDITOR_COLLECTION_SOURCES + src/Collections/UIEditorInlineRenameSession.cpp + src/Collections/UIEditorListView.cpp + src/Collections/UIEditorListViewInteraction.cpp + src/Collections/UIEditorScrollView.cpp + src/Collections/UIEditorScrollViewInteraction.cpp + src/Collections/UIEditorTabStrip.cpp + src/Collections/UIEditorTabStripInteraction.cpp + src/Collections/UIEditorTreeView.cpp + src/Collections/UIEditorTreeViewInteraction.cpp +) + +set(XCUI_EDITOR_SHELL_SOURCES + src/Shell/EditorShellAsset.cpp + src/Shell/UIEditorDockHost.cpp + src/Shell/UIEditorDockHostInteraction.cpp + src/Shell/UIEditorMenuBar.cpp + src/Shell/UIEditorMenuModel.cpp + src/Shell/UIEditorMenuPopup.cpp + src/Shell/UIEditorMenuSession.cpp + src/Shell/UIEditorPanelContentHost.cpp + src/Shell/UIEditorPanelFrame.cpp + src/Shell/UIEditorPanelHostLifecycle.cpp + src/Shell/UIEditorPanelRegistry.cpp + src/Shell/UIEditorShellCompose.cpp + src/Shell/UIEditorShellInteraction.cpp + src/Shell/UIEditorStatusBar.cpp + src/Shell/UIEditorViewportInputBridge.cpp + src/Shell/UIEditorViewportShell.cpp + src/Shell/UIEditorViewportSlot.cpp + src/Shell/UIEditorWorkspaceCompose.cpp + src/Shell/UIEditorWorkspaceController.cpp + src/Shell/UIEditorWorkspaceInteraction.cpp + src/Shell/UIEditorWorkspaceLayoutPersistence.cpp + src/Shell/UIEditorWorkspaceModel.cpp + src/Shell/UIEditorWorkspaceSession.cpp +) + +set(XCUI_EDITOR_WIDGET_SUPPORT_SOURCES + src/Widgets/UIEditorCollectionPrimitives.cpp + src/Widgets/UIEditorFieldRowLayout.cpp ) add_library(XCUIEditorLib STATIC - src/Core/EditorShellAsset.cpp - src/Core/UIEditorCommandDispatcher.cpp - src/Core/UIEditorCommandRegistry.cpp - src/Core/UIEditorBoolFieldInteraction.cpp - src/Core/UIEditorDockHostInteraction.cpp - src/Core/UIEditorEnumFieldInteraction.cpp - src/Core/UIEditorListViewInteraction.cpp - src/Core/UIEditorMenuModel.cpp - src/Core/UIEditorMenuSession.cpp - src/Core/UIEditorNumberFieldInteraction.cpp - src/Core/UIEditorPanelContentHost.cpp - src/Core/UIEditorPanelHostLifecycle.cpp - src/Core/UIEditorPanelRegistry.cpp - src/Core/UIEditorPropertyGridInteraction.cpp - src/Core/UIEditorScrollViewInteraction.cpp - src/Core/UIEditorShellCompose.cpp - src/Core/UIEditorShellInteraction.cpp - src/Core/UIEditorShortcutManager.cpp - src/Core/UIEditorTextFieldInteraction.cpp - src/Core/UIEditorTheme.cpp - src/Core/UIEditorTreeViewInteraction.cpp - src/Core/UIEditorVector2FieldInteraction.cpp - src/Core/UIEditorVector3FieldInteraction.cpp - src/Core/UIEditorVector4FieldInteraction.cpp - src/Core/UIEditorViewportInputBridge.cpp - src/Core/UIEditorViewportShell.cpp - src/Core/UIEditorWorkspaceCompose.cpp - src/Core/UIEditorWorkspaceInteraction.cpp - src/Core/UIEditorWorkspaceLayoutPersistence.cpp - src/Core/UIEditorWorkspaceController.cpp - src/Core/UIEditorWorkspaceModel.cpp - src/Core/UIEditorWorkspaceSession.cpp - src/Widgets/UIEditorCollectionPrimitives.cpp - src/Widgets/UIEditorFieldRowLayout.cpp - src/Widgets/UIEditorBoolField.cpp - src/Widgets/UIEditorDockHost.cpp - src/Widgets/UIEditorEnumField.cpp - src/Widgets/UIEditorListView.cpp - src/Widgets/UIEditorMenuBar.cpp - src/Widgets/UIEditorMenuPopup.cpp - src/Widgets/UIEditorNumberField.cpp - src/Widgets/UIEditorPanelFrame.cpp - src/Widgets/UIEditorPropertyGrid.cpp - src/Widgets/UIEditorScrollView.cpp - src/Widgets/UIEditorStatusBar.cpp - src/Widgets/UIEditorTabStrip.cpp - src/Widgets/UIEditorTextField.cpp - src/Widgets/UIEditorTreeView.cpp - src/Widgets/UIEditorVector2Field.cpp - src/Widgets/UIEditorVector3Field.cpp - src/Widgets/UIEditorVector4Field.cpp - src/Widgets/UIEditorViewportSlot.cpp + ${XCUI_EDITOR_FOUNDATION_SOURCES} + ${XCUI_EDITOR_FIELD_SOURCES} + ${XCUI_EDITOR_COLLECTION_SOURCES} + ${XCUI_EDITOR_SHELL_SOURCES} + ${XCUI_EDITOR_WIDGET_SUPPORT_SOURCES} ) target_include_directories(XCUIEditorLib @@ -75,16 +112,10 @@ target_include_directories(XCUIEditorLib ) target_compile_definitions(XCUIEditorLib PUBLIC - UNICODE - _UNICODE XCUIEDITOR_REPO_ROOT="${XCUIEDITOR_REPO_ROOT_PATH}" ) -if(MSVC) - target_compile_options(XCUIEditorLib PRIVATE /utf-8 /FS) - set_property(TARGET XCUIEditorLib PROPERTY - MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") -endif() +xcui_editor_apply_common_target_settings(XCUIEditorLib PUBLIC) target_link_libraries(XCUIEditorLib PUBLIC XCEngine @@ -99,21 +130,9 @@ target_include_directories(XCUIEditorHost PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/app ${CMAKE_SOURCE_DIR}/engine/include - PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/include - ${CMAKE_CURRENT_SOURCE_DIR}/src ) -target_compile_definitions(XCUIEditorHost PUBLIC - UNICODE - _UNICODE -) - -if(MSVC) - target_compile_options(XCUIEditorHost PRIVATE /utf-8 /FS) - set_property(TARGET XCUIEditorHost PROPERTY - MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") -endif() +xcui_editor_apply_common_target_settings(XCUIEditorHost PUBLIC) target_link_libraries(XCUIEditorHost PUBLIC XCEngine @@ -126,27 +145,18 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) add_executable(XCUIEditorApp WIN32 app/main.cpp app/Application.cpp - ${XCUI_EDITOR_RESOURCE_FILES} ) target_include_directories(XCUIEditorApp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/app - ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src - ${CMAKE_SOURCE_DIR}/engine/include ) target_compile_definitions(XCUIEditorApp PRIVATE - UNICODE - _UNICODE XCUIEDITOR_REPO_ROOT="${XCUIEDITOR_REPO_ROOT_PATH}" ) - if(MSVC) - target_compile_options(XCUIEditorApp PRIVATE /utf-8 /FS) - set_property(TARGET XCUIEditorApp PROPERTY - MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") - endif() + xcui_editor_apply_common_target_settings(XCUIEditorApp PRIVATE) target_link_libraries(XCUIEditorApp PRIVATE XCUIEditorLib @@ -158,5 +168,3 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin" ) endif() - -source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${XCUI_EDITOR_RESOURCE_FILES}) diff --git a/new_editor/app/Application.cpp b/new_editor/app/Application.cpp index d6646438..3f162cd1 100644 --- a/new_editor/app/Application.cpp +++ b/new_editor/app/Application.cpp @@ -292,7 +292,8 @@ void Application::RenderFrame() { m_pendingInputEvents.clear(); UIDrawData drawData = {}; - if (m_useStructuredScreen && m_screenPlayer.IsLoaded()) { + const bool hasAuthoredScreenDocument = !m_screenAsset.documentPath.empty(); + if (hasAuthoredScreenDocument && m_useStructuredScreen && m_screenPlayer.IsLoaded()) { UIScreenFrameInput input = {}; input.viewportRect = UIRect(0.0f, 0.0f, width, height); input.events = std::move(frameEvents); @@ -307,6 +308,10 @@ void Application::RenderFrame() { m_runtimeStatus = "XCUI Editor Shell"; m_runtimeError = frame.errorMessage; + } else if (!hasAuthoredScreenDocument) { + ++m_frameIndex; + m_runtimeStatus = "XCUI Editor Shell | Code-driven"; + m_runtimeError.clear(); } else { m_runtimeStatus = "Editor Shell | Load Error"; if (m_runtimeError.empty() && !m_screenPlayer.IsLoaded()) { @@ -401,14 +406,19 @@ void Application::QueueWindowFocusEvent(UIInputEventType type) { bool Application::LoadStructuredScreen(const char* triggerReason) { (void)triggerReason; m_screenAsset = m_structuredShell.screenAsset; - const bool loaded = m_screenPlayer.Load(m_screenAsset); const EditorShellAssetValidationResult& shellAssetValidation = m_structuredShell.assetValidation; const auto shortcutValidation = m_structuredShell.shortcutManager.ValidateConfiguration(); - m_useStructuredScreen = loaded; - m_runtimeStatus = loaded ? "XCUI Editor Shell" : "Editor Shell | Load Error"; + const bool hasAuthoredScreenDocument = !m_screenAsset.documentPath.empty(); + const bool loaded = + hasAuthoredScreenDocument ? m_screenPlayer.Load(m_screenAsset) : shellAssetValidation.IsValid(); + m_useStructuredScreen = hasAuthoredScreenDocument && loaded; + m_runtimeStatus = + hasAuthoredScreenDocument + ? (loaded ? "XCUI Editor Shell" : "Editor Shell | Load Error") + : "XCUI Editor Shell | Code-driven"; m_runtimeError.clear(); - if (!loaded) { + if (hasAuthoredScreenDocument && !loaded) { AppendErrorMessage(m_runtimeError, m_screenPlayer.GetLastError()); } if (!shellAssetValidation.IsValid()) { @@ -468,7 +478,6 @@ void Application::RebuildTrackedFileStates() { }; appendTrackedPath(m_screenAsset.documentPath); - appendTrackedPath(m_screenAsset.themePath); if (const auto* document = m_screenPlayer.GetDocument(); document != nullptr) { for (const std::string& dependency : document->dependencies) { @@ -503,14 +512,18 @@ bool Application::DetectTrackedFileChange() const { } void Application::AppendRuntimeOverlay(UIDrawData& drawData, float width, float height) const { - const bool authoredMode = m_useStructuredScreen && m_screenPlayer.IsLoaded(); + const bool authoredMode = + !m_screenAsset.documentPath.empty() && m_useStructuredScreen && m_screenPlayer.IsLoaded(); const float panelWidth = authoredMode ? 430.0f : 380.0f; std::vector detailLines = {}; detailLines.push_back( authoredMode - ? "Hot reload watches editor shell resources." - : "Authored editor shell failed to load."); - detailLines.push_back("Document: editor_shell.xcui"); + ? "Hot reload watches editor shell document and dependencies." + : "Editor shell is composed directly from fixed code definitions."); + detailLines.push_back( + authoredMode + ? "Document: " + std::filesystem::path(m_screenAsset.documentPath).filename().string() + : "Document: (none)"); if (authoredMode) { const auto& inputDebug = m_documentHost.GetInputDebugSnapshot(); @@ -560,7 +573,7 @@ void Application::AppendRuntimeOverlay(UIDrawData& drawData, float width, float } else if (!m_autoScreenshot.GetLastCaptureError().empty()) { detailLines.push_back(TruncateText(m_autoScreenshot.GetLastCaptureError(), 78u)); } else if (!authoredMode) { - detailLines.push_back("No fallback sandbox is rendered in this host."); + detailLines.push_back("No authored XCUI document is used by the editor shell host."); } const float panelHeight = 38.0f + static_cast(detailLines.size()) * 18.0f; diff --git a/new_editor/app/Application.h b/new_editor/app/Application.h index 7f76caef..46c68178 100644 --- a/new_editor/app/Application.h +++ b/new_editor/app/Application.h @@ -8,11 +8,11 @@ #include "Host/InputModifierTracker.h" #include "Host/NativeRenderer.h" -#include "Core/EditorShellAsset.h" +#include "Shell/EditorShellAsset.h" -#include -#include -#include +#include +#include +#include #include #include @@ -50,7 +50,6 @@ inline StructuredEditorShellBinding BuildStructuredEditorShellBinding( StructuredEditorShellBinding binding = {}; binding.screenAsset.screenId = asset.screenId; binding.screenAsset.documentPath = asset.documentPath.string(); - binding.screenAsset.themePath = asset.themePath.string(); binding.workspaceController = UIEditorWorkspaceController(asset.panelRegistry, asset.workspace, asset.workspaceSession); binding.shellDefinition = asset.shellDefinition; diff --git a/new_editor/app/Host/AutoScreenshot.cpp b/new_editor/app/Host/AutoScreenshot.cpp index bcf4cefa..54936063 100644 --- a/new_editor/app/Host/AutoScreenshot.cpp +++ b/new_editor/app/Host/AutoScreenshot.cpp @@ -8,6 +8,9 @@ #include #include #include +#include + +#include namespace XCEngine::UI::Editor::Host { @@ -30,16 +33,47 @@ bool IsAutoCaptureOnStartupEnabled() { normalized != "no"; } +std::filesystem::path GetExecutableDirectory() { + std::vector buffer(MAX_PATH); + while (true) { + const DWORD copied = ::GetModuleFileNameW( + nullptr, + buffer.data(), + static_cast(buffer.size())); + if (copied == 0u) { + return std::filesystem::current_path().lexically_normal(); + } + + if (copied < buffer.size() - 1u) { + return std::filesystem::path(std::wstring(buffer.data(), copied)) + .parent_path() + .lexically_normal(); + } + + buffer.resize(buffer.size() * 2u); + } +} + +std::filesystem::path ResolveBuildCaptureRoot(const std::filesystem::path& requestedCaptureRoot) { + std::filesystem::path captureRoot = GetExecutableDirectory() / "captures"; + const std::filesystem::path scenarioPath = requestedCaptureRoot.parent_path().filename(); + if (!scenarioPath.empty() && scenarioPath != "captures") { + captureRoot /= scenarioPath; + } + + return captureRoot.lexically_normal(); +} + } // namespace void AutoScreenshotController::Initialize(const std::filesystem::path& captureRoot) { - m_captureRoot = captureRoot.lexically_normal(); + m_captureRoot = ResolveBuildCaptureRoot(captureRoot); m_historyRoot = (m_captureRoot / "history").lexically_normal(); m_latestCapturePath = (m_captureRoot / "latest.png").lexically_normal(); m_captureCount = 0; m_capturePending = false; m_pendingReason.clear(); - m_lastCaptureSummary.clear(); + m_lastCaptureSummary = "Output: " + m_captureRoot.string(); m_lastCaptureError.clear(); if (IsAutoCaptureOnStartupEnabled()) { RequestCapture("startup"); diff --git a/new_editor/include/XCEditor/Collections/UIEditorInlineRenameSession.h b/new_editor/include/XCEditor/Collections/UIEditorInlineRenameSession.h new file mode 100644 index 00000000..93846975 --- /dev/null +++ b/new_editor/include/XCEditor/Collections/UIEditorInlineRenameSession.h @@ -0,0 +1,54 @@ +#pragma once + +#include + +#include + +#include +#include + +namespace XCEngine::UI::Editor { + +struct UIEditorInlineRenameSessionState { + bool active = false; + std::string itemId = {}; + Widgets::UIEditorTextFieldSpec textFieldSpec = {}; + UIEditorTextFieldInteractionState textFieldInteraction = {}; +}; + +struct UIEditorInlineRenameSessionRequest { + bool beginSession = false; + std::string itemId = {}; + std::string initialText = {}; + ::XCEngine::UI::UIRect bounds = {}; +}; + +struct UIEditorInlineRenameSessionResult { + bool consumed = false; + bool sessionStarted = false; + bool sessionCommitted = false; + bool sessionCanceled = false; + bool valueChanged = false; + bool active = false; + std::string itemId = {}; + std::string valueBefore = {}; + std::string valueAfter = {}; + UIEditorTextFieldInteractionResult textFieldResult = {}; +}; + +struct UIEditorInlineRenameSessionFrame { + Widgets::UIEditorTextFieldLayout layout = {}; + UIEditorInlineRenameSessionResult result = {}; +}; + +Widgets::UIEditorTextFieldMetrics BuildUIEditorInlineRenameTextFieldMetrics( + const ::XCEngine::UI::UIRect& bounds, + const Widgets::UIEditorTextFieldMetrics& metrics = {}); + +UIEditorInlineRenameSessionFrame UpdateUIEditorInlineRenameSession( + UIEditorInlineRenameSessionState& state, + const UIEditorInlineRenameSessionRequest& request, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + const Widgets::UIEditorTextFieldMetrics& metrics = {}); + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Widgets/UIEditorListView.h b/new_editor/include/XCEditor/Collections/UIEditorListView.h similarity index 100% rename from new_editor/include/XCEditor/Widgets/UIEditorListView.h rename to new_editor/include/XCEditor/Collections/UIEditorListView.h diff --git a/new_editor/include/XCEditor/Core/UIEditorListViewInteraction.h b/new_editor/include/XCEditor/Collections/UIEditorListViewInteraction.h similarity index 89% rename from new_editor/include/XCEditor/Core/UIEditorListViewInteraction.h rename to new_editor/include/XCEditor/Collections/UIEditorListViewInteraction.h index 7b749b67..2839edea 100644 --- a/new_editor/include/XCEditor/Core/UIEditorListViewInteraction.h +++ b/new_editor/include/XCEditor/Collections/UIEditorListViewInteraction.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -14,6 +14,7 @@ namespace XCEngine::UI::Editor { struct UIEditorListViewInteractionState { Widgets::UIEditorListViewState listViewState = {}; ::XCEngine::UI::Widgets::UIKeyboardNavigationModel keyboardNavigation = {}; + std::string selectionAnchorId = {}; ::XCEngine::UI::UIPoint pointerPosition = {}; bool hasPointerPosition = false; }; @@ -23,8 +24,10 @@ struct UIEditorListViewInteractionResult { bool selectionChanged = false; bool keyboardNavigated = false; bool secondaryClicked = false; + bool renameRequested = false; Widgets::UIEditorListViewHitTarget hitTarget = {}; std::string selectedItemId = {}; + std::string renameItemId = {}; std::size_t selectedIndex = Widgets::UIEditorListViewInvalidIndex; }; diff --git a/new_editor/include/XCEditor/Widgets/UIEditorScrollView.h b/new_editor/include/XCEditor/Collections/UIEditorScrollView.h similarity index 85% rename from new_editor/include/XCEditor/Widgets/UIEditorScrollView.h rename to new_editor/include/XCEditor/Collections/UIEditorScrollView.h index 8d80a696..35e2566d 100644 --- a/new_editor/include/XCEditor/Widgets/UIEditorScrollView.h +++ b/new_editor/include/XCEditor/Collections/UIEditorScrollView.h @@ -25,7 +25,7 @@ struct UIEditorScrollViewMetrics { float scrollbarWidth = 10.0f; float scrollbarInset = 4.0f; float minThumbHeight = 28.0f; - float cornerRounding = 6.0f; + float cornerRounding = 8.0f; float borderThickness = 1.0f; float focusedBorderThickness = 2.0f; float wheelStep = 48.0f; @@ -33,19 +33,19 @@ struct UIEditorScrollViewMetrics { struct UIEditorScrollViewPalette { ::XCEngine::UI::UIColor surfaceColor = - ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.16f, 1.0f); ::XCEngine::UI::UIColor borderColor = - ::XCEngine::UI::UIColor(0.29f, 0.29f, 0.29f, 1.0f); + ::XCEngine::UI::UIColor(0.30f, 0.32f, 0.34f, 1.0f); ::XCEngine::UI::UIColor focusedBorderColor = - ::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f); + ::XCEngine::UI::UIColor(0.78f, 0.80f, 0.84f, 1.0f); ::XCEngine::UI::UIColor scrollbarTrackColor = - ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.21f, 1.0f); ::XCEngine::UI::UIColor scrollbarThumbColor = - ::XCEngine::UI::UIColor(0.31f, 0.31f, 0.31f, 1.0f); + ::XCEngine::UI::UIColor(0.32f, 0.34f, 0.36f, 1.0f); ::XCEngine::UI::UIColor scrollbarThumbHoverColor = - ::XCEngine::UI::UIColor(0.38f, 0.38f, 0.38f, 1.0f); + ::XCEngine::UI::UIColor(0.42f, 0.44f, 0.47f, 1.0f); ::XCEngine::UI::UIColor scrollbarThumbActiveColor = - ::XCEngine::UI::UIColor(0.46f, 0.46f, 0.46f, 1.0f); + ::XCEngine::UI::UIColor(0.50f, 0.52f, 0.56f, 1.0f); }; struct UIEditorScrollViewLayout { diff --git a/new_editor/include/XCEditor/Core/UIEditorScrollViewInteraction.h b/new_editor/include/XCEditor/Collections/UIEditorScrollViewInteraction.h similarity index 95% rename from new_editor/include/XCEditor/Core/UIEditorScrollViewInteraction.h rename to new_editor/include/XCEditor/Collections/UIEditorScrollViewInteraction.h index 70623329..4efb614c 100644 --- a/new_editor/include/XCEditor/Core/UIEditorScrollViewInteraction.h +++ b/new_editor/include/XCEditor/Collections/UIEditorScrollViewInteraction.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include diff --git a/new_editor/include/XCEditor/Widgets/UIEditorTabStrip.h b/new_editor/include/XCEditor/Collections/UIEditorTabStrip.h similarity index 81% rename from new_editor/include/XCEditor/Widgets/UIEditorTabStrip.h rename to new_editor/include/XCEditor/Collections/UIEditorTabStrip.h index 0316d364..58a9aa46 100644 --- a/new_editor/include/XCEditor/Widgets/UIEditorTabStrip.h +++ b/new_editor/include/XCEditor/Collections/UIEditorTabStrip.h @@ -46,41 +46,41 @@ struct UIEditorTabStripMetrics { struct UIEditorTabStripPalette { ::XCEngine::UI::UIColor stripBackgroundColor = - ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.16f, 1.0f); ::XCEngine::UI::UIColor headerBackgroundColor = - ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f); + ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.19f, 1.0f); ::XCEngine::UI::UIColor contentBackgroundColor = - ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.16f, 1.0f); ::XCEngine::UI::UIColor stripBorderColor = - ::XCEngine::UI::UIColor(0.30f, 0.30f, 0.30f, 1.0f); + ::XCEngine::UI::UIColor(0.30f, 0.32f, 0.34f, 1.0f); ::XCEngine::UI::UIColor focusedBorderColor = - ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); + ::XCEngine::UI::UIColor(0.78f, 0.80f, 0.84f, 1.0f); ::XCEngine::UI::UIColor tabColor = - ::XCEngine::UI::UIColor(0.23f, 0.23f, 0.23f, 1.0f); + ::XCEngine::UI::UIColor(0.21f, 0.22f, 0.24f, 1.0f); ::XCEngine::UI::UIColor tabHoveredColor = - ::XCEngine::UI::UIColor(0.27f, 0.27f, 0.27f, 1.0f); + ::XCEngine::UI::UIColor(0.27f, 0.28f, 0.30f, 1.0f); ::XCEngine::UI::UIColor tabSelectedColor = - ::XCEngine::UI::UIColor(0.29f, 0.29f, 0.29f, 1.0f); + ::XCEngine::UI::UIColor(0.33f, 0.35f, 0.38f, 1.0f); ::XCEngine::UI::UIColor tabBorderColor = - ::XCEngine::UI::UIColor(0.32f, 0.32f, 0.32f, 1.0f); + ::XCEngine::UI::UIColor(0.32f, 0.34f, 0.36f, 1.0f); ::XCEngine::UI::UIColor tabHoveredBorderColor = - ::XCEngine::UI::UIColor(0.46f, 0.46f, 0.46f, 1.0f); + ::XCEngine::UI::UIColor(0.42f, 0.44f, 0.47f, 1.0f); ::XCEngine::UI::UIColor tabSelectedBorderColor = - ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); + ::XCEngine::UI::UIColor(0.50f, 0.52f, 0.56f, 1.0f); ::XCEngine::UI::UIColor textPrimary = - ::XCEngine::UI::UIColor(0.94f, 0.94f, 0.94f, 1.0f); + ::XCEngine::UI::UIColor(0.93f, 0.94f, 0.96f, 1.0f); ::XCEngine::UI::UIColor textSecondary = - ::XCEngine::UI::UIColor(0.76f, 0.76f, 0.76f, 1.0f); + ::XCEngine::UI::UIColor(0.70f, 0.72f, 0.74f, 1.0f); ::XCEngine::UI::UIColor textMuted = - ::XCEngine::UI::UIColor(0.60f, 0.60f, 0.60f, 1.0f); + ::XCEngine::UI::UIColor(0.58f, 0.59f, 0.62f, 1.0f); ::XCEngine::UI::UIColor closeButtonColor = - ::XCEngine::UI::UIColor(0.25f, 0.25f, 0.25f, 1.0f); + ::XCEngine::UI::UIColor(0.21f, 0.22f, 0.24f, 1.0f); ::XCEngine::UI::UIColor closeButtonHoveredColor = - ::XCEngine::UI::UIColor(0.36f, 0.36f, 0.36f, 1.0f); + ::XCEngine::UI::UIColor(0.27f, 0.28f, 0.30f, 1.0f); ::XCEngine::UI::UIColor closeButtonBorderColor = - ::XCEngine::UI::UIColor(0.44f, 0.44f, 0.44f, 1.0f); + ::XCEngine::UI::UIColor(0.42f, 0.44f, 0.47f, 1.0f); ::XCEngine::UI::UIColor closeGlyphColor = - ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); + ::XCEngine::UI::UIColor(0.93f, 0.94f, 0.96f, 1.0f); }; struct UIEditorTabStripLayout { diff --git a/new_editor/include/XCEditor/Collections/UIEditorTabStripInteraction.h b/new_editor/include/XCEditor/Collections/UIEditorTabStripInteraction.h new file mode 100644 index 00000000..1fffef3a --- /dev/null +++ b/new_editor/include/XCEditor/Collections/UIEditorTabStripInteraction.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include +#include + +#include +#include + +namespace XCEngine::UI::Editor { + +struct UIEditorTabStripInteractionState { + Widgets::UIEditorTabStripState tabStripState = {}; + ::XCEngine::UI::Widgets::UITabStripModel navigationModel = {}; + Widgets::UIEditorTabStripHitTarget pressedTarget = {}; + ::XCEngine::UI::UIPoint pointerPosition = {}; + bool hasPointerPosition = false; +}; + +struct UIEditorTabStripInteractionResult { + bool consumed = false; + bool selectionChanged = false; + bool closeRequested = false; + bool keyboardNavigated = false; + Widgets::UIEditorTabStripHitTarget hitTarget = {}; + std::string selectedTabId = {}; + std::size_t selectedIndex = Widgets::UIEditorTabStripInvalidIndex; + std::string closedTabId = {}; + std::size_t closedIndex = Widgets::UIEditorTabStripInvalidIndex; +}; + +struct UIEditorTabStripInteractionFrame { + Widgets::UIEditorTabStripLayout layout = {}; + UIEditorTabStripInteractionResult result = {}; + bool focused = false; +}; + +UIEditorTabStripInteractionFrame UpdateUIEditorTabStripInteraction( + UIEditorTabStripInteractionState& state, + std::string& selectedTabId, + const ::XCEngine::UI::UIRect& bounds, + const std::vector& items, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + const Widgets::UIEditorTabStripMetrics& metrics = {}); + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Widgets/UIEditorTreeView.h b/new_editor/include/XCEditor/Collections/UIEditorTreeView.h similarity index 100% rename from new_editor/include/XCEditor/Widgets/UIEditorTreeView.h rename to new_editor/include/XCEditor/Collections/UIEditorTreeView.h diff --git a/new_editor/include/XCEditor/Core/UIEditorTreeViewInteraction.h b/new_editor/include/XCEditor/Collections/UIEditorTreeViewInteraction.h similarity index 76% rename from new_editor/include/XCEditor/Core/UIEditorTreeViewInteraction.h rename to new_editor/include/XCEditor/Collections/UIEditorTreeViewInteraction.h index 07d5007f..034ab796 100644 --- a/new_editor/include/XCEditor/Core/UIEditorTreeViewInteraction.h +++ b/new_editor/include/XCEditor/Collections/UIEditorTreeViewInteraction.h @@ -1,9 +1,10 @@ #pragma once -#include +#include #include #include +#include #include #include @@ -13,6 +14,8 @@ namespace XCEngine::UI::Editor { struct UIEditorTreeViewInteractionState { Widgets::UIEditorTreeViewState treeViewState = {}; + ::XCEngine::UI::Widgets::UIKeyboardNavigationModel keyboardNavigation = {}; + std::string selectionAnchorId = {}; ::XCEngine::UI::UIPoint pointerPosition = {}; bool hasPointerPosition = false; }; @@ -21,10 +24,14 @@ struct UIEditorTreeViewInteractionResult { bool consumed = false; bool selectionChanged = false; bool expansionChanged = false; + bool keyboardNavigated = false; bool secondaryClicked = false; + bool renameRequested = false; Widgets::UIEditorTreeViewHitTarget hitTarget = {}; std::string selectedItemId = {}; + std::string renameItemId = {}; std::string toggledItemId = {}; + std::size_t selectedVisibleIndex = Widgets::UIEditorTreeViewInvalidIndex; }; struct UIEditorTreeViewInteractionFrame { diff --git a/new_editor/include/XCEditor/Core/UIEditorTheme.h b/new_editor/include/XCEditor/Core/UIEditorTheme.h deleted file mode 100644 index 281e52ae..00000000 --- a/new_editor/include/XCEditor/Core/UIEditorTheme.h +++ /dev/null @@ -1,157 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -namespace XCEngine::UI::Editor { - -float ResolveUIEditorThemeFloat( - const ::XCEngine::UI::Style::UITheme& theme, - std::string_view tokenName, - float fallbackValue); - -::XCEngine::UI::UIColor ResolveUIEditorThemeColor( - const ::XCEngine::UI::Style::UITheme& theme, - std::string_view tokenName, - const ::XCEngine::UI::UIColor& fallbackValue); - -Widgets::UIEditorBoolFieldMetrics ResolveUIEditorBoolFieldMetrics( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorBoolFieldMetrics& fallback = {}); - -Widgets::UIEditorBoolFieldPalette ResolveUIEditorBoolFieldPalette( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorBoolFieldPalette& fallback = {}); - -Widgets::UIEditorNumberFieldMetrics ResolveUIEditorNumberFieldMetrics( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorNumberFieldMetrics& fallback = {}); - -Widgets::UIEditorNumberFieldPalette ResolveUIEditorNumberFieldPalette( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorNumberFieldPalette& fallback = {}); - -Widgets::UIEditorTextFieldMetrics ResolveUIEditorTextFieldMetrics( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorTextFieldMetrics& fallback = {}); - -Widgets::UIEditorTextFieldPalette ResolveUIEditorTextFieldPalette( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorTextFieldPalette& fallback = {}); - -Widgets::UIEditorVector2FieldMetrics ResolveUIEditorVector2FieldMetrics( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorVector2FieldMetrics& fallback = {}); - -Widgets::UIEditorVector2FieldPalette ResolveUIEditorVector2FieldPalette( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorVector2FieldPalette& fallback = {}); - -Widgets::UIEditorVector3FieldMetrics ResolveUIEditorVector3FieldMetrics( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorVector3FieldMetrics& fallback = {}); - -Widgets::UIEditorVector3FieldPalette ResolveUIEditorVector3FieldPalette( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorVector3FieldPalette& fallback = {}); - -Widgets::UIEditorVector4FieldMetrics ResolveUIEditorVector4FieldMetrics( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorVector4FieldMetrics& fallback = {}); - -Widgets::UIEditorVector4FieldPalette ResolveUIEditorVector4FieldPalette( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorVector4FieldPalette& fallback = {}); - -Widgets::UIEditorEnumFieldMetrics ResolveUIEditorEnumFieldMetrics( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorEnumFieldMetrics& fallback = {}); - -Widgets::UIEditorEnumFieldPalette ResolveUIEditorEnumFieldPalette( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorEnumFieldPalette& fallback = {}); - -Widgets::UIEditorMenuPopupMetrics ResolveUIEditorMenuPopupMetrics( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorMenuPopupMetrics& fallback = {}); - -Widgets::UIEditorMenuPopupPalette ResolveUIEditorMenuPopupPalette( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorMenuPopupPalette& fallback = {}); - -Widgets::UIEditorPropertyGridMetrics ResolveUIEditorPropertyGridMetrics( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorPropertyGridMetrics& fallback = {}); - -Widgets::UIEditorPropertyGridPalette ResolveUIEditorPropertyGridPalette( - const ::XCEngine::UI::Style::UITheme& theme, - const Widgets::UIEditorPropertyGridPalette& fallback = {}); - -Widgets::UIEditorBoolFieldMetrics BuildUIEditorHostedBoolFieldMetrics( - const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, - const Widgets::UIEditorBoolFieldMetrics& fallback = {}); - -Widgets::UIEditorBoolFieldPalette BuildUIEditorHostedBoolFieldPalette( - const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, - const Widgets::UIEditorBoolFieldPalette& fallback = {}); - -Widgets::UIEditorNumberFieldMetrics BuildUIEditorHostedNumberFieldMetrics( - const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, - const Widgets::UIEditorNumberFieldMetrics& fallback = {}); - -Widgets::UIEditorNumberFieldPalette BuildUIEditorHostedNumberFieldPalette( - const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, - const Widgets::UIEditorNumberFieldPalette& fallback = {}); - -Widgets::UIEditorTextFieldMetrics BuildUIEditorHostedTextFieldMetrics( - const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, - const Widgets::UIEditorTextFieldMetrics& fallback = {}); - -Widgets::UIEditorTextFieldPalette BuildUIEditorHostedTextFieldPalette( - const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, - const Widgets::UIEditorTextFieldPalette& fallback = {}); - -Widgets::UIEditorVector2FieldMetrics BuildUIEditorHostedVector2FieldMetrics( - const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, - const Widgets::UIEditorVector2FieldMetrics& fallback = {}); - -Widgets::UIEditorVector2FieldPalette BuildUIEditorHostedVector2FieldPalette( - const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, - const Widgets::UIEditorVector2FieldPalette& fallback = {}); - -Widgets::UIEditorVector3FieldMetrics BuildUIEditorHostedVector3FieldMetrics( - const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, - const Widgets::UIEditorVector3FieldMetrics& fallback = {}); - -Widgets::UIEditorVector3FieldPalette BuildUIEditorHostedVector3FieldPalette( - const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, - const Widgets::UIEditorVector3FieldPalette& fallback = {}); - -Widgets::UIEditorVector4FieldMetrics BuildUIEditorHostedVector4FieldMetrics( - const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, - const Widgets::UIEditorVector4FieldMetrics& fallback = {}); - -Widgets::UIEditorVector4FieldPalette BuildUIEditorHostedVector4FieldPalette( - const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, - const Widgets::UIEditorVector4FieldPalette& fallback = {}); - -Widgets::UIEditorEnumFieldMetrics BuildUIEditorHostedEnumFieldMetrics( - const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, - const Widgets::UIEditorEnumFieldMetrics& fallback = {}); - -Widgets::UIEditorEnumFieldPalette BuildUIEditorHostedEnumFieldPalette( - const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, - const Widgets::UIEditorEnumFieldPalette& fallback = {}); - -} // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Fields/UIEditorAssetField.h b/new_editor/include/XCEditor/Fields/UIEditorAssetField.h new file mode 100644 index 00000000..655c4403 --- /dev/null +++ b/new_editor/include/XCEditor/Fields/UIEditorAssetField.h @@ -0,0 +1,188 @@ +#pragma once + +#include + +#include +#include + +namespace XCEngine::UI::Editor::Widgets { + +enum class UIEditorAssetFieldHitTargetKind : std::uint8_t { + None = 0, + Row, + ValueBox, + PickerButton, + ClearButton +}; + +struct UIEditorAssetFieldSpec { + std::string fieldId = {}; + std::string label = {}; + std::string assetId = {}; + std::string displayName = {}; + std::string statusText = {}; + std::string emptyText = "None"; + ::XCEngine::UI::UIColor tint = ::XCEngine::UI::UIColor(0.28f, 0.50f, 0.83f, 1.0f); + bool readOnly = false; + bool showPickerButton = true; + bool allowClear = true; + bool showStatusBadge = true; +}; + +struct UIEditorAssetFieldState { + UIEditorAssetFieldHitTargetKind hoveredTarget = UIEditorAssetFieldHitTargetKind::None; + UIEditorAssetFieldHitTargetKind activeTarget = UIEditorAssetFieldHitTargetKind::None; + bool focused = false; +}; + +struct UIEditorAssetFieldMetrics { + float rowHeight = 22.0f; + float horizontalPadding = 12.0f; + float labelControlGap = 20.0f; + float controlColumnStart = 236.0f; + float controlTrailingInset = 8.0f; + float valueBoxMinWidth = 116.0f; + float controlInsetY = 1.0f; + float labelTextInsetY = 0.0f; + float labelFontSize = 11.0f; + float valueTextInsetX = 6.0f; + float valueTextInsetY = 0.0f; + float valueFontSize = 12.0f; + float previewSize = 16.0f; + float previewInsetX = 4.0f; + float previewGap = 6.0f; + float previewGlyphFontSize = 10.0f; + float statusBadgeGap = 6.0f; + float statusBadgeMinWidth = 44.0f; + float statusBadgePaddingX = 6.0f; + float statusBadgeHeight = 14.0f; + float statusBadgeFontSize = 10.0f; + float actionButtonGap = 2.0f; + float actionButtonWidth = 20.0f; + float actionGlyphFontSize = 10.0f; + float actionGlyphInsetY = -1.0f; + float cornerRounding = 0.0f; + float valueBoxRounding = 2.0f; + float previewRounding = 2.0f; + float badgeRounding = 2.0f; + float borderThickness = 1.0f; + float focusedBorderThickness = 1.0f; +}; + +struct UIEditorAssetFieldPalette { + ::XCEngine::UI::UIColor surfaceColor = + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor borderColor = + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor focusedBorderColor = + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor rowHoverColor = + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor rowActiveColor = + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor valueBoxColor = + ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor valueBoxHoverColor = + ::XCEngine::UI::UIColor(0.22f, 0.22f, 0.22f, 1.0f); + ::XCEngine::UI::UIColor valueBoxActiveColor = + ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); + ::XCEngine::UI::UIColor readOnlyColor = + ::XCEngine::UI::UIColor(0.16f, 0.16f, 0.16f, 1.0f); + ::XCEngine::UI::UIColor controlBorderColor = + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); + ::XCEngine::UI::UIColor controlFocusedBorderColor = + ::XCEngine::UI::UIColor(0.42f, 0.42f, 0.42f, 1.0f); + ::XCEngine::UI::UIColor labelColor = + ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f); + ::XCEngine::UI::UIColor valueColor = + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); + ::XCEngine::UI::UIColor emptyValueColor = + ::XCEngine::UI::UIColor(0.60f, 0.60f, 0.60f, 1.0f); + ::XCEngine::UI::UIColor previewBaseColor = + ::XCEngine::UI::UIColor(0.23f, 0.25f, 0.28f, 1.0f); + ::XCEngine::UI::UIColor previewEmptyColor = + ::XCEngine::UI::UIColor(0.26f, 0.26f, 0.26f, 1.0f); + ::XCEngine::UI::UIColor previewBorderColor = + ::XCEngine::UI::UIColor(0.08f, 0.08f, 0.08f, 1.0f); + ::XCEngine::UI::UIColor previewGlyphColor = + ::XCEngine::UI::UIColor(0.94f, 0.94f, 0.94f, 1.0f); + ::XCEngine::UI::UIColor statusBadgeColor = + ::XCEngine::UI::UIColor(0.25f, 0.29f, 0.34f, 1.0f); + ::XCEngine::UI::UIColor statusBadgeBorderColor = + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor statusBadgeTextColor = + ::XCEngine::UI::UIColor(0.82f, 0.87f, 0.96f, 1.0f); + ::XCEngine::UI::UIColor actionButtonHoverColor = + ::XCEngine::UI::UIColor(0.28f, 0.28f, 0.28f, 1.0f); + ::XCEngine::UI::UIColor actionButtonActiveColor = + ::XCEngine::UI::UIColor(0.32f, 0.32f, 0.32f, 1.0f); + ::XCEngine::UI::UIColor actionButtonColor = + ::XCEngine::UI::UIColor(0.16f, 0.16f, 0.16f, 1.0f); + ::XCEngine::UI::UIColor separatorColor = + ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); + ::XCEngine::UI::UIColor pickerGlyphColor = + ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f); + ::XCEngine::UI::UIColor clearGlyphColor = + ::XCEngine::UI::UIColor(0.95f, 0.68f, 0.68f, 1.0f); +}; + +struct UIEditorAssetFieldLayout { + ::XCEngine::UI::UIRect bounds = {}; + ::XCEngine::UI::UIRect labelRect = {}; + ::XCEngine::UI::UIRect controlRect = {}; + ::XCEngine::UI::UIRect valueRect = {}; + ::XCEngine::UI::UIRect previewRect = {}; + ::XCEngine::UI::UIRect textRect = {}; + ::XCEngine::UI::UIRect statusBadgeRect = {}; + ::XCEngine::UI::UIRect pickerRect = {}; + ::XCEngine::UI::UIRect clearRect = {}; +}; + +struct UIEditorAssetFieldHitTarget { + UIEditorAssetFieldHitTargetKind kind = UIEditorAssetFieldHitTargetKind::None; +}; + +bool HasUIEditorAssetFieldValue(const UIEditorAssetFieldSpec& spec); + +std::string ResolveUIEditorAssetFieldValueText(const UIEditorAssetFieldSpec& spec); + +std::string ResolveUIEditorAssetFieldPreviewGlyph(const UIEditorAssetFieldSpec& spec); + +bool IsUIEditorAssetFieldPointInside( + const ::XCEngine::UI::UIRect& rect, + const ::XCEngine::UI::UIPoint& point); + +UIEditorAssetFieldLayout BuildUIEditorAssetFieldLayout( + const ::XCEngine::UI::UIRect& bounds, + const UIEditorAssetFieldSpec& spec, + const UIEditorAssetFieldMetrics& metrics = {}); + +UIEditorAssetFieldHitTarget HitTestUIEditorAssetField( + const UIEditorAssetFieldLayout& layout, + const ::XCEngine::UI::UIPoint& point); + +void AppendUIEditorAssetFieldBackground( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorAssetFieldLayout& layout, + const UIEditorAssetFieldSpec& spec, + const UIEditorAssetFieldState& state, + const UIEditorAssetFieldPalette& palette = {}, + const UIEditorAssetFieldMetrics& metrics = {}); + +void AppendUIEditorAssetFieldForeground( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorAssetFieldLayout& layout, + const UIEditorAssetFieldSpec& spec, + const UIEditorAssetFieldState& state, + const UIEditorAssetFieldPalette& palette = {}, + const UIEditorAssetFieldMetrics& metrics = {}); + +void AppendUIEditorAssetField( + ::XCEngine::UI::UIDrawList& drawList, + const ::XCEngine::UI::UIRect& bounds, + const UIEditorAssetFieldSpec& spec, + const UIEditorAssetFieldState& state, + const UIEditorAssetFieldPalette& palette = {}, + const UIEditorAssetFieldMetrics& metrics = {}); + +} // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/include/XCEditor/Fields/UIEditorAssetFieldInteraction.h b/new_editor/include/XCEditor/Fields/UIEditorAssetFieldInteraction.h new file mode 100644 index 00000000..fc6d0b28 --- /dev/null +++ b/new_editor/include/XCEditor/Fields/UIEditorAssetFieldInteraction.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include + +#include +#include + +namespace XCEngine::UI::Editor { + +struct UIEditorAssetFieldInteractionState { + Widgets::UIEditorAssetFieldState fieldState = {}; + ::XCEngine::UI::UIPoint pointerPosition = {}; + bool hasPointerPosition = false; +}; + +struct UIEditorAssetFieldInteractionResult { + bool consumed = false; + bool focusChanged = false; + bool valueChanged = false; + bool activateRequested = false; + bool pickerRequested = false; + bool clearRequested = false; + Widgets::UIEditorAssetFieldHitTarget hitTarget = {}; + std::string assetIdBefore = {}; + std::string assetIdAfter = {}; + std::string displayNameBefore = {}; + std::string displayNameAfter = {}; +}; + +struct UIEditorAssetFieldInteractionFrame { + Widgets::UIEditorAssetFieldLayout layout = {}; + UIEditorAssetFieldInteractionResult result = {}; +}; + +UIEditorAssetFieldInteractionFrame UpdateUIEditorAssetFieldInteraction( + UIEditorAssetFieldInteractionState& state, + Widgets::UIEditorAssetFieldSpec& spec, + const ::XCEngine::UI::UIRect& bounds, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + const Widgets::UIEditorAssetFieldMetrics& metrics = {}); + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Widgets/UIEditorBoolField.h b/new_editor/include/XCEditor/Fields/UIEditorBoolField.h similarity index 100% rename from new_editor/include/XCEditor/Widgets/UIEditorBoolField.h rename to new_editor/include/XCEditor/Fields/UIEditorBoolField.h diff --git a/new_editor/include/XCEditor/Core/UIEditorBoolFieldInteraction.h b/new_editor/include/XCEditor/Fields/UIEditorBoolFieldInteraction.h similarity index 95% rename from new_editor/include/XCEditor/Core/UIEditorBoolFieldInteraction.h rename to new_editor/include/XCEditor/Fields/UIEditorBoolFieldInteraction.h index a6167195..38539f6e 100644 --- a/new_editor/include/XCEditor/Core/UIEditorBoolFieldInteraction.h +++ b/new_editor/include/XCEditor/Fields/UIEditorBoolFieldInteraction.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include diff --git a/new_editor/include/XCEditor/Fields/UIEditorColorField.h b/new_editor/include/XCEditor/Fields/UIEditorColorField.h new file mode 100644 index 00000000..f4a07fe7 --- /dev/null +++ b/new_editor/include/XCEditor/Fields/UIEditorColorField.h @@ -0,0 +1,220 @@ +#pragma once + +#include + +#include +#include + +namespace XCEngine::UI::Editor::Widgets { + +enum class UIEditorColorFieldHitTargetKind : std::uint8_t { + None = 0, + Row, + Swatch, + PopupSurface, + PopupCloseButton, + HueWheel, + SaturationValue, + RedChannel, + GreenChannel, + BlueChannel, + AlphaChannel +}; + +struct UIEditorColorFieldSpec { + std::string fieldId = {}; + std::string label = {}; + ::XCEngine::UI::UIColor value = {}; + bool showAlpha = true; + bool readOnly = false; +}; + +struct UIEditorColorFieldState { + UIEditorColorFieldHitTargetKind hoveredTarget = UIEditorColorFieldHitTargetKind::None; + UIEditorColorFieldHitTargetKind activeTarget = UIEditorColorFieldHitTargetKind::None; + bool focused = false; + bool popupOpen = false; + float hue = 0.0f; + bool hueValid = false; +}; + +struct UIEditorColorFieldMetrics { + float rowHeight = 22.0f; + float horizontalPadding = 12.0f; + float labelControlGap = 20.0f; + float controlColumnStart = 236.0f; + float controlTrailingInset = 8.0f; + float swatchWidth = 54.0f; + float swatchInsetY = 1.0f; + float labelTextInsetY = 0.0f; + float labelFontSize = 11.0f; + float popupWidth = 292.0f; + float popupPadding = 10.0f; + float popupGapY = 6.0f; + float popupHeaderHeight = 30.0f; + float popupTopRowHeight = 34.0f; + float popupPreviewWidth = 96.0f; + float popupPreviewHeight = 28.0f; + float popupCloseButtonSize = 18.0f; + float borderThickness = 1.0f; + float focusedBorderThickness = 1.0f; + float swatchRounding = 2.0f; + float popupBorderRounding = 3.0f; + float wheelOuterRadius = 110.0f; + float wheelRingThickness = 24.0f; + float saturationValueSize = 114.0f; + float wheelRegionHeight = 220.0f; + float channelRowHeight = 20.0f; + float numericBoxWidth = 62.0f; + float channelLabelWidth = 12.0f; + float hexLabelWidth = 84.0f; + float controlRowSpacing = 8.0f; + float popupFieldInset = 6.0f; + float titleFontSize = 12.0f; + float valueFontSize = 11.0f; + float valueTextInsetX = 6.0f; + float valueTextInsetY = 0.0f; + float checkerSize = 6.0f; + float handleRadius = 8.0f; + float ringHandleRadius = 12.0f; +}; + +struct UIEditorColorFieldPalette { + ::XCEngine::UI::UIColor surfaceColor = + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor borderColor = + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor focusedBorderColor = + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor rowHoverColor = + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor rowActiveColor = + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor swatchBorderColor = + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor swatchHoverBorderColor = + ::XCEngine::UI::UIColor(0.34f, 0.34f, 0.34f, 1.0f); + ::XCEngine::UI::UIColor swatchReadOnlyOverlayColor = + ::XCEngine::UI::UIColor(0.08f, 0.08f, 0.08f, 0.18f); + ::XCEngine::UI::UIColor popupColor = + ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); + ::XCEngine::UI::UIColor popupBorderColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); + ::XCEngine::UI::UIColor popupHeaderColor = + ::XCEngine::UI::UIColor(0.43f, 0.24f, 0.05f, 1.0f); + ::XCEngine::UI::UIColor popupTitleColor = + ::XCEngine::UI::UIColor(0.95f, 0.95f, 0.95f, 1.0f); + ::XCEngine::UI::UIColor closeButtonColor = + ::XCEngine::UI::UIColor(0.76f, 0.35f, 0.34f, 1.0f); + ::XCEngine::UI::UIColor closeButtonHoverColor = + ::XCEngine::UI::UIColor(0.82f, 0.40f, 0.39f, 1.0f); + ::XCEngine::UI::UIColor closeGlyphColor = + ::XCEngine::UI::UIColor(0.95f, 0.95f, 0.95f, 1.0f); + ::XCEngine::UI::UIColor labelColor = + ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f); + ::XCEngine::UI::UIColor popupTextColor = + ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); + ::XCEngine::UI::UIColor popupTextMutedColor = + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); + ::XCEngine::UI::UIColor previewBorderColor = + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); + ::XCEngine::UI::UIColor previewBaseColor = + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); + ::XCEngine::UI::UIColor checkerLightColor = + ::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f); + ::XCEngine::UI::UIColor checkerDarkColor = + ::XCEngine::UI::UIColor(0.55f, 0.55f, 0.55f, 1.0f); + ::XCEngine::UI::UIColor sliderBorderColor = + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); + ::XCEngine::UI::UIColor numericBoxColor = + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); + ::XCEngine::UI::UIColor numericBoxBorderColor = + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); + ::XCEngine::UI::UIColor numericBoxTextColor = + ::XCEngine::UI::UIColor(0.90f, 0.90f, 0.90f, 1.0f); + ::XCEngine::UI::UIColor handleFillColor = + ::XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f); + ::XCEngine::UI::UIColor handleStrokeColor = + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.4f); +}; + +struct UIEditorColorFieldLayout { + ::XCEngine::UI::UIRect bounds = {}; + ::XCEngine::UI::UIRect labelRect = {}; + ::XCEngine::UI::UIRect controlRect = {}; + ::XCEngine::UI::UIRect swatchRect = {}; + ::XCEngine::UI::UIRect popupRect = {}; + ::XCEngine::UI::UIRect popupHeaderRect = {}; + ::XCEngine::UI::UIRect popupTitleRect = {}; + ::XCEngine::UI::UIRect popupCloseButtonRect = {}; + ::XCEngine::UI::UIRect popupBodyRect = {}; + ::XCEngine::UI::UIRect popupTopRowRect = {}; + ::XCEngine::UI::UIRect popupPreviewRect = {}; + ::XCEngine::UI::UIRect popupWheelRect = {}; + ::XCEngine::UI::UIPoint hueWheelCenter = {}; + float hueWheelOuterRadius = 0.0f; + float hueWheelInnerRadius = 0.0f; + ::XCEngine::UI::UIRect saturationValueRect = {}; + ::XCEngine::UI::UIRect redLabelRect = {}; + ::XCEngine::UI::UIRect redSliderRect = {}; + ::XCEngine::UI::UIRect redValueRect = {}; + ::XCEngine::UI::UIRect greenLabelRect = {}; + ::XCEngine::UI::UIRect greenSliderRect = {}; + ::XCEngine::UI::UIRect greenValueRect = {}; + ::XCEngine::UI::UIRect blueLabelRect = {}; + ::XCEngine::UI::UIRect blueSliderRect = {}; + ::XCEngine::UI::UIRect blueValueRect = {}; + ::XCEngine::UI::UIRect alphaLabelRect = {}; + ::XCEngine::UI::UIRect alphaSliderRect = {}; + ::XCEngine::UI::UIRect alphaValueRect = {}; + ::XCEngine::UI::UIRect hexLabelRect = {}; + ::XCEngine::UI::UIRect hexValueRect = {}; +}; + +struct UIEditorColorFieldHitTarget { + UIEditorColorFieldHitTargetKind kind = UIEditorColorFieldHitTargetKind::None; +}; + +std::string FormatUIEditorColorFieldRgbaText( + const UIEditorColorFieldSpec& spec); + +std::string FormatUIEditorColorFieldHexText( + const UIEditorColorFieldSpec& spec); + +UIEditorColorFieldLayout BuildUIEditorColorFieldLayout( + const ::XCEngine::UI::UIRect& bounds, + const UIEditorColorFieldSpec& spec, + const UIEditorColorFieldMetrics& metrics = {}, + const ::XCEngine::UI::UIRect& viewportRect = {}); + +UIEditorColorFieldHitTarget HitTestUIEditorColorField( + const UIEditorColorFieldLayout& layout, + bool popupOpen, + const ::XCEngine::UI::UIPoint& point); + +void AppendUIEditorColorFieldBackground( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorColorFieldLayout& layout, + const UIEditorColorFieldSpec& spec, + const UIEditorColorFieldState& state, + const UIEditorColorFieldPalette& palette = {}, + const UIEditorColorFieldMetrics& metrics = {}); + +void AppendUIEditorColorFieldForeground( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorColorFieldLayout& layout, + const UIEditorColorFieldSpec& spec, + const UIEditorColorFieldState& state, + const UIEditorColorFieldPalette& palette = {}, + const UIEditorColorFieldMetrics& metrics = {}); + +void AppendUIEditorColorField( + ::XCEngine::UI::UIDrawList& drawList, + const ::XCEngine::UI::UIRect& bounds, + const UIEditorColorFieldSpec& spec, + const UIEditorColorFieldState& state, + const UIEditorColorFieldPalette& palette = {}, + const UIEditorColorFieldMetrics& metrics = {}, + const ::XCEngine::UI::UIRect& viewportRect = {}); + +} // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/include/XCEditor/Fields/UIEditorColorFieldInteraction.h b/new_editor/include/XCEditor/Fields/UIEditorColorFieldInteraction.h new file mode 100644 index 00000000..abe375c5 --- /dev/null +++ b/new_editor/include/XCEditor/Fields/UIEditorColorFieldInteraction.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include + +#include + +namespace XCEngine::UI::Editor { + +struct UIEditorColorFieldInteractionState { + Widgets::UIEditorColorFieldState colorFieldState = {}; + ::XCEngine::UI::UIPoint pointerPosition = {}; + bool hasPointerPosition = false; +}; + +struct UIEditorColorFieldInteractionResult { + bool consumed = false; + bool focusChanged = false; + bool popupOpened = false; + bool popupClosed = false; + bool colorChanged = false; + Widgets::UIEditorColorFieldHitTarget hitTarget = {}; + ::XCEngine::UI::UIColor valueBefore = {}; + ::XCEngine::UI::UIColor valueAfter = {}; +}; + +struct UIEditorColorFieldInteractionFrame { + Widgets::UIEditorColorFieldLayout layout = {}; + UIEditorColorFieldInteractionResult result = {}; +}; + +UIEditorColorFieldInteractionFrame UpdateUIEditorColorFieldInteraction( + UIEditorColorFieldInteractionState& state, + Widgets::UIEditorColorFieldSpec& spec, + const ::XCEngine::UI::UIRect& bounds, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + const Widgets::UIEditorColorFieldMetrics& metrics = {}, + const ::XCEngine::UI::UIRect& viewportRect = {}); + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Widgets/UIEditorEnumField.h b/new_editor/include/XCEditor/Fields/UIEditorEnumField.h similarity index 100% rename from new_editor/include/XCEditor/Widgets/UIEditorEnumField.h rename to new_editor/include/XCEditor/Fields/UIEditorEnumField.h diff --git a/new_editor/include/XCEditor/Core/UIEditorEnumFieldInteraction.h b/new_editor/include/XCEditor/Fields/UIEditorEnumFieldInteraction.h similarity index 94% rename from new_editor/include/XCEditor/Core/UIEditorEnumFieldInteraction.h rename to new_editor/include/XCEditor/Fields/UIEditorEnumFieldInteraction.h index d2eff906..b12f7d5c 100644 --- a/new_editor/include/XCEditor/Core/UIEditorEnumFieldInteraction.h +++ b/new_editor/include/XCEditor/Fields/UIEditorEnumFieldInteraction.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include diff --git a/new_editor/include/XCEditor/Fields/UIEditorFieldStyle.h b/new_editor/include/XCEditor/Fields/UIEditorFieldStyle.h new file mode 100644 index 00000000..cd46ea08 --- /dev/null +++ b/new_editor/include/XCEditor/Fields/UIEditorFieldStyle.h @@ -0,0 +1,101 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace XCEngine::UI::Editor { + +const Widgets::UIEditorPropertyGridMetrics& GetUIEditorFixedPropertyGridMetrics(); + +const Widgets::UIEditorPropertyGridPalette& GetUIEditorFixedPropertyGridPalette(); + +Widgets::UIEditorBoolFieldMetrics BuildUIEditorPropertyGridBoolFieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorBoolFieldMetrics& fallback = {}); + +Widgets::UIEditorBoolFieldPalette BuildUIEditorPropertyGridBoolFieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorBoolFieldPalette& fallback = {}); + +Widgets::UIEditorNumberFieldMetrics BuildUIEditorPropertyGridNumberFieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorNumberFieldMetrics& fallback = {}); + +Widgets::UIEditorNumberFieldPalette BuildUIEditorPropertyGridNumberFieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorNumberFieldPalette& fallback = {}); + +Widgets::UIEditorTextFieldMetrics BuildUIEditorPropertyGridTextFieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorTextFieldMetrics& fallback = {}); + +Widgets::UIEditorTextFieldPalette BuildUIEditorPropertyGridTextFieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorTextFieldPalette& fallback = {}); + +Widgets::UIEditorVector2FieldMetrics BuildUIEditorPropertyGridVector2FieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorVector2FieldMetrics& fallback = {}); + +Widgets::UIEditorVector2FieldPalette BuildUIEditorPropertyGridVector2FieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorVector2FieldPalette& fallback = {}); + +Widgets::UIEditorVector3FieldMetrics BuildUIEditorPropertyGridVector3FieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorVector3FieldMetrics& fallback = {}); + +Widgets::UIEditorVector3FieldPalette BuildUIEditorPropertyGridVector3FieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorVector3FieldPalette& fallback = {}); + +Widgets::UIEditorVector4FieldMetrics BuildUIEditorPropertyGridVector4FieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorVector4FieldMetrics& fallback = {}); + +Widgets::UIEditorVector4FieldPalette BuildUIEditorPropertyGridVector4FieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorVector4FieldPalette& fallback = {}); + +Widgets::UIEditorEnumFieldMetrics BuildUIEditorPropertyGridEnumFieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorEnumFieldMetrics& fallback = {}); + +Widgets::UIEditorEnumFieldPalette BuildUIEditorPropertyGridEnumFieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorEnumFieldPalette& fallback = {}); + +Widgets::UIEditorColorFieldMetrics BuildUIEditorPropertyGridColorFieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorColorFieldMetrics& fallback = {}); + +Widgets::UIEditorColorFieldPalette BuildUIEditorPropertyGridColorFieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorColorFieldPalette& fallback = {}); + +Widgets::UIEditorObjectFieldMetrics BuildUIEditorPropertyGridObjectFieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorObjectFieldMetrics& fallback = {}); + +Widgets::UIEditorObjectFieldPalette BuildUIEditorPropertyGridObjectFieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorObjectFieldPalette& fallback = {}); + +Widgets::UIEditorAssetFieldMetrics BuildUIEditorPropertyGridAssetFieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorAssetFieldMetrics& fallback = {}); + +Widgets::UIEditorAssetFieldPalette BuildUIEditorPropertyGridAssetFieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorAssetFieldPalette& fallback = {}); + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Widgets/UIEditorNumberField.h b/new_editor/include/XCEditor/Fields/UIEditorNumberField.h similarity index 100% rename from new_editor/include/XCEditor/Widgets/UIEditorNumberField.h rename to new_editor/include/XCEditor/Fields/UIEditorNumberField.h diff --git a/new_editor/include/XCEditor/Core/UIEditorNumberFieldInteraction.h b/new_editor/include/XCEditor/Fields/UIEditorNumberFieldInteraction.h similarity index 96% rename from new_editor/include/XCEditor/Core/UIEditorNumberFieldInteraction.h rename to new_editor/include/XCEditor/Fields/UIEditorNumberFieldInteraction.h index 172de981..2063d1cf 100644 --- a/new_editor/include/XCEditor/Core/UIEditorNumberFieldInteraction.h +++ b/new_editor/include/XCEditor/Fields/UIEditorNumberFieldInteraction.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/new_editor/include/XCEditor/Fields/UIEditorObjectField.h b/new_editor/include/XCEditor/Fields/UIEditorObjectField.h new file mode 100644 index 00000000..7e88e08e --- /dev/null +++ b/new_editor/include/XCEditor/Fields/UIEditorObjectField.h @@ -0,0 +1,153 @@ +#pragma once + +#include + +#include +#include + +namespace XCEngine::UI::Editor::Widgets { + +enum class UIEditorObjectFieldHitTargetKind : std::uint8_t { + None = 0, + Row, + ValueBox, + ClearButton, + PickerButton +}; + +struct UIEditorObjectFieldSpec { + std::string fieldId = {}; + std::string label = {}; + std::string objectName = {}; + std::string objectTypeName = {}; + std::string emptyText = "(none)"; + bool hasValue = false; + bool readOnly = false; + bool showClearButton = true; + bool showPickerButton = true; +}; + +struct UIEditorObjectFieldState { + UIEditorObjectFieldHitTargetKind hoveredTarget = UIEditorObjectFieldHitTargetKind::None; + UIEditorObjectFieldHitTargetKind activeTarget = UIEditorObjectFieldHitTargetKind::None; + bool focused = false; +}; + +struct UIEditorObjectFieldMetrics { + float rowHeight = 22.0f; + float horizontalPadding = 12.0f; + float labelControlGap = 20.0f; + float controlColumnStart = 236.0f; + float controlTrailingInset = 8.0f; + float valueBoxMinWidth = 96.0f; + float controlInsetY = 1.0f; + float labelTextInsetY = 0.0f; + float labelFontSize = 11.0f; + float valueTextInsetX = 5.0f; + float valueTextInsetY = 0.0f; + float valueFontSize = 12.0f; + float typeTextInsetX = 5.0f; + float typeTextInsetY = 0.0f; + float typeFontSize = 10.0f; + float typeMaxWidth = 96.0f; + float typeMinWidth = 44.0f; + float valueTypeGap = 6.0f; + float buttonWidth = 20.0f; + float buttonGlyphInsetY = -1.0f; + float buttonGlyphFontSize = 10.0f; + float cornerRounding = 0.0f; + float valueBoxRounding = 2.0f; + float borderThickness = 1.0f; + float focusedBorderThickness = 1.0f; +}; + +struct UIEditorObjectFieldPalette { + ::XCEngine::UI::UIColor surfaceColor = + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor borderColor = + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor focusedBorderColor = + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor rowHoverColor = + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor rowActiveColor = + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor valueBoxColor = + ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor valueBoxHoverColor = + ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f); + ::XCEngine::UI::UIColor readOnlyColor = + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + ::XCEngine::UI::UIColor controlBorderColor = + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); + ::XCEngine::UI::UIColor controlFocusedBorderColor = + ::XCEngine::UI::UIColor(0.46f, 0.46f, 0.46f, 1.0f); + ::XCEngine::UI::UIColor buttonColor = + ::XCEngine::UI::UIColor(0.16f, 0.16f, 0.16f, 1.0f); + ::XCEngine::UI::UIColor buttonHoverColor = + ::XCEngine::UI::UIColor(0.22f, 0.22f, 0.22f, 1.0f); + ::XCEngine::UI::UIColor buttonActiveColor = + ::XCEngine::UI::UIColor(0.26f, 0.26f, 0.26f, 1.0f); + ::XCEngine::UI::UIColor buttonGlyphColor = + ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); + ::XCEngine::UI::UIColor separatorColor = + ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); + ::XCEngine::UI::UIColor labelColor = + ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f); + ::XCEngine::UI::UIColor valueColor = + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); + ::XCEngine::UI::UIColor emptyValueColor = + ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f); + ::XCEngine::UI::UIColor typeColor = + ::XCEngine::UI::UIColor(0.68f, 0.68f, 0.68f, 1.0f); +}; + +struct UIEditorObjectFieldLayout { + ::XCEngine::UI::UIRect bounds = {}; + ::XCEngine::UI::UIRect labelRect = {}; + ::XCEngine::UI::UIRect controlRect = {}; + ::XCEngine::UI::UIRect valueRect = {}; + ::XCEngine::UI::UIRect typeRect = {}; + ::XCEngine::UI::UIRect clearButtonRect = {}; + ::XCEngine::UI::UIRect pickerButtonRect = {}; +}; + +struct UIEditorObjectFieldHitTarget { + UIEditorObjectFieldHitTargetKind kind = UIEditorObjectFieldHitTargetKind::None; +}; + +std::string ResolveUIEditorObjectFieldDisplayText(const UIEditorObjectFieldSpec& spec); + +UIEditorObjectFieldLayout BuildUIEditorObjectFieldLayout( + const ::XCEngine::UI::UIRect& bounds, + const UIEditorObjectFieldSpec& spec, + const UIEditorObjectFieldMetrics& metrics = {}); + +UIEditorObjectFieldHitTarget HitTestUIEditorObjectField( + const UIEditorObjectFieldLayout& layout, + const ::XCEngine::UI::UIPoint& point); + +void AppendUIEditorObjectFieldBackground( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorObjectFieldLayout& layout, + const UIEditorObjectFieldSpec& spec, + const UIEditorObjectFieldState& state, + const UIEditorObjectFieldPalette& palette = {}, + const UIEditorObjectFieldMetrics& metrics = {}); + +void AppendUIEditorObjectFieldForeground( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorObjectFieldLayout& layout, + const UIEditorObjectFieldSpec& spec, + const UIEditorObjectFieldPalette& palette = {}, + const UIEditorObjectFieldMetrics& metrics = {}); + +void AppendUIEditorObjectField( + ::XCEngine::UI::UIDrawList& drawList, + const ::XCEngine::UI::UIRect& bounds, + const UIEditorObjectFieldSpec& spec, + const UIEditorObjectFieldState& state, + const UIEditorObjectFieldPalette& palette = {}, + const UIEditorObjectFieldMetrics& metrics = {}); + +} // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/include/XCEditor/Fields/UIEditorObjectFieldInteraction.h b/new_editor/include/XCEditor/Fields/UIEditorObjectFieldInteraction.h new file mode 100644 index 00000000..cdb01d33 --- /dev/null +++ b/new_editor/include/XCEditor/Fields/UIEditorObjectFieldInteraction.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include + +#include + +namespace XCEngine::UI::Editor { + +struct UIEditorObjectFieldInteractionState { + Widgets::UIEditorObjectFieldState fieldState = {}; + ::XCEngine::UI::UIPoint pointerPosition = {}; + bool hasPointerPosition = false; +}; + +struct UIEditorObjectFieldInteractionResult { + bool consumed = false; + bool focusChanged = false; + bool activateRequested = false; + bool clearRequested = false; + Widgets::UIEditorObjectFieldHitTarget hitTarget = {}; +}; + +struct UIEditorObjectFieldInteractionFrame { + Widgets::UIEditorObjectFieldLayout layout = {}; + UIEditorObjectFieldInteractionResult result = {}; +}; + +UIEditorObjectFieldInteractionFrame UpdateUIEditorObjectFieldInteraction( + UIEditorObjectFieldInteractionState& state, + const Widgets::UIEditorObjectFieldSpec& spec, + const ::XCEngine::UI::UIRect& bounds, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + const Widgets::UIEditorObjectFieldMetrics& metrics = {}); + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Widgets/UIEditorPropertyGrid.h b/new_editor/include/XCEditor/Fields/UIEditorPropertyGrid.h similarity index 95% rename from new_editor/include/XCEditor/Widgets/UIEditorPropertyGrid.h rename to new_editor/include/XCEditor/Fields/UIEditorPropertyGrid.h index 52d5e641..5e927263 100644 --- a/new_editor/include/XCEditor/Widgets/UIEditorPropertyGrid.h +++ b/new_editor/include/XCEditor/Fields/UIEditorPropertyGrid.h @@ -5,7 +5,8 @@ #include #include -#include +#include +#include #include #include @@ -23,6 +24,7 @@ enum class UIEditorPropertyGridFieldKind : std::uint8_t { Bool, Number, Enum, + Color, Vector2, Vector3, Vector4 @@ -58,6 +60,11 @@ struct UIEditorPropertyGridEnumFieldValue { std::size_t selectedIndex = 0u; }; +struct UIEditorPropertyGridColorFieldValue { + ::XCEngine::UI::UIColor value = {}; + bool showAlpha = true; +}; + struct UIEditorPropertyGridVector2FieldValue { std::array values = { 0.0, 0.0 }; std::array componentLabels = { std::string("X"), std::string("Y") }; @@ -104,6 +111,7 @@ struct UIEditorPropertyGridField { bool boolValue = false; UIEditorPropertyGridNumberFieldValue numberValue = {}; UIEditorPropertyGridEnumFieldValue enumValue = {}; + UIEditorPropertyGridColorFieldValue colorValue = {}; UIEditorPropertyGridVector2FieldValue vector2Value = {}; UIEditorPropertyGridVector3FieldValue vector3Value = {}; UIEditorPropertyGridVector4FieldValue vector4Value = {}; @@ -116,6 +124,11 @@ struct UIEditorPropertyGridSection { float desiredHeaderHeight = 0.0f; }; +struct UIEditorPropertyGridColorFieldVisualState { + std::string fieldId = {}; + UIEditorColorFieldState state = {}; +}; + struct UIEditorPropertyGridState { std::string hoveredSectionId = {}; std::string hoveredFieldId = {}; @@ -124,6 +137,7 @@ struct UIEditorPropertyGridState { std::string pressedFieldId = {}; std::string popupFieldId = {}; std::size_t popupHighlightedIndex = UIEditorPropertyGridInvalidIndex; + std::vector colorFieldStates = {}; }; struct UIEditorPropertyGridMetrics { diff --git a/new_editor/include/XCEditor/Core/UIEditorPropertyGridInteraction.h b/new_editor/include/XCEditor/Fields/UIEditorPropertyGridInteraction.h similarity index 97% rename from new_editor/include/XCEditor/Core/UIEditorPropertyGridInteraction.h rename to new_editor/include/XCEditor/Fields/UIEditorPropertyGridInteraction.h index ae0855d6..93ecc1a6 100644 --- a/new_editor/include/XCEditor/Core/UIEditorPropertyGridInteraction.h +++ b/new_editor/include/XCEditor/Fields/UIEditorPropertyGridInteraction.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/new_editor/include/XCEditor/Widgets/UIEditorTextField.h b/new_editor/include/XCEditor/Fields/UIEditorTextField.h similarity index 100% rename from new_editor/include/XCEditor/Widgets/UIEditorTextField.h rename to new_editor/include/XCEditor/Fields/UIEditorTextField.h diff --git a/new_editor/include/XCEditor/Core/UIEditorTextFieldInteraction.h b/new_editor/include/XCEditor/Fields/UIEditorTextFieldInteraction.h similarity index 96% rename from new_editor/include/XCEditor/Core/UIEditorTextFieldInteraction.h rename to new_editor/include/XCEditor/Fields/UIEditorTextFieldInteraction.h index 11fbbbc6..330ab820 100644 --- a/new_editor/include/XCEditor/Core/UIEditorTextFieldInteraction.h +++ b/new_editor/include/XCEditor/Fields/UIEditorTextFieldInteraction.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/new_editor/include/XCEditor/Widgets/UIEditorVector2Field.h b/new_editor/include/XCEditor/Fields/UIEditorVector2Field.h similarity index 100% rename from new_editor/include/XCEditor/Widgets/UIEditorVector2Field.h rename to new_editor/include/XCEditor/Fields/UIEditorVector2Field.h diff --git a/new_editor/include/XCEditor/Core/UIEditorVector2FieldInteraction.h b/new_editor/include/XCEditor/Fields/UIEditorVector2FieldInteraction.h similarity index 97% rename from new_editor/include/XCEditor/Core/UIEditorVector2FieldInteraction.h rename to new_editor/include/XCEditor/Fields/UIEditorVector2FieldInteraction.h index 4bab2a12..965979ef 100644 --- a/new_editor/include/XCEditor/Core/UIEditorVector2FieldInteraction.h +++ b/new_editor/include/XCEditor/Fields/UIEditorVector2FieldInteraction.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/new_editor/include/XCEditor/Widgets/UIEditorVector3Field.h b/new_editor/include/XCEditor/Fields/UIEditorVector3Field.h similarity index 100% rename from new_editor/include/XCEditor/Widgets/UIEditorVector3Field.h rename to new_editor/include/XCEditor/Fields/UIEditorVector3Field.h diff --git a/new_editor/include/XCEditor/Core/UIEditorVector3FieldInteraction.h b/new_editor/include/XCEditor/Fields/UIEditorVector3FieldInteraction.h similarity index 97% rename from new_editor/include/XCEditor/Core/UIEditorVector3FieldInteraction.h rename to new_editor/include/XCEditor/Fields/UIEditorVector3FieldInteraction.h index 2d75e295..ce79df86 100644 --- a/new_editor/include/XCEditor/Core/UIEditorVector3FieldInteraction.h +++ b/new_editor/include/XCEditor/Fields/UIEditorVector3FieldInteraction.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/new_editor/include/XCEditor/Widgets/UIEditorVector4Field.h b/new_editor/include/XCEditor/Fields/UIEditorVector4Field.h similarity index 100% rename from new_editor/include/XCEditor/Widgets/UIEditorVector4Field.h rename to new_editor/include/XCEditor/Fields/UIEditorVector4Field.h diff --git a/new_editor/include/XCEditor/Core/UIEditorVector4FieldInteraction.h b/new_editor/include/XCEditor/Fields/UIEditorVector4FieldInteraction.h similarity index 97% rename from new_editor/include/XCEditor/Core/UIEditorVector4FieldInteraction.h rename to new_editor/include/XCEditor/Fields/UIEditorVector4FieldInteraction.h index 6150977d..7081d3c8 100644 --- a/new_editor/include/XCEditor/Core/UIEditorVector4FieldInteraction.h +++ b/new_editor/include/XCEditor/Fields/UIEditorVector4FieldInteraction.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/new_editor/include/XCEditor/Core/UIEditorCommandDispatcher.h b/new_editor/include/XCEditor/Foundation/UIEditorCommandDispatcher.h similarity index 97% rename from new_editor/include/XCEditor/Core/UIEditorCommandDispatcher.h rename to new_editor/include/XCEditor/Foundation/UIEditorCommandDispatcher.h index 314fbe48..a7770722 100644 --- a/new_editor/include/XCEditor/Core/UIEditorCommandDispatcher.h +++ b/new_editor/include/XCEditor/Foundation/UIEditorCommandDispatcher.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/new_editor/include/XCEditor/Core/UIEditorCommandRegistry.h b/new_editor/include/XCEditor/Foundation/UIEditorCommandRegistry.h similarity index 96% rename from new_editor/include/XCEditor/Core/UIEditorCommandRegistry.h rename to new_editor/include/XCEditor/Foundation/UIEditorCommandRegistry.h index b4288c61..e6a9ddda 100644 --- a/new_editor/include/XCEditor/Core/UIEditorCommandRegistry.h +++ b/new_editor/include/XCEditor/Foundation/UIEditorCommandRegistry.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/new_editor/include/XCEditor/Core/UIEditorShortcutManager.h b/new_editor/include/XCEditor/Foundation/UIEditorShortcutManager.h similarity index 98% rename from new_editor/include/XCEditor/Core/UIEditorShortcutManager.h rename to new_editor/include/XCEditor/Foundation/UIEditorShortcutManager.h index 16fce916..6215fa7f 100644 --- a/new_editor/include/XCEditor/Core/UIEditorShortcutManager.h +++ b/new_editor/include/XCEditor/Foundation/UIEditorShortcutManager.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include diff --git a/new_editor/include/XCEditor/Foundation/UIEditorTheme.h b/new_editor/include/XCEditor/Foundation/UIEditorTheme.h new file mode 100644 index 00000000..b9b18224 --- /dev/null +++ b/new_editor/include/XCEditor/Foundation/UIEditorTheme.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace XCEngine::UI::Editor { + +const Widgets::UIEditorBoolFieldMetrics& ResolveUIEditorBoolFieldMetrics(); +const Widgets::UIEditorBoolFieldPalette& ResolveUIEditorBoolFieldPalette(); +const Widgets::UIEditorNumberFieldMetrics& ResolveUIEditorNumberFieldMetrics(); +const Widgets::UIEditorNumberFieldPalette& ResolveUIEditorNumberFieldPalette(); +const Widgets::UIEditorTextFieldMetrics& ResolveUIEditorTextFieldMetrics(); +const Widgets::UIEditorTextFieldPalette& ResolveUIEditorTextFieldPalette(); +const Widgets::UIEditorVector2FieldMetrics& ResolveUIEditorVector2FieldMetrics(); +const Widgets::UIEditorVector2FieldPalette& ResolveUIEditorVector2FieldPalette(); +const Widgets::UIEditorVector3FieldMetrics& ResolveUIEditorVector3FieldMetrics(); +const Widgets::UIEditorVector3FieldPalette& ResolveUIEditorVector3FieldPalette(); +const Widgets::UIEditorVector4FieldMetrics& ResolveUIEditorVector4FieldMetrics(); +const Widgets::UIEditorVector4FieldPalette& ResolveUIEditorVector4FieldPalette(); +const Widgets::UIEditorEnumFieldMetrics& ResolveUIEditorEnumFieldMetrics(); +const Widgets::UIEditorEnumFieldPalette& ResolveUIEditorEnumFieldPalette(); +const Widgets::UIEditorColorFieldMetrics& ResolveUIEditorColorFieldMetrics(); +const Widgets::UIEditorColorFieldPalette& ResolveUIEditorColorFieldPalette(); +const Widgets::UIEditorObjectFieldMetrics& ResolveUIEditorObjectFieldMetrics(); +const Widgets::UIEditorObjectFieldPalette& ResolveUIEditorObjectFieldPalette(); +const Widgets::UIEditorAssetFieldMetrics& ResolveUIEditorAssetFieldMetrics(); +const Widgets::UIEditorAssetFieldPalette& ResolveUIEditorAssetFieldPalette(); +const Widgets::UIEditorMenuPopupMetrics& ResolveUIEditorMenuPopupMetrics(); +const Widgets::UIEditorMenuPopupPalette& ResolveUIEditorMenuPopupPalette(); +const Widgets::UIEditorListViewMetrics& ResolveUIEditorListViewMetrics(); +const Widgets::UIEditorListViewPalette& ResolveUIEditorListViewPalette(); +const Widgets::UIEditorTreeViewMetrics& ResolveUIEditorTreeViewMetrics(); +const Widgets::UIEditorTreeViewPalette& ResolveUIEditorTreeViewPalette(); +const Widgets::UIEditorScrollViewMetrics& ResolveUIEditorScrollViewMetrics(); +const Widgets::UIEditorScrollViewPalette& ResolveUIEditorScrollViewPalette(); +const Widgets::UIEditorTabStripMetrics& ResolveUIEditorTabStripMetrics(); +const Widgets::UIEditorTabStripPalette& ResolveUIEditorTabStripPalette(); +const Widgets::UIEditorMenuBarMetrics& ResolveUIEditorMenuBarMetrics(); +const Widgets::UIEditorMenuBarPalette& ResolveUIEditorMenuBarPalette(); +const Widgets::UIEditorStatusBarMetrics& ResolveUIEditorStatusBarMetrics(); +const Widgets::UIEditorStatusBarPalette& ResolveUIEditorStatusBarPalette(); +const Widgets::UIEditorPanelFrameMetrics& ResolveUIEditorPanelFrameMetrics(); +const Widgets::UIEditorPanelFramePalette& ResolveUIEditorPanelFramePalette(); +const Widgets::UIEditorDockHostMetrics& ResolveUIEditorDockHostMetrics(); +const Widgets::UIEditorDockHostPalette& ResolveUIEditorDockHostPalette(); +const Widgets::UIEditorViewportSlotMetrics& ResolveUIEditorViewportSlotMetrics(); +const Widgets::UIEditorViewportSlotPalette& ResolveUIEditorViewportSlotPalette(); +const UIEditorShellComposeMetrics& ResolveUIEditorShellComposeMetrics(); +const UIEditorShellComposePalette& ResolveUIEditorShellComposePalette(); +const UIEditorShellInteractionMetrics& ResolveUIEditorShellInteractionMetrics(); +const UIEditorShellInteractionPalette& ResolveUIEditorShellInteractionPalette(); +const Widgets::UIEditorPropertyGridMetrics& ResolveUIEditorPropertyGridMetrics(); +const Widgets::UIEditorPropertyGridPalette& ResolveUIEditorPropertyGridPalette(); + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Widgets/UIEditorDockHost.h b/new_editor/include/XCEditor/Shell/UIEditorDockHost.h similarity index 87% rename from new_editor/include/XCEditor/Widgets/UIEditorDockHost.h rename to new_editor/include/XCEditor/Shell/UIEditorDockHost.h index 66df474d..ec09c054 100644 --- a/new_editor/include/XCEditor/Widgets/UIEditorDockHost.h +++ b/new_editor/include/XCEditor/Shell/UIEditorDockHost.h @@ -1,10 +1,10 @@ #pragma once -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include @@ -35,10 +35,16 @@ struct UIEditorDockHostHitTarget { std::size_t index = UIEditorTabStripInvalidIndex; }; +struct UIEditorDockHostTabStripVisualState { + std::string nodeId = {}; + UIEditorTabStripState state = {}; +}; + struct UIEditorDockHostState { bool focused = false; UIEditorDockHostHitTarget hoveredTarget = {}; std::string activeSplitterNodeId = {}; + std::vector tabStripStates = {}; }; struct UIEditorDockHostMetrics { @@ -58,17 +64,17 @@ struct UIEditorDockHostPalette { UIEditorTabStripPalette tabStripPalette = {}; UIEditorPanelFramePalette panelFramePalette = {}; ::XCEngine::UI::UIColor splitterColor = - ::XCEngine::UI::UIColor(0.26f, 0.26f, 0.26f, 1.0f); + ::XCEngine::UI::UIColor(0.22f, 0.23f, 0.25f, 1.0f); ::XCEngine::UI::UIColor splitterHoveredColor = - ::XCEngine::UI::UIColor(0.36f, 0.36f, 0.36f, 1.0f); + ::XCEngine::UI::UIColor(0.32f, 0.34f, 0.36f, 1.0f); ::XCEngine::UI::UIColor splitterActiveColor = - ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); + ::XCEngine::UI::UIColor(0.50f, 0.52f, 0.56f, 1.0f); ::XCEngine::UI::UIColor placeholderTitleColor = - ::XCEngine::UI::UIColor(0.94f, 0.94f, 0.94f, 1.0f); + ::XCEngine::UI::UIColor(0.93f, 0.94f, 0.96f, 1.0f); ::XCEngine::UI::UIColor placeholderTextColor = - ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); + ::XCEngine::UI::UIColor(0.70f, 0.72f, 0.74f, 1.0f); ::XCEngine::UI::UIColor placeholderMutedColor = - ::XCEngine::UI::UIColor(0.58f, 0.58f, 0.58f, 1.0f); + ::XCEngine::UI::UIColor(0.58f, 0.59f, 0.62f, 1.0f); }; struct UIEditorDockHostTabItemLayout { diff --git a/new_editor/include/XCEditor/Core/UIEditorDockHostInteraction.h b/new_editor/include/XCEditor/Shell/UIEditorDockHostInteraction.h similarity index 78% rename from new_editor/include/XCEditor/Core/UIEditorDockHostInteraction.h rename to new_editor/include/XCEditor/Shell/UIEditorDockHostInteraction.h index 59a6d23f..a80901b6 100644 --- a/new_editor/include/XCEditor/Core/UIEditorDockHostInteraction.h +++ b/new_editor/include/XCEditor/Shell/UIEditorDockHostInteraction.h @@ -1,7 +1,8 @@ #pragma once -#include -#include +#include +#include +#include #include #include @@ -11,9 +12,15 @@ namespace XCEngine::UI::Editor { +struct UIEditorDockHostTabStripInteractionEntry { + std::string nodeId = {}; + UIEditorTabStripInteractionState state = {}; +}; + struct UIEditorDockHostInteractionState { Widgets::UIEditorDockHostState dockHostState = {}; ::XCEngine::UI::Widgets::UISplitterDragState splitterDragState = {}; + std::vector tabStripInteractions = {}; ::XCEngine::UI::UIPoint pointerPosition = {}; bool hasPointerPosition = false; }; diff --git a/new_editor/include/XCEditor/Widgets/UIEditorMenuBar.h b/new_editor/include/XCEditor/Shell/UIEditorMenuBar.h similarity index 83% rename from new_editor/include/XCEditor/Widgets/UIEditorMenuBar.h rename to new_editor/include/XCEditor/Shell/UIEditorMenuBar.h index aa6ebb11..8d175ce6 100644 --- a/new_editor/include/XCEditor/Widgets/UIEditorMenuBar.h +++ b/new_editor/include/XCEditor/Shell/UIEditorMenuBar.h @@ -34,7 +34,7 @@ struct UIEditorMenuBarMetrics { float estimatedGlyphWidth = 7.0f; float labelInsetY = -1.0f; float barCornerRounding = 8.0f; - float buttonCornerRounding = 7.0f; + float buttonCornerRounding = 6.0f; float baseBorderThickness = 1.0f; float focusedBorderThickness = 2.0f; float openBorderThickness = 1.5f; @@ -42,25 +42,25 @@ struct UIEditorMenuBarMetrics { struct UIEditorMenuBarPalette { ::XCEngine::UI::UIColor barColor = - ::XCEngine::UI::UIColor(0.16f, 0.16f, 0.16f, 1.0f); + ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.19f, 1.0f); ::XCEngine::UI::UIColor buttonColor = - ::XCEngine::UI::UIColor(0.23f, 0.23f, 0.23f, 1.0f); + ::XCEngine::UI::UIColor(0.21f, 0.22f, 0.24f, 1.0f); ::XCEngine::UI::UIColor buttonHoveredColor = - ::XCEngine::UI::UIColor(0.29f, 0.29f, 0.29f, 1.0f); + ::XCEngine::UI::UIColor(0.27f, 0.28f, 0.30f, 1.0f); ::XCEngine::UI::UIColor buttonOpenColor = - ::XCEngine::UI::UIColor(0.35f, 0.35f, 0.35f, 1.0f); + ::XCEngine::UI::UIColor(0.33f, 0.35f, 0.38f, 1.0f); ::XCEngine::UI::UIColor borderColor = - ::XCEngine::UI::UIColor(0.30f, 0.30f, 0.30f, 1.0f); + ::XCEngine::UI::UIColor(0.30f, 0.32f, 0.34f, 1.0f); ::XCEngine::UI::UIColor focusedBorderColor = - ::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f); + ::XCEngine::UI::UIColor(0.78f, 0.80f, 0.84f, 1.0f); ::XCEngine::UI::UIColor openBorderColor = - ::XCEngine::UI::UIColor(0.68f, 0.68f, 0.68f, 1.0f); + ::XCEngine::UI::UIColor(0.50f, 0.52f, 0.56f, 1.0f); ::XCEngine::UI::UIColor textPrimary = - ::XCEngine::UI::UIColor(0.94f, 0.94f, 0.94f, 1.0f); + ::XCEngine::UI::UIColor(0.93f, 0.94f, 0.96f, 1.0f); ::XCEngine::UI::UIColor textMuted = - ::XCEngine::UI::UIColor(0.74f, 0.74f, 0.74f, 1.0f); + ::XCEngine::UI::UIColor(0.70f, 0.72f, 0.74f, 1.0f); ::XCEngine::UI::UIColor textDisabled = - ::XCEngine::UI::UIColor(0.52f, 0.52f, 0.52f, 1.0f); + ::XCEngine::UI::UIColor(0.54f, 0.55f, 0.58f, 1.0f); }; struct UIEditorMenuBarLayout { diff --git a/new_editor/include/XCEditor/Core/UIEditorMenuModel.h b/new_editor/include/XCEditor/Shell/UIEditorMenuModel.h similarity index 98% rename from new_editor/include/XCEditor/Core/UIEditorMenuModel.h rename to new_editor/include/XCEditor/Shell/UIEditorMenuModel.h index 66a7ff68..55b02b0c 100644 --- a/new_editor/include/XCEditor/Core/UIEditorMenuModel.h +++ b/new_editor/include/XCEditor/Shell/UIEditorMenuModel.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/new_editor/include/XCEditor/Widgets/UIEditorMenuPopup.h b/new_editor/include/XCEditor/Shell/UIEditorMenuPopup.h similarity index 99% rename from new_editor/include/XCEditor/Widgets/UIEditorMenuPopup.h rename to new_editor/include/XCEditor/Shell/UIEditorMenuPopup.h index a931c585..287c401f 100644 --- a/new_editor/include/XCEditor/Widgets/UIEditorMenuPopup.h +++ b/new_editor/include/XCEditor/Shell/UIEditorMenuPopup.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/new_editor/include/XCEditor/Core/UIEditorMenuSession.h b/new_editor/include/XCEditor/Shell/UIEditorMenuSession.h similarity index 76% rename from new_editor/include/XCEditor/Core/UIEditorMenuSession.h rename to new_editor/include/XCEditor/Shell/UIEditorMenuSession.h index 6ac2e8ab..66a14969 100644 --- a/new_editor/include/XCEditor/Core/UIEditorMenuSession.h +++ b/new_editor/include/XCEditor/Shell/UIEditorMenuSession.h @@ -24,8 +24,8 @@ struct UIEditorMenuSessionMutationResult { std::string openRootMenuId = {}; std::string openedPopupId = {}; std::vector closedPopupIds = {}; - Widgets::UIPopupDismissReason dismissReason = - Widgets::UIPopupDismissReason::None; + ::XCEngine::UI::Widgets::UIPopupDismissReason dismissReason = + ::XCEngine::UI::Widgets::UIPopupDismissReason::None; [[nodiscard]] bool HasOpenMenu() const { return !openRootMenuId.empty(); @@ -34,7 +34,7 @@ struct UIEditorMenuSessionMutationResult { class UIEditorMenuSession { public: - const Widgets::UIPopupOverlayModel& GetPopupOverlayModel() const { + const ::XCEngine::UI::Widgets::UIPopupOverlayModel& GetPopupOverlayModel() const { return m_popupOverlayModel; } @@ -66,23 +66,23 @@ public: UIEditorMenuSessionMutationResult OpenRootMenu( std::string_view menuId, - Widgets::UIPopupOverlayEntry entry); + ::XCEngine::UI::Widgets::UIPopupOverlayEntry entry); UIEditorMenuSessionMutationResult OpenMenuBarRoot( std::string_view menuId, - Widgets::UIPopupOverlayEntry entry); + ::XCEngine::UI::Widgets::UIPopupOverlayEntry entry); UIEditorMenuSessionMutationResult HoverMenuBarRoot( std::string_view menuId, - Widgets::UIPopupOverlayEntry entry); + ::XCEngine::UI::Widgets::UIPopupOverlayEntry entry); UIEditorMenuSessionMutationResult HoverSubmenu( std::string_view itemId, - Widgets::UIPopupOverlayEntry entry); + ::XCEngine::UI::Widgets::UIPopupOverlayEntry entry); UIEditorMenuSessionMutationResult CloseAll( - Widgets::UIPopupDismissReason dismissReason = - Widgets::UIPopupDismissReason::Programmatic); + ::XCEngine::UI::Widgets::UIPopupDismissReason dismissReason = + ::XCEngine::UI::Widgets::UIPopupDismissReason::Programmatic); UIEditorMenuSessionMutationResult DismissFromEscape(); UIEditorMenuSessionMutationResult DismissFromPointerDown( @@ -92,13 +92,13 @@ public: private: UIEditorMenuSessionMutationResult BuildResult( - const Widgets::UIPopupOverlayMutationResult& mutation) const; + const ::XCEngine::UI::Widgets::UIPopupOverlayMutationResult& mutation) const; void RemoveClosedPopupStates(const std::vector& closedPopupIds); void RebuildDerivedState(); std::string m_openRootMenuId = {}; - Widgets::UIPopupOverlayModel m_popupOverlayModel = {}; + ::XCEngine::UI::Widgets::UIPopupOverlayModel m_popupOverlayModel = {}; std::vector m_popupStates = {}; std::vector m_openSubmenuItemIds = {}; }; diff --git a/new_editor/include/XCEditor/Core/UIEditorPanelContentHost.h b/new_editor/include/XCEditor/Shell/UIEditorPanelContentHost.h similarity index 96% rename from new_editor/include/XCEditor/Core/UIEditorPanelContentHost.h rename to new_editor/include/XCEditor/Shell/UIEditorPanelContentHost.h index 6fbace91..c13d83a3 100644 --- a/new_editor/include/XCEditor/Core/UIEditorPanelContentHost.h +++ b/new_editor/include/XCEditor/Shell/UIEditorPanelContentHost.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include #include diff --git a/new_editor/include/XCEditor/Widgets/UIEditorPanelFrame.h b/new_editor/include/XCEditor/Shell/UIEditorPanelFrame.h similarity index 83% rename from new_editor/include/XCEditor/Widgets/UIEditorPanelFrame.h rename to new_editor/include/XCEditor/Shell/UIEditorPanelFrame.h index 355adf90..975955a8 100644 --- a/new_editor/include/XCEditor/Widgets/UIEditorPanelFrame.h +++ b/new_editor/include/XCEditor/Shell/UIEditorPanelFrame.h @@ -41,7 +41,7 @@ struct UIEditorPanelFrameText { }; struct UIEditorPanelFrameMetrics { - float cornerRounding = 10.0f; + float cornerRounding = 8.0f; float headerHeight = 36.0f; float footerHeight = 24.0f; float contentPadding = 12.0f; @@ -61,35 +61,35 @@ struct UIEditorPanelFrameMetrics { struct UIEditorPanelFramePalette { ::XCEngine::UI::UIColor surfaceColor = - ::XCEngine::UI::UIColor(0.16f, 0.16f, 0.16f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.16f, 1.0f); ::XCEngine::UI::UIColor headerColor = - ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f); + ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.19f, 1.0f); ::XCEngine::UI::UIColor footerColor = - ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.18f, 1.0f); ::XCEngine::UI::UIColor borderColor = - ::XCEngine::UI::UIColor(0.30f, 0.30f, 0.30f, 1.0f); + ::XCEngine::UI::UIColor(0.30f, 0.32f, 0.34f, 1.0f); ::XCEngine::UI::UIColor hoveredBorderColor = - ::XCEngine::UI::UIColor(0.42f, 0.42f, 0.42f, 1.0f); + ::XCEngine::UI::UIColor(0.39f, 0.41f, 0.43f, 1.0f); ::XCEngine::UI::UIColor activeBorderColor = - ::XCEngine::UI::UIColor(0.58f, 0.58f, 0.58f, 1.0f); + ::XCEngine::UI::UIColor(0.50f, 0.52f, 0.56f, 1.0f); ::XCEngine::UI::UIColor focusedBorderColor = - ::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f); + ::XCEngine::UI::UIColor(0.78f, 0.80f, 0.84f, 1.0f); ::XCEngine::UI::UIColor textPrimary = - ::XCEngine::UI::UIColor(0.94f, 0.94f, 0.94f, 1.0f); + ::XCEngine::UI::UIColor(0.93f, 0.94f, 0.96f, 1.0f); ::XCEngine::UI::UIColor textSecondary = - ::XCEngine::UI::UIColor(0.71f, 0.71f, 0.71f, 1.0f); + ::XCEngine::UI::UIColor(0.70f, 0.72f, 0.74f, 1.0f); ::XCEngine::UI::UIColor textMuted = - ::XCEngine::UI::UIColor(0.60f, 0.60f, 0.60f, 1.0f); + ::XCEngine::UI::UIColor(0.58f, 0.59f, 0.62f, 1.0f); ::XCEngine::UI::UIColor actionButtonColor = - ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); + ::XCEngine::UI::UIColor(0.21f, 0.22f, 0.24f, 1.0f); ::XCEngine::UI::UIColor actionButtonHoveredColor = - ::XCEngine::UI::UIColor(0.34f, 0.34f, 0.34f, 1.0f); + ::XCEngine::UI::UIColor(0.27f, 0.28f, 0.30f, 1.0f); ::XCEngine::UI::UIColor actionButtonSelectedColor = - ::XCEngine::UI::UIColor(0.48f, 0.48f, 0.48f, 1.0f); + ::XCEngine::UI::UIColor(0.33f, 0.35f, 0.38f, 1.0f); ::XCEngine::UI::UIColor actionButtonBorderColor = - ::XCEngine::UI::UIColor(0.52f, 0.52f, 0.52f, 1.0f); + ::XCEngine::UI::UIColor(0.42f, 0.44f, 0.47f, 1.0f); ::XCEngine::UI::UIColor actionGlyphColor = - ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); + ::XCEngine::UI::UIColor(0.93f, 0.94f, 0.96f, 1.0f); }; struct UIEditorPanelFrameLayout { diff --git a/new_editor/include/XCEditor/Core/UIEditorPanelHostLifecycle.h b/new_editor/include/XCEditor/Shell/UIEditorPanelHostLifecycle.h similarity index 94% rename from new_editor/include/XCEditor/Core/UIEditorPanelHostLifecycle.h rename to new_editor/include/XCEditor/Shell/UIEditorPanelHostLifecycle.h index 6c5b7b31..81531db6 100644 --- a/new_editor/include/XCEditor/Core/UIEditorPanelHostLifecycle.h +++ b/new_editor/include/XCEditor/Shell/UIEditorPanelHostLifecycle.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include #include diff --git a/new_editor/include/XCEditor/Core/UIEditorPanelRegistry.h b/new_editor/include/XCEditor/Shell/UIEditorPanelRegistry.h similarity index 100% rename from new_editor/include/XCEditor/Core/UIEditorPanelRegistry.h rename to new_editor/include/XCEditor/Shell/UIEditorPanelRegistry.h diff --git a/new_editor/include/XCEditor/Core/UIEditorShellCompose.h b/new_editor/include/XCEditor/Shell/UIEditorShellCompose.h similarity index 92% rename from new_editor/include/XCEditor/Core/UIEditorShellCompose.h rename to new_editor/include/XCEditor/Shell/UIEditorShellCompose.h index 9f66e433..8af84cb3 100644 --- a/new_editor/include/XCEditor/Core/UIEditorShellCompose.h +++ b/new_editor/include/XCEditor/Shell/UIEditorShellCompose.h @@ -1,8 +1,8 @@ #pragma once -#include -#include -#include +#include +#include +#include namespace XCEngine::UI::Editor { @@ -24,6 +24,7 @@ struct UIEditorShellComposeMetrics { float surfaceCornerRounding = 10.0f; Widgets::UIEditorMenuBarMetrics menuBarMetrics = {}; Widgets::UIEditorDockHostMetrics dockHostMetrics = {}; + Widgets::UIEditorViewportSlotMetrics viewportMetrics = {}; Widgets::UIEditorStatusBarMetrics statusBarMetrics = {}; }; @@ -34,6 +35,7 @@ struct UIEditorShellComposePalette { ::XCEngine::UI::UIColor(0.27f, 0.27f, 0.27f, 1.0f); Widgets::UIEditorMenuBarPalette menuBarPalette = {}; Widgets::UIEditorDockHostPalette dockHostPalette = {}; + Widgets::UIEditorViewportSlotPalette viewportPalette = {}; Widgets::UIEditorStatusBarPalette statusBarPalette = {}; }; diff --git a/new_editor/include/XCEditor/Core/UIEditorShellInteraction.h b/new_editor/include/XCEditor/Shell/UIEditorShellInteraction.h similarity index 95% rename from new_editor/include/XCEditor/Core/UIEditorShellInteraction.h rename to new_editor/include/XCEditor/Shell/UIEditorShellInteraction.h index 33e97382..a1fbe905 100644 --- a/new_editor/include/XCEditor/Core/UIEditorShellInteraction.h +++ b/new_editor/include/XCEditor/Shell/UIEditorShellInteraction.h @@ -1,11 +1,11 @@ #pragma once -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include diff --git a/new_editor/include/XCEditor/Widgets/UIEditorStatusBar.h b/new_editor/include/XCEditor/Shell/UIEditorStatusBar.h similarity index 85% rename from new_editor/include/XCEditor/Widgets/UIEditorStatusBar.h rename to new_editor/include/XCEditor/Shell/UIEditorStatusBar.h index 952376c5..09158fe7 100644 --- a/new_editor/include/XCEditor/Widgets/UIEditorStatusBar.h +++ b/new_editor/include/XCEditor/Shell/UIEditorStatusBar.h @@ -59,32 +59,32 @@ struct UIEditorStatusBarMetrics { float cornerRounding = 8.0f; float estimatedGlyphWidth = 7.0f; float borderThickness = 1.0f; - float focusedBorderThickness = 1.5f; + float focusedBorderThickness = 2.0f; }; struct UIEditorStatusBarPalette { ::XCEngine::UI::UIColor surfaceColor = - ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.16f, 1.0f); ::XCEngine::UI::UIColor borderColor = - ::XCEngine::UI::UIColor(0.28f, 0.28f, 0.28f, 1.0f); + ::XCEngine::UI::UIColor(0.30f, 0.32f, 0.34f, 1.0f); ::XCEngine::UI::UIColor focusedBorderColor = - ::XCEngine::UI::UIColor(0.78f, 0.78f, 0.78f, 1.0f); + ::XCEngine::UI::UIColor(0.78f, 0.80f, 0.84f, 1.0f); ::XCEngine::UI::UIColor segmentColor = - ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.21f, 1.0f); ::XCEngine::UI::UIColor segmentHoveredColor = - ::XCEngine::UI::UIColor(0.23f, 0.23f, 0.23f, 1.0f); + ::XCEngine::UI::UIColor(0.24f, 0.26f, 0.28f, 1.0f); ::XCEngine::UI::UIColor segmentActiveColor = - ::XCEngine::UI::UIColor(0.29f, 0.29f, 0.29f, 1.0f); + ::XCEngine::UI::UIColor(0.30f, 0.32f, 0.35f, 1.0f); ::XCEngine::UI::UIColor segmentBorderColor = - ::XCEngine::UI::UIColor(0.35f, 0.35f, 0.35f, 1.0f); + ::XCEngine::UI::UIColor(0.42f, 0.44f, 0.47f, 1.0f); ::XCEngine::UI::UIColor separatorColor = - ::XCEngine::UI::UIColor(0.32f, 0.32f, 0.32f, 1.0f); + ::XCEngine::UI::UIColor(0.32f, 0.34f, 0.36f, 1.0f); ::XCEngine::UI::UIColor textPrimary = - ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); + ::XCEngine::UI::UIColor(0.93f, 0.94f, 0.96f, 1.0f); ::XCEngine::UI::UIColor textMuted = - ::XCEngine::UI::UIColor(0.66f, 0.66f, 0.66f, 1.0f); + ::XCEngine::UI::UIColor(0.58f, 0.59f, 0.62f, 1.0f); ::XCEngine::UI::UIColor textAccent = - ::XCEngine::UI::UIColor(0.94f, 0.94f, 0.94f, 1.0f); + ::XCEngine::UI::UIColor(0.82f, 0.86f, 0.93f, 1.0f); }; struct UIEditorStatusBarLayout { diff --git a/new_editor/include/XCEditor/Core/UIEditorViewportInputBridge.h b/new_editor/include/XCEditor/Shell/UIEditorViewportInputBridge.h similarity index 100% rename from new_editor/include/XCEditor/Core/UIEditorViewportInputBridge.h rename to new_editor/include/XCEditor/Shell/UIEditorViewportInputBridge.h diff --git a/new_editor/include/XCEditor/Core/UIEditorViewportShell.h b/new_editor/include/XCEditor/Shell/UIEditorViewportShell.h similarity index 94% rename from new_editor/include/XCEditor/Core/UIEditorViewportShell.h rename to new_editor/include/XCEditor/Shell/UIEditorViewportShell.h index 441a0b0f..8522fb82 100644 --- a/new_editor/include/XCEditor/Core/UIEditorViewportShell.h +++ b/new_editor/include/XCEditor/Shell/UIEditorViewportShell.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include diff --git a/new_editor/include/XCEditor/Widgets/UIEditorViewportSlot.h b/new_editor/include/XCEditor/Shell/UIEditorViewportSlot.h similarity index 99% rename from new_editor/include/XCEditor/Widgets/UIEditorViewportSlot.h rename to new_editor/include/XCEditor/Shell/UIEditorViewportSlot.h index 3a0a0120..d6137329 100644 --- a/new_editor/include/XCEditor/Widgets/UIEditorViewportSlot.h +++ b/new_editor/include/XCEditor/Shell/UIEditorViewportSlot.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include diff --git a/new_editor/include/XCEditor/Core/UIEditorWorkspaceCompose.h b/new_editor/include/XCEditor/Shell/UIEditorWorkspaceCompose.h similarity index 87% rename from new_editor/include/XCEditor/Core/UIEditorWorkspaceCompose.h rename to new_editor/include/XCEditor/Shell/UIEditorWorkspaceCompose.h index 2a899e84..19f9a0af 100644 --- a/new_editor/include/XCEditor/Core/UIEditorWorkspaceCompose.h +++ b/new_editor/include/XCEditor/Shell/UIEditorWorkspaceCompose.h @@ -1,10 +1,10 @@ #pragma once -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include @@ -78,7 +78,8 @@ UIEditorWorkspaceComposeRequest ResolveUIEditorWorkspaceComposeRequest( const UIEditorWorkspaceSession& session, const std::vector& presentations, const Widgets::UIEditorDockHostState& dockHostState = {}, - const Widgets::UIEditorDockHostMetrics& dockHostMetrics = {}); + const Widgets::UIEditorDockHostMetrics& dockHostMetrics = {}, + const Widgets::UIEditorViewportSlotMetrics& viewportMetrics = {}); UIEditorWorkspaceComposeFrame UpdateUIEditorWorkspaceCompose( UIEditorWorkspaceComposeState& state, @@ -89,7 +90,8 @@ UIEditorWorkspaceComposeFrame UpdateUIEditorWorkspaceCompose( const std::vector& presentations, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, const Widgets::UIEditorDockHostState& dockHostState = {}, - const Widgets::UIEditorDockHostMetrics& dockHostMetrics = {}); + const Widgets::UIEditorDockHostMetrics& dockHostMetrics = {}, + const Widgets::UIEditorViewportSlotMetrics& viewportMetrics = {}); std::vector CollectUIEditorWorkspaceComposeExternalBodyPanelIds( const UIEditorWorkspaceComposeFrame& frame); @@ -98,6 +100,8 @@ void AppendUIEditorWorkspaceCompose( ::XCEngine::UI::UIDrawList& drawList, const UIEditorWorkspaceComposeFrame& frame, const Widgets::UIEditorDockHostPalette& dockHostPalette = {}, - const Widgets::UIEditorDockHostMetrics& dockHostMetrics = {}); + const Widgets::UIEditorDockHostMetrics& dockHostMetrics = {}, + const Widgets::UIEditorViewportSlotPalette& viewportPalette = {}, + const Widgets::UIEditorViewportSlotMetrics& viewportMetrics = {}); } // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Core/UIEditorWorkspaceController.h b/new_editor/include/XCEditor/Shell/UIEditorWorkspaceController.h similarity index 97% rename from new_editor/include/XCEditor/Core/UIEditorWorkspaceController.h rename to new_editor/include/XCEditor/Shell/UIEditorWorkspaceController.h index 01200302..b664fb23 100644 --- a/new_editor/include/XCEditor/Core/UIEditorWorkspaceController.h +++ b/new_editor/include/XCEditor/Shell/UIEditorWorkspaceController.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include #include diff --git a/new_editor/include/XCEditor/Core/UIEditorWorkspaceInteraction.h b/new_editor/include/XCEditor/Shell/UIEditorWorkspaceInteraction.h similarity index 88% rename from new_editor/include/XCEditor/Core/UIEditorWorkspaceInteraction.h rename to new_editor/include/XCEditor/Shell/UIEditorWorkspaceInteraction.h index 5ad9a032..1d0327c1 100644 --- a/new_editor/include/XCEditor/Core/UIEditorWorkspaceInteraction.h +++ b/new_editor/include/XCEditor/Shell/UIEditorWorkspaceInteraction.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include @@ -41,6 +41,7 @@ UIEditorWorkspaceInteractionFrame UpdateUIEditorWorkspaceInteraction( const ::XCEngine::UI::UIRect& bounds, const UIEditorWorkspaceInteractionModel& model, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - const Widgets::UIEditorDockHostMetrics& dockHostMetrics = {}); + const Widgets::UIEditorDockHostMetrics& dockHostMetrics = {}, + const Widgets::UIEditorViewportSlotMetrics& viewportMetrics = {}); } // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Core/UIEditorWorkspaceLayoutPersistence.h b/new_editor/include/XCEditor/Shell/UIEditorWorkspaceLayoutPersistence.h similarity index 96% rename from new_editor/include/XCEditor/Core/UIEditorWorkspaceLayoutPersistence.h rename to new_editor/include/XCEditor/Shell/UIEditorWorkspaceLayoutPersistence.h index 3aff41c3..c07acca4 100644 --- a/new_editor/include/XCEditor/Core/UIEditorWorkspaceLayoutPersistence.h +++ b/new_editor/include/XCEditor/Shell/UIEditorWorkspaceLayoutPersistence.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/new_editor/include/XCEditor/Core/UIEditorWorkspaceModel.h b/new_editor/include/XCEditor/Shell/UIEditorWorkspaceModel.h similarity index 100% rename from new_editor/include/XCEditor/Core/UIEditorWorkspaceModel.h rename to new_editor/include/XCEditor/Shell/UIEditorWorkspaceModel.h diff --git a/new_editor/include/XCEditor/Core/UIEditorWorkspaceSession.h b/new_editor/include/XCEditor/Shell/UIEditorWorkspaceSession.h similarity index 96% rename from new_editor/include/XCEditor/Core/UIEditorWorkspaceSession.h rename to new_editor/include/XCEditor/Shell/UIEditorWorkspaceSession.h index 64d35aa9..807c8dba 100644 --- a/new_editor/include/XCEditor/Core/UIEditorWorkspaceSession.h +++ b/new_editor/include/XCEditor/Shell/UIEditorWorkspaceSession.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include +#include #include #include diff --git a/new_editor/include/XCEditor/Widgets/UIEditorCollectionPrimitives.h b/new_editor/include/XCEditor/Widgets/UIEditorCollectionPrimitives.h index 9ecb8a57..a24d156f 100644 --- a/new_editor/include/XCEditor/Widgets/UIEditorCollectionPrimitives.h +++ b/new_editor/include/XCEditor/Widgets/UIEditorCollectionPrimitives.h @@ -1,7 +1,5 @@ #pragma once -#include - #include #include @@ -24,14 +22,11 @@ bool UsesUIEditorCollectionPrimitiveColumnLayout(UIEditorCollectionPrimitiveKind bool IsUIEditorCollectionPrimitiveHoverable(UIEditorCollectionPrimitiveKind kind); bool DoesUIEditorCollectionPrimitiveClipChildren(UIEditorCollectionPrimitiveKind kind); float ResolveUIEditorCollectionPrimitivePadding( - UIEditorCollectionPrimitiveKind kind, - const ::XCEngine::UI::Style::UITheme& theme); + UIEditorCollectionPrimitiveKind kind); float ResolveUIEditorCollectionPrimitiveDefaultHeight( - UIEditorCollectionPrimitiveKind kind, - const ::XCEngine::UI::Style::UITheme& theme); + UIEditorCollectionPrimitiveKind kind); float ResolveUIEditorCollectionPrimitiveIndent( UIEditorCollectionPrimitiveKind kind, - const ::XCEngine::UI::Style::UITheme& theme, float indentLevel); } // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/include/XCEditor/Widgets/UIEditorColorUtils.h b/new_editor/include/XCEditor/Widgets/UIEditorColorUtils.h new file mode 100644 index 00000000..acb45fee --- /dev/null +++ b/new_editor/include/XCEditor/Widgets/UIEditorColorUtils.h @@ -0,0 +1,178 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::Widgets { + +struct UIEditorHsvColor { + float hue = 0.0f; + float saturation = 0.0f; + float value = 0.0f; + float alpha = 1.0f; +}; + +inline float ClampUIEditorColorUnit(float value) { + return (std::clamp)(value, 0.0f, 1.0f); +} + +inline int ToUIEditorColorByte(float value) { + return static_cast(std::lround(ClampUIEditorColorUnit(value) * 255.0f)); +} + +inline UIEditorHsvColor ConvertUIEditorColorToHsv( + const ::XCEngine::UI::UIColor& color, + float fallbackHue = 0.0f) { + const float red = ClampUIEditorColorUnit(color.r); + const float green = ClampUIEditorColorUnit(color.g); + const float blue = ClampUIEditorColorUnit(color.b); + const float maxChannel = (std::max)({ red, green, blue }); + const float minChannel = (std::min)({ red, green, blue }); + const float delta = maxChannel - minChannel; + + UIEditorHsvColor hsv = {}; + hsv.hue = ClampUIEditorColorUnit(fallbackHue); + hsv.saturation = maxChannel <= 0.0f ? 0.0f : delta / maxChannel; + hsv.value = maxChannel; + hsv.alpha = ClampUIEditorColorUnit(color.a); + + if (delta <= 0.00001f) { + return hsv; + } + + if (maxChannel == red) { + hsv.hue = std::fmod(((green - blue) / delta), 6.0f) / 6.0f; + } else if (maxChannel == green) { + hsv.hue = (((blue - red) / delta) + 2.0f) / 6.0f; + } else { + hsv.hue = (((red - green) / delta) + 4.0f) / 6.0f; + } + + if (hsv.hue < 0.0f) { + hsv.hue += 1.0f; + } + return hsv; +} + +inline ::XCEngine::UI::UIColor ConvertUIEditorHsvToColor(const UIEditorHsvColor& hsv) { + const float hue = ClampUIEditorColorUnit(hsv.hue); + const float saturation = ClampUIEditorColorUnit(hsv.saturation); + const float value = ClampUIEditorColorUnit(hsv.value); + + if (saturation <= 0.00001f) { + return ::XCEngine::UI::UIColor(value, value, value, ClampUIEditorColorUnit(hsv.alpha)); + } + + const float sector = hue * 6.0f; + const int sectorIndex = static_cast(std::floor(sector)) % 6; + const float fraction = sector - std::floor(sector); + const float p = value * (1.0f - saturation); + const float q = value * (1.0f - saturation * fraction); + const float t = value * (1.0f - saturation * (1.0f - fraction)); + + float red = value; + float green = t; + float blue = p; + switch (sectorIndex) { + case 0: + red = value; + green = t; + blue = p; + break; + case 1: + red = q; + green = value; + blue = p; + break; + case 2: + red = p; + green = value; + blue = t; + break; + case 3: + red = p; + green = q; + blue = value; + break; + case 4: + red = t; + green = p; + blue = value; + break; + case 5: + default: + red = value; + green = p; + blue = q; + break; + } + + return ::XCEngine::UI::UIColor(red, green, blue, ClampUIEditorColorUnit(hsv.alpha)); +} + +inline UIEditorHsvColor ResolveUIEditorDisplayHsv( + const ::XCEngine::UI::UIColor& color, + float rememberedHue, + bool hueValid) { + UIEditorHsvColor hsv = ConvertUIEditorColorToHsv(color, hueValid ? rememberedHue : 0.0f); + if (hsv.saturation <= 0.00001f && hueValid) { + hsv.hue = ClampUIEditorColorUnit(rememberedHue); + } + return hsv; +} + +inline std::string FormatUIEditorColorHex( + const ::XCEngine::UI::UIColor& color, + bool includeAlpha = true) { + char buffer[16] = {}; + if (includeAlpha) { + std::snprintf( + buffer, + sizeof(buffer), + "#%02X%02X%02X%02X", + ToUIEditorColorByte(color.r), + ToUIEditorColorByte(color.g), + ToUIEditorColorByte(color.b), + ToUIEditorColorByte(color.a)); + } else { + std::snprintf( + buffer, + sizeof(buffer), + "#%02X%02X%02X", + ToUIEditorColorByte(color.r), + ToUIEditorColorByte(color.g), + ToUIEditorColorByte(color.b)); + } + return std::string(buffer); +} + +inline std::string FormatUIEditorColorChannelsText( + const ::XCEngine::UI::UIColor& color, + bool includeAlpha = true) { + char buffer[64] = {}; + if (includeAlpha) { + std::snprintf( + buffer, + sizeof(buffer), + "RGBA %d, %d, %d, %d", + ToUIEditorColorByte(color.r), + ToUIEditorColorByte(color.g), + ToUIEditorColorByte(color.b), + ToUIEditorColorByte(color.a)); + } else { + std::snprintf( + buffer, + sizeof(buffer), + "RGB %d, %d, %d", + ToUIEditorColorByte(color.r), + ToUIEditorColorByte(color.g), + ToUIEditorColorByte(color.b)); + } + return std::string(buffer); +} + +} // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/include/XCEditor/Widgets/UIEditorFieldRowLayout.h b/new_editor/include/XCEditor/Widgets/UIEditorFieldRowLayout.h index 877beccf..1f1437a7 100644 --- a/new_editor/include/XCEditor/Widgets/UIEditorFieldRowLayout.h +++ b/new_editor/include/XCEditor/Widgets/UIEditorFieldRowLayout.h @@ -4,6 +4,95 @@ namespace XCEngine::UI::Editor::Widgets { +struct UIEditorInspectorFieldStyleTokens { + ::XCEngine::UI::UIColor rowHoverColor = + ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f); + ::XCEngine::UI::UIColor rowActiveColor = + ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); + ::XCEngine::UI::UIColor labelColor = + ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f); + ::XCEngine::UI::UIColor valueColor = + ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); + ::XCEngine::UI::UIColor readOnlyValueColor = + ::XCEngine::UI::UIColor(0.60f, 0.60f, 0.60f, 1.0f); + ::XCEngine::UI::UIColor controlColor = + ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); + ::XCEngine::UI::UIColor controlHoverColor = + ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f); + ::XCEngine::UI::UIColor controlEditingColor = + ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f); + ::XCEngine::UI::UIColor controlReadOnlyColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); + ::XCEngine::UI::UIColor controlBorderColor = + ::XCEngine::UI::UIColor(0.30f, 0.30f, 0.30f, 1.0f); + ::XCEngine::UI::UIColor controlFocusedBorderColor = + ::XCEngine::UI::UIColor(0.64f, 0.64f, 0.64f, 1.0f); + ::XCEngine::UI::UIColor prefixColor = + ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f); + ::XCEngine::UI::UIColor prefixBorderColor = + ::XCEngine::UI::UIColor(0.31f, 0.31f, 0.31f, 1.0f); + ::XCEngine::UI::UIColor axisXColor = + ::XCEngine::UI::UIColor(0.78f, 0.42f, 0.42f, 1.0f); + ::XCEngine::UI::UIColor axisYColor = + ::XCEngine::UI::UIColor(0.56f, 0.72f, 0.46f, 1.0f); + ::XCEngine::UI::UIColor axisZColor = + ::XCEngine::UI::UIColor(0.45f, 0.62f, 0.82f, 1.0f); + ::XCEngine::UI::UIColor axisWColor = + ::XCEngine::UI::UIColor(0.76f, 0.66f, 0.42f, 1.0f); + ::XCEngine::UI::UIColor arrowColor = + ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f); + ::XCEngine::UI::UIColor swatchBorderColor = + ::XCEngine::UI::UIColor(0.30f, 0.30f, 0.30f, 1.0f); + ::XCEngine::UI::UIColor swatchHoverBorderColor = + ::XCEngine::UI::UIColor(0.64f, 0.64f, 0.64f, 1.0f); + ::XCEngine::UI::UIColor swatchReadOnlyOverlayColor = + ::XCEngine::UI::UIColor(0.08f, 0.08f, 0.08f, 0.18f); + ::XCEngine::UI::UIColor popupColor = + ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f); + ::XCEngine::UI::UIColor popupBorderColor = + ::XCEngine::UI::UIColor(0.12f, 0.12f, 0.12f, 1.0f); + ::XCEngine::UI::UIColor popupHeaderColor = + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + ::XCEngine::UI::UIColor popupTitleColor = + ::XCEngine::UI::UIColor(0.93f, 0.93f, 0.93f, 1.0f); + ::XCEngine::UI::UIColor popupTextColor = + ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); + ::XCEngine::UI::UIColor popupTextMutedColor = + ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); + ::XCEngine::UI::UIColor previewBorderColor = + ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); + ::XCEngine::UI::UIColor previewBaseColor = + ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); + ::XCEngine::UI::UIColor checkerLightColor = + ::XCEngine::UI::UIColor(0.84f, 0.84f, 0.84f, 1.0f); + ::XCEngine::UI::UIColor checkerDarkColor = + ::XCEngine::UI::UIColor(0.55f, 0.55f, 0.55f, 1.0f); + ::XCEngine::UI::UIColor sliderBorderColor = + ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); + ::XCEngine::UI::UIColor numericBoxColor = + ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f); + ::XCEngine::UI::UIColor numericBoxBorderColor = + ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); + ::XCEngine::UI::UIColor numericBoxTextColor = + ::XCEngine::UI::UIColor(0.93f, 0.93f, 0.93f, 1.0f); + ::XCEngine::UI::UIColor closeButtonColor = + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f); + ::XCEngine::UI::UIColor closeButtonHoverColor = + ::XCEngine::UI::UIColor(0.25f, 0.25f, 0.25f, 1.0f); + ::XCEngine::UI::UIColor closeGlyphColor = + ::XCEngine::UI::UIColor(0.85f, 0.85f, 0.85f, 1.0f); + ::XCEngine::UI::UIColor handleFillColor = + ::XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f); + ::XCEngine::UI::UIColor handleStrokeColor = + ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 0.5f); + float controlTrailingInset = 9.0f; + float controlMinWidth = 88.0f; + float dropdownArrowWidth = 14.0f; + float vectorComponentMinWidth = 74.0f; + float vectorPrefixWidth = 18.0f; + float vectorPrefixGap = 5.0f; +}; + struct UIEditorFieldRowLayoutMetrics { float rowHeight = 22.0f; float horizontalPadding = 12.0f; @@ -19,6 +108,14 @@ struct UIEditorFieldRowLayout { ::XCEngine::UI::UIRect controlRect = {}; }; +const UIEditorInspectorFieldStyleTokens& GetUIEditorInspectorFieldStyleTokens(); + +bool AreUIEditorFieldMetricsEqual(float lhs, float rhs); + +bool AreUIEditorFieldColorsEqual( + const ::XCEngine::UI::UIColor& lhs, + const ::XCEngine::UI::UIColor& rhs); + UIEditorFieldRowLayout BuildUIEditorFieldRowLayout( const ::XCEngine::UI::UIRect& bounds, float minimumControlWidth, diff --git a/new_editor/include/XCEditor/Widgets/UIEditorPanelChrome.h b/new_editor/include/XCEditor/Widgets/UIEditorPanelChrome.h deleted file mode 100644 index 95509c23..00000000 --- a/new_editor/include/XCEditor/Widgets/UIEditorPanelChrome.h +++ /dev/null @@ -1,132 +0,0 @@ -#pragma once - -#include - -#include -#include - -namespace XCEngine::UI::Editor::Widgets { - -struct UIEditorPanelChromeState { - bool active = false; - bool hovered = false; -}; - -struct UIEditorPanelChromeText { - std::string_view title = {}; - std::string_view subtitle = {}; - std::string_view footer = {}; -}; - -struct UIEditorPanelChromeMetrics { - float cornerRounding = 18.0f; - float headerHeight = 42.0f; - float titleInsetX = 16.0f; - float titleInsetY = 12.0f; - float subtitleInsetY = 28.0f; - float footerInsetX = 16.0f; - float footerInsetBottom = 18.0f; - float activeBorderThickness = 2.0f; - float inactiveBorderThickness = 1.0f; -}; - -struct UIEditorPanelChromePalette { - ::XCEngine::UI::UIColor surfaceColor = - ::XCEngine::UI::UIColor(9.0f / 255.0f, 13.0f / 255.0f, 18.0f / 255.0f, 212.0f / 255.0f); - ::XCEngine::UI::UIColor borderColor = - ::XCEngine::UI::UIColor(53.0f / 255.0f, 72.0f / 255.0f, 96.0f / 255.0f, 1.0f); - ::XCEngine::UI::UIColor accentColor = - ::XCEngine::UI::UIColor(84.0f / 255.0f, 176.0f / 255.0f, 244.0f / 255.0f, 1.0f); - ::XCEngine::UI::UIColor hoveredAccentColor = - ::XCEngine::UI::UIColor(1.0f, 206.0f / 255.0f, 112.0f / 255.0f, 1.0f); - ::XCEngine::UI::UIColor headerColor = - ::XCEngine::UI::UIColor(13.0f / 255.0f, 20.0f / 255.0f, 28.0f / 255.0f, 242.0f / 255.0f); - ::XCEngine::UI::UIColor textPrimary = - ::XCEngine::UI::UIColor(232.0f / 255.0f, 238.0f / 255.0f, 246.0f / 255.0f, 1.0f); - ::XCEngine::UI::UIColor textSecondary = - ::XCEngine::UI::UIColor(150.0f / 255.0f, 164.0f / 255.0f, 184.0f / 255.0f, 1.0f); - ::XCEngine::UI::UIColor textMuted = - ::XCEngine::UI::UIColor(108.0f / 255.0f, 123.0f / 255.0f, 145.0f / 255.0f, 1.0f); -}; - -inline ::XCEngine::UI::UIRect BuildUIEditorPanelChromeHeaderRect( - const ::XCEngine::UI::UIRect& panelRect, - const UIEditorPanelChromeMetrics& metrics = {}) { - return ::XCEngine::UI::UIRect( - panelRect.x, - panelRect.y, - panelRect.width, - metrics.headerHeight); -} - -inline ::XCEngine::UI::UIColor ResolveUIEditorPanelChromeBorderColor( - const UIEditorPanelChromeState& state, - const UIEditorPanelChromePalette& palette = {}) { - if (state.active) { - return palette.accentColor; - } - - if (state.hovered) { - return palette.hoveredAccentColor; - } - - return palette.borderColor; -} - -inline float ResolveUIEditorPanelChromeBorderThickness( - const UIEditorPanelChromeState& state, - const UIEditorPanelChromeMetrics& metrics = {}) { - return state.active - ? metrics.activeBorderThickness - : metrics.inactiveBorderThickness; -} - -inline void AppendUIEditorPanelChromeBackground( - ::XCEngine::UI::UIDrawList& drawList, - const ::XCEngine::UI::UIRect& panelRect, - const UIEditorPanelChromeState& state, - const UIEditorPanelChromePalette& palette = {}, - const UIEditorPanelChromeMetrics& metrics = {}) { - drawList.AddFilledRect(panelRect, palette.surfaceColor, metrics.cornerRounding); - drawList.AddRectOutline( - panelRect, - ResolveUIEditorPanelChromeBorderColor(state, palette), - ResolveUIEditorPanelChromeBorderThickness(state, metrics), - metrics.cornerRounding); - drawList.AddFilledRect( - BuildUIEditorPanelChromeHeaderRect(panelRect, metrics), - palette.headerColor, - metrics.cornerRounding); -} - -inline void AppendUIEditorPanelChromeForeground( - ::XCEngine::UI::UIDrawList& drawList, - const ::XCEngine::UI::UIRect& panelRect, - const UIEditorPanelChromeText& text, - const UIEditorPanelChromePalette& palette = {}, - const UIEditorPanelChromeMetrics& metrics = {}) { - if (!text.title.empty()) { - drawList.AddText( - ::XCEngine::UI::UIPoint(panelRect.x + metrics.titleInsetX, panelRect.y + metrics.titleInsetY), - std::string(text.title), - palette.textPrimary); - } - - if (!text.subtitle.empty()) { - drawList.AddText( - ::XCEngine::UI::UIPoint(panelRect.x + metrics.titleInsetX, panelRect.y + metrics.subtitleInsetY), - std::string(text.subtitle), - palette.textSecondary); - } - - if (!text.footer.empty()) { - drawList.AddText( - ::XCEngine::UI::UIPoint( - panelRect.x + metrics.footerInsetX, - panelRect.y + panelRect.height - metrics.footerInsetBottom), - std::string(text.footer), - palette.textMuted); - } -} - -} // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/src/Collections/UIEditorInlineRenameSession.cpp b/new_editor/src/Collections/UIEditorInlineRenameSession.cpp new file mode 100644 index 00000000..e7792774 --- /dev/null +++ b/new_editor/src/Collections/UIEditorInlineRenameSession.cpp @@ -0,0 +1,114 @@ +#include + +#include + +namespace XCEngine::UI::Editor { + +Widgets::UIEditorTextFieldMetrics BuildUIEditorInlineRenameTextFieldMetrics( + const ::XCEngine::UI::UIRect& bounds, + const Widgets::UIEditorTextFieldMetrics& metrics) { + Widgets::UIEditorTextFieldMetrics resolved = metrics; + resolved.rowHeight = bounds.height > 0.0f ? bounds.height : metrics.rowHeight; + resolved.horizontalPadding = 0.0f; + resolved.labelControlGap = 0.0f; + resolved.controlColumnStart = 0.0f; + resolved.controlTrailingInset = 0.0f; + resolved.valueBoxMinWidth = bounds.width; + return resolved; +} + +namespace { + +void ResetSession(UIEditorInlineRenameSessionState& state) { + state = {}; +} + +void BeginSession( + UIEditorInlineRenameSessionState& state, + const UIEditorInlineRenameSessionRequest& request, + UIEditorInlineRenameSessionResult& result) { + ResetSession(state); + state.active = true; + state.itemId = request.itemId; + state.textFieldSpec.fieldId = request.itemId; + state.textFieldSpec.label.clear(); + state.textFieldSpec.value = request.initialText; + state.textFieldSpec.readOnly = false; + state.textFieldInteraction.textFieldState.focused = true; + state.textFieldInteraction.textFieldState.editing = true; + state.textFieldInteraction.textFieldState.displayText = request.initialText; + state.textFieldInteraction.textInputState.value = request.initialText; + state.textFieldInteraction.textInputState.caret = request.initialText.size(); + state.textFieldInteraction.editModel.BeginEdit(request.itemId, request.initialText); + + result.sessionStarted = true; + result.active = true; + result.itemId = request.itemId; + result.valueBefore = request.initialText; + result.valueAfter = request.initialText; +} + +} // namespace + +UIEditorInlineRenameSessionFrame UpdateUIEditorInlineRenameSession( + UIEditorInlineRenameSessionState& state, + const UIEditorInlineRenameSessionRequest& request, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + const Widgets::UIEditorTextFieldMetrics& metrics) { + UIEditorInlineRenameSessionResult result = {}; + + if (request.beginSession && + !request.itemId.empty() && + (!state.active || state.itemId != request.itemId)) { + BeginSession(state, request, result); + } + + Widgets::UIEditorTextFieldLayout layout = {}; + if (!state.active) { + return { + std::move(layout), + std::move(result) + }; + } + + const Widgets::UIEditorTextFieldMetrics inlineMetrics = + BuildUIEditorInlineRenameTextFieldMetrics(request.bounds, metrics); + const std::string valueBefore = state.textFieldSpec.value; + UIEditorTextFieldInteractionFrame textFieldFrame = + UpdateUIEditorTextFieldInteraction( + state.textFieldInteraction, + state.textFieldSpec, + request.bounds, + inputEvents, + inlineMetrics); + + layout = textFieldFrame.layout; + result.textFieldResult = textFieldFrame.result; + result.itemId = state.itemId; + result.active = state.active; + result.consumed = textFieldFrame.result.consumed; + result.valueBefore = textFieldFrame.result.valueBefore.empty() + ? valueBefore + : textFieldFrame.result.valueBefore; + result.valueAfter = state.textFieldSpec.value; + + if (textFieldFrame.result.editCommitted) { + result.sessionCommitted = true; + result.valueChanged = textFieldFrame.result.valueChanged; + result.valueAfter = state.textFieldSpec.value; + ResetSession(state); + result.active = false; + } else if (textFieldFrame.result.editCanceled) { + result.sessionCanceled = true; + result.valueAfter = result.valueBefore; + ResetSession(state); + result.active = false; + } + + return { + std::move(layout), + std::move(result) + }; +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Widgets/UIEditorListView.cpp b/new_editor/src/Collections/UIEditorListView.cpp similarity index 99% rename from new_editor/src/Widgets/UIEditorListView.cpp rename to new_editor/src/Collections/UIEditorListView.cpp index d6978b33..1637c37a 100644 --- a/new_editor/src/Widgets/UIEditorListView.cpp +++ b/new_editor/src/Collections/UIEditorListView.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/new_editor/src/Core/UIEditorListViewInteraction.cpp b/new_editor/src/Collections/UIEditorListViewInteraction.cpp similarity index 50% rename from new_editor/src/Core/UIEditorListViewInteraction.cpp rename to new_editor/src/Collections/UIEditorListViewInteraction.cpp index 3d0e669a..d7b7563e 100644 --- a/new_editor/src/Core/UIEditorListViewInteraction.cpp +++ b/new_editor/src/Collections/UIEditorListViewInteraction.cpp @@ -1,7 +1,8 @@ -#include +#include #include +#include #include namespace XCEngine::UI::Editor { @@ -33,10 +34,6 @@ bool ShouldUsePointerPosition(const UIInputEvent& event) { } } -bool HasNavigationModifiers(const ::XCEngine::UI::UIInputModifiers& modifiers) { - return modifiers.shift || modifiers.control || modifiers.alt || modifiers.super; -} - void SyncHoverTarget( UIEditorListViewInteractionState& state, const Widgets::UIEditorListViewLayout& layout, @@ -76,21 +73,156 @@ void SyncKeyboardNavigation( } } +void SyncSelectionAnchor( + UIEditorListViewInteractionState& state, + const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel) { + if (!selectionModel.HasSelection()) { + state.selectionAnchorId.clear(); + return; + } + + if (state.selectionAnchorId.empty() || + !selectionModel.IsSelected(state.selectionAnchorId)) { + state.selectionAnchorId = selectionModel.GetSelectedId(); + } +} + +bool SelectItem( + UIEditorListViewInteractionState& state, + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + const std::vector& items, + std::size_t itemIndex, + UIEditorListViewInteractionResult& result, + bool markKeyboardNavigation) { + if (itemIndex >= items.size()) { + return false; + } + + const Widgets::UIEditorListViewItem& item = items[itemIndex]; + result.selectionChanged = selectionModel.SetSelection(item.itemId); + result.selectedItemId = item.itemId; + result.selectedIndex = itemIndex; + result.keyboardNavigated = markKeyboardNavigation; + result.consumed = true; + state.keyboardNavigation.SetCurrentIndex(itemIndex); + state.selectionAnchorId = item.itemId; + return true; +} + +bool ToggleItemSelection( + UIEditorListViewInteractionState& state, + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + const std::vector& items, + std::size_t itemIndex, + UIEditorListViewInteractionResult& result) { + if (itemIndex >= items.size()) { + return false; + } + + const Widgets::UIEditorListViewItem& item = items[itemIndex]; + result.selectionChanged = selectionModel.ToggleSelectionMembership(item.itemId, true); + result.selectedItemId = item.itemId; + result.selectedIndex = itemIndex; + result.consumed = true; + state.keyboardNavigation.SetCurrentIndex(itemIndex); + state.selectionAnchorId = item.itemId; + return true; +} + +bool SelectItemRange( + UIEditorListViewInteractionState& state, + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + const std::vector& items, + std::size_t itemIndex, + UIEditorListViewInteractionResult& result, + bool markKeyboardNavigation) { + if (itemIndex >= items.size()) { + return false; + } + + std::size_t anchorIndex = UIEditorListViewInvalidIndex; + if (!state.selectionAnchorId.empty()) { + anchorIndex = FindUIEditorListViewItemIndex(items, state.selectionAnchorId); + } + if (anchorIndex == UIEditorListViewInvalidIndex) { + return SelectItem( + state, + selectionModel, + items, + itemIndex, + result, + markKeyboardNavigation); + } + + const std::size_t rangeBegin = (std::min)(anchorIndex, itemIndex); + const std::size_t rangeEnd = (std::max)(anchorIndex, itemIndex); + std::vector selectedIds = {}; + selectedIds.reserve(rangeEnd - rangeBegin + 1u); + for (std::size_t index = rangeBegin; index <= rangeEnd; ++index) { + selectedIds.push_back(items[index].itemId); + } + + const Widgets::UIEditorListViewItem& item = items[itemIndex]; + result.selectionChanged = selectionModel.SetSelections(std::move(selectedIds), item.itemId); + result.selectedItemId = item.itemId; + result.selectedIndex = itemIndex; + result.keyboardNavigated = markKeyboardNavigation; + result.consumed = true; + state.keyboardNavigation.SetCurrentIndex(itemIndex, false); + return true; +} + bool ApplyKeyboardNavigation( UIEditorListViewInteractionState& state, - std::int32_t keyCode) { + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + const std::vector& items, + std::int32_t keyCode, + UIEditorListViewInteractionResult& result, + bool extendSelectionRange) { switch (static_cast(keyCode)) { case KeyCode::Up: - return state.keyboardNavigation.MovePrevious(); + if (!state.keyboardNavigation.MovePrevious()) { + return false; + } + break; case KeyCode::Down: - return state.keyboardNavigation.MoveNext(); + if (!state.keyboardNavigation.MoveNext()) { + return false; + } + break; case KeyCode::Home: - return state.keyboardNavigation.MoveHome(); + if (!state.keyboardNavigation.MoveHome()) { + return false; + } + break; case KeyCode::End: - return state.keyboardNavigation.MoveEnd(); + if (!state.keyboardNavigation.MoveEnd()) { + return false; + } + break; default: return false; } + + if (!state.keyboardNavigation.HasCurrentIndex()) { + return false; + } + + return extendSelectionRange + ? SelectItemRange( + state, + selectionModel, + items, + state.keyboardNavigation.GetCurrentIndex(), + result, + true) + : SelectItem( + state, + selectionModel, + items, + state.keyboardNavigation.GetCurrentIndex(), + result, + true); } } // namespace @@ -105,6 +237,7 @@ UIEditorListViewInteractionFrame UpdateUIEditorListViewInteraction( Widgets::UIEditorListViewLayout layout = BuildUIEditorListViewLayout(bounds, items, metrics); SyncKeyboardNavigation(state, selectionModel, items); + SyncSelectionAnchor(state, selectionModel); SyncHoverTarget(state, layout, items); UIEditorListViewInteractionResult interactionResult = {}; @@ -180,38 +313,79 @@ UIEditorListViewInteractionFrame UpdateUIEditorListViewInteraction( const Widgets::UIEditorListViewItem& item = items[hitTarget.itemIndex]; if (event.pointerButton == UIPointerButton::Left && hitTarget.kind == UIEditorListViewHitTargetKind::Row) { - eventResult.selectionChanged = selectionModel.SetSelection(item.itemId); - eventResult.selectedItemId = item.itemId; - eventResult.selectedIndex = hitTarget.itemIndex; - eventResult.consumed = true; + if (event.modifiers.shift) { + SelectItemRange( + state, + selectionModel, + items, + hitTarget.itemIndex, + eventResult, + false); + } else if (event.modifiers.control) { + ToggleItemSelection( + state, + selectionModel, + items, + hitTarget.itemIndex, + eventResult); + } else { + SelectItem( + state, + selectionModel, + items, + hitTarget.itemIndex, + eventResult, + false); + } state.listViewState.focused = true; - state.keyboardNavigation.SetCurrentIndex(hitTarget.itemIndex); } else if (event.pointerButton == UIPointerButton::Right && hitTarget.kind == UIEditorListViewHitTargetKind::Row) { - eventResult.selectionChanged = selectionModel.SetSelection(item.itemId); - eventResult.selectedItemId = item.itemId; - eventResult.selectedIndex = hitTarget.itemIndex; + if (selectionModel.IsSelected(item.itemId)) { + selectionModel.SetPrimarySelection(item.itemId); + eventResult.selectionChanged = false; + eventResult.selectedItemId = item.itemId; + eventResult.selectedIndex = hitTarget.itemIndex; + eventResult.consumed = true; + state.keyboardNavigation.SetCurrentIndex(hitTarget.itemIndex); + state.selectionAnchorId = item.itemId; + } else { + SelectItem( + state, + selectionModel, + items, + hitTarget.itemIndex, + eventResult, + false); + } eventResult.secondaryClicked = true; eventResult.consumed = true; state.listViewState.focused = true; - state.keyboardNavigation.SetCurrentIndex(hitTarget.itemIndex); } break; } case UIInputEventType::KeyDown: - if (state.listViewState.focused && !HasNavigationModifiers(event.modifiers)) { - if (ApplyKeyboardNavigation(state, event.keyCode) && - state.keyboardNavigation.HasCurrentIndex()) { - const std::size_t currentIndex = state.keyboardNavigation.GetCurrentIndex(); - if (currentIndex < items.size()) { - eventResult.selectionChanged = - selectionModel.SetSelection(items[currentIndex].itemId); - eventResult.keyboardNavigated = true; - eventResult.selectedItemId = items[currentIndex].itemId; - eventResult.selectedIndex = currentIndex; - eventResult.consumed = true; - } + if (state.listViewState.focused && + !event.modifiers.control && + !event.modifiers.alt && + !event.modifiers.super) { + if (event.keyCode == static_cast(KeyCode::F2) && + selectionModel.HasSelection()) { + const std::string& selectedItemId = selectionModel.GetSelectedId(); + eventResult.renameRequested = true; + eventResult.renameItemId = selectedItemId; + eventResult.selectedItemId = selectedItemId; + eventResult.selectedIndex = + FindUIEditorListViewItemIndex(items, selectedItemId); + eventResult.consumed = true; + } else if (ApplyKeyboardNavigation( + state, + selectionModel, + items, + event.keyCode, + eventResult, + event.modifiers.shift)) { + eventResult.consumed = true; } } break; @@ -222,6 +396,7 @@ UIEditorListViewInteractionFrame UpdateUIEditorListViewInteraction( layout = BuildUIEditorListViewLayout(bounds, items, metrics); SyncKeyboardNavigation(state, selectionModel, items); + SyncSelectionAnchor(state, selectionModel); SyncHoverTarget(state, layout, items); if (eventResult.hitTarget.kind == UIEditorListViewHitTargetKind::None && state.hasPointerPosition) { @@ -232,14 +407,17 @@ UIEditorListViewInteractionFrame UpdateUIEditorListViewInteraction( eventResult.selectionChanged || eventResult.keyboardNavigated || eventResult.secondaryClicked || + eventResult.renameRequested || eventResult.hitTarget.kind != UIEditorListViewHitTargetKind::None || - !eventResult.selectedItemId.empty()) { + !eventResult.selectedItemId.empty() || + !eventResult.renameItemId.empty()) { interactionResult = std::move(eventResult); } } layout = BuildUIEditorListViewLayout(bounds, items, metrics); SyncKeyboardNavigation(state, selectionModel, items); + SyncSelectionAnchor(state, selectionModel); SyncHoverTarget(state, layout, items); if (interactionResult.hitTarget.kind == UIEditorListViewHitTargetKind::None && state.hasPointerPosition) { diff --git a/new_editor/src/Widgets/UIEditorScrollView.cpp b/new_editor/src/Collections/UIEditorScrollView.cpp similarity index 79% rename from new_editor/src/Widgets/UIEditorScrollView.cpp rename to new_editor/src/Collections/UIEditorScrollView.cpp index 22740838..ae181352 100644 --- a/new_editor/src/Widgets/UIEditorScrollView.cpp +++ b/new_editor/src/Collections/UIEditorScrollView.cpp @@ -1,4 +1,4 @@ -#include +#include #include @@ -14,6 +14,47 @@ float ClampRange(float value, float minValue, float maxValue) { return (std::min)((std::max)(value, minValue), maxValue); } +::XCEngine::UI::UIColor LerpColor( + const ::XCEngine::UI::UIColor& from, + const ::XCEngine::UI::UIColor& to, + float factor) { + const float t = ClampRange(factor, 0.0f, 1.0f); + return ::XCEngine::UI::UIColor( + from.r + (to.r - from.r) * t, + from.g + (to.g - from.g) * t, + from.b + (to.b - from.b) * t, + from.a + (to.a - from.a) * t); +} + +::XCEngine::UI::UIColor ResolveBorderColor( + const UIEditorScrollViewState& state, + const UIEditorScrollViewPalette& palette) { + if (state.focused) { + return palette.focusedBorderColor; + } + + if (state.hovered || state.scrollbarHovered) { + return LerpColor(palette.borderColor, palette.focusedBorderColor, 0.45f); + } + + return palette.borderColor; +} + +float ResolveBorderThickness( + const UIEditorScrollViewState& state, + const UIEditorScrollViewMetrics& metrics) { + if (state.focused) { + return metrics.focusedBorderThickness; + } + + if (state.hovered || state.scrollbarHovered) { + return metrics.borderThickness + + (metrics.focusedBorderThickness - metrics.borderThickness) * 0.5f; + } + + return metrics.borderThickness; +} + } // namespace bool IsUIEditorScrollViewPointInside( @@ -124,8 +165,8 @@ void AppendUIEditorScrollViewBackground( drawList.AddFilledRect(layout.bounds, palette.surfaceColor, metrics.cornerRounding); drawList.AddRectOutline( layout.bounds, - state.focused ? palette.focusedBorderColor : palette.borderColor, - state.focused ? metrics.focusedBorderThickness : metrics.borderThickness, + ResolveBorderColor(state, palette), + ResolveBorderThickness(state, metrics), metrics.cornerRounding); if (!layout.hasScrollbar) { diff --git a/new_editor/src/Core/UIEditorScrollViewInteraction.cpp b/new_editor/src/Collections/UIEditorScrollViewInteraction.cpp similarity index 99% rename from new_editor/src/Core/UIEditorScrollViewInteraction.cpp rename to new_editor/src/Collections/UIEditorScrollViewInteraction.cpp index cc91805d..3efd330a 100644 --- a/new_editor/src/Core/UIEditorScrollViewInteraction.cpp +++ b/new_editor/src/Collections/UIEditorScrollViewInteraction.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Widgets/UIEditorTabStrip.cpp b/new_editor/src/Collections/UIEditorTabStrip.cpp similarity index 93% rename from new_editor/src/Widgets/UIEditorTabStrip.cpp rename to new_editor/src/Collections/UIEditorTabStrip.cpp index d560d6c8..0d592b2a 100644 --- a/new_editor/src/Widgets/UIEditorTabStrip.cpp +++ b/new_editor/src/Collections/UIEditorTabStrip.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -34,6 +34,20 @@ bool IsPointInsideRect( point.y <= rect.y + rect.height; } +float ResolveStripRounding(const UIEditorTabStripMetrics& metrics) { + return (std::max)( + (std::min)(ClampNonNegative(metrics.layoutMetrics.headerHeight) * 0.25f, kStripRounding), + 0.0f); +} + +float ResolveTabRounding(const UIEditorTabStripMetrics& metrics) { + return (std::max)(ResolveStripRounding(metrics) - 1.0f, 0.0f); +} + +float ResolveCloseButtonRounding(const UIEditorTabStripMetrics& metrics) { + return (std::min)(ClampNonNegative(metrics.closeButtonExtent) * 0.35f, 5.0f); +} + std::size_t ResolveSelectedIndex( std::size_t itemCount, std::size_t selectedIndex) { @@ -291,18 +305,20 @@ void AppendUIEditorTabStripBackground( const UIEditorTabStripState& state, const UIEditorTabStripPalette& palette, const UIEditorTabStripMetrics& metrics) { - drawList.AddFilledRect(layout.bounds, palette.stripBackgroundColor, kStripRounding); + const float stripRounding = ResolveStripRounding(metrics); + const float tabRounding = ResolveTabRounding(metrics); + drawList.AddFilledRect(layout.bounds, palette.stripBackgroundColor, stripRounding); if (layout.contentRect.height > 0.0f) { - drawList.AddFilledRect(layout.contentRect, palette.contentBackgroundColor, kStripRounding); + drawList.AddFilledRect(layout.contentRect, palette.contentBackgroundColor, stripRounding); } if (layout.headerRect.height > 0.0f) { - drawList.AddFilledRect(layout.headerRect, palette.headerBackgroundColor, kStripRounding); + drawList.AddFilledRect(layout.headerRect, palette.headerBackgroundColor, stripRounding); } drawList.AddRectOutline( layout.bounds, ResolveStripBorderColor(state, palette), ResolveStripBorderThickness(state, metrics), - kStripRounding); + stripRounding); for (std::size_t index = 0; index < layout.tabHeaderRects.size(); ++index) { const bool selected = layout.selectedIndex == index; @@ -310,12 +326,12 @@ void AppendUIEditorTabStripBackground( drawList.AddFilledRect( layout.tabHeaderRects[index], ResolveTabFillColor(selected, hovered, palette), - kTabRounding); + tabRounding); drawList.AddRectOutline( layout.tabHeaderRects[index], ResolveTabBorderColor(selected, hovered, state.focused, palette), ResolveTabBorderThickness(selected, state.focused, metrics), - kTabRounding); + tabRounding); } } @@ -361,15 +377,16 @@ void AppendUIEditorTabStripForeground( const bool closeHovered = state.closeHoveredIndex == index; const UIRect& closeRect = layout.closeButtonRects[index]; + const float closeRounding = ResolveCloseButtonRounding(metrics); drawList.AddFilledRect( closeRect, closeHovered ? palette.closeButtonHoveredColor : palette.closeButtonColor, - 4.0f); + closeRounding); drawList.AddRectOutline( closeRect, palette.closeButtonBorderColor, 1.0f, - 4.0f); + closeRounding); drawList.AddText( UIPoint( closeRect.x + (std::max)(0.0f, (closeRect.width - 7.0f) * 0.5f), diff --git a/new_editor/src/Collections/UIEditorTabStripInteraction.cpp b/new_editor/src/Collections/UIEditorTabStripInteraction.cpp new file mode 100644 index 00000000..51925558 --- /dev/null +++ b/new_editor/src/Collections/UIEditorTabStripInteraction.cpp @@ -0,0 +1,329 @@ +#include + +#include + +#include + +namespace XCEngine::UI::Editor { + +namespace { + +using ::XCEngine::Input::KeyCode; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPointerButton; +using Widgets::BuildUIEditorTabStripLayout; +using Widgets::HitTestUIEditorTabStrip; +using Widgets::ResolveUIEditorTabStripSelectedIndex; +using Widgets::UIEditorTabStripHitTarget; +using Widgets::UIEditorTabStripHitTargetKind; +using Widgets::UIEditorTabStripInvalidIndex; + +bool ShouldUsePointerPosition(const UIInputEvent& event) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + return true; + default: + return false; + } +} + +bool HasNavigationModifiers(const ::XCEngine::UI::UIInputModifiers& modifiers) { + return modifiers.shift || modifiers.control || modifiers.alt || modifiers.super; +} + +bool IsPointInside(const ::XCEngine::UI::UIRect& rect, const ::XCEngine::UI::UIPoint& point) { + return point.x >= rect.x && + point.x <= rect.x + rect.width && + point.y >= rect.y && + point.y <= rect.y + rect.height; +} + +bool AreEquivalentTargets( + const UIEditorTabStripHitTarget& lhs, + const UIEditorTabStripHitTarget& rhs) { + return lhs.kind == rhs.kind && lhs.index == rhs.index; +} + +void ClearHoverState(UIEditorTabStripInteractionState& state) { + state.tabStripState.hoveredIndex = UIEditorTabStripInvalidIndex; + state.tabStripState.closeHoveredIndex = UIEditorTabStripInvalidIndex; +} + +void SyncHoverTarget( + UIEditorTabStripInteractionState& state, + const Widgets::UIEditorTabStripLayout& layout) { + ClearHoverState(state); + if (!state.hasPointerPosition) { + return; + } + + const UIEditorTabStripHitTarget hitTarget = + HitTestUIEditorTabStrip(layout, state.tabStripState, state.pointerPosition); + if (hitTarget.index == UIEditorTabStripInvalidIndex) { + return; + } + + switch (hitTarget.kind) { + case UIEditorTabStripHitTargetKind::CloseButton: + state.tabStripState.hoveredIndex = hitTarget.index; + state.tabStripState.closeHoveredIndex = hitTarget.index; + break; + + case UIEditorTabStripHitTargetKind::Tab: + state.tabStripState.hoveredIndex = hitTarget.index; + break; + + default: + break; + } +} + +void SyncSelectionState( + UIEditorTabStripInteractionState& state, + std::string_view selectedTabId, + const std::vector& items) { + state.navigationModel.SetItemCount(items.size()); + + const std::size_t fallbackIndex = + state.navigationModel.HasSelection() + ? state.navigationModel.GetSelectedIndex() + : UIEditorTabStripInvalidIndex; + const std::size_t resolvedSelectedIndex = + ResolveUIEditorTabStripSelectedIndex(items, selectedTabId, fallbackIndex); + if (resolvedSelectedIndex != UIEditorTabStripInvalidIndex) { + state.navigationModel.SetSelectedIndex(resolvedSelectedIndex); + } + + state.tabStripState.selectedIndex = + state.navigationModel.HasSelection() + ? state.navigationModel.GetSelectedIndex() + : UIEditorTabStripInvalidIndex; +} + +bool SelectTab( + UIEditorTabStripInteractionState& state, + std::string& selectedTabId, + const std::vector& items, + std::size_t selectedIndex, + UIEditorTabStripInteractionResult& result, + bool keyboardNavigated) { + if (selectedIndex >= items.size()) { + return false; + } + + state.navigationModel.SetSelectedIndex(selectedIndex); + state.tabStripState.selectedIndex = selectedIndex; + + const std::string& tabId = items[selectedIndex].tabId; + result.selectionChanged = selectedTabId != tabId; + selectedTabId = tabId; + result.keyboardNavigated = keyboardNavigated; + result.selectedTabId = tabId; + result.selectedIndex = selectedIndex; + result.consumed = true; + return true; +} + +bool ApplyKeyboardNavigation( + UIEditorTabStripInteractionState& state, + std::int32_t keyCode) { + switch (static_cast(keyCode)) { + case KeyCode::Left: + return state.navigationModel.SelectPrevious(); + case KeyCode::Right: + return state.navigationModel.SelectNext(); + case KeyCode::Home: + return state.navigationModel.SelectFirst(); + case KeyCode::End: + return state.navigationModel.SelectLast(); + default: + return false; + } +} + +} // namespace + +UIEditorTabStripInteractionFrame UpdateUIEditorTabStripInteraction( + UIEditorTabStripInteractionState& state, + std::string& selectedTabId, + const ::XCEngine::UI::UIRect& bounds, + const std::vector& items, + const std::vector& inputEvents, + const Widgets::UIEditorTabStripMetrics& metrics) { + SyncSelectionState(state, selectedTabId, items); + Widgets::UIEditorTabStripLayout layout = + BuildUIEditorTabStripLayout(bounds, items, state.tabStripState, metrics); + SyncHoverTarget(state, layout); + + UIEditorTabStripInteractionResult interactionResult = {}; + for (const UIInputEvent& event : inputEvents) { + if (ShouldUsePointerPosition(event)) { + state.pointerPosition = event.position; + state.hasPointerPosition = true; + } else if (event.type == UIInputEventType::PointerLeave) { + state.hasPointerPosition = false; + } + + UIEditorTabStripInteractionResult eventResult = {}; + eventResult.hitTarget = + state.hasPointerPosition + ? HitTestUIEditorTabStrip(layout, state.tabStripState, state.pointerPosition) + : UIEditorTabStripHitTarget {}; + + switch (event.type) { + case UIInputEventType::FocusGained: + state.tabStripState.focused = true; + break; + + case UIInputEventType::FocusLost: + state.tabStripState.focused = false; + state.hasPointerPosition = false; + state.pressedTarget = {}; + ClearHoverState(state); + break; + + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerLeave: + break; + + case UIInputEventType::PointerButtonDown: { + if (event.pointerButton != UIPointerButton::Left) { + break; + } + + state.pressedTarget = eventResult.hitTarget; + if (eventResult.hitTarget.kind != UIEditorTabStripHitTargetKind::None || + (state.hasPointerPosition && IsPointInside(layout.bounds, state.pointerPosition))) { + state.tabStripState.focused = true; + eventResult.consumed = true; + } else { + state.tabStripState.focused = false; + state.pressedTarget = {}; + } + break; + } + + case UIInputEventType::PointerButtonUp: { + if (event.pointerButton != UIPointerButton::Left) { + break; + } + + const bool insideStrip = + state.hasPointerPosition && IsPointInside(layout.bounds, state.pointerPosition); + const bool matchedPressedTarget = + AreEquivalentTargets(state.pressedTarget, eventResult.hitTarget); + + if (matchedPressedTarget) { + switch (eventResult.hitTarget.kind) { + case UIEditorTabStripHitTargetKind::CloseButton: + if (eventResult.hitTarget.index < items.size() && + items[eventResult.hitTarget.index].closable) { + eventResult.closeRequested = true; + eventResult.closedTabId = items[eventResult.hitTarget.index].tabId; + eventResult.closedIndex = eventResult.hitTarget.index; + eventResult.consumed = true; + state.tabStripState.focused = true; + } else if (insideStrip) { + state.tabStripState.focused = true; + eventResult.consumed = true; + } + break; + + case UIEditorTabStripHitTargetKind::Tab: + SelectTab( + state, + selectedTabId, + items, + eventResult.hitTarget.index, + eventResult, + false); + state.tabStripState.focused = true; + break; + + case UIEditorTabStripHitTargetKind::HeaderBackground: + case UIEditorTabStripHitTargetKind::Content: + state.tabStripState.focused = true; + eventResult.consumed = true; + break; + + case UIEditorTabStripHitTargetKind::None: + default: + if (!insideStrip) { + state.tabStripState.focused = false; + } + break; + } + } else if (!insideStrip) { + state.tabStripState.focused = false; + } else if (eventResult.hitTarget.kind == UIEditorTabStripHitTargetKind::HeaderBackground || + eventResult.hitTarget.kind == UIEditorTabStripHitTargetKind::Content) { + state.tabStripState.focused = true; + eventResult.consumed = true; + } + + state.pressedTarget = {}; + break; + } + + case UIInputEventType::KeyDown: + if (state.tabStripState.focused && + !HasNavigationModifiers(event.modifiers) && + ApplyKeyboardNavigation(state, event.keyCode) && + state.navigationModel.HasSelection()) { + const std::size_t selectedIndex = state.navigationModel.GetSelectedIndex(); + SelectTab( + state, + selectedTabId, + items, + selectedIndex, + eventResult, + true); + } + break; + + default: + break; + } + + SyncSelectionState(state, selectedTabId, items); + layout = BuildUIEditorTabStripLayout(bounds, items, state.tabStripState, metrics); + SyncHoverTarget(state, layout); + if (eventResult.hitTarget.kind == UIEditorTabStripHitTargetKind::None && + state.hasPointerPosition) { + eventResult.hitTarget = + HitTestUIEditorTabStrip(layout, state.tabStripState, state.pointerPosition); + } + + if (eventResult.consumed || + eventResult.selectionChanged || + eventResult.closeRequested || + eventResult.keyboardNavigated || + eventResult.hitTarget.kind != UIEditorTabStripHitTargetKind::None || + !eventResult.selectedTabId.empty() || + !eventResult.closedTabId.empty()) { + interactionResult = std::move(eventResult); + } + } + + SyncSelectionState(state, selectedTabId, items); + layout = BuildUIEditorTabStripLayout(bounds, items, state.tabStripState, metrics); + SyncHoverTarget(state, layout); + if (interactionResult.hitTarget.kind == UIEditorTabStripHitTargetKind::None && + state.hasPointerPosition) { + interactionResult.hitTarget = + HitTestUIEditorTabStrip(layout, state.tabStripState, state.pointerPosition); + } + + return { + std::move(layout), + std::move(interactionResult), + state.tabStripState.focused + }; +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Widgets/UIEditorTreeView.cpp b/new_editor/src/Collections/UIEditorTreeView.cpp similarity index 99% rename from new_editor/src/Widgets/UIEditorTreeView.cpp rename to new_editor/src/Collections/UIEditorTreeView.cpp index 7f52525d..34deda3d 100644 --- a/new_editor/src/Widgets/UIEditorTreeView.cpp +++ b/new_editor/src/Collections/UIEditorTreeView.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/new_editor/src/Collections/UIEditorTreeViewInteraction.cpp b/new_editor/src/Collections/UIEditorTreeViewInteraction.cpp new file mode 100644 index 00000000..476aec20 --- /dev/null +++ b/new_editor/src/Collections/UIEditorTreeViewInteraction.cpp @@ -0,0 +1,677 @@ +#include + +#include + +#include + +namespace XCEngine::UI::Editor { + +namespace { + +using ::XCEngine::Input::KeyCode; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPointerButton; +using Widgets::BuildUIEditorTreeViewLayout; +using Widgets::DoesUIEditorTreeViewItemHaveChildren; +using Widgets::FindUIEditorTreeViewFirstVisibleChildItemIndex; +using Widgets::FindUIEditorTreeViewItemIndex; +using Widgets::FindUIEditorTreeViewParentItemIndex; +using Widgets::HitTestUIEditorTreeView; +using Widgets::IsUIEditorTreeViewPointInside; +using Widgets::UIEditorTreeViewHitTarget; +using Widgets::UIEditorTreeViewHitTargetKind; +using Widgets::UIEditorTreeViewInvalidIndex; + +bool ShouldUsePointerPosition(const UIInputEvent& event) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + return true; + default: + return false; + } +} + +bool HasNavigationModifiers(const ::XCEngine::UI::UIInputModifiers& modifiers) { + return modifiers.shift || modifiers.control || modifiers.alt || modifiers.super; +} + +void SyncHoverTarget( + UIEditorTreeViewInteractionState& state, + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items) { + state.treeViewState.hoveredItemId.clear(); + if (!state.hasPointerPosition) { + return; + } + + const UIEditorTreeViewHitTarget hitTarget = + HitTestUIEditorTreeView(layout, state.pointerPosition); + if (hitTarget.itemIndex < items.size()) { + state.treeViewState.hoveredItemId = items[hitTarget.itemIndex].itemId; + } +} + +std::size_t FindVisibleIndexForItemIndex( + const Widgets::UIEditorTreeViewLayout& layout, + std::size_t itemIndex) { + for (std::size_t visibleIndex = 0u; visibleIndex < layout.visibleItemIndices.size(); ++visibleIndex) { + if (layout.visibleItemIndices[visibleIndex] == itemIndex) { + return visibleIndex; + } + } + + return UIEditorTreeViewInvalidIndex; +} + +std::size_t FindVisibleIndexForItemId( + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::string_view itemId) { + const std::size_t itemIndex = FindUIEditorTreeViewItemIndex(items, itemId); + if (itemIndex == UIEditorTreeViewInvalidIndex) { + return UIEditorTreeViewInvalidIndex; + } + + return FindVisibleIndexForItemIndex(layout, itemIndex); +} + +void SyncKeyboardNavigation( + UIEditorTreeViewInteractionState& state, + const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items) { + state.keyboardNavigation.SetItemCount(layout.visibleItemIndices.size()); + state.keyboardNavigation.ClampToItemCount(); + + if (!selectionModel.HasSelection()) { + return; + } + + const std::size_t selectedVisibleIndex = + FindVisibleIndexForItemId(layout, items, selectionModel.GetSelectedId()); + if (selectedVisibleIndex == UIEditorTreeViewInvalidIndex) { + return; + } + + if (!state.keyboardNavigation.HasCurrentIndex() || + state.keyboardNavigation.GetCurrentIndex() != selectedVisibleIndex) { + state.keyboardNavigation.SetCurrentIndex(selectedVisibleIndex); + } +} + +void SyncSelectionAnchor( + UIEditorTreeViewInteractionState& state, + const ::XCEngine::UI::Widgets::UISelectionModel& selectionModel) { + if (!selectionModel.HasSelection()) { + state.selectionAnchorId.clear(); + return; + } + + if (state.selectionAnchorId.empty() || + !selectionModel.IsSelected(state.selectionAnchorId)) { + state.selectionAnchorId = selectionModel.GetSelectedId(); + } +} + +bool IsDescendantItem( + const std::vector& items, + std::size_t ancestorIndex, + std::size_t candidateIndex) { + if (ancestorIndex >= items.size() || candidateIndex >= items.size() || candidateIndex <= ancestorIndex) { + return false; + } + + const std::uint32_t ancestorDepth = items[ancestorIndex].depth; + for (std::size_t index = ancestorIndex + 1u; index <= candidateIndex; ++index) { + if (items[index].depth <= ancestorDepth) { + return false; + } + } + + return true; +} + +bool SelectVisibleItem( + UIEditorTreeViewInteractionState& state, + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::size_t visibleIndex, + UIEditorTreeViewInteractionResult& result, + bool markKeyboardNavigation) { + if (visibleIndex >= layout.visibleItemIndices.size()) { + return false; + } + + const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex]; + if (itemIndex >= items.size()) { + return false; + } + + const Widgets::UIEditorTreeViewItem& item = items[itemIndex]; + result.selectionChanged = selectionModel.SetSelection(item.itemId); + result.selectedItemId = item.itemId; + result.selectedVisibleIndex = visibleIndex; + result.keyboardNavigated = markKeyboardNavigation; + result.consumed = true; + state.keyboardNavigation.SetCurrentIndex(visibleIndex); + state.selectionAnchorId = item.itemId; + return true; +} + +bool ToggleVisibleItemSelection( + UIEditorTreeViewInteractionState& state, + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::size_t visibleIndex, + UIEditorTreeViewInteractionResult& result) { + if (visibleIndex >= layout.visibleItemIndices.size()) { + return false; + } + + const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex]; + if (itemIndex >= items.size()) { + return false; + } + + const Widgets::UIEditorTreeViewItem& item = items[itemIndex]; + result.selectionChanged = selectionModel.ToggleSelectionMembership(item.itemId, true); + result.selectedItemId = item.itemId; + result.selectedVisibleIndex = visibleIndex; + result.consumed = true; + state.keyboardNavigation.SetCurrentIndex(visibleIndex); + state.selectionAnchorId = item.itemId; + return true; +} + +bool SelectVisibleItemRange( + UIEditorTreeViewInteractionState& state, + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::size_t visibleIndex, + UIEditorTreeViewInteractionResult& result, + bool markKeyboardNavigation) { + if (visibleIndex >= layout.visibleItemIndices.size()) { + return false; + } + + std::size_t anchorVisibleIndex = UIEditorTreeViewInvalidIndex; + if (!state.selectionAnchorId.empty()) { + anchorVisibleIndex = FindVisibleIndexForItemId(layout, items, state.selectionAnchorId); + } + if (anchorVisibleIndex == UIEditorTreeViewInvalidIndex) { + return SelectVisibleItem( + state, + selectionModel, + layout, + items, + visibleIndex, + result, + markKeyboardNavigation); + } + + const std::size_t rangeBegin = (std::min)(anchorVisibleIndex, visibleIndex); + const std::size_t rangeEnd = (std::max)(anchorVisibleIndex, visibleIndex); + std::vector selectedIds = {}; + selectedIds.reserve(rangeEnd - rangeBegin + 1u); + for (std::size_t index = rangeBegin; index <= rangeEnd; ++index) { + const std::size_t itemIndex = layout.visibleItemIndices[index]; + if (itemIndex < items.size()) { + selectedIds.push_back(items[itemIndex].itemId); + } + } + + const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex]; + if (itemIndex >= items.size()) { + return false; + } + + const Widgets::UIEditorTreeViewItem& item = items[itemIndex]; + result.selectionChanged = selectionModel.SetSelections(std::move(selectedIds), item.itemId); + result.selectedItemId = item.itemId; + result.selectedVisibleIndex = visibleIndex; + result.keyboardNavigated = markKeyboardNavigation; + result.consumed = true; + state.keyboardNavigation.SetCurrentIndex(visibleIndex); + return true; +} + +void PopulateCurrentVisibleItemResult( + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::size_t visibleIndex, + UIEditorTreeViewInteractionResult& result) { + if (visibleIndex >= layout.visibleItemIndices.size()) { + return; + } + + const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex]; + if (itemIndex >= items.size()) { + return; + } + + result.selectedItemId = items[itemIndex].itemId; + result.selectedVisibleIndex = visibleIndex; +} + +bool ApplyVerticalNavigation( + UIEditorTreeViewInteractionState& state, + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::int32_t keyCode, + UIEditorTreeViewInteractionResult& result, + bool extendSelectionRange) { + bool moved = false; + switch (static_cast(keyCode)) { + case KeyCode::Up: + moved = state.keyboardNavigation.MovePrevious(); + break; + case KeyCode::Down: + moved = state.keyboardNavigation.MoveNext(); + break; + case KeyCode::Home: + moved = state.keyboardNavigation.MoveHome(); + break; + case KeyCode::End: + moved = state.keyboardNavigation.MoveEnd(); + break; + default: + break; + } + + if (!moved || !state.keyboardNavigation.HasCurrentIndex()) { + return false; + } + + return extendSelectionRange + ? SelectVisibleItemRange( + state, + selectionModel, + layout, + items, + state.keyboardNavigation.GetCurrentIndex(), + result, + true) + : SelectVisibleItem( + state, + selectionModel, + layout, + items, + state.keyboardNavigation.GetCurrentIndex(), + result, + true); +} + +bool ApplyHorizontalNavigation( + UIEditorTreeViewInteractionState& state, + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + ::XCEngine::UI::Widgets::UIExpansionModel& expansionModel, + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::int32_t keyCode, + UIEditorTreeViewInteractionResult& result) { + if (!state.keyboardNavigation.HasCurrentIndex()) { + return false; + } + + const std::size_t visibleIndex = state.keyboardNavigation.GetCurrentIndex(); + if (visibleIndex >= layout.visibleItemIndices.size()) { + return false; + } + + const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex]; + if (itemIndex >= items.size()) { + return false; + } + + const Widgets::UIEditorTreeViewItem& item = items[itemIndex]; + const bool hasChildren = DoesUIEditorTreeViewItemHaveChildren(items, itemIndex); + const bool expanded = expansionModel.IsExpanded(item.itemId); + + switch (static_cast(keyCode)) { + case KeyCode::Right: + if (hasChildren && !expanded) { + result.expansionChanged = expansionModel.Expand(item.itemId); + result.toggledItemId = item.itemId; + result.keyboardNavigated = true; + PopulateCurrentVisibleItemResult(layout, items, visibleIndex, result); + result.consumed = true; + return result.expansionChanged; + } + if (hasChildren && expanded) { + const std::size_t childItemIndex = + FindUIEditorTreeViewFirstVisibleChildItemIndex(items, expansionModel, itemIndex); + const std::size_t childVisibleIndex = + FindVisibleIndexForItemIndex(layout, childItemIndex); + if (childVisibleIndex != UIEditorTreeViewInvalidIndex) { + return SelectVisibleItem( + state, + selectionModel, + layout, + items, + childVisibleIndex, + result, + true); + } + } + return false; + + case KeyCode::Left: + if (hasChildren && expanded) { + result.expansionChanged = expansionModel.Collapse(item.itemId); + result.toggledItemId = item.itemId; + result.keyboardNavigated = true; + PopulateCurrentVisibleItemResult(layout, items, visibleIndex, result); + result.consumed = true; + return result.expansionChanged; + } + { + const std::size_t parentItemIndex = FindUIEditorTreeViewParentItemIndex(items, itemIndex); + const std::size_t parentVisibleIndex = + FindVisibleIndexForItemIndex(layout, parentItemIndex); + if (parentVisibleIndex != UIEditorTreeViewInvalidIndex) { + return SelectVisibleItem( + state, + selectionModel, + layout, + items, + parentVisibleIndex, + result, + true); + } + } + return false; + + default: + return false; + } +} + +void NormalizeSelectionAfterCollapse( + UIEditorTreeViewInteractionState& state, + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::string_view collapsedItemId, + UIEditorTreeViewInteractionResult& result) { + if (!selectionModel.HasSelection()) { + return; + } + + const std::size_t collapsedItemIndex = FindUIEditorTreeViewItemIndex(items, collapsedItemId); + const std::size_t selectedItemIndex = FindUIEditorTreeViewItemIndex(items, selectionModel.GetSelectedId()); + if (collapsedItemIndex == UIEditorTreeViewInvalidIndex || + selectedItemIndex == UIEditorTreeViewInvalidIndex || + !IsDescendantItem(items, collapsedItemIndex, selectedItemIndex)) { + return; + } + + const std::size_t collapsedVisibleIndex = + FindVisibleIndexForItemIndex(layout, collapsedItemIndex); + if (collapsedVisibleIndex == UIEditorTreeViewInvalidIndex) { + return; + } + + SelectVisibleItem( + state, + selectionModel, + layout, + items, + collapsedVisibleIndex, + result, + false); +} + +} // namespace + +UIEditorTreeViewInteractionFrame UpdateUIEditorTreeViewInteraction( + UIEditorTreeViewInteractionState& state, + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + ::XCEngine::UI::Widgets::UIExpansionModel& expansionModel, + const ::XCEngine::UI::UIRect& bounds, + const std::vector& items, + const std::vector& inputEvents, + const Widgets::UIEditorTreeViewMetrics& metrics) { + Widgets::UIEditorTreeViewLayout layout = + BuildUIEditorTreeViewLayout(bounds, items, expansionModel, metrics); + SyncKeyboardNavigation(state, selectionModel, layout, items); + SyncSelectionAnchor(state, selectionModel); + SyncHoverTarget(state, layout, items); + + UIEditorTreeViewInteractionResult interactionResult = {}; + for (const UIInputEvent& event : inputEvents) { + if (ShouldUsePointerPosition(event)) { + state.pointerPosition = event.position; + state.hasPointerPosition = true; + } else if (event.type == UIInputEventType::PointerLeave) { + state.hasPointerPosition = false; + } + + UIEditorTreeViewInteractionResult eventResult = {}; + switch (event.type) { + case UIInputEventType::FocusGained: + state.treeViewState.focused = true; + break; + + case UIInputEventType::FocusLost: + state.treeViewState.focused = false; + state.hasPointerPosition = false; + state.treeViewState.hoveredItemId.clear(); + break; + + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + break; + + case UIInputEventType::PointerLeave: + state.treeViewState.hoveredItemId.clear(); + break; + + case UIInputEventType::PointerButtonDown: { + const UIEditorTreeViewHitTarget hitTarget = + state.hasPointerPosition + ? HitTestUIEditorTreeView(layout, state.pointerPosition) + : UIEditorTreeViewHitTarget {}; + eventResult.hitTarget = hitTarget; + if ((event.pointerButton == UIPointerButton::Left || + event.pointerButton == UIPointerButton::Right) && + hitTarget.kind != UIEditorTreeViewHitTargetKind::None) { + state.treeViewState.focused = true; + eventResult.consumed = true; + } else if (event.pointerButton == UIPointerButton::Left && + (!state.hasPointerPosition || + !IsUIEditorTreeViewPointInside(layout.bounds, state.pointerPosition))) { + state.treeViewState.focused = false; + } + break; + } + + case UIInputEventType::PointerButtonUp: { + const UIEditorTreeViewHitTarget hitTarget = + state.hasPointerPosition + ? HitTestUIEditorTreeView(layout, state.pointerPosition) + : UIEditorTreeViewHitTarget {}; + eventResult.hitTarget = hitTarget; + + const bool insideTree = + state.hasPointerPosition && + IsUIEditorTreeViewPointInside(layout.bounds, state.pointerPosition); + + if (hitTarget.itemIndex >= items.size()) { + if (event.pointerButton == UIPointerButton::Left && insideTree) { + eventResult.consumed = true; + state.treeViewState.focused = true; + } else if (event.pointerButton == UIPointerButton::Left) { + state.treeViewState.focused = false; + } + break; + } + + const Widgets::UIEditorTreeViewItem& item = items[hitTarget.itemIndex]; + if (event.pointerButton == UIPointerButton::Left) { + if (hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure && + DoesUIEditorTreeViewItemHaveChildren(items, hitTarget.itemIndex)) { + eventResult.expansionChanged = + expansionModel.ToggleExpanded(item.itemId); + eventResult.toggledItemId = item.itemId; + eventResult.consumed = true; + state.treeViewState.focused = true; + } else if (hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { + if (event.modifiers.shift) { + SelectVisibleItemRange( + state, + selectionModel, + layout, + items, + hitTarget.visibleIndex, + eventResult, + false); + } else if (event.modifiers.control) { + ToggleVisibleItemSelection( + state, + selectionModel, + layout, + items, + hitTarget.visibleIndex, + eventResult); + } else { + SelectVisibleItem( + state, + selectionModel, + layout, + items, + hitTarget.visibleIndex, + eventResult, + false); + } + state.treeViewState.focused = true; + } + } else if (event.pointerButton == UIPointerButton::Right && + (hitTarget.kind == UIEditorTreeViewHitTargetKind::Row || + hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure)) { + if (hitTarget.visibleIndex != UIEditorTreeViewInvalidIndex && + hitTarget.itemIndex < items.size()) { + const Widgets::UIEditorTreeViewItem& hitItem = items[hitTarget.itemIndex]; + if (selectionModel.IsSelected(hitItem.itemId)) { + selectionModel.SetPrimarySelection(hitItem.itemId); + eventResult.selectedItemId = hitItem.itemId; + eventResult.selectedVisibleIndex = hitTarget.visibleIndex; + eventResult.consumed = true; + state.keyboardNavigation.SetCurrentIndex(hitTarget.visibleIndex); + state.selectionAnchorId = hitItem.itemId; + } else { + SelectVisibleItem( + state, + selectionModel, + layout, + items, + hitTarget.visibleIndex, + eventResult, + false); + } + } + eventResult.secondaryClicked = true; + eventResult.consumed = true; + state.treeViewState.focused = true; + } + break; + } + + case UIInputEventType::KeyDown: + if (state.treeViewState.focused && + !event.modifiers.control && + !event.modifiers.alt && + !event.modifiers.super) { + if (event.keyCode == static_cast(KeyCode::F2) && + selectionModel.HasSelection()) { + eventResult.renameRequested = true; + eventResult.renameItemId = selectionModel.GetSelectedId(); + eventResult.selectedItemId = selectionModel.GetSelectedId(); + eventResult.selectedVisibleIndex = + FindVisibleIndexForItemId(layout, items, selectionModel.GetSelectedId()); + eventResult.consumed = true; + break; + } + + if (ApplyVerticalNavigation( + state, + selectionModel, + layout, + items, + event.keyCode, + eventResult, + event.modifiers.shift) || + ApplyHorizontalNavigation( + state, + selectionModel, + expansionModel, + layout, + items, + event.keyCode, + eventResult)) { + eventResult.consumed = true; + } + } + break; + + default: + break; + } + + layout = BuildUIEditorTreeViewLayout(bounds, items, expansionModel, metrics); + if (eventResult.expansionChanged && + !eventResult.toggledItemId.empty() && + !expansionModel.IsExpanded(eventResult.toggledItemId)) { + NormalizeSelectionAfterCollapse( + state, + selectionModel, + layout, + items, + eventResult.toggledItemId, + eventResult); + } + SyncKeyboardNavigation(state, selectionModel, layout, items); + SyncSelectionAnchor(state, selectionModel); + SyncHoverTarget(state, layout, items); + if (eventResult.hitTarget.kind == UIEditorTreeViewHitTargetKind::None && + state.hasPointerPosition) { + eventResult.hitTarget = HitTestUIEditorTreeView(layout, state.pointerPosition); + } + + if (eventResult.consumed || + eventResult.selectionChanged || + eventResult.expansionChanged || + eventResult.keyboardNavigated || + eventResult.secondaryClicked || + eventResult.renameRequested || + eventResult.hitTarget.kind != UIEditorTreeViewHitTargetKind::None || + !eventResult.selectedItemId.empty() || + !eventResult.renameItemId.empty() || + !eventResult.toggledItemId.empty()) { + interactionResult = std::move(eventResult); + } + } + + layout = BuildUIEditorTreeViewLayout(bounds, items, expansionModel, metrics); + SyncKeyboardNavigation(state, selectionModel, layout, items); + SyncSelectionAnchor(state, selectionModel); + SyncHoverTarget(state, layout, items); + if (interactionResult.hitTarget.kind == UIEditorTreeViewHitTargetKind::None && + state.hasPointerPosition) { + interactionResult.hitTarget = HitTestUIEditorTreeView(layout, state.pointerPosition); + } + + return { + std::move(layout), + std::move(interactionResult) + }; +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Core/UIEditorDockHostInteraction.cpp b/new_editor/src/Core/UIEditorDockHostInteraction.cpp deleted file mode 100644 index 67eb4478..00000000 --- a/new_editor/src/Core/UIEditorDockHostInteraction.cpp +++ /dev/null @@ -1,304 +0,0 @@ -#include - -#include - -#include - -namespace XCEngine::UI::Editor { - -namespace { - -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIRect; -using ::XCEngine::UI::Widgets::BeginUISplitterDrag; -using ::XCEngine::UI::Widgets::EndUISplitterDrag; -using ::XCEngine::UI::Widgets::UpdateUISplitterDrag; -using Widgets::BuildUIEditorDockHostLayout; -using Widgets::FindUIEditorDockHostSplitterLayout; -using Widgets::HitTestUIEditorDockHost; -using Widgets::UIEditorDockHostHitTarget; -using Widgets::UIEditorDockHostHitTargetKind; - -bool ShouldUsePointerPosition(const UIInputEvent& event) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - return true; - default: - return false; - } -} - -UIEditorWorkspaceLayoutOperationResult ApplySplitRatio( - UIEditorWorkspaceController& controller, - std::string_view nodeId, - float splitRatio) { - return controller.SetSplitRatio(nodeId, splitRatio); -} - -void SyncHoverTarget( - UIEditorDockHostInteractionState& state, - const Widgets::UIEditorDockHostLayout& layout) { - if (state.splitterDragState.active) { - state.dockHostState.hoveredTarget = { - UIEditorDockHostHitTargetKind::SplitterHandle, - state.dockHostState.activeSplitterNodeId, - {}, - Widgets::UIEditorTabStripInvalidIndex - }; - return; - } - - if (!state.hasPointerPosition) { - state.dockHostState.hoveredTarget = {}; - return; - } - - state.dockHostState.hoveredTarget = - HitTestUIEditorDockHost(layout, state.pointerPosition); -} - -UIEditorWorkspaceCommandResult DispatchPanelCommand( - UIEditorWorkspaceController& controller, - UIEditorWorkspaceCommandKind kind, - std::string panelId) { - UIEditorWorkspaceCommand command = {}; - command.kind = kind; - command.panelId = std::move(panelId); - return controller.Dispatch(command); -} - -} // namespace - -UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( - UIEditorDockHostInteractionState& state, - UIEditorWorkspaceController& controller, - const UIRect& bounds, - const std::vector& inputEvents, - const Widgets::UIEditorDockHostMetrics& metrics) { - UIEditorDockHostInteractionResult interactionResult = {}; - Widgets::UIEditorDockHostLayout layout = BuildUIEditorDockHostLayout( - bounds, - controller.GetPanelRegistry(), - controller.GetWorkspace(), - controller.GetSession(), - state.dockHostState, - metrics); - SyncHoverTarget(state, layout); - - for (const UIInputEvent& event : inputEvents) { - if (ShouldUsePointerPosition(event)) { - state.pointerPosition = event.position; - state.hasPointerPosition = true; - } else if (event.type == UIInputEventType::PointerLeave) { - state.hasPointerPosition = false; - } - - UIEditorDockHostInteractionResult eventResult = {}; - - switch (event.type) { - case UIInputEventType::FocusGained: - state.dockHostState.focused = true; - break; - - case UIInputEventType::FocusLost: - state.dockHostState.focused = false; - state.dockHostState.hoveredTarget = {}; - if (state.splitterDragState.active) { - EndUISplitterDrag(state.splitterDragState); - state.dockHostState.activeSplitterNodeId.clear(); - eventResult.consumed = true; - eventResult.releasePointerCapture = true; - } - break; - - case UIInputEventType::PointerMove: - case UIInputEventType::PointerEnter: - if (state.splitterDragState.active) { - const auto* splitter = FindUIEditorDockHostSplitterLayout( - layout, - state.dockHostState.activeSplitterNodeId); - if (splitter != nullptr) { - ::XCEngine::UI::Layout::UISplitterLayoutResult draggedLayout = {}; - if (UpdateUISplitterDrag( - state.splitterDragState, - state.pointerPosition, - draggedLayout)) { - eventResult.layoutResult = ApplySplitRatio( - controller, - state.dockHostState.activeSplitterNodeId, - draggedLayout.splitRatio); - eventResult.layoutChanged = - eventResult.layoutResult.status == - UIEditorWorkspaceLayoutOperationStatus::Changed; - } - eventResult.consumed = true; - eventResult.hitTarget.kind = UIEditorDockHostHitTargetKind::SplitterHandle; - eventResult.hitTarget.nodeId = state.dockHostState.activeSplitterNodeId; - } - } - break; - - case UIInputEventType::PointerLeave: - if (!state.splitterDragState.active) { - state.dockHostState.hoveredTarget = {}; - } - break; - - case UIInputEventType::PointerButtonDown: - if (event.pointerButton != ::XCEngine::UI::UIPointerButton::Left) { - break; - } - - if (state.dockHostState.hoveredTarget.kind == - UIEditorDockHostHitTargetKind::SplitterHandle) { - const auto* splitter = FindUIEditorDockHostSplitterLayout( - layout, - state.dockHostState.hoveredTarget.nodeId); - if (splitter != nullptr && - BeginUISplitterDrag( - 1u, - splitter->axis == UIEditorWorkspaceSplitAxis::Horizontal - ? ::XCEngine::UI::Layout::UILayoutAxis::Horizontal - : ::XCEngine::UI::Layout::UILayoutAxis::Vertical, - splitter->bounds, - splitter->splitterLayout, - splitter->constraints, - splitter->metrics, - state.pointerPosition, - state.splitterDragState)) { - state.dockHostState.activeSplitterNodeId = splitter->nodeId; - state.dockHostState.focused = true; - eventResult.consumed = true; - eventResult.requestPointerCapture = true; - eventResult.hitTarget = state.dockHostState.hoveredTarget; - eventResult.activeSplitterNodeId = splitter->nodeId; - } - } else { - state.dockHostState.focused = - state.dockHostState.hoveredTarget.kind != - UIEditorDockHostHitTargetKind::None; - eventResult.consumed = state.dockHostState.focused; - eventResult.hitTarget = state.dockHostState.hoveredTarget; - } - break; - - case UIInputEventType::PointerButtonUp: - if (event.pointerButton != ::XCEngine::UI::UIPointerButton::Left) { - break; - } - - if (state.splitterDragState.active) { - ::XCEngine::UI::Layout::UISplitterLayoutResult draggedLayout = {}; - if (UpdateUISplitterDrag( - state.splitterDragState, - state.pointerPosition, - draggedLayout)) { - eventResult.layoutResult = ApplySplitRatio( - controller, - state.dockHostState.activeSplitterNodeId, - draggedLayout.splitRatio); - eventResult.layoutChanged = - eventResult.layoutResult.status == - UIEditorWorkspaceLayoutOperationStatus::Changed; - } - EndUISplitterDrag(state.splitterDragState); - eventResult.consumed = true; - eventResult.releasePointerCapture = true; - eventResult.activeSplitterNodeId = state.dockHostState.activeSplitterNodeId; - state.dockHostState.activeSplitterNodeId.clear(); - break; - } - - eventResult.hitTarget = state.dockHostState.hoveredTarget; - switch (state.dockHostState.hoveredTarget.kind) { - case UIEditorDockHostHitTargetKind::Tab: - case UIEditorDockHostHitTargetKind::PanelHeader: - case UIEditorDockHostHitTargetKind::PanelBody: - case UIEditorDockHostHitTargetKind::PanelFooter: - eventResult.commandResult = DispatchPanelCommand( - controller, - UIEditorWorkspaceCommandKind::ActivatePanel, - state.dockHostState.hoveredTarget.panelId); - eventResult.commandExecuted = - eventResult.commandResult.status != - UIEditorWorkspaceCommandStatus::Rejected; - eventResult.consumed = true; - state.dockHostState.focused = true; - break; - - case UIEditorDockHostHitTargetKind::TabCloseButton: - case UIEditorDockHostHitTargetKind::PanelCloseButton: - eventResult.commandResult = DispatchPanelCommand( - controller, - UIEditorWorkspaceCommandKind::ClosePanel, - state.dockHostState.hoveredTarget.panelId); - eventResult.commandExecuted = - eventResult.commandResult.status != - UIEditorWorkspaceCommandStatus::Rejected; - eventResult.consumed = true; - state.dockHostState.focused = true; - break; - - case UIEditorDockHostHitTargetKind::TabStripBackground: - state.dockHostState.focused = true; - eventResult.consumed = true; - break; - - case UIEditorDockHostHitTargetKind::None: - default: - state.dockHostState.focused = false; - break; - } - break; - - default: - break; - } - - layout = BuildUIEditorDockHostLayout( - bounds, - controller.GetPanelRegistry(), - controller.GetWorkspace(), - controller.GetSession(), - state.dockHostState, - metrics); - SyncHoverTarget(state, layout); - if (eventResult.hitTarget.kind == UIEditorDockHostHitTargetKind::None) { - eventResult.hitTarget = state.dockHostState.hoveredTarget; - } - - if (eventResult.consumed || - eventResult.commandExecuted || - eventResult.layoutChanged || - eventResult.requestPointerCapture || - eventResult.releasePointerCapture || - eventResult.layoutResult.status != UIEditorWorkspaceLayoutOperationStatus::Rejected || - eventResult.hitTarget.kind != UIEditorDockHostHitTargetKind::None || - !eventResult.activeSplitterNodeId.empty()) { - interactionResult = std::move(eventResult); - } - } - - layout = BuildUIEditorDockHostLayout( - bounds, - controller.GetPanelRegistry(), - controller.GetWorkspace(), - controller.GetSession(), - state.dockHostState, - metrics); - SyncHoverTarget(state, layout); - if (interactionResult.hitTarget.kind == UIEditorDockHostHitTargetKind::None) { - interactionResult.hitTarget = state.dockHostState.hoveredTarget; - } - return { - std::move(layout), - std::move(interactionResult), - state.dockHostState.focused - }; -} - -} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Core/UIEditorTheme.cpp b/new_editor/src/Core/UIEditorTheme.cpp deleted file mode 100644 index 1e1745eb..00000000 --- a/new_editor/src/Core/UIEditorTheme.cpp +++ /dev/null @@ -1,1615 +0,0 @@ -#include - -#include - -#include - -namespace XCEngine::UI::Editor { - -namespace { - -using ::XCEngine::Math::Color; -using ::XCEngine::UI::UIColor; -using ::XCEngine::UI::Style::UICornerRadius; -using ::XCEngine::UI::Style::UIStyleValueType; -using ::XCEngine::UI::Style::UITheme; -using ::XCEngine::UI::Style::UITokenResolveResult; -using ::XCEngine::UI::Style::UITokenResolveStatus; - -constexpr UIColor kTransparent(0.0f, 0.0f, 0.0f, 0.0f); - -UIColor ToUIColor(const Color& color) { - return UIColor(color.r, color.g, color.b, color.a); -} - -bool TryResolveThemeFloat( - const UITheme& theme, - std::string_view tokenName, - float& outValue) { - UITokenResolveResult result = - theme.ResolveToken(std::string(tokenName), UIStyleValueType::Float); - if (result.status == UITokenResolveStatus::Resolved) { - if (const float* value = result.value.TryGetFloat(); value != nullptr) { - outValue = *value; - return true; - } - } - - result = theme.ResolveToken(std::string(tokenName), UIStyleValueType::CornerRadius); - if (result.status == UITokenResolveStatus::Resolved) { - if (const UICornerRadius* value = result.value.TryGetCornerRadius(); - value != nullptr && - value->IsUniform()) { - outValue = value->topLeft; - return true; - } - } - - return false; -} - -bool TryResolveThemeColor( - const UITheme& theme, - std::string_view tokenName, - UIColor& outValue) { - const UITokenResolveResult result = - theme.ResolveToken(std::string(tokenName), UIStyleValueType::Color); - if (result.status != UITokenResolveStatus::Resolved) { - return false; - } - - const Color* value = result.value.TryGetColor(); - if (value == nullptr) { - return false; - } - - outValue = ToUIColor(*value); - return true; -} - -float ResolveThemeFloatAliases( - const UITheme& theme, - std::initializer_list tokenNames, - float fallbackValue) { - float resolvedValue = fallbackValue; - for (const std::string_view tokenName : tokenNames) { - if (TryResolveThemeFloat(theme, tokenName, resolvedValue)) { - return resolvedValue; - } - } - - return fallbackValue; -} - -UIColor ResolveThemeColorAliases( - const UITheme& theme, - std::initializer_list tokenNames, - const UIColor& fallbackValue) { - UIColor resolvedValue = fallbackValue; - for (const std::string_view tokenName : tokenNames) { - if (TryResolveThemeColor(theme, tokenName, resolvedValue)) { - return resolvedValue; - } - } - - return fallbackValue; -} - -} // namespace - -float ResolveUIEditorThemeFloat( - const UITheme& theme, - std::string_view tokenName, - float fallbackValue) { - return ResolveThemeFloatAliases(theme, { tokenName }, fallbackValue); -} - -UIColor ResolveUIEditorThemeColor( - const UITheme& theme, - std::string_view tokenName, - const UIColor& fallbackValue) { - return ResolveThemeColorAliases(theme, { tokenName }, fallbackValue); -} - -Widgets::UIEditorBoolFieldMetrics ResolveUIEditorBoolFieldMetrics( - const UITheme& theme, - const Widgets::UIEditorBoolFieldMetrics& fallback) { - Widgets::UIEditorBoolFieldMetrics metrics = fallback; - metrics.rowHeight = ResolveUIEditorThemeFloat(theme, "editor.size.field.row", metrics.rowHeight); - metrics.horizontalPadding = - ResolveUIEditorThemeFloat(theme, "editor.space.field.padding_x", metrics.horizontalPadding); - metrics.labelControlGap = - ResolveUIEditorThemeFloat(theme, "editor.space.field.label_gap", metrics.labelControlGap); - metrics.controlColumnStart = - ResolveUIEditorThemeFloat(theme, "editor.layout.field.control_column", metrics.controlColumnStart); - metrics.controlTrailingInset = ResolveThemeFloatAliases( - theme, - { "editor.space.field.control_trailing_inset", "editor.space.field.padding_x" }, - metrics.controlTrailingInset); - metrics.checkboxSize = - ResolveUIEditorThemeFloat(theme, "editor.size.field.checkbox", metrics.checkboxSize); - metrics.labelTextInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.label_inset_y", metrics.labelTextInsetY); - metrics.checkboxGlyphInsetX = ResolveThemeFloatAliases( - theme, - { "editor.space.field.checkbox_mark_inset_x", "editor.space.field.checkbox_glyph_inset_x" }, - metrics.checkboxGlyphInsetX); - metrics.checkboxGlyphInsetY = ResolveThemeFloatAliases( - theme, - { "editor.space.field.checkbox_mark_inset_y", "editor.space.field.checkbox_glyph_inset_y" }, - metrics.checkboxGlyphInsetY); - metrics.cornerRounding = - ResolveUIEditorThemeFloat(theme, "editor.radius.field.row", metrics.cornerRounding); - metrics.checkboxRounding = ResolveThemeFloatAliases( - theme, - { "editor.radius.field.checkbox", "editor.radius.field.control" }, - metrics.checkboxRounding); - metrics.borderThickness = - ResolveUIEditorThemeFloat(theme, "editor.border.field", metrics.borderThickness); - metrics.focusedBorderThickness = - ResolveUIEditorThemeFloat(theme, "editor.border.field.focus", metrics.focusedBorderThickness); - metrics.labelFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.label", "editor.font.field.label" }, - metrics.labelFontSize); - metrics.checkboxGlyphFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.checkbox_mark", "editor.font.field.glyph" }, - metrics.checkboxGlyphFontSize); - return metrics; -} - -Widgets::UIEditorBoolFieldPalette ResolveUIEditorBoolFieldPalette( - const UITheme& theme, - const Widgets::UIEditorBoolFieldPalette& fallback) { - Widgets::UIEditorBoolFieldPalette palette = fallback; - palette.surfaceColor = ResolveUIEditorThemeColor(theme, "editor.color.field.row", palette.surfaceColor); - palette.borderColor = ResolveUIEditorThemeColor(theme, "editor.color.field.border", palette.borderColor); - palette.focusedBorderColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.border_focus", palette.focusedBorderColor); - palette.rowHoverColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.row_hover", palette.rowHoverColor); - palette.rowActiveColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.row_active", palette.rowActiveColor); - palette.checkboxColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.checkbox", palette.checkboxColor); - palette.checkboxHoverColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.checkbox_hover", palette.checkboxHoverColor); - palette.checkboxReadOnlyColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.checkbox_readonly", "editor.color.field.control_readonly" }, - palette.checkboxReadOnlyColor); - palette.checkboxBorderColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.checkbox_border", - palette.checkboxBorderColor); - palette.checkboxMarkColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.checkbox_mark", - palette.checkboxMarkColor); - palette.labelColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.label", palette.labelColor); - return palette; -} - -Widgets::UIEditorNumberFieldMetrics ResolveUIEditorNumberFieldMetrics( - const UITheme& theme, - const Widgets::UIEditorNumberFieldMetrics& fallback) { - Widgets::UIEditorNumberFieldMetrics metrics = fallback; - metrics.rowHeight = ResolveUIEditorThemeFloat(theme, "editor.size.field.row", metrics.rowHeight); - metrics.horizontalPadding = - ResolveUIEditorThemeFloat(theme, "editor.space.field.padding_x", metrics.horizontalPadding); - metrics.labelControlGap = - ResolveUIEditorThemeFloat(theme, "editor.space.field.label_gap", metrics.labelControlGap); - metrics.controlColumnStart = - ResolveUIEditorThemeFloat(theme, "editor.layout.field.control_column", metrics.controlColumnStart); - metrics.controlTrailingInset = ResolveThemeFloatAliases( - theme, - { "editor.space.field.control_trailing_inset", "editor.space.field.padding_x" }, - metrics.controlTrailingInset); - metrics.valueBoxMinWidth = ResolveThemeFloatAliases( - theme, - { "editor.size.field.number_min_width", "editor.size.field.control_min_width" }, - metrics.valueBoxMinWidth); - metrics.controlInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.control_inset_y", metrics.controlInsetY); - metrics.labelTextInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.label_inset_y", metrics.labelTextInsetY); - metrics.valueTextInsetX = - ResolveUIEditorThemeFloat(theme, "editor.space.field.value_inset_x", metrics.valueTextInsetX); - metrics.valueTextInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.value_inset_y", metrics.valueTextInsetY); - metrics.cornerRounding = - ResolveUIEditorThemeFloat(theme, "editor.radius.field.row", metrics.cornerRounding); - metrics.valueBoxRounding = - ResolveUIEditorThemeFloat(theme, "editor.radius.field.control", metrics.valueBoxRounding); - metrics.borderThickness = - ResolveUIEditorThemeFloat(theme, "editor.border.field", metrics.borderThickness); - metrics.focusedBorderThickness = - ResolveUIEditorThemeFloat(theme, "editor.border.field.focus", metrics.focusedBorderThickness); - metrics.labelFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.label", "editor.font.field.label" }, - metrics.labelFontSize); - metrics.valueFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.value", "editor.font.field.value" }, - metrics.valueFontSize); - return metrics; -} - -Widgets::UIEditorNumberFieldPalette ResolveUIEditorNumberFieldPalette( - const UITheme& theme, - const Widgets::UIEditorNumberFieldPalette& fallback) { - Widgets::UIEditorNumberFieldPalette palette = fallback; - palette.surfaceColor = ResolveUIEditorThemeColor(theme, "editor.color.field.row", palette.surfaceColor); - palette.borderColor = ResolveUIEditorThemeColor(theme, "editor.color.field.border", palette.borderColor); - palette.focusedBorderColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.border_focus", palette.focusedBorderColor); - palette.rowHoverColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.row_hover", palette.rowHoverColor); - palette.rowActiveColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.row_active", palette.rowActiveColor); - palette.valueBoxColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.control", palette.valueBoxColor); - palette.valueBoxHoverColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.control_hover", palette.valueBoxHoverColor); - palette.valueBoxEditingColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.control_editing", - palette.valueBoxEditingColor); - palette.readOnlyColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.control_readonly", - palette.readOnlyColor); - palette.controlBorderColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.control_border", - palette.controlBorderColor); - palette.controlFocusedBorderColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.control_border_focus", "editor.color.field.border_focus" }, - palette.controlFocusedBorderColor); - palette.labelColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.label", palette.labelColor); - palette.valueColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.value", palette.valueColor); - palette.readOnlyValueColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.value_readonly", - palette.readOnlyValueColor); - return palette; -} - -Widgets::UIEditorTextFieldMetrics ResolveUIEditorTextFieldMetrics( - const UITheme& theme, - const Widgets::UIEditorTextFieldMetrics& fallback) { - Widgets::UIEditorTextFieldMetrics metrics = fallback; - metrics.rowHeight = ResolveUIEditorThemeFloat(theme, "editor.size.field.row", metrics.rowHeight); - metrics.horizontalPadding = - ResolveUIEditorThemeFloat(theme, "editor.space.field.padding_x", metrics.horizontalPadding); - metrics.labelControlGap = - ResolveUIEditorThemeFloat(theme, "editor.space.field.label_gap", metrics.labelControlGap); - metrics.controlColumnStart = - ResolveUIEditorThemeFloat(theme, "editor.layout.field.control_column", metrics.controlColumnStart); - metrics.controlTrailingInset = ResolveThemeFloatAliases( - theme, - { "editor.space.field.control_trailing_inset", "editor.space.field.padding_x" }, - metrics.controlTrailingInset); - metrics.valueBoxMinWidth = ResolveThemeFloatAliases( - theme, - { "editor.size.field.text_min_width", "editor.size.field.control_min_width" }, - metrics.valueBoxMinWidth); - metrics.controlInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.control_inset_y", metrics.controlInsetY); - metrics.labelTextInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.label_inset_y", metrics.labelTextInsetY); - metrics.valueTextInsetX = - ResolveUIEditorThemeFloat(theme, "editor.space.field.value_inset_x", metrics.valueTextInsetX); - metrics.valueTextInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.value_inset_y", metrics.valueTextInsetY); - metrics.cornerRounding = - ResolveUIEditorThemeFloat(theme, "editor.radius.field.row", metrics.cornerRounding); - metrics.valueBoxRounding = - ResolveUIEditorThemeFloat(theme, "editor.radius.field.control", metrics.valueBoxRounding); - metrics.borderThickness = - ResolveUIEditorThemeFloat(theme, "editor.border.field", metrics.borderThickness); - metrics.focusedBorderThickness = - ResolveUIEditorThemeFloat(theme, "editor.border.field.focus", metrics.focusedBorderThickness); - metrics.labelFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.label", "editor.font.field.label" }, - metrics.labelFontSize); - metrics.valueFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.value", "editor.font.field.value" }, - metrics.valueFontSize); - return metrics; -} - -Widgets::UIEditorTextFieldPalette ResolveUIEditorTextFieldPalette( - const UITheme& theme, - const Widgets::UIEditorTextFieldPalette& fallback) { - Widgets::UIEditorTextFieldPalette palette = fallback; - palette.surfaceColor = ResolveUIEditorThemeColor(theme, "editor.color.field.row", palette.surfaceColor); - palette.borderColor = ResolveUIEditorThemeColor(theme, "editor.color.field.border", palette.borderColor); - palette.focusedBorderColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.border_focus", palette.focusedBorderColor); - palette.rowHoverColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.row_hover", palette.rowHoverColor); - palette.rowActiveColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.row_active", palette.rowActiveColor); - palette.valueBoxColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.control", palette.valueBoxColor); - palette.valueBoxHoverColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.control_hover", palette.valueBoxHoverColor); - palette.valueBoxEditingColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.control_editing", - palette.valueBoxEditingColor); - palette.readOnlyColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.control_readonly", - palette.readOnlyColor); - palette.controlBorderColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.control_border", - palette.controlBorderColor); - palette.controlFocusedBorderColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.control_border_focus", "editor.color.field.border_focus" }, - palette.controlFocusedBorderColor); - palette.labelColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.label", palette.labelColor); - palette.valueColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.value", palette.valueColor); - palette.readOnlyValueColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.value_readonly", - palette.readOnlyValueColor); - return palette; -} - -Widgets::UIEditorVector2FieldMetrics ResolveUIEditorVector2FieldMetrics( - const UITheme& theme, - const Widgets::UIEditorVector2FieldMetrics& fallback) { - Widgets::UIEditorVector2FieldMetrics metrics = fallback; - metrics.rowHeight = ResolveUIEditorThemeFloat(theme, "editor.size.field.row", metrics.rowHeight); - metrics.horizontalPadding = - ResolveUIEditorThemeFloat(theme, "editor.space.field.padding_x", metrics.horizontalPadding); - metrics.labelControlGap = - ResolveUIEditorThemeFloat(theme, "editor.space.field.label_gap", metrics.labelControlGap); - metrics.controlColumnStart = - ResolveUIEditorThemeFloat(theme, "editor.layout.field.control_column", metrics.controlColumnStart); - metrics.controlTrailingInset = ResolveThemeFloatAliases( - theme, - { "editor.space.field.control_trailing_inset", "editor.space.field.padding_x" }, - metrics.controlTrailingInset); - metrics.controlInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.control_inset_y", metrics.controlInsetY); - metrics.componentGap = ResolveThemeFloatAliases( - theme, - { "editor.space.field.vector_component_gap", "editor.space.field.control_gap" }, - metrics.componentGap); - metrics.componentMinWidth = ResolveThemeFloatAliases( - theme, - { "editor.size.field.vector_component_min_width", "editor.size.field.control_min_width" }, - metrics.componentMinWidth); - metrics.componentPrefixWidth = ResolveThemeFloatAliases( - theme, - { "editor.size.field.vector_prefix_width", "editor.size.field.inline_prefix_width" }, - metrics.componentPrefixWidth); - metrics.componentLabelGap = ResolveThemeFloatAliases( - theme, - { "editor.space.field.vector_prefix_gap", "editor.space.field.inline_prefix_gap" }, - metrics.componentLabelGap); - metrics.labelTextInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.label_inset_y", metrics.labelTextInsetY); - metrics.labelFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.label", "editor.font.field.label" }, - metrics.labelFontSize); - metrics.valueTextInsetX = - ResolveUIEditorThemeFloat(theme, "editor.space.field.value_inset_x", metrics.valueTextInsetX); - metrics.valueTextInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.value_inset_y", metrics.valueTextInsetY); - metrics.valueFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.value", "editor.font.field.value" }, - metrics.valueFontSize); - metrics.prefixTextInsetX = ResolveThemeFloatAliases( - theme, - { "editor.space.field.vector_prefix_inset_x", "editor.space.field.inline_prefix_inset_x" }, - metrics.prefixTextInsetX); - metrics.prefixTextInsetY = ResolveThemeFloatAliases( - theme, - { "editor.space.field.vector_prefix_inset_y", "editor.space.field.inline_prefix_inset_y" }, - metrics.prefixTextInsetY); - metrics.prefixFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.vector_prefix", "editor.font.field.vector_prefix", "editor.font.field.glyph" }, - metrics.prefixFontSize); - metrics.cornerRounding = - ResolveUIEditorThemeFloat(theme, "editor.radius.field.row", metrics.cornerRounding); - metrics.componentRounding = ResolveThemeFloatAliases( - theme, - { "editor.radius.field.vector_component", "editor.radius.field.control" }, - metrics.componentRounding); - metrics.borderThickness = - ResolveUIEditorThemeFloat(theme, "editor.border.field", metrics.borderThickness); - metrics.focusedBorderThickness = - ResolveUIEditorThemeFloat(theme, "editor.border.field.focus", metrics.focusedBorderThickness); - return metrics; -} - -Widgets::UIEditorVector2FieldPalette ResolveUIEditorVector2FieldPalette( - const UITheme& theme, - const Widgets::UIEditorVector2FieldPalette& fallback) { - Widgets::UIEditorVector2FieldPalette palette = fallback; - palette.surfaceColor = ResolveUIEditorThemeColor(theme, "editor.color.field.row", palette.surfaceColor); - palette.borderColor = ResolveUIEditorThemeColor(theme, "editor.color.field.border", palette.borderColor); - palette.focusedBorderColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.border_focus", palette.focusedBorderColor); - palette.rowHoverColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.row_hover", palette.rowHoverColor); - palette.rowActiveColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.row_active", palette.rowActiveColor); - palette.componentColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.control", palette.componentColor); - palette.componentHoverColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.control_hover", palette.componentHoverColor); - palette.componentEditingColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.control_editing", - palette.componentEditingColor); - palette.readOnlyColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.control_readonly", - palette.readOnlyColor); - palette.componentBorderColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.control_border", - palette.componentBorderColor); - palette.componentFocusedBorderColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.control_border_focus", "editor.color.field.border_focus" }, - palette.componentFocusedBorderColor); - palette.prefixColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.vector_prefix", "editor.color.field.control_hover" }, - palette.prefixColor); - palette.prefixBorderColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.vector_prefix_border", "editor.color.field.control_border" }, - palette.prefixBorderColor); - palette.labelColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.label", palette.labelColor); - palette.valueColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.value", palette.valueColor); - palette.readOnlyValueColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.value_readonly", - palette.readOnlyValueColor); - palette.axisXColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.vector_axis_x", "editor.color.field.value" }, - palette.axisXColor); - palette.axisYColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.vector_axis_y", "editor.color.field.value" }, - palette.axisYColor); - return palette; -} - -Widgets::UIEditorVector3FieldMetrics ResolveUIEditorVector3FieldMetrics( - const UITheme& theme, - const Widgets::UIEditorVector3FieldMetrics& fallback) { - Widgets::UIEditorVector3FieldMetrics metrics = fallback; - metrics.rowHeight = ResolveUIEditorThemeFloat(theme, "editor.size.field.row", metrics.rowHeight); - metrics.horizontalPadding = - ResolveUIEditorThemeFloat(theme, "editor.space.field.padding_x", metrics.horizontalPadding); - metrics.labelControlGap = - ResolveUIEditorThemeFloat(theme, "editor.space.field.label_gap", metrics.labelControlGap); - metrics.controlColumnStart = - ResolveUIEditorThemeFloat(theme, "editor.layout.field.control_column", metrics.controlColumnStart); - metrics.controlTrailingInset = ResolveThemeFloatAliases( - theme, - { "editor.space.field.control_trailing_inset", "editor.space.field.padding_x" }, - metrics.controlTrailingInset); - metrics.controlInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.control_inset_y", metrics.controlInsetY); - metrics.componentGap = ResolveThemeFloatAliases( - theme, - { "editor.space.field.vector_component_gap", "editor.space.field.control_gap" }, - metrics.componentGap); - metrics.componentMinWidth = ResolveThemeFloatAliases( - theme, - { "editor.size.field.vector_component_min_width", "editor.size.field.control_min_width" }, - metrics.componentMinWidth); - metrics.componentPrefixWidth = ResolveThemeFloatAliases( - theme, - { "editor.size.field.vector_prefix_width", "editor.size.field.inline_prefix_width" }, - metrics.componentPrefixWidth); - metrics.componentLabelGap = ResolveThemeFloatAliases( - theme, - { "editor.space.field.vector_prefix_gap", "editor.space.field.inline_prefix_gap" }, - metrics.componentLabelGap); - metrics.labelTextInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.label_inset_y", metrics.labelTextInsetY); - metrics.labelFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.label", "editor.font.field.label" }, - metrics.labelFontSize); - metrics.valueTextInsetX = - ResolveUIEditorThemeFloat(theme, "editor.space.field.value_inset_x", metrics.valueTextInsetX); - metrics.valueTextInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.value_inset_y", metrics.valueTextInsetY); - metrics.valueFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.value", "editor.font.field.value" }, - metrics.valueFontSize); - metrics.prefixTextInsetX = ResolveThemeFloatAliases( - theme, - { "editor.space.field.vector_prefix_inset_x", "editor.space.field.inline_prefix_inset_x" }, - metrics.prefixTextInsetX); - metrics.prefixTextInsetY = ResolveThemeFloatAliases( - theme, - { "editor.space.field.vector_prefix_inset_y", "editor.space.field.inline_prefix_inset_y" }, - metrics.prefixTextInsetY); - metrics.prefixFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.vector_prefix", "editor.font.field.vector_prefix", "editor.font.field.glyph" }, - metrics.prefixFontSize); - metrics.cornerRounding = - ResolveUIEditorThemeFloat(theme, "editor.radius.field.row", metrics.cornerRounding); - metrics.componentRounding = ResolveThemeFloatAliases( - theme, - { "editor.radius.field.vector_component", "editor.radius.field.control" }, - metrics.componentRounding); - metrics.borderThickness = - ResolveUIEditorThemeFloat(theme, "editor.border.field", metrics.borderThickness); - metrics.focusedBorderThickness = - ResolveUIEditorThemeFloat(theme, "editor.border.field.focus", metrics.focusedBorderThickness); - return metrics; -} - -Widgets::UIEditorVector3FieldPalette ResolveUIEditorVector3FieldPalette( - const UITheme& theme, - const Widgets::UIEditorVector3FieldPalette& fallback) { - Widgets::UIEditorVector3FieldPalette palette = fallback; - palette.surfaceColor = ResolveUIEditorThemeColor(theme, "editor.color.field.row", palette.surfaceColor); - palette.borderColor = ResolveUIEditorThemeColor(theme, "editor.color.field.border", palette.borderColor); - palette.focusedBorderColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.border_focus", palette.focusedBorderColor); - palette.rowHoverColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.row_hover", palette.rowHoverColor); - palette.rowActiveColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.row_active", palette.rowActiveColor); - palette.componentColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.control", palette.componentColor); - palette.componentHoverColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.control_hover", palette.componentHoverColor); - palette.componentEditingColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.control_editing", - palette.componentEditingColor); - palette.readOnlyColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.control_readonly", - palette.readOnlyColor); - palette.componentBorderColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.control_border", - palette.componentBorderColor); - palette.componentFocusedBorderColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.control_border_focus", "editor.color.field.border_focus" }, - palette.componentFocusedBorderColor); - palette.prefixColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.vector_prefix", "editor.color.field.control_hover" }, - palette.prefixColor); - palette.prefixBorderColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.vector_prefix_border", "editor.color.field.control_border" }, - palette.prefixBorderColor); - palette.labelColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.label", palette.labelColor); - palette.valueColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.value", palette.valueColor); - palette.readOnlyValueColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.value_readonly", - palette.readOnlyValueColor); - palette.axisXColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.vector_axis_x", "editor.color.field.value" }, - palette.axisXColor); - palette.axisYColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.vector_axis_y", "editor.color.field.value" }, - palette.axisYColor); - palette.axisZColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.vector_axis_z", "editor.color.field.value" }, - palette.axisZColor); - return palette; -} - -Widgets::UIEditorVector4FieldMetrics ResolveUIEditorVector4FieldMetrics( - const UITheme& theme, - const Widgets::UIEditorVector4FieldMetrics& fallback) { - Widgets::UIEditorVector4FieldMetrics metrics = fallback; - metrics.rowHeight = ResolveUIEditorThemeFloat(theme, "editor.size.field.row", metrics.rowHeight); - metrics.horizontalPadding = - ResolveUIEditorThemeFloat(theme, "editor.space.field.padding_x", metrics.horizontalPadding); - metrics.labelControlGap = - ResolveUIEditorThemeFloat(theme, "editor.space.field.label_gap", metrics.labelControlGap); - metrics.controlColumnStart = - ResolveUIEditorThemeFloat(theme, "editor.layout.field.control_column", metrics.controlColumnStart); - metrics.controlTrailingInset = ResolveThemeFloatAliases( - theme, - { "editor.space.field.control_trailing_inset", "editor.space.field.padding_x" }, - metrics.controlTrailingInset); - metrics.controlInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.control_inset_y", metrics.controlInsetY); - metrics.componentGap = ResolveThemeFloatAliases( - theme, - { "editor.space.field.vector_component_gap", "editor.space.field.control_gap" }, - metrics.componentGap); - metrics.componentMinWidth = ResolveThemeFloatAliases( - theme, - { "editor.size.field.vector_component_min_width", "editor.size.field.control_min_width" }, - metrics.componentMinWidth); - metrics.componentPrefixWidth = ResolveThemeFloatAliases( - theme, - { "editor.size.field.vector_prefix_width", "editor.size.field.inline_prefix_width" }, - metrics.componentPrefixWidth); - metrics.componentLabelGap = ResolveThemeFloatAliases( - theme, - { "editor.space.field.vector_prefix_gap", "editor.space.field.inline_prefix_gap" }, - metrics.componentLabelGap); - metrics.labelTextInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.label_inset_y", metrics.labelTextInsetY); - metrics.labelFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.label", "editor.font.field.label" }, - metrics.labelFontSize); - metrics.valueTextInsetX = - ResolveUIEditorThemeFloat(theme, "editor.space.field.value_inset_x", metrics.valueTextInsetX); - metrics.valueTextInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.value_inset_y", metrics.valueTextInsetY); - metrics.valueFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.value", "editor.font.field.value" }, - metrics.valueFontSize); - metrics.prefixTextInsetX = ResolveThemeFloatAliases( - theme, - { "editor.space.field.vector_prefix_inset_x", "editor.space.field.inline_prefix_inset_x" }, - metrics.prefixTextInsetX); - metrics.prefixTextInsetY = ResolveThemeFloatAliases( - theme, - { "editor.space.field.vector_prefix_inset_y", "editor.space.field.inline_prefix_inset_y" }, - metrics.prefixTextInsetY); - metrics.prefixFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.vector_prefix", "editor.font.field.vector_prefix", "editor.font.field.glyph" }, - metrics.prefixFontSize); - metrics.cornerRounding = - ResolveUIEditorThemeFloat(theme, "editor.radius.field.row", metrics.cornerRounding); - metrics.componentRounding = ResolveThemeFloatAliases( - theme, - { "editor.radius.field.vector_component", "editor.radius.field.control" }, - metrics.componentRounding); - metrics.borderThickness = - ResolveUIEditorThemeFloat(theme, "editor.border.field", metrics.borderThickness); - metrics.focusedBorderThickness = - ResolveUIEditorThemeFloat(theme, "editor.border.field.focus", metrics.focusedBorderThickness); - return metrics; -} - -Widgets::UIEditorVector4FieldPalette ResolveUIEditorVector4FieldPalette( - const UITheme& theme, - const Widgets::UIEditorVector4FieldPalette& fallback) { - Widgets::UIEditorVector4FieldPalette palette = fallback; - palette.surfaceColor = ResolveUIEditorThemeColor(theme, "editor.color.field.row", palette.surfaceColor); - palette.borderColor = ResolveUIEditorThemeColor(theme, "editor.color.field.border", palette.borderColor); - palette.focusedBorderColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.border_focus", palette.focusedBorderColor); - palette.rowHoverColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.row_hover", palette.rowHoverColor); - palette.rowActiveColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.row_active", palette.rowActiveColor); - palette.componentColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.control", palette.componentColor); - palette.componentHoverColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.control_hover", palette.componentHoverColor); - palette.componentEditingColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.control_editing", - palette.componentEditingColor); - palette.readOnlyColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.control_readonly", - palette.readOnlyColor); - palette.componentBorderColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.control_border", - palette.componentBorderColor); - palette.componentFocusedBorderColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.control_border_focus", "editor.color.field.border_focus" }, - palette.componentFocusedBorderColor); - palette.prefixColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.vector_prefix", "editor.color.field.control_hover" }, - palette.prefixColor); - palette.prefixBorderColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.vector_prefix_border", "editor.color.field.control_border" }, - palette.prefixBorderColor); - palette.labelColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.label", palette.labelColor); - palette.valueColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.value", palette.valueColor); - palette.readOnlyValueColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.value_readonly", - palette.readOnlyValueColor); - palette.axisXColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.vector_axis_x", "editor.color.field.value" }, - palette.axisXColor); - palette.axisYColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.vector_axis_y", "editor.color.field.value" }, - palette.axisYColor); - palette.axisZColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.vector_axis_z", "editor.color.field.value" }, - palette.axisZColor); - palette.axisWColor = ResolveThemeColorAliases( - theme, - { "editor.color.field.vector_axis_w", "editor.color.field.value" }, - palette.axisWColor); - return palette; -} - -Widgets::UIEditorEnumFieldMetrics ResolveUIEditorEnumFieldMetrics( - const UITheme& theme, - const Widgets::UIEditorEnumFieldMetrics& fallback) { - Widgets::UIEditorEnumFieldMetrics metrics = fallback; - metrics.rowHeight = ResolveUIEditorThemeFloat(theme, "editor.size.field.row", metrics.rowHeight); - metrics.horizontalPadding = - ResolveUIEditorThemeFloat(theme, "editor.space.field.padding_x", metrics.horizontalPadding); - metrics.labelControlGap = - ResolveUIEditorThemeFloat(theme, "editor.space.field.label_gap", metrics.labelControlGap); - metrics.controlColumnStart = - ResolveUIEditorThemeFloat(theme, "editor.layout.field.control_column", metrics.controlColumnStart); - metrics.controlTrailingInset = ResolveThemeFloatAliases( - theme, - { "editor.space.field.control_trailing_inset", "editor.space.field.padding_x" }, - metrics.controlTrailingInset); - metrics.valueBoxMinWidth = ResolveThemeFloatAliases( - theme, - { "editor.size.field.enum_min_width", "editor.size.field.control_min_width" }, - metrics.valueBoxMinWidth); - metrics.controlInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.control_inset_y", metrics.controlInsetY); - metrics.labelTextInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.label_inset_y", metrics.labelTextInsetY); - metrics.valueTextInsetX = - ResolveUIEditorThemeFloat(theme, "editor.space.field.value_inset_x", metrics.valueTextInsetX); - metrics.valueTextInsetY = - ResolveUIEditorThemeFloat(theme, "editor.space.field.value_inset_y", metrics.valueTextInsetY); - metrics.dropdownArrowWidth = ResolveUIEditorThemeFloat( - theme, - "editor.size.field.dropdown_arrow_width", - metrics.dropdownArrowWidth); - metrics.dropdownArrowInsetX = ResolveUIEditorThemeFloat( - theme, - "editor.space.field.dropdown_arrow_inset_x", - metrics.dropdownArrowInsetX); - metrics.dropdownArrowInsetY = ResolveUIEditorThemeFloat( - theme, - "editor.space.field.dropdown_arrow_inset_y", - metrics.dropdownArrowInsetY); - metrics.cornerRounding = - ResolveUIEditorThemeFloat(theme, "editor.radius.field.row", metrics.cornerRounding); - metrics.valueBoxRounding = - ResolveUIEditorThemeFloat(theme, "editor.radius.field.control", metrics.valueBoxRounding); - metrics.borderThickness = - ResolveUIEditorThemeFloat(theme, "editor.border.field", metrics.borderThickness); - metrics.focusedBorderThickness = - ResolveUIEditorThemeFloat(theme, "editor.border.field.focus", metrics.focusedBorderThickness); - metrics.labelFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.label", "editor.font.field.label" }, - metrics.labelFontSize); - metrics.valueFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.value", "editor.font.field.value" }, - metrics.valueFontSize); - metrics.dropdownArrowFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.field.dropdown_arrow", "editor.font.field.glyph" }, - metrics.dropdownArrowFontSize); - return metrics; -} - -Widgets::UIEditorEnumFieldPalette ResolveUIEditorEnumFieldPalette( - const UITheme& theme, - const Widgets::UIEditorEnumFieldPalette& fallback) { - Widgets::UIEditorEnumFieldPalette palette = fallback; - palette.surfaceColor = ResolveUIEditorThemeColor(theme, "editor.color.field.row", palette.surfaceColor); - palette.borderColor = ResolveUIEditorThemeColor(theme, "editor.color.field.border", palette.borderColor); - palette.focusedBorderColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.border_focus", palette.focusedBorderColor); - palette.rowHoverColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.row_hover", palette.rowHoverColor); - palette.rowActiveColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.row_active", palette.rowActiveColor); - palette.valueBoxColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.control", palette.valueBoxColor); - palette.valueBoxHoverColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.control_hover", palette.valueBoxHoverColor); - palette.readOnlyColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.control_readonly", - palette.readOnlyColor); - palette.controlBorderColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.control_border", - palette.controlBorderColor); - palette.labelColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.label", palette.labelColor); - palette.valueColor = - ResolveUIEditorThemeColor(theme, "editor.color.field.value", palette.valueColor); - palette.arrowColor = ResolveUIEditorThemeColor( - theme, - "editor.color.field.dropdown_arrow", - palette.arrowColor); - return palette; -} - -Widgets::UIEditorMenuPopupMetrics ResolveUIEditorMenuPopupMetrics( - const UITheme& theme, - const Widgets::UIEditorMenuPopupMetrics& fallback) { - Widgets::UIEditorMenuPopupMetrics metrics = fallback; - metrics.contentPaddingX = ResolveThemeFloatAliases( - theme, - { - "editor.space.popup.padding_x", - "editor.space.menu_popup.padding_x", - "editor.space.menu.padding_x" - }, - metrics.contentPaddingX); - metrics.contentPaddingY = ResolveThemeFloatAliases( - theme, - { - "editor.space.popup.padding_y", - "editor.space.menu_popup.padding_y", - "editor.space.menu.padding_y" - }, - metrics.contentPaddingY); - metrics.itemHeight = ResolveThemeFloatAliases( - theme, - { - "editor.size.popup.item", - "editor.size.menu_popup.item", - "editor.size.menu.item" - }, - metrics.itemHeight); - metrics.separatorHeight = ResolveThemeFloatAliases( - theme, - { - "editor.size.popup.separator", - "editor.size.menu_popup.separator", - "editor.size.menu.separator" - }, - metrics.separatorHeight); - metrics.checkColumnWidth = ResolveThemeFloatAliases( - theme, - { - "editor.size.popup.check_column", - "editor.size.menu_popup.check_column", - "editor.size.menu.check_column" - }, - metrics.checkColumnWidth); - metrics.shortcutGap = ResolveThemeFloatAliases( - theme, - { - "editor.space.popup.shortcut_gap", - "editor.space.menu_popup.shortcut_gap", - "editor.space.menu.shortcut_gap" - }, - metrics.shortcutGap); - metrics.submenuIndicatorWidth = ResolveThemeFloatAliases( - theme, - { - "editor.size.popup.submenu_indicator", - "editor.size.menu_popup.submenu_indicator", - "editor.size.menu.submenu_indicator" - }, - metrics.submenuIndicatorWidth); - metrics.rowCornerRounding = ResolveThemeFloatAliases( - theme, - { - "editor.radius.popup.row", - "editor.radius.menu_popup.row", - "editor.radius.menu.row" - }, - metrics.rowCornerRounding); - metrics.popupCornerRounding = ResolveThemeFloatAliases( - theme, - { - "editor.radius.popup.surface", - "editor.radius.popup.panel", - "editor.radius.menu_popup.surface", - "editor.radius.menu.surface" - }, - metrics.popupCornerRounding); - metrics.labelInsetX = ResolveThemeFloatAliases( - theme, - { - "editor.space.popup.label_inset_x", - "editor.space.menu_popup.label_inset_x", - "editor.space.menu.label_inset_x" - }, - metrics.labelInsetX); - metrics.labelInsetY = ResolveThemeFloatAliases( - theme, - { - "editor.space.popup.label_inset_y", - "editor.space.menu_popup.label_inset_y", - "editor.space.menu.label_inset_y" - }, - metrics.labelInsetY); - metrics.labelFontSize = ResolveThemeFloatAliases( - theme, - { - "editor.typography.popup.label", - "editor.font.popup.label", - "editor.typography.menu_popup.label", - "editor.font.menu_popup.label", - "editor.typography.menu.label", - "editor.font.menu.label" - }, - metrics.labelFontSize); - metrics.shortcutInsetRight = ResolveThemeFloatAliases( - theme, - { - "editor.space.popup.shortcut_inset_right", - "editor.space.menu_popup.shortcut_inset_right", - "editor.space.menu.shortcut_inset_right" - }, - metrics.shortcutInsetRight); - metrics.estimatedGlyphWidth = ResolveThemeFloatAliases( - theme, - { - "editor.size.popup.estimated_glyph", - "editor.size.popup.estimated_glyph_width", - "editor.size.popup.glyph_width", - "editor.size.menu_popup.estimated_glyph", - "editor.size.menu_popup.estimated_glyph_width", - "editor.size.menu_popup.glyph_width", - "editor.size.menu.estimated_glyph", - "editor.size.menu.estimated_glyph_width", - "editor.size.menu.glyph_width" - }, - metrics.estimatedGlyphWidth); - metrics.glyphFontSize = ResolveThemeFloatAliases( - theme, - { - "editor.typography.popup.glyph", - "editor.font.popup.glyph", - "editor.typography.menu_popup.glyph", - "editor.font.menu_popup.glyph", - "editor.typography.menu.glyph", - "editor.font.menu.glyph" - }, - metrics.glyphFontSize); - metrics.separatorThickness = ResolveThemeFloatAliases( - theme, - { - "editor.border.popup.separator", - "editor.border.menu_popup.separator", - "editor.border.menu.separator" - }, - metrics.separatorThickness); - metrics.borderThickness = ResolveThemeFloatAliases( - theme, - { - "editor.border.popup", - "editor.border.popup.surface", - "editor.border.menu_popup", - "editor.border.menu_popup.surface", - "editor.border.menu.surface" - }, - metrics.borderThickness); - return metrics; -} - -Widgets::UIEditorMenuPopupPalette ResolveUIEditorMenuPopupPalette( - const UITheme& theme, - const Widgets::UIEditorMenuPopupPalette& fallback) { - Widgets::UIEditorMenuPopupPalette palette = fallback; - palette.popupColor = ResolveThemeColorAliases( - theme, - { - "editor.color.popup.surface", - "editor.color.menu_popup.surface", - "editor.color.menu.surface" - }, - palette.popupColor); - palette.borderColor = ResolveThemeColorAliases( - theme, - { - "editor.color.popup.border", - "editor.color.menu_popup.border", - "editor.color.menu.border" - }, - palette.borderColor); - palette.itemHoverColor = ResolveThemeColorAliases( - theme, - { - "editor.color.popup.item_hover", - "editor.color.menu_popup.item_hover", - "editor.color.menu.item_hover" - }, - palette.itemHoverColor); - palette.itemOpenColor = ResolveThemeColorAliases( - theme, - { - "editor.color.popup.item_open", - "editor.color.menu_popup.item_open", - "editor.color.menu.item_open" - }, - palette.itemOpenColor); - palette.separatorColor = ResolveThemeColorAliases( - theme, - { - "editor.color.popup.separator", - "editor.color.menu_popup.separator", - "editor.color.menu.separator" - }, - palette.separatorColor); - palette.textPrimary = ResolveThemeColorAliases( - theme, - { - "editor.color.popup.text", - "editor.color.popup.label", - "editor.color.menu_popup.text", - "editor.color.menu_popup.label", - "editor.color.menu.text", - "editor.color.menu.label" - }, - palette.textPrimary); - palette.textMuted = ResolveThemeColorAliases( - theme, - { - "editor.color.popup.shortcut", - "editor.color.popup.text_muted", - "editor.color.menu_popup.shortcut", - "editor.color.menu_popup.text_muted", - "editor.color.menu.shortcut", - "editor.color.menu.text_muted" - }, - palette.textMuted); - palette.textDisabled = ResolveThemeColorAliases( - theme, - { - "editor.color.popup.text_disabled", - "editor.color.menu_popup.text_disabled", - "editor.color.menu.text_disabled" - }, - palette.textDisabled); - palette.glyphColor = ResolveThemeColorAliases( - theme, - { - "editor.color.popup.glyph", - "editor.color.menu_popup.glyph", - "editor.color.menu.glyph" - }, - palette.glyphColor); - return palette; -} - -Widgets::UIEditorPropertyGridMetrics ResolveUIEditorPropertyGridMetrics( - const UITheme& theme, - const Widgets::UIEditorPropertyGridMetrics& fallback) { - Widgets::UIEditorPropertyGridMetrics metrics = fallback; - metrics.contentInset = - ResolveUIEditorThemeFloat(theme, "editor.space.property.content_inset", metrics.contentInset); - metrics.sectionGap = - ResolveUIEditorThemeFloat(theme, "editor.space.property.section_gap", metrics.sectionGap); - metrics.sectionHeaderHeight = ResolveUIEditorThemeFloat( - theme, - "editor.size.property.section_header", - metrics.sectionHeaderHeight); - metrics.fieldRowHeight = ResolveThemeFloatAliases( - theme, - { "editor.size.property.field_row", "editor.size.field.row" }, - metrics.fieldRowHeight); - metrics.rowGap = - ResolveUIEditorThemeFloat(theme, "editor.space.property.row_gap", metrics.rowGap); - metrics.horizontalPadding = ResolveThemeFloatAliases( - theme, - { "editor.space.property.padding_x", "editor.space.field.padding_x" }, - metrics.horizontalPadding); - metrics.controlColumnStart = ResolveThemeFloatAliases( - theme, - { "editor.layout.property.control_column", "editor.layout.field.control_column" }, - metrics.controlColumnStart); - metrics.labelControlGap = ResolveThemeFloatAliases( - theme, - { "editor.space.property.label_gap", "editor.space.field.label_gap" }, - metrics.labelControlGap); - metrics.disclosureExtent = ResolveThemeFloatAliases( - theme, - { "editor.size.property.disclosure", "editor.size.property.disclosure_extent" }, - metrics.disclosureExtent); - metrics.disclosureLabelGap = ResolveThemeFloatAliases( - theme, - { "editor.space.property.disclosure_gap", "editor.space.property.disclosure_label_gap" }, - metrics.disclosureLabelGap); - metrics.sectionTextInsetY = ResolveUIEditorThemeFloat( - theme, - "editor.space.property.section_inset_y", - metrics.sectionTextInsetY); - metrics.disclosureGlyphInsetX = ResolveUIEditorThemeFloat( - theme, - "editor.space.property.disclosure_glyph_inset_x", - metrics.disclosureGlyphInsetX); - metrics.disclosureGlyphInsetY = ResolveUIEditorThemeFloat( - theme, - "editor.space.property.disclosure_glyph_inset_y", - metrics.disclosureGlyphInsetY); - metrics.disclosureGlyphFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.property.disclosure", "editor.font.property.disclosure" }, - metrics.disclosureGlyphFontSize); - metrics.sectionFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.property.section", "editor.font.property.section" }, - metrics.sectionFontSize); - metrics.labelTextInsetY = ResolveThemeFloatAliases( - theme, - { "editor.space.property.label_inset_y", "editor.space.field.label_inset_y" }, - metrics.labelTextInsetY); - metrics.valueTextInsetY = ResolveThemeFloatAliases( - theme, - { "editor.space.property.value_inset_y", "editor.space.field.value_inset_y" }, - metrics.valueTextInsetY); - metrics.valueBoxInsetY = ResolveThemeFloatAliases( - theme, - { - "editor.space.property.control_inset_y", - "editor.space.property.value_box_inset_y", - "editor.space.field.control_inset_y" - }, - metrics.valueBoxInsetY); - metrics.valueBoxInsetX = ResolveThemeFloatAliases( - theme, - { - "editor.space.property.value_inset_x", - "editor.space.property.value_box_inset_x", - "editor.space.field.value_inset_x" - }, - metrics.valueBoxInsetX); - metrics.cornerRounding = ResolveThemeFloatAliases( - theme, - { "editor.radius.property.grid", "editor.radius.property.panel" }, - metrics.cornerRounding); - metrics.valueBoxRounding = ResolveThemeFloatAliases( - theme, - { "editor.radius.property.value", "editor.radius.field.control" }, - metrics.valueBoxRounding); - metrics.borderThickness = - ResolveUIEditorThemeFloat(theme, "editor.border.property", metrics.borderThickness); - metrics.focusedBorderThickness = - ResolveUIEditorThemeFloat(theme, "editor.border.property.focus", metrics.focusedBorderThickness); - metrics.editOutlineThickness = ResolveThemeFloatAliases( - theme, - { "editor.border.property.edit", "editor.border.property.edit_outline" }, - metrics.editOutlineThickness); - metrics.labelFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.property.label", "editor.font.property.label" }, - metrics.labelFontSize); - metrics.valueFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.property.value", "editor.font.property.value" }, - metrics.valueFontSize); - metrics.tagFontSize = ResolveThemeFloatAliases( - theme, - { "editor.typography.property.tag", "editor.font.property.tag" }, - metrics.tagFontSize); - return metrics; -} - -Widgets::UIEditorPropertyGridPalette ResolveUIEditorPropertyGridPalette( - const UITheme& theme, - const Widgets::UIEditorPropertyGridPalette& fallback) { - Widgets::UIEditorPropertyGridPalette palette = fallback; - palette.surfaceColor = - ResolveUIEditorThemeColor(theme, "editor.color.property.surface", palette.surfaceColor); - palette.borderColor = - ResolveUIEditorThemeColor(theme, "editor.color.property.border", palette.borderColor); - palette.focusedBorderColor = ResolveUIEditorThemeColor( - theme, - "editor.color.property.border_focus", - palette.focusedBorderColor); - palette.sectionHeaderColor = - ResolveUIEditorThemeColor(theme, "editor.color.property.section", palette.sectionHeaderColor); - palette.sectionHeaderHoverColor = ResolveUIEditorThemeColor( - theme, - "editor.color.property.section_hover", - palette.sectionHeaderHoverColor); - palette.fieldHoverColor = ResolveUIEditorThemeColor( - theme, - "editor.color.property.field_hover", - palette.fieldHoverColor); - palette.fieldSelectedColor = ResolveUIEditorThemeColor( - theme, - "editor.color.property.field_selected", - palette.fieldSelectedColor); - palette.fieldSelectedFocusedColor = ResolveUIEditorThemeColor( - theme, - "editor.color.property.field_selected_focused", - palette.fieldSelectedFocusedColor); - palette.valueBoxColor = ResolveThemeColorAliases( - theme, - { "editor.color.property.control", "editor.color.property.value" }, - palette.valueBoxColor); - palette.valueBoxHoverColor = ResolveThemeColorAliases( - theme, - { "editor.color.property.control_hover", "editor.color.property.value_hover" }, - palette.valueBoxHoverColor); - palette.valueBoxEditingColor = ResolveThemeColorAliases( - theme, - { "editor.color.property.control_editing", "editor.color.property.value_editing" }, - palette.valueBoxEditingColor); - palette.valueBoxReadOnlyColor = ResolveThemeColorAliases( - theme, - { "editor.color.property.control_readonly", "editor.color.property.value_readonly" }, - palette.valueBoxReadOnlyColor); - palette.valueBoxBorderColor = ResolveThemeColorAliases( - theme, - { "editor.color.property.control_border", "editor.color.property.value_border" }, - palette.valueBoxBorderColor); - palette.valueBoxEditingBorderColor = ResolveThemeColorAliases( - theme, - { "editor.color.property.control_border_editing", "editor.color.property.value_border_editing" }, - palette.valueBoxEditingBorderColor); - palette.disclosureColor = ResolveUIEditorThemeColor( - theme, - "editor.color.property.disclosure", - palette.disclosureColor); - palette.sectionTextColor = ResolveUIEditorThemeColor( - theme, - "editor.color.property.section_text", - palette.sectionTextColor); - palette.labelTextColor = - ResolveUIEditorThemeColor(theme, "editor.color.property.label", palette.labelTextColor); - palette.valueTextColor = ResolveThemeColorAliases( - theme, - { - "editor.color.property.value_text", - "editor.color.property.text", - "editor.color.property.value" - }, - palette.valueTextColor); - palette.readOnlyValueTextColor = ResolveThemeColorAliases( - theme, - { - "editor.color.property.value_text_readonly", - "editor.color.property.text_readonly", - "editor.color.property.value_readonly" - }, - palette.readOnlyValueTextColor); - palette.editTagColor = - ResolveUIEditorThemeColor(theme, "editor.color.property.edit_tag", palette.editTagColor); - return palette; -} - -Widgets::UIEditorBoolFieldMetrics BuildUIEditorHostedBoolFieldMetrics( - const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, - const Widgets::UIEditorBoolFieldMetrics& fallback) { - Widgets::UIEditorBoolFieldMetrics hosted = fallback; - hosted.rowHeight = propertyGridMetrics.fieldRowHeight; - hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; - hosted.labelControlGap = propertyGridMetrics.labelControlGap; - hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; - hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; - hosted.cornerRounding = propertyGridMetrics.cornerRounding; - hosted.borderThickness = propertyGridMetrics.borderThickness; - hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; - hosted.labelFontSize = propertyGridMetrics.labelFontSize; - hosted.checkboxGlyphFontSize = propertyGridMetrics.valueFontSize; - return hosted; -} - -Widgets::UIEditorBoolFieldPalette BuildUIEditorHostedBoolFieldPalette( - const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, - const Widgets::UIEditorBoolFieldPalette& fallback) { - Widgets::UIEditorBoolFieldPalette hosted = fallback; - hosted.surfaceColor = kTransparent; - hosted.borderColor = kTransparent; - hosted.focusedBorderColor = kTransparent; - hosted.rowHoverColor = kTransparent; - hosted.rowActiveColor = kTransparent; - hosted.checkboxColor = propertyGridPalette.valueBoxColor; - hosted.checkboxHoverColor = propertyGridPalette.valueBoxHoverColor; - hosted.checkboxReadOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; - hosted.checkboxBorderColor = propertyGridPalette.valueBoxBorderColor; - hosted.checkboxMarkColor = propertyGridPalette.valueTextColor; - hosted.labelColor = propertyGridPalette.labelTextColor; - return hosted; -} - -Widgets::UIEditorNumberFieldMetrics BuildUIEditorHostedNumberFieldMetrics( - const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, - const Widgets::UIEditorNumberFieldMetrics& fallback) { - Widgets::UIEditorNumberFieldMetrics hosted = fallback; - hosted.rowHeight = propertyGridMetrics.fieldRowHeight; - hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; - hosted.labelControlGap = propertyGridMetrics.labelControlGap; - hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; - hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; - hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; - hosted.valueTextInsetX = propertyGridMetrics.valueBoxInsetX; - hosted.valueTextInsetY = propertyGridMetrics.valueTextInsetY; - hosted.cornerRounding = propertyGridMetrics.cornerRounding; - hosted.valueBoxRounding = propertyGridMetrics.valueBoxRounding; - hosted.borderThickness = propertyGridMetrics.borderThickness; - hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; - hosted.labelFontSize = propertyGridMetrics.labelFontSize; - hosted.valueFontSize = propertyGridMetrics.valueFontSize; - return hosted; -} - -Widgets::UIEditorNumberFieldPalette BuildUIEditorHostedNumberFieldPalette( - const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, - const Widgets::UIEditorNumberFieldPalette& fallback) { - Widgets::UIEditorNumberFieldPalette hosted = fallback; - hosted.surfaceColor = kTransparent; - hosted.borderColor = kTransparent; - hosted.focusedBorderColor = kTransparent; - hosted.rowHoverColor = kTransparent; - hosted.rowActiveColor = kTransparent; - hosted.valueBoxColor = propertyGridPalette.valueBoxColor; - hosted.valueBoxHoverColor = propertyGridPalette.valueBoxHoverColor; - hosted.valueBoxEditingColor = propertyGridPalette.valueBoxEditingColor; - hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; - hosted.controlBorderColor = propertyGridPalette.valueBoxBorderColor; - hosted.controlFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; - hosted.labelColor = propertyGridPalette.labelTextColor; - hosted.valueColor = propertyGridPalette.valueTextColor; - hosted.readOnlyValueColor = propertyGridPalette.readOnlyValueTextColor; - return hosted; -} - -Widgets::UIEditorTextFieldMetrics BuildUIEditorHostedTextFieldMetrics( - const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, - const Widgets::UIEditorTextFieldMetrics& fallback) { - Widgets::UIEditorTextFieldMetrics hosted = fallback; - hosted.rowHeight = propertyGridMetrics.fieldRowHeight; - hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; - hosted.labelControlGap = propertyGridMetrics.labelControlGap; - hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; - hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; - hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; - hosted.valueTextInsetX = propertyGridMetrics.valueBoxInsetX; - hosted.valueTextInsetY = propertyGridMetrics.valueTextInsetY; - hosted.cornerRounding = propertyGridMetrics.cornerRounding; - hosted.valueBoxRounding = propertyGridMetrics.valueBoxRounding; - hosted.borderThickness = propertyGridMetrics.borderThickness; - hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; - hosted.labelFontSize = propertyGridMetrics.labelFontSize; - hosted.valueFontSize = propertyGridMetrics.valueFontSize; - return hosted; -} - -Widgets::UIEditorTextFieldPalette BuildUIEditorHostedTextFieldPalette( - const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, - const Widgets::UIEditorTextFieldPalette& fallback) { - Widgets::UIEditorTextFieldPalette hosted = fallback; - hosted.surfaceColor = kTransparent; - hosted.borderColor = kTransparent; - hosted.focusedBorderColor = kTransparent; - hosted.rowHoverColor = kTransparent; - hosted.rowActiveColor = kTransparent; - hosted.valueBoxColor = propertyGridPalette.valueBoxColor; - hosted.valueBoxHoverColor = propertyGridPalette.valueBoxHoverColor; - hosted.valueBoxEditingColor = propertyGridPalette.valueBoxEditingColor; - hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; - hosted.controlBorderColor = propertyGridPalette.valueBoxBorderColor; - hosted.controlFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; - hosted.labelColor = propertyGridPalette.labelTextColor; - hosted.valueColor = propertyGridPalette.valueTextColor; - hosted.readOnlyValueColor = propertyGridPalette.readOnlyValueTextColor; - return hosted; -} - -Widgets::UIEditorVector2FieldMetrics BuildUIEditorHostedVector2FieldMetrics( - const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, - const Widgets::UIEditorVector2FieldMetrics& fallback) { - Widgets::UIEditorVector2FieldMetrics hosted = fallback; - hosted.rowHeight = propertyGridMetrics.fieldRowHeight; - hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; - hosted.labelControlGap = propertyGridMetrics.labelControlGap; - hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; - hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; - hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; - hosted.labelFontSize = propertyGridMetrics.labelFontSize; - hosted.valueTextInsetX = propertyGridMetrics.valueBoxInsetX; - hosted.valueTextInsetY = propertyGridMetrics.valueTextInsetY; - hosted.valueFontSize = propertyGridMetrics.valueFontSize; - hosted.cornerRounding = propertyGridMetrics.cornerRounding; - hosted.componentRounding = propertyGridMetrics.valueBoxRounding; - hosted.borderThickness = propertyGridMetrics.borderThickness; - hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; - return hosted; -} - -Widgets::UIEditorVector2FieldPalette BuildUIEditorHostedVector2FieldPalette( - const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, - const Widgets::UIEditorVector2FieldPalette& fallback) { - Widgets::UIEditorVector2FieldPalette hosted = fallback; - hosted.surfaceColor = kTransparent; - hosted.borderColor = kTransparent; - hosted.focusedBorderColor = kTransparent; - hosted.rowHoverColor = kTransparent; - hosted.rowActiveColor = kTransparent; - hosted.componentColor = propertyGridPalette.valueBoxColor; - hosted.componentHoverColor = propertyGridPalette.valueBoxHoverColor; - hosted.componentEditingColor = propertyGridPalette.valueBoxEditingColor; - hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; - hosted.componentBorderColor = propertyGridPalette.valueBoxBorderColor; - hosted.componentFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; - hosted.prefixColor = propertyGridPalette.valueBoxHoverColor; - hosted.prefixBorderColor = propertyGridPalette.valueBoxBorderColor; - hosted.labelColor = propertyGridPalette.labelTextColor; - hosted.valueColor = propertyGridPalette.valueTextColor; - hosted.readOnlyValueColor = propertyGridPalette.readOnlyValueTextColor; - return hosted; -} - -Widgets::UIEditorVector3FieldMetrics BuildUIEditorHostedVector3FieldMetrics( - const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, - const Widgets::UIEditorVector3FieldMetrics& fallback) { - Widgets::UIEditorVector3FieldMetrics hosted = fallback; - hosted.rowHeight = propertyGridMetrics.fieldRowHeight; - hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; - hosted.labelControlGap = propertyGridMetrics.labelControlGap; - hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; - hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; - hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; - hosted.labelFontSize = propertyGridMetrics.labelFontSize; - hosted.valueTextInsetX = propertyGridMetrics.valueBoxInsetX; - hosted.valueTextInsetY = propertyGridMetrics.valueTextInsetY; - hosted.valueFontSize = propertyGridMetrics.valueFontSize; - hosted.cornerRounding = propertyGridMetrics.cornerRounding; - hosted.componentRounding = propertyGridMetrics.valueBoxRounding; - hosted.borderThickness = propertyGridMetrics.borderThickness; - hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; - return hosted; -} - -Widgets::UIEditorVector3FieldPalette BuildUIEditorHostedVector3FieldPalette( - const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, - const Widgets::UIEditorVector3FieldPalette& fallback) { - Widgets::UIEditorVector3FieldPalette hosted = fallback; - hosted.surfaceColor = kTransparent; - hosted.borderColor = kTransparent; - hosted.focusedBorderColor = kTransparent; - hosted.rowHoverColor = kTransparent; - hosted.rowActiveColor = kTransparent; - hosted.componentColor = propertyGridPalette.valueBoxColor; - hosted.componentHoverColor = propertyGridPalette.valueBoxHoverColor; - hosted.componentEditingColor = propertyGridPalette.valueBoxEditingColor; - hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; - hosted.componentBorderColor = propertyGridPalette.valueBoxBorderColor; - hosted.componentFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; - hosted.prefixColor = propertyGridPalette.valueBoxHoverColor; - hosted.prefixBorderColor = propertyGridPalette.valueBoxBorderColor; - hosted.labelColor = propertyGridPalette.labelTextColor; - hosted.valueColor = propertyGridPalette.valueTextColor; - hosted.readOnlyValueColor = propertyGridPalette.readOnlyValueTextColor; - return hosted; -} - -Widgets::UIEditorVector4FieldMetrics BuildUIEditorHostedVector4FieldMetrics( - const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, - const Widgets::UIEditorVector4FieldMetrics& fallback) { - Widgets::UIEditorVector4FieldMetrics hosted = fallback; - hosted.rowHeight = propertyGridMetrics.fieldRowHeight; - hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; - hosted.labelControlGap = propertyGridMetrics.labelControlGap; - hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; - hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; - hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; - hosted.labelFontSize = propertyGridMetrics.labelFontSize; - hosted.valueTextInsetX = propertyGridMetrics.valueBoxInsetX; - hosted.valueTextInsetY = propertyGridMetrics.valueTextInsetY; - hosted.valueFontSize = propertyGridMetrics.valueFontSize; - hosted.cornerRounding = propertyGridMetrics.cornerRounding; - hosted.componentRounding = propertyGridMetrics.valueBoxRounding; - hosted.borderThickness = propertyGridMetrics.borderThickness; - hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; - return hosted; -} - -Widgets::UIEditorVector4FieldPalette BuildUIEditorHostedVector4FieldPalette( - const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, - const Widgets::UIEditorVector4FieldPalette& fallback) { - Widgets::UIEditorVector4FieldPalette hosted = fallback; - hosted.surfaceColor = kTransparent; - hosted.borderColor = kTransparent; - hosted.focusedBorderColor = kTransparent; - hosted.rowHoverColor = kTransparent; - hosted.rowActiveColor = kTransparent; - hosted.componentColor = propertyGridPalette.valueBoxColor; - hosted.componentHoverColor = propertyGridPalette.valueBoxHoverColor; - hosted.componentEditingColor = propertyGridPalette.valueBoxEditingColor; - hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; - hosted.componentBorderColor = propertyGridPalette.valueBoxBorderColor; - hosted.componentFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; - hosted.prefixColor = propertyGridPalette.valueBoxHoverColor; - hosted.prefixBorderColor = propertyGridPalette.valueBoxBorderColor; - hosted.labelColor = propertyGridPalette.labelTextColor; - hosted.valueColor = propertyGridPalette.valueTextColor; - hosted.readOnlyValueColor = propertyGridPalette.readOnlyValueTextColor; - return hosted; -} - -Widgets::UIEditorEnumFieldMetrics BuildUIEditorHostedEnumFieldMetrics( - const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, - const Widgets::UIEditorEnumFieldMetrics& fallback) { - Widgets::UIEditorEnumFieldMetrics hosted = fallback; - hosted.rowHeight = propertyGridMetrics.fieldRowHeight; - hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; - hosted.labelControlGap = propertyGridMetrics.labelControlGap; - hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; - hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; - hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; - hosted.valueTextInsetX = propertyGridMetrics.valueBoxInsetX; - hosted.valueTextInsetY = propertyGridMetrics.valueTextInsetY; - hosted.cornerRounding = propertyGridMetrics.cornerRounding; - hosted.valueBoxRounding = propertyGridMetrics.valueBoxRounding; - hosted.borderThickness = propertyGridMetrics.borderThickness; - hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; - hosted.labelFontSize = propertyGridMetrics.labelFontSize; - hosted.valueFontSize = propertyGridMetrics.valueFontSize; - hosted.dropdownArrowFontSize = propertyGridMetrics.valueFontSize; - return hosted; -} - -Widgets::UIEditorEnumFieldPalette BuildUIEditorHostedEnumFieldPalette( - const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, - const Widgets::UIEditorEnumFieldPalette& fallback) { - Widgets::UIEditorEnumFieldPalette hosted = fallback; - hosted.surfaceColor = kTransparent; - hosted.borderColor = kTransparent; - hosted.focusedBorderColor = kTransparent; - hosted.rowHoverColor = kTransparent; - hosted.rowActiveColor = kTransparent; - hosted.valueBoxColor = propertyGridPalette.valueBoxColor; - hosted.valueBoxHoverColor = propertyGridPalette.valueBoxHoverColor; - hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; - hosted.controlBorderColor = propertyGridPalette.valueBoxBorderColor; - hosted.labelColor = propertyGridPalette.labelTextColor; - hosted.valueColor = propertyGridPalette.valueTextColor; - hosted.arrowColor = propertyGridPalette.valueTextColor; - return hosted; -} - -} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Core/UIEditorTreeViewInteraction.cpp b/new_editor/src/Core/UIEditorTreeViewInteraction.cpp deleted file mode 100644 index 931de985..00000000 --- a/new_editor/src/Core/UIEditorTreeViewInteraction.cpp +++ /dev/null @@ -1,192 +0,0 @@ -#include - -namespace XCEngine::UI::Editor { - -namespace { - -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIPointerButton; -using Widgets::BuildUIEditorTreeViewLayout; -using Widgets::DoesUIEditorTreeViewItemHaveChildren; -using Widgets::HitTestUIEditorTreeView; -using Widgets::IsUIEditorTreeViewPointInside; -using Widgets::UIEditorTreeViewHitTarget; -using Widgets::UIEditorTreeViewHitTargetKind; - -bool ShouldUsePointerPosition(const UIInputEvent& event) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerEnter: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - return true; - default: - return false; - } -} - -void SyncHoverTarget( - UIEditorTreeViewInteractionState& state, - const Widgets::UIEditorTreeViewLayout& layout, - const std::vector& items) { - state.treeViewState.hoveredItemId.clear(); - if (!state.hasPointerPosition) { - return; - } - - const UIEditorTreeViewHitTarget hitTarget = - HitTestUIEditorTreeView(layout, state.pointerPosition); - if (hitTarget.itemIndex < items.size()) { - state.treeViewState.hoveredItemId = items[hitTarget.itemIndex].itemId; - } -} - -} // namespace - -UIEditorTreeViewInteractionFrame UpdateUIEditorTreeViewInteraction( - UIEditorTreeViewInteractionState& state, - ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, - ::XCEngine::UI::Widgets::UIExpansionModel& expansionModel, - const ::XCEngine::UI::UIRect& bounds, - const std::vector& items, - const std::vector& inputEvents, - const Widgets::UIEditorTreeViewMetrics& metrics) { - Widgets::UIEditorTreeViewLayout layout = - BuildUIEditorTreeViewLayout(bounds, items, expansionModel, metrics); - SyncHoverTarget(state, layout, items); - - UIEditorTreeViewInteractionResult interactionResult = {}; - for (const UIInputEvent& event : inputEvents) { - if (ShouldUsePointerPosition(event)) { - state.pointerPosition = event.position; - state.hasPointerPosition = true; - } else if (event.type == UIInputEventType::PointerLeave) { - state.hasPointerPosition = false; - } - - UIEditorTreeViewInteractionResult eventResult = {}; - switch (event.type) { - case UIInputEventType::FocusGained: - state.treeViewState.focused = true; - break; - - case UIInputEventType::FocusLost: - state.treeViewState.focused = false; - state.hasPointerPosition = false; - state.treeViewState.hoveredItemId.clear(); - break; - - case UIInputEventType::PointerMove: - case UIInputEventType::PointerEnter: - break; - - case UIInputEventType::PointerLeave: - state.treeViewState.hoveredItemId.clear(); - break; - - case UIInputEventType::PointerButtonDown: { - const UIEditorTreeViewHitTarget hitTarget = - state.hasPointerPosition - ? HitTestUIEditorTreeView(layout, state.pointerPosition) - : UIEditorTreeViewHitTarget {}; - eventResult.hitTarget = hitTarget; - if ((event.pointerButton == UIPointerButton::Left || - event.pointerButton == UIPointerButton::Right) && - hitTarget.kind != UIEditorTreeViewHitTargetKind::None) { - state.treeViewState.focused = true; - eventResult.consumed = true; - } else if (event.pointerButton == UIPointerButton::Left && - (!state.hasPointerPosition || - !IsUIEditorTreeViewPointInside(layout.bounds, state.pointerPosition))) { - state.treeViewState.focused = false; - } - break; - } - - case UIInputEventType::PointerButtonUp: { - const UIEditorTreeViewHitTarget hitTarget = - state.hasPointerPosition - ? HitTestUIEditorTreeView(layout, state.pointerPosition) - : UIEditorTreeViewHitTarget {}; - eventResult.hitTarget = hitTarget; - - const bool insideTree = - state.hasPointerPosition && - IsUIEditorTreeViewPointInside(layout.bounds, state.pointerPosition); - - if (hitTarget.itemIndex >= items.size()) { - if (event.pointerButton == UIPointerButton::Left && insideTree) { - eventResult.consumed = true; - state.treeViewState.focused = true; - } else if (event.pointerButton == UIPointerButton::Left) { - state.treeViewState.focused = false; - } - break; - } - - const Widgets::UIEditorTreeViewItem& item = items[hitTarget.itemIndex]; - if (event.pointerButton == UIPointerButton::Left) { - if (hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure && - DoesUIEditorTreeViewItemHaveChildren(items, hitTarget.itemIndex)) { - eventResult.expansionChanged = - expansionModel.ToggleExpanded(item.itemId); - eventResult.toggledItemId = item.itemId; - eventResult.consumed = true; - state.treeViewState.focused = true; - } else if (hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { - eventResult.selectionChanged = - selectionModel.SetSelection(item.itemId); - eventResult.selectedItemId = item.itemId; - eventResult.consumed = true; - state.treeViewState.focused = true; - } - } else if (event.pointerButton == UIPointerButton::Right && - (hitTarget.kind == UIEditorTreeViewHitTargetKind::Row || - hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure)) { - eventResult.selectionChanged = - selectionModel.SetSelection(item.itemId); - eventResult.selectedItemId = item.itemId; - eventResult.secondaryClicked = true; - eventResult.consumed = true; - state.treeViewState.focused = true; - } - break; - } - - default: - break; - } - - layout = BuildUIEditorTreeViewLayout(bounds, items, expansionModel, metrics); - SyncHoverTarget(state, layout, items); - if (eventResult.hitTarget.kind == UIEditorTreeViewHitTargetKind::None && - state.hasPointerPosition) { - eventResult.hitTarget = HitTestUIEditorTreeView(layout, state.pointerPosition); - } - - if (eventResult.consumed || - eventResult.selectionChanged || - eventResult.expansionChanged || - eventResult.secondaryClicked || - eventResult.hitTarget.kind != UIEditorTreeViewHitTargetKind::None || - !eventResult.selectedItemId.empty() || - !eventResult.toggledItemId.empty()) { - interactionResult = std::move(eventResult); - } - } - - layout = BuildUIEditorTreeViewLayout(bounds, items, expansionModel, metrics); - SyncHoverTarget(state, layout, items); - if (interactionResult.hitTarget.kind == UIEditorTreeViewHitTargetKind::None && - state.hasPointerPosition) { - interactionResult.hitTarget = HitTestUIEditorTreeView(layout, state.pointerPosition); - } - - return { - std::move(layout), - std::move(interactionResult) - }; -} - -} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Fields/UIEditorAssetField.cpp b/new_editor/src/Fields/UIEditorAssetField.cpp new file mode 100644 index 00000000..ae817718 --- /dev/null +++ b/new_editor/src/Fields/UIEditorAssetField.cpp @@ -0,0 +1,394 @@ +#include +#include +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor::Widgets { + +namespace { + +using ::XCEngine::UI::UIDrawList; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; + +float ClampNonNegative(float value) { + return (std::max)(0.0f, value); +} + +float EstimateTextWidth(std::string_view text, float fontSize) { + return static_cast(text.size()) * fontSize * 0.58f; +} + +bool ShowsPickerButton(const UIEditorAssetFieldSpec& spec) { + return spec.showPickerButton; +} + +bool ShowsClearButton(const UIEditorAssetFieldSpec& spec) { + return spec.allowClear && HasUIEditorAssetFieldValue(spec); +} + +bool ShowsStatusBadge(const UIEditorAssetFieldSpec& spec) { + return spec.showStatusBadge && !spec.statusText.empty(); +} + +::XCEngine::UI::UIColor ResolveRowFillColor( + const UIEditorAssetFieldState& state, + const UIEditorAssetFieldPalette& palette) { + if (state.activeTarget != UIEditorAssetFieldHitTargetKind::None) { + return palette.rowActiveColor; + } + if (state.hoveredTarget != UIEditorAssetFieldHitTargetKind::None) { + return palette.rowHoverColor; + } + return palette.surfaceColor; +} + +::XCEngine::UI::UIColor ResolveValueFillColor( + const UIEditorAssetFieldSpec& spec, + const UIEditorAssetFieldState& state, + const UIEditorAssetFieldPalette& palette) { + if (spec.readOnly) { + return palette.readOnlyColor; + } + if (state.activeTarget == UIEditorAssetFieldHitTargetKind::ValueBox || + state.activeTarget == UIEditorAssetFieldHitTargetKind::PickerButton || + state.activeTarget == UIEditorAssetFieldHitTargetKind::ClearButton) { + return palette.valueBoxActiveColor; + } + if (state.hoveredTarget == UIEditorAssetFieldHitTargetKind::ValueBox || + state.hoveredTarget == UIEditorAssetFieldHitTargetKind::PickerButton || + state.hoveredTarget == UIEditorAssetFieldHitTargetKind::ClearButton) { + return palette.valueBoxHoverColor; + } + return palette.valueBoxColor; +} + +::XCEngine::UI::UIColor ResolveActionFillColor( + UIEditorAssetFieldHitTargetKind targetKind, + const UIEditorAssetFieldState& state, + const UIEditorAssetFieldPalette& palette) { + if (state.activeTarget == targetKind) { + return palette.actionButtonActiveColor; + } + if (state.hoveredTarget == targetKind) { + return palette.actionButtonHoverColor; + } + return palette.actionButtonColor; +} + +} // namespace + +bool HasUIEditorAssetFieldValue(const UIEditorAssetFieldSpec& spec) { + return !spec.assetId.empty() || !spec.displayName.empty(); +} + +std::string ResolveUIEditorAssetFieldValueText(const UIEditorAssetFieldSpec& spec) { + if (!spec.displayName.empty()) { + return spec.displayName; + } + + if (!spec.assetId.empty()) { + return spec.assetId; + } + + return spec.emptyText.empty() ? std::string("None") : spec.emptyText; +} + +std::string ResolveUIEditorAssetFieldPreviewGlyph(const UIEditorAssetFieldSpec& spec) { + const std::string source = + !spec.statusText.empty() ? spec.statusText : ResolveUIEditorAssetFieldValueText(spec); + for (char character : source) { + if (std::isalnum(static_cast(character)) != 0) { + return std::string( + 1u, + static_cast(std::toupper(static_cast(character)))); + } + } + + return "-"; +} + +bool IsUIEditorAssetFieldPointInside(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; +} + +UIEditorAssetFieldLayout BuildUIEditorAssetFieldLayout( + const UIRect& bounds, + const UIEditorAssetFieldSpec& spec, + const UIEditorAssetFieldMetrics& metrics) { + const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout( + bounds, + metrics.valueBoxMinWidth, + UIEditorFieldRowLayoutMetrics { + metrics.rowHeight, + metrics.horizontalPadding, + metrics.labelControlGap, + metrics.controlColumnStart, + metrics.controlTrailingInset, + metrics.controlInsetY, + }); + + UIEditorAssetFieldLayout layout = {}; + layout.bounds = hostLayout.bounds; + layout.labelRect = hostLayout.labelRect; + layout.controlRect = hostLayout.controlRect; + layout.valueRect = hostLayout.controlRect; + + float trailingX = layout.valueRect.x + layout.valueRect.width; + if (ShowsPickerButton(spec)) { + trailingX -= metrics.actionButtonWidth; + layout.pickerRect = UIRect( + trailingX, + layout.valueRect.y, + metrics.actionButtonWidth, + layout.valueRect.height); + trailingX -= metrics.actionButtonGap; + } + + if (ShowsClearButton(spec)) { + trailingX -= metrics.actionButtonWidth; + layout.clearRect = UIRect( + trailingX, + layout.valueRect.y, + metrics.actionButtonWidth, + layout.valueRect.height); + trailingX -= metrics.actionButtonGap; + } + + const float previewSize = + (std::min)(metrics.previewSize, ClampNonNegative(layout.valueRect.height - 4.0f)); + layout.previewRect = UIRect( + layout.valueRect.x + metrics.previewInsetX, + layout.valueRect.y + ClampNonNegative((layout.valueRect.height - previewSize) * 0.5f), + previewSize, + previewSize); + + const float contentLeft = layout.previewRect.x + layout.previewRect.width + metrics.previewGap; + if (ShowsStatusBadge(spec)) { + const float estimatedBadgeWidth = + EstimateTextWidth(spec.statusText, metrics.statusBadgeFontSize) + + metrics.statusBadgePaddingX * 2.0f; + const float badgeWidth = (std::min)( + (std::max)(metrics.statusBadgeMinWidth, estimatedBadgeWidth), + ClampNonNegative((trailingX - contentLeft) * 0.45f)); + if (badgeWidth > 20.0f) { + trailingX -= badgeWidth; + layout.statusBadgeRect = UIRect( + trailingX, + layout.valueRect.y + ClampNonNegative((layout.valueRect.height - metrics.statusBadgeHeight) * 0.5f), + badgeWidth, + (std::min)(metrics.statusBadgeHeight, layout.valueRect.height)); + trailingX -= metrics.statusBadgeGap; + } + } + + layout.textRect = UIRect( + contentLeft + metrics.valueTextInsetX, + layout.valueRect.y, + ClampNonNegative(trailingX - contentLeft - metrics.valueTextInsetX), + layout.valueRect.height); + return layout; +} + +UIEditorAssetFieldHitTarget HitTestUIEditorAssetField( + const UIEditorAssetFieldLayout& layout, + const UIPoint& point) { + if (layout.clearRect.width > 0.0f && + IsUIEditorAssetFieldPointInside(layout.clearRect, point)) { + return { UIEditorAssetFieldHitTargetKind::ClearButton }; + } + if (layout.pickerRect.width > 0.0f && + IsUIEditorAssetFieldPointInside(layout.pickerRect, point)) { + return { UIEditorAssetFieldHitTargetKind::PickerButton }; + } + if (IsUIEditorAssetFieldPointInside(layout.valueRect, point)) { + return { UIEditorAssetFieldHitTargetKind::ValueBox }; + } + if (IsUIEditorAssetFieldPointInside(layout.bounds, point)) { + return { UIEditorAssetFieldHitTargetKind::Row }; + } + return {}; +} + +void AppendUIEditorAssetFieldBackground( + UIDrawList& drawList, + const UIEditorAssetFieldLayout& layout, + const UIEditorAssetFieldSpec& spec, + const UIEditorAssetFieldState& state, + const UIEditorAssetFieldPalette& palette, + const UIEditorAssetFieldMetrics& metrics) { + const auto rowFillColor = ResolveRowFillColor(state, palette); + if (rowFillColor.a > 0.0f) { + drawList.AddFilledRect(layout.bounds, rowFillColor, metrics.cornerRounding); + } + + const auto rowBorderColor = state.focused ? palette.focusedBorderColor : palette.borderColor; + if (rowBorderColor.a > 0.0f) { + drawList.AddRectOutline( + layout.bounds, + rowBorderColor, + state.focused ? metrics.focusedBorderThickness : metrics.borderThickness, + metrics.cornerRounding); + } + + drawList.AddFilledRect( + layout.valueRect, + ResolveValueFillColor(spec, state, palette), + metrics.valueBoxRounding); + drawList.AddRectOutline( + layout.valueRect, + state.focused ? palette.controlFocusedBorderColor : palette.controlBorderColor, + state.focused ? metrics.focusedBorderThickness : metrics.borderThickness, + metrics.valueBoxRounding); + + if (HasUIEditorAssetFieldValue(spec)) { + drawList.AddFilledRectLinearGradient( + layout.previewRect, + palette.previewBaseColor, + spec.tint, + ::XCEngine::UI::UILinearGradientDirection::Vertical, + metrics.previewRounding); + } else { + drawList.AddFilledRect(layout.previewRect, palette.previewEmptyColor, metrics.previewRounding); + } + drawList.AddRectOutline( + layout.previewRect, + palette.previewBorderColor, + metrics.borderThickness, + metrics.previewRounding); + + if (layout.statusBadgeRect.width > 0.0f && layout.statusBadgeRect.height > 0.0f) { + drawList.AddFilledRect(layout.statusBadgeRect, palette.statusBadgeColor, metrics.badgeRounding); + drawList.AddRectOutline( + layout.statusBadgeRect, + palette.statusBadgeBorderColor, + metrics.borderThickness, + metrics.badgeRounding); + } + + if (layout.pickerRect.width > 0.0f) { + const auto fill = + ResolveActionFillColor(UIEditorAssetFieldHitTargetKind::PickerButton, state, palette); + drawList.AddFilledRect(layout.pickerRect, fill, metrics.valueBoxRounding); + drawList.AddLine( + UIPoint(layout.pickerRect.x, layout.pickerRect.y + 1.0f), + UIPoint(layout.pickerRect.x, layout.pickerRect.y + layout.pickerRect.height - 1.0f), + palette.separatorColor, + 1.0f); + } + + if (layout.clearRect.width > 0.0f) { + const auto fill = + ResolveActionFillColor(UIEditorAssetFieldHitTargetKind::ClearButton, state, palette); + drawList.AddFilledRect(layout.clearRect, fill, metrics.valueBoxRounding); + drawList.AddLine( + UIPoint(layout.clearRect.x, layout.clearRect.y + 1.0f), + UIPoint(layout.clearRect.x, layout.clearRect.y + layout.clearRect.height - 1.0f), + palette.separatorColor, + 1.0f); + } +} + +void AppendUIEditorAssetFieldForeground( + UIDrawList& drawList, + const UIEditorAssetFieldLayout& layout, + const UIEditorAssetFieldSpec& spec, + const UIEditorAssetFieldState&, + const UIEditorAssetFieldPalette& palette, + const UIEditorAssetFieldMetrics& metrics) { + drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, metrics.labelFontSize)); + drawList.AddText( + UIPoint( + layout.labelRect.x, + ResolveUIEditorTextTop(layout.labelRect, metrics.labelFontSize, metrics.labelTextInsetY)), + spec.label, + palette.labelColor, + metrics.labelFontSize); + drawList.PopClipRect(); + + drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.previewRect, metrics.previewGlyphFontSize)); + drawList.AddText( + UIPoint( + layout.previewRect.x + + ClampNonNegative((layout.previewRect.width - metrics.previewGlyphFontSize) * 0.5f), + ResolveUIEditorTextTop(layout.previewRect, metrics.previewGlyphFontSize, -1.0f)), + ResolveUIEditorAssetFieldPreviewGlyph(spec), + palette.previewGlyphColor, + metrics.previewGlyphFontSize); + drawList.PopClipRect(); + + drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.textRect, metrics.valueFontSize)); + drawList.AddText( + UIPoint( + layout.textRect.x, + ResolveUIEditorTextTop(layout.textRect, metrics.valueFontSize, metrics.valueTextInsetY)), + ResolveUIEditorAssetFieldValueText(spec), + HasUIEditorAssetFieldValue(spec) ? palette.valueColor : palette.emptyValueColor, + metrics.valueFontSize); + drawList.PopClipRect(); + + if (layout.statusBadgeRect.width > 0.0f && layout.statusBadgeRect.height > 0.0f) { + const UIRect badgeTextRect( + layout.statusBadgeRect.x + metrics.statusBadgePaddingX, + layout.statusBadgeRect.y, + ClampNonNegative(layout.statusBadgeRect.width - metrics.statusBadgePaddingX * 2.0f), + layout.statusBadgeRect.height); + drawList.PushClipRect(ResolveUIEditorTextClipRect(badgeTextRect, metrics.statusBadgeFontSize)); + drawList.AddText( + UIPoint( + badgeTextRect.x, + ResolveUIEditorTextTop(badgeTextRect, metrics.statusBadgeFontSize, -1.0f)), + spec.statusText, + palette.statusBadgeTextColor, + metrics.statusBadgeFontSize); + drawList.PopClipRect(); + } + + if (layout.pickerRect.width > 0.0f) { + drawList.AddText( + UIPoint( + layout.pickerRect.x + + ClampNonNegative((layout.pickerRect.width - metrics.actionGlyphFontSize) * 0.5f), + ResolveUIEditorTextTop( + layout.pickerRect, + metrics.actionGlyphFontSize, + metrics.actionGlyphInsetY)), + "o", + palette.pickerGlyphColor, + metrics.actionGlyphFontSize); + } + + if (layout.clearRect.width > 0.0f) { + drawList.AddText( + UIPoint( + layout.clearRect.x + + ClampNonNegative((layout.clearRect.width - metrics.actionGlyphFontSize) * 0.5f), + ResolveUIEditorTextTop( + layout.clearRect, + metrics.actionGlyphFontSize, + metrics.actionGlyphInsetY)), + "X", + palette.clearGlyphColor, + metrics.actionGlyphFontSize); + } +} + +void AppendUIEditorAssetField( + UIDrawList& drawList, + const UIRect& bounds, + const UIEditorAssetFieldSpec& spec, + const UIEditorAssetFieldState& state, + const UIEditorAssetFieldPalette& palette, + const UIEditorAssetFieldMetrics& metrics) { + const UIEditorAssetFieldLayout layout = BuildUIEditorAssetFieldLayout(bounds, spec, metrics); + AppendUIEditorAssetFieldBackground(drawList, layout, spec, state, palette, metrics); + AppendUIEditorAssetFieldForeground(drawList, layout, spec, state, palette, metrics); +} + +} // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/src/Fields/UIEditorAssetFieldInteraction.cpp b/new_editor/src/Fields/UIEditorAssetFieldInteraction.cpp new file mode 100644 index 00000000..98a02704 --- /dev/null +++ b/new_editor/src/Fields/UIEditorAssetFieldInteraction.cpp @@ -0,0 +1,260 @@ +#include + +#include + +#include + +namespace XCEngine::UI::Editor { + +namespace { + +using ::XCEngine::Input::KeyCode; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPointerButton; +using Widgets::BuildUIEditorAssetFieldLayout; +using Widgets::HasUIEditorAssetFieldValue; +using Widgets::HitTestUIEditorAssetField; +using Widgets::IsUIEditorAssetFieldPointInside; +using Widgets::UIEditorAssetFieldHitTarget; +using Widgets::UIEditorAssetFieldHitTargetKind; +using Widgets::UIEditorAssetFieldLayout; +using Widgets::UIEditorAssetFieldMetrics; +using Widgets::UIEditorAssetFieldSpec; + +bool ShouldUsePointerPosition(const UIInputEvent& event) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + case UIInputEventType::PointerWheel: + return true; + default: + return false; + } +} + +void SyncHoverTarget( + UIEditorAssetFieldInteractionState& state, + const UIEditorAssetFieldLayout& layout) { + if (!state.hasPointerPosition) { + state.fieldState.hoveredTarget = UIEditorAssetFieldHitTargetKind::None; + return; + } + + state.fieldState.hoveredTarget = HitTestUIEditorAssetField(layout, state.pointerPosition).kind; +} + +bool CanRequestPicker(const UIEditorAssetFieldSpec& spec) { + return !spec.readOnly && spec.showPickerButton; +} + +bool CanClearValue(const UIEditorAssetFieldSpec& spec) { + return !spec.readOnly && spec.allowClear && HasUIEditorAssetFieldValue(spec); +} + +void ClearValue( + UIEditorAssetFieldSpec& spec, + UIEditorAssetFieldInteractionResult& result) { + result.assetIdBefore = spec.assetId; + result.displayNameBefore = spec.displayName; + spec.assetId.clear(); + spec.displayName.clear(); + spec.statusText.clear(); + result.assetIdAfter = spec.assetId; + result.displayNameAfter = spec.displayName; + result.clearRequested = true; + result.valueChanged = true; + result.consumed = true; +} + +void RequestPicker(UIEditorAssetFieldInteractionResult& result) { + result.pickerRequested = true; + result.consumed = true; +} + +void RequestActivate(UIEditorAssetFieldInteractionResult& result) { + result.activateRequested = true; + result.consumed = true; +} + +} // namespace + +UIEditorAssetFieldInteractionFrame UpdateUIEditorAssetFieldInteraction( + UIEditorAssetFieldInteractionState& state, + UIEditorAssetFieldSpec& spec, + const ::XCEngine::UI::UIRect& bounds, + const std::vector& inputEvents, + const UIEditorAssetFieldMetrics& metrics) { + UIEditorAssetFieldLayout layout = BuildUIEditorAssetFieldLayout(bounds, spec, metrics); + SyncHoverTarget(state, layout); + + UIEditorAssetFieldInteractionResult interactionResult = {}; + for (const UIInputEvent& event : inputEvents) { + if (ShouldUsePointerPosition(event)) { + state.pointerPosition = event.position; + state.hasPointerPosition = true; + } else if (event.type == UIInputEventType::PointerLeave) { + state.hasPointerPosition = false; + } + + UIEditorAssetFieldInteractionResult eventResult = {}; + switch (event.type) { + case UIInputEventType::FocusGained: + eventResult.focusChanged = !state.fieldState.focused; + state.fieldState.focused = true; + break; + + case UIInputEventType::FocusLost: + eventResult.focusChanged = state.fieldState.focused; + state.fieldState.focused = false; + state.fieldState.activeTarget = UIEditorAssetFieldHitTargetKind::None; + state.hasPointerPosition = false; + break; + + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerLeave: + break; + + case UIInputEventType::PointerButtonDown: { + const UIEditorAssetFieldHitTarget hitTarget = + state.hasPointerPosition + ? HitTestUIEditorAssetField(layout, state.pointerPosition) + : UIEditorAssetFieldHitTarget {}; + eventResult.hitTarget = hitTarget; + + if (event.pointerButton != UIPointerButton::Left) { + break; + } + + const bool insideField = + state.hasPointerPosition && + IsUIEditorAssetFieldPointInside(layout.bounds, state.pointerPosition); + if (insideField) { + eventResult.focusChanged = !state.fieldState.focused; + state.fieldState.focused = true; + state.fieldState.activeTarget = + hitTarget.kind == UIEditorAssetFieldHitTargetKind::None + ? UIEditorAssetFieldHitTargetKind::Row + : hitTarget.kind; + eventResult.consumed = true; + } else { + if (state.fieldState.focused) { + eventResult.focusChanged = true; + state.fieldState.focused = false; + } + state.fieldState.activeTarget = UIEditorAssetFieldHitTargetKind::None; + } + break; + } + + case UIInputEventType::PointerButtonUp: { + const UIEditorAssetFieldHitTarget hitTarget = + state.hasPointerPosition + ? HitTestUIEditorAssetField(layout, state.pointerPosition) + : UIEditorAssetFieldHitTarget {}; + eventResult.hitTarget = hitTarget; + + if (event.pointerButton == UIPointerButton::Left) { + const UIEditorAssetFieldHitTargetKind activeTarget = state.fieldState.activeTarget; + state.fieldState.activeTarget = UIEditorAssetFieldHitTargetKind::None; + + if (activeTarget == hitTarget.kind) { + switch (activeTarget) { + case UIEditorAssetFieldHitTargetKind::PickerButton: + if (CanRequestPicker(spec)) { + RequestPicker(eventResult); + } + break; + + case UIEditorAssetFieldHitTargetKind::ClearButton: + if (CanClearValue(spec)) { + ClearValue(spec, eventResult); + } + break; + + case UIEditorAssetFieldHitTargetKind::ValueBox: + RequestActivate(eventResult); + break; + + case UIEditorAssetFieldHitTargetKind::Row: + eventResult.consumed = true; + break; + + case UIEditorAssetFieldHitTargetKind::None: + default: + break; + } + } + } + break; + } + + case UIInputEventType::KeyDown: + if (!state.fieldState.focused) { + break; + } + + switch (static_cast(event.keyCode)) { + case KeyCode::Enter: + case KeyCode::Space: + if (CanRequestPicker(spec)) { + RequestPicker(eventResult); + eventResult.hitTarget.kind = UIEditorAssetFieldHitTargetKind::PickerButton; + } else { + RequestActivate(eventResult); + eventResult.hitTarget.kind = UIEditorAssetFieldHitTargetKind::ValueBox; + } + break; + + case KeyCode::Delete: + case KeyCode::Backspace: + if (CanClearValue(spec)) { + ClearValue(spec, eventResult); + eventResult.hitTarget.kind = UIEditorAssetFieldHitTargetKind::ClearButton; + } + break; + + default: + break; + } + break; + + default: + break; + } + + layout = BuildUIEditorAssetFieldLayout(bounds, spec, metrics); + SyncHoverTarget(state, layout); + if (eventResult.hitTarget.kind == UIEditorAssetFieldHitTargetKind::None && + state.hasPointerPosition) { + eventResult.hitTarget = HitTestUIEditorAssetField(layout, state.pointerPosition); + } + + if (eventResult.consumed || + eventResult.focusChanged || + eventResult.valueChanged || + eventResult.activateRequested || + eventResult.pickerRequested || + eventResult.clearRequested || + eventResult.hitTarget.kind != UIEditorAssetFieldHitTargetKind::None) { + interactionResult = std::move(eventResult); + } + } + + layout = BuildUIEditorAssetFieldLayout(bounds, spec, metrics); + SyncHoverTarget(state, layout); + if (interactionResult.hitTarget.kind == UIEditorAssetFieldHitTargetKind::None && + state.hasPointerPosition) { + interactionResult.hitTarget = HitTestUIEditorAssetField(layout, state.pointerPosition); + } + + return { + std::move(layout), + std::move(interactionResult) + }; +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Widgets/UIEditorBoolField.cpp b/new_editor/src/Fields/UIEditorBoolField.cpp similarity index 98% rename from new_editor/src/Widgets/UIEditorBoolField.cpp rename to new_editor/src/Fields/UIEditorBoolField.cpp index 3b97849c..2d97fec4 100644 --- a/new_editor/src/Widgets/UIEditorBoolField.cpp +++ b/new_editor/src/Fields/UIEditorBoolField.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Core/UIEditorBoolFieldInteraction.cpp b/new_editor/src/Fields/UIEditorBoolFieldInteraction.cpp similarity index 99% rename from new_editor/src/Core/UIEditorBoolFieldInteraction.cpp rename to new_editor/src/Fields/UIEditorBoolFieldInteraction.cpp index 2f288704..4ed1b3be 100644 --- a/new_editor/src/Core/UIEditorBoolFieldInteraction.cpp +++ b/new_editor/src/Fields/UIEditorBoolFieldInteraction.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/new_editor/src/Fields/UIEditorColorField.cpp b/new_editor/src/Fields/UIEditorColorField.cpp new file mode 100644 index 00000000..7ad3ffcf --- /dev/null +++ b/new_editor/src/Fields/UIEditorColorField.cpp @@ -0,0 +1,873 @@ +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::Widgets { + +namespace { + +using ::XCEngine::UI::UIColor; +using ::XCEngine::UI::UIDrawList; +using ::XCEngine::UI::UILinearGradientDirection; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; +using ::XCEngine::UI::UISize; +using ::XCEngine::UI::Widgets::ResolvePopupPlacementRect; +using ::XCEngine::UI::Widgets::UIPopupPlacement; + +constexpr float kPi = 3.14159265358979323846f; + +float ClampNonNegative(float value) { + return (std::max)(0.0f, value); +} + +UIEditorColorFieldMetrics ResolveMetrics(const UIEditorColorFieldMetrics& metrics) { + const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); + + UIEditorColorFieldMetrics resolved = metrics; + if (AreUIEditorFieldMetricsEqual(metrics.controlTrailingInset, 8.0f)) { + resolved.controlTrailingInset = tokens.controlTrailingInset; + } + + return resolved; +} + +UIEditorColorFieldPalette ResolvePalette(const UIEditorColorFieldPalette& palette) { + const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); + + UIEditorColorFieldPalette resolved = palette; + if (AreUIEditorFieldColorsEqual(palette.rowHoverColor, UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.rowHoverColor = tokens.rowHoverColor; + } + if (AreUIEditorFieldColorsEqual(palette.rowActiveColor, UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.rowActiveColor = tokens.rowActiveColor; + } + if (AreUIEditorFieldColorsEqual(palette.swatchBorderColor, UIColor(0.14f, 0.14f, 0.14f, 1.0f))) { + resolved.swatchBorderColor = tokens.swatchBorderColor; + } + if (AreUIEditorFieldColorsEqual(palette.swatchHoverBorderColor, UIColor(0.34f, 0.34f, 0.34f, 1.0f))) { + resolved.swatchHoverBorderColor = tokens.swatchHoverBorderColor; + } + if (AreUIEditorFieldColorsEqual( + palette.swatchReadOnlyOverlayColor, + UIColor(0.08f, 0.08f, 0.08f, 0.18f))) { + resolved.swatchReadOnlyOverlayColor = tokens.swatchReadOnlyOverlayColor; + } + if (AreUIEditorFieldColorsEqual(palette.popupColor, UIColor(0.24f, 0.24f, 0.24f, 1.0f))) { + resolved.popupColor = tokens.popupColor; + } + if (AreUIEditorFieldColorsEqual(palette.popupBorderColor, UIColor(0.15f, 0.15f, 0.15f, 1.0f))) { + resolved.popupBorderColor = tokens.popupBorderColor; + } + if (AreUIEditorFieldColorsEqual(palette.popupHeaderColor, UIColor(0.43f, 0.24f, 0.05f, 1.0f))) { + resolved.popupHeaderColor = tokens.popupHeaderColor; + } + if (AreUIEditorFieldColorsEqual(palette.popupTitleColor, UIColor(0.95f, 0.95f, 0.95f, 1.0f))) { + resolved.popupTitleColor = tokens.popupTitleColor; + } + if (AreUIEditorFieldColorsEqual(palette.closeButtonColor, UIColor(0.76f, 0.35f, 0.34f, 1.0f))) { + resolved.closeButtonColor = tokens.closeButtonColor; + } + if (AreUIEditorFieldColorsEqual(palette.closeButtonHoverColor, UIColor(0.82f, 0.40f, 0.39f, 1.0f))) { + resolved.closeButtonHoverColor = tokens.closeButtonHoverColor; + } + if (AreUIEditorFieldColorsEqual(palette.closeGlyphColor, UIColor(0.95f, 0.95f, 0.95f, 1.0f))) { + resolved.closeGlyphColor = tokens.closeGlyphColor; + } + if (AreUIEditorFieldColorsEqual(palette.labelColor, UIColor(0.88f, 0.88f, 0.88f, 1.0f))) { + resolved.labelColor = tokens.labelColor; + } + if (AreUIEditorFieldColorsEqual(palette.popupTextColor, UIColor(0.86f, 0.86f, 0.86f, 1.0f))) { + resolved.popupTextColor = tokens.popupTextColor; + } + if (AreUIEditorFieldColorsEqual(palette.popupTextMutedColor, UIColor(0.72f, 0.72f, 0.72f, 1.0f))) { + resolved.popupTextMutedColor = tokens.popupTextMutedColor; + } + if (AreUIEditorFieldColorsEqual(palette.previewBorderColor, UIColor(0.12f, 0.12f, 0.12f, 1.0f))) { + resolved.previewBorderColor = tokens.previewBorderColor; + } + if (AreUIEditorFieldColorsEqual(palette.previewBaseColor, UIColor(0.19f, 0.19f, 0.19f, 1.0f))) { + resolved.previewBaseColor = tokens.previewBaseColor; + } + if (AreUIEditorFieldColorsEqual(palette.checkerLightColor, UIColor(0.84f, 0.84f, 0.84f, 1.0f))) { + resolved.checkerLightColor = tokens.checkerLightColor; + } + if (AreUIEditorFieldColorsEqual(palette.checkerDarkColor, UIColor(0.55f, 0.55f, 0.55f, 1.0f))) { + resolved.checkerDarkColor = tokens.checkerDarkColor; + } + if (AreUIEditorFieldColorsEqual(palette.sliderBorderColor, UIColor(0.12f, 0.12f, 0.12f, 1.0f))) { + resolved.sliderBorderColor = tokens.sliderBorderColor; + } + if (AreUIEditorFieldColorsEqual(palette.numericBoxColor, UIColor(0.19f, 0.19f, 0.19f, 1.0f))) { + resolved.numericBoxColor = tokens.numericBoxColor; + } + if (AreUIEditorFieldColorsEqual(palette.numericBoxBorderColor, UIColor(0.12f, 0.12f, 0.12f, 1.0f))) { + resolved.numericBoxBorderColor = tokens.numericBoxBorderColor; + } + if (AreUIEditorFieldColorsEqual(palette.numericBoxTextColor, UIColor(0.90f, 0.90f, 0.90f, 1.0f))) { + resolved.numericBoxTextColor = tokens.numericBoxTextColor; + } + if (AreUIEditorFieldColorsEqual(palette.handleFillColor, UIColor(1.0f, 1.0f, 1.0f, 1.0f))) { + resolved.handleFillColor = tokens.handleFillColor; + } + if (AreUIEditorFieldColorsEqual(palette.handleStrokeColor, UIColor(0.0f, 0.0f, 0.0f, 0.4f))) { + resolved.handleStrokeColor = tokens.handleStrokeColor; + } + + return resolved; +} + +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; +} + +UIColor ClampColor(const UIColor& color) { + return UIColor( + ClampUIEditorColorUnit(color.r), + ClampUIEditorColorUnit(color.g), + ClampUIEditorColorUnit(color.b), + ClampUIEditorColorUnit(color.a)); +} + +UIRect ResolveViewportRect( + const UIRect& anchorRect, + const UIRect& viewportRect) { + if (viewportRect.width > 0.0f && viewportRect.height > 0.0f) { + return viewportRect; + } + + return UIRect(anchorRect.x - 4096.0f, anchorRect.y - 4096.0f, 8192.0f, 8192.0f); +} + +UIEditorHsvColor ResolveDisplayHsv( + const UIEditorColorFieldSpec& spec, + const UIEditorColorFieldState& state) { + return ResolveUIEditorDisplayHsv(spec.value, state.hue, state.hueValid); +} + +float ResolvePopupHeight( + const UIEditorColorFieldMetrics& metrics, + bool showAlpha) { + const float channelCount = showAlpha ? 4.0f : 3.0f; + const float channelsHeight = + channelCount * metrics.channelRowHeight + + (channelCount - 1.0f) * metrics.controlRowSpacing; + return metrics.popupHeaderHeight + + metrics.popupPadding * 2.0f + + metrics.popupTopRowHeight + + metrics.popupGapY + + metrics.wheelRegionHeight + + metrics.popupGapY + + channelsHeight + + metrics.controlRowSpacing + + metrics.channelRowHeight; +} + +void AppendCheckerboard( + UIDrawList& drawList, + const UIRect& rect, + float checkerSize, + const UIColor& darkColor, + const UIColor& lightColor) { + const float tileSize = (std::max)(1.0f, checkerSize); + int row = 0; + for (float y = rect.y; y < rect.y + rect.height; y += tileSize, ++row) { + int column = 0; + for (float x = rect.x; x < rect.x + rect.width; x += tileSize, ++column) { + drawList.AddFilledRect( + UIRect( + x, + y, + (std::min)(tileSize, rect.x + rect.width - x), + (std::min)(tileSize, rect.y + rect.height - y)), + ((row + column) & 1) == 0 ? lightColor : darkColor); + } + } +} + +void AppendColorSample( + UIDrawList& drawList, + const UIRect& rect, + const UIColor& color, + bool showAlpha, + const UIEditorColorFieldPalette& palette, + const UIEditorColorFieldMetrics& metrics, + const UIColor& borderColor, + float borderThickness, + float rounding) { + const UIColor clamped = ClampColor(color); + drawList.AddFilledRect(rect, palette.previewBaseColor, rounding); + if (showAlpha) { + AppendCheckerboard( + drawList, + rect, + metrics.checkerSize, + palette.checkerDarkColor, + palette.checkerLightColor); + } + drawList.AddFilledRect( + rect, + UIColor(clamped.r, clamped.g, clamped.b, showAlpha ? clamped.a : 1.0f), + rounding); + drawList.AddRectOutline(rect, borderColor, borderThickness, rounding); +} + +void AppendPopupText( + UIDrawList& drawList, + const UIRect& rect, + const std::string& text, + const UIColor& color, + float fontSize, + float insetX, + float insetY) { + drawList.PushClipRect(ResolveUIEditorTextClipRect(rect, fontSize)); + drawList.AddText( + UIPoint(rect.x + insetX, ResolveUIEditorTextTop(rect, fontSize, insetY)), + text, + color, + fontSize); + drawList.PopClipRect(); +} + +void AppendCloseGlyph( + UIDrawList& drawList, + const UIRect& rect, + const UIColor& color) { + constexpr float kGlyphPad = 5.0f; + drawList.AddLine( + UIPoint(rect.x + kGlyphPad, rect.y + kGlyphPad), + UIPoint(rect.x + rect.width - kGlyphPad, rect.y + rect.height - kGlyphPad), + color, + 1.2f); + drawList.AddLine( + UIPoint(rect.x + rect.width - kGlyphPad, rect.y + kGlyphPad), + UIPoint(rect.x + kGlyphPad, rect.y + rect.height - kGlyphPad), + color, + 1.2f); +} + +std::string FormatChannelValue(float value) { + char buffer[32] = {}; + std::snprintf(buffer, sizeof(buffer), "%.4f", ClampUIEditorColorUnit(value)); + return std::string(buffer); +} + +std::string FormatPopupHexText(const UIEditorColorFieldSpec& spec) { + std::string hex = FormatUIEditorColorFieldHexText(spec); + if (!hex.empty() && hex.front() == '#') { + hex.erase(hex.begin()); + } + return hex; +} + +UIColor ResolvePopupSliderBorderColor(const UIEditorColorFieldPalette& palette) { + return palette.sliderBorderColor.a > 0.0f ? palette.sliderBorderColor : palette.popupBorderColor; +} + +void AppendNumericValueBox( + UIDrawList& drawList, + const UIRect& rect, + const std::string& text, + const UIEditorColorFieldPalette& palette, + const UIEditorColorFieldMetrics& metrics) { + drawList.AddFilledRect(rect, palette.numericBoxColor, metrics.swatchRounding); + drawList.AddRectOutline(rect, palette.numericBoxBorderColor, metrics.borderThickness, metrics.swatchRounding); + AppendPopupText( + drawList, + rect, + text, + palette.numericBoxTextColor, + metrics.valueFontSize, + metrics.valueTextInsetX, + metrics.valueTextInsetY); +} + +void AppendSliderHandle( + UIDrawList& drawList, + const UIRect& rect, + float unit, + const UIEditorColorFieldPalette& palette) { + const float x = rect.x + ClampUIEditorColorUnit(unit) * rect.width; + drawList.AddLine( + UIPoint(x, rect.y), + UIPoint(x, rect.y + rect.height), + palette.handleFillColor, + 2.0f); + drawList.AddLine( + UIPoint(x + 1.5f, rect.y), + UIPoint(x + 1.5f, rect.y + rect.height), + palette.handleStrokeColor, + 1.0f); +} + +void AppendChannelSlider( + UIDrawList& drawList, + const UIRect& rect, + const UIColor& startColor, + const UIColor& endColor, + float unit, + const UIEditorColorFieldPalette& palette, + const UIEditorColorFieldMetrics& metrics, + bool alphaBackground) { + if (alphaBackground) { + AppendCheckerboard( + drawList, + rect, + metrics.checkerSize - 1.0f, + palette.checkerDarkColor, + palette.checkerLightColor); + } + + drawList.AddFilledRectLinearGradient( + rect, + startColor, + endColor, + UILinearGradientDirection::Horizontal); + drawList.AddRectOutline( + rect, + ResolvePopupSliderBorderColor(palette), + metrics.borderThickness); + AppendSliderHandle(drawList, rect, unit, palette); +} + +void AppendPopupChannelRow( + UIDrawList& drawList, + const UIRect& labelRect, + const UIRect& sliderRect, + const UIRect& valueRect, + const char* label, + const std::string& valueText, + const UIColor& startColor, + const UIColor& endColor, + float unit, + bool alphaBackground, + const UIEditorColorFieldPalette& palette, + const UIEditorColorFieldMetrics& metrics) { + AppendPopupText( + drawList, + labelRect, + label, + palette.popupTextColor, + metrics.valueFontSize, + 0.0f, + metrics.valueTextInsetY); + AppendChannelSlider( + drawList, + sliderRect, + startColor, + endColor, + unit, + palette, + metrics, + alphaBackground); + AppendNumericValueBox(drawList, valueRect, valueText, palette, metrics); +} + +void AppendSaturationValueSquare( + UIDrawList& drawList, + const UIRect& rect, + const UIEditorHsvColor& hsv, + const UIEditorColorFieldPalette& palette, + const UIEditorColorFieldMetrics& metrics) { + const UIColor hueColor = ConvertUIEditorHsvToColor(UIEditorHsvColor { hsv.hue, 1.0f, 1.0f, 1.0f }); + drawList.AddFilledRectLinearGradient( + rect, + UIColor(1.0f, 1.0f, 1.0f, 1.0f), + hueColor, + UILinearGradientDirection::Horizontal); + drawList.AddFilledRectLinearGradient( + rect, + UIColor(0.0f, 0.0f, 0.0f, 0.0f), + UIColor(0.0f, 0.0f, 0.0f, 1.0f), + UILinearGradientDirection::Vertical); + drawList.AddRectOutline( + rect, + ResolvePopupSliderBorderColor(palette), + metrics.borderThickness); +} + +void AppendWheelHandle( + UIDrawList& drawList, + const UIPoint& center, + float radius, + const UIEditorColorFieldPalette& palette) { + drawList.AddFilledCircle(center, (std::max)(1.0f, radius - 1.0f), palette.handleFillColor); + drawList.AddCircleOutline(center, radius, palette.handleStrokeColor, 1.4f); +} + +void AppendHueWheel( + UIDrawList& drawList, + const UIEditorColorFieldLayout& layout, + const UIEditorColorFieldPalette& palette, + const UIEditorColorFieldMetrics& metrics) { + const float outerRadius = layout.hueWheelOuterRadius; + const float innerRadius = layout.hueWheelInnerRadius; + const float ringRadius = (outerRadius + innerRadius) * 0.5f; + const float ringThickness = ClampNonNegative(outerRadius - innerRadius); + const int segmentCount = (std::max)(192, static_cast(outerRadius * 2.4f)); + for (int segment = 0; segment < segmentCount; ++segment) { + const float t0 = static_cast(segment) / static_cast(segmentCount); + const float t1 = static_cast(segment + 1) / static_cast(segmentCount); + const float angle0 = t0 * 2.0f * kPi; + const float angle1 = t1 * 2.0f * kPi; + drawList.AddLine( + UIPoint( + layout.hueWheelCenter.x + std::cos(angle0) * ringRadius, + layout.hueWheelCenter.y + std::sin(angle0) * ringRadius), + UIPoint( + layout.hueWheelCenter.x + std::cos(angle1) * ringRadius, + layout.hueWheelCenter.y + std::sin(angle1) * ringRadius), + ConvertUIEditorHsvToColor(UIEditorHsvColor { t0, 1.0f, 1.0f, 1.0f }), + ringThickness + 1.0f); + } + + drawList.AddCircleOutline( + layout.hueWheelCenter, + outerRadius, + ResolvePopupSliderBorderColor(palette), + metrics.borderThickness); + drawList.AddCircleOutline( + layout.hueWheelCenter, + innerRadius, + ResolvePopupSliderBorderColor(palette), + metrics.borderThickness); +} + +UIPoint ResolveHueWheelHandleCenter( + const UIEditorColorFieldLayout& layout, + float hue) { + const float angle = ClampUIEditorColorUnit(hue) * 2.0f * kPi; + const float ringRadius = (layout.hueWheelOuterRadius + layout.hueWheelInnerRadius) * 0.5f; + return UIPoint( + layout.hueWheelCenter.x + std::cos(angle) * ringRadius, + layout.hueWheelCenter.y + std::sin(angle) * ringRadius); +} + +UIPoint ResolveSaturationValueHandleCenter( + const UIEditorColorFieldLayout& layout, + const UIEditorHsvColor& hsv) { + return UIPoint( + layout.saturationValueRect.x + ClampUIEditorColorUnit(hsv.saturation) * layout.saturationValueRect.width, + layout.saturationValueRect.y + (1.0f - ClampUIEditorColorUnit(hsv.value)) * layout.saturationValueRect.height); +} + +} // namespace + +std::string FormatUIEditorColorFieldRgbaText(const UIEditorColorFieldSpec& spec) { + char buffer[48] = {}; + if (spec.showAlpha) { + std::snprintf( + buffer, + sizeof(buffer), + "RGBA %d, %d, %d, %d", + ToUIEditorColorByte(spec.value.r), + ToUIEditorColorByte(spec.value.g), + ToUIEditorColorByte(spec.value.b), + ToUIEditorColorByte(spec.value.a)); + } else { + std::snprintf( + buffer, + sizeof(buffer), + "RGB %d, %d, %d", + ToUIEditorColorByte(spec.value.r), + ToUIEditorColorByte(spec.value.g), + ToUIEditorColorByte(spec.value.b)); + } + return std::string(buffer); +} + +std::string FormatUIEditorColorFieldHexText(const UIEditorColorFieldSpec& spec) { + return FormatUIEditorColorHex(spec.value, spec.showAlpha); +} + +UIEditorColorFieldLayout BuildUIEditorColorFieldLayout( + const UIRect& bounds, + const UIEditorColorFieldSpec& spec, + const UIEditorColorFieldMetrics& metrics, + const UIRect& viewportRect) { + const UIEditorColorFieldMetrics resolvedMetrics = ResolveMetrics(metrics); + const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout( + bounds, + resolvedMetrics.swatchWidth, + UIEditorFieldRowLayoutMetrics { + resolvedMetrics.rowHeight, + resolvedMetrics.horizontalPadding, + resolvedMetrics.labelControlGap, + resolvedMetrics.controlColumnStart, + resolvedMetrics.controlTrailingInset, + resolvedMetrics.swatchInsetY, + }); + + UIEditorColorFieldLayout layout = {}; + layout.bounds = hostLayout.bounds; + layout.labelRect = hostLayout.labelRect; + layout.controlRect = hostLayout.controlRect; + layout.swatchRect = UIRect( + hostLayout.controlRect.x, + hostLayout.controlRect.y + resolvedMetrics.swatchInsetY, + (std::min)(resolvedMetrics.swatchWidth, hostLayout.controlRect.width), + ClampNonNegative(hostLayout.controlRect.height - resolvedMetrics.swatchInsetY * 2.0f)); + + const auto placement = ResolvePopupPlacementRect( + layout.swatchRect, + UISize(resolvedMetrics.popupWidth, ResolvePopupHeight(resolvedMetrics, spec.showAlpha)), + ResolveViewportRect(layout.swatchRect, viewportRect), + UIPopupPlacement::BottomStart); + layout.popupRect = placement.rect; + layout.popupHeaderRect = + UIRect(layout.popupRect.x, layout.popupRect.y, layout.popupRect.width, resolvedMetrics.popupHeaderHeight); + layout.popupTitleRect = UIRect( + layout.popupRect.x + resolvedMetrics.popupPadding, + layout.popupRect.y, + ClampNonNegative( + layout.popupRect.width - resolvedMetrics.popupPadding * 3.0f - resolvedMetrics.popupCloseButtonSize), + resolvedMetrics.popupHeaderHeight); + layout.popupCloseButtonRect = UIRect( + layout.popupRect.x + layout.popupRect.width - resolvedMetrics.popupPadding - resolvedMetrics.popupCloseButtonSize, + layout.popupRect.y + (resolvedMetrics.popupHeaderHeight - resolvedMetrics.popupCloseButtonSize) * 0.5f, + resolvedMetrics.popupCloseButtonSize, + resolvedMetrics.popupCloseButtonSize); + layout.popupBodyRect = UIRect( + layout.popupRect.x + resolvedMetrics.popupPadding, + layout.popupRect.y + resolvedMetrics.popupHeaderHeight + resolvedMetrics.popupPadding, + ClampNonNegative(layout.popupRect.width - resolvedMetrics.popupPadding * 2.0f), + ClampNonNegative( + layout.popupRect.height - resolvedMetrics.popupHeaderHeight - resolvedMetrics.popupPadding * 2.0f)); + layout.popupTopRowRect = UIRect( + layout.popupBodyRect.x, + layout.popupBodyRect.y, + layout.popupBodyRect.width, + resolvedMetrics.popupTopRowHeight); + layout.popupPreviewRect = UIRect( + layout.popupTopRowRect.x + layout.popupTopRowRect.width - resolvedMetrics.popupPreviewWidth, + layout.popupTopRowRect.y + (layout.popupTopRowRect.height - resolvedMetrics.popupPreviewHeight) * 0.5f, + resolvedMetrics.popupPreviewWidth, + resolvedMetrics.popupPreviewHeight); + layout.popupWheelRect = UIRect( + layout.popupBodyRect.x, + layout.popupTopRowRect.y + layout.popupTopRowRect.height + resolvedMetrics.popupGapY, + layout.popupBodyRect.width, + resolvedMetrics.wheelRegionHeight); + layout.hueWheelCenter = UIPoint( + layout.popupWheelRect.x + layout.popupWheelRect.width * 0.5f, + layout.popupWheelRect.y + resolvedMetrics.wheelOuterRadius); + layout.hueWheelOuterRadius = resolvedMetrics.wheelOuterRadius; + layout.hueWheelInnerRadius = + (std::max)(1.0f, resolvedMetrics.wheelOuterRadius - resolvedMetrics.wheelRingThickness); + layout.saturationValueRect = UIRect( + layout.hueWheelCenter.x - resolvedMetrics.saturationValueSize * 0.5f, + layout.hueWheelCenter.y - resolvedMetrics.saturationValueSize * 0.5f, + resolvedMetrics.saturationValueSize, + resolvedMetrics.saturationValueSize); + + const float controlsX = layout.popupBodyRect.x + resolvedMetrics.popupFieldInset; + const float controlsWidth = + ClampNonNegative(layout.popupBodyRect.width - resolvedMetrics.popupFieldInset * 2.0f); + const float sliderWidth = ClampNonNegative( + controlsWidth - + resolvedMetrics.channelLabelWidth - + resolvedMetrics.controlRowSpacing - + resolvedMetrics.numericBoxWidth - + resolvedMetrics.controlRowSpacing); + float rowY = layout.popupWheelRect.y + layout.popupWheelRect.height + resolvedMetrics.popupGapY; + auto assignChannelRow = [&](UIRect& labelRect, UIRect& sliderRect, UIRect& valueRect) { + labelRect = UIRect(controlsX, rowY, resolvedMetrics.channelLabelWidth, resolvedMetrics.channelRowHeight); + sliderRect = UIRect( + labelRect.x + labelRect.width + resolvedMetrics.controlRowSpacing, + rowY, + sliderWidth, + resolvedMetrics.channelRowHeight); + valueRect = UIRect( + sliderRect.x + sliderRect.width + resolvedMetrics.controlRowSpacing, + rowY, + resolvedMetrics.numericBoxWidth, + resolvedMetrics.channelRowHeight); + rowY += resolvedMetrics.channelRowHeight + resolvedMetrics.controlRowSpacing; + }; + + assignChannelRow(layout.redLabelRect, layout.redSliderRect, layout.redValueRect); + assignChannelRow(layout.greenLabelRect, layout.greenSliderRect, layout.greenValueRect); + assignChannelRow(layout.blueLabelRect, layout.blueSliderRect, layout.blueValueRect); + if (spec.showAlpha) { + assignChannelRow(layout.alphaLabelRect, layout.alphaSliderRect, layout.alphaValueRect); + } + + layout.hexLabelRect = UIRect(controlsX, rowY, resolvedMetrics.hexLabelWidth, resolvedMetrics.channelRowHeight); + layout.hexValueRect = UIRect( + layout.hexLabelRect.x + layout.hexLabelRect.width + resolvedMetrics.controlRowSpacing, + rowY, + ClampNonNegative(controlsWidth - resolvedMetrics.hexLabelWidth - resolvedMetrics.controlRowSpacing), + resolvedMetrics.channelRowHeight); + return layout; +} + +UIEditorColorFieldHitTarget HitTestUIEditorColorField( + const UIEditorColorFieldLayout& layout, + bool popupOpen, + const UIPoint& point) { + if (popupOpen && ContainsPoint(layout.popupRect, point)) { + if (ContainsPoint(layout.popupCloseButtonRect, point)) { + return { UIEditorColorFieldHitTargetKind::PopupCloseButton }; + } + + const float dx = point.x - layout.hueWheelCenter.x; + const float dy = point.y - layout.hueWheelCenter.y; + const float distance = std::sqrt(dx * dx + dy * dy); + if (distance >= layout.hueWheelInnerRadius && distance <= layout.hueWheelOuterRadius) { + return { UIEditorColorFieldHitTargetKind::HueWheel }; + } + + if (ContainsPoint(layout.saturationValueRect, point)) { + return { UIEditorColorFieldHitTargetKind::SaturationValue }; + } + if (ContainsPoint(layout.redSliderRect, point)) { + return { UIEditorColorFieldHitTargetKind::RedChannel }; + } + if (ContainsPoint(layout.greenSliderRect, point)) { + return { UIEditorColorFieldHitTargetKind::GreenChannel }; + } + if (ContainsPoint(layout.blueSliderRect, point)) { + return { UIEditorColorFieldHitTargetKind::BlueChannel }; + } + if (layout.alphaSliderRect.width > 0.0f && ContainsPoint(layout.alphaSliderRect, point)) { + return { UIEditorColorFieldHitTargetKind::AlphaChannel }; + } + return { UIEditorColorFieldHitTargetKind::PopupSurface }; + } + + if (ContainsPoint(layout.swatchRect, point)) { + return { UIEditorColorFieldHitTargetKind::Swatch }; + } + if (ContainsPoint(layout.bounds, point)) { + return { UIEditorColorFieldHitTargetKind::Row }; + } + return {}; +} + +void AppendUIEditorColorFieldBackground( + UIDrawList& drawList, + const UIEditorColorFieldLayout& layout, + const UIEditorColorFieldSpec& spec, + const UIEditorColorFieldState& state, + const UIEditorColorFieldPalette& palette, + const UIEditorColorFieldMetrics& metrics) { + const UIEditorColorFieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorColorFieldMetrics resolvedMetrics = ResolveMetrics(metrics); + if (state.activeTarget != UIEditorColorFieldHitTargetKind::None && resolvedPalette.rowActiveColor.a > 0.0f) { + drawList.AddFilledRect(layout.bounds, resolvedPalette.rowActiveColor); + } else if ( + state.hoveredTarget != UIEditorColorFieldHitTargetKind::None && + resolvedPalette.rowHoverColor.a > 0.0f) { + drawList.AddFilledRect(layout.bounds, resolvedPalette.rowHoverColor); + } else if (resolvedPalette.surfaceColor.a > 0.0f) { + drawList.AddFilledRect(layout.bounds, resolvedPalette.surfaceColor); + } + + const bool swatchHighlighted = + state.popupOpen || + state.focused || + state.hoveredTarget == UIEditorColorFieldHitTargetKind::Swatch; + AppendColorSample( + drawList, + layout.swatchRect, + spec.value, + spec.showAlpha, + resolvedPalette, + resolvedMetrics, + swatchHighlighted ? resolvedPalette.swatchHoverBorderColor : resolvedPalette.swatchBorderColor, + swatchHighlighted ? resolvedMetrics.focusedBorderThickness : resolvedMetrics.borderThickness, + resolvedMetrics.swatchRounding); + + if (spec.readOnly) { + drawList.AddFilledRect( + layout.swatchRect, + resolvedPalette.swatchReadOnlyOverlayColor, + resolvedMetrics.swatchRounding); + } + + if (!state.popupOpen) { + return; + } + + drawList.AddFilledRect(layout.popupRect, resolvedPalette.popupColor, resolvedMetrics.popupBorderRounding); + drawList.AddRectOutline( + layout.popupRect, + resolvedPalette.popupBorderColor, + resolvedMetrics.borderThickness, + resolvedMetrics.popupBorderRounding); + drawList.AddFilledRect( + layout.popupHeaderRect, + resolvedPalette.popupHeaderColor, + resolvedMetrics.popupBorderRounding); + drawList.AddFilledRect( + UIRect( + layout.popupHeaderRect.x, + layout.popupHeaderRect.y + layout.popupHeaderRect.height - resolvedMetrics.borderThickness, + layout.popupHeaderRect.width, + resolvedMetrics.borderThickness), + resolvedPalette.popupBorderColor); + + drawList.AddFilledRect( + layout.popupCloseButtonRect, + state.hoveredTarget == UIEditorColorFieldHitTargetKind::PopupCloseButton + ? resolvedPalette.closeButtonHoverColor + : resolvedPalette.closeButtonColor, + 0.0f); + + AppendColorSample( + drawList, + layout.popupPreviewRect, + spec.value, + spec.showAlpha, + resolvedPalette, + resolvedMetrics, + resolvedPalette.previewBorderColor, + resolvedMetrics.borderThickness, + resolvedMetrics.swatchRounding); + + const UIEditorHsvColor hsv = ResolveDisplayHsv(spec, state); + AppendHueWheel(drawList, layout, resolvedPalette, resolvedMetrics); + AppendSaturationValueSquare(drawList, layout.saturationValueRect, hsv, resolvedPalette, resolvedMetrics); + + const UIColor rgbColor = ClampColor(spec.value); + AppendPopupChannelRow( + drawList, + layout.redLabelRect, + layout.redSliderRect, + layout.redValueRect, + "R", + FormatChannelValue(spec.value.r), + UIColor(0.0f, rgbColor.g, rgbColor.b, 1.0f), + UIColor(1.0f, rgbColor.g, rgbColor.b, 1.0f), + spec.value.r, + false, + resolvedPalette, + resolvedMetrics); + AppendPopupChannelRow( + drawList, + layout.greenLabelRect, + layout.greenSliderRect, + layout.greenValueRect, + "G", + FormatChannelValue(spec.value.g), + UIColor(rgbColor.r, 0.0f, rgbColor.b, 1.0f), + UIColor(rgbColor.r, 1.0f, rgbColor.b, 1.0f), + spec.value.g, + false, + resolvedPalette, + resolvedMetrics); + AppendPopupChannelRow( + drawList, + layout.blueLabelRect, + layout.blueSliderRect, + layout.blueValueRect, + "B", + FormatChannelValue(spec.value.b), + UIColor(rgbColor.r, rgbColor.g, 0.0f, 1.0f), + UIColor(rgbColor.r, rgbColor.g, 1.0f, 1.0f), + spec.value.b, + false, + resolvedPalette, + resolvedMetrics); + if (spec.showAlpha && layout.alphaSliderRect.width > 0.0f) { + AppendPopupChannelRow( + drawList, + layout.alphaLabelRect, + layout.alphaSliderRect, + layout.alphaValueRect, + "A", + FormatChannelValue(spec.value.a), + UIColor(rgbColor.r, rgbColor.g, rgbColor.b, 0.0f), + UIColor(rgbColor.r, rgbColor.g, rgbColor.b, 1.0f), + spec.value.a, + true, + resolvedPalette, + resolvedMetrics); + } + + AppendNumericValueBox(drawList, layout.hexValueRect, FormatPopupHexText(spec), resolvedPalette, resolvedMetrics); +} + +void AppendUIEditorColorFieldForeground( + UIDrawList& drawList, + const UIEditorColorFieldLayout& layout, + const UIEditorColorFieldSpec& spec, + const UIEditorColorFieldState& state, + const UIEditorColorFieldPalette& palette, + const UIEditorColorFieldMetrics& metrics) { + const UIEditorColorFieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorColorFieldMetrics resolvedMetrics = ResolveMetrics(metrics); + AppendPopupText( + drawList, + layout.labelRect, + spec.label, + resolvedPalette.labelColor, + resolvedMetrics.labelFontSize, + 0.0f, + resolvedMetrics.labelTextInsetY); + + if (!state.popupOpen) { + return; + } + + AppendPopupText( + drawList, + layout.popupTitleRect, + "Color", + resolvedPalette.popupTitleColor, + resolvedMetrics.titleFontSize, + 0.0f, + 0.0f); + AppendCloseGlyph(drawList, layout.popupCloseButtonRect, resolvedPalette.closeGlyphColor); + + const UIEditorHsvColor hsv = ResolveDisplayHsv(spec, state); + AppendWheelHandle( + drawList, + ResolveHueWheelHandleCenter(layout, hsv.hue), + resolvedMetrics.ringHandleRadius, + resolvedPalette); + AppendWheelHandle( + drawList, + ResolveSaturationValueHandleCenter(layout, hsv), + resolvedMetrics.handleRadius, + resolvedPalette); + + AppendPopupText( + drawList, + layout.hexLabelRect, + "Hexadecimal", + resolvedPalette.popupTextMutedColor, + resolvedMetrics.valueFontSize, + 0.0f, + resolvedMetrics.valueTextInsetY); +} + +void AppendUIEditorColorField( + UIDrawList& drawList, + const UIRect& bounds, + const UIEditorColorFieldSpec& spec, + const UIEditorColorFieldState& state, + const UIEditorColorFieldPalette& palette, + const UIEditorColorFieldMetrics& metrics, + const UIRect& viewportRect) { + const UIEditorColorFieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorColorFieldMetrics resolvedMetrics = ResolveMetrics(metrics); + const UIEditorColorFieldLayout layout = + BuildUIEditorColorFieldLayout(bounds, spec, resolvedMetrics, viewportRect); + AppendUIEditorColorFieldBackground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); + AppendUIEditorColorFieldForeground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); +} + +} // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/src/Fields/UIEditorColorFieldInteraction.cpp b/new_editor/src/Fields/UIEditorColorFieldInteraction.cpp new file mode 100644 index 00000000..4090a016 --- /dev/null +++ b/new_editor/src/Fields/UIEditorColorFieldInteraction.cpp @@ -0,0 +1,453 @@ +#include + +#include + +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor { + +namespace { + +using ::XCEngine::Input::KeyCode; +using ::XCEngine::UI::UIColor; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPointerButton; +using Widgets::BuildUIEditorColorFieldLayout; +using Widgets::ConvertUIEditorHsvToColor; +using Widgets::HitTestUIEditorColorField; +using Widgets::ResolveUIEditorDisplayHsv; +using Widgets::UIEditorColorFieldHitTarget; +using Widgets::UIEditorColorFieldHitTargetKind; +using Widgets::UIEditorColorFieldLayout; +using Widgets::UIEditorColorFieldMetrics; +using Widgets::UIEditorColorFieldSpec; +using Widgets::UIEditorHsvColor; + +constexpr float kPi = 3.14159265358979323846f; + +float Clamp01(float value) { + return (std::clamp)(value, 0.0f, 1.0f); +} + +bool AreColorsEqual( + const UIColor& lhs, + const UIColor& rhs) { + return lhs.r == rhs.r && + lhs.g == rhs.g && + lhs.b == rhs.b && + lhs.a == rhs.a; +} + +bool ShouldUsePointerPosition(const UIInputEvent& event) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + return true; + default: + return false; + } +} + +void SyncRememberedHue( + UIEditorColorFieldInteractionState& state, + const UIEditorColorFieldSpec& spec) { + const UIEditorHsvColor hsv = ResolveUIEditorDisplayHsv( + spec.value, + state.colorFieldState.hue, + state.colorFieldState.hueValid); + state.colorFieldState.hue = hsv.hue; + state.colorFieldState.hueValid = true; +} + +void SyncHoverTarget( + UIEditorColorFieldInteractionState& state, + const UIEditorColorFieldLayout& layout) { + if (!state.hasPointerPosition) { + state.colorFieldState.hoveredTarget = UIEditorColorFieldHitTargetKind::None; + return; + } + + state.colorFieldState.hoveredTarget = + HitTestUIEditorColorField(layout, state.colorFieldState.popupOpen, state.pointerPosition).kind; +} + +void ClosePopup( + UIEditorColorFieldInteractionState& state, + UIEditorColorFieldInteractionResult& result) { + if (!state.colorFieldState.popupOpen) { + return; + } + + state.colorFieldState.popupOpen = false; + state.colorFieldState.activeTarget = UIEditorColorFieldHitTargetKind::None; + result.popupClosed = true; + result.consumed = true; +} + +void OpenPopup( + UIEditorColorFieldInteractionState& state, + const UIEditorColorFieldSpec& spec, + UIEditorColorFieldInteractionResult& result) { + if (spec.readOnly || state.colorFieldState.popupOpen) { + return; + } + + state.colorFieldState.popupOpen = true; + SyncRememberedHue(state, spec); + result.popupOpened = true; + result.consumed = true; +} + +void AssignRawColor( + UIEditorColorFieldInteractionState& state, + UIEditorColorFieldSpec& spec, + const UIColor& color, + UIEditorColorFieldInteractionResult& result) { + result.valueBefore = spec.value; + spec.value = UIColor( + Clamp01(color.r), + Clamp01(color.g), + Clamp01(color.b), + Clamp01(color.a)); + SyncRememberedHue(state, spec); + result.valueAfter = spec.value; + result.colorChanged = !AreColorsEqual(result.valueBefore, result.valueAfter); + result.consumed = true; +} + +void AssignHsvColor( + UIEditorColorFieldInteractionState& state, + UIEditorColorFieldSpec& spec, + const UIEditorHsvColor& hsv, + UIEditorColorFieldInteractionResult& result) { + AssignRawColor(state, spec, ConvertUIEditorHsvToColor(hsv), result); + state.colorFieldState.hue = Clamp01(hsv.hue); + state.colorFieldState.hueValid = true; +} + +bool ApplySaturationValueDrag( + UIEditorColorFieldInteractionState& state, + UIEditorColorFieldSpec& spec, + const UIEditorColorFieldLayout& layout, + UIEditorColorFieldInteractionResult& result, + const ::XCEngine::UI::UIPoint& point) { + if (layout.saturationValueRect.width <= 0.0f || layout.saturationValueRect.height <= 0.0f) { + return false; + } + + UIEditorHsvColor hsv = ResolveUIEditorDisplayHsv( + spec.value, + state.colorFieldState.hue, + state.colorFieldState.hueValid); + hsv.saturation = Clamp01((point.x - layout.saturationValueRect.x) / layout.saturationValueRect.width); + hsv.value = 1.0f - Clamp01((point.y - layout.saturationValueRect.y) / layout.saturationValueRect.height); + hsv.alpha = Clamp01(spec.value.a); + AssignHsvColor(state, spec, hsv, result); + return true; +} + +bool ApplyHueWheelDrag( + UIEditorColorFieldInteractionState& state, + UIEditorColorFieldSpec& spec, + const UIEditorColorFieldLayout& layout, + UIEditorColorFieldInteractionResult& result, + const ::XCEngine::UI::UIPoint& point) { + const float dx = point.x - layout.hueWheelCenter.x; + const float dy = point.y - layout.hueWheelCenter.y; + float angle = std::atan2(dy, dx); + if (angle < 0.0f) { + angle += 2.0f * kPi; + } + + UIEditorHsvColor hsv = ResolveUIEditorDisplayHsv( + spec.value, + state.colorFieldState.hue, + state.colorFieldState.hueValid); + hsv.hue = Clamp01(angle / (2.0f * kPi)); + hsv.alpha = Clamp01(spec.value.a); + AssignHsvColor(state, spec, hsv, result); + return true; +} + +bool ApplyRgbChannelDrag( + UIEditorColorFieldInteractionState& state, + UIEditorColorFieldSpec& spec, + const ::XCEngine::UI::UIRect& sliderRect, + int componentIndex, + UIEditorColorFieldInteractionResult& result, + const ::XCEngine::UI::UIPoint& point) { + if (sliderRect.width <= 0.0f) { + return false; + } + + UIColor color = spec.value; + const float unit = Clamp01((point.x - sliderRect.x) / sliderRect.width); + switch (componentIndex) { + case 0: + color.r = unit; + break; + case 1: + color.g = unit; + break; + case 2: + color.b = unit; + break; + default: + return false; + } + + AssignRawColor(state, spec, color, result); + return true; +} + +bool ApplyAlphaChannelDrag( + UIEditorColorFieldInteractionState& state, + UIEditorColorFieldSpec& spec, + const UIEditorColorFieldLayout& layout, + UIEditorColorFieldInteractionResult& result, + const ::XCEngine::UI::UIPoint& point) { + if (!spec.showAlpha || layout.alphaSliderRect.width <= 0.0f) { + return false; + } + + UIColor color = spec.value; + color.a = Clamp01((point.x - layout.alphaSliderRect.x) / layout.alphaSliderRect.width); + AssignRawColor(state, spec, color, result); + return true; +} + +void ApplyActiveDrag( + UIEditorColorFieldInteractionState& state, + UIEditorColorFieldSpec& spec, + const UIEditorColorFieldLayout& layout, + UIEditorColorFieldInteractionResult& result) { + switch (state.colorFieldState.activeTarget) { + case UIEditorColorFieldHitTargetKind::HueWheel: + ApplyHueWheelDrag(state, spec, layout, result, state.pointerPosition); + break; + case UIEditorColorFieldHitTargetKind::SaturationValue: + ApplySaturationValueDrag(state, spec, layout, result, state.pointerPosition); + break; + case UIEditorColorFieldHitTargetKind::RedChannel: + ApplyRgbChannelDrag(state, spec, layout.redSliderRect, 0, result, state.pointerPosition); + break; + case UIEditorColorFieldHitTargetKind::GreenChannel: + ApplyRgbChannelDrag(state, spec, layout.greenSliderRect, 1, result, state.pointerPosition); + break; + case UIEditorColorFieldHitTargetKind::BlueChannel: + ApplyRgbChannelDrag(state, spec, layout.blueSliderRect, 2, result, state.pointerPosition); + break; + case UIEditorColorFieldHitTargetKind::AlphaChannel: + ApplyAlphaChannelDrag(state, spec, layout, result, state.pointerPosition); + break; + default: + break; + } +} + +void AccumulateResult( + UIEditorColorFieldInteractionResult& accumulated, + const UIEditorColorFieldInteractionResult& current) { + accumulated.consumed = accumulated.consumed || current.consumed; + accumulated.focusChanged = accumulated.focusChanged || current.focusChanged; + accumulated.popupOpened = accumulated.popupOpened || current.popupOpened; + accumulated.popupClosed = accumulated.popupClosed || current.popupClosed; + if (current.colorChanged) { + if (!accumulated.colorChanged) { + accumulated.valueBefore = current.valueBefore; + } + accumulated.valueAfter = current.valueAfter; + } + accumulated.colorChanged = accumulated.colorChanged || current.colorChanged; + if (current.hitTarget.kind != UIEditorColorFieldHitTargetKind::None) { + accumulated.hitTarget = current.hitTarget; + } +} + +bool IsDragTarget(UIEditorColorFieldHitTargetKind kind) { + switch (kind) { + case UIEditorColorFieldHitTargetKind::HueWheel: + case UIEditorColorFieldHitTargetKind::SaturationValue: + case UIEditorColorFieldHitTargetKind::RedChannel: + case UIEditorColorFieldHitTargetKind::GreenChannel: + case UIEditorColorFieldHitTargetKind::BlueChannel: + case UIEditorColorFieldHitTargetKind::AlphaChannel: + return true; + default: + return false; + } +} + +} // namespace + +UIEditorColorFieldInteractionFrame UpdateUIEditorColorFieldInteraction( + UIEditorColorFieldInteractionState& state, + UIEditorColorFieldSpec& spec, + const ::XCEngine::UI::UIRect& bounds, + const std::vector& inputEvents, + const UIEditorColorFieldMetrics& metrics, + const ::XCEngine::UI::UIRect& viewportRect) { + spec.value.r = Clamp01(spec.value.r); + spec.value.g = Clamp01(spec.value.g); + spec.value.b = Clamp01(spec.value.b); + spec.value.a = Clamp01(spec.value.a); + SyncRememberedHue(state, spec); + + UIEditorColorFieldLayout layout = + BuildUIEditorColorFieldLayout(bounds, spec, metrics, viewportRect); + SyncHoverTarget(state, layout); + + UIEditorColorFieldInteractionResult interactionResult = {}; + for (const UIInputEvent& event : inputEvents) { + if (ShouldUsePointerPosition(event)) { + state.pointerPosition = event.position; + state.hasPointerPosition = true; + } else if (event.type == UIInputEventType::PointerLeave) { + state.hasPointerPosition = false; + } + + UIEditorColorFieldInteractionResult eventResult = {}; + switch (event.type) { + case UIInputEventType::FocusGained: + eventResult.focusChanged = !state.colorFieldState.focused; + state.colorFieldState.focused = true; + break; + + case UIInputEventType::FocusLost: + eventResult.focusChanged = state.colorFieldState.focused; + state.colorFieldState.focused = false; + ClosePopup(state, eventResult); + break; + + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerLeave: + ApplyActiveDrag(state, spec, layout, eventResult); + break; + + case UIInputEventType::PointerButtonDown: { + if (event.pointerButton != UIPointerButton::Left) { + break; + } + + const UIEditorColorFieldHitTarget hitTarget = + state.hasPointerPosition + ? HitTestUIEditorColorField(layout, state.colorFieldState.popupOpen, state.pointerPosition) + : UIEditorColorFieldHitTarget {}; + eventResult.hitTarget = hitTarget; + + if (hitTarget.kind == UIEditorColorFieldHitTargetKind::None) { + if (state.colorFieldState.popupOpen) { + ClosePopup(state, eventResult); + } + if (state.colorFieldState.focused) { + eventResult.focusChanged = true; + state.colorFieldState.focused = false; + } + state.colorFieldState.activeTarget = UIEditorColorFieldHitTargetKind::None; + break; + } + + eventResult.focusChanged = !state.colorFieldState.focused; + state.colorFieldState.focused = true; + state.colorFieldState.activeTarget = hitTarget.kind; + if (IsDragTarget(hitTarget.kind)) { + ApplyActiveDrag(state, spec, layout, eventResult); + } else { + eventResult.consumed = true; + } + break; + } + + case UIInputEventType::PointerButtonUp: { + if (event.pointerButton != UIPointerButton::Left) { + break; + } + + const UIEditorColorFieldHitTarget hitTarget = + state.hasPointerPosition + ? HitTestUIEditorColorField(layout, state.colorFieldState.popupOpen, state.pointerPosition) + : UIEditorColorFieldHitTarget {}; + eventResult.hitTarget = hitTarget; + const UIEditorColorFieldHitTargetKind activeTarget = state.colorFieldState.activeTarget; + state.colorFieldState.activeTarget = UIEditorColorFieldHitTargetKind::None; + + if (activeTarget == UIEditorColorFieldHitTargetKind::Swatch && + hitTarget.kind == UIEditorColorFieldHitTargetKind::Swatch && + !spec.readOnly) { + if (state.colorFieldState.popupOpen) { + ClosePopup(state, eventResult); + } else { + OpenPopup(state, spec, eventResult); + } + } else if ( + activeTarget == UIEditorColorFieldHitTargetKind::PopupCloseButton && + hitTarget.kind == UIEditorColorFieldHitTargetKind::PopupCloseButton) { + ClosePopup(state, eventResult); + } else if (IsDragTarget(activeTarget)) { + ApplyActiveDrag(state, spec, layout, eventResult); + } else if ( + hitTarget.kind == UIEditorColorFieldHitTargetKind::PopupSurface || + hitTarget.kind == UIEditorColorFieldHitTargetKind::Row) { + eventResult.consumed = true; + } + break; + } + + case UIInputEventType::KeyDown: + if (!state.colorFieldState.focused) { + break; + } + + if (event.keyCode == static_cast(KeyCode::Escape)) { + ClosePopup(state, eventResult); + break; + } + + if ((event.keyCode == static_cast(KeyCode::Enter) || + event.keyCode == static_cast(KeyCode::Space)) && + !spec.readOnly) { + if (state.colorFieldState.popupOpen) { + ClosePopup(state, eventResult); + } else { + OpenPopup(state, spec, eventResult); + } + } + break; + + default: + break; + } + + SyncRememberedHue(state, spec); + layout = BuildUIEditorColorFieldLayout(bounds, spec, metrics, viewportRect); + SyncHoverTarget(state, layout); + if (eventResult.hitTarget.kind == UIEditorColorFieldHitTargetKind::None && + state.hasPointerPosition) { + eventResult.hitTarget = + HitTestUIEditorColorField(layout, state.colorFieldState.popupOpen, state.pointerPosition); + } + + AccumulateResult(interactionResult, eventResult); + } + + SyncRememberedHue(state, spec); + layout = BuildUIEditorColorFieldLayout(bounds, spec, metrics, viewportRect); + SyncHoverTarget(state, layout); + if (interactionResult.hitTarget.kind == UIEditorColorFieldHitTargetKind::None && + state.hasPointerPosition) { + interactionResult.hitTarget = + HitTestUIEditorColorField(layout, state.colorFieldState.popupOpen, state.pointerPosition); + } + + return { std::move(layout), std::move(interactionResult) }; +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Fields/UIEditorEnumField.cpp b/new_editor/src/Fields/UIEditorEnumField.cpp new file mode 100644 index 00000000..b0c40813 --- /dev/null +++ b/new_editor/src/Fields/UIEditorEnumField.cpp @@ -0,0 +1,250 @@ +#include +#include +#include + +#include + +namespace XCEngine::UI::Editor::Widgets { + +namespace { + +float ClampNonNegative(float value) { + return (std::max)(0.0f, value); +} + +UIEditorEnumFieldMetrics ResolveMetrics(const UIEditorEnumFieldMetrics& metrics) { + const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); + + UIEditorEnumFieldMetrics resolved = metrics; + if (AreUIEditorFieldMetricsEqual(metrics.controlTrailingInset, 8.0f)) { + resolved.controlTrailingInset = tokens.controlTrailingInset; + } + if (AreUIEditorFieldMetricsEqual(metrics.valueBoxMinWidth, 96.0f)) { + resolved.valueBoxMinWidth = tokens.controlMinWidth; + } + if (AreUIEditorFieldMetricsEqual(metrics.dropdownArrowWidth, 20.0f)) { + resolved.dropdownArrowWidth = tokens.dropdownArrowWidth; + } + + return resolved; +} + +UIEditorEnumFieldPalette ResolvePalette(const UIEditorEnumFieldPalette& palette) { + const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); + + UIEditorEnumFieldPalette resolved = palette; + if (AreUIEditorFieldColorsEqual(palette.rowHoverColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.rowHoverColor = tokens.rowHoverColor; + } + if (AreUIEditorFieldColorsEqual(palette.rowActiveColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.rowActiveColor = tokens.rowActiveColor; + } + if (AreUIEditorFieldColorsEqual(palette.focusedBorderColor, ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f))) { + resolved.focusedBorderColor = tokens.controlFocusedBorderColor; + } + if (AreUIEditorFieldColorsEqual(palette.valueBoxColor, ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f))) { + resolved.valueBoxColor = tokens.controlColor; + } + if (AreUIEditorFieldColorsEqual(palette.valueBoxHoverColor, ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f))) { + resolved.valueBoxHoverColor = tokens.controlHoverColor; + } + if (AreUIEditorFieldColorsEqual(palette.readOnlyColor, ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f))) { + resolved.readOnlyColor = tokens.controlReadOnlyColor; + } + if (AreUIEditorFieldColorsEqual(palette.controlBorderColor, ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f))) { + resolved.controlBorderColor = tokens.controlBorderColor; + } + if (AreUIEditorFieldColorsEqual(palette.labelColor, ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f))) { + resolved.labelColor = tokens.labelColor; + } + if (AreUIEditorFieldColorsEqual(palette.valueColor, ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f))) { + resolved.valueColor = tokens.valueColor; + } + if (AreUIEditorFieldColorsEqual(palette.arrowColor, ::XCEngine::UI::UIColor(0.88f, 0.88f, 0.88f, 1.0f))) { + resolved.arrowColor = tokens.arrowColor; + } + + return resolved; +} + +bool ContainsPoint(const ::XCEngine::UI::UIRect& rect, const ::XCEngine::UI::UIPoint& point) { + return point.x >= rect.x && + point.x <= rect.x + rect.width && + point.y >= rect.y && + point.y <= rect.y + rect.height; +} + +std::size_t ClampSelectedIndex(const UIEditorEnumFieldSpec& spec) { + if (spec.options.empty()) { + return 0u; + } + + return (std::min)(spec.selectedIndex, spec.options.size() - 1u); +} + +void AppendDropdownChevron( + ::XCEngine::UI::UIDrawList& drawList, + const ::XCEngine::UI::UIRect& rect, + const ::XCEngine::UI::UIColor& color) { + const float centerX = rect.x + rect.width * 0.5f; + const float centerY = rect.y + rect.height * 0.5f; + const float halfWidth = ClampNonNegative(rect.width * 0.18f); + const float halfHeight = ClampNonNegative(rect.height * 0.10f); + drawList.AddLine( + ::XCEngine::UI::UIPoint(centerX - halfWidth, centerY - halfHeight), + ::XCEngine::UI::UIPoint(centerX, centerY + halfHeight), + color, + 1.0f); + drawList.AddLine( + ::XCEngine::UI::UIPoint(centerX, centerY + halfHeight), + ::XCEngine::UI::UIPoint(centerX + halfWidth, centerY - halfHeight), + color, + 1.0f); +} + +} // namespace + +std::string ResolveUIEditorEnumFieldValueText(const UIEditorEnumFieldSpec& spec) { + if (spec.options.empty()) { + return "(none)"; + } + + return spec.options[ClampSelectedIndex(spec)]; +} + +UIEditorEnumFieldLayout BuildUIEditorEnumFieldLayout( + const ::XCEngine::UI::UIRect& bounds, + const UIEditorEnumFieldSpec&, + const UIEditorEnumFieldMetrics& metrics) { + const UIEditorEnumFieldMetrics resolvedMetrics = ResolveMetrics(metrics); + const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout( + bounds, + resolvedMetrics.valueBoxMinWidth, + UIEditorFieldRowLayoutMetrics { + resolvedMetrics.rowHeight, + resolvedMetrics.horizontalPadding, + resolvedMetrics.labelControlGap, + resolvedMetrics.controlColumnStart, + resolvedMetrics.controlTrailingInset, + resolvedMetrics.controlInsetY, + }); + + UIEditorEnumFieldLayout layout = {}; + layout.bounds = hostLayout.bounds; + layout.labelRect = hostLayout.labelRect; + layout.controlRect = hostLayout.controlRect; + layout.valueRect = layout.controlRect; + const float arrowWidth = (std::min)( + (std::max)(resolvedMetrics.dropdownArrowWidth, 0.0f), + layout.valueRect.width); + layout.arrowRect = ::XCEngine::UI::UIRect( + layout.valueRect.x + (std::max)(0.0f, layout.valueRect.width - arrowWidth), + layout.valueRect.y, + arrowWidth, + layout.valueRect.height); + return layout; +} + +UIEditorEnumFieldHitTarget HitTestUIEditorEnumField( + const UIEditorEnumFieldLayout& layout, + const ::XCEngine::UI::UIPoint& point) { + if (!ContainsPoint(layout.bounds, point)) { + return {}; + } + if (ContainsPoint(layout.arrowRect, point)) { + return { UIEditorEnumFieldHitTargetKind::DropdownArrow }; + } + if (ContainsPoint(layout.valueRect, point)) { + return { UIEditorEnumFieldHitTargetKind::ValueBox }; + } + return { UIEditorEnumFieldHitTargetKind::Row }; +} + +void AppendUIEditorEnumFieldBackground( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorEnumFieldLayout& layout, + const UIEditorEnumFieldSpec& spec, + const UIEditorEnumFieldState& state, + const UIEditorEnumFieldPalette& palette, + const UIEditorEnumFieldMetrics& metrics) { + const UIEditorEnumFieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorEnumFieldMetrics resolvedMetrics = ResolveMetrics(metrics); + const auto rowFillColor = + state.active ? resolvedPalette.rowActiveColor + : (state.hoveredTarget != UIEditorEnumFieldHitTargetKind::None + ? resolvedPalette.rowHoverColor + : resolvedPalette.surfaceColor); + if (rowFillColor.a > 0.0f) { + drawList.AddFilledRect(layout.bounds, rowFillColor, resolvedMetrics.cornerRounding); + } + + const bool controlHovered = + state.hoveredTarget == UIEditorEnumFieldHitTargetKind::ValueBox || + state.hoveredTarget == UIEditorEnumFieldHitTargetKind::DropdownArrow; + const ::XCEngine::UI::UIColor controlColor = + spec.readOnly + ? resolvedPalette.readOnlyColor + : (controlHovered || state.popupOpen ? resolvedPalette.valueBoxHoverColor : resolvedPalette.valueBoxColor); + drawList.AddFilledRect(layout.valueRect, controlColor, resolvedMetrics.valueBoxRounding); + drawList.AddRectOutline( + layout.valueRect, + state.popupOpen ? resolvedPalette.focusedBorderColor : resolvedPalette.controlBorderColor, + state.popupOpen ? resolvedMetrics.focusedBorderThickness : resolvedMetrics.borderThickness, + resolvedMetrics.valueBoxRounding); + drawList.AddLine( + ::XCEngine::UI::UIPoint(layout.arrowRect.x, layout.arrowRect.y + 1.0f), + ::XCEngine::UI::UIPoint(layout.arrowRect.x, layout.arrowRect.y + layout.arrowRect.height - 1.0f), + resolvedPalette.controlBorderColor, + 1.0f); +} + +void AppendUIEditorEnumFieldForeground( + ::XCEngine::UI::UIDrawList& drawList, + const UIEditorEnumFieldLayout& layout, + const UIEditorEnumFieldSpec& spec, + const UIEditorEnumFieldPalette& palette, + const UIEditorEnumFieldMetrics& metrics) { + const UIEditorEnumFieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorEnumFieldMetrics resolvedMetrics = ResolveMetrics(metrics); + drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, resolvedMetrics.labelFontSize)); + drawList.AddText( + ::XCEngine::UI::UIPoint( + layout.labelRect.x, + ResolveUIEditorTextTop(layout.labelRect, resolvedMetrics.labelFontSize, resolvedMetrics.labelTextInsetY)), + spec.label, + resolvedPalette.labelColor, + resolvedMetrics.labelFontSize); + drawList.PopClipRect(); + + const ::XCEngine::UI::UIRect valueTextClipRect( + layout.valueRect.x + resolvedMetrics.valueTextInsetX, + layout.valueRect.y, + ClampNonNegative(layout.arrowRect.x - layout.valueRect.x - resolvedMetrics.valueTextInsetX), + layout.valueRect.height); + drawList.PushClipRect(ResolveUIEditorTextClipRect(valueTextClipRect, resolvedMetrics.valueFontSize)); + drawList.AddText( + ::XCEngine::UI::UIPoint( + layout.valueRect.x + resolvedMetrics.valueTextInsetX, + ResolveUIEditorTextTop(layout.valueRect, resolvedMetrics.valueFontSize, resolvedMetrics.valueTextInsetY)), + ResolveUIEditorEnumFieldValueText(spec), + resolvedPalette.valueColor, + resolvedMetrics.valueFontSize); + drawList.PopClipRect(); + AppendDropdownChevron(drawList, layout.arrowRect, resolvedPalette.arrowColor); +} + +void AppendUIEditorEnumField( + ::XCEngine::UI::UIDrawList& drawList, + const ::XCEngine::UI::UIRect& bounds, + const UIEditorEnumFieldSpec& spec, + const UIEditorEnumFieldState& state, + const UIEditorEnumFieldPalette& palette, + const UIEditorEnumFieldMetrics& metrics) { + const UIEditorEnumFieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorEnumFieldMetrics resolvedMetrics = ResolveMetrics(metrics); + const UIEditorEnumFieldLayout layout = BuildUIEditorEnumFieldLayout(bounds, spec, resolvedMetrics); + AppendUIEditorEnumFieldBackground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); + AppendUIEditorEnumFieldForeground(drawList, layout, spec, resolvedPalette, resolvedMetrics); +} + +} // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/src/Core/UIEditorEnumFieldInteraction.cpp b/new_editor/src/Fields/UIEditorEnumFieldInteraction.cpp similarity index 99% rename from new_editor/src/Core/UIEditorEnumFieldInteraction.cpp rename to new_editor/src/Fields/UIEditorEnumFieldInteraction.cpp index 9c87b695..a18a0f7c 100644 --- a/new_editor/src/Core/UIEditorEnumFieldInteraction.cpp +++ b/new_editor/src/Fields/UIEditorEnumFieldInteraction.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Fields/UIEditorFieldStyle.cpp b/new_editor/src/Fields/UIEditorFieldStyle.cpp new file mode 100644 index 00000000..f17e4271 --- /dev/null +++ b/new_editor/src/Fields/UIEditorFieldStyle.cpp @@ -0,0 +1,477 @@ +#include + +namespace XCEngine::UI::Editor { + +namespace { + +using ::XCEngine::UI::UIColor; + +constexpr UIColor kTransparent(0.0f, 0.0f, 0.0f, 0.0f); + +} // namespace + +const Widgets::UIEditorPropertyGridMetrics& GetUIEditorFixedPropertyGridMetrics() { + static const Widgets::UIEditorPropertyGridMetrics kMetrics = {}; + return kMetrics; +} + +const Widgets::UIEditorPropertyGridPalette& GetUIEditorFixedPropertyGridPalette() { + static const Widgets::UIEditorPropertyGridPalette kPalette = {}; + return kPalette; +} + +Widgets::UIEditorBoolFieldMetrics BuildUIEditorPropertyGridBoolFieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorBoolFieldMetrics& fallback) { + Widgets::UIEditorBoolFieldMetrics hosted = fallback; + hosted.rowHeight = propertyGridMetrics.fieldRowHeight; + hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; + hosted.labelControlGap = propertyGridMetrics.labelControlGap; + hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; + hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; + hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; + hosted.labelFontSize = propertyGridMetrics.labelFontSize; + hosted.checkboxGlyphFontSize = propertyGridMetrics.valueFontSize; + hosted.cornerRounding = propertyGridMetrics.cornerRounding; + hosted.checkboxRounding = propertyGridMetrics.valueBoxRounding; + hosted.borderThickness = propertyGridMetrics.borderThickness; + hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; + return hosted; +} + +Widgets::UIEditorBoolFieldPalette BuildUIEditorPropertyGridBoolFieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorBoolFieldPalette& fallback) { + Widgets::UIEditorBoolFieldPalette hosted = fallback; + hosted.surfaceColor = kTransparent; + hosted.borderColor = kTransparent; + hosted.focusedBorderColor = kTransparent; + hosted.rowHoverColor = kTransparent; + hosted.rowActiveColor = kTransparent; + hosted.checkboxColor = propertyGridPalette.valueBoxColor; + hosted.checkboxHoverColor = propertyGridPalette.valueBoxHoverColor; + hosted.checkboxReadOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; + hosted.checkboxBorderColor = propertyGridPalette.valueBoxBorderColor; + hosted.checkboxMarkColor = propertyGridPalette.valueTextColor; + hosted.labelColor = propertyGridPalette.labelTextColor; + return hosted; +} + +Widgets::UIEditorNumberFieldMetrics BuildUIEditorPropertyGridNumberFieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorNumberFieldMetrics& fallback) { + Widgets::UIEditorNumberFieldMetrics hosted = fallback; + hosted.rowHeight = propertyGridMetrics.fieldRowHeight; + hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; + hosted.labelControlGap = propertyGridMetrics.labelControlGap; + hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; + hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; + hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; + hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; + hosted.labelFontSize = propertyGridMetrics.labelFontSize; + hosted.valueTextInsetX = propertyGridMetrics.valueBoxInsetX; + hosted.valueTextInsetY = propertyGridMetrics.valueTextInsetY; + hosted.valueFontSize = propertyGridMetrics.valueFontSize; + hosted.cornerRounding = propertyGridMetrics.cornerRounding; + hosted.valueBoxRounding = propertyGridMetrics.valueBoxRounding; + hosted.borderThickness = propertyGridMetrics.borderThickness; + hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; + return hosted; +} + +Widgets::UIEditorNumberFieldPalette BuildUIEditorPropertyGridNumberFieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorNumberFieldPalette& fallback) { + Widgets::UIEditorNumberFieldPalette hosted = fallback; + hosted.surfaceColor = kTransparent; + hosted.borderColor = kTransparent; + hosted.focusedBorderColor = kTransparent; + hosted.rowHoverColor = kTransparent; + hosted.rowActiveColor = kTransparent; + hosted.valueBoxColor = propertyGridPalette.valueBoxColor; + hosted.valueBoxHoverColor = propertyGridPalette.valueBoxHoverColor; + hosted.valueBoxEditingColor = propertyGridPalette.valueBoxEditingColor; + hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; + hosted.controlBorderColor = propertyGridPalette.valueBoxBorderColor; + hosted.controlFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; + hosted.labelColor = propertyGridPalette.labelTextColor; + hosted.valueColor = propertyGridPalette.valueTextColor; + hosted.readOnlyValueColor = propertyGridPalette.readOnlyValueTextColor; + return hosted; +} + +Widgets::UIEditorTextFieldMetrics BuildUIEditorPropertyGridTextFieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorTextFieldMetrics& fallback) { + Widgets::UIEditorTextFieldMetrics hosted = fallback; + hosted.rowHeight = propertyGridMetrics.fieldRowHeight; + hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; + hosted.labelControlGap = propertyGridMetrics.labelControlGap; + hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; + hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; + hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; + hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; + hosted.labelFontSize = propertyGridMetrics.labelFontSize; + hosted.valueTextInsetX = propertyGridMetrics.valueBoxInsetX; + hosted.valueTextInsetY = propertyGridMetrics.valueTextInsetY; + hosted.valueFontSize = propertyGridMetrics.valueFontSize; + hosted.cornerRounding = propertyGridMetrics.cornerRounding; + hosted.valueBoxRounding = propertyGridMetrics.valueBoxRounding; + hosted.borderThickness = propertyGridMetrics.borderThickness; + hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; + return hosted; +} + +Widgets::UIEditorTextFieldPalette BuildUIEditorPropertyGridTextFieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorTextFieldPalette& fallback) { + Widgets::UIEditorTextFieldPalette hosted = fallback; + hosted.surfaceColor = kTransparent; + hosted.borderColor = kTransparent; + hosted.focusedBorderColor = kTransparent; + hosted.rowHoverColor = kTransparent; + hosted.rowActiveColor = kTransparent; + hosted.valueBoxColor = propertyGridPalette.valueBoxColor; + hosted.valueBoxHoverColor = propertyGridPalette.valueBoxHoverColor; + hosted.valueBoxEditingColor = propertyGridPalette.valueBoxEditingColor; + hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; + hosted.controlBorderColor = propertyGridPalette.valueBoxBorderColor; + hosted.controlFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; + hosted.labelColor = propertyGridPalette.labelTextColor; + hosted.valueColor = propertyGridPalette.valueTextColor; + hosted.readOnlyValueColor = propertyGridPalette.readOnlyValueTextColor; + return hosted; +} + +Widgets::UIEditorVector2FieldMetrics BuildUIEditorPropertyGridVector2FieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorVector2FieldMetrics& fallback) { + Widgets::UIEditorVector2FieldMetrics hosted = fallback; + hosted.rowHeight = propertyGridMetrics.fieldRowHeight; + hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; + hosted.labelControlGap = propertyGridMetrics.labelControlGap; + hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; + hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; + hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; + hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; + hosted.labelFontSize = propertyGridMetrics.labelFontSize; + hosted.valueTextInsetX = propertyGridMetrics.valueBoxInsetX; + hosted.valueTextInsetY = propertyGridMetrics.valueTextInsetY; + hosted.valueFontSize = propertyGridMetrics.valueFontSize; + hosted.cornerRounding = propertyGridMetrics.cornerRounding; + hosted.componentRounding = propertyGridMetrics.valueBoxRounding; + hosted.borderThickness = propertyGridMetrics.borderThickness; + hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; + return hosted; +} + +Widgets::UIEditorVector2FieldPalette BuildUIEditorPropertyGridVector2FieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorVector2FieldPalette& fallback) { + Widgets::UIEditorVector2FieldPalette hosted = fallback; + hosted.surfaceColor = kTransparent; + hosted.borderColor = kTransparent; + hosted.focusedBorderColor = kTransparent; + hosted.rowHoverColor = kTransparent; + hosted.rowActiveColor = kTransparent; + hosted.componentColor = propertyGridPalette.valueBoxColor; + hosted.componentHoverColor = propertyGridPalette.valueBoxHoverColor; + hosted.componentEditingColor = propertyGridPalette.valueBoxEditingColor; + hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; + hosted.componentBorderColor = propertyGridPalette.valueBoxBorderColor; + hosted.componentFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; + hosted.prefixColor = propertyGridPalette.valueBoxHoverColor; + hosted.prefixBorderColor = propertyGridPalette.valueBoxBorderColor; + hosted.labelColor = propertyGridPalette.labelTextColor; + hosted.valueColor = propertyGridPalette.valueTextColor; + hosted.readOnlyValueColor = propertyGridPalette.readOnlyValueTextColor; + return hosted; +} + +Widgets::UIEditorVector3FieldMetrics BuildUIEditorPropertyGridVector3FieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorVector3FieldMetrics& fallback) { + Widgets::UIEditorVector3FieldMetrics hosted = fallback; + hosted.rowHeight = propertyGridMetrics.fieldRowHeight; + hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; + hosted.labelControlGap = propertyGridMetrics.labelControlGap; + hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; + hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; + hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; + hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; + hosted.labelFontSize = propertyGridMetrics.labelFontSize; + hosted.valueTextInsetX = propertyGridMetrics.valueBoxInsetX; + hosted.valueTextInsetY = propertyGridMetrics.valueTextInsetY; + hosted.valueFontSize = propertyGridMetrics.valueFontSize; + hosted.cornerRounding = propertyGridMetrics.cornerRounding; + hosted.componentRounding = propertyGridMetrics.valueBoxRounding; + hosted.borderThickness = propertyGridMetrics.borderThickness; + hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; + return hosted; +} + +Widgets::UIEditorVector3FieldPalette BuildUIEditorPropertyGridVector3FieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorVector3FieldPalette& fallback) { + Widgets::UIEditorVector3FieldPalette hosted = fallback; + hosted.surfaceColor = kTransparent; + hosted.borderColor = kTransparent; + hosted.focusedBorderColor = kTransparent; + hosted.rowHoverColor = kTransparent; + hosted.rowActiveColor = kTransparent; + hosted.componentColor = propertyGridPalette.valueBoxColor; + hosted.componentHoverColor = propertyGridPalette.valueBoxHoverColor; + hosted.componentEditingColor = propertyGridPalette.valueBoxEditingColor; + hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; + hosted.componentBorderColor = propertyGridPalette.valueBoxBorderColor; + hosted.componentFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; + hosted.prefixColor = propertyGridPalette.valueBoxHoverColor; + hosted.prefixBorderColor = propertyGridPalette.valueBoxBorderColor; + hosted.labelColor = propertyGridPalette.labelTextColor; + hosted.valueColor = propertyGridPalette.valueTextColor; + hosted.readOnlyValueColor = propertyGridPalette.readOnlyValueTextColor; + return hosted; +} + +Widgets::UIEditorVector4FieldMetrics BuildUIEditorPropertyGridVector4FieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorVector4FieldMetrics& fallback) { + Widgets::UIEditorVector4FieldMetrics hosted = fallback; + hosted.rowHeight = propertyGridMetrics.fieldRowHeight; + hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; + hosted.labelControlGap = propertyGridMetrics.labelControlGap; + hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; + hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; + hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; + hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; + hosted.labelFontSize = propertyGridMetrics.labelFontSize; + hosted.valueTextInsetX = propertyGridMetrics.valueBoxInsetX; + hosted.valueTextInsetY = propertyGridMetrics.valueTextInsetY; + hosted.valueFontSize = propertyGridMetrics.valueFontSize; + hosted.cornerRounding = propertyGridMetrics.cornerRounding; + hosted.componentRounding = propertyGridMetrics.valueBoxRounding; + hosted.borderThickness = propertyGridMetrics.borderThickness; + hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; + return hosted; +} + +Widgets::UIEditorVector4FieldPalette BuildUIEditorPropertyGridVector4FieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorVector4FieldPalette& fallback) { + Widgets::UIEditorVector4FieldPalette hosted = fallback; + hosted.surfaceColor = kTransparent; + hosted.borderColor = kTransparent; + hosted.focusedBorderColor = kTransparent; + hosted.rowHoverColor = kTransparent; + hosted.rowActiveColor = kTransparent; + hosted.componentColor = propertyGridPalette.valueBoxColor; + hosted.componentHoverColor = propertyGridPalette.valueBoxHoverColor; + hosted.componentEditingColor = propertyGridPalette.valueBoxEditingColor; + hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; + hosted.componentBorderColor = propertyGridPalette.valueBoxBorderColor; + hosted.componentFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; + hosted.prefixColor = propertyGridPalette.valueBoxHoverColor; + hosted.prefixBorderColor = propertyGridPalette.valueBoxBorderColor; + hosted.labelColor = propertyGridPalette.labelTextColor; + hosted.valueColor = propertyGridPalette.valueTextColor; + hosted.readOnlyValueColor = propertyGridPalette.readOnlyValueTextColor; + return hosted; +} + +Widgets::UIEditorEnumFieldMetrics BuildUIEditorPropertyGridEnumFieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorEnumFieldMetrics& fallback) { + Widgets::UIEditorEnumFieldMetrics hosted = fallback; + hosted.rowHeight = propertyGridMetrics.fieldRowHeight; + hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; + hosted.labelControlGap = propertyGridMetrics.labelControlGap; + hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; + hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; + hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; + hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; + hosted.valueTextInsetX = propertyGridMetrics.valueBoxInsetX; + hosted.valueTextInsetY = propertyGridMetrics.valueTextInsetY; + hosted.cornerRounding = propertyGridMetrics.cornerRounding; + hosted.valueBoxRounding = propertyGridMetrics.valueBoxRounding; + hosted.borderThickness = propertyGridMetrics.borderThickness; + hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; + hosted.labelFontSize = propertyGridMetrics.labelFontSize; + hosted.valueFontSize = propertyGridMetrics.valueFontSize; + hosted.dropdownArrowFontSize = propertyGridMetrics.valueFontSize; + return hosted; +} + +Widgets::UIEditorEnumFieldPalette BuildUIEditorPropertyGridEnumFieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorEnumFieldPalette& fallback) { + Widgets::UIEditorEnumFieldPalette hosted = fallback; + hosted.surfaceColor = kTransparent; + hosted.borderColor = kTransparent; + hosted.focusedBorderColor = kTransparent; + hosted.rowHoverColor = kTransparent; + hosted.rowActiveColor = kTransparent; + hosted.valueBoxColor = propertyGridPalette.valueBoxColor; + hosted.valueBoxHoverColor = propertyGridPalette.valueBoxHoverColor; + hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; + hosted.controlBorderColor = propertyGridPalette.valueBoxBorderColor; + hosted.labelColor = propertyGridPalette.labelTextColor; + hosted.valueColor = propertyGridPalette.valueTextColor; + hosted.arrowColor = propertyGridPalette.valueTextColor; + return hosted; +} + +Widgets::UIEditorColorFieldMetrics BuildUIEditorPropertyGridColorFieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorColorFieldMetrics& fallback) { + Widgets::UIEditorColorFieldMetrics hosted = fallback; + hosted.rowHeight = propertyGridMetrics.fieldRowHeight; + hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; + hosted.labelControlGap = propertyGridMetrics.labelControlGap; + hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; + hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; + hosted.swatchInsetY = propertyGridMetrics.valueBoxInsetY; + hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; + hosted.labelFontSize = propertyGridMetrics.labelFontSize; + hosted.swatchRounding = propertyGridMetrics.valueBoxRounding; + hosted.valueFontSize = propertyGridMetrics.valueFontSize; + hosted.valueTextInsetX = propertyGridMetrics.valueBoxInsetX; + hosted.valueTextInsetY = propertyGridMetrics.valueTextInsetY; + hosted.borderThickness = propertyGridMetrics.borderThickness; + hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; + return hosted; +} + +Widgets::UIEditorColorFieldPalette BuildUIEditorPropertyGridColorFieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorColorFieldPalette& fallback) { + Widgets::UIEditorColorFieldPalette hosted = fallback; + hosted.surfaceColor = kTransparent; + hosted.borderColor = kTransparent; + hosted.focusedBorderColor = kTransparent; + hosted.rowHoverColor = kTransparent; + hosted.rowActiveColor = kTransparent; + hosted.labelColor = propertyGridPalette.labelTextColor; + hosted.swatchBorderColor = propertyGridPalette.valueBoxBorderColor; + hosted.swatchHoverBorderColor = propertyGridPalette.valueBoxEditingBorderColor; + hosted.swatchReadOnlyOverlayColor = propertyGridPalette.valueBoxReadOnlyColor; + hosted.checkerLightColor = propertyGridPalette.valueBoxHoverColor; + hosted.checkerDarkColor = propertyGridPalette.valueBoxColor; + hosted.handleFillColor = propertyGridPalette.valueTextColor; + hosted.handleStrokeColor = propertyGridPalette.borderColor; + return hosted; +} + +Widgets::UIEditorObjectFieldMetrics BuildUIEditorPropertyGridObjectFieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorObjectFieldMetrics& fallback) { + Widgets::UIEditorObjectFieldMetrics hosted = fallback; + hosted.rowHeight = propertyGridMetrics.fieldRowHeight; + hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; + hosted.labelControlGap = propertyGridMetrics.labelControlGap; + hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; + hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; + hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; + hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; + hosted.labelFontSize = propertyGridMetrics.labelFontSize; + hosted.valueTextInsetX = propertyGridMetrics.valueBoxInsetX; + hosted.valueTextInsetY = propertyGridMetrics.valueTextInsetY; + hosted.valueFontSize = propertyGridMetrics.valueFontSize; + hosted.typeTextInsetX = propertyGridMetrics.valueBoxInsetX; + hosted.typeTextInsetY = propertyGridMetrics.valueTextInsetY; + hosted.typeFontSize = propertyGridMetrics.tagFontSize; + hosted.cornerRounding = propertyGridMetrics.cornerRounding; + hosted.valueBoxRounding = propertyGridMetrics.valueBoxRounding; + hosted.borderThickness = propertyGridMetrics.borderThickness; + hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; + hosted.buttonGlyphFontSize = propertyGridMetrics.valueFontSize; + return hosted; +} + +Widgets::UIEditorObjectFieldPalette BuildUIEditorPropertyGridObjectFieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorObjectFieldPalette& fallback) { + Widgets::UIEditorObjectFieldPalette hosted = fallback; + hosted.surfaceColor = kTransparent; + hosted.borderColor = kTransparent; + hosted.focusedBorderColor = kTransparent; + hosted.rowHoverColor = kTransparent; + hosted.rowActiveColor = kTransparent; + hosted.valueBoxColor = propertyGridPalette.valueBoxColor; + hosted.valueBoxHoverColor = propertyGridPalette.valueBoxHoverColor; + hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; + hosted.controlBorderColor = propertyGridPalette.valueBoxBorderColor; + hosted.controlFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; + hosted.buttonColor = propertyGridPalette.valueBoxReadOnlyColor; + hosted.buttonHoverColor = propertyGridPalette.valueBoxHoverColor; + hosted.buttonActiveColor = propertyGridPalette.valueBoxEditingColor; + hosted.buttonGlyphColor = propertyGridPalette.valueTextColor; + hosted.separatorColor = propertyGridPalette.valueBoxBorderColor; + hosted.labelColor = propertyGridPalette.labelTextColor; + hosted.valueColor = propertyGridPalette.valueTextColor; + hosted.emptyValueColor = propertyGridPalette.readOnlyValueTextColor; + hosted.typeColor = propertyGridPalette.readOnlyValueTextColor; + return hosted; +} + +Widgets::UIEditorAssetFieldMetrics BuildUIEditorPropertyGridAssetFieldMetrics( + const Widgets::UIEditorPropertyGridMetrics& propertyGridMetrics, + const Widgets::UIEditorAssetFieldMetrics& fallback) { + Widgets::UIEditorAssetFieldMetrics hosted = fallback; + hosted.rowHeight = propertyGridMetrics.fieldRowHeight; + hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; + hosted.labelControlGap = propertyGridMetrics.labelControlGap; + hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; + hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; + hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; + hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; + hosted.labelFontSize = propertyGridMetrics.labelFontSize; + hosted.valueTextInsetX = propertyGridMetrics.valueBoxInsetX; + hosted.valueTextInsetY = propertyGridMetrics.valueTextInsetY; + hosted.valueFontSize = propertyGridMetrics.valueFontSize; + hosted.previewGlyphFontSize = propertyGridMetrics.valueFontSize; + hosted.statusBadgeFontSize = propertyGridMetrics.tagFontSize; + hosted.actionGlyphFontSize = propertyGridMetrics.valueFontSize; + hosted.cornerRounding = propertyGridMetrics.cornerRounding; + hosted.valueBoxRounding = propertyGridMetrics.valueBoxRounding; + hosted.previewRounding = propertyGridMetrics.valueBoxRounding; + hosted.badgeRounding = propertyGridMetrics.valueBoxRounding; + hosted.borderThickness = propertyGridMetrics.borderThickness; + hosted.focusedBorderThickness = propertyGridMetrics.focusedBorderThickness; + return hosted; +} + +Widgets::UIEditorAssetFieldPalette BuildUIEditorPropertyGridAssetFieldPalette( + const Widgets::UIEditorPropertyGridPalette& propertyGridPalette, + const Widgets::UIEditorAssetFieldPalette& fallback) { + Widgets::UIEditorAssetFieldPalette hosted = fallback; + hosted.surfaceColor = kTransparent; + hosted.borderColor = kTransparent; + hosted.focusedBorderColor = kTransparent; + hosted.rowHoverColor = kTransparent; + hosted.rowActiveColor = kTransparent; + hosted.valueBoxColor = propertyGridPalette.valueBoxColor; + hosted.valueBoxHoverColor = propertyGridPalette.valueBoxHoverColor; + hosted.valueBoxActiveColor = propertyGridPalette.valueBoxEditingColor; + hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; + hosted.controlBorderColor = propertyGridPalette.valueBoxBorderColor; + hosted.controlFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; + hosted.labelColor = propertyGridPalette.labelTextColor; + hosted.valueColor = propertyGridPalette.valueTextColor; + hosted.emptyValueColor = propertyGridPalette.readOnlyValueTextColor; + hosted.previewBaseColor = propertyGridPalette.valueBoxColor; + hosted.previewEmptyColor = propertyGridPalette.valueBoxReadOnlyColor; + hosted.previewBorderColor = propertyGridPalette.valueBoxBorderColor; + hosted.previewGlyphColor = propertyGridPalette.valueTextColor; + hosted.statusBadgeColor = propertyGridPalette.editTagColor; + hosted.statusBadgeBorderColor = propertyGridPalette.valueBoxBorderColor; + hosted.statusBadgeTextColor = propertyGridPalette.valueTextColor; + hosted.actionButtonHoverColor = propertyGridPalette.valueBoxHoverColor; + hosted.actionButtonActiveColor = propertyGridPalette.valueBoxEditingColor; + hosted.actionButtonColor = propertyGridPalette.valueBoxReadOnlyColor; + hosted.separatorColor = propertyGridPalette.valueBoxBorderColor; + hosted.pickerGlyphColor = propertyGridPalette.valueTextColor; + hosted.clearGlyphColor = propertyGridPalette.readOnlyValueTextColor; + return hosted; +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Widgets/UIEditorNumberField.cpp b/new_editor/src/Fields/UIEditorNumberField.cpp similarity index 54% rename from new_editor/src/Widgets/UIEditorNumberField.cpp rename to new_editor/src/Fields/UIEditorNumberField.cpp index fa748521..4d788f37 100644 --- a/new_editor/src/Widgets/UIEditorNumberField.cpp +++ b/new_editor/src/Fields/UIEditorNumberField.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -16,8 +16,63 @@ using ::XCEngine::UI::UIDrawList; using ::XCEngine::UI::UIPoint; using ::XCEngine::UI::UIRect; -float ClampNonNegative(float value) { - return (std::max)(0.0f, value); +UIEditorNumberFieldMetrics ResolveMetrics(const UIEditorNumberFieldMetrics& metrics) { + const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); + + UIEditorNumberFieldMetrics resolved = metrics; + if (AreUIEditorFieldMetricsEqual(metrics.controlTrailingInset, 8.0f)) { + resolved.controlTrailingInset = tokens.controlTrailingInset; + } + if (AreUIEditorFieldMetricsEqual(metrics.valueBoxMinWidth, 96.0f)) { + resolved.valueBoxMinWidth = tokens.controlMinWidth; + } + + return resolved; +} + +UIEditorNumberFieldPalette ResolvePalette(const UIEditorNumberFieldPalette& palette) { + const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); + + UIEditorNumberFieldPalette resolved = palette; + if (AreUIEditorFieldColorsEqual(palette.rowHoverColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.rowHoverColor = tokens.rowHoverColor; + } + if (AreUIEditorFieldColorsEqual(palette.rowActiveColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.rowActiveColor = tokens.rowActiveColor; + } + if (AreUIEditorFieldColorsEqual(palette.valueBoxColor, ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f))) { + resolved.valueBoxColor = tokens.controlColor; + } + if (AreUIEditorFieldColorsEqual(palette.valueBoxHoverColor, ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f))) { + resolved.valueBoxHoverColor = tokens.controlHoverColor; + } + if (AreUIEditorFieldColorsEqual(palette.valueBoxEditingColor, ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f))) { + resolved.valueBoxEditingColor = tokens.controlEditingColor; + } + if (AreUIEditorFieldColorsEqual(palette.readOnlyColor, ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f))) { + resolved.readOnlyColor = tokens.controlReadOnlyColor; + } + if (AreUIEditorFieldColorsEqual(palette.controlBorderColor, ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f))) { + resolved.controlBorderColor = tokens.controlBorderColor; + } + if (AreUIEditorFieldColorsEqual( + palette.controlFocusedBorderColor, + ::XCEngine::UI::UIColor(0.46f, 0.46f, 0.46f, 1.0f))) { + resolved.controlFocusedBorderColor = tokens.controlFocusedBorderColor; + } + if (AreUIEditorFieldColorsEqual(palette.labelColor, ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f))) { + resolved.labelColor = tokens.labelColor; + } + if (AreUIEditorFieldColorsEqual(palette.valueColor, ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f))) { + resolved.valueColor = tokens.valueColor; + } + if (AreUIEditorFieldColorsEqual( + palette.readOnlyValueColor, + ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f))) { + resolved.readOnlyValueColor = tokens.readOnlyValueColor; + } + + return resolved; } double NormalizeRangeValue(const UIEditorNumberFieldSpec& spec, double value) { @@ -76,14 +131,16 @@ void AppendLabelText( std::string_view text, const UIEditorNumberFieldPalette& palette, const UIEditorNumberFieldMetrics& metrics) { - drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, metrics.labelFontSize)); + const UIEditorNumberFieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorNumberFieldMetrics resolvedMetrics = ResolveMetrics(metrics); + drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, resolvedMetrics.labelFontSize)); drawList.AddText( UIPoint( layout.labelRect.x, - ResolveUIEditorTextTop(layout.labelRect, metrics.labelFontSize, metrics.labelTextInsetY)), + ResolveUIEditorTextTop(layout.labelRect, resolvedMetrics.labelFontSize, resolvedMetrics.labelTextInsetY)), std::string(text), - palette.labelColor, - metrics.labelFontSize); + resolvedPalette.labelColor, + resolvedMetrics.labelFontSize); drawList.PopClipRect(); } @@ -94,14 +151,16 @@ void AppendValueText( const UIEditorNumberFieldSpec& spec, const UIEditorNumberFieldPalette& palette, const UIEditorNumberFieldMetrics& metrics) { - drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.valueRect, metrics.valueFontSize)); + const UIEditorNumberFieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorNumberFieldMetrics resolvedMetrics = ResolveMetrics(metrics); + drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.valueRect, resolvedMetrics.valueFontSize)); drawList.AddText( UIPoint( - layout.valueRect.x + metrics.valueTextInsetX, - ResolveUIEditorTextTop(layout.valueRect, metrics.valueFontSize, metrics.valueTextInsetY)), + layout.valueRect.x + resolvedMetrics.valueTextInsetX, + ResolveUIEditorTextTop(layout.valueRect, resolvedMetrics.valueFontSize, resolvedMetrics.valueTextInsetY)), std::string(text), - spec.readOnly ? palette.readOnlyValueColor : palette.valueColor, - metrics.valueFontSize); + spec.readOnly ? resolvedPalette.readOnlyValueColor : resolvedPalette.valueColor, + resolvedMetrics.valueFontSize); drawList.PopClipRect(); } @@ -158,16 +217,17 @@ UIEditorNumberFieldLayout BuildUIEditorNumberFieldLayout( const UIRect& bounds, const UIEditorNumberFieldSpec&, const UIEditorNumberFieldMetrics& metrics) { + const UIEditorNumberFieldMetrics resolvedMetrics = ResolveMetrics(metrics); const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout( bounds, - metrics.valueBoxMinWidth, + resolvedMetrics.valueBoxMinWidth, UIEditorFieldRowLayoutMetrics { - metrics.rowHeight, - metrics.horizontalPadding, - metrics.labelControlGap, - metrics.controlColumnStart, - metrics.controlTrailingInset, - metrics.controlInsetY, + resolvedMetrics.rowHeight, + resolvedMetrics.horizontalPadding, + resolvedMetrics.labelControlGap, + resolvedMetrics.controlColumnStart, + resolvedMetrics.controlTrailingInset, + resolvedMetrics.controlInsetY, }); UIEditorNumberFieldLayout layout = {}; @@ -197,28 +257,30 @@ void AppendUIEditorNumberFieldBackground( const UIEditorNumberFieldState& state, const UIEditorNumberFieldPalette& palette, const UIEditorNumberFieldMetrics& metrics) { - const auto rowFillColor = ResolveRowFillColor(state, palette); + const UIEditorNumberFieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorNumberFieldMetrics resolvedMetrics = ResolveMetrics(metrics); + const auto rowFillColor = ResolveRowFillColor(state, resolvedPalette); if (rowFillColor.a > 0.0f) { - drawList.AddFilledRect(layout.bounds, rowFillColor, metrics.cornerRounding); + drawList.AddFilledRect(layout.bounds, rowFillColor, resolvedMetrics.cornerRounding); } - const auto rowBorderColor = state.focused ? palette.focusedBorderColor : palette.borderColor; + const auto rowBorderColor = state.focused ? resolvedPalette.focusedBorderColor : resolvedPalette.borderColor; if (rowBorderColor.a > 0.0f) { drawList.AddRectOutline( layout.bounds, rowBorderColor, - state.focused ? metrics.focusedBorderThickness : metrics.borderThickness, - metrics.cornerRounding); + state.focused ? resolvedMetrics.focusedBorderThickness : resolvedMetrics.borderThickness, + resolvedMetrics.cornerRounding); } drawList.AddFilledRect( layout.valueRect, - ResolveValueFillColor(spec, state, palette), - metrics.valueBoxRounding); + ResolveValueFillColor(spec, state, resolvedPalette), + resolvedMetrics.valueBoxRounding); drawList.AddRectOutline( layout.valueRect, - state.editing ? palette.controlFocusedBorderColor : palette.controlBorderColor, - state.editing ? metrics.focusedBorderThickness : metrics.borderThickness, - metrics.valueBoxRounding); + state.editing ? resolvedPalette.controlFocusedBorderColor : resolvedPalette.controlBorderColor, + state.editing ? resolvedMetrics.focusedBorderThickness : resolvedMetrics.borderThickness, + resolvedMetrics.valueBoxRounding); } void AppendUIEditorNumberFieldForeground( @@ -228,14 +290,16 @@ void AppendUIEditorNumberFieldForeground( const UIEditorNumberFieldState& state, const UIEditorNumberFieldPalette& palette, const UIEditorNumberFieldMetrics& metrics) { - AppendLabelText(drawList, layout, spec.label, palette, metrics); + const UIEditorNumberFieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorNumberFieldMetrics resolvedMetrics = ResolveMetrics(metrics); + AppendLabelText(drawList, layout, spec.label, resolvedPalette, resolvedMetrics); AppendValueText( drawList, layout, state.editing ? std::string_view(state.displayText) : std::string_view(FormatUIEditorNumberFieldValue(spec)), spec, - palette, - metrics); + resolvedPalette, + resolvedMetrics); } void AppendUIEditorNumberField( @@ -245,19 +309,21 @@ void AppendUIEditorNumberField( const UIEditorNumberFieldState& state, const UIEditorNumberFieldPalette& palette, const UIEditorNumberFieldMetrics& metrics) { - const UIEditorNumberFieldLayout layout = BuildUIEditorNumberFieldLayout(bounds, spec, metrics); - AppendUIEditorNumberFieldBackground(drawList, layout, spec, state, palette, metrics); + const UIEditorNumberFieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorNumberFieldMetrics resolvedMetrics = ResolveMetrics(metrics); + const UIEditorNumberFieldLayout layout = BuildUIEditorNumberFieldLayout(bounds, spec, resolvedMetrics); + AppendUIEditorNumberFieldBackground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); if (state.editing) { UIEditorNumberFieldSpec editingSpec = spec; if (double parsedValue = 0.0; TryParseUIEditorNumberFieldValue(spec, state.displayText, parsedValue)) { editingSpec.value = parsedValue; } - AppendUIEditorNumberFieldForeground(drawList, layout, editingSpec, state, palette, metrics); + AppendUIEditorNumberFieldForeground(drawList, layout, editingSpec, state, resolvedPalette, resolvedMetrics); return; } - AppendUIEditorNumberFieldForeground(drawList, layout, spec, state, palette, metrics); + AppendUIEditorNumberFieldForeground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); } } // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/src/Core/UIEditorNumberFieldInteraction.cpp b/new_editor/src/Fields/UIEditorNumberFieldInteraction.cpp similarity index 99% rename from new_editor/src/Core/UIEditorNumberFieldInteraction.cpp rename to new_editor/src/Fields/UIEditorNumberFieldInteraction.cpp index 253ad1b2..5d43dccb 100644 --- a/new_editor/src/Core/UIEditorNumberFieldInteraction.cpp +++ b/new_editor/src/Fields/UIEditorNumberFieldInteraction.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/new_editor/src/Fields/UIEditorObjectField.cpp b/new_editor/src/Fields/UIEditorObjectField.cpp new file mode 100644 index 00000000..8fd509d7 --- /dev/null +++ b/new_editor/src/Fields/UIEditorObjectField.cpp @@ -0,0 +1,319 @@ +#include +#include +#include + +#include + +namespace XCEngine::UI::Editor::Widgets { + +namespace { + +using ::XCEngine::UI::UIDrawList; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; + +float ClampNonNegative(float value) { + return (std::max)(0.0f, value); +} + +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 HasClearButton(const UIEditorObjectFieldSpec& spec) { + return spec.showClearButton && spec.hasValue; +} + +bool HasPickerButton(const UIEditorObjectFieldSpec& spec) { + return spec.showPickerButton; +} + +bool HasTypeText(const UIEditorObjectFieldSpec& spec) { + return spec.hasValue && !spec.objectTypeName.empty(); +} + +::XCEngine::UI::UIColor ResolveRowFillColor( + const UIEditorObjectFieldState& state, + const UIEditorObjectFieldPalette& palette) { + if (state.activeTarget != UIEditorObjectFieldHitTargetKind::None) { + return palette.rowActiveColor; + } + if (state.hoveredTarget != UIEditorObjectFieldHitTargetKind::None) { + return palette.rowHoverColor; + } + return palette.surfaceColor; +} + +::XCEngine::UI::UIColor ResolveControlFillColor( + const UIEditorObjectFieldSpec& spec, + const UIEditorObjectFieldState& state, + const UIEditorObjectFieldPalette& palette) { + if (spec.readOnly) { + return palette.readOnlyColor; + } + if (state.hoveredTarget == UIEditorObjectFieldHitTargetKind::ValueBox || + state.activeTarget == UIEditorObjectFieldHitTargetKind::ValueBox) { + return palette.valueBoxHoverColor; + } + return palette.valueBoxColor; +} + +::XCEngine::UI::UIColor ResolveButtonFillColor( + const UIEditorObjectFieldSpec& spec, + const UIEditorObjectFieldState& state, + UIEditorObjectFieldHitTargetKind target, + const UIEditorObjectFieldPalette& palette) { + if (spec.readOnly) { + return palette.readOnlyColor; + } + if (state.activeTarget == target) { + return palette.buttonActiveColor; + } + if (state.hoveredTarget == target) { + return palette.buttonHoverColor; + } + return palette.buttonColor; +} + +void AppendButtonGlyph( + UIDrawList& drawList, + const UIRect& rect, + const char* glyph, + const UIEditorObjectFieldPalette& palette, + const UIEditorObjectFieldMetrics& metrics) { + if (rect.width <= 0.0f || rect.height <= 0.0f) { + return; + } + + drawList.AddText( + UIPoint( + rect.x + ClampNonNegative((rect.width - metrics.buttonGlyphFontSize) * 0.5f), + ResolveUIEditorTextTop(rect, metrics.buttonGlyphFontSize, metrics.buttonGlyphInsetY)), + glyph, + palette.buttonGlyphColor, + metrics.buttonGlyphFontSize); +} + +} // namespace + +std::string ResolveUIEditorObjectFieldDisplayText(const UIEditorObjectFieldSpec& spec) { + if (!spec.hasValue) { + return spec.emptyText.empty() ? "(none)" : spec.emptyText; + } + + return spec.objectName.empty() ? "(unnamed)" : spec.objectName; +} + +UIEditorObjectFieldLayout BuildUIEditorObjectFieldLayout( + const UIRect& bounds, + const UIEditorObjectFieldSpec& spec, + const UIEditorObjectFieldMetrics& metrics) { + const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout( + bounds, + metrics.valueBoxMinWidth, + UIEditorFieldRowLayoutMetrics { + metrics.rowHeight, + metrics.horizontalPadding, + metrics.labelControlGap, + metrics.controlColumnStart, + metrics.controlTrailingInset, + metrics.controlInsetY, + }); + + UIEditorObjectFieldLayout layout = {}; + layout.bounds = hostLayout.bounds; + layout.labelRect = hostLayout.labelRect; + layout.controlRect = hostLayout.controlRect; + + const float pickerWidth = + HasPickerButton(spec) ? (std::min)(metrics.buttonWidth, layout.controlRect.width) : 0.0f; + const float clearWidth = + HasClearButton(spec) + ? (std::min)(metrics.buttonWidth, ClampNonNegative(layout.controlRect.width - pickerWidth)) + : 0.0f; + const float buttonWidth = pickerWidth + clearWidth; + layout.valueRect = UIRect( + layout.controlRect.x, + layout.controlRect.y, + ClampNonNegative(layout.controlRect.width - buttonWidth), + layout.controlRect.height); + + float nextButtonX = layout.valueRect.x + layout.valueRect.width; + if (clearWidth > 0.0f) { + layout.clearButtonRect = UIRect( + nextButtonX, + layout.controlRect.y, + clearWidth, + layout.controlRect.height); + nextButtonX += clearWidth; + } + if (pickerWidth > 0.0f) { + layout.pickerButtonRect = UIRect( + nextButtonX, + layout.controlRect.y, + pickerWidth, + layout.controlRect.height); + } + + if (HasTypeText(spec) && layout.valueRect.width > metrics.typeMinWidth + metrics.valueTextInsetX) { + const float reservedWidth = ClampNonNegative( + layout.valueRect.width * 0.35f + metrics.typeTextInsetX); + const float typeWidth = (std::min)( + metrics.typeMaxWidth, + (std::max)(metrics.typeMinWidth, reservedWidth)); + if (layout.valueRect.width > typeWidth + metrics.valueTextInsetX + metrics.valueTypeGap) { + layout.typeRect = UIRect( + layout.valueRect.x + layout.valueRect.width - typeWidth, + layout.valueRect.y, + typeWidth, + layout.valueRect.height); + } + } + + return layout; +} + +UIEditorObjectFieldHitTarget HitTestUIEditorObjectField( + const UIEditorObjectFieldLayout& layout, + const UIPoint& point) { + if (!ContainsPoint(layout.bounds, point)) { + return {}; + } + if (layout.pickerButtonRect.width > 0.0f && ContainsPoint(layout.pickerButtonRect, point)) { + return { UIEditorObjectFieldHitTargetKind::PickerButton }; + } + if (layout.clearButtonRect.width > 0.0f && ContainsPoint(layout.clearButtonRect, point)) { + return { UIEditorObjectFieldHitTargetKind::ClearButton }; + } + if (ContainsPoint(layout.valueRect, point)) { + return { UIEditorObjectFieldHitTargetKind::ValueBox }; + } + return { UIEditorObjectFieldHitTargetKind::Row }; +} + +void AppendUIEditorObjectFieldBackground( + UIDrawList& drawList, + const UIEditorObjectFieldLayout& layout, + const UIEditorObjectFieldSpec& spec, + const UIEditorObjectFieldState& state, + const UIEditorObjectFieldPalette& palette, + const UIEditorObjectFieldMetrics& metrics) { + const auto rowFillColor = ResolveRowFillColor(state, palette); + if (rowFillColor.a > 0.0f) { + drawList.AddFilledRect(layout.bounds, rowFillColor, metrics.cornerRounding); + } + const auto rowBorderColor = state.focused ? palette.focusedBorderColor : palette.borderColor; + if (rowBorderColor.a > 0.0f) { + drawList.AddRectOutline( + layout.bounds, + rowBorderColor, + state.focused ? metrics.focusedBorderThickness : metrics.borderThickness, + metrics.cornerRounding); + } + + drawList.AddFilledRect( + layout.controlRect, + ResolveControlFillColor(spec, state, palette), + metrics.valueBoxRounding); + drawList.AddRectOutline( + layout.controlRect, + state.focused ? palette.controlFocusedBorderColor : palette.controlBorderColor, + state.focused ? metrics.focusedBorderThickness : metrics.borderThickness, + metrics.valueBoxRounding); + + if (layout.clearButtonRect.width > 0.0f) { + drawList.AddFilledRect( + layout.clearButtonRect, + ResolveButtonFillColor(spec, state, UIEditorObjectFieldHitTargetKind::ClearButton, palette)); + drawList.AddLine( + UIPoint(layout.clearButtonRect.x, layout.clearButtonRect.y + 1.0f), + UIPoint(layout.clearButtonRect.x, layout.clearButtonRect.y + layout.clearButtonRect.height - 1.0f), + palette.separatorColor, + 1.0f); + } + + if (layout.pickerButtonRect.width > 0.0f) { + drawList.AddFilledRect( + layout.pickerButtonRect, + ResolveButtonFillColor(spec, state, UIEditorObjectFieldHitTargetKind::PickerButton, palette)); + drawList.AddLine( + UIPoint(layout.pickerButtonRect.x, layout.pickerButtonRect.y + 1.0f), + UIPoint(layout.pickerButtonRect.x, layout.pickerButtonRect.y + layout.pickerButtonRect.height - 1.0f), + palette.separatorColor, + 1.0f); + } +} + +void AppendUIEditorObjectFieldForeground( + UIDrawList& drawList, + const UIEditorObjectFieldLayout& layout, + const UIEditorObjectFieldSpec& spec, + const UIEditorObjectFieldPalette& palette, + const UIEditorObjectFieldMetrics& metrics) { + drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, metrics.labelFontSize)); + drawList.AddText( + UIPoint( + layout.labelRect.x, + ResolveUIEditorTextTop(layout.labelRect, metrics.labelFontSize, metrics.labelTextInsetY)), + spec.label, + palette.labelColor, + metrics.labelFontSize); + drawList.PopClipRect(); + + float primaryClipWidth = layout.valueRect.width; + if (layout.typeRect.width > 0.0f) { + primaryClipWidth = + ClampNonNegative(layout.typeRect.x - layout.valueRect.x - metrics.valueTypeGap); + } + const UIRect primaryClipRect( + layout.valueRect.x + metrics.valueTextInsetX, + layout.valueRect.y, + ClampNonNegative(primaryClipWidth - metrics.valueTextInsetX), + layout.valueRect.height); + drawList.PushClipRect(ResolveUIEditorTextClipRect(primaryClipRect, metrics.valueFontSize)); + drawList.AddText( + UIPoint( + layout.valueRect.x + metrics.valueTextInsetX, + ResolveUIEditorTextTop(layout.valueRect, metrics.valueFontSize, metrics.valueTextInsetY)), + ResolveUIEditorObjectFieldDisplayText(spec), + spec.hasValue ? palette.valueColor : palette.emptyValueColor, + metrics.valueFontSize); + drawList.PopClipRect(); + + if (layout.typeRect.width > 0.0f) { + const UIRect typeClipRect( + layout.typeRect.x + metrics.typeTextInsetX, + layout.typeRect.y, + ClampNonNegative(layout.typeRect.width - metrics.typeTextInsetX), + layout.typeRect.height); + drawList.PushClipRect(ResolveUIEditorTextClipRect(typeClipRect, metrics.typeFontSize)); + drawList.AddText( + UIPoint( + layout.typeRect.x + metrics.typeTextInsetX, + ResolveUIEditorTextTop(layout.typeRect, metrics.typeFontSize, metrics.typeTextInsetY)), + spec.objectTypeName, + palette.typeColor, + metrics.typeFontSize); + drawList.PopClipRect(); + } + + AppendButtonGlyph(drawList, layout.clearButtonRect, "X", palette, metrics); + AppendButtonGlyph(drawList, layout.pickerButtonRect, "o", palette, metrics); +} + +void AppendUIEditorObjectField( + UIDrawList& drawList, + const UIRect& bounds, + const UIEditorObjectFieldSpec& spec, + const UIEditorObjectFieldState& state, + const UIEditorObjectFieldPalette& palette, + const UIEditorObjectFieldMetrics& metrics) { + const UIEditorObjectFieldLayout layout = BuildUIEditorObjectFieldLayout(bounds, spec, metrics); + AppendUIEditorObjectFieldBackground(drawList, layout, spec, state, palette, metrics); + AppendUIEditorObjectFieldForeground(drawList, layout, spec, palette, metrics); +} + +} // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/src/Fields/UIEditorObjectFieldInteraction.cpp b/new_editor/src/Fields/UIEditorObjectFieldInteraction.cpp new file mode 100644 index 00000000..ec5fd9db --- /dev/null +++ b/new_editor/src/Fields/UIEditorObjectFieldInteraction.cpp @@ -0,0 +1,174 @@ +#include + +#include + +namespace XCEngine::UI::Editor { + +namespace { + +using ::XCEngine::Input::KeyCode; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPointerButton; +using Widgets::BuildUIEditorObjectFieldLayout; +using Widgets::HitTestUIEditorObjectField; +using Widgets::UIEditorObjectFieldHitTargetKind; + +bool ShouldUsePointerPosition(const UIInputEvent& event) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + return true; + default: + return false; + } +} + +bool CanActivate(const Widgets::UIEditorObjectFieldSpec& spec) { + return !spec.readOnly; +} + +bool CanClear(const Widgets::UIEditorObjectFieldSpec& spec) { + return !spec.readOnly && spec.hasValue && spec.showClearButton; +} + +} // namespace + +UIEditorObjectFieldInteractionFrame UpdateUIEditorObjectFieldInteraction( + UIEditorObjectFieldInteractionState& state, + const Widgets::UIEditorObjectFieldSpec& spec, + const ::XCEngine::UI::UIRect& bounds, + const std::vector& inputEvents, + const Widgets::UIEditorObjectFieldMetrics& metrics) { + Widgets::UIEditorObjectFieldLayout layout = + BuildUIEditorObjectFieldLayout(bounds, spec, metrics); + if (state.hasPointerPosition) { + state.fieldState.hoveredTarget = HitTestUIEditorObjectField(layout, state.pointerPosition).kind; + } else { + state.fieldState.hoveredTarget = UIEditorObjectFieldHitTargetKind::None; + } + + UIEditorObjectFieldInteractionResult interactionResult = {}; + for (const UIInputEvent& event : inputEvents) { + if (ShouldUsePointerPosition(event)) { + state.pointerPosition = event.position; + state.hasPointerPosition = true; + } else if (event.type == UIInputEventType::PointerLeave) { + state.hasPointerPosition = false; + } + + UIEditorObjectFieldInteractionResult eventResult = {}; + switch (event.type) { + case UIInputEventType::FocusGained: + eventResult.focusChanged = !state.fieldState.focused; + state.fieldState.focused = true; + break; + + case UIInputEventType::FocusLost: + eventResult.focusChanged = state.fieldState.focused; + state.fieldState.focused = false; + state.fieldState.activeTarget = UIEditorObjectFieldHitTargetKind::None; + state.hasPointerPosition = false; + state.fieldState.hoveredTarget = UIEditorObjectFieldHitTargetKind::None; + break; + + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerLeave: + break; + + case UIInputEventType::PointerButtonDown: + eventResult.hitTarget = state.hasPointerPosition + ? HitTestUIEditorObjectField(layout, state.pointerPosition) + : Widgets::UIEditorObjectFieldHitTarget {}; + if (event.pointerButton == UIPointerButton::Left && + eventResult.hitTarget.kind != UIEditorObjectFieldHitTargetKind::None) { + eventResult.focusChanged = !state.fieldState.focused; + state.fieldState.focused = true; + state.fieldState.activeTarget = eventResult.hitTarget.kind; + eventResult.consumed = true; + } else if (event.pointerButton == UIPointerButton::Left) { + eventResult.focusChanged = state.fieldState.focused; + state.fieldState.focused = false; + state.fieldState.activeTarget = UIEditorObjectFieldHitTargetKind::None; + } + break; + + case UIInputEventType::PointerButtonUp: + eventResult.hitTarget = state.hasPointerPosition + ? HitTestUIEditorObjectField(layout, state.pointerPosition) + : Widgets::UIEditorObjectFieldHitTarget {}; + if (event.pointerButton == UIPointerButton::Left) { + const UIEditorObjectFieldHitTargetKind activeTarget = state.fieldState.activeTarget; + state.fieldState.activeTarget = UIEditorObjectFieldHitTargetKind::None; + if (eventResult.hitTarget.kind == activeTarget) { + if ((activeTarget == UIEditorObjectFieldHitTargetKind::ValueBox || + activeTarget == UIEditorObjectFieldHitTargetKind::PickerButton) && + CanActivate(spec)) { + eventResult.activateRequested = true; + eventResult.consumed = true; + } else if (activeTarget == UIEditorObjectFieldHitTargetKind::ClearButton && + CanClear(spec)) { + eventResult.clearRequested = true; + eventResult.consumed = true; + } else if (activeTarget == UIEditorObjectFieldHitTargetKind::Row) { + eventResult.consumed = true; + } + } + } + break; + + case UIInputEventType::KeyDown: + if (!state.fieldState.focused) { + break; + } + + if ((static_cast(event.keyCode) == KeyCode::Enter || + static_cast(event.keyCode) == KeyCode::Space) && + CanActivate(spec)) { + eventResult.activateRequested = true; + eventResult.consumed = true; + } else if ((static_cast(event.keyCode) == KeyCode::Delete || + static_cast(event.keyCode) == KeyCode::Backspace) && + CanClear(spec)) { + eventResult.clearRequested = true; + eventResult.consumed = true; + } + break; + + default: + break; + } + + layout = BuildUIEditorObjectFieldLayout(bounds, spec, metrics); + if (state.hasPointerPosition) { + state.fieldState.hoveredTarget = HitTestUIEditorObjectField(layout, state.pointerPosition).kind; + } else { + state.fieldState.hoveredTarget = UIEditorObjectFieldHitTargetKind::None; + } + + if (eventResult.consumed || + eventResult.focusChanged || + eventResult.activateRequested || + eventResult.clearRequested || + eventResult.hitTarget.kind != UIEditorObjectFieldHitTargetKind::None) { + interactionResult = eventResult; + } + } + + layout = BuildUIEditorObjectFieldLayout(bounds, spec, metrics); + if (state.hasPointerPosition) { + state.fieldState.hoveredTarget = HitTestUIEditorObjectField(layout, state.pointerPosition).kind; + if (interactionResult.hitTarget.kind == UIEditorObjectFieldHitTargetKind::None) { + interactionResult.hitTarget = HitTestUIEditorObjectField(layout, state.pointerPosition); + } + } else { + state.fieldState.hoveredTarget = UIEditorObjectFieldHitTargetKind::None; + } + + return { layout, interactionResult }; +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Widgets/UIEditorPropertyGrid.cpp b/new_editor/src/Fields/UIEditorPropertyGrid.cpp similarity index 86% rename from new_editor/src/Widgets/UIEditorPropertyGrid.cpp rename to new_editor/src/Fields/UIEditorPropertyGrid.cpp index 01f2cc3b..71f4fa4c 100644 --- a/new_editor/src/Widgets/UIEditorPropertyGrid.cpp +++ b/new_editor/src/Fields/UIEditorPropertyGrid.cpp @@ -1,15 +1,16 @@ -#include +#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include +#include +#include +#include #include @@ -88,6 +89,16 @@ UIEditorEnumFieldSpec BuildEnumFieldSpec(const UIEditorPropertyGridField& field) return spec; } +UIEditorColorFieldSpec BuildColorFieldSpec(const UIEditorPropertyGridField& field) { + UIEditorColorFieldSpec spec = {}; + spec.fieldId = field.fieldId; + spec.label = field.label; + spec.value = field.colorValue.value; + spec.showAlpha = field.colorValue.showAlpha; + spec.readOnly = field.readOnly; + return spec; +} + UIEditorTextFieldSpec BuildTextFieldSpec(const UIEditorPropertyGridField& field) { UIEditorTextFieldSpec spec = {}; spec.fieldId = field.fieldId; @@ -153,7 +164,7 @@ UIEditorPropertyGridFieldRects ResolveFieldRects( const UIEditorBoolFieldLayout fieldLayout = BuildUIEditorBoolFieldLayout( rowRect, BuildBoolFieldSpec(field), - ::XCEngine::UI::Editor::BuildUIEditorHostedBoolFieldMetrics(metrics)); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridBoolFieldMetrics(metrics)); return { fieldLayout.labelRect, fieldLayout.controlRect }; } @@ -161,7 +172,7 @@ UIEditorPropertyGridFieldRects ResolveFieldRects( const UIEditorNumberFieldLayout fieldLayout = BuildUIEditorNumberFieldLayout( rowRect, BuildNumberFieldSpec(field), - ::XCEngine::UI::Editor::BuildUIEditorHostedNumberFieldMetrics(metrics)); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridNumberFieldMetrics(metrics)); return { fieldLayout.labelRect, fieldLayout.valueRect }; } @@ -169,15 +180,23 @@ UIEditorPropertyGridFieldRects ResolveFieldRects( const UIEditorEnumFieldLayout fieldLayout = BuildUIEditorEnumFieldLayout( rowRect, BuildEnumFieldSpec(field), - ::XCEngine::UI::Editor::BuildUIEditorHostedEnumFieldMetrics(metrics)); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridEnumFieldMetrics(metrics)); return { fieldLayout.labelRect, fieldLayout.valueRect }; } + case UIEditorPropertyGridFieldKind::Color: { + const UIEditorColorFieldLayout fieldLayout = BuildUIEditorColorFieldLayout( + rowRect, + BuildColorFieldSpec(field), + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridColorFieldMetrics(metrics)); + return { fieldLayout.labelRect, fieldLayout.controlRect }; + } + case UIEditorPropertyGridFieldKind::Vector2: { const UIEditorVector2FieldLayout fieldLayout = BuildUIEditorVector2FieldLayout( rowRect, BuildVector2FieldSpec(field), - ::XCEngine::UI::Editor::BuildUIEditorHostedVector2FieldMetrics(metrics)); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector2FieldMetrics(metrics)); return { fieldLayout.labelRect, fieldLayout.controlRect }; } @@ -185,7 +204,7 @@ UIEditorPropertyGridFieldRects ResolveFieldRects( const UIEditorVector3FieldLayout fieldLayout = BuildUIEditorVector3FieldLayout( rowRect, BuildVector3FieldSpec(field), - ::XCEngine::UI::Editor::BuildUIEditorHostedVector3FieldMetrics(metrics)); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector3FieldMetrics(metrics)); return { fieldLayout.labelRect, fieldLayout.controlRect }; } @@ -193,7 +212,7 @@ UIEditorPropertyGridFieldRects ResolveFieldRects( const UIEditorVector4FieldLayout fieldLayout = BuildUIEditorVector4FieldLayout( rowRect, BuildVector4FieldSpec(field), - ::XCEngine::UI::Editor::BuildUIEditorHostedVector4FieldMetrics(metrics)); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector4FieldMetrics(metrics)); return { fieldLayout.labelRect, fieldLayout.controlRect }; } @@ -202,7 +221,7 @@ UIEditorPropertyGridFieldRects ResolveFieldRects( const UIEditorTextFieldLayout fieldLayout = BuildUIEditorTextFieldLayout( rowRect, BuildTextFieldSpec(field), - ::XCEngine::UI::Editor::BuildUIEditorHostedTextFieldMetrics(metrics)); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldMetrics(metrics)); return { fieldLayout.labelRect, fieldLayout.valueRect }; } } @@ -256,6 +275,18 @@ UIEditorEnumFieldHitTargetKind ResolveEnumHoveredTarget( : UIEditorEnumFieldHitTargetKind::Row; } +UIEditorColorFieldHitTargetKind ResolveColorHoveredTarget( + const UIEditorPropertyGridState& state, + const UIEditorPropertyGridField& field) { + if (state.hoveredFieldId != field.fieldId) { + return UIEditorColorFieldHitTargetKind::None; + } + + return state.hoveredHitTarget == UIEditorPropertyGridHitTargetKind::ValueBox + ? UIEditorColorFieldHitTargetKind::Swatch + : UIEditorColorFieldHitTargetKind::Row; +} + UIEditorTextFieldHitTargetKind ResolveTextHoveredTarget( const UIEditorPropertyGridState& state, const UIEditorPropertyGridField& field) { @@ -323,6 +354,18 @@ std::vector BuildEnumPopupItems( return items; } +const UIEditorPropertyGridColorFieldVisualState* FindColorFieldVisualState( + const UIEditorPropertyGridState& state, + std::string_view fieldId) { + for (const UIEditorPropertyGridColorFieldVisualState& entry : state.colorFieldStates) { + if (entry.fieldId == fieldId) { + return &entry; + } + } + + return nullptr; +} + UIRect ResolvePopupViewportRect(const UIRect& bounds) { return UIRect(bounds.x - 4096.0f, bounds.y - 4096.0f, 8192.0f, 8192.0f); } @@ -385,6 +428,10 @@ std::string FormatVector2ValueText(const UIEditorPropertyGridField& field) { FormatUIEditorVector2FieldComponentValue(spec, 1u); } +std::string FormatColorValueText(const UIEditorPropertyGridField& field) { + return FormatUIEditorColorFieldHexText(BuildColorFieldSpec(field)); +} + std::string FormatVector3ValueText(const UIEditorPropertyGridField& field) { const UIEditorVector3FieldSpec spec = BuildVector3FieldSpec(field); return FormatUIEditorVector3FieldComponentValue(spec, 0u) + ", " + @@ -450,6 +497,9 @@ std::string ResolveUIEditorPropertyGridFieldValueText( case UIEditorPropertyGridFieldKind::Enum: return ResolveUIEditorEnumFieldValueText(BuildEnumFieldSpec(field)); + case UIEditorPropertyGridFieldKind::Color: + return FormatColorValueText(field); + case UIEditorPropertyGridFieldKind::Vector2: return FormatVector2ValueText(field); @@ -689,33 +739,38 @@ void AppendUIEditorPropertyGridForeground( } const UIEditorBoolFieldMetrics boolMetrics = - ::XCEngine::UI::Editor::BuildUIEditorHostedBoolFieldMetrics(metrics); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridBoolFieldMetrics(metrics); const UIEditorBoolFieldPalette boolPalette = - ::XCEngine::UI::Editor::BuildUIEditorHostedBoolFieldPalette(palette); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridBoolFieldPalette(palette); const UIEditorNumberFieldMetrics numberMetrics = - ::XCEngine::UI::Editor::BuildUIEditorHostedNumberFieldMetrics(metrics); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridNumberFieldMetrics(metrics); const UIEditorNumberFieldPalette numberPalette = - ::XCEngine::UI::Editor::BuildUIEditorHostedNumberFieldPalette(palette); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridNumberFieldPalette(palette); const UIEditorTextFieldMetrics textMetrics = - ::XCEngine::UI::Editor::BuildUIEditorHostedTextFieldMetrics(metrics); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldMetrics(metrics); const UIEditorTextFieldPalette textPalette = - ::XCEngine::UI::Editor::BuildUIEditorHostedTextFieldPalette(palette); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldPalette(palette); const UIEditorVector2FieldMetrics vector2Metrics = - ::XCEngine::UI::Editor::BuildUIEditorHostedVector2FieldMetrics(metrics); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector2FieldMetrics(metrics); const UIEditorVector2FieldPalette vector2Palette = - ::XCEngine::UI::Editor::BuildUIEditorHostedVector2FieldPalette(palette); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector2FieldPalette(palette); const UIEditorVector3FieldMetrics vector3Metrics = - ::XCEngine::UI::Editor::BuildUIEditorHostedVector3FieldMetrics(metrics); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector3FieldMetrics(metrics); const UIEditorVector3FieldPalette vector3Palette = - ::XCEngine::UI::Editor::BuildUIEditorHostedVector3FieldPalette(palette); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector3FieldPalette(palette); const UIEditorVector4FieldMetrics vector4Metrics = - ::XCEngine::UI::Editor::BuildUIEditorHostedVector4FieldMetrics(metrics); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector4FieldMetrics(metrics); const UIEditorVector4FieldPalette vector4Palette = - ::XCEngine::UI::Editor::BuildUIEditorHostedVector4FieldPalette(palette); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridVector4FieldPalette(palette); const UIEditorEnumFieldMetrics enumMetrics = - ::XCEngine::UI::Editor::BuildUIEditorHostedEnumFieldMetrics(metrics); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridEnumFieldMetrics(metrics); const UIEditorEnumFieldPalette enumPalette = - ::XCEngine::UI::Editor::BuildUIEditorHostedEnumFieldPalette(palette); + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridEnumFieldPalette(palette); + const UIEditorColorFieldMetrics colorMetrics = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridColorFieldMetrics(metrics); + const UIEditorColorFieldPalette colorPalette = + ::XCEngine::UI::Editor::BuildUIEditorPropertyGridColorFieldPalette(palette); + const UIRect popupViewportRect = ResolvePopupViewportRect(layout.bounds); for (std::size_t visibleFieldIndex = 0u; visibleFieldIndex < layout.fieldRowRects.size(); @@ -781,6 +836,28 @@ void AppendUIEditorPropertyGridForeground( break; } + case UIEditorPropertyGridFieldKind::Color: { + UIEditorColorFieldState fieldState = {}; + if (const UIEditorPropertyGridColorFieldVisualState* visualState = + FindColorFieldVisualState(state, field.fieldId); + visualState != nullptr) { + fieldState = visualState->state; + } else { + fieldState.hoveredTarget = ResolveColorHoveredTarget(state, field); + fieldState.focused = state.focused; + } + + AppendUIEditorColorField( + drawList, + layout.fieldRowRects[visibleFieldIndex], + BuildColorFieldSpec(field), + fieldState, + colorPalette, + colorMetrics, + popupViewportRect); + break; + } + case UIEditorPropertyGridFieldKind::Vector2: { UIEditorVector2FieldState fieldState = {}; fieldState.hoveredTarget = ResolveVector2HoveredTarget(state, field); diff --git a/new_editor/src/Core/UIEditorPropertyGridInteraction.cpp b/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp similarity index 74% rename from new_editor/src/Core/UIEditorPropertyGridInteraction.cpp rename to new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp index 713db339..7b559e5a 100644 --- a/new_editor/src/Core/UIEditorPropertyGridInteraction.cpp +++ b/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp @@ -1,7 +1,9 @@ -#include +#include -#include -#include +#include +#include +#include +#include #include #include @@ -20,6 +22,10 @@ using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; using ::XCEngine::UI::UIPointerButton; using ::XCEngine::UI::UISize; +using ::XCEngine::UI::Editor::BuildUIEditorPropertyGridColorFieldMetrics; +using ::XCEngine::UI::Editor::UIEditorColorFieldInteractionFrame; +using ::XCEngine::UI::Editor::UIEditorColorFieldInteractionState; +using ::XCEngine::UI::Editor::UpdateUIEditorColorFieldInteraction; using ::XCEngine::UI::Widgets::ResolvePopupPlacementRect; using ::XCEngine::UI::Widgets::UIPopupPlacement; using Widgets::BuildUIEditorPropertyGridLayout; @@ -35,11 +41,14 @@ using Widgets::UIEditorPropertyGridHitTargetKind; using Widgets::UIEditorPropertyGridInvalidIndex; using Widgets::UIEditorPropertyGridLayout; using Widgets::UIEditorPropertyGridSection; +using Widgets::UIEditorPropertyGridColorFieldVisualState; using Widgets::UIEditorMenuPopupHitTarget; using Widgets::UIEditorMenuPopupHitTargetKind; using Widgets::UIEditorMenuPopupInvalidIndex; using Widgets::UIEditorMenuPopupItem; using Widgets::UIEditorMenuPopupLayout; +using Widgets::UIEditorColorFieldHitTargetKind; +using Widgets::UIEditorColorFieldSpec; using ::XCEngine::UI::Editor::UIEditorMenuItemKind; bool ShouldUsePointerPosition(const UIInputEvent& event) { @@ -110,6 +119,16 @@ bool IsNumberEditCharacter(const UIEditorPropertyGridField& field, std::uint32_t return !field.numberValue.integerMode && character == static_cast('.'); } +UIEditorColorFieldSpec BuildColorFieldSpec(const UIEditorPropertyGridField& field) { + UIEditorColorFieldSpec spec = {}; + spec.fieldId = field.fieldId; + spec.label = field.label; + spec.value = field.colorValue.value; + spec.showAlpha = field.colorValue.showAlpha; + spec.readOnly = field.readOnly; + return spec; +} + UIEditorPropertyGridField* FindMutableField( std::vector& sections, std::string_view fieldId) { @@ -144,6 +163,148 @@ void SetChangedValueResult( result.changedValue = ResolveUIEditorPropertyGridFieldValueText(field); } +void MergeInteractionResult( + UIEditorPropertyGridInteractionResult& accumulated, + const UIEditorPropertyGridInteractionResult& current) { + accumulated.consumed = accumulated.consumed || current.consumed; + accumulated.sectionToggled = accumulated.sectionToggled || current.sectionToggled; + accumulated.selectionChanged = accumulated.selectionChanged || current.selectionChanged; + accumulated.keyboardNavigated = accumulated.keyboardNavigated || current.keyboardNavigated; + accumulated.editStarted = accumulated.editStarted || current.editStarted; + accumulated.editValueChanged = accumulated.editValueChanged || current.editValueChanged; + accumulated.editCommitted = accumulated.editCommitted || current.editCommitted; + accumulated.editCommitRejected = accumulated.editCommitRejected || current.editCommitRejected; + accumulated.editCanceled = accumulated.editCanceled || current.editCanceled; + accumulated.popupOpened = accumulated.popupOpened || current.popupOpened; + accumulated.popupClosed = accumulated.popupClosed || current.popupClosed; + accumulated.fieldValueChanged = accumulated.fieldValueChanged || current.fieldValueChanged; + accumulated.secondaryClicked = accumulated.secondaryClicked || current.secondaryClicked; + + if (current.hitTarget.kind != UIEditorPropertyGridHitTargetKind::None) { + accumulated.hitTarget = current.hitTarget; + } + if (!current.toggledSectionId.empty()) { + accumulated.toggledSectionId = current.toggledSectionId; + } + if (!current.selectedFieldId.empty()) { + accumulated.selectedFieldId = current.selectedFieldId; + } + if (!current.activeFieldId.empty()) { + accumulated.activeFieldId = current.activeFieldId; + } + if (!current.committedFieldId.empty()) { + accumulated.committedFieldId = current.committedFieldId; + } + if (!current.committedValue.empty()) { + accumulated.committedValue = current.committedValue; + } + if (!current.changedFieldId.empty()) { + accumulated.changedFieldId = current.changedFieldId; + } + if (!current.changedValue.empty()) { + accumulated.changedValue = current.changedValue; + } +} + +UIEditorPropertyGridColorFieldVisualState* FindMutableColorFieldVisualState( + UIEditorPropertyGridInteractionState& state, + std::string_view fieldId) { + for (UIEditorPropertyGridColorFieldVisualState& entry : state.propertyGridState.colorFieldStates) { + if (entry.fieldId == fieldId) { + return &entry; + } + } + + return nullptr; +} + +const UIEditorPropertyGridColorFieldVisualState* FindColorFieldVisualState( + const UIEditorPropertyGridInteractionState& state, + std::string_view fieldId) { + for (const UIEditorPropertyGridColorFieldVisualState& entry : state.propertyGridState.colorFieldStates) { + if (entry.fieldId == fieldId) { + return &entry; + } + } + + return nullptr; +} + +UIEditorColorFieldInteractionState BuildColorFieldInteractionState( + const UIEditorPropertyGridInteractionState& state, + std::string_view fieldId) { + UIEditorColorFieldInteractionState interactionState = {}; + if (const UIEditorPropertyGridColorFieldVisualState* entry = + FindColorFieldVisualState(state, fieldId); + entry != nullptr) { + interactionState.colorFieldState = entry->state; + } + + interactionState.pointerPosition = state.pointerPosition; + interactionState.hasPointerPosition = state.hasPointerPosition; + return interactionState; +} + +void StoreColorFieldVisualState( + UIEditorPropertyGridInteractionState& state, + std::string_view fieldId, + const UIEditorColorFieldInteractionState& interactionState) { + if (UIEditorPropertyGridColorFieldVisualState* entry = + FindMutableColorFieldVisualState(state, fieldId); + entry != nullptr) { + entry->state = interactionState.colorFieldState; + return; + } + + UIEditorPropertyGridColorFieldVisualState entry = {}; + entry.fieldId = std::string(fieldId); + entry.state = interactionState.colorFieldState; + state.propertyGridState.colorFieldStates.push_back(std::move(entry)); +} + +void PruneColorFieldVisualStates( + UIEditorPropertyGridInteractionState& state, + const UIEditorPropertyGridLayout& layout, + const std::vector& sections) { + const auto isVisibleColorFieldId = [&layout, §ions](std::string_view fieldId) { + const std::size_t visibleFieldIndex = + FindUIEditorPropertyGridVisibleFieldIndex(layout, fieldId, sections); + if (visibleFieldIndex == UIEditorPropertyGridInvalidIndex || + visibleFieldIndex >= layout.visibleFieldSectionIndices.size() || + visibleFieldIndex >= layout.visibleFieldIndices.size()) { + return false; + } + + const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; + const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; + return sectionIndex < sections.size() && + fieldIndex < sections[sectionIndex].fields.size() && + sections[sectionIndex].fields[fieldIndex].kind == UIEditorPropertyGridFieldKind::Color; + }; + + state.propertyGridState.colorFieldStates.erase( + std::remove_if( + state.propertyGridState.colorFieldStates.begin(), + state.propertyGridState.colorFieldStates.end(), + [&isVisibleColorFieldId](const UIEditorPropertyGridColorFieldVisualState& entry) { + return !isVisibleColorFieldId(entry.fieldId); + }), + state.propertyGridState.colorFieldStates.end()); +} + +void CloseOtherColorFieldPopups( + UIEditorPropertyGridInteractionState& state, + std::string_view keepFieldId) { + for (UIEditorPropertyGridColorFieldVisualState& entry : state.propertyGridState.colorFieldStates) { + if (entry.fieldId == keepFieldId) { + continue; + } + + entry.state.popupOpen = false; + entry.state.activeTarget = UIEditorColorFieldHitTargetKind::None; + } +} + std::vector BuildPopupItems( const UIEditorPropertyGridField& field) { std::vector items = {}; @@ -544,6 +705,110 @@ bool ToggleBoolField( return true; } +bool ProcessColorFieldEvent( + UIEditorPropertyGridInteractionState& state, + ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, + ::XCEngine::UI::Widgets::UIPropertyEditModel& propertyEditModel, + const UIEditorPropertyGridLayout& layout, + std::vector& sections, + const Widgets::UIEditorPropertyGridMetrics& metrics, + const UIInputEvent& event, + UIEditorPropertyGridInteractionResult& result) { + const Widgets::UIEditorColorFieldMetrics colorMetrics = + BuildUIEditorPropertyGridColorFieldMetrics(metrics); + const ::XCEngine::UI::UIRect popupViewportRect = ResolvePopupViewportRect(layout.bounds); + bool handled = false; + + for (std::size_t visibleFieldIndex = 0u; + visibleFieldIndex < layout.visibleFieldIndices.size(); + ++visibleFieldIndex) { + const std::size_t sectionIndex = layout.visibleFieldSectionIndices[visibleFieldIndex]; + const std::size_t fieldIndex = layout.visibleFieldIndices[visibleFieldIndex]; + if (sectionIndex >= sections.size() || + fieldIndex >= sections[sectionIndex].fields.size()) { + continue; + } + + UIEditorPropertyGridField& field = sections[sectionIndex].fields[fieldIndex]; + if (field.kind != UIEditorPropertyGridFieldKind::Color) { + continue; + } + + UIEditorColorFieldInteractionState colorState = + BuildColorFieldInteractionState(state, field.fieldId); + const bool popupWasOpen = colorState.colorFieldState.popupOpen; + UIEditorColorFieldSpec spec = BuildColorFieldSpec(field); + const UIEditorColorFieldInteractionFrame frame = + UpdateUIEditorColorFieldInteraction( + colorState, + spec, + layout.fieldRowRects[visibleFieldIndex], + { event }, + colorMetrics, + popupViewportRect); + + if (!frame.result.consumed && + frame.result.hitTarget.kind == UIEditorColorFieldHitTargetKind::None && + !popupWasOpen && + !colorState.colorFieldState.popupOpen) { + continue; + } + + handled = true; + field.colorValue.value = spec.value; + field.colorValue.showAlpha = spec.showAlpha; + StoreColorFieldVisualState(state, field.fieldId, colorState); + + if (frame.result.popupOpened) { + ClosePopup(state, result); + CloseOtherColorFieldPopups(state, field.fieldId); + } + + state.propertyGridState.focused = colorState.colorFieldState.focused; + state.propertyGridState.pressedFieldId.clear(); + + if (frame.result.hitTarget.kind != UIEditorColorFieldHitTargetKind::None || + frame.result.popupOpened || + frame.result.colorChanged || + popupWasOpen || + colorState.colorFieldState.popupOpen) { + result.selectionChanged = + selectionModel.SetSelection(field.fieldId) || result.selectionChanged; + result.selectedFieldId = field.fieldId; + result.activeFieldId = field.fieldId; + state.keyboardNavigation.SetCurrentIndex(visibleFieldIndex); + } + + if (propertyEditModel.HasActiveEdit() && + propertyEditModel.GetActiveFieldId() != field.fieldId && + (frame.result.hitTarget.kind != UIEditorColorFieldHitTargetKind::None || + frame.result.popupOpened)) { + CommitActiveEdit(state, propertyEditModel, sections, result); + } + + result.popupOpened = result.popupOpened || frame.result.popupOpened; + result.popupClosed = result.popupClosed || frame.result.popupClosed; + result.consumed = result.consumed || frame.result.consumed; + if (frame.result.colorChanged) { + SetChangedValueResult(field, result); + } + + if (frame.result.hitTarget.kind == UIEditorColorFieldHitTargetKind::Row) { + result.hitTarget.kind = UIEditorPropertyGridHitTargetKind::FieldRow; + result.hitTarget.sectionIndex = sectionIndex; + result.hitTarget.fieldIndex = fieldIndex; + result.hitTarget.visibleFieldIndex = visibleFieldIndex; + } else if (frame.result.hitTarget.kind != UIEditorColorFieldHitTargetKind::None) { + result.hitTarget.kind = UIEditorPropertyGridHitTargetKind::ValueBox; + result.hitTarget.sectionIndex = sectionIndex; + result.hitTarget.fieldIndex = fieldIndex; + result.hitTarget.visibleFieldIndex = visibleFieldIndex; + } + } + + return handled; +} + } // namespace UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( @@ -558,6 +823,7 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( const Widgets::UIEditorMenuPopupMetrics& popupMetrics) { UIEditorPropertyGridLayout layout = BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics); + PruneColorFieldVisualStates(state, layout, sections); SyncKeyboardNavigation(state, selectionModel, layout, sections); SyncHoverTarget(state, layout, sections, popupMetrics); @@ -578,6 +844,23 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( : UIEditorPropertyGridHitTarget {}; eventResult.hitTarget = hitTarget; + if (ProcessColorFieldEvent( + state, + selectionModel, + propertyEditModel, + layout, + sections, + metrics, + event, + eventResult)) { + MergeInteractionResult(interactionResult, eventResult); + layout = BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics); + PruneColorFieldVisualStates(state, layout, sections); + SyncKeyboardNavigation(state, selectionModel, layout, sections); + SyncHoverTarget(state, layout, sections, popupMetrics); + continue; + } + switch (event.type) { case UIInputEventType::FocusGained: state.propertyGridState.focused = true; @@ -864,6 +1147,7 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( } layout = BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics); + PruneColorFieldVisualStates(state, layout, sections); SyncKeyboardNavigation(state, selectionModel, layout, sections); SyncHoverTarget(state, layout, sections, popupMetrics); if (eventResult.hitTarget.kind == UIEditorPropertyGridHitTargetKind::None && @@ -895,6 +1179,7 @@ UIEditorPropertyGridInteractionFrame UpdateUIEditorPropertyGridInteraction( } layout = BuildUIEditorPropertyGridLayout(bounds, sections, expansionModel, metrics); + PruneColorFieldVisualStates(state, layout, sections); SyncKeyboardNavigation(state, selectionModel, layout, sections); SyncHoverTarget(state, layout, sections, popupMetrics); if (interactionResult.hitTarget.kind == UIEditorPropertyGridHitTargetKind::None && diff --git a/new_editor/src/Fields/UIEditorTextField.cpp b/new_editor/src/Fields/UIEditorTextField.cpp new file mode 100644 index 00000000..c6b4f2a0 --- /dev/null +++ b/new_editor/src/Fields/UIEditorTextField.cpp @@ -0,0 +1,227 @@ +#include +#include +#include + +#include + +namespace XCEngine::UI::Editor::Widgets { + +namespace { + +using ::XCEngine::UI::UIDrawList; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; + +UIEditorTextFieldMetrics ResolveMetrics(const UIEditorTextFieldMetrics& metrics) { + const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); + + UIEditorTextFieldMetrics resolved = metrics; + if (AreUIEditorFieldMetricsEqual(metrics.controlTrailingInset, 8.0f)) { + resolved.controlTrailingInset = tokens.controlTrailingInset; + } + if (AreUIEditorFieldMetricsEqual(metrics.valueBoxMinWidth, 96.0f)) { + resolved.valueBoxMinWidth = tokens.controlMinWidth; + } + + return resolved; +} + +UIEditorTextFieldPalette ResolvePalette(const UIEditorTextFieldPalette& palette) { + const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); + + UIEditorTextFieldPalette resolved = palette; + if (AreUIEditorFieldColorsEqual(palette.rowHoverColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.rowHoverColor = tokens.rowHoverColor; + } + if (AreUIEditorFieldColorsEqual(palette.rowActiveColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.rowActiveColor = tokens.rowActiveColor; + } + if (AreUIEditorFieldColorsEqual(palette.valueBoxColor, ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f))) { + resolved.valueBoxColor = tokens.controlColor; + } + if (AreUIEditorFieldColorsEqual(palette.valueBoxHoverColor, ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f))) { + resolved.valueBoxHoverColor = tokens.controlHoverColor; + } + if (AreUIEditorFieldColorsEqual(palette.valueBoxEditingColor, ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f))) { + resolved.valueBoxEditingColor = tokens.controlEditingColor; + } + if (AreUIEditorFieldColorsEqual(palette.readOnlyColor, ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f))) { + resolved.readOnlyColor = tokens.controlReadOnlyColor; + } + if (AreUIEditorFieldColorsEqual(palette.controlBorderColor, ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f))) { + resolved.controlBorderColor = tokens.controlBorderColor; + } + if (AreUIEditorFieldColorsEqual( + palette.controlFocusedBorderColor, + ::XCEngine::UI::UIColor(0.46f, 0.46f, 0.46f, 1.0f))) { + resolved.controlFocusedBorderColor = tokens.controlFocusedBorderColor; + } + if (AreUIEditorFieldColorsEqual(palette.labelColor, ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f))) { + resolved.labelColor = tokens.labelColor; + } + if (AreUIEditorFieldColorsEqual(palette.valueColor, ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f))) { + resolved.valueColor = tokens.valueColor; + } + if (AreUIEditorFieldColorsEqual( + palette.readOnlyValueColor, + ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f))) { + resolved.readOnlyValueColor = tokens.readOnlyValueColor; + } + + return resolved; +} + +::XCEngine::UI::UIColor ResolveRowFillColor( + const UIEditorTextFieldState& state, + const UIEditorTextFieldPalette& palette) { + if (state.activeTarget != UIEditorTextFieldHitTargetKind::None) { + return palette.rowActiveColor; + } + if (state.hoveredTarget != UIEditorTextFieldHitTargetKind::None) { + return palette.rowHoverColor; + } + return palette.surfaceColor; +} + +::XCEngine::UI::UIColor ResolveValueFillColor( + const UIEditorTextFieldSpec& spec, + const UIEditorTextFieldState& state, + const UIEditorTextFieldPalette& palette) { + if (spec.readOnly) { + return palette.readOnlyColor; + } + if (state.editing) { + return palette.valueBoxEditingColor; + } + if (state.hoveredTarget == UIEditorTextFieldHitTargetKind::ValueBox) { + return palette.valueBoxHoverColor; + } + return palette.valueBoxColor; +} + +} // namespace + +bool IsUIEditorTextFieldPointInside( + 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; +} + +UIEditorTextFieldLayout BuildUIEditorTextFieldLayout( + const UIRect& bounds, + const UIEditorTextFieldSpec&, + const UIEditorTextFieldMetrics& metrics) { + const UIEditorTextFieldMetrics resolvedMetrics = ResolveMetrics(metrics); + const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout( + bounds, + resolvedMetrics.valueBoxMinWidth, + UIEditorFieldRowLayoutMetrics { + resolvedMetrics.rowHeight, + resolvedMetrics.horizontalPadding, + resolvedMetrics.labelControlGap, + resolvedMetrics.controlColumnStart, + resolvedMetrics.controlTrailingInset, + resolvedMetrics.controlInsetY, + }); + + UIEditorTextFieldLayout layout = {}; + layout.bounds = hostLayout.bounds; + layout.labelRect = hostLayout.labelRect; + layout.controlRect = hostLayout.controlRect; + layout.valueRect = layout.controlRect; + return layout; +} + +UIEditorTextFieldHitTarget HitTestUIEditorTextField( + const UIEditorTextFieldLayout& layout, + const UIPoint& point) { + if (IsUIEditorTextFieldPointInside(layout.valueRect, point)) { + return { UIEditorTextFieldHitTargetKind::ValueBox }; + } + if (IsUIEditorTextFieldPointInside(layout.bounds, point)) { + return { UIEditorTextFieldHitTargetKind::Row }; + } + return {}; +} + +void AppendUIEditorTextFieldBackground( + UIDrawList& drawList, + const UIEditorTextFieldLayout& layout, + const UIEditorTextFieldSpec& spec, + const UIEditorTextFieldState& state, + const UIEditorTextFieldPalette& palette, + const UIEditorTextFieldMetrics& metrics) { + const UIEditorTextFieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorTextFieldMetrics resolvedMetrics = ResolveMetrics(metrics); + const auto rowFillColor = ResolveRowFillColor(state, resolvedPalette); + if (rowFillColor.a > 0.0f) { + drawList.AddFilledRect(layout.bounds, rowFillColor, resolvedMetrics.cornerRounding); + } + const auto rowBorderColor = state.focused ? resolvedPalette.focusedBorderColor : resolvedPalette.borderColor; + if (rowBorderColor.a > 0.0f) { + drawList.AddRectOutline( + layout.bounds, + rowBorderColor, + state.focused ? resolvedMetrics.focusedBorderThickness : resolvedMetrics.borderThickness, + resolvedMetrics.cornerRounding); + } + + drawList.AddFilledRect( + layout.valueRect, + ResolveValueFillColor(spec, state, resolvedPalette), + resolvedMetrics.valueBoxRounding); + drawList.AddRectOutline( + layout.valueRect, + state.editing ? resolvedPalette.controlFocusedBorderColor : resolvedPalette.controlBorderColor, + state.editing ? resolvedMetrics.focusedBorderThickness : resolvedMetrics.borderThickness, + resolvedMetrics.valueBoxRounding); +} + +void AppendUIEditorTextFieldForeground( + UIDrawList& drawList, + const UIEditorTextFieldLayout& layout, + const UIEditorTextFieldSpec& spec, + const UIEditorTextFieldState& state, + const UIEditorTextFieldPalette& palette, + const UIEditorTextFieldMetrics& metrics) { + const UIEditorTextFieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorTextFieldMetrics resolvedMetrics = ResolveMetrics(metrics); + drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, resolvedMetrics.labelFontSize)); + drawList.AddText( + UIPoint( + layout.labelRect.x, + ResolveUIEditorTextTop(layout.labelRect, resolvedMetrics.labelFontSize, resolvedMetrics.labelTextInsetY)), + spec.label, + resolvedPalette.labelColor, + resolvedMetrics.labelFontSize); + drawList.PopClipRect(); + + drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.valueRect, resolvedMetrics.valueFontSize)); + drawList.AddText( + UIPoint( + layout.valueRect.x + resolvedMetrics.valueTextInsetX, + ResolveUIEditorTextTop(layout.valueRect, resolvedMetrics.valueFontSize, resolvedMetrics.valueTextInsetY)), + state.editing ? state.displayText : spec.value, + spec.readOnly ? resolvedPalette.readOnlyValueColor : resolvedPalette.valueColor, + resolvedMetrics.valueFontSize); + drawList.PopClipRect(); +} + +void AppendUIEditorTextField( + UIDrawList& drawList, + const UIRect& bounds, + const UIEditorTextFieldSpec& spec, + const UIEditorTextFieldState& state, + const UIEditorTextFieldPalette& palette, + const UIEditorTextFieldMetrics& metrics) { + const UIEditorTextFieldMetrics resolvedMetrics = ResolveMetrics(metrics); + const UIEditorTextFieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorTextFieldLayout layout = BuildUIEditorTextFieldLayout(bounds, spec, resolvedMetrics); + AppendUIEditorTextFieldBackground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); + AppendUIEditorTextFieldForeground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); +} + +} // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/src/Core/UIEditorTextFieldInteraction.cpp b/new_editor/src/Fields/UIEditorTextFieldInteraction.cpp similarity index 99% rename from new_editor/src/Core/UIEditorTextFieldInteraction.cpp rename to new_editor/src/Fields/UIEditorTextFieldInteraction.cpp index 79726805..469c4fe4 100644 --- a/new_editor/src/Core/UIEditorTextFieldInteraction.cpp +++ b/new_editor/src/Fields/UIEditorTextFieldInteraction.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/new_editor/src/Widgets/UIEditorVector2Field.cpp b/new_editor/src/Fields/UIEditorVector2Field.cpp similarity index 51% rename from new_editor/src/Widgets/UIEditorVector2Field.cpp rename to new_editor/src/Fields/UIEditorVector2Field.cpp index 2a36e296..e64aab7f 100644 --- a/new_editor/src/Widgets/UIEditorVector2Field.cpp +++ b/new_editor/src/Fields/UIEditorVector2Field.cpp @@ -1,7 +1,7 @@ -#include +#include #include -#include +#include #include #include @@ -18,6 +18,91 @@ float ClampNonNegative(float value) { return (std::max)(0.0f, value); } +UIEditorVector2FieldMetrics ResolveMetrics(const UIEditorVector2FieldMetrics& metrics) { + const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); + + UIEditorVector2FieldMetrics resolved = metrics; + if (AreUIEditorFieldMetricsEqual(metrics.controlTrailingInset, 8.0f)) { + resolved.controlTrailingInset = tokens.controlTrailingInset; + } + if (AreUIEditorFieldMetricsEqual(metrics.componentMinWidth, 72.0f)) { + resolved.componentMinWidth = tokens.vectorComponentMinWidth; + } + if (AreUIEditorFieldMetricsEqual(metrics.componentPrefixWidth, 9.0f)) { + resolved.componentPrefixWidth = tokens.vectorPrefixWidth; + } + if (AreUIEditorFieldMetricsEqual(metrics.componentLabelGap, 4.0f)) { + resolved.componentLabelGap = tokens.vectorPrefixGap; + } + + return resolved; +} + +UIEditorVector2FieldPalette ResolvePalette(const UIEditorVector2FieldPalette& palette) { + const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); + + UIEditorVector2FieldPalette resolved = palette; + if (AreUIEditorFieldColorsEqual(palette.rowHoverColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.rowHoverColor = tokens.rowHoverColor; + } + if (AreUIEditorFieldColorsEqual(palette.rowActiveColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.rowActiveColor = tokens.rowActiveColor; + } + if (AreUIEditorFieldColorsEqual(palette.componentColor, ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f))) { + resolved.componentColor = tokens.controlColor; + } + if (AreUIEditorFieldColorsEqual( + palette.componentHoverColor, + ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f))) { + resolved.componentHoverColor = tokens.controlHoverColor; + } + if (AreUIEditorFieldColorsEqual( + palette.componentEditingColor, + ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f))) { + resolved.componentEditingColor = tokens.controlEditingColor; + } + if (AreUIEditorFieldColorsEqual(palette.readOnlyColor, ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f))) { + resolved.readOnlyColor = tokens.controlReadOnlyColor; + } + if (AreUIEditorFieldColorsEqual( + palette.componentBorderColor, + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f))) { + resolved.componentBorderColor = tokens.controlBorderColor; + } + if (AreUIEditorFieldColorsEqual( + palette.componentFocusedBorderColor, + ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f))) { + resolved.componentFocusedBorderColor = tokens.controlFocusedBorderColor; + } + if (AreUIEditorFieldColorsEqual(palette.prefixColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.prefixColor = tokens.prefixColor; + } + if (AreUIEditorFieldColorsEqual( + palette.prefixBorderColor, + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.prefixBorderColor = tokens.prefixBorderColor; + } + if (AreUIEditorFieldColorsEqual(palette.labelColor, ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f))) { + resolved.labelColor = tokens.labelColor; + } + if (AreUIEditorFieldColorsEqual(palette.valueColor, ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f))) { + resolved.valueColor = tokens.valueColor; + } + if (AreUIEditorFieldColorsEqual( + palette.readOnlyValueColor, + ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f))) { + resolved.readOnlyValueColor = tokens.readOnlyValueColor; + } + if (AreUIEditorFieldColorsEqual(palette.axisXColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { + resolved.axisXColor = tokens.axisXColor; + } + if (AreUIEditorFieldColorsEqual(palette.axisYColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { + resolved.axisYColor = tokens.axisYColor; + } + + return resolved; +} + float ApproximateTextWidth(float fontSize, std::size_t characterCount) { return fontSize * 0.55f * static_cast(characterCount); } @@ -116,20 +201,22 @@ UIEditorVector2FieldLayout BuildUIEditorVector2FieldLayout( const UIRect& bounds, const UIEditorVector2FieldSpec&, const UIEditorVector2FieldMetrics& metrics) { - const float requiredControlWidth = metrics.componentMinWidth * 2.0f + metrics.componentGap; + const UIEditorVector2FieldMetrics resolvedMetrics = ResolveMetrics(metrics); + const float requiredControlWidth = + resolvedMetrics.componentMinWidth * 2.0f + resolvedMetrics.componentGap; const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout( bounds, requiredControlWidth, UIEditorFieldRowLayoutMetrics { - metrics.rowHeight, - metrics.horizontalPadding, - metrics.labelControlGap, - metrics.controlColumnStart, - metrics.controlTrailingInset, - metrics.controlInsetY, + resolvedMetrics.rowHeight, + resolvedMetrics.horizontalPadding, + resolvedMetrics.labelControlGap, + resolvedMetrics.controlColumnStart, + resolvedMetrics.controlTrailingInset, + resolvedMetrics.controlInsetY, }); const float componentWidth = - ClampNonNegative((hostLayout.controlRect.width - metrics.componentGap) / 2.0f); + ClampNonNegative((hostLayout.controlRect.width - resolvedMetrics.componentGap) / 2.0f); UIEditorVector2FieldLayout layout = {}; layout.bounds = hostLayout.bounds; @@ -138,10 +225,11 @@ UIEditorVector2FieldLayout BuildUIEditorVector2FieldLayout( for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { const float componentX = - layout.controlRect.x + (componentWidth + metrics.componentGap) * static_cast(componentIndex); + layout.controlRect.x + + (componentWidth + resolvedMetrics.componentGap) * static_cast(componentIndex); const UIRect componentRect(componentX, layout.controlRect.y, componentWidth, layout.controlRect.height); - const float prefixWidth = (std::min)(metrics.componentPrefixWidth, componentRect.width); - const float labelGap = ClampNonNegative(metrics.componentLabelGap); + const float prefixWidth = (std::min)(resolvedMetrics.componentPrefixWidth, componentRect.width); + const float labelGap = ClampNonNegative(resolvedMetrics.componentLabelGap); const float valueX = componentRect.x + prefixWidth + labelGap; layout.componentRects[componentIndex] = componentRect; layout.componentPrefixRects[componentIndex] = @@ -178,27 +266,45 @@ void AppendUIEditorVector2FieldBackground( const UIEditorVector2FieldState& state, const UIEditorVector2FieldPalette& palette, const UIEditorVector2FieldMetrics& metrics) { - if (ResolveRowFillColor(state, palette).a > 0.0f) { - drawList.AddFilledRect(layout.bounds, ResolveRowFillColor(state, palette), metrics.cornerRounding); + const UIEditorVector2FieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorVector2FieldMetrics resolvedMetrics = ResolveMetrics(metrics); + if (ResolveRowFillColor(state, resolvedPalette).a > 0.0f) { + drawList.AddFilledRect( + layout.bounds, + ResolveRowFillColor(state, resolvedPalette), + resolvedMetrics.cornerRounding); } - if ((state.focused ? palette.focusedBorderColor.a : palette.borderColor.a) > 0.0f) { + if ((state.focused ? resolvedPalette.focusedBorderColor.a : resolvedPalette.borderColor.a) > 0.0f) { drawList.AddRectOutline( layout.bounds, - state.focused ? palette.focusedBorderColor : palette.borderColor, - state.focused ? metrics.focusedBorderThickness : metrics.borderThickness, - metrics.cornerRounding); + state.focused ? resolvedPalette.focusedBorderColor : resolvedPalette.borderColor, + state.focused ? resolvedMetrics.focusedBorderThickness : resolvedMetrics.borderThickness, + resolvedMetrics.cornerRounding); } for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { + if (resolvedPalette.prefixColor.a > 0.0f) { + drawList.AddFilledRect( + layout.componentPrefixRects[componentIndex], + resolvedPalette.prefixColor, + resolvedMetrics.componentRounding); + } + if (resolvedPalette.prefixBorderColor.a > 0.0f) { + drawList.AddRectOutline( + layout.componentPrefixRects[componentIndex], + resolvedPalette.prefixBorderColor, + resolvedMetrics.borderThickness, + resolvedMetrics.componentRounding); + } drawList.AddFilledRect( layout.componentValueRects[componentIndex], - ResolveComponentFillColor(spec, state, palette, componentIndex), - metrics.componentRounding); + ResolveComponentFillColor(spec, state, resolvedPalette, componentIndex), + resolvedMetrics.componentRounding); drawList.AddRectOutline( layout.componentValueRects[componentIndex], - ResolveComponentBorderColor(state, palette, componentIndex), - metrics.borderThickness, - metrics.componentRounding); + ResolveComponentBorderColor(state, resolvedPalette, componentIndex), + resolvedMetrics.borderThickness, + resolvedMetrics.componentRounding); } } @@ -209,53 +315,55 @@ void AppendUIEditorVector2FieldForeground( const UIEditorVector2FieldState& state, const UIEditorVector2FieldPalette& palette, const UIEditorVector2FieldMetrics& metrics) { - drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, metrics.labelFontSize)); + const UIEditorVector2FieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorVector2FieldMetrics resolvedMetrics = ResolveMetrics(metrics); + drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, resolvedMetrics.labelFontSize)); drawList.AddText( UIPoint( layout.labelRect.x, - ResolveUIEditorTextTop(layout.labelRect, metrics.labelFontSize, metrics.labelTextInsetY)), + ResolveUIEditorTextTop(layout.labelRect, resolvedMetrics.labelFontSize, resolvedMetrics.labelTextInsetY)), spec.label, - palette.labelColor, - metrics.labelFontSize); + resolvedPalette.labelColor, + resolvedMetrics.labelFontSize); drawList.PopClipRect(); for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { drawList.PushClipRect( - ResolveUIEditorTextClipRect(layout.componentPrefixRects[componentIndex], metrics.prefixFontSize)); + ResolveUIEditorTextClipRect(layout.componentPrefixRects[componentIndex], resolvedMetrics.prefixFontSize)); const std::string& componentLabel = spec.componentLabels[componentIndex]; const float prefixTextX = layout.componentPrefixRects[componentIndex].x + ClampNonNegative( (layout.componentPrefixRects[componentIndex].width - - ApproximateTextWidth(metrics.prefixFontSize, componentLabel.size())) * + ApproximateTextWidth(resolvedMetrics.prefixFontSize, componentLabel.size())) * 0.5f) + - metrics.prefixTextInsetX; + resolvedMetrics.prefixTextInsetX; drawList.AddText( UIPoint( prefixTextX, ResolveUIEditorTextTop( layout.componentPrefixRects[componentIndex], - metrics.prefixFontSize, - metrics.prefixTextInsetY)), + resolvedMetrics.prefixFontSize, + resolvedMetrics.prefixTextInsetY)), componentLabel, - ResolveAxisColor(palette, componentIndex), - metrics.prefixFontSize); + ResolveAxisColor(resolvedPalette, componentIndex), + resolvedMetrics.prefixFontSize); drawList.PopClipRect(); drawList.PushClipRect( - ResolveUIEditorTextClipRect(layout.componentValueRects[componentIndex], metrics.valueFontSize)); + ResolveUIEditorTextClipRect(layout.componentValueRects[componentIndex], resolvedMetrics.valueFontSize)); drawList.AddText( UIPoint( - layout.componentValueRects[componentIndex].x + metrics.valueTextInsetX, + layout.componentValueRects[componentIndex].x + resolvedMetrics.valueTextInsetX, ResolveUIEditorTextTop( layout.componentValueRects[componentIndex], - metrics.valueFontSize, - metrics.valueTextInsetY)), + resolvedMetrics.valueFontSize, + resolvedMetrics.valueTextInsetY)), state.editing && state.selectedComponentIndex == componentIndex ? state.displayTexts[componentIndex] : FormatUIEditorVector2FieldComponentValue(spec, componentIndex), - spec.readOnly ? palette.readOnlyValueColor : palette.valueColor, - metrics.valueFontSize); + spec.readOnly ? resolvedPalette.readOnlyValueColor : resolvedPalette.valueColor, + resolvedMetrics.valueFontSize); drawList.PopClipRect(); } } @@ -267,9 +375,11 @@ void AppendUIEditorVector2Field( const UIEditorVector2FieldState& state, const UIEditorVector2FieldPalette& palette, const UIEditorVector2FieldMetrics& metrics) { - const UIEditorVector2FieldLayout layout = BuildUIEditorVector2FieldLayout(bounds, spec, metrics); - AppendUIEditorVector2FieldBackground(drawList, layout, spec, state, palette, metrics); - AppendUIEditorVector2FieldForeground(drawList, layout, spec, state, palette, metrics); + const UIEditorVector2FieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorVector2FieldMetrics resolvedMetrics = ResolveMetrics(metrics); + const UIEditorVector2FieldLayout layout = BuildUIEditorVector2FieldLayout(bounds, spec, resolvedMetrics); + AppendUIEditorVector2FieldBackground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); + AppendUIEditorVector2FieldForeground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); } } // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/src/Core/UIEditorVector2FieldInteraction.cpp b/new_editor/src/Fields/UIEditorVector2FieldInteraction.cpp similarity index 99% rename from new_editor/src/Core/UIEditorVector2FieldInteraction.cpp rename to new_editor/src/Fields/UIEditorVector2FieldInteraction.cpp index 7b86f717..c63c4a2b 100644 --- a/new_editor/src/Core/UIEditorVector2FieldInteraction.cpp +++ b/new_editor/src/Fields/UIEditorVector2FieldInteraction.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/new_editor/src/Widgets/UIEditorVector3Field.cpp b/new_editor/src/Fields/UIEditorVector3Field.cpp similarity index 51% rename from new_editor/src/Widgets/UIEditorVector3Field.cpp rename to new_editor/src/Fields/UIEditorVector3Field.cpp index 4b649271..0840a9ce 100644 --- a/new_editor/src/Widgets/UIEditorVector3Field.cpp +++ b/new_editor/src/Fields/UIEditorVector3Field.cpp @@ -1,7 +1,7 @@ -#include +#include #include -#include +#include #include #include @@ -18,6 +18,94 @@ float ClampNonNegative(float value) { return (std::max)(0.0f, value); } +UIEditorVector3FieldMetrics ResolveMetrics(const UIEditorVector3FieldMetrics& metrics) { + const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); + + UIEditorVector3FieldMetrics resolved = metrics; + if (AreUIEditorFieldMetricsEqual(metrics.controlTrailingInset, 8.0f)) { + resolved.controlTrailingInset = tokens.controlTrailingInset; + } + if (AreUIEditorFieldMetricsEqual(metrics.componentMinWidth, 72.0f)) { + resolved.componentMinWidth = tokens.vectorComponentMinWidth; + } + if (AreUIEditorFieldMetricsEqual(metrics.componentPrefixWidth, 9.0f)) { + resolved.componentPrefixWidth = tokens.vectorPrefixWidth; + } + if (AreUIEditorFieldMetricsEqual(metrics.componentLabelGap, 4.0f)) { + resolved.componentLabelGap = tokens.vectorPrefixGap; + } + + return resolved; +} + +UIEditorVector3FieldPalette ResolvePalette(const UIEditorVector3FieldPalette& palette) { + const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); + + UIEditorVector3FieldPalette resolved = palette; + if (AreUIEditorFieldColorsEqual(palette.rowHoverColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.rowHoverColor = tokens.rowHoverColor; + } + if (AreUIEditorFieldColorsEqual(palette.rowActiveColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.rowActiveColor = tokens.rowActiveColor; + } + if (AreUIEditorFieldColorsEqual(palette.componentColor, ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f))) { + resolved.componentColor = tokens.controlColor; + } + if (AreUIEditorFieldColorsEqual( + palette.componentHoverColor, + ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f))) { + resolved.componentHoverColor = tokens.controlHoverColor; + } + if (AreUIEditorFieldColorsEqual( + palette.componentEditingColor, + ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f))) { + resolved.componentEditingColor = tokens.controlEditingColor; + } + if (AreUIEditorFieldColorsEqual(palette.readOnlyColor, ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f))) { + resolved.readOnlyColor = tokens.controlReadOnlyColor; + } + if (AreUIEditorFieldColorsEqual( + palette.componentBorderColor, + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f))) { + resolved.componentBorderColor = tokens.controlBorderColor; + } + if (AreUIEditorFieldColorsEqual( + palette.componentFocusedBorderColor, + ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f))) { + resolved.componentFocusedBorderColor = tokens.controlFocusedBorderColor; + } + if (AreUIEditorFieldColorsEqual(palette.prefixColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.prefixColor = tokens.prefixColor; + } + if (AreUIEditorFieldColorsEqual( + palette.prefixBorderColor, + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.prefixBorderColor = tokens.prefixBorderColor; + } + if (AreUIEditorFieldColorsEqual(palette.labelColor, ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f))) { + resolved.labelColor = tokens.labelColor; + } + if (AreUIEditorFieldColorsEqual(palette.valueColor, ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f))) { + resolved.valueColor = tokens.valueColor; + } + if (AreUIEditorFieldColorsEqual( + palette.readOnlyValueColor, + ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f))) { + resolved.readOnlyValueColor = tokens.readOnlyValueColor; + } + if (AreUIEditorFieldColorsEqual(palette.axisXColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { + resolved.axisXColor = tokens.axisXColor; + } + if (AreUIEditorFieldColorsEqual(palette.axisYColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { + resolved.axisYColor = tokens.axisYColor; + } + if (AreUIEditorFieldColorsEqual(palette.axisZColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { + resolved.axisZColor = tokens.axisZColor; + } + + return resolved; +} + float ApproximateTextWidth(float fontSize, std::size_t characterCount) { return fontSize * 0.55f * static_cast(characterCount); } @@ -124,20 +212,22 @@ UIEditorVector3FieldLayout BuildUIEditorVector3FieldLayout( const UIRect& bounds, const UIEditorVector3FieldSpec&, const UIEditorVector3FieldMetrics& metrics) { - const float requiredControlWidth = metrics.componentMinWidth * 3.0f + metrics.componentGap * 2.0f; + const UIEditorVector3FieldMetrics resolvedMetrics = ResolveMetrics(metrics); + const float requiredControlWidth = + resolvedMetrics.componentMinWidth * 3.0f + resolvedMetrics.componentGap * 2.0f; const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout( bounds, requiredControlWidth, UIEditorFieldRowLayoutMetrics { - metrics.rowHeight, - metrics.horizontalPadding, - metrics.labelControlGap, - metrics.controlColumnStart, - metrics.controlTrailingInset, - metrics.controlInsetY, + resolvedMetrics.rowHeight, + resolvedMetrics.horizontalPadding, + resolvedMetrics.labelControlGap, + resolvedMetrics.controlColumnStart, + resolvedMetrics.controlTrailingInset, + resolvedMetrics.controlInsetY, }); const float componentWidth = - ClampNonNegative((hostLayout.controlRect.width - metrics.componentGap * 2.0f) / 3.0f); + ClampNonNegative((hostLayout.controlRect.width - resolvedMetrics.componentGap * 2.0f) / 3.0f); UIEditorVector3FieldLayout layout = {}; layout.bounds = hostLayout.bounds; @@ -146,10 +236,11 @@ UIEditorVector3FieldLayout BuildUIEditorVector3FieldLayout( for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { const float componentX = - layout.controlRect.x + (componentWidth + metrics.componentGap) * static_cast(componentIndex); + layout.controlRect.x + + (componentWidth + resolvedMetrics.componentGap) * static_cast(componentIndex); const UIRect componentRect(componentX, layout.controlRect.y, componentWidth, layout.controlRect.height); - const float prefixWidth = (std::min)(metrics.componentPrefixWidth, componentRect.width); - const float labelGap = ClampNonNegative(metrics.componentLabelGap); + const float prefixWidth = (std::min)(resolvedMetrics.componentPrefixWidth, componentRect.width); + const float labelGap = ClampNonNegative(resolvedMetrics.componentLabelGap); const float valueX = componentRect.x + prefixWidth + labelGap; layout.componentRects[componentIndex] = componentRect; layout.componentPrefixRects[componentIndex] = @@ -186,27 +277,45 @@ void AppendUIEditorVector3FieldBackground( const UIEditorVector3FieldState& state, const UIEditorVector3FieldPalette& palette, const UIEditorVector3FieldMetrics& metrics) { - if (ResolveRowFillColor(state, palette).a > 0.0f) { - drawList.AddFilledRect(layout.bounds, ResolveRowFillColor(state, palette), metrics.cornerRounding); + const UIEditorVector3FieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorVector3FieldMetrics resolvedMetrics = ResolveMetrics(metrics); + if (ResolveRowFillColor(state, resolvedPalette).a > 0.0f) { + drawList.AddFilledRect( + layout.bounds, + ResolveRowFillColor(state, resolvedPalette), + resolvedMetrics.cornerRounding); } - if ((state.focused ? palette.focusedBorderColor.a : palette.borderColor.a) > 0.0f) { + if ((state.focused ? resolvedPalette.focusedBorderColor.a : resolvedPalette.borderColor.a) > 0.0f) { drawList.AddRectOutline( layout.bounds, - state.focused ? palette.focusedBorderColor : palette.borderColor, - state.focused ? metrics.focusedBorderThickness : metrics.borderThickness, - metrics.cornerRounding); + state.focused ? resolvedPalette.focusedBorderColor : resolvedPalette.borderColor, + state.focused ? resolvedMetrics.focusedBorderThickness : resolvedMetrics.borderThickness, + resolvedMetrics.cornerRounding); } for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { + if (resolvedPalette.prefixColor.a > 0.0f) { + drawList.AddFilledRect( + layout.componentPrefixRects[componentIndex], + resolvedPalette.prefixColor, + resolvedMetrics.componentRounding); + } + if (resolvedPalette.prefixBorderColor.a > 0.0f) { + drawList.AddRectOutline( + layout.componentPrefixRects[componentIndex], + resolvedPalette.prefixBorderColor, + resolvedMetrics.borderThickness, + resolvedMetrics.componentRounding); + } drawList.AddFilledRect( layout.componentValueRects[componentIndex], - ResolveComponentFillColor(spec, state, palette, componentIndex), - metrics.componentRounding); + ResolveComponentFillColor(spec, state, resolvedPalette, componentIndex), + resolvedMetrics.componentRounding); drawList.AddRectOutline( layout.componentValueRects[componentIndex], - ResolveComponentBorderColor(state, palette, componentIndex), - metrics.borderThickness, - metrics.componentRounding); + ResolveComponentBorderColor(state, resolvedPalette, componentIndex), + resolvedMetrics.borderThickness, + resolvedMetrics.componentRounding); } } @@ -217,53 +326,55 @@ void AppendUIEditorVector3FieldForeground( const UIEditorVector3FieldState& state, const UIEditorVector3FieldPalette& palette, const UIEditorVector3FieldMetrics& metrics) { - drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, metrics.labelFontSize)); + const UIEditorVector3FieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorVector3FieldMetrics resolvedMetrics = ResolveMetrics(metrics); + drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, resolvedMetrics.labelFontSize)); drawList.AddText( UIPoint( layout.labelRect.x, - ResolveUIEditorTextTop(layout.labelRect, metrics.labelFontSize, metrics.labelTextInsetY)), + ResolveUIEditorTextTop(layout.labelRect, resolvedMetrics.labelFontSize, resolvedMetrics.labelTextInsetY)), spec.label, - palette.labelColor, - metrics.labelFontSize); + resolvedPalette.labelColor, + resolvedMetrics.labelFontSize); drawList.PopClipRect(); for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { drawList.PushClipRect( - ResolveUIEditorTextClipRect(layout.componentPrefixRects[componentIndex], metrics.prefixFontSize)); + ResolveUIEditorTextClipRect(layout.componentPrefixRects[componentIndex], resolvedMetrics.prefixFontSize)); const std::string& componentLabel = spec.componentLabels[componentIndex]; const float prefixTextX = layout.componentPrefixRects[componentIndex].x + ClampNonNegative( (layout.componentPrefixRects[componentIndex].width - - ApproximateTextWidth(metrics.prefixFontSize, componentLabel.size())) * + ApproximateTextWidth(resolvedMetrics.prefixFontSize, componentLabel.size())) * 0.5f) + - metrics.prefixTextInsetX; + resolvedMetrics.prefixTextInsetX; drawList.AddText( UIPoint( prefixTextX, ResolveUIEditorTextTop( layout.componentPrefixRects[componentIndex], - metrics.prefixFontSize, - metrics.prefixTextInsetY)), + resolvedMetrics.prefixFontSize, + resolvedMetrics.prefixTextInsetY)), componentLabel, - ResolveAxisColor(palette, componentIndex), - metrics.prefixFontSize); + ResolveAxisColor(resolvedPalette, componentIndex), + resolvedMetrics.prefixFontSize); drawList.PopClipRect(); drawList.PushClipRect( - ResolveUIEditorTextClipRect(layout.componentValueRects[componentIndex], metrics.valueFontSize)); + ResolveUIEditorTextClipRect(layout.componentValueRects[componentIndex], resolvedMetrics.valueFontSize)); drawList.AddText( UIPoint( - layout.componentValueRects[componentIndex].x + metrics.valueTextInsetX, + layout.componentValueRects[componentIndex].x + resolvedMetrics.valueTextInsetX, ResolveUIEditorTextTop( layout.componentValueRects[componentIndex], - metrics.valueFontSize, - metrics.valueTextInsetY)), + resolvedMetrics.valueFontSize, + resolvedMetrics.valueTextInsetY)), state.editing && state.selectedComponentIndex == componentIndex ? state.displayTexts[componentIndex] : FormatUIEditorVector3FieldComponentValue(spec, componentIndex), - spec.readOnly ? palette.readOnlyValueColor : palette.valueColor, - metrics.valueFontSize); + spec.readOnly ? resolvedPalette.readOnlyValueColor : resolvedPalette.valueColor, + resolvedMetrics.valueFontSize); drawList.PopClipRect(); } } @@ -275,9 +386,11 @@ void AppendUIEditorVector3Field( const UIEditorVector3FieldState& state, const UIEditorVector3FieldPalette& palette, const UIEditorVector3FieldMetrics& metrics) { - const UIEditorVector3FieldLayout layout = BuildUIEditorVector3FieldLayout(bounds, spec, metrics); - AppendUIEditorVector3FieldBackground(drawList, layout, spec, state, palette, metrics); - AppendUIEditorVector3FieldForeground(drawList, layout, spec, state, palette, metrics); + const UIEditorVector3FieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorVector3FieldMetrics resolvedMetrics = ResolveMetrics(metrics); + const UIEditorVector3FieldLayout layout = BuildUIEditorVector3FieldLayout(bounds, spec, resolvedMetrics); + AppendUIEditorVector3FieldBackground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); + AppendUIEditorVector3FieldForeground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); } } // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/src/Core/UIEditorVector3FieldInteraction.cpp b/new_editor/src/Fields/UIEditorVector3FieldInteraction.cpp similarity index 99% rename from new_editor/src/Core/UIEditorVector3FieldInteraction.cpp rename to new_editor/src/Fields/UIEditorVector3FieldInteraction.cpp index 9a3b2ad6..61c70d79 100644 --- a/new_editor/src/Core/UIEditorVector3FieldInteraction.cpp +++ b/new_editor/src/Fields/UIEditorVector3FieldInteraction.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/new_editor/src/Widgets/UIEditorVector4Field.cpp b/new_editor/src/Fields/UIEditorVector4Field.cpp similarity index 50% rename from new_editor/src/Widgets/UIEditorVector4Field.cpp rename to new_editor/src/Fields/UIEditorVector4Field.cpp index 47c778c5..07a06efe 100644 --- a/new_editor/src/Widgets/UIEditorVector4Field.cpp +++ b/new_editor/src/Fields/UIEditorVector4Field.cpp @@ -1,7 +1,7 @@ -#include +#include #include -#include +#include #include #include @@ -18,6 +18,97 @@ float ClampNonNegative(float value) { return (std::max)(0.0f, value); } +UIEditorVector4FieldMetrics ResolveMetrics(const UIEditorVector4FieldMetrics& metrics) { + const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); + + UIEditorVector4FieldMetrics resolved = metrics; + if (AreUIEditorFieldMetricsEqual(metrics.controlTrailingInset, 8.0f)) { + resolved.controlTrailingInset = tokens.controlTrailingInset; + } + if (AreUIEditorFieldMetricsEqual(metrics.componentMinWidth, 72.0f)) { + resolved.componentMinWidth = tokens.vectorComponentMinWidth; + } + if (AreUIEditorFieldMetricsEqual(metrics.componentPrefixWidth, 9.0f)) { + resolved.componentPrefixWidth = tokens.vectorPrefixWidth; + } + if (AreUIEditorFieldMetricsEqual(metrics.componentLabelGap, 4.0f)) { + resolved.componentLabelGap = tokens.vectorPrefixGap; + } + + return resolved; +} + +UIEditorVector4FieldPalette ResolvePalette(const UIEditorVector4FieldPalette& palette) { + const auto& tokens = GetUIEditorInspectorFieldStyleTokens(); + + UIEditorVector4FieldPalette resolved = palette; + if (AreUIEditorFieldColorsEqual(palette.rowHoverColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.rowHoverColor = tokens.rowHoverColor; + } + if (AreUIEditorFieldColorsEqual(palette.rowActiveColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.rowActiveColor = tokens.rowActiveColor; + } + if (AreUIEditorFieldColorsEqual(palette.componentColor, ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f))) { + resolved.componentColor = tokens.controlColor; + } + if (AreUIEditorFieldColorsEqual( + palette.componentHoverColor, + ::XCEngine::UI::UIColor(0.21f, 0.21f, 0.21f, 1.0f))) { + resolved.componentHoverColor = tokens.controlHoverColor; + } + if (AreUIEditorFieldColorsEqual( + palette.componentEditingColor, + ::XCEngine::UI::UIColor(0.24f, 0.24f, 0.24f, 1.0f))) { + resolved.componentEditingColor = tokens.controlEditingColor; + } + if (AreUIEditorFieldColorsEqual(palette.readOnlyColor, ::XCEngine::UI::UIColor(0.17f, 0.17f, 0.17f, 1.0f))) { + resolved.readOnlyColor = tokens.controlReadOnlyColor; + } + if (AreUIEditorFieldColorsEqual( + palette.componentBorderColor, + ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f))) { + resolved.componentBorderColor = tokens.controlBorderColor; + } + if (AreUIEditorFieldColorsEqual( + palette.componentFocusedBorderColor, + ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f))) { + resolved.componentFocusedBorderColor = tokens.controlFocusedBorderColor; + } + if (AreUIEditorFieldColorsEqual(palette.prefixColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.prefixColor = tokens.prefixColor; + } + if (AreUIEditorFieldColorsEqual( + palette.prefixBorderColor, + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.prefixBorderColor = tokens.prefixBorderColor; + } + if (AreUIEditorFieldColorsEqual(palette.labelColor, ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f))) { + resolved.labelColor = tokens.labelColor; + } + if (AreUIEditorFieldColorsEqual(palette.valueColor, ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f))) { + resolved.valueColor = tokens.valueColor; + } + if (AreUIEditorFieldColorsEqual( + palette.readOnlyValueColor, + ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f))) { + resolved.readOnlyValueColor = tokens.readOnlyValueColor; + } + if (AreUIEditorFieldColorsEqual(palette.axisXColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { + resolved.axisXColor = tokens.axisXColor; + } + if (AreUIEditorFieldColorsEqual(palette.axisYColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { + resolved.axisYColor = tokens.axisYColor; + } + if (AreUIEditorFieldColorsEqual(palette.axisZColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { + resolved.axisZColor = tokens.axisZColor; + } + if (AreUIEditorFieldColorsEqual(palette.axisWColor, ::XCEngine::UI::UIColor(0.67f, 0.67f, 0.67f, 1.0f))) { + resolved.axisWColor = tokens.axisWColor; + } + + return resolved; +} + float ApproximateTextWidth(float fontSize, std::size_t characterCount) { return fontSize * 0.55f * static_cast(characterCount); } @@ -126,20 +217,22 @@ UIEditorVector4FieldLayout BuildUIEditorVector4FieldLayout( const UIRect& bounds, const UIEditorVector4FieldSpec&, const UIEditorVector4FieldMetrics& metrics) { - const float requiredControlWidth = metrics.componentMinWidth * 4.0f + metrics.componentGap * 3.0f; + const UIEditorVector4FieldMetrics resolvedMetrics = ResolveMetrics(metrics); + const float requiredControlWidth = + resolvedMetrics.componentMinWidth * 4.0f + resolvedMetrics.componentGap * 3.0f; const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout( bounds, requiredControlWidth, UIEditorFieldRowLayoutMetrics { - metrics.rowHeight, - metrics.horizontalPadding, - metrics.labelControlGap, - metrics.controlColumnStart, - metrics.controlTrailingInset, - metrics.controlInsetY, + resolvedMetrics.rowHeight, + resolvedMetrics.horizontalPadding, + resolvedMetrics.labelControlGap, + resolvedMetrics.controlColumnStart, + resolvedMetrics.controlTrailingInset, + resolvedMetrics.controlInsetY, }); const float componentWidth = - ClampNonNegative((hostLayout.controlRect.width - metrics.componentGap * 3.0f) / 4.0f); + ClampNonNegative((hostLayout.controlRect.width - resolvedMetrics.componentGap * 3.0f) / 4.0f); UIEditorVector4FieldLayout layout = {}; layout.bounds = hostLayout.bounds; @@ -148,10 +241,11 @@ UIEditorVector4FieldLayout BuildUIEditorVector4FieldLayout( for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { const float componentX = - layout.controlRect.x + (componentWidth + metrics.componentGap) * static_cast(componentIndex); + layout.controlRect.x + + (componentWidth + resolvedMetrics.componentGap) * static_cast(componentIndex); const UIRect componentRect(componentX, layout.controlRect.y, componentWidth, layout.controlRect.height); - const float prefixWidth = (std::min)(metrics.componentPrefixWidth, componentRect.width); - const float labelGap = ClampNonNegative(metrics.componentLabelGap); + const float prefixWidth = (std::min)(resolvedMetrics.componentPrefixWidth, componentRect.width); + const float labelGap = ClampNonNegative(resolvedMetrics.componentLabelGap); const float valueX = componentRect.x + prefixWidth + labelGap; layout.componentRects[componentIndex] = componentRect; layout.componentPrefixRects[componentIndex] = @@ -188,27 +282,45 @@ void AppendUIEditorVector4FieldBackground( const UIEditorVector4FieldState& state, const UIEditorVector4FieldPalette& palette, const UIEditorVector4FieldMetrics& metrics) { - if (ResolveRowFillColor(state, palette).a > 0.0f) { - drawList.AddFilledRect(layout.bounds, ResolveRowFillColor(state, palette), metrics.cornerRounding); + const UIEditorVector4FieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorVector4FieldMetrics resolvedMetrics = ResolveMetrics(metrics); + if (ResolveRowFillColor(state, resolvedPalette).a > 0.0f) { + drawList.AddFilledRect( + layout.bounds, + ResolveRowFillColor(state, resolvedPalette), + resolvedMetrics.cornerRounding); } - if ((state.focused ? palette.focusedBorderColor.a : palette.borderColor.a) > 0.0f) { + if ((state.focused ? resolvedPalette.focusedBorderColor.a : resolvedPalette.borderColor.a) > 0.0f) { drawList.AddRectOutline( layout.bounds, - state.focused ? palette.focusedBorderColor : palette.borderColor, - state.focused ? metrics.focusedBorderThickness : metrics.borderThickness, - metrics.cornerRounding); + state.focused ? resolvedPalette.focusedBorderColor : resolvedPalette.borderColor, + state.focused ? resolvedMetrics.focusedBorderThickness : resolvedMetrics.borderThickness, + resolvedMetrics.cornerRounding); } for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { + if (resolvedPalette.prefixColor.a > 0.0f) { + drawList.AddFilledRect( + layout.componentPrefixRects[componentIndex], + resolvedPalette.prefixColor, + resolvedMetrics.componentRounding); + } + if (resolvedPalette.prefixBorderColor.a > 0.0f) { + drawList.AddRectOutline( + layout.componentPrefixRects[componentIndex], + resolvedPalette.prefixBorderColor, + resolvedMetrics.borderThickness, + resolvedMetrics.componentRounding); + } drawList.AddFilledRect( layout.componentValueRects[componentIndex], - ResolveComponentFillColor(spec, state, palette, componentIndex), - metrics.componentRounding); + ResolveComponentFillColor(spec, state, resolvedPalette, componentIndex), + resolvedMetrics.componentRounding); drawList.AddRectOutline( layout.componentValueRects[componentIndex], - ResolveComponentBorderColor(state, palette, componentIndex), - metrics.borderThickness, - metrics.componentRounding); + ResolveComponentBorderColor(state, resolvedPalette, componentIndex), + resolvedMetrics.borderThickness, + resolvedMetrics.componentRounding); } } @@ -219,53 +331,55 @@ void AppendUIEditorVector4FieldForeground( const UIEditorVector4FieldState& state, const UIEditorVector4FieldPalette& palette, const UIEditorVector4FieldMetrics& metrics) { - drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, metrics.labelFontSize)); + const UIEditorVector4FieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorVector4FieldMetrics resolvedMetrics = ResolveMetrics(metrics); + drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, resolvedMetrics.labelFontSize)); drawList.AddText( UIPoint( layout.labelRect.x, - ResolveUIEditorTextTop(layout.labelRect, metrics.labelFontSize, metrics.labelTextInsetY)), + ResolveUIEditorTextTop(layout.labelRect, resolvedMetrics.labelFontSize, resolvedMetrics.labelTextInsetY)), spec.label, - palette.labelColor, - metrics.labelFontSize); + resolvedPalette.labelColor, + resolvedMetrics.labelFontSize); drawList.PopClipRect(); for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { drawList.PushClipRect( - ResolveUIEditorTextClipRect(layout.componentPrefixRects[componentIndex], metrics.prefixFontSize)); + ResolveUIEditorTextClipRect(layout.componentPrefixRects[componentIndex], resolvedMetrics.prefixFontSize)); const std::string& componentLabel = spec.componentLabels[componentIndex]; const float prefixTextX = layout.componentPrefixRects[componentIndex].x + ClampNonNegative( (layout.componentPrefixRects[componentIndex].width - - ApproximateTextWidth(metrics.prefixFontSize, componentLabel.size())) * + ApproximateTextWidth(resolvedMetrics.prefixFontSize, componentLabel.size())) * 0.5f) + - metrics.prefixTextInsetX; + resolvedMetrics.prefixTextInsetX; drawList.AddText( UIPoint( prefixTextX, ResolveUIEditorTextTop( layout.componentPrefixRects[componentIndex], - metrics.prefixFontSize, - metrics.prefixTextInsetY)), + resolvedMetrics.prefixFontSize, + resolvedMetrics.prefixTextInsetY)), componentLabel, - ResolveAxisColor(palette, componentIndex), - metrics.prefixFontSize); + ResolveAxisColor(resolvedPalette, componentIndex), + resolvedMetrics.prefixFontSize); drawList.PopClipRect(); drawList.PushClipRect( - ResolveUIEditorTextClipRect(layout.componentValueRects[componentIndex], metrics.valueFontSize)); + ResolveUIEditorTextClipRect(layout.componentValueRects[componentIndex], resolvedMetrics.valueFontSize)); drawList.AddText( UIPoint( - layout.componentValueRects[componentIndex].x + metrics.valueTextInsetX, + layout.componentValueRects[componentIndex].x + resolvedMetrics.valueTextInsetX, ResolveUIEditorTextTop( layout.componentValueRects[componentIndex], - metrics.valueFontSize, - metrics.valueTextInsetY)), + resolvedMetrics.valueFontSize, + resolvedMetrics.valueTextInsetY)), state.editing && state.selectedComponentIndex == componentIndex ? state.displayTexts[componentIndex] : FormatUIEditorVector4FieldComponentValue(spec, componentIndex), - spec.readOnly ? palette.readOnlyValueColor : palette.valueColor, - metrics.valueFontSize); + spec.readOnly ? resolvedPalette.readOnlyValueColor : resolvedPalette.valueColor, + resolvedMetrics.valueFontSize); drawList.PopClipRect(); } } @@ -277,9 +391,11 @@ void AppendUIEditorVector4Field( const UIEditorVector4FieldState& state, const UIEditorVector4FieldPalette& palette, const UIEditorVector4FieldMetrics& metrics) { - const UIEditorVector4FieldLayout layout = BuildUIEditorVector4FieldLayout(bounds, spec, metrics); - AppendUIEditorVector4FieldBackground(drawList, layout, spec, state, palette, metrics); - AppendUIEditorVector4FieldForeground(drawList, layout, spec, state, palette, metrics); + const UIEditorVector4FieldPalette resolvedPalette = ResolvePalette(palette); + const UIEditorVector4FieldMetrics resolvedMetrics = ResolveMetrics(metrics); + const UIEditorVector4FieldLayout layout = BuildUIEditorVector4FieldLayout(bounds, spec, resolvedMetrics); + AppendUIEditorVector4FieldBackground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); + AppendUIEditorVector4FieldForeground(drawList, layout, spec, state, resolvedPalette, resolvedMetrics); } } // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/src/Core/UIEditorVector4FieldInteraction.cpp b/new_editor/src/Fields/UIEditorVector4FieldInteraction.cpp similarity index 99% rename from new_editor/src/Core/UIEditorVector4FieldInteraction.cpp rename to new_editor/src/Fields/UIEditorVector4FieldInteraction.cpp index f78678d0..bee7fcaf 100644 --- a/new_editor/src/Core/UIEditorVector4FieldInteraction.cpp +++ b/new_editor/src/Fields/UIEditorVector4FieldInteraction.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/new_editor/src/Core/UIEditorCommandDispatcher.cpp b/new_editor/src/Foundation/UIEditorCommandDispatcher.cpp similarity index 98% rename from new_editor/src/Core/UIEditorCommandDispatcher.cpp rename to new_editor/src/Foundation/UIEditorCommandDispatcher.cpp index c4f03d7f..3f3085ea 100644 --- a/new_editor/src/Core/UIEditorCommandDispatcher.cpp +++ b/new_editor/src/Foundation/UIEditorCommandDispatcher.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/new_editor/src/Core/UIEditorCommandRegistry.cpp b/new_editor/src/Foundation/UIEditorCommandRegistry.cpp similarity index 98% rename from new_editor/src/Core/UIEditorCommandRegistry.cpp rename to new_editor/src/Foundation/UIEditorCommandRegistry.cpp index 10770ebc..b821e442 100644 --- a/new_editor/src/Core/UIEditorCommandRegistry.cpp +++ b/new_editor/src/Foundation/UIEditorCommandRegistry.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Core/UIEditorShortcutManager.cpp b/new_editor/src/Foundation/UIEditorShortcutManager.cpp similarity index 99% rename from new_editor/src/Core/UIEditorShortcutManager.cpp rename to new_editor/src/Foundation/UIEditorShortcutManager.cpp index d106286f..f228658f 100644 --- a/new_editor/src/Core/UIEditorShortcutManager.cpp +++ b/new_editor/src/Foundation/UIEditorShortcutManager.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/new_editor/src/Foundation/UIEditorTheme.cpp b/new_editor/src/Foundation/UIEditorTheme.cpp new file mode 100644 index 00000000..1b5e467e --- /dev/null +++ b/new_editor/src/Foundation/UIEditorTheme.cpp @@ -0,0 +1,69 @@ +#include + +namespace XCEngine::UI::Editor { + +namespace { + +template +const TValue& GetDefaultValue() { + static const TValue value = {}; + return value; +} + +#define XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(Name, Type) \ + const Type& ResolveUIEditor##Name() { \ + return GetDefaultValue(); \ + } + +} // namespace + +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(BoolFieldMetrics, Widgets::UIEditorBoolFieldMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(BoolFieldPalette, Widgets::UIEditorBoolFieldPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(NumberFieldMetrics, Widgets::UIEditorNumberFieldMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(NumberFieldPalette, Widgets::UIEditorNumberFieldPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(TextFieldMetrics, Widgets::UIEditorTextFieldMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(TextFieldPalette, Widgets::UIEditorTextFieldPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(Vector2FieldMetrics, Widgets::UIEditorVector2FieldMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(Vector2FieldPalette, Widgets::UIEditorVector2FieldPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(Vector3FieldMetrics, Widgets::UIEditorVector3FieldMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(Vector3FieldPalette, Widgets::UIEditorVector3FieldPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(Vector4FieldMetrics, Widgets::UIEditorVector4FieldMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(Vector4FieldPalette, Widgets::UIEditorVector4FieldPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(EnumFieldMetrics, Widgets::UIEditorEnumFieldMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(EnumFieldPalette, Widgets::UIEditorEnumFieldPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ColorFieldMetrics, Widgets::UIEditorColorFieldMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ColorFieldPalette, Widgets::UIEditorColorFieldPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ObjectFieldMetrics, Widgets::UIEditorObjectFieldMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ObjectFieldPalette, Widgets::UIEditorObjectFieldPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(AssetFieldMetrics, Widgets::UIEditorAssetFieldMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(AssetFieldPalette, Widgets::UIEditorAssetFieldPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(MenuPopupMetrics, Widgets::UIEditorMenuPopupMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(MenuPopupPalette, Widgets::UIEditorMenuPopupPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ListViewMetrics, Widgets::UIEditorListViewMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ListViewPalette, Widgets::UIEditorListViewPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(TreeViewMetrics, Widgets::UIEditorTreeViewMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(TreeViewPalette, Widgets::UIEditorTreeViewPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ScrollViewMetrics, Widgets::UIEditorScrollViewMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ScrollViewPalette, Widgets::UIEditorScrollViewPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(TabStripMetrics, Widgets::UIEditorTabStripMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(TabStripPalette, Widgets::UIEditorTabStripPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(MenuBarMetrics, Widgets::UIEditorMenuBarMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(MenuBarPalette, Widgets::UIEditorMenuBarPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(StatusBarMetrics, Widgets::UIEditorStatusBarMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(StatusBarPalette, Widgets::UIEditorStatusBarPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(PanelFrameMetrics, Widgets::UIEditorPanelFrameMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(PanelFramePalette, Widgets::UIEditorPanelFramePalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(DockHostMetrics, Widgets::UIEditorDockHostMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(DockHostPalette, Widgets::UIEditorDockHostPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ViewportSlotMetrics, Widgets::UIEditorViewportSlotMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ViewportSlotPalette, Widgets::UIEditorViewportSlotPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ShellComposeMetrics, UIEditorShellComposeMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ShellComposePalette, UIEditorShellComposePalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ShellInteractionMetrics, UIEditorShellInteractionMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ShellInteractionPalette, UIEditorShellInteractionPalette) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(PropertyGridMetrics, Widgets::UIEditorPropertyGridMetrics) +XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(PropertyGridPalette, Widgets::UIEditorPropertyGridPalette) + +#undef XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Core/EditorShellAsset.cpp b/new_editor/src/Shell/EditorShellAsset.cpp similarity index 97% rename from new_editor/src/Core/EditorShellAsset.cpp rename to new_editor/src/Shell/EditorShellAsset.cpp index 428f7639..2c6b2eca 100644 --- a/new_editor/src/Core/EditorShellAsset.cpp +++ b/new_editor/src/Shell/EditorShellAsset.cpp @@ -155,8 +155,6 @@ UIEditorShellInteractionDefinition BuildDefaultShellDefinition( EditorShellAsset BuildDefaultEditorShellAsset(const std::filesystem::path& repoRoot) { EditorShellAsset asset = {}; - asset.documentPath = (repoRoot / "new_editor/ui/views/editor_shell.xcui").lexically_normal(); - asset.themePath = (repoRoot / "new_editor/ui/themes/editor_shell.xctheme").lexically_normal(); asset.captureRootPath = (repoRoot / "new_editor/captures").lexically_normal(); asset.panelRegistry = BuildDefaultEditorShellPanelRegistry(); asset.workspace = BuildDefaultEditorShellWorkspaceModel(); diff --git a/new_editor/src/Core/EditorShellAsset.h b/new_editor/src/Shell/EditorShellAsset.h similarity index 85% rename from new_editor/src/Core/EditorShellAsset.h rename to new_editor/src/Shell/EditorShellAsset.h index f8f02f1b..32304367 100644 --- a/new_editor/src/Core/EditorShellAsset.h +++ b/new_editor/src/Shell/EditorShellAsset.h @@ -1,10 +1,10 @@ #pragma once -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include @@ -21,7 +21,6 @@ struct EditorShellShortcutAsset { struct EditorShellAsset { std::string screenId = "editor.shell"; std::filesystem::path documentPath = {}; - std::filesystem::path themePath = {}; std::filesystem::path captureRootPath = {}; UIEditorPanelRegistry panelRegistry = {}; UIEditorWorkspaceModel workspace = {}; diff --git a/new_editor/src/Widgets/UIEditorDockHost.cpp b/new_editor/src/Shell/UIEditorDockHost.cpp similarity index 94% rename from new_editor/src/Widgets/UIEditorDockHost.cpp rename to new_editor/src/Shell/UIEditorDockHost.cpp index f15e8d84..71b609f5 100644 --- a/new_editor/src/Widgets/UIEditorDockHost.cpp +++ b/new_editor/src/Shell/UIEditorDockHost.cpp @@ -1,4 +1,4 @@ -#include +#include #include @@ -23,6 +23,14 @@ using ::XCEngine::UI::Layout::UITabStripMeasureItem; using ::XCEngine::UI::Layout::UILayoutAxis; using ::XCEngine::UI::Widgets::ExpandUISplitterHandleHitRect; +constexpr std::string_view kStandalonePanelPlaceholder = "DockHost standalone panel"; +constexpr std::string_view kStandalonePanelActiveFooter = "Active panel"; +constexpr std::string_view kStandalonePanelInactiveFooter = "Panel placeholder"; +constexpr std::string_view kStandalonePanelActiveDetail = "Active panel body is ready for composition"; +constexpr std::string_view kStandalonePanelIdleDetail = "Select the header or body to activate this panel"; +constexpr std::string_view kTabContentPlaceholder = "DockHost tab content placeholder"; +constexpr std::string_view kTabContentDetailPrefix = "Selected panel: "; + struct DockMeasureResult { bool visible = false; UISize minimumSize = {}; @@ -60,6 +68,18 @@ bool UsesExternalBodyPresentation( panelId) != options.externalBodyPanelIds.end(); } +const UIEditorDockHostTabStripVisualState* FindTabStripVisualState( + const UIEditorDockHostState& state, + std::string_view nodeId) { + for (const UIEditorDockHostTabStripVisualState& entry : state.tabStripStates) { + if (entry.nodeId == nodeId) { + return &entry; + } + } + + return nullptr; +} + bool IsPanelOpenAndVisible( const UIEditorWorkspaceSession& session, std::string_view panelId) { @@ -82,6 +102,7 @@ UIEditorPanelFrameMetrics BuildTabContentFrameMetrics( frameMetrics.actionButtonExtent = 0.0f; frameMetrics.actionInsetX = 0.0f; frameMetrics.actionGap = 0.0f; + frameMetrics.cornerRounding = (std::max)(frameMetrics.cornerRounding - 1.0f, 0.0f); return frameMetrics; } @@ -226,10 +247,14 @@ UIEditorTabStripState BuildTabStripState( std::string_view nodeId, std::size_t selectedIndex) { UIEditorTabStripState tabState = {}; + if (const auto* visualState = FindTabStripVisualState(state, nodeId)) { + tabState = visualState->state; + } else { + tabState.focused = state.focused; + } tabState.selectedIndex = selectedIndex; - tabState.focused = state.focused; - if (state.hoveredTarget.nodeId != nodeId) { + if (FindTabStripVisualState(state, nodeId) != nullptr || state.hoveredTarget.nodeId != nodeId) { return tabState; } @@ -753,16 +778,15 @@ void AppendUIEditorDockHostForeground( const UIEditorDockHostPalette& palette, const UIEditorDockHostMetrics& metrics) { for (const UIEditorDockHostPanelLayout& panel : layout.panels) { - const UIEditorPanelFrameText text = { - panel.title, - panel.panelId, - panel.active ? "Active Panel" : "Panel Placeholder" - }; AppendUIEditorPanelFrameForeground( drawList, panel.frameLayout, panel.frameState, - text, + UIEditorPanelFrameText{ + panel.title, + panel.panelId, + panel.active ? kStandalonePanelActiveFooter : kStandalonePanelInactiveFooter + }, palette.panelFramePalette, metrics.panelFrameMetrics); if (UsesExternalBodyPresentation(options, panel.panelId)) { @@ -772,8 +796,8 @@ void AppendUIEditorDockHostForeground( drawList, panel.frameLayout.bodyRect, panel.title, - "DockHost standalone panel", - panel.active ? "当前面板为 active" : "点击 header/body 可切换 active", + std::string(kStandalonePanelPlaceholder), + panel.active ? std::string(kStandalonePanelActiveDetail) : std::string(kStandalonePanelIdleDetail), palette, metrics); } @@ -821,8 +845,8 @@ void AppendUIEditorDockHostForeground( drawList, tabStack.contentFrameLayout.bodyRect, selectedTitle, - "DockHost tab content placeholder", - "Selected Panel: " + tabStack.selectedPanelId, + std::string(kTabContentPlaceholder), + std::string(kTabContentDetailPrefix) + tabStack.selectedPanelId, palette, metrics); } diff --git a/new_editor/src/Shell/UIEditorDockHostInteraction.cpp b/new_editor/src/Shell/UIEditorDockHostInteraction.cpp new file mode 100644 index 00000000..8d200326 --- /dev/null +++ b/new_editor/src/Shell/UIEditorDockHostInteraction.cpp @@ -0,0 +1,620 @@ +#include + +#include + +#include +#include +#include +#include + +namespace XCEngine::UI::Editor { + +namespace { + +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIRect; +using ::XCEngine::UI::Widgets::BeginUISplitterDrag; +using ::XCEngine::UI::Widgets::EndUISplitterDrag; +using ::XCEngine::UI::Widgets::UpdateUISplitterDrag; +using Widgets::BuildUIEditorDockHostLayout; +using Widgets::FindUIEditorDockHostSplitterLayout; +using Widgets::HitTestUIEditorDockHost; +using Widgets::UIEditorDockHostHitTarget; +using Widgets::UIEditorDockHostHitTargetKind; +using Widgets::UIEditorDockHostTabItemLayout; +using Widgets::UIEditorDockHostTabStackLayout; +using Widgets::UIEditorTabStripHitTargetKind; +using Widgets::UIEditorTabStripItem; + +struct DockHostTabStripEventResult { + bool consumed = false; + bool commandRequested = false; + UIEditorWorkspaceCommandKind commandKind = UIEditorWorkspaceCommandKind::ActivatePanel; + std::string panelId = {}; + UIEditorDockHostHitTarget hitTarget = {}; + int priority = 0; +}; + +bool ShouldUsePointerPosition(const UIInputEvent& event) { + switch (event.type) { + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + return true; + default: + return false; + } +} + +bool ShouldDispatchTabStripEvent( + const UIInputEvent& event, + bool splitterActive) { + if (splitterActive && event.type != UIInputEventType::FocusLost) { + return false; + } + + switch (event.type) { + case UIInputEventType::FocusLost: + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + case UIInputEventType::PointerLeave: + case UIInputEventType::PointerButtonDown: + case UIInputEventType::PointerButtonUp: + case UIInputEventType::KeyDown: + return true; + default: + return false; + } +} + +UIEditorWorkspaceLayoutOperationResult ApplySplitRatio( + UIEditorWorkspaceController& controller, + std::string_view nodeId, + float splitRatio) { + return controller.SetSplitRatio(nodeId, splitRatio); +} + +UIEditorWorkspaceCommandResult DispatchPanelCommand( + UIEditorWorkspaceController& controller, + UIEditorWorkspaceCommandKind kind, + std::string panelId) { + UIEditorWorkspaceCommand command = {}; + command.kind = kind; + command.panelId = std::move(panelId); + return controller.Dispatch(command); +} + +UIEditorDockHostTabStripInteractionEntry& FindOrCreateTabStripInteractionEntry( + UIEditorDockHostInteractionState& state, + std::string_view nodeId) { + for (UIEditorDockHostTabStripInteractionEntry& entry : state.tabStripInteractions) { + if (entry.nodeId == nodeId) { + return entry; + } + } + + state.tabStripInteractions.push_back({}); + UIEditorDockHostTabStripInteractionEntry& entry = state.tabStripInteractions.back(); + entry.nodeId = std::string(nodeId); + return entry; +} + +void PruneTabStripInteractionEntries( + UIEditorDockHostInteractionState& state, + const Widgets::UIEditorDockHostLayout& layout) { + const auto isVisibleNodeId = [&layout](std::string_view nodeId) { + return std::find_if( + layout.tabStacks.begin(), + layout.tabStacks.end(), + [nodeId](const UIEditorDockHostTabStackLayout& tabStack) { + return tabStack.nodeId == nodeId; + }) != layout.tabStacks.end(); + }; + + state.tabStripInteractions.erase( + std::remove_if( + state.tabStripInteractions.begin(), + state.tabStripInteractions.end(), + [&isVisibleNodeId](const UIEditorDockHostTabStripInteractionEntry& entry) { + return !isVisibleNodeId(entry.nodeId); + }), + state.tabStripInteractions.end()); + + state.dockHostState.tabStripStates.erase( + std::remove_if( + state.dockHostState.tabStripStates.begin(), + state.dockHostState.tabStripStates.end(), + [&isVisibleNodeId](const Widgets::UIEditorDockHostTabStripVisualState& entry) { + return !isVisibleNodeId(entry.nodeId); + }), + state.dockHostState.tabStripStates.end()); +} + +void SyncDockHostTabStripVisualStates(UIEditorDockHostInteractionState& state) { + state.dockHostState.tabStripStates.clear(); + state.dockHostState.tabStripStates.reserve(state.tabStripInteractions.size()); + for (const UIEditorDockHostTabStripInteractionEntry& entry : state.tabStripInteractions) { + Widgets::UIEditorDockHostTabStripVisualState visualState = {}; + visualState.nodeId = entry.nodeId; + visualState.state = entry.state.tabStripState; + state.dockHostState.tabStripStates.push_back(std::move(visualState)); + } +} + +bool HasFocusedTabStrip(const UIEditorDockHostInteractionState& state) { + return std::find_if( + state.tabStripInteractions.begin(), + state.tabStripInteractions.end(), + [](const UIEditorDockHostTabStripInteractionEntry& entry) { + return entry.state.tabStripState.focused; + }) != state.tabStripInteractions.end(); +} + +std::vector BuildTabStripItems( + const UIEditorDockHostTabStackLayout& tabStack) { + std::vector items = {}; + items.reserve(tabStack.items.size()); + for (const UIEditorDockHostTabItemLayout& itemLayout : tabStack.items) { + UIEditorTabStripItem item = {}; + item.tabId = itemLayout.panelId; + item.title = itemLayout.title; + item.closable = itemLayout.closable; + items.push_back(std::move(item)); + } + + return items; +} + +UIEditorDockHostHitTarget MapTabStripHitTarget( + const UIEditorDockHostTabStackLayout& tabStack, + const UIEditorTabStripInteractionResult& result) { + UIEditorDockHostHitTarget target = {}; + target.nodeId = tabStack.nodeId; + target.index = result.hitTarget.index; + + switch (result.hitTarget.kind) { + case UIEditorTabStripHitTargetKind::HeaderBackground: + target.kind = UIEditorDockHostHitTargetKind::TabStripBackground; + break; + case UIEditorTabStripHitTargetKind::Tab: + target.kind = UIEditorDockHostHitTargetKind::Tab; + if (result.hitTarget.index < tabStack.items.size()) { + target.panelId = tabStack.items[result.hitTarget.index].panelId; + } + break; + case UIEditorTabStripHitTargetKind::CloseButton: + target.kind = UIEditorDockHostHitTargetKind::TabCloseButton; + if (result.hitTarget.index < tabStack.items.size()) { + target.panelId = tabStack.items[result.hitTarget.index].panelId; + } + break; + default: + break; + } + + return target; +} + +int ResolveTabStripPriority(const UIEditorTabStripInteractionResult& result) { + if (result.closeRequested) { + return 4; + } + + if (result.selectionChanged || result.keyboardNavigated) { + return 3; + } + + if (result.consumed || result.hitTarget.kind != UIEditorTabStripHitTargetKind::None) { + return 2; + } + + return 0; +} + +DockHostTabStripEventResult ProcessTabStripEvent( + UIEditorDockHostInteractionState& state, + const Widgets::UIEditorDockHostLayout& layout, + const UIInputEvent& event, + const Widgets::UIEditorDockHostMetrics& metrics) { + DockHostTabStripEventResult resolved = {}; + + for (const UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { + UIEditorDockHostTabStripInteractionEntry& entry = + FindOrCreateTabStripInteractionEntry(state, tabStack.nodeId); + std::string selectedTabId = tabStack.selectedPanelId; + const std::vector items = BuildTabStripItems(tabStack); + const UIEditorTabStripInteractionFrame frame = UpdateUIEditorTabStripInteraction( + entry.state, + selectedTabId, + tabStack.bounds, + items, + { event }, + metrics.tabStripMetrics); + + const int priority = ResolveTabStripPriority(frame.result); + if (priority < resolved.priority) { + continue; + } + + resolved.hitTarget = MapTabStripHitTarget(tabStack, frame.result); + if ((frame.result.closeRequested && !frame.result.closedTabId.empty()) || + (event.type == UIInputEventType::PointerButtonUp && + frame.result.consumed && + resolved.hitTarget.kind == UIEditorDockHostHitTargetKind::TabCloseButton && + !resolved.hitTarget.panelId.empty())) { + resolved.commandRequested = true; + resolved.commandKind = UIEditorWorkspaceCommandKind::ClosePanel; + resolved.panelId = + !frame.result.closedTabId.empty() + ? frame.result.closedTabId + : resolved.hitTarget.panelId; + } else if ((frame.result.selectionChanged || + frame.result.keyboardNavigated || + (event.type == UIInputEventType::PointerButtonUp && + frame.result.consumed && + resolved.hitTarget.kind == UIEditorDockHostHitTargetKind::Tab)) && + (!frame.result.selectedTabId.empty() || !resolved.hitTarget.panelId.empty())) { + resolved.commandRequested = true; + resolved.commandKind = UIEditorWorkspaceCommandKind::ActivatePanel; + resolved.panelId = + !frame.result.selectedTabId.empty() + ? frame.result.selectedTabId + : resolved.hitTarget.panelId; + } else if (priority == 0) { + continue; + } else { + resolved.commandRequested = false; + resolved.panelId.clear(); + } + + resolved.consumed = frame.result.consumed; + resolved.priority = priority; + } + + SyncDockHostTabStripVisualStates(state); + return resolved; +} + +void SyncHoverTarget( + UIEditorDockHostInteractionState& state, + const Widgets::UIEditorDockHostLayout& layout) { + if (state.splitterDragState.active) { + state.dockHostState.hoveredTarget = { + UIEditorDockHostHitTargetKind::SplitterHandle, + state.dockHostState.activeSplitterNodeId, + {}, + Widgets::UIEditorTabStripInvalidIndex + }; + return; + } + + if (!state.hasPointerPosition) { + state.dockHostState.hoveredTarget = {}; + return; + } + + state.dockHostState.hoveredTarget = + HitTestUIEditorDockHost(layout, state.pointerPosition); +} + +} // namespace + +UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( + UIEditorDockHostInteractionState& state, + UIEditorWorkspaceController& controller, + const UIRect& bounds, + const std::vector& inputEvents, + const Widgets::UIEditorDockHostMetrics& metrics) { + SyncDockHostTabStripVisualStates(state); + Widgets::UIEditorDockHostLayout layout = BuildUIEditorDockHostLayout( + bounds, + controller.GetPanelRegistry(), + controller.GetWorkspace(), + controller.GetSession(), + state.dockHostState, + metrics); + PruneTabStripInteractionEntries(state, layout); + SyncDockHostTabStripVisualStates(state); + layout = BuildUIEditorDockHostLayout( + bounds, + controller.GetPanelRegistry(), + controller.GetWorkspace(), + controller.GetSession(), + state.dockHostState, + metrics); + SyncHoverTarget(state, layout); + + UIEditorDockHostInteractionResult interactionResult = {}; + for (const UIInputEvent& event : inputEvents) { + if (ShouldUsePointerPosition(event)) { + state.pointerPosition = event.position; + state.hasPointerPosition = true; + } else if (event.type == UIInputEventType::PointerLeave) { + state.hasPointerPosition = false; + } + + UIEditorDockHostInteractionResult eventResult = {}; + const DockHostTabStripEventResult tabStripResult = + ShouldDispatchTabStripEvent(event, state.splitterDragState.active) + ? ProcessTabStripEvent(state, layout, event, metrics) + : DockHostTabStripEventResult {}; + + switch (event.type) { + case UIInputEventType::FocusGained: + state.dockHostState.focused = true; + break; + + case UIInputEventType::FocusLost: + state.dockHostState.focused = false; + state.dockHostState.hoveredTarget = {}; + if (state.splitterDragState.active) { + EndUISplitterDrag(state.splitterDragState); + state.dockHostState.activeSplitterNodeId.clear(); + eventResult.consumed = true; + eventResult.releasePointerCapture = true; + } + break; + + case UIInputEventType::PointerMove: + case UIInputEventType::PointerEnter: + if (state.splitterDragState.active) { + const auto* splitter = FindUIEditorDockHostSplitterLayout( + layout, + state.dockHostState.activeSplitterNodeId); + if (splitter != nullptr) { + ::XCEngine::UI::Layout::UISplitterLayoutResult draggedLayout = {}; + if (UpdateUISplitterDrag( + state.splitterDragState, + state.pointerPosition, + draggedLayout)) { + eventResult.layoutResult = ApplySplitRatio( + controller, + state.dockHostState.activeSplitterNodeId, + draggedLayout.splitRatio); + eventResult.layoutChanged = + eventResult.layoutResult.status == + UIEditorWorkspaceLayoutOperationStatus::Changed; + } + eventResult.consumed = true; + eventResult.hitTarget.kind = UIEditorDockHostHitTargetKind::SplitterHandle; + eventResult.hitTarget.nodeId = state.dockHostState.activeSplitterNodeId; + } + } + break; + + case UIInputEventType::PointerLeave: + if (!state.splitterDragState.active) { + state.dockHostState.hoveredTarget = {}; + } + if (!HasFocusedTabStrip(state)) { + state.dockHostState.focused = false; + } + break; + + case UIInputEventType::PointerButtonDown: + if (event.pointerButton != ::XCEngine::UI::UIPointerButton::Left) { + break; + } + + if (state.dockHostState.hoveredTarget.kind == + UIEditorDockHostHitTargetKind::SplitterHandle) { + const auto* splitter = FindUIEditorDockHostSplitterLayout( + layout, + state.dockHostState.hoveredTarget.nodeId); + if (splitter != nullptr && + BeginUISplitterDrag( + 1u, + splitter->axis == UIEditorWorkspaceSplitAxis::Horizontal + ? ::XCEngine::UI::Layout::UILayoutAxis::Horizontal + : ::XCEngine::UI::Layout::UILayoutAxis::Vertical, + splitter->bounds, + splitter->splitterLayout, + splitter->constraints, + splitter->metrics, + state.pointerPosition, + state.splitterDragState)) { + state.dockHostState.activeSplitterNodeId = splitter->nodeId; + state.dockHostState.focused = true; + eventResult.consumed = true; + eventResult.requestPointerCapture = true; + eventResult.hitTarget = state.dockHostState.hoveredTarget; + eventResult.activeSplitterNodeId = splitter->nodeId; + } + } else { + if (tabStripResult.priority > 0) { + state.dockHostState.focused = true; + eventResult.consumed = tabStripResult.consumed; + eventResult.hitTarget = tabStripResult.hitTarget; + } else { + state.dockHostState.focused = + state.dockHostState.hoveredTarget.kind != + UIEditorDockHostHitTargetKind::None; + eventResult.consumed = state.dockHostState.focused; + eventResult.hitTarget = state.dockHostState.hoveredTarget; + } + } + break; + + case UIInputEventType::PointerButtonUp: + if (event.pointerButton != ::XCEngine::UI::UIPointerButton::Left) { + break; + } + + if (state.splitterDragState.active) { + ::XCEngine::UI::Layout::UISplitterLayoutResult draggedLayout = {}; + if (UpdateUISplitterDrag( + state.splitterDragState, + state.pointerPosition, + draggedLayout)) { + eventResult.layoutResult = ApplySplitRatio( + controller, + state.dockHostState.activeSplitterNodeId, + draggedLayout.splitRatio); + eventResult.layoutChanged = + eventResult.layoutResult.status == + UIEditorWorkspaceLayoutOperationStatus::Changed; + } + EndUISplitterDrag(state.splitterDragState); + eventResult.consumed = true; + eventResult.releasePointerCapture = true; + eventResult.activeSplitterNodeId = state.dockHostState.activeSplitterNodeId; + state.dockHostState.activeSplitterNodeId.clear(); + break; + } + + if (tabStripResult.commandRequested && !tabStripResult.panelId.empty()) { + eventResult.commandResult = DispatchPanelCommand( + controller, + tabStripResult.commandKind, + tabStripResult.panelId); + eventResult.commandExecuted = + eventResult.commandResult.status != + UIEditorWorkspaceCommandStatus::Rejected; + eventResult.consumed = true; + eventResult.hitTarget = tabStripResult.hitTarget; + state.dockHostState.focused = true; + break; + } + + if (tabStripResult.priority > 0) { + eventResult.consumed = tabStripResult.consumed; + eventResult.hitTarget = tabStripResult.hitTarget; + if (eventResult.hitTarget.kind != UIEditorDockHostHitTargetKind::None) { + state.dockHostState.focused = true; + break; + } + } + + eventResult.hitTarget = state.dockHostState.hoveredTarget; + switch (state.dockHostState.hoveredTarget.kind) { + case UIEditorDockHostHitTargetKind::PanelHeader: + case UIEditorDockHostHitTargetKind::PanelBody: + case UIEditorDockHostHitTargetKind::PanelFooter: + eventResult.commandResult = DispatchPanelCommand( + controller, + UIEditorWorkspaceCommandKind::ActivatePanel, + state.dockHostState.hoveredTarget.panelId); + eventResult.commandExecuted = + eventResult.commandResult.status != + UIEditorWorkspaceCommandStatus::Rejected; + eventResult.consumed = true; + state.dockHostState.focused = true; + break; + + case UIEditorDockHostHitTargetKind::PanelCloseButton: + eventResult.commandResult = DispatchPanelCommand( + controller, + UIEditorWorkspaceCommandKind::ClosePanel, + state.dockHostState.hoveredTarget.panelId); + eventResult.commandExecuted = + eventResult.commandResult.status != + UIEditorWorkspaceCommandStatus::Rejected; + eventResult.consumed = true; + state.dockHostState.focused = true; + break; + + case UIEditorDockHostHitTargetKind::Tab: + case UIEditorDockHostHitTargetKind::TabCloseButton: + case UIEditorDockHostHitTargetKind::TabStripBackground: + state.dockHostState.focused = true; + eventResult.consumed = tabStripResult.priority > 0 + ? tabStripResult.consumed + : true; + break; + + case UIEditorDockHostHitTargetKind::None: + default: + if (!HasFocusedTabStrip(state)) { + state.dockHostState.focused = false; + } + break; + } + break; + + case UIInputEventType::KeyDown: + if (tabStripResult.commandRequested && !tabStripResult.panelId.empty()) { + eventResult.commandResult = DispatchPanelCommand( + controller, + tabStripResult.commandKind, + tabStripResult.panelId); + eventResult.commandExecuted = + eventResult.commandResult.status != + UIEditorWorkspaceCommandStatus::Rejected; + eventResult.consumed = true; + eventResult.hitTarget = tabStripResult.hitTarget; + state.dockHostState.focused = true; + } else if (tabStripResult.priority > 0) { + eventResult.consumed = tabStripResult.consumed; + eventResult.hitTarget = tabStripResult.hitTarget; + } + break; + + default: + break; + } + + SyncDockHostTabStripVisualStates(state); + layout = BuildUIEditorDockHostLayout( + bounds, + controller.GetPanelRegistry(), + controller.GetWorkspace(), + controller.GetSession(), + state.dockHostState, + metrics); + PruneTabStripInteractionEntries(state, layout); + SyncDockHostTabStripVisualStates(state); + layout = BuildUIEditorDockHostLayout( + bounds, + controller.GetPanelRegistry(), + controller.GetWorkspace(), + controller.GetSession(), + state.dockHostState, + metrics); + SyncHoverTarget(state, layout); + if (eventResult.hitTarget.kind == UIEditorDockHostHitTargetKind::None) { + eventResult.hitTarget = state.dockHostState.hoveredTarget; + } + + if (eventResult.consumed || + eventResult.commandExecuted || + eventResult.layoutChanged || + eventResult.requestPointerCapture || + eventResult.releasePointerCapture || + eventResult.layoutResult.status != UIEditorWorkspaceLayoutOperationStatus::Rejected || + eventResult.hitTarget.kind != UIEditorDockHostHitTargetKind::None || + !eventResult.activeSplitterNodeId.empty()) { + interactionResult = std::move(eventResult); + } + } + + SyncDockHostTabStripVisualStates(state); + layout = BuildUIEditorDockHostLayout( + bounds, + controller.GetPanelRegistry(), + controller.GetWorkspace(), + controller.GetSession(), + state.dockHostState, + metrics); + PruneTabStripInteractionEntries(state, layout); + SyncDockHostTabStripVisualStates(state); + layout = BuildUIEditorDockHostLayout( + bounds, + controller.GetPanelRegistry(), + controller.GetWorkspace(), + controller.GetSession(), + state.dockHostState, + metrics); + SyncHoverTarget(state, layout); + if (interactionResult.hitTarget.kind == UIEditorDockHostHitTargetKind::None) { + interactionResult.hitTarget = state.dockHostState.hoveredTarget; + } + return { + std::move(layout), + std::move(interactionResult), + state.dockHostState.focused + }; +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Widgets/UIEditorMenuBar.cpp b/new_editor/src/Shell/UIEditorMenuBar.cpp similarity index 93% rename from new_editor/src/Widgets/UIEditorMenuBar.cpp rename to new_editor/src/Shell/UIEditorMenuBar.cpp index 0f61d8f6..31e50bae 100644 --- a/new_editor/src/Widgets/UIEditorMenuBar.cpp +++ b/new_editor/src/Shell/UIEditorMenuBar.cpp @@ -1,4 +1,4 @@ -#include +#include #include @@ -38,6 +38,16 @@ float ResolveLabelTop(const UIRect& rect, const UIEditorMenuBarMetrics& metrics) return rect.y + (std::max)(0.0f, (rect.height - kMenuBarFontSize) * 0.5f) + metrics.labelInsetY; } +bool IsButtonFocused( + const UIEditorMenuBarState& state, + std::size_t index) { + if (!state.focused) { + return false; + } + + return state.openIndex == index || state.hoveredIndex == index; +} + UIColor ResolveButtonFillColor( bool open, bool hovered, @@ -163,14 +173,15 @@ void AppendUIEditorMenuBarBackground( for (std::size_t index = 0; index < layout.buttonRects.size() && index < items.size(); ++index) { const bool open = state.openIndex == index; const bool hovered = state.hoveredIndex == index; + const bool focused = IsButtonFocused(state, index); drawList.AddFilledRect( layout.buttonRects[index], ResolveButtonFillColor(open, hovered, palette), metrics.buttonCornerRounding); drawList.AddRectOutline( layout.buttonRects[index], - ResolveButtonBorderColor(open, state.focused && open, palette), - ResolveButtonBorderThickness(open, state.focused && open, metrics), + ResolveButtonBorderColor(open, focused, palette), + ResolveButtonBorderThickness(open, focused, metrics), metrics.buttonCornerRounding); } } diff --git a/new_editor/src/Core/UIEditorMenuModel.cpp b/new_editor/src/Shell/UIEditorMenuModel.cpp similarity index 99% rename from new_editor/src/Core/UIEditorMenuModel.cpp rename to new_editor/src/Shell/UIEditorMenuModel.cpp index 9cc721f2..73551519 100644 --- a/new_editor/src/Core/UIEditorMenuModel.cpp +++ b/new_editor/src/Shell/UIEditorMenuModel.cpp @@ -1,6 +1,6 @@ -#include +#include -#include +#include #include #include diff --git a/new_editor/src/Widgets/UIEditorMenuPopup.cpp b/new_editor/src/Shell/UIEditorMenuPopup.cpp similarity index 99% rename from new_editor/src/Widgets/UIEditorMenuPopup.cpp rename to new_editor/src/Shell/UIEditorMenuPopup.cpp index bd61751e..a8a2309b 100644 --- a/new_editor/src/Widgets/UIEditorMenuPopup.cpp +++ b/new_editor/src/Shell/UIEditorMenuPopup.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/new_editor/src/Core/UIEditorMenuSession.cpp b/new_editor/src/Shell/UIEditorMenuSession.cpp similarity index 83% rename from new_editor/src/Core/UIEditorMenuSession.cpp rename to new_editor/src/Shell/UIEditorMenuSession.cpp index 5de9e4d6..4adbd3e3 100644 --- a/new_editor/src/Core/UIEditorMenuSession.cpp +++ b/new_editor/src/Shell/UIEditorMenuSession.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -8,8 +8,8 @@ namespace XCEngine::UI::Editor { namespace { bool AreEquivalentPopupEntries( - const Widgets::UIPopupOverlayEntry& lhs, - const Widgets::UIPopupOverlayEntry& rhs) { + const ::XCEngine::UI::Widgets::UIPopupOverlayEntry& lhs, + const ::XCEngine::UI::Widgets::UIPopupOverlayEntry& rhs) { return lhs.popupId == rhs.popupId && lhs.parentPopupId == rhs.parentPopupId && lhs.anchorRect.x == rhs.anchorRect.x && @@ -50,18 +50,19 @@ void UIEditorMenuSession::Reset() { UIEditorMenuSessionMutationResult UIEditorMenuSession::OpenMenuBarRoot( std::string_view menuId, - Widgets::UIPopupOverlayEntry entry) { + ::XCEngine::UI::Widgets::UIPopupOverlayEntry entry) { return OpenRootMenu(menuId, std::move(entry)); } UIEditorMenuSessionMutationResult UIEditorMenuSession::OpenRootMenu( std::string_view menuId, - Widgets::UIPopupOverlayEntry entry) { + ::XCEngine::UI::Widgets::UIPopupOverlayEntry entry) { if (menuId.empty() || entry.popupId.empty()) { return BuildResult({}); } - const Widgets::UIPopupOverlayEntry* rootPopup = m_popupOverlayModel.GetRootPopup(); + const ::XCEngine::UI::Widgets::UIPopupOverlayEntry* rootPopup = + m_popupOverlayModel.GetRootPopup(); if (rootPopup != nullptr && m_openRootMenuId == menuId && AreEquivalentPopupEntries(*rootPopup, entry)) { @@ -69,7 +70,7 @@ UIEditorMenuSessionMutationResult UIEditorMenuSession::OpenRootMenu( } entry.parentPopupId.clear(); - const Widgets::UIPopupOverlayMutationResult mutation = + const ::XCEngine::UI::Widgets::UIPopupOverlayMutationResult mutation = m_popupOverlayModel.OpenPopup(std::move(entry)); if (mutation.changed) { m_popupStates.clear(); @@ -86,7 +87,7 @@ UIEditorMenuSessionMutationResult UIEditorMenuSession::OpenRootMenu( UIEditorMenuSessionMutationResult UIEditorMenuSession::HoverMenuBarRoot( std::string_view menuId, - Widgets::UIPopupOverlayEntry entry) { + ::XCEngine::UI::Widgets::UIPopupOverlayEntry entry) { if (!HasOpenMenu() || IsMenuOpen(menuId)) { return BuildResult({}); } @@ -96,7 +97,7 @@ UIEditorMenuSessionMutationResult UIEditorMenuSession::HoverMenuBarRoot( UIEditorMenuSessionMutationResult UIEditorMenuSession::HoverSubmenu( std::string_view itemId, - Widgets::UIPopupOverlayEntry entry) { + ::XCEngine::UI::Widgets::UIPopupOverlayEntry entry) { if (!HasOpenMenu() || itemId.empty() || entry.popupId.empty() || @@ -105,14 +106,14 @@ UIEditorMenuSessionMutationResult UIEditorMenuSession::HoverSubmenu( return BuildResult({}); } - const Widgets::UIPopupOverlayEntry* existingPopup = + const ::XCEngine::UI::Widgets::UIPopupOverlayEntry* existingPopup = m_popupOverlayModel.FindPopup(entry.popupId); if (existingPopup != nullptr && existingPopup->parentPopupId == entry.parentPopupId) { return BuildResult({}); } - const Widgets::UIPopupOverlayMutationResult mutation = + const ::XCEngine::UI::Widgets::UIPopupOverlayMutationResult mutation = m_popupOverlayModel.OpenPopup(std::move(entry)); if (mutation.changed) { RemoveClosedPopupStates(mutation.closedPopupIds); @@ -129,8 +130,8 @@ UIEditorMenuSessionMutationResult UIEditorMenuSession::HoverSubmenu( } UIEditorMenuSessionMutationResult UIEditorMenuSession::CloseAll( - Widgets::UIPopupDismissReason dismissReason) { - const Widgets::UIPopupOverlayMutationResult mutation = + ::XCEngine::UI::Widgets::UIPopupDismissReason dismissReason) { + const ::XCEngine::UI::Widgets::UIPopupOverlayMutationResult mutation = m_popupOverlayModel.CloseAll(dismissReason); if (mutation.changed) { RemoveClosedPopupStates(mutation.closedPopupIds); @@ -141,7 +142,7 @@ UIEditorMenuSessionMutationResult UIEditorMenuSession::CloseAll( } UIEditorMenuSessionMutationResult UIEditorMenuSession::DismissFromEscape() { - const Widgets::UIPopupOverlayMutationResult mutation = + const ::XCEngine::UI::Widgets::UIPopupOverlayMutationResult mutation = m_popupOverlayModel.DismissFromEscape(); if (mutation.changed) { RemoveClosedPopupStates(mutation.closedPopupIds); @@ -153,7 +154,7 @@ UIEditorMenuSessionMutationResult UIEditorMenuSession::DismissFromEscape() { UIEditorMenuSessionMutationResult UIEditorMenuSession::DismissFromPointerDown( const UIInputPath& hitPath) { - const Widgets::UIPopupOverlayMutationResult mutation = + const ::XCEngine::UI::Widgets::UIPopupOverlayMutationResult mutation = m_popupOverlayModel.DismissFromPointerDown(hitPath); if (mutation.changed) { RemoveClosedPopupStates(mutation.closedPopupIds); @@ -165,7 +166,7 @@ UIEditorMenuSessionMutationResult UIEditorMenuSession::DismissFromPointerDown( UIEditorMenuSessionMutationResult UIEditorMenuSession::DismissFromFocusLoss( const UIInputPath& focusedPath) { - const Widgets::UIPopupOverlayMutationResult mutation = + const ::XCEngine::UI::Widgets::UIPopupOverlayMutationResult mutation = m_popupOverlayModel.DismissFromFocusLoss(focusedPath); if (mutation.changed) { RemoveClosedPopupStates(mutation.closedPopupIds); @@ -176,7 +177,7 @@ UIEditorMenuSessionMutationResult UIEditorMenuSession::DismissFromFocusLoss( } UIEditorMenuSessionMutationResult UIEditorMenuSession::BuildResult( - const Widgets::UIPopupOverlayMutationResult& mutation) const { + const ::XCEngine::UI::Widgets::UIPopupOverlayMutationResult& mutation) const { UIEditorMenuSessionMutationResult result = {}; result.changed = mutation.changed; result.openRootMenuId = m_openRootMenuId; diff --git a/new_editor/src/Core/UIEditorPanelContentHost.cpp b/new_editor/src/Shell/UIEditorPanelContentHost.cpp similarity index 99% rename from new_editor/src/Core/UIEditorPanelContentHost.cpp rename to new_editor/src/Shell/UIEditorPanelContentHost.cpp index 835c07a4..d39f4db5 100644 --- a/new_editor/src/Core/UIEditorPanelContentHost.cpp +++ b/new_editor/src/Shell/UIEditorPanelContentHost.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Widgets/UIEditorPanelFrame.cpp b/new_editor/src/Shell/UIEditorPanelFrame.cpp similarity index 92% rename from new_editor/src/Widgets/UIEditorPanelFrame.cpp rename to new_editor/src/Shell/UIEditorPanelFrame.cpp index 9757cf6f..735062e1 100644 --- a/new_editor/src/Widgets/UIEditorPanelFrame.cpp +++ b/new_editor/src/Shell/UIEditorPanelFrame.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -25,6 +25,18 @@ float ResolveActionButtonExtent( (std::max)(headerHeight - inset * 2.0f, 0.0f)); } +float ResolveActionCornerRounding(const UIRect& rect) { + return (std::min)((std::max)((std::min)(rect.width, rect.height) * 0.35f, 0.0f), 6.0f); +} + +float ResolveActionGlyphLeft(const UIRect& rect) { + return rect.x + (std::max)(0.0f, (rect.width - 7.0f) * 0.5f); +} + +float ResolveActionGlyphTop(const UIRect& rect) { + return rect.y + (std::max)(0.0f, (rect.height - 12.0f) * 0.5f) - 0.5f; +} + UIColor ResolveActionFillColor( bool selected, bool hovered, @@ -51,17 +63,18 @@ void AppendActionButton( return; } + const float cornerRounding = ResolveActionCornerRounding(rect); drawList.AddFilledRect( rect, ResolveActionFillColor(selected, hovered, palette), - 5.0f); + cornerRounding); drawList.AddRectOutline( rect, palette.actionButtonBorderColor, 1.0f, - 5.0f); + cornerRounding); drawList.AddText( - UIPoint(rect.x + 5.0f, rect.y + 2.0f), + UIPoint(ResolveActionGlyphLeft(rect), ResolveActionGlyphTop(rect)), std::string(glyph), palette.actionGlyphColor, 12.0f); @@ -267,8 +280,10 @@ void AppendUIEditorPanelFrameBackground( ResolveUIEditorPanelFrameBorderColor(state, palette), ResolveUIEditorPanelFrameBorderThickness(state, metrics), metrics.cornerRounding); - drawList.AddFilledRect(layout.headerRect, palette.headerColor, metrics.cornerRounding); - if (layout.hasFooter) { + if (layout.headerRect.height > 0.0f) { + drawList.AddFilledRect(layout.headerRect, palette.headerColor, metrics.cornerRounding); + } + if (layout.hasFooter && layout.footerRect.height > 0.0f) { drawList.AddFilledRect(layout.footerRect, palette.footerColor, metrics.cornerRounding); } } diff --git a/new_editor/src/Core/UIEditorPanelHostLifecycle.cpp b/new_editor/src/Shell/UIEditorPanelHostLifecycle.cpp similarity index 99% rename from new_editor/src/Core/UIEditorPanelHostLifecycle.cpp rename to new_editor/src/Shell/UIEditorPanelHostLifecycle.cpp index 9bd0515c..c9ace212 100644 --- a/new_editor/src/Core/UIEditorPanelHostLifecycle.cpp +++ b/new_editor/src/Shell/UIEditorPanelHostLifecycle.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Core/UIEditorPanelRegistry.cpp b/new_editor/src/Shell/UIEditorPanelRegistry.cpp similarity index 97% rename from new_editor/src/Core/UIEditorPanelRegistry.cpp rename to new_editor/src/Shell/UIEditorPanelRegistry.cpp index 111f88a6..91ff8b99 100644 --- a/new_editor/src/Core/UIEditorPanelRegistry.cpp +++ b/new_editor/src/Shell/UIEditorPanelRegistry.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Core/UIEditorShellCompose.cpp b/new_editor/src/Shell/UIEditorShellCompose.cpp similarity index 96% rename from new_editor/src/Core/UIEditorShellCompose.cpp rename to new_editor/src/Shell/UIEditorShellCompose.cpp index c6cacac8..77a69eb0 100644 --- a/new_editor/src/Core/UIEditorShellCompose.cpp +++ b/new_editor/src/Shell/UIEditorShellCompose.cpp @@ -1,4 +1,4 @@ -#include +#include #include @@ -107,7 +107,8 @@ UIEditorShellComposeRequest ResolveUIEditorShellComposeRequest( session, model.workspacePresentations, dockHostState, - metrics.dockHostMetrics); + metrics.dockHostMetrics, + metrics.viewportMetrics); request.layout.menuBarLayout = BuildUIEditorMenuBarLayout( request.layout.menuBarRect, model.menuBarItems, @@ -145,7 +146,8 @@ UIEditorShellComposeFrame UpdateUIEditorShellCompose( model.workspacePresentations, inputEvents, dockHostState, - metrics.dockHostMetrics); + metrics.dockHostMetrics, + metrics.viewportMetrics); frame.layout.menuBarLayout = BuildUIEditorMenuBarLayout( frame.layout.menuBarRect, model.menuBarItems, @@ -193,7 +195,9 @@ void AppendUIEditorShellCompose( drawList, frame.workspaceFrame, palette.dockHostPalette, - metrics.dockHostMetrics); + metrics.dockHostMetrics, + palette.viewportPalette, + metrics.viewportMetrics); AppendUIEditorStatusBarBackground( drawList, diff --git a/new_editor/src/Core/UIEditorShellInteraction.cpp b/new_editor/src/Shell/UIEditorShellInteraction.cpp similarity index 99% rename from new_editor/src/Core/UIEditorShellInteraction.cpp rename to new_editor/src/Shell/UIEditorShellInteraction.cpp index 66bd15cf..0890d668 100644 --- a/new_editor/src/Core/UIEditorShellInteraction.cpp +++ b/new_editor/src/Shell/UIEditorShellInteraction.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Widgets/UIEditorStatusBar.cpp b/new_editor/src/Shell/UIEditorStatusBar.cpp similarity index 96% rename from new_editor/src/Widgets/UIEditorStatusBar.cpp rename to new_editor/src/Shell/UIEditorStatusBar.cpp index cac9174b..3cd1a6ed 100644 --- a/new_editor/src/Widgets/UIEditorStatusBar.cpp +++ b/new_editor/src/Shell/UIEditorStatusBar.cpp @@ -1,4 +1,4 @@ -#include +#include #include @@ -41,6 +41,10 @@ UIColor ResolveSegmentFillColor( return palette.segmentColor; } +float ResolveSegmentCornerRounding(const UIEditorStatusBarMetrics& metrics) { + return (std::max)(metrics.cornerRounding - 2.0f, 0.0f); +} + } // namespace float ResolveUIEditorStatusBarDesiredSegmentWidth( @@ -217,19 +221,19 @@ void AppendUIEditorStatusBarBackground( const bool hovered = state.hoveredIndex == index; const bool active = state.activeIndex == index; - if (!hovered && !active) { + if ((!hovered && !active) || !segments[index].interactive) { continue; } drawList.AddFilledRect( layout.segmentRects[index], ResolveSegmentFillColor(hovered, active, palette), - 6.0f); + ResolveSegmentCornerRounding(metrics)); drawList.AddRectOutline( layout.segmentRects[index], palette.segmentBorderColor, 1.0f, - 6.0f); + ResolveSegmentCornerRounding(metrics)); } for (const UIRect& separatorRect : layout.separatorRects) { diff --git a/new_editor/src/Core/UIEditorViewportInputBridge.cpp b/new_editor/src/Shell/UIEditorViewportInputBridge.cpp similarity index 99% rename from new_editor/src/Core/UIEditorViewportInputBridge.cpp rename to new_editor/src/Shell/UIEditorViewportInputBridge.cpp index 04907f3a..e1c84c54 100644 --- a/new_editor/src/Core/UIEditorViewportInputBridge.cpp +++ b/new_editor/src/Shell/UIEditorViewportInputBridge.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/new_editor/src/Core/UIEditorViewportShell.cpp b/new_editor/src/Shell/UIEditorViewportShell.cpp similarity index 98% rename from new_editor/src/Core/UIEditorViewportShell.cpp rename to new_editor/src/Shell/UIEditorViewportShell.cpp index 5faffcb4..2c7c3b46 100644 --- a/new_editor/src/Core/UIEditorViewportShell.cpp +++ b/new_editor/src/Shell/UIEditorViewportShell.cpp @@ -1,4 +1,4 @@ -#include +#include namespace XCEngine::UI::Editor { diff --git a/new_editor/src/Widgets/UIEditorViewportSlot.cpp b/new_editor/src/Shell/UIEditorViewportSlot.cpp similarity index 99% rename from new_editor/src/Widgets/UIEditorViewportSlot.cpp rename to new_editor/src/Shell/UIEditorViewportSlot.cpp index 4aa6f262..9eb84242 100644 --- a/new_editor/src/Widgets/UIEditorViewportSlot.cpp +++ b/new_editor/src/Shell/UIEditorViewportSlot.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Core/UIEditorWorkspaceCompose.cpp b/new_editor/src/Shell/UIEditorWorkspaceCompose.cpp similarity index 93% rename from new_editor/src/Core/UIEditorWorkspaceCompose.cpp rename to new_editor/src/Shell/UIEditorWorkspaceCompose.cpp index 87ae9782..793cdc30 100644 --- a/new_editor/src/Core/UIEditorWorkspaceCompose.cpp +++ b/new_editor/src/Shell/UIEditorWorkspaceCompose.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -172,7 +172,8 @@ UIEditorWorkspaceComposeRequest ResolveUIEditorWorkspaceComposeRequest( const UIEditorWorkspaceSession& session, const std::vector& presentations, const Widgets::UIEditorDockHostState& dockHostState, - const Widgets::UIEditorDockHostMetrics& dockHostMetrics) { + const Widgets::UIEditorDockHostMetrics& dockHostMetrics, + const Widgets::UIEditorViewportSlotMetrics& viewportMetrics) { UIEditorWorkspaceComposeRequest request = {}; request.dockHostLayout = BuildUIEditorDockHostLayout( bounds, @@ -206,7 +207,8 @@ UIEditorWorkspaceComposeRequest ResolveUIEditorWorkspaceComposeRequest( viewportRequest.bounds = mountRequest.bounds; viewportRequest.viewportShellRequest = ResolveUIEditorViewportShellRequest( mountRequest.bounds, - presentation->viewportShellModel.spec); + presentation->viewportShellModel.spec, + viewportMetrics); request.viewportRequests.push_back(std::move(viewportRequest)); } @@ -222,7 +224,8 @@ UIEditorWorkspaceComposeFrame UpdateUIEditorWorkspaceCompose( const std::vector& presentations, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, const Widgets::UIEditorDockHostState& dockHostState, - const Widgets::UIEditorDockHostMetrics& dockHostMetrics) { + const Widgets::UIEditorDockHostMetrics& dockHostMetrics, + const Widgets::UIEditorViewportSlotMetrics& viewportMetrics) { UIEditorWorkspaceComposeFrame frame = {}; frame.dockHostLayout = BuildUIEditorDockHostLayout( bounds, @@ -268,7 +271,8 @@ UIEditorWorkspaceComposeFrame UpdateUIEditorWorkspaceCompose( panelState.viewportShellState, contentHostPanelState->bounds, presentation.viewportShellModel, - inputEvents); + inputEvents, + viewportMetrics); frame.viewportFrames.push_back(std::move(viewportFrame)); } @@ -284,7 +288,9 @@ void AppendUIEditorWorkspaceCompose( ::XCEngine::UI::UIDrawList& drawList, const UIEditorWorkspaceComposeFrame& frame, const Widgets::UIEditorDockHostPalette& dockHostPalette, - const Widgets::UIEditorDockHostMetrics& dockHostMetrics) { + const Widgets::UIEditorDockHostMetrics& dockHostMetrics, + const Widgets::UIEditorViewportSlotPalette& viewportPalette, + const Widgets::UIEditorViewportSlotMetrics& viewportMetrics) { AppendUIEditorDockHostBackground( drawList, frame.dockHostLayout, @@ -297,7 +303,9 @@ void AppendUIEditorWorkspaceCompose( viewportFrame.viewportShellFrame.slotLayout, viewportFrame.viewportShellModel.spec.toolItems, viewportFrame.viewportShellModel.spec.statusSegments, - viewportFrame.viewportShellFrame.slotState); + viewportFrame.viewportShellFrame.slotState, + viewportPalette, + viewportMetrics); AppendUIEditorViewportSlotForeground( drawList, viewportFrame.viewportShellFrame.slotLayout, @@ -305,7 +313,9 @@ void AppendUIEditorWorkspaceCompose( viewportFrame.viewportShellModel.frame, viewportFrame.viewportShellModel.spec.toolItems, viewportFrame.viewportShellModel.spec.statusSegments, - viewportFrame.viewportShellFrame.slotState); + viewportFrame.viewportShellFrame.slotState, + viewportPalette, + viewportMetrics); } UIEditorDockHostForegroundOptions foregroundOptions = {}; diff --git a/new_editor/src/Core/UIEditorWorkspaceController.cpp b/new_editor/src/Shell/UIEditorWorkspaceController.cpp similarity index 99% rename from new_editor/src/Core/UIEditorWorkspaceController.cpp rename to new_editor/src/Shell/UIEditorWorkspaceController.cpp index 71ece1e4..073a2e27 100644 --- a/new_editor/src/Core/UIEditorWorkspaceController.cpp +++ b/new_editor/src/Shell/UIEditorWorkspaceController.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Core/UIEditorWorkspaceInteraction.cpp b/new_editor/src/Shell/UIEditorWorkspaceInteraction.cpp similarity index 92% rename from new_editor/src/Core/UIEditorWorkspaceInteraction.cpp rename to new_editor/src/Shell/UIEditorWorkspaceInteraction.cpp index df282b2c..23f0b597 100644 --- a/new_editor/src/Core/UIEditorWorkspaceInteraction.cpp +++ b/new_editor/src/Shell/UIEditorWorkspaceInteraction.cpp @@ -1,4 +1,4 @@ -#include +#include #include @@ -28,7 +28,8 @@ UIEditorWorkspaceInteractionFrame UpdateUIEditorWorkspaceInteraction( const ::XCEngine::UI::UIRect& bounds, const UIEditorWorkspaceInteractionModel& model, const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - const Widgets::UIEditorDockHostMetrics& dockHostMetrics) { + const Widgets::UIEditorDockHostMetrics& dockHostMetrics, + const Widgets::UIEditorViewportSlotMetrics& viewportMetrics) { UIEditorWorkspaceInteractionFrame frame = {}; frame.dockHostFrame = UpdateUIEditorDockHostInteraction( state.dockHostInteractionState, @@ -45,7 +46,8 @@ UIEditorWorkspaceInteractionFrame UpdateUIEditorWorkspaceInteraction( model.workspacePresentations, inputEvents, state.dockHostInteractionState.dockHostState, - dockHostMetrics); + dockHostMetrics, + viewportMetrics); frame.result.dockHostResult = frame.dockHostFrame.result; frame.result.consumed = frame.dockHostFrame.result.consumed; diff --git a/new_editor/src/Core/UIEditorWorkspaceLayoutPersistence.cpp b/new_editor/src/Shell/UIEditorWorkspaceLayoutPersistence.cpp similarity index 99% rename from new_editor/src/Core/UIEditorWorkspaceLayoutPersistence.cpp rename to new_editor/src/Shell/UIEditorWorkspaceLayoutPersistence.cpp index f3e19e55..b1f17adf 100644 --- a/new_editor/src/Core/UIEditorWorkspaceLayoutPersistence.cpp +++ b/new_editor/src/Shell/UIEditorWorkspaceLayoutPersistence.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Core/UIEditorWorkspaceModel.cpp b/new_editor/src/Shell/UIEditorWorkspaceModel.cpp similarity index 99% rename from new_editor/src/Core/UIEditorWorkspaceModel.cpp rename to new_editor/src/Shell/UIEditorWorkspaceModel.cpp index 32875fb3..2d80894f 100644 --- a/new_editor/src/Core/UIEditorWorkspaceModel.cpp +++ b/new_editor/src/Shell/UIEditorWorkspaceModel.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include #include diff --git a/new_editor/src/Core/UIEditorWorkspaceSession.cpp b/new_editor/src/Shell/UIEditorWorkspaceSession.cpp similarity index 99% rename from new_editor/src/Core/UIEditorWorkspaceSession.cpp rename to new_editor/src/Shell/UIEditorWorkspaceSession.cpp index 0f57c5e4..bba005e3 100644 --- a/new_editor/src/Core/UIEditorWorkspaceSession.cpp +++ b/new_editor/src/Shell/UIEditorWorkspaceSession.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/new_editor/src/Widgets/UIEditorCollectionPrimitives.cpp b/new_editor/src/Widgets/UIEditorCollectionPrimitives.cpp index 87819fff..10952853 100644 --- a/new_editor/src/Widgets/UIEditorCollectionPrimitives.cpp +++ b/new_editor/src/Widgets/UIEditorCollectionPrimitives.cpp @@ -2,24 +2,6 @@ namespace XCEngine::UI::Editor::Widgets { -namespace { - -float ResolveFloatToken( - const ::XCEngine::UI::Style::UITheme& theme, - const char* tokenName, - float fallbackValue) { - const ::XCEngine::UI::Style::UITokenResolveResult result = - theme.ResolveToken(tokenName, ::XCEngine::UI::Style::UIStyleValueType::Float); - if (result.status != ::XCEngine::UI::Style::UITokenResolveStatus::Resolved) { - return fallbackValue; - } - - const float* value = result.value.TryGetFloat(); - return value != nullptr ? *value : fallbackValue; -} - -} // namespace - UIEditorCollectionPrimitiveKind ClassifyUIEditorCollectionPrimitive(std::string_view tagName) { if (tagName == "ScrollView") { return UIEditorCollectionPrimitiveKind::ScrollView; @@ -73,27 +55,25 @@ bool DoesUIEditorCollectionPrimitiveClipChildren(UIEditorCollectionPrimitiveKind } float ResolveUIEditorCollectionPrimitivePadding( - UIEditorCollectionPrimitiveKind kind, - const ::XCEngine::UI::Style::UITheme& theme) { + UIEditorCollectionPrimitiveKind kind) { return kind == UIEditorCollectionPrimitiveKind::TreeView || kind == UIEditorCollectionPrimitiveKind::ListView || kind == UIEditorCollectionPrimitiveKind::PropertySection - ? ResolveFloatToken(theme, "space.cardInset", 12.0f) + ? 12.0f : 0.0f; } float ResolveUIEditorCollectionPrimitiveDefaultHeight( - UIEditorCollectionPrimitiveKind kind, - const ::XCEngine::UI::Style::UITheme& theme) { + UIEditorCollectionPrimitiveKind kind) { switch (kind) { case UIEditorCollectionPrimitiveKind::TreeItem: - return ResolveFloatToken(theme, "size.treeItemHeight", 28.0f); + return 28.0f; case UIEditorCollectionPrimitiveKind::ListItem: - return ResolveFloatToken(theme, "size.listItemHeight", 60.0f); + return 60.0f; case UIEditorCollectionPrimitiveKind::FieldRow: - return ResolveFloatToken(theme, "size.fieldRowHeight", 32.0f); + return 32.0f; case UIEditorCollectionPrimitiveKind::PropertySection: - return ResolveFloatToken(theme, "size.propertySectionHeight", 148.0f); + return 148.0f; default: return 0.0f; } @@ -101,10 +81,9 @@ float ResolveUIEditorCollectionPrimitiveDefaultHeight( float ResolveUIEditorCollectionPrimitiveIndent( UIEditorCollectionPrimitiveKind kind, - const ::XCEngine::UI::Style::UITheme& theme, float indentLevel) { return kind == UIEditorCollectionPrimitiveKind::TreeItem - ? indentLevel * ResolveFloatToken(theme, "size.treeIndent", 18.0f) + ? indentLevel * 18.0f : 0.0f; } diff --git a/new_editor/src/Widgets/UIEditorEnumField.cpp b/new_editor/src/Widgets/UIEditorEnumField.cpp deleted file mode 100644 index 308bbf31..00000000 --- a/new_editor/src/Widgets/UIEditorEnumField.cpp +++ /dev/null @@ -1,166 +0,0 @@ -#include -#include -#include - -#include - -namespace XCEngine::UI::Editor::Widgets { - -namespace { - -float ClampNonNegative(float value) { - return (std::max)(0.0f, value); -} - -bool ContainsPoint(const ::XCEngine::UI::UIRect& rect, const ::XCEngine::UI::UIPoint& point) { - return point.x >= rect.x && - point.x <= rect.x + rect.width && - point.y >= rect.y && - point.y <= rect.y + rect.height; -} - -std::size_t ClampSelectedIndex(const UIEditorEnumFieldSpec& spec) { - if (spec.options.empty()) { - return 0u; - } - - return (std::min)(spec.selectedIndex, spec.options.size() - 1u); -} - -} // namespace - -std::string ResolveUIEditorEnumFieldValueText(const UIEditorEnumFieldSpec& spec) { - if (spec.options.empty()) { - return "(none)"; - } - - return spec.options[ClampSelectedIndex(spec)]; -} - -UIEditorEnumFieldLayout BuildUIEditorEnumFieldLayout( - const ::XCEngine::UI::UIRect& bounds, - const UIEditorEnumFieldSpec&, - const UIEditorEnumFieldMetrics& metrics) { - const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout( - bounds, - metrics.valueBoxMinWidth, - UIEditorFieldRowLayoutMetrics { - metrics.rowHeight, - metrics.horizontalPadding, - metrics.labelControlGap, - metrics.controlColumnStart, - metrics.controlTrailingInset, - metrics.controlInsetY, - }); - - UIEditorEnumFieldLayout layout = {}; - layout.bounds = hostLayout.bounds; - layout.labelRect = hostLayout.labelRect; - layout.controlRect = hostLayout.controlRect; - layout.valueRect = layout.controlRect; - const float arrowWidth = (std::min)( - (std::max)(metrics.dropdownArrowWidth, layout.valueRect.height), - layout.valueRect.width); - layout.arrowRect = ::XCEngine::UI::UIRect( - layout.valueRect.x + (std::max)(0.0f, layout.valueRect.width - arrowWidth), - layout.valueRect.y, - arrowWidth, - layout.valueRect.height); - return layout; -} - -UIEditorEnumFieldHitTarget HitTestUIEditorEnumField( - const UIEditorEnumFieldLayout& layout, - const ::XCEngine::UI::UIPoint& point) { - if (!ContainsPoint(layout.bounds, point)) { - return {}; - } - if (ContainsPoint(layout.arrowRect, point)) { - return { UIEditorEnumFieldHitTargetKind::DropdownArrow }; - } - if (ContainsPoint(layout.valueRect, point)) { - return { UIEditorEnumFieldHitTargetKind::ValueBox }; - } - return { UIEditorEnumFieldHitTargetKind::Row }; -} - -void AppendUIEditorEnumFieldBackground( - ::XCEngine::UI::UIDrawList& drawList, - const UIEditorEnumFieldLayout& layout, - const UIEditorEnumFieldSpec& spec, - const UIEditorEnumFieldState& state, - const UIEditorEnumFieldPalette& palette, - const UIEditorEnumFieldMetrics& metrics) { - const bool controlHovered = - state.hoveredTarget == UIEditorEnumFieldHitTargetKind::ValueBox || - state.hoveredTarget == UIEditorEnumFieldHitTargetKind::DropdownArrow; - const ::XCEngine::UI::UIColor controlColor = - spec.readOnly - ? palette.readOnlyColor - : (controlHovered || state.popupOpen ? palette.valueBoxHoverColor : palette.valueBoxColor); - drawList.AddFilledRect(layout.valueRect, controlColor, metrics.valueBoxRounding); - drawList.AddRectOutline( - layout.valueRect, - state.popupOpen ? palette.focusedBorderColor : palette.controlBorderColor, - state.popupOpen ? metrics.focusedBorderThickness : metrics.borderThickness, - metrics.valueBoxRounding); -} - -void AppendUIEditorEnumFieldForeground( - ::XCEngine::UI::UIDrawList& drawList, - const UIEditorEnumFieldLayout& layout, - const UIEditorEnumFieldSpec& spec, - const UIEditorEnumFieldPalette& palette, - const UIEditorEnumFieldMetrics& metrics) { - drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, metrics.labelFontSize)); - drawList.AddText( - ::XCEngine::UI::UIPoint( - layout.labelRect.x, - ResolveUIEditorTextTop(layout.labelRect, metrics.labelFontSize, metrics.labelTextInsetY)), - spec.label, - palette.labelColor, - metrics.labelFontSize); - drawList.PopClipRect(); - - const ::XCEngine::UI::UIRect valueTextClipRect( - layout.valueRect.x + metrics.valueTextInsetX, - layout.valueRect.y, - ClampNonNegative(layout.arrowRect.x - layout.valueRect.x - metrics.valueTextInsetX), - layout.valueRect.height); - drawList.PushClipRect(ResolveUIEditorTextClipRect(valueTextClipRect, metrics.valueFontSize)); - drawList.AddText( - ::XCEngine::UI::UIPoint( - layout.valueRect.x + metrics.valueTextInsetX, - ResolveUIEditorTextTop(layout.valueRect, metrics.valueFontSize, metrics.valueTextInsetY)), - ResolveUIEditorEnumFieldValueText(spec), - palette.valueColor, - metrics.valueFontSize); - drawList.PopClipRect(); - - drawList.AddText( - ::XCEngine::UI::UIPoint( - layout.arrowRect.x + - ClampNonNegative((layout.arrowRect.width - metrics.dropdownArrowFontSize) * 0.5f) + - metrics.dropdownArrowInsetX, - ResolveUIEditorTextTop( - layout.arrowRect, - metrics.dropdownArrowFontSize, - metrics.dropdownArrowInsetY)), - "V", - palette.arrowColor, - metrics.dropdownArrowFontSize); -} - -void AppendUIEditorEnumField( - ::XCEngine::UI::UIDrawList& drawList, - const ::XCEngine::UI::UIRect& bounds, - const UIEditorEnumFieldSpec& spec, - const UIEditorEnumFieldState& state, - const UIEditorEnumFieldPalette& palette, - const UIEditorEnumFieldMetrics& metrics) { - const UIEditorEnumFieldLayout layout = BuildUIEditorEnumFieldLayout(bounds, spec, metrics); - AppendUIEditorEnumFieldBackground(drawList, layout, spec, state, palette, metrics); - AppendUIEditorEnumFieldForeground(drawList, layout, spec, palette, metrics); -} - -} // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/src/Widgets/UIEditorFieldRowLayout.cpp b/new_editor/src/Widgets/UIEditorFieldRowLayout.cpp index 342d999a..c4e59319 100644 --- a/new_editor/src/Widgets/UIEditorFieldRowLayout.cpp +++ b/new_editor/src/Widgets/UIEditorFieldRowLayout.cpp @@ -1,6 +1,7 @@ #include #include +#include namespace XCEngine::UI::Editor::Widgets { @@ -10,8 +11,30 @@ float ClampNonNegative(float value) { return (std::max)(0.0f, value); } +bool AreEqual(float lhs, float rhs) { + return std::abs(lhs - rhs) <= 0.001f; +} + } // namespace +const UIEditorInspectorFieldStyleTokens& GetUIEditorInspectorFieldStyleTokens() { + static const UIEditorInspectorFieldStyleTokens kTokens = {}; + return kTokens; +} + +bool AreUIEditorFieldMetricsEqual(float lhs, float rhs) { + return AreEqual(lhs, rhs); +} + +bool AreUIEditorFieldColorsEqual( + const ::XCEngine::UI::UIColor& lhs, + const ::XCEngine::UI::UIColor& rhs) { + return AreEqual(lhs.r, rhs.r) && + AreEqual(lhs.g, rhs.g) && + AreEqual(lhs.b, rhs.b) && + AreEqual(lhs.a, rhs.a); +} + UIEditorFieldRowLayout BuildUIEditorFieldRowLayout( const ::XCEngine::UI::UIRect& bounds, float minimumControlWidth, diff --git a/new_editor/src/Widgets/UIEditorTextField.cpp b/new_editor/src/Widgets/UIEditorTextField.cpp deleted file mode 100644 index be154b29..00000000 --- a/new_editor/src/Widgets/UIEditorTextField.cpp +++ /dev/null @@ -1,165 +0,0 @@ -#include -#include -#include - -#include - -namespace XCEngine::UI::Editor::Widgets { - -namespace { - -using ::XCEngine::UI::UIDrawList; -using ::XCEngine::UI::UIPoint; -using ::XCEngine::UI::UIRect; - -float ClampNonNegative(float value) { - return (std::max)(0.0f, value); -} - -::XCEngine::UI::UIColor ResolveRowFillColor( - const UIEditorTextFieldState& state, - const UIEditorTextFieldPalette& palette) { - if (state.activeTarget != UIEditorTextFieldHitTargetKind::None) { - return palette.rowActiveColor; - } - if (state.hoveredTarget != UIEditorTextFieldHitTargetKind::None) { - return palette.rowHoverColor; - } - return palette.surfaceColor; -} - -::XCEngine::UI::UIColor ResolveValueFillColor( - const UIEditorTextFieldSpec& spec, - const UIEditorTextFieldState& state, - const UIEditorTextFieldPalette& palette) { - if (spec.readOnly) { - return palette.readOnlyColor; - } - if (state.editing) { - return palette.valueBoxEditingColor; - } - if (state.hoveredTarget == UIEditorTextFieldHitTargetKind::ValueBox) { - return palette.valueBoxHoverColor; - } - return palette.valueBoxColor; -} - -} // namespace - -bool IsUIEditorTextFieldPointInside( - 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; -} - -UIEditorTextFieldLayout BuildUIEditorTextFieldLayout( - const UIRect& bounds, - const UIEditorTextFieldSpec&, - const UIEditorTextFieldMetrics& metrics) { - const UIEditorFieldRowLayout hostLayout = BuildUIEditorFieldRowLayout( - bounds, - metrics.valueBoxMinWidth, - UIEditorFieldRowLayoutMetrics { - metrics.rowHeight, - metrics.horizontalPadding, - metrics.labelControlGap, - metrics.controlColumnStart, - metrics.controlTrailingInset, - metrics.controlInsetY, - }); - - UIEditorTextFieldLayout layout = {}; - layout.bounds = hostLayout.bounds; - layout.labelRect = hostLayout.labelRect; - layout.controlRect = hostLayout.controlRect; - layout.valueRect = layout.controlRect; - return layout; -} - -UIEditorTextFieldHitTarget HitTestUIEditorTextField( - const UIEditorTextFieldLayout& layout, - const UIPoint& point) { - if (IsUIEditorTextFieldPointInside(layout.valueRect, point)) { - return { UIEditorTextFieldHitTargetKind::ValueBox }; - } - if (IsUIEditorTextFieldPointInside(layout.bounds, point)) { - return { UIEditorTextFieldHitTargetKind::Row }; - } - return {}; -} - -void AppendUIEditorTextFieldBackground( - UIDrawList& drawList, - const UIEditorTextFieldLayout& layout, - const UIEditorTextFieldSpec& spec, - const UIEditorTextFieldState& state, - const UIEditorTextFieldPalette& palette, - const UIEditorTextFieldMetrics& metrics) { - const auto rowFillColor = ResolveRowFillColor(state, palette); - if (rowFillColor.a > 0.0f) { - drawList.AddFilledRect(layout.bounds, rowFillColor, metrics.cornerRounding); - } - const auto rowBorderColor = state.focused ? palette.focusedBorderColor : palette.borderColor; - if (rowBorderColor.a > 0.0f) { - drawList.AddRectOutline( - layout.bounds, - rowBorderColor, - state.focused ? metrics.focusedBorderThickness : metrics.borderThickness, - metrics.cornerRounding); - } - - drawList.AddFilledRect( - layout.valueRect, - ResolveValueFillColor(spec, state, palette), - metrics.valueBoxRounding); - drawList.AddRectOutline( - layout.valueRect, - state.editing ? palette.controlFocusedBorderColor : palette.controlBorderColor, - state.editing ? metrics.focusedBorderThickness : metrics.borderThickness, - metrics.valueBoxRounding); -} - -void AppendUIEditorTextFieldForeground( - UIDrawList& drawList, - const UIEditorTextFieldLayout& layout, - const UIEditorTextFieldSpec& spec, - const UIEditorTextFieldState& state, - const UIEditorTextFieldPalette& palette, - const UIEditorTextFieldMetrics& metrics) { - drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.labelRect, metrics.labelFontSize)); - drawList.AddText( - UIPoint( - layout.labelRect.x, - ResolveUIEditorTextTop(layout.labelRect, metrics.labelFontSize, metrics.labelTextInsetY)), - spec.label, - palette.labelColor, - metrics.labelFontSize); - drawList.PopClipRect(); - - drawList.PushClipRect(ResolveUIEditorTextClipRect(layout.valueRect, metrics.valueFontSize)); - drawList.AddText( - UIPoint( - layout.valueRect.x + metrics.valueTextInsetX, - ResolveUIEditorTextTop(layout.valueRect, metrics.valueFontSize, metrics.valueTextInsetY)), - state.editing ? state.displayText : spec.value, - spec.readOnly ? palette.readOnlyValueColor : palette.valueColor, - metrics.valueFontSize); - drawList.PopClipRect(); -} - -void AppendUIEditorTextField( - UIDrawList& drawList, - const UIRect& bounds, - const UIEditorTextFieldSpec& spec, - const UIEditorTextFieldState& state, - const UIEditorTextFieldPalette& palette, - const UIEditorTextFieldMetrics& metrics) { - const UIEditorTextFieldLayout layout = BuildUIEditorTextFieldLayout(bounds, spec, metrics); - AppendUIEditorTextFieldBackground(drawList, layout, spec, state, palette, metrics); - AppendUIEditorTextFieldForeground(drawList, layout, spec, state, palette, metrics); -} - -} // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/ui/themes/editor_shell.xctheme b/new_editor/ui/themes/editor_shell.xctheme deleted file mode 100644 index 2aa8452c..00000000 --- a/new_editor/ui/themes/editor_shell.xctheme +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/new_editor/ui/views/editor_shell.xcui b/new_editor/ui/views/editor_shell.xcui index 376b8116..932e6058 100644 --- a/new_editor/ui/views/editor_shell.xcui +++ b/new_editor/ui/views/editor_shell.xcui @@ -1,7 +1,5 @@ + name="EditorShell"> //`. -- Each scenario owns its own `captures/` directory. +- `tests/UI` remains the active validation entry point. `new_editor` is not a test entry point. +- Shared infrastructure belongs in `shared/`. Do not duplicate host plumbing per scenario unless the scenario is intentionally a custom host. +- One scenario maps to one executable. -Layout: - -- Primary categories are `shell/` and `state/`. -- `menu`, `workspace`, and `viewport` in scenario names describe contract families, not extra directory levels. -- `shared/`: shared host wrapper, scenario registry, shared theme -- `shell/workspace_shell_compose/`: split/tab/panel shell compose only -- `shell/editor_shell_compose/`: editor root shell compose only -- `shell/editor_shell_interaction/`: editor root shell interaction only -- `shell/dock_host_basic/`: DockHost interaction contract only -- `shell/panel_content_host_basic/`: external panel body mount/switch/unmount only -- `shell/menu_bar_basic/`: menu bar open/close/hover/dispatch only -- `shell/context_menu_basic/`: context menu root/submenu/dismiss/dispatch only -- `shell/panel_frame_basic/`: panel frame layout/state/hit-test only -- `shell/scroll_view_basic/`: ScrollView viewport, clip, thumb drag, wheel offset only -- `shell/property_grid_basic/`: PropertyGrid section toggle, field selection, value edit, keyboard navigation only -- `shell/bool_field_basic/`: BoolField click toggle, keyboard toggle, hover/focus/value feedback only -- `shell/number_field_basic/`: NumberField step buttons, direct text edit, Enter commit, Esc cancel only -- `shell/enum_field_basic/`: EnumField previous/next switch, keyboard switch, hover/focus/selection feedback only -- `shell/status_bar_basic/`: status bar slot/segment/hit-test only -- `shell/tree_view_basic/`: TreeView row layout, indent, disclosure, selection, focus, hit-test only -- `shell/tree_view_multiselect/`: TreeView multi-selection contract only -- `shell/list_view_basic/`: ListView row layout, selection, focus, keyboard navigation, hit-test only -- `shell/list_view_multiselect/`: ListView multi-selection contract only -- `shell/list_view_inline_rename/`: ListView inline rename/edit session only -- `shell/tab_strip_basic/`: tab strip layout/state/hit-test/close/navigation only -- `shell/viewport_slot_basic/`: viewport slot chrome/surface/status only -- `shell/viewport_shell_basic/`: viewport shell request/state compose only -- `shell/workspace_viewport_compose/`: workspace body external presentation compose only -- `shell/workspace_interaction_basic/`: workspace unified interaction only -- `state/panel_session_flow/`: panel session state flow only -- `state/panel_host_lifecycle/`: panel host attach/detach/show/hide/activate/focus contract only -- `state/layout_persistence/`: layout save/load/reset only -- `state/shortcut_dispatch/`: shortcut match/suppression/dispatch only -- `state/viewport_input_bridge_basic/`: viewport hover/focus/capture, local pointer coordinates, wheel/key/character bridge only - -Scenarios: - -- `editor.shell.workspace_shell_compose` - Build target: `editor_ui_workspace_shell_compose_validation` - Executable: `XCUIEditorWorkspaceShellComposeValidation.exe` - Scope: DockHost compose, splitter drag, tab host, panel frame placeholders, workspace active-panel sync - -- `editor.shell.editor_shell_compose` - Build target: `editor_ui_editor_shell_compose_validation` - Executable: `XCUIEditorShellComposeValidation.exe` - Scope: root shell compose only; MenuBar / WorkspaceCompose / StatusBar three-band layout and workspace body embedding - -- `editor.shell.editor_shell_interaction` - Build target: `editor_ui_editor_shell_interaction_validation` - Executable: `XCUIEditorShellInteractionValidation.exe` - Scope: root shell unified interaction only; menu bar root switching, submenu hover chain, outside/Esc dismiss, command hook, menu-modal workspace shielding, and post-dismiss workspace interaction restore - -- `editor.shell.dock_host_basic` - Build target: `editor_ui_dock_host_basic_validation` - Executable: `XCUIEditorDockHostBasicValidation.exe` - Scope: `UpdateUIEditorDockHostInteraction(...)` basic contract only; splitter drag, tab activate/close, standalone panel activate/close, pointer capture/release request, workspace active-panel sync - -- `editor.shell.panel_content_host_basic` - Build target: `editor_ui_panel_content_host_basic_validation` - Executable: `XCUIEditorPanelContentHostBasicValidation.exe` - Scope: external HostedContent body mount/switch/unmount only; DockHost 只画 frame chrome,真实 body 由 content host 接管 - -- `editor.shell.menu_bar_basic` - Build target: `editor_ui_menu_bar_basic_validation` - Executable: `XCUIEditorMenuBarBasicValidation.exe` - Scope: menu bar open/close, hover, dismiss, menu command dispatch only - -- `editor.shell.context_menu_basic` - Build target: `editor_ui_context_menu_basic_validation` - Executable: `XCUIEditorContextMenuBasicValidation.exe` - Scope: context menu root anchor, submenu hover, outside/Esc dismiss, command dispatch only - -- `editor.shell.panel_frame_basic` - Build target: `editor_ui_panel_frame_basic_validation` - Executable: `XCUIEditorPanelFrameBasicValidation.exe` - Scope: panel frame header/body/footer layout, focus/active/hover chrome, pin/close hit target only - -- `editor.shell.scroll_view_basic` - Build target: `editor_ui_scroll_view_basic_validation` - Executable: `XCUIEditorScrollViewBasicValidation.exe` - Scope: ScrollView viewport clip, wheel scrolling, thumb drag, focus, and hit-test only - -- `editor.shell.property_grid_basic` - Build target: `editor_ui_property_grid_basic_validation` - Executable: `XCUIEditorPropertyGridBasicValidation.exe` - Scope: PropertyGrid 基础控件验证;只检查 section toggle、field selection、value edit、Enter/Esc、keyboard navigation,不涉及业务 Inspector - -- `editor.shell.bool_field_basic` - Build target: `editor_ui_bool_field_basic_validation` - Executable: `XCUIEditorBoolFieldBasicValidation.exe` - Scope: BoolField 基础控件契约;只验证点击切换、Space/Enter 切换、hover/focus/value/result 联动 - -- `editor.shell.number_field_basic` - Build target: `editor_ui_number_field_basic_validation` - Executable: `XCUIEditorNumberFieldBasicValidation.exe` - Scope: NumberField 基础控件契约;只验证步进按钮、直接字符编辑、Enter 提交、Esc 取消、hover/focus/result 联动 - -- `editor.shell.enum_field_basic` - Build target: `editor_ui_enum_field_basic_validation` - Executable: `XCUIEditorEnumFieldBasicValidation.exe` - Scope: EnumField 基础控件契约;只验证前后切换按钮、Left/Right/Home/End 键盘切换、hover/focus/result 联动 - -- `editor.shell.status_bar_basic` - Build target: `editor_ui_status_bar_basic_validation` - Executable: `XCUIEditorStatusBarBasicValidation.exe` - Scope: status bar slot layout, hover/active segment hit target, separator layout only - -- `editor.shell.tree_view_basic` - Build target: `editor_ui_tree_view_basic_validation` - Executable: `XCUIEditorTreeViewBasicValidation.exe` - Scope: TreeView 基础控件验证;只检查行缩进、disclosure 展开/折叠、selection、hover/focus 和 hit-test,不涉及业务面板 - -- `editor.shell.tree_view_multiselect` - Build target: `editor_ui_tree_view_multiselect_validation` - Executable: `XCUIEditorTreeViewMultiSelectValidation.exe` - Scope: TreeView 多选契约验证;只检查 Ctrl/Shift 多选、锚点、右键命中已选集合、键盘范围扩选、expanded/visible/current 同步,不涉及业务面板 - -- `editor.shell.tree_view_inline_rename` - Build target: `editor_ui_tree_view_inline_rename_validation` - Executable: `XCUIEditorTreeViewInlineRenameValidation.exe` - Scope: TreeView inline rename 契约验证;只检查 F2 开启、字符编辑、Enter 提交、Esc 取消、点击外部结束编辑,以及标签写回 - -- `editor.shell.list_view_basic` - Build target: `editor_ui_list_view_basic_validation` - Executable: `XCUIEditorListViewBasicValidation.exe` - Scope: ListView 基础控件验证;只检查 row 排列、selection、hover/focus、Up/Down/Home/End 键盘导航和 hit-test,不涉及业务面板 - -- `editor.shell.list_view_multiselect` - Build target: `editor_ui_list_view_multiselect_validation` - Executable: `XCUIEditorListViewMultiSelectValidation.exe` - Scope: ListView 多选契约验证;只检查 Ctrl/Shift 多选、锚点、右键命中已选集合、键盘范围扩选、current/primary 同步,不涉及业务面板 - -- `editor.shell.list_view_inline_rename` - Build target: `editor_ui_list_view_inline_rename_validation` - Executable: `XCUIEditorListViewInlineRenameValidation.exe` - Scope: ListView inline rename 契约验证;只检查 F2 开启、字符编辑、Enter 提交、Esc 取消、点击外部结束编辑,以及标签写回 - -- `editor.shell.tab_strip_basic` - Build target: `editor_ui_tab_strip_basic_validation` - Executable: `XCUIEditorTabStripBasicValidation.exe` - Scope: tab header layout, selected/hover/focus, close hit target, close fallback, Left/Right/Home/End navigation only - -- `editor.shell.viewport_slot_basic` - Build target: `editor_ui_viewport_slot_basic_validation` - Executable: `XCUIEditorViewportSlotBasicValidation.exe` - Scope: viewport top bar / surface / status bar layout, hover/focus/active/capture, texture vs fallback only - -- `editor.shell.viewport_shell_basic` - Build target: `editor_ui_viewport_shell_basic_validation` - Executable: `XCUIEditorViewportShellBasicValidation.exe` - Scope: `ResolveUIEditorViewportShellRequest(...)` + `UpdateUIEditorViewportShell(...)` basic contract, TopBar / BottomBar request-size sync, input rect + hover/focus/capture state sync only - -- `editor.shell.workspace_viewport_compose` - Build target: `editor_ui_workspace_viewport_compose_validation` - Executable: `XCUIEditorWorkspaceViewportComposeValidation.exe` - Scope: `ResolveUIEditorWorkspaceComposeRequest(...)` + `UpdateUIEditorWorkspaceCompose(...)` body presentation contract only; selected Scene tab body is hosted by `ViewportShell`, switching back to Document restores DockHost placeholder - -- `editor.shell.workspace_interaction_basic` - Build target: `editor_ui_workspace_interaction_basic_validation` - Executable: `XCUIEditorWorkspaceInteractionBasicValidation.exe` - Scope: `UpdateUIEditorWorkspaceInteraction(...)` unified contract only; DockHost splitter/tab interaction plus ViewportShell body focus/capture routing in the same workspace layer - -- `editor.state.panel_session_flow` - Build target: `editor_ui_panel_session_flow_validation` - Executable: `XCUIEditorPanelSessionFlowValidation.exe` - Scope: command dispatch + workspace controller + open/close/show/hide/activate - -- `editor.state.panel_host_lifecycle` - Build target: `editor_ui_panel_host_lifecycle_validation` - Executable: `XCUIEditorPanelHostLifecycleValidation.exe` - Scope: panel host attach/detach/show/hide/activate/focus lifecycle only; active/focus decoupling and hidden-tab attached state - -- `editor.state.layout_persistence` - Build target: `editor_ui_layout_persistence_validation` - Executable: `XCUIEditorLayoutPersistenceValidation.exe` - Scope: layout snapshot + serialize/deserialize + invalid payload reject - -- `editor.state.shortcut_dispatch` - Build target: `editor_ui_shortcut_dispatch_validation` - Executable: `XCUIEditorShortcutDispatchValidation.exe` - Scope: shortcut match + scope + suppression + command dispatch - -- `editor.state.viewport_input_bridge_basic` - Build target: `editor_ui_viewport_input_bridge_basic_validation` - Executable: `XCUIEditorViewportInputBridgeBasicValidation.exe` - Scope: viewport hover/focus/capture, local pointer coordinates, wheel/key/character bridge only - -Run: +Build: ```bash cmake --build build --config Debug --target editor_ui_integration_tests ``` -Auto capture: +Screenshot output: -- Set `XCUI_AUTO_CAPTURE_ON_STARTUP=1` before launching a validation executable to force a first-frame screenshot into that scenario's `captures/` directory. -- Manual validation still uses `F12`; startup auto capture is only for deterministic self-check / automation. - -Selected controls: - -- `shell/workspace_shell_compose/` - Drag splitters, switch `Document A/B/C`, close tabs or side panels, press `Reset`, press `F12`. - -- `shell/editor_shell_compose/` - Click `切到 Scene / 切到 Document`, toggle `TopBar / BottomBar / Texture`, inspect `MenuBar Rect / Workspace Rect / StatusBar Rect / Selected Presentation / Request Size`, press `Reset`, press `截图` or `F12`. - -- `shell/editor_shell_interaction/` - Click `File / Window`, hover `Workspace Tools`, click outside the menu or press `Esc`, then click `Document` or drag a splitter, inspect `Open Root / Popup Chain / Submenu Path / Selected Presentation / Active Panel / Host Capture / Result`, press `Reset`, `Capture`, or `F12`. - -- `shell/dock_host_basic/` - Drag `root-split`, click `Document A`, close `Document B`, click `Details`, close `Console`, inspect `Hover / Result / Active Panel / Visible Panels / Capture / split ratio`, press `Reset`, `Capture`, or `F12`. - -- `shell/panel_content_host_basic/` - Click `Activate Doc A / Activate Doc B / Activate Console / Close Inspector / Open Inspector`, inspect `Mounted Panels / Events` 和蓝色 external body,按 `Capture` 或 `F12`。 - -- `shell/menu_bar_basic/` - Click `File / Window / Layout`, move the mouse across menu items, click outside the menu or press `Esc`, press `F12`. - -- `shell/context_menu_basic/` - Right click inside `Context Target`, hover `Workspace Tools`, click actions, click outside the menu or press `Esc`, press `F12`. - -- `shell/panel_frame_basic/` - Move the mouse over the preview panel, click `Body / Pin / Close`, toggle `Active / Focus / Closable / Footer`, press `F12`. - -- `shell/scroll_view_basic/` - 把鼠标移到右侧日志区内滚轮滚动,拖拽 scrollbar thumb,检查 `Hover / Focused / Thumb Dragging / Offset / Has Scrollbar / Result`,按 `重置`、`截图(F12)` 或直接按 `F12`。 - -- `shell/bool_field_basic/` - 先看顶部中文说明,再点击 `BoolField` 的 row 或 toggle;控件获得 focus 后按 `Space / Enter`,检查 `Hover / Focused / Value / Result` 是否同步更新,按 `重置`、`截图(F12)` 或直接按 `F12`。 -- `shell/number_field_basic/` - 先看顶部中文说明,再点击 `- / +` 检查步进;点击数值框后直接输入字符并按 `Enter` 提交,或按 `Esc` 取消,检查 `Hover / Focused / Editing / Value / Result`,按 `重置`、`截图(F12)` 或直接按 `F12`。 -- `shell/enum_field_basic/` - 先看顶部中文说明,再点击 `< / >` 切换选项;控件获得 focus 后按 `Left / Right / Home / End`,检查 `Hover / Focused / Selected / Result`,按 `重置`、`截图(F12)` 或直接按 `F12`。 -- `shell/status_bar_basic/` - Move the mouse across leading/trailing segments, click interactive segments, toggle focus/active, press `F12`. - -- `shell/tree_view_basic/` - 先看顶部中文说明“这个测试在验证什么功能”,再点击 disclosure 和树节点行,检查 `Hover / Focused / Selected / Expanded / Visible / Result`,按 `重置`、`截图(F12)` 或直接按 `F12`。 - -- `shell/tree_view_multiselect/` - 先看顶部中文说明“这个测试在验证什么功能”,再分别执行 `单击`、`Ctrl+单击`、`Shift+单击`、`Shift+Up/Down/Home/End`、`Right Click`,检查 `Primary / Selected Count / Selected Ids / Anchor / Current / Expanded / Visible / Result`。 - -- `shell/tree_view_inline_rename/` - 先看顶部中文说明“这个测试在验证什么功能”,再依次执行 `单击`、`F2`、输入字符、`Enter / Esc / 点击外部`,检查 `Rename Active / Rename Item / Draft / Committed / Result`。 - -- `shell/list_view_basic/` - 先看顶部中文说明“这个测试在验证什么功能”,再点击列表行,并在列表获得 focus 后按 `Up / Down / Home / End`,检查 `Hover / Focused / Selected / Current / Result`,按 `重置`、`截图(F12)` 或直接按 `F12`。 - -- `shell/list_view_multiselect/` - 先看顶部中文说明“这个测试在验证什么功能”,再分别执行 `单击`、`Ctrl+单击`、`Shift+单击`、`Shift+Up/Down/Home/End`、`Right Click`,检查 `Primary / Selected Count / Selected Ids / Anchor / Current / Result`。 - -- `shell/list_view_inline_rename/` - 先看顶部中文说明“这个测试在验证什么功能”,再依次执行 `单击`、`F2`、输入字符、`Enter / Esc / 点击外部`,检查 `Rename Active / Rename Item / Draft / Committed / Result`。 -- `shell/tab_strip_basic/` - Click `Document A / B / C`, click `X` on closable tabs, click content to focus, press `Left / Right / Home / End`, press `Reset`, press `F12`. - -- `shell/viewport_slot_basic/` - Hover toolbar / surface / status bar, click surface to focus, hold and release left mouse to inspect capture, toggle `TopBar / StatusBar / Texture / Aspect`, press `F12`. - -- `shell/viewport_shell_basic/` - Hover / click / drag the viewport shell surface, toggle `TopBar / BottomBar / Texture`, inspect left-side `Request Size / Input Rect / Hover Hit / Hover / Focus / Capture / Result`, press `Reset`, press `截图` or `F12`. - -- `shell/workspace_viewport_compose/` - Click `切到 Scene / 切到 Document`, toggle `TopBar / BottomBar / Texture`, hover or click the center viewport only when `Scene` is selected, inspect `Selected Presentation / Request Size / Hover / Focus / Capture / Result`, press `Reset`, press `截图` or `F12`. - -- `shell/workspace_interaction_basic/` - Click the center `Viewport` body to inspect unified focus/capture routing, click `Document` to verify fallback to DockHost placeholder, drag `root-split` to verify layout sync, inspect `Selected Presentation / Active Panel / Host Capture / root-split ratio`, press `Reset`, `Capture`, or `F12`. - -- `state/panel_session_flow/` - Click `Hide Active / Show Doc A / Close Doc B / Open Doc B / Activate Details / Reset`, press `F12`. - -- `state/panel_host_lifecycle/` - Click `Hide Active / Close Doc B / Open Doc B / Activate Details / Focus Active / Clear Focus / Reset`, inspect `Events` and the three host cards, press `Capture` or `F12`. - -- `state/layout_persistence/` - Click `Hide Active -> Save Layout -> Close Doc B -> Load Layout`, click `Load Invalid`, press `F12`. - -- `state/shortcut_dispatch/` - Press `Ctrl+P / Ctrl+H / Ctrl+W / Ctrl+O / Ctrl+R`, toggle `Text Input`, press `F12`. - -- `state/viewport_input_bridge_basic/` - Hover surface, click and drag outside, roll the wheel, press `A / W / F / Space`, type characters, click outside to clear focus, press `F12`. +- Shared Editor integration hosts write screenshots only under the active CMake build directory. +- The output root is the executable directory: `.../Debug/captures//`. +- 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` +- 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/integration/shared/CMakeLists.txt index 3256a11e..0a7557b9 100644 --- a/tests/UI/Editor/integration/shared/CMakeLists.txt +++ b/tests/UI/Editor/integration/shared/CMakeLists.txt @@ -1,4 +1,5 @@ file(TO_CMAKE_PATH "${CMAKE_SOURCE_DIR}" XCENGINE_EDITOR_UI_TESTS_REPO_ROOT_PATH) +file(TO_CMAKE_PATH "${CMAKE_BINARY_DIR}" XCENGINE_EDITOR_UI_TESTS_BUILD_ROOT_PATH) add_library(editor_ui_validation_registry STATIC src/EditorValidationScenario.cpp @@ -38,6 +39,7 @@ target_compile_definitions(editor_ui_integration_host UNICODE _UNICODE XCENGINE_EDITOR_UI_TESTS_REPO_ROOT="${XCENGINE_EDITOR_UI_TESTS_REPO_ROOT_PATH}" + XCENGINE_EDITOR_UI_TESTS_BUILD_ROOT="${XCENGINE_EDITOR_UI_TESTS_BUILD_ROOT_PATH}" ) if(MSVC) diff --git a/tests/UI/Editor/integration/shared/src/Application.cpp b/tests/UI/Editor/integration/shared/src/Application.cpp index 8d753003..cc431049 100644 --- a/tests/UI/Editor/integration/shared/src/Application.cpp +++ b/tests/UI/Editor/integration/shared/src/Application.cpp @@ -11,6 +11,14 @@ #include #include +#ifndef XCENGINE_EDITOR_UI_TESTS_REPO_ROOT +#define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "." +#endif + +#ifndef XCENGINE_EDITOR_UI_TESTS_BUILD_ROOT +#define XCENGINE_EDITOR_UI_TESTS_BUILD_ROOT "." +#endif + namespace XCEngine::Tests::EditorUI { namespace { @@ -41,6 +49,55 @@ Application* GetApplicationFromWindow(HWND hwnd) { return reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); } +std::filesystem::path GetRepoRootPath() { + std::string root = XCENGINE_EDITOR_UI_TESTS_REPO_ROOT; + if (root.size() >= 2u && root.front() == '"' && root.back() == '"') { + root = root.substr(1u, root.size() - 2u); + } + return std::filesystem::path(root).lexically_normal(); +} + +std::filesystem::path GetBuildRootPath() { + std::string root = XCENGINE_EDITOR_UI_TESTS_BUILD_ROOT; + if (root.size() >= 2u && root.front() == '"' && root.back() == '"') { + root = root.substr(1u, root.size() - 2u); + } + return std::filesystem::path(root).lexically_normal(); +} + +bool TryMakeRepoRelativePath( + const std::filesystem::path& absolutePath, + std::filesystem::path& outRelativePath) { + std::error_code errorCode = {}; + outRelativePath = std::filesystem::relative( + absolutePath, + GetRepoRootPath(), + errorCode); + if (errorCode || outRelativePath.empty()) { + return false; + } + + for (const auto& part : outRelativePath) { + if (part == "..") { + return false; + } + } + return true; +} + +std::filesystem::path ResolveCaptureOutputRoot( + const std::filesystem::path& sourceCaptureRoot) { + const std::filesystem::path normalizedSourcePath = + sourceCaptureRoot.lexically_normal(); + std::filesystem::path relativePath = {}; + if (TryMakeRepoRelativePath(normalizedSourcePath, relativePath)) { + return (GetBuildRootPath() / relativePath).lexically_normal(); + } + + return (GetBuildRootPath() / "ui_test_captures" / normalizedSourcePath.filename()) + .lexically_normal(); +} + std::string TruncateText(const std::string& text, std::size_t maxLength) { if (text.size() <= maxLength) { return text; @@ -230,7 +287,8 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { initialScenario = &GetDefaultEditorValidationScenario(); } - m_autoScreenshot.Initialize(initialScenario->captureRootPath); + m_autoScreenshot.Initialize( + ResolveCaptureOutputRoot(initialScenario->captureRootPath)); LoadStructuredScreen("startup"); return true; } @@ -406,7 +464,6 @@ bool Application::LoadStructuredScreen(const char* triggerReason) { m_screenAsset = {}; m_screenAsset.screenId = scenario->id; m_screenAsset.documentPath = scenario->documentPath.string(); - m_screenAsset.themePath = scenario->themePath.string(); const bool loaded = m_screenPlayer.Load(m_screenAsset); m_useStructuredScreen = loaded; @@ -463,7 +520,6 @@ void Application::RebuildTrackedFileStates() { }; appendTrackedPath(m_screenAsset.documentPath); - appendTrackedPath(m_screenAsset.themePath); if (const auto* document = m_screenPlayer.GetDocument(); document != nullptr) { for (const std::string& dependency : document->dependencies) { diff --git a/tests/UI/Editor/integration/shared/src/EditorValidationScenario.cpp b/tests/UI/Editor/integration/shared/src/EditorValidationScenario.cpp index 668a82aa..f3b78612 100644 --- a/tests/UI/Editor/integration/shared/src/EditorValidationScenario.cpp +++ b/tests/UI/Editor/integration/shared/src/EditorValidationScenario.cpp @@ -33,7 +33,6 @@ const std::array& GetEditorValidationScenarios() { "shell", "Editor 壳层 | 工作区组合", RepoRelative("tests/UI/Editor/integration/shell/workspace_shell_compose/View.xcui"), - RepoRelative("tests/UI/Editor/integration/shared/themes/editor_validation.xctheme"), RepoRelative("tests/UI/Editor/integration/shell/workspace_shell_compose/captures") } } }; diff --git a/tests/UI/Editor/integration/shared/src/EditorValidationScenario.h b/tests/UI/Editor/integration/shared/src/EditorValidationScenario.h index b50c2708..d8a842b5 100644 --- a/tests/UI/Editor/integration/shared/src/EditorValidationScenario.h +++ b/tests/UI/Editor/integration/shared/src/EditorValidationScenario.h @@ -16,7 +16,6 @@ struct EditorValidationScenario { std::string categoryId = {}; std::string displayName = {}; std::filesystem::path documentPath = {}; - std::filesystem::path themePath = {}; std::filesystem::path captureRootPath = {}; }; diff --git a/tests/UI/Editor/integration/shared/src/EditorValidationTheme.h b/tests/UI/Editor/integration/shared/src/EditorValidationTheme.h index 95c8e2ab..99582fa3 100644 --- a/tests/UI/Editor/integration/shared/src/EditorValidationTheme.h +++ b/tests/UI/Editor/integration/shared/src/EditorValidationTheme.h @@ -1,25 +1,9 @@ #pragma once -#include -#include -#include #include -#include -#include - -#include -#include -#include -#include namespace XCEngine::Tests::EditorUI { -struct EditorValidationThemeLoadResult { - ::XCEngine::UI::Style::UITheme theme = {}; - std::string error = {}; - bool succeeded = false; -}; - struct EditorValidationShellPalette { ::XCEngine::UI::UIColor windowBackground = ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); ::XCEngine::UI::UIColor cardBackground = ::XCEngine::UI::UIColor(0.18f, 0.18f, 0.18f, 1.0f); @@ -41,178 +25,12 @@ struct EditorValidationShellMetrics { float bodyFontSize = 12.0f; }; -inline ::XCEngine::UI::UIColor ToUIColor(const ::XCEngine::Math::Color& color) { - return ::XCEngine::UI::UIColor(color.r, color.g, color.b, color.a); +inline EditorValidationShellPalette GetEditorValidationShellPalette() { + return {}; } -inline bool TryResolveThemeFloat( - const ::XCEngine::UI::Style::UITheme& theme, - std::string_view tokenName, - float& outValue) { - const auto resolution = - theme.ResolveToken(std::string(tokenName), ::XCEngine::UI::Style::UIStyleValueType::Float); - if (resolution.status != ::XCEngine::UI::Style::UITokenResolveStatus::Resolved) { - return false; - } - - const float* value = resolution.value.TryGetFloat(); - if (value == nullptr) { - return false; - } - - outValue = *value; - return true; -} - -inline bool TryResolveThemeColor( - const ::XCEngine::UI::Style::UITheme& theme, - std::string_view tokenName, - ::XCEngine::UI::UIColor& outColor) { - const auto resolution = - theme.ResolveToken(std::string(tokenName), ::XCEngine::UI::Style::UIStyleValueType::Color); - if (resolution.status != ::XCEngine::UI::Style::UITokenResolveStatus::Resolved) { - return false; - } - - const ::XCEngine::Math::Color* value = resolution.value.TryGetColor(); - if (value == nullptr) { - return false; - } - - outColor = ToUIColor(*value); - return true; -} - -inline float ResolveThemeFloatAliases( - const ::XCEngine::UI::Style::UITheme& theme, - std::initializer_list tokenNames, - float fallbackValue) { - float resolvedValue = fallbackValue; - for (std::string_view tokenName : tokenNames) { - if (TryResolveThemeFloat(theme, tokenName, resolvedValue)) { - return resolvedValue; - } - } - - return fallbackValue; -} - -inline ::XCEngine::UI::UIColor ResolveThemeColorAliases( - const ::XCEngine::UI::Style::UITheme& theme, - std::initializer_list tokenNames, - const ::XCEngine::UI::UIColor& fallbackValue) { - ::XCEngine::UI::UIColor resolvedValue = fallbackValue; - for (std::string_view tokenName : tokenNames) { - if (TryResolveThemeColor(theme, tokenName, resolvedValue)) { - return resolvedValue; - } - } - - return fallbackValue; -} - -inline EditorValidationThemeLoadResult LoadEditorValidationTheme( - const std::filesystem::path& themePath) { - EditorValidationThemeLoadResult result = {}; - - ::XCEngine::Resources::UIDocumentCompileResult compileResult = {}; - const ::XCEngine::Containers::String pathString(themePath.generic_string().c_str()); - if (!::XCEngine::Resources::CompileUIDocument( - ::XCEngine::Resources::UIDocumentCompileRequest { - ::XCEngine::Resources::UIDocumentKind::Theme, - pathString, - ::XCEngine::Resources::GetUIDocumentDefaultRootTag( - ::XCEngine::Resources::UIDocumentKind::Theme) - }, - compileResult)) { - result.error = compileResult.errorMessage.Empty() - ? std::string("Failed to compile editor validation theme document.") - : std::string(compileResult.errorMessage.CStr()); - return result; - } - - const auto styleCompileResult = - ::XCEngine::UI::Style::CompileDocumentStyle(compileResult.document); - if (!styleCompileResult.succeeded) { - result.error = styleCompileResult.errorMessage; - return result; - } - - result.theme = styleCompileResult.theme; - result.succeeded = true; - return result; -} - -inline EditorValidationShellPalette ResolveEditorValidationShellPalette( - const ::XCEngine::UI::Style::UITheme& theme) { - EditorValidationShellPalette palette = {}; - palette.windowBackground = ResolveThemeColorAliases( - theme, - { "editor.color.validation.window", "color.bg.workspace" }, - palette.windowBackground); - palette.cardBackground = ResolveThemeColorAliases( - theme, - { "editor.color.validation.card", "color.bg.panel" }, - palette.cardBackground); - palette.cardBorder = ResolveThemeColorAliases( - theme, - { "editor.color.validation.card_border", "editor.color.menu_popup.border" }, - palette.cardBorder); - palette.textPrimary = ResolveThemeColorAliases( - theme, - { "editor.color.validation.text_primary", "color.text.primary" }, - palette.textPrimary); - palette.textMuted = ResolveThemeColorAliases( - theme, - { "editor.color.validation.text_muted", "color.text.muted" }, - palette.textMuted); - palette.textWeak = ResolveThemeColorAliases( - theme, - { "editor.color.validation.text_weak", "color.text.muted" }, - palette.textWeak); - palette.textSuccess = ResolveThemeColorAliases( - theme, - { "editor.color.validation.text_success" }, - palette.textSuccess); - palette.buttonBackground = ResolveThemeColorAliases( - theme, - { "editor.color.validation.button", "color.bg.selection" }, - palette.buttonBackground); - palette.buttonHoverBackground = ResolveThemeColorAliases( - theme, - { "editor.color.validation.button_hover", "editor.color.validation.button", "color.bg.selection" }, - palette.buttonHoverBackground); - return palette; -} - -inline EditorValidationShellMetrics ResolveEditorValidationShellMetrics( - const ::XCEngine::UI::Style::UITheme& theme) { - EditorValidationShellMetrics metrics = {}; - metrics.margin = ResolveThemeFloatAliases( - theme, - { "editor.space.validation.margin", "space.shell" }, - metrics.margin); - metrics.gap = ResolveThemeFloatAliases( - theme, - { "editor.space.validation.gap" }, - metrics.gap); - metrics.cardRadius = ResolveThemeFloatAliases( - theme, - { "editor.radius.validation.card", "radius.panel" }, - metrics.cardRadius); - metrics.buttonRadius = ResolveThemeFloatAliases( - theme, - { "editor.radius.validation.button", "radius.control" }, - metrics.buttonRadius); - metrics.titleFontSize = ResolveThemeFloatAliases( - theme, - { "editor.font.validation.title" }, - metrics.titleFontSize); - metrics.bodyFontSize = ResolveThemeFloatAliases( - theme, - { "editor.font.validation.body", "editor.font.field.value" }, - metrics.bodyFontSize); - return metrics; +inline EditorValidationShellMetrics GetEditorValidationShellMetrics() { + return {}; } } // namespace XCEngine::Tests::EditorUI diff --git a/tests/UI/Editor/integration/shared/themes/editor_validation.xctheme b/tests/UI/Editor/integration/shared/themes/editor_validation.xctheme deleted file mode 100644 index e58c984a..00000000 --- a/tests/UI/Editor/integration/shared/themes/editor_validation.xctheme +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/UI/Editor/integration/shell/CMakeLists.txt b/tests/UI/Editor/integration/shell/CMakeLists.txt index 3d7cc449..4b6095fa 100644 --- a/tests/UI/Editor/integration/shell/CMakeLists.txt +++ b/tests/UI/Editor/integration/shell/CMakeLists.txt @@ -25,6 +25,12 @@ endif() if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/number_field_basic/CMakeLists.txt") add_subdirectory(number_field_basic) endif() +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/asset_field_basic/CMakeLists.txt") + add_subdirectory(asset_field_basic) +endif() +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/object_field_basic/CMakeLists.txt") + add_subdirectory(object_field_basic) +endif() if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/text_field_basic/CMakeLists.txt") add_subdirectory(text_field_basic) endif() diff --git a/tests/UI/Editor/integration/shell/asset_field_basic/CMakeLists.txt b/tests/UI/Editor/integration/shell/asset_field_basic/CMakeLists.txt new file mode 100644 index 00000000..7ee89770 --- /dev/null +++ b/tests/UI/Editor/integration/shell/asset_field_basic/CMakeLists.txt @@ -0,0 +1,31 @@ +add_executable(editor_ui_asset_field_basic_validation WIN32 + main.cpp +) + +target_include_directories(editor_ui_asset_field_basic_validation PRIVATE + ${CMAKE_SOURCE_DIR}/tests/UI/Editor/integration/shared/src + ${CMAKE_SOURCE_DIR}/new_editor/include + ${CMAKE_SOURCE_DIR}/new_editor/app + ${CMAKE_SOURCE_DIR}/engine/include +) + +target_compile_definitions(editor_ui_asset_field_basic_validation PRIVATE + UNICODE + _UNICODE + XCENGINE_EDITOR_UI_TESTS_REPO_ROOT="${XCENGINE_EDITOR_UI_TESTS_REPO_ROOT_PATH}" +) + +if(MSVC) + target_compile_options(editor_ui_asset_field_basic_validation PRIVATE /utf-8 /FS) + set_property(TARGET editor_ui_asset_field_basic_validation PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") +endif() + +target_link_libraries(editor_ui_asset_field_basic_validation PRIVATE + XCUIEditorLib + XCUIEditorHost +) + +set_target_properties(editor_ui_asset_field_basic_validation PROPERTIES + OUTPUT_NAME "XCUIEditorAssetFieldBasicValidation" +) diff --git a/tests/UI/Editor/integration/shell/asset_field_basic/captures/.gitkeep b/tests/UI/Editor/integration/shell/asset_field_basic/captures/.gitkeep new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/tests/UI/Editor/integration/shell/asset_field_basic/captures/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/UI/Editor/integration/shell/asset_field_basic/main.cpp b/tests/UI/Editor/integration/shell/asset_field_basic/main.cpp new file mode 100644 index 00000000..0cc3e61f --- /dev/null +++ b/tests/UI/Editor/integration/shell/asset_field_basic/main.cpp @@ -0,0 +1,736 @@ +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include +#include +#include +#include "EditorValidationTheme.h" +#include "Host/AutoScreenshot.h" +#include "Host/NativeRenderer.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifndef XCENGINE_EDITOR_UI_TESTS_REPO_ROOT +#define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "." +#endif + +namespace { + +using XCEngine::Input::KeyCode; +using XCEngine::UI::UIColor; +using XCEngine::UI::UIDrawData; +using XCEngine::UI::UIDrawList; +using XCEngine::UI::UIInputEvent; +using XCEngine::UI::UIInputEventType; +using XCEngine::UI::UIPoint; +using XCEngine::UI::UIPointerButton; +using XCEngine::UI::UIRect; +using XCEngine::UI::Editor::Host::AutoScreenshotController; +using XCEngine::UI::Editor::Host::NativeRenderer; +using XCEngine::UI::Editor::UIEditorAssetFieldInteractionFrame; +using XCEngine::UI::Editor::UIEditorAssetFieldInteractionResult; +using XCEngine::UI::Editor::UIEditorAssetFieldInteractionState; +using XCEngine::UI::Editor::UpdateUIEditorAssetFieldInteraction; +using XCEngine::UI::Editor::Widgets::AppendUIEditorAssetField; +using XCEngine::UI::Editor::Widgets::HitTestUIEditorAssetField; +using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldHitTarget; +using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldHitTargetKind; +using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldPalette; +using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldSpec; + +constexpr const wchar_t* kWindowClassName = L"XCUIEditorAssetFieldBasicValidation"; +constexpr const wchar_t* kWindowTitle = L"XCUI Editor | AssetField Basic"; + +enum class ActionId : unsigned char { + None = 0, + Reset, + Capture +}; + +struct ButtonLayout { + ActionId action = ActionId::None; + const char* label = ""; + UIRect rect = {}; +}; + +struct ScenarioLayout { + UIRect introRect = {}; + UIRect controlRect = {}; + UIRect stateRect = {}; + UIRect previewRect = {}; + UIRect fieldRect = {}; + std::vector buttons = {}; +}; + +struct SampleAsset { + const char* assetId = ""; + const char* displayName = ""; + const char* statusText = ""; + UIColor tint = {}; +}; + +constexpr SampleAsset kSampleAssets[] = { + { + "assets/textures/crate_albedo", + "Crate_Albedo", + "Ready", + UIColor(0.30f, 0.55f, 0.84f, 1.0f) + }, + { + "assets/materials/sci_fi_panel", + "SciFi_Panel", + "Dirty", + UIColor(0.77f, 0.56f, 0.28f, 1.0f) + } +}; + +std::filesystem::path ResolveRepoRootPath() { + std::string root = XCENGINE_EDITOR_UI_TESTS_REPO_ROOT; + if (root.size() >= 2u && root.front() == '"' && root.back() == '"') { + root = root.substr(1u, root.size() - 2u); + } + + return std::filesystem::path(root).lexically_normal(); +} + +bool ContainsPoint(const UIRect& rect, float x, float y) { + return x >= rect.x && + x <= rect.x + rect.width && + y >= rect.y && + y <= rect.y + rect.height; +} + +std::int32_t MapAssetFieldKey(UINT keyCode) { + switch (keyCode) { + case VK_SPACE: + return static_cast(KeyCode::Space); + case VK_RETURN: + return static_cast(KeyCode::Enter); + case VK_DELETE: + return static_cast(KeyCode::Delete); + case VK_BACK: + return static_cast(KeyCode::Backspace); + default: + return static_cast(KeyCode::None); + } +} + +ScenarioLayout BuildScenarioLayout( + float width, + float height, + const XCEngine::Tests::EditorUI::EditorValidationShellMetrics& shellMetrics) { + const float margin = shellMetrics.margin; + constexpr float leftWidth = 470.0f; + const float gap = shellMetrics.gap; + + ScenarioLayout layout = {}; + layout.introRect = UIRect(margin, margin, leftWidth, 260.0f); + layout.controlRect = UIRect(margin, layout.introRect.y + layout.introRect.height + gap, leftWidth, 84.0f); + layout.stateRect = UIRect( + margin, + layout.controlRect.y + layout.controlRect.height + gap, + leftWidth, + (std::max)(220.0f, height - (layout.controlRect.y + layout.controlRect.height + gap) - margin)); + layout.previewRect = UIRect( + leftWidth + margin * 2.0f, + margin, + (std::max)(420.0f, width - leftWidth - margin * 3.0f), + height - margin * 2.0f); + layout.fieldRect = UIRect( + layout.previewRect.x + 28.0f, + layout.previewRect.y + 96.0f, + (std::min)(460.0f, layout.previewRect.width - 56.0f), + 22.0f); + + const float buttonWidth = (layout.controlRect.width - 44.0f) * 0.5f; + const float buttonY = layout.controlRect.y + 32.0f; + layout.buttons = { + { ActionId::Reset, "重置", UIRect(layout.controlRect.x + 14.0f, buttonY, buttonWidth, 36.0f) }, + { ActionId::Capture, "截图(F12)", UIRect(layout.controlRect.x + 26.0f + buttonWidth, buttonY, buttonWidth, 36.0f) } + }; + return layout; +} + +void DrawCard( + UIDrawList& drawList, + const UIRect& rect, + const XCEngine::Tests::EditorUI::EditorValidationShellPalette& shellPalette, + const XCEngine::Tests::EditorUI::EditorValidationShellMetrics& shellMetrics, + std::string_view title, + std::string_view subtitle = {}) { + drawList.AddFilledRect(rect, shellPalette.cardBackground, shellMetrics.cardRadius); + drawList.AddRectOutline(rect, shellPalette.cardBorder, 1.0f, shellMetrics.cardRadius); + drawList.AddText( + UIPoint(rect.x + 16.0f, rect.y + 14.0f), + std::string(title), + shellPalette.textPrimary, + shellMetrics.titleFontSize); + if (!subtitle.empty()) { + drawList.AddText( + UIPoint(rect.x + 16.0f, rect.y + 40.0f), + std::string(subtitle), + shellPalette.textMuted, + shellMetrics.bodyFontSize); + } +} + +void DrawButton( + UIDrawList& drawList, + const ButtonLayout& button, + const XCEngine::Tests::EditorUI::EditorValidationShellPalette& shellPalette, + const XCEngine::Tests::EditorUI::EditorValidationShellMetrics& shellMetrics, + bool hovered) { + drawList.AddFilledRect( + button.rect, + hovered ? shellPalette.buttonHoverBackground : shellPalette.buttonBackground, + shellMetrics.buttonRadius); + drawList.AddRectOutline(button.rect, shellPalette.cardBorder, 1.0f, shellMetrics.buttonRadius); + drawList.AddText( + UIPoint(button.rect.x + 16.0f, button.rect.y + 10.0f), + button.label, + shellPalette.textPrimary, + shellMetrics.bodyFontSize); +} + +std::string DescribeHitTarget(const UIEditorAssetFieldHitTarget& hitTarget) { + switch (hitTarget.kind) { + case UIEditorAssetFieldHitTargetKind::ValueBox: + return "value_box"; + case UIEditorAssetFieldHitTargetKind::PickerButton: + return "picker_button"; + case UIEditorAssetFieldHitTargetKind::ClearButton: + return "clear_button"; + case UIEditorAssetFieldHitTargetKind::Row: + return "row"; + case UIEditorAssetFieldHitTargetKind::None: + default: + return "none"; + } +} + +UIInputEvent MakePointerEvent( + UIInputEventType type, + const UIPoint& position, + UIPointerButton button = UIPointerButton::None) { + UIInputEvent event = {}; + event.type = type; + event.position = position; + event.pointerButton = button; + return event; +} + +UIInputEvent MakeKeyEvent(std::int32_t keyCode) { + UIInputEvent event = {}; + event.type = UIInputEventType::KeyDown; + event.keyCode = keyCode; + return event; +} + +UIInputEvent MakeFocusEvent(UIInputEventType type) { + UIInputEvent event = {}; + event.type = type; + return event; +} + +class ScenarioApp { +public: + int Run(HINSTANCE hInstance, int nCmdShow) { + if (!Initialize(hInstance, nCmdShow)) { + Shutdown(); + return 1; + } + + MSG message = {}; + while (message.message != WM_QUIT) { + if (PeekMessageW(&message, nullptr, 0U, 0U, PM_REMOVE)) { + TranslateMessage(&message); + DispatchMessageW(&message); + continue; + } + + RenderFrame(); + Sleep(8); + } + + Shutdown(); + return static_cast(message.wParam); + } + +private: + static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { + if (message == WM_NCCREATE) { + const auto* createStruct = reinterpret_cast(lParam); + auto* app = static_cast(createStruct->lpCreateParams); + SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(app)); + return TRUE; + } + + auto* app = reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); + if (app == nullptr) { + return DefWindowProcW(hwnd, message, wParam, lParam); + } + return app->HandleMessage(hwnd, message, wParam, lParam); + } + + bool Initialize(HINSTANCE hInstance, int nCmdShow) { + m_hInstance = hInstance; + + WNDCLASSEXW windowClass = {}; + windowClass.cbSize = sizeof(windowClass); + windowClass.style = CS_HREDRAW | CS_VREDRAW; + windowClass.lpfnWndProc = &ScenarioApp::WndProc; + windowClass.hInstance = hInstance; + windowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW); + windowClass.lpszClassName = kWindowClassName; + + m_windowClassAtom = RegisterClassExW(&windowClass); + if (m_windowClassAtom == 0) { + return false; + } + + m_hwnd = CreateWindowExW( + 0, + kWindowClassName, + kWindowTitle, + WS_OVERLAPPEDWINDOW | WS_VISIBLE, + CW_USEDEFAULT, + CW_USEDEFAULT, + 1440, + 860, + nullptr, + nullptr, + hInstance, + this); + if (m_hwnd == nullptr) { + return false; + } + + ShowWindow(m_hwnd, nCmdShow); + UpdateWindow(m_hwnd); + + if (!m_renderer.Initialize(m_hwnd)) { + return false; + } + + m_shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); + m_shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); + m_fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorAssetFieldMetrics(); + m_fieldPalette = XCEngine::UI::Editor::ResolveUIEditorAssetFieldPalette(); + + m_captureRoot = + ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/asset_field_basic/captures"; + m_autoScreenshot.Initialize(m_captureRoot); + ResetScenario(); + return true; + } + + void Shutdown() { + m_autoScreenshot.Shutdown(); + m_renderer.Shutdown(); + + if (m_hwnd != nullptr && IsWindow(m_hwnd)) { + DestroyWindow(m_hwnd); + } + m_hwnd = nullptr; + + if (m_windowClassAtom != 0 && m_hInstance != nullptr) { + UnregisterClassW(kWindowClassName, m_hInstance); + m_windowClassAtom = 0; + } + } + + void ResetScenario() { + m_spec = {}; + m_spec.fieldId = "renderer.base_map"; + m_spec.label = "Base Map"; + m_spec.emptyText = "None (Asset)"; + ApplySampleAsset(0u); + m_interactionState = {}; + m_lastResult = "已重置到默认 AssetField 状态"; + m_activateCount = 0u; + m_pressedAction = ActionId::None; + } + + void ApplySampleAsset(std::size_t sampleIndex) { + m_currentSampleIndex = sampleIndex % std::size(kSampleAssets); + const SampleAsset& sample = kSampleAssets[m_currentSampleIndex]; + m_spec.assetId = sample.assetId; + m_spec.displayName = sample.displayName; + m_spec.statusText = sample.statusText; + m_spec.tint = sample.tint; + } + + void ApplyNextSampleAsset() { + ApplySampleAsset(m_currentSampleIndex + 1u); + } + + ScenarioLayout GetLayout() const { + RECT clientRect = {}; + GetClientRect(m_hwnd, &clientRect); + const float width = static_cast((std::max)(clientRect.right - clientRect.left, 1L)); + const float height = static_cast((std::max)(clientRect.bottom - clientRect.top, 1L)); + return BuildScenarioLayout(width, height, m_shellMetrics); + } + + void RefreshFrame(const UIRect& fieldRect) { + m_frame = UpdateUIEditorAssetFieldInteraction( + m_interactionState, + m_spec, + fieldRect, + {}, + m_fieldMetrics); + } + + void RenderFrame() { + if (m_hwnd == nullptr) { + return; + } + + RECT clientRect = {}; + GetClientRect(m_hwnd, &clientRect); + const float width = static_cast((std::max)(clientRect.right - clientRect.left, 1L)); + const float height = static_cast((std::max)(clientRect.bottom - clientRect.top, 1L)); + const ScenarioLayout layout = BuildScenarioLayout(width, height, m_shellMetrics); + + std::vector events = std::move(m_pendingInputEvents); + m_pendingInputEvents.clear(); + + m_frame = UpdateUIEditorAssetFieldInteraction( + m_interactionState, + m_spec, + layout.fieldRect, + events, + m_fieldMetrics); + ApplyInteractionResult(m_frame.result, layout.fieldRect); + + const UIEditorAssetFieldHitTarget currentHit = + HitTestUIEditorAssetField(m_frame.layout, m_mousePosition); + + UIDrawData drawData = {}; + UIDrawList& drawList = drawData.EmplaceDrawList("EditorAssetFieldBasic"); + drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), m_shellPalette.windowBackground); + + DrawCard( + drawList, + layout.introRect, + m_shellPalette, + m_shellMetrics, + "这个测试在验证什么功能?", + "验证 Editor 基础 AssetField 的固定风格、清晰 hit target,以及 activate / picker / clear 三类最小请求。"); + drawList.AddText( + UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 74.0f), + "1. 字段视觉固定为 Unity 风格对象槽:标签、预览块、值文本、状态标记、选择按钮、清空按钮。", + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 98.0f), + "2. 点击值区域只产生 activateRequested;基础层不决定 ping、打开面板或业务跳转。", + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 122.0f), + "3. 点击 o,或 focused 后按 Enter / Space,只产生 pickerRequested。", + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 146.0f), + "4. 点击 X,或 focused 后按 Delete / Backspace,清空当前引用。", + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 170.0f), + "5. 本壳程序只用两个固定样本资产模拟 picker 外部响应,不引入 object picker 弹窗系统。", + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 194.0f), + "6. 左侧状态区观察 hover / focus / assetId / displayName / status / result;F12 截图。", + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + + DrawCard(drawList, layout.controlRect, m_shellPalette, m_shellMetrics, "操作"); + for (const ButtonLayout& button : layout.buttons) { + DrawButton( + drawList, + button, + m_shellPalette, + m_shellMetrics, + ContainsPoint(button.rect, m_mousePosition.x, m_mousePosition.y)); + } + + DrawCard( + drawList, + layout.stateRect, + m_shellPalette, + m_shellMetrics, + "状态摘要", + "重点看 hit、focus、值是否变化,以及请求是否保持薄语义。"); + drawList.AddText( + UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 72.0f), + "Hover: " + DescribeHitTarget(currentHit), + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 96.0f), + std::string("Focused: ") + (m_interactionState.fieldState.focused ? "yes" : "no"), + m_interactionState.fieldState.focused ? m_shellPalette.textSuccess : m_shellPalette.textMuted, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 120.0f), + "AssetId: " + (m_spec.assetId.empty() ? std::string("(empty)") : m_spec.assetId), + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 144.0f), + "Display: " + (m_spec.displayName.empty() ? std::string("(empty)") : m_spec.displayName), + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 168.0f), + "Status: " + (m_spec.statusText.empty() ? std::string("(empty)") : m_spec.statusText), + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 192.0f), + "Activate Count: " + std::to_string(m_activateCount), + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 216.0f), + "Result: " + m_lastResult, + m_shellPalette.textMuted, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 240.0f), + m_autoScreenshot.HasPendingCapture() + ? "截图排队中..." + : (m_autoScreenshot.GetLastCaptureSummary().empty() + ? std::string("F12 -> tests/UI/Editor/integration/shell/asset_field_basic/captures/") + : m_autoScreenshot.GetLastCaptureSummary()), + m_shellPalette.textWeak, + m_shellMetrics.bodyFontSize); + + DrawCard( + drawList, + layout.previewRect, + m_shellPalette, + m_shellMetrics, + "AssetField 预览", + "这里只放一个基础 AssetField,不接业务面板,也不接 picker。"); + AppendUIEditorAssetField( + drawList, + layout.fieldRect, + m_spec, + m_interactionState.fieldState, + m_fieldPalette, + m_fieldMetrics); + + const bool framePresented = m_renderer.Render(drawData); + m_autoScreenshot.CaptureIfRequested( + m_renderer, + drawData, + static_cast(width), + static_cast(height), + framePresented); + } + + void ApplyInteractionResult( + const UIEditorAssetFieldInteractionResult& result, + const UIRect& fieldRect) { + bool refreshNeeded = false; + + if (result.pickerRequested) { + ApplyNextSampleAsset(); + m_lastResult = "收到 pickerRequested,壳程序已用固定样本资产模拟外部选择结果"; + refreshNeeded = true; + } else if (result.clearRequested) { + m_lastResult = "收到 clearRequested,当前引用已清空"; + refreshNeeded = true; + } else if (result.activateRequested) { + ++m_activateCount; + m_lastResult = "收到 activateRequested;基础层只发请求,不绑定业务动作"; + } else if (result.focusChanged) { + m_lastResult = std::string("焦点变化: ") + + (m_interactionState.fieldState.focused ? "focused" : "lost"); + } else if (result.consumed) { + m_lastResult = "输入已被当前字段消费"; + } + + if (refreshNeeded) { + RefreshFrame(fieldRect); + } + } + + void ExecuteAction(ActionId action) { + switch (action) { + case ActionId::Reset: + ResetScenario(); + break; + + case ActionId::Capture: + m_autoScreenshot.RequestCapture("manual_button"); + m_lastResult = "已请求截图,输出到 captures/latest.png"; + break; + + case ActionId::None: + default: + break; + } + } + + const ButtonLayout* HitTestAction(const ScenarioLayout& layout, float x, float y) const { + for (const ButtonLayout& button : layout.buttons) { + if (ContainsPoint(button.rect, x, y)) { + return &button; + } + } + return nullptr; + } + + LRESULT HandleMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { + switch (message) { + case WM_CLOSE: + DestroyWindow(hwnd); + return 0; + + case WM_DESTROY: + PostQuitMessage(0); + return 0; + + case WM_SIZE: + if (wParam != SIZE_MINIMIZED) { + m_renderer.Resize(static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))); + } + return 0; + + case WM_MOUSEMOVE: { + m_mousePosition = UIPoint( + static_cast(GET_X_LPARAM(lParam)), + static_cast(GET_Y_LPARAM(lParam))); + TRACKMOUSEEVENT trackEvent = {}; + trackEvent.cbSize = sizeof(trackEvent); + trackEvent.dwFlags = TME_LEAVE; + trackEvent.hwndTrack = hwnd; + TrackMouseEvent(&trackEvent); + + m_pendingInputEvents.push_back(MakePointerEvent(UIInputEventType::PointerMove, m_mousePosition)); + return 0; + } + + case WM_MOUSELEAVE: + m_mousePosition = UIPoint(-1000.0f, -1000.0f); + m_pendingInputEvents.push_back(MakePointerEvent(UIInputEventType::PointerLeave, m_mousePosition)); + return 0; + + case WM_LBUTTONDOWN: { + SetFocus(hwnd); + m_mousePosition = UIPoint( + static_cast(GET_X_LPARAM(lParam)), + static_cast(GET_Y_LPARAM(lParam))); + const ScenarioLayout layout = GetLayout(); + const ButtonLayout* button = HitTestAction(layout, m_mousePosition.x, m_mousePosition.y); + m_pressedAction = button != nullptr ? button->action : ActionId::None; + if (button == nullptr) { + m_pendingInputEvents.push_back( + MakePointerEvent(UIInputEventType::PointerButtonDown, m_mousePosition, UIPointerButton::Left)); + } + return 0; + } + + case WM_LBUTTONUP: { + m_mousePosition = UIPoint( + static_cast(GET_X_LPARAM(lParam)), + static_cast(GET_Y_LPARAM(lParam))); + const ScenarioLayout layout = GetLayout(); + const ButtonLayout* button = HitTestAction(layout, m_mousePosition.x, m_mousePosition.y); + if (m_pressedAction != ActionId::None && + button != nullptr && + button->action == m_pressedAction) { + ExecuteAction(button->action); + } else { + m_pendingInputEvents.push_back( + MakePointerEvent(UIInputEventType::PointerButtonUp, m_mousePosition, UIPointerButton::Left)); + } + m_pressedAction = ActionId::None; + return 0; + } + + case WM_SETFOCUS: + m_pendingInputEvents.push_back(MakeFocusEvent(UIInputEventType::FocusGained)); + return 0; + + case WM_KILLFOCUS: + m_pendingInputEvents.push_back(MakeFocusEvent(UIInputEventType::FocusLost)); + return 0; + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + if (wParam == VK_F12) { + m_autoScreenshot.RequestCapture("manual_f12"); + m_lastResult = "已请求截图,输出到 captures/latest.png"; + return 0; + } + if (wParam == VK_F6) { + m_pendingInputEvents.push_back(MakeFocusEvent(UIInputEventType::FocusLost)); + return 0; + } + if (const std::int32_t keyCode = MapAssetFieldKey(static_cast(wParam)); + keyCode != static_cast(KeyCode::None)) { + m_pendingInputEvents.push_back(MakeKeyEvent(keyCode)); + return 0; + } + break; + + case WM_PAINT: + RenderFrame(); + ValidateRect(hwnd, nullptr); + return 0; + + case WM_ERASEBKGND: + return 1; + + default: + break; + } + + return DefWindowProcW(hwnd, message, wParam, lParam); + } + + HWND m_hwnd = nullptr; + HINSTANCE m_hInstance = nullptr; + ATOM m_windowClassAtom = 0; + NativeRenderer m_renderer = {}; + AutoScreenshotController m_autoScreenshot = {}; + std::filesystem::path m_captureRoot = {}; + XCEngine::Tests::EditorUI::EditorValidationShellPalette m_shellPalette = {}; + XCEngine::Tests::EditorUI::EditorValidationShellMetrics m_shellMetrics = {}; + UIEditorAssetFieldSpec m_spec = {}; + UIEditorAssetFieldInteractionState m_interactionState = {}; + UIEditorAssetFieldInteractionFrame m_frame = {}; + UIEditorAssetFieldPalette m_fieldPalette = {}; + XCEngine::UI::Editor::Widgets::UIEditorAssetFieldMetrics m_fieldMetrics = {}; + std::vector m_pendingInputEvents = {}; + UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f); + ActionId m_pressedAction = ActionId::None; + std::string m_lastResult = {}; + std::size_t m_currentSampleIndex = 0u; + std::size_t m_activateCount = 0u; +}; + +} // namespace + +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) { + return ScenarioApp().Run(hInstance, nCmdShow); +} diff --git a/tests/UI/Editor/integration/shell/bool_field_basic/main.cpp b/tests/UI/Editor/integration/shell/bool_field_basic/main.cpp index c302669d..1f0e9705 100644 --- a/tests/UI/Editor/integration/shell/bool_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/bool_field_basic/main.cpp @@ -1,10 +1,10 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include -#include -#include +#include +#include +#include #include "EditorValidationTheme.h" #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -47,7 +47,6 @@ using XCEngine::UI::Editor::Widgets::HitTestUIEditorBoolField; using XCEngine::UI::Editor::Widgets::UIEditorBoolFieldHitTarget; using XCEngine::UI::Editor::Widgets::UIEditorBoolFieldHitTargetKind; using XCEngine::UI::Editor::Widgets::UIEditorBoolFieldSpec; -namespace Style = XCEngine::UI::Style; constexpr const wchar_t* kWindowClassName = L"XCUIEditorBoolFieldBasicValidation"; constexpr const wchar_t* kWindowTitle = L"XCUI Editor | BoolField Basic"; @@ -80,10 +79,6 @@ std::filesystem::path ResolveRepoRootPath() { return std::filesystem::path(root).lexically_normal(); } -std::filesystem::path ResolveValidationThemePath() { - return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme") - .lexically_normal(); -} bool ContainsPoint(const UIRect& rect, float x, float y) { return x >= rect.x && @@ -371,14 +366,6 @@ private: m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/bool_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); - const auto themeLoad = - XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath()); - if (themeLoad.succeeded) { - m_theme = themeLoad.theme; - m_themeStatus = "loaded"; - } else { - m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error; - } ResetScenario(); return true; @@ -407,7 +394,7 @@ private: return BuildScenarioLayout( width, height, - XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme)); + XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics()); } void ResetScenario() { @@ -430,7 +417,7 @@ private: } const ScenarioLayout layout = GetLayout(); - const auto metrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics(m_theme); + const auto metrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics(); m_frame = UpdateUIEditorBoolFieldInteraction( m_interactionState, m_value, @@ -531,7 +518,7 @@ private: UIEditorBoolFieldInteractionResult PumpEvents(std::vector events) { const ScenarioLayout layout = GetLayout(); - const auto metrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics(m_theme); + const auto metrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics(); m_frame = UpdateUIEditorBoolFieldInteraction( m_interactionState, m_value, @@ -578,15 +565,15 @@ private: GetClientRect(m_hwnd, &clientRect); const float width = static_cast((std::max)(1L, clientRect.right - clientRect.left)); const float height = static_cast((std::max)(1L, clientRect.bottom - clientRect.top)); - const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme); - const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme); + const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); + const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics); RefreshFrame(); const UIEditorBoolFieldHitTarget currentHit = HitTestUIEditorBoolField(m_frame.layout, m_mousePosition); - const auto boolMetrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics(m_theme); - const auto boolPalette = XCEngine::UI::Editor::ResolveUIEditorBoolFieldPalette(m_theme); + const auto boolMetrics = XCEngine::UI::Editor::ResolveUIEditorBoolFieldMetrics(); + const auto boolPalette = XCEngine::UI::Editor::ResolveUIEditorBoolFieldPalette(); UIDrawData drawData = {}; UIDrawList& drawList = drawData.EmplaceDrawList("EditorBoolFieldBasic"); @@ -597,8 +584,8 @@ private: layout.introRect, shellPalette, shellMetrics, - "这个测试在验证什么功能", - "只验证 Editor BoolField 的基础交互契约,不涉及 PropertyGrid 或业务 Inspector。"); + "这个测试在验证什么功能?", + "验证 Editor BoolField 的点击切换、键盘切换和状态同步,样式固定为 Editor 字段风格。"); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), "1. 点击 row 或 checkbox,检查 true / false 是否稳定切换。", @@ -606,7 +593,7 @@ private: shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), - "2. 控件获得 focus 后按 Space / Enter,也必须能切换值。", + "2. 控件获得 focus 后按 Space / Enter,也必须能够切换值。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( @@ -644,12 +631,12 @@ private: shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 94.0f), - std::string("Focused: ") + (m_interactionState.fieldState.focused ? "开" : "关"), + std::string("Focused: ") + (m_interactionState.fieldState.focused ? "是" : "否"), shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 118.0f), - std::string("Active: ") + (m_interactionState.fieldState.active ? "开" : "关"), + std::string("Active: ") + (m_interactionState.fieldState.active ? "是" : "否"), shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( @@ -674,11 +661,6 @@ private: captureSummary, shellPalette.textWeak, shellMetrics.bodyFontSize); - drawList.AddText( - UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 214.0f), - "Theme: " + m_themeStatus, - shellPalette.textWeak, - shellMetrics.bodyFontSize); DrawCard( drawList, @@ -686,7 +668,7 @@ private: shellPalette, shellMetrics, "BoolField 预览", - "这里只放一个 Unity 风格 BoolField。"); + "这里只放一个固定样式 BoolField,不混入其他业务控件。"); UIEditorBoolFieldSpec previewSpec = m_spec; previewSpec.value = m_value; AppendUIEditorBoolField( @@ -715,12 +697,10 @@ private: bool m_value = false; UIEditorBoolFieldInteractionState m_interactionState = {}; UIEditorBoolFieldInteractionFrame m_frame = {}; - Style::UITheme m_theme = {}; UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f); ActionId m_hoveredAction = ActionId::Reset; bool m_hasHoveredAction = false; std::string m_lastResult = {}; - std::string m_themeStatus = "fallback"; }; } // namespace diff --git a/tests/UI/Editor/integration/shell/color_field_basic/main.cpp b/tests/UI/Editor/integration/shell/color_field_basic/main.cpp index f4a9d8ca..e7b50747 100644 --- a/tests/UI/Editor/integration/shell/color_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/color_field_basic/main.cpp @@ -1,10 +1,10 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include -#include -#include +#include +#include +#include #include "EditorValidationTheme.h" #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -48,8 +48,6 @@ using XCEngine::UI::Editor::Widgets::HitTestUIEditorColorField; using XCEngine::UI::Editor::Widgets::UIEditorColorFieldHitTarget; using XCEngine::UI::Editor::Widgets::UIEditorColorFieldHitTargetKind; using XCEngine::UI::Editor::Widgets::UIEditorColorFieldSpec; -namespace Style = XCEngine::UI::Style; - constexpr const wchar_t* kWindowClassName = L"XCUIEditorColorFieldBasicValidation"; constexpr const wchar_t* kWindowTitle = L"XCUI Editor | ColorField Basic"; @@ -81,11 +79,6 @@ std::filesystem::path ResolveRepoRootPath() { return std::filesystem::path(root).lexically_normal(); } -std::filesystem::path ResolveValidationThemePath() { - return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme") - .lexically_normal(); -} - bool IsTruthyEnvironmentFlag(const char* name) { const char* value = std::getenv(name); if (value == nullptr || value[0] == '\0') { @@ -135,7 +128,7 @@ ScenarioLayout BuildScenarioLayout( layout.previewRect.x + 28.0f, layout.previewRect.y + 72.0f, 360.0f, - 32.0f); + 22.0f); const float buttonWidth = (layout.controlRect.width - 44.0f) * 0.5f; const float buttonY = layout.controlRect.y + 32.0f; @@ -379,14 +372,6 @@ private: m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/color_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); - const auto themeLoad = - XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath()); - if (themeLoad.succeeded) { - m_theme = themeLoad.theme; - m_themeStatus = "loaded"; - } else { - m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error; - } ResetScenario(); if (IsTruthyEnvironmentFlag("XCUI_COLOR_FIELD_OPEN_POPUP_ON_STARTUP")) { @@ -405,6 +390,7 @@ private: }); m_lastResult = "已自动打开 ColorField 弹窗"; } + return true; } @@ -431,7 +417,7 @@ private: return BuildScenarioLayout( width, height, - XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme)); + XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics()); } UIRect GetViewportRect() const { @@ -465,7 +451,7 @@ private: } const ScenarioLayout layout = GetLayout(); - const auto metrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics(m_theme); + const auto metrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics(); m_frame = UpdateUIEditorColorFieldInteraction( m_interactionState, m_spec, @@ -558,7 +544,7 @@ private: UIEditorColorFieldInteractionResult PumpEvents(std::vector events) { const ScenarioLayout layout = GetLayout(); - const auto metrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics(m_theme); + const auto metrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics(); m_frame = UpdateUIEditorColorFieldInteraction( m_interactionState, m_spec, @@ -594,6 +580,7 @@ private: case ActionId::Reset: ResetScenario(); break; + case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); m_lastResult = "已请求截图,输出到 captures/latest.png"; @@ -602,16 +589,7 @@ private: } std::string BuildColorSummary() const { - char buffer[128] = {}; - std::snprintf( - buffer, - sizeof(buffer), - "R %.3f G %.3f B %.3f A %.3f", - m_spec.value.r, - m_spec.value.g, - m_spec.value.b, - m_spec.value.a); - return std::string(buffer); + return XCEngine::UI::Editor::Widgets::FormatUIEditorColorFieldRgbaText(m_spec); } void RenderFrame() { @@ -620,19 +598,20 @@ private: } const UIRect viewportRect = GetViewportRect(); - const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme); - const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme); - const ScenarioLayout layout = BuildScenarioLayout(viewportRect.width, viewportRect.height, shellMetrics); + const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); + const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); + const ScenarioLayout layout = BuildScenarioLayout( + viewportRect.width, + viewportRect.height, + shellMetrics); RefreshFrame(); - const UIEditorColorFieldHitTarget currentHit = - HitTestUIEditorColorField( - m_frame.layout, - m_interactionState.colorFieldState.popupOpen, - m_mousePosition); - - const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics(m_theme); - const auto fieldPalette = XCEngine::UI::Editor::ResolveUIEditorColorFieldPalette(m_theme); + const UIEditorColorFieldHitTarget currentHit = HitTestUIEditorColorField( + m_frame.layout, + m_interactionState.colorFieldState.popupOpen, + m_mousePosition); + const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorColorFieldMetrics(); + const auto fieldPalette = XCEngine::UI::Editor::ResolveUIEditorColorFieldPalette(); UIDrawData drawData = {}; UIDrawList& drawList = drawData.EmplaceDrawList("EditorColorFieldBasic"); @@ -643,31 +622,31 @@ private: layout.introRect, shellPalette, shellMetrics, - "这个测试在验证什么功能", - "只验证 Editor ColorField 的独立样式、弹窗结构和拾色交互,不混入 PropertyGrid。"); + "这个测试在验证什么功能?", + "验证 Editor ColorField 的 swatch、popup、SV square、hue wheel、RGBA slider 和关闭行为。"); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), - "1. 点击 swatch,检查 popup 是否在控件下方稳定展开。", + "1. 点击 swatch,打开 popup。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), - "2. 在 SV square 内拖拽,检查颜色是否连续变化。", + "2. 拖动 SV square,检查颜色实时变化。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), - "3. 拖拽 hue wheel 与 R/G/B/A slider,检查颜色和透明度是否同步更新。", + "3. 拖动 hue wheel 和 R / G / B / A slider,检查结果同步。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), - "4. 点击右上角 close 或点击控件外部,检查 popup 是否关闭。", + "4. 点击 close 或外部区域,关闭 popup。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f), - "5. 检查 Hexadecimal、数值框、handle、checkerboard 与截图路径是否同步。", + "5. 左侧重点看 Hex、RGBA、Popup 和 Result。", shellPalette.textPrimary, shellMetrics.bodyFontSize); @@ -687,7 +666,7 @@ private: shellPalette, shellMetrics, "状态摘要", - "重点检查 hover / popup / result / hex / rgba。"); + "重点看 hover、popup、hex、rgba 和结果。"); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Hover: " + DescribeHitTarget(currentHit), @@ -713,6 +692,7 @@ private: "Result: " + m_lastResult, shellPalette.textPrimary, shellMetrics.bodyFontSize); + const std::string captureSummary = m_autoScreenshot.HasPendingCapture() ? "截图排队中..." @@ -724,11 +704,6 @@ private: captureSummary, shellPalette.textWeak, shellMetrics.bodyFontSize); - drawList.AddText( - UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 214.0f), - "Theme: " + m_themeStatus, - shellPalette.textWeak, - shellMetrics.bodyFontSize); DrawCard( drawList, @@ -736,7 +711,7 @@ private: shellPalette, shellMetrics, "ColorField 预览", - "这里只放一个独立 ColorField,便于单点检查样式与交互。"); + "这里只放一个 ColorField,用来验证 Editor 基础字段的 popup 交互。"); AppendUIEditorColorField( drawList, layout.fieldRect, @@ -763,12 +738,10 @@ private: UIEditorColorFieldSpec m_spec = {}; UIEditorColorFieldInteractionState m_interactionState = {}; UIEditorColorFieldInteractionFrame m_frame = {}; - Style::UITheme m_theme = {}; UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f); ActionId m_hoveredAction = ActionId::Reset; bool m_hasHoveredAction = false; std::string m_lastResult = {}; - std::string m_themeStatus = "fallback"; }; } // namespace diff --git a/tests/UI/Editor/integration/shell/context_menu_basic/main.cpp b/tests/UI/Editor/integration/shell/context_menu_basic/main.cpp index 95d26690..34697e92 100644 --- a/tests/UI/Editor/integration/shell/context_menu_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/context_menu_basic/main.cpp @@ -2,13 +2,13 @@ #define NOMINMAX #endif -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -767,7 +767,7 @@ void ScenarioApp::ResetScenario() { SetCustomResult( "等待操作", "Ready", - "右键 Context Target 打开菜单;hover `Workspace Tools` 展开子菜单;Esc / 外部点击关闭。"); + "右键 Context Target 打开菜单,hover `Workspace Tools` 展开子菜单;Esc / 点击外部关闭。"); } void ScenarioApp::OnResize(UINT width, UINT height) { @@ -913,7 +913,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) { SetCustomResult( "Hover 收起更深层子菜单", "Dismissed", - "鼠标移到普通菜单项后,更深层 child popup 已收起。closed: " + + "鼠标移到普通菜单项后,更深层 child popup 已收起。Closed: " + JoinClosedPopupIds(mutation)); dirty = true; } @@ -925,7 +925,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) { SetCustomResult( "Hover popup 空白区", "Dismissed", - "鼠标停留在 popup 空白区后,更深层 child popup 已回收。closed: " + + "鼠标停留在 popup 空白区后,更深层 child popup 已回收。Closed: " + JoinClosedPopupIds(mutation)); dirty = true; } @@ -952,7 +952,7 @@ void ScenarioApp::HandleLeftClick(float x, float y) { mutation.changed ? "已展开 `" + hoveredItem->label + "` 子菜单。正常行为是 hover 也会直接展开。" - : "子菜单已经处于打开状态。"); + : "子菜单已经处于展开状态。"); InvalidateRect(m_hwnd, nullptr, FALSE); return; } @@ -983,7 +983,7 @@ void ScenarioApp::HandleLeftClick(float x, float y) { SetCustomResult( "点击 popup 空白区", "Dismissed", - "点击当前 popup 空白区后,仅更深层子菜单被关闭。closed: " + + "点击当前 popup 空白区后,仅更深层子菜单被关闭。Closed: " + JoinClosedPopupIds(mutation)); ClearHoverWhenMenuClosed(); InvalidateRect(m_hwnd, nullptr, FALSE); @@ -998,7 +998,7 @@ void ScenarioApp::HandleLeftClick(float x, float y) { "点击菜单外区域", mutation.changed ? "Dismissed" : "NoOp", mutation.changed - ? "点击外部区域后,整条菜单链已关闭。closed: " + + ? "点击外部区域后,整条菜单链已关闭。Closed: " + JoinClosedPopupIds(mutation) : "菜单链没有变化。"); ClearHoverWhenMenuClosed(); @@ -1015,7 +1015,7 @@ void ScenarioApp::HandleRightClick(float x, float y) { "右键外部区域", mutation.changed ? "Dismissed" : "NoOp", mutation.changed - ? "右键菜单外部区域后,整条菜单链已关闭。closed: " + + ? "右键菜单外部区域后,整条菜单链已关闭。Closed: " + JoinClosedPopupIds(mutation) : "菜单链没有变化。"); ClearHoverWhenMenuClosed(); @@ -1045,7 +1045,7 @@ void ScenarioApp::HandleKeyDown(UINT keyCode) { "Escape 关闭菜单", mutation.changed ? "Dismissed" : "NoOp", mutation.changed - ? "按下 Escape 后,topmost popup 已关闭。closed: " + + ? "按下 Escape 后,topmost popup 已关闭。Closed: " + JoinClosedPopupIds(mutation) : "当前没有可关闭的 popup。"); ClearHoverWhenMenuClosed(); @@ -1166,13 +1166,13 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height) DrawCard( drawList, m_headerRect, - "测试功能:Editor ContextMenu 基础层", + "这个测试验证什么功能?", "本场景只验证 root popup 锚点、submenu hover、outside/Esc dismiss、命令派发;不验证业务面板。"); drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 70.0f), "1. 在左侧 Context Target 内右键,root popup 必须贴近鼠标位置打开。", kTextPrimary, 13.0f); drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 92.0f), "2. hover `Workspace Tools`,右侧 child popup 必须立即弹出。", kTextPrimary, 13.0f); drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 114.0f), "3. 点击 Show/Activate/Hide/Reset,右侧 Details visible / active / activePanel 状态必须同步变化。", kTextPrimary, 13.0f); drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 136.0f), "4. 点击 popup 外部区域应整条关闭;如果 child popup 已打开,Esc 先关 topmost,再关 root。", kTextPrimary, 13.0f); - drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 158.0f), "5. F12 截图;R 直接触发 Reset Workspace,便于确认命令派发仍正常。", kTextPrimary, 13.0f); + drawList.AddText(UIPoint(m_headerRect.x + 18.0f, m_headerRect.y + 158.0f), "5. F12 截图,R 直接触发 Reset Workspace,便于确认命令派发仍然正常。", kTextPrimary, 13.0f); DrawCard(drawList, m_targetRect, "Context Target", "只在这里接受右键打开 ContextMenu。"); DrawCard(drawList, m_stateRect, "状态摘要", "重点看 popup chain、anchor 和 Details 状态。"); @@ -1190,22 +1190,22 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height) drawList.AddRectOutline(targetSurface, kCardBorder, 1.0f, 10.0f); drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 18.0f), "在这块区域右键打开 ContextMenu", kTextPrimary, 16.0f); drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 46.0f), "当前 anchor: " + FormatAnchorPoint(m_contextAnchorPoint, m_hasContextAnchor), kTextMuted, 12.0f); - drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 68.0f), "Visible Panels: " + JoinVisiblePanelIds(workspace, session), kTextMuted, 12.0f); - drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 90.0f), "Details visible: " + std::string(detailsState != nullptr && detailsState->visible ? "true" : "false"), kTextMuted, 12.0f); - drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 112.0f), "Details active: " + std::string(workspace.activePanelId == "details" ? "true" : "false"), kTextMuted, 12.0f); + drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 68.0f), "可见面板: " + JoinVisiblePanelIds(workspace, session), kTextMuted, 12.0f); + drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 90.0f), "Details 可见: " + std::string(detailsState != nullptr && detailsState->visible ? "true" : "false"), kTextMuted, 12.0f); + drawList.AddText(UIPoint(targetSurface.x + 18.0f, targetSurface.y + 112.0f), "Details 激活: " + std::string(workspace.activePanelId == "details" ? "true" : "false"), kTextMuted, 12.0f); const UIRect stateBox(m_stateRect.x + 18.0f, m_stateRect.y + 74.0f, m_stateRect.width - 36.0f, 220.0f); drawList.AddFilledRect(stateBox, kIndicatorBg, 10.0f); drawList.AddRectOutline(stateBox, kCardBorder, 1.0f, 10.0f); - drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 14.0f), "Open root menu", kTextMuted, 12.0f); + drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 14.0f), "已打开 root menu", kTextMuted, 12.0f); drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 34.0f), m_menuSession.HasOpenMenu() ? std::string(m_menuSession.GetOpenRootMenuId()) : "(none)", kTextPrimary, 14.0f); - drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 62.0f), "Popup chain", kTextMuted, 12.0f); + drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 62.0f), "Popup 链", kTextMuted, 12.0f); drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 82.0f), JoinPopupChainIds(m_menuSession), kTextPrimary, 14.0f); - drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 110.0f), "Submenu path", kTextMuted, 12.0f); + drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 110.0f), "Submenu 路径", kTextMuted, 12.0f); drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 130.0f), JoinSubmenuPathIds(m_menuSession), kTextPrimary, 14.0f); - drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 158.0f), "Active panel", kTextMuted, 12.0f); + drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 158.0f), "当前激活面板", kTextMuted, 12.0f); drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 178.0f), workspace.activePanelId.empty() ? "(none)" : workspace.activePanelId, kTextPrimary, 14.0f); - drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 202.0f), "Menu validation: " + std::string(menuValidation.IsValid() ? "OK" : menuValidation.message), menuValidation.IsValid() ? kSuccess : kDanger, 12.0f); + drawList.AddText(UIPoint(stateBox.x + 14.0f, stateBox.y + 202.0f), "菜单验证: " + std::string(menuValidation.IsValid() ? "OK" : menuValidation.message), menuValidation.IsValid() ? kSuccess : kDanger, 12.0f); const std::string captureSummary = m_autoScreenshot.HasPendingCapture() diff --git a/tests/UI/Editor/integration/shell/dock_host_basic/main.cpp b/tests/UI/Editor/integration/shell/dock_host_basic/main.cpp index 682f426e..bcdba739 100644 --- a/tests/UI/Editor/integration/shell/dock_host_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/dock_host_basic/main.cpp @@ -2,10 +2,10 @@ #define NOMINMAX #endif -#include -#include -#include -#include +#include +#include +#include +#include #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -584,15 +584,15 @@ private: UIDrawList& drawList = drawData.EmplaceDrawList("DockHostBasic"); drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg); - DrawCard(drawList, m_introRect, "这个测试验证什么功能", "只验证 DockHost 基础交互 contract,不做 editor 业务面板。"); + DrawCard(drawList, m_introRect, "这个测试验证什么功能?", "只验证 DockHost 基础交互 contract,不做 editor 业务面板。"); drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 72.0f), "1. 验证 splitter drag 是否只通过 DockHostInteraction + WorkspaceController 完成。", kTextPrimary, 12.0f); drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 94.0f), "2. 验证 tab activate / tab close / standalone panel activate / panel close。", kTextPrimary, 12.0f); drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 116.0f), "3. 验证 active panel、visible panels、split ratio 是否统一收口到 controller。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 138.0f), "4. 验证 pointer capture / release 请求是否由 contract 明确返回。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 162.0f), "建议操作: 先拖中间 splitter,再点 Document A。", kTextWeak, 11.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 180.0f), "然后关闭 Document B,最后点 Details 与 Console 的 X。", kTextWeak, 11.0f); + drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 138.0f), "4. 验证 pointer capture / release 请求是否通过 contract 明确返回。", kTextPrimary, 12.0f); + drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 162.0f), "建议操作:先拖中间 splitter,再点 Document A。", kTextWeak, 11.0f); + drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 180.0f), "然后关闭 Document B,最后点 Details 或 Console 的 X。", kTextWeak, 11.0f); - DrawCard(drawList, m_controlsRect, "操作", "只保留当前场景必要按钮。"); + DrawCard(drawList, m_controlsRect, "操作", "这里只保留当前场景必要按钮。"); for (const ButtonState& button : m_buttons) { DrawButton(drawList, button); } @@ -605,25 +605,25 @@ private: }; addStateLine("Hover: " + DescribeHitTarget(m_interactionState.dockHostState.hoveredTarget), kTextPrimary, 11.0f); - addStateLine("Result: " + m_lastStatus, m_lastColor); + addStateLine("结果: " + m_lastStatus, m_lastColor); drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f), m_lastMessage, kTextMuted, 11.0f); stateY += 34.0f; - addStateLine("Active Panel: " + (m_controller.GetWorkspace().activePanelId.empty() ? std::string("(none)") : m_controller.GetWorkspace().activePanelId)); - addStateLine("Visible Panels: " + JoinVisiblePanelIds(m_controller), kTextWeak, 11.0f); - addStateLine("Focused: " + FormatBool(m_cachedFrame.focused), m_cachedFrame.focused ? kSuccess : kTextMuted); - addStateLine("Capture: " + FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kSuccess : kTextMuted); + addStateLine("当前面板: " + (m_controller.GetWorkspace().activePanelId.empty() ? std::string("(none)") : m_controller.GetWorkspace().activePanelId)); + addStateLine("可见面板: " + JoinVisiblePanelIds(m_controller), kTextWeak, 11.0f); + addStateLine("焦点: " + FormatBool(m_cachedFrame.focused), m_cachedFrame.focused ? kSuccess : kTextMuted); + addStateLine("捕获: " + FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kSuccess : kTextMuted); addStateLine( - "Dragging Splitter: " + + "拖拽 Splitter: " + (m_interactionState.dockHostState.activeSplitterNodeId.empty() ? std::string("(none)") : m_interactionState.dockHostState.activeSplitterNodeId), kTextWeak, 11.0f); - addStateLine("root-split ratio: " + FormatFloat(m_controller.GetWorkspace().root.splitRatio), kTextWeak, 11.0f); + addStateLine("根分割比例: " + FormatFloat(m_controller.GetWorkspace().root.splitRatio), kTextWeak, 11.0f); if (m_controller.GetWorkspace().root.children.size() > 1u && m_controller.GetWorkspace().root.children[1].children.size() > 1u) { addStateLine( - "right-split ratio: " + + "右侧分割比例: " + FormatFloat(m_controller.GetWorkspace().root.children[1].splitRatio), kTextWeak, 11.0f); @@ -638,7 +638,7 @@ private: kTextWeak, 11.0f); - DrawCard(drawList, m_previewRect, "Preview", "真实 DockHost 交互预览,只看基础层,不接旧 editor。"); + DrawCard(drawList, m_previewRect, "预览", "真实 DockHost 交互预览,只看基础层,不接旧 editor。"); drawList.AddFilledRect(m_dockHostRect, kPreviewBg, 8.0f); AppendUIEditorDockHostBackground(drawList, m_cachedFrame.layout); AppendUIEditorDockHostForeground(drawList, m_cachedFrame.layout); diff --git a/tests/UI/Editor/integration/shell/editor_shell_compose/main.cpp b/tests/UI/Editor/integration/shell/editor_shell_compose/main.cpp index a485b834..7c8478b4 100644 --- a/tests/UI/Editor/integration/shell/editor_shell_compose/main.cpp +++ b/tests/UI/Editor/integration/shell/editor_shell_compose/main.cpp @@ -2,10 +2,12 @@ #define NOMINMAX #endif -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include "../../shared/src/EditorValidationTheme.h" #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -41,6 +43,8 @@ using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit; using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack; using XCEngine::UI::Editor::FindUIEditorWorkspaceViewportPresentationRequest; using XCEngine::UI::Editor::GetUIEditorWorkspaceCommandStatusName; +using XCEngine::UI::Editor::ResolveUIEditorShellComposeMetrics; +using XCEngine::UI::Editor::ResolveUIEditorShellComposePalette; using XCEngine::UI::Editor::ResolveUIEditorShellComposeRequest; using XCEngine::UI::Editor::UIEditorPanelPresentationKind; using XCEngine::UI::Editor::UIEditorPanelRegistry; @@ -61,19 +65,12 @@ using XCEngine::UI::Editor::Host::NativeRenderer; using XCEngine::UI::Editor::Widgets::UIEditorMenuBarItem; using XCEngine::UI::Editor::Widgets::UIEditorStatusBarSegment; using XCEngine::UI::Editor::Widgets::UIEditorStatusBarSlot; +const auto kShellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); +const auto kShellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); constexpr const wchar_t* kWindowClassName = L"XCUIEditorShellComposeValidation"; -constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Shell Compose"; +constexpr const wchar_t* kWindowTitle = L"XCUI �༭??| �������"; -constexpr UIColor kWindowBg(0.10f, 0.10f, 0.10f, 1.0f); -constexpr UIColor kCardBg(0.17f, 0.17f, 0.17f, 1.0f); -constexpr UIColor kCardBorder(0.28f, 0.28f, 0.28f, 1.0f); -constexpr UIColor kTextPrimary(0.94f, 0.94f, 0.94f, 1.0f); -constexpr UIColor kTextMuted(0.72f, 0.72f, 0.72f, 1.0f); -constexpr UIColor kTextWeak(0.56f, 0.56f, 0.56f, 1.0f); -constexpr UIColor kButtonBg(0.24f, 0.24f, 0.24f, 1.0f); -constexpr UIColor kButtonOnBg(0.36f, 0.36f, 0.36f, 1.0f); -constexpr UIColor kButtonBorder(0.46f, 0.46f, 0.46f, 1.0f); constexpr UIColor kSuccess(0.46f, 0.72f, 0.50f, 1.0f); constexpr UIColor kDanger(0.78f, 0.35f, 0.35f, 1.0f); @@ -103,6 +100,7 @@ std::filesystem::path ResolveRepoRootPath() { return std::filesystem::path(root).lexically_normal(); } + bool ContainsPoint(const UIRect& rect, float x, float y) { return x >= rect.x && x <= rect.x + rect.width && @@ -134,27 +132,27 @@ void DrawCard( const UIRect& rect, std::string_view title, std::string_view subtitle = {}) { - drawList.AddFilledRect(rect, kCardBg, 10.0f); - drawList.AddRectOutline(rect, kCardBorder, 1.0f, 10.0f); - drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 14.0f), std::string(title), kTextPrimary, 17.0f); + drawList.AddFilledRect(rect, kShellPalette.cardBackground, kShellMetrics.cardRadius); + drawList.AddRectOutline(rect, kShellPalette.cardBorder, 1.0f, kShellMetrics.cardRadius); + drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 14.0f), std::string(title), kShellPalette.textPrimary, kShellMetrics.titleFontSize); if (!subtitle.empty()) { - drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 38.0f), std::string(subtitle), kTextMuted, 12.0f); + drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 38.0f), std::string(subtitle), kShellPalette.textMuted, kShellMetrics.bodyFontSize); } } void DrawButton(UIDrawList& drawList, const ButtonState& button) { - drawList.AddFilledRect(button.rect, button.selected ? kButtonOnBg : kButtonBg, 8.0f); - drawList.AddRectOutline(button.rect, kButtonBorder, 1.0f, 8.0f); - drawList.AddText(UIPoint(button.rect.x + 12.0f, button.rect.y + 10.0f), button.label, kTextPrimary, 12.0f); + drawList.AddFilledRect(button.rect, button.selected ? kShellPalette.buttonHoverBackground : kShellPalette.buttonBackground, kShellMetrics.buttonRadius); + drawList.AddRectOutline(button.rect, kShellPalette.cardBorder, 1.0f, kShellMetrics.buttonRadius); + drawList.AddText(UIPoint(button.rect.x + 12.0f, button.rect.y + 10.0f), button.label, kShellPalette.textPrimary, kShellMetrics.bodyFontSize); } UIEditorPanelRegistry BuildPanelRegistry() { UIEditorPanelRegistry registry = {}; registry.panels = { - { "hierarchy", "Hierarchy", UIEditorPanelPresentationKind::Placeholder, true, true, false }, - { "scene", "Scene", UIEditorPanelPresentationKind::ViewportShell, false, true, false }, - { "document", "Document", UIEditorPanelPresentationKind::Placeholder, true, true, true }, - { "inspector", "Inspector", UIEditorPanelPresentationKind::Placeholder, true, true, true } + { "hierarchy", "层级", UIEditorPanelPresentationKind::Placeholder, true, true, false }, + { "scene", "场景", UIEditorPanelPresentationKind::ViewportShell, false, true, false }, + { "document", "文档", UIEditorPanelPresentationKind::Placeholder, true, true, true }, + { "inspector", "检视器", UIEditorPanelPresentationKind::Placeholder, true, true, true } }; return registry; } @@ -165,7 +163,7 @@ UIEditorWorkspaceModel BuildWorkspace() { "root-left-main", UIEditorWorkspaceSplitAxis::Horizontal, 0.23f, - BuildUIEditorWorkspacePanel("hierarchy-node", "hierarchy", "Hierarchy", true), + BuildUIEditorWorkspacePanel("hierarchy-node", "hierarchy", "层级", true), BuildUIEditorWorkspaceSplit( "main-center-right", UIEditorWorkspaceSplitAxis::Horizontal, @@ -173,11 +171,11 @@ UIEditorWorkspaceModel BuildWorkspace() { BuildUIEditorWorkspaceTabStack( "center-tabs", { - BuildUIEditorWorkspacePanel("scene-node", "scene", "Scene"), - BuildUIEditorWorkspacePanel("document-node", "document", "Document", true) + BuildUIEditorWorkspacePanel("scene-node", "scene", "场景"), + BuildUIEditorWorkspacePanel("document-node", "document", "文档", true) }, 0u), - BuildUIEditorWorkspacePanel("inspector-node", "inspector", "Inspector", true))); + BuildUIEditorWorkspacePanel("inspector-node", "inspector", "检视器", true))); workspace.activePanelId = "scene"; return workspace; } @@ -318,7 +316,7 @@ private: m_showTopBar = true; m_showBottomBar = true; m_textureEnabled = true; - m_lastResult = "Ready"; + m_lastResult = "就绪"; } void UpdateLayoutForCurrentWindow() { @@ -350,13 +348,13 @@ private: const float widthAvailable = m_controlsRect.width - 32.0f; const bool sceneSelected = GetSelectedTabId() == "scene"; m_buttons = { - { ActionId::ActivateScene, "切到 Scene", UIRect(left, top, widthAvailable, buttonHeight), sceneSelected }, - { ActionId::ActivateDocument, "切到 Document", UIRect(left, top + (buttonHeight + gap), widthAvailable, buttonHeight), !sceneSelected }, - { ActionId::ToggleTopBar, std::string("TopBar: ") + (m_showTopBar ? "开" : "关"), UIRect(left, top + (buttonHeight + gap) * 2.0f, widthAvailable, buttonHeight), m_showTopBar }, - { ActionId::ToggleBottomBar, std::string("BottomBar: ") + (m_showBottomBar ? "开" : "关"), UIRect(left, top + (buttonHeight + gap) * 3.0f, widthAvailable, buttonHeight), m_showBottomBar }, - { ActionId::ToggleTexture, std::string("Texture: ") + (m_textureEnabled ? "开" : "关"), UIRect(left, top + (buttonHeight + gap) * 4.0f, widthAvailable, buttonHeight), m_textureEnabled }, + { ActionId::ActivateScene, "Scene", UIRect(left, top, widthAvailable, buttonHeight), sceneSelected }, + { ActionId::ActivateDocument, "Document", UIRect(left, top + (buttonHeight + gap), widthAvailable, buttonHeight), !sceneSelected }, + { ActionId::ToggleTopBar, std::string("Top Bar: ") + (m_showTopBar ? "on" : "off"), UIRect(left, top + (buttonHeight + gap) * 2.0f, widthAvailable, buttonHeight), m_showTopBar }, + { ActionId::ToggleBottomBar, std::string("Bottom Bar: ") + (m_showBottomBar ? "on" : "off"), UIRect(left, top + (buttonHeight + gap) * 3.0f, widthAvailable, buttonHeight), m_showBottomBar }, + { ActionId::ToggleTexture, std::string("Texture: ") + (m_textureEnabled ? "on" : "off"), UIRect(left, top + (buttonHeight + gap) * 4.0f, widthAvailable, buttonHeight), m_textureEnabled }, { ActionId::Reset, "Reset", UIRect(left, top + (buttonHeight + gap) * 5.0f, widthAvailable, buttonHeight), false }, - { ActionId::Capture, "截图", UIRect(left, top + (buttonHeight + gap) * 6.0f, widthAvailable, buttonHeight), false } + { ActionId::Capture, "Capture", UIRect(left, top + (buttonHeight + gap) * 6.0f, widthAvailable, buttonHeight), false } }; } @@ -369,7 +367,7 @@ private: }; model.statusSegments = { UIEditorStatusBarSegment{ "mode", GetSelectedTabId() == "scene" ? "Scene" : "Document", UIEditorStatusBarSlot::Leading, {}, true, true, 86.0f }, - UIEditorStatusBarSegment{ "chrome", std::string("Top ") + (m_showTopBar ? "On" : "Off"), UIEditorStatusBarSlot::Leading, {}, true, false, 82.0f }, + UIEditorStatusBarSegment{ "chrome", std::string("Top ") + (m_showTopBar ? "on" : "off"), UIEditorStatusBarSlot::Leading, {}, true, false, 82.0f }, UIEditorStatusBarSegment{ "branch", m_textureEnabled ? "Texture" : "Fallback", UIEditorStatusBarSlot::Trailing, {}, true, true, 96.0f } }; @@ -377,7 +375,7 @@ private: presentation.panelId = "scene"; presentation.kind = UIEditorPanelPresentationKind::ViewportShell; presentation.viewportShellModel.spec.chrome.title = "Scene"; - presentation.viewportShellModel.spec.chrome.subtitle = "Editor Shell 基础层"; + presentation.viewportShellModel.spec.chrome.subtitle = "Shell compose validation"; presentation.viewportShellModel.spec.chrome.showTopBar = m_showTopBar; presentation.viewportShellModel.spec.chrome.showBottomBar = m_showBottomBar; presentation.viewportShellModel.spec.chrome.topBarHeight = 40.0f; @@ -387,16 +385,16 @@ private: }; presentation.viewportShellModel.spec.statusSegments = { { "view", "Shell", UIEditorStatusBarSlot::Leading, {}, true, true, 64.0f }, - { "branch", m_textureEnabled ? "Texture" : "Fallback", UIEditorStatusBarSlot::Trailing, {}, true, false, 96.0f } + { "branch", m_textureEnabled ? "纹理" : "回退", UIEditorStatusBarSlot::Trailing, {}, true, false, 96.0f } }; if (m_textureEnabled) { presentation.viewportShellModel.frame.hasTexture = true; presentation.viewportShellModel.frame.texture = { 1u, 1280u, 720u }; presentation.viewportShellModel.frame.presentedSize = UISize(1280.0f, 720.0f); - presentation.viewportShellModel.frame.statusText = "Fake viewport frame"; + presentation.viewportShellModel.frame.statusText = "Simulated viewport frame"; } else { presentation.viewportShellModel.frame.hasTexture = false; - presentation.viewportShellModel.frame.statusText = "这里只验证 Editor shell compose,不接 Scene/Game 业务。"; + presentation.viewportShellModel.frame.statusText = "Simulated viewport frame"; } model.workspacePresentations = { presentation }; return model; @@ -411,6 +409,7 @@ private: void UpdateShellFrame() { const UIEditorShellComposeModel model = BuildShellModel(); + const auto metrics = ResolveUIEditorShellComposeMetrics(); m_shellRequest = ResolveUIEditorShellComposeRequest( m_shellRect, m_controller.GetPanelRegistry(), @@ -418,7 +417,8 @@ private: m_controller.GetSession(), model, {}, - m_shellState); + m_shellState, + metrics); m_shellFrame = UpdateUIEditorShellCompose( m_shellState, m_shellRect, @@ -426,7 +426,9 @@ private: m_controller.GetWorkspace(), m_controller.GetSession(), model, - {}); + {}, + {}, + metrics); m_cachedModel = model; } @@ -445,30 +447,30 @@ private: void ExecuteAction(ActionId action) { switch (action) { case ActionId::ActivateScene: - DispatchWorkspaceCommand(UIEditorWorkspaceCommandKind::ActivatePanel, "scene", "Activate Scene"); + DispatchWorkspaceCommand(UIEditorWorkspaceCommandKind::ActivatePanel, "scene", "切到场景"); break; case ActionId::ActivateDocument: - DispatchWorkspaceCommand(UIEditorWorkspaceCommandKind::ActivatePanel, "document", "Activate Document"); + DispatchWorkspaceCommand(UIEditorWorkspaceCommandKind::ActivatePanel, "document", "切到文档"); break; case ActionId::ToggleTopBar: m_showTopBar = !m_showTopBar; - m_lastResult = m_showTopBar ? "TopBar 已打开" : "TopBar 已关闭"; + m_lastResult = m_showTopBar ? "Top bar enabled" : "Top bar disabled"; break; case ActionId::ToggleBottomBar: m_showBottomBar = !m_showBottomBar; - m_lastResult = m_showBottomBar ? "BottomBar 已打开" : "BottomBar 已关闭"; + m_lastResult = m_showBottomBar ? "Bottom bar enabled" : "Bottom bar disabled"; break; case ActionId::ToggleTexture: m_textureEnabled = !m_textureEnabled; - m_lastResult = m_textureEnabled ? "切到 Texture 分支" : "切到 Fallback 分支"; + m_lastResult = m_textureEnabled ? "Texture branch enabled" : "Fallback branch enabled"; break; case ActionId::Reset: ResetScenario(); - m_lastResult = "状态已重置"; + m_lastResult = "Scenario reset"; break; case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "截图已排队"; + m_lastResult = "Capture queued"; InvalidateRect(m_hwnd, nullptr, FALSE); UpdateWindow(m_hwnd); break; @@ -504,53 +506,46 @@ private: UIDrawData drawData = {}; UIDrawList& drawList = drawData.EmplaceDrawList("EditorShellCompose"); - drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg); - - DrawCard(drawList, m_introRect, "测试功能:Editor 根壳层 compose", "只验证 MenuBar / Workspace / StatusBar 三段根壳组合。"); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f), "检查 1:顶部 MenuBar、中间 WorkspaceCompose、底部 StatusBar 必须各自占带,不能互相挤压或重叠。", kTextMuted, 11.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 88.0f), "检查 2:切换 Scene / Document 时,只影响中间 Workspace body presentation,不应破坏顶栏和底栏。", kTextMuted, 11.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 110.0f), "检查 3:切换 TopBar / BottomBar / Texture 时,Request Size 必须同步,但 MenuBar / StatusBar 位置保持稳定。", kTextMuted, 11.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 132.0f), "这一层暂时不验证 menu popup 或 shortcut,只验证 Editor 根框架 compose。", kTextWeak, 11.0f); - - DrawCard(drawList, m_controlsRect, "操作", "这里只保留当前这个根壳层需要检查的 7 个控件。"); + drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kShellPalette.windowBackground); + DrawCard(drawList, m_introRect, "Validation Goal", "Verify root shell composition only."); + drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f), "1. Menu bar, workspace, and status bar must compose into one stable shell.", kShellPalette.textMuted, 11.0f); + drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 88.0f), "2. Scene should host the viewport shell, while Document should fall back to a placeholder.", kShellPalette.textMuted, 11.0f); + drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 110.0f), "3. Top bar, bottom bar, and texture toggles must update layout and branch selection together.", kShellPalette.textMuted, 11.0f); + drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 132.0f), "4. This scene validates shell composition only. No business panels are involved.", kShellPalette.textWeak, 11.0f); + DrawCard(drawList, m_controlsRect, "Actions", "Only controls required for this validation are exposed."); for (const ButtonState& button : m_buttons) { DrawButton(drawList, button); } - - DrawCard(drawList, m_stateRect, "状态", "左侧直接回显根壳层三段布局与中间 workspace request。"); + DrawCard(drawList, m_stateRect, "State", "Inspect current shell layout, request size, and validation result."); float stateY = m_stateRect.y + 66.0f; auto addStateLine = [&](std::string text, const UIColor& color, float fontSize = 12.0f) { drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY), std::move(text), color, fontSize); stateY += 18.0f; }; - addStateLine("Active Panel: " + m_controller.GetWorkspace().activePanelId, kTextPrimary); - addStateLine("Selected Tab: " + GetSelectedTabId(), kTextPrimary); - addStateLine("Selected Presentation: " + selectedPresentation, kTextPrimary); - addStateLine("MenuBar Rect: " + FormatRect(m_shellFrame.layout.menuBarRect), kTextMuted, 11.0f); - addStateLine("Workspace Rect: " + FormatRect(m_shellFrame.layout.workspaceRect), kTextMuted, 11.0f); - addStateLine("StatusBar Rect: " + FormatRect(m_shellFrame.layout.statusBarRect), kTextMuted, 11.0f); + addStateLine("Requested viewport size: " + (viewportRequest != nullptr ? FormatSize(viewportRequest->viewportShellRequest.requestedViewportSize) : std::string("n/a")), kShellPalette.textPrimary); + addStateLine("Result: " + m_lastResult, kShellPalette.textMuted); addStateLine( - "Request Size: " + - (viewportRequest != nullptr - ? FormatSize(viewportRequest->viewportShellRequest.requestedViewportSize) - : std::string("n/a")), - kTextPrimary); - addStateLine("Result: " + m_lastResult, kTextMuted); - addStateLine( - validation.IsValid() ? "Workspace Validation: OK" : "Workspace Validation: " + validation.message, + validation.IsValid() ? "工作区校验:正常" : "工作区校验:" + validation.message, validation.IsValid() ? kSuccess : kDanger, 11.0f); const std::string captureSummary = m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "Capture queued..." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("截图:F12 或按钮 -> editor_shell_compose/captures/") + ? std::string("F12 or the Capture button -> editor_shell_compose/captures/") : m_autoScreenshot.GetLastCaptureSummary()); - addStateLine(captureSummary, kTextWeak, 11.0f); - - DrawCard(drawList, m_previewRect, "Preview", "这里只有 Editor 根壳层 compose,不混入业务面板。"); - AppendUIEditorShellCompose(drawList, m_shellFrame, m_cachedModel, m_shellState); + addStateLine(captureSummary, kShellPalette.textWeak, 11.0f); + DrawCard(drawList, m_previewRect, "Preview", "Live UIEditorShellCompose preview."); + const auto palette = ResolveUIEditorShellComposePalette(); + const auto metrics = ResolveUIEditorShellComposeMetrics(); + AppendUIEditorShellCompose( + drawList, + m_shellFrame, + m_cachedModel, + m_shellState, + palette, + metrics); const bool framePresented = m_renderer.Render(drawData); m_autoScreenshot.CaptureIfRequested( diff --git a/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp b/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp index f9689e78..89c32616 100644 --- a/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp +++ b/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp @@ -2,13 +2,15 @@ #define NOMINMAX #endif -#include "Core/EditorShellAsset.h" +#include "Shell/EditorShellAsset.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include "../../shared/src/EditorValidationTheme.h" #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -19,6 +21,8 @@ #include #include +#include +#include #include #include #include @@ -54,6 +58,8 @@ using XCEngine::UI::Editor::EditorShellAsset; using XCEngine::UI::Editor::EditorShellAssetValidationResult; using XCEngine::UI::Editor::FindUIEditorWorkspaceViewportPresentationFrame; using XCEngine::UI::Editor::GetUIEditorCommandDispatchStatusName; +using XCEngine::UI::Editor::ResolveUIEditorShellInteractionMetrics; +using XCEngine::UI::Editor::ResolveUIEditorShellInteractionPalette; using XCEngine::UI::Editor::UpdateUIEditorShellInteraction; using XCEngine::UI::Editor::UIEditorCommandDispatchResult; using XCEngine::UI::Editor::UIEditorCommandDispatcher; @@ -85,19 +91,12 @@ using XCEngine::UI::Editor::Widgets::UIEditorStatusBarSlot; using XCEngine::UI::Widgets::UIPopupDismissReason; using XCEngine::UI::Editor::Host::AutoScreenshotController; using XCEngine::UI::Editor::Host::NativeRenderer; +const auto kShellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); +const auto kShellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); constexpr const wchar_t* kWindowClassName = L"XCUIEditorShellInteractionValidation"; constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Shell Interaction"; -constexpr UIColor kWindowBg(0.10f, 0.10f, 0.10f, 1.0f); -constexpr UIColor kCardBg(0.17f, 0.17f, 0.17f, 1.0f); -constexpr UIColor kCardBorder(0.28f, 0.28f, 0.28f, 1.0f); -constexpr UIColor kTextPrimary(0.94f, 0.94f, 0.94f, 1.0f); -constexpr UIColor kTextMuted(0.72f, 0.72f, 0.72f, 1.0f); -constexpr UIColor kTextWeak(0.56f, 0.56f, 0.56f, 1.0f); -constexpr UIColor kButtonBg(0.24f, 0.24f, 0.24f, 1.0f); -constexpr UIColor kButtonHover(0.32f, 0.32f, 0.32f, 1.0f); -constexpr UIColor kButtonBorder(0.46f, 0.46f, 0.46f, 1.0f); constexpr UIColor kSuccess(0.46f, 0.72f, 0.50f, 1.0f); constexpr UIColor kWarning(0.82f, 0.67f, 0.35f, 1.0f); constexpr UIColor kDanger(0.78f, 0.35f, 0.35f, 1.0f); @@ -122,6 +121,24 @@ std::filesystem::path ResolveRepoRootPath() { return std::filesystem::path(root).lexically_normal(); } +bool IsAutoCaptureOnStartupEnabled() { + const char* value = std::getenv("XCUI_AUTO_CAPTURE_ON_STARTUP"); + if (value == nullptr || value[0] == '\0') { + return false; + } + + std::string normalized = value; + for (char& character : normalized) { + character = static_cast(std::tolower(static_cast(character))); + } + + return normalized != "0" && + normalized != "false" && + normalized != "off" && + normalized != "no"; +} + + bool ContainsPoint(const UIRect& rect, float x, float y) { return x >= rect.x && x <= rect.x + rect.width && @@ -248,18 +265,18 @@ void DrawCard( const UIRect& rect, std::string_view title, std::string_view subtitle = {}) { - drawList.AddFilledRect(rect, kCardBg, 10.0f); - drawList.AddRectOutline(rect, kCardBorder, 1.0f, 10.0f); - drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 14.0f), std::string(title), kTextPrimary, 17.0f); + drawList.AddFilledRect(rect, kShellPalette.cardBackground, kShellMetrics.cardRadius); + drawList.AddRectOutline(rect, kShellPalette.cardBorder, 1.0f, kShellMetrics.cardRadius); + drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 14.0f), std::string(title), kShellPalette.textPrimary, kShellMetrics.titleFontSize); if (!subtitle.empty()) { - drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 38.0f), std::string(subtitle), kTextMuted, 12.0f); + drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 38.0f), std::string(subtitle), kShellPalette.textMuted, kShellMetrics.bodyFontSize); } } void DrawButton(UIDrawList& drawList, const ButtonState& button) { - drawList.AddFilledRect(button.rect, button.hovered ? kButtonHover : kButtonBg, 8.0f); - drawList.AddRectOutline(button.rect, kButtonBorder, 1.0f, 8.0f); - drawList.AddText(UIPoint(button.rect.x + 14.0f, button.rect.y + 10.0f), button.label, kTextPrimary, 12.0f); + drawList.AddFilledRect(button.rect, button.hovered ? kShellPalette.buttonHoverBackground : kShellPalette.buttonBackground, kShellMetrics.buttonRadius); + drawList.AddRectOutline(button.rect, kShellPalette.cardBorder, 1.0f, kShellMetrics.buttonRadius); + drawList.AddText(UIPoint(button.rect.x + 14.0f, button.rect.y + 10.0f), button.label, kShellPalette.textPrimary, kShellMetrics.bodyFontSize); } std::int32_t MapVirtualKeyToUIKeyCode(WPARAM wParam) { @@ -433,7 +450,7 @@ EditorShellAsset BuildScenarioShellAsset() { presentation.viewportShellModel.spec.chrome.showBottomBar = true; presentation.viewportShellModel.frame.hasTexture = false; presentation.viewportShellModel.frame.statusText = - "这里只验证 Editor 根壳交互,不接旧 editor 业务面板。"; + "该场景只验证 Editor Shell 交互契约,不接 editor 业务面板。"; } asset.shellDefinition.workspacePresentations.push_back(std::move(presentation)); } @@ -441,7 +458,6 @@ EditorShellAsset BuildScenarioShellAsset() { asset.shortcutAsset.commandRegistry = BuildCommandRegistry(); return asset; } - class ScenarioApp { public: int Run(HINSTANCE hInstance, int nCmdShow); @@ -485,7 +501,7 @@ private: bool m_trackingMouseLeave = false; std::string m_lastStatus = {}; std::string m_lastMessage = {}; - UIColor m_lastColor = kTextMuted; + UIColor m_lastColor = kShellPalette.textMuted; }; int ScenarioApp::Run(HINSTANCE hInstance, int nCmdShow) { @@ -666,6 +682,11 @@ bool ScenarioApp::Initialize(HINSTANCE hInstance, int nCmdShow) { } ResetScenario(); + if (IsAutoCaptureOnStartupEnabled()) { + m_lastStatus = "Capture"; + m_lastMessage = "启动自动截图已排队。先核对首帧,再检查 root switch、child popup、dismiss 与 workspace suppression。"; + m_lastColor = kWarning; + } return true; } @@ -708,10 +729,9 @@ void ScenarioApp::ResetScenario() { return; } m_lastStatus = "Ready"; - m_lastMessage = "等待交互。这里只验证 Editor 根壳统一交互 contract,不接旧 editor 业务。"; + m_lastMessage = "等待交互。重点检查根菜单切换、hover 子菜单、关闭链路与 workspace 抑制;设 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可抓启动首帧。"; m_lastColor = kWarning; } - void ScenarioApp::UpdateLayout() { RECT clientRect = {}; GetClientRect(m_hwnd, &clientRect); @@ -734,7 +754,6 @@ void ScenarioApp::UpdateLayout() { { ActionId::Capture, "截图(F12)", UIRect(left + buttonWidth + 12.0f, top, buttonWidth, 36.0f), false } }; } - void ScenarioApp::HandleMouseMove(float x, float y) { UpdateLayout(); for (ButtonState& button : m_buttons) { @@ -775,17 +794,17 @@ void ScenarioApp::ExecuteAction(ActionId action) { if (action == ActionId::Reset) { ResetScenario(); m_lastStatus = "Ready"; - m_lastMessage = "场景状态已重置。请重新检查 root open / child popup / dismiss 行为。"; + m_lastMessage = "已重置场景。请重新检查 root open / child popup / dismiss 行为。"; m_lastColor = kWarning; return; } m_autoScreenshot.RequestCapture("manual_button"); m_lastStatus = "Ready"; - m_lastMessage = "截图已排队,输出到 tests/UI/Editor/integration/shell/editor_shell_interaction/captures/。"; + m_lastMessage = + "截图请求已排队。检查状态卡片中的 Output/latest.png。"; m_lastColor = kWarning; } - bool ScenarioApp::HasInteractiveCaptureState() const { if (m_interactionState.workspaceInteractionState.dockHostInteractionState.splitterDragState.active) { return true; @@ -833,7 +852,7 @@ void ScenarioApp::SetInteractionResult(const UIEditorShellInteractionResult& res if (result.commandTriggered) { m_lastStatus = "Triggered"; - m_lastMessage = "命令已命中,但本帧没有完成 dispatch。请检查 shell services / asset contract。"; + m_lastMessage = "命令已触发但尚未派发,请检查 dispatch 链路。"; m_lastColor = kWarning; return; } @@ -842,19 +861,19 @@ void ScenarioApp::SetInteractionResult(const UIEditorShellInteractionResult& res if (!result.itemId.empty() && !result.menuMutation.openedPopupId.empty()) { m_lastStatus = "Changed"; m_lastMessage = - "已展开子菜单 `" + result.itemId + "`。请检查 child popup 是否在 hover 时直接弹出。"; + "`" + result.itemId + "` 已展开 child popup,hover 应直接弹出。"; m_lastColor = kSuccess; } else if (!result.menuId.empty() && !result.menuMutation.openedPopupId.empty()) { m_lastStatus = "Changed"; m_lastMessage = - "当前根菜单为 `" + result.menuId + "`。请检查 root popup 是否在打开态下可直接切换。"; + "当前根菜单切换为 `" + result.menuId + "`,root popup 应立即切换。"; m_lastColor = kSuccess; } else { m_lastStatus = "Dismissed"; m_lastMessage = "菜单链已收起。DismissReason = " + FormatDismissReason(result.menuMutation.dismissReason) + - "。请检查 outside pointer down / Esc / focus loss 的收束是否正确。"; + ";请检查 outside pointer down / Esc / focus loss。"; m_lastColor = kWarning; } return; @@ -888,41 +907,41 @@ void ScenarioApp::SetInteractionResult(const UIEditorShellInteractionResult& res if (result.requestPointerCapture) { m_lastStatus = "Capture"; - m_lastMessage = "宿主已收到 root shell 返回的 pointer capture 请求。"; + m_lastMessage = "控件请求了 pointer capture。"; m_lastColor = kSuccess; return; } if (result.releasePointerCapture) { m_lastStatus = "Release"; - m_lastMessage = "宿主已执行 root shell 返回的 pointer release。"; + m_lastMessage = "控件释放了 pointer capture。"; m_lastColor = kWarning; return; } if (result.consumed) { m_lastStatus = "NoOp"; - m_lastMessage = "这次输入被根壳交互层消费,但没有触发额外状态变化。"; + m_lastMessage = "输入已被消费,但没有产生更高层状态变化。"; m_lastColor = kWarning; } } - void ScenarioApp::SetDispatchResult(const UIEditorCommandDispatchResult& result) { m_lastStatus = std::string(GetUIEditorCommandDispatchStatusName(result.status)); - m_lastMessage = result.message.empty() ? std::string("命令派发完成。") : result.message; + m_lastMessage = result.message.empty() ? std::string("命令已派发。") : result.message; m_lastColor = m_lastStatus == "Dispatched" ? kSuccess : kDanger; } - void ScenarioApp::RenderFrame() { UpdateLayout(); const UIEditorShellInteractionDefinition definition = BuildInteractionDefinition(); + const auto metrics = ResolveUIEditorShellInteractionMetrics(); m_cachedFrame = UpdateUIEditorShellInteraction( m_interactionState, m_controller, m_shellRect, definition, m_pendingInputEvents, - m_shellServices); + m_shellServices, + metrics); m_pendingInputEvents.clear(); ApplyHostCaptureRequests(m_cachedFrame.result); SetInteractionResult(m_cachedFrame.result); @@ -939,71 +958,73 @@ void ScenarioApp::RenderFrame() { UIDrawData drawData = {}; UIDrawList& drawList = drawData.EmplaceDrawList("EditorShellInteraction"); - drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg); + drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kShellPalette.windowBackground); - DrawCard(drawList, m_introRect, "这个测试验证什么功能", "只验证 Editor 根壳统一交互 contract,不做业务面板。"); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 70.0f), "1. 验证 MenuBar 的 root open / root switch 行为是否统一稳定。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 92.0f), "2. 验证 hover 子菜单时,child popup 是否直接展开,不需要额外点击。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 114.0f), "3. 验证 outside pointer down / Esc / focus loss 是否能正确收起 popup chain。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 136.0f), "4. 验证菜单打开时会屏蔽 workspace 输入;菜单关闭后,workspace 交互立即恢复。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 158.0f), "5. 验证菜单命令会在 root shell 内直接 dispatch,宿主不再二次派发。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 182.0f), "建议操作:点击 File,hover `Workspace Tools`,点预览外空白处,再点 `Document` 或拖 splitter。", kTextWeak, 11.0f); + DrawCard( + drawList, + m_introRect, + "这个测试验证什么?", + "只验证 Editor Shell 统一交互 contract,不接 editor 业务面板。"); + drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 70.0f), "1. 验证 MenuBar 的 root open / root switch 行为是否统一。", kShellPalette.textPrimary, 12.0f); + drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 92.0f), "2. 验证 hover 子菜单项时,child popup 是否直接弹出。", kShellPalette.textPrimary, 12.0f); + drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 114.0f), "3. 验证 outside pointer down / Esc / focus loss 是否正确关闭 popup chain。", kShellPalette.textPrimary, 12.0f); + drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 136.0f), "4. 验证菜单打开时是否抑制 workspace 输入;菜单关闭后是否恢复。", kShellPalette.textPrimary, 12.0f); + drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 158.0f), "5. 验证命令是否由 root shell 直接 dispatch,不依赖 editor 业务层。", kShellPalette.textPrimary, 12.0f); + drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 182.0f), "建议操作:点击 File,hover `Workspace Tools`;再切换 `Document` 标签并拖动 splitter。设 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可自动抓首帧。", kShellPalette.textWeak, 11.0f); - DrawCard(drawList, m_controlsRect, "操作", "只保留这个场景必要的控制。"); + DrawCard(drawList, m_controlsRect, "操作", "只放当前批次需要检查的交互。"); for (const ButtonState& button : m_buttons) { DrawButton(drawList, button); } - DrawCard(drawList, m_stateRect, "状态", "重点检查根壳 contract 当前状态。"); + DrawCard(drawList, m_stateRect, "状态", "直接观察当前 contract 的状态。"); float stateY = m_stateRect.y + 66.0f; auto addStateLine = [&](std::string label, std::string value, const UIColor& color, float fontSize = 12.0f) { drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY), std::move(label) + ": " + std::move(value), color, fontSize); stateY += 20.0f; }; - addStateLine("Open Root", m_cachedFrame.openRootMenuId.empty() ? "(none)" : m_cachedFrame.openRootMenuId, kTextPrimary); - addStateLine("Popup Chain", JoinPopupChainIds(m_interactionState), kTextPrimary, 11.0f); - addStateLine("Submenu Path", JoinSubmenuPathIds(m_interactionState), kTextPrimary, 11.0f); + addStateLine("根菜单", m_cachedFrame.openRootMenuId.empty() ? "(none)" : m_cachedFrame.openRootMenuId, kShellPalette.textPrimary); + addStateLine("Popup 链", JoinPopupChainIds(m_interactionState), kShellPalette.textPrimary, 11.0f); + addStateLine("子菜单路径", JoinSubmenuPathIds(m_interactionState), kShellPalette.textPrimary, 11.0f); + addStateLine("资源校验", m_assetValidation.IsValid() ? "OK" : m_assetValidation.message, m_assetValidation.IsValid() ? kSuccess : kDanger, 11.0f); + addStateLine("当前呈现", selectedPresentation, kShellPalette.textPrimary, 11.0f); + addStateLine("当前面板", m_controller.GetWorkspace().activePanelId.empty() ? "(none)" : m_controller.GetWorkspace().activePanelId, kShellPalette.textPrimary, 11.0f); + addStateLine("焦点", FormatBool(m_cachedFrame.focused), m_cachedFrame.focused ? kSuccess : kShellPalette.textMuted); + addStateLine("菜单模态", FormatBool(m_cachedFrame.result.menuModal), m_cachedFrame.result.menuModal ? kSuccess : kShellPalette.textMuted, 11.0f); + addStateLine("Workspace 抑制", FormatBool(m_cachedFrame.result.workspaceInputSuppressed), m_cachedFrame.result.workspaceInputSuppressed ? kWarning : kShellPalette.textMuted, 11.0f); addStateLine( - "Asset Validation", - m_assetValidation.IsValid() ? "OK" : m_assetValidation.message, - m_assetValidation.IsValid() ? kSuccess : kDanger, - 11.0f); - addStateLine("Selected Presentation", selectedPresentation, kTextPrimary, 11.0f); - addStateLine("Active Panel", m_controller.GetWorkspace().activePanelId.empty() ? "(none)" : m_controller.GetWorkspace().activePanelId, kTextPrimary, 11.0f); - addStateLine("Focused", FormatBool(m_cachedFrame.focused), m_cachedFrame.focused ? kSuccess : kTextMuted); - addStateLine("Menu Modal", FormatBool(m_cachedFrame.result.menuModal), m_cachedFrame.result.menuModal ? kSuccess : kTextMuted, 11.0f); - addStateLine( - "Workspace Suppressed", - FormatBool(m_cachedFrame.result.workspaceInputSuppressed), - m_cachedFrame.result.workspaceInputSuppressed ? kWarning : kTextMuted, - 11.0f); - addStateLine( - "Command Dispatch", + "命令派发", m_cachedFrame.result.commandDispatched ? std::string(GetUIEditorCommandDispatchStatusName(m_cachedFrame.result.commandDispatchResult.status)) : std::string("(none)"), m_cachedFrame.result.commandDispatched && m_cachedFrame.result.commandDispatchResult.commandExecuted ? kSuccess - : kTextMuted, + : kShellPalette.textMuted, 11.0f); - addStateLine("Result", m_lastStatus, m_lastColor); - drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f), m_lastMessage, kTextMuted, 11.0f); + addStateLine("结果", m_lastStatus, m_lastColor); + drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f), m_lastMessage, kShellPalette.textMuted, 11.0f); stateY += 34.0f; - addStateLine("Visible Panels", JoinVisiblePanelIds(m_controller.GetWorkspace(), m_controller.GetSession()), kTextWeak, 11.0f); - addStateLine("Host Capture", FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kSuccess : kTextMuted, 11.0f); + addStateLine("可见面板", JoinVisiblePanelIds(m_controller.GetWorkspace(), m_controller.GetSession()), kShellPalette.textWeak, 11.0f); + addStateLine("宿主捕获", FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kSuccess : kShellPalette.textMuted, 11.0f); addStateLine( - "Screenshot", + "截图", m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." + ? "截图请求排队中..." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 或 按钮 -> captures/") + ? std::string("F12 或按钮 -> captures/") : m_autoScreenshot.GetLastCaptureSummary()), - kTextWeak, + kShellPalette.textWeak, 11.0f); - DrawCard(drawList, m_previewRect, "Preview", "真实 UIEditorShellInteraction 预览,不接旧 editor 业务。"); - AppendUIEditorShellInteraction(drawList, m_cachedFrame, m_interactionState); + DrawCard(drawList, m_previewRect, "预览", "这里是实际 UIEditorShellInteraction 预览,不接 editor 业务层。"); + const auto palette = ResolveUIEditorShellInteractionPalette(); + AppendUIEditorShellInteraction( + drawList, + m_cachedFrame, + m_interactionState, + palette, + metrics); const bool framePresented = m_renderer.Render(drawData); m_autoScreenshot.CaptureIfRequested( @@ -1013,9 +1034,9 @@ void ScenarioApp::RenderFrame() { static_cast(height), framePresented); } - } // namespace int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) { return ScenarioApp().Run(hInstance, nCmdShow); } + diff --git a/tests/UI/Editor/integration/shell/enum_field_basic/main.cpp b/tests/UI/Editor/integration/shell/enum_field_basic/main.cpp index e38eb6e7..bb027a9a 100644 --- a/tests/UI/Editor/integration/shell/enum_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/enum_field_basic/main.cpp @@ -1,11 +1,11 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include -#include -#include -#include +#include +#include +#include +#include #include "EditorValidationTheme.h" #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -51,8 +51,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorEnumFieldHitTarget; using XCEngine::UI::Editor::Widgets::UIEditorEnumFieldHitTargetKind; using XCEngine::UI::Editor::Widgets::UIEditorEnumFieldSpec; using XCEngine::UI::Editor::Widgets::UIEditorMenuPopupInvalidIndex; -namespace Style = XCEngine::UI::Style; - constexpr const wchar_t* kWindowClassName = L"XCUIEditorEnumFieldBasicValidation"; constexpr const wchar_t* kWindowTitle = L"XCUI Editor | EnumField Basic"; @@ -84,11 +82,6 @@ std::filesystem::path ResolveRepoRootPath() { return std::filesystem::path(root).lexically_normal(); } -std::filesystem::path ResolveValidationThemePath() { - return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme") - .lexically_normal(); -} - bool ContainsPoint(const UIRect& rect, float x, float y) { return x >= rect.x && x <= rect.x + rect.width && @@ -142,7 +135,7 @@ ScenarioLayout BuildScenarioLayout( layout.previewRect.x + 24.0f, layout.previewRect.y + 82.0f, 340.0f, - 32.0f); + 22.0f); const float buttonWidth = (layout.controlRect.width - 44.0f) * 0.5f; const float buttonY = layout.controlRect.y + 32.0f; @@ -387,14 +380,6 @@ private: m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/enum_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); - const auto themeLoad = - XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath()); - if (themeLoad.succeeded) { - m_theme = themeLoad.theme; - m_themeStatus = "loaded"; - } else { - m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error; - } ResetScenario(); return true; @@ -423,7 +408,7 @@ private: return BuildScenarioLayout( width, height, - XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme)); + XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics()); } UIRect GetViewportRect() const { @@ -458,8 +443,8 @@ private: } const ScenarioLayout layout = GetLayout(); - const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics(m_theme); - const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics(m_theme); + const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics(); + const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics(); m_spec.selectedIndex = m_selectedIndex; m_frame = UpdateUIEditorEnumFieldInteraction( m_interactionState, @@ -563,8 +548,8 @@ private: UIEditorEnumFieldInteractionResult PumpEvents(std::vector events) { const ScenarioLayout layout = GetLayout(); - const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics(m_theme); - const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics(m_theme); + const auto fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics(); + const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics(); m_spec.selectedIndex = m_selectedIndex; m_frame = UpdateUIEditorEnumFieldInteraction( m_interactionState, @@ -581,7 +566,7 @@ private: void UpdateResultText(const UIEditorEnumFieldInteractionResult& result) { if (result.selectionChanged && m_selectedIndex < m_spec.options.size()) { - m_lastResult = std::string("已切换到: ") + m_spec.options[m_selectedIndex]; + m_lastResult = std::string("已切换选项: ") + m_spec.options[m_selectedIndex]; return; } if (result.popupOpened) { @@ -627,17 +612,17 @@ private: } const UIRect viewportRect = GetViewportRect(); - const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme); - const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme); + const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); + const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); const ScenarioLayout layout = BuildScenarioLayout(viewportRect.width, viewportRect.height, shellMetrics); RefreshFrame(); const UIEditorEnumFieldHitTarget currentHit = HitTestUIEditorEnumField(m_frame.layout, m_mousePosition); - const auto enumMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics(m_theme); - const auto enumPalette = XCEngine::UI::Editor::ResolveUIEditorEnumFieldPalette(m_theme); - const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics(m_theme); - const auto popupPalette = XCEngine::UI::Editor::ResolveUIEditorMenuPopupPalette(m_theme); + const auto enumMetrics = XCEngine::UI::Editor::ResolveUIEditorEnumFieldMetrics(); + const auto enumPalette = XCEngine::UI::Editor::ResolveUIEditorEnumFieldPalette(); + const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics(); + const auto popupPalette = XCEngine::UI::Editor::ResolveUIEditorMenuPopupPalette(); UIDrawData drawData = {}; UIDrawList& drawList = drawData.EmplaceDrawList("EditorEnumFieldBasic"); @@ -648,26 +633,26 @@ private: layout.introRect, shellPalette, shellMetrics, - "这个测试在验证什么功能", - "只验证 Editor EnumField 的基础交互契约,不涉及 PropertyGrid 或业务 Inspector。"); + "这个测试在验证什么功能?", + "验证枚举字段的下拉打开/关闭、键盘切换、高亮同步,以及固定 Inspector 风格承载。"); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), - "1. 点击 value box 或 dropdown arrow,检查下拉菜单是否展开/收起。", + "1. 点击 value box 或 dropdown arrow,应该打开或关闭下拉菜单。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), - "2. 展开后 hover 列表项,检查高亮是否稳定跟随。", + "2. 打开后检查 hover 和高亮项是否同步更新。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), - "3. 获得 focus 后按 Up / Down / Home / End,再按 Enter / Space 选中,Esc 关闭。", + "3. 获得 focus 后,Up / Down / Home / End 切换高亮;Enter / Space 确认;Esc 关闭。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), - "4. 检查 Hover / Popup / Highlight / Selected / Result 是否同步更新。", + "4. 观察 Hover / Popup / Highlight / Selected / Result 是否同步。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( @@ -735,11 +720,6 @@ private: captureSummary, shellPalette.textWeak, shellMetrics.bodyFontSize); - drawList.AddText( - UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 238.0f), - "Theme: " + m_themeStatus, - shellPalette.textWeak, - shellMetrics.bodyFontSize); DrawCard( drawList, @@ -747,7 +727,7 @@ private: shellPalette, shellMetrics, "EnumField 预览", - "这里只放一个 Unity 风格 EnumField。"); + "这里只放一个固定样式的枚举字段预览。"); AppendUIEditorEnumField( drawList, layout.fieldRect, @@ -783,12 +763,10 @@ private: std::size_t m_selectedIndex = 0u; UIEditorEnumFieldInteractionState m_interactionState = {}; UIEditorEnumFieldInteractionFrame m_frame = {}; - Style::UITheme m_theme = {}; UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f); ActionId m_hoveredAction = ActionId::Reset; bool m_hasHoveredAction = false; std::string m_lastResult = {}; - std::string m_themeStatus = "fallback"; }; } // namespace diff --git a/tests/UI/Editor/integration/shell/list_view_basic/main.cpp b/tests/UI/Editor/integration/shell/list_view_basic/main.cpp index 459926a8..93bd3177 100644 --- a/tests/UI/Editor/integration/shell/list_view_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/list_view_basic/main.cpp @@ -643,31 +643,31 @@ private: DrawCard( drawList, layout.introRect, - "这个测试在验证什么功能", - "只验证 Editor ListView 基础控件,不涉及任何业务面板。"); + "这个测试验证什么功能?", + "只验证 Editor ListView 基础控件:行布局、单选、focus 和键盘导航。不涉及任何业务面板。"); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), - "1. 验证列表行的垂直排列是否稳定:主标题和次标题不能互相挤压。", + "1. 检查列表行垂直排布是否稳定:主标题和次标题不能互相挤压。", kTextPrimary, 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), - "2. 点击 row 只切换 selection;hover、selected、focused 三种状态要能区分。", + "2. 点击 row 只切换 selection;hover、selected、focused 必须能区分。", kTextPrimary, 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), - "3. 当列表获得 focus 后,按 Up / Down / Home / End 应稳定移动当前选择。", + "3. 列表获得 focus 后,按 Up / Down / Home / End 应稳定移动当前选择。", kTextPrimary, 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), - "4. 点击列表外空白后,focus 应清除,但 selection 不应丢失。", + "4. 点击列表外空白后,focus 应清除,但 selection 必须保留。", kTextPrimary, 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f), - "5. 按 F12 手动截图;设置 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可自动截图。", + "5. 按 F12 手动截图;设置 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可启动自动截图。", kTextPrimary, 12.0f); @@ -727,7 +727,7 @@ private: kTextWeak, 12.0f); - DrawCard(drawList, layout.previewRect, "ListView 预览", "这里只放一个 ListView,不混入 Project/Console 等业务内容。"); + DrawCard(drawList, layout.previewRect, "ListView 预览", "这里只放一个 ListView,不混入 Project / Console 等业务内容。"); AppendUIEditorListViewBackground( drawList, m_listFrame.layout, diff --git a/tests/UI/Editor/integration/shell/list_view_inline_rename/main.cpp b/tests/UI/Editor/integration/shell/list_view_inline_rename/main.cpp index 5c8dd0b5..259bba64 100644 --- a/tests/UI/Editor/integration/shell/list_view_inline_rename/main.cpp +++ b/tests/UI/Editor/integration/shell/list_view_inline_rename/main.cpp @@ -1,10 +1,11 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif #include #include #include +#include #include #include #include "EditorValidationTheme.h" @@ -14,7 +15,6 @@ #include #include -#include #include #include @@ -33,6 +33,8 @@ #define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "." #endif +// 场景说明:验证固定样式下 ListView 的 inline rename 提交、取消与外部点击提交。 + namespace { using XCEngine::Input::KeyCode; @@ -47,8 +49,8 @@ using XCEngine::UI::UIPoint; using XCEngine::UI::UIPointerButton; using XCEngine::UI::UIRect; using XCEngine::UI::Widgets::UISelectionModel; -using XCEngine::UI::Editor::BuildUIEditorHostedTextFieldMetrics; -using XCEngine::UI::Editor::BuildUIEditorHostedTextFieldPalette; +using XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldMetrics; +using XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldPalette; using XCEngine::UI::Editor::BuildUIEditorInlineRenameTextFieldMetrics; using XCEngine::UI::Editor::Host::AutoScreenshotController; using XCEngine::UI::Editor::Host::InputModifierTracker; @@ -77,8 +79,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorListViewInvalidIndex; using XCEngine::UI::Editor::Widgets::UIEditorListViewItem; using XCEngine::UI::Editor::Widgets::UIEditorTextFieldMetrics; using XCEngine::UI::Editor::Widgets::UIEditorTextFieldPalette; -namespace Style = XCEngine::UI::Style; - constexpr const wchar_t* kWindowClassName = L"XCUIEditorListViewInlineRenameValidation"; constexpr const wchar_t* kWindowTitle = L"XCUI Editor | ListView Inline Rename"; @@ -111,11 +111,6 @@ std::filesystem::path ResolveRepoRootPath() { return std::filesystem::path(root).lexically_normal(); } -std::filesystem::path ResolveValidationThemePath() { - return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme") - .lexically_normal(); -} - bool ContainsPoint(const UIRect& rect, float x, float y) { return x >= rect.x && x <= rect.x + rect.width && @@ -242,12 +237,12 @@ void DrawButton( std::vector BuildListItems() { return { - { "scene", "SampleScene.unity", "Scene | 4 分钟前修改", 0.0f }, - { "lighting", "LightingProfile.asset", "Preset | 3 个 profile", 0.0f }, + { "scene", "SampleScene.unity", "Scene | modified 4 minutes ago", 0.0f }, + { "lighting", "LightingProfile.asset", "Preset | 3 profiles", 0.0f }, { "material", "RobotBody.mat", "Material | Metallic Workflow", 0.0f }, { "script", "PlayerController.cs", "C# Script | 3.4 KB", 0.0f }, { "texture", "Checker_AO.png", "Texture2D | 2048x2048", 0.0f }, - { "prefab", "Robot.prefab", "Prefab | 9 个组件", 0.0f }, + { "prefab", "Robot.prefab", "Prefab | 9 children", 0.0f }, { "anim", "Walk.anim", "Animation Clip | 1.2 s", 0.0f }, { "shader", "Outline.shader", "Shader | URP compatible", 0.0f } }; @@ -346,8 +341,6 @@ private: AutoScreenshotController m_autoScreenshot = {}; InputModifierTracker m_modifierTracker = {}; std::filesystem::path m_captureRoot = {}; - Style::UITheme m_theme = {}; - std::string m_themeStatus = "fallback"; std::vector m_items = {}; UISelectionModel m_selectionModel = {}; UIEditorListViewInteractionState m_listInteractionState = {}; @@ -524,13 +517,6 @@ bool ScenarioApp::Initialize(HINSTANCE hInstance, int nCmdShow) { m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/list_view_inline_rename/captures"; m_autoScreenshot.Initialize(m_captureRoot); - const auto themeLoad = XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath()); - if (themeLoad.succeeded) { - m_theme = themeLoad.theme; - m_themeStatus = "loaded"; - } else { - m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error; - } m_modifierTracker.SyncFromSystemState(); ResetScenario(); @@ -560,7 +546,7 @@ ScenarioLayout ScenarioApp::GetLayout() const { return BuildScenarioLayout( width, height, - XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme)); + XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics()); } void ScenarioApp::ResetScenario() { @@ -769,7 +755,7 @@ void ScenarioApp::HandleWindowFocusLost() { PumpRenameEvents({ MakeFocusEvent(UIInputEventType::FocusLost) }); } else { PumpListEvents({ MakeFocusEvent(UIInputEventType::FocusLost) }); - m_lastResult = "窗口失去焦点,ListView focus 已清除。"; + m_lastResult = "Window focus lost. ListView focus cleared."; } InvalidateRect(m_hwnd, nullptr, FALSE); @@ -867,7 +853,6 @@ void ScenarioApp::ApplyRenameFrame(const UIEditorInlineRenameSessionFrame& frame m_lastCommittedValue = result.valueAfter; RefreshListFrame(); } - m_lastResult = "已提交 rename: " + result.itemId + " -> " + result.valueAfter; return; } @@ -918,9 +903,9 @@ UIEditorInlineRenameSessionRequest ScenarioApp::BuildRenameRequest(bool beginSes } UIEditorTextFieldMetrics ScenarioApp::ResolveHostedTextFieldMetrics() const { - const auto propertyMetrics = ResolveUIEditorPropertyGridMetrics(m_theme); - const auto textMetrics = ResolveUIEditorTextFieldMetrics(m_theme); - return BuildUIEditorHostedTextFieldMetrics(propertyMetrics, textMetrics); + const auto propertyMetrics = ResolveUIEditorPropertyGridMetrics(); + const auto textMetrics = ResolveUIEditorTextFieldMetrics(); + return BuildUIEditorPropertyGridTextFieldMetrics(propertyMetrics, textMetrics); } UIEditorTextFieldMetrics ScenarioApp::ResolveInlineRenameMetrics(const UIRect& bounds) const { @@ -928,9 +913,9 @@ UIEditorTextFieldMetrics ScenarioApp::ResolveInlineRenameMetrics(const UIRect& b } UIEditorTextFieldPalette ScenarioApp::ResolveInlineRenamePalette() const { - const auto propertyPalette = ResolveUIEditorPropertyGridPalette(m_theme); - const auto textPalette = ResolveUIEditorTextFieldPalette(m_theme); - return BuildUIEditorHostedTextFieldPalette(propertyPalette, textPalette); + const auto propertyPalette = ResolveUIEditorPropertyGridPalette(); + const auto textPalette = ResolveUIEditorTextFieldPalette(); + return BuildUIEditorPropertyGridTextFieldPalette(propertyPalette, textPalette); } void ScenarioApp::UpdateListResultText(const UIEditorListViewInteractionResult& result) { @@ -978,8 +963,8 @@ void ScenarioApp::RenderFrame() { GetClientRect(m_hwnd, &clientRect); const float width = static_cast((std::max)(1L, clientRect.right - clientRect.left)); const float height = static_cast((std::max)(1L, clientRect.bottom - clientRect.top)); - const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme); - const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme); + const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); + const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics); RefreshListFrame(); @@ -1002,36 +987,36 @@ void ScenarioApp::RenderFrame() { layout.introRect, shellPalette, shellMetrics, - "这个测试在验证什么功能?", - "只验证 Editor ListView 的 inline rename 契约:默认直接进入编辑、字符编辑、Enter 提交、Esc 取消、点击外部提交。"); + "这个测试验证什么功能?", + "只验证 Editor ListView 的 inline rename:默认进入编辑、字符编辑、Enter 提交、Esc 取消、点击外部提交。"); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), - "1. 启动后默认直接进入 script 的 rename,检查输入框是否只覆盖当前选中行主文本。", + "1. 启动后默认直接进入 script 的 rename,输入框只能覆盖当前选中行主文本。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), - "2. 输入字符后,检查 Draft 是否实时变化,且列表原文本被 overlay 正确遮住,不应双层叠字。", + "2. 输入字符后,Draft 必须实时变化,原文本不能和 overlay 叠字。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), - "3. 按 Enter,检查名称是否写回到对应 ListViewItem.primaryText,并关闭 rename。", + "3. 按 Enter:名称写回 ListViewItem.primaryText,并退出 rename。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), - "4. 按 Esc,检查是否取消编辑并保留原文本。", + "4. 按 Esc:取消编辑,保留原文本。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f), - "5. rename 过程中点击输入框外部,检查是否提交当前草稿并退出编辑态。", + "5. rename 过程中点击输入框外部:提交当前草稿,并退出编辑态。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 182.0f), - "6. 需要时可先按 Esc 退出,再用 Up/Down/Home/End 切换选中项后按 F2;F12 或按钮触发截图。", + "6. 需要时先按 Esc 退出,再用 Up/Down/Home/End 切换选中项后按 F2;F12 可截图。", shellPalette.textPrimary, shellMetrics.bodyFontSize); @@ -1116,7 +1101,7 @@ void ScenarioApp::RenderFrame() { shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 310.0f), - "Theme: " + m_themeStatus, + "Style: fixed", shellPalette.textWeak, shellMetrics.bodyFontSize); diff --git a/tests/UI/Editor/integration/shell/list_view_multiselect/main.cpp b/tests/UI/Editor/integration/shell/list_view_multiselect/main.cpp index 568b1a12..d8c7d5b1 100644 --- a/tests/UI/Editor/integration/shell/list_view_multiselect/main.cpp +++ b/tests/UI/Editor/integration/shell/list_view_multiselect/main.cpp @@ -11,7 +11,6 @@ #include #include -#include #include #include @@ -56,8 +55,6 @@ using XCEngine::UI::Editor::Widgets::AppendUIEditorListViewForeground; using XCEngine::UI::Editor::Widgets::HitTestUIEditorListView; using XCEngine::UI::Editor::Widgets::UIEditorListViewHitTarget; using XCEngine::UI::Editor::Widgets::UIEditorListViewItem; -namespace Style = XCEngine::UI::Style; - constexpr const wchar_t* kWindowClassName = L"XCUIEditorListViewMultiSelectValidation"; constexpr const wchar_t* kWindowTitle = L"XCUI Editor | ListView MultiSelect"; @@ -90,11 +87,6 @@ std::filesystem::path ResolveRepoRootPath() { return std::filesystem::path(root).lexically_normal(); } -std::filesystem::path ResolveValidationThemePath() { - return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme") - .lexically_normal(); -} - bool ContainsPoint(const UIRect& rect, float x, float y) { return x >= rect.x && x <= rect.x + rect.width && @@ -196,12 +188,12 @@ void DrawButton( std::vector BuildListItems() { return { - { "scene", "SampleScene.unity", "Scene | 4 分钟前修改", 0.0f }, - { "lighting", "LightingProfile.asset", "Preset | 3 个 profile", 0.0f }, + { "scene", "SampleScene.unity", "Scene | modified 4 minutes ago", 0.0f }, + { "lighting", "LightingProfile.asset", "Preset | 3 profiles", 0.0f }, { "material", "RobotBody.mat", "Material | Metallic Workflow", 0.0f }, { "script", "PlayerController.cs", "C# Script | 3.4 KB", 0.0f }, { "texture", "Checker_AO.png", "Texture2D | 2048x2048", 0.0f }, - { "prefab", "Robot.prefab", "Prefab | 9 个组件", 0.0f }, + { "prefab", "Robot.prefab", "Prefab | 9 children", 0.0f }, { "anim", "Walk.anim", "Animation Clip | 1.2 s", 0.0f }, { "shader", "Outline.shader", "Shader | URP compatible", 0.0f }, { "mesh", "Robot.fbx", "Model | 38k triangles", 0.0f }, @@ -439,14 +431,6 @@ private: ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/list_view_multiselect/captures"; m_autoScreenshot.Initialize(m_captureRoot); - const auto themeLoad = - XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath()); - if (themeLoad.succeeded) { - m_theme = themeLoad.theme; - m_themeStatus = "loaded"; - } else { - m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error; - } m_modifierTracker.SyncFromSystemState(); ResetScenario(); @@ -476,7 +460,7 @@ private: return BuildScenarioLayout( width, height, - XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme)); + XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics()); } void ResetScenario() { @@ -489,7 +473,7 @@ private: m_hasHoveredAction = false; m_hoveredAction = ActionId::Reset; m_lastModifiers = {}; - m_lastResult = "已重置到默认多选状态"; + m_lastResult = "Reset to the default multiselect state."; RefreshFrame(); } @@ -605,7 +589,7 @@ private: if (wParam == VK_F12) { m_autoScreenshot.RequestCapture("manual_f12"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "�������ͼ�����??captures/latest.png"; InvalidateRect(m_hwnd, nullptr, FALSE); return; } @@ -675,13 +659,13 @@ private: } if (result.keyboardNavigated && !result.selectedItemId.empty()) { - m_lastResult = "键盘导航到: " + result.selectedItemId; + m_lastResult = "���̵���?? " + result.selectedItemId; return; } if (result.selectionChanged) { m_lastResult = - "选集已更新: primary=" + m_selectionModel.GetSelectedId() + + "ѡ���Ѹ�?? primary=" + m_selectionModel.GetSelectedId() + " | ids=" + JoinSelectedIds(m_selectionModel); return; } @@ -692,7 +676,7 @@ private: } if (insideList) { - m_lastResult = "点击列表内空白,只更新 hover / focus"; + m_lastResult = "����б��ڿհף�ֻ��??hover / focus"; return; } @@ -706,7 +690,7 @@ private: break; case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "已请求截图,输出到 captures/latest.png"; + m_lastResult = "�������ͼ�����??captures/latest.png"; break; } } @@ -720,8 +704,8 @@ private: GetClientRect(m_hwnd, &clientRect); const float width = static_cast((std::max)(1L, clientRect.right - clientRect.left)); const float height = static_cast((std::max)(1L, clientRect.bottom - clientRect.top)); - const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme); - const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme); + const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); + const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics); RefreshFrame(); @@ -737,7 +721,7 @@ private: layout.introRect, shellPalette, shellMetrics, - "这个测试在验证什么功能?", + "这个测试验证什么功能?", "只验证 Editor ListView 多选 contract:Ctrl/Shift 选集、右键 primary 切换、键盘范围扩展,不涉及任何业务面板。"); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), @@ -845,11 +829,6 @@ private: captureSummary, shellPalette.textWeak, shellMetrics.bodyFontSize); - drawList.AddText( - UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 310.0f), - "Theme: " + m_themeStatus, - shellPalette.textWeak, - shellMetrics.bodyFontSize); DrawCard( drawList, @@ -881,8 +860,6 @@ private: AutoScreenshotController m_autoScreenshot = {}; InputModifierTracker m_modifierTracker = {}; std::filesystem::path m_captureRoot = {}; - Style::UITheme m_theme = {}; - std::string m_themeStatus = "fallback"; std::vector m_items = {}; UISelectionModel m_selectionModel = {}; UIEditorListViewInteractionState m_interactionState = {}; diff --git a/tests/UI/Editor/integration/shell/menu_bar_basic/main.cpp b/tests/UI/Editor/integration/shell/menu_bar_basic/main.cpp index da60152c..eaff9ab1 100644 --- a/tests/UI/Editor/integration/shell/menu_bar_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/menu_bar_basic/main.cpp @@ -2,12 +2,12 @@ #define NOMINMAX #endif -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -866,9 +866,9 @@ void ScenarioApp::ResetScenario() { m_menuPopups.clear(); m_menuItems.clear(); SetCustomResult( - "等待操作", + "绛夊緟鎿嶄綔", "Pending", - "先点 File / Window / Layout,确认一次只会打开一个菜单;再点菜单外区域或按 Escape 关闭。"); + "鍏堢偣 File / Window / Layout锛岀‘璁や竴娆″彧浼氭墦寮€涓€涓彍鍗曪紱鍐嶇偣鑿滃崟澶栧尯鍩熸垨鎸?Escape 鍏抽棴銆?); #endif } @@ -1004,7 +1004,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) { SetCustomResult( "Hover 切换顶层菜单", "Changed", - "已切换到顶层菜单 `" + hoveredButton->label + "`。重点检查:旧 root popup 被替换,新 root popup 立即出现。"); + "已切换到顶层菜单 `" + hoveredButton->label + "`。重点检查:旧 root popup 被替换后,新 root popup 是否立即出现。"); dirty = true; } } else { @@ -1014,7 +1014,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) { SetCustomResult( "Hover 收起子菜单", "Dismissed", - "鼠标回到顶层按钮后,旧的 child popup 已收起。closed: " + + "鼠标回到顶层按钮后,旧的 child popup 已收起。Closed: " + JoinClosedPopupIds(mutation)); dirty = true; } @@ -1040,7 +1040,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) { SetCustomResult( "Hover 收起子菜单", "Dismissed", - "鼠标移到普通菜单项后,旧的 child popup 已收起。closed: " + + "鼠标移到普通菜单项后,旧的 child popup 已收起。Closed: " + JoinClosedPopupIds(mutation)); dirty = true; } @@ -1052,7 +1052,7 @@ void ScenarioApp::HandleMouseMove(float x, float y) { SetCustomResult( "Hover popup 空白区", "Dismissed", - "鼠标停留在 popup 空白区后,子菜单链已回收到当前 popup。closed: " + + "鼠标停留在 popup 空白区后,子菜单链已回收到当前 popup。Closed: " + JoinClosedPopupIds(mutation)); dirty = true; } @@ -1075,7 +1075,7 @@ void ScenarioApp::HandleClick(float x, float y) { "点击关闭顶层菜单", mutation.changed ? "Dismissed" : "NoOp", mutation.changed - ? "再次点击当前顶层按钮后,整条菜单链已关闭。closed: " + + ? "再次点击当前顶层按钮后,整条菜单链已关闭。Closed: " + JoinClosedPopupIds(mutation) : "当前顶层菜单没有发生变化。"); } else { @@ -1108,8 +1108,8 @@ void ScenarioApp::HandleClick(float x, float y) { mutation.changed ? "Changed" : "NoOp", mutation.changed ? "已展开 `" + hoveredItem->label + - "` 子菜单。这个场景的正常行为是 hover 也会直接展开。" - : "子菜单已经处于打开状态。"); + "` 子菜单。这个场景里正常行为是 hover 也会直接展开。" + : "子菜单已经处于展开状态。"); InvalidateRect(m_hwnd, nullptr, FALSE); return; } @@ -1140,7 +1140,7 @@ void ScenarioApp::HandleClick(float x, float y) { SetCustomResult( "点击 popup 空白区", "Dismissed", - "点击当前 popup 空白区后,仅更深层子菜单被关闭。closed: " + + "点击当前 popup 空白区后,仅更深层子菜单被关闭。Closed: " + JoinClosedPopupIds(mutation)); ClearHoverWhenMenuClosed(); InvalidateRect(m_hwnd, nullptr, FALSE); @@ -1155,7 +1155,7 @@ void ScenarioApp::HandleClick(float x, float y) { "点击菜单外区域", mutation.changed ? "Dismissed" : "NoOp", mutation.changed - ? "点击外部区域后,整条菜单链已关闭。closed: " + + ? "点击外部区域后,整条菜单链已关闭。Closed: " + JoinClosedPopupIds(mutation) : "菜单链没有变化。"); ClearHoverWhenMenuClosed(); @@ -1172,14 +1172,14 @@ void ScenarioApp::HandleClick(float x, float y) { if (m_openMenuId == button.menuId) { m_openMenuId.clear(); m_hoveredItemId.clear(); - SetCustomResult("关闭菜单", "NoOp", "再次点击当前菜单按钮,菜单已关闭。"); + SetCustomResult("鍏抽棴鑿滃崟", "NoOp", "鍐嶆鐐瑰嚮褰撳墠鑿滃崟鎸夐挳锛岃彍鍗曞凡鍏抽棴銆?); } else { m_openMenuId = button.menuId; m_hoveredItemId.clear(); SetCustomResult( - "打开菜单", + "鎵撳紑鑿滃崟", "Changed", - "当前激活菜单: " + button.label + "。确认同一时刻只存在一个下拉菜单。"); + "褰撳墠婵€娲昏彍鍗? " + button.label + "銆傜‘璁ゅ悓涓€鏃跺埢鍙瓨鍦ㄤ竴涓笅鎷夎彍鍗曘€?); } InvalidateRect(m_hwnd, nullptr, FALSE); return; @@ -1197,9 +1197,9 @@ void ScenarioApp::HandleClick(float x, float y) { if (!item.enabled) { SetCustomResult( - "菜单项不可执行", + "鑿滃崟椤逛笉鍙墽琛?, "Disabled", - "当前工作区状态下 `" + item.label + "` 不可执行。"); + "褰撳墠宸ヤ綔鍖虹姸鎬佷笅 `" + item.label + "` 涓嶅彲鎵ц銆?); InvalidateRect(m_hwnd, nullptr, FALSE); return; } @@ -1216,7 +1216,7 @@ void ScenarioApp::HandleClick(float x, float y) { if (!m_openMenuId.empty()) { m_openMenuId.clear(); m_hoveredItemId.clear(); - SetCustomResult("菜单失焦", "Dismissed", "点击菜单外区域,菜单已关闭。"); + SetCustomResult("鑿滃崟澶辩劍", "Dismissed", "鐐瑰嚮鑿滃崟澶栧尯鍩燂紝鑿滃崟宸插叧闂€?); InvalidateRect(m_hwnd, nullptr, FALSE); } #endif @@ -1255,13 +1255,13 @@ void ScenarioApp::HandleKeyDown(UINT keyCode) { if (!m_openMenuId.empty()) { m_openMenuId.clear(); m_hoveredItemId.clear(); - SetCustomResult("Esc 关闭菜单", "Dismissed", "按下 Escape 后,菜单已关闭。"); + SetCustomResult("Esc 鍏抽棴鑿滃崟", "Dismissed", "鎸変笅 Escape 鍚庯紝鑿滃崟宸插叧闂€?); InvalidateRect(m_hwnd, nullptr, FALSE); } break; case 'R': SetDispatchResult( - "键盘 Reset Workspace", + "閿洏 Reset Workspace", m_commandDispatcher.Dispatch("workspace.reset", m_controller)); InvalidateRect(m_hwnd, nullptr, FALSE); break; @@ -1446,13 +1446,13 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height) DrawCard( drawList, headerRect, - "测试内容:Editor Menu 会话状态层", + "这个测试验证什么功能?", "本场景只验证顶层菜单切换、child popup hover、Esc / outside dismiss、命令派发;不验证业务面板。"); drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 70.0f), "1. 点击 File 打开 root popup,再 hover `Workspace Tools`,右侧 child popup 应立即弹出。", kTextPrimary, 13.0f); drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 92.0f), "2. root popup 打开时,把鼠标移到 Window / Layout 顶层按钮,旧 popup 应直接被新的 root popup 替换。", kTextPrimary, 13.0f); drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 114.0f), "3. 点击 Window -> Activate Details,再点 Window -> Hide Active,检查右侧 details visible / active 是否按状态变化。", kTextPrimary, 13.0f); drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 136.0f), "4. 如果 child popup 已打开,按一次 Esc 只关 topmost;再按一次 Esc 关闭整条菜单链。点击外部空白区也必须整条关闭。", kTextPrimary, 13.0f); - drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 158.0f), "5. F12 保存截图;R 可直接触发 Reset Workspace,方便确认命令派发仍正常。", kTextPrimary, 13.0f); + drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 158.0f), "5. F12 保存截图,R 可直接触发 Reset Workspace,方便确认命令派发仍正常。", kTextPrimary, 13.0f); DrawCard(drawList, shellRect, "操作区", "这里只保留 MenuBar 和当前 popup overlay。"); DrawCard(drawList, stateRect, "状态摘要", "重点看 menu session 当前链路和 Details 状态,不放无关杂项。"); @@ -1464,15 +1464,15 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height) const UIRect shellInfoRect(shellRect.x + 18.0f, shellRect.y + 144.0f, shellRect.width - 36.0f, 190.0f); drawList.AddFilledRect(shellInfoRect, kIndicatorBg, 8.0f); drawList.AddRectOutline(shellInfoRect, kCardBorder, 1.0f, 8.0f); - drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 14.0f), "Open root menu", kTextMuted, 12.0f); + drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 14.0f), "已打开 root menu", kTextMuted, 12.0f); drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 34.0f), m_menuSession.HasOpenMenu() ? std::string(m_menuSession.GetOpenRootMenuId()) : "(none)", kTextPrimary, 14.0f); - drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 62.0f), "Popup chain", kTextMuted, 12.0f); + drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 62.0f), "Popup 链", kTextMuted, 12.0f); drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 82.0f), JoinPopupChainIds(m_menuSession), kTextPrimary, 14.0f); - drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 110.0f), "Submenu path", kTextMuted, 12.0f); + drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 110.0f), "Submenu 路径", kTextMuted, 12.0f); drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 130.0f), JoinSubmenuPathIds(m_menuSession), kTextPrimary, 14.0f); drawList.AddText(UIPoint(shellInfoRect.x + 14.0f, shellInfoRect.y + 158.0f), "提示:popup overlay 必须压在右侧状态面板上方,这一轮会专门检查这个层级关系。", kTextMuted, 12.0f); - drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 72.0f), "Hover summary", kAccent, 15.0f); + drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 72.0f), "Hover 摘要", kAccent, 15.0f); drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 100.0f), "hover menu", kTextMuted, 12.0f); drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 120.0f), m_hoveredMenuId.empty() ? "(none)" : m_hoveredMenuId, kTextPrimary, 14.0f); drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 150.0f), "hover popup", kTextMuted, 12.0f); @@ -1480,19 +1480,19 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height) drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 200.0f), "hover item", kTextMuted, 12.0f); drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 220.0f), m_hoveredItemId.empty() ? "(none)" : m_hoveredItemId, kTextPrimary, 14.0f); - drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 262.0f), "Workspace summary", kAccent, 15.0f); - drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 290.0f), "active panel", kTextMuted, 12.0f); + drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 262.0f), "Workspace 摘要", kAccent, 15.0f); + drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 290.0f), "当前激活面板", kTextMuted, 12.0f); drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 310.0f), workspace.activePanelId.empty() ? "(none)" : workspace.activePanelId, kTextPrimary, 14.0f); - drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 338.0f), "visible panels", kTextMuted, 12.0f); + drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 338.0f), "可见面板", kTextMuted, 12.0f); drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 358.0f), JoinVisiblePanelIds(workspace, session), kTextPrimary, 14.0f); - drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 386.0f), "details visible", kTextMuted, 12.0f); + drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 386.0f), "Details 可见", kTextMuted, 12.0f); drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 406.0f), detailsState != nullptr && detailsState->visible ? "true" : "false", detailsState != nullptr && detailsState->visible ? kSuccess : kWarning, 14.0f); - drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 434.0f), "details active", kTextMuted, 12.0f); + drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 434.0f), "Details 激活", kTextMuted, 12.0f); drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 454.0f), workspace.activePanelId == "details" ? "true" : "false", workspace.activePanelId == "details" ? kSuccess : kTextMuted, 14.0f); - drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 482.0f), "menu model validation", kTextMuted, 12.0f); + drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 482.0f), "菜单模型验证", kTextMuted, 12.0f); drawList.AddText(UIPoint(stateRect.x + 18.0f, stateRect.y + 502.0f), menuValidation.IsValid() ? "OK" : menuValidation.message, menuValidation.IsValid() ? kSuccess : kDanger, 12.0f); - drawList.AddText(UIPoint(footerRect.x + 18.0f, footerRect.y + 56.0f), "Last interaction: " + m_lastActionName + " | Result: " + m_lastStatusLabel, m_lastStatusColor, 13.0f); + drawList.AddText(UIPoint(footerRect.x + 18.0f, footerRect.y + 56.0f), "最近交互: " + m_lastActionName + " | Result: " + m_lastStatusLabel, m_lastStatusColor, 13.0f); drawList.AddText(UIPoint(footerRect.x + 18.0f, footerRect.y + 74.0f), m_lastMessage, kTextPrimary, 12.0f); const std::string captureSummary = @@ -1630,7 +1630,7 @@ void ScenarioApp::BuildDrawData(UIDrawData& drawData, float width, float height) DrawCard( drawList, headerRect, - "测试内容:Editor Menu 基础壳层验证", + "这个测试验证什么功能?", "只验证 MenuBar / 下拉展开 / hover / 菜单关闭 / command dispatch;不验证业务面板,不验证完整编辑器菜单体系。"); drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 70.0f), "1. 点击 File / Window / Layout,确认同一时刻只会有一个菜单展开。", kTextPrimary, 13.0f); drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 92.0f), "2. 展开菜单后移动鼠标,确认 hover 高亮稳定,disabled 项不会误显示成可点击。", kTextPrimary, 13.0f); diff --git a/tests/UI/Editor/integration/shell/number_field_basic/main.cpp b/tests/UI/Editor/integration/shell/number_field_basic/main.cpp index 2e10f757..c2057a72 100644 --- a/tests/UI/Editor/integration/shell/number_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/number_field_basic/main.cpp @@ -1,10 +1,11 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include -#include -#include +#include +#include +#include +#include #include "EditorValidationTheme.h" #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -48,7 +49,6 @@ using XCEngine::UI::Editor::Widgets::HitTestUIEditorNumberField; using XCEngine::UI::Editor::Widgets::UIEditorNumberFieldHitTarget; using XCEngine::UI::Editor::Widgets::UIEditorNumberFieldHitTargetKind; using XCEngine::UI::Editor::Widgets::UIEditorNumberFieldSpec; -namespace Style = XCEngine::UI::Style; constexpr const wchar_t* kWindowClassName = L"XCUIEditorNumberFieldBasicValidation"; constexpr const wchar_t* kWindowTitle = L"XCUI Editor | NumberField Basic"; @@ -84,10 +84,6 @@ std::filesystem::path ResolveRepoRootPath() { return std::filesystem::path(root).lexically_normal(); } -std::filesystem::path ResolveValidationThemePath() { - return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme") - .lexically_normal(); -} bool ContainsPoint(const UIRect& rect, float x, float y) { return x >= rect.x && @@ -170,18 +166,16 @@ ScenarioLayout BuildScenarioLayout( return layout; } -XCEngine::UI::Editor::Widgets::UIEditorNumberFieldMetrics ResolveHostedNumberFieldMetrics( - const Style::UITheme& theme) { - const auto propertyMetrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(theme); - const auto numberMetrics = XCEngine::UI::Editor::ResolveUIEditorNumberFieldMetrics(theme); - return XCEngine::UI::Editor::BuildUIEditorHostedNumberFieldMetrics(propertyMetrics, numberMetrics); +XCEngine::UI::Editor::Widgets::UIEditorNumberFieldMetrics ResolvePropertyGridNumberFieldMetrics() { + return XCEngine::UI::Editor::BuildUIEditorPropertyGridNumberFieldMetrics( + XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(), + XCEngine::UI::Editor::ResolveUIEditorNumberFieldMetrics()); } -XCEngine::UI::Editor::Widgets::UIEditorNumberFieldPalette ResolveHostedNumberFieldPalette( - const Style::UITheme& theme) { - const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(theme); - const auto numberPalette = XCEngine::UI::Editor::ResolveUIEditorNumberFieldPalette(theme); - return XCEngine::UI::Editor::BuildUIEditorHostedNumberFieldPalette(propertyPalette, numberPalette); +XCEngine::UI::Editor::Widgets::UIEditorNumberFieldPalette ResolvePropertyGridNumberFieldPalette() { + return XCEngine::UI::Editor::BuildUIEditorPropertyGridNumberFieldPalette( + XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(), + XCEngine::UI::Editor::ResolveUIEditorNumberFieldPalette()); } void DrawCard( @@ -430,14 +424,6 @@ private: m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/number_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); - const auto themeLoad = - XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath()); - if (themeLoad.succeeded) { - m_theme = themeLoad.theme; - m_themeStatus = "loaded"; - } else { - m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error; - } ResetScenario(); return true; @@ -466,7 +452,7 @@ private: return BuildScenarioLayout( width, height, - XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme)); + XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics()); } void ResetScenario() { @@ -494,7 +480,7 @@ private: } const ScenarioLayout layout = GetLayout(); - const auto metrics = ResolveHostedNumberFieldMetrics(m_theme); + const auto metrics = ResolvePropertyGridNumberFieldMetrics(); m_frame = UpdateUIEditorNumberFieldInteraction( m_interactionState, m_spec, @@ -604,7 +590,7 @@ private: UIEditorNumberFieldInteractionResult PumpEvents(std::vector events) { const ScenarioLayout layout = GetLayout(); - const auto metrics = ResolveHostedNumberFieldMetrics(m_theme); + const auto metrics = ResolvePropertyGridNumberFieldMetrics(); m_frame = UpdateUIEditorNumberFieldInteraction( m_interactionState, m_spec, @@ -616,7 +602,7 @@ private: void UpdateResultText(const UIEditorNumberFieldInteractionResult& result) { if (result.editCommitRejected) { - m_lastResult = "提交失败,仍保持在编辑态"; + m_lastResult = "提交失败,仍保持在编辑状态。"; return; } if (result.editCommitted) { @@ -624,11 +610,11 @@ private: return; } if (result.editCanceled) { - m_lastResult = "已取消编辑"; + m_lastResult = "已取消编辑。"; return; } if (result.editStarted) { - m_lastResult = "已进入编辑态"; + m_lastResult = "已进入编辑状态。"; return; } if (result.valueChanged || result.stepApplied) { @@ -636,7 +622,7 @@ private: return; } if (result.consumed) { - m_lastResult = "控件已消费输入"; + m_lastResult = "控件已消费输入。"; return; } m_lastResult = "等待交互"; @@ -664,16 +650,16 @@ private: GetClientRect(m_hwnd, &clientRect); const float width = static_cast((std::max)(1L, clientRect.right - clientRect.left)); const float height = static_cast((std::max)(1L, clientRect.bottom - clientRect.top)); - const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme); - const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme); + const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); + const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics); RefreshFrame(); const UIEditorNumberFieldHitTarget currentHit = HitTestUIEditorNumberField(m_frame.layout, m_mousePosition); - const auto numberMetrics = ResolveHostedNumberFieldMetrics(m_theme); - const auto numberPalette = ResolveHostedNumberFieldPalette(m_theme); - const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme); + const auto numberMetrics = ResolvePropertyGridNumberFieldMetrics(); + const auto numberPalette = ResolvePropertyGridNumberFieldPalette(); + const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(); UIDrawData drawData = {}; UIDrawList& drawList = drawData.EmplaceDrawList("EditorNumberFieldBasic"); @@ -684,7 +670,7 @@ private: layout.introRect, shellPalette, shellMetrics, - "这个测试在验证什么功能", + "这个测试在验证什么功能?", "验证 Inspector 宿主中的 NumberField 交互契约和默认宿主风格。"); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), @@ -698,7 +684,7 @@ private: shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), - "3. 按 Enter 进入编辑态,直接输入字符,Enter commit,Esc cancel。", + "3. 按 Enter 进入编辑态,直接输入字符;Enter commit,Esc cancel。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( @@ -773,7 +759,7 @@ private: shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 238.0f), - "Theme: " + m_themeStatus, + "Style: fixed", shellPalette.textWeak, shellMetrics.bodyFontSize); @@ -783,7 +769,7 @@ private: shellPalette, shellMetrics, "NumberField 预览", - "这里仅预览 Inspector 宿主中的 Unity 风格 Number 字段。"); + "这里只预览 Inspector 宿主中的 Unity 风格 Number 字段。"); drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor); drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f); drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground); @@ -825,12 +811,10 @@ private: UIEditorNumberFieldSpec m_spec = {}; UIEditorNumberFieldInteractionState m_interactionState = {}; UIEditorNumberFieldInteractionFrame m_frame = {}; - Style::UITheme m_theme = {}; UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f); ActionId m_hoveredAction = ActionId::Reset; bool m_hasHoveredAction = false; std::string m_lastResult = {}; - std::string m_themeStatus = "fallback"; }; } // namespace diff --git a/tests/UI/Editor/integration/shell/object_field_basic/CMakeLists.txt b/tests/UI/Editor/integration/shell/object_field_basic/CMakeLists.txt new file mode 100644 index 00000000..a46e58e6 --- /dev/null +++ b/tests/UI/Editor/integration/shell/object_field_basic/CMakeLists.txt @@ -0,0 +1,31 @@ +add_executable(editor_ui_object_field_basic_validation WIN32 + main.cpp +) + +target_include_directories(editor_ui_object_field_basic_validation PRIVATE + ${CMAKE_SOURCE_DIR}/tests/UI/Editor/integration/shared/src + ${CMAKE_SOURCE_DIR}/new_editor/include + ${CMAKE_SOURCE_DIR}/new_editor/app + ${CMAKE_SOURCE_DIR}/engine/include +) + +target_compile_definitions(editor_ui_object_field_basic_validation PRIVATE + UNICODE + _UNICODE + XCENGINE_EDITOR_UI_TESTS_REPO_ROOT="${XCENGINE_EDITOR_UI_TESTS_REPO_ROOT_PATH}" +) + +if(MSVC) + target_compile_options(editor_ui_object_field_basic_validation PRIVATE /utf-8 /FS) + set_property(TARGET editor_ui_object_field_basic_validation PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") +endif() + +target_link_libraries(editor_ui_object_field_basic_validation PRIVATE + XCUIEditorLib + XCUIEditorHost +) + +set_target_properties(editor_ui_object_field_basic_validation PROPERTIES + OUTPUT_NAME "XCUIEditorObjectFieldBasicValidation" +) diff --git a/tests/UI/Editor/integration/shell/object_field_basic/captures/.gitkeep b/tests/UI/Editor/integration/shell/object_field_basic/captures/.gitkeep new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/tests/UI/Editor/integration/shell/object_field_basic/captures/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/UI/Editor/integration/shell/object_field_basic/main.cpp b/tests/UI/Editor/integration/shell/object_field_basic/main.cpp new file mode 100644 index 00000000..ecf7433a --- /dev/null +++ b/tests/UI/Editor/integration/shell/object_field_basic/main.cpp @@ -0,0 +1,736 @@ +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include +#include +#include +#include "EditorValidationTheme.h" +#include "Host/AutoScreenshot.h" +#include "Host/NativeRenderer.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#ifndef XCENGINE_EDITOR_UI_TESTS_REPO_ROOT +#define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "." +#endif + +namespace { + +using XCEngine::Input::KeyCode; +using XCEngine::UI::UIDrawData; +using XCEngine::UI::UIDrawList; +using XCEngine::UI::UIInputEvent; +using XCEngine::UI::UIInputEventType; +using XCEngine::UI::UIPoint; +using XCEngine::UI::UIPointerButton; +using XCEngine::UI::UIRect; +using XCEngine::UI::Editor::Host::AutoScreenshotController; +using XCEngine::UI::Editor::Host::NativeRenderer; +using XCEngine::UI::Editor::UIEditorObjectFieldInteractionFrame; +using XCEngine::UI::Editor::UIEditorObjectFieldInteractionResult; +using XCEngine::UI::Editor::UIEditorObjectFieldInteractionState; +using XCEngine::UI::Editor::UpdateUIEditorObjectFieldInteraction; +using XCEngine::UI::Editor::Widgets::AppendUIEditorObjectField; +using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldHitTarget; +using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldHitTargetKind; +using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldSpec; + +constexpr const wchar_t* kWindowClassName = L"XCUIEditorObjectFieldBasicValidation"; +constexpr const wchar_t* kWindowTitle = L"XCUI Editor | ObjectField Basic"; + +enum class ActionId : unsigned char { + Reset = 0, + Capture +}; + +struct ButtonLayout { + ActionId action = ActionId::Reset; + const char* label = ""; + UIRect rect = {}; +}; + +struct ScenarioLayout { + UIRect introRect = {}; + UIRect controlRect = {}; + UIRect stateRect = {}; + UIRect previewRect = {}; + UIRect fieldRect = {}; + std::vector buttons = {}; +}; + +std::filesystem::path ResolveRepoRootPath() { + std::string root = XCENGINE_EDITOR_UI_TESTS_REPO_ROOT; + if (root.size() >= 2u && root.front() == '"' && root.back() == '"') { + root = root.substr(1u, root.size() - 2u); + } + return std::filesystem::path(root).lexically_normal(); +} + +bool ContainsPoint(const UIRect& rect, float x, float y) { + return x >= rect.x && + x <= rect.x + rect.width && + y >= rect.y && + y <= rect.y + rect.height; +} + +std::int32_t MapObjectFieldKey(UINT keyCode) { + switch (keyCode) { + case VK_SPACE: + return static_cast(KeyCode::Space); + case VK_RETURN: + return static_cast(KeyCode::Enter); + case VK_DELETE: + return static_cast(KeyCode::Delete); + case VK_BACK: + return static_cast(KeyCode::Backspace); + default: + return static_cast(KeyCode::None); + } +} + +ScenarioLayout BuildScenarioLayout( + float width, + float height, + const XCEngine::Tests::EditorUI::EditorValidationShellMetrics& shellMetrics) { + const float margin = shellMetrics.margin; + constexpr float leftWidth = 440.0f; + const float gap = shellMetrics.gap; + + ScenarioLayout layout = {}; + layout.introRect = UIRect(margin, margin, leftWidth, 236.0f); + layout.controlRect = UIRect(margin, layout.introRect.y + layout.introRect.height + gap, leftWidth, 84.0f); + layout.stateRect = UIRect( + margin, + layout.controlRect.y + layout.controlRect.height + gap, + leftWidth, + (std::max)(232.0f, height - (layout.controlRect.y + layout.controlRect.height + gap) - margin)); + layout.previewRect = UIRect( + leftWidth + margin * 2.0f, + margin, + (std::max)(420.0f, width - leftWidth - margin * 3.0f), + height - margin * 2.0f); + layout.fieldRect = UIRect( + layout.previewRect.x + 24.0f, + layout.previewRect.y + 82.0f, + (std::min)(440.0f, layout.previewRect.width - 48.0f), + 22.0f); + + const float buttonWidth = (layout.controlRect.width - 44.0f) * 0.5f; + const float buttonY = layout.controlRect.y + 32.0f; + layout.buttons = { + { ActionId::Reset, "重置", UIRect(layout.controlRect.x + 14.0f, buttonY, buttonWidth, 36.0f) }, + { ActionId::Capture, "截图(F12)", UIRect(layout.controlRect.x + 26.0f + buttonWidth, buttonY, buttonWidth, 36.0f) } + }; + return layout; +} + +void DrawCard( + UIDrawList& drawList, + const UIRect& rect, + const XCEngine::Tests::EditorUI::EditorValidationShellPalette& shellPalette, + const XCEngine::Tests::EditorUI::EditorValidationShellMetrics& shellMetrics, + std::string_view title, + std::string_view subtitle = {}) { + drawList.AddFilledRect(rect, shellPalette.cardBackground, shellMetrics.cardRadius); + drawList.AddRectOutline(rect, shellPalette.cardBorder, 1.0f, shellMetrics.cardRadius); + drawList.AddText( + UIPoint(rect.x + 16.0f, rect.y + 14.0f), + std::string(title), + shellPalette.textPrimary, + shellMetrics.titleFontSize); + if (!subtitle.empty()) { + drawList.AddText( + UIPoint(rect.x + 16.0f, rect.y + 40.0f), + std::string(subtitle), + shellPalette.textMuted, + shellMetrics.bodyFontSize); + } +} + +void DrawButton( + UIDrawList& drawList, + const ButtonLayout& button, + const XCEngine::Tests::EditorUI::EditorValidationShellPalette& shellPalette, + const XCEngine::Tests::EditorUI::EditorValidationShellMetrics& shellMetrics, + bool hovered) { + drawList.AddFilledRect( + button.rect, + hovered ? shellPalette.buttonHoverBackground : shellPalette.buttonBackground, + shellMetrics.buttonRadius); + drawList.AddRectOutline(button.rect, shellPalette.cardBorder, 1.0f, shellMetrics.buttonRadius); + drawList.AddText( + UIPoint(button.rect.x + 16.0f, button.rect.y + 10.0f), + button.label, + shellPalette.textPrimary, + shellMetrics.bodyFontSize); +} + +std::string DescribeHitTarget(const UIEditorObjectFieldHitTarget& hitTarget) { + switch (hitTarget.kind) { + case UIEditorObjectFieldHitTargetKind::ValueBox: + return "value_box"; + case UIEditorObjectFieldHitTargetKind::ClearButton: + return "clear_button"; + case UIEditorObjectFieldHitTargetKind::PickerButton: + return "picker_button"; + case UIEditorObjectFieldHitTargetKind::Row: + return "row"; + case UIEditorObjectFieldHitTargetKind::None: + default: + return "none"; + } +} + +UIInputEvent MakePointerEvent( + UIInputEventType type, + const UIPoint& position, + UIPointerButton button = UIPointerButton::None) { + UIInputEvent event = {}; + event.type = type; + event.position = position; + event.pointerButton = button; + return event; +} + +UIInputEvent MakeFocusEvent(UIInputEventType type) { + UIInputEvent event = {}; + event.type = type; + return event; +} + +UIInputEvent MakeKeyEvent(std::int32_t keyCode) { + UIInputEvent event = {}; + event.type = UIInputEventType::KeyDown; + event.keyCode = keyCode; + return event; +} + +class ScenarioApp { +public: + int Run(HINSTANCE hInstance, int nCmdShow) { + if (!Initialize(hInstance, nCmdShow)) { + Shutdown(); + return 1; + } + + MSG message = {}; + while (message.message != WM_QUIT) { + if (PeekMessageW(&message, nullptr, 0U, 0U, PM_REMOVE)) { + TranslateMessage(&message); + DispatchMessageW(&message); + continue; + } + + RenderFrame(); + Sleep(8); + } + + Shutdown(); + return static_cast(message.wParam); + } + +private: + static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { + if (message == WM_NCCREATE) { + const auto* createStruct = reinterpret_cast(lParam); + auto* app = static_cast(createStruct->lpCreateParams); + SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(app)); + return TRUE; + } + + auto* app = reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); + if (app == nullptr) { + return DefWindowProcW(hwnd, message, wParam, lParam); + } + return app->HandleMessage(hwnd, message, wParam, lParam); + } + + bool Initialize(HINSTANCE hInstance, int nCmdShow) { + m_hInstance = hInstance; + + WNDCLASSEXW windowClass = {}; + windowClass.cbSize = sizeof(windowClass); + windowClass.style = CS_HREDRAW | CS_VREDRAW; + windowClass.lpfnWndProc = &ScenarioApp::WndProc; + windowClass.hInstance = hInstance; + windowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW); + windowClass.lpszClassName = kWindowClassName; + + m_windowClassAtom = RegisterClassExW(&windowClass); + if (m_windowClassAtom == 0) { + return false; + } + + m_hwnd = CreateWindowExW( + 0, + kWindowClassName, + kWindowTitle, + WS_OVERLAPPEDWINDOW | WS_VISIBLE, + CW_USEDEFAULT, + CW_USEDEFAULT, + 1360, + 820, + nullptr, + nullptr, + hInstance, + this); + if (m_hwnd == nullptr) { + return false; + } + + ShowWindow(m_hwnd, nCmdShow); + UpdateWindow(m_hwnd); + + if (!m_renderer.Initialize(m_hwnd)) { + return false; + } + + m_shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); + m_shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); + m_fieldMetrics = XCEngine::UI::Editor::ResolveUIEditorObjectFieldMetrics(); + m_fieldPalette = XCEngine::UI::Editor::ResolveUIEditorObjectFieldPalette(); + + m_captureRoot = + ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/object_field_basic/captures"; + m_autoScreenshot.Initialize(m_captureRoot); + ResetScenario(); + return true; + } + + void Shutdown() { + m_autoScreenshot.Shutdown(); + m_renderer.Shutdown(); + + if (m_hwnd != nullptr && IsWindow(m_hwnd)) { + DestroyWindow(m_hwnd); + } + m_hwnd = nullptr; + + if (m_windowClassAtom != 0 && m_hInstance != nullptr) { + UnregisterClassW(kWindowClassName, m_hInstance); + m_windowClassAtom = 0; + } + } + + void ResetScenario() { + m_spec = {}; + m_spec.fieldId = "target"; + m_spec.label = "Target"; + m_spec.objectName = "Main Camera"; + m_spec.objectTypeName = "Camera"; + m_spec.emptyText = "None (GameObject)"; + m_spec.hasValue = true; + m_spec.showClearButton = true; + m_spec.showPickerButton = true; + + m_interactionState = {}; + m_activateCount = 0u; + m_lastResult = "已重置到默认 ObjectField 状态"; + m_hoveredAction = ActionId::Reset; + m_pressedAction = ActionId::Reset; + m_hasHoveredAction = false; + m_hasPressedAction = false; + } + + ScenarioLayout GetLayout() const { + RECT clientRect = {}; + GetClientRect(m_hwnd, &clientRect); + const float width = static_cast((std::max)(clientRect.right - clientRect.left, 1L)); + const float height = static_cast((std::max)(clientRect.bottom - clientRect.top, 1L)); + return BuildScenarioLayout(width, height, m_shellMetrics); + } + + void UpdateHoveredAction(const ScenarioLayout& layout, float x, float y) { + for (const ButtonLayout& button : layout.buttons) { + if (ContainsPoint(button.rect, x, y)) { + m_hoveredAction = button.action; + m_hasHoveredAction = true; + return; + } + } + + m_hasHoveredAction = false; + } + + const ButtonLayout* HitTestAction(const ScenarioLayout& layout, float x, float y) const { + for (const ButtonLayout& button : layout.buttons) { + if (ContainsPoint(button.rect, x, y)) { + return &button; + } + } + return nullptr; + } + + void ApplyInteractionResult(const UIEditorObjectFieldInteractionResult& result) { + if (result.activateRequested) { + ++m_activateCount; + m_lastResult = "已触发 activateRequested"; + return; + } + + if (result.clearRequested) { + m_spec.hasValue = false; + m_spec.objectName.clear(); + m_spec.objectTypeName.clear(); + m_lastResult = "已触发 clearRequested,当前引用已清空"; + return; + } + + if (result.focusChanged) { + m_lastResult = std::string("焦点已切换: ") + (m_interactionState.fieldState.focused ? "focused" : "blurred"); + return; + } + + if (result.consumed) { + m_lastResult = "输入已消费"; + return; + } + } + + void QueueInput(UIInputEvent event) { + m_pendingInputEvents.push_back(std::move(event)); + } + + void ExecuteAction(ActionId action) { + switch (action) { + case ActionId::Reset: + ResetScenario(); + break; + case ActionId::Capture: + m_autoScreenshot.RequestCapture("manual_button"); + m_lastResult = "截图已排队,输出到 captures/latest.png"; + break; + } + } + + void HandleMouseMove(float x, float y) { + m_mousePosition = UIPoint(x, y); + const ScenarioLayout layout = GetLayout(); + UpdateHoveredAction(layout, x, y); + + TRACKMOUSEEVENT trackEvent = {}; + trackEvent.cbSize = sizeof(trackEvent); + trackEvent.dwFlags = TME_LEAVE; + trackEvent.hwndTrack = m_hwnd; + TrackMouseEvent(&trackEvent); + + QueueInput(MakePointerEvent(UIInputEventType::PointerMove, m_mousePosition)); + } + + void HandleMouseLeave() { + m_mousePosition = UIPoint(-1000.0f, -1000.0f); + m_hasHoveredAction = false; + QueueInput(MakePointerEvent(UIInputEventType::PointerLeave, m_mousePosition)); + } + + void HandleLeftButtonDown(float x, float y) { + SetFocus(m_hwnd); + m_mousePosition = UIPoint(x, y); + + const ScenarioLayout layout = GetLayout(); + UpdateHoveredAction(layout, x, y); + if (const ButtonLayout* button = HitTestAction(layout, x, y)) { + m_pressedAction = button->action; + m_hasPressedAction = true; + return; + } + + m_hasPressedAction = false; + QueueInput(MakePointerEvent( + UIInputEventType::PointerButtonDown, + m_mousePosition, + UIPointerButton::Left)); + } + + void HandleLeftButtonUp(float x, float y) { + m_mousePosition = UIPoint(x, y); + + const ScenarioLayout layout = GetLayout(); + UpdateHoveredAction(layout, x, y); + if (const ButtonLayout* button = HitTestAction(layout, x, y)) { + if (m_hasPressedAction && m_pressedAction == button->action) { + ExecuteAction(button->action); + } + m_hasPressedAction = false; + return; + } + + m_hasPressedAction = false; + QueueInput(MakePointerEvent( + UIInputEventType::PointerButtonUp, + m_mousePosition, + UIPointerButton::Left)); + } + + void HandleKeyDown(std::int32_t keyCode) { + QueueInput(MakeKeyEvent(keyCode)); + } + + void OnResize(UINT width, UINT height) { + if (width == 0u || height == 0u) { + return; + } + m_renderer.Resize(width, height); + } + + void RenderFrame() { + if (m_hwnd == nullptr) { + return; + } + + RECT clientRect = {}; + GetClientRect(m_hwnd, &clientRect); + const float width = static_cast((std::max)(clientRect.right - clientRect.left, 1L)); + const float height = static_cast((std::max)(clientRect.bottom - clientRect.top, 1L)); + + const ScenarioLayout layout = BuildScenarioLayout(width, height, m_shellMetrics); + std::vector events = std::move(m_pendingInputEvents); + m_pendingInputEvents.clear(); + + m_frame = UpdateUIEditorObjectFieldInteraction( + m_interactionState, + m_spec, + layout.fieldRect, + events, + m_fieldMetrics); + ApplyInteractionResult(m_frame.result); + + UIDrawData drawData = {}; + UIDrawList& drawList = drawData.EmplaceDrawList("EditorObjectFieldBasic"); + drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), m_shellPalette.windowBackground); + + DrawCard( + drawList, + layout.introRect, + m_shellPalette, + m_shellMetrics, + "这个测试在验证什么功能?", + "验证 Unity 风格 ObjectField 的值框、类型标签、clear / picker 按钮,以及 focus、activate、clear 契约。"); + drawList.AddText( + UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), + "1. 点击 value box 或 picker 按钮,应触发 activateRequested。", + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), + "2. 点击 clear 按钮,或 focused 时按 Delete / Backspace,应清空当前对象。", + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), + "3. focused 后按 Enter / Space,应继续走 activate 契约。", + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), + "4. 重点检查 Hover、Focused、Has Value、Activate Count、Result 是否同步。", + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f), + "5. 按 F12 或点击截图按钮,可导出当前窗口截图。", + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + + DrawCard( + drawList, + layout.controlRect, + m_shellPalette, + m_shellMetrics, + "操作", + "这里只保留当前场景需要的最小操作。"); + for (const ButtonLayout& button : layout.buttons) { + DrawButton( + drawList, + button, + m_shellPalette, + m_shellMetrics, + m_hasHoveredAction && m_hoveredAction == button.action); + } + + DrawCard( + drawList, + layout.stateRect, + m_shellPalette, + m_shellMetrics, + "状态摘要", + "重点检查 hit、focus、value、activate、result。"); + drawList.AddText( + UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 74.0f), + "Hover: " + DescribeHitTarget(m_frame.result.hitTarget), + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 96.0f), + std::string("Focused: ") + (m_interactionState.fieldState.focused ? "是" : "否"), + m_interactionState.fieldState.focused ? m_shellPalette.textSuccess : m_shellPalette.textMuted, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 118.0f), + std::string("Has Value: ") + (m_spec.hasValue ? "是" : "否"), + m_spec.hasValue ? m_shellPalette.textSuccess : m_shellPalette.textMuted, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 140.0f), + "Value: " + (m_spec.hasValue ? m_spec.objectName : m_spec.emptyText), + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 162.0f), + "Type: " + (m_spec.objectTypeName.empty() ? std::string("(none)") : m_spec.objectTypeName), + m_shellPalette.textMuted, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 184.0f), + "Activate Count: " + std::to_string(m_activateCount), + m_shellPalette.textPrimary, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 206.0f), + "Result: " + m_lastResult, + m_shellPalette.textMuted, + m_shellMetrics.bodyFontSize); + drawList.AddText( + UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 232.0f), + m_autoScreenshot.HasPendingCapture() + ? "截图排队中..." + : (m_autoScreenshot.GetLastCaptureSummary().empty() + ? std::string("F12 -> tests/UI/Editor/integration/shell/object_field_basic/captures/") + : m_autoScreenshot.GetLastCaptureSummary()), + m_shellPalette.textWeak, + m_shellMetrics.bodyFontSize); + + DrawCard( + drawList, + layout.previewRect, + m_shellPalette, + m_shellMetrics, + "ObjectField 预览", + "这里只放一个 Unity 风格 ObjectField,不混入业务 Inspector。"); + AppendUIEditorObjectField( + drawList, + layout.fieldRect, + m_spec, + m_interactionState.fieldState, + m_fieldPalette, + m_fieldMetrics); + + const bool framePresented = m_renderer.Render(drawData); + m_autoScreenshot.CaptureIfRequested( + m_renderer, + drawData, + static_cast(width), + static_cast(height), + framePresented); + } + + LRESULT HandleMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { + switch (message) { + case WM_CLOSE: + DestroyWindow(hwnd); + return 0; + + case WM_DESTROY: + m_hwnd = nullptr; + PostQuitMessage(0); + return 0; + + case WM_SIZE: + if (wParam != SIZE_MINIMIZED) { + OnResize(static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))); + } + return 0; + + case WM_ERASEBKGND: + return 1; + + case WM_MOUSEMOVE: + HandleMouseMove( + static_cast(GET_X_LPARAM(lParam)), + static_cast(GET_Y_LPARAM(lParam))); + return 0; + + case WM_MOUSELEAVE: + HandleMouseLeave(); + return 0; + + case WM_LBUTTONDOWN: + HandleLeftButtonDown( + static_cast(GET_X_LPARAM(lParam)), + static_cast(GET_Y_LPARAM(lParam))); + return 0; + + case WM_LBUTTONUP: + HandleLeftButtonUp( + static_cast(GET_X_LPARAM(lParam)), + static_cast(GET_Y_LPARAM(lParam))); + return 0; + + case WM_SETFOCUS: + QueueInput(MakeFocusEvent(UIInputEventType::FocusGained)); + return 0; + + case WM_KILLFOCUS: + QueueInput(MakeFocusEvent(UIInputEventType::FocusLost)); + return 0; + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + if (wParam == VK_F12) { + m_autoScreenshot.RequestCapture("manual_f12"); + m_lastResult = "截图已排队,输出到 captures/latest.png"; + return 0; + } + if (const std::int32_t keyCode = MapObjectFieldKey(static_cast(wParam)); + keyCode != static_cast(KeyCode::None)) { + HandleKeyDown(keyCode); + return 0; + } + break; + + default: + break; + } + + return DefWindowProcW(hwnd, message, wParam, lParam); + } + + HWND m_hwnd = nullptr; + HINSTANCE m_hInstance = nullptr; + ATOM m_windowClassAtom = 0; + NativeRenderer m_renderer = {}; + AutoScreenshotController m_autoScreenshot = {}; + std::filesystem::path m_captureRoot = {}; + XCEngine::Tests::EditorUI::EditorValidationShellPalette m_shellPalette = {}; + XCEngine::Tests::EditorUI::EditorValidationShellMetrics m_shellMetrics = {}; + XCEngine::UI::Editor::Widgets::UIEditorObjectFieldPalette m_fieldPalette = {}; + XCEngine::UI::Editor::Widgets::UIEditorObjectFieldMetrics m_fieldMetrics = {}; + UIEditorObjectFieldSpec m_spec = {}; + UIEditorObjectFieldInteractionState m_interactionState = {}; + UIEditorObjectFieldInteractionFrame m_frame = {}; + std::vector m_pendingInputEvents = {}; + UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f); + ActionId m_hoveredAction = ActionId::Reset; + ActionId m_pressedAction = ActionId::Reset; + bool m_hasHoveredAction = false; + bool m_hasPressedAction = false; + std::string m_lastResult = {}; + std::size_t m_activateCount = 0u; +}; + +} // namespace + +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) { + return ScenarioApp().Run(hInstance, nCmdShow); +} diff --git a/tests/UI/Editor/integration/shell/panel_content_host_basic/main.cpp b/tests/UI/Editor/integration/shell/panel_content_host_basic/main.cpp index 1b9cc069..b96c8067 100644 --- a/tests/UI/Editor/integration/shell/panel_content_host_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/panel_content_host_basic/main.cpp @@ -2,9 +2,9 @@ #define NOMINMAX #endif -#include -#include -#include +#include +#include +#include #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -376,7 +376,7 @@ private: m_composeState = {}; m_lastStatus = "Ready"; m_lastMessage = - "当前先看 doc-a 与 inspector:两者都会 mount;切到 Console 后,tab body 会回退成 placeholder。"; + "默认状态下 doc-a 和 inspector 会挂到外部内容宿主,Console 仍然走占位内容路径。"; UpdateComposeFrame(); } @@ -406,8 +406,8 @@ private: { ActionId::ActivateInspector, "Activate Inspector", UIRect(buttonLeft + buttonWidth + 16.0f, buttonTop + buttonHeight + rowGap, buttonWidth, buttonHeight), false }, { ActionId::CloseInspector, "Close Inspector", UIRect(buttonLeft, buttonTop + (buttonHeight + rowGap) * 2.0f, buttonWidth, buttonHeight), false }, { ActionId::OpenInspector, "Open Inspector", UIRect(buttonLeft + buttonWidth + 16.0f, buttonTop + (buttonHeight + rowGap) * 2.0f, buttonWidth, buttonHeight), false }, - { ActionId::Reset, "Reset", UIRect(buttonLeft, buttonTop + (buttonHeight + rowGap) * 3.0f, buttonWidth, buttonHeight), false }, - { ActionId::Capture, "Capture(F12)", UIRect(buttonLeft + buttonWidth + 16.0f, buttonTop + (buttonHeight + rowGap) * 3.0f, buttonWidth, buttonHeight), false } + { ActionId::Reset, "重置", UIRect(buttonLeft, buttonTop + (buttonHeight + rowGap) * 3.0f, buttonWidth, buttonHeight), false }, + { ActionId::Capture, "截图(F12)", UIRect(buttonLeft + buttonWidth + 16.0f, buttonTop + (buttonHeight + rowGap) * 3.0f, buttonWidth, buttonHeight), false } }; } @@ -443,7 +443,7 @@ private: m_autoScreenshot.RequestCapture("manual_button"); m_lastStatus = "Ready"; m_lastMessage = - "截图已排队,输出到 tests/UI/Editor/integration/shell/panel_content_host_basic/captures/。"; + "已请求截图,输出到 tests/UI/Editor/integration/shell/panel_content_host_basic/captures/。"; return; } @@ -507,19 +507,19 @@ private: UIDrawList& drawList = drawData.EmplaceDrawList("PanelContentHostBasic"); drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg); - DrawCard(drawList, m_introRect, "这个测试验证什么功能", "只验证 Editor panel content host contract,不做业务逻辑。"); - drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 72.0f), "1. 验证 HostedContent panel 会正式接管 DockHost body,而不是继续画 placeholder。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 94.0f), "2. 验证 tab 切换时,旧 body unmount,新 body mount。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 116.0f), "3. 验证切到 Console 这种 placeholder panel 后,external host 会退出。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 138.0f), "4. 验证 standalone HostedContent close/open 时,会发生 unmount / remount。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 164.0f), "建议操作:Doc A -> Doc B -> Console -> Open/Close Inspector,观察 Mounted Panels 和 Events。", kTextWeak, 11.0f); + DrawCard(drawList, m_introRect, "这个测试验证什么功能?", "验证面板内容宿主在 DockHost 中的挂载、卸载和占位回退契约,不做业务逻辑。"); + drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 72.0f), "1. HostedContent panel 应挂到 DockHost 外部 body,不再走占位内容。", kTextPrimary, 12.0f); + drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 94.0f), "2. 切换 tab 时,旧 body 应卸载,新 body 应挂载。", kTextPrimary, 12.0f); + drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 116.0f), "3. Console 仍然是占位面板,不应该进入 external host。", kTextPrimary, 12.0f); + drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 138.0f), "4. 关闭再打开 inspector,应看到卸载和重新挂载事件。", kTextPrimary, 12.0f); + drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 164.0f), "建议顺序:Doc A -> Doc B -> Console -> Close/Open Inspector。", kTextWeak, 11.0f); - DrawCard(drawList, m_controlsRect, "操作", "只保留内容承载 contract 必要按钮。"); + DrawCard(drawList, m_controlsRect, "操作", "这里只保留当前契约验证需要的按钮。"); for (const ButtonState& button : m_buttons) { DrawButton(drawList, button); } - DrawCard(drawList, m_stateRect, "状态", "重点检查 mounted panel 集合和本帧 mount events。"); + DrawCard(drawList, m_stateRect, "状态", "重点检查已挂载面板集合和本帧挂载事件。"); float stateY = m_stateRect.y + 66.0f; auto addStateLine = [&](std::string label, std::string value, const UIColor& color, float fontSize = 12.0f) { drawList.AddText(UIPoint(m_stateRect.x + 18.0f, stateY), std::move(label) + ": " + std::move(value), color, fontSize); @@ -539,16 +539,16 @@ private: addStateLine("doc-b", DescribeMountedState(m_composeFrame, "doc-b"), kTextWeak, 11.0f); addStateLine("inspector", DescribeMountedState(m_composeFrame, "inspector"), kTextWeak, 11.0f); addStateLine( - "Screenshot", + "截图", m_autoScreenshot.HasPendingCapture() ? "截图排队中..." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 或 Capture -> captures/") + ? std::string("F12 或点按钮 -> captures/") : m_autoScreenshot.GetLastCaptureSummary()), kTextWeak, 11.0f); - DrawCard(drawList, m_previewRect, "Preview", "DockHost 画壳;蓝色内容块是 external content host 实际挂进去的 body。"); + DrawCard(drawList, m_previewRect, "预览", "DockHost 只画外壳;蓝色内容块表示外部内容宿主实际挂入的 body。"); AppendUIEditorWorkspaceCompose(drawList, m_composeFrame); for (const UIEditorPanelContentHostPanelState& panelState : m_composeFrame.contentHostFrame.panelStates) { if (!panelState.mounted || diff --git a/tests/UI/Editor/integration/shell/panel_frame_basic/main.cpp b/tests/UI/Editor/integration/shell/panel_frame_basic/main.cpp index 693a4e18..22a45c44 100644 --- a/tests/UI/Editor/integration/shell/panel_frame_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/panel_frame_basic/main.cpp @@ -2,7 +2,7 @@ #define NOMINMAX #endif -#include +#include #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -375,7 +375,7 @@ private: { ActionId::ToggleFooter, "Footer", UIRect(left, top, width, buttonHeight), m_state.showFooter }, { ActionId::TogglePin, "Pin Button", UIRect(left, top + (buttonHeight + gap), width, buttonHeight), m_state.pinnable }, { ActionId::ToggleClose, "Close Button", UIRect(left, top + (buttonHeight + gap) * 2.0f, width, buttonHeight), m_state.closable }, - { ActionId::Reset, "Reset", UIRect(left, top + (buttonHeight + gap) * 3.0f, width, buttonHeight), false } + { ActionId::Reset, "重置", UIRect(left, top + (buttonHeight + gap) * 3.0f, width, buttonHeight), false } }; } @@ -405,25 +405,30 @@ private: DrawCard( drawList, introRect, - "测试功能:PanelFrame 基础壳层", - "重点检查:Header / Body / Footer 布局,Active / Focus 边框,Pin / Close 命中。"); + "这个测试验证什么功能?", + "验证 PanelFrame 的 header/body/footer 分区,以及 active、focus、pin、close 命中。"); drawList.AddText( UIPoint(introRect.x + 16.0f, introRect.y + 66.0f), - "操作:移动鼠标观察 hover;点 Header 切 Active;点 Body/Footer 取 Focus;点 Pin/Close 看 Result;按 F12 截图。", + "1. hover 各区域,检查命中 part 是否正确。", kTextMuted, 12.0f); drawList.AddText( UIPoint(introRect.x + 16.0f, introRect.y + 90.0f), - "这个场景只验证 Editor 基础层 PanelFrame,不包含任何业务面板。", + "2. 点击 Header、Body、Footer、Pin、Close,检查状态变化。", + kTextMuted, + 12.0f); + drawList.AddText( + UIPoint(introRect.x + 16.0f, introRect.y + 114.0f), + "3. 右侧只放一个 PanelFrame,不接业务面板。", kTextWeak, 12.0f); - DrawCard(drawList, controlsRect, "开关", "这里只保留基础状态开关,避免试验面板过杂。"); + DrawCard(drawList, controlsRect, "操作", "只保留影响 PanelFrame contract 的开关。"); for (const ButtonState& button : m_buttons) { DrawButton(drawList, button); } - DrawCard(drawList, stateRect, "状态", "右侧面板的命中结果和状态变化会同步显示在这里。"); + DrawCard(drawList, stateRect, "状态摘要", "重点看 hover、active、focus、pinned 和结果。"); drawList.AddText( UIPoint(stateRect.x + 16.0f, stateRect.y + 66.0f), "Hover: " + DescribePart(m_hoveredPart), diff --git a/tests/UI/Editor/integration/shell/property_grid_basic/main.cpp b/tests/UI/Editor/integration/shell/property_grid_basic/main.cpp index 80efd6a9..4da78d5a 100644 --- a/tests/UI/Editor/integration/shell/property_grid_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/property_grid_basic/main.cpp @@ -1,10 +1,10 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include -#include -#include +#include +#include +#include #include "EditorValidationTheme.h" #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -57,7 +57,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorPropertyGridFieldKind; using XCEngine::UI::Editor::Widgets::UIEditorPropertyGridHitTarget; using XCEngine::UI::Editor::Widgets::UIEditorPropertyGridHitTargetKind; using XCEngine::UI::Editor::Widgets::UIEditorPropertyGridSection; -namespace Style = XCEngine::UI::Style; constexpr const wchar_t* kWindowClassName = L"XCUIEditorPropertyGridBasicValidation"; constexpr const wchar_t* kWindowTitle = L"XCUI Editor | PropertyGrid Basic"; @@ -91,11 +90,6 @@ std::filesystem::path ResolveRepoRootPath() { return std::filesystem::path(root).lexically_normal(); } -std::filesystem::path ResolveValidationThemePath() { - return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme") - .lexically_normal(); -} - bool ContainsPoint(const UIRect& rect, float x, float y) { return x >= rect.x && x <= rect.x + rect.width && @@ -543,15 +537,6 @@ private: m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/property_grid_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); - const auto themeLoad = - XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath()); - if (themeLoad.succeeded) { - m_theme = themeLoad.theme; - m_themeStatus = "loaded"; - } else { - m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error; - } - ResetScenario(); return true; } @@ -579,7 +564,7 @@ private: return BuildScenarioLayout( width, height, - XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme)); + XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics()); } void ResetScenario() { @@ -606,7 +591,7 @@ private: } const ScenarioLayout layout = GetLayout(); - const auto metrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(m_theme); + const auto metrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(); m_gridFrame = UpdateUIEditorPropertyGridInteraction( m_interactionState, @@ -741,7 +726,7 @@ private: UIEditorPropertyGridInteractionResult PumpGridEvents(std::vector events) { const ScenarioLayout layout = GetLayout(); - const auto metrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(m_theme); + const auto metrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(); m_gridFrame = UpdateUIEditorPropertyGridInteraction( m_interactionState, @@ -855,17 +840,17 @@ private: GetClientRect(m_hwnd, &clientRect); const float width = static_cast((std::max)(1L, clientRect.right - clientRect.left)); const float height = static_cast((std::max)(1L, clientRect.bottom - clientRect.top)); - const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme); - const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme); + const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); + const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics); RefreshGridFrame(); const UIEditorPropertyGridHitTarget currentHit = HitTestUIEditorPropertyGrid(m_gridFrame.layout, m_mousePosition); - const auto propertyMetrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(m_theme); - const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme); - const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics(m_theme); - const auto popupPalette = XCEngine::UI::Editor::ResolveUIEditorMenuPopupPalette(m_theme); + const auto propertyMetrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(); + const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(); + const auto popupMetrics = XCEngine::UI::Editor::ResolveUIEditorMenuPopupMetrics(); + const auto popupPalette = XCEngine::UI::Editor::ResolveUIEditorMenuPopupPalette(); UIDrawData drawData = {}; UIDrawList& drawList = drawData.EmplaceDrawList("EditorPropertyGridBasic"); @@ -987,7 +972,7 @@ private: shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 310.0f), - "Theme: " + m_themeStatus, + "Style: fixed", shellPalette.textWeak, shellMetrics.bodyFontSize); @@ -1037,14 +1022,11 @@ private: UIExpansionModel m_expansionModel = {}; UIPropertyEditModel m_propertyEditModel = {}; UIEditorPropertyGridInteractionState m_interactionState = {}; - UIEditorPropertyGridInteractionFrame m_gridFrame = {}; - Style::UITheme m_theme = {}; - UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f); + UIEditorPropertyGridInteractionFrame m_gridFrame = {}; UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f); ActionId m_hoveredAction = ActionId::Reset; bool m_hasHoveredAction = false; std::string m_lastResult = {}; std::string m_lastCommit = {}; - std::string m_themeStatus = "fallback"; }; } // namespace diff --git a/tests/UI/Editor/integration/shell/scroll_view_basic/main.cpp b/tests/UI/Editor/integration/shell/scroll_view_basic/main.cpp index e682076c..74b544ae 100644 --- a/tests/UI/Editor/integration/shell/scroll_view_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/scroll_view_basic/main.cpp @@ -2,8 +2,8 @@ #define NOMINMAX #endif -#include -#include +#include +#include #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -194,7 +194,7 @@ std::string DescribeHitTarget(const UIEditorScrollViewHitTarget& hitTarget) { return "scrollbar-thumb"; case UIEditorScrollViewHitTargetKind::None: default: - return "无"; + return "none"; } } @@ -423,7 +423,7 @@ private: m_mousePosition = UIPoint(-1000.0f, -1000.0f); m_hoveredAction = ActionId::Reset; m_hasHoveredAction = false; - m_lastResult = "已重置到 " + std::to_string(m_logLines.size()) + " 行默认滚动位置"; + m_lastResult = "已重置到 " + std::to_string(m_logLines.size()) + " 行默认滚动内容"; RefreshScrollFrame(); } @@ -478,7 +478,7 @@ private: const UIEditorScrollViewInteractionResult result = PumpScrollEvents({ MakeWheelEvent(m_mousePosition, wheelDelta) }); if (result.offsetChanged) { - m_lastResult = "滚轮滚动: offset 已更新"; + m_lastResult = "滚轮滚动:offset 已更新"; } InvalidateRect(m_hwnd, nullptr, FALSE); } @@ -568,23 +568,23 @@ private: } if (result.offsetChanged) { - m_lastResult = "滚动位置变化"; + m_lastResult = "滚动位置已变化"; return; } if (result.focusChanged) { m_lastResult = m_interactionState.scrollViewState.focused - ? "ScrollView 获得 focus" + ? "ScrollView 已获得 focus" : "ScrollView focus 已清除"; return; } if (insideScrollView) { - m_lastResult = "点击内容区域: 只验证 focus / hover / scrollbar"; + m_lastResult = "点击内容区:只验证 focus / hover / scrollbar"; return; } - m_lastResult = "等待交互"; + m_lastResult = "无变化"; } void ExecuteAction(ActionId action) { @@ -622,31 +622,31 @@ private: DrawCard( drawList, layout.introRect, - "这个测试在验证什么功能", - "只验证 Editor ScrollView 基础控件,不涉及任何业务面板。"); + "这个测试验证什么功能?", + "验证滚动视图的滚轮滚动、thumb 拖拽、focus 切换和 offset clamp。"); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), - "1. 把鼠标放到右侧日志区内滚动滚轮:内容应上下移动,offset 应变化。", + "1. 在内容区滚轮滚动,检查 offset 是否连续更新。", kTextPrimary, 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), - "2. 继续滚到边界后 offset 要被 clamp,不能越界。", + "2. 滚到底部后继续滚动,offset 必须被 clamp。", kTextPrimary, 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), - "3. 拖拽 scrollbar thumb,内容位置应同步变化。", + "3. 拖拽 scrollbar thumb,检查 offset 与 thumb 位置是否同步。", kTextPrimary, 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), - "4. 点击内容区只改变 focus;点击外部空白后 focus 应清除。", + "4. 点击内容区只更新 focus / hover;点击外部应能清掉 focus。", kTextPrimary, 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f), - "5. 按 F12 手动截图;设置 XCUI_AUTO_CAPTURE_ON_STARTUP=1 可自动截图。", + "5. 按 F12 或设置 XCUI_AUTO_CAPTURE_ON_STARTUP=1 触发截图。", kTextPrimary, 12.0f); @@ -666,7 +666,7 @@ private: 12.0f); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 94.0f), - std::string("Focused: ") + (m_interactionState.scrollViewState.focused ? "开" : "关"), + std::string("Focused: ") + (m_interactionState.scrollViewState.focused ? "是" : "否"), kTextPrimary, 12.0f); drawList.AddText( diff --git a/tests/UI/Editor/integration/shell/status_bar_basic/main.cpp b/tests/UI/Editor/integration/shell/status_bar_basic/main.cpp index e701dce5..7bb8a7dd 100644 --- a/tests/UI/Editor/integration/shell/status_bar_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/status_bar_basic/main.cpp @@ -2,7 +2,7 @@ #define NOMINMAX #endif -#include +#include #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -313,10 +313,10 @@ private: if (m_hoverTarget.kind == UIEditorStatusBarHitTargetKind::Segment) { m_state.activeIndex = m_hoverTarget.index; m_state.focused = true; - m_lastResult = "激活 segment: " + m_segments[m_hoverTarget.index].segmentId; + m_lastResult = "命中 segment: " + m_segments[m_hoverTarget.index].segmentId; } else if (m_hoverTarget.kind == UIEditorStatusBarHitTargetKind::Background) { m_state.activeIndex = UIEditorStatusBarInvalidIndex; - m_lastResult = "点击 status bar background"; + m_lastResult = "命中 status bar background"; } else { m_lastResult = "命中 " + DescribeHitTarget(m_hoverTarget); } @@ -330,22 +330,22 @@ private: m_segments[1].tone == UIEditorStatusBarTextTone::Accent ? UIEditorStatusBarTextTone::Primary : UIEditorStatusBarTextTone::Accent; - m_lastResult = "切换 Selection 文本强调"; + m_lastResult = "Selection 强调已切换"; break; case ActionId::ToggleSeparator: m_segments[0].showSeparator = !m_segments[0].showSeparator; - m_lastResult = m_segments[0].showSeparator ? "开启 Leading separator" : "关闭 Leading separator"; + m_lastResult = m_segments[0].showSeparator ? "Leading separator 已开启" : "Leading separator 已关闭"; break; case ActionId::MoveToTrailing: m_segments[1].slot = m_segments[1].slot == UIEditorStatusBarSlot::Leading ? UIEditorStatusBarSlot::Trailing : UIEditorStatusBarSlot::Leading; - m_lastResult = "切换 Selection slot -> " + DescribeSlot(m_segments[1].slot); + m_lastResult = "Selection slot -> " + DescribeSlot(m_segments[1].slot); break; case ActionId::Reset: ResetState(); - m_lastResult = "状态重置"; + m_lastResult = "状态已重置"; break; case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); @@ -368,7 +368,7 @@ private: m_state.focused = true; m_state.activeIndex = 1u; m_hoverTarget = {}; - m_lastResult = "Ready"; + m_lastResult = "就绪"; } void UpdateHover() { @@ -383,11 +383,11 @@ private: const float buttonHeight = 34.0f; const float gap = 10.0f; m_buttons = { - { ActionId::ToggleAccent, "强调文本", UIRect(left, top, width, buttonHeight), m_segments[1].tone == UIEditorStatusBarTextTone::Accent }, - { ActionId::ToggleSeparator, "Leading 分隔线", UIRect(left, top + (buttonHeight + gap), width, buttonHeight), m_segments[0].showSeparator }, + { ActionId::ToggleAccent, "切换强调", UIRect(left, top, width, buttonHeight), m_segments[1].tone == UIEditorStatusBarTextTone::Accent }, + { ActionId::ToggleSeparator, "Leading separator", UIRect(left, top + (buttonHeight + gap), width, buttonHeight), m_segments[0].showSeparator }, { ActionId::MoveToTrailing, "切换 Selection Slot", UIRect(left, top + (buttonHeight + gap) * 2.0f, width, buttonHeight), m_segments[1].slot == UIEditorStatusBarSlot::Trailing }, - { ActionId::Reset, "Reset", UIRect(left, top + (buttonHeight + gap) * 3.0f, width, buttonHeight), false }, - { ActionId::Capture, "截图", UIRect(left, top + (buttonHeight + gap) * 4.0f, width, buttonHeight), false } + { ActionId::Reset, "重置", UIRect(left, top + (buttonHeight + gap) * 3.0f, width, buttonHeight), false }, + { ActionId::Capture, "截图(F12)", UIRect(left, top + (buttonHeight + gap) * 4.0f, width, buttonHeight), false } }; } @@ -429,25 +429,30 @@ private: DrawCard( drawList, introRect, - "测试功能:StatusBar 基础壳层", - "重点检查:Leading / Trailing slot 对齐,文本强调,separator 开关,hover / active 命中。"); + "这个测试验证什么功能?", + "验证 StatusBar 的 leading/trailing 布局、separator、强调文本,以及 hover/active 命中。"); drawList.AddText( UIPoint(introRect.x + 16.0f, introRect.y + 66.0f), - "操作:hover 观察 segment 高亮;点击 segment 切 active;切换左侧按钮检查 slot / separator / emphasis;按 F12 或点“截图”。", + "1. hover 不同 segment / separator,检查命中是否正确。", kTextMuted, 12.0f); drawList.AddText( UIPoint(introRect.x + 16.0f, introRect.y + 90.0f), - "这个场景只验证 Editor 基础层 StatusBar,不混入任何业务面板。", + "2. 点击 segment,检查 active 是否切到对应项。", + kTextMuted, + 12.0f); + drawList.AddText( + UIPoint(introRect.x + 16.0f, introRect.y + 114.0f), + "3. 切换强调、separator 和 Selection slot,检查布局是否稳定。", kTextWeak, 12.0f); - DrawCard(drawList, controlsRect, "开关", "只保留和当前 StatusBar contract 直接相关的操作。"); + DrawCard(drawList, controlsRect, "操作", "只保留与 StatusBar contract 直接相关的开关。"); for (const ButtonState& button : m_buttons) { DrawButton(drawList, button); } - DrawCard(drawList, stateRect, "状态", "命中、active、slot 和结果统一回显在这里。"); + DrawCard(drawList, stateRect, "状态摘要", "重点看 hover、active、Selection slot、separator 和结果。"); drawList.AddText( UIPoint(stateRect.x + 16.0f, stateRect.y + 66.0f), "Hover: " + DescribeHitTarget(m_hoverTarget), @@ -489,12 +494,12 @@ private: drawList.AddRectOutline(viewportRect, kCardBorder, 1.0f, 10.0f); drawList.AddText( UIPoint(viewportRect.x + 18.0f, viewportRect.y + 18.0f), - "Preview Host", + "预览宿主", kTextPrimary, 18.0f); drawList.AddText( UIPoint(viewportRect.x + 18.0f, viewportRect.y + 48.0f), - "这里只保留一个空白宿主区域,专门看底部 StatusBar 的对齐和交互。", + "这里只放一个宿主区域,用来观察底部 StatusBar 的布局和状态变化。", kTextMuted, 12.0f); diff --git a/tests/UI/Editor/integration/shell/tab_strip_basic/main.cpp b/tests/UI/Editor/integration/shell/tab_strip_basic/main.cpp index 35171200..55a00e9f 100644 --- a/tests/UI/Editor/integration/shell/tab_strip_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/tab_strip_basic/main.cpp @@ -2,10 +2,10 @@ #define NOMINMAX #endif -#include -#include -#include -#include +#include +#include +#include +#include #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -79,7 +79,6 @@ constexpr UIColor kTextPrimary(0.94f, 0.94f, 0.94f, 1.0f); constexpr UIColor kTextMuted(0.73f, 0.73f, 0.73f, 1.0f); constexpr UIColor kTextWeak(0.58f, 0.58f, 0.58f, 1.0f); constexpr UIColor kSuccess(0.62f, 0.74f, 0.62f, 1.0f); -constexpr UIColor kWarning(0.78f, 0.70f, 0.46f, 1.0f); constexpr UIColor kDanger(0.82f, 0.48f, 0.48f, 1.0f); constexpr UIColor kButtonBg(0.25f, 0.25f, 0.25f, 1.0f); constexpr UIColor kButtonHoveredBg(0.31f, 0.31f, 0.31f, 1.0f); @@ -402,7 +401,7 @@ private: m_tabStripFrame = {}; m_tabItems.clear(); m_mousePosition = UIPoint(-1000.0f, -1000.0f); - m_lastResult = "等待操作"; + m_lastResult = "已重置到默认标签状态"; } UIRect GetTabStripRect() const { @@ -524,7 +523,7 @@ private: if (result.hitTarget.kind == UIEditorTabStripHitTargetKind::HeaderBackground || result.hitTarget.kind == UIEditorTabStripHitTargetKind::Content) { - m_lastResult = "TabStrip 获得 focus"; + m_lastResult = "命中 TabStrip 背景,保留 focus"; return; } @@ -577,7 +576,6 @@ private: m_tabItems.push_back(std::move(item)); } } - } void RenderFrame() { @@ -610,30 +608,30 @@ private: DrawCard( drawList, introRect, - "测试功能:Editor TabStrip 基础层", - "只验证 TabStrip header / hit-test / selection / close / keyboard,不包含任何业务面板。"); + "这个测试验证什么功能?", + "验证 TabStrip 的 header 命中、选中切换、关闭请求和键盘导航,不接业务面板。"); drawList.AddText( UIPoint(introRect.x + 16.0f, introRect.y + 68.0f), - "重点检查:tab 布局是否整齐;selected / hover / focus 是否正确;Close 后 active panel 是否回退正确。", + "1. 点击 tab,检查 selected / active panel 是否同步。", kTextPrimary, 12.0f); drawList.AddText( UIPoint(introRect.x + 16.0f, introRect.y + 92.0f), - "操作:点击 tab 切换;点击 X 关闭;Document C 没有 X;点击内容区后按 Left / Right / Home / End;Reset 恢复;F12 截图。", + "2. 点击 X,只验证关闭请求;Document C 没有关闭按钮。", kTextMuted, 12.0f); drawList.AddText( UIPoint(introRect.x + 16.0f, introRect.y + 116.0f), - "预期:Close 命中优先于 tab;键盘导航只在 focused 时生效;Document C 始终保留。", + "3. Left / Right / Home / End 验证键盘导航;按重置恢复初始状态。", kTextWeak, 12.0f); DrawCard( drawList, stateRect, - "状态回显", - "这里直接回显 hover / focus / active panel / result,方便人工检查。"); - DrawButton(drawList, m_resetButtonRect, "Reset", m_resetButtonHovered); + "状态摘要", + "持续显示 hover、focus、active panel、tabs、result 和校验结果。"); + DrawButton(drawList, m_resetButtonRect, "重置", m_resetButtonHovered); const std::size_t selectedIndex = ResolveUIEditorTabStripSelectedIndex(m_tabItems, m_controller.GetWorkspace().activePanelId); @@ -676,7 +674,7 @@ private: 12.0f); drawList.AddText( UIPoint(stateRect.x + 16.0f, stateRect.y + 276.0f), - validation.IsValid() ? "Validation: OK" : "Validation: " + validation.message, + validation.IsValid() ? "校验: OK" : "校验: " + validation.message, validation.IsValid() ? kSuccess : kDanger, 12.0f); @@ -696,7 +694,7 @@ private: drawList, previewCardRect, "预览区", - "这里只放一个 TabStrip 和一个 content placeholder,避免试验面板过杂。"); + "这里只放一个 TabStrip 和一个 content placeholder,用来观察 header 与 content frame。"); AppendUIEditorTabStripBackground(drawList, m_layout, m_tabStripState); AppendUIEditorTabStripForeground(drawList, m_layout, m_tabItems, m_tabStripState); @@ -716,12 +714,12 @@ private: 13.0f); drawList.AddText( UIPoint(m_layout.contentRect.x + 20.0f, m_layout.contentRect.y + 76.0f), - "这个区域只用于验证 TabStrip content frame 与 focus,不承载业务内容。", + "这里只验证 TabStrip 的 content frame 与 focus 同步,不接业务内容。", kTextWeak, 12.0f); drawList.AddText( UIPoint(m_layout.contentRect.x + 20.0f, m_layout.contentRect.y + 100.0f), - "检查点:关闭 Document B 后,应自动回退到相邻 tab;Document C 无法关闭。", + "可点击 Document B 切换,或点击 Document C 验证不可关闭 tab。", kTextWeak, 12.0f); diff --git a/tests/UI/Editor/integration/shell/text_field_basic/main.cpp b/tests/UI/Editor/integration/shell/text_field_basic/main.cpp index 7d9707de..3ab6df2d 100644 --- a/tests/UI/Editor/integration/shell/text_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/text_field_basic/main.cpp @@ -1,10 +1,11 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include -#include -#include +#include +#include +#include +#include #include "EditorValidationTheme.h" #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -25,6 +26,8 @@ #define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "." #endif +// 场景说明:验证固定样式下 TextField 的基础编辑、提交、取消与焦点切换。 + namespace { using XCEngine::Input::KeyCode; @@ -46,7 +49,6 @@ using XCEngine::UI::Editor::Widgets::HitTestUIEditorTextField; using XCEngine::UI::Editor::Widgets::UIEditorTextFieldHitTarget; using XCEngine::UI::Editor::Widgets::UIEditorTextFieldHitTargetKind; using XCEngine::UI::Editor::Widgets::UIEditorTextFieldSpec; -namespace Style = XCEngine::UI::Style; constexpr const wchar_t* kWindowClassName = L"XCUIEditorTextFieldBasicValidation"; constexpr const wchar_t* kWindowTitle = L"XCUI Editor | TextField Basic"; @@ -83,10 +85,6 @@ std::filesystem::path ResolveRepoRootPath() { return std::filesystem::path(root).lexically_normal(); } -std::filesystem::path ResolveValidationThemePath() { - return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme") - .lexically_normal(); -} bool ContainsPoint(const UIRect& rect, float x, float y) { return x >= rect.x && @@ -157,18 +155,16 @@ ScenarioLayout BuildScenarioLayout( return layout; } -XCEngine::UI::Editor::Widgets::UIEditorTextFieldMetrics ResolveHostedTextFieldMetrics( - const Style::UITheme& theme) { - const auto propertyMetrics = XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(theme); - const auto textMetrics = XCEngine::UI::Editor::ResolveUIEditorTextFieldMetrics(theme); - return XCEngine::UI::Editor::BuildUIEditorHostedTextFieldMetrics(propertyMetrics, textMetrics); +XCEngine::UI::Editor::Widgets::UIEditorTextFieldMetrics ResolvePropertyGridTextFieldMetrics() { + return XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldMetrics( + XCEngine::UI::Editor::ResolveUIEditorPropertyGridMetrics(), + XCEngine::UI::Editor::ResolveUIEditorTextFieldMetrics()); } -XCEngine::UI::Editor::Widgets::UIEditorTextFieldPalette ResolveHostedTextFieldPalette( - const Style::UITheme& theme) { - const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(theme); - const auto textPalette = XCEngine::UI::Editor::ResolveUIEditorTextFieldPalette(theme); - return XCEngine::UI::Editor::BuildUIEditorHostedTextFieldPalette(propertyPalette, textPalette); +XCEngine::UI::Editor::Widgets::UIEditorTextFieldPalette ResolvePropertyGridTextFieldPalette() { + return XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldPalette( + XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(), + XCEngine::UI::Editor::ResolveUIEditorTextFieldPalette()); } void DrawCard( @@ -427,14 +423,6 @@ private: m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/text_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); - const auto themeLoad = - XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath()); - if (themeLoad.succeeded) { - m_theme = themeLoad.theme; - m_themeStatus = "loaded"; - } else { - m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error; - } ResetScenario(); return true; @@ -463,7 +451,7 @@ private: return BuildScenarioLayout( width, height, - XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme)); + XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics()); } void ResetScenario() { @@ -487,7 +475,7 @@ private: } const ScenarioLayout layout = GetLayout(); - const auto metrics = ResolveHostedTextFieldMetrics(m_theme); + const auto metrics = ResolvePropertyGridTextFieldMetrics(); m_frame = UpdateUIEditorTextFieldInteraction( m_interactionState, m_spec, @@ -605,7 +593,7 @@ private: UIEditorTextFieldInteractionResult PumpEvents(std::vector events) { const ScenarioLayout layout = GetLayout(); - const auto metrics = ResolveHostedTextFieldMetrics(m_theme); + const auto metrics = ResolvePropertyGridTextFieldMetrics(); m_frame = UpdateUIEditorTextFieldInteraction( m_interactionState, m_spec, @@ -661,16 +649,16 @@ private: GetClientRect(m_hwnd, &clientRect); const float width = static_cast((std::max)(1L, clientRect.right - clientRect.left)); const float height = static_cast((std::max)(1L, clientRect.bottom - clientRect.top)); - const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme); - const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme); + const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); + const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics); RefreshFrame(); const UIEditorTextFieldHitTarget currentHit = HitTestUIEditorTextField(m_frame.layout, m_mousePosition); - const auto textMetrics = ResolveHostedTextFieldMetrics(m_theme); - const auto textPalette = ResolveHostedTextFieldPalette(m_theme); - const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme); + const auto textMetrics = ResolvePropertyGridTextFieldMetrics(); + const auto textPalette = ResolvePropertyGridTextFieldPalette(); + const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(); UIDrawData drawData = {}; UIDrawList& drawList = drawData.EmplaceDrawList("EditorTextFieldBasic"); @@ -681,8 +669,8 @@ private: layout.introRect, shellPalette, shellMetrics, - "这个测试验证什么功能", - "验证 UIEditorTextField 的基础编辑交互契约,不涉及 PropertyGrid 或业务 Inspector。"); + "这个测试在验证什么功能?", + "验证 UIEditorTextField 的基础编辑契约,不涉及 PropertyGrid 或任何 Inspector 业务逻辑。"); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), "1. 点击 value box,检查是否进入编辑态。", @@ -775,7 +763,7 @@ private: shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 238.0f), - "Theme: " + m_themeStatus, + "Style: fixed", shellPalette.textWeak, shellMetrics.bodyFontSize); @@ -785,7 +773,7 @@ private: shellPalette, shellMetrics, "TextField 预览", - "这里只放一个 Editor TextField,用来验证基础字段行为。"); + "这里只放一个固定样式 TextField,用来验证基础字段行为。"); drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor); drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f); drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground); @@ -827,12 +815,10 @@ private: UIEditorTextFieldSpec m_spec = {}; UIEditorTextFieldInteractionState m_interactionState = {}; UIEditorTextFieldInteractionFrame m_frame = {}; - Style::UITheme m_theme = {}; UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f); ActionId m_hoveredAction = ActionId::Reset; bool m_hasHoveredAction = false; std::string m_lastResult = {}; - std::string m_themeStatus = "fallback"; }; } // namespace diff --git a/tests/UI/Editor/integration/shell/tree_view_basic/main.cpp b/tests/UI/Editor/integration/shell/tree_view_basic/main.cpp index 845b8e87..476aa15c 100644 --- a/tests/UI/Editor/integration/shell/tree_view_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/tree_view_basic/main.cpp @@ -684,8 +684,8 @@ private: DrawCard( drawList, layout.introRect, - "这个测试在验证什么功能?", - "只验证 Editor TreeView 的单选、层级展开/折叠和键盘导航契约,不涉及任何业务面板。"); + "这个测试验证什么功能?", + "只验证 Editor TreeView 的单选、层级展开/折叠和键盘导航。不涉及任何业务面板。"); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), "1. 点击 row:只切换 selection,hover / selected / focused 必须能明确区分。", @@ -708,7 +708,7 @@ private: 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f), - "5. 点击树外空白清除 focus;F12 手动截图;XCUI_AUTO_CAPTURE_ON_STARTUP=1 自动截图。", + "5. 点击树外空白清除 focus;F12 手动截图;XCUI_AUTO_CAPTURE_ON_STARTUP=1 可启动自动截图。", kTextPrimary, 12.0f); diff --git a/tests/UI/Editor/integration/shell/tree_view_inline_rename/main.cpp b/tests/UI/Editor/integration/shell/tree_view_inline_rename/main.cpp index 7abbabcc..e933563d 100644 --- a/tests/UI/Editor/integration/shell/tree_view_inline_rename/main.cpp +++ b/tests/UI/Editor/integration/shell/tree_view_inline_rename/main.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include "EditorValidationTheme.h" @@ -13,7 +14,6 @@ #include #include -#include #include #include @@ -32,6 +32,7 @@ #define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "." #endif +// 场景说明:验证固定样式下 TreeView 的 inline rename 启动、提交、取消与外部点击提交。 namespace { using XCEngine::Input::KeyCode; @@ -46,8 +47,8 @@ using XCEngine::UI::UIPointerButton; using XCEngine::UI::UIRect; using XCEngine::UI::Widgets::UIExpansionModel; using XCEngine::UI::Widgets::UISelectionModel; -using XCEngine::UI::Editor::BuildUIEditorHostedTextFieldMetrics; -using XCEngine::UI::Editor::BuildUIEditorHostedTextFieldPalette; +using XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldMetrics; +using XCEngine::UI::Editor::BuildUIEditorPropertyGridTextFieldPalette; using XCEngine::UI::Editor::BuildUIEditorInlineRenameTextFieldMetrics; using XCEngine::UI::Editor::Host::AutoScreenshotController; using XCEngine::UI::Editor::Host::NativeRenderer; @@ -75,8 +76,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorTreeViewInvalidIndex; using XCEngine::UI::Editor::Widgets::UIEditorTreeViewItem; using XCEngine::UI::Editor::Widgets::UIEditorTextFieldMetrics; using XCEngine::UI::Editor::Widgets::UIEditorTextFieldPalette; -namespace Style = XCEngine::UI::Style; - constexpr const wchar_t* kWindowClassName = L"XCUIEditorTreeViewInlineRenameValidation"; constexpr const wchar_t* kWindowTitle = L"XCUI Editor | TreeView Inline Rename"; @@ -93,7 +92,6 @@ struct ScenarioLayout { }; std::filesystem::path ResolveRepoRootPath(); -std::filesystem::path ResolveValidationThemePath(); bool ContainsPoint(const UIRect& rect, float x, float y); std::int32_t MapTreeKey(UINT keyCode); ScenarioLayout BuildScenarioLayout(float width, float height, const EditorValidationShellMetrics& shellMetrics); @@ -143,8 +141,6 @@ private: NativeRenderer m_renderer = {}; AutoScreenshotController m_autoScreenshot = {}; std::filesystem::path m_captureRoot = {}; - Style::UITheme m_theme = {}; - std::string m_themeStatus = "fallback"; std::vector m_items = {}; UISelectionModel m_selectionModel = {}; UIExpansionModel m_expansionModel = {}; @@ -168,10 +164,6 @@ std::filesystem::path ResolveRepoRootPath() { return std::filesystem::path(root).lexically_normal(); } -std::filesystem::path ResolveValidationThemePath() { - return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme").lexically_normal(); -} - bool ContainsPoint(const UIRect& rect, float x, float y) { return x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height; } @@ -345,13 +337,6 @@ bool ScenarioApp::Initialize(HINSTANCE hInstance, int nCmdShow) { } m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/tree_view_inline_rename/captures"; m_autoScreenshot.Initialize(m_captureRoot); - const auto themeLoad = XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath()); - if (themeLoad.succeeded) { - m_theme = themeLoad.theme; - m_themeStatus = "loaded"; - } else { - m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error; - } ResetScenario(); return true; } @@ -374,7 +359,7 @@ ScenarioLayout ScenarioApp::GetLayout() const { GetClientRect(m_hwnd, &clientRect); const float width = static_cast((std::max)(1L, clientRect.right - clientRect.left)); const float height = static_cast((std::max)(1L, clientRect.bottom - clientRect.top)); - return BuildScenarioLayout(width, height, XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme)); + return BuildScenarioLayout(width, height, XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics()); } void ScenarioApp::ResetScenario() { @@ -718,9 +703,9 @@ UIEditorInlineRenameSessionRequest ScenarioApp::BuildRenameRequest(bool beginSes } UIEditorTextFieldMetrics ScenarioApp::ResolveHostedTextFieldMetrics() const { - const auto propertyMetrics = ResolveUIEditorPropertyGridMetrics(m_theme); - const auto textMetrics = ResolveUIEditorTextFieldMetrics(m_theme); - return BuildUIEditorHostedTextFieldMetrics(propertyMetrics, textMetrics); + const auto propertyMetrics = ResolveUIEditorPropertyGridMetrics(); + const auto textMetrics = ResolveUIEditorTextFieldMetrics(); + return BuildUIEditorPropertyGridTextFieldMetrics(propertyMetrics, textMetrics); } UIEditorTextFieldMetrics ScenarioApp::ResolveInlineRenameMetrics(const UIRect& bounds) const { @@ -728,9 +713,9 @@ UIEditorTextFieldMetrics ScenarioApp::ResolveInlineRenameMetrics(const UIRect& b } UIEditorTextFieldPalette ScenarioApp::ResolveInlineRenamePalette() const { - const auto propertyPalette = ResolveUIEditorPropertyGridPalette(m_theme); - const auto textPalette = ResolveUIEditorTextFieldPalette(m_theme); - return BuildUIEditorHostedTextFieldPalette(propertyPalette, textPalette); + const auto propertyPalette = ResolveUIEditorPropertyGridPalette(); + const auto textPalette = ResolveUIEditorTextFieldPalette(); + return BuildUIEditorPropertyGridTextFieldPalette(propertyPalette, textPalette); } void ScenarioApp::ExecuteAction(ActionId action) { @@ -755,9 +740,9 @@ void ScenarioApp::RenderFrame() { const float width = static_cast((std::max)(1L, clientRect.right - clientRect.left)); const float height = static_cast((std::max)(1L, clientRect.bottom - clientRect.top)); const auto shellMetrics = - XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme); + XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); const auto shellPalette = - XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme); + XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics); RefreshTreeFrame(); @@ -781,8 +766,8 @@ void ScenarioApp::RenderFrame() { layout.introRect, shellPalette, shellMetrics, - "这个测试在验证什么功能?", - "只验证 Editor TreeView 的 inline rename 契约:默认进入编辑、字符编辑、Enter 提交、Esc 取消、点击外部提交。"); + "这个测试验证什么功能?", + "只验证 Editor TreeView 的 inline rename:默认进入编辑、字符编辑、Enter 提交、Esc 取消、点击外部提交。"); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), "1. 启动后默认进入 hero-mesh rename;输入框只能覆盖 label 区。", @@ -790,7 +775,7 @@ void ScenarioApp::RenderFrame() { shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), - "2. 输入字符后,Draft 必须实时变化,原标签不能和 overlay 叠字。", + "2. 输入字符后,Draft 必须实时变化;原标签不能和 overlay 叠字。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( @@ -805,7 +790,7 @@ void ScenarioApp::RenderFrame() { shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f), - "5. rename 中点击输入框外部:提交当前草稿并退出编辑态。", + "5. rename 中点击输入框外部:提交当前草稿,并退出编辑态。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( @@ -903,10 +888,9 @@ void ScenarioApp::RenderFrame() { shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 334.0f), - "Theme: " + m_themeStatus, + "Style: fixed", shellPalette.textWeak, shellMetrics.bodyFontSize); - DrawCard( drawList, layout.previewRect, diff --git a/tests/UI/Editor/integration/shell/tree_view_multiselect/main.cpp b/tests/UI/Editor/integration/shell/tree_view_multiselect/main.cpp index d25ed0ae..87609b76 100644 --- a/tests/UI/Editor/integration/shell/tree_view_multiselect/main.cpp +++ b/tests/UI/Editor/integration/shell/tree_view_multiselect/main.cpp @@ -810,7 +810,7 @@ private: DrawCard( drawList, layout.introRect, - "这个测试在验证什么功能?", + "这个测试验证什么功能?", "只验证 Editor TreeView 的多选契约,不混入 Hierarchy / Inspector 业务面板。"); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), @@ -819,12 +819,12 @@ private: 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), - "2. Ctrl+单击 / Shift+单击:应只验证 visible tree 范围内的多选切换与连续扩选。", + "2. Ctrl+单击 / Shift+单击:只验证 visible tree 范围内的多选切换与连续扩选。", kTextPrimary, 12.0f); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), - "3. 点击 disclosure:应只切换 expanded,不应无故打散 selection。", + "3. 点击 disclosure:只切换 expanded,不应无故打散 selection。", kTextPrimary, 12.0f); drawList.AddText( @@ -853,7 +853,7 @@ private: DrawButton(drawList, button, m_hasHoveredAction && m_hoveredAction == button.action); } - DrawCard(drawList, layout.stateRect, "状态摘要", "重点检查 multi-select 与 expanded / visible contract 是否稳定。"); + DrawCard(drawList, layout.stateRect, "状态摘要", "重点检查 multi-select 与 expanded / visible 契约是否稳定。"); drawList.AddText( UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 70.0f), "Hit: " + DescribeHitTarget(currentHit, m_items), diff --git a/tests/UI/Editor/integration/shell/vector2_field_basic/main.cpp b/tests/UI/Editor/integration/shell/vector2_field_basic/main.cpp index 37052605..3961e09e 100644 --- a/tests/UI/Editor/integration/shell/vector2_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/vector2_field_basic/main.cpp @@ -1,10 +1,10 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include -#include -#include +#include +#include +#include #include "EditorValidationTheme.h" #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -48,8 +48,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorVector2FieldHitTarget; using XCEngine::UI::Editor::Widgets::UIEditorVector2FieldHitTargetKind; using XCEngine::UI::Editor::Widgets::UIEditorVector2FieldInvalidComponentIndex; using XCEngine::UI::Editor::Widgets::UIEditorVector2FieldSpec; -namespace Style = XCEngine::UI::Style; - constexpr const wchar_t* kWindowClassName = L"XCUIEditorVector2FieldBasicValidation"; constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Vector2Field Basic"; @@ -85,11 +83,6 @@ std::filesystem::path ResolveRepoRootPath() { return std::filesystem::path(root).lexically_normal(); } -std::filesystem::path ResolveValidationThemePath() { - return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme") - .lexically_normal(); -} - bool ContainsPoint(const UIRect& rect, float x, float y) { return x >= rect.x && x <= rect.x + rect.width && @@ -437,14 +430,6 @@ private: m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/vector2_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); - const auto themeLoad = - XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath()); - if (themeLoad.succeeded) { - m_theme = themeLoad.theme; - m_themeStatus = "loaded"; - } else { - m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error; - } ResetScenario(); return true; @@ -473,7 +458,7 @@ private: return BuildScenarioLayout( width, height, - XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme)); + XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics()); } void ResetScenario() { @@ -503,7 +488,7 @@ private: } const ScenarioLayout layout = GetLayout(); - const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics(m_theme); + const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics(); m_frame = UpdateUIEditorVector2FieldInteraction( m_interactionState, m_spec, @@ -623,7 +608,7 @@ private: UIEditorVector2FieldInteractionResult PumpEvents(std::vector events) { const ScenarioLayout layout = GetLayout(); - const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics(m_theme); + const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics(); m_frame = UpdateUIEditorVector2FieldInteraction( m_interactionState, m_spec, @@ -635,12 +620,12 @@ private: void UpdateResultText(const UIEditorVector2FieldInteractionResult& result) { if (result.editCommitRejected) { - m_lastResult = "提交失败,当前文本不是合法数字"; + m_lastResult = "提交被拒绝:当前输入不是合法数字"; return; } if (result.editCommitted) { m_lastResult = - std::string("已提交 ") + + std::string("已提交编辑: ") + DescribeSelectedComponent(result.changedComponentIndex) + " = " + result.committedText; return; @@ -657,7 +642,7 @@ private: } if (result.stepApplied || result.valueChanged) { m_lastResult = - std::string("数值已更新,当前 component = ") + + std::string("值已更新,当前 component = ") + DescribeSelectedComponent(result.changedComponentIndex); return; } @@ -669,15 +654,15 @@ private: } if (result.focusChanged) { m_lastResult = - std::string("焦点变化: ") + + std::string("焦点状态: ") + (m_interactionState.vector2FieldState.focused ? "focused" : "lost"); return; } if (result.consumed) { - m_lastResult = "控件已消费输入"; + m_lastResult = "输入已处理,但没有额外状态变化"; return; } - m_lastResult = "等待交互"; + m_lastResult = "无变化"; } void ExecuteAction(ActionId action) { @@ -702,16 +687,16 @@ private: GetClientRect(m_hwnd, &clientRect); const float width = static_cast((std::max)(1L, clientRect.right - clientRect.left)); const float height = static_cast((std::max)(1L, clientRect.bottom - clientRect.top)); - const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme); - const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme); + const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); + const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics); RefreshFrame(); const UIEditorVector2FieldHitTarget currentHit = HitTestUIEditorVector2Field(m_frame.layout, m_mousePosition); - const auto vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics(m_theme); - const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector2FieldPalette(m_theme); - const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme); + const auto vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector2FieldMetrics(); + const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector2FieldPalette(); + const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(); UIDrawData drawData = {}; UIDrawList& drawList = drawData.EmplaceDrawList("EditorVector2FieldBasic"); @@ -722,36 +707,36 @@ private: layout.introRect, shellPalette, shellMetrics, - "这个测试验证什么功能", - "验证 UIEditorVector2Field 的双通道数值编辑契约,不涉及 PropertyGrid 或业务 Inspector。"); + "这个测试在验证什么功能?", + "验证 UIEditorVector2Field 的选择切换、键盘步进、编辑提交/取消,以及固定 Inspector 风格承载。"); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), - "1. 点击 X / Y 对应的 value box,检查 selected component 是否切换,并且应进入编辑态。", + "1. 点击 X / Y value box 可切换 selected component,并检查 hover 与高亮。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), - "2. 获得 focus 后按 Tab,检查 selected component 在 X / Y 之间切换。", + "2. 获得 focus 后,按 Tab 在 X / Y 之间切换 selected component。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), - "3. 非编辑态按 Up / Down / Home / End,检查当前 component 的 step / 边界行为。", + "3. 不进入编辑时,Up / Down / Home / End 会对当前 component 做 step 或边界跳转。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), - "4. 按 Enter 开始编辑;直接输入字符也应开始编辑;Enter commit,Escape cancel。", + "4. 按 Enter 开始编辑,输入后再按 Enter 提交,按 Escape 取消。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f), - "5. 编辑态按 F6 模拟 FocusLost,应提交暂存文本并退出编辑态。", + "5. 按 F6 模拟 FocusLost,检查未提交编辑是否按约定结束。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 182.0f), - "6. 检查 Hover / Selected / Editing / Values / Result 是否同步更新。", + "6. 观察 Hover / Selected / Editing / Values / Result 是否同步。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( @@ -826,19 +811,13 @@ private: captureSummary, shellPalette.textWeak, shellMetrics.bodyFontSize); - drawList.AddText( - UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 262.0f), - "Theme: " + m_themeStatus, - shellPalette.textWeak, - shellMetrics.bodyFontSize); - DrawCard( drawList, layout.previewRect, shellPalette, shellMetrics, "Vector2Field 预览", - "这里只放一个 Unity 风格的双通道 Vector2 字段。"); + "这里只放一个固定样式的 Vector2 输入项。"); drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor); drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f); drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground); @@ -880,12 +859,10 @@ private: UIEditorVector2FieldSpec m_spec = {}; UIEditorVector2FieldInteractionState m_interactionState = {}; UIEditorVector2FieldInteractionFrame m_frame = {}; - Style::UITheme m_theme = {}; UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f); ActionId m_hoveredAction = ActionId::Reset; bool m_hasHoveredAction = false; - std::string m_lastResult = "等待交互"; - std::string m_themeStatus = "fallback"; + std::string m_lastResult = "无变化"; }; } // namespace diff --git a/tests/UI/Editor/integration/shell/vector3_field_basic/main.cpp b/tests/UI/Editor/integration/shell/vector3_field_basic/main.cpp index 5c9fbc48..6bcbd203 100644 --- a/tests/UI/Editor/integration/shell/vector3_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/vector3_field_basic/main.cpp @@ -1,10 +1,10 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include -#include -#include +#include +#include +#include #include "EditorValidationTheme.h" #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -48,8 +48,6 @@ using XCEngine::UI::Editor::Widgets::UIEditorVector3FieldHitTarget; using XCEngine::UI::Editor::Widgets::UIEditorVector3FieldHitTargetKind; using XCEngine::UI::Editor::Widgets::UIEditorVector3FieldInvalidComponentIndex; using XCEngine::UI::Editor::Widgets::UIEditorVector3FieldSpec; -namespace Style = XCEngine::UI::Style; - constexpr const wchar_t* kWindowClassName = L"XCUIEditorVector3FieldBasicValidation"; constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Vector3Field Basic"; @@ -85,11 +83,6 @@ std::filesystem::path ResolveRepoRootPath() { return std::filesystem::path(root).lexically_normal(); } -std::filesystem::path ResolveValidationThemePath() { - return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme") - .lexically_normal(); -} - bool ContainsPoint(const UIRect& rect, float x, float y) { return x >= rect.x && x <= rect.x + rect.width && @@ -443,14 +436,6 @@ private: m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/vector3_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); - const auto themeLoad = - XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath()); - if (themeLoad.succeeded) { - m_theme = themeLoad.theme; - m_themeStatus = "loaded"; - } else { - m_themeStatus = themeLoad.error.empty() ? "fallback" : themeLoad.error; - } ResetScenario(); return true; @@ -479,7 +464,7 @@ private: return BuildScenarioLayout( width, height, - XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme)); + XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics()); } void ResetScenario() { @@ -509,7 +494,7 @@ private: } const ScenarioLayout layout = GetLayout(); - const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics(m_theme); + const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics(); m_frame = UpdateUIEditorVector3FieldInteraction( m_interactionState, m_spec, @@ -629,7 +614,7 @@ private: UIEditorVector3FieldInteractionResult PumpEvents(std::vector events) { const ScenarioLayout layout = GetLayout(); - const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics(m_theme); + const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics(); m_frame = UpdateUIEditorVector3FieldInteraction( m_interactionState, m_spec, @@ -641,12 +626,12 @@ private: void UpdateResultText(const UIEditorVector3FieldInteractionResult& result) { if (result.editCommitRejected) { - m_lastResult = "提交失败,当前文本不是合法数字"; + m_lastResult = "提交被拒绝:当前输入不是合法数字"; return; } if (result.editCommitted) { m_lastResult = - std::string("已提交 ") + + std::string("已提交编辑: ") + DescribeSelectedComponent(result.changedComponentIndex) + " = " + result.committedText; return; @@ -663,7 +648,7 @@ private: } if (result.stepApplied || result.valueChanged) { m_lastResult = - std::string("数值已更新,当前 component = ") + + std::string("值已更新,当前 component = ") + DescribeSelectedComponent(result.changedComponentIndex); return; } @@ -675,15 +660,15 @@ private: } if (result.focusChanged) { m_lastResult = - std::string("焦点变化: ") + + std::string("焦点状态: ") + (m_interactionState.vector3FieldState.focused ? "focused" : "lost"); return; } if (result.consumed) { - m_lastResult = "控件已消费输入"; + m_lastResult = "输入已处理,但没有额外状态变化"; return; } - m_lastResult = "等待交互"; + m_lastResult = "无变化"; } void ExecuteAction(ActionId action) { @@ -708,16 +693,16 @@ private: GetClientRect(m_hwnd, &clientRect); const float width = static_cast((std::max)(1L, clientRect.right - clientRect.left)); const float height = static_cast((std::max)(1L, clientRect.bottom - clientRect.top)); - const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme); - const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme); + const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); + const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); const ScenarioLayout layout = BuildScenarioLayout(width, height, shellMetrics); RefreshFrame(); const UIEditorVector3FieldHitTarget currentHit = HitTestUIEditorVector3Field(m_frame.layout, m_mousePosition); - const auto vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics(m_theme); - const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector3FieldPalette(m_theme); - const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme); + const auto vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector3FieldMetrics(); + const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector3FieldPalette(); + const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(); UIDrawData drawData = {}; UIDrawList& drawList = drawData.EmplaceDrawList("EditorVector3FieldBasic"); @@ -728,36 +713,36 @@ private: layout.introRect, shellPalette, shellMetrics, - "这个测试验证什么功能", - "验证 UIEditorVector3Field 的三通道数值编辑契约,不涉及 PropertyGrid 或业务 Inspector。"); + "这个测试在验证什么功能?", + "验证 UIEditorVector3Field 的选择切换、键盘步进、编辑提交/取消,以及固定 Inspector 风格承载。"); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), - "1. 点击 X / Y / Z 对应的 value box,检查 selected component 是否切换,并应进入编辑态。", + "1. 点击 X / Y / Z value box 可切换 selected component,并检查 hover 与高亮。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), - "2. 获得 focus 后按 Tab,检查 selected component 在 X / Y / Z 之间切换。", + "2. 获得 focus 后,按 Tab 在 X / Y / Z 之间切换 selected component。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), - "3. 非编辑态按 Up / Down / Home / End,检查当前 component 的 step / 边界行为。", + "3. 不进入编辑时,Up / Down / Home / End 会对当前 component 做 step 或边界跳转。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), - "4. 按 Enter 开始编辑;直接输入字符也应开始编辑;Enter commit,Escape cancel。", + "4. 按 Enter 开始编辑,输入后再按 Enter 提交,按 Escape 取消。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f), - "5. 编辑态按 F6 模拟 FocusLost,应提交暂存文本并退出编辑态。", + "5. 按 F6 模拟 FocusLost,检查未提交编辑是否按约定结束。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 182.0f), - "6. 检查 Hover / Selected / Editing / Values / Result 是否同步更新。", + "6. 观察 Hover / Selected / Editing / Values / Result 是否同步。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText( @@ -834,19 +819,13 @@ private: captureSummary, shellPalette.textWeak, shellMetrics.bodyFontSize); - drawList.AddText( - UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 262.0f), - "Theme: " + m_themeStatus, - shellPalette.textWeak, - shellMetrics.bodyFontSize); - DrawCard( drawList, layout.previewRect, shellPalette, shellMetrics, "Vector3Field 预览", - "这里只放一个 Unity 风格的三通道 Vector3 字段。"); + "这里只放一个固定样式的 Vector3 输入项。"); drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor); drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f); drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground); @@ -888,12 +867,10 @@ private: UIEditorVector3FieldSpec m_spec = {}; UIEditorVector3FieldInteractionState m_interactionState = {}; UIEditorVector3FieldInteractionFrame m_frame = {}; - Style::UITheme m_theme = {}; UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f); ActionId m_hoveredAction = ActionId::Reset; bool m_hasHoveredAction = false; - std::string m_lastResult = "等待交互"; - std::string m_themeStatus = "fallback"; + std::string m_lastResult = "无变化"; }; } // namespace diff --git a/tests/UI/Editor/integration/shell/vector4_field_basic/main.cpp b/tests/UI/Editor/integration/shell/vector4_field_basic/main.cpp index c6b482d8..45f6e417 100644 --- a/tests/UI/Editor/integration/shell/vector4_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/vector4_field_basic/main.cpp @@ -1,10 +1,10 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include -#include -#include +#include +#include +#include #include "EditorValidationTheme.h" #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -46,8 +46,6 @@ using XCEngine::UI::Editor::Widgets::HitTestUIEditorVector4Field; using XCEngine::UI::Editor::Widgets::UIEditorVector4FieldHitTarget; using XCEngine::UI::Editor::Widgets::UIEditorVector4FieldHitTargetKind; using XCEngine::UI::Editor::Widgets::UIEditorVector4FieldSpec; -namespace Style = XCEngine::UI::Style; - constexpr const wchar_t* kWindowClassName = L"XCUIEditorVector4FieldBasicValidation"; constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Vector4Field Basic"; @@ -69,11 +67,6 @@ std::filesystem::path ResolveRepoRootPath() { return std::filesystem::path(root).lexically_normal(); } -std::filesystem::path ResolveValidationThemePath() { - return (ResolveRepoRootPath() / "tests/UI/Editor/integration/shared/themes/editor_validation.xctheme") - .lexically_normal(); -} - std::int32_t MapVectorKey(UINT keyCode) { switch (keyCode) { case VK_LEFT: return static_cast(KeyCode::Left); @@ -304,9 +297,6 @@ private: UpdateWindow(m_hwnd); m_captureRoot = ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/vector4_field_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); - const auto themeLoad = XCEngine::Tests::EditorUI::LoadEditorValidationTheme(ResolveValidationThemePath()); - m_theme = themeLoad.theme; - m_themeStatus = themeLoad.succeeded ? "loaded" : (themeLoad.error.empty() ? "fallback" : themeLoad.error); ResetScenario(); return true; } @@ -344,8 +334,8 @@ private: const auto layout = BuildScenarioLayout( static_cast((std::max)(1L, clientRect.right - clientRect.left)), static_cast((std::max)(1L, clientRect.bottom - clientRect.top)), - XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme)); - const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector4FieldMetrics(m_theme); + XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics()); + const auto metrics = XCEngine::UI::Editor::ResolveUIEditorVector4FieldMetrics(); m_frame = UpdateUIEditorVector4FieldInteraction( m_interactionState, m_spec, @@ -357,21 +347,21 @@ private: void UpdateResultText(const UIEditorVector4FieldInteractionResult& result) { if (result.editCommitRejected) { - m_lastResult = "提交失败,当前文本不是合法数字"; + m_lastResult = "提交被拒绝:当前输入不是合法数字"; } else if (result.editCommitted) { - m_lastResult = "已提交 " + DescribeSelectedComponent(result.changedComponentIndex) + " = " + result.committedText; + m_lastResult = "已提交编辑: " + DescribeSelectedComponent(result.changedComponentIndex) + " = " + result.committedText; } else if (result.editCanceled) { m_lastResult = "已取消编辑"; } else if (result.editStarted) { m_lastResult = "开始编辑 component " + DescribeSelectedComponent(result.selectedComponentIndex); } else if (result.stepApplied || result.valueChanged) { - m_lastResult = "数值已更新,当前 component = " + DescribeSelectedComponent(result.changedComponentIndex); + m_lastResult = "值已更新,当前 component = " + DescribeSelectedComponent(result.changedComponentIndex); } else if (result.selectionChanged) { m_lastResult = "已切换选中 component: " + DescribeSelectedComponent(result.selectedComponentIndex); } else if (result.focusChanged) { - m_lastResult = std::string("焦点变化: ") + (m_interactionState.vector4FieldState.focused ? "focused" : "lost"); + m_lastResult = std::string("焦点状态: ") + (m_interactionState.vector4FieldState.focused ? "focused" : "lost"); } else if (result.consumed) { - m_lastResult = "控件已消费输入"; + m_lastResult = "输入已处理,但没有额外状态变化"; } } @@ -380,14 +370,14 @@ private: GetClientRect(m_hwnd, &clientRect); const float width = static_cast((std::max)(1L, clientRect.right - clientRect.left)); const float height = static_cast((std::max)(1L, clientRect.bottom - clientRect.top)); - const auto shellMetrics = XCEngine::Tests::EditorUI::ResolveEditorValidationShellMetrics(m_theme); - const auto shellPalette = XCEngine::Tests::EditorUI::ResolveEditorValidationShellPalette(m_theme); + const auto shellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); + const auto shellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); const auto layout = BuildScenarioLayout(width, height, shellMetrics); PumpEvents({}); - const auto vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector4FieldMetrics(m_theme); - const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector4FieldPalette(m_theme); - const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(m_theme); + const auto vectorMetrics = XCEngine::UI::Editor::ResolveUIEditorVector4FieldMetrics(); + const auto vectorPalette = XCEngine::UI::Editor::ResolveUIEditorVector4FieldPalette(); + const auto propertyPalette = XCEngine::UI::Editor::ResolveUIEditorPropertyGridPalette(); const auto currentHit = HitTestUIEditorVector4Field(m_frame.layout, m_mousePosition); UIDrawData drawData = {}; @@ -396,12 +386,12 @@ private: drawList.AddFilledRect(layout.introRect, shellPalette.cardBackground, shellMetrics.cardRadius); drawList.AddRectOutline(layout.introRect, shellPalette.cardBorder, 1.0f, shellMetrics.cardRadius); - drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 14.0f), "这个测试验证什么功能?", shellPalette.textPrimary, shellMetrics.titleFontSize); - drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 40.0f), "只验证 UIEditorVector4Field 的四通道编辑契约。", shellPalette.textMuted, shellMetrics.bodyFontSize); - drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), "1. 点击 X/Y/Z/W 的 value box,检查 selected component 和 editing。", shellPalette.textPrimary, shellMetrics.bodyFontSize); - drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), "2. 按 Tab/Shift+Tab 切换 component;Up/Down/Home/End 检查 step 与边界。", shellPalette.textPrimary, shellMetrics.bodyFontSize); - drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), "3. Enter 开始编辑;直接输入字符也应开始编辑;Enter commit,Escape cancel。", shellPalette.textPrimary, shellMetrics.bodyFontSize); - drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), "4. F6 模拟 FocusLost;F12 截图;R 重置当前测试。", shellPalette.textPrimary, shellMetrics.bodyFontSize); + drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 14.0f), "这个测试在验证什么功能?", shellPalette.textPrimary, shellMetrics.titleFontSize); + drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 40.0f), "验证 UIEditorVector4Field 的四分量切换、键盘步进、编辑提交/取消,以及固定 Inspector 风格展示。", shellPalette.textMuted, shellMetrics.bodyFontSize); + drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 72.0f), "1. 点击 X / Y / Z / W 的 value box,可切换 selected component。", shellPalette.textPrimary, shellMetrics.bodyFontSize); + drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 94.0f), "2. 按 Tab / Shift+Tab 切换 component;Up / Down / Home / End 检查 step 与边界。", shellPalette.textPrimary, shellMetrics.bodyFontSize); + drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 116.0f), "3. 按 Enter 开始编辑;直接输入字符也应开始编辑;Enter 提交,Escape 取消。", shellPalette.textPrimary, shellMetrics.bodyFontSize); + drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 138.0f), "4. 按 F6 模拟 FocusLost,按 F12 截图,按 R 重置当前测试。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText(UIPoint(layout.introRect.x + 16.0f, layout.introRect.y + 160.0f), "5. 重点检查 Hover / Selected / Editing / Values / Result 是否同步。", shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddFilledRect(layout.stateRect, shellPalette.cardBackground, shellMetrics.cardRadius); @@ -414,13 +404,12 @@ private: drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 142.0f), "Values: X=" + FormatUIEditorVector4FieldComponentValue(m_spec, 0u) + " Y=" + FormatUIEditorVector4FieldComponentValue(m_spec, 1u) + " Z=" + FormatUIEditorVector4FieldComponentValue(m_spec, 2u) + " W=" + FormatUIEditorVector4FieldComponentValue(m_spec, 3u), shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 166.0f), "Display: X=" + m_interactionState.vector4FieldState.displayTexts[0] + " Y=" + m_interactionState.vector4FieldState.displayTexts[1] + " Z=" + m_interactionState.vector4FieldState.displayTexts[2] + " W=" + m_interactionState.vector4FieldState.displayTexts[3], shellPalette.textPrimary, shellMetrics.bodyFontSize); drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 190.0f), "Result: " + m_lastResult, shellPalette.textPrimary, shellMetrics.bodyFontSize); - drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 214.0f), "Capture: " + (m_autoScreenshot.GetLastCaptureSummary().empty() ? std::string("F12 -> captures/") : m_autoScreenshot.GetLastCaptureSummary()), shellPalette.textWeak, shellMetrics.bodyFontSize); - drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 238.0f), "Theme: " + m_themeStatus, shellPalette.textWeak, shellMetrics.bodyFontSize); + drawList.AddText(UIPoint(layout.stateRect.x + 16.0f, layout.stateRect.y + 214.0f), "截图: " + (m_autoScreenshot.GetLastCaptureSummary().empty() ? std::string("F12 -> captures/") : m_autoScreenshot.GetLastCaptureSummary()), shellPalette.textWeak, shellMetrics.bodyFontSize); drawList.AddFilledRect(layout.previewRect, shellPalette.cardBackground, shellMetrics.cardRadius); drawList.AddRectOutline(layout.previewRect, shellPalette.cardBorder, 1.0f, shellMetrics.cardRadius); drawList.AddText(UIPoint(layout.previewRect.x + 16.0f, layout.previewRect.y + 14.0f), "Vector4Field 预览", shellPalette.textPrimary, shellMetrics.titleFontSize); - drawList.AddText(UIPoint(layout.previewRect.x + 16.0f, layout.previewRect.y + 40.0f), "这里只放一个 Unity 风格的四通道字段。", shellPalette.textMuted, shellMetrics.bodyFontSize); + drawList.AddText(UIPoint(layout.previewRect.x + 16.0f, layout.previewRect.y + 40.0f), "这里只放一个固定样式的 Vector4 输入项。", shellPalette.textMuted, shellMetrics.bodyFontSize); drawList.AddFilledRect(layout.inspectorRect, propertyPalette.surfaceColor); drawList.AddRectOutline(layout.inspectorRect, propertyPalette.borderColor, 1.0f); drawList.AddFilledRect(layout.inspectorHeaderRect, shellPalette.cardBackground); @@ -443,10 +432,8 @@ private: UIEditorVector4FieldSpec m_spec = {}; UIEditorVector4FieldInteractionState m_interactionState = {}; UIEditorVector4FieldInteractionFrame m_frame = {}; - Style::UITheme m_theme = {}; UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f); - std::string m_lastResult = "等待交互"; - std::string m_themeStatus = "fallback"; + std::string m_lastResult = "无变化"; }; } // namespace diff --git a/tests/UI/Editor/integration/shell/viewport_shell_basic/main.cpp b/tests/UI/Editor/integration/shell/viewport_shell_basic/main.cpp index e12b239f..849e6a37 100644 --- a/tests/UI/Editor/integration/shell/viewport_shell_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/viewport_shell_basic/main.cpp @@ -2,8 +2,8 @@ #define NOMINMAX #endif -#include -#include +#include +#include #include "Host/AutoScreenshot.h" #include "Host/InputModifierTracker.h" #include "Host/NativeRenderer.h" @@ -506,7 +506,7 @@ private: UIEditorViewportShellSpec BuildShellSpec() const { UIEditorViewportShellSpec spec = {}; spec.chrome.title = "Scene View"; - spec.chrome.subtitle = "ViewportShell 基础层"; + spec.chrome.subtitle = "ViewportShell 基础壳层"; spec.chrome.showTopBar = m_showTopBar; spec.chrome.showBottomBar = m_showBottomBar; spec.chrome.topBarHeight = 40.0f; @@ -565,25 +565,25 @@ private: m_buttons = { { ActionId::ToggleTopBar, - std::string("TopBar: ") + (m_showTopBar ? "开" : "关"), + std::string("TopBar: ") + (m_showTopBar ? "开启" : "关闭"), UIRect(left, top, widthAvailable, buttonHeight), m_showTopBar }, { ActionId::ToggleBottomBar, - std::string("BottomBar: ") + (m_showBottomBar ? "开" : "关"), + std::string("BottomBar: ") + (m_showBottomBar ? "开启" : "关闭"), UIRect(left, top + (buttonHeight + gap), widthAvailable, buttonHeight), m_showBottomBar }, { ActionId::ToggleTexture, - std::string("Texture: ") + (m_textureEnabled ? "开" : "关"), + std::string("Texture: ") + (m_textureEnabled ? "开启" : "关闭"), UIRect(left, top + (buttonHeight + gap) * 2.0f, widthAvailable, buttonHeight), m_textureEnabled }, { ActionId::Reset, - "Reset", + "重置", UIRect(left, top + (buttonHeight + gap) * 3.0f, widthAvailable, buttonHeight), false }, @@ -687,41 +687,41 @@ private: DrawCard( drawList, m_introRect, - "测试功能:ViewportShell 基础 contract", - "只验证 Resolve + Update,不接 Scene/Game 业务面板。"); + "这个测试验证什么功能?", + "只验证 Resolve + Update 的 ViewportShell contract,不接 Scene/Game 业务。"); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f), - "重点检查:切换 TopBar / BottomBar 后,Request Size 与 Input Rect 要同步变化。", + "1. 验证 TopBar / BottomBar 对 Request Size 和 Input Rect 的影响。", kTextMuted, 11.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 88.0f), - "重点检查:Hover / Focus / Capture 要和右侧 surface 边框状态一致。", + "2. 验证 Hover / Focus / Capture 是否与 surface 输入桥同步。", kTextMuted, 11.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 110.0f), - "重点检查:Texture 与 Fallback 只影响 frame 分支,不应破坏 shell 输入边界。", + "3. 验证 Texture / Fallback 分支切换后,frame 输出仍由 shell 承接。", kTextMuted, 11.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 132.0f), - "操作:hover Surface,click 获取 Focus,按住拖出再松开,检查 Capture。", + "4. 验证 hover Surface 与 click Surface 后的 Focus / Capture 变化。", kTextMuted, 11.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 154.0f), - "操作:点击左侧 TopBar / BottomBar / Texture,观察左侧状态与右侧布局。", + "5. 验证 TopBar / BottomBar / Texture 三个开关切换后的整体表现。", kTextMuted, 11.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 176.0f), - "操作:需要保留当前画面时点“截图”,或直接按 F12。", + "6. 验证截图链路,支持按钮触发和 F12 手动截图。", kTextMuted, 11.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 204.0f), - "结果判定:左侧 Request / Input / HoverHit / Hover / Focus / Capture 必须一致。", + "7. 左侧持续展示 Request / Input / HoverHit / Hover / Focus / Capture 摘要。", kTextWeak, 11.0f); @@ -745,18 +745,18 @@ private: addStateLine("Hover Hit: " + DescribeHitTarget(m_hoverHit), kTextPrimary); addStateLine("Hover: " + BoolText(m_shellFrame.inputFrame.hovered), kTextPrimary); addStateLine("Focus: " + BoolText(m_shellFrame.inputFrame.focused), kTextPrimary); - addStateLine("Capture: " + BoolText(m_shellFrame.inputFrame.captured), kTextPrimary); + addStateLine("捕获: " + BoolText(m_shellFrame.inputFrame.captured), kTextPrimary); addStateLine("Result: " + m_lastResult, kTextMuted); const std::string captureSummary = m_autoScreenshot.HasPendingCapture() ? "截图排队中..." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("截图:F12 或按钮 -> viewport_shell_basic/captures/") + ? std::string("截图: F12 或按钮 -> viewport_shell_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); addStateLine(captureSummary, kTextWeak, 11.0f); - DrawCard(drawList, m_previewRect, "Preview", "这里只有一个 ViewportShell,用来检验 Editor 基础层 compose。"); + DrawCard(drawList, m_previewRect, "Preview", "这里只有一个 ViewportShell,用来检查 Editor 基础壳层 compose。"); drawList.AddFilledRect( UIRect( m_previewRect.x + 12.0f, diff --git a/tests/UI/Editor/integration/shell/viewport_slot_basic/main.cpp b/tests/UI/Editor/integration/shell/viewport_slot_basic/main.cpp index 25011c74..1d8d8467 100644 --- a/tests/UI/Editor/integration/shell/viewport_slot_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/viewport_slot_basic/main.cpp @@ -2,7 +2,7 @@ #define NOMINMAX #endif -#include +#include #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -322,7 +322,7 @@ private: UIEditorViewportSlotChrome BuildChrome() const { UIEditorViewportSlotChrome chrome = {}; chrome.title = "Scene View"; - chrome.subtitle = "ViewportSlot 基础壳层"; + chrome.subtitle = "ViewportSlot 基础布局验证"; chrome.showTopBar = m_showTopBar; chrome.showBottomBar = m_showBottomBar; chrome.topBarHeight = 40.0f; @@ -341,7 +341,7 @@ private: frame.statusText = "Fake viewport frame"; } else { frame.hasTexture = false; - frame.statusText = "当前无纹理,检查 fallback message 与 input 边界。"; + frame.statusText = "当前无纹理,检查 fallback message 与 input 区域。"; } return frame; } @@ -414,10 +414,10 @@ private: const float width = m_controlsRect.width - 32.0f; m_buttons = { { ActionId::ToggleTopBar, "TopBar", UIRect(left, top, width, buttonHeight), m_showTopBar }, - { ActionId::ToggleBottomBar, "状态条", UIRect(left, top + (buttonHeight + gap), width, buttonHeight), m_showBottomBar }, + { ActionId::ToggleBottomBar, "BottomBar", UIRect(left, top + (buttonHeight + gap), width, buttonHeight), m_showBottomBar }, { ActionId::ToggleTexture, "Texture", UIRect(left, top + (buttonHeight + gap) * 2.0f, width, buttonHeight), m_textureEnabled }, { ActionId::ToggleSquareAspect, "方形比例", UIRect(left, top + (buttonHeight + gap) * 3.0f, width, buttonHeight), m_squareAspect }, - { ActionId::Reset, "Reset", UIRect(left, top + (buttonHeight + gap) * 4.0f, width, buttonHeight), false }, + { ActionId::Reset, "重置", UIRect(left, top + (buttonHeight + gap) * 4.0f, width, buttonHeight), false }, { ActionId::Capture, "截图", UIRect(left, top + (buttonHeight + gap) * 5.0f, width, buttonHeight), false } }; } @@ -480,7 +480,7 @@ private: m_slotState.focused = true; m_slotState.surfaceActive = true; m_slotState.inputCaptured = true; - m_lastResult = "Surface 按下:focus + active + capture"; + m_lastResult = "Surface 按下,进入 focus + active + capture"; } InvalidateScenario(); } @@ -493,7 +493,7 @@ private: break; case ActionId::ToggleBottomBar: m_showBottomBar = !m_showBottomBar; - m_lastResult = m_showBottomBar ? "状态条已打开" : "状态条已关闭"; + m_lastResult = m_showBottomBar ? "BottomBar 已打开" : "BottomBar 已关闭"; break; case ActionId::ToggleTexture: m_textureEnabled = !m_textureEnabled; @@ -537,11 +537,11 @@ private: switch (hit.kind) { case UIEditorViewportSlotHitTargetKind::ToolItem: m_slotState.activeToolIndex = hit.index; - m_lastResult = "ToolItem 命中:" + std::to_string(hit.index); + m_lastResult = "ToolItem 命中: " + std::to_string(hit.index); break; case UIEditorViewportSlotHitTargetKind::StatusSegment: m_slotState.statusBarState.activeIndex = hit.index; - m_lastResult = "StatusSegment 命中:" + std::to_string(hit.index); + m_lastResult = "StatusSegment 命中: " + std::to_string(hit.index); break; case UIEditorViewportSlotHitTargetKind::Surface: m_slotState.focused = true; @@ -584,40 +584,40 @@ private: DrawCard( drawList, m_introRect, - "测试功能:ViewportSlot 基础壳层", - "只验证 Editor 基础层。"); + "这个测试验证什么功能?", + "只验证 Editor ViewportSlot 的基础壳层。"); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f), - "检查1:TopBar / Surface / 状态条布局。", + "1. 验证 TopBar / Surface / BottomBar 的区域划分。", kTextMuted, 12.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 88.0f), - "检查2:hover / focus / active / capture。", + "2. 验证 hover / focus / active / capture 状态。", kTextMuted, 12.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 110.0f), - "操作1:hover toolbar / surface / status bar。", + "3. 验证 hover toolbar / surface / status bar。", kTextMuted, 12.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 132.0f), - "操作2:click surface;按住左键观察 capture。", + "4. 验证点击 surface 后进入 capture。", kTextMuted, 12.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 154.0f), - "操作3:切换 TopBar / 状态条 / Texture / 比例;F12 或“截图”。", + "5. 验证切换 TopBar / BottomBar / Texture / 方形比例四个开关。", kTextMuted, 12.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 180.0f), - "结果:chrome 开关后 surface 扩张;Texture/Fallback 正确。", + "6. Chrome 固定,surface 在 Texture / Fallback 之间切换。", kTextWeak, 12.0f); - DrawCard(drawList, m_controlsRect, "开关", "只保留和 ViewportSlot contract 直接相关的操作。"); + DrawCard(drawList, m_controlsRect, "操作", "只保留和 ViewportSlot contract 直接相关的操作。"); for (const ButtonState& button : m_buttons) { DrawButton(drawList, button); } @@ -640,12 +640,12 @@ private: 13.0f); drawList.AddText( UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 144.0f), - "Capture: " + BoolText(m_slotState.inputCaptured), + "捕获: " + BoolText(m_slotState.inputCaptured), kTextPrimary, 13.0f); drawList.AddText( UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 170.0f), - "Chrome: TopBar " + BoolText(m_showTopBar) + " | 状态条 " + BoolText(m_showBottomBar), + "Chrome: TopBar " + BoolText(m_showTopBar) + " | BottomBar " + BoolText(m_showBottomBar), kTextPrimary, 13.0f); drawList.AddText( @@ -662,7 +662,7 @@ private: m_autoScreenshot.HasPendingCapture() ? "截图排队中..." : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("截图:F12 或按钮 -> viewport_slot_basic/captures/") + ? std::string("截图: F12 或按钮 -> viewport_slot_basic/captures/") : m_autoScreenshot.GetLastCaptureSummary()); drawList.AddText( UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 248.0f), diff --git a/tests/UI/Editor/integration/shell/workspace_interaction_basic/main.cpp b/tests/UI/Editor/integration/shell/workspace_interaction_basic/main.cpp index 457f9d75..dfc13440 100644 --- a/tests/UI/Editor/integration/shell/workspace_interaction_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/workspace_interaction_basic/main.cpp @@ -2,7 +2,9 @@ #define NOMINMAX #endif -#include +#include +#include +#include "../../shared/src/EditorValidationTheme.h" #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -12,6 +14,8 @@ #include #include +#include +#include #include #include #include @@ -41,14 +45,20 @@ using XCEngine::UI::Editor::CollectUIEditorWorkspaceVisiblePanels; using XCEngine::UI::Editor::FindUIEditorWorkspaceViewportPresentationFrame; using XCEngine::UI::Editor::Host::AutoScreenshotController; using XCEngine::UI::Editor::Host::NativeRenderer; +using XCEngine::UI::Editor::ResolveUIEditorDockHostMetrics; +using XCEngine::UI::Editor::ResolveUIEditorDockHostPalette; +using XCEngine::UI::Editor::ResolveUIEditorViewportSlotMetrics; +using XCEngine::UI::Editor::ResolveUIEditorViewportSlotPalette; using XCEngine::UI::Editor::UIEditorPanelPresentationKind; using XCEngine::UI::Editor::UIEditorPanelRegistry; using XCEngine::UI::Editor::UIEditorViewportInputBridgeFrame; +using XCEngine::UI::Editor::UIEditorWorkspaceCommandStatus; using XCEngine::UI::Editor::UIEditorWorkspaceController; using XCEngine::UI::Editor::UIEditorWorkspaceInteractionFrame; using XCEngine::UI::Editor::UIEditorWorkspaceInteractionModel; using XCEngine::UI::Editor::UIEditorWorkspaceInteractionResult; using XCEngine::UI::Editor::UIEditorWorkspaceInteractionState; +using XCEngine::UI::Editor::UIEditorWorkspaceLayoutOperationStatus; using XCEngine::UI::Editor::UIEditorWorkspaceModel; using XCEngine::UI::Editor::UIEditorWorkspacePanelPresentationModel; using XCEngine::UI::Editor::UIEditorWorkspaceSplitAxis; @@ -56,18 +66,12 @@ using XCEngine::UI::Editor::UpdateUIEditorWorkspaceInteraction; using XCEngine::UI::Editor::Widgets::UIEditorDockHostHitTarget; using XCEngine::UI::Editor::Widgets::UIEditorDockHostHitTargetKind; -constexpr const wchar_t* kWindowClassName = L"XCUIEditorWorkspaceInteractionBasicValidation"; -constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Workspace Interaction"; +const auto kShellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); +const auto kShellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); + +constexpr const wchar_t* kWindowClassName = L"XCUIEditorWorkspaceInteractionBasicValidation"; +constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Workspace Interaction Basic"; -constexpr UIColor kWindowBg(0.11f, 0.11f, 0.11f, 1.0f); -constexpr UIColor kCardBg(0.17f, 0.17f, 0.17f, 1.0f); -constexpr UIColor kCardBorder(0.28f, 0.28f, 0.28f, 1.0f); -constexpr UIColor kTextPrimary(0.94f, 0.94f, 0.94f, 1.0f); -constexpr UIColor kTextMuted(0.72f, 0.72f, 0.72f, 1.0f); -constexpr UIColor kTextWeak(0.56f, 0.56f, 0.56f, 1.0f); -constexpr UIColor kButtonBg(0.24f, 0.24f, 0.24f, 1.0f); -constexpr UIColor kButtonHover(0.32f, 0.32f, 0.32f, 1.0f); -constexpr UIColor kButtonBorder(0.47f, 0.47f, 0.47f, 1.0f); constexpr UIColor kSuccess(0.48f, 0.72f, 0.52f, 1.0f); constexpr UIColor kWarning(0.82f, 0.67f, 0.35f, 1.0f); @@ -91,6 +95,23 @@ std::filesystem::path ResolveRepoRootPath() { return std::filesystem::path(root).lexically_normal(); } +bool IsAutoCaptureOnStartupEnabled() { + const char* value = std::getenv("XCUI_AUTO_CAPTURE_ON_STARTUP"); + if (value == nullptr || value[0] == '\0') { + return false; + } + + std::string normalized = value; + for (char& character : normalized) { + character = static_cast(std::tolower(static_cast(character))); + } + + return normalized != "0" && + normalized != "false" && + normalized != "off" && + normalized != "no"; +} + bool ContainsPoint(const UIRect& rect, float x, float y) { return x >= rect.x && x <= rect.x + rect.width && @@ -112,14 +133,22 @@ std::string FormatFloat(float value, int precision = 2) { std::string DescribeHitTarget(const UIEditorDockHostHitTarget& target) { switch (target.kind) { - case UIEditorDockHostHitTargetKind::SplitterHandle: return "Splitter: " + target.nodeId; - case UIEditorDockHostHitTargetKind::TabStripBackground: return "TabStripBackground: " + target.nodeId; - case UIEditorDockHostHitTargetKind::Tab: return "Tab: " + target.panelId; - case UIEditorDockHostHitTargetKind::TabCloseButton: return "TabClose: " + target.panelId; - case UIEditorDockHostHitTargetKind::PanelHeader: return "PanelHeader: " + target.panelId; - case UIEditorDockHostHitTargetKind::PanelBody: return "PanelBody: " + target.panelId; - case UIEditorDockHostHitTargetKind::PanelFooter: return "PanelFooter: " + target.panelId; - case UIEditorDockHostHitTargetKind::PanelCloseButton: return "PanelClose: " + target.panelId; + case UIEditorDockHostHitTargetKind::SplitterHandle: + return "分割条: " + target.nodeId; + case UIEditorDockHostHitTargetKind::TabStripBackground: + return "标签栏背景: " + target.nodeId; + case UIEditorDockHostHitTargetKind::Tab: + return "标签: " + target.panelId; + case UIEditorDockHostHitTargetKind::TabCloseButton: + return "标签关闭: " + target.panelId; + case UIEditorDockHostHitTargetKind::PanelHeader: + return "面板标题: " + target.panelId; + case UIEditorDockHostHitTargetKind::PanelBody: + return "面板内容: " + target.panelId; + case UIEditorDockHostHitTargetKind::PanelFooter: + return "面板底栏: " + target.panelId; + case UIEditorDockHostHitTargetKind::PanelCloseButton: + return "面板关闭: " + target.panelId; case UIEditorDockHostHitTargetKind::None: default: return "None"; @@ -146,52 +175,85 @@ std::string JoinVisiblePanelIds(const UIEditorWorkspaceController& controller) { std::string DescribeViewportEvent(const UIEditorViewportInputBridgeFrame& frame) { if (frame.captureStarted) { - return "Viewport CaptureStarted"; + return "视口开始捕获"; } if (frame.captureEnded) { - return "Viewport CaptureEnded"; + return "视口结束捕获"; } if (frame.focusGained) { - return "Viewport FocusGained"; + return "视口获得焦点"; } if (frame.focusLost) { - return "Viewport FocusLost"; + return "视口失去焦点"; } if (frame.pointerPressedInside) { - return "Viewport PointerDownInside"; + return "视口内按下"; } if (frame.pointerReleasedInside) { - return "Viewport PointerUpInside"; + return "视口内抬起"; } if (frame.pointerMoved) { - return "Viewport PointerMove"; + return "视口指针移动"; } if (frame.wheelDelta != 0.0f) { - return "Viewport Wheel " + FormatFloat(frame.wheelDelta); + return "视口滚轮 " + FormatFloat(frame.wheelDelta); } - return "Viewport Input"; + return "视口输入"; } -void DrawCard( - UIDrawList& drawList, - const UIRect& rect, - std::string_view title, - std::string_view subtitle = {}) { - drawList.AddFilledRect(rect, kCardBg, 10.0f); - drawList.AddRectOutline(rect, kCardBorder, 1.0f, 10.0f); - drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 14.0f), std::string(title), kTextPrimary, 17.0f); - if (!subtitle.empty()) { - drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 38.0f), std::string(subtitle), kTextMuted, 12.0f); +std::string DescribeValidationHitTarget(const UIEditorDockHostHitTarget& target) { + switch (target.kind) { + case UIEditorDockHostHitTargetKind::SplitterHandle: + return "Splitter: " + target.nodeId; + case UIEditorDockHostHitTargetKind::TabStripBackground: + return "Tab strip background: " + target.nodeId; + case UIEditorDockHostHitTargetKind::Tab: + return "Tab: " + target.panelId; + case UIEditorDockHostHitTargetKind::TabCloseButton: + return "Tab close: " + target.panelId; + case UIEditorDockHostHitTargetKind::PanelHeader: + return "Panel header: " + target.panelId; + case UIEditorDockHostHitTargetKind::PanelBody: + return "Panel body: " + target.panelId; + case UIEditorDockHostHitTargetKind::PanelFooter: + return "Panel footer: " + target.panelId; + case UIEditorDockHostHitTargetKind::PanelCloseButton: + return "Panel close: " + target.panelId; + case UIEditorDockHostHitTargetKind::None: + default: + return "None"; } } -void DrawButton(UIDrawList& drawList, const ButtonState& button) { - drawList.AddFilledRect(button.rect, button.hovered ? kButtonHover : kButtonBg, 8.0f); - drawList.AddRectOutline(button.rect, kButtonBorder, 1.0f, 8.0f); - drawList.AddText(UIPoint(button.rect.x + 14.0f, button.rect.y + 10.0f), button.label, kTextPrimary, 12.0f); +std::string DescribeValidationViewportEvent(const UIEditorViewportInputBridgeFrame& frame) { + if (frame.captureStarted) { + return "Viewport capture started"; + } + if (frame.captureEnded) { + return "Viewport capture ended"; + } + if (frame.focusGained) { + return "Viewport focus gained"; + } + if (frame.focusLost) { + return "Viewport focus lost"; + } + if (frame.pointerPressedInside) { + return "Viewport pointer down"; + } + if (frame.pointerReleasedInside) { + return "Viewport pointer up"; + } + if (frame.pointerMoved) { + return "Viewport pointer move"; + } + if (frame.wheelDelta != 0.0f) { + return "Viewport wheel " + FormatFloat(frame.wheelDelta); + } + return "Viewport input"; } -UIEditorPanelRegistry BuildPanelRegistry() { +UIEditorPanelRegistry BuildValidationPanelRegistry() { UIEditorPanelRegistry registry = {}; registry.panels = { { "viewport", "Viewport", UIEditorPanelPresentationKind::ViewportShell, false, true, true }, @@ -201,7 +263,7 @@ UIEditorPanelRegistry BuildPanelRegistry() { return registry; } -UIEditorWorkspaceModel BuildWorkspace() { +UIEditorWorkspaceModel BuildValidationWorkspace() { UIEditorWorkspaceModel workspace = {}; workspace.root = BuildUIEditorWorkspaceSplit( "root-split", @@ -219,17 +281,99 @@ UIEditorWorkspaceModel BuildWorkspace() { return workspace; } -UIEditorWorkspaceInteractionModel BuildInteractionModel() { +UIEditorWorkspaceInteractionModel BuildValidationInteractionModel() { UIEditorWorkspaceInteractionModel model = {}; UIEditorWorkspacePanelPresentationModel presentation = {}; presentation.panelId = "viewport"; presentation.kind = UIEditorPanelPresentationKind::ViewportShell; presentation.viewportShellModel.spec.chrome.title = "Viewport"; - presentation.viewportShellModel.spec.chrome.subtitle = "Workspace Interaction"; + presentation.viewportShellModel.spec.chrome.subtitle = "Workspace interaction contract"; presentation.viewportShellModel.spec.chrome.showTopBar = true; presentation.viewportShellModel.spec.chrome.showBottomBar = true; presentation.viewportShellModel.frame.statusText = - "这里只验证 WorkspaceInteraction contract,不接 Scene/Game 业务。"; + "Check splitter drag, tab switch, panel activation, and viewport input."; + model.workspacePresentations = { presentation }; + return model; +} + +void DrawCard( + UIDrawList& drawList, + const UIRect& rect, + std::string_view title, + std::string_view subtitle = {}) { + drawList.AddFilledRect(rect, kShellPalette.cardBackground, kShellMetrics.cardRadius); + drawList.AddRectOutline(rect, kShellPalette.cardBorder, 1.0f, kShellMetrics.cardRadius); + drawList.AddText( + UIPoint(rect.x + 16.0f, rect.y + 14.0f), + std::string(title), + kShellPalette.textPrimary, + kShellMetrics.titleFontSize); + if (!subtitle.empty()) { + drawList.AddText( + UIPoint(rect.x + 16.0f, rect.y + 38.0f), + std::string(subtitle), + kShellPalette.textMuted, + kShellMetrics.bodyFontSize); + } +} + +void DrawButton(UIDrawList& drawList, const ButtonState& button) { + drawList.AddFilledRect( + button.rect, + button.hovered ? kShellPalette.buttonHoverBackground : kShellPalette.buttonBackground, + kShellMetrics.buttonRadius); + drawList.AddRectOutline( + button.rect, + kShellPalette.cardBorder, + 1.0f, + kShellMetrics.buttonRadius); + drawList.AddText( + UIPoint(button.rect.x + 14.0f, button.rect.y + 10.0f), + button.label, + kShellPalette.textPrimary, + kShellMetrics.bodyFontSize); +} + +UIEditorPanelRegistry BuildPanelRegistry() { + UIEditorPanelRegistry registry = {}; + registry.panels = { + { "viewport", "视口", UIEditorPanelPresentationKind::ViewportShell, false, true, true }, + { "doc", "文档", UIEditorPanelPresentationKind::Placeholder, true, true, true }, + { "details", "详情", UIEditorPanelPresentationKind::Placeholder, true, true, true } + }; + return registry; +} + +UIEditorWorkspaceModel BuildWorkspace() { + UIEditorWorkspaceModel workspace = {}; + workspace.root = BuildUIEditorWorkspaceSplit( + "root-split", + UIEditorWorkspaceSplitAxis::Horizontal, + 0.7f, + BuildUIEditorWorkspaceTabStack( + "tab-stack", + { + BuildUIEditorWorkspacePanel("viewport-node", "viewport", "视口"), + BuildUIEditorWorkspacePanel("doc-node", "doc", "文档", true) + }, + 0u), + BuildUIEditorWorkspacePanel("details-node", "details", "详情", true)); + workspace.activePanelId = "viewport"; + return workspace; +} + +UIEditorWorkspaceInteractionModel BuildInteractionModel() { + UIEditorWorkspaceInteractionModel model = {}; + UIEditorWorkspacePanelPresentationModel presentation = {}; + presentation.panelId = "viewport"; + presentation.kind = UIEditorPanelPresentationKind::ViewportShell; + presentation.viewportShellModel.spec.chrome.title = "视口"; + presentation.viewportShellModel.spec.chrome.subtitle = + "验证 Workspace 与 Viewport 的统一交互"; + presentation.viewportShellModel.spec.chrome.showTopBar = true; + presentation.viewportShellModel.spec.chrome.showBottomBar = true; + presentation.viewportShellModel.frame.statusText = + "验证分割、标签切换、面板激活与视口输入桥接"; model.workspacePresentations = { presentation }; return model; } @@ -271,7 +415,9 @@ private: switch (message) { case WM_SIZE: if (app != nullptr && wParam != SIZE_MINIMIZED) { - app->m_renderer.Resize(static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))); + app->m_renderer.Resize( + static_cast(LOWORD(lParam)), + static_cast(HIWORD(lParam))); } return 0; case WM_PAINT: @@ -373,7 +519,8 @@ private: bool Initialize(HINSTANCE hInstance, int nCmdShow) { m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/workspace_interaction_basic/captures"; + ResolveRepoRootPath() / + "tests/UI/Editor/integration/shell/workspace_interaction_basic/captures"; m_autoScreenshot.Initialize(m_captureRoot); WNDCLASSEXW windowClass = {}; @@ -411,6 +558,11 @@ private: } ResetScenario(); + if (IsAutoCaptureOnStartupEnabled()) { + m_lastStatus = "Capture"; + m_lastMessage = "Startup capture queued. Verify the first frame before interacting."; + m_lastColor = kWarning; + } return true; } @@ -432,13 +584,15 @@ private: if (GetCapture() == m_hwnd) { ReleaseCapture(); } - m_controller = BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); + m_controller = BuildDefaultUIEditorWorkspaceController( + BuildValidationPanelRegistry(), + BuildValidationWorkspace()); m_interactionState = {}; - m_interactionModel = BuildInteractionModel(); + m_interactionModel = BuildValidationInteractionModel(); m_cachedFrame = {}; m_pendingInputEvents.clear(); m_lastStatus = "Ready"; - m_lastMessage = "等待交互。这里只验证 WorkspaceInteraction 统一输入路由 contract。"; + m_lastMessage = "Use the workspace on the right to exercise splitter drag, tab switch, and viewport input. Set XCUI_AUTO_CAPTURE_ON_STARTUP=1 to capture the startup frame."; m_lastColor = kWarning; } @@ -468,8 +622,8 @@ private: const float left = m_controlsRect.x + 16.0f; const float top = m_controlsRect.y + 62.0f; m_buttons = { - { ActionId::Reset, "重置", UIRect(left, top, buttonWidth, 36.0f), false }, - { ActionId::Capture, "截图(F12)", UIRect(left + buttonWidth + 12.0f, top, buttonWidth, 36.0f), false } + { ActionId::Reset, "Reset", UIRect(left, top, buttonWidth, 36.0f), false }, + { ActionId::Capture, "Capture (F12)", UIRect(left + buttonWidth + 12.0f, top, buttonWidth, 36.0f), false } }; } @@ -513,14 +667,14 @@ private: if (action == ActionId::Reset) { ResetScenario(); m_lastStatus = "Ready"; - m_lastMessage = "场景状态已重置。请重新检查 viewport body / tab switch / splitter drag。"; + m_lastMessage = "Scenario reset. Re-check splitter drag, tab switch, and viewport input."; m_lastColor = kWarning; return; } m_autoScreenshot.RequestCapture("manual_button"); - m_lastStatus = "Ready"; - m_lastMessage = "截图已排队,输出到 tests/UI/Editor/integration/shell/workspace_interaction_basic/captures/。"; + m_lastStatus = "Capture"; + m_lastMessage = "Capture queued. Check the Output/latest.png path shown below."; m_lastColor = kWarning; } @@ -556,16 +710,16 @@ private: void SetInteractionResult(const UIEditorWorkspaceInteractionResult& result) { if (result.dockHostResult.layoutResult.status != - XCEngine::UI::Editor::UIEditorWorkspaceLayoutOperationStatus::Rejected) { - m_lastStatus = "DockHostLayout"; + UIEditorWorkspaceLayoutOperationStatus::Rejected) { + m_lastStatus = "Layout"; m_lastMessage = result.dockHostResult.layoutResult.message; m_lastColor = kSuccess; return; } if (result.dockHostResult.commandResult.status != - XCEngine::UI::Editor::UIEditorWorkspaceCommandStatus::Rejected) { - m_lastStatus = "DockHostCommand"; + UIEditorWorkspaceCommandStatus::Rejected) { + m_lastStatus = "Command"; m_lastMessage = result.dockHostResult.commandResult.message; m_lastColor = kSuccess; return; @@ -573,7 +727,7 @@ private: if (!result.viewportPanelId.empty()) { m_lastStatus = result.viewportPanelId; - m_lastMessage = DescribeViewportEvent(result.viewportInputFrame); + m_lastMessage = DescribeValidationViewportEvent(result.viewportInputFrame); m_lastColor = result.viewportInputFrame.captureStarted || result.viewportInputFrame.focusGained ? kSuccess @@ -583,33 +737,165 @@ private: if (result.requestPointerCapture) { m_lastStatus = "Capture"; - m_lastMessage = "宿主已收到 WorkspaceInteraction 的 pointer capture 请求。"; + m_lastMessage = "Workspace interaction requested host pointer capture."; m_lastColor = kSuccess; return; } if (result.releasePointerCapture) { m_lastStatus = "Release"; - m_lastMessage = "宿主已执行 WorkspaceInteraction 的 pointer release。"; + m_lastMessage = "Workspace interaction released host pointer capture."; m_lastColor = kWarning; return; } if (result.consumed) { m_lastStatus = "Consumed"; - m_lastMessage = "这次输入被 WorkspaceInteraction 层消费。"; + m_lastMessage = "The workspace consumed the input, but no higher-level state changed."; m_lastColor = kWarning; } } + void RenderValidationUI(float width, float height) { + const auto* viewportFrame = + FindUIEditorWorkspaceViewportPresentationFrame(m_cachedFrame.composeFrame, "viewport"); + const std::string selectedPresentation = + viewportFrame != nullptr ? "ViewportShell" : "Placeholder"; + + UIDrawData drawData = {}; + UIDrawList& drawList = drawData.EmplaceDrawList("WorkspaceInteractionBasic"); + drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kShellPalette.windowBackground); + + DrawCard( + drawList, + m_introRect, + "What This Validates", + "Verify that workspace interaction and viewport input share one stable contract."); + drawList.AddText( + UIPoint(m_introRect.x + 16.0f, m_introRect.y + 72.0f), + "1. Splitter drag should update the right-side layout ratio immediately.", + kShellPalette.textPrimary, + 12.0f); + drawList.AddText( + UIPoint(m_introRect.x + 16.0f, m_introRect.y + 94.0f), + "2. Clicking a tab, panel, or viewport should surface the hit target and result on the left.", + kShellPalette.textPrimary, + 12.0f); + drawList.AddText( + UIPoint(m_introRect.x + 16.0f, m_introRect.y + 116.0f), + "3. Viewport hover, focus, and capture changes should be reflected in the state card.", + kShellPalette.textPrimary, + 12.0f); + drawList.AddText( + UIPoint(m_introRect.x + 16.0f, m_introRect.y + 138.0f), + "4. This scene validates editor interaction plumbing only, not business-specific panels.", + kShellPalette.textPrimary, + 12.0f); + drawList.AddText( + UIPoint(m_introRect.x + 16.0f, m_introRect.y + 162.0f), + "Use XCUI_AUTO_CAPTURE_ON_STARTUP=1 for a startup shot; otherwise press F12 after exercising the scene.", + kShellPalette.textWeak, + 11.0f); + + DrawCard(drawList, m_controlsRect, "Actions", "Only reset and capture are exposed here."); + for (const ButtonState& button : m_buttons) { + DrawButton(drawList, button); + } + + DrawCard(drawList, m_stateRect, "State", "Observe workspace interaction and viewport input in real time."); + float stateY = m_stateRect.y + 66.0f; + auto addStateLine = [&](std::string text, + const UIColor& color = kShellPalette.textPrimary, + float fontSize = 12.0f) { + drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY), std::move(text), color, fontSize); + stateY += 20.0f; + }; + + addStateLine( + "Hit target: " + + DescribeValidationHitTarget(m_interactionState.dockHostInteractionState.dockHostState.hoveredTarget), + kShellPalette.textPrimary, + 11.0f); + addStateLine("Presentation: " + selectedPresentation, kShellPalette.textPrimary); + addStateLine( + "Active panel: " + + (m_controller.GetWorkspace().activePanelId.empty() + ? std::string("(none)") + : m_controller.GetWorkspace().activePanelId)); + addStateLine("Selected tab: " + GetSelectedTabId(), kShellPalette.textPrimary); + addStateLine("Visible panels: " + JoinVisiblePanelIds(m_controller), kShellPalette.textWeak, 11.0f); + addStateLine("Result: " + m_lastStatus, m_lastColor); + drawList.AddText( + UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f), + m_lastMessage, + kShellPalette.textMuted, + 11.0f); + stateY += 34.0f; + addStateLine( + "Host capture: " + FormatBool(GetCapture() == m_hwnd), + GetCapture() == m_hwnd ? kSuccess : kShellPalette.textMuted); + addStateLine( + "Root split ratio: " + FormatFloat(m_controller.GetWorkspace().root.splitRatio), + kShellPalette.textWeak, + 11.0f); + if (viewportFrame != nullptr) { + addStateLine( + "Viewport hover: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.hovered), + kShellPalette.textWeak, + 11.0f); + addStateLine( + "Viewport focus: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.focused), + kShellPalette.textWeak, + 11.0f); + addStateLine( + "Viewport capture: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.captured), + kShellPalette.textWeak, + 11.0f); + } + addStateLine( + "Capture: " + + (m_autoScreenshot.HasPendingCapture() + ? std::string("capture queued...") + : (m_autoScreenshot.GetLastCaptureSummary().empty() + ? std::string("Set XCUI_AUTO_CAPTURE_ON_STARTUP=1 or press F12.") + : m_autoScreenshot.GetLastCaptureSummary())), + kShellPalette.textWeak, + 11.0f); + + DrawCard(drawList, m_previewRect, "Preview", "The right side renders the real workspace interaction frame."); + const auto dockPalette = ResolveUIEditorDockHostPalette(); + const auto viewportPalette = ResolveUIEditorViewportSlotPalette(); + const auto dockMetrics = ResolveUIEditorDockHostMetrics(); + const auto viewportMetrics = ResolveUIEditorViewportSlotMetrics(); + AppendUIEditorWorkspaceCompose( + drawList, + m_cachedFrame.composeFrame, + dockPalette, + dockMetrics, + viewportPalette, + viewportMetrics); + + const bool framePresented = m_renderer.Render(drawData); + m_autoScreenshot.CaptureIfRequested( + m_renderer, + drawData, + static_cast(width), + static_cast(height), + framePresented); + } + void RenderFrame() { UpdateLayout(); + const auto dockMetrics = ResolveUIEditorDockHostMetrics(); + const auto viewportMetrics = ResolveUIEditorViewportSlotMetrics(); m_cachedFrame = UpdateUIEditorWorkspaceInteraction( m_interactionState, m_controller, m_workspaceRect, m_interactionModel, - m_pendingInputEvents); + m_pendingInputEvents, + dockMetrics, + viewportMetrics); m_pendingInputEvents.clear(); ApplyHostCaptureRequests(m_cachedFrame.result); SetInteractionResult(m_cachedFrame.result); @@ -618,63 +904,126 @@ private: FindUIEditorWorkspaceViewportPresentationFrame(m_cachedFrame.composeFrame, "viewport"); const bool viewportVisible = viewportFrame != nullptr; const std::string selectedPresentation = - viewportVisible ? "ViewportShell" : "DockHost Placeholder"; + viewportVisible ? "视口呈现中" : "停用或未生成占位"; RECT clientRect = {}; GetClientRect(m_hwnd, &clientRect); const float width = static_cast((std::max)(clientRect.right - clientRect.left, 1L)); const float height = static_cast((std::max)(clientRect.bottom - clientRect.top, 1L)); + RenderValidationUI(width, height); + return; + UIDrawData drawData = {}; UIDrawList& drawList = drawData.EmplaceDrawList("WorkspaceInteractionBasic"); - drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg); + drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kShellPalette.windowBackground); - DrawCard(drawList, m_introRect, "这个测试验证什么功能", "只验证 WorkspaceInteraction 统一路由 contract,不做 editor 业务。"); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 72.0f), "1. 验证 DockHost splitter drag 与 ViewportShell input 可以被同一层统一收口。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 94.0f), "2. 验证点击 viewport body 时,focus/capture 请求会直接冒泡给宿主。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 116.0f), "3. 验证点击 Document tab 后,body 会立即从 ViewportShell 切回 DockHost placeholder。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 138.0f), "4. 验证 splitter 改尺寸后,viewport body bounds 与 workspace layout 同步变化。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 162.0f), "建议操作: 先点中间 viewport,再切到 Document,最后拖右侧 splitter。", kTextWeak, 11.0f); + DrawCard( + drawList, + m_introRect, + "这个测试验证什么", + "验证 Workspace 基础交互与 Viewport 输入桥接是否统一。"); + drawList.AddText( + UIPoint(m_introRect.x + 16.0f, m_introRect.y + 72.0f), + "1. 拖拽分割条后,右侧工作区布局比例应实时变化。", + kShellPalette.textPrimary, + 12.0f); + drawList.AddText( + UIPoint(m_introRect.x + 16.0f, m_introRect.y + 94.0f), + "2. 点击标签、面板或视口时,左侧状态应显示命中目标与结果。", + kShellPalette.textPrimary, + 12.0f); + drawList.AddText( + UIPoint(m_introRect.x + 16.0f, m_introRect.y + 116.0f), + "3. 视口 hover、focus、capture 的变化应反馈到状态面板。", + kShellPalette.textPrimary, + 12.0f); + drawList.AddText( + UIPoint(m_introRect.x + 16.0f, m_introRect.y + 138.0f), + "4. 该场景只验证 Editor 基础交互,不验证具体业务面板。", + kShellPalette.textPrimary, + 12.0f); + drawList.AddText( + UIPoint(m_introRect.x + 16.0f, m_introRect.y + 162.0f), + "重点观察:命中目标、当前激活面板、宿主 capture 与视口输入状态。", + kShellPalette.textWeak, + 11.0f); - DrawCard(drawList, m_controlsRect, "操作", "这里只保留 Reset / Capture。"); + DrawCard(drawList, m_controlsRect, "操作", "这里只保留重置和截图两个辅助动作。"); for (const ButtonState& button : m_buttons) { DrawButton(drawList, button); } - DrawCard(drawList, m_stateRect, "状态", "重点检查 WorkspaceInteraction 当前状态。"); + DrawCard(drawList, m_stateRect, "状态", "观察当前工作区交互与视口输入状态。"); float stateY = m_stateRect.y + 66.0f; - auto addStateLine = [&](std::string text, const UIColor& color = kTextPrimary, float fontSize = 12.0f) { + auto addStateLine = [&](std::string text, + const UIColor& color = kShellPalette.textPrimary, + float fontSize = 12.0f) { drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY), std::move(text), color, fontSize); stateY += 20.0f; }; - addStateLine("DockHost Hover: " + DescribeHitTarget(m_interactionState.dockHostInteractionState.dockHostState.hoveredTarget), kTextPrimary, 11.0f); - addStateLine("Selected Presentation: " + selectedPresentation, kTextPrimary); - addStateLine("Active Panel: " + (m_controller.GetWorkspace().activePanelId.empty() ? std::string("(none)") : m_controller.GetWorkspace().activePanelId)); - addStateLine("Selected Tab: " + GetSelectedTabId(), kTextPrimary); - addStateLine("Visible Panels: " + JoinVisiblePanelIds(m_controller), kTextWeak, 11.0f); - addStateLine("Result: " + m_lastStatus, m_lastColor); - drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f), m_lastMessage, kTextMuted, 11.0f); + addStateLine( + "命中目标: " + + DescribeHitTarget(m_interactionState.dockHostInteractionState.dockHostState.hoveredTarget), + kShellPalette.textPrimary, + 11.0f); + addStateLine("当前呈现: " + selectedPresentation, kShellPalette.textPrimary); + addStateLine( + "当前激活: " + + (m_controller.GetWorkspace().activePanelId.empty() + ? std::string("(none)") + : m_controller.GetWorkspace().activePanelId)); + addStateLine("当前标签: " + GetSelectedTabId(), kShellPalette.textPrimary); + addStateLine("可见面板: " + JoinVisiblePanelIds(m_controller), kShellPalette.textWeak, 11.0f); + addStateLine("结果: " + m_lastStatus, m_lastColor); + drawList.AddText( + UIPoint(m_stateRect.x + 16.0f, stateY + 4.0f), + m_lastMessage, + kShellPalette.textMuted, + 11.0f); stateY += 34.0f; - addStateLine("Host Capture: " + FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kSuccess : kTextMuted); - addStateLine("root-split ratio: " + FormatFloat(m_controller.GetWorkspace().root.splitRatio), kTextWeak, 11.0f); + addStateLine( + "宿主 capture: " + FormatBool(GetCapture() == m_hwnd), + GetCapture() == m_hwnd ? kSuccess : kShellPalette.textMuted); + addStateLine( + "根分割比例: " + FormatFloat(m_controller.GetWorkspace().root.splitRatio), + kShellPalette.textWeak, + 11.0f); if (viewportFrame != nullptr) { - addStateLine("Viewport Hover: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.hovered), kTextWeak, 11.0f); - addStateLine("Viewport Focus: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.focused), kTextWeak, 11.0f); - addStateLine("Viewport Capture: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.captured), kTextWeak, 11.0f); + addStateLine( + "视口 hover: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.hovered), + kShellPalette.textWeak, + 11.0f); + addStateLine( + "视口 focus: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.focused), + kShellPalette.textWeak, + 11.0f); + addStateLine( + "视口 capture: " + FormatBool(viewportFrame->viewportShellFrame.inputFrame.captured), + kShellPalette.textWeak, + 11.0f); } addStateLine( "截图: " + (m_autoScreenshot.HasPendingCapture() - ? std::string("截图排队中...") - : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("F12 或 按钮 -> captures/") - : m_autoScreenshot.GetLastCaptureSummary())), - kTextWeak, + ? std::string("截图排队中...") + : (m_autoScreenshot.GetLastCaptureSummary().empty() + ? std::string("按 F12 或按钮截图 -> captures/") + : m_autoScreenshot.GetLastCaptureSummary())), + kShellPalette.textWeak, 11.0f); - DrawCard(drawList, m_previewRect, "Preview", "真实 WorkspaceInteraction 预览,不再在 exe 宿主里手写拼装输入路由。"); - AppendUIEditorWorkspaceCompose(drawList, m_cachedFrame.composeFrame); + DrawCard(drawList, m_previewRect, "预览", "右侧是真实的工作区交互预览。"); + const auto dockPalette = ResolveUIEditorDockHostPalette(); + const auto viewportPalette = ResolveUIEditorViewportSlotPalette(); + AppendUIEditorWorkspaceCompose( + drawList, + m_cachedFrame.composeFrame, + dockPalette, + dockMetrics, + viewportPalette, + viewportMetrics); const bool framePresented = m_renderer.Render(drawData); m_autoScreenshot.CaptureIfRequested( @@ -704,7 +1053,7 @@ private: bool m_trackingMouseLeave = false; std::string m_lastStatus = {}; std::string m_lastMessage = {}; - UIColor m_lastColor = kTextMuted; + UIColor m_lastColor = kShellPalette.textMuted; }; } // namespace diff --git a/tests/UI/Editor/integration/shell/workspace_shell_compose/View.xcui b/tests/UI/Editor/integration/shell/workspace_shell_compose/View.xcui index e58bc99a..366ecb77 100644 --- a/tests/UI/Editor/integration/shell/workspace_shell_compose/View.xcui +++ b/tests/UI/Editor/integration/shell/workspace_shell_compose/View.xcui @@ -1,6 +1,5 @@ + name="EditorWorkspaceShellComposeValidation"> -#include -#include -#include -#include +#include +#include +#include +#include +#include #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -513,7 +513,7 @@ private: m_resetPressed = false; if (resetTriggered) { ResetScenario(); - m_lastResult = "Reset"; + m_lastResult = "已重置"; InvalidateRect(m_hwnd, nullptr, FALSE); return; } @@ -555,22 +555,22 @@ private: } if (result.requestPointerCapture) { - m_lastResult = "Capture: begin splitter drag"; + m_lastResult = "捕获:开始拖动 splitter"; return; } if (result.releasePointerCapture) { - m_lastResult = "Capture: end splitter drag"; + m_lastResult = "捕获:结束拖动 splitter"; return; } if (result.hitTarget.kind != UIEditorDockHostHitTargetKind::None) { - m_lastResult = "Hover: " + DescribeHitTarget(result.hitTarget); + m_lastResult = "悬停: " + DescribeHitTarget(result.hitTarget); return; } if (result.consumed) { - m_lastResult = "Consumed: input handled by DockHostInteraction"; + m_lastResult = "已消费: 输入由 DockHostInteraction 处理"; } } @@ -597,7 +597,7 @@ private: DrawCard( drawList, m_introRect, - "这个测试在验证什么功能?", + "这个测试验证什么功能?", "验证 Workspace + DockHost 的组合场景是否完全收口到统一交互层。"); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 68.0f), @@ -625,12 +625,12 @@ private: m_stateRect, "状态回显", "这里直接展示 hover / focus / dragging / active panel / split ratio。"); - DrawButton(drawList, m_resetButtonRect, "Reset", m_resetHovered); + DrawButton(drawList, m_resetButtonRect, "重置", m_resetHovered); const auto validation = m_controller.ValidateState(); drawList.AddText( UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 70.0f), - "Hover: " + DescribeHitTarget(m_interactionState.dockHostState.hoveredTarget), + "悬停: " + DescribeHitTarget(m_interactionState.dockHostState.hoveredTarget), kTextPrimary, 13.0f); drawList.AddText( @@ -640,7 +640,7 @@ private: 13.0f); drawList.AddText( UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 122.0f), - "Capture: " + std::string(GetCapture() == m_hwnd ? "On" : "Off"), + "捕获: " + std::string(GetCapture() == m_hwnd ? "开启" : "关闭"), GetCapture() == m_hwnd ? kSuccess : kTextMuted, 13.0f); drawList.AddText( @@ -653,12 +653,12 @@ private: 13.0f); drawList.AddText( UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 174.0f), - "Active Panel: " + m_controller.GetWorkspace().activePanelId, + "当前激活面板: " + m_controller.GetWorkspace().activePanelId, kTextPrimary, 13.0f); drawList.AddText( UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 198.0f), - "Visible Panels: " + JoinVisiblePanelIds(m_controller), + "可见面板: " + JoinVisiblePanelIds(m_controller), kTextMuted, 12.0f); drawList.AddText( @@ -683,7 +683,7 @@ private: 12.0f); drawList.AddText( UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + 382.0f), - validation.IsValid() ? "Validation: OK" : "Validation: " + validation.message, + validation.IsValid() ? "验证: OK" : "验证: " + validation.message, validation.IsValid() ? kSuccess : kDanger, 12.0f); diff --git a/tests/UI/Editor/integration/shell/workspace_viewport_compose/main.cpp b/tests/UI/Editor/integration/shell/workspace_viewport_compose/main.cpp index 0c79e735..f3564224 100644 --- a/tests/UI/Editor/integration/shell/workspace_viewport_compose/main.cpp +++ b/tests/UI/Editor/integration/shell/workspace_viewport_compose/main.cpp @@ -2,13 +2,14 @@ #define NOMINMAX #endif -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include "../../shared/src/EditorValidationTheme.h" #include "Host/AutoScreenshot.h" -#include "Host/InputModifierTracker.h" #include "Host/NativeRenderer.h" #include @@ -17,11 +18,12 @@ #include #include +#include +#include #include #include #include #include -#include #include #ifndef XCENGINE_EDITOR_UI_TESTS_REPO_ROOT @@ -47,6 +49,10 @@ using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack; using XCEngine::UI::Editor::FindUIEditorWorkspaceViewportPresentationFrame; using XCEngine::UI::Editor::FindUIEditorWorkspaceViewportPresentationRequest; using XCEngine::UI::Editor::GetUIEditorWorkspaceCommandStatusName; +using XCEngine::UI::Editor::ResolveUIEditorDockHostMetrics; +using XCEngine::UI::Editor::ResolveUIEditorDockHostPalette; +using XCEngine::UI::Editor::ResolveUIEditorViewportSlotMetrics; +using XCEngine::UI::Editor::ResolveUIEditorViewportSlotPalette; using XCEngine::UI::Editor::ResolveUIEditorWorkspaceComposeRequest; using XCEngine::UI::Editor::UIEditorPanelPresentationKind; using XCEngine::UI::Editor::UIEditorPanelRegistry; @@ -63,7 +69,6 @@ using XCEngine::UI::Editor::UIEditorWorkspacePanelPresentationModel; using XCEngine::UI::Editor::UIEditorWorkspaceSplitAxis; using XCEngine::UI::Editor::UpdateUIEditorWorkspaceCompose; using XCEngine::UI::Editor::Host::AutoScreenshotController; -using XCEngine::UI::Editor::Host::InputModifierTracker; using XCEngine::UI::Editor::Host::NativeRenderer; using XCEngine::UI::Editor::Widgets::HitTestUIEditorViewportSlot; using XCEngine::UI::Editor::Widgets::UIEditorStatusBarSegment; @@ -73,31 +78,15 @@ using XCEngine::UI::Editor::Widgets::UIEditorViewportSlotHitTargetKind; using XCEngine::UI::Editor::Widgets::UIEditorViewportSlotToolItem; using XCEngine::UI::Editor::Widgets::UIEditorViewportSlotToolSlot; -constexpr const wchar_t* kWindowClassName = L"XCUIEditorWorkspaceViewportComposeValidation"; -constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Workspace Viewport Compose"; +const auto kShellPalette = XCEngine::Tests::EditorUI::GetEditorValidationShellPalette(); +const auto kShellMetrics = XCEngine::Tests::EditorUI::GetEditorValidationShellMetrics(); -constexpr UIColor kWindowBg(0.11f, 0.11f, 0.11f, 1.0f); -constexpr UIColor kCardBg(0.17f, 0.17f, 0.17f, 1.0f); -constexpr UIColor kCardBorder(0.28f, 0.28f, 0.28f, 1.0f); -constexpr UIColor kPreviewBg(0.14f, 0.14f, 0.14f, 1.0f); -constexpr UIColor kTextPrimary(0.94f, 0.94f, 0.94f, 1.0f); -constexpr UIColor kTextMuted(0.72f, 0.72f, 0.72f, 1.0f); -constexpr UIColor kTextWeak(0.56f, 0.56f, 0.56f, 1.0f); -constexpr UIColor kButtonBg(0.24f, 0.24f, 0.24f, 1.0f); -constexpr UIColor kButtonOnBg(0.36f, 0.36f, 0.36f, 1.0f); -constexpr UIColor kButtonBorder(0.46f, 0.46f, 0.46f, 1.0f); +constexpr const wchar_t* kWindowClassName = L"XCUIEditorWorkspaceViewportComposeValidation"; +constexpr const wchar_t* kWindowTitle = L"XCUI Editor | WorkspaceViewportCompose"; constexpr UIColor kSuccess(0.46f, 0.72f, 0.50f, 1.0f); constexpr UIColor kDanger(0.78f, 0.35f, 0.35f, 1.0f); -enum class ActionId : unsigned char { - ActivateScene = 0, - ActivateDocument, - ToggleTopBar, - ToggleBottomBar, - ToggleTexture, - Reset, - Capture -}; +enum class ActionId : unsigned char { ActivateScene, ActivateDocument, ToggleTopBar, ToggleBottomBar, ToggleTexture, Reset, Capture }; struct ButtonState { ActionId action = ActionId::ActivateScene; @@ -106,92 +95,37 @@ struct ButtonState { bool selected = false; }; -std::filesystem::path ResolveRepoRootPath(); -bool ContainsPoint(const UIRect& rect, float x, float y); -std::string BoolText(bool value); -std::string FormatFloat(float value); -std::string FormatSize(const UISize& size); -std::string FormatRect(const UIRect& rect); -std::string DescribeHitTarget(const UIEditorViewportSlotHitTarget& hit); -void DrawCard(UIDrawList& drawList, const UIRect& rect, std::string_view title, std::string_view subtitle = {}); -void DrawButton(UIDrawList& drawList, const ButtonState& button); -UIEditorPanelRegistry BuildPanelRegistry(); -UIEditorWorkspaceModel BuildWorkspace(); - -class ScenarioApp { -public: - int Run(HINSTANCE hInstance, int nCmdShow); - -private: - static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); - - bool Initialize(HINSTANCE hInstance, int nCmdShow); - void Shutdown(); - void ResetScenario(); - void UpdateLayoutForCurrentWindow(); - std::vector BuildPresentationModels() const; - void QueuePointerEvent(UIInputEventType type, UIPointerButton button, WPARAM wParam, LPARAM lParam); - void QueuePointerLeaveEvent(); - void QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam); - void QueueFocusEvent(UIInputEventType type); - void HandleMouseMove(WPARAM wParam, LPARAM lParam); - void HandlePointerDown(UIPointerButton button, WPARAM wParam, LPARAM lParam); - void HandlePointerUp(UIPointerButton button, WPARAM wParam, LPARAM lParam); - void ExecuteAction(ActionId action); - void DispatchWorkspaceCommand(UIEditorWorkspaceCommandKind kind, std::string_view panelId, std::string_view label); - std::string GetSelectedTabPanelId() const; - void UpdateComposeFrame(); - void UpdateLastResult(const XCEngine::UI::Editor::UIEditorWorkspaceViewportComposeFrame* viewportFrame); - void RenderFrame(); - - HWND m_hwnd = nullptr; - ATOM m_windowClassAtom = 0; - NativeRenderer m_renderer = {}; - AutoScreenshotController m_autoScreenshot = {}; - InputModifierTracker m_inputModifierTracker = {}; - std::filesystem::path m_captureRoot = {}; - UIEditorWorkspaceController m_controller = {}; - UIEditorWorkspaceComposeState m_composeState = {}; - UIEditorWorkspaceComposeRequest m_composeRequest = {}; - UIEditorWorkspaceComposeFrame m_composeFrame = {}; - std::vector m_pendingInputEvents = {}; - std::vector m_buttons = {}; - UIRect m_introRect = {}; - UIRect m_controlsRect = {}; - UIRect m_stateRect = {}; - UIRect m_previewRect = {}; - UIRect m_workspaceRect = {}; - UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f); - UIEditorViewportSlotHitTarget m_hoverHit = {}; - bool m_showTopBar = true; - bool m_showBottomBar = true; - bool m_textureEnabled = true; - bool m_previousHovered = false; - bool m_previousFocused = false; - bool m_previousCaptured = false; - bool m_previousViewportVisible = false; - bool m_hasSnapshot = false; - std::string m_lastResult = {}; -}; - std::filesystem::path ResolveRepoRootPath() { std::string root = XCENGINE_EDITOR_UI_TESTS_REPO_ROOT; if (root.size() >= 2u && root.front() == '"' && root.back() == '"') { root = root.substr(1u, root.size() - 2u); } - return std::filesystem::path(root).lexically_normal(); } +bool IsAutoCaptureOnStartupEnabled() { + const char* value = std::getenv("XCUI_AUTO_CAPTURE_ON_STARTUP"); + if (value == nullptr || value[0] == '\0') { + return false; + } + + std::string normalized = value; + for (char& character : normalized) { + character = static_cast(std::tolower(static_cast(character))); + } + + return normalized != "0" && + normalized != "false" && + normalized != "off" && + normalized != "no"; +} + bool ContainsPoint(const UIRect& rect, float x, float y) { - return x >= rect.x && - x <= rect.x + rect.width && - y >= rect.y && - y <= rect.y + rect.height; + return x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height; } std::string BoolText(bool value) { - return value ? "On" : "Off"; + return value ? "是" : "否"; } std::string FormatFloat(float value) { @@ -202,74 +136,47 @@ std::string FormatFloat(float value) { return stream.str(); } +std::string FormatRect(const UIRect& rect) { + return "x=" + FormatFloat(rect.x) + " y=" + FormatFloat(rect.y) + " w=" + FormatFloat(rect.width) + " h=" + FormatFloat(rect.height); +} + std::string FormatSize(const UISize& size) { return FormatFloat(size.width) + " x " + FormatFloat(size.height); } -std::string FormatRect(const UIRect& rect) { - return "x=" + FormatFloat(rect.x) + - " y=" + FormatFloat(rect.y) + - " w=" + FormatFloat(rect.width) + - " h=" + FormatFloat(rect.height); -} - std::string DescribeHitTarget(const UIEditorViewportSlotHitTarget& hit) { switch (hit.kind) { - case UIEditorViewportSlotHitTargetKind::TopBar: - return "TopBar"; - case UIEditorViewportSlotHitTargetKind::Title: - return "Title"; - case UIEditorViewportSlotHitTargetKind::ToolItem: - return "ToolItem[" + std::to_string(hit.index) + "]"; - case UIEditorViewportSlotHitTargetKind::Surface: - return "Surface"; - case UIEditorViewportSlotHitTargetKind::BottomBar: - return "BottomBar"; - case UIEditorViewportSlotHitTargetKind::StatusSegment: - return "StatusSegment[" + std::to_string(hit.index) + "]"; - case UIEditorViewportSlotHitTargetKind::StatusSeparator: - return "StatusSeparator[" + std::to_string(hit.index) + "]"; + 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::StatusSegment: return "状态段[" + std::to_string(hit.index) + "]"; + case UIEditorViewportSlotHitTargetKind::StatusSeparator: return "状态分隔线[" + std::to_string(hit.index) + "]"; case UIEditorViewportSlotHitTargetKind::None: - default: - return "None"; + default: return "无"; } } -void DrawCard( - UIDrawList& drawList, - const UIRect& rect, - std::string_view title, - std::string_view subtitle) { - drawList.AddFilledRect(rect, kCardBg, 10.0f); - drawList.AddRectOutline(rect, kCardBorder, 1.0f, 10.0f); - drawList.AddText( - UIPoint(rect.x + 16.0f, rect.y + 14.0f), - std::string(title), - kTextPrimary, - 17.0f); - if (!subtitle.empty()) { - drawList.AddText( - UIPoint(rect.x + 16.0f, rect.y + 38.0f), - std::string(subtitle), - kTextMuted, - 12.0f); +std::string BoolTextAscii(bool value) { + return value ? "on" : "off"; +} + +std::string DescribeValidationHitTarget(const UIEditorViewportSlotHitTarget& hit) { + switch (hit.kind) { + case UIEditorViewportSlotHitTargetKind::TopBar: return "Top bar"; + case UIEditorViewportSlotHitTargetKind::Title: return "Title"; + case UIEditorViewportSlotHitTargetKind::ToolItem: return "Tool item[" + std::to_string(hit.index) + "]"; + case UIEditorViewportSlotHitTargetKind::Surface: return "Viewport surface"; + case UIEditorViewportSlotHitTargetKind::BottomBar: return "Bottom bar"; + case UIEditorViewportSlotHitTargetKind::StatusSegment: return "Status segment[" + std::to_string(hit.index) + "]"; + case UIEditorViewportSlotHitTargetKind::StatusSeparator: return "Status separator[" + std::to_string(hit.index) + "]"; + case UIEditorViewportSlotHitTargetKind::None: + default: return "None"; } } -void DrawButton(UIDrawList& drawList, const ButtonState& button) { - drawList.AddFilledRect( - button.rect, - button.selected ? kButtonOnBg : kButtonBg, - 8.0f); - drawList.AddRectOutline(button.rect, kButtonBorder, 1.0f, 8.0f); - drawList.AddText( - UIPoint(button.rect.x + 12.0f, button.rect.y + 10.0f), - button.label, - kTextPrimary, - 12.0f); -} - -UIEditorPanelRegistry BuildPanelRegistry() { +UIEditorPanelRegistry BuildValidationPanelRegistry() { UIEditorPanelRegistry registry = {}; registry.panels = { { "hierarchy", "Hierarchy", UIEditorPanelPresentationKind::Placeholder, true, true, false }, @@ -280,15 +187,15 @@ UIEditorPanelRegistry BuildPanelRegistry() { return registry; } -UIEditorWorkspaceModel BuildWorkspace() { +UIEditorWorkspaceModel BuildValidationWorkspace() { UIEditorWorkspaceModel workspace = {}; workspace.root = BuildUIEditorWorkspaceSplit( - "root-left-main", + "root", UIEditorWorkspaceSplitAxis::Horizontal, 0.23f, BuildUIEditorWorkspacePanel("hierarchy-node", "hierarchy", "Hierarchy", true), BuildUIEditorWorkspaceSplit( - "main-center-right", + "center-right", UIEditorWorkspaceSplitAxis::Horizontal, 0.72f, BuildUIEditorWorkspaceTabStack( @@ -303,12 +210,102 @@ UIEditorWorkspaceModel BuildWorkspace() { return workspace; } +void DrawCard(UIDrawList& drawList, const UIRect& rect, std::string_view title, std::string_view subtitle = {}) { + drawList.AddFilledRect(rect, kShellPalette.cardBackground, kShellMetrics.cardRadius); + drawList.AddRectOutline(rect, kShellPalette.cardBorder, 1.0f, kShellMetrics.cardRadius); + drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 14.0f), std::string(title), kShellPalette.textPrimary, 17.0f); + if (!subtitle.empty()) { + drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 38.0f), std::string(subtitle), kShellPalette.textMuted, 12.0f); + } +} + +void DrawButton(UIDrawList& drawList, const ButtonState& button) { + drawList.AddFilledRect(button.rect, button.selected ? kShellPalette.buttonHoverBackground : kShellPalette.buttonBackground, kShellMetrics.buttonRadius); + drawList.AddRectOutline(button.rect, kShellPalette.cardBorder, 1.0f, kShellMetrics.buttonRadius); + drawList.AddText(UIPoint(button.rect.x + 12.0f, button.rect.y + 10.0f), button.label, kShellPalette.textPrimary, 12.0f); +} + +UIEditorPanelRegistry BuildPanelRegistry() { + UIEditorPanelRegistry registry = {}; + registry.panels = { + { "hierarchy", "层级", UIEditorPanelPresentationKind::Placeholder, true, true, false }, + { "scene", "场景", UIEditorPanelPresentationKind::ViewportShell, false, true, false }, + { "document", "文档", UIEditorPanelPresentationKind::Placeholder, true, true, true }, + { "inspector", "检查器", UIEditorPanelPresentationKind::Placeholder, true, true, true } + }; + return registry; +} + +UIEditorWorkspaceModel BuildWorkspace() { + UIEditorWorkspaceModel workspace = {}; + workspace.root = BuildUIEditorWorkspaceSplit( + "root", + UIEditorWorkspaceSplitAxis::Horizontal, + 0.23f, + BuildUIEditorWorkspacePanel("hierarchy-node", "hierarchy", "层级", true), + BuildUIEditorWorkspaceSplit( + "center-right", + UIEditorWorkspaceSplitAxis::Horizontal, + 0.72f, + BuildUIEditorWorkspaceTabStack( + "center-tabs", + { + BuildUIEditorWorkspacePanel("scene-node", "scene", "场景"), + BuildUIEditorWorkspacePanel("document-node", "document", "文档", true) + }, + 0u), + BuildUIEditorWorkspacePanel("inspector-node", "inspector", "检查器", true))); + workspace.activePanelId = "scene"; + return workspace; +} + +class ScenarioApp { +public: + int Run(HINSTANCE hInstance, int nCmdShow); + +private: + static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); + bool Initialize(HINSTANCE hInstance, int nCmdShow); + void Shutdown(); + void ResetScenario(); + void UpdateLayout(); + std::vector BuildPresentations() const; + void QueuePointer(UIInputEventType type, UIPointerButton button, LPARAM lParam); + void ExecuteAction(ActionId action); + void DispatchCommand(UIEditorWorkspaceCommandKind kind, std::string_view panelId, std::string_view label); + void UpdateFrame(); + void RenderValidationUI(float width, float height); + void RenderFrame(); + + HWND m_hwnd = nullptr; + ATOM m_classAtom = 0; + NativeRenderer m_renderer = {}; + AutoScreenshotController m_autoScreenshot = {}; + std::filesystem::path m_captureRoot = {}; + UIEditorWorkspaceController m_controller = {}; + UIEditorWorkspaceComposeState m_state = {}; + UIEditorWorkspaceComposeRequest m_request = {}; + UIEditorWorkspaceComposeFrame m_frame = {}; + std::vector m_pendingEvents = {}; + std::vector m_buttons = {}; + UIRect m_introRect = {}; + UIRect m_controlsRect = {}; + UIRect m_stateRect = {}; + UIRect m_previewRect = {}; + UIRect m_workspaceRect = {}; + UIPoint m_mousePosition = UIPoint(-1000.0f, -1000.0f); + UIEditorViewportSlotHitTarget m_hoverHit = {}; + bool m_showTopBar = true; + bool m_showBottomBar = true; + bool m_textureEnabled = true; + std::string m_lastResult = {}; +}; + int ScenarioApp::Run(HINSTANCE hInstance, int nCmdShow) { if (!Initialize(hInstance, nCmdShow)) { Shutdown(); return 1; } - MSG message = {}; while (message.message != WM_QUIT) { if (PeekMessageW(&message, nullptr, 0U, 0U, PM_REMOVE)) { @@ -316,11 +313,9 @@ int ScenarioApp::Run(HINSTANCE hInstance, int nCmdShow) { DispatchMessageW(&message); continue; } - RenderFrame(); Sleep(8); } - Shutdown(); return static_cast(message.wParam); } @@ -332,82 +327,94 @@ LRESULT CALLBACK ScenarioApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(app)); return TRUE; } - auto* app = reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); + if (app == nullptr) { + return DefWindowProcW(hwnd, message, wParam, lParam); + } + switch (message) { case WM_SIZE: - if (app != nullptr && wParam != SIZE_MINIMIZED) { + if (wParam != SIZE_MINIMIZED) { app->m_renderer.Resize(static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))); } return 0; - case WM_MOUSEMOVE: - if (app != nullptr) { - app->HandleMouseMove(wParam, lParam); - return 0; - } - break; - case WM_MOUSELEAVE: - if (app != nullptr) { - app->QueuePointerLeaveEvent(); - InvalidateRect(hwnd, nullptr, FALSE); - return 0; - } - break; + case WM_MOUSEMOVE: { + app->QueuePointer(UIInputEventType::PointerMove, UIPointerButton::None, lParam); + TRACKMOUSEEVENT track = { sizeof(track), TME_LEAVE, hwnd, 0 }; + TrackMouseEvent(&track); + InvalidateRect(hwnd, nullptr, FALSE); + return 0; + } + case WM_MOUSELEAVE: { + UIInputEvent event = {}; + event.type = UIInputEventType::PointerLeave; + app->m_pendingEvents.push_back(event); + InvalidateRect(hwnd, nullptr, FALSE); + return 0; + } case WM_LBUTTONDOWN: - if (app != nullptr) { - SetFocus(hwnd); - app->HandlePointerDown(UIPointerButton::Left, wParam, lParam); - return 0; - } - break; - case WM_LBUTTONUP: - if (app != nullptr) { - app->HandlePointerUp(UIPointerButton::Left, wParam, lParam); - return 0; - } - break; - case WM_MOUSEWHEEL: - if (app != nullptr) { - app->QueuePointerWheelEvent(GET_WHEEL_DELTA_WPARAM(wParam), wParam, lParam); - InvalidateRect(hwnd, nullptr, FALSE); - return 0; - } - break; - case WM_SETFOCUS: - if (app != nullptr) { - app->m_inputModifierTracker.SyncFromSystemState(); - app->QueueFocusEvent(UIInputEventType::FocusGained); - return 0; - } - break; - case WM_KILLFOCUS: - if (app != nullptr) { - if (GetCapture() == hwnd) { - ReleaseCapture(); + SetFocus(hwnd); + app->QueuePointer(UIInputEventType::PointerButtonDown, UIPointerButton::Left, lParam); + InvalidateRect(hwnd, nullptr, FALSE); + return 0; + case WM_LBUTTONUP: { + app->QueuePointer(UIInputEventType::PointerButtonUp, UIPointerButton::Left, lParam); + const float x = static_cast(GET_X_LPARAM(lParam)); + const float y = static_cast(GET_Y_LPARAM(lParam)); + for (const ButtonState& button : app->m_buttons) { + if (ContainsPoint(button.rect, x, y)) { + app->ExecuteAction(button.action); + break; } - app->m_inputModifierTracker.Reset(); - app->QueueFocusEvent(UIInputEventType::FocusLost); - return 0; } - break; + if (GetCapture() == hwnd) { + ReleaseCapture(); + } + InvalidateRect(hwnd, nullptr, FALSE); + return 0; + } + case WM_MOUSEWHEEL: { + POINT point = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(hwnd, &point); + UIInputEvent event = {}; + event.type = UIInputEventType::PointerWheel; + event.position = UIPoint(static_cast(point.x), static_cast(point.y)); + event.wheelDelta = static_cast(GET_WHEEL_DELTA_WPARAM(wParam)); + app->m_pendingEvents.push_back(event); + InvalidateRect(hwnd, nullptr, FALSE); + return 0; + } + case WM_SETFOCUS: { + UIInputEvent event = {}; + event.type = UIInputEventType::FocusGained; + app->m_pendingEvents.push_back(event); + return 0; + } + case WM_KILLFOCUS: { + UIInputEvent event = {}; + event.type = UIInputEventType::FocusLost; + app->m_pendingEvents.push_back(event); + if (GetCapture() == hwnd) { + ReleaseCapture(); + } + return 0; + } case WM_KEYDOWN: case WM_SYSKEYDOWN: - if (app != nullptr && wParam == VK_F12) { + if (wParam == VK_F12) { app->m_autoScreenshot.RequestCapture("manual_f12"); InvalidateRect(hwnd, nullptr, FALSE); UpdateWindow(hwnd); return 0; } break; - case WM_PAINT: - if (app != nullptr) { - PAINTSTRUCT paintStruct = {}; - BeginPaint(hwnd, &paintStruct); - app->RenderFrame(); - EndPaint(hwnd, &paintStruct); - return 0; - } - break; + case WM_PAINT: { + PAINTSTRUCT ps = {}; + BeginPaint(hwnd, &ps); + app->RenderFrame(); + EndPaint(hwnd, &ps); + return 0; + } case WM_ERASEBKGND: return 1; case WM_DESTROY: @@ -416,76 +423,58 @@ LRESULT CALLBACK ScenarioApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LP default: break; } - return DefWindowProcW(hwnd, message, wParam, lParam); } 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/integration/shell/workspace_viewport_compose/captures"; m_autoScreenshot.Initialize(m_captureRoot); - WNDCLASSEXW windowClass = {}; - windowClass.cbSize = sizeof(windowClass); - windowClass.style = CS_HREDRAW | CS_VREDRAW; - windowClass.lpfnWndProc = &ScenarioApp::WndProc; - windowClass.hInstance = hInstance; - windowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW); - windowClass.lpszClassName = kWindowClassName; - - m_windowClassAtom = RegisterClassExW(&windowClass); - if (m_windowClassAtom == 0) { + WNDCLASSEXW wc = {}; + wc.cbSize = sizeof(wc); + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = &ScenarioApp::WndProc; + wc.hInstance = hInstance; + wc.hCursor = LoadCursorW(nullptr, IDC_ARROW); + wc.lpszClassName = kWindowClassName; + m_classAtom = RegisterClassExW(&wc); + if (m_classAtom == 0) { return false; } - m_hwnd = CreateWindowExW( - 0, - kWindowClassName, - kWindowTitle, - WS_OVERLAPPEDWINDOW | WS_VISIBLE, - CW_USEDEFAULT, - CW_USEDEFAULT, - 1520, - 940, - nullptr, - nullptr, - hInstance, - this); - if (m_hwnd == nullptr) { + m_hwnd = CreateWindowExW(0, kWindowClassName, kWindowTitle, WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 1520, 940, nullptr, nullptr, hInstance, this); + if (m_hwnd == nullptr || !m_renderer.Initialize(m_hwnd)) { return false; } - - if (!m_renderer.Initialize(m_hwnd)) { - return false; - } - ShowWindow(m_hwnd, nCmdShow); ResetScenario(); + if (IsAutoCaptureOnStartupEnabled()) { + m_lastResult = "Startup capture queued"; + } return true; } void ScenarioApp::Shutdown() { m_autoScreenshot.Shutdown(); m_renderer.Shutdown(); - if (m_hwnd != nullptr && IsWindow(m_hwnd)) { DestroyWindow(m_hwnd); } m_hwnd = nullptr; - - if (m_windowClassAtom != 0) { + if (m_classAtom != 0) { UnregisterClassW(kWindowClassName, GetModuleHandleW(nullptr)); - m_windowClassAtom = 0; + m_classAtom = 0; } } void ScenarioApp::ResetScenario() { - m_controller = BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); - m_composeState = {}; - m_composeRequest = {}; - m_composeFrame = {}; - m_pendingInputEvents.clear(); - m_inputModifierTracker.Reset(); + m_controller = BuildDefaultUIEditorWorkspaceController( + BuildValidationPanelRegistry(), + BuildValidationWorkspace()); + m_state = {}; + m_request = {}; + m_frame = {}; + m_pendingEvents.clear(); m_buttons.clear(); m_mousePosition = UIPoint(-1000.0f, -1000.0f); m_hoverHit = {}; @@ -493,461 +482,185 @@ void ScenarioApp::ResetScenario() { m_showBottomBar = true; m_textureEnabled = true; m_lastResult = "Ready"; - m_previousHovered = false; - m_previousFocused = false; - m_previousCaptured = false; - m_previousViewportVisible = false; - m_hasSnapshot = false; } -void ScenarioApp::UpdateLayoutForCurrentWindow() { - if (m_hwnd == nullptr) { - return; - } - +void ScenarioApp::UpdateLayout() { RECT clientRect = {}; GetClientRect(m_hwnd, &clientRect); const float width = static_cast(std::max(1L, clientRect.right - clientRect.left)); const float height = static_cast(std::max(1L, clientRect.bottom - clientRect.top)); - - constexpr float outerPadding = 20.0f; - constexpr float leftColumnWidth = 460.0f; - m_introRect = UIRect(outerPadding, outerPadding, leftColumnWidth, 184.0f); - m_controlsRect = UIRect(outerPadding, 220.0f, leftColumnWidth, 340.0f); - m_stateRect = UIRect(outerPadding, 576.0f, leftColumnWidth, height - 596.0f); - m_previewRect = UIRect( - leftColumnWidth + outerPadding * 2.0f, - outerPadding, - width - leftColumnWidth - outerPadding * 3.0f, - height - outerPadding * 2.0f); - m_workspaceRect = UIRect( - m_previewRect.x + 18.0f, - m_previewRect.y + 54.0f, - m_previewRect.width - 36.0f, - m_previewRect.height - 72.0f); + constexpr float padding = 20.0f; + constexpr float leftWidth = 460.0f; + m_introRect = UIRect(padding, padding, leftWidth, 176.0f); + m_controlsRect = UIRect(padding, 212.0f, leftWidth, 340.0f); + m_stateRect = UIRect(padding, 568.0f, leftWidth, height - 588.0f); + m_previewRect = UIRect(leftWidth + padding * 2.0f, padding, width - leftWidth - padding * 3.0f, height - padding * 2.0f); + m_workspaceRect = UIRect(m_previewRect.x + 18.0f, m_previewRect.y + 54.0f, m_previewRect.width - 36.0f, m_previewRect.height - 72.0f); const float buttonHeight = 32.0f; const float gap = 8.0f; const float left = m_controlsRect.x + 16.0f; const float top = m_controlsRect.y + 64.0f; const float widthAvailable = m_controlsRect.width - 32.0f; - const bool sceneSelected = GetSelectedTabPanelId() == "scene"; + const bool sceneSelected = m_controller.GetWorkspace().activePanelId == "scene"; m_buttons = { - { ActionId::ActivateScene, "切到 Scene", UIRect(left, top, widthAvailable, buttonHeight), sceneSelected }, - { ActionId::ActivateDocument, "切到 Document", UIRect(left, top + (buttonHeight + gap), widthAvailable, buttonHeight), !sceneSelected }, - { ActionId::ToggleTopBar, std::string("TopBar: ") + (m_showTopBar ? "开" : "关"), UIRect(left, top + (buttonHeight + gap) * 2.0f, widthAvailable, buttonHeight), m_showTopBar }, - { ActionId::ToggleBottomBar, std::string("BottomBar: ") + (m_showBottomBar ? "开" : "关"), UIRect(left, top + (buttonHeight + gap) * 3.0f, widthAvailable, buttonHeight), m_showBottomBar }, - { ActionId::ToggleTexture, std::string("Texture: ") + (m_textureEnabled ? "开" : "关"), UIRect(left, top + (buttonHeight + gap) * 4.0f, widthAvailable, buttonHeight), m_textureEnabled }, + { ActionId::ActivateScene, "Activate Scene", UIRect(left, top, widthAvailable, buttonHeight), sceneSelected }, + { ActionId::ActivateDocument, "Activate Document", UIRect(left, top + (buttonHeight + gap), widthAvailable, buttonHeight), !sceneSelected }, + { ActionId::ToggleTopBar, std::string("Top bar: ") + BoolTextAscii(m_showTopBar), UIRect(left, top + (buttonHeight + gap) * 2.0f, widthAvailable, buttonHeight), m_showTopBar }, + { ActionId::ToggleBottomBar, std::string("Bottom bar: ") + BoolTextAscii(m_showBottomBar), UIRect(left, top + (buttonHeight + gap) * 3.0f, widthAvailable, buttonHeight), m_showBottomBar }, + { ActionId::ToggleTexture, std::string("Texture branch: ") + BoolTextAscii(m_textureEnabled), UIRect(left, top + (buttonHeight + gap) * 4.0f, widthAvailable, buttonHeight), m_textureEnabled }, { ActionId::Reset, "Reset", UIRect(left, top + (buttonHeight + gap) * 5.0f, widthAvailable, buttonHeight), false }, - { ActionId::Capture, "截图", UIRect(left, top + (buttonHeight + gap) * 6.0f, widthAvailable, buttonHeight), false } + { ActionId::Capture, "Capture", UIRect(left, top + (buttonHeight + gap) * 6.0f, widthAvailable, buttonHeight), false } }; } -std::vector ScenarioApp::BuildPresentationModels() const { - UIEditorViewportShellModel viewportModel = {}; - viewportModel.spec.chrome.title = "Scene"; - viewportModel.spec.chrome.subtitle = "WorkspaceCompose 基础层"; - viewportModel.spec.chrome.showTopBar = m_showTopBar; - viewportModel.spec.chrome.showBottomBar = m_showBottomBar; - viewportModel.spec.chrome.topBarHeight = 40.0f; - viewportModel.spec.chrome.bottomBarHeight = 28.0f; - viewportModel.spec.toolItems = { - { "mode", "Perspective", UIEditorViewportSlotToolSlot::Leading, true, true, 98.0f }, - { "focus", "WorkspaceCompose", UIEditorViewportSlotToolSlot::Trailing, true, false, 132.0f } +std::vector ScenarioApp::BuildPresentations() const { + UIEditorViewportShellModel viewport = {}; + viewport.spec.chrome.title = "Scene"; + viewport.spec.chrome.subtitle = "WorkspaceViewportCompose"; + viewport.spec.chrome.showTopBar = m_showTopBar; + viewport.spec.chrome.showBottomBar = m_showBottomBar; + viewport.spec.toolItems = { + { "mode", "透视", UIEditorViewportSlotToolSlot::Leading, true, true, 98.0f }, + { "focus", "聚焦选中", UIEditorViewportSlotToolSlot::Trailing, true, false, 132.0f } }; - viewportModel.spec.statusSegments = { - { "chrome-top", std::string("TopBar ") + BoolText(m_showTopBar), UIEditorStatusBarSlot::Leading, {}, true, true, 96.0f }, - { "chrome-bottom", std::string("BottomBar ") + BoolText(m_showBottomBar), UIEditorStatusBarSlot::Leading, {}, true, false, 126.0f }, - { "branch", m_textureEnabled ? "Texture" : "Fallback", UIEditorStatusBarSlot::Trailing, {}, true, true, 100.0f } + viewport.spec.statusSegments = { + { "top", std::string("Top bar ") + BoolTextAscii(m_showTopBar), UIEditorStatusBarSlot::Leading, {}, true, true, 104.0f }, + { "bottom", std::string("Bottom bar ") + BoolTextAscii(m_showBottomBar), UIEditorStatusBarSlot::Leading, {}, true, false, 128.0f }, + { "branch", m_textureEnabled ? "Texture" : "Fallback", UIEditorStatusBarSlot::Trailing, {}, true, true, 96.0f } }; - if (m_textureEnabled) { - viewportModel.frame.hasTexture = true; - viewportModel.frame.texture = { 1u, 1280u, 720u }; - viewportModel.frame.presentedSize = UISize(1280.0f, 720.0f); - viewportModel.frame.statusText = "Fake viewport frame"; + viewport.frame.hasTexture = true; + viewport.frame.texture = { 1u, 1280u, 720u }; + viewport.frame.presentedSize = UISize(1280.0f, 720.0f); + viewport.frame.statusText = "Simulated texture branch"; } else { - viewportModel.frame.hasTexture = false; - viewportModel.frame.statusText = "这里只验证 WorkspaceCompose contract,不接 Scene/Game 业务。"; + viewport.frame.statusText = "Fallback viewport branch"; } - UIEditorWorkspacePanelPresentationModel model = {}; - model.panelId = "scene"; - model.kind = UIEditorPanelPresentationKind::ViewportShell; - model.viewportShellModel = std::move(viewportModel); - return { model }; + UIEditorWorkspacePanelPresentationModel presentation = {}; + presentation.panelId = "scene"; + presentation.kind = UIEditorPanelPresentationKind::ViewportShell; + presentation.viewportShellModel = std::move(viewport); + return { presentation }; } -void ScenarioApp::QueuePointerEvent(UIInputEventType type, UIPointerButton button, WPARAM wParam, LPARAM lParam) { +void ScenarioApp::QueuePointer(UIInputEventType type, UIPointerButton button, LPARAM lParam) { UIInputEvent event = {}; event.type = type; event.pointerButton = button; - event.position = UIPoint( - static_cast(GET_X_LPARAM(lParam)), - static_cast(GET_Y_LPARAM(lParam))); - event.modifiers = m_inputModifierTracker.BuildPointerModifiers(static_cast(wParam)); - m_pendingInputEvents.push_back(event); + event.position = UIPoint(static_cast(GET_X_LPARAM(lParam)), static_cast(GET_Y_LPARAM(lParam))); m_mousePosition = event.position; -} - -void ScenarioApp::QueuePointerLeaveEvent() { - UIInputEvent event = {}; - event.type = UIInputEventType::PointerLeave; - if (m_hwnd != nullptr) { - POINT clientPoint = {}; - GetCursorPos(&clientPoint); - ScreenToClient(m_hwnd, &clientPoint); - event.position = UIPoint(static_cast(clientPoint.x), static_cast(clientPoint.y)); - m_mousePosition = event.position; - } - m_pendingInputEvents.push_back(event); -} - -void ScenarioApp::QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam) { - if (m_hwnd == nullptr) { - return; - } - - POINT screenPoint = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(m_hwnd, &screenPoint); - - UIInputEvent event = {}; - event.type = UIInputEventType::PointerWheel; - event.position = UIPoint(static_cast(screenPoint.x), static_cast(screenPoint.y)); - event.wheelDelta = static_cast(wheelDelta); - event.modifiers = m_inputModifierTracker.BuildPointerModifiers(static_cast(wParam)); - m_pendingInputEvents.push_back(event); - m_mousePosition = event.position; -} - -void ScenarioApp::QueueFocusEvent(UIInputEventType type) { - UIInputEvent event = {}; - event.type = type; - event.modifiers = m_inputModifierTracker.GetCurrentModifiers(); - m_pendingInputEvents.push_back(event); -} - -void ScenarioApp::HandleMouseMove(WPARAM wParam, LPARAM lParam) { - QueuePointerEvent(UIInputEventType::PointerMove, UIPointerButton::None, wParam, lParam); - TRACKMOUSEEVENT event = {}; - event.cbSize = sizeof(event); - event.dwFlags = TME_LEAVE; - event.hwndTrack = m_hwnd; - TrackMouseEvent(&event); - InvalidateRect(m_hwnd, nullptr, FALSE); -} - -void ScenarioApp::HandlePointerDown(UIPointerButton button, WPARAM wParam, LPARAM lParam) { - QueuePointerEvent(UIInputEventType::PointerButtonDown, button, wParam, lParam); - - const UIPoint point( - static_cast(GET_X_LPARAM(lParam)), - static_cast(GET_Y_LPARAM(lParam))); - const auto* viewportFrame = FindUIEditorWorkspaceViewportPresentationFrame(m_composeFrame, "scene"); - if (viewportFrame != nullptr && - ContainsPoint(viewportFrame->viewportShellFrame.slotLayout.inputRect, point.x, point.y)) { - SetCapture(m_hwnd); - } - - InvalidateRect(m_hwnd, nullptr, FALSE); -} - -void ScenarioApp::HandlePointerUp(UIPointerButton button, WPARAM wParam, LPARAM lParam) { - QueuePointerEvent(UIInputEventType::PointerButtonUp, button, wParam, lParam); - - const UIPoint point( - static_cast(GET_X_LPARAM(lParam)), - static_cast(GET_Y_LPARAM(lParam))); - if (GetCapture() == m_hwnd) { - ReleaseCapture(); - } - - for (const ButtonState& buttonState : m_buttons) { - if (!ContainsPoint(buttonState.rect, point.x, point.y)) { - continue; - } - - ExecuteAction(buttonState.action); - break; - } - - InvalidateRect(m_hwnd, nullptr, FALSE); + m_pendingEvents.push_back(event); } void ScenarioApp::ExecuteAction(ActionId action) { switch (action) { - case ActionId::ActivateScene: - DispatchWorkspaceCommand(UIEditorWorkspaceCommandKind::ActivatePanel, "scene", "Activate Scene"); - break; - case ActionId::ActivateDocument: - DispatchWorkspaceCommand(UIEditorWorkspaceCommandKind::ActivatePanel, "document", "Activate Document"); - break; - case ActionId::ToggleTopBar: - m_showTopBar = !m_showTopBar; - m_lastResult = m_showTopBar ? "TopBar 已打开" : "TopBar 已关闭"; - break; - case ActionId::ToggleBottomBar: - m_showBottomBar = !m_showBottomBar; - m_lastResult = m_showBottomBar ? "BottomBar 已打开" : "BottomBar 已关闭"; - break; - case ActionId::ToggleTexture: - m_textureEnabled = !m_textureEnabled; - m_lastResult = m_textureEnabled ? "切到 Texture 分支" : "切到 Fallback 分支"; - break; - case ActionId::Reset: - ResetScenario(); - m_lastResult = "状态已重置"; - break; - case ActionId::Capture: - m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "截图已排队"; - InvalidateRect(m_hwnd, nullptr, FALSE); - UpdateWindow(m_hwnd); - break; + case ActionId::ActivateScene: DispatchCommand(UIEditorWorkspaceCommandKind::ActivatePanel, "scene", "Activate Scene"); break; + case ActionId::ActivateDocument: DispatchCommand(UIEditorWorkspaceCommandKind::ActivatePanel, "document", "Activate Document"); break; + case ActionId::ToggleTopBar: m_showTopBar = !m_showTopBar; m_lastResult = m_showTopBar ? "Top bar enabled" : "Top bar disabled"; break; + case ActionId::ToggleBottomBar: m_showBottomBar = !m_showBottomBar; m_lastResult = m_showBottomBar ? "Bottom bar enabled" : "Bottom bar disabled"; break; + case ActionId::ToggleTexture: m_textureEnabled = !m_textureEnabled; m_lastResult = m_textureEnabled ? "Texture branch enabled" : "Fallback branch enabled"; break; + case ActionId::Reset: ResetScenario(); m_lastResult = "Scenario reset"; break; + case ActionId::Capture: m_autoScreenshot.RequestCapture("manual_button"); m_lastResult = "Capture queued"; UpdateWindow(m_hwnd); break; } } -void ScenarioApp::DispatchWorkspaceCommand( - UIEditorWorkspaceCommandKind kind, - std::string_view panelId, - std::string_view label) { +void ScenarioApp::DispatchCommand(UIEditorWorkspaceCommandKind kind, std::string_view panelId, std::string_view label) { UIEditorWorkspaceCommand command = {}; command.kind = kind; command.panelId = std::string(panelId); - const UIEditorWorkspaceCommandResult result = m_controller.Dispatch(command); - m_lastResult = - std::string(label) + " -> " + - std::string(GetUIEditorWorkspaceCommandStatusName(result.status)) + - " | " + - result.message; + m_lastResult = std::string(label) + " -> " + std::string(GetUIEditorWorkspaceCommandStatusName(result.status)) + " | " + result.message; } -std::string ScenarioApp::GetSelectedTabPanelId() const { - if (!m_composeFrame.dockHostLayout.tabStacks.empty()) { - return m_composeFrame.dockHostLayout.tabStacks.front().selectedPanelId; - } - - return m_controller.GetWorkspace().activePanelId; -} - -void ScenarioApp::UpdateComposeFrame() { - const auto presentations = BuildPresentationModels(); - m_composeRequest = ResolveUIEditorWorkspaceComposeRequest( - m_workspaceRect, - m_controller.GetPanelRegistry(), - m_controller.GetWorkspace(), - m_controller.GetSession(), - presentations); - - std::vector frameEvents = std::move(m_pendingInputEvents); - m_pendingInputEvents.clear(); - m_composeFrame = UpdateUIEditorWorkspaceCompose( - m_composeState, - m_workspaceRect, - m_controller.GetPanelRegistry(), - m_controller.GetWorkspace(), - m_controller.GetSession(), - presentations, - frameEvents); - - const auto* viewportFrame = - FindUIEditorWorkspaceViewportPresentationFrame(m_composeFrame, "scene"); +void ScenarioApp::UpdateFrame() { + const auto dockMetrics = ResolveUIEditorDockHostMetrics(); + const auto viewportMetrics = ResolveUIEditorViewportSlotMetrics(); + const auto presentations = BuildPresentations(); + m_request = ResolveUIEditorWorkspaceComposeRequest(m_workspaceRect, m_controller.GetPanelRegistry(), m_controller.GetWorkspace(), m_controller.GetSession(), presentations, {}, dockMetrics, viewportMetrics); + const auto frameEvents = std::move(m_pendingEvents); + m_pendingEvents.clear(); + m_frame = UpdateUIEditorWorkspaceCompose(m_state, m_workspaceRect, m_controller.GetPanelRegistry(), m_controller.GetWorkspace(), m_controller.GetSession(), presentations, frameEvents, {}, dockMetrics, viewportMetrics); + const auto* viewportFrame = FindUIEditorWorkspaceViewportPresentationFrame(m_frame, "scene"); if (viewportFrame != nullptr) { - m_hoverHit = HitTestUIEditorViewportSlot( - viewportFrame->viewportShellFrame.slotLayout, - m_mousePosition); + m_hoverHit = HitTestUIEditorViewportSlot(viewportFrame->viewportShellFrame.slotLayout, m_mousePosition); + const auto& input = viewportFrame->viewportShellFrame.inputFrame; + if (input.captureStarted) { + m_lastResult = "Viewport capture started"; + SetCapture(m_hwnd); + } else if (input.captureEnded) { + m_lastResult = "Viewport capture ended"; + } else if (input.focusGained) { + m_lastResult = "Viewport focus gained"; + } else if (input.focusLost) { + m_lastResult = "Viewport focus lost"; + } else if (input.pointerPressedInside) { + m_lastResult = "Pointer down inside viewport"; + } else if (input.pointerReleasedInside) { + m_lastResult = "Pointer up inside viewport"; + } else if (input.wheelDelta != 0.0f) { + m_lastResult = "Wheel " + FormatFloat(input.wheelDelta); + } } else { m_hoverHit = {}; } - - UpdateLastResult(viewportFrame); -} - -void ScenarioApp::UpdateLastResult( - const XCEngine::UI::Editor::UIEditorWorkspaceViewportComposeFrame* viewportFrame) { - const bool viewportVisible = viewportFrame != nullptr; - if (!viewportVisible) { - if (m_hasSnapshot && m_previousViewportVisible) { - m_lastResult = "Viewport body 已切回 DockHost placeholder"; - } - m_previousViewportVisible = false; - m_previousHovered = false; - m_previousFocused = false; - m_previousCaptured = false; - m_hasSnapshot = true; - return; - } - - const auto& inputFrame = viewportFrame->viewportShellFrame.inputFrame; - if (inputFrame.captureStarted) { - m_lastResult = "CaptureStarted"; - } else if (inputFrame.captureEnded) { - m_lastResult = "CaptureEnded"; - } else if (inputFrame.focusLost) { - m_lastResult = "FocusLost"; - } else if (inputFrame.focusGained) { - m_lastResult = "FocusGained"; - } else if (inputFrame.pointerPressedInside) { - m_lastResult = "PointerDownInside"; - } else if (inputFrame.pointerReleasedInside) { - m_lastResult = "PointerUpInside"; - } else if (inputFrame.wheelDelta != 0.0f) { - m_lastResult = "Wheel " + FormatFloat(inputFrame.wheelDelta); - } else if (m_hasSnapshot && !m_previousViewportVisible) { - m_lastResult = "Viewport body 接管成功"; - } else if (m_hasSnapshot && m_previousHovered != inputFrame.hovered) { - m_lastResult = std::string("Hover ") + BoolText(inputFrame.hovered); - } - - m_previousViewportVisible = true; - m_previousHovered = inputFrame.hovered; - m_previousFocused = inputFrame.focused; - m_previousCaptured = inputFrame.captured; - m_hasSnapshot = true; } void ScenarioApp::RenderFrame() { if (m_hwnd == nullptr) { return; } + UpdateLayout(); + UpdateFrame(); + UpdateLayout(); RECT clientRect = {}; GetClientRect(m_hwnd, &clientRect); const float width = static_cast(std::max(1L, clientRect.right - clientRect.left)); const float height = static_cast(std::max(1L, clientRect.bottom - clientRect.top)); - - UpdateLayoutForCurrentWindow(); - UpdateComposeFrame(); - UpdateLayoutForCurrentWindow(); - - const auto* viewportRequest = - FindUIEditorWorkspaceViewportPresentationRequest(m_composeRequest, "scene"); - const auto* viewportFrame = - FindUIEditorWorkspaceViewportPresentationFrame(m_composeFrame, "scene"); - const std::string selectedTabId = GetSelectedTabPanelId(); - const std::string selectedPresentation = - selectedTabId == "scene" && viewportFrame != nullptr ? "ViewportShell" : "Placeholder"; + const auto* viewportRequest = FindUIEditorWorkspaceViewportPresentationRequest(m_request, "scene"); + const auto* viewportFrame = FindUIEditorWorkspaceViewportPresentationFrame(m_frame, "scene"); const auto validation = m_controller.ValidateState(); UIDrawData drawData = {}; UIDrawList& drawList = drawData.EmplaceDrawList("WorkspaceViewportCompose"); - drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg); + drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kShellPalette.windowBackground); - DrawCard( - drawList, - m_introRect, - "测试功能:WorkspaceCompose body 接管", - "只验证 DockHost body 被 ViewportShell 接管这条基础 contract。"); - drawList.AddText( - UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f), - "检查 1:切到 Scene 时,中间 tab body 必须由 ViewportShell 接管;切到 Document 时,必须退回 DockHost placeholder。", - kTextMuted, - 11.0f); - drawList.AddText( - UIPoint(m_introRect.x + 16.0f, m_introRect.y + 86.0f), - "检查 2:TopBar / BottomBar 只改变 viewport request size,不应破坏 workspace shell 与左右占位面板。", - kTextMuted, - 11.0f); - drawList.AddText( - UIPoint(m_introRect.x + 16.0f, m_introRect.y + 106.0f), - "检查 3:hover / focus / capture 只在 Scene 可见时生效,切回 Document 后不能残留脏状态。", - kTextMuted, - 11.0f); - drawList.AddText( - UIPoint(m_introRect.x + 16.0f, m_introRect.y + 126.0f), - "操作:先切 Scene,hover / click / drag 右侧中间 viewport;再切 Document,看 body 是否恢复 placeholder。", - kTextMuted, - 11.0f); - drawList.AddText( - UIPoint(m_introRect.x + 16.0f, m_introRect.y + 146.0f), - "操作:切换 TopBar / BottomBar / Texture,观察左侧 Request Size、右侧 viewport chrome、以及 workspace 外壳是否同步。", - kTextMuted, - 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, "操作", "这里只放当前这个 compose contract 真正需要检查的 7 个控件。"); + 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, float fontSize = 12.0f) { - drawList.AddText(UIPoint(m_stateRect.x + 16.0f, stateY), std::move(text), color, fontSize); + 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); stateY += 18.0f; }; + addStateLine("当前活动面板: " + m_controller.GetWorkspace().activePanelId); + addStateLine("当前呈现类型: " + std::string(viewportFrame != nullptr && m_controller.GetWorkspace().activePanelId == "scene" ? "ViewportShell" : "DockHost 占位")); + addStateLine("视口请求尺寸: " + (viewportRequest != nullptr ? FormatSize(viewportRequest->viewportShellRequest.requestedViewportSize) : std::string("n/a"))); + addStateLine("当前命中: " + (viewportFrame != nullptr ? DescribeHitTarget(m_hoverHit) : std::string("n/a"))); + 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("Active Panel: " + m_controller.GetWorkspace().activePanelId, kTextPrimary); - addStateLine("Selected Tab: " + selectedTabId, kTextPrimary); - addStateLine("Selected Presentation: " + selectedPresentation, kTextPrimary); - addStateLine("Viewport Visible: " + BoolText(viewportFrame != nullptr), kTextPrimary); - addStateLine( - "Texture Branch: " + std::string(m_textureEnabled ? "Texture" : "Fallback"), - kTextPrimary); - addStateLine( - "Request Size: " + - (viewportRequest != nullptr - ? FormatSize(viewportRequest->viewportShellRequest.requestedViewportSize) - : std::string("n/a")), - kTextPrimary); - addStateLine( - "Input Rect: " + - (viewportFrame != nullptr - ? FormatRect(viewportFrame->viewportShellFrame.slotLayout.inputRect) - : std::string("n/a")), - kTextMuted, - 11.0f); - addStateLine( - "Hover Hit: " + - (viewportFrame != nullptr ? DescribeHitTarget(m_hoverHit) : std::string("n/a")), - kTextPrimary); - addStateLine( - "Hover: " + - (viewportFrame != nullptr - ? BoolText(viewportFrame->viewportShellFrame.inputFrame.hovered) - : std::string("n/a")), - kTextPrimary); - addStateLine( - "Focus: " + - (viewportFrame != nullptr - ? BoolText(viewportFrame->viewportShellFrame.inputFrame.focused) - : std::string("n/a")), - kTextPrimary); - addStateLine( - "Capture: " + - (viewportFrame != nullptr - ? BoolText(viewportFrame->viewportShellFrame.inputFrame.captured) - : std::string("n/a")), - kTextPrimary); - addStateLine("Result: " + m_lastResult, kTextMuted); - addStateLine( - validation.IsValid() ? "Workspace Validation: OK" : "Workspace Validation: " + validation.message, - validation.IsValid() ? kSuccess : kDanger, - 11.0f); - - const std::string captureSummary = - m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." - : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? std::string("截图:F12 或按钮 -> workspace_viewport_compose/captures/") - : m_autoScreenshot.GetLastCaptureSummary()); - addStateLine(captureSummary, kTextWeak, 11.0f); - - DrawCard( - drawList, - m_previewRect, - "Preview", - "这里只有一个 WorkspaceCompose 试验场,不混入任何 editor 业务面板。"); - drawList.AddFilledRect(m_workspaceRect, kPreviewBg, 10.0f); - AppendUIEditorWorkspaceCompose(drawList, m_composeFrame); + DrawCard(drawList, m_previewRect, "预览", "实时绘制 UIEditorWorkspaceCompose 结果。"); + const auto dockPalette = ResolveUIEditorDockHostPalette(); + const auto viewportPalette = ResolveUIEditorViewportSlotPalette(); + const auto dockMetrics = ResolveUIEditorDockHostMetrics(); + const auto viewportMetrics = ResolveUIEditorViewportSlotMetrics(); + AppendUIEditorWorkspaceCompose(drawList, m_frame, dockPalette, dockMetrics, viewportPalette, viewportMetrics); const bool framePresented = m_renderer.Render(drawData); - m_autoScreenshot.CaptureIfRequested( - m_renderer, - drawData, - static_cast(width), - static_cast(height), - framePresented); + m_autoScreenshot.CaptureIfRequested(m_renderer, drawData, static_cast(width), static_cast(height), framePresented); } } // namespace diff --git a/tests/UI/Editor/integration/state/layout_persistence/main.cpp b/tests/UI/Editor/integration/state/layout_persistence/main.cpp index f9e866a1..6e37589c 100644 --- a/tests/UI/Editor/integration/state/layout_persistence/main.cpp +++ b/tests/UI/Editor/integration/state/layout_persistence/main.cpp @@ -1,9 +1,9 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include -#include +#include +#include #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -732,13 +732,13 @@ private: const UIRect footerRect(margin, height - 100.0f, width - margin * 2.0f, 80.0f); DrawCard(drawList, headerRect, "测试功能:Editor Layout Persistence", "只验证 Save / Load / Load Invalid / Reset 的布局恢复链路;不验证业务面板。"); - drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 70.0f), "1. 点 `1 Hide Active`,把 Document A 隐藏;active 应切到 doc-b,visible 应变成 `doc-b, details`。", kTextPrimary, 13.0f); + drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 70.0f), "1. 点 `1 Hide Active`,把 Document A 隐藏;active 应切到 `doc-b`,visible 应变成 `doc-b, details`。", kTextPrimary, 13.0f); drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 92.0f), "2. 点 `2 Save Layout` 保存当前布局;右侧 Saved 摘要必须记录刚才的 active/visible。", kTextPrimary, 13.0f); drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 114.0f), "3. 再点 `3 Close Doc B` 或 `5 Activate Details` 改状态,然后点 `4 Load Layout`;当前状态必须恢复到保存时。", kTextPrimary, 13.0f); - drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 136.0f), "4. 点 `6 Load Invalid`;Result 必须是 Rejected,且当前 active/visible 不得被污染。", kTextPrimary, 13.0f); + drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 136.0f), "4. 点 `6 Load Invalid`,Result 必须是 Rejected,且当前 active/visible 不得被污染。", kTextPrimary, 13.0f); drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 158.0f), "5. `R Reset` 必须回到基线 active=doc-a;按 `F12` 保存当前窗口截图。", kTextPrimary, 13.0f); - DrawCard(drawList, actionRect, "操作区", "只保留这一步需要检查的布局保存/恢复动作。"); + DrawCard(drawList, actionRect, "操作区", "这里只保留这一步需要检查的布局保存/恢复动作。"); DrawCard(drawList, stateRect, "状态摘要", "左侧看 Current,右侧看 Saved;重点检查 active、visible 和坏数据恢复。"); DrawCard(drawList, footerRect, "最近结果", "显示最近一次操作、状态和当前 validation。"); diff --git a/tests/UI/Editor/integration/state/panel_host_lifecycle/main.cpp b/tests/UI/Editor/integration/state/panel_host_lifecycle/main.cpp index 4bfc17d0..dd419c85 100644 --- a/tests/UI/Editor/integration/state/panel_host_lifecycle/main.cpp +++ b/tests/UI/Editor/integration/state/panel_host_lifecycle/main.cpp @@ -1,9 +1,9 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include -#include +#include +#include #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -247,7 +247,7 @@ void DrawHostStateCard( if (!attached) { footer = "host 已 detached"; } else if (!visible) { - footer = "host 保留 attach,但当前不在屏幕上"; + footer = "host 保持 attached,但当前不在屏幕中"; } else if (focused) { footer = "当前 host 已 visible + active + focused"; } else if (active) { @@ -500,7 +500,8 @@ private: if (action == ActionId::Capture) { m_autoScreenshot.RequestCapture("manual_button"); m_lastStatus = "Ready"; - m_lastMessage = "截图已排队,输出到 tests/UI/Editor/integration/state/panel_host_lifecycle/captures/。"; + m_lastMessage = + "截图已排队,输出到 tests/UI/Editor/integration/state/panel_host_lifecycle/captures/。"; return; } @@ -560,10 +561,14 @@ private: UIDrawList& drawList = drawData.EmplaceDrawList("PanelHostLifecycle"); drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg); - DrawCard(drawList, m_introRect, "这个测试验证什么功能", "只验证 Editor panel host lifecycle contract,不做业务面板。"); + DrawCard( + drawList, + m_introRect, + "这个测试验证什么功能?", + "只验证 Editor panel host lifecycle contract,不做业务面板。"); drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 72.0f), "1. 验证 attach/detach 由 panel open/close 驱动,而不是由 visible 驱动。", kTextPrimary, 12.0f); drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 94.0f), "2. 验证非选中 tab 依然 attached,但 visible=false。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 116.0f), "3. 验证 active 与 focused 是两层不同状态,focus 由外部 request 决定。", kTextPrimary, 12.0f); + drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 116.0f), "3. 验证 active 和 focused 是两层不同状态,focus 由外部 request 决定。", kTextPrimary, 12.0f); drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 138.0f), "4. 验证 hide/close/reopen/activate 会输出稳定的 lifecycle events。", kTextPrimary, 12.0f); drawList.AddText(UIPoint(m_introRect.x + 18.0f, m_introRect.y + 164.0f), "建议操作:先看 doc-b 默认 attached 但 hidden;再点 Hide Active / Focus Active / Close Doc B / Open Doc B。", kTextWeak, 11.0f); diff --git a/tests/UI/Editor/integration/state/panel_session_flow/main.cpp b/tests/UI/Editor/integration/state/panel_session_flow/main.cpp index 1aa0e700..c200a454 100644 --- a/tests/UI/Editor/integration/state/panel_session_flow/main.cpp +++ b/tests/UI/Editor/integration/state/panel_session_flow/main.cpp @@ -1,8 +1,8 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include +#include #include "Host/AutoScreenshot.h" #include "Host/NativeRenderer.h" @@ -510,7 +510,7 @@ private: const UIRect footerRect(margin, height - 96.0f, width - margin * 2.0f, 76.0f); DrawCard(drawList, headerRect, "测试功能:Editor Command Dispatch + Workspace Controller", "只验证命令分发、状态变更结果和 panel session 联动;不验证业务面板。"); - drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 70.0f), "1. 点 `1 Hide Active`,Result 应是 Changed;active 从 doc-a 切到 doc-b,selected tab 也同步到 B。", kTextPrimary, 13.0f); + drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 70.0f), "1. 点 `1 Hide Active`,Result 应是 Changed,active 从 doc-a 切到 doc-b,selected tab 也同步到 B。", kTextPrimary, 13.0f); drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 92.0f), "2. 再点 `1 Hide Active`,如果当前 panel 已 hidden,Result 应变成 NoOp,而不是乱改状态。", kTextPrimary, 13.0f); drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 114.0f), "3. 点 `3 Close Doc B` 后再点 `5 Activate Details` / `4 Open Doc B`,检查 Changed / Rejected / NoOp 是否符合提示。", kTextPrimary, 13.0f); drawList.AddText(UIPoint(headerRect.x + 18.0f, headerRect.y + 136.0f), "4. `R Reset` 必须把状态拉回基线;按 `F12` 保存当前窗口截图。", kTextPrimary, 13.0f); diff --git a/tests/UI/Editor/integration/state/shortcut_dispatch/main.cpp b/tests/UI/Editor/integration/state/shortcut_dispatch/main.cpp index 5fcb8885..cb49500f 100644 --- a/tests/UI/Editor/integration/state/shortcut_dispatch/main.cpp +++ b/tests/UI/Editor/integration/state/shortcut_dispatch/main.cpp @@ -1,8 +1,8 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include +#include #include "Host/AutoScreenshot.h" #include "Host/InputModifierTracker.h" #include "Host/NativeRenderer.h" diff --git a/tests/UI/Editor/integration/state/viewport_input_bridge_basic/main.cpp b/tests/UI/Editor/integration/state/viewport_input_bridge_basic/main.cpp index 2672dbfe..9395c899 100644 --- a/tests/UI/Editor/integration/state/viewport_input_bridge_basic/main.cpp +++ b/tests/UI/Editor/integration/state/viewport_input_bridge_basic/main.cpp @@ -1,9 +1,9 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include -#include +#include +#include #include "Host/AutoScreenshot.h" #include "Host/InputModifierTracker.h" #include "Host/NativeRenderer.h" @@ -58,7 +58,7 @@ using XCEngine::UI::Editor::Widgets::UIEditorViewportSlotToolItem; using XCEngine::UI::Editor::Widgets::UIEditorViewportSlotToolSlot; constexpr const wchar_t* kWindowClassName = L"XCUIEditorViewportInputBridgeBasicValidation"; -constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Viewport Input Bridge"; +constexpr const wchar_t* kWindowTitle = L"XCUI 编辑器 | 视口输入桥接"; constexpr UIColor kWindowBg(0.12f, 0.12f, 0.12f, 1.0f); constexpr UIColor kCardBg(0.18f, 0.18f, 0.18f, 1.0f); @@ -98,7 +98,7 @@ bool ContainsPoint(const UIRect& rect, float x, float y) { } std::string BoolText(bool value) { - return value ? "On" : "Off"; + return value ? "开" : "关"; } std::string FormatFloat(float value) { @@ -473,7 +473,7 @@ private: m_bridgeState = {}; m_bridgeFrame = {}; m_pendingEvents.clear(); - m_lastResult = "Ready"; + m_lastResult = "就绪"; } void QueuePointerEvent(UIInputEventType type, UIPointerButton button, WPARAM wParam, LPARAM lParam) { @@ -565,12 +565,12 @@ private: if (buttonState.action == ActionId::Reset) { ResetScenario(); - m_lastResult = "状态已重置"; + m_lastResult = "场景已重置"; } else { m_autoScreenshot.RequestCapture("manual_button"); InvalidateRect(m_hwnd, nullptr, FALSE); UpdateWindow(m_hwnd); - m_lastResult = "截图已排队"; + m_lastResult = "截图已加入队列"; } break; @@ -583,8 +583,8 @@ private: UIEditorViewportSlotChrome BuildChrome() const { UIEditorViewportSlotChrome chrome = {}; - chrome.title = "Scene View"; - chrome.subtitle = "ViewportInputBridge 验证面板"; + chrome.title = "场景视图"; + chrome.subtitle = "视口输入桥接验证"; chrome.showTopBar = true; chrome.showBottomBar = true; chrome.topBarHeight = 40.0f; @@ -597,23 +597,23 @@ private: frame.hasTexture = true; frame.texture = { 1u, 1280u, 720u }; frame.presentedSize = { 1280.0f, 720.0f }; - frame.statusText = "Fake viewport frame"; + frame.statusText = "模拟视口帧"; return frame; } std::vector BuildToolItems() const { return { - { "mode", "Perspective", UIEditorViewportSlotToolSlot::Leading, true, true, 98.0f }, - { "input", "Input", UIEditorViewportSlotToolSlot::Trailing, true, true, 58.0f } + { "mode", "透视", UIEditorViewportSlotToolSlot::Leading, true, true, 98.0f }, + { "input", "输入", UIEditorViewportSlotToolSlot::Trailing, true, true, 58.0f } }; } std::vector BuildStatusSegments() const { return { - { "hover", std::string("Hover ") + BoolText(m_bridgeFrame.hovered), UIEditorStatusBarSlot::Leading, {}, true, true, 96.0f }, - { "focus", std::string("Focus ") + BoolText(m_bridgeFrame.focused), UIEditorStatusBarSlot::Leading, {}, true, false, 92.0f }, - { "capture", std::string("Capture ") + BoolText(m_bridgeFrame.captured), UIEditorStatusBarSlot::Trailing, {}, true, true, 110.0f }, - { "wheel", "Wheel " + FormatFloat(m_bridgeFrame.wheelDelta), UIEditorStatusBarSlot::Trailing, {}, true, false, 86.0f } + { "hover", std::string("悬停 ") + BoolText(m_bridgeFrame.hovered), UIEditorStatusBarSlot::Leading, {}, true, true, 96.0f }, + { "focus", std::string("焦点 ") + BoolText(m_bridgeFrame.focused), UIEditorStatusBarSlot::Leading, {}, true, false, 92.0f }, + { "capture", std::string("捕获 ") + BoolText(m_bridgeFrame.captured), UIEditorStatusBarSlot::Trailing, {}, true, true, 110.0f }, + { "wheel", "滚轮 " + FormatFloat(m_bridgeFrame.wheelDelta), UIEditorStatusBarSlot::Trailing, {}, true, false, 86.0f } }; } @@ -645,7 +645,7 @@ private: const float buttonHeight = 34.0f; m_buttons = { - { ActionId::Reset, "Reset", UIRect(m_controlsRect.x + 16.0f, m_controlsRect.y + 54.0f, m_controlsRect.width - 32.0f, buttonHeight) }, + { ActionId::Reset, "重置", UIRect(m_controlsRect.x + 16.0f, m_controlsRect.y + 54.0f, m_controlsRect.width - 32.0f, buttonHeight) }, { ActionId::Capture, "截图", UIRect(m_controlsRect.x + 16.0f, m_controlsRect.y + 98.0f, m_controlsRect.width - 32.0f, buttonHeight) } }; @@ -676,25 +676,25 @@ private: m_layout.inputRect, frameEvents); if (m_bridgeFrame.focusLost) { - m_lastResult = "FocusLost"; + m_lastResult = "焦点丢失"; } else if (m_bridgeFrame.focusGained) { - m_lastResult = "FocusGained"; + m_lastResult = "获得焦点"; } else if (m_bridgeFrame.captureStarted) { - m_lastResult = "CaptureStarted"; + m_lastResult = "开始捕获"; } else if (m_bridgeFrame.captureEnded) { - m_lastResult = "CaptureEnded"; + m_lastResult = "结束捕获"; } else if (!m_bridgeFrame.characters.empty()) { - m_lastResult = "Char " + DescribeCharacters(m_bridgeFrame); + m_lastResult = "字符 " + DescribeCharacters(m_bridgeFrame); } else if (!m_bridgeFrame.pressedKeyCodes.empty()) { - m_lastResult = "KeyDown " + FormatKeyCodes(m_bridgeFrame.pressedKeyCodes); + m_lastResult = "按键按下 " + FormatKeyCodes(m_bridgeFrame.pressedKeyCodes); } else if (!m_bridgeFrame.releasedKeyCodes.empty()) { - m_lastResult = "KeyUp " + FormatKeyCodes(m_bridgeFrame.releasedKeyCodes); + m_lastResult = "按键抬起 " + FormatKeyCodes(m_bridgeFrame.releasedKeyCodes); } else if (m_bridgeFrame.wheelDelta != 0.0f) { - m_lastResult = "Wheel " + std::to_string(static_cast(m_bridgeFrame.wheelDelta)); + m_lastResult = "滚轮 " + std::to_string(static_cast(m_bridgeFrame.wheelDelta)); } else if (m_bridgeFrame.pointerPressedInside) { - m_lastResult = "PointerDownInside"; + m_lastResult = "内部按下"; } else if (m_bridgeFrame.pointerReleasedInside) { - m_lastResult = "PointerUpInside"; + m_lastResult = "内部抬起"; } UIEditorViewportSlotState slotState = {}; @@ -711,35 +711,35 @@ private: drawList, m_introRect, "测试功能:ViewportInputBridge", - "只验证 Editor viewport 输入桥,不混入 Scene/Game 业务。"); + "只验证 Editor viewport 输入桥接,不混入 Scene/Game 业务。"); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 66.0f), - "检查1:hover / focus / capture / local 坐标。", + "检查:hover / focus / capture / local 坐标。", kTextMuted, 11.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 86.0f), - "检查2:surface 内左键按下后,Focus + Capture 进入。", + "检查:surface 内左键按下后,Focus + Capture 进入。", kTextMuted, 11.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 106.0f), - "检查3:drag 出 surface 后,Capture 保留,Local Pos 继续更新。", + "检查:drag 出 surface 后,Capture 保留,Local Pos 继续更新。", kTextMuted, 11.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 126.0f), - "检查4:release 后,Capture 必须释放。", + "检查:release 后,Capture 必须释放。", kTextMuted, 11.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 146.0f), - "检查5:滚轮 / 按键 / 字符只在 Focus 下进入 frame。", + "检查:滚轮 / 按键 / 字符只在 Focus 下进入 frame。", kTextMuted, 11.0f); drawList.AddText( UIPoint(m_introRect.x + 16.0f, m_introRect.y + 166.0f), - "操作:hover,click,drag,wheel,A/W/F/Space,输入字符。", + "操作:hover、click、drag、wheel、A/W/F/Space,并输入字符。", kTextMuted, 11.0f); drawList.AddText( @@ -753,7 +753,7 @@ private: kTextWeak, 11.0f); - DrawCard(drawList, m_controlsRect, "操作", "只保留 Reset / 截图两个辅助操作。"); + DrawCard(drawList, m_controlsRect, "操作", "只保留 Reset / 截图 两个辅助操作。"); for (const ButtonState& button : m_buttons) { DrawButton(drawList, button); } @@ -796,7 +796,7 @@ private: addStateLine("Result: " + m_lastResult, kTextMuted); addStateLine(captureSummary, kTextWeak); - DrawCard(drawList, m_previewRect, "Preview", "这里只放一个 ViewportSlot,用它承载输入边界。"); + DrawCard(drawList, m_previewRect, "预览", "这里只放一个 ViewportSlot,用它承载输入边界。"); drawList.AddFilledRect( UIRect( m_previewRect.x + 12.0f, diff --git a/tests/UI/Editor/unit/CMakeLists.txt b/tests/UI/Editor/unit/CMakeLists.txt index 7df7bff6..6a3d22b8 100644 --- a/tests/UI/Editor/unit/CMakeLists.txt +++ b/tests/UI/Editor/unit/CMakeLists.txt @@ -18,21 +18,24 @@ set(EDITOR_UI_UNIT_TEST_SOURCES test_ui_editor_shell_interaction.cpp test_ui_editor_collection_primitives.cpp test_ui_editor_field_row_layout.cpp - test_ui_editor_theme.cpp + test_ui_editor_hosted_field_builders.cpp test_ui_editor_bool_field.cpp test_ui_editor_bool_field_interaction.cpp test_ui_editor_color_field.cpp test_ui_editor_color_field_interaction.cpp + test_ui_editor_asset_field.cpp + test_ui_editor_asset_field_interaction.cpp test_ui_editor_dock_host.cpp test_ui_editor_inline_rename_session.cpp test_ui_editor_list_view.cpp test_ui_editor_list_view_interaction.cpp - test_ui_editor_panel_chrome.cpp test_ui_editor_panel_frame.cpp test_ui_editor_enum_field.cpp test_ui_editor_enum_field_interaction.cpp test_ui_editor_number_field.cpp test_ui_editor_number_field_interaction.cpp + test_ui_editor_object_field.cpp + test_ui_editor_object_field_interaction.cpp test_ui_editor_text_field.cpp test_ui_editor_text_field_interaction.cpp test_ui_editor_vector2_field.cpp @@ -78,6 +81,16 @@ target_include_directories(editor_ui_tests if(MSVC) target_compile_options(editor_ui_tests PRIVATE /utf-8 /FS) + set_target_properties(editor_ui_tests PROPERTIES + MSVC_DEBUG_INFORMATION_FORMAT "$<$:Embedded>" + COMPILE_PDB_NAME "editor_ui_tests-compile" + COMPILE_PDB_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb" + COMPILE_PDB_OUTPUT_DIRECTORY_DEBUG "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/Debug" + COMPILE_PDB_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/Release" + COMPILE_PDB_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/MinSizeRel" + COMPILE_PDB_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/RelWithDebInfo" + VS_GLOBAL_UseMultiToolTask "false" + ) set_property(TARGET editor_ui_tests PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") endif() diff --git a/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp b/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp index 4c7e1a29..3f43720f 100644 --- a/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp +++ b/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp @@ -1,8 +1,8 @@ #include -#include "Core/EditorShellAsset.h" +#include "Shell/EditorShellAsset.h" -#include +#include #include @@ -35,6 +35,7 @@ TEST(EditorShellAssetValidationTest, DefaultShellAssetPassesValidation) { const auto validation = ValidateEditorShellAsset(shellAsset); EXPECT_TRUE(validation.IsValid()) << validation.message; + EXPECT_TRUE(shellAsset.documentPath.empty()); ASSERT_EQ( shellAsset.shellDefinition.workspacePresentations.size(), shellAsset.panelRegistry.panels.size()); diff --git a/tests/UI/Editor/unit/test_structured_editor_shell.cpp b/tests/UI/Editor/unit/test_structured_editor_shell.cpp index 12cf17c7..a0044d9a 100644 --- a/tests/UI/Editor/unit/test_structured_editor_shell.cpp +++ b/tests/UI/Editor/unit/test_structured_editor_shell.cpp @@ -2,16 +2,13 @@ #include "Application.h" -#include -#include -#include +#include +#include +#include #include -#include -#include #include #include -#include #ifndef XCUIEDITOR_REPO_ROOT #define XCUIEDITOR_REPO_ROOT "." @@ -29,16 +26,9 @@ using XCEngine::UI::Editor::AreUIEditorWorkspaceSessionsEquivalent; using XCEngine::UI::Editor::UIEditorCommandPanelSource; using XCEngine::UI::Editor::UIEditorMenuItemKind; using XCEngine::UI::Editor::UIEditorWorkspaceCommandKind; -using XCEngine::UI::UIDrawCommand; -using XCEngine::UI::UIDrawCommandType; -using XCEngine::UI::UIDrawData; using XCEngine::UI::UIInputEventType; using XCEngine::UI::UIShortcutBinding; using XCEngine::UI::UIShortcutScope; -using XCEngine::UI::Runtime::UIScreenAsset; -using XCEngine::UI::Runtime::UIScreenFrameInput; -using XCEngine::UI::Runtime::UIScreenPlayer; -using XCEngine::UI::Runtime::UIDocumentScreenHost; std::filesystem::path RepoRootPath() { std::string root = XCUIEDITOR_REPO_ROOT; @@ -48,30 +38,6 @@ std::filesystem::path RepoRootPath() { return std::filesystem::path(root).lexically_normal(); } -bool DrawDataContainsText(const UIDrawData& drawData, const std::string& text) { - for (const auto& drawList : drawData.GetDrawLists()) { - for (const UIDrawCommand& command : drawList.GetCommands()) { - if (command.type == UIDrawCommandType::Text && command.text == text) { - return true; - } - } - } - - return false; -} - -bool ContainsPathWithFilename( - const std::vector& paths, - const char* expectedFileName) { - for (const std::string& path : paths) { - if (std::filesystem::path(path).filename() == expectedFileName) { - return true; - } - } - - return false; -} - bool PanelRegistriesMatch( const XCEngine::UI::Editor::UIEditorPanelRegistry& lhs, const XCEngine::UI::Editor::UIEditorPanelRegistry& rhs) { @@ -157,35 +123,14 @@ UIShortcutBinding MakeBinding(std::string commandId, KeyCode keyCode) { } // namespace -TEST(EditorUIStructuredShellTest, AuthoredEditorShellLoadsFromRepositoryResources) { +TEST(EditorUIStructuredShellTest, StructuredEditorShellDoesNotRequireRepositoryXCUIDocument) { const auto shell = BuildDefaultEditorShellAsset(RepoRootPath()); const auto binding = BuildStructuredEditorShellBinding(shell); ASSERT_TRUE(binding.IsValid()) << binding.assetValidation.message; - ASSERT_TRUE(std::filesystem::exists(std::filesystem::path(binding.screenAsset.documentPath))); - ASSERT_TRUE(std::filesystem::exists(std::filesystem::path(binding.screenAsset.themePath))); - - UIDocumentScreenHost host = {}; - UIScreenPlayer player(host); - - ASSERT_TRUE(player.Load(binding.screenAsset)) << player.GetLastError(); - ASSERT_NE(player.GetDocument(), nullptr); - EXPECT_TRUE(player.GetDocument()->hasThemeDocument); - EXPECT_TRUE(ContainsPathWithFilename(player.GetDocument()->dependencies, "editor_shell.xctheme")); - - UIScreenFrameInput input = {}; - input.viewportRect = XCEngine::UI::UIRect(0.0f, 0.0f, 1440.0f, 900.0f); - input.frameIndex = 1u; - input.focused = true; - - const auto& frame = player.Update(input); - EXPECT_TRUE(frame.stats.documentLoaded); - EXPECT_GE(frame.stats.nodeCount, 2u); - EXPECT_FALSE(DrawDataContainsText(frame.drawData, "XCUI Editor Layer")); - EXPECT_FALSE(DrawDataContainsText(frame.drawData, "Left Pane Host")); - EXPECT_FALSE(DrawDataContainsText(frame.drawData, "Primary Workspace Host")); - EXPECT_FALSE(DrawDataContainsText(frame.drawData, "Right Pane Host")); - EXPECT_FALSE(DrawDataContainsText(frame.drawData, "Bottom Pane Host")); + EXPECT_TRUE(binding.screenAsset.documentPath.empty()); + EXPECT_TRUE(shell.documentPath.empty()); + EXPECT_TRUE(binding.screenAsset.themePath.empty()); } TEST(EditorUIStructuredShellTest, StructuredShellBindingUsesEditorShellAssetAsSingleSource) { @@ -214,8 +159,9 @@ TEST(EditorUIStructuredShellTest, StructuredShellBindingUsesEditorShellAssetAsSi ASSERT_TRUE(binding.IsValid()) << binding.assetValidation.message; EXPECT_EQ(binding.screenAsset.screenId, shell.screenId); - EXPECT_EQ(std::filesystem::path(binding.screenAsset.documentPath), shell.documentPath); - EXPECT_EQ(std::filesystem::path(binding.screenAsset.themePath), shell.themePath); + EXPECT_TRUE(binding.screenAsset.documentPath.empty()); + EXPECT_TRUE(shell.documentPath.empty()); + EXPECT_TRUE(binding.screenAsset.themePath.empty()); EXPECT_TRUE(PanelRegistriesMatch(binding.workspaceController.GetPanelRegistry(), shell.panelRegistry)); EXPECT_TRUE(AreUIEditorWorkspaceModelsEquivalent(binding.workspaceController.GetWorkspace(), shell.workspace)); diff --git a/tests/UI/Editor/unit/test_ui_editor_asset_field.cpp b/tests/UI/Editor/unit/test_ui_editor_asset_field.cpp new file mode 100644 index 00000000..1522ab9e --- /dev/null +++ b/tests/UI/Editor/unit/test_ui_editor_asset_field.cpp @@ -0,0 +1,123 @@ +#include + +#include +#include + +namespace { + +using XCEngine::UI::UIDrawCommandType; +using XCEngine::UI::UIDrawData; +using XCEngine::UI::UIPoint; +using XCEngine::UI::UIRect; +using XCEngine::UI::Editor::Widgets::AppendUIEditorAssetFieldBackground; +using XCEngine::UI::Editor::Widgets::AppendUIEditorAssetFieldForeground; +using XCEngine::UI::Editor::Widgets::BuildUIEditorAssetFieldLayout; +using XCEngine::UI::Editor::Widgets::HasUIEditorAssetFieldValue; +using XCEngine::UI::Editor::Widgets::HitTestUIEditorAssetField; +using XCEngine::UI::Editor::Widgets::ResolveUIEditorAssetFieldValueText; +using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldHitTargetKind; +using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldSpec; +using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldState; + +TEST(UIEditorAssetFieldTest, ValueTextUsesDisplayNameAndFallsBackToEmptyText) { + UIEditorAssetFieldSpec assignedSpec = {}; + assignedSpec.displayName = "Crate_Albedo"; + assignedSpec.emptyText = "None (Texture)"; + + EXPECT_TRUE(HasUIEditorAssetFieldValue(assignedSpec)); + EXPECT_EQ(ResolveUIEditorAssetFieldValueText(assignedSpec), "Crate_Albedo"); + + UIEditorAssetFieldSpec emptySpec = {}; + emptySpec.emptyText = "None (Texture)"; + + EXPECT_FALSE(HasUIEditorAssetFieldValue(emptySpec)); + EXPECT_EQ(ResolveUIEditorAssetFieldValueText(emptySpec), "None (Texture)"); +} + +TEST(UIEditorAssetFieldTest, LayoutReservesPreviewStatusAndActionButtons) { + UIEditorAssetFieldSpec spec = {}; + spec.fieldId = "material.base_color"; + spec.label = "Base Map"; + spec.assetId = "assets/textures/crate_albedo"; + spec.displayName = "Crate_Albedo"; + spec.statusText = "Ready"; + + const auto layout = BuildUIEditorAssetFieldLayout(UIRect(0.0f, 0.0f, 360.0f, 22.0f), spec); + + EXPECT_FLOAT_EQ(layout.controlRect.x, 236.0f); + EXPECT_FLOAT_EQ(layout.valueRect.y, 1.0f); + EXPECT_GT(layout.previewRect.width, 0.0f); + EXPECT_GT(layout.statusBadgeRect.width, 0.0f); + EXPECT_FLOAT_EQ(layout.pickerRect.width, 20.0f); + EXPECT_FLOAT_EQ(layout.clearRect.width, 20.0f); + EXPECT_GT(layout.textRect.width, 0.0f); +} + +TEST(UIEditorAssetFieldTest, HitTestResolvesClearPickerAndValueBox) { + UIEditorAssetFieldSpec spec = {}; + spec.assetId = "assets/textures/crate_albedo"; + spec.displayName = "Crate_Albedo"; + spec.statusText = "Ready"; + + const auto layout = BuildUIEditorAssetFieldLayout(UIRect(0.0f, 0.0f, 360.0f, 22.0f), spec); + + EXPECT_EQ( + HitTestUIEditorAssetField( + layout, + UIPoint(layout.clearRect.x + 2.0f, layout.clearRect.y + 2.0f)).kind, + UIEditorAssetFieldHitTargetKind::ClearButton); + EXPECT_EQ( + HitTestUIEditorAssetField( + layout, + UIPoint(layout.pickerRect.x + 2.0f, layout.pickerRect.y + 2.0f)).kind, + UIEditorAssetFieldHitTargetKind::PickerButton); + EXPECT_EQ( + HitTestUIEditorAssetField( + layout, + UIPoint(layout.textRect.x + 2.0f, layout.textRect.y + 2.0f)).kind, + UIEditorAssetFieldHitTargetKind::ValueBox); +} + +TEST(UIEditorAssetFieldTest, DrawCommandsContainPreviewStatusAndActionGlyphs) { + UIEditorAssetFieldSpec spec = {}; + spec.label = "Base Map"; + spec.assetId = "assets/textures/crate_albedo"; + spec.displayName = "Crate_Albedo"; + spec.statusText = "Ready"; + UIEditorAssetFieldState state = {}; + + UIDrawData drawData = {}; + auto& drawList = drawData.EmplaceDrawList("AssetField"); + const auto layout = BuildUIEditorAssetFieldLayout(UIRect(0.0f, 0.0f, 360.0f, 22.0f), spec); + AppendUIEditorAssetFieldBackground(drawList, layout, spec, state); + AppendUIEditorAssetFieldForeground(drawList, layout, spec, state); + + bool foundLabel = false; + bool foundValue = false; + bool foundStatus = false; + bool foundPicker = false; + bool foundClear = false; + bool foundPreviewGradient = false; + for (const auto& command : drawList.GetCommands()) { + if (command.type == UIDrawCommandType::FilledRectLinearGradient && + command.rect.x == layout.previewRect.x) { + foundPreviewGradient = true; + } + if (command.type == UIDrawCommandType::Text) { + foundLabel = foundLabel || command.text == "Base Map"; + foundValue = foundValue || command.text == "Crate_Albedo"; + foundStatus = foundStatus || command.text == "Ready"; + foundPicker = foundPicker || command.text == "o"; + foundClear = foundClear || command.text == "X"; + } + } + + EXPECT_TRUE(foundPreviewGradient); + EXPECT_TRUE(foundLabel); + EXPECT_TRUE(foundValue); + EXPECT_TRUE(foundStatus); + EXPECT_TRUE(foundPicker); + EXPECT_TRUE(foundClear); +} + +} // namespace diff --git a/tests/UI/Editor/unit/test_ui_editor_asset_field_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_asset_field_interaction.cpp new file mode 100644 index 00000000..796f9f97 --- /dev/null +++ b/tests/UI/Editor/unit/test_ui_editor_asset_field_interaction.cpp @@ -0,0 +1,189 @@ +#include + +#include + +#include + +namespace { + +using XCEngine::Input::KeyCode; +using XCEngine::UI::UIInputEvent; +using XCEngine::UI::UIInputEventType; +using XCEngine::UI::UIPoint; +using XCEngine::UI::UIPointerButton; +using XCEngine::UI::UIRect; +using XCEngine::UI::Editor::UIEditorAssetFieldInteractionState; +using XCEngine::UI::Editor::UpdateUIEditorAssetFieldInteraction; +using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldHitTargetKind; +using XCEngine::UI::Editor::Widgets::UIEditorAssetFieldSpec; + +UIInputEvent MakePointer(UIInputEventType type, float x, float y, UIPointerButton button = UIPointerButton::None) { + UIInputEvent event = {}; + event.type = type; + event.position = UIPoint(x, y); + event.pointerButton = button; + return event; +} + +UIInputEvent MakeKey(KeyCode keyCode) { + UIInputEvent event = {}; + event.type = UIInputEventType::KeyDown; + event.keyCode = static_cast(keyCode); + return event; +} + +UIEditorAssetFieldSpec MakeAssignedSpec() { + UIEditorAssetFieldSpec spec = {}; + spec.fieldId = "material.base_color"; + spec.label = "Base Map"; + spec.assetId = "assets/textures/crate_albedo"; + spec.displayName = "Crate_Albedo"; + spec.statusText = "Ready"; + return spec; +} + +} // namespace + +TEST(UIEditorAssetFieldInteractionTest, ClickPickerRequestsPickerWithoutMutatingValue) { + UIEditorAssetFieldSpec spec = MakeAssignedSpec(); + UIEditorAssetFieldInteractionState state = {}; + + auto frame = UpdateUIEditorAssetFieldInteraction( + state, + spec, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + {}); + + frame = UpdateUIEditorAssetFieldInteraction( + state, + spec, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + { + MakePointer( + UIInputEventType::PointerButtonDown, + frame.layout.pickerRect.x + 2.0f, + frame.layout.pickerRect.y + 2.0f, + UIPointerButton::Left), + MakePointer( + UIInputEventType::PointerButtonUp, + frame.layout.pickerRect.x + 2.0f, + frame.layout.pickerRect.y + 2.0f, + UIPointerButton::Left) + }); + + EXPECT_TRUE(frame.result.pickerRequested); + EXPECT_TRUE(frame.result.consumed); + EXPECT_EQ(frame.result.hitTarget.kind, UIEditorAssetFieldHitTargetKind::PickerButton); + EXPECT_EQ(spec.assetId, "assets/textures/crate_albedo"); + EXPECT_EQ(spec.displayName, "Crate_Albedo"); + EXPECT_TRUE(state.fieldState.focused); +} + +TEST(UIEditorAssetFieldInteractionTest, ClickClearClearsAssignedValue) { + UIEditorAssetFieldSpec spec = MakeAssignedSpec(); + UIEditorAssetFieldInteractionState state = {}; + + auto frame = UpdateUIEditorAssetFieldInteraction( + state, + spec, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + {}); + + frame = UpdateUIEditorAssetFieldInteraction( + state, + spec, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + { + MakePointer( + UIInputEventType::PointerButtonDown, + frame.layout.clearRect.x + 2.0f, + frame.layout.clearRect.y + 2.0f, + UIPointerButton::Left), + MakePointer( + UIInputEventType::PointerButtonUp, + frame.layout.clearRect.x + 2.0f, + frame.layout.clearRect.y + 2.0f, + UIPointerButton::Left) + }); + + EXPECT_TRUE(frame.result.clearRequested); + EXPECT_TRUE(frame.result.valueChanged); + EXPECT_EQ(frame.result.assetIdBefore, "assets/textures/crate_albedo"); + EXPECT_EQ(frame.result.displayNameBefore, "Crate_Albedo"); + EXPECT_TRUE(spec.assetId.empty()); + EXPECT_TRUE(spec.displayName.empty()); + EXPECT_TRUE(spec.statusText.empty()); +} + +TEST(UIEditorAssetFieldInteractionTest, ClickValueBoxRequestsActivate) { + UIEditorAssetFieldSpec spec = MakeAssignedSpec(); + UIEditorAssetFieldInteractionState state = {}; + + auto frame = UpdateUIEditorAssetFieldInteraction( + state, + spec, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + {}); + + frame = UpdateUIEditorAssetFieldInteraction( + state, + spec, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + { + MakePointer( + UIInputEventType::PointerButtonDown, + frame.layout.textRect.x + 2.0f, + frame.layout.textRect.y + 2.0f, + UIPointerButton::Left), + MakePointer( + UIInputEventType::PointerButtonUp, + frame.layout.textRect.x + 2.0f, + frame.layout.textRect.y + 2.0f, + UIPointerButton::Left) + }); + + EXPECT_TRUE(frame.result.activateRequested); + EXPECT_TRUE(frame.result.consumed); + EXPECT_EQ(frame.result.hitTarget.kind, UIEditorAssetFieldHitTargetKind::ValueBox); +} + +TEST(UIEditorAssetFieldInteractionTest, KeyboardEnterRequestsPickerAndDeleteClearsFocusedValue) { + UIEditorAssetFieldSpec spec = MakeAssignedSpec(); + UIEditorAssetFieldInteractionState state = {}; + state.fieldState.focused = true; + + auto frame = UpdateUIEditorAssetFieldInteraction( + state, + spec, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + { MakeKey(KeyCode::Enter) }); + EXPECT_TRUE(frame.result.pickerRequested); + EXPECT_EQ(frame.result.hitTarget.kind, UIEditorAssetFieldHitTargetKind::PickerButton); + + frame = UpdateUIEditorAssetFieldInteraction( + state, + spec, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + { MakeKey(KeyCode::Delete) }); + EXPECT_TRUE(frame.result.clearRequested); + EXPECT_TRUE(frame.result.valueChanged); + EXPECT_TRUE(spec.assetId.empty()); +} + +TEST(UIEditorAssetFieldInteractionTest, ReadOnlyFieldIgnoresPickerAndClearRequests) { + UIEditorAssetFieldSpec spec = MakeAssignedSpec(); + spec.readOnly = true; + UIEditorAssetFieldInteractionState state = {}; + state.fieldState.focused = true; + + auto frame = UpdateUIEditorAssetFieldInteraction( + state, + spec, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + { MakeKey(KeyCode::Enter), MakeKey(KeyCode::Delete) }); + + EXPECT_FALSE(frame.result.pickerRequested); + EXPECT_FALSE(frame.result.clearRequested); + EXPECT_FALSE(frame.result.valueChanged); + EXPECT_EQ(spec.assetId, "assets/textures/crate_albedo"); +} diff --git a/tests/UI/Editor/unit/test_ui_editor_bool_field.cpp b/tests/UI/Editor/unit/test_ui_editor_bool_field.cpp index 54d8125c..d8eaac48 100644 --- a/tests/UI/Editor/unit/test_ui_editor_bool_field.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_bool_field.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_bool_field_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_bool_field_interaction.cpp index e69bd573..e9e6fed2 100644 --- a/tests/UI/Editor/unit/test_ui_editor_bool_field_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_bool_field_interaction.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include diff --git a/tests/UI/Editor/unit/test_ui_editor_collection_primitives.cpp b/tests/UI/Editor/unit/test_ui_editor_collection_primitives.cpp index cea8eed3..f0d54360 100644 --- a/tests/UI/Editor/unit/test_ui_editor_collection_primitives.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_collection_primitives.cpp @@ -1,25 +1,11 @@ #include -#include -#include #include namespace { -namespace Style = XCEngine::UI::Style; namespace UIWidgets = XCEngine::UI::Editor::Widgets; -Style::UITheme BuildEditorPrimitiveTheme() { - Style::UIThemeDefinition definition = {}; - definition.SetToken("space.cardInset", Style::UIStyleValue(14.0f)); - definition.SetToken("size.treeItemHeight", Style::UIStyleValue(30.0f)); - definition.SetToken("size.listItemHeight", Style::UIStyleValue(64.0f)); - definition.SetToken("size.fieldRowHeight", Style::UIStyleValue(36.0f)); - definition.SetToken("size.propertySectionHeight", Style::UIStyleValue(156.0f)); - definition.SetToken("size.treeIndent", Style::UIStyleValue(20.0f)); - return Style::BuildTheme(definition); -} - TEST(UIEditorCollectionPrimitivesTest, ClassifyAndFlagsMatchEditorCollectionTags) { using Kind = UIWidgets::UIEditorCollectionPrimitiveKind; @@ -55,30 +41,21 @@ TEST(UIEditorCollectionPrimitivesTest, ClassifyAndFlagsMatchEditorCollectionTags EXPECT_FALSE(UIWidgets::IsUIEditorCollectionPrimitiveHoverable(Kind::TreeView)); } -TEST(UIEditorCollectionPrimitivesTest, ResolveMetricsUseThemeTokensAndFallbacks) { +TEST(UIEditorCollectionPrimitivesTest, ResolveMetricsUseFixedEditorDefaults) { using Kind = UIWidgets::UIEditorCollectionPrimitiveKind; - const Style::UITheme themed = BuildEditorPrimitiveTheme(); - const Style::UITheme fallback = Style::UITheme(); + EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::TreeView), 12.0f); + EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::ListView), 12.0f); + EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::PropertySection), 12.0f); + EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::ScrollView), 0.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::TreeView, themed), 14.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::ListView, themed), 14.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::PropertySection, themed), 14.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitivePadding(Kind::ScrollView, themed), 0.0f); + EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::TreeItem), 28.0f); + EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::ListItem), 60.0f); + EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::FieldRow), 32.0f); + EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::PropertySection), 148.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::TreeItem, themed), 30.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::ListItem, themed), 64.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::FieldRow, themed), 36.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::PropertySection, themed), 156.0f); - - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::TreeItem, fallback), 28.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::ListItem, fallback), 60.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::FieldRow, fallback), 32.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveDefaultHeight(Kind::PropertySection, fallback), 148.0f); - - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(Kind::TreeItem, themed, 2.0f), 40.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(Kind::TreeItem, fallback, 2.0f), 36.0f); - EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(Kind::ListItem, themed, 2.0f), 0.0f); + EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(Kind::TreeItem, 2.0f), 36.0f); + EXPECT_FLOAT_EQ(UIWidgets::ResolveUIEditorCollectionPrimitiveIndent(Kind::ListItem, 2.0f), 0.0f); } } // namespace diff --git a/tests/UI/Editor/unit/test_ui_editor_color_field.cpp b/tests/UI/Editor/unit/test_ui_editor_color_field.cpp index 0dad242e..524fc7f8 100644 --- a/tests/UI/Editor/unit/test_ui_editor_color_field.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_color_field.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_color_field_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_color_field_interaction.cpp index 68e4b6d6..ffe10e86 100644 --- a/tests/UI/Editor/unit/test_ui_editor_color_field_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_color_field_interaction.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include diff --git a/tests/UI/Editor/unit/test_ui_editor_dock_host.cpp b/tests/UI/Editor/unit/test_ui_editor_dock_host.cpp index b5c7232f..d1bfac41 100644 --- a/tests/UI/Editor/unit/test_ui_editor_dock_host.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_dock_host.cpp @@ -1,10 +1,10 @@ #include #include -#include -#include -#include -#include +#include +#include +#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_dock_host_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_dock_host_interaction.cpp index d7e3c193..32fffb53 100644 --- a/tests/UI/Editor/unit/test_ui_editor_dock_host_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_dock_host_interaction.cpp @@ -1,9 +1,9 @@ #include #include -#include -#include -#include +#include +#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_enum_field.cpp b/tests/UI/Editor/unit/test_ui_editor_enum_field.cpp index 75ea4ef6..f942fade 100644 --- a/tests/UI/Editor/unit/test_ui_editor_enum_field.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_enum_field.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include namespace { @@ -29,7 +29,7 @@ TEST(UIEditorEnumFieldTest, LayoutKeepsInspectorControlColumnAndUnityArrowWidth) EXPECT_FLOAT_EQ(layout.controlRect.x, 236.0f); EXPECT_FLOAT_EQ(layout.valueRect.y, 1.0f); EXPECT_FLOAT_EQ(layout.valueRect.height, 20.0f); - EXPECT_FLOAT_EQ(layout.arrowRect.width, 20.0f); + EXPECT_FLOAT_EQ(layout.arrowRect.width, 14.0f); } TEST(UIEditorEnumFieldTest, HitTestResolvesArrowAndValueBox) { @@ -44,7 +44,7 @@ TEST(UIEditorEnumFieldTest, HitTestResolvesArrowAndValueBox) { UIEditorEnumFieldHitTargetKind::ValueBox); } -TEST(UIEditorEnumFieldTest, BackgroundAndForegroundEmitControlOnlyChromeAndCenteredText) { +TEST(UIEditorEnumFieldTest, BackgroundAndForegroundEmitInspectorChromeAndChevron) { UIEditorEnumFieldSpec spec = { "blend", "Blend", { "Opaque", "Cutout", "Fade" }, 1u, false }; UIEditorEnumFieldState state = {}; state.popupOpen = true; @@ -57,21 +57,53 @@ TEST(UIEditorEnumFieldTest, BackgroundAndForegroundEmitControlOnlyChromeAndCente AppendUIEditorEnumFieldForeground(drawList, layout, spec); const auto& commands = drawList.GetCommands(); - ASSERT_EQ(commands.size(), 9u); - EXPECT_EQ(commands[0].type, UIDrawCommandType::FilledRect); - EXPECT_EQ(commands[0].rect.x, layout.valueRect.x); - EXPECT_EQ(commands[1].type, UIDrawCommandType::RectOutline); - EXPECT_EQ(commands[2].type, UIDrawCommandType::PushClipRect); - EXPECT_EQ(commands[3].type, UIDrawCommandType::Text); - EXPECT_FLOAT_EQ(commands[3].position.y, 2.0f); - EXPECT_EQ(commands[4].type, UIDrawCommandType::PopClipRect); - EXPECT_EQ(commands[5].type, UIDrawCommandType::PushClipRect); - EXPECT_EQ(commands[6].type, UIDrawCommandType::Text); - EXPECT_FLOAT_EQ(commands[6].position.y, 1.0f); - EXPECT_EQ(commands[7].type, UIDrawCommandType::PopClipRect); - EXPECT_EQ(commands[8].type, UIDrawCommandType::Text); - EXPECT_EQ(commands[8].text, "V"); - EXPECT_FLOAT_EQ(commands[8].position.y, 2.0f); + std::size_t filledRectCount = 0u; + std::size_t rectOutlineCount = 0u; + std::size_t pushClipCount = 0u; + std::size_t popClipCount = 0u; + std::size_t lineCount = 0u; + bool foundLabelText = false; + bool foundValueText = false; + + for (const auto& command : commands) { + switch (command.type) { + case UIDrawCommandType::FilledRect: + ++filledRectCount; + break; + case UIDrawCommandType::RectOutline: + ++rectOutlineCount; + break; + case UIDrawCommandType::PushClipRect: + ++pushClipCount; + break; + case UIDrawCommandType::PopClipRect: + ++popClipCount; + break; + case UIDrawCommandType::Line: + ++lineCount; + break; + case UIDrawCommandType::Text: + if (command.text == "Blend") { + foundLabelText = true; + EXPECT_FLOAT_EQ(command.position.y, 2.0f); + } + if (command.text == "Cutout") { + foundValueText = true; + EXPECT_FLOAT_EQ(command.position.y, 1.0f); + } + break; + default: + break; + } + } + + EXPECT_EQ(filledRectCount, 2u); + EXPECT_EQ(rectOutlineCount, 1u); + EXPECT_EQ(pushClipCount, 2u); + EXPECT_EQ(popClipCount, 2u); + EXPECT_EQ(lineCount, 3u); + EXPECT_TRUE(foundLabelText); + EXPECT_TRUE(foundValueText); } } // namespace diff --git a/tests/UI/Editor/unit/test_ui_editor_enum_field_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_enum_field_interaction.cpp index dd4f5302..587e288d 100644 --- a/tests/UI/Editor/unit/test_ui_editor_enum_field_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_enum_field_interaction.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include diff --git a/tests/UI/Editor/unit/test_ui_editor_hosted_field_builders.cpp b/tests/UI/Editor/unit/test_ui_editor_hosted_field_builders.cpp new file mode 100644 index 00000000..677e2b5d --- /dev/null +++ b/tests/UI/Editor/unit/test_ui_editor_hosted_field_builders.cpp @@ -0,0 +1,156 @@ +#include + +#include + +namespace { + +namespace UI = XCEngine::UI; +namespace Editor = XCEngine::UI::Editor; + +TEST(UIEditorHostedFieldBuildersTest, HostedFieldBuildersInheritPropertyGridMetricsAndPalette) { + UI::Editor::Widgets::UIEditorPropertyGridMetrics propertyMetrics = {}; + propertyMetrics.fieldRowHeight = 25.0f; + propertyMetrics.horizontalPadding = 7.0f; + propertyMetrics.labelControlGap = 11.0f; + propertyMetrics.controlColumnStart = 210.0f; + propertyMetrics.labelTextInsetY = 4.0f; + propertyMetrics.labelFontSize = 10.0f; + propertyMetrics.valueTextInsetY = 3.0f; + propertyMetrics.valueFontSize = 12.0f; + propertyMetrics.tagFontSize = 9.0f; + propertyMetrics.valueBoxInsetY = 2.0f; + propertyMetrics.valueBoxInsetX = 5.0f; + propertyMetrics.cornerRounding = 1.0f; + propertyMetrics.valueBoxRounding = 2.0f; + propertyMetrics.borderThickness = 1.0f; + propertyMetrics.focusedBorderThickness = 2.0f; + + UI::Editor::Widgets::UIEditorPropertyGridPalette propertyPalette = {}; + propertyPalette.valueBoxColor = UI::UIColor(0.2f, 0.2f, 0.2f, 1.0f); + propertyPalette.valueBoxHoverColor = UI::UIColor(0.3f, 0.3f, 0.3f, 1.0f); + propertyPalette.valueBoxEditingColor = UI::UIColor(0.4f, 0.4f, 0.4f, 1.0f); + propertyPalette.valueBoxReadOnlyColor = UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); + propertyPalette.valueBoxBorderColor = UI::UIColor(0.5f, 0.5f, 0.5f, 1.0f); + propertyPalette.valueBoxEditingBorderColor = UI::UIColor(0.55f, 0.55f, 0.55f, 1.0f); + propertyPalette.labelTextColor = UI::UIColor(0.8f, 0.8f, 0.8f, 1.0f); + propertyPalette.valueTextColor = UI::UIColor(0.9f, 0.9f, 0.9f, 1.0f); + propertyPalette.readOnlyValueTextColor = UI::UIColor(0.6f, 0.6f, 0.6f, 1.0f); + propertyPalette.editTagColor = UI::UIColor(0.52f, 0.68f, 0.94f, 1.0f); + + const auto boolMetrics = Editor::BuildUIEditorPropertyGridBoolFieldMetrics(propertyMetrics); + const auto boolPalette = Editor::BuildUIEditorPropertyGridBoolFieldPalette(propertyPalette); + EXPECT_FLOAT_EQ(boolMetrics.rowHeight, 25.0f); + EXPECT_FLOAT_EQ(boolMetrics.controlTrailingInset, 5.0f); + EXPECT_FLOAT_EQ(boolMetrics.labelFontSize, 10.0f); + EXPECT_FLOAT_EQ(boolMetrics.checkboxGlyphFontSize, 12.0f); + EXPECT_FLOAT_EQ(boolPalette.checkboxColor.r, 0.2f); + EXPECT_FLOAT_EQ(boolPalette.labelColor.r, 0.8f); + + const auto numberMetrics = Editor::BuildUIEditorPropertyGridNumberFieldMetrics(propertyMetrics); + const auto numberPalette = Editor::BuildUIEditorPropertyGridNumberFieldPalette(propertyPalette); + EXPECT_FLOAT_EQ(numberMetrics.controlTrailingInset, 5.0f); + EXPECT_FLOAT_EQ(numberMetrics.controlInsetY, 2.0f); + EXPECT_FLOAT_EQ(numberMetrics.valueTextInsetX, 5.0f); + EXPECT_FLOAT_EQ(numberMetrics.valueFontSize, 12.0f); + EXPECT_FLOAT_EQ(numberPalette.valueBoxEditingColor.r, 0.4f); + EXPECT_FLOAT_EQ(numberPalette.readOnlyValueColor.r, 0.6f); + EXPECT_FLOAT_EQ(numberPalette.controlFocusedBorderColor.r, 0.55f); + + const auto textMetrics = Editor::BuildUIEditorPropertyGridTextFieldMetrics(propertyMetrics); + const auto textPalette = Editor::BuildUIEditorPropertyGridTextFieldPalette(propertyPalette); + EXPECT_FLOAT_EQ(textMetrics.controlTrailingInset, 5.0f); + EXPECT_FLOAT_EQ(textMetrics.controlInsetY, 2.0f); + EXPECT_FLOAT_EQ(textMetrics.valueTextInsetX, 5.0f); + EXPECT_FLOAT_EQ(textMetrics.valueFontSize, 12.0f); + EXPECT_FLOAT_EQ(textPalette.valueBoxEditingColor.r, 0.4f); + EXPECT_FLOAT_EQ(textPalette.readOnlyValueColor.r, 0.6f); + EXPECT_FLOAT_EQ(textPalette.controlFocusedBorderColor.r, 0.55f); + + const auto objectMetrics = Editor::BuildUIEditorPropertyGridObjectFieldMetrics(propertyMetrics); + const auto objectPalette = Editor::BuildUIEditorPropertyGridObjectFieldPalette(propertyPalette); + EXPECT_FLOAT_EQ(objectMetrics.controlTrailingInset, 5.0f); + EXPECT_FLOAT_EQ(objectMetrics.controlInsetY, 2.0f); + EXPECT_FLOAT_EQ(objectMetrics.valueTextInsetX, 5.0f); + EXPECT_FLOAT_EQ(objectMetrics.typeTextInsetX, 5.0f); + EXPECT_FLOAT_EQ(objectMetrics.typeTextInsetY, 3.0f); + EXPECT_FLOAT_EQ(objectMetrics.typeFontSize, 9.0f); + EXPECT_FLOAT_EQ(objectMetrics.buttonGlyphFontSize, 12.0f); + EXPECT_FLOAT_EQ(objectPalette.valueBoxColor.r, 0.2f); + EXPECT_FLOAT_EQ(objectPalette.buttonColor.r, 0.15f); + EXPECT_FLOAT_EQ(objectPalette.buttonHoverColor.r, 0.3f); + EXPECT_FLOAT_EQ(objectPalette.buttonActiveColor.r, 0.4f); + EXPECT_FLOAT_EQ(objectPalette.buttonGlyphColor.r, 0.9f); + EXPECT_FLOAT_EQ(objectPalette.separatorColor.r, 0.5f); + EXPECT_FLOAT_EQ(objectPalette.typeColor.r, 0.6f); + + const auto vector2Metrics = Editor::BuildUIEditorPropertyGridVector2FieldMetrics(propertyMetrics); + const auto vector2Palette = Editor::BuildUIEditorPropertyGridVector2FieldPalette(propertyPalette); + EXPECT_FLOAT_EQ(vector2Metrics.controlInsetY, 2.0f); + EXPECT_FLOAT_EQ(vector2Metrics.controlTrailingInset, 5.0f); + EXPECT_FLOAT_EQ(vector2Metrics.valueTextInsetX, 5.0f); + EXPECT_FLOAT_EQ(vector2Metrics.componentRounding, 2.0f); + EXPECT_FLOAT_EQ(vector2Palette.componentEditingColor.r, 0.4f); + EXPECT_FLOAT_EQ(vector2Palette.componentFocusedBorderColor.r, 0.55f); + + const auto vector3Metrics = Editor::BuildUIEditorPropertyGridVector3FieldMetrics(propertyMetrics); + const auto vector3Palette = Editor::BuildUIEditorPropertyGridVector3FieldPalette(propertyPalette); + EXPECT_FLOAT_EQ(vector3Metrics.controlInsetY, 2.0f); + EXPECT_FLOAT_EQ(vector3Metrics.controlTrailingInset, 5.0f); + EXPECT_FLOAT_EQ(vector3Metrics.valueTextInsetX, 5.0f); + EXPECT_FLOAT_EQ(vector3Metrics.componentRounding, 2.0f); + EXPECT_FLOAT_EQ(vector3Palette.componentEditingColor.r, 0.4f); + EXPECT_FLOAT_EQ(vector3Palette.componentFocusedBorderColor.r, 0.55f); + + const auto vector4Metrics = Editor::BuildUIEditorPropertyGridVector4FieldMetrics(propertyMetrics); + const auto vector4Palette = Editor::BuildUIEditorPropertyGridVector4FieldPalette(propertyPalette); + EXPECT_FLOAT_EQ(vector4Metrics.controlInsetY, 2.0f); + EXPECT_FLOAT_EQ(vector4Metrics.controlTrailingInset, 5.0f); + EXPECT_FLOAT_EQ(vector4Metrics.valueTextInsetX, 5.0f); + EXPECT_FLOAT_EQ(vector4Metrics.componentRounding, 2.0f); + EXPECT_FLOAT_EQ(vector4Palette.componentEditingColor.r, 0.4f); + EXPECT_FLOAT_EQ(vector4Palette.componentFocusedBorderColor.r, 0.55f); + + const auto enumMetrics = Editor::BuildUIEditorPropertyGridEnumFieldMetrics(propertyMetrics); + const auto enumPalette = Editor::BuildUIEditorPropertyGridEnumFieldPalette(propertyPalette); + EXPECT_FLOAT_EQ(enumMetrics.controlTrailingInset, 5.0f); + EXPECT_FLOAT_EQ(enumMetrics.controlInsetY, 2.0f); + EXPECT_FLOAT_EQ(enumMetrics.valueTextInsetX, 5.0f); + EXPECT_FLOAT_EQ(enumMetrics.dropdownArrowFontSize, 12.0f); + EXPECT_FLOAT_EQ(enumPalette.arrowColor.r, 0.9f); + + const auto colorMetrics = Editor::BuildUIEditorPropertyGridColorFieldMetrics(propertyMetrics); + const auto colorPalette = Editor::BuildUIEditorPropertyGridColorFieldPalette(propertyPalette); + EXPECT_FLOAT_EQ(colorMetrics.controlTrailingInset, 5.0f); + EXPECT_FLOAT_EQ(colorMetrics.swatchInsetY, 2.0f); + EXPECT_FLOAT_EQ(colorMetrics.labelFontSize, 10.0f); + EXPECT_FLOAT_EQ(colorMetrics.valueFontSize, 12.0f); + EXPECT_FLOAT_EQ(colorMetrics.popupHeaderHeight, 30.0f); + EXPECT_FLOAT_EQ(colorPalette.labelColor.r, 0.8f); + EXPECT_FLOAT_EQ(colorPalette.popupBorderColor.r, 0.15f); + EXPECT_FLOAT_EQ(colorPalette.popupHeaderColor.r, 0.43f); + EXPECT_FLOAT_EQ(colorPalette.swatchBorderColor.r, 0.5f); + + const auto assetMetrics = Editor::BuildUIEditorPropertyGridAssetFieldMetrics(propertyMetrics); + const auto assetPalette = Editor::BuildUIEditorPropertyGridAssetFieldPalette(propertyPalette); + EXPECT_FLOAT_EQ(assetMetrics.controlTrailingInset, 5.0f); + EXPECT_FLOAT_EQ(assetMetrics.controlInsetY, 2.0f); + EXPECT_FLOAT_EQ(assetMetrics.valueTextInsetX, 5.0f); + EXPECT_FLOAT_EQ(assetMetrics.previewGlyphFontSize, 12.0f); + EXPECT_FLOAT_EQ(assetMetrics.statusBadgeFontSize, 9.0f); + EXPECT_FLOAT_EQ(assetMetrics.actionGlyphFontSize, 12.0f); + EXPECT_FLOAT_EQ(assetMetrics.previewRounding, 2.0f); + EXPECT_FLOAT_EQ(assetMetrics.badgeRounding, 2.0f); + EXPECT_FLOAT_EQ(assetPalette.valueBoxActiveColor.r, 0.4f); + EXPECT_FLOAT_EQ(assetPalette.previewBaseColor.r, 0.2f); + EXPECT_FLOAT_EQ(assetPalette.previewEmptyColor.r, 0.15f); + EXPECT_FLOAT_EQ(assetPalette.previewGlyphColor.r, 0.9f); + EXPECT_FLOAT_EQ(assetPalette.statusBadgeColor.r, 0.52f); + EXPECT_FLOAT_EQ(assetPalette.statusBadgeColor.g, 0.68f); + EXPECT_FLOAT_EQ(assetPalette.statusBadgeBorderColor.r, 0.5f); + EXPECT_FLOAT_EQ(assetPalette.actionButtonColor.r, 0.15f); + EXPECT_FLOAT_EQ(assetPalette.actionButtonHoverColor.r, 0.3f); + EXPECT_FLOAT_EQ(assetPalette.actionButtonActiveColor.r, 0.4f); + EXPECT_FLOAT_EQ(assetPalette.clearGlyphColor.r, 0.6f); +} + +} // namespace diff --git a/tests/UI/Editor/unit/test_ui_editor_menu_bar.cpp b/tests/UI/Editor/unit/test_ui_editor_menu_bar.cpp index 4010eb85..b04fee3d 100644 --- a/tests/UI/Editor/unit/test_ui_editor_menu_bar.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_menu_bar.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_menu_model.cpp b/tests/UI/Editor/unit/test_ui_editor_menu_model.cpp index 55539ef3..1e2f5244 100644 --- a/tests/UI/Editor/unit/test_ui_editor_menu_model.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_menu_model.cpp @@ -1,7 +1,7 @@ #include -#include -#include +#include +#include #include diff --git a/tests/UI/Editor/unit/test_ui_editor_menu_popup.cpp b/tests/UI/Editor/unit/test_ui_editor_menu_popup.cpp index 5b0817e8..557f0f00 100644 --- a/tests/UI/Editor/unit/test_ui_editor_menu_popup.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_menu_popup.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_menu_session.cpp b/tests/UI/Editor/unit/test_ui_editor_menu_session.cpp index 3191136e..e34667d8 100644 --- a/tests/UI/Editor/unit/test_ui_editor_menu_session.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_menu_session.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include diff --git a/tests/UI/Editor/unit/test_ui_editor_number_field.cpp b/tests/UI/Editor/unit/test_ui_editor_number_field.cpp index 0c2ddadf..d273cd3c 100644 --- a/tests/UI/Editor/unit/test_ui_editor_number_field.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_number_field.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_number_field_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_number_field_interaction.cpp index 9a93a5bb..c30a3559 100644 --- a/tests/UI/Editor/unit/test_ui_editor_number_field_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_number_field_interaction.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include diff --git a/tests/UI/Editor/unit/test_ui_editor_object_field.cpp b/tests/UI/Editor/unit/test_ui_editor_object_field.cpp new file mode 100644 index 00000000..32a13a65 --- /dev/null +++ b/tests/UI/Editor/unit/test_ui_editor_object_field.cpp @@ -0,0 +1,122 @@ +#include + +#include +#include + +namespace { + +using XCEngine::UI::UIDrawCommandType; +using XCEngine::UI::UIPoint; +using XCEngine::UI::UIRect; +using XCEngine::UI::Editor::Widgets::AppendUIEditorObjectField; +using XCEngine::UI::Editor::Widgets::BuildUIEditorObjectFieldLayout; +using XCEngine::UI::Editor::Widgets::HitTestUIEditorObjectField; +using XCEngine::UI::Editor::Widgets::ResolveUIEditorObjectFieldDisplayText; +using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldHitTargetKind; +using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldSpec; +using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldState; + +TEST(UIEditorObjectFieldTest, DisplayTextFallsBackForEmptyAndUnnamedObjects) { + UIEditorObjectFieldSpec spec = {}; + spec.emptyText = "None (Object)"; + + EXPECT_EQ(ResolveUIEditorObjectFieldDisplayText(spec), "None (Object)"); + + spec.hasValue = true; + spec.objectName.clear(); + EXPECT_EQ(ResolveUIEditorObjectFieldDisplayText(spec), "(unnamed)"); + + spec.objectName = "Main Camera"; + EXPECT_EQ(ResolveUIEditorObjectFieldDisplayText(spec), "Main Camera"); +} + +TEST(UIEditorObjectFieldTest, LayoutReservesClearAndPickerButtonsWhenValueExists) { + UIEditorObjectFieldSpec spec = {}; + spec.fieldId = "target"; + spec.label = "Target"; + spec.hasValue = true; + spec.objectName = "Main Camera"; + spec.objectTypeName = "Camera"; + + const auto layout = BuildUIEditorObjectFieldLayout( + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + spec); + + EXPECT_FLOAT_EQ(layout.valueRect.x, 236.0f); + EXPECT_GT(layout.clearButtonRect.width, 0.0f); + EXPECT_GT(layout.pickerButtonRect.width, 0.0f); + EXPECT_GT(layout.typeRect.width, 0.0f); + EXPECT_EQ( + HitTestUIEditorObjectField( + layout, + UIPoint(layout.clearButtonRect.x + 2.0f, layout.clearButtonRect.y + 2.0f)).kind, + UIEditorObjectFieldHitTargetKind::ClearButton); + EXPECT_EQ( + HitTestUIEditorObjectField( + layout, + UIPoint(layout.pickerButtonRect.x + 2.0f, layout.pickerButtonRect.y + 2.0f)).kind, + UIEditorObjectFieldHitTargetKind::PickerButton); +} + +TEST(UIEditorObjectFieldTest, LayoutWithoutValueUsesEmptyDisplayAndOmitsClearButton) { + UIEditorObjectFieldSpec spec = {}; + spec.fieldId = "target"; + spec.label = "Target"; + spec.hasValue = false; + spec.emptyText = "None (GameObject)"; + + const auto layout = BuildUIEditorObjectFieldLayout( + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + spec); + + EXPECT_FLOAT_EQ(layout.clearButtonRect.width, 0.0f); + EXPECT_GT(layout.pickerButtonRect.width, 0.0f); + EXPECT_FLOAT_EQ(layout.typeRect.width, 0.0f); + EXPECT_EQ( + HitTestUIEditorObjectField( + layout, + UIPoint(layout.valueRect.x + 4.0f, layout.valueRect.y + 4.0f)).kind, + UIEditorObjectFieldHitTargetKind::ValueBox); +} + +TEST(UIEditorObjectFieldTest, DrawEmitsLabelValueTypeAndButtonGlyphs) { + UIEditorObjectFieldSpec spec = {}; + spec.fieldId = "target"; + spec.label = "Target"; + spec.hasValue = true; + spec.objectName = "Main Camera"; + spec.objectTypeName = "Camera"; + + UIEditorObjectFieldState state = {}; + XCEngine::UI::UIDrawData drawData = {}; + auto& drawList = drawData.EmplaceDrawList("ObjectField"); + AppendUIEditorObjectField( + drawList, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + spec, + state); + + bool hasLabel = false; + bool hasValue = false; + bool hasType = false; + bool hasClearGlyph = false; + bool hasPickerGlyph = false; + bool hasOutline = false; + for (const auto& command : drawList.GetCommands()) { + hasLabel = hasLabel || command.text == "Target"; + hasValue = hasValue || command.text == "Main Camera"; + hasType = hasType || command.text == "Camera"; + hasClearGlyph = hasClearGlyph || command.text == "X"; + hasPickerGlyph = hasPickerGlyph || command.text == "o"; + hasOutline = hasOutline || command.type == UIDrawCommandType::RectOutline; + } + + EXPECT_TRUE(hasLabel); + EXPECT_TRUE(hasValue); + EXPECT_TRUE(hasType); + EXPECT_TRUE(hasClearGlyph); + EXPECT_TRUE(hasPickerGlyph); + EXPECT_TRUE(hasOutline); +} + +} // namespace diff --git a/tests/UI/Editor/unit/test_ui_editor_object_field_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_object_field_interaction.cpp new file mode 100644 index 00000000..9acb2af0 --- /dev/null +++ b/tests/UI/Editor/unit/test_ui_editor_object_field_interaction.cpp @@ -0,0 +1,157 @@ +#include + +#include + +#include + +namespace { + +using XCEngine::Input::KeyCode; +using XCEngine::UI::UIInputEvent; +using XCEngine::UI::UIInputEventType; +using XCEngine::UI::UIPoint; +using XCEngine::UI::UIPointerButton; +using XCEngine::UI::UIRect; +using XCEngine::UI::Editor::UIEditorObjectFieldInteractionState; +using XCEngine::UI::Editor::UpdateUIEditorObjectFieldInteraction; +using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldHitTargetKind; +using XCEngine::UI::Editor::Widgets::UIEditorObjectFieldSpec; + +UIInputEvent MakePointer(UIInputEventType type, float x, float y, UIPointerButton button = UIPointerButton::None) { + UIInputEvent event = {}; + event.type = type; + event.position = UIPoint(x, y); + event.pointerButton = button; + return event; +} + +UIInputEvent MakeKey(KeyCode keyCode) { + UIInputEvent event = {}; + event.type = UIInputEventType::KeyDown; + event.keyCode = static_cast(keyCode); + return event; +} + +UIEditorObjectFieldSpec BuildSpec() { + UIEditorObjectFieldSpec spec = {}; + spec.fieldId = "target"; + spec.label = "Target"; + spec.hasValue = true; + spec.objectName = "Main Camera"; + spec.objectTypeName = "Camera"; + return spec; +} + +} // namespace + +TEST(UIEditorObjectFieldInteractionTest, ClickValueBoxRequestsActivateAndFocus) { + UIEditorObjectFieldSpec spec = BuildSpec(); + UIEditorObjectFieldInteractionState state = {}; + + auto frame = UpdateUIEditorObjectFieldInteraction( + state, + spec, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + {}); + + frame = UpdateUIEditorObjectFieldInteraction( + state, + spec, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + { + MakePointer(UIInputEventType::PointerButtonDown, frame.layout.valueRect.x + 4.0f, frame.layout.valueRect.y + 4.0f, UIPointerButton::Left), + MakePointer(UIInputEventType::PointerButtonUp, frame.layout.valueRect.x + 4.0f, frame.layout.valueRect.y + 4.0f, UIPointerButton::Left) + }); + + EXPECT_TRUE(frame.result.consumed); + EXPECT_TRUE(frame.result.activateRequested); + EXPECT_TRUE(state.fieldState.focused); + EXPECT_EQ(frame.result.hitTarget.kind, UIEditorObjectFieldHitTargetKind::ValueBox); +} + +TEST(UIEditorObjectFieldInteractionTest, ClickClearButtonRequestsClear) { + UIEditorObjectFieldSpec spec = BuildSpec(); + UIEditorObjectFieldInteractionState state = {}; + + auto frame = UpdateUIEditorObjectFieldInteraction( + state, + spec, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + {}); + + frame = UpdateUIEditorObjectFieldInteraction( + state, + spec, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + { + MakePointer(UIInputEventType::PointerButtonDown, frame.layout.clearButtonRect.x + 2.0f, frame.layout.clearButtonRect.y + 2.0f, UIPointerButton::Left), + MakePointer(UIInputEventType::PointerButtonUp, frame.layout.clearButtonRect.x + 2.0f, frame.layout.clearButtonRect.y + 2.0f, UIPointerButton::Left) + }); + + EXPECT_TRUE(frame.result.consumed); + EXPECT_TRUE(frame.result.clearRequested); + EXPECT_EQ(frame.result.hitTarget.kind, UIEditorObjectFieldHitTargetKind::ClearButton); +} + +TEST(UIEditorObjectFieldInteractionTest, KeyboardActivateAndClearFollowFocus) { + UIEditorObjectFieldSpec spec = BuildSpec(); + UIEditorObjectFieldInteractionState state = {}; + state.fieldState.focused = true; + + auto frame = UpdateUIEditorObjectFieldInteraction( + state, + spec, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + { MakeKey(KeyCode::Enter) }); + EXPECT_TRUE(frame.result.activateRequested); + EXPECT_TRUE(frame.result.consumed); + + frame = UpdateUIEditorObjectFieldInteraction( + state, + spec, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + { MakeKey(KeyCode::Delete) }); + EXPECT_TRUE(frame.result.clearRequested); + EXPECT_TRUE(frame.result.consumed); +} + +TEST(UIEditorObjectFieldInteractionTest, ReadOnlyFieldSuppressesActivateAndClear) { + UIEditorObjectFieldSpec spec = BuildSpec(); + spec.readOnly = true; + UIEditorObjectFieldInteractionState state = {}; + state.fieldState.focused = true; + + auto frame = UpdateUIEditorObjectFieldInteraction( + state, + spec, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + { MakeKey(KeyCode::Space), MakeKey(KeyCode::Delete) }); + + EXPECT_FALSE(frame.result.activateRequested); + EXPECT_FALSE(frame.result.clearRequested); +} + +TEST(UIEditorObjectFieldInteractionTest, FocusLostClearsHoverAndActiveState) { + UIEditorObjectFieldSpec spec = BuildSpec(); + UIEditorObjectFieldInteractionState state = {}; + state.fieldState.focused = true; + state.fieldState.activeTarget = UIEditorObjectFieldHitTargetKind::PickerButton; + state.fieldState.hoveredTarget = UIEditorObjectFieldHitTargetKind::ValueBox; + state.hasPointerPosition = true; + state.pointerPosition = UIPoint(250.0f, 10.0f); + + UIInputEvent focusLost = {}; + focusLost.type = UIInputEventType::FocusLost; + + const auto frame = UpdateUIEditorObjectFieldInteraction( + state, + spec, + UIRect(0.0f, 0.0f, 360.0f, 22.0f), + { focusLost }); + + EXPECT_TRUE(frame.result.focusChanged); + EXPECT_FALSE(state.fieldState.focused); + EXPECT_EQ(state.fieldState.activeTarget, UIEditorObjectFieldHitTargetKind::None); + EXPECT_EQ(state.fieldState.hoveredTarget, UIEditorObjectFieldHitTargetKind::None); + EXPECT_FALSE(state.hasPointerPosition); +} diff --git a/tests/UI/Editor/unit/test_ui_editor_panel_chrome.cpp b/tests/UI/Editor/unit/test_ui_editor_panel_chrome.cpp deleted file mode 100644 index 72508a15..00000000 --- a/tests/UI/Editor/unit/test_ui_editor_panel_chrome.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include - -#include -#include - -namespace { - -using XCEngine::UI::UIColor; -using XCEngine::UI::UIDrawCommandType; -using XCEngine::UI::UIDrawList; -using XCEngine::UI::UIRect; -using XCEngine::UI::Editor::Widgets::AppendUIEditorPanelChromeBackground; -using XCEngine::UI::Editor::Widgets::AppendUIEditorPanelChromeForeground; -using XCEngine::UI::Editor::Widgets::BuildUIEditorPanelChromeHeaderRect; -using XCEngine::UI::Editor::Widgets::ResolveUIEditorPanelChromeBorderColor; -using XCEngine::UI::Editor::Widgets::ResolveUIEditorPanelChromeBorderThickness; -using XCEngine::UI::Editor::Widgets::UIEditorPanelChromePalette; -using XCEngine::UI::Editor::Widgets::UIEditorPanelChromeState; -using XCEngine::UI::Editor::Widgets::UIEditorPanelChromeText; - -void ExpectColorEq( - const UIColor& actual, - const UIColor& expected) { - EXPECT_FLOAT_EQ(actual.r, expected.r); - EXPECT_FLOAT_EQ(actual.g, expected.g); - EXPECT_FLOAT_EQ(actual.b, expected.b); - EXPECT_FLOAT_EQ(actual.a, expected.a); -} - -TEST(UIEditorPanelChromeTest, HeaderRectAndBorderPolicyMatchNativeShellCardChrome) { - const UIRect panelRect(100.0f, 200.0f, 320.0f, 180.0f); - const UIEditorPanelChromePalette palette = {}; - - const auto headerRect = BuildUIEditorPanelChromeHeaderRect(panelRect); - EXPECT_FLOAT_EQ(headerRect.x, 100.0f); - EXPECT_FLOAT_EQ(headerRect.y, 200.0f); - EXPECT_FLOAT_EQ(headerRect.width, 320.0f); - EXPECT_FLOAT_EQ(headerRect.height, 42.0f); - - ExpectColorEq( - ResolveUIEditorPanelChromeBorderColor(UIEditorPanelChromeState(), palette), - palette.borderColor); - ExpectColorEq( - ResolveUIEditorPanelChromeBorderColor(UIEditorPanelChromeState{ false, true }, palette), - palette.hoveredAccentColor); - ExpectColorEq( - ResolveUIEditorPanelChromeBorderColor(UIEditorPanelChromeState{ true, true }, palette), - palette.accentColor); - - EXPECT_FLOAT_EQ(ResolveUIEditorPanelChromeBorderThickness(UIEditorPanelChromeState()), 1.0f); - EXPECT_FLOAT_EQ(ResolveUIEditorPanelChromeBorderThickness(UIEditorPanelChromeState{ true, false }), 2.0f); -} - -TEST(UIEditorPanelChromeTest, BackgroundAppendEmitsSurfaceOutlineAndHeaderFill) { - UIDrawList drawList("PanelChrome"); - const UIRect panelRect(40.0f, 60.0f, 400.0f, 280.0f); - const UIEditorPanelChromeState state{ true, false }; - const UIEditorPanelChromePalette palette = {}; - - AppendUIEditorPanelChromeBackground(drawList, panelRect, state, palette); - - ASSERT_EQ(drawList.GetCommandCount(), 3u); - const auto& commands = drawList.GetCommands(); - EXPECT_EQ(commands[0].type, UIDrawCommandType::FilledRect); - EXPECT_EQ(commands[1].type, UIDrawCommandType::RectOutline); - EXPECT_EQ(commands[2].type, UIDrawCommandType::FilledRect); - - EXPECT_FLOAT_EQ(commands[0].rect.x, 40.0f); - EXPECT_FLOAT_EQ(commands[0].rounding, 18.0f); - ExpectColorEq(commands[0].color, palette.surfaceColor); - - EXPECT_FLOAT_EQ(commands[1].thickness, 2.0f); - EXPECT_FLOAT_EQ(commands[1].rounding, 18.0f); - ExpectColorEq(commands[1].color, palette.accentColor); - - EXPECT_FLOAT_EQ(commands[2].rect.height, 42.0f); - EXPECT_FLOAT_EQ(commands[2].rounding, 18.0f); - ExpectColorEq(commands[2].color, palette.headerColor); -} - -TEST(UIEditorPanelChromeTest, ForegroundAppendPlacesTitleSubtitleAndFooterAtCurrentOffsets) { - UIDrawList drawList("PanelChromeText"); - const UIRect panelRect(100.0f, 200.0f, 320.0f, 180.0f); - const UIEditorPanelChromePalette palette = {}; - const UIEditorPanelChromeText text{ - "XCUI Demo", - "native queued offscreen surface", - "Active | 42 elements | 9 cmds" - }; - - AppendUIEditorPanelChromeForeground(drawList, panelRect, text, palette); - - ASSERT_EQ(drawList.GetCommandCount(), 3u); - const auto& commands = drawList.GetCommands(); - EXPECT_EQ(commands[0].type, UIDrawCommandType::Text); - EXPECT_EQ(commands[1].type, UIDrawCommandType::Text); - EXPECT_EQ(commands[2].type, UIDrawCommandType::Text); - - EXPECT_EQ(commands[0].text, "XCUI Demo"); - EXPECT_FLOAT_EQ(commands[0].position.x, 116.0f); - EXPECT_FLOAT_EQ(commands[0].position.y, 212.0f); - ExpectColorEq(commands[0].color, palette.textPrimary); - - EXPECT_EQ(commands[1].text, "native queued offscreen surface"); - EXPECT_FLOAT_EQ(commands[1].position.x, 116.0f); - EXPECT_FLOAT_EQ(commands[1].position.y, 228.0f); - ExpectColorEq(commands[1].color, palette.textSecondary); - - EXPECT_EQ(commands[2].text, "Active | 42 elements | 9 cmds"); - EXPECT_FLOAT_EQ(commands[2].position.x, 116.0f); - EXPECT_FLOAT_EQ(commands[2].position.y, 362.0f); - ExpectColorEq(commands[2].color, palette.textMuted); -} - -TEST(UIEditorPanelChromeTest, ForegroundAppendSkipsEmptyStrings) { - UIDrawList drawList("PanelChromeEmptyText"); - - AppendUIEditorPanelChromeForeground( - drawList, - UIRect(0.0f, 0.0f, 320.0f, 180.0f), - UIEditorPanelChromeText{}); - - EXPECT_EQ(drawList.GetCommandCount(), 0u); -} - -} // namespace diff --git a/tests/UI/Editor/unit/test_ui_editor_panel_content_host.cpp b/tests/UI/Editor/unit/test_ui_editor_panel_content_host.cpp index 0de44e08..54c29bbb 100644 --- a/tests/UI/Editor/unit/test_ui_editor_panel_content_host.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_panel_content_host.cpp @@ -1,8 +1,8 @@ #include -#include -#include -#include +#include +#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_panel_frame.cpp b/tests/UI/Editor/unit/test_ui_editor_panel_frame.cpp index 2ade3a8e..669306e0 100644 --- a/tests/UI/Editor/unit/test_ui_editor_panel_frame.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_panel_frame.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_panel_host_lifecycle.cpp b/tests/UI/Editor/unit/test_ui_editor_panel_host_lifecycle.cpp index 0e85022d..5ba1140e 100644 --- a/tests/UI/Editor/unit/test_ui_editor_panel_host_lifecycle.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_panel_host_lifecycle.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_panel_registry.cpp b/tests/UI/Editor/unit/test_ui_editor_panel_registry.cpp index 39b4afb0..57371355 100644 --- a/tests/UI/Editor/unit/test_ui_editor_panel_registry.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_panel_registry.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_property_grid.cpp b/tests/UI/Editor/unit/test_ui_editor_property_grid.cpp index 8792335e..1821bcbf 100644 --- a/tests/UI/Editor/unit/test_ui_editor_property_grid.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_property_grid.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace { @@ -372,19 +372,13 @@ TEST(UIEditorPropertyGridTest, ColorFieldUsesHostedLayoutAndPopupAwareForeground state.focused = true; state.hoveredFieldId = "tint"; state.hoveredHitTarget = UIEditorPropertyGridHitTargetKind::ValueBox; - state.colorFieldStates = { - { - "tint", - { - XCEngine::UI::Editor::Widgets::UIEditorColorFieldHitTargetKind::Swatch, - XCEngine::UI::Editor::Widgets::UIEditorColorFieldHitTargetKind::None, - true, - true, - 0.0f, - false - } - } - }; + XCEngine::UI::Editor::Widgets::UIEditorPropertyGridColorFieldVisualState colorState = {}; + colorState.fieldId = "tint"; + colorState.state.hoveredTarget = + XCEngine::UI::Editor::Widgets::UIEditorColorFieldHitTargetKind::Swatch; + colorState.state.focused = true; + colorState.state.popupOpen = true; + state.colorFieldStates.push_back(colorState); const auto layout = BuildUIEditorPropertyGridLayout( UIRect(0.0f, 0.0f, 520.0f, 320.0f), diff --git a/tests/UI/Editor/unit/test_ui_editor_property_grid_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_property_grid_interaction.cpp index ef7d12c0..2931b1cc 100644 --- a/tests/UI/Editor/unit/test_ui_editor_property_grid_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_property_grid_interaction.cpp @@ -1,8 +1,8 @@ #include -#include -#include -#include +#include +#include +#include #include @@ -17,7 +17,7 @@ using XCEngine::UI::UIRect; using XCEngine::UI::Widgets::UIExpansionModel; using XCEngine::UI::Widgets::UIPropertyEditModel; using XCEngine::UI::Widgets::UISelectionModel; -using XCEngine::UI::Editor::BuildUIEditorHostedColorFieldMetrics; +using XCEngine::UI::Editor::BuildUIEditorPropertyGridColorFieldMetrics; using XCEngine::UI::Editor::UIEditorPropertyGridInteractionState; using XCEngine::UI::Editor::UpdateUIEditorPropertyGridInteraction; using XCEngine::UI::Editor::Widgets::BuildUIEditorColorFieldLayout; @@ -542,7 +542,7 @@ TEST(UIEditorPropertyGridInteractionTest, ColorFieldPopupCanOpenAndDragAlphaThro const auto initialColorLayout = BuildUIEditorColorFieldLayout( frame.layout.fieldRowRects[0], initialColorSpec, - BuildUIEditorHostedColorFieldMetrics({}), + BuildUIEditorPropertyGridColorFieldMetrics({}), UIRect(-4096.0f, -4096.0f, 8192.0f, 8192.0f)); const UIPoint swatchCenter = RectCenter(initialColorLayout.swatchRect); frame = UpdateUIEditorPropertyGridInteraction( @@ -581,7 +581,7 @@ TEST(UIEditorPropertyGridInteractionTest, ColorFieldPopupCanOpenAndDragAlphaThro const auto colorLayout = BuildUIEditorColorFieldLayout( popupFrame.layout.fieldRowRects[0], colorSpec, - BuildUIEditorHostedColorFieldMetrics({}), + BuildUIEditorPropertyGridColorFieldMetrics({}), UIRect(-4096.0f, -4096.0f, 8192.0f, 8192.0f)); const float alphaX = diff --git a/tests/UI/Editor/unit/test_ui_editor_scroll_view.cpp b/tests/UI/Editor/unit/test_ui_editor_scroll_view.cpp index f7751f64..3cb23f94 100644 --- a/tests/UI/Editor/unit/test_ui_editor_scroll_view.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_scroll_view.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_scroll_view_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_scroll_view_interaction.cpp index 7d172b64..5282ba84 100644 --- a/tests/UI/Editor/unit/test_ui_editor_scroll_view_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_scroll_view_interaction.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_shell_compose.cpp b/tests/UI/Editor/unit/test_ui_editor_shell_compose.cpp index 05e90f1f..b68b489d 100644 --- a/tests/UI/Editor/unit/test_ui_editor_shell_compose.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_shell_compose.cpp @@ -1,9 +1,9 @@ #include #include -#include -#include -#include +#include +#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_shell_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_shell_interaction.cpp index 64071b49..30b84284 100644 --- a/tests/UI/Editor/unit/test_ui_editor_shell_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_shell_interaction.cpp @@ -1,8 +1,8 @@ #include -#include -#include -#include +#include +#include +#include #include diff --git a/tests/UI/Editor/unit/test_ui_editor_status_bar.cpp b/tests/UI/Editor/unit/test_ui_editor_status_bar.cpp index 57c2c023..75744ca9 100644 --- a/tests/UI/Editor/unit/test_ui_editor_status_bar.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_status_bar.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_text_field.cpp b/tests/UI/Editor/unit/test_ui_editor_text_field.cpp index 6bf6d992..22be1b67 100644 --- a/tests/UI/Editor/unit/test_ui_editor_text_field.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_text_field.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_text_field_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_text_field_interaction.cpp index a749571f..dc2cfd45 100644 --- a/tests/UI/Editor/unit/test_ui_editor_text_field_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_text_field_interaction.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include diff --git a/tests/UI/Editor/unit/test_ui_editor_theme.cpp b/tests/UI/Editor/unit/test_ui_editor_theme.cpp deleted file mode 100644 index 44938a3f..00000000 --- a/tests/UI/Editor/unit/test_ui_editor_theme.cpp +++ /dev/null @@ -1,444 +0,0 @@ -#include - -#include - -#include -#include -#include - -namespace { - -namespace Math = XCEngine::Math; -namespace Style = XCEngine::UI::Style; -namespace UI = XCEngine::UI; -namespace Editor = XCEngine::UI::Editor; - -Style::UITheme BuildEditorFieldTheme() { - Style::UIThemeDefinition definition = {}; - definition.SetToken("editor.size.field.row", Style::UIStyleValue(26.0f)); - definition.SetToken("editor.space.field.padding_x", Style::UIStyleValue(8.0f)); - definition.SetToken("editor.space.field.label_gap", Style::UIStyleValue(12.0f)); - definition.SetToken("editor.layout.field.control_column", Style::UIStyleValue(220.0f)); - definition.SetToken("editor.size.field.checkbox", Style::UIStyleValue(15.0f)); - definition.SetToken("editor.space.field.control_inset_y", Style::UIStyleValue(3.0f)); - definition.SetToken("editor.space.field.label_inset_y", Style::UIStyleValue(5.0f)); - definition.SetToken("editor.space.field.value_inset_x", Style::UIStyleValue(6.0f)); - definition.SetToken("editor.space.field.value_inset_y", Style::UIStyleValue(4.0f)); - definition.SetToken("editor.space.field.checkbox_glyph_inset_x", Style::UIStyleValue(1.0f)); - definition.SetToken("editor.space.field.checkbox_glyph_inset_y", Style::UIStyleValue(-2.0f)); - definition.SetToken("editor.size.field.control_min_width", Style::UIStyleValue(88.0f)); - definition.SetToken("editor.space.field.vector_component_gap", Style::UIStyleValue(6.0f)); - definition.SetToken("editor.size.field.vector_component_min_width", Style::UIStyleValue(74.0f)); - definition.SetToken("editor.size.field.vector_prefix_width", Style::UIStyleValue(18.0f)); - definition.SetToken("editor.space.field.control_trailing_inset", Style::UIStyleValue(9.0f)); - definition.SetToken("editor.space.field.vector_prefix_gap", Style::UIStyleValue(5.0f)); - definition.SetToken("editor.space.field.vector_prefix_inset_x", Style::UIStyleValue(4.0f)); - definition.SetToken("editor.space.field.vector_prefix_inset_y", Style::UIStyleValue(5.0f)); - definition.SetToken("editor.size.field.dropdown_arrow_width", Style::UIStyleValue(14.0f)); - definition.SetToken("editor.space.field.dropdown_arrow_inset_x", Style::UIStyleValue(4.0f)); - definition.SetToken("editor.space.field.dropdown_arrow_inset_y", Style::UIStyleValue(4.0f)); - definition.SetToken("editor.size.field.color_popup_width", Style::UIStyleValue(320.0f)); - definition.SetToken("editor.size.field.color_popup_top_row", Style::UIStyleValue(36.0f)); - definition.SetToken("editor.size.field.color_preview_width", Style::UIStyleValue(108.0f)); - definition.SetToken("editor.size.field.color_preview_height", Style::UIStyleValue(26.0f)); - definition.SetToken("editor.size.field.color_wheel_outer_radius", Style::UIStyleValue(112.0f)); - definition.SetToken("editor.size.field.color_wheel_ring_thickness", Style::UIStyleValue(22.0f)); - definition.SetToken("editor.size.field.color_sv_square", Style::UIStyleValue(118.0f)); - definition.SetToken("editor.size.field.color_wheel_region_height", Style::UIStyleValue(224.0f)); - definition.SetToken("editor.size.field.color_channel_row_height", Style::UIStyleValue(21.0f)); - definition.SetToken("editor.size.field.color_numeric_box_width", Style::UIStyleValue(66.0f)); - definition.SetToken("editor.size.field.color_channel_label_width", Style::UIStyleValue(14.0f)); - definition.SetToken("editor.size.field.color_hex_label_width", Style::UIStyleValue(90.0f)); - definition.SetToken("editor.space.field.color_control_row_spacing", Style::UIStyleValue(7.0f)); - definition.SetToken("editor.space.field.color_popup_field_inset", Style::UIStyleValue(5.0f)); - definition.SetToken("editor.color.field.color_popup_surface", Style::UIStyleValue(Math::Color(0.22f, 0.22f, 0.22f, 1.0f))); - definition.SetToken("editor.color.field.color_popup_header", Style::UIStyleValue(Math::Color(0.48f, 0.28f, 0.10f, 1.0f))); - definition.SetToken("editor.color.field.color_popup_close", Style::UIStyleValue(Math::Color(0.74f, 0.34f, 0.32f, 1.0f))); - definition.SetToken("editor.color.field.color_popup_close_hover", Style::UIStyleValue(Math::Color(0.80f, 0.38f, 0.36f, 1.0f))); - definition.SetToken("editor.color.field.color_popup_slider_border", Style::UIStyleValue(Math::Color(0.11f, 0.11f, 0.11f, 1.0f))); - definition.SetToken("editor.color.field.color_popup_numeric_box", Style::UIStyleValue(Math::Color(0.17f, 0.17f, 0.17f, 1.0f))); - definition.SetToken("editor.color.field.color_popup_numeric_box_text", Style::UIStyleValue(Math::Color(0.93f, 0.93f, 0.93f, 1.0f))); - definition.SetToken("editor.color.field.color_popup_handle_outline", Style::UIStyleValue(Math::Color(0.10f, 0.10f, 0.10f, 0.5f))); - definition.SetToken("editor.radius.field.row", Style::UIStyleValue(2.0f)); - definition.SetToken("editor.radius.field.control", Style::UIStyleValue(2.0f)); - definition.SetToken("editor.border.field", Style::UIStyleValue(1.0f)); - definition.SetToken("editor.border.field.focus", Style::UIStyleValue(2.0f)); - definition.SetToken("editor.font.field.label", Style::UIStyleValue(11.0f)); - definition.SetToken("editor.font.field.value", Style::UIStyleValue(12.0f)); - definition.SetToken("editor.font.field.glyph", Style::UIStyleValue(10.0f)); - definition.SetToken("editor.space.menu_popup.padding_x", Style::UIStyleValue(6.0f)); - definition.SetToken("editor.space.menu_popup.padding_y", Style::UIStyleValue(5.0f)); - definition.SetToken("editor.size.menu_popup.item", Style::UIStyleValue(24.0f)); - definition.SetToken("editor.size.menu_popup.separator", Style::UIStyleValue(8.0f)); - definition.SetToken("editor.size.menu_popup.check_column", Style::UIStyleValue(16.0f)); - definition.SetToken("editor.space.menu_popup.shortcut_gap", Style::UIStyleValue(18.0f)); - definition.SetToken("editor.size.menu_popup.submenu_indicator", Style::UIStyleValue(12.0f)); - definition.SetToken("editor.radius.menu_popup.row", Style::UIStyleValue(3.0f)); - definition.SetToken("editor.radius.menu_popup.surface", Style::UIStyleValue(4.0f)); - definition.SetToken("editor.space.menu_popup.label_inset_x", Style::UIStyleValue(10.0f)); - definition.SetToken("editor.space.menu_popup.label_inset_y", Style::UIStyleValue(-1.0f)); - definition.SetToken("editor.font.menu_popup.label", Style::UIStyleValue(11.0f)); - definition.SetToken("editor.space.menu_popup.shortcut_inset_right", Style::UIStyleValue(18.0f)); - definition.SetToken("editor.size.menu_popup.estimated_glyph_width", Style::UIStyleValue(6.0f)); - definition.SetToken("editor.font.menu_popup.glyph", Style::UIStyleValue(10.0f)); - definition.SetToken("editor.border.menu_popup.separator", Style::UIStyleValue(1.0f)); - definition.SetToken("editor.border.menu_popup.surface", Style::UIStyleValue(1.0f)); - definition.SetToken("editor.color.field.row", Style::UIStyleValue(Math::Color(0.16f, 0.16f, 0.16f, 1.0f))); - definition.SetToken("editor.color.field.row_hover", Style::UIStyleValue(Math::Color(0.20f, 0.20f, 0.20f, 1.0f))); - definition.SetToken("editor.color.field.row_active", Style::UIStyleValue(Math::Color(0.24f, 0.24f, 0.24f, 1.0f))); - definition.SetToken("editor.color.field.border", Style::UIStyleValue(Math::Color(0.12f, 0.12f, 0.12f, 1.0f))); - definition.SetToken("editor.color.field.border_focus", Style::UIStyleValue(Math::Color(0.78f, 0.78f, 0.78f, 1.0f))); - definition.SetToken("editor.color.field.label", Style::UIStyleValue(Math::Color(0.84f, 0.84f, 0.84f, 1.0f))); - definition.SetToken("editor.color.field.value", Style::UIStyleValue(Math::Color(0.92f, 0.92f, 0.92f, 1.0f))); - definition.SetToken("editor.color.field.value_readonly", Style::UIStyleValue(Math::Color(0.60f, 0.60f, 0.60f, 1.0f))); - definition.SetToken("editor.color.field.control", Style::UIStyleValue(Math::Color(0.18f, 0.18f, 0.18f, 1.0f))); - definition.SetToken("editor.color.field.control_hover", Style::UIStyleValue(Math::Color(0.21f, 0.21f, 0.21f, 1.0f))); - definition.SetToken("editor.color.field.control_editing", Style::UIStyleValue(Math::Color(0.24f, 0.24f, 0.24f, 1.0f))); - definition.SetToken("editor.color.field.control_readonly", Style::UIStyleValue(Math::Color(0.15f, 0.15f, 0.15f, 1.0f))); - definition.SetToken("editor.color.field.control_border", Style::UIStyleValue(Math::Color(0.30f, 0.30f, 0.30f, 1.0f))); - definition.SetToken("editor.color.field.control_border_focus", Style::UIStyleValue(Math::Color(0.64f, 0.64f, 0.64f, 1.0f))); - definition.SetToken("editor.color.field.vector_prefix", Style::UIStyleValue(Math::Color(0.20f, 0.20f, 0.20f, 1.0f))); - definition.SetToken("editor.color.field.vector_prefix_border", Style::UIStyleValue(Math::Color(0.31f, 0.31f, 0.31f, 1.0f))); - definition.SetToken("editor.color.field.vector_axis_x", Style::UIStyleValue(Math::Color(0.78f, 0.42f, 0.42f, 1.0f))); - definition.SetToken("editor.color.field.vector_axis_y", Style::UIStyleValue(Math::Color(0.56f, 0.72f, 0.46f, 1.0f))); - definition.SetToken("editor.color.field.vector_axis_z", Style::UIStyleValue(Math::Color(0.45f, 0.62f, 0.82f, 1.0f))); - definition.SetToken("editor.color.field.vector_axis_w", Style::UIStyleValue(Math::Color(0.76f, 0.66f, 0.42f, 1.0f))); - definition.SetToken("editor.color.field.checkbox", Style::UIStyleValue(Math::Color(0.19f, 0.19f, 0.19f, 1.0f))); - definition.SetToken("editor.color.field.checkbox_hover", Style::UIStyleValue(Math::Color(0.22f, 0.22f, 0.22f, 1.0f))); - definition.SetToken("editor.color.field.checkbox_border", Style::UIStyleValue(Math::Color(0.33f, 0.33f, 0.33f, 1.0f))); - definition.SetToken("editor.color.field.checkbox_mark", Style::UIStyleValue(Math::Color(0.90f, 0.90f, 0.90f, 1.0f))); - definition.SetToken("editor.color.field.dropdown_arrow", Style::UIStyleValue(Math::Color(0.88f, 0.88f, 0.88f, 1.0f))); - definition.SetToken("editor.color.menu_popup.surface", Style::UIStyleValue(Math::Color(0.15f, 0.15f, 0.15f, 1.0f))); - definition.SetToken("editor.color.menu_popup.border", Style::UIStyleValue(Math::Color(0.32f, 0.32f, 0.32f, 1.0f))); - definition.SetToken("editor.color.menu_popup.item_hover", Style::UIStyleValue(Math::Color(0.24f, 0.24f, 0.24f, 1.0f))); - definition.SetToken("editor.color.menu_popup.item_open", Style::UIStyleValue(Math::Color(0.27f, 0.27f, 0.27f, 1.0f))); - definition.SetToken("editor.color.menu_popup.separator", Style::UIStyleValue(Math::Color(0.35f, 0.35f, 0.35f, 1.0f))); - definition.SetToken("editor.color.menu_popup.label", Style::UIStyleValue(Math::Color(0.91f, 0.91f, 0.91f, 1.0f))); - definition.SetToken("editor.color.menu_popup.shortcut", Style::UIStyleValue(Math::Color(0.76f, 0.76f, 0.76f, 1.0f))); - definition.SetToken("editor.color.menu_popup.text_disabled", Style::UIStyleValue(Math::Color(0.48f, 0.48f, 0.48f, 1.0f))); - definition.SetToken("editor.color.menu_popup.glyph", Style::UIStyleValue(Math::Color(0.86f, 0.86f, 0.86f, 1.0f))); - return Style::BuildTheme(definition); -} - -Style::UITheme BuildPropertyGridTheme() { - Style::UIThemeDefinition definition = {}; - definition.SetToken("editor.space.property.content_inset", Style::UIStyleValue(6.0f)); - definition.SetToken("editor.space.property.section_gap", Style::UIStyleValue(4.0f)); - definition.SetToken("editor.size.property.section_header", Style::UIStyleValue(24.0f)); - definition.SetToken("editor.size.property.field_row", Style::UIStyleValue(26.0f)); - definition.SetToken("editor.space.property.row_gap", Style::UIStyleValue(1.0f)); - definition.SetToken("editor.size.property.disclosure", Style::UIStyleValue(10.0f)); - definition.SetToken("editor.space.property.disclosure_label_gap", Style::UIStyleValue(6.0f)); - definition.SetToken("editor.space.property.section_inset_y", Style::UIStyleValue(4.0f)); - definition.SetToken("editor.space.property.disclosure_glyph_inset_x", Style::UIStyleValue(1.0f)); - definition.SetToken("editor.space.property.disclosure_glyph_inset_y", Style::UIStyleValue(-1.0f)); - definition.SetToken("editor.space.property.label_inset_y", Style::UIStyleValue(5.0f)); - definition.SetToken("editor.space.property.value_inset_y", Style::UIStyleValue(4.0f)); - definition.SetToken("editor.space.property.value_box_inset_y", Style::UIStyleValue(3.0f)); - definition.SetToken("editor.space.property.value_box_inset_x", Style::UIStyleValue(6.0f)); - definition.SetToken("editor.radius.property.panel", Style::UIStyleValue(0.0f)); - definition.SetToken("editor.radius.property.value", Style::UIStyleValue(2.0f)); - definition.SetToken("editor.border.property", Style::UIStyleValue(1.0f)); - definition.SetToken("editor.border.property.focus", Style::UIStyleValue(1.0f)); - definition.SetToken("editor.border.property.edit", Style::UIStyleValue(1.0f)); - definition.SetToken("editor.font.property.section", Style::UIStyleValue(11.0f)); - definition.SetToken("editor.font.property.disclosure", Style::UIStyleValue(10.0f)); - definition.SetToken("editor.font.property.label", Style::UIStyleValue(11.0f)); - definition.SetToken("editor.font.property.value", Style::UIStyleValue(12.0f)); - definition.SetToken("editor.font.property.tag", Style::UIStyleValue(10.0f)); - definition.SetToken("editor.color.property.surface", Style::UIStyleValue(Math::Color(0.17f, 0.17f, 0.17f, 1.0f))); - definition.SetToken("editor.color.property.border", Style::UIStyleValue(Math::Color(0.10f, 0.10f, 0.10f, 1.0f))); - definition.SetToken("editor.color.property.border_focus", Style::UIStyleValue(Math::Color(0.75f, 0.75f, 0.75f, 1.0f))); - definition.SetToken("editor.color.property.section", Style::UIStyleValue(Math::Color(0.21f, 0.21f, 0.21f, 1.0f))); - definition.SetToken("editor.color.property.section_hover", Style::UIStyleValue(Math::Color(0.24f, 0.24f, 0.24f, 1.0f))); - definition.SetToken("editor.color.property.field_hover", Style::UIStyleValue(Math::Color(0.22f, 0.22f, 0.22f, 1.0f))); - definition.SetToken("editor.color.property.field_selected", Style::UIStyleValue(Math::Color(0.26f, 0.26f, 0.26f, 1.0f))); - definition.SetToken("editor.color.property.field_selected_focused", Style::UIStyleValue(Math::Color(0.30f, 0.30f, 0.30f, 1.0f))); - definition.SetToken("editor.color.property.value", Style::UIStyleValue(Math::Color(0.18f, 0.18f, 0.18f, 1.0f))); - definition.SetToken("editor.color.property.value_hover", Style::UIStyleValue(Math::Color(0.21f, 0.21f, 0.21f, 1.0f))); - definition.SetToken("editor.color.property.value_editing", Style::UIStyleValue(Math::Color(0.23f, 0.23f, 0.23f, 1.0f))); - definition.SetToken("editor.color.property.value_readonly", Style::UIStyleValue(Math::Color(0.15f, 0.15f, 0.15f, 1.0f))); - definition.SetToken("editor.color.property.value_border", Style::UIStyleValue(Math::Color(0.28f, 0.28f, 0.28f, 1.0f))); - definition.SetToken("editor.color.property.value_border_editing", Style::UIStyleValue(Math::Color(0.72f, 0.72f, 0.72f, 1.0f))); - definition.SetToken("editor.color.property.disclosure", Style::UIStyleValue(Math::Color(0.84f, 0.84f, 0.84f, 1.0f))); - definition.SetToken("editor.color.property.section_text", Style::UIStyleValue(Math::Color(0.92f, 0.92f, 0.92f, 1.0f))); - definition.SetToken("editor.color.property.label", Style::UIStyleValue(Math::Color(0.80f, 0.80f, 0.80f, 1.0f))); - definition.SetToken("editor.color.property.value_text", Style::UIStyleValue(Math::Color(0.92f, 0.92f, 0.92f, 1.0f))); - definition.SetToken("editor.color.property.value_text_readonly", Style::UIStyleValue(Math::Color(0.60f, 0.60f, 0.60f, 1.0f))); - definition.SetToken("editor.color.property.edit_tag", Style::UIStyleValue(Math::Color(0.55f, 0.70f, 0.96f, 1.0f))); - definition.SetToken("editor.space.menu.padding_x", Style::UIStyleValue(7.0f)); - definition.SetToken("editor.space.menu.padding_y", Style::UIStyleValue(5.0f)); - definition.SetToken("editor.size.menu.item", Style::UIStyleValue(24.0f)); - definition.SetToken("editor.size.menu.separator", Style::UIStyleValue(8.0f)); - definition.SetToken("editor.size.menu.check_column", Style::UIStyleValue(16.0f)); - definition.SetToken("editor.space.menu.shortcut_gap", Style::UIStyleValue(18.0f)); - definition.SetToken("editor.size.menu.submenu_indicator", Style::UIStyleValue(12.0f)); - definition.SetToken("editor.radius.menu.row", Style::UIStyleValue(3.0f)); - definition.SetToken("editor.radius.menu.surface", Style::UIStyleValue(6.0f)); - definition.SetToken("editor.space.menu.label_inset_x", Style::UIStyleValue(11.0f)); - definition.SetToken("editor.space.menu.label_inset_y", Style::UIStyleValue(1.0f)); - definition.SetToken("editor.font.menu.label", Style::UIStyleValue(11.0f)); - definition.SetToken("editor.space.menu.shortcut_inset_right", Style::UIStyleValue(18.0f)); - definition.SetToken("editor.size.menu.glyph_width", Style::UIStyleValue(6.0f)); - definition.SetToken("editor.font.menu.glyph", Style::UIStyleValue(10.0f)); - definition.SetToken("editor.border.menu.separator", Style::UIStyleValue(1.0f)); - definition.SetToken("editor.border.menu.surface", Style::UIStyleValue(1.0f)); - definition.SetToken("editor.color.menu.surface", Style::UIStyleValue(Math::Color(0.16f, 0.16f, 0.16f, 1.0f))); - definition.SetToken("editor.color.menu.border", Style::UIStyleValue(Math::Color(0.24f, 0.24f, 0.24f, 1.0f))); - definition.SetToken("editor.color.menu.item_hover", Style::UIStyleValue(Math::Color(0.23f, 0.23f, 0.23f, 1.0f))); - definition.SetToken("editor.color.menu.item_open", Style::UIStyleValue(Math::Color(0.28f, 0.28f, 0.28f, 1.0f))); - definition.SetToken("editor.color.menu.separator", Style::UIStyleValue(Math::Color(0.30f, 0.30f, 0.30f, 1.0f))); - definition.SetToken("editor.color.menu.text", Style::UIStyleValue(Math::Color(0.92f, 0.92f, 0.92f, 1.0f))); - definition.SetToken("editor.color.menu.text_muted", Style::UIStyleValue(Math::Color(0.74f, 0.74f, 0.74f, 1.0f))); - definition.SetToken("editor.color.menu.text_disabled", Style::UIStyleValue(Math::Color(0.47f, 0.47f, 0.47f, 1.0f))); - definition.SetToken("editor.color.menu.glyph", Style::UIStyleValue(Math::Color(0.86f, 0.86f, 0.86f, 1.0f))); - return Style::BuildTheme(definition); -} - -TEST(UIEditorThemeTest, FieldResolversReadEditorThemeTokens) { - const Style::UITheme theme = BuildEditorFieldTheme(); - - const auto boolMetrics = Editor::ResolveUIEditorBoolFieldMetrics(theme); - const auto boolPalette = Editor::ResolveUIEditorBoolFieldPalette(theme); - EXPECT_FLOAT_EQ(boolMetrics.rowHeight, 26.0f); - EXPECT_FLOAT_EQ(boolMetrics.horizontalPadding, 8.0f); - EXPECT_FLOAT_EQ(boolMetrics.controlTrailingInset, 9.0f); - EXPECT_FLOAT_EQ(boolMetrics.checkboxGlyphFontSize, 10.0f); - EXPECT_FLOAT_EQ(boolPalette.rowHoverColor.r, 0.20f); - EXPECT_FLOAT_EQ(boolPalette.checkboxBorderColor.r, 0.33f); - - const auto numberMetrics = Editor::ResolveUIEditorNumberFieldMetrics(theme); - const auto numberPalette = Editor::ResolveUIEditorNumberFieldPalette(theme); - EXPECT_FLOAT_EQ(numberMetrics.controlTrailingInset, 9.0f); - EXPECT_FLOAT_EQ(numberMetrics.controlInsetY, 3.0f); - EXPECT_FLOAT_EQ(numberMetrics.valueTextInsetX, 6.0f); - EXPECT_FLOAT_EQ(numberMetrics.valueFontSize, 12.0f); - EXPECT_FLOAT_EQ(numberPalette.valueBoxEditingColor.r, 0.24f); - EXPECT_FLOAT_EQ(numberPalette.controlBorderColor.r, 0.30f); - EXPECT_FLOAT_EQ(numberPalette.controlFocusedBorderColor.r, 0.64f); - - const auto textMetrics = Editor::ResolveUIEditorTextFieldMetrics(theme); - const auto textPalette = Editor::ResolveUIEditorTextFieldPalette(theme); - EXPECT_FLOAT_EQ(textMetrics.controlTrailingInset, 9.0f); - EXPECT_FLOAT_EQ(textMetrics.valueBoxMinWidth, 88.0f); - EXPECT_FLOAT_EQ(textMetrics.valueTextInsetY, 4.0f); - EXPECT_FLOAT_EQ(textMetrics.valueFontSize, 12.0f); - EXPECT_FLOAT_EQ(textPalette.valueBoxEditingColor.r, 0.24f); - EXPECT_FLOAT_EQ(textPalette.readOnlyValueColor.r, 0.60f); - EXPECT_FLOAT_EQ(textPalette.controlFocusedBorderColor.r, 0.64f); - - const auto vector2Metrics = Editor::ResolveUIEditorVector2FieldMetrics(theme); - const auto vector2Palette = Editor::ResolveUIEditorVector2FieldPalette(theme); - EXPECT_FLOAT_EQ(vector2Metrics.componentGap, 6.0f); - EXPECT_FLOAT_EQ(vector2Metrics.componentPrefixWidth, 18.0f); - EXPECT_FLOAT_EQ(vector2Metrics.controlTrailingInset, 9.0f); - EXPECT_FLOAT_EQ(vector2Metrics.componentLabelGap, 5.0f); - EXPECT_FLOAT_EQ(vector2Metrics.prefixFontSize, 10.0f); - EXPECT_FLOAT_EQ(vector2Palette.componentFocusedBorderColor.r, 0.64f); - EXPECT_FLOAT_EQ(vector2Palette.axisXColor.r, 0.78f); - EXPECT_FLOAT_EQ(vector2Palette.axisYColor.g, 0.72f); - - const auto vector3Metrics = Editor::ResolveUIEditorVector3FieldMetrics(theme); - const auto vector3Palette = Editor::ResolveUIEditorVector3FieldPalette(theme); - EXPECT_FLOAT_EQ(vector3Metrics.componentGap, 6.0f); - EXPECT_FLOAT_EQ(vector3Metrics.componentPrefixWidth, 18.0f); - EXPECT_FLOAT_EQ(vector3Metrics.controlTrailingInset, 9.0f); - EXPECT_FLOAT_EQ(vector3Metrics.componentLabelGap, 5.0f); - EXPECT_FLOAT_EQ(vector3Metrics.prefixFontSize, 10.0f); - EXPECT_FLOAT_EQ(vector3Palette.componentFocusedBorderColor.r, 0.64f); - EXPECT_FLOAT_EQ(vector3Palette.axisZColor.b, 0.82f); - - const auto vector4Metrics = Editor::ResolveUIEditorVector4FieldMetrics(theme); - const auto vector4Palette = Editor::ResolveUIEditorVector4FieldPalette(theme); - EXPECT_FLOAT_EQ(vector4Metrics.componentGap, 6.0f); - EXPECT_FLOAT_EQ(vector4Metrics.componentPrefixWidth, 18.0f); - EXPECT_FLOAT_EQ(vector4Metrics.controlTrailingInset, 9.0f); - EXPECT_FLOAT_EQ(vector4Metrics.componentLabelGap, 5.0f); - EXPECT_FLOAT_EQ(vector4Metrics.prefixFontSize, 10.0f); - EXPECT_FLOAT_EQ(vector4Palette.componentFocusedBorderColor.r, 0.64f); - EXPECT_FLOAT_EQ(vector4Palette.axisWColor.r, 0.76f); - - const auto enumMetrics = Editor::ResolveUIEditorEnumFieldMetrics(theme); - const auto enumPalette = Editor::ResolveUIEditorEnumFieldPalette(theme); - EXPECT_FLOAT_EQ(enumMetrics.controlTrailingInset, 9.0f); - EXPECT_FLOAT_EQ(enumMetrics.valueBoxMinWidth, 88.0f); - EXPECT_FLOAT_EQ(enumMetrics.dropdownArrowWidth, 14.0f); - EXPECT_FLOAT_EQ(enumMetrics.dropdownArrowFontSize, 10.0f); - EXPECT_FLOAT_EQ(enumPalette.arrowColor.r, 0.88f); - - const auto colorMetrics = Editor::ResolveUIEditorColorFieldMetrics(theme); - const auto colorPalette = Editor::ResolveUIEditorColorFieldPalette(theme); - EXPECT_FLOAT_EQ(colorMetrics.popupWidth, 320.0f); - EXPECT_FLOAT_EQ(colorMetrics.popupTopRowHeight, 36.0f); - EXPECT_FLOAT_EQ(colorMetrics.popupPreviewWidth, 108.0f); - EXPECT_FLOAT_EQ(colorMetrics.wheelOuterRadius, 112.0f); - EXPECT_FLOAT_EQ(colorMetrics.wheelRingThickness, 22.0f); - EXPECT_FLOAT_EQ(colorMetrics.saturationValueSize, 118.0f); - EXPECT_FLOAT_EQ(colorMetrics.channelRowHeight, 21.0f); - EXPECT_FLOAT_EQ(colorMetrics.numericBoxWidth, 66.0f); - EXPECT_FLOAT_EQ(colorMetrics.hexLabelWidth, 90.0f); - EXPECT_FLOAT_EQ(colorPalette.popupColor.r, 0.22f); - EXPECT_FLOAT_EQ(colorPalette.popupHeaderColor.r, 0.48f); - EXPECT_FLOAT_EQ(colorPalette.closeButtonColor.r, 0.74f); - EXPECT_FLOAT_EQ(colorPalette.closeButtonHoverColor.r, 0.80f); - EXPECT_FLOAT_EQ(colorPalette.sliderBorderColor.r, 0.11f); - EXPECT_FLOAT_EQ(colorPalette.numericBoxTextColor.r, 0.93f); - EXPECT_FLOAT_EQ(colorPalette.handleStrokeColor.r, 0.10f); - - const auto popupMetrics = Editor::ResolveUIEditorMenuPopupMetrics(theme); - const auto popupPalette = Editor::ResolveUIEditorMenuPopupPalette(theme); - EXPECT_FLOAT_EQ(popupMetrics.contentPaddingX, 6.0f); - EXPECT_FLOAT_EQ(popupMetrics.itemHeight, 24.0f); - EXPECT_FLOAT_EQ(popupMetrics.glyphFontSize, 10.0f); - EXPECT_FLOAT_EQ(popupPalette.popupColor.r, 0.15f); - EXPECT_FLOAT_EQ(popupPalette.textPrimary.r, 0.91f); - EXPECT_FLOAT_EQ(popupPalette.textMuted.r, 0.76f); -} - -TEST(UIEditorThemeTest, PropertyGridResolversSupportOverridesAndFallbacks) { - const Style::UITheme theme = BuildPropertyGridTheme(); - const Style::UITheme fallbackTheme = Style::UITheme(); - - UI::Editor::Widgets::UIEditorPropertyGridMetrics fallbackMetrics = {}; - fallbackMetrics.sectionHeaderHeight = 40.0f; - fallbackMetrics.disclosureGlyphFontSize = 15.0f; - fallbackMetrics.tagFontSize = 13.0f; - const auto fallbackResolvedMetrics = - Editor::ResolveUIEditorPropertyGridMetrics(fallbackTheme, fallbackMetrics); - EXPECT_FLOAT_EQ(fallbackResolvedMetrics.sectionHeaderHeight, 40.0f); - EXPECT_FLOAT_EQ(fallbackResolvedMetrics.disclosureGlyphFontSize, 15.0f); - EXPECT_FLOAT_EQ(fallbackResolvedMetrics.tagFontSize, 13.0f); - - UI::Editor::Widgets::UIEditorPropertyGridPalette fallbackPalette = {}; - fallbackPalette.surfaceColor = UI::UIColor(0.5f, 0.4f, 0.3f, 1.0f); - const auto fallbackResolvedPalette = - Editor::ResolveUIEditorPropertyGridPalette(fallbackTheme, fallbackPalette); - EXPECT_FLOAT_EQ(fallbackResolvedPalette.surfaceColor.r, 0.5f); - - const auto themedMetrics = Editor::ResolveUIEditorPropertyGridMetrics(theme); - const auto themedPalette = Editor::ResolveUIEditorPropertyGridPalette(theme); - const auto popupMetrics = Editor::ResolveUIEditorMenuPopupMetrics(theme); - const auto popupPalette = Editor::ResolveUIEditorMenuPopupPalette(theme); - EXPECT_FLOAT_EQ(themedMetrics.contentInset, 6.0f); - EXPECT_FLOAT_EQ(themedMetrics.sectionHeaderHeight, 24.0f); - EXPECT_FLOAT_EQ(themedMetrics.disclosureGlyphFontSize, 10.0f); - EXPECT_FLOAT_EQ(themedMetrics.tagFontSize, 10.0f); - EXPECT_FLOAT_EQ(themedPalette.sectionHeaderColor.r, 0.21f); - EXPECT_FLOAT_EQ(themedPalette.valueBoxEditingBorderColor.r, 0.72f); - EXPECT_FLOAT_EQ(themedPalette.valueTextColor.r, 0.92f); - EXPECT_FLOAT_EQ(themedPalette.readOnlyValueTextColor.r, 0.60f); - EXPECT_FLOAT_EQ(popupMetrics.contentPaddingX, 7.0f); - EXPECT_FLOAT_EQ(popupMetrics.itemHeight, 24.0f); - EXPECT_FLOAT_EQ(popupMetrics.popupCornerRounding, 6.0f); - EXPECT_FLOAT_EQ(popupMetrics.glyphFontSize, 10.0f); - EXPECT_FLOAT_EQ(popupPalette.popupColor.r, 0.16f); - EXPECT_FLOAT_EQ(popupPalette.itemHoverColor.r, 0.23f); - EXPECT_FLOAT_EQ(popupPalette.glyphColor.r, 0.86f); -} - -TEST(UIEditorThemeTest, HostedFieldBuildersInheritPropertyGridMetricsAndPalette) { - UI::Editor::Widgets::UIEditorPropertyGridMetrics propertyMetrics = {}; - propertyMetrics.fieldRowHeight = 25.0f; - propertyMetrics.horizontalPadding = 7.0f; - propertyMetrics.labelControlGap = 11.0f; - propertyMetrics.controlColumnStart = 210.0f; - propertyMetrics.labelTextInsetY = 4.0f; - propertyMetrics.labelFontSize = 10.0f; - propertyMetrics.valueTextInsetY = 3.0f; - propertyMetrics.valueFontSize = 12.0f; - propertyMetrics.valueBoxInsetY = 2.0f; - propertyMetrics.valueBoxInsetX = 5.0f; - propertyMetrics.cornerRounding = 1.0f; - propertyMetrics.valueBoxRounding = 2.0f; - propertyMetrics.borderThickness = 1.0f; - propertyMetrics.focusedBorderThickness = 2.0f; - - UI::Editor::Widgets::UIEditorPropertyGridPalette propertyPalette = {}; - propertyPalette.valueBoxColor = UI::UIColor(0.2f, 0.2f, 0.2f, 1.0f); - propertyPalette.valueBoxHoverColor = UI::UIColor(0.3f, 0.3f, 0.3f, 1.0f); - propertyPalette.valueBoxEditingColor = UI::UIColor(0.4f, 0.4f, 0.4f, 1.0f); - propertyPalette.valueBoxReadOnlyColor = UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); - propertyPalette.valueBoxBorderColor = UI::UIColor(0.5f, 0.5f, 0.5f, 1.0f); - propertyPalette.valueBoxEditingBorderColor = UI::UIColor(0.55f, 0.55f, 0.55f, 1.0f); - propertyPalette.labelTextColor = UI::UIColor(0.8f, 0.8f, 0.8f, 1.0f); - propertyPalette.valueTextColor = UI::UIColor(0.9f, 0.9f, 0.9f, 1.0f); - propertyPalette.readOnlyValueTextColor = UI::UIColor(0.6f, 0.6f, 0.6f, 1.0f); - - const auto boolMetrics = Editor::BuildUIEditorHostedBoolFieldMetrics(propertyMetrics); - const auto boolPalette = Editor::BuildUIEditorHostedBoolFieldPalette(propertyPalette); - EXPECT_FLOAT_EQ(boolMetrics.rowHeight, 25.0f); - EXPECT_FLOAT_EQ(boolMetrics.controlTrailingInset, 5.0f); - EXPECT_FLOAT_EQ(boolMetrics.labelFontSize, 10.0f); - EXPECT_FLOAT_EQ(boolMetrics.checkboxGlyphFontSize, 12.0f); - EXPECT_FLOAT_EQ(boolPalette.checkboxColor.r, 0.2f); - EXPECT_FLOAT_EQ(boolPalette.labelColor.r, 0.8f); - - const auto numberMetrics = Editor::BuildUIEditorHostedNumberFieldMetrics(propertyMetrics); - const auto numberPalette = Editor::BuildUIEditorHostedNumberFieldPalette(propertyPalette); - EXPECT_FLOAT_EQ(numberMetrics.controlTrailingInset, 5.0f); - EXPECT_FLOAT_EQ(numberMetrics.controlInsetY, 2.0f); - EXPECT_FLOAT_EQ(numberMetrics.valueTextInsetX, 5.0f); - EXPECT_FLOAT_EQ(numberMetrics.valueFontSize, 12.0f); - EXPECT_FLOAT_EQ(numberPalette.valueBoxEditingColor.r, 0.4f); - EXPECT_FLOAT_EQ(numberPalette.readOnlyValueColor.r, 0.6f); - EXPECT_FLOAT_EQ(numberPalette.controlFocusedBorderColor.r, 0.55f); - - const auto textMetrics = Editor::BuildUIEditorHostedTextFieldMetrics(propertyMetrics); - const auto textPalette = Editor::BuildUIEditorHostedTextFieldPalette(propertyPalette); - EXPECT_FLOAT_EQ(textMetrics.controlTrailingInset, 5.0f); - EXPECT_FLOAT_EQ(textMetrics.controlInsetY, 2.0f); - EXPECT_FLOAT_EQ(textMetrics.valueTextInsetX, 5.0f); - EXPECT_FLOAT_EQ(textMetrics.valueFontSize, 12.0f); - EXPECT_FLOAT_EQ(textPalette.valueBoxEditingColor.r, 0.4f); - EXPECT_FLOAT_EQ(textPalette.readOnlyValueColor.r, 0.6f); - EXPECT_FLOAT_EQ(textPalette.controlFocusedBorderColor.r, 0.55f); - - const auto vector2Metrics = Editor::BuildUIEditorHostedVector2FieldMetrics(propertyMetrics); - const auto vector2Palette = Editor::BuildUIEditorHostedVector2FieldPalette(propertyPalette); - EXPECT_FLOAT_EQ(vector2Metrics.controlInsetY, 2.0f); - EXPECT_FLOAT_EQ(vector2Metrics.controlTrailingInset, 5.0f); - EXPECT_FLOAT_EQ(vector2Metrics.valueTextInsetX, 5.0f); - EXPECT_FLOAT_EQ(vector2Metrics.componentRounding, 2.0f); - EXPECT_FLOAT_EQ(vector2Palette.componentEditingColor.r, 0.4f); - EXPECT_FLOAT_EQ(vector2Palette.componentFocusedBorderColor.r, 0.55f); - - const auto vector3Metrics = Editor::BuildUIEditorHostedVector3FieldMetrics(propertyMetrics); - const auto vector3Palette = Editor::BuildUIEditorHostedVector3FieldPalette(propertyPalette); - EXPECT_FLOAT_EQ(vector3Metrics.controlInsetY, 2.0f); - EXPECT_FLOAT_EQ(vector3Metrics.controlTrailingInset, 5.0f); - EXPECT_FLOAT_EQ(vector3Metrics.valueTextInsetX, 5.0f); - EXPECT_FLOAT_EQ(vector3Metrics.componentRounding, 2.0f); - EXPECT_FLOAT_EQ(vector3Palette.componentEditingColor.r, 0.4f); - EXPECT_FLOAT_EQ(vector3Palette.componentFocusedBorderColor.r, 0.55f); - - const auto vector4Metrics = Editor::BuildUIEditorHostedVector4FieldMetrics(propertyMetrics); - const auto vector4Palette = Editor::BuildUIEditorHostedVector4FieldPalette(propertyPalette); - EXPECT_FLOAT_EQ(vector4Metrics.controlInsetY, 2.0f); - EXPECT_FLOAT_EQ(vector4Metrics.controlTrailingInset, 5.0f); - EXPECT_FLOAT_EQ(vector4Metrics.valueTextInsetX, 5.0f); - EXPECT_FLOAT_EQ(vector4Metrics.componentRounding, 2.0f); - EXPECT_FLOAT_EQ(vector4Palette.componentEditingColor.r, 0.4f); - EXPECT_FLOAT_EQ(vector4Palette.componentFocusedBorderColor.r, 0.55f); - - const auto enumMetrics = Editor::BuildUIEditorHostedEnumFieldMetrics(propertyMetrics); - const auto enumPalette = Editor::BuildUIEditorHostedEnumFieldPalette(propertyPalette); - EXPECT_FLOAT_EQ(enumMetrics.controlTrailingInset, 5.0f); - EXPECT_FLOAT_EQ(enumMetrics.controlInsetY, 2.0f); - EXPECT_FLOAT_EQ(enumMetrics.valueTextInsetX, 5.0f); - EXPECT_FLOAT_EQ(enumMetrics.dropdownArrowFontSize, 12.0f); - EXPECT_FLOAT_EQ(enumPalette.arrowColor.r, 0.9f); - - const auto colorMetrics = Editor::BuildUIEditorHostedColorFieldMetrics(propertyMetrics); - const auto colorPalette = Editor::BuildUIEditorHostedColorFieldPalette(propertyPalette); - EXPECT_FLOAT_EQ(colorMetrics.controlTrailingInset, 5.0f); - EXPECT_FLOAT_EQ(colorMetrics.swatchInsetY, 2.0f); - EXPECT_FLOAT_EQ(colorMetrics.labelFontSize, 10.0f); - EXPECT_FLOAT_EQ(colorMetrics.valueFontSize, 12.0f); - EXPECT_FLOAT_EQ(colorMetrics.popupHeaderHeight, 30.0f); - EXPECT_FLOAT_EQ(colorPalette.labelColor.r, 0.8f); - EXPECT_FLOAT_EQ(colorPalette.popupBorderColor.r, 0.15f); - EXPECT_FLOAT_EQ(colorPalette.popupHeaderColor.r, 0.43f); - EXPECT_FLOAT_EQ(colorPalette.swatchBorderColor.r, 0.5f); -} - -} // namespace diff --git a/tests/UI/Editor/unit/test_ui_editor_vector2_field.cpp b/tests/UI/Editor/unit/test_ui_editor_vector2_field.cpp index e13314fb..1d9d47f5 100644 --- a/tests/UI/Editor/unit/test_ui_editor_vector2_field.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_vector2_field.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_vector2_field_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_vector2_field_interaction.cpp index afca66d9..8858edf6 100644 --- a/tests/UI/Editor/unit/test_ui_editor_vector2_field_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_vector2_field_interaction.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include diff --git a/tests/UI/Editor/unit/test_ui_editor_vector3_field.cpp b/tests/UI/Editor/unit/test_ui_editor_vector3_field.cpp index 49444f2c..0d8d1063 100644 --- a/tests/UI/Editor/unit/test_ui_editor_vector3_field.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_vector3_field.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_vector3_field_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_vector3_field_interaction.cpp index 0bb2818b..bd3a67c4 100644 --- a/tests/UI/Editor/unit/test_ui_editor_vector3_field_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_vector3_field_interaction.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include diff --git a/tests/UI/Editor/unit/test_ui_editor_vector4_field.cpp b/tests/UI/Editor/unit/test_ui_editor_vector4_field.cpp index 3db23c79..e958df61 100644 --- a/tests/UI/Editor/unit/test_ui_editor_vector4_field.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_vector4_field.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_vector4_field_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_vector4_field_interaction.cpp index 25cc6405..ca2d6e0c 100644 --- a/tests/UI/Editor/unit/test_ui_editor_vector4_field_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_vector4_field_interaction.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include 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 0120e67b..9f899c61 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 @@ -1,6 +1,6 @@ #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_viewport_shell.cpp b/tests/UI/Editor/unit/test_ui_editor_viewport_shell.cpp index 9ed03c72..3d88c3d4 100644 --- a/tests/UI/Editor/unit/test_ui_editor_viewport_shell.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_viewport_shell.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_viewport_slot.cpp b/tests/UI/Editor/unit/test_ui_editor_viewport_slot.cpp index 3dac0c8b..37963348 100644 --- a/tests/UI/Editor/unit/test_ui_editor_viewport_slot.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_viewport_slot.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_workspace_compose.cpp b/tests/UI/Editor/unit/test_ui_editor_workspace_compose.cpp index 791e5b1a..56f7cf54 100644 --- a/tests/UI/Editor/unit/test_ui_editor_workspace_compose.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_workspace_compose.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_workspace_controller.cpp b/tests/UI/Editor/unit/test_ui_editor_workspace_controller.cpp index eecde12c..88d1f15b 100644 --- a/tests/UI/Editor/unit/test_ui_editor_workspace_controller.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_workspace_controller.cpp @@ -1,6 +1,6 @@ #include -#include +#include 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 7345044a..5393dd43 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,6 @@ #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_workspace_layout_persistence.cpp b/tests/UI/Editor/unit/test_ui_editor_workspace_layout_persistence.cpp index a500d134..3ba4de1f 100644 --- a/tests/UI/Editor/unit/test_ui_editor_workspace_layout_persistence.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_workspace_layout_persistence.cpp @@ -1,7 +1,7 @@ #include -#include -#include +#include +#include #include #include diff --git a/tests/UI/Editor/unit/test_ui_editor_workspace_model.cpp b/tests/UI/Editor/unit/test_ui_editor_workspace_model.cpp index 23180245..3f95542d 100644 --- a/tests/UI/Editor/unit/test_ui_editor_workspace_model.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_workspace_model.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include #include diff --git a/tests/UI/Editor/unit/test_ui_editor_workspace_session.cpp b/tests/UI/Editor/unit/test_ui_editor_workspace_session.cpp index 1052a911..d508b9de 100644 --- a/tests/UI/Editor/unit/test_ui_editor_workspace_session.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_workspace_session.cpp @@ -1,8 +1,8 @@ #include -#include -#include -#include +#include +#include +#include #include