diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index b4857db9..0da9d2e9 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -130,6 +130,7 @@ set(XCUI_EDITOR_WORKSPACE_SOURCES set(XCUI_EDITOR_WIDGET_SUPPORT_SOURCES src/Widgets/UIEditorCollectionPrimitives.cpp + src/Widgets/UIEditorColorUtils.cpp src/Widgets/UIEditorFieldRowLayout.cpp ) @@ -170,15 +171,10 @@ set(XCUI_EDITOR_HOST_PLATFORM_SOURCES app/Platform/Win32/BorderlessWindowChromeDwm.cpp app/Platform/Win32/BorderlessWindowFrame.cpp app/Platform/Win32/WindowMessageDispatcher.cpp - app/Platform/Win32/WindowMessageDispatcherChrome.cpp - app/Platform/Win32/WindowMessageDispatcherInput.cpp - app/Platform/Win32/WindowMessageDispatcherPointer.cpp - app/Platform/Win32/WindowMessageDispatcherLifecycle.cpp ) set(XCUI_EDITOR_HOST_RENDERING_SOURCES app/Rendering/Native/AutoScreenshot.cpp - app/Rendering/D3D12/D3D12HostDevice.cpp app/Rendering/D3D12/D3D12HostDeviceFence.cpp app/Rendering/D3D12/D3D12HostDeviceFrame.cpp app/Rendering/D3D12/D3D12HostDeviceLifecycle.cpp @@ -188,13 +184,11 @@ set(XCUI_EDITOR_HOST_RENDERING_SOURCES app/Rendering/D3D12/D3D12WindowInteropResources.cpp app/Rendering/D3D12/D3D12WindowInteropSourceTextures.cpp app/Rendering/D3D12/D3D12WindowRenderer.cpp - app/Rendering/D3D12/SwapChainPresenter/Presenter.cpp app/Rendering/D3D12/SwapChainPresenter/BackBuffers.cpp app/Rendering/D3D12/SwapChainPresenter/Lifecycle.cpp app/Rendering/D3D12/SwapChainPresenter/Presentation.cpp app/Rendering/D3D12/SwapChainPresenter/Resize.cpp app/Rendering/D3D12/D3D12WindowRenderLoop.cpp - app/Rendering/Native/NativeRenderer.cpp app/Rendering/Native/NativeRendererCapture.cpp app/Rendering/Native/NativeRendererDraw.cpp app/Rendering/Native/NativeRendererDrawContent.cpp @@ -238,6 +232,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Bootstrap/EditorApp.rc app/main.cpp app/Bootstrap/ApplicationBootstrap.cpp + app/Bootstrap/ApplicationBootstrapInternal.cpp app/Bootstrap/ApplicationLifecycle.cpp app/Bootstrap/ApplicationRunLoop.cpp app/Bootstrap/ApplicationWindowClass.cpp @@ -267,43 +262,42 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Features/Console/ConsolePanel.cpp app/Features/Hierarchy/HierarchyModel.cpp app/Features/Hierarchy/HierarchyPanel.cpp - app/Features/Hierarchy/HierarchyPanelCommands.cpp - app/Features/Hierarchy/HierarchyPanelInteraction.cpp - app/Features/Hierarchy/HierarchyPanelRendering.cpp + app/Features/Hierarchy/HierarchyPanelInternal.cpp app/Features/Inspector/InspectorPanel.cpp app/Features/Project/ProjectPanel.cpp - app/Features/Project/ProjectPanelCommands.cpp - app/Features/Project/ProjectPanelInteraction.cpp - app/Features/Project/ProjectPanelLayout.cpp - app/Features/Project/ProjectPanelRendering.cpp + app/Features/Project/ProjectPanelInternal.cpp app/Features/Project/ProjectBrowserModel.cpp app/Features/Project/ProjectBrowserModelAssets.cpp app/Features/Project/ProjectBrowserModelFolders.cpp + app/Features/Project/ProjectBrowserModelInternal.cpp ) set(XCUI_EDITOR_APP_RENDERING_SOURCES app/Rendering/Assets/BuiltInIcons.cpp - app/Rendering/Viewport/ViewportHostService.cpp app/Rendering/Viewport/ViewportHostServiceFrame.cpp app/Rendering/Viewport/ViewportHostServiceLifecycle.cpp - app/Rendering/Viewport/RenderTargetManager/Manager.cpp + app/Rendering/Viewport/ViewportRenderTargetInternal.cpp app/Rendering/Viewport/RenderTargetManager/Cleanup.cpp app/Rendering/Viewport/RenderTargetManager/Resources.cpp app/Rendering/Viewport/RenderTargetManager/Surfaces.cpp - app/Rendering/Viewport/ViewportRenderTargets.cpp + ) + + set(XCUI_EDITOR_APP_SUPPORT_SOURCES + app/Support/EmbeddedPngLoader.cpp ) set(XCUI_EDITOR_APP_PLATFORM_SOURCES app/Platform/Win32/EditorWindowBorderlessPlacement.cpp app/Platform/Win32/EditorWindowBorderlessResize.cpp app/Platform/Win32/EditorWindowFrame.cpp - app/Platform/Win32/EditorWindowFrameSupport.cpp app/Platform/Win32/EditorWindowFrameRuntime.cpp - app/Platform/Win32/EditorWindowTitleBar.cpp app/Platform/Win32/EditorWindowTitleBarDragRestore.cpp app/Platform/Win32/EditorWindowTitleBarInteraction.cpp app/Platform/Win32/EditorWindowTitleBarRendering.cpp app/Platform/Win32/EditorWindowInitialization.cpp + app/Platform/Win32/EditorWindowPlatformInternal.cpp + app/Platform/Win32/EditorWindowRuntimeInternal.cpp + app/Platform/Win32/EditorWindowInputInternal.cpp app/Platform/Win32/EditorWindowLifecycle.cpp app/Platform/Win32/EditorWindowMetrics.cpp app/Platform/Win32/EditorWindowInput.cpp @@ -316,6 +310,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) app/Platform/Win32/WindowManager/TabDrag.cpp app/Platform/Win32/WindowManager/Detach.cpp app/Platform/Win32/WindowManager/CrossWindowDrop.cpp + app/Platform/Win32/WindowManager/CrossWindowDropInternal.cpp ) add_executable(XCUIEditorApp WIN32 @@ -324,6 +319,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) ${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} ) diff --git a/new_editor/app/Bootstrap/Application.h b/new_editor/app/Bootstrap/Application.h index b0eb9fa2..05b63146 100644 --- a/new_editor/app/Bootstrap/Application.h +++ b/new_editor/app/Bootstrap/Application.h @@ -39,6 +39,9 @@ public: bool OwnsActiveGlobalTabDrag(std::string_view windowId) const override; void EndGlobalTabDragSession() override; void HandleDestroyedWindow(HWND hwnd) override; + void HandleWindowFrameTransferRequests( + App::EditorWindow& window, + App::EditorWindowFrameTransferRequests&& transferRequests) override; bool HandleGlobalTabDragPointerMove(HWND hwnd) override; bool HandleGlobalTabDragPointerButtonUp(HWND hwnd) override; diff --git a/new_editor/app/Bootstrap/ApplicationBootstrap.cpp b/new_editor/app/Bootstrap/ApplicationBootstrap.cpp index 3e42256e..04232ae9 100644 --- a/new_editor/app/Bootstrap/ApplicationBootstrap.cpp +++ b/new_editor/app/Bootstrap/ApplicationBootstrap.cpp @@ -1,10 +1,13 @@ #include "Bootstrap/Application.h" -#include "Bootstrap/ApplicationBootstrapSupport.h" +#include "Bootstrap/ApplicationBootstrapInternal.h" + +#include #include "State/EditorContext.h" #include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowManager.h" #include "Support/EnvironmentFlags.h" +#include "Support/ExecutablePath.h" #ifndef XCUIEDITOR_REPO_ROOT #define XCUIEDITOR_REPO_ROOT "." @@ -12,7 +15,8 @@ namespace XCEngine::UI::Editor { -using namespace BootstrapSupport; +using namespace BootstrapInternal; +using Support::GetExecutableDirectory; bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { m_hInstance = hInstance; diff --git a/new_editor/app/Bootstrap/ApplicationBootstrapSupport.h b/new_editor/app/Bootstrap/ApplicationBootstrapInternal.cpp similarity index 74% rename from new_editor/app/Bootstrap/ApplicationBootstrapSupport.h rename to new_editor/app/Bootstrap/ApplicationBootstrapInternal.cpp index a59ac87d..5474f9c3 100644 --- a/new_editor/app/Bootstrap/ApplicationBootstrapSupport.h +++ b/new_editor/app/Bootstrap/ApplicationBootstrapInternal.cpp @@ -1,22 +1,8 @@ -#pragma once +#include "Bootstrap/ApplicationBootstrapInternal.h" -#include "Bootstrap/EditorResources.h" -#include "Support/ExecutablePath.h" +namespace XCEngine::UI::Editor::BootstrapInternal { -#include - -#include - -#include -#include - -namespace XCEngine::UI::Editor::BootstrapSupport { - -inline constexpr const wchar_t* kWindowClassName = L"XCEditorShellHost"; -inline constexpr const wchar_t* kWindowTitle = L"Main Scene * - Main.xx - XCEngine Editor"; -inline constexpr DWORD kBorderlessWindowStyle = WS_POPUP | WS_THICKFRAME; - -inline void EnableDpiAwareness() { +void EnableDpiAwareness() { const HMODULE user32 = GetModuleHandleW(L"user32.dll"); if (user32 != nullptr) { using SetProcessDpiAwarenessContextFn = BOOL(WINAPI*)(DPI_AWARENESS_CONTEXT); @@ -66,6 +52,4 @@ inline void EnableDpiAwareness() { } } -using Support::GetExecutableDirectory; - -} // namespace XCEngine::UI::Editor::BootstrapSupport +} // namespace XCEngine::UI::Editor::BootstrapInternal diff --git a/new_editor/app/Bootstrap/ApplicationBootstrapInternal.h b/new_editor/app/Bootstrap/ApplicationBootstrapInternal.h new file mode 100644 index 00000000..9478e9c8 --- /dev/null +++ b/new_editor/app/Bootstrap/ApplicationBootstrapInternal.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Bootstrap/EditorResources.h" + +#include + +namespace XCEngine::UI::Editor::BootstrapInternal { + +inline constexpr const wchar_t* kWindowClassName = L"XCEditorShellHost"; +inline constexpr const wchar_t* kWindowTitle = L"Main Scene * - Main.xx - XCEngine Editor"; +inline constexpr DWORD kBorderlessWindowStyle = WS_POPUP | WS_THICKFRAME; + +void EnableDpiAwareness(); + +} // namespace XCEngine::UI::Editor::BootstrapInternal diff --git a/new_editor/app/Bootstrap/ApplicationLifecycle.cpp b/new_editor/app/Bootstrap/ApplicationLifecycle.cpp index 77dfef33..183c6432 100644 --- a/new_editor/app/Bootstrap/ApplicationLifecycle.cpp +++ b/new_editor/app/Bootstrap/ApplicationLifecycle.cpp @@ -4,6 +4,8 @@ #include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowManager.h" +#include + namespace XCEngine::UI::Editor { Application::Application() @@ -40,6 +42,16 @@ void Application::HandleDestroyedWindow(HWND hwnd) { } } +void Application::HandleWindowFrameTransferRequests( + App::EditorWindow& window, + App::EditorWindowFrameTransferRequests&& transferRequests) { + if (m_windowManager != nullptr) { + m_windowManager->HandleWindowFrameTransferRequests( + window, + std::move(transferRequests)); + } +} + bool Application::HandleGlobalTabDragPointerMove(HWND hwnd) { return m_windowManager != nullptr && m_windowManager->HandleGlobalTabDragPointerMove(hwnd); diff --git a/new_editor/app/Bootstrap/ApplicationRunLoop.cpp b/new_editor/app/Bootstrap/ApplicationRunLoop.cpp index 05fd5002..41496dd9 100644 --- a/new_editor/app/Bootstrap/ApplicationRunLoop.cpp +++ b/new_editor/app/Bootstrap/ApplicationRunLoop.cpp @@ -45,9 +45,6 @@ int Application::Run(HINSTANCE hInstance, int nCmdShow) { } if (m_windowManager != nullptr) { - m_windowManager->DestroyClosedWindows(); - m_windowManager->ProcessPendingGlobalTabDragStarts(); - m_windowManager->ProcessPendingDetachRequests(); m_windowManager->DestroyClosedWindows(); if (!m_windowManager->HasWindows()) { break; diff --git a/new_editor/app/Bootstrap/ApplicationWindowClass.cpp b/new_editor/app/Bootstrap/ApplicationWindowClass.cpp index 4f123b55..d2a90211 100644 --- a/new_editor/app/Bootstrap/ApplicationWindowClass.cpp +++ b/new_editor/app/Bootstrap/ApplicationWindowClass.cpp @@ -1,5 +1,7 @@ #include "Bootstrap/Application.h" -#include "Bootstrap/ApplicationBootstrapSupport.h" +#include "Bootstrap/ApplicationBootstrapInternal.h" + +#include #include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowManager.h" @@ -8,7 +10,7 @@ namespace XCEngine::UI::Editor { -using namespace BootstrapSupport; +using namespace BootstrapInternal; namespace { diff --git a/new_editor/app/Composition/EditorShellAssetBuilder.cpp b/new_editor/app/Composition/EditorShellAssetBuilder.cpp index 06534b41..4d554bbe 100644 --- a/new_editor/app/Composition/EditorShellAssetBuilder.cpp +++ b/new_editor/app/Composition/EditorShellAssetBuilder.cpp @@ -1,10 +1,10 @@ #include "EditorShellAssetBuilder.h" -#include "Composition/EditorShellAssetBuilderSupport.h" +#include "Composition/EditorShellAssetBuilderInternal.h" namespace XCEngine::UI::Editor::App { -using namespace CompositionSupport; +using namespace CompositionInternal; EditorShellAsset BuildEditorShellAsset(const std::filesystem::path& repoRoot) { EditorShellAsset asset = {}; diff --git a/new_editor/app/Composition/EditorShellAssetBuilder.h b/new_editor/app/Composition/EditorShellAssetBuilder.h index 9829d9f6..681771f3 100644 --- a/new_editor/app/Composition/EditorShellAssetBuilder.h +++ b/new_editor/app/Composition/EditorShellAssetBuilder.h @@ -1,19 +1,15 @@ #pragma once +#include "Composition/EditorShellVariant.h" + #include #include -#include #include #include namespace XCEngine::UI::Editor::App { -enum class EditorShellVariant : std::uint8_t { - Primary = 0, - DetachedWindow -}; - EditorShellAsset BuildEditorShellAsset(const std::filesystem::path& repoRoot); UIEditorShellInteractionDefinition BuildEditorShellInteractionDefinition( diff --git a/new_editor/app/Composition/EditorShellAssetBuilderSupport.h b/new_editor/app/Composition/EditorShellAssetBuilderInternal.h similarity index 83% rename from new_editor/app/Composition/EditorShellAssetBuilderSupport.h rename to new_editor/app/Composition/EditorShellAssetBuilderInternal.h index e97ba626..7b8ba5ed 100644 --- a/new_editor/app/Composition/EditorShellAssetBuilderSupport.h +++ b/new_editor/app/Composition/EditorShellAssetBuilderInternal.h @@ -8,7 +8,7 @@ #include #include -namespace XCEngine::UI::Editor::App::CompositionSupport { +namespace XCEngine::UI::Editor::App::CompositionInternal { UIEditorPanelRegistry BuildEditorPanelRegistry(); UIEditorWorkspaceModel BuildEditorWorkspaceModel(); @@ -20,4 +20,4 @@ std::string ResolveEditorPanelTitle( const UIEditorPanelRegistry& registry, std::string_view panelId); -} // namespace XCEngine::UI::Editor::App::CompositionSupport +} // namespace XCEngine::UI::Editor::App::CompositionInternal diff --git a/new_editor/app/Composition/EditorShellAssetCommands.cpp b/new_editor/app/Composition/EditorShellAssetCommands.cpp index f8751b69..d6bcb007 100644 --- a/new_editor/app/Composition/EditorShellAssetCommands.cpp +++ b/new_editor/app/Composition/EditorShellAssetCommands.cpp @@ -1,10 +1,10 @@ -#include "EditorShellAssetBuilderSupport.h" +#include "EditorShellAssetBuilderInternal.h" #include #include -namespace XCEngine::UI::Editor::App::CompositionSupport { +namespace XCEngine::UI::Editor::App::CompositionInternal { namespace { @@ -148,4 +148,4 @@ std::vector BuildEditorShortcutBindings() { }; } -} // namespace XCEngine::UI::Editor::App::CompositionSupport +} // namespace XCEngine::UI::Editor::App::CompositionInternal diff --git a/new_editor/app/Composition/EditorShellAssetDefinition.cpp b/new_editor/app/Composition/EditorShellAssetDefinition.cpp index 1015a294..0b0e4f58 100644 --- a/new_editor/app/Composition/EditorShellAssetDefinition.cpp +++ b/new_editor/app/Composition/EditorShellAssetDefinition.cpp @@ -1,8 +1,8 @@ -#include "EditorShellAssetBuilderSupport.h" +#include "EditorShellAssetBuilderInternal.h" #include -namespace XCEngine::UI::Editor::App::CompositionSupport { +namespace XCEngine::UI::Editor::App::CompositionInternal { namespace { @@ -72,4 +72,4 @@ std::string ResolveEditorPanelTitle( return panelId.empty() ? std::string("(none)") : std::string(panelId); } -} // namespace XCEngine::UI::Editor::App::CompositionSupport +} // namespace XCEngine::UI::Editor::App::CompositionInternal diff --git a/new_editor/app/Composition/EditorShellAssetLayout.cpp b/new_editor/app/Composition/EditorShellAssetLayout.cpp index 2bfc6e3f..748dc8c7 100644 --- a/new_editor/app/Composition/EditorShellAssetLayout.cpp +++ b/new_editor/app/Composition/EditorShellAssetLayout.cpp @@ -1,6 +1,6 @@ -#include "EditorShellAssetBuilderSupport.h" +#include "EditorShellAssetBuilderInternal.h" -namespace XCEngine::UI::Editor::App::CompositionSupport { +namespace XCEngine::UI::Editor::App::CompositionInternal { UIEditorPanelRegistry BuildEditorPanelRegistry() { UIEditorPanelRegistry registry = {}; @@ -73,4 +73,4 @@ UIEditorWorkspaceModel BuildEditorWorkspaceModel() { return workspace; } -} // namespace XCEngine::UI::Editor::App::CompositionSupport +} // namespace XCEngine::UI::Editor::App::CompositionInternal diff --git a/new_editor/app/Composition/EditorShellAssetMenu.cpp b/new_editor/app/Composition/EditorShellAssetMenu.cpp index 2cec6df1..2ad70902 100644 --- a/new_editor/app/Composition/EditorShellAssetMenu.cpp +++ b/new_editor/app/Composition/EditorShellAssetMenu.cpp @@ -1,8 +1,8 @@ -#include "EditorShellAssetBuilderSupport.h" +#include "EditorShellAssetBuilderInternal.h" #include -namespace XCEngine::UI::Editor::App::CompositionSupport { +namespace XCEngine::UI::Editor::App::CompositionInternal { namespace { @@ -164,4 +164,4 @@ UIEditorMenuModel BuildEditorMenuModel() { return model; } -} // namespace XCEngine::UI::Editor::App::CompositionSupport +} // namespace XCEngine::UI::Editor::App::CompositionInternal diff --git a/new_editor/app/Composition/EditorShellRuntime.cpp b/new_editor/app/Composition/EditorShellRuntime.cpp index 7dc6170d..18b3bce6 100644 --- a/new_editor/app/Composition/EditorShellRuntime.cpp +++ b/new_editor/app/Composition/EditorShellRuntime.cpp @@ -1,5 +1,12 @@ #include "Composition/EditorShellRuntime.h" +#include "State/EditorContext.h" + +#include +#include + +#include + namespace XCEngine::UI::Editor::App { void EditorShellRuntime::Initialize( diff --git a/new_editor/app/Composition/EditorShellRuntime.h b/new_editor/app/Composition/EditorShellRuntime.h index 5d3140cb..727f3025 100644 --- a/new_editor/app/Composition/EditorShellRuntime.h +++ b/new_editor/app/Composition/EditorShellRuntime.h @@ -1,6 +1,6 @@ #pragma once -#include "State/EditorContext.h" +#include "Composition/EditorShellVariant.h" #include "Features/Console/ConsolePanel.h" #include "Rendering/Assets/BuiltInIcons.h" #include "Features/Hierarchy/HierarchyPanel.h" @@ -9,14 +9,10 @@ #include "Rendering/Viewport/ViewportHostService.h" #include "Composition/WorkspaceEventSync.h" -#include -#include - #include #include #include -#include #include #include @@ -25,6 +21,25 @@ namespace XCEngine::UI::Editor::App { +class EditorContext; + +} // namespace XCEngine::UI::Editor::App + +namespace XCEngine::UI::Editor::Host { + +class D3D12WindowRenderer; +class NativeRenderer; + +} // namespace XCEngine::UI::Editor::Host + +namespace XCEngine::Rendering { + +class RenderContext; + +} // namespace XCEngine::Rendering + +namespace XCEngine::UI::Editor::App { + class EditorShellRuntime { public: void Initialize( diff --git a/new_editor/app/Composition/EditorShellRuntimeSupport.h b/new_editor/app/Composition/EditorShellRuntimeInternal.h similarity index 90% rename from new_editor/app/Composition/EditorShellRuntimeSupport.h rename to new_editor/app/Composition/EditorShellRuntimeInternal.h index e479930d..1a091039 100644 --- a/new_editor/app/Composition/EditorShellRuntimeSupport.h +++ b/new_editor/app/Composition/EditorShellRuntimeInternal.h @@ -4,7 +4,7 @@ #include -namespace XCEngine::UI::Editor::App::RuntimeSupport { +namespace XCEngine::UI::Editor::App::Internal { void ApplyViewportFramesToShellFrame( UIEditorShellInteractionFrame& shellFrame, @@ -30,4 +30,4 @@ void AppendShellPopups( const UIEditorShellInteractionPalette& palette, const UIEditorShellInteractionMetrics& metrics); -} // namespace XCEngine::UI::Editor::App::RuntimeSupport +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Composition/EditorShellRuntimeRendering.cpp b/new_editor/app/Composition/EditorShellRuntimeRendering.cpp index db1ffdfa..ddc22931 100644 --- a/new_editor/app/Composition/EditorShellRuntimeRendering.cpp +++ b/new_editor/app/Composition/EditorShellRuntimeRendering.cpp @@ -1,10 +1,10 @@ -#include "Composition/EditorShellRuntimeSupport.h" +#include "Composition/EditorShellRuntimeInternal.h" #include #include -namespace XCEngine::UI::Editor::App::RuntimeSupport { +namespace XCEngine::UI::Editor::App::Internal { using ::XCEngine::UI::UIDrawList; @@ -47,12 +47,12 @@ void AppendShellPopups( } } -} // namespace XCEngine::UI::Editor::App::RuntimeSupport +} // namespace XCEngine::UI::Editor::App::Internal namespace XCEngine::UI::Editor::App { using ::XCEngine::UI::UIDrawList; -using namespace RuntimeSupport; +using namespace Internal; void EditorShellRuntime::RenderRequestedViewports( const ::XCEngine::Rendering::RenderContext& renderContext) { diff --git a/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp b/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp index c771653c..f502f53f 100644 --- a/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp +++ b/new_editor/app/Composition/EditorShellRuntimeUpdate.cpp @@ -1,8 +1,10 @@ -#include "Composition/EditorShellRuntimeSupport.h" +#include "Composition/EditorShellRuntimeInternal.h" + +#include "State/EditorContext.h" #include -namespace XCEngine::UI::Editor::App::RuntimeSupport { +namespace XCEngine::UI::Editor::App::Internal { namespace { @@ -93,11 +95,11 @@ std::vector FilterHostedContentInputEventsForShellOwnership( return filteredEvents; } -} // namespace XCEngine::UI::Editor::App::RuntimeSupport +} // namespace XCEngine::UI::Editor::App::Internal namespace XCEngine::UI::Editor::App { -using namespace RuntimeSupport; +using namespace Internal; void EditorShellRuntime::Update( EditorContext& context, diff --git a/new_editor/app/Composition/EditorShellRuntimeViewport.cpp b/new_editor/app/Composition/EditorShellRuntimeViewport.cpp index 9fac20a2..3df05932 100644 --- a/new_editor/app/Composition/EditorShellRuntimeViewport.cpp +++ b/new_editor/app/Composition/EditorShellRuntimeViewport.cpp @@ -1,6 +1,6 @@ -#include "Composition/EditorShellRuntimeSupport.h" +#include "Composition/EditorShellRuntimeInternal.h" -namespace XCEngine::UI::Editor::App::RuntimeSupport { +namespace XCEngine::UI::Editor::App::Internal { namespace { @@ -79,4 +79,4 @@ void ApplyViewportFramesToShellFrame( applyToViewportFrames(shellFrame.shellFrame.workspaceFrame.viewportFrames); } -} // namespace XCEngine::UI::Editor::App::RuntimeSupport +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Composition/EditorShellVariant.h b/new_editor/app/Composition/EditorShellVariant.h new file mode 100644 index 00000000..e59000f6 --- /dev/null +++ b/new_editor/app/Composition/EditorShellVariant.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace XCEngine::UI::Editor::App { + +enum class EditorShellVariant : std::uint8_t { + Primary = 0, + DetachedWindow +}; + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp index a2a5ddc9..533b753c 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp +++ b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp @@ -1,8 +1,32 @@ -#include "HierarchyPanelSupport.h" +#include "HierarchyPanelInternal.h" + +#include namespace XCEngine::UI::Editor::App { -using namespace HierarchyPanelSupport; +using namespace HierarchyPanelInternal; + +namespace { + +UIEditorHostCommandEvaluationResult BuildEvaluationResult( + bool executable, + std::string message) { + UIEditorHostCommandEvaluationResult result = {}; + result.executable = executable; + result.message = std::move(message); + return result; +} + +UIEditorHostCommandDispatchResult BuildDispatchResult( + bool commandExecuted, + std::string message) { + UIEditorHostCommandDispatchResult result = {}; + result.commandExecuted = commandExecuted; + result.message = std::move(message); + return result; +} + +} // namespace void HierarchyPanel::Initialize() { m_model = HierarchyModel::BuildDefault(); @@ -119,4 +143,434 @@ const std::vector& HierarchyPanel::GetFrameEvents() const return m_frameEvents; } +UIEditorHostCommandEvaluationResult HierarchyPanel::EvaluateEditCommand( + std::string_view commandId) const { + const HierarchyNode* selectedNode = GetSelectedNode(); + if (selectedNode == nullptr) { + return BuildEvaluationResult(false, "Select a hierarchy object first."); + } + + if (commandId == "edit.rename") { + return BuildEvaluationResult( + true, + "Rename hierarchy object '" + selectedNode->label + "'."); + } + + if (commandId == "edit.delete") { + return BuildEvaluationResult( + true, + "Delete hierarchy object '" + selectedNode->label + "'."); + } + + if (commandId == "edit.duplicate") { + return BuildEvaluationResult( + true, + "Duplicate hierarchy object '" + selectedNode->label + "'."); + } + + if (commandId == "edit.cut" || + commandId == "edit.copy" || + commandId == "edit.paste") { + return BuildEvaluationResult( + false, + "Hierarchy clipboard has no bound transfer owner in the current shell."); + } + + return BuildEvaluationResult(false, "Hierarchy does not expose this edit command."); +} + +UIEditorHostCommandDispatchResult HierarchyPanel::DispatchEditCommand( + std::string_view commandId) { + const UIEditorHostCommandEvaluationResult evaluation = EvaluateEditCommand(commandId); + if (!evaluation.executable) { + return BuildDispatchResult(false, evaluation.message); + } + + const HierarchyNode* selectedNode = GetSelectedNode(); + if (selectedNode == nullptr) { + return BuildDispatchResult(false, "Select a hierarchy object first."); + } + + const std::string selectedNodeId = selectedNode->nodeId; + const std::string selectedNodeLabel = selectedNode->label; + + if (commandId == "edit.rename") { + EmitRenameRequestedEvent(selectedNodeId); + return BuildDispatchResult( + true, + "Hierarchy rename requested for '" + selectedNodeLabel + "'."); + } + + if (commandId == "edit.delete") { + if (!m_model.DeleteNode(selectedNodeId)) { + return BuildDispatchResult(false, "Failed to delete the selected hierarchy object."); + } + + RebuildItems(); + EmitSelectionEvent(); + return BuildDispatchResult( + true, + "Deleted hierarchy object '" + selectedNodeLabel + "'."); + } + + if (commandId == "edit.duplicate") { + const std::string duplicatedNodeId = m_model.DuplicateNode(selectedNodeId); + if (duplicatedNodeId.empty()) { + return BuildDispatchResult(false, "Failed to duplicate the selected hierarchy object."); + } + + RebuildItems(); + m_selection.SetSelection(duplicatedNodeId); + EmitSelectionEvent(); + + const HierarchyNode* duplicatedNode = m_model.FindNode(duplicatedNodeId); + const std::string duplicatedLabel = + duplicatedNode != nullptr ? duplicatedNode->label : selectedNodeLabel; + return BuildDispatchResult( + true, + "Duplicated hierarchy object '" + duplicatedLabel + "'."); + } + + return BuildDispatchResult(false, "Hierarchy does not expose this edit command."); +} + +std::vector HierarchyPanel::BuildInteractionInputEvents( + const std::vector& inputEvents, + const UIRect& bounds, + bool allowInteraction, + bool panelActive) const { + const std::vector rawEvents = FilterHierarchyInputEvents( + bounds, + inputEvents, + allowInteraction, + panelActive, + HasActivePointerCapture()); + + struct DragPreviewState { + std::string armedItemId = {}; + UIPoint pressPosition = {}; + bool armed = false; + bool dragging = false; + }; + + const Widgets::UIEditorTreeViewLayout layout = + m_treeFrame.layout.bounds.width > 0.0f + ? m_treeFrame.layout + : Widgets::BuildUIEditorTreeViewLayout( + bounds, + m_treeItems, + m_expansion, + ResolveUIEditorTreeViewMetrics()); + DragPreviewState preview = {}; + preview.armed = m_dragState.armed; + preview.armedItemId = m_dragState.armedItemId; + preview.pressPosition = m_dragState.pressPosition; + preview.dragging = m_dragState.dragging; + + std::vector filteredEvents = {}; + filteredEvents.reserve(rawEvents.size()); + for (const UIInputEvent& event : rawEvents) { + bool suppress = false; + + switch (event.type) { + case UIInputEventType::PointerButtonDown: + if (event.pointerButton == UIPointerButton::Left) { + UIEditorTreeViewHitTarget hitTarget = {}; + const Widgets::UIEditorTreeViewItem* hitItem = + ResolveHitItem(layout, m_treeItems, 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) >= + kDragThreshold * kDragThreshold) { + 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: + break; + } + + if (!suppress) { + filteredEvents.push_back(event); + } + } + + return filteredEvents; +} + +void HierarchyPanel::ProcessDragAndFrameEvents( + const std::vector& inputEvents, + const UIRect& bounds, + bool allowInteraction, + bool panelActive) { + const std::vector filteredEvents = FilterHierarchyInputEvents( + bounds, + inputEvents, + allowInteraction, + panelActive, + HasActivePointerCapture()); + + if (m_treeFrame.result.selectionChanged) { + EmitSelectionEvent(); + } + if (m_treeFrame.result.renameRequested && + !m_treeFrame.result.renameItemId.empty()) { + Event event = {}; + event.kind = EventKind::RenameRequested; + event.itemId = m_treeFrame.result.renameItemId; + if (const HierarchyNode* node = m_model.FindNode(event.itemId); node != nullptr) { + event.label = node->label; + } + m_frameEvents.push_back(std::move(event)); + } + + for (const UIInputEvent& event : filteredEvents) { + switch (event.type) { + case UIInputEventType::PointerButtonDown: + if (event.pointerButton == UIPointerButton::Left) { + UIEditorTreeViewHitTarget hitTarget = {}; + const Widgets::UIEditorTreeViewItem* hitItem = + ResolveHitItem(m_treeFrame.layout, m_treeItems, event.position, &hitTarget); + if (hitItem != nullptr && hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { + m_dragState.armed = true; + m_dragState.armedItemId = hitItem->itemId; + m_dragState.pressPosition = event.position; + } else { + m_dragState.armed = false; + m_dragState.armedItemId.clear(); + } + } + break; + + case UIInputEventType::PointerMove: + if (m_dragState.armed && + !m_dragState.dragging && + ComputeSquaredDistance(event.position, m_dragState.pressPosition) >= + kDragThreshold * kDragThreshold) { + m_dragState.dragging = !m_dragState.armedItemId.empty(); + m_dragState.draggedItemId = m_dragState.armedItemId; + m_dragState.dropTargetItemId.clear(); + m_dragState.dropToRoot = false; + m_dragState.validDropTarget = false; + if (m_dragState.dragging) { + m_dragState.requestPointerCapture = true; + if (!m_selection.IsSelected(m_dragState.draggedItemId)) { + m_selection.SetSelection(m_dragState.draggedItemId); + EmitSelectionEvent(); + } + } + } + + if (m_dragState.dragging) { + UIEditorTreeViewHitTarget hitTarget = {}; + const Widgets::UIEditorTreeViewItem* hitItem = + ResolveHitItem(m_treeFrame.layout, m_treeItems, event.position, &hitTarget); + + m_dragState.dropTargetItemId.clear(); + m_dragState.dropToRoot = false; + m_dragState.validDropTarget = false; + + if (hitItem != nullptr && + (hitTarget.kind == UIEditorTreeViewHitTargetKind::Row || + hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure)) { + m_dragState.dropTargetItemId = hitItem->itemId; + m_dragState.validDropTarget = + m_model.CanReparent(m_dragState.draggedItemId, m_dragState.dropTargetItemId); + } else if (ContainsPoint(bounds, event.position)) { + m_dragState.dropToRoot = true; + m_dragState.validDropTarget = + m_model.GetParentId(m_dragState.draggedItemId).has_value(); + } + } + break; + + case UIInputEventType::PointerButtonUp: + if (event.pointerButton != UIPointerButton::Left) { + break; + } + + if (m_dragState.dragging) { + if (m_dragState.validDropTarget) { + const std::string draggedItemId = m_dragState.draggedItemId; + const std::string dropTargetItemId = m_dragState.dropTargetItemId; + const bool changed = + m_dragState.dropToRoot + ? m_model.MoveToRoot(draggedItemId) + : m_model.Reparent(draggedItemId, dropTargetItemId); + if (changed) { + RebuildItems(); + EmitReparentEvent( + m_dragState.dropToRoot ? EventKind::MovedToRoot : EventKind::Reparented, + draggedItemId, + dropTargetItemId); + } + } + + m_dragState.armed = false; + m_dragState.dragging = false; + m_dragState.armedItemId.clear(); + m_dragState.draggedItemId.clear(); + m_dragState.dropTargetItemId.clear(); + m_dragState.dropToRoot = false; + m_dragState.validDropTarget = false; + m_dragState.requestPointerRelease = true; + } else { + m_dragState.armed = false; + m_dragState.armedItemId.clear(); + } + break; + + case UIInputEventType::FocusLost: + if (m_dragState.dragging) { + m_dragState.requestPointerRelease = true; + } + m_dragState = {}; + break; + + default: + break; + } + } +} + +void HierarchyPanel::Update( + const UIEditorPanelContentHostFrame& contentHostFrame, + const std::vector& inputEvents, + bool allowInteraction, + bool panelActive) { + ResetTransientState(); + + const UIEditorPanelContentHostPanelState* panelState = + FindMountedHierarchyPanel(contentHostFrame); + if (panelState == nullptr) { + m_visible = false; + m_treeFrame = {}; + m_dragState = {}; + return; + } + + if (m_treeItems.empty()) { + RebuildItems(); + } + + m_visible = true; + const std::vector interactionEvents = + BuildInteractionInputEvents( + inputEvents, + panelState->bounds, + allowInteraction, + panelActive); + m_treeFrame = UpdateUIEditorTreeViewInteraction( + m_treeInteractionState, + m_selection, + m_expansion, + panelState->bounds, + m_treeItems, + interactionEvents, + ResolveUIEditorTreeViewMetrics()); + ProcessDragAndFrameEvents( + inputEvents, + panelState->bounds, + allowInteraction, + panelActive); +} + +void HierarchyPanel::Append(UIDrawList& drawList) const { + if (!m_visible || + m_treeFrame.layout.bounds.width <= 0.0f || + m_treeFrame.layout.bounds.height <= 0.0f) { + return; + } + + const Widgets::UIEditorTreeViewPalette palette = ResolveUIEditorTreeViewPalette(); + const Widgets::UIEditorTreeViewMetrics metrics = ResolveUIEditorTreeViewMetrics(); + AppendUIEditorTreeViewBackground( + drawList, + m_treeFrame.layout, + m_treeItems, + m_selection, + m_treeInteractionState.treeViewState, + palette, + metrics); + AppendUIEditorTreeViewForeground( + drawList, + m_treeFrame.layout, + m_treeItems, + palette, + metrics); + + if (!m_dragState.dragging || !m_dragState.validDropTarget) { + return; + } + + if (m_dragState.dropToRoot) { + drawList.AddRectOutline( + m_treeFrame.layout.bounds, + kDragPreviewColor, + 1.0f, + 0.0f); + return; + } + + const std::size_t visibleIndex = FindVisibleIndexForItemId( + m_treeFrame.layout, + m_treeItems, + m_dragState.dropTargetItemId); + if (visibleIndex == UIEditorTreeViewInvalidIndex || + visibleIndex >= m_treeFrame.layout.rowRects.size()) { + return; + } + + drawList.AddRectOutline( + m_treeFrame.layout.rowRects[visibleIndex], + kDragPreviewColor, + 1.0f, + 0.0f); +} + } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanelCommands.cpp b/new_editor/app/Features/Hierarchy/HierarchyPanelCommands.cpp deleted file mode 100644 index 6d649ae8..00000000 --- a/new_editor/app/Features/Hierarchy/HierarchyPanelCommands.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "HierarchyPanelSupport.h" - -#include - -namespace XCEngine::UI::Editor::App { - -namespace { - -UIEditorHostCommandEvaluationResult BuildEvaluationResult( - bool executable, - std::string message) { - UIEditorHostCommandEvaluationResult result = {}; - result.executable = executable; - result.message = std::move(message); - return result; -} - -UIEditorHostCommandDispatchResult BuildDispatchResult( - bool commandExecuted, - std::string message) { - UIEditorHostCommandDispatchResult result = {}; - result.commandExecuted = commandExecuted; - result.message = std::move(message); - return result; -} - -} // namespace - -UIEditorHostCommandEvaluationResult HierarchyPanel::EvaluateEditCommand( - std::string_view commandId) const { - const HierarchyNode* selectedNode = GetSelectedNode(); - if (selectedNode == nullptr) { - return BuildEvaluationResult(false, "Select a hierarchy object first."); - } - - if (commandId == "edit.rename") { - return BuildEvaluationResult( - true, - "Rename hierarchy object '" + selectedNode->label + "'."); - } - - if (commandId == "edit.delete") { - return BuildEvaluationResult( - true, - "Delete hierarchy object '" + selectedNode->label + "'."); - } - - if (commandId == "edit.duplicate") { - return BuildEvaluationResult( - true, - "Duplicate hierarchy object '" + selectedNode->label + "'."); - } - - if (commandId == "edit.cut" || - commandId == "edit.copy" || - commandId == "edit.paste") { - return BuildEvaluationResult( - false, - "Hierarchy clipboard has no bound transfer owner in the current shell."); - } - - return BuildEvaluationResult(false, "Hierarchy does not expose this edit command."); -} - -UIEditorHostCommandDispatchResult HierarchyPanel::DispatchEditCommand( - std::string_view commandId) { - const UIEditorHostCommandEvaluationResult evaluation = EvaluateEditCommand(commandId); - if (!evaluation.executable) { - return BuildDispatchResult(false, evaluation.message); - } - - const HierarchyNode* selectedNode = GetSelectedNode(); - if (selectedNode == nullptr) { - return BuildDispatchResult(false, "Select a hierarchy object first."); - } - - const std::string selectedNodeId = selectedNode->nodeId; - const std::string selectedNodeLabel = selectedNode->label; - - if (commandId == "edit.rename") { - EmitRenameRequestedEvent(selectedNodeId); - return BuildDispatchResult( - true, - "Hierarchy rename requested for '" + selectedNodeLabel + "'."); - } - - if (commandId == "edit.delete") { - if (!m_model.DeleteNode(selectedNodeId)) { - return BuildDispatchResult(false, "Failed to delete the selected hierarchy object."); - } - - RebuildItems(); - EmitSelectionEvent(); - return BuildDispatchResult( - true, - "Deleted hierarchy object '" + selectedNodeLabel + "'."); - } - - if (commandId == "edit.duplicate") { - const std::string duplicatedNodeId = m_model.DuplicateNode(selectedNodeId); - if (duplicatedNodeId.empty()) { - return BuildDispatchResult(false, "Failed to duplicate the selected hierarchy object."); - } - - RebuildItems(); - m_selection.SetSelection(duplicatedNodeId); - EmitSelectionEvent(); - - const HierarchyNode* duplicatedNode = m_model.FindNode(duplicatedNodeId); - const std::string duplicatedLabel = - duplicatedNode != nullptr ? duplicatedNode->label : selectedNodeLabel; - return BuildDispatchResult( - true, - "Duplicated hierarchy object '" + duplicatedLabel + "'."); - } - - return BuildDispatchResult(false, "Hierarchy does not expose this edit command."); -} - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanelInteraction.cpp b/new_editor/app/Features/Hierarchy/HierarchyPanelInteraction.cpp deleted file mode 100644 index 6ed2d90e..00000000 --- a/new_editor/app/Features/Hierarchy/HierarchyPanelInteraction.cpp +++ /dev/null @@ -1,293 +0,0 @@ -#include "HierarchyPanelSupport.h" - -namespace XCEngine::UI::Editor::App { - -using namespace HierarchyPanelSupport; - -std::vector HierarchyPanel::BuildInteractionInputEvents( - const std::vector& inputEvents, - const UIRect& bounds, - bool allowInteraction, - bool panelActive) const { - const std::vector rawEvents = FilterHierarchyInputEvents( - bounds, - inputEvents, - allowInteraction, - panelActive, - HasActivePointerCapture()); - - struct DragPreviewState { - std::string armedItemId = {}; - UIPoint pressPosition = {}; - bool armed = false; - bool dragging = false; - }; - - const Widgets::UIEditorTreeViewLayout layout = - m_treeFrame.layout.bounds.width > 0.0f - ? m_treeFrame.layout - : Widgets::BuildUIEditorTreeViewLayout( - bounds, - m_treeItems, - m_expansion, - BuildEditorTreeViewMetrics()); - DragPreviewState preview = {}; - preview.armed = m_dragState.armed; - preview.armedItemId = m_dragState.armedItemId; - preview.pressPosition = m_dragState.pressPosition; - preview.dragging = m_dragState.dragging; - - std::vector filteredEvents = {}; - filteredEvents.reserve(rawEvents.size()); - for (const UIInputEvent& event : rawEvents) { - bool suppress = false; - - switch (event.type) { - case UIInputEventType::PointerButtonDown: - if (event.pointerButton == UIPointerButton::Left) { - UIEditorTreeViewHitTarget hitTarget = {}; - const Widgets::UIEditorTreeViewItem* hitItem = - ResolveHitItem(layout, m_treeItems, 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) >= - kDragThreshold * kDragThreshold) { - 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: - break; - } - - if (!suppress) { - filteredEvents.push_back(event); - } - } - - return filteredEvents; -} - -void HierarchyPanel::ProcessDragAndFrameEvents( - const std::vector& inputEvents, - const UIRect& bounds, - bool allowInteraction, - bool panelActive) { - const std::vector filteredEvents = FilterHierarchyInputEvents( - bounds, - inputEvents, - allowInteraction, - panelActive, - HasActivePointerCapture()); - - if (m_treeFrame.result.selectionChanged) { - EmitSelectionEvent(); - } - if (m_treeFrame.result.renameRequested && - !m_treeFrame.result.renameItemId.empty()) { - Event event = {}; - event.kind = EventKind::RenameRequested; - event.itemId = m_treeFrame.result.renameItemId; - if (const HierarchyNode* node = m_model.FindNode(event.itemId); node != nullptr) { - event.label = node->label; - } - m_frameEvents.push_back(std::move(event)); - } - - for (const UIInputEvent& event : filteredEvents) { - switch (event.type) { - case UIInputEventType::PointerButtonDown: - if (event.pointerButton == UIPointerButton::Left) { - UIEditorTreeViewHitTarget hitTarget = {}; - const Widgets::UIEditorTreeViewItem* hitItem = - ResolveHitItem(m_treeFrame.layout, m_treeItems, event.position, &hitTarget); - if (hitItem != nullptr && hitTarget.kind == UIEditorTreeViewHitTargetKind::Row) { - m_dragState.armed = true; - m_dragState.armedItemId = hitItem->itemId; - m_dragState.pressPosition = event.position; - } else { - m_dragState.armed = false; - m_dragState.armedItemId.clear(); - } - } - break; - - case UIInputEventType::PointerMove: - if (m_dragState.armed && - !m_dragState.dragging && - ComputeSquaredDistance(event.position, m_dragState.pressPosition) >= - kDragThreshold * kDragThreshold) { - m_dragState.dragging = !m_dragState.armedItemId.empty(); - m_dragState.draggedItemId = m_dragState.armedItemId; - m_dragState.dropTargetItemId.clear(); - m_dragState.dropToRoot = false; - m_dragState.validDropTarget = false; - if (m_dragState.dragging) { - m_dragState.requestPointerCapture = true; - if (!m_selection.IsSelected(m_dragState.draggedItemId)) { - m_selection.SetSelection(m_dragState.draggedItemId); - EmitSelectionEvent(); - } - } - } - - if (m_dragState.dragging) { - UIEditorTreeViewHitTarget hitTarget = {}; - const Widgets::UIEditorTreeViewItem* hitItem = - ResolveHitItem(m_treeFrame.layout, m_treeItems, event.position, &hitTarget); - - m_dragState.dropTargetItemId.clear(); - m_dragState.dropToRoot = false; - m_dragState.validDropTarget = false; - - if (hitItem != nullptr && - (hitTarget.kind == UIEditorTreeViewHitTargetKind::Row || - hitTarget.kind == UIEditorTreeViewHitTargetKind::Disclosure)) { - m_dragState.dropTargetItemId = hitItem->itemId; - m_dragState.validDropTarget = - m_model.CanReparent(m_dragState.draggedItemId, m_dragState.dropTargetItemId); - } else if (ContainsPoint(bounds, event.position)) { - m_dragState.dropToRoot = true; - m_dragState.validDropTarget = - m_model.GetParentId(m_dragState.draggedItemId).has_value(); - } - } - break; - - case UIInputEventType::PointerButtonUp: - if (event.pointerButton != UIPointerButton::Left) { - break; - } - - if (m_dragState.dragging) { - if (m_dragState.validDropTarget) { - const std::string draggedItemId = m_dragState.draggedItemId; - const std::string dropTargetItemId = m_dragState.dropTargetItemId; - const bool changed = - m_dragState.dropToRoot - ? m_model.MoveToRoot(draggedItemId) - : m_model.Reparent(draggedItemId, dropTargetItemId); - if (changed) { - RebuildItems(); - EmitReparentEvent( - m_dragState.dropToRoot ? EventKind::MovedToRoot : EventKind::Reparented, - draggedItemId, - dropTargetItemId); - } - } - - m_dragState.armed = false; - m_dragState.dragging = false; - m_dragState.armedItemId.clear(); - m_dragState.draggedItemId.clear(); - m_dragState.dropTargetItemId.clear(); - m_dragState.dropToRoot = false; - m_dragState.validDropTarget = false; - m_dragState.requestPointerRelease = true; - } else { - m_dragState.armed = false; - m_dragState.armedItemId.clear(); - } - break; - - case UIInputEventType::FocusLost: - if (m_dragState.dragging) { - m_dragState.requestPointerRelease = true; - } - m_dragState = {}; - break; - - default: - break; - } - } -} - -void HierarchyPanel::Update( - const UIEditorPanelContentHostFrame& contentHostFrame, - const std::vector& inputEvents, - bool allowInteraction, - bool panelActive) { - ResetTransientState(); - - const UIEditorPanelContentHostPanelState* panelState = - FindMountedHierarchyPanel(contentHostFrame); - if (panelState == nullptr) { - m_visible = false; - m_treeFrame = {}; - m_dragState = {}; - return; - } - - if (m_treeItems.empty()) { - RebuildItems(); - } - - m_visible = true; - const std::vector interactionEvents = - BuildInteractionInputEvents( - inputEvents, - panelState->bounds, - allowInteraction, - panelActive); - m_treeFrame = UpdateUIEditorTreeViewInteraction( - m_treeInteractionState, - m_selection, - m_expansion, - panelState->bounds, - m_treeItems, - interactionEvents, - BuildEditorTreeViewMetrics()); - ProcessDragAndFrameEvents( - inputEvents, - panelState->bounds, - allowInteraction, - panelActive); -} - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanelSupport.h b/new_editor/app/Features/Hierarchy/HierarchyPanelInternal.cpp similarity index 64% rename from new_editor/app/Features/Hierarchy/HierarchyPanelSupport.h rename to new_editor/app/Features/Hierarchy/HierarchyPanelInternal.cpp index 8bee84a1..a287c16b 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanelSupport.h +++ b/new_editor/app/Features/Hierarchy/HierarchyPanelInternal.cpp @@ -1,59 +1,32 @@ -#pragma once +#include "HierarchyPanelInternal.h" -#include "HierarchyPanel.h" - -#include "Rendering/Assets/BuiltInIcons.h" -#include "UI/Styles/EditorTreeViewStyle.h" - -#include - -#include #include -#include -#include -namespace XCEngine::UI::Editor::App::HierarchyPanelSupport { +namespace XCEngine::UI::Editor::App::HierarchyPanelInternal { -using ::XCEngine::UI::UIColor; -using ::XCEngine::UI::UIDrawList; -using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIPoint; -using ::XCEngine::UI::UIPointerButton; -using ::XCEngine::UI::UIRect; -using Widgets::AppendUIEditorTreeViewBackground; -using Widgets::AppendUIEditorTreeViewForeground; -using Widgets::DoesUIEditorTreeViewItemHaveChildren; using Widgets::HitTestUIEditorTreeView; -using Widgets::IsUIEditorTreeViewPointInside; -using Widgets::UIEditorTreeViewHitTarget; -using Widgets::UIEditorTreeViewHitTargetKind; -using Widgets::UIEditorTreeViewInvalidIndex; -inline constexpr std::string_view kHierarchyPanelId = "hierarchy"; -inline constexpr float kDragThreshold = 4.0f; -inline constexpr UIColor kDragPreviewColor(0.92f, 0.92f, 0.92f, 0.42f); - -inline bool ContainsPoint(const UIRect& rect, const UIPoint& point) { +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) { +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 ::XCEngine::UI::UITextureHandle ResolveGameObjectIcon(const BuiltInIcons* icons) { +::XCEngine::UI::UITextureHandle ResolveGameObjectIcon(const BuiltInIcons* icons) { return icons != nullptr ? icons->Resolve(BuiltInIconKind::GameObject) : ::XCEngine::UI::UITextureHandle {}; } -inline std::vector FilterHierarchyInputEvents( +std::vector FilterHierarchyInputEvents( const UIRect& bounds, const std::vector& inputEvents, bool allowInteraction, @@ -99,11 +72,11 @@ inline std::vector FilterHierarchyInputEvents( return filteredEvents; } -inline const Widgets::UIEditorTreeViewItem* ResolveHitItem( +const Widgets::UIEditorTreeViewItem* ResolveHitItem( const Widgets::UIEditorTreeViewLayout& layout, const std::vector& items, const UIPoint& point, - UIEditorTreeViewHitTarget* hitTargetOutput = nullptr) { + UIEditorTreeViewHitTarget* hitTargetOutput) { const UIEditorTreeViewHitTarget hitTarget = HitTestUIEditorTreeView(layout, point); if (hitTargetOutput != nullptr) { *hitTargetOutput = hitTarget; @@ -116,7 +89,7 @@ inline const Widgets::UIEditorTreeViewItem* ResolveHitItem( return &items[hitTarget.itemIndex]; } -inline std::size_t FindVisibleIndexForItemId( +std::size_t FindVisibleIndexForItemId( const Widgets::UIEditorTreeViewLayout& layout, const std::vector& items, std::string_view itemId) { @@ -130,4 +103,4 @@ inline std::size_t FindVisibleIndexForItemId( return UIEditorTreeViewInvalidIndex; } -} // namespace XCEngine::UI::Editor::App::HierarchyPanelSupport +} // namespace XCEngine::UI::Editor::App::HierarchyPanelInternal diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanelInternal.h b/new_editor/app/Features/Hierarchy/HierarchyPanelInternal.h new file mode 100644 index 00000000..ee565c32 --- /dev/null +++ b/new_editor/app/Features/Hierarchy/HierarchyPanelInternal.h @@ -0,0 +1,46 @@ +#pragma once + +#include "HierarchyPanel.h" + +#include "Rendering/Assets/BuiltInIcons.h" + +#include +#include + +#include +#include + +namespace XCEngine::UI::Editor::App::HierarchyPanelInternal { + +using ::XCEngine::UI::UIColor; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; +using Widgets::UIEditorTreeViewHitTarget; +using Widgets::UIEditorTreeViewHitTargetKind; +using Widgets::UIEditorTreeViewInvalidIndex; + +inline constexpr std::string_view kHierarchyPanelId = "hierarchy"; +inline constexpr float kDragThreshold = 4.0f; +inline constexpr UIColor kDragPreviewColor(0.92f, 0.92f, 0.92f, 0.42f); + +bool ContainsPoint(const UIRect& rect, const UIPoint& point); +float ComputeSquaredDistance(const UIPoint& lhs, const UIPoint& rhs); +::XCEngine::UI::UITextureHandle ResolveGameObjectIcon(const BuiltInIcons* icons); +std::vector FilterHierarchyInputEvents( + const UIRect& bounds, + const std::vector& inputEvents, + bool allowInteraction, + bool panelActive, + bool captureActive); +const Widgets::UIEditorTreeViewItem* ResolveHitItem( + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + const UIPoint& point, + UIEditorTreeViewHitTarget* hitTargetOutput = nullptr); +std::size_t FindVisibleIndexForItemId( + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::string_view itemId); + +} // namespace XCEngine::UI::Editor::App::HierarchyPanelInternal diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanelRendering.cpp b/new_editor/app/Features/Hierarchy/HierarchyPanelRendering.cpp deleted file mode 100644 index b322ba54..00000000 --- a/new_editor/app/Features/Hierarchy/HierarchyPanelRendering.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "HierarchyPanelSupport.h" - -namespace XCEngine::UI::Editor::App { - -using namespace HierarchyPanelSupport; - -void HierarchyPanel::Append(UIDrawList& drawList) const { - if (!m_visible || - m_treeFrame.layout.bounds.width <= 0.0f || - m_treeFrame.layout.bounds.height <= 0.0f) { - return; - } - - const Widgets::UIEditorTreeViewPalette palette = BuildEditorTreeViewPalette(); - const Widgets::UIEditorTreeViewMetrics metrics = BuildEditorTreeViewMetrics(); - AppendUIEditorTreeViewBackground( - drawList, - m_treeFrame.layout, - m_treeItems, - m_selection, - m_treeInteractionState.treeViewState, - palette, - metrics); - AppendUIEditorTreeViewForeground( - drawList, - m_treeFrame.layout, - m_treeItems, - palette, - metrics); - - if (!m_dragState.dragging || !m_dragState.validDropTarget) { - return; - } - - if (m_dragState.dropToRoot) { - drawList.AddRectOutline( - m_treeFrame.layout.bounds, - kDragPreviewColor, - 1.0f, - 0.0f); - return; - } - - const std::size_t visibleIndex = FindVisibleIndexForItemId( - m_treeFrame.layout, - m_treeItems, - m_dragState.dropTargetItemId); - if (visibleIndex == UIEditorTreeViewInvalidIndex || - visibleIndex >= m_treeFrame.layout.rowRects.size()) { - return; - } - - drawList.AddRectOutline( - m_treeFrame.layout.rowRects[visibleIndex], - kDragPreviewColor, - 1.0f, - 0.0f); -} - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Project/ProjectBrowserModel.cpp b/new_editor/app/Features/Project/ProjectBrowserModel.cpp index a1bbc4b3..5545ce1e 100644 --- a/new_editor/app/Features/Project/ProjectBrowserModel.cpp +++ b/new_editor/app/Features/Project/ProjectBrowserModel.cpp @@ -1,8 +1,9 @@ -#include "ProjectBrowserModelSupport.h" +#include "ProjectBrowserModel.h" +#include "ProjectBrowserModelInternal.h" namespace XCEngine::UI::Editor::App { -using namespace ProjectBrowserModelSupport; +using namespace ProjectBrowserModelInternal; void ProjectBrowserModel::Initialize(const std::filesystem::path& repoRoot) { m_assetsRootPath = (repoRoot / "project/Assets").lexically_normal(); diff --git a/new_editor/app/Features/Project/ProjectBrowserModelAssets.cpp b/new_editor/app/Features/Project/ProjectBrowserModelAssets.cpp index 212cab42..cde48021 100644 --- a/new_editor/app/Features/Project/ProjectBrowserModelAssets.cpp +++ b/new_editor/app/Features/Project/ProjectBrowserModelAssets.cpp @@ -1,8 +1,9 @@ -#include "ProjectBrowserModelSupport.h" +#include "ProjectBrowserModel.h" +#include "ProjectBrowserModelInternal.h" namespace XCEngine::UI::Editor::App { -using namespace ProjectBrowserModelSupport; +using namespace ProjectBrowserModelInternal; void ProjectBrowserModel::RefreshAssetList() { EnsureValidCurrentFolder(); diff --git a/new_editor/app/Features/Project/ProjectBrowserModelFolders.cpp b/new_editor/app/Features/Project/ProjectBrowserModelFolders.cpp index db7696d8..57a2b594 100644 --- a/new_editor/app/Features/Project/ProjectBrowserModelFolders.cpp +++ b/new_editor/app/Features/Project/ProjectBrowserModelFolders.cpp @@ -1,8 +1,9 @@ -#include "ProjectBrowserModelSupport.h" +#include "ProjectBrowserModel.h" +#include "ProjectBrowserModelInternal.h" namespace XCEngine::UI::Editor::App { -using namespace ProjectBrowserModelSupport; +using namespace ProjectBrowserModelInternal; void ProjectBrowserModel::RefreshFolderTree() { m_folderEntries.clear(); diff --git a/new_editor/app/Features/Project/ProjectBrowserModelSupport.h b/new_editor/app/Features/Project/ProjectBrowserModelInternal.cpp similarity index 75% rename from new_editor/app/Features/Project/ProjectBrowserModelSupport.h rename to new_editor/app/Features/Project/ProjectBrowserModelInternal.cpp index fa3ece5d..b9119565 100644 --- a/new_editor/app/Features/Project/ProjectBrowserModelSupport.h +++ b/new_editor/app/Features/Project/ProjectBrowserModelInternal.cpp @@ -1,19 +1,14 @@ -#pragma once - -#include "ProjectBrowserModel.h" +#include "ProjectBrowserModelInternal.h" #include "Support/StringEncoding.h" #include #include -#include #include -namespace XCEngine::UI::Editor::App::ProjectBrowserModelSupport { +namespace XCEngine::UI::Editor::App::ProjectBrowserModelInternal { -inline constexpr std::string_view kAssetsRootId = "Assets"; - -inline std::string ToLowerCopy(std::string value) { +std::string ToLowerCopy(std::string value) { std::transform( value.begin(), value.end(), @@ -24,16 +19,16 @@ inline std::string ToLowerCopy(std::string value) { return value; } -inline std::string PathToUtf8String(const std::filesystem::path& path) { +std::string PathToUtf8String(const std::filesystem::path& path) { return Support::WideToUtf8(path.native()); } -inline std::string NormalizePathSeparators(std::string value) { +std::string NormalizePathSeparators(std::string value) { std::replace(value.begin(), value.end(), '\\', '/'); return value; } -inline std::string BuildRelativeItemId( +std::string BuildRelativeItemId( const std::filesystem::path& path, const std::filesystem::path& assetsRoot) { const std::filesystem::path relative = @@ -43,7 +38,7 @@ inline std::string BuildRelativeItemId( return normalized.empty() ? std::string(kAssetsRootId) : normalized; } -inline std::string BuildAssetDisplayName(const std::filesystem::path& path, bool directory) { +std::string BuildAssetDisplayName(const std::filesystem::path& path, bool directory) { if (directory) { return PathToUtf8String(path.filename()); } @@ -57,11 +52,11 @@ inline std::string BuildAssetDisplayName(const std::filesystem::path& path, bool return filename.substr(0u, extensionOffset); } -inline bool IsMetaFile(const std::filesystem::path& path) { +bool IsMetaFile(const std::filesystem::path& path) { return ToLowerCopy(path.extension().string()) == ".meta"; } -inline bool HasChildDirectories(const std::filesystem::path& folderPath) { +bool HasChildDirectories(const std::filesystem::path& folderPath) { std::error_code errorCode = {}; const std::filesystem::directory_iterator end = {}; for (std::filesystem::directory_iterator iterator(folderPath, errorCode); @@ -75,7 +70,7 @@ inline bool HasChildDirectories(const std::filesystem::path& folderPath) { return false; } -inline std::vector CollectSortedChildDirectories( +std::vector CollectSortedChildDirectories( const std::filesystem::path& folderPath) { std::vector paths = {}; std::error_code errorCode = {}; @@ -98,4 +93,4 @@ inline std::vector CollectSortedChildDirectories( return paths; } -} // namespace XCEngine::UI::Editor::App::ProjectBrowserModelSupport +} // namespace XCEngine::UI::Editor::App::ProjectBrowserModelInternal diff --git a/new_editor/app/Features/Project/ProjectBrowserModelInternal.h b/new_editor/app/Features/Project/ProjectBrowserModelInternal.h new file mode 100644 index 00000000..b78d12b8 --- /dev/null +++ b/new_editor/app/Features/Project/ProjectBrowserModelInternal.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include + +namespace XCEngine::UI::Editor::App::ProjectBrowserModelInternal { + +inline constexpr std::string_view kAssetsRootId = "Assets"; + +std::string ToLowerCopy(std::string value); +std::string PathToUtf8String(const std::filesystem::path& path); +std::string NormalizePathSeparators(std::string value); +std::string BuildRelativeItemId( + const std::filesystem::path& path, + const std::filesystem::path& assetsRoot); +std::string BuildAssetDisplayName(const std::filesystem::path& path, bool directory); +bool IsMetaFile(const std::filesystem::path& path); +bool HasChildDirectories(const std::filesystem::path& folderPath); +std::vector CollectSortedChildDirectories( + const std::filesystem::path& folderPath); + +} // namespace XCEngine::UI::Editor::App::ProjectBrowserModelInternal diff --git a/new_editor/app/Features/Project/ProjectPanel.cpp b/new_editor/app/Features/Project/ProjectPanel.cpp index da88f9f6..37ac67d2 100644 --- a/new_editor/app/Features/Project/ProjectPanel.cpp +++ b/new_editor/app/Features/Project/ProjectPanel.cpp @@ -1,10 +1,34 @@ -#include "ProjectPanelSupport.h" +#include "ProjectPanelInternal.h" + +#include #include namespace XCEngine::UI::Editor::App { -using namespace ProjectPanelSupport; +using namespace ProjectPanelInternal; + +namespace { + +UIEditorHostCommandEvaluationResult BuildEvaluationResult( + bool executable, + std::string message) { + UIEditorHostCommandEvaluationResult result = {}; + result.executable = executable; + result.message = std::move(message); + return result; +} + +UIEditorHostCommandDispatchResult BuildDispatchResult( + bool commandExecuted, + std::string message) { + UIEditorHostCommandDispatchResult result = {}; + result.commandExecuted = commandExecuted; + result.message = std::move(message); + return result; +} + +} // namespace void ProjectPanel::Initialize(const std::filesystem::path& repoRoot) { m_browserModel.Initialize(repoRoot); @@ -180,4 +204,559 @@ void ProjectPanel::EmitSelectionClearedEvent(EventSource source) { m_frameEvents.push_back(std::move(event)); } +UIEditorHostCommandEvaluationResult ProjectPanel::EvaluateEditCommand( + std::string_view commandId) const { + const std::optional target = ResolveEditCommandTarget(); + if (!target.has_value()) { + return BuildEvaluationResult(false, "Select an asset or folder in Project first."); + } + + if (target->assetsRoot && + (commandId == "edit.rename" || commandId == "edit.delete")) { + return BuildEvaluationResult(false, "The Assets root cannot be renamed or deleted."); + } + + if (commandId == "edit.rename") { + return BuildEvaluationResult( + true, + "Rename project item '" + target->displayName + "'."); + } + + if (commandId == "edit.delete") { + return BuildEvaluationResult( + false, + "Project delete is blocked until asset metadata ownership is wired."); + } + + if (commandId == "edit.duplicate") { + return BuildEvaluationResult( + false, + "Project duplicate is blocked until asset metadata rewrite is wired."); + } + + if (commandId == "edit.cut" || + commandId == "edit.copy" || + commandId == "edit.paste") { + return BuildEvaluationResult( + false, + "Project clipboard has no bound asset transfer owner in the current shell."); + } + + return BuildEvaluationResult(false, "Project does not expose this edit command."); +} + +UIEditorHostCommandDispatchResult ProjectPanel::DispatchEditCommand( + std::string_view commandId) { + const UIEditorHostCommandEvaluationResult evaluation = EvaluateEditCommand(commandId); + if (!evaluation.executable) { + return BuildDispatchResult(false, evaluation.message); + } + + if (commandId == "edit.rename") { + if (m_assetSelection.HasSelection()) { + if (const AssetEntry* asset = FindAssetEntry(m_assetSelection.GetSelectedId()); + asset != nullptr) { + EmitEvent(EventKind::RenameRequested, EventSource::None, asset); + return BuildDispatchResult( + true, + "Project rename requested for '" + asset->displayName + "'."); + } + } + + if (m_folderSelection.HasSelection()) { + if (const FolderEntry* folder = FindFolderEntry(m_folderSelection.GetSelectedId()); + folder != nullptr) { + EmitEvent(EventKind::RenameRequested, EventSource::None, folder); + return BuildDispatchResult( + true, + "Project rename requested for '" + folder->label + "'."); + } + } + + return BuildDispatchResult(false, "Select an asset or folder in Project first."); + } + + return BuildDispatchResult(false, "Project does not expose this edit command."); +} + +void ProjectPanel::ResetTransientFrames() { + m_treeFrame = {}; + m_frameEvents.clear(); + m_layout = {}; + m_hoveredAssetItemId.clear(); + m_hoveredBreadcrumbIndex = kInvalidLayoutIndex; + m_pressedBreadcrumbIndex = kInvalidLayoutIndex; + m_splitterHovered = false; + m_splitterDragging = false; +} + +void ProjectPanel::Update( + const UIEditorPanelContentHostFrame& contentHostFrame, + const std::vector& inputEvents, + bool allowInteraction, + bool panelActive) { + m_requestPointerCapture = false; + m_requestPointerRelease = false; + m_frameEvents.clear(); + + const UIEditorPanelContentHostPanelState* panelState = + FindMountedProjectPanel(contentHostFrame); + if (panelState == nullptr) { + if (m_splitterDragging) { + m_requestPointerRelease = true; + } + m_visible = false; + ResetTransientFrames(); + return; + } + + if (m_browserModel.GetTreeItems().empty()) { + m_browserModel.Refresh(); + SyncCurrentFolderSelection(); + } + + m_visible = true; + const std::vector filteredEvents = + FilterProjectPanelInputEvents( + panelState->bounds, + inputEvents, + allowInteraction, + panelActive, + m_splitterDragging); + + m_navigationWidth = ClampNavigationWidth(m_navigationWidth, panelState->bounds.width); + m_layout = BuildLayout(panelState->bounds); + const Widgets::UIEditorTreeViewMetrics treeMetrics = ResolveUIEditorTreeViewMetrics(); + const std::vector treeEvents = + FilterTreeInputEvents(filteredEvents, m_splitterDragging); + m_treeFrame = UpdateUIEditorTreeViewInteraction( + m_treeInteractionState, + m_folderSelection, + m_folderExpansion, + m_layout.treeRect, + m_browserModel.GetTreeItems(), + treeEvents, + treeMetrics); + + if (m_treeFrame.result.selectionChanged && + !m_treeFrame.result.selectedItemId.empty() && + m_treeFrame.result.selectedItemId != m_browserModel.GetCurrentFolderId()) { + NavigateToFolder(m_treeFrame.result.selectedItemId, EventSource::Tree); + m_layout = BuildLayout(panelState->bounds); + } + + for (const UIInputEvent& event : filteredEvents) { + switch (event.type) { + case UIInputEventType::FocusLost: + m_hoveredAssetItemId.clear(); + m_hoveredBreadcrumbIndex = kInvalidLayoutIndex; + m_pressedBreadcrumbIndex = kInvalidLayoutIndex; + m_splitterHovered = false; + if (m_splitterDragging) { + m_splitterDragging = false; + m_requestPointerRelease = true; + } + break; + + case UIInputEventType::PointerMove: { + if (m_splitterDragging) { + m_navigationWidth = + ClampNavigationWidth(event.position.x - panelState->bounds.x, panelState->bounds.width); + m_layout = BuildLayout(panelState->bounds); + } + + m_splitterHovered = + m_splitterDragging || ContainsPoint(m_layout.dividerRect, event.position); + m_hoveredBreadcrumbIndex = HitTestBreadcrumbItem(event.position); + const std::size_t hoveredAssetIndex = HitTestAssetTile(event.position); + const auto& assetEntries = m_browserModel.GetAssetEntries(); + m_hoveredAssetItemId = + hoveredAssetIndex < assetEntries.size() + ? assetEntries[hoveredAssetIndex].itemId + : std::string(); + break; + } + + case UIInputEventType::PointerLeave: + if (!m_splitterDragging) { + m_splitterHovered = false; + } + m_hoveredBreadcrumbIndex = kInvalidLayoutIndex; + m_hoveredAssetItemId.clear(); + break; + + case UIInputEventType::PointerButtonDown: + if (event.pointerButton == ::XCEngine::UI::UIPointerButton::Left) { + if (ContainsPoint(m_layout.dividerRect, event.position)) { + m_splitterDragging = true; + m_splitterHovered = true; + m_pressedBreadcrumbIndex = kInvalidLayoutIndex; + m_requestPointerCapture = true; + break; + } + + m_pressedBreadcrumbIndex = HitTestBreadcrumbItem(event.position); + + if (!ContainsPoint(m_layout.gridRect, event.position)) { + break; + } + + const auto& assetEntries = m_browserModel.GetAssetEntries(); + const std::size_t hitIndex = HitTestAssetTile(event.position); + if (hitIndex >= assetEntries.size()) { + if (m_assetSelection.HasSelection()) { + m_assetSelection.ClearSelection(); + EmitSelectionClearedEvent(EventSource::Background); + } + break; + } + + const AssetEntry& assetEntry = assetEntries[hitIndex]; + const bool alreadySelected = m_assetSelection.IsSelected(assetEntry.itemId); + const bool selectionChanged = m_assetSelection.SetSelection(assetEntry.itemId); + if (selectionChanged) { + EmitEvent(EventKind::AssetSelected, EventSource::GridPrimary, &assetEntry); + } + + const std::uint64_t nowMs = GetTickCount64(); + const std::uint64_t doubleClickThresholdMs = + static_cast(GetDoubleClickTime()); + const bool doubleClicked = + alreadySelected && + m_lastPrimaryClickedAssetId == assetEntry.itemId && + nowMs >= m_lastPrimaryClickTimeMs && + nowMs - m_lastPrimaryClickTimeMs <= doubleClickThresholdMs; + + m_lastPrimaryClickedAssetId = assetEntry.itemId; + m_lastPrimaryClickTimeMs = nowMs; + if (!doubleClicked) { + break; + } + + if (assetEntry.directory) { + NavigateToFolder(assetEntry.itemId, EventSource::GridDoubleClick); + m_layout = BuildLayout(panelState->bounds); + m_hoveredAssetItemId.clear(); + } else { + EmitEvent(EventKind::AssetOpened, EventSource::GridDoubleClick, &assetEntry); + } + break; + } + + if (event.pointerButton == ::XCEngine::UI::UIPointerButton::Right && + ContainsPoint(m_layout.gridRect, event.position)) { + const auto& assetEntries = m_browserModel.GetAssetEntries(); + const std::size_t hitIndex = HitTestAssetTile(event.position); + if (hitIndex >= assetEntries.size()) { + EmitEvent( + EventKind::ContextMenuRequested, + EventSource::Background, + static_cast(nullptr)); + break; + } + + const AssetEntry& assetEntry = assetEntries[hitIndex]; + if (!m_assetSelection.IsSelected(assetEntry.itemId)) { + m_assetSelection.SetSelection(assetEntry.itemId); + EmitEvent(EventKind::AssetSelected, EventSource::GridSecondary, &assetEntry); + } + EmitEvent(EventKind::ContextMenuRequested, EventSource::GridSecondary, &assetEntry); + } + break; + + case UIInputEventType::PointerButtonUp: + if (event.pointerButton != ::XCEngine::UI::UIPointerButton::Left) { + break; + } + + if (m_splitterDragging) { + m_splitterDragging = false; + m_splitterHovered = ContainsPoint(m_layout.dividerRect, event.position); + m_requestPointerRelease = true; + break; + } + + { + const std::size_t releasedBreadcrumbIndex = + HitTestBreadcrumbItem(event.position); + if (m_pressedBreadcrumbIndex != kInvalidLayoutIndex && + m_pressedBreadcrumbIndex == releasedBreadcrumbIndex && + releasedBreadcrumbIndex < m_layout.breadcrumbItems.size()) { + const BreadcrumbItemLayout& item = + m_layout.breadcrumbItems[releasedBreadcrumbIndex]; + if (item.clickable) { + NavigateToFolder(item.targetFolderId, EventSource::Breadcrumb); + m_layout = BuildLayout(panelState->bounds); + } + } + m_pressedBreadcrumbIndex = kInvalidLayoutIndex; + } + break; + + default: + break; + } + } +} + +ProjectPanel::Layout ProjectPanel::BuildLayout(const UIRect& bounds) const { + Layout layout = {}; + const auto& assetEntries = m_browserModel.GetAssetEntries(); + const std::vector breadcrumbSegments = + m_browserModel.BuildBreadcrumbSegments(); + const float dividerThickness = ResolveUIEditorDockHostMetrics().splitterMetrics.thickness; + layout.bounds = UIRect( + bounds.x, + bounds.y, + ClampNonNegative(bounds.width), + ClampNonNegative(bounds.height)); + + const float leftWidth = ClampNavigationWidth(m_navigationWidth, layout.bounds.width); + layout.leftPaneRect = UIRect( + layout.bounds.x, + layout.bounds.y, + leftWidth, + layout.bounds.height); + layout.dividerRect = UIRect( + layout.leftPaneRect.x + layout.leftPaneRect.width, + layout.bounds.y, + dividerThickness, + layout.bounds.height); + layout.rightPaneRect = UIRect( + layout.dividerRect.x + layout.dividerRect.width, + layout.bounds.y, + ClampNonNegative(layout.bounds.width - layout.leftPaneRect.width - layout.dividerRect.width), + layout.bounds.height); + + layout.treeRect = UIRect( + layout.leftPaneRect.x, + layout.leftPaneRect.y + kTreeTopPadding, + layout.leftPaneRect.width, + ClampNonNegative(layout.leftPaneRect.height - kTreeTopPadding)); + + layout.browserHeaderRect = UIRect( + layout.rightPaneRect.x, + layout.rightPaneRect.y, + layout.rightPaneRect.width, + (std::min)(kBrowserHeaderHeight, layout.rightPaneRect.height)); + layout.browserBodyRect = UIRect( + layout.rightPaneRect.x, + layout.browserHeaderRect.y + layout.browserHeaderRect.height, + layout.rightPaneRect.width, + ClampNonNegative(layout.rightPaneRect.height - layout.browserHeaderRect.height)); + layout.gridRect = UIRect( + layout.browserBodyRect.x + kGridInsetX, + layout.browserBodyRect.y + kGridInsetY, + ClampNonNegative(layout.browserBodyRect.width - kGridInsetX * 2.0f), + ClampNonNegative(layout.browserBodyRect.height - kGridInsetY * 2.0f)); + + const float breadcrumbRowHeight = kHeaderFontSize + kBreadcrumbItemPaddingY * 2.0f; + const float breadcrumbY = + layout.browserHeaderRect.y + std::floor((layout.browserHeaderRect.height - breadcrumbRowHeight) * 0.5f); + const float headerRight = + layout.browserHeaderRect.x + layout.browserHeaderRect.width - kHeaderHorizontalPadding; + float nextItemX = layout.browserHeaderRect.x + kHeaderHorizontalPadding; + for (std::size_t index = 0u; index < breadcrumbSegments.size(); ++index) { + if (index > 0u) { + const float separatorWidth = MeasureTextWidth(m_textMeasurer, ">", kHeaderFontSize); + if (nextItemX < headerRight && separatorWidth > 0.0f) { + layout.breadcrumbItems.push_back({ + ">", + {}, + UIRect( + nextItemX, + breadcrumbY, + ClampNonNegative((std::min)(separatorWidth, headerRight - nextItemX)), + breadcrumbRowHeight), + true, + false, + false + }); + } + nextItemX += separatorWidth + kBreadcrumbSpacing; + } + + const ProjectBrowserModel::BreadcrumbSegment& segment = breadcrumbSegments[index]; + const float labelWidth = MeasureTextWidth(m_textMeasurer, segment.label, kHeaderFontSize); + const float itemWidth = labelWidth + kBreadcrumbItemPaddingX * 2.0f; + const float availableWidth = headerRight - nextItemX; + if (availableWidth <= 0.0f) { + break; + } + + layout.breadcrumbItems.push_back({ + segment.label, + segment.targetFolderId, + UIRect( + nextItemX, + breadcrumbY, + ClampNonNegative((std::min)(itemWidth, availableWidth)), + breadcrumbRowHeight), + false, + !segment.current, + segment.current + }); + nextItemX += itemWidth + kBreadcrumbSpacing; + } + + const float effectiveTileWidth = kGridTileWidth + kGridTileGapX; + int columnCount = effectiveTileWidth > 0.0f + ? static_cast((layout.gridRect.width + kGridTileGapX) / effectiveTileWidth) + : 1; + if (columnCount < 1) { + columnCount = 1; + } + + layout.assetTiles.reserve(assetEntries.size()); + for (std::size_t index = 0; index < assetEntries.size(); ++index) { + const int column = static_cast(index % static_cast(columnCount)); + const int row = static_cast(index / static_cast(columnCount)); + const float tileX = layout.gridRect.x + static_cast(column) * (kGridTileWidth + kGridTileGapX); + const float tileY = layout.gridRect.y + static_cast(row) * (kGridTileHeight + kGridTileGapY); + + AssetTileLayout tile = {}; + tile.itemIndex = index; + tile.tileRect = UIRect(tileX, tileY, kGridTileWidth, kGridTileHeight); + tile.previewRect = UIRect( + tile.tileRect.x + (tile.tileRect.width - kGridPreviewWidth) * 0.5f, + tile.tileRect.y + 6.0f, + kGridPreviewWidth, + kGridPreviewHeight); + tile.labelRect = UIRect( + tile.tileRect.x + 4.0f, + tile.previewRect.y + tile.previewRect.height + 8.0f, + tile.tileRect.width - 8.0f, + 18.0f); + layout.assetTiles.push_back(tile); + } + + return layout; +} + +std::size_t ProjectPanel::HitTestBreadcrumbItem(const UIPoint& point) const { + for (std::size_t index = 0u; index < m_layout.breadcrumbItems.size(); ++index) { + const BreadcrumbItemLayout& item = m_layout.breadcrumbItems[index]; + if (!item.separator && ContainsPoint(item.rect, point)) { + return index; + } + } + + return kInvalidLayoutIndex; +} + +std::size_t ProjectPanel::HitTestAssetTile(const UIPoint& point) const { + for (const AssetTileLayout& tile : m_layout.assetTiles) { + if (ContainsPoint(tile.tileRect, point)) { + return tile.itemIndex; + } + } + + return kInvalidLayoutIndex; +} + +void ProjectPanel::Append(UIDrawList& drawList) const { + if (!m_visible || m_layout.bounds.width <= 0.0f || m_layout.bounds.height <= 0.0f) { + return; + } + + const auto& assetEntries = m_browserModel.GetAssetEntries(); + + drawList.AddFilledRect(m_layout.bounds, kSurfaceColor); + drawList.AddFilledRect(m_layout.leftPaneRect, kPaneColor); + drawList.AddFilledRect(m_layout.rightPaneRect, kPaneColor); + drawList.AddFilledRect( + m_layout.dividerRect, + ResolveUIEditorDockHostPalette().splitterColor); + + drawList.AddFilledRect(m_layout.browserHeaderRect, kHeaderColor); + drawList.AddFilledRect( + UIRect( + m_layout.browserHeaderRect.x, + m_layout.browserHeaderRect.y + m_layout.browserHeaderRect.height - kHeaderBottomBorderThickness, + m_layout.browserHeaderRect.width, + kHeaderBottomBorderThickness), + ResolveUIEditorDockHostPalette().splitterColor); + + const Widgets::UIEditorTreeViewPalette treePalette = ResolveUIEditorTreeViewPalette(); + const Widgets::UIEditorTreeViewMetrics treeMetrics = ResolveUIEditorTreeViewMetrics(); + AppendUIEditorTreeViewBackground( + drawList, + m_treeFrame.layout, + m_browserModel.GetTreeItems(), + m_folderSelection, + m_treeInteractionState.treeViewState, + treePalette, + treeMetrics); + AppendUIEditorTreeViewForeground( + drawList, + m_treeFrame.layout, + m_browserModel.GetTreeItems(), + treePalette, + treeMetrics); + + drawList.PushClipRect(m_layout.browserHeaderRect); + for (std::size_t index = 0u; index < m_layout.breadcrumbItems.size(); ++index) { + const BreadcrumbItemLayout& item = m_layout.breadcrumbItems[index]; + const UIColor textColor = + item.separator + ? kTextMuted + : (index == m_hoveredBreadcrumbIndex && item.clickable + ? kTextStrong + : (item.current ? kTextPrimary : kTextMuted)); + const float textWidth = MeasureTextWidth(m_textMeasurer, item.label, kHeaderFontSize); + const float textX = item.separator + ? item.rect.x + : item.rect.x + (item.rect.width - textWidth) * 0.5f; + drawList.AddText( + UIPoint(textX, ResolveTextTop(item.rect.y, item.rect.height, kHeaderFontSize)), + item.label, + textColor, + kHeaderFontSize); + } + drawList.PopClipRect(); + + for (const AssetTileLayout& tile : m_layout.assetTiles) { + if (tile.itemIndex >= assetEntries.size()) { + continue; + } + + const AssetEntry& assetEntry = assetEntries[tile.itemIndex]; + const bool selected = m_assetSelection.IsSelected(assetEntry.itemId); + const bool hovered = m_hoveredAssetItemId == assetEntry.itemId; + + if (selected || hovered) { + drawList.AddFilledRect( + tile.tileRect, + selected ? kTileSelectedColor : kTileHoverColor); + } + + AppendTilePreview(drawList, tile.previewRect, assetEntry.directory); + + drawList.PushClipRect(tile.labelRect); + const float textWidth = + MeasureTextWidth(m_textMeasurer, assetEntry.displayName, kTileLabelFontSize); + drawList.AddText( + UIPoint( + tile.labelRect.x + (tile.labelRect.width - textWidth) * 0.5f, + ResolveTextTop(tile.labelRect.y, tile.labelRect.height, kTileLabelFontSize)), + assetEntry.displayName, + kTextPrimary, + kTileLabelFontSize); + drawList.PopClipRect(); + } + + if (assetEntries.empty()) { + const UIRect messageRect( + m_layout.gridRect.x, + m_layout.gridRect.y, + m_layout.gridRect.width, + 18.0f); + drawList.AddText( + UIPoint(messageRect.x, ResolveTextTop(messageRect.y, messageRect.height, kHeaderFontSize)), + "Current folder is empty.", + kTextMuted, + kHeaderFontSize); + } +} + } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Project/ProjectPanelCommands.cpp b/new_editor/app/Features/Project/ProjectPanelCommands.cpp deleted file mode 100644 index 4b761195..00000000 --- a/new_editor/app/Features/Project/ProjectPanelCommands.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "ProjectPanelSupport.h" - -#include - -namespace XCEngine::UI::Editor::App { - -namespace { - -UIEditorHostCommandEvaluationResult BuildEvaluationResult( - bool executable, - std::string message) { - UIEditorHostCommandEvaluationResult result = {}; - result.executable = executable; - result.message = std::move(message); - return result; -} - -UIEditorHostCommandDispatchResult BuildDispatchResult( - bool commandExecuted, - std::string message) { - UIEditorHostCommandDispatchResult result = {}; - result.commandExecuted = commandExecuted; - result.message = std::move(message); - return result; -} - -} // namespace - -UIEditorHostCommandEvaluationResult ProjectPanel::EvaluateEditCommand( - std::string_view commandId) const { - const std::optional target = ResolveEditCommandTarget(); - if (!target.has_value()) { - return BuildEvaluationResult(false, "Select an asset or folder in Project first."); - } - - if (target->assetsRoot && - (commandId == "edit.rename" || commandId == "edit.delete")) { - return BuildEvaluationResult(false, "The Assets root cannot be renamed or deleted."); - } - - if (commandId == "edit.rename") { - return BuildEvaluationResult( - true, - "Rename project item '" + target->displayName + "'."); - } - - if (commandId == "edit.delete") { - return BuildEvaluationResult( - false, - "Project delete is blocked until asset metadata ownership is wired."); - } - - if (commandId == "edit.duplicate") { - return BuildEvaluationResult( - false, - "Project duplicate is blocked until asset metadata rewrite is wired."); - } - - if (commandId == "edit.cut" || - commandId == "edit.copy" || - commandId == "edit.paste") { - return BuildEvaluationResult( - false, - "Project clipboard has no bound asset transfer owner in the current shell."); - } - - return BuildEvaluationResult(false, "Project does not expose this edit command."); -} - -UIEditorHostCommandDispatchResult ProjectPanel::DispatchEditCommand( - std::string_view commandId) { - const UIEditorHostCommandEvaluationResult evaluation = EvaluateEditCommand(commandId); - if (!evaluation.executable) { - return BuildDispatchResult(false, evaluation.message); - } - - if (commandId == "edit.rename") { - if (m_assetSelection.HasSelection()) { - if (const AssetEntry* asset = FindAssetEntry(m_assetSelection.GetSelectedId()); - asset != nullptr) { - EmitEvent(EventKind::RenameRequested, EventSource::None, asset); - return BuildDispatchResult( - true, - "Project rename requested for '" + asset->displayName + "'."); - } - } - - if (m_folderSelection.HasSelection()) { - if (const FolderEntry* folder = FindFolderEntry(m_folderSelection.GetSelectedId()); - folder != nullptr) { - EmitEvent(EventKind::RenameRequested, EventSource::None, folder); - return BuildDispatchResult( - true, - "Project rename requested for '" + folder->label + "'."); - } - } - - return BuildDispatchResult(false, "Select an asset or folder in Project first."); - } - - return BuildDispatchResult(false, "Project does not expose this edit command."); -} - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Project/ProjectPanelInteraction.cpp b/new_editor/app/Features/Project/ProjectPanelInteraction.cpp deleted file mode 100644 index 4d227dc3..00000000 --- a/new_editor/app/Features/Project/ProjectPanelInteraction.cpp +++ /dev/null @@ -1,229 +0,0 @@ -#include "ProjectPanelSupport.h" - -#include - -namespace XCEngine::UI::Editor::App { - -using namespace ProjectPanelSupport; - -void ProjectPanel::ResetTransientFrames() { - m_treeFrame = {}; - m_frameEvents.clear(); - m_layout = {}; - m_hoveredAssetItemId.clear(); - m_hoveredBreadcrumbIndex = kInvalidLayoutIndex; - m_pressedBreadcrumbIndex = kInvalidLayoutIndex; - m_splitterHovered = false; - m_splitterDragging = false; -} - -void ProjectPanel::Update( - const UIEditorPanelContentHostFrame& contentHostFrame, - const std::vector& inputEvents, - bool allowInteraction, - bool panelActive) { - m_requestPointerCapture = false; - m_requestPointerRelease = false; - m_frameEvents.clear(); - - const UIEditorPanelContentHostPanelState* panelState = - FindMountedProjectPanel(contentHostFrame); - if (panelState == nullptr) { - if (m_splitterDragging) { - m_requestPointerRelease = true; - } - m_visible = false; - ResetTransientFrames(); - return; - } - - if (m_browserModel.GetTreeItems().empty()) { - m_browserModel.Refresh(); - SyncCurrentFolderSelection(); - } - - m_visible = true; - const std::vector filteredEvents = - FilterProjectPanelInputEvents( - panelState->bounds, - inputEvents, - allowInteraction, - panelActive, - m_splitterDragging); - - m_navigationWidth = ClampNavigationWidth(m_navigationWidth, panelState->bounds.width); - m_layout = BuildLayout(panelState->bounds); - const Widgets::UIEditorTreeViewMetrics treeMetrics = BuildEditorTreeViewMetrics(); - const std::vector treeEvents = - FilterTreeInputEvents(filteredEvents, m_splitterDragging); - m_treeFrame = UpdateUIEditorTreeViewInteraction( - m_treeInteractionState, - m_folderSelection, - m_folderExpansion, - m_layout.treeRect, - m_browserModel.GetTreeItems(), - treeEvents, - treeMetrics); - - if (m_treeFrame.result.selectionChanged && - !m_treeFrame.result.selectedItemId.empty() && - m_treeFrame.result.selectedItemId != m_browserModel.GetCurrentFolderId()) { - NavigateToFolder(m_treeFrame.result.selectedItemId, EventSource::Tree); - m_layout = BuildLayout(panelState->bounds); - } - - for (const UIInputEvent& event : filteredEvents) { - switch (event.type) { - case UIInputEventType::FocusLost: - m_hoveredAssetItemId.clear(); - m_hoveredBreadcrumbIndex = kInvalidLayoutIndex; - m_pressedBreadcrumbIndex = kInvalidLayoutIndex; - m_splitterHovered = false; - if (m_splitterDragging) { - m_splitterDragging = false; - m_requestPointerRelease = true; - } - break; - - case UIInputEventType::PointerMove: { - if (m_splitterDragging) { - m_navigationWidth = - ClampNavigationWidth(event.position.x - panelState->bounds.x, panelState->bounds.width); - m_layout = BuildLayout(panelState->bounds); - } - - m_splitterHovered = - m_splitterDragging || ContainsPoint(m_layout.dividerRect, event.position); - m_hoveredBreadcrumbIndex = HitTestBreadcrumbItem(event.position); - const std::size_t hoveredAssetIndex = HitTestAssetTile(event.position); - const auto& assetEntries = m_browserModel.GetAssetEntries(); - m_hoveredAssetItemId = - hoveredAssetIndex < assetEntries.size() - ? assetEntries[hoveredAssetIndex].itemId - : std::string(); - break; - } - - case UIInputEventType::PointerLeave: - if (!m_splitterDragging) { - m_splitterHovered = false; - } - m_hoveredBreadcrumbIndex = kInvalidLayoutIndex; - m_hoveredAssetItemId.clear(); - break; - - case UIInputEventType::PointerButtonDown: - if (event.pointerButton == ::XCEngine::UI::UIPointerButton::Left) { - if (ContainsPoint(m_layout.dividerRect, event.position)) { - m_splitterDragging = true; - m_splitterHovered = true; - m_pressedBreadcrumbIndex = kInvalidLayoutIndex; - m_requestPointerCapture = true; - break; - } - - m_pressedBreadcrumbIndex = HitTestBreadcrumbItem(event.position); - - if (!ContainsPoint(m_layout.gridRect, event.position)) { - break; - } - - const auto& assetEntries = m_browserModel.GetAssetEntries(); - const std::size_t hitIndex = HitTestAssetTile(event.position); - if (hitIndex >= assetEntries.size()) { - if (m_assetSelection.HasSelection()) { - m_assetSelection.ClearSelection(); - EmitSelectionClearedEvent(EventSource::Background); - } - break; - } - - const AssetEntry& assetEntry = assetEntries[hitIndex]; - const bool alreadySelected = m_assetSelection.IsSelected(assetEntry.itemId); - const bool selectionChanged = m_assetSelection.SetSelection(assetEntry.itemId); - if (selectionChanged) { - EmitEvent(EventKind::AssetSelected, EventSource::GridPrimary, &assetEntry); - } - - const std::uint64_t nowMs = GetTickCount64(); - const std::uint64_t doubleClickThresholdMs = - static_cast(GetDoubleClickTime()); - const bool doubleClicked = - alreadySelected && - m_lastPrimaryClickedAssetId == assetEntry.itemId && - nowMs >= m_lastPrimaryClickTimeMs && - nowMs - m_lastPrimaryClickTimeMs <= doubleClickThresholdMs; - - m_lastPrimaryClickedAssetId = assetEntry.itemId; - m_lastPrimaryClickTimeMs = nowMs; - if (!doubleClicked) { - break; - } - - if (assetEntry.directory) { - NavigateToFolder(assetEntry.itemId, EventSource::GridDoubleClick); - m_layout = BuildLayout(panelState->bounds); - m_hoveredAssetItemId.clear(); - } else { - EmitEvent(EventKind::AssetOpened, EventSource::GridDoubleClick, &assetEntry); - } - break; - } - - if (event.pointerButton == ::XCEngine::UI::UIPointerButton::Right && - ContainsPoint(m_layout.gridRect, event.position)) { - const auto& assetEntries = m_browserModel.GetAssetEntries(); - const std::size_t hitIndex = HitTestAssetTile(event.position); - if (hitIndex >= assetEntries.size()) { - EmitEvent( - EventKind::ContextMenuRequested, - EventSource::Background, - static_cast(nullptr)); - break; - } - - const AssetEntry& assetEntry = assetEntries[hitIndex]; - if (!m_assetSelection.IsSelected(assetEntry.itemId)) { - m_assetSelection.SetSelection(assetEntry.itemId); - EmitEvent(EventKind::AssetSelected, EventSource::GridSecondary, &assetEntry); - } - EmitEvent(EventKind::ContextMenuRequested, EventSource::GridSecondary, &assetEntry); - } - break; - - case UIInputEventType::PointerButtonUp: - if (event.pointerButton != ::XCEngine::UI::UIPointerButton::Left) { - break; - } - - if (m_splitterDragging) { - m_splitterDragging = false; - m_splitterHovered = ContainsPoint(m_layout.dividerRect, event.position); - m_requestPointerRelease = true; - break; - } - - { - const std::size_t releasedBreadcrumbIndex = - HitTestBreadcrumbItem(event.position); - if (m_pressedBreadcrumbIndex != kInvalidLayoutIndex && - m_pressedBreadcrumbIndex == releasedBreadcrumbIndex && - releasedBreadcrumbIndex < m_layout.breadcrumbItems.size()) { - const BreadcrumbItemLayout& item = - m_layout.breadcrumbItems[releasedBreadcrumbIndex]; - if (item.clickable) { - NavigateToFolder(item.targetFolderId, EventSource::Breadcrumb); - m_layout = BuildLayout(panelState->bounds); - } - } - m_pressedBreadcrumbIndex = kInvalidLayoutIndex; - } - break; - - default: - break; - } - } -} - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Project/ProjectPanelSupport.h b/new_editor/app/Features/Project/ProjectPanelInternal.cpp similarity index 58% rename from new_editor/app/Features/Project/ProjectPanelSupport.h rename to new_editor/app/Features/Project/ProjectPanelInternal.cpp index 960ab529..35562ebf 100644 --- a/new_editor/app/Features/Project/ProjectPanelSupport.h +++ b/new_editor/app/Features/Project/ProjectPanelInternal.cpp @@ -1,82 +1,26 @@ -#pragma once +#include "ProjectPanelInternal.h" -#include "ProjectPanel.h" +namespace XCEngine::UI::Editor::App::ProjectPanelInternal { -#include "Rendering/Assets/BuiltInIcons.h" -#include "UI/Styles/EditorTreeViewStyle.h" - -#include -#include - -#include -#include -#include - -namespace XCEngine::UI::Editor::App::ProjectPanelSupport { - -using ::XCEngine::UI::Editor::UIEditorTextMeasureRequest; -using ::XCEngine::UI::Editor::UIEditorTextMeasurer; -using ::XCEngine::UI::UIColor; -using ::XCEngine::UI::UIDrawList; -using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIPoint; -using ::XCEngine::UI::UIRect; -using Widgets::AppendUIEditorTreeViewBackground; -using Widgets::AppendUIEditorTreeViewForeground; -inline constexpr std::string_view kProjectPanelId = "project"; -inline constexpr std::size_t kInvalidLayoutIndex = static_cast(-1); - -inline constexpr float kBrowserHeaderHeight = 24.0f; -inline constexpr float kNavigationMinWidth = 180.0f; -inline constexpr float kBrowserMinWidth = 260.0f; -inline constexpr float kHeaderHorizontalPadding = 10.0f; -inline constexpr float kHeaderBottomBorderThickness = 1.0f; -inline constexpr float kBreadcrumbItemPaddingX = 4.0f; -inline constexpr float kBreadcrumbItemPaddingY = 1.0f; -inline constexpr float kBreadcrumbSpacing = 3.0f; -inline constexpr float kTreeTopPadding = 0.0f; -inline constexpr float kGridInsetX = 16.0f; -inline constexpr float kGridInsetY = 12.0f; -inline constexpr float kGridTileWidth = 92.0f; -inline constexpr float kGridTileHeight = 92.0f; -inline constexpr float kGridTileGapX = 12.0f; -inline constexpr float kGridTileGapY = 12.0f; -inline constexpr float kGridPreviewWidth = 68.0f; -inline constexpr float kGridPreviewHeight = 54.0f; -inline constexpr float kHeaderFontSize = 12.0f; -inline constexpr float kTileLabelFontSize = 11.0f; - -inline constexpr UIColor kSurfaceColor(0.10f, 0.10f, 0.10f, 1.0f); -inline constexpr UIColor kPaneColor(0.10f, 0.10f, 0.10f, 1.0f); -inline constexpr UIColor kHeaderColor(0.11f, 0.11f, 0.11f, 1.0f); -inline constexpr UIColor kTextPrimary(0.880f, 0.880f, 0.880f, 1.0f); -inline constexpr UIColor kTextStrong(0.930f, 0.930f, 0.930f, 1.0f); -inline constexpr UIColor kTextMuted(0.640f, 0.640f, 0.640f, 1.0f); -inline constexpr UIColor kTileHoverColor(0.14f, 0.14f, 0.14f, 1.0f); -inline constexpr UIColor kTileSelectedColor(0.18f, 0.18f, 0.18f, 1.0f); -inline constexpr UIColor kTilePreviewFillColor(0.15f, 0.15f, 0.15f, 1.0f); -inline constexpr UIColor kTilePreviewShadeColor(0.12f, 0.12f, 0.12f, 1.0f); -inline constexpr UIColor kTilePreviewOutlineColor(0.920f, 0.920f, 0.920f, 0.20f); - -inline bool ContainsPoint(const UIRect& rect, const UIPoint& point) { +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 ClampNonNegative(float value) { +float ClampNonNegative(float value) { return (std::max)(value, 0.0f); } -inline float ResolveTextTop(float rectY, float rectHeight, float fontSize) { +float ResolveTextTop(float rectY, float rectHeight, float fontSize) { const float lineHeight = fontSize * 1.6f; return rectY + std::floor((rectHeight - lineHeight) * 0.5f); } -inline float MeasureTextWidth( +float MeasureTextWidth( const UIEditorTextMeasurer* textMeasurer, std::string_view text, float fontSize) { @@ -95,7 +39,7 @@ inline float MeasureTextWidth( return static_cast(text.size()) * fontSize * 0.56f; } -inline std::vector FilterProjectPanelInputEvents( +std::vector FilterProjectPanelInputEvents( const UIRect& bounds, const std::vector& inputEvents, bool allowInteraction, @@ -141,7 +85,7 @@ inline std::vector FilterProjectPanelInputEvents( return filteredEvents; } -inline std::vector FilterTreeInputEvents( +std::vector FilterTreeInputEvents( const std::vector& inputEvents, bool suppressPointerInput) { if (!suppressPointerInput) { @@ -166,13 +110,13 @@ inline std::vector FilterTreeInputEvents( return filteredEvents; } -inline ::XCEngine::UI::UITextureHandle ResolveFolderIcon(const BuiltInIcons* icons) { +::XCEngine::UI::UITextureHandle ResolveFolderIcon(const BuiltInIcons* icons) { return icons != nullptr ? icons->Resolve(BuiltInIconKind::Folder) : ::XCEngine::UI::UITextureHandle {}; } -inline float ClampNavigationWidth(float value, float totalWidth) { +float ClampNavigationWidth(float value, float totalWidth) { const float maxWidth = (std::max)( kNavigationMinWidth, @@ -180,7 +124,7 @@ inline float ClampNavigationWidth(float value, float totalWidth) { return std::clamp(value, kNavigationMinWidth, maxWidth); } -inline void AppendTilePreview( +void AppendTilePreview( UIDrawList& drawList, const UIRect& previewRect, bool directory) { @@ -216,4 +160,4 @@ inline void AppendTilePreview( drawList.AddRectOutline(sheetRect, kTilePreviewOutlineColor, 1.0f, 1.0f); } -} // namespace XCEngine::UI::Editor::App::ProjectPanelSupport +} // namespace XCEngine::UI::Editor::App::ProjectPanelInternal diff --git a/new_editor/app/Features/Project/ProjectPanelInternal.h b/new_editor/app/Features/Project/ProjectPanelInternal.h new file mode 100644 index 00000000..c6e90290 --- /dev/null +++ b/new_editor/app/Features/Project/ProjectPanelInternal.h @@ -0,0 +1,82 @@ +#pragma once + +#include "ProjectPanel.h" + +#include "Rendering/Assets/BuiltInIcons.h" + +#include +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor::App::ProjectPanelInternal { + +using ::XCEngine::UI::Editor::UIEditorTextMeasureRequest; +using ::XCEngine::UI::Editor::UIEditorTextMeasurer; +using ::XCEngine::UI::UIColor; +using ::XCEngine::UI::UIDrawList; +using ::XCEngine::UI::UIInputEvent; +using ::XCEngine::UI::UIPoint; +using ::XCEngine::UI::UIRect; + +inline constexpr std::string_view kProjectPanelId = "project"; +inline constexpr std::size_t kInvalidLayoutIndex = static_cast(-1); + +inline constexpr float kBrowserHeaderHeight = 24.0f; +inline constexpr float kNavigationMinWidth = 180.0f; +inline constexpr float kBrowserMinWidth = 260.0f; +inline constexpr float kHeaderHorizontalPadding = 10.0f; +inline constexpr float kHeaderBottomBorderThickness = 1.0f; +inline constexpr float kBreadcrumbItemPaddingX = 4.0f; +inline constexpr float kBreadcrumbItemPaddingY = 1.0f; +inline constexpr float kBreadcrumbSpacing = 3.0f; +inline constexpr float kTreeTopPadding = 0.0f; +inline constexpr float kGridInsetX = 16.0f; +inline constexpr float kGridInsetY = 12.0f; +inline constexpr float kGridTileWidth = 92.0f; +inline constexpr float kGridTileHeight = 92.0f; +inline constexpr float kGridTileGapX = 12.0f; +inline constexpr float kGridTileGapY = 12.0f; +inline constexpr float kGridPreviewWidth = 68.0f; +inline constexpr float kGridPreviewHeight = 54.0f; +inline constexpr float kHeaderFontSize = 12.0f; +inline constexpr float kTileLabelFontSize = 11.0f; + +inline constexpr UIColor kSurfaceColor(0.10f, 0.10f, 0.10f, 1.0f); +inline constexpr UIColor kPaneColor(0.10f, 0.10f, 0.10f, 1.0f); +inline constexpr UIColor kHeaderColor(0.11f, 0.11f, 0.11f, 1.0f); +inline constexpr UIColor kTextPrimary(0.880f, 0.880f, 0.880f, 1.0f); +inline constexpr UIColor kTextStrong(0.930f, 0.930f, 0.930f, 1.0f); +inline constexpr UIColor kTextMuted(0.640f, 0.640f, 0.640f, 1.0f); +inline constexpr UIColor kTileHoverColor(0.14f, 0.14f, 0.14f, 1.0f); +inline constexpr UIColor kTileSelectedColor(0.18f, 0.18f, 0.18f, 1.0f); +inline constexpr UIColor kTilePreviewFillColor(0.15f, 0.15f, 0.15f, 1.0f); +inline constexpr UIColor kTilePreviewShadeColor(0.12f, 0.12f, 0.12f, 1.0f); +inline constexpr UIColor kTilePreviewOutlineColor(0.920f, 0.920f, 0.920f, 0.20f); + +bool ContainsPoint(const UIRect& rect, const UIPoint& point); +float ClampNonNegative(float value); +float ResolveTextTop(float rectY, float rectHeight, float fontSize); +float MeasureTextWidth( + const UIEditorTextMeasurer* textMeasurer, + std::string_view text, + float fontSize); +std::vector FilterProjectPanelInputEvents( + const UIRect& bounds, + const std::vector& inputEvents, + bool allowInteraction, + bool panelActive, + bool captureActive); +std::vector FilterTreeInputEvents( + const std::vector& inputEvents, + bool suppressPointerInput); +::XCEngine::UI::UITextureHandle ResolveFolderIcon(const BuiltInIcons* icons); +float ClampNavigationWidth(float value, float totalWidth); +void AppendTilePreview( + UIDrawList& drawList, + const UIRect& previewRect, + bool directory); + +} // namespace XCEngine::UI::Editor::App::ProjectPanelInternal diff --git a/new_editor/app/Features/Project/ProjectPanelLayout.cpp b/new_editor/app/Features/Project/ProjectPanelLayout.cpp deleted file mode 100644 index 7af277a7..00000000 --- a/new_editor/app/Features/Project/ProjectPanelLayout.cpp +++ /dev/null @@ -1,162 +0,0 @@ -#include "ProjectPanelSupport.h" - -namespace XCEngine::UI::Editor::App { - -using namespace ProjectPanelSupport; - -ProjectPanel::Layout ProjectPanel::BuildLayout(const UIRect& bounds) const { - Layout layout = {}; - const auto& assetEntries = m_browserModel.GetAssetEntries(); - const std::vector breadcrumbSegments = - m_browserModel.BuildBreadcrumbSegments(); - const float dividerThickness = ResolveUIEditorDockHostMetrics().splitterMetrics.thickness; - layout.bounds = UIRect( - bounds.x, - bounds.y, - ClampNonNegative(bounds.width), - ClampNonNegative(bounds.height)); - - const float leftWidth = ClampNavigationWidth(m_navigationWidth, layout.bounds.width); - layout.leftPaneRect = UIRect( - layout.bounds.x, - layout.bounds.y, - leftWidth, - layout.bounds.height); - layout.dividerRect = UIRect( - layout.leftPaneRect.x + layout.leftPaneRect.width, - layout.bounds.y, - dividerThickness, - layout.bounds.height); - layout.rightPaneRect = UIRect( - layout.dividerRect.x + layout.dividerRect.width, - layout.bounds.y, - ClampNonNegative(layout.bounds.width - layout.leftPaneRect.width - layout.dividerRect.width), - layout.bounds.height); - - layout.treeRect = UIRect( - layout.leftPaneRect.x, - layout.leftPaneRect.y + kTreeTopPadding, - layout.leftPaneRect.width, - ClampNonNegative(layout.leftPaneRect.height - kTreeTopPadding)); - - layout.browserHeaderRect = UIRect( - layout.rightPaneRect.x, - layout.rightPaneRect.y, - layout.rightPaneRect.width, - (std::min)(kBrowserHeaderHeight, layout.rightPaneRect.height)); - layout.browserBodyRect = UIRect( - layout.rightPaneRect.x, - layout.browserHeaderRect.y + layout.browserHeaderRect.height, - layout.rightPaneRect.width, - ClampNonNegative(layout.rightPaneRect.height - layout.browserHeaderRect.height)); - layout.gridRect = UIRect( - layout.browserBodyRect.x + kGridInsetX, - layout.browserBodyRect.y + kGridInsetY, - ClampNonNegative(layout.browserBodyRect.width - kGridInsetX * 2.0f), - ClampNonNegative(layout.browserBodyRect.height - kGridInsetY * 2.0f)); - - const float breadcrumbRowHeight = kHeaderFontSize + kBreadcrumbItemPaddingY * 2.0f; - const float breadcrumbY = - layout.browserHeaderRect.y + std::floor((layout.browserHeaderRect.height - breadcrumbRowHeight) * 0.5f); - const float headerRight = - layout.browserHeaderRect.x + layout.browserHeaderRect.width - kHeaderHorizontalPadding; - float nextItemX = layout.browserHeaderRect.x + kHeaderHorizontalPadding; - for (std::size_t index = 0u; index < breadcrumbSegments.size(); ++index) { - if (index > 0u) { - const float separatorWidth = MeasureTextWidth(m_textMeasurer, ">", kHeaderFontSize); - if (nextItemX < headerRight && separatorWidth > 0.0f) { - layout.breadcrumbItems.push_back({ - ">", - {}, - UIRect( - nextItemX, - breadcrumbY, - ClampNonNegative((std::min)(separatorWidth, headerRight - nextItemX)), - breadcrumbRowHeight), - true, - false, - false - }); - } - nextItemX += separatorWidth + kBreadcrumbSpacing; - } - - const ProjectBrowserModel::BreadcrumbSegment& segment = breadcrumbSegments[index]; - const float labelWidth = MeasureTextWidth(m_textMeasurer, segment.label, kHeaderFontSize); - const float itemWidth = labelWidth + kBreadcrumbItemPaddingX * 2.0f; - const float availableWidth = headerRight - nextItemX; - if (availableWidth <= 0.0f) { - break; - } - - layout.breadcrumbItems.push_back({ - segment.label, - segment.targetFolderId, - UIRect( - nextItemX, - breadcrumbY, - ClampNonNegative((std::min)(itemWidth, availableWidth)), - breadcrumbRowHeight), - false, - !segment.current, - segment.current - }); - nextItemX += itemWidth + kBreadcrumbSpacing; - } - - const float effectiveTileWidth = kGridTileWidth + kGridTileGapX; - int columnCount = effectiveTileWidth > 0.0f - ? static_cast((layout.gridRect.width + kGridTileGapX) / effectiveTileWidth) - : 1; - if (columnCount < 1) { - columnCount = 1; - } - - layout.assetTiles.reserve(assetEntries.size()); - for (std::size_t index = 0; index < assetEntries.size(); ++index) { - const int column = static_cast(index % static_cast(columnCount)); - const int row = static_cast(index / static_cast(columnCount)); - const float tileX = layout.gridRect.x + static_cast(column) * (kGridTileWidth + kGridTileGapX); - const float tileY = layout.gridRect.y + static_cast(row) * (kGridTileHeight + kGridTileGapY); - - AssetTileLayout tile = {}; - tile.itemIndex = index; - tile.tileRect = UIRect(tileX, tileY, kGridTileWidth, kGridTileHeight); - tile.previewRect = UIRect( - tile.tileRect.x + (tile.tileRect.width - kGridPreviewWidth) * 0.5f, - tile.tileRect.y + 6.0f, - kGridPreviewWidth, - kGridPreviewHeight); - tile.labelRect = UIRect( - tile.tileRect.x + 4.0f, - tile.previewRect.y + tile.previewRect.height + 8.0f, - tile.tileRect.width - 8.0f, - 18.0f); - layout.assetTiles.push_back(tile); - } - - return layout; -} - -std::size_t ProjectPanel::HitTestBreadcrumbItem(const UIPoint& point) const { - for (std::size_t index = 0u; index < m_layout.breadcrumbItems.size(); ++index) { - const BreadcrumbItemLayout& item = m_layout.breadcrumbItems[index]; - if (!item.separator && ContainsPoint(item.rect, point)) { - return index; - } - } - - return kInvalidLayoutIndex; -} - -std::size_t ProjectPanel::HitTestAssetTile(const UIPoint& point) const { - for (const AssetTileLayout& tile : m_layout.assetTiles) { - if (ContainsPoint(tile.tileRect, point)) { - return tile.itemIndex; - } - } - - return kInvalidLayoutIndex; -} - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Features/Project/ProjectPanelRendering.cpp b/new_editor/app/Features/Project/ProjectPanelRendering.cpp deleted file mode 100644 index af352bc5..00000000 --- a/new_editor/app/Features/Project/ProjectPanelRendering.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "ProjectPanelSupport.h" - -namespace XCEngine::UI::Editor::App { - -using namespace ProjectPanelSupport; - -void ProjectPanel::Append(UIDrawList& drawList) const { - if (!m_visible || m_layout.bounds.width <= 0.0f || m_layout.bounds.height <= 0.0f) { - return; - } - - const auto& assetEntries = m_browserModel.GetAssetEntries(); - - drawList.AddFilledRect(m_layout.bounds, kSurfaceColor); - drawList.AddFilledRect(m_layout.leftPaneRect, kPaneColor); - drawList.AddFilledRect(m_layout.rightPaneRect, kPaneColor); - drawList.AddFilledRect( - m_layout.dividerRect, - ResolveUIEditorDockHostPalette().splitterColor); - - drawList.AddFilledRect(m_layout.browserHeaderRect, kHeaderColor); - drawList.AddFilledRect( - UIRect( - m_layout.browserHeaderRect.x, - m_layout.browserHeaderRect.y + m_layout.browserHeaderRect.height - kHeaderBottomBorderThickness, - m_layout.browserHeaderRect.width, - kHeaderBottomBorderThickness), - ResolveUIEditorDockHostPalette().splitterColor); - - const Widgets::UIEditorTreeViewPalette treePalette = BuildEditorTreeViewPalette(); - const Widgets::UIEditorTreeViewMetrics treeMetrics = BuildEditorTreeViewMetrics(); - AppendUIEditorTreeViewBackground( - drawList, - m_treeFrame.layout, - m_browserModel.GetTreeItems(), - m_folderSelection, - m_treeInteractionState.treeViewState, - treePalette, - treeMetrics); - AppendUIEditorTreeViewForeground( - drawList, - m_treeFrame.layout, - m_browserModel.GetTreeItems(), - treePalette, - treeMetrics); - - drawList.PushClipRect(m_layout.browserHeaderRect); - for (std::size_t index = 0u; index < m_layout.breadcrumbItems.size(); ++index) { - const BreadcrumbItemLayout& item = m_layout.breadcrumbItems[index]; - const UIColor textColor = - item.separator - ? kTextMuted - : (index == m_hoveredBreadcrumbIndex && item.clickable - ? kTextStrong - : (item.current ? kTextPrimary : kTextMuted)); - const float textWidth = MeasureTextWidth(m_textMeasurer, item.label, kHeaderFontSize); - const float textX = item.separator - ? item.rect.x - : item.rect.x + (item.rect.width - textWidth) * 0.5f; - drawList.AddText( - UIPoint(textX, ResolveTextTop(item.rect.y, item.rect.height, kHeaderFontSize)), - item.label, - textColor, - kHeaderFontSize); - } - drawList.PopClipRect(); - - for (const AssetTileLayout& tile : m_layout.assetTiles) { - if (tile.itemIndex >= assetEntries.size()) { - continue; - } - - const AssetEntry& assetEntry = assetEntries[tile.itemIndex]; - const bool selected = m_assetSelection.IsSelected(assetEntry.itemId); - const bool hovered = m_hoveredAssetItemId == assetEntry.itemId; - - if (selected || hovered) { - drawList.AddFilledRect( - tile.tileRect, - selected ? kTileSelectedColor : kTileHoverColor); - } - - AppendTilePreview(drawList, tile.previewRect, assetEntry.directory); - - drawList.PushClipRect(tile.labelRect); - const float textWidth = - MeasureTextWidth(m_textMeasurer, assetEntry.displayName, kTileLabelFontSize); - drawList.AddText( - UIPoint( - tile.labelRect.x + (tile.labelRect.width - textWidth) * 0.5f, - ResolveTextTop(tile.labelRect.y, tile.labelRect.height, kTileLabelFontSize)), - assetEntry.displayName, - kTextPrimary, - kTileLabelFontSize); - drawList.PopClipRect(); - } - - if (assetEntries.empty()) { - const UIRect messageRect( - m_layout.gridRect.x, - m_layout.gridRect.y, - m_layout.gridRect.width, - 18.0f); - drawList.AddText( - UIPoint(messageRect.x, ResolveTextTop(messageRect.y, messageRect.height, kHeaderFontSize)), - "Current folder is empty.", - kTextMuted, - kHeaderFontSize); - } -} - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindow.h b/new_editor/app/Platform/Win32/EditorWindow.h index 4d461dba..d9632aee 100644 --- a/new_editor/app/Platform/Win32/EditorWindow.h +++ b/new_editor/app/Platform/Win32/EditorWindow.h @@ -4,7 +4,6 @@ #define NOMINMAX #endif -#include "State/EditorContext.h" #include "Platform/Win32/EditorWindowState.h" #include @@ -19,17 +18,20 @@ namespace XCEngine::UI::Editor::App { -struct EditorWindowPendingTabDragStart { - std::string nodeId = {}; - std::string panelId = {}; - POINT screenPoint = {}; -}; +class EditorContext; -struct EditorWindowPendingDetachRequest { - std::string nodeId = {}; - std::string panelId = {}; - POINT screenPoint = {}; -}; +namespace Internal { +class EditorWindowHostRuntime; +class EditorWindowWorkspaceCoordinator; +} + +} // namespace XCEngine::UI::Editor::App + +namespace XCEngine::UI::Editor::Host { +class WindowMessageDispatcher; +} + +namespace XCEngine::UI::Editor::App { class EditorWindow { public: @@ -48,17 +50,23 @@ public: HWND GetHwnd() const; bool HasHwnd() const; bool IsPrimary() const; - bool IsRenderReady() const; - bool IsTrackingMouseLeave() const; - bool HasHoveredBorderlessResizeEdge() const; const std::wstring& GetTitle() const; const UIEditorWorkspaceController& GetWorkspaceController() const; UIEditorWorkspaceController& GetWorkspaceController(); + const UIEditorShellInteractionFrame& GetShellFrame() const; + ::XCEngine::UI::UIPoint ConvertScreenPixelsToClientDips(const POINT& screenPoint) const; + +private: + friend class Host::WindowMessageDispatcher; + friend class Internal::EditorWindowHostRuntime; + friend class Internal::EditorWindowWorkspaceCoordinator; + + bool IsRenderReady() const; + bool IsTrackingMouseLeave() const; + bool HasHoveredBorderlessResizeEdge() const; const EditorShellRuntime& GetShellRuntime() const; EditorShellRuntime& GetShellRuntime(); - const UIEditorShellInteractionFrame& GetShellFrame() const; const UIEditorShellInteractionState& GetShellInteractionState() const; - ::XCEngine::UI::UIPoint ConvertScreenPixelsToClientDips(const POINT& screenPoint) const; void InvalidateHostWindow() const; void SetExternalDockHostDropPreview( const Widgets::UIEditorDockHostDropPreviewState& preview); @@ -78,13 +86,10 @@ public: void Shutdown(); void ResetInteractionState(); - std::optional ConsumePendingTabDragStart(); - std::optional ConsumeDetachRequest(); - - void RenderFrame( + EditorWindowFrameTransferRequests RenderFrame( EditorContext& editorContext, bool globalTabDragActive); - void OnPaintMessage( + EditorWindowFrameTransferRequests OnPaintMessage( EditorContext& editorContext, bool globalTabDragActive); void OnResize(UINT width, UINT height); @@ -141,14 +146,13 @@ public: void ResetInputModifiers(); void RequestManualScreenshot(); -private: bool ApplyWindowResize(UINT width, UINT height); bool QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const; bool ResolveRenderClientPixelSize(UINT& outWidth, UINT& outHeight) const; ::XCEngine::UI::UIRect ResolveWorkspaceBounds( float clientWidthDips, float clientHeightDips) const; - void RenderRuntimeFrame( + EditorWindowFrameTransferRequests RenderRuntimeFrame( EditorContext& editorContext, bool globalTabDragActive, const ::XCEngine::UI::UIRect& workspaceBounds, @@ -160,10 +164,10 @@ private: EditorContext& editorContext, const std::vector<::XCEngine::UI::UIInputEvent>& frameEvents, const UIEditorShellInteractionFrame& shellFrame) const; - void QueueShellTransferRequests( + EditorWindowFrameTransferRequests BuildShellTransferRequests( bool globalTabDragActive, const UIEditorDockHostInteractionState& dockHostInteractionState, - const UIEditorShellInteractionFrame& shellFrame); + const UIEditorShellInteractionFrame& shellFrame) const; bool IsPointerInsideClientArea() const; LPCWSTR ResolveCurrentCursorResource() const; float GetDpiScale() const; @@ -195,8 +199,6 @@ private: EditorContext& editorContext, bool globalTabDragActive, Host::BorderlessWindowChromeHitTarget target); - void QueuePendingTabDragStart(std::string_view nodeId, std::string_view panelId); - void QueuePendingDetachRequest(std::string_view nodeId, std::string_view panelId); void UpdateCachedTitleText(); static bool IsVerboseRuntimeTraceEnabled(); @@ -205,8 +207,6 @@ private: EditorWindowInputState m_input = {}; EditorWindowCompositionState m_composition = {}; EditorWindowChromeRuntimeState m_chrome = {}; - EditorWindowPanelTransferState m_pendingDetachRequest = {}; - EditorWindowPanelTransferState m_pendingTabDragStart = {}; }; } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp b/new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp index 4f3e4b67..5b61cd62 100644 --- a/new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowBorderlessPlacement.cpp @@ -88,7 +88,7 @@ bool EditorWindow::ApplyPredictedWindowRectTransition( static_cast(width), static_cast(height)); ApplyWindowResize(static_cast(width), static_cast(height)); - RenderFrame(editorContext, globalTabDragActive); + (void)RenderFrame(editorContext, globalTabDragActive); SetWindowPos( m_window.hwnd, nullptr, diff --git a/new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp b/new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp index ed44f041..8890d22e 100644 --- a/new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowBorderlessResize.cpp @@ -87,7 +87,7 @@ bool EditorWindow::HandleBorderlessWindowResizePointerMove( static_cast(width), static_cast(height)); ApplyWindowResize(static_cast(width), static_cast(height)); - RenderFrame(editorContext, globalTabDragActive); + (void)RenderFrame(editorContext, globalTabDragActive); SetWindowPos( m_window.hwnd, diff --git a/new_editor/app/Platform/Win32/EditorWindowConstants.h b/new_editor/app/Platform/Win32/EditorWindowConstants.h index e8d01a60..09df1f30 100644 --- a/new_editor/app/Platform/Win32/EditorWindowConstants.h +++ b/new_editor/app/Platform/Win32/EditorWindowConstants.h @@ -1,10 +1,10 @@ #pragma once -namespace XCEngine::UI::Editor::App::EditorWindowSupport { +namespace XCEngine::UI::Editor::App::EditorWindowInternal { inline constexpr unsigned int kDefaultDpi = 96u; inline constexpr float kBaseDpiScale = 96.0f; inline constexpr float kBorderlessTitleBarHeightDips = 28.0f; inline constexpr float kBorderlessTitleBarFontSize = 12.0f; -} // namespace XCEngine::UI::Editor::App::EditorWindowSupport +} // namespace XCEngine::UI::Editor::App::EditorWindowInternal diff --git a/new_editor/app/Platform/Win32/EditorWindowFrame.cpp b/new_editor/app/Platform/Win32/EditorWindowFrame.cpp index 7578636f..cf36e05b 100644 --- a/new_editor/app/Platform/Win32/EditorWindowFrame.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowFrame.cpp @@ -1,8 +1,8 @@ #include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowConstants.h" -#include "Platform/Win32/EditorWindowInputSupport.h" -#include "Platform/Win32/EditorWindowRuntimeSupport.h" +#include "Platform/Win32/EditorWindowRuntimeInternal.h" #include "Platform/Win32/EditorWindowStyle.h" +#include "State/EditorContext.h" #include @@ -10,24 +10,24 @@ namespace XCEngine::UI::Editor::App { -using namespace EditorWindowSupport; +using namespace EditorWindowInternal; using ::XCEngine::UI::UIDrawData; using ::XCEngine::UI::UIDrawList; using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIPoint; using ::XCEngine::UI::UIRect; -void EditorWindow::RenderFrame( +EditorWindowFrameTransferRequests EditorWindow::RenderFrame( EditorContext& editorContext, bool globalTabDragActive) { if (!m_render.ready || m_window.hwnd == nullptr) { - return; + return {}; } UINT pixelWidth = 0u; UINT pixelHeight = 0u; if (!ResolveRenderClientPixelSize(pixelWidth, pixelHeight)) { - return; + return {}; } const float width = PixelsToDips(static_cast(pixelWidth)); @@ -40,8 +40,10 @@ void EditorWindow::RenderFrame( UIRect(0.0f, 0.0f, width, height), kShellSurfaceColor); + EditorWindowFrameTransferRequests transferRequests = {}; if (editorContext.IsValid()) { - RenderRuntimeFrame(editorContext, globalTabDragActive, workspaceBounds, drawList); + transferRequests = + RenderRuntimeFrame(editorContext, globalTabDragActive, workspaceBounds, drawList); } else { RenderInvalidFrame(editorContext, drawList); } @@ -60,19 +62,22 @@ void EditorWindow::RenderFrame( pixelWidth, pixelHeight, presentResult.framePresented); + return transferRequests; } -void EditorWindow::OnPaintMessage( +EditorWindowFrameTransferRequests EditorWindow::OnPaintMessage( EditorContext& editorContext, bool globalTabDragActive) { if (!m_render.ready || m_window.hwnd == nullptr) { - return; + return {}; } PAINTSTRUCT paintStruct = {}; BeginPaint(m_window.hwnd, &paintStruct); - RenderFrame(editorContext, globalTabDragActive); + const EditorWindowFrameTransferRequests transferRequests = + RenderFrame(editorContext, globalTabDragActive); EndPaint(m_window.hwnd, &paintStruct); + return transferRequests; } UIRect EditorWindow::ResolveWorkspaceBounds(float clientWidthDips, float clientHeightDips) const { diff --git a/new_editor/app/Platform/Win32/EditorWindowFrameRuntime.cpp b/new_editor/app/Platform/Win32/EditorWindowFrameRuntime.cpp index 36110256..78ee96b3 100644 --- a/new_editor/app/Platform/Win32/EditorWindowFrameRuntime.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowFrameRuntime.cpp @@ -1,21 +1,23 @@ #include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowConstants.h" -#include "Platform/Win32/EditorWindowInputSupport.h" -#include "Platform/Win32/EditorWindowRuntimeSupport.h" +#include "Platform/Win32/EditorWindowInputInternal.h" +#include "Platform/Win32/EditorWindowRuntimeInternal.h" #include "Platform/Win32/EditorWindowStyle.h" +#include "State/EditorContext.h" #include #include namespace XCEngine::UI::Editor::App { -using namespace EditorWindowSupport; +using namespace EditorWindowInternal; +using namespace EditorWindowInputInternal; using ::XCEngine::UI::UIDrawList; using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIPoint; using ::XCEngine::UI::UIRect; -void EditorWindow::RenderRuntimeFrame( +EditorWindowFrameTransferRequests EditorWindow::RenderRuntimeFrame( EditorContext& editorContext, bool globalTabDragActive, const UIRect& workspaceBounds, @@ -59,7 +61,8 @@ void EditorWindow::RenderRuntimeFrame( .dockHostInteractionState; LogFrameInteractionTrace(editorContext, frameEvents, shellFrame); - QueueShellTransferRequests(globalTabDragActive, dockHostInteractionState, shellFrame); + const EditorWindowFrameTransferRequests transferRequests = + BuildShellTransferRequests(globalTabDragActive, dockHostInteractionState, shellFrame); ApplyHostCaptureRequests(shellFrame.result); for (const WorkspaceTraceEntry& entry : m_composition.shellRuntime.GetTraceEntries()) { @@ -71,6 +74,7 @@ void EditorWindow::RenderRuntimeFrame( if (frameContext.canRenderViewports) { m_composition.shellRuntime.RenderRequestedViewports(frameContext.renderContext); } + return transferRequests; } void EditorWindow::RenderInvalidFrame( @@ -79,14 +83,14 @@ void EditorWindow::RenderInvalidFrame( drawList.AddText( UIPoint(28.0f, 28.0f), "Editor shell asset invalid.", - EditorWindowSupport::kShellTextColor, + EditorWindowInternal::kShellTextColor, 16.0f); drawList.AddText( UIPoint(28.0f, 54.0f), editorContext.GetValidationMessage().empty() ? std::string("Unknown validation error.") : editorContext.GetValidationMessage(), - EditorWindowSupport::kShellMutedTextColor, + EditorWindowInternal::kShellMutedTextColor, 12.0f); } @@ -115,23 +119,93 @@ void EditorWindow::LogFrameInteractionTrace( LogRuntimeTrace("frame", frameTrace.str()); } -void EditorWindow::QueueShellTransferRequests( +EditorWindowFrameTransferRequests EditorWindow::BuildShellTransferRequests( bool globalTabDragActive, const UIEditorDockHostInteractionState& dockHostInteractionState, - const UIEditorShellInteractionFrame& shellFrame) { + const UIEditorShellInteractionFrame& shellFrame) const { + EditorWindowFrameTransferRequests transferRequests = {}; + POINT screenPoint = {}; + const bool hasScreenPoint = GetCursorPos(&screenPoint) != FALSE; + if (!globalTabDragActive && !dockHostInteractionState.activeTabDragNodeId.empty() && - !dockHostInteractionState.activeTabDragPanelId.empty()) { - QueuePendingTabDragStart( + !dockHostInteractionState.activeTabDragPanelId.empty() && + hasScreenPoint) { + transferRequests.beginGlobalTabDrag = EditorWindowPanelTransferRequest{ dockHostInteractionState.activeTabDragNodeId, - dockHostInteractionState.activeTabDragPanelId); + dockHostInteractionState.activeTabDragPanelId, + screenPoint, + }; } - if (shellFrame.result.workspaceResult.dockHostResult.detachRequested) { - QueuePendingDetachRequest( + if (shellFrame.result.workspaceResult.dockHostResult.detachRequested && + hasScreenPoint) { + transferRequests.detachPanel = EditorWindowPanelTransferRequest{ shellFrame.result.workspaceResult.dockHostResult.detachedNodeId, - shellFrame.result.workspaceResult.dockHostResult.detachedPanelId); + shellFrame.result.workspaceResult.dockHostResult.detachedPanelId, + screenPoint, + }; + } + + return transferRequests; +} + +std::string EditorWindow::BuildCaptureStatusText() const { + if (m_render.autoScreenshot.HasPendingCapture()) { + return "Shot pending..."; + } + + if (!m_render.autoScreenshot.GetLastCaptureError().empty()) { + return TruncateText(m_render.autoScreenshot.GetLastCaptureError(), 38u); + } + + if (!m_render.autoScreenshot.GetLastCaptureSummary().empty()) { + return TruncateText(m_render.autoScreenshot.GetLastCaptureSummary(), 38u); + } + + return {}; +} + +void EditorWindow::ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result) { + if (result.requestPointerCapture && GetCapture() != m_window.hwnd) { + SetCapture(m_window.hwnd); + } + if (result.releasePointerCapture && GetCapture() == m_window.hwnd) { + ReleaseCapture(); } } +void EditorWindow::ApplyHostedContentCaptureRequests() { + if (m_composition.shellRuntime.WantsHostPointerCapture() && + GetCapture() != m_window.hwnd) { + SetCapture(m_window.hwnd); + } + + if (m_composition.shellRuntime.WantsHostPointerRelease() && + GetCapture() == m_window.hwnd && + !m_composition.shellRuntime.HasShellInteractiveCapture()) { + ReleaseCapture(); + } +} + +std::string EditorWindow::DescribeInputEvents( + const std::vector& events) const { + std::ostringstream stream = {}; + stream << "events=["; + for (std::size_t index = 0; index < events.size(); ++index) { + if (index > 0u) { + stream << " | "; + } + + const UIInputEvent& event = events[index]; + stream << DescribeInputEventType(event) + << '@' + << static_cast(event.position.x) + << ',' + << static_cast(event.position.y); + } + stream << ']'; + return stream.str(); +} + } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowFrameSupport.cpp b/new_editor/app/Platform/Win32/EditorWindowFrameSupport.cpp deleted file mode 100644 index 27663cc6..00000000 --- a/new_editor/app/Platform/Win32/EditorWindowFrameSupport.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "Platform/Win32/EditorWindow.h" -#include "Platform/Win32/EditorWindowInputSupport.h" -#include "Support/TextFormat.h" - -#include - -namespace XCEngine::UI::Editor::App { - -using namespace EditorWindowSupport; -using ::XCEngine::UI::Editor::Support::TruncateText; -using ::XCEngine::UI::UIInputEvent; - -std::string EditorWindow::BuildCaptureStatusText() const { - if (m_render.autoScreenshot.HasPendingCapture()) { - return "Shot pending..."; - } - - if (!m_render.autoScreenshot.GetLastCaptureError().empty()) { - return TruncateText(m_render.autoScreenshot.GetLastCaptureError(), 38u); - } - - if (!m_render.autoScreenshot.GetLastCaptureSummary().empty()) { - return TruncateText(m_render.autoScreenshot.GetLastCaptureSummary(), 38u); - } - - return {}; -} - -void EditorWindow::ApplyHostCaptureRequests(const UIEditorShellInteractionResult& result) { - if (result.requestPointerCapture && GetCapture() != m_window.hwnd) { - SetCapture(m_window.hwnd); - } - if (result.releasePointerCapture && GetCapture() == m_window.hwnd) { - ReleaseCapture(); - } -} - -void EditorWindow::ApplyHostedContentCaptureRequests() { - if (m_composition.shellRuntime.WantsHostPointerCapture() && - GetCapture() != m_window.hwnd) { - SetCapture(m_window.hwnd); - } - - if (m_composition.shellRuntime.WantsHostPointerRelease() && - GetCapture() == m_window.hwnd && - !m_composition.shellRuntime.HasShellInteractiveCapture()) { - ReleaseCapture(); - } -} - -std::string EditorWindow::DescribeInputEvents( - const std::vector& events) const { - std::ostringstream stream = {}; - stream << "events=["; - for (std::size_t index = 0; index < events.size(); ++index) { - if (index > 0u) { - stream << " | "; - } - - const UIInputEvent& event = events[index]; - stream << DescribeInputEventType(event) - << '@' - << static_cast(event.position.x) - << ',' - << static_cast(event.position.y); - } - stream << ']'; - return stream.str(); -} - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowInitialization.cpp b/new_editor/app/Platform/Win32/EditorWindowInitialization.cpp index 0a0aeef4..71fcbb8a 100644 --- a/new_editor/app/Platform/Win32/EditorWindowInitialization.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowInitialization.cpp @@ -1,15 +1,16 @@ #include "Platform/Win32/EditorWindow.h" #include "Bootstrap/EditorResources.h" -#include "Platform/Win32/EditorWindowPlatformSupport.h" -#include "Platform/Win32/EditorWindowRuntimeSupport.h" +#include "Platform/Win32/EditorWindowPlatformInternal.h" +#include "Platform/Win32/EditorWindowRuntimeInternal.h" +#include "State/EditorContext.h" #include #include namespace XCEngine::UI::Editor::App { -using namespace EditorWindowSupport; +using namespace EditorWindowInternal; bool EditorWindow::Initialize( const std::filesystem::path& repoRoot, @@ -102,8 +103,6 @@ void EditorWindow::Shutdown() { m_input.pendingEvents.clear(); m_chrome.chromeState = {}; m_chrome.runtime.Reset(); - m_pendingDetachRequest = {}; - m_pendingTabDragStart = {}; } void EditorWindow::ResetInteractionState() { @@ -121,8 +120,6 @@ void EditorWindow::ResetInteractionState() { m_chrome.runtime.EndInteractiveResize(); m_chrome.runtime.SetHoveredBorderlessResizeEdge(Host::BorderlessWindowResizeEdge::None); m_chrome.runtime.ClearPredictedClientPixelSize(); - m_pendingDetachRequest = {}; - m_pendingTabDragStart = {}; } } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowInput.cpp b/new_editor/app/Platform/Win32/EditorWindowInput.cpp index a4d6852a..5c9f48ad 100644 --- a/new_editor/app/Platform/Win32/EditorWindowInput.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowInput.cpp @@ -1,5 +1,5 @@ #include "Platform/Win32/EditorWindow.h" -#include "Platform/Win32/EditorWindowInputSupport.h" +#include "Platform/Win32/EditorWindowInputInternal.h" #include @@ -7,7 +7,7 @@ namespace XCEngine::UI::Editor::App { -using namespace EditorWindowSupport; +using namespace EditorWindowInputInternal; using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; using ::XCEngine::UI::UIPointerButton; diff --git a/new_editor/app/Platform/Win32/EditorWindowInputSupport.h b/new_editor/app/Platform/Win32/EditorWindowInputInternal.cpp similarity index 92% rename from new_editor/app/Platform/Win32/EditorWindowInputSupport.h rename to new_editor/app/Platform/Win32/EditorWindowInputInternal.cpp index 9af08853..a6a7038b 100644 --- a/new_editor/app/Platform/Win32/EditorWindowInputSupport.h +++ b/new_editor/app/Platform/Win32/EditorWindowInputInternal.cpp @@ -1,16 +1,10 @@ -#pragma once +#include "Platform/Win32/EditorWindowInputInternal.h" #include -#include -#include -#include +namespace XCEngine::UI::Editor::App::EditorWindowInputInternal { -#include - -namespace XCEngine::UI::Editor::App::EditorWindowSupport { - -inline std::int32_t MapVirtualKeyToUIKeyCode(WPARAM wParam) { +std::int32_t MapVirtualKeyToUIKeyCode(WPARAM wParam) { using ::XCEngine::Input::KeyCode; switch (wParam) { @@ -83,11 +77,11 @@ inline std::int32_t MapVirtualKeyToUIKeyCode(WPARAM wParam) { } } -inline bool IsRepeatKeyMessage(LPARAM lParam) { +bool IsRepeatKeyMessage(LPARAM lParam) { return (static_cast(lParam) & (1ul << 30)) != 0ul; } -inline std::string DescribeInputEventType(const ::XCEngine::UI::UIInputEvent& event) { +std::string DescribeInputEventType(const ::XCEngine::UI::UIInputEvent& event) { using ::XCEngine::UI::UIInputEventType; switch (event.type) { @@ -106,4 +100,4 @@ inline std::string DescribeInputEventType(const ::XCEngine::UI::UIInputEvent& ev } } -} // namespace XCEngine::UI::Editor::App::EditorWindowSupport +} // namespace XCEngine::UI::Editor::App::EditorWindowInputInternal diff --git a/new_editor/app/Platform/Win32/EditorWindowInputInternal.h b/new_editor/app/Platform/Win32/EditorWindowInputInternal.h new file mode 100644 index 00000000..68475a2a --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowInputInternal.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include +#include + +#include + +namespace XCEngine::UI::Editor::App::EditorWindowInputInternal { + +std::int32_t MapVirtualKeyToUIKeyCode(WPARAM wParam); +bool IsRepeatKeyMessage(LPARAM lParam); +std::string DescribeInputEventType(const ::XCEngine::UI::UIInputEvent& event); + +} // namespace XCEngine::UI::Editor::App::EditorWindowInputInternal diff --git a/new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp b/new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp index 0e0a2635..13e5ea06 100644 --- a/new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowLifecycle.cpp @@ -1,9 +1,9 @@ #include "Platform/Win32/EditorWindow.h" -#include "Platform/Win32/EditorWindowRuntimeSupport.h" +#include "Platform/Win32/EditorWindowRuntimeInternal.h" namespace XCEngine::UI::Editor::App { -using namespace EditorWindowSupport; +using namespace EditorWindowInternal; EditorWindow::EditorWindow( std::string windowId, @@ -108,62 +108,6 @@ void EditorWindow::ReplaceWorkspaceController(UIEditorWorkspaceController worksp m_composition.workspaceController = std::move(workspaceController); } -std::optional EditorWindow::ConsumePendingTabDragStart() { - if (!m_pendingTabDragStart.pending || - m_pendingTabDragStart.nodeId.empty() || - m_pendingTabDragStart.panelId.empty()) { - return std::nullopt; - } - - EditorWindowPendingTabDragStart pending = {}; - pending.nodeId = std::move(m_pendingTabDragStart.nodeId); - pending.panelId = std::move(m_pendingTabDragStart.panelId); - pending.screenPoint = m_pendingTabDragStart.screenPoint; - - m_pendingTabDragStart = {}; - return pending; -} - -std::optional EditorWindow::ConsumeDetachRequest() { - if (!m_pendingDetachRequest.pending || - m_pendingDetachRequest.nodeId.empty() || - m_pendingDetachRequest.panelId.empty()) { - return std::nullopt; - } - - EditorWindowPendingDetachRequest pending = {}; - pending.nodeId = std::move(m_pendingDetachRequest.nodeId); - pending.panelId = std::move(m_pendingDetachRequest.panelId); - pending.screenPoint = m_pendingDetachRequest.screenPoint; - - m_pendingDetachRequest = {}; - return pending; -} - -void EditorWindow::QueuePendingTabDragStart(std::string_view nodeId, std::string_view panelId) { - POINT screenPoint = {}; - if (!GetCursorPos(&screenPoint)) { - return; - } - - m_pendingTabDragStart.pending = true; - m_pendingTabDragStart.nodeId = std::string(nodeId); - m_pendingTabDragStart.panelId = std::string(panelId); - m_pendingTabDragStart.screenPoint = screenPoint; -} - -void EditorWindow::QueuePendingDetachRequest(std::string_view nodeId, std::string_view panelId) { - POINT screenPoint = {}; - if (!GetCursorPos(&screenPoint)) { - return; - } - - m_pendingDetachRequest.pending = true; - m_pendingDetachRequest.nodeId = std::string(nodeId); - m_pendingDetachRequest.panelId = std::string(panelId); - m_pendingDetachRequest.screenPoint = screenPoint; -} - void EditorWindow::InvalidateHostWindow() const { if (m_window.hwnd != nullptr && IsWindow(m_window.hwnd)) { InvalidateRect(m_window.hwnd, nullptr, FALSE); diff --git a/new_editor/app/Platform/Win32/EditorWindowManager.h b/new_editor/app/Platform/Win32/EditorWindowManager.h index f87cbc8b..11aa0bbc 100644 --- a/new_editor/app/Platform/Win32/EditorWindowManager.h +++ b/new_editor/app/Platform/Win32/EditorWindowManager.h @@ -4,10 +4,6 @@ #define NOMINMAX #endif -#include -#include -#include - #include #include @@ -16,10 +12,22 @@ #include #include +namespace XCEngine::UI::Editor { + +class UIEditorWindowWorkspaceController; +class UIEditorWorkspaceController; + +struct UIEditorWindowWorkspaceSet; +struct UIEditorWindowWorkspaceState; + +} // namespace XCEngine::UI::Editor + namespace XCEngine::UI::Editor::App { class EditorContext; class EditorWindow; +struct EditorWindowPanelTransferRequest; +struct EditorWindowFrameTransferRequests; struct EditorWindowHostConfig { HINSTANCE hInstance = nullptr; @@ -29,6 +37,11 @@ struct EditorWindowHostConfig { void* windowUserData = nullptr; }; +namespace Internal { +class EditorWindowHostRuntime; +class EditorWindowWorkspaceCoordinator; +} + class EditorWindowManager final { public: struct CreateParams { @@ -75,72 +88,15 @@ public: bool OwnsActiveGlobalTabDrag(std::string_view windowId) const; void EndGlobalTabDragSession(); void HandleDestroyedWindow(HWND hwnd); + void HandleWindowFrameTransferRequests( + EditorWindow& sourceWindow, + EditorWindowFrameTransferRequests&& transferRequests); bool HandleGlobalTabDragPointerMove(HWND hwnd); bool HandleGlobalTabDragPointerButtonUp(HWND hwnd); - void ProcessPendingGlobalTabDragStarts(); - void ProcessPendingDetachRequests(); - private: - struct GlobalTabDragSession { - bool active = false; - std::string panelWindowId = {}; - std::string sourceNodeId = {}; - std::string panelId = {}; - std::string previewWindowId = {}; - POINT screenPoint = {}; - POINT dragHotspot = {}; - }; - - void DestroyEditorWindow(EditorWindow& window); - UIEditorWindowWorkspaceSet BuildWindowWorkspaceSet( - std::string_view activeWindowId = {}) const; - UIEditorWindowWorkspaceController BuildLiveWindowWorkspaceController( - std::string_view activeWindowId) const; - bool SynchronizeWindowsFromWindowSet( - const UIEditorWindowWorkspaceSet& windowSet, - std::string_view preferredNewWindowId, - const POINT& preferredScreenPoint); - bool SynchronizeWindowsFromController( - const UIEditorWindowWorkspaceController& windowWorkspaceController, - std::string_view preferredNewWindowId, - const POINT& preferredScreenPoint); - UIEditorWorkspaceController BuildWorkspaceControllerForWindow( - const UIEditorWindowWorkspaceState& windowState) const; - std::wstring BuildWindowTitle( - const UIEditorWorkspaceController& workspaceController) const; - RECT BuildDetachedWindowRect(const POINT& screenPoint) const; - void BeginGlobalTabDragSession( - std::string_view panelWindowId, - std::string_view sourceNodeId, - std::string_view panelId, - const POINT& screenPoint, - const POINT& dragHotspot); - bool TryResolveGlobalTabDragHotspot( - const EditorWindow& sourceWindow, - std::string_view nodeId, - std::string_view panelId, - const POINT& screenPoint, - POINT& outDragHotspot) const; - void ClearGlobalTabDragDropPreview(); - void UpdateGlobalTabDragDropPreview(); - void UpdateGlobalTabDragOwnerWindowPosition(); - EditorWindow* FindTopmostWindowAtScreenPoint( - const POINT& screenPoint, - std::string_view excludedWindowId = {}); - const EditorWindow* FindTopmostWindowAtScreenPoint( - const POINT& screenPoint, - std::string_view excludedWindowId = {}) const; - bool TryStartGlobalTabDrag(EditorWindow& sourceWindow); - bool TryProcessDetachRequest(EditorWindow& sourceWindow); - void LogRuntimeTrace(std::string_view channel, std::string_view message) const; - - EditorWindowHostConfig m_hostConfig = {}; - std::filesystem::path m_repoRoot = {}; - EditorContext& m_editorContext; - std::vector> m_windows = {}; - GlobalTabDragSession m_globalTabDragSession = {}; - EditorWindow* m_pendingCreateWindow = nullptr; + std::unique_ptr m_hostRuntime = {}; + std::unique_ptr m_workspaceCoordinator = {}; }; } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowMetrics.cpp b/new_editor/app/Platform/Win32/EditorWindowMetrics.cpp index b391a57c..cab12a11 100644 --- a/new_editor/app/Platform/Win32/EditorWindowMetrics.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowMetrics.cpp @@ -1,12 +1,12 @@ #include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowConstants.h" -#include "Platform/Win32/EditorWindowRuntimeSupport.h" +#include "Platform/Win32/EditorWindowRuntimeInternal.h" #include namespace XCEngine::UI::Editor::App { -using namespace EditorWindowSupport; +using namespace EditorWindowInternal; using ::XCEngine::UI::UIPoint; bool EditorWindow::ApplyWindowResize(UINT width, UINT height) { diff --git a/new_editor/app/Platform/Win32/EditorWindowPlatformSupport.h b/new_editor/app/Platform/Win32/EditorWindowPlatformInternal.cpp similarity index 69% rename from new_editor/app/Platform/Win32/EditorWindowPlatformSupport.h rename to new_editor/app/Platform/Win32/EditorWindowPlatformInternal.cpp index 9db61934..f3bf6307 100644 --- a/new_editor/app/Platform/Win32/EditorWindowPlatformSupport.h +++ b/new_editor/app/Platform/Win32/EditorWindowPlatformInternal.cpp @@ -1,15 +1,8 @@ -#pragma once +#include "Platform/Win32/EditorWindowPlatformInternal.h" -#include "Platform/Win32/EditorWindowConstants.h" -#include "Support/EmbeddedPngLoader.h" +namespace XCEngine::UI::Editor::App::EditorWindowInternal { -#include - -namespace XCEngine::UI::Editor::App::EditorWindowSupport { - -using Support::LoadEmbeddedPngTexture; - -inline UINT QuerySystemDpi() { +UINT QuerySystemDpi() { HDC screenDc = GetDC(nullptr); if (screenDc == nullptr) { return kDefaultDpi; @@ -20,7 +13,7 @@ inline UINT QuerySystemDpi() { return dpiX > 0 ? static_cast(dpiX) : kDefaultDpi; } -inline UINT QueryWindowDpi(HWND hwnd) { +UINT QueryWindowDpi(HWND hwnd) { if (hwnd != nullptr) { const HMODULE user32 = GetModuleHandleW(L"user32.dll"); if (user32 != nullptr) { @@ -39,4 +32,4 @@ inline UINT QueryWindowDpi(HWND hwnd) { return QuerySystemDpi(); } -} // namespace XCEngine::UI::Editor::App::EditorWindowSupport +} // namespace XCEngine::UI::Editor::App::EditorWindowInternal diff --git a/new_editor/app/Platform/Win32/EditorWindowPlatformInternal.h b/new_editor/app/Platform/Win32/EditorWindowPlatformInternal.h new file mode 100644 index 00000000..432d895f --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowPlatformInternal.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Platform/Win32/EditorWindowConstants.h" +#include "Support/EmbeddedPngLoader.h" + +#include + +namespace XCEngine::UI::Editor::App::EditorWindowInternal { + +using Support::LoadEmbeddedPngTexture; + +UINT QuerySystemDpi(); +UINT QueryWindowDpi(HWND hwnd); + +} // namespace XCEngine::UI::Editor::App::EditorWindowInternal diff --git a/new_editor/app/Platform/Win32/EditorWindowResizeLifecycle.cpp b/new_editor/app/Platform/Win32/EditorWindowResizeLifecycle.cpp index 448af12f..29ac17b9 100644 --- a/new_editor/app/Platform/Win32/EditorWindowResizeLifecycle.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowResizeLifecycle.cpp @@ -1,12 +1,12 @@ #include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowConstants.h" -#include "Platform/Win32/EditorWindowRuntimeSupport.h" +#include "Platform/Win32/EditorWindowRuntimeInternal.h" #include namespace XCEngine::UI::Editor::App { -using namespace EditorWindowSupport; +using namespace EditorWindowInternal; void EditorWindow::OnResize(UINT width, UINT height) { bool matchesPredictedClientSize = false; diff --git a/new_editor/app/Platform/Win32/EditorWindowRuntimeInternal.cpp b/new_editor/app/Platform/Win32/EditorWindowRuntimeInternal.cpp new file mode 100644 index 00000000..610559f9 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowRuntimeInternal.cpp @@ -0,0 +1,24 @@ +#include "Platform/Win32/EditorWindowRuntimeInternal.h" + +#include + +namespace XCEngine::UI::Editor::App::EditorWindowInternal { + +bool ResolveVerboseRuntimeTraceEnabled() { + wchar_t buffer[8] = {}; + const DWORD length = GetEnvironmentVariableW( + L"XCUIEDITOR_VERBOSE_TRACE", + buffer, + static_cast(std::size(buffer))); + return length > 0u && buffer[0] != L'0'; +} + +void LogRuntimeTrace(std::string_view channel, std::string_view message) { + AppendUIEditorRuntimeTrace(channel, message); +} + +bool IsAutoCaptureOnStartupEnabled() { + return Support::IsEnvironmentFlagEnabled("XCUI_AUTO_CAPTURE_ON_STARTUP"); +} + +} // namespace XCEngine::UI::Editor::App::EditorWindowInternal diff --git a/new_editor/app/Platform/Win32/EditorWindowRuntimeInternal.h b/new_editor/app/Platform/Win32/EditorWindowRuntimeInternal.h new file mode 100644 index 00000000..2eddc555 --- /dev/null +++ b/new_editor/app/Platform/Win32/EditorWindowRuntimeInternal.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Support/EnvironmentFlags.h" +#include "Support/StringEncoding.h" +#include "Support/TextFormat.h" + +#include +#include + +#include + +namespace XCEngine::UI::Editor::App::EditorWindowInternal { + +bool ResolveVerboseRuntimeTraceEnabled(); +void LogRuntimeTrace(std::string_view channel, std::string_view message); +bool IsAutoCaptureOnStartupEnabled(); + +using Support::TruncateText; +using Support::WideToUtf8; + +} // namespace XCEngine::UI::Editor::App::EditorWindowInternal diff --git a/new_editor/app/Platform/Win32/EditorWindowRuntimeSupport.h b/new_editor/app/Platform/Win32/EditorWindowRuntimeSupport.h deleted file mode 100644 index d79e8eb1..00000000 --- a/new_editor/app/Platform/Win32/EditorWindowRuntimeSupport.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include "Support/EnvironmentFlags.h" -#include "Support/StringEncoding.h" -#include "Support/TextFormat.h" - -#include - -#include -#include - -#include - -namespace XCEngine::UI::Editor::App::EditorWindowSupport { - -inline bool ResolveVerboseRuntimeTraceEnabled() { - wchar_t buffer[8] = {}; - const DWORD length = GetEnvironmentVariableW( - L"XCUIEDITOR_VERBOSE_TRACE", - buffer, - static_cast(std::size(buffer))); - return length > 0u && buffer[0] != L'0'; -} - -inline void LogRuntimeTrace(std::string_view channel, std::string_view message) { - AppendUIEditorRuntimeTrace(channel, message); -} - -inline bool IsAutoCaptureOnStartupEnabled() { - return Support::IsEnvironmentFlagEnabled("XCUI_AUTO_CAPTURE_ON_STARTUP"); -} - -using Support::TruncateText; -using Support::WideToUtf8; - -} // namespace XCEngine::UI::Editor::App::EditorWindowSupport diff --git a/new_editor/app/Platform/Win32/EditorWindowState.h b/new_editor/app/Platform/Win32/EditorWindowState.h index 91478f34..350d6245 100644 --- a/new_editor/app/Platform/Win32/EditorWindowState.h +++ b/new_editor/app/Platform/Win32/EditorWindowState.h @@ -20,6 +20,7 @@ #include +#include #include #include @@ -58,11 +59,23 @@ struct EditorWindowChromeRuntimeState { Host::HostRuntimeState runtime = {}; }; -struct EditorWindowPanelTransferState { - bool pending = false; +struct EditorWindowPanelTransferRequest { std::string nodeId = {}; std::string panelId = {}; POINT screenPoint = {}; + + bool IsValid() const { + return !nodeId.empty() && !panelId.empty(); + } +}; + +struct EditorWindowFrameTransferRequests { + std::optional beginGlobalTabDrag = {}; + std::optional detachPanel = {}; + + bool HasPendingRequests() const { + return beginGlobalTabDrag.has_value() || detachPanel.has_value(); + } }; } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowStyle.h b/new_editor/app/Platform/Win32/EditorWindowStyle.h index 34a35601..7dbfd9d4 100644 --- a/new_editor/app/Platform/Win32/EditorWindowStyle.h +++ b/new_editor/app/Platform/Win32/EditorWindowStyle.h @@ -2,11 +2,11 @@ #include -namespace XCEngine::UI::Editor::App::EditorWindowSupport { +namespace XCEngine::UI::Editor::App::EditorWindowInternal { inline const ::XCEngine::UI::UIColor kShellSurfaceColor(0.10f, 0.10f, 0.10f, 1.0f); inline const ::XCEngine::UI::UIColor kShellBorderColor(0.15f, 0.15f, 0.15f, 1.0f); inline const ::XCEngine::UI::UIColor kShellTextColor(0.92f, 0.92f, 0.92f, 1.0f); inline const ::XCEngine::UI::UIColor kShellMutedTextColor(0.70f, 0.70f, 0.70f, 1.0f); -} // namespace XCEngine::UI::Editor::App::EditorWindowSupport +} // namespace XCEngine::UI::Editor::App::EditorWindowInternal diff --git a/new_editor/app/Platform/Win32/EditorWindowTitleBar.cpp b/new_editor/app/Platform/Win32/EditorWindowTitleBar.cpp deleted file mode 100644 index c0e1fa46..00000000 --- a/new_editor/app/Platform/Win32/EditorWindowTitleBar.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "Platform/Win32/EditorWindow.h" - -namespace XCEngine::UI::Editor::App { - -// Intentionally kept as the translation unit that matches the public title-bar -// entrypoint name; implementation now lives in focused interaction/rendering files. - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowTitleBarDragRestore.cpp b/new_editor/app/Platform/Win32/EditorWindowTitleBarDragRestore.cpp index af7097dc..2cce7756 100644 --- a/new_editor/app/Platform/Win32/EditorWindowTitleBarDragRestore.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowTitleBarDragRestore.cpp @@ -5,7 +5,7 @@ namespace XCEngine::UI::Editor::App { -using namespace EditorWindowSupport; +using namespace EditorWindowInternal; bool EditorWindow::HandleBorderlessWindowChromeDragRestorePointerMove( EditorContext& editorContext, diff --git a/new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp b/new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp index 585a288f..897d6350 100644 --- a/new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowTitleBarInteraction.cpp @@ -5,7 +5,7 @@ namespace XCEngine::UI::Editor::App { -using namespace EditorWindowSupport; +using namespace EditorWindowInternal; bool EditorWindow::UpdateBorderlessWindowChromeHover(LPARAM lParam) { if (m_chrome.runtime.GetHoveredBorderlessResizeEdge() != diff --git a/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp b/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp index 15fc4162..179a9ef1 100644 --- a/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowTitleBarRendering.cpp @@ -15,7 +15,7 @@ namespace XCEngine::UI::Editor::App { -using namespace EditorWindowSupport; +using namespace EditorWindowInternal; using ::XCEngine::UI::Layout::MeasureUITabStripHeaderWidth; using ::XCEngine::UI::UIColor; using ::XCEngine::UI::UIDrawList; diff --git a/new_editor/app/Platform/Win32/WindowManager/Creation.cpp b/new_editor/app/Platform/Win32/WindowManager/Creation.cpp index 49ba186c..e938850a 100644 --- a/new_editor/app/Platform/Win32/WindowManager/Creation.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/Creation.cpp @@ -1,4 +1,4 @@ -#include "Platform/Win32/EditorWindowManager.h" +#include "Platform/Win32/WindowManager/Internal.h" #include "State/EditorContext.h" #include "Bootstrap/EditorResources.h" @@ -6,9 +6,9 @@ #include -namespace XCEngine::UI::Editor::App { +namespace XCEngine::UI::Editor::App::Internal { -EditorWindow* EditorWindowManager::CreateEditorWindow( +EditorWindow* EditorWindowHostRuntime::CreateEditorWindow( UIEditorWorkspaceController workspaceController, const CreateParams& params) { auto windowPtr = std::make_unique( @@ -98,10 +98,10 @@ EditorWindow* EditorWindowManager::CreateEditorWindow( return rawWindow; } -void EditorWindowManager::HandlePendingNativeWindowCreated(HWND hwnd) { +void EditorWindowHostRuntime::HandlePendingNativeWindowCreated(HWND hwnd) { if (m_pendingCreateWindow != nullptr && !m_pendingCreateWindow->HasHwnd()) { m_pendingCreateWindow->AttachHwnd(hwnd); } } -} // namespace XCEngine::UI::Editor::App +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/WindowManager/CrossWindowDrop.cpp b/new_editor/app/Platform/Win32/WindowManager/CrossWindowDrop.cpp index 382337af..8e54e82b 100644 --- a/new_editor/app/Platform/Win32/WindowManager/CrossWindowDrop.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/CrossWindowDrop.cpp @@ -1,21 +1,23 @@ -#include "Platform/Win32/EditorWindowManager.h" +#include "Platform/Win32/WindowManager/Internal.h" #include "Platform/Win32/WindowManager/CrossWindowDropInternal.h" #include "State/EditorContext.h" #include "Platform/Win32/EditorWindow.h" -namespace XCEngine::UI::Editor::App { +#include + +namespace XCEngine::UI::Editor::App::Internal { using Win32::Internal::CrossWindowDockDropTarget; using Win32::Internal::TryResolveCrossWindowDockDropTarget; using ::XCEngine::UI::UIPoint; -bool EditorWindowManager::HandleGlobalTabDragPointerButtonUp(HWND hwnd) { +bool EditorWindowWorkspaceCoordinator::HandleGlobalTabDragPointerButtonUp(HWND hwnd) { if (!m_globalTabDragSession.active) { return false; } - const EditorWindow* ownerWindow = FindWindow(m_globalTabDragSession.panelWindowId); + const EditorWindow* ownerWindow = m_hostRuntime.FindWindow(m_globalTabDragSession.panelWindowId); if (ownerWindow == nullptr || ownerWindow->GetHwnd() != hwnd) { return false; } @@ -76,8 +78,10 @@ bool EditorWindowManager::HandleGlobalTabDragPointerButtonUp(HWND hwnd) { return true; } - if (targetWindow->GetHwnd() != nullptr) { - SetForegroundWindow(targetWindow->GetHwnd()); + if (EditorWindow* updatedTargetWindow = m_hostRuntime.FindWindow(targetWindow->GetWindowId()); + updatedTargetWindow != nullptr && + updatedTargetWindow->GetHwnd() != nullptr) { + SetForegroundWindow(updatedTargetWindow->GetHwnd()); } LogRuntimeTrace( "drag", @@ -86,4 +90,4 @@ bool EditorWindowManager::HandleGlobalTabDragPointerButtonUp(HWND hwnd) { return true; } -} // namespace XCEngine::UI::Editor::App +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/WindowManager/CrossWindowDropInternal.cpp b/new_editor/app/Platform/Win32/WindowManager/CrossWindowDropInternal.cpp new file mode 100644 index 00000000..d15743b7 --- /dev/null +++ b/new_editor/app/Platform/Win32/WindowManager/CrossWindowDropInternal.cpp @@ -0,0 +1,109 @@ +#include "Platform/Win32/WindowManager/CrossWindowDropInternal.h" + +#include + +#include + +namespace XCEngine::UI::Editor::App::Win32::Internal { + +bool TryResolveCrossWindowDockDropTarget( + const Widgets::UIEditorDockHostLayout& layout, + const ::XCEngine::UI::UIPoint& point, + CrossWindowDockDropTarget& outTarget) { + using ::XCEngine::UI::UIPoint; + using ::XCEngine::UI::UIRect; + + const auto isPointInsideRect = [](const UIRect& rect, const UIPoint& targetPoint) { + return targetPoint.x >= rect.x && + targetPoint.x <= rect.x + rect.width && + targetPoint.y >= rect.y && + targetPoint.y <= rect.y + rect.height; + }; + + const auto resolveInsertionIndex = [&](const Widgets::UIEditorDockHostTabStackLayout& tabStack) { + if (!isPointInsideRect(tabStack.tabStripLayout.headerRect, point)) { + return Widgets::UIEditorTabStripInvalidIndex; + } + + std::size_t insertionIndex = 0u; + for (const UIRect& rect : tabStack.tabStripLayout.tabHeaderRects) { + const float midpoint = rect.x + rect.width * 0.5f; + if (point.x > midpoint) { + ++insertionIndex; + } + } + return insertionIndex; + }; + + const auto resolvePlacement = [&](const Widgets::UIEditorDockHostTabStackLayout& tabStack) { + if (isPointInsideRect(tabStack.tabStripLayout.headerRect, point)) { + return UIEditorWorkspaceDockPlacement::Center; + } + + const float leftDistance = point.x - tabStack.bounds.x; + const float rightDistance = tabStack.bounds.x + tabStack.bounds.width - point.x; + const float topDistance = point.y - tabStack.bounds.y; + const float bottomDistance = tabStack.bounds.y + tabStack.bounds.height - point.y; + const float minHorizontalThreshold = tabStack.bounds.width * 0.25f; + const float minVerticalThreshold = tabStack.bounds.height * 0.25f; + const float nearestEdge = (std::min)( + (std::min)(leftDistance, rightDistance), + (std::min)(topDistance, bottomDistance)); + + if (nearestEdge == leftDistance && leftDistance <= minHorizontalThreshold) { + return UIEditorWorkspaceDockPlacement::Left; + } + if (nearestEdge == rightDistance && rightDistance <= minHorizontalThreshold) { + return UIEditorWorkspaceDockPlacement::Right; + } + if (nearestEdge == topDistance && topDistance <= minVerticalThreshold) { + return UIEditorWorkspaceDockPlacement::Top; + } + if (nearestEdge == bottomDistance && bottomDistance <= minVerticalThreshold) { + return UIEditorWorkspaceDockPlacement::Bottom; + } + + return UIEditorWorkspaceDockPlacement::Center; + }; + + outTarget = {}; + if (!isPointInsideRect(layout.bounds, point)) { + return false; + } + + const Widgets::UIEditorDockHostHitTarget hitTarget = + Widgets::HitTestUIEditorDockHost(layout, point); + for (const Widgets::UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { + if ((!hitTarget.nodeId.empty() && tabStack.nodeId != hitTarget.nodeId) || + !isPointInsideRect(tabStack.bounds, point)) { + continue; + } + + outTarget.valid = true; + outTarget.nodeId = tabStack.nodeId; + outTarget.placement = resolvePlacement(tabStack); + if (outTarget.placement == UIEditorWorkspaceDockPlacement::Center) { + outTarget.insertionIndex = resolveInsertionIndex(tabStack); + if (outTarget.insertionIndex == Widgets::UIEditorTabStripInvalidIndex) { + outTarget.insertionIndex = tabStack.items.size(); + } + } + return true; + } + + for (const Widgets::UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { + if (isPointInsideRect(tabStack.bounds, point)) { + outTarget.valid = true; + outTarget.nodeId = tabStack.nodeId; + outTarget.placement = resolvePlacement(tabStack); + if (outTarget.placement == UIEditorWorkspaceDockPlacement::Center) { + outTarget.insertionIndex = tabStack.items.size(); + } + return true; + } + } + + return false; +} + +} // namespace XCEngine::UI::Editor::App::Win32::Internal diff --git a/new_editor/app/Platform/Win32/WindowManager/CrossWindowDropInternal.h b/new_editor/app/Platform/Win32/WindowManager/CrossWindowDropInternal.h index 9316dd3f..0b1de4b5 100644 --- a/new_editor/app/Platform/Win32/WindowManager/CrossWindowDropInternal.h +++ b/new_editor/app/Platform/Win32/WindowManager/CrossWindowDropInternal.h @@ -2,7 +2,6 @@ #include -#include #include #include @@ -19,104 +18,9 @@ struct CrossWindowDockDropTarget { std::size_t insertionIndex = Widgets::UIEditorTabStripInvalidIndex; }; -inline bool TryResolveCrossWindowDockDropTarget( +bool TryResolveCrossWindowDockDropTarget( const Widgets::UIEditorDockHostLayout& layout, const ::XCEngine::UI::UIPoint& point, - CrossWindowDockDropTarget& outTarget) { - using ::XCEngine::UI::UIPoint; - using ::XCEngine::UI::UIRect; - - const auto isPointInsideRect = [](const UIRect& rect, const UIPoint& targetPoint) { - return targetPoint.x >= rect.x && - targetPoint.x <= rect.x + rect.width && - targetPoint.y >= rect.y && - targetPoint.y <= rect.y + rect.height; - }; - - const auto resolveInsertionIndex = [&](const Widgets::UIEditorDockHostTabStackLayout& tabStack) { - if (!isPointInsideRect(tabStack.tabStripLayout.headerRect, point)) { - return Widgets::UIEditorTabStripInvalidIndex; - } - - std::size_t insertionIndex = 0u; - for (const UIRect& rect : tabStack.tabStripLayout.tabHeaderRects) { - const float midpoint = rect.x + rect.width * 0.5f; - if (point.x > midpoint) { - ++insertionIndex; - } - } - return insertionIndex; - }; - - const auto resolvePlacement = [&](const Widgets::UIEditorDockHostTabStackLayout& tabStack) { - if (isPointInsideRect(tabStack.tabStripLayout.headerRect, point)) { - return UIEditorWorkspaceDockPlacement::Center; - } - - const float leftDistance = point.x - tabStack.bounds.x; - const float rightDistance = tabStack.bounds.x + tabStack.bounds.width - point.x; - const float topDistance = point.y - tabStack.bounds.y; - const float bottomDistance = tabStack.bounds.y + tabStack.bounds.height - point.y; - const float minHorizontalThreshold = tabStack.bounds.width * 0.25f; - const float minVerticalThreshold = tabStack.bounds.height * 0.25f; - const float nearestEdge = (std::min)( - (std::min)(leftDistance, rightDistance), - (std::min)(topDistance, bottomDistance)); - - if (nearestEdge == leftDistance && leftDistance <= minHorizontalThreshold) { - return UIEditorWorkspaceDockPlacement::Left; - } - if (nearestEdge == rightDistance && rightDistance <= minHorizontalThreshold) { - return UIEditorWorkspaceDockPlacement::Right; - } - if (nearestEdge == topDistance && topDistance <= minVerticalThreshold) { - return UIEditorWorkspaceDockPlacement::Top; - } - if (nearestEdge == bottomDistance && bottomDistance <= minVerticalThreshold) { - return UIEditorWorkspaceDockPlacement::Bottom; - } - - return UIEditorWorkspaceDockPlacement::Center; - }; - - outTarget = {}; - if (!isPointInsideRect(layout.bounds, point)) { - return false; - } - - const Widgets::UIEditorDockHostHitTarget hitTarget = - Widgets::HitTestUIEditorDockHost(layout, point); - for (const Widgets::UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { - if ((!hitTarget.nodeId.empty() && tabStack.nodeId != hitTarget.nodeId) || - !isPointInsideRect(tabStack.bounds, point)) { - continue; - } - - outTarget.valid = true; - outTarget.nodeId = tabStack.nodeId; - outTarget.placement = resolvePlacement(tabStack); - if (outTarget.placement == UIEditorWorkspaceDockPlacement::Center) { - outTarget.insertionIndex = resolveInsertionIndex(tabStack); - if (outTarget.insertionIndex == Widgets::UIEditorTabStripInvalidIndex) { - outTarget.insertionIndex = tabStack.items.size(); - } - } - return true; - } - - for (const Widgets::UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { - if (isPointInsideRect(tabStack.bounds, point)) { - outTarget.valid = true; - outTarget.nodeId = tabStack.nodeId; - outTarget.placement = resolvePlacement(tabStack); - if (outTarget.placement == UIEditorWorkspaceDockPlacement::Center) { - outTarget.insertionIndex = tabStack.items.size(); - } - return true; - } - } - - return false; -} + CrossWindowDockDropTarget& outTarget); } // namespace XCEngine::UI::Editor::App::Win32::Internal diff --git a/new_editor/app/Platform/Win32/WindowManager/Detach.cpp b/new_editor/app/Platform/Win32/WindowManager/Detach.cpp index 7fedf530..47721949 100644 --- a/new_editor/app/Platform/Win32/WindowManager/Detach.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/Detach.cpp @@ -1,44 +1,23 @@ -#include "Platform/Win32/EditorWindowManager.h" +#include "Platform/Win32/WindowManager/Internal.h" #include "State/EditorContext.h" #include "Platform/Win32/EditorWindow.h" -namespace XCEngine::UI::Editor::App { +#include -void EditorWindowManager::ProcessPendingDetachRequests() { - if (m_globalTabDragSession.active) { - return; - } - - std::vector windowsToProcess = {}; - for (const std::unique_ptr& window : m_windows) { - if (window != nullptr && window->GetHwnd() != nullptr) { - windowsToProcess.push_back(window.get()); - } - } - - for (EditorWindow* window : windowsToProcess) { - if (window != nullptr) { - TryProcessDetachRequest(*window); - } - } -} - -bool EditorWindowManager::TryProcessDetachRequest(EditorWindow& sourceWindow) { - const std::optional pending = - sourceWindow.ConsumeDetachRequest(); - if (!pending.has_value()) { - return false; - } +namespace XCEngine::UI::Editor::App::Internal { +bool EditorWindowWorkspaceCoordinator::TryProcessDetachRequest( + EditorWindow& sourceWindow, + const EditorWindowPanelTransferRequest& request) { const std::string sourceWindowId(sourceWindow.GetWindowId()); UIEditorWindowWorkspaceController windowWorkspaceController = BuildLiveWindowWorkspaceController(sourceWindowId); const UIEditorWindowWorkspaceOperationResult result = windowWorkspaceController.DetachPanelToNewWindow( sourceWindowId, - pending->nodeId, - pending->panelId); + request.nodeId, + request.panelId); if (result.status != UIEditorWindowWorkspaceOperationStatus::Changed) { LogRuntimeTrace("detach", "detach request rejected: " + result.message); return false; @@ -47,12 +26,12 @@ bool EditorWindowManager::TryProcessDetachRequest(EditorWindow& sourceWindow) { if (!SynchronizeWindowsFromController( windowWorkspaceController, result.targetWindowId, - pending->screenPoint)) { + request.screenPoint)) { LogRuntimeTrace("detach", "failed to synchronize detached window state"); return false; } - if (EditorWindow* detachedWindow = FindWindow(result.targetWindowId); + if (EditorWindow* detachedWindow = m_hostRuntime.FindWindow(result.targetWindowId); detachedWindow != nullptr && detachedWindow->GetHwnd() != nullptr) { SetForegroundWindow(detachedWindow->GetHwnd()); @@ -60,9 +39,9 @@ bool EditorWindowManager::TryProcessDetachRequest(EditorWindow& sourceWindow) { LogRuntimeTrace( "detach", - "detached panel '" + pending->panelId + "' from window '" + sourceWindowId + + "detached panel '" + request.panelId + "' from window '" + sourceWindowId + "' to window '" + result.targetWindowId + "'"); return true; } -} // namespace XCEngine::UI::Editor::App +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/WindowManager/Internal.h b/new_editor/app/Platform/Win32/WindowManager/Internal.h new file mode 100644 index 00000000..34bb853a --- /dev/null +++ b/new_editor/app/Platform/Win32/WindowManager/Internal.h @@ -0,0 +1,151 @@ +#pragma once + +#include "Platform/Win32/EditorWindowManager.h" + +#include +#include + +namespace XCEngine::UI::Editor::App::Internal { + +class EditorWindowHostRuntime final { +public: + using CreateParams = EditorWindowManager::CreateParams; + + EditorWindowHostRuntime( + EditorWindowHostConfig hostConfig, + std::filesystem::path repoRoot, + EditorContext& editorContext); + ~EditorWindowHostRuntime(); + + EditorWindow* CreateEditorWindow( + UIEditorWorkspaceController workspaceController, + const CreateParams& params); + void HandlePendingNativeWindowCreated(HWND hwnd); + void Shutdown(); + + EditorWindow* FindWindow(HWND hwnd); + const EditorWindow* FindWindow(HWND hwnd) const; + EditorWindow* FindWindow(std::string_view windowId); + const EditorWindow* FindWindow(std::string_view windowId) const; + EditorWindow* FindPrimaryWindow(); + const EditorWindow* FindPrimaryWindow() const; + + bool HasWindows() const; + void DestroyClosedWindows(); + void RenderAllWindows( + bool globalTabDragActive, + EditorWindowWorkspaceCoordinator& workspaceCoordinator); + void HandleDestroyedWindow(HWND hwnd); + + EditorContext& GetEditorContext() { + return m_editorContext; + } + + const EditorContext& GetEditorContext() const { + return m_editorContext; + } + + const EditorWindowHostConfig& GetHostConfig() const { + return m_hostConfig; + } + + const std::filesystem::path& GetRepoRoot() const { + return m_repoRoot; + } + + std::vector>& GetWindows() { + return m_windows; + } + + const std::vector>& GetWindows() const { + return m_windows; + } + + void LogRuntimeTrace(std::string_view channel, std::string_view message) const; + +private: + void DestroyEditorWindow(EditorWindow& window); + + EditorWindowHostConfig m_hostConfig = {}; + std::filesystem::path m_repoRoot = {}; + EditorContext& m_editorContext; + std::vector> m_windows = {}; + EditorWindow* m_pendingCreateWindow = nullptr; +}; + +class EditorWindowWorkspaceCoordinator final { +public: + explicit EditorWindowWorkspaceCoordinator(EditorWindowHostRuntime& hostRuntime); + ~EditorWindowWorkspaceCoordinator(); + + bool IsGlobalTabDragActive() const; + bool OwnsActiveGlobalTabDrag(std::string_view windowId) const; + void EndGlobalTabDragSession(); + bool HandleGlobalTabDragPointerMove(HWND hwnd); + bool HandleGlobalTabDragPointerButtonUp(HWND hwnd); + void HandleWindowFrameTransferRequests( + EditorWindow& sourceWindow, + EditorWindowFrameTransferRequests&& transferRequests); + +private: + struct GlobalTabDragSession { + bool active = false; + std::string panelWindowId = {}; + std::string sourceNodeId = {}; + std::string panelId = {}; + std::string previewWindowId = {}; + POINT screenPoint = {}; + POINT dragHotspot = {}; + }; + + UIEditorWindowWorkspaceSet BuildWindowWorkspaceSet( + std::string_view activeWindowId = {}) const; + UIEditorWindowWorkspaceController BuildLiveWindowWorkspaceController( + std::string_view activeWindowId) const; + bool SynchronizeWindowsFromWindowSet( + const UIEditorWindowWorkspaceSet& windowSet, + std::string_view preferredNewWindowId, + const POINT& preferredScreenPoint); + bool SynchronizeWindowsFromController( + const UIEditorWindowWorkspaceController& windowWorkspaceController, + std::string_view preferredNewWindowId, + const POINT& preferredScreenPoint); + UIEditorWorkspaceController BuildWorkspaceControllerForWindow( + const UIEditorWindowWorkspaceState& windowState) const; + std::wstring BuildWindowTitle( + const UIEditorWorkspaceController& workspaceController) const; + RECT BuildDetachedWindowRect(const POINT& screenPoint) const; + void BeginGlobalTabDragSession( + std::string_view panelWindowId, + std::string_view sourceNodeId, + std::string_view panelId, + const POINT& screenPoint, + const POINT& dragHotspot); + bool TryResolveGlobalTabDragHotspot( + const EditorWindow& sourceWindow, + std::string_view nodeId, + std::string_view panelId, + const POINT& screenPoint, + POINT& outDragHotspot) const; + void ClearGlobalTabDragDropPreview(); + void UpdateGlobalTabDragDropPreview(); + void UpdateGlobalTabDragOwnerWindowPosition(); + EditorWindow* FindTopmostWindowAtScreenPoint( + const POINT& screenPoint, + std::string_view excludedWindowId = {}); + const EditorWindow* FindTopmostWindowAtScreenPoint( + const POINT& screenPoint, + std::string_view excludedWindowId = {}) const; + bool TryStartGlobalTabDrag( + EditorWindow& sourceWindow, + const EditorWindowPanelTransferRequest& request); + bool TryProcessDetachRequest( + EditorWindow& sourceWindow, + const EditorWindowPanelTransferRequest& request); + void LogRuntimeTrace(std::string_view channel, std::string_view message) const; + + EditorWindowHostRuntime& m_hostRuntime; + GlobalTabDragSession m_globalTabDragSession = {}; +}; + +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp b/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp index 56b5b2ce..f2a95b5b 100644 --- a/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/Lifecycle.cpp @@ -1,13 +1,118 @@ -#include "Platform/Win32/EditorWindowManager.h" +#include "Platform/Win32/WindowManager/Internal.h" #include "State/EditorContext.h" #include "Platform/Win32/EditorWindow.h" #include +#include + namespace XCEngine::UI::Editor::App { EditorWindowManager::EditorWindowManager( + EditorWindowHostConfig hostConfig, + std::filesystem::path repoRoot, + EditorContext& editorContext) + : m_hostRuntime(std::make_unique( + hostConfig, + std::move(repoRoot), + editorContext)) { + m_workspaceCoordinator = + std::make_unique(*m_hostRuntime); +} + +EditorWindowManager::~EditorWindowManager() = default; + +EditorWindow* EditorWindowManager::CreateEditorWindow( + UIEditorWorkspaceController workspaceController, + const CreateParams& params) { + return m_hostRuntime->CreateEditorWindow(std::move(workspaceController), params); +} + +void EditorWindowManager::HandlePendingNativeWindowCreated(HWND hwnd) { + m_hostRuntime->HandlePendingNativeWindowCreated(hwnd); +} + +void EditorWindowManager::Shutdown() { + m_workspaceCoordinator->EndGlobalTabDragSession(); + m_hostRuntime->Shutdown(); +} + +EditorWindow* EditorWindowManager::FindWindow(HWND hwnd) { + return m_hostRuntime->FindWindow(hwnd); +} + +const EditorWindow* EditorWindowManager::FindWindow(HWND hwnd) const { + return m_hostRuntime->FindWindow(hwnd); +} + +EditorWindow* EditorWindowManager::FindWindow(std::string_view windowId) { + return m_hostRuntime->FindWindow(windowId); +} + +const EditorWindow* EditorWindowManager::FindWindow(std::string_view windowId) const { + return m_hostRuntime->FindWindow(windowId); +} + +EditorWindow* EditorWindowManager::FindPrimaryWindow() { + return m_hostRuntime->FindPrimaryWindow(); +} + +const EditorWindow* EditorWindowManager::FindPrimaryWindow() const { + return m_hostRuntime->FindPrimaryWindow(); +} + +bool EditorWindowManager::HasWindows() const { + return m_hostRuntime->HasWindows(); +} + +void EditorWindowManager::DestroyClosedWindows() { + m_hostRuntime->DestroyClosedWindows(); +} + +void EditorWindowManager::RenderAllWindows() { + m_hostRuntime->RenderAllWindows( + m_workspaceCoordinator->IsGlobalTabDragActive(), + *m_workspaceCoordinator); +} + +bool EditorWindowManager::IsGlobalTabDragActive() const { + return m_workspaceCoordinator->IsGlobalTabDragActive(); +} + +bool EditorWindowManager::OwnsActiveGlobalTabDrag(std::string_view windowId) const { + return m_workspaceCoordinator->OwnsActiveGlobalTabDrag(windowId); +} + +void EditorWindowManager::EndGlobalTabDragSession() { + m_workspaceCoordinator->EndGlobalTabDragSession(); +} + +void EditorWindowManager::HandleDestroyedWindow(HWND hwnd) { + m_hostRuntime->HandleDestroyedWindow(hwnd); +} + +void EditorWindowManager::HandleWindowFrameTransferRequests( + EditorWindow& sourceWindow, + EditorWindowFrameTransferRequests&& transferRequests) { + m_workspaceCoordinator->HandleWindowFrameTransferRequests( + sourceWindow, + std::move(transferRequests)); +} + +bool EditorWindowManager::HandleGlobalTabDragPointerMove(HWND hwnd) { + return m_workspaceCoordinator->HandleGlobalTabDragPointerMove(hwnd); +} + +bool EditorWindowManager::HandleGlobalTabDragPointerButtonUp(HWND hwnd) { + return m_workspaceCoordinator->HandleGlobalTabDragPointerButtonUp(hwnd); +} + +} // namespace XCEngine::UI::Editor::App + +namespace XCEngine::UI::Editor::App::Internal { + +EditorWindowHostRuntime::EditorWindowHostRuntime( EditorWindowHostConfig hostConfig, std::filesystem::path repoRoot, EditorContext& editorContext) @@ -15,9 +120,9 @@ EditorWindowManager::EditorWindowManager( m_repoRoot(std::move(repoRoot)), m_editorContext(editorContext) {} -EditorWindowManager::~EditorWindowManager() = default; +EditorWindowHostRuntime::~EditorWindowHostRuntime() = default; -void EditorWindowManager::Shutdown() { +void EditorWindowHostRuntime::Shutdown() { for (const std::unique_ptr& window : m_windows) { if (window != nullptr) { DestroyEditorWindow(*window); @@ -25,14 +130,13 @@ void EditorWindowManager::Shutdown() { } m_windows.clear(); m_pendingCreateWindow = nullptr; - m_globalTabDragSession = {}; } -bool EditorWindowManager::HasWindows() const { +bool EditorWindowHostRuntime::HasWindows() const { return !m_windows.empty(); } -void EditorWindowManager::DestroyEditorWindow(EditorWindow& window) { +void EditorWindowHostRuntime::DestroyEditorWindow(EditorWindow& window) { const HWND hwnd = window.GetHwnd(); if (GetCapture() == hwnd) { ReleaseCapture(); @@ -45,7 +149,7 @@ void EditorWindowManager::DestroyEditorWindow(EditorWindow& window) { window.MarkDestroyed(); } -void EditorWindowManager::DestroyClosedWindows() { +void EditorWindowHostRuntime::DestroyClosedWindows() { for (auto it = m_windows.begin(); it != m_windows.end();) { EditorWindow* const window = it->get(); if (window == nullptr || window->GetHwnd() != nullptr) { @@ -62,17 +166,46 @@ void EditorWindowManager::DestroyClosedWindows() { } } -void EditorWindowManager::RenderAllWindows() { +void EditorWindowHostRuntime::RenderAllWindows( + bool globalTabDragActive, + EditorWindowWorkspaceCoordinator& workspaceCoordinator) { + struct WindowFrameTransferBatch { + EditorWindow* sourceWindow = nullptr; + EditorWindowFrameTransferRequests requests = {}; + }; + + std::vector transferBatches = {}; + transferBatches.reserve(m_windows.size()); + for (const std::unique_ptr& window : m_windows) { if (window == nullptr || window->GetHwnd() == nullptr) { continue; } - window->RenderFrame(m_editorContext, IsGlobalTabDragActive()); + EditorWindowFrameTransferRequests transferRequests = + window->RenderFrame(m_editorContext, globalTabDragActive); + if (!transferRequests.HasPendingRequests()) { + continue; + } + + transferBatches.push_back(WindowFrameTransferBatch{ + window.get(), + std::move(transferRequests), + }); + } + + for (WindowFrameTransferBatch& batch : transferBatches) { + if (batch.sourceWindow == nullptr || batch.sourceWindow->GetHwnd() == nullptr) { + continue; + } + + workspaceCoordinator.HandleWindowFrameTransferRequests( + *batch.sourceWindow, + std::move(batch.requests)); } } -void EditorWindowManager::HandleDestroyedWindow(HWND hwnd) { +void EditorWindowHostRuntime::HandleDestroyedWindow(HWND hwnd) { if (EditorWindow* window = FindWindow(hwnd); window != nullptr) { window->MarkDestroyed(); if (window->IsPrimary()) { @@ -87,10 +220,32 @@ void EditorWindowManager::HandleDestroyedWindow(HWND hwnd) { } } -void EditorWindowManager::LogRuntimeTrace( +void EditorWindowHostRuntime::LogRuntimeTrace( std::string_view channel, std::string_view message) const { AppendUIEditorRuntimeTrace(channel, message); } -} // namespace XCEngine::UI::Editor::App +EditorWindowWorkspaceCoordinator::EditorWindowWorkspaceCoordinator( + EditorWindowHostRuntime& hostRuntime) + : m_hostRuntime(hostRuntime) {} + +EditorWindowWorkspaceCoordinator::~EditorWindowWorkspaceCoordinator() = default; + +void EditorWindowWorkspaceCoordinator::HandleWindowFrameTransferRequests( + EditorWindow& sourceWindow, + EditorWindowFrameTransferRequests&& transferRequests) { + if (!m_globalTabDragSession.active && + transferRequests.beginGlobalTabDrag.has_value() && + transferRequests.beginGlobalTabDrag->IsValid()) { + TryStartGlobalTabDrag(sourceWindow, *transferRequests.beginGlobalTabDrag); + } + + if (!m_globalTabDragSession.active && + transferRequests.detachPanel.has_value() && + transferRequests.detachPanel->IsValid()) { + TryProcessDetachRequest(sourceWindow, *transferRequests.detachPanel); + } +} + +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/WindowManager/Lookup.cpp b/new_editor/app/Platform/Win32/WindowManager/Lookup.cpp index e3bfaca9..7e52e1ee 100644 --- a/new_editor/app/Platform/Win32/WindowManager/Lookup.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/Lookup.cpp @@ -1,10 +1,10 @@ -#include "Platform/Win32/EditorWindowManager.h" +#include "Platform/Win32/WindowManager/Internal.h" #include "Platform/Win32/EditorWindow.h" -namespace XCEngine::UI::Editor::App { +namespace XCEngine::UI::Editor::App::Internal { -EditorWindow* EditorWindowManager::FindWindow(HWND hwnd) { +EditorWindow* EditorWindowHostRuntime::FindWindow(HWND hwnd) { if (hwnd == nullptr) { return nullptr; } @@ -18,11 +18,11 @@ EditorWindow* EditorWindowManager::FindWindow(HWND hwnd) { return nullptr; } -const EditorWindow* EditorWindowManager::FindWindow(HWND hwnd) const { - return const_cast(this)->FindWindow(hwnd); +const EditorWindow* EditorWindowHostRuntime::FindWindow(HWND hwnd) const { + return const_cast(this)->FindWindow(hwnd); } -EditorWindow* EditorWindowManager::FindWindow(std::string_view windowId) { +EditorWindow* EditorWindowHostRuntime::FindWindow(std::string_view windowId) { if (windowId.empty()) { return nullptr; } @@ -36,11 +36,11 @@ EditorWindow* EditorWindowManager::FindWindow(std::string_view windowId) { return nullptr; } -const EditorWindow* EditorWindowManager::FindWindow(std::string_view windowId) const { - return const_cast(this)->FindWindow(windowId); +const EditorWindow* EditorWindowHostRuntime::FindWindow(std::string_view windowId) const { + return const_cast(this)->FindWindow(windowId); } -EditorWindow* EditorWindowManager::FindPrimaryWindow() { +EditorWindow* EditorWindowHostRuntime::FindPrimaryWindow() { for (const std::unique_ptr& window : m_windows) { if (window != nullptr && window->IsPrimary()) { return window.get(); @@ -50,49 +50,8 @@ EditorWindow* EditorWindowManager::FindPrimaryWindow() { return nullptr; } -const EditorWindow* EditorWindowManager::FindPrimaryWindow() const { - return const_cast(this)->FindPrimaryWindow(); +const EditorWindow* EditorWindowHostRuntime::FindPrimaryWindow() const { + return const_cast(this)->FindPrimaryWindow(); } -EditorWindow* EditorWindowManager::FindTopmostWindowAtScreenPoint( - const POINT& screenPoint, - std::string_view excludedWindowId) { - if (const HWND hitWindow = WindowFromPoint(screenPoint); hitWindow != nullptr) { - const HWND rootWindow = GetAncestor(hitWindow, GA_ROOT); - if (EditorWindow* window = FindWindow(rootWindow); - window != nullptr && - window->GetWindowId() != excludedWindowId) { - return window; - } - } - - for (auto it = m_windows.rbegin(); it != m_windows.rend(); ++it) { - EditorWindow* const window = it->get(); - if (window == nullptr || - window->GetHwnd() == nullptr || - window->GetWindowId() == excludedWindowId) { - continue; - } - - RECT windowRect = {}; - if (GetWindowRect(window->GetHwnd(), &windowRect) && - screenPoint.x >= windowRect.left && - screenPoint.x < windowRect.right && - screenPoint.y >= windowRect.top && - screenPoint.y < windowRect.bottom) { - return window; - } - } - - return nullptr; -} - -const EditorWindow* EditorWindowManager::FindTopmostWindowAtScreenPoint( - const POINT& screenPoint, - std::string_view excludedWindowId) const { - return const_cast(this)->FindTopmostWindowAtScreenPoint( - screenPoint, - excludedWindowId); -} - -} // namespace XCEngine::UI::Editor::App +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/WindowManager/TabDrag.cpp b/new_editor/app/Platform/Win32/WindowManager/TabDrag.cpp index 78f597df..013db2d3 100644 --- a/new_editor/app/Platform/Win32/WindowManager/TabDrag.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/TabDrag.cpp @@ -1,13 +1,15 @@ -#include "Platform/Win32/EditorWindowManager.h" +#include "Platform/Win32/WindowManager/Internal.h" #include "Platform/Win32/WindowManager/CrossWindowDropInternal.h" #include "State/EditorContext.h" #include "Platform/Win32/EditorWindow.h" +#include + #include #include -namespace XCEngine::UI::Editor::App { +namespace XCEngine::UI::Editor::App::Internal { namespace { @@ -35,16 +37,16 @@ float ResolveWindowDpiScale(HWND hwnd) { } // namespace -bool EditorWindowManager::IsGlobalTabDragActive() const { +bool EditorWindowWorkspaceCoordinator::IsGlobalTabDragActive() const { return m_globalTabDragSession.active; } -bool EditorWindowManager::OwnsActiveGlobalTabDrag(std::string_view windowId) const { +bool EditorWindowWorkspaceCoordinator::OwnsActiveGlobalTabDrag(std::string_view windowId) const { return m_globalTabDragSession.active && m_globalTabDragSession.panelWindowId == windowId; } -void EditorWindowManager::BeginGlobalTabDragSession( +void EditorWindowWorkspaceCoordinator::BeginGlobalTabDragSession( std::string_view panelWindowId, std::string_view sourceNodeId, std::string_view panelId, @@ -58,7 +60,7 @@ void EditorWindowManager::BeginGlobalTabDragSession( m_globalTabDragSession.dragHotspot = dragHotspot; } -bool EditorWindowManager::TryResolveGlobalTabDragHotspot( +bool EditorWindowWorkspaceCoordinator::TryResolveGlobalTabDragHotspot( const EditorWindow& sourceWindow, std::string_view nodeId, std::string_view panelId, @@ -105,12 +107,12 @@ bool EditorWindowManager::TryResolveGlobalTabDragHotspot( return false; } -void EditorWindowManager::UpdateGlobalTabDragOwnerWindowPosition() { +void EditorWindowWorkspaceCoordinator::UpdateGlobalTabDragOwnerWindowPosition() { if (!m_globalTabDragSession.active) { return; } - EditorWindow* ownerWindow = FindWindow(m_globalTabDragSession.panelWindowId); + EditorWindow* ownerWindow = m_hostRuntime.FindWindow(m_globalTabDragSession.panelWindowId); if (ownerWindow == nullptr || ownerWindow->GetHwnd() == nullptr) { return; } @@ -138,12 +140,12 @@ void EditorWindowManager::UpdateGlobalTabDragOwnerWindowPosition() { SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } -void EditorWindowManager::ClearGlobalTabDragDropPreview() { +void EditorWindowWorkspaceCoordinator::ClearGlobalTabDragDropPreview() { if (m_globalTabDragSession.previewWindowId.empty()) { return; } - if (EditorWindow* previewWindow = FindWindow(m_globalTabDragSession.previewWindowId); + if (EditorWindow* previewWindow = m_hostRuntime.FindWindow(m_globalTabDragSession.previewWindowId); previewWindow != nullptr) { previewWindow->ClearExternalDockHostDropPreview(); previewWindow->InvalidateHostWindow(); @@ -151,7 +153,7 @@ void EditorWindowManager::ClearGlobalTabDragDropPreview() { m_globalTabDragSession.previewWindowId.clear(); } -void EditorWindowManager::UpdateGlobalTabDragDropPreview() { +void EditorWindowWorkspaceCoordinator::UpdateGlobalTabDragDropPreview() { if (!m_globalTabDragSession.active) { return; } @@ -192,14 +194,14 @@ void EditorWindowManager::UpdateGlobalTabDragDropPreview() { m_globalTabDragSession.previewWindowId = std::string(targetWindow->GetWindowId()); } -void EditorWindowManager::EndGlobalTabDragSession() { +void EditorWindowWorkspaceCoordinator::EndGlobalTabDragSession() { if (!m_globalTabDragSession.active) { return; } ClearGlobalTabDragDropPreview(); - if (EditorWindow* ownerWindow = FindWindow(m_globalTabDragSession.panelWindowId); + if (EditorWindow* ownerWindow = m_hostRuntime.FindWindow(m_globalTabDragSession.panelWindowId); ownerWindow != nullptr) { if (GetCapture() == ownerWindow->GetHwnd()) { ReleaseCapture(); @@ -210,12 +212,12 @@ void EditorWindowManager::EndGlobalTabDragSession() { m_globalTabDragSession = {}; } -bool EditorWindowManager::HandleGlobalTabDragPointerMove(HWND hwnd) { +bool EditorWindowWorkspaceCoordinator::HandleGlobalTabDragPointerMove(HWND hwnd) { if (!m_globalTabDragSession.active) { return false; } - const EditorWindow* ownerWindow = FindWindow(m_globalTabDragSession.panelWindowId); + const EditorWindow* ownerWindow = m_hostRuntime.FindWindow(m_globalTabDragSession.panelWindowId); if (ownerWindow == nullptr || ownerWindow->GetHwnd() != hwnd) { return false; } @@ -229,35 +231,15 @@ bool EditorWindowManager::HandleGlobalTabDragPointerMove(HWND hwnd) { return true; } -void EditorWindowManager::ProcessPendingGlobalTabDragStarts() { - if (m_globalTabDragSession.active) { - return; - } - - for (const std::unique_ptr& window : m_windows) { - if (window == nullptr || window->GetHwnd() == nullptr) { - continue; - } - - if (TryStartGlobalTabDrag(*window)) { - return; - } - } -} - -bool EditorWindowManager::TryStartGlobalTabDrag(EditorWindow& sourceWindow) { - const std::optional pending = - sourceWindow.ConsumePendingTabDragStart(); - if (!pending.has_value()) { - return false; - } - +bool EditorWindowWorkspaceCoordinator::TryStartGlobalTabDrag( + EditorWindow& sourceWindow, + const EditorWindowPanelTransferRequest& request) { POINT dragHotspot = BuildFallbackGlobalTabDragHotspot(); TryResolveGlobalTabDragHotspot( sourceWindow, - pending->nodeId, - pending->panelId, - pending->screenPoint, + request.nodeId, + request.panelId, + request.screenPoint, dragHotspot); if (sourceWindow.IsPrimary()) { @@ -266,8 +248,8 @@ bool EditorWindowManager::TryStartGlobalTabDrag(EditorWindow& sourceWindow) { const UIEditorWindowWorkspaceOperationResult result = windowWorkspaceController.DetachPanelToNewWindow( sourceWindow.GetWindowId(), - pending->nodeId, - pending->panelId); + request.nodeId, + request.panelId); if (result.status != UIEditorWindowWorkspaceOperationStatus::Changed) { LogRuntimeTrace( "drag", @@ -278,12 +260,12 @@ bool EditorWindowManager::TryStartGlobalTabDrag(EditorWindow& sourceWindow) { if (!SynchronizeWindowsFromController( windowWorkspaceController, result.targetWindowId, - pending->screenPoint)) { + request.screenPoint)) { LogRuntimeTrace("drag", "failed to synchronize detached drag window state"); return false; } - EditorWindow* detachedWindow = FindWindow(result.targetWindowId); + EditorWindow* detachedWindow = m_hostRuntime.FindWindow(result.targetWindowId); if (detachedWindow == nullptr || detachedWindow->GetHwnd() == nullptr) { LogRuntimeTrace("drag", "detached drag window was not created."); return false; @@ -292,15 +274,15 @@ bool EditorWindowManager::TryStartGlobalTabDrag(EditorWindow& sourceWindow) { BeginGlobalTabDragSession( detachedWindow->GetWindowId(), detachedWindow->GetWorkspaceController().GetWorkspace().root.nodeId, - pending->panelId, - pending->screenPoint, + request.panelId, + request.screenPoint, dragHotspot); UpdateGlobalTabDragOwnerWindowPosition(); SetCapture(detachedWindow->GetHwnd()); SetForegroundWindow(detachedWindow->GetHwnd()); LogRuntimeTrace( "drag", - "started global tab drag by detaching panel '" + pending->panelId + + "started global tab drag by detaching panel '" + request.panelId + "' into window '" + std::string(detachedWindow->GetWindowId()) + "'"); return true; } @@ -308,9 +290,9 @@ bool EditorWindowManager::TryStartGlobalTabDrag(EditorWindow& sourceWindow) { sourceWindow.ResetInteractionState(); BeginGlobalTabDragSession( sourceWindow.GetWindowId(), - pending->nodeId, - pending->panelId, - pending->screenPoint, + request.nodeId, + request.panelId, + request.screenPoint, dragHotspot); UpdateGlobalTabDragOwnerWindowPosition(); if (sourceWindow.GetHwnd() != nullptr) { @@ -320,8 +302,55 @@ bool EditorWindowManager::TryStartGlobalTabDrag(EditorWindow& sourceWindow) { "drag", "started global tab drag from detached window '" + std::string(sourceWindow.GetWindowId()) + - "' panel '" + pending->panelId + "'"); + "' panel '" + request.panelId + "'"); return true; } -} // namespace XCEngine::UI::Editor::App +EditorWindow* EditorWindowWorkspaceCoordinator::FindTopmostWindowAtScreenPoint( + const POINT& screenPoint, + std::string_view excludedWindowId) { + if (const HWND hitWindow = WindowFromPoint(screenPoint); hitWindow != nullptr) { + const HWND rootWindow = GetAncestor(hitWindow, GA_ROOT); + if (EditorWindow* window = m_hostRuntime.FindWindow(rootWindow); + window != nullptr && + window->GetWindowId() != excludedWindowId) { + return window; + } + } + + for (auto it = m_hostRuntime.GetWindows().rbegin(); it != m_hostRuntime.GetWindows().rend(); ++it) { + EditorWindow* const window = it->get(); + if (window == nullptr || + window->GetHwnd() == nullptr || + window->GetWindowId() == excludedWindowId) { + continue; + } + + RECT windowRect = {}; + if (GetWindowRect(window->GetHwnd(), &windowRect) && + screenPoint.x >= windowRect.left && + screenPoint.x < windowRect.right && + screenPoint.y >= windowRect.top && + screenPoint.y < windowRect.bottom) { + return window; + } + } + + return nullptr; +} + +const EditorWindow* EditorWindowWorkspaceCoordinator::FindTopmostWindowAtScreenPoint( + const POINT& screenPoint, + std::string_view excludedWindowId) const { + return const_cast(this)->FindTopmostWindowAtScreenPoint( + screenPoint, + excludedWindowId); +} + +void EditorWindowWorkspaceCoordinator::LogRuntimeTrace( + std::string_view channel, + std::string_view message) const { + m_hostRuntime.LogRuntimeTrace(channel, message); +} + +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/WindowManager/WindowSet.cpp b/new_editor/app/Platform/Win32/WindowManager/WindowSet.cpp index df7008e9..151ce0cb 100644 --- a/new_editor/app/Platform/Win32/WindowManager/WindowSet.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/WindowSet.cpp @@ -1,19 +1,22 @@ -#include "Platform/Win32/EditorWindowManager.h" +#include "Platform/Win32/WindowManager/Internal.h" #include "State/EditorContext.h" #include "Platform/Win32/EditorWindow.h" -namespace XCEngine::UI::Editor::App { +#include +#include -UIEditorWindowWorkspaceSet EditorWindowManager::BuildWindowWorkspaceSet( +namespace XCEngine::UI::Editor::App::Internal { + +UIEditorWindowWorkspaceSet EditorWindowWorkspaceCoordinator::BuildWindowWorkspaceSet( std::string_view activeWindowId) const { UIEditorWindowWorkspaceSet windowSet = {}; - if (const EditorWindow* primaryWindow = FindPrimaryWindow(); + if (const EditorWindow* primaryWindow = m_hostRuntime.FindPrimaryWindow(); primaryWindow != nullptr) { windowSet.primaryWindowId = std::string(primaryWindow->GetWindowId()); } - for (const std::unique_ptr& window : m_windows) { + for (const std::unique_ptr& window : m_hostRuntime.GetWindows()) { if (window == nullptr || window->GetHwnd() == nullptr) { continue; } @@ -26,7 +29,7 @@ UIEditorWindowWorkspaceSet EditorWindowManager::BuildWindowWorkspaceSet( } windowSet.activeWindowId = - !activeWindowId.empty() && FindWindow(activeWindowId) != nullptr + !activeWindowId.empty() && m_hostRuntime.FindWindow(activeWindowId) != nullptr ? std::string(activeWindowId) : windowSet.primaryWindowId; @@ -34,19 +37,19 @@ UIEditorWindowWorkspaceSet EditorWindowManager::BuildWindowWorkspaceSet( } UIEditorWindowWorkspaceController -EditorWindowManager::BuildLiveWindowWorkspaceController( +EditorWindowWorkspaceCoordinator::BuildLiveWindowWorkspaceController( std::string_view activeWindowId) const { return UIEditorWindowWorkspaceController( - m_editorContext.GetShellAsset().panelRegistry, + m_hostRuntime.GetEditorContext().GetShellAsset().panelRegistry, BuildWindowWorkspaceSet(activeWindowId)); } -UIEditorWorkspaceController EditorWindowManager::BuildWorkspaceControllerForWindow( +UIEditorWorkspaceController EditorWindowWorkspaceCoordinator::BuildWorkspaceControllerForWindow( const UIEditorWindowWorkspaceState& windowState) const { return UIEditorWorkspaceController( - m_editorContext.GetShellAsset().panelRegistry, + m_hostRuntime.GetEditorContext().GetShellAsset().panelRegistry, windowState.workspace, windowState.session); } -} // namespace XCEngine::UI::Editor::App +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/WindowManager/WindowSync.cpp b/new_editor/app/Platform/Win32/WindowManager/WindowSync.cpp index 90a3bd77..e50c32e2 100644 --- a/new_editor/app/Platform/Win32/WindowManager/WindowSync.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/WindowSync.cpp @@ -1,13 +1,16 @@ -#include "Platform/Win32/EditorWindowManager.h" +#include "Platform/Win32/WindowManager/Internal.h" #include "State/EditorContext.h" #include "Platform/Win32/EditorWindow.h" +#include +#include + #include -namespace XCEngine::UI::Editor::App { +namespace XCEngine::UI::Editor::App::Internal { -std::wstring EditorWindowManager::BuildWindowTitle( +std::wstring EditorWindowWorkspaceCoordinator::BuildWindowTitle( const UIEditorWorkspaceController& workspaceController) const { const std::string& activePanelId = workspaceController.GetWorkspace().activePanelId; if (const UIEditorPanelDescriptor* descriptor = @@ -23,7 +26,7 @@ std::wstring EditorWindowManager::BuildWindowTitle( return std::wstring(L"XCEngine Editor"); } -RECT EditorWindowManager::BuildDetachedWindowRect(const POINT& screenPoint) const { +RECT EditorWindowWorkspaceCoordinator::BuildDetachedWindowRect(const POINT& screenPoint) const { RECT rect = { screenPoint.x - 420, screenPoint.y - 24, @@ -47,7 +50,7 @@ RECT EditorWindowManager::BuildDetachedWindowRect(const POINT& screenPoint) cons return rect; } -bool EditorWindowManager::SynchronizeWindowsFromWindowSet( +bool EditorWindowWorkspaceCoordinator::SynchronizeWindowsFromWindowSet( const UIEditorWindowWorkspaceSet& windowSet, std::string_view preferredNewWindowId, const POINT& preferredScreenPoint) { @@ -56,7 +59,7 @@ bool EditorWindowManager::SynchronizeWindowsFromWindowSet( for (const UIEditorWindowWorkspaceState& entry : windowSet.windows) { windowIdsInSet.push_back(entry.windowId); - if (EditorWindow* existingWindow = FindWindow(entry.windowId); + if (EditorWindow* existingWindow = m_hostRuntime.FindWindow(entry.windowId); existingWindow != nullptr) { existingWindow->ReplaceWorkspaceController(BuildWorkspaceControllerForWindow(entry)); existingWindow->ResetInteractionState(); @@ -70,15 +73,15 @@ bool EditorWindowManager::SynchronizeWindowsFromWindowSet( continue; } - CreateParams createParams = {}; + EditorWindowHostRuntime::CreateParams createParams = {}; createParams.windowId = entry.windowId; createParams.primary = entry.windowId == windowSet.primaryWindowId; createParams.title = createParams.primary ? std::wstring( - m_hostConfig.primaryWindowTitle != nullptr && - m_hostConfig.primaryWindowTitle[0] != L'\0' - ? m_hostConfig.primaryWindowTitle + m_hostRuntime.GetHostConfig().primaryWindowTitle != nullptr && + m_hostRuntime.GetHostConfig().primaryWindowTitle[0] != L'\0' + ? m_hostRuntime.GetHostConfig().primaryWindowTitle : L"XCEngine Editor") : BuildWindowTitle(BuildWorkspaceControllerForWindow(entry)); if (entry.windowId == preferredNewWindowId) { @@ -89,12 +92,12 @@ bool EditorWindowManager::SynchronizeWindowsFromWindowSet( createParams.initialHeight = detachedRect.bottom - detachedRect.top; } - if (CreateEditorWindow(BuildWorkspaceControllerForWindow(entry), createParams) == nullptr) { + if (m_hostRuntime.CreateEditorWindow(BuildWorkspaceControllerForWindow(entry), createParams) == nullptr) { return false; } } - for (const std::unique_ptr& window : m_windows) { + for (const std::unique_ptr& window : m_hostRuntime.GetWindows()) { if (window == nullptr || window->GetHwnd() == nullptr || window->IsPrimary()) { @@ -114,7 +117,7 @@ bool EditorWindowManager::SynchronizeWindowsFromWindowSet( return true; } -bool EditorWindowManager::SynchronizeWindowsFromController( +bool EditorWindowWorkspaceCoordinator::SynchronizeWindowsFromController( const UIEditorWindowWorkspaceController& windowWorkspaceController, std::string_view preferredNewWindowId, const POINT& preferredScreenPoint) { @@ -124,4 +127,4 @@ bool EditorWindowManager::SynchronizeWindowsFromController( preferredScreenPoint); } -} // namespace XCEngine::UI::Editor::App +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/WindowMessageDispatchHandlers.h b/new_editor/app/Platform/Win32/WindowMessageDispatchHandlers.h deleted file mode 100644 index dd42ede1..00000000 --- a/new_editor/app/Platform/Win32/WindowMessageDispatchHandlers.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include - -namespace XCEngine::UI::Editor { -namespace App { -class EditorWindow; -} -} - -namespace XCEngine::UI::Editor::Host { - -class WindowMessageHost; - -struct WindowMessageDispatchContext { - HWND hwnd = nullptr; - WindowMessageHost& windowHost; - App::EditorWindow& window; -}; - -void RenderAndValidateWindow(const WindowMessageDispatchContext& context); - -bool TryDispatchWindowChromeMessage( - const WindowMessageDispatchContext& context, - UINT message, - WPARAM wParam, - LPARAM lParam, - LRESULT& outResult); - -bool TryDispatchWindowLifecycleMessage( - const WindowMessageDispatchContext& context, - UINT message, - WPARAM wParam, - LPARAM lParam, - LRESULT& outResult); - -bool TryDispatchWindowInputMessage( - const WindowMessageDispatchContext& context, - UINT message, - WPARAM wParam, - LPARAM lParam, - LRESULT& outResult); - -bool TryDispatchWindowPointerMessage( - const WindowMessageDispatchContext& context, - UINT message, - WPARAM wParam, - LPARAM lParam, - LRESULT& outResult); - -} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Platform/Win32/WindowMessageDispatcher.cpp b/new_editor/app/Platform/Win32/WindowMessageDispatcher.cpp index 96580930..a308352f 100644 --- a/new_editor/app/Platform/Win32/WindowMessageDispatcher.cpp +++ b/new_editor/app/Platform/Win32/WindowMessageDispatcher.cpp @@ -1,9 +1,377 @@ #include "WindowMessageDispatcher.h" -#include "WindowMessageDispatchHandlers.h" +#include "Platform/Win32/EditorWindow.h" +#include "WindowMessageHost.h" + +#include namespace XCEngine::UI::Editor::Host { +namespace { + +constexpr UINT kMessageNcUaDrawCaption = 0x00AEu; +constexpr UINT kMessageNcUaDrawFrame = 0x00AFu; + +} // namespace + +struct WindowMessageDispatcher::DispatchContext { + HWND hwnd = nullptr; + WindowMessageHost& windowHost; + App::EditorWindow& window; +}; + +void WindowMessageDispatcher::RenderAndValidateWindow(const DispatchContext& context) { + if (!context.window.IsRenderReady()) { + return; + } + + App::EditorWindowFrameTransferRequests transferRequests = context.window.RenderFrame( + context.windowHost.GetEditorContext(), + context.windowHost.IsGlobalTabDragActive()); + if (transferRequests.HasPendingRequests()) { + context.windowHost.HandleWindowFrameTransferRequests( + context.window, + std::move(transferRequests)); + } + if (context.hwnd != nullptr && IsWindow(context.hwnd)) { + ValidateRect(context.hwnd, nullptr); + } +} + +bool WindowMessageDispatcher::EnsureTrackingMouseLeave(const DispatchContext& context) { + if (context.window.IsTrackingMouseLeave()) { + return true; + } + + TRACKMOUSEEVENT trackMouseEvent = {}; + trackMouseEvent.cbSize = sizeof(trackMouseEvent); + trackMouseEvent.dwFlags = TME_LEAVE; + trackMouseEvent.hwndTrack = context.hwnd; + if (!TrackMouseEvent(&trackMouseEvent)) { + return false; + } + + context.window.SetTrackingMouseLeave(true); + return true; +} + +bool WindowMessageDispatcher::TryHandleChromeHoverConsumption( + const DispatchContext& context, + LPARAM lParam, + LRESULT& outResult) { + const bool resizeHoverChanged = context.window.UpdateBorderlessWindowResizeHover(lParam); + if (context.window.UpdateBorderlessWindowChromeHover(lParam)) { + context.window.InvalidateHostWindow(); + } + if (resizeHoverChanged) { + context.window.InvalidateHostWindow(); + } + + EnsureTrackingMouseLeave(context); + if (context.window.HasHoveredBorderlessResizeEdge()) { + outResult = 0; + return true; + } + + const BorderlessWindowChromeHitTarget chromeHitTarget = + context.window.HitTestBorderlessWindowChrome(lParam); + if (chromeHitTarget == BorderlessWindowChromeHitTarget::MinimizeButton || + chromeHitTarget == BorderlessWindowChromeHitTarget::MaximizeRestoreButton || + chromeHitTarget == BorderlessWindowChromeHitTarget::CloseButton) { + outResult = 0; + return true; + } + + return false; +} + +bool WindowMessageDispatcher::TryDispatchWindowPointerMessage( + const DispatchContext& context, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult) { + switch (message) { + case WM_MOUSEMOVE: + if (context.windowHost.HandleGlobalTabDragPointerMove(context.hwnd)) { + outResult = 0; + return true; + } + if (context.window.HandleBorderlessWindowResizePointerMove( + context.windowHost.GetEditorContext(), + context.windowHost.IsGlobalTabDragActive())) { + outResult = 0; + return true; + } + if (context.window.HandleBorderlessWindowChromeDragRestorePointerMove( + context.windowHost.GetEditorContext(), + context.windowHost.IsGlobalTabDragActive())) { + outResult = 0; + return true; + } + if (TryHandleChromeHoverConsumption(context, lParam, outResult)) { + return true; + } + + context.window.QueuePointerEvent( + ::XCEngine::UI::UIInputEventType::PointerMove, + ::XCEngine::UI::UIPointerButton::None, + wParam, + lParam); + outResult = 0; + return true; + case WM_MOUSELEAVE: + context.window.SetTrackingMouseLeave(false); + context.window.ClearBorderlessWindowResizeState(); + context.window.ClearBorderlessWindowChromeDragRestoreState(); + context.window.ClearBorderlessWindowChromeState(); + context.window.QueuePointerLeaveEvent(); + outResult = 0; + return true; + case WM_LBUTTONDOWN: + if (context.window.HandleBorderlessWindowResizeButtonDown(lParam)) { + outResult = 0; + return true; + } + if (context.window.HandleBorderlessWindowChromeButtonDown(lParam)) { + outResult = 0; + return true; + } + SetFocus(context.hwnd); + context.window.QueuePointerEvent( + ::XCEngine::UI::UIInputEventType::PointerButtonDown, + ::XCEngine::UI::UIPointerButton::Left, + wParam, + lParam); + outResult = 0; + return true; + case WM_LBUTTONUP: + if (context.windowHost.HandleGlobalTabDragPointerButtonUp(context.hwnd)) { + outResult = 0; + return true; + } + if (context.window.HandleBorderlessWindowResizeButtonUp()) { + outResult = 0; + return true; + } + if (context.window.HandleBorderlessWindowChromeButtonUp( + context.windowHost.GetEditorContext(), + context.windowHost.IsGlobalTabDragActive(), + lParam)) { + outResult = 0; + return true; + } + context.window.QueuePointerEvent( + ::XCEngine::UI::UIInputEventType::PointerButtonUp, + ::XCEngine::UI::UIPointerButton::Left, + wParam, + lParam); + outResult = 0; + return true; + case WM_LBUTTONDBLCLK: + if (context.window.HandleBorderlessWindowChromeDoubleClick( + context.windowHost.GetEditorContext(), + context.windowHost.IsGlobalTabDragActive(), + lParam)) { + outResult = 0; + return true; + } + return false; + case WM_MOUSEWHEEL: + context.window.QueuePointerWheelEvent(GET_WHEEL_DELTA_WPARAM(wParam), wParam, lParam); + outResult = 0; + return true; + default: + return false; + } +} + +bool WindowMessageDispatcher::TryDispatchWindowInputMessage( + const DispatchContext& context, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult) { + if (TryDispatchWindowPointerMessage(context, message, wParam, lParam, outResult)) { + return true; + } + + switch (message) { + case WM_SETFOCUS: + context.window.SyncInputModifiersFromSystemState(); + context.window.QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType::FocusGained); + outResult = 0; + return true; + case WM_KILLFOCUS: + context.window.ResetInputModifiers(); + context.window.QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType::FocusLost); + outResult = 0; + return true; + case WM_CAPTURECHANGED: + if (context.windowHost.OwnsActiveGlobalTabDrag(context.window.GetWindowId()) && + reinterpret_cast(lParam) != context.hwnd) { + context.windowHost.EndGlobalTabDragSession(); + outResult = 0; + return true; + } + if (reinterpret_cast(lParam) != context.hwnd && + context.window.HasInteractiveCaptureState()) { + context.window.QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType::FocusLost); + context.window.ForceClearBorderlessWindowResizeState(); + context.window.ClearBorderlessWindowChromeDragRestoreState(); + context.window.ClearBorderlessWindowChromeState(); + outResult = 0; + return true; + } + if (reinterpret_cast(lParam) != context.hwnd) { + context.window.ForceClearBorderlessWindowResizeState(); + context.window.ClearBorderlessWindowChromeDragRestoreState(); + context.window.ClearBorderlessWindowChromeState(); + } + return false; + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + if (wParam == VK_F12) { + context.window.RequestManualScreenshot(); + } + context.window.QueueKeyEvent(::XCEngine::UI::UIInputEventType::KeyDown, wParam, lParam); + outResult = 0; + return true; + case WM_KEYUP: + case WM_SYSKEYUP: + context.window.QueueKeyEvent(::XCEngine::UI::UIInputEventType::KeyUp, wParam, lParam); + outResult = 0; + return true; + case WM_CHAR: + context.window.QueueCharacterEvent(wParam, lParam); + outResult = 0; + return true; + default: + return false; + } +} + +bool WindowMessageDispatcher::TryDispatchWindowLifecycleMessage( + const DispatchContext& context, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult) { + switch (message) { + case WM_DPICHANGED: + if (lParam == 0) { + return false; + } + context.window.OnDpiChanged( + static_cast(LOWORD(wParam)), + *reinterpret_cast(lParam)); + RenderAndValidateWindow(context); + outResult = 0; + return true; + case WM_ENTERSIZEMOVE: + context.window.OnEnterSizeMove(); + outResult = 0; + return true; + case WM_EXITSIZEMOVE: + context.window.OnExitSizeMove(); + RenderAndValidateWindow(context); + outResult = 0; + return true; + case WM_SIZE: + if (wParam != SIZE_MINIMIZED) { + context.window.OnResize( + static_cast(LOWORD(lParam)), + static_cast(HIWORD(lParam))); + RenderAndValidateWindow(context); + } + outResult = 0; + return true; + case WM_PAINT: + if (App::EditorWindowFrameTransferRequests transferRequests = context.window.OnPaintMessage( + context.windowHost.GetEditorContext(), + context.windowHost.IsGlobalTabDragActive()); + transferRequests.HasPendingRequests()) { + context.windowHost.HandleWindowFrameTransferRequests( + context.window, + std::move(transferRequests)); + } + outResult = 0; + return true; + case WM_ERASEBKGND: + outResult = 1; + return true; + case WM_DESTROY: + if (context.windowHost.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) { + context.windowHost.EndGlobalTabDragSession(); + } + context.windowHost.HandleDestroyedWindow(context.hwnd); + outResult = 0; + return true; + default: + return false; + } +} + +bool WindowMessageDispatcher::TryDispatchWindowChromeMessage( + const DispatchContext& context, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult) { + switch (message) { + case WM_GETMINMAXINFO: + if (context.window.IsBorderlessWindowEnabled() && + context.window.HandleBorderlessWindowGetMinMaxInfo(lParam)) { + outResult = 0; + return true; + } + return false; + case WM_NCCALCSIZE: + if (context.window.IsBorderlessWindowEnabled()) { + outResult = context.window.HandleBorderlessWindowNcCalcSize(wParam, lParam); + return true; + } + return false; + case WM_NCACTIVATE: + if (context.window.IsBorderlessWindowEnabled()) { + outResult = TRUE; + return true; + } + return false; + case WM_NCHITTEST: + if (context.window.IsBorderlessWindowEnabled()) { + outResult = HTCLIENT; + return true; + } + return false; + case WM_NCPAINT: + case kMessageNcUaDrawCaption: + case kMessageNcUaDrawFrame: + if (context.window.IsBorderlessWindowEnabled()) { + outResult = 0; + return true; + } + return false; + case WM_SYSCOMMAND: + if (context.window.HandleBorderlessWindowSystemCommand( + context.windowHost.GetEditorContext(), + context.windowHost.IsGlobalTabDragActive(), + wParam)) { + outResult = 0; + return true; + } + return false; + case WM_SETCURSOR: + if (LOWORD(lParam) == HTCLIENT && context.window.ApplyCurrentCursor()) { + outResult = TRUE; + return true; + } + return false; + default: + return false; + } +} + bool WindowMessageDispatcher::TryDispatch( HWND hwnd, WindowMessageHost& windowHost, @@ -11,8 +379,8 @@ bool WindowMessageDispatcher::TryDispatch( UINT message, WPARAM wParam, LPARAM lParam, - LRESULT& outResult) { - const WindowMessageDispatchContext context = { + LRESULT& outResult) { + const DispatchContext context = { .hwnd = hwnd, .windowHost = windowHost, .window = window, diff --git a/new_editor/app/Platform/Win32/WindowMessageDispatcher.h b/new_editor/app/Platform/Win32/WindowMessageDispatcher.h index a40dd0ec..af8372dd 100644 --- a/new_editor/app/Platform/Win32/WindowMessageDispatcher.h +++ b/new_editor/app/Platform/Win32/WindowMessageDispatcher.h @@ -26,6 +26,40 @@ public: WPARAM wParam, LPARAM lParam, LRESULT& outResult); + +private: + struct DispatchContext; + + static void RenderAndValidateWindow(const DispatchContext& context); + static bool EnsureTrackingMouseLeave(const DispatchContext& context); + static bool TryHandleChromeHoverConsumption( + const DispatchContext& context, + LPARAM lParam, + LRESULT& outResult); + static bool TryDispatchWindowPointerMessage( + const DispatchContext& context, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult); + static bool TryDispatchWindowInputMessage( + const DispatchContext& context, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult); + static bool TryDispatchWindowLifecycleMessage( + const DispatchContext& context, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult); + static bool TryDispatchWindowChromeMessage( + const DispatchContext& context, + UINT message, + WPARAM wParam, + LPARAM lParam, + LRESULT& outResult); }; } // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Platform/Win32/WindowMessageDispatcherChrome.cpp b/new_editor/app/Platform/Win32/WindowMessageDispatcherChrome.cpp deleted file mode 100644 index 1ac7a937..00000000 --- a/new_editor/app/Platform/Win32/WindowMessageDispatcherChrome.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "WindowMessageDispatchHandlers.h" - -#include "Platform/Win32/EditorWindow.h" -#include "WindowMessageHost.h" - -namespace XCEngine::UI::Editor::Host { - -namespace { - -constexpr UINT kMessageNcUaDrawCaption = 0x00AEu; -constexpr UINT kMessageNcUaDrawFrame = 0x00AFu; - -} // namespace - -bool TryDispatchWindowChromeMessage( - const WindowMessageDispatchContext& context, - UINT message, - WPARAM wParam, - LPARAM lParam, - LRESULT& outResult) { - switch (message) { - case WM_GETMINMAXINFO: - if (context.window.IsBorderlessWindowEnabled() && - context.window.HandleBorderlessWindowGetMinMaxInfo(lParam)) { - outResult = 0; - return true; - } - return false; - case WM_NCCALCSIZE: - if (context.window.IsBorderlessWindowEnabled()) { - outResult = context.window.HandleBorderlessWindowNcCalcSize(wParam, lParam); - return true; - } - return false; - case WM_NCACTIVATE: - if (context.window.IsBorderlessWindowEnabled()) { - outResult = TRUE; - return true; - } - return false; - case WM_NCHITTEST: - if (context.window.IsBorderlessWindowEnabled()) { - outResult = HTCLIENT; - return true; - } - return false; - case WM_NCPAINT: - case kMessageNcUaDrawCaption: - case kMessageNcUaDrawFrame: - if (context.window.IsBorderlessWindowEnabled()) { - outResult = 0; - return true; - } - return false; - case WM_SYSCOMMAND: - if (context.window.HandleBorderlessWindowSystemCommand( - context.windowHost.GetEditorContext(), - context.windowHost.IsGlobalTabDragActive(), - wParam)) { - outResult = 0; - return true; - } - return false; - case WM_SETCURSOR: - if (LOWORD(lParam) == HTCLIENT && context.window.ApplyCurrentCursor()) { - outResult = TRUE; - return true; - } - return false; - default: - return false; - } -} - -} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Platform/Win32/WindowMessageDispatcherInput.cpp b/new_editor/app/Platform/Win32/WindowMessageDispatcherInput.cpp deleted file mode 100644 index 30a36887..00000000 --- a/new_editor/app/Platform/Win32/WindowMessageDispatcherInput.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include "WindowMessageDispatchHandlers.h" - -#include "Platform/Win32/EditorWindow.h" -#include "WindowMessageHost.h" - -namespace XCEngine::UI::Editor::Host { - -bool TryDispatchWindowInputMessage( - const WindowMessageDispatchContext& context, - UINT message, - WPARAM wParam, - LPARAM lParam, - LRESULT& outResult) { - if (TryDispatchWindowPointerMessage(context, message, wParam, lParam, outResult)) { - return true; - } - - switch (message) { - case WM_SETFOCUS: - context.window.SyncInputModifiersFromSystemState(); - context.window.QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType::FocusGained); - outResult = 0; - return true; - case WM_KILLFOCUS: - context.window.ResetInputModifiers(); - context.window.QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType::FocusLost); - outResult = 0; - return true; - case WM_CAPTURECHANGED: - if (context.windowHost.OwnsActiveGlobalTabDrag(context.window.GetWindowId()) && - reinterpret_cast(lParam) != context.hwnd) { - context.windowHost.EndGlobalTabDragSession(); - outResult = 0; - return true; - } - if (reinterpret_cast(lParam) != context.hwnd && - context.window.HasInteractiveCaptureState()) { - context.window.QueueWindowFocusEvent(::XCEngine::UI::UIInputEventType::FocusLost); - context.window.ForceClearBorderlessWindowResizeState(); - context.window.ClearBorderlessWindowChromeDragRestoreState(); - context.window.ClearBorderlessWindowChromeState(); - outResult = 0; - return true; - } - if (reinterpret_cast(lParam) != context.hwnd) { - context.window.ForceClearBorderlessWindowResizeState(); - context.window.ClearBorderlessWindowChromeDragRestoreState(); - context.window.ClearBorderlessWindowChromeState(); - } - return false; - case WM_KEYDOWN: - case WM_SYSKEYDOWN: - if (wParam == VK_F12) { - context.window.RequestManualScreenshot(); - } - context.window.QueueKeyEvent(::XCEngine::UI::UIInputEventType::KeyDown, wParam, lParam); - outResult = 0; - return true; - case WM_KEYUP: - case WM_SYSKEYUP: - context.window.QueueKeyEvent(::XCEngine::UI::UIInputEventType::KeyUp, wParam, lParam); - outResult = 0; - return true; - case WM_CHAR: - context.window.QueueCharacterEvent(wParam, lParam); - outResult = 0; - return true; - default: - return false; - } -} - -} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Platform/Win32/WindowMessageDispatcherLifecycle.cpp b/new_editor/app/Platform/Win32/WindowMessageDispatcherLifecycle.cpp deleted file mode 100644 index 43ed68f4..00000000 --- a/new_editor/app/Platform/Win32/WindowMessageDispatcherLifecycle.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "WindowMessageDispatchHandlers.h" - -#include "Platform/Win32/EditorWindow.h" -#include "WindowMessageHost.h" - -namespace XCEngine::UI::Editor::Host { - -void RenderAndValidateWindow(const WindowMessageDispatchContext& context) { - if (!context.window.IsRenderReady()) { - return; - } - - context.window.RenderFrame( - context.windowHost.GetEditorContext(), - context.windowHost.IsGlobalTabDragActive()); - if (context.hwnd != nullptr && IsWindow(context.hwnd)) { - ValidateRect(context.hwnd, nullptr); - } -} - -bool TryDispatchWindowLifecycleMessage( - const WindowMessageDispatchContext& context, - UINT message, - WPARAM wParam, - LPARAM lParam, - LRESULT& outResult) { - switch (message) { - case WM_DPICHANGED: - if (lParam == 0) { - return false; - } - context.window.OnDpiChanged( - static_cast(LOWORD(wParam)), - *reinterpret_cast(lParam)); - RenderAndValidateWindow(context); - outResult = 0; - return true; - case WM_ENTERSIZEMOVE: - context.window.OnEnterSizeMove(); - outResult = 0; - return true; - case WM_EXITSIZEMOVE: - context.window.OnExitSizeMove(); - RenderAndValidateWindow(context); - outResult = 0; - return true; - case WM_SIZE: - if (wParam != SIZE_MINIMIZED) { - context.window.OnResize( - static_cast(LOWORD(lParam)), - static_cast(HIWORD(lParam))); - RenderAndValidateWindow(context); - } - outResult = 0; - return true; - case WM_PAINT: - context.window.OnPaintMessage( - context.windowHost.GetEditorContext(), - context.windowHost.IsGlobalTabDragActive()); - outResult = 0; - return true; - case WM_ERASEBKGND: - outResult = 1; - return true; - case WM_DESTROY: - if (context.windowHost.OwnsActiveGlobalTabDrag(context.window.GetWindowId())) { - context.windowHost.EndGlobalTabDragSession(); - } - context.windowHost.HandleDestroyedWindow(context.hwnd); - outResult = 0; - return true; - default: - return false; - } -} - -} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Platform/Win32/WindowMessageDispatcherPointer.cpp b/new_editor/app/Platform/Win32/WindowMessageDispatcherPointer.cpp deleted file mode 100644 index ca4a6be7..00000000 --- a/new_editor/app/Platform/Win32/WindowMessageDispatcherPointer.cpp +++ /dev/null @@ -1,160 +0,0 @@ -#include "WindowMessageDispatchHandlers.h" - -#include "Platform/Win32/EditorWindow.h" -#include "WindowMessageHost.h" - -namespace XCEngine::UI::Editor::Host { - -namespace { - -bool EnsureTrackingMouseLeave(const WindowMessageDispatchContext& context) { - if (context.window.IsTrackingMouseLeave()) { - return true; - } - - TRACKMOUSEEVENT trackMouseEvent = {}; - trackMouseEvent.cbSize = sizeof(trackMouseEvent); - trackMouseEvent.dwFlags = TME_LEAVE; - trackMouseEvent.hwndTrack = context.hwnd; - if (!TrackMouseEvent(&trackMouseEvent)) { - return false; - } - - context.window.SetTrackingMouseLeave(true); - return true; -} - -bool TryHandleChromeHoverConsumption( - const WindowMessageDispatchContext& context, - LPARAM lParam, - LRESULT& outResult) { - const bool resizeHoverChanged = context.window.UpdateBorderlessWindowResizeHover(lParam); - if (context.window.UpdateBorderlessWindowChromeHover(lParam)) { - context.window.InvalidateHostWindow(); - } - if (resizeHoverChanged) { - context.window.InvalidateHostWindow(); - } - - EnsureTrackingMouseLeave(context); - if (context.window.HasHoveredBorderlessResizeEdge()) { - outResult = 0; - return true; - } - - const BorderlessWindowChromeHitTarget chromeHitTarget = - context.window.HitTestBorderlessWindowChrome(lParam); - if (chromeHitTarget == BorderlessWindowChromeHitTarget::MinimizeButton || - chromeHitTarget == BorderlessWindowChromeHitTarget::MaximizeRestoreButton || - chromeHitTarget == BorderlessWindowChromeHitTarget::CloseButton) { - outResult = 0; - return true; - } - - return false; -} - -} // namespace - -bool TryDispatchWindowPointerMessage( - const WindowMessageDispatchContext& context, - UINT message, - WPARAM wParam, - LPARAM lParam, - LRESULT& outResult) { - switch (message) { - case WM_MOUSEMOVE: - if (context.windowHost.HandleGlobalTabDragPointerMove(context.hwnd)) { - outResult = 0; - return true; - } - if (context.window.HandleBorderlessWindowResizePointerMove( - context.windowHost.GetEditorContext(), - context.windowHost.IsGlobalTabDragActive())) { - outResult = 0; - return true; - } - if (context.window.HandleBorderlessWindowChromeDragRestorePointerMove( - context.windowHost.GetEditorContext(), - context.windowHost.IsGlobalTabDragActive())) { - outResult = 0; - return true; - } - if (TryHandleChromeHoverConsumption(context, lParam, outResult)) { - return true; - } - - context.window.QueuePointerEvent( - ::XCEngine::UI::UIInputEventType::PointerMove, - ::XCEngine::UI::UIPointerButton::None, - wParam, - lParam); - outResult = 0; - return true; - case WM_MOUSELEAVE: - context.window.SetTrackingMouseLeave(false); - context.window.ClearBorderlessWindowResizeState(); - context.window.ClearBorderlessWindowChromeDragRestoreState(); - context.window.ClearBorderlessWindowChromeState(); - context.window.QueuePointerLeaveEvent(); - outResult = 0; - return true; - case WM_LBUTTONDOWN: - if (context.window.HandleBorderlessWindowResizeButtonDown(lParam)) { - outResult = 0; - return true; - } - if (context.window.HandleBorderlessWindowChromeButtonDown(lParam)) { - outResult = 0; - return true; - } - SetFocus(context.hwnd); - context.window.QueuePointerEvent( - ::XCEngine::UI::UIInputEventType::PointerButtonDown, - ::XCEngine::UI::UIPointerButton::Left, - wParam, - lParam); - outResult = 0; - return true; - case WM_LBUTTONUP: - if (context.windowHost.HandleGlobalTabDragPointerButtonUp(context.hwnd)) { - outResult = 0; - return true; - } - if (context.window.HandleBorderlessWindowResizeButtonUp()) { - outResult = 0; - return true; - } - if (context.window.HandleBorderlessWindowChromeButtonUp( - context.windowHost.GetEditorContext(), - context.windowHost.IsGlobalTabDragActive(), - lParam)) { - outResult = 0; - return true; - } - context.window.QueuePointerEvent( - ::XCEngine::UI::UIInputEventType::PointerButtonUp, - ::XCEngine::UI::UIPointerButton::Left, - wParam, - lParam); - outResult = 0; - return true; - case WM_LBUTTONDBLCLK: - if (context.window.HandleBorderlessWindowChromeDoubleClick( - context.windowHost.GetEditorContext(), - context.windowHost.IsGlobalTabDragActive(), - lParam)) { - outResult = 0; - return true; - } - return false; - case WM_MOUSEWHEEL: - context.window.QueuePointerWheelEvent(GET_WHEEL_DELTA_WPARAM(wParam), wParam, lParam); - outResult = 0; - return true; - default: - return false; - } -} - -} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Platform/Win32/WindowMessageHost.h b/new_editor/app/Platform/Win32/WindowMessageHost.h index 403c81a9..7b2e432d 100644 --- a/new_editor/app/Platform/Win32/WindowMessageHost.h +++ b/new_editor/app/Platform/Win32/WindowMessageHost.h @@ -10,6 +10,8 @@ namespace XCEngine::UI::Editor::App { class EditorContext; +class EditorWindow; +struct EditorWindowFrameTransferRequests; } namespace XCEngine::UI::Editor::Host { @@ -24,6 +26,9 @@ public: virtual bool OwnsActiveGlobalTabDrag(std::string_view windowId) const = 0; virtual void EndGlobalTabDragSession() = 0; virtual void HandleDestroyedWindow(HWND hwnd) = 0; + virtual void HandleWindowFrameTransferRequests( + App::EditorWindow& window, + App::EditorWindowFrameTransferRequests&& transferRequests) = 0; virtual bool HandleGlobalTabDragPointerMove(HWND hwnd) = 0; virtual bool HandleGlobalTabDragPointerButtonUp(HWND hwnd) = 0; }; diff --git a/new_editor/app/Rendering/D3D12/D3D12HostDevice.cpp b/new_editor/app/Rendering/D3D12/D3D12HostDevice.cpp deleted file mode 100644 index 6fd8d28e..00000000 --- a/new_editor/app/Rendering/D3D12/D3D12HostDevice.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "D3D12HostDevice.h" - -namespace XCEngine::UI::Editor::Host { - -// D3D12HostDevice implementations are split into lifecycle, frame, and fence -// translation units to keep device-host responsibilities isolated. - -} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp index 2cb12460..02f9f4d4 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp +++ b/new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp @@ -1,8 +1,8 @@ -#include "D3D12WindowInteropSupport.h" +#include "D3D12WindowInteropInternal.h" #include -namespace XCEngine::UI::Editor::Host::InteropSupport { +namespace XCEngine::UI::Editor::Host::D3D12WindowInteropInternal { std::string HrToInteropString(const char* operation, HRESULT hr) { char buffer[128] = {}; @@ -42,7 +42,7 @@ void CollectInteropTextureHandles( } } -} // namespace XCEngine::UI::Editor::Host::InteropSupport +} // namespace XCEngine::UI::Editor::Host::D3D12WindowInteropInternal namespace XCEngine::UI::Editor::Host { diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowInteropDevice.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowInteropDevice.cpp index 7c92ee01..e43c7ea2 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowInteropDevice.cpp +++ b/new_editor/app/Rendering/D3D12/D3D12WindowInteropDevice.cpp @@ -1,10 +1,10 @@ -#include "D3D12WindowInteropSupport.h" +#include "D3D12WindowInteropInternal.h" #include namespace XCEngine::UI::Editor::Host { -using namespace InteropSupport; +using namespace D3D12WindowInteropInternal; bool D3D12WindowInteropContext::EnsureInterop() { if (m_windowRenderer == nullptr) { diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowInteropSupport.h b/new_editor/app/Rendering/D3D12/D3D12WindowInteropInternal.h similarity index 76% rename from new_editor/app/Rendering/D3D12/D3D12WindowInteropSupport.h rename to new_editor/app/Rendering/D3D12/D3D12WindowInteropInternal.h index 69422600..c8326964 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowInteropSupport.h +++ b/new_editor/app/Rendering/D3D12/D3D12WindowInteropInternal.h @@ -2,7 +2,7 @@ #include "D3D12WindowInteropContext.h" -namespace XCEngine::UI::Editor::Host::InteropSupport { +namespace XCEngine::UI::Editor::Host::D3D12WindowInteropInternal { std::string HrToInteropString(const char* operation, HRESULT hr); @@ -16,4 +16,4 @@ void CollectInteropTextureHandles( const ::XCEngine::UI::UIDrawData& drawData, std::vector<::XCEngine::UI::UITextureHandle>& outTextures); -} // namespace XCEngine::UI::Editor::Host::InteropSupport +} // namespace XCEngine::UI::Editor::Host::D3D12WindowInteropInternal diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowInteropResources.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowInteropResources.cpp index a6bec2ad..2080d895 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowInteropResources.cpp +++ b/new_editor/app/Rendering/D3D12/D3D12WindowInteropResources.cpp @@ -1,8 +1,8 @@ -#include "D3D12WindowInteropSupport.h" +#include "D3D12WindowInteropInternal.h" namespace XCEngine::UI::Editor::Host { -using namespace InteropSupport; +using namespace D3D12WindowInteropInternal; void D3D12WindowInteropContext::ReleaseBackBufferTargets() { ClearSourceTextures(); diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowInteropSourceTextures.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowInteropSourceTextures.cpp index 62ddc135..e7eda6d6 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowInteropSourceTextures.cpp +++ b/new_editor/app/Rendering/D3D12/D3D12WindowInteropSourceTextures.cpp @@ -1,11 +1,11 @@ -#include "D3D12WindowInteropSupport.h" +#include "D3D12WindowInteropInternal.h" #include #include namespace XCEngine::UI::Editor::Host { -using namespace InteropSupport; +using namespace D3D12WindowInteropInternal; bool D3D12WindowInteropContext::PrepareSourceTextures( const ::XCEngine::UI::UIDrawData& drawData) { diff --git a/new_editor/app/Rendering/D3D12/SwapChainPresenter/Presenter.cpp b/new_editor/app/Rendering/D3D12/SwapChainPresenter/Presenter.cpp deleted file mode 100644 index 0bc1dd0d..00000000 --- a/new_editor/app/Rendering/D3D12/SwapChainPresenter/Presenter.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "Rendering/D3D12/D3D12WindowSwapChainPresenter.h" - -namespace XCEngine::UI::Editor::Host { - -// D3D12WindowSwapChainPresenter implementations are split by responsibility -// into lifecycle, resize, back-buffer, and presentation translation units. - -} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/Native/NativeRenderer.cpp b/new_editor/app/Rendering/Native/NativeRenderer.cpp deleted file mode 100644 index 8f5e71a2..00000000 --- a/new_editor/app/Rendering/Native/NativeRenderer.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "NativeRendererSupport.h" - -namespace XCEngine::UI::Editor::Host { - -// NativeRenderer implementations are split by responsibility into dedicated -// translation units to keep lifecycle, texture, text, capture, and rendering -// codepaths isolated. - -} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/Native/NativeRendererCapture.cpp b/new_editor/app/Rendering/Native/NativeRendererCapture.cpp index 6994bf7e..de17d980 100644 --- a/new_editor/app/Rendering/Native/NativeRendererCapture.cpp +++ b/new_editor/app/Rendering/Native/NativeRendererCapture.cpp @@ -1,8 +1,8 @@ -#include "NativeRendererSupport.h" +#include "NativeRendererInternal.h" namespace XCEngine::UI::Editor::Host { -using namespace NativeRendererSupport; +using namespace NativeRendererInternal; bool NativeRenderer::CaptureToPng( const ::XCEngine::UI::UIDrawData& drawData, diff --git a/new_editor/app/Rendering/Native/NativeRendererDraw.cpp b/new_editor/app/Rendering/Native/NativeRendererDraw.cpp index cb2cea28..35d79051 100644 --- a/new_editor/app/Rendering/Native/NativeRendererDraw.cpp +++ b/new_editor/app/Rendering/Native/NativeRendererDraw.cpp @@ -1,8 +1,8 @@ -#include "NativeRendererSupport.h" +#include "NativeRendererInternal.h" namespace XCEngine::UI::Editor::Host { -using namespace NativeRendererSupport; +using namespace NativeRendererInternal; bool NativeRenderer::RenderToTarget( ID2D1RenderTarget& renderTarget, diff --git a/new_editor/app/Rendering/Native/NativeRendererDrawContent.cpp b/new_editor/app/Rendering/Native/NativeRendererDrawContent.cpp index 3a4cee54..92165e45 100644 --- a/new_editor/app/Rendering/Native/NativeRendererDrawContent.cpp +++ b/new_editor/app/Rendering/Native/NativeRendererDrawContent.cpp @@ -1,11 +1,11 @@ -#include "NativeRendererSupport.h" +#include "NativeRendererInternal.h" #include #include namespace XCEngine::UI::Editor::Host { -using namespace NativeRendererSupport; +using namespace NativeRendererInternal; void NativeRenderer::RenderTextCommand( ID2D1RenderTarget& renderTarget, diff --git a/new_editor/app/Rendering/Native/NativeRendererDrawGeometry.cpp b/new_editor/app/Rendering/Native/NativeRendererDrawGeometry.cpp index 4acb5bf7..ea991eea 100644 --- a/new_editor/app/Rendering/Native/NativeRendererDrawGeometry.cpp +++ b/new_editor/app/Rendering/Native/NativeRendererDrawGeometry.cpp @@ -1,8 +1,8 @@ -#include "NativeRendererSupport.h" +#include "NativeRendererInternal.h" namespace XCEngine::UI::Editor::Host { -using namespace NativeRendererSupport; +using namespace NativeRendererInternal; void NativeRenderer::RenderFilledRectCommand( ID2D1RenderTarget& renderTarget, diff --git a/new_editor/app/Rendering/Native/NativeRendererSupport.h b/new_editor/app/Rendering/Native/NativeRendererInternal.h similarity index 94% rename from new_editor/app/Rendering/Native/NativeRendererSupport.h rename to new_editor/app/Rendering/Native/NativeRendererInternal.h index 38d5ff04..423d751b 100644 --- a/new_editor/app/Rendering/Native/NativeRendererSupport.h +++ b/new_editor/app/Rendering/Native/NativeRendererInternal.h @@ -5,8 +5,9 @@ #include #include #include +#include -namespace XCEngine::UI::Editor::Host::NativeRendererSupport { +namespace XCEngine::UI::Editor::Host::NativeRendererInternal { inline constexpr float kBaseDpi = 96.0f; inline constexpr float kDefaultFontSize = 16.0f; @@ -85,4 +86,4 @@ inline void CollectInteropTextureHandles( } } -} // namespace XCEngine::UI::Editor::Host::NativeRendererSupport +} // namespace XCEngine::UI::Editor::Host::NativeRendererInternal diff --git a/new_editor/app/Rendering/Native/NativeRendererLifecycle.cpp b/new_editor/app/Rendering/Native/NativeRendererLifecycle.cpp index 197bfa2d..0a4bfbfb 100644 --- a/new_editor/app/Rendering/Native/NativeRendererLifecycle.cpp +++ b/new_editor/app/Rendering/Native/NativeRendererLifecycle.cpp @@ -1,8 +1,8 @@ -#include "NativeRendererSupport.h" +#include "NativeRendererInternal.h" namespace XCEngine::UI::Editor::Host { -using namespace NativeRendererSupport; +using namespace NativeRendererInternal; bool NativeRenderer::Initialize(HWND hwnd) { Shutdown(); diff --git a/new_editor/app/Rendering/Native/NativeRendererRenderTarget.cpp b/new_editor/app/Rendering/Native/NativeRendererRenderTarget.cpp index 0597e7b7..dc9b9c71 100644 --- a/new_editor/app/Rendering/Native/NativeRendererRenderTarget.cpp +++ b/new_editor/app/Rendering/Native/NativeRendererRenderTarget.cpp @@ -1,8 +1,8 @@ -#include "NativeRendererSupport.h" +#include "NativeRendererInternal.h" namespace XCEngine::UI::Editor::Host { -using namespace NativeRendererSupport; +using namespace NativeRendererInternal; void NativeRenderer::Resize(UINT width, UINT height) { if (!m_renderTarget || width == 0 || height == 0) { diff --git a/new_editor/app/Rendering/Native/NativeRendererRendering.cpp b/new_editor/app/Rendering/Native/NativeRendererRendering.cpp index 8c40e1de..fc0687bb 100644 --- a/new_editor/app/Rendering/Native/NativeRendererRendering.cpp +++ b/new_editor/app/Rendering/Native/NativeRendererRendering.cpp @@ -1,8 +1,8 @@ -#include "NativeRendererSupport.h" +#include "NativeRendererInternal.h" namespace XCEngine::UI::Editor::Host { -using namespace NativeRendererSupport; +using namespace NativeRendererInternal; bool NativeRenderer::Render(const ::XCEngine::UI::UIDrawData& drawData) { if (!EnsureRenderTarget()) { diff --git a/new_editor/app/Rendering/Native/NativeRendererText.cpp b/new_editor/app/Rendering/Native/NativeRendererText.cpp index 6ad7d12f..bbb59bad 100644 --- a/new_editor/app/Rendering/Native/NativeRendererText.cpp +++ b/new_editor/app/Rendering/Native/NativeRendererText.cpp @@ -1,11 +1,11 @@ -#include "NativeRendererSupport.h" +#include "NativeRendererInternal.h" #include #include namespace XCEngine::UI::Editor::Host { -using namespace NativeRendererSupport; +using namespace NativeRendererInternal; float NativeRenderer::MeasureTextWidth( const ::XCEngine::UI::Editor::UIEditorTextMeasureRequest& request) const { diff --git a/new_editor/app/Rendering/Native/NativeRendererTextureDecoding.cpp b/new_editor/app/Rendering/Native/NativeRendererTextureDecoding.cpp index 4c1ed5b8..032fe7fe 100644 --- a/new_editor/app/Rendering/Native/NativeRendererTextureDecoding.cpp +++ b/new_editor/app/Rendering/Native/NativeRendererTextureDecoding.cpp @@ -1,10 +1,10 @@ -#include "NativeRendererSupport.h" +#include "NativeRendererInternal.h" #include namespace XCEngine::UI::Editor::Host { -using namespace NativeRendererSupport; +using namespace NativeRendererInternal; bool NativeRenderer::DecodeTextureFile( const std::filesystem::path& path, diff --git a/new_editor/app/Rendering/Native/NativeRendererTextures.cpp b/new_editor/app/Rendering/Native/NativeRendererTextures.cpp index ab692d7b..c6a13c82 100644 --- a/new_editor/app/Rendering/Native/NativeRendererTextures.cpp +++ b/new_editor/app/Rendering/Native/NativeRendererTextures.cpp @@ -1,11 +1,11 @@ -#include "NativeRendererSupport.h" +#include "NativeRendererInternal.h" #include #include namespace XCEngine::UI::Editor::Host { -using namespace NativeRendererSupport; +using namespace NativeRendererInternal; bool NativeRenderer::LoadTextureFromFile( const std::filesystem::path& path, diff --git a/new_editor/app/Rendering/Native/NativeRendererWindowInterop.cpp b/new_editor/app/Rendering/Native/NativeRendererWindowInterop.cpp index 9b722a7f..84552d79 100644 --- a/new_editor/app/Rendering/Native/NativeRendererWindowInterop.cpp +++ b/new_editor/app/Rendering/Native/NativeRendererWindowInterop.cpp @@ -1,8 +1,8 @@ -#include "NativeRendererSupport.h" +#include "NativeRendererInternal.h" namespace XCEngine::UI::Editor::Host { -using namespace NativeRendererSupport; +using namespace NativeRendererInternal; bool NativeRenderer::AttachWindowRenderer(D3D12WindowRenderer& windowRenderer) { if (m_windowRenderer != &windowRenderer) { diff --git a/new_editor/app/Rendering/Viewport/RenderTargetManager/Manager.cpp b/new_editor/app/Rendering/Viewport/RenderTargetManager/Manager.cpp deleted file mode 100644 index 5f9cc038..00000000 --- a/new_editor/app/Rendering/Viewport/RenderTargetManager/Manager.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "Rendering/Viewport/ViewportRenderTargets.h" - -namespace XCEngine::UI::Editor::App { - -// Viewport render target management is split into resource and surface -// translation units to keep viewport host rendering responsibilities isolated. - -} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Rendering/Viewport/ViewportHostService.cpp b/new_editor/app/Rendering/Viewport/ViewportHostService.cpp deleted file mode 100644 index 45b1bd01..00000000 --- a/new_editor/app/Rendering/Viewport/ViewportHostService.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "ViewportHostService.h" - -namespace XCEngine::UI::Editor::App { - -// ViewportHostService implementations are split into lifecycle and frame files -// so window/renderer attachment and per-frame viewport work do not stay mixed. - -} // namespace XCEngine::UI::Editor::App - diff --git a/new_editor/app/Rendering/Viewport/ViewportRenderTargetSupport.h b/new_editor/app/Rendering/Viewport/ViewportRenderTargetInternal.cpp similarity index 61% rename from new_editor/app/Rendering/Viewport/ViewportRenderTargetSupport.h rename to new_editor/app/Rendering/Viewport/ViewportRenderTargetInternal.cpp index 57836188..9f344c9f 100644 --- a/new_editor/app/Rendering/Viewport/ViewportRenderTargetSupport.h +++ b/new_editor/app/Rendering/Viewport/ViewportRenderTargetInternal.cpp @@ -1,49 +1,15 @@ -#pragma once - -#include "ViewportTypes.h" - -#include -#include -#include -#include +#include "Rendering/Viewport/ViewportRenderTargetInternal.h" #include #include -#include namespace XCEngine::UI::Editor::App { -struct ViewportResourcePresence { - bool hasColorTexture = false; - bool hasColorView = false; - bool hasDepthTexture = false; - bool hasDepthView = false; - bool hasDepthShaderView = false; - bool hasObjectIdTexture = false; - bool hasObjectIdDepthTexture = false; - bool hasObjectIdDepthView = false; - bool hasObjectIdView = false; - bool hasObjectIdShaderView = false; - bool hasSelectionMaskTexture = false; - bool hasSelectionMaskView = false; - bool hasSelectionMaskShaderView = false; - bool hasTextureDescriptor = false; -}; - -struct ViewportResourceReuseQuery { - ViewportKind kind = ViewportKind::Scene; - std::uint32_t width = 0; - std::uint32_t height = 0; - std::uint32_t requestedWidth = 0; - std::uint32_t requestedHeight = 0; - ViewportResourcePresence resources = {}; -}; - -inline bool ViewportRequiresObjectIdResources(ViewportKind kind) { +bool ViewportRequiresObjectIdResources(ViewportKind kind) { return kind == ViewportKind::Scene; } -inline std::uint32_t ClampViewportPixelCoordinate(float value, std::uint32_t extent) { +std::uint32_t ClampViewportPixelCoordinate(float value, std::uint32_t extent) { if (extent == 0u) { return 0u; } @@ -53,7 +19,7 @@ inline std::uint32_t ClampViewportPixelCoordinate(float value, std::uint32_t ext return static_cast(std::floor(clamped)); } -inline bool CanReuseViewportResources(const ViewportResourceReuseQuery& query) { +bool CanReuseViewportResources(const ViewportResourceReuseQuery& query) { if (query.requestedWidth == 0u || query.requestedHeight == 0u) { return false; } @@ -85,7 +51,7 @@ inline bool CanReuseViewportResources(const ViewportResourceReuseQuery& query) { query.resources.hasSelectionMaskShaderView; } -inline ::XCEngine::RHI::TextureDesc BuildViewportTextureDesc( +::XCEngine::RHI::TextureDesc BuildViewportTextureDesc( std::uint32_t width, std::uint32_t height, ::XCEngine::RHI::Format format) { @@ -103,7 +69,7 @@ inline ::XCEngine::RHI::TextureDesc BuildViewportTextureDesc( return desc; } -inline ::XCEngine::RHI::ResourceViewDesc BuildViewportTextureViewDesc( +::XCEngine::RHI::ResourceViewDesc BuildViewportTextureViewDesc( ::XCEngine::RHI::Format format) { ::XCEngine::RHI::ResourceViewDesc desc = {}; desc.format = static_cast(format); @@ -111,14 +77,13 @@ inline ::XCEngine::RHI::ResourceViewDesc BuildViewportTextureViewDesc( return desc; } -inline ::XCEngine::Rendering::RenderSurface BuildViewportRenderSurface( +::XCEngine::Rendering::RenderSurface BuildViewportRenderSurface( std::uint32_t width, std::uint32_t height, ::XCEngine::RHI::RHIResourceView* colorView, ::XCEngine::RHI::RHIResourceView* depthView, ::XCEngine::RHI::ResourceStates colorStateBefore, - ::XCEngine::RHI::ResourceStates colorStateAfter = - ::XCEngine::RHI::ResourceStates::PixelShaderResource) { + ::XCEngine::RHI::ResourceStates colorStateAfter) { ::XCEngine::Rendering::RenderSurface surface(width, height); surface.SetColorAttachment(colorView); surface.SetDepthAttachment(depthView); @@ -128,4 +93,3 @@ inline ::XCEngine::Rendering::RenderSurface BuildViewportRenderSurface( } } // namespace XCEngine::UI::Editor::App - diff --git a/new_editor/app/Rendering/Viewport/ViewportRenderTargetInternal.h b/new_editor/app/Rendering/Viewport/ViewportRenderTargetInternal.h new file mode 100644 index 00000000..a938055d --- /dev/null +++ b/new_editor/app/Rendering/Viewport/ViewportRenderTargetInternal.h @@ -0,0 +1,58 @@ +#pragma once + +#include "ViewportTypes.h" + +#include +#include +#include +#include + +#include + +namespace XCEngine::UI::Editor::App { + +struct ViewportResourcePresence { + bool hasColorTexture = false; + bool hasColorView = false; + bool hasDepthTexture = false; + bool hasDepthView = false; + bool hasDepthShaderView = false; + bool hasObjectIdTexture = false; + bool hasObjectIdDepthTexture = false; + bool hasObjectIdDepthView = false; + bool hasObjectIdView = false; + bool hasObjectIdShaderView = false; + bool hasSelectionMaskTexture = false; + bool hasSelectionMaskView = false; + bool hasSelectionMaskShaderView = false; + bool hasTextureDescriptor = false; +}; + +struct ViewportResourceReuseQuery { + ViewportKind kind = ViewportKind::Scene; + std::uint32_t width = 0; + std::uint32_t height = 0; + std::uint32_t requestedWidth = 0; + std::uint32_t requestedHeight = 0; + ViewportResourcePresence resources = {}; +}; + +bool ViewportRequiresObjectIdResources(ViewportKind kind); +std::uint32_t ClampViewportPixelCoordinate(float value, std::uint32_t extent); +bool CanReuseViewportResources(const ViewportResourceReuseQuery& query); +::XCEngine::RHI::TextureDesc BuildViewportTextureDesc( + std::uint32_t width, + std::uint32_t height, + ::XCEngine::RHI::Format format); +::XCEngine::RHI::ResourceViewDesc BuildViewportTextureViewDesc( + ::XCEngine::RHI::Format format); +::XCEngine::Rendering::RenderSurface BuildViewportRenderSurface( + std::uint32_t width, + std::uint32_t height, + ::XCEngine::RHI::RHIResourceView* colorView, + ::XCEngine::RHI::RHIResourceView* depthView, + ::XCEngine::RHI::ResourceStates colorStateBefore, + ::XCEngine::RHI::ResourceStates colorStateAfter = + ::XCEngine::RHI::ResourceStates::PixelShaderResource); + +} // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp b/new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp deleted file mode 100644 index b1f49b4f..00000000 --- a/new_editor/app/Rendering/Viewport/ViewportRenderTargets.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#include "ViewportRenderTargets.h" - diff --git a/new_editor/app/Rendering/Viewport/ViewportRenderTargets.h b/new_editor/app/Rendering/Viewport/ViewportRenderTargets.h index 7eaaa070..1b0deac3 100644 --- a/new_editor/app/Rendering/Viewport/ViewportRenderTargets.h +++ b/new_editor/app/Rendering/Viewport/ViewportRenderTargets.h @@ -1,6 +1,6 @@ #pragma once -#include "ViewportRenderTargetSupport.h" +#include "ViewportRenderTargetInternal.h" #include diff --git a/new_editor/app/State/EditorContext.h b/new_editor/app/State/EditorContext.h index 943ddbbb..9f62195a 100644 --- a/new_editor/app/State/EditorContext.h +++ b/new_editor/app/State/EditorContext.h @@ -2,7 +2,7 @@ #include "Composition/EditorHostCommandBridge.h" #include "State/EditorSession.h" -#include "Composition/EditorShellAssetBuilder.h" +#include "Composition/EditorShellVariant.h" #include #include diff --git a/new_editor/app/State/EditorSession.cpp b/new_editor/app/State/EditorSession.cpp index 2aae8c02..85c36597 100644 --- a/new_editor/app/State/EditorSession.cpp +++ b/new_editor/app/State/EditorSession.cpp @@ -1,5 +1,7 @@ #include "EditorSession.h" +#include + namespace XCEngine::UI::Editor::App { std::string_view GetEditorRuntimeModeName(EditorRuntimeMode mode) { diff --git a/new_editor/app/State/EditorSession.h b/new_editor/app/State/EditorSession.h index d979aaa9..202f5f4d 100644 --- a/new_editor/app/State/EditorSession.h +++ b/new_editor/app/State/EditorSession.h @@ -1,12 +1,16 @@ #pragma once -#include - #include #include #include #include +namespace XCEngine::UI::Editor { + +class UIEditorWorkspaceController; + +} // namespace XCEngine::UI::Editor + namespace XCEngine::UI::Editor::App { enum class EditorRuntimeMode : std::uint8_t { diff --git a/new_editor/app/Support/EmbeddedPngLoader.cpp b/new_editor/app/Support/EmbeddedPngLoader.cpp new file mode 100644 index 00000000..cc4c3d38 --- /dev/null +++ b/new_editor/app/Support/EmbeddedPngLoader.cpp @@ -0,0 +1,63 @@ +#include "Support/EmbeddedPngLoader.h" + +namespace XCEngine::UI::Editor::Support { + +bool LoadEmbeddedPngBytes( + UINT resourceId, + const std::uint8_t*& outData, + std::size_t& outSize, + std::string& outError) { + outData = nullptr; + outSize = 0u; + outError.clear(); + + HMODULE module = GetModuleHandleW(nullptr); + if (module == nullptr) { + outError = "GetModuleHandleW(nullptr) returned null."; + return false; + } + + HRSRC resource = FindResourceW(module, MAKEINTRESOURCEW(resourceId), L"PNG"); + if (resource == nullptr) { + outError = "FindResourceW failed."; + return false; + } + + HGLOBAL resourceData = LoadResource(module, resource); + if (resourceData == nullptr) { + outError = "LoadResource failed."; + return false; + } + + const DWORD resourceSize = SizeofResource(module, resource); + if (resourceSize == 0u) { + outError = "SizeofResource returned zero."; + return false; + } + + const void* lockedBytes = LockResource(resourceData); + if (lockedBytes == nullptr) { + outError = "LockResource failed."; + return false; + } + + outData = reinterpret_cast(lockedBytes); + outSize = static_cast(resourceSize); + return true; +} + +bool LoadEmbeddedPngTexture( + Host::NativeRenderer& renderer, + UINT resourceId, + ::XCEngine::UI::UITextureHandle& outTexture, + std::string& outError) { + const std::uint8_t* bytes = nullptr; + std::size_t byteCount = 0u; + if (!LoadEmbeddedPngBytes(resourceId, bytes, byteCount, outError)) { + return false; + } + + return renderer.LoadTextureFromMemory(bytes, byteCount, outTexture, outError); +} + +} // namespace XCEngine::UI::Editor::Support diff --git a/new_editor/app/Support/EmbeddedPngLoader.h b/new_editor/app/Support/EmbeddedPngLoader.h index 6ebdf86c..51be82ea 100644 --- a/new_editor/app/Support/EmbeddedPngLoader.h +++ b/new_editor/app/Support/EmbeddedPngLoader.h @@ -12,62 +12,16 @@ namespace XCEngine::UI::Editor::Support { -inline bool LoadEmbeddedPngBytes( +bool LoadEmbeddedPngBytes( UINT resourceId, const std::uint8_t*& outData, std::size_t& outSize, - std::string& outError) { - outData = nullptr; - outSize = 0u; - outError.clear(); + std::string& outError); - HMODULE module = GetModuleHandleW(nullptr); - if (module == nullptr) { - outError = "GetModuleHandleW(nullptr) returned null."; - return false; - } - - HRSRC resource = FindResourceW(module, MAKEINTRESOURCEW(resourceId), L"PNG"); - if (resource == nullptr) { - outError = "FindResourceW failed."; - return false; - } - - HGLOBAL resourceData = LoadResource(module, resource); - if (resourceData == nullptr) { - outError = "LoadResource failed."; - return false; - } - - const DWORD resourceSize = SizeofResource(module, resource); - if (resourceSize == 0u) { - outError = "SizeofResource returned zero."; - return false; - } - - const void* lockedBytes = LockResource(resourceData); - if (lockedBytes == nullptr) { - outError = "LockResource failed."; - return false; - } - - outData = reinterpret_cast(lockedBytes); - outSize = static_cast(resourceSize); - return true; -} - -inline bool LoadEmbeddedPngTexture( +bool LoadEmbeddedPngTexture( Host::NativeRenderer& renderer, UINT resourceId, ::XCEngine::UI::UITextureHandle& outTexture, - std::string& outError) { - const std::uint8_t* bytes = nullptr; - std::size_t byteCount = 0u; - if (!LoadEmbeddedPngBytes(resourceId, bytes, byteCount, outError)) { - return false; - } - - return renderer.LoadTextureFromMemory(bytes, byteCount, outTexture, outError); -} + std::string& outError); } // namespace XCEngine::UI::Editor::Support diff --git a/new_editor/app/UI/Styles/EditorTreeViewStyle.h b/new_editor/app/UI/Styles/EditorTreeViewStyle.h deleted file mode 100644 index babccfcb..00000000 --- a/new_editor/app/UI/Styles/EditorTreeViewStyle.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include - -namespace XCEngine::UI::Editor::App { - -inline constexpr ::XCEngine::UI::UIColor kEditorTreeSurfaceColor(0.10f, 0.10f, 0.10f, 1.0f); -inline constexpr ::XCEngine::UI::UIColor kEditorTreeHoverColor(0.13f, 0.13f, 0.13f, 1.0f); -inline constexpr ::XCEngine::UI::UIColor kEditorTreeSelectedColor(0.17f, 0.17f, 0.17f, 1.0f); -inline constexpr ::XCEngine::UI::UIColor kEditorTreeDisclosureColor(0.620f, 0.620f, 0.620f, 1.0f); -inline constexpr ::XCEngine::UI::UIColor kEditorTreeTextColor(0.920f, 0.920f, 0.920f, 1.0f); - -inline Widgets::UIEditorTreeViewMetrics BuildEditorTreeViewMetrics() { - Widgets::UIEditorTreeViewMetrics metrics = {}; - metrics.rowHeight = 20.0f; - metrics.rowGap = 0.0f; - metrics.horizontalPadding = 6.0f; - metrics.indentWidth = 14.0f; - metrics.disclosureExtent = 18.0f; - metrics.disclosureLabelGap = 2.0f; - metrics.iconExtent = 18.0f; - metrics.iconLabelGap = 2.0f; - metrics.iconInsetY = -1.0f; - metrics.labelInsetY = 0.0f; - metrics.cornerRounding = 0.0f; - metrics.borderThickness = 0.0f; - metrics.focusedBorderThickness = 0.0f; - return metrics; -} - -inline Widgets::UIEditorTreeViewPalette BuildEditorTreeViewPalette() { - Widgets::UIEditorTreeViewPalette palette = {}; - palette.surfaceColor = kEditorTreeSurfaceColor; - palette.borderColor = kEditorTreeSurfaceColor; - palette.focusedBorderColor = kEditorTreeSurfaceColor; - palette.rowHoverColor = kEditorTreeHoverColor; - palette.rowSelectedColor = kEditorTreeSelectedColor; - palette.rowSelectedFocusedColor = kEditorTreeSelectedColor; - palette.disclosureColor = kEditorTreeDisclosureColor; - palette.textColor = kEditorTreeTextColor; - return palette; -} - -} // namespace XCEngine::UI::Editor::App - diff --git a/new_editor/include/XCEditor/Collections/UIEditorTabStrip.h b/new_editor/include/XCEditor/Collections/UIEditorTabStrip.h index 9d407087..a6d5efb2 100644 --- a/new_editor/include/XCEditor/Collections/UIEditorTabStrip.h +++ b/new_editor/include/XCEditor/Collections/UIEditorTabStrip.h @@ -18,43 +18,24 @@ inline constexpr std::size_t UIEditorTabStripInvalidIndex = struct UIEditorTabStripItem { std::string tabId = {}; std::string title = {}; - bool closable = true; float desiredHeaderLabelWidth = 0.0f; }; -struct UIEditorTabStripReorderState { - ::XCEngine::UI::UIPoint pressPosition = {}; - std::size_t pressedIndex = UIEditorTabStripInvalidIndex; - std::size_t sourceIndex = UIEditorTabStripInvalidIndex; - std::size_t previewInsertionIndex = UIEditorTabStripInvalidIndex; - bool armed = false; - bool dragging = false; -}; - struct UIEditorTabStripState { std::size_t selectedIndex = UIEditorTabStripInvalidIndex; std::size_t hoveredIndex = UIEditorTabStripInvalidIndex; - std::size_t closeHoveredIndex = UIEditorTabStripInvalidIndex; bool focused = false; - UIEditorTabStripReorderState reorder = {}; }; struct UIEditorTabStripMetrics { ::XCEngine::UI::Layout::UITabStripMetrics layoutMetrics = ::XCEngine::UI::Layout::UITabStripMetrics{ 22.0f, 68.0f, 8.0f, 1.0f }; float estimatedGlyphWidth = 6.0f; - float closeButtonExtent = 10.0f; - float closeButtonGap = 4.0f; - float closeInsetRight = 6.0f; - float closeInsetY = 0.0f; float labelInsetX = 8.0f; float labelInsetY = -0.5f; float baseBorderThickness = 1.0f; float selectedBorderThickness = 1.0f; - float focusedBorderThickness = 1.0f; - float reorderDragThreshold = 6.0f; - float reorderPreviewThickness = 2.0f; - float reorderPreviewInsetY = 3.0f; + float dragThreshold = 6.0f; }; struct UIEditorTabStripPalette { @@ -64,12 +45,8 @@ struct UIEditorTabStripPalette { ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); ::XCEngine::UI::UIColor contentBackgroundColor = ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); - ::XCEngine::UI::UIColor stripBorderColor = - ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor headerContentSeparatorColor = ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); - ::XCEngine::UI::UIColor focusedBorderColor = - ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); ::XCEngine::UI::UIColor tabColor = ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); ::XCEngine::UI::UIColor tabHoveredColor = @@ -86,24 +63,6 @@ struct UIEditorTabStripPalette { ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 1.0f); ::XCEngine::UI::UIColor textSecondary = ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); - ::XCEngine::UI::UIColor textMuted = - ::XCEngine::UI::UIColor(0.62f, 0.62f, 0.62f, 1.0f); - ::XCEngine::UI::UIColor closeButtonColor = - ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); - ::XCEngine::UI::UIColor closeButtonHoveredColor = - ::XCEngine::UI::UIColor(0.14f, 0.14f, 0.14f, 1.0f); - ::XCEngine::UI::UIColor closeButtonBorderColor = - ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); - ::XCEngine::UI::UIColor closeGlyphColor = - ::XCEngine::UI::UIColor(0.86f, 0.86f, 0.86f, 1.0f); - ::XCEngine::UI::UIColor reorderPreviewColor = - ::XCEngine::UI::UIColor(0.92f, 0.92f, 0.92f, 0.90f); -}; - -struct UIEditorTabStripInsertionPreview { - bool visible = false; - std::size_t insertionIndex = UIEditorTabStripInvalidIndex; - ::XCEngine::UI::UIRect indicatorRect = {}; }; struct UIEditorTabStripLayout { @@ -111,17 +70,13 @@ struct UIEditorTabStripLayout { ::XCEngine::UI::UIRect headerRect = {}; ::XCEngine::UI::UIRect contentRect = {}; std::vector<::XCEngine::UI::UIRect> tabHeaderRects = {}; - std::vector<::XCEngine::UI::UIRect> closeButtonRects = {}; - std::vector showCloseButtons = {}; std::size_t selectedIndex = UIEditorTabStripInvalidIndex; - UIEditorTabStripInsertionPreview insertionPreview = {}; }; enum class UIEditorTabStripHitTargetKind : std::uint8_t { None = 0, HeaderBackground, Tab, - CloseButton, Content }; @@ -139,11 +94,6 @@ std::size_t ResolveUIEditorTabStripSelectedIndex( std::string_view selectedTabId, std::size_t fallbackIndex = UIEditorTabStripInvalidIndex); -std::size_t ResolveUIEditorTabStripSelectedIndexAfterClose( - std::size_t selectedIndex, - std::size_t closedIndex, - std::size_t itemCountBeforeClose); - UIEditorTabStripLayout BuildUIEditorTabStripLayout( const ::XCEngine::UI::UIRect& bounds, const std::vector& items, diff --git a/new_editor/include/XCEditor/Collections/UIEditorTabStripInteraction.h b/new_editor/include/XCEditor/Collections/UIEditorTabStripInteraction.h index bf974c09..c96e123a 100644 --- a/new_editor/include/XCEditor/Collections/UIEditorTabStripInteraction.h +++ b/new_editor/include/XCEditor/Collections/UIEditorTabStripInteraction.h @@ -14,37 +14,29 @@ namespace XCEngine::UI::Editor { struct UIEditorTabStripInteractionState { Widgets::UIEditorTabStripState tabStripState = {}; ::XCEngine::UI::Widgets::UITabStripModel navigationModel = {}; - ::XCEngine::UI::Widgets::UIDragDropState reorderDragState = {}; + ::XCEngine::UI::Widgets::UIDragDropState dragState = {}; Widgets::UIEditorTabStripHitTarget pressedTarget = {}; ::XCEngine::UI::UIPoint pointerPosition = {}; - std::size_t reorderSourceIndex = Widgets::UIEditorTabStripInvalidIndex; - std::size_t reorderPreviewIndex = Widgets::UIEditorTabStripInvalidIndex; + std::size_t dragSourceIndex = Widgets::UIEditorTabStripInvalidIndex; bool hasPointerPosition = false; - bool reorderCaptureActive = false; + bool dragCaptureActive = false; }; struct UIEditorTabStripInteractionResult { bool consumed = false; bool selectionChanged = false; - bool closeRequested = false; bool keyboardNavigated = false; bool requestPointerCapture = false; bool releasePointerCapture = false; bool dragStarted = false; bool dragEnded = false; bool dragCanceled = false; - bool reorderRequested = false; - bool reorderPreviewActive = false; Widgets::UIEditorTabStripHitTarget hitTarget = {}; std::string selectedTabId = {}; std::size_t selectedIndex = Widgets::UIEditorTabStripInvalidIndex; - std::string closedTabId = {}; std::size_t closedIndex = Widgets::UIEditorTabStripInvalidIndex; std::string draggedTabId = {}; std::size_t dragSourceIndex = Widgets::UIEditorTabStripInvalidIndex; - std::size_t dropInsertionIndex = Widgets::UIEditorTabStripInvalidIndex; - std::size_t reorderToIndex = Widgets::UIEditorTabStripInvalidIndex; - std::size_t reorderPreviewIndex = Widgets::UIEditorTabStripInvalidIndex; }; struct UIEditorTabStripInteractionFrame { diff --git a/new_editor/include/XCEditor/Docking/UIEditorDockHost.h b/new_editor/include/XCEditor/Docking/UIEditorDockHost.h index af9fc9ce..c16663a7 100644 --- a/new_editor/include/XCEditor/Docking/UIEditorDockHost.h +++ b/new_editor/include/XCEditor/Docking/UIEditorDockHost.h @@ -21,11 +21,9 @@ enum class UIEditorDockHostHitTargetKind : std::uint8_t { SplitterHandle, TabStripBackground, Tab, - TabCloseButton, PanelHeader, PanelBody, - PanelFooter, - PanelCloseButton + PanelFooter }; struct UIEditorDockHostHitTarget { @@ -95,7 +93,6 @@ struct UIEditorDockHostPalette { struct UIEditorDockHostTabItemLayout { std::string panelId = {}; std::string title = {}; - bool closable = true; bool active = false; }; diff --git a/new_editor/include/XCEditor/Widgets/UIEditorColorUtils.h b/new_editor/include/XCEditor/Widgets/UIEditorColorUtils.h index acb45fee..f5cfb5fa 100644 --- a/new_editor/include/XCEditor/Widgets/UIEditorColorUtils.h +++ b/new_editor/include/XCEditor/Widgets/UIEditorColorUtils.h @@ -2,9 +2,6 @@ #include -#include -#include -#include #include namespace XCEngine::UI::Editor::Widgets { @@ -16,163 +13,21 @@ struct UIEditorHsvColor { float alpha = 1.0f; }; -inline float ClampUIEditorColorUnit(float value) { - return (std::clamp)(value, 0.0f, 1.0f); -} - -inline int ToUIEditorColorByte(float value) { - return static_cast(std::lround(ClampUIEditorColorUnit(value) * 255.0f)); -} - -inline UIEditorHsvColor ConvertUIEditorColorToHsv( +float ClampUIEditorColorUnit(float value); +int ToUIEditorColorByte(float value); +UIEditorHsvColor ConvertUIEditorColorToHsv( const ::XCEngine::UI::UIColor& color, - float fallbackHue = 0.0f) { - const float red = ClampUIEditorColorUnit(color.r); - const float green = ClampUIEditorColorUnit(color.g); - const float blue = ClampUIEditorColorUnit(color.b); - const float maxChannel = (std::max)({ red, green, blue }); - const float minChannel = (std::min)({ red, green, blue }); - const float delta = maxChannel - minChannel; - - UIEditorHsvColor hsv = {}; - hsv.hue = ClampUIEditorColorUnit(fallbackHue); - hsv.saturation = maxChannel <= 0.0f ? 0.0f : delta / maxChannel; - hsv.value = maxChannel; - hsv.alpha = ClampUIEditorColorUnit(color.a); - - if (delta <= 0.00001f) { - return hsv; - } - - if (maxChannel == red) { - hsv.hue = std::fmod(((green - blue) / delta), 6.0f) / 6.0f; - } else if (maxChannel == green) { - hsv.hue = (((blue - red) / delta) + 2.0f) / 6.0f; - } else { - hsv.hue = (((red - green) / delta) + 4.0f) / 6.0f; - } - - if (hsv.hue < 0.0f) { - hsv.hue += 1.0f; - } - return hsv; -} - -inline ::XCEngine::UI::UIColor ConvertUIEditorHsvToColor(const UIEditorHsvColor& hsv) { - const float hue = ClampUIEditorColorUnit(hsv.hue); - const float saturation = ClampUIEditorColorUnit(hsv.saturation); - const float value = ClampUIEditorColorUnit(hsv.value); - - if (saturation <= 0.00001f) { - return ::XCEngine::UI::UIColor(value, value, value, ClampUIEditorColorUnit(hsv.alpha)); - } - - const float sector = hue * 6.0f; - const int sectorIndex = static_cast(std::floor(sector)) % 6; - const float fraction = sector - std::floor(sector); - const float p = value * (1.0f - saturation); - const float q = value * (1.0f - saturation * fraction); - const float t = value * (1.0f - saturation * (1.0f - fraction)); - - float red = value; - float green = t; - float blue = p; - switch (sectorIndex) { - case 0: - red = value; - green = t; - blue = p; - break; - case 1: - red = q; - green = value; - blue = p; - break; - case 2: - red = p; - green = value; - blue = t; - break; - case 3: - red = p; - green = q; - blue = value; - break; - case 4: - red = t; - green = p; - blue = value; - break; - case 5: - default: - red = value; - green = p; - blue = q; - break; - } - - return ::XCEngine::UI::UIColor(red, green, blue, ClampUIEditorColorUnit(hsv.alpha)); -} - -inline UIEditorHsvColor ResolveUIEditorDisplayHsv( + float fallbackHue = 0.0f); +::XCEngine::UI::UIColor ConvertUIEditorHsvToColor(const UIEditorHsvColor& hsv); +UIEditorHsvColor ResolveUIEditorDisplayHsv( const ::XCEngine::UI::UIColor& color, float rememberedHue, - bool hueValid) { - UIEditorHsvColor hsv = ConvertUIEditorColorToHsv(color, hueValid ? rememberedHue : 0.0f); - if (hsv.saturation <= 0.00001f && hueValid) { - hsv.hue = ClampUIEditorColorUnit(rememberedHue); - } - return hsv; -} - -inline std::string FormatUIEditorColorHex( + bool hueValid); +std::string FormatUIEditorColorHex( const ::XCEngine::UI::UIColor& color, - bool includeAlpha = true) { - char buffer[16] = {}; - if (includeAlpha) { - std::snprintf( - buffer, - sizeof(buffer), - "#%02X%02X%02X%02X", - ToUIEditorColorByte(color.r), - ToUIEditorColorByte(color.g), - ToUIEditorColorByte(color.b), - ToUIEditorColorByte(color.a)); - } else { - std::snprintf( - buffer, - sizeof(buffer), - "#%02X%02X%02X", - ToUIEditorColorByte(color.r), - ToUIEditorColorByte(color.g), - ToUIEditorColorByte(color.b)); - } - return std::string(buffer); -} - -inline std::string FormatUIEditorColorChannelsText( + bool includeAlpha = true); +std::string FormatUIEditorColorChannelsText( const ::XCEngine::UI::UIColor& color, - bool includeAlpha = true) { - char buffer[64] = {}; - if (includeAlpha) { - std::snprintf( - buffer, - sizeof(buffer), - "RGBA %d, %d, %d, %d", - ToUIEditorColorByte(color.r), - ToUIEditorColorByte(color.g), - ToUIEditorColorByte(color.b), - ToUIEditorColorByte(color.a)); - } else { - std::snprintf( - buffer, - sizeof(buffer), - "RGB %d, %d, %d", - ToUIEditorColorByte(color.r), - ToUIEditorColorByte(color.g), - ToUIEditorColorByte(color.b)); - } - return std::string(buffer); -} + bool includeAlpha = true); } // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceController.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceController.h index 79c6c1e5..b7bdb7be 100644 --- a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceController.h +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceController.h @@ -105,10 +105,6 @@ public: UIEditorWorkspaceLayoutOperationResult SetSplitRatio( std::string_view nodeId, float splitRatio); - UIEditorWorkspaceLayoutOperationResult ReorderTab( - std::string_view nodeId, - std::string_view panelId, - std::size_t targetVisibleInsertionIndex); UIEditorWorkspaceLayoutOperationResult MoveTabToStack( std::string_view sourceNodeId, std::string_view panelId, diff --git a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceModel.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceModel.h index 97dd0f6e..aaffad76 100644 --- a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceModel.h +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceModel.h @@ -1,9 +1,9 @@ #pragma once +#include #include #include #include -#include #include namespace XCEngine::UI::Editor { @@ -50,29 +50,6 @@ struct UIEditorWorkspaceModel { std::string activePanelId = {}; }; -enum class UIEditorWorkspaceValidationCode : std::uint8_t { - None = 0, - EmptyNodeId, - InvalidSplitChildCount, - InvalidSplitRatio, - EmptyTabStack, - InvalidSelectedTabIndex, - NonPanelTabChild, - EmptyPanelId, - EmptyPanelTitle, - DuplicatePanelId, - InvalidActivePanelId -}; - -struct UIEditorWorkspaceValidationResult { - UIEditorWorkspaceValidationCode code = UIEditorWorkspaceValidationCode::None; - std::string message = {}; - - [[nodiscard]] bool IsValid() const { - return code == UIEditorWorkspaceValidationCode::None; - } -}; - struct UIEditorWorkspaceVisiblePanel { std::string panelId = {}; std::string title = {}; @@ -106,23 +83,9 @@ UIEditorWorkspaceNode BuildUIEditorWorkspaceSplit( UIEditorWorkspaceNode primary, UIEditorWorkspaceNode secondary); -UIEditorWorkspaceValidationResult ValidateUIEditorWorkspace( - const UIEditorWorkspaceModel& workspace); - UIEditorWorkspaceModel CanonicalizeUIEditorWorkspaceModel( UIEditorWorkspaceModel workspace); -std::vector CollectUIEditorWorkspaceVisiblePanels( - const UIEditorWorkspaceModel& workspace); - -bool ContainsUIEditorWorkspacePanel( - const UIEditorWorkspaceModel& workspace, - std::string_view panelId); - -const UIEditorWorkspaceNode* FindUIEditorWorkspaceNode( - const UIEditorWorkspaceModel& workspace, - std::string_view nodeId); - bool AreUIEditorWorkspaceNodesEquivalent( const UIEditorWorkspaceNode& lhs, const UIEditorWorkspaceNode& rhs); @@ -131,62 +94,4 @@ bool AreUIEditorWorkspaceModelsEquivalent( const UIEditorWorkspaceModel& lhs, const UIEditorWorkspaceModel& rhs); -const UIEditorWorkspacePanelState* FindUIEditorWorkspaceActivePanel( - const UIEditorWorkspaceModel& workspace); - -bool TryActivateUIEditorWorkspacePanel( - UIEditorWorkspaceModel& workspace, - std::string_view panelId); - -bool TrySetUIEditorWorkspaceSplitRatio( - UIEditorWorkspaceModel& workspace, - std::string_view nodeId, - float splitRatio); - -bool TryReorderUIEditorWorkspaceTab( - UIEditorWorkspaceModel& workspace, - const UIEditorWorkspaceSession& session, - std::string_view nodeId, - std::string_view panelId, - std::size_t targetVisibleInsertionIndex); - -bool TryExtractUIEditorWorkspaceVisiblePanelNode( - UIEditorWorkspaceModel& workspace, - const UIEditorWorkspaceSession& session, - std::string_view sourceNodeId, - std::string_view panelId, - UIEditorWorkspaceNode& extractedPanel); - -bool TryInsertUIEditorWorkspacePanelNodeToStack( - UIEditorWorkspaceModel& workspace, - const UIEditorWorkspaceSession& session, - UIEditorWorkspaceNode panelNode, - std::string_view targetNodeId, - std::size_t targetVisibleInsertionIndex); - -bool TryDockUIEditorWorkspacePanelNodeRelative( - UIEditorWorkspaceModel& workspace, - const UIEditorWorkspaceSession& session, - UIEditorWorkspaceNode panelNode, - std::string_view targetNodeId, - UIEditorWorkspaceDockPlacement placement, - float splitRatio = 0.5f); - -bool TryMoveUIEditorWorkspaceTabToStack( - UIEditorWorkspaceModel& workspace, - const UIEditorWorkspaceSession& session, - std::string_view sourceNodeId, - std::string_view panelId, - std::string_view targetNodeId, - std::size_t targetVisibleInsertionIndex); - -bool TryDockUIEditorWorkspaceTabRelative( - UIEditorWorkspaceModel& workspace, - const UIEditorWorkspaceSession& session, - std::string_view sourceNodeId, - std::string_view panelId, - std::string_view targetNodeId, - UIEditorWorkspaceDockPlacement placement, - float splitRatio = 0.5f); - } // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceMutation.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceMutation.h new file mode 100644 index 00000000..4f89a2a9 --- /dev/null +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceMutation.h @@ -0,0 +1,60 @@ +#pragma once + +#include + +#include +#include + +namespace XCEngine::UI::Editor { + +struct UIEditorWorkspaceSession; + +bool TryActivateUIEditorWorkspacePanel( + UIEditorWorkspaceModel& workspace, + std::string_view panelId); + +bool TrySetUIEditorWorkspaceSplitRatio( + UIEditorWorkspaceModel& workspace, + std::string_view nodeId, + float splitRatio); + +bool TryExtractUIEditorWorkspaceVisiblePanelNode( + UIEditorWorkspaceModel& workspace, + const UIEditorWorkspaceSession& session, + std::string_view sourceNodeId, + std::string_view panelId, + UIEditorWorkspaceNode& extractedPanel); + +bool TryInsertUIEditorWorkspacePanelNodeToStack( + UIEditorWorkspaceModel& workspace, + const UIEditorWorkspaceSession& session, + UIEditorWorkspaceNode panelNode, + std::string_view targetNodeId, + std::size_t targetVisibleInsertionIndex); + +bool TryDockUIEditorWorkspacePanelNodeRelative( + UIEditorWorkspaceModel& workspace, + const UIEditorWorkspaceSession& session, + UIEditorWorkspaceNode panelNode, + std::string_view targetNodeId, + UIEditorWorkspaceDockPlacement placement, + float splitRatio = 0.5f); + +bool TryMoveUIEditorWorkspaceTabToStack( + UIEditorWorkspaceModel& workspace, + const UIEditorWorkspaceSession& session, + std::string_view sourceNodeId, + std::string_view panelId, + std::string_view targetNodeId, + std::size_t targetVisibleInsertionIndex); + +bool TryDockUIEditorWorkspaceTabRelative( + UIEditorWorkspaceModel& workspace, + const UIEditorWorkspaceSession& session, + std::string_view sourceNodeId, + std::string_view panelId, + std::string_view targetNodeId, + UIEditorWorkspaceDockPlacement placement, + float splitRatio = 0.5f); + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceQueries.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceQueries.h new file mode 100644 index 00000000..e8cb2d49 --- /dev/null +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceQueries.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include +#include + +namespace XCEngine::UI::Editor { + +std::vector CollectUIEditorWorkspaceVisiblePanels( + const UIEditorWorkspaceModel& workspace); + +bool ContainsUIEditorWorkspacePanel( + const UIEditorWorkspaceModel& workspace, + std::string_view panelId); + +const UIEditorWorkspaceNode* FindUIEditorWorkspaceNode( + const UIEditorWorkspaceModel& workspace, + std::string_view nodeId); + +const UIEditorWorkspacePanelState* FindUIEditorWorkspaceActivePanel( + const UIEditorWorkspaceModel& workspace); + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceValidation.h b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceValidation.h new file mode 100644 index 00000000..f5a68b1e --- /dev/null +++ b/new_editor/include/XCEditor/Workspace/UIEditorWorkspaceValidation.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include +#include + +namespace XCEngine::UI::Editor { + +enum class UIEditorWorkspaceValidationCode : std::uint8_t { + None = 0, + EmptyNodeId, + InvalidSplitChildCount, + InvalidSplitRatio, + EmptyTabStack, + InvalidSelectedTabIndex, + NonPanelTabChild, + EmptyPanelId, + EmptyPanelTitle, + DuplicatePanelId, + InvalidActivePanelId +}; + +struct UIEditorWorkspaceValidationResult { + UIEditorWorkspaceValidationCode code = UIEditorWorkspaceValidationCode::None; + std::string message = {}; + + [[nodiscard]] bool IsValid() const { + return code == UIEditorWorkspaceValidationCode::None; + } +}; + +UIEditorWorkspaceValidationResult ValidateUIEditorWorkspace( + const UIEditorWorkspaceModel& workspace); + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Collections/UIEditorTabStrip.cpp b/new_editor/src/Collections/UIEditorTabStrip.cpp index 46234003..c8e4a298 100644 --- a/new_editor/src/Collections/UIEditorTabStrip.cpp +++ b/new_editor/src/Collections/UIEditorTabStrip.cpp @@ -196,41 +196,6 @@ void AppendSelectedTabBottomBorderMask( 0.0f); } -UIEditorTabStripInsertionPreview BuildInsertionPreview( - const UIEditorTabStripLayout& layout, - const UIEditorTabStripState& state, - const UIEditorTabStripMetrics& metrics) { - UIEditorTabStripInsertionPreview preview = {}; - if (!state.reorder.dragging || - state.reorder.previewInsertionIndex == UIEditorTabStripInvalidIndex || - layout.tabHeaderRects.empty() || - state.reorder.previewInsertionIndex > layout.tabHeaderRects.size() || - layout.headerRect.height <= 0.0f) { - return preview; - } - - float indicatorX = layout.tabHeaderRects.front().x; - if (state.reorder.previewInsertionIndex >= layout.tabHeaderRects.size()) { - const UIRect& lastRect = layout.tabHeaderRects.back(); - indicatorX = lastRect.x + lastRect.width; - } else { - indicatorX = layout.tabHeaderRects[state.reorder.previewInsertionIndex].x; - } - - const float thickness = (std::max)(ClampNonNegative(metrics.reorderPreviewThickness), 1.0f); - const float insetY = ClampNonNegative(metrics.reorderPreviewInsetY); - const float indicatorHeight = (std::max)(layout.headerRect.height - insetY * 2.0f, thickness); - - preview.visible = true; - preview.insertionIndex = state.reorder.previewInsertionIndex; - preview.indicatorRect = UIRect( - indicatorX - thickness * 0.5f, - layout.headerRect.y + insetY, - thickness, - indicatorHeight); - return preview; -} - } // namespace float ResolveUIEditorTabStripDesiredHeaderLabelWidth( @@ -287,9 +252,6 @@ UIEditorTabStripLayout BuildUIEditorTabStripLayout( layout.headerRect = arranged.headerRect; layout.contentRect = arranged.contentRect; layout.tabHeaderRects = arranged.tabHeaderRects; - layout.closeButtonRects.resize(items.size()); - layout.showCloseButtons.resize(items.size(), false); - layout.insertionPreview = BuildInsertionPreview(layout, state, metrics); return layout; } @@ -361,12 +323,6 @@ void AppendUIEditorTabStripBackground( } } - if (layout.insertionPreview.visible) { - drawList.AddFilledRect( - layout.insertionPreview.indicatorRect, - palette.reorderPreviewColor, - 0.0f); - } } void AppendUIEditorTabStripForeground( diff --git a/new_editor/src/Collections/UIEditorTabStripInteraction.cpp b/new_editor/src/Collections/UIEditorTabStripInteraction.cpp index 01c5785a..61df5236 100644 --- a/new_editor/src/Collections/UIEditorTabStripInteraction.cpp +++ b/new_editor/src/Collections/UIEditorTabStripInteraction.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include namespace XCEngine::UI::Editor { @@ -20,9 +20,7 @@ using ::XCEngine::UI::Widgets::UIDragDropOperation; using ::XCEngine::UI::Widgets::UIDragDropPayload; using ::XCEngine::UI::Widgets::UIDragDropResult; using ::XCEngine::UI::Widgets::UIDragDropSourceDescriptor; -using ::XCEngine::UI::Widgets::UIDragDropTargetDescriptor; using ::XCEngine::UI::Widgets::UpdateUIDragDropPointer; -using ::XCEngine::UI::Widgets::UpdateUIDragDropTarget; using Widgets::BuildUIEditorTabStripLayout; using Widgets::HitTestUIEditorTabStrip; using Widgets::ResolveUIEditorTabStripSelectedIndex; @@ -64,15 +62,12 @@ bool AreEquivalentTargets( void ClearHoverState(UIEditorTabStripInteractionState& state) { state.tabStripState.hoveredIndex = UIEditorTabStripInvalidIndex; - state.tabStripState.closeHoveredIndex = UIEditorTabStripInvalidIndex; } -void ClearReorderState(UIEditorTabStripInteractionState& state) { - state.tabStripState.reorder = {}; - state.reorderDragState = {}; - state.reorderSourceIndex = UIEditorTabStripInvalidIndex; - state.reorderPreviewIndex = UIEditorTabStripInvalidIndex; - state.reorderCaptureActive = false; +void ClearDragState(UIEditorTabStripInteractionState& state) { + state.dragState = {}; + state.dragSourceIndex = UIEditorTabStripInvalidIndex; + state.dragCaptureActive = false; } void SyncHoverTarget( @@ -89,13 +84,8 @@ void SyncHoverTarget( return; } - switch (hitTarget.kind) { - case UIEditorTabStripHitTargetKind::Tab: + if (hitTarget.kind == UIEditorTabStripHitTargetKind::Tab) { state.tabStripState.hoveredIndex = hitTarget.index; - break; - - default: - break; } } @@ -162,152 +152,25 @@ bool ApplyKeyboardNavigation( } } -bool HasReorderInteraction(const UIEditorTabStripInteractionState& state) { - return state.reorderDragState.armed || state.reorderDragState.active; +bool HasTabDragInteraction(const UIEditorTabStripInteractionState& state) { + return state.dragState.armed || state.dragState.active; } -std::size_t ResolveVisibleDropInsertionIndex( - const Widgets::UIEditorTabStripLayout& layout, - const ::XCEngine::UI::UIPoint& pointerPosition) { - if (!IsPointInside(layout.headerRect, pointerPosition)) { - return UIEditorTabStripInvalidIndex; - } - - std::size_t insertionIndex = 0u; - for (const ::XCEngine::UI::UIRect& rect : layout.tabHeaderRects) { - const float midpoint = rect.x + rect.width * 0.5f; - if (pointerPosition.x > midpoint) { - ++insertionIndex; - } - } - - return insertionIndex; -} - -std::size_t ResolveReorderTargetIndex( - std::size_t sourceIndex, - std::size_t dropInsertionIndex, - std::size_t itemCount) { - if (sourceIndex >= itemCount || dropInsertionIndex > itemCount) { - return UIEditorTabStripInvalidIndex; - } - - if (dropInsertionIndex <= sourceIndex) { - return dropInsertionIndex; - } - - return dropInsertionIndex - 1u; -} - -std::size_t ResolveDropInsertionIndexFromPreviewTargetIndex( - std::size_t sourceIndex, - std::size_t previewTargetIndex) { - if (sourceIndex == UIEditorTabStripInvalidIndex || - previewTargetIndex == UIEditorTabStripInvalidIndex) { - return UIEditorTabStripInvalidIndex; - } - - return previewTargetIndex <= sourceIndex - ? previewTargetIndex - : previewTargetIndex + 1u; -} - -std::size_t ResolveCommittedReorderInsertionIndex( +void SyncDragInfo( const UIEditorTabStripInteractionState& state, - const Widgets::UIEditorTabStripLayout& layout) { - const std::size_t previewInsertionIndex = - ResolveDropInsertionIndexFromPreviewTargetIndex( - state.reorderSourceIndex, - state.reorderPreviewIndex); - if (previewInsertionIndex != UIEditorTabStripInvalidIndex) { - return previewInsertionIndex; - } - - if (state.tabStripState.reorder.previewInsertionIndex != UIEditorTabStripInvalidIndex) { - return state.tabStripState.reorder.previewInsertionIndex; - } - - if (!state.hasPointerPosition) { - return UIEditorTabStripInvalidIndex; - } - - return ResolveVisibleDropInsertionIndex(layout, state.pointerPosition); -} - -void SyncReorderPreview( - UIEditorTabStripInteractionState& state, - const Widgets::UIEditorTabStripLayout& layout, const std::vector& items, UIEditorTabStripInteractionResult& result) { - state.tabStripState.reorder.armed = state.reorderDragState.armed; - state.tabStripState.reorder.dragging = state.reorderDragState.active; - state.tabStripState.reorder.sourceIndex = state.reorderSourceIndex; - state.tabStripState.reorder.pressedIndex = state.reorderSourceIndex; - state.tabStripState.reorder.pressPosition = state.reorderDragState.pointerDownPosition; - result.dragSourceIndex = state.reorderSourceIndex; - if (state.reorderSourceIndex < items.size()) { - result.draggedTabId = items[state.reorderSourceIndex].tabId; + result.dragSourceIndex = state.dragSourceIndex; + if (state.dragSourceIndex < items.size()) { + result.draggedTabId = items[state.dragSourceIndex].tabId; } - - if (!state.reorderDragState.active || !state.hasPointerPosition || items.size() < 2u) { - UIDragDropResult dragDropResult = {}; - UpdateUIDragDropTarget(state.reorderDragState, nullptr, &dragDropResult); - state.tabStripState.reorder.previewInsertionIndex = UIEditorTabStripInvalidIndex; - state.reorderPreviewIndex = UIEditorTabStripInvalidIndex; - result.dropInsertionIndex = UIEditorTabStripInvalidIndex; - result.reorderToIndex = UIEditorTabStripInvalidIndex; - result.reorderPreviewIndex = UIEditorTabStripInvalidIndex; - return; - } - - const std::size_t dropInsertionIndex = - ResolveVisibleDropInsertionIndex(layout, state.pointerPosition); - if (dropInsertionIndex == UIEditorTabStripInvalidIndex) { - UIDragDropResult dragDropResult = {}; - UpdateUIDragDropTarget(state.reorderDragState, nullptr, &dragDropResult); - state.tabStripState.reorder.previewInsertionIndex = UIEditorTabStripInvalidIndex; - state.reorderPreviewIndex = UIEditorTabStripInvalidIndex; - result.dropInsertionIndex = UIEditorTabStripInvalidIndex; - result.reorderToIndex = UIEditorTabStripInvalidIndex; - result.reorderPreviewIndex = UIEditorTabStripInvalidIndex; - return; - } - - const std::size_t reorderToIndex = - ResolveReorderTargetIndex( - state.reorderSourceIndex, - dropInsertionIndex, - items.size()); - if (reorderToIndex == UIEditorTabStripInvalidIndex) { - return; - } - - static constexpr std::array kAcceptedPayloadTypes = { - kTabStripDragPayloadType - }; - const std::string targetId = "insert:" + std::to_string(dropInsertionIndex); - UIDragDropTargetDescriptor target = {}; - target.ownerId = kTabStripDragOwnerId; - target.targetId = targetId; - target.acceptedPayloadTypes = kAcceptedPayloadTypes; - target.acceptedOperations = UIDragDropOperation::Move; - target.preferredOperation = UIDragDropOperation::Move; - - UIDragDropResult dragDropResult = {}; - UpdateUIDragDropTarget(state.reorderDragState, &target, &dragDropResult); - - state.tabStripState.reorder.previewInsertionIndex = dropInsertionIndex; - state.reorderPreviewIndex = reorderToIndex; - result.reorderPreviewActive = true; - result.dropInsertionIndex = dropInsertionIndex; - result.reorderToIndex = reorderToIndex; - result.reorderPreviewIndex = reorderToIndex; } -void BeginTabReorder( +void BeginTabDrag( UIEditorTabStripInteractionState& state, const std::vector& items, - std::size_t sourceIndex) { + std::size_t sourceIndex, + const Widgets::UIEditorTabStripMetrics& metrics) { if (sourceIndex >= items.size()) { return; } @@ -322,43 +185,32 @@ void BeginTabReorder( items[sourceIndex].title }; source.allowedOperations = UIDragDropOperation::Move; - source.activationDistance = 4.0f; + source.activationDistance = (std::max)(metrics.dragThreshold, 0.0f); UIDragDropResult dragDropResult = {}; - if (BeginUIDragDrop(source, state.reorderDragState, &dragDropResult)) { - state.tabStripState.reorder.pressPosition = state.pointerPosition; - state.tabStripState.reorder.pressedIndex = sourceIndex; - state.tabStripState.reorder.sourceIndex = sourceIndex; - state.tabStripState.reorder.previewInsertionIndex = UIEditorTabStripInvalidIndex; - state.tabStripState.reorder.armed = true; - state.tabStripState.reorder.dragging = false; - state.reorderSourceIndex = sourceIndex; - state.reorderPreviewIndex = UIEditorTabStripInvalidIndex; + if (BeginUIDragDrop(source, state.dragState, &dragDropResult)) { + state.dragSourceIndex = sourceIndex; } } -void CancelTabReorder( +void CancelTabDrag( UIEditorTabStripInteractionState& state, UIEditorTabStripInteractionResult& result, const std::vector& items) { - if (!HasReorderInteraction(state)) { + if (!HasTabDragInteraction(state)) { return; } + SyncDragInfo(state, items, result); result.dragCanceled = true; - result.consumed = state.reorderCaptureActive || state.reorderDragState.active; - result.dragSourceIndex = state.reorderSourceIndex; - result.reorderPreviewIndex = state.reorderPreviewIndex; - if (state.reorderSourceIndex < items.size()) { - result.draggedTabId = items[state.reorderSourceIndex].tabId; - } - if (state.reorderCaptureActive) { + result.consumed = state.dragCaptureActive || state.dragState.active; + if (state.dragCaptureActive) { result.releasePointerCapture = true; } UIDragDropResult dragDropResult = {}; - CancelUIDragDrop(state.reorderDragState, &dragDropResult); - ClearReorderState(state); + CancelUIDragDrop(state.dragState, &dragDropResult); + ClearDragState(state); } } // namespace @@ -368,7 +220,7 @@ void ClearUIEditorTabStripTransientInteraction( state.pressedTarget = {}; state.hasPointerPosition = false; ClearHoverState(state); - ClearReorderState(state); + ClearDragState(state); } UIEditorTabStripInteractionFrame UpdateUIEditorTabStripInteraction( @@ -408,36 +260,35 @@ UIEditorTabStripInteractionFrame UpdateUIEditorTabStripInteraction( state.hasPointerPosition = false; state.pressedTarget = {}; ClearHoverState(state); - CancelTabReorder(state, eventResult, items); + CancelTabDrag(state, eventResult, items); break; case UIInputEventType::PointerMove: case UIInputEventType::PointerEnter: - if (HasReorderInteraction(state)) { + if (HasTabDragInteraction(state)) { UIDragDropResult dragDropResult = {}; - UpdateUIDragDropPointer(state.reorderDragState, state.pointerPosition, &dragDropResult); - if (dragDropResult.activated && !state.reorderCaptureActive) { - state.reorderCaptureActive = true; + UpdateUIDragDropPointer(state.dragState, state.pointerPosition, &dragDropResult); + if (dragDropResult.activated && !state.dragCaptureActive) { + state.dragCaptureActive = true; eventResult.requestPointerCapture = true; eventResult.dragStarted = true; eventResult.consumed = true; } - - SyncReorderPreview(state, layout, items, eventResult); - if (state.reorderDragState.active) { + SyncDragInfo(state, items, eventResult); + if (state.dragState.active) { eventResult.consumed = true; } } break; case UIInputEventType::PointerLeave: - if (state.reorderDragState.active) { - SyncReorderPreview(state, layout, items, eventResult); + if (state.dragState.active) { + SyncDragInfo(state, items, eventResult); eventResult.consumed = true; } break; - case UIInputEventType::PointerButtonDown: { + case UIInputEventType::PointerButtonDown: if (event.pointerButton != UIPointerButton::Left) { break; } @@ -448,14 +299,13 @@ UIEditorTabStripInteractionFrame UpdateUIEditorTabStripInteraction( state.tabStripState.focused = true; eventResult.consumed = true; if (eventResult.hitTarget.kind == UIEditorTabStripHitTargetKind::Tab) { - BeginTabReorder(state, items, eventResult.hitTarget.index); + BeginTabDrag(state, items, eventResult.hitTarget.index, metrics); } } else { state.tabStripState.focused = false; state.pressedTarget = {}; } break; - } case UIInputEventType::PointerButtonUp: { if (event.pointerButton != UIPointerButton::Left) { @@ -467,45 +317,27 @@ UIEditorTabStripInteractionFrame UpdateUIEditorTabStripInteraction( const bool matchedPressedTarget = AreEquivalentTargets(state.pressedTarget, eventResult.hitTarget); - if (state.reorderDragState.active || state.reorderCaptureActive) { + if (state.dragState.active || state.dragCaptureActive) { UIDragDropResult dragDropResult = {}; - EndUIDragDrop(state.reorderDragState, dragDropResult); + EndUIDragDrop(state.dragState, dragDropResult); - eventResult.dragEnded = state.reorderCaptureActive; - eventResult.releasePointerCapture = state.reorderCaptureActive; + eventResult.dragEnded = state.dragCaptureActive; + eventResult.releasePointerCapture = state.dragCaptureActive; eventResult.consumed = true; - eventResult.dragSourceIndex = state.reorderSourceIndex; - if (state.reorderSourceIndex < items.size()) { - eventResult.draggedTabId = items[state.reorderSourceIndex].tabId; - } - eventResult.dropInsertionIndex = - ResolveCommittedReorderInsertionIndex(state, layout); - eventResult.reorderToIndex = - ResolveReorderTargetIndex( - state.reorderSourceIndex, - eventResult.dropInsertionIndex, - items.size()); - eventResult.reorderPreviewIndex = state.reorderPreviewIndex; - if (dragDropResult.completed && - eventResult.dropInsertionIndex != UIEditorTabStripInvalidIndex && - eventResult.reorderToIndex != UIEditorTabStripInvalidIndex) { - eventResult.reorderRequested = - eventResult.dropInsertionIndex != state.reorderSourceIndex && - eventResult.dropInsertionIndex != state.reorderSourceIndex + 1u; - } else { + SyncDragInfo(state, items, eventResult); + if (!dragDropResult.completed) { eventResult.dragCanceled = true; } - ClearReorderState(state); + ClearDragState(state); state.pressedTarget = {}; break; } - if (state.reorderDragState.armed) { + if (state.dragState.armed) { UIDragDropResult dragDropResult = {}; - CancelUIDragDrop(state.reorderDragState, &dragDropResult); - state.reorderSourceIndex = UIEditorTabStripInvalidIndex; - state.reorderPreviewIndex = UIEditorTabStripInvalidIndex; + CancelUIDragDrop(state.dragState, &dragDropResult); + ClearDragState(state); } if (matchedPressedTarget) { @@ -547,9 +379,9 @@ UIEditorTabStripInteractionFrame UpdateUIEditorTabStripInteraction( } case UIInputEventType::KeyDown: - if (HasReorderInteraction(state) && + if (HasTabDragInteraction(state) && static_cast(event.keyCode) == KeyCode::Escape) { - CancelTabReorder(state, eventResult, items); + CancelTabDrag(state, eventResult, items); break; } @@ -581,24 +413,16 @@ UIEditorTabStripInteractionFrame UpdateUIEditorTabStripInteraction( HitTestUIEditorTabStrip(layout, state.tabStripState, state.pointerPosition); } - if (state.reorderDragState.active) { - SyncReorderPreview(state, layout, items, eventResult); - } - if (eventResult.consumed || eventResult.selectionChanged || - eventResult.closeRequested || eventResult.keyboardNavigated || eventResult.requestPointerCapture || eventResult.releasePointerCapture || eventResult.dragStarted || eventResult.dragEnded || eventResult.dragCanceled || - eventResult.reorderRequested || - eventResult.reorderPreviewActive || eventResult.hitTarget.kind != UIEditorTabStripHitTargetKind::None || !eventResult.selectedTabId.empty() || - !eventResult.closedTabId.empty() || !eventResult.draggedTabId.empty()) { interactionResult = std::move(eventResult); } @@ -612,9 +436,6 @@ UIEditorTabStripInteractionFrame UpdateUIEditorTabStripInteraction( interactionResult.hitTarget = HitTestUIEditorTabStrip(layout, state.tabStripState, state.pointerPosition); } - if (state.reorderDragState.active) { - SyncReorderPreview(state, layout, items, interactionResult); - } return { std::move(layout), diff --git a/new_editor/src/Docking/DockHostHitTest.cpp b/new_editor/src/Docking/DockHostHitTest.cpp index faefd0cc..24fb6b6e 100644 --- a/new_editor/src/Docking/DockHostHitTest.cpp +++ b/new_editor/src/Docking/DockHostHitTest.cpp @@ -38,11 +38,6 @@ UIEditorDockHostHitTarget MapPanelFrameHitTarget( return MakePanelHitTarget(UIEditorDockHostHitTargetKind::PanelBody, nodeId, panelId); case UIEditorPanelFrameHitTarget::Footer: return MakePanelHitTarget(UIEditorDockHostHitTargetKind::PanelFooter, nodeId, panelId); - case UIEditorPanelFrameHitTarget::CloseButton: - return MakePanelHitTarget( - UIEditorDockHostHitTargetKind::PanelCloseButton, - nodeId, - panelId); default: return {}; } @@ -68,16 +63,6 @@ UIEditorDockHostHitTarget HitTestUIEditorDockHost( const UIEditorTabStripHitTarget tabHit = HitTestUIEditorTabStrip(tabStack.tabStripLayout, tabStack.tabStripState, point); switch (tabHit.kind) { - case UIEditorTabStripHitTargetKind::CloseButton: { - UIEditorDockHostHitTarget target = {}; - target.kind = UIEditorDockHostHitTargetKind::TabCloseButton; - target.nodeId = tabStack.nodeId; - target.index = tabHit.index; - if (tabHit.index < tabStack.items.size()) { - target.panelId = tabStack.items[tabHit.index].panelId; - } - return target; - } case UIEditorTabStripHitTargetKind::Tab: { UIEditorDockHostHitTarget target = {}; target.kind = UIEditorDockHostHitTargetKind::Tab; diff --git a/new_editor/src/Docking/DockHostInteractionHelpers.cpp b/new_editor/src/Docking/DockHostInteractionHelpers.cpp index 7ee267c6..844efc2d 100644 --- a/new_editor/src/Docking/DockHostInteractionHelpers.cpp +++ b/new_editor/src/Docking/DockHostInteractionHelpers.cpp @@ -184,7 +184,6 @@ std::vector BuildTabStripItems( UIEditorTabStripItem item = {}; item.tabId = itemLayout.panelId; item.title = itemLayout.title; - item.closable = itemLayout.closable; items.push_back(std::move(item)); } @@ -208,12 +207,6 @@ UIEditorDockHostHitTarget MapTabStripHitTarget( target.panelId = tabStack.items[result.hitTarget.index].panelId; } break; - case UIEditorTabStripHitTargetKind::CloseButton: - target.kind = UIEditorDockHostHitTargetKind::TabCloseButton; - if (result.hitTarget.index < tabStack.items.size()) { - target.panelId = tabStack.items[result.hitTarget.index].panelId; - } - break; default: break; } @@ -222,17 +215,12 @@ UIEditorDockHostHitTarget MapTabStripHitTarget( } int ResolveTabStripPriority(const UIEditorTabStripInteractionResult& result) { - if (result.reorderRequested || - result.dragStarted || + if (result.dragStarted || result.dragEnded || result.dragCanceled) { return 5; } - if (result.closeRequested) { - return 4; - } - if (result.selectionChanged || result.keyboardNavigated) { return 3; } @@ -282,34 +270,15 @@ DockHostTabStripEventResult ProcessTabStripEvent( resolved.dragStarted = frame.result.dragStarted; resolved.dragEnded = frame.result.dragEnded; resolved.dragCanceled = frame.result.dragCanceled; - resolved.dropInsertionIndex = frame.result.dropInsertionIndex; resolved.draggedTabId = frame.result.draggedTabId; - if ((frame.result.closeRequested && !frame.result.closedTabId.empty()) || - (event.type == UIInputEventType::PointerButtonUp && - frame.result.consumed && - resolved.hitTarget.kind == UIEditorDockHostHitTargetKind::TabCloseButton && + if ((frame.result.selectionChanged || + frame.result.keyboardNavigated || + (event.type == UIInputEventType::PointerButtonUp && + frame.result.consumed && + resolved.hitTarget.kind == UIEditorDockHostHitTargetKind::Tab)) && + (!frame.result.selectedTabId.empty() || !resolved.hitTarget.panelId.empty())) { resolved.commandRequested = true; - resolved.commandKind = UIEditorWorkspaceCommandKind::ClosePanel; - resolved.panelId = - !frame.result.closedTabId.empty() - ? frame.result.closedTabId - : resolved.hitTarget.panelId; - } else if (frame.result.reorderRequested && - !frame.result.draggedTabId.empty() && - frame.result.dropInsertionIndex != - Widgets::UIEditorTabStripInvalidIndex) { - resolved.reorderRequested = true; - resolved.panelId = frame.result.draggedTabId; - resolved.dropInsertionIndex = frame.result.dropInsertionIndex; - } else if ((frame.result.selectionChanged || - frame.result.keyboardNavigated || - (event.type == UIInputEventType::PointerButtonUp && - frame.result.consumed && - resolved.hitTarget.kind == UIEditorDockHostHitTargetKind::Tab)) && - (!frame.result.selectedTabId.empty() || - !resolved.hitTarget.panelId.empty())) { - resolved.commandRequested = true; resolved.commandKind = UIEditorWorkspaceCommandKind::ActivatePanel; resolved.panelId = !frame.result.selectedTabId.empty() @@ -319,7 +288,6 @@ DockHostTabStripEventResult ProcessTabStripEvent( continue; } else { resolved.commandRequested = false; - resolved.reorderRequested = false; resolved.panelId.clear(); } diff --git a/new_editor/src/Docking/DockHostInteractionInternal.h b/new_editor/src/Docking/DockHostInteractionInternal.h index 3a01e836..36549025 100644 --- a/new_editor/src/Docking/DockHostInteractionInternal.h +++ b/new_editor/src/Docking/DockHostInteractionInternal.h @@ -7,14 +7,12 @@ namespace XCEngine::UI::Editor::Internal { struct DockHostTabStripEventResult { bool consumed = false; bool commandRequested = false; - bool reorderRequested = false; bool dragStarted = false; bool dragEnded = false; bool dragCanceled = false; bool requestPointerCapture = false; bool releasePointerCapture = false; UIEditorWorkspaceCommandKind commandKind = UIEditorWorkspaceCommandKind::ActivatePanel; - std::size_t dropInsertionIndex = Widgets::UIEditorTabStripInvalidIndex; std::string panelId = {}; std::string nodeId = {}; std::string draggedTabId = {}; diff --git a/new_editor/src/Docking/DockHostMeasure.cpp b/new_editor/src/Docking/DockHostMeasure.cpp index 197c30f0..ce8852c7 100644 --- a/new_editor/src/Docking/DockHostMeasure.cpp +++ b/new_editor/src/Docking/DockHostMeasure.cpp @@ -20,6 +20,7 @@ DockHostNodeMeasureResult MeasureDockHostTabStackNode( const UIEditorPanelRegistry& panelRegistry, const UIEditorWorkspaceSession& session, const Widgets::UIEditorDockHostMetrics& metrics) { + (void)panelRegistry; std::vector measureItems = {}; for (const UIEditorWorkspaceNode& child : node.children) { if (child.kind != UIEditorWorkspaceNodeKind::Panel || @@ -30,12 +31,6 @@ DockHostNodeMeasureResult MeasureDockHostTabStackNode( UIEditorTabStripItem item = {}; item.tabId = child.panel.panelId; item.title = child.panel.title; - if (const UIEditorPanelDescriptor* descriptor = - FindUIEditorPanelDescriptor(panelRegistry, child.panel.panelId); - descriptor != nullptr) { - item.closable = descriptor->canClose; - } - UITabStripMeasureItem measureItem = {}; measureItem.desiredHeaderLabelWidth = ResolveUIEditorTabStripDesiredHeaderLabelWidth(item, metrics.tabStripMetrics); diff --git a/new_editor/src/Docking/DockHostRendering.cpp b/new_editor/src/Docking/DockHostRendering.cpp index 1e9778b0..2cb4168c 100644 --- a/new_editor/src/Docking/DockHostRendering.cpp +++ b/new_editor/src/Docking/DockHostRendering.cpp @@ -58,16 +58,6 @@ void AppendUIEditorDockHostBackground( const UIEditorDockHostPalette& palette, const UIEditorDockHostMetrics& metrics) { for (const UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { - std::vector tabItems = {}; - tabItems.reserve(tabStack.items.size()); - for (const UIEditorDockHostTabItemLayout& item : tabStack.items) { - UIEditorTabStripItem tabItem = {}; - tabItem.tabId = item.panelId; - tabItem.title = item.title; - tabItem.closable = item.closable; - tabItems.push_back(std::move(tabItem)); - } - AppendUIEditorTabStripBackground( drawList, tabStack.tabStripLayout, @@ -97,7 +87,6 @@ void AppendUIEditorDockHostForeground( UIEditorTabStripItem tabItem = {}; tabItem.tabId = item.panelId; tabItem.title = item.title; - tabItem.closable = item.closable; tabItems.push_back(std::move(tabItem)); } diff --git a/new_editor/src/Docking/UIEditorDockHost.cpp b/new_editor/src/Docking/UIEditorDockHost.cpp index 264ff520..72196064 100644 --- a/new_editor/src/Docking/UIEditorDockHost.cpp +++ b/new_editor/src/Docking/UIEditorDockHost.cpp @@ -23,12 +23,6 @@ bool IsPanelOpenAndVisible( return Internal::IsWorkspacePanelOpenAndVisible(session, panelId); } -const UIEditorPanelDescriptor* FindPanelDescriptor( - const UIEditorPanelRegistry& panelRegistry, - std::string_view panelId) { - return FindUIEditorPanelDescriptor(panelRegistry, panelId); -} - std::size_t ResolveSelectedVisibleTabIndex( const UIEditorWorkspaceNode& node, const std::vector& visibleChildIndices) { @@ -78,10 +72,6 @@ UIEditorTabStripState BuildTabStripState( case UIEditorDockHostHitTargetKind::Tab: tabState.hoveredIndex = state.hoveredTarget.index; break; - case UIEditorDockHostHitTargetKind::TabCloseButton: - tabState.hoveredIndex = state.hoveredTarget.index; - tabState.closeHoveredIndex = state.hoveredTarget.index; - break; default: break; } @@ -135,6 +125,7 @@ void LayoutTabStackNode( const UIEditorDockHostState& state, const UIEditorDockHostMetrics& metrics, UIEditorDockHostLayout& layout) { + (void)panelRegistry; std::vector visibleChildIndices = {}; visibleChildIndices.reserve(node.children.size()); @@ -156,15 +147,11 @@ void LayoutTabStackNode( itemLayout.panelId = child.panel.panelId; itemLayout.title = child.panel.title; itemLayout.active = workspace.activePanelId == child.panel.panelId; - const UIEditorPanelDescriptor* descriptor = - FindPanelDescriptor(panelRegistry, child.panel.panelId); - itemLayout.closable = descriptor != nullptr ? descriptor->canClose : true; tabStackLayout.items.push_back(itemLayout); UIEditorTabStripItem tabItem = {}; tabItem.tabId = itemLayout.panelId; tabItem.title = itemLayout.title; - tabItem.closable = itemLayout.closable; tabStripItems.push_back(std::move(tabItem)); } diff --git a/new_editor/src/Docking/UIEditorDockHostInteraction.cpp b/new_editor/src/Docking/UIEditorDockHostInteraction.cpp index e119a171..c10bb8a5 100644 --- a/new_editor/src/Docking/UIEditorDockHostInteraction.cpp +++ b/new_editor/src/Docking/UIEditorDockHostInteraction.cpp @@ -238,31 +238,6 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( break; } - if (tabStripResult.reorderRequested && - !tabStripResult.nodeId.empty() && - !tabStripResult.draggedTabId.empty() && - tabStripResult.dropInsertionIndex != Widgets::UIEditorTabStripInvalidIndex) { - { - std::ostringstream trace = {}; - trace << "same-stack reorder node=" << tabStripResult.nodeId - << " panel=" << tabStripResult.draggedTabId - << " insertion=" << tabStripResult.dropInsertionIndex; - AppendUIEditorRuntimeTrace("dock", trace.str()); - } - eventResult.layoutResult = controller.ReorderTab( - tabStripResult.nodeId, - tabStripResult.draggedTabId, - tabStripResult.dropInsertionIndex); - eventResult.layoutChanged = - eventResult.layoutResult.status == - UIEditorWorkspaceLayoutOperationStatus::Changed; - eventResult.consumed = true; - eventResult.hitTarget = tabStripResult.hitTarget; - Internal::ClearTabDockDragState(state); - state.dockHostState.focused = true; - break; - } - if (state.dockHostState.dropPreview.visible && !state.activeTabDragNodeId.empty() && !state.activeTabDragPanelId.empty()) { @@ -381,20 +356,7 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( state.dockHostState.focused = true; break; - case UIEditorDockHostHitTargetKind::PanelCloseButton: - eventResult.commandResult = Internal::DispatchPanelCommand( - controller, - UIEditorWorkspaceCommandKind::ClosePanel, - state.dockHostState.hoveredTarget.panelId); - eventResult.commandExecuted = - eventResult.commandResult.status != - UIEditorWorkspaceCommandStatus::Rejected; - eventResult.consumed = true; - state.dockHostState.focused = true; - break; - case UIEditorDockHostHitTargetKind::Tab: - case UIEditorDockHostHitTargetKind::TabCloseButton: case UIEditorDockHostHitTargetKind::TabStripBackground: state.dockHostState.focused = true; eventResult.consumed = diff --git a/new_editor/src/Foundation/UIEditorTheme.cpp b/new_editor/src/Foundation/UIEditorTheme.cpp index 1f8d8d19..b2855516 100644 --- a/new_editor/src/Foundation/UIEditorTheme.cpp +++ b/new_editor/src/Foundation/UIEditorTheme.cpp @@ -73,16 +73,34 @@ Widgets::UIEditorListViewPalette BuildListViewPalette() { Widgets::UIEditorTreeViewPalette BuildTreeViewPalette() { Widgets::UIEditorTreeViewPalette palette = {}; palette.surfaceColor = kSurfaceBase; - palette.borderColor = kBorderDefault; - palette.focusedBorderColor = kBorderFocused; - palette.rowHoverColor = kSurfaceHover; + palette.borderColor = kSurfaceBase; + palette.focusedBorderColor = kSurfaceBase; + palette.rowHoverColor = UIColor(0.13f, 0.13f, 0.13f, 1.0f); palette.rowSelectedColor = kSurfaceActive; - palette.rowSelectedFocusedColor = kSurfaceActiveStrong; + palette.rowSelectedFocusedColor = kSurfaceActive; palette.disclosureColor = kTextMuted; palette.textColor = kTextPrimary; return palette; } +Widgets::UIEditorTreeViewMetrics BuildTreeViewMetrics() { + Widgets::UIEditorTreeViewMetrics metrics = {}; + metrics.rowHeight = 20.0f; + metrics.rowGap = 0.0f; + metrics.horizontalPadding = 6.0f; + metrics.indentWidth = 14.0f; + metrics.disclosureExtent = 18.0f; + metrics.disclosureLabelGap = 2.0f; + metrics.iconExtent = 18.0f; + metrics.iconLabelGap = 2.0f; + metrics.iconInsetY = -1.0f; + metrics.labelInsetY = 0.0f; + metrics.cornerRounding = 0.0f; + metrics.borderThickness = 0.0f; + metrics.focusedBorderThickness = 0.0f; + return metrics; +} + Widgets::UIEditorScrollViewPalette BuildScrollViewPalette() { Widgets::UIEditorScrollViewPalette palette = {}; palette.surfaceColor = kSurfaceBase; @@ -100,9 +118,7 @@ Widgets::UIEditorTabStripPalette BuildTabStripPalette() { palette.stripBackgroundColor = kSurfaceBase; palette.headerBackgroundColor = kSurfaceRaised; palette.contentBackgroundColor = kSurfaceBase; - palette.stripBorderColor = kBorderDefault; palette.headerContentSeparatorColor = kBorderDefault; - palette.focusedBorderColor = kBorderFocused; palette.tabColor = kSurfaceRaised; palette.tabHoveredColor = kSurfaceHover; palette.tabSelectedColor = kSurfaceRaised; @@ -111,12 +127,6 @@ Widgets::UIEditorTabStripPalette BuildTabStripPalette() { palette.tabSelectedBorderColor = kBorderDefault; palette.textPrimary = kTextPrimary; palette.textSecondary = kTextSecondary; - palette.textMuted = kTextMuted; - palette.closeButtonColor = kSurfaceRaised; - palette.closeButtonHoveredColor = kSurfaceHover; - palette.closeButtonBorderColor = kBorderDefault; - palette.closeGlyphColor = UIColor(0.86f, 0.86f, 0.86f, 1.0f); - palette.reorderPreviewColor = UIColor(0.92f, 0.92f, 0.92f, 0.90f); return palette; } @@ -276,7 +286,6 @@ XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(AssetFieldMetrics, Widgets::UIEditorAssetFie XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(AssetFieldPalette, Widgets::UIEditorAssetFieldPalette) XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(MenuPopupMetrics, Widgets::UIEditorMenuPopupMetrics) XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ListViewMetrics, Widgets::UIEditorListViewMetrics) -XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(TreeViewMetrics, Widgets::UIEditorTreeViewMetrics) XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(ScrollViewMetrics, Widgets::UIEditorScrollViewMetrics) XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(TabStripMetrics, Widgets::UIEditorTabStripMetrics) XCUIEDITOR_DEFINE_DEFAULT_ACCESSORS(MenuBarMetrics, Widgets::UIEditorMenuBarMetrics) @@ -303,6 +312,11 @@ const Widgets::UIEditorTreeViewPalette& ResolveUIEditorTreeViewPalette() { return palette; } +const Widgets::UIEditorTreeViewMetrics& ResolveUIEditorTreeViewMetrics() { + static const Widgets::UIEditorTreeViewMetrics metrics = BuildTreeViewMetrics(); + return metrics; +} + const Widgets::UIEditorScrollViewPalette& ResolveUIEditorScrollViewPalette() { static const Widgets::UIEditorScrollViewPalette palette = BuildScrollViewPalette(); return palette; diff --git a/new_editor/src/Panels/UIEditorPanelHostLifecycle.cpp b/new_editor/src/Panels/UIEditorPanelHostLifecycle.cpp index 6e76bf46..a0d13657 100644 --- a/new_editor/src/Panels/UIEditorPanelHostLifecycle.cpp +++ b/new_editor/src/Panels/UIEditorPanelHostLifecycle.cpp @@ -1,4 +1,5 @@ #include +#include #include #include diff --git a/new_editor/src/Shell/UIEditorShellAsset.cpp b/new_editor/src/Shell/UIEditorShellAsset.cpp index d5e7f341..5dec3bd7 100644 --- a/new_editor/src/Shell/UIEditorShellAsset.cpp +++ b/new_editor/src/Shell/UIEditorShellAsset.cpp @@ -1,4 +1,5 @@ #include +#include #include #include diff --git a/new_editor/src/Widgets/UIEditorColorUtils.cpp b/new_editor/src/Widgets/UIEditorColorUtils.cpp new file mode 100644 index 00000000..a8420fd8 --- /dev/null +++ b/new_editor/src/Widgets/UIEditorColorUtils.cpp @@ -0,0 +1,168 @@ +#include + +#include +#include +#include + +namespace XCEngine::UI::Editor::Widgets { + +float ClampUIEditorColorUnit(float value) { + return (std::clamp)(value, 0.0f, 1.0f); +} + +int ToUIEditorColorByte(float value) { + return static_cast(std::lround(ClampUIEditorColorUnit(value) * 255.0f)); +} + +UIEditorHsvColor ConvertUIEditorColorToHsv( + const ::XCEngine::UI::UIColor& color, + float fallbackHue) { + const float red = ClampUIEditorColorUnit(color.r); + const float green = ClampUIEditorColorUnit(color.g); + const float blue = ClampUIEditorColorUnit(color.b); + const float maxChannel = (std::max)({ red, green, blue }); + const float minChannel = (std::min)({ red, green, blue }); + const float delta = maxChannel - minChannel; + + UIEditorHsvColor hsv = {}; + hsv.hue = ClampUIEditorColorUnit(fallbackHue); + hsv.saturation = maxChannel <= 0.0f ? 0.0f : delta / maxChannel; + hsv.value = maxChannel; + hsv.alpha = ClampUIEditorColorUnit(color.a); + + if (delta <= 0.00001f) { + return hsv; + } + + if (maxChannel == red) { + hsv.hue = std::fmod(((green - blue) / delta), 6.0f) / 6.0f; + } else if (maxChannel == green) { + hsv.hue = (((blue - red) / delta) + 2.0f) / 6.0f; + } else { + hsv.hue = (((red - green) / delta) + 4.0f) / 6.0f; + } + + if (hsv.hue < 0.0f) { + hsv.hue += 1.0f; + } + return hsv; +} + +::XCEngine::UI::UIColor ConvertUIEditorHsvToColor(const UIEditorHsvColor& hsv) { + const float hue = ClampUIEditorColorUnit(hsv.hue); + const float saturation = ClampUIEditorColorUnit(hsv.saturation); + const float value = ClampUIEditorColorUnit(hsv.value); + + if (saturation <= 0.00001f) { + return ::XCEngine::UI::UIColor(value, value, value, ClampUIEditorColorUnit(hsv.alpha)); + } + + const float sector = hue * 6.0f; + const int sectorIndex = static_cast(std::floor(sector)) % 6; + const float fraction = sector - std::floor(sector); + const float p = value * (1.0f - saturation); + const float q = value * (1.0f - saturation * fraction); + const float t = value * (1.0f - saturation * (1.0f - fraction)); + + float red = value; + float green = t; + float blue = p; + switch (sectorIndex) { + case 0: + red = value; + green = t; + blue = p; + break; + case 1: + red = q; + green = value; + blue = p; + break; + case 2: + red = p; + green = value; + blue = t; + break; + case 3: + red = p; + green = q; + blue = value; + break; + case 4: + red = t; + green = p; + blue = value; + break; + case 5: + default: + red = value; + green = p; + blue = q; + break; + } + + return ::XCEngine::UI::UIColor(red, green, blue, ClampUIEditorColorUnit(hsv.alpha)); +} + +UIEditorHsvColor ResolveUIEditorDisplayHsv( + const ::XCEngine::UI::UIColor& color, + float rememberedHue, + bool hueValid) { + UIEditorHsvColor hsv = ConvertUIEditorColorToHsv(color, hueValid ? rememberedHue : 0.0f); + if (hsv.saturation <= 0.00001f && hueValid) { + hsv.hue = ClampUIEditorColorUnit(rememberedHue); + } + return hsv; +} + +std::string FormatUIEditorColorHex( + const ::XCEngine::UI::UIColor& color, + bool includeAlpha) { + char buffer[16] = {}; + if (includeAlpha) { + std::snprintf( + buffer, + sizeof(buffer), + "#%02X%02X%02X%02X", + ToUIEditorColorByte(color.r), + ToUIEditorColorByte(color.g), + ToUIEditorColorByte(color.b), + ToUIEditorColorByte(color.a)); + } else { + std::snprintf( + buffer, + sizeof(buffer), + "#%02X%02X%02X", + ToUIEditorColorByte(color.r), + ToUIEditorColorByte(color.g), + ToUIEditorColorByte(color.b)); + } + return std::string(buffer); +} + +std::string FormatUIEditorColorChannelsText( + const ::XCEngine::UI::UIColor& color, + bool includeAlpha) { + char buffer[64] = {}; + if (includeAlpha) { + std::snprintf( + buffer, + sizeof(buffer), + "RGBA %d, %d, %d, %d", + ToUIEditorColorByte(color.r), + ToUIEditorColorByte(color.g), + ToUIEditorColorByte(color.b), + ToUIEditorColorByte(color.a)); + } else { + std::snprintf( + buffer, + sizeof(buffer), + "RGB %d, %d, %d", + ToUIEditorColorByte(color.r), + ToUIEditorColorByte(color.g), + ToUIEditorColorByte(color.b)); + } + return std::string(buffer); +} + +} // namespace XCEngine::UI::Editor::Widgets diff --git a/new_editor/src/Workspace/SplitterDragCorrection/Internal.h b/new_editor/src/Workspace/SplitterDragCorrection/Internal.h index 6a064644..5faf9935 100644 --- a/new_editor/src/Workspace/SplitterDragCorrection/Internal.h +++ b/new_editor/src/Workspace/SplitterDragCorrection/Internal.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include "Docking/DockHostMeasureInternal.h" diff --git a/new_editor/src/Workspace/UIEditorWindowWorkspaceController.cpp b/new_editor/src/Workspace/UIEditorWindowWorkspaceController.cpp index bb7da8c3..8954b0b2 100644 --- a/new_editor/src/Workspace/UIEditorWindowWorkspaceController.cpp +++ b/new_editor/src/Workspace/UIEditorWindowWorkspaceController.cpp @@ -1,5 +1,6 @@ #include +#include #include #include diff --git a/new_editor/src/Workspace/UIEditorWindowWorkspaceModel.cpp b/new_editor/src/Workspace/UIEditorWindowWorkspaceModel.cpp index 1524769b..a7a466e5 100644 --- a/new_editor/src/Workspace/UIEditorWindowWorkspaceModel.cpp +++ b/new_editor/src/Workspace/UIEditorWindowWorkspaceModel.cpp @@ -1,4 +1,5 @@ #include +#include #include #include diff --git a/new_editor/src/Workspace/UIEditorWorkspaceLayoutPersistence.cpp b/new_editor/src/Workspace/UIEditorWorkspaceLayoutPersistence.cpp index d488e65f..ea05a887 100644 --- a/new_editor/src/Workspace/UIEditorWorkspaceLayoutPersistence.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceLayoutPersistence.cpp @@ -1,4 +1,5 @@ #include +#include #include #include diff --git a/new_editor/src/Workspace/UIEditorWorkspaceSession.cpp b/new_editor/src/Workspace/UIEditorWorkspaceSession.cpp index ffbb41f5..bc77ad6b 100644 --- a/new_editor/src/Workspace/UIEditorWorkspaceSession.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceSession.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include diff --git a/new_editor/src/Workspace/UIEditorWorkspaceTransfer.cpp b/new_editor/src/Workspace/UIEditorWorkspaceTransfer.cpp index d7bc0adc..79c80831 100644 --- a/new_editor/src/Workspace/UIEditorWorkspaceTransfer.cpp +++ b/new_editor/src/Workspace/UIEditorWorkspaceTransfer.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include diff --git a/new_editor/src/Workspace/WorkspaceControllerDispatch.cpp b/new_editor/src/Workspace/WorkspaceControllerDispatch.cpp index ed2a5baa..12225142 100644 --- a/new_editor/src/Workspace/WorkspaceControllerDispatch.cpp +++ b/new_editor/src/Workspace/WorkspaceControllerDispatch.cpp @@ -1,4 +1,5 @@ #include +#include namespace XCEngine::UI::Editor { diff --git a/new_editor/src/Workspace/WorkspaceControllerInternal.h b/new_editor/src/Workspace/WorkspaceControllerInternal.h index a65170e5..3b573b34 100644 --- a/new_editor/src/Workspace/WorkspaceControllerInternal.h +++ b/new_editor/src/Workspace/WorkspaceControllerInternal.h @@ -1,6 +1,10 @@ #pragma once #include +#include +#include +#include + #include #include #include diff --git a/new_editor/src/Workspace/WorkspaceControllerLayoutOps.cpp b/new_editor/src/Workspace/WorkspaceControllerLayoutOps.cpp index 08fc0e3c..8d2161b7 100644 --- a/new_editor/src/Workspace/WorkspaceControllerLayoutOps.cpp +++ b/new_editor/src/Workspace/WorkspaceControllerLayoutOps.cpp @@ -129,94 +129,6 @@ UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::SetSplitRati "Split ratio updated."); } -UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::ReorderTab( - std::string_view nodeId, - std::string_view panelId, - std::size_t targetVisibleInsertionIndex) { - const UIEditorWorkspaceControllerValidationResult validation = ValidateState(); - if (!validation.IsValid()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "Controller state invalid: " + validation.message); - } - - if (nodeId.empty()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "ReorderTab requires a tab stack node id."); - } - - if (panelId.empty()) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "ReorderTab requires a panel id."); - } - - const UIEditorWorkspaceNode* tabStack = FindUIEditorWorkspaceNode(m_workspace, nodeId); - if (tabStack == nullptr || tabStack->kind != UIEditorWorkspaceNodeKind::TabStack) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "ReorderTab target tab stack is missing."); - } - - const Internal::VisibleTabStackInfo tabInfo = - Internal::ResolveVisibleTabStackInfo(*tabStack, m_session, panelId); - if (!tabInfo.panelExists) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "ReorderTab target panel is missing from the specified tab stack."); - } - - if (!tabInfo.panelVisible) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "ReorderTab only supports open and visible tabs."); - } - - if (targetVisibleInsertionIndex > tabInfo.visibleTabCount) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "ReorderTab target visible insertion index is out of range."); - } - - if (targetVisibleInsertionIndex == tabInfo.currentVisibleIndex || - targetVisibleInsertionIndex == tabInfo.currentVisibleIndex + 1u) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::NoOp, - "Visible tab order already matches the requested insertion."); - } - - const UIEditorWorkspaceModel previousWorkspace = m_workspace; - if (!TryReorderUIEditorWorkspaceTab( - m_workspace, - m_session, - nodeId, - panelId, - targetVisibleInsertionIndex)) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "Tab reorder rejected."); - } - - if (AreUIEditorWorkspaceModelsEquivalent(previousWorkspace, m_workspace)) { - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::NoOp, - "Visible tab order already matches the requested insertion."); - } - - const UIEditorWorkspaceControllerValidationResult postValidation = ValidateState(); - if (!postValidation.IsValid()) { - m_workspace = previousWorkspace; - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Rejected, - "Tab reorder produced invalid controller state: " + postValidation.message); - } - - return BuildLayoutOperationResult( - UIEditorWorkspaceLayoutOperationStatus::Changed, - "Tab reordered."); -} - UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::MoveTabToStack( std::string_view sourceNodeId, std::string_view panelId, @@ -251,7 +163,9 @@ UIEditorWorkspaceLayoutOperationResult UIEditorWorkspaceController::MoveTabToSta } if (sourceNodeId == targetNodeId) { - return ReorderTab(sourceNodeId, panelId, targetVisibleInsertionIndex); + return BuildLayoutOperationResult( + UIEditorWorkspaceLayoutOperationStatus::Rejected, + "MoveTabToStack requires distinct source and target tab stack ids."); } const UIEditorWorkspaceNode* sourceTabStack = diff --git a/new_editor/src/Workspace/WorkspaceModelInternal.h b/new_editor/src/Workspace/WorkspaceModelInternal.h index 4740d189..2ee47ee4 100644 --- a/new_editor/src/Workspace/WorkspaceModelInternal.h +++ b/new_editor/src/Workspace/WorkspaceModelInternal.h @@ -2,7 +2,9 @@ #include #include +#include #include +#include #include #include diff --git a/new_editor/src/Workspace/WorkspaceModelMutation.cpp b/new_editor/src/Workspace/WorkspaceModelMutation.cpp index cb587201..ab5cb676 100644 --- a/new_editor/src/Workspace/WorkspaceModelMutation.cpp +++ b/new_editor/src/Workspace/WorkspaceModelMutation.cpp @@ -38,101 +38,6 @@ bool TrySetUIEditorWorkspaceSplitRatio( return true; } -bool TryReorderUIEditorWorkspaceTab( - UIEditorWorkspaceModel& workspace, - const UIEditorWorkspaceSession& session, - std::string_view nodeId, - std::string_view panelId, - std::size_t targetVisibleInsertionIndex) { - UIEditorWorkspaceNode* node = - Internal::FindMutableNodeRecursive(workspace.root, nodeId); - if (node == nullptr || node->kind != UIEditorWorkspaceNodeKind::TabStack) { - return false; - } - - std::vector visibleChildIndices = {}; - std::vector reorderedVisibleChildren = {}; - visibleChildIndices.reserve(node->children.size()); - reorderedVisibleChildren.reserve(node->children.size()); - - std::size_t sourceVisibleIndex = node->children.size(); - for (std::size_t index = 0; index < node->children.size(); ++index) { - const UIEditorWorkspaceNode& child = node->children[index]; - if (child.kind != UIEditorWorkspaceNodeKind::Panel) { - return false; - } - - if (!Internal::IsPanelOpenAndVisibleInSession(session, child.panel.panelId)) { - continue; - } - - if (child.panel.panelId == panelId) { - sourceVisibleIndex = visibleChildIndices.size(); - } - - visibleChildIndices.push_back(index); - reorderedVisibleChildren.push_back(child); - } - - if (sourceVisibleIndex >= reorderedVisibleChildren.size() || - targetVisibleInsertionIndex > reorderedVisibleChildren.size()) { - return false; - } - - if (targetVisibleInsertionIndex == sourceVisibleIndex || - targetVisibleInsertionIndex == sourceVisibleIndex + 1u) { - return false; - } - - UIEditorWorkspaceNode movedChild = - std::move(reorderedVisibleChildren[sourceVisibleIndex]); - reorderedVisibleChildren.erase( - reorderedVisibleChildren.begin() + - static_cast(sourceVisibleIndex)); - - std::size_t adjustedInsertionIndex = targetVisibleInsertionIndex; - if (adjustedInsertionIndex > sourceVisibleIndex) { - --adjustedInsertionIndex; - } - if (adjustedInsertionIndex > reorderedVisibleChildren.size()) { - adjustedInsertionIndex = reorderedVisibleChildren.size(); - } - - reorderedVisibleChildren.insert( - reorderedVisibleChildren.begin() + - static_cast(adjustedInsertionIndex), - std::move(movedChild)); - - std::string selectedPanelId = {}; - if (node->selectedTabIndex < node->children.size()) { - selectedPanelId = node->children[node->selectedTabIndex].panel.panelId; - } - - const std::vector originalChildren = node->children; - std::size_t nextVisibleIndex = 0u; - for (std::size_t index = 0; index < originalChildren.size(); ++index) { - const UIEditorWorkspaceNode& originalChild = originalChildren[index]; - if (!Internal::IsPanelOpenAndVisibleInSession( - session, - originalChild.panel.panelId)) { - node->children[index] = originalChild; - continue; - } - - node->children[index] = reorderedVisibleChildren[nextVisibleIndex]; - ++nextVisibleIndex; - } - - for (std::size_t index = 0; index < node->children.size(); ++index) { - if (node->children[index].panel.panelId == selectedPanelId) { - node->selectedTabIndex = index; - break; - } - } - - return true; -} - bool TryExtractUIEditorWorkspaceVisiblePanelNode( UIEditorWorkspaceModel& workspace, const UIEditorWorkspaceSession& session, @@ -287,12 +192,7 @@ bool TryMoveUIEditorWorkspaceTabToStack( } if (sourceNodeId == targetNodeId) { - return TryReorderUIEditorWorkspaceTab( - workspace, - session, - sourceNodeId, - panelId, - targetVisibleInsertionIndex); + return false; } const UIEditorWorkspaceNode* targetNode = diff --git a/tests/UI/Editor/integration/CMakeLists.txt b/tests/UI/Editor/integration/CMakeLists.txt index f21e0e70..908ef04c 100644 --- a/tests/UI/Editor/integration/CMakeLists.txt +++ b/tests/UI/Editor/integration/CMakeLists.txt @@ -148,11 +148,6 @@ if(TARGET editor_ui_scroll_view_basic_validation) editor_ui_scroll_view_basic_validation) endif() -if(TARGET editor_ui_dock_tab_reorder_same_stack_validation) - list(APPEND EDITOR_UI_INTEGRATION_TARGETS - editor_ui_dock_tab_reorder_same_stack_validation) -endif() - add_custom_target(editor_ui_integration_tests DEPENDS ${EDITOR_UI_INTEGRATION_TARGETS} diff --git a/tests/UI/Editor/integration/shared/src/Application.h b/tests/UI/Editor/integration/shared/src/Application.h index 63f65f8e..7e3d0613 100644 --- a/tests/UI/Editor/integration/shared/src/Application.h +++ b/tests/UI/Editor/integration/shared/src/Application.h @@ -6,9 +6,9 @@ #include "EditorValidationScenario.h" -#include "Host/AutoScreenshot.h" -#include "Host/InputModifierTracker.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Platform/Win32/InputModifierTracker.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/CMakeLists.txt b/tests/UI/Editor/integration/shell/CMakeLists.txt index 0e45003d..4b6095fa 100644 --- a/tests/UI/Editor/integration/shell/CMakeLists.txt +++ b/tests/UI/Editor/integration/shell/CMakeLists.txt @@ -13,9 +13,6 @@ endif() if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/dock_host_basic/CMakeLists.txt") add_subdirectory(dock_host_basic) endif() -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/dock_tab_reorder_same_stack/CMakeLists.txt") - add_subdirectory(dock_tab_reorder_same_stack) -endif() if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/panel_content_host_basic/CMakeLists.txt") add_subdirectory(panel_content_host_basic) endif() diff --git a/tests/UI/Editor/integration/shell/asset_field_basic/main.cpp b/tests/UI/Editor/integration/shell/asset_field_basic/main.cpp index 0cc3e61f..7c7f9e13 100644 --- a/tests/UI/Editor/integration/shell/asset_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/asset_field_basic/main.cpp @@ -1,4 +1,4 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif @@ -6,8 +6,8 @@ #include #include #include "EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/bool_field_basic/main.cpp b/tests/UI/Editor/integration/shell/bool_field_basic/main.cpp index 1f0e9705..88a394e1 100644 --- a/tests/UI/Editor/integration/shell/bool_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/bool_field_basic/main.cpp @@ -1,4 +1,4 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif @@ -6,8 +6,8 @@ #include #include #include "EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/color_field_basic/main.cpp b/tests/UI/Editor/integration/shell/color_field_basic/main.cpp index e7b50747..def04ca4 100644 --- a/tests/UI/Editor/integration/shell/color_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/color_field_basic/main.cpp @@ -1,4 +1,4 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif @@ -6,8 +6,8 @@ #include #include #include "EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include diff --git a/tests/UI/Editor/integration/shell/context_menu_basic/main.cpp b/tests/UI/Editor/integration/shell/context_menu_basic/main.cpp index 34697e92..ad73be84 100644 --- a/tests/UI/Editor/integration/shell/context_menu_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/context_menu_basic/main.cpp @@ -3,14 +3,14 @@ #endif #include -#include -#include +#include +#include #include -#include -#include -#include -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include +#include +#include +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/dock_host_basic/main.cpp b/tests/UI/Editor/integration/shell/dock_host_basic/main.cpp index 8723d9b5..8545f99c 100644 --- a/tests/UI/Editor/integration/shell/dock_host_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/dock_host_basic/main.cpp @@ -2,12 +2,12 @@ #define NOMINMAX #endif -#include -#include -#include -#include -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include +#include +#include +#include +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include @@ -124,16 +124,12 @@ std::string DescribeHitTarget(const UIEditorDockHostHitTarget& target) { return "TabStripBackground: " + target.nodeId; case UIEditorDockHostHitTargetKind::Tab: return "Tab: " + target.panelId; - case UIEditorDockHostHitTargetKind::TabCloseButton: - return "TabClose: " + target.panelId; case UIEditorDockHostHitTargetKind::PanelHeader: return "PanelHeader: " + target.panelId; case UIEditorDockHostHitTargetKind::PanelBody: return "PanelBody: " + target.panelId; case UIEditorDockHostHitTargetKind::PanelFooter: return "PanelFooter: " + target.panelId; - case UIEditorDockHostHitTargetKind::PanelCloseButton: - return "PanelClose: " + target.panelId; case UIEditorDockHostHitTargetKind::None: default: return "None"; diff --git a/tests/UI/Editor/integration/shell/dock_tab_reorder_same_stack/CMakeLists.txt b/tests/UI/Editor/integration/shell/dock_tab_reorder_same_stack/CMakeLists.txt deleted file mode 100644 index 8738a30e..00000000 --- a/tests/UI/Editor/integration/shell/dock_tab_reorder_same_stack/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ -add_executable(editor_ui_dock_tab_reorder_same_stack_validation WIN32 - main.cpp -) - -target_include_directories(editor_ui_dock_tab_reorder_same_stack_validation PRIVATE - ${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/include - ${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT} - ${CMAKE_SOURCE_DIR}/engine/include -) - -target_compile_definitions(editor_ui_dock_tab_reorder_same_stack_validation PRIVATE - UNICODE - _UNICODE - XCENGINE_EDITOR_UI_TESTS_REPO_ROOT="${XCENGINE_EDITOR_UI_TESTS_REPO_ROOT_PATH}" -) - -if(MSVC) - target_compile_options(editor_ui_dock_tab_reorder_same_stack_validation PRIVATE /utf-8 /FS) - set_property(TARGET editor_ui_dock_tab_reorder_same_stack_validation PROPERTY - MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") -endif() - -target_link_libraries(editor_ui_dock_tab_reorder_same_stack_validation PRIVATE - XCUIEditorLib - XCUIEditorHost -) - -set_target_properties(editor_ui_dock_tab_reorder_same_stack_validation PROPERTIES - OUTPUT_NAME "XCUIEditorDockTabReorderSameStackValidation" -) diff --git a/tests/UI/Editor/integration/shell/dock_tab_reorder_same_stack/main.cpp b/tests/UI/Editor/integration/shell/dock_tab_reorder_same_stack/main.cpp deleted file mode 100644 index 4f41876e..00000000 --- a/tests/UI/Editor/integration/shell/dock_tab_reorder_same_stack/main.cpp +++ /dev/null @@ -1,680 +0,0 @@ -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include -#include -#include -#include -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -#ifndef XCENGINE_EDITOR_UI_TESTS_REPO_ROOT -#define XCENGINE_EDITOR_UI_TESTS_REPO_ROOT "." -#endif - -namespace { - -using XCEngine::Input::KeyCode; -using XCEngine::UI::UIColor; -using XCEngine::UI::UIDrawData; -using XCEngine::UI::UIDrawList; -using XCEngine::UI::UIInputEvent; -using XCEngine::UI::UIInputEventType; -using XCEngine::UI::UIPoint; -using XCEngine::UI::UIPointerButton; -using XCEngine::UI::UIRect; -using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceController; -using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel; -using XCEngine::UI::Editor::BuildUIEditorWorkspaceSingleTabStack; -using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit; -using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack; -using XCEngine::UI::Editor::FindUIEditorWorkspaceNode; -using XCEngine::UI::Editor::Host::AutoScreenshotController; -using XCEngine::UI::Editor::Host::NativeRenderer; -using XCEngine::UI::Editor::UIEditorDockHostInteractionFrame; -using XCEngine::UI::Editor::UIEditorDockHostInteractionResult; -using XCEngine::UI::Editor::UIEditorDockHostInteractionState; -using XCEngine::UI::Editor::UIEditorDockHostTabStripInteractionEntry; -using XCEngine::UI::Editor::UIEditorPanelRegistry; -using XCEngine::UI::Editor::UIEditorWorkspaceController; -using XCEngine::UI::Editor::UIEditorWorkspaceLayoutOperationStatus; -using XCEngine::UI::Editor::UIEditorWorkspaceModel; -using XCEngine::UI::Editor::UIEditorWorkspaceSplitAxis; -using XCEngine::UI::Editor::UpdateUIEditorDockHostInteraction; -using XCEngine::UI::Editor::Widgets::AppendUIEditorDockHostBackground; -using XCEngine::UI::Editor::Widgets::AppendUIEditorDockHostForeground; -using XCEngine::UI::Editor::Widgets::UIEditorDockHostHitTarget; -using XCEngine::UI::Editor::Widgets::UIEditorDockHostHitTargetKind; -using XCEngine::UI::Editor::Widgets::UIEditorDockHostTabStackLayout; - -constexpr const wchar_t* kWindowClassName = L"XCUIEditorDockTabReorderSameStackValidation"; -constexpr const wchar_t* kWindowTitle = L"XCUI Editor | Dock Tab Reorder Same Stack"; - -constexpr UIColor kWindowBg(0.10f, 0.10f, 0.10f, 1.0f); -constexpr UIColor kCardBg(0.16f, 0.16f, 0.16f, 1.0f); -constexpr UIColor kCardBorder(0.28f, 0.28f, 0.28f, 1.0f); -constexpr UIColor kPreviewBg(0.12f, 0.12f, 0.12f, 1.0f); -constexpr UIColor kTextPrimary(0.92f, 0.92f, 0.92f, 1.0f); -constexpr UIColor kTextMuted(0.74f, 0.74f, 0.74f, 1.0f); -constexpr UIColor kTextWeak(0.57f, 0.57f, 0.57f, 1.0f); -constexpr UIColor kButtonBg(0.24f, 0.24f, 0.24f, 1.0f); -constexpr UIColor kButtonHover(0.31f, 0.31f, 0.31f, 1.0f); -constexpr UIColor kButtonBorder(0.44f, 0.44f, 0.44f, 1.0f); -constexpr UIColor kOk(0.47f, 0.71f, 0.50f, 1.0f); -constexpr UIColor kWarn(0.85f, 0.68f, 0.36f, 1.0f); - -enum class ActionId : unsigned char { - Reset = 0, - Capture -}; - -struct ButtonState { - ActionId action = ActionId::Reset; - std::string label = {}; - UIRect rect = {}; - bool hovered = false; -}; - -std::filesystem::path ResolveRepoRootPath() { - std::string root = XCENGINE_EDITOR_UI_TESTS_REPO_ROOT; - if (root.size() >= 2u && root.front() == '"' && root.back() == '"') { - root = root.substr(1u, root.size() - 2u); - } - - return std::filesystem::path(root).lexically_normal(); -} - -bool ContainsPoint(const UIRect& rect, float x, float y) { - return x >= rect.x && - x <= rect.x + rect.width && - y >= rect.y && - y <= rect.y + rect.height; -} - -std::string FormatBool(bool value) { - return value ? "是" : "否"; -} - -std::string FormatOptionalIndex(std::size_t index) { - if (index == XCEngine::UI::Editor::Widgets::UIEditorTabStripInvalidIndex) { - return "无"; - } - - return std::to_string(index); -} - -std::string DescribeHitTarget(const UIEditorDockHostHitTarget& target) { - switch (target.kind) { - case UIEditorDockHostHitTargetKind::Tab: - return "标签: " + target.panelId; - case UIEditorDockHostHitTargetKind::TabStripBackground: - return "标签栏空白"; - case UIEditorDockHostHitTargetKind::PanelBody: - return "面板主体: " + target.panelId; - case UIEditorDockHostHitTargetKind::SplitterHandle: - return "分割条: " + target.nodeId; - case UIEditorDockHostHitTargetKind::None: - default: - return "无"; - } -} - -void DrawCard( - UIDrawList& drawList, - const UIRect& rect, - std::string_view title, - std::string_view subtitle = {}) { - drawList.AddFilledRect(rect, kCardBg, 10.0f); - drawList.AddRectOutline(rect, kCardBorder, 1.0f, 10.0f); - drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 14.0f), std::string(title), kTextPrimary, 17.0f); - if (!subtitle.empty()) { - drawList.AddText(UIPoint(rect.x + 16.0f, rect.y + 38.0f), std::string(subtitle), kTextMuted, 12.0f); - } -} - -void DrawButton(UIDrawList& drawList, const ButtonState& button) { - drawList.AddFilledRect(button.rect, button.hovered ? kButtonHover : kButtonBg, 8.0f); - drawList.AddRectOutline(button.rect, kButtonBorder, 1.0f, 8.0f); - drawList.AddText(UIPoint(button.rect.x + 14.0f, button.rect.y + 10.0f), button.label, kTextPrimary, 12.0f); -} - -UIEditorPanelRegistry BuildPanelRegistry() { - UIEditorPanelRegistry registry = {}; - registry.panels = { - { "doc-a", "Document A", {}, true, true, true }, - { "doc-b", "Document B", {}, true, true, true }, - { "doc-c", "Document C", {}, true, true, true }, - { "details", "Details", {}, true, true, true } - }; - return registry; -} - -UIEditorWorkspaceModel BuildWorkspace() { - UIEditorWorkspaceModel workspace = {}; - workspace.root = BuildUIEditorWorkspaceSplit( - "root-split", - UIEditorWorkspaceSplitAxis::Horizontal, - 0.72f, - BuildUIEditorWorkspaceTabStack( - "document-tabs", - { - BuildUIEditorWorkspacePanel("doc-a-node", "doc-a", "Document A", true), - BuildUIEditorWorkspacePanel("doc-b-node", "doc-b", "Document B", true), - BuildUIEditorWorkspacePanel("doc-c-node", "doc-c", "Document C", true) - }, - 0u), - BuildUIEditorWorkspaceSingleTabStack("details-node", "details", "Details", true)); - workspace.activePanelId = "doc-a"; - return workspace; -} - -std::string CollectDocumentTabOrder(const UIEditorWorkspaceController& controller) { - const auto* node = FindUIEditorWorkspaceNode(controller.GetWorkspace(), "document-tabs"); - if (node == nullptr) { - return "缺失"; - } - - std::ostringstream stream = {}; - for (std::size_t index = 0; index < node->children.size(); ++index) { - if (index > 0u) { - stream << " | "; - } - stream << node->children[index].panel.panelId; - } - return stream.str(); -} - -const XCEngine::UI::Editor::Widgets::UIEditorDockHostTabStackLayout* FindDocumentTabStackLayout( - const UIEditorDockHostInteractionFrame& frame) { - for (const auto& tabStack : frame.layout.tabStacks) { - if (tabStack.nodeId == "document-tabs") { - return &tabStack; - } - } - - return nullptr; -} - -const UIEditorDockHostTabStripInteractionEntry* FindDocumentTabStripInteractionEntry( - const UIEditorDockHostInteractionState& state) { - for (const UIEditorDockHostTabStripInteractionEntry& entry : state.tabStripInteractions) { - if (entry.nodeId == "document-tabs") { - return &entry; - } - } - - return nullptr; -} - -class ScenarioApp { -public: - int Run(HINSTANCE hInstance, int nCmdShow) { - if (!Initialize(hInstance, nCmdShow)) { - Shutdown(); - return 1; - } - - MSG message = {}; - while (message.message != WM_QUIT) { - if (PeekMessageW(&message, nullptr, 0U, 0U, PM_REMOVE)) { - TranslateMessage(&message); - DispatchMessageW(&message); - continue; - } - - RenderFrame(); - Sleep(8); - } - - Shutdown(); - return static_cast(message.wParam); - } - -private: - static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { - if (message == WM_NCCREATE) { - const auto* createStruct = reinterpret_cast(lParam); - auto* app = reinterpret_cast(createStruct->lpCreateParams); - SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(app)); - return TRUE; - } - - auto* app = reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); - switch (message) { - case WM_SIZE: - if (app != nullptr && wParam != SIZE_MINIMIZED) { - app->m_renderer.Resize(static_cast(LOWORD(lParam)), static_cast(HIWORD(lParam))); - } - return 0; - case WM_PAINT: - if (app != nullptr) { - PAINTSTRUCT paintStruct = {}; - BeginPaint(hwnd, &paintStruct); - app->RenderFrame(); - EndPaint(hwnd, &paintStruct); - return 0; - } - break; - case WM_MOUSEMOVE: - if (app != nullptr) { - if (!app->m_trackingMouseLeave) { - TRACKMOUSEEVENT trackMouseEvent = {}; - trackMouseEvent.cbSize = sizeof(trackMouseEvent); - trackMouseEvent.dwFlags = TME_LEAVE; - trackMouseEvent.hwndTrack = hwnd; - if (TrackMouseEvent(&trackMouseEvent)) { - app->m_trackingMouseLeave = true; - } - } - app->HandleMouseMove( - static_cast(GET_X_LPARAM(lParam)), - static_cast(GET_Y_LPARAM(lParam))); - return 0; - } - break; - case WM_MOUSELEAVE: - if (app != nullptr) { - app->m_trackingMouseLeave = false; - UIInputEvent event = {}; - event.type = UIInputEventType::PointerLeave; - app->m_pendingInputEvents.push_back(event); - return 0; - } - break; - case WM_LBUTTONDOWN: - if (app != nullptr) { - SetFocus(hwnd); - app->HandleLeftButtonDown( - static_cast(GET_X_LPARAM(lParam)), - static_cast(GET_Y_LPARAM(lParam))); - return 0; - } - break; - case WM_LBUTTONUP: - if (app != nullptr) { - app->HandleLeftButtonUp( - static_cast(GET_X_LPARAM(lParam)), - static_cast(GET_Y_LPARAM(lParam))); - return 0; - } - break; - case WM_SETFOCUS: - if (app != nullptr) { - UIInputEvent event = {}; - event.type = UIInputEventType::FocusGained; - app->m_pendingInputEvents.push_back(event); - return 0; - } - break; - case WM_KILLFOCUS: - if (app != nullptr) { - UIInputEvent event = {}; - event.type = UIInputEventType::FocusLost; - app->m_pendingInputEvents.push_back(event); - app->m_lastInputCause = "focus_lost"; - return 0; - } - break; - case WM_CAPTURECHANGED: - if (app != nullptr && - !app->m_interactionState.activeTabDragNodeId.empty() && - reinterpret_cast(lParam) != hwnd) { - UIInputEvent event = {}; - event.type = UIInputEventType::FocusLost; - app->m_pendingInputEvents.push_back(event); - app->m_lastInputCause = "focus_lost"; - return 0; - } - break; - case WM_KEYDOWN: - case WM_SYSKEYDOWN: - if (app != nullptr) { - if (wParam == VK_F12) { - app->m_autoScreenshot.RequestCapture("manual_f12"); - return 0; - } - - UIInputEvent event = {}; - event.type = UIInputEventType::KeyDown; - event.keyCode = static_cast(wParam == VK_ESCAPE ? KeyCode::Escape : KeyCode::None); - app->m_pendingInputEvents.push_back(event); - return 0; - } - break; - case WM_ERASEBKGND: - return 1; - case WM_DESTROY: - PostQuitMessage(0); - return 0; - default: - break; - } - - return DefWindowProcW(hwnd, message, wParam, lParam); - } - - bool Initialize(HINSTANCE hInstance, int nCmdShow) { - m_captureRoot = - ResolveRepoRootPath() / "tests/UI/Editor/integration/shell/dock_tab_reorder_same_stack/captures"; - m_autoScreenshot.Initialize(m_captureRoot); - - WNDCLASSEXW windowClass = {}; - windowClass.cbSize = sizeof(windowClass); - windowClass.style = CS_HREDRAW | CS_VREDRAW; - windowClass.lpfnWndProc = &ScenarioApp::WndProc; - windowClass.hInstance = hInstance; - windowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW); - windowClass.lpszClassName = kWindowClassName; - m_windowClassAtom = RegisterClassExW(&windowClass); - if (m_windowClassAtom == 0) { - return false; - } - - m_hwnd = CreateWindowExW( - 0, - kWindowClassName, - kWindowTitle, - WS_OVERLAPPEDWINDOW | WS_VISIBLE, - CW_USEDEFAULT, - CW_USEDEFAULT, - 1500, - 920, - nullptr, - nullptr, - hInstance, - this); - if (m_hwnd == nullptr) { - return false; - } - - ShowWindow(m_hwnd, nCmdShow); - if (!m_renderer.Initialize(m_hwnd)) { - return false; - } - - ResetScenario(); - return true; - } - - void Shutdown() { - if (GetCapture() == m_hwnd) { - ReleaseCapture(); - } - m_autoScreenshot.Shutdown(); - m_renderer.Shutdown(); - if (m_hwnd != nullptr && IsWindow(m_hwnd)) { - DestroyWindow(m_hwnd); - } - if (m_windowClassAtom != 0) { - UnregisterClassW(kWindowClassName, GetModuleHandleW(nullptr)); - } - } - - void ResetScenario() { - if (GetCapture() == m_hwnd) { - ReleaseCapture(); - } - - m_controller = BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); - m_interactionState = {}; - m_cachedFrame = {}; - m_pendingInputEvents.clear(); - m_lastInputCause.clear(); - m_hoverText = "无"; - m_lastResult = "等待验证:请在同一标签栏内拖拽标签。"; - m_lastResultColor = kWarn; - } - - void UpdateLayout() { - RECT clientRect = {}; - GetClientRect(m_hwnd, &clientRect); - const float width = static_cast((std::max)(clientRect.right - clientRect.left, 1L)); - const float height = static_cast((std::max)(clientRect.bottom - clientRect.top, 1L)); - constexpr float padding = 20.0f; - constexpr float sidebarWidth = 360.0f; - - m_introRect = UIRect(padding, padding, width - padding * 2.0f, 168.0f); - m_previewRect = UIRect( - padding, - m_introRect.y + m_introRect.height + 16.0f, - width - sidebarWidth - padding * 3.0f, - height - m_introRect.height - padding * 3.0f); - m_stateRect = UIRect( - m_previewRect.x + m_previewRect.width + 20.0f, - m_previewRect.y, - sidebarWidth, - m_previewRect.height); - m_dockHostRect = UIRect( - m_previewRect.x + 16.0f, - m_previewRect.y + 64.0f, - m_previewRect.width - 32.0f, - m_previewRect.height - 80.0f); - - const float buttonWidth = (m_stateRect.width - 44.0f) * 0.5f; - const float buttonY = m_stateRect.y + m_stateRect.height - 52.0f; - m_buttons = { - { ActionId::Reset, "重置", UIRect(m_stateRect.x + 16.0f, buttonY, buttonWidth, 36.0f), false }, - { ActionId::Capture, "截图(F12)", UIRect(m_stateRect.x + 28.0f + buttonWidth, buttonY, buttonWidth, 36.0f), false } - }; - } - - void HandleMouseMove(float x, float y) { - UpdateLayout(); - for (ButtonState& button : m_buttons) { - button.hovered = ContainsPoint(button.rect, x, y); - } - - UIInputEvent event = {}; - event.type = UIInputEventType::PointerMove; - event.position = UIPoint(x, y); - m_pendingInputEvents.push_back(event); - } - - void HandleLeftButtonDown(float x, float y) { - UpdateLayout(); - for (const ButtonState& button : m_buttons) { - if (ContainsPoint(button.rect, x, y)) { - ExecuteAction(button.action); - return; - } - } - - UIInputEvent event = {}; - event.type = UIInputEventType::PointerButtonDown; - event.position = UIPoint(x, y); - event.pointerButton = UIPointerButton::Left; - m_pendingInputEvents.push_back(event); - } - - void HandleLeftButtonUp(float x, float y) { - UIInputEvent event = {}; - event.type = UIInputEventType::PointerButtonUp; - event.position = UIPoint(x, y); - event.pointerButton = UIPointerButton::Left; - m_pendingInputEvents.push_back(event); - } - - void ExecuteAction(ActionId action) { - if (action == ActionId::Reset) { - ResetScenario(); - m_lastResult = "已重置:请重新检查提交、取消和失焦取消。"; - m_lastResultColor = kWarn; - return; - } - - m_autoScreenshot.RequestCapture("manual_button"); - m_lastResult = "截图已排队:输出到 captures/。"; - m_lastResultColor = kWarn; - } - - void ApplyHostCaptureRequests(const UIEditorDockHostInteractionResult& result) { - if (result.requestPointerCapture && GetCapture() != m_hwnd) { - SetCapture(m_hwnd); - } - if (result.releasePointerCapture && GetCapture() == m_hwnd) { - ReleaseCapture(); - } - } - - void UpdateLastResult(const UIEditorDockHostInteractionResult& result) { - if (result.layoutResult.status == UIEditorWorkspaceLayoutOperationStatus::Changed) { - m_lastResult = "结果:同一标签栏内的标签重排已提交。"; - m_lastResultColor = kOk; - return; - } - - if (result.requestPointerCapture) { - m_lastResult = "结果:开始拖拽,宿主已拿到 pointer capture。"; - m_lastResultColor = kOk; - return; - } - - if (result.releasePointerCapture && !result.layoutChanged && !result.commandExecuted) { - if (m_lastInputCause == "focus_lost") { - m_lastResult = "结果:窗口失焦,本次重排已取消。"; - } else { - m_lastResult = "结果:拖拽已取消。拖到 body 区再松开时,标签顺序应保持不变。"; - } - m_lastResultColor = kWarn; - return; - } - } - - void RenderFrame() { - UpdateLayout(); - m_cachedFrame = UpdateUIEditorDockHostInteraction( - m_interactionState, - m_controller, - m_dockHostRect, - m_pendingInputEvents); - m_pendingInputEvents.clear(); - ApplyHostCaptureRequests(m_cachedFrame.result); - UpdateLastResult(m_cachedFrame.result); - m_lastInputCause.clear(); - - RECT clientRect = {}; - GetClientRect(m_hwnd, &clientRect); - const float width = static_cast((std::max)(clientRect.right - clientRect.left, 1L)); - const float height = static_cast((std::max)(clientRect.bottom - clientRect.top, 1L)); - - UIDrawData drawData = {}; - UIDrawList& drawList = drawData.EmplaceDrawList("DockTabReorderSameStack"); - drawList.AddFilledRect(UIRect(0.0f, 0.0f, width, height), kWindowBg); - - DrawCard(drawList, m_introRect, "这个测试验证什么功能?", "只验证同一标签栏内 tab 重排的基础 contract,不做 editor 业务。"); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 70.0f), "1. 拖动 Document A / B / C 的 tab header 到同一行其他位置。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 92.0f), "2. 在 header 区松开:应提交重排,并更新右侧标签顺序。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 114.0f), "3. 拖到 body 区再松开:应取消,本次标签顺序不能变化。", kTextPrimary, 12.0f); - drawList.AddText(UIPoint(m_introRect.x + 16.0f, m_introRect.y + 136.0f), "4. 拖拽中切走窗口或按 Esc:应释放 capture,并取消这次重排。", kTextWeak, 11.0f); - - DrawCard(drawList, m_previewRect, "预览区", "这里只保留同栏重排所需的最小工作区。"); - drawList.AddFilledRect(m_dockHostRect, kPreviewBg, 8.0f); - AppendUIEditorDockHostBackground(drawList, m_cachedFrame.layout); - AppendUIEditorDockHostForeground(drawList, m_cachedFrame.layout); - - DrawCard(drawList, m_stateRect, "状态", "拖拽时重点观察右侧状态是否和画面一致。"); - float y = m_stateRect.y + 68.0f; - auto addLine = [&](std::string text, const UIColor& color = kTextPrimary, float fontSize = 12.0f) mutable { - drawList.AddText(UIPoint(m_stateRect.x + 16.0f, y), std::move(text), color, fontSize); - y += 22.0f; - }; - - const auto* documentTabStack = FindDocumentTabStackLayout(m_cachedFrame); - const bool previewVisible = - documentTabStack != nullptr && - documentTabStack->tabStripLayout.insertionPreview.visible; - const std::size_t previewInsertionIndex = - documentTabStack != nullptr - ? documentTabStack->tabStripLayout.insertionPreview.insertionIndex - : XCEngine::UI::Editor::Widgets::UIEditorTabStripInvalidIndex; - - addLine("当前标签顺序: " + CollectDocumentTabOrder(m_controller), kTextPrimary, 11.0f); - addLine("活动面板: " + m_controller.GetWorkspace().activePanelId, kTextPrimary, 11.0f); - addLine("已聚焦: " + FormatBool(m_cachedFrame.focused), m_cachedFrame.focused ? kOk : kTextMuted, 11.0f); - addLine("宿主捕获: " + FormatBool(GetCapture() == m_hwnd), GetCapture() == m_hwnd ? kOk : kTextMuted, 11.0f); - addLine( - "拖拽标签栈: " + - (m_interactionState.activeTabDragNodeId.empty() - ? std::string("无") - : m_interactionState.activeTabDragNodeId), - kTextWeak, - 11.0f); - addLine("预览插入位激活: " + FormatBool(previewVisible), previewVisible ? kOk : kTextWeak, 11.0f); - addLine("预览插入索引: " + FormatOptionalIndex(previewInsertionIndex), kTextWeak, 11.0f); - addLine("当前 Hover: " + m_hoverText, kTextWeak, 11.0f); - - drawList.AddText( - UIPoint(m_stateRect.x + 16.0f, y + 8.0f), - "最近结果", - kTextPrimary, - 13.0f); - drawList.AddText( - UIPoint(m_stateRect.x + 16.0f, y + 32.0f), - m_lastResult, - m_lastResultColor, - 12.0f); - - const std::string captureSummary = - m_autoScreenshot.HasPendingCapture() - ? "截图排队中..." - : (m_autoScreenshot.GetLastCaptureSummary().empty() - ? "F12 或按钮 -> captures/" - : m_autoScreenshot.GetLastCaptureSummary()); - drawList.AddText( - UIPoint(m_stateRect.x + 16.0f, m_stateRect.y + m_stateRect.height - 86.0f), - captureSummary, - kTextWeak, - 11.0f); - for (const ButtonState& button : m_buttons) { - DrawButton(drawList, button); - } - - const bool framePresented = m_renderer.Render(drawData); - m_autoScreenshot.CaptureIfRequested( - m_renderer, - drawData, - static_cast(width), - static_cast(height), - framePresented); - } - - HWND m_hwnd = nullptr; - ATOM m_windowClassAtom = 0; - NativeRenderer m_renderer = {}; - AutoScreenshotController m_autoScreenshot = {}; - std::filesystem::path m_captureRoot = {}; - UIEditorWorkspaceController m_controller = {}; - UIEditorDockHostInteractionState m_interactionState = {}; - UIEditorDockHostInteractionFrame m_cachedFrame = {}; - std::vector m_pendingInputEvents = {}; - std::vector m_buttons = {}; - UIRect m_introRect = {}; - UIRect m_previewRect = {}; - UIRect m_stateRect = {}; - UIRect m_dockHostRect = {}; - bool m_trackingMouseLeave = false; - std::string m_lastInputCause = {}; - std::string m_hoverText = {}; - std::string m_lastResult = {}; - UIColor m_lastResultColor = kTextMuted; -}; - -} // namespace - -int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) { - return ScenarioApp().Run(hInstance, nCmdShow); -} diff --git a/tests/UI/Editor/integration/shell/editor_shell_compose/main.cpp b/tests/UI/Editor/integration/shell/editor_shell_compose/main.cpp index 7c8478b4..9b5ccf0a 100644 --- a/tests/UI/Editor/integration/shell/editor_shell_compose/main.cpp +++ b/tests/UI/Editor/integration/shell/editor_shell_compose/main.cpp @@ -2,14 +2,14 @@ #define NOMINMAX #endif -#include +#include #include -#include -#include +#include +#include #include #include "../../shared/src/EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include diff --git a/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp b/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp index d9d14708..c34b0025 100644 --- a/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp +++ b/tests/UI/Editor/integration/shell/editor_shell_interaction/main.cpp @@ -1,4 +1,4 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif @@ -6,13 +6,13 @@ #include #include -#include +#include #include -#include -#include +#include +#include #include "../../shared/src/EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/enum_field_basic/main.cpp b/tests/UI/Editor/integration/shell/enum_field_basic/main.cpp index bb027a9a..252e9498 100644 --- a/tests/UI/Editor/integration/shell/enum_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/enum_field_basic/main.cpp @@ -1,14 +1,14 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif #include #include #include -#include +#include #include "EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/list_view_basic/main.cpp b/tests/UI/Editor/integration/shell/list_view_basic/main.cpp index 93bd3177..ec5401f1 100644 --- a/tests/UI/Editor/integration/shell/list_view_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/list_view_basic/main.cpp @@ -4,8 +4,8 @@ #include #include -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/list_view_inline_rename/main.cpp b/tests/UI/Editor/integration/shell/list_view_inline_rename/main.cpp index 259bba64..dad17914 100644 --- a/tests/UI/Editor/integration/shell/list_view_inline_rename/main.cpp +++ b/tests/UI/Editor/integration/shell/list_view_inline_rename/main.cpp @@ -9,9 +9,9 @@ #include #include #include "EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/InputModifierTracker.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Platform/Win32/InputModifierTracker.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/list_view_multiselect/main.cpp b/tests/UI/Editor/integration/shell/list_view_multiselect/main.cpp index d8c7d5b1..b458a7ea 100644 --- a/tests/UI/Editor/integration/shell/list_view_multiselect/main.cpp +++ b/tests/UI/Editor/integration/shell/list_view_multiselect/main.cpp @@ -5,9 +5,9 @@ #include #include #include "EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/InputModifierTracker.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Platform/Win32/InputModifierTracker.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/menu_bar_basic/main.cpp b/tests/UI/Editor/integration/shell/menu_bar_basic/main.cpp index eaff9ab1..bdb4a076 100644 --- a/tests/UI/Editor/integration/shell/menu_bar_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/menu_bar_basic/main.cpp @@ -3,13 +3,13 @@ #endif #include -#include -#include +#include +#include #include -#include -#include -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include +#include +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/number_field_basic/main.cpp b/tests/UI/Editor/integration/shell/number_field_basic/main.cpp index c2057a72..f595353c 100644 --- a/tests/UI/Editor/integration/shell/number_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/number_field_basic/main.cpp @@ -1,4 +1,4 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif @@ -7,8 +7,8 @@ #include #include #include "EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/object_field_basic/main.cpp b/tests/UI/Editor/integration/shell/object_field_basic/main.cpp index ecf7433a..b5d2dba0 100644 --- a/tests/UI/Editor/integration/shell/object_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/object_field_basic/main.cpp @@ -1,4 +1,4 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif @@ -6,8 +6,8 @@ #include #include #include "EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/panel_content_host_basic/main.cpp b/tests/UI/Editor/integration/shell/panel_content_host_basic/main.cpp index b96c8067..fd1a17c9 100644 --- a/tests/UI/Editor/integration/shell/panel_content_host_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/panel_content_host_basic/main.cpp @@ -2,11 +2,11 @@ #define NOMINMAX #endif -#include -#include -#include -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include +#include +#include +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include diff --git a/tests/UI/Editor/integration/shell/panel_frame_basic/main.cpp b/tests/UI/Editor/integration/shell/panel_frame_basic/main.cpp index 22a45c44..027775a6 100644 --- a/tests/UI/Editor/integration/shell/panel_frame_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/panel_frame_basic/main.cpp @@ -2,9 +2,9 @@ #define NOMINMAX #endif -#include -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include diff --git a/tests/UI/Editor/integration/shell/property_grid_basic/main.cpp b/tests/UI/Editor/integration/shell/property_grid_basic/main.cpp index 4da78d5a..23f4360e 100644 --- a/tests/UI/Editor/integration/shell/property_grid_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/property_grid_basic/main.cpp @@ -1,4 +1,4 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif @@ -6,8 +6,8 @@ #include #include #include "EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/scroll_view_basic/main.cpp b/tests/UI/Editor/integration/shell/scroll_view_basic/main.cpp index 74b544ae..ba1ff37f 100644 --- a/tests/UI/Editor/integration/shell/scroll_view_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/scroll_view_basic/main.cpp @@ -4,8 +4,8 @@ #include #include -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include diff --git a/tests/UI/Editor/integration/shell/status_bar_basic/main.cpp b/tests/UI/Editor/integration/shell/status_bar_basic/main.cpp index 7bb8a7dd..00e7ee45 100644 --- a/tests/UI/Editor/integration/shell/status_bar_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/status_bar_basic/main.cpp @@ -3,8 +3,8 @@ #endif #include -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include diff --git a/tests/UI/Editor/integration/shell/tab_strip_basic/main.cpp b/tests/UI/Editor/integration/shell/tab_strip_basic/main.cpp index eb0ce684..c8ffe109 100644 --- a/tests/UI/Editor/integration/shell/tab_strip_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/tab_strip_basic/main.cpp @@ -3,11 +3,11 @@ #endif #include -#include -#include +#include +#include #include -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include @@ -40,12 +40,10 @@ using XCEngine::UI::UIRect; using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceController; using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel; using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack; -using XCEngine::UI::Editor::FindUIEditorPanelDescriptor; using XCEngine::UI::Editor::FindUIEditorPanelSessionState; using XCEngine::UI::Editor::GetUIEditorWorkspaceCommandStatusName; using XCEngine::UI::Editor::Host::AutoScreenshotController; using XCEngine::UI::Editor::Host::NativeRenderer; -using XCEngine::UI::Editor::UIEditorPanelDescriptor; using XCEngine::UI::Editor::UIEditorPanelRegistry; using XCEngine::UI::Editor::UIEditorTabStripInteractionFrame; using XCEngine::UI::Editor::UIEditorTabStripInteractionResult; @@ -534,7 +532,6 @@ private: void RefreshTabItems() { const UIEditorWorkspaceModel& workspace = m_controller.GetWorkspace(); - const UIEditorPanelRegistry& registry = m_controller.GetPanelRegistry(); const auto* tabStack = GetRootTabStack(workspace); m_tabItems.clear(); @@ -550,12 +547,9 @@ private: continue; } - const UIEditorPanelDescriptor* descriptor = - FindUIEditorPanelDescriptor(registry, child.panel.panelId); UIEditorTabStripItem item = {}; item.tabId = child.panel.panelId; item.title = child.panel.title; - item.closable = descriptor != nullptr ? descriptor->canClose : true; m_tabItems.push_back(std::move(item)); } } diff --git a/tests/UI/Editor/integration/shell/text_field_basic/main.cpp b/tests/UI/Editor/integration/shell/text_field_basic/main.cpp index 3ab6df2d..108df075 100644 --- a/tests/UI/Editor/integration/shell/text_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/text_field_basic/main.cpp @@ -1,4 +1,4 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif @@ -7,8 +7,8 @@ #include #include #include "EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/tree_view_basic/main.cpp b/tests/UI/Editor/integration/shell/tree_view_basic/main.cpp index 476aa15c..0fd3cbea 100644 --- a/tests/UI/Editor/integration/shell/tree_view_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/tree_view_basic/main.cpp @@ -4,8 +4,8 @@ #include #include -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/tree_view_inline_rename/main.cpp b/tests/UI/Editor/integration/shell/tree_view_inline_rename/main.cpp index e933563d..934299cd 100644 --- a/tests/UI/Editor/integration/shell/tree_view_inline_rename/main.cpp +++ b/tests/UI/Editor/integration/shell/tree_view_inline_rename/main.cpp @@ -9,8 +9,8 @@ #include #include #include "EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/tree_view_multiselect/main.cpp b/tests/UI/Editor/integration/shell/tree_view_multiselect/main.cpp index 87609b76..396ce762 100644 --- a/tests/UI/Editor/integration/shell/tree_view_multiselect/main.cpp +++ b/tests/UI/Editor/integration/shell/tree_view_multiselect/main.cpp @@ -4,8 +4,8 @@ #include #include -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/vector2_field_basic/main.cpp b/tests/UI/Editor/integration/shell/vector2_field_basic/main.cpp index 3961e09e..f5586b06 100644 --- a/tests/UI/Editor/integration/shell/vector2_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/vector2_field_basic/main.cpp @@ -1,4 +1,4 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif @@ -6,8 +6,8 @@ #include #include #include "EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/vector3_field_basic/main.cpp b/tests/UI/Editor/integration/shell/vector3_field_basic/main.cpp index 6bcbd203..6175f1d9 100644 --- a/tests/UI/Editor/integration/shell/vector3_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/vector3_field_basic/main.cpp @@ -1,4 +1,4 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif @@ -6,8 +6,8 @@ #include #include #include "EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/vector4_field_basic/main.cpp b/tests/UI/Editor/integration/shell/vector4_field_basic/main.cpp index 45f6e417..7e404d81 100644 --- a/tests/UI/Editor/integration/shell/vector4_field_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/vector4_field_basic/main.cpp @@ -1,4 +1,4 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif @@ -6,8 +6,8 @@ #include #include #include "EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/shell/viewport_shell_basic/main.cpp b/tests/UI/Editor/integration/shell/viewport_shell_basic/main.cpp index 849e6a37..64a5781c 100644 --- a/tests/UI/Editor/integration/shell/viewport_shell_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/viewport_shell_basic/main.cpp @@ -2,11 +2,11 @@ #define NOMINMAX #endif -#include -#include -#include "Host/AutoScreenshot.h" -#include "Host/InputModifierTracker.h" -#include "Host/NativeRenderer.h" +#include +#include +#include "Rendering/Native/AutoScreenshot.h" +#include "Platform/Win32/InputModifierTracker.h" +#include "Rendering/Native/NativeRenderer.h" #include diff --git a/tests/UI/Editor/integration/shell/viewport_slot_basic/main.cpp b/tests/UI/Editor/integration/shell/viewport_slot_basic/main.cpp index 1d8d8467..a964e2be 100644 --- a/tests/UI/Editor/integration/shell/viewport_slot_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/viewport_slot_basic/main.cpp @@ -2,9 +2,9 @@ #define NOMINMAX #endif -#include -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include diff --git a/tests/UI/Editor/integration/shell/workspace_interaction_basic/main.cpp b/tests/UI/Editor/integration/shell/workspace_interaction_basic/main.cpp index dfc13440..f2917a92 100644 --- a/tests/UI/Editor/integration/shell/workspace_interaction_basic/main.cpp +++ b/tests/UI/Editor/integration/shell/workspace_interaction_basic/main.cpp @@ -3,10 +3,10 @@ #endif #include -#include +#include #include "../../shared/src/EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include @@ -139,16 +139,12 @@ std::string DescribeHitTarget(const UIEditorDockHostHitTarget& target) { return "标签栏背景: " + target.nodeId; case UIEditorDockHostHitTargetKind::Tab: return "标签: " + target.panelId; - case UIEditorDockHostHitTargetKind::TabCloseButton: - return "标签关闭: " + target.panelId; case UIEditorDockHostHitTargetKind::PanelHeader: return "面板标题: " + target.panelId; case UIEditorDockHostHitTargetKind::PanelBody: return "面板内容: " + target.panelId; case UIEditorDockHostHitTargetKind::PanelFooter: return "面板底栏: " + target.panelId; - case UIEditorDockHostHitTargetKind::PanelCloseButton: - return "面板关闭: " + target.panelId; case UIEditorDockHostHitTargetKind::None: default: return "None"; @@ -209,16 +205,12 @@ std::string DescribeValidationHitTarget(const UIEditorDockHostHitTarget& target) return "Tab strip background: " + target.nodeId; case UIEditorDockHostHitTargetKind::Tab: return "Tab: " + target.panelId; - case UIEditorDockHostHitTargetKind::TabCloseButton: - return "Tab close: " + target.panelId; case UIEditorDockHostHitTargetKind::PanelHeader: return "Panel header: " + target.panelId; case UIEditorDockHostHitTargetKind::PanelBody: return "Panel body: " + target.panelId; case UIEditorDockHostHitTargetKind::PanelFooter: return "Panel footer: " + target.panelId; - case UIEditorDockHostHitTargetKind::PanelCloseButton: - return "Panel close: " + target.panelId; case UIEditorDockHostHitTargetKind::None: default: return "None"; diff --git a/tests/UI/Editor/integration/shell/workspace_shell_compose/main.cpp b/tests/UI/Editor/integration/shell/workspace_shell_compose/main.cpp index e43ac77a..6c1d8193 100644 --- a/tests/UI/Editor/integration/shell/workspace_shell_compose/main.cpp +++ b/tests/UI/Editor/integration/shell/workspace_shell_compose/main.cpp @@ -2,13 +2,13 @@ #define NOMINMAX #endif -#include -#include -#include -#include -#include -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include +#include +#include +#include +#include +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include @@ -166,16 +166,12 @@ std::string DescribeHitTarget(const UIEditorDockHostHitTarget& target) { return "TabStripBackground: " + target.nodeId; case UIEditorDockHostHitTargetKind::Tab: return "Tab: " + target.panelId; - case UIEditorDockHostHitTargetKind::TabCloseButton: - return "TabClose: " + target.panelId; case UIEditorDockHostHitTargetKind::PanelHeader: return "PanelHeader: " + target.panelId; case UIEditorDockHostHitTargetKind::PanelBody: return "PanelBody: " + target.panelId; case UIEditorDockHostHitTargetKind::PanelFooter: return "PanelFooter: " + target.panelId; - case UIEditorDockHostHitTargetKind::PanelCloseButton: - return "PanelClose: " + target.panelId; case UIEditorDockHostHitTargetKind::None: default: return "None"; diff --git a/tests/UI/Editor/integration/shell/workspace_viewport_compose/main.cpp b/tests/UI/Editor/integration/shell/workspace_viewport_compose/main.cpp index f3564224..1699f8f8 100644 --- a/tests/UI/Editor/integration/shell/workspace_viewport_compose/main.cpp +++ b/tests/UI/Editor/integration/shell/workspace_viewport_compose/main.cpp @@ -3,14 +3,14 @@ #endif #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include "../../shared/src/EditorValidationTheme.h" -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include diff --git a/tests/UI/Editor/integration/state/layout_persistence/main.cpp b/tests/UI/Editor/integration/state/layout_persistence/main.cpp index 6e37589c..0e39ab76 100644 --- a/tests/UI/Editor/integration/state/layout_persistence/main.cpp +++ b/tests/UI/Editor/integration/state/layout_persistence/main.cpp @@ -1,11 +1,11 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include -#include -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include +#include +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include diff --git a/tests/UI/Editor/integration/state/panel_host_lifecycle/main.cpp b/tests/UI/Editor/integration/state/panel_host_lifecycle/main.cpp index dd419c85..3af7f792 100644 --- a/tests/UI/Editor/integration/state/panel_host_lifecycle/main.cpp +++ b/tests/UI/Editor/integration/state/panel_host_lifecycle/main.cpp @@ -1,11 +1,11 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include -#include -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include +#include +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include diff --git a/tests/UI/Editor/integration/state/panel_session_flow/main.cpp b/tests/UI/Editor/integration/state/panel_session_flow/main.cpp index c200a454..d6de8390 100644 --- a/tests/UI/Editor/integration/state/panel_session_flow/main.cpp +++ b/tests/UI/Editor/integration/state/panel_session_flow/main.cpp @@ -1,10 +1,10 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include -#include "Host/AutoScreenshot.h" -#include "Host/NativeRenderer.h" +#include +#include "Rendering/Native/AutoScreenshot.h" +#include "Rendering/Native/NativeRenderer.h" #include diff --git a/tests/UI/Editor/integration/state/shortcut_dispatch/main.cpp b/tests/UI/Editor/integration/state/shortcut_dispatch/main.cpp index cb49500f..aadf5c8a 100644 --- a/tests/UI/Editor/integration/state/shortcut_dispatch/main.cpp +++ b/tests/UI/Editor/integration/state/shortcut_dispatch/main.cpp @@ -1,11 +1,11 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif #include -#include "Host/AutoScreenshot.h" -#include "Host/InputModifierTracker.h" -#include "Host/NativeRenderer.h" +#include "Rendering/Native/AutoScreenshot.h" +#include "Platform/Win32/InputModifierTracker.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/integration/state/viewport_input_bridge_basic/main.cpp b/tests/UI/Editor/integration/state/viewport_input_bridge_basic/main.cpp index 9395c899..1185338f 100644 --- a/tests/UI/Editor/integration/state/viewport_input_bridge_basic/main.cpp +++ b/tests/UI/Editor/integration/state/viewport_input_bridge_basic/main.cpp @@ -1,12 +1,12 @@ -#ifndef NOMINMAX +#ifndef NOMINMAX #define NOMINMAX #endif -#include -#include -#include "Host/AutoScreenshot.h" -#include "Host/InputModifierTracker.h" -#include "Host/NativeRenderer.h" +#include +#include +#include "Rendering/Native/AutoScreenshot.h" +#include "Platform/Win32/InputModifierTracker.h" +#include "Rendering/Native/NativeRenderer.h" #include #include diff --git a/tests/UI/Editor/unit/CMakeLists.txt b/tests/UI/Editor/unit/CMakeLists.txt index 0434d495..061020af 100644 --- a/tests/UI/Editor/unit/CMakeLists.txt +++ b/tests/UI/Editor/unit/CMakeLists.txt @@ -1,4 +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 @@ -60,12 +61,18 @@ set(EDITOR_UI_UNIT_TEST_SOURCES test_ui_editor_workspace_controller.cpp test_ui_editor_workspace_layout_persistence.cpp test_ui_editor_workspace_model.cpp + test_ui_editor_workspace_splitter_drag_correction.cpp test_ui_editor_workspace_session.cpp test_ui_editor_window_workspace_controller.cpp ) add_executable(editor_ui_tests ${EDITOR_UI_UNIT_TEST_SOURCES}) +target_sources(editor_ui_tests + PRIVATE + ${CMAKE_SOURCE_DIR}/new_editor/app/Composition/EditorHostCommandBridge.cpp +) + target_link_libraries(editor_ui_tests PRIVATE XCUIEditorLib diff --git a/tests/UI/Editor/unit/test_editor_host_command_bridge.cpp b/tests/UI/Editor/unit/test_editor_host_command_bridge.cpp new file mode 100644 index 00000000..3c1ffa2d --- /dev/null +++ b/tests/UI/Editor/unit/test_editor_host_command_bridge.cpp @@ -0,0 +1,85 @@ +#include + +#include "Composition/EditorEditCommandRoute.h" +#include "Composition/EditorHostCommandBridge.h" +#include "State/EditorSession.h" + +namespace { + +using XCEngine::UI::Editor::App::EditorActionRoute; +using XCEngine::UI::Editor::App::EditorEditCommandRoute; +using XCEngine::UI::Editor::App::EditorHostCommandBridge; +using XCEngine::UI::Editor::App::EditorSession; +using XCEngine::UI::Editor::UIEditorHostCommandDispatchResult; +using XCEngine::UI::Editor::UIEditorHostCommandEvaluationResult; + +class StubEditCommandRoute final : public EditorEditCommandRoute { +public: + UIEditorHostCommandEvaluationResult EvaluateEditCommand( + std::string_view commandId) const override { + lastEvaluatedCommandId = std::string(commandId); + return evaluationResult; + } + + UIEditorHostCommandDispatchResult DispatchEditCommand( + std::string_view commandId) override { + lastDispatchedCommandId = std::string(commandId); + return dispatchResult; + } + + mutable std::string lastEvaluatedCommandId = {}; + std::string lastDispatchedCommandId = {}; + UIEditorHostCommandEvaluationResult evaluationResult = {}; + UIEditorHostCommandDispatchResult dispatchResult = {}; +}; + +TEST(EditorHostCommandBridgeTest, HierarchyEditCommandsDelegateToBoundRoute) { + EditorSession session = {}; + session.activeRoute = EditorActionRoute::Hierarchy; + + StubEditCommandRoute hierarchyRoute = {}; + hierarchyRoute.evaluationResult.executable = true; + hierarchyRoute.evaluationResult.message = "Hierarchy route owns rename."; + hierarchyRoute.dispatchResult.commandExecuted = true; + hierarchyRoute.dispatchResult.message = "Hierarchy rename dispatched."; + + EditorHostCommandBridge bridge = {}; + bridge.BindSession(session); + bridge.BindEditCommandRoutes(&hierarchyRoute, nullptr); + + const UIEditorHostCommandEvaluationResult evaluation = + bridge.EvaluateHostCommand("edit.rename"); + EXPECT_TRUE(evaluation.executable); + EXPECT_EQ(evaluation.message, "Hierarchy route owns rename."); + EXPECT_EQ(hierarchyRoute.lastEvaluatedCommandId, "edit.rename"); + + const UIEditorHostCommandDispatchResult dispatch = + bridge.DispatchHostCommand("edit.rename"); + EXPECT_TRUE(dispatch.commandExecuted); + EXPECT_EQ(dispatch.message, "Hierarchy rename dispatched."); + EXPECT_EQ(hierarchyRoute.lastDispatchedCommandId, "edit.rename"); +} + +TEST(EditorHostCommandBridgeTest, UnsupportedHostCommandsUseHonestMessages) { + EditorSession session = {}; + session.activeRoute = EditorActionRoute::None; + + EditorHostCommandBridge bridge = {}; + bridge.BindSession(session); + + const UIEditorHostCommandEvaluationResult aboutEvaluation = + bridge.EvaluateHostCommand("help.about"); + EXPECT_FALSE(aboutEvaluation.executable); + EXPECT_EQ( + aboutEvaluation.message, + "About dialog is unavailable in the current shell."); + + const UIEditorHostCommandEvaluationResult fileEvaluation = + bridge.EvaluateHostCommand("file.save_scene"); + EXPECT_FALSE(fileEvaluation.executable); + EXPECT_EQ( + fileEvaluation.message, + "Only file.exit has a bound host owner in the current shell."); +} + +} // namespace diff --git a/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp b/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp index 77b513df..66112dcd 100644 --- a/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp +++ b/tests/UI/Editor/unit/test_editor_shell_asset_validation.cpp @@ -2,7 +2,7 @@ #include -#include +#include #include @@ -67,7 +67,14 @@ TEST(EditorShellAssetValidationTest, ValidationRejectsWorkspacePanelMissingFromR TEST(EditorShellAssetValidationTest, ValidationRejectsWorkspaceTitleDriftFromRegistry) { auto shellAsset = BuildDefaultEditorShellAsset("."); shellAsset.workspace.activePanelId = "editor-foundation-root"; - shellAsset.workspace.root.panel.title = "Drifted Title"; + 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"; const auto validation = ValidateEditorShellAsset(shellAsset); EXPECT_EQ(validation.code, EditorShellAssetValidationCode::PanelTitleMismatch); diff --git a/tests/UI/Editor/unit/test_input_modifier_tracker.cpp b/tests/UI/Editor/unit/test_input_modifier_tracker.cpp index d380ff3e..90f50c89 100644 --- a/tests/UI/Editor/unit/test_input_modifier_tracker.cpp +++ b/tests/UI/Editor/unit/test_input_modifier_tracker.cpp @@ -4,7 +4,7 @@ #include -#include "Host/InputModifierTracker.h" +#include "Platform/Win32/InputModifierTracker.h" #include diff --git a/tests/UI/Editor/unit/test_structured_editor_shell.cpp b/tests/UI/Editor/unit/test_structured_editor_shell.cpp index 99afaae3..ae23d375 100644 --- a/tests/UI/Editor/unit/test_structured_editor_shell.cpp +++ b/tests/UI/Editor/unit/test_structured_editor_shell.cpp @@ -3,8 +3,8 @@ #include #include #include -#include -#include +#include +#include #include #include diff --git a/tests/UI/Editor/unit/test_ui_editor_bool_field.cpp b/tests/UI/Editor/unit/test_ui_editor_bool_field.cpp index d8eaac48..7742caf5 100644 --- a/tests/UI/Editor/unit/test_ui_editor_bool_field.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_bool_field.cpp @@ -46,12 +46,13 @@ TEST(UIEditorBoolFieldTest, BackgroundAndForegroundEmitCheckboxOnlyChromeAndCent UIEditorBoolFieldState state = {}; state.focused = true; state.hoveredTarget = UIEditorBoolFieldHitTargetKind::Checkbox; + const XCEngine::UI::Editor::Widgets::UIEditorBoolFieldPalette palette = {}; XCEngine::UI::UIDrawData drawData = {}; auto& drawList = drawData.EmplaceDrawList("BoolField"); const auto layout = BuildUIEditorBoolFieldLayout(UIRect(0.0f, 0.0f, 360.0f, 22.0f), spec); - AppendUIEditorBoolFieldBackground(drawList, layout, spec, state); - AppendUIEditorBoolFieldForeground(drawList, layout, spec); + AppendUIEditorBoolFieldBackground(drawList, layout, spec, state, palette); + AppendUIEditorBoolFieldForeground(drawList, layout, spec, palette); const auto& commands = drawList.GetCommands(); ASSERT_EQ(commands.size(), 6u); @@ -59,7 +60,7 @@ TEST(UIEditorBoolFieldTest, BackgroundAndForegroundEmitCheckboxOnlyChromeAndCent EXPECT_EQ(commands[0].rect.x, layout.checkboxRect.x); EXPECT_EQ(commands[0].rect.y, layout.checkboxRect.y); EXPECT_EQ(commands[1].type, UIDrawCommandType::RectOutline); - EXPECT_EQ(commands[1].color.r, 0.14f); + EXPECT_FLOAT_EQ(commands[1].color.r, palette.checkboxBorderColor.r); EXPECT_EQ(commands[2].type, UIDrawCommandType::PushClipRect); EXPECT_EQ(commands[3].type, UIDrawCommandType::Text); EXPECT_FLOAT_EQ(commands[3].position.y, 2.0f); diff --git a/tests/UI/Editor/unit/test_ui_editor_dock_host.cpp b/tests/UI/Editor/unit/test_ui_editor_dock_host.cpp index 8f038dec..9b62be5c 100644 --- a/tests/UI/Editor/unit/test_ui_editor_dock_host.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_dock_host.cpp @@ -1,10 +1,10 @@ #include #include -#include -#include -#include -#include +#include +#include +#include +#include namespace { @@ -100,8 +100,14 @@ TEST(UIEditorDockHostTest, LayoutComposesOnlyUnifiedTabStacksFromWorkspaceTree) const auto* rootSplitter = FindUIEditorDockHostSplitterLayout(layout, "root-split"); ASSERT_NE(rootSplitter, nullptr); - EXPECT_FLOAT_EQ(rootSplitter->splitterLayout.handleRect.x, 399.5f); - EXPECT_FLOAT_EQ(rootSplitter->splitterLayout.handleRect.width, 1.0f); + const UIEditorDockHostMetrics metrics = {}; + EXPECT_FLOAT_EQ( + rootSplitter->splitterLayout.handleRect.width, + metrics.splitterMetrics.thickness); + EXPECT_FLOAT_EQ( + rootSplitter->splitterLayout.handleRect.x + + rootSplitter->splitterLayout.handleRect.width * 0.5f, + 400.0f); const auto& tabStack = layout.tabStacks.front(); EXPECT_EQ(tabStack.nodeId, "document-tabs"); diff --git a/tests/UI/Editor/unit/test_ui_editor_dock_host_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_dock_host_interaction.cpp index b1107a31..a954ca05 100644 --- a/tests/UI/Editor/unit/test_ui_editor_dock_host_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_dock_host_interaction.cpp @@ -1,9 +1,10 @@ #include #include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -518,222 +519,6 @@ TEST(UIEditorDockHostInteractionTest, ClickingSingleTabStackBodyActivatesTargetP EXPECT_EQ(controller.GetWorkspace().activePanelId, "details"); } -TEST(UIEditorDockHostInteractionTest, DraggingTabWithinSameStackRequestsCaptureAndCommitsReorder) { - auto controller = - BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); - UIEditorDockHostInteractionState state = {}; - - auto frame = UpdateUIEditorDockHostInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 800.0f, 600.0f), - {}); - const auto* documentStack = FindTabStackByNodeId(frame.layout, "document-tabs"); - ASSERT_NE(documentStack, nullptr); - const UIPoint sourceCenter = RectCenter(documentStack->tabStripLayout.tabHeaderRects[0]); - const UIPoint dropPoint( - documentStack->tabStripLayout.headerRect.x + - documentStack->tabStripLayout.headerRect.width - 2.0f, - sourceCenter.y); - - frame = UpdateUIEditorDockHostInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 800.0f, 600.0f), - { MakePointerDown(sourceCenter.x, sourceCenter.y) }); - EXPECT_TRUE(frame.result.consumed); - - frame = UpdateUIEditorDockHostInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 800.0f, 600.0f), - { MakePointerMove(dropPoint.x, dropPoint.y) }); - EXPECT_TRUE(frame.result.requestPointerCapture); - EXPECT_TRUE(frame.result.consumed); - - frame = UpdateUIEditorDockHostInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 800.0f, 600.0f), - { MakePointerUp(dropPoint.x, dropPoint.y) }); - EXPECT_TRUE(frame.result.releasePointerCapture); - EXPECT_TRUE(frame.result.layoutChanged) - << " status=" << static_cast(frame.result.layoutResult.status) - << " message=" << frame.result.layoutResult.message - << " commandExecuted=" << frame.result.commandExecuted; - - const auto* documentTabs = - FindUIEditorWorkspaceNode(controller.GetWorkspace(), "document-tabs"); - ASSERT_NE(documentTabs, nullptr); - ASSERT_EQ(documentTabs->children.size(), 2u); - EXPECT_EQ(documentTabs->children[0].panel.panelId, "doc-b"); - EXPECT_EQ(documentTabs->children[1].panel.panelId, "doc-a"); -} - -TEST(UIEditorDockHostInteractionTest, DraggingRightmostTabIntoMiddleCommitsSameStackReorder) { - auto controller = - BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildThreeDocumentWorkspace()); - UIEditorDockHostInteractionState state = {}; - - auto frame = UpdateUIEditorDockHostInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 800.0f, 600.0f), - {}); - const auto* documentStack = FindTabStackByNodeId(frame.layout, "document-tabs"); - ASSERT_NE(documentStack, nullptr); - const UIPoint sourceCenter = RectCenter(documentStack->tabStripLayout.tabHeaderRects[2]); - const UIPoint dropPoint( - documentStack->tabStripLayout.tabHeaderRects[1].x + 4.0f, - sourceCenter.y); - - frame = UpdateUIEditorDockHostInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 800.0f, 600.0f), - { MakePointerDown(sourceCenter.x, sourceCenter.y) }); - EXPECT_TRUE(frame.result.consumed); - - frame = UpdateUIEditorDockHostInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 800.0f, 600.0f), - { MakePointerMove(dropPoint.x, dropPoint.y) }); - EXPECT_TRUE(frame.result.requestPointerCapture); - EXPECT_TRUE(frame.result.consumed); - - frame = UpdateUIEditorDockHostInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 800.0f, 600.0f), - { MakePointerUp(dropPoint.x, dropPoint.y) }); - EXPECT_TRUE(frame.result.releasePointerCapture); - EXPECT_TRUE(frame.result.layoutChanged) - << " status=" << static_cast(frame.result.layoutResult.status) - << " message=" << frame.result.layoutResult.message - << " commandExecuted=" << frame.result.commandExecuted; - - const auto* documentTabs = - FindUIEditorWorkspaceNode(controller.GetWorkspace(), "document-tabs"); - ASSERT_NE(documentTabs, nullptr); - ASSERT_EQ(documentTabs->children.size(), 3u); - EXPECT_EQ(documentTabs->children[0].panel.panelId, "doc-a"); - EXPECT_EQ(documentTabs->children[1].panel.panelId, "doc-c"); - EXPECT_EQ(documentTabs->children[2].panel.panelId, "doc-b"); -} - -TEST(UIEditorDockHostInteractionTest, DraggingRightmostTabToFrontCommitsSameStackReorder) { - auto controller = - BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildThreeDocumentWorkspace()); - UIEditorDockHostInteractionState state = {}; - - auto frame = UpdateUIEditorDockHostInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 800.0f, 600.0f), - {}); - const auto* documentStack = FindTabStackByNodeId(frame.layout, "document-tabs"); - ASSERT_NE(documentStack, nullptr); - const UIPoint sourceCenter = RectCenter(documentStack->tabStripLayout.tabHeaderRects[2]); - const UIPoint dropPoint( - documentStack->tabStripLayout.tabHeaderRects[0].x + 4.0f, - sourceCenter.y); - - frame = UpdateUIEditorDockHostInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 800.0f, 600.0f), - { MakePointerDown(sourceCenter.x, sourceCenter.y) }); - EXPECT_TRUE(frame.result.consumed); - - frame = UpdateUIEditorDockHostInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 800.0f, 600.0f), - { MakePointerMove(dropPoint.x, dropPoint.y) }); - EXPECT_TRUE(frame.result.requestPointerCapture); - EXPECT_TRUE(frame.result.consumed); - - frame = UpdateUIEditorDockHostInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 800.0f, 600.0f), - { MakePointerUp(dropPoint.x, dropPoint.y) }); - EXPECT_TRUE(frame.result.releasePointerCapture); - EXPECT_TRUE(frame.result.layoutChanged); - - const auto* documentTabs = - FindUIEditorWorkspaceNode(controller.GetWorkspace(), "document-tabs"); - ASSERT_NE(documentTabs, nullptr); - ASSERT_EQ(documentTabs->children.size(), 3u); - EXPECT_EQ(documentTabs->children[0].panel.panelId, "doc-c"); - EXPECT_EQ(documentTabs->children[1].panel.panelId, "doc-a"); - EXPECT_EQ(documentTabs->children[2].panel.panelId, "doc-b"); -} - -TEST(UIEditorDockHostInteractionTest, SameStackReorderCommitsFromLastVisiblePreviewEvenIfReleaseDropsBelowHeader) { - auto controller = - BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); - UIEditorDockHostInteractionState state = {}; - - auto frame = UpdateUIEditorDockHostInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 800.0f, 600.0f), - {}); - const auto* documentStack = FindTabStackByNodeId(frame.layout, "document-tabs"); - ASSERT_NE(documentStack, nullptr); - const UIPoint sourceCenter = RectCenter(documentStack->tabStripLayout.tabHeaderRects[0]); - const UIPoint previewPoint( - documentStack->tabStripLayout.headerRect.x + - documentStack->tabStripLayout.headerRect.width - 2.0f, - sourceCenter.y); - const UIPoint releasePoint( - previewPoint.x, - documentStack->tabStripLayout.headerRect.y + - documentStack->tabStripLayout.headerRect.height + 8.0f); - - frame = UpdateUIEditorDockHostInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 800.0f, 600.0f), - { MakePointerDown(sourceCenter.x, sourceCenter.y) }); - EXPECT_TRUE(frame.result.consumed); - - frame = UpdateUIEditorDockHostInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 800.0f, 600.0f), - { MakePointerMove(previewPoint.x, previewPoint.y) }); - EXPECT_TRUE(frame.result.requestPointerCapture); - EXPECT_TRUE(frame.result.consumed); - const auto tabStripStateIt = std::find_if( - state.dockHostState.tabStripStates.begin(), - state.dockHostState.tabStripStates.end(), - [](const auto& tabStripState) { - return tabStripState.nodeId == "document-tabs"; - }); - ASSERT_NE(tabStripStateIt, state.dockHostState.tabStripStates.end()); - ASSERT_NE( - tabStripStateIt->state.reorder.previewInsertionIndex, - XCEngine::UI::Editor::Widgets::UIEditorTabStripInvalidIndex); - - frame = UpdateUIEditorDockHostInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 800.0f, 600.0f), - { MakePointerUp(releasePoint.x, releasePoint.y) }); - EXPECT_TRUE(frame.result.releasePointerCapture); - EXPECT_TRUE(frame.result.layoutChanged); - - const auto* documentTabs = - FindUIEditorWorkspaceNode(controller.GetWorkspace(), "document-tabs"); - ASSERT_NE(documentTabs, nullptr); - ASSERT_EQ(documentTabs->children.size(), 2u); - EXPECT_EQ(documentTabs->children[0].panel.panelId, "doc-b"); - EXPECT_EQ(documentTabs->children[1].panel.panelId, "doc-a"); -} - TEST(UIEditorDockHostInteractionTest, ReleasingDraggedTabOutsideHeaderCancelsWithoutChangingOrder) { auto controller = BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); diff --git a/tests/UI/Editor/unit/test_ui_editor_hosted_field_builders.cpp b/tests/UI/Editor/unit/test_ui_editor_hosted_field_builders.cpp index 677e2b5d..a4986775 100644 --- a/tests/UI/Editor/unit/test_ui_editor_hosted_field_builders.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_hosted_field_builders.cpp @@ -120,6 +120,7 @@ TEST(UIEditorHostedFieldBuildersTest, HostedFieldBuildersInheritPropertyGridMetr const auto colorMetrics = Editor::BuildUIEditorPropertyGridColorFieldMetrics(propertyMetrics); const auto colorPalette = Editor::BuildUIEditorPropertyGridColorFieldPalette(propertyPalette); + const auto defaultColorPalette = UI::Editor::Widgets::UIEditorColorFieldPalette {}; EXPECT_FLOAT_EQ(colorMetrics.controlTrailingInset, 5.0f); EXPECT_FLOAT_EQ(colorMetrics.swatchInsetY, 2.0f); EXPECT_FLOAT_EQ(colorMetrics.labelFontSize, 10.0f); @@ -127,7 +128,7 @@ TEST(UIEditorHostedFieldBuildersTest, HostedFieldBuildersInheritPropertyGridMetr EXPECT_FLOAT_EQ(colorMetrics.popupHeaderHeight, 30.0f); EXPECT_FLOAT_EQ(colorPalette.labelColor.r, 0.8f); EXPECT_FLOAT_EQ(colorPalette.popupBorderColor.r, 0.15f); - EXPECT_FLOAT_EQ(colorPalette.popupHeaderColor.r, 0.43f); + EXPECT_FLOAT_EQ(colorPalette.popupHeaderColor.r, defaultColorPalette.popupHeaderColor.r); EXPECT_FLOAT_EQ(colorPalette.swatchBorderColor.r, 0.5f); const auto assetMetrics = Editor::BuildUIEditorPropertyGridAssetFieldMetrics(propertyMetrics); diff --git a/tests/UI/Editor/unit/test_ui_editor_menu_bar.cpp b/tests/UI/Editor/unit/test_ui_editor_menu_bar.cpp index 0c16fef3..1d59d8b4 100644 --- a/tests/UI/Editor/unit/test_ui_editor_menu_bar.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_menu_bar.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_menu_model.cpp b/tests/UI/Editor/unit/test_ui_editor_menu_model.cpp index 1e2f5244..cfd92583 100644 --- a/tests/UI/Editor/unit/test_ui_editor_menu_model.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_menu_model.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include #include diff --git a/tests/UI/Editor/unit/test_ui_editor_menu_popup.cpp b/tests/UI/Editor/unit/test_ui_editor_menu_popup.cpp index 23ca9b85..d9a5d715 100644 --- a/tests/UI/Editor/unit/test_ui_editor_menu_popup.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_menu_popup.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_menu_session.cpp b/tests/UI/Editor/unit/test_ui_editor_menu_session.cpp index e34667d8..e6da2db1 100644 --- a/tests/UI/Editor/unit/test_ui_editor_menu_session.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_menu_session.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include diff --git a/tests/UI/Editor/unit/test_ui_editor_panel_content_host.cpp b/tests/UI/Editor/unit/test_ui_editor_panel_content_host.cpp index 835a85a5..4b99ec3b 100644 --- a/tests/UI/Editor/unit/test_ui_editor_panel_content_host.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_panel_content_host.cpp @@ -1,8 +1,8 @@ #include -#include -#include -#include +#include +#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_panel_frame.cpp b/tests/UI/Editor/unit/test_ui_editor_panel_frame.cpp index 669306e0..42b6a6cb 100644 --- a/tests/UI/Editor/unit/test_ui_editor_panel_frame.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_panel_frame.cpp @@ -1,7 +1,9 @@ #include +#include + #include -#include +#include namespace { @@ -20,6 +22,7 @@ using XCEngine::UI::Editor::Widgets::ResolveUIEditorPanelFrameBorderThickness; using XCEngine::UI::Editor::Widgets::UIEditorPanelFrameAction; using XCEngine::UI::Editor::Widgets::UIEditorPanelFrameHitTarget; using XCEngine::UI::Editor::Widgets::UIEditorPanelFrameLayout; +using XCEngine::UI::Editor::Widgets::UIEditorPanelFrameMetrics; using XCEngine::UI::Editor::Widgets::UIEditorPanelFramePalette; using XCEngine::UI::Editor::Widgets::UIEditorPanelFrameState; using XCEngine::UI::Editor::Widgets::UIEditorPanelFrameText; @@ -41,34 +44,43 @@ TEST(UIEditorPanelFrameTest, LayoutBuildsHeaderBodyFooterAndActionRectsFromState state.pinnable = true; state.showFooter = true; + const UIEditorPanelFrameMetrics metrics = {}; const UIEditorPanelFrameLayout layout = - BuildUIEditorPanelFrameLayout(UIRect(10.0f, 20.0f, 300.0f, 200.0f), state); + BuildUIEditorPanelFrameLayout(UIRect(10.0f, 20.0f, 300.0f, 200.0f), state, metrics); EXPECT_FLOAT_EQ(layout.headerRect.x, 10.0f); EXPECT_FLOAT_EQ(layout.headerRect.y, 20.0f); EXPECT_FLOAT_EQ(layout.headerRect.width, 300.0f); - EXPECT_FLOAT_EQ(layout.headerRect.height, 36.0f); + EXPECT_FLOAT_EQ(layout.headerRect.height, metrics.headerHeight); EXPECT_TRUE(layout.hasFooter); - EXPECT_FLOAT_EQ(layout.footerRect.y, 196.0f); - EXPECT_FLOAT_EQ(layout.footerRect.height, 24.0f); + EXPECT_FLOAT_EQ(layout.footerRect.y, 20.0f + 200.0f - metrics.footerHeight); + EXPECT_FLOAT_EQ(layout.footerRect.height, metrics.footerHeight); - EXPECT_FLOAT_EQ(layout.bodyRect.x, 22.0f); - EXPECT_FLOAT_EQ(layout.bodyRect.y, 68.0f); - EXPECT_FLOAT_EQ(layout.bodyRect.width, 276.0f); - EXPECT_FLOAT_EQ(layout.bodyRect.height, 116.0f); + EXPECT_FLOAT_EQ(layout.bodyRect.x, 10.0f + metrics.contentPadding); + EXPECT_FLOAT_EQ(layout.bodyRect.y, 20.0f + metrics.headerHeight + metrics.contentPadding); + EXPECT_FLOAT_EQ(layout.bodyRect.width, 300.0f - metrics.contentPadding * 2.0f); + EXPECT_FLOAT_EQ( + layout.bodyRect.height, + 200.0f - metrics.headerHeight - metrics.footerHeight - metrics.contentPadding * 2.0f); EXPECT_TRUE(layout.showPinButton); EXPECT_TRUE(layout.showCloseButton); - EXPECT_FLOAT_EQ(layout.closeButtonRect.x, 286.0f); - EXPECT_FLOAT_EQ(layout.closeButtonRect.y, 32.0f); - EXPECT_FLOAT_EQ(layout.closeButtonRect.width, 12.0f); - EXPECT_FLOAT_EQ(layout.pinButtonRect.x, 268.0f); - EXPECT_FLOAT_EQ(layout.pinButtonRect.y, 32.0f); + const float buttonExtent = + (std::min)(metrics.actionButtonExtent, metrics.headerHeight - metrics.actionInsetX * 2.0f); + const float buttonY = 20.0f + (metrics.headerHeight - buttonExtent) * 0.5f; + EXPECT_FLOAT_EQ(layout.closeButtonRect.width, buttonExtent); + EXPECT_FLOAT_EQ(layout.closeButtonRect.x, 10.0f + 300.0f - metrics.actionInsetX - buttonExtent); + EXPECT_FLOAT_EQ(layout.closeButtonRect.y, buttonY); + EXPECT_FLOAT_EQ( + layout.pinButtonRect.x, + layout.closeButtonRect.x - metrics.actionGap - buttonExtent); + EXPECT_FLOAT_EQ(layout.pinButtonRect.y, buttonY); } TEST(UIEditorPanelFrameTest, BorderPolicyUsesFocusBeforeActiveBeforeHover) { const UIEditorPanelFramePalette palette = {}; + const UIEditorPanelFrameMetrics metrics = {}; UIEditorPanelFrameState hoveredOnly = {}; hoveredOnly.hovered = true; @@ -87,10 +99,18 @@ TEST(UIEditorPanelFrameTest, BorderPolicyUsesFocusBeforeActiveBeforeHover) { ResolveUIEditorPanelFrameBorderColor(focusedState, palette), palette.focusedBorderColor); - EXPECT_FLOAT_EQ(ResolveUIEditorPanelFrameBorderThickness(UIEditorPanelFrameState{}), 1.0f); - EXPECT_FLOAT_EQ(ResolveUIEditorPanelFrameBorderThickness(hoveredOnly), 1.25f); - EXPECT_FLOAT_EQ(ResolveUIEditorPanelFrameBorderThickness(activeOnly), 1.5f); - EXPECT_FLOAT_EQ(ResolveUIEditorPanelFrameBorderThickness(focusedState), 2.0f); + EXPECT_FLOAT_EQ( + ResolveUIEditorPanelFrameBorderThickness(UIEditorPanelFrameState{}, metrics), + metrics.baseBorderThickness); + EXPECT_FLOAT_EQ( + ResolveUIEditorPanelFrameBorderThickness(hoveredOnly, metrics), + metrics.hoveredBorderThickness); + EXPECT_FLOAT_EQ( + ResolveUIEditorPanelFrameBorderThickness(activeOnly, metrics), + metrics.activeBorderThickness); + EXPECT_FLOAT_EQ( + ResolveUIEditorPanelFrameBorderThickness(focusedState, metrics), + metrics.focusedBorderThickness); } TEST(UIEditorPanelFrameTest, HitTestReturnsActionAndRegionTargetsFromLayout) { @@ -103,10 +123,20 @@ TEST(UIEditorPanelFrameTest, HitTestReturnsActionAndRegionTargetsFromLayout) { BuildUIEditorPanelFrameLayout(UIRect(10.0f, 20.0f, 300.0f, 200.0f), state); EXPECT_EQ( - HitTestUIEditorPanelFrameAction(layout, state, UIPoint(269.0f, 35.0f)), + HitTestUIEditorPanelFrameAction( + layout, + state, + UIPoint( + layout.pinButtonRect.x + layout.pinButtonRect.width * 0.5f, + layout.pinButtonRect.y + layout.pinButtonRect.height * 0.5f)), UIEditorPanelFrameAction::Pin); EXPECT_EQ( - HitTestUIEditorPanelFrameAction(layout, state, UIPoint(287.0f, 35.0f)), + HitTestUIEditorPanelFrameAction( + layout, + state, + UIPoint( + layout.closeButtonRect.x + layout.closeButtonRect.width * 0.5f, + layout.closeButtonRect.y + layout.closeButtonRect.height * 0.5f)), UIEditorPanelFrameAction::Close); EXPECT_EQ( HitTestUIEditorPanelFrame(layout, state, UIPoint(40.0f, 35.0f)), @@ -141,12 +171,14 @@ TEST(UIEditorPanelFrameTest, BackgroundAndForegroundEmitExpectedChromeAndActionC UIDrawList background("PanelFrameBackground"); AppendUIEditorPanelFrameBackground(background, layout, state, palette); - ASSERT_EQ(background.GetCommandCount(), 4u); + ASSERT_EQ(background.GetCommandCount(), 6u); const auto& backgroundCommands = background.GetCommands(); EXPECT_EQ(backgroundCommands[0].type, UIDrawCommandType::FilledRect); EXPECT_EQ(backgroundCommands[1].type, UIDrawCommandType::RectOutline); EXPECT_EQ(backgroundCommands[2].type, UIDrawCommandType::FilledRect); - EXPECT_EQ(backgroundCommands[3].type, UIDrawCommandType::FilledRect); + EXPECT_EQ(backgroundCommands[3].type, UIDrawCommandType::Line); + EXPECT_EQ(backgroundCommands[4].type, UIDrawCommandType::FilledRect); + EXPECT_EQ(backgroundCommands[5].type, UIDrawCommandType::Line); ExpectColorEq(backgroundCommands[1].color, palette.focusedBorderColor); UIDrawList foreground("PanelFrameForeground"); diff --git a/tests/UI/Editor/unit/test_ui_editor_panel_host_lifecycle.cpp b/tests/UI/Editor/unit/test_ui_editor_panel_host_lifecycle.cpp index 5ba1140e..7a4acaf1 100644 --- a/tests/UI/Editor/unit/test_ui_editor_panel_host_lifecycle.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_panel_host_lifecycle.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_panel_registry.cpp b/tests/UI/Editor/unit/test_ui_editor_panel_registry.cpp index 57371355..ae43ab0d 100644 --- a/tests/UI/Editor/unit/test_ui_editor_panel_registry.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_panel_registry.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_shell_compose.cpp b/tests/UI/Editor/unit/test_ui_editor_shell_compose.cpp index b68b489d..ff158aee 100644 --- a/tests/UI/Editor/unit/test_ui_editor_shell_compose.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_shell_compose.cpp @@ -2,8 +2,8 @@ #include #include -#include -#include +#include +#include namespace { @@ -113,8 +113,8 @@ TEST(UIEditorShellComposeTest, LayoutAllocatesMenuWorkspaceAndStatusBandsWithout EXPECT_GT(layout.statusBarRect.height, 0.0f); EXPECT_LE(layout.menuBarRect.y + layout.menuBarRect.height, layout.workspaceRect.y); EXPECT_LE(layout.workspaceRect.y + layout.workspaceRect.height, layout.statusBarRect.y); - EXPECT_FLOAT_EQ(layout.contentRect.x, 12.0f); - EXPECT_FLOAT_EQ(layout.contentRect.y, 12.0f); + EXPECT_FLOAT_EQ(layout.contentRect.x, 0.0f); + EXPECT_FLOAT_EQ(layout.contentRect.y, 0.0f); } TEST(UIEditorShellComposeTest, ResolveRequestRoutesWorkspaceComposeThroughWorkspaceBand) { diff --git a/tests/UI/Editor/unit/test_ui_editor_shell_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_shell_interaction.cpp index eacc709c..ef27eb18 100644 --- a/tests/UI/Editor/unit/test_ui_editor_shell_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_shell_interaction.cpp @@ -1,8 +1,8 @@ #include #include -#include -#include +#include +#include #include @@ -40,6 +40,7 @@ using XCEngine::UI::Editor::UIEditorShellInteractionMenuButtonRequest; using XCEngine::UI::Editor::UIEditorShellInteractionModel; using XCEngine::UI::Editor::UIEditorShellInteractionPopupItemRequest; using XCEngine::UI::Editor::UIEditorShellInteractionServices; +using XCEngine::UI::Editor::UIEditorShellInteractionRequest; using XCEngine::UI::Editor::UIEditorShellInteractionState; using XCEngine::UI::Editor::UIEditorTextMeasureRequest; using XCEngine::UI::Editor::UIEditorTextMeasurer; @@ -54,6 +55,8 @@ using XCEngine::UI::Editor::Widgets::UIEditorMenuPopupInvalidIndex; using XCEngine::UI::Editor::Widgets::UIEditorStatusBarSlot; using XCEngine::UI::Widgets::UIPopupDismissReason; +constexpr UIRect kShellBounds(0.0f, 0.0f, 1280.0f, 720.0f); + class StubTextMeasurer final : public UIEditorTextMeasurer { public: float MeasureTextWidth(const UIEditorTextMeasureRequest& request) const override { @@ -310,6 +313,18 @@ const UIEditorShellInteractionMenuButtonRequest* FindMenuButton( return nullptr; } +const UIEditorShellInteractionMenuButtonRequest* FindMenuButton( + const UIEditorShellInteractionRequest& request, + std::string_view menuId) { + for (const auto& button : request.menuButtons) { + if (button.menuId == menuId) { + return &button; + } + } + + return nullptr; +} + const UIEditorShellInteractionPopupItemRequest* FindPopupItem( const UIEditorShellInteractionFrame& frame, std::string_view itemId) { @@ -343,6 +358,36 @@ UIInputEvent MakeLeftPointerDown(const UIPoint& position) { return event; } +UIEditorShellInteractionFrame OpenRootMenu( + UIEditorShellInteractionState& state, + UIEditorWorkspaceController& controller, + const UIEditorShellInteractionModel& model, + std::string_view menuId, + const UIEditorShellInteractionServices& services = {}, + const XCEngine::UI::Editor::UIEditorShellInteractionMetrics& metrics = {}) { + const UIEditorShellInteractionRequest request = ResolveUIEditorShellInteractionRequest( + kShellBounds, + controller, + model, + state, + metrics, + services); + const auto* button = FindMenuButton(request, menuId); + if (button == nullptr) { + ADD_FAILURE() << "Menu button not found: " << menuId; + return {}; + } + + return UpdateUIEditorShellInteraction( + state, + controller, + kShellBounds, + model, + { MakeLeftPointerDown(RectCenter(button->rect)) }, + services, + metrics); +} + UIInputEvent MakeKeyDown(KeyCode keyCode) { UIInputEvent event = {}; event.type = UIInputEventType::KeyDown; @@ -389,12 +434,7 @@ TEST(UIEditorShellInteractionTest, HoverOtherRootSwitchesRootPopup) { const auto model = BuildInteractionModel(); UIEditorShellInteractionState state = {}; - auto frame = UpdateUIEditorShellInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 1280.0f, 720.0f), - model, - { MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) }); + auto frame = OpenRootMenu(state, controller, model, "file"); const auto* windowButton = FindMenuButton(frame, "window"); ASSERT_NE(windowButton, nullptr); @@ -416,12 +456,7 @@ TEST(UIEditorShellInteractionTest, HoverSubmenuOpensChildPopupAndEscapeCollapses const auto model = BuildInteractionModel(); UIEditorShellInteractionState state = {}; - auto frame = UpdateUIEditorShellInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 1280.0f, 720.0f), - model, - { MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) }); + auto frame = OpenRootMenu(state, controller, model, "file"); const auto* submenuItem = FindPopupItem(frame, "file-workspace-tools"); ASSERT_NE(submenuItem, nullptr); ASSERT_TRUE(submenuItem->hasSubmenu); @@ -461,12 +496,7 @@ TEST(UIEditorShellInteractionTest, ClickCommandReturnsDispatchHookAndClosesMenu) const auto model = BuildInteractionModel(); UIEditorShellInteractionState state = {}; - auto frame = UpdateUIEditorShellInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 1280.0f, 720.0f), - model, - { MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) }); + auto frame = OpenRootMenu(state, controller, model, "file"); const auto* commandItem = FindPopupItem(frame, "file-close"); ASSERT_NE(commandItem, nullptr); @@ -593,12 +623,7 @@ TEST(UIEditorShellInteractionTest, PointerDownOutsideDismissesWholeMenuChain) { const auto model = BuildInteractionModel(); UIEditorShellInteractionState state = {}; - auto frame = UpdateUIEditorShellInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 1280.0f, 720.0f), - model, - { MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) }); + auto frame = OpenRootMenu(state, controller, model, "file"); ASSERT_TRUE(state.menuSession.HasOpenMenu()); frame = UpdateUIEditorShellInteraction( @@ -619,12 +644,7 @@ TEST(UIEditorShellInteractionTest, DisabledSubmenuDoesNotOpenChildPopup) { model.resolvedMenuModel.menus[0].items[0].enabled = false; UIEditorShellInteractionState state = {}; - auto frame = UpdateUIEditorShellInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 1280.0f, 720.0f), - model, - { MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) }); + auto frame = OpenRootMenu(state, controller, model, "file"); const auto* submenuItem = FindPopupItem(frame, "file-workspace-tools"); ASSERT_NE(submenuItem, nullptr); ASSERT_TRUE(submenuItem->hasSubmenu); @@ -665,12 +685,7 @@ TEST(UIEditorShellInteractionTest, HoverDisabledSubmenuSiblingClosesOnlyOpenChil model.resolvedMenuModel.menus[0].items.push_back(disabledSibling); UIEditorShellInteractionState state = {}; - auto frame = UpdateUIEditorShellInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 1280.0f, 720.0f), - model, - { MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) }); + auto frame = OpenRootMenu(state, controller, model, "file"); const auto* submenuItem = FindPopupItem(frame, "file-workspace-tools"); const auto* disabledSiblingItem = FindPopupItem(frame, "file-disabled-tools"); ASSERT_NE(submenuItem, nullptr); @@ -710,12 +725,7 @@ TEST(UIEditorShellInteractionTest, FocusLostDismissesWholeMenuChain) { const auto model = BuildInteractionModel(); UIEditorShellInteractionState state = {}; - auto frame = UpdateUIEditorShellInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 1280.0f, 720.0f), - model, - { MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) }); + auto frame = OpenRootMenu(state, controller, model, "file"); const auto* submenuItem = FindPopupItem(frame, "file-workspace-tools"); ASSERT_NE(submenuItem, nullptr); @@ -817,12 +827,7 @@ TEST(UIEditorShellInteractionTest, OpenMenuConsumesWorkspacePointerDownForThatFr const auto model = BuildInteractionModel(); UIEditorShellInteractionState state = {}; - auto frame = UpdateUIEditorShellInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 1280.0f, 720.0f), - model, - { MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) }); + auto frame = OpenRootMenu(state, controller, model, "file"); ASSERT_TRUE(state.menuSession.HasOpenMenu()); ASSERT_FALSE(frame.shellFrame.workspaceFrame.viewportFrames.empty()); @@ -879,12 +884,7 @@ TEST(UIEditorShellInteractionTest, ClosingMenuRestoresWorkspaceInteractionOnNext const auto model = BuildInteractionModel(); UIEditorShellInteractionState state = {}; - auto frame = UpdateUIEditorShellInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 1280.0f, 720.0f), - model, - { MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) }); + auto frame = OpenRootMenu(state, controller, model, "file"); ASSERT_TRUE(state.menuSession.HasOpenMenu()); ASSERT_FALSE(frame.shellFrame.workspaceFrame.viewportFrames.empty()); @@ -920,12 +920,7 @@ TEST(UIEditorShellInteractionTest, InvalidResolvedMenuStateClosesInvisibleModalC const auto model = BuildInteractionModel(); UIEditorShellInteractionState state = {}; - auto frame = UpdateUIEditorShellInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 1280.0f, 720.0f), - model, - { MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) }); + auto frame = OpenRootMenu(state, controller, model, "file"); ASSERT_TRUE(state.menuSession.HasOpenMenu()); ASSERT_EQ(frame.request.popupRequests.size(), 1u); @@ -951,12 +946,7 @@ TEST(UIEditorShellInteractionTest, OpenedSubmenuClosesWhenSourceItemBecomesDisab auto model = BuildInteractionModel(); UIEditorShellInteractionState state = {}; - auto frame = UpdateUIEditorShellInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 1280.0f, 720.0f), - model, - { MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) }); + auto frame = OpenRootMenu(state, controller, model, "file"); const auto* submenuItem = FindPopupItem(frame, "file-workspace-tools"); ASSERT_NE(submenuItem, nullptr); @@ -988,12 +978,7 @@ TEST(UIEditorShellInteractionTest, OpenedSubmenuClosesWhenSourceItemLosesChildre auto model = BuildInteractionModel(); UIEditorShellInteractionState state = {}; - auto frame = UpdateUIEditorShellInteraction( - state, - controller, - UIRect(0.0f, 0.0f, 1280.0f, 720.0f), - model, - { MakeLeftPointerDown(UIPoint(40.0f, 30.0f)) }); + auto frame = OpenRootMenu(state, controller, model, "file"); const auto* submenuItem = FindPopupItem(frame, "file-workspace-tools"); ASSERT_NE(submenuItem, nullptr); diff --git a/tests/UI/Editor/unit/test_ui_editor_status_bar.cpp b/tests/UI/Editor/unit/test_ui_editor_status_bar.cpp index 75744ca9..17eb7b14 100644 --- a/tests/UI/Editor/unit/test_ui_editor_status_bar.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_status_bar.cpp @@ -48,27 +48,48 @@ TEST(UIEditorStatusBarTest, DesiredWidthUsesExplicitValueBeforeLabelEstimate) { UIEditorStatusBarSegment inferredWidth = {}; inferredWidth.label = "Scene"; + const XCEngine::UI::Editor::Widgets::UIEditorStatusBarMetrics metrics = {}; - EXPECT_FLOAT_EQ(ResolveUIEditorStatusBarDesiredSegmentWidth(explicitWidth), 84.0f); - EXPECT_FLOAT_EQ(ResolveUIEditorStatusBarDesiredSegmentWidth(inferredWidth), 55.0f); + EXPECT_FLOAT_EQ(ResolveUIEditorStatusBarDesiredSegmentWidth(explicitWidth, metrics), 84.0f); + EXPECT_FLOAT_EQ( + ResolveUIEditorStatusBarDesiredSegmentWidth(inferredWidth, metrics), + metrics.segmentPaddingX * 2.0f + + static_cast(inferredWidth.label.size()) * metrics.estimatedGlyphWidth); } TEST(UIEditorStatusBarTest, LayoutBuildsLeadingAndTrailingSlotsWithSeparators) { + const XCEngine::UI::Editor::Widgets::UIEditorStatusBarMetrics metrics = {}; const UIEditorStatusBarLayout layout = - BuildUIEditorStatusBarLayout(UIRect(20.0f, 40.0f, 520.0f, 28.0f), BuildSegments()); + BuildUIEditorStatusBarLayout(UIRect(20.0f, 40.0f, 520.0f, 28.0f), BuildSegments(), metrics); + const float leftStart = 20.0f + metrics.outerPaddingX; + const float rightLimit = 20.0f + 520.0f - metrics.outerPaddingX; - EXPECT_FLOAT_EQ(layout.leadingSlotRect.x, 30.0f); - EXPECT_FLOAT_EQ(layout.leadingSlotRect.width, 235.0f); - EXPECT_FLOAT_EQ(layout.trailingSlotRect.x, 371.0f); - EXPECT_FLOAT_EQ(layout.trailingSlotRect.width, 159.0f); + EXPECT_FLOAT_EQ(layout.leadingSlotRect.x, leftStart); + EXPECT_FLOAT_EQ(layout.segmentRects[0].x, leftStart); + EXPECT_FLOAT_EQ( + layout.separatorRects[0].x, + layout.segmentRects[0].x + layout.segmentRects[0].width); + EXPECT_FLOAT_EQ( + layout.segmentRects[1].x, + layout.separatorRects[0].x + layout.separatorRects[0].width + metrics.segmentGap); + EXPECT_FLOAT_EQ( + layout.leadingSlotRect.width, + layout.segmentRects[1].x + layout.segmentRects[1].width - layout.leadingSlotRect.x); - EXPECT_FLOAT_EQ(layout.segmentRects[0].x, 30.0f); - EXPECT_FLOAT_EQ(layout.segmentRects[1].x, 127.0f); - EXPECT_FLOAT_EQ(layout.segmentRects[2].x, 371.0f); - EXPECT_FLOAT_EQ(layout.segmentRects[3].x, 444.0f); + EXPECT_FLOAT_EQ( + layout.segmentRects[3].x + layout.segmentRects[3].width, + rightLimit); + EXPECT_FLOAT_EQ( + layout.separatorRects[2].x, + layout.segmentRects[2].x + layout.segmentRects[2].width + metrics.segmentGap); + EXPECT_FLOAT_EQ( + layout.segmentRects[3].x, + layout.separatorRects[2].x + layout.separatorRects[2].width + metrics.segmentGap); + EXPECT_FLOAT_EQ(layout.trailingSlotRect.x, layout.segmentRects[2].x); + EXPECT_FLOAT_EQ( + layout.trailingSlotRect.width, + rightLimit - layout.trailingSlotRect.x); - EXPECT_FLOAT_EQ(layout.separatorRects[0].x, 122.0f); - EXPECT_FLOAT_EQ(layout.separatorRects[2].x, 439.0f); EXPECT_FLOAT_EQ(layout.separatorRects[1].width, 0.0f); } @@ -76,11 +97,19 @@ TEST(UIEditorStatusBarTest, HitTestReturnsSeparatorThenSegmentThenBackground) { const UIEditorStatusBarLayout layout = BuildUIEditorStatusBarLayout(UIRect(20.0f, 40.0f, 520.0f, 28.0f), BuildSegments()); - auto hit = HitTestUIEditorStatusBar(layout, UIPoint(122.5f, 54.0f)); + auto hit = HitTestUIEditorStatusBar( + layout, + UIPoint( + layout.separatorRects[0].x + layout.separatorRects[0].width * 0.5f, + layout.separatorRects[0].y + layout.separatorRects[0].height * 0.5f)); EXPECT_EQ(hit.kind, UIEditorStatusBarHitTargetKind::Separator); EXPECT_EQ(hit.index, 0u); - hit = HitTestUIEditorStatusBar(layout, UIPoint(150.0f, 54.0f)); + hit = HitTestUIEditorStatusBar( + layout, + UIPoint( + layout.segmentRects[1].x + layout.segmentRects[1].width * 0.5f, + layout.segmentRects[1].y + layout.segmentRects[1].height * 0.5f)); EXPECT_EQ(hit.kind, UIEditorStatusBarHitTargetKind::Segment); EXPECT_EQ(hit.index, 1u); diff --git a/tests/UI/Editor/unit/test_ui_editor_tab_strip.cpp b/tests/UI/Editor/unit/test_ui_editor_tab_strip.cpp index a749092d..11578aab 100644 --- a/tests/UI/Editor/unit/test_ui_editor_tab_strip.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_tab_strip.cpp @@ -26,16 +26,13 @@ TEST(UIEditorTabStripTest, DesiredHeaderWidthUsesLabelWidthAndLeftInsetOnly) { UIEditorTabStripMetrics metrics = {}; metrics.layoutMetrics.tabHorizontalPadding = 10.0f; metrics.estimatedGlyphWidth = 8.0f; - metrics.closeButtonExtent = 14.0f; - metrics.closeButtonGap = 6.0f; - metrics.closeInsetRight = 14.0f; metrics.labelInsetX = 12.0f; const float measuredWidth = ResolveUIEditorTabStripDesiredHeaderLabelWidth( - UIEditorTabStripItem{ "doc-a", "ABCD", true, 0.0f }, + UIEditorTabStripItem{ "doc-a", "ABCD", 0.0f }, metrics); const float fixedWidth = ResolveUIEditorTabStripDesiredHeaderLabelWidth( - UIEditorTabStripItem{ "doc-b", "Ignored", false, 42.0f }, + UIEditorTabStripItem{ "doc-b", "Ignored", 42.0f }, metrics); EXPECT_FLOAT_EQ(measuredWidth, 34.0f); @@ -44,9 +41,9 @@ TEST(UIEditorTabStripTest, DesiredHeaderWidthUsesLabelWidthAndLeftInsetOnly) { TEST(UIEditorTabStripTest, SelectedIndexResolvesByTabIdAndFallsBackToValidRange) { const std::vector items = { - { "doc-a", "Document A", true, 0.0f }, - { "doc-b", "Document B", true, 0.0f }, - { "doc-c", "Document C", false, 0.0f } + { "doc-a", "Document A", 0.0f }, + { "doc-b", "Document B", 0.0f }, + { "doc-c", "Document C", 0.0f } }; EXPECT_EQ(ResolveUIEditorTabStripSelectedIndex(items, "doc-b"), 1u); @@ -63,14 +60,11 @@ TEST(UIEditorTabStripTest, LayoutUsesCoreTabArrangementWithoutCloseButtons) { metrics.layoutMetrics.tabMinWidth = 80.0f; metrics.layoutMetrics.tabHorizontalPadding = 12.0f; metrics.layoutMetrics.tabGap = 4.0f; - metrics.closeButtonExtent = 12.0f; - metrics.closeButtonGap = 6.0f; - metrics.closeInsetRight = 12.0f; metrics.labelInsetX = 12.0f; const std::vector items = { - { "doc-a", "Document A", true, 48.0f }, - { "doc-b", "Document B", false, 40.0f } + { "doc-a", "Document A", 48.0f }, + { "doc-b", "Document B", 40.0f } }; UIEditorTabStripState state = {}; @@ -94,16 +88,12 @@ TEST(UIEditorTabStripTest, LayoutUsesCoreTabArrangementWithoutCloseButtons) { EXPECT_FLOAT_EQ(layout.tabHeaderRects[0].width, 80.0f); EXPECT_FLOAT_EQ(layout.tabHeaderRects[1].x, 94.0f); EXPECT_FLOAT_EQ(layout.tabHeaderRects[1].width, 80.0f); - - ASSERT_EQ(layout.closeButtonRects.size(), 2u); - EXPECT_FALSE(layout.showCloseButtons[0]); - EXPECT_FALSE(layout.showCloseButtons[1]); } TEST(UIEditorTabStripTest, HitTestPrioritizesTabThenHeaderThenContent) { const std::vector items = { - { "doc-a", "Document A", true, 48.0f }, - { "doc-b", "Document B", false, 40.0f } + { "doc-a", "Document A", 48.0f }, + { "doc-b", "Document B", 40.0f } }; UIEditorTabStripState state = {}; @@ -135,8 +125,8 @@ TEST(UIEditorTabStripTest, HitTestPrioritizesTabThenHeaderThenContent) { TEST(UIEditorTabStripTest, BackgroundAndForegroundEmitStableChromeCommands) { const std::vector items = { - { "doc-a", "Document A", true, 48.0f }, - { "doc-b", "Document B", false, 40.0f } + { "doc-a", "Document A", 48.0f }, + { "doc-b", "Document B", 40.0f } }; UIEditorTabStripState state = {}; @@ -183,8 +173,8 @@ TEST(UIEditorTabStripTest, ForegroundCentersTabLabelsWithinHeaderContentArea) { metrics.labelInsetX = 8.0f; const std::vector items = { - { "doc-a", "Scene", true, 30.0f }, - { "doc-b", "Game", true, 24.0f } + { "doc-a", "Scene", 30.0f }, + { "doc-b", "Game", 24.0f } }; UIEditorTabStripState state = {}; diff --git a/tests/UI/Editor/unit/test_ui_editor_tab_strip_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_tab_strip_interaction.cpp index 2e8d97d3..9553c778 100644 --- a/tests/UI/Editor/unit/test_ui_editor_tab_strip_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_tab_strip_interaction.cpp @@ -20,9 +20,9 @@ using XCEngine::UI::Editor::Widgets::UIEditorTabStripItem; std::vector BuildTabItems() { return { - { "doc-a", "Document A", true, 48.0f }, - { "doc-b", "Document B", true, 46.0f }, - { "doc-c", "Document C", false, 44.0f } + { "doc-a", "Document A", 48.0f }, + { "doc-b", "Document B", 46.0f }, + { "doc-c", "Document C", 44.0f } }; } @@ -97,8 +97,6 @@ TEST(UIEditorTabStripInteractionTest, PointerMoveUpdatesHoveredTabOnly) { EXPECT_EQ(frame.result.hitTarget.kind, UIEditorTabStripHitTargetKind::Tab); EXPECT_EQ(state.tabStripState.hoveredIndex, 1u); - EXPECT_EQ(state.tabStripState.closeHoveredIndex, UIEditorTabStripInvalidIndex); - EXPECT_FALSE(frame.result.closeRequested); } TEST(UIEditorTabStripInteractionTest, LeftClickTabSelectsAndFocusesStrip) { @@ -175,7 +173,6 @@ TEST(UIEditorTabStripInteractionTest, OutsideClickAndFocusLostClearFocusAndHover UIEditorTabStripInteractionState state = {}; state.tabStripState.focused = true; state.tabStripState.hoveredIndex = 1u; - state.tabStripState.closeHoveredIndex = 1u; auto frame = UpdateUIEditorTabStripInteraction( state, @@ -198,11 +195,10 @@ TEST(UIEditorTabStripInteractionTest, OutsideClickAndFocusLostClearFocusAndHover { MakePointerLeave(), MakeFocusLost() }); EXPECT_FALSE(state.tabStripState.focused); EXPECT_EQ(state.tabStripState.hoveredIndex, UIEditorTabStripInvalidIndex); - EXPECT_EQ(state.tabStripState.closeHoveredIndex, UIEditorTabStripInvalidIndex); EXPECT_FALSE(state.hasPointerPosition); } -TEST(UIEditorTabStripInteractionTest, DraggingTabRequestsPointerCaptureAndShowsInsertionPreview) { +TEST(UIEditorTabStripInteractionTest, DraggingTabRequestsPointerCaptureAndReportsDraggedTab) { const auto items = BuildTabItems(); std::string selectedTabId = "doc-a"; UIEditorTabStripInteractionState state = {}; @@ -214,8 +210,9 @@ TEST(UIEditorTabStripInteractionTest, DraggingTabRequestsPointerCaptureAndShowsI items, {}); const UIPoint sourceCenter = RectCenter(initialFrame.layout.tabHeaderRects[0]); - const auto& lastRect = initialFrame.layout.tabHeaderRects.back(); - const UIPoint dragPoint(lastRect.x + lastRect.width - 2.0f, sourceCenter.y); + const UIPoint dragPoint( + initialFrame.layout.tabHeaderRects[2].x + 8.0f, + sourceCenter.y); const auto frame = UpdateUIEditorTabStripInteraction( state, @@ -232,18 +229,12 @@ TEST(UIEditorTabStripInteractionTest, DraggingTabRequestsPointerCaptureAndShowsI EXPECT_TRUE(frame.result.consumed); EXPECT_EQ(frame.result.draggedTabId, "doc-a"); EXPECT_EQ(frame.result.dragSourceIndex, 0u); - EXPECT_EQ(frame.result.dropInsertionIndex, items.size()); - EXPECT_EQ(frame.result.reorderToIndex, 2u); - EXPECT_TRUE(frame.result.reorderPreviewActive); - EXPECT_EQ(frame.result.reorderPreviewIndex, 2u); - EXPECT_TRUE(state.reorderCaptureActive); - EXPECT_EQ(state.reorderSourceIndex, 0u); - EXPECT_EQ(state.reorderPreviewIndex, 2u); - EXPECT_TRUE(frame.layout.insertionPreview.visible); - EXPECT_EQ(frame.layout.insertionPreview.insertionIndex, items.size()); + EXPECT_TRUE(state.dragCaptureActive); + EXPECT_EQ(state.dragSourceIndex, 0u); + EXPECT_TRUE(state.dragState.active); } -TEST(UIEditorTabStripInteractionTest, DroppingDraggedTabEmitsSameStackReorderRequest) { +TEST(UIEditorTabStripInteractionTest, ReleasingActiveDragReleasesCaptureAndClearsDragState) { const auto items = BuildTabItems(); std::string selectedTabId = "doc-a"; UIEditorTabStripInteractionState state = {}; @@ -255,8 +246,9 @@ TEST(UIEditorTabStripInteractionTest, DroppingDraggedTabEmitsSameStackReorderReq items, {}); const UIPoint sourceCenter = RectCenter(initialFrame.layout.tabHeaderRects[0]); - const auto& lastRect = initialFrame.layout.tabHeaderRects.back(); - const UIPoint dragPoint(lastRect.x + lastRect.width - 2.0f, sourceCenter.y); + const UIPoint dragPoint( + initialFrame.layout.tabHeaderRects[2].x + 8.0f, + sourceCenter.y); const auto frame = UpdateUIEditorTabStripInteraction( state, @@ -271,184 +263,11 @@ TEST(UIEditorTabStripInteractionTest, DroppingDraggedTabEmitsSameStackReorderReq EXPECT_TRUE(frame.result.releasePointerCapture); EXPECT_TRUE(frame.result.dragEnded); - EXPECT_TRUE(frame.result.reorderRequested); - EXPECT_FALSE(frame.result.dragCanceled); + EXPECT_TRUE(frame.result.dragCanceled); EXPECT_EQ(frame.result.draggedTabId, "doc-a"); EXPECT_EQ(frame.result.dragSourceIndex, 0u); - EXPECT_EQ(frame.result.dropInsertionIndex, items.size()); - EXPECT_EQ(frame.result.reorderToIndex, 2u); - EXPECT_FALSE(state.reorderCaptureActive); - EXPECT_EQ(state.reorderSourceIndex, UIEditorTabStripInvalidIndex); - EXPECT_EQ(state.reorderPreviewIndex, UIEditorTabStripInvalidIndex); - EXPECT_FALSE(frame.layout.insertionPreview.visible); -} - -TEST(UIEditorTabStripInteractionTest, DroppingRightmostTabToFrontCommitsReorderRequest) { - const auto items = BuildTabItems(); - std::string selectedTabId = "doc-c"; - UIEditorTabStripInteractionState state = {}; - - const auto initialFrame = UpdateUIEditorTabStripInteraction( - state, - selectedTabId, - UIRect(0.0f, 0.0f, 320.0f, 180.0f), - items, - {}); - const UIPoint sourceCenter = RectCenter(initialFrame.layout.tabHeaderRects[2]); - const UIPoint dropPoint( - initialFrame.layout.tabHeaderRects[0].x + 4.0f, - sourceCenter.y); - - const auto frame = UpdateUIEditorTabStripInteraction( - state, - selectedTabId, - UIRect(0.0f, 0.0f, 320.0f, 180.0f), - items, - { - MakePointerDown(sourceCenter.x, sourceCenter.y), - MakePointerMove(dropPoint.x, dropPoint.y), - MakePointerUp(dropPoint.x, dropPoint.y) - }); - - EXPECT_TRUE(frame.result.releasePointerCapture); - EXPECT_TRUE(frame.result.dragEnded); - EXPECT_TRUE(frame.result.reorderRequested); - EXPECT_FALSE(frame.result.dragCanceled); - EXPECT_EQ(frame.result.draggedTabId, "doc-c"); - EXPECT_EQ(frame.result.dragSourceIndex, 2u); - EXPECT_EQ(frame.result.dropInsertionIndex, 0u); - EXPECT_EQ(frame.result.reorderToIndex, 0u); -} - -TEST(UIEditorTabStripInteractionTest, DroppingRightmostTabIntoMiddleCommitsReorderRequest) { - const auto items = BuildTabItems(); - std::string selectedTabId = "doc-c"; - UIEditorTabStripInteractionState state = {}; - - const auto initialFrame = UpdateUIEditorTabStripInteraction( - state, - selectedTabId, - UIRect(0.0f, 0.0f, 320.0f, 180.0f), - items, - {}); - const UIPoint sourceCenter = RectCenter(initialFrame.layout.tabHeaderRects[2]); - const UIPoint dropPoint( - initialFrame.layout.tabHeaderRects[1].x + 4.0f, - sourceCenter.y); - - const auto frame = UpdateUIEditorTabStripInteraction( - state, - selectedTabId, - UIRect(0.0f, 0.0f, 320.0f, 180.0f), - items, - { - MakePointerDown(sourceCenter.x, sourceCenter.y), - MakePointerMove(dropPoint.x, dropPoint.y), - MakePointerUp(dropPoint.x, dropPoint.y) - }); - - EXPECT_TRUE(frame.result.releasePointerCapture); - EXPECT_TRUE(frame.result.dragEnded); - EXPECT_TRUE(frame.result.reorderRequested); - EXPECT_FALSE(frame.result.dragCanceled); - EXPECT_EQ(frame.result.draggedTabId, "doc-c"); - EXPECT_EQ(frame.result.dragSourceIndex, 2u); - EXPECT_EQ(frame.result.dropInsertionIndex, 1u); - EXPECT_EQ(frame.result.reorderToIndex, 1u); -} - -TEST(UIEditorTabStripInteractionTest, SeparateFrameDropOfRightmostTabIntoMiddleStillCommitsReorderRequest) { - const auto items = BuildTabItems(); - std::string selectedTabId = "doc-c"; - UIEditorTabStripInteractionState state = {}; - - const auto initialFrame = UpdateUIEditorTabStripInteraction( - state, - selectedTabId, - UIRect(0.0f, 0.0f, 320.0f, 180.0f), - items, - {}); - const UIPoint sourceCenter = RectCenter(initialFrame.layout.tabHeaderRects[2]); - const UIPoint dropPoint( - initialFrame.layout.tabHeaderRects[1].x + 4.0f, - sourceCenter.y); - - auto frame = UpdateUIEditorTabStripInteraction( - state, - selectedTabId, - UIRect(0.0f, 0.0f, 320.0f, 180.0f), - items, - { - MakePointerDown(sourceCenter.x, sourceCenter.y), - MakePointerMove(dropPoint.x, dropPoint.y) - }); - ASSERT_TRUE(frame.result.requestPointerCapture); - ASSERT_TRUE(frame.result.reorderPreviewActive); - ASSERT_TRUE(state.reorderDragState.active); - ASSERT_FALSE(state.reorderDragState.targetId.empty()); - - frame = UpdateUIEditorTabStripInteraction( - state, - selectedTabId, - UIRect(0.0f, 0.0f, 320.0f, 180.0f), - items, - { MakePointerUp(dropPoint.x, dropPoint.y) }); - - EXPECT_TRUE(frame.result.releasePointerCapture); - EXPECT_TRUE(frame.result.dragEnded); - EXPECT_TRUE(frame.result.reorderRequested); - EXPECT_FALSE(frame.result.dragCanceled); - EXPECT_EQ(frame.result.draggedTabId, "doc-c"); - EXPECT_EQ(frame.result.dragSourceIndex, 2u); - EXPECT_EQ(frame.result.dropInsertionIndex, 1u); - EXPECT_EQ(frame.result.reorderToIndex, 1u); -} - -TEST(UIEditorTabStripInteractionTest, ReleasingOutsideHeaderStillCommitsLastVisibleReorderPreview) { - const auto items = BuildTabItems(); - std::string selectedTabId = "doc-a"; - UIEditorTabStripInteractionState state = {}; - - const auto initialFrame = UpdateUIEditorTabStripInteraction( - state, - selectedTabId, - UIRect(0.0f, 0.0f, 320.0f, 180.0f), - items, - {}); - const UIPoint sourceCenter = RectCenter(initialFrame.layout.tabHeaderRects[0]); - const auto& lastRect = initialFrame.layout.tabHeaderRects.back(); - const UIPoint dragPoint(lastRect.x + lastRect.width - 2.0f, sourceCenter.y); - const UIPoint releasePoint( - dragPoint.x, - initialFrame.layout.headerRect.y + initialFrame.layout.headerRect.height + 8.0f); - - auto frame = UpdateUIEditorTabStripInteraction( - state, - selectedTabId, - UIRect(0.0f, 0.0f, 320.0f, 180.0f), - items, - { - MakePointerDown(sourceCenter.x, sourceCenter.y), - MakePointerMove(dragPoint.x, dragPoint.y) - }); - ASSERT_TRUE(frame.result.reorderPreviewActive); - ASSERT_EQ(state.tabStripState.reorder.previewInsertionIndex, items.size()); - - frame = UpdateUIEditorTabStripInteraction( - state, - selectedTabId, - UIRect(0.0f, 0.0f, 320.0f, 180.0f), - items, - { MakePointerUp(releasePoint.x, releasePoint.y) }); - - EXPECT_TRUE(frame.result.releasePointerCapture); - EXPECT_TRUE(frame.result.dragEnded); - EXPECT_TRUE(frame.result.reorderRequested); - EXPECT_FALSE(frame.result.dragCanceled); - EXPECT_EQ(frame.result.draggedTabId, "doc-a"); - EXPECT_EQ(frame.result.dragSourceIndex, 0u); - EXPECT_EQ(frame.result.dropInsertionIndex, items.size()); - EXPECT_EQ(frame.result.reorderToIndex, 2u); + EXPECT_FALSE(state.dragCaptureActive); + EXPECT_EQ(state.dragSourceIndex, UIEditorTabStripInvalidIndex); } TEST(UIEditorTabStripInteractionTest, EscapeCancelsActiveTabDragAndReleasesCapture) { @@ -477,7 +296,7 @@ TEST(UIEditorTabStripInteractionTest, EscapeCancelsActiveTabDragAndReleasesCaptu MakePointerMove(dragPoint.x, dragPoint.y) }); ASSERT_TRUE(frame.result.dragStarted); - ASSERT_TRUE(state.reorderCaptureActive); + ASSERT_TRUE(state.dragCaptureActive); frame = UpdateUIEditorTabStripInteraction( state, @@ -488,13 +307,12 @@ TEST(UIEditorTabStripInteractionTest, EscapeCancelsActiveTabDragAndReleasesCaptu EXPECT_TRUE(frame.result.dragCanceled); EXPECT_TRUE(frame.result.releasePointerCapture); - EXPECT_FALSE(frame.result.reorderRequested); EXPECT_EQ(frame.result.draggedTabId, "doc-b"); - EXPECT_FALSE(state.reorderCaptureActive); - EXPECT_EQ(state.reorderSourceIndex, UIEditorTabStripInvalidIndex); + EXPECT_FALSE(state.dragCaptureActive); + EXPECT_EQ(state.dragSourceIndex, UIEditorTabStripInvalidIndex); } -TEST(UIEditorTabStripInteractionTest, FocusLostCancelsActiveTabDragAndClearsPreview) { +TEST(UIEditorTabStripInteractionTest, FocusLostCancelsActiveTabDrag) { const auto items = BuildTabItems(); std::string selectedTabId = "doc-a"; UIEditorTabStripInteractionState state = {}; @@ -520,7 +338,6 @@ TEST(UIEditorTabStripInteractionTest, FocusLostCancelsActiveTabDragAndClearsPrev MakePointerMove(dragPoint.x, dragPoint.y) }); ASSERT_TRUE(frame.result.dragStarted); - ASSERT_TRUE(frame.result.reorderPreviewActive); frame = UpdateUIEditorTabStripInteraction( state, @@ -531,43 +348,7 @@ TEST(UIEditorTabStripInteractionTest, FocusLostCancelsActiveTabDragAndClearsPrev EXPECT_TRUE(frame.result.dragCanceled); EXPECT_TRUE(frame.result.releasePointerCapture); - EXPECT_FALSE(frame.result.reorderRequested); EXPECT_FALSE(state.tabStripState.focused); - EXPECT_FALSE(state.reorderCaptureActive); - EXPECT_EQ(state.reorderPreviewIndex, UIEditorTabStripInvalidIndex); - EXPECT_FALSE(frame.layout.insertionPreview.visible); -} - -TEST(UIEditorTabStripInteractionTest, VisibleInsertionIndexTracksHeaderGapsInsteadOfFinalTabIndices) { - const auto items = BuildTabItems(); - std::string selectedTabId = "doc-b"; - UIEditorTabStripInteractionState state = {}; - - const auto initialFrame = UpdateUIEditorTabStripInteraction( - state, - selectedTabId, - UIRect(0.0f, 0.0f, 320.0f, 180.0f), - items, - {}); - const UIPoint sourceCenter = RectCenter(initialFrame.layout.tabHeaderRects[1]); - const float betweenFirstAndSecond = - (initialFrame.layout.tabHeaderRects[0].x + initialFrame.layout.tabHeaderRects[0].width + - initialFrame.layout.tabHeaderRects[1].x) * 0.5f; - - const auto frame = UpdateUIEditorTabStripInteraction( - state, - selectedTabId, - UIRect(0.0f, 0.0f, 320.0f, 180.0f), - items, - { - MakePointerDown(sourceCenter.x, sourceCenter.y), - MakePointerMove(betweenFirstAndSecond, sourceCenter.y) - }); - - EXPECT_TRUE(frame.result.requestPointerCapture); - EXPECT_TRUE(frame.result.reorderPreviewActive); - EXPECT_EQ(frame.result.dragSourceIndex, 1u); - EXPECT_EQ(frame.result.dropInsertionIndex, 1u); - EXPECT_EQ(frame.result.reorderToIndex, 1u); - EXPECT_EQ(frame.result.reorderPreviewIndex, 1u); + EXPECT_FALSE(state.dragCaptureActive); + EXPECT_EQ(state.dragSourceIndex, UIEditorTabStripInvalidIndex); } diff --git a/tests/UI/Editor/unit/test_ui_editor_viewport_input_bridge.cpp b/tests/UI/Editor/unit/test_ui_editor_viewport_input_bridge.cpp index 9f899c61..ece8b1ba 100644 --- a/tests/UI/Editor/unit/test_ui_editor_viewport_input_bridge.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_viewport_input_bridge.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_viewport_shell.cpp b/tests/UI/Editor/unit/test_ui_editor_viewport_shell.cpp index 1dd2ad2c..84c41583 100644 --- a/tests/UI/Editor/unit/test_ui_editor_viewport_shell.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_viewport_shell.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace { diff --git a/tests/UI/Editor/unit/test_ui_editor_viewport_slot.cpp b/tests/UI/Editor/unit/test_ui_editor_viewport_slot.cpp index cb4d804a..b7ae53dd 100644 --- a/tests/UI/Editor/unit/test_ui_editor_viewport_slot.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_viewport_slot.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include namespace { @@ -123,9 +123,9 @@ TEST(UIEditorViewportSlotTest, LayoutBuildsTopBarSurfaceBottomBarAndAspectFitted EXPECT_FLOAT_EQ(layout.requestedSurfaceSize.width, 800.0f); EXPECT_FLOAT_EQ(layout.requestedSurfaceSize.height, 532.0f); EXPECT_FLOAT_EQ(layout.textureRect.x, 10.0f); - EXPECT_FLOAT_EQ(layout.textureRect.y, 101.0f); + EXPECT_FLOAT_EQ(layout.textureRect.y, layout.inputRect.y); EXPECT_FLOAT_EQ(layout.textureRect.width, 800.0f); - EXPECT_FLOAT_EQ(layout.textureRect.height, 450.0f); + EXPECT_FLOAT_EQ(layout.textureRect.height, layout.inputRect.height); } TEST(UIEditorViewportSlotTest, ToolItemsAlignToEdgesAndTitleRectClampsBetweenToolBands) { @@ -234,7 +234,7 @@ TEST(UIEditorViewportSlotTest, BackgroundAndForegroundEmitChromeAndImageBranchCo EXPECT_TRUE(ContainsText(foreground, "Scene View")); EXPECT_TRUE(ContainsText(foreground, "Perspective")); - EXPECT_TRUE(ContainsText(foreground, "Texture 1280x720")); + EXPECT_FALSE(ContainsText(foreground, "Texture 1280x720")); bool foundImage = false; for (const UIDrawCommand& command : foreground.GetCommands()) { @@ -274,7 +274,7 @@ TEST(UIEditorViewportSlotTest, ForegroundFallsBackToStatusTextWhenTextureIsUnava statusSegments, UIEditorViewportSlotState{}); - EXPECT_TRUE(ContainsText(foreground, "Viewport is waiting for frame")); + EXPECT_FALSE(ContainsText(foreground, "Viewport is waiting for frame")); EXPECT_FALSE(ContainsText(foreground, "Texture 1280x720")); } diff --git a/tests/UI/Editor/unit/test_ui_editor_window_workspace_controller.cpp b/tests/UI/Editor/unit/test_ui_editor_window_workspace_controller.cpp index 16251c5c..5c53dd65 100644 --- a/tests/UI/Editor/unit/test_ui_editor_window_workspace_controller.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_window_workspace_controller.cpp @@ -1,9 +1,12 @@ #include -#include +#include +#include namespace { +using XCEngine::UI::Editor::AreUIEditorWorkspaceModelsEquivalent; +using XCEngine::UI::Editor::AreUIEditorWorkspaceSessionsEquivalent; using XCEngine::UI::Editor::BuildDefaultUIEditorWindowWorkspaceController; using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel; using XCEngine::UI::Editor::BuildUIEditorWorkspaceSingleTabStack; @@ -16,8 +19,10 @@ using XCEngine::UI::Editor::FindUIEditorWindowWorkspaceState; using XCEngine::UI::Editor::UIEditorPanelRegistry; using XCEngine::UI::Editor::UIEditorWindowWorkspaceController; using XCEngine::UI::Editor::UIEditorWindowWorkspaceOperationStatus; +using XCEngine::UI::Editor::UIEditorWindowWorkspaceSet; using XCEngine::UI::Editor::UIEditorWorkspaceDockPlacement; using XCEngine::UI::Editor::UIEditorWorkspaceModel; +using XCEngine::UI::Editor::UIEditorWorkspaceNodeKind; using XCEngine::UI::Editor::UIEditorWorkspaceSplitAxis; UIEditorPanelRegistry BuildPanelRegistry() { @@ -64,6 +69,28 @@ std::vector CollectVisiblePanelIds( return ids; } +bool AreWindowSetsEquivalent( + const UIEditorWindowWorkspaceSet& lhs, + const UIEditorWindowWorkspaceSet& rhs) { + if (lhs.primaryWindowId != rhs.primaryWindowId || + lhs.activeWindowId != rhs.activeWindowId || + lhs.windows.size() != rhs.windows.size()) { + return false; + } + + for (std::size_t index = 0; index < lhs.windows.size(); ++index) { + const auto& lhsWindow = lhs.windows[index]; + const auto& rhsWindow = rhs.windows[index]; + if (lhsWindow.windowId != rhsWindow.windowId || + !AreUIEditorWorkspaceModelsEquivalent(lhsWindow.workspace, rhsWindow.workspace) || + !AreUIEditorWorkspaceSessionsEquivalent(lhsWindow.session, rhsWindow.session)) { + return false; + } + } + + return true; +} + } // namespace TEST(UIEditorWindowWorkspaceControllerTest, DetachPanelCreatesNewDetachedWindowAndMovesSessionState) { @@ -171,3 +198,253 @@ TEST(UIEditorWindowWorkspaceControllerTest, DockingDetachedPanelIntoMainWindowAl EXPECT_EQ(visibleIds[1], "doc-b"); EXPECT_EQ(visibleIds[2], "inspector"); } + +TEST(UIEditorWindowWorkspaceControllerTest, DetachWithDuplicatePreferredWindowIdCreatesUniqueWindowId) { + UIEditorWindowWorkspaceController controller = + BuildDefaultUIEditorWindowWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); + + const auto result = controller.DetachPanelToNewWindow( + "main-window", + "document-tabs", + "doc-b", + "main-window"); + EXPECT_EQ(result.status, UIEditorWindowWorkspaceOperationStatus::Changed); + EXPECT_EQ(result.targetWindowId, "main-window-1"); + EXPECT_EQ(result.activeWindowId, "main-window-1"); + ASSERT_EQ(result.windowIds.size(), 2u); + + EXPECT_NE( + FindUIEditorWindowWorkspaceState(controller.GetWindowSet(), "main-window-1"), + nullptr); +} + +TEST(UIEditorWindowWorkspaceControllerTest, DetachingSinglePanelDetachedWindowIsNoOp) { + UIEditorWindowWorkspaceController controller = + BuildDefaultUIEditorWindowWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); + ASSERT_EQ( + controller.DetachPanelToNewWindow( + "main-window", + "document-tabs", + "doc-b", + "doc-b-window").status, + UIEditorWindowWorkspaceOperationStatus::Changed); + + const auto result = controller.DetachPanelToNewWindow( + "doc-b-window", + "doc-b-window-root", + "doc-b", + "doc-b-window-again"); + EXPECT_EQ(result.status, UIEditorWindowWorkspaceOperationStatus::NoOp); + EXPECT_EQ(result.targetWindowId, "doc-b-window"); + EXPECT_EQ(result.activeWindowId, "doc-b-window"); + ASSERT_EQ(result.windowIds.size(), 2u); + + EXPECT_EQ( + FindUIEditorWindowWorkspaceState(controller.GetWindowSet(), "doc-b-window-again"), + nullptr); + EXPECT_NE( + FindUIEditorWindowWorkspaceState(controller.GetWindowSet(), "doc-b-window"), + nullptr); +} + +TEST(UIEditorWindowWorkspaceControllerTest, MovingPanelBetweenDetachedWindowsClosesEmptiedSourceWindow) { + UIEditorWindowWorkspaceController controller = + BuildDefaultUIEditorWindowWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); + ASSERT_EQ( + controller.DetachPanelToNewWindow( + "main-window", + "document-tabs", + "doc-b", + "doc-b-window").status, + UIEditorWindowWorkspaceOperationStatus::Changed); + ASSERT_EQ( + controller.DetachPanelToNewWindow( + "main-window", + "inspector-panel", + "inspector", + "inspector-window").status, + UIEditorWindowWorkspaceOperationStatus::Changed); + + const auto result = controller.MovePanelToStack( + "doc-b-window", + "doc-b-window-root", + "doc-b", + "inspector-window", + "inspector-window-root", + 1u); + EXPECT_EQ(result.status, UIEditorWindowWorkspaceOperationStatus::Changed); + EXPECT_EQ(result.activeWindowId, "inspector-window"); + ASSERT_EQ(result.windowIds.size(), 2u); + + EXPECT_EQ( + FindUIEditorWindowWorkspaceState(controller.GetWindowSet(), "doc-b-window"), + nullptr); + + const auto* mainWindow = + FindUIEditorWindowWorkspaceState(controller.GetWindowSet(), "main-window"); + ASSERT_NE(mainWindow, nullptr); + const auto mainVisibleIds = + CollectVisiblePanelIds(mainWindow->workspace, mainWindow->session); + ASSERT_EQ(mainVisibleIds.size(), 1u); + EXPECT_EQ(mainVisibleIds[0], "doc-a"); + + const auto* inspectorWindow = + FindUIEditorWindowWorkspaceState(controller.GetWindowSet(), "inspector-window"); + ASSERT_NE(inspectorWindow, nullptr); + ASSERT_TRUE(ContainsUIEditorWorkspacePanel(inspectorWindow->workspace, "doc-b")); + ASSERT_TRUE(ContainsUIEditorWorkspacePanel(inspectorWindow->workspace, "inspector")); + ASSERT_NE(FindUIEditorPanelSessionState(inspectorWindow->session, "doc-b"), nullptr); + ASSERT_NE(FindUIEditorPanelSessionState(inspectorWindow->session, "inspector"), nullptr); + EXPECT_EQ(inspectorWindow->workspace.activePanelId, "doc-b"); + ASSERT_EQ(inspectorWindow->workspace.root.children.size(), 2u); + EXPECT_EQ(inspectorWindow->workspace.root.children[0].panel.panelId, "inspector"); + EXPECT_EQ(inspectorWindow->workspace.root.children[1].panel.panelId, "doc-b"); + EXPECT_EQ(inspectorWindow->workspace.root.selectedTabIndex, 1u); + ASSERT_EQ(inspectorWindow->session.panelStates.size(), 2u); + EXPECT_EQ(inspectorWindow->session.panelStates[0].panelId, "inspector"); + EXPECT_EQ(inspectorWindow->session.panelStates[1].panelId, "doc-b"); +} + +TEST(UIEditorWindowWorkspaceControllerTest, RejectedCrossWindowMoveDoesNotMutateWindowSet) { + UIEditorWindowWorkspaceController controller = + BuildDefaultUIEditorWindowWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); + ASSERT_EQ( + controller.DetachPanelToNewWindow( + "main-window", + "document-tabs", + "doc-b", + "doc-b-window").status, + UIEditorWindowWorkspaceOperationStatus::Changed); + + const auto result = controller.MovePanelToStack( + "doc-b-window", + "doc-b-window-root", + "doc-b", + "main-window", + "missing-target-node", + 0u); + EXPECT_EQ(result.status, UIEditorWindowWorkspaceOperationStatus::Rejected); + EXPECT_EQ(result.activeWindowId, "doc-b-window"); + ASSERT_EQ(result.windowIds.size(), 2u); + + const auto* mainWindow = + FindUIEditorWindowWorkspaceState(controller.GetWindowSet(), "main-window"); + ASSERT_NE(mainWindow, nullptr); + EXPECT_FALSE(ContainsUIEditorWorkspacePanel(mainWindow->workspace, "doc-b")); + EXPECT_EQ(FindUIEditorPanelSessionState(mainWindow->session, "doc-b"), nullptr); + + const auto* detachedWindow = + FindUIEditorWindowWorkspaceState(controller.GetWindowSet(), "doc-b-window"); + ASSERT_NE(detachedWindow, nullptr); + EXPECT_TRUE(ContainsUIEditorWorkspacePanel(detachedWindow->workspace, "doc-b")); + EXPECT_EQ(detachedWindow->workspace.activePanelId, "doc-b"); + EXPECT_NE(FindUIEditorPanelSessionState(detachedWindow->session, "doc-b"), nullptr); +} + +TEST(UIEditorWindowWorkspaceControllerTest, DockingPanelBetweenDetachedWindowsClosesEmptiedSourceWindow) { + UIEditorWindowWorkspaceController controller = + BuildDefaultUIEditorWindowWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); + ASSERT_EQ( + controller.DetachPanelToNewWindow( + "main-window", + "document-tabs", + "doc-b", + "doc-b-window").status, + UIEditorWindowWorkspaceOperationStatus::Changed); + ASSERT_EQ( + controller.DetachPanelToNewWindow( + "main-window", + "inspector-panel", + "inspector", + "inspector-window").status, + UIEditorWindowWorkspaceOperationStatus::Changed); + + const auto result = controller.DockPanelRelative( + "doc-b-window", + "doc-b-window-root", + "doc-b", + "inspector-window", + "inspector-window-root", + UIEditorWorkspaceDockPlacement::Bottom, + 0.35f); + EXPECT_EQ(result.status, UIEditorWindowWorkspaceOperationStatus::Changed); + EXPECT_EQ(result.activeWindowId, "inspector-window"); + ASSERT_EQ(result.windowIds.size(), 2u); + + const UIEditorWindowWorkspaceSet& windowSet = controller.GetWindowSet(); + EXPECT_EQ(windowSet.primaryWindowId, "main-window"); + EXPECT_EQ(windowSet.activeWindowId, "inspector-window"); + EXPECT_EQ( + FindUIEditorWindowWorkspaceState(windowSet, "doc-b-window"), + nullptr); + + const auto* mainWindow = + FindUIEditorWindowWorkspaceState(windowSet, "main-window"); + ASSERT_NE(mainWindow, nullptr); + const auto mainVisibleIds = + CollectVisiblePanelIds(mainWindow->workspace, mainWindow->session); + ASSERT_EQ(mainVisibleIds.size(), 1u); + EXPECT_EQ(mainVisibleIds[0], "doc-a"); + + const auto* inspectorWindow = + FindUIEditorWindowWorkspaceState(windowSet, "inspector-window"); + ASSERT_NE(inspectorWindow, nullptr); + EXPECT_TRUE(ContainsUIEditorWorkspacePanel(inspectorWindow->workspace, "inspector")); + EXPECT_TRUE(ContainsUIEditorWorkspacePanel(inspectorWindow->workspace, "doc-b")); + EXPECT_EQ(inspectorWindow->workspace.activePanelId, "doc-b"); + EXPECT_EQ(inspectorWindow->workspace.root.kind, UIEditorWorkspaceNodeKind::Split); + ASSERT_EQ(inspectorWindow->session.panelStates.size(), 2u); + EXPECT_EQ(inspectorWindow->session.panelStates[0].panelId, "inspector"); + EXPECT_EQ(inspectorWindow->session.panelStates[1].panelId, "doc-b"); +} + +TEST(UIEditorWindowWorkspaceControllerTest, RejectedCrossWindowDockDoesNotMutateWindowSet) { + UIEditorWindowWorkspaceController controller = + BuildDefaultUIEditorWindowWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); + ASSERT_EQ( + controller.DetachPanelToNewWindow( + "main-window", + "document-tabs", + "doc-b", + "doc-b-window").status, + UIEditorWindowWorkspaceOperationStatus::Changed); + + const UIEditorWindowWorkspaceSet windowSetBefore = controller.GetWindowSet(); + const auto result = controller.DockPanelRelative( + "doc-b-window", + "doc-b-window-root", + "doc-b", + "missing-window", + "inspector-panel", + UIEditorWorkspaceDockPlacement::Left, + 0.4f); + EXPECT_EQ(result.status, UIEditorWindowWorkspaceOperationStatus::Rejected); + EXPECT_EQ(result.activeWindowId, "doc-b-window"); + EXPECT_EQ(controller.GetWindowSet().primaryWindowId, "main-window"); + EXPECT_TRUE(AreWindowSetsEquivalent(controller.GetWindowSet(), windowSetBefore)); +} + +TEST(UIEditorWindowWorkspaceControllerTest, RejectedCrossWindowMoveFromMissingSourceDoesNotMutateWindowSet) { + UIEditorWindowWorkspaceController controller = + BuildDefaultUIEditorWindowWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); + ASSERT_EQ( + controller.DetachPanelToNewWindow( + "main-window", + "document-tabs", + "doc-b", + "doc-b-window").status, + UIEditorWindowWorkspaceOperationStatus::Changed); + + const UIEditorWindowWorkspaceSet windowSetBefore = controller.GetWindowSet(); + const auto result = controller.MovePanelToStack( + "missing-window", + "doc-b-window-root", + "doc-b", + "main-window", + "document-tabs", + 0u); + EXPECT_EQ(result.status, UIEditorWindowWorkspaceOperationStatus::Rejected); + EXPECT_EQ(result.activeWindowId, "doc-b-window"); + EXPECT_EQ(controller.GetWindowSet().primaryWindowId, "main-window"); + EXPECT_TRUE(AreWindowSetsEquivalent(controller.GetWindowSet(), windowSetBefore)); +} diff --git a/tests/UI/Editor/unit/test_ui_editor_workspace_compose.cpp b/tests/UI/Editor/unit/test_ui_editor_workspace_compose.cpp index 56f7cf54..4cc7b6f0 100644 --- a/tests/UI/Editor/unit/test_ui_editor_workspace_compose.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_workspace_compose.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace { @@ -27,6 +27,8 @@ using XCEngine::UI::Editor::UIEditorWorkspaceModel; using XCEngine::UI::Editor::UIEditorWorkspacePanelPresentationModel; using XCEngine::UI::Editor::UIEditorWorkspaceSplitAxis; using XCEngine::UI::Editor::UpdateUIEditorWorkspaceCompose; +using XCEngine::UI::Editor::Widgets::UIEditorDockHostLayout; +using XCEngine::UI::Editor::Widgets::UIEditorDockHostTabStackLayout; UIEditorPanelRegistry BuildRegistryWithViewportPanels() { UIEditorPanelRegistry registry = {}; @@ -135,6 +137,18 @@ UIEditorWorkspacePanelPresentationModel BuildViewportPresentationModel( return model; } +const UIEditorDockHostTabStackLayout* FindTabStackByNodeId( + const UIEditorDockHostLayout& layout, + std::string_view nodeId) { + for (const UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { + if (tabStack.nodeId == nodeId) { + return &tabStack; + } + } + + return nullptr; +} + } // namespace TEST(UIEditorWorkspaceComposeTest, ResolveRequestMapsViewportPresentationToVisiblePanelBodyRect) { @@ -157,9 +171,10 @@ TEST(UIEditorWorkspaceComposeTest, ResolveRequestMapsViewportPresentationToVisib FindUIEditorWorkspaceViewportPresentationRequest(request, "viewport"); ASSERT_NE(viewportRequest, nullptr); - ASSERT_EQ(request.dockHostLayout.tabStacks.size(), 1u); - const UIRect expectedBodyRect = - request.dockHostLayout.tabStacks.front().contentFrameLayout.bodyRect; + ASSERT_EQ(request.dockHostLayout.tabStacks.size(), 2u); + const auto* documentStack = FindTabStackByNodeId(request.dockHostLayout, "tab-stack"); + ASSERT_NE(documentStack, nullptr); + const UIRect expectedBodyRect = documentStack->contentFrameLayout.bodyRect; EXPECT_FLOAT_EQ(viewportRequest->bounds.x, expectedBodyRect.x); EXPECT_FLOAT_EQ(viewportRequest->bounds.y, expectedBodyRect.y); EXPECT_FLOAT_EQ(viewportRequest->bounds.width, expectedBodyRect.width); diff --git a/tests/UI/Editor/unit/test_ui_editor_workspace_controller.cpp b/tests/UI/Editor/unit/test_ui_editor_workspace_controller.cpp index 133f1568..ea7d09c6 100644 --- a/tests/UI/Editor/unit/test_ui_editor_workspace_controller.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_workspace_controller.cpp @@ -1,6 +1,8 @@ #include -#include +#include +#include +#include #include @@ -61,34 +63,6 @@ UIEditorWorkspaceModel BuildWorkspace() { return workspace; } -UIEditorWorkspaceModel BuildReorderWorkspace() { - UIEditorWorkspaceModel workspace = {}; - workspace.root = BuildUIEditorWorkspaceTabStack( - "document-tabs", - { - BuildUIEditorWorkspacePanel("doc-a-node", "doc-a", "Document A", true), - BuildUIEditorWorkspacePanel("hidden-a-node", "hidden-a", "Hidden A", true), - BuildUIEditorWorkspacePanel("doc-b-node", "doc-b", "Document B", true), - BuildUIEditorWorkspacePanel("hidden-b-node", "hidden-b", "Hidden B", true), - BuildUIEditorWorkspacePanel("doc-c-node", "doc-c", "Document C", true) - }, - 2u); - workspace.activePanelId = "doc-b"; - return workspace; -} - -UIEditorWorkspaceSession BuildReorderSession() { - UIEditorWorkspaceSession session = {}; - session.panelStates = { - { "doc-a", true, true }, - { "hidden-a", true, false }, - { "doc-b", true, true }, - { "hidden-b", true, false }, - { "doc-c", true, true } - }; - return session; -} - } // namespace TEST(UIEditorWorkspaceControllerTest, CommandNameHelpersExposeStableDebugNames) { @@ -187,52 +161,6 @@ TEST(UIEditorWorkspaceControllerTest, RejectsUnknownPanelAndNonCloseablePanelCom EXPECT_EQ(rootController.GetWorkspace().activePanelId, "root"); } -TEST(UIEditorWorkspaceControllerTest, ReorderTabUsesModelSurgeryAndPreservesSelectionActiveAndSession) { - UIEditorWorkspaceController controller( - BuildPanelRegistry(), - BuildReorderWorkspace(), - BuildReorderSession()); - - const auto result = controller.ReorderTab("document-tabs", "doc-c", 0u); - EXPECT_EQ(result.status, UIEditorWorkspaceLayoutOperationStatus::Changed); - EXPECT_EQ(result.activePanelId, "doc-b"); - ASSERT_EQ(result.visiblePanelIds.size(), 1u); - EXPECT_EQ(result.visiblePanelIds[0], "doc-b"); - - const UIEditorWorkspaceNode* tabStack = - FindUIEditorWorkspaceNode(controller.GetWorkspace(), "document-tabs"); - ASSERT_NE(tabStack, nullptr); - ASSERT_EQ(tabStack->children.size(), 5u); - EXPECT_EQ(tabStack->children[0].panel.panelId, "doc-c"); - EXPECT_EQ(tabStack->children[1].panel.panelId, "hidden-a"); - EXPECT_EQ(tabStack->children[2].panel.panelId, "doc-a"); - EXPECT_EQ(tabStack->children[3].panel.panelId, "hidden-b"); - EXPECT_EQ(tabStack->children[4].panel.panelId, "doc-b"); - EXPECT_EQ(tabStack->selectedTabIndex, 4u); - - const auto* hiddenState = - FindUIEditorPanelSessionState(controller.GetSession(), "hidden-a"); - ASSERT_NE(hiddenState, nullptr); - EXPECT_TRUE(hiddenState->open); - EXPECT_FALSE(hiddenState->visible); -} - -TEST(UIEditorWorkspaceControllerTest, ReorderTabRejectsHiddenPanelsAndOutOfRangeTargetsAndReportsNoOp) { - UIEditorWorkspaceController controller( - BuildPanelRegistry(), - BuildReorderWorkspace(), - BuildReorderSession()); - - const auto hidden = controller.ReorderTab("document-tabs", "hidden-a", 0u); - EXPECT_EQ(hidden.status, UIEditorWorkspaceLayoutOperationStatus::Rejected); - - const auto outOfRange = controller.ReorderTab("document-tabs", "doc-a", 4u); - EXPECT_EQ(outOfRange.status, UIEditorWorkspaceLayoutOperationStatus::Rejected); - - const auto noOp = controller.ReorderTab("document-tabs", "doc-a", 0u); - EXPECT_EQ(noOp.status, UIEditorWorkspaceLayoutOperationStatus::NoOp); -} - TEST(UIEditorWorkspaceControllerTest, MoveTabToStackMovesVisibleTabAcrossStacks) { UIEditorWorkspaceModel workspace = {}; workspace.root = BuildUIEditorWorkspaceSplit( @@ -273,6 +201,14 @@ TEST(UIEditorWorkspaceControllerTest, MoveTabToStackMovesVisibleTabAcrossStacks) EXPECT_EQ(detailsTabs->children[1].panel.panelId, "doc-b"); } +TEST(UIEditorWorkspaceControllerTest, MoveTabToStackRejectsSameStackRequests) { + UIEditorWorkspaceController controller = + BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace()); + + const auto result = controller.MoveTabToStack("document-tabs", "doc-b", "document-tabs", 0u); + EXPECT_EQ(result.status, UIEditorWorkspaceLayoutOperationStatus::Rejected); +} + TEST(UIEditorWorkspaceControllerTest, DockTabRelativeCreatesSplitAroundTargetStack) { UIEditorWorkspaceModel workspace = {}; workspace.root = BuildUIEditorWorkspaceSplit( diff --git a/tests/UI/Editor/unit/test_ui_editor_workspace_interaction.cpp b/tests/UI/Editor/unit/test_ui_editor_workspace_interaction.cpp index 5393dd43..eb020f0b 100644 --- a/tests/UI/Editor/unit/test_ui_editor_workspace_interaction.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_workspace_interaction.cpp @@ -1,6 +1,7 @@ #include -#include +#include +#include namespace { @@ -23,6 +24,9 @@ using XCEngine::UI::Editor::UIEditorWorkspaceModel; using XCEngine::UI::Editor::UIEditorWorkspacePanelPresentationModel; using XCEngine::UI::Editor::UIEditorWorkspaceSplitAxis; using XCEngine::UI::Editor::UpdateUIEditorWorkspaceInteraction; +using XCEngine::UI::Editor::Widgets::FindUIEditorDockHostSplitterLayout; +using XCEngine::UI::Editor::Widgets::UIEditorDockHostLayout; +using XCEngine::UI::Editor::Widgets::UIEditorDockHostTabStackLayout; UIEditorPanelRegistry BuildPanelRegistry() { UIEditorPanelRegistry registry = {}; @@ -88,6 +92,18 @@ UIPoint RectCenter(const UIRect& rect) { return UIPoint(rect.x + rect.width * 0.5f, rect.y + rect.height * 0.5f); } +const UIEditorDockHostTabStackLayout* FindTabStackByNodeId( + const UIEditorDockHostLayout& layout, + std::string_view nodeId) { + for (const UIEditorDockHostTabStackLayout& tabStack : layout.tabStacks) { + if (tabStack.nodeId == nodeId) { + return &tabStack; + } + } + + return nullptr; +} + } // namespace TEST(UIEditorWorkspaceInteractionTest, PointerDownInsideViewportBubblesPointerCaptureRequest) { @@ -182,9 +198,11 @@ TEST(UIEditorWorkspaceInteractionTest, ActivatingDocumentTabRemovesViewportPrese UIRect(0.0f, 0.0f, 1280.0f, 720.0f), model, {}); - ASSERT_EQ(frame.composeFrame.dockHostLayout.tabStacks.size(), 1u); - const UIRect docTabRect = - frame.composeFrame.dockHostLayout.tabStacks.front().tabStripLayout.tabHeaderRects[1]; + ASSERT_EQ(frame.composeFrame.dockHostLayout.tabStacks.size(), 2u); + const auto* documentStack = + FindTabStackByNodeId(frame.composeFrame.dockHostLayout, "tab-stack"); + ASSERT_NE(documentStack, nullptr); + const UIRect docTabRect = documentStack->tabStripLayout.tabHeaderRects[1]; const UIPoint docTabCenter = RectCenter(docTabRect); frame = UpdateUIEditorWorkspaceInteraction( @@ -201,8 +219,10 @@ TEST(UIEditorWorkspaceInteractionTest, ActivatingDocumentTabRemovesViewportPrese EXPECT_TRUE(frame.result.dockHostResult.commandExecuted); EXPECT_EQ(controller.GetWorkspace().activePanelId, "doc"); EXPECT_TRUE(frame.composeFrame.viewportFrames.empty()); - ASSERT_EQ(frame.composeFrame.dockHostLayout.tabStacks.size(), 1u); - EXPECT_EQ(frame.composeFrame.dockHostLayout.tabStacks.front().selectedPanelId, "doc"); + ASSERT_EQ(frame.composeFrame.dockHostLayout.tabStacks.size(), 2u); + documentStack = FindTabStackByNodeId(frame.composeFrame.dockHostLayout, "tab-stack"); + ASSERT_NE(documentStack, nullptr); + EXPECT_EQ(documentStack->selectedPanelId, "doc"); } TEST(UIEditorWorkspaceInteractionTest, SplitterDragKeepsViewportBoundsSyncedAfterLayoutChange) { @@ -221,6 +241,10 @@ TEST(UIEditorWorkspaceInteractionTest, SplitterDragKeepsViewportBoundsSyncedAfte FindUIEditorWorkspaceViewportPresentationFrame(frame.composeFrame, "viewport"); ASSERT_NE(viewportFrame, nullptr); const float initialWidth = viewportFrame->bounds.width; + const auto* splitter = + FindUIEditorDockHostSplitterLayout(frame.composeFrame.dockHostLayout, "root-split"); + ASSERT_NE(splitter, nullptr); + const UIPoint splitterCenter = RectCenter(splitter->handleHitRect); frame = UpdateUIEditorWorkspaceInteraction( state, @@ -228,18 +252,27 @@ TEST(UIEditorWorkspaceInteractionTest, SplitterDragKeepsViewportBoundsSyncedAfte UIRect(0.0f, 0.0f, 1280.0f, 720.0f), model, { - MakePointerEvent(UIInputEventType::PointerMove, 888.0f, 140.0f), - MakePointerEvent(UIInputEventType::PointerButtonDown, 888.0f, 140.0f, UIPointerButton::Left) + MakePointerEvent(UIInputEventType::PointerMove, splitterCenter.x, splitterCenter.y), + MakePointerEvent( + UIInputEventType::PointerButtonDown, + splitterCenter.x, + splitterCenter.y, + UIPointerButton::Left) }); EXPECT_TRUE(frame.result.requestPointerCapture); + splitter = FindUIEditorDockHostSplitterLayout(frame.composeFrame.dockHostLayout, "root-split"); + ASSERT_NE(splitter, nullptr); + const UIPoint draggedCenter( + splitter->handleHitRect.x + splitter->handleHitRect.width * 0.5f + 92.0f, + splitter->handleHitRect.y + splitter->handleHitRect.height * 0.5f); frame = UpdateUIEditorWorkspaceInteraction( state, controller, UIRect(0.0f, 0.0f, 1280.0f, 720.0f), model, { - MakePointerEvent(UIInputEventType::PointerMove, 980.0f, 140.0f) + MakePointerEvent(UIInputEventType::PointerMove, draggedCenter.x, draggedCenter.y) }); EXPECT_TRUE(frame.result.dockHostResult.layoutChanged); diff --git a/tests/UI/Editor/unit/test_ui_editor_workspace_layout_persistence.cpp b/tests/UI/Editor/unit/test_ui_editor_workspace_layout_persistence.cpp index 6383bcda..30f20d70 100644 --- a/tests/UI/Editor/unit/test_ui_editor_workspace_layout_persistence.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_workspace_layout_persistence.cpp @@ -1,7 +1,7 @@ #include -#include -#include +#include +#include #include #include diff --git a/tests/UI/Editor/unit/test_ui_editor_workspace_model.cpp b/tests/UI/Editor/unit/test_ui_editor_workspace_model.cpp index 06db2ad3..37d8af84 100644 --- a/tests/UI/Editor/unit/test_ui_editor_workspace_model.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_workspace_model.cpp @@ -1,7 +1,10 @@ #include -#include -#include +#include +#include +#include +#include +#include #include #include @@ -22,7 +25,6 @@ using XCEngine::UI::Editor::FindUIEditorWorkspaceNode; using XCEngine::UI::Editor::TryActivateUIEditorWorkspacePanel; using XCEngine::UI::Editor::TryDockUIEditorWorkspaceTabRelative; using XCEngine::UI::Editor::TryMoveUIEditorWorkspaceTabToStack; -using XCEngine::UI::Editor::TryReorderUIEditorWorkspaceTab; using XCEngine::UI::Editor::TrySetUIEditorWorkspaceSplitRatio; using XCEngine::UI::Editor::UIEditorWorkspaceDockPlacement; using XCEngine::UI::Editor::UIEditorWorkspaceModel; @@ -179,78 +181,6 @@ TEST(UIEditorWorkspaceModelTest, SplitRatioMutationTargetsSplitNodeAndRejectsInv EXPECT_FALSE(TrySetUIEditorWorkspaceSplitRatio(workspace, "root-split", 1.0f)); } -TEST(UIEditorWorkspaceModelTest, ReorderTabMovesVisibleTabsAndPreservesHiddenOrderSelectionAndActivePanel) { - UIEditorWorkspaceModel workspace = {}; - workspace.root = BuildUIEditorWorkspaceTabStack( - "document-tabs", - { - BuildUIEditorWorkspacePanel("doc-a-node", "doc-a", "Document A", true), - BuildUIEditorWorkspacePanel("hidden-a-node", "hidden-a", "Hidden A", true), - BuildUIEditorWorkspacePanel("doc-b-node", "doc-b", "Document B", true), - BuildUIEditorWorkspacePanel("hidden-b-node", "hidden-b", "Hidden B", true), - BuildUIEditorWorkspacePanel("doc-c-node", "doc-c", "Document C", true) - }, - 2u); - workspace.activePanelId = "doc-b"; - - UIEditorWorkspaceSession session = {}; - session.panelStates = { - { "doc-a", true, true }, - { "hidden-a", true, false }, - { "doc-b", true, true }, - { "hidden-b", true, false }, - { "doc-c", true, true } - }; - - ASSERT_TRUE(TryReorderUIEditorWorkspaceTab( - workspace, - session, - "document-tabs", - "doc-c", - 0u)); - - const UIEditorWorkspaceNode* tabStack = - FindUIEditorWorkspaceNode(workspace, "document-tabs"); - ASSERT_NE(tabStack, nullptr); - ASSERT_EQ(tabStack->children.size(), 5u); - EXPECT_EQ(tabStack->children[0].panel.panelId, "doc-c"); - EXPECT_EQ(tabStack->children[1].panel.panelId, "hidden-a"); - EXPECT_EQ(tabStack->children[2].panel.panelId, "doc-a"); - EXPECT_EQ(tabStack->children[3].panel.panelId, "hidden-b"); - EXPECT_EQ(tabStack->children[4].panel.panelId, "doc-b"); - EXPECT_EQ(tabStack->selectedTabIndex, 4u); - EXPECT_EQ(workspace.activePanelId, "doc-b"); - - EXPECT_FALSE(TryReorderUIEditorWorkspaceTab( - workspace, - session, - "document-tabs", - "hidden-a", - 0u)); - - ASSERT_TRUE(TryReorderUIEditorWorkspaceTab( - workspace, - session, - "document-tabs", - "doc-a", - 3u)); - tabStack = FindUIEditorWorkspaceNode(workspace, "document-tabs"); - ASSERT_NE(tabStack, nullptr); - EXPECT_EQ(tabStack->children[0].panel.panelId, "doc-c"); - EXPECT_EQ(tabStack->children[1].panel.panelId, "hidden-a"); - EXPECT_EQ(tabStack->children[2].panel.panelId, "doc-b"); - EXPECT_EQ(tabStack->children[3].panel.panelId, "hidden-b"); - EXPECT_EQ(tabStack->children[4].panel.panelId, "doc-a"); - EXPECT_EQ(tabStack->selectedTabIndex, 2u); - - EXPECT_FALSE(TryReorderUIEditorWorkspaceTab( - workspace, - session, - "document-tabs", - "doc-a", - 4u)); -} - TEST(UIEditorWorkspaceModelTest, CanonicalizeWrapsStandalonePanelsIntoSingleTabStacks) { UIEditorWorkspaceModel workspace = {}; workspace.root = BuildUIEditorWorkspaceSplit( @@ -327,6 +257,32 @@ TEST(UIEditorWorkspaceModelTest, MoveTabToStackMergesIntoTargetStackAndActivates EXPECT_EQ(workspace.activePanelId, "doc-b"); } +TEST(UIEditorWorkspaceModelTest, MoveTabToStackRejectsSameStackRequests) { + UIEditorWorkspaceModel workspace = {}; + workspace.root = BuildUIEditorWorkspaceTabStack( + "left-tabs", + { + BuildUIEditorWorkspacePanel("doc-a-node", "doc-a", "Document A", true), + BuildUIEditorWorkspacePanel("doc-b-node", "doc-b", "Document B", true) + }, + 0u); + workspace.activePanelId = "doc-a"; + + UIEditorWorkspaceSession session = {}; + session.panelStates = { + { "doc-a", true, true }, + { "doc-b", true, true } + }; + + EXPECT_FALSE(TryMoveUIEditorWorkspaceTabToStack( + workspace, + session, + "left-tabs", + "doc-b", + "left-tabs", + 1u)); +} + TEST(UIEditorWorkspaceModelTest, DockTabRelativeSplitsTargetStackAndCreatesNewLeaf) { UIEditorWorkspaceModel workspace = {}; workspace.root = BuildUIEditorWorkspaceSplit( diff --git a/tests/UI/Editor/unit/test_ui_editor_workspace_session.cpp b/tests/UI/Editor/unit/test_ui_editor_workspace_session.cpp index d508b9de..3aa0c2b7 100644 --- a/tests/UI/Editor/unit/test_ui_editor_workspace_session.cpp +++ b/tests/UI/Editor/unit/test_ui_editor_workspace_session.cpp @@ -1,8 +1,8 @@ #include -#include -#include -#include +#include +#include +#include #include diff --git a/tests/UI/Editor/unit/test_ui_editor_workspace_splitter_drag_correction.cpp b/tests/UI/Editor/unit/test_ui_editor_workspace_splitter_drag_correction.cpp new file mode 100644 index 00000000..bd727dbe --- /dev/null +++ b/tests/UI/Editor/unit/test_ui_editor_workspace_splitter_drag_correction.cpp @@ -0,0 +1,120 @@ +#include + +#include +#include +#include + +namespace { + +using XCEngine::UI::UIPoint; +using XCEngine::UI::UIRect; +using XCEngine::UI::Editor::BuildDefaultUIEditorWorkspaceController; +using XCEngine::UI::Editor::BuildUIEditorWorkspacePanel; +using XCEngine::UI::Editor::BuildUIEditorWorkspaceSingleTabStack; +using XCEngine::UI::Editor::BuildUIEditorWorkspaceSplit; +using XCEngine::UI::Editor::BuildUIEditorWorkspaceTabStack; +using XCEngine::UI::Editor::UIEditorPanelRegistry; +using XCEngine::UI::Editor::UIEditorWorkspaceModel; +using XCEngine::UI::Editor::UIEditorWorkspaceSplitAxis; +using XCEngine::UI::Editor::UIEditorWorkspaceSplitterDragCorrectionState; +using XCEngine::UI::Editor::BeginUIEditorWorkspaceSplitterDragCorrection; +using XCEngine::UI::Editor::TryBuildUIEditorWorkspaceSplitterDragCorrectedSnapshot; +using XCEngine::UI::Editor::Widgets::BuildUIEditorDockHostLayout; +using XCEngine::UI::Editor::Widgets::FindUIEditorDockHostSplitterLayout; + +UIEditorPanelRegistry BuildPanelRegistry() { + UIEditorPanelRegistry registry = {}; + registry.panels = { + { "doc-a", "Document A", {}, true, true, true }, + { "doc-b", "Document B", {}, true, true, true }, + { "details", "Details", {}, true, true, true }, + { "console", "Console", {}, true, true, true } + }; + return registry; +} + +UIEditorWorkspaceModel BuildWorkspace() { + UIEditorWorkspaceModel workspace = {}; + workspace.root = BuildUIEditorWorkspaceSplit( + "root-split", + UIEditorWorkspaceSplitAxis::Horizontal, + 0.5f, + BuildUIEditorWorkspaceTabStack( + "document-tabs", + { + BuildUIEditorWorkspacePanel("doc-a-node", "doc-a", "Document A", true), + BuildUIEditorWorkspacePanel("doc-b-node", "doc-b", "Document B", true) + }, + 1u), + BuildUIEditorWorkspaceSplit( + "right-split", + UIEditorWorkspaceSplitAxis::Vertical, + 0.6f, + BuildUIEditorWorkspaceSingleTabStack("details-node", "details", "Details", true), + BuildUIEditorWorkspaceSingleTabStack("console-node", "console", "Console", true))); + workspace.activePanelId = "doc-b"; + return workspace; +} + +} // namespace + +TEST(UIEditorWorkspaceSplitterDragCorrectionTest, BuildsCorrectedSnapshotForActiveSplitterDrag) { + const UIEditorPanelRegistry registry = BuildPanelRegistry(); + auto controller = BuildDefaultUIEditorWorkspaceController(registry, BuildWorkspace()); + const auto baselineSnapshot = controller.CaptureLayoutSnapshot(); + const auto baselineLayout = BuildUIEditorDockHostLayout( + UIRect(0.0f, 0.0f, 800.0f, 600.0f), + registry, + controller.GetWorkspace(), + controller.GetSession()); + const auto* splitter = FindUIEditorDockHostSplitterLayout(baselineLayout, "root-split"); + ASSERT_NE(splitter, nullptr); + + UIEditorWorkspaceSplitterDragCorrectionState correctionState = {}; + BeginUIEditorWorkspaceSplitterDragCorrection( + correctionState, + "root-split", + baselineSnapshot, + baselineLayout); + + XCEngine::UI::Editor::UIEditorWorkspaceLayoutSnapshot correctedSnapshot = {}; + const UIPoint pointerPosition( + splitter->splitterLayout.handleRect.x + splitter->splitterLayout.handleRect.width * 0.5f + 48.0f, + splitter->splitterLayout.handleRect.y + splitter->splitterLayout.handleRect.height * 0.5f); + + EXPECT_TRUE(TryBuildUIEditorWorkspaceSplitterDragCorrectedSnapshot( + correctionState, + pointerPosition, + true, + registry, + {}, + correctedSnapshot)); + EXPECT_GT(correctedSnapshot.workspace.root.splitRatio, baselineSnapshot.workspace.root.splitRatio); +} + +TEST(UIEditorWorkspaceSplitterDragCorrectionTest, RejectsMissingPointerPosition) { + const UIEditorPanelRegistry registry = BuildPanelRegistry(); + auto controller = BuildDefaultUIEditorWorkspaceController(registry, BuildWorkspace()); + const auto baselineSnapshot = controller.CaptureLayoutSnapshot(); + const auto baselineLayout = BuildUIEditorDockHostLayout( + UIRect(0.0f, 0.0f, 800.0f, 600.0f), + registry, + controller.GetWorkspace(), + controller.GetSession()); + + UIEditorWorkspaceSplitterDragCorrectionState correctionState = {}; + BeginUIEditorWorkspaceSplitterDragCorrection( + correctionState, + "root-split", + baselineSnapshot, + baselineLayout); + + XCEngine::UI::Editor::UIEditorWorkspaceLayoutSnapshot correctedSnapshot = {}; + EXPECT_FALSE(TryBuildUIEditorWorkspaceSplitterDragCorrectedSnapshot( + correctionState, + UIPoint(0.0f, 0.0f), + false, + registry, + {}, + correctedSnapshot)); +}