refactor(new_editor): tighten app dependency boundaries
This commit is contained in:
216
docs/used/NewEditor_UI第二阶段依赖方向补充收口计划_阶段归档_2026-04-19.md
Normal file
216
docs/used/NewEditor_UI第二阶段依赖方向补充收口计划_阶段归档_2026-04-19.md
Normal file
@@ -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<EditorWindowState>` 私有持有。
|
||||
|
||||
本轮效果:
|
||||
|
||||
- `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。
|
||||
@@ -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
|
||||
$<TARGET_FILE_DIR:XCUIEditorApp>/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 $<TARGET_FILE:XCUIEditorApp>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEditor/App/EditorEditCommandRoute.h>
|
||||
#include <XCEditor/App/EditorSession.h>
|
||||
#include "Commands/EditorEditCommandRoute.h"
|
||||
#include "State/EditorSession.h"
|
||||
|
||||
#include <XCEditor/Foundation/UIEditorCommandDispatcher.h>
|
||||
|
||||
#include <functional>
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "EditorShellAssetBuilderInternal.h"
|
||||
|
||||
#include <XCEditor/App/EditorPanelIds.h>
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "EditorShellAssetBuilderInternal.h"
|
||||
|
||||
#include <XCEditor/App/EditorPanelIds.h>
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
|
||||
namespace XCEngine::UI::Editor::App::CompositionInternal {
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "EditorShellAssetBuilderInternal.h"
|
||||
|
||||
#include <XCEditor/App/EditorPanelIds.h>
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
#include "Composition/EditorShellRuntime.h"
|
||||
|
||||
#include "Host/TextureHost.h"
|
||||
#include "Host/ViewportRenderHost.h"
|
||||
#include "State/EditorContext.h"
|
||||
|
||||
#include <Rendering/D3D12/D3D12WindowRenderer.h>
|
||||
#include <Rendering/Native/NativeRenderer.h>
|
||||
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "Composition/WorkspaceEventSync.h"
|
||||
|
||||
#include <XCEditor/Shell/UIEditorShellInteraction.h>
|
||||
#include <XCEditor/Foundation/UIEditorTextMeasurement.h>
|
||||
#include <XCEditor/Workspace/UIEditorWorkspaceLayoutPersistence.h>
|
||||
#include <XCEditor/Workspace/UIEditorWorkspaceSplitterDragCorrection.h>
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "State/EditorContext.h"
|
||||
|
||||
#include <XCEditor/App/EditorPanelIds.h>
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
|
||||
namespace XCEngine::UI::Editor::App::Internal {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "Composition/EditorShellRuntimeInternal.h"
|
||||
|
||||
#include <XCEditor/App/EditorPanelIds.h>
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
|
||||
namespace XCEngine::UI::Editor::App::Internal {
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "Features/Project/ProjectPanel.h"
|
||||
#include "Composition/EditorShellRuntime.h"
|
||||
|
||||
#include <XCEditor/App/EditorPanelIds.h>
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "ConsolePanel.h"
|
||||
|
||||
#include <XCEditor/App/EditorPanelIds.h>
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEditor/App/EditorSession.h>
|
||||
#include "State/EditorSession.h"
|
||||
|
||||
#include <XCEditor/Panels/UIEditorPanelContentHost.h>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
|
||||
using namespace HierarchyPanelInternal;
|
||||
namespace TreeDrag = TreeItemDragDrop;
|
||||
namespace TreeDrag = XCEngine::UI::Editor::Collections::TreeDragDrop;
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "Features/Shared/TreeItemDragDrop.h"
|
||||
#include "HierarchyModel.h"
|
||||
|
||||
#include <XCEditor/App/EditorEditCommandRoute.h>
|
||||
#include "Commands/EditorEditCommandRoute.h"
|
||||
#include <XCEditor/Collections/UIEditorTreeDragDrop.h>
|
||||
#include <XCEditor/Collections/UIEditorInlineRenameSession.h>
|
||||
#include <XCEditor/Collections/UIEditorTreeViewInteraction.h>
|
||||
#include <XCEditor/Panels/UIEditorPanelContentHost.h>
|
||||
@@ -102,7 +102,7 @@ private:
|
||||
UIEditorInlineRenameSessionFrame m_renameFrame = {};
|
||||
std::string m_pendingRenameItemId = {};
|
||||
std::vector<Event> m_frameEvents = {};
|
||||
TreeItemDragDrop::State m_dragState = {};
|
||||
Collections::TreeDragDrop::State m_dragState = {};
|
||||
bool m_visible = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include "Rendering/Assets/BuiltInIcons.h"
|
||||
|
||||
#include <XCEditor/App/EditorPanelIds.h>
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
#include <XCEditor/Collections/UIEditorTreeView.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "InspectorPanel.h"
|
||||
|
||||
#include <XCEditor/App/EditorPanelIds.h>
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
#include <XCEditor/Fields/UIEditorFieldStyle.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "Features/Inspector/InspectorPresentationModel.h"
|
||||
#include "Features/Inspector/InspectorSubject.h"
|
||||
|
||||
#include <XCEditor/App/EditorEditCommandRoute.h>
|
||||
#include "Commands/EditorEditCommandRoute.h"
|
||||
#include <XCEditor/Fields/UIEditorPropertyGridInteraction.h>
|
||||
#include <XCEditor/Panels/UIEditorPanelContentHost.h>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEditor/App/EditorSession.h>
|
||||
#include "State/EditorSession.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "Features/Shared/GridItemDragDrop.h"
|
||||
#include "Features/Shared/TreeItemDragDrop.h"
|
||||
#include "Project/EditorProjectRuntime.h"
|
||||
#include "ProjectBrowserModel.h"
|
||||
|
||||
#include <XCEditor/App/EditorEditCommandRoute.h>
|
||||
#include "Commands/EditorEditCommandRoute.h"
|
||||
#include <XCEditor/Collections/UIEditorGridDragDrop.h>
|
||||
#include <XCEditor/Collections/UIEditorInlineRenameSession.h>
|
||||
#include <XCEditor/Collections/UIEditorTreeDragDrop.h>
|
||||
#include <XCEditor/Collections/UIEditorTreeViewInteraction.h>
|
||||
#include <XCEditor/Foundation/UIEditorTextMeasurement.h>
|
||||
#include <XCEditor/Menu/UIEditorMenuPopup.h>
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include "Rendering/Assets/BuiltInIcons.h"
|
||||
|
||||
#include <XCEditor/App/EditorPanelIds.h>
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
#include <XCEditor/Collections/UIEditorTreeView.h>
|
||||
#include <XCEditor/Foundation/UIEditorTheme.h>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEditor/App/EditorEditCommandRoute.h>
|
||||
#include "Commands/EditorEditCommandRoute.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
#include "Rendering/Viewport/ViewportHostService.h"
|
||||
#include "Scene/EditorSceneRuntime.h"
|
||||
|
||||
#include <Rendering/Native/NativeRenderer.h>
|
||||
|
||||
#include <XCEditor/App/EditorPanelIds.h>
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
#include <XCEditor/Viewport/UIEditorViewportInputBridge.h>
|
||||
#include <XCEditor/Viewport/UIEditorViewportSlot.h>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "Features/Scene/SceneViewportToolOverlay.h"
|
||||
|
||||
#include <Rendering/Native/NativeRenderer.h>
|
||||
#include "Host/TextureHost.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -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);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Scene/SceneToolState.h"
|
||||
#include "Host/HostFwd.h"
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
#include <XCEngine/UI/Types.h>
|
||||
@@ -9,12 +10,6 @@
|
||||
#include <filesystem>
|
||||
#include <cstddef>
|
||||
|
||||
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(
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
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 <typename Callbacks>
|
||||
ProcessResult ProcessInputEvents(
|
||||
State& state,
|
||||
const std::vector<UIInputEvent>& 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
|
||||
@@ -1,324 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEditor/Collections/UIEditorTreeView.h>
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
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<Widgets::UIEditorTreeViewItem>& 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<Widgets::UIEditorTreeViewItem>& 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<UIInputEvent> BuildInteractionInputEvents(
|
||||
const State& state,
|
||||
const Widgets::UIEditorTreeViewLayout& layout,
|
||||
const std::vector<Widgets::UIEditorTreeViewItem>& items,
|
||||
const std::vector<UIInputEvent>& 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<UIInputEvent> 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 <typename Callbacks>
|
||||
ProcessResult ProcessInputEvents(
|
||||
State& state,
|
||||
const Widgets::UIEditorTreeViewLayout& layout,
|
||||
const std::vector<Widgets::UIEditorTreeViewItem>& items,
|
||||
const std::vector<UIInputEvent>& 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
|
||||
12
new_editor/app/Host/HostFwd.h
Normal file
12
new_editor/app/Host/HostFwd.h
Normal file
@@ -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
|
||||
31
new_editor/app/Host/ShaderResourceDescriptorAllocator.h
Normal file
31
new_editor/app/Host/ShaderResourceDescriptorAllocator.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <XCEngine/RHI/RHIDevice.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
|
||||
#include <d3d12.h>
|
||||
|
||||
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
|
||||
28
new_editor/app/Host/TextureHost.h
Normal file
28
new_editor/app/Host/TextureHost.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
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
|
||||
14
new_editor/app/Host/ViewportRenderHost.h
Normal file
14
new_editor/app/Host/ViewportRenderHost.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/RHI/RHIDevice.h>
|
||||
|
||||
namespace XCEngine::UI::Editor::Host {
|
||||
|
||||
class ViewportRenderHost {
|
||||
public:
|
||||
virtual ~ViewportRenderHost() = default;
|
||||
|
||||
[[nodiscard]] virtual ::XCEngine::RHI::RHIDevice* GetRHIDevice() const = 0;
|
||||
};
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Host
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <Rendering/Native/NativeRenderer.h>
|
||||
#include "Host/HostFwd.h"
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,21 +4,61 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include "Platform/Win32/EditorWindowState.h"
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
#include "Platform/Win32/EditorWindowPointerCapture.h"
|
||||
#include "Platform/Win32/EditorWindowTransferRequests.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
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<EditorWindowState> m_state = {};
|
||||
};
|
||||
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
|
||||
@@ -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<UINT>(width),
|
||||
static_cast<UINT>(height));
|
||||
ApplyWindowResize(static_cast<UINT>(width), static_cast<UINT>(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,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "Platform/Win32/EditorWindow.h"
|
||||
#include "Platform/Win32/EditorWindowInternalState.h"
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
@@ -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<UINT>(width),
|
||||
static_cast<UINT>(height));
|
||||
ApplyWindowResize(static_cast<UINT>(width), static_cast<UINT>(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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<UIInputEvent> frameEvents = std::move(m_input.pendingEvents);
|
||||
m_input.pendingEvents.clear();
|
||||
std::vector<UIInputEvent> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "Platform/Win32/EditorWindow.h"
|
||||
#include "Platform/Win32/EditorWindowInternalState.h"
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
#include <XCEngine/UI/Types.h>
|
||||
@@ -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<std::size_t>(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<float>(wheelDelta);
|
||||
event.modifiers = m_input.modifierTracker.ApplyPointerMessage(
|
||||
event.modifiers = m_state->input.modifierTracker.ApplyPointerMessage(
|
||||
UIInputEventType::PointerWheel,
|
||||
UIPointerButton::None,
|
||||
static_cast<std::size_t>(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<std::uint32_t>(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<SHORT>(screenPoint.x),
|
||||
static_cast<SHORT>(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:
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include "Platform/Win32/EditorWindowPointerCapture.h"
|
||||
#include "Composition/EditorShellRuntime.h"
|
||||
#include "Platform/Win32/EditorWindowPointerCapture.h"
|
||||
|
||||
#include <Platform/Win32/BorderlessWindowChrome.h>
|
||||
#include <Platform/Win32/HostRuntimeState.h>
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -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<EditorWindowPanelTransferRequest> beginGlobalTabDrag = {};
|
||||
std::optional<EditorWindowPanelTransferRequest> 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
|
||||
@@ -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<EditorWindowState>()) {
|
||||
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<float>(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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "Platform/Win32/EditorWindow.h"
|
||||
#include "Platform/Win32/EditorWindowConstants.h"
|
||||
#include "Platform/Win32/EditorWindowInternalState.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 <XCEditor/Foundation/UIEditorTheme.h>
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
33
new_editor/app/Platform/Win32/EditorWindowTransferRequests.h
Normal file
33
new_editor/app/Platform/Win32/EditorWindowTransferRequests.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
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<EditorWindowPanelTransferRequest> beginGlobalTabDrag = {};
|
||||
std::optional<EditorWindowPanelTransferRequest> detachPanel = {};
|
||||
|
||||
bool HasPendingRequests() const {
|
||||
return beginGlobalTabDrag.has_value() || detachPanel.has_value();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
#include "Platform/Win32/EditorWindowManager.h"
|
||||
|
||||
#include <XCEditor/Workspace/UIEditorWindowWorkspaceController.h>
|
||||
#include <XCEditor/Workspace/UIEditorWorkspaceController.h>
|
||||
|
||||
namespace XCEngine::UI::Editor::App::Internal {
|
||||
|
||||
class EditorWindowHostRuntime final {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "Platform/Win32/EditorWindow.h"
|
||||
|
||||
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
|
||||
#include <XCEditor/Workspace/UIEditorWindowWorkspaceController.h>
|
||||
#include <XCEditor/Workspace/UIEditorWorkspaceController.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "Features/Project/ProjectBrowserModel.h"
|
||||
|
||||
#include <XCEditor/App/EditorSession.h>
|
||||
#include "State/EditorSession.h"
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "BuiltInIcons.h"
|
||||
#include "Bootstrap/EditorResources.h"
|
||||
#include "Host/TextureHost.h"
|
||||
#include "Internal/EmbeddedPngLoader.h"
|
||||
|
||||
#include <sstream>
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <Rendering/Native/NativeRenderer.h>
|
||||
#include "Host/HostFwd.h"
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include "Host/ShaderResourceDescriptorAllocator.h"
|
||||
|
||||
#include <XCEngine/RHI/D3D12/D3D12DescriptorHeap.h>
|
||||
#include <XCEngine/RHI/D3D12/D3D12Device.h>
|
||||
#include <XCEngine/RHI/D3D12/D3D12Texture.h>
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include "Host/TextureHost.h"
|
||||
|
||||
#include <Rendering/D3D12/D3D12WindowInteropContext.h>
|
||||
#include <Rendering/D3D12/D3D12WindowRenderer.h>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 <XCEngine/Rendering/Internal/RenderSurfacePipelineUtils.h>
|
||||
#include <XCEngine/Rendering/Internal/ShaderVariantUtils.h>
|
||||
#include <XCEngine/Rendering/Materials/RenderMaterialStateUtils.h>
|
||||
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Math/Matrix4.h>
|
||||
|
||||
@@ -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 <XCEngine/Rendering/Internal/RenderSurfacePipelineUtils.h>
|
||||
#include <XCEngine/Rendering/Internal/ShaderVariantUtils.h>
|
||||
#include <XCEngine/Rendering/Materials/RenderMaterialStateUtils.h>
|
||||
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Math/Vector4.h>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "ViewportHostService.h"
|
||||
|
||||
#include "Host/ViewportRenderHost.h"
|
||||
#include <Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.h>
|
||||
|
||||
#include <XCEngine/Components/CameraComponent.h>
|
||||
#include <XCEngine/RHI/RHICommandList.h>
|
||||
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
|
||||
@@ -22,16 +25,20 @@ void SetViewportStatusIfEmpty(
|
||||
|
||||
} // namespace
|
||||
|
||||
ViewportHostService::ViewportHostService() = default;
|
||||
ViewportHostService::ViewportHostService()
|
||||
: m_textureDescriptorAllocator(
|
||||
std::make_unique<Host::D3D12ShaderResourceDescriptorAllocator>()) {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "Host/HostFwd.h"
|
||||
#include "SceneViewportRenderPassBundle.h"
|
||||
#include "ViewportObjectIdPicker.h"
|
||||
#include "Scene/EditorSceneRuntime.h"
|
||||
#include "ViewportRenderTargets.h"
|
||||
|
||||
#include <Rendering/D3D12/D3D12WindowRenderer.h>
|
||||
#include <Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
@@ -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<Host::ShaderResourceDescriptorAllocator> m_textureDescriptorAllocator = {};
|
||||
ViewportRenderTargetManager m_renderTargetManager = {};
|
||||
bool m_surfacePresentationEnabled = false;
|
||||
SceneViewportRenderRequest m_sceneViewportRenderRequest = {};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "Host/HostFwd.h"
|
||||
#include "ViewportRenderTargetInternal.h"
|
||||
|
||||
#include <Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.h>
|
||||
|
||||
#include <XCEngine/RHI/RHIDevice.h>
|
||||
#include <XCEngine/RHI/RHIResourceView.h>
|
||||
#include <XCEngine/RHI/RHITexture.h>
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <d3d12.h>
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "Scene/EditorSceneRuntime.h"
|
||||
#include "State/EditorSelectionStamp.h"
|
||||
|
||||
#include <XCEditor/App/EditorPanelIds.h>
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#include "Project/EditorProjectRuntime.h"
|
||||
#include "Scene/EditorSceneRuntime.h"
|
||||
|
||||
#include <XCEditor/App/EditorHostCommandBridge.h>
|
||||
#include <XCEditor/App/EditorSession.h>
|
||||
#include "Commands/EditorHostCommandBridge.h"
|
||||
#include "State/EditorSession.h"
|
||||
#include <XCEditor/Foundation/UIEditorShortcutManager.h>
|
||||
#include <XCEditor/Shell/UIEditorShellAsset.h>
|
||||
#include <XCEditor/Shell/UIEditorShellInteraction.h>
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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 <typename StateLike>
|
||||
inline void ResetTransientRequests(StateLike& state) {
|
||||
state.requestPointerCapture = false;
|
||||
state.requestPointerRelease = false;
|
||||
}
|
||||
|
||||
template <typename StateLike>
|
||||
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 <typename StateLike>
|
||||
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 <typename ResolveDraggableItem>
|
||||
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 <typename StateLike, typename Callbacks>
|
||||
inline void ResetInteractionSession(StateLike& state, Callbacks& callbacks) {
|
||||
state.armed = false;
|
||||
state.dragging = false;
|
||||
state.armedItemId.clear();
|
||||
state.draggedItemId.clear();
|
||||
callbacks.ResetDropTarget(state);
|
||||
}
|
||||
|
||||
template <typename StateLike, typename Callbacks>
|
||||
ProcessResult ProcessInputEvents(
|
||||
StateLike& state,
|
||||
const std::vector<UIInputEvent>& 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
|
||||
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEditor/Collections/UIEditorDragDropInteraction.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
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 <typename Callbacks>
|
||||
ProcessResult ProcessInputEvents(
|
||||
State& state,
|
||||
const std::vector<DragDropInteraction::UIInputEvent>& 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
|
||||
201
new_editor/include/XCEditor/Collections/UIEditorTreeDragDrop.h
Normal file
201
new_editor/include/XCEditor/Collections/UIEditorTreeDragDrop.h
Normal file
@@ -0,0 +1,201 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEditor/Collections/UIEditorDragDropInteraction.h>
|
||||
#include <XCEditor/Collections/UIEditorTreeView.h>
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
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<Widgets::UIEditorTreeViewItem>& 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<Widgets::UIEditorTreeViewItem>& 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<DragDropInteraction::UIInputEvent> BuildInteractionInputEvents(
|
||||
const State& state,
|
||||
const Widgets::UIEditorTreeViewLayout& layout,
|
||||
const std::vector<Widgets::UIEditorTreeViewItem>& items,
|
||||
const std::vector<DragDropInteraction::UIInputEvent>& 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<DragDropInteraction::UIInputEvent> 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 <typename Callbacks>
|
||||
ProcessResult ProcessInputEvents(
|
||||
State& state,
|
||||
const Widgets::UIEditorTreeViewLayout& layout,
|
||||
const std::vector<Widgets::UIEditorTreeViewItem>& items,
|
||||
const std::vector<DragDropInteraction::UIInputEvent>& inputEvents,
|
||||
const UIRect& bounds,
|
||||
Callbacks& callbacks,
|
||||
float dragThreshold = kDefaultDragThreshold) {
|
||||
struct AdaptedCallbacks {
|
||||
const Widgets::UIEditorTreeViewLayout& layout;
|
||||
const std::vector<Widgets::UIEditorTreeViewItem>& 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
|
||||
@@ -45,8 +45,6 @@ struct UIEditorPanelRegistryValidationResult {
|
||||
}
|
||||
};
|
||||
|
||||
UIEditorPanelRegistry BuildEditorFoundationPanelRegistry();
|
||||
|
||||
const UIEditorPanelDescriptor* FindUIEditorPanelDescriptor(
|
||||
const UIEditorPanelRegistry& registry,
|
||||
std::string_view panelId);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -57,8 +57,6 @@ struct UIEditorWorkspaceVisiblePanel {
|
||||
bool placeholder = false;
|
||||
};
|
||||
|
||||
UIEditorWorkspaceModel BuildEditorFoundationWorkspaceModel();
|
||||
|
||||
UIEditorWorkspaceNode BuildUIEditorWorkspacePanel(
|
||||
std::string nodeId,
|
||||
std::string panelId,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <XCEditor/App/EditorHostCommandBridge.h>
|
||||
#include "Commands/EditorHostCommandBridge.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <XCEditor/App/EditorSession.h>
|
||||
#include <XCEditor/App/EditorPanelIds.h>
|
||||
#include "State/EditorSession.h"
|
||||
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
|
||||
#include <XCEditor/Workspace/UIEditorWorkspaceController.h>
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/App/EditorEditCommandRoute.h>
|
||||
#include <XCEditor/App/EditorHostCommandBridge.h>
|
||||
#include <XCEditor/App/EditorSession.h>
|
||||
#include "Commands/EditorEditCommandRoute.h"
|
||||
#include "Commands/EditorHostCommandBridge.h"
|
||||
#include "State/EditorSession.h"
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Shell/UIEditorShellAsset.h>
|
||||
#include "Composition/EditorShellAssetBuilder.h"
|
||||
|
||||
#include <XCEditor/Panels/UIEditorPanelRegistry.h>
|
||||
#include <XCEditor/Shell/UIEditorShellAsset.h>
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
|
||||
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<XCEngine::UI::Editor::UIEditorPanelDescriptor*>(
|
||||
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());
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "Features/Project/ProjectPanel.h"
|
||||
#include "Rendering/Assets/BuiltInIcons.h"
|
||||
|
||||
#include <XCEditor/App/EditorPanelIds.h>
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "Rendering/Viewport/ViewportHostService.h"
|
||||
#include "State/EditorSelectionStamp.h"
|
||||
|
||||
#include <XCEditor/App/EditorPanelIds.h>
|
||||
#include "Composition/EditorPanelIds.h"
|
||||
#include <XCEditor/Viewport/UIEditorViewportInputBridge.h>
|
||||
#include <XCEditor/Viewport/UIEditorViewportSlot.h>
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Composition/EditorShellAssetBuilder.h"
|
||||
|
||||
#include <XCEditor/Shell/UIEditorShellAsset.h>
|
||||
#include <XCEditor/Shell/UIEditorStructuredShell.h>
|
||||
#include <XCEditor/Shell/UIEditorShellInteraction.h>
|
||||
@@ -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());
|
||||
|
||||
286
tests/UI/Editor/unit/test_ui_editor_drag_drop.cpp
Normal file
286
tests/UI/Editor/unit/test_ui_editor_drag_drop.cpp
Normal file
@@ -0,0 +1,286 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEditor/Collections/UIEditorGridDragDrop.h>
|
||||
#include <XCEditor/Collections/UIEditorTreeDragDrop.h>
|
||||
|
||||
#include <XCEngine/UI/Widgets/UIExpansionModel.h>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
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<Widgets::UIEditorTreeViewItem> 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<UIInputEvent> 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
|
||||
@@ -1,10 +1,12 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Composition/EditorShellAssetBuilder.h"
|
||||
|
||||
#include <XCEditor/Panels/UIEditorPanelRegistry.h>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user