refactor(new_editor): tighten app dependency boundaries

This commit is contained in:
2026-04-19 02:48:41 +08:00
parent 7429f22fb1
commit c59cd83c38
86 changed files with 1754 additions and 1077 deletions

View 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。

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,6 +1,6 @@
#include "EditorShellAssetBuilderInternal.h"
#include <XCEditor/App/EditorPanelIds.h>
#include "Composition/EditorPanelIds.h"
#include <XCEngine/Input/InputTypes.h>
#include <utility>

View File

@@ -1,6 +1,6 @@
#include "EditorShellAssetBuilderInternal.h"
#include <XCEditor/App/EditorPanelIds.h>
#include "Composition/EditorPanelIds.h"
namespace XCEngine::UI::Editor::App::CompositionInternal {

View File

@@ -1,6 +1,6 @@
#include "EditorShellAssetBuilderInternal.h"
#include <XCEditor/App/EditorPanelIds.h>
#include "Composition/EditorPanelIds.h"
#include <utility>

View File

@@ -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();
}

View File

@@ -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 = {};

View File

@@ -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 {

View File

@@ -1,6 +1,6 @@
#include "Composition/EditorShellRuntimeInternal.h"
#include <XCEditor/App/EditorPanelIds.h>
#include "Composition/EditorPanelIds.h"
namespace XCEngine::UI::Editor::App::Internal {

View File

@@ -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>

View File

@@ -1,6 +1,6 @@
#include "ConsolePanel.h"
#include <XCEditor/App/EditorPanelIds.h>
#include "Composition/EditorPanelIds.h"
#include <XCEditor/Foundation/UIEditorTheme.h>
#include <algorithm>

View File

@@ -1,6 +1,6 @@
#pragma once
#include <XCEditor/App/EditorSession.h>
#include "State/EditorSession.h"
#include <XCEditor/Panels/UIEditorPanelContentHost.h>

View File

@@ -10,7 +10,7 @@
namespace XCEngine::UI::Editor::App {
using namespace HierarchyPanelInternal;
namespace TreeDrag = TreeItemDragDrop;
namespace TreeDrag = XCEngine::UI::Editor::Collections::TreeDragDrop;
namespace {

View File

@@ -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;
};

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,6 +1,6 @@
#pragma once
#include <XCEditor/App/EditorSession.h>
#include "State/EditorSession.h"
#include <cstdint>
#include <string>

View File

@@ -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 {

View File

@@ -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 = {};

View File

@@ -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>

View File

@@ -1,6 +1,6 @@
#pragma once
#include <XCEditor/App/EditorEditCommandRoute.h>
#include "Commands/EditorEditCommandRoute.h"
#include <string>

View File

@@ -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();
}

View File

@@ -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(

View File

@@ -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);

View File

@@ -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(

View File

@@ -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

View File

@@ -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

View 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

View 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

View 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

View 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

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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,

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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());
}

View 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

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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"

View File

@@ -2,7 +2,7 @@
#include "Features/Project/ProjectBrowserModel.h"
#include <XCEditor/App/EditorSession.h>
#include "State/EditorSession.h"
#include <XCEngine/UI/Types.h>

View File

@@ -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;

View File

@@ -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 = {};

View File

@@ -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(

View File

@@ -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;

View File

@@ -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(

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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 = {};

View File

@@ -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);

View File

@@ -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;
};

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -45,8 +45,6 @@ struct UIEditorPanelRegistryValidationResult {
}
};
UIEditorPanelRegistry BuildEditorFoundationPanelRegistry();
const UIEditorPanelDescriptor* FindUIEditorPanelDescriptor(
const UIEditorPanelRegistry& registry,
std::string_view panelId);

View File

@@ -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);

View File

@@ -57,8 +57,6 @@ struct UIEditorWorkspaceVisiblePanel {
bool placeholder = false;
};
UIEditorWorkspaceModel BuildEditorFoundationWorkspaceModel();
UIEditorWorkspaceNode BuildUIEditorWorkspacePanel(
std::string nodeId,
std::string panelId,

View File

@@ -1,4 +1,4 @@
#include <XCEditor/App/EditorHostCommandBridge.h>
#include "Commands/EditorHostCommandBridge.h"
#include <string>
#include <utility>

View File

@@ -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>

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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 =

View File

@@ -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

View File

@@ -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 {

View File

@@ -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());

View File

@@ -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>

View File

@@ -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>

View File

@@ -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());

View 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

View File

@@ -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);
}