diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index 0da9d2e9..d4f07c86 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -283,7 +283,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) ) set(XCUI_EDITOR_APP_SUPPORT_SOURCES - app/Support/EmbeddedPngLoader.cpp + app/Internal/EmbeddedPngLoader.cpp ) set(XCUI_EDITOR_APP_PLATFORM_SOURCES diff --git a/new_editor/app/Bootstrap/ApplicationBootstrap.cpp b/new_editor/app/Bootstrap/ApplicationBootstrap.cpp index 04232ae9..cd94de95 100644 --- a/new_editor/app/Bootstrap/ApplicationBootstrap.cpp +++ b/new_editor/app/Bootstrap/ApplicationBootstrap.cpp @@ -6,8 +6,8 @@ #include "State/EditorContext.h" #include "Platform/Win32/EditorWindow.h" #include "Platform/Win32/EditorWindowManager.h" -#include "Support/EnvironmentFlags.h" -#include "Support/ExecutablePath.h" +#include "Internal/EnvironmentFlags.h" +#include "Internal/ExecutablePath.h" #ifndef XCUIEDITOR_REPO_ROOT #define XCUIEDITOR_REPO_ROOT "." @@ -16,7 +16,7 @@ namespace XCEngine::UI::Editor { using namespace BootstrapInternal; -using Support::GetExecutableDirectory; +using App::Internal::GetExecutableDirectory; bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { m_hInstance = hInstance; @@ -81,7 +81,7 @@ bool Application::Initialize(HINSTANCE hInstance, int nCmdShow) { } void Application::InitializeSmokeTestConfig() { - m_smokeTestEnabled = Support::IsEnvironmentFlagEnabled("XCUIEDITOR_SMOKE_TEST"); + m_smokeTestEnabled = App::Internal::IsEnvironmentFlagEnabled("XCUIEDITOR_SMOKE_TEST"); m_smokeTestFrameLimit = 0; m_smokeTestRenderedFrames = 0; m_smokeTestCloseRequested = false; @@ -91,7 +91,7 @@ void Application::InitializeSmokeTestConfig() { constexpr int kDefaultSmokeFrameLimit = 4; const std::optional frameLimit = - Support::TryGetEnvironmentInt("XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT"); + App::Internal::TryGetEnvironmentInt("XCUIEDITOR_SMOKE_TEST_FRAME_LIMIT"); m_smokeTestFrameLimit = frameLimit.has_value() && frameLimit.value() > 0 ? frameLimit.value() diff --git a/new_editor/app/Features/Project/ProjectBrowserModelInternal.cpp b/new_editor/app/Features/Project/ProjectBrowserModelInternal.cpp index b9119565..4fda0fd5 100644 --- a/new_editor/app/Features/Project/ProjectBrowserModelInternal.cpp +++ b/new_editor/app/Features/Project/ProjectBrowserModelInternal.cpp @@ -1,6 +1,6 @@ #include "ProjectBrowserModelInternal.h" -#include "Support/StringEncoding.h" +#include "Internal/StringEncoding.h" #include #include @@ -20,7 +20,7 @@ std::string ToLowerCopy(std::string value) { } std::string PathToUtf8String(const std::filesystem::path& path) { - return Support::WideToUtf8(path.native()); + return App::Internal::WideToUtf8(path.native()); } std::string NormalizePathSeparators(std::string value) { diff --git a/new_editor/app/Support/EmbeddedPngLoader.cpp b/new_editor/app/Internal/EmbeddedPngLoader.cpp similarity index 92% rename from new_editor/app/Support/EmbeddedPngLoader.cpp rename to new_editor/app/Internal/EmbeddedPngLoader.cpp index cc4c3d38..f3f58f90 100644 --- a/new_editor/app/Support/EmbeddedPngLoader.cpp +++ b/new_editor/app/Internal/EmbeddedPngLoader.cpp @@ -1,6 +1,6 @@ -#include "Support/EmbeddedPngLoader.h" +#include "Internal/EmbeddedPngLoader.h" -namespace XCEngine::UI::Editor::Support { +namespace XCEngine::UI::Editor::App::Internal { bool LoadEmbeddedPngBytes( UINT resourceId, @@ -60,4 +60,4 @@ bool LoadEmbeddedPngTexture( return renderer.LoadTextureFromMemory(bytes, byteCount, outTexture, outError); } -} // namespace XCEngine::UI::Editor::Support +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Support/EmbeddedPngLoader.h b/new_editor/app/Internal/EmbeddedPngLoader.h similarity index 82% rename from new_editor/app/Support/EmbeddedPngLoader.h rename to new_editor/app/Internal/EmbeddedPngLoader.h index 51be82ea..ea1f5ab9 100644 --- a/new_editor/app/Support/EmbeddedPngLoader.h +++ b/new_editor/app/Internal/EmbeddedPngLoader.h @@ -10,7 +10,7 @@ #include -namespace XCEngine::UI::Editor::Support { +namespace XCEngine::UI::Editor::App::Internal { bool LoadEmbeddedPngBytes( UINT resourceId, @@ -24,4 +24,4 @@ bool LoadEmbeddedPngTexture( ::XCEngine::UI::UITextureHandle& outTexture, std::string& outError); -} // namespace XCEngine::UI::Editor::Support +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Support/EnvironmentFlags.h b/new_editor/app/Internal/EnvironmentFlags.h similarity index 92% rename from new_editor/app/Support/EnvironmentFlags.h rename to new_editor/app/Internal/EnvironmentFlags.h index 1aabfd23..cb9c083c 100644 --- a/new_editor/app/Support/EnvironmentFlags.h +++ b/new_editor/app/Internal/EnvironmentFlags.h @@ -6,7 +6,7 @@ #include #include -namespace XCEngine::UI::Editor::Support { +namespace XCEngine::UI::Editor::App::Internal { inline bool IsEnvironmentFlagEnabled(const char* envName) { if (envName == nullptr || envName[0] == '\0') { @@ -51,4 +51,4 @@ inline std::optional TryGetEnvironmentInt(const char* envName) { return static_cast(parsed); } -} // namespace XCEngine::UI::Editor::Support +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Support/ExecutablePath.h b/new_editor/app/Internal/ExecutablePath.h similarity index 87% rename from new_editor/app/Support/ExecutablePath.h rename to new_editor/app/Internal/ExecutablePath.h index 4488b27c..be0244d5 100644 --- a/new_editor/app/Support/ExecutablePath.h +++ b/new_editor/app/Internal/ExecutablePath.h @@ -5,7 +5,7 @@ #include -namespace XCEngine::UI::Editor::Support { +namespace XCEngine::UI::Editor::App::Internal { inline std::filesystem::path GetExecutableDirectory() { std::vector buffer(MAX_PATH); @@ -28,4 +28,4 @@ inline std::filesystem::path GetExecutableDirectory() { } } -} // namespace XCEngine::UI::Editor::Support +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Support/StringEncoding.h b/new_editor/app/Internal/StringEncoding.h similarity index 89% rename from new_editor/app/Support/StringEncoding.h rename to new_editor/app/Internal/StringEncoding.h index a85abf91..7a1adfbe 100644 --- a/new_editor/app/Support/StringEncoding.h +++ b/new_editor/app/Internal/StringEncoding.h @@ -5,7 +5,7 @@ #include -namespace XCEngine::UI::Editor::Support { +namespace XCEngine::UI::Editor::App::Internal { inline std::string WideToUtf8(std::wstring_view text) { if (text.empty()) { @@ -43,4 +43,4 @@ inline std::string WideToUtf8(std::wstring_view text) { return utf8; } -} // namespace XCEngine::UI::Editor::Support +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Support/TextFormat.h b/new_editor/app/Internal/TextFormat.h similarity index 75% rename from new_editor/app/Support/TextFormat.h rename to new_editor/app/Internal/TextFormat.h index e2e23cd0..4c191f43 100644 --- a/new_editor/app/Support/TextFormat.h +++ b/new_editor/app/Internal/TextFormat.h @@ -2,7 +2,7 @@ #include -namespace XCEngine::UI::Editor::Support { +namespace XCEngine::UI::Editor::App::Internal { inline std::string TruncateText(const std::string& text, std::size_t maxLength) { if (text.size() <= maxLength) { @@ -16,4 +16,4 @@ inline std::string TruncateText(const std::string& text, std::size_t maxLength) return text.substr(0u, maxLength - 3u) + "..."; } -} // namespace XCEngine::UI::Editor::Support +} // namespace XCEngine::UI::Editor::App::Internal diff --git a/new_editor/app/Platform/Win32/EditorWindowPlatformInternal.h b/new_editor/app/Platform/Win32/EditorWindowPlatformInternal.h index 432d895f..ac11f8e2 100644 --- a/new_editor/app/Platform/Win32/EditorWindowPlatformInternal.h +++ b/new_editor/app/Platform/Win32/EditorWindowPlatformInternal.h @@ -1,13 +1,13 @@ #pragma once #include "Platform/Win32/EditorWindowConstants.h" -#include "Support/EmbeddedPngLoader.h" +#include "Internal/EmbeddedPngLoader.h" #include namespace XCEngine::UI::Editor::App::EditorWindowInternal { -using Support::LoadEmbeddedPngTexture; +using App::Internal::LoadEmbeddedPngTexture; UINT QuerySystemDpi(); UINT QueryWindowDpi(HWND hwnd); diff --git a/new_editor/app/Platform/Win32/EditorWindowRuntimeInternal.cpp b/new_editor/app/Platform/Win32/EditorWindowRuntimeInternal.cpp index 610559f9..0e4b4a3e 100644 --- a/new_editor/app/Platform/Win32/EditorWindowRuntimeInternal.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowRuntimeInternal.cpp @@ -18,7 +18,7 @@ void LogRuntimeTrace(std::string_view channel, std::string_view message) { } bool IsAutoCaptureOnStartupEnabled() { - return Support::IsEnvironmentFlagEnabled("XCUI_AUTO_CAPTURE_ON_STARTUP"); + return App::Internal::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 index 2eddc555..25ad6364 100644 --- a/new_editor/app/Platform/Win32/EditorWindowRuntimeInternal.h +++ b/new_editor/app/Platform/Win32/EditorWindowRuntimeInternal.h @@ -1,8 +1,8 @@ #pragma once -#include "Support/EnvironmentFlags.h" -#include "Support/StringEncoding.h" -#include "Support/TextFormat.h" +#include "Internal/EnvironmentFlags.h" +#include "Internal/StringEncoding.h" +#include "Internal/TextFormat.h" #include #include @@ -15,7 +15,7 @@ bool ResolveVerboseRuntimeTraceEnabled(); void LogRuntimeTrace(std::string_view channel, std::string_view message); bool IsAutoCaptureOnStartupEnabled(); -using Support::TruncateText; -using Support::WideToUtf8; +using App::Internal::TruncateText; +using App::Internal::WideToUtf8; } // namespace XCEngine::UI::Editor::App::EditorWindowInternal diff --git a/new_editor/app/Rendering/Assets/BuiltInIcons.cpp b/new_editor/app/Rendering/Assets/BuiltInIcons.cpp index c1bf08a8..230755f9 100644 --- a/new_editor/app/Rendering/Assets/BuiltInIcons.cpp +++ b/new_editor/app/Rendering/Assets/BuiltInIcons.cpp @@ -1,6 +1,6 @@ #include "BuiltInIcons.h" #include "Bootstrap/EditorResources.h" -#include "Support/EmbeddedPngLoader.h" +#include "Internal/EmbeddedPngLoader.h" #include @@ -29,7 +29,7 @@ void LoadEmbeddedIconTexture( ::XCEngine::UI::UITextureHandle& outTexture, std::ostringstream& errorStream) { std::string error = {}; - if (!Support::LoadEmbeddedPngTexture(renderer, resourceId, outTexture, error)) { + if (!Internal::LoadEmbeddedPngTexture(renderer, resourceId, outTexture, error)) { AppendLoadError(errorStream, label, error); } } diff --git a/new_editor/app/Rendering/Native/AutoScreenshot.cpp b/new_editor/app/Rendering/Native/AutoScreenshot.cpp index 8b0da9e9..4f583838 100644 --- a/new_editor/app/Rendering/Native/AutoScreenshot.cpp +++ b/new_editor/app/Rendering/Native/AutoScreenshot.cpp @@ -1,8 +1,8 @@ #include "AutoScreenshot.h" #include "NativeRenderer.h" -#include "Support/EnvironmentFlags.h" -#include "Support/ExecutablePath.h" +#include "Internal/EnvironmentFlags.h" +#include "Internal/ExecutablePath.h" #include #include @@ -14,7 +14,7 @@ namespace XCEngine::UI::Editor::Host { namespace { std::filesystem::path ResolveBuildCaptureRoot(const std::filesystem::path& requestedCaptureRoot) { - std::filesystem::path captureRoot = Support::GetExecutableDirectory() / "captures"; + std::filesystem::path captureRoot = App::Internal::GetExecutableDirectory() / "captures"; const std::filesystem::path scenarioPath = requestedCaptureRoot.parent_path().filename(); if (!scenarioPath.empty() && scenarioPath != "captures") { captureRoot /= scenarioPath; @@ -34,7 +34,7 @@ void AutoScreenshotController::Initialize(const std::filesystem::path& captureRo m_pendingReason.clear(); m_lastCaptureSummary = "Output: " + m_captureRoot.string(); m_lastCaptureError.clear(); - if (Support::IsEnvironmentFlagEnabled("XCUI_AUTO_CAPTURE_ON_STARTUP")) { + if (App::Internal::IsEnvironmentFlagEnabled("XCUI_AUTO_CAPTURE_ON_STARTUP")) { RequestCapture("startup"); } } diff --git a/new_editor/src/Docking/UIEditorDockHostInteraction.cpp b/new_editor/src/Docking/UIEditorDockHostInteraction.cpp index c10bb8a5..90034286 100644 --- a/new_editor/src/Docking/UIEditorDockHostInteraction.cpp +++ b/new_editor/src/Docking/UIEditorDockHostInteraction.cpp @@ -13,6 +13,7 @@ namespace { using ::XCEngine::UI::UIInputEvent; using ::XCEngine::UI::UIInputEventType; +using ::XCEngine::UI::UIPointerButton; using ::XCEngine::UI::Widgets::BeginUISplitterDrag; using ::XCEngine::UI::Widgets::EndUISplitterDrag; using ::XCEngine::UI::Widgets::UpdateUISplitterDrag; @@ -21,13 +22,16 @@ using Widgets::FindUIEditorDockHostSplitterLayout; using Widgets::UIEditorDockHostHitTarget; using Widgets::UIEditorDockHostHitTargetKind; -} // namespace +struct DockHostEventContext { + UIEditorDockHostHitTarget pointerHitTarget = {}; + Internal::DockHostTabStripEventResult tabStripResult = {}; + bool splitterOwnsPointerDown = false; +}; -UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( +Widgets::UIEditorDockHostLayout RebuildDockHostLayout( UIEditorDockHostInteractionState& state, UIEditorWorkspaceController& controller, const ::XCEngine::UI::UIRect& bounds, - const std::vector& inputEvents, const Widgets::UIEditorDockHostMetrics& metrics) { Internal::SyncDockHostTabStripVisualStates(state); Widgets::UIEditorDockHostLayout layout = BuildUIEditorDockHostLayout( @@ -47,45 +51,351 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( state.dockHostState, metrics); Internal::SyncHoverTarget(state, layout); + return layout; +} + +void UpdateDockHostPointerState( + UIEditorDockHostInteractionState& state, + const UIInputEvent& event) { + if (Internal::ShouldUseDockHostPointerPosition(event)) { + state.pointerPosition = event.position; + state.hasPointerPosition = true; + } else if (event.type == UIInputEventType::PointerLeave) { + state.hasPointerPosition = false; + } +} + +DockHostEventContext BuildDockHostEventContext( + UIEditorDockHostInteractionState& state, + const Widgets::UIEditorDockHostLayout& layout, + const UIInputEvent& event, + const Widgets::UIEditorDockHostMetrics& metrics) { + UpdateDockHostPointerState(state, event); + + DockHostEventContext context = {}; + context.pointerHitTarget = state.hasPointerPosition + ? HitTestUIEditorDockHost(layout, state.pointerPosition) + : UIEditorDockHostHitTarget {}; + context.splitterOwnsPointerDown = + event.type == UIInputEventType::PointerButtonDown && + event.pointerButton == UIPointerButton::Left && + context.pointerHitTarget.kind == UIEditorDockHostHitTargetKind::SplitterHandle; + if (!context.splitterOwnsPointerDown && + Internal::ShouldDispatchTabStripEvent(event, state.splitterDragState.active)) { + context.tabStripResult = Internal::ProcessTabStripEvent(state, layout, event, metrics); + } + + return context; +} + +void SyncDockPreviewForEvent( + UIEditorDockHostInteractionState& state, + const Widgets::UIEditorDockHostLayout& layout, + const UIInputEvent& event, + const DockHostEventContext& context) { + if (!context.tabStripResult.draggedTabId.empty() && + !state.activeTabDragNodeId.empty()) { + state.activeTabDragPanelId = context.tabStripResult.draggedTabId; + } + + if (!state.activeTabDragNodeId.empty() && + !state.activeTabDragPanelId.empty() && + !state.splitterDragState.active) { + Internal::SyncDockPreview(state, layout); + return; + } + + if (event.type == UIInputEventType::PointerLeave || + state.activeTabDragNodeId.empty()) { + state.dockHostState.dropPreview = {}; + } +} + +bool HasMeaningfulDockHostResult( + const UIEditorDockHostInteractionResult& result) { + return result.consumed || + result.commandExecuted || + result.layoutChanged || + result.detachRequested || + result.requestPointerCapture || + result.releasePointerCapture || + result.layoutResult.status != UIEditorWorkspaceLayoutOperationStatus::Rejected || + result.hitTarget.kind != UIEditorDockHostHitTargetKind::None || + !result.activeSplitterNodeId.empty() || + !result.detachedNodeId.empty() || + !result.detachedPanelId.empty(); +} + +UIEditorDockHostInteractionResult BeginEventResult( + const DockHostEventContext& context) { + UIEditorDockHostInteractionResult result = {}; + result.requestPointerCapture = context.tabStripResult.requestPointerCapture; + result.releasePointerCapture = context.tabStripResult.releasePointerCapture; + return result; +} + +void ExecutePanelCommand( + UIEditorWorkspaceController& controller, + UIEditorDockHostInteractionState& state, + UIEditorDockHostInteractionResult& result, + UIEditorWorkspaceCommandKind commandKind, + std::string panelId, + UIEditorDockHostHitTarget hitTarget) { + result.commandResult = Internal::DispatchPanelCommand( + controller, + commandKind, + std::move(panelId)); + result.commandExecuted = + result.commandResult.status != UIEditorWorkspaceCommandStatus::Rejected; + result.consumed = true; + result.hitTarget = std::move(hitTarget); + state.dockHostState.focused = true; +} + +bool TryFinishSplitterDrag( + UIEditorDockHostInteractionState& state, + UIEditorWorkspaceController& controller, + const DockHostEventContext& context, + UIEditorDockHostInteractionResult& result) { + if (!state.splitterDragState.active) { + return false; + } + + ::XCEngine::UI::Layout::UISplitterLayoutResult draggedLayout = {}; + if (UpdateUISplitterDrag( + state.splitterDragState, + state.pointerPosition, + draggedLayout)) { + result.layoutResult = Internal::ApplySplitRatio( + controller, + state.dockHostState.activeSplitterNodeId, + draggedLayout.splitRatio); + result.layoutChanged = + result.layoutResult.status == UIEditorWorkspaceLayoutOperationStatus::Changed; + } + EndUISplitterDrag(state.splitterDragState); + Internal::ClearAllTabStripTransientInteractions(state); + result.consumed = true; + result.releasePointerCapture = true; + result.releasePointerCapture = + result.releasePointerCapture || context.tabStripResult.releasePointerCapture; + result.activeSplitterNodeId = state.dockHostState.activeSplitterNodeId; + state.dockHostState.activeSplitterNodeId.clear(); + return true; +} + +bool TryCommitDropPreview( + UIEditorDockHostInteractionState& state, + UIEditorWorkspaceController& controller, + const Widgets::UIEditorDockHostLayout& layout, + const DockHostEventContext& context, + UIEditorDockHostInteractionResult& result) { + if (!state.dockHostState.dropPreview.visible || + state.activeTabDragNodeId.empty() || + state.activeTabDragPanelId.empty()) { + return false; + } + + const Widgets::UIEditorDockHostDropPreviewState preview = + state.dockHostState.dropPreview; + { + std::ostringstream trace = {}; + trace << "drop commit sourceNode=" << state.activeTabDragNodeId + << " panel=" << state.activeTabDragPanelId + << " targetNode=" << preview.targetNodeId + << " placement=" << static_cast(preview.placement) + << " insertion=" << preview.insertionIndex; + AppendUIEditorRuntimeTrace("dock", trace.str()); + } + + if (preview.placement == UIEditorWorkspaceDockPlacement::Center) { + std::size_t insertionIndex = preview.insertionIndex; + if (insertionIndex == Widgets::UIEditorTabStripInvalidIndex) { + if (const auto* targetTabStack = + Internal::FindTabStackLayoutByNodeId(layout, preview.targetNodeId); + targetTabStack != nullptr) { + insertionIndex = targetTabStack->items.size(); + } else { + insertionIndex = 0u; + } + } + + result.layoutResult = controller.MoveTabToStack( + state.activeTabDragNodeId, + state.activeTabDragPanelId, + preview.targetNodeId, + insertionIndex); + } else { + result.layoutResult = controller.DockTabRelative( + state.activeTabDragNodeId, + state.activeTabDragPanelId, + preview.targetNodeId, + preview.placement); + } + result.layoutChanged = + result.layoutResult.status == UIEditorWorkspaceLayoutOperationStatus::Changed; + AppendUIEditorRuntimeTrace( + "dock", + "drop result status=" + + std::string(GetUIEditorWorkspaceLayoutOperationStatusName( + result.layoutResult.status)) + + " message=" + result.layoutResult.message); + result.consumed = true; + result.hitTarget.nodeId = preview.targetNodeId; + result.releasePointerCapture = + result.releasePointerCapture || context.tabStripResult.releasePointerCapture; + Internal::ClearTabDockDragState(state); + state.dockHostState.focused = true; + return true; +} + +bool TryDetachDraggedTab( + UIEditorDockHostInteractionState& state, + const Widgets::UIEditorDockHostLayout& layout, + const DockHostEventContext& context, + UIEditorDockHostInteractionResult& result) { + if (state.activeTabDragNodeId.empty() || + state.activeTabDragPanelId.empty() || + !state.hasPointerPosition || + Internal::IsPointInsideRect(layout.bounds, state.pointerPosition)) { + return false; + } + + result.detachRequested = true; + result.consumed = true; + result.detachedNodeId = state.activeTabDragNodeId; + result.detachedPanelId = state.activeTabDragPanelId; + result.releasePointerCapture = + result.releasePointerCapture || context.tabStripResult.releasePointerCapture; + Internal::ClearTabDockDragState(state); + state.dockHostState.focused = true; + return true; +} + +void HandleHoveredActivation( + UIEditorDockHostInteractionState& state, + UIEditorWorkspaceController& controller, + const DockHostEventContext& context, + UIEditorDockHostInteractionResult& result) { + result.hitTarget = state.dockHostState.hoveredTarget; + switch (state.dockHostState.hoveredTarget.kind) { + case UIEditorDockHostHitTargetKind::PanelHeader: + case UIEditorDockHostHitTargetKind::PanelBody: + case UIEditorDockHostHitTargetKind::PanelFooter: + ExecutePanelCommand( + controller, + state, + result, + UIEditorWorkspaceCommandKind::ActivatePanel, + state.dockHostState.hoveredTarget.panelId, + state.dockHostState.hoveredTarget); + break; + + case UIEditorDockHostHitTargetKind::Tab: + case UIEditorDockHostHitTargetKind::TabStripBackground: + state.dockHostState.focused = true; + result.consumed = + context.tabStripResult.priority > 0 ? context.tabStripResult.consumed : true; + break; + + case UIEditorDockHostHitTargetKind::None: + default: + if (!Internal::HasFocusedTabStrip(state)) { + state.dockHostState.focused = false; + } + break; + } +} + +void HandlePointerButtonUp( + UIEditorDockHostInteractionState& state, + UIEditorWorkspaceController& controller, + const Widgets::UIEditorDockHostLayout& layout, + const DockHostEventContext& context, + UIEditorDockHostInteractionResult& result) { + if (TryFinishSplitterDrag(state, controller, context, result) || + TryCommitDropPreview(state, controller, layout, context, result) || + TryDetachDraggedTab(state, layout, context, result)) { + return; + } + + if (context.tabStripResult.dragEnded || context.tabStripResult.dragCanceled) { + result.consumed = context.tabStripResult.consumed; + result.hitTarget = context.tabStripResult.hitTarget; + Internal::ClearTabDockDragState(state); + state.dockHostState.focused = true; + return; + } + + if (context.tabStripResult.commandRequested && + !context.tabStripResult.panelId.empty()) { + ExecutePanelCommand( + controller, + state, + result, + context.tabStripResult.commandKind, + context.tabStripResult.panelId, + context.tabStripResult.hitTarget); + return; + } + + if (context.tabStripResult.priority > 0) { + result.consumed = context.tabStripResult.consumed; + result.hitTarget = context.tabStripResult.hitTarget; + if (result.hitTarget.kind != UIEditorDockHostHitTargetKind::None) { + state.dockHostState.focused = true; + return; + } + } + + HandleHoveredActivation(state, controller, context, result); +} + +void HandleKeyDown( + UIEditorDockHostInteractionState& state, + UIEditorWorkspaceController& controller, + const DockHostEventContext& context, + UIEditorDockHostInteractionResult& result) { + if (context.tabStripResult.dragCanceled) { + result.consumed = true; + result.hitTarget = context.tabStripResult.hitTarget; + Internal::ClearTabDockDragState(state); + state.dockHostState.focused = true; + return; + } + + if (context.tabStripResult.commandRequested && + !context.tabStripResult.panelId.empty()) { + ExecutePanelCommand( + controller, + state, + result, + context.tabStripResult.commandKind, + context.tabStripResult.panelId, + context.tabStripResult.hitTarget); + } else if (context.tabStripResult.priority > 0) { + result.consumed = context.tabStripResult.consumed; + result.hitTarget = context.tabStripResult.hitTarget; + } +} + +} // namespace + +UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( + UIEditorDockHostInteractionState& state, + UIEditorWorkspaceController& controller, + const ::XCEngine::UI::UIRect& bounds, + const std::vector& inputEvents, + const Widgets::UIEditorDockHostMetrics& metrics) { + Widgets::UIEditorDockHostLayout layout = + RebuildDockHostLayout(state, controller, bounds, metrics); UIEditorDockHostInteractionResult interactionResult = {}; for (const UIInputEvent& event : inputEvents) { - if (Internal::ShouldUseDockHostPointerPosition(event)) { - state.pointerPosition = event.position; - state.hasPointerPosition = true; - } else if (event.type == UIInputEventType::PointerLeave) { - state.hasPointerPosition = false; - } - - const UIEditorDockHostHitTarget pointerHitTarget = - state.hasPointerPosition - ? HitTestUIEditorDockHost(layout, state.pointerPosition) - : UIEditorDockHostHitTarget {}; - const bool splitterOwnsPointerDown = - event.type == UIInputEventType::PointerButtonDown && - event.pointerButton == ::XCEngine::UI::UIPointerButton::Left && - pointerHitTarget.kind == UIEditorDockHostHitTargetKind::SplitterHandle; - - UIEditorDockHostInteractionResult eventResult = {}; - const Internal::DockHostTabStripEventResult tabStripResult = - !splitterOwnsPointerDown && - Internal::ShouldDispatchTabStripEvent(event, state.splitterDragState.active) - ? Internal::ProcessTabStripEvent(state, layout, event, metrics) - : Internal::DockHostTabStripEventResult {}; - eventResult.requestPointerCapture = tabStripResult.requestPointerCapture; - eventResult.releasePointerCapture = tabStripResult.releasePointerCapture; - if (!tabStripResult.draggedTabId.empty() && - !state.activeTabDragNodeId.empty()) { - state.activeTabDragPanelId = tabStripResult.draggedTabId; - } - if (!state.activeTabDragNodeId.empty() && - !state.activeTabDragPanelId.empty() && - !state.splitterDragState.active) { - Internal::SyncDockPreview(state, layout); - } else if (event.type == UIInputEventType::PointerLeave || - state.activeTabDragNodeId.empty()) { - state.dockHostState.dropPreview = {}; - } + const DockHostEventContext context = + BuildDockHostEventContext(state, layout, event, metrics); + SyncDockPreviewForEvent(state, layout, event, context); + UIEditorDockHostInteractionResult eventResult = BeginEventResult(context); switch (event.type) { case UIInputEventType::FocusGained: @@ -102,11 +412,12 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( eventResult.consumed = true; eventResult.releasePointerCapture = true; } - if (!state.activeTabDragNodeId.empty() || tabStripResult.dragCanceled) { + if (!state.activeTabDragNodeId.empty() || context.tabStripResult.dragCanceled) { Internal::ClearTabDockDragState(state); eventResult.consumed = true; eventResult.releasePointerCapture = - eventResult.releasePointerCapture || tabStripResult.releasePointerCapture; + eventResult.releasePointerCapture || + context.tabStripResult.releasePointerCapture; } break; @@ -134,15 +445,17 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( eventResult.hitTarget.kind = UIEditorDockHostHitTargetKind::SplitterHandle; eventResult.hitTarget.nodeId = state.dockHostState.activeSplitterNodeId; } - } else if (tabStripResult.priority > 0) { - eventResult.consumed = tabStripResult.consumed || tabStripResult.dragStarted; - eventResult.hitTarget = tabStripResult.hitTarget; - if (tabStripResult.dragStarted) { - state.activeTabDragNodeId = tabStripResult.nodeId; - state.activeTabDragPanelId = tabStripResult.draggedTabId; + } else if (context.tabStripResult.priority > 0) { + eventResult.consumed = + context.tabStripResult.consumed || context.tabStripResult.dragStarted; + eventResult.hitTarget = context.tabStripResult.hitTarget; + if (context.tabStripResult.dragStarted) { + state.activeTabDragNodeId = context.tabStripResult.nodeId; + state.activeTabDragPanelId = context.tabStripResult.draggedTabId; Internal::SyncDockPreview(state, layout); } - if (tabStripResult.dragEnded || tabStripResult.dragCanceled) { + if (context.tabStripResult.dragEnded || + context.tabStripResult.dragCanceled) { Internal::ClearTabDockDragState(state); } if (eventResult.consumed || @@ -163,15 +476,15 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( break; case UIInputEventType::PointerButtonDown: - if (event.pointerButton != ::XCEngine::UI::UIPointerButton::Left) { + if (event.pointerButton != UIPointerButton::Left) { break; } - if (pointerHitTarget.kind == + if (context.pointerHitTarget.kind == UIEditorDockHostHitTargetKind::SplitterHandle) { const auto* splitter = FindUIEditorDockHostSplitterLayout( layout, - pointerHitTarget.nodeId); + context.pointerHitTarget.nodeId); if (splitter != nullptr && BeginUISplitterDrag( 1u, @@ -189,272 +502,53 @@ UIEditorDockHostInteractionFrame UpdateUIEditorDockHostInteraction( state.dockHostState.focused = true; eventResult.consumed = true; eventResult.requestPointerCapture = true; - eventResult.hitTarget = pointerHitTarget; + eventResult.hitTarget = context.pointerHitTarget; eventResult.activeSplitterNodeId = splitter->nodeId; } } else { - if (tabStripResult.priority > 0) { + if (context.tabStripResult.priority > 0) { state.dockHostState.focused = true; - eventResult.consumed = tabStripResult.consumed; - eventResult.hitTarget = tabStripResult.hitTarget; + eventResult.consumed = context.tabStripResult.consumed; + eventResult.hitTarget = context.tabStripResult.hitTarget; } else { state.dockHostState.focused = state.dockHostState.hoveredTarget.kind != UIEditorDockHostHitTargetKind::None; eventResult.consumed = state.dockHostState.focused; eventResult.hitTarget = - pointerHitTarget.kind != UIEditorDockHostHitTargetKind::None - ? pointerHitTarget + context.pointerHitTarget.kind != UIEditorDockHostHitTargetKind::None + ? context.pointerHitTarget : state.dockHostState.hoveredTarget; } } break; case UIInputEventType::PointerButtonUp: - if (event.pointerButton != ::XCEngine::UI::UIPointerButton::Left) { - break; - } - - if (state.splitterDragState.active) { - ::XCEngine::UI::Layout::UISplitterLayoutResult draggedLayout = {}; - if (UpdateUISplitterDrag( - state.splitterDragState, - state.pointerPosition, - draggedLayout)) { - eventResult.layoutResult = Internal::ApplySplitRatio( - controller, - state.dockHostState.activeSplitterNodeId, - draggedLayout.splitRatio); - eventResult.layoutChanged = - eventResult.layoutResult.status == - UIEditorWorkspaceLayoutOperationStatus::Changed; - } - EndUISplitterDrag(state.splitterDragState); - Internal::ClearAllTabStripTransientInteractions(state); - eventResult.consumed = true; - eventResult.releasePointerCapture = true; - eventResult.activeSplitterNodeId = state.dockHostState.activeSplitterNodeId; - state.dockHostState.activeSplitterNodeId.clear(); - break; - } - - if (state.dockHostState.dropPreview.visible && - !state.activeTabDragNodeId.empty() && - !state.activeTabDragPanelId.empty()) { - const Widgets::UIEditorDockHostDropPreviewState preview = - state.dockHostState.dropPreview; - { - std::ostringstream trace = {}; - trace << "drop commit sourceNode=" << state.activeTabDragNodeId - << " panel=" << state.activeTabDragPanelId - << " targetNode=" << preview.targetNodeId - << " placement=" << static_cast(preview.placement) - << " insertion=" << preview.insertionIndex; - AppendUIEditorRuntimeTrace("dock", trace.str()); - } - if (preview.placement == UIEditorWorkspaceDockPlacement::Center) { - std::size_t insertionIndex = preview.insertionIndex; - if (insertionIndex == Widgets::UIEditorTabStripInvalidIndex) { - if (const auto* targetTabStack = - Internal::FindTabStackLayoutByNodeId(layout, preview.targetNodeId); - targetTabStack != nullptr) { - insertionIndex = targetTabStack->items.size(); - } else { - insertionIndex = 0u; - } - } - - eventResult.layoutResult = controller.MoveTabToStack( - state.activeTabDragNodeId, - state.activeTabDragPanelId, - preview.targetNodeId, - insertionIndex); - } else { - eventResult.layoutResult = controller.DockTabRelative( - state.activeTabDragNodeId, - state.activeTabDragPanelId, - preview.targetNodeId, - preview.placement); - } - eventResult.layoutChanged = - eventResult.layoutResult.status == - UIEditorWorkspaceLayoutOperationStatus::Changed; - AppendUIEditorRuntimeTrace( - "dock", - "drop result status=" + - std::string(GetUIEditorWorkspaceLayoutOperationStatusName( - eventResult.layoutResult.status)) + - " message=" + eventResult.layoutResult.message); - eventResult.consumed = true; - eventResult.hitTarget.nodeId = preview.targetNodeId; - eventResult.releasePointerCapture = - eventResult.releasePointerCapture || tabStripResult.releasePointerCapture; - Internal::ClearTabDockDragState(state); - state.dockHostState.focused = true; - break; - } - - if (!state.activeTabDragNodeId.empty() && - !state.activeTabDragPanelId.empty() && - state.hasPointerPosition && - !Internal::IsPointInsideRect(layout.bounds, state.pointerPosition)) { - eventResult.detachRequested = true; - eventResult.consumed = true; - eventResult.detachedNodeId = state.activeTabDragNodeId; - eventResult.detachedPanelId = state.activeTabDragPanelId; - eventResult.releasePointerCapture = - eventResult.releasePointerCapture || tabStripResult.releasePointerCapture; - Internal::ClearTabDockDragState(state); - state.dockHostState.focused = true; - break; - } - - if (tabStripResult.dragEnded || tabStripResult.dragCanceled) { - eventResult.consumed = tabStripResult.consumed; - eventResult.hitTarget = tabStripResult.hitTarget; - Internal::ClearTabDockDragState(state); - state.dockHostState.focused = true; - break; - } - - if (tabStripResult.commandRequested && !tabStripResult.panelId.empty()) { - eventResult.commandResult = Internal::DispatchPanelCommand( - controller, - tabStripResult.commandKind, - tabStripResult.panelId); - eventResult.commandExecuted = - eventResult.commandResult.status != - UIEditorWorkspaceCommandStatus::Rejected; - eventResult.consumed = true; - eventResult.hitTarget = tabStripResult.hitTarget; - state.dockHostState.focused = true; - break; - } - - if (tabStripResult.priority > 0) { - eventResult.consumed = tabStripResult.consumed; - eventResult.hitTarget = tabStripResult.hitTarget; - if (eventResult.hitTarget.kind != UIEditorDockHostHitTargetKind::None) { - state.dockHostState.focused = true; - break; - } - } - - eventResult.hitTarget = state.dockHostState.hoveredTarget; - switch (state.dockHostState.hoveredTarget.kind) { - case UIEditorDockHostHitTargetKind::PanelHeader: - case UIEditorDockHostHitTargetKind::PanelBody: - case UIEditorDockHostHitTargetKind::PanelFooter: - eventResult.commandResult = Internal::DispatchPanelCommand( - controller, - UIEditorWorkspaceCommandKind::ActivatePanel, - state.dockHostState.hoveredTarget.panelId); - eventResult.commandExecuted = - eventResult.commandResult.status != - UIEditorWorkspaceCommandStatus::Rejected; - eventResult.consumed = true; - state.dockHostState.focused = true; - break; - - case UIEditorDockHostHitTargetKind::Tab: - case UIEditorDockHostHitTargetKind::TabStripBackground: - state.dockHostState.focused = true; - eventResult.consumed = - tabStripResult.priority > 0 ? tabStripResult.consumed : true; - break; - - case UIEditorDockHostHitTargetKind::None: - default: - if (!Internal::HasFocusedTabStrip(state)) { - state.dockHostState.focused = false; - } + if (event.pointerButton != UIPointerButton::Left) { break; } + HandlePointerButtonUp(state, controller, layout, context, eventResult); break; case UIInputEventType::KeyDown: - if (tabStripResult.dragCanceled) { - eventResult.consumed = true; - eventResult.hitTarget = tabStripResult.hitTarget; - Internal::ClearTabDockDragState(state); - state.dockHostState.focused = true; - break; - } - - if (tabStripResult.commandRequested && !tabStripResult.panelId.empty()) { - eventResult.commandResult = Internal::DispatchPanelCommand( - controller, - tabStripResult.commandKind, - tabStripResult.panelId); - eventResult.commandExecuted = - eventResult.commandResult.status != - UIEditorWorkspaceCommandStatus::Rejected; - eventResult.consumed = true; - eventResult.hitTarget = tabStripResult.hitTarget; - state.dockHostState.focused = true; - } else if (tabStripResult.priority > 0) { - eventResult.consumed = tabStripResult.consumed; - eventResult.hitTarget = tabStripResult.hitTarget; - } + HandleKeyDown(state, controller, context, eventResult); break; default: break; } - Internal::SyncDockHostTabStripVisualStates(state); - layout = BuildUIEditorDockHostLayout( - bounds, - controller.GetPanelRegistry(), - controller.GetWorkspace(), - controller.GetSession(), - state.dockHostState, - metrics); - Internal::PruneTabStripInteractionEntries(state, layout); - Internal::SyncDockHostTabStripVisualStates(state); - layout = BuildUIEditorDockHostLayout( - bounds, - controller.GetPanelRegistry(), - controller.GetWorkspace(), - controller.GetSession(), - state.dockHostState, - metrics); - Internal::SyncHoverTarget(state, layout); + layout = RebuildDockHostLayout(state, controller, bounds, metrics); if (eventResult.hitTarget.kind == UIEditorDockHostHitTargetKind::None) { eventResult.hitTarget = state.dockHostState.hoveredTarget; } - if (eventResult.consumed || - eventResult.commandExecuted || - eventResult.layoutChanged || - eventResult.requestPointerCapture || - eventResult.releasePointerCapture || - eventResult.layoutResult.status != - UIEditorWorkspaceLayoutOperationStatus::Rejected || - eventResult.hitTarget.kind != UIEditorDockHostHitTargetKind::None || - !eventResult.activeSplitterNodeId.empty()) { + if (HasMeaningfulDockHostResult(eventResult)) { interactionResult = std::move(eventResult); } } - Internal::SyncDockHostTabStripVisualStates(state); - layout = BuildUIEditorDockHostLayout( - bounds, - controller.GetPanelRegistry(), - controller.GetWorkspace(), - controller.GetSession(), - state.dockHostState, - metrics); - Internal::PruneTabStripInteractionEntries(state, layout); - Internal::SyncDockHostTabStripVisualStates(state); - layout = BuildUIEditorDockHostLayout( - bounds, - controller.GetPanelRegistry(), - controller.GetWorkspace(), - controller.GetSession(), - state.dockHostState, - metrics); - Internal::SyncHoverTarget(state, layout); + layout = RebuildDockHostLayout(state, controller, bounds, metrics); if (interactionResult.hitTarget.kind == UIEditorDockHostHitTargetKind::None) { interactionResult.hitTarget = state.dockHostState.hoveredTarget; }