From c59cd83c3816179c1b0714ec79caae2c7e84367d Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sun, 19 Apr 2026 02:48:41 +0800 Subject: [PATCH] refactor(new_editor): tighten app dependency boundaries --- ...段依赖方向补充收口计划_阶段归档_2026-04-19.md | 216 ++++++++++++ new_editor/CMakeLists.txt | 49 ++- .../Commands}/EditorEditCommandRoute.h | 0 .../Commands}/EditorHostCommandBridge.h | 5 +- .../App => app/Composition}/EditorPanelIds.h | 0 .../Composition/EditorShellAssetCommands.cpp | 2 +- .../Composition/EditorShellAssetLayout.cpp | 2 +- .../app/Composition/EditorShellAssetMenu.cpp | 2 +- .../app/Composition/EditorShellRuntime.cpp | 24 +- .../app/Composition/EditorShellRuntime.h | 12 +- .../Composition/EditorShellRuntimeUpdate.cpp | 2 +- .../EditorShellRuntimeViewport.cpp | 2 +- .../app/Composition/WorkspaceEventSync.cpp | 2 +- .../app/Features/Console/ConsolePanel.cpp | 2 +- .../app/Features/Console/ConsolePanel.h | 2 +- .../app/Features/Hierarchy/HierarchyPanel.cpp | 2 +- .../app/Features/Hierarchy/HierarchyPanel.h | 6 +- .../Hierarchy/HierarchyPanelInternal.h | 2 +- .../app/Features/Inspector/InspectorPanel.cpp | 2 +- .../app/Features/Inspector/InspectorPanel.h | 2 +- .../app/Features/Inspector/InspectorSubject.h | 2 +- .../app/Features/Project/ProjectPanel.cpp | 4 +- .../app/Features/Project/ProjectPanel.h | 10 +- .../Features/Project/ProjectPanelInternal.h | 2 +- .../Features/Scene/SceneEditCommandRoute.h | 2 +- .../Scene/SceneViewportController.cpp | 8 +- .../Features/Scene/SceneViewportController.h | 6 +- .../Scene/SceneViewportToolOverlay.cpp | 6 +- .../Features/Scene/SceneViewportToolOverlay.h | 11 +- .../app/Features/Shared/GridItemDragDrop.h | 157 --------- .../app/Features/Shared/TreeItemDragDrop.h | 324 ------------------ new_editor/app/Host/HostFwd.h | 12 + .../Host/ShaderResourceDescriptorAllocator.h | 31 ++ new_editor/app/Host/TextureHost.h | 28 ++ new_editor/app/Host/ViewportRenderHost.h | 14 + new_editor/app/Internal/EmbeddedPngLoader.cpp | 4 +- new_editor/app/Internal/EmbeddedPngLoader.h | 8 +- .../LegacySceneViewportGizmoSupport.cpp | 1 + new_editor/app/Platform/Win32/EditorWindow.h | 55 ++- .../Win32/EditorWindowBorderlessPlacement.cpp | 35 +- .../Win32/EditorWindowBorderlessResize.cpp | 57 +-- .../app/Platform/Win32/EditorWindowFrame.cpp | 70 ++-- .../app/Platform/Win32/EditorWindowInput.cpp | 101 +++--- ...dowState.h => EditorWindowInternalState.h} | 26 +- .../Platform/Win32/EditorWindowLifecycle.cpp | 222 ++++++------ .../Win32/EditorWindowTitleBarInteraction.cpp | 60 ++-- .../Win32/EditorWindowTitleBarRendering.cpp | 26 +- .../Win32/EditorWindowTransferRequests.h | 33 ++ .../Platform/Win32/WindowManager/Internal.h | 3 - .../Win32/WindowManager/Lifecycle.cpp | 2 + .../Win32/WindowMessageDispatcher.cpp | 2 + new_editor/app/Project/EditorProjectRuntime.h | 2 +- .../app/Rendering/Assets/BuiltInIcons.cpp | 5 +- .../app/Rendering/Assets/BuiltInIcons.h | 6 +- .../D3D12ShaderResourceDescriptorAllocator.h | 14 +- .../app/Rendering/D3D12/D3D12WindowRenderer.h | 5 +- .../app/Rendering/Native/NativeRenderer.h | 12 +- .../Viewport/Passes/SceneViewportGridPass.cpp | 6 +- .../SceneViewportSelectionOutlinePass.cpp | 6 +- .../Viewport/ViewportHostService.cpp | 25 +- .../Rendering/Viewport/ViewportHostService.h | 11 +- .../Viewport/ViewportRenderTargets.cpp | 8 +- .../Viewport/ViewportRenderTargets.h | 13 +- new_editor/app/State/EditorContext.cpp | 2 +- new_editor/app/State/EditorContext.h | 4 +- .../App => app/State}/EditorSession.h | 0 .../Collections/UIEditorDragDropInteraction.h | 242 +++++++++++++ .../Collections/UIEditorGridDragDrop.h | 73 ++++ .../Collections/UIEditorTreeDragDrop.h | 201 +++++++++++ .../XCEditor/Panels/UIEditorPanelRegistry.h | 2 - .../XCEditor/Shell/UIEditorShellAsset.h | 3 +- .../Workspace/UIEditorWorkspaceModel.h | 2 - .../src/App/EditorHostCommandBridge.cpp | 2 +- new_editor/src/App/EditorSession.cpp | 5 +- .../src/Panels/UIEditorPanelRegistry.cpp | 15 - new_editor/src/Shell/UIEditorShellAsset.cpp | 68 ---- .../src/Workspace/UIEditorWorkspaceModel.cpp | 15 - .../shell/editor_shell_interaction/main.cpp | 5 +- tests/UI/Editor/unit/CMakeLists.txt | 18 +- .../unit/test_editor_host_command_bridge.cpp | 6 +- .../test_editor_shell_asset_validation.cpp | 83 ++--- tests/UI/Editor/unit/test_project_panel.cpp | 2 +- .../unit/test_scene_viewport_runtime.cpp | 2 +- .../unit/test_structured_editor_shell.cpp | 11 +- .../Editor/unit/test_ui_editor_drag_drop.cpp | 286 ++++++++++++++++ .../unit/test_ui_editor_panel_registry.cpp | 13 +- 86 files changed, 1754 insertions(+), 1077 deletions(-) create mode 100644 docs/used/NewEditor_UI第二阶段依赖方向补充收口计划_阶段归档_2026-04-19.md rename new_editor/{include/XCEditor/App => app/Commands}/EditorEditCommandRoute.h (100%) rename new_editor/{include/XCEditor/App => app/Commands}/EditorHostCommandBridge.h (96%) rename new_editor/{include/XCEditor/App => app/Composition}/EditorPanelIds.h (100%) delete mode 100644 new_editor/app/Features/Shared/GridItemDragDrop.h delete mode 100644 new_editor/app/Features/Shared/TreeItemDragDrop.h create mode 100644 new_editor/app/Host/HostFwd.h create mode 100644 new_editor/app/Host/ShaderResourceDescriptorAllocator.h create mode 100644 new_editor/app/Host/TextureHost.h create mode 100644 new_editor/app/Host/ViewportRenderHost.h rename new_editor/app/Platform/Win32/{EditorWindowState.h => EditorWindowInternalState.h} (77%) create mode 100644 new_editor/app/Platform/Win32/EditorWindowTransferRequests.h rename new_editor/{include/XCEditor/App => app/State}/EditorSession.h (100%) create mode 100644 new_editor/include/XCEditor/Collections/UIEditorDragDropInteraction.h create mode 100644 new_editor/include/XCEditor/Collections/UIEditorGridDragDrop.h create mode 100644 new_editor/include/XCEditor/Collections/UIEditorTreeDragDrop.h create mode 100644 tests/UI/Editor/unit/test_ui_editor_drag_drop.cpp diff --git a/docs/used/NewEditor_UI第二阶段依赖方向补充收口计划_阶段归档_2026-04-19.md b/docs/used/NewEditor_UI第二阶段依赖方向补充收口计划_阶段归档_2026-04-19.md new file mode 100644 index 00000000..0703ee24 --- /dev/null +++ b/docs/used/NewEditor_UI第二阶段依赖方向补充收口计划_阶段归档_2026-04-19.md @@ -0,0 +1,216 @@ +# NewEditor UI第二阶段依赖方向补充收口计划 + +日期:2026-04-19 + +## 背景 + +这是对 `docs/plan/NewEditor_UI第二阶段依赖方向子计划_2026-04-19.md` 的补充收口说明。 +本补充只覆盖本轮严格审查后确认的两个根因,以及对应的正式解法与本轮落地结果。 + +## 严格审查补充结论 + +### 1. 真正的问题不是“几个 include 写错了”,而是 `new_editor/app` 里混放了两种责任 + +- 一类是 `AppCore` 责任:产品状态、面板逻辑、场景工具、viewport 业务渲染组织。 +- 一类是 `Win32/Host` 组合责任:窗口宿主、D3D12 设备、NativeRenderer、窗口渲染循环。 +- 现在这两类代码虽然目录上看似分层,但仍然通过具体实现头直接耦合,导致 `XCUIEditorAppLib` 的边界仍然偏假。 + +根因判断: + +- 真正缺失的是一层窄契约,导致 `AppCore` 代码只能直接抓 `NativeRenderer`、`D3D12WindowRenderer`、`D3D12ShaderResourceDescriptorAllocator` 这类宿主实现。 +- 只要没有窄契约,后续即使继续删 include,也只是补丁式修补,无法为后续 target 拆分提供稳定基础。 + +正式解法: + +1. 先在 `new_editor/app/Host` 建立窄契约层,把 `AppCore` 真实需要的能力抽出来。 +2. `AppCore` 只依赖窄契约,不再直接依赖 Host 具体实现头。 +3. 在此基础上,再进入下一步 target 收口:把当前 `XCUIEditorAppLib` 继续拆成真正的 `AppCore` 与 `Win32 组合边界`。 + +### 2. 公共 API 中真正存在的产品默认值泄漏,当前只确认到 `EditorShellAsset.screenId` + +- `BuildDefaultUIEditorWorkspaceSession` +- `BuildDefaultUIEditorWorkspaceController` +- `BuildDefaultUIEditorWindowWorkspaceSet` +- `BuildDefaultUIEditorWindowWorkspaceController` + +严格审查结论: + +- 这四个 `BuildDefault*` 当前仍被大量公共层单测、集成测试和 structured shell 装配逻辑直接使用。 +- 从行为上看,它们表达的是 UI workspace 的通用默认装配语义,不是 `new_editor` 产品私有策略。 +- 因此这一批 API 当前不应强行下沉,否则会把通用装配能力误收口成产品实现。 + +真正的问题点是: + +- `XCEditor/Shell/UIEditorShellAsset.h` 的 `screenId = "editor.shell"` 把产品默认值放进了公共结构体默认值里。 + +正式解法: + +1. 公共 `EditorShellAsset` 只保留空默认值。 +2. `"editor.shell"` 只在 `new_editor/app/Composition/EditorShellAssetBuilder.cpp` 这种产品装配位置赋值。 + +## 本轮执行项 + +### 已执行:抽离 Host 窄契约 + +新增: + +- `new_editor/app/Host/TextureHost.h` +- `new_editor/app/Host/ViewportRenderHost.h` +- `new_editor/app/Host/ShaderResourceDescriptorAllocator.h` + +本轮已经把以下 `AppCore` 代码改为依赖窄契约,而不是直接依赖 Host 具体实现: + +- `BuiltInIcons` +- `EmbeddedPngLoader` +- `SceneViewportToolOverlay` +- `SceneViewportController` +- `ViewportRenderTargets` +- `ViewportHostService` +- `EditorShellRuntime` + +本轮效果: + +- feature / rendering / composition 中真正属于 `AppCore` 的代码,已经不再直接 include `NativeRenderer.h` / `D3D12WindowRenderer.h` / `D3D12ShaderResourceDescriptorAllocator.h` +- 当前残留的具体 Host 头依赖,已经主要收缩到真正的 Win32/Host 组合边界: + - `new_editor/app/Platform/Win32/EditorWindowInternalState.h` + - `new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.h` + - `new_editor/app/Rendering/Native/NativeRenderer.h` + - `new_editor/app/Rendering/Viewport/ViewportHostService.cpp` 中的具体 allocator 构造点 + +### 已执行:收掉公共 API 的产品默认值 + +已调整: + +- `new_editor/include/XCEditor/Shell/UIEditorShellAsset.h` + +当前状态: + +- `EditorShellAsset.screenId` 默认值已改为空 +- `"editor.shell"` 继续只在 `new_editor/app/Composition/EditorShellAssetBuilder.cpp` 中设置 + +## 验证结果 + +使用全新验证构建目录 `build_codex_verify` 完成了本轮验证。 + +已通过: + +- `cmake -S . -B build_codex_verify -G "Visual Studio 17 2022" -A x64 -DXCENGINE_BUILD_XCUI_EDITOR_APP=ON -DFETCHCONTENT_SOURCE_DIR_GOOGLETEST=D:/Xuanchi/Main/XCEngine/build/_deps/googletest-src` +- `cmake --build build_codex_verify --config Debug --target editor_app_feature_tests` +- `build_codex_verify/tests/UI/Editor/unit/Debug/editor_app_feature_tests.exe` + +结果: + +- `82 / 82` 通过 + +## 本轮追加执行结果 + +### 已完成:把 `XCUIEditorAppLib` 正式拆成 `AppCore` 与 Win32 组合层 + +已调整: + +- `new_editor/CMakeLists.txt` +- `tests/UI/Editor/unit/CMakeLists.txt` + +当前 target 结构: + +- `XCUIEditorAppCore` + - 承接 `app/State` + - 承接 `app/Composition` + - 承接 `app/Features` + - 承接 `app/Rendering/Viewport` 与 `app/Rendering/Assets` + - 承接 `app/Project` / `app/Scene` / `app/Internal` + - 承接 `src/App` +- `XCUIEditorAppLib` + - 只承接 `app/Platform/Win32` + - 作为 Win32 产品组合层,依赖 `XCUIEditorAppCore` + +本轮效果: + +- `AppCore` 已经从构建层面独立出来,不再和 Win32 窗口宿主代码混编在同一个 target 中 +- `EditorWindow`、`WindowManager`、`ApplicationBootstrap` 这类窗口组合代码继续保留在最外层产品组合边界 +- `editor_app_feature_tests` 现已显式链接 `XCUIEditorAppCore`,不再把所有产品逻辑都隐式压在 `XCUIEditorAppLib` 一个 target 上 + +### 已完成:补齐 `XCUIEditorApp` 的运行时 DLL 部署规则 + +根因: + +- 干净构建目录下 `xcui_editor_app_smoke` 失败,退出码 `0xc0000135` +- 原因不是代码逻辑错误,而是 `XCUIEditorApp` target 缺少 `assimp` 与 `PhysX` 运行时 DLL 的 post-build 拷贝规则 + +已修复: + +- `new_editor/CMakeLists.txt` + +结果: + +- `XCUIEditor.exe` 输出目录已自动部署: + - `assimp-vc143-mt.dll` + - `PhysXCommon_64.dll` + - `PhysXCooking_64.dll` + - `PhysXFoundation_64.dll` + - `PhysX_64.dll` + - `PVDRuntime_64.dll` + +### 追加验证 + +已通过: + +- `cmake --build build_codex_verify --config Debug --target XCUIEditorApp editor_app_feature_tests` +- `build_codex_verify/tests/UI/Editor/unit/Debug/editor_app_feature_tests.exe` +- `ctest --output-on-failure -C Debug --test-dir build_codex_verify -R '^xcui_editor_app_smoke$'` + +结果: + +- `editor_app_feature_tests`: `82 / 82` 通过 +- `xcui_editor_app_smoke`: 通过 + +### 已完成:把 `EditorWindow` 的传输数据与私有状态正式拆开 + +已调整: + +- `new_editor/app/Platform/Win32/EditorWindow.h` +- `new_editor/app/Platform/Win32/EditorWindowTransferRequests.h` +- `new_editor/app/Platform/Win32/EditorWindowInternalState.h` +- `new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp` +- `new_editor/app/Platform/Win32/EditorWindowFrame.cpp` +- `new_editor/app/Platform/Win32/EditorWindowInput.cpp` +- `new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp` +- `new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp` +- `new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp` +- `new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp` +- `new_editor/app/Platform/Win32/WindowMessageDispatcher.cpp` +- `new_editor/app/Platform/Win32/WindowManager/Internal.h` +- `new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp` + +根因与处理: + +- 旧的 `EditorWindowState.h` 同时承载了两类语义: + - 跨窗口/管理层需要消费的传输请求 + - 只属于 `EditorWindow` 实现细节的渲染、输入、shell runtime、chrome 运行时状态 +- 这会让 `EditorWindow.h` 把 `EditorShellRuntime`、`NativeRenderer`、`D3D12WindowRenderer`、`D3D12WindowRenderLoop`、`AutoScreenshot` 等具体实现全部泄漏到 Win32 公开头面。 +- 本轮将 `EditorWindowPanelTransferRequest` 与 `EditorWindowFrameTransferRequests` 独立到轻量头 `EditorWindowTransferRequests.h`。 +- 将窗口私有实现状态收回到 `EditorWindowInternalState.h`,并由 `EditorWindow` 通过 `std::unique_ptr` 私有持有。 + +本轮效果: + +- `EditorWindow.h` 不再直接暴露私有状态聚合,也不再 include `Composition/EditorShellRuntime.h`、`Rendering/Native/NativeRenderer.h`、`Rendering/D3D12/D3D12WindowRenderer.h`、`Rendering/D3D12/D3D12WindowRenderLoop.h` 等宿主实现头。 +- `EditorWindow.h` 进一步收缩为以前向声明为主,不再顺带带出 `XCEditor` 的 shell/workspace/dock 公开头。 +- `WindowManager/Internal.h` 也不再顺带 include `UIEditorWindowWorkspaceController.h` 与 `UIEditorWorkspaceController.h`,这些公开 workspace 类型的完整依赖已回收到真正使用它们的 `WindowManager/Lifecycle.cpp`。 +- 当前 `new_editor/app/Platform/Win32` 头文件中,上述重依赖已只剩 `EditorWindowInternalState.h` 一处持有,Win32 公开头面的边界已经和实现边界对齐。 + +追加验证: + +- `cmake --build build_codex_verify --config Debug --target XCUIEditorApp editor_app_feature_tests` +- `build_codex_verify/tests/UI/Editor/unit/Debug/editor_app_feature_tests.exe` +- `ctest --output-on-failure -C Debug --test-dir build_codex_verify -R '^xcui_editor_app_smoke$'` + +结果: + +- `editor_app_feature_tests`: `82 / 82` 通过 +- `xcui_editor_app_smoke`: 通过 + +## 下一步收口 + +1. 在 `XCUIEditorAppCore` / `XCUIEditorAppLib` 已分离、`EditorWindow` 公开头已收缩的基础上,继续收紧 `target_include_directories`,把当前“目录还可穿透、但代码已不再依赖”的状态彻底变成“构建层也不能穿透”。 +2. 继续审查 `WindowManager` / `Bootstrap` 头面的公开依赖,确认是否还存在类似 `EditorWindow` 这种“实现状态被头面顺带暴露”的问题。 +3. 视下一轮审查结果,再决定是否需要把 `Bootstrap` 从可执行入口中进一步下沉为单独的产品装配 target。 diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index 48a76e57..17471026 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -27,11 +27,6 @@ set(XCUI_EDITOR_FOUNDATION_SOURCES src/Foundation/UIEditorTheme.cpp ) -set(XCUI_EDITOR_APP_API_SOURCES - src/App/EditorHostCommandBridge.cpp - src/App/EditorSession.cpp -) - set(XCUI_EDITOR_FIELD_SOURCES src/Fields/UIEditorAssetField.cpp src/Fields/UIEditorAssetFieldInteraction.cpp @@ -144,7 +139,6 @@ set(XCUI_EDITOR_WIDGET_SUPPORT_SOURCES add_library(XCUIEditorLib STATIC ${XCUI_EDITOR_FOUNDATION_SOURCES} - ${XCUI_EDITOR_APP_API_SOURCES} ${XCUI_EDITOR_FIELD_SOURCES} ${XCUI_EDITOR_COLLECTION_SOURCES} ${XCUI_EDITOR_DOCKING_SOURCES} @@ -160,7 +154,6 @@ target_include_directories(XCUIEditorLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/engine/include - ${CMAKE_SOURCE_DIR}/engine/include/XCEngine PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src ) @@ -304,6 +297,8 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Scene/EditorSceneRuntime.cpp app/Internal/EmbeddedPngLoader.cpp app/Scene/EditorSceneBridge.cpp + src/App/EditorHostCommandBridge.cpp + src/App/EditorSession.cpp ) set(XCUI_EDITOR_APP_PLATFORM_SOURCES @@ -319,17 +314,37 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Platform/Win32/WindowManager/TabDrag.cpp ) - set(XCUI_EDITOR_APP_LAYER_SOURCES + set(XCUI_EDITOR_APP_CORE_SOURCES ${XCUI_EDITOR_APP_STATE_SOURCES} ${XCUI_EDITOR_APP_COMPOSITION_SOURCES} ${XCUI_EDITOR_APP_FEATURE_SOURCES} ${XCUI_EDITOR_APP_RENDERING_SOURCES} ${XCUI_EDITOR_APP_SUPPORT_SOURCES} - ${XCUI_EDITOR_APP_PLATFORM_SOURCES} + ) + + add_library(XCUIEditorAppCore STATIC + ${XCUI_EDITOR_APP_CORE_SOURCES} + ) + + target_include_directories(XCUIEditorAppCore PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/app + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/engine/include + ) + + target_compile_definitions(XCUIEditorAppCore PRIVATE + XCUIEDITOR_REPO_ROOT="${XCUIEDITOR_REPO_ROOT_PATH}" + ) + + xcui_editor_apply_common_target_settings(XCUIEditorAppCore PRIVATE) + + target_link_libraries(XCUIEditorAppCore PRIVATE + XCUIEditorLib + XCUIEditorHost ) add_library(XCUIEditorAppLib STATIC - ${XCUI_EDITOR_APP_LAYER_SOURCES} + ${XCUI_EDITOR_APP_PLATFORM_SOURCES} ) target_include_directories(XCUIEditorAppLib PRIVATE @@ -345,6 +360,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) xcui_editor_apply_common_target_settings(XCUIEditorAppLib PRIVATE) target_link_libraries(XCUIEditorAppLib PRIVATE + XCUIEditorAppCore XCUIEditorLib XCUIEditorHost ) @@ -365,6 +381,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) target_link_libraries(XCUIEditorApp PRIVATE XCUIEditorAppLib + XCUIEditorAppCore XCUIEditorLib XCUIEditorHost d2d1.lib @@ -380,6 +397,18 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) OUTPUT_NAME "XCUIEditor" ) + if(WIN32 AND EXISTS "${CMAKE_SOURCE_DIR}/engine/third_party/assimp/bin/assimp-vc143-mt.dll") + add_custom_command(TARGET XCUIEditorApp POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_SOURCE_DIR}/engine/third_party/assimp/bin/assimp-vc143-mt.dll + $/assimp-vc143-mt.dll + ) + endif() + + if(WIN32 AND XCENGINE_ENABLE_PHYSX) + xcengine_copy_physx_runtime_dlls(XCUIEditorApp) + endif() + add_test( NAME xcui_editor_app_smoke COMMAND $ diff --git a/new_editor/include/XCEditor/App/EditorEditCommandRoute.h b/new_editor/app/Commands/EditorEditCommandRoute.h similarity index 100% rename from new_editor/include/XCEditor/App/EditorEditCommandRoute.h rename to new_editor/app/Commands/EditorEditCommandRoute.h diff --git a/new_editor/include/XCEditor/App/EditorHostCommandBridge.h b/new_editor/app/Commands/EditorHostCommandBridge.h similarity index 96% rename from new_editor/include/XCEditor/App/EditorHostCommandBridge.h rename to new_editor/app/Commands/EditorHostCommandBridge.h index 6d31f565..0f217cda 100644 --- a/new_editor/include/XCEditor/App/EditorHostCommandBridge.h +++ b/new_editor/app/Commands/EditorHostCommandBridge.h @@ -1,7 +1,8 @@ #pragma once -#include -#include +#include "Commands/EditorEditCommandRoute.h" +#include "State/EditorSession.h" + #include #include diff --git a/new_editor/include/XCEditor/App/EditorPanelIds.h b/new_editor/app/Composition/EditorPanelIds.h similarity index 100% rename from new_editor/include/XCEditor/App/EditorPanelIds.h rename to new_editor/app/Composition/EditorPanelIds.h diff --git a/new_editor/app/Composition/EditorShellAssetCommands.cpp b/new_editor/app/Composition/EditorShellAssetCommands.cpp index 4d242ae8..e3848f12 100644 --- a/new_editor/app/Composition/EditorShellAssetCommands.cpp +++ b/new_editor/app/Composition/EditorShellAssetCommands.cpp @@ -1,6 +1,6 @@ #include "EditorShellAssetBuilderInternal.h" -#include +#include "Composition/EditorPanelIds.h" #include #include diff --git a/new_editor/app/Composition/EditorShellAssetLayout.cpp b/new_editor/app/Composition/EditorShellAssetLayout.cpp index 10764f36..c828b770 100644 --- a/new_editor/app/Composition/EditorShellAssetLayout.cpp +++ b/new_editor/app/Composition/EditorShellAssetLayout.cpp @@ -1,6 +1,6 @@ #include "EditorShellAssetBuilderInternal.h" -#include +#include "Composition/EditorPanelIds.h" namespace XCEngine::UI::Editor::App::CompositionInternal { diff --git a/new_editor/app/Composition/EditorShellAssetMenu.cpp b/new_editor/app/Composition/EditorShellAssetMenu.cpp index ced4d838..7e022ec0 100644 --- a/new_editor/app/Composition/EditorShellAssetMenu.cpp +++ b/new_editor/app/Composition/EditorShellAssetMenu.cpp @@ -1,6 +1,6 @@ #include "EditorShellAssetBuilderInternal.h" -#include +#include "Composition/EditorPanelIds.h" #include diff --git a/new_editor/app/Composition/EditorShellRuntime.cpp b/new_editor/app/Composition/EditorShellRuntime.cpp index a245a72c..70a4b398 100644 --- a/new_editor/app/Composition/EditorShellRuntime.cpp +++ b/new_editor/app/Composition/EditorShellRuntime.cpp @@ -1,28 +1,28 @@ #include "Composition/EditorShellRuntime.h" +#include "Host/TextureHost.h" +#include "Host/ViewportRenderHost.h" #include "State/EditorContext.h" -#include -#include - #include namespace XCEngine::UI::Editor::App { void EditorShellRuntime::Initialize( const std::filesystem::path& repoRoot, - Host::NativeRenderer& renderer) { - m_nativeRenderer = &renderer; - m_builtInIcons.Initialize(renderer); - m_sceneViewportController.Initialize(repoRoot, renderer); + Host::TextureHost& textureHost, + UIEditorTextMeasurer& textMeasurer) { + m_textureHost = &textureHost; + m_builtInIcons.Initialize(textureHost); + m_sceneViewportController.Initialize(repoRoot, textureHost); m_hierarchyPanel.SetBuiltInIcons(&m_builtInIcons); m_projectPanel.SetBuiltInIcons(&m_builtInIcons); - m_projectPanel.SetTextMeasurer(&renderer); + m_projectPanel.SetTextMeasurer(&textMeasurer); m_hierarchyPanel.Initialize(); m_projectPanel.Initialize(repoRoot); } -void EditorShellRuntime::AttachViewportWindowRenderer(Host::D3D12WindowRenderer& renderer) { +void EditorShellRuntime::AttachViewportWindowRenderer(Host::ViewportRenderHost& renderer) { m_viewportHostService.AttachWindowRenderer(renderer); } @@ -40,10 +40,10 @@ void EditorShellRuntime::Shutdown() { m_splitterDragCorrectionState = {}; m_traceEntries.clear(); m_sceneEditCommandRoute = {}; - if (m_nativeRenderer != nullptr) { - m_sceneViewportController.Shutdown(*m_nativeRenderer); + if (m_textureHost != nullptr) { + m_sceneViewportController.Shutdown(*m_textureHost); m_builtInIcons.Shutdown(); - m_nativeRenderer = nullptr; + m_textureHost = nullptr; } else { m_builtInIcons.Shutdown(); } diff --git a/new_editor/app/Composition/EditorShellRuntime.h b/new_editor/app/Composition/EditorShellRuntime.h index 95f34f0b..cec14a13 100644 --- a/new_editor/app/Composition/EditorShellRuntime.h +++ b/new_editor/app/Composition/EditorShellRuntime.h @@ -12,6 +12,7 @@ #include "Composition/WorkspaceEventSync.h" #include +#include #include #include @@ -29,8 +30,8 @@ class EditorContext; namespace XCEngine::UI::Editor::Host { -class D3D12WindowRenderer; -class NativeRenderer; +class TextureHost; +class ViewportRenderHost; } // namespace XCEngine::UI::Editor::Host @@ -46,10 +47,11 @@ class EditorShellRuntime { public: void Initialize( const std::filesystem::path& repoRoot, - Host::NativeRenderer& renderer); + Host::TextureHost& textureHost, + UIEditorTextMeasurer& textMeasurer); void Shutdown(); void ResetInteractionState(); - void AttachViewportWindowRenderer(Host::D3D12WindowRenderer& renderer); + void AttachViewportWindowRenderer(Host::ViewportRenderHost& renderer); void DetachViewportWindowRenderer(); void SetViewportSurfacePresentationEnabled(bool enabled); @@ -87,7 +89,7 @@ public: private: ViewportHostService m_viewportHostService = {}; BuiltInIcons m_builtInIcons = {}; - Host::NativeRenderer* m_nativeRenderer = nullptr; + Host::TextureHost* m_textureHost = nullptr; ConsolePanel m_consolePanel = {}; HierarchyPanel m_hierarchyPanel = {}; InspectorPanel m_inspectorPanel = {}; diff --git a/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp b/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp index a3388e40..da14daa0 100644 --- a/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp +++ b/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp @@ -2,7 +2,7 @@ #include "State/EditorContext.h" -#include +#include "Composition/EditorPanelIds.h" #include namespace XCEngine::UI::Editor::App::Internal { diff --git a/new_editor/app/Composition/EditorShellRuntimeViewport.cpp b/new_editor/app/Composition/EditorShellRuntimeViewport.cpp index 90a6bda9..9f31a2a9 100644 --- a/new_editor/app/Composition/EditorShellRuntimeViewport.cpp +++ b/new_editor/app/Composition/EditorShellRuntimeViewport.cpp @@ -1,6 +1,6 @@ #include "Composition/EditorShellRuntimeInternal.h" -#include +#include "Composition/EditorPanelIds.h" namespace XCEngine::UI::Editor::App::Internal { diff --git a/new_editor/app/Composition/WorkspaceEventSync.cpp b/new_editor/app/Composition/WorkspaceEventSync.cpp index 28a30547..b3fc5f1b 100644 --- a/new_editor/app/Composition/WorkspaceEventSync.cpp +++ b/new_editor/app/Composition/WorkspaceEventSync.cpp @@ -5,7 +5,7 @@ #include "Features/Project/ProjectPanel.h" #include "Composition/EditorShellRuntime.h" -#include +#include "Composition/EditorPanelIds.h" #include #include diff --git a/new_editor/app/Features/Console/ConsolePanel.cpp b/new_editor/app/Features/Console/ConsolePanel.cpp index 9d50fc60..f4f6d788 100644 --- a/new_editor/app/Features/Console/ConsolePanel.cpp +++ b/new_editor/app/Features/Console/ConsolePanel.cpp @@ -1,6 +1,6 @@ #include "ConsolePanel.h" -#include +#include "Composition/EditorPanelIds.h" #include #include diff --git a/new_editor/app/Features/Console/ConsolePanel.h b/new_editor/app/Features/Console/ConsolePanel.h index b017d4ab..f8216370 100644 --- a/new_editor/app/Features/Console/ConsolePanel.h +++ b/new_editor/app/Features/Console/ConsolePanel.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "State/EditorSession.h" #include diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp index d32d5235..a50afeb5 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp +++ b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp @@ -10,7 +10,7 @@ namespace XCEngine::UI::Editor::App { using namespace HierarchyPanelInternal; -namespace TreeDrag = TreeItemDragDrop; +namespace TreeDrag = XCEngine::UI::Editor::Collections::TreeDragDrop; namespace { diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanel.h b/new_editor/app/Features/Hierarchy/HierarchyPanel.h index 33bd38a1..61c60f95 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanel.h +++ b/new_editor/app/Features/Hierarchy/HierarchyPanel.h @@ -1,9 +1,9 @@ #pragma once -#include "Features/Shared/TreeItemDragDrop.h" #include "HierarchyModel.h" -#include +#include "Commands/EditorEditCommandRoute.h" +#include #include #include #include @@ -102,7 +102,7 @@ private: UIEditorInlineRenameSessionFrame m_renameFrame = {}; std::string m_pendingRenameItemId = {}; std::vector m_frameEvents = {}; - TreeItemDragDrop::State m_dragState = {}; + Collections::TreeDragDrop::State m_dragState = {}; bool m_visible = false; }; diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanelInternal.h b/new_editor/app/Features/Hierarchy/HierarchyPanelInternal.h index 7de5e63b..cae353d9 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanelInternal.h +++ b/new_editor/app/Features/Hierarchy/HierarchyPanelInternal.h @@ -4,7 +4,7 @@ #include "Rendering/Assets/BuiltInIcons.h" -#include +#include "Composition/EditorPanelIds.h" #include #include diff --git a/new_editor/app/Features/Inspector/InspectorPanel.cpp b/new_editor/app/Features/Inspector/InspectorPanel.cpp index 4bc67b9d..41fbb1ea 100644 --- a/new_editor/app/Features/Inspector/InspectorPanel.cpp +++ b/new_editor/app/Features/Inspector/InspectorPanel.cpp @@ -1,6 +1,6 @@ #include "InspectorPanel.h" -#include +#include "Composition/EditorPanelIds.h" #include #include diff --git a/new_editor/app/Features/Inspector/InspectorPanel.h b/new_editor/app/Features/Inspector/InspectorPanel.h index 1c2e7f61..1add1924 100644 --- a/new_editor/app/Features/Inspector/InspectorPanel.h +++ b/new_editor/app/Features/Inspector/InspectorPanel.h @@ -3,7 +3,7 @@ #include "Features/Inspector/InspectorPresentationModel.h" #include "Features/Inspector/InspectorSubject.h" -#include +#include "Commands/EditorEditCommandRoute.h" #include #include diff --git a/new_editor/app/Features/Inspector/InspectorSubject.h b/new_editor/app/Features/Inspector/InspectorSubject.h index 703f20a3..39851c21 100644 --- a/new_editor/app/Features/Inspector/InspectorSubject.h +++ b/new_editor/app/Features/Inspector/InspectorSubject.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "State/EditorSession.h" #include #include diff --git a/new_editor/app/Features/Project/ProjectPanel.cpp b/new_editor/app/Features/Project/ProjectPanel.cpp index 5cc23045..70003877 100644 --- a/new_editor/app/Features/Project/ProjectPanel.cpp +++ b/new_editor/app/Features/Project/ProjectPanel.cpp @@ -20,8 +20,8 @@ namespace XCEngine::UI::Editor::App { using namespace ProjectPanelInternal; -namespace GridDrag = GridItemDragDrop; -namespace TreeDrag = TreeItemDragDrop; +namespace GridDrag = XCEngine::UI::Editor::Collections::GridDragDrop; +namespace TreeDrag = XCEngine::UI::Editor::Collections::TreeDragDrop; namespace { diff --git a/new_editor/app/Features/Project/ProjectPanel.h b/new_editor/app/Features/Project/ProjectPanel.h index 904942b9..0545c438 100644 --- a/new_editor/app/Features/Project/ProjectPanel.h +++ b/new_editor/app/Features/Project/ProjectPanel.h @@ -1,12 +1,12 @@ #pragma once -#include "Features/Shared/GridItemDragDrop.h" -#include "Features/Shared/TreeItemDragDrop.h" #include "Project/EditorProjectRuntime.h" #include "ProjectBrowserModel.h" -#include +#include "Commands/EditorEditCommandRoute.h" +#include #include +#include #include #include #include @@ -229,8 +229,8 @@ private: ::XCEngine::UI::Widgets::UISelectionModel m_folderSelection = {}; ::XCEngine::UI::Widgets::UIExpansionModel m_folderExpansion = {}; ::XCEngine::UI::Widgets::UISelectionModel m_assetSelection = {}; - GridItemDragDrop::State m_assetDragState = {}; - TreeItemDragDrop::State m_treeDragState = {}; + Collections::GridDragDrop::State m_assetDragState = {}; + Collections::TreeDragDrop::State m_treeDragState = {}; UIEditorTreeViewInteractionState m_treeInteractionState = {}; UIEditorTreeViewInteractionFrame m_treeFrame = {}; UIEditorInlineRenameSessionState m_renameState = {}; diff --git a/new_editor/app/Features/Project/ProjectPanelInternal.h b/new_editor/app/Features/Project/ProjectPanelInternal.h index f695ccb0..b894a8a3 100644 --- a/new_editor/app/Features/Project/ProjectPanelInternal.h +++ b/new_editor/app/Features/Project/ProjectPanelInternal.h @@ -4,7 +4,7 @@ #include "Rendering/Assets/BuiltInIcons.h" -#include +#include "Composition/EditorPanelIds.h" #include #include diff --git a/new_editor/app/Features/Scene/SceneEditCommandRoute.h b/new_editor/app/Features/Scene/SceneEditCommandRoute.h index 37239faa..83169a38 100644 --- a/new_editor/app/Features/Scene/SceneEditCommandRoute.h +++ b/new_editor/app/Features/Scene/SceneEditCommandRoute.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "Commands/EditorEditCommandRoute.h" #include diff --git a/new_editor/app/Features/Scene/SceneViewportController.cpp b/new_editor/app/Features/Scene/SceneViewportController.cpp index f2401c1e..1046c1f3 100644 --- a/new_editor/app/Features/Scene/SceneViewportController.cpp +++ b/new_editor/app/Features/Scene/SceneViewportController.cpp @@ -3,9 +3,7 @@ #include "Rendering/Viewport/ViewportHostService.h" #include "Scene/EditorSceneRuntime.h" -#include - -#include +#include "Composition/EditorPanelIds.h" #include #include @@ -120,12 +118,12 @@ void ApplySceneToolMode( void SceneViewportController::Initialize( const std::filesystem::path& repoRoot, - Host::NativeRenderer& renderer) { + Host::TextureHost& renderer) { m_toolOverlay.Initialize(repoRoot, renderer); ResetInteractionState(); } -void SceneViewportController::Shutdown(Host::NativeRenderer& renderer) { +void SceneViewportController::Shutdown(Host::TextureHost& renderer) { m_toolOverlay.Shutdown(renderer); ResetInteractionState(); } diff --git a/new_editor/app/Features/Scene/SceneViewportController.h b/new_editor/app/Features/Scene/SceneViewportController.h index cce91dfd..8ab02cae 100644 --- a/new_editor/app/Features/Scene/SceneViewportController.h +++ b/new_editor/app/Features/Scene/SceneViewportController.h @@ -20,7 +20,7 @@ class ViewportHostService; namespace XCEngine::UI::Editor::Host { -class NativeRenderer; +class TextureHost; } // namespace XCEngine::UI::Editor::Host @@ -30,8 +30,8 @@ class SceneViewportController { public: void Initialize( const std::filesystem::path& repoRoot, - Host::NativeRenderer& renderer); - void Shutdown(Host::NativeRenderer& renderer); + Host::TextureHost& renderer); + void Shutdown(Host::TextureHost& renderer); void ResetInteractionState(); void Update( diff --git a/new_editor/app/Features/Scene/SceneViewportToolOverlay.cpp b/new_editor/app/Features/Scene/SceneViewportToolOverlay.cpp index b2e5d5b4..60a7885d 100644 --- a/new_editor/app/Features/Scene/SceneViewportToolOverlay.cpp +++ b/new_editor/app/Features/Scene/SceneViewportToolOverlay.cpp @@ -1,6 +1,6 @@ #include "Features/Scene/SceneViewportToolOverlay.h" -#include +#include "Host/TextureHost.h" #include #include @@ -57,7 +57,7 @@ UIRect BuildButtonRect(const UIRect& panelRect, std::size_t index) { bool SceneViewportToolOverlay::Initialize( const std::filesystem::path& repoRoot, - Host::NativeRenderer& renderer) { + Host::TextureHost& renderer) { Shutdown(renderer); const std::filesystem::path iconRoot = @@ -87,7 +87,7 @@ bool SceneViewportToolOverlay::Initialize( return loadedAnyTexture; } -void SceneViewportToolOverlay::Shutdown(Host::NativeRenderer& renderer) { +void SceneViewportToolOverlay::Shutdown(Host::TextureHost& renderer) { for (ToolTextureSet& textureSet : m_toolTextures) { renderer.ReleaseTexture(textureSet.inactiveTexture); renderer.ReleaseTexture(textureSet.activeTexture); diff --git a/new_editor/app/Features/Scene/SceneViewportToolOverlay.h b/new_editor/app/Features/Scene/SceneViewportToolOverlay.h index f055d8bc..fc076097 100644 --- a/new_editor/app/Features/Scene/SceneViewportToolOverlay.h +++ b/new_editor/app/Features/Scene/SceneViewportToolOverlay.h @@ -1,6 +1,7 @@ #pragma once #include "Scene/SceneToolState.h" +#include "Host/HostFwd.h" #include #include @@ -9,12 +10,6 @@ #include #include -namespace XCEngine::UI::Editor::Host { - -class NativeRenderer; - -} // namespace XCEngine::UI::Editor::Host - namespace XCEngine::UI::Editor::App { inline constexpr std::size_t kSceneViewportToolOverlayInvalidIndex = @@ -41,8 +36,8 @@ class SceneViewportToolOverlay { public: bool Initialize( const std::filesystem::path& repoRoot, - Host::NativeRenderer& renderer); - void Shutdown(Host::NativeRenderer& renderer); + Host::TextureHost& renderer); + void Shutdown(Host::TextureHost& renderer); void ResetFrame(); void BuildFrame( diff --git a/new_editor/app/Features/Shared/GridItemDragDrop.h b/new_editor/app/Features/Shared/GridItemDragDrop.h deleted file mode 100644 index 3304c9e5..00000000 --- a/new_editor/app/Features/Shared/GridItemDragDrop.h +++ /dev/null @@ -1,157 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -namespace XCEngine::UI::Editor::App::GridItemDragDrop { - -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIPoint; -using ::XCEngine::UI::UIPointerButton; - -inline constexpr float kDefaultDragThreshold = 4.0f; - -struct State { - std::string armedItemId = {}; - std::string draggedItemId = {}; - std::string dropTargetItemId = {}; - UIPoint pressPosition = {}; - bool armed = false; - bool dragging = false; - bool validDropTarget = false; - bool requestPointerCapture = false; - bool requestPointerRelease = false; -}; - -struct ProcessResult { - bool selectionForced = false; - bool dropCommitted = false; - std::string draggedItemId = {}; - std::string dropTargetItemId = {}; -}; - -inline void ResetTransientRequests(State& state) { - state.requestPointerCapture = false; - state.requestPointerRelease = false; -} - -inline bool HasActivePointerCapture(const State& state) { - return state.dragging; -} - -inline float ComputeSquaredDistance(const UIPoint& lhs, const UIPoint& rhs) { - const float dx = lhs.x - rhs.x; - const float dy = lhs.y - rhs.y; - return dx * dx + dy * dy; -} - -template -ProcessResult ProcessInputEvents( - State& state, - const std::vector& inputEvents, - Callbacks& callbacks, - float dragThreshold = kDefaultDragThreshold) { - ProcessResult result = {}; - - for (const UIInputEvent& event : inputEvents) { - switch (event.type) { - case UIInputEventType::PointerButtonDown: - if (event.pointerButton == UIPointerButton::Left) { - state.armedItemId = callbacks.ResolveDraggableItem(event.position); - state.pressPosition = event.position; - state.armed = !state.armedItemId.empty(); - if (!state.armed) { - state.draggedItemId.clear(); - state.dropTargetItemId.clear(); - state.validDropTarget = false; - } - } - break; - - case UIInputEventType::PointerMove: - if (state.armed && - !state.dragging && - ComputeSquaredDistance(event.position, state.pressPosition) >= - dragThreshold * dragThreshold) { - state.dragging = !state.armedItemId.empty(); - state.draggedItemId = state.armedItemId; - state.dropTargetItemId.clear(); - state.validDropTarget = false; - if (state.dragging) { - state.requestPointerCapture = true; - if (!callbacks.IsItemSelected(state.draggedItemId)) { - result.selectionForced = - callbacks.SelectDraggedItem(state.draggedItemId); - } - } - } - - if (state.dragging) { - state.dropTargetItemId = - callbacks.ResolveDropTargetItem( - state.draggedItemId, - event.position); - state.validDropTarget = - !state.dropTargetItemId.empty() && - callbacks.CanDropOnItem( - state.draggedItemId, - state.dropTargetItemId); - } - break; - - case UIInputEventType::PointerButtonUp: - if (event.pointerButton != UIPointerButton::Left) { - break; - } - - if (state.dragging) { - if (state.validDropTarget) { - result.draggedItemId = state.draggedItemId; - result.dropTargetItemId = state.dropTargetItemId; - result.dropCommitted = - callbacks.CommitDropOnItem( - state.draggedItemId, - state.dropTargetItemId); - } - - state.armed = false; - state.dragging = false; - state.armedItemId.clear(); - state.draggedItemId.clear(); - state.dropTargetItemId.clear(); - state.validDropTarget = false; - state.requestPointerRelease = true; - } else { - state.armed = false; - state.armedItemId.clear(); - } - break; - - case UIInputEventType::PointerLeave: - if (state.dragging) { - state.dropTargetItemId.clear(); - state.validDropTarget = false; - } - break; - - case UIInputEventType::FocusLost: - { - const bool requestPointerRelease = state.dragging; - state = {}; - state.requestPointerRelease = requestPointerRelease; - } - break; - - default: - break; - } - } - - return result; -} - -} // namespace XCEngine::UI::Editor::App::GridItemDragDrop diff --git a/new_editor/app/Features/Shared/TreeItemDragDrop.h b/new_editor/app/Features/Shared/TreeItemDragDrop.h deleted file mode 100644 index 97d819d5..00000000 --- a/new_editor/app/Features/Shared/TreeItemDragDrop.h +++ /dev/null @@ -1,324 +0,0 @@ -#pragma once - -#include - -#include - -#include -#include -#include - -namespace XCEngine::UI::Editor::App::TreeItemDragDrop { - -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIPoint; -using ::XCEngine::UI::UIRect; -using ::XCEngine::UI::UIPointerButton; -using Widgets::HitTestUIEditorTreeView; -using Widgets::UIEditorTreeViewHitTarget; -using Widgets::UIEditorTreeViewHitTargetKind; -using Widgets::UIEditorTreeViewInvalidIndex; - -inline constexpr float kDefaultDragThreshold = 4.0f; - -struct State { - std::string armedItemId = {}; - std::string draggedItemId = {}; - std::string dropTargetItemId = {}; - UIPoint pressPosition = {}; - bool armed = false; - bool dragging = false; - bool dropToRoot = false; - bool validDropTarget = false; - bool requestPointerCapture = false; - bool requestPointerRelease = false; -}; - -struct ProcessResult { - bool selectionForced = false; - bool dropCommitted = false; - bool droppedToRoot = false; - std::string draggedItemId = {}; - std::string dropTargetItemId = {}; -}; - -inline void ResetTransientRequests(State& state) { - state.requestPointerCapture = false; - state.requestPointerRelease = false; -} - -inline bool HasActivePointerCapture(const State& state) { - return state.dragging; -} - -inline 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; -} - -inline float ComputeSquaredDistance(const UIPoint& lhs, const UIPoint& rhs) { - const float dx = lhs.x - rhs.x; - const float dy = lhs.y - rhs.y; - return dx * dx + dy * dy; -} - -inline const Widgets::UIEditorTreeViewItem* ResolveHitItem( - const Widgets::UIEditorTreeViewLayout& layout, - const std::vector& items, - const UIPoint& point, - UIEditorTreeViewHitTarget* hitTargetOutput = nullptr) { - const UIEditorTreeViewHitTarget hitTarget = HitTestUIEditorTreeView(layout, point); - if (hitTargetOutput != nullptr) { - *hitTargetOutput = hitTarget; - } - - if (hitTarget.itemIndex >= items.size()) { - return nullptr; - } - - return &items[hitTarget.itemIndex]; -} - -inline std::size_t FindVisibleIndexForItemId( - const Widgets::UIEditorTreeViewLayout& layout, - const std::vector& items, - std::string_view itemId) { - for (std::size_t visibleIndex = 0u; visibleIndex < layout.visibleItemIndices.size(); ++visibleIndex) { - const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex]; - if (itemIndex < items.size() && items[itemIndex].itemId == itemId) { - return visibleIndex; - } - } - - return UIEditorTreeViewInvalidIndex; -} - -inline std::vector BuildInteractionInputEvents( - const State& state, - const Widgets::UIEditorTreeViewLayout& layout, - const std::vector& items, - const std::vector& rawEvents, - float dragThreshold = kDefaultDragThreshold) { - struct PreviewState { - std::string armedItemId = {}; - UIPoint pressPosition = {}; - bool armed = false; - bool dragging = false; - }; - - PreviewState preview = {}; - preview.armed = state.armed; - preview.armedItemId = state.armedItemId; - preview.pressPosition = state.pressPosition; - preview.dragging = state.dragging; - - std::vector filteredEvents = {}; - filteredEvents.reserve(rawEvents.size()); - for (const UIInputEvent& event : rawEvents) { - bool suppress = false; - - switch (event.type) { - case UIInputEventType::PointerButtonDown: - if (event.pointerButton == UIPointerButton::Left) { - UIEditorTreeViewHitTarget hitTarget = {}; - const Widgets::UIEditorTreeViewItem* hitItem = - ResolveHitItem(layout, items, event.position, &hitTarget); - if (hitItem != nullptr && hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { - preview.armed = true; - preview.armedItemId = hitItem->itemId; - preview.pressPosition = event.position; - } else { - preview.armed = false; - preview.armedItemId.clear(); - } - } - if (preview.dragging) { - suppress = true; - } - break; - - case UIInputEventType::PointerMove: - if (preview.dragging) { - suppress = true; - break; - } - - if (preview.armed && - ComputeSquaredDistance(event.position, preview.pressPosition) >= - dragThreshold * dragThreshold) { - preview.dragging = true; - suppress = true; - } - break; - - case UIInputEventType::PointerButtonUp: - if (event.pointerButton == UIPointerButton::Left) { - if (preview.dragging) { - suppress = true; - preview.dragging = false; - } - preview.armed = false; - preview.armedItemId.clear(); - } else if (preview.dragging) { - suppress = true; - } - break; - - case UIInputEventType::PointerLeave: - if (preview.dragging) { - suppress = true; - } - break; - - case UIInputEventType::FocusLost: - preview.armed = false; - preview.dragging = false; - preview.armedItemId.clear(); - break; - - default: - if (preview.dragging && - (event.type == UIInputEventType::PointerWheel || - event.type == UIInputEventType::PointerEnter)) { - suppress = true; - } - break; - } - - if (!suppress) { - filteredEvents.push_back(event); - } - } - - return filteredEvents; -} - -template -ProcessResult ProcessInputEvents( - State& state, - const Widgets::UIEditorTreeViewLayout& layout, - const std::vector& items, - const std::vector& inputEvents, - const UIRect& bounds, - Callbacks& callbacks, - float dragThreshold = kDefaultDragThreshold) { - ProcessResult result = {}; - - for (const UIInputEvent& event : inputEvents) { - switch (event.type) { - case UIInputEventType::PointerButtonDown: - if (event.pointerButton == UIPointerButton::Left) { - UIEditorTreeViewHitTarget hitTarget = {}; - const Widgets::UIEditorTreeViewItem* hitItem = - ResolveHitItem(layout, items, event.position, &hitTarget); - if (hitItem != nullptr && hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { - state.armed = true; - state.armedItemId = hitItem->itemId; - state.pressPosition = event.position; - } else { - state.armed = false; - state.armedItemId.clear(); - } - } - break; - - case UIInputEventType::PointerMove: - if (state.armed && - !state.dragging && - ComputeSquaredDistance(event.position, state.pressPosition) >= - dragThreshold * dragThreshold) { - state.dragging = !state.armedItemId.empty(); - state.draggedItemId = state.armedItemId; - state.dropTargetItemId.clear(); - state.dropToRoot = false; - state.validDropTarget = false; - if (state.dragging) { - state.requestPointerCapture = true; - if (!callbacks.IsItemSelected(state.draggedItemId)) { - result.selectionForced = callbacks.SelectDraggedItem(state.draggedItemId); - } - } - } - - if (state.dragging) { - UIEditorTreeViewHitTarget hitTarget = {}; - const Widgets::UIEditorTreeViewItem* hitItem = - ResolveHitItem(layout, items, event.position, &hitTarget); - - state.dropTargetItemId.clear(); - state.dropToRoot = false; - state.validDropTarget = false; - - if (hitItem != nullptr && - (hitTarget.kind == UIEditorTreeViewHitTargetKind::Row || - hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure)) { - state.dropTargetItemId = hitItem->itemId; - state.validDropTarget = - callbacks.CanDropOnItem(state.draggedItemId, state.dropTargetItemId); - } else if (ContainsPoint(bounds, event.position)) { - state.dropToRoot = true; - state.validDropTarget = callbacks.CanDropToRoot(state.draggedItemId); - } - } - break; - - case UIInputEventType::PointerButtonUp: - if (event.pointerButton != UIPointerButton::Left) { - break; - } - - if (state.dragging) { - if (state.validDropTarget) { - result.draggedItemId = state.draggedItemId; - result.dropTargetItemId = state.dropTargetItemId; - result.droppedToRoot = state.dropToRoot; - result.dropCommitted = - state.dropToRoot - ? callbacks.CommitDropToRoot(state.draggedItemId) - : callbacks.CommitDropOnItem( - state.draggedItemId, - state.dropTargetItemId); - } - - state.armed = false; - state.dragging = false; - state.armedItemId.clear(); - state.draggedItemId.clear(); - state.dropTargetItemId.clear(); - state.dropToRoot = false; - state.validDropTarget = false; - state.requestPointerRelease = true; - } else { - state.armed = false; - state.armedItemId.clear(); - } - break; - - case UIInputEventType::PointerLeave: - if (state.dragging) { - state.dropTargetItemId.clear(); - state.dropToRoot = false; - state.validDropTarget = false; - } - break; - - case UIInputEventType::FocusLost: - { - const bool requestPointerRelease = state.dragging; - state = {}; - state.requestPointerRelease = requestPointerRelease; - } - break; - - default: - break; - } - } - - return result; -} - -} // namespace XCEngine::UI::Editor::App::TreeItemDragDrop diff --git a/new_editor/app/Host/HostFwd.h b/new_editor/app/Host/HostFwd.h new file mode 100644 index 00000000..75975fce --- /dev/null +++ b/new_editor/app/Host/HostFwd.h @@ -0,0 +1,12 @@ +#pragma once + +namespace XCEngine::UI::Editor::Host { + +class ShaderResourceDescriptorAllocator; +class D3D12ShaderResourceDescriptorAllocator; +class D3D12WindowRenderer; +class NativeRenderer; +class TextureHost; +class ViewportRenderHost; + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/ShaderResourceDescriptorAllocator.h b/new_editor/app/Host/ShaderResourceDescriptorAllocator.h new file mode 100644 index 00000000..035520c1 --- /dev/null +++ b/new_editor/app/Host/ShaderResourceDescriptorAllocator.h @@ -0,0 +1,31 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include +#include + +#include + +namespace XCEngine::UI::Editor::Host { + +class ShaderResourceDescriptorAllocator { +public: + virtual ~ShaderResourceDescriptorAllocator() = default; + + virtual bool Initialize(::XCEngine::RHI::RHIDevice& device, UINT descriptorCount = 64u) = 0; + virtual void Shutdown() = 0; + + [[nodiscard]] virtual bool IsInitialized() const = 0; + virtual bool CreateTextureDescriptor( + ::XCEngine::RHI::RHITexture* texture, + D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle, + D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) = 0; + virtual void Free( + D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle, + D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle) = 0; +}; + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/TextureHost.h b/new_editor/app/Host/TextureHost.h new file mode 100644 index 00000000..344c96d7 --- /dev/null +++ b/new_editor/app/Host/TextureHost.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::Host { + +class TextureHost { +public: + virtual ~TextureHost() = default; + + virtual bool LoadTextureFromFile( + const std::filesystem::path& path, + ::XCEngine::UI::UITextureHandle& outTexture, + std::string& outError) = 0; + virtual bool LoadTextureFromMemory( + const std::uint8_t* data, + std::size_t size, + ::XCEngine::UI::UITextureHandle& outTexture, + std::string& outError) = 0; + virtual void ReleaseTexture(::XCEngine::UI::UITextureHandle& texture) = 0; +}; + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Host/ViewportRenderHost.h b/new_editor/app/Host/ViewportRenderHost.h new file mode 100644 index 00000000..3c9854d1 --- /dev/null +++ b/new_editor/app/Host/ViewportRenderHost.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace XCEngine::UI::Editor::Host { + +class ViewportRenderHost { +public: + virtual ~ViewportRenderHost() = default; + + [[nodiscard]] virtual ::XCEngine::RHI::RHIDevice* GetRHIDevice() const = 0; +}; + +} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Internal/EmbeddedPngLoader.cpp b/new_editor/app/Internal/EmbeddedPngLoader.cpp index f3f58f90..712ed7a4 100644 --- a/new_editor/app/Internal/EmbeddedPngLoader.cpp +++ b/new_editor/app/Internal/EmbeddedPngLoader.cpp @@ -1,5 +1,7 @@ #include "Internal/EmbeddedPngLoader.h" +#include "Host/TextureHost.h" + namespace XCEngine::UI::Editor::App::Internal { bool LoadEmbeddedPngBytes( @@ -47,7 +49,7 @@ bool LoadEmbeddedPngBytes( } bool LoadEmbeddedPngTexture( - Host::NativeRenderer& renderer, + Host::TextureHost& renderer, UINT resourceId, ::XCEngine::UI::UITextureHandle& outTexture, std::string& outError) { diff --git a/new_editor/app/Internal/EmbeddedPngLoader.h b/new_editor/app/Internal/EmbeddedPngLoader.h index ea1f5ab9..e6bb5549 100644 --- a/new_editor/app/Internal/EmbeddedPngLoader.h +++ b/new_editor/app/Internal/EmbeddedPngLoader.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "Host/HostFwd.h" #include @@ -8,6 +8,10 @@ #include #include +#ifndef NOMINMAX +#define NOMINMAX +#endif + #include namespace XCEngine::UI::Editor::App::Internal { @@ -19,7 +23,7 @@ bool LoadEmbeddedPngBytes( std::string& outError); bool LoadEmbeddedPngTexture( - Host::NativeRenderer& renderer, + Host::TextureHost& renderer, UINT resourceId, ::XCEngine::UI::UITextureHandle& outTexture, std::string& outError); diff --git a/new_editor/app/Legacy/Viewport/LegacySceneViewportGizmoSupport.cpp b/new_editor/app/Legacy/Viewport/LegacySceneViewportGizmoSupport.cpp index 2210a1c1..348dc84f 100644 --- a/new_editor/app/Legacy/Viewport/LegacySceneViewportGizmoSupport.cpp +++ b/new_editor/app/Legacy/Viewport/LegacySceneViewportGizmoSupport.cpp @@ -36,6 +36,7 @@ constexpr float kSceneViewportMoveArrowHalfWidthPixels = 7.0f; using ::XCEngine::Components::GameObject; using ::XCEngine::Components::TransformComponent; using ::XCEngine::Math::Color; +using ::XCEngine::Math::Matrix4x4; using ::XCEngine::Math::Plane; using ::XCEngine::Math::Quaternion; using ::XCEngine::Math::Ray; diff --git a/new_editor/app/Platform/Win32/EditorWindow.h b/new_editor/app/Platform/Win32/EditorWindow.h index f9cb62ad..a46ddac1 100644 --- a/new_editor/app/Platform/Win32/EditorWindow.h +++ b/new_editor/app/Platform/Win32/EditorWindow.h @@ -4,21 +4,61 @@ #define NOMINMAX #endif -#include "Platform/Win32/EditorWindowState.h" - -#include +#include "Platform/Win32/EditorWindowPointerCapture.h" +#include "Platform/Win32/EditorWindowTransferRequests.h" #include +#include #include -#include +#include #include #include #include +namespace XCEngine::UI { + +class UIDrawList; + +struct UIPoint; +struct UIRect; +struct UIInputEvent; +struct UIInputModifiers; + +enum class UIPointerButton : std::uint8_t; +enum class UIInputEventType : std::uint8_t; + +} // namespace XCEngine::UI + +namespace XCEngine::UI::Editor { + +class UIEditorWorkspaceController; + +struct UIEditorDockHostInteractionState; +struct UIEditorShellInteractionFrame; +struct UIEditorShellInteractionState; +struct UIEditorShellInteractionResult; + +namespace Widgets { +struct UIEditorDockHostDropPreviewState; +} + +} // namespace XCEngine::UI::Editor + +namespace XCEngine::UI::Editor::Host { + +enum class BorderlessWindowChromeHitTarget : std::uint8_t; +enum class BorderlessWindowResizeEdge : std::uint8_t; + +struct BorderlessWindowChromeLayout; + +} // namespace XCEngine::UI::Editor::Host + namespace XCEngine::UI::Editor::App { class EditorContext; +class EditorShellRuntime; +struct EditorWindowState; namespace Internal { class EditorWindowHostRuntime; @@ -40,6 +80,7 @@ public: std::wstring title, bool primary, UIEditorWorkspaceController workspaceController); + ~EditorWindow(); EditorWindow(const EditorWindow&) = delete; EditorWindow& operator=(const EditorWindow&) = delete; @@ -215,11 +256,7 @@ private: void UpdateCachedTitleText(); static bool IsVerboseRuntimeTraceEnabled(); - EditorWindowWindowState m_window = {}; - EditorWindowRenderState m_render = {}; - EditorWindowInputState m_input = {}; - EditorWindowCompositionState m_composition = {}; - EditorWindowChromeRuntimeState m_chrome = {}; + std::unique_ptr m_state = {}; }; } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp b/new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp index 5b61cd62..5d4896cb 100644 --- a/new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp @@ -1,4 +1,5 @@ #include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowInternalState.h" namespace XCEngine::UI::Editor::App { bool EditorWindow::IsBorderlessWindowEnabled() const { @@ -6,7 +7,7 @@ bool EditorWindow::IsBorderlessWindowEnabled() const { } bool EditorWindow::IsBorderlessWindowMaximized() const { - return m_chrome.runtime.IsBorderlessWindowMaximized(); + return m_state->chrome.runtime.IsBorderlessWindowMaximized(); } bool EditorWindow::HandleBorderlessWindowSystemCommand( @@ -22,7 +23,7 @@ bool EditorWindow::HandleBorderlessWindowSystemCommand( ToggleBorderlessWindowMaximizeRestore(editorContext, globalTabDragActive); return true; case SC_RESTORE: - if (!IsIconic(m_window.hwnd)) { + if (!IsIconic(m_state->window.hwnd)) { ToggleBorderlessWindowMaximizeRestore(editorContext, globalTabDragActive); return true; } @@ -33,29 +34,31 @@ bool EditorWindow::HandleBorderlessWindowSystemCommand( } bool EditorWindow::HandleBorderlessWindowGetMinMaxInfo(LPARAM lParam) const { - return Host::HandleBorderlessWindowGetMinMaxInfo(m_window.hwnd, lParam); + return Host::HandleBorderlessWindowGetMinMaxInfo(m_state->window.hwnd, lParam); } LRESULT EditorWindow::HandleBorderlessWindowNcCalcSize(WPARAM wParam, LPARAM lParam) const { return Host::HandleBorderlessWindowNcCalcSize( - m_window.hwnd, + m_state->window.hwnd, wParam, lParam, - m_chrome.runtime.GetWindowDpi()); + m_state->chrome.runtime.GetWindowDpi()); } bool EditorWindow::QueryCurrentWindowRect(RECT& outRect) const { outRect = {}; - return m_window.hwnd != nullptr && GetWindowRect(m_window.hwnd, &outRect) != FALSE; + return m_state->window.hwnd != nullptr && + GetWindowRect(m_state->window.hwnd, &outRect) != FALSE; } bool EditorWindow::QueryBorderlessWindowWorkAreaRect(RECT& outRect) const { outRect = {}; - if (m_window.hwnd == nullptr) { + if (m_state->window.hwnd == nullptr) { return false; } - const HMONITOR monitor = MonitorFromWindow(m_window.hwnd, MONITOR_DEFAULTTONEAREST); + const HMONITOR monitor = + MonitorFromWindow(m_state->window.hwnd, MONITOR_DEFAULTTONEAREST); if (monitor == nullptr) { return false; } @@ -74,7 +77,7 @@ bool EditorWindow::ApplyPredictedWindowRectTransition( EditorContext& editorContext, bool globalTabDragActive, const RECT& targetRect) { - if (m_window.hwnd == nullptr) { + if (m_state->window.hwnd == nullptr) { return false; } @@ -84,13 +87,13 @@ bool EditorWindow::ApplyPredictedWindowRectTransition( return false; } - m_chrome.runtime.SetPredictedClientPixelSize( + m_state->chrome.runtime.SetPredictedClientPixelSize( static_cast(width), static_cast(height)); ApplyWindowResize(static_cast(width), static_cast(height)); (void)RenderFrame(editorContext, globalTabDragActive); SetWindowPos( - m_window.hwnd, + m_state->window.hwnd, nullptr, targetRect.left, targetRect.top, @@ -104,7 +107,7 @@ bool EditorWindow::ApplyPredictedWindowRectTransition( void EditorWindow::ToggleBorderlessWindowMaximizeRestore( EditorContext& editorContext, bool globalTabDragActive) { - if (m_window.hwnd == nullptr) { + if (m_state->window.hwnd == nullptr) { return; } @@ -116,8 +119,8 @@ void EditorWindow::ToggleBorderlessWindowMaximizeRestore( return; } - m_chrome.runtime.SetBorderlessWindowRestoreRect(currentRect); - m_chrome.runtime.SetBorderlessWindowMaximized(true); + m_state->chrome.runtime.SetBorderlessWindowRestoreRect(currentRect); + m_state->chrome.runtime.SetBorderlessWindowMaximized(true); ApplyPredictedWindowRectTransition( editorContext, globalTabDragActive, @@ -126,11 +129,11 @@ void EditorWindow::ToggleBorderlessWindowMaximizeRestore( } RECT restoreRect = {}; - if (!m_chrome.runtime.TryGetBorderlessWindowRestoreRect(restoreRect)) { + if (!m_state->chrome.runtime.TryGetBorderlessWindowRestoreRect(restoreRect)) { return; } - m_chrome.runtime.SetBorderlessWindowMaximized(false); + m_state->chrome.runtime.SetBorderlessWindowMaximized(false); ApplyPredictedWindowRectTransition( editorContext, globalTabDragActive, diff --git a/new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp b/new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp index 9236070c..169f4a32 100644 --- a/new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp @@ -1,4 +1,5 @@ #include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowInternalState.h" #include @@ -13,11 +14,11 @@ using ::XCEngine::UI::UIRect; bool EditorWindow::UpdateBorderlessWindowResizeHover(LPARAM lParam) { const Host::BorderlessWindowResizeEdge hoveredEdge = HitTestBorderlessWindowResizeEdge(lParam); - if (m_chrome.runtime.GetHoveredBorderlessResizeEdge() == hoveredEdge) { + if (m_state->chrome.runtime.GetHoveredBorderlessResizeEdge() == hoveredEdge) { return false; } - m_chrome.runtime.SetHoveredBorderlessResizeEdge(hoveredEdge); + m_state->chrome.runtime.SetHoveredBorderlessResizeEdge(hoveredEdge); ApplyBorderlessWindowResizeCursorHoverPriority(); return true; } @@ -25,7 +26,8 @@ bool EditorWindow::UpdateBorderlessWindowResizeHover(LPARAM lParam) { bool EditorWindow::HandleBorderlessWindowResizeButtonDown(LPARAM lParam) { const Host::BorderlessWindowResizeEdge edge = HitTestBorderlessWindowResizeEdge(lParam); - if (edge == Host::BorderlessWindowResizeEdge::None || m_window.hwnd == nullptr) { + if (edge == Host::BorderlessWindowResizeEdge::None || + m_state->window.hwnd == nullptr) { return false; } @@ -35,22 +37,22 @@ bool EditorWindow::HandleBorderlessWindowResizeButtonDown(LPARAM lParam) { } RECT windowRect = {}; - if (!GetWindowRect(m_window.hwnd, &windowRect)) { + if (!GetWindowRect(m_state->window.hwnd, &windowRect)) { return false; } - m_chrome.runtime.BeginBorderlessResize(edge, screenPoint, windowRect); + m_state->chrome.runtime.BeginBorderlessResize(edge, screenPoint, windowRect); AcquirePointerCapture(EditorWindowPointerCaptureOwner::BorderlessResize); InvalidateHostWindow(); return true; } bool EditorWindow::HandleBorderlessWindowResizeButtonUp() { - if (!m_chrome.runtime.IsBorderlessResizeActive()) { + if (!m_state->chrome.runtime.IsBorderlessResizeActive()) { return false; } - m_chrome.runtime.EndBorderlessResize(); + m_state->chrome.runtime.EndBorderlessResize(); ReleasePointerCapture(EditorWindowPointerCaptureOwner::BorderlessResize); InvalidateHostWindow(); return true; @@ -59,7 +61,8 @@ bool EditorWindow::HandleBorderlessWindowResizeButtonUp() { bool EditorWindow::HandleBorderlessWindowResizePointerMove( EditorContext& editorContext, bool globalTabDragActive) { - if (!m_chrome.runtime.IsBorderlessResizeActive() || m_window.hwnd == nullptr) { + if (!m_state->chrome.runtime.IsBorderlessResizeActive() || + m_state->window.hwnd == nullptr) { return false; } @@ -69,10 +72,10 @@ bool EditorWindow::HandleBorderlessWindowResizePointerMove( } RECT targetRect = Host::ComputeBorderlessWindowResizeRect( - m_chrome.runtime.GetBorderlessResizeInitialWindowRect(), - m_chrome.runtime.GetBorderlessResizeInitialScreenPoint(), + m_state->chrome.runtime.GetBorderlessResizeInitialWindowRect(), + m_state->chrome.runtime.GetBorderlessResizeInitialScreenPoint(), currentScreenPoint, - m_chrome.runtime.GetBorderlessResizeEdge(), + m_state->chrome.runtime.GetBorderlessResizeEdge(), 640, 360); const int width = targetRect.right - targetRect.left; @@ -81,14 +84,14 @@ bool EditorWindow::HandleBorderlessWindowResizePointerMove( return true; } - m_chrome.runtime.SetPredictedClientPixelSize( + m_state->chrome.runtime.SetPredictedClientPixelSize( static_cast(width), static_cast(height)); ApplyWindowResize(static_cast(width), static_cast(height)); (void)RenderFrame(editorContext, globalTabDragActive); SetWindowPos( - m_window.hwnd, + m_state->window.hwnd, nullptr, targetRect.left, targetRect.top, @@ -99,40 +102,44 @@ bool EditorWindow::HandleBorderlessWindowResizePointerMove( } void EditorWindow::ClearBorderlessWindowResizeState() { - if (m_chrome.runtime.IsBorderlessResizeActive()) { + if (m_state->chrome.runtime.IsBorderlessResizeActive()) { return; } - if (m_chrome.runtime.GetHoveredBorderlessResizeEdge() == + if (m_state->chrome.runtime.GetHoveredBorderlessResizeEdge() == Host::BorderlessWindowResizeEdge::None) { return; } - m_chrome.runtime.SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None); + m_state->chrome.runtime.SetHoveredBorderlessResizeEdge( + Host::BorderlessWindowResizeEdge::None); InvalidateHostWindow(); } void EditorWindow::ForceClearBorderlessWindowResizeState() { - if (m_chrome.runtime.GetHoveredBorderlessResizeEdge() == + if (m_state->chrome.runtime.GetHoveredBorderlessResizeEdge() == Host::BorderlessWindowResizeEdge::None && - !m_chrome.runtime.IsBorderlessResizeActive()) { + !m_state->chrome.runtime.IsBorderlessResizeActive()) { return; } - m_chrome.runtime.SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None); - m_chrome.runtime.EndBorderlessResize(); + m_state->chrome.runtime.SetHoveredBorderlessResizeEdge( + Host::BorderlessWindowResizeEdge::None); + m_state->chrome.runtime.EndBorderlessResize(); ReleasePointerCapture(EditorWindowPointerCaptureOwner::BorderlessResize); InvalidateHostWindow(); } Host::BorderlessWindowResizeEdge EditorWindow::HitTestBorderlessWindowResizeEdge( LPARAM lParam) const { - if (!IsBorderlessWindowEnabled() || m_window.hwnd == nullptr || IsBorderlessWindowMaximized()) { + if (!IsBorderlessWindowEnabled() || + m_state->window.hwnd == nullptr || + IsBorderlessWindowMaximized()) { return Host::BorderlessWindowResizeEdge::None; } RECT clientRect = {}; - if (!GetClientRect(m_window.hwnd, &clientRect)) { + if (!GetClientRect(m_state->window.hwnd, &clientRect)) { return Host::BorderlessWindowResizeEdge::None; } @@ -146,10 +153,10 @@ Host::BorderlessWindowResizeEdge EditorWindow::HitTestBorderlessWindowResizeEdge } void EditorWindow::ApplyBorderlessWindowResizeCursorHoverPriority() { - if (m_chrome.runtime.GetHoveredBorderlessResizeEdge() != + if (m_state->chrome.runtime.GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None || - m_chrome.runtime.IsBorderlessResizeActive()) { - m_chrome.chromeState.hoveredTarget = + m_state->chrome.runtime.IsBorderlessResizeActive()) { + m_state->chrome.chromeState.hoveredTarget = Host::BorderlessWindowChromeHitTarget::None; } } diff --git a/new_editor/app/Platform/Win32/EditorWindowFrame.cpp b/new_editor/app/Platform/Win32/EditorWindowFrame.cpp index 5cb22fb9..49fe1a1a 100644 --- a/new_editor/app/Platform/Win32/EditorWindowFrame.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowFrame.cpp @@ -1,5 +1,6 @@ #include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowConstants.h" +#include "Platform/Win32/EditorWindowInternalState.h" #include "Platform/Win32/EditorWindowRuntimeInternal.h" #include "Platform/Win32/EditorWindowStyle.h" #include "State/EditorContext.h" @@ -127,7 +128,7 @@ std::uint8_t ResolveExpectedShellCaptureButtons( EditorWindowFrameTransferRequests EditorWindow::RenderFrame( EditorContext& editorContext, bool globalTabDragActive) { - if (!m_render.ready || m_window.hwnd == nullptr) { + if (!m_state->render.ready || m_state->window.hwnd == nullptr) { return {}; } @@ -158,13 +159,13 @@ EditorWindowFrameTransferRequests EditorWindow::RenderFrame( AppendBorderlessWindowChrome(drawList, width); const Host::D3D12WindowRenderLoopPresentResult presentResult = - m_render.windowRenderLoop.Present(drawData); + m_state->render.windowRenderLoop.Present(drawData); if (!presentResult.warning.empty()) { LogRuntimeTrace("present", presentResult.warning); } - m_render.autoScreenshot.CaptureIfRequested( - m_render.renderer, + m_state->render.autoScreenshot.CaptureIfRequested( + m_state->render.renderer, drawData, pixelWidth, pixelHeight, @@ -175,15 +176,15 @@ EditorWindowFrameTransferRequests EditorWindow::RenderFrame( EditorWindowFrameTransferRequests EditorWindow::OnPaintMessage( EditorContext& editorContext, bool globalTabDragActive) { - if (!m_render.ready || m_window.hwnd == nullptr) { + if (!m_state->render.ready || m_state->window.hwnd == nullptr) { return {}; } PAINTSTRUCT paintStruct = {}; - BeginPaint(m_window.hwnd, &paintStruct); + BeginPaint(m_state->window.hwnd, &paintStruct); const EditorWindowFrameTransferRequests transferRequests = RenderFrame(editorContext, globalTabDragActive); - EndPaint(m_window.hwnd, &paintStruct); + EndPaint(m_state->window.hwnd, &paintStruct); return transferRequests; } @@ -210,40 +211,40 @@ EditorWindowFrameTransferRequests EditorWindow::RenderRuntimeFrame( const UIRect& workspaceBounds, UIDrawList& drawList) { SyncShellCapturedPointerButtonsFromSystemState(); - std::vector frameEvents = std::move(m_input.pendingEvents); - m_input.pendingEvents.clear(); + std::vector frameEvents = std::move(m_state->input.pendingEvents); + m_state->input.pendingEvents.clear(); if (!frameEvents.empty() && IsVerboseRuntimeTraceEnabled()) { LogRuntimeTrace( "input", DescribeInputEvents(frameEvents) + " | " + editorContext.DescribeWorkspaceState( - m_composition.workspaceController, - m_composition.shellRuntime.GetShellInteractionState())); + m_state->composition.workspaceController, + m_state->composition.shellRuntime.GetShellInteractionState())); } const Host::D3D12WindowRenderLoopFrameContext frameContext = - m_render.windowRenderLoop.BeginFrame(); + m_state->render.windowRenderLoop.BeginFrame(); if (!frameContext.warning.empty()) { LogRuntimeTrace("viewport", frameContext.warning); } - editorContext.AttachTextMeasurer(m_render.renderer); + editorContext.AttachTextMeasurer(m_state->render.renderer); const bool useDetachedTitleBarTabStrip = ShouldUseDetachedTitleBarTabStrip(); - m_composition.shellRuntime.Update( + m_state->composition.shellRuntime.Update( editorContext, - m_composition.workspaceController, + m_state->composition.workspaceController, workspaceBounds, frameEvents, BuildCaptureStatusText(), - m_window.primary + m_state->window.primary ? EditorShellVariant::Primary : EditorShellVariant::DetachedWindow, useDetachedTitleBarTabStrip, useDetachedTitleBarTabStrip ? kBorderlessTitleBarHeightDips : 0.0f); const UIEditorShellInteractionFrame& shellFrame = - m_composition.shellRuntime.GetShellFrame(); + m_state->composition.shellRuntime.GetShellFrame(); const UIEditorDockHostInteractionState& dockHostInteractionState = - m_composition.shellRuntime + m_state->composition.shellRuntime .GetShellInteractionState() .workspaceInteractionState .dockHostInteractionState; @@ -253,14 +254,15 @@ EditorWindowFrameTransferRequests EditorWindow::RenderRuntimeFrame( BuildShellTransferRequests(globalTabDragActive, dockHostInteractionState, shellFrame); ApplyHostCaptureRequests(shellFrame.result); - for (const WorkspaceTraceEntry& entry : m_composition.shellRuntime.GetTraceEntries()) { + for (const WorkspaceTraceEntry& entry : + m_state->composition.shellRuntime.GetTraceEntries()) { LogRuntimeTrace(entry.channel, entry.message); } ApplyHostedContentCaptureRequests(); ApplyCurrentCursor(); - m_composition.shellRuntime.Append(drawList); + m_state->composition.shellRuntime.Append(drawList); if (frameContext.canRenderViewports) { - m_composition.shellRuntime.RenderRequestedViewports(frameContext.renderContext); + m_state->composition.shellRuntime.RenderRequestedViewports(frameContext.renderContext); } return transferRequests; } @@ -301,7 +303,7 @@ void EditorWindow::LogFrameInteractionTrace( << " commandExecuted=" << (shellFrame.result.workspaceResult.dockHostResult.commandExecuted ? "true" : "false") << " active=" - << m_composition.workspaceController.GetWorkspace().activePanelId + << m_state->composition.workspaceController.GetWorkspace().activePanelId << " message=" << shellFrame.result.workspaceResult.dockHostResult.layoutResult.message; LogRuntimeTrace("frame", frameTrace.str()); @@ -339,33 +341,33 @@ EditorWindowFrameTransferRequests EditorWindow::BuildShellTransferRequests( } std::string EditorWindow::BuildCaptureStatusText() const { - if (m_render.autoScreenshot.HasPendingCapture()) { + if (m_state->render.autoScreenshot.HasPendingCapture()) { return "Shot pending..."; } - if (!m_render.autoScreenshot.GetLastCaptureError().empty()) { - return TruncateText(m_render.autoScreenshot.GetLastCaptureError(), 38u); + if (!m_state->render.autoScreenshot.GetLastCaptureError().empty()) { + return TruncateText(m_state->render.autoScreenshot.GetLastCaptureError(), 38u); } - if (!m_render.autoScreenshot.GetLastCaptureSummary().empty()) { - return TruncateText(m_render.autoScreenshot.GetLastCaptureSummary(), 38u); + if (!m_state->render.autoScreenshot.GetLastCaptureSummary().empty()) { + return TruncateText(m_state->render.autoScreenshot.GetLastCaptureSummary(), 38u); } return {}; } void EditorWindow::SyncShellCapturedPointerButtonsFromSystemState() { - m_input.modifierTracker.SyncFromSystemState(); + m_state->input.modifierTracker.SyncFromSystemState(); const std::uint8_t expectedButtons = - ResolveExpectedShellCaptureButtons(m_composition.shellRuntime); + ResolveExpectedShellCaptureButtons(m_state->composition.shellRuntime); if (expectedButtons == 0u || - HasPendingPointerStateReconciliationEvent(m_input.pendingEvents)) { + HasPendingPointerStateReconciliationEvent(m_state->input.pendingEvents)) { return; } const UIInputModifiers modifiers = - m_input.modifierTracker.GetCurrentModifiers(); + m_state->input.modifierTracker.GetCurrentModifiers(); if ((ButtonMaskFromModifiers(modifiers) & expectedButtons) == expectedButtons) { return; } @@ -383,12 +385,12 @@ void EditorWindow::ApplyHostCaptureRequests(const UIEditorShellInteractionResult } void EditorWindow::ApplyHostedContentCaptureRequests() { - if (m_composition.shellRuntime.WantsHostPointerCapture()) { + if (m_state->composition.shellRuntime.WantsHostPointerCapture()) { AcquirePointerCapture(EditorWindowPointerCaptureOwner::HostedContent); } - if (m_composition.shellRuntime.WantsHostPointerRelease() && - !m_composition.shellRuntime.HasShellInteractiveCapture()) { + if (m_state->composition.shellRuntime.WantsHostPointerRelease() && + !m_state->composition.shellRuntime.HasShellInteractiveCapture()) { ReleasePointerCapture(EditorWindowPointerCaptureOwner::HostedContent); } } diff --git a/new_editor/app/Platform/Win32/EditorWindowInput.cpp b/new_editor/app/Platform/Win32/EditorWindowInput.cpp index 7cae9f70..3072f6d3 100644 --- a/new_editor/app/Platform/Win32/EditorWindowInput.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowInput.cpp @@ -1,4 +1,5 @@ #include "Platform/Win32/EditorWindow.h" +#include "Platform/Win32/EditorWindowInternalState.h" #include #include @@ -128,59 +129,59 @@ bool EditorWindow::ApplyCurrentCursor() const { bool EditorWindow::HasInteractiveCaptureState() const { - return m_composition.shellRuntime.HasInteractiveCapture() || - m_chrome.runtime.IsBorderlessWindowDragRestoreArmed() || - m_chrome.runtime.IsBorderlessResizeActive() || - m_input.pointerCaptureOwner != EditorWindowPointerCaptureOwner::None; + return m_state->composition.shellRuntime.HasInteractiveCapture() || + m_state->chrome.runtime.IsBorderlessWindowDragRestoreArmed() || + m_state->chrome.runtime.IsBorderlessResizeActive() || + m_state->input.pointerCaptureOwner != EditorWindowPointerCaptureOwner::None; } EditorWindowPointerCaptureOwner EditorWindow::GetPointerCaptureOwner() const { - return m_input.pointerCaptureOwner; + return m_state->input.pointerCaptureOwner; } bool EditorWindow::OwnsPointerCapture(EditorWindowPointerCaptureOwner owner) const { - return m_input.pointerCaptureOwner == owner; + return m_state->input.pointerCaptureOwner == owner; } void EditorWindow::AcquirePointerCapture(EditorWindowPointerCaptureOwner owner) { if (owner == EditorWindowPointerCaptureOwner::None || - m_window.hwnd == nullptr || - !IsWindow(m_window.hwnd)) { + m_state->window.hwnd == nullptr || + !IsWindow(m_state->window.hwnd)) { return; } - m_input.pointerCaptureOwner = owner; - if (GetCapture() != m_window.hwnd) { - SetCapture(m_window.hwnd); + m_state->input.pointerCaptureOwner = owner; + if (GetCapture() != m_state->window.hwnd) { + SetCapture(m_state->window.hwnd); } } void EditorWindow::ReleasePointerCapture(EditorWindowPointerCaptureOwner owner) { - if (m_input.pointerCaptureOwner != owner) { + if (m_state->input.pointerCaptureOwner != owner) { return; } - m_input.pointerCaptureOwner = EditorWindowPointerCaptureOwner::None; - if (m_window.hwnd != nullptr && GetCapture() == m_window.hwnd) { + m_state->input.pointerCaptureOwner = EditorWindowPointerCaptureOwner::None; + if (m_state->window.hwnd != nullptr && GetCapture() == m_state->window.hwnd) { ReleaseCapture(); } } void EditorWindow::ForceReleasePointerCapture() { - m_input.pointerCaptureOwner = EditorWindowPointerCaptureOwner::None; - if (m_window.hwnd != nullptr && GetCapture() == m_window.hwnd) { + m_state->input.pointerCaptureOwner = EditorWindowPointerCaptureOwner::None; + if (m_state->window.hwnd != nullptr && GetCapture() == m_state->window.hwnd) { ReleaseCapture(); } } void EditorWindow::ClearPointerCaptureOwner() { - m_input.pointerCaptureOwner = EditorWindowPointerCaptureOwner::None; + m_state->input.pointerCaptureOwner = EditorWindowPointerCaptureOwner::None; } void EditorWindow::TryStartImmediateShellPointerCapture(LPARAM lParam) { - if (m_window.hwnd == nullptr || - !IsWindow(m_window.hwnd) || - GetCapture() == m_window.hwnd) { + if (m_state->window.hwnd == nullptr || + !IsWindow(m_state->window.hwnd) || + GetCapture() == m_state->window.hwnd) { return; } @@ -188,7 +189,7 @@ void EditorWindow::TryStartImmediateShellPointerCapture(LPARAM lParam) { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); if (!ShouldStartImmediateUIEditorShellPointerCapture( - m_composition.shellRuntime.GetShellFrame(), + m_state->composition.shellRuntime.GetShellFrame(), clientPoint)) { return; } @@ -207,16 +208,16 @@ void EditorWindow::QueuePointerEvent( event.position = ConvertClientPixelsToDips( GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); - event.modifiers = m_input.modifierTracker.ApplyPointerMessage( + event.modifiers = m_state->input.modifierTracker.ApplyPointerMessage( type, button, static_cast(wParam)); - m_input.pendingEvents.push_back(event); + m_state->input.pendingEvents.push_back(event); } void EditorWindow::QueueSyntheticPointerStateSyncEvent( const ::XCEngine::UI::UIInputModifiers& modifiers) { - if (m_window.hwnd == nullptr || !IsWindow(m_window.hwnd)) { + if (m_state->window.hwnd == nullptr || !IsWindow(m_state->window.hwnd)) { return; } @@ -224,7 +225,7 @@ void EditorWindow::QueueSyntheticPointerStateSyncEvent( if (!GetCursorPos(&screenPoint)) { return; } - if (!ScreenToClient(m_window.hwnd, &screenPoint)) { + if (!ScreenToClient(m_state->window.hwnd, &screenPoint)) { return; } @@ -232,24 +233,24 @@ void EditorWindow::QueueSyntheticPointerStateSyncEvent( event.type = UIInputEventType::PointerMove; event.position = ConvertClientPixelsToDips(screenPoint.x, screenPoint.y); event.modifiers = modifiers; - m_input.pendingEvents.push_back(event); + m_state->input.pendingEvents.push_back(event); } void EditorWindow::QueuePointerLeaveEvent() { UIInputEvent event = {}; event.type = UIInputEventType::PointerLeave; - if (m_window.hwnd != nullptr) { + if (m_state->window.hwnd != nullptr) { POINT clientPoint = {}; GetCursorPos(&clientPoint); - ScreenToClient(m_window.hwnd, &clientPoint); + ScreenToClient(m_state->window.hwnd, &clientPoint); event.position = ConvertClientPixelsToDips(clientPoint.x, clientPoint.y); } - event.modifiers = m_input.modifierTracker.GetCurrentModifiers(); - m_input.pendingEvents.push_back(event); + event.modifiers = m_state->input.modifierTracker.GetCurrentModifiers(); + m_state->input.pendingEvents.push_back(event); } void EditorWindow::QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam) { - if (m_window.hwnd == nullptr) { + if (m_state->window.hwnd == nullptr) { return; } @@ -257,56 +258,56 @@ void EditorWindow::QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARA GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(m_window.hwnd, &screenPoint); + ScreenToClient(m_state->window.hwnd, &screenPoint); UIInputEvent event = {}; event.type = UIInputEventType::PointerWheel; event.position = ConvertClientPixelsToDips(screenPoint.x, screenPoint.y); event.wheelDelta = static_cast(wheelDelta); - event.modifiers = m_input.modifierTracker.ApplyPointerMessage( + event.modifiers = m_state->input.modifierTracker.ApplyPointerMessage( UIInputEventType::PointerWheel, UIPointerButton::None, static_cast(wParam)); - m_input.pendingEvents.push_back(event); + m_state->input.pendingEvents.push_back(event); } void EditorWindow::QueueKeyEvent(UIInputEventType type, WPARAM wParam, LPARAM lParam) { UIInputEvent event = {}; event.type = type; event.keyCode = MapVirtualKeyToUIKeyCode(wParam); - event.modifiers = m_input.modifierTracker.ApplyKeyMessage(type, wParam, lParam); + event.modifiers = m_state->input.modifierTracker.ApplyKeyMessage(type, wParam, lParam); event.repeat = IsRepeatKeyMessage(lParam); - m_input.pendingEvents.push_back(event); + m_state->input.pendingEvents.push_back(event); } void EditorWindow::QueueCharacterEvent(WPARAM wParam, LPARAM) { UIInputEvent event = {}; event.type = UIInputEventType::Character; event.character = static_cast(wParam); - event.modifiers = m_input.modifierTracker.GetCurrentModifiers(); - m_input.pendingEvents.push_back(event); + event.modifiers = m_state->input.modifierTracker.GetCurrentModifiers(); + m_state->input.pendingEvents.push_back(event); } void EditorWindow::QueueWindowFocusEvent(UIInputEventType type) { UIInputEvent event = {}; event.type = type; - m_input.pendingEvents.push_back(event); + m_state->input.pendingEvents.push_back(event); } void EditorWindow::SyncInputModifiersFromSystemState() { - m_input.modifierTracker.SyncFromSystemState(); + m_state->input.modifierTracker.SyncFromSystemState(); } void EditorWindow::ResetInputModifiers() { - m_input.modifierTracker.Reset(); + m_state->input.modifierTracker.Reset(); } void EditorWindow::RequestManualScreenshot() { - m_render.autoScreenshot.RequestCapture("manual_f12"); + m_state->render.autoScreenshot.RequestCapture("manual_f12"); } bool EditorWindow::IsPointerInsideClientArea() const { - if (m_window.hwnd == nullptr || !IsWindow(m_window.hwnd)) { + if (m_state->window.hwnd == nullptr || !IsWindow(m_state->window.hwnd)) { return false; } @@ -315,26 +316,26 @@ bool EditorWindow::IsPointerInsideClientArea() const { return false; } - if (!IsScreenPointOverWindow(m_window.hwnd, screenPoint)) { + if (!IsScreenPointOverWindow(m_state->window.hwnd, screenPoint)) { return false; } const LPARAM pointParam = MAKELPARAM( static_cast(screenPoint.x), static_cast(screenPoint.y)); - return SendMessageW(m_window.hwnd, WM_NCHITTEST, 0, pointParam) == HTCLIENT; + return SendMessageW(m_state->window.hwnd, WM_NCHITTEST, 0, pointParam) == HTCLIENT; } LPCWSTR EditorWindow::ResolveCurrentCursorResource() const { const Host::BorderlessWindowResizeEdge borderlessResizeEdge = - m_chrome.runtime.IsBorderlessResizeActive() - ? m_chrome.runtime.GetBorderlessResizeEdge() - : m_chrome.runtime.GetHoveredBorderlessResizeEdge(); + m_state->chrome.runtime.IsBorderlessResizeActive() + ? m_state->chrome.runtime.GetBorderlessResizeEdge() + : m_state->chrome.runtime.GetHoveredBorderlessResizeEdge(); if (borderlessResizeEdge != Host::BorderlessWindowResizeEdge::None) { return Host::ResolveBorderlessWindowResizeCursor(borderlessResizeEdge); } - switch (m_composition.shellRuntime.GetHostedContentCursorKind()) { + switch (m_state->composition.shellRuntime.GetHostedContentCursorKind()) { case ProjectPanel::CursorKind::ResizeEW: return IDC_SIZEWE; case ProjectPanel::CursorKind::Arrow: @@ -342,7 +343,7 @@ LPCWSTR EditorWindow::ResolveCurrentCursorResource() const { break; } - switch (m_composition.shellRuntime.GetDockCursorKind()) { + switch (m_state->composition.shellRuntime.GetDockCursorKind()) { case Widgets::UIEditorDockHostCursorKind::ResizeEW: return IDC_SIZEWE; case Widgets::UIEditorDockHostCursorKind::ResizeNS: diff --git a/new_editor/app/Platform/Win32/EditorWindowState.h b/new_editor/app/Platform/Win32/EditorWindowInternalState.h similarity index 77% rename from new_editor/app/Platform/Win32/EditorWindowState.h rename to new_editor/app/Platform/Win32/EditorWindowInternalState.h index 8e21b6f5..a85cbacf 100644 --- a/new_editor/app/Platform/Win32/EditorWindowState.h +++ b/new_editor/app/Platform/Win32/EditorWindowInternalState.h @@ -4,8 +4,8 @@ #define NOMINMAX #endif -#include "Platform/Win32/EditorWindowPointerCapture.h" #include "Composition/EditorShellRuntime.h" +#include "Platform/Win32/EditorWindowPointerCapture.h" #include #include @@ -21,7 +21,6 @@ #include -#include #include #include @@ -63,23 +62,12 @@ struct EditorWindowChromeRuntimeState { Host::HostRuntimeState runtime = {}; }; -struct EditorWindowPanelTransferRequest { - std::string nodeId = {}; - std::string panelId = {}; - POINT screenPoint = {}; - - bool IsValid() const { - return !nodeId.empty() && !panelId.empty(); - } -}; - -struct EditorWindowFrameTransferRequests { - std::optional beginGlobalTabDrag = {}; - std::optional detachPanel = {}; - - bool HasPendingRequests() const { - return beginGlobalTabDrag.has_value() || detachPanel.has_value(); - } +struct EditorWindowState { + EditorWindowWindowState window = {}; + EditorWindowRenderState render = {}; + EditorWindowInputState input = {}; + EditorWindowCompositionState composition = {}; + EditorWindowChromeRuntimeState chrome = {}; }; } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp b/new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp index 7f12f519..b7ef3e64 100644 --- a/new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp @@ -2,6 +2,7 @@ #include "Bootstrap/EditorResources.h" #include "Platform/Win32/EditorWindowConstants.h" +#include "Platform/Win32/EditorWindowInternalState.h" #include "Platform/Win32/EditorWindowPlatformInternal.h" #include "Platform/Win32/EditorWindowRuntimeInternal.h" #include "State/EditorContext.h" @@ -73,122 +74,121 @@ EditorWindow::EditorWindow( std::wstring title, bool primary, UIEditorWorkspaceController workspaceController) - : m_window{ - nullptr, - std::move(windowId), - std::move(title), - {}, - primary, - false } - , m_composition{ std::move(workspaceController), {} } { + : m_state(std::make_unique()) { + m_state->window.windowId = std::move(windowId); + m_state->window.title = std::move(title); + m_state->window.primary = primary; + m_state->composition.workspaceController = std::move(workspaceController); UpdateCachedTitleText(); } +EditorWindow::~EditorWindow() = default; + std::string_view EditorWindow::GetWindowId() const { - return m_window.windowId; + return m_state->window.windowId; } HWND EditorWindow::GetHwnd() const { - return m_window.hwnd; + return m_state->window.hwnd; } bool EditorWindow::HasHwnd() const { - return m_window.hwnd != nullptr; + return m_state->window.hwnd != nullptr; } bool EditorWindow::IsPrimary() const { - return m_window.primary; + return m_state->window.primary; } bool EditorWindow::IsClosing() const { - return m_window.closing; + return m_state->window.closing; } bool EditorWindow::IsRenderReady() const { - return m_render.ready; + return m_state->render.ready; } bool EditorWindow::IsTrackingMouseLeave() const { - return m_input.trackingMouseLeave; + return m_state->input.trackingMouseLeave; } bool EditorWindow::HasHoveredBorderlessResizeEdge() const { - return m_chrome.runtime.GetHoveredBorderlessResizeEdge() != + return m_state->chrome.runtime.GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None; } const std::wstring& EditorWindow::GetTitle() const { - return m_window.title; + return m_state->window.title; } const UIEditorWorkspaceController& EditorWindow::GetWorkspaceController() const { - return m_composition.workspaceController; + return m_state->composition.workspaceController; } UIEditorWorkspaceController& EditorWindow::GetWorkspaceController() { - return m_composition.workspaceController; + return m_state->composition.workspaceController; } const EditorShellRuntime& EditorWindow::GetShellRuntime() const { - return m_composition.shellRuntime; + return m_state->composition.shellRuntime; } EditorShellRuntime& EditorWindow::GetShellRuntime() { - return m_composition.shellRuntime; + return m_state->composition.shellRuntime; } const UIEditorShellInteractionFrame& EditorWindow::GetShellFrame() const { - return m_composition.shellRuntime.GetShellFrame(); + return m_state->composition.shellRuntime.GetShellFrame(); } const UIEditorShellInteractionState& EditorWindow::GetShellInteractionState() const { - return m_composition.shellRuntime.GetShellInteractionState(); + return m_state->composition.shellRuntime.GetShellInteractionState(); } void EditorWindow::SetExternalDockHostDropPreview( const Widgets::UIEditorDockHostDropPreviewState& preview) { - m_composition.shellRuntime.SetExternalDockHostDropPreview(preview); + m_state->composition.shellRuntime.SetExternalDockHostDropPreview(preview); } void EditorWindow::ClearExternalDockHostDropPreview() { - m_composition.shellRuntime.ClearExternalDockHostDropPreview(); + m_state->composition.shellRuntime.ClearExternalDockHostDropPreview(); } void EditorWindow::AttachHwnd(HWND hwnd) { - m_window.hwnd = hwnd; - m_window.closing = false; + m_state->window.hwnd = hwnd; + m_state->window.closing = false; } void EditorWindow::MarkDestroyed() { - m_window.hwnd = nullptr; - m_window.closing = false; - m_input.trackingMouseLeave = false; + m_state->window.hwnd = nullptr; + m_state->window.closing = false; + m_state->input.trackingMouseLeave = false; } void EditorWindow::MarkClosing() { - m_window.closing = true; + m_state->window.closing = true; } void EditorWindow::ClearClosing() { - m_window.closing = false; + m_state->window.closing = false; } void EditorWindow::SetTrackingMouseLeave(bool trackingMouseLeave) { - m_input.trackingMouseLeave = trackingMouseLeave; + m_state->input.trackingMouseLeave = trackingMouseLeave; } void EditorWindow::SetTitle(std::wstring title) { - m_window.title = std::move(title); + m_state->window.title = std::move(title); UpdateCachedTitleText(); } void EditorWindow::ReplaceWorkspaceController(UIEditorWorkspaceController workspaceController) { - m_composition.workspaceController = std::move(workspaceController); + m_state->composition.workspaceController = std::move(workspaceController); } void EditorWindow::InvalidateHostWindow() const { - if (m_window.hwnd != nullptr && IsWindow(m_window.hwnd)) { - InvalidateRect(m_window.hwnd, nullptr, FALSE); + if (m_state->window.hwnd != nullptr && IsWindow(m_state->window.hwnd)) { + InvalidateRect(m_state->window.hwnd, nullptr, FALSE); } } @@ -197,71 +197,80 @@ bool EditorWindow::Initialize( EditorContext& editorContext, const std::filesystem::path& captureRoot, bool autoCaptureOnStartup) { - if (m_window.hwnd == nullptr) { + if (m_state->window.hwnd == nullptr) { LogRuntimeTrace("app", "window initialize skipped: hwnd is null"); return false; } - Host::RefreshBorderlessWindowDwmDecorations(m_window.hwnd); - m_chrome.runtime.Reset(); - m_chrome.runtime.SetWindowDpi(QueryWindowDpi(m_window.hwnd)); - m_render.renderer.SetDpiScale(GetDpiScale()); + Host::RefreshBorderlessWindowDwmDecorations(m_state->window.hwnd); + m_state->chrome.runtime.Reset(); + m_state->chrome.runtime.SetWindowDpi(QueryWindowDpi(m_state->window.hwnd)); + m_state->render.renderer.SetDpiScale(GetDpiScale()); std::ostringstream dpiTrace = {}; - dpiTrace << "initial dpi=" << m_chrome.runtime.GetWindowDpi() + dpiTrace << "initial dpi=" << m_state->chrome.runtime.GetWindowDpi() << " scale=" << GetDpiScale(); LogRuntimeTrace("window", dpiTrace.str()); - if (!m_render.renderer.Initialize(m_window.hwnd)) { + if (!m_state->render.renderer.Initialize(m_state->window.hwnd)) { LogRuntimeTrace("app", "renderer initialization failed"); return false; } RECT clientRect = {}; - GetClientRect(m_window.hwnd, &clientRect); + GetClientRect(m_state->window.hwnd, &clientRect); const int clientWidth = (std::max)(clientRect.right - clientRect.left, 1L); const int clientHeight = (std::max)(clientRect.bottom - clientRect.top, 1L); - if (!m_render.windowRenderer.Initialize(m_window.hwnd, clientWidth, clientHeight)) { + if (!m_state->render.windowRenderer.Initialize( + m_state->window.hwnd, + clientWidth, + clientHeight)) { LogRuntimeTrace("app", "d3d12 window renderer initialization failed"); - m_render.renderer.Shutdown(); + m_state->render.renderer.Shutdown(); return false; } const Host::D3D12WindowRenderLoopAttachResult attachResult = - m_render.windowRenderLoop.Attach(m_render.renderer, m_render.windowRenderer); + m_state->render.windowRenderLoop.Attach( + m_state->render.renderer, + m_state->render.windowRenderer); if (!attachResult.interopWarning.empty()) { LogRuntimeTrace("app", attachResult.interopWarning); } - editorContext.AttachTextMeasurer(m_render.renderer); - m_composition.shellRuntime.Initialize(repoRoot, m_render.renderer); - m_composition.shellRuntime.AttachViewportWindowRenderer(m_render.windowRenderer); - m_composition.shellRuntime.SetViewportSurfacePresentationEnabled( + editorContext.AttachTextMeasurer(m_state->render.renderer); + m_state->composition.shellRuntime.Initialize( + repoRoot, + m_state->render.renderer, + m_state->render.renderer); + m_state->composition.shellRuntime.AttachViewportWindowRenderer( + m_state->render.windowRenderer); + m_state->composition.shellRuntime.SetViewportSurfacePresentationEnabled( attachResult.hasViewportSurfacePresentation); std::string titleBarLogoError = {}; if (!LoadEmbeddedPngTexture( - m_render.renderer, + m_state->render.renderer, IDR_PNG_LOGO_ICON, - m_render.titleBarLogoIcon, + m_state->render.titleBarLogoIcon, titleBarLogoError)) { LogRuntimeTrace("icons", "titlebar logo_icon.png: " + titleBarLogoError); } - if (!m_composition.shellRuntime.GetBuiltInIconError().empty()) { - LogRuntimeTrace("icons", m_composition.shellRuntime.GetBuiltInIconError()); + if (!m_state->composition.shellRuntime.GetBuiltInIconError().empty()) { + LogRuntimeTrace("icons", m_state->composition.shellRuntime.GetBuiltInIconError()); } LogRuntimeTrace( "app", "shell runtime initialized: " + editorContext.DescribeWorkspaceState( - m_composition.workspaceController, - m_composition.shellRuntime.GetShellInteractionState())); - m_render.ready = true; + m_state->composition.workspaceController, + m_state->composition.shellRuntime.GetShellInteractionState())); + m_state->render.ready = true; - m_render.autoScreenshot.Initialize(captureRoot); + m_state->render.autoScreenshot.Initialize(captureRoot); if (autoCaptureOnStartup && IsAutoCaptureOnStartupEnabled()) { - m_render.autoScreenshot.RequestCapture("startup"); + m_state->render.autoScreenshot.RequestCapture("startup"); editorContext.SetStatus("Capture", "Startup capture requested."); } @@ -271,41 +280,42 @@ bool EditorWindow::Initialize( void EditorWindow::Shutdown() { ForceReleasePointerCapture(); - m_render.ready = false; - m_render.autoScreenshot.Shutdown(); - m_composition.shellRuntime.Shutdown(); - m_render.renderer.ReleaseTexture(m_render.titleBarLogoIcon); - m_render.windowRenderLoop.Detach(); - m_render.windowRenderer.Shutdown(); - m_render.renderer.Shutdown(); - m_input.pendingEvents.clear(); - m_chrome.chromeState = {}; - m_chrome.runtime.Reset(); + m_state->render.ready = false; + m_state->render.autoScreenshot.Shutdown(); + m_state->composition.shellRuntime.Shutdown(); + m_state->render.renderer.ReleaseTexture(m_state->render.titleBarLogoIcon); + m_state->render.windowRenderLoop.Detach(); + m_state->render.windowRenderer.Shutdown(); + m_state->render.renderer.Shutdown(); + m_state->input.pendingEvents.clear(); + m_state->chrome.chromeState = {}; + m_state->chrome.runtime.Reset(); } void EditorWindow::ResetInteractionState() { ForceReleasePointerCapture(); - m_input.pendingEvents.clear(); - m_input.trackingMouseLeave = false; - m_input.modifierTracker.Reset(); - m_composition.shellRuntime.ResetInteractionState(); - m_chrome.chromeState = {}; - m_chrome.runtime.EndBorderlessResize(); - m_chrome.runtime.EndBorderlessWindowDragRestore(); - m_chrome.runtime.EndInteractiveResize(); - m_chrome.runtime.SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None); - m_chrome.runtime.ClearPredictedClientPixelSize(); + m_state->input.pendingEvents.clear(); + m_state->input.trackingMouseLeave = false; + m_state->input.modifierTracker.Reset(); + m_state->composition.shellRuntime.ResetInteractionState(); + m_state->chrome.chromeState = {}; + m_state->chrome.runtime.EndBorderlessResize(); + m_state->chrome.runtime.EndBorderlessWindowDragRestore(); + m_state->chrome.runtime.EndInteractiveResize(); + m_state->chrome.runtime.SetHoveredBorderlessResizeEdge( + Host::BorderlessWindowResizeEdge::None); + m_state->chrome.runtime.ClearPredictedClientPixelSize(); } bool EditorWindow::ApplyWindowResize(UINT width, UINT height) { - if (!m_render.ready || width == 0u || height == 0u) { + if (!m_state->render.ready || width == 0u || height == 0u) { return false; } const Host::D3D12WindowRenderLoopResizeResult resizeResult = - m_render.windowRenderLoop.ApplyResize(width, height); - m_composition.shellRuntime.SetViewportSurfacePresentationEnabled( + m_state->render.windowRenderLoop.ApplyResize(width, height); + m_state->composition.shellRuntime.SetViewportSurfacePresentationEnabled( resizeResult.hasViewportSurfacePresentation); if (!resizeResult.windowRendererWarning.empty()) { @@ -322,12 +332,12 @@ bool EditorWindow::ApplyWindowResize(UINT width, UINT height) { bool EditorWindow::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const { outWidth = 0u; outHeight = 0u; - if (m_window.hwnd == nullptr || !IsWindow(m_window.hwnd)) { + if (m_state->window.hwnd == nullptr || !IsWindow(m_state->window.hwnd)) { return false; } RECT clientRect = {}; - if (!GetClientRect(m_window.hwnd, &clientRect)) { + if (!GetClientRect(m_state->window.hwnd, &clientRect)) { return false; } @@ -343,7 +353,7 @@ bool EditorWindow::QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) } bool EditorWindow::ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const { - if (m_chrome.runtime.TryGetPredictedClientPixelSize(outWidth, outHeight)) { + if (m_state->chrome.runtime.TryGetPredictedClientPixelSize(outWidth, outHeight)) { return true; } @@ -351,7 +361,7 @@ bool EditorWindow::ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) } float EditorWindow::GetDpiScale() const { - return m_chrome.runtime.GetDpiScale(kBaseDpiScale); + return m_state->chrome.runtime.GetDpiScale(kBaseDpiScale); } float EditorWindow::PixelsToDips(float pixels) const { @@ -367,11 +377,11 @@ UIPoint EditorWindow::ConvertClientPixelsToDips(LONG x, LONG y) const { UIPoint EditorWindow::ConvertScreenPixelsToClientDips(const POINT& screenPoint) const { POINT clientPoint = screenPoint; - if (m_window.hwnd != nullptr) { - ScreenToClient(m_window.hwnd, &clientPoint); + if (m_state->window.hwnd != nullptr) { + ScreenToClient(m_state->window.hwnd, &clientPoint); } - const float dpiScale = m_chrome.runtime.GetDpiScale(kBaseDpiScale); + const float dpiScale = m_state->chrome.runtime.GetDpiScale(kBaseDpiScale); return UIPoint( dpiScale > 0.0f ? static_cast(clientPoint.x) / dpiScale @@ -385,15 +395,17 @@ void EditorWindow::OnResize(UINT width, UINT height) { bool matchesPredictedClientSize = false; UINT predictedWidth = 0u; UINT predictedHeight = 0u; - if (m_chrome.runtime.TryGetPredictedClientPixelSize(predictedWidth, predictedHeight)) { + if (m_state->chrome.runtime.TryGetPredictedClientPixelSize( + predictedWidth, + predictedHeight)) { matchesPredictedClientSize = predictedWidth == width && predictedHeight == height; } - m_chrome.runtime.ClearPredictedClientPixelSize(); - if (IsBorderlessWindowEnabled() && m_window.hwnd != nullptr) { - Host::RefreshBorderlessWindowDwmDecorations(m_window.hwnd); + m_state->chrome.runtime.ClearPredictedClientPixelSize(); + if (IsBorderlessWindowEnabled() && m_state->window.hwnd != nullptr) { + Host::RefreshBorderlessWindowDwmDecorations(m_state->window.hwnd); } if (!matchesPredictedClientSize) { @@ -402,12 +414,12 @@ void EditorWindow::OnResize(UINT width, UINT height) { } void EditorWindow::OnEnterSizeMove() { - m_chrome.runtime.BeginInteractiveResize(); + m_state->chrome.runtime.BeginInteractiveResize(); } void EditorWindow::OnExitSizeMove() { - m_chrome.runtime.EndInteractiveResize(); - m_chrome.runtime.ClearPredictedClientPixelSize(); + m_state->chrome.runtime.EndInteractiveResize(); + m_state->chrome.runtime.ClearPredictedClientPixelSize(); UINT width = 0u; UINT height = 0u; if (QueryCurrentClientPixelSize(width, height)) { @@ -416,13 +428,13 @@ void EditorWindow::OnExitSizeMove() { } void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { - m_chrome.runtime.SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi); - m_render.renderer.SetDpiScale(GetDpiScale()); - if (m_window.hwnd != nullptr) { + m_state->chrome.runtime.SetWindowDpi(dpi == 0u ? kDefaultDpi : dpi); + m_state->render.renderer.SetDpiScale(GetDpiScale()); + if (m_state->window.hwnd != nullptr) { const LONG windowWidth = suggestedRect.right - suggestedRect.left; const LONG windowHeight = suggestedRect.bottom - suggestedRect.top; SetWindowPos( - m_window.hwnd, + m_state->window.hwnd, nullptr, suggestedRect.left, suggestedRect.top, @@ -434,11 +446,11 @@ void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { if (QueryCurrentClientPixelSize(clientWidth, clientHeight)) { ApplyWindowResize(clientWidth, clientHeight); } - Host::RefreshBorderlessWindowDwmDecorations(m_window.hwnd); + Host::RefreshBorderlessWindowDwmDecorations(m_state->window.hwnd); } std::ostringstream trace = {}; - trace << "dpi changed to " << m_chrome.runtime.GetWindowDpi() + trace << "dpi changed to " << m_state->chrome.runtime.GetWindowDpi() << " scale=" << GetDpiScale(); LogRuntimeTrace("window", trace.str()); } @@ -449,7 +461,7 @@ bool EditorWindow::IsVerboseRuntimeTraceEnabled() { } void EditorWindow::UpdateCachedTitleText() { - m_window.titleText = WideToUtf8(m_window.title); + m_state->window.titleText = WideToUtf8(m_state->window.title); } } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp b/new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp index 8685de4b..d36a7e69 100644 --- a/new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp @@ -1,5 +1,6 @@ #include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowConstants.h" +#include "Platform/Win32/EditorWindowInternalState.h" #include @@ -10,13 +11,13 @@ namespace XCEngine::UI::Editor::App { using namespace EditorWindowInternal; bool EditorWindow::UpdateBorderlessWindowChromeHover(LPARAM lParam) { - if (m_chrome.runtime.GetHoveredBorderlessResizeEdge() != + if (m_state->chrome.runtime.GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None || - m_chrome.runtime.IsBorderlessResizeActive()) { + m_state->chrome.runtime.IsBorderlessResizeActive()) { const bool changed = - m_chrome.chromeState.hoveredTarget != + m_state->chrome.chromeState.hoveredTarget != Host::BorderlessWindowChromeHitTarget::None; - m_chrome.chromeState.hoveredTarget = + m_state->chrome.chromeState.hoveredTarget = Host::BorderlessWindowChromeHitTarget::None; return changed; } @@ -29,18 +30,18 @@ bool EditorWindow::UpdateBorderlessWindowChromeHover(LPARAM lParam) { hitTarget == Host::BorderlessWindowChromeHitTarget::CloseButton ? hitTarget : Host::BorderlessWindowChromeHitTarget::None; - if (m_chrome.chromeState.hoveredTarget == buttonTarget) { + if (m_state->chrome.chromeState.hoveredTarget == buttonTarget) { return false; } - m_chrome.chromeState.hoveredTarget = buttonTarget; + m_state->chrome.chromeState.hoveredTarget = buttonTarget; return true; } bool EditorWindow::HandleBorderlessWindowChromeButtonDown(LPARAM lParam) { - if (m_chrome.runtime.GetHoveredBorderlessResizeEdge() != + if (m_state->chrome.runtime.GetHoveredBorderlessResizeEdge() != Host::BorderlessWindowResizeEdge::None || - m_chrome.runtime.IsBorderlessResizeActive()) { + m_state->chrome.runtime.IsBorderlessResizeActive()) { return false; } @@ -50,23 +51,23 @@ bool EditorWindow::HandleBorderlessWindowChromeButtonDown(LPARAM lParam) { case Host::BorderlessWindowChromeHitTarget::MinimizeButton: case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton: case Host::BorderlessWindowChromeHitTarget::CloseButton: - m_chrome.chromeState.pressedTarget = hitTarget; + m_state->chrome.chromeState.pressedTarget = hitTarget; AcquirePointerCapture(EditorWindowPointerCaptureOwner::BorderlessChrome); InvalidateHostWindow(); return true; case Host::BorderlessWindowChromeHitTarget::DragRegion: - if (m_window.hwnd != nullptr) { + if (m_state->window.hwnd != nullptr) { if (IsBorderlessWindowMaximized()) { POINT screenPoint = {}; if (GetCursorPos(&screenPoint)) { - m_chrome.runtime.BeginBorderlessWindowDragRestore(screenPoint); + m_state->chrome.runtime.BeginBorderlessWindowDragRestore(screenPoint); AcquirePointerCapture(EditorWindowPointerCaptureOwner::BorderlessChrome); return true; } } ForceReleasePointerCapture(); - SendMessageW(m_window.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); + SendMessageW(m_state->window.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); } return true; case Host::BorderlessWindowChromeHitTarget::None: @@ -79,13 +80,13 @@ bool EditorWindow::HandleBorderlessWindowChromeButtonUp( EditorContext& editorContext, bool globalTabDragActive, LPARAM lParam) { - if (m_chrome.runtime.IsBorderlessWindowDragRestoreArmed()) { + if (m_state->chrome.runtime.IsBorderlessWindowDragRestoreArmed()) { ClearBorderlessWindowChromeDragRestoreState(); return true; } const Host::BorderlessWindowChromeHitTarget pressedTarget = - m_chrome.chromeState.pressedTarget; + m_state->chrome.chromeState.pressedTarget; if (pressedTarget != Host::BorderlessWindowChromeHitTarget::MinimizeButton && pressedTarget != Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton && pressedTarget != Host::BorderlessWindowChromeHitTarget::CloseButton) { @@ -94,7 +95,7 @@ bool EditorWindow::HandleBorderlessWindowChromeButtonUp( const Host::BorderlessWindowChromeHitTarget releasedTarget = HitTestBorderlessWindowChrome(lParam); - m_chrome.chromeState.pressedTarget = + m_state->chrome.chromeState.pressedTarget = Host::BorderlessWindowChromeHitTarget::None; ReleasePointerCapture(EditorWindowPointerCaptureOwner::BorderlessChrome); InvalidateHostWindow(); @@ -112,7 +113,7 @@ bool EditorWindow::HandleBorderlessWindowChromeDoubleClick( EditorContext& editorContext, bool globalTabDragActive, LPARAM lParam) { - if (m_chrome.runtime.IsBorderlessWindowDragRestoreArmed()) { + if (m_state->chrome.runtime.IsBorderlessWindowDragRestoreArmed()) { ClearBorderlessWindowChromeDragRestoreState(); } @@ -131,7 +132,8 @@ bool EditorWindow::HandleBorderlessWindowChromeDoubleClick( bool EditorWindow::HandleBorderlessWindowChromeDragRestorePointerMove( EditorContext& editorContext, bool globalTabDragActive) { - if (!m_chrome.runtime.IsBorderlessWindowDragRestoreArmed() || m_window.hwnd == nullptr) { + if (!m_state->chrome.runtime.IsBorderlessWindowDragRestoreArmed() || + m_state->window.hwnd == nullptr) { return false; } @@ -141,7 +143,7 @@ bool EditorWindow::HandleBorderlessWindowChromeDragRestorePointerMove( } const POINT initialScreenPoint = - m_chrome.runtime.GetBorderlessWindowDragRestoreInitialScreenPoint(); + m_state->chrome.runtime.GetBorderlessWindowDragRestoreInitialScreenPoint(); const int dragThresholdX = (std::max)(GetSystemMetrics(SM_CXDRAG), 1); const int dragThresholdY = (std::max)(GetSystemMetrics(SM_CYDRAG), 1); const LONG deltaX = currentScreenPoint.x - initialScreenPoint.x; @@ -154,7 +156,7 @@ bool EditorWindow::HandleBorderlessWindowChromeDragRestorePointerMove( RECT restoreRect = {}; RECT currentRect = {}; RECT workAreaRect = {}; - if (!m_chrome.runtime.TryGetBorderlessWindowRestoreRect(restoreRect) || + if (!m_state->chrome.runtime.TryGetBorderlessWindowRestoreRect(restoreRect) || !QueryCurrentWindowRect(currentRect) || !QueryBorderlessWindowWorkAreaRect(workAreaRect)) { ClearBorderlessWindowChromeDragRestoreState(); @@ -193,34 +195,34 @@ bool EditorWindow::HandleBorderlessWindowChromeDragRestorePointerMove( newTop + restoreHeight }; - m_chrome.runtime.SetBorderlessWindowMaximized(false); + m_state->chrome.runtime.SetBorderlessWindowMaximized(false); ApplyPredictedWindowRectTransition( editorContext, globalTabDragActive, targetRect); ClearBorderlessWindowChromeDragRestoreState(); - SendMessageW(m_window.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); + SendMessageW(m_state->window.hwnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); return true; } void EditorWindow::ClearBorderlessWindowChromeDragRestoreState() { - if (!m_chrome.runtime.IsBorderlessWindowDragRestoreArmed()) { + if (!m_state->chrome.runtime.IsBorderlessWindowDragRestoreArmed()) { return; } - m_chrome.runtime.EndBorderlessWindowDragRestore(); + m_state->chrome.runtime.EndBorderlessWindowDragRestore(); ReleasePointerCapture(EditorWindowPointerCaptureOwner::BorderlessChrome); } void EditorWindow::ClearBorderlessWindowChromeState() { - if (m_chrome.chromeState.hoveredTarget == + if (m_state->chrome.chromeState.hoveredTarget == Host::BorderlessWindowChromeHitTarget::None && - m_chrome.chromeState.pressedTarget == + m_state->chrome.chromeState.pressedTarget == Host::BorderlessWindowChromeHitTarget::None) { return; } - m_chrome.chromeState = {}; + m_state->chrome.chromeState = {}; InvalidateHostWindow(); } @@ -228,19 +230,19 @@ void EditorWindow::ExecuteBorderlessWindowChromeAction( EditorContext& editorContext, bool globalTabDragActive, Host::BorderlessWindowChromeHitTarget target) { - if (m_window.hwnd == nullptr) { + if (m_state->window.hwnd == nullptr) { return; } switch (target) { case Host::BorderlessWindowChromeHitTarget::MinimizeButton: - ShowWindow(m_window.hwnd, SW_MINIMIZE); + ShowWindow(m_state->window.hwnd, SW_MINIMIZE); break; case Host::BorderlessWindowChromeHitTarget::MaximizeRestoreButton: ToggleBorderlessWindowMaximizeRestore(editorContext, globalTabDragActive); break; case Host::BorderlessWindowChromeHitTarget::CloseButton: - PostMessageW(m_window.hwnd, WM_CLOSE, 0, 0); + PostMessageW(m_state->window.hwnd, WM_CLOSE, 0, 0); break; case Host::BorderlessWindowChromeHitTarget::DragRegion: case Host::BorderlessWindowChromeHitTarget::None: diff --git a/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp b/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp index 179a9ef1..4eba303e 100644 --- a/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp @@ -1,5 +1,6 @@ #include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowConstants.h" +#include "Platform/Win32/EditorWindowInternalState.h" #include "Platform/Win32/EditorWindowStyle.h" #include @@ -100,17 +101,18 @@ UIRect BuildDetachedTitleLogoRect(const Host::BorderlessWindowChromeLayout& layo } // namespace bool EditorWindow::ShouldUseDetachedTitleBarTabStrip() const { - return !m_window.primary && HasSingleVisibleRootTab(m_composition.workspaceController); + return !m_state->window.primary && + HasSingleVisibleRootTab(m_state->composition.workspaceController); } Host::BorderlessWindowChromeHitTarget EditorWindow::HitTestBorderlessWindowChrome( LPARAM lParam) const { - if (!IsBorderlessWindowEnabled() || m_window.hwnd == nullptr) { + if (!IsBorderlessWindowEnabled() || m_state->window.hwnd == nullptr) { return Host::BorderlessWindowChromeHitTarget::None; } RECT clientRect = {}; - if (!GetClientRect(m_window.hwnd, &clientRect)) { + if (!GetClientRect(m_state->window.hwnd, &clientRect)) { return Host::BorderlessWindowChromeHitTarget::None; } @@ -156,11 +158,11 @@ void EditorWindow::AppendBorderlessWindowChrome( 1.0f); } - if (!m_window.primary) { - if (m_render.titleBarLogoIcon.IsValid()) { + if (!m_state->window.primary) { + if (m_state->render.titleBarLogoIcon.IsValid()) { drawList.AddImage( BuildDetachedTitleLogoRect(layout), - m_render.titleBarLogoIcon, + m_state->render.titleBarLogoIcon, UIColor(1.0f, 1.0f, 1.0f, 1.0f)); } } else { @@ -168,19 +170,21 @@ void EditorWindow::AppendBorderlessWindowChrome( const float iconY = layout.titleBarRect.y + (std::max)(0.0f, (layout.titleBarRect.height - kTitleBarLogoExtent) * 0.5f); - if (m_render.titleBarLogoIcon.IsValid()) { + if (m_state->render.titleBarLogoIcon.IsValid()) { drawList.AddImage( UIRect(iconX, iconY, kTitleBarLogoExtent, kTitleBarLogoExtent), - m_render.titleBarLogoIcon, + m_state->render.titleBarLogoIcon, UIColor(1.0f, 1.0f, 1.0f, 1.0f)); } const std::string& titleText = - m_window.titleText.empty() ? std::string("XCEngine Editor") : m_window.titleText; + m_state->window.titleText.empty() + ? std::string("XCEngine Editor") + : m_state->window.titleText; drawList.AddText( UIPoint( iconX + - (m_render.titleBarLogoIcon.IsValid() + (m_state->render.titleBarLogoIcon.IsValid() ? (kTitleBarLogoExtent + kTitleBarLogoTextGap) : 4.0f), layout.titleBarRect.y + @@ -195,7 +199,7 @@ void EditorWindow::AppendBorderlessWindowChrome( Host::AppendBorderlessWindowChrome( drawList, layout, - m_chrome.chromeState, + m_state->chrome.chromeState, IsBorderlessWindowMaximized()); } diff --git a/new_editor/app/Platform/Win32/EditorWindowTransferRequests.h b/new_editor/app/Platform/Win32/EditorWindowTransferRequests.h new file mode 100644 index 00000000..b2994f12 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowTransferRequests.h @@ -0,0 +1,33 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include + +#include +#include + +namespace XCEngine::UI::Editor::App { + +struct EditorWindowPanelTransferRequest { + std::string nodeId = {}; + std::string panelId = {}; + POINT screenPoint = {}; + + bool IsValid() const { + return !nodeId.empty() && !panelId.empty(); + } +}; + +struct EditorWindowFrameTransferRequests { + std::optional beginGlobalTabDrag = {}; + std::optional detachPanel = {}; + + bool HasPendingRequests() const { + return beginGlobalTabDrag.has_value() || detachPanel.has_value(); + } +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/WindowManager/Internal.h b/new_editor/app/Platform/Win32/WindowManager/Internal.h index 34bb853a..e0bc1445 100644 --- a/new_editor/app/Platform/Win32/WindowManager/Internal.h +++ b/new_editor/app/Platform/Win32/WindowManager/Internal.h @@ -2,9 +2,6 @@ #include "Platform/Win32/EditorWindowManager.h" -#include -#include - namespace XCEngine::UI::Editor::App::Internal { class EditorWindowHostRuntime final { diff --git a/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp b/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp index 90447a7f..11315a02 100644 --- a/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp @@ -5,6 +5,8 @@ #include "Platform/Win32/EditorWindow.h" #include +#include +#include #include #include diff --git a/new_editor/app/Platform/Win32/WindowMessageDispatcher.cpp b/new_editor/app/Platform/Win32/WindowMessageDispatcher.cpp index 9205b753..15827d39 100644 --- a/new_editor/app/Platform/Win32/WindowMessageDispatcher.cpp +++ b/new_editor/app/Platform/Win32/WindowMessageDispatcher.cpp @@ -1,5 +1,7 @@ #include "WindowMessageDispatcher.h" +#include "Composition/EditorShellRuntime.h" +#include "Platform/Win32/BorderlessWindowChrome.h" #include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowPointerCapture.h" #include "WindowMessageHost.h" diff --git a/new_editor/app/Project/EditorProjectRuntime.h b/new_editor/app/Project/EditorProjectRuntime.h index eeaed41a..801f3e8f 100644 --- a/new_editor/app/Project/EditorProjectRuntime.h +++ b/new_editor/app/Project/EditorProjectRuntime.h @@ -2,7 +2,7 @@ #include "Features/Project/ProjectBrowserModel.h" -#include +#include "State/EditorSession.h" #include diff --git a/new_editor/app/Rendering/Assets/BuiltInIcons.cpp b/new_editor/app/Rendering/Assets/BuiltInIcons.cpp index 230755f9..f11ea178 100644 --- a/new_editor/app/Rendering/Assets/BuiltInIcons.cpp +++ b/new_editor/app/Rendering/Assets/BuiltInIcons.cpp @@ -1,5 +1,6 @@ #include "BuiltInIcons.h" #include "Bootstrap/EditorResources.h" +#include "Host/TextureHost.h" #include "Internal/EmbeddedPngLoader.h" #include @@ -23,7 +24,7 @@ void AppendLoadError( } void LoadEmbeddedIconTexture( - Host::NativeRenderer& renderer, + Host::TextureHost& renderer, UINT resourceId, std::string_view label, ::XCEngine::UI::UITextureHandle& outTexture, @@ -36,7 +37,7 @@ void LoadEmbeddedIconTexture( } // namespace -void BuiltInIcons::Initialize(Host::NativeRenderer& renderer) { +void BuiltInIcons::Initialize(Host::TextureHost& renderer) { Shutdown(); m_renderer = &renderer; diff --git a/new_editor/app/Rendering/Assets/BuiltInIcons.h b/new_editor/app/Rendering/Assets/BuiltInIcons.h index d48a63c4..c67a1f59 100644 --- a/new_editor/app/Rendering/Assets/BuiltInIcons.h +++ b/new_editor/app/Rendering/Assets/BuiltInIcons.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "Host/HostFwd.h" #include @@ -17,14 +17,14 @@ enum class BuiltInIconKind : std::uint8_t { class BuiltInIcons { public: - void Initialize(Host::NativeRenderer& renderer); + void Initialize(Host::TextureHost& renderer); void Shutdown(); const ::XCEngine::UI::UITextureHandle& Resolve(BuiltInIconKind kind) const; const std::string& GetLastError() const; private: - Host::NativeRenderer* m_renderer = nullptr; + Host::TextureHost* m_renderer = nullptr; ::XCEngine::UI::UITextureHandle m_folderIcon = {}; ::XCEngine::UI::UITextureHandle m_gameObjectIcon = {}; ::XCEngine::UI::UITextureHandle m_sceneIcon = {}; diff --git a/new_editor/app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.h b/new_editor/app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.h index e0bf9e83..6ddeed82 100644 --- a/new_editor/app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.h +++ b/new_editor/app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.h @@ -4,6 +4,8 @@ #define NOMINMAX #endif +#include "Host/ShaderResourceDescriptorAllocator.h" + #include #include #include @@ -18,12 +20,12 @@ namespace XCEngine::UI::Editor::Host { -class D3D12ShaderResourceDescriptorAllocator { +class D3D12ShaderResourceDescriptorAllocator : public ShaderResourceDescriptorAllocator { public: - bool Initialize(::XCEngine::RHI::RHIDevice& device, UINT descriptorCount = 64u); - void Shutdown(); + bool Initialize(::XCEngine::RHI::RHIDevice& device, UINT descriptorCount = 64u) override; + void Shutdown() override; - bool IsInitialized() const; + bool IsInitialized() const override; ID3D12DescriptorHeap* GetDescriptorHeap() const; UINT GetDescriptorSize() const; UINT GetDescriptorCount() const; @@ -34,10 +36,10 @@ public: bool CreateTextureDescriptor( ::XCEngine::RHI::RHITexture* texture, D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle, - D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle); + D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle) override; void Free( D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle, - D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle); + D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle) override; private: void AllocateInternal( diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h b/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h index 49921442..853e2b14 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h +++ b/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h @@ -4,6 +4,7 @@ #define NOMINMAX #endif +#include "Host/ViewportRenderHost.h" #include "D3D12HostDevice.h" #include "D3D12WindowSwapChainPresenter.h" @@ -18,7 +19,7 @@ namespace XCEngine::UI::Editor::Host { -class D3D12WindowRenderer { +class D3D12WindowRenderer : public ViewportRenderHost { public: static constexpr std::uint32_t kSwapChainBufferCount = D3D12WindowSwapChainPresenter::kSwapChainBufferCount; @@ -35,7 +36,7 @@ public: ID3D12Device* GetDevice() const; ID3D12CommandQueue* GetCommandQueue() const; const std::string& GetLastError() const; - ::XCEngine::RHI::RHIDevice* GetRHIDevice() const; + ::XCEngine::RHI::RHIDevice* GetRHIDevice() const override; ::XCEngine::RHI::RHISwapChain* GetSwapChain() const; const ::XCEngine::Rendering::RenderSurface* GetCurrentRenderSurface() const; const ::XCEngine::RHI::D3D12Texture* GetCurrentBackBufferTexture() const; diff --git a/new_editor/app/Rendering/Native/NativeRenderer.h b/new_editor/app/Rendering/Native/NativeRenderer.h index 665f7487..5ff11732 100644 --- a/new_editor/app/Rendering/Native/NativeRenderer.h +++ b/new_editor/app/Rendering/Native/NativeRenderer.h @@ -4,6 +4,8 @@ #define NOMINMAX #endif +#include "Host/TextureHost.h" + #include #include @@ -29,7 +31,9 @@ namespace XCEngine::UI::Editor::Host { -class NativeRenderer : public ::XCEngine::UI::Editor::UIEditorTextMeasurer { +class NativeRenderer + : public TextureHost + , public ::XCEngine::UI::Editor::UIEditorTextMeasurer { public: bool Initialize(HWND hwnd); void Shutdown(); @@ -47,13 +51,13 @@ public: bool LoadTextureFromFile( const std::filesystem::path& path, ::XCEngine::UI::UITextureHandle& outTexture, - std::string& outError); + std::string& outError) override; bool LoadTextureFromMemory( const std::uint8_t* data, std::size_t size, ::XCEngine::UI::UITextureHandle& outTexture, - std::string& outError); - void ReleaseTexture(::XCEngine::UI::UITextureHandle& texture); + std::string& outError) override; + void ReleaseTexture(::XCEngine::UI::UITextureHandle& texture) override; float MeasureTextWidth( const ::XCEngine::UI::Editor::UIEditorTextMeasureRequest& request) const override; bool CaptureToPng( diff --git a/new_editor/app/Rendering/Viewport/Passes/SceneViewportGridPass.cpp b/new_editor/app/Rendering/Viewport/Passes/SceneViewportGridPass.cpp index 7dc38475..c7fb9caf 100644 --- a/new_editor/app/Rendering/Viewport/Passes/SceneViewportGridPass.cpp +++ b/new_editor/app/Rendering/Viewport/Passes/SceneViewportGridPass.cpp @@ -2,9 +2,9 @@ #include "Rendering/Viewport/SceneViewportResourcePaths.h" -#include "Rendering/Internal/RenderSurfacePipelineUtils.h" -#include "Rendering/Internal/ShaderVariantUtils.h" -#include "Rendering/Materials/RenderMaterialStateUtils.h" +#include +#include +#include #include #include diff --git a/new_editor/app/Rendering/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp b/new_editor/app/Rendering/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp index ffce755b..da00feb9 100644 --- a/new_editor/app/Rendering/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp +++ b/new_editor/app/Rendering/Viewport/Passes/SceneViewportSelectionOutlinePass.cpp @@ -2,9 +2,9 @@ #include "Rendering/Viewport/ViewportRenderTargets.h" -#include "Rendering/Internal/RenderSurfacePipelineUtils.h" -#include "Rendering/Internal/ShaderVariantUtils.h" -#include "Rendering/Materials/RenderMaterialStateUtils.h" +#include +#include +#include #include #include diff --git a/new_editor/app/Rendering/Viewport/ViewportHostService.cpp b/new_editor/app/Rendering/Viewport/ViewportHostService.cpp index 39639822..67a90dc6 100644 --- a/new_editor/app/Rendering/Viewport/ViewportHostService.cpp +++ b/new_editor/app/Rendering/Viewport/ViewportHostService.cpp @@ -1,5 +1,8 @@ #include "ViewportHostService.h" +#include "Host/ViewportRenderHost.h" +#include + #include #include #include @@ -22,16 +25,20 @@ void SetViewportStatusIfEmpty( } // namespace -ViewportHostService::ViewportHostService() = default; +ViewportHostService::ViewportHostService() + : m_textureDescriptorAllocator( + std::make_unique()) { +} ViewportHostService::~ViewportHostService() = default; void ViewportHostService::AttachWindowRenderer( - Host::D3D12WindowRenderer& windowRenderer) { + Host::ViewportRenderHost& windowRenderer) { if (m_windowRenderer == &windowRenderer) { m_device = windowRenderer.GetRHIDevice(); - if (m_device != nullptr && !m_textureDescriptorAllocator.IsInitialized()) { - m_textureDescriptorAllocator.Initialize(*m_device); + if (m_device != nullptr && + !m_textureDescriptorAllocator->IsInitialized()) { + m_textureDescriptorAllocator->Initialize(*m_device); } return; } @@ -40,7 +47,7 @@ void ViewportHostService::AttachWindowRenderer( m_windowRenderer = &windowRenderer; m_device = windowRenderer.GetRHIDevice(); if (m_device != nullptr) { - m_textureDescriptorAllocator.Initialize(*m_device); + m_textureDescriptorAllocator->Initialize(*m_device); } } @@ -63,7 +70,7 @@ void ViewportHostService::Shutdown() { } m_sceneViewportRenderPassBundle.Shutdown(); - m_textureDescriptorAllocator.Shutdown(); + m_textureDescriptorAllocator->Shutdown(); m_windowRenderer = nullptr; m_device = nullptr; m_surfacePresentationEnabled = false; @@ -97,7 +104,9 @@ const ViewportHostService::ViewportEntry& ViewportHostService::GetEntry( } void ViewportHostService::DestroyViewportEntry(ViewportEntry& entry) { - m_renderTargetManager.DestroyTargets(&m_textureDescriptorAllocator, entry.renderTargets); + m_renderTargetManager.DestroyTargets( + m_textureDescriptorAllocator.get(), + entry.renderTargets); entry = {}; } @@ -202,7 +211,7 @@ bool ViewportHostService::EnsureViewportResources(ViewportEntry& entry) { entry.requestedWidth, entry.requestedHeight, *m_device, - m_textureDescriptorAllocator, + *m_textureDescriptorAllocator, entry.renderTargets); } diff --git a/new_editor/app/Rendering/Viewport/ViewportHostService.h b/new_editor/app/Rendering/Viewport/ViewportHostService.h index ae97880b..2a600a34 100644 --- a/new_editor/app/Rendering/Viewport/ViewportHostService.h +++ b/new_editor/app/Rendering/Viewport/ViewportHostService.h @@ -1,13 +1,10 @@ #pragma once +#include "Host/HostFwd.h" #include "SceneViewportRenderPassBundle.h" #include "ViewportObjectIdPicker.h" -#include "Scene/EditorSceneRuntime.h" #include "ViewportRenderTargets.h" -#include -#include - #include #include @@ -31,7 +28,7 @@ public: ViewportHostService(); ~ViewportHostService(); - void AttachWindowRenderer(Host::D3D12WindowRenderer& windowRenderer); + void AttachWindowRenderer(Host::ViewportRenderHost& windowRenderer); void DetachWindowRenderer(); void SetSurfacePresentationEnabled(bool enabled); void SetSceneViewportRenderRequest(SceneViewportRenderRequest request); @@ -91,9 +88,9 @@ private: const ViewportEntry& entry, const ::XCEngine::UI::UISize& requestedSize) const; - Host::D3D12WindowRenderer* m_windowRenderer = nullptr; + Host::ViewportRenderHost* m_windowRenderer = nullptr; ::XCEngine::RHI::RHIDevice* m_device = nullptr; - Host::D3D12ShaderResourceDescriptorAllocator m_textureDescriptorAllocator = {}; + std::unique_ptr m_textureDescriptorAllocator = {}; ViewportRenderTargetManager m_renderTargetManager = {}; bool m_surfacePresentationEnabled = false; SceneViewportRenderRequest m_sceneViewportRenderRequest = {}; diff --git a/new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp b/new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp index fc2022e8..104550bd 100644 --- a/new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp +++ b/new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp @@ -1,5 +1,7 @@ #include "Rendering/Viewport/ViewportRenderTargets.h" +#include "Host/ShaderResourceDescriptorAllocator.h" + namespace XCEngine::UI::Editor::App { namespace { @@ -111,7 +113,7 @@ bool CreateViewportSelectionMaskResources( } bool CreateViewportTextureDescriptor( - Host::D3D12ShaderResourceDescriptorAllocator& textureDescriptorAllocator, + Host::ShaderResourceDescriptorAllocator& textureDescriptorAllocator, ViewportRenderTargets& targets) { if (!textureDescriptorAllocator.CreateTextureDescriptor( targets.colorTexture, @@ -217,7 +219,7 @@ bool ViewportRenderTargetManager::EnsureTargets( std::uint32_t width, std::uint32_t height, ::XCEngine::RHI::RHIDevice& device, - Host::D3D12ShaderResourceDescriptorAllocator& textureDescriptorAllocator, + Host::ShaderResourceDescriptorAllocator& textureDescriptorAllocator, ViewportRenderTargets& targets) const { if (width == 0u || height == 0u) { return false; @@ -245,7 +247,7 @@ bool ViewportRenderTargetManager::EnsureTargets( } void ViewportRenderTargetManager::DestroyTargets( - Host::D3D12ShaderResourceDescriptorAllocator* textureDescriptorAllocator, + Host::ShaderResourceDescriptorAllocator* textureDescriptorAllocator, ViewportRenderTargets& targets) const { if (textureDescriptorAllocator != nullptr && targets.srvCpuHandle.ptr != 0) { textureDescriptorAllocator->Free(targets.srvCpuHandle, targets.srvGpuHandle); diff --git a/new_editor/app/Rendering/Viewport/ViewportRenderTargets.h b/new_editor/app/Rendering/Viewport/ViewportRenderTargets.h index 1b0deac3..c0944604 100644 --- a/new_editor/app/Rendering/Viewport/ViewportRenderTargets.h +++ b/new_editor/app/Rendering/Viewport/ViewportRenderTargets.h @@ -1,14 +1,19 @@ #pragma once +#include "Host/HostFwd.h" #include "ViewportRenderTargetInternal.h" -#include - #include #include #include #include +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include + namespace XCEngine::UI::Editor::App { struct ViewportRenderTargets { @@ -56,10 +61,10 @@ public: std::uint32_t width, std::uint32_t height, ::XCEngine::RHI::RHIDevice& device, - Host::D3D12ShaderResourceDescriptorAllocator& textureDescriptorAllocator, + Host::ShaderResourceDescriptorAllocator& textureDescriptorAllocator, ViewportRenderTargets& targets) const; void DestroyTargets( - Host::D3D12ShaderResourceDescriptorAllocator* textureDescriptorAllocator, + Host::ShaderResourceDescriptorAllocator* textureDescriptorAllocator, ViewportRenderTargets& targets) const; }; diff --git a/new_editor/app/State/EditorContext.cpp b/new_editor/app/State/EditorContext.cpp index d41a203b..0e54e9f8 100644 --- a/new_editor/app/State/EditorContext.cpp +++ b/new_editor/app/State/EditorContext.cpp @@ -4,7 +4,7 @@ #include "Scene/EditorSceneRuntime.h" #include "State/EditorSelectionStamp.h" -#include +#include "Composition/EditorPanelIds.h" #include #include diff --git a/new_editor/app/State/EditorContext.h b/new_editor/app/State/EditorContext.h index ca2e39e1..ff35605f 100644 --- a/new_editor/app/State/EditorContext.h +++ b/new_editor/app/State/EditorContext.h @@ -4,8 +4,8 @@ #include "Project/EditorProjectRuntime.h" #include "Scene/EditorSceneRuntime.h" -#include -#include +#include "Commands/EditorHostCommandBridge.h" +#include "State/EditorSession.h" #include #include #include diff --git a/new_editor/include/XCEditor/App/EditorSession.h b/new_editor/app/State/EditorSession.h similarity index 100% rename from new_editor/include/XCEditor/App/EditorSession.h rename to new_editor/app/State/EditorSession.h diff --git a/new_editor/include/XCEditor/Collections/UIEditorDragDropInteraction.h b/new_editor/include/XCEditor/Collections/UIEditorDragDropInteraction.h new file mode 100644 index 00000000..dd2a543d --- /dev/null +++ b/new_editor/include/XCEditor/Collections/UIEditorDragDropInteraction.h @@ -0,0 +1,242 @@ +#pragma once + +#include + +#include +#include + +namespace XCEngine::UI::Editor::Collections::DragDropInteraction { + +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIPointerButton; + +inline constexpr float kDefaultDragThreshold = 4.0f; + +struct State { + std::string armedItemId = {}; + std::string draggedItemId = {}; + std::string dropTargetItemId = {}; + UIPoint pressPosition = {}; + bool armed = false; + bool dragging = false; + bool validDropTarget = false; + bool requestPointerCapture = false; + bool requestPointerRelease = false; +}; + +struct PreviewState { + std::string armedItemId = {}; + UIPoint pressPosition = {}; + bool armed = false; + bool dragging = false; +}; + +struct ProcessResult { + bool selectionForced = false; + bool dropCommitted = false; + std::string draggedItemId = {}; + std::string dropTargetItemId = {}; +}; + +template +inline void ResetTransientRequests(StateLike& state) { + state.requestPointerCapture = false; + state.requestPointerRelease = false; +} + +template +inline bool HasActivePointerCapture(const StateLike& state) { + return state.dragging; +} + +inline float ComputeSquaredDistance(const UIPoint& lhs, const UIPoint& rhs) { + const float dx = lhs.x - rhs.x; + const float dy = lhs.y - rhs.y; + return dx * dx + dy * dy; +} + +template +inline PreviewState BuildPreviewState(const StateLike& state) { + PreviewState preview = {}; + preview.armedItemId = state.armedItemId; + preview.pressPosition = state.pressPosition; + preview.armed = state.armed; + preview.dragging = state.dragging; + return preview; +} + +template +inline bool ProcessPreviewEvent( + PreviewState& preview, + const UIInputEvent& event, + ResolveDraggableItem&& resolveDraggableItem, + float dragThreshold = kDefaultDragThreshold) { + bool suppress = false; + + switch (event.type) { + case UIInputEventType::PointerButtonDown: + if (event.pointerButton == UIPointerButton::Left) { + preview.armedItemId = resolveDraggableItem(event.position); + preview.pressPosition = event.position; + preview.armed = !preview.armedItemId.empty(); + if (!preview.armed) { + preview.armedItemId.clear(); + } + } + if (preview.dragging) { + suppress = true; + } + break; + + case UIInputEventType::PointerMove: + if (preview.dragging) { + suppress = true; + break; + } + + if (preview.armed && + ComputeSquaredDistance(event.position, preview.pressPosition) >= + dragThreshold * dragThreshold) { + preview.dragging = !preview.armedItemId.empty(); + suppress = preview.dragging; + } + break; + + case UIInputEventType::PointerButtonUp: + if (event.pointerButton == UIPointerButton::Left) { + if (preview.dragging) { + suppress = true; + preview.dragging = false; + } + preview.armed = false; + preview.armedItemId.clear(); + } else if (preview.dragging) { + suppress = true; + } + break; + + case UIInputEventType::PointerLeave: + if (preview.dragging) { + suppress = true; + } + break; + + case UIInputEventType::FocusLost: + preview.armed = false; + preview.dragging = false; + preview.armedItemId.clear(); + break; + + default: + if (preview.dragging && + (event.type == UIInputEventType::PointerWheel || + event.type == UIInputEventType::PointerEnter)) { + suppress = true; + } + break; + } + + return suppress; +} + +template +inline void ResetInteractionSession(StateLike& state, Callbacks& callbacks) { + state.armed = false; + state.dragging = false; + state.armedItemId.clear(); + state.draggedItemId.clear(); + callbacks.ResetDropTarget(state); +} + +template +ProcessResult ProcessInputEvents( + StateLike& state, + const std::vector& inputEvents, + Callbacks& callbacks, + float dragThreshold = kDefaultDragThreshold) { + ProcessResult result = {}; + + for (const UIInputEvent& event : inputEvents) { + switch (event.type) { + case UIInputEventType::PointerButtonDown: + if (event.pointerButton == UIPointerButton::Left) { + state.draggedItemId.clear(); + callbacks.ResetDropTarget(state); + state.armedItemId = callbacks.ResolveDraggableItem(event.position); + state.pressPosition = event.position; + state.armed = !state.armedItemId.empty(); + if (!state.armed) { + state.armedItemId.clear(); + } + } + break; + + case UIInputEventType::PointerMove: + if (state.armed && + !state.dragging && + ComputeSquaredDistance(event.position, state.pressPosition) >= + dragThreshold * dragThreshold) { + state.dragging = !state.armedItemId.empty(); + state.draggedItemId = state.armedItemId; + callbacks.ResetDropTarget(state); + if (state.dragging) { + state.requestPointerCapture = true; + if (!callbacks.IsItemSelected(state.draggedItemId)) { + result.selectionForced = callbacks.SelectDraggedItem(state.draggedItemId); + } + } + } + + if (state.dragging) { + callbacks.ResetDropTarget(state); + callbacks.UpdateDropTarget(state, state.draggedItemId, event.position); + } + break; + + case UIInputEventType::PointerButtonUp: + if (event.pointerButton != UIPointerButton::Left) { + break; + } + + if (state.dragging) { + if (state.validDropTarget) { + result.draggedItemId = state.draggedItemId; + result.dropTargetItemId = state.dropTargetItemId; + result.dropCommitted = callbacks.CommitDrop(state, result); + } + + ResetInteractionSession(state, callbacks); + state.requestPointerRelease = true; + } else { + state.armed = false; + state.armedItemId.clear(); + } + break; + + case UIInputEventType::PointerLeave: + if (state.dragging) { + callbacks.ResetDropTarget(state); + } + break; + + case UIInputEventType::FocusLost: + { + const bool requestPointerRelease = state.dragging; + ResetInteractionSession(state, callbacks); + state.pressPosition = {}; + state.requestPointerCapture = false; + state.requestPointerRelease = requestPointerRelease; + } + break; + + default: + break; + } + } + + return result; +} + +} // namespace XCEngine::UI::Editor::Collections::DragDropInteraction diff --git a/new_editor/include/XCEditor/Collections/UIEditorGridDragDrop.h b/new_editor/include/XCEditor/Collections/UIEditorGridDragDrop.h new file mode 100644 index 00000000..b5a7ed9f --- /dev/null +++ b/new_editor/include/XCEditor/Collections/UIEditorGridDragDrop.h @@ -0,0 +1,73 @@ +#pragma once + +#include + +#include + +namespace XCEngine::UI::Editor::Collections::GridDragDrop { + +using ::XCEngine::UI::UIPoint; +using DragDropInteraction::kDefaultDragThreshold; +using DragDropInteraction::ProcessResult; +using DragDropInteraction::State; + +inline void ResetTransientRequests(State& state) { + DragDropInteraction::ResetTransientRequests(state); +} + +inline bool HasActivePointerCapture(const State& state) { + return DragDropInteraction::HasActivePointerCapture(state); +} + +template +ProcessResult ProcessInputEvents( + State& state, + const std::vector& inputEvents, + Callbacks& callbacks, + float dragThreshold = kDefaultDragThreshold) { + struct AdaptedCallbacks { + Callbacks& callbacks; + + std::string ResolveDraggableItem(const UIPoint& point) const { + return callbacks.ResolveDraggableItem(point); + } + + void ResetDropTarget(State& state) const { + state.dropTargetItemId.clear(); + state.validDropTarget = false; + } + + void UpdateDropTarget( + State& state, + std::string_view draggedItemId, + const UIPoint& point) const { + state.dropTargetItemId = + callbacks.ResolveDropTargetItem(draggedItemId, point); + state.validDropTarget = + !state.dropTargetItemId.empty() && + callbacks.CanDropOnItem(draggedItemId, state.dropTargetItemId); + } + + bool IsItemSelected(std::string_view itemId) const { + return callbacks.IsItemSelected(itemId); + } + + bool SelectDraggedItem(std::string_view itemId) const { + return callbacks.SelectDraggedItem(itemId); + } + + bool CommitDrop(State& state, ProcessResult&) const { + return callbacks.CommitDropOnItem( + state.draggedItemId, + state.dropTargetItemId); + } + } adaptedCallbacks{ callbacks }; + + return DragDropInteraction::ProcessInputEvents( + state, + inputEvents, + adaptedCallbacks, + dragThreshold); +} + +} // namespace XCEngine::UI::Editor::Collections::GridDragDrop diff --git a/new_editor/include/XCEditor/Collections/UIEditorTreeDragDrop.h b/new_editor/include/XCEditor/Collections/UIEditorTreeDragDrop.h new file mode 100644 index 00000000..ada20cae --- /dev/null +++ b/new_editor/include/XCEditor/Collections/UIEditorTreeDragDrop.h @@ -0,0 +1,201 @@ +#pragma once + +#include +#include + +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor::Collections::TreeDragDrop { + +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; +using DragDropInteraction::kDefaultDragThreshold; +using Widgets::HitTestUIEditorTreeView; +using Widgets::UIEditorTreeViewHitTarget; +using Widgets::UIEditorTreeViewHitTargetKind; +using Widgets::UIEditorTreeViewInvalidIndex; + +struct State : DragDropInteraction::State { + bool dropToRoot = false; +}; + +struct ProcessResult : DragDropInteraction::ProcessResult { + bool droppedToRoot = false; +}; + +inline void ResetTransientRequests(State& state) { + DragDropInteraction::ResetTransientRequests(state); +} + +inline bool HasActivePointerCapture(const State& state) { + return DragDropInteraction::HasActivePointerCapture(state); +} + +inline 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; +} + +inline const Widgets::UIEditorTreeViewItem* ResolveHitItem( + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + const UIPoint& point, + UIEditorTreeViewHitTarget* hitTargetOutput = nullptr) { + const UIEditorTreeViewHitTarget hitTarget = HitTestUIEditorTreeView(layout, point); + if (hitTargetOutput != nullptr) { + *hitTargetOutput = hitTarget; + } + + if (hitTarget.itemIndex >= items.size()) { + return nullptr; + } + + return &items[hitTarget.itemIndex]; +} + +inline std::size_t FindVisibleIndexForItemId( + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::string_view itemId) { + for (std::size_t visibleIndex = 0u; visibleIndex < layout.visibleItemIndices.size(); ++visibleIndex) { + const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex]; + if (itemIndex < items.size() && items[itemIndex].itemId == itemId) { + return visibleIndex; + } + } + + return UIEditorTreeViewInvalidIndex; +} + +inline std::vector BuildInteractionInputEvents( + const State& state, + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + const std::vector& rawEvents, + float dragThreshold = kDefaultDragThreshold) { + auto resolveDraggableItem = + [&layout, &items](const UIPoint& point) -> std::string { + UIEditorTreeViewHitTarget hitTarget = {}; + const Widgets::UIEditorTreeViewItem* hitItem = + ResolveHitItem(layout, items, point, &hitTarget); + if (hitItem != nullptr && + hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { + return hitItem->itemId; + } + + return {}; + }; + + DragDropInteraction::PreviewState preview = + DragDropInteraction::BuildPreviewState(state); + std::vector filteredEvents = {}; + filteredEvents.reserve(rawEvents.size()); + for (const DragDropInteraction::UIInputEvent& event : rawEvents) { + if (!DragDropInteraction::ProcessPreviewEvent( + preview, + event, + resolveDraggableItem, + dragThreshold)) { + filteredEvents.push_back(event); + } + } + + return filteredEvents; +} + +template +ProcessResult ProcessInputEvents( + State& state, + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + const std::vector& inputEvents, + const UIRect& bounds, + Callbacks& callbacks, + float dragThreshold = kDefaultDragThreshold) { + struct AdaptedCallbacks { + const Widgets::UIEditorTreeViewLayout& layout; + const std::vector& items; + const UIRect& bounds; + Callbacks& callbacks; + bool droppedToRoot = false; + + std::string ResolveDraggableItem(const UIPoint& point) const { + UIEditorTreeViewHitTarget hitTarget = {}; + const Widgets::UIEditorTreeViewItem* hitItem = + ResolveHitItem(layout, items, point, &hitTarget); + if (hitItem != nullptr && + hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { + return hitItem->itemId; + } + + return {}; + } + + void ResetDropTarget(State& state) const { + state.dropTargetItemId.clear(); + state.dropToRoot = false; + state.validDropTarget = false; + } + + void UpdateDropTarget( + State& state, + std::string_view draggedItemId, + const UIPoint& point) const { + UIEditorTreeViewHitTarget hitTarget = {}; + const Widgets::UIEditorTreeViewItem* hitItem = + ResolveHitItem(layout, items, point, &hitTarget); + if (hitItem != nullptr && + (hitTarget.kind == UIEditorTreeViewHitTargetKind::Row || + hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure)) { + state.dropTargetItemId = hitItem->itemId; + state.validDropTarget = + callbacks.CanDropOnItem(draggedItemId, state.dropTargetItemId); + return; + } + + if (ContainsPoint(bounds, point)) { + state.dropToRoot = true; + state.validDropTarget = callbacks.CanDropToRoot(draggedItemId); + } + } + + bool IsItemSelected(std::string_view itemId) const { + return callbacks.IsItemSelected(itemId); + } + + bool SelectDraggedItem(std::string_view itemId) const { + return callbacks.SelectDraggedItem(itemId); + } + + bool CommitDrop(State& state, DragDropInteraction::ProcessResult&) { + droppedToRoot = state.dropToRoot; + return state.dropToRoot + ? callbacks.CommitDropToRoot(state.draggedItemId) + : callbacks.CommitDropOnItem( + state.draggedItemId, + state.dropTargetItemId); + } + } adaptedCallbacks{ layout, items, bounds, callbacks }; + + ProcessResult result = {}; + const DragDropInteraction::ProcessResult interactionResult = + DragDropInteraction::ProcessInputEvents( + state, + inputEvents, + adaptedCallbacks, + dragThreshold); + result.selectionForced = interactionResult.selectionForced; + result.dropCommitted = interactionResult.dropCommitted; + result.draggedItemId = interactionResult.draggedItemId; + result.dropTargetItemId = interactionResult.dropTargetItemId; + result.droppedToRoot = adaptedCallbacks.droppedToRoot; + return result; +} + +} // namespace XCEngine::UI::Editor::Collections::TreeDragDrop diff --git a/new_editor/include/XCEditor/Panels/UIEditorPanelRegistry.h b/new_editor/include/XCEditor/Panels/UIEditorPanelRegistry.h index c32fc918..e5b61021 100644 --- a/new_editor/include/XCEditor/Panels/UIEditorPanelRegistry.h +++ b/new_editor/include/XCEditor/Panels/UIEditorPanelRegistry.h @@ -45,8 +45,6 @@ struct UIEditorPanelRegistryValidationResult { } }; -UIEditorPanelRegistry BuildEditorFoundationPanelRegistry(); - const UIEditorPanelDescriptor* FindUIEditorPanelDescriptor( const UIEditorPanelRegistry& registry, std::string_view panelId); diff --git a/new_editor/include/XCEditor/Shell/UIEditorShellAsset.h b/new_editor/include/XCEditor/Shell/UIEditorShellAsset.h index ce278d46..69be987e 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorShellAsset.h +++ b/new_editor/include/XCEditor/Shell/UIEditorShellAsset.h @@ -20,7 +20,7 @@ struct EditorShellShortcutAsset { }; struct EditorShellAsset { - std::string screenId = "editor.shell"; + std::string screenId = {}; std::filesystem::path documentPath = {}; std::filesystem::path captureRootPath = {}; UIEditorPanelRegistry panelRegistry = {}; @@ -55,7 +55,6 @@ struct EditorShellAssetValidationResult { } }; -EditorShellAsset BuildEditorFoundationShellAsset(const std::filesystem::path& repoRoot); UIEditorShortcutManager BuildEditorShellShortcutManager(const EditorShellAsset& asset); EditorShellAssetValidationResult ValidateEditorShellAsset(const EditorShellAsset& asset); diff --git a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceModel.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceModel.h index 7153584a..bd7f8573 100644 --- a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceModel.h +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceModel.h @@ -57,8 +57,6 @@ struct UIEditorWorkspaceVisiblePanel { bool placeholder = false; }; -UIEditorWorkspaceModel BuildEditorFoundationWorkspaceModel(); - UIEditorWorkspaceNode BuildUIEditorWorkspacePanel( std::string nodeId, std::string panelId, diff --git a/new_editor/src/App/EditorHostCommandBridge.cpp b/new_editor/src/App/EditorHostCommandBridge.cpp index 456a170b..4c95b802 100644 --- a/new_editor/src/App/EditorHostCommandBridge.cpp +++ b/new_editor/src/App/EditorHostCommandBridge.cpp @@ -1,4 +1,4 @@ -#include +#include "Commands/EditorHostCommandBridge.h" #include #include diff --git a/new_editor/src/App/EditorSession.cpp b/new_editor/src/App/EditorSession.cpp index b5a2a2dc..080e976a 100644 --- a/new_editor/src/App/EditorSession.cpp +++ b/new_editor/src/App/EditorSession.cpp @@ -1,5 +1,6 @@ -#include -#include +#include "State/EditorSession.h" + +#include "Composition/EditorPanelIds.h" #include diff --git a/new_editor/src/Panels/UIEditorPanelRegistry.cpp b/new_editor/src/Panels/UIEditorPanelRegistry.cpp index c907efb7..7b4a2805 100644 --- a/new_editor/src/Panels/UIEditorPanelRegistry.cpp +++ b/new_editor/src/Panels/UIEditorPanelRegistry.cpp @@ -18,21 +18,6 @@ UIEditorPanelRegistryValidationResult MakeValidationError( } // namespace -UIEditorPanelRegistry BuildEditorFoundationPanelRegistry() { - UIEditorPanelRegistry registry = {}; - registry.panels = { - { - "editor-foundation-root", - "Root Surface", - UIEditorPanelPresentationKind::Placeholder, - true, - false, - false - } - }; - return registry; -} - const UIEditorPanelDescriptor* FindUIEditorPanelDescriptor( const UIEditorPanelRegistry& registry, std::string_view panelId) { diff --git a/new_editor/src/Shell/UIEditorShellAsset.cpp b/new_editor/src/Shell/UIEditorShellAsset.cpp index c677db05..cc06e67e 100644 --- a/new_editor/src/Shell/UIEditorShellAsset.cpp +++ b/new_editor/src/Shell/UIEditorShellAsset.cpp @@ -8,31 +8,6 @@ namespace XCEngine::UI::Editor { namespace { -Widgets::UIEditorStatusBarSegment BuildDefaultShellModeSegment() { - Widgets::UIEditorStatusBarSegment segment = {}; - segment.segmentId = "mode"; - segment.label = "Editor Shell"; - segment.slot = Widgets::UIEditorStatusBarSlot::Leading; - segment.tone = Widgets::UIEditorStatusBarTextTone::Primary; - segment.interactive = false; - segment.showSeparator = true; - segment.desiredWidth = 112.0f; - return segment; -} - -Widgets::UIEditorStatusBarSegment BuildDefaultActivePanelSegment( - const UIEditorWorkspaceModel& workspace) { - Widgets::UIEditorStatusBarSegment segment = {}; - segment.segmentId = "active-panel"; - segment.label = workspace.activePanelId.empty() ? std::string("(none)") : workspace.activePanelId; - segment.slot = Widgets::UIEditorStatusBarSlot::Trailing; - segment.tone = Widgets::UIEditorStatusBarTextTone::Muted; - segment.interactive = false; - segment.showSeparator = false; - segment.desiredWidth = 144.0f; - return segment; -} - EditorShellAssetValidationResult MakeValidationError( EditorShellAssetValidationCode code, std::string message) { @@ -122,51 +97,8 @@ EditorShellAssetValidationResult ValidateShellDefinitionAgainstRegistry( return {}; } -UIEditorWorkspacePanelPresentationModel BuildShellPresentation( - const UIEditorPanelDescriptor& descriptor) { - UIEditorWorkspacePanelPresentationModel presentation = {}; - presentation.panelId = descriptor.panelId; - presentation.kind = descriptor.presentationKind; - if (descriptor.presentationKind == UIEditorPanelPresentationKind::ViewportShell) { - presentation.viewportShellModel.spec = descriptor.viewportShellSpec; - if (presentation.viewportShellModel.spec.chrome.title.empty()) { - presentation.viewportShellModel.spec.chrome.title = descriptor.defaultTitle; - } - if (presentation.viewportShellModel.spec.chrome.subtitle.empty()) { - presentation.viewportShellModel.spec.chrome.subtitle = "Editor Shell"; - } - presentation.viewportShellModel.frame.statusText = descriptor.defaultTitle; - } - return presentation; -} - -UIEditorShellInteractionDefinition BuildFoundationShellDefinition( - const UIEditorPanelRegistry& panelRegistry, - const UIEditorWorkspaceModel& workspace) { - UIEditorShellInteractionDefinition definition = {}; - definition.statusSegments = { - BuildDefaultShellModeSegment(), - BuildDefaultActivePanelSegment(workspace) - }; - definition.workspacePresentations.reserve(panelRegistry.panels.size()); - for (const UIEditorPanelDescriptor& descriptor : panelRegistry.panels) { - definition.workspacePresentations.push_back(BuildShellPresentation(descriptor)); - } - return definition; -} - } // namespace -EditorShellAsset BuildEditorFoundationShellAsset(const std::filesystem::path& repoRoot) { - EditorShellAsset asset = {}; - asset.captureRootPath = (repoRoot / "new_editor/captures").lexically_normal(); - asset.panelRegistry = BuildEditorFoundationPanelRegistry(); - asset.workspace = BuildEditorFoundationWorkspaceModel(); - asset.workspaceSession = BuildDefaultUIEditorWorkspaceSession(asset.panelRegistry, asset.workspace); - asset.shellDefinition = BuildFoundationShellDefinition(asset.panelRegistry, asset.workspace); - return asset; -} - UIEditorShortcutManager BuildEditorShellShortcutManager(const EditorShellAsset& asset) { UIEditorShortcutManager manager(asset.shortcutAsset.commandRegistry); for (const XCEngine::UI::UIShortcutBinding& binding : asset.shortcutAsset.bindings) { diff --git a/new_editor/src/Workspace/UIEditorWorkspaceModel.cpp b/new_editor/src/Workspace/UIEditorWorkspaceModel.cpp index f4353780..994ff0e8 100644 --- a/new_editor/src/Workspace/UIEditorWorkspaceModel.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceModel.cpp @@ -599,21 +599,6 @@ bool AreUIEditorWorkspaceModelsEquivalent( AreUIEditorWorkspaceNodesEquivalent(lhs.root, rhs.root); } -UIEditorWorkspaceModel BuildEditorFoundationWorkspaceModel() { - const UIEditorPanelRegistry registry = BuildEditorFoundationPanelRegistry(); - const UIEditorPanelDescriptor& rootPanel = - Internal::RequirePanelDescriptor(registry, "editor-foundation-root"); - - UIEditorWorkspaceModel workspace = {}; - workspace.root = BuildUIEditorWorkspaceSingleTabStack( - "editor-foundation-root-node", - rootPanel.panelId, - rootPanel.defaultTitle, - rootPanel.placeholder); - workspace.activePanelId = rootPanel.panelId; - return workspace; -} - UIEditorWorkspaceNode BuildUIEditorWorkspacePanel( std::string nodeId, std::string panelId, 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 f5801208..b36ed4ba 100644 --- a/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp +++ b/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp @@ -46,7 +46,6 @@ using XCEngine::UI::UIPoint; using XCEngine::UI::UIPointerButton; using XCEngine::UI::UIRect; using XCEngine::UI::Editor::AppendUIEditorShellInteraction; -using XCEngine::UI::Editor::BuildEditorFoundationShellAsset; using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceSession; using XCEngine::UI::Editor::BuildEditorShellShortcutManager; using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceController; @@ -407,7 +406,9 @@ UIEditorMenuModel BuildMenuModel() { } EditorShellAsset BuildScenarioShellAsset() { - EditorShellAsset asset = BuildEditorFoundationShellAsset(ResolveRepoRootPath()); + EditorShellAsset asset = {}; + asset.captureRootPath = + (ResolveRepoRootPath() / "new_editor/captures").lexically_normal(); asset.panelRegistry = BuildPanelRegistry(); asset.workspace = BuildWorkspace(); asset.workspaceSession = diff --git a/tests/UI/Editor/unit/CMakeLists.txt b/tests/UI/Editor/unit/CMakeLists.txt index d169fcec..4e03cacd 100644 --- a/tests/UI/Editor/unit/CMakeLists.txt +++ b/tests/UI/Editor/unit/CMakeLists.txt @@ -1,8 +1,5 @@ set(EDITOR_UI_UNIT_TEST_SOURCES - test_editor_host_command_bridge.cpp - test_editor_shell_asset_validation.cpp test_input_modifier_tracker.cpp - test_structured_editor_shell.cpp test_ui_editor_command_dispatcher.cpp test_ui_editor_command_registry.cpp test_ui_editor_dock_host_interaction.cpp @@ -12,13 +9,12 @@ set(EDITOR_UI_UNIT_TEST_SOURCES test_ui_editor_menu_popup.cpp test_ui_editor_panel_content_host.cpp test_ui_editor_panel_host_lifecycle.cpp - test_ui_editor_panel_registry.cpp test_ui_editor_property_grid.cpp test_ui_editor_property_grid_interaction.cpp test_ui_editor_shell_compose.cpp - test_editor_window_input_routing.cpp test_ui_editor_shell_interaction.cpp test_ui_editor_collection_primitives.cpp + test_ui_editor_drag_drop.cpp test_ui_editor_field_row_layout.cpp test_ui_editor_hosted_field_builders.cpp test_ui_editor_bool_field.cpp @@ -53,7 +49,6 @@ set(EDITOR_UI_UNIT_TEST_SOURCES test_ui_editor_tab_strip_interaction.cpp test_ui_editor_tree_view.cpp test_ui_editor_tree_view_interaction.cpp - test_viewport_object_id_picker.cpp test_ui_editor_viewport_input_bridge.cpp test_ui_editor_viewport_shell.cpp test_ui_editor_viewport_slot.cpp @@ -79,7 +74,6 @@ target_link_libraries(editor_ui_tests target_include_directories(editor_ui_tests PRIVATE ${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/include - ${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT} ${CMAKE_SOURCE_DIR}/engine/include ) @@ -119,6 +113,15 @@ if(TARGET XCUIEditorAppLib) ) endif() + list(APPEND EDITOR_APP_FEATURE_TEST_SOURCES + test_editor_host_command_bridge.cpp + test_editor_shell_asset_validation.cpp + test_structured_editor_shell.cpp + test_editor_window_input_routing.cpp + test_ui_editor_panel_registry.cpp + test_viewport_object_id_picker.cpp + ) + add_executable(editor_app_feature_tests ${EDITOR_APP_FEATURE_TEST_SOURCES} ) @@ -126,6 +129,7 @@ if(TARGET XCUIEditorAppLib) target_link_libraries(editor_app_feature_tests PRIVATE XCUIEditorAppLib + XCUIEditorAppCore XCUIEditorLib XCUIEditorHost GTest::gtest_main diff --git a/tests/UI/Editor/unit/test_editor_host_command_bridge.cpp b/tests/UI/Editor/unit/test_editor_host_command_bridge.cpp index cc0b7967..d5a5ed99 100644 --- a/tests/UI/Editor/unit/test_editor_host_command_bridge.cpp +++ b/tests/UI/Editor/unit/test_editor_host_command_bridge.cpp @@ -1,8 +1,8 @@ #include -#include -#include -#include +#include "Commands/EditorEditCommandRoute.h" +#include "Commands/EditorHostCommandBridge.h" +#include "State/EditorSession.h" namespace { 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 9917704a..d5468ee6 100644 --- a/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp +++ b/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp @@ -1,15 +1,15 @@ #include -#include +#include "Composition/EditorShellAssetBuilder.h" -#include +#include #include namespace { using XCEngine::Input::KeyCode; -using XCEngine::UI::Editor::BuildEditorFoundationShellAsset; +using XCEngine::UI::Editor::App::BuildEditorApplicationShellAsset; using XCEngine::UI::Editor::EditorShellAssetValidationCode; using XCEngine::UI::Editor::FindUIEditorPanelDescriptor; using XCEngine::UI::Editor::UIEditorCommandPanelSource; @@ -20,6 +20,23 @@ using XCEngine::UI::UIInputEventType; using XCEngine::UI::UIShortcutBinding; using XCEngine::UI::UIShortcutScope; +XCEngine::UI::Editor::UIEditorWorkspaceNode* FindWorkspacePanelNode( + XCEngine::UI::Editor::UIEditorWorkspaceNode& node, + std::string_view panelId) { + if (node.kind == XCEngine::UI::Editor::UIEditorWorkspaceNodeKind::Panel && + node.panel.panelId == panelId) { + return &node; + } + + for (auto& child : node.children) { + if (auto* panel = FindWorkspacePanelNode(child, panelId); panel != nullptr) { + return panel; + } + } + + return nullptr; +} + UIShortcutBinding MakeBinding(std::string commandId, KeyCode keyCode) { UIShortcutBinding binding = {}; binding.commandId = std::move(commandId); @@ -31,7 +48,7 @@ UIShortcutBinding MakeBinding(std::string commandId, KeyCode keyCode) { } TEST(EditorShellAssetValidationTest, DefaultShellAssetPassesValidation) { - const auto shellAsset = BuildEditorFoundationShellAsset("."); + const auto shellAsset = BuildEditorApplicationShellAsset("."); const auto validation = ValidateEditorShellAsset(shellAsset); EXPECT_TRUE(validation.IsValid()) << validation.message; @@ -39,25 +56,24 @@ TEST(EditorShellAssetValidationTest, DefaultShellAssetPassesValidation) { ASSERT_EQ( shellAsset.shellDefinition.workspacePresentations.size(), shellAsset.panelRegistry.panels.size()); - ASSERT_EQ(shellAsset.shellDefinition.statusSegments.size(), 2u); - EXPECT_EQ(shellAsset.shellDefinition.statusSegments.front().label, "Editor Shell"); - EXPECT_EQ(shellAsset.shellDefinition.statusSegments.back().label, "editor-foundation-root"); EXPECT_EQ( shellAsset.shellDefinition.workspacePresentations.front().panelId, - "editor-foundation-root"); + shellAsset.panelRegistry.panels.front().panelId); EXPECT_EQ( shellAsset.shellDefinition.workspacePresentations.front().kind, shellAsset.panelRegistry.panels.front().presentationKind); } TEST(EditorShellAssetValidationTest, ValidationRejectsWorkspacePanelMissingFromRegistry) { - auto shellAsset = BuildEditorFoundationShellAsset("."); + auto shellAsset = BuildEditorApplicationShellAsset("."); auto* documentPanel = const_cast( - FindUIEditorPanelDescriptor(shellAsset.panelRegistry, "editor-foundation-root")); + FindUIEditorPanelDescriptor( + shellAsset.panelRegistry, + shellAsset.panelRegistry.panels.front().panelId)); ASSERT_NE(documentPanel, nullptr); - documentPanel->panelId = "editor-foundation-root-renamed"; + documentPanel->panelId = "renamed-panel"; const auto validation = ValidateEditorShellAsset(shellAsset); EXPECT_EQ(validation.code, EditorShellAssetValidationCode::MissingPanelDescriptor) @@ -65,24 +81,19 @@ TEST(EditorShellAssetValidationTest, ValidationRejectsWorkspacePanelMissingFromR } TEST(EditorShellAssetValidationTest, ValidationRejectsWorkspaceTitleDriftFromRegistry) { - auto shellAsset = BuildEditorFoundationShellAsset("."); - shellAsset.workspace.activePanelId = "editor-foundation-root"; - ASSERT_EQ( - shellAsset.workspace.root.kind, - XCEngine::UI::Editor::UIEditorWorkspaceNodeKind::TabStack); - ASSERT_EQ(shellAsset.workspace.root.children.size(), 1u); - ASSERT_EQ( - shellAsset.workspace.root.children.front().kind, - XCEngine::UI::Editor::UIEditorWorkspaceNodeKind::Panel); - shellAsset.workspace.root.children.front().panel.title = "Drifted Title"; + auto shellAsset = BuildEditorApplicationShellAsset("."); + auto* scenePanel = FindWorkspacePanelNode(shellAsset.workspace.root, "scene"); + ASSERT_NE(scenePanel, nullptr); + shellAsset.workspace.activePanelId = scenePanel->panel.panelId; + scenePanel->panel.title = "Drifted Title"; const auto validation = ValidateEditorShellAsset(shellAsset); EXPECT_EQ(validation.code, EditorShellAssetValidationCode::PanelTitleMismatch); } TEST(EditorShellAssetValidationTest, ValidationRejectsInvalidWorkspaceSessionState) { - auto shellAsset = BuildEditorFoundationShellAsset("."); - ASSERT_EQ(shellAsset.workspaceSession.panelStates.size(), 1u); + auto shellAsset = BuildEditorApplicationShellAsset("."); + ASSERT_FALSE(shellAsset.workspaceSession.panelStates.empty()); shellAsset.workspaceSession.panelStates.front().open = false; const auto validation = ValidateEditorShellAsset(shellAsset); @@ -90,12 +101,12 @@ TEST(EditorShellAssetValidationTest, ValidationRejectsInvalidWorkspaceSessionSta } TEST(EditorShellAssetValidationTest, ValidationRejectsShellPresentationMissingFromRegistry) { - auto shellAsset = BuildEditorFoundationShellAsset("."); + auto shellAsset = BuildEditorApplicationShellAsset("."); ASSERT_EQ( shellAsset.shellDefinition.workspacePresentations.size(), shellAsset.panelRegistry.panels.size()); shellAsset.shellDefinition.workspacePresentations.front().panelId = - "editor-foundation-root-renamed"; + "renamed-panel"; const auto validation = ValidateEditorShellAsset(shellAsset); EXPECT_EQ( @@ -104,7 +115,7 @@ TEST(EditorShellAssetValidationTest, ValidationRejectsShellPresentationMissingFr } TEST(EditorShellAssetValidationTest, ValidationRejectsDuplicateShellPresentationPanelId) { - auto shellAsset = BuildEditorFoundationShellAsset("."); + auto shellAsset = BuildEditorApplicationShellAsset("."); ASSERT_EQ( shellAsset.shellDefinition.workspacePresentations.size(), shellAsset.panelRegistry.panels.size()); @@ -118,7 +129,7 @@ TEST(EditorShellAssetValidationTest, ValidationRejectsDuplicateShellPresentation } TEST(EditorShellAssetValidationTest, ValidationRejectsMissingRequiredShellPresentation) { - auto shellAsset = BuildEditorFoundationShellAsset("."); + auto shellAsset = BuildEditorApplicationShellAsset("."); shellAsset.shellDefinition.workspacePresentations.clear(); const auto validation = ValidateEditorShellAsset(shellAsset); @@ -128,7 +139,7 @@ TEST(EditorShellAssetValidationTest, ValidationRejectsMissingRequiredShellPresen } TEST(EditorShellAssetValidationTest, ValidationRejectsInvalidShellMenuModel) { - auto shellAsset = BuildEditorFoundationShellAsset("."); + auto shellAsset = BuildEditorApplicationShellAsset("."); shellAsset.shellDefinition.menuModel.menus = { { "window", @@ -153,17 +164,9 @@ TEST(EditorShellAssetValidationTest, ValidationRejectsInvalidShellMenuModel) { } TEST(EditorShellAssetValidationTest, ValidationRejectsInvalidShortcutConfiguration) { - auto shellAsset = BuildEditorFoundationShellAsset("."); - shellAsset.shortcutAsset.commandRegistry.commands = { - { - "workspace.reset_layout", - "Reset Layout", - { UIEditorWorkspaceCommandKind::ResetWorkspace, UIEditorCommandPanelSource::None, {} } - } - }; - shellAsset.shortcutAsset.bindings = { - MakeBinding("missing.command", KeyCode::R) - }; + auto shellAsset = BuildEditorApplicationShellAsset("."); + shellAsset.shortcutAsset.bindings.push_back( + MakeBinding("missing.command", KeyCode::R)); const auto validation = ValidateEditorShellAsset(shellAsset); EXPECT_EQ( @@ -172,7 +175,7 @@ TEST(EditorShellAssetValidationTest, ValidationRejectsInvalidShortcutConfigurati } TEST(EditorShellAssetValidationTest, ValidationRejectsShellPresentationKindMismatch) { - auto shellAsset = BuildEditorFoundationShellAsset("."); + auto shellAsset = BuildEditorApplicationShellAsset("."); ASSERT_EQ( shellAsset.shellDefinition.workspacePresentations.size(), shellAsset.panelRegistry.panels.size()); diff --git a/tests/UI/Editor/unit/test_project_panel.cpp b/tests/UI/Editor/unit/test_project_panel.cpp index 064acb03..cfb0b14e 100644 --- a/tests/UI/Editor/unit/test_project_panel.cpp +++ b/tests/UI/Editor/unit/test_project_panel.cpp @@ -1,7 +1,7 @@ #include "Features/Project/ProjectPanel.h" #include "Rendering/Assets/BuiltInIcons.h" -#include +#include "Composition/EditorPanelIds.h" #include diff --git a/tests/UI/Editor/unit/test_scene_viewport_runtime.cpp b/tests/UI/Editor/unit/test_scene_viewport_runtime.cpp index d408bbf0..fc6b616d 100644 --- a/tests/UI/Editor/unit/test_scene_viewport_runtime.cpp +++ b/tests/UI/Editor/unit/test_scene_viewport_runtime.cpp @@ -4,7 +4,7 @@ #include "Rendering/Viewport/ViewportHostService.h" #include "State/EditorSelectionStamp.h" -#include +#include "Composition/EditorPanelIds.h" #include #include diff --git a/tests/UI/Editor/unit/test_structured_editor_shell.cpp b/tests/UI/Editor/unit/test_structured_editor_shell.cpp index 20285f0a..80fa7740 100644 --- a/tests/UI/Editor/unit/test_structured_editor_shell.cpp +++ b/tests/UI/Editor/unit/test_structured_editor_shell.cpp @@ -1,5 +1,7 @@ #include +#include "Composition/EditorShellAssetBuilder.h" + #include #include #include @@ -17,7 +19,7 @@ namespace { using XCEngine::Input::KeyCode; -using XCEngine::UI::Editor::BuildEditorFoundationShellAsset; +using XCEngine::UI::Editor::App::BuildEditorApplicationShellAsset; using XCEngine::UI::Editor::BuildStructuredEditorShellBinding; using XCEngine::UI::Editor::BuildStructuredEditorShellServices; using XCEngine::UI::Editor::ResolveUIEditorShellInteractionModel; @@ -124,7 +126,7 @@ UIShortcutBinding MakeBinding(std::string commandId, KeyCode keyCode) { } // namespace TEST(EditorUIStructuredShellTest, StructuredEditorShellDoesNotRequireRepositoryXCUIDocument) { - const auto shell = BuildEditorFoundationShellAsset(RepoRootPath()); + const auto shell = BuildEditorApplicationShellAsset(RepoRootPath()); const auto binding = BuildStructuredEditorShellBinding(shell); ASSERT_TRUE(binding.IsValid()) << binding.assetValidation.message; @@ -134,8 +136,7 @@ TEST(EditorUIStructuredShellTest, StructuredEditorShellDoesNotRequireRepositoryX } TEST(EditorUIStructuredShellTest, StructuredShellBindingUsesEditorShellAssetAsSingleSource) { - auto shell = BuildEditorFoundationShellAsset(RepoRootPath()); - shell.shellDefinition.statusSegments.front().label = "Asset Contract"; + auto shell = BuildEditorApplicationShellAsset(RepoRootPath()); shell.shortcutAsset.commandRegistry.commands = { { "workspace.reset_layout", @@ -187,7 +188,7 @@ TEST(EditorUIStructuredShellTest, StructuredShellBindingUsesEditorShellAssetAsSi EXPECT_EQ(model.resolvedMenuModel.menus.front().items.front().label, "Reset Layout"); EXPECT_EQ(model.resolvedMenuModel.menus.front().items.front().shortcutText, "Ctrl+R"); ASSERT_EQ(model.statusSegments.size(), shell.shellDefinition.statusSegments.size()); - EXPECT_EQ(model.statusSegments.front().label, shell.shellDefinition.statusSegments.front().label); + ASSERT_EQ(model.statusSegments.size(), shell.shellDefinition.statusSegments.size()); ASSERT_EQ( model.workspacePresentations.size(), shell.shellDefinition.workspacePresentations.size()); diff --git a/tests/UI/Editor/unit/test_ui_editor_drag_drop.cpp b/tests/UI/Editor/unit/test_ui_editor_drag_drop.cpp new file mode 100644 index 00000000..5739484a --- /dev/null +++ b/tests/UI/Editor/unit/test_ui_editor_drag_drop.cpp @@ -0,0 +1,286 @@ +#include + +#include +#include + +#include + +#include +#include +#include + +namespace { + +namespace GridDrag = XCEngine::UI::Editor::Collections::GridDragDrop; +namespace TreeDrag = XCEngine::UI::Editor::Collections::TreeDragDrop; +namespace Widgets = XCEngine::UI::Editor::Widgets; + +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIPointerButton; +using ::XCEngine::UI::UIRect; + +UIInputEvent MakePointerButtonDown(float x, float y, UIPointerButton button) { + UIInputEvent event = {}; + event.type = UIInputEventType::PointerButtonDown; + event.position = UIPoint(x, y); + event.pointerButton = button; + return event; +} + +UIInputEvent MakePointerMove(float x, float y) { + UIInputEvent event = {}; + event.type = UIInputEventType::PointerMove; + event.position = UIPoint(x, y); + return event; +} + +UIInputEvent MakePointerButtonUp(float x, float y, UIPointerButton button) { + UIInputEvent event = {}; + event.type = UIInputEventType::PointerButtonUp; + event.position = UIPoint(x, y); + event.pointerButton = button; + return event; +} + +UIInputEvent MakeFocusLost() { + UIInputEvent event = {}; + event.type = UIInputEventType::FocusLost; + return event; +} + +struct GridCallbacks { + std::string committedDraggedItemId = {}; + std::string committedDropTargetItemId = {}; + int selectCount = 0; + int commitCount = 0; + bool selected = false; + + std::string ResolveDraggableItem(const UIPoint& point) const { + return point.x <= 20.0f ? "AssetA" : ""; + } + + std::string ResolveDropTargetItem( + std::string_view, + const UIPoint& point) const { + return point.x >= 60.0f ? "FolderB" : ""; + } + + bool IsItemSelected(std::string_view) const { + return selected; + } + + bool SelectDraggedItem(std::string_view) { + selected = true; + ++selectCount; + return true; + } + + bool CanDropOnItem( + std::string_view draggedItemId, + std::string_view targetItemId) const { + return !draggedItemId.empty() && + targetItemId == "FolderB" && + draggedItemId != targetItemId; + } + + bool CommitDropOnItem( + std::string_view draggedItemId, + std::string_view targetItemId) { + committedDraggedItemId = std::string(draggedItemId); + committedDropTargetItemId = std::string(targetItemId); + ++commitCount; + return true; + } +}; + +TEST(UIEditorGridDragDropTests, ProcessInputEventsCapturesAndCommitsDrop) { + GridDrag::State state = {}; + GridCallbacks callbacks = {}; + + const GridDrag::ProcessResult result = + GridDrag::ProcessInputEvents( + state, + { + MakePointerButtonDown(10.0f, 12.0f, UIPointerButton::Left), + MakePointerMove(24.0f, 12.0f), + MakePointerMove(80.0f, 12.0f), + MakePointerButtonUp(80.0f, 12.0f, UIPointerButton::Left) + }, + callbacks); + + EXPECT_TRUE(result.selectionForced); + EXPECT_TRUE(result.dropCommitted); + EXPECT_EQ(result.draggedItemId, "AssetA"); + EXPECT_EQ(result.dropTargetItemId, "FolderB"); + EXPECT_EQ(callbacks.selectCount, 1); + EXPECT_EQ(callbacks.commitCount, 1); + EXPECT_EQ(callbacks.committedDraggedItemId, "AssetA"); + EXPECT_EQ(callbacks.committedDropTargetItemId, "FolderB"); + EXPECT_TRUE(state.requestPointerCapture); + EXPECT_TRUE(state.requestPointerRelease); + EXPECT_FALSE(state.armed); + EXPECT_FALSE(state.dragging); + EXPECT_FALSE(state.validDropTarget); +} + +TEST(UIEditorGridDragDropTests, FocusLostWhileDraggingRequestsReleaseAndClearsState) { + GridDrag::State state = {}; + GridCallbacks callbacks = {}; + + const GridDrag::ProcessResult result = + GridDrag::ProcessInputEvents( + state, + { + MakePointerButtonDown(10.0f, 12.0f, UIPointerButton::Left), + MakePointerMove(24.0f, 12.0f), + MakeFocusLost() + }, + callbacks); + + EXPECT_FALSE(result.dropCommitted); + EXPECT_TRUE(state.requestPointerRelease); + EXPECT_FALSE(state.requestPointerCapture); + EXPECT_FALSE(state.armed); + EXPECT_FALSE(state.dragging); + EXPECT_TRUE(state.armedItemId.empty()); + EXPECT_TRUE(state.draggedItemId.empty()); + EXPECT_TRUE(state.dropTargetItemId.empty()); +} + +struct TreeFixtureData { + std::vector items = {}; + ::XCEngine::UI::Widgets::UIExpansionModel expansion = {}; + Widgets::UIEditorTreeViewLayout layout = {}; +}; + +TreeFixtureData BuildTreeFixture() { + TreeFixtureData fixture = {}; + fixture.items = { + Widgets::UIEditorTreeViewItem{ .itemId = "NodeA", .label = "NodeA" }, + Widgets::UIEditorTreeViewItem{ .itemId = "NodeB", .label = "NodeB" } + }; + fixture.layout = Widgets::BuildUIEditorTreeViewLayout( + UIRect(0.0f, 0.0f, 200.0f, 120.0f), + fixture.items, + fixture.expansion); + return fixture; +} + +TEST(UIEditorTreeDragDropTests, BuildInteractionInputEventsSuppressesTreeViewDragGesture) { + const TreeFixtureData fixture = BuildTreeFixture(); + TreeDrag::State state = {}; + + ASSERT_GE(fixture.layout.rowRects.size(), 2u); + const UIRect sourceRow = fixture.layout.rowRects[0]; + const UIRect targetRow = fixture.layout.rowRects[1]; + + const std::vector filteredEvents = + TreeDrag::BuildInteractionInputEvents( + state, + fixture.layout, + fixture.items, + { + MakePointerButtonDown( + sourceRow.x + 12.0f, + sourceRow.y + sourceRow.height * 0.5f, + UIPointerButton::Left), + MakePointerMove( + sourceRow.x + 24.0f, + sourceRow.y + sourceRow.height * 0.5f), + MakePointerMove( + targetRow.x + 12.0f, + targetRow.y + targetRow.height * 0.5f), + MakePointerButtonUp( + targetRow.x + 12.0f, + targetRow.y + targetRow.height * 0.5f, + UIPointerButton::Left) + }); + + ASSERT_EQ(filteredEvents.size(), 1u); + EXPECT_EQ(filteredEvents[0].type, UIInputEventType::PointerButtonDown); +} + +struct TreeCallbacks { + int selectCount = 0; + int commitToRootCount = 0; + bool selected = false; + + bool IsItemSelected(std::string_view) const { + return selected; + } + + bool SelectDraggedItem(std::string_view) { + selected = true; + ++selectCount; + return true; + } + + bool CanDropOnItem(std::string_view, std::string_view) const { + return false; + } + + bool CanDropToRoot(std::string_view draggedItemId) const { + return !draggedItemId.empty(); + } + + bool CommitDropOnItem(std::string_view, std::string_view) { + return false; + } + + bool CommitDropToRoot(std::string_view) { + ++commitToRootCount; + return true; + } +}; + +TEST(UIEditorTreeDragDropTests, ProcessInputEventsSupportsDropToRoot) { + TreeFixtureData fixture = BuildTreeFixture(); + fixture.items.resize(1u); + fixture.layout = Widgets::BuildUIEditorTreeViewLayout( + fixture.layout.bounds, + fixture.items, + fixture.expansion); + + TreeDrag::State state = {}; + TreeCallbacks callbacks = {}; + + ASSERT_FALSE(fixture.layout.rowRects.empty()); + const UIRect sourceRow = fixture.layout.rowRects.front(); + const UIPoint sourcePoint( + sourceRow.x + 12.0f, + sourceRow.y + sourceRow.height * 0.5f); + const UIPoint rootDropPoint( + fixture.layout.bounds.x + fixture.layout.bounds.width * 0.5f, + fixture.layout.bounds.y + fixture.layout.bounds.height - 8.0f); + + const TreeDrag::ProcessResult result = + TreeDrag::ProcessInputEvents( + state, + fixture.layout, + fixture.items, + { + MakePointerButtonDown(sourcePoint.x, sourcePoint.y, UIPointerButton::Left), + MakePointerMove(rootDropPoint.x, rootDropPoint.y), + MakePointerButtonUp(rootDropPoint.x, rootDropPoint.y, UIPointerButton::Left) + }, + fixture.layout.bounds, + callbacks); + + EXPECT_TRUE(result.selectionForced); + EXPECT_TRUE(result.dropCommitted); + EXPECT_TRUE(result.droppedToRoot); + EXPECT_EQ(result.draggedItemId, "NodeA"); + EXPECT_TRUE(result.dropTargetItemId.empty()); + EXPECT_EQ(callbacks.selectCount, 1); + EXPECT_EQ(callbacks.commitToRootCount, 1); + EXPECT_TRUE(state.requestPointerCapture); + EXPECT_TRUE(state.requestPointerRelease); + EXPECT_FALSE(state.armed); + EXPECT_FALSE(state.dragging); + EXPECT_FALSE(state.dropToRoot); + EXPECT_FALSE(state.validDropTarget); +} + +} // 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 22efe20a..10fa442c 100644 --- a/tests/UI/Editor/unit/test_ui_editor_panel_registry.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_panel_registry.cpp @@ -1,10 +1,12 @@ #include +#include "Composition/EditorShellAssetBuilder.h" + #include namespace { -using XCEngine::UI::Editor::BuildEditorFoundationPanelRegistry; +using XCEngine::UI::Editor::App::BuildEditorApplicationShellAsset; using XCEngine::UI::Editor::FindUIEditorPanelDescriptor; using XCEngine::UI::Editor::UIEditorPanelDescriptor; using XCEngine::UI::Editor::UIEditorPanelRegistry; @@ -12,14 +14,17 @@ using XCEngine::UI::Editor::UIEditorPanelRegistryValidationCode; using XCEngine::UI::Editor::ValidateUIEditorPanelRegistry; TEST(UIEditorPanelRegistryTest, DefaultRegistryContainsShellDescriptors) { - const UIEditorPanelRegistry registry = BuildEditorFoundationPanelRegistry(); + const UIEditorPanelRegistry registry = + BuildEditorApplicationShellAsset(".").panelRegistry; - ASSERT_EQ(registry.panels.size(), 1u); + ASSERT_EQ(registry.panels.size(), 6u); const UIEditorPanelDescriptor* descriptor = - FindUIEditorPanelDescriptor(registry, "editor-foundation-root"); + FindUIEditorPanelDescriptor(registry, "hierarchy"); ASSERT_NE(descriptor, nullptr); EXPECT_FALSE(descriptor->canHide); EXPECT_FALSE(descriptor->canClose); + EXPECT_NE(FindUIEditorPanelDescriptor(registry, "scene"), nullptr); + EXPECT_NE(FindUIEditorPanelDescriptor(registry, "project"), nullptr); EXPECT_EQ(FindUIEditorPanelDescriptor(registry, "missing-panel"), nullptr); }